Advertisement
Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- const os = require('os');
- const playSong = require('./playSong');
- const sys = require('sys');
- class MidiFile {
- static startSequence = [
- [0x4D, 0x54, 0x68, 0x64],
- [0x4D, 0x54, 0x72, 0x6B],
- [0xFF]
- ];
- static typeDict = {
- 0x00: "Sequence Number",
- 0x01: "Text Event",
- 0x02: "Copyright Notice",
- 0x03: "Sequence/Track Name",
- 0x04: "Instrument Name",
- 0x05: "Lyric",
- 0x06: "Marker",
- 0x07: "Cue Point",
- 0x20: "MIDI Channel Prefix",
- 0x2F: "End of Track",
- 0x51: "Set Tempo",
- 0x54: "SMTPE Offset",
- 0x58: "Time Signature",
- 0x59: "Key Signature",
- 0x7F: "Sequencer-Specific Meta-event",
- 0x21: "Prefix Port",
- 0x20: "Prefix Channel",
- 0x09: "Other text format [0x09]",
- 0x08: "Other text format [0x08]",
- 0x0A: "Other text format [0x0A]",
- 0x0C: "Other text format [0x0C]"
- };
- constructor(midi_file, verbose = false, debug = false) {
- this.verbose = verbose;
- this.debug = debug;
- this.bytes = -1;
- this.headerLength = -1;
- this.headerOffset = 23;
- this.format = -1;
- this.tracks = -1;
- this.division = -1;
- this.divisionType = -1;
- this.itr = 0;
- this.runningStatus = -1;
- this.tempo = 0;
- this.midiRecord_list = [];
- this.record_file = "midiRecord.txt";
- this.midi_file = midi_file;
- this.deltaTimeStarted = false;
- this.deltaTime = 0;
- this.key_press_count = 0;
- this.virtualPianoScale = Array.from("1!2@34$5%6^78*9(0qQwWeErtTyYuiIoOpPasSdDfgGhHjJklLzZxcCvVbBnm");
- this.startCounter = Array(MidiFile.startSequence.length).fill(0);
- this.runningStatusSet = false;
- this.events = [];
- this.notes = [];
- this.success = false;
- console.log("Processing", midi_file);
- try {
- const fs = require('fs');
- this.bytes = fs.readFileSync(this.midi_file);
- this.readEvents();
- console.log(this.key_press_count, "notes processed");
- this.clean_notes();
- this.success = true;
- } finally {
- this.save_record(this.record_file);
- }
- }
- checkStartSequence() {
- for (let i = 0; i < this.startSequence.length; i++) {
- if (this.startSequence[i].length === this.startCounter[i]) {
- return true;
- }
- }
- return false;
- }
- skip(i) {
- this.itr += i;
- }
- readLength() {
- let contFlag = true;
- let length = 0;
- while (contFlag) {
- if ((this.bytes[this.itr] & 0x80) >> 7 === 0x1) {
- length = (length << 7) + (this.bytes[this.itr] & 0x7F);
- } else {
- contFlag = false;
- length = (length << 7) + (this.bytes[this.itr] & 0x7F);
- }
- this.itr += 1;
- }
- return length;
- }
- readMTrk() {
- const length = this.getInt(4);
- this.log("MTrk len", length);
- this.readMidiTrackEvent(length);
- }
- readMThd() {
- this.headerLength = this.getInt(4);
- this.log("HeaderLength", this.headerLength);
- this.format = this.getInt(2);
- this.tracks = this.getInt(2);
- const div = this.getInt(2);
- this.divisionType = (div & 0x8000) >> 16;
- this.division = div & 0x7FFF;
- this.log(`Format ${this.format}\nTracks ${this.tracks}\nDivisionType ${this.divisionType}\nDivision ${this.division}`);
- }
- readText(length) {
- let s = "";
- const start = this.itr;
- while (this.itr < length + start) {
- s += String.fromCharCode(this.bytes[this.itr]);
- this.itr += 1;
- }
- return s;
- }
- readMidiMetaEvent(deltaT) {
- const type = this.bytes[this.itr];
- this.itr += 1;
- const length = this.readLength();
- let eventName;
- try {
- eventName = this.typeDict[type];
- } catch {
- eventName = "Unknown Event " + type;
- }
- this.log("MIDIMETAEVENT", eventName, "LENGTH", length, "DT", deltaT);
- if (type === 0x2F) {
- this.log("END TRACK");
- this.itr += 2;
- return false;
- } else if ([0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0C].includes(type)) {
- this.log("\t", this.readText(length));
- } else if (type === 0x51) {
- const tempo = Math.round(60000000 / this.getInt(3));
- this.tempo = tempo;
- this.notes.push([(this.deltaTime / this.division), "tempo=" + tempo]);
- this.log("\tNew tempo is", tempo);
- } else {
- this.itr += length;
- }
- return true;
- }
- readMidiTrackEvent(length) {
- this.log("TRACKEVENT");
- this.deltaTime = 0;
- const start = this.itr;
- let continueFlag = true;
- while (length > this.itr - start && continueFlag) {
- const deltaT = this.readLength();
- this.deltaTime += deltaT;
- if (this.bytes[this.itr] === 0xFF) {
- this.itr += 1;
- continueFlag = this.readMidiMetaEvent(deltaT);
- } else if (this.bytes[this.itr] >= 0xF0 && this.bytes[this.itr] <= 0xF7) {
- this.runningStatusSet = false;
- this.runningStatus = -1;
- this.log("RUNNING STATUS SET:", "CLEARED");
- } else {
- this.readVoiceEvent(deltaT);
- }
- }
- this.log("End of MTrk event, jumping from", this.itr, "to", start + length);
- this.itr = start + length;
- }
- readVoiceEvent(deltaT) {
- let type, channel;
- if (this.bytes[this.itr] < 0x80 && this.runningStatusSet) {
- type = this.runningStatus;
- channel = type & 0x0F;
- } else {
- type = this.bytes[this.itr];
- channel = this.bytes[this.itr] & 0x0F;
- if (type >= 0x80 && type <= 0xF7) {
- this.log("RUNNING STATUS SET:", hex(type));
- this.runningStatus = type;
- this.runningStatusSet = true;
- }
- this.itr += 1;
- }
- if (type >> 4 === 0x9) {
- const key = this.bytes[this.itr];
- this.itr += 1;
- const velocity = this.bytes[this.itr];
- this.itr += 1;
- let map = key - 23 - 12 - 1;
- while (map >= this.virtualPianoScale.length) {
- map -= 12;
- }
- while (map < 0) {
- map += 12;
- }
- if (velocity === 0) {
- this.log(this.deltaTime / this.division, "~" + this.virtualPianoScale[map]);
- this.notes.push([(this.deltaTime / this.division), "~" + this.virtualPianoScale[map]]);
- } else {
- this.log(this.deltaTime / this.division, this.virtualPianoScale[map]);
- this.notes.push([(this.deltaTime / this.division), this.virtualPianoScale[map]]);
- this.key_press_count += 1;
- }
- } else if (type >> 4 === 0x8) {
- const key = this.bytes[this.itr];
- this.itr += 1;
- const velocity = this.bytes[this.itr];
- this.itr += 1;
- let map = key - 23 - 12 - 1;
- while (map >= this.virtualPianoScale.length) {
- map -= 12;
- }
- while (map < 0) {
- map += 12;
- }
- this.log(this.deltaTime / this.division, "~" + this.virtualPianoScale[map]);
- this.notes.push([(this.deltaTime / this.division), "~" + this.virtualPianoScale[map]]);
- } else if (![0x8, 0x9, 0xA, 0xB, 0xD, 0xE].includes(type >> 4)) {
- this.log("VoiceEvent", hex(type), hex(this.bytes[this.itr]), "DT", deltaT);
- this.itr += 1;
- } else {
- this.log("VoiceEvent", hex(type), hex(this.bytes[this.itr]), hex(this.bytes[this.itr + 1]), "DT", deltaT);
- this.itr += 2;
- }
- }
- readEvents() {
- while (this.itr + 1 < this.bytes.length) {
- for (let i = 0; i < this.startCounter.length; i++) {
- this.startCounter[i] = 0;
- }
- while (this.itr + 1 < this.bytes.length && !this.checkStartSequence()) {
- for (let i = 0; i < this.startSequence.length; i++) {
- if (this.bytes[this.itr] === this.startSequence[i][this.startCounter[i]]) {
- this.startCounter[i] += 1;
- } else {
- this.startCounter[i] = 0;
- }
- }
- if (this.itr + 1 < this.bytes.length) {
- this.itr += 1;
- }
- if (this.startCounter[0] === 4) {
- this.readMThd();
- } else if (this.startCounter[1] === 4) {
- this.readMTrk();
- }
- }
- }
- }
- log(...arg) {
- if (this.verbose || this.debug) {
- for (let s = 0; s < arg.length; s++) {
- try {
- process.stdout.write(`${arg[s]} `);
- this.midiRecord_list.push(`${arg[s]} `);
- } catch {
- process.stdout.write("[?] ");
- this.midiRecord_list.push("[?] ");
- }
- }
- console.log();
- if (this.debug) {
- const readlineSync = require('readline-sync');
- readlineSync.question();
- }
- this.midiRecord_list.push("\n");
- } else {
- for (let s = 0; s < arg.length; s++) {
- try {
- this.midiRecord_list.push(`${arg[s]} `);
- } catch {
- this.midiRecord_list.push("[?] ");
- }
- }
- this.midiRecord_list.push("\n");
- }
- }
- getInt(i) {
- let k = 0;
- for (const n of this.bytes.slice(this.itr, this.itr + i)) {
- k = (k << 8) + n;
- }
- this.itr += i;
- return k;
- }
- round(i) {
- const up = Math.floor(i + 1);
- const down = Math.floor(i - 1);
- if (up - i < i - down) {
- return up;
- } else {
- return down;
- }
- }
- clean_notes() {
- this.notes = this.notes.sort((a, b) => parseFloat(a[0]) - parseFloat(b[0]));
- if (this.verbose) {
- for (const x of this.notes) {
- console.log(x);
- }
- }
- let i = 0;
- while (i < this.notes.length - 1) {
- const a_time = this.notes[i][0];
- const b_time = this.notes[i + 1][0];
- if (a_time === b_time) {
- const a_notes = this.notes[i][1];
- const b_notes = this.notes[i + 1][1];
- if (!a_notes.includes("tempo") && !b_notes.includes("tempo") && !a_notes.includes("~") && !b_notes.includes("~")) {
- this.notes[i][1] += this.notes[i + 1][1];
- this.notes.splice(i + 1, 1);
- } else {
- i += 1;
- }
- } else {
- i += 1;
- }
- }
- for (let q = 0; q < this.notes.length; q++) {
- const letterDict = {};
- const newline = [];
- if (!this.notes[q][1].includes("tempo") && !this.notes[q][1].includes("~")) {
- for (let i = 0; i < this.notes[q][1].length; i++) {
- if (!(this.notes[q][1][i] in letterDict)) {
- newline.push(this.notes[q][1][i]);
- letterDict[this.notes[q][1][i]] = true;
- }
- }
- this.notes[q][1] = newline.join("");
- }
- }
- }
- save_song(song_file) {
- console.log("Saving notes to", song_file);
- const fs = require('fs');
- fs.writeFileSync(song_file, `playback_speed=1.1\n${this.notes.map(l => `${l[0]} ${l[1]}`).join("\n")}`);
- }
- save_sheet(sheet_file) {
- console.log("Saving sheets to", sheet_file);
- const offset = this.notes[0][0];
- let noteCount = 0;
- const fs = require('fs');
- const stream = fs.createWriteStream(sheet_file);
- for (const [timing, notes] of this.notes) {
- if (!notes.includes("tempo") && !notes.includes("~")) {
- const note = notes.length > 1 ? `[${notes}]` : notes;
- noteCount += 1;
- stream.write(`${note.padStart(7)} `);
- if (noteCount % 8 === 0) {
- stream.write("\n");
- }
- }
- }
- stream.end();
- }
- save_record(record_file) {
- console.log("Saving processing log to", record_file);
- const fs = require('fs');
- fs.writeFileSync(record_file, this.midiRecord_list.join(""));
- }
- }
- function get_file_choice() {
- const midi_folder = 'midi';
- if (!fs.existsSync(midi_folder)) {
- fs.mkdirSync(midi_folder);
- }
- const midList = fs.readdirSync(midi_folder).filter(f => f.toLowerCase().endsWith('.mid'));
- if (!midList.length) {
- console.log("No MIDI files detected. Please add MIDI files to the 'midi' folder.");
- return null;
- }
- console.log("\nType the number of a MIDI file and press enter:\n");
- for (let i = 0; i < midList.length; i++) {
- console.log(`${i + 1}: ${midList[i]}`);
- }
- try {
- const readlineSync = require('readline-sync');
- const choice = parseInt(readlineSync.question("> "));
- return path.join(midi_folder, midList[choice - 1]);
- } catch (error) {
- console.log("Invalid selection. Please try again.");
- return null;
- }
- }
- function runPlaySong() {
- try {
- playSong.main();
- } catch (error) {
- console.log(`Failed to run playSong.py: ${error}`);
- }
- }
- function main() {
- let midi_file;
- if (process.argv.length > 2) {
- midi_file = process.argv[2];
- if (!fs.existsSync(midi_file)) {
- console.log(`Error: file not found '${midi_file}'`);
- return 1;
- }
- if (![".mid", ".mid"].some(ext => midi_file.toLowerCase().includes(ext))) {
- console.log(`'${midi_file}' has an incorrect file extension`);
- console.log("Make sure this file ends in '.mid'");
- return 1;
- }
- } else {
- midi_file = get_file_choice();
- if (midi_file === null) {
- return 1;
- }
- }
- try {
- const midi = new MidiFile(midi_file);
- const song_file = "song.txt";
- const sheet_file = "sheetConversion.txt";
- midi.save_song(song_file);
- midi.save_sheet(sheet_file);
- runPlaySong();
- } catch (error) {
- console.log("An error has occurred during processing:\n\n");
- throw error;
- return 1;
- }
- }
- main();
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement