diff --git a/.gitignore b/.gitignore index 62c405e..3d68d95 100644 --- a/.gitignore +++ b/.gitignore @@ -5,8 +5,7 @@ build build_win include -seq2wav -seq2wav_d +seq2wav_test* *.dll *.a *.so diff --git a/Makefile b/Makefile index 703020a..8b838ee 100644 --- a/Makefile +++ b/Makefile @@ -1,8 +1,8 @@ include config.mak -all: seq2wav$(EXE) +all: seq2wav_test$(EXE) -debug: seq2wav_d$(EXE) +debug: seq2wav_test_d$(EXE) static: $(BUILDPATH)/libseq2wav.a @@ -13,7 +13,7 @@ includes: $(INCLUDES) build/Makefile.d: $(wildcard src/*.cpp src/*/*.cpp src/*.h src/*/*.h) Makefile src/Makefile $(MAKE) -C src ../build/Makefile.d -seq2wav$(EXE) seq2wav_d$(EXE): src/Makefile build/Makefile.d +seq2wav_test$(EXE) seq2wav_test_d$(EXE): src/Makefile build/Makefile.d $(MAKE) -C src ../$@ $(BUILDPATH)/libseq2wav.a $(BUILDPATH)/libseq2wav_d.a: src/Makefile build/Makefile.d $(INCLUDES) diff --git a/src/Makefile b/src/Makefile index 5e69959..e20023c 100644 --- a/src/Makefile +++ b/src/Makefile @@ -2,10 +2,10 @@ include ../config.mak SOURCES = $(filter-out main.cpp, $(wildcard *.cpp */*.cpp)) -../seq2wav$(EXE): ../$(BUILDPATH)/main.o ../$(BUILDPATH)/libseq2wav.a +../seq2wav_test$(EXE): ../$(BUILDPATH)/main.o ../$(BUILDPATH)/libseq2wav.a $(CXX) -o $@ $^ -../seq2wav_d$(EXE): ../$(BUILDPATH)/main_d.o ../$(BUILDPATH)/libseq2wav_d.$(DLL) +../seq2wav_test_d$(EXE): ../$(BUILDPATH)/main_d.o ../$(BUILDPATH)/libseq2wav_d.$(DLL) $(CXX) -o $@ $^ ../$(BUILDPATH)/libseq2wav.a: $(patsubst %.cpp, ../$(BUILDPATH)/%.o, $(SOURCES)) ../$(BUILDPATH)/Makefile.d diff --git a/src/codec/riffcodec.cpp b/src/codec/riffcodec.cpp index 25dbd2a..2ef2ccb 100644 --- a/src/codec/riffcodec.cpp +++ b/src/codec/riffcodec.cpp @@ -61,16 +61,17 @@ SampleData* RiffCodec::decodeRange(std::vector::const_iterator start, s if (offset + 8 + chunkSize > size) { throw std::runtime_error("RIFF chunk data is invalid"); } + offset += 8; if (magic == 'fmt ') { - fmt = WaveFormatEx(start + offset + 8, start + offset + chunkSize); + fmt = WaveFormatEx(start + offset, start + offset + chunkSize); } else if (magic == 'data') { data.insert( data.end(), - buffer + (offset + 8), + buffer + offset, buffer + (offset + chunkSize) ); } - offset += 8 + chunkSize; + offset += chunkSize; } std::unique_ptr codec; if (fmt.format == 1) { diff --git a/src/main.cpp b/src/main.cpp index 3ed102d..d1c2c78 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,12 +1,14 @@ #include "seq/testsequence.h" #include "synth/channel.h" #include "synth/synthcontext.h" +#include "s2wcontext.h" #include "riffwriter.h" #include int main(int argc, char** argv) { - TestSequence seq; - SynthContext ctx(44100); + S2WContext s2w; + TestSequence seq(&s2w); + SynthContext ctx(&s2w, 44100); int numTracks = seq.numTracks(); for (int i = 0; i < numTracks; i++) { ctx.channels.emplace_back(new Channel(&ctx, seq.getTrack(i))); diff --git a/src/seq/sequenceevent.cpp b/src/seq/sequenceevent.cpp index b8e9c06..a3fd037 100644 --- a/src/seq/sequenceevent.cpp +++ b/src/seq/sequenceevent.cpp @@ -86,6 +86,12 @@ OscillatorEvent::OscillatorEvent() // initializers only } +InstrumentNoteEvent::InstrumentNoteEvent() +: pitch(69) +{ + // initializers only +} + AudioNodeEvent::AudioNodeEvent(std::shared_ptr<::AudioNode> node) : node(node) { @@ -109,3 +115,9 @@ ChannelEvent::ChannelEvent(uint32_t param, double value) { // initializers only } + +ChannelEvent::ChannelEvent(uint32_t param, uint64_t intValue) +: param(param), intValue(intValue) +{ + // initializers only +} diff --git a/src/seq/sequenceevent.h b/src/seq/sequenceevent.h index 8b81f7f..1d7e783 100644 --- a/src/seq/sequenceevent.h +++ b/src/seq/sequenceevent.h @@ -2,6 +2,7 @@ #define S2W_SEQUENCEEVENT_H #include +#include #include #include "synth/audionode.h" @@ -12,10 +13,11 @@ class SequenceEvent { enum EventTypes { Sample, Oscillator, + InstrumentNote, + AudioNode, + Channel, Modulator, Kill, - Channel, - AudioNode, UserBase, }; @@ -138,6 +140,15 @@ class OscillatorEvent : public NoteEvent { +public: + InstrumentNoteEvent(); + + double pitch; + std::vector floatParams; + std::vector intParams; +}; + class AudioNodeEvent : public NoteEvent { public: AudioNodeEvent(std::shared_ptr<::AudioNode> node); @@ -165,9 +176,13 @@ class KillEvent : public BaseEvent { class ChannelEvent : public BaseEvent { public: ChannelEvent(uint32_t param, double value); + ChannelEvent(uint32_t param, uint64_t intValue); uint32_t param; - double value; + union { + double value; + uint64_t intValue; + }; }; #endif diff --git a/src/seq/testsequence.cpp b/src/seq/testsequence.cpp index 1ce5c56..d802fc5 100644 --- a/src/seq/testsequence.cpp +++ b/src/seq/testsequence.cpp @@ -2,6 +2,7 @@ #include "itrack.h" #include "utility.h" #include "codec/riffcodec.h" +#include "synth/oscillator.h" static const double testNotes[] = { 440, 554.36, 659.26, 880, 1108.73 }; @@ -14,6 +15,11 @@ TestSequence::TestSequence(S2WContext* ctx) : BaseSequence(ctx) addTrack(new BasicTrack()); } + static const BaseOscillator::WaveformPreset waveforms[] = { + BaseOscillator::Triangle, + BaseOscillator::Square25, + BaseOscillator::Square50, + }; for (int i = 0; i < 3; i++) { for (int j = 0; j < 3; j++) { OscillatorEvent* event = new OscillatorEvent; @@ -57,9 +63,18 @@ TestSequence::TestSequence(S2WContext* ctx) : BaseSequence(ctx) OscillatorEvent* e = new OscillatorEvent; e->setEnvelope(.25, .5, .5, .5, .5, .5); - e->waveformID = -1; + e->waveformID = BaseOscillator::Sine; e->duration = 2.5; e->timestamp = 1.0 + sample->duration() * 3; tracks[0]->addEvent(e); + for (int i = 0; i < BaseOscillator::NumPresets; i++) { + OscillatorEvent* e = new OscillatorEvent; + e->waveformID = i; + e->frequency = (i >= BaseOscillator::Noise ? 880.0 : 440.0); + e->duration = .5; + e->volume = .5; + e->timestamp = 4.5 + sample->duration() * 3 + i * .75; + tracks[0]->addEvent(e); + } } diff --git a/src/synth/channel.cpp b/src/synth/channel.cpp index d748123..d45815d 100644 --- a/src/synth/channel.cpp +++ b/src/synth/channel.cpp @@ -12,6 +12,12 @@ Channel::Note::Note() // initializers only } +Channel::Note::Note(std::shared_ptr event, AudioNode* source, double duration) +: Note(event, std::shared_ptr(source), duration) +{ + // forwarded constructor only +} + Channel::Note::Note(std::shared_ptr event, std::shared_ptr source, double duration) : event(event), source(source), duration(duration), kill(false) { @@ -51,7 +57,11 @@ uint32_t Channel::fillBuffer(std::vector& buffer, ssize_t numSamples) break; } if (auto chEvent = ChannelEvent::castShared(event)) { - instrument->channelEvent(this, chEvent); + if (chEvent->param == 'inst') { + instrument = ctx->getInstrument(chEvent->intValue); + } else { + instrument->channelEvent(this, chEvent); + } } else if (auto modEvent = ModulatorEvent::castShared(event)) { instrument->modulatorEvent(this, modEvent); } else if (auto noteEvent = BaseNoteEvent::castShared(event)) { diff --git a/src/synth/channel.h b/src/synth/channel.h index 0799e4e..9aa345e 100644 --- a/src/synth/channel.h +++ b/src/synth/channel.h @@ -29,6 +29,7 @@ class Channel : public AudioParamContainer { struct Note { Note(); + Note(std::shared_ptr event, AudioNode* source, double duration); Note(std::shared_ptr event, std::shared_ptr source, double duration); Note(const Note& other) = default; Note(Note&& other) = default; diff --git a/src/synth/iinstrument.cpp b/src/synth/iinstrument.cpp index 6eaf367..4b0d03f 100644 --- a/src/synth/iinstrument.cpp +++ b/src/synth/iinstrument.cpp @@ -41,7 +41,7 @@ Channel::Note* DefaultInstrument::noteEvent(Channel* channel, std::shared_ptrcast()) { node = nodeEvent->node; } else if (OscillatorEvent* oscEvent = event->cast()) { - BaseOscillator* osc = BaseOscillator::create(channel->ctx, oscEvent->waveformID, oscEvent->frequency, oscEvent->volume, oscEvent->pan); + BaseOscillator* osc = BaseOscillator::create(channel->ctx, BaseOscillator::WaveformPreset(oscEvent->waveformID), oscEvent->frequency, oscEvent->volume, oscEvent->pan); duration = oscEvent->duration; node.reset(osc); } else if (SampleEvent* sampEvent = event->cast()) { @@ -58,18 +58,19 @@ Channel::Note* DefaultInstrument::noteEvent(Channel* channel, std::shared_ptrduration(); } } else { - std::cerr << "ERROR: unhandled user event" << std::endl; + std::cerr << "ERROR: unhandled instrument or user event" << std::endl; return nullptr; } - Channel::Note* note = new Channel::Note(std::shared_ptr(event), node, duration); + Channel::Note* note = new Channel::Note(event, node, duration); if (event->useEnvelope) { - applyEnvelope(channel, note, event); + applyEnvelope(channel, note); } return note; } -void DefaultInstrument::applyEnvelope(Channel* channel, Channel::Note* note, std::shared_ptr event) +void DefaultInstrument::applyEnvelope(Channel* channel, Channel::Note* note) { + const auto& event = note->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; diff --git a/src/synth/iinstrument.h b/src/synth/iinstrument.h index 08ad175..a10515a 100644 --- a/src/synth/iinstrument.h +++ b/src/synth/iinstrument.h @@ -22,7 +22,7 @@ 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); + void applyEnvelope(Channel* channel, Channel::Note* note); }; #endif diff --git a/src/synth/oscillator.cpp b/src/synth/oscillator.cpp index d5b0594..2a826d2 100644 --- a/src/synth/oscillator.cpp +++ b/src/synth/oscillator.cpp @@ -3,28 +3,52 @@ #include #include -BaseOscillator* BaseOscillator::create(const SynthContext* ctx, uint64_t waveformID, double frequency, double amplitude, double pan) +BaseOscillator* BaseOscillator::create(const SynthContext* ctx, BaseOscillator::WaveformPreset waveform, double frequency, double amplitude, double pan) { BaseOscillator* osc; - switch(waveformID) { - case 0: + switch (waveform) { + case Triangle: + osc = new TriangleOscillator(ctx); + break; + case NESTriangle: + osc = new TriangleOscillator(ctx, 16, true); + break; + case Sawtooth: + osc = new SawtoothOscillator(ctx); + break; + case Square50: osc = new SquareOscillator(ctx, 0.5); break; - case 1: + case Square75: osc = new SquareOscillator(ctx, 0.75); break; - case 2: + case Square25: osc = new SquareOscillator(ctx, 0.25); break; - case 3: + case Square125: osc = new SquareOscillator(ctx, 0.125); break; - case 4: - osc = new TriangleOscillator(ctx); + case Noise: + osc = new NoiseOscillator(ctx); + break; + case LinearNoise: + osc = new NoiseOscillator(ctx, NoiseOscillator::Linear); break; - case 5: + case CosineNoise: + osc = new NoiseOscillator(ctx, NoiseOscillator::Cosine); + break; + case NESNoise: osc = new NESNoiseOscillator(ctx); break; + case NESNoise93: + osc = new NESNoise93Oscillator(ctx); + break; + case GBNoise: + osc = new GBNoiseOscillator(ctx); + break; + case GBNoise127: + osc = new GBNoise127Oscillator(ctx); + break; default: osc = new SineOscillator(ctx); break; @@ -62,6 +86,7 @@ int16_t BaseOscillator::generateSample(double time, int channel) SineOscillator::SineOscillator(const SynthContext* ctx) : BaseOscillator(ctx) { + // initializers only } double SineOscillator::calcSample(double) @@ -80,6 +105,17 @@ double SquareOscillator::calcSample(double time) return phase < dutyCycle->valueAt(time) ? 1.0 : -1.0; } +SawtoothOscillator::SawtoothOscillator(const SynthContext* ctx) +: BaseOscillator(ctx) +{ + // initializers only +} + +double SawtoothOscillator::calcSample(double time) +{ + return 2.0 * (phase - int64_t(phase) - 0.5); +} + TriangleOscillator::TriangleOscillator(const SynthContext* ctx, int quantize, bool skew) : BaseOscillator(ctx), quantize(quantize), skew(skew) { @@ -107,6 +143,28 @@ double TriangleOscillator::calcSample(double) return level * 2.0 - 1.0; } +NoiseOscillator::NoiseOscillator(const SynthContext* ctx, Smoothing smoothing) +: BaseOscillator(ctx), smoothing(smoothing), lastPhase(0), lastLevel(0), nextLevel(0) +{ + // initializers only +} + +double NoiseOscillator::calcSample(double) +{ + if (lastPhase > phase) { + lastLevel = nextLevel; + nextLevel = std::rand() / (RAND_MAX / 2.0) - 1.0; + } + lastPhase = phase; + if (smoothing == None) { + return nextLevel; + } else if (smoothing == Linear) { + return lerp(lastLevel, nextLevel, phase); + } else { + return lerp(nextLevel, lastLevel, fastCos(phase * M_PI) * 0.5 + 0.5); + } +} + NESNoiseOscillator::NESNoiseOscillator(const SynthContext* ctx) : BaseOscillator(ctx), state(1), lastPhase(0) { diff --git a/src/synth/oscillator.h b/src/synth/oscillator.h index f71713e..88a0142 100644 --- a/src/synth/oscillator.h +++ b/src/synth/oscillator.h @@ -5,13 +5,33 @@ class BaseOscillator : public AudioNode { public: + enum WaveformPreset { + Sine, + Triangle, + NESTriangle, + Sawtooth, + Square50, + Square75, + Square25, + Square125, + Noise, + LinearNoise, + CosineNoise, + NESNoise, + NESNoise93, + GBNoise, + GBNoise127, + NumPresets, + }; + enum ParamType { Frequency = 'freq', DutyCycle = 'duty', PitchBend = 'bend', + DCOffset = 'dc ', }; - static BaseOscillator* create(const SynthContext* ctx, uint64_t waveformID, double frequency = 440.0, double amplitude = 1.0, double pan = 0.5); + static BaseOscillator* create(const SynthContext* ctx, WaveformPreset waveform, double frequency = 440.0, double amplitude = 1.0, double pan = 0.5); virtual bool isActive() const; virtual int16_t generateSample(double time, int channel = 0); @@ -47,6 +67,14 @@ class SquareOscillator : public BaseOscillator { AudioParam* dutyCycle; }; +class SawtoothOscillator : public BaseOscillator { +public: + SawtoothOscillator(const SynthContext* ctx); + +protected: + virtual double calcSample(double time); +}; + class TriangleOscillator : public BaseOscillator { public: TriangleOscillator(const SynthContext* ctx, int quantize = 0, bool skew = false); @@ -58,6 +86,26 @@ class TriangleOscillator : public BaseOscillator { virtual double calcSample(double time); }; +class NoiseOscillator : public BaseOscillator { +public: + enum Smoothing { + None, + Linear, + Cosine, + }; + NoiseOscillator(const SynthContext* ctx, Smoothing smoothing = None); + + Smoothing smoothing; + +protected: + virtual double calcSample(double time); + +private: + double lastPhase; + double lastLevel; + double nextLevel; +}; + class NESNoiseOscillator : public BaseOscillator { public: NESNoiseOscillator(const SynthContext* ctx); diff --git a/src/utility.cpp b/src/utility.cpp index 10f4935..b9092b1 100644 --- a/src/utility.cpp +++ b/src/utility.cpp @@ -1,6 +1,4 @@ #include "utility.h" -#include -#include #include #include #include diff --git a/src/utility.h b/src/utility.h index fa52b1b..a8c1466 100644 --- a/src/utility.h +++ b/src/utility.h @@ -7,6 +7,8 @@ #include #include #include +#include +#include using OpenFn = std::function(const std::string&)>; std::unique_ptr openFstream(const std::string& path); @@ -56,6 +58,7 @@ int countBits(uint64_t value); double noteToFreq(double midiNote); double fastExp(double r, double dt); double fastSin(double theta); +inline double fastCos(double theta) { return fastSin(M_PI_2 - theta); } inline double combinePan(double a, double b) { return clamp(a + b - 0.5, 0.0, 1.0); }