--- linuxsampler/trunk/src/engines/gig/Engine.cpp 2004/06/06 20:57:28 112 +++ linuxsampler/trunk/src/engines/gig/Engine.cpp 2016/04/19 14:07:53 2879 @@ -2,7 +2,9 @@ * * * LinuxSampler - modular, streaming capable sampler * * * - * Copyright (C) 2003, 2004 by Benno Senoner and Christian Schoenebeck * + * Copyright (C) 2003,2004 by Benno Senoner and Christian Schoenebeck * + * Copyright (C) 2005-2008 Christian Schoenebeck * + * Copyright (C) 2009-2010 Christian Schoenebeck and Grigor Iliev * * * * 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 * @@ -20,701 +22,359 @@ * MA 02111-1307 USA * ***************************************************************************/ -#include -#include "DiskThread.h" -#include "Voice.h" - #include "Engine.h" +#include "EngineChannel.h" +#include "InstrumentScriptVM.h" namespace LinuxSampler { namespace gig { + Engine::Format Engine::GetEngineFormat() { return GIG; } - InstrumentResourceManager Engine::Instruments; - - Engine::Engine() { - pRIFF = NULL; - pGig = NULL; - pInstrument = NULL; - pAudioOutputDevice = NULL; - pDiskThread = NULL; - pEventGenerator = NULL; - pEventQueue = new RingBuffer(MAX_EVENTS_PER_FRAGMENT); - pEventPool = new RTELMemoryPool(MAX_EVENTS_PER_FRAGMENT); - pVoicePool = new RTELMemoryPool(MAX_AUDIO_VOICES); - pActiveKeys = new RTELMemoryPool(128); - pEvents = new RTEList(pEventPool); - pCCEvents = new RTEList(pEventPool); - for (uint i = 0; i < Event::destination_count; i++) { - pSynthesisEvents[i] = new RTEList(pEventPool); - } - for (uint i = 0; i < 128; i++) { - pMIDIKeyInfo[i].pActiveVoices = new RTEList(pVoicePool); - pMIDIKeyInfo[i].KeyPressed = false; - pMIDIKeyInfo[i].Active = false; - pMIDIKeyInfo[i].pSelf = NULL; - pMIDIKeyInfo[i].pEvents = new RTEList(pEventPool); - } - for (Voice* pVoice = pVoicePool->alloc(); pVoice; pVoice = pVoicePool->alloc()) { - pVoice->SetEngine(this); - } - pVoicePool->clear(); - - pSynthesisParameters[0] = NULL; // we allocate when an audio device is connected - pBasicFilterParameters = NULL; - pMainFilterParameters = NULL; - - InstrumentIdx = -1; - - ResetInternal(); - } - - Engine::~Engine() { - if (pDiskThread) { - pDiskThread->StopThread(); - delete pDiskThread; - } - if (pGig) delete pGig; - if (pRIFF) delete pRIFF; - for (uint i = 0; i < 128; i++) { - if (pMIDIKeyInfo[i].pActiveVoices) delete pMIDIKeyInfo[i].pActiveVoices; - if (pMIDIKeyInfo[i].pEvents) delete pMIDIKeyInfo[i].pEvents; - } - for (uint i = 0; i < Event::destination_count; i++) { - if (pSynthesisEvents[i]) delete pSynthesisEvents[i]; - } - delete[] pSynthesisEvents; - if (pEvents) delete pEvents; - if (pCCEvents) delete pCCEvents; - if (pEventQueue) delete pEventQueue; - if (pEventPool) delete pEventPool; - if (pVoicePool) delete pVoicePool; - if (pActiveKeys) delete pActiveKeys; - if (pEventGenerator) delete pEventGenerator; - if (pMainFilterParameters) delete[] pMainFilterParameters; - if (pBasicFilterParameters) delete[] pBasicFilterParameters; - if (pSynthesisParameters[0]) delete[] pSynthesisParameters[0]; - } - - void Engine::Enable() { - dmsg(3,("gig::Engine: enabling\n")); - EngineDisabled.PushAndUnlock(false, 2); // set condition object 'EngineDisabled' to false (wait max. 2s) - dmsg(3,("gig::Engine: enabled (val=%d)\n", EngineDisabled.GetUnsafe())); - } - - void Engine::Disable() { - dmsg(3,("gig::Engine: disabling\n")); - bool* pWasDisabled = EngineDisabled.PushAndUnlock(true, 2); // wait max. 2s - if (!pWasDisabled) dmsg(3,("gig::Engine warning: Timeout waiting to disable engine.\n")); - } - - void Engine::DisableAndLock() { - dmsg(3,("gig::Engine: disabling\n")); - bool* pWasDisabled = EngineDisabled.Push(true, 2); // wait max. 2s - if (!pWasDisabled) dmsg(3,("gig::Engine warning: Timeout waiting to disable engine.\n")); - } - - /** - * Reset all voices and disk thread and clear input event queue and all - * control and status variables. - */ - void Engine::Reset() { - DisableAndLock(); - - //if (pAudioOutputDevice->IsPlaying()) { // if already running - /* - // signal audio thread not to enter render part anymore - SuspensionRequested = true; - // sleep until wakened by audio thread - pthread_mutex_lock(&__render_state_mutex); - pthread_cond_wait(&__render_exit_condition, &__render_state_mutex); - pthread_mutex_unlock(&__render_state_mutex); - */ - //} - - //if (wasplaying) pAudioOutputDevice->Stop(); - - ResetInternal(); - - // signal audio thread to continue with rendering - //SuspensionRequested = false; - Enable(); - } - - /** - * Reset all voices and disk thread and clear input event queue and all - * control and status variables. This method is not thread safe! - */ - void Engine::ResetInternal() { - Pitch = 0; - SustainPedal = false; - ActiveVoiceCount = 0; - ActiveVoiceCountMax = 0; - - // set all MIDI controller values to zero - memset(ControllerTable, 0x00, 128); - - // reset key info - for (uint i = 0; i < 128; i++) { - pMIDIKeyInfo[i].pActiveVoices->clear(); - pMIDIKeyInfo[i].pEvents->clear(); - pMIDIKeyInfo[i].KeyPressed = false; - pMIDIKeyInfo[i].Active = false; - pMIDIKeyInfo[i].pSelf = NULL; - } - - // reset all voices - for (Voice* pVoice = pVoicePool->alloc(); pVoice; pVoice = pVoicePool->alloc()) { - pVoice->Reset(); - } - pVoicePool->clear(); - - // free all active keys - pActiveKeys->clear(); - - // reset disk thread - if (pDiskThread) pDiskThread->Reset(); - - // delete all input events - pEventQueue->init(); - } - - /** - * Load an instrument from a .gig file. - * - * @param FileName - file name of the Gigasampler instrument file - * @param Instrument - index of the instrument in the .gig file - * @throws LinuxSamplerException on error - * @returns detailed description of the method call result - */ - void Engine::LoadInstrument(const char* FileName, uint Instrument) { - - DisableAndLock(); - - ResetInternal(); // reset engine - - // free old instrument - if (pInstrument) { - // give old instrument back to instrument manager - Instruments.HandBack(pInstrument, this); - } - - InstrumentIdx = -1; - - // request gig instrument from instrument manager - try { - instrument_id_t instrid; - instrid.FileName = FileName; - instrid.iInstrument = Instrument; - pInstrument = Instruments.Borrow(instrid, this); - if (!pInstrument) { - dmsg(1,("no instrument loaded!!!\n")); - exit(EXIT_FAILURE); - } - } - catch (RIFF::Exception e) { - String msg = "gig::Engine error: Failed to load instrument, cause: " + e.Message; - throw LinuxSamplerException(msg); - } - catch (InstrumentResourceManagerException e) { - String msg = "gig::Engine error: Failed to load instrument, cause: " + e.Message(); - throw LinuxSamplerException(msg); - } - catch (...) { - throw LinuxSamplerException("gig::Engine error: Failed to load instrument, cause: Unknown exception while trying to parse gig file."); - } - - InstrumentFile = FileName; - InstrumentIdx = Instrument; - - // inform audio driver for the need of two channels - try { - if (pAudioOutputDevice) pAudioOutputDevice->AcquireChannels(2); // gig Engine only stereo - } - catch (AudioOutputException e) { - String msg = "Audio output device unable to provide 2 audio channels, cause: " + e.Message(); - throw LinuxSamplerException(msg); - } - - Enable(); - } - - /** - * Will be called by the InstrumentResourceManager when the instrument - * we are currently using in this engine is going to be updated, so we - * can stop playback before that happens. - */ - void Engine::ResourceToBeUpdated(::gig::Instrument* pResource, void*& pUpdateArg) { - dmsg(3,("gig::Engine: Received instrument update message.\n")); - DisableAndLock(); - ResetInternal(); - this->pInstrument = NULL; - } - - /** - * Will be called by the InstrumentResourceManager when the instrument - * update process was completed, so we can continue with playback. - */ - void Engine::ResourceUpdated(::gig::Instrument* pOldResource, ::gig::Instrument* pNewResource, void* pUpdateArg) { - this->pInstrument = pNewResource; - Enable(); - } - - void Engine::Connect(AudioOutputDevice* pAudioOut) { - pAudioOutputDevice = pAudioOut; - - ResetInternal(); - - // inform audio driver for the need of two channels - try { - pAudioOutputDevice->AcquireChannels(2); // gig engine only stereo - } - catch (AudioOutputException e) { - String msg = "Audio output device unable to provide 2 audio channels, cause: " + e.Message(); - throw LinuxSamplerException(msg); - } - - // (re)create disk thread - if (this->pDiskThread) { - this->pDiskThread->StopThread(); - delete this->pDiskThread; - } - this->pDiskThread = new DiskThread(((pAudioOut->MaxSamplesPerCycle() << MAX_PITCH) << 1) + 6); //FIXME: assuming stereo - if (!pDiskThread) { - dmsg(0,("gig::Engine new diskthread = NULL\n")); - exit(EXIT_FAILURE); - } - - for (Voice* pVoice = pVoicePool->alloc(); pVoice; pVoice = pVoicePool->alloc()) { - pVoice->pDiskThread = this->pDiskThread; - pVoice->SetOutput(pAudioOut); - dmsg(3,("d")); - } - pVoicePool->clear(); - - // (re)create event generator - if (pEventGenerator) delete pEventGenerator; - pEventGenerator = new EventGenerator(pAudioOut->SampleRate()); - - // (re)allocate synthesis parameter matrix - if (pSynthesisParameters[0]) delete[] pSynthesisParameters[0]; - pSynthesisParameters[0] = new float[Event::destination_count * pAudioOut->MaxSamplesPerCycle()]; - for (int dst = 1; dst < Event::destination_count; dst++) - pSynthesisParameters[dst] = pSynthesisParameters[dst - 1] + pAudioOut->MaxSamplesPerCycle(); - - // (re)allocate biquad filter parameter sequence - if (pBasicFilterParameters) delete[] pBasicFilterParameters; - if (pMainFilterParameters) delete[] pMainFilterParameters; - pBasicFilterParameters = new biquad_param_t[pAudioOut->MaxSamplesPerCycle()]; - pMainFilterParameters = new biquad_param_t[pAudioOut->MaxSamplesPerCycle()]; - - dmsg(1,("Starting disk thread...")); - pDiskThread->StartThread(); - dmsg(1,("OK\n")); - - for (Voice* pVoice = pVoicePool->first(); pVoice; pVoice = pVoicePool->next()) { - if (!pVoice->pDiskThread) { - dmsg(0,("Engine -> voice::trigger: !pDiskThread\n")); - exit(EXIT_FAILURE); - } - } - } - - void Engine::DisconnectAudioOutputDevice() { - if (pAudioOutputDevice) { // if clause to prevent disconnect loops - AudioOutputDevice* olddevice = pAudioOutputDevice; - pAudioOutputDevice = NULL; - olddevice->Disconnect(this); - } - } - - /** - * Let this engine proceed to render the given amount of sample points. The - * calculated audio data of all voices of this engine will be placed into - * the engine's audio sum buffer which has to be copied and eventually be - * converted to the appropriate value range by the audio output class (e.g. - * AlsaIO or JackIO) right after. - * - * @param Samples - number of sample points to be rendered - * @returns 0 on success - */ - int Engine::RenderAudio(uint Samples) { - dmsg(5,("RenderAudio(Samples=%d)\n", Samples)); - - // return if no instrument loaded or engine disabled - if (EngineDisabled.Pop()) { - dmsg(5,("gig::Engine: engine disabled (val=%d)\n",EngineDisabled.GetUnsafe())); - return 0; - } - if (!pInstrument) { - dmsg(5,("gig::Engine: no instrument loaded\n")); - return 0; - } - - - // empty the event lists for the new fragment - pEvents->clear(); - pCCEvents->clear(); - for (uint i = 0; i < Event::destination_count; i++) { - pSynthesisEvents[i]->clear(); - } - - // read and copy events from input queue - Event event = pEventGenerator->CreateEvent(); - while (true) { - if (!pEventQueue->pop(&event)) break; - pEvents->alloc_assign(event); - } - - - // update time of start and end of this audio fragment (as events' time stamps relate to this) - pEventGenerator->UpdateFragmentTime(Samples); - - - // process events - Event* pNextEvent = pEvents->first(); - while (pNextEvent) { - Event* pEvent = pNextEvent; - pEvents->set_current(pEvent); - pNextEvent = pEvents->next(); - switch (pEvent->Type) { - case Event::type_note_on: - dmsg(5,("Audio Thread: Note on received\n")); - ProcessNoteOn(pEvent); - break; - case Event::type_note_off: - dmsg(5,("Audio Thread: Note off received\n")); - ProcessNoteOff(pEvent); - break; - case Event::type_control_change: - dmsg(5,("Audio Thread: MIDI CC received\n")); - ProcessControlChange(pEvent); - break; - case Event::type_pitchbend: - dmsg(5,("Audio Thread: Pitchbend received\n")); - ProcessPitchbend(pEvent); - break; - } - } - - - // render audio from all active voices - int active_voices = 0; - uint* piKey = pActiveKeys->first(); - while (piKey) { // iterate through all active keys - midi_key_info_t* pKey = &pMIDIKeyInfo[*piKey]; - pActiveKeys->set_current(piKey); - piKey = pActiveKeys->next(); - - Voice* pVoiceNext = pKey->pActiveVoices->first(); - while (pVoiceNext) { // iterate through all voices on this key - // already get next voice on key - Voice* pVoice = pVoiceNext; - pKey->pActiveVoices->set_current(pVoice); - pVoiceNext = pKey->pActiveVoices->next(); - - // now render current voice - pVoice->Render(Samples); - if (pVoice->IsActive()) active_voices++; // still active - else { // voice reached end, is now inactive - KillVoice(pVoice); // remove voice from the list of active voices - } - } - pKey->pEvents->clear(); // free all events on the key - } - - - // write that to the disk thread class so that it can print it - // on the console for debugging purposes - ActiveVoiceCount = active_voices; - if (ActiveVoiceCount > ActiveVoiceCountMax) ActiveVoiceCountMax = ActiveVoiceCount; - - - return 0; - } - - /** - * Will be called by the MIDIIn Thread to let the audio thread trigger a new - * voice for the given key. - * - * @param Key - MIDI key number of the triggered key - * @param Velocity - MIDI velocity value of the triggered key - */ - void Engine::SendNoteOn(uint8_t Key, uint8_t Velocity) { - Event event = pEventGenerator->CreateEvent(); - event.Type = Event::type_note_on; - event.Key = Key; - event.Velocity = Velocity; - if (this->pEventQueue->write_space() > 0) this->pEventQueue->push(&event); - else dmsg(1,("Engine: Input event queue full!")); - } - - /** - * Will be called by the MIDIIn Thread to signal the audio thread to release - * voice(s) on the given key. - * - * @param Key - MIDI key number of the released key - * @param Velocity - MIDI release velocity value of the released key - */ - void Engine::SendNoteOff(uint8_t Key, uint8_t Velocity) { - Event event = pEventGenerator->CreateEvent(); - event.Type = Event::type_note_off; - event.Key = Key; - event.Velocity = Velocity; - if (this->pEventQueue->write_space() > 0) this->pEventQueue->push(&event); - else dmsg(1,("Engine: Input event queue full!")); - } - - /** - * Will be called by the MIDIIn Thread to signal the audio thread to change - * the pitch value for all voices. - * - * @param Pitch - MIDI pitch value (-8192 ... +8191) - */ - void Engine::SendPitchbend(int Pitch) { - Event event = pEventGenerator->CreateEvent(); - event.Type = Event::type_pitchbend; - event.Pitch = Pitch; - if (this->pEventQueue->write_space() > 0) this->pEventQueue->push(&event); - else dmsg(1,("Engine: Input event queue full!")); - } - - /** - * Will be called by the MIDIIn Thread to signal the audio thread that a - * continuous controller value has changed. - * - * @param Controller - MIDI controller number of the occured control change - * @param Value - value of the control change - */ - void Engine::SendControlChange(uint8_t Controller, uint8_t Value) { - Event event = pEventGenerator->CreateEvent(); - event.Type = Event::type_control_change; - event.Controller = Controller; - event.Value = Value; - if (this->pEventQueue->write_space() > 0) this->pEventQueue->push(&event); - else dmsg(1,("Engine: Input event queue full!")); - } - - /** - * Assigns and triggers a new voice for the respective MIDI key. - * - * @param pNoteOnEvent - key, velocity and time stamp of the event - */ - void Engine::ProcessNoteOn(Event* pNoteOnEvent) { - midi_key_info_t* pKey = &pMIDIKeyInfo[pNoteOnEvent->Key]; - - pKey->KeyPressed = true; // the MIDI key was now pressed down - - // cancel release process of voices on this key if needed - if (pKey->Active && !SustainPedal) { - pNoteOnEvent->Type = Event::type_cancel_release; // transform event type - pEvents->move(pNoteOnEvent, pKey->pEvents); // move event to the key's own event list - } - - // allocate a new voice for the key - Voice* pNewVoice = pKey->pActiveVoices->alloc(); - if (pNewVoice) { - // launch the new voice - if (pNewVoice->Trigger(pNoteOnEvent, this->Pitch, this->pInstrument) < 0) { - dmsg(1,("Triggering new voice failed!\n")); - pKey->pActiveVoices->free(pNewVoice); - } - else if (!pKey->Active) { // mark as active key - pKey->Active = true; - pKey->pSelf = pActiveKeys->alloc(); - *pKey->pSelf = pNoteOnEvent->Key; - } - } - else std::cerr << "No free voice!" << std::endl << std::flush; - } - - /** - * Releases the voices on the given key if sustain pedal is not pressed. - * If sustain is pressed, the release of the note will be postponed until - * sustain pedal will be released or voice turned inactive by itself (e.g. - * due to completion of sample playback). - * - * @param pNoteOffEvent - key, velocity and time stamp of the event - */ - void Engine::ProcessNoteOff(Event* pNoteOffEvent) { - midi_key_info_t* pKey = &pMIDIKeyInfo[pNoteOffEvent->Key]; - - pKey->KeyPressed = false; // the MIDI key was now released - - // release voices on this key if needed - if (pKey->Active && !SustainPedal) { - pNoteOffEvent->Type = Event::type_release; // transform event type - pEvents->move(pNoteOffEvent, pKey->pEvents); // move event to the key's own event list - } - } - - /** - * Moves pitchbend event from the general (input) event list to the pitch - * event list. - * - * @param pPitchbendEvent - absolute pitch value and time stamp of the event - */ - void Engine::ProcessPitchbend(Event* pPitchbendEvent) { - this->Pitch = pPitchbendEvent->Pitch; // store current pitch value - pEvents->move(pPitchbendEvent, pSynthesisEvents[Event::destination_vco]); - } - - /** - * Immediately kills the voice given with pVoice (no matter if sustain is - * pressed or not) and removes it from the MIDI key's list of active voice. - * This method will e.g. be called if a voice went inactive by itself. - * - * @param pVoice - points to the voice to be killed - */ - void Engine::KillVoice(Voice* pVoice) { - if (pVoice) { - if (pVoice->IsActive()) pVoice->Kill(); - - midi_key_info_t* pKey = &pMIDIKeyInfo[pVoice->MIDIKey]; - - // free the voice object - pVoicePool->free(pVoice); - - // check if there are no voices left on the MIDI key and update the key info if so - if (pKey->pActiveVoices->is_empty()) { - pKey->Active = false; - pActiveKeys->free(pKey->pSelf); // remove key from list of active keys - pKey->pSelf = NULL; - dmsg(3,("Key has no more voices now\n")); - } - } - else std::cerr << "Couldn't release voice! (pVoice == NULL)\n" << std::flush; + void Engine::CreateInstrumentScriptVM() { + dmsg(2,("gig::Engine created Giga format scriptvm\n")); + if (pScriptVM) return; + pScriptVM = new InstrumentScriptVM; // gig format specific extended script runner } /** * Reacts on supported control change commands (e.g. pitch bend wheel, * modulation wheel, aftertouch). * - * @param pControlChangeEvent - controller, value and time stamp of the event + * @param pEngineChannel - engine channel on which this event occured on + * @param itControlChangeEvent - controller, value and time stamp of the event */ - void Engine::ProcessControlChange(Event* pControlChangeEvent) { - dmsg(4,("Engine::ContinuousController cc=%d v=%d\n", pControlChangeEvent->Controller, pControlChangeEvent->Value)); - - switch (pControlChangeEvent->Controller) { - case 64: { - if (pControlChangeEvent->Value >= 64 && !SustainPedal) { - dmsg(4,("PEDAL DOWN\n")); - SustainPedal = true; - - // cancel release process of voices if necessary - uint* piKey = pActiveKeys->first(); - if (piKey) { - pControlChangeEvent->Type = Event::type_cancel_release; // transform event type - while (piKey) { - midi_key_info_t* pKey = &pMIDIKeyInfo[*piKey]; - pActiveKeys->set_current(piKey); - piKey = pActiveKeys->next(); - if (!pKey->KeyPressed) { - Event* pNewEvent = pKey->pEvents->alloc(); - if (pNewEvent) *pNewEvent = *pControlChangeEvent; // copy event to the key's own event list + void Engine::ProcessControlChange ( + LinuxSampler::EngineChannel* pEngineChannel, + Pool::Iterator& itControlChangeEvent + ) { + dmsg(4,("Engine::ContinuousController cc=%d v=%d\n", itControlChangeEvent->Param.CC.Controller, itControlChangeEvent->Param.CC.Value)); + + EngineChannel* pChannel = dynamic_cast(pEngineChannel); + // handle the "control triggered" MIDI rule: a control change + // event can trigger a new note on or note off event + if (pChannel->pInstrument) { + + ::gig::MidiRule* rule; + for (int i = 0 ; (rule = pChannel->pInstrument->GetMidiRule(i)) ; i++) { + + if (::gig::MidiRuleCtrlTrigger* ctrlTrigger = + dynamic_cast< ::gig::MidiRuleCtrlTrigger*>(rule)) { + if (itControlChangeEvent->Param.CC.Controller == + ctrlTrigger->ControllerNumber) { + + uint8_t oldCCValue = pChannel->ControllerTable[ + itControlChangeEvent->Param.CC.Controller]; + uint8_t newCCValue = itControlChangeEvent->Param.CC.Value; + + for (int i = 0 ; i < ctrlTrigger->Triggers ; i++) { + ::gig::MidiRuleCtrlTrigger::trigger_t* pTrigger = + &ctrlTrigger->pTriggers[i]; + + // check if the controller has passed the + // trigger point in the right direction + if ((pTrigger->Descending && + oldCCValue > pTrigger->TriggerPoint && + newCCValue <= pTrigger->TriggerPoint) || + (!pTrigger->Descending && + oldCCValue < pTrigger->TriggerPoint && + newCCValue >= pTrigger->TriggerPoint)) { + + RTList::Iterator itNewEvent = pGlobalEvents->allocAppend(); + if (itNewEvent) { + *itNewEvent = *itControlChangeEvent; + itNewEvent->Param.Note.Key = pTrigger->Key; + + if (pTrigger->NoteOff || pTrigger->Velocity == 0) { + itNewEvent->Type = Event::type_note_off; + itNewEvent->Param.Note.Velocity = 100; + + ProcessNoteOff(pEngineChannel, itNewEvent); + } else { + itNewEvent->Type = Event::type_note_on; + //TODO: if Velocity is 255, the triggered velocity should + // depend on how fast the controller is moving + itNewEvent->Param.Note.Velocity = + pTrigger->Velocity == 255 ? 100 : + pTrigger->Velocity; + + ProcessNoteOn(pEngineChannel, itNewEvent); + } + } else dmsg(1,("Event pool emtpy!\n")); } } } } - if (pControlChangeEvent->Value < 64 && SustainPedal) { - dmsg(4,("PEDAL UP\n")); - SustainPedal = false; - - // release voices if their respective key is not pressed - uint* piKey = pActiveKeys->first(); - if (piKey) { - pControlChangeEvent->Type = Event::type_release; // transform event type - while (piKey) { - midi_key_info_t* pKey = &pMIDIKeyInfo[*piKey]; - pActiveKeys->set_current(piKey); - piKey = pActiveKeys->next(); - if (!pKey->KeyPressed) { - Event* pNewEvent = pKey->pEvents->alloc(); - if (pNewEvent) *pNewEvent = *pControlChangeEvent; // copy event to the key's own event list - else dmsg(1,("Event pool emtpy!\n")); - } - } - } - } - break; } } - // update controller value in the engine's controller table - ControllerTable[pControlChangeEvent->Controller] = pControlChangeEvent->Value; + // update controller value in the engine channel's controller table + pChannel->ControllerTable[itControlChangeEvent->Param.CC.Controller] = itControlChangeEvent->Param.CC.Value; + + ProcessHardcodedControllers(pEngineChannel, itControlChangeEvent); - // move event from the unsorted event list to the control change event list - pEvents->move(pControlChangeEvent, pCCEvents); + // handle FX send controllers + ProcessFxSendControllers(pChannel, itControlChangeEvent); } - /** - * Initialize the parameter sequence for the modulation destination given by - * by 'dst' with the constant value given by val. - */ - void Engine::ResetSynthesisParameters(Event::destination_t dst, float val) { - int maxsamples = pAudioOutputDevice->MaxSamplesPerCycle(); - float* m = &pSynthesisParameters[dst][0]; - for (int i = 0; i < maxsamples; i += 4) { - m[i] = val; - m[i+1] = val; - m[i+2] = val; - m[i+3] = val; + void Engine::ProcessChannelPressure(LinuxSampler::EngineChannel* pEngineChannel, Pool::Iterator& itChannelPressureEvent) { + // if required: engine global aftertouch handling (apart from the per voice handling) + } + + void Engine::ProcessPolyphonicKeyPressure(LinuxSampler::EngineChannel* pEngineChannel, Pool::Iterator& itNotePressureEvent) { + // if required: engine global aftertouch handling (apart from the per voice handling) + } + + DiskThread* Engine::CreateDiskThread() { + return new DiskThread ( + iMaxDiskStreams, + ((pAudioOutputDevice->MaxSamplesPerCycle() << CONFIG_MAX_PITCH) << 1) + 6, //FIXME: assuming stereo + &instruments + ); + } + + void Engine::TriggerNewVoices ( + LinuxSampler::EngineChannel* pEngineChannel, + RTList::Iterator& itNoteOnEvent, + bool HandleKeyGroupConflicts + ) { + EngineChannel* pChannel = static_cast(pEngineChannel); + // first, get total amount of required voices (dependant on amount of layers) + ::gig::Region* pRegion = pChannel->pInstrument->GetRegion(itNoteOnEvent->Param.Note.Key); + if (!pRegion || RegionSuspended(pRegion)) + return; + const int voicesRequired = pRegion->Layers; + if (voicesRequired <= 0) + return; + + NoteIterator itNote = GetNotePool()->fromID(itNoteOnEvent->Param.Note.ID); + if (!itNote) { + dmsg(1,("gig::Engine: No Note object for triggering new voices!\n")); + return; + } + + // now launch the required amount of voices + for (int i = 0; i < voicesRequired; i++) { + VoiceIterator itNewVoice = + LaunchVoice(pChannel, itNoteOnEvent, i, false, true, HandleKeyGroupConflicts); + if (!itNewVoice) continue; + itNewVoice.moveToEndOf(itNote->pActiveVoices); + } + } + + void Engine::TriggerReleaseVoices ( + LinuxSampler::EngineChannel* pEngineChannel, + RTList::Iterator& itNoteOffEvent + ) { + EngineChannel* pChannel = static_cast(pEngineChannel); + MidiKey* pKey = &pChannel->pMIDIKeyInfo[itNoteOffEvent->Param.Note.Key]; + // first, get total amount of required voices (dependant on amount of layers) + ::gig::Region* pRegion = pChannel->pInstrument->GetRegion(itNoteOffEvent->Param.Note.Key); + if (!pRegion) + return; + const int voicesRequired = pRegion->Layers; + if (voicesRequired <= 0) + return; + + NoteIterator itNote = GetNotePool()->fromID(itNoteOffEvent->Param.Note.ID); + if (!itNote) { + dmsg(1,("gig::Engine: No Note object for triggering new release voices!\n")); + return; + } + + // MIDI note-on velocity is used instead of note-off velocity + itNoteOffEvent->Param.Note.Velocity = pKey->Velocity; + + // now launch the required amount of voices + for (int i = 0; i < voicesRequired; i++) { + VoiceIterator itNewVoice = + LaunchVoice(pChannel, itNoteOffEvent, i, true, false, false); //FIXME: for the moment we don't perform voice stealing for release triggered samples + if (!itNewVoice) continue; + itNewVoice.moveToEndOf(itNote->pActiveVoices); + } + } + + Pool::Iterator Engine::LaunchVoice ( + LinuxSampler::EngineChannel* pEngineChannel, + Pool::Iterator& itNoteOnEvent, + int iLayer, + bool ReleaseTriggerVoice, + bool VoiceStealing, + bool HandleKeyGroupConflicts + ) { + EngineChannel* pChannel = static_cast(pEngineChannel); + int MIDIKey = itNoteOnEvent->Param.Note.Key; + //EngineChannel::MidiKey* pKey = &pChannel->pMIDIKeyInfo[MIDIKey]; + ::gig::Region* pRegion = pChannel->pInstrument->GetRegion(MIDIKey); + + // if nothing defined for this key + if (!pRegion) return Pool::Iterator(); // nothing to do + + int iKeyGroup = pRegion->KeyGroup; + // only need to send a group event from the first voice in a layered region, + // as all layers in a region always belongs to the same key group + if (HandleKeyGroupConflicts && iLayer == 0) pChannel->HandleKeyGroupConflicts(iKeyGroup, itNoteOnEvent); + + Voice::type_t VoiceType = Voice::type_normal; + + // get current dimension values to select the right dimension region + //TODO: for stolen voices this dimension region selection block is processed twice, this should be changed + //FIXME: controller values for selecting the dimension region here are currently not sample accurate + uint DimValues[8] = { 0 }; + for (int i = pRegion->Dimensions - 1; i >= 0; i--) { + switch (pRegion->pDimensionDefinitions[i].dimension) { + case ::gig::dimension_samplechannel: + DimValues[i] = 0; //TODO: we currently ignore this dimension + break; + case ::gig::dimension_layer: + DimValues[i] = iLayer; + break; + case ::gig::dimension_velocity: + DimValues[i] = itNoteOnEvent->Param.Note.Velocity; + break; + case ::gig::dimension_channelaftertouch: + DimValues[i] = pChannel->ControllerTable[128]; + break; + case ::gig::dimension_releasetrigger: + VoiceType = (ReleaseTriggerVoice) ? Voice::type_release_trigger : (!iLayer) ? Voice::type_release_trigger_required : Voice::type_normal; + DimValues[i] = (uint) ReleaseTriggerVoice; + break; + case ::gig::dimension_keyboard: + DimValues[i] = (uint) (pChannel->CurrentKeyDimension * pRegion->pDimensionDefinitions[i].zones); + break; + case ::gig::dimension_roundrobin: + DimValues[i] = uint(*pChannel->pMIDIKeyInfo[MIDIKey].pRoundRobinIndex % pRegion->pDimensionDefinitions[i].zones); // RoundRobinIndex is incremented for each note on in this Region + break; + case ::gig::dimension_roundrobinkeyboard: + DimValues[i] = uint(pChannel->RoundRobinIndex % pRegion->pDimensionDefinitions[i].zones); // RoundRobinIndex is incremented for each note on + break; + case ::gig::dimension_random: + DimValues[i] = uint(Random() * pRegion->pDimensionDefinitions[i].zones); + break; + case ::gig::dimension_smartmidi: + DimValues[i] = 0; + break; + case ::gig::dimension_modwheel: + DimValues[i] = pChannel->ControllerTable[1]; + break; + case ::gig::dimension_breath: + DimValues[i] = pChannel->ControllerTable[2]; + break; + case ::gig::dimension_foot: + DimValues[i] = pChannel->ControllerTable[4]; + break; + case ::gig::dimension_portamentotime: + DimValues[i] = pChannel->ControllerTable[5]; + break; + case ::gig::dimension_effect1: + DimValues[i] = pChannel->ControllerTable[12]; + break; + case ::gig::dimension_effect2: + DimValues[i] = pChannel->ControllerTable[13]; + break; + case ::gig::dimension_genpurpose1: + DimValues[i] = pChannel->ControllerTable[16]; + break; + case ::gig::dimension_genpurpose2: + DimValues[i] = pChannel->ControllerTable[17]; + break; + case ::gig::dimension_genpurpose3: + DimValues[i] = pChannel->ControllerTable[18]; + break; + case ::gig::dimension_genpurpose4: + DimValues[i] = pChannel->ControllerTable[19]; + break; + case ::gig::dimension_sustainpedal: + DimValues[i] = pChannel->ControllerTable[64]; + break; + case ::gig::dimension_portamento: + DimValues[i] = pChannel->ControllerTable[65]; + break; + case ::gig::dimension_sostenutopedal: + DimValues[i] = pChannel->ControllerTable[66]; + break; + case ::gig::dimension_softpedal: + DimValues[i] = pChannel->ControllerTable[67]; + break; + case ::gig::dimension_genpurpose5: + DimValues[i] = pChannel->ControllerTable[80]; + break; + case ::gig::dimension_genpurpose6: + DimValues[i] = pChannel->ControllerTable[81]; + break; + case ::gig::dimension_genpurpose7: + DimValues[i] = pChannel->ControllerTable[82]; + break; + case ::gig::dimension_genpurpose8: + DimValues[i] = pChannel->ControllerTable[83]; + break; + case ::gig::dimension_effect1depth: + DimValues[i] = pChannel->ControllerTable[91]; + break; + case ::gig::dimension_effect2depth: + DimValues[i] = pChannel->ControllerTable[92]; + break; + case ::gig::dimension_effect3depth: + DimValues[i] = pChannel->ControllerTable[93]; + break; + case ::gig::dimension_effect4depth: + DimValues[i] = pChannel->ControllerTable[94]; + break; + case ::gig::dimension_effect5depth: + DimValues[i] = pChannel->ControllerTable[95]; + break; + case ::gig::dimension_none: + std::cerr << "gig::Engine::LaunchVoice() Error: dimension=none\n" << std::flush; + break; + default: + std::cerr << "gig::Engine::LaunchVoice() Error: Unknown dimension\n" << std::flush; + } } - } - float Engine::Volume() { - return GlobalVolume; - } + // return if this is a release triggered voice and there is no + // releasetrigger dimension (could happen if an instrument + // change has occured between note on and off) + if (ReleaseTriggerVoice && !(VoiceType & Voice::type_release_trigger)) return Pool::Iterator(); + + NoteIterator itNote = GetNotePool()->fromID(itNoteOnEvent->Param.Note.ID); + + ::gig::DimensionRegion* pDimRgn; + if (!itNote->Format.Gig.DimMask) { // normal case ... + pDimRgn = pRegion->GetDimensionRegionByValue(DimValues); + } else { // some dimension zones were overridden (i.e. by instrument script) ... + dmsg(3,("trigger with dim mask=%d val=%d\n", itNote->Format.Gig.DimMask, itNote->Format.Gig.DimBits)); + int index = pRegion->GetDimensionRegionIndexByValue(DimValues); + index &= ~itNote->Format.Gig.DimMask; + index |= itNote->Format.Gig.DimBits & itNote->Format.Gig.DimMask; + pDimRgn = pRegion->pDimensionRegions[index & 255]; + } + if (!pDimRgn) return Pool::Iterator(); // error (could not resolve dimension region) - void Engine::Volume(float f) { - GlobalVolume = f; - } + // no need to continue if sample is silent + if (!pDimRgn->pSample || !pDimRgn->pSample->SamplesTotal) return Pool::Iterator(); - uint Engine::VoiceCount() { - return ActiveVoiceCount; - } + // allocate a new voice for the key + Pool::Iterator itNewVoice = GetVoicePool()->allocAppend(); + + int res = InitNewVoice ( + pChannel, pDimRgn, itNoteOnEvent, VoiceType, iLayer, + iKeyGroup, ReleaseTriggerVoice, VoiceStealing, itNewVoice + ); + if (!res) return itNewVoice; - uint Engine::VoiceCountMax() { - return ActiveVoiceCountMax; + return Pool::Iterator(); // no free voice or error } bool Engine::DiskStreamSupported() { return true; } - uint Engine::DiskStreamCount() { - return (pDiskThread) ? pDiskThread->ActiveStreamCount : 0; - } - - uint Engine::DiskStreamCountMax() { - return (pDiskThread) ? pDiskThread->ActiveStreamCountMax : 0; - } - - String Engine::DiskStreamBufferFillBytes() { - return pDiskThread->GetBufferFillBytes(); - } - - String Engine::DiskStreamBufferFillPercentage() { - return pDiskThread->GetBufferFillPercentage(); - } - - String Engine::EngineName() { - return "GigEngine"; - } - - String Engine::InstrumentFileName() { - return InstrumentFile; - } - - int Engine::InstrumentIndex() { - return InstrumentIdx; - } - String Engine::Description() { - return "Gigasampler Engine"; + return "GigaSampler Format Engine"; } String Engine::Version() { - return "0.0.1-0cvs20040423"; + String s = "$Revision$"; + return s.substr(11, s.size() - 13); // cut dollar signs, spaces and CVS macro keyword } }} // namespace LinuxSampler::gig