mirror of https://github.com/AxioDL/amuse.git
Initial SongState, timing is hilariously wrong ATM
This commit is contained in:
parent
aedbc72766
commit
3433a70462
|
@ -10,6 +10,7 @@ set(SOURCES
|
||||||
lib/Listener.cpp
|
lib/Listener.cpp
|
||||||
lib/Sequencer.cpp
|
lib/Sequencer.cpp
|
||||||
lib/SoundMacroState.cpp
|
lib/SoundMacroState.cpp
|
||||||
|
lib/SongState.cpp
|
||||||
lib/Voice.cpp
|
lib/Voice.cpp
|
||||||
lib/Submix.cpp
|
lib/Submix.cpp
|
||||||
lib/EffectBase.cpp
|
lib/EffectBase.cpp
|
||||||
|
@ -32,6 +33,7 @@ set(HEADERS
|
||||||
include/amuse/Listener.hpp
|
include/amuse/Listener.hpp
|
||||||
include/amuse/Sequencer.hpp
|
include/amuse/Sequencer.hpp
|
||||||
include/amuse/SoundMacroState.hpp
|
include/amuse/SoundMacroState.hpp
|
||||||
|
include/amuse/SongState.hpp
|
||||||
include/amuse/Voice.hpp
|
include/amuse/Voice.hpp
|
||||||
include/amuse/Submix.hpp
|
include/amuse/Submix.hpp
|
||||||
include/amuse/IBackendSubmix.hpp
|
include/amuse/IBackendSubmix.hpp
|
||||||
|
|
|
@ -157,7 +157,7 @@ struct AppCallback : boo::IApplicationCallback
|
||||||
|
|
||||||
/* Amuse engine */
|
/* Amuse engine */
|
||||||
std::experimental::optional<amuse::Engine> m_engine;
|
std::experimental::optional<amuse::Engine> m_engine;
|
||||||
int m_groupId;
|
int m_groupId = -1;
|
||||||
bool m_sfxGroup;
|
bool m_sfxGroup;
|
||||||
|
|
||||||
/* Song playback selection */
|
/* Song playback selection */
|
||||||
|
@ -166,6 +166,7 @@ struct AppCallback : boo::IApplicationCallback
|
||||||
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;
|
||||||
|
std::unique_ptr<uint8_t[]> m_arrData;
|
||||||
|
|
||||||
/* SFX playback selection */
|
/* SFX playback selection */
|
||||||
int m_sfxId = -1;
|
int m_sfxId = -1;
|
||||||
|
@ -204,6 +205,10 @@ struct AppCallback : boo::IApplicationCallback
|
||||||
m_seq->allOff();
|
m_seq->allOff();
|
||||||
m_seq = m_engine->seqPlay(m_groupId, setupId, nullptr);
|
m_seq = m_engine->seqPlay(m_groupId, setupId, nullptr);
|
||||||
m_seq->setVolume(m_volume);
|
m_seq->setVolume(m_volume);
|
||||||
|
|
||||||
|
if (m_arrData)
|
||||||
|
m_seq->playSong(m_arrData.get(), false);
|
||||||
|
|
||||||
UpdateSongDisplay();
|
UpdateSongDisplay();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -225,7 +230,18 @@ 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);
|
{
|
||||||
|
if (m_setupId == -1)
|
||||||
|
SelectSong(setupIt->first);
|
||||||
|
else
|
||||||
|
{
|
||||||
|
while (setupIt != sortEntries.cend() && setupIt->first != m_setupId)
|
||||||
|
++setupIt;
|
||||||
|
if (setupIt == sortEntries.cend())
|
||||||
|
setupIt = sortEntries.cbegin();
|
||||||
|
SelectSong(setupIt->first);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
while (m_running)
|
while (m_running)
|
||||||
{
|
{
|
||||||
|
@ -273,7 +289,7 @@ struct AppCallback : boo::IApplicationCallback
|
||||||
UpdateSongDisplay();
|
UpdateSongDisplay();
|
||||||
}
|
}
|
||||||
|
|
||||||
m_engine->pumpEngine();
|
m_engine->pumpEngine(1.0 / 60.0);
|
||||||
|
|
||||||
size_t voxCount;
|
size_t voxCount;
|
||||||
if (m_seq)
|
if (m_seq)
|
||||||
|
@ -359,7 +375,7 @@ struct AppCallback : boo::IApplicationCallback
|
||||||
UpdateSFXDisplay();
|
UpdateSFXDisplay();
|
||||||
}
|
}
|
||||||
|
|
||||||
m_engine->pumpEngine();
|
m_engine->pumpEngine(1.0 / 60.0);
|
||||||
|
|
||||||
if (m_vox && m_vox->state() == amuse::VoiceState::Dead)
|
if (m_vox && m_vox->state() == amuse::VoiceState::Dead)
|
||||||
{
|
{
|
||||||
|
@ -411,6 +427,7 @@ struct AppCallback : boo::IApplicationCallback
|
||||||
m_seq->nextChanProgram(m_chanId);
|
m_seq->nextChanProgram(m_chanId);
|
||||||
m_updateDisp = true;
|
m_updateDisp = true;
|
||||||
break;
|
break;
|
||||||
|
default: break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -640,12 +657,47 @@ struct AppCallback : boo::IApplicationCallback
|
||||||
Log.report(logvisor::Fatal, "incomplete data in args");
|
Log.report(logvisor::Fatal, "incomplete data in args");
|
||||||
Log.report(logvisor::Info, "Found '%s' Audio Group data", desc.c_str());
|
Log.report(logvisor::Info, "Found '%s' Audio Group data", desc.c_str());
|
||||||
|
|
||||||
|
/* Attempt loading song */
|
||||||
|
if (m_argc > 2)
|
||||||
|
{
|
||||||
|
std::experimental::optional<athena::io::FileReader> r;
|
||||||
|
r.emplace(m_argv[m_argc-1], 32 * 1024, false);
|
||||||
|
if (!r->hasError())
|
||||||
|
{
|
||||||
|
uint32_t version = r->readUint32Big();
|
||||||
|
if (version == 0x18)
|
||||||
|
{
|
||||||
|
/* Raw SON data */
|
||||||
|
r->seek(0, athena::SeekOrigin::Begin);
|
||||||
|
m_arrData = r->readUBytes(r->length());
|
||||||
|
}
|
||||||
|
else if (version == 0x2)
|
||||||
|
{
|
||||||
|
/* Retro CSNG data */
|
||||||
|
m_setupId = r->readUint32Big();
|
||||||
|
m_groupId = r->readUint32Big();
|
||||||
|
r->readUint32Big();
|
||||||
|
uint32_t sonLength = r->readUint32Big();
|
||||||
|
m_arrData = r->readUBytes(sonLength);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/* Load project to assemble group list */
|
/* Load project to assemble group list */
|
||||||
amuse::AudioGroupProject proj(data.getProj());
|
amuse::AudioGroupProject proj(data.getProj());
|
||||||
|
|
||||||
/* Get group selection from user */
|
/* Get group selection from user */
|
||||||
size_t totalGroups = proj.sfxGroups().size() + proj.songGroups().size();
|
size_t totalGroups = proj.sfxGroups().size() + proj.songGroups().size();
|
||||||
if (totalGroups > 1)
|
if (m_groupId != -1)
|
||||||
|
{
|
||||||
|
if (proj.getSongGroupIndex(m_groupId))
|
||||||
|
m_sfxGroup = false;
|
||||||
|
else if (proj.getSFXGroupIndex(m_groupId))
|
||||||
|
m_sfxGroup = true;
|
||||||
|
else
|
||||||
|
Log.report(logvisor::Fatal, "unable to find Group %d", m_groupId);
|
||||||
|
}
|
||||||
|
else if (totalGroups > 1)
|
||||||
{
|
{
|
||||||
/* Ask user to specify which group in project */
|
/* Ask user to specify which group in project */
|
||||||
printf("Multiple Audio Groups discovered:\n");
|
printf("Multiple Audio Groups discovered:\n");
|
||||||
|
|
|
@ -58,7 +58,7 @@ public:
|
||||||
IBackendVoiceAllocator& getBackend() {return m_backend;}
|
IBackendVoiceAllocator& getBackend() {return m_backend;}
|
||||||
|
|
||||||
/** Update all active audio entities and fill OS audio buffers as needed */
|
/** Update all active audio entities and fill OS audio buffers as needed */
|
||||||
void pumpEngine();
|
void pumpEngine(double dt);
|
||||||
|
|
||||||
/** Add audio group data pointers to engine; must remain resident! */
|
/** Add audio group data pointers to engine; must remain resident! */
|
||||||
const AudioGroup* addAudioGroup(const AudioGroupData& data);
|
const AudioGroup* addAudioGroup(const AudioGroupData& data);
|
||||||
|
|
|
@ -3,6 +3,8 @@
|
||||||
|
|
||||||
#include "Entity.hpp"
|
#include "Entity.hpp"
|
||||||
#include "AudioGroupProject.hpp"
|
#include "AudioGroupProject.hpp"
|
||||||
|
#include "SongState.hpp"
|
||||||
|
#include "optional.hpp"
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
#include <unordered_set>
|
#include <unordered_set>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
@ -30,6 +32,7 @@ class Sequencer : public Entity
|
||||||
std::list<std::shared_ptr<Sequencer>>::iterator m_engineIt; /**< Iterator to self within Engine's list for quick deletion */
|
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 */
|
const unsigned char* m_arrData = nullptr; /**< Current playing arrangement data */
|
||||||
|
SongState m_songState; /**< State of current arrangement playback */
|
||||||
double m_ticksPerSec = 1000.0; /**< Current ticks per second (tempo) for 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 */
|
SequencerState m_state = SequencerState::Interactive; /**< Current high-level state of sequencer */
|
||||||
bool m_dieOnEnd = false; /**< Sequencer will be killed when current arrangement completes */
|
bool m_dieOnEnd = false; /**< Sequencer will be killed when current arrangement completes */
|
||||||
|
@ -53,6 +56,8 @@ class Sequencer : public Entity
|
||||||
int8_t m_ctrlVals[128]; /**< MIDI controller values */
|
int8_t m_ctrlVals[128]; /**< MIDI controller values */
|
||||||
float m_curPitchWheel = 0.f; /**< MIDI pitch-wheel */
|
float m_curPitchWheel = 0.f; /**< MIDI pitch-wheel */
|
||||||
int8_t m_curProgram = 0; /**< MIDI program number */
|
int8_t m_curProgram = 0; /**< MIDI program number */
|
||||||
|
float m_curVol = 1.f; /**< Current volume of channel */
|
||||||
|
float m_curPan = 0.f; /**< Current panning of channel */
|
||||||
|
|
||||||
void _bringOutYourDead();
|
void _bringOutYourDead();
|
||||||
size_t getVoiceCount() const;
|
size_t getVoiceCount() const;
|
||||||
|
@ -64,12 +69,13 @@ class Sequencer : public Entity
|
||||||
void prevProgram();
|
void prevProgram();
|
||||||
void setPitchWheel(float pitchWheel);
|
void setPitchWheel(float pitchWheel);
|
||||||
void setVolume(float vol);
|
void setVolume(float vol);
|
||||||
|
void setPan(float pan);
|
||||||
void allOff();
|
void allOff();
|
||||||
void killKeygroup(uint8_t kg, bool now);
|
void killKeygroup(uint8_t kg, bool now);
|
||||||
std::shared_ptr<Voice> findVoice(int vid);
|
std::shared_ptr<Voice> findVoice(int vid);
|
||||||
void sendMacroMessage(ObjectId macroId, int32_t val);
|
void sendMacroMessage(ObjectId macroId, int32_t val);
|
||||||
};
|
};
|
||||||
std::unordered_map<uint8_t, std::unique_ptr<ChannelState>> m_chanStates; /**< Lazily-allocated channel states */
|
std::array<std::experimental::optional<ChannelState>, 16> m_chanStates; /**< Lazily-allocated channel states */
|
||||||
|
|
||||||
void _bringOutYourDead();
|
void _bringOutYourDead();
|
||||||
void _destroy();
|
void _destroy();
|
||||||
|
@ -78,6 +84,9 @@ public:
|
||||||
Sequencer(Engine& engine, const AudioGroup& group, int groupId,
|
Sequencer(Engine& engine, const AudioGroup& group, int groupId,
|
||||||
const SongGroupIndex& songGroup, int setupId, Submix* smx);
|
const SongGroupIndex& songGroup, int setupId, Submix* smx);
|
||||||
|
|
||||||
|
/** Advance current song data (if any) */
|
||||||
|
void advance(double dt);
|
||||||
|
|
||||||
/** Obtain pointer to Sequencer's Submix */
|
/** Obtain pointer to Sequencer's Submix */
|
||||||
Submix* getSubmix() {return m_submix;}
|
Submix* getSubmix() {return m_submix;}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,114 @@
|
||||||
|
#ifndef __AMUSE_SONGSTATE_HPP__
|
||||||
|
#define __AMUSE_SONGSTATE_HPP__
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <vector>
|
||||||
|
#include <list>
|
||||||
|
#include "optional.hpp"
|
||||||
|
#include "Entity.hpp"
|
||||||
|
|
||||||
|
namespace amuse
|
||||||
|
{
|
||||||
|
class Sequencer;
|
||||||
|
|
||||||
|
enum class SongPlayState
|
||||||
|
{
|
||||||
|
Stopped,
|
||||||
|
Playing
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Indexes and temporally plays back commands stored in Song data blobs */
|
||||||
|
class SongState
|
||||||
|
{
|
||||||
|
friend class Voice;
|
||||||
|
|
||||||
|
/** Song header */
|
||||||
|
struct Header
|
||||||
|
{
|
||||||
|
uint32_t m_version;
|
||||||
|
uint32_t m_chanIdxOff;
|
||||||
|
uint32_t m_chanMapOff;
|
||||||
|
uint32_t m_tempoTableOff;
|
||||||
|
uint32_t m_initialTempo;
|
||||||
|
uint32_t m_unkOff;
|
||||||
|
uint32_t m_chanOffs[64];
|
||||||
|
void swapBig();
|
||||||
|
} m_header;
|
||||||
|
|
||||||
|
/** Channel header */
|
||||||
|
struct ChanHeader
|
||||||
|
{
|
||||||
|
uint32_t m_startTick;
|
||||||
|
uint16_t m_unk1;
|
||||||
|
uint16_t m_unk2;
|
||||||
|
uint16_t m_dataIndex;
|
||||||
|
uint16_t m_unk3;
|
||||||
|
uint32_t m_startTick2;
|
||||||
|
uint16_t m_unk4;
|
||||||
|
uint16_t m_unk5;
|
||||||
|
uint16_t m_unk6;
|
||||||
|
uint16_t m_unk7;
|
||||||
|
void swapBig();
|
||||||
|
};
|
||||||
|
|
||||||
|
/** Tempo change entry */
|
||||||
|
struct TempoChange
|
||||||
|
{
|
||||||
|
uint32_t m_tick; /**< Relative song ticks from previous tempo change */
|
||||||
|
uint32_t m_tempo; /**< Tempo value in beats-per-minute (at 384 ticks per quarter-note) */
|
||||||
|
void swapBig();
|
||||||
|
};
|
||||||
|
|
||||||
|
/** State of a single channel within arrangement */
|
||||||
|
struct Channel
|
||||||
|
{
|
||||||
|
struct Header
|
||||||
|
{
|
||||||
|
uint32_t m_type;
|
||||||
|
uint32_t m_pitchOff;
|
||||||
|
uint32_t m_modOff;
|
||||||
|
void swapBig();
|
||||||
|
};
|
||||||
|
|
||||||
|
SongState& m_parent;
|
||||||
|
uint8_t m_midiChan; /**< MIDI channel number of song channel */
|
||||||
|
uint32_t m_startTick; /**< Tick to start execution of channel commands */
|
||||||
|
|
||||||
|
const unsigned char* m_dataBase; /**< Base pointer to command data */
|
||||||
|
const unsigned char* m_data; /**< Pointer to upcoming command data */
|
||||||
|
const unsigned char* m_pitchWheelData = nullptr; /**< Pointer to upcoming pitch data */
|
||||||
|
const unsigned char* m_modWheelData = nullptr; /**< Pointer to upcoming modulation data */
|
||||||
|
uint32_t m_lastPitchTick = 0; /**< Last position of pitch wheel change */
|
||||||
|
int32_t m_lastPitchVal = 0; /**< Last value of pitch */
|
||||||
|
uint32_t m_lastModTick = 0; /**< Last position of mod wheel change */
|
||||||
|
int32_t m_lastModVal = 0; /**< Last value of mod */
|
||||||
|
std::array<uint16_t, 128> m_remNoteLengths = {}; /**< Remaining ticks per note */
|
||||||
|
|
||||||
|
int32_t m_waitCountdown = 0; /**< Current wait in ticks */
|
||||||
|
|
||||||
|
Channel(SongState& parent, uint8_t midiChan, uint32_t startTick,
|
||||||
|
const unsigned char* song, const unsigned char* chan);
|
||||||
|
bool advance(Sequencer& seq, int32_t ticks);
|
||||||
|
};
|
||||||
|
std::array<std::experimental::optional<Channel>, 64> m_channels;
|
||||||
|
|
||||||
|
/** Current pointer to tempo control, iterated over playback */
|
||||||
|
const TempoChange* m_tempoPtr = nullptr;
|
||||||
|
uint32_t m_tempo = 120; /**< Current tempo (beats per minute) */
|
||||||
|
|
||||||
|
uint32_t m_curTick = 0; /**< Current playback position for all channels */
|
||||||
|
SongPlayState m_songState = SongPlayState::Playing; /**< High-level state of Song playback */
|
||||||
|
|
||||||
|
public:
|
||||||
|
/** initialize state for Song data at `ptr` */
|
||||||
|
void initialize(const unsigned char* ptr);
|
||||||
|
|
||||||
|
/** advances `dt` seconds worth of commands in the Song
|
||||||
|
* @return `true` if END reached
|
||||||
|
*/
|
||||||
|
bool advance(Sequencer& seq, double dt);
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif // __AMUSE_SONGSTATE_HPP__
|
|
@ -153,8 +153,11 @@ void Engine::_bringOutYourDead()
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Update all active audio entities and fill OS audio buffers as needed */
|
/** Update all active audio entities and fill OS audio buffers as needed */
|
||||||
void Engine::pumpEngine()
|
void Engine::pumpEngine(double dt)
|
||||||
{
|
{
|
||||||
|
for (std::shared_ptr<Sequencer>& seq : m_activeSequencers)
|
||||||
|
seq->advance(dt);
|
||||||
|
|
||||||
m_backend.pumpAndMixVoices();
|
m_backend.pumpAndMixVoices();
|
||||||
_bringOutYourDead();
|
_bringOutYourDead();
|
||||||
|
|
||||||
|
|
|
@ -36,7 +36,8 @@ void Sequencer::ChannelState::_bringOutYourDead()
|
||||||
void Sequencer::_bringOutYourDead()
|
void Sequencer::_bringOutYourDead()
|
||||||
{
|
{
|
||||||
for (auto& chan : m_chanStates)
|
for (auto& chan : m_chanStates)
|
||||||
chan.second->_bringOutYourDead();
|
if (chan)
|
||||||
|
chan->_bringOutYourDead();
|
||||||
|
|
||||||
if (!m_arrData && m_dieOnEnd && getVoiceCount() == 0)
|
if (!m_arrData && m_dieOnEnd && getVoiceCount() == 0)
|
||||||
m_state = SequencerState::Dead;
|
m_state = SequencerState::Dead;
|
||||||
|
@ -53,11 +54,13 @@ void Sequencer::_destroy()
|
||||||
|
|
||||||
for (auto& chan : m_chanStates)
|
for (auto& chan : m_chanStates)
|
||||||
{
|
{
|
||||||
ChannelState& st = *chan.second;
|
if (chan)
|
||||||
if (st.m_submix)
|
|
||||||
{
|
{
|
||||||
m_engine.removeSubmix(st.m_submix);
|
if (chan->m_submix)
|
||||||
st.m_submix = nullptr;
|
{
|
||||||
|
m_engine.removeSubmix(chan->m_submix);
|
||||||
|
chan->m_submix = nullptr;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -100,6 +103,20 @@ Sequencer::ChannelState::ChannelState(Sequencer& parent, uint8_t chanId)
|
||||||
m_submix->makeReverbStd(0.5f, m_setup.reverb / 127.f, 5.f, 0.5f, 0.f);
|
m_submix->makeReverbStd(0.5f, m_setup.reverb / 127.f, 5.f, 0.5f, 0.f);
|
||||||
if (m_setup.chorus)
|
if (m_setup.chorus)
|
||||||
m_submix->makeChorus(15, m_setup.chorus * 5 / 127, 5000);
|
m_submix->makeChorus(15, m_setup.chorus * 5 / 127, 5000);
|
||||||
|
|
||||||
|
m_curVol = m_setup.volume / 127.f;
|
||||||
|
m_curPan = m_setup.panning / 64.f - 1.f;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Sequencer::advance(double dt)
|
||||||
|
{
|
||||||
|
if (m_state == SequencerState::Playing)
|
||||||
|
if (m_songState.advance(*this, dt))
|
||||||
|
{
|
||||||
|
m_arrData = nullptr;
|
||||||
|
m_state = SequencerState::Interactive;
|
||||||
|
allOff();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t Sequencer::ChannelState::getVoiceCount() const
|
size_t Sequencer::ChannelState::getVoiceCount() const
|
||||||
|
@ -116,7 +133,8 @@ 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)
|
||||||
ret += chan.second->getVoiceCount();
|
if (chan)
|
||||||
|
ret += chan->getVoiceCount();
|
||||||
return ret;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -135,8 +153,8 @@ std::shared_ptr<Voice> Sequencer::ChannelState::keyOn(uint8_t note, uint8_t velo
|
||||||
m_parent.m_engine._destroyVoice(ret.get());
|
m_parent.m_engine._destroyVoice(ret.get());
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
ret->setVolume(m_parent.m_curVol * m_setup.volume / 127.f);
|
ret->setVolume(m_parent.m_curVol * m_curVol);
|
||||||
ret->setPan(m_setup.panning / 64.f - 127.f);
|
ret->setPan(m_curPan);
|
||||||
ret->setPitchWheel(m_curPitchWheel);
|
ret->setPitchWheel(m_curPitchWheel);
|
||||||
|
|
||||||
if (m_ctrlVals[64] > 64)
|
if (m_ctrlVals[64] > 64)
|
||||||
|
@ -147,14 +165,13 @@ std::shared_ptr<Voice> Sequencer::ChannelState::keyOn(uint8_t note, uint8_t velo
|
||||||
|
|
||||||
std::shared_ptr<Voice> Sequencer::keyOn(uint8_t chan, uint8_t note, uint8_t velocity)
|
std::shared_ptr<Voice> Sequencer::keyOn(uint8_t chan, uint8_t note, uint8_t velocity)
|
||||||
{
|
{
|
||||||
auto chanSearch = m_chanStates.find(chan);
|
if (chan > 15)
|
||||||
if (chanSearch == m_chanStates.cend())
|
return {};
|
||||||
{
|
|
||||||
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);
|
if (!m_chanStates[chan])
|
||||||
|
m_chanStates[chan].emplace(*this, chan);
|
||||||
|
|
||||||
|
return m_chanStates[chan]->keyOn(note, velocity);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Sequencer::ChannelState::keyOff(uint8_t note, uint8_t velocity)
|
void Sequencer::ChannelState::keyOff(uint8_t note, uint8_t velocity)
|
||||||
|
@ -170,11 +187,10 @@ void Sequencer::ChannelState::keyOff(uint8_t note, uint8_t velocity)
|
||||||
|
|
||||||
void Sequencer::keyOff(uint8_t chan, uint8_t note, uint8_t velocity)
|
void Sequencer::keyOff(uint8_t chan, uint8_t note, uint8_t velocity)
|
||||||
{
|
{
|
||||||
auto chanSearch = m_chanStates.find(chan);
|
if (chan > 15 || !m_chanStates[chan])
|
||||||
if (chanSearch == m_chanStates.cend())
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
chanSearch->second->keyOff(note, velocity);
|
m_chanStates[chan]->keyOff(note, velocity);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Sequencer::ChannelState::setCtrlValue(uint8_t ctrl, int8_t val)
|
void Sequencer::ChannelState::setCtrlValue(uint8_t ctrl, int8_t val)
|
||||||
|
@ -184,6 +200,11 @@ void Sequencer::ChannelState::setCtrlValue(uint8_t ctrl, int8_t val)
|
||||||
vox.second->notifyCtrlChange(ctrl, val);
|
vox.second->notifyCtrlChange(ctrl, val);
|
||||||
for (const auto& vox : m_keyoffVoxs)
|
for (const auto& vox : m_keyoffVoxs)
|
||||||
vox->notifyCtrlChange(ctrl, val);
|
vox->notifyCtrlChange(ctrl, val);
|
||||||
|
|
||||||
|
if (ctrl == 7)
|
||||||
|
setVolume(val / 127.f);
|
||||||
|
else if (ctrl == 8)
|
||||||
|
setPan(val / 64.f - 1.f);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Sequencer::ChannelState::programChange(int8_t prog)
|
bool Sequencer::ChannelState::programChange(int8_t prog)
|
||||||
|
@ -229,11 +250,13 @@ void Sequencer::ChannelState::prevProgram()
|
||||||
|
|
||||||
void Sequencer::setCtrlValue(uint8_t chan, uint8_t ctrl, int8_t val)
|
void Sequencer::setCtrlValue(uint8_t chan, uint8_t ctrl, int8_t val)
|
||||||
{
|
{
|
||||||
auto chanSearch = m_chanStates.find(chan);
|
if (chan > 15)
|
||||||
if (chanSearch == m_chanStates.cend())
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
chanSearch->second->setCtrlValue(ctrl, val);
|
if (!m_chanStates[chan])
|
||||||
|
m_chanStates[chan].emplace(*this, chan);
|
||||||
|
|
||||||
|
m_chanStates[chan]->setCtrlValue(ctrl, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Sequencer::ChannelState::setPitchWheel(float pitchWheel)
|
void Sequencer::ChannelState::setPitchWheel(float pitchWheel)
|
||||||
|
@ -247,11 +270,13 @@ void Sequencer::ChannelState::setPitchWheel(float pitchWheel)
|
||||||
|
|
||||||
void Sequencer::setPitchWheel(uint8_t chan, float pitchWheel)
|
void Sequencer::setPitchWheel(uint8_t chan, float pitchWheel)
|
||||||
{
|
{
|
||||||
auto chanSearch = m_chanStates.find(chan);
|
if (chan > 15)
|
||||||
if (chanSearch == m_chanStates.cend())
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
chanSearch->second->setPitchWheel(pitchWheel);
|
if (!m_chanStates[chan])
|
||||||
|
m_chanStates[chan].emplace(*this, chan);
|
||||||
|
|
||||||
|
m_chanStates[chan]->setPitchWheel(pitchWheel);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Sequencer::setTempo(double ticksPerSec)
|
void Sequencer::setTempo(double ticksPerSec)
|
||||||
|
@ -270,16 +295,20 @@ void Sequencer::allOff(bool now)
|
||||||
if (now)
|
if (now)
|
||||||
for (auto& chan : m_chanStates)
|
for (auto& chan : m_chanStates)
|
||||||
{
|
{
|
||||||
for (const auto& vox : chan.second->m_chanVoxs)
|
if (chan)
|
||||||
m_engine._destroyVoice(vox.second.get());
|
{
|
||||||
for (const auto& vox : chan.second->m_keyoffVoxs)
|
for (const auto& vox : chan->m_chanVoxs)
|
||||||
m_engine._destroyVoice(vox.get());
|
m_engine._destroyVoice(vox.second.get());
|
||||||
chan.second->m_chanVoxs.clear();
|
for (const auto& vox : chan->m_keyoffVoxs)
|
||||||
chan.second->m_keyoffVoxs.clear();
|
m_engine._destroyVoice(vox.get());
|
||||||
|
chan->m_chanVoxs.clear();
|
||||||
|
chan->m_keyoffVoxs.clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
for (auto& chan : m_chanStates)
|
for (auto& chan : m_chanStates)
|
||||||
chan.second->allOff();
|
if (chan)
|
||||||
|
chan->allOff();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Sequencer::ChannelState::killKeygroup(uint8_t kg, bool now)
|
void Sequencer::ChannelState::killKeygroup(uint8_t kg, bool now)
|
||||||
|
@ -317,7 +346,8 @@ void Sequencer::ChannelState::killKeygroup(uint8_t kg, bool now)
|
||||||
void Sequencer::killKeygroup(uint8_t kg, bool now)
|
void Sequencer::killKeygroup(uint8_t kg, bool now)
|
||||||
{
|
{
|
||||||
for (auto& chan : m_chanStates)
|
for (auto& chan : m_chanStates)
|
||||||
chan.second->killKeygroup(kg, now);
|
if (chan)
|
||||||
|
chan->killKeygroup(kg, now);
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<Voice> Sequencer::ChannelState::findVoice(int vid)
|
std::shared_ptr<Voice> Sequencer::ChannelState::findVoice(int vid)
|
||||||
|
@ -335,9 +365,12 @@ std::shared_ptr<Voice> Sequencer::findVoice(int vid)
|
||||||
{
|
{
|
||||||
for (auto& chan : m_chanStates)
|
for (auto& chan : m_chanStates)
|
||||||
{
|
{
|
||||||
std::shared_ptr<Voice> ret = chan.second->findVoice(vid);
|
if (chan)
|
||||||
if (ret)
|
{
|
||||||
return ret;
|
std::shared_ptr<Voice> ret = chan->findVoice(vid);
|
||||||
|
if (ret)
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
@ -361,28 +394,46 @@ void Sequencer::ChannelState::sendMacroMessage(ObjectId macroId, int32_t val)
|
||||||
void Sequencer::sendMacroMessage(ObjectId macroId, int32_t val)
|
void Sequencer::sendMacroMessage(ObjectId macroId, int32_t val)
|
||||||
{
|
{
|
||||||
for (auto& chan : m_chanStates)
|
for (auto& chan : m_chanStates)
|
||||||
chan.second->sendMacroMessage(macroId, val);
|
if (chan)
|
||||||
|
chan->sendMacroMessage(macroId, val);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Sequencer::playSong(const unsigned char* arrData, bool dieOnEnd)
|
void Sequencer::playSong(const unsigned char* arrData, bool dieOnEnd)
|
||||||
{
|
{
|
||||||
m_arrData = arrData;
|
m_arrData = arrData;
|
||||||
m_dieOnEnd = dieOnEnd;
|
m_dieOnEnd = dieOnEnd;
|
||||||
|
m_songState.initialize(arrData);
|
||||||
m_state = SequencerState::Playing;
|
m_state = SequencerState::Playing;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Sequencer::ChannelState::setVolume(float vol)
|
void Sequencer::ChannelState::setVolume(float vol)
|
||||||
{
|
{
|
||||||
vol = vol * m_setup.volume / 127.f;
|
m_curVol = vol;
|
||||||
|
float voxVol = m_parent.m_curVol * m_curVol;
|
||||||
for (const auto& v : m_chanVoxs)
|
for (const auto& v : m_chanVoxs)
|
||||||
{
|
{
|
||||||
Voice* vox = v.second.get();
|
Voice* vox = v.second.get();
|
||||||
vox->setVolume(vol);
|
vox->setVolume(voxVol);
|
||||||
}
|
}
|
||||||
for (const auto& v : m_keyoffVoxs)
|
for (const auto& v : m_keyoffVoxs)
|
||||||
{
|
{
|
||||||
Voice* vox = v.get();
|
Voice* vox = v.get();
|
||||||
vox->setVolume(vol);
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -390,43 +441,49 @@ void Sequencer::setVolume(float vol)
|
||||||
{
|
{
|
||||||
m_curVol = vol;
|
m_curVol = vol;
|
||||||
for (auto& chan : m_chanStates)
|
for (auto& chan : m_chanStates)
|
||||||
chan.second->setVolume(vol);
|
if (chan)
|
||||||
|
chan->setVolume(chan->m_curVol);
|
||||||
}
|
}
|
||||||
|
|
||||||
int8_t Sequencer::getChanProgram(int8_t chanId) const
|
int8_t Sequencer::getChanProgram(int8_t chanId) const
|
||||||
{
|
{
|
||||||
auto chanSearch = m_chanStates.find(chanId);
|
if (chanId > 15 || !m_chanStates[chanId])
|
||||||
if (chanSearch == m_chanStates.cend())
|
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
return chanSearch->second->m_curProgram;
|
return m_chanStates[chanId]->m_curProgram;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool Sequencer::setChanProgram(int8_t chanId, int8_t prog)
|
bool Sequencer::setChanProgram(int8_t chanId, int8_t prog)
|
||||||
{
|
{
|
||||||
auto chanSearch = m_chanStates.find(chanId);
|
if (chanId > 15)
|
||||||
if (chanSearch == m_chanStates.cend())
|
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return chanSearch->second->programChange(prog);
|
if (!m_chanStates[chanId])
|
||||||
|
m_chanStates[chanId].emplace(*this, chanId);
|
||||||
|
|
||||||
|
return m_chanStates[chanId]->programChange(prog);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Sequencer::nextChanProgram(int8_t chanId)
|
void Sequencer::nextChanProgram(int8_t chanId)
|
||||||
{
|
{
|
||||||
auto chanSearch = m_chanStates.find(chanId);
|
if (chanId > 15)
|
||||||
if (chanSearch == m_chanStates.cend())
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
return chanSearch->second->nextProgram();
|
if (!m_chanStates[chanId])
|
||||||
|
m_chanStates[chanId].emplace(*this, chanId);
|
||||||
|
|
||||||
|
return m_chanStates[chanId]->nextProgram();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Sequencer::prevChanProgram(int8_t chanId)
|
void Sequencer::prevChanProgram(int8_t chanId)
|
||||||
{
|
{
|
||||||
auto chanSearch = m_chanStates.find(chanId);
|
if (chanId > 15)
|
||||||
if (chanSearch == m_chanStates.cend())
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
return chanSearch->second->prevProgram();
|
if (!m_chanStates[chanId])
|
||||||
|
m_chanStates[chanId].emplace(*this, chanId);
|
||||||
|
|
||||||
|
return m_chanStates[chanId]->prevProgram();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,348 @@
|
||||||
|
#include "amuse/SongState.hpp"
|
||||||
|
#include "amuse/Common.hpp"
|
||||||
|
#include "amuse/Sequencer.hpp"
|
||||||
|
|
||||||
|
namespace amuse
|
||||||
|
{
|
||||||
|
|
||||||
|
static uint32_t DecodeRLE(const unsigned char*& data)
|
||||||
|
{
|
||||||
|
uint32_t ret = 0;
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
uint32_t thisPart = *data & 0x7f;
|
||||||
|
if (*data & 0x80)
|
||||||
|
{
|
||||||
|
++data;
|
||||||
|
thisPart = thisPart * 256 + *data;
|
||||||
|
if (thisPart == 0)
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (thisPart == 32767)
|
||||||
|
{
|
||||||
|
ret += 32767;
|
||||||
|
data += 2;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret += thisPart;
|
||||||
|
data += 1;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int32_t DecodeContinuousRLE(const unsigned char*& data)
|
||||||
|
{
|
||||||
|
int32_t ret = int32_t(DecodeRLE(data));
|
||||||
|
if (ret >= 16384)
|
||||||
|
return ret - 32767;
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
static uint32_t DecodeTimeRLE(const unsigned char*& data)
|
||||||
|
{
|
||||||
|
uint32_t ret = 0;
|
||||||
|
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
uint16_t thisPart = SBig(*reinterpret_cast<const uint16_t*>(data));
|
||||||
|
if (thisPart == 0xffff)
|
||||||
|
{
|
||||||
|
ret += 65535;
|
||||||
|
data += 4;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ret += thisPart;
|
||||||
|
data += 2;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SongState::Header::swapBig()
|
||||||
|
{
|
||||||
|
m_version = SBig(m_version);
|
||||||
|
m_chanIdxOff = SBig(m_chanIdxOff);
|
||||||
|
m_chanMapOff = SBig(m_chanMapOff);
|
||||||
|
m_tempoTableOff = SBig(m_tempoTableOff);
|
||||||
|
m_initialTempo = SBig(m_initialTempo);
|
||||||
|
m_unkOff = SBig(m_unkOff);
|
||||||
|
for (int i=0 ; i<64 ; ++i)
|
||||||
|
m_chanOffs[i] = SBig(m_chanOffs[i]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SongState::ChanHeader::swapBig()
|
||||||
|
{
|
||||||
|
m_startTick = SBig(m_startTick);
|
||||||
|
m_unk1 = SBig(m_unk1);
|
||||||
|
m_unk2 = SBig(m_unk2);
|
||||||
|
m_dataIndex = SBig(m_dataIndex);
|
||||||
|
m_unk3 = SBig(m_unk3);
|
||||||
|
m_startTick2 = SBig(m_startTick2);
|
||||||
|
m_unk4 = SBig(m_unk4);
|
||||||
|
m_unk5 = SBig(m_unk5);
|
||||||
|
m_unk6 = SBig(m_unk6);
|
||||||
|
m_unk7 = SBig(m_unk7);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SongState::TempoChange::swapBig()
|
||||||
|
{
|
||||||
|
m_tick = SBig(m_tick);
|
||||||
|
m_tempo = SBig(m_tempo);
|
||||||
|
}
|
||||||
|
|
||||||
|
void SongState::Channel::Header::swapBig()
|
||||||
|
{
|
||||||
|
m_type = SBig(m_type);
|
||||||
|
m_pitchOff = SBig(m_pitchOff);
|
||||||
|
m_modOff = SBig(m_modOff);
|
||||||
|
}
|
||||||
|
|
||||||
|
SongState::Channel::Channel(SongState& parent, uint8_t midiChan, uint32_t startTick,
|
||||||
|
const unsigned char* song, const unsigned char* chan)
|
||||||
|
: m_parent(parent), m_midiChan(midiChan), m_startTick(startTick), m_dataBase(chan + 12)
|
||||||
|
{
|
||||||
|
m_data = m_dataBase;
|
||||||
|
|
||||||
|
Header header = *reinterpret_cast<const Header*>(chan);
|
||||||
|
header.swapBig();
|
||||||
|
if (header.m_type != 8)
|
||||||
|
{
|
||||||
|
m_data = nullptr;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (header.m_pitchOff)
|
||||||
|
m_pitchWheelData = song + header.m_pitchOff;
|
||||||
|
if (header.m_modOff)
|
||||||
|
m_modWheelData = song + header.m_modOff;
|
||||||
|
|
||||||
|
m_waitCountdown = startTick;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SongState::initialize(const unsigned char* ptr)
|
||||||
|
{
|
||||||
|
m_header = *reinterpret_cast<const Header*>(ptr);
|
||||||
|
m_header.swapBig();
|
||||||
|
|
||||||
|
/* Initialize all channels */
|
||||||
|
for (int i=0 ; i<64 ; ++i)
|
||||||
|
{
|
||||||
|
if (m_header.m_chanOffs[i])
|
||||||
|
{
|
||||||
|
ChanHeader cHeader = *reinterpret_cast<const ChanHeader*>(ptr + m_header.m_chanOffs[i]);
|
||||||
|
cHeader.swapBig();
|
||||||
|
const uint32_t* chanIdx = reinterpret_cast<const uint32_t*>(ptr + m_header.m_chanIdxOff);
|
||||||
|
const uint8_t* chanMap = reinterpret_cast<const uint8_t*>(ptr + m_header.m_chanMapOff);
|
||||||
|
m_channels[i].emplace(*this, chanMap[i], cHeader.m_startTick, ptr,
|
||||||
|
ptr + SBig(chanIdx[cHeader.m_dataIndex]));
|
||||||
|
}
|
||||||
|
else
|
||||||
|
m_channels[i] = std::experimental::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Initialize tempo */
|
||||||
|
if (m_header.m_tempoTableOff)
|
||||||
|
m_tempoPtr = reinterpret_cast<const TempoChange*>(ptr + m_header.m_tempoTableOff);
|
||||||
|
else
|
||||||
|
m_tempoPtr = nullptr;
|
||||||
|
|
||||||
|
m_tempo = m_header.m_initialTempo;
|
||||||
|
m_curTick = 0;
|
||||||
|
m_songState = SongPlayState::Playing;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SongState::Channel::advance(Sequencer& seq, int32_t ticks)
|
||||||
|
{
|
||||||
|
if (!m_data)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
int32_t endTick = m_parent.m_curTick + ticks;
|
||||||
|
|
||||||
|
/* Update continuous pitch data */
|
||||||
|
if (m_pitchWheelData)
|
||||||
|
{
|
||||||
|
int32_t pitchTick = m_parent.m_curTick;
|
||||||
|
int32_t remPitchTicks = ticks;
|
||||||
|
while (pitchTick < endTick)
|
||||||
|
{
|
||||||
|
/* See if there's an upcoming pitch change in this interval */
|
||||||
|
const unsigned char* ptr = m_pitchWheelData;
|
||||||
|
uint32_t deltaTicks = DecodeRLE(ptr);
|
||||||
|
if (deltaTicks != -1)
|
||||||
|
{
|
||||||
|
uint32_t nextTick = m_lastPitchTick + deltaTicks;
|
||||||
|
if (pitchTick + remPitchTicks > nextTick)
|
||||||
|
{
|
||||||
|
/* Update pitch */
|
||||||
|
int32_t pitchDelta = DecodeContinuousRLE(ptr);
|
||||||
|
m_lastPitchVal += pitchDelta;
|
||||||
|
m_pitchWheelData = ptr;
|
||||||
|
m_lastPitchTick = nextTick;
|
||||||
|
remPitchTicks -= (nextTick - pitchTick);
|
||||||
|
pitchTick = nextTick;
|
||||||
|
seq.setPitchWheel(m_midiChan, clamp(-1.f, m_lastPitchVal / 8192.f, 1.f));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
remPitchTicks -= (nextTick - pitchTick);
|
||||||
|
pitchTick = nextTick;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Update continuous modulation data */
|
||||||
|
if (m_modWheelData)
|
||||||
|
{
|
||||||
|
int32_t modTick = m_parent.m_curTick;
|
||||||
|
int32_t remModTicks = ticks;
|
||||||
|
while (modTick < endTick)
|
||||||
|
{
|
||||||
|
/* See if there's an upcoming modulation change in this interval */
|
||||||
|
const unsigned char* ptr = m_modWheelData;
|
||||||
|
uint32_t deltaTicks = DecodeRLE(ptr);
|
||||||
|
if (deltaTicks != -1)
|
||||||
|
{
|
||||||
|
uint32_t nextTick = m_lastModTick + deltaTicks;
|
||||||
|
if (modTick + remModTicks > nextTick)
|
||||||
|
{
|
||||||
|
/* Update modulation */
|
||||||
|
int32_t modDelta = DecodeContinuousRLE(ptr);
|
||||||
|
m_lastModVal += modDelta;
|
||||||
|
m_modWheelData = ptr;
|
||||||
|
m_lastModTick = nextTick;
|
||||||
|
remModTicks -= (nextTick - modTick);
|
||||||
|
modTick = nextTick;
|
||||||
|
seq.setCtrlValue(m_midiChan, 1, clamp(0, (m_lastModVal + 8192) * 128 / 16384, 127));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
remModTicks -= (nextTick - modTick);
|
||||||
|
modTick = nextTick;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Stop finished notes */
|
||||||
|
for (int i=0 ; i<128 ; ++i)
|
||||||
|
{
|
||||||
|
if (m_remNoteLengths[i])
|
||||||
|
{
|
||||||
|
if (m_remNoteLengths[i] <= ticks)
|
||||||
|
{
|
||||||
|
seq.keyOff(m_midiChan, i, 0);
|
||||||
|
m_remNoteLengths[i] = 0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
m_remNoteLengths[i] -= ticks;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Bootstrap first delta-time */
|
||||||
|
if (m_data == m_dataBase)
|
||||||
|
m_waitCountdown = DecodeTimeRLE(m_data);
|
||||||
|
|
||||||
|
/* Loop through as many commands as we can for this time period */
|
||||||
|
while (true)
|
||||||
|
{
|
||||||
|
/* Advance wait timer if active, returning if waiting */
|
||||||
|
if (m_waitCountdown)
|
||||||
|
{
|
||||||
|
m_waitCountdown -= ticks;
|
||||||
|
if (m_waitCountdown <= 0)
|
||||||
|
m_waitCountdown = 0;
|
||||||
|
else
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Load next command */
|
||||||
|
if (*reinterpret_cast<const uint16_t*>(m_data) == 0xffff)
|
||||||
|
{
|
||||||
|
/* End of channel */
|
||||||
|
m_data = nullptr;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
else if (m_data[0] & 0x80)
|
||||||
|
{
|
||||||
|
/* Control change */
|
||||||
|
uint8_t val = m_data[0] & 0x7f;
|
||||||
|
uint8_t ctrl = m_data[1] & 0x7f;
|
||||||
|
seq.setCtrlValue(m_midiChan, ctrl, val);
|
||||||
|
m_data += 2;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
/* Note */
|
||||||
|
uint8_t note = m_data[0] & 0x7f;
|
||||||
|
uint8_t vel = m_data[1] & 0x7f;
|
||||||
|
uint16_t length = SBig(*reinterpret_cast<const uint16_t*>(m_data + 2));
|
||||||
|
seq.keyOn(m_midiChan, note, vel);
|
||||||
|
m_remNoteLengths[note] = length;
|
||||||
|
m_data += 4;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Set next delta-time */
|
||||||
|
m_waitCountdown = DecodeTimeRLE(m_data);
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool SongState::advance(Sequencer& seq, double dt)
|
||||||
|
{
|
||||||
|
/* Stopped */
|
||||||
|
if (m_songState == SongPlayState::Stopped)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
bool done = true;
|
||||||
|
while (dt > 0.0)
|
||||||
|
{
|
||||||
|
/* Compute ticks to compute based on current tempo */
|
||||||
|
int32_t remTicks = dt * m_tempo * 384 / 60;
|
||||||
|
if (!remTicks) remTicks = 1;
|
||||||
|
|
||||||
|
/* See if there's an upcoming tempo change in this interval */
|
||||||
|
if (m_tempoPtr && m_tempoPtr->m_tick != 0xffffffff)
|
||||||
|
{
|
||||||
|
TempoChange change = *m_tempoPtr;
|
||||||
|
change.swapBig();
|
||||||
|
|
||||||
|
if (m_curTick + remTicks > change.m_tick)
|
||||||
|
remTicks = change.m_tick - m_curTick;
|
||||||
|
|
||||||
|
if (remTicks <= 0)
|
||||||
|
{
|
||||||
|
/* Turn over tempo */
|
||||||
|
m_tempo = change.m_tempo;
|
||||||
|
++m_tempoPtr;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Advance all channels */
|
||||||
|
for (std::experimental::optional<Channel>& chan : m_channels)
|
||||||
|
if (chan)
|
||||||
|
done &= chan->advance(seq, remTicks);
|
||||||
|
|
||||||
|
m_curTick += remTicks;
|
||||||
|
|
||||||
|
if (m_tempo == 0)
|
||||||
|
dt = 0.0;
|
||||||
|
else
|
||||||
|
dt -= remTicks / double(m_tempo * 384 / 60);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (done)
|
||||||
|
m_songState = SongPlayState::Stopped;
|
||||||
|
return done;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -6,6 +6,7 @@ namespace amuse
|
||||||
void Submix::_destroy()
|
void Submix::_destroy()
|
||||||
{
|
{
|
||||||
m_destroyed = true;
|
m_destroyed = true;
|
||||||
|
m_backendSubmix.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
Submix::Submix(Engine& engine, Submix* smx)
|
Submix::Submix(Engine& engine, Submix* smx)
|
||||||
|
|
|
@ -18,6 +18,8 @@ void Voice::_destroy()
|
||||||
|
|
||||||
for (std::shared_ptr<Voice>& vox : m_childVoices)
|
for (std::shared_ptr<Voice>& vox : m_childVoices)
|
||||||
vox->_destroy();
|
vox->_destroy();
|
||||||
|
|
||||||
|
m_backendVoice.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
Voice::~Voice()
|
Voice::~Voice()
|
||||||
|
|
Loading…
Reference in New Issue