mirror of https://github.com/AxioDL/amuse.git
Add MIDI input for interactive sequencer playback
This commit is contained in:
parent
3a8a9f0ef2
commit
2a5823d6de
|
@ -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();
|
||||||
|
|
|
@ -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;}
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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:
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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));
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
|
|
|
@ -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() ;)
|
||||||
|
|
Loading…
Reference in New Issue