From 2c643aca4ce7772b30eea20cddb71c68a700772c Mon Sep 17 00:00:00 2001 From: tildearrow Date: Sun, 8 May 2022 02:01:32 -0500 Subject: [PATCH] add preliminary TX81Z SysEx response - load voice data --- CMakeLists.txt | 1 + src/audio/abstract.cpp | 2 + src/audio/rtmidi.cpp | 20 +++++- src/audio/taAudio.h | 3 +- src/engine/engine.cpp | 16 +++++ src/engine/engine.h | 3 + src/gui/gui.cpp | 19 ++++- src/gui/gui.h | 6 ++ src/gui/insEdit.cpp | 15 ++++ src/gui/sysEx.cpp | 156 +++++++++++++++++++++++++++++++++++++++++ 10 files changed, 237 insertions(+), 4 deletions(-) create mode 100644 src/gui/sysEx.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index b41090d5..73ea7e07 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -423,6 +423,7 @@ src/gui/songInfo.cpp src/gui/songNotes.cpp src/gui/stats.cpp src/gui/sysConf.cpp +src/gui/sysEx.cpp src/gui/util.cpp src/gui/waveEdit.cpp src/gui/volMeter.cpp diff --git a/src/audio/abstract.cpp b/src/audio/abstract.cpp index 45960fea..e44fdf7e 100644 --- a/src/audio/abstract.cpp +++ b/src/audio/abstract.cpp @@ -18,6 +18,7 @@ */ #include "taAudio.h" +#include "../ta-log.h" void TAAudio::setSampleRateChangeCallback(void (*callback)(SampleRateChangeEvent)) { sampleRateChanged=callback; @@ -62,6 +63,7 @@ bool TAMidiIn::gather() { } bool TAMidiOut::send(const TAMidiMessage& what) { + logE("virtual method TAMidiOut::send() called! this is a bug!"); return false; } diff --git a/src/audio/rtmidi.cpp b/src/audio/rtmidi.cpp index c4a6f8f9..a5c5bad0 100644 --- a/src/audio/rtmidi.cpp +++ b/src/audio/rtmidi.cpp @@ -36,6 +36,11 @@ bool TAMidiInRtMidi::gather() { m.type=msg[0]; if (m.type!=TA_MIDI_SYSEX && msg.size()>1) { memcpy(m.data,msg.data()+1,MIN(msg.size()-1,7)); + } else if (m.type==TA_MIDI_SYSEX) { + m.sysExData.reset(new unsigned char[msg.size()]); + m.sysExLen=msg.size(); + logD("got a SysEx of length %ld!",msg.size()); + memcpy(m.sysExData.get(),msg.data(),msg.size()); } queue.push(m); } @@ -105,6 +110,7 @@ bool TAMidiInRtMidi::init() { if (port!=NULL) return true; try { port=new RtMidiIn; + port->ignoreTypes(false,true,true); } catch (RtMidiError& e) { logW("could not initialize RtMidi in! %s",e.what()); return false; @@ -140,8 +146,18 @@ bool TAMidiOutRtMidi::send(const TAMidiMessage& what) { break; } if (len==0) switch (what.type) { - case TA_MIDI_SYSEX: // currently not supported :< - return false; + case TA_MIDI_SYSEX: + if (what.sysExLen<1) { + logE("sysExLen is NULL!"); + return false; + } + if (what.sysExData.get()==NULL) { + logE("sysExData is NULL!"); + return false; + } + len=what.sysExLen; + port->sendMessage(what.sysExData.get(),len); + return true; break; case TA_MIDI_MTC_FRAME: case TA_MIDI_SONG_SELECT: diff --git a/src/audio/taAudio.h b/src/audio/taAudio.h index 818122bf..801a7bff 100644 --- a/src/audio/taAudio.h +++ b/src/audio/taAudio.h @@ -20,6 +20,7 @@ #ifndef _TAAUDIO_H #define _TAAUDIO_H #include "../ta-utils.h" +#include #include #include @@ -93,7 +94,7 @@ struct TAMidiMessage { double time; unsigned char type; unsigned char data[7]; - unsigned char* sysExData; + std::shared_ptr sysExData; size_t sysExLen; void submitSysEx(std::vector data); diff --git a/src/engine/engine.cpp b/src/engine/engine.cpp index 364b0d13..3419365b 100644 --- a/src/engine/engine.cpp +++ b/src/engine/engine.cpp @@ -2383,6 +2383,22 @@ void DivEngine::setMidiCallback(std::function what) { midiCallback=what; } +bool DivEngine::sendMidiMessage(TAMidiMessage& msg) { + if (output==NULL) { + logW("output is NULL!"); + return false; + } + if (output->midiOut==NULL) { + logW("MIDI output is NULL!"); + return false; + } + BUSY_BEGIN; + logD("sending MIDI message..."); + bool ret=(output->midiOut->send(msg)); + BUSY_END; + return ret; +} + void DivEngine::synchronized(const std::function& what) { BUSY_BEGIN; what(); diff --git a/src/engine/engine.h b/src/engine/engine.h index e9a5db2e..4b5614a6 100644 --- a/src/engine/engine.h +++ b/src/engine/engine.h @@ -826,6 +826,9 @@ class DivEngine { // if the specified function returns -2, note feedback will be inhibited. void setMidiCallback(std::function what); + // send MIDI message + bool sendMidiMessage(TAMidiMessage& msg); + // perform secure/sync operation void synchronized(const std::function& what); diff --git a/src/gui/gui.cpp b/src/gui/gui.cpp index 700e55d0..19fff8bb 100644 --- a/src/gui/gui.cpp +++ b/src/gui/gui.cpp @@ -2452,6 +2452,19 @@ bool FurnaceGUI::loop() { TAMidiMessage msg=midiQueue.front(); midiLock.unlock(); + if (msg.type==TA_MIDI_SYSEX) { + unsigned char* data=msg.sysExData.get(); + for (size_t i=0; i=0 && learning<(int)midiMap.binds.size()) { @@ -3745,6 +3758,7 @@ bool FurnaceGUI::init() { midiQueue.push(msg); midiLock.unlock(); e->setMidiBaseChan(cursor.xCoarse); + if (msg.type==TA_MIDI_SYSEX) return -2; if (midiMap.valueInputStyle!=0 && cursor.xFine!=0 && edit) return -2; if (!midiMap.noteInput) return -2; if (learning!=-1) return -2; @@ -4062,7 +4076,8 @@ FurnaceGUI::FurnaceGUI(): followLog(true), pianoOctaves(7), pianoOptions(false), - pianoOffset(6) { + pianoOffset(6), + hasACED(false) { // value keys valueKeys[SDLK_0]=0; valueKeys[SDLK_1]=1; @@ -4120,4 +4135,6 @@ FurnaceGUI::FurnaceGUI(): memset(chanOscLP0,0,sizeof(float)*DIV_MAX_CHANS); memset(chanOscLP1,0,sizeof(float)*DIV_MAX_CHANS); memset(lastCorrPos,0,sizeof(short)*DIV_MAX_CHANS); + + memset(acedData,0,23); } diff --git a/src/gui/gui.h b/src/gui/gui.h index c4196533..c6e75723 100644 --- a/src/gui/gui.h +++ b/src/gui/gui.h @@ -1164,6 +1164,10 @@ class FurnaceGUI { float pianoKeyHit[180]; int pianoOffset; + // TX81Z + bool hasACED; + unsigned char acedData[23]; + void drawSSGEnv(unsigned char type, const ImVec2& size); void drawWaveform(unsigned char type, bool opz, const ImVec2& size); void drawAlgorithm(unsigned char alg, FurnaceGUIFMAlgs algType, const ImVec2& size); @@ -1292,6 +1296,8 @@ class FurnaceGUI { int load(String path); void exportAudio(String path, DivAudioExportModes mode); + bool parseSysEx(unsigned char* data, size_t len); + void applyUISettings(bool updateFonts=true); void initSystemPresets(); diff --git a/src/gui/insEdit.cpp b/src/gui/insEdit.cpp index bec07b43..29be67c4 100644 --- a/src/gui/insEdit.cpp +++ b/src/gui/insEdit.cpp @@ -27,6 +27,10 @@ #include #include "plot_nolerp.h" +const unsigned char avRequest[15]={ + 0xf0, 0x43, 0x20, 0x7e, 0x4c, 0x4d, 0x20, 0x20, 0x38, 0x39, 0x37, 0x36, 0x41, 0x45, 0xf7 +}; + const char* ssgEnvTypes[8]={ "Down Down Down", "Down.", "Down Up Down Up", "Down UP", "Up Up Up", "Up.", "Up Down Up Down", "Up DOWN" }; @@ -1426,6 +1430,17 @@ void FurnaceGUI::drawInsEdit() { P(CWSliderScalar(FM_NAME(FM_AMS2),ImGuiDataType_U8,&ins->fm.ams2,&_ZERO,&_THREE)); rightClickable ImGui::TableNextColumn(); drawAlgorithm(ins->fm.alg,FM_ALGS_4OP,ImVec2(ImGui::GetContentRegionAvail().x,48.0*dpiScale)); + if (ImGui::Button("Request from TX81Z")) { + TAMidiMessage msg; + msg.type=TA_MIDI_SYSEX; + msg.sysExData.reset(new unsigned char[15]); + msg.sysExLen=15; + memcpy(msg.sysExData.get(),avRequest,15); + if (!e->sendMidiMessage(msg)) { + showError("Error while sending request (MIDI output not configured?)"); + } + } + ImGui::SameLine(); if (ImGui::Button("Send to TX81Z")) { showError("Coming soon!"); } diff --git a/src/gui/sysEx.cpp b/src/gui/sysEx.cpp new file mode 100644 index 00000000..52a9603c --- /dev/null +++ b/src/gui/sysEx.cpp @@ -0,0 +1,156 @@ +#include "gui.h" +#include "../ta-log.h" + +bool FurnaceGUI::parseSysEx(unsigned char* data, size_t len) { + SafeReader reader(data,len); + + try { + unsigned char isSysEx=reader.readC(); + if (isSysEx!=0xf0) { + logW("but this isn't a SysEx! (%x)",isSysEx); + return false; + } + + unsigned char vendor=reader.readC(); + if (vendor!=0x43) { + logV("not Yamaha. skipping."); + return true; + } + + unsigned char channel=reader.readC(); + logV("channel: %d",channel); + if (channel>15) { + logV("not a valid one"); + return true; + } + + unsigned char msgType=reader.readC(); + unsigned short msgLen=reader.readS_BE(); + std::vector instruments; + + switch (msgType) { + case 0x03: { // VCED - voice data + logD("reading VCED..."); + DivInstrument* ins=new DivInstrument; + ins->type=DIV_INS_FM; + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=ins->fm.op[i]; + op.ar=reader.readC(); + op.dr=reader.readC(); + op.d2r=reader.readC(); + op.rr=reader.readC(); + op.sl=15-reader.readC(); + reader.readC(); // LS - ignore + op.rs=reader.readC(); + reader.readC(); // EBS - ignore + op.am=reader.readC(); + reader.readC(); // KVS - ignore + op.tl=3+((99-reader.readC())*124)/99; + unsigned char freq=reader.readC(); + logV("OP%d freq: %d",i,freq); + op.mult=freq>>2; + op.dt2=freq&3; + op.dt=reader.readC(); + } + + ins->fm.alg=reader.readC(); + ins->fm.fb=reader.readC(); + reader.readC(); // LFO speed - ignore + reader.readC(); // LFO delay - ignore + reader.readC(); // PMD + reader.readC(); // AMD + reader.readC(); // LFO sync + reader.readC(); // LFO shape + ins->fm.fms=reader.readC(); + ins->fm.ams=reader.readC(); + reader.readC(); // transpose + + reader.readC(); // poly/mono + reader.readC(); // pitch bend range + reader.readC(); // porta mode + reader.readC(); // porta time + reader.readC(); // FC volume + reader.readC(); // sustain + reader.readC(); // portamento + reader.readC(); // chorus + reader.readC(); // mod wheel pitch + reader.readC(); // mod wheel amp + reader.readC(); // breath pitch + reader.readC(); // breath amp + reader.readC(); // breath pitch bias + reader.readC(); // breath EG bias + + ins->name=reader.readString(10); + + for (int i=0; i<7; i++) { // reserved (except the last one, which we don't support yet) + reader.readC(); + } + + // TX81Z-specific data + if (hasACED) { + hasACED=false; + ins->type=DIV_INS_OPZ; + + for (int i=0; i<4; i++) { + DivInstrumentFM::Operator& op=ins->fm.op[i]; + op.egt=acedData[(5*i)+0]; + if (op.egt) { + op.dt=acedData[(5*i)+1]; + } + op.dvb=acedData[(5*i)+2]; + op.ws=acedData[(5*i)+3]; + op.ksl=acedData[(5*i)+4]; + op.dam=acedData[20]; + } + } + + instruments.push_back(ins); + break; + } + case 0x7e: { // ACED - TX81Z extended data + logD("reading ACED..."); + String acedMagic=reader.readString(10); + if (acedMagic!="LM 8976AE") { + logD("not TX81Z ACED data"); + break; + } + reader.read(acedData,23); + hasACED=true; + break; + } + } + + if (!reader.seek(6+msgLen,SEEK_SET)) { + logW("couldn't seek for checksum!"); + for (DivInstrument* i: instruments) delete i; + return false; + } + unsigned char checkSum=reader.readC(); + unsigned char localCheckSum=0xff; + if (!reader.seek(6,SEEK_SET)) { + logW("couldn't seek for checksum!"); + for (DivInstrument* i: instruments) delete i; + return false; + } + for (unsigned short i=0; iname); + e->addInstrumentPtr(i); + curIns=e->song.insLen-1; + } + } catch (EndOfFileException e) { + logW("end of data already?"); + return false; + } + return true; +}