4 |
* * |
* * |
5 |
* Copyright (C) 2003,2004 by Benno Senoner and Christian Schoenebeck * |
* Copyright (C) 2003,2004 by Benno Senoner and Christian Schoenebeck * |
6 |
* Copyright (C) 2005-2008 Christian Schoenebeck * |
* Copyright (C) 2005-2008 Christian Schoenebeck * |
7 |
* Copyright (C) 2009-2010 Christian Schoenebeck and Grigor Iliev * |
* Copyright (C) 2009-2013 Christian Schoenebeck and Grigor Iliev * |
8 |
* * |
* * |
9 |
* This program is free software; you can redistribute it and/or modify * |
* This program is free software; you can redistribute it and/or modify * |
10 |
* it under the terms of the GNU General Public License as published by * |
* it under the terms of the GNU General Public License as published by * |
104 |
* @param Samples - number of sample points to be rendered |
* @param Samples - number of sample points to be rendered |
105 |
* @returns 0 on success |
* @returns 0 on success |
106 |
*/ |
*/ |
107 |
virtual int RenderAudio(uint Samples) { |
virtual int RenderAudio(uint Samples) OVERRIDE { |
108 |
dmsg(8,("RenderAudio(Samples=%d)\n", Samples)); |
dmsg(8,("RenderAudio(Samples=%d)\n", Samples)); |
109 |
|
|
110 |
// return if engine disabled |
// return if engine disabled |
190 |
// been deleted by the disk thread |
// been deleted by the disk thread |
191 |
if (iPendingStreamDeletions) ProcessPendingStreamDeletions(); |
if (iPendingStreamDeletions) ProcessPendingStreamDeletions(); |
192 |
|
|
193 |
|
// Release the instrument change command. (This has to |
194 |
|
// be done after all voices have been rendered and not |
195 |
|
// in HandleInstrumentChanges, as the RegionsInUse |
196 |
|
// list has been built up by the voice renderers.) |
197 |
|
for (int i = 0; i < engineChannels.size(); i++) { |
198 |
|
EngineChannelBase<V, R, I>* channel = |
199 |
|
static_cast<EngineChannelBase<V, R, I>*>(engineChannels[i]); |
200 |
|
channel->InstrumentChangeCommandReader.Unlock(); |
201 |
|
} |
202 |
FrameTime += Samples; |
FrameTime += Samples; |
203 |
|
|
204 |
EngineDisabled.RttDone(); |
EngineDisabled.RttDone(); |
205 |
return 0; |
return 0; |
206 |
} |
} |
207 |
|
|
208 |
virtual int MaxVoices() { return pVoicePool->poolSize(); } |
virtual int MaxVoices() OVERRIDE { return pVoicePool->poolSize(); } |
209 |
|
|
210 |
virtual void SetMaxVoices(int iVoices) throw (Exception) { |
virtual void SetMaxVoices(int iVoices) throw (Exception) OVERRIDE { |
211 |
if (iVoices < 1) |
if (iVoices < 1) |
212 |
throw Exception("Maximum voices for an engine cannot be set lower than 1"); |
throw Exception("Maximum voices for an engine cannot be set lower than 1"); |
213 |
|
|
243 |
} |
} |
244 |
pVoicePool->clear(); |
pVoicePool->clear(); |
245 |
|
|
246 |
|
PostSetMaxVoices(iVoices); |
247 |
ResumeAll(); |
ResumeAll(); |
248 |
} |
} |
249 |
|
|
250 |
|
/** Called after the new max number of voices is set and before resuming the engine. */ |
251 |
|
virtual void PostSetMaxVoices(int iVoices) { } |
252 |
|
|
253 |
virtual uint DiskStreamCount() { return (pDiskThread) ? pDiskThread->GetActiveStreamCount() : 0; } |
virtual uint DiskStreamCount() OVERRIDE { return (pDiskThread) ? pDiskThread->GetActiveStreamCount() : 0; } |
254 |
virtual uint DiskStreamCountMax() { return (pDiskThread) ? pDiskThread->ActiveStreamCountMax : 0; } |
virtual uint DiskStreamCountMax() OVERRIDE { return (pDiskThread) ? pDiskThread->ActiveStreamCountMax : 0; } |
255 |
virtual int MaxDiskStreams() { return iMaxDiskStreams; } |
virtual int MaxDiskStreams() OVERRIDE { return iMaxDiskStreams; } |
256 |
|
|
257 |
virtual void SetMaxDiskStreams(int iStreams) throw (Exception) { |
virtual void SetMaxDiskStreams(int iStreams) throw (Exception) OVERRIDE { |
258 |
if (iStreams < 0) |
if (iStreams < 0) |
259 |
throw Exception("Maximum disk streams for an engine cannot be set lower than 0"); |
throw Exception("Maximum disk streams for an engine cannot be set lower than 0"); |
260 |
|
|
269 |
ResumeAll(); |
ResumeAll(); |
270 |
} |
} |
271 |
|
|
272 |
virtual String DiskStreamBufferFillBytes() { return (pDiskThread) ? pDiskThread->GetBufferFillBytes() : ""; } |
virtual String DiskStreamBufferFillBytes() OVERRIDE { return (pDiskThread) ? pDiskThread->GetBufferFillBytes() : ""; } |
273 |
virtual String DiskStreamBufferFillPercentage() { return (pDiskThread) ? pDiskThread->GetBufferFillPercentage() : ""; } |
virtual String DiskStreamBufferFillPercentage() OVERRIDE { return (pDiskThread) ? pDiskThread->GetBufferFillPercentage() : ""; } |
274 |
virtual InstrumentManager* GetInstrumentManager() { return &instruments; } |
virtual InstrumentManager* GetInstrumentManager() OVERRIDE { return &instruments; } |
275 |
|
|
276 |
/** |
/** |
277 |
* Connect this engine instance with the given audio output device. |
* Connect this engine instance with the given audio output device. |
282 |
* |
* |
283 |
* @param pAudioOut - audio output device to connect to |
* @param pAudioOut - audio output device to connect to |
284 |
*/ |
*/ |
285 |
virtual void Connect(AudioOutputDevice* pAudioOut) { |
virtual void Connect(AudioOutputDevice* pAudioOut) OVERRIDE { |
286 |
// caution: don't ignore if connecting to the same device here, |
// caution: don't ignore if connecting to the same device here, |
287 |
// because otherwise SetMaxDiskStreams() implementation won't work anymore! |
// because otherwise SetMaxDiskStreams() implementation won't work anymore! |
288 |
|
|
312 |
// lower minimum release time |
// lower minimum release time |
313 |
const float minReleaseTime = (float) MaxSamplesPerCycle / (float) SampleRate; |
const float minReleaseTime = (float) MaxSamplesPerCycle / (float) SampleRate; |
314 |
for (VoiceIterator iterVoice = pVoicePool->allocAppend(); iterVoice == pVoicePool->last(); iterVoice = pVoicePool->allocAppend()) { |
for (VoiceIterator iterVoice = pVoicePool->allocAppend(); iterVoice == pVoicePool->last(); iterVoice = pVoicePool->allocAppend()) { |
315 |
iterVoice->pEG1->CalculateFadeOutCoeff(minReleaseTime, SampleRate); |
iterVoice->CalculateFadeOutCoeff(minReleaseTime, SampleRate); |
316 |
} |
} |
317 |
pVoicePool->clear(); |
pVoicePool->clear(); |
318 |
} |
} |
345 |
pDiskThread->StartThread(); |
pDiskThread->StartThread(); |
346 |
dmsg(1,("OK\n")); |
dmsg(1,("OK\n")); |
347 |
|
|
348 |
|
bool printEqInfo = true; |
349 |
for (VoiceIterator iterVoice = pVoicePool->allocAppend(); iterVoice == pVoicePool->last(); iterVoice = pVoicePool->allocAppend()) { |
for (VoiceIterator iterVoice = pVoicePool->allocAppend(); iterVoice == pVoicePool->last(); iterVoice = pVoicePool->allocAppend()) { |
350 |
if (!iterVoice->pDiskThread) { |
if (!iterVoice->pDiskThread) { |
351 |
dmsg(0,("Engine -> voice::trigger: !pDiskThread\n")); |
dmsg(0,("Engine -> voice::trigger: !pDiskThread\n")); |
352 |
exit(EXIT_FAILURE); |
exit(EXIT_FAILURE); |
353 |
} |
} |
354 |
|
|
355 |
|
iterVoice->CreateEq(); |
356 |
|
|
357 |
|
if(printEqInfo) { |
358 |
|
iterVoice->PrintEqInfo(); |
359 |
|
printEqInfo = false; |
360 |
|
} |
361 |
} |
} |
362 |
pVoicePool->clear(); |
pVoicePool->clear(); |
363 |
|
|
364 |
|
// (re)create dedicated voice audio buffers |
365 |
|
//TODO: we could optimize resource usage a bit by just allocating these dedicated voice buffers when there is at least one engine channel with FX sends, because only in this case those special buffers are used actually, but since it would usually only save couple bytes in total, its probably not worth it |
366 |
|
if (pDedicatedVoiceChannelLeft) delete pDedicatedVoiceChannelLeft; |
367 |
|
if (pDedicatedVoiceChannelRight) delete pDedicatedVoiceChannelRight; |
368 |
|
pDedicatedVoiceChannelLeft = new AudioChannel(0, MaxSamplesPerCycle); |
369 |
|
pDedicatedVoiceChannelRight = new AudioChannel(1, MaxSamplesPerCycle); |
370 |
|
} |
371 |
|
|
372 |
|
// Implementattion for abstract method derived from Engine. |
373 |
|
virtual void ReconnectAudioOutputDevice() OVERRIDE { |
374 |
|
SuspendAll(); |
375 |
|
if (pAudioOutputDevice) Connect(pAudioOutputDevice); |
376 |
|
ResumeAll(); |
377 |
} |
} |
378 |
|
|
379 |
/** |
/** |
429 |
*/ |
*/ |
430 |
virtual void Suspend(RR* pRegion) { |
virtual void Suspend(RR* pRegion) { |
431 |
dmsg(2,("EngineBase: Suspending Region %x ...\n",pRegion)); |
dmsg(2,("EngineBase: Suspending Region %x ...\n",pRegion)); |
432 |
SuspendedRegionsMutex.Lock(); |
{ |
433 |
SuspensionChangeOngoing.Set(true); |
LockGuard lock(SuspendedRegionsMutex); |
434 |
pPendingRegionSuspension = pRegion; |
SuspensionChangeOngoing.Set(true); |
435 |
SuspensionChangeOngoing.WaitAndUnlockIf(true); |
pPendingRegionSuspension = pRegion; |
436 |
SuspendedRegionsMutex.Unlock(); |
SuspensionChangeOngoing.WaitAndUnlockIf(true); |
437 |
|
} |
438 |
dmsg(2,("EngineBase: Region %x suspended.",pRegion)); |
dmsg(2,("EngineBase: Region %x suspended.",pRegion)); |
439 |
} |
} |
440 |
|
|
446 |
*/ |
*/ |
447 |
virtual void Resume(RR* pRegion) { |
virtual void Resume(RR* pRegion) { |
448 |
dmsg(2,("EngineBase: Resuming Region %x ...\n",pRegion)); |
dmsg(2,("EngineBase: Resuming Region %x ...\n",pRegion)); |
449 |
SuspendedRegionsMutex.Lock(); |
{ |
450 |
SuspensionChangeOngoing.Set(true); |
LockGuard lock(SuspendedRegionsMutex); |
451 |
pPendingRegionResumption = pRegion; |
SuspensionChangeOngoing.Set(true); |
452 |
SuspensionChangeOngoing.WaitAndUnlockIf(true); |
pPendingRegionResumption = pRegion; |
453 |
SuspendedRegionsMutex.Unlock(); |
SuspensionChangeOngoing.WaitAndUnlockIf(true); |
454 |
|
} |
455 |
dmsg(2,("EngineBase: Region %x resumed.\n",pRegion)); |
dmsg(2,("EngineBase: Region %x resumed.\n",pRegion)); |
456 |
} |
} |
457 |
|
|
567 |
|
|
568 |
//friend class EngineChannelBase<V, R, I>; |
//friend class EngineChannelBase<V, R, I>; |
569 |
|
|
570 |
|
static IM instruments; |
571 |
|
|
572 |
protected: |
protected: |
573 |
class SuspensionVoiceHandler : public MidiKeyboardManager<V>::VoiceHandler { |
class SuspensionVoiceHandler : public MidiKeyboardManager<V>::VoiceHandler { |
574 |
public: |
public: |
575 |
int PendingStreamDeletions; |
int PendingStreamDeletions; |
576 |
RR* pPendingRegionSuspension; |
RR* pPendingRegionSuspension; |
577 |
|
|
578 |
SuspensionVoiceHandler(RR* pPendingRegionSuspension) { |
SuspensionVoiceHandler(RR* pPendingRegionSuspension) { |
579 |
PendingStreamDeletions = 0; |
PendingStreamDeletions = 0; |
580 |
this->pPendingRegionSuspension = pPendingRegionSuspension; |
this->pPendingRegionSuspension = pPendingRegionSuspension; |
581 |
} |
} |
582 |
|
|
583 |
virtual bool Process(MidiKey* pMidiKey) { |
virtual bool Process(MidiKey* pMidiKey) OVERRIDE { |
584 |
VoiceIterator itVoice = pMidiKey->pActiveVoices->first(); |
VoiceIterator itVoice = pMidiKey->pActiveVoices->first(); |
585 |
// if current key is not associated with this region, skip this key |
// if current key is not associated with this region, skip this key |
586 |
if (itVoice->GetRegion()->GetParent() != pPendingRegionSuspension) return false; |
if (itVoice->GetRegion()->GetParent() != pPendingRegionSuspension) return false; |
588 |
return true; |
return true; |
589 |
} |
} |
590 |
|
|
591 |
virtual void Process(VoiceIterator& itVoice) { |
virtual void Process(VoiceIterator& itVoice) OVERRIDE { |
592 |
// request a notification from disk thread side for stream deletion |
// request a notification from disk thread side for stream deletion |
593 |
const Stream::Handle hStream = itVoice->KillImmediately(true); |
const Stream::Handle hStream = itVoice->KillImmediately(true); |
594 |
if (hStream != Stream::INVALID_HANDLE) { // voice actually used a stream |
if (hStream != Stream::INVALID_HANDLE) { // voice actually used a stream |
598 |
} |
} |
599 |
}; |
}; |
600 |
|
|
|
static IM instruments; |
|
|
|
|
601 |
Pool<R*>* pRegionPool[2]; ///< Double buffered pool, used by the engine channels to keep track of regions in use. |
Pool<R*>* pRegionPool[2]; ///< Double buffered pool, used by the engine channels to keep track of regions in use. |
602 |
int MinFadeOutSamples; ///< The number of samples needed to make an instant fade out (e.g. for voice stealing) without leading to clicks. |
int MinFadeOutSamples; ///< The number of samples needed to make an instant fade out (e.g. for voice stealing) without leading to clicks. |
603 |
D* pDiskThread; |
D* pDiskThread; |
803 |
//TODO: this is a lazy solution ATM and not safe in case somebody is currently editing the instrument we're currently switching to (we should store all suspended regions on instrument manager side and when switching to another instrument copy that list to the engine's local list of suspensions |
//TODO: this is a lazy solution ATM and not safe in case somebody is currently editing the instrument we're currently switching to (we should store all suspended regions on instrument manager side and when switching to another instrument copy that list to the engine's local list of suspensions |
804 |
ResetSuspendedRegions(); |
ResetSuspendedRegions(); |
805 |
} |
} |
|
|
|
|
for (int i = 0; i < engineChannels.size(); i++) { |
|
|
EngineChannelBase<V, R, I>* channel = |
|
|
static_cast<EngineChannelBase<V, R, I>*>(engineChannels[i]); |
|
|
channel->InstrumentChangeCommandReader.Unlock(); |
|
|
} |
|
806 |
} |
} |
807 |
|
|
808 |
/** |
/** |
889 |
pChannel->ClearEventLists(); |
pChannel->ClearEventLists(); |
890 |
} |
} |
891 |
|
|
892 |
|
/** |
893 |
|
* Process MIDI control change events with hard coded behavior, |
894 |
|
* that is controllers whose behavior is defined independently |
895 |
|
* of the actual sampler engine type and instrument. |
896 |
|
* |
897 |
|
* @param pEngineChannel - engine channel on which the MIDI CC event was received |
898 |
|
* @param itControlChangeEvent - the actual MIDI CC event |
899 |
|
*/ |
900 |
void ProcessHardcodedControllers ( |
void ProcessHardcodedControllers ( |
901 |
EngineChannel* pEngineChannel, |
EngineChannel* pEngineChannel, |
902 |
Pool<Event>::Iterator& itControlChangeEvent |
Pool<Event>::Iterator& itControlChangeEvent |
909 |
pChannel->PortamentoTime = (float) itControlChangeEvent->Param.CC.Value / 127.0f * (float) CONFIG_PORTAMENTO_TIME_MAX + (float) CONFIG_PORTAMENTO_TIME_MIN; |
pChannel->PortamentoTime = (float) itControlChangeEvent->Param.CC.Value / 127.0f * (float) CONFIG_PORTAMENTO_TIME_MAX + (float) CONFIG_PORTAMENTO_TIME_MIN; |
910 |
break; |
break; |
911 |
} |
} |
912 |
case 6: { // data entry (currently only used for RPN controllers) |
case 6: { // data entry (currently only used for RPN and NRPN controllers) |
913 |
if (pChannel->GetMidiRpnController() == 2) { // coarse tuning in half tones |
//dmsg(1,("DATA ENTRY %d\n", itControlChangeEvent->Param.CC.Value)); |
914 |
int transpose = (int) itControlChangeEvent->Param.CC.Value - 64; |
if (pChannel->GetMidiRpnController() >= 0) { // RPN controller number was sent previously ... |
915 |
// limit to +- two octaves for now |
dmsg(4,("Guess it's an RPN ...\n")); |
916 |
transpose = RTMath::Min(transpose, 24); |
if (pChannel->GetMidiRpnController() == 2) { // coarse tuning in half tones |
917 |
transpose = RTMath::Max(transpose, -24); |
int transpose = (int) itControlChangeEvent->Param.CC.Value - 64; |
918 |
pChannel->GlobalTranspose = transpose; |
// limit to +- two octaves for now |
919 |
// workaround, so we won't have hanging notes |
transpose = RTMath::Min(transpose, 24); |
920 |
pChannel->ReleaseAllVoices(itControlChangeEvent); |
transpose = RTMath::Max(transpose, -24); |
921 |
|
pChannel->GlobalTranspose = transpose; |
922 |
|
// workaround, so we won't have hanging notes |
923 |
|
pChannel->ReleaseAllVoices(itControlChangeEvent); |
924 |
|
} |
925 |
|
// to prevent other MIDI CC #6 messages to be misenterpreted as RPN controller data |
926 |
|
pChannel->ResetMidiRpnController(); |
927 |
|
} else if (pChannel->GetMidiNrpnController() >= 0) { // NRPN controller number was sent previously ... |
928 |
|
dmsg(4,("Guess it's an NRPN ...\n")); |
929 |
|
const int NrpnCtrlMSB = pChannel->GetMidiNrpnController() >> 8; |
930 |
|
const int NrpnCtrlLSB = pChannel->GetMidiNrpnController() & 0xff; |
931 |
|
dmsg(4,("NRPN MSB=%d LSB=%d Data=%d\n", NrpnCtrlMSB, NrpnCtrlLSB, itControlChangeEvent->Param.CC.Value)); |
932 |
|
switch (NrpnCtrlMSB) { |
933 |
|
case 0x1a: { // volume level of note (Roland GS NRPN) |
934 |
|
const uint note = NrpnCtrlLSB; |
935 |
|
const uint vol = itControlChangeEvent->Param.CC.Value; |
936 |
|
dmsg(4,("Note Volume NRPN received (note=%d,vol=%d).\n", note, vol)); |
937 |
|
if (note < 128 && vol < 128) |
938 |
|
pChannel->pMIDIKeyInfo[note].Volume = VolumeCurve[vol]; |
939 |
|
break; |
940 |
|
} |
941 |
|
case 0x1c: { // panpot of note (Roland GS NRPN) |
942 |
|
const uint note = NrpnCtrlLSB; |
943 |
|
const uint pan = itControlChangeEvent->Param.CC.Value; |
944 |
|
dmsg(4,("Note Pan NRPN received (note=%d,pan=%d).\n", note, pan)); |
945 |
|
if (note < 128 && pan < 128) { |
946 |
|
pChannel->pMIDIKeyInfo[note].PanLeft = PanCurve[128 - pan]; |
947 |
|
pChannel->pMIDIKeyInfo[note].PanRight = PanCurve[pan]; |
948 |
|
} |
949 |
|
break; |
950 |
|
} |
951 |
|
case 0x1d: { // reverb send of note (Roland GS NRPN) |
952 |
|
const uint note = NrpnCtrlLSB; |
953 |
|
const float reverb = float(itControlChangeEvent->Param.CC.Value) / 127.0f; |
954 |
|
dmsg(4,("Note Reverb Send NRPN received (note=%d,send=%d).\n", note, reverb)); |
955 |
|
if (note < 128) |
956 |
|
pChannel->pMIDIKeyInfo[note].ReverbSend = reverb; |
957 |
|
break; |
958 |
|
} |
959 |
|
case 0x1e: { // chorus send of note (Roland GS NRPN) |
960 |
|
const uint note = NrpnCtrlLSB; |
961 |
|
const float chorus = float(itControlChangeEvent->Param.CC.Value) / 127.0f; |
962 |
|
dmsg(4,("Note Chorus Send NRPN received (note=%d,send=%d).\n", note, chorus)); |
963 |
|
if (note < 128) |
964 |
|
pChannel->pMIDIKeyInfo[note].ChorusSend = chorus; |
965 |
|
break; |
966 |
|
} |
967 |
|
} |
968 |
|
// to prevent other MIDI CC #6 messages to be misenterpreted as NRPN controller data |
969 |
|
pChannel->ResetMidiNrpnController(); |
970 |
} |
} |
|
// to avoid other MIDI CC #6 messages to be misenterpreted as RPN controller data |
|
|
pChannel->ResetMidiRpnController(); |
|
971 |
break; |
break; |
972 |
} |
} |
973 |
case 7: { // volume |
case 7: { // volume |
978 |
} |
} |
979 |
case 10: { // panpot |
case 10: { // panpot |
980 |
//TODO: not sample accurate yet |
//TODO: not sample accurate yet |
|
pChannel->GlobalPanLeft = PanCurve[128 - itControlChangeEvent->Param.CC.Value]; |
|
|
pChannel->GlobalPanRight = PanCurve[itControlChangeEvent->Param.CC.Value]; |
|
981 |
pChannel->iLastPanRequest = itControlChangeEvent->Param.CC.Value; |
pChannel->iLastPanRequest = itControlChangeEvent->Param.CC.Value; |
982 |
break; |
break; |
983 |
} |
} |
1054 |
} |
} |
1055 |
break; |
break; |
1056 |
} |
} |
1057 |
|
case 98: { // NRPN controller LSB |
1058 |
|
dmsg(4,("NRPN LSB %d\n", itControlChangeEvent->Param.CC.Value)); |
1059 |
|
pEngineChannel->SetMidiNrpnControllerLsb(itControlChangeEvent->Param.CC.Value); |
1060 |
|
break; |
1061 |
|
} |
1062 |
|
case 99: { // NRPN controller MSB |
1063 |
|
dmsg(4,("NRPN MSB %d\n", itControlChangeEvent->Param.CC.Value)); |
1064 |
|
pEngineChannel->SetMidiNrpnControllerMsb(itControlChangeEvent->Param.CC.Value); |
1065 |
|
break; |
1066 |
|
} |
1067 |
case 100: { // RPN controller LSB |
case 100: { // RPN controller LSB |
1068 |
|
dmsg(4,("RPN LSB %d\n", itControlChangeEvent->Param.CC.Value)); |
1069 |
pEngineChannel->SetMidiRpnControllerLsb(itControlChangeEvent->Param.CC.Value); |
pEngineChannel->SetMidiRpnControllerLsb(itControlChangeEvent->Param.CC.Value); |
1070 |
break; |
break; |
1071 |
} |
} |
1072 |
case 101: { // RPN controller MSB |
case 101: { // RPN controller MSB |
1073 |
|
dmsg(4,("RPN MSB %d\n", itControlChangeEvent->Param.CC.Value)); |
1074 |
pEngineChannel->SetMidiRpnControllerMsb(itControlChangeEvent->Param.CC.Value); |
pEngineChannel->SetMidiRpnControllerMsb(itControlChangeEvent->Param.CC.Value); |
1075 |
break; |
break; |
1076 |
} |
} |
1161 |
VoiceIterator itVoiceToBeKilled = pOtherKey->pActiveVoices->first(); |
VoiceIterator itVoiceToBeKilled = pOtherKey->pActiveVoices->first(); |
1162 |
VoiceIterator end = pOtherKey->pActiveVoices->end(); |
VoiceIterator end = pOtherKey->pActiveVoices->end(); |
1163 |
for (; itVoiceToBeKilled != end; ++itVoiceToBeKilled) { |
for (; itVoiceToBeKilled != end; ++itVoiceToBeKilled) { |
1164 |
if (itVoiceToBeKilled->Type != Voice::type_release_trigger) |
if (!(itVoiceToBeKilled->Type & Voice::type_release_trigger)) |
1165 |
itVoiceToBeKilled->Kill(itNoteOnEventOnKeyList); |
itVoiceToBeKilled->Kill(itNoteOnEventOnKeyList); |
1166 |
} |
} |
1167 |
} |
} |
1298 |
VoiceIterator itVoiceToBeKilled = pKey->pActiveVoices->first(); |
VoiceIterator itVoiceToBeKilled = pKey->pActiveVoices->first(); |
1299 |
VoiceIterator end = pKey->pActiveVoices->end(); |
VoiceIterator end = pKey->pActiveVoices->end(); |
1300 |
for (; itVoiceToBeKilled != end; ++itVoiceToBeKilled) { |
for (; itVoiceToBeKilled != end; ++itVoiceToBeKilled) { |
1301 |
if (itVoiceToBeKilled->Type != Voice::type_release_trigger) |
if (!(itVoiceToBeKilled->Type & Voice::type_release_trigger)) |
1302 |
itVoiceToBeKilled->Kill(itNoteOffEventOnKeyList); |
itVoiceToBeKilled->Kill(itNoteOffEventOnKeyList); |
1303 |
} |
} |
1304 |
} |
} |
1328 |
* control and status variables. This method is protected by a mutex. |
* control and status variables. This method is protected by a mutex. |
1329 |
*/ |
*/ |
1330 |
virtual void ResetInternal() { |
virtual void ResetInternal() { |
1331 |
ResetInternalMutex.Lock(); |
LockGuard lock(ResetInternalMutex); |
1332 |
|
|
1333 |
// make sure that the engine does not get any sysex messages |
// make sure that the engine does not get any sysex messages |
1334 |
// while it's reseting |
// while it's reseting |
1357 |
pEventQueue->init(); |
pEventQueue->init(); |
1358 |
pSysexBuffer->init(); |
pSysexBuffer->init(); |
1359 |
if (sysexDisabled) MidiInputPort::AddSysexListener(this); |
if (sysexDisabled) MidiInputPort::AddSysexListener(this); |
|
ResetInternalMutex.Unlock(); |
|
1360 |
} |
} |
1361 |
|
|
1362 |
/** |
/** |
1430 |
pKey->itSelf = pChannel->pActiveKeys->allocAppend(); |
pKey->itSelf = pChannel->pActiveKeys->allocAppend(); |
1431 |
*pKey->itSelf = itNoteOnEvent->Param.Note.Key; |
*pKey->itSelf = itNoteOnEvent->Param.Note.Key; |
1432 |
} |
} |
1433 |
if (itNewVoice->KeyGroup) { |
if (itNewVoice->Type & Voice::type_release_trigger_required) pKey->ReleaseTrigger = true; // mark key for the need of release triggered voice(s) |
|
uint** ppKeyGroup = &pChannel->ActiveKeyGroups[itNewVoice->KeyGroup]; |
|
|
*ppKeyGroup = &*pKey->itSelf; // put key as the (new) active key to its key group |
|
|
} |
|
|
if (itNewVoice->Type == Voice::type_release_trigger_required) pKey->ReleaseTrigger = true; // mark key for the need of release triggered voice(s) |
|
1434 |
return 0; // success |
return 0; // success |
1435 |
} |
} |
1436 |
} |
} |