添加一些关于midi播放的项目
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
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.
|
||||
@@ -0,0 +1,51 @@
|
||||
# Midifile.js
|
||||
|
||||
This module parses the Standard Midi File.
|
||||
|
||||
The code was modified to adapt to the 'require' way of nodejs. Irrelevant code have been removed.
|
||||
|
||||
The SMF parser part of the jasmid project is used. See jasmid's README and LICENSE for more info
|
||||
|
||||
Jasmid's original repository: https://github.com/gasman/jasmid
|
||||
|
||||
# JASMID Original Readme
|
||||
|
||||
```
|
||||
jasmid - A Javascript MIDI file reader and synthesiser
|
||||
|
||||
Originally presented at BarCamp London 8, 13-14 November 2010
|
||||
|
||||
Instructions:
|
||||
Open index.html in browser. Turn up volume. Click on link.
|
||||
|
||||
Sound output is via one of the following mechanisms, according to what your
|
||||
browser supports:
|
||||
* Mozilla Audio Data API <https://wiki.mozilla.org/Audio_Data_API>
|
||||
* Web Audio API
|
||||
<https://dvcs.w3.org/hg/audio/raw-file/tip/webaudio/specification.html>
|
||||
* a Flash fallback originally taken from dynamicaudio.js by Ben Firshman
|
||||
<https://github.com/bfirsh/dynamicaudio.js> and hacked around by me.
|
||||
|
||||
|
||||
The code:
|
||||
stream.js - helper library for reading a string as a stream of typed data
|
||||
midifile.js - parses the MIDI file format into a header and a list of tracks,
|
||||
each consisting of a list of event objects
|
||||
replayer.js - steps over the data structure generated by midifile.js and calls
|
||||
the appropriate operations on the synthesiser
|
||||
synth.js - audio synthesiser; generates waveforms according to tweakable
|
||||
parameters
|
||||
audio.js - passes the generated waveform to either the Audio Data API or the
|
||||
Flash fallback widget (da.swf)
|
||||
|
||||
|
||||
Limitations:
|
||||
* The only event types supported by replayer.js are note on, note off, tempo
|
||||
change and program change
|
||||
* There are currently only two instrument presets defined in synth.js - one for
|
||||
strings and a 'piano' one for everything else - and neither of them are
|
||||
particularly good (just a single volume-modulated sine wave).
|
||||
|
||||
|
||||
Matt Westcott <[email protected]> - @gasmanic - http://matt.west.co.tt/
|
||||
```
|
||||
@@ -0,0 +1,241 @@
|
||||
// This file has been converted for node 'require'
|
||||
/*
|
||||
class to parse the .mid file format
|
||||
(depends on stream.js)
|
||||
*/
|
||||
var Stream = require("./stream.js");
|
||||
|
||||
module.exports = function(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
|
||||
/*
|
||||
console.log("Unrecognised MIDI event type: " + eventType);
|
||||
stream.readInt8();
|
||||
event.subtype = 'unknown';
|
||||
return event;
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
// This file has been converted for node 'require'
|
||||
|
||||
/* Wrapper for accessing strings through sequential reads */
|
||||
module.exports = function(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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user