Work on Sequencer

This commit is contained in:
Jack Andersen 2016-05-15 11:56:23 -10:00
parent 18f3ce6f44
commit 2c3c9d3885
14 changed files with 396 additions and 90 deletions

View File

@ -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;

View File

@ -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");

View File

@ -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;

View File

@ -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);

View File

@ -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;}
}; };

View File

@ -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;}
}; };
} }

View File

@ -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;}
}; };
} }

View File

@ -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;
}; };

View File

@ -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())

View File

@ -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);
} }
} }

View File

@ -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;
} }
} }

View File

@ -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;
} }
} }

View File

@ -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;

View File

@ -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;