/* * 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-2018 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.com/ * * This software is maintained by Dave Mielke . */ #include "prologue.h" #include #include #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; channelchannelCount; channel+=1) { for (int byte=0; byteblockAddress[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) { return pcmFlushBlock(device); } const NoteMethods pcmNoteMethods = { .construct = pcmConstruct, .destruct = pcmDestruct, .tone = pcmTone, .note = pcmNote, .flush = pcmFlush };