add preliminary TX81Z SysEx response
- load voice data
This commit is contained in:
parent
38b4d1d39e
commit
2c643aca4c
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
@ -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
156
src/gui/sysEx.cpp
Normal 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;
|
||||||
|
}
|
Loading…
Reference in a new issue