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/stats.cpp
src/gui/sysConf.cpp
src/gui/sysEx.cpp
src/gui/util.cpp
src/gui/waveEdit.cpp
src/gui/volMeter.cpp

View File

@ -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;
}

View File

@ -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:

View File

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

View File

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

View File

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

View File

@ -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<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
if (learning!=-1) {
if (learning>=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);
}

View File

@ -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();

View File

@ -27,6 +27,10 @@
#include <imgui.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]={
"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!");
}

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;
}