parent
38b4d1d39e
commit
2c643aca4c
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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!");
|
||||
}
|
||||
|
|
|
@ -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 New Issue