-
Notifications
You must be signed in to change notification settings - Fork 0
/
frame.html
142 lines (127 loc) · 4.19 KB
/
frame.html
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>msend</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<div class="App">
<div class="App-header">
<section class="row" id="connector-row">
<select id="output-select" aria-placeholder="select output"></select>
<input type="button" tabindex="0" value="connect" id="connect-btn" />
</section>
<section class="row">
<input type="file" tabindex="0" value="send file" id="file-btn" accept=".mid" />
<input type="button" tabindex="0" value="send" id="send-btn" />
</section>
<pre id="info"></pre>
</div>
</div>
<script type="text/javascript" id="timerworker">/* eslint-disable no-unused-vars */
let ppqn = 120;
let timesig = 4; //ratio of 4/4 etc
let msqn = 315789; // msqn315789 ppqn120
// msqn, ppq msqn, ppq
let waittime = msqn / 1000 / timesig;
// const intervalMillisecond = microsecondPerQuarterNote / 1000 / timeSignature;
let timer = null,
ticks = 0;
let startTime,
lastTick = 0;
onmessage = ({data}) => {
const {tm, stop, start, reset, load} = data;
if (tm) {
ppqn = tm.ppqn;
msqn = tm.msqn;
waittime = msqn / 1000 / timesig;
}
if (start) {
clearTimeout(timer);
startTime = performance.now();
lastTick = startTime;
timer = setTimeout(ontick, waittime);
} else if (stop) {
clearTimeout(timer);
} else if (reset) {
clearTimeout(timer);
postMessage(ticks);
ticks = 0;
}
};
function ontick() {
postMessage(ticks);
ticks += ppqn / timesig;
let now = performance.now;
lastTick = now;
const drift = waittime - (now - lastTick);
timer = setTimeout(ontick, waittime);
}
</script>
<script type="module">
import {readMidi} from 'https://unpkg.com/midiread';
const connectRow = document.querySelector("#connector-row")
const connectBtn = document.querySelector("#connect-btn");
const outputSelect = document.querySelector("#output-select");
const fileButton = document.querySelector("#file-btn");
const sendBtn = document.querySelector("#send-btn");
const info = document.querySelector("#info");
const workerUrl = URL.createObjectURL(new Blob([document.querySelector("#timerworker").textContent], {type: "application/javascript"}))
let midiinfo, outputChannel, midiAccess;
connectBtn.addEventListener("click", initNavigatorMidiAccess);
outputSelect.oninput = (e) => {
outputChannel = midiAccess.outputs.get(e.target.value)
}
fileButton.addEventListener('input', async function (e) {
if (!e.target.files[0]) return;
const ab = await (e.target.files[0]).arrayBuffer();
midiinfo = readMidi(new Uint8Array(ab));
// info.innerHTML = JSON.stringify(midiinfo, (k, v) => typeof (v) === 'array' ? v : v, "")
})
sendBtn.onclick = () => {
if (!midiinfo || !midiinfo.tracks) {
alert('nofile read');
return;
}
const {tempos, tracks, division, presets, ntracks, metas} = midiinfo;
const worker = new Worker(workerUrl);
let msqn = tempos?.[0]?.tempo || 500000;
let ppqn = division;
worker.postMessage({tm: {msqn, ppqn}});
const soundtracks = tracks.map((track) =>
track.filter((event) => event.t && event.channel)
);
worker.postMessage({start: 1})
worker.onmessage = ({data}) => {
const sysTick = data;
for (let i = 0;i < soundtracks.length;i++) {
const track = soundtracks[i];
while (track.length && track[0].t <= sysTick) {
const e = track.shift();
if (e.meta) console.log(e.meta);
else outputChannel.send(e.channel);
}
}
};
}
async function initNavigatorMidiAccess() {
midiAccess = await navigator.requestMIDIAccess();
if (!midiAccess) {
outputSelect.ariaPlaceholder = "MIDI Access not obtained";
return;
}
const outputs = midiAccess.outputs.values();
outputSelect.append(new Option("select output", null))
for (const output of Array.from(outputs)) {
outputSelect.append(new Option(output.name, output.id))
}
}
navigator.permissions.query({name: "midi"}).then(({state}) => {
initNavigatorMidiAccess();
});
</script>
</body>
</html>