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.
|
||||
* 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;
|
||||
|
|
|
@ -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");
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;}
|
||||
};
|
||||
|
||||
|
|
|
@ -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;}
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -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;}
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
int8_t getModWheel() const
|
||||
if (!m_extCtrlVals)
|
||||
{
|
||||
if (!m_ctrlVals)
|
||||
return 0;
|
||||
return m_ctrlVals[1];
|
||||
std::unique_ptr<int8_t[]>& vals = _ensureCtrlVals();
|
||||
vals[ctrl] = val;
|
||||
}
|
||||
void installCtrlValues(int8_t* cvs) {m_ctrlVals = cvs;}
|
||||
else
|
||||
m_extCtrlVals[ctrl] = val;
|
||||
}
|
||||
|
||||
/** Get ModWheel value on voice */
|
||||
int8_t getModWheel() const {return getCtrlValue(1);}
|
||||
|
||||
/** 'install' external MIDI controller storage */
|
||||
void installCtrlValues(int8_t* cvs)
|
||||
{
|
||||
m_ctrlValsSelf.reset();
|
||||
m_extCtrlVals = 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;
|
||||
|
||||
};
|
||||
|
|
|
@ -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())
|
||||
|
|
144
lib/Engine.cpp
144
lib/Engine.cpp
|
@ -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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue