Created
September 17, 2014 04:55
-
-
Save seemikehack/287d3c8cd836b1807b44 to your computer and use it in GitHub Desktop.
// source http://jsbin.com/jekoce/42
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="utf-8"> | |
<title>JS Bin</title> | |
</head> | |
<body> | |
<script src="http://cdnjs.cloudflare.com/ajax/libs/bluebird/1.2.2/bluebird.js"></script> | |
<script src="https://rawgithub.com/keithwhor/audiosynth/master/audiosynth.js"></script> | |
<script id="jsbin-javascript"> | |
if (!window.AssertException) { | |
window.AssertException = function (msg) { | |
this.message = msg; | |
}; | |
window.AssertException.prototype.toString = function () { | |
return 'AssertException: ' + this.message; | |
}; | |
window.assert = function (exp, msg) { | |
if (!exp) throw new AssertException(msg); | |
}; | |
} | |
window.AudioContext = window.AudioContext || window.webkitAudioContext; | |
assert(window.AudioContext, 'AudioContext unavailable in this environment'); | |
assert(window.Promise, 'Promises unavailable in this environment'); | |
function RiffPlayer(instrument, articulation) { | |
// initial note values, relative to the beat (denote rhythm) | |
// NOTE these values assume */4 time signature (quarter note is the beat) | |
var noteValues = { | |
W : 4, // Whole | |
H : 2, // Half | |
Q : 1, // Quarter | |
I : 1/2, // eIghth (deconflicting with the scale tone E, unnecessary but less ambiguous) | |
S : 1/4, // Sixteenth | |
T : 1/8, // Thirty-second | |
X : 1/16 // siXty-fourth (obviously deconflicting with the Sixteenth and eIghth notes) | |
}; | |
var noteExtractor = /[A-G]#?/i, | |
octaveExtractor = /[1-7]/, | |
noteValueExtractor = /[WHQISTX]/i; | |
// initialize synth | |
var ctx = new AudioContext(), | |
synth = Synth.createInstrument(instrument || 'piano'), | |
noteLength = articulation; | |
function getNoteUri(noteData) { | |
var note = noteData.match(noteExtractor)[0], | |
octave = noteData.match(octaveExtractor)[0], | |
noteValue = noteData.match(noteValueExtractor)[0], | |
length = noteLength; | |
assert(note, 'unable to generate note uri, note was unspecified'); | |
assert(octave, 'unable to generate note uri, octave was unspecified'); | |
assert(noteValue, 'unable to generate note uri, noteValue was unspecified'); | |
return { | |
uri: synth.generate(note, octave, length), | |
value: noteValue | |
}; | |
} | |
function getNoteData(noteData) { | |
var promise = new Promise(function (resolve, reject) { | |
var request = new XMLHttpRequest(); | |
request.open('GET', noteData.uri, true); | |
request.responseType = 'arraybuffer'; | |
// TODO is onload standard? I think not... | |
request.onload = function() { | |
ctx.decodeAudioData(request.response, function (response) { | |
resolve({ | |
note: response, | |
value: noteData.value | |
}); | |
}, reject); | |
}; | |
request.send(); | |
}); | |
return promise; | |
} | |
function playNote(audio, time) { | |
var src = ctx.createBufferSource(); | |
src.buffer = audio; | |
src.connect(ctx.destination); | |
src.start(time); | |
} | |
function atTempo(tempo) { | |
return function (time, noteValue) { | |
return time + 60 / tempo * noteValues[noteValue]; | |
}; | |
} | |
///////////// | |
// public API | |
this.play = function (notes, tempo) { | |
// TODO .map(f).map(g) === .map(compose (g, f)) | |
Promise.all(notes.map(getNoteUri).map(getNoteData)).then(function (samples) { | |
var time = ctx.currentTime; | |
var stepTime = atTempo(tempo); | |
samples.forEach(function (sample) { | |
playNote(sample.note, time); | |
time = stepTime(time, sample.value); | |
}); | |
}).catch(function (error) { | |
// FIXME implement | |
console.log(error); | |
}); | |
}; | |
} | |
// pre-built instuments | |
RiffPlayer.instruments = { | |
piano : 0, | |
organ : 1, | |
acoustic : 2, | |
edm : 3 | |
}; | |
// sample (note) lengths for the synth, i.e., how long each sample plays | |
// NOTE these are different from note values, which denote rhythm | |
RiffPlayer.articulations = { | |
legato : 2.0, | |
tenuto : 1.0, | |
staccato : 0.5 | |
}; | |
//////////////////////////////// | |
// end library, begin test usage | |
var notes = ['C3S','E3S','G3S','C4S','G3S','E3S','C3S'], | |
notes2 = ['A2I', 'C3S', 'F3S', 'A3I', 'G3S', 'F3S', 'E3I', 'C3I', 'A2I'], | |
tempo = 108, | |
instrument = RiffPlayer.instruments.acoustic, | |
articulation = RiffPlayer.articulations.tenuto; | |
var player = new RiffPlayer(instrument, articulation); | |
player.play(notes2, tempo); | |
</script> | |
<script id="jsbin-source-html" type="text/html"><!DOCTYPE html> | |
<html> | |
<head> | |
<meta charset="utf-8"> | |
<title>JS Bin</title> | |
</head> | |
<body> | |
<script src="//cdnjs.cloudflare.com/ajax/libs/bluebird/1.2.2/bluebird.js"><\/script> | |
<script src="https://rawgithub.com/keithwhor/audiosynth/master/audiosynth.js"><\/script> | |
</body> | |
</html></script> | |
<script id="jsbin-source-javascript" type="text/javascript">if (!window.AssertException) { | |
window.AssertException = function (msg) { | |
this.message = msg; | |
}; | |
window.AssertException.prototype.toString = function () { | |
return 'AssertException: ' + this.message; | |
}; | |
window.assert = function (exp, msg) { | |
if (!exp) throw new AssertException(msg); | |
}; | |
} | |
window.AudioContext = window.AudioContext || window.webkitAudioContext; | |
assert(window.AudioContext, 'AudioContext unavailable in this environment'); | |
assert(window.Promise, 'Promises unavailable in this environment'); | |
function RiffPlayer(instrument, articulation) { | |
// initial note values, relative to the beat (denote rhythm) | |
// NOTE these values assume */4 time signature (quarter note is the beat) | |
var noteValues = { | |
W : 4, // Whole | |
H : 2, // Half | |
Q : 1, // Quarter | |
I : 1/2, // eIghth (deconflicting with the scale tone E, unnecessary but less ambiguous) | |
S : 1/4, // Sixteenth | |
T : 1/8, // Thirty-second | |
X : 1/16 // siXty-fourth (obviously deconflicting with the Sixteenth and eIghth notes) | |
}; | |
var noteExtractor = /[A-G]#?/i, | |
octaveExtractor = /[1-7]/, | |
noteValueExtractor = /[WHQISTX]/i; | |
// initialize synth | |
var ctx = new AudioContext(), | |
synth = Synth.createInstrument(instrument || 'piano'), | |
noteLength = articulation; | |
function getNoteUri(noteData) { | |
var note = noteData.match(noteExtractor)[0], | |
octave = noteData.match(octaveExtractor)[0], | |
noteValue = noteData.match(noteValueExtractor)[0], | |
length = noteLength; | |
assert(note, 'unable to generate note uri, note was unspecified'); | |
assert(octave, 'unable to generate note uri, octave was unspecified'); | |
assert(noteValue, 'unable to generate note uri, noteValue was unspecified'); | |
return { | |
uri: synth.generate(note, octave, length), | |
value: noteValue | |
}; | |
} | |
function getNoteData(noteData) { | |
var promise = new Promise(function (resolve, reject) { | |
var request = new XMLHttpRequest(); | |
request.open('GET', noteData.uri, true); | |
request.responseType = 'arraybuffer'; | |
// TODO is onload standard? I think not... | |
request.onload = function() { | |
ctx.decodeAudioData(request.response, function (response) { | |
resolve({ | |
note: response, | |
value: noteData.value | |
}); | |
}, reject); | |
}; | |
request.send(); | |
}); | |
return promise; | |
} | |
function playNote(audio, time) { | |
var src = ctx.createBufferSource(); | |
src.buffer = audio; | |
src.connect(ctx.destination); | |
src.start(time); | |
} | |
function atTempo(tempo) { | |
return function (time, noteValue) { | |
return time + 60 / tempo * noteValues[noteValue]; | |
}; | |
} | |
///////////// | |
// public API | |
this.play = function (notes, tempo) { | |
// TODO .map(f).map(g) === .map(compose (g, f)) | |
Promise.all(notes.map(getNoteUri).map(getNoteData)).then(function (samples) { | |
var time = ctx.currentTime; | |
var stepTime = atTempo(tempo); | |
samples.forEach(function (sample) { | |
playNote(sample.note, time); | |
time = stepTime(time, sample.value); | |
}); | |
}).catch(function (error) { | |
// FIXME implement | |
console.log(error); | |
}); | |
}; | |
} | |
// pre-built instuments | |
RiffPlayer.instruments = { | |
piano : 0, | |
organ : 1, | |
acoustic : 2, | |
edm : 3 | |
}; | |
// sample (note) lengths for the synth, i.e., how long each sample plays | |
// NOTE these are different from note values, which denote rhythm | |
RiffPlayer.articulations = { | |
legato : 2.0, | |
tenuto : 1.0, | |
staccato : 0.5 | |
}; | |
//////////////////////////////// | |
// end library, begin test usage | |
var notes = ['C3S','E3S','G3S','C4S','G3S','E3S','C3S'], | |
notes2 = ['A2I', 'C3S', 'F3S', 'A3I', 'G3S', 'F3S', 'E3I', 'C3I', 'A2I'], | |
tempo = 108, | |
instrument = RiffPlayer.instruments.acoustic, | |
articulation = RiffPlayer.articulations.tenuto; | |
var player = new RiffPlayer(instrument, articulation); | |
player.play(notes2, tempo); | |
</script></body> | |
</html> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
if (!window.AssertException) { | |
window.AssertException = function (msg) { | |
this.message = msg; | |
}; | |
window.AssertException.prototype.toString = function () { | |
return 'AssertException: ' + this.message; | |
}; | |
window.assert = function (exp, msg) { | |
if (!exp) throw new AssertException(msg); | |
}; | |
} | |
window.AudioContext = window.AudioContext || window.webkitAudioContext; | |
assert(window.AudioContext, 'AudioContext unavailable in this environment'); | |
assert(window.Promise, 'Promises unavailable in this environment'); | |
function RiffPlayer(instrument, articulation) { | |
// initial note values, relative to the beat (denote rhythm) | |
// NOTE these values assume */4 time signature (quarter note is the beat) | |
var noteValues = { | |
W : 4, // Whole | |
H : 2, // Half | |
Q : 1, // Quarter | |
I : 1/2, // eIghth (deconflicting with the scale tone E, unnecessary but less ambiguous) | |
S : 1/4, // Sixteenth | |
T : 1/8, // Thirty-second | |
X : 1/16 // siXty-fourth (obviously deconflicting with the Sixteenth and eIghth notes) | |
}; | |
var noteExtractor = /[A-G]#?/i, | |
octaveExtractor = /[1-7]/, | |
noteValueExtractor = /[WHQISTX]/i; | |
// initialize synth | |
var ctx = new AudioContext(), | |
synth = Synth.createInstrument(instrument || 'piano'), | |
noteLength = articulation; | |
function getNoteUri(noteData) { | |
var note = noteData.match(noteExtractor)[0], | |
octave = noteData.match(octaveExtractor)[0], | |
noteValue = noteData.match(noteValueExtractor)[0], | |
length = noteLength; | |
assert(note, 'unable to generate note uri, note was unspecified'); | |
assert(octave, 'unable to generate note uri, octave was unspecified'); | |
assert(noteValue, 'unable to generate note uri, noteValue was unspecified'); | |
return { | |
uri: synth.generate(note, octave, length), | |
value: noteValue | |
}; | |
} | |
function getNoteData(noteData) { | |
var promise = new Promise(function (resolve, reject) { | |
var request = new XMLHttpRequest(); | |
request.open('GET', noteData.uri, true); | |
request.responseType = 'arraybuffer'; | |
// TODO is onload standard? I think not... | |
request.onload = function() { | |
ctx.decodeAudioData(request.response, function (response) { | |
resolve({ | |
note: response, | |
value: noteData.value | |
}); | |
}, reject); | |
}; | |
request.send(); | |
}); | |
return promise; | |
} | |
function playNote(audio, time) { | |
var src = ctx.createBufferSource(); | |
src.buffer = audio; | |
src.connect(ctx.destination); | |
src.start(time); | |
} | |
function atTempo(tempo) { | |
return function (time, noteValue) { | |
return time + 60 / tempo * noteValues[noteValue]; | |
}; | |
} | |
///////////// | |
// public API | |
this.play = function (notes, tempo) { | |
// TODO .map(f).map(g) === .map(compose (g, f)) | |
Promise.all(notes.map(getNoteUri).map(getNoteData)).then(function (samples) { | |
var time = ctx.currentTime; | |
var stepTime = atTempo(tempo); | |
samples.forEach(function (sample) { | |
playNote(sample.note, time); | |
time = stepTime(time, sample.value); | |
}); | |
}).catch(function (error) { | |
// FIXME implement | |
console.log(error); | |
}); | |
}; | |
} | |
// pre-built instuments | |
RiffPlayer.instruments = { | |
piano : 0, | |
organ : 1, | |
acoustic : 2, | |
edm : 3 | |
}; | |
// sample (note) lengths for the synth, i.e., how long each sample plays | |
// NOTE these are different from note values, which denote rhythm | |
RiffPlayer.articulations = { | |
legato : 2.0, | |
tenuto : 1.0, | |
staccato : 0.5 | |
}; | |
//////////////////////////////// | |
// end library, begin test usage | |
var notes = ['C3S','E3S','G3S','C4S','G3S','E3S','C3S'], | |
notes2 = ['A2I', 'C3S', 'F3S', 'A3I', 'G3S', 'F3S', 'E3I', 'C3I', 'A2I'], | |
tempo = 108, | |
instrument = RiffPlayer.instruments.acoustic, | |
articulation = RiffPlayer.articulations.tenuto; | |
var player = new RiffPlayer(instrument, articulation); | |
player.play(notes2, tempo); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment