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.
* A client-assigned integer serves as the handle to the group once loaded
*/
int groupId = 1;
amuse::IntrusiveAudioGroupData data = LoadMyAudioGroup(groupId);
snd.addAudioGroup(groupId, data);
amuse::IntrusiveAudioGroupData data = LoadMyAudioGroup();
snd.addAudioGroup(data);
/* Starting a SoundMacro playing is accomplished like so: */
int sfxId = 0x1337;

View File

@ -170,7 +170,7 @@ struct AppCallback : boo::IApplicationCallback
/* Song playback selection */
int m_setupId = -1;
int m_chanId = -1;
int m_chanId = 0;
int8_t m_octave = 4;
int8_t m_velocity = 64;
std::shared_ptr<amuse::Sequencer> m_seq;
@ -227,10 +227,7 @@ struct AppCallback : boo::IApplicationCallback
(index.m_midiSetups.cbegin(), index.m_midiSetups.cend());
auto setupIt = sortEntries.cbegin();
if (setupIt != sortEntries.cend())
{
m_setupId = setupIt->first;
m_chanId = 0;
}
SelectSong(setupIt->first);
while (m_running)
{
@ -268,7 +265,12 @@ struct AppCallback : boo::IApplicationCallback
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)
{
m_lastVoxCount = voxCount;
@ -610,7 +612,7 @@ struct AppCallback : boo::IApplicationCallback
m_engine.emplace(booBackend);
/* Load group into engine */
const amuse::AudioGroup* group = m_engine->addAudioGroup(m_groupId, data);
const amuse::AudioGroup* group = m_engine->addAudioGroup(data);
if (!group)
Log.report(logvisor::Fatal, "unable to add audio group");

View File

@ -14,7 +14,6 @@ using Sample = std::pair<AudioGroupSampleDirectory::Entry,
class AudioGroup
{
int m_groupId;
AudioGroupProject m_proj;
AudioGroupPool m_pool;
AudioGroupSampleDirectory m_sdir;
@ -22,9 +21,7 @@ class AudioGroup
bool m_valid;
public:
operator bool() const {return m_valid;}
AudioGroup(int groupId, const AudioGroupData& data);
int groupId() const {return m_groupId;}
AudioGroup(const AudioGroupData& data);
const Sample* getSample(int sfxId) const;
const unsigned char* getSampleData(uint32_t offset) const;

View File

@ -27,18 +27,25 @@ class Engine
friend class Sequencer::ChannelState;
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<Emitter>> m_activeEmitters;
std::list<std::shared_ptr<Sequencer>> m_activeSequencers;
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;
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);
std::shared_ptr<Sequencer> _allocateSequencer(const AudioGroup& group, int groupId,
int setupId, Submix* smx);
Submix* _allocateSubmix(Submix* smx);
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);
void _bringOutYourDead();
public:
@ -52,10 +59,10 @@ public:
void pumpEngine();
/** 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 */
void removeAudioGroup(int groupId);
void removeAudioGroup(const AudioGroupData& data);
/** Create new Submix (a.k.a 'Studio') within root mix engine */
Submix* addSubmix(Submix* parent=nullptr);
@ -72,7 +79,8 @@ public:
Submix* smx=nullptr);
/** 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 */
std::shared_ptr<Voice> findVoice(int vid);

View File

@ -41,10 +41,11 @@ protected:
}
Engine& m_engine;
const AudioGroup& m_audioGroup;
int m_groupId;
ObjectId m_objectId = 0xffff; /* if applicable */
public:
Entity(Engine& engine, const AudioGroup& group, ObjectId oid=ObjectId())
: m_engine(engine), m_audioGroup(group), m_objectId(oid) {}
Entity(Engine& engine, const AudioGroup& group, int groupId, ObjectId oid=ObjectId())
: m_engine(engine), m_audioGroup(group), m_groupId(groupId), m_objectId(oid) {}
~Entity()
{
#ifndef NDEBUG
@ -55,6 +56,7 @@ public:
Engine& getEngine() {return m_engine;}
const AudioGroup& getAudioGroup() const {return m_audioGroup;}
int getGroupId() const {return m_groupId;}
ObjectId getObjectId() const {return m_objectId;}
};

View File

@ -15,7 +15,8 @@ public:
Attack,
Decay,
Sustain,
Release
Release,
Complete
};
private:
State m_phase = State::Attack; /**< Current envelope state */
@ -27,6 +28,7 @@ public:
void reset(const ADSR* adsr);
void keyOff();
float nextSample(double sampleRate);
bool isComplete() const {return m_phase == State::Complete;}
};
}

View File

@ -5,18 +5,33 @@
#include "AudioGroupProject.hpp"
#include <unordered_map>
#include <memory>
#include <list>
namespace amuse
{
class Submix;
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
{
friend class Engine;
const SongGroupIndex& m_songGroup; /**< Quick access to song group project index */
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) */
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 */
struct ChannelState
@ -32,20 +47,31 @@ class Sequencer : public Entity
std::unordered_map<uint8_t, std::shared_ptr<Voice>> m_chanVoxs;
int8_t m_ctrlVals[128]; /**< MIDI controller values */
void _bringOutYourDead();
std::shared_ptr<Voice> keyOn(uint8_t note, uint8_t velocity);
void keyOff(uint8_t note, uint8_t velocity);
void setCtrlValue(uint8_t ctrl, int8_t val);
void setPitchWheel(float pitchWheel);
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();
public:
~Sequencer();
Sequencer(Engine& engine, const AudioGroup& group,
Sequencer(Engine& engine, const AudioGroup& group, int groupId,
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 */
size_t getVoiceCount() const;
@ -63,6 +89,24 @@ public:
/** Send keyoffs to all active notes, silence immediately if `now` set */
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_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 _reset();
@ -122,6 +123,7 @@ class Voice : public Entity
bool _isRecursivelyDead();
void _bringOutYourDead();
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::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);
public:
~Voice();
Voice(Engine& engine, const AudioGroup& group, 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, 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,
* internally advancing the voice stream */
@ -250,28 +252,50 @@ public:
/** Assign voice to keygroup for coordinated mass-silencing */
void setKeygroup(uint8_t kg) {m_keygroup = kg;}
/** Get note played on voice */
uint8_t getLastNote() const {return m_state.m_initKey;}
/** Get MIDI Controller value on voice */
int8_t getCtrlValue(uint8_t ctrl) const
{
if (!m_ctrlVals)
if (!m_extCtrlVals)
{
if (m_ctrlValsSelf)
m_ctrlValsSelf[ctrl];
return 0;
return m_ctrlVals[ctrl];
}
return m_extCtrlVals[ctrl];
}
/** Set MIDI Controller value on voice */
void setCtrlValue(uint8_t ctrl, int8_t val)
{
if (!m_ctrlVals)
return;
m_ctrlVals[ctrl] = val;
if (!m_extCtrlVals)
{
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)
return 0;
return m_ctrlVals[1];
m_ctrlValsSelf.reset();
m_extCtrlVals = cvs;
}
void installCtrlValues(int8_t* cvs) {m_ctrlVals = cvs;}
/** Get MIDI pitch wheel value on voice */
int8_t getPitchWheel() const {return m_curPitchWheel * 127;}
/** Get MIDI aftertouch value on voice */
int8_t getAftertouch() const {return m_curAftertouch;}
/** Get count of all voices in hierarchy, including this one */
size_t getTotalVoices() const;
};

View File

@ -4,9 +4,8 @@
namespace amuse
{
AudioGroup::AudioGroup(int groupId, const AudioGroupData& data)
: m_groupId(groupId),
m_proj(data.getProj()),
AudioGroup::AudioGroup(const AudioGroupData& data)
: m_proj(data.getProj()),
m_pool(data.getPool()),
m_sdir(data.getSdir()),
m_samp(data.getSamp())

View File

@ -17,16 +17,51 @@ Engine::Engine(IBackendVoiceAllocator& 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)
{
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_backend.allocateVoice(*m_activeVoices.back(), sampleRate, dynamicPitch);
m_activeVoices.back()->m_engineIt = it;
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)
{
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);
}
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)
{
#ifndef NDEBUG
@ -78,6 +122,18 @@ void Engine::_bringOutYourDead()
}
++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 */
@ -94,31 +150,33 @@ void Engine::pumpEngine()
}
/** 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)
return nullptr;
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 */
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());
for (const auto& pair : sfxGroup.m_sfxEntries)
m_sfxLookup[pair.first] = std::make_pair(ret, SBig(pair.second->objId));
for (const auto& ent : sfxGroup.m_sfxEntries)
m_sfxLookup[ent.first] = std::make_tuple(ret, grp.first, SBig(ent.second->objId));
}
return ret;
}
/** Remove audio group from engine */
void Engine::removeAudioGroup(int groupId)
void Engine::removeAudioGroup(const AudioGroupData& data)
{
auto search = m_audioGroups.find(groupId);
if (search == m_audioGroups.end())
auto search = m_audioGroups.find(&data);
if (search == m_audioGroups.cend())
return;
AudioGroup* grp = search->second.get();
@ -126,7 +184,7 @@ void Engine::removeAudioGroup(int groupId)
for (auto it = m_activeVoices.begin() ; it != m_activeVoices.end() ;)
{
Voice* vox = it->get();
if (vox->getAudioGroup().groupId() == groupId)
if (&vox->getAudioGroup() == grp)
{
vox->_destroy();
it = m_activeVoices.erase(it);
@ -138,7 +196,7 @@ void Engine::removeAudioGroup(int groupId)
for (auto it = m_activeEmitters.begin() ; it != m_activeEmitters.end() ;)
{
Emitter* emitter = it->get();
if (emitter->getAudioGroup().groupId() == groupId)
if (&emitter->getAudioGroup() == grp)
{
emitter->_destroy();
it = m_activeEmitters.erase(it);
@ -150,7 +208,7 @@ void Engine::removeAudioGroup(int groupId)
for (auto it = m_activeSequencers.begin() ; it != m_activeSequencers.end() ;)
{
Sequencer* seq = it->get();
if (seq->getAudioGroup().groupId() == groupId)
if (&seq->getAudioGroup() == grp)
{
seq->_destroy();
it = m_activeSequencers.erase(it);
@ -167,7 +225,7 @@ void Engine::removeAudioGroup(int groupId)
m_sfxLookup.erase(pair.first);
}
m_audioGroups.erase(groupId);
m_audioGroups.erase(search);
}
/** Create new Submix (a.k.a 'Studio') within root mix engine */
@ -194,6 +252,21 @@ void Engine::removeSubmix(Submix* smx)
++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 */
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())
return nullptr;
AudioGroup* grp = search->second.first;
AudioGroup* grp = std::get<0>(search->second);
if (!grp)
return nullptr;
std::shared_ptr<Voice> ret = _allocateVoice(*grp, 32000.0, true, false, smx);
if (!ret->loadSoundObject(search->second.second, 0, 1000.f, 0x3c, 0, 0))
std::shared_ptr<Voice> ret = _allocateVoice(*grp, std::get<1>(search->second), 32000.0, true, false, smx);
if (!ret->loadSoundObject(std::get<2>(search->second), 0, 1000.f, 0x3c, 0, 0))
{
_destroyVoice(ret.get());
return {};
@ -241,14 +314,14 @@ std::shared_ptr<Emitter> Engine::addEmitter(const Vector3f& pos, const Vector3f&
if (search == m_sfxLookup.end())
return nullptr;
AudioGroup* grp = search->second.first;
AudioGroup* grp = std::get<0>(search->second);
if (!grp)
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)));
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();
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 */
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 */
@ -279,6 +363,14 @@ std::shared_ptr<Voice> Engine::findVoice(int vid)
if (ret)
return ret;
}
for (std::shared_ptr<Sequencer>& seq : m_activeSequencers)
{
std::shared_ptr<Voice> ret = seq->findVoice(vid);
if (ret)
return ret;
}
return {};
}
@ -299,6 +391,9 @@ void Engine::killKeygroup(uint8_t kg, bool now)
}
++it;
}
for (std::shared_ptr<Sequencer>& seq : m_activeSequencers)
seq->killKeygroup(kg, now);
}
/** Send all voices using `macroId` the message `val` */
@ -310,6 +405,9 @@ void Engine::sendMacroMessage(ObjectId macroId, int32_t val)
if (vox->getObjectId() == macroId)
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_phase == State::Release)
if (m_phase == State::Release || m_phase == State::Complete)
return 0.f;
return 1.f;
}
@ -79,12 +79,20 @@ float Envelope::nextSample(double sampleRate)
{
uint16_t release = m_curADSR->releaseCoarse * 255 + m_curADSR->releaseFine;
if (release == 0)
{
m_phase = State::Complete;
return 0.f;
}
double releaseFac = m_curMs / double(release);
if (releaseFac >= 1.0)
{
m_phase = State::Complete;
return 0.f;
}
return (1.0 - releaseFac) * m_releaseStartFactor;
}
case State::Complete:
return 0.f;
}
}

View File

@ -6,6 +6,30 @@
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()
{
Entity::_destroy();
@ -13,15 +37,15 @@ void Sequencer::_destroy()
m_submix->m_activeSequencers.erase(this);
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();
}
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)
: 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);
if (it != m_songGroup.m_midiSetups.cend())
@ -63,7 +87,7 @@ size_t Sequencer::getVoiceCount() const
{
size_t ret = 0;
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();
return ret;
}
@ -73,9 +97,11 @@ std::shared_ptr<Voice> Sequencer::ChannelState::keyOn(uint8_t note, uint8_t velo
if (!m_page)
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);
m_chanVoxs[note] = ret;
ret->installCtrlValues(m_ctrlVals);
if (!ret->loadSoundObject(SBig(m_page->objId), 0, 1000.f, note, velocity, m_ctrlVals[1]))
{
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);
if (chanSearch == m_chanStates.cend())
{
auto it = m_chanStates.emplace(std::make_pair(chan, ChannelState(*this, chan)));
it.first->second.keyOn(note, velocity);
auto it = m_chanStates.emplace(std::make_pair(chan, std::make_unique<ChannelState>(*this, chan)));
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)
@ -114,7 +140,7 @@ void Sequencer::keyOff(uint8_t chan, uint8_t note, uint8_t velocity)
if (chanSearch == m_chanStates.cend())
return;
chanSearch->second.keyOff(note, velocity);
chanSearch->second->keyOff(note, velocity);
}
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())
return;
chanSearch->second.setCtrlValue(ctrl, val);
chanSearch->second->setCtrlValue(ctrl, val);
}
void Sequencer::ChannelState::setPitchWheel(float pitchWheel)
@ -143,7 +169,12 @@ void Sequencer::setPitchWheel(uint8_t chan, float pitchWheel)
if (chanSearch == m_chanStates.cend())
return;
chanSearch->second.setPitchWheel(pitchWheel);
chanSearch->second->setPitchWheel(pitchWheel);
}
void Sequencer::setTempo(double ticksPerSec)
{
m_ticksPerSec = ticksPerSec;
}
void Sequencer::ChannelState::allOff()
@ -156,10 +187,76 @@ void Sequencer::allOff(bool now)
{
if (now)
for (auto& chan : m_chanStates)
chan.second.m_chanVoxs.clear();
chan.second->m_chanVoxs.clear();
else
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_useAdsrControllers = false;
m_portamentoMode = 0;
m_keyoffTrap.macroId = 0xffff;
m_sampleEndTrap.macroId = 0xffff;
m_messageTrap.macroId = 0xffff;
m_header = *reinterpret_cast<const Header*>(ptr);
m_header.swapBig();
}
@ -794,15 +797,15 @@ bool SoundMacroState::advance(Voice& vox, double dt)
switch (event)
{
case 0:
m_keyoffTrap.macroId = ObjectId();
m_keyoffTrap.macroId = 0xffff;
m_keyoffTrap.macroStep = -1;
break;
case 1:
m_sampleEndTrap.macroId = ObjectId();
m_sampleEndTrap.macroId = 0xffff;
m_sampleEndTrap.macroStep = -1;
break;
case 2:
m_messageTrap.macroId = ObjectId();
m_messageTrap.macroId = 0xffff;
m_messageTrap.macroStep = -1;
break;
default: break;

View File

@ -22,18 +22,23 @@ void Voice::_destroy()
vox->_destroy();
}
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)
Voice::~Voice()
{
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)
m_submix->m_activeVoices.insert(this);
}
Voice::Voice(Engine& engine, const AudioGroup& group, ObjectId oid, int vid, bool emitter, Submix* smx)
: Entity(engine, group, oid), m_vid(vid), m_emitter(emitter), m_submix(smx)
Voice::Voice(Engine& engine, const AudioGroup& group, int groupId, ObjectId oid, int vid, bool emitter, 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)
m_submix->m_activeVoices.insert(this);
}
@ -62,7 +67,7 @@ void Voice::_reset()
m_tremoloModScale = 0.f;
m_lfoPeriods[0] = 0.f;
m_lfoPeriods[1] = 0.f;
memset(m_ctrlVals, 0, 128);
memset(m_extCtrlVals, 0, 128);
}
bool Voice::_checkSamplePos()
@ -84,6 +89,15 @@ bool Voice::_checkSamplePos()
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;
}
@ -142,10 +156,19 @@ std::shared_ptr<Voice> Voice::_findVoice(int vid, std::weak_ptr<Voice> thisPtr)
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)
{
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_engine.getBackend().allocateVoice(*m_childVoices.back(), sampleRate, dynamicPitch);
m_childVoices.back()->m_engineIt = it;