144 lines
5.5 KiB
C++
144 lines
5.5 KiB
C++
#pragma once
|
|
|
|
#include <JuceHeader.h>
|
|
#include <array>
|
|
#include <vector>
|
|
#include <complex>
|
|
#include <algorithm>
|
|
#include <cmath>
|
|
#include <atomic>
|
|
|
|
// ============================================================
|
|
// A wave frame: 2048 samples with six mip levels (0 = full bandwidth)
|
|
struct WavetableFrame
|
|
{
|
|
std::array<std::vector<float>, 6> mip;
|
|
};
|
|
|
|
// A complete morph set contains 16 frames
|
|
struct WaveMorph
|
|
{
|
|
std::array<WavetableFrame, 16> frames;
|
|
};
|
|
|
|
// ============================================================
|
|
|
|
class WavetableSynthAudioProcessor : public juce::AudioProcessor
|
|
{
|
|
public:
|
|
// -------- engine configuration ----------
|
|
static constexpr int kBrowserCols = 4;
|
|
static constexpr int kBrowserRows = 10;
|
|
static constexpr int kBrowserCapacity = kBrowserCols * kBrowserRows; // 40
|
|
static constexpr int kFactorySlots = 20; // first 20 fixed
|
|
static constexpr int kMorphFrames = 16;
|
|
static constexpr int kMipLevels = 6;
|
|
static constexpr int kTableSize = 2048;
|
|
|
|
// --- minimal editor accessors (public) -------------------------------------
|
|
int getBrowserCols() const noexcept { return kBrowserCols; }
|
|
int getBrowserRows() const noexcept { return kBrowserRows; }
|
|
int getWaveTableCount() const noexcept { return (int) waves.size(); }
|
|
const std::vector<float>* getWaveTablePtr (int index) const noexcept
|
|
{
|
|
// Preview uses the first frame at the widest mip level.
|
|
return (index >= 0 && index < (int) waves.size())
|
|
? &waves[(size_t) index].frames[0].mip[0] : nullptr;
|
|
}
|
|
|
|
// -------- juce plumbing ----------
|
|
WavetableSynthAudioProcessor();
|
|
~WavetableSynthAudioProcessor() override = default;
|
|
|
|
// AudioProcessor
|
|
void prepareToPlay (double sampleRate, int samplesPerBlock) override;
|
|
void releaseResources() override {}
|
|
void processBlock (juce::AudioBuffer<float>&, juce::MidiBuffer&) override;
|
|
|
|
// Editor
|
|
juce::AudioProcessorEditor* createEditor() override;
|
|
bool hasEditor() const override { return true; }
|
|
|
|
// Meta
|
|
const juce::String getName() const override { return "WavetableSynth"; }
|
|
bool acceptsMidi() const override { return true; }
|
|
bool producesMidi() const override { return false; }
|
|
bool isMidiEffect() const override { return false; }
|
|
double getTailLengthSeconds() const override { return 0.0; }
|
|
|
|
// Programs (unused)
|
|
int getNumPrograms() override { return 1; }
|
|
int getCurrentProgram() override { return 0; }
|
|
void setCurrentProgram (int) override {}
|
|
const juce::String getProgramName (int) override { return {}; }
|
|
void changeProgramName (int, const juce::String&) override {}
|
|
|
|
// State
|
|
void getStateInformation (juce::MemoryBlock& destData) override;
|
|
void setStateInformation (const void* data, int sizeInBytes) override;
|
|
|
|
// Parameters
|
|
juce::AudioProcessorValueTreeState apvts;
|
|
static juce::AudioProcessorValueTreeState::ParameterLayout createParameterLayout();
|
|
|
|
// -------- public data used by editor ----------
|
|
std::vector<WaveMorph> waves; // size <= 40; first 20 are factory
|
|
int defaultTableCount { kFactorySlots };
|
|
int nextUserInsert { 0 }; // round-robin inside user region
|
|
|
|
// For the editor thumbnails (take the widest-band level 0)
|
|
const std::vector<float>* getPreviewTablePtr (int index) const;
|
|
|
|
// API for editor to push user waves (single cycle, any shape)
|
|
// Internally this builds a mip set by partial truncation.
|
|
int addOrReplaceUserWavetable (const std::vector<float>& singleCycle01);
|
|
void notifyPresetLoaded();
|
|
float getMorphDisplayValue() const noexcept { return morphDisplay.load (std::memory_order_relaxed); }
|
|
bool isMorphLoopActive() const noexcept;
|
|
|
|
private:
|
|
// ---------- synthesis core ----------
|
|
juce::Synthesiser synth;
|
|
std::vector<float> morphBuffer;
|
|
float morphState { 0.0f };
|
|
float morphLoopPhase { 0.0f };
|
|
int morphLoopDirection { 1 };
|
|
int morphLoopStage { 0 };
|
|
float morphLoopStagePhase { 0.0f };
|
|
std::atomic<float> morphDisplay { 0.0f };
|
|
juce::LinearSmoothedValue<float> presetFade { 1.0f };
|
|
|
|
juce::dsp::Chorus<float> chorus;
|
|
juce::dsp::Reverb reverb;
|
|
juce::dsp::Reverb::Parameters reverbParams;
|
|
|
|
// -------- wave construction helpers ----------
|
|
static void normalize (std::vector<float>& t);
|
|
static void addSine (std::vector<float>& t, int harmonic, float amp);
|
|
static void removeDC (std::vector<float>& t);
|
|
static void enforceZeroStart (std::vector<float>& t);
|
|
static WaveMorph buildAdditiveMorph (std::function<float(int)> ampFn,
|
|
bool oddOnly = false,
|
|
float phaseAlt = +1.0f);
|
|
static WaveMorph makeSine();
|
|
static WaveMorph makeSaw();
|
|
static WaveMorph makeSquare();
|
|
static WaveMorph makeTriangle();
|
|
static WaveMorph makePulse (float duty); // 0<d<1
|
|
static WaveMorph makeEven();
|
|
static WaveMorph makeOdd();
|
|
static WaveMorph makeHalfSineRect();
|
|
static WaveMorph makeBell();
|
|
static WaveMorph makeOrgan();
|
|
void buildFactoryWaves();
|
|
|
|
// choose mip level for a given fundamental
|
|
int chooseMipLevel (float fundamentalHz) const;
|
|
|
|
const WaveMorph* getWavePtr (int index) const;
|
|
|
|
JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (WavetableSynthAudioProcessor)
|
|
|
|
friend class WavetableVoice;
|
|
};
|