delete mid.js
This commit is contained in:
parent
11f32eb701
commit
34e4a5130e
29
libs/thirdparty/LICENSE.md
vendored
29
libs/thirdparty/LICENSE.md
vendored
@ -229,35 +229,6 @@ localforage
|
|||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
|
|
||||||
|
|
||||||
mid
|
|
||||||
============
|
|
||||||
|
|
||||||
Copyright (c) 2010, Matt Westcott & Ben Firshman
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
Redistribution and use in source and binary forms, with or without
|
|
||||||
modification, are permitted provided that the following conditions are met:
|
|
||||||
|
|
||||||
* Redistributions of source code must retain the above copyright notice, this
|
|
||||||
list of conditions and the following disclaimer.
|
|
||||||
* Redistributions in binary form must reproduce the above copyright notice,
|
|
||||||
this list of conditions and the following disclaimer in the documentation
|
|
||||||
and/or other materials provided with the distribution.
|
|
||||||
* The names of its contributors may not be used to endorse or promote products
|
|
||||||
derived from this software without specific prior written permission.
|
|
||||||
|
|
||||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
||||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
||||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
||||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
|
||||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
|
||||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
|
||||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
|
||||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
|
||||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
|
|
||||||
zip
|
zip
|
||||||
============
|
============
|
||||||
|
|
||||||
|
|||||||
701
libs/thirdparty/mid.js
vendored
701
libs/thirdparty/mid.js
vendored
@ -1,701 +0,0 @@
|
|||||||
var sampleRate = 44100; /* hard-coded in Flash player */
|
|
||||||
|
|
||||||
function AudioPlayer(context, generator, loop) {
|
|
||||||
|
|
||||||
// Uses Webkit Web Audio API if available
|
|
||||||
sampleRate = context.sampleRate;
|
|
||||||
|
|
||||||
var channelCount = 2;
|
|
||||||
var bufferSize = 4096*4; // Higher for less gitches, lower for less latency
|
|
||||||
|
|
||||||
var node = context.createScriptProcessor(bufferSize, 0, channelCount);
|
|
||||||
|
|
||||||
node.onaudioprocess = function(e) { process(e) };
|
|
||||||
|
|
||||||
function process(e) {
|
|
||||||
if (generator.finished) {
|
|
||||||
if (loop) {
|
|
||||||
generator.reset();
|
|
||||||
generator.finished = false;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
node.disconnect();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var dataLeft = e.outputBuffer.getChannelData(0);
|
|
||||||
var dataRight = e.outputBuffer.getChannelData(1);
|
|
||||||
|
|
||||||
var generate = generator.generate(bufferSize);
|
|
||||||
|
|
||||||
for (var i = 0; i < bufferSize; ++i) {
|
|
||||||
dataLeft[i] = generate[i*2];
|
|
||||||
dataRight[i] = generate[i*2+1];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// start
|
|
||||||
// node.connect(context.destination);
|
|
||||||
|
|
||||||
return {
|
|
||||||
'play': function () {
|
|
||||||
node.connect(context.destination);
|
|
||||||
},
|
|
||||||
'pause': function() {
|
|
||||||
node.disconnect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
/*
|
|
||||||
class to parse the .mid file format
|
|
||||||
(depends on stream.js)
|
|
||||||
*/
|
|
||||||
function MidiFile(data) {
|
|
||||||
function readChunk(stream) {
|
|
||||||
var id = stream.read(4);
|
|
||||||
var length = stream.readInt32();
|
|
||||||
return {
|
|
||||||
'id': id,
|
|
||||||
'length': length,
|
|
||||||
'data': stream.read(length)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
var lastEventTypeByte;
|
|
||||||
|
|
||||||
function readEvent(stream) {
|
|
||||||
var event = {};
|
|
||||||
event.deltaTime = stream.readVarInt();
|
|
||||||
var eventTypeByte = stream.readInt8();
|
|
||||||
if ((eventTypeByte & 0xf0) == 0xf0) {
|
|
||||||
/* system / meta event */
|
|
||||||
if (eventTypeByte == 0xff) {
|
|
||||||
/* meta event */
|
|
||||||
event.type = 'meta';
|
|
||||||
var subtypeByte = stream.readInt8();
|
|
||||||
var length = stream.readVarInt();
|
|
||||||
switch(subtypeByte) {
|
|
||||||
case 0x00:
|
|
||||||
event.subtype = 'sequenceNumber';
|
|
||||||
if (length != 2) throw "Expected length for sequenceNumber event is 2, got " + length;
|
|
||||||
event.number = stream.readInt16();
|
|
||||||
return event;
|
|
||||||
case 0x01:
|
|
||||||
event.subtype = 'text';
|
|
||||||
event.text = stream.read(length);
|
|
||||||
return event;
|
|
||||||
case 0x02:
|
|
||||||
event.subtype = 'copyrightNotice';
|
|
||||||
event.text = stream.read(length);
|
|
||||||
return event;
|
|
||||||
case 0x03:
|
|
||||||
event.subtype = 'trackName';
|
|
||||||
event.text = stream.read(length);
|
|
||||||
return event;
|
|
||||||
case 0x04:
|
|
||||||
event.subtype = 'instrumentName';
|
|
||||||
event.text = stream.read(length);
|
|
||||||
return event;
|
|
||||||
case 0x05:
|
|
||||||
event.subtype = 'lyrics';
|
|
||||||
event.text = stream.read(length);
|
|
||||||
return event;
|
|
||||||
case 0x06:
|
|
||||||
event.subtype = 'marker';
|
|
||||||
event.text = stream.read(length);
|
|
||||||
return event;
|
|
||||||
case 0x07:
|
|
||||||
event.subtype = 'cuePoint';
|
|
||||||
event.text = stream.read(length);
|
|
||||||
return event;
|
|
||||||
case 0x20:
|
|
||||||
event.subtype = 'midiChannelPrefix';
|
|
||||||
if (length != 1) throw "Expected length for midiChannelPrefix event is 1, got " + length;
|
|
||||||
event.channel = stream.readInt8();
|
|
||||||
return event;
|
|
||||||
case 0x2f:
|
|
||||||
event.subtype = 'endOfTrack';
|
|
||||||
if (length != 0) throw "Expected length for endOfTrack event is 0, got " + length;
|
|
||||||
return event;
|
|
||||||
case 0x51:
|
|
||||||
event.subtype = 'setTempo';
|
|
||||||
if (length != 3) throw "Expected length for setTempo event is 3, got " + length;
|
|
||||||
event.microsecondsPerBeat = (
|
|
||||||
(stream.readInt8() << 16)
|
|
||||||
+ (stream.readInt8() << 8)
|
|
||||||
+ stream.readInt8()
|
|
||||||
)
|
|
||||||
return event;
|
|
||||||
case 0x54:
|
|
||||||
event.subtype = 'smpteOffset';
|
|
||||||
if (length != 5) throw "Expected length for smpteOffset event is 5, got " + length;
|
|
||||||
var hourByte = stream.readInt8();
|
|
||||||
event.frameRate = {
|
|
||||||
0x00: 24, 0x20: 25, 0x40: 29, 0x60: 30
|
|
||||||
}[hourByte & 0x60];
|
|
||||||
event.hour = hourByte & 0x1f;
|
|
||||||
event.min = stream.readInt8();
|
|
||||||
event.sec = stream.readInt8();
|
|
||||||
event.frame = stream.readInt8();
|
|
||||||
event.subframe = stream.readInt8();
|
|
||||||
return event;
|
|
||||||
case 0x58:
|
|
||||||
event.subtype = 'timeSignature';
|
|
||||||
if (length != 4) throw "Expected length for timeSignature event is 4, got " + length;
|
|
||||||
event.numerator = stream.readInt8();
|
|
||||||
event.denominator = Math.pow(2, stream.readInt8());
|
|
||||||
event.metronome = stream.readInt8();
|
|
||||||
event.thirtyseconds = stream.readInt8();
|
|
||||||
return event;
|
|
||||||
case 0x59:
|
|
||||||
event.subtype = 'keySignature';
|
|
||||||
if (length != 2) throw "Expected length for keySignature event is 2, got " + length;
|
|
||||||
event.key = stream.readInt8(true);
|
|
||||||
event.scale = stream.readInt8();
|
|
||||||
return event;
|
|
||||||
case 0x7f:
|
|
||||||
event.subtype = 'sequencerSpecific';
|
|
||||||
event.data = stream.read(length);
|
|
||||||
return event;
|
|
||||||
default:
|
|
||||||
// console.log("Unrecognised meta event subtype: " + subtypeByte);
|
|
||||||
event.subtype = 'unknown'
|
|
||||||
event.data = stream.read(length);
|
|
||||||
return event;
|
|
||||||
}
|
|
||||||
event.data = stream.read(length);
|
|
||||||
return event;
|
|
||||||
} else if (eventTypeByte == 0xf0) {
|
|
||||||
event.type = 'sysEx';
|
|
||||||
var length = stream.readVarInt();
|
|
||||||
event.data = stream.read(length);
|
|
||||||
return event;
|
|
||||||
} else if (eventTypeByte == 0xf7) {
|
|
||||||
event.type = 'dividedSysEx';
|
|
||||||
var length = stream.readVarInt();
|
|
||||||
event.data = stream.read(length);
|
|
||||||
return event;
|
|
||||||
} else {
|
|
||||||
throw "Unrecognised MIDI event type byte: " + eventTypeByte;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
/* channel event */
|
|
||||||
var param1;
|
|
||||||
if ((eventTypeByte & 0x80) == 0) {
|
|
||||||
/* running status - reuse lastEventTypeByte as the event type.
|
|
||||||
eventTypeByte is actually the first parameter
|
|
||||||
*/
|
|
||||||
param1 = eventTypeByte;
|
|
||||||
eventTypeByte = lastEventTypeByte;
|
|
||||||
} else {
|
|
||||||
param1 = stream.readInt8();
|
|
||||||
lastEventTypeByte = eventTypeByte;
|
|
||||||
}
|
|
||||||
var eventType = eventTypeByte >> 4;
|
|
||||||
event.channel = eventTypeByte & 0x0f;
|
|
||||||
event.type = 'channel';
|
|
||||||
switch (eventType) {
|
|
||||||
case 0x08:
|
|
||||||
event.subtype = 'noteOff';
|
|
||||||
event.noteNumber = param1;
|
|
||||||
event.velocity = stream.readInt8();
|
|
||||||
return event;
|
|
||||||
case 0x09:
|
|
||||||
event.noteNumber = param1;
|
|
||||||
event.velocity = stream.readInt8();
|
|
||||||
if (event.velocity == 0) {
|
|
||||||
event.subtype = 'noteOff';
|
|
||||||
} else {
|
|
||||||
event.subtype = 'noteOn';
|
|
||||||
}
|
|
||||||
return event;
|
|
||||||
case 0x0a:
|
|
||||||
event.subtype = 'noteAftertouch';
|
|
||||||
event.noteNumber = param1;
|
|
||||||
event.amount = stream.readInt8();
|
|
||||||
return event;
|
|
||||||
case 0x0b:
|
|
||||||
event.subtype = 'controller';
|
|
||||||
event.controllerType = param1;
|
|
||||||
event.value = stream.readInt8();
|
|
||||||
return event;
|
|
||||||
case 0x0c:
|
|
||||||
event.subtype = 'programChange';
|
|
||||||
event.programNumber = param1;
|
|
||||||
return event;
|
|
||||||
case 0x0d:
|
|
||||||
event.subtype = 'channelAftertouch';
|
|
||||||
event.amount = param1;
|
|
||||||
return event;
|
|
||||||
case 0x0e:
|
|
||||||
event.subtype = 'pitchBend';
|
|
||||||
event.value = param1 + (stream.readInt8() << 7);
|
|
||||||
return event;
|
|
||||||
default:
|
|
||||||
throw "Unrecognised MIDI event type: " + eventType
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
stream = Stream(data);
|
|
||||||
var headerChunk = readChunk(stream);
|
|
||||||
if (headerChunk.id != 'MThd' || headerChunk.length != 6) {
|
|
||||||
throw "Bad .mid file - header not found";
|
|
||||||
}
|
|
||||||
var headerStream = Stream(headerChunk.data);
|
|
||||||
var formatType = headerStream.readInt16();
|
|
||||||
var trackCount = headerStream.readInt16();
|
|
||||||
var timeDivision = headerStream.readInt16();
|
|
||||||
|
|
||||||
if (timeDivision & 0x8000) {
|
|
||||||
throw "Expressing time division in SMTPE frames is not supported yet"
|
|
||||||
} else {
|
|
||||||
ticksPerBeat = timeDivision;
|
|
||||||
}
|
|
||||||
|
|
||||||
var header = {
|
|
||||||
'formatType': formatType,
|
|
||||||
'trackCount': trackCount,
|
|
||||||
'ticksPerBeat': ticksPerBeat
|
|
||||||
}
|
|
||||||
var tracks = [];
|
|
||||||
for (var i = 0; i < header.trackCount; i++) {
|
|
||||||
tracks[i] = [];
|
|
||||||
var trackChunk = readChunk(stream);
|
|
||||||
if (trackChunk.id != 'MTrk') {
|
|
||||||
throw "Unexpected chunk - expected MTrk, got "+ trackChunk.id;
|
|
||||||
}
|
|
||||||
var trackStream = Stream(trackChunk.data);
|
|
||||||
while (!trackStream.eof()) {
|
|
||||||
var event = readEvent(trackStream);
|
|
||||||
tracks[i].push(event);
|
|
||||||
//console.log(event);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
'header': header,
|
|
||||||
'tracks': tracks
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function Replayer(midiFile, synth) {
|
|
||||||
var trackStates = [];
|
|
||||||
var beatsPerMinute = 120;
|
|
||||||
var ticksPerBeat = midiFile.header.ticksPerBeat;
|
|
||||||
var channelCount = 16;
|
|
||||||
var channels = [];
|
|
||||||
var nextEventInfo;
|
|
||||||
var samplesToNextEvent;
|
|
||||||
|
|
||||||
function Channel() {
|
|
||||||
|
|
||||||
var generatorsByNote = {};
|
|
||||||
var currentProgram = PianoProgram;
|
|
||||||
|
|
||||||
function noteOn(note, velocity) {
|
|
||||||
if (generatorsByNote[note] && !generatorsByNote[note].released) {
|
|
||||||
/* playing same note before releasing the last one. BOO */
|
|
||||||
generatorsByNote[note].noteOff(); /* TODO: check whether we ought to be passing a velocity in */
|
|
||||||
}
|
|
||||||
generator = currentProgram.createNote(note, velocity);
|
|
||||||
synth.addGenerator(generator);
|
|
||||||
generatorsByNote[note] = generator;
|
|
||||||
}
|
|
||||||
function noteOff(note, velocity) {
|
|
||||||
if (generatorsByNote[note] && !generatorsByNote[note].released) {
|
|
||||||
generatorsByNote[note].noteOff(velocity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function setProgram(programNumber) {
|
|
||||||
currentProgram = PROGRAMS[programNumber] || PianoProgram;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
'noteOn': noteOn,
|
|
||||||
'noteOff': noteOff,
|
|
||||||
'setProgram': setProgram
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function getNextEvent() {
|
|
||||||
var ticksToNextEvent = null;
|
|
||||||
var nextEventTrack = null;
|
|
||||||
var nextEventIndex = null;
|
|
||||||
|
|
||||||
for (var i = 0; i < trackStates.length; i++) {
|
|
||||||
if (
|
|
||||||
trackStates[i].ticksToNextEvent != null
|
|
||||||
&& (ticksToNextEvent == null || trackStates[i].ticksToNextEvent < ticksToNextEvent)
|
|
||||||
) {
|
|
||||||
ticksToNextEvent = trackStates[i].ticksToNextEvent;
|
|
||||||
nextEventTrack = i;
|
|
||||||
nextEventIndex = trackStates[i].nextEventIndex;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (nextEventTrack != null) {
|
|
||||||
/* consume event from that track */
|
|
||||||
var nextEvent = midiFile.tracks[nextEventTrack][nextEventIndex];
|
|
||||||
if (midiFile.tracks[nextEventTrack][nextEventIndex + 1]) {
|
|
||||||
trackStates[nextEventTrack].ticksToNextEvent += midiFile.tracks[nextEventTrack][nextEventIndex + 1].deltaTime;
|
|
||||||
} else {
|
|
||||||
trackStates[nextEventTrack].ticksToNextEvent = null;
|
|
||||||
}
|
|
||||||
trackStates[nextEventTrack].nextEventIndex += 1;
|
|
||||||
/* advance timings on all tracks by ticksToNextEvent */
|
|
||||||
for (var i = 0; i < trackStates.length; i++) {
|
|
||||||
if (trackStates[i].ticksToNextEvent != null) {
|
|
||||||
trackStates[i].ticksToNextEvent -= ticksToNextEvent
|
|
||||||
}
|
|
||||||
}
|
|
||||||
nextEventInfo = {
|
|
||||||
'ticksToEvent': ticksToNextEvent,
|
|
||||||
'event': nextEvent,
|
|
||||||
'track': nextEventTrack
|
|
||||||
}
|
|
||||||
var beatsToNextEvent = ticksToNextEvent / ticksPerBeat;
|
|
||||||
var secondsToNextEvent = beatsToNextEvent / (beatsPerMinute / 60);
|
|
||||||
samplesToNextEvent += secondsToNextEvent * synth.sampleRate;
|
|
||||||
} else {
|
|
||||||
nextEventInfo = null;
|
|
||||||
samplesToNextEvent = null;
|
|
||||||
self.finished = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function generate(samples) {
|
|
||||||
var data = new Array(samples*2);
|
|
||||||
var samplesRemaining = samples;
|
|
||||||
var dataOffset = 0;
|
|
||||||
|
|
||||||
while (true) {
|
|
||||||
if (samplesToNextEvent != null && samplesToNextEvent <= samplesRemaining) {
|
|
||||||
/* generate samplesToNextEvent samples, process event and repeat */
|
|
||||||
var samplesToGenerate = Math.ceil(samplesToNextEvent);
|
|
||||||
if (samplesToGenerate > 0) {
|
|
||||||
synth.generateIntoBuffer(samplesToGenerate, data, dataOffset);
|
|
||||||
dataOffset += samplesToGenerate * 2;
|
|
||||||
samplesRemaining -= samplesToGenerate;
|
|
||||||
samplesToNextEvent -= samplesToGenerate;
|
|
||||||
}
|
|
||||||
|
|
||||||
handleEvent();
|
|
||||||
getNextEvent();
|
|
||||||
} else {
|
|
||||||
/* generate samples to end of buffer */
|
|
||||||
if (samplesRemaining > 0) {
|
|
||||||
synth.generateIntoBuffer(samplesRemaining, data, dataOffset);
|
|
||||||
samplesToNextEvent -= samplesRemaining;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
function handleEvent() {
|
|
||||||
var event = nextEventInfo.event;
|
|
||||||
switch (event.type) {
|
|
||||||
case 'meta':
|
|
||||||
switch (event.subtype) {
|
|
||||||
case 'setTempo':
|
|
||||||
beatsPerMinute = 60000000 / event.microsecondsPerBeat
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case 'channel':
|
|
||||||
switch (event.subtype) {
|
|
||||||
case 'noteOn':
|
|
||||||
channels[event.channel].noteOn(event.noteNumber, event.velocity);
|
|
||||||
break;
|
|
||||||
case 'noteOff':
|
|
||||||
channels[event.channel].noteOff(event.noteNumber, event.velocity);
|
|
||||||
break;
|
|
||||||
case 'programChange':
|
|
||||||
//console.log('program change to ' + event.programNumber);
|
|
||||||
channels[event.channel].setProgram(event.programNumber);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function reset() {
|
|
||||||
for (var i = 0; i < midiFile.tracks.length; i++) {
|
|
||||||
trackStates[i] = {
|
|
||||||
'nextEventIndex': 0,
|
|
||||||
'ticksToNextEvent': (
|
|
||||||
midiFile.tracks[i].length ?
|
|
||||||
midiFile.tracks[i][0].deltaTime :
|
|
||||||
null
|
|
||||||
)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
for (var i = 0; i < channelCount; i++) {
|
|
||||||
channels[i] = Channel();
|
|
||||||
}
|
|
||||||
samplesToNextEvent = 0;
|
|
||||||
getNextEvent();
|
|
||||||
}
|
|
||||||
|
|
||||||
reset();
|
|
||||||
|
|
||||||
var self = {
|
|
||||||
'reset': reset,
|
|
||||||
'generate': generate,
|
|
||||||
'finished': false
|
|
||||||
}
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
/* Wrapper for accessing strings through sequential reads */
|
|
||||||
function Stream(str) {
|
|
||||||
var position = 0;
|
|
||||||
|
|
||||||
function read(length) {
|
|
||||||
var result = str.substr(position, length);
|
|
||||||
position += length;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* read a big-endian 32-bit integer */
|
|
||||||
function readInt32() {
|
|
||||||
var result = (
|
|
||||||
(str.charCodeAt(position) << 24)
|
|
||||||
+ (str.charCodeAt(position + 1) << 16)
|
|
||||||
+ (str.charCodeAt(position + 2) << 8)
|
|
||||||
+ str.charCodeAt(position + 3));
|
|
||||||
position += 4;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* read a big-endian 16-bit integer */
|
|
||||||
function readInt16() {
|
|
||||||
var result = (
|
|
||||||
(str.charCodeAt(position) << 8)
|
|
||||||
+ str.charCodeAt(position + 1));
|
|
||||||
position += 2;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* read an 8-bit integer */
|
|
||||||
function readInt8(signed) {
|
|
||||||
var result = str.charCodeAt(position);
|
|
||||||
if (signed && result > 127) result -= 256;
|
|
||||||
position += 1;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
function eof() {
|
|
||||||
return position >= str.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* read a MIDI-style variable-length integer
|
|
||||||
(big-endian value in groups of 7 bits,
|
|
||||||
with top bit set to signify that another byte follows)
|
|
||||||
*/
|
|
||||||
function readVarInt() {
|
|
||||||
var result = 0;
|
|
||||||
while (true) {
|
|
||||||
var b = readInt8();
|
|
||||||
if (b & 0x80) {
|
|
||||||
result += (b & 0x7f);
|
|
||||||
result <<= 7;
|
|
||||||
} else {
|
|
||||||
/* b is the last byte */
|
|
||||||
return result + b;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
'eof': eof,
|
|
||||||
'read': read,
|
|
||||||
'readInt32': readInt32,
|
|
||||||
'readInt16': readInt16,
|
|
||||||
'readInt8': readInt8,
|
|
||||||
'readVarInt': readVarInt
|
|
||||||
}
|
|
||||||
}
|
|
||||||
function SineGenerator(freq) {
|
|
||||||
var self = {'alive': true};
|
|
||||||
var period = sampleRate / freq;
|
|
||||||
var t = 0;
|
|
||||||
|
|
||||||
self.generate = function(buf, offset, count) {
|
|
||||||
for (; count; count--) {
|
|
||||||
var phase = t / period;
|
|
||||||
var result = Math.sin(phase * 2 * Math.PI);
|
|
||||||
buf[offset++] += result;
|
|
||||||
buf[offset++] += result;
|
|
||||||
t++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
function SquareGenerator(freq, phase) {
|
|
||||||
var self = {'alive': true};
|
|
||||||
var period = sampleRate / freq;
|
|
||||||
var t = 0;
|
|
||||||
|
|
||||||
self.generate = function(buf, offset, count) {
|
|
||||||
for (; count; count--) {
|
|
||||||
var result = ( (t / period) % 1 > phase ? 1 : -1);
|
|
||||||
buf[offset++] += result;
|
|
||||||
buf[offset++] += result;
|
|
||||||
t++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
function ADSRGenerator(child, attackAmplitude, sustainAmplitude, attackTimeS, decayTimeS, releaseTimeS) {
|
|
||||||
var self = {'alive': true}
|
|
||||||
var attackTime = sampleRate * attackTimeS;
|
|
||||||
var decayTime = sampleRate * (attackTimeS + decayTimeS);
|
|
||||||
var decayRate = (attackAmplitude - sustainAmplitude) / (decayTime - attackTime);
|
|
||||||
var releaseTime = null; /* not known yet */
|
|
||||||
var endTime = null; /* not known yet */
|
|
||||||
var releaseRate = sustainAmplitude / (sampleRate * releaseTimeS);
|
|
||||||
var t = 0;
|
|
||||||
|
|
||||||
self.noteOff = function() {
|
|
||||||
if (self.released) return;
|
|
||||||
releaseTime = t;
|
|
||||||
self.released = true;
|
|
||||||
endTime = releaseTime + sampleRate * releaseTimeS;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.generate = function(buf, offset, count) {
|
|
||||||
if (!self.alive) return;
|
|
||||||
var input = new Array(count * 2);
|
|
||||||
for (var i = 0; i < count*2; i++) {
|
|
||||||
input[i] = 0;
|
|
||||||
}
|
|
||||||
child.generate(input, 0, count);
|
|
||||||
|
|
||||||
childOffset = 0;
|
|
||||||
while(count) {
|
|
||||||
if (releaseTime != null) {
|
|
||||||
if (t < endTime) {
|
|
||||||
/* release */
|
|
||||||
while(count && t < endTime) {
|
|
||||||
var ampl = sustainAmplitude - releaseRate * (t - releaseTime);
|
|
||||||
buf[offset++] += input[childOffset++] * ampl;
|
|
||||||
buf[offset++] += input[childOffset++] * ampl;
|
|
||||||
t++;
|
|
||||||
count--;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
/* dead */
|
|
||||||
self.alive = false;
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else if (t < attackTime) {
|
|
||||||
/* attack */
|
|
||||||
while(count && t < attackTime) {
|
|
||||||
var ampl = attackAmplitude * t / attackTime;
|
|
||||||
buf[offset++] += input[childOffset++] * ampl;
|
|
||||||
buf[offset++] += input[childOffset++] * ampl;
|
|
||||||
t++;
|
|
||||||
count--;
|
|
||||||
}
|
|
||||||
} else if (t < decayTime) {
|
|
||||||
/* decay */
|
|
||||||
while(count && t < decayTime) {
|
|
||||||
var ampl = attackAmplitude - decayRate * (t - attackTime);
|
|
||||||
buf[offset++] += input[childOffset++] * ampl;
|
|
||||||
buf[offset++] += input[childOffset++] * ampl;
|
|
||||||
t++;
|
|
||||||
count--;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
/* sustain */
|
|
||||||
while(count) {
|
|
||||||
buf[offset++] += input[childOffset++] * sustainAmplitude;
|
|
||||||
buf[offset++] += input[childOffset++] * sustainAmplitude;
|
|
||||||
t++;
|
|
||||||
count--;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return self;
|
|
||||||
}
|
|
||||||
|
|
||||||
function midiToFrequency(note) {
|
|
||||||
return 440 * Math.pow(2, (note-69)/12);
|
|
||||||
}
|
|
||||||
|
|
||||||
PianoProgram = {
|
|
||||||
'attackAmplitude': 0.2,
|
|
||||||
'sustainAmplitude': 0.1,
|
|
||||||
'attackTime': 0.02,
|
|
||||||
'decayTime': 0.3,
|
|
||||||
'releaseTime': 0.02,
|
|
||||||
'createNote': function(note, velocity) {
|
|
||||||
var frequency = midiToFrequency(note);
|
|
||||||
return ADSRGenerator(
|
|
||||||
SineGenerator(frequency),
|
|
||||||
this.attackAmplitude * (velocity / 128), this.sustainAmplitude * (velocity / 128),
|
|
||||||
this.attackTime, this.decayTime, this.releaseTime
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
StringProgram = {
|
|
||||||
'createNote': function(note, velocity) {
|
|
||||||
var frequency = midiToFrequency(note);
|
|
||||||
return ADSRGenerator(
|
|
||||||
SineGenerator(frequency),
|
|
||||||
0.5 * (velocity / 128), 0.2 * (velocity / 128),
|
|
||||||
0.4, 0.8, 0.4
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
PROGRAMS = {
|
|
||||||
41: StringProgram,
|
|
||||||
42: StringProgram,
|
|
||||||
43: StringProgram,
|
|
||||||
44: StringProgram,
|
|
||||||
45: StringProgram,
|
|
||||||
46: StringProgram,
|
|
||||||
47: StringProgram,
|
|
||||||
49: StringProgram,
|
|
||||||
50: StringProgram
|
|
||||||
};
|
|
||||||
|
|
||||||
function Synth(sampleRate) {
|
|
||||||
|
|
||||||
var generators = [];
|
|
||||||
|
|
||||||
function addGenerator(generator) {
|
|
||||||
generators.push(generator);
|
|
||||||
}
|
|
||||||
|
|
||||||
function generate(samples) {
|
|
||||||
var data = new Array(samples*2);
|
|
||||||
generateIntoBuffer(samples, data, 0);
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
function generateIntoBuffer(samplesToGenerate, buffer, offset) {
|
|
||||||
for (var i = offset; i < offset + samplesToGenerate * 2; i++) {
|
|
||||||
buffer[i] = 0;
|
|
||||||
}
|
|
||||||
for (var i = generators.length - 1; i >= 0; i--) {
|
|
||||||
generators[i].generate(buffer, offset, samplesToGenerate);
|
|
||||||
if (!generators[i].alive) generators.splice(i, 1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
'sampleRate': sampleRate,
|
|
||||||
'addGenerator': addGenerator,
|
|
||||||
'generate': generate,
|
|
||||||
'generateIntoBuffer': generateIntoBuffer
|
|
||||||
}
|
|
||||||
}
|
|
||||||
1
libs/thirdparty/mid.min.js
vendored
1
libs/thirdparty/mid.min.js
vendored
File diff suppressed because one or more lines are too long
Loading…
Reference in New Issue
Block a user