diff --git a/Gruntfile.js b/Gruntfile.js index aa189ff4..aa4cdca1 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -93,6 +93,7 @@ module.exports = function(grunt) { 'metro': 'src/metro', 'peakdetect': 'src/peakDetect', 'gain': 'src/gain', + 'audioVoice': 'src/audioVoice', 'monosynth': 'src/monosynth', 'polysynth': 'src/polysynth' }, diff --git a/examples/synthpresets/sketch.js b/examples/synthpresets/sketch.js index 6eeb6279..157fe52b 100644 --- a/examples/synthpresets/sketch.js +++ b/examples/synthpresets/sketch.js @@ -1,21 +1,32 @@ var monoSynth; var note; +var octave = 0; function setup() { monoSynth = new p5.MonoSynth(); + monoSynth.loadPreset('punchyBass'); } function keyPressed() { - // pick a random midi note - var midiVal = round( random(50,72) ); - monoSynth.triggerAttack(keyToMidi(), 1 ); - console.log(key); - + // monoSynth.triggerAttack(keyToMidi() + octave, 1 ) + + + + //OCTAVEf + if (keyCode === UP_ARROW) { + octave +=12; + } else if (keyCode === DOWN_ARROW) { + octave -=12; + } + else { + monoSynth.play(keyToMidi() + octave, 1); + + } } -function keyReleased() { - monoSynth.triggerRelease(); -} +// function keyReleased() { +// monoSynth.triggerRelease(); +// } function keyToMidi() { diff --git a/src/app.js b/src/app.js index 76eef455..1403ab67 100644 --- a/src/app.js +++ b/src/app.js @@ -32,6 +32,7 @@ define(function (require) { require('monosynth'); require('polysynth'); require('distortion'); + require('audioVoice'); require('monosynth'); require('polysynth'); diff --git a/src/audioVoice.js b/src/audioVoice.js new file mode 100644 index 00000000..5a5246de --- /dev/null +++ b/src/audioVoice.js @@ -0,0 +1,70 @@ +'use strict'; +define(function() { + var p5sound = require('master'); + + /** + * Base class for synthesizers and instruments + * handles note triggering and what not + */ + p5.AudioVoice = function () { + + + //this.osctype = 'sine'; + //this.volume= 0.33; + //this.note = 60; + this.note; + + // this.attack = 0.25; + // this.decay=0.25; + // this.sustain=0.95; + // this.release=0.25; + //this.env = new p5.Env(this.attack,this.volume, this.decay,this.volume, this.sustain, this.volume,this.release); + + //this.filter.set(22050, 5); + + //this.env.connect(this.filter); + // + this.ac = p5sound.audiocontext; + this.output = this.ac.createGain(); + this.connect(); + + p5sound.soundArray.push(this); + }; + + p5.AudioVoice.prototype._setNote = function() { + }; + + p5.AudioVoice.prototype.play = function () { + }; + + p5.AudioVoice.prototype.triggerAttack = function (note, velocity, secondsFromNow) { + }; + + p5.AudioVoice.prototype.triggerRelease = function () { + }; + + p5.AudioVoice.prototype.amp = function(vol, rampTime) { + }; + + p5.AudioVoice.prototype.setParams = function(params) { + }; + + p5.AudioVoice.prototype.loadPreset = function(preset) { + }; + + p5.AudioVoice.prototype.connect = function(unit) { + var u = unit || p5sound.input; + this.output.connect(u.input ? u.input : u); + }; + + p5.AudioVoice.prototype.disconect = function() { + this.output.disconnect(); + }; + + p5.AudioVoice.prototype.dispose = function() { + this.output.disconnect(); + delete this.output; + }; + + return p5.AudioVoice; +}); diff --git a/src/monosynth.js b/src/monosynth.js index 4757785a..32fad1fb 100644 --- a/src/monosynth.js +++ b/src/monosynth.js @@ -2,6 +2,7 @@ define(function (require) { var p5sound = require('master'); + var AudioVoice = require('audioVoice'); require('sndcore'); require('oscillator'); require('env'); @@ -19,10 +20,11 @@ define(function (require) { **/ p5.MonoSynth = function () { + AudioVoice.call(this); - this.ac = p5sound.audiocontext; + // this.ac = p5sound.audiocontext; - this.output = this.ac.createGain(); + // this.output = this.ac.createGain(); // this.attack = 0.02; // this.decay=0.25; @@ -54,6 +56,8 @@ define(function (require) { this.oscillator.start(); + this._isOn = false; + p5sound.soundArray.push(this); }; @@ -112,7 +116,7 @@ define(function (require) { */ p5.MonoSynth.prototype.triggerAttack = function (note, velocity, secondsFromNow) { this._setNote(note, secondsFromNow); - + this._isOn = true; this.env.ramp(this.output, secondsFromNow , velocity); // this.env.triggerAttack(this.output, secondsFromNow); }; @@ -128,6 +132,7 @@ define(function (require) { p5.MonoSynth.prototype.triggerRelease = function (secondsFromNow) { this.env.ramp(this.output, secondsFromNow, 0); + this._isOn = false; // this.env.triggerRelease(this.output, secondsFromNow); }; @@ -150,14 +155,17 @@ define(function (require) { }; - - p5.MonoSynth.prototype.loadPreset = function(preset) { + var options = this[preset]; + this.oscillator.setType(options.oscillator.type); - }; - - + this.env.setADSR(options.env.attack, options.env.decay, + options.env.sustain, options.env.release); + this.filter.setType(options.filter.type); + this.filter.set(options.filter.freq, options.filter.res); + return this; + }; //PRESETS // @@ -166,10 +174,13 @@ define(function (require) { 'type' : 'sine' }, 'env' : { - 'setADSR': [0.02, 0.25, 0.05, 0.35] + 'attack' : 0.02, + 'decay' : 0.25, + 'sustain': 0.05, + 'release': 0.35 }, 'filter': { - 'setType' : 'highpass', + 'type' : 'highpass', 'freq' : 5, 'res' : 1 } @@ -180,14 +191,34 @@ define(function (require) { 'type' : 'square' }, 'env' : { - 'setADSR': [0.02, 0.25, 0.05, 0.35] + 'attack': 0, + 'decay': .60, + 'sustain': 0.1, + 'release': .28 + }, + 'filter' : { + 'type' : 'lowpass', + 'freq' : 15000, + 'res' : 1 + } + }; + + p5.MonoSynth.prototype.electricPiano = { + 'oscillator' : { + 'type' : 'sine' + }, + 'env' : { + 'attack': 0.029, + 'decay': .16, + 'sustain': 0.1, + 'release': .1 }, 'filter': { - 'setType' : 'highpass', - 'freq' : 5, + 'type' : 'lowpass', + 'freq' : 15000, 'res' : 1 } - } + }; /** * Set values like a traditional * @@ -210,14 +241,50 @@ define(function (require) { * @param {Number} [releaseTime] Time in seconds from now (defaults to 0) **/ - p5.MonoSynth.prototype.setADSR = function (a,d,s,r) { - this.attack = a; - this.decay=d; - this.sustain=s; - this.release=r; - this.env.setADSR(this.attack, this.decay, this.sustain, this.release); + p5.MonoSynth.prototype.setADSR = function (attack,decay,sustain,release) { + this.env.setADSR(attack, decay, sustain, release); }; + + Object.defineProperties(p5.MonoSynth, { + 'attack': { + get : function() { + return this.env.aTime; + }, + set : function(attack) { + this.env.setADSR(attack, this.env.dTime, + this.env.sPercent, this.env.rTime); + } + }, + 'decay': { + get : function() { + return this.env.dTime; + }, + set : function(decay) { + this.env.setADSR(this.env.aTime, decay, + this.env.sPercent, this.env.rTime); + } + }, + 'sustain': { + get : function() { + return this.env.sPercent; + }, + set : function(sustain) { + this.env.setADSR(this.env.aTime, this.env.dTime, + sustain, this.env.rTime); + } + }, + 'release': { + get : function() { + return this.env.rTime; + }, + set : function(release) { + this.env.setADSR(this.env.aTime, this.env.dTime, + this.env.sPercent, release); + } + }, + }); + p5.MonoSynth.prototype.amp = function(vol, rampTime) { var t = rampTime || 0; if (typeof vol !== 'undefined') { @@ -233,7 +300,7 @@ define(function (require) { * @method connect * @param {Object} unit A p5.sound or Web Audio object */ - + p5.MonoSynth.prototype.connect = function(unit) { var u = unit || p5sound.input; this.output.connect(u.input ? u.input : u); @@ -255,19 +322,15 @@ define(function (require) { * @method dispose */ p5.MonoSynth.prototype.dispose = function() { + AudioVoice.prototype.disposed.apply(this); + this.filter.dispose(); this.env.dispose(); - try { this.oscillator.dispose(); } catch(e) { console.error('mono synth default oscillator already disposed'); } - - this.output.disconnect(); - this.output = undefined; }; - - }); diff --git a/src/polysynth.js b/src/polysynth.js index 231c4e2d..e7da7b4e 100644 --- a/src/polysynth.js +++ b/src/polysynth.js @@ -15,15 +15,39 @@ define(function (require) { * @constructor * * @param {Number} [synthVoice] A monophonic synth voice inheriting - * the AudioVoice class. - * + * the AudioVoice class. Defaults ot p5.MonoSynth + * + * @param {Number} [polyValue] Number of voices, defaults to 8; **/ -p5.PolySynth = function(synthVoice){ - this.voices = {}; - this.synthVoice = synthVoice; + p5.PolySynth = function(audioVoice, polyValue) { + //this.voices = {}; - p5sound.soundArray.push(this); -} + //this will hold all possible notes and say if they are being played or not + //this.notes = {}; + + //array to hold the voices + this.audiovoices = []; + this._voicesInUse = []; + + this._newest = 0; + this._oldest = 0; + + this._voicesInUse = 0; + + this.polyValue = polyValue || 8; + + this.AudioVoice = audioVoice; + + this.allocateVoices(); + + p5sound.soundArray.push(this); + }; + + p5.PolySynth.prototype.allocateVoices = function() { + for(var i = 0; i< this.polyValue; i++) { + this.audiovoices.push(new this.AudioVoice()); + } + }; /** * Play a note. @@ -35,12 +59,34 @@ p5.PolySynth = function(synthVoice){ * @param {Number} [sustainTime] time to sustain before releasing the envelope */ p5.PolySynth.prototype.play = function (note,velocity, secondsFromNow, susTime){ - if (this.voices[note] == null) { - this.voices[note] = new this.synthVoice(); - } - this.voices[note].env.setRange(velocity,0); - this.voices[note]._setNote(note); - this.voices[note].play(note,velocity, secondsFromNow, susTime); + // if (this.voices[note] === null) { + // this.voices[note] = new this.AudioVoice(); + // } + // this.voices[note].env.setRange(velocity,0); + // this.voices[note]._setNote(note); + // this.voices[note].play(note,velocity, secondsFromNow, susTime); + + + if(this._voicesInUse < this.polyValue) { + var currentVoice = this._voicesInUse; + this.noteAttack(currentVoice, note, velocity, secondsFromNow); + this.noteRelease(currentVoice, secondsFromNow + susTime); + } else { + if(this.audiovoices[this._oldest]._isOn) { + this.noteRelease(this._oldest); + } + this.noteAttack(this._oldest, note, velocity, secondsFromNow); + this.noteRelease(this._oldest, secondsFromNow + susTime); + + this._newest = this._oldest; + this._oldest = ( this._oldest + 1 ) % (this.polyValue - 1); + } + + + + // this.noteAttack(this._newest, note, velocity,secondsFromNow); + // this.noteRelease(this._newest, secondsFromNow+susTime); + return this.audiovoices[this._newest]; } @@ -69,9 +115,10 @@ p5.PolySynth.prototype.play = function (note,velocity, secondsFromNow, susTime){ * then decayLevel would increase proportionally, to become 0.5. * @param {Number} [releaseTime] Time in seconds from now (defaults to 0) **/ + p5.PolySynth.prototype.noteADSR = function (note,a,d,s,r){ if(this.voices[note] == null){ - this.voices[note] = new this.synthVoice(); + this.voices[note] = new this.AudioVoice(); } this.voices[note]._setNote(note); this.voices[note].setADSR(a,d,s,r); @@ -88,13 +135,19 @@ p5.PolySynth.prototype.noteADSR = function (note,a,d,s,r){ * @param {Number} [secondsFromNow] time from now (in seconds) * */ -p5.PolySynth.prototype.noteAttack = function (note,velocity, secondsFromNow){ - if(this.voices[note] == null){ - this.voices[note] = new this.synthVoice(); - } - this.voices[note]._setNote(note); - this.voices[note].env.setRange(velocity,0); - this.voices[note].triggerAttack(secondsFromNow); +p5.PolySynth.prototype.noteAttack = function (voice, note, velocity, secondsFromNow){ + this._voicesInUse += 1; + + this._newest = voice; + this.audiovoices[voice].triggerAttack(note, velocity, secondsFromNow) + console.log + + // if(this.voices[note] == null){ + // this.voices[note] = new this.AudioVoice(); + // } + // this.voices[note]._setNote(note); + // this.voices[note].env.setRange(velocity,0); + // this.voices[note].triggerAttack(secondsFromNow); } @@ -109,12 +162,22 @@ p5.PolySynth.prototype.noteAttack = function (note,velocity, secondsFromNow){ * */ -p5.PolySynth.prototype.noteRelease = function (note,secondsFromNow){ - if (this.voices[note] != null) { - this.voices[note]._setNote(note); - this.voices[note].triggerRelease(secondsFromNow); - delete this.voices[note]; - } +p5.PolySynth.prototype.noteRelease = function (voice,secondsFromNow){ + console.log('voice '+voice); + console.log('release '+ secondsFromNow); + + + this.audiovoices[voice].triggerRelease(secondsFromNow); + this._voicesInUse -= 1; + + this._newest = this._newest === 0 ? 0 : (this._newest - 1) % (this.polyValue - 1); + + + // if (this.voices[note] != null) { + // this.voices[note]._setNote(note); + // this.voices[note].triggerRelease(secondsFromNow); + // delete this.voices[note]; + // } } @@ -132,7 +195,7 @@ p5.PolySynth.prototype.noteRelease = function (note,secondsFromNow){ */ p5.PolySynth.prototype.noteParams = function (note,params){ if(this.voices[note] == null){ - this.voices[note] = new this.synthVoice(); + this.voices[note] = new this.AudioVoice(); } this.voices[note].setParams(params);