|
| 1 | + |
| 2 | +// This example shows a more complex use of the .rampAD function for the envelope. |
| 3 | +// You can use it to make a simple attack/decay envelope for struck or plucked style notes. |
| 4 | +// Here, we're creating synthetic bells using additive synthesis, and triggering each of their attacks and decays differently to make different harmonics last for different times. |
| 5 | + |
| 6 | +var osc = []; |
| 7 | +var envelope = []; |
| 8 | +var fft; |
| 9 | +var myPhraseAttack, myPhraseRelease, myPart; |
| 10 | +var atPattern = [1, 1,1,1,0,1,1,1,1,0,0,0,0]; // this rhythmic pattern puts some rests in there |
| 11 | +var patternArray = [0,1,2,3,3,2,0,1]; // pattern of the notes (in terms of array indices from scaleArray) |
| 12 | +var scaleArray = [64, 60, 62, 55]; // classic bell tune |
| 13 | +var harmonicsArray = [.5, 1., 1.183, 1.506, 2., 2.514, 2.662, 3.011, 4.166, 5.433, 6.796, 8.215]; // bell partials taken from https://en.wikipedia.org/wiki/Strike_tone |
| 14 | +var idealArray = [.5, 1., 1.2, 1.5, 2, 2.5, 2.6667, 3.0, 4.0, 5.3333, 6.6667, 8.0]; // ideal bell partials |
| 15 | +var note = 0; |
| 16 | +var startPoint = 0; |
| 17 | +var endPoint = 0; |
| 18 | +var numWaveforms = 100; |
| 19 | +var numOsc = 12; // reduce this to reduce the number of overtones, 4 makes a nice, dark gamelan sound |
| 20 | +var numNotes = 4; |
| 21 | +var rawImpulse; |
| 22 | +var cVerb; |
| 23 | +var oscVols = []; |
| 24 | +var firstNote = 1; |
| 25 | +var pitchRatio = .8; //change this to transpose things around |
| 26 | +var pitchDeviation = .001; |
| 27 | +var idealOrReal = 0; // change this to 1 to change to an ideal bell instead of a measured bell |
| 28 | +var maxAttack = .001; // in seconds ... setting to .001 makes things very percussive, setting to > 1 makes them sound far away |
| 29 | +var maxDecay = 9.0; // in seconds ... short times make for deader bells |
| 30 | +var percentWashed = 0.0; |
| 31 | +var washedMax = 4; |
| 32 | + |
| 33 | + |
| 34 | +function preload() |
| 35 | +{ |
| 36 | + // create a p5.Convolver |
| 37 | + cVerb = createConvolver('/assets/LadyChapelStAlbansCathedral.wav'); |
| 38 | + |
| 39 | +} |
| 40 | + |
| 41 | +function setup() |
| 42 | +{ |
| 43 | + createCanvas(1000, 400); |
| 44 | + rawImpulse = loadSound('assets/' + cVerb.impulses[0].name); |
| 45 | + |
| 46 | + for (var i = 0; i < numNotes; i++) |
| 47 | + { |
| 48 | + // make the arrays into 2D arrays |
| 49 | + osc[i] = []; |
| 50 | + envelope[i] = []; |
| 51 | + oscVols[i] = []; |
| 52 | + var midiValue = scaleArray[i]; |
| 53 | + var freqValue = midiToFreq(midiValue); |
| 54 | + |
| 55 | + for(var j = 0; j < numOsc; j++) |
| 56 | + { |
| 57 | + // make arrays of sine waves for each note, additive synthesis, and assign independent envelopes, amplitudes, and slight detunings for each harmonic |
| 58 | + osc[i][j] = new p5.SinOsc(); |
| 59 | + envelope[i][j] = new p5.Env(); |
| 60 | + if (random(0, 1) > percentWashed) |
| 61 | + { |
| 62 | + myMaxAttack = maxAttack; |
| 63 | + print("normal"); |
| 64 | + } |
| 65 | + else |
| 66 | + { |
| 67 | + myMaxAttack = washedMax; |
| 68 | + print("washed"); |
| 69 | + } |
| 70 | + envelope[i][j].setRampAD(random(.001, myMaxAttack), random(.01, maxDecay)); // turning sustain level to 0. makes an AD envelope |
| 71 | + osc[i][j].amp(0.); |
| 72 | + oscVols[i][j] = random(.01, .3); |
| 73 | + if (idealOrReal == 0) |
| 74 | + { |
| 75 | + var myOvertone = harmonicsArray[j]; |
| 76 | + } |
| 77 | + else |
| 78 | + { |
| 79 | + var myOvertone = idealArray[j]; |
| 80 | + } |
| 81 | + osc[i][j].freq(freqValue * harmonicsArray[j] * random(1.0 - pitchDeviation, 1 + pitchDeviation) * pitchRatio); |
| 82 | + osc[i][j].start(); |
| 83 | + osc[i][j].disconnect(); |
| 84 | + //put 'em through that reverb, ahhhhhh yeah it's like a New Age in here |
| 85 | + cVerb.process(osc[i][j]); |
| 86 | + } |
| 87 | + } |
| 88 | + myPhraseAttack = new p5.Phrase('testerAttack', makeSoundAttack, atPattern); |
| 89 | + myPart = new p5.Part(); |
| 90 | + myPart.addPhrase(myPhraseAttack); |
| 91 | + myPart.setBPM(15); // super slow because it's in 16th notes |
| 92 | + myPart.loop(); |
| 93 | + myPart.start(); |
| 94 | + fft = new p5.FFT(); // for the drawing of the waveform (just using the buffer part) |
| 95 | + endPoint = width / numWaveforms; // for the drawing |
| 96 | + background(20); |
| 97 | +} |
| 98 | + |
| 99 | +function draw() |
| 100 | +{ |
| 101 | + background(0, 0, 0, 9); //to make the trails fade like on a scope :) |
| 102 | + var waveform = fft.waveform(); // analyze the waveform |
| 103 | + fft.analyze(); |
| 104 | + beginShape(); |
| 105 | + noFill(); |
| 106 | + stroke(fft.getEnergy("bass") * 2.0, fft.getEnergy("mid")* 2.0, fft.getEnergy("treble") * 2.0); // the (* 2.0) is just to make the colors a little brighter |
| 107 | + for (var i = 0; i < waveform.length; i++) |
| 108 | + { |
| 109 | + var x = map(i, 0, waveform.length, startPoint, endPoint); |
| 110 | + var y = map(waveform[i], -.9, .9, height, 0); |
| 111 | + vertex(x, y); |
| 112 | + } |
| 113 | + endShape(); |
| 114 | + startPoint = endPoint + 1; |
| 115 | + endPoint += (width / numWaveforms); |
| 116 | + if (endPoint > width) |
| 117 | + { |
| 118 | + redrawWaveform(); |
| 119 | + } |
| 120 | +} |
| 121 | + |
| 122 | +function makeSoundAttack(time, playbackRate) |
| 123 | +{ |
| 124 | + var whichNote = patternArray[note]; |
| 125 | + for (var i = 0; i < numOsc; i++) |
| 126 | + { |
| 127 | + envelope[whichNote][i].rampAD(osc[whichNote][i], time, (oscVols[whichNote][i] * random(.8, 1.0))); // the added randomness just makes each strike a little different. |
| 128 | + } |
| 129 | + note = (note + 1) % patternArray.length; |
| 130 | + if (firstNote == 1) |
| 131 | + { |
| 132 | + setTimeout(redrawWaveform, time * 1000.0); // just so the drawing display starts at the left on the first note |
| 133 | + } |
| 134 | + firstNote = 0; |
| 135 | +} |
| 136 | + |
| 137 | + |
| 138 | +function redrawWaveform() |
| 139 | +{ |
| 140 | + startPoint = 0; |
| 141 | + endPoint = (width / numWaveforms); |
| 142 | +} |
0 commit comments