xxxxxxxxxx
292
/*
* Main Sketch
* Jack B. Du (github@jackbdu.com)
* https://instagram.com/jackbdu/
*/
// scheduling timing does not seem accurate
const sketch = ( p ) => {
p.specs = {
fps: 60,
numLoopFrames: 120,
backgroundColor: 0,
strokeColor: 255,
outputWidth: 'auto',
outputHeight: 'auto',
}
p.options = {
sound: {
midiScale: [60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84],
noteIndices: [0, 2, 4, 5, 7, 9, 11, 12, 14, 16, 17, 19, 21, 23, 24], // major
// noteIndices: [0, 2, 3, 5, 7, 8, 10, 12], // minor
shortestDurationNthPower: 4,
interval: '32n',
maxPolyphony: 32,
meter: {
smoothing: 0.9,
normalRange: true,
channels: 1,
},
},
node: {
diameter: 0.05,
},
}
p.soundManager = {
polySynth: new Tone.PolySynth({
maxPolyphony: p.options.sound.maxPolyphony,
}),
meter: new Tone.Meter(p.options.sound.meter),
notes: [],
activeMelody: undefined,
activeNoteStartTime: -1,
setup: function(options) {
this.polySynth.toDestination();
this.polySynth.connect(this.meter);
this.notes = this.getNotes(options.midiScale, options.noteIndices);
Tone.Transport.scheduleRepeat((time) => {
// console.log(Tone.Time("4n"));
if (this.activeMelody) {
const duration = this.activeMelody.getDuration();
if (time - this.activeNoteStartTime >= Tone.Time(duration).toSeconds()) {
if (this.activeMelody.prev) {
this.activeMelody.prev.meter = undefined;
}
this.activeMelody.meter = this.meter;
// const distToNext = this.activeMelody.distToNext();
// const mappedDist = p.floor(p.map(distToNext,0,300,6,0));
// const nthPower = p.constrain(mappedDist, 0, 6);
// const duration = Math.pow(2,nthPower)+'n';
// console.log(duration);
this.activeNoteStartTime = time;
// console.log(time, duration);
this.playNotes(this.activeMelody.note, duration, time);
if (this.activeMelody.next) {
this.activeMelody = this.activeMelody.next;
} else {
// this.activeMelody.meter = undefined;
this.activeMelody = undefined;
Tone.Transport.stop(time);
}
}
}
}, options.interval);
},
// playNode: function(node, startTime) {
// // console.log(startTime);
// Tone.Transport.scheduleOnce((time) => {
// if (node.prev) {
// node.prev.meter = undefined;
// }
// node.meter = this.meter;
// const duration = node.getDuration();
// console.log(time, Tone.Time(duration).toSeconds());
// this.playNotes(node.note, duration, time);
// if (node.next) {
// this.playNode(node.next, time+duration);
// } else {
// Tone.Transport.stop(duration);
// }
// }, startTime);
// },
getNotes: function(scale, indices) {
const notes = [];
indices.forEach((index) => {
const note = Tone.Frequency(scale[index], 'midi');
notes.push(note);
});
return notes;
},
getNoteAt: function(percentage) {
const constrainedPercentage = p.constrain(percentage, 0, 1);
const index = Math.floor(constrainedPercentage * (this.notes.length-1));
return this.notes[index];
},
getRandomNote: function() {
const randomIndex = Math.floor(Math.random()*this.notes.length);
return this.notes[randomIndex];
},
playNthNote: function(n, duration) {
const index = n % this.notes.length;
const note = this.notes[index];
this.polySynth.triggerAttackRelease(note, duration);
},
playNotes: function(notes, duration, time) {
this.polySynth.triggerAttackRelease(notes, duration, time);
}
}
p.nodeManager = {
melodies: [],
activeNode: undefined,
diameter: p.options.node.diameter,
shortestDurationNthPower: p.options.sound.shortestDurationNthPower,
refSize: undefined,
updateRefSize: function(refSize) {
this.refSize = refSize;
},
update(mouseX, mouseY, millis) {
if (this.activeNode &&
this.nodeIsHovered(this.activeNode, mouseX, mouseY)) {
this.activeNode.endMillis = millis;
}
},
createNode: function(x, y, note, durationNthPower, millis, prev = undefined) {
return {
x,
y,
note,
prev,
durationNthPower,
startMillis: millis,
endMillis: millis,
getDuration: function() {
const durationMillis = this.endMillis - this.startMillis;
const durationNthPower = p.constrain(p.floor(p.map(durationMillis, 0, 500, this.durationNthPower, 0)), 2, this.durationNthPower);
// console.log(this.durationNthPower);
return Math.pow(2, durationNthPower)+'n';
},
// increaseDuration: function() {
// if (this.durationNthPower > 0) this.durationNthPower--;
// },
d: this.diameter,
meter: undefined,
distTo: function(x, y) {
return Math.sqrt(Math.pow(this.x-x, 2)+Math.pow(this.y-y, 2));
},
distToNext: function() {
if (this.next) return this.distTo(this.next.x, this.next.y);
return 0;
},
}
},
addMelody: function(x, y, note, millis) {
this.activeNode = this.createNode(x, y, note, this.shortestDurationNthPower, millis);
this.melodies.push(this.activeNode);
},
// getDistBetween: function(node1, node2) {
// return Math.sqrt(Math.pow(node1.x-node2.x, 2)+Math.pow(node1.y-node2.y, 2));
// },
mousePressed: function(mx, my, note, millis) {
this.melodies = [];
this.addMelody(mx, my, note, millis)
},
mouseDragged: function(mx, my, note, millis) {
if (!this.nodeIsHovered(this.activeNode, mx, my)) this.addNode(mx, my, note, millis);
},
nodeIsHovered: function(node, mx, my) {
return node.distTo(mx, my) <= node.d/2 * this.refSize;
},
addNode: function(mx, my, note, millis) {
this.activeNode.next = this.createNode(mx, my, note, this.shortestDurationNthPower, millis, this.activeNode);
this.activeNode = this.activeNode.next;
},
drawNodes: function() {
for (let melody of this.melodies) {
this.drawNode(melody);
}
},
drawNode: function(node) {
const meterValue = node.meter ? node.meter.getValue() : 0;
const diameter = this.refSize * (node.d + node.d * meterValue);
// console.log(size);
p.ellipse(node.x, node.y, diameter);
if (node.next) {
p.line(node.x, node.y, node.next.x, node.next.y);
this.drawNode(node.next, this.refSize);
}
},
// playMelodies: function(playNotes) {
// for (let melody of this.melodies) {
// this.playMelody(melody, playNotes);
// }
// },
// playMelody: function(node, playNotes) {
// playNotes(node.note, '16n');
// // if (node.next) {
// // this.playMelody(node.next, playNotes);
// // }
// },
}
p.setup = () => {
p.updateCanvas(p.specs.outputWidth, p.specs.outputHeight);
p.frameRate(p.specs.fps);
p.smooth();
p.soundManager.setup(p.options.sound);
};
p.draw = () => {
const canvasShort = p.min(p.width, p.height);
if (p.beforeDraw) p.beforeDraw();
p.background(p.specs.backgroundColor);
p.stroke(p.specs.strokeColor);
p.fill(p.specs.backgroundColor);
const [mouseX, mouseY] = p.getMousePosition(p.mouseX, p.mouseY);
p.nodeManager.update(mouseX, mouseY, p.millis());
p.nodeManager.drawNodes(canvasShort);
if (p.afterDraw) p.afterDraw();
};
p.mousePressed = (event) => {
const [mouseX, mouseY] = p.getMousePosition(event.x, event.y);
const percentageY = 1-event.y/p.height;
p.nodeManager.mousePressed(mouseX, mouseY, p.soundManager.getNoteAt(percentageY), p.millis());
}
p.mouseDragged = (event) => {
const [mouseX, mouseY] = p.getMousePosition(event.x, event.y);
const percentageY = 1-event.y/p.height;
p.nodeManager.mouseDragged(mouseX, mouseY, p.soundManager.getNoteAt(percentageY), p.millis());
}
p.mouseReleased = (event) => {
p.soundManager.activeMelody = p.nodeManager.melodies[p.nodeManager.melodies.length-1];
Tone.Transport.start();
// p.soundManager.playNode(p.soundManager.activeMelody, 0);
}
p.getMousePosition = (mx, my) => {
const mouseX = p.map(mx, 0, p.width, -p.width/2, p.width/2)
const mouseY = p.map(my, 0, p.height, -p.height/2, p.height/2)
return [mouseX, mouseY];
}
// p.keyPressed = () => {
// p.soundManager.playNthNote(p.key, '16n');
// console.log(p.soundManager.polySynth);
// }
p.beforeDraw = () => {
if (p.beginCapture) p.beginCapture();
}
p.afterDraw = () => {
if (p.endCapture) p.endCapture();
}
p.windowResized = () => {
p.updateCanvas(p.specs.outputWidth, p.specs.outputHeight);
}
p.updateCanvas = (outputWidth = 'auto', outputHeight = 'auto') => {
const pd = p.pixelDensity();
const canvasWidth = outputWidth && outputWidth !== 'auto' ? outputWidth/pd : p.windowWidth;
const canvasHeight = outputHeight && outputHeight !== 'auto' ? outputHeight/pd : p.windowHeight;
if (canvasWidth !== p.width || canvasHeight !== p.height) {
if (!p.hasCanvas) {
p.createCanvas(canvasWidth, canvasHeight, p.WEBGL);
p.hasCanvas = true;
} else {
p.resizeCanvas(canvasWidth, canvasHeight);
}
}
const canvasShort = p.min(canvasWidth, canvasHeight);
p.nodeManager.updateRefSize(canvasShort);
}
};
let p5sketch = new p5(sketch);