-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Yisheng Jiang
committed
Mar 20, 2023
1 parent
00914c7
commit 0558a47
Showing
7 changed files
with
400 additions
and
65 deletions.
There are no files selected for viewing
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,7 @@ | ||
import { mkcanvas, chart } from "https://unpkg.com/[email protected]/chart.js"; | ||
import { mkdiv } from "https://unpkg.com/[email protected]/mkdiv.js"; | ||
import { SpinNode } from "./spin/spin.js"; | ||
import mkpath from "./src/path.js"; | ||
import { mkpath } from "./src/path.js"; | ||
import SF2Service from "./sf2-service/index.js"; | ||
const sf2url = "file.sf2"; | ||
|
||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,9 +1,7 @@ | ||
import { mkdiv, logdiv, mkdiv2 } from "https://unpkg.com/[email protected]/mkdiv.js"; | ||
|
||
import { SpinNode } from "../spin/spin.js"; | ||
import { mkui } from "./ui.js"; | ||
import SF2Service from "https://unpkg.com/[email protected]/index.js"; | ||
import { fetchmidilist, fetchSF2List } from "./midilist.js"; | ||
import { fetchmidilist } from "./midilist.js"; | ||
import { mkeventsPipe } from "./mkeventsPipe.js"; | ||
import { createChannel } from "./createChannel.js"; | ||
import { midi_ch_cmds, range } from "./constants.js"; | ||
|
@@ -116,14 +114,11 @@ async function main(sf2file, midifile) { | |
style: "width:300px", | ||
value: midifile, | ||
onchange: (e) => { | ||
document.location.href = `?midifile=${e.target.value}&sf2file=${sf2file}`; | ||
midiworker.postMessage({ cmd: "load", url: e.target.value }); | ||
e.preventDefault(); | ||
}, | ||
children: midiList.map((f) => | ||
mkdiv( | ||
"option", | ||
{ value: f.get("Url"), seleced: f.get("Url").includes(midifile) }, | ||
f.get("Name").substring(0, 80) | ||
) | ||
mkdiv("option", { value: f.get("Url") }, f.get("Name").substring(0, 80)) | ||
), | ||
}); | ||
midiSelect.attachTo(msel); | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
import { readMidi } from "./midiread.js"; | ||
|
||
export function scheduler(midi_u8, cb) { | ||
return scheduleMidiPlayer(readMidi(midi_u8), cb); | ||
} | ||
export function scheduleMidiPlayer(midiInfo, cb) { | ||
const { tempos, tracks, division, presets, ntracks } = readMidi(midiInfo); | ||
|
||
const tp_qn = division; // ticks per quarter node, | ||
let msqn = tempos?.[0]?.tempo || 500000; // ms per qn; | ||
let timeSignature = 4; | ||
let qn = 0; | ||
|
||
const totalTicks = tracks | ||
.map((t) => t[t.length - 1]) | ||
.reduce((lastEvent, eventt) => Math.max(eventt.t, lastEvent), 0); | ||
let currentTick = 0; | ||
let clockTime = 0; | ||
let paused = false; | ||
|
||
const trackEventIndex = new Array(ntracks).fill(0); | ||
const clockTimeMap = []; | ||
const markClockTime = () => clockTimeMap.push([clockTime, currentTick]); | ||
|
||
timeSignature = (newevent.timeSignature[0] / newevent.timeSignature[1]) * 4; | ||
|
||
async function run() { | ||
paused = false; | ||
while (currentTick < totalTicks) { | ||
playToCurrentTick(cb); | ||
if (paused) break; | ||
await sleepTillNextTimeInterval(); | ||
updateTempoAndTime(newEvent); | ||
} | ||
cb({ eof: 1 }); | ||
} | ||
|
||
function updateTempoAndTime(newEvent) { | ||
if (newEvent.tempos) return; | ||
if (tempos && tempos.length > 1 && currentTick >= tempos[0].t) { | ||
tempos.shift(); | ||
msqn = tempos[0].tempo; | ||
cb({ tempo: 60 / (msqn / 1e6) }); | ||
} | ||
if ((timeSignature = event.timeSignature)) { | ||
(timeSignature[0] / timeSignature[1]) * 4; | ||
} | ||
} | ||
|
||
function playToCurrentTick(callBack) { | ||
for (let i in tracks) { | ||
const track = tracks[i]; | ||
if (!track.length) continue; | ||
for ( | ||
let idx = trackEventIndex[i], nextEvent = track[idx]; | ||
nextEvent?.t < currentTick; | ||
idx++, nextEvent = track[idx] | ||
) { | ||
callBack(nextEvent); | ||
updateTempoAndTime(nextEvent); | ||
} | ||
} | ||
} | ||
|
||
async function sleepTillNextTimeInterval() { | ||
const intervalMillisecond = msqn / 1000 / timeSignature; | ||
await new Promise((resolve) => setTimeout(resolve, intervalMillisecond)); | ||
currentTick += tp_qn / timeSignature; | ||
qn++; | ||
clockTime += intervalMillisecond; | ||
cb({ clockTime, qn, tick: currentTick }); | ||
} | ||
|
||
function rwd(miliseconds) { | ||
const target = clockTime - miliseconds; | ||
for (const [ct, tick] of markClockTime) { | ||
if (ct <= target) { | ||
currentTick = tick; | ||
clockTime = ct; | ||
} | ||
} | ||
} | ||
function pause() { | ||
paused = true; | ||
} | ||
function resume() { | ||
paused = false; | ||
run(); | ||
} | ||
return { | ||
ctrls: { pause, rwd, run, resume }, | ||
tracks, | ||
ntracks, | ||
presets, | ||
totalTicks, | ||
}; | ||
} | ||
6; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,149 @@ | ||
export function readMidi(buffer) { | ||
const reader = bufferReader2(buffer); | ||
const { fgetc, btoa, read24, readString, read32, readVarLength, read16 } = | ||
reader; | ||
const chunkType = [btoa(), btoa(), btoa(), btoa()].join(""); | ||
const headerLength = read32(); | ||
const format = read16(); | ||
const ntracks = read16(); | ||
const division = read16(); | ||
const tracks = []; | ||
const limit = buffer.byteLength; | ||
let lasttype; | ||
function readNextEvent() { | ||
const { fgetc, read24, readString, read32, readVarLength, read16 } = reader; | ||
let type = fgetc(); | ||
if (type == null) return []; | ||
if ((type & 0xf0) == 0xf0) { | ||
switch (type) { | ||
case 0xff: { | ||
const meta = fgetc(); | ||
const len = readVarLength(); | ||
switch (meta) { | ||
case 0x21: | ||
return { port: fgetc() }; | ||
case 0x51: | ||
return { tempo: read24() }; | ||
case 0x59: | ||
return { meta, payload: [fgetc(), fgetc()] }; | ||
default: | ||
return { meta, payload: readString(len), len }; | ||
} | ||
} | ||
case 0xf0: | ||
case 0xf7: | ||
return { sysex: readString(readVarLength()) }; | ||
default: | ||
return { type, system: readString(readVarLength()) }; | ||
} | ||
} else { | ||
let param; | ||
if (0 === (type & 0x80)) { | ||
param = type; | ||
type = lasttype; | ||
} else { | ||
param = fgetc(); | ||
lasttype = type; | ||
} | ||
switch (type >> 4) { | ||
case 0x0c: | ||
case 0x0d: | ||
return { | ||
ch: type & 0x0f, | ||
cmd: (type >> 4).toString(16), | ||
channel: [type, param, 0], | ||
}; | ||
default: | ||
return { | ||
ch: type & 0x0f, | ||
cmd: (type >> 4).toString(16), | ||
channel: [type, param, fgetc()], | ||
}; | ||
} | ||
} | ||
} | ||
const presets = []; | ||
const tempos = []; | ||
while (reader.offset < limit) { | ||
fgetc(), fgetc(), fgetc(), fgetc(); | ||
let t = 0; | ||
const mhrkLength = read32(); | ||
const endofTrack = reader.offset + mhrkLength; | ||
const track = []; | ||
while (reader.offset < limit && reader.offset < endofTrack) { | ||
const delay = readVarLength(); | ||
const nextEvent = readNextEvent(); | ||
if (!nextEvent) break; | ||
if (nextEvent.eot) break; | ||
t += delay; | ||
if (nextEvent.tempo) { | ||
tempos.push({ | ||
t, | ||
delay, | ||
track: track.length, | ||
...nextEvent, | ||
}); | ||
} else if (nextEvent.channel && nextEvent.channel[0] >> 4 == 0x0c) { | ||
presets.push({ | ||
t, | ||
channel: nextEvent.channel[0] & 0x0f, | ||
pid: nextEvent.channel[1] & 0x7f, | ||
}); | ||
// const evtObj = { offset: reader.offset, t, delay, ...nextEvent }; | ||
// track.push(evtObj); | ||
} else { | ||
const evtObj = { offset: reader.offset, t, delay, ...nextEvent }; | ||
track.push(evtObj); | ||
} | ||
} | ||
if (track.length) tracks.push(track); | ||
reader.offset = endofTrack; | ||
} | ||
return { division, tracks, ntracks, presets, tempos }; | ||
} | ||
function bufferReader2(bytes) { | ||
let _offset = 0; | ||
const fgetc = () => bytes[_offset++]; | ||
const read32 = () => | ||
(fgetc() << 24) | (fgetc() << 16) | (fgetc() << 8) | fgetc(); | ||
const read16 = () => (fgetc() << 8) | fgetc(); | ||
const read24 = () => (fgetc() << 16) | (fgetc() << 8) | fgetc(); | ||
function readVarLength() { | ||
let v = 0; | ||
let n = fgetc(); | ||
v = n & 0x7f; | ||
while (n & 0x80) { | ||
n = fgetc(); | ||
v = (v << 7) | (n & 0x7f); | ||
} | ||
return v; | ||
} | ||
function btoa() { | ||
const code = fgetc(); | ||
return code >= 32 && code <= 122 | ||
? ` !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[~]^_@abcdefghijklmnopqrstuvwxyz`.split( | ||
"" | ||
)[code - 32] | ||
: code; | ||
} | ||
const readString = (n) => { | ||
let str = ""; | ||
while (n--) str += btoa(); | ||
return str; | ||
}; | ||
return { | ||
get offset() { | ||
return _offset; | ||
}, | ||
set offset(o) { | ||
_offset = o; | ||
}, | ||
fgetc, | ||
read32, | ||
read24, | ||
read16, | ||
readVarLength, | ||
readString, | ||
btoa, | ||
}; | ||
} |
Oops, something went wrong.