--- linuxsampler/trunk/src/engines/EngineBase.h 2016/10/31 00:05:00 3034 +++ linuxsampler/trunk/src/engines/EngineBase.h 2017/07/30 14:33:15 3335 @@ -5,7 +5,7 @@ * Copyright (C) 2003,2004 by Benno Senoner and Christian Schoenebeck * * Copyright (C) 2005-2008 Christian Schoenebeck * * Copyright (C) 2009-2012 Christian Schoenebeck and Grigor Iliev * - * Copyright (C) 2012-2016 Christian Schoenebeck and Andreas Persson * + * Copyright (C) 2012-2017 Christian Schoenebeck and Andreas Persson * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * @@ -202,6 +202,23 @@ PostProcess(engineChannels[i]); } + // Just for debugging: dump the amount of free Note objects to + // the terminal (note due to the static variables being used, + // this is currently just intended for debugging with only one + // engine channel). + #if (CONFIG_DEBUG_LEVEL >= 3) + { + static int slice = 0; + static int noteCount = -1; + if (slice++ % 10 == 0) { + int n = pNotePool->countFreeElements(); + if (n != noteCount) { + noteCount = n; + dmsg(1,("[%d] free Note objects count = %d\n", slice / 10, n)); + } + } + } + #endif // empty the engine's event list for the next audio fragment ClearEventLists(); @@ -379,9 +396,8 @@ } pVoicePool->clear(); - // (re)create event generator - if (pEventGenerator) delete pEventGenerator; - pEventGenerator = new EventGenerator(pAudioOut->SampleRate()); + // update event generator + pEventGenerator->SetSampleRate(pAudioOut->SampleRate()); dmsg(1,("Starting disk thread...")); pDiskThread->StartThread(); @@ -597,7 +613,7 @@ } // implementation of abstract method derived from class 'LinuxSampler::RegionPools' - virtual Pool* GetRegionPool(int index) { + virtual Pool* GetRegionPool(int index) OVERRIDE { if (index < 0 || index > 1) throw Exception("Index out of bounds"); return pRegionPool[index]; } @@ -605,7 +621,7 @@ // implementation of abstract methods derived from class 'LinuxSampler::NotePool' virtual Pool* GetVoicePool() OVERRIDE { return pVoicePool; } virtual Pool< Note >* GetNotePool() OVERRIDE { return pNotePool; } - virtual Pool* GetNodeIDPool() OVERRIDE { return ¬eIDPool; } + virtual Pool* GetNoteIDPool() OVERRIDE { return ¬eIDPool; } D* GetDiskThread() { return pDiskThread; } @@ -676,7 +692,7 @@ * @param pNoteOnEvent - event which caused this * @returns new note's unique ID (or zero on error) */ - note_id_t LaunchNewNote(LinuxSampler::EngineChannel* pEngineChannel, Event* pNoteOnEvent) OVERRIDE { + note_id_t LaunchNewNote(LinuxSampler::EngineChannel* pEngineChannel, Pool::Iterator& itNoteOnEvent) OVERRIDE { EngineChannelBase* pChannel = static_cast*>(pEngineChannel); Pool< Note >* pNotePool = GetNotePool(); @@ -691,7 +707,7 @@ const note_id_t newNoteID = pNotePool->getID(itNewNote); // remember the engine's time when this note was triggered exactly - itNewNote->triggerSchedTime = pNoteOnEvent->SchedTime(); + itNewNote->triggerSchedTime = itNoteOnEvent->SchedTime(); // usually the new note (and its subsequent voices) will be // allocated on the key provided by the event's note number, @@ -699,11 +715,11 @@ // note, but rather a child note, then this new note will be // allocated on the parent note's key instead in order to // release the child note simultaniously with its parent note - itNewNote->hostKey = pNoteOnEvent->Param.Note.Key; + itNewNote->hostKey = itNoteOnEvent->Param.Note.Key; // in case this new note was requested to be a child note, // then retrieve its parent note and link them with each other - const note_id_t parentNoteID = pNoteOnEvent->Param.Note.ParentNoteID; + const note_id_t parentNoteID = itNoteOnEvent->Param.Note.ParentNoteID; if (parentNoteID) { NoteIterator itParentNote = pNotePool->fromID(parentNoteID); if (itParentNote) { @@ -731,15 +747,19 @@ dmsg(2,("Launched new note on host key %d\n", itNewNote->hostKey)); // copy event which caused this note - itNewNote->cause = *pNoteOnEvent; - itNewNote->eventID = pEventPool->getID(pNoteOnEvent); + itNewNote->cause = *itNoteOnEvent; + itNewNote->eventID = pEventPool->getID(itNoteOnEvent); + if (!itNewNote->eventID) { + dmsg(0,("Engine: No valid event ID resolved for note. This is a bug!!!\n")); + } // move new note to its host key MidiKey* pKey = &pChannel->pMIDIKeyInfo[itNewNote->hostKey]; itNewNote.moveToEndOf(pKey->pActiveNotes); + pChannel->markKeyAsActive(pKey); // assign unique note ID of this new note to the original note on event - pNoteOnEvent->Param.Note.ID = newNoteID; + itNoteOnEvent->Param.Note.ID = newNoteID; return newNoteID; // success } @@ -809,6 +829,7 @@ case Event::type_release_note: case Event::type_play_note: case Event::type_stop_note: + case Event::type_kill_note: case Event::type_note_synth_param: break; // noop } @@ -881,6 +902,10 @@ dmsg(5,("Engine: Stop Note received\n")); ProcessNoteOff((EngineChannel*)itEvent->pEngineChannel, itEvent); break; + case Event::type_kill_note: + dmsg(5,("Engine: Kill Note received\n")); + ProcessKillNote((EngineChannel*)itEvent->pEngineChannel, itEvent); + break; case Event::type_control_change: dmsg(5,("Engine: MIDI CC received\n")); ProcessControlChange((EngineChannel*)itEvent->pEngineChannel, itEvent); @@ -976,6 +1001,10 @@ // script event object RTList::Iterator itScriptEvent = pChannel->pScript->pEvents->allocAppend(); + // if event handler uses polyphonic variables, reset them + // to zero values before starting to execute the handler + if (pEventHandler->isPolyphonic()) + itScriptEvent->execCtx->resetPolyphonicData(); ProcessScriptEvent( pChannel, itEvent, pEventHandler, itScriptEvent ); @@ -1008,10 +1037,15 @@ // initialize/reset other members itScriptEvent->cause = *itEvent; + itScriptEvent->scheduleTime = itEvent->SchedTime(); itScriptEvent->currentHandler = 0; itScriptEvent->executionSlices = 0; itScriptEvent->ignoreAllWaitCalls = false; itScriptEvent->handlerType = pEventHandler->eventHandlerType(); + itScriptEvent->parentHandlerID = 0; + itScriptEvent->childHandlerID[0] = 0; + itScriptEvent->autoAbortByParent = false; + itScriptEvent->forkIndex = 0; // this is the native representation of the $EVENT_ID script variable itScriptEvent->id = (itEvent->Type == Event::type_note_on) @@ -1124,6 +1158,7 @@ * @returns 0 on success, a value < 0 if no active voice could be picked for voice stealing */ int StealVoice(EngineChannel* pEngineChannel, Pool::Iterator& itNoteOnEvent) { + dmsg(3,("StealVoice()\n")); if (VoiceSpawnsLeft <= 0) { dmsg(1,("Max. voice thefts per audio fragment reached (you may raise CONFIG_MAX_VOICES).\n")); return -1; @@ -1148,6 +1183,10 @@ int iChannelIndex; VoiceIterator itSelectedVoice; + #if CONFIG_DEVMODE + EngineChannel* pBegin = NULL; // to detect endless loop + #endif + // select engine channel if (pLastStolenChannel) { pSelectedChannel = pLastStolenChannel; @@ -1190,7 +1229,7 @@ } #if CONFIG_DEVMODE - EngineChannel* pBegin = pSelectedChannel; // to detect endless loop + pBegin = pSelectedChannel; // to detect endless loop #endif // CONFIG_DEVMODE while (true) { // iterate through engine channels @@ -1283,17 +1322,36 @@ RTList::Iterator itScriptEvent = pEngineChannel->pScript->pEvents->allocAppend(); + itScriptEvent->cause = pEventGenerator->CreateEvent(0); + itScriptEvent->cause.Type = (Event::type_t) -1; // some invalid type to avoid random event processing itScriptEvent->cause.pEngineChannel = pEngineChannel; + itScriptEvent->cause.pMidiInputPort = pEngineChannel->GetMidiInputPort(); + itScriptEvent->id = 0; itScriptEvent->handlers[0] = pEngineChannel->pScript->handlerInit; itScriptEvent->handlers[1] = NULL; itScriptEvent->currentHandler = 0; itScriptEvent->executionSlices = 0; itScriptEvent->ignoreAllWaitCalls = false; itScriptEvent->handlerType = VM_EVENT_HANDLER_INIT; - - /*VMExecStatus_t res = */ pScriptVM->exec( - pEngineChannel->pScript->parserContext, &*itScriptEvent - ); + itScriptEvent->parentHandlerID = 0; + itScriptEvent->childHandlerID[0] = 0; + itScriptEvent->autoAbortByParent = false; + itScriptEvent->forkIndex = 0; + + VMExecStatus_t res; + size_t instructionsCount = 0; + const size_t maxInstructions = 200000; // aiming approx. 1 second max. (based on very roughly 5us / instruction) + bool bWarningShown = false; + do { + res = pScriptVM->exec( + pEngineChannel->pScript->parserContext, &*itScriptEvent + ); + instructionsCount += itScriptEvent->execCtx->instructionsPerformed(); + if (instructionsCount > maxInstructions && !bWarningShown) { + bWarningShown = true; + dmsg(0,("[ScriptVM] WARNING: \"init\" event handler of instrument script executing for long time!\n")); + } + } while (res & VM_EXEC_SUSPENDED && !(res & VM_EXEC_ERROR)); pEngineChannel->pScript->pEvents->free(itScriptEvent); } @@ -1354,7 +1412,8 @@ // usually there should already be a new Note object NoteIterator itNote = GetNotePool()->fromID(itVoiceStealEvent->Param.Note.ID); if (!itNote) { // should not happen, but just to be sure ... - const note_id_t noteID = LaunchNewNote(pEngineChannel, &*itVoiceStealEvent); + dmsg(2,("Engine: No Note object for stolen voice!\n")); + const note_id_t noteID = LaunchNewNote(pEngineChannel, itVoiceStealEvent); if (!noteID) { dmsg(1,("Engine: Voice stealing failed; No Note object and Note pool empty!\n")); continue; @@ -1398,7 +1457,7 @@ void PostProcess(EngineChannel* pEngineChannel) { EngineChannelBase* pChannel = static_cast*>(pEngineChannel); - pChannel->FreeAllInactiveKyes(); + pChannel->FreeAllInactiveKeys(); // empty the engine channel's own event lists // (only events of the current audio fragment cycle) @@ -1631,7 +1690,7 @@ * @param pEngineChannel - engine channel on which this event occurred on * @param itNoteOnEvent - key, velocity and time stamp of the event */ - virtual void ProcessNoteOn(EngineChannel* pEngineChannel, Pool::Iterator& itNoteOnEvent) { + virtual void ProcessNoteOn(EngineChannel* pEngineChannel, Pool::Iterator& itNoteOnEvent) OVERRIDE { EngineChannelBase* pChannel = static_cast*>(pEngineChannel); @@ -1762,7 +1821,7 @@ * @param pEngineChannel - engine channel on which this event occurred on * @param itNoteOffEvent - key, velocity and time stamp of the event */ - virtual void ProcessNoteOff(EngineChannel* pEngineChannel, Pool::Iterator& itNoteOffEvent) { + virtual void ProcessNoteOff(EngineChannel* pEngineChannel, Pool::Iterator& itNoteOffEvent) OVERRIDE { EngineChannelBase* pChannel = static_cast*>(pEngineChannel); const int iKey = itNoteOffEvent->Param.Note.Key; @@ -1831,7 +1890,7 @@ itPseudoNoteOnEvent->Param.Note.Key = i; itPseudoNoteOnEvent->Param.Note.Velocity = pOtherKey->Velocity; // assign a new note to this note-on event - if (LaunchNewNote(pChannel, &*itPseudoNoteOnEvent)) { + if (LaunchNewNote(pChannel, itPseudoNoteOnEvent)) { // allocate and trigger new voice(s) for the other key TriggerNewVoices(pChannel, itPseudoNoteOnEvent, false); } @@ -1918,7 +1977,7 @@ // spawn release triggered voice(s) if needed if (pKey->ReleaseTrigger && pChannel->pInstrument) { // assign a new note to this release event - if (LaunchNewNote(pChannel, &*itEvent)) { + if (LaunchNewNote(pChannel, itEvent)) { // allocate and trigger new release voice(s) TriggerReleaseVoices(pChannel, itEvent); } @@ -1927,9 +1986,27 @@ } /** + * Called on "kill note" events, which currently only happens on + * built-in real-time instrument script function fade_out(). This + * method only fulfills one task: moving the even to the Note's own + * event list so that its voices can process the kill event sample + * accurately. + */ + void ProcessKillNote(EngineChannel* pEngineChannel, RTList::Iterator& itEvent) { + EngineChannelBase* pChannel = static_cast*>(pEngineChannel); + + NoteBase* pNote = pChannel->pEngine->NoteByID( itEvent->Param.Note.ID ); + if (!pNote || pNote->hostKey < 0 || pNote->hostKey >= 128) return; + + // move note kill event to its MIDI key + MidiKey* pKey = &pChannel->pMIDIKeyInfo[pNote->hostKey]; + itEvent.moveToEndOf(pKey->pEvents); + } + + /** * Called on note synthesis parameter change events. These are * internal events caused by calling built-in real-time instrument - * script functions like change_vol(), change_pitch(), etc. + * script functions like change_vol(), change_tune(), etc. * * This method performs two tasks: * @@ -1960,6 +2037,13 @@ pNote->Override.Volume = itEvent->Param.NoteSynthParam.Delta; itEvent->Param.NoteSynthParam.AbsValue = pNote->Override.Volume; break; + case Event::synth_param_volume_time: + pNote->Override.VolumeTime = itEvent->Param.NoteSynthParam.AbsValue = itEvent->Param.NoteSynthParam.Delta; + break; + case Event::synth_param_volume_curve: + itEvent->Param.NoteSynthParam.AbsValue = itEvent->Param.NoteSynthParam.Delta; + pNote->Override.VolumeCurve = (fade_curve_t) itEvent->Param.NoteSynthParam.AbsValue; + break; case Event::synth_param_pitch: if (relative) pNote->Override.Pitch *= itEvent->Param.NoteSynthParam.Delta; @@ -1967,6 +2051,13 @@ pNote->Override.Pitch = itEvent->Param.NoteSynthParam.Delta; itEvent->Param.NoteSynthParam.AbsValue = pNote->Override.Pitch; break; + case Event::synth_param_pitch_time: + pNote->Override.PitchTime = itEvent->Param.NoteSynthParam.AbsValue = itEvent->Param.NoteSynthParam.Delta; + break; + case Event::synth_param_pitch_curve: + itEvent->Param.NoteSynthParam.AbsValue = itEvent->Param.NoteSynthParam.Delta; + pNote->Override.PitchCurve = (fade_curve_t) itEvent->Param.NoteSynthParam.AbsValue; + break; case Event::synth_param_pan: if (relative) { pNote->Override.Pan = RTMath::RelativeSummedAvg(pNote->Override.Pan, itEvent->Param.NoteSynthParam.Delta, ++pNote->Override.PanSources); @@ -1976,6 +2067,13 @@ } itEvent->Param.NoteSynthParam.AbsValue = pNote->Override.Pan; break; + case Event::synth_param_pan_time: + pNote->Override.PanTime = itEvent->Param.NoteSynthParam.AbsValue = itEvent->Param.NoteSynthParam.Delta; + break; + case Event::synth_param_pan_curve: + itEvent->Param.NoteSynthParam.AbsValue = itEvent->Param.NoteSynthParam.Delta; + pNote->Override.PanCurve = (fade_curve_t) itEvent->Param.NoteSynthParam.AbsValue; + break; case Event::synth_param_cutoff: pNote->Override.Cutoff = itEvent->Param.NoteSynthParam.AbsValue = itEvent->Param.NoteSynthParam.Delta; break; @@ -1988,9 +2086,24 @@ case Event::synth_param_decay: pNote->Override.Decay = itEvent->Param.NoteSynthParam.AbsValue = itEvent->Param.NoteSynthParam.Delta; break; + case Event::synth_param_sustain: + pNote->Override.Sustain = itEvent->Param.NoteSynthParam.AbsValue = itEvent->Param.NoteSynthParam.Delta; + break; case Event::synth_param_release: pNote->Override.Release = itEvent->Param.NoteSynthParam.AbsValue = itEvent->Param.NoteSynthParam.Delta; break; + case Event::synth_param_amp_lfo_depth: + pNote->Override.AmpLFODepth = itEvent->Param.NoteSynthParam.AbsValue = itEvent->Param.NoteSynthParam.Delta; + break; + case Event::synth_param_amp_lfo_freq: + pNote->Override.AmpLFOFreq = itEvent->Param.NoteSynthParam.AbsValue = itEvent->Param.NoteSynthParam.Delta; + break; + case Event::synth_param_pitch_lfo_depth: + pNote->Override.PitchLFODepth = itEvent->Param.NoteSynthParam.AbsValue = itEvent->Param.NoteSynthParam.Delta; + break; + case Event::synth_param_pitch_lfo_freq: + pNote->Override.PitchLFOFreq = itEvent->Param.NoteSynthParam.AbsValue = itEvent->Param.NoteSynthParam.Delta; + break; } // move note parameter event to its MIDI key @@ -2002,7 +2115,7 @@ * Reset all voices and disk thread and clear input event queue and all * control and status variables. This method is protected by a mutex. */ - virtual void ResetInternal() { + virtual void ResetInternal() OVERRIDE { LockGuard lock(ResetInternalMutex); // make sure that the engine does not get any sysex messages @@ -2061,7 +2174,7 @@ * @param pEngineChannel - engine channel on which all voices should be killed * @param itKillEvent - event which caused this killing of all voices */ - virtual void KillAllVoices(EngineChannel* pEngineChannel, Pool::Iterator& itKillEvent) { + virtual void KillAllVoices(EngineChannel* pEngineChannel, Pool::Iterator& itKillEvent) OVERRIDE { EngineChannelBase* pChannel = static_cast*>(pEngineChannel); int count = pChannel->KillAllVoices(itKillEvent); VoiceSpawnsLeft -= count; //FIXME: just a temporary workaround, we should check the cause in StealVoice() instead @@ -2096,7 +2209,7 @@ bool HandleKeyGroupConflicts ) = 0; - virtual int GetMinFadeOutSamples() { return MinFadeOutSamples; } + virtual int GetMinFadeOutSamples() OVERRIDE { return MinFadeOutSamples; } int InitNewVoice ( EngineChannelBase* pChannel, @@ -2119,11 +2232,11 @@ } else { // on success --VoiceSpawnsLeft; - if (!pKey->Active) { // mark as active key - pKey->Active = true; - pKey->itSelf = pChannel->pActiveKeys->allocAppend(); - *pKey->itSelf = itNoteOnEvent->Param.Note.Key; - } + + // should actually be superfluous now, since this is + // already done in LaunchNewNote() + pChannel->markKeyAsActive(pKey); + if (itNewVoice->Type & Voice::type_release_trigger_required) pKey->ReleaseTrigger = true; // mark key for the need of release triggered voice(s) return 0; // success }