Commit 640f55b4 authored by Max-Ferdinand Suffel's avatar Max-Ferdinand Suffel
Browse files

enhance MIDI.js with a PCM callback

parent 066e8ed7
......@@ -15,30 +15,88 @@
<body>
<script type="text/javascript">
// This iterates through all the notes in these two soundfonts.
// The synth_drum does not have as high or low of range of notes,
// so you wont hear it for a few seconds.
// For debug purpose only.
function array2str(ua) {
var h = '';
for (var i = 0; i < ua.length; i++) {
h += "\\0x" + ua[i].toString(16);
}
return h;
}
// Utilities for sending light data over a web socket.
function sendLightData(socket, color, brightness) {
if (brightness < 0 || brightness > 255) {
throw new Error('Brightness must be between 0 and 255 (inclusive)');
}
var outputBuffer;
if (color == "red") {
outputBuffer = new Uint8Array([0x01, brightness]);
} else if (color == "blue") {
outputBuffer = new Uint8Array([0x02, brightness]);
} else {
throw new Error('Color must be \'red\' or \'blue\'');
}
socket.send(outputBuffer);
}
// Utilities for sending audio data over a web socket.
function float32To16bitPCM(input) {
var output = new Int16Array(input.length);
for (var i = 0; i < input.length; ++i) {
var sample = input[i]; //saturation: Math.max(-1, Math.min(1, buffer[l]));
output[i] = (sample < 0) ? (sample * 0x8000) : (sample * 0x7FFF);
}
return output.buffer;
}
function concatBuffers(buffer1, buffer2) {
var tmp = new Uint8Array(buffer1.byteLength + buffer2.byteLength);
tmp.set(new Uint8Array(buffer1), 0);
tmp.set(new Uint8Array(buffer2), buffer1.byteLength);
return tmp.buffer;
}
function sendAudioData(socket, data) {
var pcmBuffer = float32To16bitPCM(data);
var header = new Uint8Array([0x00]);
var outputBuffer = concatBuffers(header.buffer, pcmBuffer);
socket.send(outputBuffer);
}
// On web page is loaded:
window.onload = function () {
MIDI.loadPlugin({
soundfontUrl: "./soundfont/",
instruments: [ "acoustic_grand_piano", "synth_drum" ],
callback: function() {
MIDI.programChange(0, 0);
MIDI.programChange(1, 118);
for (var n = 0; n < 100; n ++) {
var delay = n / 4; // play one note every quarter second
var note = MIDI.pianoKeyOffset + n; // the MIDI note
var velocity = 127; // how hard the note hits
// play the note
MIDI.noteOn(0, note, velocity, delay);
// play the some note 3-steps up
MIDI.noteOn(1, note + 3, velocity, delay);
}
}
});
};
// Connect to relay server via web socket.
var relaySocket = new WebSocket('ws://localhost:9000');
relaySocket.binaryType = "arraybuffer";
relaySocket.onopen = function(e) {
MIDI.loadPlugin({
soundfontUrl: "./soundfont/",
instruments: [ "acoustic_grand_piano", "synth_drum" ],
callback: function() {
MIDI.programChange(0, 0);
MIDI.programChange(1, 118);
for (var n = 0; n < 100; n ++) {
var delay = n / 4; // play one note every quarter second
var note = MIDI.pianoKeyOffset + n; // the MIDI note
var velocity = 127; // how hard the note hits
// play the note
MIDI.noteOn(0, note, velocity, delay);
// play the some note 3-steps up
MIDI.noteOn(1, note + 3, velocity, delay);
}
},
callbackPCM: function (pcmBuffer) {
sendAudioData(relaySocket, pcmBuffer);
},
silent: true // Debug: remove to hear audio on system speaker
});
};
};
</script>
</body>
</html>
\ No newline at end of file
......@@ -172,32 +172,59 @@ if (window.AudioContext || window.webkitAudioContext) (function () {
};
root.noteOn = function (channel, note, velocity, delay) {
/// check whether the note exists
if (!MIDI.channels[channel]) return;
var instrument = MIDI.channels[channel].instrument;
if (!audioBuffers[instrument + "" + note]) return;
/// convert relative delay to absolute delay
if (delay < ctx.currentTime) delay += ctx.currentTime;
/// crate audio buffer
var source = ctx.createBufferSource();
sources[channel + "" + note] = source;
source.buffer = audioBuffers[instrument + "" + note];
source.connect(ctx.destination);
///
if (ctx.createGain) { // firefox
source.gainNode = ctx.createGain();
} else { // chrome
source.gainNode = ctx.createGainNode();
}
}
var value = (velocity / 127) * (masterVolume / 127) * 2 - 1;
source.gainNode.connect(ctx.destination);
source.gainNode.gain.value = Math.max(-1, value);
source.connect(source.gainNode);
// SoundBrain: Inject an audio processor right before the speaker sink,
// to hijack the raw PCM 16-bit audio data.
if (root.callbackPCM) {
if (!ctx.audioProcessor) {
ctx.audioProcessor = ctx.createScriptProcessor(0, 1, 1);
ctx.audioProcessor.onaudioprocess = function(e) {
var inputBuffer = e.inputBuffer.getChannelData(0);
root.callbackPCM(inputBuffer);
if (!root.silent) {
var outputBuffer = e.outputBuffer.getChannelData(0);
for (var sample = 0; sample < inputBuffer.length; sample++) {
outputBuffer[sample] = inputBuffer[sample];
}
}
};
ctx.audioProcessor.connect(ctx.destination);
}
source.connect(ctx.audioProcessor);
source.gainNode.connect(ctx.audioProcessor);
} else {
source.connect(ctx.destination);
source.gainNode.connect(ctx.destination);
}
if (source.noteOn) { // old api
source.noteOn(delay || 0);
} else { // new api
source.start(delay || 0);
}
return source;
};
......@@ -254,10 +281,13 @@ if (window.AudioContext || window.webkitAudioContext) (function () {
};
root.connect = function (conf) {
root.callbackPCM = conf.callbackPCM;
root.silent = conf.silent;
setPlugin(root);
//
MIDI.Player.ctx = ctx = new AudioContext();
///
var urlList = [];
var keyToNote = MIDI.keyToNote;
for (var key in keyToNote) urlList.push(key);
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment