/* * 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-2021 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 . */ /* * Miscellaneous FM chip soundcard routines for BRLTTY. * Implemented by Dave Mielke . * Method gleaned from sccw, a morse code program written * by Steven J. Merrifield (VK3ESM). * Must compile with -O2. * Must link with -lm. * May compile with -DDEBUG_ADLIB. */ #include "prologue.h" #include "log.h" #include "async_wait.h" #include "timing.h" #include "ports.h" #include "fm.h" #include "fm_adlib.h" const unsigned char AL_channelOffsets[] = { /* 1 2 3 4 5 6 7 8 9 */ 0X00, 0X01, 0X02, 0X08, 0X09, 0X0A, 0X10, 0X11, 0X12 }; const unsigned char AL_channelCount = ARRAY_COUNT(AL_channelOffsets); static unsigned int portsEnabledCount = 0; int fmEnablePorts (int errorLevel) { if (portsEnabledCount) return 1; if (enablePorts(errorLevel, ALP_REGISTER, 1)) { if (enablePorts(errorLevel, ALP_DATA, 1)) { portsEnabledCount++; return 1; } disablePorts(ALP_REGISTER, 1); } return 0; } void fmDisablePorts (void) { if (!--portsEnabledCount) { disablePorts(ALP_REGISTER, 1); disablePorts(ALP_DATA, 1); } } unsigned char AL_readStatus (void) { return readPort1(ALP_STATUS); } static void AL_writeDelay (int delay) { while (delay-- > 0) { AL_readStatus(); } } void AL_writeRegister (int number, unsigned char data) { /* logMessage(LOG_DEBUG, "AL_writeRegister: %2.2X=%2.2X", number, data); */ writePort1(ALP_REGISTER, number); AL_writeDelay(6); writePort1(ALP_DATA, data); AL_writeDelay(35); } void fmResetCard (void) { int number; for (number=ALR_FIRST; number<=ALR_LAST; ++number) { AL_writeRegister(number, 0); } } static void AL_resetTimers (void) { AL_writeRegister(ALR_TCTL, AL_TCTL_T1MASK|AL_TCTL_T2MASK); AL_writeRegister(ALR_TCTL, AL_TCTL_RESET); } int fmTestCard (int errorLevel) { const unsigned char mask = AL_STAT_EXP | AL_STAT_EXP1 | AL_STAT_EXP2; AL_resetTimers(); if (!(AL_readStatus() & mask)) { unsigned char status; AL_writeRegister(ALR_T1DATA, 0xFF); AL_writeRegister(ALR_TCTL, AL_TCTL_T1START|AL_TCTL_T2MASK); { const TimeValue duration = { .seconds = 0, .nanoseconds = 80 * NSECS_PER_USEC }; accurateDelay(&duration); } status = AL_readStatus(); AL_resetTimers(); if ((status & mask) == (AL_STAT_EXP | AL_STAT_EXP1)) return 1; } logMessage(errorLevel, "FM synthesizer initialization failure"); return 0; } static void AL_evaluatePitch (int pitch, int *exponent, int *mantissa) { int shift = 21; while ((*mantissa = (int)((float)pitch * (1 << --shift) / 50000.0)) > 0X3FF); *exponent = 20 - shift; } static void AL_initiateTone (int channel, int exponent, int mantissa) { /* logMessage(LOG_DEBUG, "AL_initiateTone: %1.1X[%3.3X]", exponent, mantissa); */ AL_writeRegister(ALR_FREQUENCY_LSB(channel), (mantissa & 0XFF)); AL_writeRegister(ALR_FREQUENCY_MSB(channel), (((mantissa >> 8) & 0X3) | ((exponent & 0X7) << AL_OCTAVE_SHIFT) | AL_FREQ_ON)); } void fmStartTone (int channel, int pitch) { int exponent; int mantissa; AL_evaluatePitch(pitch, &exponent, &mantissa); /* logMessage(LOG_DEBUG, "fmStartTone: %d", pitch); */ AL_initiateTone(channel, exponent, mantissa); } void fmStopTone (int channel) { AL_writeRegister(ALR_FREQUENCY_MSB(channel), 0); } void fmPlayTone (int channel, unsigned int pitch, unsigned long int duration, unsigned int volume) { /* Play tone at fundamental frequency. */ AL_writeRegister(ALR_MODULATOR(ALG_EFFECT, channel), (AL_HARMONIC_1 << AL_HARMONIC_SHIFT)); /* Set the carrier to the fundamental frequency. */ AL_writeRegister(ALR_CARRIER(ALG_EFFECT, channel), (AL_HARMONIC_1 << AL_HARMONIC_SHIFT)); /* Set the volume (passed in as 0-100) */ AL_writeRegister(ALR_CARRIER(ALG_LEVEL, channel), ((AL_VOLUME_SOFT - ((AL_VOLUME_SOFT * volume) / 100)) << AL_VOLUME_SHIFT)); /* Set fast attack and slow decay. */ AL_writeRegister(ALR_CARRIER(ALG_ATTDEC, channel), ((AL_ATTACK_FAST << AL_ATTACK_SHIFT) | (AL_DECAY_SLOW << AL_DECAY_SHIFT))); /* Set soft sustain and fast release. */ AL_writeRegister(ALR_CARRIER(ALG_SUSREL, channel), ((AL_SUSTAIN_SOFT << AL_SUSTAIN_SHIFT) | (AL_RELEASE_FAST << AL_RELEASE_SHIFT))); fmStartTone(channel, pitch); asyncWait(duration); fmStopTone(channel); }