From c00ed093984bc4eb60ffa1806170eb95e5400df2 Mon Sep 17 00:00:00 2001 From: Holger Date: Tue, 24 May 2022 19:11:23 +0200 Subject: [PATCH] Fixes for SYSEX channel messages / Added master volume method (also for SYSEX) (#249) * Fix for using the right MIDI channel for SYSEX. Fix for SYSEX MIDI dump output. * Small fixes for recognizing MIDI channel in SYSEX. Disabled printing of MIDI data in incoming serial data. Added some mor debug output. * Reenabled showing incomfing MIDI data when MIDI-DUmp is enabled. Fix for using MIDI channel for SYSEX. * Several fixes for SYSEX handling. * Code for sending a voice dump via MIDI started. * Fix for sending SYSEX voice dump to all interfaces. * Sending voice data via SYSEX when voice is changed. Adding master volume and changing master volume when SYSEX master volume is triggered. * Forgot to initialize nMasterVolume - just added it. * Merge. * Added a SpinLock around MIDI message processing. * Added notesOff() when changing algorithm parameter (can be extended later for other parameters). Co-authored-by: Holger Wirtz --- src/mididevice.cpp | 106 +++++++++++++++++++++------- src/mididevice.h | 9 +-- src/minidexed.cpp | 144 +++++++++++++++++++++++++++------------ src/minidexed.h | 5 ++ src/serialmididevice.cpp | 16 +++-- 5 files changed, 199 insertions(+), 81 deletions(-) diff --git a/src/mididevice.cpp b/src/mididevice.cpp index 5daacc1..d96d089 100644 --- a/src/mididevice.cpp +++ b/src/mididevice.cpp @@ -114,24 +114,37 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign switch(pMessage[0]) { case MIDI_SYSTEM_EXCLUSIVE_BEGIN: - printf("SysEx data length: [%d]\n",uint16_t(nLength)); - printf("SysEx data:\n"); + printf("MIDI%u: SysEx data length: [%d]:",nCable, uint16_t(nLength)); for (uint16_t i = 0; i < nLength; i++) { - if((i % 8) == 0) - printf("%04d:",i); + if((i % 16) == 0) + printf("\n%04d:",i); printf(" 0x%02x",pMessage[i]); - if((i % 8) == 0) - printf("\n"); } + printf("\n"); break; default: - printf("Unhandled MIDI event type %0x02x\n",pMessage[0]); + printf("MIDI%u: Unhandled MIDI event type %0x02x\n",nCable,pMessage[0]); } break; } } + // Only for debugging: +/* + if(pMessage[0]==MIDI_SYSTEM_EXCLUSIVE_BEGIN) + { + printf("MIDI%u: SysEx data length: [%d]:",nCable, uint16_t(nLength)); + for (uint16_t i = 0; i < nLength; i++) + { + if((i % 16) == 0) + printf("\n%04d:",i); + printf(" 0x%02x",pMessage[i]); + } + printf("\n"); + } +*/ + // Handle MIDI Thru if (m_DeviceName.compare (m_pConfig->GetMIDIThruIn ()) == 0) { @@ -150,6 +163,8 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign return; } + m_MIDISpinLock.Acquire (); + u8 ucStatus = pMessage[0]; u8 ucChannel = ucStatus & 0x0F; u8 ucType = ucStatus >> 4; @@ -157,17 +172,24 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign // GLOBAL MIDI SYSEX if (pMessage[0] == MIDI_SYSTEM_EXCLUSIVE_BEGIN && pMessage[3] == 0x04 && pMessage[4] == 0x01 && pMessage[nLength-1] == MIDI_SYSTEM_EXCLUSIVE_END) // MASTER VOLUME { - float32_t nMasterVolume=(pMessage[5] & (pMessage[6]<<7))/(1<<14); + float32_t nMasterVolume=((pMessage[5] & 0x7c) & ((pMessage[6] & 0x7c) <<7))/(1<<14); LOGNOTE("Master volume: %f",nMasterVolume); - // TODO: Handle global master volume + m_pSynthesizer->setMasterVolume(nMasterVolume); } else { for (unsigned nTG = 0; nTG < CConfig::ToneGenerators; nTG++) { - // MIDI SYSEX per MIDI channel - if (ucStatus == MIDI_SYSTEM_EXCLUSIVE_BEGIN && m_ChannelMap[nTG] == pMessage[2] & 0x07) - HandleSystemExclusive(pMessage, nLength, nTG); + if (ucStatus == MIDI_SYSTEM_EXCLUSIVE_BEGIN) + { + // MIDI SYSEX per MIDI channel + uint8_t ucSysExChannel = (pMessage[2] & 0x07); + if (m_ChannelMap[nTG] == ucSysExChannel || m_ChannelMap[nTG] == OmniMode) + { + LOGNOTE("MIDI-SYSEX: channel: %u, len: %u, TG: %u",m_ChannelMap[nTG],nLength,nTG); + HandleSystemExclusive(pMessage, nLength, nCable, nTG); + } + } else { if ( m_ChannelMap[nTG] == ucChannel @@ -293,6 +315,7 @@ void CMIDIDevice::MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsign } } } + m_MIDISpinLock.Release (); } void CMIDIDevice::AddDevice (const char *pDeviceName) @@ -306,7 +329,7 @@ void CMIDIDevice::AddDevice (const char *pDeviceName) s_DeviceMap.insert (std::pair (pDeviceName, this)); } -void CMIDIDevice::HandleSystemExclusive(const uint8_t* pMessage, const size_t nLength, const uint8_t nTG) +void CMIDIDevice::HandleSystemExclusive(const uint8_t* pMessage, const size_t nLength, const unsigned nCable, const uint8_t nTG) { int16_t sysex_return; @@ -319,33 +342,33 @@ void CMIDIDevice::HandleSystemExclusive(const uint8_t* pMessage, const size_t nL LOGERR("SysEx end status byte not detected."); break; case -2: - LOGERR("E: SysEx vendor not Yamaha."); + LOGERR("SysEx vendor not Yamaha."); break; case -3: - LOGERR("E: Unknown SysEx parameter change."); + LOGERR("Unknown SysEx parameter change."); break; case -4: - LOGERR(" Unknown SysEx voice or function."); + LOGERR("Unknown SysEx voice or function."); break; case -5: - LOGERR("E: Not a SysEx voice bulk upload."); + LOGERR("Not a SysEx voice bulk upload."); break; case -6: - LOGERR("E: Wrong length for SysEx voice bulk upload (not 155)."); + LOGERR("Wrong length for SysEx voice bulk upload (not 155)."); break; case -7: - LOGERR("E: Checksum error for one voice."); + LOGERR("Checksum error for one voice."); break; case -8: - LOGERR("E: Not a SysEx bank bulk upload."); + LOGERR("Not a SysEx bank bulk upload."); break; case -9: - LOGERR("E: Wrong length for SysEx bank bulk upload (not 4096)."); + LOGERR("Wrong length for SysEx bank bulk upload (not 4096)."); case -10: - LOGERR("E: Checksum error for bank."); + LOGERR("Checksum error for bank."); break; case -11: - LOGERR("E: Unknown SysEx message."); + LOGERR("Unknown SysEx message."); break; case 64: LOGDBG("SysEx Function parameter change: %d Value %d",pMessage[4],pMessage[5]); @@ -407,7 +430,6 @@ void CMIDIDevice::HandleSystemExclusive(const uint8_t* pMessage, const size_t nL // load sysex-data into voice memory LOGDBG("One Voice bulk upload"); m_pSynthesizer->loadVoiceParameters(pMessage,nTG); - break; case 200: LOGDBG("Bank bulk upload."); @@ -415,9 +437,41 @@ void CMIDIDevice::HandleSystemExclusive(const uint8_t* pMessage, const size_t nL LOGNOTE("Currently code for storing a bulk bank upload is missing!"); break; default: - LOGDBG("SysEx voice parameter change: %d value: %d",pMessage[4] + ((pMessage[3] & 0x03) * 128), pMessage[5]); - m_pSynthesizer->setVoiceDataElement(pMessage[4] + ((pMessage[3] & 0x03) * 128), pMessage[5],nTG); + if(sysex_return >= 300 && sysex_return < 500) + { + LOGDBG("SysEx voice parameter change: Parameter %d value: %d",pMessage[4] + ((pMessage[3] & 0x03) * 128), pMessage[5]); + m_pSynthesizer->setVoiceDataElement(pMessage[4] + ((pMessage[3] & 0x03) * 128), pMessage[5],nTG); + switch(pMessage[4] + ((pMessage[3] & 0x03) * 128)) + { + case 134: + m_pSynthesizer->notesOff(0,nTG); + break; + } + } + else if(sysex_return >= 500 && sysex_return < 600) + { + LOGDBG("SysEx send voice %u request",sysex_return-500); + SendSystemExclusiveVoice(sysex_return-500, nCable, nTG); + } break; } } +void CMIDIDevice::SendSystemExclusiveVoice(uint8_t nVoice, const unsigned nCable, uint8_t nTG) +{ + uint8_t voicedump[163]; + + LOGDBG("Sending SysEx voice %u ",nVoice); + + // Get voice sysex dump from TG + m_pSynthesizer->getSysExVoiceDump(voicedump, nTG); + + TDeviceMap::const_iterator Iterator; + + // send voice dump to all MIDI interfaces + for(Iterator = s_DeviceMap.begin(); Iterator != s_DeviceMap.end(); ++Iterator) + { + Iterator->second->Send (voicedump, sizeof(voicedump)*sizeof(uint8_t), nCable); + LOGNOTE("Send SYSEX voice dump to \"%s\"\n",Iterator->first); + } +} diff --git a/src/mididevice.h b/src/mididevice.h index 0b116d1..1bd5396 100644 --- a/src/mididevice.h +++ b/src/mididevice.h @@ -27,6 +27,7 @@ #include #include #include +#include class CMiniDexed; @@ -49,14 +50,12 @@ public: u8 GetChannel (unsigned nTG) const; virtual void Send (const u8 *pMessage, size_t nLength, unsigned nCable = 0) {} + virtual void SendSystemExclusiveVoice(uint8_t nVoice, const unsigned nCable, uint8_t nTG); protected: void MIDIMessageHandler (const u8 *pMessage, size_t nLength, unsigned nCable = 0); - void AddDevice (const char *pDeviceName); - - void HandleSystemExclusive(const uint8_t* pMessage, const size_t nLength, const uint8_t nTG); - + void HandleSystemExclusive(const uint8_t* pMessage, const size_t nLength, const unsigned nCable, const uint8_t nTG); private: CMiniDexed *m_pSynthesizer; CConfig *m_pConfig; @@ -67,6 +66,8 @@ private: typedef std::unordered_map TDeviceMap; static TDeviceMap s_DeviceMap; + + CSpinLock m_MIDISpinLock; }; #endif diff --git a/src/minidexed.cpp b/src/minidexed.cpp index 3eba164..5e685e2 100644 --- a/src/minidexed.cpp +++ b/src/minidexed.cpp @@ -125,6 +125,8 @@ CMiniDexed::CMiniDexed (CConfig *pConfig, CInterruptSystem *pInterrupt, } #endif + setMasterVolume(1.0); + // BEGIN setup tg_mixer tg_mixer = new AudioStereoMixer(pConfig->GetChunkSize()/2); // END setup tgmixer @@ -371,6 +373,7 @@ void CMiniDexed::ProgramChange (unsigned nProgram, unsigned nTG) assert (m_pTG[nTG]); m_pTG[nTG]->loadVoiceParameters (Buffer); + m_SerialMIDI.SendSystemExclusiveVoice(nProgram,0,nTG); m_UI.ParameterChanged (); } @@ -875,56 +878,70 @@ void CMiniDexed::ProcessSound (void) } // BEGIN TG mixing - for (uint8_t i = 0; i < CConfig::ToneGenerators; i++) - { - tg_mixer->doAddMix(i,m_OutputLevel[i]); - reverb_send_mixer->doAddMix(i,m_OutputLevel[i]); - } - // END TG mixing - - // BEGIN create SampleBuffer for holding audio data - float32_t SampleBuffer[2][nFrames]; - // END create SampleBuffer for holding audio data - - // get the mix of all TGs - tg_mixer->getMix(SampleBuffer[indexL], SampleBuffer[indexR]); + float32_t tmp_float[nFrames*2]; + int16_t tmp_int[nFrames*2]; - // BEGIN adding reverb - if (m_nParameter[ParameterReverbEnable]) + if(nMasterVolume > 0.0) { - float32_t ReverbBuffer[2][nFrames]; - float32_t ReverbSendBuffer[2][nFrames]; - - arm_fill_f32(0.0f, ReverbBuffer[indexL], nFrames); - arm_fill_f32(0.0f, ReverbBuffer[indexR], nFrames); - arm_fill_f32(0.0f, ReverbSendBuffer[indexR], nFrames); - arm_fill_f32(0.0f, ReverbSendBuffer[indexL], nFrames); - - m_ReverbSpinLock.Acquire (); - - reverb_send_mixer->getMix(ReverbSendBuffer[indexL], ReverbSendBuffer[indexR]); - reverb->doReverb(ReverbSendBuffer[indexL],ReverbSendBuffer[indexR],ReverbBuffer[indexL], ReverbBuffer[indexR],nFrames); + for (uint8_t i = 0; i < CConfig::ToneGenerators; i++) + { + tg_mixer->doAddMix(i,m_OutputLevel[i]); + reverb_send_mixer->doAddMix(i,m_OutputLevel[i]); + } + // END TG mixing + + // BEGIN create SampleBuffer for holding audio data + float32_t SampleBuffer[2][nFrames]; + // END create SampleBuffer for holding audio data - // scale down and add left reverb buffer by reverb level - arm_scale_f32(ReverbBuffer[indexL], reverb->get_level(), ReverbBuffer[indexL], nFrames); - arm_add_f32(SampleBuffer[indexL], ReverbBuffer[indexL], SampleBuffer[indexL], nFrames); - // scale down and add right reverb buffer by reverb level - arm_scale_f32(ReverbBuffer[indexR], reverb->get_level(), ReverbBuffer[indexR], nFrames); - arm_add_f32(SampleBuffer[indexR], ReverbBuffer[indexR], SampleBuffer[indexR], nFrames); + // get the mix of all TGs + tg_mixer->getMix(SampleBuffer[indexL], SampleBuffer[indexR]); - m_ReverbSpinLock.Release (); - } - // END adding reverb + // BEGIN adding reverb + if (m_nParameter[ParameterReverbEnable]) + { + float32_t ReverbBuffer[2][nFrames]; + float32_t ReverbSendBuffer[2][nFrames]; - // Convert dual float array (left, right) to single int16 array (left/right) - float32_t tmp_float[nFrames*2]; - int16_t tmp_int[nFrames*2]; - for(uint16_t i=0; igetMix(ReverbSendBuffer[indexL], ReverbSendBuffer[indexR]); + reverb->doReverb(ReverbSendBuffer[indexL],ReverbSendBuffer[indexR],ReverbBuffer[indexL], ReverbBuffer[indexR],nFrames); + + // scale down and add left reverb buffer by reverb level + arm_scale_f32(ReverbBuffer[indexL], reverb->get_level(), ReverbBuffer[indexL], nFrames); + arm_add_f32(SampleBuffer[indexL], ReverbBuffer[indexL], SampleBuffer[indexL], nFrames); + // scale down and add right reverb buffer by reverb level + arm_scale_f32(ReverbBuffer[indexR], reverb->get_level(), ReverbBuffer[indexR], nFrames); + arm_add_f32(SampleBuffer[indexR], ReverbBuffer[indexR], SampleBuffer[indexR], nFrames); + + m_ReverbSpinLock.Release (); + } + // END adding reverb + + // Convert dual float array (left, right) to single int16 array (left/right) + for(uint16_t i=0; i0.0 && nMasterVolume <1.0) + { + tmp_float[i*2]=SampleBuffer[indexL][i] * nMasterVolume; + tmp_float[(i*2)+1]=SampleBuffer[indexR][i] * nMasterVolume; + } + else if(nMasterVolume == 1.0) + { + tmp_float[i*2]=SampleBuffer[indexL][i]; + tmp_float[(i*2)+1]=SampleBuffer[indexR][i]; + } + } + arm_float_to_q15(tmp_float,tmp_int,nFrames*2); } - arm_float_to_q15(tmp_float,tmp_int,nFrames*2); + else + arm_fill_q15(0, tmp_int, nFrames * 2); if (m_pSoundDevice->Write (tmp_int, sizeof(tmp_int)) != (int) sizeof(tmp_int)) { @@ -1168,5 +1185,44 @@ void CMiniDexed::setVoiceDataElement(uint8_t data, uint8_t number, uint8_t nTG) int16_t CMiniDexed::checkSystemExclusive(const uint8_t* pMessage,const uint16_t nLength, uint8_t nTG) { + assert (nTG < CConfig::ToneGenerators); + assert (m_pTG[nTG]); + return(m_pTG[nTG]->checkSystemExclusive(pMessage, nLength)); } + +void CMiniDexed::getSysExVoiceDump(uint8_t* dest, uint8_t nTG) +{ + uint8_t checksum = 0; + uint8_t data[155]; + + assert (nTG < CConfig::ToneGenerators); + assert (m_pTG[nTG]); + + m_pTG[nTG]->getVoiceData(data); + + dest[0] = 0xF0; // SysEx start + dest[1] = 0x43; // ID=Yamaha + dest[2] = GetTGParameter(TGParameterMIDIChannel, nTG); // Sub-status and MIDI channel + dest[3] = 0x00; // Format number (0=1 voice) + dest[4] = 0x01; // Byte count MSB + dest[5] = 0x1B; // Byte count LSB + for (uint8_t n = 0; n < 155; n++) + { + checksum -= data[n]; + dest[6 + n] = data[n]; + } + dest[161] = checksum & 0x7f; // Checksum + dest[162] = 0xF7; // SysEx end +} + +void CMiniDexed::setMasterVolume (float32_t vol) +{ + if(vol < 0.0) + vol = 0.0; + else if(vol > 1.0) + vol = 1.0; + + nMasterVolume=vol; +} + diff --git a/src/minidexed.h b/src/minidexed.h index 7acf52f..0c8b575 100644 --- a/src/minidexed.h +++ b/src/minidexed.h @@ -100,6 +100,7 @@ public: void setAftertouchTarget(uint8_t target, uint8_t nTG); void loadVoiceParameters(const uint8_t* data, uint8_t nTG); void setVoiceDataElement(uint8_t data, uint8_t number, uint8_t nTG); + void getSysExVoiceDump(uint8_t* dest, uint8_t nTG); int16_t checkSystemExclusive(const uint8_t* pMessage, const uint16_t nLength, uint8_t nTG); @@ -151,6 +152,8 @@ public: bool SavePerformance (void); bool DoSavePerformance (void); + void setMasterVolume (float32_t vol); + private: int16_t ApplyNoteLimits (int16_t pitch, unsigned nTG); // returns < 0 to ignore note uint8_t m_uchOPMask[CConfig::ToneGenerators]; @@ -195,6 +198,8 @@ private: unsigned m_nReverbSend[CConfig::ToneGenerators]; + float32_t nMasterVolume; + CUserInterface m_UI; CSysExFileLoader m_SysExFileLoader; CPerformanceConfig m_PerformanceConfig; diff --git a/src/serialmididevice.cpp b/src/serialmididevice.cpp index ec819d0..aed64a6 100644 --- a/src/serialmididevice.cpp +++ b/src/serialmididevice.cpp @@ -20,10 +20,14 @@ // You should have received a copy of the GNU General Public License // along with this program. If not, see . // + +#include #include #include "serialmididevice.h" #include +LOGMODULE("serialmididevice"); + CSerialMIDIDevice::CSerialMIDIDevice (CMiniDexed *pSynthesizer, CInterruptSystem *pInterrupt, CConfig *pConfig) : CMIDIDevice (pSynthesizer, pConfig), @@ -58,23 +62,20 @@ void CSerialMIDIDevice::Process (void) if (nResult <= 0) { if(nResult!=0) - printf("Serial-Read: %d\n",nResult); + LOGERR("Serial.Read() error: %d\n",nResult); return; } if (m_pConfig->GetMIDIDumpEnabled ()) { - printf("Incoming MIDI data:\n"); + printf("Incoming MIDI data:"); for (uint16_t i = 0; i < nResult; i++) { if((i % 8) == 0) - printf("%04d:",i); + printf("\n%04d:",i); printf(" 0x%02x",Buffer[i]); - if((i > 1 ) && (i % 8) == 0) - printf("\n"); } - if((nResult % 8) != 0) - printf("\n"); + printf("\n"); } // Process MIDI messages @@ -157,6 +158,7 @@ void CSerialMIDIDevice::Process (void) } } } + void CSerialMIDIDevice::Send (const u8 *pMessage, size_t nLength, unsigned nCable) { m_SendBuffer.Write (pMessage, nLength);