amuse/lib/Sequencer.cpp

639 lines
16 KiB
C++
Raw Normal View History

#include "amuse/Sequencer.hpp"
#include <map>
#include "amuse/Engine.hpp"
#include "amuse/Voice.hpp"
2018-12-08 05:20:09 +00:00
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;
2016-05-15 21:56:23 +00:00
}
2018-12-08 05:20:09 +00:00
++it;
}
for (auto it = m_keyoffVoxs.begin(); it != m_keyoffVoxs.end();) {
Voice* vox = it->get();
vox->_bringOutYourDead();
if (vox->_isRecursivelyDead()) {
it = m_keyoffVoxs.erase(it);
continue;
}
2018-12-08 05:20:09 +00:00
++it;
}
std::unordered_set<ObjToken<Voice>> newSet = m_keyoffVoxs;
m_keyoffVoxs = newSet;
2016-05-15 21:56:23 +00:00
}
2018-12-08 05:20:09 +00:00
void Sequencer::_bringOutYourDead() {
for (auto& chan : m_chanStates)
if (chan)
chan._bringOutYourDead();
2016-05-15 21:56:23 +00:00
2018-12-08 05:20:09 +00:00
if (!m_arrData && m_dieOnEnd && getVoiceCount() == 0)
m_state = SequencerState::Dead;
2016-05-15 21:56:23 +00:00
}
2018-12-08 05:20:09 +00:00
void Sequencer::_destroy() {
Entity::_destroy();
if (m_studio)
m_studio.reset();
2016-05-15 06:48:26 +00:00
}
2018-12-08 05:20:09 +00:00
Sequencer::~Sequencer() {
if (m_studio)
m_studio.reset();
}
2016-05-14 22:38:37 +00:00
Sequencer::Sequencer(Engine& engine, const AudioGroup& group, GroupId groupId, const SongGroupIndex* songGroup,
SongId setupId, ObjToken<Studio> studio)
: Entity(engine, group, groupId), m_songGroup(songGroup), m_studio(std::move(studio)) {
2018-12-08 05:20:09 +00:00
auto it = m_songGroup->m_midiSetups.find(setupId);
if (it != m_songGroup->m_midiSetups.cend())
m_midiSetup = it->second.data();
2016-05-15 06:48:26 +00:00
}
Sequencer::Sequencer(Engine& engine, const AudioGroup& group, GroupId groupId, const SFXGroupIndex* sfxGroup,
2018-07-29 03:37:06 +00:00
ObjToken<Studio> studio)
: Entity(engine, group, groupId), m_sfxGroup(sfxGroup), m_studio(std::move(studio)) {
2019-07-20 04:23:25 +00:00
std::map<ObjectId, const SFXGroupIndex::SFXEntry*> sortSFX;
2018-12-08 05:20:09 +00:00
for (const auto& sfx : sfxGroup->m_sfxEntries)
sortSFX[sfx.first] = &sfx.second;
2018-12-08 05:20:09 +00:00
m_sfxMappings.reserve(sortSFX.size());
for (const auto& sfx : sortSFX)
m_sfxMappings.push_back(sfx.second);
}
Sequencer::ChannelState::~ChannelState() = default;
2016-05-15 06:48:26 +00:00
2018-12-08 05:20:09 +00:00
Sequencer::ChannelState::ChannelState(Sequencer& parent, uint8_t chanId) : m_parent(&parent), m_chanId(chanId) {
if (m_parent->m_songGroup) {
if (m_parent->m_midiSetup) {
m_setup = &m_parent->m_midiSetup[chanId];
if (chanId == 9) {
auto it = m_parent->m_songGroup->m_drumPages.find(m_setup->programNo);
if (it != m_parent->m_songGroup->m_drumPages.cend()) {
m_page = &it->second;
m_curProgram = m_setup->programNo;
2016-06-07 03:42:51 +00:00
}
2018-12-08 05:20:09 +00:00
} else {
auto it = m_parent->m_songGroup->m_normPages.find(m_setup->programNo);
if (it != m_parent->m_songGroup->m_normPages.cend()) {
m_page = &it->second;
m_curProgram = m_setup->programNo;
2016-06-07 03:42:51 +00:00
}
2018-12-08 05:20:09 +00:00
}
m_curVol = m_setup->volume / 127.f;
m_curPan = m_setup->panning / 64.f - 1.f;
m_ctrlVals[0x5b] = m_setup->reverb;
m_ctrlVals[0x5d] = m_setup->chorus;
} else {
if (chanId == 9) {
auto it = m_parent->m_songGroup->m_drumPages.find(0);
if (it != m_parent->m_songGroup->m_drumPages.cend())
m_page = &it->second;
} else {
auto it = m_parent->m_songGroup->m_normPages.find(0);
if (it != m_parent->m_songGroup->m_normPages.cend())
m_page = &it->second;
}
m_curVol = 1.f;
m_curPan = 0.f;
m_ctrlVals[0x5b] = 0;
m_ctrlVals[0x5d] = 0;
2016-05-15 06:48:26 +00:00
}
2018-12-08 05:20:09 +00:00
} else if (m_parent->m_sfxGroup) {
m_curVol = 1.f;
m_curPan = 0.f;
m_ctrlVals[0x5b] = 0;
m_ctrlVals[0x5d] = 0;
}
m_ctrlVals[7] = 127;
m_ctrlVals[10] = 64;
}
void Sequencer::advance(double dt) {
if (m_state == SequencerState::Playing) {
if (m_stopFadeTime) {
float step = dt / m_stopFadeTime * m_stopFadeBeginVol;
float vol = std::max(0.f, m_curVol - step);
if (vol == 0.f) {
m_arrData = nullptr;
m_state = SequencerState::Interactive;
allOff(true);
m_stopFadeTime = 0.f;
return;
} else {
setVolume(vol);
}
} else if (m_volFadeTime) {
float step = dt / m_volFadeTime * std::fabs(m_volFadeTarget - m_volFadeStart);
float vol;
if (m_curVol < m_volFadeTarget)
vol = std::min(m_volFadeTarget, m_curVol + step);
else
vol = std::max(m_volFadeTarget, m_curVol - step);
if (vol == m_volFadeTarget)
m_volFadeTime = 0.f;
else
setVolume(vol);
2016-05-15 06:48:26 +00:00
}
2018-12-08 05:20:09 +00:00
if (m_songState.advance(*this, dt)) {
m_arrData = nullptr;
m_state = SequencerState::Interactive;
allOff();
2017-02-27 05:24:58 +00:00
}
2018-12-08 05:20:09 +00:00
}
2016-05-15 06:48:26 +00:00
}
2018-12-08 05:20:09 +00:00
size_t Sequencer::ChannelState::getVoiceCount() const {
size_t ret = 0;
for (const auto& vox : m_chanVoxs)
ret += vox.second->getTotalVoices();
for (const auto& vox : m_keyoffVoxs)
ret += vox->getTotalVoices();
return ret;
}
2018-12-08 05:20:09 +00:00
size_t Sequencer::getVoiceCount() const {
size_t ret = 0;
for (const auto& chan : m_chanStates)
if (chan)
ret += chan.getVoiceCount();
return ret;
2016-05-15 06:48:26 +00:00
}
2018-12-08 05:20:09 +00:00
ObjToken<Voice> Sequencer::ChannelState::keyOn(uint8_t note, uint8_t velocity) {
if (m_parent->m_songGroup && !m_page)
return {};
2016-05-19 05:27:39 +00:00
2018-12-08 05:20:09 +00:00
if (m_lastVoice && m_lastVoice->isDestroyed())
m_lastVoice.reset();
2018-12-08 05:20:09 +00:00
/* If portamento is enabled for voice, pre-empt spawning new voices */
if (ObjToken<Voice> lastVoice = m_lastVoice) {
uint8_t lastNote = lastVoice->getLastNote();
if (lastVoice->doPortamento(note)) {
m_chanVoxs.erase(lastNote);
m_chanVoxs[note] = lastVoice;
return lastVoice;
}
2018-12-08 05:20:09 +00:00
}
2018-12-08 05:20:09 +00:00
/* Ensure keyoff sent first */
auto keySearch = m_chanVoxs.find(note);
if (keySearch != m_chanVoxs.cend()) {
if (keySearch->second == m_lastVoice)
m_lastVoice.reset();
2016-05-15 06:48:26 +00:00
keySearch->second->keyOff();
2018-12-08 05:20:09 +00:00
keySearch->second->setPedal(false);
m_keyoffVoxs.emplace(keySearch->second);
2016-05-15 06:48:26 +00:00
m_chanVoxs.erase(keySearch);
2018-12-08 05:20:09 +00:00
}
std::list<ObjToken<Voice>>::iterator ret = m_parent->m_engine._allocateVoice(
m_parent->m_audioGroup, m_parent->m_groupId, NativeSampleRate, true, false, m_parent->m_studio);
if (*ret) {
(*ret)->m_sequencer = m_parent;
m_chanVoxs[note] = *ret;
(*ret)->installCtrlValues(m_ctrlVals.data());
2018-12-08 05:20:09 +00:00
ObjectId oid;
bool res;
if (m_parent->m_songGroup) {
oid = m_page->objId;
res = (*ret)->loadPageObject(oid, m_ticksPerSec, note, velocity, m_ctrlVals[1]);
} else if (m_parent->m_sfxMappings.size()) {
size_t lookupIdx = note % m_parent->m_sfxMappings.size();
const SFXGroupIndex::SFXEntry* sfxEntry = m_parent->m_sfxMappings[lookupIdx];
oid = sfxEntry->objId;
note = sfxEntry->defKey;
res = (*ret)->loadPageObject(oid, m_ticksPerSec, note, velocity, m_ctrlVals[1]);
} else
return {};
if (!res) {
m_parent->m_engine._destroyVoice(ret);
return {};
2016-05-22 08:35:55 +00:00
}
2018-12-08 05:20:09 +00:00
(*ret)->setVolume(m_parent->m_curVol * m_curVol);
(*ret)->setReverbVol(m_ctrlVals[0x5b] / 127.f);
(*ret)->setAuxBVol(m_ctrlVals[0x5d] / 127.f);
(*ret)->setPan(m_curPan);
(*ret)->setPitchWheel(m_curPitchWheel);
if (m_pitchWheelRange != -1)
(*ret)->setPitchWheelRange(m_pitchWheelRange, m_pitchWheelRange);
2018-12-08 05:20:09 +00:00
if (m_ctrlVals[64] > 64)
(*ret)->setPedal(true);
2018-12-08 05:20:09 +00:00
m_lastVoice = *ret;
}
2018-12-08 05:20:09 +00:00
return *ret;
2016-05-15 06:48:26 +00:00
}
2018-12-08 05:20:09 +00:00
ObjToken<Voice> Sequencer::keyOn(uint8_t chan, uint8_t note, uint8_t velocity) {
if (chan >= m_chanStates.size()) {
2018-12-08 05:20:09 +00:00
return {};
}
2016-05-15 06:48:26 +00:00
if (!m_chanStates[chan]) {
2018-12-08 05:20:09 +00:00
m_chanStates[chan] = ChannelState(*this, chan);
}
2018-12-08 05:20:09 +00:00
return m_chanStates[chan].keyOn(note, velocity);
}
void Sequencer::ChannelState::keyOff(uint8_t note, uint8_t velocity) {
auto keySearch = m_chanVoxs.find(note);
if (keySearch == m_chanVoxs.cend())
return;
if ((m_lastVoice && m_lastVoice->isDestroyed()) || keySearch->second == m_lastVoice)
m_lastVoice.reset();
keySearch->second->keyOff();
m_keyoffVoxs.emplace(keySearch->second);
m_chanVoxs.erase(keySearch);
}
void Sequencer::keyOff(uint8_t chan, uint8_t note, uint8_t velocity) {
if (chan >= m_chanStates.size() || !m_chanStates[chan]) {
2018-12-08 05:20:09 +00:00
return;
}
2018-12-08 05:20:09 +00:00
m_chanStates[chan].keyOff(note, velocity);
}
void Sequencer::ChannelState::setCtrlValue(uint8_t ctrl, int8_t val) {
m_ctrlVals[ctrl] = val;
for (const auto& vox : m_chanVoxs)
vox.second->_notifyCtrlChange(ctrl, val);
for (const auto& vox : m_keyoffVoxs)
vox->_notifyCtrlChange(ctrl, val);
switch (ctrl) {
case 7:
setVolume(val / 127.f);
break;
case 10:
setPan(val / 64.f - 1.f);
break;
case 98:
// RPN LSB
m_rpn &= ~0x7f;
m_rpn |= val;
2019-02-18 05:45:24 +00:00
break;
2018-12-08 05:20:09 +00:00
case 99:
// RPN MSB
m_rpn &= ~0x3f80;
m_rpn |= val << 7;
2019-02-18 05:45:24 +00:00
break;
2018-12-08 05:20:09 +00:00
case 6:
if (m_rpn == 0)
m_pitchWheelRange = val;
2019-02-18 05:45:24 +00:00
break;
2018-12-08 05:20:09 +00:00
case 96:
if (m_rpn == 0)
m_pitchWheelRange += 1;
2019-02-18 05:45:24 +00:00
break;
2018-12-08 05:20:09 +00:00
case 97:
if (m_rpn == 0)
m_pitchWheelRange -= 1;
2019-02-18 05:45:24 +00:00
break;
2018-12-08 05:20:09 +00:00
default:
break;
}
}
bool Sequencer::ChannelState::programChange(int8_t prog) {
if (m_parent->m_songGroup) {
if (m_chanId == 9) {
auto it = m_parent->m_songGroup->m_drumPages.find(prog);
if (it != m_parent->m_songGroup->m_drumPages.cend()) {
m_page = &it->second;
m_curProgram = prog;
return true;
}
} else {
auto it = m_parent->m_songGroup->m_normPages.find(prog);
if (it != m_parent->m_songGroup->m_normPages.cend()) {
m_page = &it->second;
m_curProgram = prog;
return true;
}
}
2018-12-08 05:20:09 +00:00
}
return false;
2016-05-15 06:48:26 +00:00
}
2018-12-08 05:20:09 +00:00
void Sequencer::ChannelState::nextProgram() {
int newProg = m_curProgram;
while ((newProg += 1) <= 127)
if (programChange(newProg))
break;
2016-05-15 06:48:26 +00:00
}
2018-12-08 05:20:09 +00:00
void Sequencer::ChannelState::prevProgram() {
int newProg = m_curProgram;
while ((newProg -= 1) >= 0)
if (programChange(newProg))
break;
}
2016-05-15 06:48:26 +00:00
2018-12-08 05:20:09 +00:00
void Sequencer::setCtrlValue(uint8_t chan, uint8_t ctrl, int8_t val) {
if (chan >= m_chanStates.size()) {
2018-12-08 05:20:09 +00:00
return;
}
2018-12-08 05:20:09 +00:00
if (ctrl == 0x66) {
2020-04-11 22:49:30 +00:00
fmt::print(FMT_STRING("Loop Start\n"));
2018-12-08 05:20:09 +00:00
} else if (ctrl == 0x67) {
2020-04-11 22:49:30 +00:00
fmt::print(FMT_STRING("Loop End\n"));
2018-12-08 05:20:09 +00:00
}
2016-05-15 21:56:23 +00:00
if (!m_chanStates[chan]) {
2018-12-08 05:20:09 +00:00
m_chanStates[chan] = ChannelState(*this, chan);
}
2018-09-08 21:34:01 +00:00
2018-12-08 05:20:09 +00:00
m_chanStates[chan].setCtrlValue(ctrl, val);
2018-09-08 21:34:01 +00:00
}
2016-05-15 06:48:26 +00:00
2018-12-08 05:20:09 +00:00
void Sequencer::ChannelState::setPitchWheel(float pitchWheel) {
m_curPitchWheel = pitchWheel;
for (const auto& vox : m_chanVoxs)
vox.second->setPitchWheel(pitchWheel);
for (const auto& vox : m_keyoffVoxs)
vox->setPitchWheel(pitchWheel);
2016-05-15 06:48:26 +00:00
}
2018-12-08 05:20:09 +00:00
void Sequencer::setPitchWheel(uint8_t chan, float pitchWheel) {
if (chan >= m_chanStates.size()) {
2018-12-08 05:20:09 +00:00
return;
}
2016-05-15 21:56:23 +00:00
if (!m_chanStates[chan]) {
2018-12-08 05:20:09 +00:00
m_chanStates[chan] = ChannelState(*this, chan);
}
2018-12-08 05:20:09 +00:00
m_chanStates[chan].setPitchWheel(pitchWheel);
}
2018-12-08 05:20:09 +00:00
void Sequencer::setTempo(uint8_t chan, double ticksPerSec) { m_chanStates[chan].m_ticksPerSec = ticksPerSec; }
2018-12-08 05:20:09 +00:00
void Sequencer::setTempo(double ticksPerSec) {
for (auto& c : m_chanStates)
c.m_ticksPerSec = ticksPerSec;
2016-05-15 21:56:23 +00:00
}
2018-12-08 05:20:09 +00:00
void Sequencer::ChannelState::allOff() {
if (m_lastVoice && m_lastVoice->isDestroyed())
m_lastVoice.reset();
for (auto it = m_chanVoxs.begin(); it != m_chanVoxs.end();) {
if (it->second == m_lastVoice)
m_lastVoice.reset();
it->second->keyOff();
m_keyoffVoxs.emplace(it->second);
it = m_chanVoxs.erase(it);
}
2016-05-15 21:56:23 +00:00
}
2018-12-08 05:20:09 +00:00
void Sequencer::allOff(bool now) {
if (now)
for (auto& chan : m_chanStates) {
if (chan) {
for (const auto& vox : chan.m_chanVoxs)
vox.second->kill();
for (const auto& vox : chan.m_keyoffVoxs)
vox->kill();
chan.m_chanVoxs.clear();
chan.m_keyoffVoxs.clear();
}
2016-05-15 21:56:23 +00:00
}
2018-12-08 05:20:09 +00:00
else
for (auto& chan : m_chanStates)
if (chan)
chan.allOff();
2016-05-15 21:56:23 +00:00
}
2018-12-08 05:20:09 +00:00
void Sequencer::allOff(uint8_t chan, bool now) {
if (chan >= m_chanStates.size() || !m_chanStates[chan]) {
2018-12-08 05:20:09 +00:00
return;
}
2016-05-15 21:56:23 +00:00
2018-12-08 05:20:09 +00:00
if (now) {
for (const auto& vox : m_chanStates[chan].m_chanVoxs) {
2018-12-08 05:20:09 +00:00
vox.second->kill();
}
for (const auto& vox : m_chanStates[chan].m_keyoffVoxs) {
2018-12-08 05:20:09 +00:00
vox->kill();
}
2018-12-08 05:20:09 +00:00
m_chanStates[chan].m_chanVoxs.clear();
m_chanStates[chan].m_keyoffVoxs.clear();
} else {
2018-12-08 05:20:09 +00:00
m_chanStates[chan].allOff();
}
2016-05-15 21:56:23 +00:00
}
2018-12-08 05:20:09 +00:00
void Sequencer::ChannelState::killKeygroup(uint8_t kg, bool now) {
if (m_lastVoice && m_lastVoice->isDestroyed())
m_lastVoice.reset();
2016-05-15 06:48:26 +00:00
2018-12-08 05:20:09 +00:00
for (auto it = m_chanVoxs.begin(); it != m_chanVoxs.end();) {
Voice* vox = it->second.get();
if (vox->m_keygroup == kg) {
if (it->second == m_lastVoice)
m_lastVoice.reset();
if (now) {
vox->kill();
it = m_chanVoxs.erase(it);
continue;
}
vox->keyOff();
m_keyoffVoxs.emplace(it->second);
it = m_chanVoxs.erase(it);
continue;
2017-02-27 05:24:58 +00:00
}
2018-12-08 05:20:09 +00:00
++it;
}
if (now) {
for (auto it = m_keyoffVoxs.begin(); it != m_keyoffVoxs.end();) {
Voice* vox = it->get();
if (vox->m_keygroup == kg) {
vox->kill();
it = m_keyoffVoxs.erase(it);
continue;
}
++it;
2017-02-27 05:24:58 +00:00
}
2018-12-08 05:20:09 +00:00
}
2016-05-19 10:12:32 +00:00
}
2018-12-08 05:20:09 +00:00
void Sequencer::killKeygroup(uint8_t kg, bool now) {
for (auto& chan : m_chanStates)
if (chan)
chan.killKeygroup(kg, now);
}
2018-12-08 05:20:09 +00:00
ObjToken<Voice> Sequencer::ChannelState::findVoice(int vid) {
for (const auto& vox : m_chanVoxs)
if (vox.second->vid() == vid)
return vox.second;
for (const auto& vox : m_keyoffVoxs)
if (vox->vid() == vid)
return vox;
return {};
}
2018-12-08 05:20:09 +00:00
ObjToken<Voice> Sequencer::findVoice(int vid) {
for (auto& chan : m_chanStates) {
if (chan) {
ObjToken<Voice> ret = chan.findVoice(vid);
if (ret)
return ret;
2017-02-27 05:24:58 +00:00
}
2018-12-08 05:20:09 +00:00
}
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);
}
for (const auto& v : m_keyoffVoxs) {
Voice* vox = v.get();
if (vox->getObjectId() == macroId)
vox->message(val);
}
}
void Sequencer::sendMacroMessage(ObjectId macroId, int32_t val) {
for (auto& chan : m_chanStates)
if (chan)
chan.sendMacroMessage(macroId, val);
}
void Sequencer::playSong(const unsigned char* arrData, bool loop, bool dieOnEnd) {
m_arrData = arrData;
m_dieOnEnd = dieOnEnd;
m_songState.initialize(arrData, loop);
setTempo(m_songState.getInitialTempo() * 384 / 60.0);
m_state = SequencerState::Playing;
}
void Sequencer::stopSong(float fadeTime, bool now) {
if (fadeTime == 0.f) {
allOff(now);
m_arrData = nullptr;
m_state = SequencerState::Interactive;
} else {
m_stopFadeTime = fadeTime;
m_stopFadeBeginVol = m_curVol;
}
}
void Sequencer::ChannelState::setVolume(float vol) {
m_curVol = vol;
float voxVol = m_parent->m_curVol * m_curVol;
for (const auto& v : m_chanVoxs) {
Voice* vox = v.second.get();
vox->setVolume(voxVol);
}
for (const auto& v : m_keyoffVoxs) {
Voice* vox = v.get();
vox->setVolume(voxVol);
}
}
void Sequencer::ChannelState::setPan(float pan) {
m_curPan = pan;
for (const auto& v : m_chanVoxs) {
Voice* vox = v.second.get();
vox->setPan(m_curPan);
}
for (const auto& v : m_keyoffVoxs) {
Voice* vox = v.get();
vox->setPan(m_curPan);
}
}
void Sequencer::setVolume(float vol, float fadeTime) {
if (fadeTime == 0.f) {
m_curVol = vol;
for (auto& chan : m_chanStates)
if (chan)
chan.setVolume(chan.m_curVol);
} else {
m_volFadeTime = fadeTime;
m_volFadeTarget = vol;
m_volFadeStart = m_curVol;
}
}
2018-12-08 05:20:09 +00:00
int8_t Sequencer::getChanProgram(int8_t chanId) const {
if (static_cast<size_t>(chanId) >= m_chanStates.size()) {
2018-12-08 05:20:09 +00:00
return 0;
}
2018-12-08 05:20:09 +00:00
if (!m_chanStates[chanId]) {
if (!m_midiSetup) {
2018-12-08 05:20:09 +00:00
return 0;
}
2018-12-08 05:20:09 +00:00
return m_midiSetup[chanId].programNo;
}
2018-12-08 05:20:09 +00:00
return m_chanStates[chanId].m_curProgram;
}
2018-12-08 05:20:09 +00:00
bool Sequencer::setChanProgram(int8_t chanId, int8_t prog) {
if (static_cast<size_t>(chanId) >= m_chanStates.size()) {
2018-12-08 05:20:09 +00:00
return false;
}
if (!m_chanStates[chanId]) {
2018-12-08 05:20:09 +00:00
m_chanStates[chanId] = ChannelState(*this, chanId);
}
2018-12-08 05:20:09 +00:00
return m_chanStates[chanId].programChange(prog);
}
2018-12-08 05:20:09 +00:00
void Sequencer::nextChanProgram(int8_t chanId) {
if (static_cast<size_t>(chanId) >= m_chanStates.size()) {
2018-12-08 05:20:09 +00:00
return;
}
if (!m_chanStates[chanId]) {
2018-12-08 05:20:09 +00:00
m_chanStates[chanId] = ChannelState(*this, chanId);
}
2018-12-08 05:20:09 +00:00
return m_chanStates[chanId].nextProgram();
}
2018-12-08 05:20:09 +00:00
void Sequencer::prevChanProgram(int8_t chanId) {
if (static_cast<size_t>(chanId) >= m_chanStates.size()) {
2018-12-08 05:20:09 +00:00
return;
}
if (!m_chanStates[chanId]) {
2018-12-08 05:20:09 +00:00
m_chanStates[chanId] = ChannelState(*this, chanId);
}
2018-12-08 05:20:09 +00:00
return m_chanStates[chanId].prevProgram();
}
2018-12-08 05:20:09 +00:00
} // namespace amuse