From c0166cfd7fc8290f183e0d121d6c0161f710ec4b Mon Sep 17 00:00:00 2001 From: David Sexton Date: Thu, 4 Sep 2025 12:26:39 -0700 Subject: [PATCH 01/16] refactor: remove deprecated VirtualMidiService and integrate into MidiService MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Remove VirtualMidiService.ts as functionality moved to MidiService - Update App.tsx to remove VirtualMidiService initialization - Simplify MIDI initialization to single service 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- src/App.tsx | 12 +----- src/VirtualMidiService.ts | 78 --------------------------------------- 2 files changed, 1 insertion(+), 89 deletions(-) delete mode 100644 src/VirtualMidiService.ts diff --git a/src/App.tsx b/src/App.tsx index 7138b14..9b84474 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -2,7 +2,6 @@ import React, { useCallback, useEffect, useRef, useState } from "react"; import { useBeforeunload } from "react-beforeunload"; import "./App.css"; import MudClient from "./client"; -import { virtualMidiService } from "./VirtualMidiService"; import CommandInput from "./components/input"; import OutputWindow from "./components/output"; import PreferencesDialog, { @@ -125,16 +124,7 @@ function App() { window.mudClient = newClient; clientInitialized.current = true; - // Initialize virtual MIDI synthesizer - virtualMidiService.initialize().then((success) => { - if (success) { - console.log("Virtual MIDI synthesizer initialized"); - } else { - console.log("Failed to initialize virtual MIDI synthesizer"); - } - }).catch((error) => { - console.error("Error initializing virtual MIDI synthesizer:", error); - }); + // Virtual MIDI synthesizers are now initialized with MidiService // Listen to 'keydown' event const handleKeyDown = (event: KeyboardEvent) => { diff --git a/src/VirtualMidiService.ts b/src/VirtualMidiService.ts deleted file mode 100644 index 26e85bc..0000000 --- a/src/VirtualMidiService.ts +++ /dev/null @@ -1,78 +0,0 @@ -import JZZ from 'jzz'; -import { Tiny } from 'jzz-synth-tiny'; - -export class VirtualMidiService { - private static instance: VirtualMidiService; - private isInitialized = false; - private virtualPort: any = null; - private readonly portName = 'Virtual Synthesizer'; - - private constructor() {} - - static getInstance(): VirtualMidiService { - if (!VirtualMidiService.instance) { - VirtualMidiService.instance = new VirtualMidiService(); - } - return VirtualMidiService.instance; - } - - async initialize(): Promise { - if (this.isInitialized) { - return true; - } - - try { - // Wait for JZZ to be ready first - const jzz = await JZZ(); - - // Initialize JZZ with Tiny synthesizer - Tiny(JZZ); - - // Register the virtual synthesizer as a MIDI port - JZZ.synth.Tiny.register(this.portName); - - // Refresh JZZ to update the device list - jzz.refresh(); - - console.log(`Virtual MIDI synthesizer registered as: ${this.portName}`); - this.isInitialized = true; - return true; - } catch (error) { - console.error('Failed to initialize virtual MIDI synthesizer:', error); - return false; - } - } - - async getVirtualPort(): Promise { - if (!this.isInitialized) { - const success = await this.initialize(); - if (!success) return null; - } - - try { - // Get the virtual port through JZZ - this.virtualPort = await JZZ().openMidiOut(this.portName); - return this.virtualPort; - } catch (error) { - console.error('Failed to open virtual MIDI port:', error); - return null; - } - } - - getPortName(): string { - return this.portName; - } - - get initialized(): boolean { - return this.isInitialized; - } - - close(): void { - if (this.virtualPort) { - this.virtualPort.close(); - this.virtualPort = null; - } - } -} - -export const virtualMidiService = VirtualMidiService.getInstance(); \ No newline at end of file From 2d6a7c65c6d171543f3d59883e15d5526817e6c7 Mon Sep 17 00:00:00 2001 From: David Sexton Date: Thu, 4 Sep 2025 12:26:52 -0700 Subject: [PATCH 02/16] feat: add TypeScript declarations for MIDI.js integration MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add comprehensive type definitions for MIDI.js global API - Support for loadPlugin, loadResource, and audio context methods - Enable type-safe MIDI.js usage in TypeScript components 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- src/@types/midi.d.ts | 111 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) create mode 100644 src/@types/midi.d.ts diff --git a/src/@types/midi.d.ts b/src/@types/midi.d.ts new file mode 100644 index 0000000..b3322da --- /dev/null +++ b/src/@types/midi.d.ts @@ -0,0 +1,111 @@ +/** + * TypeScript declarations for MIDI.js library + * Based on the working example from midi-testing + */ + +declare global { + interface Window { + MIDI: { + /** + * Load MIDI.js plugin with configuration + */ + loadPlugin(options: { + soundfontUrl?: string; + instruments?: string[]; + onsuccess?: () => void; + onerror?: (error: any) => void; + onprogress?: (state: string, progress: number) => void; + targetFormat?: 'mp3' | 'ogg'; + api?: 'webaudio' | 'audiotag' | 'webmidi'; + }): void; + + /** + * Play a MIDI note + */ + noteOn(channel: number, note: number, velocity: number, delay?: number): void; + + /** + * Stop a MIDI note + */ + noteOff(channel: number, note: number, delay?: number): void; + + /** + * Change instrument program + */ + programChange?(channel: number, program: number): void; + + /** + * Control change + */ + controlChange?(channel: number, controller: number, value: number): void; + + /** + * Pitch bend + */ + pitchBend?(channel: number, value: number): void; + + /** + * Channel aftertouch + */ + channelAftertouch?(channel: number, pressure: number): void; + + /** + * Polyphonic aftertouch + */ + polyAftertouch?(channel: number, note: number, pressure: number): void; + + /** + * Load additional soundfont resource + */ + loadResource?(options: { + instrument: string; + onSuccess?: () => void; + onError?: (error: any) => void; + }): void; + + /** + * General MIDI instrument mappings + */ + GM?: any; + + /** + * Soundfont data + */ + Soundfont?: { [key: string]: any }; + + /** + * Current API being used + */ + api?: string; + + /** + * Audio format being used + */ + __audioFormat?: string; + }; + } + + // JZZ namespace extensions for MIDI.js synthesizer + namespace JZZ { + namespace synth { + /** + * Create MIDI.js synthesizer instance + */ + function MIDIjs(options?: { + soundfontUrl?: string; + instruments?: string[]; + targetFormat?: 'mp3' | 'ogg'; + }): any; + + namespace MIDIjs { + /** + * Register MIDI.js synthesizer with JZZ + */ + function register(name?: string, options?: any): any; + } + } + } +} + +// Ensure this file is treated as a module +export {}; \ No newline at end of file From ea4c247b48dd07a69551358604dbe11eeb5d945b Mon Sep 17 00:00:00 2001 From: David Sexton Date: Thu, 4 Sep 2025 12:27:05 -0700 Subject: [PATCH 03/16] feat: add dynamic MIDI.js script loader utility MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Implement fail-fast script loading with detailed error reporting - Load scripts in correct order: JZZ → MIDI.js → JZZ.synth.MIDIjs - Add timeout handling and verification checks - Support singleton pattern for efficient loading 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- src/utils/MidiJsScriptLoader.ts | 188 ++++++++++++++++++++++++++++++++ 1 file changed, 188 insertions(+) create mode 100644 src/utils/MidiJsScriptLoader.ts diff --git a/src/utils/MidiJsScriptLoader.ts b/src/utils/MidiJsScriptLoader.ts new file mode 100644 index 0000000..d41e82e --- /dev/null +++ b/src/utils/MidiJsScriptLoader.ts @@ -0,0 +1,188 @@ +/** + * MidiJsScriptLoader - Dynamically loads MIDI.js scripts for use with JZZ + * + * Loads scripts in the correct order: JZZ → MIDI.js → JZZ.synth.MIDIjs + * Based on working pattern from midi-testing example + */ + +export interface ScriptLoadError { + script: string; + error: string; + timestamp: number; +} + +export class MidiJsScriptLoader { + private static instance: MidiJsScriptLoader; + private scriptsLoaded = false; + private loadPromise: Promise | null = null; + + // Scripts must be loaded in this exact order + private readonly scriptUrls = [ + // Note: JZZ is already loaded via import in MidiService + // Files are served from public/ folder in Vite + '/MIDI.js', + '/JZZ.synth.MIDIjs.js' + ]; + + private constructor() {} + + static getInstance(): MidiJsScriptLoader { + if (!MidiJsScriptLoader.instance) { + MidiJsScriptLoader.instance = new MidiJsScriptLoader(); + } + return MidiJsScriptLoader.instance; + } + + /** + * Load all required MIDI.js scripts + * Fails fast with detailed error information + */ + async loadScripts(): Promise { + console.log('🚀 loadScripts() called'); + + if (this.scriptsLoaded) { + console.log('✅ MIDI.js scripts already loaded'); + return; + } + + if (this.loadPromise) { + console.log('⏳ MIDI.js scripts already loading, waiting...'); + return this.loadPromise; + } + + console.log('📋 Starting MIDI.js script loading sequence...'); + console.log('📋 Scripts to load:', this.scriptUrls); + const startTime = performance.now(); + + this.loadPromise = this.performScriptLoading(); + + try { + await this.loadPromise; + const endTime = performance.now(); + console.log(`MIDI.js scripts loaded successfully in ${endTime - startTime}ms`); + this.scriptsLoaded = true; + } catch (error) { + this.loadPromise = null; // Allow retry on failure + console.error('❌ Script loading promise rejected:', error); + const errorMessage = error instanceof Error ? error.message : String(error); + throw new Error(`Script loading failed: ${errorMessage}`); + } + } + + private async performScriptLoading(): Promise { + // Check if JZZ is available + if (typeof window.JZZ === 'undefined') { + const error = 'JZZ not found - must be loaded before MIDI.js scripts'; + console.error('❌', error); + throw new Error(error); + } + + console.log('✅ JZZ is available, proceeding with script loading...'); + + // Load each script in sequence + for (let i = 0; i < this.scriptUrls.length; i++) { + const url = this.scriptUrls[i]; + console.log(`📥 Loading script ${i + 1}/${this.scriptUrls.length}: ${url}`); + + try { + await this.loadScript(url); + } catch (scriptError) { + console.error(`❌ Failed to load script ${i + 1}: ${url}`, scriptError); + throw scriptError; // Re-throw to stop the sequence + } + + // Verify script loaded correctly + if (i === 0) { + // After MIDI.js loads + if (typeof window.MIDI === 'undefined') { + throw new Error(`MIDI.js failed to create window.MIDI object after loading ${url}`); + } + console.log('✓ MIDI.js loaded - window.MIDI available'); + } else if (i === 1) { + // After JZZ.synth.MIDIjs loads + if (!window.JZZ.synth || typeof window.JZZ.synth.MIDIjs !== 'function') { + throw new Error(`JZZ.synth.MIDIjs failed to register after loading ${url}`); + } + console.log('✓ JZZ.synth.MIDIjs loaded - JZZ.synth.MIDIjs() available'); + } + } + + console.log('All MIDI.js scripts loaded and verified successfully'); + } + + private loadScript(url: string): Promise { + return new Promise((resolve, reject) => { + // Check if script is already loaded + const existingScript = document.querySelector(`script[src="${url}"]`); + if (existingScript) { + console.log(`Script already exists in DOM: ${url}`); + resolve(); + return; + } + + const script = document.createElement('script'); + script.src = url; + script.type = 'text/javascript'; + + const timeout = setTimeout(() => { + cleanup(); + reject(new Error(`Timeout loading script: ${url} (30s timeout exceeded)`)); + }, 30000); + + const cleanup = () => { + clearTimeout(timeout); + script.removeEventListener('load', onLoad); + script.removeEventListener('error', onError); + }; + + const onLoad = () => { + cleanup(); + console.log(`✓ Script loaded: ${url}`); + resolve(); + }; + + const onError = (event: any) => { + cleanup(); + if (script.parentNode) { + script.parentNode.removeChild(script); // Clean up failed script safely + } + + const error: ScriptLoadError = { + script: url, + error: event.message || event.error || `HTTP error or file not found: ${url}`, + timestamp: Date.now() + }; + + console.error('❌ Script loading failed:', error); + console.error('Event details:', event); + reject(new Error(`Failed to load script ${url}: ${error.error}. Check if file exists and is accessible.`)); + }; + + script.addEventListener('load', onLoad); + script.addEventListener('error', onError); + + document.head.appendChild(script); + }); + } + + /** + * Check if all required scripts are loaded and functional + */ + isLoaded(): boolean { + return this.scriptsLoaded && + typeof window.MIDI !== 'undefined' && + typeof window.JZZ !== 'undefined' && + window.JZZ.synth && + typeof window.JZZ.synth.MIDIjs === 'function'; + } + + /** + * Reset loader state (for testing purposes) + */ + reset(): void { + this.scriptsLoaded = false; + this.loadPromise = null; + } +} + +export const midiJsScriptLoader = MidiJsScriptLoader.getInstance(); \ No newline at end of file From cdff4b09ff6ff123a49f2f43f6d41033b4687ee8 Mon Sep 17 00:00:00 2001 From: David Sexton Date: Thu, 4 Sep 2025 12:27:19 -0700 Subject: [PATCH 04/16] feat: add enhanced JZZ.synth.MIDIjs bridge with dynamic instrument loading MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Implement dynamic instrument loading with piano fallback - Add program change interception and deferred execution - Prevent duplicate loading with state management - Support multi-channel program changes - Add comprehensive error handling and logging 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- public/JZZ.synth.MIDIjs.js | 171 +++++++++++++++++++++++++++++++++++++ src/JZZ.synth.MIDIjs.js | 171 +++++++++++++++++++++++++++++++++++++ 2 files changed, 342 insertions(+) create mode 100644 public/JZZ.synth.MIDIjs.js create mode 100644 src/JZZ.synth.MIDIjs.js diff --git a/public/JZZ.synth.MIDIjs.js b/public/JZZ.synth.MIDIjs.js new file mode 100644 index 0000000..a676962 --- /dev/null +++ b/public/JZZ.synth.MIDIjs.js @@ -0,0 +1,171 @@ +(function() { + if (!JZZ) return; + if (!JZZ.synth) JZZ.synth = {}; + + function _name(name) { return name ? name : 'JZZ.synth.MIDIjs'; } + + var _waiting = false; + var _running = false; + var _bad = false; + var _error; + + // Dynamic loading state management + var _loadingInstruments = {}; // Track instruments currently being loaded + var _pendingProgramChanges = []; // Queue program changes waiting for load completion + + function _receive(a) { + var s = a[0]>>4; + var c = a[0]&0xf; + + if (s == 0xC) { // Program Change (0xC0-0xCF) + var program = a[1]; + var instrumentName = MIDI.GM && MIDI.GM.byId && MIDI.GM.byId[program] ? MIDI.GM.byId[program].id : null; + + if (instrumentName && MIDI.Soundfont && !MIDI.Soundfont[instrumentName]) { + // Instrument not loaded + console.log('Program change to unloaded instrument:', instrumentName, 'program:', program); + + // Check if already loading this instrument + if (_loadingInstruments[instrumentName]) { + // Queue this program change for after loading completes + _pendingProgramChanges.push({channel: c, program: program, instrument: instrumentName}); + console.log('Queuing program change while loading:', instrumentName); + return; // Don't process original program change + } + + // Start loading process + _loadingInstruments[instrumentName] = true; + _pendingProgramChanges.push({channel: c, program: program, instrument: instrumentName}); + + // Immediate fallback to acoustic_grand_piano (program 0) + console.log('Falling back to piano while loading:', instrumentName); + if (MIDI.programChange) { + MIDI.programChange(c, 0); + } + + // Load the requested instrument + if (MIDI.loadResource) { + MIDI.loadResource({ + instruments: [instrumentName], + onsuccess: function() { + console.log('Successfully loaded instrument:', instrumentName); + + // Send deferred program changes for this instrument + var pending = _pendingProgramChanges.filter(function(p) { return p.instrument === instrumentName; }); + pending.forEach(function(p) { + console.log('Sending deferred program change:', p.instrument, 'program:', p.program); + if (MIDI.programChange) { + MIDI.programChange(p.channel, p.program); + } + }); + + // Clean up + delete _loadingInstruments[instrumentName]; + _pendingProgramChanges = _pendingProgramChanges.filter(function(p) { return p.instrument !== instrumentName; }); + }, + onerror: function(err) { + console.warn('Failed to load instrument:', instrumentName, 'error:', err, '- staying on piano'); + delete _loadingInstruments[instrumentName]; + _pendingProgramChanges = _pendingProgramChanges.filter(function(p) { return p.instrument !== instrumentName; }); + } + }); + } + + return; // Don't process original program change + } + + // Instrument already loaded or no MIDI.js available yet - pass through + if (MIDI.programChange) { + MIDI.programChange(c, program); + } + return; + } + + // Standard MIDI message processing + if (s == 0x8) { + if (MIDI.noteOff) { + MIDI.noteOff(c, a[1]); + } + } + else if (s == 0x9) { + if (MIDI.noteOn) { + MIDI.noteOn(c, a[1], a[2]); + } + } + } + + var _ports = []; + function _release(port, name) { + port._info = _engine._info(name); + port._receive = _receive; + port._resume(); + } + + function _onsuccess() { + _running = true; + _waiting = false; + for (var i=0; i<_ports.length; i++) _release(_ports[i][0], _ports[i][1]); + } + + function _onerror(evt) { + _bad = true; + _error = evt; + for (var i=0; i<_ports.length; i++) _ports[i][0]._crash(_error); + } + + var _engine = {}; + + _engine._info = function(name) { + return { + type: 'MIDI.js', + name: _name(name), + manufacturer: 'virtual', + version: '0.3.2' + }; + } + + _engine._openOut = function(port, name) { + if (_running) { + _release(port, name); + return; + } + if (_bad) { + port._crash(_error); + return; + } + port._pause(); + _ports.push([port, name]); + if (_waiting) return; + _waiting = true; + var arg = _engine._arg; + if (!arg) arg = {}; + arg.onsuccess = _onsuccess; + arg.onerror = _onerror; + try { + MIDI.loadPlugin(arg); + } + catch(e) { + _error = e.message; + _onerror(_error); + } + } + + JZZ.synth.MIDIjs = function() { + var name, arg; + if (arguments.length == 1) arg = arguments[0]; + else { name = arguments[0]; arg = arguments[1];} + name = _name(name); + if (!_running && !_waiting) _engine._arg = arg; + return JZZ.lib.openMidiOut(name, _engine); + } + + JZZ.synth.MIDIjs.register = function() { + var name, arg; + if (arguments.length == 1) arg = arguments[0]; + else { name = arguments[0]; arg = arguments[1];} + name = _name(name); + if (!_running && !_waiting) _engine._arg = arg; + return JZZ.lib.registerMidiOut(name, _engine); + } + +})(); \ No newline at end of file diff --git a/src/JZZ.synth.MIDIjs.js b/src/JZZ.synth.MIDIjs.js new file mode 100644 index 0000000..a676962 --- /dev/null +++ b/src/JZZ.synth.MIDIjs.js @@ -0,0 +1,171 @@ +(function() { + if (!JZZ) return; + if (!JZZ.synth) JZZ.synth = {}; + + function _name(name) { return name ? name : 'JZZ.synth.MIDIjs'; } + + var _waiting = false; + var _running = false; + var _bad = false; + var _error; + + // Dynamic loading state management + var _loadingInstruments = {}; // Track instruments currently being loaded + var _pendingProgramChanges = []; // Queue program changes waiting for load completion + + function _receive(a) { + var s = a[0]>>4; + var c = a[0]&0xf; + + if (s == 0xC) { // Program Change (0xC0-0xCF) + var program = a[1]; + var instrumentName = MIDI.GM && MIDI.GM.byId && MIDI.GM.byId[program] ? MIDI.GM.byId[program].id : null; + + if (instrumentName && MIDI.Soundfont && !MIDI.Soundfont[instrumentName]) { + // Instrument not loaded + console.log('Program change to unloaded instrument:', instrumentName, 'program:', program); + + // Check if already loading this instrument + if (_loadingInstruments[instrumentName]) { + // Queue this program change for after loading completes + _pendingProgramChanges.push({channel: c, program: program, instrument: instrumentName}); + console.log('Queuing program change while loading:', instrumentName); + return; // Don't process original program change + } + + // Start loading process + _loadingInstruments[instrumentName] = true; + _pendingProgramChanges.push({channel: c, program: program, instrument: instrumentName}); + + // Immediate fallback to acoustic_grand_piano (program 0) + console.log('Falling back to piano while loading:', instrumentName); + if (MIDI.programChange) { + MIDI.programChange(c, 0); + } + + // Load the requested instrument + if (MIDI.loadResource) { + MIDI.loadResource({ + instruments: [instrumentName], + onsuccess: function() { + console.log('Successfully loaded instrument:', instrumentName); + + // Send deferred program changes for this instrument + var pending = _pendingProgramChanges.filter(function(p) { return p.instrument === instrumentName; }); + pending.forEach(function(p) { + console.log('Sending deferred program change:', p.instrument, 'program:', p.program); + if (MIDI.programChange) { + MIDI.programChange(p.channel, p.program); + } + }); + + // Clean up + delete _loadingInstruments[instrumentName]; + _pendingProgramChanges = _pendingProgramChanges.filter(function(p) { return p.instrument !== instrumentName; }); + }, + onerror: function(err) { + console.warn('Failed to load instrument:', instrumentName, 'error:', err, '- staying on piano'); + delete _loadingInstruments[instrumentName]; + _pendingProgramChanges = _pendingProgramChanges.filter(function(p) { return p.instrument !== instrumentName; }); + } + }); + } + + return; // Don't process original program change + } + + // Instrument already loaded or no MIDI.js available yet - pass through + if (MIDI.programChange) { + MIDI.programChange(c, program); + } + return; + } + + // Standard MIDI message processing + if (s == 0x8) { + if (MIDI.noteOff) { + MIDI.noteOff(c, a[1]); + } + } + else if (s == 0x9) { + if (MIDI.noteOn) { + MIDI.noteOn(c, a[1], a[2]); + } + } + } + + var _ports = []; + function _release(port, name) { + port._info = _engine._info(name); + port._receive = _receive; + port._resume(); + } + + function _onsuccess() { + _running = true; + _waiting = false; + for (var i=0; i<_ports.length; i++) _release(_ports[i][0], _ports[i][1]); + } + + function _onerror(evt) { + _bad = true; + _error = evt; + for (var i=0; i<_ports.length; i++) _ports[i][0]._crash(_error); + } + + var _engine = {}; + + _engine._info = function(name) { + return { + type: 'MIDI.js', + name: _name(name), + manufacturer: 'virtual', + version: '0.3.2' + }; + } + + _engine._openOut = function(port, name) { + if (_running) { + _release(port, name); + return; + } + if (_bad) { + port._crash(_error); + return; + } + port._pause(); + _ports.push([port, name]); + if (_waiting) return; + _waiting = true; + var arg = _engine._arg; + if (!arg) arg = {}; + arg.onsuccess = _onsuccess; + arg.onerror = _onerror; + try { + MIDI.loadPlugin(arg); + } + catch(e) { + _error = e.message; + _onerror(_error); + } + } + + JZZ.synth.MIDIjs = function() { + var name, arg; + if (arguments.length == 1) arg = arguments[0]; + else { name = arguments[0]; arg = arguments[1];} + name = _name(name); + if (!_running && !_waiting) _engine._arg = arg; + return JZZ.lib.openMidiOut(name, _engine); + } + + JZZ.synth.MIDIjs.register = function() { + var name, arg; + if (arguments.length == 1) arg = arguments[0]; + else { name = arguments[0]; arg = arguments[1];} + name = _name(name); + if (!_running && !_waiting) _engine._arg = arg; + return JZZ.lib.registerMidiOut(name, _engine); + } + +})(); \ No newline at end of file From d027e4d86b436f8d2b0288be73efbbaf0f62fd5a Mon Sep 17 00:00:00 2001 From: David Sexton Date: Thu, 4 Sep 2025 12:27:31 -0700 Subject: [PATCH 05/16] feat: add MIDI.js library for browser-based soundfont synthesis MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Add MIDI.js v0.3.2 for Web Audio API based synthesis - Support dynamic soundfont loading from remote URLs - Enable high-quality instrument synthesis with MP3/OGG formats - Integrate with existing JZZ MIDI infrastructure 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude --- public/MIDI.js | 1928 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1928 insertions(+) create mode 100644 public/MIDI.js diff --git a/public/MIDI.js b/public/MIDI.js new file mode 100644 index 0000000..1e76bb7 --- /dev/null +++ b/public/MIDI.js @@ -0,0 +1,1928 @@ +/* + ---------------------------------------------------------- + MIDI.audioDetect : 0.3.2 : 2015-03-26 + ---------------------------------------------------------- + https://github.com/mudcube/MIDI.js + ---------------------------------------------------------- + Probably, Maybe, No... Absolutely! + Test to see what types of