diff --git a/Source/AudioBufferQueue.h b/Source/AudioBufferQueue.h index 30bb6f7..db9efa0 100644 --- a/Source/AudioBufferQueue.h +++ b/Source/AudioBufferQueue.h @@ -1,49 +1,49 @@ -#pragma once - -//============================================================================== -template -class AudioBufferQueue -{ -public: - //============================================================================== - static constexpr size_t order = 9; - static constexpr size_t bufferSize = 1U << order; - static constexpr size_t numBuffers = 5; - - //============================================================================== - void push(const SampleType* dataToPush, size_t numSamples) - { - jassert(numSamples <= bufferSize); - - int start1, size1, start2, size2; - abstractFifo.prepareToWrite(1, start1, size1, start2, size2); - - jassert(size1 <= 1); - jassert(size2 == 0); - - if (size1 > 0) - juce::FloatVectorOperations::copy(buffers[(size_t)start1].data(), dataToPush, (int)juce::jmin(bufferSize, numSamples)); - - abstractFifo.finishedWrite(size1); - } - - //============================================================================== - void pop(SampleType* outputBuffer) - { - int start1, size1, start2, size2; - abstractFifo.prepareToRead(1, start1, size1, start2, size2); - - jassert(size1 <= 1); - jassert(size2 == 0); - - if (size1 > 0) - juce::FloatVectorOperations::copy(outputBuffer, buffers[(size_t)start1].data(), (int)bufferSize); - - abstractFifo.finishedRead(size1); - } - -private: - //============================================================================== - juce::AbstractFifo abstractFifo{ numBuffers }; - std::array, numBuffers> buffers; +#pragma once + +//============================================================================== +template +class AudioBufferQueue +{ +public: + //============================================================================== + static constexpr size_t order = 9; + static constexpr size_t bufferSize = 1U << order; + static constexpr size_t numBuffers = 5; + + //============================================================================== + void push(const SampleType* dataToPush, size_t numSamples) + { + jassert(numSamples <= bufferSize); + + int start1, size1, start2, size2; + abstractFifo.prepareToWrite(1, start1, size1, start2, size2); + + jassert(size1 <= 1); + jassert(size2 == 0); + + if (size1 > 0) + juce::FloatVectorOperations::copy(buffers[(size_t)start1].data(), dataToPush, (int)juce::jmin(bufferSize, numSamples)); + + abstractFifo.finishedWrite(size1); + } + + //============================================================================== + void pop(SampleType* outputBuffer) + { + int start1, size1, start2, size2; + abstractFifo.prepareToRead(1, start1, size1, start2, size2); + + jassert(size1 <= 1); + jassert(size2 == 0); + + if (size1 > 0) + juce::FloatVectorOperations::copy(outputBuffer, buffers[(size_t)start1].data(), (int)bufferSize); + + abstractFifo.finishedRead(size1); + } + +private: + //============================================================================== + juce::AbstractFifo abstractFifo{ numBuffers }; + std::array, numBuffers> buffers; }; \ No newline at end of file diff --git a/Source/AudioEngine.h b/Source/AudioEngine.h index ce59eb6..d8e24e1 100644 --- a/Source/AudioEngine.h +++ b/Source/AudioEngine.h @@ -1,43 +1,79 @@ -#pragma once - -#include "SynthVoice.h" -#include - -class NeuralAudioEngine : public juce::MPESynthesiser -{ -public: - static constexpr auto maxNumVoices = 4; - - //============================================================================== - NeuralAudioEngine(NeuralSharedParams &sp) - { - for (auto i = 0; i < maxNumVoices; ++i) - addVoice(new NeuralSynthVoice(sp)); - - setVoiceStealingEnabled(true); - } - - //============================================================================== - void prepare(const juce::dsp::ProcessSpec& spec) noexcept - { - setCurrentPlaybackSampleRate(spec.sampleRate); - - for (auto* v : voices) - dynamic_cast (v)->prepare(spec); - } - - //============================================================================== - template - void applyToVoices(VoiceFunc&& fn) noexcept - { - for (auto* v : voices) - fn(dynamic_cast (v)); - } - -private: - //============================================================================== - void renderNextSubBlock(juce::AudioBuffer& outputAudio, int startSample, int numSamples) override - { - MPESynthesiser::renderNextSubBlock(outputAudio, startSample, numSamples); - } -}; \ No newline at end of file +#pragma once + +#include +#include "SynthVoice.h" + +//============================================================================== +// MPESynthesiser wrapper that owns voices, installs a catch-all Sound, +// forwards prepare() to each voice, and renders MIDI blocks. +class NeuralAudioEngine : public juce::MPESynthesiser +{ +public: + static constexpr int maxNumVoices = 8; + + explicit NeuralAudioEngine (NeuralSharedParams& sp) + { + // Call Synthesiser base API explicitly via a base pointer (portable on MSVC). + auto* base = static_cast(this); + base->clearVoices(); + base->clearSounds(); + + // Create voices (MPESynthesiser takes ownership) + for (int i = 0; i < maxNumVoices; ++i) + addVoice (new NeuralSynthVoice (sp)); + + // Catch-all Sound so any MIDI note/channel is accepted. + struct AnySound final : public juce::SynthesiserSound + { + bool appliesToNote (int) override { return true; } + bool appliesToChannel (int) override { return true; } + }; + // Use raw pointer for this JUCE API on your version. + base->addSound (new AnySound()); + + // Respond to standard MIDI in non-MPE hosts + enableLegacyMode(); + + setVoiceStealingEnabled (true); + } + + // Called from processor::prepareToPlay() + void prepare (const juce::dsp::ProcessSpec& spec) noexcept + { + setCurrentPlaybackSampleRate (spec.sampleRate); + + for (auto* v : voices) + if (auto* nv = dynamic_cast (v)) + nv->prepare (spec); + } + + // Forward the MIDI-buffer overload so MIDI is consumed. + void renderNextBlock (juce::AudioBuffer& outputAudio, + juce::MidiBuffer& midiMessages, + int startSample, + int numSamples) + { + juce::MPESynthesiser::renderNextBlock (outputAudio, midiMessages, + startSample, numSamples); + } + + // Utility: apply a lambda to all voices + template + void applyToVoices (VoiceFunc&& fn) noexcept + { + for (auto* v : voices) + if (auto* nv = dynamic_cast (v)) + fn (nv); + } + +private: + // Keep base rendering behaviour for the sub-block overload. + using juce::MPESynthesiser::renderNextSubBlock; + + void renderNextSubBlock (juce::AudioBuffer& buffer, + int startSample, + int numSamples) override + { + juce::MPESynthesiser::renderNextSubBlock (buffer, startSample, numSamples); + } +}; diff --git a/Source/BlepOsc.h b/Source/BlepOsc.h new file mode 100644 index 0000000..46aa63f --- /dev/null +++ b/Source/BlepOsc.h @@ -0,0 +1,80 @@ +#pragma once +#include + +enum class BlepWave : int { Sine = 0, Saw, Square, Triangle }; + +class BlepOsc +{ +public: + void prepare (double sampleRate) { sr = sampleRate; resetPhase(); } + void setWave (BlepWave w) { wave = w; } + void setFrequency (float f) { freq = juce::jmax (0.0f, f); inc = freq / (float) sr; } + void resetPhase (float p = 0.0f) { phase = juce::jlimit (0.0f, 1.0f, p); } + + inline float process() + { + // phase in [0..1) + float out = 0.0f; + float t = phase; + phase += inc; + if (phase >= 1.0f) phase -= 1.0f; + + switch (wave) + { + case BlepWave::Sine: out = std::sin (2.0f * juce::MathConstants::pi * t); break; + + case BlepWave::Saw: + { + // naive saw in [-1..1] + float s = 2.0f * t - 1.0f; + // apply BLEP at the discontinuity crossing t=0 + s -= polyBlep (t, inc); + out = s; + } break; + + case BlepWave::Square: + { + float s = (t < 0.5f ? 1.0f : -1.0f); + // rising edge at 0.0, falling at 0.5 + s += polyBlep (t, inc) - polyBlep (std::fmod (t + 0.5f, 1.0f), inc); + out = s; + } break; + + case BlepWave::Triangle: + { + // integrate the BLEP square for band-limited tri + float sq = (t < 0.5f ? 1.0f : -1.0f); + sq += polyBlep (t, inc) - polyBlep (std::fmod (t + 0.5f, 1.0f), inc); + // leaky integrator to keep DC under control + z1 = z1 + (sq - z1) * inc; + out = 2.0f * z1; // scale + } break; + } + + return out; + } + +private: + // PolyBLEP as in Valimäki/Huovilainen + static inline float polyBlep (float t, float dt) + { + // t in [0..1) + if (t < dt) + { + t /= dt; + return t + t - t * t - 1.0f; + } + else if (t > 1.0f - dt) + { + t = (t - 1.0f) / dt; + return t * t + t + t + 1.0f; + } + return 0.0f; + } + + double sr = 44100.0; + float freq = 440.0f, inc = 440.0f / 44100.0f; + float phase = 0.0f; + float z1 = 0.0f; + BlepWave wave = BlepWave::Sine; +};