Spaces:
Running
Running
| // NanoMaestro Web Worker for isolated model inference and parsing | |
| try { | |
| importScripts("https://cdn.jsdelivr.net/npm/onnxruntime-web@1.20.1/dist/ort.min.js"); | |
| } catch (err) { | |
| console.error("Worker failed to import ONNX Runtime script:", err); | |
| } | |
| // ONNX runtime configuration | |
| if (typeof ort !== "undefined") { | |
| ort.env.wasm.wasmPaths = "https://cdn.jsdelivr.net/npm/onnxruntime-web@1.20.1/dist/"; | |
| ort.env.wasm.numThreads = Math.min(4, navigator.hardwareConcurrency || 1); | |
| } | |
| // State variables | |
| let session = null; | |
| let stoi = null; | |
| let itos = null; | |
| let vocabSize = 0; | |
| let h = null; | |
| let c = null; | |
| let currentId = 0; | |
| let activeModelName = ""; | |
| // Model parameters | |
| let hiddenSize = 2048; | |
| let numLayers = 2; | |
| // Playback settings | |
| let temperature = 0.85; | |
| let topK = 40; | |
| let currentBpm = 120; | |
| let midiBpmMode = "lock"; | |
| // Seeding settings | |
| let playingStyle = "default"; | |
| let midiTokens = []; | |
| let midiStartBar = 0; | |
| let isWarmingUp = false; | |
| // Temporal queues for gathering parsed events | |
| let tempQueue = []; | |
| let parser = null; | |
| function tokenId(token) { | |
| return stoi?.[token] ?? null; | |
| } | |
| function makeTensorId(id) { | |
| return new ort.Tensor("int64", BigInt64Array.from([BigInt(id)]), [1, 1]); | |
| } | |
| function zeroState() { | |
| return new ort.Tensor("float32", new Float32Array(numLayers * 1 * hiddenSize), [numLayers, 1, hiddenSize]); | |
| } | |
| function updateModelDimensions(session) { | |
| try { | |
| const inputNames = session.inputNames; | |
| if (inputNames.includes("h") && session.handler && session.handler._model) { | |
| const inputs = session.handler._model.graph.inputs; | |
| const hInput = inputs.find(i => i.name === "h"); | |
| if (hInput && hInput.type && hInput.type.tensorType && hInput.type.tensorType.shape) { | |
| const shape = hInput.type.tensorType.shape.dim; | |
| const layers = Number(shape[0].dimValue); | |
| const hidden = Number(shape[2].dimValue); | |
| if (layers > 0 && hidden > 0) { | |
| numLayers = layers; | |
| hiddenSize = hidden; | |
| console.log(`Worker set model dimensions: layers=${numLayers}, hiddenSize=${hiddenSize}`); | |
| } | |
| } | |
| } | |
| } catch (e) { | |
| console.error("Worker failed to detect model dimensions:", e); | |
| } | |
| } | |
| // Model Step Execution with Memory Management | |
| async function stepModel(record = true) { | |
| if (!session) return "?"; | |
| const inputTensor = makeTensorId(currentId); | |
| const output = await session.run({ input: inputTensor, h, c }); | |
| // Dispose input tensor to prevent memory leak | |
| inputTensor.dispose(); | |
| // Dispose previous recurrent states to prevent memory leaks during continuous generation | |
| if (h) { | |
| try { h.dispose(); } catch (e) {} | |
| } | |
| if (c) { | |
| try { c.dispose(); } catch (e) {} | |
| } | |
| h = output.h_out; | |
| c = output.c_out; | |
| const logitsData = output.logits.data; | |
| currentId = sampleFromLogits(logitsData); | |
| output.logits.dispose(); | |
| const token = itos[currentId] ?? "?"; | |
| // Reset state on EOS | |
| if (token === "EOS") { | |
| if (h) { try { h.dispose(); } catch (e) {} } | |
| if (c) { try { c.dispose(); } catch (e) {} } | |
| h = zeroState(); | |
| c = zeroState(); | |
| } | |
| if (record && parser) { | |
| parser.feed(token); | |
| } | |
| return token; | |
| } | |
| function sampleFromLogits(logits) { | |
| const temp = Math.max(0.05, temperature); | |
| const k = Math.max(1, topK); | |
| const scored = []; | |
| for (let i = 0; i < logits.length; i += 1) { | |
| const token = itos[i]; | |
| if (token === undefined) continue; | |
| let score = logits[i] / temp; | |
| if (token === "<EOP>" || token === "EOS") score -= 1.0; | |
| scored.push([i, score]); | |
| } | |
| scored.sort((a, b) => b[1] - a[1]); | |
| const picked = scored.slice(0, Math.min(k, scored.length)); | |
| const maxScore = picked[0]?.[1] ?? 0; | |
| let sum = 0; | |
| for (const item of picked) { | |
| item[2] = Math.exp(item[1] - maxScore); | |
| sum += item[2]; | |
| } | |
| let r = Math.random() * sum; | |
| for (const item of picked) { | |
| r -= item[2]; | |
| if (r <= 0) return item[0]; | |
| } | |
| return picked[picked.length - 1][0]; | |
| } | |
| // Duration converters | |
| function durationToSecondsFromEventSteps(steps, grid) { | |
| const quarterSeconds = 60 / currentBpm; | |
| return Math.max(0.035, (Math.max(1, steps) * 4 * quarterSeconds) / Math.max(1, grid)); | |
| } | |
| function midiToTonePitch(midi) { | |
| const names = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"]; | |
| return `${names[((midi % 12) + 12) % 12]}${Math.floor(midi / 12) - 1}`; | |
| } | |
| function pushEvent(event) { | |
| if (isWarmingUp) return; // Discard prompt history events | |
| tempQueue.push(event); | |
| } | |
| // Token Slicers | |
| function getTokensUpToBar(tokens, targetBar) { | |
| let barCount = 0; | |
| const sliced = []; | |
| for (const t of tokens) { | |
| if (t === "BAR") { | |
| barCount += 1; | |
| if (barCount > targetBar) { | |
| break; | |
| } | |
| } | |
| sliced.push(t); | |
| } | |
| return sliced; | |
| } | |
| // Event Parser | |
| function makeEventParser() { | |
| return { | |
| grid: 64, | |
| bar: -1, | |
| pos: 0, | |
| pendingPosition: null, | |
| pendingNotes: [], | |
| pendingNote: null, | |
| lastQ: 0, | |
| reset() { | |
| this.grid = 64; | |
| this.bar = -1; | |
| this.pos = 0; | |
| this.pendingPosition = null; | |
| this.pendingNotes = []; | |
| this.pendingNote = null; | |
| this.lastQ = 0; | |
| }, | |
| feed(token) { | |
| if (token.startsWith("BPM_")) { | |
| const bpm = Number(token.slice(4)); | |
| if (Number.isFinite(bpm) && bpm >= 40 && bpm <= 220) { | |
| if (playingStyle !== "midi" || isWarmingUp || midiBpmMode === "model") { | |
| currentBpm = bpm; | |
| self.postMessage({ action: "tempo", bpm: bpm }); | |
| } | |
| } | |
| return; | |
| } | |
| if (token.startsWith("GRID_")) { | |
| const grid = Number(token.slice(5)); | |
| if (Number.isFinite(grid) && grid > 0) this.grid = grid; | |
| return; | |
| } | |
| if (token === "BAR") { | |
| this.flushTo(this.absoluteQFor(this.bar + 1, 0)); | |
| this.bar += 1; | |
| this.pos = 0; | |
| return; | |
| } | |
| if (token.startsWith("POS_")) { | |
| const pos = Number(token.slice(4)); | |
| if (!Number.isFinite(pos)) return; | |
| this.flushTo(this.absoluteQFor(this.bar, pos)); | |
| this.pos = pos; | |
| return; | |
| } | |
| if (token.startsWith("NOTE_")) { | |
| const midi = Number(token.slice(5)); | |
| if (Number.isFinite(midi) && midi >= 0 && midi <= 127) { | |
| this.pendingNote = { midi, durationSteps: 1, velocity: 0.72 }; | |
| } | |
| return; | |
| } | |
| if (token.startsWith("DUR_") && this.pendingNote) { | |
| const steps = Number(token.slice(4)); | |
| if (Number.isFinite(steps)) this.pendingNote.durationSteps = Math.max(1, steps); | |
| return; | |
| } | |
| if (token.startsWith("VEL_") && this.pendingNote) { | |
| const bucket = Number(token.slice(4)); | |
| if (Number.isFinite(bucket)) this.pendingNote.velocity = Math.max(0.2, Math.min(0.95, bucket / 8)); | |
| const q = this.absoluteQFor(this.bar, this.pos); | |
| this.pendingPosition ??= q; | |
| this.pendingNotes.push(this.pendingNote); | |
| this.pendingNote = null; | |
| } | |
| }, | |
| absoluteQFor(bar, pos) { | |
| return Math.max(0, bar) * 4 + (Math.max(0, pos) * 4) / Math.max(1, this.grid); | |
| }, | |
| flushTo(nextQ) { | |
| if (this.pendingNote) { | |
| const q = this.absoluteQFor(this.bar, this.pos); | |
| this.pendingPosition ??= q; | |
| this.pendingNotes.push(this.pendingNote); | |
| this.pendingNote = null; | |
| } | |
| if (this.pendingNotes.length && this.pendingPosition !== null) { | |
| const gap = Math.max(0, this.pendingPosition - this.lastQ); | |
| if (gap > 0) pushEvent({ type: "rest", duration: this.quartersToSeconds(gap) }); | |
| pushEvent({ | |
| type: "note", | |
| notes: this.pendingNotes.map((note) => midiToTonePitch(note.midi)), | |
| perNoteDurations: this.pendingNotes.map((note) => durationToSecondsFromEventSteps(note.durationSteps, this.grid)), | |
| velocities: this.pendingNotes.map((note) => note.velocity), | |
| duration: 0, | |
| advance: 0, | |
| }); | |
| this.lastQ = this.pendingPosition; | |
| } | |
| const finalGap = Math.max(0, nextQ - this.lastQ); | |
| if (finalGap > 0) pushEvent({ type: "rest", duration: this.quartersToSeconds(finalGap) }); | |
| this.lastQ = Math.max(this.lastQ, nextQ); | |
| this.pendingPosition = null; | |
| this.pendingNotes = []; | |
| }, | |
| quartersToSeconds(quarters) { | |
| return (quarters * 60) / currentBpm; | |
| }, | |
| }; | |
| } | |
| function makeParser() { | |
| return makeEventParser(); | |
| } | |
| async function warmPrompt() { | |
| h = zeroState(); | |
| c = zeroState(); | |
| let allTokensStr; | |
| if (playingStyle === "midi" && midiTokens.length > 0) { | |
| allTokensStr = getTokensUpToBar(midiTokens, midiStartBar); | |
| } else if (playingStyle === "bach") { | |
| allTokensStr = [ | |
| "BOS", "BPM_100", "GRID_64", | |
| "BAR", | |
| "POS_0", "NOTE_81", "DUR_4", "VEL_6", | |
| "POS_4", "NOTE_79", "DUR_4", "VEL_6", | |
| "POS_8", "NOTE_77", "DUR_4", "VEL_6", | |
| "POS_12", "NOTE_76", "DUR_4", "VEL_6", | |
| "POS_16", "NOTE_74", "DUR_4", "VEL_6", | |
| "POS_20", "NOTE_73", "DUR_4", "VEL_6", | |
| "POS_24", "NOTE_74", "DUR_16", "VEL_6", | |
| "BAR" | |
| ]; | |
| } else if (playingStyle === "mozart") { | |
| allTokensStr = [ | |
| "BOS", "BPM_132", "GRID_64", | |
| "BAR", | |
| // Bright G-major chamber-style opening inspired by Mozart's serenades. | |
| "POS_0", "NOTE_55", "DUR_8", "VEL_6", | |
| "NOTE_67", "DUR_8", "VEL_6", | |
| "POS_8", "NOTE_74", "DUR_4", "VEL_7", | |
| "POS_12", "NOTE_71", "DUR_4", "VEL_6", | |
| "POS_16", "NOTE_79", "DUR_8", "VEL_7", | |
| "POS_24", "NOTE_78", "DUR_4", "VEL_6", | |
| "POS_28", "NOTE_76", "DUR_4", "VEL_6", | |
| "POS_32", "NOTE_74", "DUR_8", "VEL_7", | |
| "POS_40", "NOTE_72", "DUR_4", "VEL_6", | |
| "POS_44", "NOTE_71", "DUR_4", "VEL_6", | |
| "POS_48", "NOTE_69", "DUR_8", "VEL_6", | |
| "POS_56", "NOTE_67", "DUR_8", "VEL_7", | |
| "BAR", | |
| "POS_0", "NOTE_60", "DUR_16", "VEL_5", | |
| "NOTE_64", "DUR_16", "VEL_5", | |
| "NOTE_69", "DUR_16", "VEL_5", | |
| "POS_16", "NOTE_72", "DUR_4", "VEL_6", | |
| "POS_20", "NOTE_76", "DUR_4", "VEL_6", | |
| "POS_24", "NOTE_81", "DUR_8", "VEL_7", | |
| "POS_32", "NOTE_79", "DUR_4", "VEL_6", | |
| "POS_36", "NOTE_78", "DUR_4", "VEL_6", | |
| "POS_40", "NOTE_76", "DUR_4", "VEL_6", | |
| "POS_44", "NOTE_74", "DUR_4", "VEL_6", | |
| "POS_48", "NOTE_71", "DUR_8", "VEL_6", | |
| "POS_56", "NOTE_67", "DUR_8", "VEL_7", | |
| "BAR" | |
| ]; | |
| } else if (playingStyle === "chopin") { | |
| allTokensStr = [ | |
| "BOS", "BPM_68", "GRID_64", | |
| "BAR", | |
| // Original C-minor nocturne texture: rolling bass beneath a lyrical upper line. | |
| "POS_0", "NOTE_36", "DUR_16", "VEL_4", "NOTE_72", "DUR_12", "VEL_6", | |
| "POS_8", "NOTE_43", "DUR_8", "VEL_4", | |
| "POS_12", "NOTE_75", "DUR_8", "VEL_6", | |
| "POS_16", "NOTE_48", "DUR_8", "VEL_4", | |
| "POS_24", "NOTE_51", "DUR_8", "VEL_4", "NOTE_77", "DUR_12", "VEL_6", | |
| "POS_32", "NOTE_39", "DUR_16", "VEL_4", "NOTE_79", "DUR_16", "VEL_7", | |
| "POS_40", "NOTE_46", "DUR_8", "VEL_4", | |
| "POS_48", "NOTE_51", "DUR_8", "VEL_4", "NOTE_77", "DUR_8", "VEL_6", | |
| "POS_56", "NOTE_55", "DUR_8", "VEL_4", "NOTE_75", "DUR_8", "VEL_5", | |
| "BAR", | |
| "POS_0", "NOTE_41", "DUR_16", "VEL_4", "NOTE_72", "DUR_16", "VEL_6", | |
| "POS_8", "NOTE_48", "DUR_8", "VEL_4", | |
| "POS_16", "NOTE_53", "DUR_8", "VEL_4", "NOTE_74", "DUR_8", "VEL_6", | |
| "POS_24", "NOTE_56", "DUR_8", "VEL_4", "NOTE_75", "DUR_8", "VEL_6", | |
| "POS_32", "NOTE_43", "DUR_16", "VEL_4", "NOTE_79", "DUR_12", "VEL_7", | |
| "POS_40", "NOTE_50", "DUR_8", "VEL_4", | |
| "POS_44", "NOTE_77", "DUR_8", "VEL_6", | |
| "POS_48", "NOTE_55", "DUR_8", "VEL_4", | |
| "POS_56", "NOTE_60", "DUR_8", "VEL_4", "NOTE_75", "DUR_8", "VEL_5", | |
| "BAR" | |
| ]; | |
| } else if (playingStyle === "debussy") { | |
| allTokensStr = [ | |
| "BOS", "BPM_74", "GRID_64", | |
| "BAR", | |
| // Original impressionist sketch: open fifths and slowly shifting parallel colors. | |
| "POS_0", "NOTE_38", "DUR_32", "VEL_3", "NOTE_57", "DUR_16", "VEL_4", "NOTE_62", "DUR_16", "VEL_4", "NOTE_69", "DUR_16", "VEL_5", | |
| "POS_16", "NOTE_59", "DUR_16", "VEL_4", "NOTE_64", "DUR_16", "VEL_4", "NOTE_71", "DUR_16", "VEL_5", | |
| "POS_32", "NOTE_45", "DUR_32", "VEL_3", "NOTE_61", "DUR_16", "VEL_4", "NOTE_66", "DUR_16", "VEL_4", "NOTE_73", "DUR_16", "VEL_5", | |
| "POS_48", "NOTE_64", "DUR_16", "VEL_4", "NOTE_69", "DUR_16", "VEL_4", "NOTE_76", "DUR_16", "VEL_5", | |
| "BAR", | |
| "POS_0", "NOTE_40", "DUR_32", "VEL_3", "NOTE_59", "DUR_16", "VEL_4", "NOTE_64", "DUR_16", "VEL_4", "NOTE_71", "DUR_16", "VEL_5", | |
| "POS_16", "NOTE_61", "DUR_16", "VEL_4", "NOTE_66", "DUR_16", "VEL_4", "NOTE_73", "DUR_16", "VEL_5", | |
| "POS_32", "NOTE_43", "DUR_32", "VEL_3", "NOTE_62", "DUR_16", "VEL_4", "NOTE_67", "DUR_16", "VEL_4", "NOTE_74", "DUR_16", "VEL_5", | |
| "POS_48", "NOTE_66", "DUR_16", "VEL_4", "NOTE_71", "DUR_16", "VEL_4", "NOTE_78", "DUR_16", "VEL_5", | |
| "BAR" | |
| ]; | |
| } else if (playingStyle === "beethoven") { | |
| allTokensStr = [ | |
| "BOS", "BPM_126", "GRID_64", | |
| "BAR", | |
| // Original dramatic sonata gesture: compact motif, octave bass, accented response. | |
| "POS_0", "NOTE_36", "DUR_8", "VEL_7", "NOTE_48", "DUR_8", "VEL_7", "NOTE_67", "DUR_4", "VEL_7", | |
| "POS_4", "NOTE_70", "DUR_4", "VEL_7", | |
| "POS_8", "NOTE_68", "DUR_8", "VEL_8", | |
| "POS_16", "NOTE_43", "DUR_8", "VEL_6", "NOTE_55", "DUR_8", "VEL_6", "NOTE_65", "DUR_4", "VEL_7", | |
| "POS_20", "NOTE_68", "DUR_4", "VEL_7", | |
| "POS_24", "NOTE_67", "DUR_8", "VEL_8", | |
| "POS_32", "NOTE_39", "DUR_8", "VEL_7", "NOTE_51", "DUR_8", "VEL_7", "NOTE_63", "DUR_4", "VEL_7", | |
| "POS_36", "NOTE_67", "DUR_4", "VEL_7", | |
| "POS_40", "NOTE_72", "DUR_8", "VEL_8", | |
| "POS_48", "NOTE_44", "DUR_8", "VEL_6", "NOTE_56", "DUR_8", "VEL_6", "NOTE_70", "DUR_4", "VEL_7", | |
| "POS_52", "NOTE_68", "DUR_4", "VEL_7", | |
| "POS_56", "NOTE_67", "DUR_8", "VEL_8", | |
| "BAR", | |
| "POS_0", "NOTE_41", "DUR_16", "VEL_7", "NOTE_53", "DUR_16", "VEL_7", "NOTE_65", "DUR_8", "VEL_7", | |
| "POS_8", "NOTE_68", "DUR_4", "VEL_7", | |
| "POS_12", "NOTE_72", "DUR_4", "VEL_8", | |
| "POS_16", "NOTE_77", "DUR_8", "VEL_8", | |
| "POS_24", "NOTE_75", "DUR_8", "VEL_7", | |
| "POS_32", "NOTE_43", "DUR_16", "VEL_7", "NOTE_55", "DUR_16", "VEL_7", "NOTE_74", "DUR_8", "VEL_7", | |
| "POS_40", "NOTE_72", "DUR_8", "VEL_7", | |
| "POS_48", "NOTE_67", "DUR_16", "VEL_8", "NOTE_72", "DUR_16", "VEL_8", "NOTE_75", "DUR_16", "VEL_8", | |
| "BAR" | |
| ]; | |
| } else if (playingStyle === "satie") { | |
| allTokensStr = [ | |
| "BOS", "BPM_62", "GRID_64", | |
| "BAR", | |
| // Original sparse meditation: low bass, suspended chords, unhurried melody. | |
| "POS_0", "NOTE_38", "DUR_32", "VEL_3", "NOTE_69", "DUR_20", "VEL_5", | |
| "POS_16", "NOTE_57", "DUR_24", "VEL_3", "NOTE_62", "DUR_24", "VEL_3", "NOTE_65", "DUR_24", "VEL_3", | |
| "POS_32", "NOTE_45", "DUR_32", "VEL_3", "NOTE_72", "DUR_16", "VEL_5", | |
| "POS_48", "NOTE_55", "DUR_24", "VEL_3", "NOTE_60", "DUR_24", "VEL_3", "NOTE_64", "DUR_24", "VEL_3", | |
| "BAR", | |
| "POS_0", "NOTE_40", "DUR_32", "VEL_3", "NOTE_71", "DUR_20", "VEL_5", | |
| "POS_16", "NOTE_59", "DUR_24", "VEL_3", "NOTE_64", "DUR_24", "VEL_3", "NOTE_67", "DUR_24", "VEL_3", | |
| "POS_32", "NOTE_43", "DUR_32", "VEL_3", "NOTE_74", "DUR_16", "VEL_5", | |
| "POS_48", "NOTE_57", "DUR_24", "VEL_3", "NOTE_62", "DUR_24", "VEL_3", "NOTE_66", "DUR_24", "VEL_3", | |
| "BAR" | |
| ]; | |
| } else { | |
| allTokensStr = ["BOS", "BPM_120", "GRID_64", "BAR", "POS_0"]; | |
| } | |
| isWarmingUp = true; | |
| // 1. Feed all prompt tokens to parser to advance its clock | |
| if (parser) { | |
| for (const token of allTokensStr) { | |
| parser.feed(token); | |
| } | |
| } | |
| // 2. Slice model warm-up tokens to last 256 (matching model's context window) | |
| const modelTokensStr = allTokensStr.slice(-256); | |
| const ids = modelTokensStr.map(tokenId).filter((id) => id !== null); | |
| for (const id of ids) { | |
| currentId = id; | |
| await stepModel(false); | |
| } | |
| isWarmingUp = false; | |
| } | |
| async function pumpTokens(targetCount = 96) { | |
| tempQueue = []; | |
| let steps = 0; | |
| // Keep stepping the model until we collect enough parsed events or hit loop bounds | |
| while (tempQueue.length < targetCount && steps < 300) { | |
| await stepModel(true); | |
| steps += 1; | |
| } | |
| return { | |
| events: tempQueue, | |
| lastToken: itos[currentId] ?? "?" | |
| }; | |
| } | |
| // Message Router | |
| self.onmessage = async function (e) { | |
| const data = e.data; | |
| switch (data.action) { | |
| case "init": | |
| try { | |
| console.log(`Worker loading model: ${data.activeModelName}, requested EP: ${data.ep || "wasm"}`); | |
| activeModelName = data.activeModelName; | |
| stoi = data.vocab.stoi; | |
| itos = Object.fromEntries(Object.entries(data.vocab.itos).map(([key, value]) => [Number(key), value])); | |
| vocabSize = data.vocab.vocab_size; | |
| if (data.modelConfig) { | |
| numLayers = data.modelConfig.numLayers || 2; | |
| hiddenSize = data.modelConfig.hiddenSize || 2048; | |
| } | |
| if (session) { | |
| try { session.dispose(); } catch (e) {} | |
| session = null; | |
| } | |
| let epUsed = data.ep || "wasm"; | |
| if (data.ep === "webgpu") { | |
| try { | |
| console.log("Attempting WebGPU execution provider..."); | |
| session = await ort.InferenceSession.create(data.modelBuffer, { | |
| executionProviders: ["webgpu"], | |
| }); | |
| console.log("WebGPU session created successfully!"); | |
| epUsed = "webgpu"; | |
| } catch (webgpuErr) { | |
| console.warn("WebGPU EP failed or unsupported by browser, falling back to CPU WASM:", webgpuErr); | |
| session = await ort.InferenceSession.create(data.modelBuffer, { | |
| executionProviders: ["wasm"], | |
| graphOptimizationLevel: "all", | |
| }); | |
| epUsed = "wasm (CPU fallback)"; | |
| } | |
| } else { | |
| session = await ort.InferenceSession.create(data.modelBuffer, { | |
| executionProviders: ["wasm"], | |
| graphOptimizationLevel: "all", | |
| }); | |
| epUsed = "wasm"; | |
| } | |
| updateModelDimensions(session); | |
| if (h) { try { h.dispose(); } catch (e) {} h = null; } | |
| if (c) { try { c.dispose(); } catch (e) {} c = null; } | |
| self.postMessage({ | |
| action: "initialized", | |
| activeModelName: activeModelName, | |
| vocabSize: vocabSize, | |
| epUsed: epUsed | |
| }); | |
| } catch (err) { | |
| console.error("Worker initialization failed:", err); | |
| self.postMessage({ action: "error", message: `Init failed: ${err.message}` }); | |
| } | |
| break; | |
| case "start": | |
| try { | |
| temperature = data.temperature; | |
| topK = data.topK; | |
| currentBpm = data.bpm; | |
| playingStyle = data.playingStyle || "default"; | |
| midiTokens = data.midiTokens; | |
| midiStartBar = data.midiStartBar; | |
| midiBpmMode = data.midiBpmMode || "lock"; | |
| parser = makeParser(); | |
| parser.reset(); | |
| await warmPrompt(); | |
| const initialBatch = await pumpTokens(96); | |
| self.postMessage({ | |
| action: "started", | |
| events: initialBatch.events, | |
| lastToken: initialBatch.lastToken | |
| }); | |
| } catch (err) { | |
| console.error("Worker start failed:", err); | |
| self.postMessage({ action: "error", message: `Start failed: ${err.message}` }); | |
| } | |
| break; | |
| case "pump": | |
| try { | |
| temperature = data.temperature; | |
| topK = data.topK; | |
| const batch = await pumpTokens(96); | |
| self.postMessage({ | |
| action: "events", | |
| events: batch.events, | |
| lastToken: batch.lastToken | |
| }); | |
| } catch (err) { | |
| console.error("Worker pump failed:", err); | |
| self.postMessage({ action: "error", message: `Pump failed: ${err.message}` }); | |
| } | |
| break; | |
| case "stop": | |
| // Halts ongoing actions, resets states if needed | |
| break; | |
| } | |
| }; | |