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/IAudioSubmix.hpp>
|
||||
#include <boo/audiodev/IMIDIReader.hpp>
|
||||
#include <boo/audiodev/MIDIDecoder.hpp>
|
||||
#include "IBackendVoice.hpp"
|
||||
#include "IBackendSubmix.hpp"
|
||||
#include "IBackendVoiceAllocator.hpp"
|
||||
|
@ -61,6 +63,47 @@ public:
|
|||
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 */
|
||||
class BooBackendVoiceAllocator : public IBackendVoiceAllocator
|
||||
{
|
||||
|
@ -69,6 +112,8 @@ public:
|
|||
BooBackendVoiceAllocator(boo::IAudioVoiceEngine& booEngine);
|
||||
std::unique_ptr<IBackendVoice> allocateVoice(Voice& clientVox, double sampleRate, bool dynamicPitch);
|
||||
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);
|
||||
AudioChannelSet getAvailableSet();
|
||||
void pumpAndMixVoices();
|
||||
|
|
|
@ -18,6 +18,7 @@ class Submix;
|
|||
class Emitter;
|
||||
class AudioGroup;
|
||||
class AudioGroupData;
|
||||
class IMIDIReader;
|
||||
|
||||
/** Main audio playback system for a single audio output */
|
||||
class Engine
|
||||
|
@ -28,6 +29,7 @@ class Engine
|
|||
friend class Sequencer::ChannelState;
|
||||
|
||||
IBackendVoiceAllocator& m_backend;
|
||||
std::unique_ptr<IMIDIReader> m_midiReader;
|
||||
std::unordered_map<const AudioGroupData*, std::unique_ptr<AudioGroup>> m_audioGroups;
|
||||
std::list<std::shared_ptr<Voice>> m_activeVoices;
|
||||
std::list<std::shared_ptr<Emitter>> m_activeEmitters;
|
||||
|
@ -96,6 +98,9 @@ public:
|
|||
|
||||
/** Obtain next random number from engine's PRNG */
|
||||
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:
|
||||
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_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_curTime = 0.0; /**< Current time of envelope stage in seconds */
|
||||
public:
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include <memory>
|
||||
#include <functional>
|
||||
#include <vector>
|
||||
|
||||
namespace amuse
|
||||
{
|
||||
|
@ -10,6 +11,7 @@ class IBackendVoice;
|
|||
class IBackendSubmix;
|
||||
class Voice;
|
||||
class Submix;
|
||||
class Engine;
|
||||
|
||||
/** Same enum as boo for describing speaker-configuration */
|
||||
enum class AudioChannelSet
|
||||
|
@ -21,6 +23,15 @@ enum class AudioChannelSet
|
|||
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 */
|
||||
class IBackendVoiceAllocator
|
||||
{
|
||||
|
@ -35,6 +46,12 @@ public:
|
|||
/** Amuse obtains a new submix from the platform this way */
|
||||
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 */
|
||||
virtual AudioChannelSet getAvailableSet()=0;
|
||||
|
||||
|
|
|
@ -112,6 +112,9 @@ public:
|
|||
/** Send keyoffs to all active notes, silence immediately if `now` set */
|
||||
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 */
|
||||
void killKeygroup(uint8_t kg, bool now);
|
||||
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
#include "amuse/BooBackend.hpp"
|
||||
#include "amuse/Voice.hpp"
|
||||
#include "amuse/Submix.hpp"
|
||||
#include "amuse/Engine.hpp"
|
||||
|
||||
namespace amuse
|
||||
{
|
||||
|
@ -100,6 +101,124 @@ SubmixFormat BooBackendSubmix::getSampleFormat() const
|
|||
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)
|
||||
: m_booEngine(booEngine)
|
||||
{}
|
||||
|
@ -115,6 +234,25 @@ std::unique_ptr<IBackendSubmix> BooBackendVoiceAllocator::allocateSubmix(Submix&
|
|||
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)
|
||||
{
|
||||
m_booEngine.register5MsCallback(std::move(callback));
|
||||
|
|
|
@ -27,6 +27,7 @@ Engine::Engine(IBackendVoiceAllocator& backend)
|
|||
: m_backend(backend)
|
||||
{
|
||||
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
|
||||
|
@ -156,6 +157,8 @@ void Engine::_bringOutYourDead()
|
|||
|
||||
void Engine::_5MsCallback(double dt)
|
||||
{
|
||||
if (m_midiReader)
|
||||
m_midiReader->pumpReader();
|
||||
for (std::shared_ptr<Sequencer>& seq : m_activeSequencers)
|
||||
seq->advance(dt);
|
||||
}
|
||||
|
|
|
@ -213,7 +213,7 @@ void Sequencer::ChannelState::setCtrlValue(uint8_t ctrl, int8_t val)
|
|||
|
||||
if (ctrl == 7)
|
||||
setVolume(val / 127.f);
|
||||
else if (ctrl == 8)
|
||||
else if (ctrl == 8 || ctrl == 10)
|
||||
setPan(val / 64.f - 1.f);
|
||||
}
|
||||
|
||||
|
@ -321,6 +321,24 @@ void Sequencer::allOff(bool now)
|
|||
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)
|
||||
{
|
||||
for (auto it = m_chanVoxs.begin() ; it != m_chanVoxs.end() ;)
|
||||
|
|
Loading…
Reference in New Issue