Add MIDI interface classes

This commit is contained in:
Jack Andersen 2016-05-19 20:16:07 -10:00
parent 77507459cc
commit 7756fcaf76
10 changed files with 675 additions and 6 deletions

View File

@ -191,6 +191,9 @@ add_library(boo
lib/audiodev/AudioSubmix.hpp
lib/audiodev/AudioSubmix.cpp
lib/audiodev/IAudioMix.hpp
lib/audiodev/MIDIEncoder.cpp
lib/audiodev/MIDIDecoder.cpp
lib/audiodev/MIDICommon.hpp
include/boo/inputdev/IHIDListener.hpp
include/boo/IGraphicsContext.hpp
include/boo/graphicsdev/IGraphicsDataFactory.hpp
@ -198,6 +201,9 @@ add_library(boo
include/boo/audiodev/IAudioSubmix.hpp
include/boo/audiodev/IAudioVoice.hpp
include/boo/audiodev/IMIDIPort.hpp
include/boo/audiodev/IMIDIReader.hpp
include/boo/audiodev/MIDIEncoder.hpp
include/boo/audiodev/MIDIDecoder.hpp
include/boo/audiodev/IAudioVoiceEngine.hpp
include/boo/IWindow.hpp
include/boo/IApplication.hpp

View File

@ -45,7 +45,7 @@ struct IAudioVoiceEngine
virtual void pumpAndMixVoices()=0;
/** Get list of MIDI devices found on system */
virtual std::vector<std::string> enumerateMIDIDevices() const=0;
virtual std::vector<std::pair<std::string, std::string>> enumerateMIDIDevices() const=0;
/** Create ad-hoc MIDI in port and register with system */
virtual std::unique_ptr<IMIDIIn> newVirtualMIDIIn()=0;

View File

@ -2,6 +2,7 @@
#define BOO_IMIDIPORT_HPP
#include <string>
#include <functional>
namespace boo
{

View File

@ -0,0 +1,43 @@
#ifndef BOO_IMIDIREADER_HPP
#define BOO_IMIDIREADER_HPP
#include <stdlib.h>
#include <stdint.h>
namespace boo
{
class IMIDIReader
{
public:
virtual void noteOff(uint8_t chan, uint8_t key, uint8_t velocity)=0;
virtual void noteOn(uint8_t chan, uint8_t key, uint8_t velocity)=0;
virtual void notePressure(uint8_t chan, uint8_t key, uint8_t pressure)=0;
virtual void controlChange(uint8_t chan, uint8_t control, uint8_t value)=0;
virtual void programChange(uint8_t chan, uint8_t program)=0;
virtual void channelPressure(uint8_t chan, uint8_t pressure)=0;
virtual void pitchBend(uint8_t chan, int16_t pitch)=0;
virtual void allSoundOff(uint8_t chan)=0;
virtual void resetAllControllers(uint8_t chan)=0;
virtual void localControl(uint8_t chan, bool on)=0;
virtual void allNotesOff(uint8_t chan)=0;
virtual void omniMode(uint8_t chan, bool on)=0;
virtual void polyMode(uint8_t chan, bool on)=0;
virtual void sysex(const void* data, size_t len)=0;
virtual void timeCodeQuarterFrame(uint8_t message, uint8_t value)=0;
virtual void songPositionPointer(uint16_t pointer)=0;
virtual void songSelect(uint8_t song)=0;
virtual void tuneRequest()=0;
virtual void startSeq()=0;
virtual void continueSeq()=0;
virtual void stopSeq()=0;
virtual void reset()=0;
};
}
#endif // BOO_IMIDIREADER_HPP

View File

@ -0,0 +1,32 @@
#ifndef BOO_MIDIDECODER_HPP
#define BOO_MIDIDECODER_HPP
#include "boo/audiodev/IMIDIReader.hpp"
#include "boo/audiodev/IMIDIPort.hpp"
#include <functional>
namespace boo
{
class MIDIDecoder
{
IMIDIReader& m_out;
uint8_t m_status = 0;
struct ReadController
{
IMIDIIn& m_in;
bool readByte(uint8_t& a);
bool read2Bytes(uint8_t& a, uint8_t& b);
bool readBuffer(void* buf, size_t len);
ReadController(IMIDIIn& in) : m_in(in) {}
} m_readControl;
uint32_t _readContinuedValue(uint8_t a);
public:
MIDIDecoder(IMIDIIn& in, IMIDIReader& out) : m_readControl(in), m_out(out) {}
bool receiveBytes();
};
}
#endif // BOO_MIDIDECODER_HPP

View File

@ -0,0 +1,50 @@
#ifndef BOO_MIDIENCODER_HPP
#define BOO_MIDIENCODER_HPP
#include "boo/audiodev/IMIDIReader.hpp"
#include "boo/audiodev/IMIDIPort.hpp"
namespace boo
{
template <class Sender>
class MIDIEncoder : public IMIDIReader
{
Sender& m_sender;
uint8_t m_status = 0;
void _sendMessage(const uint8_t* data, size_t len);
void _sendContinuedValue(uint32_t val);
public:
MIDIEncoder(Sender& sender) : m_sender(sender) {}
void noteOff(uint8_t chan, uint8_t key, uint8_t velocity);
void noteOn(uint8_t chan, uint8_t key, uint8_t velocity);
void notePressure(uint8_t chan, uint8_t key, uint8_t pressure);
void controlChange(uint8_t chan, uint8_t control, uint8_t value);
void programChange(uint8_t chan, uint8_t program);
void channelPressure(uint8_t chan, uint8_t pressure);
void pitchBend(uint8_t chan, int16_t pitch);
void allSoundOff(uint8_t chan);
void resetAllControllers(uint8_t chan);
void localControl(uint8_t chan, bool on);
void allNotesOff(uint8_t chan);
void omniMode(uint8_t chan, bool on);
void polyMode(uint8_t chan, bool on);
void sysex(const void* data, size_t len);
void timeCodeQuarterFrame(uint8_t message, uint8_t value);
void songPositionPointer(uint16_t pointer);
void songSelect(uint8_t song);
void tuneRequest();
void startSeq();
void continueSeq();
void stopSeq();
void reset();
};
}
#endif // BOO_MIDIENCODER_HPP

View File

@ -320,9 +320,9 @@ struct ALSAAudioVoiceEngine : BaseAudioVoiceEngine
}
}
std::vector<std::string> enumerateMIDIDevices() const
std::vector<std::pair<std::string, std::string>> enumerateMIDIDevices() const
{
std::vector<std::string> ret;
std::vector<std::pair<std::string, std::string>> ret;
int status;
int card = -1; /* use -1 to prime the pump of iterating through card list */
@ -350,7 +350,8 @@ struct ALSAAudioVoiceEngine : BaseAudioVoiceEngine
snd_rawmidi_info_t *info;
snd_rawmidi_info_alloca(&info);
snd_rawmidi_info_set_device(info, device);
ret.push_back(snd_rawmidi_info_get_name(info));
ret.push_back(std::make_pair(snd_rawmidi_info_get_id(info),
snd_rawmidi_info_get_name(info)));
}
} while (device >= 0);
@ -370,6 +371,7 @@ struct ALSAAudioVoiceEngine : BaseAudioVoiceEngine
MIDIIn(snd_rawmidi_t* midi, bool virt)
: m_midi(midi), m_virtual(virt) {}
~MIDIIn() {snd_rawmidi_close(m_midi);}
bool isVirtual() const {return m_virtual;}
std::string description() const
{
@ -385,35 +387,130 @@ struct ALSAAudioVoiceEngine : BaseAudioVoiceEngine
}
};
struct MIDIOut : public IMIDIOut
{
snd_rawmidi_t* m_midi;
bool m_virtual;
MIDIOut(snd_rawmidi_t* midi, bool virt)
: m_midi(midi), m_virtual(virt) {}
~MIDIOut() {snd_rawmidi_close(m_midi);}
bool isVirtual() const {return m_virtual;}
std::string description() const
{
snd_rawmidi_info_t* info;
snd_rawmidi_info_alloca(&info);
snd_rawmidi_info(m_midi, info);
std::string ret = snd_rawmidi_info_get_name(info);
return ret;
}
size_t send(const void* buf, size_t len) const
{
return std::max(0l, snd_rawmidi_write(m_midi, buf, len));
}
};
struct MIDIInOut : public IMIDIInOut
{
snd_rawmidi_t* m_midiIn;
snd_rawmidi_t* m_midiOut;
bool m_virtual;
MIDIInOut(snd_rawmidi_t* midiIn, snd_rawmidi_t* midiOut, bool virt)
: m_midiIn(midiIn), m_midiOut(midiOut), m_virtual(virt) {}
~MIDIInOut()
{
snd_rawmidi_close(m_midiIn);
snd_rawmidi_close(m_midiOut);
}
bool isVirtual() const {return m_virtual;}
std::string description() const
{
snd_rawmidi_info_t* info;
snd_rawmidi_info_alloca(&info);
snd_rawmidi_info(m_midiIn, info);
std::string ret = snd_rawmidi_info_get_name(info);
return ret;
}
size_t receive(void* buf, size_t len) const
{
return std::max(0l, snd_rawmidi_read(m_midiOut, buf, len));
}
size_t send(const void* buf, size_t len) const
{
return std::max(0l, snd_rawmidi_write(m_midiOut, buf, len));
}
};
std::unique_ptr<IMIDIIn> newVirtualMIDIIn()
{
int status;
snd_rawmidi_t* midi;
status = snd_rawmidi_open(&midi, nullptr, "virtual", 0);
status = snd_rawmidi_open(&midi, nullptr, "virtual", SND_RAWMIDI_NONBLOCK);
if (status)
return {};
return std::make_unique<MIDIIn>(midi, true);
}
std::unique_ptr<IMIDIOut> newVirtualMIDIOut()
{
int status;
snd_rawmidi_t* midi;
status = snd_rawmidi_open(nullptr, &midi, "virtual", SND_RAWMIDI_NONBLOCK);
if (status)
return {};
return std::make_unique<MIDIOut>(midi, true);
}
std::unique_ptr<IMIDIInOut> newVirtualMIDIInOut()
{
int status;
snd_rawmidi_t* midiIn;
snd_rawmidi_t* midiOut;
status = snd_rawmidi_open(&midiIn, &midiOut, "virtual", SND_RAWMIDI_NONBLOCK);
if (status)
return {};
return std::make_unique<MIDIInOut>(midiIn, midiOut, true);
}
std::unique_ptr<IMIDIIn> newRealMIDIIn(const char* name)
{
int status;
char path[128];
snprintf(path, 128, "hw:%s", name);
snd_rawmidi_t* midi;
status = snd_rawmidi_open(&midi, nullptr, path, SND_RAWMIDI_NONBLOCK);
if (status)
return {};
return std::make_unique<MIDIIn>(midi, true);
}
std::unique_ptr<IMIDIOut> newRealMIDIOut(const char* name)
{
int status;
char path[128];
snprintf(path, 128, "hw:%s", name);
snd_rawmidi_t* midi;
status = snd_rawmidi_open(nullptr, &midi, path, SND_RAWMIDI_NONBLOCK);
if (status)
return {};
return std::make_unique<MIDIOut>(midi, true);
}
std::unique_ptr<IMIDIInOut> newRealMIDIInOut(const char* name)
{
int status;
char path[128];
snprintf(path, 128, "hw:%s", name);
snd_rawmidi_t* midiIn;
snd_rawmidi_t* midiOut;
status = snd_rawmidi_open(&midiIn, &midiOut, path, SND_RAWMIDI_NONBLOCK);
if (status)
return {};
return std::make_unique<MIDIInOut>(midiIn, midiOut, true);
}
};

View File

@ -0,0 +1,32 @@
#ifndef BOO_MIDICOMMON_HPP
#define BOO_MIDICOMMON_HPP
namespace boo
{
enum class Status
{
NoteOff = 0x80,
NoteOn = 0x90,
NotePressure = 0xA0,
ControlChange = 0xB0,
ProgramChange = 0xC0,
ChannelPressure = 0xD0,
PitchBend = 0xE0,
SysEx = 0xF0,
TimecodeQuarterFrame = 0xF1,
SongPositionPointer = 0xF2,
SongSelect = 0xF3,
TuneRequest = 0xF6,
SysExTerm = 0xF7,
TimingClock = 0xF8,
Start = 0xFA,
Continue = 0xFB,
Stop = 0xFC,
ActiveSensing = 0xFE,
Reset = 0xFF,
};
}
#endif // BOO_MIDICOMMON_HPP

View File

@ -0,0 +1,181 @@
#include "boo/audiodev/MIDIDecoder.hpp"
#include "MIDICommon.hpp"
#include <memory>
namespace boo
{
static inline uint8_t clamp7(uint8_t val) {return std::max(0, std::min(127, int(val)));}
bool MIDIDecoder::ReadController::readByte(uint8_t& a)
{
return m_in.receive(&a, 1) != 0;
}
bool MIDIDecoder::ReadController::read2Bytes(uint8_t& a, uint8_t& b)
{
uint8_t buf[2];
int len = m_in.receive(buf, 2);
a = buf[0];
b = buf[1];
return len > 1;
}
bool MIDIDecoder::ReadController::readBuffer(void* buf, size_t len)
{
return m_in.receive(buf, len) == len;
}
uint32_t MIDIDecoder::_readContinuedValue(uint8_t a)
{
uint32_t ret = a & 0x7f;
if (a & 0x80)
{
ret <<= 7;
bool good = m_readControl.readByte(a);
if (!good)
return ret;
ret |= a & 0x7f;
if (a & 0x80)
{
ret <<= 7;
good = m_readControl.readByte(a);
if (!good)
return ret;
ret |= a & 0x7f;
}
}
return ret;
}
bool MIDIDecoder::receiveBytes()
{
uint8_t a, b;
bool good = m_readControl.read2Bytes(a, b);
if (!good)
return false;
if (a & 0x80)
m_status = a;
else
b = a;
uint8_t chan = m_status & 0xf;
switch (Status(m_status & 0xf0))
{
case Status::NoteOff:
{
good = m_readControl.read2Bytes(a, b);
if (!good)
return false;
m_out.noteOff(chan, clamp7(a), clamp7(b));
break;
}
case Status::NoteOn:
{
good = m_readControl.read2Bytes(a, b);
if (!good)
return false;
m_out.noteOn(chan, clamp7(a), clamp7(b));
break;
}
case Status::NotePressure:
{
good = m_readControl.read2Bytes(a, b);
if (!good)
return false;
m_out.notePressure(chan, clamp7(a), clamp7(b));
break;
}
case Status::ControlChange:
{
good = m_readControl.read2Bytes(a, b);
if (!good)
return false;
m_out.controlChange(chan, clamp7(a), clamp7(b));
break;
}
case Status::ProgramChange:
{
m_out.programChange(chan, clamp7(b));
break;
}
case Status::ChannelPressure:
{
m_out.channelPressure(chan, clamp7(b));
break;
}
case Status::PitchBend:
{
good = m_readControl.read2Bytes(a, b);
if (!good)
return false;
m_out.pitchBend(chan, clamp7(b) * 128 + clamp7(a));
break;
}
case Status::SysEx:
{
switch (Status(m_status & 0xff))
{
case Status::SysEx:
{
uint32_t len = _readContinuedValue(a);
std::unique_ptr<uint8_t[]> buf(new uint8_t[len]);
if (!m_readControl.readBuffer(buf.get(), len))
return false;
m_out.sysex(buf.get(), len);
break;
}
case Status::TimecodeQuarterFrame:
{
good = m_readControl.read2Bytes(a, b);
if (!good)
return false;
m_out.timeCodeQuarterFrame(a >> 4 & 0x7, a & 0xf);
break;
}
case Status::SongPositionPointer:
{
good = m_readControl.read2Bytes(a, b);
if (!good)
return false;
m_out.songPositionPointer(clamp7(b) * 128 + clamp7(a));
break;
}
case Status::SongSelect:
{
m_out.songSelect(clamp7(b));
break;
}
case Status::TuneRequest:
m_out.tuneRequest();
break;
case Status::Start:
m_out.startSeq();
break;
case Status::Continue:
m_out.continueSeq();
break;
case Status::Stop:
m_out.stopSeq();
break;
case Status::Reset:
m_out.reset();
break;
case Status::SysExTerm:
case Status::TimingClock:
case Status::ActiveSensing:
default: break;
}
break;
}
default: break;
}
return true;
}
}

View File

@ -0,0 +1,227 @@
#include "boo/audiodev/MIDIEncoder.hpp"
#include "MIDICommon.hpp"
namespace boo
{
template <class Sender>
void MIDIEncoder<Sender>::_sendMessage(const uint8_t* data, size_t len)
{
if (data[0] == m_status)
m_sender.send(data + 1, len - 1);
else
{
if (data[0] & 0x80)
m_status = data[0];
m_sender.send(data, len);
}
}
template <class Sender>
void MIDIEncoder<Sender>::_sendContinuedValue(uint32_t val)
{
uint32_t send[3] = {};
uint32_t* ptr = nullptr;
if (val >= 0x4000)
{
ptr = &send[0];
send[0] = (val / 0x4000) & 0x7f;
val &= 0x3fff;
}
if (val >= 0x80)
{
if (!ptr)
ptr = &send[1];
send[1] = (val / 0x80) & 0x7f;
}
if (!ptr)
ptr = &send[2];
send[2] = val & 0x7f;
m_sender.send(ptr, 3 - (ptr - send));
}
template <class Sender>
void MIDIEncoder<Sender>::noteOff(uint8_t chan, uint8_t key, uint8_t velocity)
{
uint8_t cmd[3] = {uint8_t(int(Status::NoteOff) | (chan & 0xf)),
uint8_t(key & 0x7f), uint8_t(velocity & 0x7f)};
_sendMessage(cmd, 3);
}
template <class Sender>
void MIDIEncoder<Sender>::noteOn(uint8_t chan, uint8_t key, uint8_t velocity)
{
uint8_t cmd[3] = {uint8_t(int(Status::NoteOn) | (chan & 0xf)),
uint8_t(key & 0x7f), uint8_t(velocity & 0x7f)};
_sendMessage(cmd, 3);
}
template <class Sender>
void MIDIEncoder<Sender>::notePressure(uint8_t chan, uint8_t key, uint8_t pressure)
{
uint8_t cmd[3] = {uint8_t(int(Status::NotePressure) | (chan & 0xf)),
uint8_t(key & 0x7f), uint8_t(pressure & 0x7f)};
_sendMessage(cmd, 3);
}
template <class Sender>
void MIDIEncoder<Sender>::controlChange(uint8_t chan, uint8_t control, uint8_t value)
{
uint8_t cmd[3] = {uint8_t(int(Status::ControlChange) | (chan & 0xf)),
uint8_t(control & 0x7f), uint8_t(value & 0x7f)};
_sendMessage(cmd, 3);
}
template <class Sender>
void MIDIEncoder<Sender>::programChange(uint8_t chan, uint8_t program)
{
uint8_t cmd[2] = {uint8_t(int(Status::ProgramChange) | (chan & 0xf)),
uint8_t(program & 0x7f)};
_sendMessage(cmd, 2);
}
template <class Sender>
void MIDIEncoder<Sender>::channelPressure(uint8_t chan, uint8_t pressure)
{
uint8_t cmd[2] = {uint8_t(int(Status::ChannelPressure) | (chan & 0xf)),
uint8_t(pressure & 0x7f)};
_sendMessage(cmd, 2);
}
template <class Sender>
void MIDIEncoder<Sender>::pitchBend(uint8_t chan, int16_t pitch)
{
uint8_t cmd[3] = {uint8_t(int(Status::PitchBend) | (chan & 0xf)),
uint8_t((pitch % 128) & 0x7f), uint8_t((pitch / 128) & 0x7f)};
_sendMessage(cmd, 3);
}
template <class Sender>
void MIDIEncoder<Sender>::allSoundOff(uint8_t chan)
{
uint8_t cmd[3] = {uint8_t(int(Status::ControlChange) | (chan & 0xf)),
120, 0};
_sendMessage(cmd, 3);
}
template <class Sender>
void MIDIEncoder<Sender>::resetAllControllers(uint8_t chan)
{
uint8_t cmd[3] = {uint8_t(int(Status::ControlChange) | (chan & 0xf)),
121, 0};
_sendMessage(cmd, 3);
}
template <class Sender>
void MIDIEncoder<Sender>::localControl(uint8_t chan, bool on)
{
uint8_t cmd[3] = {uint8_t(int(Status::ControlChange) | (chan & 0xf)),
122, uint8_t(on ? 127 : 0)};
_sendMessage(cmd, 3);
}
template <class Sender>
void MIDIEncoder<Sender>::allNotesOff(uint8_t chan)
{
uint8_t cmd[3] = {uint8_t(int(Status::ControlChange) | (chan & 0xf)),
123, 0};
_sendMessage(cmd, 3);
}
template <class Sender>
void MIDIEncoder<Sender>::omniMode(uint8_t chan, bool on)
{
uint8_t cmd[3] = {uint8_t(int(Status::ControlChange) | (chan & 0xf)),
uint8_t(on ? 125 : 124), 0};
_sendMessage(cmd, 3);
}
template <class Sender>
void MIDIEncoder<Sender>::polyMode(uint8_t chan, bool on)
{
uint8_t cmd[3] = {uint8_t(int(Status::ControlChange) | (chan & 0xf)),
uint8_t(on ? 127 : 126), 0};
_sendMessage(cmd, 3);
}
template <class Sender>
void MIDIEncoder<Sender>::sysex(const void* data, size_t len)
{
uint8_t cmd = uint8_t(Status::SysEx);
_sendMessage(&cmd, 1);
_sendContinuedValue(len);
m_sender.send(data, len);
cmd = uint8_t(Status::SysExTerm);
_sendMessage(&cmd, 1);
}
template <class Sender>
void MIDIEncoder<Sender>::timeCodeQuarterFrame(uint8_t message, uint8_t value)
{
uint8_t cmd[2] = {uint8_t(int(Status::TimecodeQuarterFrame)),
uint8_t((message & 0x7 << 4) | (value & 0xf))};
_sendMessage(cmd, 2);
}
template <class Sender>
void MIDIEncoder<Sender>::songPositionPointer(uint16_t pointer)
{
uint8_t cmd[3] = {uint8_t(int(Status::SongPositionPointer)),
uint8_t((pointer % 128) & 0x7f), uint8_t((pointer / 128) & 0x7f)};
_sendMessage(cmd, 3);
}
template <class Sender>
void MIDIEncoder<Sender>::songSelect(uint8_t song)
{
uint8_t cmd[2] = {uint8_t(int(Status::TimecodeQuarterFrame)),
uint8_t(song & 0x7f)};
_sendMessage(cmd, 2);
}
template <class Sender>
void MIDIEncoder<Sender>::tuneRequest()
{
uint8_t cmd = uint8_t(Status::TuneRequest);
_sendMessage(&cmd, 1);
}
template <class Sender>
void MIDIEncoder<Sender>::startSeq()
{
uint8_t cmd = uint8_t(Status::Start);
_sendMessage(&cmd, 1);
}
template <class Sender>
void MIDIEncoder<Sender>::continueSeq()
{
uint8_t cmd = uint8_t(Status::Continue);
_sendMessage(&cmd, 1);
}
template <class Sender>
void MIDIEncoder<Sender>::stopSeq()
{
uint8_t cmd = uint8_t(Status::Stop);
_sendMessage(&cmd, 1);
}
template <class Sender>
void MIDIEncoder<Sender>::reset()
{
uint8_t cmd = uint8_t(Status::Reset);
_sendMessage(&cmd, 1);
}
template class MIDIEncoder<IMIDIOut>;
template class MIDIEncoder<IMIDIInOut>;
}