--- linuxsampler/trunk/src/drivers/midi/MidiInputPort.cpp 2005/06/22 22:09:28 675 +++ linuxsampler/trunk/src/drivers/midi/MidiInputPort.cpp 2012/02/19 12:13:19 2317 @@ -3,7 +3,7 @@ * LinuxSampler - modular, streaming capable sampler * * * * Copyright (C) 2003, 2004 by Benno Senoner and Christian Schoenebeck * - * Copyright (C) 2005 Christian Schoenebeck * + * Copyright (C) 2005 - 2012 Christian Schoenebeck * * * * 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 * @@ -23,7 +23,13 @@ #include "MidiInputPort.h" +#include "../../common/global_private.h" +#include "MidiInstrumentMapper.h" #include "../../Sampler.h" +#include "../../engines/EngineFactory.h" +#include "VirtualMidiDevice.h" + +#include namespace LinuxSampler { @@ -50,7 +56,7 @@ return std::vector(); } - void MidiInputPort::ParameterName::OnSetValue(String s) throw (LinuxSamplerException) { + void MidiInputPort::ParameterName::OnSetValue(String s) throw (Exception) { return; /* FIXME: Nothing to do here */ } @@ -62,17 +68,19 @@ MidiInputPort::~MidiInputPort() { std::map::iterator iter = Parameters.begin(); while (iter != Parameters.end()) { - Parameters.erase(iter); delete iter->second; iter++; } + Parameters.clear(); } - MidiInputPort::MidiInputPort(MidiInputDevice* pDevice, int portNumber) { + MidiInputPort::MidiInputPort(MidiInputDevice* pDevice, int portNumber) + : MidiChannelMapReader(MidiChannelMap), + SysexListenersReader(SysexListeners), + virtualMidiDevicesReader(virtualMidiDevices) { this->pDevice = pDevice; this->portNumber = portNumber; Parameters["NAME"] = new ParameterName(this); - pPreviousProgramChangeEngineChannel = NULL; } MidiInputDevice* MidiInputPort::GetDevice() { @@ -88,123 +96,348 @@ } void MidiInputPort::DispatchNoteOn(uint8_t Key, uint8_t Velocity, uint MidiChannel) { + if (Key > 127 || Velocity > 127 || MidiChannel > 16) return; + const MidiChannelMap_t& midiChannelMap = MidiChannelMapReader.Lock(); // dispatch event for engines listening to the same MIDI channel { - std::set::iterator engineiter = MidiChannelMap[MidiChannel].begin(); - std::set::iterator end = MidiChannelMap[MidiChannel].end(); - for (; engineiter != end; engineiter++) (*engineiter)->SendNoteOn(Key, Velocity); + std::set::iterator engineiter = midiChannelMap[MidiChannel].begin(); + std::set::iterator end = midiChannelMap[MidiChannel].end(); + for (; engineiter != end; engineiter++) (*engineiter)->SendNoteOn(Key, Velocity, MidiChannel); } // dispatch event for engines listening to ALL MIDI channels { - std::set::iterator engineiter = MidiChannelMap[midi_chan_all].begin(); - std::set::iterator end = MidiChannelMap[midi_chan_all].end(); - for (; engineiter != end; engineiter++) (*engineiter)->SendNoteOn(Key, Velocity); + std::set::iterator engineiter = midiChannelMap[midi_chan_all].begin(); + std::set::iterator end = midiChannelMap[midi_chan_all].end(); + for (; engineiter != end; engineiter++) (*engineiter)->SendNoteOn(Key, Velocity, MidiChannel); + } + MidiChannelMapReader.Unlock(); + + // dispatch event to all low priority MIDI listeners + const std::vector& listeners = + virtualMidiDevicesReader.Lock(); + for (int i = 0; i < listeners.size(); ++i) + listeners[i]->SendNoteOnToDevice(Key, Velocity); + virtualMidiDevicesReader.Unlock(); + } + + void MidiInputPort::DispatchNoteOn(uint8_t Key, uint8_t Velocity, uint MidiChannel, int32_t FragmentPos) { + if (Key > 127 || Velocity > 127 || MidiChannel > 16) return; + const MidiChannelMap_t& midiChannelMap = MidiChannelMapReader.Lock(); + // dispatch event for engines listening to the same MIDI channel + { + std::set::iterator engineiter = midiChannelMap[MidiChannel].begin(); + std::set::iterator end = midiChannelMap[MidiChannel].end(); + for (; engineiter != end; engineiter++) (*engineiter)->SendNoteOn(Key, Velocity, MidiChannel, FragmentPos); } + // dispatch event for engines listening to ALL MIDI channels + { + std::set::iterator engineiter = midiChannelMap[midi_chan_all].begin(); + std::set::iterator end = midiChannelMap[midi_chan_all].end(); + for (; engineiter != end; engineiter++) (*engineiter)->SendNoteOn(Key, Velocity, MidiChannel, FragmentPos); + } + MidiChannelMapReader.Unlock(); + + // dispatch event to all low priority MIDI listeners + const std::vector& listeners = + virtualMidiDevicesReader.Lock(); + for (int i = 0; i < listeners.size(); ++i) + listeners[i]->SendNoteOnToDevice(Key, Velocity); + virtualMidiDevicesReader.Unlock(); } void MidiInputPort::DispatchNoteOff(uint8_t Key, uint8_t Velocity, uint MidiChannel) { + if (Key > 127 || Velocity > 127 || MidiChannel > 16) return; + const MidiChannelMap_t& midiChannelMap = MidiChannelMapReader.Lock(); // dispatch event for engines listening to the same MIDI channel { - std::set::iterator engineiter = MidiChannelMap[MidiChannel].begin(); - std::set::iterator end = MidiChannelMap[MidiChannel].end(); - for (; engineiter != end; engineiter++) (*engineiter)->SendNoteOff(Key, Velocity); + std::set::iterator engineiter = midiChannelMap[MidiChannel].begin(); + std::set::iterator end = midiChannelMap[MidiChannel].end(); + for (; engineiter != end; engineiter++) (*engineiter)->SendNoteOff(Key, Velocity, MidiChannel); } // dispatch event for engines listening to ALL MIDI channels { - std::set::iterator engineiter = MidiChannelMap[midi_chan_all].begin(); - std::set::iterator end = MidiChannelMap[midi_chan_all].end(); - for (; engineiter != end; engineiter++) (*engineiter)->SendNoteOff(Key, Velocity); + std::set::iterator engineiter = midiChannelMap[midi_chan_all].begin(); + std::set::iterator end = midiChannelMap[midi_chan_all].end(); + for (; engineiter != end; engineiter++) (*engineiter)->SendNoteOff(Key, Velocity, MidiChannel); + } + MidiChannelMapReader.Unlock(); + + // dispatch event to all low priority MIDI listeners + const std::vector& listeners = + virtualMidiDevicesReader.Lock(); + for (int i = 0; i < listeners.size(); ++i) + listeners[i]->SendNoteOffToDevice(Key, Velocity); + virtualMidiDevicesReader.Unlock(); + } + + void MidiInputPort::DispatchNoteOff(uint8_t Key, uint8_t Velocity, uint MidiChannel, int32_t FragmentPos) { + if (Key > 127 || Velocity > 127 || MidiChannel > 16) return; + const MidiChannelMap_t& midiChannelMap = MidiChannelMapReader.Lock(); + // dispatch event for engines listening to the same MIDI channel + { + std::set::iterator engineiter = midiChannelMap[MidiChannel].begin(); + std::set::iterator end = midiChannelMap[MidiChannel].end(); + for (; engineiter != end; engineiter++) (*engineiter)->SendNoteOff(Key, Velocity, MidiChannel, FragmentPos); } + // dispatch event for engines listening to ALL MIDI channels + { + std::set::iterator engineiter = midiChannelMap[midi_chan_all].begin(); + std::set::iterator end = midiChannelMap[midi_chan_all].end(); + for (; engineiter != end; engineiter++) (*engineiter)->SendNoteOff(Key, Velocity, MidiChannel, FragmentPos); + } + MidiChannelMapReader.Unlock(); + + // dispatch event to all low priority MIDI listeners + const std::vector& listeners = + virtualMidiDevicesReader.Lock(); + for (int i = 0; i < listeners.size(); ++i) + listeners[i]->SendNoteOffToDevice(Key, Velocity); + virtualMidiDevicesReader.Unlock(); } void MidiInputPort::DispatchPitchbend(int Pitch, uint MidiChannel) { + if (Pitch < -8192 || Pitch > 8191 || MidiChannel > 16) return; + const MidiChannelMap_t& midiChannelMap = MidiChannelMapReader.Lock(); // dispatch event for engines listening to the same MIDI channel { - std::set::iterator engineiter = MidiChannelMap[MidiChannel].begin(); - std::set::iterator end = MidiChannelMap[MidiChannel].end(); - for (; engineiter != end; engineiter++) (*engineiter)->SendPitchbend(Pitch); + std::set::iterator engineiter = midiChannelMap[MidiChannel].begin(); + std::set::iterator end = midiChannelMap[MidiChannel].end(); + for (; engineiter != end; engineiter++) (*engineiter)->SendPitchbend(Pitch, MidiChannel); } // dispatch event for engines listening to ALL MIDI channels { - std::set::iterator engineiter = MidiChannelMap[midi_chan_all].begin(); - std::set::iterator end = MidiChannelMap[midi_chan_all].end(); - for (; engineiter != end; engineiter++) (*engineiter)->SendPitchbend(Pitch); + std::set::iterator engineiter = midiChannelMap[midi_chan_all].begin(); + std::set::iterator end = midiChannelMap[midi_chan_all].end(); + for (; engineiter != end; engineiter++) (*engineiter)->SendPitchbend(Pitch, MidiChannel); } + MidiChannelMapReader.Unlock(); } - void MidiInputPort::DispatchControlChange(uint8_t Controller, uint8_t Value, uint MidiChannel) { + void MidiInputPort::DispatchPitchbend(int Pitch, uint MidiChannel, int32_t FragmentPos) { + if (Pitch < -8192 || Pitch > 8191 || MidiChannel > 16) return; + const MidiChannelMap_t& midiChannelMap = MidiChannelMapReader.Lock(); // dispatch event for engines listening to the same MIDI channel { - std::set::iterator engineiter = MidiChannelMap[MidiChannel].begin(); - std::set::iterator end = MidiChannelMap[MidiChannel].end(); - for (; engineiter != end; engineiter++) (*engineiter)->SendControlChange(Controller, Value); + std::set::iterator engineiter = midiChannelMap[MidiChannel].begin(); + std::set::iterator end = midiChannelMap[MidiChannel].end(); + for (; engineiter != end; engineiter++) (*engineiter)->SendPitchbend(Pitch, MidiChannel, FragmentPos); } // dispatch event for engines listening to ALL MIDI channels { - std::set::iterator engineiter = MidiChannelMap[midi_chan_all].begin(); - std::set::iterator end = MidiChannelMap[midi_chan_all].end(); - for (; engineiter != end; engineiter++) (*engineiter)->SendControlChange(Controller, Value); + std::set::iterator engineiter = midiChannelMap[midi_chan_all].begin(); + std::set::iterator end = midiChannelMap[midi_chan_all].end(); + for (; engineiter != end; engineiter++) (*engineiter)->SendPitchbend(Pitch, MidiChannel, FragmentPos); } + MidiChannelMapReader.Unlock(); } - void MidiInputPort::DispatchSysex(void* pData, uint Size) { + void MidiInputPort::DispatchControlChange(uint8_t Controller, uint8_t Value, uint MidiChannel) { + if (Controller > 128 || Value > 127 || MidiChannel > 16) return; + const MidiChannelMap_t& midiChannelMap = MidiChannelMapReader.Lock(); // dispatch event for engines listening to the same MIDI channel { - for (uint MidiChannel = 0; MidiChannel <= 16; MidiChannel++) { - std::set::iterator engineiter = MidiChannelMap[MidiChannel].begin(); - std::set::iterator end = MidiChannelMap[MidiChannel].end(); - for (; engineiter != end; engineiter++) { - Engine* pEngine = (*engineiter)->GetEngine(); - if (pEngine) pEngine->SendSysex(pData, Size); - } - } + std::set::iterator engineiter = midiChannelMap[MidiChannel].begin(); + std::set::iterator end = midiChannelMap[MidiChannel].end(); + for (; engineiter != end; engineiter++) (*engineiter)->SendControlChange(Controller, Value, MidiChannel); } // dispatch event for engines listening to ALL MIDI channels { - for (uint MidiChannel = 0; MidiChannel <= 16; MidiChannel++) { - std::set::iterator engineiter = MidiChannelMap[midi_chan_all].begin(); - std::set::iterator end = MidiChannelMap[midi_chan_all].end(); - for (; engineiter != end; engineiter++) { - Engine* pEngine = (*engineiter)->GetEngine(); - if (pEngine) pEngine->SendSysex(pData, Size); - } - } + std::set::iterator engineiter = midiChannelMap[midi_chan_all].begin(); + std::set::iterator end = midiChannelMap[midi_chan_all].end(); + for (; engineiter != end; engineiter++) (*engineiter)->SendControlChange(Controller, Value, MidiChannel); + } + MidiChannelMapReader.Unlock(); + + // dispatch event to all low priority MIDI listeners + const std::vector& listeners = + virtualMidiDevicesReader.Lock(); + for (int i = 0; i < listeners.size(); ++i) + listeners[i]->SendCCToDevice(Controller, Value); + virtualMidiDevicesReader.Unlock(); + } + + void MidiInputPort::DispatchControlChange(uint8_t Controller, uint8_t Value, uint MidiChannel, int32_t FragmentPos) { + if (Controller > 128 || Value > 127 || MidiChannel > 16) return; + const MidiChannelMap_t& midiChannelMap = MidiChannelMapReader.Lock(); + // dispatch event for engines listening to the same MIDI channel + { + std::set::iterator engineiter = midiChannelMap[MidiChannel].begin(); + std::set::iterator end = midiChannelMap[MidiChannel].end(); + for (; engineiter != end; engineiter++) (*engineiter)->SendControlChange(Controller, Value, MidiChannel, FragmentPos); } + // dispatch event for engines listening to ALL MIDI channels + { + std::set::iterator engineiter = midiChannelMap[midi_chan_all].begin(); + std::set::iterator end = midiChannelMap[midi_chan_all].end(); + for (; engineiter != end; engineiter++) (*engineiter)->SendControlChange(Controller, Value, MidiChannel, FragmentPos); + } + MidiChannelMapReader.Unlock(); + + // dispatch event to all low priority MIDI listeners + const std::vector& listeners = + virtualMidiDevicesReader.Lock(); + for (int i = 0; i < listeners.size(); ++i) + listeners[i]->SendCCToDevice(Controller, Value); + virtualMidiDevicesReader.Unlock(); + } + + void MidiInputPort::DispatchSysex(void* pData, uint Size) { + const std::set allEngines = SysexListenersReader.Lock(); + // dispatch event to all engine instances + std::set::iterator engineiter = allEngines.begin(); + std::set::iterator end = allEngines.end(); + for (; engineiter != end; engineiter++) (*engineiter)->SendSysex(pData, Size, this); + SysexListenersReader.Unlock(); } void MidiInputPort::DispatchProgramChange(uint8_t Program, uint MidiChannel) { + if (Program > 127 || MidiChannel > 16) return; if (!pDevice || !pDevice->pSampler) { std::cerr << "MidiInputPort: ERROR, no sampler instance to handle program change." << "This is a bug, please report it!\n" << std::flush; return; } - Sampler* pSampler = (Sampler*) pDevice->pSampler; - SamplerChannel* pSamplerChannel = pSampler->GetSamplerChannel(Program); - if (!pSamplerChannel) return; + const MidiChannelMap_t& midiChannelMap = MidiChannelMapReader.Lock(); + // dispatch event for engines listening to the same MIDI channel + { + std::set::iterator engineiter = midiChannelMap[MidiChannel].begin(); + std::set::iterator end = midiChannelMap[MidiChannel].end(); + for (; engineiter != end; engineiter++) (*engineiter)->SendProgramChange(Program); + } + // dispatch event for engines listening to ALL MIDI channels + { + std::set::iterator engineiter = midiChannelMap[midi_chan_all].begin(); + std::set::iterator end = midiChannelMap[midi_chan_all].end(); + for (; engineiter != end; engineiter++) (*engineiter)->SendProgramChange(Program); + } + MidiChannelMapReader.Unlock(); + } - EngineChannel* pEngineChannel = pSamplerChannel->GetEngineChannel(); - if (!pEngineChannel) return; + void MidiInputPort::DispatchBankSelectMsb(uint8_t BankMSB, uint MidiChannel) { + if (BankMSB > 127 || MidiChannel > 16) return; + if (!pDevice || !pDevice->pSampler) { + std::cerr << "MidiInputPort: ERROR, no sampler instance to handle bank select MSB." + << "This is a bug, please report it!\n" << std::flush; + return; + } + const MidiChannelMap_t& midiChannelMap = MidiChannelMapReader.Lock(); + // dispatch event for engines listening to the same MIDI channel + { + std::set::iterator engineiter = midiChannelMap[MidiChannel].begin(); + std::set::iterator end = midiChannelMap[MidiChannel].end(); + // according to the MIDI specs, a bank select should not alter the patch + for (; engineiter != end; engineiter++) (*engineiter)->SetMidiBankMsb(BankMSB); + } + // dispatch event for engines listening to ALL MIDI channels + { + std::set::iterator engineiter = midiChannelMap[midi_chan_all].begin(); + std::set::iterator end = midiChannelMap[midi_chan_all].end(); + // according to the MIDI specs, a bank select should not alter the patch + for (; engineiter != end; engineiter++) (*engineiter)->SetMidiBankMsb(BankMSB); + } + MidiChannelMapReader.Unlock(); + } - // disconnect from the engine channel which was connected by the last PC event - if (pPreviousProgramChangeEngineChannel) - Disconnect(pPreviousProgramChangeEngineChannel); + void MidiInputPort::DispatchBankSelectLsb(uint8_t BankLSB, uint MidiChannel) { + if (BankLSB > 127 || MidiChannel > 16) return; + if (!pDevice || !pDevice->pSampler) { + std::cerr << "MidiInputPort: ERROR, no sampler instance to handle bank select LSB." + << "This is a bug, please report it!\n" << std::flush; + return; + } + const MidiChannelMap_t& midiChannelMap = MidiChannelMapReader.Lock(); + // dispatch event for engines listening to the same MIDI channel + { + std::set::iterator engineiter = midiChannelMap[MidiChannel].begin(); + std::set::iterator end = midiChannelMap[MidiChannel].end(); + // according to the MIDI specs, a bank select should not alter the patch + for (; engineiter != end; engineiter++) (*engineiter)->SetMidiBankLsb(BankLSB); + } + // dispatch event for engines listening to ALL MIDI channels + { + std::set::iterator engineiter = midiChannelMap[midi_chan_all].begin(); + std::set::iterator end = midiChannelMap[midi_chan_all].end(); + // according to the MIDI specs, a bank select should not alter the patch + for (; engineiter != end; engineiter++) (*engineiter)->SetMidiBankLsb(BankLSB); + } + MidiChannelMapReader.Unlock(); + } + + void MidiInputPort::DispatchRaw(uint8_t* pData) { + uint8_t channel = pData[0] & 0x0f; + switch (pData[0] & 0xf0) { + case 0x80: + DispatchNoteOff(pData[1], pData[2], channel); + break; + case 0x90: + if (pData[2]) { + DispatchNoteOn(pData[1], pData[2], channel); + } else { + DispatchNoteOff(pData[1], pData[2], channel); + } + break; + case 0xb0: + if (pData[1] == 0) { + DispatchBankSelectMsb(pData[2], channel); + } else if (pData[1] == 32) { + DispatchBankSelectLsb(pData[2], channel); + } + DispatchControlChange(pData[1], pData[2], channel); + break; + case 0xc0: + DispatchProgramChange(pData[1], channel); + break; + case 0xd0: + DispatchControlChange(128, pData[1], channel); + break; + case 0xe0: + DispatchPitchbend((pData[1] | pData[2] << 7) - 8192, channel); + break; + } + } - // now connect to the new engine channel and remember it - try { - Connect(pEngineChannel, (midi_chan_t) MidiChannel); - pPreviousProgramChangeEngineChannel = pEngineChannel; + void MidiInputPort::DispatchRaw(uint8_t* pData, int32_t FragmentPos) { + uint8_t channel = pData[0] & 0x0f; + switch (pData[0] & 0xf0) { + case 0x80: + DispatchNoteOff(pData[1], pData[2], channel, FragmentPos); + break; + case 0x90: + if (pData[2]) { + DispatchNoteOn(pData[1], pData[2], channel, FragmentPos); + } else { + DispatchNoteOff(pData[1], pData[2], channel, FragmentPos); + } + break; + case 0xb0: + if (pData[1] == 0) { + DispatchBankSelectMsb(pData[2], channel); + } else if (pData[1] == 32) { + DispatchBankSelectLsb(pData[2], channel); + } + DispatchControlChange(pData[1], pData[2], channel, FragmentPos); + break; + case 0xc0: + DispatchProgramChange(pData[1], channel); + break; + case 0xd0: + DispatchControlChange(128, pData[1], channel, FragmentPos); + break; + case 0xe0: + DispatchPitchbend((pData[1] | pData[2] << 7) - 8192, channel, FragmentPos); + break; } - catch (...) { /* NOOP */ } } void MidiInputPort::Connect(EngineChannel* pEngineChannel, midi_chan_t MidiChannel) { if (MidiChannel < 0 || MidiChannel > 16) throw MidiInputException("MIDI channel index out of bounds"); - // firt check if desired connection is already established + // first check if desired connection is already established MidiChannelMapMutex.Lock(); - bool bAlreadyDone = MidiChannelMap[MidiChannel].count(pEngineChannel); + MidiChannelMap_t& midiChannelMap = MidiChannelMap.GetConfigForUpdate(); + bool bAlreadyDone = midiChannelMap[MidiChannel].count(pEngineChannel); MidiChannelMapMutex.Unlock(); if (bAlreadyDone) return; @@ -213,7 +446,8 @@ // register engine channel on the desired MIDI channel MidiChannelMapMutex.Lock(); - MidiChannelMap[MidiChannel].insert(pEngineChannel); + MidiChannelMap.GetConfigForUpdate()[MidiChannel].insert(pEngineChannel); + MidiChannelMap.SwitchConfig()[MidiChannel].insert(pEngineChannel); MidiChannelMapMutex.Unlock(); // inform engine channel about this connection @@ -231,9 +465,20 @@ // unregister engine channel from all MIDI channels MidiChannelMapMutex.Lock(); try { - for (int i = 0; i <= 16; i++) { - bChannelFound |= MidiChannelMap[i].count(pEngineChannel); - MidiChannelMap[i].erase(pEngineChannel); + { + MidiChannelMap_t& midiChannelMap = MidiChannelMap.GetConfigForUpdate(); + for (int i = 0; i <= 16; i++) { + bChannelFound |= midiChannelMap[i].count(pEngineChannel); + midiChannelMap[i].erase(pEngineChannel); + } + } + // do the same update again, after switching to the other config + { + MidiChannelMap_t& midiChannelMap = MidiChannelMap.SwitchConfig(); + for (int i = 0; i <= 16; i++) { + bChannelFound |= midiChannelMap[i].count(pEngineChannel); + midiChannelMap[i].erase(pEngineChannel); + } } } catch(...) { /* NOOP */ } @@ -246,4 +491,49 @@ pEngineChannel->StatusChanged(true); } + SynchronizedConfig > MidiInputPort::SysexListeners; + + void MidiInputPort::AddSysexListener(Engine* engine) { + std::pair::iterator, bool> p = SysexListeners.GetConfigForUpdate().insert(engine); + if (p.second) SysexListeners.SwitchConfig().insert(engine); + } + + bool MidiInputPort::RemoveSysexListener(Engine* engine) { + int count = SysexListeners.GetConfigForUpdate().erase(engine); + if (count) SysexListeners.SwitchConfig().erase(engine); + return count; + } + + void MidiInputPort::Connect(VirtualMidiDevice* pDevice) { + virtualMidiDevicesMutex.Lock(); + // double buffer ... double work ... + { + std::vector& devices = + virtualMidiDevices.GetConfigForUpdate(); + devices.push_back(pDevice); + } + { + std::vector& devices = + virtualMidiDevices.SwitchConfig(); + devices.push_back(pDevice); + } + virtualMidiDevicesMutex.Unlock(); + } + + void MidiInputPort::Disconnect(VirtualMidiDevice* pDevice) { + virtualMidiDevicesMutex.Lock(); + // double buffer ... double work ... + { + std::vector& devices = + virtualMidiDevices.GetConfigForUpdate(); + devices.erase(std::find(devices.begin(), devices.end(), pDevice)); + } + { + std::vector& devices = + virtualMidiDevices.SwitchConfig(); + devices.erase(std::find(devices.begin(), devices.end(), pDevice)); + } + virtualMidiDevicesMutex.Unlock(); + } + } // namespace LinuxSampler