Skip to content

Commit

Permalink
lts
Browse files Browse the repository at this point in the history
  • Loading branch information
Yisheng Jiang committed Mar 20, 2023
1 parent 00914c7 commit 0558a47
Show file tree
Hide file tree
Showing 7 changed files with 400 additions and 65 deletions.
6 changes: 0 additions & 6 deletions .gitmodules

This file was deleted.

2 changes: 1 addition & 1 deletion e2e.js
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";

Expand Down
13 changes: 4 additions & 9 deletions src/index.js
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";
Expand Down Expand Up @@ -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);
Expand Down
98 changes: 98 additions & 0 deletions src/midi-scheduler.js
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;
149 changes: 149 additions & 0 deletions src/midiread.js
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,
};
}
Loading

0 comments on commit 0558a47

Please sign in to comment.