Some SoundMacro command implementations

This commit is contained in:
Jack Andersen 2016-05-02 19:16:37 -10:00
parent fa8d9038df
commit 338df76711
9 changed files with 580 additions and 35 deletions

View File

@ -46,7 +46,7 @@ int main(int argc, char* argv[])
int sfxId = 0x1337;
float vol = 1.0f;
float pan = 0.0f;
int voiceId = snd.fxStart(sfxId, vol, pan);
Voice* voice = snd.fxStart(sfxId, vol, pan);
/* Play for ~5 sec */
int passedFrames = 0;
@ -57,10 +57,10 @@ int main(int argc, char* argv[])
WaitForVSync();
}
/* Stopping a SoundMacro is accomplished by sending the engine a
/* Stopping a SoundMacro is accomplished by sending a
* MIDI-style 'KeyOff' message for the voice
*/
snd.fxKeyOff(voiceId);
voice.keyOff();
/* Play for 2 more seconds to allow the macro to gracefully fade-out */
passedFrames = 0;

View File

@ -35,7 +35,7 @@ public:
void swapBig();
};
private:
std::vector<Entry> m_entries;
std::vector<std::pair<Entry, ADPCMParms>> m_entries;
public:
AudioGroupSampleDirectory(const unsigned char* data);
};

View File

@ -21,10 +21,10 @@ class Engine
{
IBackendVoiceAllocator& m_backend;
std::unordered_map<int, std::unique_ptr<AudioGroup>> m_audioGroups;
std::list<std::unique_ptr<Voice>> m_activeVoices;
std::list<std::unique_ptr<Emitter>> m_activeEmitters;
std::list<std::unique_ptr<Sequencer>> m_activeSequencers;
Voice* _allocateVoice(int groupId, double sampleRate, bool dynamicPitch);
std::list<Voice> m_activeVoices;
std::list<Emitter> m_activeEmitters;
std::list<Sequencer> m_activeSequencers;
Voice* _allocateVoice(int groupId, double sampleRate, bool dynamicPitch, bool emitter);
AudioGroup* _findGroupFromSfxId(int sfxId, const AudioGroupSampleDirectory::Entry*& entOut) const;
AudioGroup* _findGroupFromSongId(int songId) const;
public:
@ -48,6 +48,9 @@ public:
/** Start song playing from loaded audio groups */
Sequencer* seqPlay(int songId, const unsigned char* arrData);
/** Find voice from VoiceId */
Voice* findVoice(int vid);
};
}

View File

@ -1,11 +1,153 @@
#ifndef __AMUSE_SOUNDMACROSTATE_HPP__
#define __AMUSE_SOUNDMACROSTATE_HPP__
#include <stdint.h>
#include <random>
namespace amuse
{
class Voice;
class SoundMacroState
{
/** SoundMacro header */
struct Header
{
uint32_t m_size;
uint16_t m_macroId;
uint8_t m_volume;
uint8_t m_pan;
void swapBig();
} m_header;
/** SoundMacro command operations */
enum class Op : uint8_t
{
End,
Stop,
SplitKey,
SplitVel,
WaitTicks,
Loop,
Goto,
WaitMs,
PlayMacro,
SendKeyOff,
SplitMod,
PianoPan,
SetAdsr,
ScaleVolume,
Panning,
Envelope,
StartSample,
StopSample,
KeyOff,
SplitRnd,
FadeIn,
Spanning,
SetAdsrCtrl,
RndNote,
AddNote,
SetNote,
LastNote,
Portamento,
Vibrato,
PitchSweep1,
PitchSweep2,
SetPitch,
SetPitchAdsr,
ScaleVolumeDLS,
Mod2Vibrange,
SetupTremolo,
Return,
GoSub,
TrapEvent = 0x28,
SendMessage,
GetMessage,
GetVid,
AddAgeCount = 0x30,
SetAgeCount,
SendFlag,
PitchWheelR,
SetPriority,
AddPriority,
AgeCntSpeed,
AgeCntVel,
VolSelect = 0x40,
PanSelect,
PitchWheelSelect,
ModWheelSelect,
PedalSelect,
PortaSelect,
ReverbSelect,
SpanSelect,
DopplerSelect,
TremoloSelect,
PreASelect,
PreBSelect,
PostBSelect,
AuxAFXSelect,
AuxBFXSelect,
SetupLFO = 0x50,
ModeSelect = 0x58,
SetKeygroup,
SRCmodeSelect,
AddVars = 0x60,
SubVars,
MulVars,
DivVars,
AddIVars,
IfEqual = 0x70,
IfLess,
};
/** SoundMacro command structure */
struct Command
{
Op m_op;
char m_data[7];
void swapBig();
};
const unsigned char* m_ptr = nullptr; /**< pointer to selected SoundMacro data */
std::vector<int> m_pc; /**< 'program counter' stack for the active SoundMacro */
float m_ticksPerSec; /**< ratio for resolving ticks in commands that use them */
uint8_t m_midiKey; /**< Key played for this macro invocation */
uint8_t m_midiVel; /**< Velocity played for this macro invocation */
uint8_t m_midiMod; /**< Modulation played for this macro invocation */
std::linear_congruential_engine<uint32_t, 0x41c64e6d, 0x3039, UINT32_MAX> m_random;
float m_execTime; /**< time in seconds of SoundMacro execution */
bool m_keyoff; /**< keyoff message has been received */
bool m_sampleEnd; /**< sample has finished playback */
bool m_inWait = false; /**< set when timer/keyoff/sampleend wait active */
bool m_keyoffWait = false; /**< set when active wait is a keyoff wait */
bool m_sampleEndWait = false; /**< set when active wait is a sampleend wait */
float m_waitCountdown; /**< countdown timer for active wait */
int m_loopCountdown = -1; /**< countdown for current loop */
int m_lastPlayMacroVid = -1; /**< VoiceId from last PlayMacro command */
int32_t m_variables[256]; /**< 32-bit variables set with relevant commands */
public:
/** initialize state for SoundMacro data at `ptr` */
void initialize(const unsigned char* ptr);
void initialize(const unsigned char* ptr, float ticksPerSec,
uint8_t midiKey, uint8_t midiVel, uint8_t midiMod);
/** advances `dt` seconds worth of commands in the SoundMacro
* @return `true` if END reached
*/
bool advance(Voice& vox, float dt);
/** keyoff event */
void keyoff();
/** sample end event */
void sampleEnd();
};
}

View File

@ -23,10 +23,13 @@ enum class VoiceState
class Voice : public Entity
{
friend class Engine;
int m_vid;
bool m_emitter;
std::unique_ptr<IBackendVoice> m_backendVoice;
SoundMacroState m_state;
Voice* m_sibling = nullptr;
public:
Voice(Engine& engine, int groupId);
Voice(Engine& engine, int groupId, int vid, bool emitter);
/** Request specified count of audio frames (samples) from voice,
* internally advancing the voice stream */
@ -35,6 +38,15 @@ public:
/** Get current state of voice */
VoiceState state() const;
/** Get VoiceId of this voice (unique to all currently-playing voices) */
int vid() const {return m_vid;}
/** Allocate parallel macro and tie to voice for possible emitter influence */
Voice* startSiblingMacro(int8_t addNote, int macroId, int macroStep);
/** Load specified SoundMacro ID of within group into voice */
bool loadSoundMacro(int macroId, int macroStep=0);
/** Signals voice to begin fade-out, eventually reaching silence */
void keyOff();

View File

@ -22,9 +22,9 @@ bool AudioGroup::songInGroup(int songId) const
const AudioGroupSampleDirectory::Entry* AudioGroup::getSfxEntry(int sfxId) const
{
for (const AudioGroupSampleDirectory::Entry& ent : m_sdir.m_entries)
if (ent.m_sfxId == sfxId)
return &ent;
for (const auto& ent : m_sdir.m_entries)
if (ent.first.m_sfxId == sfxId)
return &ent.first;
return nullptr;
}

View File

@ -13,13 +13,12 @@ Engine::Engine(IBackendVoiceAllocator& backend)
: m_backend(backend)
{}
Voice* Engine::_allocateVoice(int groupId, double sampleRate, bool dynamicPitch)
Voice* Engine::_allocateVoice(int groupId, double sampleRate, bool dynamicPitch, bool emitter)
{
std::unique_ptr<Voice> vox = std::make_unique<Voice>(*this, groupId);
vox->m_backendVoice = m_backend.allocateVoice(*vox, sampleRate, dynamicPitch);
Voice* ret = vox.get();
m_activeVoices.push_back(std::move(vox));
return ret;
m_activeVoices.emplace_back(*this, groupId, m_activeVoices.size(), emitter);
m_activeVoices.back().m_backendVoice =
m_backend.allocateVoice(m_activeVoices.back(), sampleRate, dynamicPitch);
return &m_activeVoices.back();
}
AudioGroup* Engine::_findGroupFromSfxId(int sfxId, const AudioGroupSampleDirectory::Entry*& entOut) const
@ -61,7 +60,7 @@ void Engine::removeAudioGroup(int groupId)
{
for (auto it = m_activeVoices.begin() ; it != m_activeVoices.end() ;)
{
if ((*it)->getGroupId() == groupId)
if (it->getGroupId() == groupId)
{
it = m_activeVoices.erase(it);
continue;
@ -71,7 +70,7 @@ void Engine::removeAudioGroup(int groupId)
for (auto it = m_activeEmitters.begin() ; it != m_activeEmitters.end() ;)
{
if ((*it)->getGroupId() == groupId)
if (it->getGroupId() == groupId)
{
it = m_activeEmitters.erase(it);
continue;
@ -81,7 +80,7 @@ void Engine::removeAudioGroup(int groupId)
for (auto it = m_activeSequencers.begin() ; it != m_activeSequencers.end() ;)
{
if ((*it)->getGroupId() == groupId)
if (it->getGroupId() == groupId)
{
it = m_activeSequencers.erase(it);
continue;
@ -100,7 +99,7 @@ Voice* Engine::fxStart(int sfxId, float vol, float pan)
if (!grp)
return nullptr;
Voice* ret = _allocateVoice(grp->groupId(), entry->m_sampleRate, true);
Voice* ret = _allocateVoice(grp->groupId(), entry->m_sampleRate, true, false);
ret->setVolume(vol);
ret->setPanning(pan);
return ret;
@ -115,18 +114,17 @@ Emitter* Engine::addEmitter(const Vector3f& pos, const Vector3f& dir, float maxD
if (!grp)
return nullptr;
Voice* vox = _allocateVoice(grp->groupId(), entry->m_sampleRate, true);
std::unique_ptr<Emitter> emtr = std::make_unique<Emitter>(*this, grp->groupId(), *vox);
Emitter* ret = emtr.get();
ret->setPos(pos);
ret->setDir(dir);
ret->setMaxDist(maxDist);
ret->setFalloff(falloff);
ret->setMinVol(minVol);
ret->setMaxVol(maxVol);
Voice* vox = _allocateVoice(grp->groupId(), entry->m_sampleRate, true, true);
m_activeEmitters.emplace_back(*this, grp->groupId(), *vox);
Emitter& ret = m_activeEmitters.back();
ret.setPos(pos);
ret.setDir(dir);
ret.setMaxDist(maxDist);
ret.setFalloff(falloff);
ret.setMinVol(minVol);
ret.setMaxVol(maxVol);
m_activeEmitters.push_back(std::move(emtr));
return ret;
return &ret;
}
/** Start song playing from loaded audio groups */
@ -134,4 +132,13 @@ Sequencer* Engine::seqPlay(int songId, const unsigned char* arrData)
{
}
/** Find voice from VoiceId */
Voice* Engine::findVoice(int vid)
{
for (Voice& vox : m_activeVoices)
if (vox.vid() == vid)
return &vox;
return nullptr;
}
}

View File

@ -0,0 +1,373 @@
#include "amuse/SoundMacroState.hpp"
#include "amuse/Voice.hpp"
#include "amuse/Engine.hpp"
#include "amuse/Common.hpp"
#include <string.h>
namespace amuse
{
void SoundMacroState::Header::swapBig()
{
m_size = SBig(m_size);
m_macroId = SBig(m_macroId);
}
void SoundMacroState::Command::swapBig()
{
uint32_t* words = reinterpret_cast<uint32_t*>(this);
words[0] = SBig(words[0]);
words[1] = SBig(words[1]);
}
void SoundMacroState::initialize(const unsigned char* ptr)
{
m_ptr = ptr;
m_ticksPerSec = 1000.f;
m_midiKey = 0;
m_midiVel = 0;
m_midiMod = 0;
m_random.seed();
m_pc.clear();
m_pc.push_back(-1);
m_execTime = 0.f;
m_keyoff = false;
m_sampleEnd = false;
m_loopCountdown = -1;
m_lastPlayMacroVid = -1;
memcpy(&m_header, ptr, sizeof(Header));
m_header.swapBig();
}
void SoundMacroState::initialize(const unsigned char* ptr, float ticksPerSec,
uint8_t midiKey, uint8_t midiVel, uint8_t midiMod)
{
m_ptr = ptr;
m_ticksPerSec = ticksPerSec;
m_midiKey = midiKey;
m_midiVel = midiVel;
m_midiMod = midiMod;
m_random.seed();
m_pc.clear();
m_pc.push_back(-1);
m_execTime = 0.f;
m_keyoff = false;
m_sampleEnd = false;
m_loopCountdown = -1;
m_lastPlayMacroVid = -1;
memcpy(&m_header, ptr, sizeof(Header));
m_header.swapBig();
}
bool SoundMacroState::advance(Voice& vox, float dt)
{
/* Nothing if uninitialized or finished */
if (m_pc.back() == -1)
return true;
/* Loop through as many commands as we can for this time period */
while (true)
{
/* Advance wait timer if active, returning if waiting */
if (m_inWait)
{
m_waitCountdown -= dt;
if (m_waitCountdown < 0.f)
m_inWait = false;
else
{
m_execTime += dt;
return false;
}
}
/* Load next command based on counter */
const Command* commands = reinterpret_cast<const Command*>(m_ptr + sizeof(Header));
Command cmd = commands[m_pc.back()++];
cmd.swapBig();
/* Perform function of command */
switch (cmd.m_op)
{
case Op::End:
case Op::Stop:
m_pc.back() = -1;
return true;
case Op::SplitKey:
{
uint8_t keyNumber = cmd.m_data[0];
int16_t macroId = *reinterpret_cast<int16_t*>(&cmd.m_data[1]);
int16_t macroStep = *reinterpret_cast<int16_t*>(&cmd.m_data[3]);
if (m_midiKey >= keyNumber)
{
/* Do Branch */
if (macroId == m_header.m_macroId)
m_pc.back() = macroStep;
else
vox.loadSoundMacro(macroId, macroStep);
}
break;
}
case Op::SplitVel:
{
uint8_t velocity = cmd.m_data[0];
int16_t macroId = *reinterpret_cast<int16_t*>(&cmd.m_data[1]);
int16_t macroStep = *reinterpret_cast<int16_t*>(&cmd.m_data[3]);
if (m_midiVel >= velocity)
{
/* Do Branch */
if (macroId == m_header.m_macroId)
m_pc.back() = macroStep;
else
vox.loadSoundMacro(macroId, macroStep);
}
break;
}
case Op::WaitTicks:
{
bool keyRelease = cmd.m_data[0];
bool random = cmd.m_data[1];
bool sampleEnd = cmd.m_data[2];
bool absolute = cmd.m_data[3];
bool ms = cmd.m_data[4];
int16_t time = *reinterpret_cast<int16_t*>(&cmd.m_data[5]);
/* Set wait state */
float q = ms ? 1000.f : m_ticksPerSec;
float secTime = time / q;
if (absolute)
{
if (secTime <= m_execTime)
break;
m_waitCountdown = secTime - m_execTime;
}
else
m_waitCountdown = secTime;
/* Randomize at the proper resolution */
if (random)
secTime = std::fmod(m_random() / q, secTime);
m_inWait = true;
m_keyoffWait = keyRelease;
m_sampleEndWait = sampleEnd;
break;
}
case Op::Loop:
{
bool keyRelease = cmd.m_data[0];
bool random = cmd.m_data[1];
bool sampleEnd = cmd.m_data[2];
int16_t step = *reinterpret_cast<int16_t*>(&cmd.m_data[3]);
int16_t times = *reinterpret_cast<int16_t*>(&cmd.m_data[5]);
if ((keyRelease && m_keyoff) || (sampleEnd && m_sampleEnd))
{
/* Break out of loop */
m_loopCountdown = -1;
break;
}
if (random)
times = m_random() % times;
if (m_loopCountdown == -1 && times != -1)
m_loopCountdown = times;
if (m_loopCountdown > 0)
{
/* Loop back to step */
--m_loopCountdown;
m_pc.back() = step;
}
else /* Break out of loop */
m_loopCountdown = -1;
break;
}
case Op::Goto:
{
int16_t macroId = *reinterpret_cast<int16_t*>(&cmd.m_data[1]);
int16_t macroStep = *reinterpret_cast<int16_t*>(&cmd.m_data[3]);
/* Do Branch */
if (macroId == m_header.m_macroId)
m_pc.back() = macroStep;
else
vox.loadSoundMacro(macroId, macroStep);
break;
}
case Op::WaitMs:
{
bool keyRelease = cmd.m_data[0];
bool random = cmd.m_data[1];
bool sampleEnd = cmd.m_data[2];
bool absolute = cmd.m_data[3];
int16_t time = *reinterpret_cast<int16_t*>(&cmd.m_data[5]);
/* Set wait state */
float secTime = time / 1000.f;
if (absolute)
{
if (secTime <= m_execTime)
break;
m_waitCountdown = secTime - m_execTime;
}
else
m_waitCountdown = secTime;
/* Randomize at the proper resolution */
if (random)
secTime = std::fmod(m_random() / 1000.f, secTime);
m_inWait = true;
m_keyoffWait = keyRelease;
m_sampleEndWait = sampleEnd;
break;
}
case Op::PlayMacro:
{
int8_t addNote = cmd.m_data[0];
int16_t macroId = *reinterpret_cast<int16_t*>(&cmd.m_data[1]);
int16_t macroStep = *reinterpret_cast<int16_t*>(&cmd.m_data[3]);
int8_t priority = cmd.m_data[5];
int8_t maxVoices = cmd.m_data[6];
Voice* sibVox = vox.startSiblingMacro(addNote, macroId, macroStep);
if (sibVox)
m_lastPlayMacroVid = sibVox->vid();
break;
}
case Op::SendKeyOff:
{
uint8_t vid = cmd.m_data[0];
bool lastStarted = cmd.m_data[1];
if (lastStarted)
{
if (m_lastPlayMacroVid != -1)
{
Voice* otherVox = vox.getEngine().findVoice(m_lastPlayMacroVid);
otherVox->keyOff();
}
}
else
{
Voice* otherVox = vox.getEngine().findVoice(m_variables[vid]);
otherVox->keyOff();
}
break;
}
case Op::SplitMod:
{
uint8_t mod = cmd.m_data[0];
int16_t macroId = *reinterpret_cast<int16_t*>(&cmd.m_data[1]);
int16_t macroStep = *reinterpret_cast<int16_t*>(&cmd.m_data[3]);
if (m_midiMod >= mod)
{
/* Do Branch */
if (macroId == m_header.m_macroId)
m_pc.back() = macroStep;
else
vox.loadSoundMacro(macroId, macroStep);
}
break;
}
case Op::PianoPan:
case Op::SetAdsr:
case Op::ScaleVolume:
case Op::Panning:
case Op::Envelope:
case Op::StartSample:
case Op::StopSample:
case Op::KeyOff:
case Op::SplitRnd:
case Op::FadeIn:
case Op::Spanning:
case Op::SetAdsrCtrl:
case Op::RndNote:
case Op::AddNote:
case Op::SetNote:
case Op::LastNote:
case Op::Portamento:
case Op::Vibrato:
case Op::PitchSweep1:
case Op::PitchSweep2:
case Op::SetPitch:
case Op::SetPitchAdsr:
case Op::ScaleVolumeDLS:
case Op::Mod2Vibrange:
case Op::SetupTremolo:
case Op::Return:
case Op::GoSub:
case Op::TrapEvent:
case Op::SendMessage:
case Op::GetMessage:
case Op::GetVid:
case Op::AddAgeCount:
case Op::SetAgeCount:
case Op::SendFlag:
case Op::PitchWheelR:
case Op::SetPriority:
case Op::AddPriority:
case Op::AgeCntSpeed:
case Op::AgeCntVel:
case Op::VolSelect:
case Op::PanSelect:
case Op::PitchWheelSelect:
case Op::ModWheelSelect:
case Op::PedalSelect:
case Op::PortaSelect:
case Op::ReverbSelect:
case Op::SpanSelect:
case Op::DopplerSelect:
case Op::TremoloSelect:
case Op::PreASelect:
case Op::PreBSelect:
case Op::PostBSelect:
case Op::AuxAFXSelect:
case Op::AuxBFXSelect:
case Op::SetupLFO:
case Op::ModeSelect:
case Op::SetKeygroup:
case Op::SRCmodeSelect:
case Op::AddVars:
case Op::SubVars:
case Op::MulVars:
case Op::DivVars:
case Op::AddIVars:
case Op::IfEqual:
case Op::IfLess:
default:
break;
}
}
m_execTime += dt;
return false;
}
void SoundMacroState::keyoff()
{
m_keyoff = true;
if (m_inWait && m_keyoffWait)
m_inWait = false;
}
void SoundMacroState::sampleEnd()
{
m_sampleEnd = true;
if (m_inWait && m_sampleEndWait)
m_inWait = false;
}
}

View File

@ -4,8 +4,8 @@
namespace amuse
{
Voice::Voice(Engine& engine, int groupId)
: Entity(engine, groupId)
Voice::Voice(Engine& engine, int groupId, int vid, bool emitter)
: Entity(engine, groupId), m_vid(vid), m_emitter(emitter)
{
}
@ -13,4 +13,12 @@ size_t Voice::supplyAudio(size_t frames, int16_t* data)
{
}
Voice* Voice::startSiblingMacro(int8_t addNote, int macroId, int macroStep)
{
}
bool Voice::loadSoundMacro(int macroId, int macroStep)
{
}
}