#ifndef MODULE_MULTIOSCILLATOR_HPP #define MODULE_MULTIOSCILLATOR_HPP //---------------------------------------------------------------------- // Multi-oscillator (digital) for drawing. //---------------------------------------------------------------------- #include "rack.hpp" using namespace rack; #include "trowaSoft.hpp" #include "dsp/digital.hpp" #define DEBUG 1 #define TROWA_MOSC_DEFAULT_NUM_OSCILLATORS 3 // Default # of oscillators. #define TROWA_MOSC_DEFAULT_NUM_OSC_OUTPUTS 2 // For a given frequency, how many output signals #define TROWA_MOSC_KNOB_MIN_V -10.0f // [V] #define TROWA_MOSC_KNOB_MAX_V 10.0f // [V] #define TROWA_MOSC_INPUT_MIN_V -10.0f // [V] #define TROWA_MOSC_INPUT_MAX_V 10.0f // [V] #define TROWA_MOSC_KNOB_AUX_MIN_V 0.0f // [V] Min knob voltage for AUX #define TROWA_MOSC_KNOB_AUX_MAX_V 10.0f // [V] Max knob voltage for AUX #define TROWA_MOSC_AUX_MIN_V -5.0f // [V] Min input voltage for AUX #define TROWA_MOSC_AUX_MAX_V 5.0f // [V] Max input voltage for AUX #define TROWA_MOSC_TYPE_INPUT_MIN_V -5.0f // [V] #define TROWA_MOSC_TYPE_INPUT_MAX_V 5.0f // [V] #define TROWA_MOSC_MIX_MIN_V 0.0f // [V] Min input knob voltage for MIX #define TROWA_MOSC_MIX_MAX_V 1.0f // [V] Max input knob voltage for MIX #define TROWA_MOSC_MIX_DEF_V 0.5f // [V] Default knob voltage for MIX #define MOSC_FREQ_DEFAULT_HZ 432.0f // Default Frequency [Hz] #define MOSC_PHASE_SHIFT_DEFAULT_DEG 0.0f // [degrees] #define MOSC_OFFSET_DEFAULT_V 0.0f // [V] #define MOSC_AMPLITUDE_DEFAULT_V 5.0f // Default Amplittude [V] #define MOSC_FREQ_MIN_HZ 0.0f // Min Frequency [Hz] #define MOSC_PHASE_SHIFT_MIN_DEG -360.0f // [degrees]. Since we will listen to up to -10V, if -5V is input limit this will still give -180 #define MOSC_OFFSET_MIN_V -10.0f // [V] #define MOSC_AMPLITUDE_MIN_V -10.0f // Min Amplittude [V] #define MOSC_FREQ_MAX_HZ 20000.0f // Max Frequency [Hz] #define MOSC_PHASE_SHIFT_MAX_DEG 360.0f // [degrees]. #define MOSC_OFFSET_MAX_V 10.0f // [V] #define MOSC_AMPLITUDE_MAX_V 10.0f // Max Amplitude [V] //#define TROWA_MOSC_F_KNOB_MIN_V -20.0f // Frequency //#define TROWA_MOSC_F_KNOB_MAX_V 20.0f // Frequency #define TROWA_MOSC_F_KNOB_MIN_V MOSC_FREQ_MIN_HZ // Frequency #define TROWA_MOSC_F_KNOB_MAX_V MOSC_FREQ_MAX_HZ // Frequency #define TROWA_MOSC_FREQ_KNOB_NEEDS_CONVERSION 0 // If Knob value is same as the frequency values, then we don't need to convert. // Wave form type (SINE, SQUARE, TRIANGLE, SAW). enum WaveFormType { // Sine WAVEFORM_SIN, // Triangle WAVEFORM_TRI, // Saw WAVEFORM_SAW, // Square WAVEFORM_SQR, // The number of wave forms. NUM_WAVEFORMS }; // Oscillator output. struct TS_OscillatorOutput { // Base param ids for the oscilator enum BaseParamIds { // What type of oscillator (SIN, SQU, TRI, SAW). [Voltage Range: +/- 10V] OUT_OSC_TYPE_PARAM, // Secondary parameter (i.e. Pulse width for Rectangle, Ramp slope sign for Saw). OUT_AUX_PARAM, // Phi_degrees: Phase Shift (degrees) [-360 to 360]. OUT_PHASE_SHIFT_PARAM, // Mix of the raw with the AM signal. 1.0 is all AM; 0.0 is all raw. OUT_AM_MIX_PARAM, // Toggle (Digital or Ring Modulation) OUT_AM_TYPE_PARAM, // Number of params for an oscillator. OUT_NUM_PARAMS }; // Base input ids for the oscilator enum BaseInputIds { // Oscillator type (SIN, SQU, TRI, SAW). [Voltage Range: +/- 5V]. OUT_OSC_TYPE_INPUT, // Secondary input (i.e. Pulse width for Rectangle, Ramp slope sign for Saw). OUT_AUX_INPUT, // Phi_degrees: Phase Shift (degrees) [-360 to 360]. [Voltage Range: +/- 10V] OUT_PHASE_SHIFT_INPUT, // Amplitude modulation OUT_AM_INPUT, // Number of inputs for an oscillator. OUT_NUM_INPUTS }; // Base output ids for the oscillator. enum BaseOutputIds { // Raw output. OUT_RAW_SIGNAL, // After amplitude modulation. OUT_MULTIPLIED_SIGNAL, // Number of outputs for an oscillator. OUT_NUM_OUTPUTS }; // Base light ids for the oscillator. enum BaseLightIds { OUT_OSC_TYPE_LED, // On = Ring modulation, OFf = Digital. OUT_AM_MODE_LED, OUT_NUM_LIGHTS }; int outputChannelNumber = 0; // Phase shift (degrees) from user inputs (not CV). float ui_phaseShift_deg = 0.0f; // Phase shift (degrees) from user inputs and CV. float phaseShift_deg = 0.0f; // Phase shift (-1 to 1). float phaseShift_norm = 0.0f; // Which wave form to output. WaveFormType waveFormType = WaveFormType::WAVEFORM_SIN; // Waveform from knob/ui control (not CV). WaveFormType ui_waveFormType = WaveFormType::WAVEFORM_SIN; // [Rectangle] Pulse width (normalized 0-1). // [Ramp] Or >= 0.5f for positive Ramp, < 0.5f for negative Ramp. float auxParam_norm = 0.5; // For cycling through waveform types (btn). SchmittTrigger waveFormTrigger; // Digital (false) or Ring Mod (true) bool amRingModulation = false; // For AM mode (btn). Digital (false) or Ring Mod (true). SchmittTrigger amRingModulationTrigger; #if DEBUG // For debugging: float lastPhi_deg = 0.0f; #endif TS_OscillatorOutput() { initialize(); return; } //-------------------------------------------------------- // getRampSlope() // @returns: True for positive slope, false for negative slope. //-------------------------------------------------------- bool getRampSlope() { return auxParam_norm >= 0.5f; } //-------------------------------------------------------- // getRectPulseWidth() // @returns: Pulse width. //-------------------------------------------------------- bool getRectPulseWidth() { return auxParam_norm; } //-------------------------------------------------------- // initialize() // Initialize (UI) values to default values. //-------------------------------------------------------- void initialize(); //-------------------------------------------------------- // serialize() // @returns : The TS_Oscillator json node. //-------------------------------------------------------- json_t* serialize(); //-------------------------------------------------------- // deserialize() // @rootJ : (IN) The TS_Oscillator json node. //-------------------------------------------------------- void deserialize(json_t* rootJ); //-------------------------------------------------------- // setPhaseShift_deg() // @deg : (IN) The phase shift in degrees. //-------------------------------------------------------- void setPhaseShift_deg(float deg); }; // https://www.researchgate.net/publication/230561132_A_Simple_Digital_Model_of_the_Diode-Based_Ring-Modulator struct TS_RingMod { // h is a parameter specifying the slope of the linear section. float h = 0.5f; // vb is a parameter specifying the equivalent of the diode forward bias voltage. // Per the googles: Silicon diodes have a forward voltage of approximately 0.7 volts. Germanium diodes have a forward voltage of approximately 0.3 volts. float v_b = 0.7f; // vL is a parameter giving the voltage beyond which the function is linear. float v_L = 1.2f; inline float diode_shaping(float v_in) { if (v_in <= v_b) { // Zero return 0; } else if (v_in > v_L) { // Linear float temp = (v_L - v_b); return h * (v_in - v_L + temp * temp / (2 * (v_L - v_b))); } else //if (v_in <= v_L) { // Polynomial float temp = (v_in - v_b); return h * temp*temp / (2*(v_L - v_b)); } } //---------------------------------------------------------- // ringMod() // Emulate ring modulation. (Figure 4 in paper). // @v_in : (IN) Voltage input. // @v_c : (IN) Carrier voltage. // @returns : The modulator input. //---------------------------------------------------------- float ringMod(float v_in, float v_c) { float voltageA = v_c + v_in / 2; // Top Input float voltageB = v_c - v_in / 2; // Bottom Input // Top Block - Bottom Block (Figure 4) return diode_shaping(voltageA) + diode_shaping(-voltageA) - (diode_shaping(voltageB) + diode_shaping(-voltageB)); } }; // A base oscillator (basically a frequency) with N waveform outputs based on this frequency struct TS_Oscillator { // Base param ids for the oscilator enum BaseParamIds { // A_V: Amplitude (Volts). Should not be = 0 (pointless) and max +/-12 V. OSCWF_AMPLITUDE_PARAM, // f_Hz: Frequency (Hz) OSCWF_FREQUENCY_PARAM, // Phi_degrees: Phase Shift (degrees) [0-360] OSCWF_PHASE_SHIFT_PARAM, // y0_V : Offset (Volts). +/- 10 V? OSCWF_OFFSET_PARAM, // Sync/Restart waveform. OSCWF_SYNC_PARAM, // Number of params for an oscillator. OSCWF_NUM_PARAMS }; // Base input ids for the oscilator enum BaseInputIds { // A_V: Amplitude (Volts). [Voltage Range: +/-12 V] OSCWF_AMPLITUDE_INPUT, // f_Hz: Frequency (Hz). [Voltage Range: +/- 10V] OSCWF_FREQUENCY_INPUT, // Phi_degrees: Phase Shift (degrees) [0-360]. [Voltage Range: +/- 10V] OSCWF_PHASE_SHIFT_INPUT, // y0_V : Offset (Volts). [Voltage Range: +/- 10V] OSCWF_OFFSET_INPUT, // Sync/Restart waveform. OSCWF_SYNC_INPUT, // Frequency Modulator Input OSCWF_FM_INPUT, // Number of inputs for an oscillator. OSCWF_NUM_INPUTS }; // Base output ids for the oscillator. enum BaseOutputIds { // Any time sync happens or oscillator is at 0 phase. OSCWF_SYNC_OUTPUT, // Number of outputs for an oscillator. OSCWF_NUM_OUTPUTS }; // Base light ids for the oscillator. enum BaseLightIds{ OSCWF_SYNC_LED, OSCWF_NUM_LIGHTS }; // Amplitutde (V) (used value). float amplitude_V = 1.0f; // Frequency (Hz) (used value). float frequency_Hz = 500.0f; // Phase shift (degrees) (used value). float phaseShift_deg = 0.0f; // Phase shift (-1 to 1). float phaseShift_norm = 0.0f; // Phase shift (radians) (used value). //float phaseShift_radians = 0.0f; // Offset (V) (used value). float offset_V = 0.0f; // Amplitutde (V) from user inputs (not CV). float ui_amplitude_V = 1.0f; // Frequency (Hz) from user inputs (not CV). float ui_frequency_Hz = 500.0f; // Phase shift (degrees) from user inputs (not CV). float ui_phaseShift_deg = 0.0f; // Offset (V) from user inputs (not CV). float ui_offset_V = 0.0f; // Phase from 0-1. float phase = 0.0f; // Shift phase from 0-1. float shiftedPhase = 0.0f; // The number of output waves. int numOutputWaveForms = TROWA_MOSC_DEFAULT_NUM_OSC_OUTPUTS; // The output waveforms std::vector outputWaveforms; /// TODO: Figure out good h, v_b, and v_L. // Ring modulator. TS_RingMod ringModulator; // For synch button detection SchmittTrigger synchTrigger; // Sync out PulseGenerator synchPulse; //// If this oscillator should sync with another, the source oscillator index. //int syncSrcOscillatorIx = -1; //// If this step, the oscillator is restarted either by sync input or by reaching the end. //bool oscillatorRestart = false; //-------------------------------------------------------- // TS_Oscillator() // @numOutWaveForms: (IN) The number of output waveforms we will have from this oscillator. //-------------------------------------------------------- TS_Oscillator(int numOutWaveForms); //-------------------------------------------------------- // TS_Oscillator() //-------------------------------------------------------- TS_Oscillator() : TS_Oscillator(TROWA_MOSC_DEFAULT_NUM_OSC_OUTPUTS) { return; } //-------------------------------------------------------- // ~TS_Oscillator() //-------------------------------------------------------- ~TS_Oscillator() { outputWaveforms.clear(); return; } //-------------------------------------------------------- // initialize() // Initialize (UI) values to default values. //-------------------------------------------------------- void initialize(); //-------------------------------------------------------- // serialize() // @returns : The TS_Oscillator json node. //-------------------------------------------------------- json_t* serialize(); //-------------------------------------------------------- // deserialize() // @rootJ : (IN) The TS_Oscillator json node. //-------------------------------------------------------- void deserialize(json_t* rootJ); //-------------------------------------------------------- // calculatePhase() // @dt : (IN) Time elapsed. // @doSync : (IN) If sync / reset requested. // @returns: True if shifted phase has reset (gone over 1) //-------------------------------------------------------- bool calculatePhase(float dt, bool doSync); //-------------------------------------------------------- // setPhaseShift_deg() // @deg : (IN) The phase shift in degrees. //-------------------------------------------------------- void setPhaseShift_deg(float deg); float calcSin(); float calcSquare(); float calcTri(); float calcSaw(); float calcSin(float phaseShift_n); float calcRect(float phaseShift_n, float pulseWidth_n); float calcTri(float phaseShift_n); float calcSaw(float phaseShift_n, bool posRamp); }; //=============================================================================== //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- // multiOscillator // Simple digital oscillator for drawing. //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- //=============================================================================== struct multiOscillator : Module { // User control parameters enum ParamIds { SYNC_PARAM, OSC_PARAM_START, NUM_PARAMS = OSC_PARAM_START // Add #oscillators * OSCWF_NUM_PARAMS to this }; enum InputIds { SYNC_INPUT, OSC_INPUT_START, NUM_INPUTS = OSC_INPUT_START // Add # oscillators * OSCWF_NUM_INPUTS to this }; enum OutputIds { SYNC_OUTPUT, OSC_OUTPUT_START, NUM_OUTPUTS = OSC_OUTPUT_START // Add # oscillators * OSCWF_NUM_OUTPUTS to this Determined by # of channels }; enum LightIds { SYNC_LIGHT, OSC_LIGHT_START, NUM_LIGHTS = OSC_LIGHT_START // Add # oscillators }; // Number of oscillators int numberOscillators = TROWA_MOSC_DEFAULT_NUM_OSCILLATORS; // Collection of oscillators. TS_Oscillator* oscillators = NULL; // The number of output signals from the oscillator. int numOscillatorOutputs = TROWA_MOSC_DEFAULT_NUM_OSC_OUTPUTS; // 3 letter wave form abbreviations. static const char* WaveFormAbbr[WaveFormType::NUM_WAVEFORMS];// = { "SIN", "TRI", "SAW", "SQR" }; // If this has it controls configured. bool isInitialized = false; const float lightLambda = 0.005f; //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- // multiOscillator() // @numOscillators: (IN) Number of oscillators. // @numOscillatorOutputs: (IN) Number of oscillators output signals (channels per oscillator). //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- multiOscillator(int numOscillators, int numOscillatorOutputs); //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- // multiOscillator() //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- multiOscillator() : multiOscillator(TROWA_MOSC_DEFAULT_NUM_OSCILLATORS, TROWA_MOSC_DEFAULT_NUM_OSC_OUTPUTS) { return; } ~multiOscillator(); //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- // findSyncedOscillators(void) // Try to find if osccilator A should be synced to oscillator B. // @returns: Number of oscillators that have sync. //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- int findSyncedOscillators(); //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- // initializeOscillators(void) // Set oscillators to default values. //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- void initialOscillators() { for (int i = 0; i < numberOscillators; i++) { oscillators[i].initialize(); } return; } //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- // step(void) // Process. //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- void step() override; //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- // reset(void) // Initialize values. //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- void reset() override; //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- // randomize(void) // Randomize button stuff. //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- void randomize() override { for (int i = 0; i < numberOscillators; i++) { for (int j = 0; j < oscillators[i].numOutputWaveForms; j++) { oscillators[i].outputWaveforms[j].amRingModulation = (rand() % 100 > 50); } } return; } //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- // toJson(void) //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- json_t *toJson() override; //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- // fromJson(void) //-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*-*- void fromJson(json_t *rootJ) override; }; #endif // !MODULE_MULTIOSCILLATOR_HPP