Add MIDI input for interactive sequencer playback

This commit is contained in:
Jack Andersen 2016-05-19 20:17:02 -10:00
parent 3a8a9f0ef2
commit 2a5823d6de
8 changed files with 232 additions and 3 deletions

View File

@ -3,6 +3,8 @@
#include <boo/audiodev/IAudioVoiceEngine.hpp> #include <boo/audiodev/IAudioVoiceEngine.hpp>
#include <boo/audiodev/IAudioSubmix.hpp> #include <boo/audiodev/IAudioSubmix.hpp>
#include <boo/audiodev/IMIDIReader.hpp>
#include <boo/audiodev/MIDIDecoder.hpp>
#include "IBackendVoice.hpp" #include "IBackendVoice.hpp"
#include "IBackendSubmix.hpp" #include "IBackendSubmix.hpp"
#include "IBackendVoiceAllocator.hpp" #include "IBackendVoiceAllocator.hpp"
@ -61,6 +63,47 @@ public:
SubmixFormat getSampleFormat() const; SubmixFormat getSampleFormat() const;
}; };
/** Backend MIDI event reader for controlling sequencer with external hardware / software */
class BooBackendMIDIReader : public IMIDIReader, public boo::IMIDIReader
{
Engine& m_engine;
std::unique_ptr<boo::IMIDIIn> m_midiIn;
boo::MIDIDecoder m_decoder;
public:
BooBackendMIDIReader(Engine& engine, std::unique_ptr<boo::IMIDIIn>&& in)
: m_engine(engine), m_midiIn(std::move(in)), m_decoder(*m_midiIn, *this) {}
std::string description();
void pumpReader();
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();
};
/** Backend voice allocator implementation for boo mixer */ /** Backend voice allocator implementation for boo mixer */
class BooBackendVoiceAllocator : public IBackendVoiceAllocator class BooBackendVoiceAllocator : public IBackendVoiceAllocator
{ {
@ -69,6 +112,8 @@ public:
BooBackendVoiceAllocator(boo::IAudioVoiceEngine& booEngine); BooBackendVoiceAllocator(boo::IAudioVoiceEngine& booEngine);
std::unique_ptr<IBackendVoice> allocateVoice(Voice& clientVox, double sampleRate, bool dynamicPitch); std::unique_ptr<IBackendVoice> allocateVoice(Voice& clientVox, double sampleRate, bool dynamicPitch);
std::unique_ptr<IBackendSubmix> allocateSubmix(Submix& clientSmx); std::unique_ptr<IBackendSubmix> allocateSubmix(Submix& clientSmx);
std::vector<std::pair<std::string, std::string>> enumerateMIDIDevices();
std::unique_ptr<IMIDIReader> allocateMIDIReader(Engine& engine, const char* name=nullptr);
void register5MsCallback(std::function<void(double)>&& callback); void register5MsCallback(std::function<void(double)>&& callback);
AudioChannelSet getAvailableSet(); AudioChannelSet getAvailableSet();
void pumpAndMixVoices(); void pumpAndMixVoices();

View File

@ -18,6 +18,7 @@ class Submix;
class Emitter; class Emitter;
class AudioGroup; class AudioGroup;
class AudioGroupData; class AudioGroupData;
class IMIDIReader;
/** Main audio playback system for a single audio output */ /** Main audio playback system for a single audio output */
class Engine class Engine
@ -28,6 +29,7 @@ class Engine
friend class Sequencer::ChannelState; friend class Sequencer::ChannelState;
IBackendVoiceAllocator& m_backend; IBackendVoiceAllocator& m_backend;
std::unique_ptr<IMIDIReader> m_midiReader;
std::unordered_map<const AudioGroupData*, std::unique_ptr<AudioGroup>> m_audioGroups; std::unordered_map<const AudioGroupData*, std::unique_ptr<AudioGroup>> m_audioGroups;
std::list<std::shared_ptr<Voice>> m_activeVoices; std::list<std::shared_ptr<Voice>> m_activeVoices;
std::list<std::shared_ptr<Emitter>> m_activeEmitters; std::list<std::shared_ptr<Emitter>> m_activeEmitters;
@ -96,6 +98,9 @@ public:
/** Obtain next random number from engine's PRNG */ /** Obtain next random number from engine's PRNG */
uint32_t nextRandom() {return m_random();} uint32_t nextRandom() {return m_random();}
/** Obtain list of active sequencers */
std::list<std::shared_ptr<Sequencer>>& getActiveSequencers() {return m_activeSequencers;}
}; };
} }

View File

@ -20,10 +20,10 @@ public:
}; };
private: private:
State m_phase = State::Attack; /**< Current envelope state */ State m_phase = State::Attack; /**< Current envelope state */
double m_attackTime = 0.0; /**< Time of attack in seconds */ double m_attackTime = 0.01; /**< Time of attack in seconds */
double m_decayTime = 0.0; /**< Time of decay in seconds */ double m_decayTime = 0.0; /**< Time of decay in seconds */
double m_sustainFactor = 1.0; /**< Evaluated sustain percentage */ double m_sustainFactor = 1.0; /**< Evaluated sustain percentage */
double m_releaseTime = 0.0; /**< Time of release in seconds */ double m_releaseTime = 0.01; /**< Time of release in seconds */
double m_releaseStartFactor = 0.0; /**< Level at whenever release event occurs */ double m_releaseStartFactor = 0.0; /**< Level at whenever release event occurs */
double m_curTime = 0.0; /**< Current time of envelope stage in seconds */ double m_curTime = 0.0; /**< Current time of envelope stage in seconds */
public: public:

View File

@ -3,6 +3,7 @@
#include <memory> #include <memory>
#include <functional> #include <functional>
#include <vector>
namespace amuse namespace amuse
{ {
@ -10,6 +11,7 @@ class IBackendVoice;
class IBackendSubmix; class IBackendSubmix;
class Voice; class Voice;
class Submix; class Submix;
class Engine;
/** Same enum as boo for describing speaker-configuration */ /** Same enum as boo for describing speaker-configuration */
enum class AudioChannelSet enum class AudioChannelSet
@ -21,6 +23,15 @@ enum class AudioChannelSet
Unknown = 0xff Unknown = 0xff
}; };
/** Handle to MIDI-reader, implementation opaque */
class IMIDIReader
{
public:
virtual ~IMIDIReader()=default;
virtual std::string description()=0;
virtual void pumpReader()=0;
};
/** Client-implemented voice allocator */ /** Client-implemented voice allocator */
class IBackendVoiceAllocator class IBackendVoiceAllocator
{ {
@ -35,6 +46,12 @@ public:
/** Amuse obtains a new submix from the platform this way */ /** Amuse obtains a new submix from the platform this way */
virtual std::unique_ptr<IBackendSubmix> allocateSubmix(Submix& clientSmx)=0; virtual std::unique_ptr<IBackendSubmix> allocateSubmix(Submix& clientSmx)=0;
/** Amuse obtains a list of all MIDI devices this way */
virtual std::vector<std::pair<std::string, std::string>> enumerateMIDIDevices()=0;
/** Amuse obtains an interactive MIDI-in connection from the OS this way */
virtual std::unique_ptr<IMIDIReader> allocateMIDIReader(Engine& engine, const char* name=nullptr)=0;
/** Amuse obtains speaker-configuration from the platform this way */ /** Amuse obtains speaker-configuration from the platform this way */
virtual AudioChannelSet getAvailableSet()=0; virtual AudioChannelSet getAvailableSet()=0;

View File

@ -112,6 +112,9 @@ public:
/** Send keyoffs to all active notes, silence immediately if `now` set */ /** Send keyoffs to all active notes, silence immediately if `now` set */
void allOff(bool now=false); void allOff(bool now=false);
/** Send keyoffs to all active notes on specified channel, silence immediately if `now` set */
void allOff(uint8_t chan, bool now=false);
/** Stop all voices in `kg`, stops immediately (no KeyOff) when `now` set */ /** Stop all voices in `kg`, stops immediately (no KeyOff) when `now` set */
void killKeygroup(uint8_t kg, bool now); void killKeygroup(uint8_t kg, bool now);

View File

@ -1,6 +1,7 @@
#include "amuse/BooBackend.hpp" #include "amuse/BooBackend.hpp"
#include "amuse/Voice.hpp" #include "amuse/Voice.hpp"
#include "amuse/Submix.hpp" #include "amuse/Submix.hpp"
#include "amuse/Engine.hpp"
namespace amuse namespace amuse
{ {
@ -100,6 +101,124 @@ SubmixFormat BooBackendSubmix::getSampleFormat() const
return SubmixFormat(m_booSubmix->getSampleFormat()); return SubmixFormat(m_booSubmix->getSampleFormat());
} }
std::string BooBackendMIDIReader::description()
{
return m_midiIn->description();
}
void BooBackendMIDIReader::pumpReader()
{
while (m_decoder.receiveBytes()) {}
}
void BooBackendMIDIReader::noteOff(uint8_t chan, uint8_t key, uint8_t velocity)
{
for (std::shared_ptr<Sequencer>& seq : m_engine.getActiveSequencers())
seq->keyOff(chan, key, velocity);
}
void BooBackendMIDIReader::noteOn(uint8_t chan, uint8_t key, uint8_t velocity)
{
for (std::shared_ptr<Sequencer>& seq : m_engine.getActiveSequencers())
seq->keyOn(chan, key, velocity);
}
void BooBackendMIDIReader::notePressure(uint8_t chan, uint8_t key, uint8_t pressure)
{
}
void BooBackendMIDIReader::controlChange(uint8_t chan, uint8_t control, uint8_t value)
{
for (std::shared_ptr<Sequencer>& seq : m_engine.getActiveSequencers())
seq->setCtrlValue(chan, control, value);
}
void BooBackendMIDIReader::programChange(uint8_t chan, uint8_t program)
{
for (std::shared_ptr<Sequencer>& seq : m_engine.getActiveSequencers())
seq->setChanProgram(chan, program);
}
void BooBackendMIDIReader::channelPressure(uint8_t chan, uint8_t pressure)
{
}
void BooBackendMIDIReader::pitchBend(uint8_t chan, int16_t pitch)
{
for (std::shared_ptr<Sequencer>& seq : m_engine.getActiveSequencers())
seq->setPitchWheel(chan, (pitch - 0x2000) / float(0x2000));
}
void BooBackendMIDIReader::allSoundOff(uint8_t chan)
{
for (std::shared_ptr<Sequencer>& seq : m_engine.getActiveSequencers())
seq->allOff(chan, true);
}
void BooBackendMIDIReader::resetAllControllers(uint8_t chan)
{
}
void BooBackendMIDIReader::localControl(uint8_t chan, bool on)
{
}
void BooBackendMIDIReader::allNotesOff(uint8_t chan)
{
for (std::shared_ptr<Sequencer>& seq : m_engine.getActiveSequencers())
seq->allOff(chan, false);
}
void BooBackendMIDIReader::omniMode(uint8_t chan, bool on)
{
}
void BooBackendMIDIReader::polyMode(uint8_t chan, bool on)
{
}
void BooBackendMIDIReader::sysex(const void* data, size_t len)
{
}
void BooBackendMIDIReader::timeCodeQuarterFrame(uint8_t message, uint8_t value)
{
}
void BooBackendMIDIReader::songPositionPointer(uint16_t pointer)
{
}
void BooBackendMIDIReader::songSelect(uint8_t song)
{
}
void BooBackendMIDIReader::tuneRequest()
{
}
void BooBackendMIDIReader::startSeq()
{
}
void BooBackendMIDIReader::continueSeq()
{
}
void BooBackendMIDIReader::stopSeq()
{
}
void BooBackendMIDIReader::reset()
{
}
BooBackendVoiceAllocator::BooBackendVoiceAllocator(boo::IAudioVoiceEngine& booEngine) BooBackendVoiceAllocator::BooBackendVoiceAllocator(boo::IAudioVoiceEngine& booEngine)
: m_booEngine(booEngine) : m_booEngine(booEngine)
{} {}
@ -115,6 +234,25 @@ std::unique_ptr<IBackendSubmix> BooBackendVoiceAllocator::allocateSubmix(Submix&
return std::make_unique<BooBackendSubmix>(m_booEngine, clientSmx); return std::make_unique<BooBackendSubmix>(m_booEngine, clientSmx);
} }
std::vector<std::pair<std::string, std::string>> BooBackendVoiceAllocator::enumerateMIDIDevices()
{
return m_booEngine.enumerateMIDIDevices();
}
std::unique_ptr<IMIDIReader> BooBackendVoiceAllocator::allocateMIDIReader(Engine& engine, const char* name)
{
std::unique_ptr<boo::IMIDIIn> inPort;
if (!name)
inPort = m_booEngine.newVirtualMIDIIn();
else
inPort = m_booEngine.newRealMIDIIn(name);
if (!inPort)
return {};
return std::make_unique<BooBackendMIDIReader>(engine, std::move(inPort));
}
void BooBackendVoiceAllocator::register5MsCallback(std::function<void(double)>&& callback) void BooBackendVoiceAllocator::register5MsCallback(std::function<void(double)>&& callback)
{ {
m_booEngine.register5MsCallback(std::move(callback)); m_booEngine.register5MsCallback(std::move(callback));

View File

@ -27,6 +27,7 @@ Engine::Engine(IBackendVoiceAllocator& backend)
: m_backend(backend) : m_backend(backend)
{ {
backend.register5MsCallback(std::bind(&Engine::_5MsCallback, this, std::placeholders::_1)); backend.register5MsCallback(std::bind(&Engine::_5MsCallback, this, std::placeholders::_1));
m_midiReader = backend.allocateMIDIReader(*this);
} }
std::pair<AudioGroup*, const SongGroupIndex*> Engine::_findSongGroup(int groupId) const std::pair<AudioGroup*, const SongGroupIndex*> Engine::_findSongGroup(int groupId) const
@ -156,6 +157,8 @@ void Engine::_bringOutYourDead()
void Engine::_5MsCallback(double dt) void Engine::_5MsCallback(double dt)
{ {
if (m_midiReader)
m_midiReader->pumpReader();
for (std::shared_ptr<Sequencer>& seq : m_activeSequencers) for (std::shared_ptr<Sequencer>& seq : m_activeSequencers)
seq->advance(dt); seq->advance(dt);
} }

View File

@ -213,7 +213,7 @@ void Sequencer::ChannelState::setCtrlValue(uint8_t ctrl, int8_t val)
if (ctrl == 7) if (ctrl == 7)
setVolume(val / 127.f); setVolume(val / 127.f);
else if (ctrl == 8) else if (ctrl == 8 || ctrl == 10)
setPan(val / 64.f - 1.f); setPan(val / 64.f - 1.f);
} }
@ -321,6 +321,24 @@ void Sequencer::allOff(bool now)
chan->allOff(); chan->allOff();
} }
void Sequencer::allOff(uint8_t chan, bool now)
{
if (chan > 15 || !m_chanStates[chan])
return;
if (now)
{
for (const auto& vox : m_chanStates[chan]->m_chanVoxs)
m_engine._destroyVoice(vox.second.get());
for (const auto& vox : m_chanStates[chan]->m_keyoffVoxs)
m_engine._destroyVoice(vox.get());
m_chanStates[chan]->m_chanVoxs.clear();
m_chanStates[chan]->m_keyoffVoxs.clear();
}
else
m_chanStates[chan]->allOff();
}
void Sequencer::ChannelState::killKeygroup(uint8_t kg, bool now) void Sequencer::ChannelState::killKeygroup(uint8_t kg, bool now)
{ {
for (auto it = m_chanVoxs.begin() ; it != m_chanVoxs.end() ;) for (auto it = m_chanVoxs.begin() ; it != m_chanVoxs.end() ;)