GG.fullscreen(); true => int PLAY_NARRATIVE; 8 => int N_BANDS; // Audio Params 2000 => float HIGH_CUTOFF; 2 => float BANDS_PER_OCTAVE; // TODO: should be 1 / lowest freq in each band // Ratio between each band's envelope tracker's time constant // and period of the band's lowest frequency. // In other words, the number of periods per time constant 10 => float TAU_RATIO; // Xylophone model params // TODO: change by bar? 18 => int BASE_POINTS_PER_SIDE; // Number of points on each side of lowest bar float PROPAGATION_CONST; // stiffness, (k * gamma / h)^2. (0, 1). 0 is liquidier dur DECAY_TAU; float MODEL_INPUT_GAIN; dur MODEL_TRAVEL_TIME; .225 => float NODE_FRACTION; float FIXED_DAMPING_CONST; // [0, 1], 1 is less damped fun void setSolid() { .99 => PROPAGATION_CONST; // stiffness, (k * gamma / h)^2. (0, 1). 0 is liquidier 1.5::second => DECAY_TAU; .08 => MODEL_INPUT_GAIN; .23::second => MODEL_TRAVEL_TIME; .8 => FIXED_DAMPING_CONST; // [0, 1], 1 is less damped } fun void setLiquid() { .17 => PROPAGATION_CONST; // stiffness, (k * gamma / h)^2. (0, 1). 0 is liquidier 2::second => DECAY_TAU; .06 => MODEL_INPUT_GAIN; .15::second => MODEL_TRAVEL_TIME; 1 => FIXED_DAMPING_CONST; // [0, 1], 1 is less damped } setSolid(); // Xylo graphics poarams 5 => float BAR_WIDTH_SCALE; .49 => float BAR_BASE_HEIGHT; 7$float / N_BANDS => float BAR_Y_SPACING; 1. / 1 => float BAR_OSC_AMP; @(.56, .33, .83) @=> vec3 BAR_COLOR; // @(.6, .6, 1) 0 => float DISTANCE_DAMPING_POWER; 6 => float BAR_Z_AMP; // Time domain graphics params 50 => int NUM_DISPLACEMENT_WAVES; 100 => int NUM_DISPLACEMENT_POINTS; 2 => float BASE_RADIUS; 6.8 / NUM_DISPLACEMENT_WAVES => float RADIUS_SPACING; RADIUS_SPACING * 9 => float RADIUS_OSC_AMP; 4 * 80 => int DISPLACEMENT_DOWNSAMPLE_FACTOR; @(.3, .7, .2) @=> vec3 RING_COLOR; 1 => float RINGS_Z_AMP; // Narrative Params 12 => int NUM_XLYO_HITS; 14::second => dur FREQ_RAMP_TIME; 28::second => dur LIQUID_DUR; - Math.pi * .44 => float ROT_Z_RADS; 3.65 => float ROT_Y_TRANS; 5.2 => float ROT_Z_TRANS; UGen input; // Filterbank float cutoffFreqs[N_BANDS]; // Lowpass cuttoffs for each band. HIGH_CUTOFF => cutoffFreqs[N_BANDS - 1]; for (N_BANDS - 2 => int band; band >= 0; 1 -=> band) { cutoffFreqs[band + 1] / Math.pow(2, 1. / BANDS_PER_OCTAVE) => cutoffFreqs[band]; } UGen bandEnvelopes[N_BANDS]; for (0 => int i; i < N_BANDS; 1 +=> i) { input => UGen filtered; float lowestFreq; dur tau; if (i != 0) { filtered => HPF hp @=> filtered; hp.set(cutoffFreqs[i - 1], .5); cutoffFreqs[i - 1] => lowestFreq; second / lowestFreq * TAU_RATIO => tau; } else { filtered => HPF hp @=> filtered; hp.set(20, .5); // Assume 20 Hz is lowest freq for lowest band for tau calcs. second / 20 * TAU_RATIO => tau; // Use 0 Hz as lowest freq for energy calcs. 20 => lowestFreq; } float highestFreq; second / samp / 2 => float nyquistFreq; if (i != N_BANDS - 1) { filtered => LPF lp @=> filtered; lp.set(cutoffFreqs[i], .5); cutoffFreqs[i] => highestFreq; } else { nyquistFreq => highestFreq; } filtered => FullRect rect => OnePole smoo; smoo.pole(Math.exp(-1 * samp / tau)); smoo => Gain gain => bandEnvelopes[i] => blackhole; // Normalize power; gain.gain(Math.sqrt(nyquistFreq / (highestFreq - lowestFreq))); } // Xylophone model if (NODE_FRACTION > .5) { 1 - NODE_FRACTION => NODE_FRACTION; } MODEL_TRAVEL_TIME / BASE_POINTS_PER_SIDE => dur POINT_UPDATE_PERIOD; float barWidthRatios[N_BANDS]; for (0 => int i; i < N_BANDS; 1 +=> i) { Math.pow(2, -1. / 4) => float pitchRatio; Math.pow(Math.sqrt(pitchRatio), i) => barWidthRatios[i]; } int POINTS_PER_SIDE[N_BANDS]; int N_POINTS[N_BANDS]; int fixedIndexOffsets[N_BANDS]; for (0 => int bar; bar < N_BANDS; 1 +=> bar) { (BASE_POINTS_PER_SIDE * barWidthRatios[bar])$int => POINTS_PER_SIDE[bar]; 2 * POINTS_PER_SIDE[bar] + 1 => N_POINTS[bar]; Math.round((N_POINTS[bar] - 1) * NODE_FRACTION)$int => fixedIndexOffsets[bar]; // from either end } fun float[][] makeBarPoints() { float barPoints[N_BANDS][0]; for (0 => int bar; bar < N_BANDS; 1 +=> bar) { float arr[N_POINTS[bar]] @=> barPoints[bar]; } return barPoints; } Math.exp(-1 * POINT_UPDATE_PERIOD / DECAY_TAU) => float DECAY_CONST; makeBarPoints() @=> float barPoints[][]; makeBarPoints() @=> float prevBarPoints[][]; fun void runXyloModel() { while (true) { makeBarPoints() @=> float nextBarPoints[][]; for (0 => int bar; bar < N_BANDS; 1 +=> bar) { // Wave equation updates // Edges PROPAGATION_CONST * (barPoints[bar][1] - barPoints[bar][0]) + DECAY_CONST * (2 * barPoints[bar][0] - prevBarPoints[bar][0]) => nextBarPoints[bar][0]; PROPAGATION_CONST * (-barPoints[bar][N_POINTS[bar] - 1] + barPoints[bar][N_POINTS[bar] - 2]) + DECAY_CONST * (2 * barPoints[bar][N_POINTS[bar] - 1] - prevBarPoints[bar][N_POINTS[bar] - 1]) => nextBarPoints[bar][N_POINTS[bar] - 1]; for (1 => int i; i < N_POINTS[bar] - 1; 1 +=> i) { // TODO: different update PROPAGATION_CONST * (barPoints[bar][i + 1] - 2 * barPoints[bar][i] + barPoints[bar][i - 1]) + DECAY_CONST * (2 * barPoints[bar][i] - prevBarPoints[bar][i]) => float newVal; newVal => nextBarPoints[bar][i]; } // TODO: how to add force? bandEnvelopes[bar].last() * MODEL_INPUT_GAIN -=> nextBarPoints[bar][POINTS_PER_SIDE[bar]]; //<<>>; // Propagate across boundary by inverting fixedIndexOffsets[bar] => int fixedIndexOffset; // from either end /*if (fixedIndexOffset > 1) { // left of left fixed point (fixedIndexOffset) PROPAGATION_CONST * (-barPoints[bar][fixedIndexOffset + 1] - 2 * barPoints[bar][fixedIndexOffset - 1] + barPoints[bar][fixedIndexOffset - 2]) + DECAY_CONST * (2 * barPoints[bar][fixedIndexOffset - 1] - prevBarPoints[bar][fixedIndexOffset - 1]) => nextBarPoints[bar][fixedIndexOffset - 1]; // right of left fixed point (fixedIndexOffset) PROPAGATION_CONST * (barPoints[bar][fixedIndexOffset + 2] - 2 * barPoints[bar][fixedIndexOffset + 1] - barPoints[bar][fixedIndexOffset - 1]) + DECAY_CONST * (2 * barPoints[bar][fixedIndexOffset + 1] - prevBarPoints[bar][fixedIndexOffset + 1]) => nextBarPoints[bar][fixedIndexOffset + 1]; // right of right fixed point (N_POINTS[bar] - fixedIndexOffset - 1) PROPAGATION_CONST * (barPoints[bar][N_POINTS[bar] - fixedIndexOffset + 1] - 2 * barPoints[bar][N_POINTS[bar] - fixedIndexOffset] - barPoints[bar][N_POINTS[bar] - fixedIndexOffset - 2]) + DECAY_CONST * (2 * barPoints[bar][N_POINTS[bar] - fixedIndexOffset] - prevBarPoints[bar][N_POINTS[bar] - fixedIndexOffset]) => nextBarPoints[bar][N_POINTS[bar] - fixedIndexOffset]; // left of right fixed point (N_POINTS[bar] - fixedIndexOffset - 1) PROPAGATION_CONST * (-barPoints[bar][N_POINTS[bar] - fixedIndexOffset] - 2 * barPoints[bar][N_POINTS[bar] - fixedIndexOffset - 2] + barPoints[bar][N_POINTS[bar] - fixedIndexOffset - 3]) + DECAY_CONST * (2 * barPoints[bar][N_POINTS[bar] - fixedIndexOffset - 2] - prevBarPoints[bar][N_POINTS[bar] - fixedIndexOffset - 2]) => nextBarPoints[bar][N_POINTS[bar] - fixedIndexOffset - 2]; }*/ // Fixed points FIXED_DAMPING_CONST *=> nextBarPoints[bar][fixedIndexOffset]; FIXED_DAMPING_CONST *=> nextBarPoints[bar][N_POINTS[bar] - 1 - fixedIndexOffset]; } barPoints @=> prevBarPoints; nextBarPoints @=> barPoints; POINT_UPDATE_PERIOD => now; } } spork ~ runXyloModel(); // Xylo Graphics float barWidths[N_BANDS]; for (0 => int i; i < N_BANDS; 1 +=> i) { BAR_WIDTH_SCALE * barWidthRatios[i] => barWidths[i]; } GMesh gBars[N_BANDS][0]; CustomGeometry geo[N_BANDS][0]; for (0 => int bar; bar < N_BANDS; 1 +=> bar) { // * 2 for triangles GMesh meshArr[(N_POINTS[bar] - 1) * 2] @=> gBars[bar]; CustomGeometry geoArr[(N_POINTS[bar] - 1) * 2] @=> geo[bar]; } FlatMaterial mat; mat.color(BAR_COLOR); GGen gXylo --> GGen yDisplacement --> GG.scene(); gXylo.posZ(-10); yDisplacement.posY(-3.1); for (0 => int i; i < N_BANDS; 1 +=> i) { for (0 => int p; p < N_POINTS[i] - 1; 1 +=> p) { // pointing up triangle gBars[i][2 * p] --> gXylo; gBars[i][2 * p].posY(i * BAR_Y_SPACING); gBars[i][2 * p].posX((p + .5 - POINTS_PER_SIDE[i])$float * barWidths[i] / N_POINTS[i]); gBars[i][2 * p].set(geo[i][2 * p], mat); // pointing up triangle gBars[i][2 * p + 1] --> gXylo; gBars[i][2 * p + 1].posY(i * BAR_Y_SPACING); gBars[i][2 * p + 1].posX((p + .5 - POINTS_PER_SIDE[i])$float * barWidths[i] / N_POINTS[i]); gBars[i][2 * p + 1].set(geo[i][2 * p + 1], mat); } } fun void setXyloPlanes() { for (0 => int bar; bar < N_BANDS; 1 +=> bar) { barWidths[bar] / N_POINTS[bar] => float width; for (0 => int p; p < N_POINTS[bar] - 1; 1 +=> p) { Math.min(Math.abs(fixedIndexOffsets[bar] - p), Math.abs(N_POINTS[bar] - 1 - fixedIndexOffsets[bar] - p)) / POINTS_PER_SIDE[bar] => float lDistanceDamping; Math.min(Math.abs(fixedIndexOffsets[bar] - p - 1), Math.abs(N_POINTS[bar] - 1 - fixedIndexOffsets[bar] - p - 1)) / POINTS_PER_SIDE[bar] => float rDistanceDamping; BAR_Z_AMP * barPoints[bar][p] * Math.pow(lDistanceDamping,DISTANCE_DAMPING_POWER) => float lHeight; BAR_Z_AMP * barPoints[bar][p + 1] * Math.pow(rDistanceDamping,DISTANCE_DAMPING_POWER) => float rHeight; // pointing down triangle //geo[bar][2 * p].positions([@(-width/2,(BAR_BASE_HEIGHT+lHeight)/2,0), @(width/2,(BAR_BASE_HEIGHT+rHeight)/2,0), @(-width/2,(-BAR_BASE_HEIGHT+lHeight)/2,0)]); geo[bar][2 * p].positions([@(-width/2,BAR_BASE_HEIGHT/2,lHeight/2), @(width/2,BAR_BASE_HEIGHT/2,rHeight/2), @(-width/2,-BAR_BASE_HEIGHT/2,lHeight/2)]); // pointing up triangle //geo[bar][2 * p + 1].positions([@(-width/2,(-BAR_BASE_HEIGHT+lHeight)/2,0), @(width/2,(-BAR_BASE_HEIGHT+rHeight)/2,0), @(width/2,(BAR_BASE_HEIGHT+rHeight)/2,0)]); geo[bar][2 * p + 1].positions([@(-width/2,-BAR_BASE_HEIGHT/2,lHeight/2), @(width/2,-BAR_BASE_HEIGHT/2,rHeight/2), @(width/2,BAR_BASE_HEIGHT/2,rHeight/2)]); } } } // TODO: draw holes // Time domain graphics GLines displacementWaves[NUM_DISPLACEMENT_WAVES]; // TODO: set number GGen gRings --> gXylo; gRings.posY(2.78); gRings.posZ(-RINGS_Z_AMP - .1); for (0 => int i; i < NUM_DISPLACEMENT_WAVES; 1 +=> i) { displacementWaves[i] --> gRings; displacementWaves[i].mat().color(RING_COLOR); } input => Flip displacementBuf => blackhole; displacementBuf.size(NUM_DISPLACEMENT_WAVES * DISPLACEMENT_DOWNSAMPLE_FACTOR); fun void setDisplacementWaves() { displacementBuf.upchuck(); for (0 => int d; d < NUM_DISPLACEMENT_WAVES; 1 +=> d) { BASE_RADIUS + d * RADIUS_SPACING => float radius; displacementBuf.fval((NUM_DISPLACEMENT_WAVES - 1 - d) * DISPLACEMENT_DOWNSAMPLE_FACTOR) * RADIUS_OSC_AMP => float delta; //delta +=> radius; vec3 positions[NUM_DISPLACEMENT_POINTS]; for (0 => int p; p < NUM_DISPLACEMENT_POINTS; 1 +=> p) { vec3 pos; radius * Math.cos(2 * pi * p / (NUM_DISPLACEMENT_POINTS - 1)) => pos.x; radius * Math.sin(2 * pi * p / (NUM_DISPLACEMENT_POINTS - 1)) => pos.y; delta * RINGS_Z_AMP => pos.z; pos @=> positions[p]; } displacementWaves[d].geo().positions(positions); } } // Sound Narrative UGen narrativeInput; if (!PLAY_NARRATIVE) { adc => input; } else { narrativeInput => input; narrativeInput => dac; } fun void rotateXylo(float dir) { ROT_Z_RADS => float rads; 6::second => dur rotTime; 10::ms => dur dt; (rotTime / dt)$int => int nReps; rads / nReps => float dAngle; repeat (nReps) { gXylo.rotX(dAngle * dir); gXylo.translate(@(0,ROT_Y_TRANS,ROT_Z_TRANS) / nReps * dir); dt => now; } } // load the file fun void playNarrative() { 1.6::second => now; SndBuf buf => narrativeInput; me.dir() + "617324__kutalkilic__xylophone-c4.wav" => string filename; filename => buf.read; repeat (NUM_XLYO_HITS) { 0 => buf.pos; Math.random2f(.25,.5) => buf.gain; Math.random2f(.2,3) => buf.rate; buf.length() / buf.rate() * .6 + Math.random2f(.3, .9)::second => now; } me.dir() + "232009__danmitch3ll__xylophone-sweep.wav" => filename; 1.5::second => now; spork ~ rotateXylo(1); filename => buf.read; .5 => buf.gain; .4 => buf.rate; buf.length() / buf.rate() + 2.4::second => now; buf =< narrativeInput; // Begin liquid setLiquid(); SawOsc osc => LPF lpf => narrativeInput; osc.gain(.2); 2.83 * Math.pi => float oscBaseFreq; lpf.Q(12); TriOsc lfo => blackhole; lfo.freq(10 * Math.sqrt(2)); // Freq ramp up now => time startTime; repeat (LIQUID_DUR/samp) { osc.freq(Math.min(1, Math.exp(4 * ((now - startTime) / FREQ_RAMP_TIME - 1))) * (oscBaseFreq + 40 * Math.random2f(-1, 1))); lpf.freq(700 * (1 + .96 * lfo.last())); samp => now; } lpf =< narrativeInput; DECAY_TAU => now; buf => narrativeInput; me.dir() + "232009__danmitch3ll__xylophone-sweep.wav" => filename; 1.5::second => now; spork ~ rotateXylo(-1); filename => buf.read; .5 => buf.gain; buf.samples() => buf.pos; -.4 => buf.rate; buf.length() / (-buf.rate()) + 2.4::second => now; DECAY_TAU * 4 => now; buf =< narrativeInput; narrativeInput =< dac; adc => input; setSolid(); } spork ~ playNarrative(); while (true) { setXyloPlanes(); setDisplacementWaves(); // next graphics frame GG.nextFrame() => now; }