diff --git a/src/seq/sequenceevent.cpp b/src/seq/sequenceevent.cpp index 98a9201..b8e9c06 100644 --- a/src/seq/sequenceevent.cpp +++ b/src/seq/sequenceevent.cpp @@ -1,10 +1,23 @@ #include "sequenceevent.h" +#include +#include +#include -static uint64_t _nextPlaybackID = 1; +static std::atomic_uint64_t _nextPlaybackID = 1; + +bool SequenceEvent::isNoteEvent() const +{ + return false; +} + +bool BaseNoteEvent::isNoteEvent() const +{ + return true; +} inline uint64_t BaseNoteEvent::nextPlaybackID() { - return _nextPlaybackID++; + return _nextPlaybackID.fetch_add(1, std::memory_order_relaxed); } BaseNoteEvent::BaseNoteEvent() @@ -21,6 +34,7 @@ void BaseNoteEvent::setEnvelope(double attack, double decay, double sustain, dou this->attack = attack; this->decay = decay; this->sustain = sustain; + this->fade = HUGE_VAL; this->release = release; } @@ -32,6 +46,7 @@ void BaseNoteEvent::setEnvelope(double attack, double hold, double decay, double this->hold = hold; this->decay = decay; this->sustain = sustain; + this->fade = HUGE_VAL; this->release = release; } @@ -43,6 +58,19 @@ void BaseNoteEvent::setEnvelope(double start, double attack, double hold, double this->hold = hold; this->decay = decay; this->sustain = sustain; + this->fade = HUGE_VAL; + this->release = release; +} + +void BaseNoteEvent::setEnvelope(double start, double attack, double hold, double decay, double sustain, double fade, double release) +{ + useEnvelope = true; + this->startGain = start; + this->attack = attack; + this->hold = hold; + this->decay = decay; + this->sustain = sustain; + this->fade = fade; this->release = release; } diff --git a/src/seq/sequenceevent.h b/src/seq/sequenceevent.h index 701de67..8b81f7f 100644 --- a/src/seq/sequenceevent.h +++ b/src/seq/sequenceevent.h @@ -22,9 +22,10 @@ class SequenceEvent { double timestamp; virtual int eventType() const = 0; + virtual bool isNoteEvent() const; template - const T* cast() const { + inline const T* cast() const { if (eventType() == T::TypeID) { return static_cast(this); } @@ -32,26 +33,41 @@ class SequenceEvent { } template - T* cast() { + inline T* cast() { if (eventType() == T::TypeID) { return static_cast(this); } return nullptr; } + + template + static inline std::shared_ptr castShared(std::shared_ptr& event) { + if (event->eventType() == T::TypeID) { + return std::static_pointer_cast(event); + } + return nullptr; + } }; -template +template class BaseEvent : public SequenceEvent { public: enum { TypeID = TYPE_ID }; virtual int eventType() const { return TypeID; } + + template + static inline std::shared_ptr castShared(std::shared_ptr& event) { + return SequenceEvent::castShared(event); + } }; -struct BaseNoteEvent { +struct BaseNoteEvent : public SequenceEvent { static uint64_t nextPlaybackID(); BaseNoteEvent(); + virtual bool isNoteEvent() const; + uint64_t playbackID; // for modulation double duration; double volume; @@ -60,18 +76,53 @@ struct BaseNoteEvent { bool useEnvelope : 1; bool expAttack : 1; bool expDecay : 1; - double startGain, attack, hold, decay, sustain, release; + double startGain, attack, hold, decay, sustain, fade, release; void setEnvelope(double attack, double decay, double sustain, double release); void setEnvelope(double attack, double hold, double decay, double sustain, double release); void setEnvelope(double start, double attack, double hold, double decay, double sustain, double release); + void setEnvelope(double start, double attack, double hold, double decay, double sustain, double fade, double release); + + template + static inline std::shared_ptr castShared(std::shared_ptr& event) { + if (event->isNoteEvent()) { + return std::static_pointer_cast(event); + } + return nullptr; + } }; -template -class NoteEvent : public BaseEvent, public BaseNoteEvent { +template <> +inline const BaseNoteEvent* SequenceEvent::cast() const +{ + if (isNoteEvent()) { + return static_cast(this); + } + return nullptr; +} + +template <> +inline BaseNoteEvent* SequenceEvent::cast() +{ + if (isNoteEvent()) { + return static_cast(this); + } + return nullptr; +} + +template +class NoteEvent : public BaseNoteEvent { public: + enum { TypeID = TYPE_ID }; + + virtual int eventType() const { return TypeID; } + + template + static inline std::shared_ptr castShared(std::shared_ptr& event) { + return SequenceEvent::castShared(event); + } }; -class SampleEvent : public NoteEvent { +class SampleEvent : public NoteEvent { public: SampleEvent(); @@ -79,7 +130,7 @@ class SampleEvent : public NoteEvent { double pitchBend; }; -class OscillatorEvent : public NoteEvent { +class OscillatorEvent : public NoteEvent { public: OscillatorEvent(); @@ -87,14 +138,14 @@ class OscillatorEvent : public NoteEvent { double frequency; }; -class AudioNodeEvent : public NoteEvent { +class AudioNodeEvent : public NoteEvent { public: AudioNodeEvent(std::shared_ptr<::AudioNode> node); std::shared_ptr<::AudioNode> node; }; -class ModulatorEvent : public BaseEvent { +class ModulatorEvent : public BaseEvent { public: ModulatorEvent(uint64_t playbackID, int32_t param, double value); @@ -103,7 +154,7 @@ class ModulatorEvent : public BaseEvent { double value; }; -class KillEvent : public BaseEvent { +class KillEvent : public BaseEvent { public: KillEvent(uint64_t playbackID, double timestamp); @@ -111,7 +162,7 @@ class KillEvent : public BaseEvent { bool immediate; }; -class ChannelEvent : public BaseEvent { +class ChannelEvent : public BaseEvent { public: ChannelEvent(uint32_t param, double value); diff --git a/src/synth/channel.cpp b/src/synth/channel.cpp index 5c4d557..d748123 100644 --- a/src/synth/channel.cpp +++ b/src/synth/channel.cpp @@ -1,21 +1,29 @@ #include "channel.h" -#include "oscillator.h" -#include "sampler.h" #include "synthcontext.h" #include "s2wcontext.h" +#include "iinstrument.h" #include "seq/itrack.h" #include "seq/sequenceevent.h" #include -Channel::Note::Note(std::shared_ptr event, std::shared_ptr source, double duration) +Channel::Note::Note() +: event(nullptr), source(nullptr), duration(0), kill(true) +{ + // initializers only +} + +Channel::Note::Note(std::shared_ptr event, std::shared_ptr source, double duration) : event(event), source(source), duration(duration), kill(false) { // initializers only } Channel::Channel(const SynthContext* ctx, ITrack* track) -: gain(ctx, 0.5), pan(ctx, 0.5), ctx(ctx), track(track), nextEvent(nullptr) +: AudioParamContainer(ctx), track(track), nextEvent(nullptr) { + instrument = ctx->defaultInstrument(); + gain = addParam(AudioNode::Gain, 0.5).get(); + pan = addParam(AudioNode::Pan, 0.5).get(); timestamp = 0; } @@ -42,50 +50,22 @@ uint32_t Channel::fillBuffer(std::vector& buffer, ssize_t numSamples) nextEvent = event; break; } - std::shared_ptr noteNode = nullptr; - BaseNoteEvent* noteEvent = nullptr; - Note* note = nullptr; - if (ChannelEvent* chEvent = event->cast()) { - // TODO: queuing - // TODO: more types - if (chEvent->param == AudioNode::Gain) { - gain = chEvent->value; - } else if (chEvent->param == AudioNode::Pan) { - pan = chEvent->value; + if (auto chEvent = ChannelEvent::castShared(event)) { + instrument->channelEvent(this, chEvent); + } else if (auto modEvent = ModulatorEvent::castShared(event)) { + instrument->modulatorEvent(this, modEvent); + } else if (auto noteEvent = BaseNoteEvent::castShared(event)) { + Note* note = instrument->noteEvent(this, noteEvent); + if (note && !note->kill) { + notes.emplace(std::make_pair(note->event->playbackID, note)); } - //std::cout << "ChannelEvent " << gain.valueAt(timestamp) << std::endl; - } else if (ModulatorEvent* modEvent = event->cast()) { - auto noteIter = notes.find(modEvent->playbackID); - if (noteIter != notes.end()) { - auto param = noteIter->second->source->param(modEvent->param); - if (param) { - param->setConstant(modEvent->value); - } + } else if (event->eventType() >= SequenceEvent::UserBase) { + // This must go after the BaseNoteEvent case because user-defined + // note events should be sent there. + Note* note = instrument->userEvent(this, event); + if (note && !note->kill) { + notes.emplace(std::make_pair(note->event->playbackID, note)); } - } else if (AudioNodeEvent* nodeEvent = event->cast()) { - noteEvent = nodeEvent; - noteNode = nodeEvent->node; - note = new Note(event, noteNode, 0); - notes.emplace(std::make_pair(nodeEvent->playbackID, note)); - } else if (OscillatorEvent* oscEvent = event->cast()) { - noteEvent = oscEvent; - BaseOscillator* osc = BaseOscillator::create(ctx, oscEvent->waveformID, oscEvent->frequency, oscEvent->volume, oscEvent->pan); - noteNode.reset(osc); - note = new Note(event, noteNode, oscEvent->duration); - notes.emplace(std::make_pair(oscEvent->playbackID, note)); - } else if (SampleEvent* sampEvent = event->cast()) { - noteEvent = sampEvent; - SampleData* sampleData = ctx->s2wContext()->getSample(sampEvent->sampleID); - if (!sampleData) { - std::cerr << "ERROR: sample " << std::hex << sampEvent->sampleID << std::dec << " not found" << std::endl; - continue; - } - Sampler* samp = new Sampler(ctx, sampleData, sampEvent->pitchBend); - noteNode.reset(samp); - samp->param(AudioNode::Gain)->setConstant(sampEvent->volume); - samp->param(AudioNode::Pan)->setConstant(sampEvent->pan); - note = new Note(event, noteNode, sampEvent->duration != 0 ? sampEvent->duration : sampleData->duration()); - notes.emplace(std::make_pair(sampEvent->playbackID, note)); } else if (KillEvent* killEvent = event->cast()) { auto noteIter = notes.find(killEvent->playbackID); if (noteIter != notes.end()) { @@ -93,20 +73,11 @@ uint32_t Channel::fillBuffer(std::vector& buffer, ssize_t numSamples) noteIter->second->kill = noteIter->second->kill || killEvent->immediate; } } - if (noteEvent && noteEvent->useEnvelope) { - Envelope* env = new Envelope(ctx, noteEvent->attack, noteEvent->hold, noteEvent->decay, noteEvent->sustain, noteEvent->release); - env->expAttack = noteEvent->expAttack; - env->expDecay = noteEvent->expDecay; - env->param(Envelope::StartGain)->setConstant(noteEvent->startGain); - env->connect(noteNode); - noteNode.reset(env); - note->source = noteNode; - } } } while (event); int pos = 0; while (pos < buffer.size() && !isFinished()) { - double panValue = ctx->outputChannels > 1 ? pan.valueAt(timestamp) : 1; + double panValue = ctx->outputChannels > 1 ? pan->valueAt(timestamp) : 1; for (int ch = 0; ch < ctx->outputChannels; ch++) { int32_t sample = 0; std::vector toErase; @@ -139,7 +110,7 @@ uint32_t Channel::fillBuffer(std::vector& buffer, ssize_t numSamples) for (uint64_t id : toErase) { notes.erase(id); } - buffer[pos] = sample * gain.valueAt(timestamp) * panValue; + buffer[pos] = sample * gain->valueAt(timestamp) * panValue; panValue = 1 - panValue; ++pos; } diff --git a/src/synth/channel.h b/src/synth/channel.h index 5045f4c..0799e4e 100644 --- a/src/synth/channel.h +++ b/src/synth/channel.h @@ -11,37 +11,41 @@ class SynthContext; class ITrack; class SequenceEvent; +class BaseNoteEvent; +class IInstrument; -class Channel { +class Channel : public AudioParamContainer { friend class SynthContext; public: Channel(const SynthContext* ctx, ITrack* track); ~Channel(); - AudioParam gain, pan; + AudioParam* gain; + AudioParam* pan; void seek(double timestamp); uint32_t fillBuffer(std::vector& buffer, ssize_t numSamples = -1); bool isFinished() const; -private: - const SynthContext* const ctx; - ITrack* track; - std::shared_ptr nextEvent; - struct Note { - Note(std::shared_ptr event, std::shared_ptr source, double duration); + Note(); + Note(std::shared_ptr event, std::shared_ptr source, double duration); Note(const Note& other) = default; Note(Note&& other) = default; Note& operator=(const Note& other) = default; - std::shared_ptr event; + std::shared_ptr event; std::shared_ptr source; double duration; bool kill; }; std::unordered_map> notes; +private: + ITrack* track; + IInstrument* instrument; + std::shared_ptr nextEvent; + double timestamp; }; diff --git a/src/synth/envelope.cpp b/src/synth/envelope.cpp index 498a14e..9bb9f56 100644 --- a/src/synth/envelope.cpp +++ b/src/synth/envelope.cpp @@ -3,6 +3,12 @@ #include Envelope::Envelope(const SynthContext* ctx, double attack, double hold, double decay, double sustain, double release) +: Envelope(ctx, attack, hold, decay, sustain, HUGE_VAL, release) +{ + // forwarded constructor only +} + +Envelope::Envelope(const SynthContext* ctx, double attack, double hold, double decay, double sustain, double fade, double release) : FilterNode(ctx), expAttack(false), expDecay(false), stepAt(0), lastLevel(0), step(Attack) { addParam(StartGain, 0.0); @@ -10,6 +16,7 @@ Envelope::Envelope(const SynthContext* ctx, double attack, double hold, double d addParam(Hold, hold); addParam(Decay, decay); addParam(Sustain, sustain); + addParam(Fade, fade); addParam(Release, release); addParam(Trigger, 1.0); } @@ -67,7 +74,20 @@ int16_t Envelope::filterSample(double time, int channel, int16_t sample) stepAt = time; } case Sustain: { - lastLevel = paramValue(Sustain, time); + double f = paramValue(Fade, time); + if (std::isfinite(f)) { + if (expDecay && f < 0) { + double dt = time - stepAt; + lastLevel = fastExp(-f, dt); + if (lastLevel < 1.0 / 32767.0) { + lastLevel = 0; + } + } else if (time < f + stepAt) { + lastLevel = lerp(paramValue(Sustain, time), 0.0, time, stepAt, f + stepAt); + } + } else { + lastLevel = paramValue(Sustain, time); + } return lastLevel * sample; } case Release: { diff --git a/src/synth/envelope.h b/src/synth/envelope.h index 1cc1a09..47a3056 100644 --- a/src/synth/envelope.h +++ b/src/synth/envelope.h @@ -13,10 +13,12 @@ class Envelope : public FilterNode Hold = 'hold', Decay = 'decy', Sustain = 'sus ', + Fade = 'fade', Release = 'rels', }; Envelope(const SynthContext* ctx, double attack, double hold, double decay, double sustain, double release); + Envelope(const SynthContext* ctx, double attack, double hold, double decay, double sustain, double fade, double release); virtual bool isActive() const; bool expAttack, expDecay; diff --git a/src/synth/iinstrument.cpp b/src/synth/iinstrument.cpp new file mode 100644 index 0000000..6eaf367 --- /dev/null +++ b/src/synth/iinstrument.cpp @@ -0,0 +1,79 @@ +#include "iinstrument.h" +#include "channel.h" +#include "sampler.h" +#include "oscillator.h" +#include "synthcontext.h" +#include "s2wcontext.h" +#include "seq/sequenceevent.h" +#include + +void IInstrument::channelEvent(Channel* channel, std::shared_ptr event) +{ + // TODO: queuing + // TODO: more types + auto target = channel->param(event->param); + if (target) { + target->setConstant(event->value); + } +} + +void IInstrument::modulatorEvent(Channel* channel, std::shared_ptr event) +{ + auto noteIter = channel->notes.find(event->playbackID); + if (noteIter != channel->notes.end()) { + auto param = noteIter->second->source->param(event->param); + if (param) { + param->setConstant(event->value); + } + } +} + +Channel::Note* IInstrument::userEvent(Channel* channel, std::shared_ptr event) +{ + std::cerr << "ERROR: unhandled user event" << std::endl; + return nullptr; +} + +Channel::Note* DefaultInstrument::noteEvent(Channel* channel, std::shared_ptr event) +{ + std::shared_ptr node; + double duration = event->duration; + if (AudioNodeEvent* nodeEvent = event->cast()) { + node = nodeEvent->node; + } else if (OscillatorEvent* oscEvent = event->cast()) { + BaseOscillator* osc = BaseOscillator::create(channel->ctx, oscEvent->waveformID, oscEvent->frequency, oscEvent->volume, oscEvent->pan); + duration = oscEvent->duration; + node.reset(osc); + } else if (SampleEvent* sampEvent = event->cast()) { + SampleData* sampleData = channel->ctx->s2wContext()->getSample(sampEvent->sampleID); + if (!sampleData) { + std::cerr << "ERROR: sample " << std::hex << sampEvent->sampleID << std::dec << " not found" << std::endl; + return nullptr; + } + Sampler* samp = new Sampler(channel->ctx, sampleData, sampEvent->pitchBend); + node.reset(samp); + samp->param(AudioNode::Gain)->setConstant(sampEvent->volume); + samp->param(AudioNode::Pan)->setConstant(sampEvent->pan); + if (!duration) { + duration = sampleData->duration(); + } + } else { + std::cerr << "ERROR: unhandled user event" << std::endl; + return nullptr; + } + Channel::Note* note = new Channel::Note(std::shared_ptr(event), node, duration); + if (event->useEnvelope) { + applyEnvelope(channel, note, event); + } + return note; +} + +void DefaultInstrument::applyEnvelope(Channel* channel, Channel::Note* note, std::shared_ptr event) +{ + Envelope* env = new Envelope(channel->ctx, event->attack, event->hold, event->decay, event->sustain, event->fade, event->release); + env->expAttack = event->expAttack; + env->expDecay = event->expDecay; + env->param(Envelope::StartGain)->setConstant(event->startGain); + env->connect(note->source); + note->source.reset(env); +} diff --git a/src/synth/iinstrument.h b/src/synth/iinstrument.h new file mode 100644 index 0000000..08ad175 --- /dev/null +++ b/src/synth/iinstrument.h @@ -0,0 +1,28 @@ +#ifndef S2W_IINSTRUMENT_H +#define S2W_IINSTRUMENT_H + +#include +#include "channel.h" +class SequenceEvent; +class BaseNoteEvent; +class ChannelEvent; +class ModulatorEvent; +class AudioNode; +class Channel; + +class IInstrument { +public: + virtual void channelEvent(Channel* channel, std::shared_ptr event); + virtual Channel::Note* noteEvent(Channel* channel, std::shared_ptr event) = 0; + virtual void modulatorEvent(Channel* channel, std::shared_ptr event); + virtual Channel::Note* userEvent(Channel* channel, std::shared_ptr event); +}; + +class DefaultInstrument : public IInstrument { +public: + virtual Channel::Note* noteEvent(Channel* channel, std::shared_ptr event); + + void applyEnvelope(Channel* channel, Channel::Note* note, std::shared_ptr event); +}; + +#endif diff --git a/src/synth/synthcontext.cpp b/src/synth/synthcontext.cpp index 03ec652..6d579fb 100644 --- a/src/synth/synthcontext.cpp +++ b/src/synth/synthcontext.cpp @@ -4,6 +4,7 @@ #include "iinterpolator.h" #include "riffwriter.h" #include "utility.h" +#include "iinstrument.h" static const int BUFFER_SIZE = 10240; @@ -12,7 +13,7 @@ SynthContext::SynthContext(S2WContext* ctx, double sampleRate, int outputChannel interpolator(IInterpolator::get(IInterpolator::Zero)), mixBuffer(BUFFER_SIZE >> 1), currentTimestamp(0), maximumTimestamp(0), ctx(ctx) { - // initializers only + defaultInst.reset(new DefaultInstrument); } SynthContext::~SynthContext() @@ -98,3 +99,21 @@ void SynthContext::save(RiffWriter* riff) } while (written > 0); } +void SynthContext::registerInstrument(uint64_t id, std::unique_ptr&& inst) +{ + instruments[id] = std::move(inst); +} + +IInstrument* SynthContext::getInstrument(uint64_t id) const +{ + auto iter = instruments.find(id); + if (iter == instruments.end()) { + return nullptr; + } + return iter->second.get(); +} + +IInstrument* SynthContext::defaultInstrument() const +{ + return defaultInst.get(); +} diff --git a/src/synth/synthcontext.h b/src/synth/synthcontext.h index 4d147b3..b61e059 100644 --- a/src/synth/synthcontext.h +++ b/src/synth/synthcontext.h @@ -4,10 +4,12 @@ #include #include #include +#include class Channel; class IInterpolator; class ITrack; class RiffWriter; +class IInstrument; class S2WContext; class SynthContext { @@ -33,7 +35,13 @@ class SynthContext { void stream(std::ostream& output); void save(RiffWriter* riff); + void registerInstrument(uint64_t id, std::unique_ptr&& inst); + IInstrument* getInstrument(uint64_t id) const; + IInstrument* defaultInstrument() const; + private: + std::unordered_map> instruments; + std::unique_ptr defaultInst; std::vector mixBuffer; double currentTimestamp, maximumTimestamp; S2WContext* ctx;