earliest MIDI input! (no note input tho)

This commit is contained in:
tildearrow 2022-03-28 03:46:50 -05:00
parent 13a8873050
commit 5360cd73f4
15 changed files with 525 additions and 27 deletions

View file

@ -177,6 +177,7 @@ endif()
set(AUDIO_SOURCES
src/audio/abstract.cpp
src/audio/midi.cpp
src/audio/sdl.cpp
)
@ -199,6 +200,7 @@ endif()
if (USE_RTMIDI)
list(APPEND AUDIO_SOURCES src/audio/rtmidi.cpp)
message(STATUS "Building with RtMidi")
list(APPEND DEPENDENCIES_DEFINES HAVE_RTMIDI)
else()
message(STATUS "Building without RtMidi")
endif()

15
TODO.md Normal file
View file

@ -0,0 +1,15 @@
# to-do for 0.6
- **auto-backup. please.**
- non-monospaced fonts in pattern editor
- panning macro
- pitch macro
- sample editor
- keybinds
- don't allow editing samples that aren't 8/16-bit
- maybe an hex editor
- piano/input pad
- MIDI input
- all the systems
- Also confused as to why after playing a row with Ctrl-Enter, you are locked out of moving the cursor outside of +/- 1 position and THEN typing something places it under the actual cursor, and not the ghost cursor. it seems like just a way to get "please can we play rows" people to be quiet because it's otherwise effectively useless
- edit latch! and maybe separate edit masks

View file

@ -0,0 +1,21 @@
if(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt")
message(FATAL_ERROR "Cannot find install manifest: \"@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt\"")
endif(NOT EXISTS "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt")
file(READ "@CMAKE_CURRENT_BINARY_DIR@/install_manifest.txt" files)
string(REGEX REPLACE "\n" ";" files "${files}")
foreach(file ${files})
message(STATUS "Uninstalling \"$ENV{DESTDIR}${file}\"")
if(EXISTS "$ENV{DESTDIR}${file}")
exec_program(
"@CMAKE_COMMAND@" ARGS "-E remove \"$ENV{DESTDIR}${file}\""
OUTPUT_VARIABLE rm_out
RETURN_VALUE rm_retval
)
if(NOT "${rm_retval}" STREQUAL 0)
message(FATAL_ERROR "Problem when removing \"$ENV{DESTDIR}${file}\"")
endif(NOT "${rm_retval}" STREQUAL 0)
else(EXISTS "$ENV{DESTDIR}${file}")
message(STATUS "File \"$ENV{DESTDIR}${file}\" does not exist.")
endif(EXISTS "$ENV{DESTDIR}${file}")
endforeach(file)

View file

@ -56,3 +56,61 @@ bool TAAudio::init(TAAudioDesc& request, TAAudioDesc& response) {
TAAudio::~TAAudio() {
}
bool TAMidiIn::gather() {
return false;
}
bool TAMidiIn::isDeviceOpen() {
return false;
}
bool TAMidiOut::isDeviceOpen() {
return false;
}
bool TAMidiIn::openDevice(String name) {
return false;
}
bool TAMidiOut::openDevice(String name) {
return false;
}
bool TAMidiIn::closeDevice() {
return false;
}
bool TAMidiOut::closeDevice() {
return false;
}
std::vector<String> TAMidiIn::listDevices() {
return std::vector<String>();
}
std::vector<String> TAMidiOut::listDevices() {
return std::vector<String>();
}
bool TAMidiIn::init() {
return false;
}
bool TAMidiOut::init() {
return false;
}
bool TAMidiIn::quit() {
return true;
}
bool TAMidiOut::quit() {
return true;
}
TAMidiIn::~TAMidiIn() {
}
TAMidiOut::~TAMidiOut() {
}

View file

@ -52,6 +52,7 @@ void TAAudioJACK::onBufferSize(jack_nframes_t bufsize) {
void TAAudioJACK::onProcess(jack_nframes_t nframes) {
if (audioProcCallback!=NULL) {
if (midiIn!=NULL) midiIn->gather();
audioProcCallback(audioProcCallbackUser,inBufs,outBufs,desc.inChans,desc.outChans,desc.bufsize);
}
for (int i=0; i<desc.inChans; i++) {

61
src/audio/midi.cpp Normal file
View file

@ -0,0 +1,61 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 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 "taAudio.h"
#ifdef HAVE_RTMIDI
#include "rtmidi.h"
#endif
bool TAAudio::initMidi(bool jack) {
#ifndef HAVE_RTMIDI
return false;
#else
midiIn=new TAMidiInRtMidi;
midiOut=new TAMidiOutRtMidi;
if (!midiIn->init()) {
delete midiIn;
midiIn=NULL;
return false;
}
if (!midiOut->init()) {
midiIn->quit();
delete midiOut;
delete midiIn;
midiOut=NULL;
midiIn=NULL;
return false;
}
return true;
#endif
}
void TAAudio::quitMidi() {
if (midiIn!=NULL) {
midiIn->quit();
delete midiIn;
midiIn=NULL;
}
if (midiOut!=NULL) {
midiOut->quit();
delete midiOut;
midiOut=NULL;
}
}

View file

@ -1 +1,201 @@
/**
* Furnace Tracker - multi-system chiptune tracker
* Copyright (C) 2021-2022 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"
// --- IN ---
bool TAMidiInRtMidi::gather() {
std::vector<unsigned char> msg;
if (port==NULL) return false;
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));
}
queue.push(m);
}
return true;
}
std::vector<String> TAMidiInRtMidi::listDevices() {
std::vector<String> ret;
logD("listing devices.\n");
if (port==NULL) return ret;
try {
unsigned int count=port->getPortCount();
logD("got port count.\n");
for (unsigned int i=0; i<count; i++) {
String name=port->getPortName(i);
if (name!="") ret.push_back(name);
}
} catch (RtMidiError& e) {
logW("could not get MIDI inputs! %s\n",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();
for (unsigned int i=0; i<count; i++) {
if (port->getPortName(i)==name) {
port->openPort(i);
portOpen=true;
break;
}
}
isOpen=portOpen;
return portOpen;
} catch (RtMidiError& e) {
logW("could not open MIDI in device! %s\n",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\n",e.what());
isOpen=false; // still
return false;
}
isOpen=false;
return true;
}
bool TAMidiInRtMidi::init() {
if (port!=NULL) return true;
try {
port=new RtMidiIn;
} catch (RtMidiError& e) {
logW("could not initialize RtMidi in! %s\n",e.what());
return false;
}
return true;
}
bool TAMidiInRtMidi::quit() {
if (port!=NULL) {
delete port;
port=NULL;
}
return true;
}
// --- OUT ---
bool TAMidiOutRtMidi::send(TAMidiMessage& what) {
// TODO
return false;
}
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();
for (unsigned int i=0; i<count; i++) {
if (port->getPortName(i)==name) {
port->openPort(i);
portOpen=true;
break;
}
}
isOpen=portOpen;
return portOpen;
} catch (RtMidiError& e) {
logW("could not open MIDI out device! %s\n",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\n",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=port->getPortName(i);
if (name!="") ret.push_back(name);
}
} catch (RtMidiError& e) {
logW("could not get MIDI outputs! %s\n",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\n",e.what());
return false;
}
return true;
}
bool TAMidiOutRtMidi::quit() {
if (port!=NULL) {
delete port;
port=NULL;
}
return true;
}

View file

@ -21,9 +21,33 @@
#include "taAudio.h"
class TAMidiInRtMidi: public TAMidiIn {
RtMidiIn* port;
bool isOpen;
public:
bool gather();
bool isDeviceOpen();
bool openDevice(String name);
bool closeDevice();
std::vector<String> listDevices();
bool quit();
bool init();
TAMidiInRtMidi():
port(NULL),
isOpen(false) {}
};
class TAMidiOutRtMidi: public TAMidiOut {
RtMidiOut* port;
bool isOpen;
public:
bool send(TAMidiMessage& what);
bool isDeviceOpen();
bool openDevice(String name);
bool closeDevice();
std::vector<String> listDevices();
bool quit();
bool init();
TAMidiOutRtMidi():
port(NULL),
isOpen(false) {}
};

View file

@ -30,6 +30,7 @@ void taSDLProcess(void* inst, unsigned char* buf, int nframes) {
void TAAudioSDL::onProcess(unsigned char* buf, int nframes) {
if (audioProcCallback!=NULL) {
if (midiIn!=NULL) midiIn->gather();
audioProcCallback(audioProcCallbackUser,inBufs,outBufs,desc.inChans,desc.outChans,desc.bufsize);
}
float* fbuf=(float*)buf;

View file

@ -90,28 +90,9 @@ enum TAMidiMessageTypes {
};
struct TAMidiMessage {
double time;
unsigned char type;
union {
struct {
unsigned char note, vol;
} note;
struct {
unsigned char which, val;
} control;
unsigned char patch;
unsigned char pressure;
struct {
unsigned char low, high;
} pitch;
struct {
unsigned int vendor;
} sysEx;
unsigned char timeCode;
struct {
unsigned char low, high;
} position;
unsigned char song;
} data;
unsigned char data[7];
unsigned char* sysExData;
size_t sysExLen;
@ -119,6 +100,7 @@ struct TAMidiMessage {
void done();
TAMidiMessage():
time(0.0),
type(0),
sysExData(NULL),
sysExLen(0) {
@ -127,16 +109,34 @@ struct TAMidiMessage {
};
class TAMidiIn {
std::queue<TAMidiMessage> queue;
public:
std::queue<TAMidiMessage> queue;
virtual bool gather();
bool next(TAMidiMessage& where);
virtual bool isDeviceOpen();
virtual bool openDevice(String name);
virtual bool closeDevice();
virtual std::vector<String> listDevices();
virtual bool init();
virtual bool quit();
TAMidiIn() {
}
virtual ~TAMidiIn();
};
class TAMidiOut {
std::queue<TAMidiMessage> queue;
public:
bool send(TAMidiMessage& what);
virtual bool isDeviceOpen();
virtual bool openDevice(String name);
virtual bool closeDevice();
virtual std::vector<String> listDevices();
virtual bool init();
virtual bool quit();
TAMidiOut() {
}
virtual ~TAMidiOut();
};
class TAAudio {
@ -162,6 +162,8 @@ class TAAudio {
virtual bool quit();
virtual bool setRun(bool run);
virtual std::vector<String> listAudioDevices();
bool initMidi(bool jack);
void quitMidi();
virtual bool init(TAAudioDesc& request, TAAudioDesc& response);
TAAudio():
@ -172,7 +174,9 @@ class TAAudio {
outBufs(NULL),
audioProcCallback(NULL),
sampleRateChanged(NULL),
bufferSizeChanged(NULL) {}
bufferSizeChanged(NULL),
midiIn(NULL),
midiOut(NULL) {}
virtual ~TAAudio();
};

View file

@ -2653,10 +2653,24 @@ std::vector<String>& DivEngine::getAudioDevices() {
return audioDevs;
}
std::vector<String>& DivEngine::getMidiIns() {
return midiIns;
}
std::vector<String>& DivEngine::getMidiOuts() {
return midiOuts;
}
void DivEngine::rescanAudioDevices() {
audioDevs.clear();
if (output!=NULL) {
audioDevs=output->listAudioDevices();
if (output->midiIn!=NULL) {
midiIns=output->midiIn->listDevices();
}
if (output->midiOut!=NULL) {
midiOuts=output->midiOut->listDevices();
}
}
}
@ -2786,11 +2800,35 @@ bool DivEngine::initAudioBackend() {
return false;
}
if (output->initMidi(false)) {
midiIns=output->midiIn->listDevices();
midiOuts=output->midiOut->listDevices();
} else {
logW("error while initializing MIDI!\n");
}
if (output->midiIn) {
String inName=getConfString("midiInDevice","");
if (!inName.empty()) {
// try opening device
logI("opening MIDI input.\n");
if (!output->midiIn->openDevice(inName)) {
logW("could not open MIDI input device!\n");
}
}
}
return true;
}
bool DivEngine::deinitAudioBackend() {
if (output!=NULL) {
if (output->midiIn) {
if (output->midiIn->isDeviceOpen()) {
logI("closing MIDI input.\n");
output->midiIn->closeDevice();
}
}
output->quitMidi();
output->quit();
delete output;
output=NULL;

View file

@ -214,6 +214,8 @@ class DivEngine {
String lastError;
String warnings;
std::vector<String> audioDevs;
std::vector<String> midiIns;
std::vector<String> midiOuts;
std::vector<DivCommand> cmdStream;
struct SamplePreview {
@ -570,6 +572,12 @@ class DivEngine {
// get available audio devices
std::vector<String>& getAudioDevices();
// get available MIDI inputs
std::vector<String>& getMidiIns();
// get available MIDI inputs
std::vector<String>& getMidiOuts();
// rescan audio devices
void rescanAudioDevices();

View file

@ -1538,6 +1538,31 @@ void DivEngine::nextBuf(float** in, float** out, int inChans, int outChans, unsi
}
got.bufsize=size;
// process MIDI events (TODO: everything)
if (output->midiIn) while (!output->midiIn->queue.empty()) {
TAMidiMessage& msg=output->midiIn->queue.front();
int chan=msg.type&15;
switch (msg.type&0xf0) {
case TA_MIDI_NOTE_OFF: {
if (chan<0 || chan>=chans) break;
pendingNotes.push(DivNoteEvent(msg.type&15,-1,-1,-1,false));
break;
}
case TA_MIDI_NOTE_ON: {
if (chan<0 || chan>=chans) break;
pendingNotes.push(DivNoteEvent(msg.type&15,-1,(int)msg.data[0]-12,msg.data[1],true));
break;
}
case TA_MIDI_PROGRAM: {
// TODO: change instrument event thingy
break;
}
}
logD("%.2x\n",msg.type);
output->midiIn->queue.pop();
}
// process audio
if (out!=NULL && ((sPreview.sample>=0 && sPreview.sample<(int)song.sample.size()) || (sPreview.wave>=0 && sPreview.wave<(int)song.wave.size()))) {
unsigned int samp_bbOff=0;
unsigned int prevAvail=blip_samples_avail(samp_bb);

View file

@ -628,6 +628,8 @@ class FurnaceGUI {
String mainFontPath;
String patFontPath;
String audioDevice;
String midiInDevice;
String midiOutDevice;
Settings():
mainFontSize(18),
@ -679,7 +681,9 @@ class FurnaceGUI {
maxUndoSteps(100),
mainFontPath(""),
patFontPath(""),
audioDevice("") {}
audioDevice(""),
midiInDevice(""),
midiOutDevice("") {}
} settings;
char finalLayoutPath[4096];

View file

@ -222,7 +222,7 @@ void FurnaceGUI::drawSettings() {
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("Audio")) {
if (ImGui::BeginTabItem("Audio/MIDI")) {
ImGui::Text("Backend");
ImGui::SameLine();
ImGui::Combo("##Backend",&settings.audioEngine,audioBackends,2);
@ -286,6 +286,38 @@ void FurnaceGUI::drawSettings() {
ImGui::Text("want: %d samples @ %.0fHz\n",audioWant.bufsize,audioWant.rate);
ImGui::Text("got: %d samples @ %.0fHz\n",audioGot.bufsize,audioGot.rate);
ImGui::Separator();
ImGui::Text("MIDI input");
ImGui::SameLine();
String midiInName=settings.midiInDevice.empty()?"<disabled>":settings.midiInDevice;
if (ImGui::BeginCombo("##MidiInDevice",midiInName.c_str())) {
if (ImGui::Selectable("<disabled>",settings.midiInDevice.empty())) {
settings.midiInDevice="";
}
for (String& i: e->getMidiIns()) {
if (ImGui::Selectable(i.c_str(),i==settings.midiInDevice)) {
settings.midiInDevice=i;
}
}
ImGui::EndCombo();
}
ImGui::Text("MIDI output");
ImGui::SameLine();
String midiOutName=settings.midiOutDevice.empty()?"<disabled>":settings.midiOutDevice;
if (ImGui::BeginCombo("##MidiOutDevice",midiOutName.c_str())) {
if (ImGui::Selectable("<disabled>",settings.midiOutDevice.empty())) {
settings.midiOutDevice="";
}
for (String& i: e->getMidiIns()) {
if (ImGui::Selectable(i.c_str(),i==settings.midiOutDevice)) {
settings.midiOutDevice=i;
}
}
ImGui::EndCombo();
}
ImGui::EndTabItem();
}
if (ImGui::BeginTabItem("Emulation")) {
@ -970,6 +1002,8 @@ void FurnaceGUI::syncSettings() {
settings.iconSize=e->getConfInt("iconSize",16);
settings.audioEngine=(e->getConfString("audioEngine","SDL")=="SDL")?1:0;
settings.audioDevice=e->getConfString("audioDevice","");
settings.midiInDevice=e->getConfString("midiInDevice","");
settings.midiOutDevice=e->getConfString("midiOutDevice","");
settings.audioQuality=e->getConfInt("audioQuality",0);
settings.audioBufSize=e->getConfInt("audioBufSize",1024);
settings.audioRate=e->getConfInt("audioRate",44100);
@ -1252,6 +1286,8 @@ void FurnaceGUI::commitSettings() {
e->setConf("iconSize",settings.iconSize);
e->setConf("audioEngine",String(audioBackends[settings.audioEngine]));
e->setConf("audioDevice",settings.audioDevice);
e->setConf("midiInDevice",settings.midiInDevice);
e->setConf("midiOutDevice",settings.midiOutDevice);
e->setConf("audioQuality",settings.audioQuality);
e->setConf("audioBufSize",settings.audioBufSize);
e->setConf("audioRate",settings.audioRate);