add preliminary TX81Z SysEx response

- load voice data
This commit is contained in:
tildearrow 2022-05-08 02:01:32 -05:00
parent 38b4d1d39e
commit 2c643aca4c
10 changed files with 237 additions and 4 deletions

View file

@ -423,6 +423,7 @@ src/gui/songInfo.cpp
src/gui/songNotes.cpp src/gui/songNotes.cpp
src/gui/stats.cpp src/gui/stats.cpp
src/gui/sysConf.cpp src/gui/sysConf.cpp
src/gui/sysEx.cpp
src/gui/util.cpp src/gui/util.cpp
src/gui/waveEdit.cpp src/gui/waveEdit.cpp
src/gui/volMeter.cpp src/gui/volMeter.cpp

View file

@ -18,6 +18,7 @@
*/ */
#include "taAudio.h" #include "taAudio.h"
#include "../ta-log.h"
void TAAudio::setSampleRateChangeCallback(void (*callback)(SampleRateChangeEvent)) { void TAAudio::setSampleRateChangeCallback(void (*callback)(SampleRateChangeEvent)) {
sampleRateChanged=callback; sampleRateChanged=callback;
@ -62,6 +63,7 @@ bool TAMidiIn::gather() {
} }
bool TAMidiOut::send(const TAMidiMessage& what) { bool TAMidiOut::send(const TAMidiMessage& what) {
logE("virtual method TAMidiOut::send() called! this is a bug!");
return false; return false;
} }

View file

@ -36,6 +36,11 @@ bool TAMidiInRtMidi::gather() {
m.type=msg[0]; m.type=msg[0];
if (m.type!=TA_MIDI_SYSEX && msg.size()>1) { if (m.type!=TA_MIDI_SYSEX && msg.size()>1) {
memcpy(m.data,msg.data()+1,MIN(msg.size()-1,7)); 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); queue.push(m);
} }
@ -105,6 +110,7 @@ bool TAMidiInRtMidi::init() {
if (port!=NULL) return true; if (port!=NULL) return true;
try { try {
port=new RtMidiIn; port=new RtMidiIn;
port->ignoreTypes(false,true,true);
} catch (RtMidiError& e) { } catch (RtMidiError& e) {
logW("could not initialize RtMidi in! %s",e.what()); logW("could not initialize RtMidi in! %s",e.what());
return false; return false;
@ -140,8 +146,18 @@ bool TAMidiOutRtMidi::send(const TAMidiMessage& what) {
break; break;
} }
if (len==0) switch (what.type) { if (len==0) switch (what.type) {
case TA_MIDI_SYSEX: // currently not supported :< case TA_MIDI_SYSEX:
return false; 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; break;
case TA_MIDI_MTC_FRAME: case TA_MIDI_MTC_FRAME:
case TA_MIDI_SONG_SELECT: case TA_MIDI_SONG_SELECT:

View file

@ -20,6 +20,7 @@
#ifndef _TAAUDIO_H #ifndef _TAAUDIO_H
#define _TAAUDIO_H #define _TAAUDIO_H
#include "../ta-utils.h" #include "../ta-utils.h"
#include <memory>
#include <queue> #include <queue>
#include <vector> #include <vector>
@ -93,7 +94,7 @@ struct TAMidiMessage {
double time; double time;
unsigned char type; unsigned char type;
unsigned char data[7]; unsigned char data[7];
unsigned char* sysExData; std::shared_ptr<unsigned char[]> sysExData;
size_t sysExLen; size_t sysExLen;
void submitSysEx(std::vector<unsigned char> data); void submitSysEx(std::vector<unsigned char> data);

View file

@ -2383,6 +2383,22 @@ void DivEngine::setMidiCallback(std::function<int(const TAMidiMessage&)> what) {
midiCallback=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<void()>& what) { void DivEngine::synchronized(const std::function<void()>& what) {
BUSY_BEGIN; BUSY_BEGIN;
what(); what();

View file

@ -826,6 +826,9 @@ class DivEngine {
// if the specified function returns -2, note feedback will be inhibited. // if the specified function returns -2, note feedback will be inhibited.
void setMidiCallback(std::function<int(const TAMidiMessage&)> what); void setMidiCallback(std::function<int(const TAMidiMessage&)> what);
// send MIDI message
bool sendMidiMessage(TAMidiMessage& msg);
// perform secure/sync operation // perform secure/sync operation
void synchronized(const std::function<void()>& what); void synchronized(const std::function<void()>& what);

View file

@ -2452,6 +2452,19 @@ bool FurnaceGUI::loop() {
TAMidiMessage msg=midiQueue.front(); TAMidiMessage msg=midiQueue.front();
midiLock.unlock(); midiLock.unlock();
if (msg.type==TA_MIDI_SYSEX) {
unsigned char* data=msg.sysExData.get();
for (size_t i=0; i<msg.sysExLen; i++) {
if ((i&15)==0) printf("\n");
printf("%.2x ",data[i]);
}
printf("\n");
if (!parseSysEx(data,msg.sysExLen)) {
logW("error while parsing SysEx data!");
}
}
// parse message here // parse message here
if (learning!=-1) { if (learning!=-1) {
if (learning>=0 && learning<(int)midiMap.binds.size()) { if (learning>=0 && learning<(int)midiMap.binds.size()) {
@ -3745,6 +3758,7 @@ bool FurnaceGUI::init() {
midiQueue.push(msg); midiQueue.push(msg);
midiLock.unlock(); midiLock.unlock();
e->setMidiBaseChan(cursor.xCoarse); e->setMidiBaseChan(cursor.xCoarse);
if (msg.type==TA_MIDI_SYSEX) return -2;
if (midiMap.valueInputStyle!=0 && cursor.xFine!=0 && edit) return -2; if (midiMap.valueInputStyle!=0 && cursor.xFine!=0 && edit) return -2;
if (!midiMap.noteInput) return -2; if (!midiMap.noteInput) return -2;
if (learning!=-1) return -2; if (learning!=-1) return -2;
@ -4062,7 +4076,8 @@ FurnaceGUI::FurnaceGUI():
followLog(true), followLog(true),
pianoOctaves(7), pianoOctaves(7),
pianoOptions(false), pianoOptions(false),
pianoOffset(6) { pianoOffset(6),
hasACED(false) {
// value keys // value keys
valueKeys[SDLK_0]=0; valueKeys[SDLK_0]=0;
valueKeys[SDLK_1]=1; valueKeys[SDLK_1]=1;
@ -4120,4 +4135,6 @@ FurnaceGUI::FurnaceGUI():
memset(chanOscLP0,0,sizeof(float)*DIV_MAX_CHANS); memset(chanOscLP0,0,sizeof(float)*DIV_MAX_CHANS);
memset(chanOscLP1,0,sizeof(float)*DIV_MAX_CHANS); memset(chanOscLP1,0,sizeof(float)*DIV_MAX_CHANS);
memset(lastCorrPos,0,sizeof(short)*DIV_MAX_CHANS); memset(lastCorrPos,0,sizeof(short)*DIV_MAX_CHANS);
memset(acedData,0,23);
} }

View file

@ -1164,6 +1164,10 @@ class FurnaceGUI {
float pianoKeyHit[180]; float pianoKeyHit[180];
int pianoOffset; int pianoOffset;
// TX81Z
bool hasACED;
unsigned char acedData[23];
void drawSSGEnv(unsigned char type, const ImVec2& size); void drawSSGEnv(unsigned char type, const ImVec2& size);
void drawWaveform(unsigned char type, bool opz, const ImVec2& size); void drawWaveform(unsigned char type, bool opz, const ImVec2& size);
void drawAlgorithm(unsigned char alg, FurnaceGUIFMAlgs algType, const ImVec2& size); void drawAlgorithm(unsigned char alg, FurnaceGUIFMAlgs algType, const ImVec2& size);
@ -1292,6 +1296,8 @@ class FurnaceGUI {
int load(String path); int load(String path);
void exportAudio(String path, DivAudioExportModes mode); void exportAudio(String path, DivAudioExportModes mode);
bool parseSysEx(unsigned char* data, size_t len);
void applyUISettings(bool updateFonts=true); void applyUISettings(bool updateFonts=true);
void initSystemPresets(); void initSystemPresets();

View file

@ -27,6 +27,10 @@
#include <imgui.h> #include <imgui.h>
#include "plot_nolerp.h" #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]={ const char* ssgEnvTypes[8]={
"Down Down Down", "Down.", "Down Up Down Up", "Down UP", "Up Up Up", "Up.", "Up Down Up Down", "Up DOWN" "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 P(CWSliderScalar(FM_NAME(FM_AMS2),ImGuiDataType_U8,&ins->fm.ams2,&_ZERO,&_THREE)); rightClickable
ImGui::TableNextColumn(); ImGui::TableNextColumn();
drawAlgorithm(ins->fm.alg,FM_ALGS_4OP,ImVec2(ImGui::GetContentRegionAvail().x,48.0*dpiScale)); 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")) { if (ImGui::Button("Send to TX81Z")) {
showError("Coming soon!"); showError("Coming soon!");
} }

156
src/gui/sysEx.cpp Normal file
View file

@ -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<DivInstrument*> 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; i<msgLen; i++) {
localCheckSum+=reader.readC();
}
localCheckSum=(localCheckSum&0x7f)^0x7f;
logD("checksums: %.2x %.2x",checkSum,localCheckSum);
if (checkSum!=localCheckSum) {
logW("checksum invalid!");
return false;
}
for (DivInstrument* i: instruments) {
logI("got instrument from MIDI: %s",i->name);
e->addInstrumentPtr(i);
curIns=e->song.insLen-1;
}
} catch (EndOfFileException e) {
logW("end of data already?");
return false;
}
return true;
}