From cb278a425758147bbf53e4438ae8f11a2c2cf5d7 Mon Sep 17 00:00:00 2001 From: xnieamo Date: Mon, 10 Apr 2017 14:46:06 -0400 Subject: [PATCH 01/76] added initial files from project 1 --- .gitignore | 2 + index.html | 19 +++++ package.json | 30 +++++++ src/framework.js | 75 ++++++++++++++++ src/main.js | 169 +++++++++++++++++++++++++++++++++++++ src/shaders/adam-frag.glsl | 16 ++++ src/shaders/adam-vert.glsl | 82 ++++++++++++++++++ webpack.config.js | 28 ++++++ 8 files changed, 421 insertions(+) create mode 100644 .gitignore create mode 100644 index.html create mode 100644 package.json create mode 100644 src/framework.js create mode 100644 src/main.js create mode 100644 src/shaders/adam-frag.glsl create mode 100644 src/shaders/adam-vert.glsl create mode 100644 webpack.config.js diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..8d4aaf40 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules +*.map \ No newline at end of file diff --git a/index.html b/index.html new file mode 100644 index 00000000..f775186a --- /dev/null +++ b/index.html @@ -0,0 +1,19 @@ + + + + HW1: Noise + + + + + + \ No newline at end of file diff --git a/package.json b/package.json new file mode 100644 index 00000000..fadfb242 --- /dev/null +++ b/package.json @@ -0,0 +1,30 @@ +{ + "scripts": { + "start": "webpack-dev-server --hot --inline", + "build": "webpack", + "deploy": "gh-pages-deploy" + }, + "gh-pages-deploy": { + "prep": [ + "build" + ], + "noprompt": true + }, + "dependencies": { + "dat-gui": "^0.5.0", + "gl-matrix": "^2.3.2", + "stats-js": "^1.0.0-alpha1", + "three": "^0.82.1", + "three-orbit-controls": "^82.1.0", + "tonal": "^0.69.5" + }, + "devDependencies": { + "babel-core": "^6.18.2", + "babel-loader": "^6.2.8", + "babel-preset-es2015": "^6.18.0", + "gh-pages-deploy": "^0.4.2", + "webpack": "^1.13.3", + "webpack-dev-server": "^1.16.2", + "webpack-glsl-loader": "^1.0.1" + } +} diff --git a/src/framework.js b/src/framework.js new file mode 100644 index 00000000..9cfcd1b4 --- /dev/null +++ b/src/framework.js @@ -0,0 +1,75 @@ + +const THREE = require('three'); +const OrbitControls = require('three-orbit-controls')(THREE) +import Stats from 'stats-js' +import DAT from 'dat-gui' + +// when the scene is done initializing, the function passed as `callback` will be executed +// then, every frame, the function passed as `update` will be executed +function init(callback, update) { + var stats = new Stats(); + stats.setMode(1); + stats.domElement.style.position = 'absolute'; + stats.domElement.style.left = '0px'; + stats.domElement.style.top = '0px'; + document.body.appendChild(stats.domElement); + + var gui = new DAT.GUI(); + + var framework = { + gui: gui, + stats: stats + }; + + // run this function after the window loads + window.addEventListener('load', function() { + + var scene = new THREE.Scene(); + var camera = new THREE.PerspectiveCamera( 75, window.innerWidth/window.innerHeight, 0.1, 1000 ); + var renderer = new THREE.WebGLRenderer( { antialias: true } ); + renderer.setPixelRatio(window.devicePixelRatio); + renderer.setSize(window.innerWidth, window.innerHeight); + renderer.setClearColor(0x020202, 0); + + var controls = new OrbitControls(camera, renderer.domElement); + controls.enableDamping = true; + controls.enableZoom = true; + controls.target.set(0, 0, 0); + controls.rotateSpeed = 0.3; + controls.zoomSpeed = 1.0; + controls.panSpeed = 2.0; + + document.body.appendChild(renderer.domElement); + + // resize the canvas when the window changes + window.addEventListener('resize', function() { + camera.aspect = window.innerWidth / window.innerHeight; + camera.updateProjectionMatrix(); + renderer.setSize(window.innerWidth, window.innerHeight); + }); + + // assign THREE.js objects to the object we will return + framework.scene = scene; + framework.camera = camera; + framework.renderer = renderer; + + // begin the animation loop + (function tick() { + stats.begin(); + update(framework); // perform any requested updates + renderer.render(scene, camera); // render the scene + stats.end(); + requestAnimationFrame(tick); // register to call this again when the browser renders a new frame + })(); + + // we will pass the scene, gui, renderer, camera, etc... to the callback function + return callback(framework); + }); +} + +export default { + init: init +} + +export const PI = 3.14159265 +export const e = 2.7181718 \ No newline at end of file diff --git a/src/main.js b/src/main.js new file mode 100644 index 00000000..f3bd585f --- /dev/null +++ b/src/main.js @@ -0,0 +1,169 @@ + +const THREE = require('three'); // older modules are imported like this. You shouldn't have to worry about this much +import Framework from './framework' + +// Colors +var additionalControls = { + 'Color' : [255, 255, 255], + 'scale' : 1., + 'music' : true, + 'inv persistence' : 2., + 'radius' : 0.7, + 'detail' : 6. +}; + +// Local global to allow for modifying variables +var noiseCloud = { + mesh : {}, +}; + +// called after the scene loads +function onLoad(framework) { + var scene = framework.scene; + var camera = framework.camera; + var renderer = framework.renderer; + var gui = framework.gui; + var stats = framework.stats; + + // LOOK: the line below is synyatic sugar for the code above. Optional, but I sort of recommend it. + // var {scene, camera, renderer, gui, stats} = framework; + var adamMaterial = new THREE.ShaderMaterial({ + uniforms: { + image: { // Check the Three.JS documentation for the different allowed types and values + type: "t", + value: THREE.ImageUtils.loadTexture('./adam.jpg') + }, + inv_persistence: { + type: "f", + value: 2.0 + }, + time: { + type: "f", + value: 0. + }, + music: { + type: "f", + value: 1. + }, + music2: { + type: "f", + value: 1. + }, + colorMult: { + value: new THREE.Vector3(additionalControls['Color'][0]/255, additionalControls['Color'][1]/255, additionalControls['Color'][2]/255,) + } + }, + vertexShader: require('./shaders/adam-vert.glsl'), + fragmentShader: require('./shaders/adam-frag.glsl') + }); + + var iso = new THREE.IcosahedronBufferGeometry(0.7, 6); + noiseCloud.mesh = new THREE.Mesh(iso, adamMaterial); + noiseCloud.mesh.name = "adamCube"; + + // set camera position + camera.position.set(1, 1, 6); + camera.lookAt(new THREE.Vector3(0,0,0)); + + scene.add(noiseCloud.mesh); + + // edit params and listen to changes like this + // more information here: https://workshop.chromeexperiments.com/examples/gui/#1--Basic-Usage + gui.add(camera, 'fov', 0, 180).onChange(function(newVal) { + camera.updateProjectionMatrix(); + }); + + gui.add(additionalControls, 'inv persistence', 1., 10.).onChange(function(newVal) { + noiseCloud.mesh.material.uniforms.inv_persistence.value = newVal; + }); + + gui.add(additionalControls, 'music').onChange(function(newVal) { + additionalControls.music = newVal; + }); + + // Color menu + gui.addColor(additionalControls, 'Color').onChange(function(newVal) { + noiseCloud.mesh.material.uniforms.colorMult.value = new THREE.Vector3(newVal[0]/255, newVal[1]/255, newVal[2]/255,); + }); + + + // Audio stuff below + // http://raathigesh.com/Audio-Visualization-with-Web-Audio-and-ThreeJS/ + // http://stackoverflow.com/questions/27589179/basic-web-audio-api-not-playing-a-mp3-file + // http://stackoverflow.com/questions/3273552/html5-audio-looping + var context = new AudioContext(); + var jsNode = context.createScriptProcessor(2048,1,1); + jsNode.connect(context.destination); + + // Load file and set to repeat + var audio = new Audio(); + audio.src = "./sounds/music2.mp3"; //https://www.jamendo.com/track/1350213/jumper + audio.controls = true; + audio.autoplay = true; + audio.addEventListener('ended', function(){ + this.currentTime = 0; + this.play(); + }, false); + audio.loop = true; + + // Play file + var source = context.createMediaElementSource(audio); + source.connect(context.destination); + source.mediaElement.play(); + + // Analyze waveform data + var analyser = context.createAnalyser(); + analyser.fftSize = 128; + analyser.smoothingTimeConstat = 0.8; + source.connect(analyser); + + // Action to take with processed data + jsNode.onaudioprocess = function () { + + // If music sync box is checked + if (additionalControls.music) { + var array = new Uint8Array(analyser.frequencyBinCount); + analyser.getByteFrequencyData(array); + // console.log(analyser.maxDecibels) + + var Z = [array.slice(0, 9).reduce((a, b) => a + b, 0) / 10 /256, + array.slice(10, 19).reduce((a, b) => a + b, 0) / 10 /256, + array.slice(20, 29).reduce((a, b) => a + b, 0) / 10 /256, + array.slice(30, 39).reduce((a, b) => a + b, 0) / 10 /256, + array.slice(40, 49).reduce((a, b) => a + b, 0) / 10 /256, + array.slice(50, 59).reduce((a, b) => a + b, 0) / 10 /256]; + // console.log(Z); + + noiseCloud.mesh.material.uniforms.music.value = Z[4]; + noiseCloud.mesh.material.uniforms.music2.value = Z[1]; + } else { + noiseCloud.mesh.material.uniforms.music.value = 1.; + noiseCloud.mesh.material.uniforms.music2.value = 0.; + } + + + } + + +} + +// called on frame updates +function onUpdate(framework) { + + + framework.scene.traverse(function (object) + { + if (object instanceof THREE.Mesh) + { + if (object.name === 'adamCube') { + // var d = new Date(); + // console.log(`the time is ${(object.material.uniforms.time.value)}`); + object.material.uniforms.time.value += .01; + + } + } + }); +} + +// when the scene is done initializing, it will call onLoad, then on frame updates, call onUpdate +Framework.init(onLoad, onUpdate); \ No newline at end of file diff --git a/src/shaders/adam-frag.glsl b/src/shaders/adam-frag.glsl new file mode 100644 index 00000000..dd579bf4 --- /dev/null +++ b/src/shaders/adam-frag.glsl @@ -0,0 +1,16 @@ +varying vec2 vUv; +varying vec3 nor; +varying float noise; +varying float mus; + +uniform sampler2D image; +uniform vec3 colorMult; + + +void main() { + + vec4 color = texture2D( image, vUv ); + + gl_FragColor = vec4( (1. - noise) * nor.rgb + noise * colorMult, 1.0 ); + // gl_FragColor = vec4( mus * color.rgb, 1.0 ); +} \ No newline at end of file diff --git a/src/shaders/adam-vert.glsl b/src/shaders/adam-vert.glsl new file mode 100644 index 00000000..b821a35c --- /dev/null +++ b/src/shaders/adam-vert.glsl @@ -0,0 +1,82 @@ + +varying vec2 vUv; +varying vec3 nor; +varying float noise; +varying float mus; + +uniform float inv_persistence; +uniform float time; +uniform float music; +uniform float music2; + +#define M_PI 3.14159265 +const int N_OCTAVES = 5; + +float sampleNoise(vec3 pos) { + float x = fract(sin(dot(pos, vec3(134.9235, 63.5879, 218.9542))) * 27495.2467); + return x; +} + +float interpolate(float a, float b, float t) { + float cos_t = (1. - cos(t * M_PI)) * 0.5; + return a * (1. - cos_t) + b * cos_t; +} + +float interpNoise(vec3 pos, float f) { + + // Calculate the min/max positions of cube + vec3 p0 = floor(pos * f) / f; + vec3 p1 = p0 + 1. / f; + vec3 t = (pos - p0) * f; + + // Find noise values at corners of cube + float A = sampleNoise(vec3(p0.x, p0.y, p0.z)); + float E = sampleNoise(vec3(p1.x, p0.y, p0.z)); + + float B = sampleNoise(vec3(p0.x, p1.y, p0.z)); + float F = sampleNoise(vec3(p1.x, p1.y, p0.z)); + + float C = sampleNoise(vec3(p0.x, p0.y, p1.z)); + float G = sampleNoise(vec3(p1.x, p0.y, p1.z)); + + float D = sampleNoise(vec3(p0.x, p1.y, p1.z)); + float H = sampleNoise(vec3(p1.x, p1.y, p1.z)); + + // First pass of interpolation + float interpLi_AE = interpolate(A, E, t.x); + float interpLi_BF = interpolate(B, F, t.x); + float interpLi_CG = interpolate(C, G, t.x); + float interpLi_DH = interpolate(D, H, t.x); + + // Second pass of interpolation + float interpBi_12 = interpolate(interpLi_AE, interpLi_BF, t.y); + float interpBi_34 = interpolate(interpLi_CG, interpLi_DH, t.y); + + // Third pass + return interpolate(interpBi_12, interpBi_34, t.z); +} + +float multiOctaveNoise(float offset) { + + float total = 0.; + float persistence = 1. / inv_persistence; + + for (int i = 0; i < N_OCTAVES; i++) { + + float frequency = pow(2., float(i)); + float amplitude = pow(persistence, float(i)); + total += interpNoise(position + offset, frequency) * amplitude; + } + + return total; +} + +void main() { + vUv = uv; + mus = music; + + noise = multiOctaveNoise(time); + nor = vec3(projectionMatrix * modelViewMatrix * vec4(normal, 0.)); + gl_Position = projectionMatrix * modelViewMatrix * vec4(position + noise * nor * music, 1.); +} + diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 00000000..57dce485 --- /dev/null +++ b/webpack.config.js @@ -0,0 +1,28 @@ +const path = require('path'); + +module.exports = { + entry: path.join(__dirname, "src/main"), + output: { + filename: "./bundle.js" + }, + module: { + loaders: [ + { + test: /\.js$/, + exclude: /(node_modules|bower_components)/, + loader: 'babel', + query: { + presets: ['es2015'] + } + }, + { + test: /\.glsl$/, + loader: "webpack-glsl" + }, + ] + }, + devtool: 'source-map', + devServer: { + port: 7000 + } +} \ No newline at end of file From add709c12defaae00d285637aa6ebe8ad1f0895d Mon Sep 17 00:00:00 2001 From: xnieamo Date: Tue, 11 Apr 2017 14:14:02 -0400 Subject: [PATCH 02/76] plays some notes woohoo --- package.json | 4 +- src/main.js | 144 +++++++++++++++------------------------------------ 2 files changed, 46 insertions(+), 102 deletions(-) diff --git a/package.json b/package.json index fadfb242..bdaee0e9 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,9 @@ "stats-js": "^1.0.0-alpha1", "three": "^0.82.1", "three-orbit-controls": "^82.1.0", - "tonal": "^0.69.5" + "tonal": "^0.69.5", + "midi.js": "", + "soundfont-player": "^0.10.5" }, "devDependencies": { "babel-core": "^6.18.2", diff --git a/src/main.js b/src/main.js index f3bd585f..d9e9d67e 100644 --- a/src/main.js +++ b/src/main.js @@ -1,6 +1,9 @@ const THREE = require('three'); // older modules are imported like this. You shouldn't have to worry about this much import Framework from './framework' +import MIDI from 'midi.js' +import Soundfont from 'soundfont-player' +var ac = new AudioContext() // Colors var additionalControls = { @@ -17,6 +20,8 @@ var noiseCloud = { mesh : {}, }; +var t = Date.now(); + // called after the scene loads function onLoad(framework) { var scene = framework.scene; @@ -27,35 +32,10 @@ function onLoad(framework) { // LOOK: the line below is synyatic sugar for the code above. Optional, but I sort of recommend it. // var {scene, camera, renderer, gui, stats} = framework; - var adamMaterial = new THREE.ShaderMaterial({ - uniforms: { - image: { // Check the Three.JS documentation for the different allowed types and values - type: "t", - value: THREE.ImageUtils.loadTexture('./adam.jpg') - }, - inv_persistence: { - type: "f", - value: 2.0 - }, - time: { - type: "f", - value: 0. - }, - music: { - type: "f", - value: 1. - }, - music2: { - type: "f", - value: 1. - }, - colorMult: { - value: new THREE.Vector3(additionalControls['Color'][0]/255, additionalControls['Color'][1]/255, additionalControls['Color'][2]/255,) - } - }, - vertexShader: require('./shaders/adam-vert.glsl'), - fragmentShader: require('./shaders/adam-frag.glsl') - }); + var adamMaterial = new THREE.MeshBasicMaterial({ + color:0xFFFFFF, + side:THREE.DoubleSide + }); var iso = new THREE.IcosahedronBufferGeometry(0.7, 6); noiseCloud.mesh = new THREE.Mesh(iso, adamMaterial); @@ -67,6 +47,9 @@ function onLoad(framework) { scene.add(noiseCloud.mesh); + + + // edit params and listen to changes like this // more information here: https://workshop.chromeexperiments.com/examples/gui/#1--Basic-Usage gui.add(camera, 'fov', 0, 180).onChange(function(newVal) { @@ -86,83 +69,42 @@ function onLoad(framework) { noiseCloud.mesh.material.uniforms.colorMult.value = new THREE.Vector3(newVal[0]/255, newVal[1]/255, newVal[2]/255,); }); - - // Audio stuff below - // http://raathigesh.com/Audio-Visualization-with-Web-Audio-and-ThreeJS/ - // http://stackoverflow.com/questions/27589179/basic-web-audio-api-not-playing-a-mp3-file - // http://stackoverflow.com/questions/3273552/html5-audio-looping - var context = new AudioContext(); - var jsNode = context.createScriptProcessor(2048,1,1); - jsNode.connect(context.destination); - - // Load file and set to repeat - var audio = new Audio(); - audio.src = "./sounds/music2.mp3"; //https://www.jamendo.com/track/1350213/jumper - audio.controls = true; - audio.autoplay = true; - audio.addEventListener('ended', function(){ - this.currentTime = 0; - this.play(); - }, false); - audio.loop = true; - - // Play file - var source = context.createMediaElementSource(audio); - source.connect(context.destination); - source.mediaElement.play(); - - // Analyze waveform data - var analyser = context.createAnalyser(); - analyser.fftSize = 128; - analyser.smoothingTimeConstat = 0.8; - source.connect(analyser); - - // Action to take with processed data - jsNode.onaudioprocess = function () { - - // If music sync box is checked - if (additionalControls.music) { - var array = new Uint8Array(analyser.frequencyBinCount); - analyser.getByteFrequencyData(array); - // console.log(analyser.maxDecibels) - - var Z = [array.slice(0, 9).reduce((a, b) => a + b, 0) / 10 /256, - array.slice(10, 19).reduce((a, b) => a + b, 0) / 10 /256, - array.slice(20, 29).reduce((a, b) => a + b, 0) / 10 /256, - array.slice(30, 39).reduce((a, b) => a + b, 0) / 10 /256, - array.slice(40, 49).reduce((a, b) => a + b, 0) / 10 /256, - array.slice(50, 59).reduce((a, b) => a + b, 0) / 10 /256]; - // console.log(Z); - - noiseCloud.mesh.material.uniforms.music.value = Z[4]; - noiseCloud.mesh.material.uniforms.music2.value = Z[1]; - } else { - noiseCloud.mesh.material.uniforms.music.value = 1.; - noiseCloud.mesh.material.uniforms.music2.value = 0.; - } - - - } - - } // called on frame updates function onUpdate(framework) { + var newTime = Date.now(); + var instrumentName = "acoustic_grand_piano"; + if (newTime - t > 1500) { + // MIDI.loadPlugin({ + // soundfontUrl: "./soundfont/", + // instrument: instrumentName, + // onprogress: function(state, progress) { + // console.log(state, progress); + // }, + // onsuccess: function() { + // MIDI.programChange(0, MIDI.GM.byName[instrumentName].number); + + // var delay = 0; // play one note every quarter second + // var note = 50; // the MIDI note + // var velocity = 127; // how hard the note hits + // // play the note + // MIDI.setVolume(0, 127); + // MIDI.noteOn(0, note, velocity, delay); + // MIDI.noteOn(0, note + 2, velocity, delay); + // MIDI.noteOn(0, note + 5, velocity, delay); + // // MIDI.noteOff(0, note, delay + 0.75); + // } + // }); + Soundfont.instrument(ac, 'marimba', { soundfont: 'MusyngKite' }).then(function (marimba) { + marimba.play('C4') + }) + Soundfont.instrument(ac, 'clarinet', { soundfont: 'MusyngKite' }).then(function (marimba) { + marimba.play('D4') + }) + t = newTime; + } - - framework.scene.traverse(function (object) - { - if (object instanceof THREE.Mesh) - { - if (object.name === 'adamCube') { - // var d = new Date(); - // console.log(`the time is ${(object.material.uniforms.time.value)}`); - object.material.uniforms.time.value += .01; - - } - } - }); } // when the scene is done initializing, it will call onLoad, then on frame updates, call onUpdate From f5a3afbd269fee8f9f5731868ee3531b1cf24d16 Mon Sep 17 00:00:00 2001 From: xnieamo Date: Tue, 11 Apr 2017 14:35:03 -0400 Subject: [PATCH 03/76] headaches --- src/main.js | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/main.js b/src/main.js index d9e9d67e..35aa96e0 100644 --- a/src/main.js +++ b/src/main.js @@ -75,6 +75,10 @@ function onLoad(framework) { function onUpdate(framework) { var newTime = Date.now(); var instrumentName = "acoustic_grand_piano"; + var NOTES = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] + var notes = NOTES.map(function (n, i) { + return { time: i * 0.3, note: n + 60 } + }) if (newTime - t > 1500) { // MIDI.loadPlugin({ // soundfontUrl: "./soundfont/", @@ -97,11 +101,14 @@ function onUpdate(framework) { // } // }); Soundfont.instrument(ac, 'marimba', { soundfont: 'MusyngKite' }).then(function (marimba) { - marimba.play('C4') - }) - Soundfont.instrument(ac, 'clarinet', { soundfont: 'MusyngKite' }).then(function (marimba) { - marimba.play('D4') + marimba.play('C4') + marimba.play('E4') + marimba.play('A3') + Soundfont.instrument(ac, 'cello', { soundfont: 'MusyngKite' }).then(function (marimba) { + marimba.schedule(ac.currentTime, notes); + }) }) + t = newTime; } From a01bf1acbfcfb9a2db90e04bd2fb936538ba32b0 Mon Sep 17 00:00:00 2001 From: xnieamo Date: Mon, 17 Apr 2017 10:40:30 -0400 Subject: [PATCH 04/76] euclidian rhythms and beats --- src/main.js | 45 ++++++++++++------------------- src/utils/euclid.js | 54 +++++++++++++++++++++++++++++++++++++ src/utils/musicGenerator.js | 22 +++++++++++++++ 3 files changed, 93 insertions(+), 28 deletions(-) create mode 100644 src/utils/euclid.js create mode 100644 src/utils/musicGenerator.js diff --git a/src/main.js b/src/main.js index 35aa96e0..4e1ba9b1 100644 --- a/src/main.js +++ b/src/main.js @@ -3,6 +3,9 @@ const THREE = require('three'); // older modules are imported like this. You sho import Framework from './framework' import MIDI from 'midi.js' import Soundfont from 'soundfont-player' +import {euclid} from './utils/euclid.js' +import beatGenerator from './utils/musicGenerator.js' + var ac = new AudioContext() // Colors @@ -24,6 +27,9 @@ var t = Date.now(); // called after the scene loads function onLoad(framework) { + console.log(euclid(5,16)); + console.log(beatGenerator(euclid(5,16), 120)) + var scene = framework.scene; var camera = framework.camera; var renderer = framework.renderer; @@ -79,34 +85,17 @@ function onUpdate(framework) { var notes = NOTES.map(function (n, i) { return { time: i * 0.3, note: n + 60 } }) - if (newTime - t > 1500) { - // MIDI.loadPlugin({ - // soundfontUrl: "./soundfont/", - // instrument: instrumentName, - // onprogress: function(state, progress) { - // console.log(state, progress); - // }, - // onsuccess: function() { - // MIDI.programChange(0, MIDI.GM.byName[instrumentName].number); - - // var delay = 0; // play one note every quarter second - // var note = 50; // the MIDI note - // var velocity = 127; // how hard the note hits - // // play the note - // MIDI.setVolume(0, 127); - // MIDI.noteOn(0, note, velocity, delay); - // MIDI.noteOn(0, note + 2, velocity, delay); - // MIDI.noteOn(0, note + 5, velocity, delay); - // // MIDI.noteOff(0, note, delay + 0.75); - // } - // }); - Soundfont.instrument(ac, 'marimba', { soundfont: 'MusyngKite' }).then(function (marimba) { - marimba.play('C4') - marimba.play('E4') - marimba.play('A3') - Soundfont.instrument(ac, 'cello', { soundfont: 'MusyngKite' }).then(function (marimba) { - marimba.schedule(ac.currentTime, notes); - }) + + // console.log(notes); + if (newTime - t > 3000) { + + Soundfont.instrument(ac, 'pad_6_metallic', { soundfont: 'MusyngKite' }).then(function (marimba) { + marimba.schedule(ac.currentTime, beatGenerator(euclid(7,16), 120)); + // marimba.play('E4') + // marimba.play('A3') + // Soundfont.instrument(ac, 'cello', { soundfont: 'MusyngKite' }).then(function (marimba) { + // marimba.schedule(ac.currentTime, notes); + // }) }) t = newTime; diff --git a/src/utils/euclid.js b/src/utils/euclid.js new file mode 100644 index 00000000..f2fc5b41 --- /dev/null +++ b/src/utils/euclid.js @@ -0,0 +1,54 @@ +export {euclid} + +// http://cgm.cs.mcgill.ca/~godfried/publications/banff.pdf +// The euclid function is an implementation of the Bjorklund algorithm +// described in the paper linked above. + +function euclid(m, k) { + // Create the head 1's and tail 0's + var head = []; + var tail = []; + for (var i = 0; i < m; i++) { head.push([1]) }; + for (var i = 0; i < k-m; i++) { tail.push(0) }; + + // When m>=k/2, we need to add the extra 1's to the tail. + if (m >= Math.floor(k/2)) { + // Remove extra 1's from head. Then assign all 0's to each head. + var overflow = k - m; + head.splice(overflow, m-overflow); + for (var i = 0; i < head.length; i++) { + var t = tail.pop(); + head[i].push(t); + } + + // Create new tail of 1's + for (var i = 0; i < m - overflow; i++) { tail.push(1) }; + } + + // Run algorithm as usual. + var result = euclidHelper(head, tail); + head = result.h; + + var rhythm = []; + for (var i = 0; i < head.length; i++) { + rhythm = rhythm.concat(head[i]); + } + return rhythm; +} + +function euclidHelper(head, tail) { + while (tail.length > 0) { + if (tail.length < head.length) { + head[head.length-1] = head[head.length-1].concat(tail); + break; + } + + for (var i = 0; i < head.length; i++) { + if (tail.length == 0) + break; + var t = tail.pop(); + head[i].push(t); + } + } + return {h: head, t: tail}; +} \ No newline at end of file diff --git a/src/utils/musicGenerator.js b/src/utils/musicGenerator.js new file mode 100644 index 00000000..eb88c97b --- /dev/null +++ b/src/utils/musicGenerator.js @@ -0,0 +1,22 @@ + + + +export default function beatGenerator(rhythm, tempo) { + var unitTime = 60 / tempo / rhythm.length * 4; + var analogRhythm = []; + var beatLength = 0; + for (var i = 0; i < rhythm.length; i++) { + if (rhythm[i] == 1 && i > 0) { + analogRhythm.push(i-1); + } + } + analogRhythm.push(rhythm.length - 1); + // console.log(analogRhythm) + + var notes = []; + for (var i = 0; i < analogRhythm.length; i++) { + notes.push({note : (i==0 ? 30:60), time : analogRhythm[i] * unitTime}); + } + // console.log(notes) + return notes; +} \ No newline at end of file From 37a6578710c0bdcc6319235947ba62ba72b525f2 Mon Sep 17 00:00:00 2001 From: xnieamo Date: Mon, 17 Apr 2017 16:53:50 -0400 Subject: [PATCH 05/76] MorseThue and got tonal to work --- package.json | 5 +- src/fractals/lsystem.js | 156 ++++++++++++++++++++++++++++++++++++ src/main.js | 28 +++++-- src/utils/musicGenerator.js | 38 ++++++++- webpack.config.js | 8 ++ 5 files changed, 222 insertions(+), 13 deletions(-) create mode 100644 src/fractals/lsystem.js diff --git a/package.json b/package.json index bdaee0e9..27d1d6ad 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "stats-js": "^1.0.0-alpha1", "three": "^0.82.1", "three-orbit-controls": "^82.1.0", - "tonal": "^0.69.5", + "tonal": "0.69.0", "midi.js": "", "soundfont-player": "^0.10.5" }, @@ -27,6 +27,7 @@ "gh-pages-deploy": "^0.4.2", "webpack": "^1.13.3", "webpack-dev-server": "^1.16.2", - "webpack-glsl-loader": "^1.0.1" + "webpack-glsl-loader": "^1.0.1", + "json-loader": "^0.5.4" } } diff --git a/src/fractals/lsystem.js b/src/fractals/lsystem.js new file mode 100644 index 00000000..00ba7c77 --- /dev/null +++ b/src/fractals/lsystem.js @@ -0,0 +1,156 @@ +// A class that represents a symbol replacement rule to +// be used when expanding an L-system grammar. +function Rule(prob, str) { + this.probability = prob; // The probability that this Rule will be used when replacing a character in the grammar string + this.successorString = str; // The string that will replace the char that maps to this Rule +} + +// TODO: Implement a linked list class and its requisite functions +// as described in the homework writeup +var Node = { + symbol : '', + nextNode : null, + prevNode : null +}; + +function LinkedList(){ + this.head = null; + this.tail = null; + this.push = function(val) { + if (!this.head) { + this.head = {symbol: val, nextNode: null, prevNode: null}; + this.tail = this.head; + } else if (this.head == this.tail) { + var newNode = {symbol: val, nextNode: null, prevNode: this.head}; + this.head.nextNode = newNode; + this.tail = newNode; + } else { + var newNode = {symbol: val, nextNode: null, prevNode: this.tail}; + this.tail.nextNode = newNode; + this.tail = newNode; + } + }; +}; + +// TODO: Turn the string into linked list +export function stringToLinkedList(input_string) { + // ex. assuming input_string = "F+X" + // you should return a linked list where the head is + // at Node('F') and the tail is at Node('X') + var ll = new LinkedList(); + for (var i = 0; i < input_string.length; i++) { + ll.push(input_string.charAt(i)); + } + return ll; +} + +// TODO: Return a string form of the LinkedList +export function linkedListToString(linkedList) { + // ex. Node1("F")->Node2("X") should be "FX" + var node = linkedList.head; + var result = ''; + + while (node) { + result = result.concat(node.symbol); + node = node.nextNode; + } + return result; +} + +// TODO: Given the node to be replaced, +// insert a sub-linked-list that represents replacementString +function replaceNode(linkedList, node, replacementString) { + var replacementList = stringToLinkedList(replacementString); + if (linkedList.head == linkedList.tail == node) { + linkedList = replacementList; + } + else if (linkedList.head == node) { + var next = node.next; + linkedList.head = replacementList.head; + next.prevNode = replacementList.tail; + replacementList.tail.next = next; + } + else if (linkedList.tail == node) { + var prev = node.prevNode; + linkedList.tail = replacementList.tail; + prev.nextNode = replacementList.head; + replacementList.head.prevNode = prev; + } else { + var next = node.nextNode; + var prev = node.prevNode; + next.prevNode = replacementList.tail; + replacementList.tail.nextNode = next; + + prev.nextNode = replacementList.head; + replacementList.head.nextNode = prev; + } + return replacementList.tail.next; +} + +export default function Lsystem(axiom, grammar, iterations) { + // default LSystem + this.axiom = "FX"; + this.grammar = {}; + this.grammar['X'] = [ + new Rule(1.0, '[-FX][+FX]') + ]; + this.iterations = 0; + + // Set up the axiom string + if (typeof axiom !== "undefined") { + this.axiom = axiom; + } + + // Set up the grammar as a dictionary that + // maps a single character (symbol) to a Rule. + if (typeof grammar !== "undefined") { + this.grammar = Object.assign({}, grammar); + } + + // Set up iterations (the number of times you + // should expand the axiom in DoIterations) + if (typeof iterations !== "undefined") { + this.iterations = iterations; + } + + // A function to alter the axiom string stored + // in the L-system + this.updateAxiom = function(axiom) { + // Setup axiom + if (typeof axiom !== "undefined") { + this.axiom = axiom; + } + } + + this.updateString = function(n) { + var stringResult = this.axiom; + for (var i = 0; i < n; i++) { + var newString = ''; + for (var j = 0; j < stringResult.length; j++) { + var currentChar = stringResult.charAt(j); + if (this.grammar[currentChar]) { + newString = newString.concat(this.grammar[currentChar].successorString); + } else { + newString = newString.concat(currentChar); + } + } + stringResult = newString; + } + + // console.log(stringResult); + return stringResult; + } + + + // TODO + // This function returns a linked list that is the result + // of expanding the L-system's axiom n times. + // The implementation we have provided you just returns a linked + // list of the axiom. + this.doIterations = function(n) { + + var lSystemLL = stringToLinkedList(this.updateString(n)); + + return linkedListToString(lSystemLL); + } +} \ No newline at end of file diff --git a/src/main.js b/src/main.js index 4e1ba9b1..d8cd2420 100644 --- a/src/main.js +++ b/src/main.js @@ -1,10 +1,14 @@ const THREE = require('three'); // older modules are imported like this. You shouldn't have to worry about this much +const tonal = require('tonal') + import Framework from './framework' import MIDI from 'midi.js' import Soundfont from 'soundfont-player' import {euclid} from './utils/euclid.js' -import beatGenerator from './utils/musicGenerator.js' +import {beatGenerator, MorseThue} from './utils/musicGenerator.js' +import Lsystem from './fractals/lsystem.js' + var ac = new AudioContext() @@ -30,6 +34,16 @@ function onLoad(framework) { console.log(euclid(5,16)); console.log(beatGenerator(euclid(5,16), 120)) + var grammar = {}; + grammar['0'] = {probability: 1.0, successorString: '01'}; + grammar['1'] = {probability: 1.0, successorString: '10'}; + var L = new Lsystem('0',grammar, 2); + console.log(L.doIterations(3)); + + var s = tonal.scale.get('major', 'C4'); + console.log(s) + console.log(tonal.transpose(s[0], 'P8')) + var scene = framework.scene; var camera = framework.camera; var renderer = framework.renderer; @@ -89,13 +103,11 @@ function onUpdate(framework) { // console.log(notes); if (newTime - t > 3000) { - Soundfont.instrument(ac, 'pad_6_metallic', { soundfont: 'MusyngKite' }).then(function (marimba) { - marimba.schedule(ac.currentTime, beatGenerator(euclid(7,16), 120)); - // marimba.play('E4') - // marimba.play('A3') - // Soundfont.instrument(ac, 'cello', { soundfont: 'MusyngKite' }).then(function (marimba) { - // marimba.schedule(ac.currentTime, notes); - // }) + // Soundfont.instrument(ac, 'acoustic_grand_piano', { soundfont: 'MusyngKite' }).then(function (marimba) { + // marimba.schedule(ac.currentTime, MorseThue(2,1,8,120)); + // }) + Soundfont.instrument(ac, 'acoustic_grand_piano', { soundfont: 'MusyngKite' }).then(function (marimba) { + marimba.schedule(ac.currentTime, beatGenerator(euclid(9,16), 120)); }) t = newTime; diff --git a/src/utils/musicGenerator.js b/src/utils/musicGenerator.js index eb88c97b..5b4ac212 100644 --- a/src/utils/musicGenerator.js +++ b/src/utils/musicGenerator.js @@ -1,7 +1,7 @@ +var tonal = require('tonal') - -export default function beatGenerator(rhythm, tempo) { +export function beatGenerator(rhythm, tempo) { var unitTime = 60 / tempo / rhythm.length * 4; var analogRhythm = []; var beatLength = 0; @@ -14,9 +14,41 @@ export default function beatGenerator(rhythm, tempo) { // console.log(analogRhythm) var notes = []; + var s = tonal.scale.get('major', 'C4'); for (var i = 0; i < analogRhythm.length; i++) { - notes.push({note : (i==0 ? 30:60), time : analogRhythm[i] * unitTime}); + // notes.push({note : (i==0 ? 20:31), time : analogRhythm[i] * unitTime}); + notes.push({note : MorseThueSingle(2, 1, i, s), time : analogRhythm[i] * unitTime}); } // console.log(notes) return notes; +} + +// n is number of notes +export function MorseThue(base, multi, n, tempo) { + var s = tonal.scale.get('major', 'C4'); + var unitTime = 60 / tempo / n * 4; + var notes = []; + for (var i = 0; i < n; i++) { + notes.push({note: MorseThueSingle(base, multi, i, s), time: i * unitTime}); + } + return notes; +} + +function MorseThueSingle(base, multi, n, scale) { + var val = n * multi; + val = parseInt(val.toString(base), 10); + val = sumDigits(val) % scale.length; + return scale[val] +} + +// http://stackoverflow.com/questions/9138064/sum-of-the-digits-of-a-number-javascript +function sumDigits(number) { + var str = number.toString(); + var sum = 0; + + for (var i = 0; i < str.length; i++) { + sum += parseInt(str.charAt(i), 10); + } + + return sum; } \ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js index 57dce485..1019f92c 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -19,10 +19,18 @@ module.exports = { test: /\.glsl$/, loader: "webpack-glsl" }, + { + test: /\.json$/, + loader: "json" + } ] }, devtool: 'source-map', devServer: { port: 7000 + }, + resolve : { + root : __dirname, + moduleDirectories : ["node_modules/tonal"] } } \ No newline at end of file From f1acd4f35ecdfb28bb9de40552ba660a104f0ff5 Mon Sep 17 00:00:00 2001 From: xnieamo Date: Mon, 17 Apr 2017 20:15:07 -0400 Subject: [PATCH 06/76] playing around with chords --- src/main.js | 44 ++++++++++------------------- src/utils/euclid.js | 2 ++ src/utils/musicGenerator.js | 55 +++++++++++++++++++++++++++++++++++-- 3 files changed, 69 insertions(+), 32 deletions(-) diff --git a/src/main.js b/src/main.js index d8cd2420..a71397af 100644 --- a/src/main.js +++ b/src/main.js @@ -6,7 +6,7 @@ import Framework from './framework' import MIDI from 'midi.js' import Soundfont from 'soundfont-player' import {euclid} from './utils/euclid.js' -import {beatGenerator, MorseThue} from './utils/musicGenerator.js' +import {beatGenerator, MorseThue, melodyGenerator} from './utils/musicGenerator.js' import Lsystem from './fractals/lsystem.js' @@ -31,18 +31,19 @@ var t = Date.now(); // called after the scene loads function onLoad(framework) { - console.log(euclid(5,16)); - console.log(beatGenerator(euclid(5,16), 120)) + console.log(euclid(8,8)); var grammar = {}; grammar['0'] = {probability: 1.0, successorString: '01'}; grammar['1'] = {probability: 1.0, successorString: '10'}; var L = new Lsystem('0',grammar, 2); - console.log(L.doIterations(3)); + // console.log(L.doIterations(3)); - var s = tonal.scale.get('major', 'C4'); - console.log(s) - console.log(tonal.transpose(s[0], 'P8')) + // var s = tonal.scale.get('major', 'C4'); + // console.log(s) + console.log(tonal.transpose('C2', 'P8')) + console.log(tonal.ivl.invert('P8')) + // console.log(tonal.chord.names()) var scene = framework.scene; var camera = framework.camera; @@ -67,8 +68,13 @@ function onLoad(framework) { scene.add(noiseCloud.mesh); - - + var music = melodyGenerator(50, 120); + // Soundfont.instrument(ac, 'acoustic_grand_piano', { soundfont: 'MusyngKite', gain: 2 }).then(function (marimba) { + // marimba.schedule(ac.currentTime, music[0]); + // }) + Soundfont.instrument(ac, 'acoustic_grand_piano', { soundfont: 'MusyngKite' }).then(function (marimba) { + marimba.schedule(ac.currentTime, music[1]); + }) // edit params and listen to changes like this // more information here: https://workshop.chromeexperiments.com/examples/gui/#1--Basic-Usage @@ -93,26 +99,6 @@ function onLoad(framework) { // called on frame updates function onUpdate(framework) { - var newTime = Date.now(); - var instrumentName = "acoustic_grand_piano"; - var NOTES = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12] - var notes = NOTES.map(function (n, i) { - return { time: i * 0.3, note: n + 60 } - }) - - // console.log(notes); - if (newTime - t > 3000) { - - // Soundfont.instrument(ac, 'acoustic_grand_piano', { soundfont: 'MusyngKite' }).then(function (marimba) { - // marimba.schedule(ac.currentTime, MorseThue(2,1,8,120)); - // }) - Soundfont.instrument(ac, 'acoustic_grand_piano', { soundfont: 'MusyngKite' }).then(function (marimba) { - marimba.schedule(ac.currentTime, beatGenerator(euclid(9,16), 120)); - }) - - t = newTime; - } - } // when the scene is done initializing, it will call onLoad, then on frame updates, call onUpdate diff --git a/src/utils/euclid.js b/src/utils/euclid.js index f2fc5b41..4acc4cc6 100644 --- a/src/utils/euclid.js +++ b/src/utils/euclid.js @@ -24,6 +24,8 @@ function euclid(m, k) { // Create new tail of 1's for (var i = 0; i < m - overflow; i++) { tail.push(1) }; } + + if (head.length == 0) { head = [[]]; } // Run algorithm as usual. var result = euclidHelper(head, tail); diff --git a/src/utils/musicGenerator.js b/src/utils/musicGenerator.js index 5b4ac212..da35914d 100644 --- a/src/utils/musicGenerator.js +++ b/src/utils/musicGenerator.js @@ -13,11 +13,23 @@ export function beatGenerator(rhythm, tempo) { analogRhythm.push(rhythm.length - 1); // console.log(analogRhythm) - var notes = []; - var s = tonal.scale.get('major', 'C4'); + var notes = [[],[],[],[]]; + var s = tonal.scale.get('minor', 'C4'); + var chordTypes = tonal.chord.names(); + var chordIdx = Math.floor(Math.random() * chordTypes.length); + // console.log(tonal.chord.get('Maj7','C4')) for (var i = 0; i < analogRhythm.length; i++) { // notes.push({note : (i==0 ? 20:31), time : analogRhythm[i] * unitTime}); - notes.push({note : MorseThueSingle(2, 1, i, s), time : analogRhythm[i] * unitTime}); + var baseNote = MorseThueSingle(6, 7, i, s); + var thisChord = tonal.chord.get(chordTypes[chordIdx], baseNote); + notes[0].push({note : thisChord[0], time : analogRhythm[i] * unitTime}); + if (i % 4 == 0) { + notes[1].push({note : thisChord[1], time : analogRhythm[i] * unitTime}); + notes[2].push({note : thisChord[2], time : analogRhythm[i] * unitTime}); + if (thisChord.length > 3) + notes[3].push({note : thisChord[3], time : analogRhythm[i] * unitTime}); + } + } // console.log(notes) return notes; @@ -34,6 +46,43 @@ export function MorseThue(base, multi, n, tempo) { return notes; } +// n is total number of notes +export function melodyGenerator(n, tempo) { + var unitTime = 60 / tempo; + var scaleTypes = tonal.scale.names(); + var scaleIdx = Math.floor( Math.random() * scaleTypes.length ); + var s = tonal.scale.get(scaleTypes[scaleIdx], 'C2'); + + var chordTypes = tonal.chord.names(); + var chordIdx = Math.floor( Math.random() * chordTypes.length ); + + var notes = [[],[]]; + for ( var i = 0; i < n; i++ ) { + var note = MorseThueSingle( 3, 5, i, s ) + notes[0].push( {note: note, time: i * unitTime} ); + + + // Transpose note up 3 octaves for the arpeggios. + var tNote = note; + tNote = tonal.transpose( tNote, '8P' ); + tNote = tonal.transpose( tNote, '8P' ); + // tNote = tonal.transpose( tNote, '8P' ); + + // Create a chord + var chord = tonal.chord.get( chordTypes[chordIdx], tNote ); + + // Adjust arpeggio pattern if 3 note chord. + if ( chord.length == 3 ) { chord.push(chord[1]); } + + // Push notes at 16th time + for ( var j = 0; j < 4; j++ ) { + notes[1].push( {note: chord[j], time: i * unitTime + j * unitTime / 4} ); + } + } + + return notes; +} + function MorseThueSingle(base, multi, n, scale) { var val = n * multi; val = parseInt(val.toString(base), 10); From e658ba9e52f4df72a841cee92f31d3ed9efbaea4 Mon Sep 17 00:00:00 2001 From: xnieamo Date: Mon, 17 Apr 2017 23:33:20 -0400 Subject: [PATCH 07/76] readjust how chords work --- src/main.js | 11 +++--- src/utils/musicGenerator.js | 72 ++++++++++++++++++++++++++++++++----- 2 files changed, 70 insertions(+), 13 deletions(-) diff --git a/src/main.js b/src/main.js index a71397af..3a868162 100644 --- a/src/main.js +++ b/src/main.js @@ -42,7 +42,7 @@ function onLoad(framework) { // var s = tonal.scale.get('major', 'C4'); // console.log(s) console.log(tonal.transpose('C2', 'P8')) - console.log(tonal.ivl.invert('P8')) + console.log(tonal.ivl.invert(['C4', 'E4'])) // console.log(tonal.chord.names()) var scene = framework.scene; @@ -69,12 +69,13 @@ function onLoad(framework) { scene.add(noiseCloud.mesh); var music = melodyGenerator(50, 120); - // Soundfont.instrument(ac, 'acoustic_grand_piano', { soundfont: 'MusyngKite', gain: 2 }).then(function (marimba) { - // marimba.schedule(ac.currentTime, music[0]); - // }) - Soundfont.instrument(ac, 'acoustic_grand_piano', { soundfont: 'MusyngKite' }).then(function (marimba) { + Soundfont.instrument(ac, 'acoustic_grand_piano', { soundfont: 'MusyngKite', gain: 2 }).then(function (marimba) { + marimba.schedule(ac.currentTime, music[0]); marimba.schedule(ac.currentTime, music[1]); }) + // Soundfont.instrument(ac, 'acoustic_grand_piano', { soundfont: 'MusyngKite' }).then(function (marimba) { + // marimba.schedule(ac.currentTime, music[1]); + // }) // edit params and listen to changes like this // more information here: https://workshop.chromeexperiments.com/examples/gui/#1--Basic-Usage diff --git a/src/utils/musicGenerator.js b/src/utils/musicGenerator.js index da35914d..68ec6880 100644 --- a/src/utils/musicGenerator.js +++ b/src/utils/musicGenerator.js @@ -49,40 +49,96 @@ export function MorseThue(base, multi, n, tempo) { // n is total number of notes export function melodyGenerator(n, tempo) { var unitTime = 60 / tempo; + // var scaleTypes = tonal.scale.names(); + // var scaleIdx = Math.floor( Math.random() * scaleTypes.length ); + // var s = tonal.scale.get(scaleTypes[scaleIdx], 'C2'); + // s.reverse(); + + // var chordTypes = tonal.chord.names(); + // var chordIdx = Math.floor( Math.random() * chordTypes.length ); + + // var notes = [[],[]]; + // for ( var i = 0; i < n; i++ ) { + // var note = MorseThueSingle( 6, 7, i, s ) + // notes[0].push( {note: note, time: i * unitTime} ); + + + // // Transpose note up 3 octaves for the arpeggios. + // var tNote = note; + // tNote = tonal.transpose( tNote, '8P' ); + // tNote = tonal.transpose( tNote, '8P' ); + // // tNote = tonal.transpose( tNote, '8P' ); + + // // Create a chord + // var chord = tonal.chord.get( chordTypes[chordIdx], tNote ); + // // chord = tonal.chord.inversion(0, chord); + + // // Adjust arpeggio pattern if 3 note chord. + // if ( chord.length == 3 ) { chord.push(chord[1]); } + + // // Push notes at 16th time + // for ( var j = 0; j < 4; j++ ) { + // notes[1].push( {note: chord[j], time: i * unitTime + j * unitTime / 4} ); + // } + // } + var notes = [[],[]]; + var mel = melodyNotes(n); + for ( var i = 0; i < mel[0].length; i++ ) { + notes[0].push( {note: mel[0][i], time: i * unitTime}) + } + for ( var i = 0; i < mel[1].length; i++ ) { + notes[1].push( {note: mel[1][i], time: i * unitTime / 4}) + } + + return notes; +} + +function melodyNotes(n) { var scaleTypes = tonal.scale.names(); var scaleIdx = Math.floor( Math.random() * scaleTypes.length ); - var s = tonal.scale.get(scaleTypes[scaleIdx], 'C2'); + var s = tonal.scale.get(scaleTypes[scaleIdx], 'C4'); + s.reverse(); var chordTypes = tonal.chord.names(); var chordIdx = Math.floor( Math.random() * chordTypes.length ); var notes = [[],[]]; for ( var i = 0; i < n; i++ ) { - var note = MorseThueSingle( 3, 5, i, s ) - notes[0].push( {note: note, time: i * unitTime} ); - + var note = MorseThueSingle( 3, 4, i, s ) + notes[0].push( note ); // Transpose note up 3 octaves for the arpeggios. var tNote = note; - tNote = tonal.transpose( tNote, '8P' ); - tNote = tonal.transpose( tNote, '8P' ); + tNote = tonal.transpose( tNote, '3M' ); + // tNote = tonal.transpose( tNote, '8P' ); // tNote = tonal.transpose( tNote, '8P' ); // Create a chord - var chord = tonal.chord.get( chordTypes[chordIdx], tNote ); + // var chord = tonal.chord.get( chordTypes[chordIdx], tNote ); + var chord = createChord(tNote); + // chord = tonal.chord.inversion(0, chord); // Adjust arpeggio pattern if 3 note chord. if ( chord.length == 3 ) { chord.push(chord[1]); } // Push notes at 16th time for ( var j = 0; j < 4; j++ ) { - notes[1].push( {note: chord[j], time: i * unitTime + j * unitTime / 4} ); + notes[1].push( chord[j] ); } } return notes; } +// Alternative chord creation +// https://github.com/devinroth/GenerativeMusic/blob/master/Moonlight.playground/Contents.swift +function createChord(note) { + var midiNote = tonal.note.midi(note); + var third = midiNote + 3 + Math.round(Math.random() * 2); + var fifth = midiNote + 7; + return [midiNote, third, fifth]; +} + function MorseThueSingle(base, multi, n, scale) { var val = n * multi; val = parseInt(val.toString(base), 10); From 2cf26c5d7fa7e978e51683caba42abc040450ee7 Mon Sep 17 00:00:00 2001 From: xnieamo Date: Tue, 18 Apr 2017 21:33:59 -0400 Subject: [PATCH 08/76] add second instrument and drum beats --- src/main.js | 121 ++++++++++++++++++-------- src/utils/musicGenerator.js | 165 +++++++++++++++++++++--------------- 2 files changed, 182 insertions(+), 104 deletions(-) diff --git a/src/main.js b/src/main.js index 3a868162..0089a0d1 100644 --- a/src/main.js +++ b/src/main.js @@ -6,43 +6,78 @@ import Framework from './framework' import MIDI from 'midi.js' import Soundfont from 'soundfont-player' import {euclid} from './utils/euclid.js' -import {beatGenerator, MorseThue, melodyGenerator} from './utils/musicGenerator.js' +import {beatGenerator, MorseThue, melodyGenerator, rhythmicMelodyGenerator, EarthWorm} from './utils/musicGenerator.js' import Lsystem from './fractals/lsystem.js' var ac = new AudioContext() -// Colors +// parameters var additionalControls = { - 'Color' : [255, 255, 255], - 'scale' : 1., - 'music' : true, - 'inv persistence' : 2., - 'radius' : 0.7, - 'detail' : 6. + 'base' : 11, + 'multi' : 13 }; +// update flag +var update = true; + // Local global to allow for modifying variables var noiseCloud = { mesh : {}, }; -var t = Date.now(); +var testInstrument = Soundfont.instrument(ac, 'acoustic_grand_piano', { soundfont: 'MusyngKite', gain: 1.25, adsr: [0.5, 0.8, 1, 0.7] }); +var instrument2 = Soundfont.instrument(ac, 'acoustic_grand_piano', { soundfont: 'MusyngKite', gain: 1 }); +var synthDrums = Soundfont.instrument(ac, 'synth_drum', { soundfont: 'MusyngKite', gain: 0.5 }); + +function rate_limit(func) { + var running = false; + var next = undefined; + + function onDone() { + running = false; // set the flag to allow the function to be called again + if (typeof next !== 'undefined') { + callFunc(next); // call the function again with the queued args + } + } + + function callFunc(args) { + if (running) { + // if the function is already running, remember the arguments passed in so we can call the func with them after we're ready + next = args; + } else { + running = true; // prevent other function calls from running until we're done + next = undefined; + func.apply(func, args); // call the func with the arguments + } + } + + // return a new function wrapping the function we want to rate limit + return function() { + // we use the same arguments but add the onDone callback as the last argument + var args = new Array(arguments.length + 1); + for (var i = 0; i < arguments.length; ++i) { + args[i] = arguments[i]; + } + args[arguments.length] = onDone; + callFunc(args); + } +} // called after the scene loads function onLoad(framework) { - console.log(euclid(8,8)); + // console.log(euclid(8,8)); - var grammar = {}; - grammar['0'] = {probability: 1.0, successorString: '01'}; - grammar['1'] = {probability: 1.0, successorString: '10'}; - var L = new Lsystem('0',grammar, 2); + // var grammar = {}; + // grammar['0'] = {probability: 1.0, successorString: '01'}; + // grammar['1'] = {probability: 1.0, successorString: '10'}; + // var L = new Lsystem('0',grammar, 2); // console.log(L.doIterations(3)); // var s = tonal.scale.get('major', 'C4'); // console.log(s) - console.log(tonal.transpose('C2', 'P8')) - console.log(tonal.ivl.invert(['C4', 'E4'])) + // console.log(tonal.transpose('C2', 'P8')) + // console.log(tonal.ivl.invert(['C4', 'E4'])) // console.log(tonal.chord.names()) var scene = framework.scene; @@ -68,14 +103,6 @@ function onLoad(framework) { scene.add(noiseCloud.mesh); - var music = melodyGenerator(50, 120); - Soundfont.instrument(ac, 'acoustic_grand_piano', { soundfont: 'MusyngKite', gain: 2 }).then(function (marimba) { - marimba.schedule(ac.currentTime, music[0]); - marimba.schedule(ac.currentTime, music[1]); - }) - // Soundfont.instrument(ac, 'acoustic_grand_piano', { soundfont: 'MusyngKite' }).then(function (marimba) { - // marimba.schedule(ac.currentTime, music[1]); - // }) // edit params and listen to changes like this // more information here: https://workshop.chromeexperiments.com/examples/gui/#1--Basic-Usage @@ -83,23 +110,45 @@ function onLoad(framework) { camera.updateProjectionMatrix(); }); - gui.add(additionalControls, 'inv persistence', 1., 10.).onChange(function(newVal) { - noiseCloud.mesh.material.uniforms.inv_persistence.value = newVal; - }); - - gui.add(additionalControls, 'music').onChange(function(newVal) { - additionalControls.music = newVal; - }); - - // Color menu - gui.addColor(additionalControls, 'Color').onChange(function(newVal) { - noiseCloud.mesh.material.uniforms.colorMult.value = new THREE.Vector3(newVal[0]/255, newVal[1]/255, newVal[2]/255,); - }); + gui.add(additionalControls, 'base', 1, 20).onChange(rate_limit(function(newVal, done) { + additionalControls['base'] = Math.round(newVal); + update = true; + })); + gui.add(additionalControls, 'multi', 1, 20).onChange(rate_limit(function(newVal, done) { + additionalControls['multi'] = Math.round(newVal); + update = true; + })); } // called on frame updates function onUpdate(framework) { + if (update) { + var music = melodyGenerator(100, 120, additionalControls.base, additionalControls.multi); + var music2 = EarthWorm(345,17,5,100,120); + var rMusic = rhythmicMelodyGenerator(100, euclid(3,8), 60, additionalControls.base, additionalControls.multi); + var rMusic2 = rhythmicMelodyGenerator(200, euclid(6,8), 240, additionalControls.base + 3, additionalControls.multi + 4); + var beat = beatGenerator(euclid(4,6), 120, 100); + + var t = 10; + + testInstrument.then(function (marimba) { + marimba.stop(); + marimba.schedule(ac.currentTime, music[0]); + + }) + synthDrums.then(function (marimba) { + marimba.stop(); + marimba.schedule(ac.currentTime, beat); + }) + + instrument2.then(function (marimba) { + marimba.stop(); + marimba.schedule(ac.currentTime, music2); + }) + + update = false; + } } // when the scene is done initializing, it will call onLoad, then on frame updates, call onUpdate diff --git a/src/utils/musicGenerator.js b/src/utils/musicGenerator.js index 68ec6880..4d5df79e 100644 --- a/src/utils/musicGenerator.js +++ b/src/utils/musicGenerator.js @@ -1,37 +1,21 @@ var tonal = require('tonal') -export function beatGenerator(rhythm, tempo) { - var unitTime = 60 / tempo / rhythm.length * 4; - var analogRhythm = []; - var beatLength = 0; - for (var i = 0; i < rhythm.length; i++) { - if (rhythm[i] == 1 && i > 0) { - analogRhythm.push(i-1); - } - } - analogRhythm.push(rhythm.length - 1); +export function beatGenerator(rhythm, tempo, n) { + var unitTime = 60 / tempo; + var analogRhythm = getAnalogRhythm(rhythm); // console.log(analogRhythm) - var notes = [[],[],[],[]]; - var s = tonal.scale.get('minor', 'C4'); - var chordTypes = tonal.chord.names(); - var chordIdx = Math.floor(Math.random() * chordTypes.length); - // console.log(tonal.chord.get('Maj7','C4')) - for (var i = 0; i < analogRhythm.length; i++) { - // notes.push({note : (i==0 ? 20:31), time : analogRhythm[i] * unitTime}); - var baseNote = MorseThueSingle(6, 7, i, s); - var thisChord = tonal.chord.get(chordTypes[chordIdx], baseNote); - notes[0].push({note : thisChord[0], time : analogRhythm[i] * unitTime}); - if (i % 4 == 0) { - notes[1].push({note : thisChord[1], time : analogRhythm[i] * unitTime}); - notes[2].push({note : thisChord[2], time : analogRhythm[i] * unitTime}); - if (thisChord.length > 3) - notes[3].push({note : thisChord[3], time : analogRhythm[i] * unitTime}); - } + var notes = []; + var t = 0; + for (var i = 0; i < n; i++) { + var t = analogRhythm[i % analogRhythm.length] + Math.floor(i / analogRhythm.length) * analogRhythm[analogRhythm.length-1]; + + notes.push({note : 'D3', time : t * unitTime}); } // console.log(notes) + // console.log(baseConversion(101,2)); return notes; } @@ -46,77 +30,99 @@ export function MorseThue(base, multi, n, tempo) { return notes; } -// n is total number of notes -export function melodyGenerator(n, tempo) { +// n is number of notes +export function EarthWorm(init, multi, digits, n, tempo) { var unitTime = 60 / tempo; - // var scaleTypes = tonal.scale.names(); - // var scaleIdx = Math.floor( Math.random() * scaleTypes.length ); - // var s = tonal.scale.get(scaleTypes[scaleIdx], 'C2'); - // s.reverse(); - - // var chordTypes = tonal.chord.names(); - // var chordIdx = Math.floor( Math.random() * chordTypes.length ); - - // var notes = [[],[]]; - // for ( var i = 0; i < n; i++ ) { - // var note = MorseThueSingle( 6, 7, i, s ) - // notes[0].push( {note: note, time: i * unitTime} ); + var scaleTypes = tonal.scale.names(); + var scaleIdx = Math.floor( Math.random() * scaleTypes.length ); + var s = tonal.scale.get('major', 'C4'); + var ts = tonal.map(tonal.transpose('8P'), s) + s = s.concat(ts); - // // Transpose note up 3 octaves for the arpeggios. - // var tNote = note; - // tNote = tonal.transpose( tNote, '8P' ); - // tNote = tonal.transpose( tNote, '8P' ); - // // tNote = tonal.transpose( tNote, '8P' ); - // // Create a chord - // var chord = tonal.chord.get( chordTypes[chordIdx], tNote ); - // // chord = tonal.chord.inversion(0, chord); + var notes = []; + var val = init; + var restraint = Math.pow(10, digits); + for (var i = 0; i < n; i++) { + val *= multi; + val = val % restraint; + notes[i] = { note: s[val % s.length], time: i*unitTime }; + } - // // Adjust arpeggio pattern if 3 note chord. - // if ( chord.length == 3 ) { chord.push(chord[1]); } + // console.log(notes) + return notes; +} - // // Push notes at 16th time - // for ( var j = 0; j < 4; j++ ) { - // notes[1].push( {note: chord[j], time: i * unitTime + j * unitTime / 4} ); - // } - // } +// n is total number of notes +export function melodyGenerator(n, tempo, base, multi) { + var unitTime = 60 / tempo; var notes = [[],[]]; - var mel = melodyNotes(n); + var mel = melodyNotes(n, base, multi); for ( var i = 0; i < mel[0].length; i++ ) { - notes[0].push( {note: mel[0][i], time: i * unitTime}) + var t = i * unitTime; + notes[0].push({ note: tonal.note.midi(mel[0][i]), time: t }); } for ( var i = 0; i < mel[1].length; i++ ) { - notes[1].push( {note: mel[1][i], time: i * unitTime / 4}) + notes[1].push({ note: tonal.note.midi(mel[1][i]), time: i * unitTime / 4 }); } return notes; } -function melodyNotes(n) { +export function rhythmicMelodyGenerator(n, rhythm, tempo, base, multi) { + var unitTime = 60 / tempo; + var notes = [[],[],[]]; + var mel = melodyNotes(n, base, multi); + // console.log(mel) + + var aRhythm = getAnalogRhythm(rhythm); + + // var timetest = []; + for ( var i = 0; i < mel[0].length; i++ ) { + var c = createChord(mel[0][i]); + var t = (aRhythm[i % aRhythm.length] + Math.floor(i/aRhythm.length) * (aRhythm[aRhythm.length-1] + 1)) * unitTime; + notes[0].push( {note: mel[0][i], time: t} ); + // notes[1].push( {note: c[1], time: t} ); + + // notes[2].push( {note: c[2], time: t} ); + // timetest.push(Math.log((t+2) * (t+2))) + } + + // console.log(timetest) + return notes; +} + +function melodyNotes(n, base, multi) { var scaleTypes = tonal.scale.names(); var scaleIdx = Math.floor( Math.random() * scaleTypes.length ); - var s = tonal.scale.get(scaleTypes[scaleIdx], 'C4'); - s.reverse(); + var s = tonal.scale.get('major', 'C4'); + + var ts = tonal.map(tonal.transpose('8P'), s) + s = s.concat(ts); + // s = s.concat(tonal.map(tonal.transpose('8P'), ts)); + // console.log(s) + + // s.reverse(); var chordTypes = tonal.chord.names(); var chordIdx = Math.floor( Math.random() * chordTypes.length ); var notes = [[],[]]; for ( var i = 0; i < n; i++ ) { - var note = MorseThueSingle( 3, 4, i, s ) - notes[0].push( note ); + var note = MorseThueSingle( base, multi, i, s ) + // Transpose note up 3 octaves for the arpeggios. var tNote = note; - tNote = tonal.transpose( tNote, '3M' ); + // tNote = tonal.transpose( tNote, 'P5' ); // tNote = tonal.transpose( tNote, '8P' ); // tNote = tonal.transpose( tNote, '8P' ); // Create a chord // var chord = tonal.chord.get( chordTypes[chordIdx], tNote ); - var chord = createChord(tNote); - // chord = tonal.chord.inversion(0, chord); + var chord = createChord(note); + notes[0].push( tNote ); // Adjust arpeggio pattern if 3 note chord. if ( chord.length == 3 ) { chord.push(chord[1]); } @@ -141,9 +147,10 @@ function createChord(note) { function MorseThueSingle(base, multi, n, scale) { var val = n * multi; - val = parseInt(val.toString(base), 10); + val = baseConversion(val, base); val = sumDigits(val) % scale.length; - return scale[val] + // console.log(n + " val: " + val) + return scale[val]; } // http://stackoverflow.com/questions/9138064/sum-of-the-digits-of-a-number-javascript @@ -156,4 +163,26 @@ function sumDigits(number) { } return sum; +} + +function baseConversion(number, base) { + var base10Num = 0; + var str = number.toString(); + + for (var i = 0; i < str.length; i++) { + base10Num += parseInt(str.charAt(i), 10) * Math.pow(base, str.length - i - 1); + } + + return base10Num; +} + +function getAnalogRhythm(digitalRhythm) { + var analogRhythm = []; + for (var i = 0; i < digitalRhythm.length; i++) { + if (digitalRhythm[i] == 1 && i > 0) { + analogRhythm.push(i-1); + } + } + analogRhythm.push(digitalRhythm.length - 1); + return analogRhythm; } \ No newline at end of file From cd89d3b8d6bd093361ec4fe189d80140c43c94f8 Mon Sep 17 00:00:00 2001 From: xnieamo Date: Tue, 18 Apr 2017 22:37:13 -0400 Subject: [PATCH 09/76] integrate save point --- src/main.js | 55 +++++++++++++++++++------------------ src/utils/musicGenerator.js | 9 ++++-- 2 files changed, 35 insertions(+), 29 deletions(-) diff --git a/src/main.js b/src/main.js index 0089a0d1..0a4aecd6 100644 --- a/src/main.js +++ b/src/main.js @@ -26,9 +26,9 @@ var noiseCloud = { mesh : {}, }; -var testInstrument = Soundfont.instrument(ac, 'acoustic_grand_piano', { soundfont: 'MusyngKite', gain: 1.25, adsr: [0.5, 0.8, 1, 0.7] }); -var instrument2 = Soundfont.instrument(ac, 'acoustic_grand_piano', { soundfont: 'MusyngKite', gain: 1 }); -var synthDrums = Soundfont.instrument(ac, 'synth_drum', { soundfont: 'MusyngKite', gain: 0.5 }); +var testInstrument = Soundfont.instrument(ac, 'acoustic_grand_piano', { soundfont: 'MusyngKite', gain: 1.0 });// +var instrument2 = Soundfont.instrument(ac, 'pad_3_polysynth', { soundfont: 'MusyngKite', adsr: [0,0,0,0], gain: 0.5 }); +var synthDrums = Soundfont.instrument(ac, 'synth_drum', { soundfont: 'MusyngKite', gain: 1.0 }); function rate_limit(func) { var running = false; @@ -66,19 +66,6 @@ function rate_limit(func) { // called after the scene loads function onLoad(framework) { - // console.log(euclid(8,8)); - - // var grammar = {}; - // grammar['0'] = {probability: 1.0, successorString: '01'}; - // grammar['1'] = {probability: 1.0, successorString: '10'}; - // var L = new Lsystem('0',grammar, 2); - // console.log(L.doIterations(3)); - - // var s = tonal.scale.get('major', 'C4'); - // console.log(s) - // console.log(tonal.transpose('C2', 'P8')) - // console.log(tonal.ivl.invert(['C4', 'E4'])) - // console.log(tonal.chord.names()) var scene = framework.scene; var camera = framework.camera; @@ -121,34 +108,50 @@ function onLoad(framework) { })); } +var music = []; +var beats = []; +var time = Date.now(); + // called on frame updates function onUpdate(framework) { if (update) { - var music = melodyGenerator(100, 120, additionalControls.base, additionalControls.multi); - var music2 = EarthWorm(345,17,5,100,120); - var rMusic = rhythmicMelodyGenerator(100, euclid(3,8), 60, additionalControls.base, additionalControls.multi); - var rMusic2 = rhythmicMelodyGenerator(200, euclid(6,8), 240, additionalControls.base + 3, additionalControls.multi + 4); - var beat = beatGenerator(euclid(4,6), 120, 100); + // var music = melodyGenerator(100, 120, 2, 3); + music.push(EarthWorm(343, 13, 4, 140, 240)); + music.push(rhythmicMelodyGenerator(40, euclid(5,8), 60, additionalControls.base, additionalControls.multi)); + // var rMusic2 = rhythmicMelodyGenerator(200, euclid(6,8), 240, additionalControls.base + 3, additionalControls.multi + 4); + beats.push(beatGenerator(euclid(5,7), 180, 100)); + beats.push(beatGenerator(euclid(3,7), 120, 70, 'C4')); + beats.push(beatGenerator(euclid(6,9), 180, 100, 'G2')); - var t = 10; + // console.log(euclid(5,7)) testInstrument.then(function (marimba) { marimba.stop(); - marimba.schedule(ac.currentTime, music[0]); - + marimba.schedule(ac.currentTime, music[1][0]); }) + synthDrums.then(function (marimba) { marimba.stop(); - marimba.schedule(ac.currentTime, beat); + marimba.schedule(ac.currentTime, beats[0]); + marimba.schedule(ac.currentTime, beats[1]); + // marimba.schedule(ac.currentTime, beat3); }) instrument2.then(function (marimba) { marimba.stop(); - marimba.schedule(ac.currentTime, music2); + // console.log(music2) + marimba.schedule(ac.currentTime, music[0]); }) update = false; } + + var nTime = Date.now(); + var indices = [0,0,0,0,0]; + var times = [1/240, 1/60, 1/180, 1/120, 1/180]; + var deltaT = (nTime - time) / 1000; + + } // when the scene is done initializing, it will call onLoad, then on frame updates, call onUpdate diff --git a/src/utils/musicGenerator.js b/src/utils/musicGenerator.js index 4d5df79e..4c662f3c 100644 --- a/src/utils/musicGenerator.js +++ b/src/utils/musicGenerator.js @@ -1,7 +1,7 @@ var tonal = require('tonal') -export function beatGenerator(rhythm, tempo, n) { +export function beatGenerator(rhythm, tempo, n, drumNote = 'G3') { var unitTime = 60 / tempo; var analogRhythm = getAnalogRhythm(rhythm); // console.log(analogRhythm) @@ -11,7 +11,7 @@ export function beatGenerator(rhythm, tempo, n) { for (var i = 0; i < n; i++) { var t = analogRhythm[i % analogRhythm.length] + Math.floor(i / analogRhythm.length) * analogRhythm[analogRhythm.length-1]; - notes.push({note : 'D3', time : t * unitTime}); + notes.push({note : drumNote, time : t * unitTime}); } // console.log(notes) @@ -40,6 +40,7 @@ export function EarthWorm(init, multi, digits, n, tempo) { var ts = tonal.map(tonal.transpose('8P'), s) s = s.concat(ts); + var baseNote = 'E5'; var notes = []; var val = init; @@ -47,7 +48,9 @@ export function EarthWorm(init, multi, digits, n, tempo) { for (var i = 0; i < n; i++) { val *= multi; val = val % restraint; - notes[i] = { note: s[val % s.length], time: i*unitTime }; + // notes[i] = { note: s[val % s.length], time: i*unitTime }; + // console.log(i + " " + val) + notes[i] = { note: (i % 2 == 0) ? baseNote : (tonal.note.midi(baseNote) + val % 10) , time: i*unitTime }; } // console.log(notes) From a389190bb0dddc182411d228f2a95e8131c73fc9 Mon Sep 17 00:00:00 2001 From: Brian Tong Date: Tue, 18 Apr 2017 22:49:11 -0400 Subject: [PATCH 10/76] milestone 1 visuals WIP --- README.md | 113 ++------------------------------------------------ src/main.js | 111 +++++++++++++++++++++++++++++++++++++++---------- src/visual.js | 110 ++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 202 insertions(+), 132 deletions(-) create mode 100644 src/visual.js diff --git a/README.md b/README.md index 71a9296d..c1038dd4 100644 --- a/README.md +++ b/README.md @@ -1,112 +1,5 @@ -# CIS700 Procedural Graphics: Final Project - -Time to show off your new bag of procedural tricks by creating one polished final project. For this assignment you will have four weeks to create and document a portfolio piece that demonstrates your mastery of procedural thinking and implementation. You may work in groups of up to three (working alone is fine too). You may use any language / platform you choose for this assignment (given our approval if it’s not JavaScript/WebGL or C++/OpenGL). - -As usual with this class, we want to encourage you to take this opportunity to explore and experiment. To get you started, however, we’ve provided a few open-ended prompts below. Interesting and complex visuals are the goal in all of these prompts, but they encourage focus on different aspects of proceduralism. - -## Prompts: - -- ### A classic 4k demo - * In the spirit of the demo scene, create an animation that fits into a 4k executable that runs in real-time. Feel free to take inspiration from the many existing demos. Focus on efficiency and elegance in your implementation. - * Examples: [cdak by Quite & orange](https://www.youtube.com/watch?v=RCh3Q08HMfs&list=PLA5E2FF8E143DA58C) - -- ### A forgery - * Taking inspiration from a particular natural phenomenon or distinctive set of visuals, implement a detailed, procedural recreation of that aesthetic. This includes modeling, texturing and object placement within your scene. Does not need to be real-time. Focus on detail and visual accuracy in your implementation. - * Examples: - - [Snail](https://www.shadertoy.com/view/ld3Gz2), [Journey](https://www.shadertoy.com/view/ldlcRf), Big Hero 6 Wormhole: [Image 1](http://2.bp.blogspot.com/-R-6AN2cWjwg/VTyIzIQSQfI/AAAAAAAABLA/GC0yzzz4wHw/s1600/big-hero-6-disneyscreencaps.com-10092.jpg) , [Image 2](https://i.stack.imgur.com/a9RGL.jpg) - -- ### A game level - * Like generations of game makers before us, create a game which generates an navigable environment (eg. a roguelike dungeon, platforms) and some sort of goal or conflict (eg. enemy agents to avoid or items to collect). Must run in real-time. Aim to create an experience that will challenge players and vary noticeably in different playthroughs, whether that means complex dungeon generation, careful resource management or a sophisticated AI model. Focus on designing a system that will generate complex challenges and goals. - * Examples: Spore, Dwarf Fortress, Minecraft, Rogue - -- ### An animated environment / music visualizer - * Create an environment full of interactive procedural animation. The goal of this project is to create an environment that feels responsive and alive. Whether or not animations are musically-driven, sound should be an important component. Focus on user interactions, motion design and experimental interfaces. - * Examples: [Panoramical](https://www.youtube.com/watch?v=gBTTMNFXHTk), [Bound](https://www.youtube.com/watch?v=aE37l6RvF-c) -- ### Own proposal - * You are of course **welcome to propose your own topic**. Regardless of what you choose, you and your team must research your topic and relevant techniques and come up with a detailed plan of execution. You will meet with some subset of the procedural staff before starting implementation for approval. - -**Final grading will be individual** and will be based on both the final product and how well you were able to achieve your intended effect according to your execution plan. Plans change of course, and we don’t expect you to follow your execution plan to a T, but if your final project looks pretty good, but you cut corners and only did 30% of what you outlined in your design doc, you will be penalized. - -But overall, have fun! This is your opportunity to work on whatever procedural project inspires you. The best way to ensure a good result is to pick something you’re passionate about. :) - -## Timeline - -- 4/08 Design doc due / Have met with procedural staff -- 4/18 Milestone 1 (short write-up + demo) -- 4/25 Milestone 2 (short write-up + demo) -- 5/3 Final presentations (3-5 pm, Siglab), final reports due - -## Design Doc - -Your design doc should follow the following template. Note, each section can be pretty short, but cover them all! This will serve as valuable documentation when showing this project off in the future AND doing a good job will make it much easier for you to succeed, so please take this seriously. - -### Design Doc Template: - -- #### Introduction - * What motivates this project? -- #### Goal - * What do you intend to achieve with this project? -- #### Inspiration/reference: - * Attach some materials, visual or otherwise you intend as reference -- #### Specification: - * Outline the main features of your project -- #### Techniques: - * What are the main technical/algorithmic tools you’ll be using? Give an overview, citing specific papers/articles -- #### Design: - * How will your program fit together? Make a simple free-body diagram illustrating the pieces. -- #### Timeline: - * Create a week-by-week set of milestones for each person in your group. - - -Along with your final project demo, you will submit a final report, in which you will update correct your original design doc as needed and add a few post-mortem items. - -## Milestones - -To keep you honest / on-track, we will be checking on your progress at weekly intervals, according to milestones you’ll define at the outset (pending our approval). For each of the two milestones prior to the final submission, you will submit a short write up explaining whether or not you individually achieved your goals (specifying the files where the work happened), along with a link to a demo / images. These don’t have to be super polished -- we just want to see that you’re getting things done. - -Example: - -“Milestone 1: - Adam: -Made some procedural terrain code in src/terrain.js. Implemented 3D simplex noise to do it. Also applied coloring via custom shader based on this cool paper X (see src/shaders/dirt.glsl). IMAGE - -Austin: -I managed to set up my voronoi diagram shader (see src/shaders/voronoi.glsl). -Experimented with different scattering techniques. It’s working with the euclidean distance metric. I’m using it in src/main.js to color stones. IMAGE - -Rachel: -I tried really hard to make my toon shader work (src/shaders/toon.glsl), but I still have a bug! T_T BUGGY IMAGE. DEMO LINK” - -## Final Report - -In addition to your demo, you will create a final report documenting your project overall. This document should be clear enough to explain the value and details of your project to a random computer graphics person with no knowledge of this class. - -### Final Report Template: - -- #### Updated design doc: - * All the sections of your original design doc, corrected if necessary -- #### Results: - * Provide images of your finished project -- #### Evaluation (this is a big one!): - * How well did you do? What parameters did you tune along the way? Include some WIP shots that compare intermediate results to your final. Explain why you made the decisions you did. -- #### Future work: - * Given more time, what would you add/improve -- #### Acknowledgements: - * Cite _EVERYTHING_. Implemented a paper? Used some royalty-free music? Talked to classmates / a professor in a way that influenced your project? Attribute everything! - -## Logistics - -Like every prior project, your code will be submitted via github. Fork the empty final project repo and start your code base from there. Take this as an opportunity to practice using git properly in a team setting if you’re a new user. For each weekly submission, provide a link to your pull request. Your repo will contain all the code and documentation associated with your project. The readme for your repo will eventually be your final report. At the top level, include a folder called “documentation”, where you’ll put your design doc and milestone write-ups. - -Don’t wait to merge your code! Seriously, there be dragons. Try to have a working version including all your code so that compatibility and merge issues don’t sneak up on you near the end. - -## Grading - -- 15% Design Doc (graded as a group) -- 15% Milestone 1 (graded as a group) -- 15% Milestone 2 (graded as a group) -- 55% Final demo + report (graded individually) - -NOTE: We’ve been pretty lax about our late policy throughout the semester, but our margins on the final project are tight, therefore late submissions will NOT be accepted. If you have a significant reason for being unable to complete your goals, talk to us, and we’ll discuss getting you an incomplete and figure out an adjusted work plan with your group. +## Music Visualizer +## Resources +[1] https://github.com/spite/THREE.MeshLine - Three.js lines are a little wonky. This fixes issues with using linewidth. diff --git a/src/main.js b/src/main.js index 0a4aecd6..23886f98 100644 --- a/src/main.js +++ b/src/main.js @@ -1,7 +1,8 @@ - const THREE = require('three'); // older modules are imported like this. You shouldn't have to worry about this much const tonal = require('tonal') +const MeshLine = require( 'three.meshline' ); +// Audio import Framework from './framework' import MIDI from 'midi.js' import Soundfont from 'soundfont-player' @@ -9,6 +10,61 @@ import {euclid} from './utils/euclid.js' import {beatGenerator, MorseThue, melodyGenerator, rhythmicMelodyGenerator, EarthWorm} from './utils/musicGenerator.js' import Lsystem from './fractals/lsystem.js' +// Visual +import Visual from './visual' + +/******************************************************************************/ + + var clock = new THREE.Clock(); + var visualConfig = { + startTime: 0, + prevTime: 0, + camera: { + pos: new THREE.Vector3( 80,0,0 ), + vel: new THREE.Vector3( 0,0,0 ), + acc: new THREE.Vector3( 0,0,0 ), + look: new THREE.Vector3( 0,0,0 ), + }, + sceneReady: false, + }; + + function initInputHandler(framework) { + document.onkeydown = function(e) { + switch (e.keyCode) { + case 83: + //alert('s'); + earthVel = -10; + break; + case 68: + var stars = framework.scene.getObjectByName("small_star_cloud"); + if (stars !== undefined) { + stars.visible = !stars.visible; + } + break; + case 70: + var stars = framework.scene.getObjectByName("large_star_cloud"); + if (stars !== undefined) { + stars.visible = !stars.visible; + } + break; + case 74: + alert('j'); + break; + case 75: + alert('k'); + break; + case 76: + alert('l'); + break; + case 32: + visualConfig.camera.acc = new THREE.Vector3( -5,0,0 ); + break; + } + } + } + + +/******************************************************************************/ var ac = new AudioContext() @@ -37,7 +93,7 @@ function rate_limit(func) { function onDone() { running = false; // set the flag to allow the function to be called again if (typeof next !== 'undefined') { - callFunc(next); // call the function again with the queued args + callFunc(next); // call the function again with the queued args } } @@ -73,24 +129,6 @@ function onLoad(framework) { var gui = framework.gui; var stats = framework.stats; - // LOOK: the line below is synyatic sugar for the code above. Optional, but I sort of recommend it. - // var {scene, camera, renderer, gui, stats} = framework; - var adamMaterial = new THREE.MeshBasicMaterial({ - color:0xFFFFFF, - side:THREE.DoubleSide - }); - - var iso = new THREE.IcosahedronBufferGeometry(0.7, 6); - noiseCloud.mesh = new THREE.Mesh(iso, adamMaterial); - noiseCloud.mesh.name = "adamCube"; - - // set camera position - camera.position.set(1, 1, 6); - camera.lookAt(new THREE.Vector3(0,0,0)); - - scene.add(noiseCloud.mesh); - - // edit params and listen to changes like this // more information here: https://workshop.chromeexperiments.com/examples/gui/#1--Basic-Usage gui.add(camera, 'fov', 0, 180).onChange(function(newVal) { @@ -106,6 +144,18 @@ function onLoad(framework) { additionalControls['multi'] = Math.round(newVal); update = true; })); + + // Visual + camera.position.set(50, 0, 0); + camera.lookAt(new THREE.Vector3(0,0,0)); + scene.background = new THREE.Color( 0xffffff ); + + Visual.initScene(scene, visualConfig); + + initInputHandler(framework); + + visualConfig.startTime = clock.getElapsedTime(); + visualConfig.prevTime = clock.getElapsedTime(); } var music = []; @@ -150,9 +200,26 @@ function onUpdate(framework) { var indices = [0,0,0,0,0]; var times = [1/240, 1/60, 1/180, 1/120, 1/180]; var deltaT = (nTime - time) / 1000; - + // Visual + var camera = framework.camera; + var scene = framework.scene; + + var delta = visualConfig.prevTime - clock.getElapsedTime(); + visualConfig.prevTime = clock.getElapsedTime(); + + + Visual.updateScene(scene, visualConfig, delta); + + visualConfig.camera.vel = visualConfig.camera.vel.clone().add(visualConfig.camera.acc.clone().multiplyScalar(delta)); + visualConfig.camera.pos = visualConfig.camera.pos.clone().add(visualConfig.camera.vel.clone().multiplyScalar(delta)); + if (Visual.changeTrigger(visualConfig)) { + visualConfig.camera.vel = new THREE.Vector3( 0,0,0 ); + visualConfig.camera.pos = new THREE.Vector3( 0,0,0 ); + Visual.cleanupScene(scene); + } + camera.position.set( visualConfig.camera.pos.x, visualConfig.camera.pos.y, visualConfig.camera.pos.z ); } // when the scene is done initializing, it will call onLoad, then on frame updates, call onUpdate -Framework.init(onLoad, onUpdate); \ No newline at end of file +Framework.init(onLoad, onUpdate); diff --git a/src/visual.js b/src/visual.js new file mode 100644 index 00000000..209c2e47 --- /dev/null +++ b/src/visual.js @@ -0,0 +1,110 @@ + +const THREE = require('three'); // older modules are imported like this. You shouldn't have to worry about this much +const MeshLine = require( 'three.meshline' ); + + + +// Each scene uses the following API: +// initScene() +// updateScene() +// changeTrigger() + + + +/*********************************** VISUAL ***********************************/ +function initScene(scene, visualConfig) { + var geometry = new THREE.IcosahedronGeometry(5,4); + var material = new THREE.MeshBasicMaterial({ color: 0, side: THREE.DoubleSide }); + var mesh = new THREE.Mesh(geometry, material); + scene.add(mesh); + + visualConfig.sceneProps = { particles: [] }; + visualConfig.sceneProps.particles.push(genParticle(scene)); + visualConfig.sceneReady = true; +} + +function updateScene(scene, visualConfig, delta) { + if (visualConfig.sceneReady) { + for (var i = 0; i < visualConfig.sceneProps.particles.length; i++) { + visualConfig.sceneProps.particles[i].update(0.01); + visualConfig.sceneProps.particles[i].meshLine.advance(visualConfig.sceneProps.particles[i].pos); + } + + if (Math.random() < 0.01) { + visualConfig.sceneProps.particles.push(genParticle(scene)); + } + } +} + +function genParticle(scene) { + var meshLine = new MeshLine.MeshLine(); + var meshLineGeo = new THREE.Geometry(); + for (var i = 0; i < 5000; i++) { + meshLineGeo.vertices.push(new THREE.Vector3( 0,0,0 )); + } + meshLine.setGeometry( meshLineGeo, function( p ) { return p; } ); + + + var meshLineMat = new MeshLine.MeshLineMaterial( { + color: new THREE.Color( "rgb( 0,0,0 )" ), + opacity: 1, + resolution: new THREE.Vector2( window.innerWidth, window.innerHeight ), + sizeAttenuation: 1, + lineWidth: 0.5, + near: 1, + far: 100000, + depthTest: false, + blending: THREE.AdditiveBlending, + transparent: false, + side: THREE.DoubleSide + }); + var meshLineMesh = new THREE.Mesh( meshLine.geometry, meshLineMat ); // this syntax could definitely be improved! + meshLineMesh.frustumCulled = false; + + scene.add(meshLineMesh); + + return { + meshLine: meshLine, + pos: new THREE.Vector3( 0,0,0 ), + vel: new THREE.Vector3( (Math.random()-0.5) * 20,(Math.random()-0.5) * 20,(Math.random()-0.5) * 20 ), + acc: new THREE.Vector3( (Math.random()-0.5) * 20,(Math.random()-0.5) * 20,(Math.random()-0.5) * 20 ), + lastChange: 0, + update(delta) { + + if (this.lastChange > 1) { + this.lastChange = 0; + this.acc = new THREE.Vector3( (Math.random()-0.5) * 20,(Math.random()-0.5) * 20,(Math.random()-0.5) * 20 ); + } else { + this.lastChange += delta; + } + + this.vel = this.vel.clone().add(this.acc.clone().multiplyScalar(delta)); + this.pos = this.pos.clone().add(this.vel.clone().multiplyScalar(delta)); + + } + }; +} + +function changeTrigger(visualConfig) { + return visualConfig.camera.pos.x < 1; +} + +function cleanupScene(scene) { + scene.background = new THREE.Color( 0xff0000 ); + while(scene.children.length > 0){ + scene.remove(scene.children[0]); + } +} + +/*********************************** EXPORT ***********************************/ + +export default { + initScene: initScene, + updateScene: updateScene, + changeTrigger: changeTrigger, + cleanupScene: cleanupScene +} + +export function other() { + return 2 +} From cb5446edc14f1c911f6a9bf1c01fd27dc73b2ef1 Mon Sep 17 00:00:00 2001 From: xnieamo Date: Tue, 18 Apr 2017 22:51:08 -0400 Subject: [PATCH 11/76] single note play removes lag --- src/main.js | 37 ++++++++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 7 deletions(-) diff --git a/src/main.js b/src/main.js index 0a4aecd6..7412eda7 100644 --- a/src/main.js +++ b/src/main.js @@ -111,6 +111,7 @@ function onLoad(framework) { var music = []; var beats = []; var time = Date.now(); +var indices = [0,0,0,0,0]; // called on frame updates function onUpdate(framework) { @@ -118,6 +119,7 @@ function onUpdate(framework) { // var music = melodyGenerator(100, 120, 2, 3); music.push(EarthWorm(343, 13, 4, 140, 240)); music.push(rhythmicMelodyGenerator(40, euclid(5,8), 60, additionalControls.base, additionalControls.multi)); + // var rMusic2 = rhythmicMelodyGenerator(200, euclid(6,8), 240, additionalControls.base + 3, additionalControls.multi + 4); beats.push(beatGenerator(euclid(5,7), 180, 100)); beats.push(beatGenerator(euclid(3,7), 120, 70, 'C4')); @@ -127,30 +129,51 @@ function onUpdate(framework) { testInstrument.then(function (marimba) { marimba.stop(); - marimba.schedule(ac.currentTime, music[1][0]); + // marimba.schedule(ac.currentTime, music[1][0]); }) synthDrums.then(function (marimba) { marimba.stop(); - marimba.schedule(ac.currentTime, beats[0]); - marimba.schedule(ac.currentTime, beats[1]); + // marimba.schedule(ac.currentTime, beats[0]); + // marimba.schedule(ac.currentTime, beats[1]); // marimba.schedule(ac.currentTime, beat3); }) instrument2.then(function (marimba) { marimba.stop(); // console.log(music2) - marimba.schedule(ac.currentTime, music[0]); + // marimba.schedule(ac.currentTime, music[0]); }) update = false; } var nTime = Date.now(); - var indices = [0,0,0,0,0]; - var times = [1/240, 1/60, 1/180, 1/120, 1/180]; + + var times = [1, 1/60, 1/180, 1/120, 1/180]; var deltaT = (nTime - time) / 1000; - + + // console.log(indices[0]) + if (deltaT > times[0] && indices[0] < music[0].length) { + + testInstrument.then(function(piano){ + piano.play(music[1][0][indices[0]].note); + }) + + instrument2.then(function(synth) { + synth.play(music[0][indices[1]].note); + }) + + synthDrums.then(function (drums) { + drums.stop(); + drums.play(beats[0][indices[2]].note); + }) + + indices[0]++; + indices[1]++; + indices[2]++; + time = nTime; + } } From 710af64ed5e1f2bc162682d37b86abf328144e4a Mon Sep 17 00:00:00 2001 From: Brian Tong Date: Tue, 18 Apr 2017 22:54:39 -0400 Subject: [PATCH 12/76] update package.json --- README.md | 4 ++++ package.json | 7 ++++--- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c1038dd4..bb60be21 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,9 @@ ## Music Visualizer +# Procedural Music (Xiaomao) + +# Visualizer (Brian) + ## Resources [1] https://github.com/spite/THREE.MeshLine - Three.js lines are a little wonky. This fixes issues with using linewidth. diff --git a/package.json b/package.json index 27d1d6ad..84e8e209 100644 --- a/package.json +++ b/package.json @@ -13,12 +13,13 @@ "dependencies": { "dat-gui": "^0.5.0", "gl-matrix": "^2.3.2", + "midi.js": "", + "soundfont-player": "^0.10.5", "stats-js": "^1.0.0-alpha1", "three": "^0.82.1", "three-orbit-controls": "^82.1.0", - "tonal": "0.69.0", - "midi.js": "", - "soundfont-player": "^0.10.5" + "three.meshline": "^1.0.3", + "tonal": "0.69.0" }, "devDependencies": { "babel-core": "^6.18.2", From 18a60668ebac50e84d488827323ffb756e21d7dd Mon Sep 17 00:00:00 2001 From: Xiaomao Ding Date: Tue, 18 Apr 2017 23:15:59 -0400 Subject: [PATCH 13/76] Update README.md --- README.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/README.md b/README.md index bb60be21..4cf490d9 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,20 @@ ## Music Visualizer # Procedural Music (Xiaomao) +The two main implementations done this week were rhythm generation and melody generation. The rhythms are generated using the Bjorklund algorithm which attempts to evenly spread out notes, creating Euclidian rhythms. That is, given a number of notes, and a number of time intervals, this algorithm attempts to evenly distribute the notes among the time intervals (assuming #notes < #intervals) [2]. The main purpose of this algorithm is to create interesting beats for the music. This can be heard in the underlying drum beats in the default scene. + +The procedural melodies are generated using modifications of the Morse-Thue Fractal and the Earthworm sequence. The methods are taken from Gustavo Diaz-Jerez's blog where he details how these procedural number patterns were used in his procedural music application [3]. In short, the Morse-Thue algorithm requires a base and a multiplier. A counter is started and multiplied by the multiplier. This number is converted from the chosen base to base 10. Then, the sum of the digits of the result modulus the number of notes in the scale is used to determine a note to play. + +The Earthworm sequence requires an initial value, a multiplier, and a constant. At every iteration, the value is the previous value times the multiplier. The constant determines how many digits to keep. If the constant is 3, the only the first three rightmost digits of the number is kept. The same modulus operation as above is used to determine the note. + +Both of these algorithms are implemented in this version of the code. `melodyGenerator` by uses the Morse-Thue while `EarthWorm` uses the Earthworm sequence to generate a set of notes. `rhythmicMelodyGenerator` combines the output of a Morse-Thue sequence with a Euclidian rhythm. # Visualizer (Brian) ## Resources [1] https://github.com/spite/THREE.MeshLine - Three.js lines are a little wonky. This fixes issues with using linewidth. + +[2] http://cgm.cs.mcgill.ca/~godfried/publications/banff.pdf + +[3] http://www.gustavodiazjerez.com/gdj/?cat=15 From cdbe33f942049c02434df38cf7a5c6c8cb31040a Mon Sep 17 00:00:00 2001 From: Xiaomao Ding Date: Tue, 18 Apr 2017 23:22:45 -0400 Subject: [PATCH 14/76] Update README.md --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 4cf490d9..9b81de85 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,10 @@ The Earthworm sequence requires an initial value, a multiplier, and a constant. Both of these algorithms are implemented in this version of the code. `melodyGenerator` by uses the Morse-Thue while `EarthWorm` uses the Earthworm sequence to generate a set of notes. `rhythmicMelodyGenerator` combines the output of a Morse-Thue sequence with a Euclidian rhythm. +The Soundfont-player and Tonal.js libaries are used to generate scales and play the MIDI notes [4][5]. The main music play functions are located in the `onUpdate` function in `main.js`. I recently discovered a bug where preloading multiple channels/tracks of audio causes static noise build up in the overall track. Thus, if this occurs, disable a few `.schedule` calls, which may help alleviate the problem. The current code utilizes 3 instruments: an acoustic piano, a polysynth, and a synthetic drumset. The drum lines demonstrate how the Euclidian rhythms can be combined to create beats, while the other two instruments demonstrate the melody generation algorithms. + +One noticeable issue is that sometimes the melody generation sometimes is too random. One new goal for the next week is to "decrease" the randomness by artificially introducing constant structures and musical motifs. One example present in the code currently is that the `EarthWorm` function takes the generated EarthWorm sequence and injects a constant note at every other interval, which makes the resulting sequence sound more "musical". + # Visualizer (Brian) @@ -18,3 +22,7 @@ Both of these algorithms are implemented in this version of the code. `melodyGen [2] http://cgm.cs.mcgill.ca/~godfried/publications/banff.pdf [3] http://www.gustavodiazjerez.com/gdj/?cat=15 + +[4] https://github.com/danigb/soundfont-player + +[5] https://github.com/danigb/tonal From a79dc9fbba5c5123b3d7c5fb8067a7a61cd28ed3 Mon Sep 17 00:00:00 2001 From: Brian Tong Date: Tue, 18 Apr 2017 23:23:31 -0400 Subject: [PATCH 15/76] milestone 1 visual readme --- README.md | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index bb60be21..35ba64a0 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,30 @@ ## Music Visualizer -# Procedural Music (Xiaomao) +### Procedural Music (Xiaomao) -# Visualizer (Brian) +### Visualizer (Brian) +Milestone 1: + +* Handles basic user inputs +* Framework code for moving camera +* Framework code for triggering scene changes +* Implemented 2 scenes: + +![scene 1](http://i.imgur.com/2ZfjrG1.png) + +![scene 2](http://i.imgur.com/vTInVNX.png) + + +Milestone 2: + +* User input to trigger musical and visual effect +* Implement 2 additional scenes +* Implement post effects to change tone/mood of scenes +* Work on transitions between scenes +* Integrate with the music to match the tone/mood + +### Demo ## Resources [1] https://github.com/spite/THREE.MeshLine - Three.js lines are a little wonky. This fixes issues with using linewidth. From 62c1360fee80c0f60259c18ab7e359a326624b60 Mon Sep 17 00:00:00 2001 From: Xiaomao Ding Date: Tue, 18 Apr 2017 23:23:36 -0400 Subject: [PATCH 16/76] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9b81de85..2eda0d94 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ The Earthworm sequence requires an initial value, a multiplier, and a constant. Both of these algorithms are implemented in this version of the code. `melodyGenerator` by uses the Morse-Thue while `EarthWorm` uses the Earthworm sequence to generate a set of notes. `rhythmicMelodyGenerator` combines the output of a Morse-Thue sequence with a Euclidian rhythm. -The Soundfont-player and Tonal.js libaries are used to generate scales and play the MIDI notes [4][5]. The main music play functions are located in the `onUpdate` function in `main.js`. I recently discovered a bug where preloading multiple channels/tracks of audio causes static noise build up in the overall track. Thus, if this occurs, disable a few `.schedule` calls, which may help alleviate the problem. The current code utilizes 3 instruments: an acoustic piano, a polysynth, and a synthetic drumset. The drum lines demonstrate how the Euclidian rhythms can be combined to create beats, while the other two instruments demonstrate the melody generation algorithms. +The Soundfont-player and Tonal.js libaries are used to generate scales and play the MIDI notes [4][5]. The main music play functions are located in the `onUpdate` function in `main.js`. I recently discovered a bug where preloading multiple channels/tracks of audio causes static noise build up in the overall track. Thus, if this occurs, disable a few `.schedule` calls, which may help alleviate the problem. In the next version, I will manually time the notes which alleviates this problem. The current code utilizes 3 instruments: an acoustic piano, a polysynth, and a synthetic drumset. The drum lines demonstrate how the Euclidian rhythms can be combined to create beats, while the other two instruments demonstrate the melody generation algorithms. One noticeable issue is that sometimes the melody generation sometimes is too random. One new goal for the next week is to "decrease" the randomness by artificially introducing constant structures and musical motifs. One example present in the code currently is that the `EarthWorm` function takes the generated EarthWorm sequence and injects a constant note at every other interval, which makes the resulting sequence sound more "musical". From 0a4afdcbdbc0730b4e63cb8ef7aef7f63dd3c6b7 Mon Sep 17 00:00:00 2001 From: xnieamo Date: Tue, 18 Apr 2017 23:23:52 -0400 Subject: [PATCH 17/76] revert to proper tempo music --- src/main.js | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/src/main.js b/src/main.js index 3cb7cd36..34d6d3b5 100644 --- a/src/main.js +++ b/src/main.js @@ -184,15 +184,15 @@ function onUpdate(framework) { synthDrums.then(function (marimba) { marimba.stop(); - // marimba.schedule(ac.currentTime, beats[0]); - // marimba.schedule(ac.currentTime, beats[1]); + marimba.schedule(ac.currentTime, beats[0]); + marimba.schedule(ac.currentTime, beats[1]); // marimba.schedule(ac.currentTime, beat3); }) instrument2.then(function (marimba) { marimba.stop(); // console.log(music2) - // marimba.schedule(ac.currentTime, music[0]); + marimba.schedule(ac.currentTime, music[0]); }) update = false; @@ -203,27 +203,27 @@ function onUpdate(framework) { var times = [1, 1/60, 1/180, 1/120, 1/180]; var deltaT = (nTime - time) / 1000; - // console.log(indices[0]) - if (deltaT > times[0] && indices[0] < music[0].length) { + // // console.log(indices[0]) + // if (deltaT > times[0] && indices[0] < music[0].length) { - testInstrument.then(function(piano){ - piano.play(music[1][0][indices[0]].note); - }) + // testInstrument.then(function(piano){ + // piano.play(music[1][0][indices[0]].note); + // }) - instrument2.then(function(synth) { - synth.play(music[0][indices[1]].note); - }) + // instrument2.then(function(synth) { + // synth.play(music[0][indices[1]].note); + // }) - synthDrums.then(function (drums) { - drums.stop(); - drums.play(beats[0][indices[2]].note); - }) + // synthDrums.then(function (drums) { + // drums.stop(); + // drums.play(beats[0][indices[2]].note); + // }) - indices[0]++; - indices[1]++; - indices[2]++; - time = nTime; - } + // indices[0]++; + // indices[1]++; + // indices[2]++; + // time = nTime; + // } // Visual var camera = framework.camera; From c4948bd8e0156bb18a958cc9d6cfaff63f6c8e89 Mon Sep 17 00:00:00 2001 From: xnieamo Date: Tue, 18 Apr 2017 23:41:16 -0400 Subject: [PATCH 18/76] change page title --- index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.html b/index.html index f775186a..3ee251a2 100644 --- a/index.html +++ b/index.html @@ -1,7 +1,7 @@ - HW1: Noise + Final Project