mirror of https://github.com/AxioDL/amuse.git
Work on Sequencer
This commit is contained in:
parent
18f3ce6f44
commit
2c3c9d3885
|
@ -42,9 +42,8 @@ int main(int argc, char* argv[])
|
||||||
/* An 'AudioGroup' is an atomically-loadable unit within Amuse.
|
/* An 'AudioGroup' is an atomically-loadable unit within Amuse.
|
||||||
* A client-assigned integer serves as the handle to the group once loaded
|
* A client-assigned integer serves as the handle to the group once loaded
|
||||||
*/
|
*/
|
||||||
int groupId = 1;
|
amuse::IntrusiveAudioGroupData data = LoadMyAudioGroup();
|
||||||
amuse::IntrusiveAudioGroupData data = LoadMyAudioGroup(groupId);
|
snd.addAudioGroup(data);
|
||||||
snd.addAudioGroup(groupId, data);
|
|
||||||
|
|
||||||
/* Starting a SoundMacro playing is accomplished like so: */
|
/* Starting a SoundMacro playing is accomplished like so: */
|
||||||
int sfxId = 0x1337;
|
int sfxId = 0x1337;
|
||||||
|
|
|
@ -170,7 +170,7 @@ struct AppCallback : boo::IApplicationCallback
|
||||||
|
|
||||||
/* Song playback selection */
|
/* Song playback selection */
|
||||||
int m_setupId = -1;
|
int m_setupId = -1;
|
||||||
int m_chanId = -1;
|
int m_chanId = 0;
|
||||||
int8_t m_octave = 4;
|
int8_t m_octave = 4;
|
||||||
int8_t m_velocity = 64;
|
int8_t m_velocity = 64;
|
||||||
std::shared_ptr<amuse::Sequencer> m_seq;
|
std::shared_ptr<amuse::Sequencer> m_seq;
|
||||||
|
@ -227,10 +227,7 @@ struct AppCallback : boo::IApplicationCallback
|
||||||
(index.m_midiSetups.cbegin(), index.m_midiSetups.cend());
|
(index.m_midiSetups.cbegin(), index.m_midiSetups.cend());
|
||||||
auto setupIt = sortEntries.cbegin();
|
auto setupIt = sortEntries.cbegin();
|
||||||
if (setupIt != sortEntries.cend())
|
if (setupIt != sortEntries.cend())
|
||||||
{
|
SelectSong(setupIt->first);
|
||||||
m_setupId = setupIt->first;
|
|
||||||
m_chanId = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
while (m_running)
|
while (m_running)
|
||||||
{
|
{
|
||||||
|
@ -268,7 +265,12 @@ struct AppCallback : boo::IApplicationCallback
|
||||||
|
|
||||||
m_engine->pumpEngine();
|
m_engine->pumpEngine();
|
||||||
|
|
||||||
size_t voxCount = m_seq->getVoiceCount();
|
size_t voxCount;
|
||||||
|
if (m_seq)
|
||||||
|
voxCount = m_seq->getVoiceCount();
|
||||||
|
else
|
||||||
|
voxCount = 0;
|
||||||
|
|
||||||
if (m_lastVoxCount != voxCount)
|
if (m_lastVoxCount != voxCount)
|
||||||
{
|
{
|
||||||
m_lastVoxCount = voxCount;
|
m_lastVoxCount = voxCount;
|
||||||
|
@ -610,7 +612,7 @@ struct AppCallback : boo::IApplicationCallback
|
||||||
m_engine.emplace(booBackend);
|
m_engine.emplace(booBackend);
|
||||||
|
|
||||||
/* Load group into engine */
|
/* Load group into engine */
|
||||||
const amuse::AudioGroup* group = m_engine->addAudioGroup(m_groupId, data);
|
const amuse::AudioGroup* group = m_engine->addAudioGroup(data);
|
||||||
if (!group)
|
if (!group)
|
||||||
Log.report(logvisor::Fatal, "unable to add audio group");
|
Log.report(logvisor::Fatal, "unable to add audio group");
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,6 @@ using Sample = std::pair<AudioGroupSampleDirectory::Entry,
|
||||||
|
|
||||||
class AudioGroup
|
class AudioGroup
|
||||||
{
|
{
|
||||||
int m_groupId;
|
|
||||||
AudioGroupProject m_proj;
|
AudioGroupProject m_proj;
|
||||||
AudioGroupPool m_pool;
|
AudioGroupPool m_pool;
|
||||||
AudioGroupSampleDirectory m_sdir;
|
AudioGroupSampleDirectory m_sdir;
|
||||||
|
@ -22,9 +21,7 @@ class AudioGroup
|
||||||
bool m_valid;
|
bool m_valid;
|
||||||
public:
|
public:
|
||||||
operator bool() const {return m_valid;}
|
operator bool() const {return m_valid;}
|
||||||
AudioGroup(int groupId, const AudioGroupData& data);
|
AudioGroup(const AudioGroupData& data);
|
||||||
|
|
||||||
int groupId() const {return m_groupId;}
|
|
||||||
|
|
||||||
const Sample* getSample(int sfxId) const;
|
const Sample* getSample(int sfxId) const;
|
||||||
const unsigned char* getSampleData(uint32_t offset) const;
|
const unsigned char* getSampleData(uint32_t offset) const;
|
||||||
|
|
|
@ -27,18 +27,25 @@ class Engine
|
||||||
friend class Sequencer::ChannelState;
|
friend class Sequencer::ChannelState;
|
||||||
|
|
||||||
IBackendVoiceAllocator& m_backend;
|
IBackendVoiceAllocator& m_backend;
|
||||||
std::unordered_map<int, 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;
|
||||||
std::list<std::shared_ptr<Sequencer>> m_activeSequencers;
|
std::list<std::shared_ptr<Sequencer>> m_activeSequencers;
|
||||||
std::list<Submix> m_activeSubmixes;
|
std::list<Submix> m_activeSubmixes;
|
||||||
std::unordered_map<uint16_t, std::pair<AudioGroup*, ObjectId>> m_sfxLookup;
|
std::unordered_map<uint16_t, std::tuple<AudioGroup*, int, ObjectId>> m_sfxLookup;
|
||||||
std::linear_congruential_engine<uint32_t, 0x41c64e6d, 0x3039, UINT32_MAX> m_random;
|
std::linear_congruential_engine<uint32_t, 0x41c64e6d, 0x3039, UINT32_MAX> m_random;
|
||||||
int m_nextVid = 0;
|
int m_nextVid = 0;
|
||||||
std::shared_ptr<Voice> _allocateVoice(const AudioGroup& group, double sampleRate,
|
|
||||||
|
std::pair<AudioGroup*, const SongGroupIndex*> _findSongGroup(int groupId) const;
|
||||||
|
std::pair<AudioGroup*, const SFXGroupIndex*> _findSFXGroup(int groupId) const;
|
||||||
|
|
||||||
|
std::shared_ptr<Voice> _allocateVoice(const AudioGroup& group, int groupId, double sampleRate,
|
||||||
bool dynamicPitch, bool emitter, Submix* smx);
|
bool dynamicPitch, bool emitter, Submix* smx);
|
||||||
|
std::shared_ptr<Sequencer> _allocateSequencer(const AudioGroup& group, int groupId,
|
||||||
|
int setupId, Submix* smx);
|
||||||
Submix* _allocateSubmix(Submix* smx);
|
Submix* _allocateSubmix(Submix* smx);
|
||||||
std::list<std::shared_ptr<Voice>>::iterator _destroyVoice(Voice* voice);
|
std::list<std::shared_ptr<Voice>>::iterator _destroyVoice(Voice* voice);
|
||||||
|
std::list<std::shared_ptr<Sequencer>>::iterator _destroySequencer(Sequencer* sequencer);
|
||||||
std::list<Submix>::iterator _destroySubmix(Submix* smx);
|
std::list<Submix>::iterator _destroySubmix(Submix* smx);
|
||||||
void _bringOutYourDead();
|
void _bringOutYourDead();
|
||||||
public:
|
public:
|
||||||
|
@ -52,10 +59,10 @@ public:
|
||||||
void pumpEngine();
|
void pumpEngine();
|
||||||
|
|
||||||
/** Add audio group data pointers to engine; must remain resident! */
|
/** Add audio group data pointers to engine; must remain resident! */
|
||||||
const AudioGroup* addAudioGroup(int groupId, const AudioGroupData& data);
|
const AudioGroup* addAudioGroup(const AudioGroupData& data);
|
||||||
|
|
||||||
/** Remove audio group from engine */
|
/** Remove audio group from engine */
|
||||||
void removeAudioGroup(int groupId);
|
void removeAudioGroup(const AudioGroupData& data);
|
||||||
|
|
||||||
/** Create new Submix (a.k.a 'Studio') within root mix engine */
|
/** Create new Submix (a.k.a 'Studio') within root mix engine */
|
||||||
Submix* addSubmix(Submix* parent=nullptr);
|
Submix* addSubmix(Submix* parent=nullptr);
|
||||||
|
@ -72,7 +79,8 @@ public:
|
||||||
Submix* smx=nullptr);
|
Submix* smx=nullptr);
|
||||||
|
|
||||||
/** Start song playing from loaded audio groups */
|
/** Start song playing from loaded audio groups */
|
||||||
std::shared_ptr<Sequencer> seqPlay(int groupId, int songId, const unsigned char* arrData);
|
std::shared_ptr<Sequencer> seqPlay(int groupId, int songId, const unsigned char* arrData,
|
||||||
|
Submix* smx=nullptr);
|
||||||
|
|
||||||
/** Find voice from VoiceId */
|
/** Find voice from VoiceId */
|
||||||
std::shared_ptr<Voice> findVoice(int vid);
|
std::shared_ptr<Voice> findVoice(int vid);
|
||||||
|
|
|
@ -41,10 +41,11 @@ protected:
|
||||||
}
|
}
|
||||||
Engine& m_engine;
|
Engine& m_engine;
|
||||||
const AudioGroup& m_audioGroup;
|
const AudioGroup& m_audioGroup;
|
||||||
|
int m_groupId;
|
||||||
ObjectId m_objectId = 0xffff; /* if applicable */
|
ObjectId m_objectId = 0xffff; /* if applicable */
|
||||||
public:
|
public:
|
||||||
Entity(Engine& engine, const AudioGroup& group, ObjectId oid=ObjectId())
|
Entity(Engine& engine, const AudioGroup& group, int groupId, ObjectId oid=ObjectId())
|
||||||
: m_engine(engine), m_audioGroup(group), m_objectId(oid) {}
|
: m_engine(engine), m_audioGroup(group), m_groupId(groupId), m_objectId(oid) {}
|
||||||
~Entity()
|
~Entity()
|
||||||
{
|
{
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
|
@ -55,6 +56,7 @@ public:
|
||||||
|
|
||||||
Engine& getEngine() {return m_engine;}
|
Engine& getEngine() {return m_engine;}
|
||||||
const AudioGroup& getAudioGroup() const {return m_audioGroup;}
|
const AudioGroup& getAudioGroup() const {return m_audioGroup;}
|
||||||
|
int getGroupId() const {return m_groupId;}
|
||||||
ObjectId getObjectId() const {return m_objectId;}
|
ObjectId getObjectId() const {return m_objectId;}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -15,7 +15,8 @@ public:
|
||||||
Attack,
|
Attack,
|
||||||
Decay,
|
Decay,
|
||||||
Sustain,
|
Sustain,
|
||||||
Release
|
Release,
|
||||||
|
Complete
|
||||||
};
|
};
|
||||||
private:
|
private:
|
||||||
State m_phase = State::Attack; /**< Current envelope state */
|
State m_phase = State::Attack; /**< Current envelope state */
|
||||||
|
@ -27,6 +28,7 @@ public:
|
||||||
void reset(const ADSR* adsr);
|
void reset(const ADSR* adsr);
|
||||||
void keyOff();
|
void keyOff();
|
||||||
float nextSample(double sampleRate);
|
float nextSample(double sampleRate);
|
||||||
|
bool isComplete() const {return m_phase == State::Complete;}
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,18 +5,33 @@
|
||||||
#include "AudioGroupProject.hpp"
|
#include "AudioGroupProject.hpp"
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <list>
|
||||||
|
|
||||||
namespace amuse
|
namespace amuse
|
||||||
{
|
{
|
||||||
class Submix;
|
class Submix;
|
||||||
class Voice;
|
class Voice;
|
||||||
|
|
||||||
|
/** State of sequencer over lifetime */
|
||||||
|
enum class SequencerState
|
||||||
|
{
|
||||||
|
Playing, /**< Sequencer actively playing arrangement */
|
||||||
|
Interactive, /**< Interactive sequencer for live MIDI message processing, will not automatically die */
|
||||||
|
Dead /**< Set when arrangement complete and `dieOnEnd` was set, or manually with die() */
|
||||||
|
};
|
||||||
|
|
||||||
class Sequencer : public Entity
|
class Sequencer : public Entity
|
||||||
{
|
{
|
||||||
friend class Engine;
|
friend class Engine;
|
||||||
const SongGroupIndex& m_songGroup; /**< Quick access to song group project index */
|
const SongGroupIndex& m_songGroup; /**< Quick access to song group project index */
|
||||||
const SongGroupIndex::MIDISetup* m_midiSetup = nullptr; /**< Selected MIDI setup */
|
const SongGroupIndex::MIDISetup* m_midiSetup = nullptr; /**< Selected MIDI setup */
|
||||||
Submix* m_submix = nullptr; /**< Submix this sequencer outputs to (or NULL for the main output mix) */
|
Submix* m_submix = nullptr; /**< Submix this sequencer outputs to (or NULL for the main output mix) */
|
||||||
|
std::list<std::shared_ptr<Sequencer>>::iterator m_engineIt; /**< Iterator to self within Engine's list for quick deletion */
|
||||||
|
|
||||||
|
const unsigned char* m_arrData = nullptr; /**< Current playing arrangement data */
|
||||||
|
double m_ticksPerSec = 1000.0; /**< Current ticks per second (tempo) for arrangement data */
|
||||||
|
SequencerState m_state = SequencerState::Interactive; /**< Current high-level state of sequencer */
|
||||||
|
bool m_dieOnEnd = false; /**< Sequencer will be killed when current arrangement completes */
|
||||||
|
|
||||||
/** State of a single MIDI channel */
|
/** State of a single MIDI channel */
|
||||||
struct ChannelState
|
struct ChannelState
|
||||||
|
@ -32,20 +47,31 @@ class Sequencer : public Entity
|
||||||
std::unordered_map<uint8_t, std::shared_ptr<Voice>> m_chanVoxs;
|
std::unordered_map<uint8_t, std::shared_ptr<Voice>> m_chanVoxs;
|
||||||
int8_t m_ctrlVals[128]; /**< MIDI controller values */
|
int8_t m_ctrlVals[128]; /**< MIDI controller values */
|
||||||
|
|
||||||
|
void _bringOutYourDead();
|
||||||
std::shared_ptr<Voice> keyOn(uint8_t note, uint8_t velocity);
|
std::shared_ptr<Voice> keyOn(uint8_t note, uint8_t velocity);
|
||||||
void keyOff(uint8_t note, uint8_t velocity);
|
void keyOff(uint8_t note, uint8_t velocity);
|
||||||
void setCtrlValue(uint8_t ctrl, int8_t val);
|
void setCtrlValue(uint8_t ctrl, int8_t val);
|
||||||
void setPitchWheel(float pitchWheel);
|
void setPitchWheel(float pitchWheel);
|
||||||
void allOff();
|
void allOff();
|
||||||
|
void killKeygroup(uint8_t kg, bool now);
|
||||||
|
std::shared_ptr<Voice> findVoice(int vid);
|
||||||
|
void sendMacroMessage(ObjectId macroId, int32_t val);
|
||||||
};
|
};
|
||||||
std::unordered_map<uint8_t, ChannelState> m_chanStates; /**< Lazily-allocated channel states */
|
std::unordered_map<uint8_t, std::unique_ptr<ChannelState>> m_chanStates; /**< Lazily-allocated channel states */
|
||||||
|
|
||||||
|
void _bringOutYourDead();
|
||||||
void _destroy();
|
void _destroy();
|
||||||
public:
|
public:
|
||||||
~Sequencer();
|
~Sequencer();
|
||||||
Sequencer(Engine& engine, const AudioGroup& group,
|
Sequencer(Engine& engine, const AudioGroup& group, int groupId,
|
||||||
const SongGroupIndex& songGroup, int setupId, Submix* smx);
|
const SongGroupIndex& songGroup, int setupId, Submix* smx);
|
||||||
|
|
||||||
|
/** Obtain pointer to Sequencer's Submix */
|
||||||
|
Submix* getSubmix() {return m_submix;}
|
||||||
|
|
||||||
|
/** Get current state of sequencer */
|
||||||
|
SequencerState state() const {return m_state;}
|
||||||
|
|
||||||
/** Get number of active voices */
|
/** Get number of active voices */
|
||||||
size_t getVoiceCount() const;
|
size_t getVoiceCount() const;
|
||||||
|
|
||||||
|
@ -63,6 +89,24 @@ 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);
|
||||||
|
|
||||||
|
/** Stop all voices in `kg`, stops immediately (no KeyOff) when `now` set */
|
||||||
|
void killKeygroup(uint8_t kg, bool now);
|
||||||
|
|
||||||
|
/** Find voice instance contained within Sequencer */
|
||||||
|
std::shared_ptr<Voice> findVoice(int vid);
|
||||||
|
|
||||||
|
/** Send all voices using `macroId` the message `val` */
|
||||||
|
void sendMacroMessage(ObjectId macroId, int32_t val);
|
||||||
|
|
||||||
|
/** Set tempo of sequencer and all voices in ticks per second */
|
||||||
|
void setTempo(double ticksPerSec);
|
||||||
|
|
||||||
|
/** Play MIDI arrangement */
|
||||||
|
void playSong(const unsigned char* arrData, bool dieOnEnd=true);
|
||||||
|
|
||||||
|
/** Manually kill sequencer for deferred release from engine */
|
||||||
|
void kill() {m_state = SequencerState::Dead;}
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -111,7 +111,8 @@ class Voice : public Entity
|
||||||
float m_tremoloModScale; /**< minimum volume factor produced via LFO, scaled via mod wheel */
|
float m_tremoloModScale; /**< minimum volume factor produced via LFO, scaled via mod wheel */
|
||||||
|
|
||||||
float m_lfoPeriods[2]; /**< time-periods for LFO1 and LFO2 */
|
float m_lfoPeriods[2]; /**< time-periods for LFO1 and LFO2 */
|
||||||
int8_t* m_ctrlVals = nullptr; /**< MIDI Controller values (external storage) */
|
std::unique_ptr<int8_t[]> m_ctrlValsSelf; /**< Self-owned MIDI Controller values */
|
||||||
|
int8_t* m_extCtrlVals = nullptr; /**< MIDI Controller values (external storage) */
|
||||||
|
|
||||||
void _destroy();
|
void _destroy();
|
||||||
void _reset();
|
void _reset();
|
||||||
|
@ -122,6 +123,7 @@ class Voice : public Entity
|
||||||
bool _isRecursivelyDead();
|
bool _isRecursivelyDead();
|
||||||
void _bringOutYourDead();
|
void _bringOutYourDead();
|
||||||
std::shared_ptr<Voice> _findVoice(int vid, std::weak_ptr<Voice> thisPtr);
|
std::shared_ptr<Voice> _findVoice(int vid, std::weak_ptr<Voice> thisPtr);
|
||||||
|
std::unique_ptr<int8_t[]>& _ensureCtrlVals();
|
||||||
|
|
||||||
std::shared_ptr<Voice> _allocateVoice(double sampleRate, bool dynamicPitch);
|
std::shared_ptr<Voice> _allocateVoice(double sampleRate, bool dynamicPitch);
|
||||||
std::list<std::shared_ptr<Voice>>::iterator _destroyVoice(Voice* voice);
|
std::list<std::shared_ptr<Voice>>::iterator _destroyVoice(Voice* voice);
|
||||||
|
@ -136,8 +138,8 @@ class Voice : public Entity
|
||||||
uint8_t midiKey, uint8_t midiVel, uint8_t midiMod, bool pushPc=false);
|
uint8_t midiKey, uint8_t midiVel, uint8_t midiMod, bool pushPc=false);
|
||||||
public:
|
public:
|
||||||
~Voice();
|
~Voice();
|
||||||
Voice(Engine& engine, const AudioGroup& group, int vid, bool emitter, Submix* smx);
|
Voice(Engine& engine, const AudioGroup& group, int groupId, int vid, bool emitter, Submix* smx);
|
||||||
Voice(Engine& engine, const AudioGroup& group, ObjectId oid, int vid, bool emitter, Submix* smx);
|
Voice(Engine& engine, const AudioGroup& group, int groupId, ObjectId oid, int vid, bool emitter, Submix* smx);
|
||||||
|
|
||||||
/** Request specified count of audio frames (samples) from voice,
|
/** Request specified count of audio frames (samples) from voice,
|
||||||
* internally advancing the voice stream */
|
* internally advancing the voice stream */
|
||||||
|
@ -250,28 +252,50 @@ public:
|
||||||
/** Assign voice to keygroup for coordinated mass-silencing */
|
/** Assign voice to keygroup for coordinated mass-silencing */
|
||||||
void setKeygroup(uint8_t kg) {m_keygroup = kg;}
|
void setKeygroup(uint8_t kg) {m_keygroup = kg;}
|
||||||
|
|
||||||
|
/** Get note played on voice */
|
||||||
uint8_t getLastNote() const {return m_state.m_initKey;}
|
uint8_t getLastNote() const {return m_state.m_initKey;}
|
||||||
|
|
||||||
|
/** Get MIDI Controller value on voice */
|
||||||
int8_t getCtrlValue(uint8_t ctrl) const
|
int8_t getCtrlValue(uint8_t ctrl) const
|
||||||
{
|
{
|
||||||
if (!m_ctrlVals)
|
if (!m_extCtrlVals)
|
||||||
|
{
|
||||||
|
if (m_ctrlValsSelf)
|
||||||
|
m_ctrlValsSelf[ctrl];
|
||||||
return 0;
|
return 0;
|
||||||
return m_ctrlVals[ctrl];
|
}
|
||||||
|
return m_extCtrlVals[ctrl];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Set MIDI Controller value on voice */
|
||||||
void setCtrlValue(uint8_t ctrl, int8_t val)
|
void setCtrlValue(uint8_t ctrl, int8_t val)
|
||||||
{
|
{
|
||||||
if (!m_ctrlVals)
|
if (!m_extCtrlVals)
|
||||||
return;
|
{
|
||||||
m_ctrlVals[ctrl] = val;
|
std::unique_ptr<int8_t[]>& vals = _ensureCtrlVals();
|
||||||
|
vals[ctrl] = val;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
m_extCtrlVals[ctrl] = val;
|
||||||
}
|
}
|
||||||
int8_t getModWheel() const
|
|
||||||
|
/** Get ModWheel value on voice */
|
||||||
|
int8_t getModWheel() const {return getCtrlValue(1);}
|
||||||
|
|
||||||
|
/** 'install' external MIDI controller storage */
|
||||||
|
void installCtrlValues(int8_t* cvs)
|
||||||
{
|
{
|
||||||
if (!m_ctrlVals)
|
m_ctrlValsSelf.reset();
|
||||||
return 0;
|
m_extCtrlVals = cvs;
|
||||||
return m_ctrlVals[1];
|
|
||||||
}
|
}
|
||||||
void installCtrlValues(int8_t* cvs) {m_ctrlVals = cvs;}
|
|
||||||
|
/** Get MIDI pitch wheel value on voice */
|
||||||
int8_t getPitchWheel() const {return m_curPitchWheel * 127;}
|
int8_t getPitchWheel() const {return m_curPitchWheel * 127;}
|
||||||
|
|
||||||
|
/** Get MIDI aftertouch value on voice */
|
||||||
int8_t getAftertouch() const {return m_curAftertouch;}
|
int8_t getAftertouch() const {return m_curAftertouch;}
|
||||||
|
|
||||||
|
/** Get count of all voices in hierarchy, including this one */
|
||||||
size_t getTotalVoices() const;
|
size_t getTotalVoices() const;
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -4,9 +4,8 @@
|
||||||
namespace amuse
|
namespace amuse
|
||||||
{
|
{
|
||||||
|
|
||||||
AudioGroup::AudioGroup(int groupId, const AudioGroupData& data)
|
AudioGroup::AudioGroup(const AudioGroupData& data)
|
||||||
: m_groupId(groupId),
|
: m_proj(data.getProj()),
|
||||||
m_proj(data.getProj()),
|
|
||||||
m_pool(data.getPool()),
|
m_pool(data.getPool()),
|
||||||
m_sdir(data.getSdir()),
|
m_sdir(data.getSdir()),
|
||||||
m_samp(data.getSamp())
|
m_samp(data.getSamp())
|
||||||
|
|
146
lib/Engine.cpp
146
lib/Engine.cpp
|
@ -17,16 +17,51 @@ Engine::Engine(IBackendVoiceAllocator& backend)
|
||||||
: m_backend(backend)
|
: m_backend(backend)
|
||||||
{}
|
{}
|
||||||
|
|
||||||
std::shared_ptr<Voice> Engine::_allocateVoice(const AudioGroup& group, double sampleRate,
|
std::pair<AudioGroup*, const SongGroupIndex*> Engine::_findSongGroup(int groupId) const
|
||||||
|
{
|
||||||
|
for (const auto& pair : m_audioGroups)
|
||||||
|
{
|
||||||
|
const SongGroupIndex* ret = pair.second->getProj().getSongGroupIndex(groupId);
|
||||||
|
if (ret)
|
||||||
|
return {pair.second.get(), ret};
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::pair<AudioGroup*, const SFXGroupIndex*> Engine::_findSFXGroup(int groupId) const
|
||||||
|
{
|
||||||
|
for (const auto& pair : m_audioGroups)
|
||||||
|
{
|
||||||
|
const SFXGroupIndex* ret = pair.second->getProj().getSFXGroupIndex(groupId);
|
||||||
|
if (ret)
|
||||||
|
return {pair.second.get(), ret};
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<Voice> Engine::_allocateVoice(const AudioGroup& group, int groupId, double sampleRate,
|
||||||
bool dynamicPitch, bool emitter, Submix* smx)
|
bool dynamicPitch, bool emitter, Submix* smx)
|
||||||
{
|
{
|
||||||
auto it = m_activeVoices.emplace(m_activeVoices.end(), new Voice(*this, group, m_nextVid++, emitter, smx));
|
auto it = m_activeVoices.emplace(m_activeVoices.end(),
|
||||||
|
new Voice(*this, group, groupId, m_nextVid++, emitter, smx));
|
||||||
m_activeVoices.back()->m_backendVoice =
|
m_activeVoices.back()->m_backendVoice =
|
||||||
m_backend.allocateVoice(*m_activeVoices.back(), sampleRate, dynamicPitch);
|
m_backend.allocateVoice(*m_activeVoices.back(), sampleRate, dynamicPitch);
|
||||||
m_activeVoices.back()->m_engineIt = it;
|
m_activeVoices.back()->m_engineIt = it;
|
||||||
return m_activeVoices.back();
|
return m_activeVoices.back();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<Sequencer> Engine::_allocateSequencer(const AudioGroup& group, int groupId,
|
||||||
|
int setupId, Submix* smx)
|
||||||
|
{
|
||||||
|
const SongGroupIndex* songGroup = group.getProj().getSongGroupIndex(groupId);
|
||||||
|
if (!songGroup)
|
||||||
|
return {};
|
||||||
|
auto it = m_activeSequencers.emplace(m_activeSequencers.end(),
|
||||||
|
new Sequencer(*this, group, groupId, *songGroup, setupId, smx));
|
||||||
|
m_activeSequencers.back()->m_engineIt = it;
|
||||||
|
return m_activeSequencers.back();
|
||||||
|
}
|
||||||
|
|
||||||
Submix* Engine::_allocateSubmix(Submix* smx)
|
Submix* Engine::_allocateSubmix(Submix* smx)
|
||||||
{
|
{
|
||||||
auto it = m_activeSubmixes.emplace(m_activeSubmixes.end(), *this, smx);
|
auto it = m_activeSubmixes.emplace(m_activeSubmixes.end(), *this, smx);
|
||||||
|
@ -44,6 +79,15 @@ std::list<std::shared_ptr<Voice>>::iterator Engine::_destroyVoice(Voice* voice)
|
||||||
return m_activeVoices.erase(voice->m_engineIt);
|
return m_activeVoices.erase(voice->m_engineIt);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::list<std::shared_ptr<Sequencer>>::iterator Engine::_destroySequencer(Sequencer* sequencer)
|
||||||
|
{
|
||||||
|
#ifndef NDEBUG
|
||||||
|
assert(this == &sequencer->getEngine());
|
||||||
|
#endif
|
||||||
|
sequencer->_destroy();
|
||||||
|
return m_activeSequencers.erase(sequencer->m_engineIt);
|
||||||
|
}
|
||||||
|
|
||||||
std::list<Submix>::iterator Engine::_destroySubmix(Submix* smx)
|
std::list<Submix>::iterator Engine::_destroySubmix(Submix* smx)
|
||||||
{
|
{
|
||||||
#ifndef NDEBUG
|
#ifndef NDEBUG
|
||||||
|
@ -78,6 +122,18 @@ void Engine::_bringOutYourDead()
|
||||||
}
|
}
|
||||||
++it;
|
++it;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (auto it = m_activeSequencers.begin() ; it != m_activeSequencers.end() ;)
|
||||||
|
{
|
||||||
|
Sequencer* seq = it->get();
|
||||||
|
seq->_bringOutYourDead();
|
||||||
|
if (seq->m_state == SequencerState::Dead)
|
||||||
|
{
|
||||||
|
it = _destroySequencer(seq);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
++it;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Update all active audio entities and fill OS audio buffers as needed */
|
/** Update all active audio entities and fill OS audio buffers as needed */
|
||||||
|
@ -94,31 +150,33 @@ void Engine::pumpEngine()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Add audio group data pointers to engine; must remain resident! */
|
/** Add audio group data pointers to engine; must remain resident! */
|
||||||
const AudioGroup* Engine::addAudioGroup(int groupId, const AudioGroupData& data)
|
const AudioGroup* Engine::addAudioGroup(const AudioGroupData& data)
|
||||||
{
|
{
|
||||||
std::unique_ptr<AudioGroup> grp = std::make_unique<AudioGroup>(groupId, data);
|
removeAudioGroup(data);
|
||||||
|
|
||||||
|
std::unique_ptr<AudioGroup> grp = std::make_unique<AudioGroup>(data);
|
||||||
if (!grp)
|
if (!grp)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
AudioGroup* ret = grp.get();
|
AudioGroup* ret = grp.get();
|
||||||
m_audioGroups.emplace(std::make_pair(groupId, std::move(grp)));
|
m_audioGroups.emplace(std::make_pair(&data, std::move(grp)));
|
||||||
|
|
||||||
/* setup SFX index for contained objects */
|
/* setup SFX index for contained objects */
|
||||||
for (const auto& pair : ret->getProj().sfxGroups())
|
for (const auto& grp : ret->getProj().sfxGroups())
|
||||||
{
|
{
|
||||||
const SFXGroupIndex& sfxGroup = pair.second;
|
const SFXGroupIndex& sfxGroup = grp.second;
|
||||||
m_sfxLookup.reserve(m_sfxLookup.size() + sfxGroup.m_sfxEntries.size());
|
m_sfxLookup.reserve(m_sfxLookup.size() + sfxGroup.m_sfxEntries.size());
|
||||||
for (const auto& pair : sfxGroup.m_sfxEntries)
|
for (const auto& ent : sfxGroup.m_sfxEntries)
|
||||||
m_sfxLookup[pair.first] = std::make_pair(ret, SBig(pair.second->objId));
|
m_sfxLookup[ent.first] = std::make_tuple(ret, grp.first, SBig(ent.second->objId));
|
||||||
}
|
}
|
||||||
|
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Remove audio group from engine */
|
/** Remove audio group from engine */
|
||||||
void Engine::removeAudioGroup(int groupId)
|
void Engine::removeAudioGroup(const AudioGroupData& data)
|
||||||
{
|
{
|
||||||
auto search = m_audioGroups.find(groupId);
|
auto search = m_audioGroups.find(&data);
|
||||||
if (search == m_audioGroups.end())
|
if (search == m_audioGroups.cend())
|
||||||
return;
|
return;
|
||||||
AudioGroup* grp = search->second.get();
|
AudioGroup* grp = search->second.get();
|
||||||
|
|
||||||
|
@ -126,7 +184,7 @@ void Engine::removeAudioGroup(int groupId)
|
||||||
for (auto it = m_activeVoices.begin() ; it != m_activeVoices.end() ;)
|
for (auto it = m_activeVoices.begin() ; it != m_activeVoices.end() ;)
|
||||||
{
|
{
|
||||||
Voice* vox = it->get();
|
Voice* vox = it->get();
|
||||||
if (vox->getAudioGroup().groupId() == groupId)
|
if (&vox->getAudioGroup() == grp)
|
||||||
{
|
{
|
||||||
vox->_destroy();
|
vox->_destroy();
|
||||||
it = m_activeVoices.erase(it);
|
it = m_activeVoices.erase(it);
|
||||||
|
@ -138,7 +196,7 @@ void Engine::removeAudioGroup(int groupId)
|
||||||
for (auto it = m_activeEmitters.begin() ; it != m_activeEmitters.end() ;)
|
for (auto it = m_activeEmitters.begin() ; it != m_activeEmitters.end() ;)
|
||||||
{
|
{
|
||||||
Emitter* emitter = it->get();
|
Emitter* emitter = it->get();
|
||||||
if (emitter->getAudioGroup().groupId() == groupId)
|
if (&emitter->getAudioGroup() == grp)
|
||||||
{
|
{
|
||||||
emitter->_destroy();
|
emitter->_destroy();
|
||||||
it = m_activeEmitters.erase(it);
|
it = m_activeEmitters.erase(it);
|
||||||
|
@ -150,7 +208,7 @@ void Engine::removeAudioGroup(int groupId)
|
||||||
for (auto it = m_activeSequencers.begin() ; it != m_activeSequencers.end() ;)
|
for (auto it = m_activeSequencers.begin() ; it != m_activeSequencers.end() ;)
|
||||||
{
|
{
|
||||||
Sequencer* seq = it->get();
|
Sequencer* seq = it->get();
|
||||||
if (seq->getAudioGroup().groupId() == groupId)
|
if (&seq->getAudioGroup() == grp)
|
||||||
{
|
{
|
||||||
seq->_destroy();
|
seq->_destroy();
|
||||||
it = m_activeSequencers.erase(it);
|
it = m_activeSequencers.erase(it);
|
||||||
|
@ -167,7 +225,7 @@ void Engine::removeAudioGroup(int groupId)
|
||||||
m_sfxLookup.erase(pair.first);
|
m_sfxLookup.erase(pair.first);
|
||||||
}
|
}
|
||||||
|
|
||||||
m_audioGroups.erase(groupId);
|
m_audioGroups.erase(search);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Create new Submix (a.k.a 'Studio') within root mix engine */
|
/** Create new Submix (a.k.a 'Studio') within root mix engine */
|
||||||
|
@ -194,6 +252,21 @@ void Engine::removeSubmix(Submix* smx)
|
||||||
++it;
|
++it;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Delete all sequencers bound to submix */
|
||||||
|
for (auto it = m_activeSequencers.begin() ; it != m_activeSequencers.end() ;)
|
||||||
|
{
|
||||||
|
Sequencer* seq = it->get();
|
||||||
|
|
||||||
|
Submix* ssmx = seq->getSubmix();
|
||||||
|
if (ssmx && ssmx == smx)
|
||||||
|
{
|
||||||
|
seq->_destroy();
|
||||||
|
it = m_activeSequencers.erase(it);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
|
||||||
/* Delete all submixes bound to submix */
|
/* Delete all submixes bound to submix */
|
||||||
for (auto it = m_activeSubmixes.begin() ; it != m_activeSubmixes.end() ;)
|
for (auto it = m_activeSubmixes.begin() ; it != m_activeSubmixes.end() ;)
|
||||||
{
|
{
|
||||||
|
@ -218,12 +291,12 @@ std::shared_ptr<Voice> Engine::fxStart(int sfxId, float vol, float pan, Submix*
|
||||||
if (search == m_sfxLookup.end())
|
if (search == m_sfxLookup.end())
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
AudioGroup* grp = search->second.first;
|
AudioGroup* grp = std::get<0>(search->second);
|
||||||
if (!grp)
|
if (!grp)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
std::shared_ptr<Voice> ret = _allocateVoice(*grp, 32000.0, true, false, smx);
|
std::shared_ptr<Voice> ret = _allocateVoice(*grp, std::get<1>(search->second), 32000.0, true, false, smx);
|
||||||
if (!ret->loadSoundObject(search->second.second, 0, 1000.f, 0x3c, 0, 0))
|
if (!ret->loadSoundObject(std::get<2>(search->second), 0, 1000.f, 0x3c, 0, 0))
|
||||||
{
|
{
|
||||||
_destroyVoice(ret.get());
|
_destroyVoice(ret.get());
|
||||||
return {};
|
return {};
|
||||||
|
@ -241,14 +314,14 @@ std::shared_ptr<Emitter> Engine::addEmitter(const Vector3f& pos, const Vector3f&
|
||||||
if (search == m_sfxLookup.end())
|
if (search == m_sfxLookup.end())
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
AudioGroup* grp = search->second.first;
|
AudioGroup* grp = std::get<0>(search->second);
|
||||||
if (!grp)
|
if (!grp)
|
||||||
return nullptr;
|
return nullptr;
|
||||||
|
|
||||||
std::shared_ptr<Voice> vox = _allocateVoice(*grp, 32000.0, true, true, smx);
|
std::shared_ptr<Voice> vox = _allocateVoice(*grp, std::get<1>(search->second), 32000.0, true, true, smx);
|
||||||
m_activeEmitters.emplace(m_activeEmitters.end(), new Emitter(*this, *grp, std::move(vox)));
|
m_activeEmitters.emplace(m_activeEmitters.end(), new Emitter(*this, *grp, std::move(vox)));
|
||||||
Emitter& ret = *m_activeEmitters.back();
|
Emitter& ret = *m_activeEmitters.back();
|
||||||
if (!ret.getVoice()->loadSoundObject(search->second.second, 0, 1000.f, 0x3c, 0, 0))
|
if (!ret.getVoice()->loadSoundObject(std::get<2>(search->second), 0, 1000.f, 0x3c, 0, 0))
|
||||||
{
|
{
|
||||||
ret._destroy();
|
ret._destroy();
|
||||||
m_activeEmitters.pop_back();
|
m_activeEmitters.pop_back();
|
||||||
|
@ -265,9 +338,20 @@ std::shared_ptr<Emitter> Engine::addEmitter(const Vector3f& pos, const Vector3f&
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Start song playing from loaded audio groups */
|
/** Start song playing from loaded audio groups */
|
||||||
std::shared_ptr<Sequencer> Engine::seqPlay(int groupId, int songId, const unsigned char* arrData)
|
std::shared_ptr<Sequencer> Engine::seqPlay(int groupId, int songId,
|
||||||
|
const unsigned char* arrData, Submix* smx)
|
||||||
{
|
{
|
||||||
return {};
|
std::pair<AudioGroup*, const SongGroupIndex*> songGrp = _findSongGroup(groupId);
|
||||||
|
if (!songGrp.second)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
std::shared_ptr<Sequencer> ret = _allocateSequencer(*songGrp.first, groupId, songId, smx);
|
||||||
|
if (!ret)
|
||||||
|
return {};
|
||||||
|
|
||||||
|
if (arrData)
|
||||||
|
ret->playSong(arrData);
|
||||||
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Find voice from VoiceId */
|
/** Find voice from VoiceId */
|
||||||
|
@ -279,6 +363,14 @@ std::shared_ptr<Voice> Engine::findVoice(int vid)
|
||||||
if (ret)
|
if (ret)
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (std::shared_ptr<Sequencer>& seq : m_activeSequencers)
|
||||||
|
{
|
||||||
|
std::shared_ptr<Voice> ret = seq->findVoice(vid);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -299,6 +391,9 @@ void Engine::killKeygroup(uint8_t kg, bool now)
|
||||||
}
|
}
|
||||||
++it;
|
++it;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (std::shared_ptr<Sequencer>& seq : m_activeSequencers)
|
||||||
|
seq->killKeygroup(kg, now);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Send all voices using `macroId` the message `val` */
|
/** Send all voices using `macroId` the message `val` */
|
||||||
|
@ -310,6 +405,9 @@ void Engine::sendMacroMessage(ObjectId macroId, int32_t val)
|
||||||
if (vox->getObjectId() == macroId)
|
if (vox->getObjectId() == macroId)
|
||||||
vox->message(val);
|
vox->message(val);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (std::shared_ptr<Sequencer>& seq : m_activeSequencers)
|
||||||
|
seq->sendMacroMessage(macroId, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -22,7 +22,7 @@ float Envelope::nextSample(double sampleRate)
|
||||||
{
|
{
|
||||||
if (!m_curADSR)
|
if (!m_curADSR)
|
||||||
{
|
{
|
||||||
if (m_phase == State::Release)
|
if (m_phase == State::Release || m_phase == State::Complete)
|
||||||
return 0.f;
|
return 0.f;
|
||||||
return 1.f;
|
return 1.f;
|
||||||
}
|
}
|
||||||
|
@ -79,12 +79,20 @@ float Envelope::nextSample(double sampleRate)
|
||||||
{
|
{
|
||||||
uint16_t release = m_curADSR->releaseCoarse * 255 + m_curADSR->releaseFine;
|
uint16_t release = m_curADSR->releaseCoarse * 255 + m_curADSR->releaseFine;
|
||||||
if (release == 0)
|
if (release == 0)
|
||||||
|
{
|
||||||
|
m_phase = State::Complete;
|
||||||
return 0.f;
|
return 0.f;
|
||||||
|
}
|
||||||
double releaseFac = m_curMs / double(release);
|
double releaseFac = m_curMs / double(release);
|
||||||
if (releaseFac >= 1.0)
|
if (releaseFac >= 1.0)
|
||||||
|
{
|
||||||
|
m_phase = State::Complete;
|
||||||
return 0.f;
|
return 0.f;
|
||||||
|
}
|
||||||
return (1.0 - releaseFac) * m_releaseStartFactor;
|
return (1.0 - releaseFac) * m_releaseStartFactor;
|
||||||
}
|
}
|
||||||
|
case State::Complete:
|
||||||
|
return 0.f;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,6 +6,30 @@
|
||||||
namespace amuse
|
namespace amuse
|
||||||
{
|
{
|
||||||
|
|
||||||
|
void Sequencer::ChannelState::_bringOutYourDead()
|
||||||
|
{
|
||||||
|
for (auto it = m_chanVoxs.begin() ; it != m_chanVoxs.end() ;)
|
||||||
|
{
|
||||||
|
Voice* vox = it->second.get();
|
||||||
|
vox->_bringOutYourDead();
|
||||||
|
if (vox->_isRecursivelyDead())
|
||||||
|
{
|
||||||
|
it = m_chanVoxs.erase(it);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sequencer::_bringOutYourDead()
|
||||||
|
{
|
||||||
|
for (auto& chan : m_chanStates)
|
||||||
|
chan.second->_bringOutYourDead();
|
||||||
|
|
||||||
|
if (!m_arrData && m_dieOnEnd && getVoiceCount() == 0)
|
||||||
|
m_state = SequencerState::Dead;
|
||||||
|
}
|
||||||
|
|
||||||
void Sequencer::_destroy()
|
void Sequencer::_destroy()
|
||||||
{
|
{
|
||||||
Entity::_destroy();
|
Entity::_destroy();
|
||||||
|
@ -13,15 +37,15 @@ void Sequencer::_destroy()
|
||||||
m_submix->m_activeSequencers.erase(this);
|
m_submix->m_activeSequencers.erase(this);
|
||||||
|
|
||||||
for (const auto& chan : m_chanStates)
|
for (const auto& chan : m_chanStates)
|
||||||
for (const auto& vox : chan.second.m_chanVoxs)
|
for (const auto& vox : chan.second->m_chanVoxs)
|
||||||
vox.second->_destroy();
|
vox.second->_destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
Sequencer::~Sequencer() {}
|
Sequencer::~Sequencer() {}
|
||||||
|
|
||||||
Sequencer::Sequencer(Engine& engine, const AudioGroup& group,
|
Sequencer::Sequencer(Engine& engine, const AudioGroup& group, int groupId,
|
||||||
const SongGroupIndex& songGroup, int setupId, Submix* smx)
|
const SongGroupIndex& songGroup, int setupId, Submix* smx)
|
||||||
: Entity(engine, group), m_songGroup(songGroup), m_submix(smx)
|
: Entity(engine, group, groupId), m_songGroup(songGroup), m_submix(smx)
|
||||||
{
|
{
|
||||||
auto it = m_songGroup.m_midiSetups.find(setupId);
|
auto it = m_songGroup.m_midiSetups.find(setupId);
|
||||||
if (it != m_songGroup.m_midiSetups.cend())
|
if (it != m_songGroup.m_midiSetups.cend())
|
||||||
|
@ -63,7 +87,7 @@ size_t Sequencer::getVoiceCount() const
|
||||||
{
|
{
|
||||||
size_t ret = 0;
|
size_t ret = 0;
|
||||||
for (const auto& chan : m_chanStates)
|
for (const auto& chan : m_chanStates)
|
||||||
for (const auto& vox : chan.second.m_chanVoxs)
|
for (const auto& vox : chan.second->m_chanVoxs)
|
||||||
ret += vox.second->getTotalVoices();
|
ret += vox.second->getTotalVoices();
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
@ -73,9 +97,11 @@ std::shared_ptr<Voice> Sequencer::ChannelState::keyOn(uint8_t note, uint8_t velo
|
||||||
if (!m_page)
|
if (!m_page)
|
||||||
return {};
|
return {};
|
||||||
|
|
||||||
std::shared_ptr<Voice> ret = m_parent.m_engine._allocateVoice(m_parent.m_audioGroup, 32000.0,
|
std::shared_ptr<Voice> ret = m_parent.m_engine._allocateVoice(m_parent.m_audioGroup,
|
||||||
|
m_parent.m_groupId, 32000.0,
|
||||||
true, false, m_submix);
|
true, false, m_submix);
|
||||||
m_chanVoxs[note] = ret;
|
m_chanVoxs[note] = ret;
|
||||||
|
ret->installCtrlValues(m_ctrlVals);
|
||||||
if (!ret->loadSoundObject(SBig(m_page->objId), 0, 1000.f, note, velocity, m_ctrlVals[1]))
|
if (!ret->loadSoundObject(SBig(m_page->objId), 0, 1000.f, note, velocity, m_ctrlVals[1]))
|
||||||
{
|
{
|
||||||
m_parent.m_engine._destroyVoice(ret.get());
|
m_parent.m_engine._destroyVoice(ret.get());
|
||||||
|
@ -91,11 +117,11 @@ std::shared_ptr<Voice> Sequencer::keyOn(uint8_t chan, uint8_t note, uint8_t velo
|
||||||
auto chanSearch = m_chanStates.find(chan);
|
auto chanSearch = m_chanStates.find(chan);
|
||||||
if (chanSearch == m_chanStates.cend())
|
if (chanSearch == m_chanStates.cend())
|
||||||
{
|
{
|
||||||
auto it = m_chanStates.emplace(std::make_pair(chan, ChannelState(*this, chan)));
|
auto it = m_chanStates.emplace(std::make_pair(chan, std::make_unique<ChannelState>(*this, chan)));
|
||||||
it.first->second.keyOn(note, velocity);
|
return it.first->second->keyOn(note, velocity);
|
||||||
}
|
}
|
||||||
|
|
||||||
return chanSearch->second.keyOn(note, velocity);
|
return chanSearch->second->keyOn(note, velocity);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Sequencer::ChannelState::keyOff(uint8_t note, uint8_t velocity)
|
void Sequencer::ChannelState::keyOff(uint8_t note, uint8_t velocity)
|
||||||
|
@ -114,7 +140,7 @@ void Sequencer::keyOff(uint8_t chan, uint8_t note, uint8_t velocity)
|
||||||
if (chanSearch == m_chanStates.cend())
|
if (chanSearch == m_chanStates.cend())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
chanSearch->second.keyOff(note, velocity);
|
chanSearch->second->keyOff(note, velocity);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Sequencer::ChannelState::setCtrlValue(uint8_t ctrl, int8_t val)
|
void Sequencer::ChannelState::setCtrlValue(uint8_t ctrl, int8_t val)
|
||||||
|
@ -128,7 +154,7 @@ void Sequencer::setCtrlValue(uint8_t chan, uint8_t ctrl, int8_t val)
|
||||||
if (chanSearch == m_chanStates.cend())
|
if (chanSearch == m_chanStates.cend())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
chanSearch->second.setCtrlValue(ctrl, val);
|
chanSearch->second->setCtrlValue(ctrl, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Sequencer::ChannelState::setPitchWheel(float pitchWheel)
|
void Sequencer::ChannelState::setPitchWheel(float pitchWheel)
|
||||||
|
@ -143,7 +169,12 @@ void Sequencer::setPitchWheel(uint8_t chan, float pitchWheel)
|
||||||
if (chanSearch == m_chanStates.cend())
|
if (chanSearch == m_chanStates.cend())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
chanSearch->second.setPitchWheel(pitchWheel);
|
chanSearch->second->setPitchWheel(pitchWheel);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sequencer::setTempo(double ticksPerSec)
|
||||||
|
{
|
||||||
|
m_ticksPerSec = ticksPerSec;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Sequencer::ChannelState::allOff()
|
void Sequencer::ChannelState::allOff()
|
||||||
|
@ -156,10 +187,76 @@ void Sequencer::allOff(bool now)
|
||||||
{
|
{
|
||||||
if (now)
|
if (now)
|
||||||
for (auto& chan : m_chanStates)
|
for (auto& chan : m_chanStates)
|
||||||
chan.second.m_chanVoxs.clear();
|
chan.second->m_chanVoxs.clear();
|
||||||
else
|
else
|
||||||
for (auto& chan : m_chanStates)
|
for (auto& chan : m_chanStates)
|
||||||
chan.second.allOff();
|
chan.second->allOff();
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sequencer::ChannelState::killKeygroup(uint8_t kg, bool now)
|
||||||
|
{
|
||||||
|
for (auto it = m_chanVoxs.begin() ; it != m_chanVoxs.end() ;)
|
||||||
|
{
|
||||||
|
Voice* vox = it->second.get();
|
||||||
|
if (vox->m_keygroup == kg)
|
||||||
|
{
|
||||||
|
if (now)
|
||||||
|
{
|
||||||
|
it = m_chanVoxs.erase(it);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
vox->keyOff();
|
||||||
|
}
|
||||||
|
++it;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sequencer::killKeygroup(uint8_t kg, bool now)
|
||||||
|
{
|
||||||
|
for (auto& chan : m_chanStates)
|
||||||
|
chan.second->killKeygroup(kg, now);
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<Voice> Sequencer::ChannelState::findVoice(int vid)
|
||||||
|
{
|
||||||
|
for (const auto& vox : m_chanVoxs)
|
||||||
|
if (vox.second->vid() == vid)
|
||||||
|
return vox.second;
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
std::shared_ptr<Voice> Sequencer::findVoice(int vid)
|
||||||
|
{
|
||||||
|
for (auto& chan : m_chanStates)
|
||||||
|
{
|
||||||
|
std::shared_ptr<Voice> ret = chan.second->findVoice(vid);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sequencer::ChannelState::sendMacroMessage(ObjectId macroId, int32_t val)
|
||||||
|
{
|
||||||
|
for (const auto& v : m_chanVoxs)
|
||||||
|
{
|
||||||
|
Voice* vox = v.second.get();
|
||||||
|
if (vox->getObjectId() == macroId)
|
||||||
|
vox->message(val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sequencer::sendMacroMessage(ObjectId macroId, int32_t val)
|
||||||
|
{
|
||||||
|
for (auto& chan : m_chanStates)
|
||||||
|
chan.second->sendMacroMessage(macroId, val);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sequencer::playSong(const unsigned char* arrData, bool dieOnEnd)
|
||||||
|
{
|
||||||
|
m_arrData = arrData;
|
||||||
|
m_dieOnEnd = dieOnEnd;
|
||||||
|
m_state = SequencerState::Playing;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -135,6 +135,9 @@ void SoundMacroState::initialize(const unsigned char* ptr, int step, double tick
|
||||||
m_lastPlayMacroVid = -1;
|
m_lastPlayMacroVid = -1;
|
||||||
m_useAdsrControllers = false;
|
m_useAdsrControllers = false;
|
||||||
m_portamentoMode = 0;
|
m_portamentoMode = 0;
|
||||||
|
m_keyoffTrap.macroId = 0xffff;
|
||||||
|
m_sampleEndTrap.macroId = 0xffff;
|
||||||
|
m_messageTrap.macroId = 0xffff;
|
||||||
m_header = *reinterpret_cast<const Header*>(ptr);
|
m_header = *reinterpret_cast<const Header*>(ptr);
|
||||||
m_header.swapBig();
|
m_header.swapBig();
|
||||||
}
|
}
|
||||||
|
@ -794,15 +797,15 @@ bool SoundMacroState::advance(Voice& vox, double dt)
|
||||||
switch (event)
|
switch (event)
|
||||||
{
|
{
|
||||||
case 0:
|
case 0:
|
||||||
m_keyoffTrap.macroId = ObjectId();
|
m_keyoffTrap.macroId = 0xffff;
|
||||||
m_keyoffTrap.macroStep = -1;
|
m_keyoffTrap.macroStep = -1;
|
||||||
break;
|
break;
|
||||||
case 1:
|
case 1:
|
||||||
m_sampleEndTrap.macroId = ObjectId();
|
m_sampleEndTrap.macroId = 0xffff;
|
||||||
m_sampleEndTrap.macroStep = -1;
|
m_sampleEndTrap.macroStep = -1;
|
||||||
break;
|
break;
|
||||||
case 2:
|
case 2:
|
||||||
m_messageTrap.macroId = ObjectId();
|
m_messageTrap.macroId = 0xffff;
|
||||||
m_messageTrap.macroStep = -1;
|
m_messageTrap.macroStep = -1;
|
||||||
break;
|
break;
|
||||||
default: break;
|
default: break;
|
||||||
|
|
|
@ -22,18 +22,23 @@ void Voice::_destroy()
|
||||||
vox->_destroy();
|
vox->_destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
Voice::~Voice() {}
|
Voice::~Voice()
|
||||||
|
|
||||||
Voice::Voice(Engine& engine, const AudioGroup& group, int vid, bool emitter, Submix* smx)
|
|
||||||
: Entity(engine, group), m_vid(vid), m_emitter(emitter), m_submix(smx)
|
|
||||||
{
|
{
|
||||||
|
fprintf(stderr, "DEALLOC %p\n", this);
|
||||||
|
}
|
||||||
|
|
||||||
|
Voice::Voice(Engine& engine, const AudioGroup& group, int groupId, int vid, bool emitter, Submix* smx)
|
||||||
|
: Entity(engine, group, groupId), m_vid(vid), m_emitter(emitter), m_submix(smx)
|
||||||
|
{
|
||||||
|
fprintf(stderr, "ALLOC %p\n", this);
|
||||||
if (m_submix)
|
if (m_submix)
|
||||||
m_submix->m_activeVoices.insert(this);
|
m_submix->m_activeVoices.insert(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
Voice::Voice(Engine& engine, const AudioGroup& group, ObjectId oid, int vid, bool emitter, Submix* smx)
|
Voice::Voice(Engine& engine, const AudioGroup& group, int groupId, ObjectId oid, int vid, bool emitter, Submix* smx)
|
||||||
: Entity(engine, group, oid), m_vid(vid), m_emitter(emitter), m_submix(smx)
|
: Entity(engine, group, groupId, oid), m_vid(vid), m_emitter(emitter), m_submix(smx)
|
||||||
{
|
{
|
||||||
|
fprintf(stderr, "ALLOC %p\n", this);
|
||||||
if (m_submix)
|
if (m_submix)
|
||||||
m_submix->m_activeVoices.insert(this);
|
m_submix->m_activeVoices.insert(this);
|
||||||
}
|
}
|
||||||
|
@ -62,7 +67,7 @@ void Voice::_reset()
|
||||||
m_tremoloModScale = 0.f;
|
m_tremoloModScale = 0.f;
|
||||||
m_lfoPeriods[0] = 0.f;
|
m_lfoPeriods[0] = 0.f;
|
||||||
m_lfoPeriods[1] = 0.f;
|
m_lfoPeriods[1] = 0.f;
|
||||||
memset(m_ctrlVals, 0, 128);
|
memset(m_extCtrlVals, 0, 128);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Voice::_checkSamplePos()
|
bool Voice::_checkSamplePos()
|
||||||
|
@ -84,6 +89,15 @@ bool Voice::_checkSamplePos()
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Looped samples issue sample end when ADSR envelope complete */
|
||||||
|
if (m_volAdsr.isComplete())
|
||||||
|
{
|
||||||
|
m_state.sampleEndNotify(*this);
|
||||||
|
m_curSample = nullptr;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -142,10 +156,19 @@ std::shared_ptr<Voice> Voice::_findVoice(int vid, std::weak_ptr<Voice> thisPtr)
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::unique_ptr<int8_t[]>& Voice::_ensureCtrlVals()
|
||||||
|
{
|
||||||
|
if (m_ctrlValsSelf)
|
||||||
|
return m_ctrlValsSelf;
|
||||||
|
m_ctrlValsSelf.reset(new int8_t[128]);
|
||||||
|
memset(m_ctrlValsSelf.get(), 0, 128);
|
||||||
|
return m_ctrlValsSelf;
|
||||||
|
}
|
||||||
|
|
||||||
std::shared_ptr<Voice> Voice::_allocateVoice(double sampleRate, bool dynamicPitch)
|
std::shared_ptr<Voice> Voice::_allocateVoice(double sampleRate, bool dynamicPitch)
|
||||||
{
|
{
|
||||||
auto it = m_childVoices.emplace(m_childVoices.end(), new Voice(m_engine, m_audioGroup,
|
auto it = m_childVoices.emplace(m_childVoices.end(), new Voice(m_engine, m_audioGroup,
|
||||||
m_engine.m_nextVid++, m_emitter, m_submix));
|
m_groupId, m_engine.m_nextVid++, m_emitter, m_submix));
|
||||||
m_childVoices.back()->m_backendVoice =
|
m_childVoices.back()->m_backendVoice =
|
||||||
m_engine.getBackend().allocateVoice(*m_childVoices.back(), sampleRate, dynamicPitch);
|
m_engine.getBackend().allocateVoice(*m_childVoices.back(), sampleRate, dynamicPitch);
|
||||||
m_childVoices.back()->m_engineIt = it;
|
m_childVoices.back()->m_engineIt = it;
|
||||||
|
|
Loading…
Reference in New Issue