/***********************************************************************/ /* */ /* Additive synthesis with Harmonics */ /* by Ben Guaraldi */ /* */ /***********************************************************************/ /* This program examines the tambral variances that occur when you mix sine waves together. It begins with several close-in-frequency, close-in amplitude waves, also playing their harmonics, slightly varied according to the Harmonic Adjustment knobs. The first knob on the left varies the ratio of the frequency of the fundamental to each harmonic. (That is, if it's set to 2 and the fundamental is 440, then the first harmonic is 880 and the second is 1760. If it's at 3, though, the first harmonic is 1320 and the second is 3960.) The second alters the relative amplitude of the harmonics to the fundamental. The third adds a constant to each harmonic, the fourth adds a phase. It should be noted that the program is designed to function with initial conditions other than the ones set here. Simply change numberFundamentals to get more or fewer fundamentals to play with and change numberHarmonics to get more or fewer auto-generated harmonics. This program obviously owes a lot to TaeHong's program that we used in Assignment #2, though I like to think mine has a lot cleaner code. */ ( // Declare and set up variables and parameters... var w, numberFundamentals, numberHarmonics; var leftEnd, topEnd, rightEnd, bottomEnd, xOffset, yOffset; var minKnobFreq, maxKnobFreq, minKnobAmp, maxKnobAmp, minKnobPhase, maxKnobPhase; var link1, link2, controls, controlColumnOdd; var ran, ran2, left, top, right, bottom, color; var scopeDuration; numberFundamentals = 4; numberHarmonics = 8; xOffset = 0; yOffset = 0; minKnobFreq = 20; maxKnobFreq = 1000; minKnobAmp = 0; maxKnobAmp = 1/numberFundamentals; // Because we don't want to clip the signal. scopeDuration = 0.05; // These are the dimensions for our window. leftEnd = xOffset; rightEnd = xOffset + 500; topEnd = yOffset + 80; bottomEnd = yOffset + 100 + (numberFundamentals * 30); // Create the window. w = GUIWindow.new("Somebody Come and Play?", Rect.new(leftEnd, topEnd, rightEnd, bottomEnd)); // Some macros... link1 = { arg in, out; w.views.at(in).action = { w.views.at(out).value = w.views.at(in).value }}; link2 = { arg in, out; link1.value(out, in); link1.value(in,out)}; // link1 links in to out, so that when you change the value of in, out changes as well. // link2 links in to out and out to in (by calling link1), so that when you change the value of either, the other changes as well. // Make the frequency knobs... color = rgb(170,40,40); ran = minKnobFreq + (maxKnobFreq - minKnobFreq).rand; numberFundamentals.do({ arg i; // i will go from 0 to numberFundamentals top = 28 * i + 28; bottom = 28 * i + 44; ran2 = 25.rand; NumericalView.new(w, Rect.new( 20, top, 60, bottom), nil, ran + ran2, minKnobFreq, maxKnobFreq, 0.01) .backColor_(color).labelColor_(color); SliderView.new(w, Rect.new( 70, top, 160, bottom), nil, ran + ran2, minKnobFreq, maxKnobFreq, 0.01) .knobColor_(color); link2.value(i*2, (i*2)+1); // Links the text boxes to the sliders. }); // Then make the amplitude knobs. color = rgb(40,40,170); ran = minKnobAmp + (maxKnobAmp - minKnobAmp).rand; numberFundamentals.do({ arg i; top = 28 * i + 28; bottom = 28 * i + 44; ran2 = (((maxKnobAmp - minKnobAmp)/3).rand); NumericalView.new(w, Rect.new( 175, top, 215, bottom), nil, ran + ran2, minKnobAmp, maxKnobAmp, 0.0001) .backColor_(color).labelColor_(color); SliderView.new(w, Rect.new( 225, top, 315, bottom), nil, ran + ran2, minKnobAmp, maxKnobAmp, 0.0001) .knobColor_(color); link2.value( ((i+numberFundamentals)*2), (((i+numberFundamentals)*2)+1) ); // Links the text boxes to the sliders. }); // Now make the wave shape knobs. This is not a do loop because the knobs vary quite a bit. color = rgb(40,170,40); // Harmonic Multiple NumericalView.new(w, Rect.new( 325, 28, 365, 44), nil, 2, 1, 10, 0.01) .backColor_(color).labelColor_(color); SliderView.new(w, Rect.new( 375, 28, 465, 44), nil, 2, 1, 10, 0.01) .knobColor_(color); link2.value( (numberFundamentals*4), (numberFundamentals*4)+1 ); // Amplitude division NumericalView.new(w, Rect.new( 325, 56, 365, 72), nil, 2, 1, 5, 0.01) .backColor_(color).labelColor_(color); SliderView.new(w, Rect.new( 375, 56, 465, 72), nil, 2, 1, 5, 0.01) .knobColor_(color); link2.value( (numberFundamentals*4)+2, (numberFundamentals*4)+3 ); // Harmonic Add NumericalView.new(w, Rect.new( 325, 84, 365, 100), nil, 0, -500, 500, 1) .backColor_(color).labelColor_(color); SliderView.new(w, Rect.new( 375, 84, 465, 100), nil, 0, -500, 500, 1) .knobColor_(color); link2.value( (numberFundamentals*4)+4, (numberFundamentals*4)+5 ); // Phase NumericalView.new(w, Rect.new( 325, 112, 365, 128), nil, 0, 0, 1000, 1) .backColor_(color).labelColor_(color); SliderView.new(w, Rect.new( 375, 112, 465, 128), nil, 0, 0, 1000, 1) .knobColor_(color); link2.value( (numberFundamentals*4)+6, (numberFundamentals*4)+7 ); // Add some labels. StringView.new( w, Rect.newBy( 60, 6, 60, 19 ), "Frequency") .labelColor_(rgb(170,40,40)); StringView.new( w, Rect.newBy( 215, 6, 215, 19 ), "Amplitude") .labelColor_(rgb(40,40,170)); StringView.new( w, Rect.newBy( 365, 6, 365, 19 ), "Harmonic Adj.") .labelColor_(rgb(40,170,40)); // Create the function that gets values out of the knobs. controlColumnOdd = { arg row, columnStart, theWindow; theWindow.views.at( (row*2)+(((columnStart-1)*numberFundamentals)+1) ) }; // Mix the signal together Synth.scope( { Mix.ar ( Array.fill ( numberFundamentals, { arg i; Array.fill ( numberHarmonics, { arg j; SinOsc.ar( ControlIn.kr( controlColumnOdd.value(i, 1, w) ) * (1 + (j * ControlIn.kr( controlColumnOdd.value(0, 5, w) ))) + (j*ControlIn.kr( controlColumnOdd.value(2, 5, w) )), j*ControlIn.kr( controlColumnOdd.value(3, 5, w) ), ControlIn.kr( controlColumnOdd.value(i, 3, w) ) * (1 / (j * ControlIn.kr( controlColumnOdd.value(1, 5, w) ))) ) } ) } ) ) }, scopeDuration, "Additive Synthesis", Rect.newBy(rightEnd, topEnd, rightEnd + 480, bottomEnd + 100) ); w.close; )