#include "userosc.h" #include "stmlib/dsp/dsp.h" #include "stmlib/dsp/limiter.h" #include "stmlib/dsp/cosine_oscillator.h" #include "plaits/dsp/engine/speech_engine.h" #include "plaits/dsp/speech/naive_speech_synth.h" #include "plaits/dsp/speech/sam_speech_synth.h" #include "utils/float_math.h" #include "utils/fixed_math.h" uint16_t p_values[6] = {0}; float shape = 0, shiftshape = 0, shape_lfo = 0, lfo2 = 0, mix = 0, blend = 0; bool gate = false, previous_gate = false; plaits::EngineParameters parameters = { .trigger = plaits::TRIGGER_UNPATCHED, .note = 0, .timbre = 0, .morph = 0, .harmonics = 0, .accent = 0 }; stmlib::CosineOscillator lfo; enum LfoTarget { LfoTargetShape, LfoTargetShiftShape, LfoTargetParam1, LfoTargetParam2, LfoTargetPitch, LfoTargetAmplitude, LfoTargetLfo2Frequency, LfoTargetLfo2Depth }; inline float get_lfo_value(enum LfoTarget target) { return (p_values[k_user_osc_param_id3] == target ? shape_lfo : 0.0f) + (p_values[k_user_osc_param_id6] == target ? lfo2 : 0.0f); } inline float get_shape() { return clip01f(shape + get_lfo_value(LfoTargetShape)); } inline float get_shift_shape() { return clip01f(shiftshape + get_lfo_value(LfoTargetShiftShape)); } inline float get_param_id1() { return clip01f((p_values[k_user_osc_param_id1] * 0.005f) + get_lfo_value(LfoTargetParam1)); } inline float get_param_id2() { return clip01f((p_values[k_user_osc_param_id2] * 0.01f) + get_lfo_value(LfoTargetParam2)); } inline float get_param_lfo2_frequency() { return clip01f((p_values[k_user_osc_param_id4] * 0.01f) + get_lfo_value(LfoTargetLfo2Frequency)); } inline float get_param_lfo2_depth() { return clip01f((p_values[k_user_osc_param_id5] * 0.01f) + get_lfo_value(LfoTargetLfo2Depth)); } plaits::NaiveSpeechSynth naive_speech_synth_; plaits::SAMSpeechSynth sam_speech_synth_; void OSC_INIT(uint32_t platform, uint32_t api) { lfo.InitApproximate(0); lfo.Start(); sam_speech_synth_.Init(); naive_speech_synth_.Init(); } void OSC_NOTEON(const user_osc_param_t * const params) { gate = true; } void OSC_NOTEOFF(const user_osc_param_t * const params) { gate = false; } void OSC_CYCLE(const user_osc_param_t *const params, int32_t *yn, const uint32_t frames) { static float out1[plaits::kMaxBlockSize], out2[plaits::kMaxBlockSize]; static float aux1[plaits::kMaxBlockSize], aux2[plaits::kMaxBlockSize]; shape_lfo = q31_to_f32(params->shape_lfo); lfo.InitApproximate(get_param_lfo2_frequency() / 600.f); lfo2 = (lfo.Next() - 0.5f) * 2.0f * get_param_lfo2_depth(); parameters.note = ((float)(params->pitch >> 8)) + ((params->pitch & 0xFF) * k_note_mod_fscale); parameters.note += (get_lfo_value(LfoTargetPitch) * 0.5); if(gate && !previous_gate) { parameters.trigger = plaits::TRIGGER_RISING_EDGE; } else { parameters.trigger = plaits::TRIGGER_LOW; } previous_gate = gate; parameters.timbre = get_shift_shape(); parameters.morph = get_shape(); blend = get_param_id1(); mix = get_param_id2(); const float f0 = plaits::NoteToFrequency(parameters.note); naive_speech_synth_.Render( parameters.trigger == plaits::TRIGGER_RISING_EDGE, f0, parameters.morph, parameters.timbre, nullptr, aux1, out1, plaits::kMaxBlockSize); sam_speech_synth_.Render( parameters.trigger == plaits::TRIGGER_RISING_EDGE, f0, parameters.morph, parameters.timbre, aux2, out2, plaits::kMaxBlockSize); for(size_t i=0;i<plaits::kMaxBlockSize;i++) { float o = stmlib::Crossfade(out1[i], out2[i], blend); float a = stmlib::Crossfade(aux1[i], aux2[i], blend); yn[i] = f32_to_q31(stmlib::Crossfade(o, a, mix)); } } void OSC_PARAM(uint16_t index, uint16_t value) { switch (index) { case k_user_osc_param_id1: case k_user_osc_param_id2: case k_user_osc_param_id3: case k_user_osc_param_id4: case k_user_osc_param_id5: case k_user_osc_param_id6: p_values[index] = value; break; case k_user_osc_param_shape: shape = param_val_to_f32(value); break; case k_user_osc_param_shiftshape: shiftshape = param_val_to_f32(value); break; default: break; } }