BogaudioModules

BogaudioModules for VCV Rack
Log | Files | Refs | README | LICENSE

vco_base.cpp (5934B)


      1 #include "vco_base.hpp"
      2 #include "dsp/pitch.hpp"
      3 
      4 #define POLY_INPUT "poly_input"
      5 #define DC_CORRECTION "dc_correction"
      6 
      7 float VCOBase::VCOFrequencyParamQuantity::offset() {
      8 	auto vco = dynamic_cast<VCOBase*>(module);
      9 	return vco->_slowMode ? vco->slowModeOffset : 0.0f;
     10 }
     11 
     12 float VCOBase::VCOFrequencyParamQuantity::getDisplayValue() {
     13 	float v = getValue();
     14 	if (!module) {
     15 		return v;
     16 	}
     17 
     18 	auto vco = dynamic_cast<VCOBase*>(module);
     19 	return vco->_linearMode ? (vco->_slowMode ? v : v * 1000.0f) : FrequencyParamQuantity::getDisplayValue();
     20 }
     21 
     22 void VCOBase::VCOFrequencyParamQuantity::setDisplayValue(float v) {
     23 	if (!module) {
     24 		return;
     25 	}
     26 
     27 	auto vco = dynamic_cast<VCOBase*>(module);
     28 	if (vco->_linearMode) {
     29 		if (vco->_slowMode) {
     30 			setValue(v / 1000.0f);
     31 		}
     32 		else {
     33 			setValue(v);
     34 		}
     35 	}
     36 	else {
     37 		FrequencyParamQuantity::setDisplayValue(v);
     38 	}
     39 }
     40 
     41 void VCOBase::Engine::reset() {
     42 	syncTrigger.reset();
     43 }
     44 
     45 void VCOBase::Engine::sampleRateChange(float sampleRate) {
     46 	phasor.setSampleRate(sampleRate);
     47 	square.setSampleRate(sampleRate);
     48 	saw.setSampleRate(sampleRate);
     49 	squareDecimator.setParams(sampleRate, oversample);
     50 	sawDecimator.setParams(sampleRate, oversample);
     51 	triangleDecimator.setParams(sampleRate, oversample);
     52 	squarePulseWidthSL.setParams(sampleRate, 0.1f, 2.0f);
     53 }
     54 
     55 void VCOBase::Engine::setFrequency(float f) {
     56 	if (frequency != f && f < 0.475f * phasor._sampleRate) {
     57 		frequency = f;
     58 		phasor.setFrequency(frequency / (float)oversample);
     59 		square.setFrequency(frequency);
     60 		saw.setFrequency(frequency);
     61 	}
     62 }
     63 
     64 void VCOBase::reset() {
     65 	for (int c = 0; c < _channels; ++c) {
     66 		_engines[c]->reset();
     67 	}
     68 }
     69 
     70 void VCOBase::sampleRateChange() {
     71 	float sampleRate = APP->engine->getSampleRate();
     72 	_oversampleThreshold = 0.06f * sampleRate;
     73 
     74 	for (int c = 0; c < _channels; ++c) {
     75 		_engines[c]->sampleRateChange(sampleRate);
     76 	}
     77 }
     78 
     79 json_t* VCOBase::saveToJson(json_t* root) {
     80 	json_object_set_new(root, POLY_INPUT, json_integer(_polyInputID));
     81 	json_object_set_new(root, DC_CORRECTION, json_boolean(_dcCorrection));
     82 	return root;
     83 }
     84 
     85 void VCOBase::loadFromJson(json_t* root) {
     86 	json_t* p = json_object_get(root, POLY_INPUT);
     87 	if (p) {
     88 		_polyInputID = json_integer_value(p);
     89 	}
     90 
     91 	json_t* dc = json_object_get(root, DC_CORRECTION);
     92 	if (dc) {
     93 		_dcCorrection = json_boolean_value(dc);
     94 	}
     95 }
     96 
     97 int VCOBase::channels() {
     98 	return _polyInputID == _fmInputID ? inputs[_fmInputID].getChannels() : inputs[_pitchInputID].getChannels();
     99 }
    100 
    101 void VCOBase::addChannel(int c) {
    102 	_engines[c] = new Engine();
    103 	_engines[c]->reset();
    104 	_engines[c]->sampleRateChange(APP->engine->getSampleRate());
    105 	if (c > 0) {
    106 		_engines[c]->phasor.syncPhase(_engines[0]->phasor);
    107 	}
    108 }
    109 
    110 void VCOBase::removeChannel(int c) {
    111 	delete _engines[c];
    112 	_engines[c] = NULL;
    113 }
    114 
    115 void VCOBase::modulateChannel(int c) {
    116 	Engine& e = *_engines[c];
    117 
    118 	e.baseVOct = params[_frequencyParamID].getValue();
    119 	if (_fineFrequencyParamID >= 0) {
    120 		e.baseVOct += params[_fineFrequencyParamID].getValue() / 12.0f;
    121 	}
    122 	if (inputs[_pitchInputID].isConnected()) {
    123 		e.baseVOct += clamp(inputs[_pitchInputID].getVoltage(c), -5.0f, 5.0f);
    124 	}
    125 	if (_linearMode) {
    126 		e.baseHz = linearModeVoltsToHertz(e.baseVOct);
    127 	}
    128 	else {
    129 		if (_slowMode) {
    130 			e.baseVOct += slowModeOffset;
    131 		}
    132 		e.baseHz = cvToFrequency(e.baseVOct);
    133 	}
    134 }
    135 
    136 void VCOBase::processChannel(const ProcessArgs& args, int c) {
    137 	Engine& e = *_engines[c];
    138 
    139 	if (e.syncTrigger.next(inputs[_syncInputID].getPolyVoltage(c))) {
    140 		e.phasor.resetPhase();
    141 	}
    142 
    143 	float frequency = e.baseHz;
    144 	Phasor::phase_delta_t phaseOffset = 0;
    145 	if (_fmInputID >= 0 && inputs[_fmInputID].isConnected() && _fmDepth > 0.01f) {
    146 		float fm = inputs[_fmInputID].getPolyVoltage(c) * _fmDepth;
    147 		if (_fmLinearMode) {
    148 			phaseOffset = Phasor::radiansToPhase(2.0f * fm);
    149 		}
    150 		else if (_linearMode) {
    151 			frequency += linearModeVoltsToHertz(fm);
    152 		}
    153 		else {
    154 			frequency = cvToFrequency(e.baseVOct + fm);
    155 		}
    156 	}
    157 	e.setFrequency(frequency);
    158 
    159 	const float oversampleWidth = 100.0f;
    160 	float mix, oMix;
    161 	if (frequency > _oversampleThreshold) {
    162 		if (frequency > _oversampleThreshold + oversampleWidth) {
    163 			mix = 0.0f;
    164 			oMix = 1.0f;
    165 		}
    166 		else {
    167 			oMix = (frequency - _oversampleThreshold) / oversampleWidth;
    168 			mix = 1.0f - oMix;
    169 		}
    170 	}
    171 	else {
    172 		mix = 1.0f;
    173 		oMix = 0.0f;
    174 	}
    175 
    176 	e.squareOut = 0.0f;
    177 	e.sawOut = 0.0f;
    178 	e.triangleOut = 0.0f;
    179 	if (oMix > 0.0f) {
    180 		for (int i = 0; i < Engine::oversample; ++i) {
    181 			e.phasor.advancePhase();
    182 			if (e.squareActive) {
    183 				e.squareBuffer[i] = e.square.nextFromPhasor(e.phasor, phaseOffset + e.additionalPhaseOffset);
    184 			}
    185 			if (e.sawActive) {
    186 				e.sawBuffer[i] = e.saw.nextFromPhasor(e.phasor, phaseOffset + e.additionalPhaseOffset);
    187 			}
    188 			if (e.triangleActive) {
    189 				e.triangleBuffer[i] = e.triangle.nextFromPhasor(e.phasor, phaseOffset + e.additionalPhaseOffset);
    190 			}
    191 		}
    192 		if (e.squareActive) {
    193 			e.squareOut += oMix * amplitude * e.squareDecimator.next(e.squareBuffer);
    194 		}
    195 		if (e.sawActive) {
    196 			e.sawOut += oMix * amplitude * e.sawDecimator.next(e.sawBuffer);
    197 		}
    198 		if (e.triangleActive) {
    199 			e.triangleOut += oMix * amplitude * e.triangleDecimator.next(e.triangleBuffer);
    200 		}
    201 	}
    202 	else {
    203 		e.phasor.advancePhase(Engine::oversample);
    204 	}
    205 	if (mix > 0.0f) {
    206 		if (e.squareActive) {
    207 			e.squareOut += mix * amplitude * e.square.nextFromPhasor(e.phasor, phaseOffset + e.additionalPhaseOffset);
    208 		}
    209 		if (e.sawActive) {
    210 			e.sawOut += mix * amplitude * e.saw.nextFromPhasor(e.phasor, phaseOffset + e.additionalPhaseOffset);
    211 		}
    212 		if (e.triangleActive) {
    213 			e.triangleOut += mix * amplitude * e.triangle.nextFromPhasor(e.phasor, phaseOffset + e.additionalPhaseOffset);
    214 		}
    215 	}
    216 
    217 	e.sineOut = e.sineActive ? (amplitude * e.sine.nextFromPhasor(e.phasor, phaseOffset + e.additionalPhaseOffset)) : 0.0f;
    218 }
    219 
    220 
    221 void VCOBaseModuleWidget::contextMenu(Menu* menu) {
    222 	auto m = dynamic_cast<VCOBase*>(module);
    223 	assert(m);
    224 	menu->addChild(new BoolOptionMenuItem("DC offset correction", [m]() { return &m->_dcCorrection; }));
    225 }