MIRROR: javascript for ๐Ÿœ's, a tiny runtime with big ambitions
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

support multiple input files via command line argument

+220 -2
+218
examples/midi2bells.js
··· 1 + import { readFile } from 'node:fs'; 2 + 3 + const NOTE_NAMES = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B']; 4 + 5 + function midiToNote(midi) { 6 + const octave = Math.floor(midi / 12) - 1; 7 + const name = NOTE_NAMES[midi % 12]; 8 + return { name, octave, str: `${name}${octave}` }; 9 + } 10 + 11 + function readVarLen(buf, pos) { 12 + let value = 0; 13 + let byte; 14 + do { 15 + byte = buf[pos++]; 16 + value = (value << 7) | (byte & 0x7f); 17 + } while (byte & 0x80); 18 + return { value, pos }; 19 + } 20 + 21 + function parseMidi(buf) { 22 + let pos = 0; 23 + 24 + const headerChunk = String.fromCharCode(buf[0], buf[1], buf[2], buf[3]); 25 + if (headerChunk !== 'MThd') throw new Error('Not a MIDI file'); 26 + const format = (buf[8] << 8) | buf[9]; 27 + const numTracks = (buf[10] << 8) | buf[11]; 28 + const division = (buf[12] << 8) | buf[13]; 29 + pos = 14; 30 + 31 + console.log(`Format: ${format}, Tracks: ${numTracks}, Division: ${division}`); 32 + 33 + const tracks = []; 34 + let tempo = 500000; 35 + 36 + for (let t = 0; t < numTracks; t++) { 37 + const chunkType = String.fromCharCode(buf[pos], buf[pos + 1], buf[pos + 2], buf[pos + 3]); 38 + pos += 4; 39 + const chunkLen = (buf[pos] << 24) | (buf[pos + 1] << 16) | (buf[pos + 2] << 8) | buf[pos + 3]; 40 + pos += 4; 41 + 42 + if (chunkType !== 'MTrk') { 43 + pos += chunkLen; 44 + continue; 45 + } 46 + 47 + const endPos = pos + chunkLen; 48 + const events = []; 49 + let absTick = 0; 50 + let runningStatus = 0; 51 + let trackName = `Track ${t}`; 52 + 53 + while (pos < endPos) { 54 + const delta = readVarLen(buf, pos); 55 + absTick += delta.value; 56 + pos = delta.pos; 57 + 58 + let status = buf[pos]; 59 + if (status < 0x80) { 60 + status = runningStatus; 61 + } else { 62 + runningStatus = status; 63 + pos++; 64 + } 65 + 66 + const type = status & 0xf0; 67 + const channel = status & 0x0f; 68 + 69 + if (type === 0x90) { 70 + const note = buf[pos++]; 71 + const velocity = buf[pos++]; 72 + if (velocity > 0) { 73 + events.push({ type: 'on', tick: absTick, note, velocity, channel }); 74 + } else { 75 + events.push({ type: 'off', tick: absTick, note, channel }); 76 + } 77 + } else if (type === 0x80) { 78 + const note = buf[pos++]; 79 + pos++; 80 + events.push({ type: 'off', tick: absTick, note, channel }); 81 + } else if (type === 0xa0 || type === 0xb0 || type === 0xe0) { 82 + pos += 2; 83 + } else if (type === 0xc0 || type === 0xd0) { 84 + pos += 1; 85 + } else if (status === 0xff) { 86 + const metaType = buf[pos++]; 87 + const len = readVarLen(buf, pos); 88 + pos = len.pos; 89 + if (metaType === 0x03) { 90 + trackName = ''; 91 + for (let i = 0; i < len.value; i++) trackName += String.fromCharCode(buf[pos + i]); 92 + } else if (metaType === 0x51 && len.value === 3) { 93 + tempo = (buf[pos] << 16) | (buf[pos + 1] << 8) | buf[pos + 2]; 94 + } 95 + pos += len.value; 96 + } else if (status === 0xf0 || status === 0xf7) { 97 + const len = readVarLen(buf, pos); 98 + pos = len.pos + len.value; 99 + } 100 + } 101 + 102 + tracks.push({ name: trackName, events }); 103 + } 104 + 105 + return { format, numTracks, division, tracks, tempo }; 106 + } 107 + 108 + function buildNoteList(track) { 109 + const notes = []; 110 + const pending = new Map(); 111 + 112 + for (const ev of track.events) { 113 + if (ev.type === 'on') { 114 + pending.set(ev.note, ev.tick); 115 + } else if (ev.type === 'off' && pending.has(ev.note)) { 116 + const startTick = pending.get(ev.note); 117 + const duration = ev.tick - startTick; 118 + const info = midiToNote(ev.note); 119 + notes.push({ ...info, startTick, duration, midi: ev.note }); 120 + pending.delete(ev.note); 121 + } 122 + } 123 + 124 + notes.sort((a, b) => a.startTick - b.startTick || a.midi - b.midi); 125 + return notes; 126 + } 127 + 128 + function notesToBells(notes, division) { 129 + const eighth = division / 2; 130 + let bells = ''; 131 + let currentTick = 0; 132 + 133 + for (let i = 0; i < notes.length; i++) { 134 + const note = notes[i]; 135 + 136 + const chord = [note]; 137 + while (i + 1 < notes.length && notes[i + 1].startTick === note.startTick) { 138 + chord.push(notes[++i]); 139 + } 140 + 141 + const gap = note.startTick - currentTick; 142 + const restCount = Math.round(gap / eighth); 143 + for (let r = 0; r < restCount; r++) bells += '/'; 144 + 145 + const maxDur = Math.max(...chord.map(n => n.duration)); 146 + const eighths = Math.max(1, Math.round(maxDur / eighth)); 147 + const tildes = Math.max(0, eighths - 1); 148 + 149 + if (chord.length > 1) { 150 + bells += '['; 151 + for (const n of chord) { 152 + bells += n.str; 153 + } 154 + for (let t = 0; t < tildes; t++) bells += '~'; 155 + bells += ']'; 156 + } else { 157 + bells += note.str; 158 + for (let t = 0; t < tildes; t++) bells += '~'; 159 + } 160 + 161 + currentTick = note.startTick + maxDur; 162 + } 163 + 164 + return bells; 165 + } 166 + 167 + async function main() { 168 + const path = process.argv[2]; 169 + if (!path) { 170 + console.error('Usage: ant midi2bells.js <path-to-midi>'); 171 + process.exit(1); 172 + } 173 + 174 + const buf = await readFile(path); 175 + const data = new Uint8Array(buf); 176 + const midi = parseMidi(data); 177 + 178 + console.log('\n=== Tracks ==='); 179 + for (const track of midi.tracks) { 180 + const noteEvents = track.events.filter(e => e.type === 'on'); 181 + console.log(`${track.name}: ${noteEvents.length} note-on events`); 182 + } 183 + 184 + const melodicTracks = midi.tracks.filter(t => { 185 + const notes = t.events.filter(e => e.type === 'on' && e.channel !== 9); 186 + return notes.length > 0; 187 + }); 188 + 189 + console.log(`\nMelodic tracks: ${melodicTracks.map(t => t.name).join(', ')}`); 190 + 191 + const bellParts = []; 192 + for (const track of melodicTracks) { 193 + const filteredEvents = track.events.filter(e => e.channel !== 9); 194 + const notes = buildNoteList({ events: filteredEvents }, midi.division); 195 + if (notes.length === 0) continue; 196 + const bells = notesToBells(notes, midi.division); 197 + console.log(`\n--- ${track.name} (${notes.length} notes) ---`); 198 + console.log(bells.substring(0, 200) + (bells.length > 200 ? '...' : '')); 199 + bellParts.push({ name: track.name, bells }); 200 + } 201 + 202 + const order = ['square', 'overdrive', 'bass', 'organ', 'brass']; 203 + const sorted = []; 204 + for (const name of order) { 205 + const found = bellParts.find(p => p.name.toLowerCase().includes(name)); 206 + if (found) sorted.push(found); 207 + } 208 + for (const p of bellParts) { 209 + if (!sorted.includes(p)) sorted.push(p); 210 + } 211 + 212 + const bpm = Math.round(60000000 / midi.tempo); 213 + const combined = `${bpm}${sorted.map(p => p.bells).join('|')}`; 214 + console.log('\n\n=== FINAL BELL NOTATION ===\n'); 215 + console.log(combined); 216 + } 217 + 218 + main();
+2 -2
src/main.c
··· 285 285 286 286 struct arg_str *eval = arg_str0("e", "eval", "<script>", "evaluate script"); 287 287 struct arg_lit *print = arg_lit0("p", "print", "evaluate script and print result"); 288 - 289 - struct arg_file *file = arg_file0(NULL, NULL, NULL, NULL); 288 + 289 + struct arg_file *file = arg_filen(NULL, NULL, NULL, 0, argc, NULL); 290 290 struct arg_file *localstorage_file = arg_file0(NULL, "localstorage-file", "<path>", "file path for localStorage persistence"); 291 291 292 292 struct arg_lit *version = arg_lit0("v", "version", "display version information and exit");