diff options
Diffstat (limited to 'backends/midi/mt32/part.cpp')
-rw-r--r-- | backends/midi/mt32/part.cpp | 595 |
1 files changed, 595 insertions, 0 deletions
diff --git a/backends/midi/mt32/part.cpp b/backends/midi/mt32/part.cpp new file mode 100644 index 0000000000..cfb40f3d50 --- /dev/null +++ b/backends/midi/mt32/part.cpp @@ -0,0 +1,595 @@ +/* Copyright (c) 2003-2004 Various contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to + * deal in the Software without restriction, including without limitation the + * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS + * IN THE SOFTWARE. + */ + +#include <string.h> +#include <math.h> + +#include "mt32emu.h" + +// Debugging stuff +// Shows the instruments played +#define DISPLAYINSTR 1 + +namespace MT32Emu { + +static const Bit8u PartialStruct[13] = { + 0, 0, 2, 2, 1, 3, + 3, 0, 3, 0, 2, 1, 3 }; + +static const Bit8u PartialMixStruct[13] = { + 0, 1, 0, 1, 1, 0, + 1, 3, 3, 2, 2, 2, 2 }; + +static const Bit32u drumBend = 0x1000; + +// This caches the timbres/settings in use by the rhythm part +static PatchCache drumCache[94][4]; + +static volset drumPan[64]; + +//FIXME:KG: Put this dpoly stuff somewhere better +bool dpoly::isActive() { + return partials[0] != NULL || partials[1] != NULL || partials[2] != NULL || partials[3] != NULL; +} + +Bit64s dpoly::getAge() { + for (int i = 0; i < 4; i++) { + if (partials[i] != NULL) { + return partials[i]->age; + } + } + return 0; +} + +Part::Part(Synth *useSynth, int usePartNum) { + this->synth = useSynth; + this->partNum = usePartNum; + isRhythm = (usePartNum == 8); + holdpedal = false; + if (isRhythm) { + strcpy(name, "Rhythm"); + patchTemp = NULL; + timbreTemp = NULL; + rhythmTemp = &synth->mt32ram.params.rhythmSettings[0]; + } else { + sprintf(name, "Part %d", partNum + 1); + patchTemp = &synth->mt32ram.params.patchSettings[partNum]; + timbreTemp = &synth->mt32ram.params.timbreSettings[partNum]; + rhythmTemp = NULL; + } + currentInstr[0] = 0; + volume = 102; + volumesetting.leftvol = 32767; + volumesetting.rightvol = 32767; + bend = 0x1000; + memset(polyTable,0,sizeof(polyTable)); + memset(patchCache, 0, sizeof(patchCache)); + + if (isRhythm) { + init = true; + RefreshDrumCache(); + } + init = false; +} + +void Part::SetHoldPedal(bool pedalval) { + if (holdpedal && !pedalval) + StopPedalHold(); + holdpedal = pedalval; +} + +void Part::SetBend(int vol) { + if (isRhythm) { + synth->printDebug("%s: Setting bend (%d) not supported on rhythm", name, vol); + return; + } + //int tmpbend = ((vol - 0x2000) * (int)patchTemp->patch.benderRange) >> 13; + //bend = bendtable[tmpbend+24]; + + float bend_range = (float)patchTemp->patch.benderRange / 24; + bend = (Bit32u)(4096 + (vol - 8192) * bend_range); +} + +void Part::SetModulation(int vol) { + if (isRhythm) { + synth->printDebug("%s: Setting modulation (%d) not supported on rhythm", name, vol); + return; + } + // Just a bloody guess, as always, before I get things figured out + for (int t = 0; t < 4; t++) { + if (patchCache[t].playPartial) { + int newrate = (patchCache[t].modsense * vol) >> 7; + //patchCache[t].lfoperiod = lfotable[newrate]; + patchCache[t].lfodepth = newrate; + //FIXME:KG: timbreTemp->partial[t].lfo.depth = + } + } +} + +void Part::RefreshDrumCache() { + if (!isRhythm) { + synth->printDebug("ERROR: RefreshDrumCache() called on non-rhythm part"); + } + // Cache drum patches + for (int m = 0; m < 64; m++) { + int drumTimbre = rhythmTemp[m].timbre; + if (drumTimbre >= 94) + continue; + SetPatch(drumTimbre + 128); // This is to cache all the mapped drum timbres ahead of time + Bit16s pan = rhythmTemp[m].panpot; // They use R-L 0-14... + // FIXME:KG: If I don't have left/right mixed up here, it's pure luck + if (pan < 7) { + drumPan[m].leftvol = 32767; + drumPan[m].rightvol = pan * 4681; + } else { + drumPan[m].rightvol = 32767; + drumPan[m].leftvol = (14 - pan) * 4681; + } + } +} + +int Part::FixBiaslevel(int srcpnt, int *dir) { + int noteat = srcpnt & 63; + int outnote; + *dir = 1; + if (srcpnt < 64) + *dir = 0; + outnote = 33 + noteat; + //synth->printDebug("Bias note %d, dir %d", outnote, *dir); + + return outnote; +} + +int Part::FixKeyfollow(int srckey, int *dir) { + if (srckey>=0 && srckey<=16) { + //int keyfix[17] = { 256, 128, 64, 0, 32, 64, 96, 128, 128+32, 192, 192+32, 256, 256+64, 256+128, 512, 259, 269 }; + int keyfix[17] = { 256*16, 128*16, 64*16, 0, 32*16, 64*16, 96*16, 128*16, (128+32)*16, 192*16, (192+32)*16, 256*16, (256+64)*16, (256+128)*16, (512)*16, 4100, 4116}; + + if (srckey<3) + *dir = -1; + else if (srckey==3) + *dir = 0; + else + *dir = 1; + + return keyfix[srckey]; + } else { + //LOG(LOG_ERROR|LOG_MISC,"Missed key: %d", srckey); + return 256; + } +} + +void Part::RefreshPatch() { + SetPatch(-1); +} + +void Part::AbortPoly(dpoly *poly) { + if (!poly->isPlaying) { + return; + } + for (int i = 0; i < 4; i++) { + Partial *partial = poly->partials[i]; + if (partial != NULL) { + partial->deactivate(); + } + } + poly->isPlaying = false; +} + +void Part::setPatch(PatchParam *patch) { + patchTemp->patch = *patch; +} + +void Part::setTimbre(TimbreParam *timbre) { + *timbreTemp = *timbre; +} + +unsigned int Part::getAbsTimbreNum() { + if (isRhythm) { + synth->printDebug("%s: Attempted to call getAbsTimbreNum() - doesn't make sense for rhythm"); + return 0; + } + return (patchTemp->patch.timbreGroup * 64) + patchTemp->patch.timbreNum; +} + +void Part::SetPatch(int patchNum) { + int pcm; + int absTimbreNum = -1; // Initialised to please compiler + TimbreParam timSrc; + if (isRhythm) { + // "patchNum" is treated as "timbreNum" for rhythm part + if (patchNum < 128) { + synth->printDebug("%s: Patch #%d is not valid for rhythm (must be >= 128)", name, patchNum); + return; + } + absTimbreNum = patchNum; + timSrc = synth->mt32ram.params.timbres[absTimbreNum].timbre; + } else { + if (patchNum >= 0) { + setPatch(&synth->mt32ram.params.patches[patchNum]); + } + if (patchNum >= 0) { + setTimbre(&synth->mt32ram.params.timbres[getAbsTimbreNum()].timbre); + } + timSrc = *timbreTemp; +#if 0 + // Immediately stop all partials on this part (this is apparently *not* the correct behaviour) + for (int m = 0; m < MAXPOLY; m++) { + AbortPoly(poly); + } +#else + // check if any partials are still playing on this part + // if so then duplicate the cached data from the part to the partial so that + // we can change the part's cache without affecting the partial. + // Hopefully this is fairly rare. + for (int m = 0; m < MAXPOLY; m++) { + for (int i = 0; i < 4; i++) { + Partial *partial = polyTable[m].partials[i]; + if (partial != NULL) { + // copy cache data + partial->cachebackup = patchCache[i]; + // update pointers + partial->patchCache = &partial->cachebackup; + } + } + } +#endif + } + + memcpy(currentInstr, timSrc.common.name, 10); + currentInstr[10] = 0; + + int partialCount = 0; + for (int t=0;t<4;t++) { + if ( ((timSrc.common.pmute >> (t)) & 0x1) == 1 ) { + patchCache[t].playPartial = true; + partialCount++; + } else { + patchCache[t].playPartial = false; + continue; + } + + // Calculate and cache common parameters + + pcm = timSrc.partial[t].wg.pcmwave; + + patchCache[t].pcm = timSrc.partial[t].wg.pcmwave; + patchCache[t].useBender = (timSrc.partial[t].wg.bender == 1); + + switch (t) { + case 0: + patchCache[t].PCMPartial = (PartialStruct[(int)timSrc.common.pstruct12] & 0x2) ? true : false; + patchCache[t].mix = PartialMixStruct[(int)timSrc.common.pstruct12]; + patchCache[t].structurePosition = 0; + patchCache[t].structurePair = 1; + break; + case 1: + patchCache[t].PCMPartial = (PartialStruct[(int)timSrc.common.pstruct12] & 0x1) ? true : false; + patchCache[t].mix = PartialMixStruct[(int)timSrc.common.pstruct12]; + patchCache[t].structurePosition = 1; + patchCache[t].structurePair = 0; + break; + case 2: + patchCache[t].PCMPartial = (PartialStruct[(int)timSrc.common.pstruct34] & 0x2) ? true : false; + patchCache[t].mix = PartialMixStruct[(int)timSrc.common.pstruct34]; + patchCache[t].structurePosition = 0; + patchCache[t].structurePair = 3; + break; + case 3: + patchCache[t].PCMPartial = (PartialStruct[(int)timSrc.common.pstruct34] & 0x1) ? true : false; + patchCache[t].mix = PartialMixStruct[(int)timSrc.common.pstruct34]; + patchCache[t].structurePosition = 1; + patchCache[t].structurePair = 2; + break; + default: + break; + } + + patchCache[t].waveform = timSrc.partial[t].wg.waveform; + patchCache[t].pulsewidth = timSrc.partial[t].wg.pulsewid; + patchCache[t].pwsens = timSrc.partial[t].wg.pwvelo; + patchCache[t].pitchkeyfollow = FixKeyfollow(timSrc.partial[t].wg.keyfollow, &patchCache[t].pitchkeydir); + + // Calculate and cache pitch stuff + patchCache[t].pitchshift = timSrc.partial[t].wg.coarse; + Bit32s pFine, fShift; + pFine = (Bit32s)timSrc.partial[t].wg.fine; + if (isRhythm) { + patchCache[t].pitchshift += 24; + fShift = pFine + 50; + } else { + patchCache[t].pitchshift += patchTemp->patch.keyShift; + fShift = pFine + (Bit32s)patchTemp->patch.fineTune; + } + patchCache[t].fineshift = finetable[fShift]; + + patchCache[t].pitchEnv = timSrc.partial[t].env; + patchCache[t].pitchEnv.sensitivity = (char)((float)patchCache[t].pitchEnv.sensitivity*1.27); + patchCache[t].pitchsustain = patchCache[t].pitchEnv.level[3]; + + // Calculate and cache TVA envelope stuff + patchCache[t].ampEnv = timSrc.partial[t].tva; + for (int i = 0; i < 4; i++) + patchCache[t].ampEnv.envlevel[i] = (char)((float)patchCache[t].ampEnv.envlevel[i]*1.27); + patchCache[t].ampEnv.level = (char)((float)patchCache[t].ampEnv.level*1.27); + float tvelo = ((float)timSrc.partial[t].tva.velosens / 100.0f); + float velo = fabs(tvelo-0.5f) * 2.0f; + velo *= 63.0f; + patchCache[t].ampEnv.velosens = (char)velo; + if (tvelo<0.5f) + patchCache[t].ampenvdir = 1; + else + patchCache[t].ampenvdir = 0; + + patchCache[t].ampbias[0] = FixBiaslevel(patchCache[t].ampEnv.biaspoint1, &patchCache[t].ampdir[0]); + patchCache[t].ampblevel[0] = 12 - patchCache[t].ampEnv.biaslevel1; + patchCache[t].ampbias[1] = FixBiaslevel(patchCache[t].ampEnv.biaspoint2, &patchCache[t].ampdir[1]); + patchCache[t].ampblevel[1] = 12 - patchCache[t].ampEnv.biaslevel2; + patchCache[t].ampdepth = patchCache[t].ampEnv.envvkf * patchCache[t].ampEnv.envvkf; + patchCache[t].ampsustain = patchCache[t].ampEnv.envlevel[3]; + patchCache[t].amplevel = patchCache[t].ampEnv.level; + + // Calculate and cache filter stuff + patchCache[t].filtEnv = timSrc.partial[t].tvf; + patchCache[t].tvfdepth = patchCache[t].filtEnv.envdkf; + patchCache[t].filtkeyfollow = FixKeyfollow(patchCache[t].filtEnv.keyfollow, &patchCache[t].keydir); + patchCache[t].filtEnv.envdepth = (char)((float)patchCache[t].filtEnv.envdepth * 1.27); + patchCache[t].tvfbias = FixBiaslevel(patchCache[t].filtEnv.biaspoint, &patchCache[t].tvfdir); + patchCache[t].tvfblevel = patchCache[t].filtEnv.biaslevel; + patchCache[t].filtsustain = patchCache[t].filtEnv.envlevel[3]; + + // Calculate and cache LFO stuff + patchCache[t].lfodepth = timSrc.partial[t].lfo.depth; + patchCache[t].lfoperiod = lfotable[(int)timSrc.partial[t].lfo.rate]; + patchCache[t].lforate = timSrc.partial[t].lfo.rate; + patchCache[t].modsense = timSrc.partial[t].lfo.modsense; + } + for (int t = 0; t < 4; t++) { + // Common parameters, stored redundantly + patchCache[t].partialCount = partialCount; + patchCache[t].sustain = (timSrc.common.nosustain == 0); + } + //synth->printDebug("Res 1: %d 2: %d 3: %d 4: %d", patchCache[0].waveform, patchCache[1].waveform, patchCache[2].waveform, patchCache[3].waveform); + + if (isRhythm) + memcpy(drumCache[absTimbreNum - 128], patchCache, sizeof(patchCache)); + else + AllStop(); +#if DISPLAYINSTR == 1 + synth->printDebug("%s: Recache, param %d (timbre: %s), %d partials", name, patchNum, currentInstr, partialCount); + for (int i = 0; i < 4; i++) { + synth->printDebug(" %d: play=%s, pcm=%d, wave=%d", i, patchCache[i].playPartial ? "YES" : "NO", timSrc.partial[i].wg.pcmwave, timSrc.partial[i].wg.waveform); + } +#endif +} + +char *Part::getName() { + return name; +} + +void Part::SetVolume(int vol) { + volume = voltable[vol]; +} + +void Part::SetPan(int pan) { + // FIXME:KG: This is unchangeable for drums (they always use drumPan), is that correct? + // FIXME:KG: There is no way to get a centred balance here... And the middle two + // pan settings have a distance of 1024, double the usual. + // Perhaps we should multiply by 516? 520? 520.111..? + // KG: Changed to 516, to mostly eliminate that jump in the middle + if (pan < 64) { + volumesetting.leftvol = 32767; + volumesetting.rightvol = (Bit16s)(pan * 516); + } else { + volumesetting.rightvol = 32767; + volumesetting.leftvol = (Bit16s)((127 - pan) * 516); + } + //synth->printDebug("%s (%s): Set pan to %d", name, currentInstr, panpot); +} + +void Part::PlayNote(PartialManager *partialManager, int f, int vel) { + int drumNum = -1; // Initialised to please compiler + int drumTimbre = -1; // As above + int freqNum; + + if (isRhythm) { + if (f < 24 || f > 87) { + synth->printDebug("%s: Attempted to play invalid note %d", name, f); + return; + } + drumNum = f - 24; + drumTimbre = rhythmTemp[drumNum].timbre; + if (drumTimbre >= 94) { + synth->printDebug("%s: Attempted to play unmapped note %d!", name, f); + return; + } + memcpy(patchCache, drumCache[drumTimbre], sizeof(patchCache)); + memcpy(¤tInstr, synth->mt32ram.params.timbres[128 + drumTimbre].timbre.common.name, 10); + currentInstr[10] = 0; + freqNum = MIDDLEC; + } else { + if (f < 12 || f > 108) { + synth->printDebug("%s (%s): Attempted to play invalid note %d", name, currentInstr, f); + return; + } + freqNum = f; + } + // POLY1 mode, Single Assign + // Haven't found any software that uses any of the other poly modes + // FIXME:KG: Should this also apply to rhythm? + if (!isRhythm) { + for (int i = 0; i < MAXPOLY; i++) { + if (polyTable[i].isActive() && (polyTable[i].note == f)) { + //AbortPoly(&polyTable[i]); + StopNote(f); + break; + } + } + } + + unsigned int needPartials = patchCache[0].partialCount; + + if (needPartials > partialManager->GetFreePartialCount()) { + if (!partialManager->FreePartials(needPartials, partNum)) { + synth->printDebug("%s (%s): Insufficient free partials to play note %d (vel=%d)", name, currentInstr, f, vel); + return; + } + } + // Find free note + int m; + for (m = 0; m < MAXPOLY; m++) { + if (!polyTable[m].isActive()) { + break; + } + } + if (m == MAXPOLY) { + synth->printDebug("%s (%s): No free poly to play note %d (vel %d)", name, currentInstr, f, vel); + return; + } + + dpoly *tpoly = &polyTable[m]; + Bit16s freq = freqtable[freqNum]; + + tpoly->isPlaying = true; + tpoly->note = f; + tpoly->isDecay = false; + tpoly->freq = freq; + tpoly->freqnum = freqNum; + tpoly->vel = vel; + tpoly->pedalhold = false; + + bool allnull = true; + for (int x = 0; x < 4; x++) { + if (patchCache[x].playPartial) { + tpoly->partials[x] = partialManager->AllocPartial(partNum); + allnull = false; + } else { + tpoly->partials[x] = NULL; + } + } + + if (allnull) + synth->printDebug("%s (%s): No partials to play for this instrument", name, this->currentInstr); + + if (isRhythm) { + tpoly->bendptr = &drumBend; + tpoly->pansetptr = &drumPan[drumNum]; + tpoly->reverb = rhythmTemp[drumNum].reverbSwitch > 0; + } else { + tpoly->bendptr = &bend; + tpoly->pansetptr = &volumesetting; + tpoly->reverb = patchTemp->patch.reverbSwitch > 0; + } + tpoly->sustain = patchCache[0].sustain; + tpoly->volumeptr = &volume; + + for (int x = 0; x < 4; x++) { + if (tpoly->partials[x] != NULL) { + tpoly->partials[x]->startPartial(tpoly, &patchCache[x], tpoly->partials[patchCache[x].structurePair]); + } + } + +#if DISPLAYINSTR == 1 + if (isRhythm) { + synth->printDebug("%s (%s): starting note poly %d (drum %d, timbre %d) - Vel %d Vol %d", name, currentInstr, m, drumNum, drumTimbre, vel, volume); + } else { + synth->printDebug("%s (%s): starting note poly %d - Vel %d Freq %d Vol %d", name, currentInstr, m, vel, f, volume); + } +#endif +} + +static void StartDecayPoly(dpoly *tpoly) { + if (tpoly->isDecay) { + return; + } + tpoly->isDecay = true; + + for (int t = 0; t < 4; t++) { + Partial *partial = tpoly->partials[t]; + if (partial == NULL) + continue; + partial->startDecayAll(); + } + tpoly->isPlaying = false; +} + +void Part::AllStop() { + for (int q = 0; q < MAXPOLY; q++) { + dpoly *tpoly = &polyTable[q]; + if (tpoly->isPlaying) { + StartDecayPoly(tpoly); + } + } +} + +void Part::StopPedalHold() { + for (int q = 0; q < MAXPOLY; q++) { + dpoly *tpoly; + tpoly = &polyTable[q]; + if (tpoly->isActive() && tpoly->pedalhold) + StopNote(tpoly->note); + } +} + +void Part::StopNote(int f) { + // Non-sustaining instruments ignore stop note commands. + // They die away eventually anyway + //if (!tpoly->sustain) return; + +#if DISPLAYINSTR == 1 + synth->printDebug("%s (%s): stopping note %d", name, currentInstr, f); +#endif + + if (f != -1) { + for (int q = 0; q < MAXPOLY; q++) { + dpoly *tpoly = &polyTable[q]; + if (tpoly->isPlaying && tpoly->note == f) { + if (holdpedal) + tpoly->pedalhold = true; + else if (tpoly->sustain) + StartDecayPoly(tpoly); + } + } + return; + } + + // Find oldest note... yes, the MT-32 can be reconfigured to kill different note first + // This is simplest + int oldest = -1; + Bit64s oldage = -1; + + for (int q = 0; q < MAXPOLY; q++) { + dpoly *tpoly = &polyTable[q]; + + if (tpoly->isPlaying && !tpoly->isDecay) { + if (tpoly->getAge() >= oldage) { + oldage = tpoly->getAge(); + oldest = q; + } + } + } + + if (oldest!=-1) { + StartDecayPoly(&polyTable[oldest]); + } +} + +} |