Examples accompanying the paper Dynamic FM synthesis using a network of complex resonator filters by Julian Parker and Till Bovermann, presented at SMC 2013. It was developed as part of the DEIND project. The paper can be found here.
Last active
January 22, 2024 00:13
-
-
Save LFSaw/3bb64d0ae780e73adaa6ea3ecbf89210 to your computer and use it in GitHub Desktop.
compar code examples
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Compar is a feed-forward resonator network featuring three ComplexRes nodes. | |
// This code example accompanies the paper "Dynamic FM synthesis using a network of complex resonator filters" by Julian Parker and Till Bovermann. | |
// see http://tai-studio.org/projects/complexres/ for details. | |
( | |
Ndef(\compar, {|in = 0| | |
var ctlLag = \ctlLag.kr(0.1); | |
var src, dst, filterIn, mods, env, tmpIter; | |
var ampEnvs; | |
// number of FM neurons | |
var numNodes = 3; | |
// controls | |
var preAmp = \preAmp.kr(0.1, ctlLag); | |
var postAmp = \postAmp.kr(0.1, ctlLag); | |
var dryAmp = \dryAmp.kr(1, ctlLag); | |
var filterWet = \filterWet.kr(0.9, ctlLag); | |
var reverbWet = \reverbWet.kr(0.9, ctlLag); | |
var freqs = numNodes.collect{|i| | |
("freq"++i).asSymbol.kr((500 + (i * 100)), ctlLag) | |
}.flat; | |
var amps = numNodes.collect{|i| | |
("amp"++i).asSymbol.kr(1, ctlLag) | |
}.flat; | |
var fms = numNodes.collect{|i| | |
("fm"++i).asSymbol.kr(1, ctlLag) | |
}.flat; | |
var decays = numNodes.collect{|i| | |
("decay" ++i).asSymbol.kr(0.1, ctlLag) | |
}.flat; | |
// signal preparation | |
src = LeakDC.ar(SoundIn.ar(in)); | |
filterIn = preAmp * src; | |
// FM network | |
dst = freqs.inject([filterIn, 0], {|in, freq, i| | |
tmpIter = ComplexRes.ar( | |
filterIn, | |
freq | |
+ (fms[i] * in[0]), | |
decays[i] | |
); | |
[ | |
tmpIter * amps[i], | |
in[1] + tmpIter | |
] | |
}); | |
dst = Mix.ar(dst * [1, amps.sum * numNodes.reciprocal]); | |
// dryWet | |
dst = SelectX.ar(filterWet, [ | |
OnePole.ar(dryAmp * src, \lpCoeff.kr(0.3, 0.1)), | |
postAmp * LeakDC.ar(dst) | |
]); | |
// compressor | |
dst = Compander.ar(dst,dst, | |
thresh: \compThresh.kr(0.5,0.1), | |
slopeBelow: 1 , | |
slopeAbove: \compRatio.kr(0.3, 0.1), | |
clampTime: 0.0001, | |
relaxTime: 0.1 | |
); | |
// reverb + gp limiting | |
Limiter.ar( | |
SelectX.ar(reverbWet, [dst, AdCVerb.ar(0.1 * dst, 10)]) | |
); | |
}); | |
) | |
( | |
// control range specifications | |
Spec.add(\freq0, [1, 20000, \exp]); | |
Spec.add(\freq1, \freq0); | |
Spec.add(\freq2, \freq0); | |
Spec.add(\in, [0, 2, \lin, 1, 1]); | |
Spec.add(\amp0, [0, 1]); | |
Spec.add(\amp1, \amp0); | |
Spec.add(\amp2, \amp0); | |
Spec.add(\fm0, [0, 10000]); | |
Spec.add(\fm1, \fm0); | |
Spec.add(\fm2, \fm0); | |
Spec.add(\decay0, [0.01, 5, \exp]); | |
Spec.add(\decay1, \decay0); | |
Spec.add(\decay2, \decay0); | |
Spec.add(\filterWet, [0, 1]); | |
Spec.add(\reverbWet, [0, 1]); | |
Spec.add(\preAmp, [0.5, 5, \exp]); | |
Spec.add(\dryAmp, [0.5, 5, \exp]); | |
Spec.add(\postAmp, [0.5, 50, \exp]); | |
Spec.add(\lpCoeff, [0, 1]); | |
) | |
( | |
// GUI | |
Ndef(\compar).gui; | |
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// ComparFeedback implements an FM feedback matrix based on the ComplexRes UGen. The number of nodes can be set when defining the synthesis engine. ComparFeedback implements a superset of the Compar system (see Compar.scd in this folder). | |
// This code example accompanies the paper "Dynamic FM synthesis using a network of complex resonator filters" by Julian Parker and Till Bovermann. | |
// see http://tai-studio.org/media/projects/complexres/ for details. | |
( | |
q = q ? (); | |
q.numOscs = 10; | |
q.numChans = 2; | |
) | |
( | |
// careful! the system uses AudioIn as input, feedback might cause serious trouble, possibly use headphones unless you know what you're doing. | |
Ndef(\comparMatrix, { | |
var numChans = q.numChans; | |
var in = LeakDC.ar(SoundIn.ar(\in.kr)); | |
var filterIn, feedbacks, filterOut, dst; | |
var ctlLag = \ctlLag.kr(0.1); | |
var numOscs = q.numOscs; | |
var preAmp = \preAmp.kr(0.1, ctlLag); | |
var postAmp = \postAmp.kr(0.1, ctlLag); | |
var dryAmp = \dryAmp.kr(1, ctlLag); | |
var filterWet = \filterWet.kr(0.9, ctlLag); | |
var reverbWet = \reverbWet.kr(0.9, ctlLag); | |
var freqs = \freqs.kr({|i| 500}!numOscs); | |
var modParams = \modParams.kr({|i| 0}!(numOscs**2)).clump(numOscs).postln; | |
var amps = \amps .kr(0!numOscs); | |
var decays = \decays.kr(0.01!numOscs); | |
var oscs; | |
var tmpOsc; | |
// FM network | |
feedbacks = LocalIn.ar(numOscs); | |
filterIn = preAmp * in; | |
oscs = freqs.inject([], {|oscArray, freq, i| | |
tmpOsc = ComplexRes.ar(filterIn, | |
freq | |
+ oscArray.inject(0, {|sum, osc, j| | |
// modulators from already instantiated oscs | |
sum + (feedbacks[j] * modParams[i][j]) }) | |
+ (numOscs - 1 - Array.iota(numOscs - (i))).inject(0, {|sum, g| | |
// modulators from to be instantiated oscs | |
sum + (feedbacks[g] * modParams[i][g]) }), | |
decays[i] | |
); | |
oscArray ++ tmpOsc; | |
}); // end inject | |
LocalOut.ar(oscs); // feedback is pre-"fader" | |
filterOut = oscs * amps * postAmp; | |
// end filter | |
// dryWet | |
dst = SelectX.ar(filterWet, [ | |
OnePole.ar(dryAmp * in, \lpCoeff.kr(0.3, 0.1)), | |
postAmp * LeakDC.ar(filterOut) | |
]); | |
// Compressor | |
dst = Compander.ar(dst,dst, | |
thresh: \compThresh.kr(0.5,0.1), | |
slopeBelow: 1 , | |
slopeAbove: \compRatio.kr(0.3, 0.1), | |
clampTime: 0.0001, | |
relaxTime: 0.1 | |
); | |
// reverb, channel spreading + gp limiting | |
Limiter.ar( | |
SelectX.ar(reverbWet, [ | |
SplayAz.ar(numChans, dst), | |
AdCVerb.ar(0.1 * dst, 10, nOuts: numChans) | |
]) | |
); | |
}) | |
) | |
( | |
// control range specifications | |
Spec.add(\freq0, [1, 20000, \exp]); | |
Spec.add(\freq1, \freq0); | |
Spec.add(\freq2, \freq0); | |
Spec.add(\in, [0, 2, \lin, 1, 1]); | |
Spec.add(\filterWet, [0, 1]); | |
Spec.add(\reverbWet, [0, 1]); | |
Spec.add(\preAmp, [0.5, 5, \exp]); | |
Spec.add(\dryAmp, [0.5, 5, \exp]); | |
Spec.add(\postAmp, [0.5, 50, \exp]); | |
Spec.add(\lpCoeff, [0, 1]); | |
) | |
( | |
// the standard Ndef gui and an example preset. | |
// to hear something, press play. | |
Ndef('comparMatrix').set('preAmp', 5.0, 'reverbWet', 0.036082474226804, 'postAmp', 7.7229139013412, 'filterWet', 1.0, 'compThresh', 0.11354325669618, 'compRatio', 1.0642438515127); | |
Ndef('comparMatrix').setn('amps', [ 0.0, 0.0, 0.0625, 0.8875, 0.0, 0.0, 0.2125, 0.275, 0.05, 0.0625 ], 'modParams', [ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 8697.0, 0, 0, 0, 2275.0, 728.0, 6045.0, 0, 0, 0.0, 169.0, 104.0, 0, 156.0, 104.0, 13.0, 39.0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 5863.0, 0, 0, 0, 0, 741.0, 0, 0, 0, 9997.0, 3900.0, 0, 0, 9997.0, 0.0, 9997.0, 0.0, 0, 0, 6097.0, 6097.0, 0, 0, 7163.0, 0.0, 0.0, 0, 0, 0, 9997.0, 9100.0, 0, 0, 117.0, 611.0, 13.0, 3887.0 ], 'decays', [ 0.01, 0.01, 0.01, 0.55, 0.65, 1.8, 0.72, 2.97, 0.18, 0.26 ], 'freqs', [ 500, 500, 1537.5, 58.5, 500, 500, 179.5, 500.6, 365.7, 305.8 ]); | |
Ndef(\comparMatrix).gui; | |
Ndef(\comparMatrix).setn(\modParams, 0!(q.numOscs**2)); | |
// GUI | |
( | |
var specs = ( | |
freqs: [0, 10000, \lin, 0.1].asSpec, | |
modParams: [0, 10000, \lin, 13].asSpec, | |
in: [0, 3, \lin, 1].asSpec, | |
decays: [0.01, 4, \lin, 0.01].asSpec, | |
); | |
var modParams = Ndef(\comparMatrix).get(\modParams).clump(q.numOscs); | |
var freqState = Ndef(\comparMatrix).get(\freqs); | |
var ampState = Ndef(\comparMatrix).get(\amps); | |
var decayState = Ndef(\comparMatrix).get(\decays); | |
var colWidth = 40; | |
var knobHeight = 50; | |
var idxKnobColors = [ | |
// upper right area | |
[Color.gray(0.8), Color.blue, blend(Color.white, Color.blue, 0.5)], | |
[Color.gray(0.8), Color.blue, blend(Color.white, Color.blue, 0.2)], | |
// lower left area | |
[Color.gray(0.8), Color.red, blend(Color.white, Color.red, 0.5)], | |
[Color.gray(0.8), Color.red, blend(Color.white, Color.red, 0.2)] | |
]; | |
var bgColors = [ | |
Color.gray(0.8), Color.gray(1), | |
Color.gray(0.6), Color.gray(0.8), | |
]; | |
q.win = Window.new("ComparFeedback", Rect(100, 100, (q.numOscs+5) * colWidth, 800)).decorate.front; | |
/////////// INDEXES | |
StaticText(q.win, Rect(10, 10, q.numOscs * (colWidth + 5) + 150, 20)).string_("-- modulation "); | |
q.win.view.decorator.nextLine; | |
q.higherAmpSliders = q.numOscs.collect{|i| | |
var slider; | |
//(i+1).do{|j| | |
q.numOscs.do{|j| | |
var ez; | |
ez = EZKnob(q.win, Rect(25, 25, colWidth, knobHeight), | |
controlSpec: specs[\modParams], | |
initAction: true, | |
initVal: modParams[i][j].postln | |
) | |
.action_{|knob| | |
modParams[i][j] = knob.value; | |
Ndef(\comparMatrix).setn(\modParams, modParams.flat); | |
}; | |
ez.knobView.mode_(\vert); | |
((j) > i).if({ | |
ez.setColors(knobColors: idxKnobColors[j%2]); | |
ez.setColors(background: bgColors [j%2]); | |
}, { | |
ez.setColors(knobColors: idxKnobColors[j%2 + 2]); | |
ez.setColors(background: bgColors [j%2 + 2]); | |
}); | |
(i == j).if{ | |
ez.knobView.color_([Color.gray, Color.blue, Color.green]); | |
}; | |
}; | |
slider = EZSlider(q.win, Rect(0, 0, 150, knobHeight * 0.5), | |
label: i, | |
layout: 'horz', | |
numberWidth: 0, | |
labelWidth: 10, | |
initVal: ampState[i] | |
) | |
.action_{|slider| | |
ampState[i] = slider.value; | |
q.lowerAmpSliders[i].value = slider.value; | |
Ndef(\comparMatrix).setn(\amps, ampState); | |
}; | |
slider.setColors(background: bgColors[i%2 + 2]); | |
q.win.view.decorator.nextLine; | |
// return | |
slider | |
}; | |
q.win.view.decorator.nextLine; | |
/////////// FREQS | |
StaticText(q.win, Rect(10, 10, q.numOscs * (colWidth + 5), 20)).string_("-- freqs ----------"); | |
q.win.view.decorator.nextLine; | |
q.numOscs.do{|i| | |
var ez; | |
ez = EZKnob(q.win, Rect(0, 0, colWidth, knobHeight), | |
controlSpec: specs[\freqs], | |
initAction: true, | |
initVal: freqState[i] | |
) | |
.action_{|knob| | |
freqState[i] = knob.value; | |
Ndef(\comparMatrix).setn(\freqs, freqState); | |
}; | |
ez.knobView.mode_(\vert); | |
ez.setColors(background: bgColors[i%2 + 2]); | |
}; | |
/////////// DECAYS | |
q.win.view.decorator.nextLine; | |
StaticText(q.win, Rect(10, 10, q.numOscs * (colWidth + 5), 20)).string_("-- decays ----------"); | |
q.win.view.decorator.nextLine; | |
q.decays = q.numOscs.collect{|i| | |
var ez; | |
ez = EZKnob(q.win, Rect(25, 25, colWidth, knobHeight), | |
controlSpec: specs[\decays], | |
initVal: decayState[i] | |
) | |
.action_{|knob| | |
decayState[i] = knob.value; | |
Ndef(\comparMatrix).setn(\decays, decayState); | |
}; | |
ez.knobView.mode_(\vert); | |
ez.setColors(background: bgColors[i%2 + 2]); | |
}; | |
q.win.view.decorator.nextLine; | |
q.lowerAmpSliders = q.numOscs.collect{|i| | |
var ez; | |
ez = EZSlider(q.win, Rect(0, 0, colWidth, 150), | |
label: i, | |
layout: 'vert', | |
initVal: ampState[i] | |
) | |
.action_{|slider| | |
ampState[i] = slider.value; | |
Ndef(\comparMatrix).setn(\amps, ampState); | |
q.higherAmpSliders[i].value = slider.value; | |
}; | |
ez.setColors(background: bgColors[i%2 + 2]); | |
} | |
) | |
///////////// set random values /////////// | |
Ndef(\comparMatrix).setn(\decays, {1.0.rand}!10) | |
Ndef(\comparMatrix).setn(\freqs, {200.rand}!10) | |
Ndef(\comparMatrix).setn(\amps, {1.0.rand}!10) | |
Ndef(\comparMatrix).setn(\modParams, {10000.0.rand}!100) | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment