blob: f79c1a7d7c09e836c5098b5d9d3deacc8eaa5e65 [file] [log] [blame] [edit]
/*
* BRLTTY - A background process providing access to the console screen (when in
* text mode) for a blind person using a refreshable braille display.
*
* Copyright (C) 1995-2023 by The BRLTTY Developers.
*
* BRLTTY comes with ABSOLUTELY NO WARRANTY.
*
* This is free software, placed under the terms of the
* GNU Lesser General Public License, as published by the Free Software
* Foundation; either version 2.1 of the License, or (at your option) any
* later version. Please see the file LICENSE-LGPL for details.
*
* Web Page: http://brltty.app/
*
* This software is maintained by Dave Mielke <dave@mielke.cc>.
*/
#include "prologue.h"
#include <string.h>
#include <errno.h>
#include "prefs.h"
#include "log.h"
#include "pcm.h"
#include "notes.h"
char *opt_pcmDevice;
struct NoteDeviceStruct {
PcmDevice *pcm;
int blockSize;
int sampleRate;
int channelCount;
PcmAmplitudeFormat amplitudeFormat;
unsigned char *blockAddress;
int blockUsed;
PcmSampleMaker makeSample;
};
static int
pcmFlushBytes (NoteDevice *device) {
int ok = writePcmData(device->pcm, device->blockAddress, device->blockUsed);
if (ok) device->blockUsed = 0;
return ok;
}
static int
pcmWriteSample (NoteDevice *device, int16_t amplitude) {
PcmSample *sample = (PcmSample *)&device->blockAddress[device->blockUsed];
PcmSampleSize size = device->makeSample(sample, amplitude);
device->blockUsed += size;
for (int channel=1; channel<device->channelCount; channel+=1) {
for (int byte=0; byte<size; byte+=1) {
device->blockAddress[device->blockUsed++] = sample->bytes[byte];
}
}
if (device->blockUsed == device->blockSize) {
if (!pcmFlushBytes(device)) {
return 0;
}
}
return 1;
}
static int
pcmFlushBlock (NoteDevice *device) {
while (device->blockUsed)
if (!pcmWriteSample(device, 0))
return 0;
return 1;
}
static NoteDevice *
pcmConstruct (int errorLevel) {
NoteDevice *device;
if ((device = malloc(sizeof(*device)))) {
memset(device, 0, sizeof(*device));
if ((device->pcm = openPcmDevice(errorLevel, opt_pcmDevice))) {
device->blockSize = getPcmBlockSize(device->pcm);
device->sampleRate = getPcmSampleRate(device->pcm);
device->channelCount = getPcmChannelCount(device->pcm);
device->amplitudeFormat = getPcmAmplitudeFormat(device->pcm);
device->blockUsed = 0;
device->makeSample = getPcmSampleMaker(device->amplitudeFormat);
PcmSample sample;
PcmSampleSize sampleSize = device->makeSample(&sample, 0);
sampleSize *= device->channelCount;
if (sampleSize && device->blockSize &&
!(device->blockSize % sampleSize)) {
if ((device->blockAddress = malloc(device->blockSize))) {
logMessage(LOG_DEBUG, "PCM enabled: BlkSz:%d Rate:%d ChnCt:%d Fmt:%d",
device->blockSize, device->sampleRate, device->channelCount, device->amplitudeFormat);
return device;
} else {
logMallocError();
}
} else {
logMessage(LOG_ERR,
"PCM block size not multiple of sample size:"
" BlkSz:%d" " SmpSz:%u",
device->blockSize, sampleSize);
}
closePcmDevice(device->pcm);
}
free(device);
} else {
logMallocError();
}
logMessage(LOG_DEBUG, "PCM not available");
return NULL;
}
static void
pcmDestruct (NoteDevice *device) {
pcmFlushBlock(device);
free(device->blockAddress);
closePcmDevice(device->pcm);
free(device);
logMessage(LOG_DEBUG, "PCM disabled");
}
static int
pcmTone (NoteDevice *device, unsigned int duration, NoteFrequency frequency) {
int32_t sampleCount = device->sampleRate * duration / 1000;
logMessage(LOG_DEBUG, "tone: MSecs:%u SmpCt:%"PRId32 " Freq:%"PRIfreq,
duration, sampleCount, frequency);
if (frequency) {
/* A triangle waveform sounds nice, is lightweight, and avoids
* relying too much on floating-point performance and/or on
* expensive math functions like sin(). Considerations like
* these are especially important on PDAs without any FPU.
*/
/* We need to know the maximum amplitude based on the currently set
* volume percentage. This percentage then needs to be squared because
* we perceive loudness exponentially.
*/
const unsigned char fullVolume = 100;
const unsigned char currentVolume = MIN(fullVolume, prefs.pcmVolume);
const int32_t maximumAmplitude = INT16_MAX
* (currentVolume * currentVolume)
/ (fullVolume * fullVolume);
/* The calculations for triangle wave generation work out nicely and
* efficiently if we map a full period onto a 32-bit unsigned range.
*/
/* The two high-order bits specify which quarter wave a sample is for.
* 00 -> ascending from the negative peak to zero
* 01 -> ascending from zero to the positive peak
* 10 -> descending from the positive peak to zero
* 11 -> descending from zero to the negative peak
* The higher bit is 0 for the ascending segment and 1 for the
* descending segment. The lower bit is 0 when going from a peak to
* zero and 1 when going from zero to a peak.
*/
const uint8_t magnitudeWidth = 32 - 2;
/* The amplitude is 0 when the lower bit of the quarter wave indicator
* is 1 and the rest of the (magnitude) bits are all 0.
*/
const uint32_t zeroValue = UINT32_C(1) << magnitudeWidth;
/* We need to know how many steps to make from one sample to the next.
* stepsPerSample = stepsPerWave * wavesPerSecond / samplesPerSecond
* = stepsPerWave * frequency / sampleRate
* = stepsPerWave / sampleRate * frequency
*/
const uint32_t stepsPerSample = (NoteFrequency)UINT32_MAX
/ (NoteFrequency)device->sampleRate
* frequency;
/* The current value needs to be a signed value so that the >> operator
* will extend its sign bit. We start by initializing it to the value
* that corresponds to the start of the first logical quarter wave
* (the one that ascends from zero to the positive peak).
*/
int32_t currentValue = zeroValue;
/* Round the number of samples up to a whole number of periods:
* partialSteps = (sampleCount * stepsPerSample) % stepsPerWave
*
* With stepsPerWave being (1 << 32), we simply let the product
* overflow. The modulus corresponds to the remaining 32 low bits:
* partialSteps = (uint32_t)(sampleCount * stepsPerSample)
*
* missingSteps = stepsPerWave - partialSteps
* = (uint32_t) -partialSteps
* extraSamples = missingSteps / stepsPerSample
*/
sampleCount += (uint32_t)(sampleCount * -stepsPerSample) / stepsPerSample;
while (sampleCount > 0) {
/* Convert the current 32-bit unsigned linear value to a 31-bit
* triangular amplitude by inverting its low-order 31 bits if its
* high-order (sign) bit is set.
*/
int32_t amplitude = currentValue ^ (currentValue >> 31);
/* Convert the 31-bit amplitude from unsigned to signed. */
amplitude -= zeroValue;
/* Convert the amplitude's magnitude from 30 bits to 16 bits. */
amplitude >>= magnitudeWidth - 16;
/* Adjust the 17-bit signed amplitude (sign bit + 16-bit value) by
* the currently set volume (15-bit value):
* (16-bit value) * (15-bit value) + (sign bit) = 32-bit signed value
*/
amplitude *= maximumAmplitude;
/* Convert the signed amplitude from 32 bits to 16 bits. */
amplitude >>= 16;
if (!pcmWriteSample(device, amplitude)) break;
currentValue += stepsPerSample;
sampleCount -= 1;
}
} else {
/* generate silence */
while (sampleCount > 0) {
if (!pcmWriteSample(device, 0)) break;
sampleCount -= 1;
}
}
return (sampleCount > 0) ? 0 : 1;
}
static int
pcmNote (NoteDevice *device, unsigned int duration, unsigned char note) {
return pcmTone(device, duration, getNoteFrequency(note));
}
static int
pcmFlush (NoteDevice *device) {
int ok = pcmFlushBlock(device);
if (ok) pushPcmOutput(device->pcm);
return ok;
}
const NoteMethods pcmNoteMethods = {
.construct = pcmConstruct,
.destruct = pcmDestruct,
.tone = pcmTone,
.note = pcmNote,
.flush = pcmFlush
};