commit 464e7ac36f5ba93ec4637fb0e3f93a3d59d0a112
parent 12944f65c1f0ad53243ef2b1de02a2a631735533
Author: Matt Demanett <matt@demanett.net>
Date: Wed, 4 Apr 2018 13:42:20 -0400
XCO: implement mods for triangle and sine channels.
Diffstat:
6 files changed, 88 insertions(+), 3 deletions(-)
diff --git a/res-src/XCO-src.svg b/res-src/XCO-src.svg
@@ -367,6 +367,11 @@
<!-- <rect width="12" height="8" fill="none" stroke-width="1" stroke="#0f0" /> -->
<polyline points="0,4 3,0 9,8 12,4" stroke-width="1" stroke="#333" fill="none" />
</g>
+ <g transform="translate(3 30)">
+ <use id="TRIANGLE_SAMPLE_PARAM" xlink:href="#knob-smallest" transform="translate(4 0)" />
+ <text font-size="6pt" letter-spacing="2px" transform="translate(2 30)">S&H</text>
+ <use id="TRIANGLE_SAMPLE_INPUT" xlink:href="#input" transform="translate(0 35)" />
+ </g>
<g transform="translate(3 118)">
<use id="TRIANGLE_PHASE_PARAM" xlink:href="#knob-smallest" transform="translate(4 0)" />
<text font-size="6pt" letter-spacing="2px" transform="translate(2 30)">PHS</text>
@@ -386,6 +391,7 @@
</g>
</g>
</g>
+ <use xlink:href="#knobguide-mintick" transform="translate(215 48)" />
<use xlink:href="#knobguide-centertick" transform="translate(215 136)" />
<use xlink:href="#knobguide-maxtick" transform="translate(215 225)" />
@@ -401,6 +407,11 @@
<path d="M 0 4 A 2 3 0 0 1 6 4" stroke="#333" stroke-width="1" fill="none" />
<path d="M 6 4 A 2 3 0 0 0 12 4" stroke="#333" stroke-width="1" fill="none" />
</g>
+ <g transform="translate(3 30)">
+ <use id="SINE_FEEDBACK_PARAM" xlink:href="#knob-smallest" transform="translate(4 0)" />
+ <text font-size="6pt" letter-spacing="1px" transform="translate(0.5 30)">FDBK</text>
+ <use id="SINE_FEEDBACK_INPUT" xlink:href="#input" transform="translate(0 35)" />
+ </g>
<g transform="translate(3 118)">
<use id="SINE_PHASE_PARAM" xlink:href="#knob-smallest" transform="translate(4 0)" />
<text font-size="6pt" letter-spacing="2px" transform="translate(2 30)">PHS</text>
@@ -420,6 +431,7 @@
</g>
</g>
</g>
+ <use xlink:href="#knobguide-mintick" transform="translate(255 48)" />
<use xlink:href="#knobguide-centertick" transform="translate(255 136)" />
<use xlink:href="#knobguide-maxtick" transform="translate(255 225)" />
diff --git a/res/XCO.svg b/res/XCO.svg
Binary files differ.
diff --git a/src/XCO.cpp b/src/XCO.cpp
@@ -5,11 +5,13 @@
void XCO::onReset() {
_syncTrigger.reset();
_modulationStep = modulationSteps;
+ _triangleSampleStep = _phasor._sampleRate;
}
void XCO::onSampleRateChange() {
_phasor.setSampleRate(engineGetSampleRate());
_modulationStep = modulationSteps;
+ _triangleSampleStep = _phasor._sampleRate;
}
void XCO::step() {
@@ -60,6 +62,18 @@ void XCO::step() {
}
_saw.setSaturation(saturation * 10.f);
+ _sineFeedback = params[SINE_FEEDBACK_PARAM].value;
+ if (inputs[SINE_FEEDBACK_INPUT].active) {
+ _sineFeedback *= clamp(inputs[SINE_FEEDBACK_INPUT].value / 10.0f, 0.0f, 1.0f);
+ }
+
+ float sample = params[TRIANGLE_SAMPLE_PARAM].value;
+ if (inputs[TRIANGLE_SAMPLE_INPUT].active) {
+ sample *= clamp(inputs[TRIANGLE_SAMPLE_INPUT].value / 10.0f, 0.0f, 1.0f);
+ }
+ float maxSampleSteps = (_phasor._sampleRate / _phasor._frequency) / 4.0f;
+ _triangleSampleSteps = clamp((int)(sample * maxSampleSteps), 1, (int)maxSampleSteps);
+
_fmDepth = params[FM_PARAM].value;
_squarePhaseOffset = phaseOffset(params[SQUARE_PHASE_PARAM], inputs[SQUARE_PHASE_INPUT]);
@@ -73,8 +87,10 @@ void XCO::step() {
_sineMix = level(params[SINE_MIX_PARAM], inputs[SINE_MIX_INPUT]);
}
+ bool fmOn = false;
float phaseOffset = 0.0f;
if (inputs[FM_INPUT].active && _fmDepth > 0.01f) {
+ fmOn = true;
float fm = inputs[FM_INPUT].value * _fmDepth;
if (_fmLinearMode) {
_phasor.setFrequency(_baseHz);
@@ -87,7 +103,13 @@ void XCO::step() {
else {
_phasor.setFrequency(_baseHz);
}
- _phasor.advancePhase();
+ float sineFeedbackOffset = 0.0f;
+ if (_sineFeedback > 0.001f) {
+ sineFeedbackOffset = Phasor::radiansToPhase(_sineFeedback * _phasor.next());
+ }
+ else {
+ _phasor.advancePhase();
+ }
float mix = 0.0f;
if (outputs[MIX_OUTPUT].active || outputs[SQUARE_OUTPUT].active) {
mix += outputs[SQUARE_OUTPUT].value = amplitude * _squareMix * _square.nextFromPhasor(_phasor, _squarePhaseOffset + phaseOffset);
@@ -96,10 +118,34 @@ void XCO::step() {
mix += outputs[SAW_OUTPUT].value = amplitude * _sawMix * _saw.nextFromPhasor(_phasor, _sawPhaseOffset + phaseOffset);
}
if (outputs[MIX_OUTPUT].active || outputs[TRIANGLE_OUTPUT].active) {
- mix += outputs[TRIANGLE_OUTPUT].value = amplitude * _triangleMix * _triangle.nextFromPhasor(_phasor, _trianglePhaseOffset + phaseOffset);
+ bool useSample = false;
+ if (_triangleSampleSteps > 1) {
+ ++_triangleSampleStep;
+ if (_triangleSampleStep < _triangleSampleSteps) {
+ useSample = true;
+ }
+ else {
+ _triangleSampleStep = 0;
+ }
+ }
+
+ if (fmOn && _fmLinearMode) {
+ if (!useSample) {
+ _triangleSample = _phasor._phase;
+ }
+ mix += outputs[TRIANGLE_OUTPUT].value = amplitude * _triangleMix * _triangle.nextForPhase(_triangleSample + _trianglePhaseOffset + phaseOffset);
+ }
+ else {
+ if (useSample) {
+ mix += outputs[TRIANGLE_OUTPUT].value = _triangleSample;
+ }
+ else {
+ mix += outputs[TRIANGLE_OUTPUT].value = _triangleSample = amplitude * _triangleMix * _triangle.nextFromPhasor(_phasor, _trianglePhaseOffset + phaseOffset);
+ }
+ }
}
if (outputs[MIX_OUTPUT].active || outputs[SINE_OUTPUT].active) {
- mix += outputs[SINE_OUTPUT].value = amplitude * _sineMix * _sine.nextFromPhasor(_phasor, _sinePhaseOffset + phaseOffset);
+ mix += outputs[SINE_OUTPUT].value = amplitude * _sineMix * _sine.nextFromPhasor(_phasor, sineFeedbackOffset + _sinePhaseOffset + phaseOffset);
}
if (outputs[MIX_OUTPUT].active) {
outputs[MIX_OUTPUT].value = mix;
@@ -150,8 +196,10 @@ struct XCOWidget : ModuleWidget {
auto sawSaturationParamPosition = Vec(187.0, 60.0);
auto sawPhaseParamPosition = Vec(187.0, 148.0);
auto sawMixParamPosition = Vec(187.0, 237.0);
+ auto triangleSampleParamPosition = Vec(227.0, 60.0);
auto trianglePhaseParamPosition = Vec(227.0, 148.0);
auto triangleMixParamPosition = Vec(227.0, 237.0);
+ auto sineFeedbackParamPosition = Vec(267.0, 60.0);
auto sinePhaseParamPosition = Vec(267.0, 148.0);
auto sineMixParamPosition = Vec(267.0, 237.0);
@@ -162,8 +210,10 @@ struct XCOWidget : ModuleWidget {
auto sawSaturationInputPosition = Vec(183.0, 95.0);
auto sawPhaseInputPosition = Vec(183.0, 183.0);
auto sawMixInputPosition = Vec(183.0, 272.0);
+ auto triangleSampleInputPosition = Vec(223.0, 95.0);
auto trianglePhaseInputPosition = Vec(223.0, 183.0);
auto triangleMixInputPosition = Vec(223.0, 272.0);
+ auto sineFeedbackInputPosition = Vec(263.0, 95.0);
auto sinePhaseInputPosition = Vec(263.0, 183.0);
auto sineMixInputPosition = Vec(263.0, 272.0);
auto pitchInputPosition = Vec(14.0, 318.0);
@@ -191,8 +241,10 @@ struct XCOWidget : ModuleWidget {
addParam(ParamWidget::create<Knob16>(sawSaturationParamPosition, module, XCO::SAW_SATURATION_PARAM, 0.0, 1.0, 0.0));
addParam(ParamWidget::create<Knob16>(sawPhaseParamPosition, module, XCO::SAW_PHASE_PARAM, -1.0, 1.0, 0.0));
addParam(ParamWidget::create<Knob16>(sawMixParamPosition, module, XCO::SAW_MIX_PARAM, 0.0, 1.0, 1.0));
+ addParam(ParamWidget::create<Knob16>(triangleSampleParamPosition, module, XCO::TRIANGLE_SAMPLE_PARAM, 0.0, 1.0, 0.0));
addParam(ParamWidget::create<Knob16>(trianglePhaseParamPosition, module, XCO::TRIANGLE_PHASE_PARAM, -1.0, 1.0, 0.0));
addParam(ParamWidget::create<Knob16>(triangleMixParamPosition, module, XCO::TRIANGLE_MIX_PARAM, 0.0, 1.0, 1.0));
+ addParam(ParamWidget::create<Knob16>(sineFeedbackParamPosition, module, XCO::SINE_FEEDBACK_PARAM, 0.0, 1.0, 0.0));
addParam(ParamWidget::create<Knob16>(sinePhaseParamPosition, module, XCO::SINE_PHASE_PARAM, -1.0, 1.0, 0.0));
addParam(ParamWidget::create<Knob16>(sineMixParamPosition, module, XCO::SINE_MIX_PARAM, 0.0, 1.0, 1.0));
@@ -203,8 +255,10 @@ struct XCOWidget : ModuleWidget {
addInput(Port::create<Port24>(sawSaturationInputPosition, Port::INPUT, module, XCO::SAW_SATURATION_INPUT));
addInput(Port::create<Port24>(sawPhaseInputPosition, Port::INPUT, module, XCO::SAW_PHASE_INPUT));
addInput(Port::create<Port24>(sawMixInputPosition, Port::INPUT, module, XCO::SAW_MIX_INPUT));
+ addInput(Port::create<Port24>(triangleSampleInputPosition, Port::INPUT, module, XCO::TRIANGLE_SAMPLE_INPUT));
addInput(Port::create<Port24>(trianglePhaseInputPosition, Port::INPUT, module, XCO::TRIANGLE_PHASE_INPUT));
addInput(Port::create<Port24>(triangleMixInputPosition, Port::INPUT, module, XCO::TRIANGLE_MIX_INPUT));
+ addInput(Port::create<Port24>(sineFeedbackInputPosition, Port::INPUT, module, XCO::SINE_FEEDBACK_INPUT));
addInput(Port::create<Port24>(sinePhaseInputPosition, Port::INPUT, module, XCO::SINE_PHASE_INPUT));
addInput(Port::create<Port24>(sineMixInputPosition, Port::INPUT, module, XCO::SINE_MIX_INPUT));
addInput(Port::create<Port24>(pitchInputPosition, Port::INPUT, module, XCO::PITCH_INPUT));
diff --git a/src/XCO.hpp b/src/XCO.hpp
@@ -22,8 +22,10 @@ struct XCO : Module {
SAW_SATURATION_PARAM,
SAW_PHASE_PARAM,
SAW_MIX_PARAM,
+ TRIANGLE_SAMPLE_PARAM,
TRIANGLE_PHASE_PARAM,
TRIANGLE_MIX_PARAM,
+ SINE_FEEDBACK_PARAM,
SINE_PHASE_PARAM,
SINE_MIX_PARAM,
NUM_PARAMS
@@ -37,8 +39,10 @@ struct XCO : Module {
SAW_SATURATION_INPUT,
SAW_PHASE_INPUT,
SAW_MIX_INPUT,
+ TRIANGLE_SAMPLE_INPUT,
TRIANGLE_PHASE_INPUT,
TRIANGLE_MIX_INPUT,
+ SINE_FEEDBACK_INPUT,
SINE_PHASE_INPUT,
SINE_MIX_INPUT,
PITCH_INPUT,
@@ -70,6 +74,10 @@ struct XCO : Module {
bool _slowMode = false;
float _fmDepth = 0.0f;
bool _fmLinearMode = false;
+ int _triangleSampleStep = 0;
+ int _triangleSampleSteps = 0;
+ float _triangleSample = 0.0f;
+ float _sineFeedback = 0.0f;
float _squarePhaseOffset = 0.0f;
float _sawPhaseOffset = 0.0f;
float _trianglePhaseOffset = 0.0f;
diff --git a/src/dsp/oscillator.cpp b/src/dsp/oscillator.cpp
@@ -35,6 +35,16 @@ void Phasor::_update() {
}
}
+float Phasor::nextForPhase(float phase) {
+ while (phase >= maxPhase) {
+ phase -= maxPhase;
+ }
+ while (phase < 0.0f) {
+ phase += maxPhase;
+ }
+ return _nextForPhase(phase);
+}
+
void Phasor::advancePhase() {
_phase += _delta;
if (_phase >= maxPhase) {
diff --git a/src/dsp/oscillator.hpp b/src/dsp/oscillator.hpp
@@ -80,6 +80,7 @@ struct Phasor : OscillatorGenerator {
void setPhase(float radians);
float nextFromPhasor(const Phasor& phasor, float offset = 0.0f); // offset is not radians, but local phase.
+ float nextForPhase(float phase); // local phase, not radians.
virtual void _update();
void advancePhase();
void advancePhasePositive();