PageRenderTime 45ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/WebMIDIAPI/sample01/js/smfParser.js

https://bitbucket.org/ryoyakawai/public
JavaScript | 427 lines | 341 code | 21 blank | 65 comment | 90 complexity | e4a45ff70ab58299e874d57e74cbb153 MD5 | raw file
  1. /**
  2. * Based on T'SoundSystem for JavaScript
  3. */
  4. /**
  5. * Standard MIDI File Parser
  6. *
  7. * Play Standard MIDI Files.
  8. *
  9. */
  10. function SmfParser () {
  11. return {
  12. init: function() {
  13. this.masterChannel = null;
  14. this.defaultDevice = new MidiChannel();
  15. this.devices = [];
  16. this.tracks = [];
  17. this.numberOfTracks = 0;
  18. this.usecTempo = 1000 * 1000;
  19. this.timeUnit = 480;
  20. this.error = false;
  21. this.handleFalcomStyleInfiniteLoop = true;
  22. this.TRACK_DEFAULT = -1;
  23. this._SMF_CHUNK_HEADER = ('M'.charCodeAt(0) << 24) |
  24. ('T'.charCodeAt(0) << 16) |
  25. ('h'.charCodeAt(0) << 8) |
  26. 'd'.charCodeAt(0);
  27. this._SMF_CHUNK_TRACK = ('M'.charCodeAt(0) << 24) |
  28. ('T'.charCodeAt(0) << 16) |
  29. ('r'.charCodeAt(0) << 8) |
  30. 'k'.charCodeAt(0);
  31. this._SMF_FORMAT_0 = 0;
  32. this._SMF_FORMAT_1 = 1;
  33. this._SMF_EVENT_SYSEX = 0xf0;
  34. this._SMF_EVENT_META = 0xff;
  35. this._SMF_META_SEQUENCE_NUMBER = 0x00;
  36. this._SMF_META_TEXT = 0x01;
  37. this._SMF_META_COPYRIGHT = 0x02;
  38. this._SMF_META_TRACK_NAME = 0x03;
  39. this._SMF_META_INSTRUMENT_NAME = 0x04;
  40. this._SMF_META_LYLIC = 0x05;
  41. this._SMF_META_MARKER = 0x06;
  42. this._SMF_META_CUE_POINT = 0x07;
  43. this._SMF_META_CHANNEL_PREFIX = 0x20;
  44. this._SMF_META_END_OF_TRACK = 0x2f;
  45. this._SMF_META_SET_TEMPO = 0x51;
  46. this._SMF_META_SMPTE_OFFSET = 0x54;
  47. this._SMF_META_TIME_SIGNATURE = 0x58;
  48. this._SMF_META_KEY_SIGNATURE = 0x59;
  49. },
  50. /**
  51. * Send MIDI events to a device
  52. * @param track track id
  53. * @param data data array
  54. */
  55. _sendEvent: function(track, data) {
  56. var device = this.devices[track];
  57. if (!device)
  58. device = this.defaultDevice;
  59. if (!device)
  60. return;
  61. // var event = MidiChannel.createEvent(data);
  62. var event = midi.createEvent(data);
  63. device.processEvents(event);
  64. },
  65. /**
  66. * Read unsigned 16bit data.
  67. * @param offset offset
  68. * @return unsigned 16bit data
  69. */
  70. _readUint16: function (offset) {
  71. return (this.input[offset] << 8) |
  72. this.input[offset + 1];
  73. },
  74. /**
  75. * Read unsigned 32bit data.
  76. * @param offset offset
  77. * @return unsigned 32bit data
  78. */
  79. _readUint32: function (offset) {
  80. var i32 = (this.input[offset] << 24) |
  81. (this.input[offset + 1] << 16) |
  82. (this.input[offset + 2] << 8) |
  83. this.input[offset + 3];
  84. if (i32 < 0)
  85. return 0x100000000 + i32;
  86. return i32;
  87. },
  88. /**
  89. * Read variable length unsigned integer data.
  90. * @param offset offset
  91. * @return object
  92. * value: unsigned integer data
  93. * length: variable length
  94. */
  95. _readUint: function (array, offset) {
  96. var n = 0;
  97. var length = array.byteLength;
  98. var success = false;
  99. for (var i = offset; i < length; i++) {
  100. var c = array[i];
  101. if (c < 0x80) {
  102. n += c;
  103. success = true;
  104. i++;
  105. break;
  106. } else {
  107. n += c & 0x7f;
  108. n <<= 7;
  109. }
  110. }
  111. return {
  112. value: n,
  113. length: i - offset,
  114. success: success
  115. };
  116. },
  117. /**
  118. * Read chunk header (8 bytes).
  119. * @param offset offset
  120. * @return object
  121. * type: chunk type
  122. * length: chunk length (except for header size)
  123. */
  124. _readChunkHeader: function (offset) {
  125. var type = this._readUint32(offset);
  126. var length = this._readUint32(offset + 4);
  127. if (this._SMF_CHUNK_HEADER == type)
  128. Log.getLog().info("SMF: MThd (header chunk)");
  129. else if (this._SMF_CHUNK_TRACK == type)
  130. Log.getLog().info("SMF: MTrk (track chunk)");
  131. else
  132. Log.getLog().warn("SMF: " + String.fromCharCode(offset) +
  133. String.fromCharCode(offset + 1) +
  134. String.fromCharCode(offset + 2) +
  135. String.fromCharCode(offset + 3) + " (unknown)");
  136. Log.getLog().info("SMF: length: " + length);
  137. return {
  138. type: type,
  139. length: length
  140. };
  141. },
  142. /**
  143. * Update timer callback.
  144. */
  145. _updateTimer: function () {
  146. var interval = (this.usecTempo / 1000) / this.timeUnit;
  147. Log.getLog().info("SMF: set timer interval as " + interval);
  148. this.masterChannel.setPlayerInterval(interval);
  149. },
  150. /**
  151. * Set master channel to write output stream.
  152. * @param channel master channel
  153. */
  154. setMasterChannel: function (channel) {
  155. if (!channel) {
  156. this.masterChannel = null;
  157. return;
  158. }
  159. channel.clearChannel();
  160. if (this.defaultDevice)
  161. channel.addChannel(this.defaultDevice);
  162. for (var i = 0; i < this.devices.length; i++) {
  163. if (!this.devices[i])
  164. continue;
  165. channel.addChannel(this.devices[i]);
  166. }
  167. channel.setPlayer(this);
  168. this.masterChannel = channel;
  169. },
  170. /**
  171. * Set playback device for each track. If track is SmfPlayer.TRACK_DEFAULT
  172. * the device will be used for tracks which doesn't have specific setting.
  173. * @param track track to set
  174. * @param device device
  175. */
  176. setDevice: function (track, device) {
  177. if (this.TRACK_DEFAULT == track) {
  178. if (this.masterChannel) {
  179. if (this.defaultDevice)
  180. this.masterChannel.removeChannel(this.defaultDevice);
  181. if (device)
  182. this.masterChannel.addChannel(device);
  183. }
  184. this.defaultDevice = device;
  185. } else {
  186. if (this.masterChannel) {
  187. if (this.devices[track])
  188. this.masterChannel.removeChannel(this.devices[track]);
  189. if (device)
  190. this.masterChannel.addChannel(device);
  191. }
  192. this.devices[track] = device;
  193. }
  194. },
  195. /**
  196. * Channel reach to periodical call back point.
  197. */
  198. updateDevice: function () {
  199. if (this.error)
  200. return;
  201. for (var track = 0; track < this.numberOfTracks; track++) {
  202. var work = this.tracks[track];
  203. if (!work.active)
  204. continue;
  205. work.count--;
  206. if (0 != work.count)
  207. continue;
  208. var length = work.data.byteLength;
  209. do {
  210. if (work.offset == length) {
  211. Log.getLog().error("SMF: event not found");
  212. this.error = true;
  213. break;
  214. }
  215. var event = work.data[work.offset];
  216. // Handle running status.
  217. if (event >= 0x80) {
  218. work.lastEvent = event;
  219. } else {
  220. event = work.lastEvent;
  221. work.offset--;
  222. }
  223. var dataLength = 3;
  224. if ((0xc0 <= event) && (event < 0xe0))
  225. dataLength = 2;
  226. if ((work.offset + dataLength) > length) {
  227. Log.getLog().error("SMF: invalid event data at " +
  228. work.offset);
  229. this.error = true;
  230. break;
  231. }
  232. if (this._SMF_EVENT_SYSEX == event) {
  233. dataLength = 2 + work.data[work.offset + 1];
  234. if (work.offset + dataLength > length) {
  235. Log.getLog().error("SMF: invalid sysex data");
  236. this.error = true;
  237. break;
  238. }
  239. } else if (this._SMF_EVENT_META == event) {
  240. var type = work.data[work.offset + 1];
  241. dataLength = 3 + work.data[work.offset + 2];
  242. if (work.offset + dataLength > length) {
  243. Log.getLog().error("SMF: invalid meta data");
  244. this.error = true;
  245. break;
  246. }
  247. if (this._SMF_META_TRACK_NAME == type) {
  248. try {
  249. var text = TString.createFromUint8Array(
  250. work.data.subarray(work.offset + 3,
  251. work.offset + dataLength));
  252. Log.getLog().info("SMF: track name; " +
  253. text.toString());
  254. } catch (e) {
  255. Log.getLog().warn("SMF: track name is not UTF-8 text");
  256. }
  257. } else if (this._SMF_META_MARKER == type) {
  258. try {
  259. var marker = TString.createFromUint8Array(
  260. work.data.subarray(work.offset + 3,
  261. work.offset + dataLength));
  262. Log.getLog().info("SMF: marker; " + marker.toString());
  263. } catch (e) {
  264. Log.getLog().warn("SMF: marker is not UTF-8 text");
  265. }
  266. if (this.handleFalcomStyleInfiniteLoop &&
  267. (1 == work.data[work.offset + 2])) {
  268. var mark = work.data[work.offset + 3];
  269. if (0x41 == mark) {
  270. work.loopOffset = work.offset;
  271. Log.getLog().info("SMF: marker A handled as " +
  272. "infinite loop start in falcom style");
  273. } else if (0x42 == mark) {
  274. work.offset = work.loopOffset;
  275. Log.getLog().info("SMF: marker B handled as " +
  276. "infinite loop end in falcom style");
  277. continue;
  278. }
  279. }
  280. } else if (this._SMF_META_SET_TEMPO == type) {
  281. this.usecTempo = (work.data[work.offset + 3] << 16) |
  282. (work.data[work.offset + 4] << 8) |
  283. work.data[work.offset + 5];
  284. var tempo = ~~(60000000 / this.usecTempo);
  285. Log.getLog().info("SMF: tempo " + tempo);
  286. this._updateTimer();
  287. } else {
  288. Log.getLog().info("SMF: meta type " + type);
  289. }
  290. } else if (0xf0 == (event & 0xf0)) {
  291. // Handle running status.
  292. Log.getLog().error("SMF: unsupported system common message " +
  293. event.toString(16));
  294. this.error = true;
  295. break;
  296. } else if (event < 0x80) {
  297. Log.getLog().warn("SMF: skip invalid data " +
  298. event.toString(16));
  299. work.offset++;
  300. continue;
  301. }
  302. if (this._SMF_EVENT_META != event) {
  303. var eventData = new Uint8Array(dataLength);
  304. eventData[0] = event;
  305. for (var offset = 1; offset < dataLength; offset++)
  306. eventData[offset] = work.data[work.offset + offset];
  307. this._sendEvent(track, eventData);
  308. }
  309. work.offset += dataLength;
  310. var deltaTime = this._readUint(work.data, work.offset);
  311. if (!deltaTime.success) {
  312. if (0 == deltaTime.length) {
  313. Log.getLog().info("SMF: track " + track + " end");
  314. work.active = false;
  315. } else {
  316. Log.getLog().error("SMF: invalid delta time");
  317. this.error = true;
  318. }
  319. break;
  320. }
  321. work.count = deltaTime.value;
  322. work.offset += deltaTime.length;
  323. } while (0 == work.count);
  324. }
  325. },
  326. /**
  327. * Decode and play.
  328. * @param newInput ArrayBuffer to play
  329. * @return success or not
  330. */
  331. play: function (newInput) {
  332. this.input = new Uint8Array(newInput);
  333. var length = this.input.byteLength;
  334. Log.getLog().info("SMF: load " + length + " Bytes");
  335. var totalLength = 0;
  336. var headerProcessed = false;
  337. var processedTracks = 0;
  338. for (var offset = 0; offset < length; offset += totalLength) {
  339. var chunkHeader = this._readChunkHeader(offset);
  340. offset += 8;
  341. if (this._SMF_CHUNK_HEADER == chunkHeader.type) {
  342. if (headerProcessed) {
  343. Log.getLog().error("SMF: header chunks appear twice");
  344. return false;
  345. }
  346. headerProcessed = true;
  347. var format = this._readUint16(offset);
  348. this.numberOfTracks = this._readUint16(offset + 2);
  349. this.timeUnit = this._readUint16(offset + 4);
  350. if (6 != chunkHeader.length)
  351. Log.getLog().warn("SMF: invalid chunk header length: " +
  352. chunkHeader.length);
  353. Log.getLog().info("SMF: format " + format);
  354. Log.getLog().info("SMF: tracks " + this.numberOfTracks);
  355. Log.getLog().info("SMF: time unit " + this.timeUnit);
  356. if (this._SMF_FORMAT_0 == format) {
  357. if (1 != this.numberOfTracks) {
  358. Log.getLog().warn("SMF: invalid track number in format 0");
  359. this.numberOfTracks = 1;
  360. }
  361. }
  362. } else if (this._SMF_CHUNK_TRACK == chunkHeader.type) {
  363. Log.getLog().info("SMF: track chunk " + processedTracks);
  364. if (processedTracks >= this.numberOfTracks) {
  365. Log.getLog().error("SMF: too many tracks");
  366. return false;
  367. }
  368. var subArray =
  369. this.input.subarray(offset, offset + chunkHeader.length);
  370. var deltaTime = this._readUint(subArray, 0);
  371. if (!deltaTime.success) {
  372. Log.getLog().error("SMF: track " + processedTracks +
  373. " has invalid data");
  374. return false;
  375. }
  376. Log.getLog().info("SMF: track " + processedTracks + " initial " +
  377. "delta time " + deltaTime.value);
  378. this.tracks[processedTracks++] = {
  379. data: subArray,
  380. offset: deltaTime.length,
  381. count: deltaTime.value + 1,
  382. lastEvent: 0,
  383. loopOffset: 0,
  384. active: true
  385. };
  386. }
  387. offset += chunkHeader.length;
  388. }
  389. if (headerProcessed) {
  390. if (!this.masterChannel) {
  391. Log.getLog().error("SMF: master channel is not set");
  392. return false;
  393. }
  394. this._updateTimer();
  395. }
  396. this.error = !headerProcessed;
  397. return headerProcessed;
  398. },
  399. /**
  400. * Stop playback.
  401. */
  402. stop: function () {
  403. this.error = true;
  404. }
  405. };
  406. }