| /* |
| * 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>. |
| */ |
| |
| /* |
| * Miscellaneous FM chip soundcard routines for BRLTTY. |
| * Implemented by Dave Mielke <dave@mielke.cc>. |
| * Method gleaned from sccw, a morse code program written |
| * by Steven J. Merrifield <sjm@ee.latrobe.edu.au> (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); |
| } |