/* * Copyright (c) 1992 Microsoft Corporation */ /* * definition of interface functions to the adlib midi device type. * * These functions are called from midi.c when the kernel driver * has decreed that this is an adlib-compatible device. * * Geraint Davies, Dec 92 */ #include #include #include #include "driver.h" #include "adlib.h" /* * overview * * The FM synthesis chip consists of 18 operator cells or 'slots'. Each slot * can produce a sine wave modified by a number of parameters such * as frequency, output level and envelope shape (attack/decay/sustain * release). Slots are arranged in pairs, with one slot modulating * the sine wave of another to produce the harmonics desired for * a given instrument sound. Thus one pair of slots is a 'voice' and can * play one note at a time. * * In percussive mode (which we always use), there are 6 melodic voices * available, and one voice for the base drum. The remaining four slots * are used singly rather than in pairs to produce four further percussive * voices. The 6 melodic voices will be changed to any given timbre * as needed. The five percussive voices are fixed to particular instrument * timbres: bass drum, snare, tom-tom, hi-hat and cymbal. * * To play a note, we first find a free voice of the appropriate type. If * there are none free, we use the oldest busy one. We then set the * parameters for both operator slots from the patch table - this table gives * parameter settings for the different instrument timbres available. * We adjust the output level and frequency depending on the * note played and the velocity it was played with, and then switch on the * note. */ /* --- typedefs ------------------------------------------------- */ #define NUMVOICES (11) // number of voices we have #define NUMMELODIC (6) // number of melodic voices #define NUMPERCUSSIVE (5) // number of percussive voices #define MAXPATCH 180 // nr of patches (including drums) #define MAXVOLUME 0x7f #define NUMLOCPARAM 14 // number of loc params per slot #define FIRSTDRUMNOTE 35 #define LASTDRUMNOTE 81 #define NUMDRUMNOTES (LASTDRUMNOTE - FIRSTDRUMNOTE + 1) #define MAX_PITCH 0x3fff // maximum pitch bend value #define MID_PITCH 0x2000 // mid pitch bend value (no shift) #define PITCHRANGE 2 // total bend range +- 2 semitones #define NR_STEP_PITCH 25 // steps per half-tone for pitch bend #define MID_C 60 // MIDI standard mid C #define CHIP_MID_C 48 // sound chip mid C /* * to write to the device, we write a SYNTH_DATA port,data pair * to the kernel driver. */ #define SndOutput(p,d) MidiSendFM(p, d) /**************************************************************************** * * definitions of sound chip parameters */ // parameters of each voice #define prmKsl 0 // key scale level (KSL) - level controller #define prmMulti 1 // frequency multiplier (MULTI) - oscillator #define prmFeedBack 2 // modulation feedback (FB) - oscillator #define prmAttack 3 // attack rate (AR) - envelope generator #define prmSustain 4 // sustain level (SL) - envelope generator #define prmStaining 5 // sustaining sound (SS) - envelope generator #define prmDecay 6 // decay rate (DR) - envelope generator #define prmRelease 7 // release rate (RR) - envelope generator #define prmLevel 8 // output level (OL) - level controller #define prmAm 9 // amplitude vibrato (AM) - level controller #define prmVib 10 // frequency vibrator (VIB) - oscillator #define prmKsr 11 // envelope scaling (KSR) - envelope generator #define prmFm 12 // fm=0, additive=1 (FM) (operator 0 only) #define prmWaveSel 13 // wave select // global parameters #define prmAmDepth 14 #define prmVibDepth 15 #define prmNoteSel 16 #define prmPercussion 17 // percussive voice numbers (0-5 are melodic voices, 2 op): #define BD 6 // bass drum (2 op) #define SD 7 // snare drum (1 op) #define TOM 8 // tom-tom (1 op) #define CYMB 9 // cymbal (1 op) #define HIHAT 10 // hi-hat (1 op) // In percussive mode, the last 4 voices (SD TOM HH CYMB) are created // using melodic voices 7 & 8. A noise generator uses channels 7 & 8 // frequency information for creating rhythm instruments. Best results // are obtained by setting TOM two octaves below mid C and SD 7 half-tones // above TOM. In this implementation, only the TOM pitch may vary, with the // SD always set 7 half-tones above. #define TOM_PITCH 24 // best frequency, in range of 0 to 95 #define TOM_TO_SD 7 // 7 half-tones between voice 7 & 8 #define SD_PITCH (TOM_PITCH + TOM_TO_SD) /**************************************************************************** * * bank file support * ***************************************************************************/ #define BANK_SIG_LEN 6 #define BANK_FILLER_SIZE 8 typedef BYTE *HPBYTE; typedef BYTE NEAR * NPBYTE; typedef WORD NEAR * NPWORD; // instrument bank file header typedef struct { char majorVersion; char minorVersion; char sig[BANK_SIG_LEN]; // signature: "ADLIB-" WORD nrDefined; // number of list entries used WORD nrEntry; // number of total entries in list long offsetIndex; // offset of start of list of names long offsetTimbre; // offset of start of data char filler[BANK_FILLER_SIZE]; // must be zero } BANKHDR, NEAR *NPBANKHDR, FAR *LPBANKHDR; // all the parameters for one slot typedef struct { BYTE ksl; // KSL BYTE freqMult; // MULTI BYTE feedBack; // FB BYTE attack; // AR BYTE sustLevel; // SL BYTE sustain; // SS BYTE decay; // DR BYTE release; // RR BYTE output; // OL BYTE am; // AM BYTE vib; // VIB BYTE ksr; // KSR BYTE fm; // FM } OPERATOR, NEAR *NPOPERATOR, FAR *LPOPERATOR; // definition of a particular instrument sound or timbre - // one of these per patch typedef struct { BYTE mode; // 0 = melodic, 1 = percussive BYTE percVoice; // if mode == 1, voice number to be used OPERATOR op0; // params for slot 0 OPERATOR op1; // params for slot 1 BYTE wave0; // waveform for operator 0 BYTE wave1; // waveform for operator 1 } TIMBRE, NEAR *NPTIMBRE, FAR *LPTIMBRE; typedef struct drumpatch_tag { BYTE patch; // the patch to use BYTE note; // the note to play } DRUMPATCH; // format of drumkit.dat file typedef struct drumfilepatch_tag { BYTE key; // the key to map BYTE patch; // the patch to use BYTE note; // the note to play } DRUMFILEPATCH, *PDRUMFILEPATCH; typedef struct _VOICE { BYTE alloc; // is voice allocated? BYTE note; // note that is currently being played BYTE channel; // channel that it is being played on BYTE volume; // current volume setting of voice DWORD dwTimeStamp; // time voice was allocated } VOICE; #define GetLocPrm(slot_num, prm) ((WORD)paramSlot[slot_num][prm]) /* --- module data ---------------------------------------------- */ // this table gives the offset of each slot within the chip. BYTE offsetSlot[] = { 0, 1, 2, 3, 4, 5, 8, 9, 10, 11, 12, 13, 16, 17, 18, 19, 20, 21 }; static BYTE percMasks[] = { 0x10, 0x08, 0x04, 0x02, 0x01 }; // voice number associated with each slot (melodic mode only) static BYTE voiceSlot[] = { 0, 1, 2, 0, 1, 2, 3, 4, 5, 3, 4, 5, 6, 7, 8, 6, 7, 8, }; // slot numbers for percussive voices (0 indicates that there is only one slot) static BYTE slotPerc[][2] = { {12, 15}, // Bass Drum {16, 0}, // SD {14, 0}, // TOM {17, 0}, // TOP-CYM {13, 0} // HH }; // slot numbers as a function of the voice and the operator (melodic only) static BYTE slotVoice[][2] = { {0, 3}, // voice 0 {1, 4}, // 1 {2, 5}, // 2 {6, 9}, // 3 {7, 10}, // 4 {8, 11}, // 5 {12, 15}, // 6 {13, 16}, // 7 {14, 17} // 8 }; // this table indicates if the slot is a modulator (0) or a carrier (1). BYTE operSlot[] = { 0, 0, 0, // 1 2 3 1, 1, 1, // 4 5 6 0, 0, 0, // 7 8 9 1, 1, 1, // 10 11 12 0, 0, 0, // 13 14 15 1, 1, 1, // 16 17 18 }; // this array adjusts the octave of a note for certain patches. static char patchKeyOffset[] = { 0, -12, 12, 0, 0, 12, -12, 0, 0, -24, // 0 - 9 0, 0, 0, 0, 0, 0, 0, 0, -12, 0, // 10 - 19 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 20 - 29 0, 0, 12, 12, 12, 0, 0, 12, 12, 0, // 30 - 39 -12, -12, 0, 12, -12, -12, 0, 12, 0, 0, // 40 - 49 -12, 0, 0, 0, 12, 12, 0, 0, 12, 0, // 50 - 59 0, 0, 12, 0, 0, 0, 12, 12, 0, 12, // 60 - 69 0, 0, -12, 0, -12, -12, 0, 0, -12, -12, // 70 - 79 0, 0, 0, 0, 0, -12, -19, 0, 0, -12, // 80 - 89 0, 0, 0, 0, 0, 0, -31, -12, 0, 12, // 90 - 99 12, 12, 12, 0, 12, 0, 12, 0, 0, 0, // 100 - 109 0, 12, 0, 0, 0, 0, 12, 12, 12, 0, // 110 - 119 0, 0, 0, 0, -24, -36, 0, 0}; // 120 - 127 static BYTE loudervol[128] = { 0, 0, 65, 65, 66, 66, 67, 67, // 0 - 7 68, 68, 69, 69, 70, 70, 71, 71, // 8 - 15 72, 72, 73, 73, 74, 74, 75, 75, // 16 - 23 76, 76, 77, 77, 78, 78, 79, 79, // 24 - 31 80, 80, 81, 81, 82, 82, 83, 83, // 32 - 39 84, 84, 85, 85, 86, 86, 87, 87, // 40 - 47 88, 88, 89, 89, 90, 90, 91, 91, // 48 - 55 92, 92, 93, 93, 94, 94, 95, 95, // 56 - 63 96, 96, 97, 97, 98, 98, 99, 99, // 64 - 71 100, 100, 101, 101, 102, 102, 103, 103, // 72 - 79 104, 104, 105, 105, 106, 106, 107, 107, // 80 - 87 108, 108, 109, 109, 110, 110, 111, 111, // 88 - 95 112, 112, 113, 113, 114, 114, 115, 115, // 96 - 103 116, 116, 117, 117, 118, 118, 119, 119, // 104 - 111 120, 120, 121, 121, 122, 122, 123, 123, // 112 - 119 124, 124, 125, 125, 126, 126, 127, 127}; // 120 - 127 /* * attenuation setting for each slot - combination * of the channel attenuation for this channel, and the * velocity (converted to attenuation). Combine with * global attenuation and timbre output attenuation before * writing to device. */ WORD slotAtten[18]; // vol-control attenuation of slots WORD wSynthAtten; // overall volume setting BYTE gbChanAtten[NUMCHANNELS]; // attenuation for each channel VOICE voices[11]; // 9 voices if melodic mode or 11 if percussive BYTE voiceKeyOn[11]; // keyOn bit for each voice (implicit 0 init) BYTE paramSlot[18][NUMLOCPARAM]; // all the parameters of slots... BYTE percBits; // control bits of percussive voices WORD fNumNotes[NR_STEP_PITCH][12]; PWORD fNumFreqPtr[11]; // lines of fNumNotes table (one per voice) int halfToneOffset[11]; // one per voice BYTE notePitch[11]; // pitch value for each voice (implicit 0 init) // patches - parameters for different instrument timbres TIMBRE patches[MAXPATCH]; // patch data DRUMPATCH drumpatch[NUMDRUMNOTES]; // drum kit data DWORD dwAge = 0; // voice relative age /* --- internal functions --------------------------------------- */ /* -- initialisation --- */ /**************************************************************************** * @doc INTERNAL * * @api int | LoadPatches | Reads the patch set from the BANK resource and * builds the

array. * * @rdesc Returns the number of patches loaded, or 0 if an error occurs. ***************************************************************************/ static BYTE PatchRes[] = { #include "adlib.dat" }; int LoadPatches(void) { #ifdef WIN16 HANDLE hResInfo; HANDLE hResData; #endif LPSTR lpRes; int iPatches; DWORD dwOffset; DWORD dwResSize; LPTIMBRE lpBankTimbre; LPTIMBRE lpPatchTimbre; LPBANKHDR lpBankHdr; // find resource and get its size #ifdef WIN16 hResInfo = FindResource(ghInstance, MAKEINTRESOURCE(DEFAULTBANK), MAKEINTRESOURCE(RT_BANK)); if (!hResInfo) { D1("Default bank resource not found"); return 0; } dwResSize = (DWORD)SizeofResource(ghInstance, hResInfo); // load and lock resource hResData = LoadResource(ghInstance, hResInfo); if (!hResData) { D1("Bank resource not loaded"); return 0; } lpRes = LockResource(hResData); if (!lpRes) { D1("Bank resource not locked"); return 0; } #else lpRes = PatchRes; dwResSize = sizeof(PatchRes); #endif // read the bank resource, loading patches as we find them D2("loading patches"); lpBankHdr = (LPBANKHDR)lpRes; dwOffset = lpBankHdr->offsetTimbre; // point to first one for (iPatches = 0; iPatches < MAXPATCH; iPatches++) { lpBankTimbre = (LPTIMBRE)(lpRes + dwOffset); lpPatchTimbre = &patches[iPatches]; *lpPatchTimbre = *lpBankTimbre; dwOffset += sizeof(TIMBRE); if (dwOffset + sizeof(TIMBRE) > dwResSize) { D1("Attempt to read past end of bank resource"); break; } } #ifdef WIN16 UnlockResource(hResData); FreeResource(hResData); #endif return iPatches; } /**************************************************************************** * @doc INTERNAL * * @api int | LoadDrumPatches | Reads the drum kit patch set from the * DRUMKIT resource and builds the

array. * * @comm Each entry of the array (representing a key number * from the "drum patch") consists of a patch number and note number * from some other patch. * * @rdesc Returns the number of patches loaded, or 0 if an error occurs. ***************************************************************************/ static BYTE DrumRes[] = { #include "drumkit.dat" }; int LoadDrumPatches(void) { #ifdef WIN16 HANDLE hResInfo; HANDLE hResData; #endif LPSTR lpRes; int iPatches; int key; DWORD dwOffset; DWORD dwResSize; PDRUMFILEPATCH lpResPatch; #ifdef WIN16 // find resource and get its size hResInfo = FindResource(ghInstance, MAKEINTRESOURCE(DEFAULTDRUMKIT), MAKEINTRESOURCE(RT_DRUMKIT)); if (!hResInfo) { D1("Default drum resource not found"); return 0; } dwResSize = (DWORD)SizeofResource(ghInstance, hResInfo); // load and lock resource hResData = LoadResource(ghInstance, hResInfo); if (!hResData) { D1("Drum resource not loaded"); return 0; } lpRes = LockResource(hResData); if (!lpRes) { D1("Drum resource not locked"); return 0; } #else lpRes = DrumRes; dwResSize = sizeof(DrumRes); #endif // read the drum resource, loading patches as we find them D2("reading drum data"); dwOffset = 0; for (iPatches = 0; iPatches < NUMDRUMNOTES; iPatches++) { lpResPatch = (PDRUMFILEPATCH)(lpRes + dwOffset); key = lpResPatch->key; if ((key >= FIRSTDRUMNOTE) && (key <= LASTDRUMNOTE)) { drumpatch[key - FIRSTDRUMNOTE].patch = lpResPatch->patch; drumpatch[key - FIRSTDRUMNOTE].note = lpResPatch->note; } else { D1("Drum patch key out of range"); } dwOffset += sizeof(DRUMFILEPATCH); if (dwOffset > dwResSize) { D1("Attempt to read past end of drum resource"); break; } } #ifdef WIN16 UnlockResource(hResData); FreeResource(hResData); #endif return iPatches; } /**************************************************************************** * @doc INTERNAL * * @api long | CalcPremFNum | Calculates some magic number that is used in * setting the values in the

table. * * @parm int | numDeltaDemiTon | Numerator (-100 to +100). * * @parm int | denDeltaDemiTon | Denominator (1 to 100). * * @comm If the numerator (numDeltaDemiTon) is positive, the frequency is * increased; if negative, it is decreased. The function calculates: * f8 = Fb(1 + 0.06 num /den) (where Fb = 26044 * 2 / 25) * fNum8 = f8 * 65536 * 72 / 3.58e6 * * @rdesc Returns fNum8, which is the binary value of the frequency 260.44 (C) * shifted by +/-

/

* 8. ***************************************************************************/ long NEAR CalcPremFNum(int numDeltaDemiTon, int denDeltaDemiTon) { long f8; long fNum8; long d100; d100 = denDeltaDemiTon * 100; f8 = (d100 + 6 * numDeltaDemiTon) * (26044L * 2L); f8 /= d100 * 25; fNum8 = f8 * 16384; fNum8 *= 9L; fNum8 /= 179L * 625L; return fNum8; } /**************************************************************************** * @doc INTERNAL * * @api void | SetFNum | Initializes a line in the frequency table with * shifted frequency values. The values are shifted a fraction (num/den) * of a half-tone. * * @parm NPWORD | fNumVec | The line from the frequency table. * * @parm int | num | Numerator. * * @parm int | den | Denominator. * * @xref CalcPremFNum * * @rdesc There is no return value. ***************************************************************************/ void NEAR SetFNum(NPWORD fNumVec, int num, int den) { int i; long val; *fNumVec++ = (WORD)((4 + (val = CalcPremFNum(num, den))) >> 3); for (i = 1; i < 12; i++) { val *= 106; *fNumVec++ = (WORD)((4 + (val /= 100)) >> 3); } } /**************************************************************************** * @doc INTERNAL * * @api void | InitFNums | Initializes all lines of the frequency table * (the

array). Each line represents 12 half-tones shifted * by (n / NR_STEP_PITCH), where 'n' is the line number and ranges from * 1 to NR_STEP_PITCH. * * @rdesc There is no return value. ***************************************************************************/ void NEAR InitFNums(void) { WORD i; WORD num; // numerator WORD numStep; // step value for numerator WORD row; // row in the frequency table // calculate each row in the fNumNotes table numStep = 100 / NR_STEP_PITCH; for (num = row = 0; row < NR_STEP_PITCH; row++, num += numStep) SetFNum(fNumNotes[row], num, 100); // fNumFreqPtr has an element for each voice, pointing to the // appropriate row in the fNumNotes table. They're all initialized // to the first row, which represents no pitch shift. for (i = 0; i < 11; i++) { fNumFreqPtr[i] = fNumNotes[0]; halfToneOffset[i] = 0; } } /**************************************************************************** * @doc INTERNAL * * @api void | SoundChut | Sets the frequency of voice

to 0 Hz. * * @parm BYTE | voice | Specifies which voice to set. * * @rdesc There is no return value. ***************************************************************************/ void NEAR SoundChut(BYTE voice) { SndOutput((BYTE)(0xA0 | voice), 0); SndOutput((BYTE)(0xB0 | voice), 0); } /* --- write parameters to chip ------------------------*/ /* * switch chip to rhythm (percussive) mode, set the amplitude and * vibrato depth and switch on any percussion voices that are on */ void SndSAmVibRhythm(void) { // we always set amdepth, vibdepth to 0 and perc mode on // t1 = (BYTE)(amDepth ? 0x80 : 0); // t1 |= vibDepth ? 0x40 : 0; // t1 |= (percussionmode ? 0x20 : 0); SndOutput((BYTE)0xBD, (BYTE)(0x20|percBits)); } /**************************************************************************** * @doc INTERNAL * * @api void | SndSNoteSel | Sets the NoteSel value. * * @rdesc There is no return value. ***************************************************************************/ void SndSNoteSel(BOOL bNoteSel) { SndOutput(0x08, (BYTE)(bNoteSel ? 64 : 0)); } /**************************************************************************** * @doc INTERNAL * * @api void | SndSKslLevel | Sets the KSL and LEVEL values. * * @parm BYTE | slot | Specifies which slot to set. * * @rdesc There is no return value. ***************************************************************************/ void SndSKslLevel(BYTE slot) { WORD t1; t1 = GetLocPrm(slot, prmLevel) & 0x3f; t1 += slotAtten[slot]; t1 = min (t1, 0x3f); t1 |= GetLocPrm(slot, prmKsl) << 6; SndOutput((BYTE)(0x40 | offsetSlot[slot]), (BYTE) t1); } /**************************************************************************** * @doc INTERNAL * * @api void | SndSAVEK | Sets the AM, VIB, EG-TYP (sustaining), KSR, and * MULTI values. * * @parm BYTE | slot | Specifies which slot to set. * * @rdesc There is no return value. ***************************************************************************/ void SndSAVEK(BYTE slot) { BYTE t1; t1 = (BYTE)(GetLocPrm(slot, prmAm) ? 0x80 : 0); t1 += GetLocPrm(slot, prmVib) ? 0x40 : 0; t1 += GetLocPrm(slot, prmStaining) ? 0x20 : 0; t1 += GetLocPrm(slot, prmKsr) ? 0x10 : 0; t1 += GetLocPrm(slot, prmMulti) & 0xf; SndOutput((BYTE)(0x20 | offsetSlot[slot]), t1); } /**************************************************************************** * @doc INTERNAL * * @api void | SndSFeedFm | Sets the FEEDBACK and FM (connection) values. * Applicable only to operator 0 for melodic voices. * * @parm BYTE | slot | Specifies which slot to set. * * @rdesc There is no return value. ***************************************************************************/ void SndSFeedFm(BYTE slot) { BYTE t1; if (operSlot[slot]) return; t1 = (BYTE)(GetLocPrm(slot, prmFeedBack) << 1); t1 |= GetLocPrm(slot, prmFm) ? 0 : 1; SndOutput((BYTE)(0xC0 | voiceSlot[slot]), t1); } /**************************************************************************** * @doc INTERNAL * * @api void | SndSAttDecay | Sets the ATTACK and DECAY values. * * @parm BYTE | slot | Specifies which slot to set. * * @rdesc There is no return value. ***************************************************************************/ void SndSAttDecay(BYTE slot) { BYTE t1; t1 = (BYTE)(GetLocPrm(slot, prmAttack) << 4); t1 |= GetLocPrm(slot, prmDecay) & 0xf; SndOutput((BYTE)(0x60 | offsetSlot[slot]), t1); } /**************************************************************************** * @doc INTERNAL * * @api void | SndSSusRelease | Sets the SUSTAIN and RELEASE values. * * @parm BYTE | slot | Specifies which slot to set. * * @rdesc There is no return value. ***************************************************************************/ void SndSSusRelease(BYTE slot) { BYTE t1; t1 = (BYTE)(GetLocPrm(slot, prmSustain) << 4); t1 |= GetLocPrm(slot, prmRelease) & 0xf; SndOutput((BYTE)(0x80 | offsetSlot[slot]), t1); } /**************************************************************************** * @doc INTERNAL * * @api void | SndWaveSelect | Sets the wave-select parameter. * * @parm BYTE | slot | Specifies which slot to set. * * @rdesc There is no return value. ***************************************************************************/ void SndWaveSelect(BYTE slot) { BYTE wave; wave = (BYTE)(GetLocPrm(slot, prmWaveSel) & 0x03); SndOutput((BYTE)(0xE0 | offsetSlot[slot]), wave); } /**************************************************************************** * @doc INTERNAL * * @api void | SndSetAllPrm | Transfers all the parameters from slot

* to the chip. * * @parm BYTE | slot | Specifies which slot to set. * * @rdesc There is no return value. ***************************************************************************/ void SndSetAllPrm(BYTE slot) { /* global am-depth and vib-depth settings */ SndSAmVibRhythm(); /* note sel is always false */ SndSNoteSel(FALSE); /* slot-specific parameters */ /* initialise volume to minimum to avoid clicks if the note is * off but still playing (during decay phase). * * only applies to carrier slots. */ if (operSlot[slot]) { /* its a carrier slot */ slotAtten[slot] = 0x3f; // max attenuation } SndSKslLevel(slot); SndSFeedFm(slot); SndSAttDecay(slot); SndSSusRelease(slot); SndSAVEK(slot); SndWaveSelect(slot); } /* --- setting slot parameters ------------------ */ /**************************************************************************** * @doc INTERNAL * * @api void | SetSlotParam | Sets the 14 parameters (13 in

, * 1 in

) of slot

. Updates both the parameter array * and the chip. * * @parm BYTE | slot | Specifies which slot to set. * * @parm NPBYTE | param | Pointer to the new parameter array. * * @parm BYTE | waveSel | The new waveSel value. * * @rdesc There is no return value. ***************************************************************************/ void SetSlotParam(BYTE slot, NPOPERATOR pOper, BYTE waveSel) { LPBYTE ptr; ptr = ¶mSlot[slot][0]; *ptr++ = pOper->ksl; *ptr++ = pOper->freqMult; *ptr++ = pOper->feedBack; *ptr++ = pOper->attack; *ptr++ = pOper->sustLevel; *ptr++ = pOper->sustain; *ptr++ = pOper->decay; *ptr++ = pOper->release; *ptr++ = pOper->output; *ptr++ = pOper->am; *ptr++ = pOper->vib; *ptr++ = pOper->ksr; *ptr++ = pOper->fm; *ptr = waveSel & 0x3; // set default volume settings slotAtten[slot] = 0; SndSetAllPrm(slot); } /* * set this voice up to the correct parameters for a timbre * copy the parameters to the slot array and write them to the * chip * */ void SetVoiceTimbre(BYTE voice, NPTIMBRE pTimbre) { if (voice < BD) { // melodic only SetSlotParam(slotVoice[voice][0], &pTimbre->op0, pTimbre->wave0); SetSlotParam(slotVoice[voice][1], &pTimbre->op1, pTimbre->wave1); } else if (voice == BD) { // bass drum SetSlotParam(slotPerc[0][0], &pTimbre->op0, pTimbre->wave0); SetSlotParam(slotPerc[0][1], &pTimbre->op1, pTimbre->wave1); } else { // percussion, 1 slot SetSlotParam(slotPerc[voice - BD][0], &pTimbre->op0, pTimbre->wave0); } } /* --- frequency calculation -------------------- */ /* convert the given pitch (0 .. 95) into a frequency * and write to the chip. * * this will turn the note on if keyon is true. */ VOID SetFreq(BYTE voice, BYTE pitch, BYTE keyOn) { WORD FNum; BYTE t1; // remember the keyon and pitch of the voice voiceKeyOn[voice] = keyOn; notePitch[voice] = pitch; pitch += halfToneOffset[voice]; if (pitch > 95) pitch = 95; // get the FNum for the voice FNum = * (fNumFreqPtr[voice] + pitch % 12); // output the FNum, KeyOn and Block values SndOutput((BYTE)(0xA0 | voice), (BYTE)FNum); // FNum bits 0 - 7 (D0 - D7) t1 = (BYTE)(keyOn ? 32 : 0); // Key On (D5) t1 += (pitch / 12 << 2); // Block (D2 - D4) t1 += (0x3 & (FNum >> 8)); // FNum bits 8 - 9 (D0 - D1) SndOutput((BYTE)(0xB0 | voice), t1); } /**************************************************************************** * @doc INTERNAL * * @api void | ChangePitch | This routine sets the and * arrays. These two global variables are used to * determine the frequency variation to use when a note is played. * * @parm BYTE | voice | Specifies which voice to use. * * @parm WORD | pitchBend | Specifies the pitch bend value (0 to 0x3fff, * where 0x2000 is exact tuning). * * @rdesc There is no return value. ***************************************************************************/ void ChangePitch(BYTE voice, WORD pitchBend) { int t1; int t2; int delta; int pitchrange; /* pitch bend range 0-3fff is +-PITCHRANGE semitones. We move * NR_STEP_PITCH steps per semitone. * so the bend range is +- (PITCHRANGE * NR_STEP_PITCH) steps. */ pitchrange = PITCHRANGE * NR_STEP_PITCH; t1 = (int)(((long)((int)pitchBend - MID_PITCH) * pitchrange) / MID_PITCH); if (t1 < 0) { t2 = NR_STEP_PITCH - 1 - t1; halfToneOffset[voice] = -(t2 / NR_STEP_PITCH); delta = (t2 - NR_STEP_PITCH + 1) % NR_STEP_PITCH; if (delta) { delta = NR_STEP_PITCH - delta; } } else { halfToneOffset[voice] = t1 / NR_STEP_PITCH; delta = t1 % NR_STEP_PITCH; } fNumFreqPtr[voice] = fNumNotes[delta]; } /**************************************************************************** * @doc INTERNAL * * @api void | SetVoicePitch | Changes the pitch value of a voice. Does * not affect the percussive voices, except for the bass drum. The change * takes place immediately. * * @parm BYTE | voice | Specifies which voice to set. * * @parm WORD | pitchBend | Specifies the new pitch bend value (0 to 0x3fff, * where 0x2000 == exact tuning). * * @comm The variation in pitch is a function of the previous call to * and the value of

. A value of 0 means * -half-tone * pitchRangeStep, 0x2000 means no variation (exact pitch) and * 0x3fff means +half-tone * pitchRangeStep. * * @rdesc There is no return value. ***************************************************************************/ void SetVoicePitch(BYTE voice, WORD pitchBend) { if (voice <= BD) { // melodic and bass drum voices if (pitchBend > MAX_PITCH) { pitchBend = MAX_PITCH; } ChangePitch(voice, pitchBend); SetFreq(voice, notePitch[voice], voiceKeyOn[voice]); } } /* --- volume calculation ------------------------ */ /* * set the attenuation for a slot (combine the channel attenuation * setting with the key velocity). */ VOID SetVoiceAtten(BYTE voice, BYTE bChannel, BYTE bVelocity) { BYTE bAtten; BYTE slot; PBYTE slots; // scale up the volume since the patch maps are too quiet //bVelocity = loudervol[bVelocity]; /* * set channel attenuation */ bAtten = gbVelocityAtten[bVelocity >> 2] + gbChanAtten[bChannel]; /* * add on any global (volume-setting) attenuation */ bAtten += wSynthAtten; /* save this for each non-modifier slot */ if (voice <= BD) { // melodic voice slots = slotVoice[voice]; slotAtten[slots[1]] = bAtten; SndSKslLevel(slots[1]); if (!GetLocPrm(slots[0], prmFm)) { // additive synthesis: set volume of first slot too slotAtten[slots[0]] = bAtten; SndSKslLevel(slots[0]); } } else { // percussive voice slot = slotPerc[voice - BD][0]; slotAtten[slot] = bAtten; SndSKslLevel(slot); } } /* adjust each slot attenuation to allow for a global volume * change - note that we only need to change the * carrier, not the modifier slots. * * change for channel bChannel, or all channels if bChannel is * 0xff */ VOID ChangeAtten(BYTE bChannel, int iChangeAtten) { BYTE voice; BYTE slot; PBYTE slots; /* find voices using this channel */ for (voice = 0; voice < NUMVOICES; voice++) { if ((voices[voice].channel == bChannel) || (bChannel == 0xff)) { if (voice <= BD) { /* melodic voice */ slots = slotVoice[voice]; slotAtten[slots[1]] += iChangeAtten; SndSKslLevel(slots[1]); if (!GetLocPrm(slots[0], prmFm)) { // additive synthesis: set volume of first slot too slotAtten[slots[0]] += iChangeAtten; SndSKslLevel(slots[0]); } } else { slot = slotPerc[voice - BD][0]; slotAtten[slot] += iChangeAtten; SndSKslLevel(slot); } } } } /* --- note on/off ------------------------------- */ /* switch off the note a given voice is playing */ void NoteOff(BYTE voice) { if (voice < BD) { /* melodic voice */ SetFreq(voice, notePitch[voice], 0); } else { percBits &= ~percMasks[voice-BD]; SndSAmVibRhythm(); } } /* switch on a voice at a given note (0 - 127, where 60 is middle c) */ void NoteOn(BYTE voice, BYTE bNote) { BYTE bPitch; /* convert note to chip pitch */ if (bNote < (MID_C - CHIP_MID_C)) { bPitch = 0; } else { bPitch = bNote - (MID_C - CHIP_MID_C); } if (voice < BD) { /* melodic voice */ /* set frequency and start note */ SetFreq(voice, bPitch, 1); } else { /* * nb we don't change the pitch of some percussion instruments. * * also note that for percussive instruments (including BD), * the note-on setting should always be 0. You switch the percussion * on by writing to the AmVibRhythm register. */ if (voice == BD) { SetFreq(voice, bPitch, 0); } else if (voice == TOM) { /* * for best sounds, we do change the TOM freq, but we always keep * the SD 7 semi-tones above TOM. */ SetFreq(TOM, bPitch, 0); SetFreq(SD, (BYTE)(bPitch + TOM_TO_SD), 0); } /* other instruments never change */ percBits |= percMasks[voice - BD]; SndSAmVibRhythm(); } } /* -- voice allocation -------- */ /* * find the voice that is currently playing a given channel/note pair * (if any) */ BYTE FindVoice(BYTE bNote, BYTE bChannel) { BYTE i; for (i = 0; i < (BYTE)NUMVOICES; i++) { if ((voices[i].alloc) && (voices[i].note == bNote) && (voices[i].channel == bChannel)) { voices[i].dwTimeStamp = dwAge++; return(i); } } /* no voice is playing this */ return(0xff); } /* * mark a voice as unused */ VOID FreeVoice(voice) { voices[voice].alloc = 0; } /* * GetNewVoice - allocate a voice to play this note. if no voices are * free, re-use the note with the oldest timestamp. */ BYTE GetNewVoice(BYTE patch, BYTE note, BYTE channel) { BYTE i; BYTE voice; BYTE bVoiceToUse, bVoiceSame, bVoiceOldest; DWORD dwOldestTime = dwAge + 1; // init to past current "time" DWORD dwOldestSame = dwAge + 1; DWORD dwOldestOff = dwAge + 1; if (patches[patch].mode) { // it's a percussive patch voice = patches[patch].percVoice; // use fixed percussion voice voices[voice].alloc = TRUE; voices[voice].note = note; voices[voice].channel = channel; voices[voice].dwTimeStamp = MAKELONG(patch, 0); SetVoiceTimbre(voice, &patches[patch]); return voice; } bVoiceToUse = bVoiceSame = bVoiceOldest = 0xff; // find a free melodic voice to use for (i = 0; i < (BYTE)NUMMELODIC; i++) { // it's a melodic patch if (!voices[i].alloc) { if (voices[i].dwTimeStamp < dwOldestOff) { bVoiceToUse = i; dwOldestOff = voices[i].dwTimeStamp; } } else if (voices[i].channel == channel) { if (voices[i].dwTimeStamp < dwOldestSame) { dwOldestSame = voices[i].dwTimeStamp; bVoiceSame = i; } } else if (voices[i].dwTimeStamp < dwOldestTime) { dwOldestTime = voices[i].dwTimeStamp; bVoiceOldest = i; // remember oldest one to steal } } // choose a free voice if we have found one. If not, choose the // oldest voice of the same channel. if none, choose the oldest voice. if (bVoiceToUse == 0xff) { if (bVoiceSame != 0xff) { bVoiceToUse = bVoiceSame; } else { bVoiceToUse = bVoiceOldest; } } if (voices[bVoiceToUse].alloc) { // if we stole it, turn it off NoteOff(bVoiceToUse); } voices[bVoiceToUse].alloc = 1; voices[bVoiceToUse].note = note; voices[bVoiceToUse].channel = channel; voices[bVoiceToUse].dwTimeStamp = dwAge++; SetVoiceTimbre(bVoiceToUse, &patches[patch]); return bVoiceToUse; } /* --- externally-called functions ------------------------------ */ /* * Adlib_NoteOn - This turns a note on. (Including drums, with * a patch # of the drum Note + 128) * * inputs * BYTE bPatch - MIDI patch number * BYTE bNote - MIDI note number * BYTE bChannel - MIDI channel # * BYTE bVelocity - Velocity # * short iBend - current pitch bend from -32768, to 32767 * returns * none */ VOID NEAR PASCAL Adlib_NoteOn (BYTE bPatch, BYTE bNote, BYTE bChannel, BYTE bVelocity, short iBend) { BYTE voice; WORD wBend; if (bVelocity == 0) { // 0 velocity means note off Adlib_NoteOff(bPatch, bNote, bChannel); return; } // octave registration for melodic patches if (bPatch < 128) { bNote += patchKeyOffset[bPatch]; if ((bNote < 0) || (bNote > 127)) { bNote -= patchKeyOffset[bPatch]; } } if (bPatch >= 128) { /* * it's a percussion note */ bNote = bPatch - 128; if ((bNote < FIRSTDRUMNOTE) || (bNote > LASTDRUMNOTE)) { return; } /* use the drum patch table to map the note to a given * TIMBRE/note pair */ bPatch = drumpatch[bNote - FIRSTDRUMNOTE].patch; bNote = drumpatch[bNote - FIRSTDRUMNOTE].note; /* each drum patch plays on one specific voice. * find that voice */ voice = patches[bPatch].percVoice; /* switch note off if playing */ if (voices[voice].alloc) { NoteOff(voice); } /* call GetNewVoice to set the voice params and timestamp * even if we found it. */ voice = GetNewVoice(bPatch, bNote, bChannel); } else { /* switch note off if it's playing */ if ( (voice = FindVoice(bNote, bChannel)) != 0xFF ) { NoteOff(voice); } else { voice = GetNewVoice(bPatch, bNote, bChannel); } } /* convert the velocity to an attenuation setting, and * write that to the device */ SetVoiceAtten(voice, bChannel, bVelocity); /* * apply pitch bend. note that we are passed a pitch bend in the * range 8000-7fff, but our code assumes 0-3fff, so we convert here. */ wBend = (((WORD)iBend + 0x8000) >> 2) & 0x3fff; SetVoicePitch(voice, wBend); // play the note NoteOn(voice, bNote); } /* Adlib_NoteOff - This turns a note off. (Including drums, * with a patch # of the drum note + 128) * * inputs * BYTE bPatch - MIDI patch # * BYTE bNote - MIDI note number * BYTE bChannel - MIDI channel # * returns * none */ VOID FAR PASCAL Adlib_NoteOff (BYTE bPatch, BYTE bNote, BYTE bChannel) { BYTE bVoice; if (bPatch > 127) { /* drum note. These all use a fixed voice */ if ((bNote < FIRSTDRUMNOTE) || (bNote > LASTDRUMNOTE)) { return; } /* use the drum patch table to map the note to a given * TIMBRE/note pair */ bPatch = drumpatch[bNote - FIRSTDRUMNOTE].patch; bNote = drumpatch[bNote - FIRSTDRUMNOTE].note; bVoice = patches[bPatch].percVoice; /* switch note off if playing our patch */ if (LOWORD(voices[bVoice].dwTimeStamp) == bPatch) { NoteOff(bVoice); } } else { bVoice = FindVoice(bNote, bChannel); if (bVoice != 0xff) { if (voices[bVoice].note) { NoteOff(bVoice); FreeVoice(bVoice); } } } } /* Adlib_AllNotesOff - turn off all notes * * inputs - none * returns - none */ VOID Adlib_AllNotesOff(void) { BYTE i; for (i = 0; i < NUMVOICES; i++) { NoteOff(i); } } /* Adlib_NewVolume - This should be called if a volume level * has changed. This will adjust the levels of all the playing * voices. * * inputs * WORD wLeft - left attenuation (1.5 db units) * WORD wRight - right attenuation (ignore if mono) * returns * none */ VOID FAR PASCAL Adlib_NewVolume (WORD wLeft, WORD wRight) { /* ignore the right attenuation since this is a mono device */ int iChange; wLeft = min(wLeft, wRight) << 1; iChange = wLeft - wSynthAtten; wSynthAtten = wLeft; /* change attenuation for all channels */ ChangeAtten(0xff, iChange); } /* Adlib_ChannelVolume - set the volume level for an individual channel. * * inputs * BYTE bChannel - channel number to change * WORD wAtten - attenuation in 1.5 db units * * returns * none */ VOID FAR PASCAL Adlib_ChannelVolume(BYTE bChannel, WORD wAtten) { int iChange; iChange = wAtten - gbChanAtten[bChannel]; gbChanAtten[bChannel] = (BYTE)wAtten; /* change attenuation for this channel */ ChangeAtten(bChannel, iChange); } /* Adlib_SetPan - set the left-right pan position. * * inputs * BYTE bChannel - channel number to alter * BYTE bPan - 0 for left, 127 for right or somewhere in the middle. * * returns - none * * does nothing - this is a mono device */ VOID FAR PASCAL Adlib_SetPan(BYTE bChannel, BYTE bPan) { /* do nothing */ } /* Adlib_PitchBend - This pitch bends a channel. * * inputs * BYTE bChannel - channel * short iBend - Values from -32768 to 32767, being * -2 to +2 half steps * returns * none */ VOID NEAR PASCAL Adlib_PitchBend (BYTE bChannel, short iBend) { BYTE i; WORD w; /* note that our code expects 0 - 0x3fff not 0x8000 - 0x7fff */ w = (((WORD) iBend + 0x8000) >> 2) & 0x3fff; for (i = 0; i < NUMVOICES; i++) { if ((voices[i].alloc) && (voices[i].channel == bChannel)) { SetVoicePitch(i, w); } } } /* Adlib_BoardInit - initialise board and load patches as necessary. * * inputs - none * returns - 0 for success or the error code */ WORD Adlib_BoardInit(void) { BYTE i; wSynthAtten = 0; /* build the freq table */ InitFNums(); /* silence and free all voices */ for (i = 0; i <= 8; i++) { SoundChut(i); FreeVoice(i); } /* switch to percussive mode and set fixed frequencies */ SetFreq(TOM, TOM_PITCH, 0); SetFreq(SD, SD_PITCH, 0); percBits = 0; SndSAmVibRhythm(); /* init all slots to sine-wave */ for (i= 0; i < 18; i++) { SndOutput((BYTE)(0xE0 | offsetSlot[i]), 0); } /* enable wave-form selection */ SndOutput(1, 0x20); LoadPatches(); LoadDrumPatches(); // don't initialise - the data is static and will thus // be initialised to 0 at load time. no other change should be made // since the mci sequencer will not re-send channel volume change // messages. // // /* init all channels to loudest */ // for (i = 0; i < NUMCHANNELS; i++) { // gbChanAtten[i] = 4; // } return(0); } /* * Adlib_BoardReset - silence the board and set all voices off. */ VOID Adlib_BoardReset(void) { BYTE i; /* silence and free all voices */ for (i = 0; i <= 8; i++) { SoundChut(i); FreeVoice(i); } /* switch to percussive mode and set fixed frequencies */ SetFreq(TOM, TOM_PITCH, 0); SetFreq(SD, SD_PITCH, 0); percBits = 0; SndSAmVibRhythm(); }