Work on Voice state

This commit is contained in:
Jack Andersen 2016-05-11 11:30:45 -10:00
parent 1102d50f8f
commit e510a0ad84
11 changed files with 610 additions and 264 deletions

View File

@ -7,13 +7,6 @@
#include "optional.hpp"
#include <stdio.h>
#include <string.h>
#ifndef _WIN32
#include <sys/select.h>
#include <termios.h>
#include <unistd.h>
#else
#include <conio.h>
#endif
#include <thread>
#include <map>
@ -186,7 +179,7 @@ struct AppCallback : boo::IApplicationCallback
"░░░ A │ S │ D ┃ F │ G │ H │ J ┃ K │ L │ ; ░░░\n"
"░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░\n"
"<left/right>: cycle MIDI setup / channel, <up/down>: volume\n"
"<tab>: sustain pedal, <1/2>: pitch wheel, <3-8>: mod wheel\n"
"<tab>: sustain pedal, <window-Y>: pitch wheel, <window-X>: mod wheel\n"
"<Z/X>: octave, <C/V>: velocity\n"
"<Q>: quit\n");

View File

@ -55,6 +55,9 @@ class AudioGroupPool
std::unordered_map<ObjectId, std::vector<const LayerMapping*>> m_layers;
public:
AudioGroupPool(const unsigned char* data);
const unsigned char* soundMacro(ObjectId id) const;
const Keymap* keymap(ObjectId id) const;
const std::vector<const LayerMapping*>* layer(ObjectId id) const;
const ADSR* tableAsAdsr(ObjectId id) const;
const Curve* tableAsCurves(ObjectId id) const
{return reinterpret_cast<const Curve*>(tableAsAdsr(id));}

View File

@ -22,6 +22,8 @@ class AudioGroupData;
/** Main audio playback system for a single audio output */
class Engine
{
friend class Voice;
IBackendVoiceAllocator& m_backend;
std::unordered_map<int, std::unique_ptr<AudioGroup>> m_audioGroups;
std::list<Voice> m_activeVoices;
@ -39,6 +41,9 @@ class Engine
public:
Engine(IBackendVoiceAllocator& backend);
/** Access voice backend of engine */
IBackendVoiceAllocator& getBackend() {return m_backend;}
/** Update all active audio entities and fill OS audio buffers as needed */
void pumpEngine();

View File

@ -1,11 +1,32 @@
#ifndef __AMUSE_ENVELOPE_HPP__
#define __AMUSE_ENVELOPE_HPP__
#include "AudioGroupPool.hpp"
namespace amuse
{
/** Per-sample state tracker for ADSR envelope data */
class Envelope
{
public:
enum class State
{
Attack,
Decay,
Sustain,
Release
};
private:
State m_phase = State::Attack; /**< Current envelope state */
const ADSR* m_curADSR = nullptr; /**< Current timing envelope */
double m_sustainFactor; /**< Evaluated sustain percentage as a double */
double m_releaseStartFactor; /**< Level at whenever release event occurs */
unsigned m_curMs; /**< Current time of envelope stage */
public:
void reset(const ADSR* adsr);
void keyOff();
float nextSample(double sampleRate);
};
}

View File

@ -12,6 +12,8 @@ class Voice;
class SoundMacroState
{
friend class Voice;
/** SoundMacro header */
struct Header
{
@ -115,15 +117,6 @@ class SoundMacroState
/** 'program counter' stack for the active SoundMacro */
std::vector<std::pair<const unsigned char*, int>> m_pc;
float m_curVol; /**< final volume sent to voice */
bool m_volDirty; /**< set when voice needs updated volume */
float m_curPan; /**< final pan sent to voice */
bool m_panDirty; /**< set when voice needs updated pan */
float m_curSpan; /**< final span sent to voice */
bool m_spanDirty; /**< set when voice needs updated span */
float m_ticksPerSec; /**< ratio for resolving ticks in commands that use them */
uint8_t m_initVel; /**< Velocity played for this macro invocation */
uint8_t m_initMod; /**< Modulation played for this macro invocation */
@ -131,34 +124,11 @@ class SoundMacroState
uint8_t m_curVel; /**< Current velocity played for this macro invocation */
uint8_t m_curMod; /**< Current modulation played for this macro invocation */
uint32_t m_curKey; /**< Current key played for this macro invocation (in cents) */
uint32_t m_pitchSweep1; /**< Current value of PITCHSWEEP1 controller (in cents) */
uint32_t m_pitchSweep2; /**< Current value of PITCHSWEEP2 controller (in cents) */
int16_t m_pitchSweep1Add; /**< Value to add to PITCHSWEEP1 controller each cycle */
int16_t m_pitchSweep2Add; /**< Value to add to PITCHSWEEP2 controller each cycle */
uint8_t m_pitchSweep1Times; /**< Remaining times to advance PITCHSWEEP1 controller */
uint8_t m_pitchSweep2Times; /**< Remaining times to advance PITCHSWEEP2 controller */
bool m_pitchDirty; /**< set when voice needs latest pitch computation */
float m_execTime; /**< time in seconds of SoundMacro execution */
float m_execTime; /**< time in seconds of SoundMacro execution (per-update resolution) */
bool m_keyoff; /**< keyoff message has been received */
bool m_sampleEnd; /**< sample has finished playback */
float m_envelopeTime; /**< time since last ENVELOPE command, -1 for no active volume-sweep */
float m_envelopeDur; /**< requested duration of last ENVELOPE command */
uint8_t m_envelopeStart; /**< initial value for last ENVELOPE command */
uint8_t m_envelopeEnd; /**< final value for last ENVELOPE command */
const Curve* m_envelopeCurve; /**< curve to use for ENVELOPE command */
float m_panningTime; /**< time since last PANNING command, -1 for no active pan-sweep */
float m_panningDur; /**< requested duration of last PANNING command */
uint8_t m_panPos; /**< initial pan value of last PANNING command */
uint8_t m_panWidth; /**< delta pan value to target of last PANNING command */
float m_spanningTime; /**< time since last SPANNING command, -1 for no active span-sweep */
float m_spanningDur; /**< requested duration of last SPANNING command */
uint8_t m_spanPos; /**< initial pan value of last SPANNING command */
uint8_t m_spanWidth; /**< delta pan value to target of last SPANNING command */
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 */
@ -177,16 +147,6 @@ class SoundMacroState
uint8_t m_portamentoType; /**< (0: New key pressed while old key pressed, 1: Always) */
float m_portamentoTime; /**< portamento transition time, 0.f will perform legato */
int32_t m_vibratoLevel; /**< scale of vibrato effect (in cents) */
int32_t m_vibratoModLevel; /**< scale of vibrato mod-wheel influence (in cents) */
float m_vibratoPeriod; /**< vibrato wave period-time, 0.f will disable vibrato */
bool m_vibratoModWheel; /**< vibrato scaled with mod-wheel if set */
float m_tremoloScale; /**< minimum volume factor produced via LFO */
float m_tremoloModScale; /**< minimum volume factor produced via LFO, scaled via mod wheel */
float m_lfoPeriods[2]; /**< time-periods for LFO1 and LFO2 */
/** Used to build a multi-component formula for overriding controllers */
struct Evaluator
{
@ -259,8 +219,8 @@ class SoundMacroState
public:
/** initialize state for SoundMacro data at `ptr` */
void initialize(const unsigned char* ptr);
void initialize(const unsigned char* ptr, float ticksPerSec,
void initialize(const unsigned char* ptr, int step);
void initialize(const unsigned char* ptr, int step, float ticksPerSec,
uint8_t midiKey, uint8_t midiVel, uint8_t midiMod);
/** advances `dt` seconds worth of commands in the SoundMacro

View File

@ -9,6 +9,7 @@
#include "Entity.hpp"
#include "AudioGroupSampleDirectory.hpp"
#include "AudioGroup.hpp"
#include "Envelope.hpp"
namespace amuse
{
@ -27,14 +28,15 @@ enum class VoiceState
class Voice : public Entity
{
friend class Engine;
friend class SoundMacroState;
int m_vid; /**< VoiceID of this voice instance */
bool m_emitter; /**< Voice is part of an Emitter */
Submix* m_submix = nullptr; /**< Submix this voice outputs to (or NULL for the main output mix) */
std::list<Voice>::iterator m_engineIt; /**< Iterator to self within Engine's list for quick deletion */
std::unique_ptr<IBackendVoice> m_backendVoice; /**< Handle to client-implemented backend voice */
SoundMacroState m_state; /**< State container for SoundMacro playback */
std::list<Voice> m_childVoices; /**< Child voices for PLAYMACRO usage */
uint8_t m_lastNote = 0; /**< Last MIDI semitone played by voice */
uint8_t m_keygroup = 0; /**< Keygroup voice is a member of */
const Sample* m_curSample = nullptr; /**< Current sample entry playing */
@ -43,14 +45,67 @@ class Voice : public Entity
uint32_t m_lastSamplePos = 0; /**< Last sample position (or last loop sample) */
int16_t m_prev1 = 0; /**< DSPADPCM prev sample */
int16_t m_prev2 = 0; /**< DSPADPCM prev-prev sample */
double m_sampleRate; /**< Current sample rate computed from relative sample key or SETPITCH */
double m_voiceTime; /**< Current seconds of voice playback (per-sample resolution) */
VoiceState m_voxState = VoiceState::Finished; /**< Current high-level state of voice */
bool m_sustained = false; /**< Sustain pedal pressed for this voice */
bool m_sustainKeyOff = false; /**< Keyoff event occured while sustained */
float m_curVol; /**< Current volume of voice */
float m_curReverbVol; /**< Current reverb volume of voice */
float m_curPan; /**< Current pan of voice */
float m_curSpan; /**< Current surround pan of voice */
int32_t m_curPitch; /**< Current base pitch in cents */
bool m_pitchDirty; /**< m_curPitch has been updated and needs sending to voice */
Envelope m_volAdsr; /**< Volume envelope */
double m_envelopeTime; /**< time since last ENVELOPE command, -1 for no active volume-sweep */
double m_envelopeDur; /**< requested duration of last ENVELOPE command */
float m_envelopeStart; /**< initial value for last ENVELOPE command */
float m_envelopeEnd; /**< final value for last ENVELOPE command */
const Curve* m_envelopeCurve; /**< curve to use for ENVELOPE command */
bool m_pitchEnv = false; /**< Pitch envelope activated */
Envelope m_pitchAdsr; /**< Pitch envelope for SETPITCHADSR */
int32_t m_pitchEnvRange; /**< Pitch delta for SETPITCHADSR (in cents) */
uint32_t m_pitchSweep1; /**< Current value of PITCHSWEEP1 controller (in cents) */
uint32_t m_pitchSweep2; /**< Current value of PITCHSWEEP2 controller (in cents) */
int16_t m_pitchSweep1Add; /**< Value to add to PITCHSWEEP1 controller each cycle */
int16_t m_pitchSweep2Add; /**< Value to add to PITCHSWEEP2 controller each cycle */
uint8_t m_pitchSweep1Times; /**< Remaining times to advance PITCHSWEEP1 controller */
uint8_t m_pitchSweep2Times; /**< Remaining times to advance PITCHSWEEP2 controller */
float m_panningTime; /**< time since last PANNING command, -1 for no active pan-sweep */
float m_panningDur; /**< requested duration of last PANNING command */
uint8_t m_panPos; /**< initial pan value of last PANNING command */
uint8_t m_panWidth; /**< delta pan value to target of last PANNING command */
float m_spanningTime; /**< time since last SPANNING command, -1 for no active span-sweep */
float m_spanningDur; /**< requested duration of last SPANNING command */
uint8_t m_spanPos; /**< initial pan value of last SPANNING command */
uint8_t m_spanWidth; /**< delta pan value to target of last SPANNING command */
int32_t m_vibratoLevel; /**< scale of vibrato effect (in cents) */
int32_t m_vibratoModLevel; /**< scale of vibrato mod-wheel influence (in cents) */
float m_vibratoPeriod; /**< vibrato wave period-time, 0.f will disable vibrato */
bool m_vibratoModWheel; /**< vibrato scaled with mod-wheel if set */
float m_tremoloScale; /**< minimum volume factor produced via LFO */
float m_tremoloModScale; /**< minimum volume factor produced via LFO, scaled via mod wheel */
float m_lfoPeriods[2]; /**< time-periods for LFO1 and LFO2 */
void _destroy();
void _reset();
bool _checkSamplePos();
void _doKeyOff();
bool _advanceSample(int16_t& samp);
void _setTotalPitch(int32_t cents);
Voice* _allocateVoice(double sampleRate, bool dynamicPitch);
std::list<Voice>::iterator _destroyVoice(Voice* voice);
public:
Voice(Engine& engine, const AudioGroup& group, int vid, bool emitter, Submix* smx);
@ -69,11 +124,16 @@ public:
/** Get VoiceId of this voice (unique to all currently-playing voices) */
int vid() const {return m_vid;}
/** Get max VoiceId of this voice and any contained children */
int maxVid() const;
/** Allocate parallel macro and tie to voice for possible emitter influence */
Voice* startChildMacro(int8_t addNote, ObjectId macroId, int macroStep);
/** Load specified SoundMacro ID of within group into voice */
bool loadSoundMacro(ObjectId macroId, int macroStep=0, bool pushPc=false);
bool loadSoundMacro(ObjectId macroId, int macroStep, float ticksPerSec,
uint8_t midiKey, uint8_t midiVel, uint8_t midiMod,
bool pushPc=false);
/** Signals voice to begin fade-out (or defer if sustained), eventually reaching silence */
void keyOff();
@ -91,10 +151,22 @@ public:
void setVolume(float vol);
/** Set current voice panning immediately */
void setPanning(float pan);
void setPan(float pan);
/** Set current voice surround-panning immediately */
void setSurroundPanning(float span);
void setSurroundPan(float span);
/** Start volume envelope to specified level */
void startEnvelope(double dur, float vol, const Curve* envCurve);
/** Start volume envelope from zero to current level */
void startFadeIn(double dur, float vol, const Curve* envCurve);
/** Start pan envelope to specified position */
void startPanning(double dur, uint8_t panPos, uint8_t panWidth);
/** Start span envelope to specified position */
void startSpanning(double dur, uint8_t spanPos, uint8_t spanWidth);
/** Set voice relative-pitch in cents */
void setPitchKey(int32_t cents);
@ -108,6 +180,27 @@ public:
/** Set doppler factor for voice */
void setDoppler(float doppler);
/** Set vibrato parameters for voice */
void setVibrato(int32_t level, int32_t modLevel, float period);
/** Configure modwheel influence range over vibrato */
void setMod2VibratoRange(int32_t modLevel);
/** Setup tremolo parameters for voice */
void setTremolo(float tremoloScale, float tremoloModScale);
/** Setup LFO1 for voice */
void setLFO1Period(float period) {m_lfoPeriods[0] = period;}
/** Setup LFO2 for voice */
void setLFO2Period(float period) {m_lfoPeriods[1] = period;}
/** Setup pitch sweep controller 1 */
void setPitchSweep1(uint8_t times, int16_t add);
/** Setup pitch sweep controller 2 */
void setPitchSweep2(uint8_t times, int16_t add);
/** Set reverb mix for voice */
void setReverbVol(float rvol);
@ -126,7 +219,7 @@ public:
/** Assign voice to keygroup for coordinated mass-silencing */
void setKeygroup(uint8_t kg) {m_keygroup = kg;}
uint8_t getLastNote() const {return m_lastNote;}
uint8_t getLastNote() const {return m_state.m_initKey;}
int8_t getCtrlValue(uint8_t ctrl) const {}
void setCtrlValue(uint8_t ctrl, int8_t val) {}
int8_t getPitchWheel() const {}

View File

@ -81,6 +81,30 @@ AudioGroupPool::AudioGroupPool(const unsigned char* data)
}
}
const unsigned char* AudioGroupPool::soundMacro(ObjectId id) const
{
auto search = m_soundMacros.find(id);
if (search == m_soundMacros.cend())
return nullptr;
return search->second;
}
const Keymap* AudioGroupPool::keymap(ObjectId id) const
{
auto search = m_keymaps.find(id);
if (search == m_keymaps.cend())
return nullptr;
return search->second;
}
const std::vector<const LayerMapping*>* AudioGroupPool::layer(ObjectId id) const
{
auto search = m_layers.find(id);
if (search == m_layers.cend())
return nullptr;
return &search->second;
}
const ADSR* AudioGroupPool::tableAsAdsr(ObjectId id) const
{
auto search = m_tables.find(id);

View File

@ -56,9 +56,7 @@ void Engine::pumpEngine()
{
int maxVid = -1;
for (Voice& vox : m_activeVoices)
{
maxVid = std::max(maxVid, vox.vid());
}
maxVid = std::max(maxVid, vox.maxVid());
m_nextVid = maxVid + 1;
}
@ -188,7 +186,7 @@ Voice* Engine::fxStart(int sfxId, float vol, float pan, Submix* smx)
Voice* ret = _allocateVoice(*grp, 32000.0, true, false, smx);
ret->setVolume(vol);
ret->setPanning(pan);
ret->setPan(pan);
return ret;
}

View File

@ -0,0 +1,73 @@
#include "amuse/Envelope.hpp"
namespace amuse
{
void Envelope::reset(const ADSR* adsr)
{
m_phase = State::Attack;
m_curADSR = adsr;
m_curMs = 0;
m_sustainFactor = (adsr->sustainCoarse * 6.25 + adsr->sustainFine * 0.0244) / 100.0;
m_releaseStartFactor = 0.0;
}
void Envelope::keyOff()
{
m_phase = State::Release;
m_curMs = 0;
}
float Envelope::nextSample(double sampleRate)
{
if (!m_curADSR)
{
if (m_phase == State::Release)
return 0.f;
return 1.f;
}
m_curMs += 1.0 / (sampleRate / 1000.0);
switch (m_phase)
{
case State::Attack:
{
double attackFac = m_curMs / (m_curADSR->attackCoarse * 255 + m_curADSR->attackFine);
if (attackFac >= 1.0)
{
m_phase = State::Decay;
m_curMs = 0;
return 1.f;
}
m_releaseStartFactor = attackFac;
return attackFac;
}
case State::Decay:
{
double decayFac = m_curMs / (m_curADSR->decayCoarse * 255 + m_curADSR->decayFine);
if (decayFac >= 1.0)
{
m_phase = State::Sustain;
m_curMs = 0;
m_releaseStartFactor = m_sustainFactor;
return m_sustainFactor;
}
m_releaseStartFactor = (1.0 - decayFac) + decayFac * m_sustainFactor;
return m_releaseStartFactor;
}
case State::Sustain:
{
return m_sustainFactor;
}
case State::Release:
{
double releaseFac = m_curMs / (m_curADSR->releaseCoarse * 255 + m_curADSR->releaseFine);
if (releaseFac >= 1.0)
return 0.f;
return (1.0 - releaseFac) * m_releaseStartFactor;
}
}
}
}

View File

@ -52,17 +52,17 @@ float SoundMacroState::Evaluator::evaluate(Voice& vox, const SoundMacroState& st
break;
case 130:
/* LFO1 */
if (st.m_lfoPeriods[0])
thisValue = std::sin(st.m_execTime / st.m_lfoPeriods[0] * 2.f * M_PIF);
if (vox.m_lfoPeriods[0])
thisValue = std::sin(vox.m_voiceTime / vox.m_lfoPeriods[0] * 2.f * M_PIF);
break;
case 131:
/* LFO2 */
if (st.m_lfoPeriods[1])
thisValue = std::sin(st.m_execTime / st.m_lfoPeriods[1] * 2.f * M_PIF);
if (vox.m_lfoPeriods[1])
thisValue = std::sin(vox.m_voiceTime / vox.m_lfoPeriods[1] * 2.f * M_PIF);
break;
case 132:
/* Surround panning */
thisValue = st.m_curSpan * 64.f + 64.f;
thisValue = vox.m_curSpan * 64.f + 64.f;
break;
case 133:
/* Macro-starting key */
@ -110,50 +110,30 @@ float SoundMacroState::Evaluator::evaluate(Voice& vox, const SoundMacroState& st
return value;
}
void SoundMacroState::initialize(const unsigned char* ptr)
void SoundMacroState::initialize(const unsigned char* ptr, int step)
{
initialize(ptr, 1000.f, 0, 0, 0);
initialize(ptr, step, 1000.f, 0, 0, 0);
}
void SoundMacroState::initialize(const unsigned char* ptr, float ticksPerSec,
void SoundMacroState::initialize(const unsigned char* ptr, int step, float ticksPerSec,
uint8_t midiKey, uint8_t midiVel, uint8_t midiMod)
{
m_curVol = 1.f;
m_volDirty = true;
m_curPan = 0.f;
m_panDirty = true;
m_curSpan = 0.f;
m_spanDirty = true;
m_ticksPerSec = ticksPerSec;
m_initKey = 0;
m_initVel = 0;
m_initMod = 0;
m_initKey = midiKey;
m_initVel = midiVel;
m_initMod = midiMod;
m_curVel = 0;
m_curMod = 0;
m_curKey = 0;
m_pitchSweep1 = 0;
m_pitchSweep1Times = 0;
m_pitchSweep2 = 0;
m_pitchSweep2Times = 0;
m_pitchDirty = true;
m_pc.clear();
m_pc.push_back({ptr, 0});
m_pc.push_back({ptr, step});
m_execTime = 0.f;
m_keyoff = false;
m_sampleEnd = false;
m_envelopeTime = -1.f;
m_panningTime = -1.f;
m_loopCountdown = -1;
m_lastPlayMacroVid = -1;
m_useAdsrControllers = false;
m_portamentoMode = 0;
m_vibratoLevel = 0;
m_vibratoModLevel = 0;
m_vibratoPeriod = 0.f;
m_tremoloScale = 0.f;
m_tremoloModScale = 0.f;
m_lfoPeriods[0] = 0.f;
m_lfoPeriods[1] = 0.f;
m_header = *reinterpret_cast<const Header*>(ptr);
m_header.swapBig();
}
@ -164,107 +144,6 @@ bool SoundMacroState::advance(Voice& vox, float dt)
if (m_pc.empty() || m_pc.back().first == nullptr || m_pc.back().second == -1)
return true;
/* Process active envelope */
if (m_envelopeTime >= 0.f)
{
m_envelopeTime += dt;
float start = m_envelopeStart / 127.f;
float end = m_envelopeEnd / 127.f;
float t = std::max(0.f, std::min(1.f, m_envelopeTime / m_envelopeDur));
if (m_envelopeCurve)
t = (*m_envelopeCurve)[int(t*127.f)] / 127.f;
m_curVol = (start * (1.0f - t)) + (end * t);
m_volDirty = true;
/* Done with envelope */
if (m_envelopeTime > m_envelopeDur)
m_envelopeTime = -1.f;
}
/* Apply tremolo */
float totalVol = m_curVol;
if (m_tremoloSel && (m_tremoloScale || m_tremoloModScale))
{
float t = m_tremoloSel.evaluate(vox, *this);
if (m_tremoloScale && m_tremoloModScale)
{
float fac = (1.0f - t) + (m_tremoloScale * t);
float modT = vox.getModWheel() / 127.f;
float modFac = (1.0f - modT) + (m_tremoloModScale * modT);
totalVol *= fac * modFac;
}
else if (m_tremoloScale)
{
float fac = (1.0f - t) + (m_tremoloScale * t);
totalVol *= fac;
}
else if (m_tremoloModScale)
{
float modT = vox.getModWheel() / 127.f;
float modFac = (1.0f - modT) + (m_tremoloModScale * modT);
totalVol *= modFac;
}
m_volDirty = true;
}
/* Apply total volume */
if (m_volDirty)
{
vox.setVolume(totalVol);
m_volDirty = false;
}
/* Process active pan-sweep */
if (m_panningTime >= 0.f)
{
m_panningTime += dt;
float start = (m_panPos - 64) / 64.f;
float end = (m_panPos + m_panWidth - 64) / 64.f;
float t = std::max(0.f, std::min(1.f, m_panningTime / m_panningDur));
vox.setPanning((start * (1.0f - t)) + (end * t));
/* Done with panning */
if (m_panningTime > m_panningDur)
m_panningTime = -1.f;
}
/* Process active span-sweep */
if (m_spanningTime >= 0.f)
{
m_spanningTime += dt;
float start = (m_spanPos - 64) / 64.f;
float end = (m_spanPos + m_spanWidth - 64) / 64.f;
float t = std::max(0.f, std::min(1.f, m_spanningTime / m_spanningDur));
vox.setSurroundPanning((start * (1.0f - t)) + (end * t));
/* Done with spanning */
if (m_spanningTime > m_spanningDur)
m_spanningTime = -1.f;
}
/* Process pitch sweep 1 */
if (m_pitchSweep1Times)
{
m_pitchSweep1 += m_pitchSweep1Add;
--m_pitchSweep1Times;
m_pitchDirty = true;
}
/* Process pitch sweep 2 */
if (m_pitchSweep2Times)
{
m_pitchSweep2 += m_pitchSweep2Add;
--m_pitchSweep2Times;
m_pitchDirty = true;
}
/* Apply total pitch */
if (m_pitchDirty)
{
vox.setPitchKey(m_curKey + m_pitchSweep1 + m_pitchSweep2);
m_pitchDirty = false;
}
/* Loop through as many commands as we can for this time period */
while (true)
{
@ -305,7 +184,8 @@ bool SoundMacroState::advance(Voice& vox, float dt)
if (macroId == m_header.m_macroId)
m_pc.back().second = macroStep;
else
vox.loadSoundMacro(macroId, macroStep);
vox.loadSoundMacro(macroId, macroStep, m_ticksPerSec,
m_initKey, m_initVel, m_initMod);
}
break;
@ -322,7 +202,8 @@ bool SoundMacroState::advance(Voice& vox, float dt)
if (macroId == m_header.m_macroId)
m_pc.back().second = macroStep;
else
vox.loadSoundMacro(macroId, macroStep);
vox.loadSoundMacro(macroId, macroStep, m_ticksPerSec,
m_initKey, m_initVel, m_initMod);
}
break;
@ -398,7 +279,8 @@ bool SoundMacroState::advance(Voice& vox, float dt)
if (macroId == m_header.m_macroId)
m_pc.back().second = macroStep;
else
vox.loadSoundMacro(macroId, macroStep);
vox.loadSoundMacro(macroId, macroStep, m_ticksPerSec,
m_initKey, m_initVel, m_initMod);
break;
}
@ -479,7 +361,8 @@ bool SoundMacroState::advance(Voice& vox, float dt)
if (macroId == m_header.m_macroId)
m_pc.back().second = macroStep;
else
vox.loadSoundMacro(macroId, macroStep);
vox.loadSoundMacro(macroId, macroStep, m_ticksPerSec,
m_initKey, m_initVel, m_initMod);
}
break;
@ -492,7 +375,7 @@ bool SoundMacroState::advance(Voice& vox, float dt)
int32_t pan = int32_t(m_initKey - cenKey) * scale / 127 + cenPan;
pan = std::max(-127, std::min(127, pan));
vox.setPanning(pan / 127.f);
vox.setPan(pan / 127.f);
break;
}
case Op::SetAdsr:
@ -516,14 +399,12 @@ bool SoundMacroState::advance(Voice& vox, float dt)
const Curve* curveData = vox.getAudioGroup().getPool().tableAsCurves(curve);
if (curveData)
{
m_curVol = (*curveData)[eval] / 127.f;
m_volDirty = true;
vox.setVolume((*curveData)[eval] / 127.f);
break;
}
}
m_curVol = eval / 127.f;
m_volDirty = true;
vox.setVolume(eval / 127.f);
break;
}
case Op::Panning:
@ -532,11 +413,7 @@ bool SoundMacroState::advance(Voice& vox, float dt)
int16_t timeMs = *reinterpret_cast<int16_t*>(&cmd.m_data[1]);
int8_t width = cmd.m_data[3];
m_panningTime = 0.f;
m_panningDur = timeMs / 1000.f;
m_panPos = panPos;
m_panWidth = width;
vox.startPanning(timeMs / 1000.0, panPos, width);
break;
}
case Op::Envelope:
@ -547,22 +424,19 @@ bool SoundMacroState::advance(Voice& vox, float dt)
bool ms = cmd.m_data[4];
int16_t fadeTime = *reinterpret_cast<int16_t*>(&cmd.m_data[5]);
float q = ms ? 1000.f : m_ticksPerSec;
float secTime = fadeTime / q;
double q = ms ? 1000.0 : m_ticksPerSec;
double secTime = fadeTime / q;
int32_t eval = int32_t(m_curVel) * scale / 127 + add;
eval = std::max(0, std::min(127, eval));
m_envelopeTime = 0.f;
m_envelopeDur = secTime;
m_envelopeStart = m_curVel;
m_envelopeEnd = eval;
const Curve* curveData;
if (curve != 0)
m_envelopeCurve = vox.getAudioGroup().getPool().tableAsCurves(curve);
curveData = vox.getAudioGroup().getPool().tableAsCurves(curve);
else
m_envelopeCurve = nullptr;
curveData = nullptr;
vox.startEnvelope(secTime, eval, curveData);
break;
}
case Op::StartSample:
@ -608,7 +482,8 @@ bool SoundMacroState::advance(Voice& vox, float dt)
if (macroId == m_header.m_macroId)
m_pc.back().second = macroStep;
else
vox.loadSoundMacro(macroId, macroStep);
vox.loadSoundMacro(macroId, macroStep, m_ticksPerSec,
m_initKey, m_initVel, m_initMod);
}
break;
@ -627,16 +502,13 @@ bool SoundMacroState::advance(Voice& vox, float dt)
int32_t eval = int32_t(m_curVel) * scale / 127 + add;
eval = std::max(0, std::min(127, eval));
m_envelopeTime = 0.f;
m_envelopeDur = secTime;
m_envelopeStart = 0.f;
m_envelopeEnd = eval;
const Curve* curveData;
if (curve != 0)
m_envelopeCurve = vox.getAudioGroup().getPool().tableAsCurves(curve);
curveData = vox.getAudioGroup().getPool().tableAsCurves(curve);
else
m_envelopeCurve = nullptr;
curveData = nullptr;
vox.startFadeIn(secTime, eval, curveData);
break;
}
case Op::Spanning:
@ -645,11 +517,7 @@ bool SoundMacroState::advance(Voice& vox, float dt)
int16_t timeMs = *reinterpret_cast<int16_t*>(&cmd.m_data[1]);
int8_t width = cmd.m_data[3];
m_spanningTime = 0.f;
m_spanningDur = timeMs / 1000.f;
m_spanPos = panPos;
m_spanWidth = width;
vox.startSpanning(timeMs / 1000.0, panPos, width);
break;
}
case Op::SetAdsrCtrl:
@ -682,7 +550,7 @@ bool SoundMacroState::advance(Voice& vox, float dt)
if (!free)
m_curKey = m_curKey / 100 * 100 + detune;
m_pitchDirty = true;
vox.setPitchKey(m_curKey);
break;
}
case Op::AddNote:
@ -704,7 +572,7 @@ bool SoundMacroState::advance(Voice& vox, float dt)
m_inWait = true;
}
m_pitchDirty = true;
vox.setPitchKey(m_curKey);
break;
}
case Op::SetNote:
@ -725,7 +593,7 @@ bool SoundMacroState::advance(Voice& vox, float dt)
m_inWait = true;
}
m_pitchDirty = true;
vox.setPitchKey(m_curKey);
break;
}
case Op::LastNote:
@ -746,7 +614,7 @@ bool SoundMacroState::advance(Voice& vox, float dt)
m_inWait = true;
}
m_pitchDirty = true;
vox.setPitchKey(m_curKey);
break;
}
case Op::Portamento:
@ -762,20 +630,19 @@ bool SoundMacroState::advance(Voice& vox, float dt)
}
case Op::Vibrato:
{
m_vibratoModLevel = m_vibratoLevel = cmd.m_data[0] * 100 + cmd.m_data[1];
m_vibratoModWheel = cmd.m_data[2];
int32_t level = cmd.m_data[0] * 100 + cmd.m_data[1];
int32_t modLevel = cmd.m_data[2];
int8_t ms = cmd.m_data[4];
int16_t timeMs = *reinterpret_cast<int16_t*>(&cmd.m_data[5]);
float q = ms ? 1000.f : m_ticksPerSec;
m_vibratoPeriod = timeMs / q;
vox.setVibrato(level, modLevel, timeMs / q);
break;
}
case Op::PitchSweep1:
{
m_pitchSweep1 = 0;
m_pitchSweep1Times = int32_t(cmd.m_data[0]);
m_pitchSweep1Add = *reinterpret_cast<int16_t*>(&cmd.m_data[1]);
int32_t times = int32_t(cmd.m_data[0]);
int16_t add = *reinterpret_cast<int16_t*>(&cmd.m_data[1]);
int8_t ms = cmd.m_data[4];
int16_t timeMs = *reinterpret_cast<int16_t*>(&cmd.m_data[5]);
@ -789,13 +656,13 @@ bool SoundMacroState::advance(Voice& vox, float dt)
m_inWait = true;
}
vox.setPitchSweep1(times, add);
break;
}
case Op::PitchSweep2:
{
m_pitchSweep2 = 0;
m_pitchSweep2Times = int32_t(cmd.m_data[0]);
m_pitchSweep2Add = *reinterpret_cast<int16_t*>(&cmd.m_data[1]);
int32_t times = int32_t(cmd.m_data[0]);
int16_t add = *reinterpret_cast<int16_t*>(&cmd.m_data[1]);
int8_t ms = cmd.m_data[4];
int16_t timeMs = *reinterpret_cast<int16_t*>(&cmd.m_data[5]);
@ -809,6 +676,7 @@ bool SoundMacroState::advance(Voice& vox, float dt)
m_inWait = true;
}
vox.setPitchSweep2(times, add);
break;
}
case Op::SetPitch:
@ -830,23 +698,21 @@ bool SoundMacroState::advance(Voice& vox, float dt)
{
int16_t scale = *reinterpret_cast<int16_t*>(&cmd.m_data[0]);
bool orgVel = cmd.m_data[2];
m_curVol = int32_t(orgVel ? m_initVel : m_curVel) * scale / 4096.f / 127.f;
m_volDirty = true;
vox.setVolume(int32_t(orgVel ? m_initVel : m_curVel) * scale / 4096.f / 127.f);
break;
}
case Op::Mod2Vibrange:
{
int8_t keys = cmd.m_data[0];
int8_t cents = cmd.m_data[1];
m_vibratoModLevel = keys * 100 + cents;
vox.setMod2VibratoRange(keys * 100 + cents);
break;
}
case Op::SetupTremolo:
{
int16_t scale = *reinterpret_cast<int16_t*>(&cmd.m_data[0]);
int16_t modScale = *reinterpret_cast<int16_t*>(&cmd.m_data[3]);
m_tremoloScale = scale / 4096.f;
m_tremoloModScale = modScale / 4096.f;
vox.setTremolo(scale / 4096.f, modScale / 4096.f);
break;
}
case Op::Return:
@ -868,7 +734,8 @@ bool SoundMacroState::advance(Voice& vox, float dt)
if (macroId == m_header.m_macroId)
m_pc.push_back({m_pc.back().first, macroStep});
else
vox.loadSoundMacro(macroId, macroStep, true);
vox.loadSoundMacro(macroId, macroStep, m_ticksPerSec,
m_initKey, m_initVel, m_initMod, true);
m_header = *reinterpret_cast<const Header*>(m_pc.back().first);
m_header.swapBig();
@ -1128,8 +995,10 @@ bool SoundMacroState::advance(Voice& vox, float dt)
{
uint8_t number = cmd.m_data[0];
int16_t period = *reinterpret_cast<int16_t*>(&cmd.m_data[1]);
if (number <= 1)
m_lfoPeriods[number] = period / 1000.f;
if (number == 0)
vox.setLFO1Period(period / 1000.f);
else if (number == 1)
vox.setLFO2Period(period / 1000.f);
break;
}
case Op::SetKeygroup:
@ -1338,7 +1207,8 @@ void SoundMacroState::keyoffNotify(Voice& vox)
if (m_keyoffTrap.macroId == m_header.m_macroId)
m_pc.back().second = m_keyoffTrap.macroStep;
else
vox.loadSoundMacro(m_keyoffTrap.macroId, m_keyoffTrap.macroStep);
vox.loadSoundMacro(m_keyoffTrap.macroId, m_keyoffTrap.macroStep,
m_ticksPerSec, m_initKey, m_initVel, m_initMod);
}
}
@ -1353,7 +1223,8 @@ void SoundMacroState::sampleEndNotify(Voice& vox)
if (m_sampleEndTrap.macroId == m_header.m_macroId)
m_pc.back().second = m_sampleEndTrap.macroStep;
else
vox.loadSoundMacro(m_sampleEndTrap.macroId, m_sampleEndTrap.macroStep);
vox.loadSoundMacro(m_sampleEndTrap.macroId, m_sampleEndTrap.macroStep,
m_ticksPerSec, m_initKey, m_initVel, m_initMod);
}
}
@ -1366,7 +1237,8 @@ void SoundMacroState::messageNotify(Voice& vox, int32_t val)
if (m_messageTrap.macroId == m_header.m_macroId)
m_pc.back().second = m_messageTrap.macroStep;
else
vox.loadSoundMacro(m_messageTrap.macroId, m_messageTrap.macroStep);
vox.loadSoundMacro(m_messageTrap.macroId, m_messageTrap.macroStep,
m_ticksPerSec, m_initKey, m_initVel, m_initMod);
}
}

View File

@ -1,8 +1,12 @@
#include "amuse/Voice.hpp"
#include "amuse/Submix.hpp"
#include "amuse/IBackendVoice.hpp"
#include "amuse/IBackendVoiceAllocator.hpp"
#include "amuse/AudioGroup.hpp"
#include "amuse/Common.hpp"
#include "amuse/Engine.hpp"
#include "amuse/dsp.h"
#include <cmath>
#include <string.h>
namespace amuse
@ -29,6 +33,27 @@ Voice::Voice(Engine& engine, const AudioGroup& group, ObjectId oid, int vid, boo
m_submix->m_activeVoices.insert(this);
}
void Voice::_reset()
{
m_curVol = 1.f;
m_curReverbVol = 0.f;
m_curPan = 0.f;
m_curSpan = 0.f;
m_pitchSweep1 = 0;
m_pitchSweep1Times = 0;
m_pitchSweep2 = 0;
m_pitchSweep2Times = 0;
m_envelopeTime = -1.f;
m_panningTime = -1.f;
m_vibratoLevel = 0;
m_vibratoModLevel = 0;
m_vibratoPeriod = 0.f;
m_tremoloScale = 0.f;
m_tremoloModScale = 0.f;
m_lfoPeriods[0] = 0.f;
m_lfoPeriods[1] = 0.f;
}
bool Voice::_checkSamplePos()
{
if (m_curSamplePos >= m_lastSamplePos)
@ -53,11 +78,157 @@ bool Voice::_checkSamplePos()
void Voice::_doKeyOff()
{
m_volAdsr.keyOff();
m_pitchAdsr.keyOff();
}
void Voice::_setTotalPitch(int32_t cents)
{
int32_t interval = cents - m_curSample->first.m_pitch * 100;
double ratio = std::pow(2.0, interval / 1200.0);
m_sampleRate = m_curSample->first.m_sampleRate * ratio;
m_backendVoice->setPitchRatio(ratio);
}
Voice* Voice::_allocateVoice(double sampleRate, bool dynamicPitch)
{
auto it = m_childVoices.emplace(m_childVoices.end(), m_engine, m_audioGroup,
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;
return &m_childVoices.back();
}
std::list<Voice>::iterator Voice::_destroyVoice(Voice* voice)
{
voice->_destroy();
return m_childVoices.erase(voice->m_engineIt);
}
bool Voice::_advanceSample(int16_t& samp)
{
double dt = 1.0 / m_sampleRate;
m_voiceTime += dt;
bool refresh = false;
/* Process active envelope */
if (m_envelopeTime >= 0.0)
{
m_envelopeTime += dt;
float start = m_envelopeStart / 127.f;
float end = m_envelopeEnd / 127.f;
double t = std::max(0.0, std::min(1.0, m_envelopeTime / m_envelopeDur));
if (m_envelopeCurve)
t = (*m_envelopeCurve)[int(t*127.f)] / 127.f;
m_curVol = (start * (1.0f - t)) + (end * t);
/* Done with envelope */
if (m_envelopeTime > m_envelopeDur)
m_envelopeTime = -1.f;
}
/* Factor in ADSR envelope state */
float totalVol = m_curVol * m_volAdsr.nextSample(m_sampleRate);
/* Apply tremolo */
if (m_state.m_tremoloSel && (m_tremoloScale || m_tremoloModScale))
{
float t = m_state.m_tremoloSel.evaluate(*this, m_state);
if (m_tremoloScale && m_tremoloModScale)
{
float fac = (1.0f - t) + (m_tremoloScale * t);
float modT = getModWheel() / 127.f;
float modFac = (1.0f - modT) + (m_tremoloModScale * modT);
totalVol *= fac * modFac;
}
else if (m_tremoloScale)
{
float fac = (1.0f - t) + (m_tremoloScale * t);
totalVol *= fac;
}
else if (m_tremoloModScale)
{
float modT = getModWheel() / 127.f;
float modFac = (1.0f - modT) + (m_tremoloModScale * modT);
totalVol *= modFac;
}
}
/* Multiply sample with total volume */
samp = ClampFull<int16_t>(samp * totalVol);
/* Process active pan-sweep */
if (m_panningTime >= 0.f)
{
m_panningTime += dt;
float start = (m_panPos - 64) / 64.f;
float end = (m_panPos + m_panWidth - 64) / 64.f;
float t = std::max(0.f, std::min(1.f, m_panningTime / m_panningDur));
setPan((start * (1.0f - t)) + (end * t));
refresh = true;
/* Done with panning */
if (m_panningTime > m_panningDur)
m_panningTime = -1.f;
}
/* Process active span-sweep */
if (m_spanningTime >= 0.f)
{
m_spanningTime += dt;
float start = (m_spanPos - 64) / 64.f;
float end = (m_spanPos + m_spanWidth - 64) / 64.f;
float t = std::max(0.f, std::min(1.f, m_spanningTime / m_spanningDur));
setSurroundPan((start * (1.0f - t)) + (end * t));
refresh = true;
/* Done with spanning */
if (m_spanningTime > m_spanningDur)
m_spanningTime = -1.f;
}
/* Calculate total pitch */
int32_t totalPitch = m_curPitch;
bool pitchDirty = m_pitchDirty;
m_pitchDirty = false;
if (m_pitchEnv)
{
totalPitch = m_curPitch * m_pitchAdsr.nextSample(m_sampleRate);
pitchDirty = true;
}
/* Process pitch sweep 1 */
if (m_pitchSweep1Times)
{
m_pitchSweep1 += m_pitchSweep1Add;
--m_pitchSweep1Times;
pitchDirty = true;
}
/* Process pitch sweep 2 */
if (m_pitchSweep2Times)
{
m_pitchSweep2 += m_pitchSweep2Add;
--m_pitchSweep2Times;
pitchDirty = true;
}
/* Apply total pitch */
if (pitchDirty)
{
_setTotalPitch(totalPitch + m_pitchSweep1 + m_pitchSweep2);
refresh = true;
}
/* True if backend voice needs reconfiguration before next sample */
return refresh;
}
size_t Voice::supplyAudio(size_t samples, int16_t* data)
{
uint32_t samplesRem = samples;
size_t samplesProc = 0;
if (m_curSample)
{
uint32_t block = m_curSamplePos / 14;
@ -70,7 +241,16 @@ size_t Voice::supplyAudio(size_t samples, int16_t* data)
&m_prev1, &m_prev2, rem,
std::min(samplesRem,
m_lastSamplePos - block * 14));
m_curSamplePos += decSamples;
/* Per-sample processing */
for (int i=0 ; i<decSamples ; ++i)
{
++samplesProc;
++m_curSamplePos;
if (_advanceSample(data[i]))
return samplesProc;
}
samplesRem -= decSamples;
data += decSamples;
}
@ -90,7 +270,16 @@ size_t Voice::supplyAudio(size_t samples, int16_t* data)
&m_prev1, &m_prev2,
std::min(samplesRem,
m_lastSamplePos - block * 14));
m_curSamplePos += decSamples;
/* Per-sample processing */
for (int i=0 ; i<decSamples ; ++i)
{
++samplesProc;
++m_curSamplePos;
if (_advanceSample(data[i]))
return samplesProc;
}
samplesRem -= decSamples;
data += decSamples;
@ -108,12 +297,42 @@ size_t Voice::supplyAudio(size_t samples, int16_t* data)
return samples;
}
Voice* Voice::startChildMacro(int8_t addNote, ObjectId macroId, int macroStep)
int Voice::maxVid() const
{
int maxVid = m_vid;
for (const Voice& vox : m_childVoices)
maxVid = std::max(maxVid, vox.maxVid());
return maxVid;
}
bool Voice::loadSoundMacro(ObjectId macroId, int macroStep, bool pushPc)
Voice* Voice::startChildMacro(int8_t addNote, ObjectId macroId, int macroStep)
{
Voice* vox = _allocateVoice(32000.0, true);
vox->loadSoundMacro(macroId, macroStep, 1000.f, m_state.m_initKey + addNote,
m_state.m_initVel, m_state.m_initMod);
return vox;
}
bool Voice::loadSoundMacro(ObjectId macroId, int macroStep, float ticksPerSec,
uint8_t midiKey, uint8_t midiVel, uint8_t midiMod,
bool pushPc)
{
const unsigned char* macroData = m_audioGroup.getPool().soundMacro(macroId);
if (!macroData)
return false;
if (m_state.m_pc.empty())
m_state.initialize(macroData, macroStep, ticksPerSec, midiKey, midiVel, midiMod);
else
{
if (!pushPc)
m_state.m_pc.clear();
m_state.m_pc.push_back({macroData, macroStep});
m_state.m_header = *reinterpret_cast<const SoundMacroState::Header*>(macroData);
m_state.m_header.swapBig();
}
return true;
}
void Voice::keyOff()
@ -133,6 +352,10 @@ void Voice::startSample(int16_t sampId, int32_t offset)
m_curSample = m_audioGroup.getSample(sampId);
if (m_curSample)
{
_reset();
m_sampleRate = m_curSample->first.m_sampleRate;
m_curPitch = m_curSample->first.m_pitch;
m_pitchDirty = true;
m_backendVoice->stop();
m_backendVoice->resetSampleRate(m_curSample->first.m_sampleRate);
m_backendVoice->start();
@ -154,18 +377,57 @@ void Voice::stopSample()
void Voice::setVolume(float vol)
{
m_curVol = vol;
}
void Voice::setPanning(float pan)
void Voice::setPan(float pan)
{
m_curPan = pan;
}
void Voice::setSurroundPanning(float span)
void Voice::setSurroundPan(float span)
{
m_curSpan = span;
}
void Voice::startEnvelope(double dur, float vol, const Curve* envCurve)
{
m_envelopeTime = m_voiceTime;
m_envelopeDur = dur;
m_envelopeStart = m_curVol;
m_envelopeEnd = vol;
m_envelopeCurve = envCurve;
}
void Voice::startFadeIn(double dur, float vol, const Curve* envCurve)
{
m_envelopeTime = m_voiceTime;
m_envelopeDur = dur;
m_envelopeStart = 0.f;
m_envelopeEnd = m_curVol;
m_envelopeCurve = envCurve;
}
void Voice::startPanning(double dur, uint8_t panPos, uint8_t panWidth)
{
m_panningTime = m_voiceTime;
m_panningDur = dur;
m_panPos = panPos;
m_panWidth = panWidth;
}
void Voice::startSpanning(double dur, uint8_t spanPos, uint8_t spanWidth)
{
m_spanningTime = m_voiceTime;
m_spanningDur = dur;
m_spanPos = spanPos;
m_spanWidth = spanWidth;
}
void Voice::setPitchKey(int32_t cents)
{
m_curPitch = cents;
m_pitchDirty = true;
}
void Voice::setModulation(float mod)
@ -186,20 +448,62 @@ void Voice::setDoppler(float doppler)
{
}
void Voice::setVibrato(int32_t level, int32_t modLevel, float period)
{
m_vibratoLevel = level;
m_vibratoModLevel = modLevel;
m_vibratoPeriod = period;
}
void Voice::setMod2VibratoRange(int32_t modLevel)
{
m_vibratoModLevel = modLevel;
}
void Voice::setTremolo(float tremoloScale, float tremoloModScale)
{
m_tremoloScale = tremoloScale;
m_tremoloModScale = tremoloModScale;
}
void Voice::setPitchSweep1(uint8_t times, int16_t add)
{
m_pitchSweep1 = 0;
m_pitchSweep1Times = times;
m_pitchSweep1Add = add;
}
void Voice::setPitchSweep2(uint8_t times, int16_t add)
{
m_pitchSweep2 = 0;
m_pitchSweep2Times = times;
m_pitchSweep2Add = add;
}
void Voice::setReverbVol(float rvol)
{
m_curReverbVol = rvol;
}
void Voice::setAdsr(ObjectId adsrId)
{
const ADSR* adsr = m_audioGroup.getPool().tableAsAdsr(adsrId);
m_volAdsr.reset(adsr);
}
void Voice::setPitchFrequency(uint32_t hz, uint16_t fine)
{
m_sampleRate = hz + fine / 65536.0;
m_backendVoice->setPitchRatio(1.0);
m_backendVoice->resetSampleRate(m_sampleRate);
}
void Voice::setPitchAdsr(ObjectId adsrId, int32_t cents)
{
const ADSR* adsr = m_audioGroup.getPool().tableAsAdsr(adsrId);
m_pitchAdsr.reset(adsr);
m_pitchEnvRange = cents;
m_pitchEnv = true;
}
void Voice::setPitchWheelRange(int8_t up, int8_t down)