36#include "Synthesis/oscillator.h"
51static constexpr float DEFAULT_LFO_AMPLITUDE = 0.5f;
52static constexpr float DEFAULT_DETUNE_HZ = 2.5f;
53static constexpr float TARGET_FREQUENCY_TOLERANCE = 1.0f;
54static constexpr size_t NEAR_BEGINNING_SAMPLES = 4800;
59using SampleRate = NamedType<float, struct SampleRateTag>;
60using AnimationMultiplier = NamedType<float, struct AnimationMultiplierTag>;
61using DetuneHz = NamedType<float, struct DetuneHzTag>;
62using OscillatorFrequencyRange = NamedType<Range, struct OscillatorFrequencyRangeTag>;
63using OscillatorValue = NamedType<float, struct OscillatorValueTag>;
68 template <
typename T,
typename... Args>
void operator()(T first, Args... rest)
const {}
70 template <
typename T>
void operator()(T value)
const {}
92 static constexpr size_t MAX_OSCILLATORS = 16;
93 const float LFO_AMPLITUDE{constants::DEFAULT_LFO_AMPLITUDE};
97 PENDING_TRANSIT_TO_TARGET,
105 nt::OscillatorFrequency get_target_frequency()
const noexcept {
return target_frequency; }
107 void set_target_frequency(
const nt::OscillatorFrequency freq)
109 if(freq.get() < 0.0f)
111 throw std::invalid_argument(
"Target frequency must be non-negative");
116 this->start_frequency = this->current_frequency;
117 this->target_frequency = freq;
118 this->state = PENDING_TRANSIT_TO_TARGET;
121 nt::OscillatorFrequency get_start_frequency()
const noexcept {
return start_frequency; }
123 void set_start_frequency(
const nt::OscillatorFrequency freq)
125 if(freq.get() < 0.0f)
127 throw std::invalid_argument(
"Start frequency must be non-negative");
131 this->start_frequency = freq;
132 this->current_frequency = this->start_frequency;
133 this->state = PENDING_TRANSIT_TO_TARGET;
136 nt::OscillatorFrequency get_current_frequency()
const noexcept {
return current_frequency; }
138 void set_current_frequency(
const nt::OscillatorFrequency freq)
noexcept { this->current_frequency = freq; }
140 void scale_lfo_base_freq(
const nt::AnimationMultiplier mulitplier)
142 if(mulitplier.get() < 0.0f)
144 throw std::invalid_argument(
"Animation multiplier must be non-negative");
147 lfo.SetFreq(lfo_base_freq.get() * mulitplier.get());
150 nt::OscillatorFrequency get_lfo_base_freq()
const noexcept {
return lfo_base_freq; }
152 void set_lfo_base_freq(
const nt::OscillatorFrequency freq)
noexcept { this->lfo_base_freq = freq; }
154 bool is_at_target()
const noexcept {
return state == AT_TARGET; }
156 State get_state()
const noexcept {
return state; }
158 void set_state(
const State state)
noexcept { this->state = state; }
160 void init_lfo(
const nt::SampleRate sample_rate,
const nt::OscillatorFrequency base_freq)
162 if(sample_rate.get() <= 0.0f)
164 throw std::invalid_argument(
"Sample rate must be positive");
166 if(base_freq.get() < 0.0f)
168 throw std::invalid_argument(
"LFO base frequency must be non-negative");
171 lfo_base_freq = base_freq;
172 lfo.Init(sample_rate.get());
173 lfo.SetAmp(LFO_AMPLITUDE);
174 lfo.SetWaveform(daisysp::Oscillator::WAVE_RAMP);
175 lfo.SetFreq(lfo_base_freq.get());
178 nt::OscillatorValue process_lfo() {
return nt::OscillatorValue(lfo.Process() + LFO_AMPLITUDE); }
180 void reset_lfo()
noexcept { lfo.Reset(); }
182 void init_oscillators(
const size_t count, nt::SampleRate sample_rate, nt::OscillatorFrequency start_frequency)
186 throw std::invalid_argument(
"Oscillator count must be at least 1");
188 if(count > MAX_OSCILLATORS)
190 throw std::invalid_argument(
"Oscillator count exceeds maximum of " + std::to_string(MAX_OSCILLATORS));
192 if(sample_rate.get() <= 0.0f)
194 throw std::invalid_argument(
"Sample rate must be positive");
196 if(start_frequency.get() < 0.0f)
198 throw std::invalid_argument(
"Start frequency must be non-negative");
201 oscillator_count = count;
202 for(
size_t i = 0; i < oscillator_count; ++i)
204 oscillators[i].oscillator.Init(sample_rate.get());
205 oscillators[i].oscillator.SetWaveform(daisysp::Oscillator::WAVE_POLYBLEP_SAW);
206 oscillators[i].oscillator.SetFreq(start_frequency.get());
207 oscillators[i].detune_amount = 0.f;
226 const auto half = oscillator_count / 2;
227 for(
size_t i = 0; i < oscillator_count; ++i)
229 if(oscillator_count <= 1)
231 oscillators[i].detune_amount = 0.f;
235 const int8_t idx = i - half + ((i >= half) ? 1 : 0);
236 oscillators[i].detune_amount = idx * detune.get();
241 nt::OscillatorValue process_oscillators()
243 float osc_value{0.f};
244 for(
size_t i = 0; i < oscillator_count; ++i)
246 oscillators[i].oscillator.SetFreq(current_frequency.get() + oscillators[i].detune_amount);
247 osc_value += oscillators[i].oscillator.Process();
249 return nt::OscillatorValue(osc_value);
253 struct DetunedOscillator
255 daisysp::Oscillator oscillator;
259 State state{PENDING_TRANSIT_TO_TARGET};
260 nt::OscillatorFrequency start_frequency{0.f};
261 nt::OscillatorFrequency target_frequency{0.f};
262 nt::OscillatorFrequency current_frequency{0.f};
263 std::array<DetunedOscillator, MAX_OSCILLATORS> oscillators{};
264 size_t oscillator_count{0};
265 nt::OscillatorFrequency lfo_base_freq{0.f};
266 daisysp::Oscillator lfo;
280 const nt::OscillatorFrequency start_frequency,
const nt::SampleRate sample_rate,
281 const nt::OscillatorFrequency lfo_frequency,
282 const nt::DetuneHz detune = nt::DetuneHz(constants::DEFAULT_DETUNE_HZ))
284 voice.set_start_frequency(start_frequency);
285 voice.set_current_frequency(start_frequency);
286 voice.set_target_frequency(start_frequency);
287 voice.set_state(voice.PENDING_TRANSIT_TO_TARGET);
288 voice.init_lfo(sample_rate, lfo_frequency);
289 voice.init_oscillators(oscillator_count, sample_rate, start_frequency);
295nt::OscillatorFrequency calculate_shaped_frequency(DeepnoteVoice &voice,
const nt::AnimationMultiplier lfo_multiplier,
296 const nt::ControlPoint1 cp1,
const nt::ControlPoint2 cp2)
299 voice.scale_lfo_base_freq(lfo_multiplier);
300 const auto raw_lfo_value = voice.process_lfo();
301 auto shaped_lfo_value = nt::OscillatorValue(BezierUnitShaper(cp1, cp2)(raw_lfo_value.get()));
303 const auto start_frequency = voice.get_start_frequency();
304 const auto target_frequency = voice.get_target_frequency();
307 if(start_frequency.get() > target_frequency.get())
309 shaped_lfo_value = nt::OscillatorValue(1.f - shaped_lfo_value.get());
313 const auto animationScaler =
314 Scaler(nt::InputRange(Range(nt::RangeLow(0.f), nt::RangeHigh(1.f))),
315 nt::OutputRange(Range(nt::RangeLow(start_frequency.get()), nt::RangeHigh(target_frequency.get()))));
317 return nt::OscillatorFrequency(animationScaler(shaped_lfo_value.get()));
320DeepnoteVoice::State update_voice_state(
const DeepnoteVoice &voice,
const DeepnoteVoice::State current_state,
321 const nt::OscillatorFrequency current_frequency)
324 auto state = current_state;
325 const auto start_frequency = voice.get_start_frequency();
326 const auto target_frequency = voice.get_target_frequency();
330 const auto freq_low = std::min(start_frequency.get(), target_frequency.get());
331 const auto freq_high = std::max(start_frequency.get(), target_frequency.get());
333 const auto validFrequencyRange =
334 nt::OscillatorFrequencyRange{Range{nt::RangeLow(freq_low), nt::RangeHigh(freq_high)}};
336 if(validFrequencyRange.get().contains(current_frequency.get()))
340 if(state == DeepnoteVoice::IN_TRANSIT_TO_TARGET)
342 const auto targetRange = nt::OscillatorFrequencyRange{
343 Range{nt::RangeLow(target_frequency.get() - constants::TARGET_FREQUENCY_TOLERANCE),
344 nt::RangeHigh(target_frequency.get() + constants::TARGET_FREQUENCY_TOLERANCE)}};
346 if(targetRange.get().contains(current_frequency.get()))
348 state = DeepnoteVoice::AT_TARGET;
355 state = (state == DeepnoteVoice::IN_TRANSIT_TO_TARGET) ? DeepnoteVoice::AT_TARGET : state;
361nt::OscillatorFrequency constrain_frequency(
const DeepnoteVoice &voice,
const nt::OscillatorFrequency frequency)
364 const auto start_frequency = voice.get_start_frequency();
365 const auto target_frequency = voice.get_target_frequency();
368 const auto freq_low = std::min(start_frequency.get(), target_frequency.get());
369 const auto freq_high = std::max(start_frequency.get(), target_frequency.get());
371 const auto validFrequencyRange =
372 nt::OscillatorFrequencyRange{Range{nt::RangeLow(freq_low), nt::RangeHigh(freq_high)}};
374 return nt::OscillatorFrequency(validFrequencyRange.get().constrain(frequency.get()));
392template <
typename TraceFunc = NoopTrace>
394 const nt::ControlPoint1 cp1,
const nt::ControlPoint2 cp2,
395 const TraceFunc &trace_functor = NoopTrace())
397 nt::OscillatorFrequency unconstrained_freq(0.f);
398 const auto in_state{voice.get_state()};
399 auto state = in_state;
402 if(state == DeepnoteVoice::PENDING_TRANSIT_TO_TARGET)
405 state = DeepnoteVoice::IN_TRANSIT_TO_TARGET;
408 const auto start_frequency = voice.get_start_frequency();
409 const auto target_frequency = voice.get_target_frequency();
410 nt::OscillatorFrequency current_frequency(0.0f);
412 if(state == DeepnoteVoice::AT_TARGET)
414 current_frequency = target_frequency;
418 current_frequency = calculate_shaped_frequency(voice, lfo_multiplier, cp1, cp2);
419 unconstrained_freq = current_frequency;
420 state = update_voice_state(voice, state, current_frequency);
421 current_frequency = constrain_frequency(voice, current_frequency);
424 if(state == DeepnoteVoice::AT_TARGET)
426 current_frequency = target_frequency;
430 voice.set_current_frequency(current_frequency);
431 voice.set_state(state);
434 nt::OscillatorValue osc_value = voice.process_oscillators();
437 trace_functor(start_frequency.get(), target_frequency.get(), in_state, state,
440 unconstrained_freq.get(), current_frequency.get(), osc_value.get());
Bezier curve shaping utilities for the Deep Note synthesizer.
void init_voice(DeepnoteVoice &voice, const size_t oscillator_count, const nt::OscillatorFrequency start_frequency, const nt::SampleRate sample_rate, const nt::OscillatorFrequency lfo_frequency, const nt::DetuneHz detune=nt::DetuneHz(constants::DEFAULT_DETUNE_HZ))
Initialize a DeepnoteVoice with specified parameters.
nt::OscillatorValue process_voice(DeepnoteVoice &voice, const nt::AnimationMultiplier lfo_multiplier, const nt::ControlPoint1 cp1, const nt::ControlPoint2 cp2, const TraceFunc &trace_functor=NoopTrace())
Process a single audio sample from the voice.
Frequency table implementation for voice management in the Deep Note synthesizer.
Oscillator frequency calculation utilities for the Deep Note synthesizer.
Range constraint and validation utilities for the Deep Note synthesizer.
Value scaling and mapping utilities for the Deep Note synthesizer.
A synthesizer voice implementing the THX Deep Note effect.
void detune_oscillators(const nt::DetuneHz detune)
Detune oscillators symmetrically around the fundamental frequency.