furnace/src/audio/rtmidi.cpp

296 lines
7.1 KiB
C++

/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2023 tildearrow and contributors
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#include "rtmidi.h"
#include "../ta-log.h"
#include "taAudio.h"
String sanitizePortName(const String& name) {
#if defined(_WIN32)
// remove port number
size_t namePos=name.rfind(' ');
if (namePos!=String::npos) {
return name.substr(0,namePos);
}
return name;
#elif defined(__linux__)
// remove port location
size_t namePos=name.rfind(' ');
if (namePos!=String::npos) {
return name.substr(0,namePos);
}
return name;
#else
return name;
#endif
}
// --- IN ---
bool TAMidiInRtMidi::gather() {
std::vector<unsigned char> msg;
if (port==NULL) return false;
try {
while (true) {
TAMidiMessage m;
double t=port->getMessage(&msg);
if (msg.empty()) break;
// parse message
m.time=t;
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=std::shared_ptr<unsigned char>(new unsigned char[msg.size()],std::default_delete<unsigned char[]>());
m.sysExLen=msg.size();
logD("got a SysEx of length %ld!",msg.size());
memcpy(m.sysExData.get(),msg.data(),msg.size());
}
queue.push(m);
}
} catch (RtMidiError& e) {
logE("MIDI input error! %s",e.what());
closeDevice();
return false;
}
return true;
}
std::vector<String> TAMidiInRtMidi::listDevices() {
std::vector<String> ret;
logD("listing devices.");
if (port==NULL) return ret;
try {
unsigned int count=port->getPortCount();
logD("got port count.");
for (unsigned int i=0; i<count; i++) {
String name=sanitizePortName(port->getPortName(i));
if (name!="") ret.push_back(name);
}
} catch (RtMidiError& e) {
logW("could not get MIDI inputs! %s",e.what());
}
return ret;
}
bool TAMidiInRtMidi::isDeviceOpen() {
return isOpen;
}
bool TAMidiInRtMidi::openDevice(String name) {
if (port==NULL) return false;
if (isOpen) return false;
try {
bool portOpen=false;
unsigned int count=port->getPortCount();
logD("finding port %s...",name);
for (unsigned int i=0; i<count; i++) {
String portName=sanitizePortName(port->getPortName(i));
logV("- %d: %s",i,portName);
if (portName==name) {
logD("opening port %d...",i);
port->openPort(i);
portOpen=true;
break;
}
}
isOpen=portOpen;
if (!portOpen) logW("could not find MIDI in device...");
return portOpen;
} catch (RtMidiError& e) {
logW("could not open MIDI in device! %s",e.what());
return false;
}
return true;
}
bool TAMidiInRtMidi::closeDevice() {
if (port==NULL) return false;
if (!isOpen) return false;
try {
port->closePort();
} catch (RtMidiError& e) {
logW("could not close MIDI in device! %s",e.what());
isOpen=false; // still
return false;
}
isOpen=false;
return true;
}
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;
}
return true;
}
bool TAMidiInRtMidi::quit() {
if (port!=NULL) {
delete port;
port=NULL;
}
return true;
}
// --- OUT ---
bool TAMidiOutRtMidi::send(const TAMidiMessage& what) {
if (!isOpen) return false;
if (what.type<0x80) return false;
size_t len=0;
switch (what.type&0xf0) {
case TA_MIDI_NOTE_OFF:
case TA_MIDI_NOTE_ON:
case TA_MIDI_AFTERTOUCH:
case TA_MIDI_CONTROL:
case TA_MIDI_PITCH_BEND:
len=3;
break;
case TA_MIDI_PROGRAM:
case TA_MIDI_CHANNEL_AFTERTOUCH:
len=2;
break;
}
if (len==0) switch (what.type) {
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;
try {
port->sendMessage(what.sysExData.get(),len);
} catch (RtMidiError& e) {
logE("MIDI output error! %s",e.what());
return false;
}
return true;
break;
case TA_MIDI_MTC_FRAME:
case TA_MIDI_SONG_SELECT:
len=2;
break;
case TA_MIDI_POSITION:
len=3;
break;
default:
len=1;
break;
}
try {
port->sendMessage((const unsigned char*)&what.type,len);
} catch (RtMidiError& e) {
logE("MIDI output error! %s",e.what());
return false;
}
return true;
}
bool TAMidiOutRtMidi::isDeviceOpen() {
return isOpen;
}
bool TAMidiOutRtMidi::openDevice(String name) {
if (port==NULL) return false;
if (isOpen) return false;
try {
bool portOpen=false;
unsigned int count=port->getPortCount();
logD("finding port %s...",name);
for (unsigned int i=0; i<count; i++) {
String portName=sanitizePortName(port->getPortName(i));
logV("- %d: %s",i,portName);
if (portName==name) {
logD("opening port %d...",i);
port->openPort(i);
portOpen=true;
break;
}
}
isOpen=portOpen;
if (!portOpen) logW("could not find MIDI out device...");
return portOpen;
} catch (RtMidiError& e) {
logW("could not open MIDI out device! %s",e.what());
return false;
}
return true;
}
bool TAMidiOutRtMidi::closeDevice() {
if (port==NULL) return false;
if (!isOpen) return false;
try {
port->closePort();
} catch (RtMidiError& e) {
logW("could not close MIDI out device! %s",e.what());
isOpen=false; // still
return false;
}
isOpen=false;
return true;
}
std::vector<String> TAMidiOutRtMidi::listDevices() {
std::vector<String> ret;
if (port==NULL) return ret;
try {
unsigned int count=port->getPortCount();
for (unsigned int i=0; i<count; i++) {
String name=sanitizePortName(port->getPortName(i));
if (name!="") ret.push_back(name);
}
} catch (RtMidiError& e) {
logW("could not get MIDI outputs! %s",e.what());
}
return ret;
}
bool TAMidiOutRtMidi::init() {
if (port!=NULL) return true;
try {
port=new RtMidiOut;
} catch (RtMidiError& e) {
logW("could not initialize RtMidi out! %s",e.what());
return false;
}
return true;
}
bool TAMidiOutRtMidi::quit() {
if (port!=NULL) {
delete port;
port=NULL;
}
return true;
}