Skip to content

Commit

Permalink
Refactor for multi-instrument support
Browse files Browse the repository at this point in the history
  • Loading branch information
MaxLaumeister committed Dec 27, 2019
1 parent 8e7155b commit e7342bd
Show file tree
Hide file tree
Showing 3 changed files with 59 additions and 40 deletions.
39 changes: 35 additions & 4 deletions src/Grid.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/* global GridRenderer */
/* global NotePlayer */
/* global SynthInstrument */
/* global Util */
/** A 2-D matrix that keeps track of notes and can enable, disable, and play them */
class Grid { // eslint-disable-line no-unused-vars
Expand All @@ -15,7 +15,38 @@ class Grid { // eslint-disable-line no-unused-vars
this.width = width;
this.height = height;
this.renderer = new GridRenderer(width, height, canvas);
this.player = new NotePlayer(width, height);
this.currentInstrument = 0;
this.instruments = [];
this.instruments.push(new SynthInstrument(width, height, {
oscillator: {
type: 'sine',
},
envelope: {
attack: 0.005,
decay: 0.1,
sustain: 0.3,
release: 1,
},
},
{
frequency: 1100,
rolloff: -12,
}));
this.instruments.push(new SynthInstrument(width, height, {
oscillator: {
type: 'sawtooth',
},
envelope: {
attack: 0.005,
decay: 0.1,
sustain: 0.3,
release: 2,
},
},
{
frequency: 1100,
rolloff: -12,
}));
}

/**
Expand Down Expand Up @@ -51,11 +82,11 @@ class Grid { // eslint-disable-line no-unused-vars
if (this.getTileValue(x, y)) return;
// Turning on, schedule note

this.data[Util.coordToIndex(x, y, this.height)] = this.player.scheduleNote(x, y);
this.data[Util.coordToIndex(x, y, this.height)] = this.instruments[this.currentInstrument].scheduleNote(x, y);
} else {
if (!this.getTileValue(x, y)) return;
// Turning off, unschedule note
this.player.unscheduleNote(this.data[Util.coordToIndex(x, y, this.height)]);
this.instruments[this.currentInstrument].unscheduleNote(this.data[Util.coordToIndex(x, y, this.height)]);
this.data[Util.coordToIndex(x, y, this.height)] = false;
}
}
Expand Down
2 changes: 1 addition & 1 deletion src/GridRenderer.js
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ class GridRenderer { // eslint-disable-line no-unused-vars
draw(grid, mouseX, mouseY) {
Util.assert(arguments.length === 3);

const playheadX = grid.player.getPlayheadX();
const playheadX = grid.instruments[grid.currentInstrument].getPlayheadX();
const dpr = Util.getDevicePixelRatio();

// Defaults
Expand Down
58 changes: 23 additions & 35 deletions src/NotePlayer.js → src/SynthInstrument.js
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
/* global Tone */
/* global Util */
/** Manages instruments and allows the playback of notes */
class NotePlayer { // eslint-disable-line no-unused-vars
/** Allows the audio playback of notes */
class SynthInstrument { // eslint-disable-line no-unused-vars
/**
* Creates a NotePlayer. One player is sufficient for any number of instruments and notes.
* Creates a synth instrument
* @param {number} gridWidth - The width of the grid, in tiles
* @param {number} gridHeight - The height of the grid, in tiles
*/
constructor(gridWidth, gridHeight) {
Util.assert(arguments.length === 2);
constructor(gridWidth, gridHeight, options, filterOptions) {
Util.assert(arguments.length === 4);

this.gridWidth = gridWidth;
this.gridHeight = gridHeight;
Expand All @@ -18,12 +18,12 @@ class NotePlayer { // eslint-disable-line no-unused-vars
const pentatonic = ['B#', 'D', 'F', 'G', 'A'];
const octave = 3; // base octave
const octaveoffset = 4;
let scale = Array(gridHeight);
const scale = Array(gridHeight);
for (let i = 0; i < gridHeight; i += 1) {
scale[i] = pentatonic[i % pentatonic.length]
+ (octave + Math.floor((i + octaveoffset) / pentatonic.length));
}
scale = scale.reverse(); // higher notes at lower y values, near the top
this.scale = scale.reverse(); // higher notes at lower y values, near the top

// Pre-render synth

Expand All @@ -34,40 +34,28 @@ class NotePlayer { // eslint-disable-line no-unused-vars

this.currentPlayer = 0;

const self = this;
// Init polyphony tracker. More notes playing at the same time
// means that each note needs to play quieter

Tone.Offline(() => {
const lowPass = new Tone.Filter({
frequency: 1100,
rolloff: -12,
}).toDestination();
this.polyphony = Array(gridWidth).fill(0);
this.notes = []; // Sparse array

const synth = new Tone.Synth({
oscillator: {
type: 'sine',
},
envelope: {
attack: 0.005,
decay: 0.1,
sustain: 0.3,
release: 1,
},
}).connect(lowPass);
const self = this;
Tone.Offline(() => {
const filter = new Tone.Filter(filterOptions).toDestination();
const synth = new Tone.Synth(options).connect(filter);

scale.forEach((el, idx) => {
synth.triggerAttackRelease(el, Tone.Time('1m') / gridWidth, idx * self.noteOffset);
this.scale.forEach((el, idx) => {
synth.triggerAttackRelease(el, Tone.Time('1m') / this.gridWidth, idx * self.noteOffset);
});
}, this.noteOffset * scale.length).then((buffer) => {
for (let i = 0; i < scale.length * self.numVoices; i += 1) {
this.players.push(new Tone.Player(buffer).toDestination());
}, this.noteOffset * this.scale.length).then((buffer) => {
for (let i = 0; i < this.scale.length * self.numVoices; i += 1) {
Tone.setContext(Tone.context);
const player = new Tone.Player(buffer);
Tone.connect(player, Tone.Destination);
this.players.push(player);
}
});

// Init polyphony tracker. More notes playing at the same time
// means that each note needs to play quieter

this.polyphony = Array(gridWidth).fill(0);
this.notes = []; // Sparse array
}

/**
Expand Down

0 comments on commit e7342bd

Please sign in to comment.