Skip to content

Commit b9aff1c

Browse files
committed
synths accept notes as frequency or as midi note string (C4)
1 parent ebeba8a commit b9aff1c

File tree

3 files changed

+81
-60
lines changed

3 files changed

+81
-60
lines changed

src/audioVoice.js

Lines changed: 22 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,12 @@ define(function() {
33
var p5sound = require('master');
44

55
/**
6-
* Base class for synthesizers and instruments
7-
* handles note triggering and what not
6+
* Base class for monophonic synthesizers. Any extensions of this class
7+
* should follow the API and implement the methods below in order to
8+
* remain compatible with p5.PolySynth();
9+
*
10+
* @class AudioVoice
11+
* @constructor
812
*/
913
p5.AudioVoice = function () {
1014
this.ac = p5sound.audiocontext;
@@ -13,14 +17,19 @@ define(function() {
1317
p5sound.soundArray.push(this);
1418
};
1519

20+
/**
21+
* This method converts midi notes specified as a string "C4", "Eb3"...etc
22+
* to frequency
23+
* @private
24+
* @method _setNote
25+
* @param {String} note
26+
*/
1627
p5.AudioVoice.prototype._setNote = function(note) {
1728
var wholeNotes = {A:21, B:23, C:24, D:26, E:28, F:29, G:31};
18-
1929
var value = wholeNotes[ note[0] ];
2030
var octave = typeof Number(note.slice(-1)) === 'number'? note.slice(-1) : 0;
2131
value += 12 * octave;
2232
value = note[1] === '#' ? value+1 : note[1] ==='b' ? value - 1 : value;
23-
2433
//return midi value converted to frequency
2534
return p5.prototype.midiToFreq(value);
2635
};
@@ -37,17 +46,20 @@ define(function() {
3746
p5.AudioVoice.prototype.amp = function(vol, rampTime) {
3847
};
3948

40-
p5.AudioVoice.prototype.setParams = function(params) {
41-
};
42-
43-
p5.AudioVoice.prototype.loadPreset = function(preset) {
44-
};
45-
49+
/**
50+
* Connect to p5 objects or Web Audio Nodes
51+
* @method connect
52+
* @param {Object} unit
53+
*/
4654
p5.AudioVoice.prototype.connect = function(unit) {
4755
var u = unit || p5sound.input;
4856
this.output.connect(u.input ? u.input : u);
4957
};
5058

59+
/**
60+
* Disconnect from soundOut
61+
* @method disconnect
62+
*/
5163
p5.AudioVoice.prototype.disconect = function() {
5264
this.output.disconnect();
5365
};

src/monosynth.js

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,8 @@ define(function (require) {
6565
p5.MonoSynth.prototype = Object.create(p5.AudioVoice.prototype);
6666

6767
/**
68-
* Play tells the MonoSynth to start playing a note
68+
* Play tells the MonoSynth to start playing a note. This method schedules
69+
* the calling of .triggerAttack and .triggerRelease.
6970
*
7071
* @method play
7172
* @param {String | Number} note the note you want to play, specified as a
@@ -78,7 +79,6 @@ define(function (require) {
7879
* @param {Number} [sustainTime] time to sustain before releasing the envelope
7980
*
8081
*/
81-
8282
p5.MonoSynth.prototype.play = function (note, velocity, secondsFromNow, susTime) {
8383
// set range of env (TO DO: allow this to be scheduled in advance)
8484
var susTime = susTime || this.sustain;
@@ -104,24 +104,24 @@ define(function (require) {
104104
*/
105105
p5.MonoSynth.prototype.triggerAttack = function (note, velocity, secondsFromNow) {
106106
var secondsFromNow = secondsFromNow || 0;
107-
var freq = typeof note === 'string' ? this._setNote(note)
108-
: typeof note === 'number' ? note : 440;
109-
var vel = velocity || 1;
110107

108+
//triggerAttack uses ._setNote to convert a midi string to a frequency if necessary
109+
var freq = typeof note === 'string' ? this._setNote(note)
110+
: typeof note === 'number' ? note : 440;
111+
var vel = velocity || 1;
111112
this._isOn = true;
112113
this.oscillator.freq(freq, 0, secondsFromNow);
113114
this.env.ramp(this.output, secondsFromNow, vel);
114115
};
115116

116117
/**
117-
* Trigger the Release of the Envelope. This is similar to releasing
118+
* Trigger the release of the Envelope. This is similar to releasing
118119
* the key on a piano and letting the sound fade according to the
119120
* release level and release time.
120121
*
121122
* @param {Number} secondsFromNow time to trigger the release
122123
* @method triggerRelease
123124
*/
124-
125125
p5.MonoSynth.prototype.triggerRelease = function (secondsFromNow) {
126126
var secondsFromNow = secondsFromNow || 0;
127127
this.env.ramp(this.output, secondsFromNow, 0);
@@ -148,8 +148,7 @@ define(function (require) {
148148
* increased to 1.0 (using <code>setRange</code>),
149149
* then decayLevel would increase proportionally, to become 0.5.
150150
* @param {Number} [releaseTime] Time in seconds from now (defaults to 0)
151-
**/
152-
151+
*/
153152
p5.MonoSynth.prototype.setADSR = function (attack,decay,sustain,release) {
154153
this.env.setADSR(attack, decay, sustain, release);
155154
};
@@ -232,7 +231,7 @@ define(function (require) {
232231
/**
233232
* Disconnect all outputs
234233
*
235-
* @method disconnects
234+
* @method disconnect
236235
*/
237236
p5.MonoSynth.prototype.disconnect = function() {
238237
this.output.disconnect();

src/polysynth.js

Lines changed: 50 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,9 @@ define(function (require) {
1717
*
1818
* @param {Number} [synthVoice] A monophonic synth voice inheriting
1919
* the AudioVoice class. Defaults to p5.MonoSynth
20-
*
2120
* @param {Number} [polyValue] Number of voices, defaults to 8;
2221
*
2322
*
24-
*
2523
* @example
2624
* <div><code>
2725
* var polysynth;
@@ -85,6 +83,7 @@ define(function (require) {
8583
/**
8684
* Construct the appropriate number of audiovoices
8785
* @private
86+
* @method _allocateVoices
8887
*/
8988
p5.PolySynth.prototype._allocateVoices = function() {
9089
for(var i = 0; i< this.polyValue; i++) {
@@ -183,43 +182,61 @@ define(function (require) {
183182
//this value is used by this._voicesInUse
184183
var t = now + tFromNow;
185184

186-
// var note = _note === undefined ? 60 : _note;
187-
//
188-
var note = typeof _note === 'string' ? this.AudioVoice.prototype._setNote(_note)
189-
: typeof _note === 'number' ? _note : 440;
185+
//Convert note to frequency if necessary. This is because entries into this.notes
186+
//should be based on frequency for the sake of consistency.
187+
var note = typeof _note === 'string' ? this.AudioVoice.prototype._setNote(_note)
188+
: typeof _note === 'number' ? _note : 440;
190189
var velocity = _velocity === undefined ? 1 : _velocity;
191190

192191
var currentVoice;
193192

193+
//Release the note if it is already playing
194194
if (this.notes[note] !== undefined) {
195195
this.noteRelease(note,0);
196196
}
197197

198-
198+
//Check to see how many voices are in use at the time the note will start
199199
if(this._voicesInUse.getValueAtTime(t) < this.polyValue) {
200200
currentVoice = this._voicesInUse.getValueAtTime(t);
201-
} else {
201+
}
202+
//If we are exceeding the polyvalue, bump off the oldest notes and replace
203+
//with a new note
204+
else {
202205
currentVoice = this._oldest;
203206

204207
var oldestNote = p5.prototype.freqToMidi(this.audiovoices[this._oldest].oscillator.freq().value);
205208
this.noteRelease(oldestNote);
206209
this._oldest = ( this._oldest + 1 ) % (this.polyValue - 1);
207210
}
208211

212+
//Overrite the entry in the notes object. A note (frequency value)
213+
//corresponds to the index of the audiovoice that is playing it
209214
this.notes[note] = currentVoice;
210215

211-
//this._voicesInUse.setValueAtTime(this._voicesInUse.getValueAtTime(t) + 1, t);
212-
216+
//Find the scheduled change in this._voicesInUse that will be previous to this new note
217+
//Add 1 and schedule this value at time 't', when this note will start playing
213218
var previousVal = this._voicesInUse._searchBefore(t) === null ? 0 : this._voicesInUse._searchBefore(t).value;
214219
this._voicesInUse.setValueAtTime(previousVal + 1, t);
220+
221+
//Then update all scheduled values that follow to increase by 1
215222
this._updateAfter(t, 1);
216223

217224
this._newest = currentVoice;
218225

226+
//The audiovoice handles the actual scheduling of the note
219227
this.audiovoices[currentVoice].triggerAttack(note, velocity, tFromNow);
220-
221228
};
222229

230+
/**
231+
* Private method to ensure accurate values of this._voicesInUse
232+
* Any time a new value is scheduled, it is necessary to increment all subsequent
233+
* scheduledValues after attack, and decrement all subsequent
234+
* scheduledValues after release
235+
*
236+
* @param {[type]} time [description]
237+
* @param {[type]} value [description]
238+
* @return {[type]} [description]
239+
*/
223240
p5.PolySynth.prototype._updateAfter = function(time, value) {
224241

225242
if(this._voicesInUse._searchAfter(time) === null) {
@@ -229,8 +246,7 @@ define(function (require) {
229246
var nextTime = this._voicesInUse._searchAfter(time).time;
230247
this._updateAfter(nextTime, value);
231248
}
232-
}
233-
249+
};
234250

235251

236252
/**
@@ -245,12 +261,9 @@ define(function (require) {
245261
*/
246262

247263
p5.PolySynth.prototype.noteRelease = function (_note,secondsFromNow) {
248-
// var note = _note === undefined ?
249-
// p5.prototype.freqToMidi(this.audiovoices[this._newest].oscillator.freq().value)
250-
// : _note;
264+
//Make sure note is in frequency inorder to query the this.notes object
251265
var note = typeof _note === 'string' ? this.AudioVoice.prototype._setNote(_note)
252-
: typeof _note === 'number' ? _note : this.audiovoices[this._newest].oscillator.freq().value;
253-
266+
: typeof _note === 'number' ? _note : this.audiovoices[this._newest].oscillator.freq().value;
254267

255268
if (this.notes[note] === undefined) {
256269
console.warn('Cannot release a note that is not already playing');
@@ -259,10 +272,11 @@ define(function (require) {
259272
var tFromNow = secondsFromNow || 0;
260273
var t = now + tFromNow;
261274

262-
// this._voicesInUse.setValueAtTime(this._voicesInUse.getValueAtTime(t)-1, t);
263-
// console.log('value at time: '+ t +' value '+this._voicesInUse.getValueAtTime(t));
275+
//Find the scheduled change in this._voicesInUse that will be previous to this new note
276+
//subtract 1 and schedule this value at time 't', when this note will stop playing
264277
var previousVal = this._voicesInUse._searchBefore(t) === null ? 0 : this._voicesInUse._searchBefore(t).value;
265278
this._voicesInUse.setValueAtTime(previousVal - 1, t);
279+
//Then update all scheduled values that follow to decrease by 1
266280
this._updateAfter(t, -1);
267281

268282
// console.log('RELEASE ' + note);
@@ -274,37 +288,33 @@ define(function (require) {
274288

275289
};
276290

277-
278291
/**
279-
* Set cutoms parameters to a specific synth implementation
280-
* with the help of JavaScript Object Notation (JSON).
281-
*
282-
* @method setParams
283-
* @param JSON object
284-
*
285-
* For instance to set the detune parameter of a synth, call :
286-
* setParams({detune: 15 });
287-
*
288-
*/
289-
p5.PolySynth.prototype.noteParams = function (note,params) {
290-
if(this.voices[note] == null) {
291-
this.voices[note] = new this.AudioVoice();
292-
}
293-
this.voices[note].setParams(params);
294-
295-
};
296-
292+
* Connect to a p5.sound / Web Audio object.
293+
*
294+
* @method connect
295+
* @param {Object} unit A p5.sound or Web Audio object
296+
*/
297297
p5.PolySynth.prototype.connect = function (unit) {
298298
var u = unit || p5sound.input;
299299
this.output.connect(u.input ? u.input : u);
300300
};
301301

302+
/**
303+
* Disconnect all outputs
304+
*
305+
* @method disconnect
306+
*/
302307
p5.PolySynth.prototype.disconnect = function() {
303308
this.output.disconnect();
304309
};
305310

311+
/**
312+
* Get rid of the MonoSynth and free up its resources / memory.
313+
*
314+
* @method dispose
315+
*/
306316
p5.PolySynth.prototype.dispose = function() {
307-
this.audiovoices.forEach(function(voice){
317+
this.audiovoices.forEach(function(voice) {
308318
voice.dispose();
309319
});
310320

0 commit comments

Comments
 (0)