--- linuxsampler/trunk/src/engines/gig/Engine.cpp 2004/10/25 15:14:27 293 +++ 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,1151 +22,358 @@ * MA 02111-1307 USA * ***************************************************************************/ -#include -#include "DiskThread.h" -#include "Voice.h" -#include "EGADSR.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; - pSysexBuffer = new RingBuffer(SYSEX_BUFFER_SIZE, 0); - pEventQueue = new RingBuffer(MAX_EVENTS_PER_FRAGMENT, 0); - pEventPool = new Pool(MAX_EVENTS_PER_FRAGMENT); - pVoicePool = new Pool(MAX_AUDIO_VOICES); - pActiveKeys = new Pool(128); - pVoiceStealingQueue = new RTList(pEventPool); - pEvents = new RTList(pEventPool); - pCCEvents = new RTList(pEventPool); - for (uint i = 0; i < Event::destination_count; i++) { - pSynthesisEvents[i] = new RTList(pEventPool); - } - for (uint i = 0; i < 128; i++) { - pMIDIKeyInfo[i].pActiveVoices = new RTList(pVoicePool); - pMIDIKeyInfo[i].KeyPressed = false; - pMIDIKeyInfo[i].Active = false; - pMIDIKeyInfo[i].ReleaseTrigger = false; - pMIDIKeyInfo[i].pEvents = new RTList(pEventPool); - } - for (RTList::Iterator iterVoice = pVoicePool->allocAppend(); iterVoice == pVoicePool->last(); iterVoice = pVoicePool->allocAppend()) { - iterVoice->SetEngine(this); - } - pVoicePool->clear(); - - pSynthesisParameters[0] = NULL; // we allocate when an audio device is connected - pBasicFilterParameters = NULL; - pMainFilterParameters = NULL; - - InstrumentIdx = -1; - InstrumentStat = -1; - - AudioDeviceChannelLeft = -1; - AudioDeviceChannelRight = -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 (pSysexBuffer) delete pSysexBuffer; - if (pEventGenerator) delete pEventGenerator; - if (pMainFilterParameters) delete[] pMainFilterParameters; - if (pBasicFilterParameters) delete[] pBasicFilterParameters; - if (pSynthesisParameters[0]) delete[] pSynthesisParameters[0]; - if (pVoiceStealingQueue) delete pVoiceStealingQueue; - } - - 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; - GlobalVolume = 1.0; - - // reset voice stealing parameters - itLastStolenVoice = RTList::Iterator(); - iuiLastStolenKey = RTList::Iterator(); - pVoiceStealingQueue->clear(); - - // reset to normal chromatic scale (means equal temper) - memset(&ScaleTuning[0], 0x00, 12); - - // 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].ReleaseTrigger = false; - pMIDIKeyInfo[i].itSelf = Pool::Iterator(); - } - - // reset all key groups - map::iterator iter = ActiveKeyGroups.begin(); - for (; iter != ActiveKeyGroups.end(); iter++) iter->second = NULL; - - // reset all voices - for (RTList::Iterator iterVoice = pVoicePool->allocAppend(); iterVoice == pVoicePool->last(); iterVoice = pVoicePool->allocAppend()) { - iterVoice->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); - } - - InstrumentFile = FileName; - InstrumentIdx = Instrument; - InstrumentStat = 0; - - // delete all key groups - ActiveKeyGroups.clear(); - - // request gig instrument from instrument manager - try { - instrument_id_t instrid; - instrid.FileName = FileName; - instrid.iInstrument = Instrument; - pInstrument = Instruments.Borrow(instrid, this); - if (!pInstrument) { - InstrumentStat = -1; - dmsg(1,("no instrument loaded!!!\n")); - exit(EXIT_FAILURE); - } - } - catch (RIFF::Exception e) { - InstrumentStat = -2; - String msg = "gig::Engine error: Failed to load instrument, cause: " + e.Message; - throw LinuxSamplerException(msg); - } - catch (InstrumentResourceManagerException e) { - InstrumentStat = -3; - String msg = "gig::Engine error: Failed to load instrument, cause: " + e.Message(); - throw LinuxSamplerException(msg); - } - catch (...) { - InstrumentStat = -4; - throw LinuxSamplerException("gig::Engine error: Failed to load instrument, cause: Unknown exception while trying to parse gig file."); - } - - // rebuild ActiveKeyGroups map with key groups of current instrument - for (::gig::Region* pRegion = pInstrument->GetFirstRegion(); pRegion; pRegion = pInstrument->GetNextRegion()) - if (pRegion->KeyGroup) ActiveKeyGroups[pRegion->KeyGroup] = NULL; - - InstrumentStat = 100; - - // 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; //TODO: there are couple of engine parameters we should update here as well if the instrument was updated (see LoadInstrument()) - 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); - } - - this->AudioDeviceChannelLeft = 0; - this->AudioDeviceChannelRight = 1; - this->pOutputLeft = pAudioOutputDevice->Channel(0)->Buffer(); - this->pOutputRight = pAudioOutputDevice->Channel(1)->Buffer(); - this->MaxSamplesPerCycle = pAudioOutputDevice->MaxSamplesPerCycle(); - this->SampleRate = pAudioOutputDevice->SampleRate(); - - // FIXME: audio drivers with varying fragment sizes might be a problem here - MaxFadeOutPos = MaxSamplesPerCycle - int(double(SampleRate) * EG_MIN_RELEASE_TIME) - 1; - if (MaxFadeOutPos < 0) - throw LinuxSamplerException("EG_MIN_RELEASE_TIME in EGADSR.h to big for current audio fragment size / sampling rate!"); - - // (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 (RTList::Iterator iterVoice = pVoicePool->allocAppend(); iterVoice == pVoicePool->last(); iterVoice = pVoicePool->allocAppend()) { - iterVoice->pDiskThread = this->pDiskThread; - 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 (RTList::Iterator iterVoice = pVoicePool->allocAppend(); iterVoice == pVoicePool->last(); iterVoice = pVoicePool->allocAppend()) { - if (!iterVoice->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); - AudioDeviceChannelLeft = -1; - AudioDeviceChannelRight = -1; - } - } - - /** - * 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; - } - - - // update time of start and end of this audio fragment (as events' time stamps relate to this) - pEventGenerator->UpdateFragmentTime(Samples); - - - // empty the event lists for the new fragment - pEvents->clear(); - pCCEvents->clear(); - for (uint i = 0; i < Event::destination_count; i++) { - pSynthesisEvents[i]->clear(); - } - { - RTList::Iterator iuiKey = pActiveKeys->first(); - RTList::Iterator end = pActiveKeys->end(); - for(; iuiKey != end; ++iuiKey) { - pMIDIKeyInfo[*iuiKey].pEvents->clear(); // free all events on the key - } - } - - - // get all events from the input event queue which belong to the current fragment - { - RingBuffer::NonVolatileReader eventQueueReader = pEventQueue->get_non_volatile_reader(); - Event* pEvent; - while (true) { - // get next event from input event queue - if (!(pEvent = eventQueueReader.pop())) break; - // if younger event reached, ignore that and all subsequent ones for now - if (pEvent->FragmentPos() >= Samples) { - eventQueueReader--; - dmsg(2,("Younger Event, pos=%d ,Samples=%d!\n",pEvent->FragmentPos(),Samples)); - pEvent->ResetFragmentPos(); - break; - } - // copy event to internal event list - if (pEvents->poolIsEmpty()) { - dmsg(1,("Event pool emtpy!\n")); - break; - } - *pEvents->allocAppend() = *pEvent; - } - eventQueueReader.free(); // free all copied events from input queue - } - - - // process events - { - RTList::Iterator itEvent = pEvents->first(); - RTList::Iterator end = pEvents->end(); - for (; itEvent != end; ++itEvent) { - switch (itEvent->Type) { - case Event::type_note_on: - dmsg(5,("Engine: Note on received\n")); - ProcessNoteOn(itEvent); - break; - case Event::type_note_off: - dmsg(5,("Engine: Note off received\n")); - ProcessNoteOff(itEvent); - break; - case Event::type_control_change: - dmsg(5,("Engine: MIDI CC received\n")); - ProcessControlChange(itEvent); - break; - case Event::type_pitchbend: - dmsg(5,("Engine: Pitchbend received\n")); - ProcessPitchbend(itEvent); - break; - case Event::type_sysex: - dmsg(5,("Engine: Sysex received\n")); - ProcessSysex(itEvent); - break; - } - } - } - - - int active_voices = 0; - - // render audio from all active voices - { - RTList::Iterator iuiKey = pActiveKeys->first(); - RTList::Iterator end = pActiveKeys->end(); - while (iuiKey != end) { // iterate through all active keys - midi_key_info_t* pKey = &pMIDIKeyInfo[*iuiKey]; - ++iuiKey; - - RTList::Iterator itVoice = pKey->pActiveVoices->first(); - RTList::Iterator itVoicesEnd = pKey->pActiveVoices->end(); - for (; itVoice != itVoicesEnd; ++itVoice) { // iterate through all voices on this key - // now render current voice - itVoice->Render(Samples); - if (itVoice->IsActive()) active_voices++; // still active - else { // voice reached end, is now inactive - FreeVoice(itVoice); // remove voice from the list of active voices - } - } - } - } - - - // now render all postponed voices from voice stealing - { - RTList::Iterator itVoiceStealEvent = pVoiceStealingQueue->first(); - RTList::Iterator end = pVoiceStealingQueue->end(); - for (; itVoiceStealEvent != end; ++itVoiceStealEvent) { - Pool::Iterator itNewVoice = LaunchVoice(itVoiceStealEvent, itVoiceStealEvent->Param.Note.Layer, itVoiceStealEvent->Param.Note.ReleaseTrigger, false); - if (itNewVoice) { - for (; itNewVoice; itNewVoice = itNewVoice->itChildVoice) { - itNewVoice->Render(Samples); - if (itNewVoice->IsActive()) active_voices++; // still active - else { // voice reached end, is now inactive - FreeVoice(itNewVoice); // remove voice from the list of active voices - } - } - } - else dmsg(1,("gig::Engine: ERROR, voice stealing didn't work out!\n")); - } - } - // reset voice stealing for the new fragment - pVoiceStealingQueue->clear(); - itLastStolenVoice = RTList::Iterator(); - iuiLastStolenKey = RTList::Iterator(); - - - // free all keys which have no active voices left - { - RTList::Iterator iuiKey = pActiveKeys->first(); - RTList::Iterator end = pActiveKeys->end(); - while (iuiKey != end) { // iterate through all active keys - midi_key_info_t* pKey = &pMIDIKeyInfo[*iuiKey]; - ++iuiKey; - if (pKey->pActiveVoices->isEmpty()) FreeKey(pKey); - #if DEVMODE - else { // FIXME: should be removed before the final release (purpose: just a sanity check for debugging) - RTList::Iterator itVoice = pKey->pActiveVoices->first(); - RTList::Iterator itVoicesEnd = pKey->pActiveVoices->end(); - for (; itVoice != itVoicesEnd; ++itVoice) { // iterate through all voices on this key - if (itVoice->itKillEvent) { - dmsg(1,("gig::Engine: ERROR, killed voice survived !!!\n")); - } - } - } - #endif // DEVMODE - } - } - - - // 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.Param.Note.Key = Key; - event.Param.Note.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.Param.Note.Key = Key; - event.Param.Note.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.Param.Pitch.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.Param.CC.Controller = Controller; - event.Param.CC.Value = Value; - if (this->pEventQueue->write_space() > 0) this->pEventQueue->push(&event); - else dmsg(1,("Engine: Input event queue full!")); - } - - /** - * Will be called by the MIDI input device whenever a MIDI system - * exclusive message has arrived. - * - * @param pData - pointer to sysex data - * @param Size - lenght of sysex data (in bytes) - */ - void Engine::SendSysex(void* pData, uint Size) { - Event event = pEventGenerator->CreateEvent(); - event.Type = Event::type_sysex; - event.Param.Sysex.Size = Size; - if (pEventQueue->write_space() > 0) { - if (pSysexBuffer->write_space() >= Size) { - // copy sysex data to input buffer - uint toWrite = Size; - uint8_t* pPos = (uint8_t*) pData; - while (toWrite) { - const uint writeNow = RTMath::Min(toWrite, pSysexBuffer->write_space_to_end()); - pSysexBuffer->write(pPos, writeNow); - toWrite -= writeNow; - pPos += writeNow; - - } - // finally place sysex event into input event queue - pEventQueue->push(&event); - } - else dmsg(1,("Engine: Sysex message too large (%d byte) for input buffer (%d byte)!",Size,SYSEX_BUFFER_SIZE)); - } - else dmsg(1,("Engine: Input event queue full!")); - } - - /** - * Assigns and triggers a new voice for the respective MIDI key. - * - * @param itNoteOnEvent - key, velocity and time stamp of the event - */ - void Engine::ProcessNoteOn(Pool::Iterator& itNoteOnEvent) { - midi_key_info_t* pKey = &pMIDIKeyInfo[itNoteOnEvent->Param.Note.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) { - RTList::Iterator itCancelReleaseEvent = pKey->pEvents->allocAppend(); - if (itCancelReleaseEvent) { - *itCancelReleaseEvent = *itNoteOnEvent; // copy event - itCancelReleaseEvent->Type = Event::type_cancel_release; // transform event type - } - else dmsg(1,("Event pool emtpy!\n")); - } - - // move note on event to the key's own event list - RTList::Iterator itNoteOnEventOnKeyList = itNoteOnEvent.moveToEndOf(pKey->pEvents); - - // allocate and trigger a new voice for the key - LaunchVoice(itNoteOnEventOnKeyList, 0, false, true); - } - - /** - * 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 itNoteOffEvent - key, velocity and time stamp of the event - */ - void Engine::ProcessNoteOff(Pool::Iterator& itNoteOffEvent) { - midi_key_info_t* pKey = &pMIDIKeyInfo[itNoteOffEvent->Param.Note.Key]; - - pKey->KeyPressed = false; // the MIDI key was now released - - // release voices on this key if needed - if (pKey->Active && !SustainPedal) { - itNoteOffEvent->Type = Event::type_release; // transform event type - } - - // move event to the key's own event list - RTList::Iterator itNoteOffEventOnKeyList = itNoteOffEvent.moveToEndOf(pKey->pEvents); - - // spawn release triggered voice(s) if needed - if (pKey->ReleaseTrigger) { - LaunchVoice(itNoteOffEventOnKeyList, 0, true, false); //FIXME: for the moment we don't perform voice stealing for release triggered samples - pKey->ReleaseTrigger = false; - } - } - - /** - * Moves pitchbend event from the general (input) event list to the pitch - * event list. - * - * @param itPitchbendEvent - absolute pitch value and time stamp of the event - */ - void Engine::ProcessPitchbend(Pool::Iterator& itPitchbendEvent) { - this->Pitch = itPitchbendEvent->Param.Pitch.Pitch; // store current pitch value - itPitchbendEvent.moveToEndOf(pSynthesisEvents[Event::destination_vco]); - } - - /** - * Allocates and triggers a new voice. This method will usually be - * called by the ProcessNoteOn() method and by the voices itself - * (e.g. to spawn further voices on the same key for layered sounds). - * - * @param itNoteOnEvent - key, velocity and time stamp of the event - * @param iLayer - layer index for the new voice (optional - only - * in case of layered sounds of course) - * @param ReleaseTriggerVoice - if new voice is a release triggered voice - * (optional, default = false) - * @param VoiceStealing - if voice stealing should be performed - * when there is no free voice - * (optional, default = true) - * @returns pointer to new voice or NULL if there was no free voice or - * if an error occured while trying to trigger the new voice - */ - Pool::Iterator Engine::LaunchVoice(Pool::Iterator& itNoteOnEvent, int iLayer, bool ReleaseTriggerVoice, bool VoiceStealing) { - midi_key_info_t* pKey = &pMIDIKeyInfo[itNoteOnEvent->Param.Note.Key]; - - // allocate a new voice for the key - Pool::Iterator itNewVoice = pKey->pActiveVoices->allocAppend(); - if (itNewVoice) { - // launch the new voice - if (itNewVoice->Trigger(itNoteOnEvent, this->Pitch, this->pInstrument, iLayer, ReleaseTriggerVoice, VoiceStealing) < 0) { - dmsg(1,("Triggering new voice failed!\n")); - pKey->pActiveVoices->free(itNewVoice); - } - else { // on success - uint** ppKeyGroup = NULL; - if (itNewVoice->KeyGroup) { // if this voice / key belongs to a key group - ppKeyGroup = &ActiveKeyGroups[itNewVoice->KeyGroup]; - if (*ppKeyGroup) { // if there's already an active key in that key group - midi_key_info_t* pOtherKey = &pMIDIKeyInfo[**ppKeyGroup]; - // kill all voices on the (other) key - RTList::Iterator itVoiceToBeKilled = pOtherKey->pActiveVoices->first(); - RTList::Iterator end = pOtherKey->pActiveVoices->end(); - for (; itVoiceToBeKilled != end; ++itVoiceToBeKilled) { - if (itVoiceToBeKilled->Type != Voice::type_release_trigger) itVoiceToBeKilled->Kill(itNoteOnEvent); - } - } - } - if (!pKey->Active) { // mark as active key - pKey->Active = true; - pKey->itSelf = pActiveKeys->allocAppend(); - *pKey->itSelf = itNoteOnEvent->Param.Note.Key; - } - if (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) - return itNewVoice; // success - } - } - else if (VoiceStealing) { - // first, get total amount of required voices (dependant on amount of layers) - ::gig::Region* pRegion = pInstrument->GetRegion(itNoteOnEvent->Param.Note.Key); - if (!pRegion) return Pool::Iterator(); // nothing defined for this MIDI key, so no voice needed - int voicesRequired = pRegion->Layers; - - // now steal the (remaining) amount of voices - for (int i = iLayer; i < voicesRequired; i++) - StealVoice(itNoteOnEvent); - - // put note-on event into voice-stealing queue, so it will be reprocessed after killed voice died - RTList::Iterator itStealEvent = pVoiceStealingQueue->allocAppend(); - if (itStealEvent) { - *itStealEvent = *itNoteOnEvent; // copy event - itStealEvent->Param.Note.Layer = iLayer; - itStealEvent->Param.Note.ReleaseTrigger = ReleaseTriggerVoice; - } - else dmsg(1,("Voice stealing queue full!\n")); - } - - return Pool::Iterator(); // no free voice or error - } - - /** - * Will be called by LaunchVoice() method in case there are no free - * voices left. This method will select and kill one old voice for - * voice stealing and postpone the note-on event until the selected - * voice actually died. - * - * @param itNoteOnEvent - key, velocity and time stamp of the event - */ - void Engine::StealVoice(Pool::Iterator& itNoteOnEvent) { - if (!pEventPool->poolIsEmpty()) { - - RTList::Iterator iuiOldestKey; - RTList::Iterator itOldestVoice; - - // Select one voice for voice stealing - switch (VOICE_STEAL_ALGORITHM) { - - // try to pick the oldest voice on the key where the new - // voice should be spawned, if there is no voice on that - // key, or no voice left to kill there, then procceed with - // 'oldestkey' algorithm - case voice_steal_algo_keymask: { - midi_key_info_t* pOldestKey = &pMIDIKeyInfo[itNoteOnEvent->Param.Note.Key]; - if (itLastStolenVoice) { - itOldestVoice = itLastStolenVoice; - ++itOldestVoice; - } - else { // no voice stolen in this audio fragment cycle yet - itOldestVoice = pOldestKey->pActiveVoices->first(); - } - if (itOldestVoice) { - iuiOldestKey = pOldestKey->itSelf; - break; // selection succeeded - } - } // no break - intentional ! - - // try to pick the oldest voice on the oldest active key - // (caution: must stay after 'keymask' algorithm !) - case voice_steal_algo_oldestkey: { - if (itLastStolenVoice) { - midi_key_info_t* pOldestKey = &pMIDIKeyInfo[*iuiLastStolenKey]; - itOldestVoice = itLastStolenVoice; - ++itOldestVoice; - if (!itOldestVoice) { - iuiOldestKey = iuiLastStolenKey; - ++iuiOldestKey; - if (iuiOldestKey) { - midi_key_info_t* pOldestKey = &pMIDIKeyInfo[*iuiOldestKey]; - itOldestVoice = pOldestKey->pActiveVoices->first(); - } - else { - dmsg(1,("gig::Engine: Warning, too less voices, even for voice stealing! - Better recompile with higher MAX_AUDIO_VOICES.\n")); - return; - } - } - else iuiOldestKey = iuiLastStolenKey; - } - else { // no voice stolen in this audio fragment cycle yet - iuiOldestKey = pActiveKeys->first(); - midi_key_info_t* pOldestKey = &pMIDIKeyInfo[*iuiOldestKey]; - itOldestVoice = pOldestKey->pActiveVoices->first(); - } - break; - } - - // don't steal anything - case voice_steal_algo_none: - default: { - dmsg(1,("No free voice (voice stealing disabled)!\n")); - return; - } - } - - //FIXME: can be removed, just a sanity check for debugging - if (!itOldestVoice->IsActive()) dmsg(1,("gig::Engine: ERROR, tried to steal a voice which was not active !!!\n")); - - // now kill the selected voice - itOldestVoice->Kill(itNoteOnEvent); - // remember which voice on which key we stole, so we can simply proceed for the next voice stealing - this->itLastStolenVoice = itOldestVoice; - this->iuiLastStolenKey = iuiOldestKey; - } - else dmsg(1,("Event pool emtpy!\n")); - } - - /** - * Removes the given voice from the MIDI key's list of active voices. - * This method will be called when a voice went inactive, e.g. because - * it finished to playback its sample, finished its release stage or - * just was killed. - * - * @param itVoice - points to the voice to be freed - */ - void Engine::FreeVoice(Pool::Iterator& itVoice) { - if (itVoice) { - midi_key_info_t* pKey = &pMIDIKeyInfo[itVoice->MIDIKey]; - - uint keygroup = itVoice->KeyGroup; - - // free the voice object - pVoicePool->free(itVoice); - - // if no other voices left and member of a key group, remove from key group - if (pKey->pActiveVoices->isEmpty() && keygroup) { - uint** ppKeyGroup = &ActiveKeyGroups[keygroup]; - if (*ppKeyGroup == &*pKey->itSelf) *ppKeyGroup = NULL; // remove key from key group - } - } - else std::cerr << "Couldn't release voice! (!itVoice)\n" << std::flush; - } - - /** - * Called when there's no more voice left on a key, this call will - * update the key info respectively. - * - * @param pKey - key which is now inactive - */ - void Engine::FreeKey(midi_key_info_t* pKey) { - if (pKey->pActiveVoices->isEmpty()) { - pKey->Active = false; - pActiveKeys->free(pKey->itSelf); // remove key from list of active keys - pKey->itSelf = RTList::Iterator(); - pKey->ReleaseTrigger = false; - pKey->pEvents->clear(); - dmsg(3,("Key has no more voices now\n")); - } - else dmsg(1,("gig::Engine: Oops, tried to free a key which contains voices.\n")); + 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 pEngineChannel - engine channel on which this event occured on * @param itControlChangeEvent - controller, value and time stamp of the event */ - void Engine::ProcessControlChange(Pool::Iterator& itControlChangeEvent) { + 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)); - switch (itControlChangeEvent->Param.CC.Controller) { - case 64: { - if (itControlChangeEvent->Param.CC.Value >= 64 && !SustainPedal) { - dmsg(4,("PEDAL DOWN\n")); - SustainPedal = true; - - // cancel release process of voices if necessary - RTList::Iterator iuiKey = pActiveKeys->first(); - if (iuiKey) { - itControlChangeEvent->Type = Event::type_cancel_release; // transform event type - while (iuiKey) { - midi_key_info_t* pKey = &pMIDIKeyInfo[*iuiKey]; - ++iuiKey; - if (!pKey->KeyPressed) { - RTList::Iterator itNewEvent = pKey->pEvents->allocAppend(); - if (itNewEvent) *itNewEvent = *itControlChangeEvent; // copy event to the key's own event list - else dmsg(1,("Event pool emtpy!\n")); - } - } - } - } - if (itControlChangeEvent->Param.CC.Value < 64 && SustainPedal) { - dmsg(4,("PEDAL UP\n")); - SustainPedal = false; - - // release voices if their respective key is not pressed - RTList::Iterator iuiKey = pActiveKeys->first(); - if (iuiKey) { - itControlChangeEvent->Type = Event::type_release; // transform event type - while (iuiKey) { - midi_key_info_t* pKey = &pMIDIKeyInfo[*iuiKey]; - ++iuiKey; - if (!pKey->KeyPressed) { - RTList::Iterator itNewEvent = pKey->pEvents->allocAppend(); - if (itNewEvent) *itNewEvent = *itControlChangeEvent; // copy event to the key's own event list + 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")); } } } } - break; } } - // update controller value in the engine's controller table - ControllerTable[itControlChangeEvent->Param.CC.Controller] = itControlChangeEvent->Param.CC.Value; + // update controller value in the engine channel's controller table + pChannel->ControllerTable[itControlChangeEvent->Param.CC.Controller] = itControlChangeEvent->Param.CC.Value; - // move event from the unsorted event list to the control change event list - itControlChangeEvent.moveToEndOf(pCCEvents); - } + ProcessHardcodedControllers(pEngineChannel, itControlChangeEvent); - /** - * Reacts on MIDI system exclusive messages. - * - * @param itSysexEvent - sysex data size and time stamp of the sysex event - */ - void Engine::ProcessSysex(Pool::Iterator& itSysexEvent) { - RingBuffer::NonVolatileReader reader = pSysexBuffer->get_non_volatile_reader(); + // handle FX send controllers + ProcessFxSendControllers(pChannel, itControlChangeEvent); + } - uint8_t exclusive_status, id; - if (!reader.pop(&exclusive_status)) goto free_sysex_data; - if (!reader.pop(&id)) goto free_sysex_data; - if (exclusive_status != 0xF0) goto free_sysex_data; - - switch (id) { - case 0x41: { // Roland - uint8_t device_id, model_id, cmd_id; - if (!reader.pop(&device_id)) goto free_sysex_data; - if (!reader.pop(&model_id)) goto free_sysex_data; - if (!reader.pop(&cmd_id)) goto free_sysex_data; - if (model_id != 0x42 /*GS*/) goto free_sysex_data; - if (cmd_id != 0x12 /*DT1*/) goto free_sysex_data; - - // command address - uint8_t addr[3]; // 2 byte addr MSB, followed by 1 byte addr LSB) - const RingBuffer::NonVolatileReader checksum_reader = reader; // so we can calculate the check sum later - if (reader.read(&addr[0], 3) != 3) goto free_sysex_data; - if (addr[0] == 0x40 && addr[1] == 0x00) { // System Parameters - } - else if (addr[0] == 0x40 && addr[1] == 0x01) { // Common Parameters - } - else if (addr[0] == 0x40 && (addr[1] & 0xf0) == 0x10) { // Part Parameters (1) - switch (addr[3]) { - case 0x40: { // scale tuning - uint8_t scale_tunes[12]; // detuning of all 12 semitones of an octave - if (reader.read(&scale_tunes[0], 12) != 12) goto free_sysex_data; - uint8_t checksum; - if (!reader.pop(&checksum)) goto free_sysex_data; - if (GSCheckSum(checksum_reader, 12) != checksum) goto free_sysex_data; - for (int i = 0; i < 12; i++) scale_tunes[i] -= 64; - AdjustScale((int8_t*) scale_tunes); - break; - } - } - } - else if (addr[0] == 0x40 && (addr[1] & 0xf0) == 0x20) { // Part Parameters (2) - } - else if (addr[0] == 0x41) { // Drum Setup Parameters - } - break; + 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; } } - free_sysex_data: // finally free sysex data - pSysexBuffer->increment_read_ptr(itSysexEvent->Param.Sysex.Size); - } + // 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(); - /** - * Calculates the Roland GS sysex check sum. - * - * @param AddrReader - reader which currently points to the first GS - * command address byte of the GS sysex message in - * question - * @param DataSize - size of the GS message data (in bytes) - */ - uint8_t Engine::GSCheckSum(const RingBuffer::NonVolatileReader AddrReader, uint DataSize) { - RingBuffer::NonVolatileReader reader = AddrReader; - uint bytes = 3 /*addr*/ + DataSize; - uint8_t addr_and_data[bytes]; - reader.read(&addr_and_data[0], bytes); - uint8_t sum = 0; - for (uint i = 0; i < bytes; i++) sum += addr_and_data[i]; - return 128 - sum % 128; - } + NoteIterator itNote = GetNotePool()->fromID(itNoteOnEvent->Param.Note.ID); - /** - * Allows to tune each of the twelve semitones of an octave. - * - * @param ScaleTunes - detuning of all twelve semitones (in cents) - */ - void Engine::AdjustScale(int8_t ScaleTunes[12]) { - memcpy(&this->ScaleTuning[0], &ScaleTunes[0], 12); //TODO: currently not sample accurate - } - - /** - * 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; + ::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]; } - } - - float Engine::Volume() { - return GlobalVolume; - } - - void Engine::Volume(float f) { - GlobalVolume = f; - } - - uint Engine::Channels() { - return 2; - } + if (!pDimRgn) return Pool::Iterator(); // error (could not resolve dimension region) - void Engine::SetOutputChannel(uint EngineAudioChannel, uint AudioDeviceChannel) { - AudioChannel* pChannel = pAudioOutputDevice->Channel(AudioDeviceChannel); - if (!pChannel) throw AudioOutputException("Invalid audio output device channel " + ToString(AudioDeviceChannel)); - switch (EngineAudioChannel) { - case 0: // left output channel - pOutputLeft = pChannel->Buffer(); - AudioDeviceChannelLeft = AudioDeviceChannel; - break; - case 1: // right output channel - pOutputRight = pChannel->Buffer(); - AudioDeviceChannelRight = AudioDeviceChannel; - break; - default: - throw AudioOutputException("Invalid engine audio channel " + ToString(EngineAudioChannel)); - } - } + // no need to continue if sample is silent + if (!pDimRgn->pSample || !pDimRgn->pSample->SamplesTotal) return Pool::Iterator(); - int Engine::OutputChannel(uint EngineAudioChannel) { - switch (EngineAudioChannel) { - case 0: // left channel - return AudioDeviceChannelLeft; - case 1: // right channel - return AudioDeviceChannelRight; - default: - throw AudioOutputException("Invalid engine audio channel " + ToString(EngineAudioChannel)); - } - } + // allocate a new voice for the key + Pool::Iterator itNewVoice = GetVoicePool()->allocAppend(); - uint Engine::VoiceCount() { - return ActiveVoiceCount; - } + 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; - } - - int Engine::InstrumentStatus() { - return InstrumentStat; - } - String Engine::Description() { - return "Gigasampler Engine"; + return "GigaSampler Format Engine"; } String Engine::Version() { - String s = "$Revision: 1.18 $"; + String s = "$Revision$"; return s.substr(11, s.size() - 13); // cut dollar signs, spaces and CVS macro keyword }