Skip to content

Commit 4b934b0

Browse files
authored
Merge pull request #287 from cuthbertLab/webmidi_modernize
modernize webmidi.ts
2 parents 675a7c2 + 6b33573 commit 4b934b0

File tree

2 files changed

+122
-45
lines changed

2 files changed

+122
-45
lines changed

src/webmidi.ts

Lines changed: 57 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,13 @@
22
* music21j -- Javascript reimplementation of Core music21p features.
33
* music21/webmidi -- webmidi or wrapper around the Jazz Plugin
44
*
5-
* For non webmidi -- Uses the cross-platform, cross-browser plugin from
6-
* http://jazz-soft.net/doc/Jazz-Plugin/Plugin.html
7-
* P.S. by the standards of divinity of most major religions, Sema Kachalo is a god.
5+
* Recommendation (2026): use Google Chrome or Edge on Mac or PC.
86
*
9-
* Copyright (c) 2013-24, Michael Scott Asato Cuthbert
10-
* Based on music21 (=music21p), Copyright (c) 2006-24, Michael Scott Asato Cuthbert
7+
* For non webmidi -- Uses the cross-platform, cross-browser plugin from
8+
* https://jazz-soft.net/doc/Jazz-Plugin/Plugin.html
9+
*
10+
* Copyright (c) 2013-26, Michael Scott Asato Cuthbert
11+
* Based on music21 (=music21p), Copyright (c) 2006-26, Michael Scott Asato Cuthbert
1112
*
1213
*/
1314
/**
@@ -142,7 +143,7 @@ export function midiInArrived(midiMessageEvent) {
142143
const midiCallbacks: miditools.CallbackInterface = miditools.callbacks;
143144
const eventObject = midiCallbacks.raw(t, a, b, c);
144145
if (midiCallbacks.general instanceof Array) {
145-
return midiCallbacks.general.forEach((el, index, array) => {
146+
return midiCallbacks.general.forEach((el, _index, _array) => {
146147
el(eventObject);
147148
});
148149
} else if (midiCallbacks.general instanceof Function) {
@@ -203,21 +204,29 @@ export function createPlugin(
203204
/**
204205
* Creates a <select> object for selecting among the MIDI choices in Jazz
205206
*
206-
* @param {HTMLElement} [$newSelect=document.body] - object to append the select to
207+
* Changed 2026 Jan -- returns bool about whether it succeeded (does not call
208+
* onAccessFailure)
209+
*
210+
* @param {HTMLElement} newSelect - object to append the select to
207211
* @param {Object} [options] - see createSelector for details
208-
* @returns {HTMLElement|undefined} DOM object containing the select tag, or undefined if Jazz cannot be loaded.
212+
* @returns {boolean} DOM object containing the select tag, or undefined if Jazz cannot be loaded.
209213
*/
210-
export function createJazzSelector(newSelect: HTMLElement, options: MIDISelectorOptions = {}): HTMLElement|undefined {
214+
export function createJazzSelector(
215+
newSelect: HTMLElement,
216+
options: MIDISelectorOptions = {},
217+
): boolean {
211218
const params: MIDISelectorOptions = {
219+
autoUpdate: false, // Jazz cannot autoUpdate
212220
onsuccess: undefined,
213221
oninputsuccess: undefined,
214222
oninputempty: undefined,
223+
onAccessFailure: undefined,
215224
};
216225
common.merge(params, options);
217226

218227
const jazzPlugin: Jazz = createPlugin();
219-
if (jazzPlugin === undefined) {
220-
return undefined;
228+
if (!jazzPlugin) {
229+
return false;
221230
}
222231

223232
newSelect.addEventListener('change', e => {
@@ -240,7 +249,7 @@ export function createJazzSelector(newSelect: HTMLElement, options: MIDISelector
240249
const noneAppendOption = <HTMLOptionElement> to_el("<option value='None'>None selected</option>");
241250
newSelect.appendChild(noneAppendOption);
242251

243-
let anySelected = false;
252+
let anySelected: boolean = false;
244253
const allAppendOptions: HTMLOptionElement[] = [];
245254
for (let i = 0; i < midiOptions.length; i++) {
246255
const appendOption = <HTMLOptionElement> to_el(
@@ -258,7 +267,7 @@ export function createJazzSelector(newSelect: HTMLElement, options: MIDISelector
258267
// console.log(appendOption);
259268
newSelect.appendChild(appendOption);
260269
}
261-
if (anySelected === false && midiOptions.length > 0) {
270+
if (!anySelected && midiOptions.length > 0) {
262271
allAppendOptions[0].setAttribute('selected', 'true');
263272
webmidi.selectedJazzInterface = jazzPlugin.MidiInOpen(
264273
midiOptions[0],
@@ -268,15 +277,13 @@ export function createJazzSelector(newSelect: HTMLElement, options: MIDISelector
268277
} else {
269278
noneAppendOption.setAttribute('selected', 'true');
270279
}
271-
if (params.onsuccess !== undefined) {
272-
params.onsuccess();
273-
}
274-
if (anySelected === true && params.oninputsuccess !== undefined) {
275-
params.oninputsuccess();
276-
} else if (anySelected === false && params.oninputempty !== undefined) {
277-
params.oninputempty();
280+
params.onsuccess?.();
281+
if (anySelected) {
282+
params.oninputsuccess?.();
283+
} else {
284+
params.oninputempty?.();
278285
}
279-
return newSelect;
286+
return true;
280287
}
281288

282289
/**
@@ -319,7 +326,7 @@ interface MIDISelectorOptions {
319326
/**
320327
* Function to call on all successful port queries.
321328
*/
322-
onsuccess?: Function;
329+
onsuccess?: () => void;
323330

324331
/**
325332
* Function to call if port query is successful and at least one input device exists.
@@ -330,6 +337,11 @@ interface MIDISelectorOptions {
330337
* Function to call if port query is successful but no input devices are found.
331338
*/
332339
oninputempty?: Function;
340+
341+
/**
342+
* On access failure
343+
*/
344+
onAccessFailure?: (e: DOMException) => void;
333345
}
334346

335347

@@ -342,30 +354,40 @@ interface MIDISelectorOptions {
342354
export function createSelector(
343355
where: HTMLElement,
344356
options: MIDISelectorOptions = {}
345-
): HTMLSelectElement {
357+
): HTMLSelectElement|undefined {
346358
let existingMidiSelect = false;
359+
const midiSelectDiv = <HTMLDivElement> common.coerceHTMLElement(where);
360+
347361
const params: MIDISelectorOptions = {
348362
autoUpdate: true,
349363
onsuccess: undefined,
350364
oninputsuccess: undefined,
351365
oninputempty: undefined,
366+
onAccessFailure: (e: DOMException) => { midiSelectDiv.innerHTML = e.message; },
352367
};
353368
common.merge(params, options);
354-
const midiSelectDiv = <HTMLDivElement> common.coerceHTMLElement(where);
355369

356370
let newSelect: HTMLSelectElement;
357-
const foundMidiSelects = midiSelectDiv.querySelectorAll('select#midiInSelect');
371+
const foundMidiSelects = midiSelectDiv.querySelectorAll<HTMLSelectElement>(
372+
'select#midiInSelect'
373+
);
358374
if (foundMidiSelects.length > 0) {
359-
newSelect = <HTMLSelectElement> foundMidiSelects[0];
375+
newSelect = foundMidiSelects[0];
360376
existingMidiSelect = true;
361377
} else {
362-
newSelect = <HTMLSelectElement> to_el('<select id="midiInSelect"></select>');
378+
newSelect = to_el<HTMLSelectElement>('<select id="midiInSelect"></select>');
363379
midiSelectDiv.appendChild(newSelect);
364380
}
365381
webmidi.select = newSelect;
366382

367-
if (navigator.requestMIDIAccess === undefined) {
368-
createJazzSelector(newSelect, params);
383+
if (!navigator.requestMIDIAccess) {
384+
const jazz_success = createJazzSelector(newSelect, params);
385+
if (!jazz_success) {
386+
params.onAccessFailure?.(new DOMException(
387+
'Your browser does not support MIDI input; please use Chrome or Edge.',
388+
'NotSupportedError'
389+
));
390+
}
369391
} else {
370392
if (!existingMidiSelect) {
371393
newSelect.addEventListener('change', e => selectionChanged(e));
@@ -379,24 +401,14 @@ export function createSelector(
379401
}
380402
const changeEvent = new Event('change', {bubbles: true});
381403
webmidi.select.dispatchEvent(changeEvent);
382-
if (params.onsuccess !== undefined) {
383-
params.onsuccess();
384-
}
385-
if (
386-
webmidi.selectedInputPort !== 'None'
387-
&& params.oninputsuccess !== undefined
388-
) {
389-
params.oninputsuccess();
390-
} else if (
391-
webmidi.selectedInputPort === 'None'
392-
&& params.oninputempty !== undefined
393-
) {
394-
params.oninputempty();
404+
params.onsuccess?.();
405+
if (webmidi.selectedInputPort !== 'None') {
406+
params.oninputsuccess?.();
407+
} else {
408+
params.oninputempty?.();
395409
}
396410
},
397-
e => {
398-
midiSelectDiv.innerHTML = e.message;
399-
}
411+
e => params.onAccessFailure?.(e),
400412
);
401413
}
402414
miditools.clearOldChords(); // starts the chord checking process.
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
<html lang="en">
2+
<head>
3+
<title>MIDI In Chords for Music21j</title>
4+
<meta charset="utf-8">
5+
<link rel="stylesheet" href="../css/m21.css">
6+
</head>
7+
<body>
8+
<h1>MIDI In Chords with Custom onAccessFailure</h1>
9+
<div>
10+
MIDI Input:
11+
<div id="putMidiSelectHere"></div>
12+
</div>
13+
<h2>Chord and Duration rendering and Metronome Demo.</h2>
14+
<div>
15+
<i>Requires MIDI keyboard and Jazz Plugin to work.</i>
16+
</div>
17+
<div id="svgDiv" class='streamHolding'>
18+
</div>
19+
<div id="metronomeDiv"></div>
20+
21+
<script src="../releases/music21.debug.js"></script>
22+
<script type="module" src="./m21-dev.ts"></script>
23+
<script>
24+
let s; // will become Stream object soon...
25+
let metro; // will become Metronome object soon...
26+
27+
function appendElement(appendObject) {
28+
if (s.length > 7) {
29+
s.elements = s.elements.slice(1)
30+
}
31+
// if (s.length > 0) {
32+
// let lastNote = s.elements[s.length - 1];
33+
// }
34+
s.append(appendObject);
35+
const svgDiv = document.querySelector("#svgDiv");
36+
svgDiv.replaceChildren();
37+
s.appendNewDOM(svgDiv);
38+
}
39+
40+
function main() {
41+
s = new music21.stream.Measure();
42+
s.clef = new music21.clef.TrebleClef();
43+
s.renderOptions.staffLines = 5;
44+
metro = new music21.tempo.Metronome();
45+
metro.addDiv(document.querySelector("#metronomeDiv"));
46+
47+
music21.miditools.config.metronome = metro;
48+
49+
music21.webmidi.createSelector(
50+
document.querySelector("#putMidiSelectHere"),
51+
{onAccessFailure: e => { alert(e.message + '(' + e.name + ')')}}
52+
);
53+
music21.miditools.callbacks.general = [
54+
music21.miditools.makeChords,
55+
music21.miditools.sendToMIDIjs,
56+
];
57+
music21.miditools.callbacks.sendOutChord = appendElement;
58+
}
59+
document.addEventListener('DOMContentLoaded', () => {
60+
main();
61+
});
62+
</script>
63+
64+
</body>
65+
</html>

0 commit comments

Comments
 (0)