mirror of
https://github.com/scummvm/scummvm.git
synced 2026-06-20 05:45:29 +00:00
This update uses upstream commit 777c51cdb4dbb4e02a53c23edea9086f0b600e26. The new SampleRateConverter is added, but not built as we don't use it. Also, building it without source changes will need additional include directories. This update of Munt reduces the stack size, and thus fixes bug #9630.
696 lines
21 KiB
C++
Executable File
696 lines
21 KiB
C++
Executable File
/* Copyright (C) 2003, 2004, 2005, 2006, 2008, 2009 Dean Beeler, Jerome Fisher
|
|
* Copyright (C) 2011-2016 Dean Beeler, Jerome Fisher, Sergey V. Mikayev
|
|
*
|
|
* This program is free software: you can redistribute it and/or modify
|
|
* it 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.
|
|
*
|
|
* This program is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
* GNU Lesser General Public License for more details.
|
|
*
|
|
* You should have received a copy of the GNU Lesser General Public License
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
*/
|
|
|
|
#include <cstdio>
|
|
#include <cstring>
|
|
|
|
#include "internals.h"
|
|
|
|
#include "Part.h"
|
|
#include "Partial.h"
|
|
#include "PartialManager.h"
|
|
#include "Poly.h"
|
|
#include "Synth.h"
|
|
|
|
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
|
|
};
|
|
|
|
RhythmPart::RhythmPart(Synth *useSynth, unsigned int usePartNum): Part(useSynth, usePartNum) {
|
|
strcpy(name, "Rhythm");
|
|
rhythmTemp = &synth->mt32ram.rhythmTemp[0];
|
|
refresh();
|
|
}
|
|
|
|
Part::Part(Synth *useSynth, unsigned int usePartNum) {
|
|
synth = useSynth;
|
|
partNum = usePartNum;
|
|
patchCache[0].dirty = true;
|
|
holdpedal = false;
|
|
patchTemp = &synth->mt32ram.patchTemp[partNum];
|
|
if (usePartNum == 8) {
|
|
// Nasty hack for rhythm
|
|
timbreTemp = NULL;
|
|
} else {
|
|
sprintf(name, "Part %d", partNum + 1);
|
|
timbreTemp = &synth->mt32ram.timbreTemp[partNum];
|
|
}
|
|
currentInstr[0] = 0;
|
|
currentInstr[10] = 0;
|
|
modulation = 0;
|
|
expression = 100;
|
|
pitchBend = 0;
|
|
activePartialCount = 0;
|
|
memset(patchCache, 0, sizeof(patchCache));
|
|
}
|
|
|
|
Part::~Part() {
|
|
while (!activePolys.isEmpty()) {
|
|
delete activePolys.takeFirst();
|
|
}
|
|
}
|
|
|
|
void Part::setDataEntryMSB(unsigned char midiDataEntryMSB) {
|
|
if (nrpn) {
|
|
// The last RPN-related control change was for an NRPN,
|
|
// which the real synths don't support.
|
|
return;
|
|
}
|
|
if (rpn != 0) {
|
|
// The RPN has been set to something other than 0,
|
|
// which is the only RPN that these synths support
|
|
return;
|
|
}
|
|
patchTemp->patch.benderRange = midiDataEntryMSB > 24 ? 24 : midiDataEntryMSB;
|
|
updatePitchBenderRange();
|
|
}
|
|
|
|
void Part::setNRPN() {
|
|
nrpn = true;
|
|
}
|
|
|
|
void Part::setRPNLSB(unsigned char midiRPNLSB) {
|
|
nrpn = false;
|
|
rpn = (rpn & 0xFF00) | midiRPNLSB;
|
|
}
|
|
|
|
void Part::setRPNMSB(unsigned char midiRPNMSB) {
|
|
nrpn = false;
|
|
rpn = (rpn & 0x00FF) | (midiRPNMSB << 8);
|
|
}
|
|
|
|
void Part::setHoldPedal(bool pressed) {
|
|
if (holdpedal && !pressed) {
|
|
holdpedal = false;
|
|
stopPedalHold();
|
|
} else {
|
|
holdpedal = pressed;
|
|
}
|
|
}
|
|
|
|
Bit32s Part::getPitchBend() const {
|
|
return pitchBend;
|
|
}
|
|
|
|
void Part::setBend(unsigned int midiBend) {
|
|
// CONFIRMED:
|
|
pitchBend = ((signed(midiBend) - 8192) * pitchBenderRange) >> 14; // PORTABILITY NOTE: Assumes arithmetic shift
|
|
}
|
|
|
|
Bit8u Part::getModulation() const {
|
|
return modulation;
|
|
}
|
|
|
|
void Part::setModulation(unsigned int midiModulation) {
|
|
modulation = Bit8u(midiModulation);
|
|
}
|
|
|
|
void Part::resetAllControllers() {
|
|
modulation = 0;
|
|
expression = 100;
|
|
pitchBend = 0;
|
|
setHoldPedal(false);
|
|
}
|
|
|
|
void Part::reset() {
|
|
resetAllControllers();
|
|
allSoundOff();
|
|
rpn = 0xFFFF;
|
|
}
|
|
|
|
void RhythmPart::refresh() {
|
|
// (Re-)cache all the mapped timbres ahead of time
|
|
for (unsigned int drumNum = 0; drumNum < synth->controlROMMap->rhythmSettingsCount; drumNum++) {
|
|
int drumTimbreNum = rhythmTemp[drumNum].timbre;
|
|
if (drumTimbreNum >= 127) { // 94 on MT-32
|
|
continue;
|
|
}
|
|
PatchCache *cache = drumCache[drumNum];
|
|
backupCacheToPartials(cache);
|
|
for (int t = 0; t < 4; t++) {
|
|
// Common parameters, stored redundantly
|
|
cache[t].dirty = true;
|
|
cache[t].reverb = rhythmTemp[drumNum].reverbSwitch > 0;
|
|
}
|
|
}
|
|
updatePitchBenderRange();
|
|
}
|
|
|
|
void Part::refresh() {
|
|
backupCacheToPartials(patchCache);
|
|
for (int t = 0; t < 4; t++) {
|
|
// Common parameters, stored redundantly
|
|
patchCache[t].dirty = true;
|
|
patchCache[t].reverb = patchTemp->patch.reverbSwitch > 0;
|
|
}
|
|
memcpy(currentInstr, timbreTemp->common.name, 10);
|
|
synth->newTimbreSet(partNum, patchTemp->patch.timbreGroup, patchTemp->patch.timbreNum, currentInstr);
|
|
updatePitchBenderRange();
|
|
}
|
|
|
|
const char *Part::getCurrentInstr() const {
|
|
return ¤tInstr[0];
|
|
}
|
|
|
|
void RhythmPart::refreshTimbre(unsigned int absTimbreNum) {
|
|
for (int m = 0; m < 85; m++) {
|
|
if (rhythmTemp[m].timbre == absTimbreNum - 128) {
|
|
drumCache[m][0].dirty = true;
|
|
}
|
|
}
|
|
}
|
|
|
|
void Part::refreshTimbre(unsigned int absTimbreNum) {
|
|
if (getAbsTimbreNum() == absTimbreNum) {
|
|
memcpy(currentInstr, timbreTemp->common.name, 10);
|
|
patchCache[0].dirty = true;
|
|
}
|
|
}
|
|
|
|
void Part::setPatch(const PatchParam *patch) {
|
|
patchTemp->patch = *patch;
|
|
}
|
|
|
|
void RhythmPart::setTimbre(TimbreParam * /*timbre*/) {
|
|
synth->printDebug("%s: Attempted to call setTimbre() - doesn't make sense for rhythm", name);
|
|
}
|
|
|
|
void Part::setTimbre(TimbreParam *timbre) {
|
|
*timbreTemp = *timbre;
|
|
}
|
|
|
|
unsigned int RhythmPart::getAbsTimbreNum() const {
|
|
synth->printDebug("%s: Attempted to call getAbsTimbreNum() - doesn't make sense for rhythm", name);
|
|
return 0;
|
|
}
|
|
|
|
unsigned int Part::getAbsTimbreNum() const {
|
|
return (patchTemp->patch.timbreGroup * 64) + patchTemp->patch.timbreNum;
|
|
}
|
|
|
|
#if MT32EMU_MONITOR_MIDI > 0
|
|
void RhythmPart::setProgram(unsigned int patchNum) {
|
|
synth->printDebug("%s: Attempt to set program (%d) on rhythm is invalid", name, patchNum);
|
|
}
|
|
#else
|
|
void RhythmPart::setProgram(unsigned int) { }
|
|
#endif
|
|
|
|
void Part::setProgram(unsigned int patchNum) {
|
|
setPatch(&synth->mt32ram.patches[patchNum]);
|
|
holdpedal = false;
|
|
allSoundOff();
|
|
setTimbre(&synth->mt32ram.timbres[getAbsTimbreNum()].timbre);
|
|
refresh();
|
|
}
|
|
|
|
void Part::updatePitchBenderRange() {
|
|
pitchBenderRange = patchTemp->patch.benderRange * 683;
|
|
}
|
|
|
|
void Part::backupCacheToPartials(PatchCache cache[4]) {
|
|
// check if any partials are still playing with the old patch cache
|
|
// 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.
|
|
// We delay this until now to avoid a copy operation with every note played
|
|
for (Poly *poly = activePolys.getFirst(); poly != NULL; poly = poly->getNext()) {
|
|
poly->backupCacheToPartials(cache);
|
|
}
|
|
}
|
|
|
|
void Part::cacheTimbre(PatchCache cache[4], const TimbreParam *timbre) {
|
|
backupCacheToPartials(cache);
|
|
int partialCount = 0;
|
|
for (int t = 0; t < 4; t++) {
|
|
if (((timbre->common.partialMute >> t) & 0x1) == 1) {
|
|
cache[t].playPartial = true;
|
|
partialCount++;
|
|
} else {
|
|
cache[t].playPartial = false;
|
|
continue;
|
|
}
|
|
|
|
// Calculate and cache common parameters
|
|
cache[t].srcPartial = timbre->partial[t];
|
|
|
|
cache[t].pcm = timbre->partial[t].wg.pcmWave;
|
|
|
|
switch (t) {
|
|
case 0:
|
|
cache[t].PCMPartial = (PartialStruct[int(timbre->common.partialStructure12)] & 0x2) ? true : false;
|
|
cache[t].structureMix = PartialMixStruct[int(timbre->common.partialStructure12)];
|
|
cache[t].structurePosition = 0;
|
|
cache[t].structurePair = 1;
|
|
break;
|
|
case 1:
|
|
cache[t].PCMPartial = (PartialStruct[int(timbre->common.partialStructure12)] & 0x1) ? true : false;
|
|
cache[t].structureMix = PartialMixStruct[int(timbre->common.partialStructure12)];
|
|
cache[t].structurePosition = 1;
|
|
cache[t].structurePair = 0;
|
|
break;
|
|
case 2:
|
|
cache[t].PCMPartial = (PartialStruct[int(timbre->common.partialStructure34)] & 0x2) ? true : false;
|
|
cache[t].structureMix = PartialMixStruct[int(timbre->common.partialStructure34)];
|
|
cache[t].structurePosition = 0;
|
|
cache[t].structurePair = 3;
|
|
break;
|
|
case 3:
|
|
cache[t].PCMPartial = (PartialStruct[int(timbre->common.partialStructure34)] & 0x1) ? true : false;
|
|
cache[t].structureMix = PartialMixStruct[int(timbre->common.partialStructure34)];
|
|
cache[t].structurePosition = 1;
|
|
cache[t].structurePair = 2;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
cache[t].partialParam = &timbre->partial[t];
|
|
|
|
cache[t].waveform = timbre->partial[t].wg.waveform;
|
|
}
|
|
for (int t = 0; t < 4; t++) {
|
|
// Common parameters, stored redundantly
|
|
cache[t].dirty = false;
|
|
cache[t].partialCount = partialCount;
|
|
cache[t].sustain = (timbre->common.noSustain == 0);
|
|
}
|
|
//synth->printDebug("Res 1: %d 2: %d 3: %d 4: %d", cache[0].waveform, cache[1].waveform, cache[2].waveform, cache[3].waveform);
|
|
|
|
#if MT32EMU_MONITOR_INSTRUMENTS > 0
|
|
synth->printDebug("%s (%s): Recached timbre", name, currentInstr);
|
|
for (int i = 0; i < 4; i++) {
|
|
synth->printDebug(" %d: play=%s, pcm=%s (%d), wave=%d", i, cache[i].playPartial ? "YES" : "NO", cache[i].PCMPartial ? "YES" : "NO", timbre->partial[i].wg.pcmWave, timbre->partial[i].wg.waveform);
|
|
}
|
|
#endif
|
|
}
|
|
|
|
const char *Part::getName() const {
|
|
return name;
|
|
}
|
|
|
|
void Part::setVolume(unsigned int midiVolume) {
|
|
// CONFIRMED: This calculation matches the table used in the control ROM
|
|
patchTemp->outputLevel = Bit8u(midiVolume * 100 / 127);
|
|
//synth->printDebug("%s (%s): Set volume to %d", name, currentInstr, midiVolume);
|
|
}
|
|
|
|
Bit8u Part::getVolume() const {
|
|
return patchTemp->outputLevel;
|
|
}
|
|
|
|
Bit8u Part::getExpression() const {
|
|
return expression;
|
|
}
|
|
|
|
void Part::setExpression(unsigned int midiExpression) {
|
|
// CONFIRMED: This calculation matches the table used in the control ROM
|
|
expression = Bit8u(midiExpression * 100 / 127);
|
|
}
|
|
|
|
void RhythmPart::setPan(unsigned int midiPan) {
|
|
// CONFIRMED: This does change patchTemp, but has no actual effect on playback.
|
|
#if MT32EMU_MONITOR_MIDI > 0
|
|
synth->printDebug("%s: Pointlessly setting pan (%d) on rhythm part", name, midiPan);
|
|
#endif
|
|
Part::setPan(midiPan);
|
|
}
|
|
|
|
void Part::setPan(unsigned int midiPan) {
|
|
// NOTE: Panning is inverted compared to GM.
|
|
|
|
// CM-32L: Divide by 8.5
|
|
patchTemp->panpot = Bit8u((midiPan << 3) / 68);
|
|
// FIXME: MT-32: Divide by 9
|
|
//patchTemp->panpot = Bit8u(midiPan / 9);
|
|
|
|
//synth->printDebug("%s (%s): Set pan to %d", name, currentInstr, panpot);
|
|
}
|
|
|
|
/**
|
|
* Applies key shift to a MIDI key and converts it into an internal key value in the range 12-108.
|
|
*/
|
|
unsigned int Part::midiKeyToKey(unsigned int midiKey) {
|
|
int key = midiKey + patchTemp->patch.keyShift;
|
|
if (key < 36) {
|
|
// After keyShift is applied, key < 36, so move up by octaves
|
|
while (key < 36) {
|
|
key += 12;
|
|
}
|
|
} else if (key > 132) {
|
|
// After keyShift is applied, key > 132, so move down by octaves
|
|
while (key > 132) {
|
|
key -= 12;
|
|
}
|
|
}
|
|
key -= 24;
|
|
return key;
|
|
}
|
|
|
|
void RhythmPart::noteOn(unsigned int midiKey, unsigned int velocity) {
|
|
if (midiKey < 24 || midiKey > 108) { /*> 87 on MT-32)*/
|
|
synth->printDebug("%s: Attempted to play invalid key %d (velocity %d)", name, midiKey, velocity);
|
|
return;
|
|
}
|
|
unsigned int key = midiKey;
|
|
unsigned int drumNum = key - 24;
|
|
int drumTimbreNum = rhythmTemp[drumNum].timbre;
|
|
const int drumTimbreCount = 64 + synth->controlROMMap->timbreRCount; // 94 on MT-32, 128 on LAPC-I/CM32-L
|
|
if (drumTimbreNum == 127 || drumTimbreNum >= drumTimbreCount) { // timbre #127 is OFF, no sense to play it
|
|
synth->printDebug("%s: Attempted to play unmapped key %d (velocity %d)", name, midiKey, velocity);
|
|
return;
|
|
}
|
|
// CONFIRMED: Two special cases described by Mok
|
|
if (drumTimbreNum == 64 + 6) {
|
|
noteOff(0);
|
|
key = 1;
|
|
} else if (drumTimbreNum == 64 + 7) {
|
|
// This noteOff(0) is not performed on MT-32, only LAPC-I
|
|
noteOff(0);
|
|
key = 0;
|
|
}
|
|
int absTimbreNum = drumTimbreNum + 128;
|
|
TimbreParam *timbre = &synth->mt32ram.timbres[absTimbreNum].timbre;
|
|
memcpy(currentInstr, timbre->common.name, 10);
|
|
if (drumCache[drumNum][0].dirty) {
|
|
cacheTimbre(drumCache[drumNum], timbre);
|
|
}
|
|
#if MT32EMU_MONITOR_INSTRUMENTS > 0
|
|
synth->printDebug("%s (%s): Start poly (drum %d, timbre %d): midiKey %u, key %u, velo %u, mod %u, exp %u, bend %u", name, currentInstr, drumNum, absTimbreNum, midiKey, key, velocity, modulation, expression, pitchBend);
|
|
#if MT32EMU_MONITOR_INSTRUMENTS > 1
|
|
// According to info from Mok, keyShift does not appear to affect anything on rhythm part on LAPC-I, but may do on MT-32 - needs investigation
|
|
synth->printDebug(" Patch: (timbreGroup %u), (timbreNum %u), (keyShift %u), fineTune %u, benderRange %u, assignMode %u, (reverbSwitch %u)", patchTemp->patch.timbreGroup, patchTemp->patch.timbreNum, patchTemp->patch.keyShift, patchTemp->patch.fineTune, patchTemp->patch.benderRange, patchTemp->patch.assignMode, patchTemp->patch.reverbSwitch);
|
|
synth->printDebug(" PatchTemp: outputLevel %u, (panpot %u)", patchTemp->outputLevel, patchTemp->panpot);
|
|
synth->printDebug(" RhythmTemp: timbre %u, outputLevel %u, panpot %u, reverbSwitch %u", rhythmTemp[drumNum].timbre, rhythmTemp[drumNum].outputLevel, rhythmTemp[drumNum].panpot, rhythmTemp[drumNum].reverbSwitch);
|
|
#endif
|
|
#endif
|
|
playPoly(drumCache[drumNum], &rhythmTemp[drumNum], midiKey, key, velocity);
|
|
}
|
|
|
|
void Part::noteOn(unsigned int midiKey, unsigned int velocity) {
|
|
unsigned int key = midiKeyToKey(midiKey);
|
|
if (patchCache[0].dirty) {
|
|
cacheTimbre(patchCache, timbreTemp);
|
|
}
|
|
#if MT32EMU_MONITOR_INSTRUMENTS > 0
|
|
synth->printDebug("%s (%s): Start poly: midiKey %u, key %u, velo %u, mod %u, exp %u, bend %u", name, currentInstr, midiKey, key, velocity, modulation, expression, pitchBend);
|
|
#if MT32EMU_MONITOR_INSTRUMENTS > 1
|
|
synth->printDebug(" Patch: timbreGroup %u, timbreNum %u, keyShift %u, fineTune %u, benderRange %u, assignMode %u, reverbSwitch %u", patchTemp->patch.timbreGroup, patchTemp->patch.timbreNum, patchTemp->patch.keyShift, patchTemp->patch.fineTune, patchTemp->patch.benderRange, patchTemp->patch.assignMode, patchTemp->patch.reverbSwitch);
|
|
synth->printDebug(" PatchTemp: outputLevel %u, panpot %u", patchTemp->outputLevel, patchTemp->panpot);
|
|
#endif
|
|
#endif
|
|
playPoly(patchCache, NULL, midiKey, key, velocity);
|
|
}
|
|
|
|
bool Part::abortFirstPoly(unsigned int key) {
|
|
for (Poly *poly = activePolys.getFirst(); poly != NULL; poly = poly->getNext()) {
|
|
if (poly->getKey() == key) {
|
|
return poly->startAbort();
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool Part::abortFirstPoly(PolyState polyState) {
|
|
for (Poly *poly = activePolys.getFirst(); poly != NULL; poly = poly->getNext()) {
|
|
if (poly->getState() == polyState) {
|
|
return poly->startAbort();
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool Part::abortFirstPolyPreferHeld() {
|
|
if (abortFirstPoly(POLY_Held)) {
|
|
return true;
|
|
}
|
|
return abortFirstPoly();
|
|
}
|
|
|
|
bool Part::abortFirstPoly() {
|
|
if (activePolys.isEmpty()) {
|
|
return false;
|
|
}
|
|
return activePolys.getFirst()->startAbort();
|
|
}
|
|
|
|
void Part::playPoly(const PatchCache cache[4], const MemParams::RhythmTemp *rhythmTemp, unsigned int midiKey, unsigned int key, unsigned int velocity) {
|
|
// CONFIRMED: Even in single-assign mode, we don't abort playing polys if the timbre to play is completely muted.
|
|
unsigned int needPartials = cache[0].partialCount;
|
|
if (needPartials == 0) {
|
|
synth->printDebug("%s (%s): Completely muted instrument", name, currentInstr);
|
|
return;
|
|
}
|
|
|
|
if ((patchTemp->patch.assignMode & 2) == 0) {
|
|
// Single-assign mode
|
|
abortFirstPoly(key);
|
|
if (synth->isAbortingPoly()) return;
|
|
}
|
|
|
|
if (!synth->partialManager->freePartials(needPartials, partNum)) {
|
|
#if MT32EMU_MONITOR_PARTIALS > 0
|
|
synth->printDebug("%s (%s): Insufficient free partials to play key %d (velocity %d); needed=%d, free=%d, assignMode=%d", name, currentInstr, midiKey, velocity, needPartials, synth->partialManager->getFreePartialCount(), patchTemp->patch.assignMode);
|
|
synth->printPartialUsage();
|
|
#endif
|
|
return;
|
|
}
|
|
if (synth->isAbortingPoly()) return;
|
|
|
|
Poly *poly = synth->partialManager->assignPolyToPart(this);
|
|
if (poly == NULL) {
|
|
synth->printDebug("%s (%s): No free poly to play key %d (velocity %d)", name, currentInstr, midiKey, velocity);
|
|
return;
|
|
}
|
|
if (patchTemp->patch.assignMode & 1) {
|
|
// Priority to data first received
|
|
activePolys.prepend(poly);
|
|
} else {
|
|
activePolys.append(poly);
|
|
}
|
|
|
|
Partial *partials[4];
|
|
for (int x = 0; x < 4; x++) {
|
|
if (cache[x].playPartial) {
|
|
partials[x] = synth->partialManager->allocPartial(partNum);
|
|
activePartialCount++;
|
|
} else {
|
|
partials[x] = NULL;
|
|
}
|
|
}
|
|
poly->reset(key, velocity, cache[0].sustain, partials);
|
|
|
|
for (int x = 0; x < 4; x++) {
|
|
if (partials[x] != NULL) {
|
|
#if MT32EMU_MONITOR_PARTIALS > 2
|
|
synth->printDebug("%s (%s): Allocated partial %d", name, currentInstr, partials[x]->debugGetPartialNum());
|
|
#endif
|
|
partials[x]->startPartial(this, poly, &cache[x], rhythmTemp, partials[cache[x].structurePair]);
|
|
}
|
|
}
|
|
#if MT32EMU_MONITOR_PARTIALS > 1
|
|
synth->printPartialUsage();
|
|
#endif
|
|
synth->reportHandler->onPolyStateChanged(Bit8u(partNum));
|
|
}
|
|
|
|
void Part::allNotesOff() {
|
|
// The MIDI specification states - and Mok confirms - that all notes off (0x7B)
|
|
// should treat the hold pedal as usual.
|
|
for (Poly *poly = activePolys.getFirst(); poly != NULL; poly = poly->getNext()) {
|
|
// FIXME: This has special handling of key 0 in NoteOff that Mok has not yet confirmed applies to AllNotesOff.
|
|
// if (poly->canSustain() || poly->getKey() == 0) {
|
|
// FIXME: The real devices are found to be ignoring non-sustaining polys while processing AllNotesOff. Need to be confirmed.
|
|
if (poly->canSustain()) {
|
|
poly->noteOff(holdpedal);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Part::allSoundOff() {
|
|
// MIDI "All sound off" (0x78) should release notes immediately regardless of the hold pedal.
|
|
// This controller is not actually implemented by the synths, though (according to the docs and Mok) -
|
|
// we're only using this method internally.
|
|
for (Poly *poly = activePolys.getFirst(); poly != NULL; poly = poly->getNext()) {
|
|
poly->startDecay();
|
|
}
|
|
}
|
|
|
|
void Part::stopPedalHold() {
|
|
for (Poly *poly = activePolys.getFirst(); poly != NULL; poly = poly->getNext()) {
|
|
poly->stopPedalHold();
|
|
}
|
|
}
|
|
|
|
void RhythmPart::noteOff(unsigned int midiKey) {
|
|
stopNote(midiKey);
|
|
}
|
|
|
|
void Part::noteOff(unsigned int midiKey) {
|
|
stopNote(midiKeyToKey(midiKey));
|
|
}
|
|
|
|
void Part::stopNote(unsigned int key) {
|
|
#if MT32EMU_MONITOR_INSTRUMENTS > 0
|
|
synth->printDebug("%s (%s): stopping key %d", name, currentInstr, key);
|
|
#endif
|
|
|
|
for (Poly *poly = activePolys.getFirst(); poly != NULL; poly = poly->getNext()) {
|
|
// Generally, non-sustaining instruments ignore note off. They die away eventually anyway.
|
|
// Key 0 (only used by special cases on rhythm part) reacts to note off even if non-sustaining or pedal held.
|
|
if (poly->getKey() == key && (poly->canSustain() || key == 0)) {
|
|
if (poly->noteOff(holdpedal && key != 0)) {
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const MemParams::PatchTemp *Part::getPatchTemp() const {
|
|
return patchTemp;
|
|
}
|
|
|
|
unsigned int Part::getActivePartialCount() const {
|
|
return activePartialCount;
|
|
}
|
|
|
|
const Poly *Part::getFirstActivePoly() const {
|
|
return activePolys.getFirst();
|
|
}
|
|
|
|
unsigned int Part::getActiveNonReleasingPartialCount() const {
|
|
unsigned int activeNonReleasingPartialCount = 0;
|
|
for (Poly *poly = activePolys.getFirst(); poly != NULL; poly = poly->getNext()) {
|
|
if (poly->getState() != POLY_Releasing) {
|
|
activeNonReleasingPartialCount += poly->getActivePartialCount();
|
|
}
|
|
}
|
|
return activeNonReleasingPartialCount;
|
|
}
|
|
|
|
Synth *Part::getSynth() const {
|
|
return synth;
|
|
}
|
|
|
|
void Part::partialDeactivated(Poly *poly) {
|
|
activePartialCount--;
|
|
if (!poly->isActive()) {
|
|
activePolys.remove(poly);
|
|
synth->partialManager->polyFreed(poly);
|
|
synth->reportHandler->onPolyStateChanged(Bit8u(partNum));
|
|
}
|
|
}
|
|
|
|
PolyList::PolyList() : firstPoly(NULL), lastPoly(NULL) {}
|
|
|
|
bool PolyList::isEmpty() const {
|
|
#ifdef MT32EMU_POLY_LIST_DEBUG
|
|
if ((firstPoly == NULL || lastPoly == NULL) && firstPoly != lastPoly) {
|
|
printf("PolyList: desynchronised firstPoly & lastPoly pointers\n");
|
|
}
|
|
#endif
|
|
return firstPoly == NULL && lastPoly == NULL;
|
|
}
|
|
|
|
Poly *PolyList::getFirst() const {
|
|
return firstPoly;
|
|
}
|
|
|
|
Poly *PolyList::getLast() const {
|
|
return lastPoly;
|
|
}
|
|
|
|
void PolyList::prepend(Poly *poly) {
|
|
#ifdef MT32EMU_POLY_LIST_DEBUG
|
|
if (poly->getNext() != NULL) {
|
|
printf("PolyList: Non-NULL next field in a Poly being prepended is ignored\n");
|
|
}
|
|
#endif
|
|
poly->setNext(firstPoly);
|
|
firstPoly = poly;
|
|
if (lastPoly == NULL) {
|
|
lastPoly = poly;
|
|
}
|
|
}
|
|
|
|
void PolyList::append(Poly *poly) {
|
|
#ifdef MT32EMU_POLY_LIST_DEBUG
|
|
if (poly->getNext() != NULL) {
|
|
printf("PolyList: Non-NULL next field in a Poly being appended is ignored\n");
|
|
}
|
|
#endif
|
|
poly->setNext(NULL);
|
|
if (lastPoly != NULL) {
|
|
#ifdef MT32EMU_POLY_LIST_DEBUG
|
|
if (lastPoly->getNext() != NULL) {
|
|
printf("PolyList: Non-NULL next field in the lastPoly\n");
|
|
}
|
|
#endif
|
|
lastPoly->setNext(poly);
|
|
}
|
|
lastPoly = poly;
|
|
if (firstPoly == NULL) {
|
|
firstPoly = poly;
|
|
}
|
|
}
|
|
|
|
Poly *PolyList::takeFirst() {
|
|
Poly *oldFirst = firstPoly;
|
|
firstPoly = oldFirst->getNext();
|
|
if (firstPoly == NULL) {
|
|
#ifdef MT32EMU_POLY_LIST_DEBUG
|
|
if (lastPoly != oldFirst) {
|
|
printf("PolyList: firstPoly != lastPoly in a list with a single Poly\n");
|
|
}
|
|
#endif
|
|
lastPoly = NULL;
|
|
}
|
|
oldFirst->setNext(NULL);
|
|
return oldFirst;
|
|
}
|
|
|
|
void PolyList::remove(Poly * const polyToRemove) {
|
|
if (polyToRemove == firstPoly) {
|
|
takeFirst();
|
|
return;
|
|
}
|
|
for (Poly *poly = firstPoly; poly != NULL; poly = poly->getNext()) {
|
|
if (poly->getNext() == polyToRemove) {
|
|
if (polyToRemove == lastPoly) {
|
|
#ifdef MT32EMU_POLY_LIST_DEBUG
|
|
if (lastPoly->getNext() != NULL) {
|
|
printf("PolyList: Non-NULL next field in the lastPoly\n");
|
|
}
|
|
#endif
|
|
lastPoly = poly;
|
|
}
|
|
poly->setNext(polyToRemove->getNext());
|
|
polyToRemove->setNext(NULL);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace MT32Emu
|