polytone v0.1.0
polytone
Create polyphonic instruments from audio sources
polytone
provides a clean API to work with web audio source nodes. It handles common tasks like converting from note names to frequency, add gain, envelope and filter support, events, scheduling or even handling audio context and node lifecycles.
The gist:
var polytone = require('polytone')
// Create a function that returns an audio source node
function synth (options) {
var osc = options.context.createOscillator()
osc.frequency.value = options.frequency
return osc
}
// specify the default options and the synth function
var inst = polytone(synth, { gain: 0.5, attack: 0.01, release: 0.2 })
// pass the note name and receive the frequency and midi note number
inst.start({ note: 'C4' })
// works when passing just the note
inst.start('C4')
// override any option
inst.start({ gain: 0.8, release: 0.4, duration: 1 })
// stop all sounds after 3 seconds
inst.stop(null, 3)
## Examples
Create a audio buffer player
var snare = polytone(<AudioBuffer>)
snare.start() // play the buffer
snare.start({ gain: 0.5 }, null, 1) // => play after 1 second
Create an oscillator voice
var sine = polytone('sine')
sine.start({ note: 'C4' })
sine.start({ note: 'G4' })
sine.stop(null, 1) // => stop all after 1 second
Create a sampled instrument
You can combine multiple AudioBuffers in one polytone instrument:
var drumMachine = polytone({ snare: <AudioBuffer>, kick: <AudioBuffer>, ... })
drumMachine.start('kick') // => play the kick
drumMachine.start('snare', null, 1) // => play the snare after 1 second
drumMachine.start({ name: 'conga', gain: 0.5 }) // => play the conga with options
If the sample names are notes, some conversions are performed:
var piano = polytone({ 'C4': <AudioBuffer>, 'C#4': <AudioBuffer>, 'D4': ... })
piano.start('Db4') // => handles enharmonic notes
piano.start(70) // => can use midi numbers
piano.start(70.5) // => if midi is float, the buffer is detuned
piano.start({ note: 'C4', duration: 1 })
Create a synthetized drum machine
var dm = polytone(null, {
snare: function(options) { /* returns an AudioNode or similar object */ },
kick: function(options) { /* returns an AudioNode or similar object */ },
hihat: function(options) { /* returns an AudioNode or similar object */ },
...
})
dm.start('snare')
dm.start({ name: 'kick', gain: 1.2 })
See my Tiny808 gist for a complete example.
Usage
Note names midi note numbers, frequency and detune
Note names or midi numbers are converted to frequencies:
var instrument = polytone(synth)
instrument.start('A4') // => Options are { note: 'A4', midi: 69, frequency: 440 }
instrument.start(60.2) // => options are { midi: 60, detune: 20, frequency: ... }
instrument.start({ note: 'C4 '}) // the same as passing the note directly
instrument.start({ name: 'C4 '}) // the same as before
Gain, Envelope and Filter
You can set the gain:
instrument.start({ note: 'C4', gain: 0.2 })
Use a simple envelope with attack
and release
:
instrument.start({ note: 'C4', attack: 0.2, release: 0.5, duration: 1 })
// (the real duration of the note will be 1 + 0.5 secs of the release envelope)
Or a filter:
instrument.start({ note: 'Db3', filter: { type: 'lowpass', frequency: 500 }})
Events
You can listen all events:
var instrument = polytone(synth).on('*', function (event, time, node) {
console.log('EVENT: ', event, time, node)
})
instrument.play('C4') // => all events to the console
Or to single events (start
, stop
, ended
, disconnect
are accepted):
var instrument = polytone(synth)
instrument.on('start', function (event, time, node) {
console.log('EVENT: ', event, time, node)
})
instrument.start(440) // => only 'start' events are displayed
Audio Context
By default, polytone uses audio-context module to get the audio context, so no initialization is required. Anyway, you can pass your own context if you prefer:
var context = new AudioContext()
var instrument = polytone({ context: context }, synth)
You can provide any option either to the polytone constructor or the start
function:
function synth (options) { console.log(options) }
var instrument = polytone({ one: 1, two: 2 }, synth)
instrument.start({ two: 22, three: 333 })
// => { context: <AudioContext>, one: 1, two: 22, three: 333 }
Connect and disconnect
By default, all sources are automatically connected to the audio context destination, but you can override this option:
var reverb = /* <AudioNode> */
var instrument = polytone(synth, { connect: reverb })
instrument.start('d4')
Or for each instance individually:
var instrument = polytone(synth)
instrument.start({ note: 'C4', gain: 0.2, connect: reverb })
Are nodes are disconnected automatically after they stop.
Connect to midi inputs via Web Midi API
var piano = polytone({ 'c2': <AudioBuffer>, 'c#2': <AudioBuffer>, ... })
window.navigator.requestMIDIAccess().then(function (midiAccess) {
midiAccess.inputs.forEach(function (midiInput) {
piano.listenToMidi(midiInput)
})
})
Schedule events
A simple convenient function is provide to schedule a group of events:
var piano = polytone(...)
piano.schedule([
{ time: 0, note: 'C4' },
{ time: 0.5, note: 'G4' }
], null, 3) // => shedule it after 3 second
Or more compact:
piano.schedule('C4 G4 C5'.split(' ').map(function (note, i) {
return { time: 0.5 * i, note: note }
}))
Documentation, tests and examples
To run the examples, install budo: npm i -g budo
and then:
budo examples/index
License
MIT License
8 years ago