@yam8d/m8-sdk 0.5.6 → 0.5.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["overlap","version","description","notes","views","fxCommands","viewSchema","MAX_STEPS","views","graphJson","viewListJson","rgbHex","TICKS_PER_STEP","instrumentTypes","instrumentTypeNames","paramSchema","instrumentTypes","schema","hex2","squash","schema","createEventBus","eventMap","args","event","cb","callback","littleEndian","parseRectCommand","x","data","y","length","bytes","i","parseCharacterCommand","parseWaveCommand","wave","parseKeyCommand","device","parseSystemCommand","protocol","lastSystemInfo","eventBus","cachedSystemInfos","computeLayoutMetrics","spacingX","spacingY","offY","screenWidth","screenHeight","rectOffset","sys","isV2","fm","dataLength","buffer","command","metrics","createSlipParser","view","state","finalizePacket","out","dv","pushByte","byte","queuedUsbReader","parser","messages","terminated","resolver","result","maybe","msg","resolve","queuedSerialReader","reader","done","value","chunk","vendorId","productId","waitTime","usbWriter","serialWriter","writer","midiCommands","commands","interval","output","press","keys","msb","velocity","note","safeChannel","channel","safeProgram","program","statusByte","M8_SYSEX_HEADER","SYSEX_END","isM8Sysex","decode7BitPacked","msbByte","remaining","groupLen","bit","low7","decodeM8Sysex","encoded","midiReader","pending","findHeaderIndex","header","start","buf","match","j","appendBytes","merged","a","b","processChunk","hdrIdx","endIdx","decoded","handler","evt","message","stopped","stop","input","onStateChange","beginRead","read","transportLabel","connection","webMidiAvailable","webSerialAvailable","webUsbAvailable","usbConnect","devices","d","prompt","deviceCommands","serialConnect","ports","port","p","rawReader","portCommands","midiConnect","access","disconnectM8Devices","inputs","outputs","outputCommands","available","chosen","transport","protocolHandler","closing","closed","error","connectWithRetry","attempt","maxAttempts","connect","err","retryDelayMs","M8_AUDIO_CAPTURE_CONSTRAINTS","findM8Input","getM8AudioInputDevice","getM8InputStream","probe","requireM8","track","createM8Audio","ctx","connecting","resumeOnGesture","context","resume","disconnect","open","stream","source","destination","tracks","DIRS","MAX_LAYOUTS","cellId","rect","parseCellId","w","h","id","emptyLayout","createCursorMap","layouts","changed","options","touch","layout","key","oldest","addCell","saved","from","dir","to","moves","text","fromId","toId","textKey","wentBackwards","GUESS_COST","overlap$1","a1","b1","a0","b0","guessMove","cx","cy","best","cell","ox","oy","along","across","findPath","isGoal","cost","previous","lowest","candidate","c","path","at","step","skip","forbid","learned","guessed","toRect","next","m8_view_context_default","\"fontMode\"","\"playbackIndicator\"","\"screenGeometry\"","\"emptyCell\"","\"relativeHex\"","\"rowIndex\"","\"titleAsterisk\"","\"chainRef\"","\"phraseRef\"","\"note\"","\"velocity\"","\"instrumentRef\"","\"fxCommand\"","\"fxValue\"","\"transpose\"","\"ticks\"","\"volume\"","\"ppq\"","\"instrumentName\"","\"eqSlot\"","\"noteInterval\"","\"onOff\"","\"semitoneOffset\"","\"swing\"","\"instrumentType\"","\"name\"","\"parameter\"","\"option\"","\"action\"","\"fileEntry\"","\"_note\"","\"types\"","\"sampler\"","\"verified\"","\"commands\"","\"macrosynth\"","\"fmsynth\"","\"hypersynth\"","\"midiout\"","\"modulation\"","\"envelope\"","\"lfo\"","\"relativeCommands\"","\"description\"","OVERHANG_PX","ROW_WEIGHT","SAME_ROW_REACH","PANEL_COLUMN","ROW_NUMBER_WIDTH","name","gridRows","width","viewName","first","last","lines","line","row","FIXED_GRID_VIEWS","GRID_COLUMNS","hasFixedGrid","fixedGridLayout","columns","grid","idAt","column","snapToGrid","col","chars","rowOf","panelColumnFor","layoutSignature","token","panelColumn","charRect","textRuns","runs","visible","ch","covered","cellHasText","overlap","coversX","cursor","target","coversY","covers","center","rectDistance","ca","gapX","gapY","pointOffset","point","pointGoal","goalReached","goal","candidates","inGrid","cells","char","isRowNumber","run","pickGoal","goals","textAt","runEnd","besideClick","area","dy","dx","anyBeside","anyKnown","knownFirst","rightOfClick","score","tie","M8KeyMask","pressKeys","left","right","up","down","shift","play","opt","edit","keyMap","DIRECTION_MASK","MODIFIER_MASK","NOT_HELD_MASK","MAX_STEPS$1","MAX_EXPLORE","MAX_RETURN_STEPS","MAX_GOALS","MOVE_TIMEOUT_MS","MOVE_GRACE_MS","PASSIVE_SETTLE_MS","sameRect","singleDirection","dirs","createCursorNavigator","currentKeysMask","holdMask","waitingForKeyAck","moveTimeout","passive","store","getRect","current","layoutKey","system","getScreenLines","clearMoveTimeout","scheduleMoveTimeout","advance","ms","finish","reached","tap","bus","isGoalCell","r","towardGoal","leavesGrid","planLayout","cursorMap","chooseMove","known","here","retry","move","giveUp","localMinimum","onRow","leftOfCursor","cursorRow","towardRow","blockedNextToRow","clicked","settle","reason","onKey","onRect","unsubscribeRect","carried","fixed","normalizeForSearch","FILE_BROWSER_EXTENSION_SUFFIX_REGEX","getFileBrowserSnapshotFingerprint","snapshot","getSelectedFileBrowserEntry","isFileBrowserView","normalizedViewName","normalizedViewTitle","isParentDirectoryEntry","entry","isDirectoryEntry","isM8FileEntry","getFileEntrySearchTerms","trimmedEntry","normalizedFullName","normalizedBaseName","classifyFileBrowserEntry","parseFileBrowserScreen","rowText","title","entries","cursorText","mergeFileBrowserOrder","screen","order","anchor","offset","index","position","tokenize","TITLE_STOP_WORDS","getDirectoryHintTokens","targetText","viewTitle","scoreDirectoryRelevance","directoryKey","related","hints","wait","createHostIO","hasBus","getKeySink","sendKeys","pressAndRelease","delayMs","waitForTextUnderCursorChange","settled","unsubscribe","timeout","currentValue","previousValue","timeoutMs","getFileBrowserSnapshot","unsubscribeText","unsubscribeLine","unsubscribeCursor","unsubscribeViewName","unsubscribeViewTitle","previousFingerprint","previousSnapshot","checkForChange","currentSnapshot","browseLog","details","JUMP_ROWS","MIN_FULL_WINDOW","MAX_DEPTH","MAX_SCREENS_PER_LEVEL","createLevel","opposite","direction","createFileBrowserSearch","io","fullWindowRows","jumpClampsAtEnd","foldersFirst","normalizedTarget","targetIncludesExtension","isActive","stepFileBrowser","keyMask","nextSnapshot","waitForFileBrowserSnapshotChange","stepMask","jumpMask","applyAlphabeticalSort","entryMatchesTarget","exact","term","comparableName","trimmed","primarySearchTerm","typedTarget","readScreen","absorb","level","viewKey","pageLevel","jumped","moved","reachedEnd","fromIndex","toIndex","travelled","directionToward","moveCursorToKey","startScreen","reversed","delta","way","selectVisibleMatch","levelFiles","reviewSortedLevel","files","file","firstFile","folder","nothingLeftBelow","alphabeticalHint","pickDirection","pickNextDirectory","cursorIndex","kind","onScreen","distance","rank","climbToParent","searchLevel","depth","pages","picked","directory","entered","outcome","parseHexValue","cleaned","parsed","parseIntValue","FLOAT_TOKEN_REGEX","parseFloatTokens","tokens","raw","negative","unsigned","dotIndex","intDigits","decDigits","intValue","decValue","decScale","tickHundredths","valueHundredths","pickFloatTokenIndex","hint","hintText","detectFloatCursorFocus","unsignedRaw","inIntPart","inDecPart","floatHintMatchesToken","isCursorOnForeignFloat","ownIndex","formatFloatFromTicks","hundredths","ticks","sign","absolute","intPart","NOTE_ORDER","parseNoteValue","label","noteName","octave","noteOffset","createFloatEditor","targetFloat","tokenIndex","lastTokens","readFloatState","initialTokens","debugLog","initialToken","targetTicks","makeFocusStats","statsByFocus","UNIT_IN_TICKS","startedAt","focus","sideControlDisabled","driftedToForeignField","previousTicks","valueTwoStepsAgo","bestTicks","bestDistance","fruitlessStreak","reachedTarget","gaveUp","getMaskForChannel","positiveKey","negativeKey","effectiveDirection","applyBurst","stats","mask","beforeLine","presses","waitForCurrentLineChange","ensureFloatCursorSide","currentFocusOf","side","beforeHint","towardDot","awayFromDot","landed","diffTicks","isOscillating","crossesTarget","desiredSide","focusStats","upDownProbeGateTicks","beforeTicks","focusAtPress","channelAtPress","nextState","deltaTicks","pressedStats","snapStartedAt","snapState","gap","snapDistance","snapStats","snapChannel","snapPresses","beforeSnapTicks","afterSnap","finalToken","success","m8Graph_generated_default","\"to\"","\"dir\"","\"keys\"","\"weight\"","\"back\"","\"via\"","\"cursor\"","\"label\"","viewlist_default","buildGraph","graph","edgeKeys","rawKey","list","json","edges","perFrom","e","viewGraph","viewNames","getViewGraph","getViewNames","s","STABILIZE_MS","defaultCursor","AssemblyCursorExtractor","now","completedCursor","assembly","newAssembly","selectionCursor","incrementalSelection","isHorizontal","prev","top","bottom","lineLeft","lineRight","prevSpan","lineTop","lineBottom","outerW","outerH","sample","horizontals","verticals","refCursor","prevCenterX","prevCenterY","v","rightX","latestTs","centerX","centerY","tlX","tlRect","tlY","corner","height","rgbHex$1","registerViewExtractor","CursorAssembly","AssemblyViewNameExtractor","noSpaces","titleRaw","normalized","fg","bg","upto","currentGx","partialRaw","partial","pick","gx","cmd","offX","cellW","gy","cellH","extractor","CharacterGridTracker","sortedKeys","toRemove","cursorRect","pos","startGx","endGx","textUnderCursor","textHighlightColor","currentLine","maxX","fgRow","bgRow","charGridTracker","debugRectEndTime","win","cursorExtractor","cursorColor","prevPos","prevSelectionMode","selectionMode","prevRect","modeJustChanged","systemInfoHandler","info","onText","events","getCurrentState","macroStatus","textGridToPixel","cellMetrics","gridX","gridY","keyNamesToMask","createHostMethods","guarded","fn","fallback","navigateToView","navigateTo","hex","editors","targetInt","setValueFloat","noteString","targetString","searchInCurrentLine","browseFile","getScreenCellColors","enableMidiTransport","TICKS_PER_STEP$1","createMidiTransport","lastTickAt","msPerTick","epoch","timeStamp","onMessage","emit","spp","generation","started","m8","opening","calculateKeySequence","sequence","targetValue","diff","absDiff","steps16","stepsToTake","calculateNoteKeySequence","currentIndex","targetIndex","createValueEditors","targetHex","currentText","isInitialDashDash","startValue","beforePrime","newText","newValue","beforeEnterEdit","keySequence","finalText","finalValue","startTime","beforeStepText","bestValue","newDistance","diffToBest","absDiffToBest","directionToBest","beforeSnapStepText","snappedValue","targetNoteString","currentNormalized","afterFirst","parsedTarget","currentNote","precomputedSequence","precomputedFinal","seen","useOctaveStep","positiveDirection","reverseKey","previousNote","previousDistance","newNote","beforeReverseStep","steppedBack","getSearchSource","matches","normalizedValue","initialCursorText","bottomNoChangeCount","bottomStartTime","bottomSeedValues","normalizedCurrent","nextText","searchSource","normalizedText","createMacroRunner","queue","lastSent","active","onKeyDown","cancel","activate","onKeyEcho","cancelOnKeydown","deactivate","echoed","sent","initialStoreState","createM8Store","initial","listeners","allListeners","listener","set","zeroHeuristic","defaultEdgeCost","aStar","g","openSet","cameFrom","gScore","fScore","node","heuristic","lowestF","bestVal","val","cur","link","neighbors","edgeCost","tentativeG","MAX_STEPS","STEP_TIMEOUT_MS","SETTLE_MS","CURSOR_TIMEOUT_MS","CURSOR_SETTLE_MS","toKeyMasks","withShift","opts","map","seq","directions","FALLBACK_EDGES","edgeSignature","withFinalRelease","frames","GOAL_STATE","stateId","createViewNavigation","viewSet","steps","pendingResolve","session","outcomes","lastView","unsubscribeHistory","timer","clearTimer","resolveTarget","wanted","outcomeKey","movesFrom","groups","moveCursor","sig","group","back","others","stateGraph","before","nav","nextEdge","res","buttonCell","cursorCovers","complete","finishStep","reachedView","edge","executeNextStep","runButton","mySession","runner","goalTitle","startTitle","onChange","running","unsubscribeView","unsubscribeMacro","targetViewTitle","STATE_KEYS","createM8Engine","payload","macro","cursorNavigator","viewNavigation","midiTransport","methods","dirty","flushScheduled","lastCursor","lastText","flush","unsubscribeStore","m8_parameter_views_default","\"rows\"","\"zones\"","\"formats\"","\"unverified\"","\"WAVS\"","\"MACRO\"","\"SAMPL\"","\"FM\"","\"HYPER\"","\"EXTERN\"","\"MIDI\"","rawViews","instrumentTypes$1","instrumentTypeNames","def","parent","own","getParameterLayout","slice","cond","detectSection","sections","linesUpward","upper","hit","detectSlot","zone","z","zoneId","pattern","instrumentKeyFromText","letters","prefix","instrumentTypeFromLines","getInstrumentType","lookupVocabulary","norm","instrumentKey","k","camel","LABEL_SHAPE","OPERATOR_ROWS","OPERATOR_MOD_VALUE","operatorCells","cellOf","letter","rowKey","vocab","modSlotEntry","values","dest","slot","CC_LABEL","CC_VALUE_WIDTH","readParameterCells","m","found","zones","valueX","valueWidth","cc","nextIsLabel","base","NONE","detectFormat","parseValue","format","n","describeOption","t","fxCommands","instrumentTypes","VERIFIED_TYPES","typesOffering","offering","lookupFxCommand","category","mod","isRelativeFx","getInstrumentFxCommands","type","RELATIVE_FX_RULE","hex2$1","hex1","times","signed8","fmtSigned","formatFxValue","formatIncrement","inc","PITCH_CLASSES","noteToIndex","pc","indexToNote","KEY_NAMES","TICKS_PER_STEP","rowsPerStep","describeTic","where","describeFxValue","semis","eighths","limit","analyzeFxLane","carriedIn","atLimit","repStart","rows","cmdCell","lane","valCell","describeFxLane","src","relativeResetBy","inst","analyzeRelativeFx","states","resetStep","resetBy","trigger","trace","carriedLane","applied","viaRep","st","fmtOffset","sinceText","RESET_HINT","describeRelativeFx","rep","describeRelativeReset","SCALE_INTERVALS","SCALES","INTERVAL_NAMES","normalizeInterval","describeInterval","semitone","matchScaleShapes","enabledSemitones","hits","scale","root","analyzeScale","enabledNotes","notes","enabled","shapes","detuned","scaleSummaryParts","shape","formatOffset","EMPTY_PATTERNS","isEmptyRaw","parseHex","extractFromLine","parseViewTitle","extractViewId","rawTitle","markerMatch","marker","parts","stripPlaybackIndicator","parseColumn","empty","keepCase","fileKindOf","numericTypes","decimalTypes","hexValue","ext","parseRowIndex","rf","findColumn","cols","cursorX","bestDist","dist","parseRow","fields","viewDef","squash$1","SCREEN_MARGIN","screenDataLine","screenMatchesCursor","PHRASE_STEPS","GRID_FIRST_ROW","GRID_LAST_ROW","parseGridScreen","isSeparator","readPlayPositions","isMarker","readPhraseSteps","y0","cursorStep","carriedRepLane","caret","nearestFxLane","dists","min","closest","parseScaleNote","en","readScaleNotes","getParameterContext","head","trusted","instrumentType","pad","padded","section","activeField","getSemanticContext","isGridView","viewDescription","paramLayout","rawLine","scaleLayout","field","fxLane","relativeFx","resetsRelative","phrase","scaleNote","describeContextParts","describeParameterContext","scaleParts","describeScaleContext","partsOf","f","formatFieldValue","meaning","describeFxCell","describeInstrumentCell","describeTableVolume","explained","fxInfoText","cmdKey","cmdField","rowHex","describeContext","repeated","relative","laneText","shown","described","INSTRUMENT_TYPE_LABELS","SECTION_NAMES","FILE_KIND_TEXT","detune","formatParameterValue","unit","named","signed","YAM8D_FRAME_NAME","DEFAULT_HANDSHAKE_TIMEOUT_MS","hostBackend","remote","method","engineBackend","engine","disposableMessenger","removers","inner","transfer","remove","inIframe","isUserCancel","getDeviceSupport","getDefaultState","M8ClientImpl","config","mode","markedByYam8d","messenger","WindowMessenger","DebugMessenger","disposable","handshake","ChildHandshake","timedOut","conn","lastError","opened","backend","subs","semantic","createM8Client","client","createM8ClientSync","client_default","M8_THEME_ROLES","M8_THEME_VIEW","squash","hex2","rgbToHex","hsvToHex","hue","sat","q","ROLE_BY_LABEL","role","parseThemeScreen","colors","rgbHex","observeThemeColors","background","titles"],"sources":["../src/engine/eventBus.ts","../src/engine/protocol.ts","../src/engine/queuedReaders.ts","../src/engine/connection.ts","../src/engine/connectWithRetry.ts","../src/engine/audio.ts","../src/engine/cursorMap.ts","../src/m8-view-context.json","../src/engine/cursorTargets.ts","../src/engine/keys.ts","../src/engine/cursorNavigator.ts","../src/engine/host/fileBrowserHelpers.ts","../src/engine/host/hostIO.ts","../src/engine/host/fileBrowserSearch.ts","../src/engine/host/valueParsers.ts","../src/engine/host/floatEditor.ts","../src/engine/data/m8Graph.generated.json","../src/engine/data/viewlist.json","../src/engine/viewGraph.ts","../src/engine/viewExtractor.ts","../src/engine/host/hostState.ts","../src/engine/host/hostMethods.ts","../src/engine/host/midiTransport.ts","../src/engine/host/keySequences.ts","../src/engine/host/valueEditors.ts","../src/engine/macroRunner.ts","../src/engine/store.ts","../src/engine/astar.ts","../src/engine/viewNavigation.ts","../src/engine/engine.ts","../src/m8-parameter-views.json","../src/parameterViews.ts","../src/fxCommands.ts","../src/fxInsight.ts","../src/scaleInsight.ts","../src/viewContext.ts","../src/client.ts","../src/theme.ts"],"sourcesContent":["// A minimal typed event emitter.\n\n// biome-ignore lint/suspicious/noExplicitAny: library code, allowed to do that\nexport const createEventBus = <T extends Record<string, (...args: any[]) => void>>() => {\n // biome-ignore lint/suspicious/noExplicitAny: library code, allowed to do that\n const eventMap = {} as Record<keyof T, Set<(...args: any[]) => void>>\n\n return {\n emit: <K extends keyof T>(event: K, ...args: Parameters<T[K]>) => {\n if (!(event in eventMap)) {\n return\n }\n // Iterate a copy: a listener added while this event is dispatched (e.g. a macro\n // started from a key echo) must not receive the same event.\n for (const cb of [...eventMap[event]]) {\n cb(...args)\n }\n },\n\n on: <K extends keyof T>(event: K, callback: T[K]) => {\n if (!eventMap[event]) {\n eventMap[event] = new Set()\n }\n eventMap[event].add(callback)\n },\n\n off: <K extends keyof T>(event: K, callback: T[K]) => {\n if (!eventMap[event]) {\n return\n }\n eventMap[event].delete(callback)\n },\n }\n}\n","// Parses M8 display protocol packets (draw rect / character / wave, key echo, system info)\n// and dispatches them as typed events.\n\nimport { createEventBus } from './eventBus'\n\nconst littleEndian = true\nexport type RectCommand = ReturnType<typeof parseRectCommand>\nconst parseRectCommand = (data: DataView, length: number) => {\n const x = data.getUint16(1, littleEndian)\n const y = data.getUint16(3, littleEndian)\n switch (length) {\n // 1b: command | 2b: x | 2b: y\n case 5:\n return {\n type: 1,\n pos: { x, y },\n size: { width: 1, height: 1 },\n } as const\n // 1b: command | 2b: x | 2b: y | 1b: r | 1b: g | 1b: b\n case 8:\n return {\n type: 2,\n pos: { x, y },\n size: { width: 1, height: 1 },\n color: { r: data.getUint8(5), g: data.getUint8(6), b: data.getUint8(7) },\n } as const\n // 1b: command | 2b: x | 2b: y | 2b: width | 2b: height\n case 9:\n return {\n type: 3,\n pos: { x, y },\n size: { width: data.getUint16(5, littleEndian), height: data.getUint16(7, littleEndian) },\n } as const\n // 1b: command | 2b: x | 2b: y | 2b: width | 2b: height | 1b: r | 1b: g | 1b: b\n case 12:\n return {\n type: 4,\n pos: { x, y },\n size: { width: data.getUint16(5, littleEndian), height: data.getUint16(7, littleEndian) },\n color: { r: data.getUint8(9), g: data.getUint8(10), b: data.getUint8(11) },\n } as const\n }\n const bytes = []\n for (let i = 0; i < length; i += 1) {\n bytes.push(data.getUint8(i).toString(16).padStart(2, '0'.toUpperCase()))\n }\n console.error(`Invalid length (${length}) of rect command data: ${bytes.join(' ')}`)\n // throw new Error(`Invalid length (${length}) of rect command data`)\n // never undefined\n return {\n type: 0,\n pos: { x, y },\n size: { width: 0, height: 0 },\n } as const\n}\n\nexport type CharacterCommand = ReturnType<typeof parseCharacterCommand>\nconst parseCharacterCommand = (data: DataView, length: number) => {\n if (length !== 12) {\n throw new Error(`Invalid length (${length}) of char command data`)\n }\n const char = {\n character: String.fromCharCode(data.getUint8(1)),\n pos: { x: data.getUint16(2, littleEndian), y: data.getUint16(4, littleEndian) },\n foreground: { r: data.getUint8(6), g: data.getUint8(7), b: data.getUint8(8) },\n background: { r: data.getUint8(9), g: data.getUint8(10), b: data.getUint8(11) },\n }\n return char\n}\n\nexport type WaveCommand = ReturnType<typeof parseWaveCommand>\nconst parseWaveCommand = (data: DataView, length: number) => {\n if (length < 4 || length > 484) {\n throw new Error(`Invalid length (${length}) of wave command data`)\n }\n const wave: number[] = []\n for (let i = 4; i < length; i += 1) {\n wave.push(data.getUint8(i))\n }\n return {\n color: { r: data.getUint8(1), g: data.getUint8(2), b: data.getUint8(3) },\n wave,\n }\n}\n\nexport type KeyCommand = ReturnType<typeof parseKeyCommand>\nconst parseKeyCommand = (data: DataView, length: number) => {\n if (length !== 3) {\n throw new Error(`Invalid length (${length}) of key command data`)\n }\n return {\n keys: data.getUint8(1),\n }\n}\n\nexport type SystemCommand = ReturnType<typeof parseSystemCommand>\nconst device = ['Headless', 'Beta M8', 'M8 Model:01', 'M8 Model:02'] as const\nconst parseSystemCommand = (data: DataView, length: number) => {\n if (length !== 6) {\n throw new Error(`Invalid length (${length}) of system command data`)\n }\n\n return {\n model: device[data.getUint8(1)],\n version: `v${data.getUint8(2)}.${data.getUint8(3)}.${data.getUint8(4)}`,\n fontMode: data.getUint8(5) as 0 | 1 | 2,\n }\n}\n\n/** System command plus the layout metrics derived from the model and font mode. */\nexport type SystemInfos = {\n model: SystemCommand['model']\n fontMode: SystemCommand['fontMode']\n spacingX: number\n spacingY: number\n offX: number\n offY: number\n screenWidth: number\n screenHeight: number\n rectOffset: number\n}\n\nexport type ProtocolEvents = {\n rect: (data: RectCommand) => void\n text: (data: CharacterCommand) => void\n wave: (data: WaveCommand) => void\n key: (data: KeyCommand) => void\n system: (data: SystemCommand) => void\n systemInfo: (data: SystemInfos) => void\n any: (data: Uint8Array<ArrayBufferLike>) => void\n}\n\nexport const protocol = () => {\n let lastSystemInfo: SystemCommand | undefined\n const eventBus = createEventBus<ProtocolEvents>()\n\n // Cached enriched system infos (includes layout metrics)\n let cachedSystemInfos: SystemInfos | undefined\n\n const computeLayoutMetrics = (sys: SystemCommand | undefined) => {\n // Defaults assume Model:02 small font\n let spacingX = 12\n let spacingY = 14\n const offX = 0\n let offY = 0\n let screenWidth = 480\n let screenHeight = 320\n let rectOffset = 0\n\n if (!sys) return { spacingX, spacingY, offX, offY, screenWidth, screenHeight, rectOffset }\n\n const isV2 = sys.model === 'M8 Model:02' || sys.model === 'Beta M8'\n const fm = sys.fontMode // 0 small, 1 bold, 2 large/no-scope\n\n if (isV2) {\n screenWidth = 480\n screenHeight = 320\n if (fm === 0) {\n spacingX = 12\n spacingY = 14\n offY = 0\n rectOffset = 0\n } else if (fm === 1) {\n spacingX = 12\n spacingY = 14\n offY = 0\n rectOffset = 0\n } else {\n spacingX = 15\n spacingY = 16\n offY = 0\n rectOffset = -45\n }\n } else {\n // Model:01\n screenWidth = 320\n screenHeight = 240\n if (fm === 0) {\n spacingX = 8\n spacingY = 10\n offY = 0\n rectOffset = 0\n } else {\n spacingX = 10\n spacingY = 12\n offY = 0\n rectOffset = -40\n }\n }\n return { spacingX, spacingY, offX, offY, screenWidth, screenHeight, rectOffset }\n }\n\n const dispatch = (data: DataView, dataLength: number) => {\n try {\n if (dataLength <= 0) {\n return\n }\n\n const buffer = new Uint8Array(data.buffer, data.byteOffset, dataLength)\n eventBus.emit('any', buffer)\n\n switch (data.getUint8(0)) {\n case 0xfe: {\n // draw rect\n const command = parseRectCommand(data, dataLength)\n if (command) {\n eventBus.emit('rect', command)\n }\n break\n }\n case 0xfd: // draw character\n eventBus.emit('text', parseCharacterCommand(data, dataLength))\n break\n case 0xfc: // draw wave\n eventBus.emit('wave', parseWaveCommand(data, dataLength))\n break\n case 0xfb: // key events\n eventBus.emit('key', parseKeyCommand(data, dataLength))\n break\n case 0xff: {\n // system info\n lastSystemInfo = parseSystemCommand(data, dataLength)\n eventBus.emit('system', lastSystemInfo)\n const metrics = computeLayoutMetrics(lastSystemInfo)\n cachedSystemInfos = {\n model: lastSystemInfo.model,\n fontMode: lastSystemInfo.fontMode,\n ...metrics,\n }\n eventBus.emit('systemInfo', cachedSystemInfos)\n break\n }\n default:\n console.warn(`Unknown byte 0x${data.getUint8(0).toString(16).padStart(2, '0')} as command`)\n break\n }\n } catch (_e) {}\n }\n\n return {\n eventBus,\n dispatch,\n getSystemInfo: () => lastSystemInfo,\n getSystemInfos: () => {\n // Build from last system if not yet computed\n if (!cachedSystemInfos) {\n const metrics = computeLayoutMetrics(lastSystemInfo)\n if (lastSystemInfo) {\n cachedSystemInfos = {\n model: lastSystemInfo.model,\n fontMode: lastSystemInfo.fontMode,\n ...metrics,\n }\n } else {\n // Unknown yet; return defaults\n cachedSystemInfos = {\n model: 'Headless',\n fontMode: 0,\n ...metrics,\n }\n }\n }\n return cachedSystemInfos\n },\n }\n}\n","export type SlipState = 'read' | 'escape' | 'error'\n\nexport const createSlipParser = () => {\n const buffer = new Uint8Array(1024)\n const view = new DataView(buffer.buffer)\n let dataLength = 0\n let state: SlipState = 'read'\n\n const finalizePacket = (): DataView | undefined => {\n if (dataLength <= 0) {\n dataLength = 0\n return undefined\n }\n const out = new Uint8Array(dataLength)\n out.set(buffer.subarray(0, dataLength))\n const dv = new DataView(out.buffer)\n dataLength = 0\n return dv\n }\n\n const pushByte = (byte: number) => {\n if (dataLength >= buffer.length) {\n // overflow safeguard: drop packet and reset\n dataLength = 0\n state = 'error'\n return\n }\n view.setUint8(dataLength++, byte)\n }\n\n const feed = (byte: number): DataView | undefined => {\n switch (state) {\n case 'read':\n switch (byte) {\n case 0xc0:\n return finalizePacket()\n case 0xdb:\n state = 'escape'\n return undefined\n default:\n pushByte(byte)\n return undefined\n }\n case 'escape':\n switch (byte) {\n case 0xdc: // escaped 0xc0\n pushByte(0xc0)\n state = 'read'\n return undefined\n case 0xdd: // escaped 0xdb\n pushByte(0xdb)\n state = 'read'\n return undefined\n default:\n state = 'error'\n return undefined\n }\n case 'error':\n if (byte === 0xc0) {\n // reset on end marker\n state = 'read'\n dataLength = 0\n }\n return undefined\n }\n }\n\n return { feed }\n}\n\n// USB reader with SLIP framing -> queued DataViews\nexport const queuedUsbReader = (device: USBDevice) => {\n const parser = createSlipParser()\n const messages: DataView[] = []\n let terminated = false\n let resolver: ((value: { terminated: true } | { terminated: false; data: DataView }) => void) | undefined\n\n const pump = async () => {\n try {\n while (true) {\n const result = await device.transferIn(3, 512)\n if (result.status === 'stall') {\n terminated = true\n if (resolver && messages.length === 0) {\n resolver({ terminated: true })\n resolver = undefined\n }\n break\n }\n const data = result.data\n if (!data) continue\n for (let i = 0; i < data.byteLength; i++) {\n const maybe = parser.feed(data.getUint8(i))\n if (maybe) {\n messages.push(maybe)\n if (resolver) {\n const msg = messages.shift()\n if (msg) {\n resolver({ terminated: false, data: msg })\n resolver = undefined\n }\n }\n }\n }\n }\n } catch (_e) {\n terminated = true\n if (resolver && messages.length === 0) {\n resolver({ terminated: true })\n resolver = undefined\n }\n }\n }\n\n pump()\n\n return async () => {\n if (terminated && messages.length === 0) {\n return { terminated: true as const }\n }\n if (messages.length > 0) {\n const msg = messages.shift()\n if (msg) {\n return { terminated: false as const, data: msg }\n }\n }\n return new Promise<{ terminated: true } | { terminated: false; data: DataView }>((resolve) => {\n resolver = resolve\n })\n }\n}\n\n// Serial reader with SLIP framing -> queued DataViews\nexport const queuedSerialReader = (reader: ReadableStreamDefaultReader<Uint8Array<ArrayBufferLike>>) => {\n const parser = createSlipParser()\n const messages: DataView[] = []\n let terminated = false\n let resolver: ((value: { terminated: true } | { terminated: false; data: DataView }) => void) | undefined\n\n const pump = async () => {\n try {\n while (true) {\n const { value, done } = await reader.read()\n if (done) {\n terminated = true\n if (resolver && messages.length === 0) {\n resolver({ terminated: true })\n resolver = undefined\n }\n break\n }\n if (!value) continue\n const chunk = value as unknown as Uint8Array\n for (let i = 0; i < chunk.byteLength; i++) {\n const maybe = parser.feed(chunk[i])\n if (maybe) {\n messages.push(maybe)\n if (resolver) {\n const msg = messages.shift()\n if (msg) {\n resolver({ terminated: false, data: msg })\n resolver = undefined\n }\n }\n }\n }\n }\n } catch (_e) {\n terminated = true\n if (resolver && messages.length === 0) {\n resolver({ terminated: true })\n resolver = undefined\n }\n }\n }\n\n pump()\n\n return async () => {\n if (terminated && messages.length === 0) {\n return { terminated: true as const }\n }\n if (messages.length > 0) {\n const msg = messages.shift()\n if (msg) {\n return { terminated: false as const, data: msg }\n }\n }\n return new Promise<{ terminated: true } | { terminated: false; data: DataView }>((resolve) => {\n resolver = resolve\n })\n }\n}\n","// Connects to the M8 over Web Serial, WebUSB or Web MIDI (SysEx), reads its display\n// stream into the protocol parser and sends key / note / program change commands.\n\nimport { protocol } from './protocol'\nimport { queuedSerialReader, queuedUsbReader } from './queuedReaders'\n\nexport type M8DeviceTransport = 'serial' | 'usb' | 'midi'\n\nexport interface M8ConnectOptions {\n /** Which browser API to use. 'auto' (default) picks Web Serial, then WebUSB, then Web MIDI, by availability. */\n transport?: M8DeviceTransport | 'auto'\n /**\n * false: only reuse a device the page was already granted, never open the browser's device\n * picker (which needs a user gesture). Rejects when there is none. Default true.\n */\n prompt?: boolean\n}\n\nconst vendorId = 0x16c0\nconst productId = 0x048a\n// the device processes frame by frame ingress messages - which means each button press needs to wait for ~5ms currently\n// doubling the time is a good safe margin\nconst waitTime = 20\n\nconst usbWriter = (device: USBDevice) => (data: Uint8Array<ArrayBuffer>) => device.transferOut(3, data)\nconst serialWriter = (writer: WritableStreamDefaultWriter<Uint8Array<ArrayBufferLike>>) => (data: Uint8Array<ArrayBuffer>) => writer.write(data)\n\nconst midiCommands = (output: MIDIOutput) => {\n const commands: Uint8Array<ArrayBuffer>[] = []\n const frameCheck = () => {\n const command = commands.shift()\n if (command) {\n output.send(command)\n }\n }\n\n const interval = setInterval(frameCheck, waitTime)\n\n return {\n stop: () => clearInterval(interval),\n resetScreen: () => {\n commands.push(new Uint8Array([0xf0, 0x00, 0x02, 0x61, 0x00, 0x00, 0x44, 0xf7]))\n commands.push(new Uint8Array([0xf0, 0x00, 0x02, 0x61, 0x00, 0x00, 0x45, 0x52, 0xf7]))\n },\n sendKeys: (keys: number) => {\n const press = keys\n const msb = 0 | (press & 0x80 ? 1 << 2 : 0)\n\n commands.push(new Uint8Array([0xf0, 0x00, 0x02, 0x61, 0x00, msb, 0x00, 0x43, press & 0x7f, 0xf7]))\n },\n sendNoteOn: (note: number, velocity: number) => {\n velocity = Math.min(0x7f, Math.max(0x00, velocity))\n const msb = 0 | (note & 0x80 ? 1 << 2 : 0) | (velocity & 0x80 ? 1 << 3 : 0)\n commands.push(new Uint8Array([0xf0, 0x00, 0x02, 0x61, 0x00, msb, 0x00, 0x48, note & 0x7f, velocity & 0x7f, 0xf7]))\n },\n sendNoteOff: () => {\n const msb = 0 | (1 << 2)\n commands.push(new Uint8Array([0xf0, 0x00, 0x02, 0x61, 0x00, msb, 0x48, 0x7f, 0xf7]))\n },\n sendProgramChange: (program: number, channel: number = 0) => {\n // MIDI Program Change: 0xC0-0xCF (C = 12 in decimal, shifted left 4 bits)\n // Program change message: status byte = 0xC0 + channel, data byte = program number (0-127)\n const safeChannel = Math.min(15, Math.max(0, channel))\n const safeProgram = Math.min(127, Math.max(0, program))\n const statusByte = 0xc0 | safeChannel\n commands.push(new Uint8Array([statusByte, safeProgram]))\n },\n }\n}\n\nconst commands = (writer: ReturnType<typeof usbWriter> | ReturnType<typeof serialWriter>) => {\n const commands: Uint8Array<ArrayBuffer>[] = []\n\n const frameCheck = () => {\n const command = commands.shift()\n if (command) {\n void Promise.resolve(writer(command)).catch(() => {\n // The device went away; the reader reports the termination.\n })\n }\n }\n\n const interval = setInterval(frameCheck, waitTime)\n\n return {\n stop: () => clearInterval(interval),\n resetScreen: async () => {\n commands.push(new Uint8Array([0x44]))\n commands.push(new Uint8Array([0x45, 0x52]))\n },\n sendKeys: (keys: number) => {\n const press = keys\n commands.push(new Uint8Array([0x43, press]))\n },\n sendNoteOn: async (note: number, velocity: number) => {\n commands.push(new Uint8Array([0x4b, note, velocity]))\n },\n sendNoteOff: async () => {\n commands.push(new Uint8Array([0x4b, 255]))\n },\n sendProgramChange: async (program: number, _channel: number = 0) => {\n // USB/Serial: Use 0x4c prefix for program change (similar to 0x4b for note on/off)\n const safeProgram = Math.min(127, Math.max(0, program))\n commands.push(new Uint8Array([0x4c, safeProgram]))\n },\n }\n}\n\nconst M8_SYSEX_HEADER = [0xf0, 0x00, 0x02, 0x61, 0x00]\nconst SYSEX_END = 0xf7\n\nfunction isM8Sysex(data: Uint8Array<ArrayBuffer>) {\n if (data.length < 5) return false\n for (let i = 0; i < M8_SYSEX_HEADER.length; i++) {\n if (data[i] !== M8_SYSEX_HEADER[i]) return false\n }\n return true\n}\n\nexport function decode7BitPacked(data: Uint8Array): Uint8Array {\n const out: number[] = []\n\n let i = 0\n while (i < data.length) {\n const msbByte = data[i++]\n if (i >= data.length) break // truncated group, bail\n\n const remaining = data.length - i\n const groupLen = Math.min(7, remaining)\n\n for (let bit = 0; bit < groupLen; bit++) {\n const low7 = data[i++] // 0x00–0x7F\n const msb = (msbByte >> bit) & 0x01\n out.push((msb << 7) | low7) // reattach MSB -> full 8-bit\n }\n }\n\n return new Uint8Array(out)\n}\n\nfunction decodeM8Sysex(encoded: Uint8Array<ArrayBuffer>): Uint8Array | undefined {\n if (!isM8Sysex(encoded)) {\n console.log('not M8Sysex')\n return undefined\n }\n\n if (encoded[encoded.length - 1] !== SYSEX_END) {\n console.log('Unterminated SysEx')\n return undefined\n }\n const locs = [...encoded].map((x, i) => (x === SYSEX_END ? `${i}` : undefined)).filter((x) => !!x)\n if (locs.length > 1) {\n console.warn(encoded)\n }\n const packed = encoded.slice(M8_SYSEX_HEADER.length, encoded.length - 1)\n return decode7BitPacked(packed)\n}\n\nconst midiReader = (input: MIDIInput) => {\n const messages: DataView[] = []\n\n // Pending buffer of raw MIDI bytes across events to safely reassemble SysEx\n let pending = new Uint8Array(0) as unknown as Uint8Array<ArrayBufferLike>\n\n let resolver:\n | ((\n value:\n | {\n terminated: true\n }\n | {\n terminated: false\n data: DataView\n },\n ) => void)\n | undefined\n\n // Find the first occurrence of the M8 SysEx header in a buffer\n const findHeaderIndex = (buf: Uint8Array<ArrayBufferLike>, start = 0): number => {\n const header = M8_SYSEX_HEADER\n for (let i = start; i <= buf.length - header.length; i++) {\n let match = true\n for (let j = 0; j < header.length; j++) {\n if (buf[i + j] !== header[j]) {\n match = false\n break\n }\n }\n if (match) return i\n }\n return -1\n }\n\n const appendBytes = (a: Uint8Array<ArrayBufferLike>, b: Uint8Array<ArrayBufferLike>): Uint8Array<ArrayBufferLike> => {\n const merged = new Uint8Array(a.length + b.length) as unknown as Uint8Array<ArrayBufferLike>\n merged.set(a, 0)\n merged.set(b, a.length)\n return merged\n }\n\n const processChunk = (chunk: Uint8Array<ArrayBufferLike>) => {\n // Accumulate incoming bytes\n pending = appendBytes(pending, chunk)\n\n while (true) {\n // Align to header if present; if not, keep a short tail to catch split headers\n let hdrIdx = findHeaderIndex(pending, 0)\n if (hdrIdx === -1) {\n // Keep only the last header.length - 1 bytes in case the header is split across chunks\n if (pending.length > M8_SYSEX_HEADER.length - 1) {\n pending = pending.slice(pending.length - (M8_SYSEX_HEADER.length - 1))\n }\n break\n }\n\n // Drop any leading noise before header\n if (hdrIdx > 0) {\n pending = pending.slice(hdrIdx)\n hdrIdx = 0\n }\n\n // Look for SysEx end after header\n const endIdx = pending.indexOf(SYSEX_END, M8_SYSEX_HEADER.length)\n if (endIdx === -1) {\n // Need more bytes\n break\n }\n\n // Extract a single, complete M8 SysEx message [header ... 0xF7]\n const oneMessage = pending.slice(0, endIdx + 1)\n const decoded = decodeM8Sysex(oneMessage)\n if (decoded) {\n messages.push(new DataView(decoded.buffer, decoded.byteOffset, decoded.byteLength))\n }\n\n // Remove processed message and continue scanning in case of concatenated messages\n pending = pending.slice(endIdx + 1)\n }\n }\n\n const handler = (evt: MIDIMessageEvent) => {\n if (!evt.data || evt.data.length === 0) return\n // Process all bytes; this handles split and concatenated SysEx packets\n processChunk(evt.data as unknown as Uint8Array<ArrayBufferLike>)\n\n if (resolver && messages.length > 0) {\n const message = messages.shift()\n if (message) {\n resolver({\n terminated: false,\n data: message,\n })\n resolver = undefined\n }\n }\n }\n\n input.addEventListener('midimessage', handler)\n\n let stopped = false\n const stop = () => {\n if (stopped) return\n stopped = true\n input.removeEventListener('midimessage', handler)\n input.removeEventListener('statechange', onStateChange)\n if (resolver) {\n resolver({ terminated: true })\n resolver = undefined\n }\n }\n const onStateChange = () => {\n if (input.state === 'disconnected') stop()\n }\n input.addEventListener('statechange', onStateChange)\n\n const read = async () => {\n if (stopped || input.state === 'disconnected') {\n return { terminated: true }\n }\n\n if (messages.length > 0) {\n return {\n terminated: false,\n data: messages.shift(),\n }\n }\n return new Promise<{ terminated: true } | { terminated: false; data: DataView }>((resolve) => {\n resolver = resolve\n })\n }\n\n return { read, stop }\n}\n\ntype ReadFn = () => Promise<{ terminated: boolean; data?: DataView }>\n\n/** Pumps packets into the protocol handler; resolves once the reader terminates. */\nconst beginRead = async (read: ReadFn, handler: ReturnType<typeof protocol>) => {\n while (true) {\n const { terminated, data } = await read()\n if (data) {\n handler.dispatch(data, data.byteLength)\n }\n if (terminated) {\n break\n }\n }\n}\n\nconst transportLabel: Record<M8DeviceTransport, string> = { serial: 'Web Serial', usb: 'WebUSB', midi: 'Web MIDI' }\n\nexport const connection = () => {\n const webMidiAvailable = 'requestMIDIAccess' in navigator\n const webSerialAvailable = 'serial' in navigator\n const webUsbAvailable = 'usb' in navigator\n if (!webMidiAvailable && !webSerialAvailable && !webUsbAvailable) {\n return {\n browserSupport: false,\n } as const\n }\n\n const usbConnect = async (prompt: boolean) => {\n const devices = (await navigator.usb.getDevices()).filter((d) => d /* d.vendorId === vendorId && d.productId === productId */)\n // TODO: check out what happens with two devices attached\n if (devices.length > 1) {\n console.warn('Suspicious: there is more than one device, are multiple devices connected?')\n }\n if (devices.length <= 0 && !prompt) {\n throw new Error('No M8 USB device was granted to this page yet')\n }\n const device =\n devices.length <= 0\n ? await navigator.usb.requestDevice({\n filters: [{ vendorId: vendorId, productId: productId }],\n })\n : (devices.find((d) => d.vendorId === vendorId) ?? devices[0])\n\n if (!device) {\n throw new Error('No devices found')\n }\n await device.open()\n await device.selectConfiguration(1)\n await device.claimInterface(1)\n await device.controlTransferOut({\n requestType: 'class',\n recipient: 'interface',\n request: 0x22,\n value: 0x03,\n index: 0x01,\n })\n await device.controlTransferOut(\n {\n requestType: 'class',\n recipient: 'interface',\n request: 0x20,\n value: 0x00,\n index: 0x01,\n },\n //new Uint8Array([0x80, 0x25, 0x00, 0x00, 0x00, 0x00, 0x08]),\n )\n\n const deviceCommands = commands(usbWriter(device))\n return {\n transport: 'usb' as M8DeviceTransport,\n commands: deviceCommands,\n reader: queuedUsbReader(device),\n close: async () => {\n deviceCommands.stop()\n await device.close()\n },\n }\n }\n\n const serialConnect = async (prompt: boolean) => {\n const ports = (await navigator.serial.getPorts()).filter(() => {\n // const info = x.getInfo()\n return true // info.usbVendorId === vendorId // && info.usbProductId === productId\n })\n if (ports.length > 1) {\n console.warn('Suspicious: there are more than one port, when expecting just one.')\n }\n if (ports.length <= 0 && !prompt) {\n throw new Error('No M8 serial port was granted to this page yet')\n }\n\n const port =\n ports.length <= 0\n ? await navigator.serial.requestPort({ filters: [{ usbVendorId: vendorId /*, usbProductId: productId */ }] })\n : (ports.find((p) => p.getInfo().usbVendorId === vendorId) ?? ports[0])\n\n await port.open({\n baudRate: 115200,\n dataBits: 8,\n stopBits: 1,\n parity: 'none',\n bufferSize: 4096,\n })\n\n if (!port.readable || !port.writable) {\n throw new Error(\"Ports should've been read/writeable, but apparently they're not\")\n }\n\n const writer = port.writable.getWriter()\n const rawReader = port.readable.getReader()\n const portCommands = commands(serialWriter(writer))\n return {\n transport: 'serial' as M8DeviceTransport,\n commands: portCommands,\n reader: queuedSerialReader(rawReader),\n close: async () => {\n portCommands.stop()\n await rawReader.cancel().catch(() => undefined)\n rawReader.releaseLock()\n writer.releaseLock()\n await port.close()\n },\n }\n }\n\n const midiConnect = async (prompt: boolean) => {\n if (!prompt) {\n const permission = await navigator.permissions?.query({ name: 'midi', sysex: true } as unknown as PermissionDescriptor).catch(() => null)\n if (permission?.state !== 'granted') {\n throw new Error('Web MIDI SysEx access was not granted to this page yet')\n }\n }\n const access = await navigator.requestMIDIAccess({ sysex: true })\n\n // Disconnect any existing M8 connections first\n const disconnectM8Devices = (devices: MIDIPort[]) => {\n devices.forEach((device) => {\n // Check if device is M8 and open, then close it\n if (device.name === 'M8' && device.state === 'connected') {\n device.close()\n console.log(`Disconnected existing M8: ${device.name} (${device.id})`)\n }\n })\n }\n\n // Disconnect existing M8 inputs and outputs\n disconnectM8Devices([...access.inputs.values()])\n disconnectM8Devices([...access.outputs.values()])\n\n // Find M8 devices with better error handling\n const inputs = [...access.inputs.values()].filter((input) => input.name === 'M8')\n const outputs = [...access.outputs.values()].filter((output) => output.name === 'M8')\n\n if (inputs.length === 0 || outputs.length === 0) {\n throw new Error('M8 device not found. Please ensure your M8 is connected and powered on.')\n }\n\n // Handle multiple devices with clearer messaging\n if (inputs.length > 1) {\n console.warn(`Multiple M8 inputs detected. Using first device: ${inputs[0].name}`)\n }\n if (outputs.length > 1) {\n console.warn(`Multiple M8 outputs detected. Using first device: ${outputs[0].name}`)\n }\n\n const input = inputs[0]\n const output = outputs[0]\n\n // Check if devices are already open\n if (input.connection === 'open') {\n console.log('M8 input is already open')\n } else {\n await input.open()\n }\n\n if (output.connection === 'open') {\n console.log('M8 output is already open')\n } else {\n await output.open()\n }\n\n // Optional: Log connection info\n console.log(`Connected to M8 - Input: ${input.id}, Output: ${output.id}`)\n\n const outputCommands = midiCommands(output)\n const reader = midiReader(input)\n return {\n transport: 'midi' as M8DeviceTransport,\n commands: outputCommands,\n reader: reader.read,\n close: async () => {\n outputCommands.stop()\n reader.stop()\n await input.close()\n await output.close()\n },\n }\n }\n\n const available: Record<M8DeviceTransport, boolean> = { serial: webSerialAvailable, usb: webUsbAvailable, midi: webMidiAvailable }\n\n const connect = async ({ transport = 'auto', prompt = true }: M8ConnectOptions = {}) => {\n const chosen: M8DeviceTransport = transport !== 'auto' ? transport : webSerialAvailable ? 'serial' : webUsbAvailable ? 'usb' : 'midi'\n if (!available[chosen]) {\n throw new Error(`This browser does not support ${transportLabel[chosen]}`)\n }\n const connection = await (chosen === 'serial' ? serialConnect(prompt) : chosen === 'usb' ? usbConnect(prompt) : midiConnect(prompt))\n // resetScreen is intentionally NOT called here — it must be called by the\n // screen component AFTER it has registered its event listeners, so the\n // M8's initial screen dump is not lost before anyone is listening.\n const protocolHandler = protocol()\n let closing = false\n const closed = beginRead(connection.reader as ReadFn, protocolHandler)\n return {\n transport: connection.transport,\n commands: connection.commands,\n reader: connection.reader,\n protocol: protocolHandler,\n /** Resolves when the device stops sending (unplugged, or close() was called). */\n closed,\n /** Releases the device so another page (or yam8d) can open it. */\n close: async () => {\n if (closing) return closed\n closing = true\n await connection.close().catch((error: unknown) => console.warn('[M8] close failed:', error))\n await closed\n },\n }\n }\n\n return {\n browserSupport: true,\n available,\n connect,\n } as const\n}\n\nexport type ConnectedBus = Awaited<ReturnType<Exclude<ReturnType<typeof connection>['connect'], undefined>>>\n","/**\n * Runs `connect` until it succeeds. The M8 Headless is slower to enumerate and may not be\n * immediately ready when the user clicks Connect, so failures are retried after a delay.\n * Resolves to undefined once all attempts have failed.\n */\nexport const connectWithRetry = async <T>(\n connect: () => Promise<T>,\n { maxAttempts = 3, retryDelayMs = 1500 }: { maxAttempts?: number; retryDelayMs?: number } = {},\n): Promise<T | undefined> => {\n for (let attempt = 1; attempt <= maxAttempts; attempt++) {\n try {\n return await connect()\n } catch (err) {\n console.warn(`Connection attempt ${attempt}/${maxAttempts} failed:`, err)\n if (attempt < maxAttempts) {\n await new Promise<void>((resolve) => setTimeout(resolve, retryDelayMs))\n } else {\n console.error('Could not connect to M8 after all attempts:', err)\n }\n }\n }\n return undefined\n}\n","// The M8's USB audio: it shows up as an audio input (\"M8\" in its label). Playing it through\n// the page lets you hear the M8 on the computer; the same graph gives a capture stream for\n// recording.\n\nexport const M8_AUDIO_CAPTURE_CONSTRAINTS = {\n autoGainControl: false,\n channelCount: { ideal: 2 },\n echoCancellation: false,\n noiseSuppression: false,\n} satisfies MediaTrackConstraints\n\nconst findM8Input = async (): Promise<MediaDeviceInfo | null> => {\n const devices = (await navigator.mediaDevices.enumerateDevices()).filter(\n (x) => x.kind === 'audioinput' && x.label.includes('M8') && x.deviceId !== 'default' && x.deviceId !== 'communications',\n )\n if (devices.length > 1) {\n console.warn('Suspicious: More than one M8 audio input found')\n }\n return devices[0] ?? null\n}\n\n/** The M8's audio input, or null (device labels are only visible once audio permission was given). */\nexport const getM8AudioInputDevice = async (): Promise<MediaDeviceInfo | null> => {\n if (!navigator.mediaDevices?.enumerateDevices) return null\n return findM8Input()\n}\n\n/**\n * A stream from the M8's audio input. Without a labelled M8 input yet, it asks for audio\n * permission first and looks again. Still none: `requireM8` rejects, otherwise the default\n * input is returned (yam8d's recorder relies on that fallback).\n */\nexport const getM8InputStream = async ({ requireM8 = false }: { requireM8?: boolean } = {}): Promise<MediaStream> => {\n if (!navigator.mediaDevices?.getUserMedia) throw new Error('Audio capture is not supported in this browser')\n\n let device = await getM8AudioInputDevice()\n if (!device) {\n // Labels are hidden until the page has audio permission: get it, then look again\n const probe = await navigator.mediaDevices.getUserMedia({ audio: M8_AUDIO_CAPTURE_CONSTRAINTS })\n device = await getM8AudioInputDevice()\n if (!device) {\n if (!requireM8) return probe\n for (const track of probe.getTracks()) track.stop()\n throw new Error('No M8 audio input found (is the M8 connected over USB?)')\n }\n for (const track of probe.getTracks()) track.stop()\n }\n\n return navigator.mediaDevices.getUserMedia({\n audio: {\n ...M8_AUDIO_CAPTURE_CONSTRAINTS,\n deviceId: { exact: device.deviceId },\n },\n })\n}\n\nexport interface M8Audio {\n /** Starts playing the M8's audio input through the page's speakers. Idempotent. */\n connect(options?: { requireM8?: boolean }): Promise<void>\n disconnect(): Promise<void>\n readonly connected: boolean\n /** A copy of what is being played, for recording; null while not connected. */\n getOutputCaptureStream(): MediaStream | null\n}\n\nexport const createM8Audio = (): M8Audio => {\n let ctx: AudioContext | null = null\n let input: MediaStream | null = null\n let output: MediaStream | null = null\n let connecting: Promise<void> | null = null\n\n // A context created after an await may start suspended: the next click or key resumes it\n const resumeOnGesture = (context: AudioContext) => {\n if (context.state !== 'suspended' || typeof window === 'undefined') return\n const resume = () => {\n void context.resume()\n window.removeEventListener('pointerdown', resume)\n window.removeEventListener('keydown', resume)\n }\n window.addEventListener('pointerdown', resume)\n window.addEventListener('keydown', resume)\n }\n\n const disconnect = async () => {\n for (const track of input?.getTracks() ?? []) track.stop()\n const context = ctx\n ctx = null\n input = null\n output = null\n await context?.close().catch(() => undefined)\n }\n\n const open = async (requireM8: boolean) => {\n const stream = await getM8InputStream({ requireM8 })\n const context = new AudioContext()\n const source = context.createMediaStreamSource(stream)\n const destination = context.createMediaStreamDestination()\n source.connect(context.destination)\n source.connect(destination)\n ctx = context\n input = stream\n output = destination.stream\n\n for (const track of stream.getAudioTracks()) {\n // Unplugged: release the graph\n track.addEventListener('ended', () => {\n if (input === stream) void disconnect()\n })\n }\n await context.resume().catch(() => undefined)\n resumeOnGesture(context)\n }\n\n return {\n connect: async ({ requireM8 = false } = {}) => {\n if (ctx) return\n connecting ??= open(requireM8).finally(() => {\n connecting = null\n })\n return connecting\n },\n disconnect,\n get connected() {\n return ctx !== null\n },\n getOutputCaptureStream: () => {\n const tracks = output?.getAudioTracks() ?? []\n if (tracks.length <= 0) return null\n return new MediaStream(tracks.map((track) => track.clone()))\n },\n }\n}\n","// What the cursor navigator has learned about each screen layout: the cursor cells seen there,\r\n// where each arrow key went from each cell (or that it did not move), and the screen texts the\r\n// cursor could not reach (labels; once they failed in two navigations). Cells are the M8's own cursor rectangles in M8 pixels, so the\r\n// map fits any model and font mode. Layouts are keyed by the caller (view, model, font mode and\r\n// the labels on screen); the oldest are dropped past MAX_LAYOUTS.\r\n\r\nexport type CursorDir = 'up' | 'down' | 'left' | 'right'\r\nexport type CellRect = { x: number; y: number; w: number; h: number }\r\n\r\n/** The cells and moves of one layout. A move to `null` means the key did not move the cursor. */\r\nexport interface CursorLayout {\r\n readonly cells: Map<string, CellRect>\r\n readonly moves: Map<string, Partial<Record<CursorDir, string | null>>>\r\n readonly unreachable: Set<string>\r\n /** Texts that failed once: one more failure makes them unreachable. */\r\n readonly suspects: Set<string>\r\n}\r\n\r\nexport type CursorMapData = {\r\n version: 1\r\n layouts: { key: string; cells: string[]; moves: [string, CursorDir, string | null][]; unreachable: string[]; suspects?: string[] }[]\r\n}\r\n\r\nexport interface CursorMap {\r\n /** The layout, or undefined when nothing was learned for it. */\r\n get(key: string): CursorLayout | undefined\r\n recordCell(key: string, rect: CellRect): void\r\n /** `to` null: the key was pressed and the cursor did not move. */\r\n recordMove(key: string, from: CellRect, dir: CursorDir, to: CellRect | null): void\r\n /** A navigation could not reach this text: the second time, it is unreachable. */\r\n markUnreachable(key: string, textKey: string): void\r\n toJSON(): CursorMapData\r\n}\r\n\r\nexport const DIRS: readonly CursorDir[] = ['up', 'down', 'left', 'right']\r\nconst MAX_LAYOUTS = 64\r\n\r\nexport const cellId = (rect: CellRect) => `${rect.x},${rect.y},${rect.w},${rect.h}`\r\n\r\nexport const parseCellId = (id: string): CellRect | null => {\r\n const [x, y, w, h] = id.split(',').map(Number)\r\n if (![x, y, w, h].every(Number.isFinite)) return null\r\n return { x, y, w, h }\r\n}\r\n\r\nconst emptyLayout = (): CursorLayout => ({ cells: new Map(), moves: new Map(), unreachable: new Set(), suspects: new Set() })\r\n\r\nexport const createCursorMap = (options: { data?: CursorMapData | null; onChange?: () => void } = {}): CursorMap => {\r\n // Insertion order is the use order: a used layout is moved to the end\r\n const layouts = new Map<string, CursorLayout>()\r\n const changed = () => options.onChange?.()\r\n\r\n const touch = (key: string): CursorLayout => {\r\n let layout = layouts.get(key)\r\n if (layout) layouts.delete(key)\r\n else layout = emptyLayout()\r\n layouts.set(key, layout)\r\n while (layouts.size > MAX_LAYOUTS) {\r\n const oldest = layouts.keys().next().value\r\n if (oldest === undefined) break\r\n layouts.delete(oldest)\r\n }\r\n return layout\r\n }\r\n\r\n const addCell = (layout: CursorLayout, rect: CellRect) => {\r\n const id = cellId(rect)\r\n if (layout.cells.has(id)) return false\r\n layout.cells.set(id, { x: rect.x, y: rect.y, w: rect.w, h: rect.h })\r\n return true\r\n }\r\n\r\n if (options.data?.version === 1) {\r\n for (const saved of options.data.layouts ?? []) {\r\n const layout = touch(saved.key)\r\n for (const id of saved.cells ?? []) {\r\n const rect = parseCellId(id)\r\n if (rect) addCell(layout, rect)\r\n }\r\n for (const [from, dir, to] of saved.moves ?? []) {\r\n if (!DIRS.includes(dir)) continue\r\n const moves = layout.moves.get(from) ?? {}\r\n moves[dir] = to\r\n layout.moves.set(from, moves)\r\n }\r\n for (const text of saved.unreachable ?? []) layout.unreachable.add(text)\r\n for (const text of saved.suspects ?? []) layout.suspects.add(text)\r\n }\r\n }\r\n\r\n return {\r\n get: (key) => layouts.get(key),\r\n recordCell: (key, rect) => {\r\n if (addCell(touch(key), rect)) changed()\r\n },\r\n recordMove: (key, from, dir, to) => {\r\n const layout = touch(key)\r\n addCell(layout, from)\r\n if (to) addCell(layout, to)\r\n const fromId = cellId(from)\r\n const toId = to ? cellId(to) : null\r\n const moves = layout.moves.get(fromId) ?? {}\r\n if (moves[dir] === toId) return\r\n moves[dir] = toId\r\n layout.moves.set(fromId, moves)\r\n changed()\r\n },\r\n markUnreachable: (key, textKey) => {\r\n const layout = touch(key)\r\n if (layout.unreachable.has(textKey)) return\r\n if (layout.suspects.delete(textKey)) layout.unreachable.add(textKey)\r\n else layout.suspects.add(textKey)\r\n changed()\r\n },\r\n toJSON: () => ({\r\n version: 1,\r\n layouts: [...layouts.entries()].map(([key, layout]) => ({\r\n key,\r\n cells: [...layout.cells.keys()],\r\n moves: [...layout.moves.entries()].flatMap(([from, moves]) =>\r\n DIRS.filter((dir) => moves[dir] !== undefined).map((dir): [string, CursorDir, string | null] => [from, dir, moves[dir] ?? null]),\r\n ),\r\n unreachable: [...layout.unreachable],\r\n suspects: [...layout.suspects],\r\n })),\r\n }),\r\n }\r\n}\r\n\r\n/** Whether a move landed on the wrong side (Up landing lower...): a wrap, not a move on this screen. */\r\nexport const wentBackwards = (from: CellRect, dir: CursorDir, to: CellRect): boolean => {\r\n if (dir === 'up') return to.y > from.y\r\n if (dir === 'down') return to.y < from.y\r\n if (dir === 'left') return to.x > from.x\r\n return to.x < from.x\r\n}\r\n\r\n// A move not learned yet costs a hair more than a learned one: learned moves only break ties (guesses are\r\n// right on the M8 grids, and a wrong one is learned and replanned)\r\nconst GUESS_COST = 1.01\r\n\r\nconst overlap = (a0: number, a1: number, b0: number, b1: number) => Math.min(a1, b1) - Math.max(a0, b0)\r\n\r\n/**\r\n * Where a key not learned yet probably goes: Up/Down to the nearest cell above/below that shares\r\n * columns with this one, Left/Right to the nearest cell of the same row. Null when there is none.\r\n */\r\nexport const guessMove = (layout: CursorLayout, from: CellRect, dir: CursorDir): string | null => {\r\n const cx = from.x + from.w / 2\r\n const cy = from.y + from.h / 2\r\n let best: { id: string; along: number; across: number } | null = null\r\n for (const [id, cell] of layout.cells) {\r\n const ox = cell.x + cell.w / 2 - cx\r\n const oy = cell.y + cell.h / 2 - cy\r\n let along: number\r\n let across: number\r\n if (dir === 'up' || dir === 'down') {\r\n along = dir === 'down' ? oy : -oy\r\n across = Math.abs(ox)\r\n if (overlap(from.x, from.x + from.w, cell.x, cell.x + cell.w) <= 0) continue\r\n } else {\r\n along = dir === 'right' ? ox : -ox\r\n across = Math.abs(oy)\r\n if (overlap(from.y, from.y + from.h, cell.y, cell.y + cell.h) < Math.min(from.h, cell.h) / 2) continue\r\n }\r\n if (along < Math.min(from.h, cell.h) / 2) continue\r\n if (!best || along < best.along - 0.5 || (Math.abs(along - best.along) <= 0.5 && across < best.across)) best = { id, along, across }\r\n }\r\n return best?.id ?? null\r\n}\r\n\r\n/**\r\n * Cheapest key sequence from `fromId` to a cell that `isGoal` accepts, over the learned moves and,\r\n * for keys not learned yet from a cell, the move `guessMove` expects (marked `guessed`). Moves\r\n * listed in `skip` (`\"<cellId>:<dir>\"`) or refused by `forbid` are not used, learned or guessed,\r\n * nor are learned moves that went backwards (a phrase wrapping to the next one). Null when no path exists.\r\n */\r\nexport const findPath = (\r\n layout: CursorLayout,\r\n fromId: string,\r\n isGoal: (id: string) => boolean,\r\n skip: ReadonlySet<string> = new Set(),\r\n forbid: (rect: CellRect, dir: CursorDir) => boolean = () => false,\r\n): { dir: CursorDir; to: string; guessed: boolean }[] | null => {\r\n if (isGoal(fromId)) return []\r\n const cost = new Map<string, number>([[fromId, 0]])\r\n const previous = new Map<string, { from: string; dir: CursorDir; guessed: boolean }>()\r\n const done = new Set<string>()\r\n const open = new Set([fromId])\r\n while (open.size > 0) {\r\n let id = ''\r\n let lowest = Number.POSITIVE_INFINITY\r\n for (const candidate of open) {\r\n const c = cost.get(candidate) ?? Number.POSITIVE_INFINITY\r\n if (c < lowest) {\r\n lowest = c\r\n id = candidate\r\n }\r\n }\r\n open.delete(id)\r\n if (done.has(id)) continue\r\n done.add(id)\r\n if (isGoal(id)) {\r\n const path: { dir: CursorDir; to: string; guessed: boolean }[] = []\r\n for (let at = id; at !== fromId; ) {\r\n const step = previous.get(at) as { from: string; dir: CursorDir; guessed: boolean }\r\n path.unshift({ dir: step.dir, to: at, guessed: step.guessed })\r\n at = step.from\r\n }\r\n return path\r\n }\r\n const rect = layout.cells.get(id)\r\n const moves = layout.moves.get(id) ?? {}\r\n for (const dir of DIRS) {\r\n if (skip.has(`${id}:${dir}`) || (rect && forbid(rect, dir))) continue\r\n const learned = moves[dir]\r\n if (learned === null) continue\r\n const guessed = learned === undefined\r\n const to = guessed ? (rect ? guessMove(layout, rect, dir) : null) : learned\r\n if (!to || done.has(to)) continue\r\n const toRect = layout.cells.get(to)\r\n if (!guessed && rect && toRect && wentBackwards(rect, dir, toRect)) continue\r\n const next = lowest + (guessed ? GUESS_COST : 1)\r\n if (next < (cost.get(to) ?? Number.POSITIVE_INFINITY)) {\r\n cost.set(to, next)\r\n previous.set(to, { from: id, dir, guessed })\r\n open.add(to)\r\n }\r\n }\r\n }\r\n return null\r\n}\r\n","{\r\n \"version\": \"1.0\",\r\n \"description\": \"Semantic mapping of M8 tracker views: defines the structure of each grid view so cursor position + line text can be interpreted as typed field values.\",\r\n \"notes\": {\r\n \"fontMode\": \"All x/width values assume font mode 0 (standard, 12×14 px cells, 40 cols × ~22 rows). Font modes 1 (bold uppercase) and 2 (large) may shift column positions.\",\r\n \"playbackIndicator\": \"The M8 renders a '>' character one column to the left of the first data column when that step/row/track is currently playing. This character is part of the text stream; the editing cursor is a separate rect overlay.\",\r\n \"screenGeometry\": \"The screen is 40 columns x 24 rows. Every row has a one-column left margin (the playback marker is drawn in it), so x positions in this schema count from column 1 of the raw screen and cursorPos.x is the schema x + 1. Titles are on row 3, content starts on row 5. The text stream is lower case ('tempo', 'b-4', 'ff').\",\r\n \"emptyCell\": \"An empty or unused cell displays '--' (2 chars) or '---' (3 chars) or '---00' (command+value). Parsed hexValue will be null for these.\",\r\n \"relativeHex\": \"Transpose and some FX values use relative hex: 00 = no change, 01-7F = +1-127, FF-80 = -1-128. See 'Relative and Absolute FX Commands' in the manual.\",\r\n \"rowIndex\": \"Row/step indices are parsed from the leftmost characters of currentLine. They are hex for all grid views.\",\r\n \"titleAsterisk\": \"A '*' next to the item ID in the screen title (e.g. 'PHRASE 03*') indicates the item is used elsewhere in the chain or song (phrase view); on other views it marks unsaved changes. See 'An asterisk next to the phrase number' in the manual.\"\r\n },\r\n \"fieldTypes\": {\r\n \"chainRef\": \"Reference to a chain number (hex 00-FF). '--' means empty/unused.\",\r\n \"phraseRef\": \"Reference to a phrase number (hex 00-FF). '--' means empty/unused.\",\r\n \"note\": \"Musical note in M8 format: [A-G][#|-][0-9] e.g. 'C-4', 'A#3'. '---' = empty. 'OFF' = note-off.\",\r\n \"velocity\": \"Note velocity 00-7F (hex), from 00 (silence) to 7F (maximum). Default is 64 (hex). '--' = inherit.\",\r\n \"instrumentRef\": \"Instrument number 00-7F (hex). '--' = no retrigger (keep current instrument).\",\r\n \"fxCommand\": \"3-character sequencer or instrument FX command name, e.g. 'KIL', 'ARP', 'DEL'. '---' = none.\",\r\n \"fxValue\": \"2-char hex value 00-FF accompanying an FX command. Interpretation depends on the command.\",\r\n \"transpose\": \"Semitone or tick transpose. Relative: 00=none, 01-7F=+1-+127, FF-80=-1--128.\",\r\n \"ticks\": \"Number of ticks this step consumes in a groove (hex 01-FF). '00' skips the step. '--' = empty (groove loops at this point).\",\r\n \"volume\": \"Volume multiplier 00-FF (hex). Multiplied with the phrase-row velocity. '--' = no override.\", \"ppq\": \"Pulses Per Quarter note: resolution of the groove clock (decimal). One of 24 (default), 48, 96 or 192. Only present on groove row 0; absent on all other rows.\",\r\n \"instrumentName\": \"Instrument name string (up to 12 characters). Text field, no hex value.\",\r\n \"eqSlot\": \"EQ slot assignment (hex). '--' = no EQ applied to this instrument.\",\r\n \"rowIndex\": \"The positional index of this row in the current view (song row, chain position, phrase step, etc.).\",\r\n \"noteInterval\": \"Scale row label: one of the 12 note intervals C, C#, D, D#, E, F, F#, G, G#, A, A#, B (sharps use '#', naturals may render a dim '-' pad, e.g. 'C-').\",\r\n \"onOff\": \"Toggle field displayed as 'ON' or 'OFF'; the scale view 'EN' column shows '--' for a note out of the scale. Used by the scale view 'EN' column.\",\r\n \"semitoneOffset\": \"Signed semitone offset in decimal notation with two decimals (-24.00 to +24.00), e.g. '00.00', '-00.50', '12.00'. Used by the scale view 'OFFSET' column.\",\r\n \"swing\": \"Groove swing amount as a percentage with one decimal, e.g. '50.0%' = straight timing, higher = swung. Shown on every second groove row (0, 2, 4, …): the swing of that pair of steps (ticks 07,05 → 58.3%).\",\r\n \"instrumentType\": \"Instrument engine chosen with the TYPE field of the Instrument view: WAVSYNTH, MACROSYN, SAMPLER, FMSYNTH, HYPERSYNTH, EXTERNAL INST, MIDI OUT (NONE for an unused slot). Text value; changes which parameters the view shows.\",\r\n \"name\": \"Free-text name field (instrument, song, sample, directory). Up to 12 characters; a row of dashes means unnamed.\",\r\n \"parameter\": \"Editable value of a named parameter (CUTOFF, TEMPO, GAIN, …). Its 'format' says how to read it: hex byte, 'XX:YY' pair, decimal, decimal with a fraction, or free text.\",\r\n \"option\": \"Choice from a list, shown as its name, optionally preceded by its index in hex (e.g. '00CHORUS', 'STEREO', 'BELL'). hexValue holds the index when there is one, text holds the name.\",\r\n \"action\": \"Button-like text that does something when [EDIT] is pressed on it (LOAD, SAVE, RENDER, SETTINGS, VIEW INST. POOL, EQ, …). The text is the caption.\",\r\n \"fileEntry\": \"Row of a file browser: '/name' is a directory, '/..' the parent directory, anything else a file (e.g. 'Bass.m8i').\"\r\n },\r\n \"views\": {\r\n \"song\": {\r\n \"title\": \"Song View\",\r\n \"type\": \"grid\",\r\n \"description\": \"Arranges chains across 8 independent tracks to form the song structure. Each row is a song position (up to 256 rows); each column is a track. Each track advances vertically until it reaches an empty column ('--'), then loops back to row 00. Chains containing no notes are grayed out; chains '00' or 'FE' are commonly used as silent spacers. On row '00', [UP][UP] enters track reordering mode.\",\r\n \"rowField\": {\r\n \"x\": 0, \"width\": 2, \"parse\": \"hex\", \"type\": \"rowIndex\",\r\n \"key\": \"songRow\", \"description\": \"Song row 00-FF. Each track loops back to row 00 when it reaches '--'.\"\r\n },\r\n \"columns\": [\r\n { \"x\": 3, \"width\": 2, \"key\": \"track1\", \"type\": \"chainRef\", \"label\": \"Track 1\", \"meta\": { \"trackIndex\": 1 } },\r\n { \"x\": 6, \"width\": 2, \"key\": \"track2\", \"type\": \"chainRef\", \"label\": \"Track 2\", \"meta\": { \"trackIndex\": 2 } },\r\n { \"x\": 9, \"width\": 2, \"key\": \"track3\", \"type\": \"chainRef\", \"label\": \"Track 3\", \"meta\": { \"trackIndex\": 3 } },\r\n { \"x\": 12, \"width\": 2, \"key\": \"track4\", \"type\": \"chainRef\", \"label\": \"Track 4\", \"meta\": { \"trackIndex\": 4 } },\r\n { \"x\": 15, \"width\": 2, \"key\": \"track5\", \"type\": \"chainRef\", \"label\": \"Track 5\", \"meta\": { \"trackIndex\": 5 } },\r\n { \"x\": 18, \"width\": 2, \"key\": \"track6\", \"type\": \"chainRef\", \"label\": \"Track 6\", \"meta\": { \"trackIndex\": 6 } },\r\n { \"x\": 21, \"width\": 2, \"key\": \"track7\", \"type\": \"chainRef\", \"label\": \"Track 7\", \"meta\": { \"trackIndex\": 7 } },\r\n { \"x\": 24, \"width\": 2, \"key\": \"track8\", \"type\": \"chainRef\", \"label\": \"Track 8\", \"meta\": { \"trackIndex\": 8 } }\r\n ]\r\n },\r\n \"chain\": {\r\n \"title\": \"Chain View\",\r\n \"type\": \"grid\",\r\n \"description\": \"An ordered list of up to 16 phrases (256 steps). The first '--' phrase ends the chain and the song continues on to the next chain in the song view. The 'TSP' column optionally transposes every note in the phrase in semitones (relative hex).\",\r\n \"rowField\": {\r\n \"x\": 0, \"width\": 1, \"parse\": \"hex\", \"type\": \"rowIndex\",\r\n \"key\": \"chainPos\", \"description\": \"Position within the chain (0-F, max 16 entries).\"\r\n },\r\n \"columns\": [\r\n { \"x\": 2, \"width\": 2, \"key\": \"phrase\", \"type\": \"phraseRef\", \"label\": \"Phrase (PH)\", \"description\": \"Phrase number to play at this chain position.\" },\r\n { \"x\": 5, \"width\": 2, \"key\": \"transpose\", \"type\": \"transpose\", \"label\": \"Transpose (TSP)\", \"description\": \"Optional note transpose in semitones applied to every note in the phrase (relative hex).\" }\r\n ]\r\n },\r\n \"phrase\": {\r\n \"title\": \"Phrase View\",\r\n \"type\": \"grid\",\r\n \"description\": \"A 16-step sequence. Each step holds a note, velocity, instrument assignment, and up to 3 FX command+value pairs. Notes cover 11 octaves (the full 128 MIDI note range); velocity defaults to 64 (hex); an empty instrument column plays the note without retriggering the instrument. A '*' next to the phrase number indicates the phrase is used elsewhere in the chain or song.\",\r\n \"rowField\": {\r\n \"x\": 0, \"width\": 1, \"parse\": \"hex\", \"type\": \"rowIndex\",\r\n \"key\": \"step\", \"description\": \"Step index 0-F (16th note by default; altered by groove).\"\r\n },\r\n \"columns\": [\r\n { \"x\": 2, \"width\": 3, \"key\": \"note\", \"type\": \"note\", \"label\": \"Note (N)\" },\r\n { \"x\": 6, \"width\": 2, \"key\": \"vel\", \"type\": \"velocity\", \"label\": \"Velocity (V)\" },\r\n { \"x\": 9, \"width\": 2, \"key\": \"inst\", \"type\": \"instrumentRef\", \"label\": \"Instrument (I)\" },\r\n { \"x\": 12, \"width\": 3, \"key\": \"fx1cmd\", \"type\": \"fxCommand\", \"label\": \"FX1 Command\" },\r\n { \"x\": 15, \"width\": 2, \"key\": \"fx1val\", \"type\": \"fxValue\", \"label\": \"FX1 Value\" },\r\n { \"x\": 18, \"width\": 3, \"key\": \"fx2cmd\", \"type\": \"fxCommand\", \"label\": \"FX2 Command\" },\r\n { \"x\": 21, \"width\": 2, \"key\": \"fx2val\", \"type\": \"fxValue\", \"label\": \"FX2 Value\" },\r\n { \"x\": 24, \"width\": 3, \"key\": \"fx3cmd\", \"type\": \"fxCommand\", \"label\": \"FX3 Command\" },\r\n { \"x\": 27, \"width\": 2, \"key\": \"fx3val\", \"type\": \"fxValue\", \"label\": \"FX3 Value\" }\r\n ]\r\n },\r\n \"table\": {\r\n \"title\": \"Table View\",\r\n \"type\": \"grid\",\r\n \"description\": \"A 16-step mini-sequencer that runs alongside an instrument. Used for arpeggios, multi-stage envelopes, and automated FX. The 'N' column transposes the instrument for the step (relative hex); 'V' multiplies with the phrase-row velocity. Each instrument has a dedicated table plus 128 assignable tables (TBL command). FX columns can run at independent speeds via TIC; a TIC on the last table row affects all rows. HOP shortens or loops the table.\",\r\n \"rowField\": {\r\n \"x\": 0, \"width\": 1, \"parse\": \"hex\", \"type\": \"rowIndex\",\r\n \"key\": \"step\", \"description\": \"Table step 0-F. Loops from bottom unless a HOP command redirects.\"\r\n },\r\n \"columns\": [\r\n { \"x\": 2, \"width\": 2, \"key\": \"transpose\", \"type\": \"transpose\", \"label\": \"Transpose (N)\", \"description\": \"Transposes the instrument for this step (relative hex). '--' = no transpose.\" },\r\n { \"x\": 5, \"width\": 2, \"key\": \"volume\", \"type\": \"volume\", \"label\": \"Volume (V)\", \"description\": \"Volume multiplier applied on top of the phrase velocity (hex). '--' = no override.\" },\r\n { \"x\": 8, \"width\": 3, \"key\": \"fx1cmd\", \"type\": \"fxCommand\", \"label\": \"FX1 Command\", \"description\": \"3-char command name for table FX slot 1.\" },\r\n { \"x\": 11, \"width\": 2, \"key\": \"fx1val\", \"type\": \"fxValue\", \"label\": \"FX1 Value\", \"description\": \"Value for table FX1.\" },\r\n { \"x\": 14, \"width\": 3, \"key\": \"fx2cmd\", \"type\": \"fxCommand\", \"label\": \"FX2 Command\", \"description\": \"3-char command name for table FX slot 2.\" },\r\n { \"x\": 17, \"width\": 2, \"key\": \"fx2val\", \"type\": \"fxValue\", \"label\": \"FX2 Value\", \"description\": \"Value for table FX2.\" },\r\n { \"x\": 20, \"width\": 3, \"key\": \"fx3cmd\", \"type\": \"fxCommand\", \"label\": \"FX3 Command\", \"description\": \"3-char command name for table FX slot 3.\" },\r\n { \"x\": 23, \"width\": 2, \"key\": \"fx3val\", \"type\": \"fxValue\", \"label\": \"FX3 Value\", \"description\": \"Value for table FX3.\" }\r\n ]\r\n },\r\n \"groove\": {\r\n \"title\": \"Groove View\",\r\n \"type\": \"grid\",\r\n \"description\": \"Defines the timing of each phrase step in ticks. Used to create swing, shuffle, triplets, and other rhythmic feels. At the default 24 PPQ a step is 6 ticks and a full phrase totals 96 ticks. The groove loops when a '--' entry is reached; a '00' entry skips the phrase step. Row 0 also holds the PPQ resolution (24/48/96/192) and the SWING percentage; editing the row 0 tick value alters rows 0 and 1 together (a common swing setting is 07,05 or 08,04).\",\r\n \"rowField\": {\r\n \"x\": 0, \"width\": 1, \"parse\": \"hex\", \"type\": \"rowIndex\",\r\n \"key\": \"step\", \"description\": \"Groove step 0-F. The groove loops when a '--' entry is reached.\"\r\n },\r\n \"columns\": [\r\n { \"x\": 2, \"width\": 2, \"key\": \"ticks\", \"type\": \"ticks\", \"label\": \"Ticks (TIC)\", \"description\": \"Number of ticks this step takes. Default is 06 (at 24 PPQN = 1 sixteenth note). '00' skips the step.\" },\r\n { \"x\": 6, \"width\": 2, \"key\": \"ppq\", \"type\": \"ppq\", \"label\": \"PPQ\", \"description\": \"Pulses Per Quarter note: 24, 48, 96 or 192. Only present on groove row 0; absent on all other rows.\" },\r\n { \"x\": 10, \"width\": 5, \"key\": \"swing\", \"type\": \"swing\", \"label\": \"Swing\", \"description\": \"Swing amount as a percentage (e.g. '50.0%' = straight). Shown on every second row (0, 2, 4, …) for that pair of steps; empty on the others.\" }\r\n ]\r\n },\r\n \"scale\": {\r\n \"title\": \"Scale View\",\r\n \"type\": \"grid\",\r\n \"description\": \"Defines and quantizes note-related events (project/chain transpose, note entry, ARP, PIT, and the MIDIOUT instrument CHD and ADD commands). Up to 16 user-defined scales per project; scale '00' is the default for all 8 tracks and can be overridden per track with SCA/SCG FX commands. Each of the 12 note intervals can be enabled ('EN' = ON) with an optional detuning offset (-24.00 to +24.00 semitones); a note out of the scale reads '-- -- --'. Rows that are not column-mapped (Model:02, fw 6.5): KEY under the title (global root note for the default scale, the same on every scale), then after the intervals TUNE (A4 reference in Hz, e.g. '440.00'), NAME (up to 16 characters, 92 factory scales load from /Scales/Factory), and the LOAD/SAVE actions.\",\r\n \"columns\": [\r\n { \"x\": 0, \"width\": 2, \"key\": \"interval\", \"type\": \"noteInterval\", \"label\": \"Note Interval\", \"description\": \"Note interval row label: C, C#, D, D#, E, F, F#, G, G#, A, A#, B.\" },\r\n { \"x\": 3, \"width\": 3, \"key\": \"en\", \"type\": \"onOff\", \"label\": \"Enabled (EN)\", \"description\": \"Enables this note interval in the scale: 'ON', or '--' when the note is out of the scale (the device draws the row '-- -- --').\" },\r\n { \"x\": 6, \"width\": 6, \"key\": \"offset\", \"type\": \"semitoneOffset\", \"label\": \"Offset (OFFSET)\", \"description\": \"Detune offset in semitones, decimal with two decimals (-24.00 to +24.00), e.g. '00.00'.\" }\r\n ]\r\n },\r\n \"instrumentpool\": {\r\n \"title\": \"Instrument Pool\",\r\n \"type\": \"grid\",\r\n \"description\": \"Lists all 128 instrument slots with their mix routing. Each row shows the instrument name (up to 12 chars, '------------' = empty slot) and its dry/FX send levels. Mixer-related settings can be adjusted per instrument here; [EDIT] on an empty slot loads an instrument or sample.\",\r\n \"rowField\": {\r\n \"x\": 0, \"width\": 2, \"parse\": \"hex\", \"type\": \"rowIndex\",\r\n \"key\": \"instIndex\", \"description\": \"Instrument slot index 00-7F.\"\r\n },\r\n \"columns\": [\r\n { \"x\": 3, \"width\": 12, \"key\": \"name\", \"type\": \"instrumentName\", \"label\": \"Instrument Name\", \"description\": \"Name of the instrument in this slot (up to 12 chars). '------------' = empty slot.\" },\r\n { \"x\": 16, \"width\": 2, \"key\": \"dry\", \"type\": \"volume\", \"label\": \"Dry (DRY)\", \"description\": \"Direct output volume for this instrument (hex 00-FF).\" },\r\n { \"x\": 19, \"width\": 2, \"key\": \"mx\", \"type\": \"volume\", \"label\": \"ModFX (MX)\", \"description\": \"ModFX send level (hex 00-FF).\" },\r\n { \"x\": 22, \"width\": 2, \"key\": \"de\", \"type\": \"volume\", \"label\": \"Delay (DE)\", \"description\": \"Delay send level (hex 00-FF).\" },\r\n { \"x\": 25, \"width\": 2, \"key\": \"rv\", \"type\": \"volume\", \"label\": \"Reverb (RV)\", \"description\": \"Reverb send level (hex 00-FF).\" },\r\n { \"x\": 28, \"width\": 2, \"key\": \"eq\", \"type\": \"eqSlot\", \"label\": \"EQ Slot (EQ)\", \"description\": \"EQ slot assignment. '--' = no EQ.\" }\r\n ]\r\n },\r\n \"inst\": {\r\n \"title\": \"Instrument View\",\r\n \"type\": \"parameter\",\r\n \"description\": \"Per-instrument parameters for the selected slot: TYPE (Wavsynth, Macrosynth, Sampler, FM Synth, Hypersynth, External Instrument, MIDI Out), LOAD/SAVE presets, NAME, TRANSP. (enable/disable transposition from scales and transpose columns), TABLE TIC (default table speed), and EQ slot, plus the synth engine parameters. Almost every parameter has an associated FX command; highlighting a parameter and navigating to the phrase or table view sets that parameter's FX command as default.\"\r\n },\r\n \"instmods\": {\r\n \"title\": \"Instrument Modulation View\",\r\n \"type\": \"parameter\",\r\n \"description\": \"Four configurable modulation slots per instrument: AHD envelope, ADSR envelope, DRUM envelope, LFO, TRIG envelope, or TRACKING, each with a destination parameter (DEST) and optional source (SRC).\"\r\n },\r\n \"mixer\": {\r\n \"title\": \"Mixer View\",\r\n \"type\": \"parameter\",\r\n \"description\": \"Mix levels for the 8 tracks, the 3 send effects (MX/DE/RE), the analog input (INPUT, with dual-mono mode) and USB input, the DJ filter (DJF), OTT compression, main EQ, limiter (LIM) and main volume (MIX). All signals are summed through OTT, main EQ, limiter, DJ filter and main volume. Most values are also exposed as FX commands (VMV, VMX, VDE, VRE, VT1-8, DJC/DJR/DJT, IVO/IMX/IDE/IRV, USB, ...).\"\r\n },\r\n \"project\": {\r\n \"title\": \"Project View\",\r\n \"type\": \"parameter\",\r\n \"description\": \"Song-wide settings such as tempo, project transpose (identical to the TSP FX command), and LIVE QUANTIZE behavior for cueing tracks in live mode. [SHIFT]+[OPTION] stores a song snapshot; [SHIFT]+[EDIT] recalls it.\"\r\n },\r\n \"effectsettings\": {\r\n \"title\": \"Effect Settings View\",\r\n \"type\": \"parameter\",\r\n \"description\": \"Settings for the 3 send effects: ModFX type/depth/frequency/width, Delay time/feedback/width, and Reverb size/decay/modulation/width/freeze — all addressable via the XMT-XMW, XDT-XDR and XRS-XRZ FX commands.\"\r\n },\r\n \"mixeq\": {\r\n \"title\": \"Mix EQ View\",\r\n \"type\": \"parameter\",\r\n \"description\": \"EQ editor for the main mix EQ slot (assign with the EQM FX command). Selecting EQ in the Mixer view and pressing [EDIT] or [SHIFT]+[RIGHT] opens this view.\"\r\n },\r\n \"modfxeq\": {\r\n \"title\": \"ModFX EQ View\",\r\n \"type\": \"parameter\",\r\n \"description\": \"EQ editor for the ModFX send path.\"\r\n },\r\n \"delayeq\": {\r\n \"title\": \"Delay EQ View\",\r\n \"type\": \"parameter\",\r\n \"description\": \"EQ editor for the Delay send path.\"\r\n },\r\n \"reverbeq\": {\r\n \"title\": \"Reverb EQ View\",\r\n \"type\": \"parameter\",\r\n \"description\": \"EQ editor for the Reverb send path.\"\r\n },\r\n \"mixscope\": {\r\n \"title\": \"Mix Scope View\",\r\n \"type\": \"parameter\",\r\n \"description\": \"Graphical scope of the main mix activity. Opened from the Mixer view with MIX selected via [EDIT] or [SHIFT]+[RIGHT].\"\r\n },\r\n \"limiterscope\": {\r\n \"title\": \"Limiter Scope View\",\r\n \"type\": \"parameter\",\r\n \"description\": \"Graphical scope of the limiter's compression activity (the white line on the MIX indicator). Opened from the Mixer view with LIM selected via [EDIT] or [SHIFT]+[RIGHT].\"\r\n },\r\n \"systemsettings\": {\r\n \"title\": \"System Settings View\",\r\n \"type\": \"parameter\",\r\n \"description\": \"System-level settings for the device.\"\r\n },\r\n \"loadinstrument\": {\r\n \"title\": \"Load Instrument\",\r\n \"type\": \"parameter\",\r\n \"description\": \"File browser for loading an instrument preset from the SD card ([EDIT] on an empty instrument slot, or LOAD in the Instrument view).\"\r\n },\r\n \"selectsavedirectory\": {\r\n \"title\": \"Select Save Directory\",\r\n \"type\": \"parameter\",\r\n \"description\": \"File browser directory-selection step when saving a preset or project.\"\r\n },\r\n \"createdirectory\": {\r\n \"title\": \"Create Directory\",\r\n \"type\": \"parameter\",\r\n \"description\": \"File browser dialog for creating a new directory on the SD card.\"\r\n }\r\n },\r\n \"fxCommands\": {\r\n \"sequencer\": {\r\n \"ARP\": { \"description\": \"Arpeggio: plays 3 notes rapidly. X=first interval semitones, Y=second interval semitones.\" },\r\n \"ARC\": { \"description\": \"Arpeggio Config: X=direction and number of octaves, Y=speed in ticks.\" },\r\n \"CHA\": { \"description\": \"Chance: X=probability left of command (0=never, F=always), Y=probability right. In a table: probability for everything to the left of CHA from 00 (never) to FF (always).\" },\r\n \"DEL\": { \"description\": \"Delay: in phrase delays entire row by XX ticks; in table delays table playhead.\" },\r\n \"GRV\": { \"description\": \"Groove: sets groove number XX for the current track.\" },\r\n \"GGR\": { \"description\": \"Global Groove: sets groove XX for all tracks.\" },\r\n \"HOP\": { \"description\": \"Hop: in phrase jumps to row Y of next phrase; HOPFF stops track. In table loops to row Y X times.\" },\r\n \"INS\": { \"description\": \"Instrument: changes active instrument to XX as an FX command.\" },\r\n \"KIL\": { \"description\": \"Kill: stops the playing instrument after XX ticks.\" },\r\n \"RND\": { \"description\": \"Random: randomises the preceding FX command. X=left range, Y=right range (0=none, F=full).\" },\r\n \"RNL\": { \"description\": \"Randomise Left: randomises the FX command to the left (X=left range, Y=right range). In the first column RNL randomises the note and instrument number in phrase, or note and velocity in table.\" },\r\n \"RET\": { \"description\": \"Retrig: retriggers current row. Y≠0: retrig every Y ticks with X volume ramp. Y=0: single retrig after X ticks.\" },\r\n \"REP\": { \"description\": \"Repeat: repeats and increments the last FX command by XX each step. Active until a new command or REP00 stops it; it does not need to appear on every row. In a table: loops the last row XX times. A double-caret '^^' appears next to an FX lane while a REP from a previous phrase is active.\" },\r\n \"RTO\": { \"description\": \"Repeat To: use after REP to hold at the given value (the min/max REP stops at). In a table, used alone: loops until the relative value is reached.\" },\r\n \"RMX\": { \"description\": \"Remix: repositions the phrase playhead (Y) on tracks to the left (selected by X).\" },\r\n \"NTH\": { \"description\": \"Nth Trigger: conditional trigger on the loop count. X=affects what is left of the command, Y=what is right (in a table: what is left).\" },\r\n \"PSL\": { \"description\": \"Pitch Slide (portamento): slides to the given note over XX ticks.\" },\r\n \"PBN\": { \"description\": \"Pitch Bend: continuous pitch slide up (00-7F) or down (80-FF).\" },\r\n \"PVB\": { \"description\": \"Vibrato: X=speed, Y=depth.\" },\r\n \"PVX\": { \"description\": \"Extreme Vibrato: like PVB but with higher range.\" },\r\n \"SCA\": { \"description\": \"Track Scale: X=key signature, Y=scale number for this track.\" },\r\n \"SCG\": { \"description\": \"Global Scale: X=key, Y=scale number for the whole song.\" },\r\n \"SNG\": { \"description\": \"Song Hop: moves this track's song position by XX rows relative to current. Ignored if the resulting song position is not playable.\" },\r\n \"SED\": { \"description\": \"Random Seed: sets the random seed XX for this track.\" },\r\n \"TBL\": { \"description\": \"Table: assigns table number XX to the current instrument.\" },\r\n \"THO\": { \"description\": \"Table Hop: jumps all table columns to position 0X.\" },\r\n \"TIC\": { \"description\": \"Table Tick: sets the table tick rate. 00 = increment row on each instrument trigger; 01-FB = ticks per row; FC = Octave Map (playing octave maps to table row); FD = Velocity Map; FE = Note Map (pair with HOP00 on row 0C to limit to 12 notes/octave); FF = increment at 200 Hz. In a table each FX column can run independently; a TIC on the last table row affects all rows.\" },\r\n \"TBX\": { \"description\": \"Aux Table: runs table XX in parallel with the instrument table. 00=stop aux table. A TIC in the same row as a TBX affects the auxiliary table's tick rate.\" },\r\n \"TPO\": { \"description\": \"Tempo: sets song BPM to XX. In a table the tempo is relative.\" },\r\n \"TSP\": { \"description\": \"Global Song Transpose: transposes the entire song by XX semitones. Identical to the transpose value in the Project View.\" },\r\n \"NXT\": { \"description\": \"Next Track Trigger: plays instrument XX on the track to the right using the current note/velocity. Does not work on track 8.\" },\r\n \"OFF\": { \"description\": \"Note Off: stops the playing instrument after XX ticks. Triggers ADSR release stage.\" },\r\n \"MTT\": { \"description\": \"Micro Time: adjusts row playback timing in positive or negative sub-tick (1/8 tick) amounts. Negative amounts do not work on the first phrase row. Currently has no action in tables.\" }\r\n },\r\n \"instrument\": {\r\n \"VOL\": { \"description\": \"Volume (relative).\", \"relative\": true },\r\n \"PIT\": { \"description\": \"Pitch in semitones (relative).\", \"relative\": true },\r\n \"FIN\": { \"description\": \"Fine tune: pitch shift of up to about one semitone (relative).\", \"relative\": true },\r\n \"PLY\": { \"description\": \"Sampler play mode (FWD, REV, loop modes…).\" },\r\n \"STA\": { \"description\": \"Sampler start position.\" },\r\n \"LOP\": { \"description\": \"Sampler loop start (relative).\", \"relative\": true },\r\n \"LEN\": { \"description\": \"Sampler length (relative).\", \"relative\": true },\r\n \"DEG\": { \"description\": \"Degrade amount (relative).\", \"relative\": true },\r\n \"SLI\": { \"description\": \"Sampler slice to play.\" },\r\n \"ERR\": { \"description\": \"Error: randomizes pitch and parameters by the given amount.\" },\r\n \"FLT\": { \"description\": \"Filter type.\" },\r\n \"FIL\": { \"description\": \"Filter type.\" },\r\n \"ALG\": { \"description\": \"FM algorithm.\" },\r\n \"FM1\": { \"description\": \"FM modulation amount 1 (relative).\", \"relative\": true },\r\n \"FM2\": { \"description\": \"FM modulation amount 2 (relative).\", \"relative\": true },\r\n \"FM3\": { \"description\": \"FM modulation amount 3 (relative).\", \"relative\": true },\r\n \"FM4\": { \"description\": \"FM modulation amount 4 (relative).\", \"relative\": true },\r\n \"SNC\": { \"description\": \"Sync: on an FM synth the FM sync phase position; on a Hypersynth it offsets the sync of the oscillators by the given amount.\" },\r\n \"CRD\": { \"description\": \"Hypersynth chord set.\" },\r\n \"CVO\": { \"description\": \"Hypersynth chord voice offset: X=voice, Y=offset.\" },\r\n \"SWM\": { \"description\": \"Hypersynth oscillator swarm (relative).\", \"relative\": true },\r\n \"WID\": { \"description\": \"Hypersynth stereo width (relative).\", \"relative\": true },\r\n \"SUB\": { \"description\": \"Hypersynth sub-oscillator amount (relative).\", \"relative\": true },\r\n \"OSC\": { \"description\": \"Macrosynth oscillator shape.\" },\r\n \"TBR\": { \"description\": \"Macrosynth timbre (relative).\", \"relative\": true },\r\n \"COL\": { \"description\": \"Macrosynth color (relative).\", \"relative\": true },\r\n \"RED\": { \"description\": \"Macrosynth bit reduction (relative).\", \"relative\": true },\r\n \"TRG\": { \"description\": \"Macrosynth trigger/strike.\" },\r\n \"CUT\": { \"description\": \"Filter cutoff (relative).\", \"relative\": true },\r\n \"RES\": { \"description\": \"Filter resonance (relative).\", \"relative\": true },\r\n \"AMP\": { \"description\": \"Amp level (relative).\", \"relative\": true },\r\n \"LIM\": { \"description\": \"Limit mode: how the amp stage clips.\" },\r\n \"PAN\": { \"description\": \"Pan (relative).\", \"relative\": true },\r\n \"DRY\": { \"description\": \"Dry volume: the instrument's level in the main mix.\" },\r\n \"SMX\": { \"description\": \"Send to ModFX.\" },\r\n \"SDL\": { \"description\": \"Send to Delay.\" },\r\n \"SRV\": { \"description\": \"Send to Reverb.\" },\r\n \"CHD\": { \"description\": \"Chord (MIDIOUT instrument): triggers a chord. Quantized by the active scale.\" },\r\n \"ADD\": { \"description\": \"Add Note (MIDIOUT instrument): adds a note to the chord. Quantized by the active scale.\" }\r\n },\r\n \"modulation\": {\r\n \"EA\": { \"description\": \"Envelope amount of modulator N (relative).\", \"relative\": true },\r\n \"AT\": { \"description\": \"Envelope attack of modulator N.\" },\r\n \"HO\": { \"description\": \"Envelope hold of modulator N.\" },\r\n \"DE\": { \"description\": \"Envelope decay of modulator N.\" },\r\n \"ET\": { \"description\": \"Envelope trigger of modulator N: retriggers the envelope.\" },\r\n \"LA\": { \"description\": \"LFO amount of modulator N (relative).\", \"relative\": true },\r\n \"LO\": { \"description\": \"LFO oscillator shape of modulator N.\" },\r\n \"LS\": { \"description\": \"LFO trigger settings of modulator N.\" },\r\n \"LF\": { \"description\": \"LFO frequency of modulator N.\" },\r\n \"LT\": { \"description\": \"LFO trigger of modulator N: at the given offset, resets the LFO to that position.\" }\r\n },\r\n \"mixer\": {\r\n \"VMV\": { \"description\": \"Main Volume: sets the main song output volume.\" },\r\n \"VMX\": { \"description\": \"ModFX Volume.\" },\r\n \"VDE\": { \"description\": \"Delay Volume.\" },\r\n \"VRE\": { \"description\": \"Reverb Volume.\" },\r\n \"VT1\": { \"description\": \"Track 1 Volume.\" },\r\n \"VT2\": { \"description\": \"Track 2 Volume.\" },\r\n \"VT3\": { \"description\": \"Track 3 Volume.\" },\r\n \"VT4\": { \"description\": \"Track 4 Volume.\" },\r\n \"VT5\": { \"description\": \"Track 5 Volume.\" },\r\n \"VT6\": { \"description\": \"Track 6 Volume.\" },\r\n \"VT7\": { \"description\": \"Track 7 Volume.\" },\r\n \"VT8\": { \"description\": \"Track 8 Volume.\" },\r\n \"DJC\": { \"description\": \"DJ Filter Cutoff.\" },\r\n \"DJR\": { \"description\": \"DJ Filter Resonance.\" },\r\n \"DJT\": { \"description\": \"DJ Filter Type.\" },\r\n \"EQM\": { \"description\": \"Assign main mix EQ slot.\" },\r\n \"EQI\": { \"description\": \"Assign current instrument EQ slot.\" },\r\n \"XMT\": { \"description\": \"Send Effect: ModFX mod type and phase position (Effect Settings view).\" },\r\n \"XMM\": { \"description\": \"Send Effect: ModFX mod depth (Effect Settings view).\" },\r\n \"XMF\": { \"description\": \"Send Effect: ModFX mod frequency (Effect Settings view).\" },\r\n \"XMW\": { \"description\": \"Send Effect: ModFX stereo width (Effect Settings view).\" },\r\n \"XMR\": { \"description\": \"Send Effect: ModFX to reverb mix (Effect Settings view).\" },\r\n \"XDT\": { \"description\": \"Send Effect: Delay time — X = left, Y = right, in large increments (Effect Settings view).\" },\r\n \"XDF\": { \"description\": \"Send Effect: Delay feedback (Effect Settings view).\" },\r\n \"XDW\": { \"description\": \"Send Effect: Delay stereo width (Effect Settings view).\" },\r\n \"XDR\": { \"description\": \"Send Effect: Delay to reverb mix (Effect Settings view).\" },\r\n \"XRS\": { \"description\": \"Send Effect: Reverb room size (Effect Settings view).\" },\r\n \"XRD\": { \"description\": \"Send Effect: Reverb decay (Effect Settings view).\" },\r\n \"XRM\": { \"description\": \"Send Effect: Reverb mod depth (Effect Settings view).\" },\r\n \"XRF\": { \"description\": \"Send Effect: Reverb mod frequency (Effect Settings view).\" },\r\n \"XRW\": { \"description\": \"Send Effect: Reverb stereo width (Effect Settings view).\" },\r\n \"XRZ\": { \"description\": \"Send Effect: Reverb freeze — enabled with a value greater than 00, disabled at 00.\" },\r\n \"VIN\": { \"description\": \"Line Input Mixer: analog input volume (Mixer view).\" },\r\n \"XRH\": { \"description\": \"Send Effect: Reverb shimmer (Effect Settings view).\" },\r\n \"OTT\": { \"description\": \"OTT mix amount (Mixer OTT settings; OTC and OTI default to 80).\" },\r\n \"OTC\": { \"description\": \"OTT color (default 80).\" },\r\n \"OTI\": { \"description\": \"OTT time (default 80).\" },\r\n \"IVO\": { \"description\": \"Alias of VIN as named in the manual: analog input volume (Mixer view).\" },\r\n \"IMX\": { \"description\": \"Line Input Mixer: analog input ModFX send (Mixer view).\" },\r\n \"IDE\": { \"description\": \"Line Input Mixer: analog input delay send (Mixer view).\" },\r\n \"IRV\": { \"description\": \"Line Input Mixer: analog input reverb send (Mixer view). Also referenced as 'IRE' in the manual text.\" },\r\n \"IRE\": { \"description\": \"Alias of IRV: analog input reverb send (Mixer view).\" },\r\n \"IV2\": { \"description\": \"Line Input Mixer: volume for the 2nd input (right channel in dual-mono input mode, set in the Mixer view).\" },\r\n \"IM2\": { \"description\": \"Line Input Mixer: ModFX send for the 2nd input (right channel in dual-mono input mode).\" },\r\n \"ID2\": { \"description\": \"Line Input Mixer: delay send for the 2nd input (right channel in dual-mono input mode).\" },\r\n \"IR2\": { \"description\": \"Line Input Mixer: reverb send for the 2nd input (right channel in dual-mono input mode).\" },\r\n \"USB\": { \"description\": \"USB input volume (Mixer view).\" }\r\n }\r\n },\r\n \"instrumentFx\": {\r\n \"_note\": \"Instrument commands offered by [EDIT]+[UP/DOWN] (Effect Command Help view) in the Current Instrument section depend on the instrument type; the Instrument Mods section depends on the modulators of that instrument, and Sequencer FX and Mixer/Effects are the same for every type. Lists marked verified were read from a real M8 (Model:02). Wavsynth, External and MIDI Out were not available on that device: their lists are not known here.\",\r\n \"types\": {\r\n \"sampler\": {\r\n \"verified\": true,\r\n \"commands\": [\"VOL\", \"PIT\", \"FIN\", \"PLY\", \"STA\", \"LOP\", \"LEN\", \"DEG\", \"FLT\", \"CUT\", \"RES\", \"AMP\", \"LIM\", \"PAN\", \"SLI\", \"ERR\", \"DRY\", \"SMX\", \"SDL\", \"SRV\"]\r\n },\r\n \"macrosynth\": {\r\n \"verified\": true,\r\n \"commands\": [\"VOL\", \"PIT\", \"FIN\", \"OSC\", \"TBR\", \"COL\", \"DEG\", \"RED\", \"FIL\", \"CUT\", \"RES\", \"AMP\", \"LIM\", \"PAN\", \"TRG\", \"ERR\", \"DRY\", \"SMX\", \"SDL\", \"SRV\"]\r\n },\r\n \"fmsynth\": {\r\n \"verified\": true,\r\n \"commands\": [\"VOL\", \"PIT\", \"FIN\", \"ALG\", \"FM1\", \"FM2\", \"FM3\", \"FM4\", \"FIL\", \"CUT\", \"RES\", \"AMP\", \"LIM\", \"PAN\", \"SNC\", \"ERR\", \"DRY\", \"SMX\", \"SDL\", \"SRV\"]\r\n },\r\n \"hypersynth\": {\r\n \"verified\": true,\r\n \"commands\": [\"VOL\", \"PIT\", \"FIN\", \"CRD\", \"CVO\", \"SWM\", \"WID\", \"SUB\", \"FIL\", \"CUT\", \"RES\", \"AMP\", \"LIM\", \"PAN\", \"SNC\", \"ERR\", \"DRY\", \"SMX\", \"SDL\", \"SRV\"]\r\n },\r\n \"midiout\": {\r\n \"verified\": false,\r\n \"commands\": [\"CHD\", \"ADD\"]\r\n }\r\n },\r\n \"modulation\": {\r\n \"_note\": \"Modulator slot N (1-4) offers the commands of its kind. Seen: an envelope slot (AHD) offers EA AT HO DE ET, an LFO slot offers LA LO LS LF LT. Slot kinds vary per instrument (e.g. Sampler 00: env, env, LFO, LFO; Hypersynth 01: env, LFO, LFO, LFO). Other modulator kinds were not captured.\",\r\n \"envelope\": [\"EA\", \"AT\", \"HO\", \"DE\", \"ET\"],\r\n \"lfo\": [\"LA\", \"LO\", \"LS\", \"LF\", \"LT\"]\r\n },\r\n \"relativeCommands\": {\r\n \"description\": \"Relative commands add to the instrument parameter's current value (01-7F add, 80-FF subtract) and keep accumulating until a note is triggered with a RET command or with an instrument number in the I column; that resets every parameter to the instrument's own value.\"\r\n }\r\n }\r\n}\r\n","// Turns a point on the M8 screen into the cell the cursor should go to, and the geometry the\r\n// cursor navigator measures with. Candidates are the learned cursor cells that have text on the\r\n// current screen, plus the runs of text no learned cell covers yet (minus those the cursor could\r\n// not reach before). A click goes to the nearest candidate on its own row when one is close, else to\r\n// the nearest one on the screen. Pure functions.\r\n\r\nimport viewSchema from '../m8-view-context.json'\r\nimport { type CellRect, type CursorDir, type CursorLayout, cellId } from './cursorMap'\r\nimport type { M8CellMetrics } from './store'\r\n\r\nexport type Point = { x: number; y: number }\r\n\r\n/**\r\n * Where the cursor should go. `cell`: a learned cursor cell (reached when the cursor is that cell).\r\n * `text`: text no learned cell covers yet; `rect` is the character aimed at (reached when the\r\n * cursor covers it), `run` the whole run (one run can be two cells, as an FX command and value).\r\n * `point`: no text near the point, or selection mode (reached when the cursor covers the point).\r\n */\r\nexport type CursorGoal =\r\n | { kind: 'cell'; id: string; rect: CellRect }\r\n | { kind: 'text'; key: string; rect: CellRect; run: CellRect; row: number }\r\n | { kind: 'point'; rect: CellRect }\r\n\r\nexport type TextRun = { key: string; rect: CellRect; row: number; col: number; text: string }\r\n\r\n// The 1–2 px corners a cursor rectangle draws outside its cell\r\nconst OVERHANG_PX = 3\r\n// Between rows, vertical distance counts more: a column is easier to aim at than a row\r\nconst ROW_WEIGHT = 1.5\r\n// A candidate of the clicked row this close (in characters), with no other text between, beats every\r\n// other candidate: a label goes to the value beside it (Project, Instrument). Otherwise the nearest wins: a\r\n// label under its value goes up to it (Mixer).\r\nconst SAME_ROW_REACH = 12\r\n// On 40-column layouts the track panel starts here; the cursor never goes there\r\nconst PANEL_COLUMN = 34\r\n\r\n// Grid views with a row number column (song rows, chain, phrase, table and groove steps, instrument\r\n// slots), and its width. Up on their first row and Down on their last scroll the view or move to\r\n// the neighbouring chain or phrase (and Up Up on song row 00 starts track reordering), so a\r\n// navigation stays between them.\r\nconst ROW_NUMBER_WIDTH: Record<string, number> = Object.fromEntries(\r\n Object.entries(viewSchema.views as Record<string, { rowField?: { width: number } }>).flatMap(([name, view]) =>\r\n view.rowField ? [[name, view.rowField.width]] : [],\r\n ),\r\n)\r\n\r\nexport type GridRows = { first: number; last: number; numberWidth: number }\r\n\r\n/** The first and last rows that start with a row number, on grid views that have one; else null. */\r\nexport const gridRows = (viewName: string | null, lines: readonly string[]): GridRows | null => {\r\n const width = viewName ? ROW_NUMBER_WIDTH[viewName] : undefined\r\n if (!width) return null\r\n let first = -1\r\n let last = -1\r\n lines.forEach((line, row) => {\r\n if (/^[0-9a-f]+$/i.test(line.slice(1, 1 + width)) && (line[1 + width] ?? ' ') === ' ') {\r\n if (first < 0) first = row\r\n last = row\r\n }\r\n })\r\n return first < 0 ? null : { first, last, numberWidth: width }\r\n}\r\n\r\n// Grid views whose cursor cells are their schema columns on every numbered row, one character right of\r\n// the schema's x (measured on Model:02 fw 6.5, small and large font). The navigator plans over this\r\n// grid instead of learning it: learned moves there went wrong, as the M8 draws the new view's cursor\r\n// before its title, so cells of one view were learned in another's layout.\r\nconst FIXED_GRID_VIEWS = ['song', 'chain', 'phrase']\r\nconst GRID_COLUMNS: Record<string, { x: number; width: number }[]> = Object.fromEntries(\r\n Object.entries(viewSchema.views as Record<string, { columns?: { x: number; width: number }[] }>).flatMap(([name, view]) =>\r\n FIXED_GRID_VIEWS.includes(name) && view.columns ? [[name, view.columns]] : [],\r\n ),\r\n)\r\n\r\n/** Whether the navigator knows this view's grid and learns nothing there. */\r\nexport const hasFixedGrid = (viewName: string | null) => !!viewName && viewName in GRID_COLUMNS\r\n\r\n/**\r\n * The cells and moves of a fixed grid view's numbered rows (Up/Down keep the column, Left/Right keep the\r\n * row); null on other views. Moves past the grid's edges are left out.\r\n */\r\nexport const fixedGridLayout = (viewName: string | null, grid: GridRows | null, metrics: M8CellMetrics): CursorLayout | null => {\r\n const columns = viewName ? GRID_COLUMNS[viewName] : undefined\r\n if (!columns || !grid) return null\r\n const layout: CursorLayout = { cells: new Map(), moves: new Map(), unreachable: new Set(), suspects: new Set() }\r\n const idAt = (row: number, column: { x: number; width: number }) => {\r\n const rect = {\r\n x: (column.x + 1) * metrics.cellW + metrics.offX,\r\n y: row * metrics.cellH + metrics.offY,\r\n w: column.width * metrics.cellW,\r\n h: metrics.cellH,\r\n }\r\n const id = cellId(rect)\r\n layout.cells.set(id, rect)\r\n return id\r\n }\r\n for (let row = grid.first; row <= grid.last; row++) {\r\n columns.forEach((column, i) => {\r\n const moves: Partial<Record<CursorDir, string>> = {}\r\n if (row > grid.first) moves.up = idAt(row - 1, column)\r\n if (row < grid.last) moves.down = idAt(row + 1, column)\r\n if (i > 0) moves.left = idAt(row, columns[i - 1])\r\n if (i < columns.length - 1) moves.right = idAt(row, columns[i + 1])\r\n layout.moves.set(idAt(row, column), moves)\r\n })\r\n }\r\n return layout\r\n}\r\n\r\n/** A cursor rectangle as the character cells it covers (column, row, width in characters). */\r\nexport const snapToGrid = (rect: CellRect, metrics: M8CellMetrics): CellRect => {\r\n const col = Math.round((rect.x - metrics.offX) / metrics.cellW)\r\n const row = Math.round((rect.y - metrics.offY) / metrics.cellH)\r\n const chars = Math.max(1, Math.round(rect.w / metrics.cellW))\r\n return { x: col * metrics.cellW + metrics.offX, y: row * metrics.cellH + metrics.offY, w: chars * metrics.cellW, h: metrics.cellH }\r\n}\r\n\r\n/** The screen row of a cursor cell. */\r\nexport const rowOf = (rect: CellRect, metrics: M8CellMetrics) => Math.round((rect.y - metrics.offY) / metrics.cellH)\r\n\r\n/** First column of the track panel, or null when the layout has no 40 columns. */\r\nexport const panelColumnFor = (screenWidth: number, metrics: M8CellMetrics): number | null =>\r\n Math.round(screenWidth / metrics.cellW) >= 40 ? PANEL_COLUMN : null\r\n\r\n/**\r\n * The labels that start the rows (words with a letter outside hex digits, two characters or more).\r\n * Values, row numbers and markers are left out, so the signature changes with the layout\r\n * (instrument type, modulator type) but not with the song.\r\n */\r\nexport const layoutSignature = (lines: readonly string[], panelColumn: number | null): string =>\r\n lines\r\n .map((line, row) => {\r\n const token = (panelColumn === null ? line : line.slice(0, panelColumn)).trim().split(/\\s+/)[0] ?? ''\r\n return token.length >= 2 && /[g-z]/i.test(token) ? `${row}${token.toLowerCase()}` : ''\r\n })\r\n .filter(Boolean)\r\n .join(' ')\r\n\r\nconst charRect = (row: number, col: number, metrics: M8CellMetrics): CellRect => ({\r\n x: col * metrics.cellW + metrics.offX,\r\n y: row * metrics.cellH + metrics.offY,\r\n w: metrics.cellW,\r\n h: metrics.cellH,\r\n})\r\n\r\n/**\r\n * Runs of text on the screen, in M8 pixels. Column 0 (the playback marker margin) is left out, and\r\n * so are the characters `covered` says belong to a known cell.\r\n */\r\nexport const textRuns = (\r\n lines: readonly string[],\r\n metrics: M8CellMetrics,\r\n panelColumn: number | null,\r\n covered: (row: number, col: number) => boolean = () => false,\r\n): TextRun[] => {\r\n const runs: TextRun[] = []\r\n lines.forEach((line, row) => {\r\n const chars = [...` ${(panelColumn === null ? line : line.slice(0, panelColumn)).slice(1)}`]\r\n const visible = chars.map((ch, col) => (ch !== ' ' && covered(row, col) ? ' ' : ch)).join('')\r\n for (const match of visible.matchAll(/\\S+/g)) {\r\n const col = match.index ?? 0\r\n const text = match[0]\r\n runs.push({\r\n key: `${row}:${col}:${text.toLowerCase()}`,\r\n rect: { x: col * metrics.cellW + metrics.offX, y: row * metrics.cellH + metrics.offY, w: text.length * metrics.cellW, h: metrics.cellH },\r\n row,\r\n col,\r\n text,\r\n })\r\n }\r\n })\r\n return runs\r\n}\r\n\r\n/** Whether the screen has text under a cursor cell (the same columns as textUnderCursor). */\r\nexport const cellHasText = (rect: CellRect, lines: readonly string[], metrics: M8CellMetrics): boolean => {\r\n const row = rowOf(rect, metrics)\r\n const first = Math.round((rect.x - metrics.offX) / metrics.cellW)\r\n const last = Math.floor((rect.x + rect.w - 1 - metrics.offX) / metrics.cellW)\r\n return (lines[row] ?? '').slice(Math.max(0, first), last + 1).trim().length > 0\r\n}\r\n\r\nconst overlap = (a0: number, a1: number, b0: number, b1: number) => Math.max(0, Math.min(a1, b1) - Math.max(a0, b0))\r\n\r\n/** Whether `cursor` covers `target` horizontally (at least half a character, or half the target). */\r\nexport const coversX = (cursor: CellRect, target: CellRect, metrics: M8CellMetrics) =>\r\n overlap(cursor.x, cursor.x + cursor.w, target.x, target.x + target.w) >= Math.min(metrics.cellW, target.w) / 2\r\n\r\n/** Whether `cursor` covers `target` vertically (at least half a row, or half the target). */\r\nexport const coversY = (cursor: CellRect, target: CellRect, metrics: M8CellMetrics) =>\r\n overlap(cursor.y, cursor.y + cursor.h, target.y, target.y + target.h) >= Math.min(metrics.cellH, target.h) / 2\r\n\r\nexport const covers = (cursor: CellRect, target: CellRect, metrics: M8CellMetrics) =>\r\n coversX(cursor, target, metrics) && coversY(cursor, target, metrics)\r\n\r\nconst center = (rect: CellRect): Point => ({ x: rect.x + rect.w / 2, y: rect.y + rect.h / 2 })\r\n\r\n/** How far the cursor is from a target: the gap between the rectangles, plus a small centre term to break ties. */\r\nexport const rectDistance = (a: CellRect, b: CellRect): number => {\r\n const ca = center(a)\r\n const cb = center(b)\r\n const gapX = Math.max(0, Math.abs(ca.x - cb.x) - (a.w + b.w) / 2 + OVERHANG_PX)\r\n const gapY = Math.max(0, Math.abs(ca.y - cb.y) - (a.h + b.h) / 2 + OVERHANG_PX)\r\n return Math.hypot(gapX, gapY) + 0.01 * Math.hypot(ca.x - cb.x, ca.y - cb.y)\r\n}\r\n\r\nconst pointOffset = (point: Point, rect: CellRect) => ({\r\n dx: Math.max(0, rect.x - point.x, point.x - (rect.x + rect.w)),\r\n dy: Math.max(0, rect.y - point.y, point.y - (rect.y + rect.h)),\r\n})\r\n\r\nexport const pointGoal = (point: Point): CursorGoal => ({ kind: 'point', rect: { x: point.x, y: point.y, w: 1, h: 1 } })\r\n\r\n/** Whether the cursor rectangle has reached the goal. */\r\nexport const goalReached = (goal: CursorGoal, cursor: CellRect, metrics: M8CellMetrics): boolean => {\r\n if (goal.kind === 'cell') return cellId(cursor) === goal.id || covers(cursor, goal.rect, metrics)\r\n return covers(cursor, goal.rect, metrics)\r\n}\r\n\r\n/**\r\n * What a click can go to: the learned cells with text on screen, and the runs of the text they do\r\n * not cover (an FX value stays a candidate once only its command is learned).\r\n */\r\nexport const candidates = (\r\n layout: CursorLayout | undefined,\r\n lines: readonly string[],\r\n metrics: M8CellMetrics,\r\n panelColumn: number | null,\r\n grid: GridRows | null = null,\r\n): { cells: { id: string; rect: CellRect }[]; runs: TextRun[] } => {\r\n // On grid views only the numbered rows hold cells (not the headers or the title)\r\n const inGrid = (row: number) => !grid || (row >= grid.first && row <= grid.last)\r\n const cells: { id: string; rect: CellRect }[] = []\r\n for (const [id, rect] of layout?.cells ?? []) {\r\n if (inGrid(rowOf(rect, metrics)) && cellHasText(rect, lines, metrics)) cells.push({ id, rect })\r\n }\r\n const covered = (row: number, col: number) => {\r\n const char = charRect(row, col, metrics)\r\n return cells.some((cell) => covers(cell.rect, char, metrics))\r\n }\r\n // The row numbers are not fields either\r\n const isRowNumber = (run: TextRun) => !!grid && run.col === 1 && run.text.length <= grid.numberWidth\r\n return { cells, runs: textRuns(lines, metrics, panelColumn, covered).filter((run) => inGrid(run.row) && !isRowNumber(run)) }\r\n}\r\n\r\n/** The candidate nearest to `point`, or a point goal when the screen offers none. */\r\nexport const pickGoal = (\r\n point: Point,\r\n layout: CursorLayout | undefined,\r\n lines: readonly string[],\r\n metrics: M8CellMetrics,\r\n panelColumn: number | null,\r\n grid: GridRows | null = null,\r\n /** Texts to leave out besides the unreachable ones (those that failed in this navigation). */\r\n skip: ReadonlySet<string> = new Set(),\r\n /** After a text failed: only learned cells and texts beside the click (not the next label down the column). */\r\n knownFirst = false,\r\n): CursorGoal => {\r\n const { cells, runs } = candidates(layout, lines, metrics, panelColumn, grid)\r\n const goals: { goal: CursorGoal; area: CellRect }[] = cells.map((cell) => ({ goal: { kind: 'cell', ...cell }, area: cell.rect }))\r\n for (const run of runs) {\r\n // On grid views the text is song data (it differs per song) and every cell is reachable by arrows: a failure there was\r\n // a slow or missed press, so nothing is ever remembered as unreachable\r\n if ((!grid && layout?.unreachable.has(run.key)) || skip.has(run.key)) continue\r\n // Aim at the clicked character of the run, or its end nearest the click\r\n const col = Math.min(run.col + run.text.length - 1, Math.max(run.col, Math.floor((point.x - metrics.offX) / metrics.cellW)))\r\n goals.push({ goal: { kind: 'text', key: run.key, rect: charRect(run.row, col, metrics), run: run.rect, row: run.row }, area: run.rect })\r\n }\r\n\r\n // A candidate of the clicked row is \"beside\" the click when it is close and, when it lies to the right,\r\n // no other text lies between them (the value after the next label belongs to that label). Text between\r\n // the click and a candidate on its left is that candidate's own display ('00C CHROMATIC' is the cell 00).\r\n // On a tie, the candidate on the right wins: a value follows its label.\r\n const line = lines[Math.floor((point.y - metrics.offY) / metrics.cellH)] ?? ''\r\n const textAt = (col: number) => col > 0 && (line[col] ?? ' ') !== ' '\r\n const clickCol = Math.floor((point.x - metrics.offX) / metrics.cellW)\r\n // The end of the clicked text (the click itself when it is on a space). A single space does not end it:\r\n // label words are one space apart ('NOTE PREVIEW'), a label and its value two or more.\r\n let runEnd = clickCol\r\n while (textAt(runEnd) && (textAt(runEnd + 1) || textAt(runEnd + 2))) runEnd += textAt(runEnd + 1) ? 1 : 2\r\n const besideClick = (area: CellRect) => {\r\n const { dx, dy } = pointOffset(point, area)\r\n if (dy > 0 || dx > SAME_ROW_REACH * metrics.cellW) return false\r\n const first = Math.round((area.x - metrics.offX) / metrics.cellW)\r\n if (first <= runEnd) return true\r\n for (let col = runEnd + 1; col < first; col++) if (textAt(col)) return false\r\n return true\r\n }\r\n const anyBeside = goals.some(({ area }) => besideClick(area))\r\n const anyKnown = goals.some(({ goal }) => goal.kind === 'cell')\r\n\r\n let best: { goal: CursorGoal; score: number; tie: number } | null = null\r\n for (const { goal, area } of goals) {\r\n const { dx, dy } = pointOffset(point, area)\r\n if (anyBeside && !besideClick(area)) continue\r\n if (knownFirst && !anyBeside && anyKnown && goal.kind !== 'cell') continue\r\n // A learned cell wins a tie with a text run\r\n const rightOfClick = area.x > point.x\r\n const score = Math.hypot(dx, dy * ROW_WEIGHT) - (goal.kind === 'cell' ? 0.001 : 0) - (rightOfClick ? 0.5 : 0)\r\n const c = center(area)\r\n const tie = Math.hypot(c.x - point.x, c.y - point.y)\r\n if (!best || score < best.score - 1e-6 || (Math.abs(score - best.score) <= 1e-6 && tie < best.tie)) best = { goal, score, tie }\r\n }\r\n return best?.goal ?? pointGoal(point)\r\n}\r\n","export const M8KeyMask = {\n Left: 0b10000000,\n Up: 0b01000000,\n Down: 0b00100000,\n Shift: 0b00010000,\n Play: 0b00001000,\n Right: 0b00000100,\n Opt: 0b00000010,\n Edit: 0b00000001,\n} as const\n\nexport const isLeft = (frame: number) => !!(frame & M8KeyMask.Left)\nexport const isUp = (frame: number) => !!(frame & M8KeyMask.Up)\nexport const isDown = (frame: number) => !!(frame & M8KeyMask.Down)\nexport const isShift = (frame: number) => !!(frame & M8KeyMask.Shift)\nexport const isPlay = (frame: number) => !!(frame & M8KeyMask.Play)\nexport const isRight = (frame: number) => !!(frame & M8KeyMask.Right)\nexport const isOpt = (frame: number) => !!(frame & M8KeyMask.Opt)\nexport const isEdit = (frame: number) => !!(frame & M8KeyMask.Edit)\n\nexport const pressKeys = ({\n left,\n right,\n up,\n down,\n shift,\n play,\n opt,\n edit,\n}: {\n left?: boolean\n right?: boolean\n up?: boolean\n down?: boolean\n shift?: boolean\n play?: boolean\n opt?: boolean\n edit?: boolean\n}) => {\n let state = 0x00\n if (left) {\n state |= M8KeyMask.Left\n }\n if (right) {\n state |= M8KeyMask.Right\n }\n if (up) {\n state |= M8KeyMask.Up\n }\n if (down) {\n state |= M8KeyMask.Down\n }\n if (shift) {\n state |= M8KeyMask.Shift\n }\n if (play) {\n state |= M8KeyMask.Play\n }\n if (opt) {\n state |= M8KeyMask.Opt\n }\n if (edit) {\n state |= M8KeyMask.Edit\n }\n return state\n}\n","// Moves the M8 cursor to a point on the screen with arrow presses. The point is first turned into\r\n// a goal cell (cursorTargets.ts: the nearest learned cursor cell or text run), so a click between\r\n// cells goes to the nearest one. Each key is pressed alone and its effect checked on the cursor\r\n// rectangle; what it did is learned (cursorMap.ts) per layout, and later moves follow the shortest\r\n// learned path. Plain arrow presses made outside a navigation (the user's own) are learned too.\r\n// A text the cursor cannot reach (a label) is remembered and the next nearest cell is tried; when\r\n// nothing works the cursor goes to the learned cell nearest the click, checking each step. On the\r\n// Song, Chain and Phrase grids the cells and moves are known (fixedGridLayout) and nothing is learned.\r\n\r\nimport type { EngineBus } from './bus'\r\nimport { type CellRect, type CursorDir, type CursorLayout, type CursorMap, cellId, createCursorMap, findPath, wentBackwards } from './cursorMap'\r\nimport {\r\n type CursorGoal,\r\n candidates,\r\n covers,\r\n coversX,\r\n coversY,\r\n cellHasText,\r\n fixedGridLayout,\r\n goalReached,\r\n type GridRows,\r\n gridRows,\r\n hasFixedGrid,\r\n layoutSignature,\r\n panelColumnFor,\r\n pickGoal,\r\n type Point,\r\n pointGoal,\r\n rectDistance,\r\n rowOf,\r\n snapToGrid,\r\n} from './cursorTargets'\r\nimport { M8KeyMask } from './keys'\r\nimport type { KeyCommand } from './protocol'\r\nimport type { M8Store } from './store'\r\n\r\nexport type { Point } from './cursorTargets'\r\n\r\nconst keyMap: Record<CursorDir, number> = {\r\n up: M8KeyMask.Up,\r\n down: M8KeyMask.Down,\r\n left: M8KeyMask.Left,\r\n right: M8KeyMask.Right,\r\n}\r\nconst DIRECTION_MASK = M8KeyMask.Up | M8KeyMask.Down | M8KeyMask.Left | M8KeyMask.Right\r\nconst MODIFIER_MASK = M8KeyMask.Edit | M8KeyMask.Opt | M8KeyMask.Shift | M8KeyMask.Play\r\n// Never kept down during a move: Edit+arrow edits the value, Option+arrow pages, Shift+arrow changes view. The\r\n// M8 does not always echo a release (an Edit that opens another view), so the last echo can still show one.\r\nconst NOT_HELD_MASK = DIRECTION_MASK | M8KeyMask.Edit | M8KeyMask.Opt | M8KeyMask.Shift\r\n\r\n// Key presses per navigation, and how many of them may be tries of moves not learned yet\r\nconst MAX_STEPS = 48\r\nconst MAX_EXPLORE = 40\r\n// Key presses allowed to go back to the closest cell after a goal turned out unreachable\r\nconst MAX_RETURN_STEPS = 16\r\n// Text runs tried in turn when the nearest one is a label the cursor cannot reach\r\nconst MAX_GOALS = 4\r\n// No cursor move this long after the key echo: probably blocked. It is only learned as blocked when the\r\n// cursor still has not moved after the grace: a busy M8 (playing, redrawing) can answer late, and a key\r\n// wrongly learned as blocked would hide the cells behind it.\r\nconst MOVE_TIMEOUT_MS = 150\r\nconst MOVE_GRACE_MS = 300\r\n// A move drawn this long after the release of the user's own arrow press still belongs to it\r\nconst PASSIVE_SETTLE_MS = 200\r\nconst DEBUG_NAVIGATOR = false\r\n\r\nconst log = (message: string, data?: unknown) => {\r\n if (!DEBUG_NAVIGATOR) return\r\n if (data !== undefined) console.log(`[cursorNavigator] ${message}`, data)\r\n else console.log(`[cursorNavigator] ${message}`)\r\n}\r\n\r\nconst sameRect = (a: CellRect | null, b: CellRect | null) => !!a && !!b && a.x === b.x && a.y === b.y && a.w === b.w && a.h === b.h\r\n\r\nconst singleDirection = (keys: number): CursorDir | null => {\r\n if (keys & MODIFIER_MASK) return null\r\n const dirs = (Object.keys(keyMap) as CursorDir[]).filter((dir) => keys & keyMap[dir])\r\n return dirs.length === 1 ? dirs[0] : null\r\n}\r\n\r\nexport interface CursorNavigator {\r\n /**\r\n * Moves the cursor to the cell at or nearest to `target` (M8 pixel coordinates). Resolves true\r\n * when it got there, false when it stopped on the closest cell it could reach (or was cancelled).\r\n */\r\n navigateTo(target: Point): Promise<boolean>\r\n dispose(): void\r\n}\r\n\r\nexport interface CursorNavigatorDeps {\r\n bus: EngineBus\r\n store: M8Store\r\n /** The screen text (one string per row, columns kept), used to find the cells near a click. */\r\n getScreenLines?: () => string[]\r\n /** Where the learned layouts are kept; a new empty map by default. */\r\n cursorMap?: CursorMap\r\n}\r\n\r\ntype PendingMove = {\r\n from: CellRect\r\n dir: CursorDir\r\n /** Cell the map expects (undefined: no expectation). */\r\n expected: string | null | undefined\r\n /** The expectation is a guess, not a learned move: a miss is only learned, not distrusted. */\r\n guessed: boolean\r\n /** The first timeout passed: waiting the grace before learning the key as blocked. */\r\n graced?: boolean\r\n}\r\n\r\ntype Run = {\r\n point: Point\r\n /** First and last numbered rows on grid views: the cursor never leaves them. */\r\n grid: GridRows | null\r\n /** The known grid of a Song, Chain or Phrase view, planned over instead of the learned layout. */\r\n fixed: CursorLayout | null\r\n goal: CursorGoal\r\n goals: number\r\n phase: 'seek' | 'return'\r\n viewName: string | null\r\n /** Null while nothing may be learned (selection mode, unknown view). */\r\n layoutKey: string | null\r\n steps: number\r\n explored: number\r\n /** `\"<cellId>:<dir>\"` pressed in this phase. */\r\n tried: Set<string>\r\n /** Presses sent again after the M8 did not answer them (grid views). */\r\n retried: Set<string>\r\n /** Learned moves that did something else during this run. */\r\n distrust: Set<string>\r\n visited: Map<string, CellRect>\r\n /** Texts that failed in this navigation. */\r\n failedTexts: Set<string>\r\n pending: PendingMove | null\r\n resolve: (reached: boolean) => void\r\n}\r\n\r\nexport const createCursorNavigator = ({\r\n bus,\r\n store,\r\n getScreenLines = () => [],\r\n cursorMap = createCursorMap(),\r\n}: CursorNavigatorDeps): CursorNavigator => {\r\n let run: Run | null = null\r\n let currentKeysMask = 0\r\n let holdMask = 0\r\n let waitingForKeyAck = false\r\n let moveTimeout: ReturnType<typeof setTimeout> | null = null\r\n // The user's own arrow press being learned\r\n let passive: { dir: CursorDir; from: CellRect | null; layoutKey: string | null; releasedAt: number | null } | null = null\r\n\r\n const metrics = () => store.get('cellMetrics')\r\n const panelColumn = () => panelColumnFor(store.get('systemInfo')?.screenWidth ?? 480, metrics())\r\n\r\n // The cursor rectangle snapped to the character grid: the M8 draws the same cell a pixel or two\r\n // differently at times (x 332 w 28 h 16, then x 333 w 26 h 14), which must stay one cell\r\n const getRect = (): CellRect | null => {\r\n const current = store.get('cursorRect')\r\n return current ? snapToGrid(current, metrics()) : null\r\n }\r\n\r\n // Layouts are told apart by view, model, font mode and the row labels on screen\r\n const layoutKey = (): string | null => {\r\n const viewName = store.get('viewName')\r\n if (!viewName || store.get('selectionMode')) return null\r\n const system = store.get('systemInfo')\r\n return [\r\n viewName,\r\n system?.model ?? store.get('deviceModel') ?? '?',\r\n system?.fontMode ?? store.get('fontMode') ?? '?',\r\n layoutSignature(getScreenLines(), panelColumn()),\r\n ].join('|')\r\n }\r\n\r\n const clearMoveTimeout = () => {\r\n if (moveTimeout) {\r\n clearTimeout(moveTimeout)\r\n moveTimeout = null\r\n }\r\n }\r\n\r\n const scheduleMoveTimeout = (ms = MOVE_TIMEOUT_MS) => {\r\n clearMoveTimeout()\r\n moveTimeout = setTimeout(() => {\r\n moveTimeout = null\r\n log('move timeout fired', { waitingForKeyAck })\r\n advance('timeout')\r\n }, ms)\r\n }\r\n\r\n const finish = (reached: boolean) => {\r\n if (!run) return\r\n const { resolve } = run\r\n log('finish', { reached, goal: run.goal, rect: getRect() })\r\n run = null\r\n clearMoveTimeout()\r\n waitingForKeyAck = false\r\n holdMask = currentKeysMask & ~NOT_HELD_MASK\r\n resolve(reached)\r\n }\r\n\r\n const tap = (dir: CursorDir) => {\r\n bus.commands.sendKeys(holdMask | keyMap[dir])\r\n bus.commands.sendKeys(holdMask)\r\n }\r\n\r\n const isGoalCell = (r: Run, id: string, rect: CellRect | undefined) => {\r\n if (r.goal.kind === 'cell') return id === r.goal.id\r\n return !!rect && goalReached(r.goal, rect, metrics())\r\n }\r\n\r\n // Arrow keys that bring the cursor closer to the goal on an axis it does not cover yet, vertical first\r\n // (vertical moves keep the column on the M8's grid views).\r\n const towardGoal = (rect: CellRect, goal: CursorGoal): CursorDir[] => {\r\n const dirs: CursorDir[] = []\r\n const cy = rect.y + rect.h / 2\r\n const cx = rect.x + rect.w / 2\r\n if (!coversY(rect, goal.rect, metrics())) dirs.push(goal.rect.y + goal.rect.h / 2 < cy ? 'up' : 'down')\r\n if (!coversX(rect, goal.rect, metrics())) dirs.push(goal.rect.x + goal.rect.w / 2 < cx ? 'left' : 'right')\r\n return dirs\r\n }\r\n\r\n type Move = { dir: CursorDir; expected: string | null | undefined; guessed: boolean }\r\n\r\n // Up on a grid's first row or Down on its last would scroll or change the phrase or chain shown\r\n const leavesGrid = (r: Run, rect: CellRect, dir: CursorDir) => {\r\n if (!r.grid) return false\r\n const row = rowOf(rect, metrics())\r\n return (dir === 'up' && row <= r.grid.first) || (dir === 'down' && row >= r.grid.last)\r\n }\r\n\r\n const planLayout = (r: Run) => r.fixed ?? (r.layoutKey ? cursorMap.get(r.layoutKey) : undefined)\r\n\r\n const chooseMove = (r: Run, rect: CellRect): Move | null => {\r\n const id = cellId(rect)\r\n const layout = planLayout(r)\r\n\r\n if (layout) {\r\n const path = findPath(\r\n layout,\r\n id,\r\n (cell) => isGoalCell(r, cell, layout.cells.get(cell)),\r\n r.distrust,\r\n (cell, dir) => leavesGrid(r, cell, dir),\r\n )\r\n const first = path?.[0]\r\n if (first && !(first.guessed && (r.explored >= MAX_EXPLORE || r.tried.has(`${id}:${first.dir}`)))) {\r\n return { dir: first.dir, expected: first.to, guessed: first.guessed }\r\n }\r\n }\r\n\r\n const known = layout?.moves.get(id) ?? {}\r\n const here = rectDistance(rect, r.goal.rect)\r\n // A key learned as blocked is tried again when nothing else is left: it may have been a late answer\r\n let retry: Move | null = null\r\n for (const dir of towardGoal(rect, r.goal)) {\r\n const move = `${id}:${dir}`\r\n if (r.tried.has(move) || leavesGrid(r, rect, dir)) continue\r\n const to = r.distrust.has(move) ? undefined : known[dir]\r\n if (to === null) {\r\n retry ??= { dir, expected: null, guessed: false }\r\n continue\r\n }\r\n if (to !== undefined) {\r\n // Learned, but no learned path goes on from it: take it only when it gets closer\r\n const toRect = layout?.cells.get(to)\r\n if (toRect && !wentBackwards(rect, dir, toRect) && rectDistance(toRect, r.goal.rect) < here) return { dir, expected: to, guessed: false }\r\n continue\r\n }\r\n if (r.explored >= MAX_EXPLORE) return null\r\n return { dir, expected: undefined, guessed: true }\r\n }\r\n return retry\r\n }\r\n\r\n const press = (r: Run, rect: CellRect, move: Move) => {\r\n tap(move.dir)\r\n r.steps += 1\r\n if (move.guessed) r.explored += 1\r\n r.tried.add(`${cellId(rect)}:${move.dir}`)\r\n r.pending = { from: rect, dir: move.dir, expected: move.expected, guessed: move.guessed }\r\n waitingForKeyAck = true\r\n log('step sent', { dir: move.dir, expected: move.expected, rect })\r\n scheduleMoveTimeout()\r\n }\r\n\r\n // The goal cannot be reached: go back to the visited cell closest to it, then stop\r\n const giveUp = (r: Run, rect: CellRect, localMinimum: boolean) => {\r\n if (r.phase === 'seek') {\r\n // On another part of the same text (two cells drawn as one run): that is the field\r\n if (r.goal.kind === 'text' && covers(rect, r.goal.run, metrics())) {\r\n finish(true)\r\n return\r\n }\r\n if (localMinimum && r.goal.kind === 'text' && r.layoutKey) {\r\n // Not reachable from here: on the goal's row, the texts past the cursor on the goal's side as well\r\n // (labels under or beside values); the whole row when the cursor is next to it and the key toward\r\n // it is blocked (a header); else only the goal (on views where Up/Down jump columns, a row can be\r\n // passed by). Marked once, they are only skipped for this navigation; a second failure in another\r\n // one makes them unreachable.\r\n const { row, run } = r.goal\r\n const onRow = coversY(rect, run, metrics())\r\n const leftOfCursor = run.x + run.w / 2 < rect.x + rect.w / 2\r\n const cursorRow = rowOf(rect, metrics())\r\n const towardRow = row < cursorRow ? 'up' : 'down'\r\n const blockedNextToRow = Math.abs(row - cursorRow) === 1 && planLayout(r)?.moves.get(cellId(rect))?.[towardRow] === null\r\n const keys =\r\n onRow || blockedNextToRow\r\n ? candidates(planLayout(r), getScreenLines(), metrics(), panelColumn(), r.grid)\r\n .runs.filter((text) => text.row === row)\r\n .filter((text) => !onRow || (leftOfCursor ? text.rect.x + text.rect.w <= rect.x + 3 : text.rect.x >= rect.x + rect.w - 3))\r\n .map((text) => text.key)\r\n : []\r\n if (!keys.includes(r.goal.key)) keys.push(r.goal.key)\r\n for (const key of keys) {\r\n r.failedTexts.add(key)\r\n if (!r.grid) cursorMap.markUnreachable(r.layoutKey, key)\r\n }\r\n const next = pickGoal(r.point, planLayout(r), getScreenLines(), metrics(), panelColumn(), r.grid, r.failedTexts, true)\r\n if (r.goals < MAX_GOALS && next.kind !== 'point' && next.rect !== r.goal.rect) {\r\n log('text not reachable, trying the next nearest cell', { from: r.goal, to: next })\r\n r.goal = next\r\n r.goals += 1\r\n r.explored = 0\r\n r.tried.clear()\r\n step(r)\r\n return\r\n }\r\n }\r\n // Out of goals: the learned cell nearest the click, else the visited cell nearest the goal\r\n const clicked = { x: r.point.x, y: r.point.y, w: 1, h: 1 }\r\n const known = candidates(planLayout(r), getScreenLines(), metrics(), panelColumn(), r.grid).cells\r\n let best = rect\r\n if (known.length > 0) {\r\n for (const cell of known) if (rectDistance(cell.rect, clicked) < rectDistance(best, clicked) - 1e-6) best = cell.rect\r\n } else {\r\n for (const cell of r.visited.values()) if (rectDistance(cell, r.goal.rect) < rectDistance(best, r.goal.rect) - 1e-6) best = cell\r\n }\r\n if (!sameRect(best, rect)) {\r\n log('goal not reached, going to the closest cell', { goal: r.goal, best })\r\n r.phase = 'return'\r\n r.goal = { kind: 'cell', id: cellId(best), rect: best }\r\n r.steps = 0\r\n r.explored = 0\r\n r.tried.clear()\r\n step(r)\r\n return\r\n }\r\n }\r\n finish(false)\r\n }\r\n\r\n function step(r: Run) {\r\n const rect = getRect()\r\n if (!rect) {\r\n finish(false)\r\n return\r\n }\r\n if (goalReached(r.goal, rect, metrics())) {\r\n finish(r.phase === 'seek')\r\n return\r\n }\r\n if (r.steps >= (r.phase === 'seek' ? MAX_STEPS : MAX_RETURN_STEPS)) {\r\n giveUp(r, rect, false)\r\n return\r\n }\r\n const move = chooseMove(r, rect)\r\n if (!move) {\r\n giveUp(r, rect, r.explored < MAX_EXPLORE)\r\n return\r\n }\r\n press(r, rect, move)\r\n }\r\n\r\n // A pressed key moved the cursor to `to`, or did not move it (`to` null)\r\n const settle = (r: Run, pending: PendingMove, to: CellRect | null) => {\r\n r.pending = null\r\n clearMoveTimeout()\r\n waitingForKeyAck = false\r\n const key = layoutKey()\r\n if (r.layoutKey && key === r.layoutKey && !r.fixed) cursorMap.recordMove(r.layoutKey, pending.from, pending.dir, to)\r\n const toId = to ? cellId(to) : null\r\n if (!pending.guessed && pending.expected !== undefined && toId !== pending.expected) {\r\n log('learned move did something else', { pending, to })\r\n r.distrust.add(`${cellId(pending.from)}:${pending.dir}`)\r\n }\r\n if (to) r.visited.set(cellId(to), to)\r\n }\r\n\r\n function advance(reason: 'rect' | 'timeout') {\r\n const r = run\r\n if (!r) return\r\n const rect = getRect()\r\n if (!rect) {\r\n finish(false)\r\n return\r\n }\r\n if (r.pending) {\r\n if (!sameRect(r.pending.from, rect)) settle(r, r.pending, rect)\r\n else if (reason === 'timeout' && !r.pending.graced) {\r\n r.pending.graced = true\r\n scheduleMoveTimeout(MOVE_GRACE_MS)\r\n return\r\n } else if (reason === 'timeout') {\r\n // The M8 drops keys pressed right after a view change, and on a grid view no key is ever blocked\r\n // inside the rows: press once more before the key is learned as blocked.\r\n const move = `${cellId(r.pending.from)}:${r.pending.dir}`\r\n if (r.grid && !r.retried.has(move)) {\r\n log('no move after the grace, pressing again', { move })\r\n r.retried.add(move)\r\n r.tried.delete(move)\r\n r.pending = null\r\n waitingForKeyAck = false\r\n } else settle(r, r.pending, null)\r\n } else return\r\n }\r\n // A plain arrow never changes the view; if it did (a dialog), stop\r\n if (store.get('viewName') !== r.viewName) {\r\n finish(false)\r\n return\r\n }\r\n step(r)\r\n }\r\n\r\n const onKey = (data: KeyCommand) => {\r\n currentKeysMask = data.keys ?? 0\r\n if (run) {\r\n passive = null\r\n if (waitingForKeyAck) {\r\n waitingForKeyAck = false\r\n if (run.pending) scheduleMoveTimeout()\r\n }\r\n return\r\n }\r\n holdMask = currentKeysMask & ~NOT_HELD_MASK\r\n const dir = singleDirection(currentKeysMask)\r\n if (dir) {\r\n if (!passive || passive.dir !== dir || passive.releasedAt !== null) passive = { dir, from: getRect(), layoutKey: layoutKey(), releasedAt: null }\r\n } else if (currentKeysMask === 0 && passive) {\r\n passive.releasedAt ??= Date.now()\r\n } else {\r\n passive = null\r\n }\r\n }\r\n\r\n const onRect = () => {\r\n if (run) {\r\n advance('rect')\r\n return\r\n }\r\n const rect = getRect()\r\n const key = layoutKey()\r\n // Nothing to learn on the known grids\r\n if (!rect || !key || hasFixedGrid(store.get('viewName'))) {\r\n passive = null\r\n return\r\n }\r\n // Only moves made by an arrow are learned: a rectangle drawn while the view changes can be the old view's\r\n const p = passive\r\n if (p?.from && p.layoutKey === key && (p.releasedAt === null || Date.now() - p.releasedAt <= PASSIVE_SETTLE_MS)) {\r\n const lines = getScreenLines()\r\n if (!sameRect(p.from, rect) && cellHasText(p.from, lines, metrics()) && cellHasText(rect, lines, metrics())) {\r\n cursorMap.recordMove(key, p.from, p.dir, rect)\r\n }\r\n // Auto-repeat goes on from here\r\n p.from = rect\r\n } else {\r\n passive = null\r\n }\r\n }\r\n\r\n bus.protocol.eventBus.on('key', onKey)\r\n const unsubscribeRect = store.sub('cursorRect', onRect)\r\n\r\n return {\r\n navigateTo: (point: Point): Promise<boolean> =>\r\n new Promise((resolve) => {\r\n const rect = getRect()\r\n if (!store.get('cursorPos') || !rect) {\r\n resolve(false)\r\n return\r\n }\r\n\r\n // A move still on its way belongs to the previous navigation: wait for it before planning\r\n const carried = run?.pending ?? null\r\n if (run) {\r\n const previous = run\r\n run = null\r\n previous.resolve(false)\r\n }\r\n\r\n const key = layoutKey()\r\n const lines = getScreenLines()\r\n const grid = gridRows(store.get('viewName'), lines)\r\n // The known grid, unless the cursor is on none of its cells (another model or firmware): then it is learned\r\n const known = key ? fixedGridLayout(store.get('viewName'), grid, metrics()) : null\r\n const fixed = known?.cells.has(cellId(rect)) ? known : null\r\n const layout = fixed ?? (key ? cursorMap.get(key) : undefined)\r\n const goal = store.get('selectionMode') ? pointGoal(point) : pickGoal(point, layout, lines, metrics(), panelColumn(), grid)\r\n if (key && !fixed && cellHasText(rect, lines, metrics())) cursorMap.recordCell(key, rect)\r\n if (!carried) holdMask = currentKeysMask & ~NOT_HELD_MASK\r\n passive = null\r\n log('navigateTo start', { point, goal })\r\n\r\n const r: Run = {\r\n point,\r\n grid,\r\n fixed,\r\n goal,\r\n goals: 1,\r\n phase: 'seek',\r\n viewName: store.get('viewName'),\r\n layoutKey: key,\r\n steps: 0,\r\n explored: 0,\r\n tried: new Set(),\r\n retried: new Set(),\r\n distrust: new Set(),\r\n visited: new Map([[cellId(rect), rect]]),\r\n failedTexts: new Set(),\r\n pending: carried ? { ...carried, expected: undefined, guessed: true } : null,\r\n resolve,\r\n }\r\n run = r\r\n if (carried) {\r\n if (!moveTimeout) scheduleMoveTimeout()\r\n return\r\n }\r\n step(r)\r\n }),\r\n dispose: () => {\r\n finish(false)\r\n bus.protocol.eventBus.off('key', onKey)\r\n unsubscribeRect()\r\n },\r\n }\r\n}\r\n","import type { M8State } from '../../types'\n\n// Pure helpers to read the M8 file browser (load/save views) from screen snapshots.\n\nexport const normalizeForSearch = (text: string | null): string => {\n return text?.trim().toLowerCase() ?? ''\n}\n\n// M8 browser files use short extensions such as .MBN, .MNS, .MNT...\n// Accept \".M\" followed by two alphanumeric characters so base-name matching\n// works across file browser types without hardcoding each one.\nexport const FILE_BROWSER_EXTENSION_SUFFIX_REGEX = /\\.m[a-z0-9]{2}$/i\n\nexport type FileBrowserSnapshot = {\n viewName: string | null\n viewTitle: string | null\n cursorPos: M8State['cursorPos']\n textUnderCursor: string | null\n currentLine: string | null\n}\n\nexport const getFileBrowserSnapshotFingerprint = (snapshot: FileBrowserSnapshot): string => {\n return [\n snapshot.viewName ?? '',\n snapshot.viewTitle ?? '',\n snapshot.cursorPos?.x ?? '',\n snapshot.cursorPos?.y ?? '',\n snapshot.textUnderCursor ?? '',\n snapshot.currentLine ?? '',\n ].join('|')\n}\n\nexport const getSelectedFileBrowserEntry = (snapshot: FileBrowserSnapshot): string | null => {\n return snapshot.textUnderCursor?.trim() || snapshot.currentLine?.trim() || null\n}\n\nexport const isFileBrowserView = (snapshot: Pick<FileBrowserSnapshot, 'viewName' | 'viewTitle'>): boolean => {\n const normalizedViewName = normalizeForSearch(snapshot.viewName)\n const normalizedViewTitle = normalizeForSearch(snapshot.viewTitle)\n return (\n normalizedViewName.includes('load') ||\n normalizedViewName.includes('save') ||\n normalizedViewTitle.includes('load') ||\n normalizedViewTitle.includes('save')\n )\n}\n\nexport const isParentDirectoryEntry = (entry: string | null): boolean => normalizeForSearch(entry) === '/..'\nexport const isDirectoryEntry = (entry: string | null): boolean => !!entry?.trim().startsWith('/')\nexport const isM8FileEntry = (entry: string | null): boolean =>\n !!entry && !isDirectoryEntry(entry) && FILE_BROWSER_EXTENSION_SUFFIX_REGEX.test(entry.trim())\nexport const getFileEntrySearchTerms = (entry: string | null): string[] => {\n const trimmedEntry = entry?.trim() ?? ''\n if (!trimmedEntry) return []\n\n const normalizedFullName = normalizeForSearch(trimmedEntry)\n const normalizedBaseName = normalizeForSearch(trimmedEntry.replace(FILE_BROWSER_EXTENSION_SUFFIX_REGEX, ''))\n return Array.from(new Set([normalizedFullName, normalizedBaseName].filter(Boolean)))\n}\n\n// --- Whole-screen reading -------------------------------------------------------------\n// The browser lists one entry per screen row, so a single screen read shows the visible\n// window of the current directory: directories (\"/name\"), the parent entry (\"/..\") and\n// files (\"name.m8s\"). Only entries with an M8 extension can be a search result; the rest\n// (\"other\") is still a row the cursor can stand on.\n\nexport type FileBrowserEntryKind = 'parent' | 'dir' | 'file' | 'other'\n\nexport type FileBrowserScreenEntry = {\n /** Screen row, equal to the cursor grid row when the cursor stands on it. */\n row: number\n text: string\n /** Normalized text, unique inside a directory. */\n key: string\n kind: FileBrowserEntryKind\n}\n\nexport type FileBrowserScreen = {\n entries: FileBrowserScreenEntry[]\n cursorRow: number | null\n cursorKey: string | null\n /** The \"/..\" entry is visible, so the top of the directory is on screen. */\n hasParent: boolean\n /** False when the cursor row disagrees with the cursor atoms: the screen is mid-redraw. */\n consistent: boolean\n}\n\nexport const PARENT_ENTRY_KEY = '/..'\n\nexport const classifyFileBrowserEntry = (text: string): FileBrowserEntryKind => {\n if (isParentDirectoryEntry(text)) return 'parent'\n // A directory name never contains a slash, so \"/a/b\" is a path shown in a header.\n if (isDirectoryEntry(text)) return /^\\/[^/]+$/.test(text.trim()) ? 'dir' : 'other'\n return isM8FileEntry(text) ? 'file' : 'other'\n}\n\nexport const parseFileBrowserScreen = (\n lines: string[],\n snapshot: Pick<FileBrowserSnapshot, 'cursorPos' | 'viewTitle' | 'textUnderCursor'>,\n): FileBrowserScreen => {\n const rowText = (row: number): string => lines[row]?.trim() ?? ''\n const cursorRow = snapshot.cursorPos?.y ?? null\n const title = normalizeForSearch(snapshot.viewTitle)\n\n // The list is the run of non-empty rows around the cursor; anything separated from it\n // by a blank row (footer, hints) is not part of it.\n let first = 0\n let last = lines.length - 1\n if (cursorRow !== null && rowText(cursorRow)) {\n first = cursorRow\n while (first > 0 && rowText(first - 1)) first -= 1\n last = cursorRow\n while (last < lines.length - 1 && rowText(last + 1)) last += 1\n }\n\n const entries: FileBrowserScreenEntry[] = []\n for (let row = first; row <= last; row += 1) {\n const text = rowText(row)\n if (!text || (title && normalizeForSearch(text) === title)) continue\n entries.push({ row, text, key: normalizeForSearch(text), kind: classifyFileBrowserEntry(text) })\n }\n\n const cursorText = snapshot.textUnderCursor?.trim()\n return {\n entries,\n cursorRow,\n cursorKey: cursorRow === null ? null : (entries.find((entry) => entry.row === cursorRow)?.key ?? null),\n hasParent: entries.some((entry) => entry.kind === 'parent'),\n consistent: !cursorText || cursorRow === null || rowText(cursorRow).includes(cursorText),\n }\n}\n\n/**\n * Merge the entry keys of one screen into the display order accumulated for a directory.\n * Screens overlap, so the first known entry anchors the new ones. When the screen\n * contradicts the accumulated order (listing re-sorted or reloaded) the order restarts from\n * the screen and `consistent` is false.\n */\nexport const mergeFileBrowserOrder = (order: string[], screen: string[]): { order: string[]; consistent: boolean } => {\n if (screen.length === 0) return { order, consistent: true }\n if (order.length === 0) return { order: [...screen], consistent: true }\n\n const anchor = screen.findIndex((key) => order.includes(key))\n if (anchor === -1) return { order: [...screen], consistent: false }\n\n const start = order.indexOf(screen[anchor]) - anchor\n const merged = start < 0 ? [...new Array<string>(-start).fill(''), ...order] : [...order]\n const offset = Math.max(0, start)\n for (let index = 0; index < screen.length; index += 1) {\n const position = offset + index\n const known = position < merged.length && merged[position] !== ''\n if (known) {\n if (merged[position] !== screen[index]) return { order: [...screen], consistent: false }\n continue\n }\n // A new entry that is already known elsewhere means the listing was reordered.\n if (merged.includes(screen[index])) return { order: [...screen], consistent: false }\n if (position >= merged.length) merged.push(screen[index])\n else merged[position] = screen[index]\n }\n return { order: merged, consistent: true }\n}\n\nconst tokenize = (text: string): string[] => text.split(/[^a-z0-9]+/).filter((token) => token.length >= 3)\nconst TITLE_STOP_WORDS = new Set(['load', 'save', 'select', 'create', 'directory', 'new'])\n\n/** Words of the search target (without its extension) and of the view title, to rank directories. */\nexport const getDirectoryHintTokens = (targetText: string, viewTitle: string | null) => ({\n target: tokenize(normalizeForSearch(targetText).replace(FILE_BROWSER_EXTENSION_SUFFIX_REGEX, '')),\n title: tokenize(normalizeForSearch(viewTitle)).filter((token) => !TITLE_STOP_WORDS.has(token)),\n})\n\n/**\n * 2: the folder name shares a word with the target (\"/Bass\" for \"bassline\"),\n * 1: it shares a word with the view title (\"/Instruments\" in \"LOAD INSTRUMENT\"), else 0.\n * Only used to decide which folder to open first, never to skip one.\n */\nexport const scoreDirectoryRelevance = (directoryKey: string, hints: ReturnType<typeof getDirectoryHintTokens>): number => {\n const name = directoryKey.replace(/^\\//, '')\n if (name.length < 3) return 0\n const related = (token: string) => token.includes(name) || name.includes(token)\n if (hints.target.some(related)) return 2\n if (hints.title.some(related)) return 1\n return 0\n}\n","import type { M8Store } from '../store'\nimport { type FileBrowserSnapshot, getFileBrowserSnapshotFingerprint } from './fileBrowserHelpers'\n\n// Wait for a specific duration\nexport const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))\n\n/** Where key frames go: a connected bus's commands, or nothing while disconnected. */\nexport type KeySink = { sendKeys: (keys: number) => void }\n\n/**\n * What the value editors and the file browser search need from the engine: key output\n * to the M8 and observation of the screen state.\n */\nexport type HostIO = {\n store: M8Store\n debugLog: (...args: unknown[]) => void\n hasBus: () => boolean\n sendKeys: (keys: number) => void\n pressAndRelease: (keys: number, delayMs?: number) => Promise<void>\n waitForTextUnderCursorChange: (previousValue: string | null, timeoutMs?: number) => Promise<string | null>\n waitForCurrentLineChange: (previousValue: string | null, timeoutMs?: number) => Promise<string | null>\n getFileBrowserSnapshot: () => FileBrowserSnapshot\n waitForFileBrowserSnapshotChange: (previousSnapshot: FileBrowserSnapshot, timeoutMs?: number) => Promise<FileBrowserSnapshot>\n /** Whole M8 screen as text rows (row index === cursor grid row). */\n getScreenLines: () => string[]\n}\n\nexport const createHostIO = ({\n getKeySink,\n store,\n debugLog,\n getScreenLines,\n}: {\n getKeySink: () => KeySink | undefined\n store: M8Store\n debugLog: (...args: unknown[]) => void\n getScreenLines: () => string[]\n}): HostIO => {\n const hasBus = () => !!getKeySink()\n\n // Send keys helper - reads the sink lazily so a reconnected device is picked up\n const sendKeys = (keys: number) => {\n getKeySink()?.sendKeys(keys)\n }\n\n // Send keys and release\n const pressAndRelease = async (keys: number, delayMs: number = 50) => {\n sendKeys(keys)\n await wait(delayMs)\n sendKeys(0)\n await wait(delayMs)\n }\n\n // Prefer atom-change synchronization over fixed sleeps when reading post-key values.\n const waitForTextUnderCursorChange = (previousValue: string | null, timeoutMs: number = 500): Promise<string | null> => {\n return new Promise((resolve) => {\n let settled = false\n let unsubscribe: (() => void) | null = null\n let timeout: ReturnType<typeof setTimeout> | null = null\n\n const finish = (value: string | null) => {\n if (settled) return\n settled = true\n if (timeout !== null) {\n clearTimeout(timeout)\n }\n unsubscribe?.()\n resolve(value)\n }\n\n // If the value already changed before we started waiting (race with key press timing),\n // resolve immediately instead of subscribing and timing out.\n const currentValue = store.get('textUnderCursor')\n if (currentValue !== previousValue) {\n finish(currentValue)\n return\n }\n\n unsubscribe = store.sub('textUnderCursor', () => {\n const next = store.get('textUnderCursor')\n if (next !== previousValue) {\n finish(next)\n }\n })\n\n timeout = setTimeout(() => {\n finish(store.get('textUnderCursor'))\n }, timeoutMs)\n })\n }\n\n const getFileBrowserSnapshot = (): FileBrowserSnapshot => {\n return {\n viewName: store.get('viewName'),\n viewTitle: store.get('viewTitle'),\n cursorPos: store.get('cursorPos'),\n textUnderCursor: store.get('textUnderCursor'),\n currentLine: store.get('currentLine'),\n }\n }\n\n const waitForFileBrowserSnapshotChange = (previousSnapshot: FileBrowserSnapshot, timeoutMs: number = 500): Promise<FileBrowserSnapshot> => {\n return new Promise((resolve) => {\n let settled = false\n let unsubscribeText: (() => void) | null = null\n let unsubscribeLine: (() => void) | null = null\n let unsubscribeCursor: (() => void) | null = null\n let unsubscribeViewName: (() => void) | null = null\n let unsubscribeViewTitle: (() => void) | null = null\n let timeout: ReturnType<typeof setTimeout> | null = null\n const previousFingerprint = getFileBrowserSnapshotFingerprint(previousSnapshot)\n\n const finish = (snapshot: FileBrowserSnapshot) => {\n if (settled) return\n settled = true\n if (timeout !== null) {\n clearTimeout(timeout)\n }\n unsubscribeText?.()\n unsubscribeLine?.()\n unsubscribeCursor?.()\n unsubscribeViewName?.()\n unsubscribeViewTitle?.()\n resolve(snapshot)\n }\n\n const checkForChange = () => {\n const snapshot = getFileBrowserSnapshot()\n if (getFileBrowserSnapshotFingerprint(snapshot) !== previousFingerprint) {\n finish(snapshot)\n }\n }\n\n const currentSnapshot = getFileBrowserSnapshot()\n if (getFileBrowserSnapshotFingerprint(currentSnapshot) !== previousFingerprint) {\n finish(currentSnapshot)\n return\n }\n\n unsubscribeText = store.sub('textUnderCursor', checkForChange)\n unsubscribeLine = store.sub('currentLine', checkForChange)\n unsubscribeCursor = store.sub('cursorPos', checkForChange)\n unsubscribeViewName = store.sub('viewName', checkForChange)\n unsubscribeViewTitle = store.sub('viewTitle', checkForChange)\n\n timeout = setTimeout(() => {\n finish(getFileBrowserSnapshot())\n }, timeoutMs)\n })\n }\n\n // Prefer atom-change synchronization over fixed sleeps when reading post-key values.\n const waitForCurrentLineChange = (previousValue: string | null, timeoutMs: number = 500): Promise<string | null> => {\n return new Promise((resolve) => {\n let settled = false\n let unsubscribe: (() => void) | null = null\n let timeout: ReturnType<typeof setTimeout> | null = null\n\n const finish = (value: string | null) => {\n if (settled) return\n settled = true\n if (timeout !== null) {\n clearTimeout(timeout)\n }\n unsubscribe?.()\n resolve(value)\n }\n\n // If the value already changed before we started waiting (race with key press timing),\n // resolve immediately instead of subscribing and timing out.\n const currentValue = store.get('currentLine')\n if (currentValue !== previousValue) {\n finish(currentValue)\n return\n }\n\n unsubscribe = store.sub('currentLine', () => {\n const next = store.get('currentLine')\n if (next !== previousValue) {\n finish(next)\n }\n })\n\n timeout = setTimeout(() => {\n finish(store.get('currentLine'))\n }, timeoutMs)\n })\n }\n\n return {\n store,\n debugLog,\n hasBus,\n sendKeys,\n pressAndRelease,\n waitForTextUnderCursorChange,\n waitForCurrentLineChange,\n getFileBrowserSnapshot,\n waitForFileBrowserSnapshotChange,\n getScreenLines,\n }\n}\n","import { M8KeyMask } from '../keys'\nimport {\n FILE_BROWSER_EXTENSION_SUFFIX_REGEX,\n type FileBrowserEntryKind,\n type FileBrowserScreen,\n type FileBrowserSnapshot,\n getDirectoryHintTokens,\n getFileBrowserSnapshotFingerprint,\n getFileEntrySearchTerms,\n getSelectedFileBrowserEntry,\n isFileBrowserView,\n isM8FileEntry,\n mergeFileBrowserOrder,\n normalizeForSearch,\n PARENT_ENTRY_KEY,\n parseFileBrowserScreen,\n scoreDirectoryRelevance,\n} from './fileBrowserHelpers'\nimport { type HostIO, wait } from './hostIO'\n\nconst browseLog = (message: string, details?: unknown) => {\n if (details === undefined) {\n console.log('[M8SDK browseFile]', message)\n return\n }\n console.log('[M8SDK browseFile]', message, details)\n}\n\ntype Direction = 'up' | 'down'\ntype SearchOutcome = 'found' | 'missed' | 'aborted'\n\n/** Rows moved by Opt+Up / Opt+Down. */\nconst JUMP_ROWS = 8\n/**\n * A window showing fewer entries than this cannot be scrolled, so it is the whole\n * directory. Deliberately below any real list height (the screen has 24 rows): the\n * actual height is learned from the first scrolled window.\n */\nconst MIN_FULL_WINDOW = 8\nconst MAX_DEPTH = 64\nconst MAX_SCREENS_PER_LEVEL = 512\n\n/** What is known about one directory while it is being searched. */\ntype Level = {\n /** Entry keys in display order, merged from every screen seen. */\n order: string[]\n kinds: Map<string, FileBrowserEntryKind>\n /** Entry text as displayed. */\n names: Map<string, string>\n /** The first / last entry of the directory has been on screen. */\n top: boolean\n bottom: boolean\n /** \"/..\" exists, i.e. this is not the SD card root. */\n hasParent: boolean\n /** Alphabetical sort was applied to this listing and the order seen so far agrees with it. */\n sorted: boolean\n visited: Set<string>\n /** Matches the cursor could not be moved onto. */\n rejected: Set<string>\n}\n\nconst createLevel = (): Level => ({\n order: [],\n kinds: new Map(),\n names: new Map(),\n top: false,\n bottom: false,\n hasParent: false,\n sorted: false,\n visited: new Set(),\n rejected: new Set(),\n})\n\nconst opposite = (direction: Direction): Direction => (direction === 'up' ? 'down' : 'up')\n\n/**\n * Navigate the M8 file browser (load/save views) to a file by name.\n *\n * The whole screen is readable, so every move is decided from what is visible:\n * - a match anywhere on screen is reached directly;\n * - a directory is read page by page (Opt+Up/Down) before any sub-directory is opened,\n * and a window that is not full is the whole directory (nothing to page);\n * - sub-directories are only opened once the files of the level have been checked, and\n * after coming back the cursor already sits on the folder just visited, so the next\n * folder is a few rows away instead of a trip to the top of the list.\n */\nexport const createFileBrowserSearch = (io: HostIO) => {\n const { hasBus, pressAndRelease, getFileBrowserSnapshot, waitForFileBrowserSnapshotChange, getScreenLines } = io\n\n // Layout facts learned from the device, reused by later searches on this host.\n // Rows of a window known to be scrolled (so full), per view.\n const fullWindowRows = new Map<string, number>()\n // Whether Opt+Up/Down moves the remaining rows near an end (true) or refuses to move (false).\n let jumpClampsAtEnd: boolean | null = null\n // Whether a sorted listing puts every folder before the first file. Unknown until a sorted\n // listing shows a folder before a file it would follow alphabetically (true), or a folder\n // after a file (false).\n let foldersFirst: boolean | null = null\n\n const browseFileImpl = async (targetText: string, exact: boolean = true): Promise<boolean> => {\n if (!hasBus()) return false\n\n const normalizedTarget = normalizeForSearch(targetText)\n if (!normalizedTarget) return false\n const targetIncludesExtension = FILE_BROWSER_EXTENSION_SUFFIX_REGEX.test(normalizedTarget)\n const hints = getDirectoryHintTokens(targetText, getFileBrowserSnapshot().viewTitle)\n\n browseLog('start', { targetText, normalizedTarget, exact })\n\n const isActive = (): boolean => isFileBrowserView(getFileBrowserSnapshot())\n\n const stepFileBrowser = async (keyMask: number, timeoutMs: number = 350): Promise<{ changed: boolean; snapshot: FileBrowserSnapshot }> => {\n const previousSnapshot = getFileBrowserSnapshot()\n await pressAndRelease(keyMask, 35)\n const nextSnapshot = await waitForFileBrowserSnapshotChange(previousSnapshot, timeoutMs)\n const changed = getFileBrowserSnapshotFingerprint(nextSnapshot) !== getFileBrowserSnapshotFingerprint(previousSnapshot)\n return { changed, snapshot: nextSnapshot }\n }\n\n const stepMask = (direction: Direction): number => (direction === 'up' ? M8KeyMask.Up : M8KeyMask.Down)\n const jumpMask = (direction: Direction): number => M8KeyMask.Opt | stepMask(direction)\n\n const applyAlphabeticalSort = async (): Promise<void> => {\n const previousSnapshot = getFileBrowserSnapshot()\n browseLog('apply alphabetical sort')\n await pressAndRelease(M8KeyMask.Shift | M8KeyMask.Opt, 35)\n // Sorting the browser can take a moment; give the UI time to settle\n // before relying on the next cursor/text snapshot.\n await wait(180)\n await waitForFileBrowserSnapshotChange(previousSnapshot, 300)\n }\n\n const entryMatchesTarget = (entry: string | null): boolean => {\n if (!isM8FileEntry(entry)) return false\n const searchTerms = getFileEntrySearchTerms(entry)\n return searchTerms.some((term) => (exact ? term === normalizedTarget : term.includes(normalizedTarget)))\n }\n\n // Name of a file as compared with the target: with its extension only if the target has one.\n const comparableName = (entry: string): string => {\n const trimmed = entry.trim()\n return targetIncludesExtension ? trimmed : trimmed.replace(FILE_BROWSER_EXTENSION_SUFFIX_REGEX, '')\n }\n const primarySearchTerm = (entry: string): string => normalizeForSearch(comparableName(entry))\n const typedTarget = comparableName(targetText)\n\n // --- Reading the screen ---------------------------------------------------------\n\n const readScreen = async (): Promise<FileBrowserScreen> => {\n for (let attempt = 0; ; attempt += 1) {\n const screen = parseFileBrowserScreen(getScreenLines(), getFileBrowserSnapshot())\n // Cursor atoms and screen text are updated separately: re-read when they disagree.\n if (screen.consistent || attempt >= 2) return screen\n await wait(40)\n }\n }\n\n // Fold a screen into what is known about the directory.\n const absorb = (level: Level, screen: FileBrowserScreen): void => {\n const keys = screen.entries.map((entry) => entry.key)\n const merged = mergeFileBrowserOrder(level.order, keys)\n level.order = merged.order\n if (!merged.consistent) {\n level.top = false\n level.bottom = false\n }\n for (const entry of screen.entries) {\n level.kinds.set(entry.key, entry.kind)\n level.names.set(entry.key, entry.text)\n }\n\n const viewKey = normalizeForSearch(getFileBrowserSnapshot().viewName)\n if (screen.hasParent) {\n level.hasParent = true\n level.top = true\n } else if (level.hasParent && keys.length > 0) {\n // \"/..\" is above the window: the list is scrolled, so the window is full.\n const known = fullWindowRows.get(viewKey)\n if (known === undefined || keys.length < known) fullWindowRows.set(viewKey, keys.length)\n }\n\n // A window that is not full cannot be scrolled: it already is the whole directory.\n if (keys.length < Math.max(MIN_FULL_WINDOW, fullWindowRows.get(viewKey) ?? 0)) {\n level.top = true\n level.bottom = true\n }\n }\n\n // --- Moving the cursor ----------------------------------------------------------\n\n // Move one page toward `direction`. Returns the new screen, or null at the end of the list.\n const pageLevel = async (level: Level, screen: FileBrowserScreen, direction: Direction): Promise<FileBrowserScreen | null> => {\n const jumped = (await stepFileBrowser(jumpMask(direction), 280)).changed\n let moved = jumped\n let reachedEnd = false\n\n if (!jumped && jumpClampsAtEnd !== true) {\n // Opt+Up/Down may refuse to move when less than a page remains, or the cursor may\n // already be on the last entry. Plain steps tell the two apart and walk the rest of\n // the way; the window follows the cursor, so one read at the end shows it all.\n for (let step = 0; step < JUMP_ROWS; step += 1) {\n if (!(await stepFileBrowser(stepMask(direction), 250)).changed) {\n reachedEnd = true\n break\n }\n moved = true\n jumpClampsAtEnd = false\n }\n } else if (!jumped) {\n reachedEnd = true\n }\n\n if (reachedEnd) {\n if (direction === 'up') level.top = true\n else level.bottom = true\n browseLog('end of list', { direction })\n }\n if (!moved) return null\n\n const next = await readScreen()\n absorb(level, next)\n // Indexes are taken after the merge, which may have inserted entries above.\n const fromIndex = screen.cursorKey ? level.order.indexOf(screen.cursorKey) : -1\n const toIndex = next.cursorKey ? level.order.indexOf(next.cursorKey) : -1\n // A jump that moved fewer rows than a full page was cut short by the end of the list.\n const travelled = Math.abs(toIndex - fromIndex)\n if (jumped && fromIndex >= 0 && toIndex >= 0 && travelled > 0 && travelled < JUMP_ROWS) {\n jumpClampsAtEnd = true\n }\n return next\n }\n\n const directionToward = (level: Level, screen: FileBrowserScreen, key: string): Direction => {\n if (key === PARENT_ENTRY_KEY) return 'up'\n const target = level.order.indexOf(key)\n const cursor = screen.cursorKey ? level.order.indexOf(screen.cursorKey) : -1\n if (target >= 0 && cursor >= 0) return target < cursor ? 'up' : 'down'\n return level.top && !level.bottom ? 'down' : 'up'\n }\n\n // Put the cursor on `key`. Returns the screen it ends on, or null if it cannot get there.\n const moveCursorToKey = async (level: Level, key: string, startScreen: FileBrowserScreen): Promise<FileBrowserScreen | null> => {\n let screen = startScreen\n let direction: Direction | null = null\n let reversed = false\n\n for (let i = 0; i < 96; i += 1) {\n const entry = screen.entries.find((candidate) => candidate.key === key)\n if (entry) {\n if (screen.cursorRow === null) return null\n const delta = entry.row - screen.cursorRow\n if (delta === 0) return screen\n // Screen row delta === rows to travel; jump when far, then fine steps.\n const way: Direction = delta < 0 ? 'up' : 'down'\n const result = await stepFileBrowser(Math.abs(delta) >= JUMP_ROWS ? jumpMask(way) : stepMask(way), 250)\n if (!result.changed) return null\n screen = await readScreen()\n absorb(level, screen)\n continue\n }\n\n // Not on screen: page toward it, turning round once if that end is reached.\n direction ??= directionToward(level, screen, key)\n const next = await pageLevel(level, screen, direction)\n if (next) {\n screen = next\n } else if (reversed) {\n return null\n } else {\n reversed = true\n direction = opposite(direction)\n }\n }\n return null\n }\n\n // --- Searching ------------------------------------------------------------------\n\n // 'failed': a match is visible but the cursor never landed on it (e.g. stale screen).\n const selectVisibleMatch = async (level: Level, screen: FileBrowserScreen): Promise<'found' | 'none' | 'failed'> => {\n const cursorRow = screen.cursorRow ?? 0\n const candidate = screen.entries\n .filter((entry) => entry.kind === 'file' && !level.rejected.has(entry.key) && entryMatchesTarget(entry.text))\n .sort((a, b) => Math.abs(a.row - cursorRow) - Math.abs(b.row - cursorRow))[0]\n if (!candidate) return 'none'\n\n const reached = await moveCursorToKey(level, candidate.key, screen)\n const entry = getSelectedFileBrowserEntry(getFileBrowserSnapshot())\n if (reached && entryMatchesTarget(entry)) {\n browseLog('match found', { entry, y: reached.cursorRow })\n await stepFileBrowser(M8KeyMask.Edit, 500)\n return 'found'\n }\n\n browseLog('match not reachable', { candidate: candidate.text })\n level.rejected.add(candidate.key)\n return 'failed'\n }\n\n // The files of a level in display order, as (case-insensitive, as-displayed) comparable names.\n const levelFiles = (level: Level): { ci: string; raw: string }[] =>\n level.order\n .filter((key) => level.kinds.get(key) === 'file')\n .map((key) => {\n const text = level.names.get(key) ?? ''\n return { ci: primarySearchTerm(text), raw: comparableName(text) }\n })\n\n // Called on a listing we sorted: checks the order is really alphabetical (by name with or\n // without case, since the firmware's rule is not known) and learns where folders go.\n const reviewSortedLevel = (level: Level): void => {\n if (!level.sorted) return\n const files = levelFiles(level)\n if (files.some((file, index) => index > 0 && (files[index - 1].ci > file.ci || files[index - 1].raw > file.raw))) {\n level.sorted = false\n browseLog('listing is not alphabetical, order hints disabled')\n return\n }\n\n const entries = level.order.map((key) => {\n const text = (level.names.get(key) ?? '').trim()\n return { kind: level.kinds.get(key), ci: normalizeForSearch(text).replace(/^\\//, ''), raw: text.replace(/^\\//, '') }\n })\n const firstFile = entries.findIndex((entry) => entry.kind === 'file')\n if (firstFile < 0) return\n\n if (entries.slice(firstFile).some((entry) => entry.kind === 'dir')) {\n foldersFirst = false\n return\n }\n // A folder listed before a file it follows alphabetically cannot come from merging\n // folders and files by name, so folders really are listed first.\n const proof = entries.some(\n (folder, index) =>\n folder.kind === 'dir' && entries.slice(index + 1).some((file) => file.kind === 'file' && folder.ci > file.ci && folder.raw > file.raw),\n )\n if (proof && foldersFirst === null) foldersFirst = true\n }\n\n // The listing is sorted, folders precede files, the top is on screen and a visible file\n // already follows the target alphabetically: whatever is below is a later file, so neither\n // the target nor a folder can be there. Only used when the device has shown folders-first.\n const nothingLeftBelow = (level: Level): boolean => {\n if (!exact || !level.sorted || foldersFirst !== true || !level.top || level.bottom) return false\n return levelFiles(level).some(\n (file) => /^[a-z0-9]/.test(file.ci) && file.ci > normalizedTarget && file.raw > typedTarget && file.raw > normalizedTarget,\n )\n }\n\n // In an alphabetical listing the visible files tell where the target is. This only\n // chooses which end to read first.\n const alphabeticalHint = (level: Level, screen: FileBrowserScreen): Direction | null => {\n if (!exact || !level.sorted) return null\n const visible = screen.entries.filter((entry) => entry.kind === 'file').map((entry) => primarySearchTerm(entry.text))\n const first = visible[0]\n const last = visible[visible.length - 1]\n if (first === undefined || last === undefined) return null\n if (normalizedTarget < first) return 'up'\n if (normalizedTarget > last) return 'down'\n return null\n }\n\n const pickDirection = (level: Level, screen: FileBrowserScreen): Direction => {\n if (level.top !== level.bottom) return level.top ? 'down' : 'up'\n return alphabeticalHint(level, screen) ?? 'up'\n }\n\n // Next folder to open: most relevant by name first, then the nearest to the cursor.\n const pickNextDirectory = (level: Level, screen: FileBrowserScreen): string | null => {\n const cursorIndex = screen.cursorKey ? level.order.indexOf(screen.cursorKey) : -1\n let best: { key: string; rank: number } | null = null\n for (const [key, kind] of level.kinds) {\n if (kind !== 'dir' || level.visited.has(key)) continue\n const onScreen = screen.entries.find((entry) => entry.key === key)\n const index = level.order.indexOf(key)\n const distance =\n onScreen && screen.cursorRow !== null\n ? Math.abs(onScreen.row - screen.cursorRow)\n : 100 + (cursorIndex >= 0 && index >= 0 ? Math.abs(index - cursorIndex) : 500)\n const rank = scoreDirectoryRelevance(key, hints) * 10000 - distance\n if (!best || rank > best.rank) best = { key, rank }\n }\n return best?.key ?? null\n }\n\n // \"/..\" is the first entry of its directory: jump up until it is on screen, then onto it.\n const climbToParent = async (level: Level, screen: FileBrowserScreen): Promise<boolean> => {\n const reached = await moveCursorToKey(level, PARENT_ENTRY_KEY, screen)\n if (!reached) {\n browseLog('parent directory entry unreachable')\n return false\n }\n browseLog('navigate to parent directory')\n const result = await stepFileBrowser(M8KeyMask.Edit, 500)\n return result.changed && isActive()\n }\n\n const searchLevel = async (depth: number): Promise<SearchOutcome> => {\n if (depth > MAX_DEPTH || !isActive()) {\n browseLog('search aborted', { depth, active: isActive() })\n return 'aborted'\n }\n\n const level = createLevel()\n let screen = await readScreen()\n absorb(level, screen)\n browseLog('search directory', { depth, entries: screen.entries.length, complete: level.top && level.bottom })\n\n // A long listing is worth sorting: the visible names then say which end to read first.\n if (exact && !(level.top && level.bottom)) {\n await applyAlphabeticalSort()\n // Whatever was read before the sort is in an order that no longer holds.\n level.order = []\n level.top = false\n level.bottom = false\n level.sorted = true\n screen = await readScreen()\n absorb(level, screen)\n }\n\n // 1. Every file of this level, before going deeper.\n for (let pages = 0; ; pages += 1) {\n const picked = await selectVisibleMatch(level, screen)\n if (picked === 'found') return 'found'\n if (picked === 'failed') {\n screen = await readScreen()\n absorb(level, screen)\n }\n reviewSortedLevel(level)\n if (nothingLeftBelow(level)) {\n browseLog('rest of listing is past the target', { depth })\n level.bottom = true\n }\n if ((level.top && level.bottom) || pages >= MAX_SCREENS_PER_LEVEL) break\n\n const next = await pageLevel(level, screen, pickDirection(level, screen))\n if (next) screen = next\n }\n\n // 2. Sub-directories. The listing is already known, so folders are taken in the\n // order that costs the fewest moves.\n for (;;) {\n const directory = pickNextDirectory(level, screen)\n if (!directory) break\n level.visited.add(directory)\n\n const reached = await moveCursorToKey(level, directory, screen)\n if (!reached) {\n browseLog('directory unreachable', { directory })\n screen = await readScreen()\n absorb(level, screen)\n continue\n }\n\n browseLog('enter directory', { directory, depth })\n const entered = await stepFileBrowser(M8KeyMask.Edit, 500)\n if (!isActive()) return 'aborted'\n if (!entered.changed) {\n screen = await readScreen()\n absorb(level, screen)\n continue\n }\n\n const outcome = await searchLevel(depth + 1)\n if (outcome !== 'missed') return outcome\n\n // Back in this directory with the cursor on the folder just left.\n screen = await readScreen()\n absorb(level, screen)\n }\n\n if (depth === 0) return 'missed'\n // Nothing here: leave straight away, the parent continues with the next folder.\n return (await climbToParent(level, screen)) ? 'missed' : 'aborted'\n }\n\n if (!isActive()) {\n console.warn('[M8SDK] browseFile requires a file browser view (load/save)')\n return false\n }\n if (getScreenLines().length === 0) {\n console.warn('[M8SDK] browseFile needs the screen reader, which is not available')\n return false\n }\n\n const outcome = await searchLevel(0)\n if (outcome !== 'found') browseLog('search failed', { targetText, outcome })\n return outcome === 'found'\n }\n\n return browseFileImpl\n}\n","// Pure text parsers for values rendered on the M8 screen (hex, int, float, note).\n\n// Parse hex value from text (handles formats like \"3F\", \"0x3F\", \"3f\", \"--\")\n// Returns 0 for '--' which represents 00 in the M8 UI\nexport const parseHexValue = (text: string | null): number | null => {\n if (!text) return null\n const cleaned = text.trim()\n // Handle '--' as 00\n if (cleaned === '--') return 0\n const hexCleaned = cleaned.replace(/^0x/i, '')\n const match = hexCleaned.match(/^[0-9A-Fa-f]+/)\n if (!match) return null\n const parsed = parseInt(match[0], 16)\n return Number.isNaN(parsed) ? null : parsed\n}\n\n// Parse integer value from text (handles formats like \"123\", \"-12\", \"+7\", \"--\")\n// Returns 0 for '--' which is commonly used as an empty numeric placeholder in M8 UI\nexport const parseIntValue = (text: string | null): number | null => {\n if (!text) return null\n const cleaned = text.trim()\n if (cleaned === '--') return 0\n const match = cleaned.match(/^[+-]?\\d+/)\n if (!match) return null\n const parsed = Number.parseInt(match[0], 10)\n return Number.isNaN(parsed) ? null : parsed\n}\n\n// Parse every float value token from a full line. M8 float parameters are rendered\n// as two linked groups separated by a dot, e.g. \"TUNE 440.00 G\" or \"C ON-10.69 7 ---\".\n// The cursor can rest either on the integer part (\"440\") or the decimal part (\"00\"),\n// and changing the decimal part past its bounds carries into the integer part\n// (e.g. 1.99 -> 2.00), so the whole signed token behaves like a single number.\nconst FLOAT_TOKEN_REGEX = /[+-]?\\d+\\.\\d+/g\n\nexport type ParsedFloatToken = {\n // Full token as displayed, e.g. \"-10.69\"\n raw: string\n negative: boolean\n intDigits: string\n decDigits: string\n // 10 ** decDigits.length (100 for two displayed decimals)\n decScale: number\n // Smallest displayable step expressed in hundredths (1 for two decimals, 10 for one)\n tickHundredths: number\n // Signed value measured in tick steps: (-10.69 with two decimals) => -1069\n ticks: number\n}\n\nexport const parseFloatTokens = (line: string | null): ParsedFloatToken[] => {\n if (!line) return []\n const tokens: ParsedFloatToken[] = []\n\n FLOAT_TOKEN_REGEX.lastIndex = 0\n let match: RegExpExecArray | null = FLOAT_TOKEN_REGEX.exec(line)\n while (match !== null) {\n const raw = match[0]\n const negative = raw.startsWith('-')\n const unsigned = negative || raw.startsWith('+') ? raw.slice(1) : raw\n const dotIndex = unsigned.indexOf('.')\n const intDigits = unsigned.slice(0, dotIndex)\n const decDigits = unsigned.slice(dotIndex + 1)\n const intValue = Number.parseInt(intDigits || '0', 10)\n const decValue = Number.parseInt(decDigits || '0', 10)\n const decScale = 10 ** decDigits.length\n const tickHundredths = decScale > 0 ? 100 / decScale : 1\n const valueHundredths = intValue * 100 + (decValue * 100) / decScale\n\n if (!Number.isNaN(intValue) && !Number.isNaN(decValue)) {\n tokens.push({\n raw,\n negative,\n intDigits,\n decDigits,\n decScale,\n tickHundredths,\n ticks: (negative ? -1 : 1) * Math.round(valueHundredths / tickHundredths),\n })\n }\n\n match = FLOAT_TOKEN_REGEX.exec(line)\n }\n\n return tokens\n}\n\n// When several float tokens exist on a line, prefer the one the cursor is on,\n// deduced from the highlighted text. The highlight can be the whole float —\n// possibly WITHOUT its sign (\"19.75\" for a \"-19.75\" field) — or just one digit\n// group (\"440\", \"00\", \"-10\"...).\nexport const pickFloatTokenIndex = (tokens: ParsedFloatToken[], hintText: string | null): number => {\n if (tokens.length <= 1) return 0\n const hint = hintText?.trim() ?? ''\n if (!hint) return 0\n\n const index = tokens.findIndex(\n (token) =>\n token.raw === hint ||\n token.raw.replace(/^[+-]/, '') === hint ||\n `${token.negative ? '-' : ''}${token.intDigits}` === hint ||\n token.intDigits === hint ||\n token.decDigits === hint,\n )\n return index >= 0 ? index : 0\n}\n\n// What the cursor currently highlights on a float field:\n// - 'full': the whole float, e.g. \"19.75\" (the sign may be excluded from the highlight)\n// - 'int' / 'dec': only one digit group (\"440\" / \"00\")\n// Returns null when the hint cannot be attributed confidently; callers must cope\n// with that instead of assuming.\nexport type FloatCursorFocus = 'full' | 'int' | 'dec'\n\nexport const detectFloatCursorFocus = (token: ParsedFloatToken | null, hintText: string | null): FloatCursorFocus | null => {\n if (!token) return null\n const hint = hintText?.trim() ?? ''\n if (!hint) return null\n\n const unsignedRaw = token.raw.replace(/^[+-]/, '')\n if (token.raw === hint || unsignedRaw === hint) return 'full'\n\n const signedIntGroup = `${token.negative ? '-' : ''}${token.intDigits}`\n if (signedIntGroup === hint || token.intDigits === hint) return 'int'\n if (token.decDigits === hint) return 'dec'\n\n // Single-digit highlight: attribute it only when the digit belongs to exactly one group.\n if (hint.length === 1) {\n const inIntPart = token.intDigits.includes(hint)\n const inDecPart = token.decDigits.includes(hint)\n if (inIntPart && !inDecPart) return 'int'\n if (inDecPart && !inIntPart) return 'dec'\n }\n\n return null\n}\n\n// Does a highlighted text match one token (whole float, with or without sign, or\n// a whole digit group)? Single-character hints are only trusted for full-token\n// matches because a lone digit is too ambiguous.\nconst floatHintMatchesToken = (token: ParsedFloatToken, hint: string): boolean => {\n if (hint.length === 1) {\n return token.raw === hint || token.raw.replace(/^[+-]/, '') === hint\n }\n return (\n token.raw === hint ||\n token.raw.replace(/^[+-]/, '') === hint ||\n `${token.negative ? '-' : ''}${token.intDigits}` === hint ||\n token.intDigits === hint ||\n token.decDigits === hint\n )\n}\n\n// Detect whether the highlighted text now belongs to a DIFFERENT float token on\n// the same line (the cursor drifted to another field mid-run). The hint must not\n// match the edited token at all while matching another one, so ambiguous cases\n// never trigger a false positive.\nexport const isCursorOnForeignFloat = (tokens: ParsedFloatToken[], ownIndex: number, hintText: string | null): boolean => {\n const hint = hintText?.trim() ?? ''\n if (!hint || !tokens[ownIndex]) return false\n if (floatHintMatchesToken(tokens[ownIndex], hint)) return false\n return tokens.some((token, index) => index !== ownIndex && floatHintMatchesToken(token, hint))\n}\n\n// Pretty-print a parsed token value for logs, e.g. ticks=-1069 with 2 decimals => \"-10.69\"\nexport const formatFloatFromTicks = (ticks: number, tickHundredths: number): string => {\n const hundredths = Math.round(ticks * tickHundredths)\n const sign = hundredths < 0 ? '-' : ''\n const absolute = Math.abs(hundredths)\n const intPart = Math.floor(absolute / 100)\n const fracPart = absolute - intPart * 100\n return `${sign}${intPart}.${fracPart.toString().padStart(2, '0')}`\n}\n\nconst NOTE_ORDER = ['C-', 'C#', 'D-', 'D#', 'E-', 'F-', 'F#', 'G-', 'G#', 'A-', 'A#', 'B-'] as const\n\nexport type ParsedNote = {\n label: string\n noteName: string\n octave: number\n semitoneIndex: number\n}\n\nexport const parseNoteValue = (text: string | null): ParsedNote | null => {\n if (!text) return null\n const match = text\n .trim()\n .toUpperCase()\n .match(/([A-G][#-][0-9A-F])/)\n if (!match) return null\n\n const label = match[1]\n const noteName = label.slice(0, 2)\n const octave = Number.parseInt(label.slice(2, 3), 16)\n const noteOffset = NOTE_ORDER.indexOf(noteName as (typeof NOTE_ORDER)[number])\n\n if (noteOffset < 0 || Number.isNaN(octave)) {\n return null\n }\n\n return {\n label,\n noteName,\n octave,\n semitoneIndex: octave * 12 + noteOffset,\n }\n}\n","import { M8KeyMask } from '../keys'\nimport { type HostIO, wait } from './hostIO'\nimport {\n detectFloatCursorFocus,\n type FloatCursorFocus,\n formatFloatFromTicks,\n isCursorOnForeignFloat,\n type ParsedFloatToken,\n parseFloatTokens,\n pickFloatTokenIndex,\n} from './valueParsers'\n\n// Edit a float field (e.g. \"TUNE 440.00 G\") using edit+arrow key combos.\nexport const createFloatEditor = (io: HostIO) => {\n const { store, debugLog, hasBus, sendKeys, pressAndRelease, waitForTextUnderCursorChange, waitForCurrentLineChange } = io\n\n // Implementation of setValueFloat using edit+navigation keys.\n //\n // Float fields (e.g. \"TUNE 440.00 G\" or \"GAIN 05.25 -19.75 -15.00\") are rendered\n // as two linked digit groups separated by a dot: the integer part and the decimal\n // part. Stepping the decimal part past its bounds carries into the integer part\n // (1.99 -> 2.00), so the whole signed token behaves like a single number that the\n // closed loop below steers through measured edit+arrow key steps.\n //\n // Cursor focus modes (deduced from textUnderCursor):\n // - 'full': the cursor spans the whole float (\"19.75\", sign often excluded).\n // There, edit+up/down moves the INTEGER part and edit+left/right the DECIMAL\n // part, so both granularities are directly available and no cursor relocation\n // is needed (attempting it could even drift to a neighboring field).\n // - 'int' / 'dec': the cursor highlights a single digit group. The cursor is then\n // relocated with bare left/right onto whichever group is most efficient:\n // integer part for whole-unit travel, decimal part for sub-unit finishing.\n //\n // Step sizes and arrow polarities are measured from real device feedback\n // (\"learned\") per focus mode and per arrow channel (up/down vs left/right),\n // then reused for fast press bursts.\n // Negative targets are supported (fields display like \"-10.69\").\n // Note: opt+edit resets the field to its default value (0.00 / 440.00 / ...).\n const setValueFloatImpl = async (targetFloat: number): Promise<boolean> => {\n if (!hasBus()) return false\n if (!Number.isFinite(targetFloat)) return false\n\n let tokenIndex = 0\n let tickHundredths = 1\n\n let lastTokens: ParsedFloatToken[] = []\n\n const readFloatState = (): ParsedFloatToken | null => {\n const tokens = parseFloatTokens(store.get('currentLine'))\n lastTokens = tokens\n if (!tokens.length) return null\n tokenIndex = Math.min(tokenIndex, tokens.length - 1)\n const token = tokens[tokenIndex]\n if (token.tickHundredths > 0) tickHundredths = token.tickHundredths\n return token\n }\n\n const initialTokens = parseFloatTokens(store.get('currentLine'))\n if (!initialTokens.length) {\n console.warn('[M8SDK] Could not find a float value like \"440.00\" or \"-10.69\" on the current line:', store.get('currentLine'))\n return false\n }\n\n tokenIndex = pickFloatTokenIndex(initialTokens, store.get('textUnderCursor'))\n if (initialTokens.length > 1) {\n debugLog('[M8SDK] Multiple float values on line, editing:', initialTokens[tokenIndex]?.raw)\n }\n\n const initialToken = initialTokens[tokenIndex]\n tickHundredths = initialToken.tickHundredths\n // Caller-provided floats are quantized to the precision actually displayed.\n const targetTicks = Math.round(Math.round(targetFloat * 100) / tickHundredths)\n\n if (initialToken.ticks === targetTicks) {\n debugLog('[M8SDK] Already at target float value:', formatFloatFromTicks(targetTicks, tickHundredths))\n return true\n }\n\n debugLog(\n '[M8SDK] Setting float value from',\n formatFloatFromTicks(initialToken.ticks, tickHundredths),\n 'to',\n formatFloatFromTicks(targetTicks, tickHundredths),\n )\n\n // Enter edit mode. Numeric rows do not necessarily change their text when\n // entering edit mode, so a short settle wait is enough here.\n await pressAndRelease(M8KeyMask.Edit)\n await wait(80)\n\n // Measured step size and arrow polarity per arrow channel (up/down vs\n // left/right), tracked PER focus mode: 'full' has up/down -> integer part\n // and left/right -> decimal part, while 'int'/'dec' group focus changes\n // what each arrow channel steps. Measured magnitudes are in ticks.\n type FloatChannel = 'upDown' | 'leftRight'\n type FloatChannelStats = {\n mag: number | null\n bias: number\n }\n type FloatFocusStats = Record<FloatChannel, FloatChannelStats>\n const makeFocusStats = (): FloatFocusStats => ({\n upDown: { mag: null, bias: 1 },\n leftRight: { mag: null, bias: 1 },\n })\n const statsByFocus: Record<FloatCursorFocus, FloatFocusStats> = {\n full: makeFocusStats(),\n int: makeFocusStats(),\n dec: makeFocusStats(),\n }\n\n // Ticks contained in one whole unit (100 for two decimals, 10 for one).\n const UNIT_IN_TICKS = initialToken.decScale\n\n const COARSE_MIN_TICKS = 32\n const MAX_RUNTIME_MS = 15000\n const MAX_PRESSES_PER_BURST = 96\n const startedAt = Date.now()\n\n // Trust textUnderCursor to tell which part of the field is highlighted;\n // when unclear, assume integer-group focus (self-corrects via measurement).\n let focus: FloatCursorFocus = detectFloatCursorFocus(initialToken, store.get('textUnderCursor')) ?? 'int'\n debugLog('[M8SDK] Float cursor focus:', focus)\n // Set when cursor relocation proves unreliable (layout quirks); the value\n // closed-loop below keeps working without side management.\n let sideControlDisabled = false\n // Set when the cursor drifted onto another field: editing must stop at once.\n let driftedToForeignField = false\n\n let previousTicks = initialToken.ticks\n let valueTwoStepsAgo: number | null = null\n let bestTicks = initialToken.ticks\n let bestDistance = Math.abs(initialToken.ticks - targetTicks)\n let fruitlessStreak = 0\n let reachedTarget = false\n let gaveUp = false\n\n // Build an edit+arrow mask for an arrow channel.\n const getMaskForChannel = (channel: FloatChannel, effectiveDirection: number): number => {\n const positiveKey = channel === 'upDown' ? M8KeyMask.Up : M8KeyMask.Right\n const negativeKey = channel === 'upDown' ? M8KeyMask.Down : M8KeyMask.Left\n return M8KeyMask.Edit | (effectiveDirection >= 0 ? positiveKey : negativeKey)\n }\n\n const applyBurst = async (channel: FloatChannel, direction: number, presses: number): Promise<void> => {\n const stats = statsByFocus[focus][channel]\n const mask = getMaskForChannel(channel, direction * stats.bias)\n const beforeLine = store.get('currentLine')\n for (let i = 0; i < presses; i++) {\n sendKeys(mask)\n await wait(22)\n }\n await waitForCurrentLineChange(beforeLine, 350)\n }\n\n // Hop the cursor onto the wanted digit group using bare left/right (no edit):\n // the decimal part sits right of the dot, the integer part left of it. The\n // landing is verified through textUnderCursor; if the first arrow did not\n // reach the wanted group, the opposite arrow is tried before giving up.\n // Only ever called from 'int'/'dec' focus — never from 'full', where a bare\n // arrow could leave the field entirely.\n const ensureFloatCursorSide = async (side: 'int' | 'dec'): Promise<boolean> => {\n const currentFocusOf = (): FloatCursorFocus | null => detectFloatCursorFocus(readFloatState(), store.get('textUnderCursor'))\n\n const detected = currentFocusOf()\n if (detected === side) {\n focus = side\n return true\n }\n\n const beforeHint = store.get('textUnderCursor')\n const towardDot = side === 'dec' ? M8KeyMask.Right : M8KeyMask.Left\n const awayFromDot = side === 'dec' ? M8KeyMask.Left : M8KeyMask.Right\n\n await pressAndRelease(towardDot, 35)\n await waitForTextUnderCursorChange(beforeHint, 250)\n if (currentFocusOf() === side) {\n focus = side\n return true\n }\n\n // Unexpected layout: try the reverse arrow instead of getting stuck.\n await pressAndRelease(awayFromDot, 35)\n await waitForTextUnderCursorChange(beforeHint, 250)\n const landed = currentFocusOf() === side\n if (landed) focus = side\n return landed\n }\n\n while (!reachedTarget && !gaveUp && Date.now() - startedAt < MAX_RUNTIME_MS) {\n const state = readFloatState()\n if (!state) {\n console.warn('[M8SDK] Float value no longer parseable while editing:', store.get('currentLine'))\n gaveUp = true\n break\n }\n\n // Never keep editing when the cursor moved onto another field of the line.\n if (isCursorOnForeignFloat(lastTokens, tokenIndex, store.get('textUnderCursor'))) {\n console.warn('[M8SDK] Float cursor drifted to another field, aborting to avoid editing the wrong value')\n driftedToForeignField = true\n gaveUp = true\n break\n }\n\n const diffTicks = targetTicks - state.ticks\n if (diffTicks === 0) {\n reachedTarget = true\n break\n }\n\n const distance = Math.abs(diffTicks)\n if (distance < bestDistance) {\n bestDistance = distance\n bestTicks = state.ticks\n }\n\n // Detect 2-value bounce (A -> B -> A), especially when crossing the target.\n const isOscillating = valueTwoStepsAgo !== null && state.ticks === valueTwoStepsAgo\n const crossesTarget = (previousTicks - targetTicks) * (state.ticks - targetTicks) < 0\n if (isOscillating && crossesTarget) {\n debugLog('[M8SDK] Float adjustment bouncing around target, stopping')\n break\n }\n valueTwoStepsAgo = previousTicks\n previousTicks = state.ticks\n\n // Route the cursor between digit groups for efficiency, but only when a\n // single group is focused. With 'full' focus, up/down already drives the\n // integer part and left/right the decimal part, so there is nothing to\n // relocate — and a bare arrow there could leave the field entirely.\n if (focus !== 'full' && !sideControlDisabled) {\n const desiredSide: 'int' | 'dec' = distance >= UNIT_IN_TICKS ? 'int' : 'dec'\n if (desiredSide !== focus) {\n const hopped = await ensureFloatCursorSide(desiredSide)\n if (!hopped) {\n // The cursor may have landed on another field during the\n // failed relocation: verify before pressing edit combos.\n if (isCursorOnForeignFloat(lastTokens, tokenIndex, store.get('textUnderCursor'))) {\n console.warn('[M8SDK] Float cursor drifted to another field, aborting to avoid editing the wrong value')\n driftedToForeignField = true\n gaveUp = true\n break\n }\n debugLog('[M8SDK] Could not relocate float cursor, continuing on current digit group')\n sideControlDisabled = true\n }\n }\n }\n\n // Choose the arrow channel and burst size for this round, using only\n // measurements gathered in the current focus mode. up/down probing is\n // gated by focus: on the integer group a jump spans several whole units\n // and would badly overshoot short distances; spanning the whole float it\n // moves whole units too; on the decimal group jumps are sub-unit only,\n // so probing pays off much sooner.\n const focusStats = statsByFocus[focus]\n const upDownProbeGateTicks =\n focus === 'int'\n ? Math.max(COARSE_MIN_TICKS, 3 * UNIT_IN_TICKS)\n : focus === 'full'\n ? UNIT_IN_TICKS\n : Math.max(8, Math.floor(COARSE_MIN_TICKS / 4))\n\n let channel: FloatChannel\n let presses = 1\n if (focusStats.upDown.mag !== null && focusStats.upDown.mag > 0 && distance >= focusStats.upDown.mag) {\n channel = 'upDown'\n presses = Math.min(Math.max(1, Math.floor(distance / focusStats.upDown.mag)), MAX_PRESSES_PER_BURST)\n } else if (focusStats.upDown.mag === null && distance >= upDownProbeGateTicks) {\n // One probe press measures the actual up/down step in this focus mode.\n channel = 'upDown'\n presses = 1\n } else if (focusStats.leftRight.mag !== null && focusStats.leftRight.mag > 0) {\n channel = 'leftRight'\n presses = Math.min(Math.max(1, Math.floor(distance / focusStats.leftRight.mag)), MAX_PRESSES_PER_BURST)\n } else {\n // left/right magnitude unknown (or not yet usable): probe it once.\n channel = 'leftRight'\n presses = 1\n }\n\n const direction = diffTicks > 0 ? 1 : -1\n const beforeTicks = state.ticks\n const focusAtPress = focus\n const channelAtPress = channel\n await applyBurst(channel, direction, presses)\n\n const nextState = readFloatState()\n if (!nextState) {\n gaveUp = true\n break\n }\n const deltaTicks = nextState.ticks - beforeTicks\n\n if (deltaTicks === 0) {\n fruitlessStreak += 1\n if (fruitlessStreak >= 4) {\n console.warn(\n `[M8SDK] Float value stopped reacting at ${formatFloatFromTicks(nextState.ticks, tickHundredths)}` +\n ` (target ${formatFloatFromTicks(targetTicks, tickHundredths)} may be out of range)`,\n )\n gaveUp = true\n }\n continue\n }\n fruitlessStreak = 0\n\n // Learn/refresh the measured magnitude and polarity for the channel that\n // was used, in the focus mode it was used from.\n const pressedStats = statsByFocus[focusAtPress][channelAtPress]\n pressedStats.mag = Math.max(1, Math.round(Math.abs(deltaTicks) / presses))\n\n // If the pressed arrows moved away from the target despite our assumed\n // mapping, flip that channel's arrow expectation for next rounds.\n if (Math.sign(deltaTicks) !== Math.sign(diffTicks)) {\n debugLog(`[M8SDK] Float ${channelAtPress} arrows move oppositely in '${focusAtPress}' focus, inverting mapping`)\n pressedStats.bias *= -1\n valueTwoStepsAgo = null\n }\n }\n\n // If the exact target was never reached, walk back to the closest observed\n // value so the parameter is never left worse off than necessary. Skipped\n // after a foreign-field drift — recovery presses would edit the wrong value.\n if (!reachedTarget && !driftedToForeignField) {\n const snapStartedAt = Date.now()\n while (Date.now() - snapStartedAt < 1800) {\n const snapState = readFloatState()\n if (!snapState || snapState.ticks === bestTicks) break\n\n const gap = bestTicks - snapState.ticks\n const snapDistance = Math.abs(gap)\n\n // Same digit-group routing as the main loop for efficient recovery.\n if (focus !== 'full' && !sideControlDisabled && snapDistance >= UNIT_IN_TICKS !== (focus === 'int')) {\n const hoppedBack = await ensureFloatCursorSide(snapDistance >= UNIT_IN_TICKS ? 'int' : 'dec')\n if (!hoppedBack) sideControlDisabled = true\n }\n\n const snapStats = statsByFocus[focus]\n let snapChannel: FloatChannel\n let snapPresses = 1\n if (snapStats.upDown.mag !== null && snapStats.upDown.mag > 0 && snapDistance >= snapStats.upDown.mag) {\n snapChannel = 'upDown'\n snapPresses = Math.min(Math.max(1, Math.floor(snapDistance / snapStats.upDown.mag)), 16)\n } else if (snapStats.leftRight.mag !== null && snapStats.leftRight.mag > 0) {\n snapChannel = 'leftRight'\n snapPresses = Math.min(Math.max(1, Math.floor(snapDistance / snapStats.leftRight.mag)), 16)\n } else {\n snapChannel = 'leftRight'\n }\n\n const beforeSnapTicks = snapState.ticks\n await applyBurst(snapChannel, gap > 0 ? 1 : -1, snapPresses)\n const afterSnap = readFloatState()\n if (!afterSnap || afterSnap.ticks === beforeSnapTicks) break\n }\n }\n\n // Exit edit mode and verify the final value against the parsed line.\n // After a foreign-field drift, pressing edit could open edit mode on the\n // wrong field, so skip it and just report failure.\n if (!driftedToForeignField) {\n await pressAndRelease(M8KeyMask.Edit)\n }\n\n const finalToken = readFloatState()\n const success = !!finalToken && finalToken.ticks === targetTicks\n debugLog(\n `[M8SDK] Float setting ${success ? 'succeeded' : 'failed'}: \"${finalToken?.raw ?? 'N/A'}\"` +\n ` (${finalToken ? formatFloatFromTicks(finalToken.ticks, tickHundredths) : 'N/A'})`,\n )\n return success\n }\n\n return setValueFloatImpl\n}\n","{\n \"song\": [\n {\n \"to\": \"project\",\n \"dir\": \"up\",\n \"keys\": [80, 0]\n },\n {\n \"to\": \"mixer\",\n \"dir\": \"down\",\n \"keys\": [48, 0],\n \"weight\": 10\n },\n {\n \"to\": \"chain\",\n \"dir\": \"right\",\n \"keys\": [20, 0]\n }\n ],\n \"project\": [\n {\n \"to\": \"song\",\n \"dir\": \"down\",\n \"keys\": [48, 0],\n \"back\": true\n },\n {\n \"to\": \"chain\",\n \"dir\": \"down\",\n \"keys\": [48, 0],\n \"back\": true\n },\n {\n \"to\": \"midisettings\",\n \"via\": \"button\",\n \"cursor\": [14, 11],\n \"label\": \"SETTINGS\",\n \"keys\": [1, 0],\n \"weight\": 2\n },\n {\n \"to\": \"midimapping\",\n \"via\": \"button\",\n \"cursor\": [23, 11],\n \"label\": \"MAPPINGS\",\n \"keys\": [1, 0],\n \"weight\": 2\n },\n {\n \"to\": \"renderaudio\",\n \"via\": \"button\",\n \"cursor\": [14, 15],\n \"label\": \"RENDER\",\n \"keys\": [1, 0],\n \"weight\": 2\n },\n {\n \"to\": \"instrumentpool\",\n \"via\": \"button\",\n \"cursor\": [14, 17],\n \"label\": \"VIEW INST. POOL\",\n \"keys\": [1, 0],\n \"weight\": 2\n },\n {\n \"to\": \"timestats\",\n \"via\": \"button\",\n \"cursor\": [14, 19],\n \"label\": \"VIEW TIME STATS\",\n \"keys\": [1, 0],\n \"weight\": 2\n },\n {\n \"to\": \"systemsettings\",\n \"via\": \"button\",\n \"cursor\": [14, 20],\n \"label\": \"SETTINGS\",\n \"keys\": [1, 0],\n \"weight\": 2\n }\n ],\n \"mixer\": [\n {\n \"to\": \"effectsettings\",\n \"dir\": \"down\",\n \"keys\": [48, 0]\n },\n {\n \"to\": \"song\",\n \"dir\": \"up\",\n \"keys\": [80, 0],\n \"weight\": 10,\n \"back\": true\n },\n {\n \"to\": \"chain\",\n \"dir\": \"up\",\n \"keys\": [80, 0],\n \"weight\": 10,\n \"back\": true\n },\n {\n \"to\": \"phrase\",\n \"dir\": \"up\",\n \"keys\": [80, 0],\n \"weight\": 10,\n \"back\": true\n },\n {\n \"to\": \"inst\",\n \"dir\": \"up\",\n \"keys\": [80, 0],\n \"weight\": 10,\n \"back\": true\n },\n {\n \"to\": \"table\",\n \"dir\": \"up\",\n \"keys\": [80, 0],\n \"weight\": 10,\n \"back\": true\n },\n {\n \"to\": \"mixeq\",\n \"via\": \"button\",\n \"cursor\": [28, 17],\n \"keys\": [64, 0, 20, 0],\n \"weight\": 2\n },\n {\n \"to\": \"mixscope\",\n \"via\": \"button\",\n \"cursor\": [28, 17],\n \"keys\": [20, 0],\n \"weight\": 2\n },\n {\n \"to\": \"limiterscope\",\n \"via\": \"button\",\n \"cursor\": [28, 18],\n \"keys\": [20, 0],\n \"weight\": 2\n }\n ],\n \"effectsettings\": [\n {\n \"to\": \"mixer\",\n \"dir\": \"up\",\n \"keys\": [80, 0]\n },\n {\n \"to\": \"modfxeq\",\n \"via\": \"button\",\n \"cursor\": [22, 6],\n \"label\": \"EQ\",\n \"keys\": [1, 0],\n \"weight\": 2\n },\n {\n \"to\": \"delayeq\",\n \"via\": \"button\",\n \"cursor\": [22, 11],\n \"label\": \"EQ\",\n \"keys\": [1, 0],\n \"weight\": 2\n },\n {\n \"to\": \"reverbeq\",\n \"via\": \"button\",\n \"cursor\": [22, 17],\n \"label\": \"EQ\",\n \"keys\": [1, 0],\n \"weight\": 2\n }\n ],\n \"inst\": [\n {\n \"to\": \"table\",\n \"dir\": \"right\",\n \"keys\": [20, 0]\n },\n {\n \"to\": \"phrase\",\n \"dir\": \"left\",\n \"keys\": [144, 0]\n },\n {\n \"to\": \"instmods\",\n \"dir\": \"up\",\n \"keys\": [80, 0]\n },\n {\n \"to\": \"mixer\",\n \"dir\": \"down\",\n \"keys\": [48, 0],\n \"weight\": 10\n },\n {\n \"to\": \"loadinstrument\",\n \"via\": \"button\",\n \"cursor\": [23, 5],\n \"label\": \"LOAD\",\n \"keys\": [1, 0],\n \"weight\": 2\n },\n {\n \"to\": \"instrumentpool\",\n \"dir\": \"left\",\n \"keys\": [144, 0],\n \"back\": true\n }\n ],\n \"table\": [\n {\n \"to\": \"inst\",\n \"dir\": \"left\",\n \"keys\": [144, 0]\n },\n {\n \"to\": \"instmods\",\n \"dir\": \"up\",\n \"keys\": [80, 0]\n },\n {\n \"to\": \"mixer\",\n \"dir\": \"down\",\n \"keys\": [48, 0],\n \"weight\": 10\n }\n ],\n \"phrase\": [\n {\n \"to\": \"inst\",\n \"dir\": \"right\",\n \"keys\": [20, 0]\n },\n {\n \"to\": \"chain\",\n \"dir\": \"left\",\n \"keys\": [144, 0]\n },\n {\n \"to\": \"groove\",\n \"dir\": \"up\",\n \"keys\": [80, 0]\n },\n {\n \"to\": \"mixer\",\n \"dir\": \"down\",\n \"keys\": [48, 0],\n \"weight\": 10\n }\n ],\n \"chain\": [\n {\n \"to\": \"phrase\",\n \"dir\": \"right\",\n \"keys\": [20, 0]\n },\n {\n \"to\": \"song\",\n \"dir\": \"left\",\n \"keys\": [144, 0]\n },\n {\n \"to\": \"project\",\n \"dir\": \"up\",\n \"keys\": [80, 0]\n },\n {\n \"to\": \"mixer\",\n \"dir\": \"down\",\n \"keys\": [48, 0],\n \"weight\": 10\n }\n ],\n \"groove\": [\n {\n \"to\": \"scale\",\n \"dir\": \"up\",\n \"keys\": [80, 0]\n },\n {\n \"to\": \"phrase\",\n \"dir\": \"down\",\n \"keys\": [48, 0]\n }\n ],\n \"scale\": [\n {\n \"to\": \"groove\",\n \"dir\": \"down\",\n \"keys\": [48, 0]\n }\n ],\n \"instmods\": [\n {\n \"to\": \"instrumentpool\",\n \"dir\": \"up\",\n \"keys\": [80, 0]\n },\n {\n \"to\": \"inst\",\n \"dir\": \"down\",\n \"keys\": [48, 0],\n \"back\": true\n },\n {\n \"to\": \"table\",\n \"dir\": \"down\",\n \"keys\": [48, 0],\n \"back\": true\n }\n ],\n \"instrumentpool\": [\n {\n \"to\": \"instmods\",\n \"dir\": \"down\",\n \"keys\": [48, 0]\n },\n {\n \"to\": \"inst\",\n \"dir\": \"right\",\n \"keys\": [20, 0]\n },\n {\n \"to\": \"phrase\",\n \"dir\": \"left\",\n \"keys\": [144, 0],\n \"weight\": 4\n }\n ],\n \"systemsettings\": [\n {\n \"to\": \"themesettings\",\n \"via\": \"button\",\n \"cursor\": [16, 7],\n \"label\": \"EDIT THEME\",\n \"keys\": [1, 0],\n \"weight\": 2\n },\n {\n \"to\": \"project\",\n \"via\": \"exit\",\n \"keys\": [2, 0]\n },\n {\n \"to\": \"project\",\n \"dir\": \"up\",\n \"keys\": [80, 0],\n \"weight\": 2\n }\n ],\n \"midisettings\": [\n {\n \"to\": \"project\",\n \"via\": \"exit\",\n \"keys\": [2, 0]\n },\n {\n \"to\": \"project\",\n \"dir\": \"up\",\n \"keys\": [80, 0],\n \"weight\": 3\n }\n ],\n \"midimapping\": [\n {\n \"to\": \"project\",\n \"via\": \"exit\",\n \"keys\": [2, 0]\n },\n {\n \"to\": \"project\",\n \"dir\": \"up\",\n \"keys\": [80, 0],\n \"weight\": 3\n }\n ],\n \"renderaudio\": [\n {\n \"to\": \"project\",\n \"via\": \"exit\",\n \"keys\": [2, 0]\n },\n {\n \"to\": \"project\",\n \"dir\": \"up\",\n \"keys\": [80, 0],\n \"weight\": 3\n }\n ],\n \"timestats\": [\n {\n \"to\": \"project\",\n \"via\": \"exit\",\n \"keys\": [2, 0]\n },\n {\n \"to\": \"project\",\n \"dir\": \"up\",\n \"keys\": [80, 0],\n \"weight\": 3\n }\n ],\n \"loadproject\": [\n {\n \"to\": \"project\",\n \"via\": \"exit\",\n \"keys\": [2, 0]\n }\n ],\n \"themesettings\": [\n {\n \"to\": \"systemsettings\",\n \"via\": \"exit\",\n \"keys\": [2, 0]\n }\n ],\n \"mixeq\": [\n {\n \"to\": \"mixer\",\n \"via\": \"exit\",\n \"keys\": [2, 0]\n },\n {\n \"to\": \"mixer\",\n \"dir\": \"left\",\n \"keys\": [144, 0],\n \"weight\": 3\n }\n ],\n \"mixscope\": [\n {\n \"to\": \"mixer\",\n \"via\": \"exit\",\n \"keys\": [2, 0]\n },\n {\n \"to\": \"mixer\",\n \"dir\": \"left\",\n \"keys\": [144, 0],\n \"weight\": 3\n }\n ],\n \"limiterscope\": [\n {\n \"to\": \"mixer\",\n \"via\": \"exit\",\n \"keys\": [2, 0]\n },\n {\n \"to\": \"mixer\",\n \"dir\": \"left\",\n \"keys\": [144, 0],\n \"weight\": 3\n }\n ],\n \"modfxeq\": [\n {\n \"to\": \"effectsettings\",\n \"via\": \"exit\",\n \"keys\": [2, 0]\n }\n ],\n \"delayeq\": [\n {\n \"to\": \"effectsettings\",\n \"via\": \"exit\",\n \"keys\": [2, 0]\n }\n ],\n \"reverbeq\": [\n {\n \"to\": \"effectsettings\",\n \"via\": \"exit\",\n \"keys\": [2, 0]\n }\n ],\n \"loadinstrument\": [\n {\n \"to\": \"inst\",\n \"via\": \"exit\",\n \"keys\": [2, 0]\n }\n ]\n}\n","{\n \"views\": [\n \"chain\",\n \"effectsettings\",\n \"groove\",\n \"instmods\",\n \"inst\",\n \"instrumentpool\",\n \"mixer\",\n \"phrase\",\n \"project\",\n \"scale\",\n \"song\",\n \"table\",\n \"mixeq\",\n \"limiterscope\",\n \"mixscope\",\n \"modfxeq\",\n \"delayeq\",\n \"reverbeq\",\n \"loadinstrument\",\n \"selectsavedirectory\",\n \"createdirectory\",\n \"systemsettings\",\n \"midisettings\",\n \"midimapping\",\n \"renderaudio\",\n \"timestats\",\n \"themesettings\",\n \"loadproject\"\n ]\n}\n","// The M8 view graph used to navigate between views, and the list of view names. Both are\n// bundled copies of yam8d's public/m8Graph.generated.json and public/viewlist.json (a yam8d\n// test keeps them identical).\n//\n// Edges come in three kinds:\n// - shift: Shift+direction (`dir`), the views of the main grid.\n// - button: put the cursor on a cell of the screen (`cursor`, moved to the nearest place\n// where `label` is written when it is on screen), then press `keys`. Sub-views such as\n// MIDI Settings or the Mixer EQ sit behind buttons.\n// - exit: Option, which leaves a sub-view. Where it lands is not always known, so the\n// navigation checks the view after each step.\n//\n// Some keys go back to the view the M8 came from (`back`): the Mixer's Shift+Up, the\n// Instrument's Shift+Left after the Instrument Pool. Among the edges of one key, a `back` edge\n// is taken only when its target is the previous view; otherwise the other edges stand.\n\nimport type { Direction, Edge, Graph } from './astar'\nimport graphJson from './data/m8Graph.generated.json'\nimport viewListJson from './data/viewlist.json'\n\ntype EdgeJson = {\n to: string\n dir?: Direction\n via?: 'button' | 'exit'\n cursor?: [number, number]\n label?: string\n keys?: number[]\n weight?: number\n back?: boolean\n}\n\nexport type NavEdgeKind = 'shift' | 'button' | 'exit'\n\nexport interface NavEdge extends Edge {\n from: string\n kind: NavEdgeKind\n /** Key frames to play (after the cursor move, for buttons). Empty: Shift+dir. */\n keys: number[]\n /** Button cell, in text grid coordinates. */\n cursor?: { x: number; y: number }\n /** Button text, used to find the button where the layout differs from `cursor`. */\n label?: string\n /** Where this key goes when the M8 came from `to` (it goes back). */\n back?: boolean\n}\n\nexport interface M8ViewGraph {\n graph: Graph<NavEdge>\n /** Explicit key frame sequences per edge: from -> to -> frames. */\n edgeKeys: Map<string, Map<string, number[]>>\n}\n\nconst buildGraph = (json: Record<string, EdgeJson[]>): M8ViewGraph => {\n const graph: Graph<NavEdge> = new Map()\n const edgeKeys = new Map<string, Map<string, number[]>>()\n for (const [rawKey, list] of Object.entries(json)) {\n const from = rawKey.toLowerCase()\n const edges: NavEdge[] = []\n const perFrom = new Map<string, number[]>()\n for (const e of list) {\n const kind: NavEdgeKind = e.via ?? 'shift'\n if (kind === 'shift' && !e.dir) continue\n if (kind === 'button' && !e.cursor) continue\n const keys = e.keys?.slice() ?? []\n edges.push({\n from,\n to: e.to.toLowerCase(),\n dir: e.dir,\n weight: e.weight,\n kind,\n keys,\n cursor: e.cursor ? { x: e.cursor[0], y: e.cursor[1] } : undefined,\n label: e.label,\n back: e.back,\n })\n if (kind === 'shift' && keys.length) perFrom.set(e.to, keys)\n }\n graph.set(from, edges)\n if (perFrom.size) edgeKeys.set(from, perFrom)\n }\n // Views that are only targets still need a node\n for (const edges of [...graph.values()]) {\n for (const e of edges) if (!graph.has(e.to)) graph.set(e.to, [])\n }\n return { graph, edgeKeys }\n}\n\nlet viewGraph: M8ViewGraph | null = null\nlet viewNames: Set<string> | null = null\n\nexport const getViewGraph = (): M8ViewGraph => {\n viewGraph ??= buildGraph(graphJson as unknown as Record<string, EdgeJson[]>)\n return viewGraph\n}\n\n/** Names of the M8 views, lowercased (as viewName reports them). */\nexport const getViewNames = (): Set<string> => {\n if (!viewNames) {\n const list = Array.isArray((viewListJson as { views?: unknown }).views) ? (viewListJson as { views: unknown[] }).views : []\n viewNames = new Set(list.map((s) => String(s).trim().toLowerCase()).filter((s) => !!s))\n }\n return viewNames\n}\n","// Turns the M8 draw stream into screen state: the character grid (whole screen text and\n// colours), the view title, and the cursor assembled from its corner rectangles.\n\nimport type { CursorRect, RGB } from '../types'\nimport type { CharacterCommand, RectCommand, SystemInfos } from './protocol'\nimport type { M8Store } from './store'\nimport { getViewNames } from './viewGraph'\n\n// Heuristics aligned with existing extraction logic\nconst STABILIZE_MS = 50\n\ninterface CornerCandidate {\n x: number\n y: number\n // Expected positions for verification pieces\n expectedVert: { x: number; y: number } | null // 1x2 vertical edge\n expectedTip: { x: number; y: number } | null // 1x1 tip\n foundVert: boolean\n foundTip: boolean\n}\n\ninterface CursorAssembly {\n tl: CornerCandidate // Top-left (colored, type 4)\n tr: CornerCandidate | null // Top-right\n bl: CornerCandidate | null // Bottom-left\n br: CornerCandidate | null // Bottom-right\n startTime: number\n state: 'waiting' | 'building' | 'complete'\n}\n\nconst defaultCursor: CursorRect = { x: 0, y: 0, w: 1, h: 1 }\n\ninterface SelectionRectSample {\n x: number\n y: number\n w: number\n h: number\n ts: number\n hasColor: boolean\n}\n\nclass AssemblyCursorExtractor {\n // Active cursor assemblies being built\n private assemblies: CursorAssembly[] = []\n\n // Recent 1px-border segments used to detect selection rectangles\n private selectionRectSamples: SelectionRectSample[] = []\n\n // Last cursor from the corner-based assembly. NEVER written by selection detection.\n private lastCursor: CursorRect = defaultCursor\n\n // Last selection rectangle (separate from lastCursor to avoid cross-contamination)\n private lastSelectionCursor: CursorRect | null = null\n\n // Last detected cursor color (from type 4 rectangles)\n private lastCursorColor: RGB | null = null\n\n // Selection mode tracking\n private lastSelectionMode = false\n private lastSelectionSeenAt = 0\n\n // Timeout for stale assemblies (ms)\n private readonly ASSEMBLY_TIMEOUT = 100\n // Max age of segment samples considered part of the same selection draw call\n private readonly SELECTION_SAMPLE_WINDOW = 50\n // How long to stay in selection mode after the last fresh detection\n private readonly SELECTION_HOLD_DURATION = 150\n\n /**\n * Process a rectangle command.\n * Strategy: run normal corner assembly FIRST (unchanged), only attempt\n * selection detection as a fallback. Selection detection NEVER writes\n * lastCursor, so false positives cannot corrupt normal cursor tracking.\n */\n processRect(rect: RectCommand): { cursor: CursorRect; cursorColor: RGB | null; selectionMode: boolean } {\n if (!rect) {\n const cursorOut = this.lastSelectionMode && this.lastSelectionCursor ? this.lastSelectionCursor : this.lastCursor\n return { cursor: cursorOut, cursorColor: this.lastCursorColor, selectionMode: this.lastSelectionMode }\n }\n\n const now = Date.now()\n\n // ----- 1. Normal corner-based cursor assembly (original, unchanged logic) -----\n this.assemblies = this.assemblies.filter((a) => now - a.startTime < this.ASSEMBLY_TIMEOUT)\n\n let completedCursor: CursorRect | null = null\n for (const assembly of this.assemblies) {\n if (assembly.state === 'complete') continue\n this.processRectAgainstAssembly(rect, assembly)\n if (this.isAssemblyComplete(assembly)) {\n completedCursor = this.buildCursorFromAssembly(assembly)\n assembly.state = 'complete'\n }\n }\n\n if (completedCursor) {\n this.lastCursor = completedCursor\n this.assemblies = []\n // Normal cursor confirmed → exit selection mode immediately\n this.lastSelectionMode = false\n this.lastSelectionCursor = null\n this.selectionRectSamples = []\n return { cursor: completedCursor, cursorColor: this.lastCursorColor, selectionMode: false }\n }\n\n // Start a new assembly from a type-4 TL corner candidate\n if (rect.type === 4 && rect.size.width === 3 && rect.size.height === 1) {\n const newAssembly = this.createNewAssembly(rect)\n this.assemblies.push(newAssembly)\n if ('color' in rect && rect.color) {\n this.lastCursorColor = rect.color\n }\n if (this.assemblies.length > 2) {\n this.assemblies = this.assemblies.slice(-2)\n }\n }\n\n // ----- 2. Selection border detection (only if normal assembly didn't complete) -----\n this.recordSelectionSample(rect, now)\n const selectionCursor = this.detectSelectionCursor(now)\n\n if (selectionCursor) {\n this.lastSelectionCursor = selectionCursor\n this.lastSelectionMode = true\n this.lastSelectionSeenAt = now\n // Clear after a full detection so next cycle starts with a clean buffer.\n // This prevents old edges (still within the 50ms window) from polluting\n // the next detection and making the old position win the score race.\n this.selectionRectSamples = []\n return { cursor: selectionCursor, cursorColor: this.lastCursorColor, selectionMode: true }\n }\n\n // Incremental update path: while in selection mode, M8 can redraw only one\n // border segment at a time. Keep rect tracking independent from key events\n // by updating the previous selection rectangle from partial edge draws.\n const incrementalSelection = this.updateSelectionFromPartialRect(rect)\n if (incrementalSelection) {\n this.lastSelectionCursor = incrementalSelection\n this.lastSelectionMode = true\n this.lastSelectionSeenAt = now\n return { cursor: incrementalSelection, cursorColor: this.lastCursorColor, selectionMode: true }\n }\n\n // Stay in selection mode for SELECTION_HOLD_DURATION after last fresh detection\n if (this.lastSelectionMode) {\n if (now - this.lastSelectionSeenAt > this.SELECTION_HOLD_DURATION) {\n this.lastSelectionMode = false\n this.lastSelectionCursor = null\n } else if (this.lastSelectionCursor) {\n return { cursor: this.lastSelectionCursor, cursorColor: this.lastCursorColor, selectionMode: true }\n }\n }\n\n return { cursor: this.lastCursor, cursorColor: this.lastCursorColor, selectionMode: false }\n }\n\n private updateSelectionFromPartialRect(rect: RectCommand): CursorRect | null {\n if (!this.lastSelectionMode || !this.lastSelectionCursor) return null\n\n const w = rect.size.width\n const h = rect.size.height\n const isHorizontal = h === 1 && w >= 5\n const isVertical = w === 1 && h >= 3\n if (!isHorizontal && !isVertical) return null\n\n const prev = this.lastSelectionCursor\n let left = prev.x - 1\n let top = prev.y - 1\n let right = prev.x + prev.w\n let bottom = prev.y + prev.h\n\n if (isHorizontal) {\n const lineLeft = rect.pos.x\n const lineRight = rect.pos.x + w - 1\n const prevSpan = right - left + 1\n const overlap = Math.min(lineRight, right) - Math.max(lineLeft, left) + 1\n\n // Reject unrelated horizontal lines.\n if (overlap < Math.max(3, Math.floor(prevSpan * 0.6))) return null\n\n const y = rect.pos.y\n if (Math.abs(y - top) <= Math.abs(y - bottom)) {\n top = y\n } else {\n bottom = y\n }\n\n // Keep lateral bounds coherent with the longest observed edge.\n if (lineRight - lineLeft > right - left) {\n left = lineLeft\n right = lineRight\n }\n } else {\n const lineTop = rect.pos.y\n const lineBottom = rect.pos.y + h - 1\n const prevSpan = bottom - top + 1\n const overlap = Math.min(lineBottom, bottom) - Math.max(lineTop, top) + 1\n\n // Reject unrelated vertical lines.\n if (overlap < Math.max(3, Math.floor(prevSpan * 0.6))) return null\n\n const x = rect.pos.x\n if (Math.abs(x - left) <= Math.abs(x - right)) {\n left = x\n } else {\n right = x\n }\n\n // Keep vertical bounds coherent with the longest observed edge.\n if (lineBottom - lineTop > bottom - top) {\n top = lineTop\n bottom = lineBottom\n }\n }\n\n const outerW = right - left + 1\n const outerH = bottom - top + 1\n if (outerW < 3 || outerH < 3) return null\n\n return {\n x: left + 1,\n y: top + 1,\n w: outerW - 2,\n h: outerH - 2,\n }\n }\n\n private recordSelectionSample(rect: RectCommand, now: number): void {\n const w = rect.size.width\n const h = rect.size.height\n\n // w >= 5 excludes the normal corner cursor's 3px-wide pieces (TL/TR/BL/BR are all 3x1)\n // h >= 3 excludes the 1x2 corner-edge pieces\n const isHorizontal = h === 1 && w >= 5\n const isVertical = w === 1 && h >= 3\n if (!isHorizontal && !isVertical) return\n\n this.selectionRectSamples.push({\n x: rect.pos.x,\n y: rect.pos.y,\n w,\n h,\n ts: now,\n hasColor: rect.type === 2 || rect.type === 4,\n })\n\n this.selectionRectSamples = this.selectionRectSamples.filter((sample) => now - sample.ts <= this.SELECTION_SAMPLE_WINDOW)\n }\n\n private detectSelectionCursor(now: number): CursorRect | null {\n this.selectionRectSamples = this.selectionRectSamples.filter((sample) => now - sample.ts <= this.SELECTION_SAMPLE_WINDOW)\n if (this.selectionRectSamples.length < 4) return null\n\n const horizontals = this.selectionRectSamples.filter((sample) => sample.h === 1 && sample.w >= 3)\n const verticals = this.selectionRectSamples.filter((sample) => sample.w === 1 && sample.h >= 3)\n if (horizontals.length === 0 || verticals.length === 0) return null\n\n const refCursor = this.lastSelectionMode && this.lastSelectionCursor ? this.lastSelectionCursor : this.lastCursor\n const prevCenterX = refCursor.x + refCursor.w / 2\n const prevCenterY = refCursor.y + refCursor.h / 2\n\n let best: { cursor: CursorRect; score: number; latestTs: number } | null = null\n\n for (const top of horizontals) {\n for (const bottom of horizontals) {\n if (bottom.y <= top.y) continue\n if (bottom.x !== top.x || bottom.w !== top.w) continue\n\n const outerH = bottom.y - top.y + 1\n if (outerH < 3) continue\n\n // M8 may draw verticals starting 1px inside the corners:\n // left side: x=top.x, y=top.y+1, h=outerH-2 (skipping corners)\n // OR: x=top.x, y=top.y, h=outerH (covering corners)\n // Both are valid – check that the segment spans from near top to near bottom.\n const left = verticals.find((v) => v.x === top.x && v.y >= top.y && v.y <= top.y + 2 && v.y + v.h - 1 >= bottom.y - 1)\n if (!left) continue\n\n const rightX = top.x + top.w - 1\n const right = verticals.find((v) => v.x === rightX && v.y >= top.y && v.y <= top.y + 2 && v.y + v.h - 1 >= bottom.y - 1)\n if (!right) continue\n\n const cursor = {\n x: top.x + 1,\n y: top.y + 1,\n w: top.w - 2,\n h: outerH - 2,\n }\n\n if (cursor.w <= 0 || cursor.h <= 0) continue\n\n const latestTs = Math.max(top.ts, bottom.ts, left.ts, right.ts)\n\n // Tiebreak by distance from previous cursor center (prefer spatially close)\n const centerX = cursor.x + cursor.w / 2\n const centerY = cursor.y + cursor.h / 2\n const dx = centerX - prevCenterX\n const dy = centerY - prevCenterY\n const score = dx * dx + dy * dy\n\n // Primary sort: freshest quad wins (avoids stale previous-frame edges\n // shadowing new ones when both are within the SELECTION_SAMPLE_WINDOW).\n // Secondary sort: closest to previous center.\n if (!best || latestTs > best.latestTs || (latestTs === best.latestTs && score < best.score)) {\n best = { cursor, score, latestTs }\n }\n }\n }\n\n return best?.cursor ?? null\n }\n\n /**\n * Create a new cursor assembly from TL starter\n */\n private createNewAssembly(tlRect: RectCommand): CursorAssembly {\n const tlX = tlRect?.pos.x ?? 0\n const tlY = tlRect?.pos.y ?? 0\n\n return {\n tl: {\n x: tlX,\n y: tlY,\n expectedVert: { x: tlX, y: tlY + 1 }, // 1x2 below the 3x1\n expectedTip: { x: tlX + 1, y: tlY + 1 }, // 1x1 inside corner\n foundVert: false,\n foundTip: false,\n },\n tr: null,\n bl: null,\n br: null,\n startTime: Date.now(),\n state: 'building',\n }\n }\n\n /**\n * Process a rectangle against a specific assembly\n */\n private processRectAgainstAssembly(rect: RectCommand, assembly: CursorAssembly): void {\n // Check if rectangle matches any expected pattern in the assembly\n\n // 1. Check TL corner verification pieces\n this.checkCornerVerification(rect, assembly.tl)\n\n // 2. Look for TR corner (3x1, type 3, same y as TL)\n if (\n !assembly.tr &&\n rect.type === 3 &&\n rect.size.width === 3 &&\n rect.size.height === 1 &&\n rect.pos.y === assembly.tl.y &&\n rect.pos.x > assembly.tl.x\n ) {\n // Must be to the right\n\n assembly.tr = {\n x: rect.pos.x,\n y: rect.pos.y,\n expectedVert: { x: rect.pos.x + 2, y: rect.pos.y + 1 }, // 1x2 right of the 3x1\n expectedTip: { x: rect.pos.x + 1, y: rect.pos.y + 1 }, // 1x1 inside corner\n foundVert: false,\n foundTip: false,\n }\n }\n\n // 3. Look for BL corner (3x1, type 3, same x as TL)\n if (\n !assembly.bl &&\n rect.type === 3 &&\n rect.size.width === 3 &&\n rect.size.height === 1 &&\n rect.pos.x === assembly.tl.x &&\n rect.pos.y > assembly.tl.y\n ) {\n // Must be below\n\n assembly.bl = {\n x: rect.pos.x,\n y: rect.pos.y,\n expectedVert: { x: rect.pos.x, y: rect.pos.y - 2 }, // 1x2 above the 3x1\n expectedTip: { x: rect.pos.x + 1, y: rect.pos.y - 1 }, // 1x1 inside corner\n foundVert: false,\n foundTip: false,\n }\n }\n\n // 4. Look for BR corner (3x1, type 3)\n if (\n assembly.tr &&\n assembly.bl &&\n !assembly.br &&\n rect.type === 3 &&\n rect.size.width === 3 &&\n rect.size.height === 1 &&\n rect.pos.x === assembly.tr.x &&\n rect.pos.y === assembly.bl.y\n ) {\n assembly.br = {\n x: rect.pos.x,\n y: rect.pos.y,\n expectedVert: { x: rect.pos.x + 2, y: rect.pos.y - 2 }, // 1x2 above-right\n expectedTip: { x: rect.pos.x + 1, y: rect.pos.y - 1 }, // 1x1 inside corner\n foundVert: false,\n foundTip: false,\n }\n }\n\n // 5. Check verification pieces for other corners\n if (assembly.tr) this.checkCornerVerification(rect, assembly.tr)\n if (assembly.bl) this.checkCornerVerification(rect, assembly.bl)\n if (assembly.br) this.checkCornerVerification(rect, assembly.br)\n }\n\n /**\n * Check if a rectangle verifies a corner pattern\n */\n private checkCornerVerification(rect: RectCommand, corner: CornerCandidate): void {\n if (!corner.expectedVert || !corner.expectedTip) return\n\n // Check for 1x2 vertical edge\n if (\n !corner.foundVert &&\n rect.type === 3 &&\n rect.size.width === 1 &&\n rect.size.height === 2 &&\n rect.pos.x === corner.expectedVert.x &&\n rect.pos.y === corner.expectedVert.y\n ) {\n corner.foundVert = true\n }\n\n // Check for 1x1 tip\n if (\n !corner.foundTip &&\n rect.type === 1 &&\n rect.size.width === 1 &&\n rect.size.height === 1 &&\n rect.pos.x === corner.expectedTip.x &&\n rect.pos.y === corner.expectedTip.y\n ) {\n corner.foundTip = true\n }\n }\n\n /**\n * Check if assembly has enough evidence to be a complete cursor\n */\n private isAssemblyComplete(assembly: CursorAssembly): boolean {\n // Must have all 4 corners\n if (!assembly.tr || !assembly.bl || !assembly.br) {\n return false\n }\n\n // Basic cursor geometry checks\n const width = assembly.tr.x - assembly.tl.x\n const height = assembly.bl.y - assembly.tl.y\n\n // Width and height should be positive\n if (width <= 0 || height <= 0) {\n return false\n }\n\n // Check if corners form a proper rectangle\n if (assembly.br.x !== assembly.tr.x || assembly.br.y !== assembly.bl.y) {\n return false\n }\n\n // We need some verification evidence to be confident\n // Count how many corners have at least one verification piece\n const corners = [assembly.tl, assembly.tr, assembly.bl, assembly.br]\n const verifiedCorners = corners.filter((c) => c.foundVert || c.foundTip).length\n\n // Require at least 2 corners to have verification\n return verifiedCorners >= 2\n }\n\n /**\n * Build final cursor rectangle from complete assembly\n */\n private buildCursorFromAssembly(assembly: CursorAssembly): CursorRect {\n // Must have all 4 corners\n if (!assembly.tr || !assembly.bl || !assembly.br) {\n return defaultCursor\n }\n\n // Basic cursor geometry checks\n const width = assembly.tr.x - assembly.tl.x - 1\n const height = assembly.bl.y - assembly.tl.y - 1\n\n // Cursor is 1 pixel inside the 3x1 edges\n const x = assembly.tl.x + 1\n const y = assembly.tl.y + 1\n const w = width\n const h = height\n\n return { x, y, w, h }\n }\n\n /**\n * Reset detection\n */\n reset(): void {\n this.assemblies = []\n this.selectionRectSamples = []\n this.lastCursor = { x: 0, y: 0, w: 1, h: 1 }\n this.lastSelectionCursor = null\n this.lastSelectionMode = false\n this.lastSelectionSeenAt = 0\n }\n}\n\nexport const SCREEN_ROWS = 24\nexport const SCREEN_COLUMNS = 40\n\n/** Colour of every character cell, row by row ('#rrggbb', null where nothing was drawn). */\nexport type ScreenCellColors = { fg: (string | null)[][]; bg: (string | null)[][] }\n\n/** The protocol events the extractor listens to (a ConnectedBus's protocol.eventBus fits). */\nexport interface DrawEventSource {\n on(event: 'text', callback: (data: CharacterCommand) => void): void\n on(event: 'rect', callback: (data: RectCommand) => void): void\n on(event: 'systemInfo', callback: (data: SystemInfos) => void): void\n off(event: 'text', callback: (data: CharacterCommand) => void): void\n off(event: 'rect', callback: (data: RectCommand) => void): void\n off(event: 'systemInfo', callback: (data: SystemInfos) => void): void\n}\n\nexport interface ViewExtractor {\n /** Whole M8 screen as text rows, one string per row (columns preserved, trailing spaces trimmed). */\n getScreenLines(): string[]\n /** Colours of the whole M8 screen, same grid as getScreenLines. */\n getScreenCellColors(): ScreenCellColors\n dispose(): void\n}\n\nconst rgbHex = (c: RGB | null | undefined): string | null => (c ? `#${[c.r, c.g, c.b].map((v) => v.toString(16).padStart(2, '0')).join('')}` : null)\n\n/** Listens to the draw stream and keeps `store` up to date. */\nexport function registerViewExtractor(events: DrawEventSource, store: M8Store): ViewExtractor {\n const CursorAssembly = new AssemblyCursorExtractor()\n // Title extractor with early acceptance\n class AssemblyViewNameExtractor {\n private rowChars: Map<number, { ch: string; fg: RGB | null; bg: RGB | null }> = new Map()\n //private lastChange = 0\n private timer: ReturnType<typeof setTimeout> | null = null\n private knownViews: string[] = []\n private earlyAccepted = false\n\n init() {\n this.knownViews = Array.from(getViewNames())\n }\n\n private normForCompare(s: string) {\n s = s.includes('live') ? 'song' : s\n return s.toLowerCase().trim().replace(/[{}]/g, '0') //.replace(/[^a-z0-9]/g, '')\n }\n\n private normalizeFinal(titleRaw: string) {\n // Trim, remove trailing hex token and optional ^/*, strip spaces/punct, lower, cap 20\n const trimmed = titleRaw.trim()\n\n // biome-ignore lint/complexity/noUselessEscapeInRegex: needed for * or ^\n const noTrail = trimmed.replace(/[{}]/g, '0').replace(/\\s[0-9a-fA-F]{1,2}[\\^\\*]?\\s*$/, '')\n const cleaned = noTrail\n .replace(/[^a-z0-9 ]/gi, '')\n .replace(/\\s+/g, ' ')\n .trim()\n const noSpaces = cleaned.replace(/\\s+/g, '').toLowerCase().slice(0, 20)\n return noSpaces.includes('live') ? 'song' : noSpaces\n }\n\n private pickColors() {\n // pick first non-space char colors\n const entries = Array.from(this.rowChars.entries()).sort((a, b) => a[0] - b[0])\n for (const [, v] of entries) {\n if (v.ch?.trim()) return { fg: v.fg, bg: v.bg }\n }\n return { fg: null as RGB | null, bg: null as RGB | null }\n }\n\n private emitStable() {\n const entries = Array.from(this.rowChars.entries()).sort((a, b) => a[0] - b[0])\n const raw = entries.map(([, v]) => v.ch || ' ').join('')\n const normalized = this.normalizeFinal(raw)\n const { fg, bg } = this.pickColors()\n store.set('viewTitle', raw || null)\n store.set('titleColor', fg)\n store.set('backgroundColor', bg)\n store.set('viewName', normalized || null)\n }\n\n private tryEarlyAccept(currentGx: number) {\n if (this.earlyAccepted) return true\n if (!this.knownViews || this.knownViews.length === 0) return false\n // Build partial from 0..currentGx (inclusive), preserving spaces\n const upto = Math.max(0, Math.min(currentGx, 39))\n const partialRaw = Array.from({ length: upto + 1 }, (_, i) => this.rowChars.get(i)?.ch || ' ').join('')\n const partial = this.normForCompare(partialRaw)\n if (!partial || partial.length < 2) return false\n const candidates = this.knownViews.filter((v) => v.startsWith(partial))\n if (candidates.length === 1) {\n const pick = candidates[0].slice(0, 20)\n const { fg, bg } = this.pickColors()\n store.set('viewTitle', partial)\n store.set('titleColor', fg)\n store.set('backgroundColor', bg)\n store.set('viewName', pick)\n this.earlyAccepted = true\n return true\n }\n return false\n }\n\n processChar(cmd: CharacterCommand) {\n const { cellW, cellH, offX, offY } = store.get('cellMetrics')\n // Use floor to consistently map pixel positions to grid positions\n const gx = Math.floor((cmd.pos.x - offX) / cellW)\n const gy = Math.floor((cmd.pos.y - offY) / cellH)\n\n // If we observe rows beyond the title area, finalize immediately\n if (gy > 4) {\n if (this.rowChars.size > 0) {\n if (this.timer) {\n clearTimeout(this.timer)\n this.timer = null\n }\n this.emitStable()\n }\n return\n }\n\n // Title row detection: based on debug logs, title consistently at gy==3\n // across font modes (Model:02 + Headless). Narrow acceptance to row 3\n // to reduce noise and speed early acceptance.\n if (gy !== 3) return\n\n if (gx >= 30) return // cap title length to 20\n\n this.rowChars.set(gx, { ch: cmd.character, fg: cmd.foreground, bg: cmd.background })\n //this.lastChange = Date.now()\n\n // Early acceptance attempt\n if (this.tryEarlyAccept(gx)) {\n // continue to collect for full colors/raw title\n }\n\n if (this.timer) clearTimeout(this.timer)\n this.timer = setTimeout(() => {\n // If no match yet, accept stabilized buffer\n this.emitStable()\n }, STABILIZE_MS)\n }\n\n reset() {\n this.rowChars.clear()\n if (this.timer) {\n clearTimeout(this.timer)\n this.timer = null\n }\n this.earlyAccepted = false\n }\n }\n\n const extractor = new AssemblyViewNameExtractor()\n extractor.init()\n\n // Character grid tracker for text extraction\n class CharacterGridTracker {\n // Grid: y -> x -> { char, fg, bg }\n private grid: Map<number, Map<number, { ch: string; fg: RGB; bg: RGB }>> = new Map()\n\n processChar(cmd: CharacterCommand) {\n const { cellW, cellH, offX, offY } = store.get('cellMetrics')\n const gx = Math.floor((cmd.pos.x - offX) / cellW)\n const gy = Math.floor((cmd.pos.y - offY) / cellH)\n\n if (!this.grid.has(gy)) {\n this.grid.set(gy, new Map())\n }\n this.grid.get(gy)?.set(gx, { ch: cmd.character.replace(/[{}]/g, '0'), fg: cmd.foreground, bg: cmd.background })\n\n // Clean up old rows (keep only last 30 rows)\n if (this.grid.size > 30) {\n console.log('too much rows', this.grid.size)\n const sortedKeys = Array.from(this.grid.keys()).sort((a, b) => a - b)\n const toRemove = sortedKeys.slice(0, sortedKeys.length - 30)\n for (const key of toRemove) {\n this.grid.delete(key)\n }\n }\n // Update highlight info for new cursor position\n this.updateHighlightAtCursor()\n }\n\n updateHighlightAtCursor() {\n const cursorRect = store.get('cursorRect')\n const pos = store.get('cursorPos')\n\n if (!pos || !cursorRect) {\n store.set('textUnderCursor', null)\n store.set('currentLine', null)\n store.set('highlightColor', null)\n return\n }\n\n const { cellW, offX } = store.get('cellMetrics')\n\n // Convert pixel cursor bounds to grid coordinates.\n // startGx uses Math.round to absorb the 1–2px leftward overhang of the cursor\n // border so we don't pick up the character in the column to the left.\n // endGx uses Math.floor: the cursor's right pixel (tr.x - 1) falls exactly at\n // the last cell's rightmost pixel, so floor is already correct there.\n const startGx = Math.round((cursorRect.x - offX) / cellW)\n const endGx = Math.floor((cursorRect.x + cursorRect.w - 1 - offX) / cellW)\n const gy = pos.y // Use cursor's grid Y position\n\n // Extract text within cursor bounds (no hue filtering)\n const textUnderCursor = this.getTextUnderCursor(startGx, endGx, gy)\n\n // Extract highlight color from text under cursor (stable, not pulsing)\n const textHighlightColor = this.getHighlightColorFromText(startGx, endGx, gy)\n\n const currentLine = this.getCurrentLine(gy)\n\n store.set('textUnderCursor', textUnderCursor)\n store.set('currentLine', currentLine)\n\n // Use stable text color as highlight color\n if (textHighlightColor) {\n store.set('highlightColor', textHighlightColor)\n }\n }\n\n getTextUnderCursor(startGx: number, endGx: number, gy: number): string | null {\n const row = this.grid.get(gy)\n if (!row) return null\n\n // Extract all characters within cursor bounds without hue filtering\n const text: string[] = []\n for (let x = startGx; x <= endGx; x++) {\n const cell = row.get(x)\n if (cell) {\n text.push(cell.ch)\n }\n }\n\n return text.join('').trim() || null\n }\n\n /**\n * Get the stable highlight color from text under cursor\n * This is the foreground color of the text, which doesn't pulse\n */\n getHighlightColorFromText(startGx: number, endGx: number, gy: number): RGB | null {\n const row = this.grid.get(gy)\n if (!row) return null\n\n // Find first non-space character within cursor bounds\n for (let x = startGx; x <= endGx; x++) {\n const cell = row.get(x)\n if (cell?.ch.trim()) {\n return cell.fg\n }\n }\n return null\n }\n\n getCurrentLine(gy: number): string | null {\n const row = this.grid.get(gy)\n if (!row || row.size === 0) return null\n\n // Get all characters in the row, sorted by x position\n const sortedEntries = Array.from(row.entries()).sort((a, b) => a[0] - b[0])\n const line = sortedEntries.map(([, v]) => v.ch).join('')\n return line.trim() || null\n }\n\n /**\n * Whole screen as text, one string per row. Unlike getCurrentLine, columns are\n * preserved (missing cells become spaces) so x offsets stay meaningful.\n */\n getScreenLines(height: number): string[] {\n const lines: string[] = []\n for (let gy = 0; gy < height; gy += 1) {\n const row = this.grid.get(gy)\n if (!row || row.size === 0) {\n lines.push('')\n continue\n }\n let maxX = 0\n for (const x of row.keys()) if (x > maxX) maxX = x\n const chars: string[] = []\n for (let x = 0; x <= maxX; x += 1) chars.push(row.get(x)?.ch ?? ' ')\n lines.push(chars.join('').trimEnd())\n }\n return lines\n }\n\n /** Foreground/background colour of every cell, `width` entries per row. */\n getCellColors(height: number, width: number): ScreenCellColors {\n const fg: (string | null)[][] = []\n const bg: (string | null)[][] = []\n for (let gy = 0; gy < height; gy += 1) {\n const row = this.grid.get(gy)\n const fgRow: (string | null)[] = []\n const bgRow: (string | null)[] = []\n for (let x = 0; x < width; x += 1) {\n const cell = row?.get(x)\n fgRow.push(rgbHex(cell?.fg))\n bgRow.push(rgbHex(cell?.bg))\n }\n fg.push(fgRow)\n bg.push(bgRow)\n }\n return { fg, bg }\n }\n\n reset() {\n this.grid.clear()\n }\n }\n\n const charGridTracker = new CharacterGridTracker()\n\n // Temporary debug: set window.__debugRects = true in the console for 1 second of rect logging\n let debugRectEndTime = 0\n if (typeof window !== 'undefined') {\n const win = window as unknown as Record<string, unknown>\n win.__debugRects = false\n Object.defineProperty(win, '__debugRects', {\n configurable: true,\n set(v: unknown) {\n if (v) debugRectEndTime = Date.now() + 1000\n },\n })\n }\n\n const cursorExtractor = (data: RectCommand) => {\n if (!data) return\n\n if (Date.now() < debugRectEndTime) {\n console.log(`[rect] type=${data.type} pos=(${data.pos.x},${data.pos.y}) size=${data.size.width}x${data.size.height}`)\n }\n\n const { cellW, cellH, offX, offY } = store.get('cellMetrics')\n const { cursor, cursorColor, selectionMode } = CursorAssembly.processRect(data)\n if (cursorColor && rgbHex(cursorColor) !== rgbHex(store.get('cursorColor'))) {\n store.set('cursorColor', cursorColor)\n }\n const { x, y, w, h } = cursor\n // Use top-left of cursor to determine grid position (same as characters).\n // Math.round instead of Math.floor absorbs the 1–2px leftward overhang of the\n // cursor border corners so gx maps to the correct cell (not the one to its left).\n const gx = Math.round((x - offX) / cellW)\n const gy = Math.round((y - offY) / cellH)\n\n const prevPos = store.get('cursorPos')\n const prevSelectionMode = store.get('selectionMode')\n\n if (!selectionMode) {\n // Normal cursor: update when grid position changed OR when\n // transitioning out of selection mode (rect size changes dramatically).\n const modeJustChanged = prevSelectionMode !== selectionMode\n if (!prevPos || prevPos.x !== gx || prevPos.y !== gy || modeJustChanged) {\n store.set('cursorPos', { x: gx, y: gy })\n store.set('cursorRect', { x, y, w, h })\n }\n } else {\n // Selection mode: update independently — selection can grow at the same\n // anchor grid cell (SHIFT+direction extends rect without moving anchor).\n const prevRect = store.get('cursorRect')\n if (!prevPos || prevPos.x !== gx || prevPos.y !== gy) {\n store.set('cursorPos', { x: gx, y: gy })\n }\n if (!prevRect || prevRect.x !== x || prevRect.y !== y || prevRect.w !== w || prevRect.h !== h) {\n store.set('cursorRect', { x, y, w, h })\n }\n }\n\n if (prevSelectionMode !== selectionMode) {\n store.set('selectionMode', selectionMode)\n }\n }\n\n const systemInfoHandler = (info: SystemInfos) => {\n store.set('deviceModel', info.model)\n store.set('fontMode', info.fontMode)\n store.set('cellMetrics', { cellW: info.spacingX, cellH: info.spacingY, offX: info.offX, offY: info.offY })\n store.set('systemInfo', {\n model: info.model,\n fontMode: info.fontMode,\n spacingX: info.spacingX,\n spacingY: info.spacingY,\n offX: info.offX,\n offY: info.offY,\n screenWidth: info.screenWidth ?? 480,\n screenHeight: info.screenHeight ?? 320,\n rectOffset: info.rectOffset,\n })\n }\n\n const onText = (d: CharacterCommand) => {\n extractor.processChar(d)\n charGridTracker.processChar(d)\n }\n events.on('text', onText)\n events.on('rect', cursorExtractor)\n events.on('systemInfo', systemInfoHandler)\n\n return {\n getScreenLines: () => charGridTracker.getScreenLines(SCREEN_ROWS),\n getScreenCellColors: () => charGridTracker.getCellColors(SCREEN_ROWS, SCREEN_COLUMNS),\n dispose: () => {\n events.off('text', onText)\n events.off('rect', cursorExtractor)\n events.off('systemInfo', systemInfoHandler)\n extractor.reset()\n charGridTracker.reset()\n },\n }\n}\n","import type { M8State } from '../../types'\nimport type { M8Store } from '../store'\n\n// The state exposed to SDK clients, read from the engine store\nexport const getCurrentState = (store: M8Store): M8State => {\n const macroStatus = store.get('macroStatus')\n\n return {\n viewName: store.get('viewName'),\n viewTitle: store.get('viewTitle'),\n minimapKey: store.get('minimapKey'),\n cursorPos: store.get('cursorPos'),\n cursorRect: store.get('cursorRect'),\n selectionMode: store.get('selectionMode'),\n highlightColor: store.get('highlightColor'),\n titleColor: store.get('titleColor'),\n backgroundColor: store.get('backgroundColor'),\n cursorColor: store.get('cursorColor'),\n textUnderCursor: store.get('textUnderCursor'),\n currentLine: store.get('currentLine'),\n deviceModel: store.get('deviceModel'),\n fontMode: store.get('fontMode'),\n systemInfo: store.get('systemInfo') as M8State['systemInfo'],\n macroRunning: macroStatus.running,\n macroCurrentStep: macroStatus.currentStep,\n macroSequenceLength: macroStatus.sequenceLength,\n }\n}\n\n// Convert text grid coordinates to pixel coordinates\n// Text grid: x (0-39), y (0-23) - independent of font size\n// Pixel: depends on current cell metrics (cellW, cellH, offX, offY)\n// Send the center of the target cell so M8 places the cursor exactly there.\n// gx extraction in viewExtractor uses Math.round to absorb the cursor border\n// overhang, so no additional x compensation is needed here.\nexport const textGridToPixel = (store: M8Store, gridX: number, gridY: number): { x: number; y: number } => {\n const cellMetrics = store.get('cellMetrics')\n\n // Center of cell (gridX, gridY) in raw M8 protocol pixel space\n const pixelX = gridX * cellMetrics.cellW + cellMetrics.offX + cellMetrics.cellW / 2\n const pixelY = gridY * cellMetrics.cellH + cellMetrics.offY + cellMetrics.cellH / 2\n\n return { x: pixelX, y: pixelY }\n}\n","import type { M8HostMethods, M8KeyName } from '../../types'\r\nimport { pressKeys } from '../keys'\r\nimport { SCREEN_COLUMNS, SCREEN_ROWS, type ScreenCellColors } from '../viewExtractor'\r\nimport type { HostIO } from './hostIO'\r\nimport { getCurrentState, textGridToPixel } from './hostState'\r\nimport type { createValueEditors } from './valueEditors'\r\n\r\nconst keyNamesToMask = (keys: M8KeyName[]) =>\r\n pressKeys({\r\n left: keys.includes('left'),\r\n right: keys.includes('right'),\r\n up: keys.includes('up'),\r\n down: keys.includes('down'),\r\n shift: keys.includes('shift'),\r\n play: keys.includes('play'),\r\n opt: keys.includes('opt'),\r\n edit: keys.includes('edit'),\r\n })\r\n\r\nexport type HostMethodsDeps = {\r\n io: HostIO\r\n editors: ReturnType<typeof createValueEditors>\r\n setValueFloat: (targetFloat: number) => Promise<boolean>\r\n browseFile: (targetText: string, exact: boolean) => Promise<boolean>\r\n navigateToView: (viewName: string) => Promise<boolean>\r\n navigateTo: (point: { x: number; y: number }) => Promise<boolean>\r\n enableMidiTransport: () => Promise<{ ok: boolean; error?: string }>\r\n getScreenCellColors: () => ScreenCellColors\r\n}\r\n\r\n/** The methods an SDK client can call on the engine (transport-agnostic). */\r\nexport const createHostMethods = ({\r\n io,\r\n editors,\r\n setValueFloat,\r\n browseFile,\r\n navigateToView,\r\n navigateTo,\r\n enableMidiTransport,\r\n getScreenCellColors,\r\n}: HostMethodsDeps): M8HostMethods => {\r\n const { debugLog, hasBus, sendKeys, pressAndRelease, store, getScreenLines } = io\r\n\r\n // Wrap a method so it warns and returns `fallback` when no M8 is connected\r\n const guarded =\r\n <A extends unknown[], R>(name: string, fallback: R, fn: (...args: A) => Promise<R>) =>\r\n async (...args: A): Promise<R> => {\r\n if (!hasBus()) {\r\n console.warn(`[M8SDK] ${name}: no bus connection`)\r\n return fallback\r\n }\r\n return fn(...args)\r\n }\r\n\r\n return {\r\n navigateToView: guarded('navigateToView', false, async (viewName: string): Promise<boolean> => {\r\n debugLog('[M8SDK] Executing navigateToView:', viewName)\r\n return navigateToView(viewName)\r\n }),\r\n navigateTo: guarded('navigateTo', false, async (gridX: number, gridY: number): Promise<boolean> => {\r\n // Convert text grid coordinates (0-39, 0-23) to pixel coordinates\r\n return navigateTo(textGridToPixel(store, gridX, gridY))\r\n }),\r\n setValueToHex: guarded('setValueToHex', false, async (hex: number): Promise<boolean> => {\r\n debugLog('[M8SDK] Executing setValueToHex:', hex)\r\n return editors.setValueToHex(hex)\r\n }),\r\n setValueToInt: guarded('setValueToInt', false, async (targetInt: number): Promise<boolean> => {\r\n debugLog('[M8SDK] Executing setValueToInt:', targetInt)\r\n return editors.setValueToInt(targetInt)\r\n }),\r\n setValueFloat: guarded('setValueFloat', false, async (targetFloat: number): Promise<boolean> => {\r\n debugLog('[M8SDK] Executing setValueFloat:', targetFloat)\r\n return setValueFloat(targetFloat)\r\n }),\r\n setNote: guarded('setNote', false, async (noteString: string): Promise<boolean> => {\r\n debugLog('[M8SDK] Executing setNote:', noteString)\r\n return editors.setNote(noteString)\r\n }),\r\n setValueToString: guarded(\r\n 'setValueToString',\r\n false,\r\n async (targetString: string, exact: boolean = true, searchInCurrentLine: boolean = false): Promise<boolean> => {\r\n debugLog('[M8SDK] Executing setValueToString:', { targetString, exact, searchInCurrentLine })\r\n return editors.setValueToString(targetString, exact, searchInCurrentLine)\r\n },\r\n ),\r\n browseFile: guarded('browseFile', false, async (targetText: string, exact: boolean = true): Promise<boolean> => {\r\n debugLog('[M8SDK] Executing browseFile:', { targetText, exact })\r\n return browseFile(targetText, exact)\r\n }),\r\n sendKeyDown: guarded('sendKeyDown', undefined, async (keys: M8KeyName[]): Promise<void> => {\r\n debugLog('[M8SDK] Executing sendKeyDown:', keys)\r\n sendKeys(keyNamesToMask(keys))\r\n }),\r\n sendKeyUp: guarded('sendKeyUp', undefined, async (): Promise<void> => {\r\n debugLog('[M8SDK] Executing sendKeyUp')\r\n sendKeys(0)\r\n }),\r\n sendKeyPress: guarded('sendKeyPress', undefined, async (keys: M8KeyName[]): Promise<void> => {\r\n debugLog('[M8SDK] Executing sendKeyPress:', keys)\r\n await pressAndRelease(keyNamesToMask(keys))\r\n }),\r\n getState: async () => getCurrentState(store),\r\n getScreen: async () => ({ width: SCREEN_COLUMNS, height: SCREEN_ROWS, lines: getScreenLines() }),\r\n getScreenCells: async () => ({ width: SCREEN_COLUMNS, height: SCREEN_ROWS, lines: getScreenLines(), ...getScreenCellColors() }),\r\n enableMidiTransport: async () => enableMidiTransport(),\r\n }\r\n}\r\n","// MIDI clock and transport from the M8's USB MIDI output, for SDK clients that follow the\n// song while it plays. The M8 sends them when Project > MIDI Settings > SYNC OUT includes\n// CLOCK / TRANSPORT / SPP (a per-song setting). Independent of React.\n\nimport type { M8TransportEvent } from '../../types'\n\n/** Clock ticks per sixteenth note at 24 PPQ: one phrase step with the default groove. */\nconst TICKS_PER_STEP = 6\n\nexport type MidiTransport = {\n /** Opens the M8 MIDI input (asks for Web MIDI permission the first time). */\n enable: () => Promise<{ ok: boolean; error?: string }>\n disable: () => void\n}\n\nexport const createMidiTransport = (emit: (event: M8TransportEvent) => void): MidiTransport => {\n let input: MIDIInput | null = null\n let ticks = 0\n let lastTickAt: number | null = null\n let msPerTick: number | null = null\n\n const epoch = (timeStamp: number) => performance.timeOrigin + timeStamp\n\n const onMessage = (e: MIDIMessageEvent) => {\n const data = e.data\n if (!data || data.length === 0) return\n const at = epoch(e.timeStamp)\n switch (data[0]) {\n case 0xf8: {\n // Clock: estimate the tick length, report once per step.\n if (lastTickAt != null) {\n const gap = at - lastTickAt\n if (gap > 0 && gap < 200) msPerTick = msPerTick == null ? gap : msPerTick * 0.9 + gap * 0.1\n }\n lastTickAt = at\n // The M8 can send clock without transport (SYNC OUT = CLOCK) and keeps it running\n // while stopped: the count is a monotonic time base, never reset.\n ticks += 1\n if (ticks % TICKS_PER_STEP === 0) emit({ kind: 'clock', ticks, at, msPerTick })\n return\n }\n case 0xfa:\n emit({ kind: 'start', ticks, at, msPerTick })\n return\n case 0xfb:\n emit({ kind: 'continue', ticks, at, msPerTick })\n return\n case 0xfc:\n emit({ kind: 'stop', ticks, at, msPerTick })\n return\n case 0xf2: {\n // Song Position Pointer: sixteenths since the song start.\n const spp = (data[1] ?? 0) | ((data[2] ?? 0) << 7)\n emit({ kind: 'position', ticks, at, msPerTick, spp })\n return\n }\n }\n }\n\n type Result = { ok: boolean; error?: string }\n\n let pending: Promise<Result> | null = null\n /** Counts disables, so an open that one overtook adds no listener. */\n let generation = 0\n\n const open = async (): Promise<Result> => {\n const started = generation\n if (!('requestMIDIAccess' in navigator)) return { ok: false, error: 'Web MIDI is not available in this browser' }\n try {\n const access = await navigator.requestMIDIAccess()\n const m8 = [...access.inputs.values()].find((i) => i.name === 'M8')\n if (!m8) return { ok: false, error: 'no MIDI input named M8' }\n await m8.open()\n // A call that was disabled meanwhile must not leave a listener behind.\n if (generation !== started) return { ok: false, error: 'disabled' }\n m8.addEventListener('midimessage', onMessage)\n input = m8\n return { ok: true }\n } catch (error) {\n return { ok: false, error: error instanceof Error ? error.message : String(error) }\n }\n }\n\n // Calls that come while the input is still opening share its answer: Chrome gives every\n // requestMIDIAccess() its own MIDIInput objects, so two opens would each add the listener and\n // count every message twice (the tick count then runs at twice the clock).\n return {\n enable: () => {\n if (input) return Promise.resolve({ ok: true })\n if (pending) return pending\n const opening = open()\n pending = opening\n void opening.then((result) => {\n // A failure is not kept: the next call tries again.\n if (pending === opening && !result.ok) pending = null\n })\n return opening\n },\n disable: () => {\n generation += 1\n pending = null\n input?.removeEventListener('midimessage', onMessage)\n input = null\n },\n }\n}\n","import { M8KeyMask } from '../keys'\n\n// Pure key-press sequence planners (press/release frames) for editing values.\n\n// Precalculate the key sequence needed to reach target value from current value\n// Uses edit+left/right for ±1 and edit+up/down for ±16\nexport const calculateKeySequence = (currentValue: number, targetValue: number): number[] => {\n const sequence: number[] = []\n let current = currentValue\n const target = targetValue\n\n while (current !== target) {\n const diff = target - current\n const absDiff = Math.abs(diff)\n const direction = diff > 0 ? 1 : -1\n\n if (absDiff >= 16) {\n // Use large steps (±16) - edit+up for +16, edit+down for -16\n const steps16 = Math.floor(absDiff / 16)\n const key = direction > 0 ? M8KeyMask.Up : M8KeyMask.Down\n const keys = M8KeyMask.Edit | key\n\n // Take as many large steps as possible (capped at keep-alive limit)\n const stepsToTake = Math.min(steps16, 10)\n for (let i = 0; i < stepsToTake; i++) {\n // Press key\n sequence.push(keys)\n // Release key\n sequence.push(0)\n current += direction * 16\n if (current === target) break\n }\n } else if (absDiff > 0) {\n // Use fine adjustment (±1) - edit+right for +1, edit+left for -1\n const key = direction > 0 ? M8KeyMask.Right : M8KeyMask.Left\n const keys = M8KeyMask.Edit | key\n\n // Press key\n sequence.push(keys)\n // Release key\n sequence.push(0)\n current += direction * 1\n } else {\n break\n }\n }\n\n return sequence\n}\n\n// Precalculate note moves in semitone space.\n// Uses edit+up/down for octave jumps (±12) and edit+left/right for semitone steps (±1).\nexport const calculateNoteKeySequence = (currentIndex: number, targetIndex: number): number[] => {\n const sequence: number[] = []\n let current = currentIndex\n\n while (current !== targetIndex) {\n const diff = targetIndex - current\n const direction = diff > 0 ? 1 : -1\n\n if (Math.abs(diff) >= 12) {\n const key = direction > 0 ? M8KeyMask.Up : M8KeyMask.Down\n sequence.push(M8KeyMask.Edit | key)\n sequence.push(0)\n current += direction * 12\n } else {\n const key = direction > 0 ? M8KeyMask.Right : M8KeyMask.Left\n sequence.push(M8KeyMask.Edit | key)\n sequence.push(0)\n current += direction\n }\n\n // Safety cap for malformed input.\n if (sequence.length > 2048) {\n break\n }\n }\n\n return sequence\n}\n","import { M8KeyMask } from '../keys'\nimport { normalizeForSearch } from './fileBrowserHelpers'\nimport { type HostIO, wait } from './hostIO'\nimport { calculateKeySequence, calculateNoteKeySequence } from './keySequences'\nimport { parseHexValue, parseIntValue, parseNoteValue } from './valueParsers'\n\n// Edit the value under the M8 cursor using edit+arrow key combos.\nexport const createValueEditors = (io: HostIO) => {\n const { store, debugLog, hasBus, sendKeys, pressAndRelease, waitForTextUnderCursorChange } = io\n\n // Implementation of setValueToHex using edit+navigation keys\n // Precalculates the entire key sequence and sends it to the macroRunner\n const setValueToHexImpl = async (targetHex: number): Promise<boolean> => {\n if (!hasBus()) return false\n\n // Validate target\n targetHex = Math.max(0, Math.min(255, targetHex))\n\n // Read current value using the atom directly (no wait needed)\n const currentText = store.get('textUnderCursor')\n const currentValue = parseHexValue(currentText)\n const isInitialDashDash = currentText?.trim() === '--'\n\n if (currentValue === null) {\n console.warn('[M8SDK] Could not parse current value under cursor:', currentText)\n return false\n }\n\n if (currentValue === targetHex && !isInitialDashDash) {\n debugLog('[M8SDK] Already at target value:', targetHex.toString(16).padStart(2, '0').toUpperCase())\n return true\n }\n\n debugLog(\n `[M8SDK] Setting value from \"${currentText?.trim() ?? 'N/A'}\" (${currentValue.toString(16).padStart(2, '0').toUpperCase()}) to ${targetHex.toString(16).padStart(2, '0').toUpperCase()}`,\n )\n\n // Starting value after entering edit mode\n let startValue = currentValue\n\n // If initial value is '--', we need to press edit first to recall the last value\n if (isInitialDashDash) {\n debugLog('[M8SDK] Initial value is \"--\", pressing edit to recall last value')\n const beforePrime = currentText\n await pressAndRelease(M8KeyMask.Edit)\n await waitForTextUnderCursorChange(beforePrime, 250)\n\n // Read the actual value after pressing edit (not always 00!)\n const newText = store.get('textUnderCursor')\n const newValue = parseHexValue(newText)\n debugLog(\n `[M8SDK] After edit press, value is now: \"${newText?.trim() ?? 'N/A'}\" (${newValue?.toString(16).padStart(2, '0').toUpperCase() ?? 'N/A'})`,\n )\n\n if (newValue === null) {\n console.warn('[M8SDK] Failed to read value after edit press on \"--\"')\n // Try to exit edit mode and return failure\n await pressAndRelease(M8KeyMask.Edit)\n return false\n }\n\n // Use the actual value after edit press as starting point\n startValue = newValue\n } else {\n // Normal case: enter edit mode\n const beforeEnterEdit = currentText\n await pressAndRelease(M8KeyMask.Edit)\n await waitForTextUnderCursorChange(beforeEnterEdit, 250)\n }\n\n // Precalculate the key sequence (starting from the actual current value after edit mode)\n const keySequence = calculateKeySequence(startValue, targetHex)\n debugLog(\n `[M8SDK] Precalculated ${keySequence.length / 2} key presses from ${startValue.toString(16).padStart(2, '0').toUpperCase()} to ${targetHex.toString(16).padStart(2, '0').toUpperCase()}`,\n )\n\n // Execute the key sequence\n for (const keys of keySequence) {\n sendKeys(keys)\n await wait(30) // Short delay between key presses\n }\n\n // Wait for final value to settle\n await waitForTextUnderCursorChange(currentText, 250)\n\n // Read final value using the atom\n const finalText = store.get('textUnderCursor')\n let finalValue = parseHexValue(finalText)\n\n // Check if precomputation succeeded, if not try iterative mode\n if (finalValue !== targetHex) {\n debugLog('[M8SDK] Precomputation missed target, falling back to iterative mode')\n\n // Continue from current position using iterative approach\n let current = finalValue ?? startValue\n const timeoutMs = 5000\n const startTime = Date.now()\n\n while (current !== targetHex && Date.now() - startTime < timeoutMs) {\n const diff = targetHex - current\n const absDiff = Math.abs(diff)\n const direction = diff > 0 ? 1 : -1\n const beforeStepText = store.get('textUnderCursor')\n\n if (absDiff >= 16) {\n // Large step\n const key = direction > 0 ? M8KeyMask.Up : M8KeyMask.Down\n const keys = M8KeyMask.Edit | key\n await pressAndRelease(keys)\n } else if (absDiff > 0) {\n // Fine adjustment\n const key = direction > 0 ? M8KeyMask.Right : M8KeyMask.Left\n const keys = M8KeyMask.Edit | key\n await pressAndRelease(keys)\n }\n\n await waitForTextUnderCursorChange(beforeStepText, 250)\n const newText = store.get('textUnderCursor')\n const newValue = parseHexValue(newText)\n if (newValue !== null) {\n current = newValue\n debugLog(`[M8SDK] Iterative: current value: ${current.toString(16).padStart(2, '0').toUpperCase()}`)\n }\n }\n\n // Read final value after iterative mode\n finalValue = parseHexValue(store.get('textUnderCursor'))\n }\n\n // Exit edit mode\n await pressAndRelease(M8KeyMask.Edit)\n\n const success = finalValue === targetHex\n debugLog(\n `[M8SDK] Value setting ${success ? 'succeeded' : 'failed'}. Final value: \"${finalText?.trim() ?? 'N/A'}\" (${finalValue?.toString(16).padStart(2, '0').toUpperCase() ?? 'N/A'})`,\n )\n\n return success\n }\n\n // Implementation of setValueToInt using edit+navigation keys\n const setValueToIntImpl = async (targetInt: number): Promise<boolean> => {\n if (!hasBus()) return false\n\n targetInt = Math.floor(targetInt)\n\n const currentText = store.get('textUnderCursor')\n const currentValue = parseIntValue(currentText)\n if (currentValue === null) {\n console.warn('[M8SDK] Could not parse current integer value under cursor:', currentText)\n return false\n }\n\n if (currentValue === targetInt) {\n return true\n }\n\n // Enter edit mode\n await pressAndRelease(M8KeyMask.Edit)\n await waitForTextUnderCursorChange(currentText, 250)\n\n let current = currentValue\n let bestValue = currentValue\n let bestDistance = Math.abs(currentValue - targetInt)\n let valueTwoStepsAgo: number | null = null\n const timeoutMs = 5000\n const startedAt = Date.now()\n\n while (current !== targetInt && Date.now() - startedAt < timeoutMs) {\n const diff = targetInt - current\n const absDiff = Math.abs(diff)\n const direction = diff > 0 ? 1 : -1\n const beforeStepText = store.get('textUnderCursor')\n const previousValue = current\n\n if (absDiff >= 16) {\n const key = direction > 0 ? M8KeyMask.Up : M8KeyMask.Down\n await pressAndRelease(M8KeyMask.Edit | key, 35)\n } else {\n const key = direction > 0 ? M8KeyMask.Right : M8KeyMask.Left\n await pressAndRelease(M8KeyMask.Edit | key, 35)\n }\n\n await waitForTextUnderCursorChange(beforeStepText, 250)\n const newValue = parseIntValue(store.get('textUnderCursor'))\n if (newValue === null || newValue === current) {\n break\n }\n\n const newDistance = Math.abs(newValue - targetInt)\n if (newDistance < bestDistance) {\n bestDistance = newDistance\n bestValue = newValue\n }\n\n // Detect 2-value bounce (A -> B -> A), especially when crossing target.\n const isOscillating = valueTwoStepsAgo !== null && newValue === valueTwoStepsAgo\n const crossesTarget = (previousValue - targetInt) * (newValue - targetInt) < 0\n\n current = newValue\n if (isOscillating && crossesTarget) {\n break\n }\n\n valueTwoStepsAgo = previousValue\n }\n\n // If exact target was not reached, move back to the nearest value observed.\n if (current !== bestValue) {\n const snapStartedAt = Date.now()\n while (current !== bestValue && Date.now() - snapStartedAt < 1500) {\n const diffToBest = bestValue - current\n const absDiffToBest = Math.abs(diffToBest)\n const directionToBest = diffToBest > 0 ? 1 : -1\n const beforeSnapStepText = store.get('textUnderCursor')\n\n if (absDiffToBest >= 16) {\n const key = directionToBest > 0 ? M8KeyMask.Up : M8KeyMask.Down\n await pressAndRelease(M8KeyMask.Edit | key, 35)\n } else {\n const key = directionToBest > 0 ? M8KeyMask.Right : M8KeyMask.Left\n await pressAndRelease(M8KeyMask.Edit | key, 35)\n }\n\n await waitForTextUnderCursorChange(beforeSnapStepText, 250)\n const snappedValue = parseIntValue(store.get('textUnderCursor'))\n if (snappedValue === null || snappedValue === current) {\n break\n }\n current = snappedValue\n }\n }\n\n await pressAndRelease(M8KeyMask.Edit)\n\n const finalValue = parseIntValue(store.get('textUnderCursor'))\n return finalValue === targetInt\n }\n\n // Implementation of setNote using edit+up/down for octave and edit+left/right for semitone\n // If the exact note is unreachable (for example due to scale), this stops on the closest note found.\n const setNoteImpl = async (targetNoteString: string): Promise<boolean> => {\n if (!hasBus()) return false\n\n const normalizedTarget = targetNoteString.trim().toUpperCase()\n\n // Handle special values: '---' (no note / delete) and 'OFF' (note off)\n // opt+edit cycles: regular note → '---' → 'OFF' → '---' → ...\n if (normalizedTarget === '---' || normalizedTarget === 'OFF') {\n const currentText = store.get('textUnderCursor')\n const currentNormalized = currentText?.trim().toUpperCase() ?? ''\n\n if (currentNormalized === normalizedTarget) return true\n\n // If current is a regular note, one opt+edit lands on '---'\n // then a second opt+edit lands on 'OFF'\n await pressAndRelease(M8KeyMask.Opt | M8KeyMask.Edit)\n await waitForTextUnderCursorChange(currentText, 250)\n\n if (normalizedTarget === 'OFF' && currentNormalized !== '---') {\n // Was a regular note: first press gave '---', need a second press for 'OFF'\n const afterFirst = store.get('textUnderCursor')\n if (afterFirst?.trim().toUpperCase() === '---') {\n await pressAndRelease(M8KeyMask.Opt | M8KeyMask.Edit)\n await waitForTextUnderCursorChange(afterFirst, 250)\n }\n }\n\n const finalText = store.get('textUnderCursor')?.trim().toUpperCase()\n debugLog('[M8SDK] setNote special value result:', finalText)\n return finalText === normalizedTarget\n }\n\n const parsedTarget = parseNoteValue(targetNoteString)\n if (!parsedTarget) {\n console.warn('[M8SDK] Invalid note format. Expected like C-1, C#1, D#A, ---, OFF:', targetNoteString)\n return false\n }\n\n let currentText = store.get('textUnderCursor')\n if (currentText?.trim() === '---' || currentText?.trim().toUpperCase() === 'OFF') {\n debugLog('[M8SDK] Initial note is \"---\" or \"OFF\", priming with edit key')\n await pressAndRelease(M8KeyMask.Edit)\n await waitForTextUnderCursorChange(currentText, 250)\n currentText = store.get('textUnderCursor')\n }\n\n let currentNote = parseNoteValue(currentText)\n if (!currentNote) {\n console.warn('[M8SDK] Could not parse current note under cursor:', currentText)\n return false\n }\n\n if (currentNote.semitoneIndex === parsedTarget.semitoneIndex) {\n return true\n }\n\n // First attempt: precomputed semitone/octave path (fast path).\n await pressAndRelease(M8KeyMask.Edit)\n await waitForTextUnderCursorChange(currentText, 250)\n\n const precomputedSequence = calculateNoteKeySequence(currentNote.semitoneIndex, parsedTarget.semitoneIndex)\n if (precomputedSequence.length > 0) {\n for (const keys of precomputedSequence) {\n sendKeys(keys)\n await wait(25)\n }\n\n await waitForTextUnderCursorChange(currentText, 280)\n const precomputedFinal = parseNoteValue(store.get('textUnderCursor'))\n if (precomputedFinal?.semitoneIndex === parsedTarget.semitoneIndex) {\n await pressAndRelease(M8KeyMask.Edit)\n return true\n }\n\n // Precomputed path can miss when scale quantization makes exact steps unreachable.\n currentNote = precomputedFinal ?? currentNote\n }\n\n // Fallback: iterative closest-note search.\n\n const seen = new Set<string>()\n seen.add(currentNote.label)\n\n const timeoutMs = 7000\n const startedAt = Date.now()\n\n while (Date.now() - startedAt < timeoutMs) {\n const diff = parsedTarget.semitoneIndex - currentNote.semitoneIndex\n if (diff === 0) {\n break\n }\n\n const useOctaveStep = Math.abs(diff) >= 12\n const positiveDirection = diff > 0\n const key = useOctaveStep ? (positiveDirection ? M8KeyMask.Up : M8KeyMask.Down) : positiveDirection ? M8KeyMask.Right : M8KeyMask.Left\n const reverseKey = useOctaveStep ? (positiveDirection ? M8KeyMask.Down : M8KeyMask.Up) : positiveDirection ? M8KeyMask.Left : M8KeyMask.Right\n\n const previousNote = currentNote\n const previousDistance = Math.abs(previousNote.semitoneIndex - parsedTarget.semitoneIndex)\n\n const beforeStepText = store.get('textUnderCursor')\n await pressAndRelease(M8KeyMask.Edit | key, 35)\n await waitForTextUnderCursorChange(beforeStepText, 250)\n\n const newNote = parseNoteValue(store.get('textUnderCursor'))\n if (!newNote) {\n break\n }\n if (newNote.label === previousNote.label) {\n break\n }\n\n currentNote = newNote\n const currentDistance = Math.abs(currentNote.semitoneIndex - parsedTarget.semitoneIndex)\n\n if (currentDistance > previousDistance) {\n // Overshot into a worse candidate, step back and stop on the closer one.\n const beforeReverseStep = store.get('textUnderCursor')\n await pressAndRelease(M8KeyMask.Edit | reverseKey, 35)\n await waitForTextUnderCursorChange(beforeReverseStep, 250)\n const steppedBack = parseNoteValue(store.get('textUnderCursor'))\n if (steppedBack) {\n currentNote = steppedBack\n }\n break\n }\n\n if (seen.has(currentNote.label)) {\n break\n }\n seen.add(currentNote.label)\n }\n\n await pressAndRelease(M8KeyMask.Edit)\n\n const finalNote = parseNoteValue(store.get('textUnderCursor'))\n return finalNote?.semitoneIndex === parsedTarget.semitoneIndex\n }\n\n // Implementation of setValueToString by anchoring at bottom and scanning forward one step at a time.\n const setValueToStringImpl = async (targetString: string, exact: boolean = true, searchInCurrentLine: boolean = false): Promise<boolean> => {\n if (!hasBus()) return false\n\n const normalizedTarget = normalizeForSearch(targetString)\n if (!normalizedTarget) {\n return false\n }\n\n const getSearchSource = (): string | null => {\n return searchInCurrentLine ? store.get('currentLine') : store.get('textUnderCursor')\n }\n\n const matches = (value: string | null): boolean => {\n const normalizedValue = normalizeForSearch(value)\n if (!normalizedValue) return false\n return exact ? normalizedValue === normalizedTarget : normalizedValue.includes(normalizedTarget)\n }\n\n // 0) Check if we're already on the right value before doing anything.\n if (matches(getSearchSource())) {\n return true\n }\n\n // Some fields start at \"---\" and need one edit press before they expose real values.\n const initialCursorText = store.get('textUnderCursor')\n if (initialCursorText?.trim() === '---') {\n debugLog('[M8SDK] Initial string value is \"---\", priming with edit key')\n await pressAndRelease(M8KeyMask.Edit)\n await waitForTextUnderCursorChange(initialCursorText, 250)\n }\n\n const beforeEnterEdit = store.get('textUnderCursor')\n await pressAndRelease(M8KeyMask.Edit)\n await waitForTextUnderCursorChange(beforeEnterEdit, 250)\n\n // Check again right after entering edit mode before going to the bottom.\n if (matches(getSearchSource())) {\n await pressAndRelease(M8KeyMask.Edit)\n return true\n }\n\n // 1) Go to the bottom: keep sending edit+down until value stops changing.\n // Guard against circular lists (no bottom) using a seed of the first few observed\n // values, and a hard timeout.\n let currentText = store.get('textUnderCursor')\n const maxBottomConfirmationRetries = 1\n let bottomNoChangeCount = 0\n const bottomStartTime = Date.now()\n const BOTTOM_TIMEOUT_MS = 2000\n const CIRCULAR_SEED_COUNT = 4\n const bottomSeedValues: string[] = []\n for (let i = 0; i < 512; i++) {\n if (Date.now() - bottomStartTime > BOTTOM_TIMEOUT_MS) {\n debugLog('[M8SDK] Timeout reached while seeking bottom of list')\n break\n }\n\n const normalizedCurrent = normalizeForSearch(currentText)\n if (normalizedCurrent) {\n if (i < CIRCULAR_SEED_COUNT) {\n bottomSeedValues.push(normalizedCurrent)\n } else if (bottomSeedValues.includes(normalizedCurrent)) {\n debugLog('[M8SDK] Circular list detected while seeking bottom, treating current position as start')\n break\n }\n }\n\n const beforeStepText = currentText\n await pressAndRelease(M8KeyMask.Edit | M8KeyMask.Down, 35)\n const nextText = await waitForTextUnderCursorChange(beforeStepText, 250)\n if (normalizeForSearch(nextText) === normalizeForSearch(currentText)) {\n bottomNoChangeCount += 1\n } else {\n bottomNoChangeCount = 0\n currentText = nextText\n }\n\n // Once bottom is detected (no change), allow only one confirmation retry.\n if (bottomNoChangeCount > maxBottomConfirmationRetries) {\n break\n }\n }\n\n // 2) Walk forward with edit+right and compare text at each step.\n const seen = new Set<string>()\n for (let i = 0; i < 1024; i++) {\n const searchSource = getSearchSource()\n if (matches(searchSource)) {\n await pressAndRelease(M8KeyMask.Edit)\n return true\n }\n\n const normalizedText = normalizeForSearch(searchSource)\n if (normalizedText) {\n if (seen.has(normalizedText)) {\n break\n }\n seen.add(normalizedText)\n }\n\n const beforeStepText = store.get('textUnderCursor')\n await pressAndRelease(M8KeyMask.Edit | M8KeyMask.Right, 35)\n const nextText = await waitForTextUnderCursorChange(beforeStepText, 250)\n if (normalizeForSearch(nextText) === normalizedText) {\n break\n }\n }\n\n await pressAndRelease(M8KeyMask.Edit)\n return false\n }\n\n return {\n setValueToHex: setValueToHexImpl,\n setValueToInt: setValueToIntImpl,\n setNote: setNoteImpl,\n setValueToString: setValueToStringImpl,\n }\n}\n","// Plays a sequence of key frames on the M8, one frame per key echo: the next frame is sent\n// when the M8 echoes the previous one. An unexpected echo (someone else pressed a key)\n// cancels the macro. Progress is published in the store's macroStatus.\n\nimport type { EngineBus } from './bus'\nimport type { KeyCommand } from './protocol'\nimport type { M8Store } from './store'\n\nexport interface MacroRunner {\n /** Starts a sequence. While one is running, the new frames replace the remaining ones. */\n start(sequence: number[]): void\n cancel(reason?: string): void\n readonly running: boolean\n dispose(): void\n}\n\nexport const createMacroRunner = ({\n bus,\n store,\n cancelOnKeydown = false,\n}: {\n bus: EngineBus\n store: M8Store\n /** Cancel the running macro on any keydown in this window (yam8d does, for its keyboard input). */\n cancelOnKeydown?: boolean\n}): MacroRunner => {\n let queue: number[] = []\n let lastSent: number | null = null\n let active = false\n\n const onKeyDown = () => {\n if (active) cancel('preempted by keyboard')\n }\n\n const activate = () => {\n if (active) return\n active = true\n bus.protocol.eventBus.on('key', onKeyEcho)\n if (cancelOnKeydown && typeof window !== 'undefined') window.addEventListener('keydown', onKeyDown)\n }\n\n const deactivate = () => {\n if (!active) return\n active = false\n bus.protocol.eventBus.off('key', onKeyEcho)\n if (cancelOnKeydown && typeof window !== 'undefined') window.removeEventListener('keydown', onKeyDown)\n }\n\n function onKeyEcho(data: KeyCommand) {\n if (!active) return\n const echoed = data.keys\n // Preempt on unexpected echo (likely external input)\n if (lastSent !== null && echoed !== lastSent) {\n cancel('external key event')\n return\n }\n const next = queue.shift()\n if (next !== undefined) {\n lastSent = next\n bus.commands.sendKeys(next)\n const sent = (store.get('macroStatus').currentStep ?? 0) + 1\n store.set('macroStatus', { running: true, currentStep: sent, sequenceLength: queue.length + sent })\n } else {\n deactivate()\n lastSent = null\n store.set('macroStatus', { running: false })\n }\n }\n\n function cancel(_reason?: string) {\n if (!active && queue.length === 0 && !store.get('macroStatus').running) return\n // Never leave a key down: the release frame was still queued\n if (lastSent) bus.commands.sendKeys(0)\n queue = []\n lastSent = null\n deactivate()\n store.set('macroStatus', { running: false })\n }\n\n const start = (sequence: number[]) => {\n queue = Array.isArray(sequence) ? sequence.slice() : []\n if (queue.length === 0) return\n store.set('macroStatus', { running: true, currentStep: 0, sequenceLength: queue.length })\n // Already running: the next echo picks up the new frames\n if (active) return\n\n activate()\n const first = queue.shift() as number\n lastSent = first\n bus.commands.sendKeys(first)\n }\n\n return {\n start,\n cancel,\n get running() {\n return active\n },\n dispose: () => {\n queue = []\n lastSent = null\n deactivate()\n },\n }\n}\n","// Observable state that the engine derives from the M8 screen stream: view, cursor, text\n// under the cursor, colours, device info and macro status. Framework-free; yam8d mirrors\n// it into jotai atoms, the standalone client reads it directly.\n\nimport type { CursorPos, CursorRect, RGB } from '../types'\n\n/** Model, font mode and the layout metrics derived from them (from the M8's system command). */\nexport interface M8SystemInfo {\n model: string\n fontMode: number\n spacingX: number\n spacingY: number\n offX: number\n offY: number\n screenWidth: number\n screenHeight: number\n rectOffset: number\n}\n\nexport interface M8CellMetrics {\n cellW: number\n cellH: number\n offX: number\n offY: number\n}\n\nexport interface M8MacroStatus {\n running: boolean\n currentStep?: number\n sequenceLength?: number\n}\n\nexport interface M8StoreState {\n /** Current view name (lowercased, cleaned). */\n viewName: string | null\n /** Raw title row text. */\n viewTitle: string | null\n minimapKey: string | null\n /** Cursor position in character grid coordinates (0..39, 0..23). */\n cursorPos: CursorPos | null\n /** Cursor rectangle in M8 pixel space. */\n cursorRect: CursorRect | null\n /** Whether the cursor is drawn as a 1px selection rectangle. */\n selectionMode: boolean\n /** Foreground colour of the text under the cursor. */\n highlightColor: RGB | null\n /** Colour of the cursor rectangle corners as last drawn. */\n cursorColor: RGB | null\n textUnderCursor: string | null\n /** The whole screen row the cursor is on. */\n currentLine: string | null\n titleColor: RGB | null\n backgroundColor: RGB | null\n deviceModel: string | null\n fontMode: number | null\n /** Pixel size and offset of one character cell (model and font dependent). */\n cellMetrics: M8CellMetrics\n systemInfo: M8SystemInfo | null\n macroStatus: M8MacroStatus\n}\n\nexport type M8StoreKey = keyof M8StoreState\n\nexport interface M8Store {\n get<K extends M8StoreKey>(key: K): M8StoreState[K]\n /** Sets a value; listeners run synchronously, and only when the value changed (by identity). */\n set<K extends M8StoreKey>(key: K, value: M8StoreState[K]): void\n /** Listens to one key. Returns the unsubscribe function. */\n sub(key: M8StoreKey, listener: () => void): () => void\n /** Listens to every key. Returns the unsubscribe function. */\n subAll(listener: (key: M8StoreKey) => void): () => void\n snapshot(): M8StoreState\n}\n\nexport const initialStoreState = (): M8StoreState => ({\n viewName: null,\n viewTitle: null,\n minimapKey: null,\n cursorPos: null,\n cursorRect: null,\n selectionMode: false,\n highlightColor: null,\n cursorColor: null,\n textUnderCursor: null,\n currentLine: null,\n titleColor: null,\n backgroundColor: null,\n deviceModel: null,\n fontMode: null,\n cellMetrics: { cellW: 12, cellH: 14, offX: 0, offY: 0 },\n systemInfo: null,\n macroStatus: { running: false },\n})\n\nexport const createM8Store = (initial: Partial<M8StoreState> = {}): M8Store => {\n const state: M8StoreState = { ...initialStoreState(), ...initial }\n const listeners = new Map<M8StoreKey, Set<() => void>>()\n const allListeners = new Set<(key: M8StoreKey) => void>()\n\n return {\n get: (key) => state[key],\n set: (key, value) => {\n if (Object.is(state[key], value)) return\n state[key] = value\n for (const listener of [...(listeners.get(key) ?? [])]) listener()\n for (const listener of [...allListeners]) listener(key)\n },\n sub: (key, listener) => {\n let set = listeners.get(key)\n if (!set) {\n set = new Set()\n listeners.set(key, set)\n }\n set.add(listener)\n return () => {\n set.delete(listener)\n }\n },\n subAll: (listener) => {\n allListeners.add(listener)\n return () => {\n allListeners.delete(listener)\n }\n },\n snapshot: () => ({ ...state }),\n }\n}\n","// A* shortest path over a directed graph of views. Edges carry their own data (direction,\n// key frames...), which the path returns step by step.\n\nexport type Direction = 'up' | 'down' | 'left' | 'right'\n\nexport type Edge = { to: string; dir?: Direction; weight?: number }\nexport type Graph<E extends Edge = Edge> = Map<string, E[]>\n\nexport type Heuristic = (a: string, b: string) => number\nexport type EdgeCostFn = (from: string, to: string, dir?: Direction) => number\n\nconst zeroHeuristic: Heuristic = () => 0\n\n// Default edge cost function: uniform cost for all edges\n// Edge-specific weights should be defined in the graph JSON file\nconst defaultEdgeCost: EdgeCostFn = () => 1\n\nexport interface PathResult<E extends Edge = Edge> {\n pages: string[] // includes start and goal\n directions: (Direction | undefined)[] // one per step\n edges: E[] // one per step\n cost: number // sum of the edge costs\n}\n\nexport function aStar<E extends Edge = Edge>(\n graph: Graph<E>,\n start: string,\n goal: string,\n heuristic: Heuristic = zeroHeuristic,\n edgeCost: EdgeCostFn = defaultEdgeCost,\n): PathResult<E> | null {\n const s = start.trim()\n const g = goal.trim()\n if (!graph.has(s) || !graph.has(g)) return null\n\n const openSet = new Set<string>([s])\n const cameFrom = new Map<string, { prev: string; via: E }>()\n const gScore = new Map<string, number>()\n const fScore = new Map<string, number>()\n\n for (const node of graph.keys()) {\n gScore.set(node, Infinity)\n fScore.set(node, Infinity)\n }\n gScore.set(s, 0)\n fScore.set(s, heuristic(s, g))\n\n function lowestF(): string | null {\n let best: string | null = null\n let bestVal = Infinity\n for (const node of openSet) {\n const val = fScore.get(node) ?? Infinity\n if (val < bestVal) {\n bestVal = val\n best = node\n }\n }\n return best\n }\n\n while (openSet.size) {\n const current = lowestF()\n if (!current) break\n if (current === g) {\n // reconstruct\n const pages: string[] = [g]\n const edges: E[] = []\n let cur = g\n while (cur !== s) {\n const link = cameFrom.get(cur)\n if (!link) break // safety\n edges.unshift(link.via)\n pages.unshift(link.prev)\n cur = link.prev\n }\n return { pages, directions: edges.map((e) => e.dir), edges, cost: gScore.get(g) ?? edges.length }\n }\n openSet.delete(current)\n const neighbors: E[] = graph.get(current) ?? []\n for (const e of neighbors) {\n const cost = e.weight ?? edgeCost(current, e.to, e.dir)\n const tentativeG = (gScore.get(current) ?? Infinity) + cost\n if (tentativeG < (gScore.get(e.to) ?? Infinity)) {\n cameFrom.set(e.to, { prev: current, via: e })\n gScore.set(e.to, tentativeG)\n fScore.set(e.to, tentativeG + heuristic(e.to, g))\n if (!openSet.has(e.to)) openSet.add(e.to)\n }\n }\n }\n\n return null\n}\n","// Navigates between M8 views by name. Each step finds a path in the view graph with A* (Shift+\r\n// direction edges, buttons that open sub-views, Option to leave them), takes its first edge and\r\n// waits to see which view the M8 shows. That outcome is remembered for the rest of the\r\n// navigation and the path is recomputed from wherever the M8 landed:\r\n// - an edge that changed nothing is blocked (Shift+Right on an empty chain row has no phrase\r\n// to go to), and the planner goes around it (Project > View Inst. Pool > Instrument);\r\n// - an edge that landed elsewhere (Option, or the Mixer's Shift+Up, which goes back to the\r\n// last view) now leads where it went.\r\n// Where a key leads can depend on the view the M8 came from (the Instrument's Shift+Left goes\r\n// back to the Instrument Pool when it was entered from there), so the search runs over\r\n// (view, previous view) pairs and outcomes are remembered per pair.\r\n// On a view that is not in the graph, Option is tried first, then Shift+Up.\r\n\r\nimport { aStar, type Direction, type Edge, type Graph } from './astar'\r\nimport { M8KeyMask } from './keys'\r\nimport type { MacroRunner } from './macroRunner'\r\nimport type { M8Store } from './store'\r\nimport { getViewGraph, getViewNames, type NavEdge } from './viewGraph'\r\n\r\nconst MAX_STEPS = 20 // Safety limit to prevent infinite loops\r\n/** After the keys of a step, no view change in this time means the edge is blocked. */\r\nconst STEP_TIMEOUT_MS = 700\r\n/** A new view name must hold this long: the title is guessed early while it is drawn. */\r\nconst SETTLE_MS = 80\r\n/** Longest cursor move to a button. */\r\nconst CURSOR_TIMEOUT_MS = 4000\r\n/** After a cursor move, before the button keys: the move's last release is still on its way (20 ms command slots). */\r\nconst CURSOR_SETTLE_MS = 120\r\n\r\n/** Shift+direction frames, used for edges without an explicit key sequence. */\r\nexport const toKeyMasks = (directions: Direction[], opts?: { shift?: boolean }) => {\r\n const withShift = !!opts?.shift\r\n const s = M8KeyMask.Shift\r\n const map: Record<Direction, number> = {\r\n up: (withShift ? s : 0) | M8KeyMask.Up,\r\n down: (withShift ? s : 0) | M8KeyMask.Down,\r\n left: (withShift ? s : 0) | M8KeyMask.Left,\r\n right: (withShift ? s : 0) | M8KeyMask.Right,\r\n }\r\n const seq: number[] = []\r\n if (withShift) seq.push(s)\r\n for (const d of directions) {\r\n seq.push(map[d], withShift ? s : 0)\r\n }\r\n // final release\r\n seq.push(0)\r\n return seq\r\n}\r\n\r\nexport interface ViewNavigation {\r\n /** Resolves true when the view is reached; false when no path is left, the step limit is hit or another navigation starts. */\r\n navigateToView(viewName: string): Promise<boolean>\r\n dispose(): void\r\n}\r\n\r\nexport interface ViewNavigationDeps {\r\n store: M8Store\r\n runner: MacroRunner\r\n /** The 24 text rows of the screen, to find buttons by their label. */\r\n getScreenLines?: () => string[]\r\n /** Moves the cursor to a text grid cell. Without it, button edges are not used. */\r\n moveCursor?: (x: number, y: number) => Promise<unknown>\r\n}\r\n\r\n// Tried in order on a view the graph does not know (or that has no way left)\r\nconst FALLBACK_EDGES = (from: string): NavEdge[] => [\r\n { from, to: '', kind: 'exit', keys: [M8KeyMask.Opt, 0] },\r\n { from, to: '', kind: 'shift', dir: 'up', keys: [M8KeyMask.Shift | M8KeyMask.Up, 0] },\r\n]\r\n\r\nconst edgeSignature = (e: NavEdge) => `${e.kind}:${e.dir ?? ''}:${e.keys.join(',')}:${e.cursor ? `${e.cursor.x},${e.cursor.y}` : ''}`\r\n\r\nconst withFinalRelease = (frames: number[]) => (frames[frames.length - 1] === 0 ? frames.slice() : [...frames, 0])\r\n\r\n/** An edge of the search over (view, previous view) states. */\r\ntype StateEdge = Edge & { nav: NavEdge }\r\nconst GOAL_STATE = '#goal'\r\nconst stateId = (view: string, prev: string) => `${view}<${prev}`\r\n\r\nconst timeout = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms))\r\n\r\nexport const createViewNavigation = ({ store, runner, getScreenLines, moveCursor }: ViewNavigationDeps): ViewNavigation => {\r\n const viewSet = getViewNames()\r\n let goal: string | null = null\r\n let steps = 0\r\n let pendingResolve: ((reached: boolean) => void) | null = null\r\n // Bumped by every navigation, so a step of an older one stops after its cursor move\r\n let session = 0\r\n /**\r\n * What each edge did during this navigation: `${from}<${previous view}|${signature}` -> the view\r\n * it reached. A blocked edge (it changed nothing) is kept under the previous view '*': that\r\n * depends on what the view shows, not on where the M8 came from.\r\n */\r\n let outcomes = new Map<string, string>()\r\n // The view the M8 showed before the current one ('' when not known), followed at all times\r\n let cameFrom = ''\r\n let lastView = store.get('viewName') || ''\r\n const unsubscribeHistory = store.sub('viewName', () => {\r\n const view = store.get('viewName') || ''\r\n if (view && view !== lastView) {\r\n if (lastView) cameFrom = lastView\r\n lastView = view\r\n }\r\n })\r\n\r\n type Step = { from: string; prev: string; edge: NavEdge; phase: 'cursor' | 'keys' }\r\n let step: Step | null = null\r\n let timer: ReturnType<typeof setTimeout> | null = null\r\n\r\n const clearTimer = () => {\r\n if (timer) clearTimeout(timer)\r\n timer = null\r\n }\r\n\r\n const resolveTarget = (name: string) => {\r\n const wanted = (name || '').toLowerCase().trim()\r\n if (!viewSet.has(wanted)) console.warn('Target not in viewlist', { goal: wanted })\r\n return wanted\r\n }\r\n\r\n const outcomeKey = (e: NavEdge, prev: string) => `${e.from}<${prev}|${edgeSignature(e)}`\r\n\r\n // The moves from `view` when the M8 came from `prev`, one per key: what this navigation saw\r\n // that key do, else the back edge to `prev`, else the key's other edges.\r\n const movesFrom = (view: string, prev: string): NavEdge[] => {\r\n const groups = new Map<string, NavEdge[]>()\r\n for (const e of getViewGraph().graph.get(view) ?? []) {\r\n if (e.kind === 'button' && !moveCursor) continue\r\n const sig = edgeSignature(e)\r\n groups.set(sig, [...(groups.get(sig) ?? []), e])\r\n }\r\n const moves: NavEdge[] = []\r\n for (const group of groups.values()) {\r\n if (outcomes.get(outcomeKey(group[0], '*')) === view) continue\r\n const seen = outcomes.get(outcomeKey(group[0], prev))\r\n if (seen !== undefined) {\r\n if (seen !== view) moves.push({ ...group[0], to: seen })\r\n continue\r\n }\r\n const back = group.find((e) => e.back && e.to === prev)\r\n if (back) {\r\n moves.push(back)\r\n continue\r\n }\r\n const others = group.filter((e) => !e.back)\r\n moves.push(...(others.length ? others : group))\r\n }\r\n return moves\r\n }\r\n\r\n // The (view, previous view) states reachable from the current one, with a goal node\r\n const stateGraph = (from: string, prev: string, target: string): Graph<StateEdge> => {\r\n const graph: Graph<StateEdge> = new Map([[GOAL_STATE, []]])\r\n const queue: [string, string][] = [[from, prev]]\r\n graph.set(stateId(from, prev), [])\r\n while (queue.length) {\r\n const [view, before] = queue.shift() as [string, string]\r\n const edges = graph.get(stateId(view, before)) as StateEdge[]\r\n if (view === target) {\r\n edges.push({ to: GOAL_STATE, weight: 0, nav: { from: view, to: view, kind: 'shift', keys: [] } })\r\n continue\r\n }\r\n for (const nav of movesFrom(view, before)) {\r\n const id = stateId(nav.to, view)\r\n edges.push({ to: id, weight: nav.weight, nav })\r\n if (!graph.has(id)) {\r\n graph.set(id, [])\r\n queue.push([nav.to, view])\r\n }\r\n }\r\n }\r\n return graph\r\n }\r\n\r\n const nextEdge = (from: string, target: string): NavEdge | null => {\r\n const res = aStar(stateGraph(from, cameFrom, target), stateId(from, cameFrom), GOAL_STATE)\r\n if (res?.edges.length) return res.edges[0].nav\r\n // A view without edges, or a sub-view with no way left: try to leave it. Main views get\r\n // no Option press (it is the mute/solo modifier there).\r\n const edges = getViewGraph().graph.get(from) ?? []\r\n if (edges.length && !edges.some((e) => e.kind === 'exit')) return null\r\n return FALLBACK_EDGES(from).find((e) => !outcomes.has(outcomeKey(e, cameFrom)) && !outcomes.has(outcomeKey(e, '*'))) ?? null\r\n }\r\n\r\n // Where to put the cursor for a button: the label nearest to the measured cell, else the cell\r\n const buttonCell = (e: NavEdge) => {\r\n const cell = e.cursor as { x: number; y: number }\r\n if (!e.label || !getScreenLines) return cell\r\n let best: { x: number; y: number; d: number } | null = null\r\n const lines = getScreenLines()\r\n // Skip the title rows and the track panel on the right\r\n for (let row = 4; row < lines.length; row++) {\r\n const line = (lines[row] ?? '').slice(0, 33)\r\n for (let i = line.indexOf(e.label); i >= 0; i = line.indexOf(e.label, i + 1)) {\r\n const d = Math.hypot(i - cell.x, (row - cell.y) * 1.5)\r\n if (!best || d < best.d) best = { x: i, y: row, d }\r\n }\r\n }\r\n return best ? { x: best.x, y: best.y } : cell\r\n }\r\n\r\n const cursorCovers = (cell: { x: number; y: number }) => {\r\n const pos = store.get('cursorPos')\r\n const rect = store.get('cursorRect')\r\n if (!pos || pos.y !== cell.y) return false\r\n const width = rect ? Math.max(1, Math.round(rect.w / store.get('cellMetrics').cellW)) : 1\r\n return cell.x >= pos.x && cell.x < pos.x + width\r\n }\r\n\r\n const complete = (reached: boolean) => {\r\n goal = null\r\n steps = 0\r\n step = null\r\n clearTimer()\r\n if (pendingResolve) {\r\n const resolve = pendingResolve\r\n pendingResolve = null\r\n resolve(reached)\r\n }\r\n }\r\n\r\n const finishStep = (reachedView: string) => {\r\n if (!step) return\r\n const { edge, from, prev } = step\r\n const moved = !!reachedView && reachedView !== from\r\n outcomes.set(outcomeKey(edge, moved ? prev : '*'), moved ? reachedView : from)\r\n step = null\r\n clearTimer()\r\n if (reachedView && reachedView !== from && edge.to && reachedView !== edge.to) {\r\n console.log('Navigation step landed elsewhere', { from, expected: edge.to, reached: reachedView })\r\n } else if (!reachedView || reachedView === from) {\r\n console.log('Navigation step blocked', { from, to: edge.to || '?', kind: edge.kind, dir: edge.dir })\r\n }\r\n executeNextStep()\r\n }\r\n\r\n const runButton = async (s: Step, mySession: number) => {\r\n const cell = buttonCell(s.edge)\r\n if (!cursorCovers(cell)) {\r\n await Promise.race([(moveCursor as NonNullable<typeof moveCursor>)(cell.x, cell.y), timeout(CURSOR_TIMEOUT_MS)])\r\n // Its release echo would otherwise reach the macro runner as a foreign key and cancel the macro\r\n await timeout(CURSOR_SETTLE_MS)\r\n }\r\n if (mySession !== session || step !== s) return\r\n // Never press a key on the wrong cell: on the Project view, Edit on TEMPO taps the tempo\r\n if (store.get('viewName') !== s.from || !cursorCovers(cell)) {\r\n console.warn('Navigation: button not reached', { from: s.from, to: s.edge.to, cell, cursor: store.get('cursorPos') })\r\n finishStep(store.get('viewName') !== s.from ? store.get('viewName') || '' : s.from)\r\n return\r\n }\r\n s.phase = 'keys'\r\n runner.cancel('navigation button')\r\n runner.start(withFinalRelease(s.edge.keys))\r\n }\r\n\r\n const executeNextStep = () => {\r\n const goalTitle = goal\r\n const startTitle = store.get('viewName') || ''\r\n\r\n if (!goalTitle) return\r\n if (!startTitle) {\r\n console.warn('Navigation aborted: no view name (a dialog?)', { goal: goalTitle })\r\n complete(false)\r\n return\r\n }\r\n if (startTitle === goalTitle) {\r\n console.log('Navigation complete:', { goal: goalTitle, steps })\r\n complete(true)\r\n return\r\n }\r\n if (steps >= MAX_STEPS) {\r\n console.warn('Navigation aborted: max steps reached', { startTitle, goalTitle, steps })\r\n complete(false)\r\n return\r\n }\r\n\r\n const edge = nextEdge(startTitle, goalTitle)\r\n if (!edge) {\r\n console.warn('No path found', { startTitle, goalTitle, step: steps })\r\n complete(false)\r\n return\r\n }\r\n\r\n steps += 1\r\n console.log('Navigation step', { step: steps, from: startTitle, to: edge.to || '?', kind: edge.kind, dir: edge.dir, goal: goalTitle })\r\n\r\n const s: Step = { from: startTitle, prev: cameFrom, edge, phase: edge.kind === 'button' ? 'cursor' : 'keys' }\r\n step = s\r\n if (edge.kind === 'button') {\r\n void runButton(s, session)\r\n return\r\n }\r\n const frames = edge.keys.length ? withFinalRelease(edge.keys) : toKeyMasks([edge.dir as Direction], { shift: true })\r\n runner.cancel('iterative navigation step')\r\n runner.start(frames)\r\n }\r\n\r\n // Once the keys of a step are played: wait for the view to change and settle, or time out\r\n const onChange = () => {\r\n if (step?.phase !== 'keys') return\r\n const running = store.get('macroStatus').running\r\n const view = store.get('viewName') || ''\r\n const changed = !!view && view !== step.from\r\n if (running && !changed) return\r\n clearTimer()\r\n if (running) {\r\n // The view changed but the macro still waits for an echo: the M8 does not always echo the\r\n // release of a key that opened another view. Release explicitly and go on.\r\n timer = setTimeout(() => {\r\n runner.cancel('navigation: no release echo')\r\n runner.start([0])\r\n finishStep(store.get('viewName') || '')\r\n }, STEP_TIMEOUT_MS)\r\n return\r\n }\r\n timer = setTimeout(() => finishStep(store.get('viewName') || ''), changed ? SETTLE_MS : STEP_TIMEOUT_MS)\r\n }\r\n const unsubscribeView = store.sub('viewName', onChange)\r\n const unsubscribeMacro = store.sub('macroStatus', onChange)\r\n\r\n return {\r\n navigateToView: (targetViewTitle: string): Promise<boolean> =>\r\n new Promise((resolve) => {\r\n const goalTitle = resolveTarget(targetViewTitle)\r\n if (!goalTitle) {\r\n resolve(false)\r\n return\r\n }\r\n\r\n // A navigation already running ends here\r\n complete(false)\r\n session += 1\r\n outcomes = new Map()\r\n pendingResolve = resolve\r\n goal = goalTitle\r\n steps = 0\r\n\r\n console.log('Starting iterative navigation', { goal: goalTitle, from: store.get('viewName') || 'unknown' })\r\n executeNextStep()\r\n }),\r\n dispose: () => {\r\n unsubscribeHistory()\r\n unsubscribeView()\r\n unsubscribeMacro()\r\n session += 1\r\n complete(false)\r\n },\r\n }\r\n}\r\n","// Everything that runs next to a connected M8, without a framework: screen state extraction,\r\n// cursor and view navigation, value editors, file browsing, MIDI clock, and the SDK host\r\n// methods and events built on them. yam8d runs one per bus and exposes it to iframes; the\r\n// standalone client runs one in the app's own page.\r\n\r\nimport type { M8HostEvents, M8HostMethods, M8State } from '../types'\r\nimport type { EngineBus } from './bus'\r\nimport { type CursorMap, createCursorMap } from './cursorMap'\r\nimport { type CursorNavigator, createCursorNavigator, type Point } from './cursorNavigator'\r\nimport { createFileBrowserSearch } from './host/fileBrowserSearch'\r\nimport { createFloatEditor } from './host/floatEditor'\r\nimport { createHostIO } from './host/hostIO'\r\nimport { createHostMethods } from './host/hostMethods'\r\nimport { getCurrentState, textGridToPixel } from './host/hostState'\r\nimport { createMidiTransport } from './host/midiTransport'\r\nimport { createValueEditors } from './host/valueEditors'\r\nimport { createMacroRunner, type MacroRunner } from './macroRunner'\r\nimport type { KeyCommand } from './protocol'\r\nimport { createM8Store, type M8Store, type M8StoreKey } from './store'\r\nimport { registerViewExtractor, type ScreenCellColors } from './viewExtractor'\r\nimport { createViewNavigation } from './viewNavigation'\r\n\r\nexport interface M8EngineOptions {\r\n debug?: boolean\r\n /** Cancel a running macro (view navigation) on any keydown in this window. Default false. */\r\n cancelMacroOnKeydown?: boolean\r\n /** Use this store instead of a new one (for tests, or to keep state across reconnects). */\r\n store?: M8Store\r\n /** Where the cursor navigator keeps the layouts it learns (share one to keep them across reconnects). */\r\n cursorMap?: CursorMap\r\n}\r\n\r\nexport interface M8Engine {\r\n readonly bus: EngineBus\r\n readonly store: M8Store\r\n /** The SDK methods, as an iframe client or the standalone client calls them. */\r\n readonly methods: M8HostMethods\r\n readonly macro: MacroRunner\r\n /** Moves the cursor to the cell at or nearest to a point in M8 pixel coordinates. Resolves true when it got there. */\r\n navigateTo(point: Point): Promise<boolean>\r\n /** Resolves true when the view is reached. */\r\n navigateToView(viewName: string): Promise<boolean>\r\n getState(): M8State\r\n getScreenLines(): string[]\r\n getScreenCellColors(): ScreenCellColors\r\n /** Listens to an SDK event. Returns the unsubscribe function. */\r\n on<K extends keyof M8HostEvents>(event: K, callback: (payload: M8HostEvents[K]) => void): () => void\r\n dispose(): void\r\n}\r\n\r\n// Store keys whose change is reported as a whole stateChanged event\r\nconst STATE_KEYS = new Set<M8StoreKey>(['macroStatus', 'deviceModel', 'fontMode', 'systemInfo', 'titleColor', 'backgroundColor', 'minimapKey'])\r\n\r\nexport const createM8Engine = (bus: EngineBus, options: M8EngineOptions = {}): M8Engine => {\r\n const store = options.store ?? createM8Store()\r\n const debugLog = (...args: unknown[]) => {\r\n if (options.debug) console.log(...args)\r\n }\r\n\r\n const listeners = new Map<keyof M8HostEvents, Set<(payload: never) => void>>()\r\n const emit = <K extends keyof M8HostEvents>(event: K, payload: M8HostEvents[K]) => {\r\n for (const cb of [...(listeners.get(event) ?? [])]) (cb as (p: M8HostEvents[K]) => void)(payload)\r\n }\r\n\r\n const extractor = registerViewExtractor(bus.protocol.eventBus, store)\r\n const macro = createMacroRunner({ bus, store, cancelOnKeydown: options.cancelMacroOnKeydown })\r\n const cursorNavigator: CursorNavigator = createCursorNavigator({\r\n bus,\r\n store,\r\n getScreenLines: extractor.getScreenLines,\r\n cursorMap: options.cursorMap ?? createCursorMap(),\r\n })\r\n const viewNavigation = createViewNavigation({\r\n store,\r\n runner: macro,\r\n getScreenLines: extractor.getScreenLines,\r\n moveCursor: (x, y) => cursorNavigator.navigateTo(textGridToPixel(store, x, y)),\r\n })\r\n const midiTransport = createMidiTransport((event) => emit('transport', event))\r\n\r\n const io = createHostIO({ getKeySink: () => bus.commands, store, debugLog, getScreenLines: extractor.getScreenLines })\r\n const methods = createHostMethods({\r\n io,\r\n editors: createValueEditors(io),\r\n setValueFloat: createFloatEditor(io),\r\n browseFile: createFileBrowserSearch(io),\r\n navigateToView: (viewName) => viewNavigation.navigateToView(viewName),\r\n navigateTo: (point) => cursorNavigator.navigateTo(point),\r\n enableMidiTransport: () => midiTransport.enable(),\r\n getScreenCellColors: extractor.getScreenCellColors,\r\n })\r\n\r\n // Store changes are collected and reported once per microtask, so the several values\r\n // the extractor sets for one cursor move become one cursorMoved event.\r\n const dirty = new Set<M8StoreKey>()\r\n let flushScheduled = false\r\n let lastView: M8HostEvents['viewChanged'] | null = null\r\n let lastCursor: M8HostEvents['cursorMoved'] | null = null\r\n let lastText: M8HostEvents['textUpdated'] | null = null\r\n\r\n const flush = () => {\r\n flushScheduled = false\r\n const keys = new Set(dirty)\r\n dirty.clear()\r\n\r\n if (keys.has('viewName') || keys.has('viewTitle')) {\r\n const view = { viewName: store.get('viewName'), viewTitle: store.get('viewTitle') }\r\n if (!lastView || lastView.viewName !== view.viewName || lastView.viewTitle !== view.viewTitle) {\r\n lastView = view\r\n debugLog('[M8SDK] View changed:', view.viewName, view.viewTitle)\r\n emit('viewChanged', view)\r\n }\r\n }\r\n\r\n if (keys.has('cursorPos') || keys.has('cursorRect') || keys.has('selectionMode')) {\r\n const cursor = { pos: store.get('cursorPos'), rect: store.get('cursorRect'), selectionMode: store.get('selectionMode') }\r\n const prev = lastCursor\r\n const same =\r\n prev &&\r\n prev.pos?.x === cursor.pos?.x &&\r\n prev.pos?.y === cursor.pos?.y &&\r\n prev.rect?.x === cursor.rect?.x &&\r\n prev.rect?.y === cursor.rect?.y &&\r\n prev.rect?.w === cursor.rect?.w &&\r\n prev.rect?.h === cursor.rect?.h &&\r\n prev.selectionMode === cursor.selectionMode\r\n if (!same) {\r\n lastCursor = cursor\r\n debugLog('[M8SDK] Cursor moved:', cursor.pos, cursor.rect, 'selection:', cursor.selectionMode)\r\n emit('cursorMoved', cursor)\r\n }\r\n }\r\n\r\n if (keys.has('textUnderCursor') || keys.has('currentLine')) {\r\n const text = { textUnderCursor: store.get('textUnderCursor'), currentLine: store.get('currentLine') }\r\n if (!lastText || lastText.textUnderCursor !== text.textUnderCursor || lastText.currentLine !== text.currentLine) {\r\n lastText = text\r\n debugLog('[M8SDK] Text updated:', text.textUnderCursor, text.currentLine)\r\n emit('textUpdated', text)\r\n }\r\n }\r\n\r\n if ([...keys].some((key) => STATE_KEYS.has(key))) {\r\n emit('stateChanged', getCurrentState(store))\r\n }\r\n }\r\n\r\n const unsubscribeStore = store.subAll((key) => {\r\n dirty.add(key)\r\n if (!flushScheduled) {\r\n flushScheduled = true\r\n queueMicrotask(flush)\r\n }\r\n })\r\n\r\n // Key echoes from the M8 (including releases, keys === 0)\r\n const onKey = (data: KeyCommand) => {\r\n debugLog('[M8SDK] Key event:', data.keys)\r\n emit('keyPressed', { keys: data.keys })\r\n }\r\n bus.protocol.eventBus.on('key', onKey)\r\n\r\n return {\r\n bus,\r\n store,\r\n methods,\r\n macro,\r\n navigateTo: (point) => cursorNavigator.navigateTo(point),\r\n navigateToView: (viewName) => viewNavigation.navigateToView(viewName),\r\n getState: () => getCurrentState(store),\r\n getScreenLines: extractor.getScreenLines,\r\n getScreenCellColors: extractor.getScreenCellColors,\r\n on: (event, callback) => {\r\n let set = listeners.get(event)\r\n if (!set) {\r\n set = new Set()\r\n listeners.set(event, set)\r\n }\r\n set.add(callback as (payload: never) => void)\r\n return () => {\r\n set.delete(callback as (payload: never) => void)\r\n }\r\n },\r\n dispose: () => {\r\n bus.protocol.eventBus.off('key', onKey)\r\n unsubscribeStore()\r\n viewNavigation.dispose()\r\n cursorNavigator.dispose()\r\n macro.dispose()\r\n midiTransport.disable()\r\n extractor.dispose()\r\n listeners.clear()\r\n },\r\n }\r\n}\r\n","{\n \"version\": \"1.0\",\n \"description\": \"Layouts of the non-grid ('parameter') views: which text at which column is an instrument type, name, parameter, option or action. Column positions were measured on the manual's screenshots (font mode 0, 40 columns) and cross-checked with the phrase view grid; descriptions come from the manual.\",\n \"notes\": {\n \"rows\": \"A row is recognised by 'match' conditions on the line text (x = column, text = exact text, regex = pattern on the value.width chars). The first matching row wins; its cells give the field columns.\",\n \"zones\": \"Views whose labels change (instrument parameters depend on the instrument type, modulation slots on the modulation type) use two label/value zones instead: the label text is read at labelX and the value at x. The label is then looked up in the vocabulary.\",\n \"formats\": \"hex = 2 hex digits; hexOption = 2 hex digits followed by the option name ('00CHORUS'); hexPair = 'XX:YY' (or 'XX/YY'); decimal / decimalPair = base-10 numbers ('120', '150:45'); float = decimal with a fraction ('-05.00'); text = free text or option name; auto = detect from the text.\",\n \"unverified\": \"Positions are read off the manual's screenshots, not from a device capture. Instrument-type specific layouts other than the Wavsynth header/parameter grid (Sampler SAMPLE row, FM operators, Hypersynth chord, MIDI CC rows) are only reached through the generic label/value zones.\"\n },\n \"views\": {\n \"project\": {\n \"rows\": [\n { \"id\": \"tempo\", \"match\": [{ \"x\": 0, \"text\": \"TEMPO\" }], \"cells\": [\n { \"x\": 13, \"width\": 6, \"key\": \"tempo\", \"type\": \"parameter\", \"format\": \"float\", \"unit\": \" BPM\", \"label\": \"Tempo (TEMPO)\", \"description\": \"Song speed in beats per minute. [EDIT] pressed rhythmically sets the tempo by tapping.\" }\n ] },\n { \"id\": \"transpose\", \"match\": [{ \"x\": 0, \"text\": \"TRANSPOSE\" }], \"cells\": [\n { \"x\": 13, \"width\": 2, \"key\": \"transpose\", \"type\": \"transpose\", \"format\": \"hex\", \"label\": \"Project Transpose (TRANSPOSE)\", \"description\": \"Transposes the whole song in semitones (same as the TSP command). Only affects instruments with TRANSP. enabled; applied when [EDIT] is released.\" }\n ] },\n { \"id\": \"groove\", \"match\": [{ \"x\": 0, \"text\": \"GROOVE\" }], \"cells\": [\n { \"x\": 13, \"width\": 14, \"key\": \"groove\", \"type\": \"option\", \"format\": \"hexOption\", \"label\": \"Default Groove (GROOVE)\", \"description\": \"Groove used when the song plays (number and name); applied when [EDIT] is released. See Groove View.\" }\n ] },\n { \"id\": \"scale\", \"match\": [{ \"x\": 0, \"text\": \"SCALE\" }], \"cells\": [\n { \"x\": 13, \"width\": 16, \"key\": \"scale\", \"type\": \"option\", \"format\": \"hexOption\", \"label\": \"Default Scale (SCALE)\", \"description\": \"Scale (number, key and name) used when the song plays; applied when [EDIT] is released. See Scale View.\" }\n ] },\n { \"id\": \"liveQuantize\", \"match\": [{ \"x\": 0, \"text\": \"LIVE QUANTIZ\" }], \"cells\": [\n { \"x\": 13, \"width\": 14, \"key\": \"liveQuantize\", \"type\": \"option\", \"format\": \"hexOption\", \"label\": \"Live Quantize (LIVE QUANTIZE)\", \"description\": \"How long a cued chain waits before starting when playing in live mode.\" }\n ] },\n { \"id\": \"midi\", \"match\": [{ \"x\": 0, \"text\": \"MIDI\" }, { \"x\": 13, \"text\": \"SETTINGS\" }], \"cells\": [\n { \"x\": 13, \"width\": 8, \"key\": \"midiSettings\", \"type\": \"action\", \"format\": \"text\", \"label\": \"MIDI Settings\", \"description\": \"Open the MIDI Settings View.\" },\n { \"x\": 22, \"width\": 8, \"key\": \"midiMappings\", \"type\": \"action\", \"format\": \"text\", \"label\": \"MIDI Mappings\", \"description\": \"Open the MIDI Mappings View.\" }\n ] },\n { \"id\": \"name\", \"match\": [{ \"x\": 0, \"text\": \"NAME\" }], \"cells\": [\n { \"x\": 13, \"width\": 12, \"key\": \"name\", \"type\": \"name\", \"format\": \"text\", \"label\": \"Song Name (NAME)\", \"description\": \"Name of the current song, entered before saving.\" }\n ] },\n { \"id\": \"file\", \"match\": [{ \"x\": 0, \"text\": \"PROJECT\" }, { \"x\": 13, \"text\": \"LOAD\" }], \"cells\": [\n { \"x\": 13, \"width\": 4, \"key\": \"load\", \"type\": \"action\", \"format\": \"text\", \"label\": \"Load Song\", \"description\": \"Load a song from the SD card.\" },\n { \"x\": 18, \"width\": 4, \"key\": \"save\", \"type\": \"action\", \"format\": \"text\", \"label\": \"Save Song\", \"description\": \"Save the current song.\" },\n { \"x\": 23, \"width\": 3, \"key\": \"new\", \"type\": \"action\", \"format\": \"text\", \"label\": \"New Song\", \"description\": \"Create a new song.\" }\n ] },\n { \"id\": \"export\", \"match\": [{ \"x\": 0, \"text\": \"EXPORT/SHARE\" }], \"cells\": [\n { \"x\": 13, \"width\": 6, \"key\": \"render\", \"type\": \"action\", \"format\": \"text\", \"label\": \"Render\", \"description\": \"Open the Render View to render audio.\" },\n { \"x\": 20, \"width\": 6, \"key\": \"bundle\", \"type\": \"action\", \"format\": \"text\", \"label\": \"Bundle\", \"description\": \"Bundle the song with the instruments and samples it uses.\" }\n ] },\n { \"id\": \"clearUnused\", \"match\": [{ \"x\": 0, \"text\": \"CLEAR UNUSED\" }], \"cells\": [\n { \"x\": 13, \"width\": 7, \"key\": \"clearPhrases\", \"type\": \"action\", \"format\": \"text\", \"label\": \"Clear Unused Phrases\", \"description\": \"Frees chains and phrases that are not used in the song.\" },\n { \"x\": 21, \"width\": 8, \"key\": \"clearInstTbl\", \"type\": \"action\", \"format\": \"text\", \"label\": \"Clear Unused Instruments/Tables\", \"description\": \"Frees instruments, EQs and tables that are not used in the song.\" }\n ] },\n { \"id\": \"instPool\", \"match\": [{ \"x\": 0, \"text\": \"INST. POOL\" }], \"cells\": [\n { \"x\": 13, \"width\": 15, \"key\": \"viewInstPool\", \"type\": \"action\", \"format\": \"text\", \"label\": \"View Instrument Pool\", \"description\": \"Go to the Instrument Pool View.\" }\n ] },\n { \"id\": \"timeStats\", \"match\": [{ \"x\": 0, \"text\": \"TIME STATS\" }], \"cells\": [\n { \"x\": 13, \"width\": 15, \"key\": \"viewTimeStats\", \"type\": \"action\", \"format\": \"text\", \"label\": \"View Time Stats\", \"description\": \"Open the Time Stats View (time spent on the song).\" }\n ] },\n { \"id\": \"system\", \"match\": [{ \"x\": 0, \"text\": \"SYSTEM\" }, { \"x\": 13, \"text\": \"SETTINGS\" }], \"cells\": [\n { \"x\": 13, \"width\": 8, \"key\": \"systemSettings\", \"type\": \"action\", \"format\": \"text\", \"label\": \"System Settings\", \"description\": \"Go to the System Settings View.\" },\n { \"x\": 22, \"width\": 9, \"key\": \"usbDrive\", \"type\": \"action\", \"format\": \"text\", \"label\": \"USB Drive\", \"description\": \"Puts the M8 in USB drive mode so SD card files can be transferred to a computer.\" }\n ] }\n ]\n },\n\n \"systemsettings\": {\n \"rows\": [\n { \"id\": \"backlight\", \"match\": [{ \"x\": 0, \"text\": \"BACKLIGHT\" }], \"cells\": [\n { \"x\": 15, \"width\": 2, \"key\": \"backlight\", \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Backlight (BACKLIGHT)\", \"description\": \"Backlight LED brightness of the display.\" }\n ] },\n { \"id\": \"fontOptions\", \"match\": [{ \"x\": 0, \"text\": \"FONT OPTIONS\" }], \"cells\": [\n { \"x\": 15, \"width\": 15, \"key\": \"fontOptions\", \"type\": \"option\", \"format\": \"text\", \"label\": \"Font Options (FONT OPTIONS)\", \"description\": \"Font settings, including a large font mode for readability.\" }\n ] },\n { \"id\": \"theme\", \"match\": [{ \"x\": 0, \"text\": \"THEME\" }], \"cells\": [\n { \"x\": 15, \"width\": 10, \"key\": \"editTheme\", \"type\": \"action\", \"format\": \"text\", \"label\": \"Theme (EDIT THEME)\", \"description\": \"Open the Theme View.\" }\n ] },\n { \"id\": \"notePreview\", \"match\": [{ \"x\": 0, \"text\": \"NOTE PREVIEW\" }], \"cells\": [\n { \"x\": 15, \"width\": 3, \"key\": \"notePreview\", \"type\": \"onOff\", \"format\": \"text\", \"label\": \"Note Preview (NOTE PREVIEW)\", \"description\": \"Plays the note when editing notes in the phrase view.\" }\n ] },\n { \"id\": \"recCountIn\", \"match\": [{ \"x\": 0, \"regex\": \"^REC\\\\.\\\\s?(COUNT IN|METRONOME)\\\\s*$\", \"width\": 13 }], \"cells\": [\n { \"x\": 15, \"width\": 6, \"key\": \"recCountIn\", \"type\": \"option\", \"format\": \"hexOption\", \"label\": \"Recording Count-in (REC. COUNT IN)\", \"description\": \"Number of phrases/measures of metronome count-in before recording audio in the Sample Editor or Selection to Sample.\" }\n ] },\n { \"id\": \"metronomeVol\", \"match\": [{ \"x\": 0, \"text\": \"METRONOME VOL\" }], \"cells\": [\n { \"x\": 15, \"width\": 2, \"key\": \"metronomeVol\", \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Metronome Volume (METRONOME VOL)\", \"description\": \"Volume of the metronome sound (edit /System/Metronome.m8i to change the sound).\" }\n ] },\n { \"id\": \"songTemplate\", \"match\": [{ \"x\": 0, \"text\": \"SONG TEMPLATE\" }], \"cells\": [\n { \"x\": 15, \"width\": 4, \"key\": \"songTemplateSave\", \"type\": \"action\", \"format\": \"text\", \"label\": \"Song Template (SAVE)\", \"description\": \"Use the currently opened song as the template for new songs.\" },\n { \"x\": 20, \"width\": 5, \"key\": \"songTemplateClear\", \"type\": \"action\", \"format\": \"text\", \"label\": \"Song Template (CLEAR)\", \"description\": \"Clear the song template.\" }\n ] },\n { \"id\": \"usbAudioMode\", \"match\": [{ \"x\": 0, \"text\": \"USB AUDIO MODE\" }], \"cells\": [\n { \"x\": 15, \"width\": 14, \"key\": \"usbAudioMode\", \"type\": \"option\", \"format\": \"text\", \"label\": \"USB Audio Mode (USB AUDIO MODE)\", \"description\": \"USB output as stereo or multi-channel (multi-channel sends all 8 tracks and the effects individually, 12 stereo channels).\",\n \"options\": { \"STEREO OUT\": \"Stereo mix over USB.\" } }\n ] },\n { \"id\": \"usbMainOut\", \"match\": [{ \"x\": 0, \"text\": \"USB MAIN OUT\" }], \"cells\": [\n { \"x\": 15, \"width\": 6, \"key\": \"usbMainOut\", \"type\": \"option\", \"format\": \"text\", \"label\": \"USB Main Out (USB MAIN OUT)\", \"description\": \"Routing behaviour of USB audio (see the USB audio routing options).\" }\n ] },\n { \"id\": \"lineInGate\", \"match\": [{ \"x\": 0, \"text\": \"LINE-IN GATE\" }], \"cells\": [\n { \"x\": 15, \"width\": 6, \"key\": \"lineInGate\", \"type\": \"option\", \"format\": \"text\", \"label\": \"Line-in Gate (LINE-IN GATE)\", \"description\": \"Noise gate threshold for the line input, adjustable from -70 dB to -10 dB (OFF disables it).\" }\n ] },\n { \"id\": \"splashScreen\", \"match\": [{ \"x\": 0, \"text\": \"SPLASH SCREEN\" }], \"cells\": [\n { \"x\": 15, \"width\": 3, \"key\": \"splashScreen\", \"type\": \"onOff\", \"format\": \"text\", \"label\": \"Splash Screen (SPLASH SCREEN)\", \"description\": \"Show the M8 logo and play the start-up sound when turning on the device.\" }\n ] },\n { \"id\": \"hpProtection\", \"match\": [{ \"x\": 0, \"text\": \"HP PROTECTION\" }], \"cells\": [\n { \"x\": 15, \"width\": 3, \"key\": \"hpProtection\", \"type\": \"onOff\", \"format\": \"text\", \"label\": \"Headphone Protection (HP PROTECTION)\", \"description\": \"Limits the headphone output volume to F0 when a pack is unplugged and re-inserted.\" }\n ] },\n { \"id\": \"keyDelayRep\", \"match\": [{ \"x\": 0, \"text\": \"KEY DELAY:REP\" }], \"cells\": [\n { \"x\": 15, \"width\": 7, \"key\": \"keyDelayRep\", \"type\": \"parameter\", \"format\": \"decimalPair\", \"unit\": \" ms\", \"parts\": [\"delay\", \"repeat\"], \"label\": \"Key Delay:Repeat (KEY DELAY:REP)\", \"description\": \"Key repeat timing in milliseconds: delay before a held key starts repeating, then the time between repeats.\" }\n ] },\n { \"id\": \"battCharging\", \"match\": [{ \"x\": 0, \"regex\": \"^BATT\\\\.\\\\s?(CHARGING|STATUS)\\\\s*$\", \"width\": 14 }], \"cells\": [\n { \"x\": 15, \"width\": 14, \"key\": \"battCharging\", \"type\": \"option\", \"format\": \"text\", \"label\": \"Battery Charging (BATT. CHARGING / STATUS)\", \"description\": \"Whether the battery charges while the M8 is on and USB is connected (turn off to reduce power drawn from the host); also shows the current charging status.\" }\n ] }\n ]\n },\n\n \"effectsettings\": {\n \"sections\": [\"MODFX\", \"DELAY\", \"REVERB\"],\n \"rows\": [\n { \"id\": \"modfxType\", \"section\": \"MODFX\", \"match\": [{ \"x\": 7, \"text\": \"MOD TYPE\" }], \"cells\": [\n { \"x\": 21, \"width\": 10, \"key\": \"modfxType\", \"type\": \"option\", \"format\": \"hexOption\", \"label\": \"ModFX Type (MOD TYPE)\", \"description\": \"ModFX effect: Chorus, Phaser or Flanger (also sets the phase position).\",\n \"options\": { \"CHORUS\": \"Stereo chorus.\", \"PHASER\": \"Stereo phaser.\", \"FLANGER\": \"Stereo flanger.\" } }\n ] },\n { \"id\": \"modfxInputEq\", \"section\": \"MODFX\", \"match\": [{ \"x\": 7, \"text\": \"INPUT EQ\" }], \"cells\": [\n { \"x\": 21, \"width\": 2, \"key\": \"modfxInputEq\", \"type\": \"action\", \"format\": \"text\", \"label\": \"Input EQ (INPUT EQ)\", \"description\": \"Open the EQ editor for this effect's input.\" }\n ] },\n { \"id\": \"modfxDepthFreq\", \"section\": \"MODFX\", \"match\": [{ \"x\": 7, \"text\": \"MOD DEPTH:FRQ\" }], \"cells\": [\n { \"x\": 21, \"width\": 5, \"key\": \"modfxDepthFreq\", \"type\": \"parameter\", \"format\": \"hexPair\", \"parts\": [\"mod depth\", \"mod frequency\"], \"label\": \"ModFX Mod Depth:Frequency (MOD DEPTH:FRQ)\", \"description\": \"Amount of modulation in the stereo signal (first value) and speed of the modulation (second value).\" }\n ] },\n { \"id\": \"modfxWidth\", \"section\": \"MODFX\", \"match\": [{ \"x\": 7, \"text\": \"STEREO WIDTH\" }], \"cells\": [\n { \"x\": 21, \"width\": 2, \"key\": \"modfxWidth\", \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Stereo Width (STEREO WIDTH)\", \"description\": \"Stereo width: 00 is mono, FF is stereo.\" }\n ] },\n { \"id\": \"modfxReverbSend\", \"section\": \"MODFX\", \"match\": [{ \"x\": 7, \"text\": \"REVERB SEND\" }], \"cells\": [\n { \"x\": 21, \"width\": 2, \"key\": \"modfxReverbSend\", \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Reverb Send (REVERB SEND)\", \"description\": \"Amount of this effect sent to the reverb.\" }\n ] },\n\n { \"id\": \"delayInputEq\", \"section\": \"DELAY\", \"match\": [{ \"x\": 7, \"text\": \"INPUT EQ\" }], \"cells\": [\n { \"x\": 21, \"width\": 2, \"key\": \"delayInputEq\", \"type\": \"action\", \"format\": \"text\", \"label\": \"Input EQ (INPUT EQ)\", \"description\": \"Open the EQ editor for this effect's input.\" }\n ] },\n { \"id\": \"delayTime\", \"section\": \"DELAY\", \"match\": [{ \"x\": 7, \"text\": \"TIME L:R\" }], \"cells\": [\n { \"x\": 21, \"width\": 5, \"key\": \"delayTime\", \"type\": \"parameter\", \"format\": \"hexPair\", \"parts\": [\"left time\", \"right time\"], \"label\": \"Delay Time L:R (TIME L:R)\", \"description\": \"Delay time for the left and right channels: first digit in beats, second digit in fractions of a beat (40 is 4 beats).\" }\n ] },\n { \"id\": \"delayFeedback\", \"section\": \"DELAY\", \"match\": [{ \"x\": 7, \"text\": \"FEEDBACK\" }], \"cells\": [\n { \"x\": 21, \"width\": 2, \"key\": \"delayFeedback\", \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Feedback (FEEDBACK)\", \"description\": \"Amount of the delayed signal fed back into the delay.\" }\n ] },\n { \"id\": \"delayWidth\", \"section\": \"DELAY\", \"match\": [{ \"x\": 7, \"text\": \"STEREO WIDTH\" }], \"cells\": [\n { \"x\": 21, \"width\": 2, \"key\": \"delayWidth\", \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Stereo Width (STEREO WIDTH)\", \"description\": \"Stereo width: 00 is mono, FF is stereo.\" }\n ] },\n { \"id\": \"delayReverbSend\", \"section\": \"DELAY\", \"match\": [{ \"x\": 7, \"text\": \"REVERB SEND\" }], \"cells\": [\n { \"x\": 21, \"width\": 2, \"key\": \"delayReverbSend\", \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Reverb Send (REVERB SEND)\", \"description\": \"Amount of this effect sent to the reverb.\" }\n ] },\n\n { \"id\": \"reverbInputEq\", \"section\": \"REVERB\", \"match\": [{ \"x\": 7, \"text\": \"INPUT EQ\" }], \"cells\": [\n { \"x\": 21, \"width\": 2, \"key\": \"reverbInputEq\", \"type\": \"action\", \"format\": \"text\", \"label\": \"Input EQ (INPUT EQ)\", \"description\": \"Open the EQ editor for this effect's input.\" }\n ] },\n { \"id\": \"reverbRoomSize\", \"section\": \"REVERB\", \"match\": [{ \"x\": 7, \"text\": \"ROOM SIZE\" }], \"cells\": [\n { \"x\": 21, \"width\": 2, \"key\": \"reverbRoomSize\", \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Room Size (ROOM SIZE)\", \"description\": \"Size of the reverb room.\" }\n ] },\n { \"id\": \"reverbDecayShimmer\", \"section\": \"REVERB\", \"match\": [{ \"x\": 7, \"text\": \"DECAY:SHIMMER\" }], \"cells\": [\n { \"x\": 21, \"width\": 5, \"key\": \"reverbDecayShimmer\", \"type\": \"parameter\", \"format\": \"hexPair\", \"parts\": [\"decay\", \"shimmer\"], \"label\": \"Reverb Decay:Shimmer (DECAY:SHIMMER)\", \"description\": \"Reverb decay time (first value) and shimmer amount (second value).\" }\n ] },\n { \"id\": \"reverbDepthFreq\", \"section\": \"REVERB\", \"match\": [{ \"x\": 7, \"text\": \"MOD DEPTH:FRQ\" }], \"cells\": [\n { \"x\": 21, \"width\": 5, \"key\": \"reverbDepthFreq\", \"type\": \"parameter\", \"format\": \"hexPair\", \"parts\": [\"mod depth\", \"mod frequency\"], \"label\": \"Reverb Mod Depth:Frequency (MOD DEPTH:FRQ)\", \"description\": \"Reverb modulation depth (first value) and frequency (second value).\" }\n ] },\n { \"id\": \"reverbWidth\", \"section\": \"REVERB\", \"match\": [{ \"x\": 7, \"text\": \"STEREO WIDTH\" }], \"cells\": [\n { \"x\": 21, \"width\": 2, \"key\": \"reverbWidth\", \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Stereo Width (STEREO WIDTH)\", \"description\": \"Stereo width: 00 is mono, FF is stereo.\" }\n ] }\n ]\n },\n\n \"mixer\": {\n \"rows\": [\n { \"id\": \"speaker\", \"match\": [{ \"x\": 0, \"text\": \"SPEAKER\" }], \"cells\": [\n { \"x\": 12, \"width\": 2, \"key\": \"speakerVol\", \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Speaker Volume (SPEAKER VOL)\", \"description\": \"Volume of the built-in speaker.\" }\n ] },\n { \"id\": \"outputVol\", \"match\": [{ \"x\": 0, \"text\": \"OUTPUT VOL\" }], \"cells\": [\n { \"x\": 12, \"width\": 2, \"key\": \"outputVol\", \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Output Volume (OUTPUT VOL)\", \"description\": \"Volume of the audio output (headphones / line out).\" }\n ] },\n { \"id\": \"tracks\", \"match\": [{ \"x\": 0, \"regex\": \"^[0-9A-F]{2}$\", \"width\": 2 }, { \"x\": 21, \"regex\": \"^[0-9A-F]{2}$\", \"width\": 2 }], \"cells\": [\n { \"x\": 0, \"width\": 2, \"key\": \"track1\", \"type\": \"volume\", \"format\": \"hex\", \"label\": \"Track 1 Volume\", \"meta\": { \"trackIndex\": 1 }, \"description\": \"Volume of track 1. Instrument volumes are applied on top; the track's send levels are set in the instruments.\" },\n { \"x\": 3, \"width\": 2, \"key\": \"track2\", \"type\": \"volume\", \"format\": \"hex\", \"label\": \"Track 2 Volume\", \"meta\": { \"trackIndex\": 2 }, \"description\": \"Volume of track 2.\" },\n { \"x\": 6, \"width\": 2, \"key\": \"track3\", \"type\": \"volume\", \"format\": \"hex\", \"label\": \"Track 3 Volume\", \"meta\": { \"trackIndex\": 3 }, \"description\": \"Volume of track 3.\" },\n { \"x\": 9, \"width\": 2, \"key\": \"track4\", \"type\": \"volume\", \"format\": \"hex\", \"label\": \"Track 4 Volume\", \"meta\": { \"trackIndex\": 4 }, \"description\": \"Volume of track 4.\" },\n { \"x\": 12, \"width\": 2, \"key\": \"track5\", \"type\": \"volume\", \"format\": \"hex\", \"label\": \"Track 5 Volume\", \"meta\": { \"trackIndex\": 5 }, \"description\": \"Volume of track 5.\" },\n { \"x\": 15, \"width\": 2, \"key\": \"track6\", \"type\": \"volume\", \"format\": \"hex\", \"label\": \"Track 6 Volume\", \"meta\": { \"trackIndex\": 6 }, \"description\": \"Volume of track 6.\" },\n { \"x\": 18, \"width\": 2, \"key\": \"track7\", \"type\": \"volume\", \"format\": \"hex\", \"label\": \"Track 7 Volume\", \"meta\": { \"trackIndex\": 7 }, \"description\": \"Volume of track 7.\" },\n { \"x\": 21, \"width\": 2, \"key\": \"track8\", \"type\": \"volume\", \"format\": \"hex\", \"label\": \"Track 8 Volume\", \"meta\": { \"trackIndex\": 8 }, \"description\": \"Volume of track 8.\" }\n ] },\n { \"id\": \"eq\", \"match\": [{ \"x\": 27, \"text\": \"EQ\" }], \"cells\": [\n { \"x\": 27, \"width\": 2, \"key\": \"mixEq\", \"type\": \"action\", \"format\": \"text\", \"label\": \"Main Mix EQ (EQ)\", \"description\": \"Open the main mix EQ editor ([EDIT] or [SHIFT]+[RIGHT]).\" }\n ] },\n { \"id\": \"levels\", \"match\": [{ \"x\": 0, \"regex\": \"^[0-9A-F]{2}$\", \"width\": 2 }, { \"x\": 3, \"regex\": \"^[0-9A-F]{2}$\", \"width\": 2 }, { \"x\": 6, \"regex\": \"^[0-9A-F]{2}$\", \"width\": 2 }, { \"x\": 12, \"regex\": \"^[0-9A-F]{2}$\", \"width\": 2 }], \"cells\": [\n { \"x\": 0, \"width\": 2, \"key\": \"modfxVol\", \"type\": \"volume\", \"format\": \"hex\", \"label\": \"ModFX Volume (MX)\", \"description\": \"Output level of the ModFX send effect.\" },\n { \"x\": 3, \"width\": 2, \"key\": \"delayVol\", \"type\": \"volume\", \"format\": \"hex\", \"label\": \"Delay Volume (DE)\", \"description\": \"Output level of the delay send effect.\" },\n { \"x\": 6, \"width\": 2, \"key\": \"reverbVol\", \"type\": \"volume\", \"format\": \"hex\", \"label\": \"Reverb Volume (RE)\", \"description\": \"Output level of the reverb send effect.\" },\n { \"x\": 12, \"width\": 2, \"key\": \"inputVol\", \"type\": \"volume\", \"format\": \"hex\", \"label\": \"Analog Input Volume (INPUT)\", \"description\": \"Volume of the analog input for monitoring. In dual-mono mode this is the left channel.\" },\n { \"x\": 15, \"width\": 2, \"key\": \"inputVol2\", \"type\": \"volume\", \"format\": \"hex\", \"label\": \"Analog Input Volume, 2nd channel\", \"description\": \"Enables dual-mono input mode when set (right channel volume); clear with [EDIT]+[OPTION] to disable.\" },\n { \"x\": 18, \"width\": 2, \"key\": \"usbVol\", \"type\": \"volume\", \"format\": \"hex\", \"label\": \"USB Input Volume (USB)\", \"description\": \"Volume of anything the USB host computer sends to the M8.\" },\n { \"x\": 27, \"width\": 2, \"key\": \"mainVol\", \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Main Volume (MIX)\", \"description\": \"Main song volume, applied after the limiter, DJ filter and OTT. [EDIT] or [SHIFT]+[RIGHT] opens the Mix Scope.\" }\n ] },\n { \"id\": \"limiter\", \"match\": [{ \"x\": 0, \"text\": \"MX\" }, { \"x\": 3, \"text\": \"DE\" }, { \"x\": 6, \"text\": \"RE\" }], \"cells\": [\n { \"x\": 27, \"width\": 2, \"key\": \"limiter\", \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Limiter (LIM)\", \"description\": \"Main mix limiter amount; the limiter is engaged when the value is above 00. [EDIT] or [SHIFT]+[RIGHT] opens the Limiter Scope.\" }\n ] },\n { \"id\": \"sendModfx\", \"match\": [{ \"x\": 9, \"text\": \"MX\" }], \"cells\": [\n { \"x\": 12, \"width\": 2, \"key\": \"inputModfx\", \"type\": \"volume\", \"format\": \"hex\", \"label\": \"Analog Input ModFX Send (INPUT MX)\", \"description\": \"How much of the analog input goes to the ModFX effect.\" },\n { \"x\": 18, \"width\": 2, \"key\": \"usbModfx\", \"type\": \"volume\", \"format\": \"hex\", \"label\": \"USB Input ModFX Send (USB MX)\", \"description\": \"How much of the USB input goes to the ModFX effect.\" },\n { \"x\": 27, \"width\": 2, \"key\": \"djFilter\", \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"DJ Filter (DJF)\", \"description\": \"DJ filter cutoff: 80 is off, below 80 low-pass (or band-stop), above 80 high-pass. Mode and options are in the Scope View.\" }\n ] },\n { \"id\": \"sendDelay\", \"match\": [{ \"x\": 9, \"text\": \"DE\" }], \"cells\": [\n { \"x\": 12, \"width\": 2, \"key\": \"inputDelay\", \"type\": \"volume\", \"format\": \"hex\", \"label\": \"Analog Input Delay Send (INPUT DE)\", \"description\": \"How much of the analog input goes to the delay effect.\" },\n { \"x\": 18, \"width\": 2, \"key\": \"usbDelay\", \"type\": \"volume\", \"format\": \"hex\", \"label\": \"USB Input Delay Send (USB DE)\", \"description\": \"How much of the USB input goes to the delay effect.\" },\n { \"x\": 27, \"width\": 2, \"key\": \"ott\", \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"OTT (OTT)\", \"description\": \"Over The Top multi-band compression mixed in parallel: 00 to 80 mixes it in; from 80 and above the main mix is faded out.\" }\n ] },\n { \"id\": \"sendReverb\", \"match\": [{ \"x\": 9, \"text\": \"RE\" }], \"cells\": [\n { \"x\": 12, \"width\": 2, \"key\": \"inputReverb\", \"type\": \"volume\", \"format\": \"hex\", \"label\": \"Analog Input Reverb Send (INPUT RE)\", \"description\": \"How much of the analog input goes to the reverb effect.\" },\n { \"x\": 18, \"width\": 2, \"key\": \"usbReverb\", \"type\": \"volume\", \"format\": \"hex\", \"label\": \"USB Input Reverb Send (USB RE)\", \"description\": \"How much of the USB input goes to the reverb effect.\" }\n ] }\n ]\n },\n\n \"mixeq\": {\n \"rows\": [\n { \"id\": \"gain\", \"match\": [{ \"x\": 0, \"text\": \"GAIN\" }], \"cells\": [\n { \"x\": 5, \"width\": 6, \"key\": \"lowGain\", \"type\": \"parameter\", \"format\": \"float\", \"unit\": \" dB\", \"label\": \"LOW Gain\", \"description\": \"Boost (positive) or cut (negative) applied by the LOW band, in decibels.\" },\n { \"x\": 16, \"width\": 6, \"key\": \"midGain\", \"type\": \"parameter\", \"format\": \"float\", \"unit\": \" dB\", \"label\": \"MID Gain\", \"description\": \"Boost (positive) or cut (negative) applied by the MID band, in decibels.\" },\n { \"x\": 27, \"width\": 6, \"key\": \"highGain\", \"type\": \"parameter\", \"format\": \"float\", \"unit\": \" dB\", \"label\": \"HIGH Gain\", \"description\": \"Boost (positive) or cut (negative) applied by the HIGH band, in decibels.\" }\n ] },\n { \"id\": \"freq\", \"match\": [{ \"x\": 0, \"text\": \"FREQ\" }], \"cells\": [\n { \"x\": 6, \"width\": 5, \"key\": \"lowFreq\", \"type\": \"parameter\", \"format\": \"decimal\", \"unit\": \" Hz\", \"label\": \"LOW Frequency\", \"description\": \"Centre (or corner) frequency of the LOW band.\" },\n { \"x\": 17, \"width\": 5, \"key\": \"midFreq\", \"type\": \"parameter\", \"format\": \"decimal\", \"unit\": \" Hz\", \"label\": \"MID Frequency\", \"description\": \"Centre (or corner) frequency of the MID band.\" },\n { \"x\": 28, \"width\": 5, \"key\": \"highFreq\", \"type\": \"parameter\", \"format\": \"decimal\", \"unit\": \" Hz\", \"label\": \"HIGH Frequency\", \"description\": \"Centre (or corner) frequency of the HIGH band.\" }\n ] },\n { \"id\": \"q\", \"match\": [{ \"x\": 0, \"text\": \"Q\" }, { \"x\": 1, \"regex\": \"^\\\\s?$\", \"width\": 1 }], \"cells\": [\n { \"x\": 6, \"width\": 3, \"key\": \"lowQ\", \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"LOW Q\", \"description\": \"Quality factor (bandwidth) of the LOW band: higher values narrow it.\" },\n { \"x\": 17, \"width\": 3, \"key\": \"midQ\", \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"MID Q\", \"description\": \"Quality factor (bandwidth) of the MID band: higher values narrow it.\" },\n { \"x\": 28, \"width\": 3, \"key\": \"highQ\", \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"HIGH Q\", \"description\": \"Quality factor (bandwidth) of the HIGH band: higher values narrow it.\" }\n ] },\n { \"id\": \"type\", \"match\": [{ \"x\": 0, \"text\": \"TYPE\" }], \"cells\": [\n { \"x\": 6, \"width\": 9, \"key\": \"lowType\", \"type\": \"option\", \"format\": \"text\", \"label\": \"LOW Type\", \"description\": \"Filter shape of the LOW band.\", \"options\": { \"LOWCUT\": \"Removes frequencies below the cutoff.\", \"LOWSHELF\": \"Boosts or cuts frequencies below the shelf frequency.\", \"BELL\": \"Peaking filter: boosts or cuts around the centre frequency.\", \"BANDPASS\": \"Passes frequencies around the centre frequency, attenuating both sides.\", \"HI.SHELF\": \"Boosts or cuts frequencies above the shelf frequency.\", \"HI.CUT\": \"Attenuates or removes frequencies above the cutoff.\", \"ALLPASS\": \"Passes all frequencies at equal gain while shifting their phase.\" } },\n { \"x\": 17, \"width\": 9, \"key\": \"midType\", \"type\": \"option\", \"format\": \"text\", \"label\": \"MID Type\", \"description\": \"Filter shape of the MID band.\", \"options\": { \"LOWCUT\": \"Removes frequencies below the cutoff.\", \"LOWSHELF\": \"Boosts or cuts frequencies below the shelf frequency.\", \"BELL\": \"Peaking filter: boosts or cuts around the centre frequency.\", \"BANDPASS\": \"Passes frequencies around the centre frequency, attenuating both sides.\", \"HI.SHELF\": \"Boosts or cuts frequencies above the shelf frequency.\", \"HI.CUT\": \"Attenuates or removes frequencies above the cutoff.\", \"ALLPASS\": \"Passes all frequencies at equal gain while shifting their phase.\" } },\n { \"x\": 28, \"width\": 9, \"key\": \"highType\", \"type\": \"option\", \"format\": \"text\", \"label\": \"HIGH Type\", \"description\": \"Filter shape of the HIGH band.\", \"options\": { \"LOWCUT\": \"Removes frequencies below the cutoff.\", \"LOWSHELF\": \"Boosts or cuts frequencies below the shelf frequency.\", \"BELL\": \"Peaking filter: boosts or cuts around the centre frequency.\", \"BANDPASS\": \"Passes frequencies around the centre frequency, attenuating both sides.\", \"HI.SHELF\": \"Boosts or cuts frequencies above the shelf frequency.\", \"HI.CUT\": \"Attenuates or removes frequencies above the cutoff.\", \"ALLPASS\": \"Passes all frequencies at equal gain while shifting their phase.\" } }\n ] },\n { \"id\": \"mode\", \"match\": [{ \"x\": 0, \"text\": \"MODE\" }], \"cells\": [\n { \"x\": 6, \"width\": 6, \"key\": \"lowMode\", \"type\": \"option\", \"format\": \"text\", \"label\": \"LOW Mode\", \"description\": \"How the LOW band is applied to the stereo signal.\", \"options\": { \"STEREO\": \"Processes left and right identically.\", \"MID\": \"Affects only the mid (centre) information.\", \"SIDE\": \"Affects only the side information (difference between left and right).\", \"LEFT\": \"Affects only the left channel.\", \"RIGHT\": \"Affects only the right channel.\" } },\n { \"x\": 17, \"width\": 6, \"key\": \"midMode\", \"type\": \"option\", \"format\": \"text\", \"label\": \"MID Mode\", \"description\": \"How the MID band is applied to the stereo signal.\", \"options\": { \"STEREO\": \"Processes left and right identically.\", \"MID\": \"Affects only the mid (centre) information.\", \"SIDE\": \"Affects only the side information (difference between left and right).\", \"LEFT\": \"Affects only the left channel.\", \"RIGHT\": \"Affects only the right channel.\" } },\n { \"x\": 28, \"width\": 6, \"key\": \"highMode\", \"type\": \"option\", \"format\": \"text\", \"label\": \"HIGH Mode\", \"description\": \"How the HIGH band is applied to the stereo signal.\", \"options\": { \"STEREO\": \"Processes left and right identically.\", \"MID\": \"Affects only the mid (centre) information.\", \"SIDE\": \"Affects only the side information (difference between left and right).\", \"LEFT\": \"Affects only the left channel.\", \"RIGHT\": \"Affects only the right channel.\" } }\n ] }\n ]\n },\n \"modfxeq\": { \"extends\": \"mixeq\" },\n \"delayeq\": { \"extends\": \"mixeq\" },\n \"reverbeq\": { \"extends\": \"mixeq\" },\n\n \"mixscope\": {\n \"rows\": [\n { \"id\": \"limiterAtkRel\", \"match\": [{ \"x\": 9, \"text\": \"ATK\" }, { \"x\": 16, \"text\": \"REL\" }, { \"x\": 23, \"text\": \"LIM\" }], \"cells\": [\n { \"x\": 13, \"width\": 2, \"key\": \"attack\", \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Limiter Attack (ATK)\", \"description\": \"Limiter attack time from 0 to 100 ms (see the on-screen help text for the conversion to milliseconds).\" },\n { \"x\": 20, \"width\": 2, \"key\": \"release\", \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Limiter Release (REL)\", \"description\": \"Limiter release time from 4 to 1000 ms; 00 activates AUTO mode (100-900 ms depending on the compression amount).\" },\n { \"x\": 27, \"width\": 2, \"key\": \"limiter\", \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Limiter (LIM)\", \"description\": \"Limiter amount.\" }\n ] },\n { \"id\": \"zoomPeak\", \"match\": [{ \"x\": 0, \"text\": \"ZOOM\" }], \"cells\": [\n { \"x\": 5, \"width\": 5, \"key\": \"zoom\", \"type\": \"parameter\", \"format\": \"text\", \"label\": \"Zoom (ZOOM)\", \"description\": \"Lower limit of the scope histogram, in decibels.\" },\n { \"x\": 20, \"width\": 6, \"key\": \"peak\", \"type\": \"parameter\", \"format\": \"float\", \"unit\": \" dB\", \"label\": \"Peak (PEAK)\", \"description\": \"Current peak level of the main mix in dB. Resets when MIX or LIM change; clear it with [OPT]+[EDIT].\" }\n ] },\n { \"id\": \"eq\", \"match\": [{ \"x\": 27, \"text\": \"EQ\" }], \"cells\": [\n { \"x\": 27, \"width\": 2, \"key\": \"mixEq\", \"type\": \"action\", \"format\": \"text\", \"label\": \"Main Mix EQ (EQ)\", \"description\": \"Open the main mix EQ editor.\" }\n ] },\n { \"id\": \"softClip\", \"match\": [{ \"x\": 9, \"text\": \"SOFT CLIP\" }], \"cells\": [\n { \"x\": 19, \"width\": 3, \"key\": \"softClip\", \"type\": \"onOff\", \"format\": \"text\", \"label\": \"Soft Clip (SOFT CLIP)\", \"description\": \"Gentle saturation after the limiter to prevent harsh clipping; raise MIX to exaggerate it.\" },\n { \"x\": 27, \"width\": 2, \"key\": \"mainVol\", \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Main Volume (MIX)\", \"description\": \"Main mix / song volume, applied after the limiter stage.\" }\n ] },\n { \"id\": \"mix\", \"match\": [{ \"x\": 23, \"text\": \"MIX\" }], \"cells\": [\n { \"x\": 27, \"width\": 2, \"key\": \"mainVol\", \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Main Volume (MIX)\", \"description\": \"Main mix / song volume, applied after the limiter stage.\" }\n ] },\n { \"id\": \"lim\", \"match\": [{ \"x\": 23, \"text\": \"LIM\" }], \"cells\": [\n { \"x\": 27, \"width\": 2, \"key\": \"limiter\", \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Limiter (LIM)\", \"description\": \"Limiter amount.\" }\n ] },\n { \"id\": \"djf\", \"match\": [{ \"x\": 23, \"text\": \"DJF\" }], \"cells\": [\n { \"x\": 27, \"width\": 2, \"key\": \"djFilter\", \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"DJ Filter (DJF)\", \"description\": \"DJ filter cutoff: 80 is off, 00-80 low-pass, 80-FF high-pass (mode set with TYPE).\" }\n ] },\n { \"id\": \"ott\", \"match\": [{ \"x\": 23, \"text\": \"OTT\" }], \"cells\": [\n { \"x\": 27, \"width\": 2, \"key\": \"ott\", \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"OTT (OTT)\", \"description\": \"OTT mix amount; from 80 and above the main mix volume is faded out.\" }\n ] }\n ],\n \"zones\": [{ \"id\": \"right\", \"labelX\": 23, \"labelWidth\": 4, \"x\": 27, \"width\": 6 }],\n \"vocabulary\": {\n \"ATK\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Limiter Attack (ATK)\", \"description\": \"Limiter attack time from 0 to 100 ms (see the on-screen help text for the conversion).\" },\n \"REL\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Limiter Release (REL)\", \"description\": \"Limiter release time from 4 to 1000 ms; 00 activates AUTO mode (100-900 ms depending on the compression amount).\" },\n \"TYPE\": { \"type\": \"option\", \"format\": \"hexOption\", \"label\": \"DJ Filter Type (TYPE)\", \"description\": \"DJ filter type: low-pass/high-pass, low-pass/band-stop or band-stop/high-pass.\" },\n \"RES\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"DJ Filter Resonance (RES)\", \"description\": \"Resonance of the DJ filter.\" },\n \"TIME\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"OTT Time (TIME)\", \"description\": \"OTT compressor envelope time as a percentage from 10% to 1000%.\" },\n \"COLOR\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"OTT Color (COLOR)\", \"description\": \"OTT compressor bands and low/high gains: above 80 shifts toward treble, below 80 toward bass.\" }\n }\n },\n \"limiterscope\": { \"extends\": \"mixscope\" },\n\n \"inst\": {\n \"rows\": [\n { \"id\": \"type\", \"match\": [{ \"x\": 0, \"text\": \"TYPE\" }], \"cells\": [\n { \"x\": 8, \"width\": 13, \"key\": \"instrumentType\", \"type\": \"instrumentType\", \"format\": \"text\", \"label\": \"Instrument Type (TYPE)\", \"description\": \"Kind of instrument in this slot: Wavsynth, Macrosynth, Sampler, FM Synth, Hypersynth, External Instrument, MIDI Out (or NONE for an unused slot). Changing it changes the parameters below.\" },\n { \"x\": 22, \"width\": 4, \"key\": \"load\", \"type\": \"action\", \"format\": \"text\", \"label\": \"Load Preset (LOAD)\", \"description\": \"Load an instrument preset from the SD card.\" },\n { \"x\": 27, \"width\": 4, \"key\": \"save\", \"type\": \"action\", \"format\": \"text\", \"label\": \"Save Preset (SAVE)\", \"description\": \"Save this instrument as a preset on the SD card.\" }\n ] },\n { \"id\": \"name\", \"match\": [{ \"x\": 0, \"text\": \"NAME\" }], \"cells\": [\n { \"x\": 8, \"width\": 12, \"key\": \"name\", \"type\": \"name\", \"format\": \"text\", \"label\": \"Instrument Name (NAME)\", \"description\": \"Name of the instrument: helps keep the song organised when browsing, saving or bundling instruments.\" }\n ] },\n { \"id\": \"transp\", \"match\": [{ \"x\": 0, \"text\": \"TRANSP.\" }], \"cells\": [\n { \"x\": 8, \"width\": 3, \"key\": \"transpose\", \"type\": \"onOff\", \"format\": \"text\", \"label\": \"Transpose Enabled (TRANSP.)\", \"description\": \"Enable or disable note transposition from scales, chain transpose columns and the project transpose for this instrument (turn off for percussive or effect instruments).\" },\n { \"x\": 21, \"width\": 2, \"key\": \"tableTic\", \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Table Tick (TBL. TIC)\", \"description\": \"Default speed at which this instrument's table advances: 01 is one tick per step, 02 two ticks per step. Can be overridden by the TIC command.\" },\n { \"x\": 29, \"width\": 2, \"key\": \"eq\", \"type\": \"eqSlot\", \"format\": \"hex\", \"label\": \"EQ Slot (EQ)\", \"description\": \"EQ bank assigned to this instrument (-- for none). [EDIT] or [SHIFT]+[RIGHT] opens the EQ editor.\" }\n ] }\n ],\n \"zones\": [\n { \"id\": \"left\", \"labelX\": 0, \"labelWidth\": 8, \"x\": 8, \"width\": 24 },\n { \"id\": \"right\", \"labelX\": 18, \"labelWidth\": 4, \"x\": 22, \"width\": 11 }\n ],\n \"operators\": { \"instrument\": \"fmsynth\", \"x\": 8, \"step\": 6, \"width\": 5, \"letters\": [\"A\", \"B\", \"C\", \"D\"] },\n \"vocabularyFrom\": \"instrumentTypes\"\n },\n\n \"instmods\": {\n \"slotLabel\": \"MOD\\\\d\",\n \"zones\": [\n { \"id\": \"left\", \"labelX\": 0, \"labelWidth\": 4, \"x\": 5, \"width\": 10 },\n { \"id\": \"right\", \"labelX\": 16, \"labelWidth\": 4, \"x\": 21, \"width\": 10 }\n ],\n \"vocabulary\": {\n \"MOD#\": { \"type\": \"option\", \"format\": \"hexOption\", \"label\": \"Modulator Type\", \"description\": \"Type of this modulation slot: AHD envelope, ADSR envelope, DRUM envelope, LFO, TRIG envelope or TRACKING. Slots can modulate their neighbour (Mod 1 affects Mod 2, 2→3, 3→4, 4→1).\",\n \"options\": { \"AHD ENV\": \"Attack Hold Decay envelope based on tempo.\", \"ADSR ENV\": \"Traditional Attack Decay Sustain Release envelope.\", \"DRUM ENV\": \"Sharp peak and swelling hold envelope for percussive elements.\", \"LFO\": \"Tempo-synced low frequency oscillator.\", \"TRIG ENV\": \"AHD envelope triggered by another playing track or instrument.\", \"TRACKING\": \"Modulates an instrument parameter from notes or velocities.\", \"OFF\": \"Slot unused.\" } },\n \"DEST\": { \"type\": \"option\", \"format\": \"hexOption\", \"label\": \"Destination (DEST)\", \"description\": \"Instrument parameter this modulator controls. MOD AMT, MOD RATE or MOD BOTH modulate the neighbouring modulator's amount, rate or both.\" },\n \"SRC\": { \"type\": \"parameter\", \"format\": \"hexOption\", \"label\": \"Source (SRC)\", \"description\": \"Modulation source. Trig envelope: an instrument number (00-7F) or a whole track (80-87 = tracks 1-8). Tracking: NOTE, VELOCITY or VELOCITY TAKEOVER (velocity no longer affects instrument volume).\" },\n \"AMT\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Amount (AMT)\", \"description\": \"How strongly the modulator is applied to the destination.\" },\n \"ATK\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Attack (ATK)\", \"description\": \"Time to reach the amount (in ticks, relative to tempo).\" },\n \"HOLD\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Hold (HOLD)\", \"description\": \"Time to wait after the attack before decaying (in ticks).\" },\n \"DEC\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Decay (DEC)\", \"description\": \"Time to fall back (AHD/Drum: to zero; ADSR: to the sustain level), in ticks.\" },\n \"SUS\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Sustain (SUS)\", \"description\": \"Level held while the note is playing (ADSR).\" },\n \"REL\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Release (REL)\", \"description\": \"Time to fall back to zero after the note is released (ADSR); needs an OFF note or FX command.\" },\n \"PEAK\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Peak (PEAK)\", \"description\": \"Shape of the initial transient (drum envelope); modifies the duck amount and peak time.\" },\n \"BODY\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Body (BODY)\", \"description\": \"Time to hold the modulation before decaying (drum envelope).\" },\n \"OSC\": { \"type\": \"option\", \"format\": \"hexOption\", \"label\": \"LFO Shape (OSC)\", \"description\": \"LFO shape: triangle, sine, ramp down/up, exponential down/up, square down/up, random, drunk. The 'T' variants run in ticks (much faster).\",\n \"options\": { \"TRI\": \"Triangle.\", \"SIN\": \"Sine.\", \"RAMP DN\": \"Ramp down.\", \"RAMP UP\": \"Ramp up.\", \"EXP DN\": \"Exponential down.\", \"EXP UP\": \"Exponential up.\", \"SQU DN\": \"Square down.\", \"SQU UP\": \"Square up.\", \"RANDOM\": \"Random values.\", \"DRUNK\": \"Random walk.\" } },\n \"TRIG\": { \"type\": \"option\", \"format\": \"hexOption\", \"label\": \"LFO Trigger (TRIG)\", \"description\": \"How the LFO reacts when the instrument triggers and when the shape ends.\",\n \"options\": { \"FREE\": \"Loop the shape and don't reset on new notes.\", \"RETRIG\": \"Loop the shape and reset on each instrument trigger.\", \"HOLD\": \"Hold the last value of the shape.\", \"ONCE\": \"Play the shape once and reset to the start value.\" } },\n \"FREQ\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"LFO Frequency (FREQ)\", \"description\": \"Rate of the LFO in steps (16th notes), or in ticks for the 'T' shapes.\" },\n \"LVAL\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Low Value (LVAL)\", \"description\": \"Lowest value of the source range scaled to the destination (tracking). Can be higher than HVAL to invert the range.\" },\n \"HVAL\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"High Value (HVAL)\", \"description\": \"Highest value of the source range scaled to the destination (tracking).\" }\n }\n },\n\n \"scale\": {\n \"notes\": \"Only the rows below the 12 interval rows; the intervals themselves are grid columns in m8-view-context.json.\",\n \"rows\": [\n { \"id\": \"key\", \"match\": [{ \"x\": 0, \"text\": \"KEY\" }], \"cells\": [\n { \"x\": 6, \"width\": 2, \"key\": \"key\", \"type\": \"option\", \"format\": \"text\", \"label\": \"Key (KEY)\", \"description\": \"Global key: the root note of the default scale (can be overridden per track with SCA / SCG).\" }\n ] },\n { \"id\": \"tune\", \"match\": [{ \"x\": 0, \"text\": \"TUNE\" }], \"cells\": [\n { \"x\": 6, \"width\": 6, \"key\": \"tune\", \"type\": \"parameter\", \"format\": \"float\", \"unit\": \" Hz\", \"label\": \"Tuning (TUNE)\", \"description\": \"Reference pitch of A4 in Hz (440.00 is standard).\" }\n ] },\n { \"id\": \"name\", \"match\": [{ \"x\": 0, \"text\": \"NAME\" }], \"cells\": [\n { \"x\": 6, \"width\": 16, \"key\": \"name\", \"type\": \"name\", \"format\": \"text\", \"label\": \"Scale Name (NAME)\", \"description\": \"Name of this scale (up to 16 characters); 92 factory scales load from /Scales/Factory.\" }\n ] },\n { \"id\": \"file\", \"match\": [{ \"x\": 6, \"text\": \"LOAD\" }, { \"x\": 11, \"text\": \"SAVE\" }], \"cells\": [\n { \"x\": 6, \"width\": 4, \"key\": \"load\", \"type\": \"action\", \"format\": \"text\", \"label\": \"Load Scale (LOAD)\", \"description\": \"Load a scale from the SD card.\" },\n { \"x\": 11, \"width\": 4, \"key\": \"save\", \"type\": \"action\", \"format\": \"text\", \"label\": \"Save Scale (SAVE)\", \"description\": \"Save this scale to the SD card.\" }\n ] }\n ]\n },\n\n \"loadinstrument\": { \"kind\": \"browser\", \"browserFor\": \"instrument preset\" },\n \"selectsavedirectory\": { \"kind\": \"browser\", \"browserFor\": \"save location\" },\n \"createdirectory\": { \"kind\": \"text\", \"fieldLabel\": \"Directory Name\", \"description\": \"Name of the directory to create on the SD card.\" }\n },\n\n \"instrumentTypes\": {\n \"common\": {\n \"name\": \"Common\",\n \"description\": \"Parameters shared by most instrument types.\",\n \"params\": {\n \"FILTER\": { \"type\": \"option\", \"format\": \"hexOption\", \"label\": \"Filter Type (FILTER)\", \"description\": \"Multi-mode filter type: low-pass, high-pass, band-pass, band-stop, LP>HP (low-pass with RES controlling a 1-pole high-pass), ZDF low-pass, ZDF high-pass.\" },\n \"CUTOFF\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Filter Cutoff (CUTOFF)\", \"description\": \"Filter cutoff frequency.\" },\n \"RES\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Filter Resonance (RES)\", \"description\": \"Filter resonance amount.\" },\n \"AMP\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Amplification (AMP)\", \"description\": \"Amplifies the signal; values above the maximum level are handled according to LIM.\" },\n \"LIM\": { \"type\": \"option\", \"format\": \"hexOption\", \"label\": \"Limit Mode (LIM)\", \"description\": \"Limit / clipping behaviour of the amplified signal.\",\n \"options\": { \"CLIP\": \"Values above the maximum level are clipped.\", \"SIN\": \"The waveform is fed through a sine function.\", \"FOLD\": \"Values above the maximum level are folded back into themselves.\", \"WRAP\": \"Values above the maximum level are wrapped vertically.\", \"POST\": \"Amplification applied with hard-clipping after the filter stage.\", \"POST:AD\": \"Post filter amplification with soft-clipping.\", \"POST:W1\": \"Post filter folding distortion.\", \"POST:W2\": \"Post filter folding distortion.\", \"POST:W3\": \"Post filter folding distortion.\" } },\n \"PAN\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Pan (PAN)\", \"description\": \"Stereo position of the instrument (80 is centre).\" },\n \"DRY\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Dry Volume (DRY)\", \"description\": \"Dry volume sent to the mixer channel.\" },\n \"MFX\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"ModFX Send (MFX)\", \"description\": \"ModFX send effect volume.\" },\n \"DEL\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Delay Send (DEL)\", \"description\": \"Delay send effect volume.\" },\n \"REV\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Reverb Send (REV)\", \"description\": \"Reverb send effect volume.\" }\n }\n },\n \"wavsynth\": {\n \"name\": \"Wavsynth\",\n \"description\": \"Wave table synthesizer and noise generator: an 8-bit sample buffer generated in real time from one of 9 basic shapes (repeated, skewed, mirrored, filtered, overdriven) or one of 61 built-in wave tables of 64 waveforms each.\",\n \"params\": {\n \"SHAPE\": { \"type\": \"option\", \"format\": \"hexOption\", \"label\": \"Shape (SHAPE)\", \"description\": \"Base waveform: PULSE 12% to 75%, SAW, TRIANGLE, SINE, NOISE PITCHED, NOISE, or WT wave tables. For PWM set PULSE 50% and adjust MIRROR (smoothness set by SIZE).\" },\n \"SIZE\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Size (SIZE)\", \"description\": \"Horizontal size of the waveform (number of samples).\" },\n \"MULT\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Multiply (MULT)\", \"description\": \"Multiplies the waveform by adding repeats of the shape; can sound like hard sync.\" },\n \"WARP\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Warp (WARP)\", \"description\": \"Pushes the shape to one side of the wave table.\" },\n \"SCAN\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Scan / Mirror (SCAN)\", \"description\": \"Shapes 0-8: mirrors the waveform at the given position (0-200%). Wave tables: scans through the 64 waveforms.\" },\n \"FILTER\": { \"type\": \"option\", \"format\": \"hexOption\", \"label\": \"Filter Type (FILTER)\", \"description\": \"Multi-mode filter type, plus 4 modes that apply the filter into the waveform: WAV LP, WAV HP, WAV BP, WAV BS.\" }\n }\n },\n \"macrosynth\": {\n \"name\": \"Macrosynth\",\n \"description\": \"44 engines ('shapes') from basic analog to 2-op FM, physical modelling and percussion; TIMBRE and COLOR change meaning per shape.\",\n \"params\": {\n \"SHAPE\": { \"type\": \"option\", \"format\": \"hexOption\", \"label\": \"Shape (SHAPE)\", \"description\": \"One of the 44 Macrosynth engines (see the Macrosynth Models appendix).\" },\n \"TIMBRE\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Timbre (TIMBRE)\", \"description\": \"First parameter of the selected shape.\" },\n \"COLOR\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Color (COLOR)\", \"description\": \"Second parameter of the selected shape.\" },\n \"DEGRADE\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Degrade (DEGRADE)\", \"description\": \"Sample rate reduction effect.\" },\n \"REDUX\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Redux (REDUX)\", \"description\": \"Bit rate reduction effect.\" }\n }\n },\n \"sampler\": {\n \"name\": \"Sampler\",\n \"description\": \"Sample player, editor and recorder for 1 or 2 channel PCM .wav files.\",\n \"params\": {\n \"SAMPLE\": { \"type\": \"name\", \"format\": \"text\", \"label\": \"Sample (SAMPLE)\", \"description\": \"The loaded sample; use LOAD to browse for one, REC. to record or EDIT to open the Sample Editor.\" },\n \"SLICE\": { \"type\": \"option\", \"format\": \"hexOption\", \"label\": \"Slice (SLICE)\", \"description\": \"Slices the sample into equal sections, mapped from the lowest octave (C-1); 01 FILE uses the markers stored in the file.\" },\n \"PLAY\": { \"type\": \"option\", \"format\": \"hexOption\", \"label\": \"Play Mode (PLAY)\", \"description\": \"Play direction and loop mode.\",\n \"options\": { \"FWD\": \"Forward, no loop.\", \"REV\": \"Reverse, no loop.\", \"FWDLOOP\": \"Looping forward.\", \"REVLOOP\": \"Looping reverse.\", \"FWD PP\": \"Ping-pong looping, forward first.\", \"REV PP\": \"Ping-pong looping, reverse first.\", \"OSC\": \"Oscillator mode: ignores the start position and doesn't restart on retrigger.\", \"OSC REV\": \"Oscillator mode, reverse.\", \"OSC PP\": \"Oscillator mode, ping-pong.\", \"REPITCH\": \"Pitches the sample from the song tempo (adjust with STEPS/BPM).\" } },\n \"START\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Start (START)\", \"description\": \"Sample start position (ignored in OSC play modes).\" },\n \"LOOP ST.\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Loop Start (LOOP ST.)\", \"description\": \"Where the sample loops back to when it reaches the end in a looping play mode.\" },\n \"LOOP ST\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Loop Start (LOOP ST)\", \"description\": \"Where the sample loops back to when it reaches the end in a looping play mode.\" },\n \"LEN\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Length (LEN)\", \"description\": \"Sample length.\" },\n \"LENGTH\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Length (LENGTH)\", \"description\": \"Sample length.\" },\n \"DETUNE\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Detune (DETUNE)\", \"description\": \"Detune with 80 as the centre frequency: first digit steps semitones, second digit 1/16 semitone.\" },\n \"DEGRADE\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Degrade (DEGRADE)\", \"description\": \"Sample rate reduction; any value above 00 disables sample interpolation.\" }\n }\n },\n \"fmsynth\": {\n \"name\": \"FM Synth\",\n \"description\": \"Four-operator, 12-algorithm FM synthesizer. Each operator has a base shape, ratio, level, feedback and two modulation slots.\",\n \"operatorDestinations\": {\n \"LEV\": \"controls the level from 00 to the currently set level.\",\n \"RAT\": \"adds 0.00 to +16.00 to the current ratio.\",\n \"PIT\": \"adds to the note pitch, in semitones, for this operator.\",\n \"FBK\": \"controls the feedback from 00 to the currently set feedback.\"\n },\n \"params\": {\n \"ALGO\": { \"type\": \"option\", \"format\": \"hexOption\", \"label\": \"Algorithm (ALGO)\", \"description\": \"One of 12 algorithms (oscillator routing); the last one is additive mode.\" },\n \"A\": { \"type\": \"option\", \"format\": \"hexOption\", \"label\": \"Operator A (A)\", \"description\": \"Operator letter with optional shape (SIN, SW2-SW6, SAW, SQU, PUL, IMP, TRI, NOISE, then wave tables).\" },\n \"B\": { \"type\": \"option\", \"format\": \"hexOption\", \"label\": \"Operator B (B)\", \"description\": \"Operator letter with optional shape (SIN, SW2-SW6, SAW, SQU, PUL, IMP, TRI, NOISE, then wave tables).\" },\n \"C\": { \"type\": \"option\", \"format\": \"hexOption\", \"label\": \"Operator C (C)\", \"description\": \"Operator letter with optional shape (SIN, SW2-SW6, SAW, SQU, PUL, IMP, TRI, NOISE, then wave tables).\" },\n \"D\": { \"type\": \"option\", \"format\": \"hexOption\", \"label\": \"Operator D (D)\", \"description\": \"Operator letter with optional shape (SIN, SW2-SW6, SAW, SQU, PUL, IMP, TRI, NOISE, then wave tables).\" },\n \"RATIO\": { \"type\": \"parameter\", \"format\": \"float\", \"label\": \"Ratio (RATIO)\", \"description\": \"Operator frequency ratio relative to the played note (2.00 is an octave up, 0.50 an octave down).\" },\n \"LEV/FB\": { \"type\": \"parameter\", \"format\": \"hexPair\", \"parts\": [\"level\", \"feedback\"], \"label\": \"Level / Feedback (LEV/FB)\", \"description\": \"Operator volume and feedback.\" },\n \"MOD\": { \"type\": \"option\", \"format\": \"hexOption\", \"label\": \"Modulation Slot (MOD)\", \"description\": \"Assigns one of MOD1-MOD4 to control a destination on the operator.\" },\n \"LEV\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Level Mod (LEV)\", \"description\": \"Controls the level from 00 to the currently set level.\" },\n \"RAT\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Ratio Mod (RAT)\", \"description\": \"Adds 0.00 to +16.00 to the current ratio.\" },\n \"PIT\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Pitch Mod (PIT)\", \"description\": \"Adds to the note pitch, in semitones, for the operator.\" },\n \"FBK\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Feedback Mod (FBK)\", \"description\": \"Controls feedback from 00 to the currently set feedback.\" },\n \"MOD#\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Modulation Source\", \"description\": \"Modulation source value freely assigned to operators through the MOD slots.\" }\n }\n },\n \"hypersynth\": {\n \"name\": \"Hypersynth\",\n \"description\": \"Six-note saw wave chord synthesizer with 16 chord banks of up to 6 intervals, detune (swarm), stereo width and a square sub-oscillator.\",\n \"params\": {\n \"SCALE\": { \"type\": \"option\", \"format\": \"hexOption\", \"label\": \"Scale (SCALE)\", \"description\": \"One of the song's scales, or 00 to use the song/track scale settings.\" },\n \"CHORD\": { \"type\": \"option\", \"format\": \"hexOption\", \"label\": \"Chord (CHORD)\", \"description\": \"Chord bank and its intervals (copy with [SHIFT]+[OPTION], paste with [SHIFT]+[EDIT]).\" },\n \"SHIFT\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Shift (SHIFT)\", \"description\": \"Cross-fades volume between the first 3 intervals and the second 3.\" },\n \"SHAPE\": { \"type\": \"option\", \"format\": \"hexOption\", \"label\": \"Shape (SHAPE)\", \"description\": \"Waveform of the chord oscillators (SAW by default; not in the manual, seen on a Model:02, firmware 6.5).\" },\n \"SWARM\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Swarm (SWARM)\", \"description\": \"Detunes the oscillators relative to their interval.\" },\n \"WIDTH\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Stereo Width (WIDTH)\", \"description\": \"Stereo width of the oscillators, from mono 00 to full stereo FF.\" },\n \"SUBOSC\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Sub Oscillator (SUBOSC)\", \"description\": \"Mixes in a sub-oscillator 2 octaves below the root (below 80) or 1 octave below (above 80).\" }\n }\n },\n \"external\": {\n \"name\": \"External Instrument\",\n \"description\": \"Hybrid MIDI output and audio input device: notes and MIDI FX are sent to the chosen port and channel, audio returns through its filter and amp stage.\",\n \"params\": {\n \"INPUT\": { \"type\": \"option\", \"format\": \"hexOption\", \"label\": \"Audio Input (INPUT)\", \"description\": \"Audio input: line-in (stereo, left, right), USB (stereo, left, right), or line-in and USB.\" },\n \"PORT\": { \"type\": \"option\", \"format\": \"hexOption\", \"label\": \"MIDI Port (PORT)\", \"description\": \"MIDI output port: MIDI and USB, MIDI only, USB only, or Internal (track MIDI input / global MIDI CCs from the MIDI Settings View).\" },\n \"CHANNEL\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"MIDI Channel (CHANNEL)\", \"description\": \"MIDI channel, 1 to 16.\" },\n \"BANK:PG\": { \"type\": \"parameter\", \"format\": \"hexPair\", \"parts\": [\"bank\", \"program\"], \"label\": \"Bank / Program (BANK:PG)\", \"description\": \"Optional MIDI bank select and program change (program change can also be sent as an FX command).\" },\n \"CC#\": { \"type\": \"parameter\", \"format\": \"text\", \"label\": \"Custom CC (CC:VAL)\", \"description\": \"A custom MIDI CC number, then an optional default value (-- for none), sent with the note-on after program change messages.\" }\n }\n },\n \"midiout\": {\n \"name\": \"MIDI Out\",\n \"description\": \"Controls external devices over USB MIDI or the TRS (type A) MIDI output.\",\n \"params\": {\n \"PORT\": { \"type\": \"option\", \"format\": \"hexOption\", \"label\": \"MIDI Port (PORT)\", \"description\": \"MIDI output port: MIDI and USB, MIDI only, USB only, or Internal (track MIDI input / global MIDI CCs from the MIDI Settings View).\" },\n \"CHANNEL\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"MIDI Channel (CHANNEL)\", \"description\": \"MIDI channel, 1 to 16.\" },\n \"BANK:PG\": { \"type\": \"parameter\", \"format\": \"hexPair\", \"parts\": [\"bank\", \"program\"], \"label\": \"Bank / Program (BANK:PG)\", \"description\": \"Optional MIDI bank select and program change (program change can also be sent as an FX command).\" },\n \"CC#\": { \"type\": \"parameter\", \"format\": \"text\", \"label\": \"Custom CC (CC:VAL)\", \"description\": \"One of up to 10 custom MIDI CC numbers per instrument, then an optional default value (-- for none), sent with the note-on after program change messages.\" }\n }\n }\n },\n\n \"instrumentTypeNames\": {\n \"WAVS\": \"wavsynth\",\n \"MACRO\": \"macrosynth\",\n \"SAMPL\": \"sampler\",\n \"FM\": \"fmsynth\",\n \"HYPER\": \"hypersynth\",\n \"EXTERN\": \"external\",\n \"MIDI\": \"midiout\"\n }\n}\n","/**\r\n * parameterViews.ts\r\n *\r\n * Maps the non-grid (\"parameter\") views - instrument, modulation, mixer, project,\r\n * effects, EQ, scope, system settings, file browsers - onto typed field cells,\r\n * using m8-parameter-views.json.\r\n *\r\n * These views are label/value screens, so a row is recognised from its text (its\r\n * label), not from a row number, and the cells of that row come from the schema. Views\r\n * whose labels change with the instrument or modulation type are read through two\r\n * label/value \"zones\" and the label is looked up in a vocabulary.\r\n *\r\n * Pure text in, cell definitions out; viewContext.ts turns them into parsed fields.\r\n */\r\n\r\nimport paramSchema from './m8-parameter-views.json'\r\nimport type { ColumnDef } from './viewContext'\r\n\r\nexport type ValueFormat = 'hex' | 'hexOption' | 'hexPair' | 'decimal' | 'decimalPair' | 'float' | 'text' | 'auto'\r\n\r\ninterface MatchDef {\r\n x: number\r\n text?: string\r\n regex?: string\r\n width?: number\r\n}\r\n\r\ninterface RowDef {\r\n id: string\r\n section?: string\r\n match: MatchDef[]\r\n cells: ColumnDef[]\r\n}\r\n\r\ninterface ZoneDef {\r\n id: string\r\n labelX: number\r\n labelWidth: number\r\n x: number\r\n width: number\r\n}\r\n\r\ntype VocabEntry = Pick<ColumnDef, 'type' | 'format' | 'label' | 'description' | 'parts' | 'options' | 'unit'>\r\n\r\ninterface InstrumentTypeDef {\r\n name: string\r\n description: string\r\n params: Record<string, VocabEntry>\r\n /** FM: what each destination of an operator's MOD slot does ('LEV', 'RAT'...). */\r\n operatorDestinations?: Record<string, string>\r\n}\r\n\r\n/** Rows with one value per operator (FM): the columns of the values. */\r\ninterface OperatorsDef {\r\n instrument: string\r\n x: number\r\n step: number\r\n width: number\r\n letters: string[]\r\n}\r\n\r\ninterface LayoutDef {\r\n extends?: string\r\n /** Pattern of the heading label that names the block a cell belongs to ('MOD\\\\d'): read upward in the cell's zone. */\r\n slotLabel?: string\r\n kind?: 'browser' | 'text'\r\n browserFor?: string\r\n fieldLabel?: string\r\n description?: string\r\n sections?: string[]\r\n rows?: RowDef[]\r\n zones?: ZoneDef[]\r\n operators?: OperatorsDef\r\n vocabulary?: Record<string, VocabEntry>\r\n vocabularyFrom?: 'instrumentTypes'\r\n}\r\n\r\nexport type ParameterLayout = Omit<LayoutDef, 'extends'>\r\n\r\nconst rawViews = paramSchema.views as unknown as Record<string, LayoutDef>\r\nconst instrumentTypes = paramSchema.instrumentTypes as unknown as Record<string, InstrumentTypeDef>\r\nconst instrumentTypeNames = paramSchema.instrumentTypeNames as Record<string, string>\r\n\r\nconst layouts: Record<string, ParameterLayout> = {}\r\nfor (const [name, def] of Object.entries(rawViews)) {\r\n const { extends: parent, ...own } = def\r\n layouts[name] = parent ? { ...rawViews[parent], ...own } : own\r\n}\r\n\r\n/** Layout of a parameter view, or null when the view has none. */\r\nexport function getParameterLayout(viewName: string): ParameterLayout | null {\r\n return layouts[viewName] ?? null\r\n}\r\n\r\n// text helpers \r\n\r\nconst slice = (line: string, x: number, width: number): string => (x >= line.length ? '' : line.slice(x, x + width))\r\n\r\nfunction matches(cond: MatchDef, line: string): boolean {\r\n const width = cond.width ?? cond.text?.length ?? 1\r\n // The device's text stream is lower case ('tempo', 'b-4'); schema text is upper case.\r\n const text = slice(line, cond.x, width)\r\n if (cond.text != null) return text.toUpperCase() === cond.text.toUpperCase()\r\n if (cond.regex != null) return new RegExp(cond.regex, 'i').test(text)\r\n return false\r\n}\r\n\r\n// sections and instrument type \r\n\r\n/**\r\n * Section heading ('MODFX', 'DELAY', 'REVERB') the cursor line belongs to: the\r\n * nearest heading at column 0 on this line or above it. `linesUpward` starts with the\r\n * cursor line, then the lines above it.\r\n */\r\nexport function detectSection(layout: ParameterLayout, linesUpward: string[]): string | null {\r\n const sections = layout.sections\r\n if (!sections) return null\r\n for (const line of linesUpward) {\r\n const upper = line.toUpperCase()\r\n const hit = sections.find((s) => upper.startsWith(s))\r\n if (hit) return hit\r\n }\r\n return null\r\n}\r\n\r\n/**\r\n * Block heading ('MOD1'…'MOD4') a cell in `zoneId` belongs to: the nearest line at or\r\n * above the cursor with a heading label in that zone.\r\n */\r\nexport function detectSlot(layout: ParameterLayout, zoneId: string, linesUpward: string[]): string | null {\r\n const zone = layout.zones?.find((z) => z.id === zoneId)\r\n if (!layout.slotLabel || !zone) return null\r\n const pattern = new RegExp(`^${layout.slotLabel}$`, 'i')\r\n for (const line of linesUpward) {\r\n const label = slice(line, zone.labelX, zone.labelWidth).trim()\r\n if (pattern.test(label)) return label.toUpperCase()\r\n }\r\n return null\r\n}\r\n\r\n/** 'WAVSYNTH' → 'wavsynth', 'MIDI OUT' → 'midiout', unknown text → null. */\r\nexport function instrumentKeyFromText(text: string): string | null {\r\n const letters = text.toUpperCase().replace(/[^A-Z]/g, '')\r\n if (!letters) return null\r\n const prefix = Object.keys(instrumentTypeNames).find((p) => letters.startsWith(p))\r\n return prefix ? instrumentTypeNames[prefix] : null\r\n}\r\n\r\n/** Instrument type shown on the TYPE row of the Instrument View, read off the screen lines. */\r\nexport function instrumentTypeFromLines(lines: string[]): { text: string; key: string | null } | null {\r\n for (const line of lines) {\r\n if (line.toUpperCase().startsWith('TYPE')) {\r\n const text = slice(line, 8, 13).trim()\r\n return text ? { text, key: instrumentKeyFromText(text) } : null\r\n }\r\n }\r\n return null\r\n}\r\n\r\n/** Display name and description of an instrument type key ('wavsynth' → Wavsynth). */\r\nexport function getInstrumentType(key: string): { name: string; description: string } | null {\r\n const t = instrumentTypes[key]\r\n return t ? { name: t.name, description: t.description } : null\r\n}\r\n\r\n// vocabulary \r\n\r\nfunction lookupVocabulary(layout: ParameterLayout, label: string, instrumentKey: string | null): VocabEntry | null {\r\n // 'MOD1'..'MOD4' and 'CCA'..'CCJ' share one entry.\r\n const norm = label.replace(/^MOD\\d$/, 'MOD#').replace(/^CC[A-J]$/, 'CC#')\r\n if (layout.vocabulary) return layout.vocabulary[norm] ?? null\r\n if (layout.vocabularyFrom !== 'instrumentTypes') return null\r\n const order = instrumentKey ? [instrumentKey, 'common'] : ['common', ...Object.keys(instrumentTypes).filter((k) => k !== 'common')]\r\n for (const key of order) {\r\n const hit = instrumentTypes[key]?.params[norm]\r\n if (hit) return hit\r\n }\r\n return null\r\n}\r\n\r\nconst camel = (label: string): string =>\r\n label\r\n .toLowerCase()\r\n .replace(/[^a-z0-9]+(.)?/g, (_, c: string | undefined) => (c ? c.toUpperCase() : ''))\r\n .replace(/^[^a-z]+/, '')\r\n\r\nconst LABEL_SHAPE = /^[A-Z][A-Z0-9.:#/ -]*$/\r\n\r\n// reading a line \r\n\r\n/** Operator rows of the Instrument view: label to the vocabulary key and key prefix of its cells. */\r\nconst OPERATOR_ROWS: Record<string, string> = { RATIO: 'ratio', 'LEV/FB': 'levFb', MOD: 'mod' }\r\nconst OPERATOR_MOD_VALUE = /^(?:-----|[1-4]>[A-Z]{3})$/i\r\n\r\n/**\r\n * The cells of an FM row with one value per operator (RATIO, LEV/FB, the two MOD rows, the\r\n * shapes under ALGO), or null when the line is none of them. Each cell names its operator, so\r\n * the four columns of a row are described alike.\r\n */\r\nfunction operatorCells(def: OperatorsDef, line: string, vocab: InstrumentTypeDef): ColumnDef[] | null {\r\n const label = slice(line, 0, def.x).trim().toUpperCase()\r\n const columns = def.letters.map((letter, i) => ({ letter, x: def.x + i * def.step }))\r\n const cellOf = (key: string, x: number, width: number, entry: Pick<ColumnDef, 'type' | 'format' | 'label' | 'description'> & Partial<ColumnDef>, letter: string): ColumnDef => ({\r\n ...entry,\r\n x,\r\n width,\r\n key: `${key}${letter}`,\r\n label: `${entry.label} · operator ${letter}`,\r\n meta: { paramLabel: label || key, operator: letter },\r\n })\r\n\r\n const rowKey = OPERATOR_ROWS[label]\r\n if (rowKey) {\r\n const entry = vocab.params[label]\r\n if (!entry) return null\r\n return columns.map(({ letter, x }) => cellOf(rowKey, x, def.width, label === 'MOD' ? modSlotEntry(entry, 1, vocab) : entry, letter))\r\n }\r\n if (label) return null\r\n\r\n // The shapes under ALGO: 'A SW4 B W0F C SIN D SIN', each as wide as its name.\r\n if (/^\\s+[A-D] \\S/i.test(line)) {\r\n const cells: ColumnDef[] = []\r\n for (const match of line.matchAll(/([A-D]) (\\S+)/gi)) {\r\n const letter = match[1].toUpperCase()\r\n const entry = vocab.params[letter]\r\n if (!entry || !def.letters.includes(letter)) continue\r\n cells.push({ ...entry, type: 'option', format: 'text', x: (match.index ?? 0) + 2, width: match[2].length, key: `shape${letter}`, label: `Shape · operator ${letter}`, meta: { paramLabel: 'SHAPE', operator: letter } })\r\n }\r\n return cells.length ? cells : null\r\n }\r\n\r\n // The second MOD row has no label: four slots, each '-----' or 'n>DEST'.\r\n const values = columns.map(({ x }) => slice(line, x, def.width).trim())\r\n const entry = vocab.params.MOD\r\n if (entry && values.every((value) => OPERATOR_MOD_VALUE.test(value)) && !slice(line, 0, def.x).trim()) {\r\n return columns.map(({ letter, x }) => cellOf('mod2', x, def.width, modSlotEntry(entry, 2, vocab), letter))\r\n }\r\n return null\r\n}\r\n\r\n/** The vocabulary entry of an operator's MOD slot `slot` (1 or 2), with what each 'n>DEST' value does. */\r\nfunction modSlotEntry(entry: VocabEntry, slot: number, vocab: InstrumentTypeDef): VocabEntry {\r\n const options: Record<string, string> = {}\r\n for (const [dest, text] of Object.entries(vocab.operatorDestinations ?? {})) {\r\n for (let source = 1; source <= 4; source += 1) options[`${source}>${dest}`] = `MOD${source} ${dest}: ${text}`\r\n }\r\n return { ...entry, type: 'option', format: 'text', label: `Modulation Slot ${slot} (MOD)`, options }\r\n}\r\n\r\n/** The custom CC row of MIDI OUT ('CCA CC:VAL 074:7F') or EXTERNAL ('CCA 074:7F'): its label and where its value starts. */\r\nconst CC_LABEL = /^(CC[A-J])(?: CC:VAL)? +(?=\\S)/i\r\nconst CC_VALUE_WIDTH = 6\r\n\r\nexport interface ParameterLineOptions {\r\n section: string | null\r\n instrumentKey: string | null\r\n /** Text under the cursor; the entry of a file-browser row. */\r\n entryText?: string | null\r\n}\r\n\r\n/**\r\n * The cells present on `line`: the schema row whose label text matches, or else the\r\n * label/value zones. Empty when nothing on the line is recognised.\r\n */\r\nexport function readParameterCells(\r\n layout: ParameterLayout,\r\n line: string,\r\n opts: ParameterLineOptions,\r\n): { rowId: string | null; cells: ColumnDef[] } {\r\n if (layout.kind === 'browser') {\r\n const entry = opts.entryText?.trim() || line.trim()\r\n if (!entry) return { rowId: null, cells: [] }\r\n const x = Math.max(0, line.indexOf(entry))\r\n return {\r\n rowId: 'entry',\r\n cells: [{ x, width: entry.length, key: 'entry', type: 'fileEntry', format: 'text', label: 'File Browser Entry', meta: { browserFor: layout.browserFor } }],\r\n }\r\n }\r\n if (layout.kind === 'text') {\r\n if (!line.trim()) return { rowId: null, cells: [] }\r\n const x = Math.max(0, line.search(/\\S/))\r\n return {\r\n rowId: 'text',\r\n cells: [{ x, width: Math.max(12, line.length - x), key: 'name', type: 'name', format: 'text', label: layout.fieldLabel ?? 'Name', description: layout.description }],\r\n }\r\n }\r\n\r\n for (const row of layout.rows ?? []) {\r\n if (row.section && opts.section && row.section !== opts.section) continue\r\n if (row.match.every((m) => matches(m, line))) return { rowId: row.id, cells: row.cells }\r\n }\r\n\r\n if (layout.operators && opts.instrumentKey === layout.operators.instrument) {\r\n const found = operatorCells(layout.operators, line, instrumentTypes[layout.operators.instrument])\r\n if (found) return { rowId: 'operators', cells: found }\r\n }\r\n\r\n const zones = layout.zones ?? []\r\n const cells: ColumnDef[] = []\r\n zones.forEach((zone, i) => {\r\n let label = slice(line, zone.labelX, zone.labelWidth).trim().toUpperCase()\r\n let valueX = zone.x\r\n let valueWidth: number | null = null\r\n // Custom CC rows have a longer label and their own value column.\r\n const cc = i === 0 ? CC_LABEL.exec(line) : null\r\n if (cc) {\r\n label = cc[1].toUpperCase()\r\n valueX = cc[0].length\r\n valueWidth = CC_VALUE_WIDTH\r\n }\r\n if (!LABEL_SHAPE.test(label)) return\r\n // The value ends where the next zone's label begins, when that label is present.\r\n const next = zones[i + 1]\r\n // A label is set off by a blank; letters spilling out of a long value ('16wt-osc:tube')\r\n // are not one.\r\n const nextIsLabel =\r\n next != null &&\r\n slice(line, next.labelX - 1, 1).trim() === '' &&\r\n LABEL_SHAPE.test(slice(line, next.labelX, next.labelWidth).trim().toUpperCase())\r\n const width = valueWidth ?? (next && nextIsLabel ? Math.max(1, next.labelX - 1 - valueX) : zone.width)\r\n if (!slice(line, valueX, width).trim()) return\r\n const vocab = lookupVocabulary(layout, label, opts.instrumentKey)\r\n // The same label can sit in both zones (two modulators side by side): keep keys unique.\r\n const base = camel(label) || zone.id\r\n const key = cells.some((c) => c.key === base) ? `${base}${zone.id[0].toUpperCase()}${zone.id.slice(1)}` : base\r\n cells.push({\r\n x: valueX,\r\n width,\r\n key,\r\n type: vocab?.type ?? 'parameter',\r\n format: vocab?.format ?? 'auto',\r\n label: vocab?.label ?? label,\r\n description: vocab?.description,\r\n parts: vocab?.parts,\r\n options: vocab?.options,\r\n unit: vocab?.unit,\r\n meta: { paramLabel: label, zone: zone.id },\r\n })\r\n })\r\n return { rowId: cells.length ? 'params' : null, cells }\r\n}\r\n\r\n// values \r\n\r\nexport interface ParsedValue {\r\n hexValue: number | null\r\n /** Natural number of the value: the hex value, decimal integer or float. */\r\n numericValue: number | null\r\n /** Both numbers of an 'XX:YY' pair. */\r\n values: number[] | null\r\n /** The non-numeric part: option name, caption, name text. */\r\n text: string | null\r\n}\r\n\r\nconst NONE: ParsedValue = { hexValue: null, numericValue: null, values: null, text: null }\r\n\r\nfunction detectFormat(s: string): ValueFormat {\r\n if (/^[0-9A-F]{2}$/i.test(s)) return 'hex'\r\n if (/^[0-9A-F]{2}:[0-9A-F]{2}$/i.test(s)) return 'hexPair'\r\n if (/^[+-]?\\d+([.,]\\d+)?$/.test(s)) return 'float'\r\n if (/^[0-9A-F]{2}\\S/i.test(s)) return 'hexOption'\r\n return 'text'\r\n}\r\n\r\n/** Reads a cell's text according to its format. Empty text gives all-null. */\r\nexport function parseValue(raw: string, format: ValueFormat): ParsedValue {\r\n const s = raw.trim()\r\n if (!s) return NONE\r\n const fmt = format === 'auto' ? detectFormat(s) : format\r\n\r\n switch (fmt) {\r\n case 'hex': {\r\n const m = /^([0-9A-F]{2})/i.exec(s)\r\n const n = m ? parseInt(m[1], 16) : null\r\n return { hexValue: n, numericValue: n, values: null, text: m ? null : s }\r\n }\r\n case 'hexOption': {\r\n const m = /^([0-9A-F]{2})\\s?(.*)$/i.exec(s)\r\n if (!m) return { ...NONE, text: s }\r\n const n = parseInt(m[1], 16)\r\n return { hexValue: n, numericValue: n, values: null, text: m[2].trim() || null }\r\n }\r\n case 'hexPair': {\r\n const m = /^([0-9A-F]{2})[:/]([0-9A-F]{2})/i.exec(s)\r\n if (!m) return { ...NONE, text: s }\r\n const values = [parseInt(m[1], 16), parseInt(m[2], 16)]\r\n return { hexValue: values[0], numericValue: values[0], values, text: null }\r\n }\r\n case 'decimal': {\r\n const n = parseInt(s, 10)\r\n return Number.isNaN(n) ? { ...NONE, text: s } : { hexValue: null, numericValue: n, values: null, text: null }\r\n }\r\n case 'decimalPair': {\r\n const m = /^(\\d+):(\\d+)/.exec(s)\r\n if (!m) return { ...NONE, text: s }\r\n const values = [parseInt(m[1], 10), parseInt(m[2], 10)]\r\n return { hexValue: null, numericValue: values[0], values, text: null }\r\n }\r\n case 'float': {\r\n const n = parseFloat(s.replace(',', '.'))\r\n return Number.isNaN(n) ? { ...NONE, text: s } : { hexValue: null, numericValue: n, values: null, text: null }\r\n }\r\n default:\r\n return { ...NONE, text: s }\r\n }\r\n}\r\n\r\n/** Description of the option currently selected ('CLIP' → \"Values above the maximum level are clipped.\"). */\r\nexport function describeOption(options: Record<string, string> | undefined, text: string | null): string | null {\r\n if (!options || !text) return null\r\n const t = text.toUpperCase().trim()\r\n if (options[t]) return options[t]\r\n const key = Object.keys(options).find((k) => k.length >= 3 && t.startsWith(k))\r\n return key ? options[key] : null\r\n}\r\n","/**\r\n * fxCommands.ts\r\n *\r\n * Lookup of FX commands (the 3-letter names in the FX columns) with what is known\r\n * about them: description, whether they are relative, and which instrument types\r\n * offer them. Data lives in m8-view-context.json (`fxCommands`, `instrumentFx`).\r\n */\r\n\r\nimport schema from './m8-view-context.json'\r\n\r\nexport type FxCategory = 'sequencer' | 'instrument' | 'mixer' | 'modulation'\r\n\r\nexport interface M8FxCommandInfo {\r\n description: string\r\n category: FxCategory\r\n /**\r\n * Relative instrument commands add to the parameter's current value (01-7F add,\r\n * 80-FF subtract) and accumulate until a note is triggered with RET or with an\r\n * instrument number in the I column.\r\n */\r\n relative: boolean\r\n /**\r\n * Instrument types that offer the command, when it is not offered by all of them\r\n * (`null` = every type, or not an instrument command). Only types whose command\r\n * list was read from a device are considered.\r\n */\r\n instrumentTypes: string[] | null\r\n /** Modulator slot 1-4 for envelope/LFO commands (EA1, LF3…). */\r\n slot: number | null\r\n}\r\n\r\ninterface FxEntry {\r\n description: string\r\n relative?: boolean\r\n}\r\n\r\ntype FxCommandMap = Record<string, FxEntry>\r\n\r\nconst fxCommands = schema.fxCommands as unknown as Record<FxCategory, FxCommandMap>\r\nconst instrumentTypes = schema.instrumentFx.types as Record<string, { verified: boolean; commands: string[] }>\r\n\r\nconst VERIFIED_TYPES = Object.entries(instrumentTypes)\r\n .filter(([, t]) => t.verified)\r\n .map(([name]) => name)\r\n\r\n/** Verified instrument types that offer `cmd`, or null when all of them do (or none does). */\r\nfunction typesOffering(cmd: string): string[] | null {\r\n const offering = VERIFIED_TYPES.filter((t) => instrumentTypes[t].commands.includes(cmd))\r\n return offering.length === 0 || offering.length === VERIFIED_TYPES.length ? null : offering\r\n}\r\n\r\n/**\r\n * Look up a 3-character FX command name. Modulator commands carry their slot\r\n * number as the third character (EA1, LF3…). Returns undefined for unknown commands.\r\n */\r\nexport function lookupFxCommand(cmd: string): M8FxCommandInfo | undefined {\r\n const upper = cmd.toUpperCase()\r\n\r\n for (const category of ['sequencer', 'instrument', 'mixer'] as const) {\r\n const entry = fxCommands[category][upper]\r\n if (entry) {\r\n return {\r\n description: entry.description,\r\n category,\r\n relative: entry.relative === true,\r\n instrumentTypes: category === 'instrument' ? typesOffering(upper) : null,\r\n slot: null,\r\n }\r\n }\r\n }\r\n\r\n const mod = /^([A-Z]{2})([1-4])$/.exec(upper)\r\n const entry = mod ? fxCommands.modulation[mod[1]] : undefined\r\n if (mod && entry) {\r\n return {\r\n description: entry.description.replace('modulator N', `modulator ${mod[2]}`),\r\n category: 'modulation',\r\n relative: entry.relative === true,\r\n instrumentTypes: null,\r\n slot: Number(mod[2]),\r\n }\r\n }\r\n return undefined\r\n}\r\n\r\n/**\r\n * True for relative instrument commands: values 01-7F add to the parameter, 80-FF\r\n * subtract (see \"Relative and Absolute FX Commands\" in the manual).\r\n */\r\nexport function isRelativeFx(cmd: string): boolean {\r\n return lookupFxCommand(cmd)?.relative === true\r\n}\r\n\r\nexport interface M8InstrumentFxList {\r\n /** Type key as used in the schema: sampler, macrosynth, fmsynth, hypersynth, midiout. */\r\n type: string\r\n /** Read from a real device (false = taken from documentation). */\r\n verified: boolean\r\n commands: string[]\r\n}\r\n\r\n/** Instrument command names offered for an instrument type, or null when unknown. */\r\nexport function getInstrumentFxCommands(type: string): M8InstrumentFxList | null {\r\n const t = instrumentTypes[type.toLowerCase()]\r\n return t ? { type: type.toLowerCase(), verified: t.verified, commands: [...t.commands] } : null\r\n}\r\n\r\n/** What the manual says about relative commands (for tooltips and descriptions). */\r\nexport const RELATIVE_FX_RULE: string = schema.instrumentFx.relativeCommands.description\r\n","/**\n * fxInsight.ts\n *\n * Explains what an FX value does and follows FX \"lanes\" (FX1/FX2/FX3 columns)\n * down a phrase, so a REP command - or an empty cell sitting under one - can be\n * described in terms of the command it is repeating.\n *\n * Purely structural: works on already-parsed cells, no dependency on viewContext.\n */\n\nimport { isRelativeFx } from './fxCommands'\n\nexport { isRelativeFx }\n\n/** The subset of a parsed cell this module needs. */\nexport interface FxCell {\n rawValue: string\n hexValue: number | null\n isEmpty: boolean\n}\n\n/** One phrase step, indexed by column key ('note', 'fx1cmd', 'fx1val', …). */\nexport interface FxStepRow {\n fields: Record<string, FxCell | undefined>\n}\n\nexport type FxLaneEvent =\n /** Nothing FX-related happens in this lane at this step. */\n | 'none'\n /** A regular command starts a new base value; any REP ends. */\n | 'set'\n /** A regular command replaces a REP that was running. */\n | 'replace'\n /** REP XX (XX>0) starts repeating. */\n | 'rep'\n /** REP XX while another REP was already running (new increment). */\n | 'repChange'\n /** REP 00 stops the repeat. */\n | 'repStop'\n /** REP with no earlier command in this lane and none carried over. */\n | 'orphan'\n /** RTO sets the limit for the running REP. */\n | 'rto'\n /** Empty cell while a REP is active: the repeated command fires again. */\n | 'continue'\n\nexport interface M8FxLaneTrace {\n /** FX lane, 1-3. */\n lane: number\n /** Phrase step this trace was computed for. */\n step: number\n event: FxLaneEvent\n /** Last regular command in the lane at or before `step`. */\n source: { cmd: string; value: number | null; step: number } | null\n /** True when a REP is running after this step. */\n active: boolean\n /** Per-step increment of the running REP (0x01-0xFF, 0x80+ = negative). */\n increment: number | null\n /** Value set with RTO, if any. */\n limit: number | null\n /** Value the repeated command has at this step (null when unknown). */\n value: number | null\n /** True when `value` was clamped by the RTO limit. */\n atLimit: boolean\n /** Step where the running REP began. */\n repStartStep: number | null\n /** True when a REP from a previous phrase is running here (value unknown). */\n carried: boolean\n}\n\n// formatting helpers \n\nexport const hex2 = (n: number): string => n.toString(16).toUpperCase().padStart(2, '0')\nconst hex1 = (n: number): string => n.toString(16).toUpperCase()\nconst times = (n: number): string => (n === 1 ? 'once' : n === 2 ? 'twice' : `${n} times`)\nconst signed8 = (n: number): number => (n > 0x7f ? n - 0x100 : n)\nconst fmtSigned = (n: number): string => `${n > 0 ? '+' : n < 0 ? '−' : '±'}${Math.abs(n)}`\n\n/** Hex text of a value, with its signed reading when the command is relative and negative. */\nexport function formatFxValue(cmd: string, value: number): string {\n if (isRelativeFx(cmd) && value > 0x7f) return `${hex2(value)} (${signed8(value)})`\n return hex2(value)\n}\n\n/** REP increment as shown to the user: +05, −03 (for FD). */\nfunction formatIncrement(inc: number): string {\n return inc > 0x7f ? `−${hex2(0x100 - inc)}` : `+${hex2(inc)}`\n}\n\n// notes (for ARP) \n\nconst PITCH_CLASSES = ['C-', 'C#', 'D-', 'D#', 'E-', 'F-', 'F#', 'G-', 'G#', 'A-', 'A#', 'B-']\n\nfunction noteToIndex(note: string | null | undefined): number | null {\n const m = /^([A-G])([#-])(\\d)$/.exec((note ?? '').trim().toUpperCase())\n if (!m) return null\n const pc = PITCH_CLASSES.indexOf(`${m[1]}${m[2]}`)\n return pc < 0 ? null : Number(m[3]) * 12 + pc\n}\n\nfunction indexToNote(i: number): string {\n return `${PITCH_CLASSES[((i % 12) + 12) % 12]}${Math.floor(i / 12)}`\n}\n\n// value interpretation \n\nexport interface FxValueContext {\n /** Note on the same step (used by ARP to spell out the notes). */\n note?: string | null\n /** Table view: HOP, CHA, DEL, TIC, REP, RTO and MTT mean something different there. */\n inTable?: boolean\n /** Row (phrase step or table row) the command is on: a HOP onto its own row, MTT on the first step. */\n row?: number | null\n}\n\nconst KEY_NAMES = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B']\n\n/** Ticks in a step with the default groove. */\nconst TICKS_PER_STEP = 6\n\n/** How a table rate of `ticks` per row reads in steps: \"2 rows per step\", \"one row every 2 steps\", or ''. */\nfunction rowsPerStep(ticks: number): string {\n if (ticks === TICKS_PER_STEP) return 'one row per step'\n if (TICKS_PER_STEP % ticks === 0) return `${TICKS_PER_STEP / ticks} rows per step`\n if (ticks % TICKS_PER_STEP === 0) return `one row every ${ticks / TICKS_PER_STEP} steps`\n return ''\n}\n\n/** TIC value meaning; `where` is 'this column' in a table, \"the instrument's table\" in a phrase. */\nfunction describeTic(value: number, where: string): string | null {\n if (value === 0) return `${where} moves one row each time the instrument is triggered`\n if (value <= 0xfb) {\n const steps = rowsPerStep(value)\n return `${where} moves one row every ${value} tick${value === 1 ? '' : 's'}${steps ? ` (${steps})` : ''}`\n }\n // FC to FF\n const modes = [\n 'octave map: the octave played picks the table row (instrument tables only)',\n 'velocity map: the velocity played picks the table row (instrument tables only)',\n 'note map: the note played picks the table row (instrument tables only)',\n `${where} moves one row every 5 ms (200 Hz)`,\n ]\n return modes[value - 0xfc] ?? null\n}\n\n/**\n * Short explanation of what `value` does for `cmd` (phrase view), or null when\n * the command has no interpretation worth adding beyond its name.\n */\nexport function describeFxValue(cmd: string, value: number, ctx: FxValueContext = {}): string | null {\n const c = cmd.toUpperCase()\n const x = value >> 4\n const y = value & 0xf\n const ticks = (n: number) => `${n} tick${n === 1 ? '' : 's'}`\n\n switch (c) {\n case 'ARP': {\n const base = noteToIndex(ctx.note)\n const semis = `+${x} and +${y} semitones`\n if (base == null) return `plays the note, then ${semis}`\n return `${semis}: ${indexToNote(base)} ${indexToNote(base + x)} ${indexToNote(base + y)}`\n }\n case 'ARC':\n return `arp mode ${x}, speed ${ticks(y)}`\n case 'CHA':\n if (ctx.inTable) return `everything left of it plays with probability ${value}/255 (00 never, FF always)`\n return `${x}/15 chance for what is left of it, ${y}/15 for what is right (0 never, F always)`\n case 'DEL':\n if (ctx.inTable) return `holds the table's playhead for ${ticks(value)}`\n return `delays this row by ${ticks(value)}`\n case 'KIL':\n return `stops the note after ${ticks(value)}`\n case 'OFF':\n return `note off after ${ticks(value)}`\n case 'PSL':\n return `portamento over ${ticks(value)}`\n case 'GRV':\n return `track uses groove ${hex2(value)}`\n case 'GGR':\n return `all tracks use groove ${hex2(value)}`\n case 'INS':\n return `switches to instrument ${hex2(value)}`\n case 'TBL':\n return `instrument uses table ${hex2(value)}`\n case 'TBX':\n return value === 0 ? 'stops the auxiliary table' : `plays table ${hex2(value)} alongside the instrument table`\n case 'THO':\n if (ctx.inTable) return `every column of the table jumps to row ${hex1(y)}`\n return `the instrument's table jumps to row ${hex1(y)} (the aux table when a TBX is on this step)`\n case 'HOP':\n if (ctx.inTable) {\n // A HOP that lands on its own row keeps landing there: the column stops.\n if (y === ctx.row) return x === 0 ? `this column stops on row ${hex1(y)}` : `holds this column on row ${hex1(y)} ${times(x)}`\n if (x === 0) return `always jumps back to row ${hex1(y)}: the rows in between loop`\n return `jumps to row ${hex1(y)} ${times(x)}, then the column runs on past it`\n }\n return value === 0xff ? 'stops this track' : `jumps to row ${hex1(y)} of the next phrase`\n case 'REP':\n if (!ctx.inTable || value === 0) return null\n return `plays the command above it ${value} more time${value === 1 ? '' : 's'}, one row each`\n case 'RTO': {\n if (!ctx.inTable) return null\n const total = value > 0x7f ? `${hex2(value)} (${signed8(value)})` : hex2(value)\n return `repeats the relative command above it until it has added ${total} in total`\n }\n case 'RMX':\n return `tracks to its left (selection ${hex1(x)}) jump to phrase row ${hex1(y)}`\n case 'RND':\n return `randomises the previous FX: left range ${x}, right range ${y} (0 none, F full)`\n case 'RNL':\n return `randomises the FX to its left: left range ${x}, right range ${y} (0 none, F full)`\n case 'RET':\n if (y === 0) return `single retrig after ${ticks(x)}`\n return `retrigs every ${ticks(y)}, volume ${x < 8 ? 'falls' : 'rises'} on each retrig (ramp ${x})`\n case 'PVB':\n case 'PVX':\n return `vibrato speed ${x}, depth ${y}`\n case 'PBN':\n return value < 0x80 ? `pitch slides up by ${value}` : `pitch slides down by ${0x100 - value}`\n case 'SCA':\n case 'SCG': {\n const key = KEY_NAMES[x]\n return `${c === 'SCA' ? 'track' : 'song'} scale ${y.toString(16).toUpperCase()}${key ? `, key ${key}` : ''}`\n }\n case 'SNG':\n return `moves the song position by ${fmtSigned(signed8(value))} row(s)`\n case 'SED':\n return `random seed ${hex2(value)}`\n case 'NXT':\n return `plays instrument ${hex2(value)} on the next track`\n case 'MTT': {\n if (ctx.inTable) return 'no effect in a table'\n const eighths = signed8(value)\n if (eighths < 0 && ctx.row === 0) return 'no effect: a row cannot be moved earlier on the first step'\n return eighths === 0 ? 'no timing shift' : `shifts the row by ${fmtSigned(eighths)}/8 tick`\n }\n case 'TPO':\n return `${value} BPM`\n case 'TSP':\n return `transposes the song by ${fmtSigned(signed8(value))} semitones`\n case 'TIC':\n return describeTic(value, ctx.inTable ? 'this column' : \"the instrument's table\")\n case 'XRZ':\n return value > 0 ? 'reverb freeze on' : 'reverb freeze off'\n default:\n break\n }\n\n if (isRelativeFx(c)) {\n const s = signed8(value)\n if (c === 'PIT') return s === 0 ? 'no change' : `${fmtSigned(s)} semitones`\n return s === 0 ? 'no change' : fmtSigned(s)\n }\n return null\n}\n\n// FX lane tracing (REP / RTO) \n\nfunction advance(running: number, inc: number, limit: number | null): { value: number; atLimit: boolean } {\n let next = (running + inc) & 0xff\n if (limit != null) {\n const rising = inc < 0x80\n // Past the limit, or wrapped around 8 bits on the way there → hold at the limit.\n if (rising ? next < running || next > limit : next > running || next < limit) next = limit\n }\n return { value: next, atLimit: limit != null && next === limit }\n}\n\n/**\n * Walks FX lane `lane` (1-3) from step 0 to `step` and reports what is in effect\n * there. `rows` must hold every step of the phrase, index = step.\n *\n * Model (manual: \"Repeat\"): REP XX repeats the lane's last command with its value\n * bumped by XX on each step, keeps going through empty cells, and stops on a new\n * command or REP00. RTO XX in the same lane sets the value REP stops at.\n * `carriedIn` says a REP from a previous phrase is running (the '^^' marker).\n */\nexport function analyzeFxLane(rows: FxStepRow[], lane: number, step: number, carriedIn = false): M8FxLaneTrace {\n let source: M8FxLaneTrace['source'] = null\n let running: number | null = null\n let inc: number | null = null\n let limit: number | null = null\n let active = carriedIn\n let atLimit = false\n let repStart: number | null = null\n let event: FxLaneEvent = 'none'\n\n const last = Math.min(step, rows.length - 1)\n for (let i = 0; i <= last; i += 1) {\n const cmdCell = rows[i].fields[`fx${lane}cmd`]\n const valCell = rows[i].fields[`fx${lane}val`]\n event = 'none'\n\n if (cmdCell && !cmdCell.isEmpty) {\n const cmd = cmdCell.rawValue.toUpperCase()\n const val = valCell && !valCell.isEmpty ? valCell.hexValue : null\n\n if (cmd === 'REP') {\n if (!val) {\n event = 'repStop'\n active = false\n inc = null\n atLimit = false\n } else if (!source && !carriedIn) {\n event = 'orphan'\n } else {\n event = active ? 'repChange' : 'rep'\n if (!active) repStart = i\n active = true\n inc = val\n if (running != null) ({ value: running, atLimit } = advance(running, inc, limit))\n }\n } else if (cmd === 'RTO') {\n event = 'rto'\n limit = val\n } else {\n event = active ? 'replace' : 'set'\n source = { cmd, value: val, step: i }\n running = val\n active = false\n inc = null\n limit = null\n atLimit = false\n repStart = null\n }\n } else if (active) {\n event = 'continue'\n if (running != null && inc != null) ({ value: running, atLimit } = advance(running, inc, limit))\n }\n }\n\n return {\n lane,\n step,\n event,\n source,\n active,\n increment: inc,\n limit,\n value: active ? running : null,\n atLimit,\n repStartStep: repStart,\n carried: active && !source,\n }\n}\n\n/**\n * One-sentence explanation of a lane trace, or null when the lane has nothing to\n * say at this step. Meant to be appended to the cell description.\n */\nexport function describeFxLane(t: M8FxLaneTrace): string | null {\n const src = t.source\n const val = (v: number | null) => (v == null ? '??' : src ? formatFxValue(src.cmd, v) : hex2(v))\n const inc = t.increment != null ? formatIncrement(t.increment) : ''\n const at = t.atLimit && t.limit != null ? `, held at the RTO limit ${hex2(t.limit)}` : ''\n\n switch (t.event) {\n case 'rep':\n case 'repChange': {\n if (t.carried) return `REP ${inc} per step continues the repeat from the previous phrase`\n const verb = t.event === 'rep' ? 'repeats' : 'now repeats'\n return `${verb} ${src?.cmd} from ${val(src?.value ?? null)} (step ${src?.step.toString(16).toUpperCase()}), ${inc} per step → ${val(t.value)} on this step${at}`\n }\n case 'continue':\n if (t.carried) return 'REP from a previous phrase is still active in this lane (^^)'\n return `REP still active: ${src?.cmd} reaches ${val(t.value)} on this step (${inc} per step since step ${t.repStartStep?.toString(16).toUpperCase()})${at}`\n case 'repStop':\n return src ? `REP00 stops the repeat of ${src.cmd}` : 'REP00 stops the repeat'\n case 'orphan':\n return 'REP has no earlier command in this lane of the phrase to repeat'\n case 'rto':\n return t.active && t.limit != null ? `sets the value the running REP stops at (${hex2(t.limit)})` : 'sets the stop value for a REP'\n case 'replace':\n return src ? `replaces the running REP (${src.cmd} is no longer repeated)` : null\n default:\n return null\n }\n}\n\n// relative commands \n\n/**\n * What a relative instrument command (VOL, CUT, PIT…) has done to its parameter\n * since the last note trigger.\n */\nexport interface M8RelativeFxState {\n cmd: string\n /** Net change applied to the parameter (signed; the device clamps to the parameter's range). */\n total: number\n /** Times the command was applied (REP-driven repeats included). */\n count: number\n /** Step of the last trigger that reset it, or null when none happened in this phrase. */\n resetStep: number | null\n /** What reset it: a row with an instrument number in the I column, or a RET command. */\n resetBy: 'instrument' | 'ret' | null\n /** True when some of the applications came from a running REP. */\n viaRep: boolean\n}\n\n/**\n * What makes step `row` trigger the instrument again, which resets every relative change:\n * an instrument number in the I column, a RET command, or nothing (null).\n */\nexport function relativeResetBy(row: FxStepRow): M8RelativeFxState['resetBy'] {\n const inst = row.fields.inst\n if (inst && !inst.isEmpty) return 'instrument'\n const retriggers = [1, 2, 3].some((lane) => {\n const c = row.fields[`fx${lane}cmd`]\n return c != null && !c.isEmpty && c.rawValue.toUpperCase() === 'RET'\n })\n return retriggers ? 'ret' : null\n}\n\n/**\n * Follows every relative instrument command through the phrase up to `step` (inclusive).\n *\n * Model (manual: \"Relative and Absolute FX Commands\"): a relative command adds its\n * value (01-7F) or subtracts it (80-FF) from the instrument's parameter, and the change\n * stays until a note is triggered with a RET command or with an instrument number in\n * the I column, which puts every parameter back to the instrument's assigned value.\n * A row that triggers resets first, then applies its own commands. State from a\n * previous phrase isn't visible, so totals only cover this phrase.\n */\nexport function analyzeRelativeFx(\n rows: FxStepRow[],\n step: number,\n carriedLane: number | null = null,\n): Record<string, M8RelativeFxState> {\n const states: Record<string, M8RelativeFxState> = {}\n let resetStep: number | null = null\n let resetBy: M8RelativeFxState['resetBy'] = null\n\n const last = Math.min(step, rows.length - 1)\n for (let i = 0; i <= last; i += 1) {\n const trigger = relativeResetBy(rows[i])\n if (trigger) {\n for (const k of Object.keys(states)) delete states[k]\n resetStep = i\n resetBy = trigger\n }\n\n for (const lane of [1, 2, 3]) {\n const trace = analyzeFxLane(rows, lane, i, carriedLane === lane)\n const src = trace.source\n if (!src || trace.carried || !isRelativeFx(src.cmd)) continue\n\n let applied: number | null = null\n let viaRep = false\n if (trace.event === 'set' || trace.event === 'replace') applied = src.value\n else if (trace.event === 'rep' || trace.event === 'repChange' || trace.event === 'continue') {\n applied = trace.value\n viaRep = true\n }\n if (applied == null) continue\n\n let st = states[src.cmd]\n if (!st) {\n st = { cmd: src.cmd, total: 0, count: 0, resetStep, resetBy, viaRep: false }\n states[src.cmd] = st\n }\n st.total += signed8(applied)\n st.count += 1\n st.viaRep ||= viaRep\n }\n }\n return states\n}\n\n/** \"+0A\", \"−1F\" — the net change of a relative command, in hex like the values on screen. */\nconst fmtOffset = (n: number): string => (n === 0 ? '±00' : `${n > 0 ? '+' : '−'}${hex2(Math.abs(n))}`)\n\n/** Where the accumulation started, for messages. */\nfunction sinceText(s: M8RelativeFxState): string {\n if (s.resetStep == null) return 'the start of this phrase (earlier state unknown)'\n const at = s.resetStep.toString(16).toUpperCase()\n return s.resetBy === 'ret' ? `the RET on step ${at}` : `the instrument number on step ${at}`\n}\n\nconst RESET_HINT = 'stays until a note with an instrument number (I) or a RET resets it'\n\n/** Sentence about a relative command's accumulated effect, for the cell that applies it. */\nexport function describeRelativeFx(s: M8RelativeFxState | null | undefined): string {\n if (!s || s.count <= 1) return `relative: ${RESET_HINT}`\n const rep = s.viaRep ? ', including REP repeats' : ''\n const moved = s.total === 0 ? `has had no net effect` : `has moved by ${fmtOffset(s.total)}`\n return `relative: ${s.cmd} ${moved} over ${s.count} steps since ${sinceText(s)}${rep}; ${RESET_HINT}`\n}\n\n/**\n * For an instrument number typed on a step: which relative changes are still in force\n * and will be wiped by the note it triggers. Null when nothing relative is active.\n */\nexport function describeRelativeReset(active: Record<string, M8RelativeFxState>): string | null {\n const list = Object.values(active).filter((s) => s.total !== 0)\n if (!list.length) return null\n return `triggers the instrument again: resets the relative changes still in force (${list.map((s) => `${s.cmd} ${fmtOffset(s.total)}`).join(', ')})`\n}\n","/**\r\n * scaleInsight.ts\r\n *\r\n * Interprets the Scale View: which of the 12 note intervals are enabled, what\r\n * well-known scale that shape spells, and what a given row means musically.\r\n */\r\n\r\n/** Interval row labels in screen order. */\r\nexport const SCALE_INTERVALS = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'] as const\r\n\r\nexport interface M8ScaleNote {\r\n /** Row label, e.g. 'D#'. */\r\n interval: string\r\n /** Semitones above the first row ('C'), 0-11. */\r\n semitone: number\r\n /** true = ON, false = out of the scale ('--' on the device, 'OFF' if shown), null = not read. */\r\n enabled: boolean | null\r\n /** Detune in semitones (-24..+24), null when unreadable. */\r\n offset: number | null\r\n}\r\n\r\nexport interface M8ScaleInfo {\r\n /** The 12 rows, in screen order. */\r\n notes: M8ScaleNote[]\r\n /** Labels of the enabled intervals, e.g. ['C', 'D', 'E', …]. */\r\n enabled: string[]\r\n /** Well-known scales this set of notes spells, most natural reading first. */\r\n shapes: string[]\r\n /** Intervals carrying a non-zero detune. */\r\n detuned: { interval: string; offset: number }[]\r\n /** One-line summary, e.g. \"C D E F G A B — Major (Ionian)\". */\r\n summary: string\r\n}\r\n\r\nconst SCALES: { name: string; steps: number[] }[] = [\r\n { name: 'Major (Ionian)', steps: [0, 2, 4, 5, 7, 9, 11] },\r\n { name: 'Natural minor (Aeolian)', steps: [0, 2, 3, 5, 7, 8, 10] },\r\n { name: 'Harmonic minor', steps: [0, 2, 3, 5, 7, 8, 11] },\r\n { name: 'Melodic minor', steps: [0, 2, 3, 5, 7, 9, 11] },\r\n { name: 'Dorian', steps: [0, 2, 3, 5, 7, 9, 10] },\r\n { name: 'Phrygian', steps: [0, 1, 3, 5, 7, 8, 10] },\r\n { name: 'Lydian', steps: [0, 2, 4, 6, 7, 9, 11] },\r\n { name: 'Mixolydian', steps: [0, 2, 4, 5, 7, 9, 10] },\r\n { name: 'Locrian', steps: [0, 1, 3, 5, 6, 8, 10] },\r\n { name: 'Phrygian dominant', steps: [0, 1, 4, 5, 7, 8, 10] },\r\n { name: 'Double harmonic', steps: [0, 1, 4, 5, 7, 8, 11] },\r\n { name: 'Major pentatonic', steps: [0, 2, 4, 7, 9] },\r\n { name: 'Minor pentatonic', steps: [0, 3, 5, 7, 10] },\r\n { name: 'Blues', steps: [0, 3, 5, 6, 7, 10] },\r\n { name: 'Whole tone', steps: [0, 2, 4, 6, 8, 10] },\r\n { name: 'Diminished (whole-half)', steps: [0, 2, 3, 5, 6, 8, 9, 11] },\r\n { name: 'Diminished (half-whole)', steps: [0, 1, 3, 4, 6, 7, 9, 10] },\r\n { name: 'Chromatic', steps: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] },\r\n]\r\n\r\nconst INTERVAL_NAMES = [\r\n 'the root',\r\n 'a minor 2nd',\r\n 'a major 2nd',\r\n 'a minor 3rd',\r\n 'a major 3rd',\r\n 'a perfect 4th',\r\n 'a tritone',\r\n 'a perfect 5th',\r\n 'a minor 6th',\r\n 'a major 6th',\r\n 'a minor 7th',\r\n 'a major 7th',\r\n]\r\n\r\n/** 'C-' → 'C', 'C#' → 'C#'. Returns null when the text is not an interval label. */\r\nexport function normalizeInterval(raw: string): string | null {\r\n const s = raw.replace(/[-\\s]/g, '').toUpperCase()\r\n return (SCALE_INTERVALS as readonly string[]).includes(s) ? s : null\r\n}\r\n\r\n/** \"a major 3rd (+4 semitones above C)\" for a given interval label. */\r\nexport function describeInterval(interval: string): string {\r\n const semitone = (SCALE_INTERVALS as readonly string[]).indexOf(interval)\r\n if (semitone <= 0) return semitone === 0 ? 'the first row (the scale root)' : interval\r\n return `${INTERVAL_NAMES[semitone]} (+${semitone} semitones above C)`\r\n}\r\n\r\n/** Names of well-known scales whose notes equal the enabled set, C-rooted reading first. */\r\nexport function matchScaleShapes(enabledSemitones: number[]): string[] {\r\n const set = new Set(enabledSemitones)\r\n if (set.size === 0) return []\r\n const hits: { root: number; name: string }[] = []\r\n for (const scale of SCALES) {\r\n if (scale.steps.length !== set.size) continue\r\n for (let root = 0; root < 12; root += 1) {\r\n if (scale.steps.every((s) => set.has((s + root) % 12))) hits.push({ root, name: scale.name })\r\n }\r\n }\r\n // Tonic on the first row ('C') is the natural reading; the rest are modes of the same notes.\r\n hits.sort((a, b) => a.root - b.root)\r\n return hits.map((h) => (h.root === 0 ? h.name : `${h.name} from ${SCALE_INTERVALS[h.root]}`))\r\n}\r\n\r\nexport function analyzeScale(notes: M8ScaleNote[]): M8ScaleInfo {\r\n const enabledNotes = notes.filter((n) => n.enabled === true)\r\n const enabled = enabledNotes.map((n) => n.interval)\r\n const shapes = matchScaleShapes(enabledNotes.map((n) => n.semitone))\r\n const detuned = notes\r\n .filter((n) => n.enabled === true && n.offset != null && n.offset !== 0)\r\n .map((n) => ({ interval: n.interval, offset: n.offset as number }))\r\n\r\n const summary = scaleSummaryParts({ enabled, shapes, detuned }).join(' — ')\r\n return { notes, enabled, shapes, detuned, summary }\r\n}\r\n\r\n/**\r\n * The two halves of the summary: the enabled notes (\"C D E F G A B\"), then the scale\r\n * they spell with any detune (\"Major (Ionian); detuned: E −0.50 st\"), when notes are enabled.\r\n */\r\nexport function scaleSummaryParts(info: Pick<M8ScaleInfo, 'enabled' | 'shapes' | 'detuned'>): string[] {\r\n const { enabled, shapes, detuned } = info\r\n if (!enabled.length) return ['no notes enabled']\r\n let shape = shapes[0] ?? `${enabled.length} notes, custom scale`\r\n if (detuned.length) shape += `; detuned: ${detuned.map((d) => `${d.interval} ${formatOffset(d.offset)}`).join(', ')}`\r\n return [enabled.join(' '), shape]\r\n}\r\n\r\n/** '+0.50 st' style detune text. */\r\nexport function formatOffset(offset: number): string {\r\n const sign = offset > 0 ? '+' : offset < 0 ? '−' : ''\r\n return `${sign}${Math.abs(offset).toFixed(2)} st`\r\n}\r\n","/**\r\n * viewContext.ts\r\n *\r\n * Derives semantic meaning from raw M8State by mapping cursor position and\r\n * currentLine content to typed, labelled fields using the m8-view-context.json schema.\r\n *\r\n * Every view listed in viewlist.json is mapped: the grid views (song, chain, phrase,\r\n * table, groove, scale, instrument pool) through their columns, the parameter views\r\n * (instrument, modulation, mixer, project, effects, EQ, scope, system settings, file\r\n * browsers) through the label/value layouts in m8-parameter-views.json.\r\n */\r\n\r\nimport type { M8Screen, M8State } from './types'\r\nimport schema from './m8-view-context.json'\r\nimport {\r\n describeOption,\r\n detectSection,\r\n detectSlot,\r\n getInstrumentType,\r\n getParameterLayout,\r\n instrumentTypeFromLines,\r\n parseValue,\r\n readParameterCells,\r\n type ParameterLayout,\r\n type ValueFormat,\r\n} from './parameterViews'\r\nimport {\r\n analyzeFxLane,\r\n analyzeRelativeFx,\r\n describeFxLane,\r\n describeFxValue,\r\n describeRelativeFx,\r\n describeRelativeReset,\r\n formatFxValue,\r\n isRelativeFx,\r\n relativeResetBy,\r\n type M8FxLaneTrace,\r\n type M8RelativeFxState,\r\n} from './fxInsight'\r\nimport { lookupFxCommand, type M8FxCommandInfo } from './fxCommands'\r\nimport {\r\n analyzeScale,\r\n describeInterval,\r\n formatOffset,\r\n normalizeInterval,\r\n SCALE_INTERVALS,\r\n scaleSummaryParts,\r\n type M8ScaleInfo,\r\n type M8ScaleNote,\r\n} from './scaleInsight'\r\n\r\nexport type { M8FxLaneTrace, M8RelativeFxState } from './fxInsight'\r\nexport { getInstrumentFxCommands, lookupFxCommand, RELATIVE_FX_RULE } from './fxCommands'\r\nexport type { M8FxCommandInfo, M8InstrumentFxList } from './fxCommands'\r\nexport type { M8ScaleInfo, M8ScaleNote } from './scaleInsight'\r\n\r\n// Public field type union \r\n\r\nexport type M8FieldType =\r\n | 'chainRef'\r\n | 'phraseRef'\r\n | 'note'\r\n | 'velocity'\r\n | 'instrumentRef'\r\n | 'fxCommand'\r\n | 'fxValue'\r\n | 'transpose'\r\n | 'ticks'\r\n | 'volume'\r\n | 'ppq'\r\n | 'instrumentName'\r\n | 'eqSlot'\r\n | 'rowIndex'\r\n | 'noteInterval'\r\n | 'onOff'\r\n | 'semitoneOffset'\r\n | 'swing'\r\n /** Instrument engine of the TYPE field (WAVSYNTH, SAMPLER, …). */\r\n | 'instrumentType'\r\n /** Free-text name field (instrument, song, directory). */\r\n | 'name'\r\n /** Named, editable value (CUTOFF, TEMPO, GAIN, …). */\r\n | 'parameter'\r\n /** Choice from a list, e.g. '00CHORUS', 'BELL'. */\r\n | 'option'\r\n /** Button-like caption (LOAD, SAVE, RENDER, SETTINGS, …). */\r\n | 'action'\r\n /** Row of a file browser ('/folder', '/..', 'Bass.m8i'). */\r\n | 'fileEntry'\r\n\r\n/** What a file browser entry is: '/..', '/folder', 'Bass.m8i', 'kick.wav', 'song.m8s', anything else. */\r\nexport type M8FileKind = 'parent' | 'directory' | 'instrumentPreset' | 'sample' | 'm8file' | 'file'\r\n\r\n// Schema shapes (mirror m8-view-context.json / m8-parameter-views.json)\r\n\r\nexport interface ColumnDef {\r\n x: number\r\n width: number\r\n key: string\r\n type: M8FieldType\r\n label: string\r\n description?: string\r\n meta?: Record<string, unknown>\r\n /** How to read the text (parameter views); grid views use the type's built-in reading. */\r\n format?: ValueFormat\r\n /** Unit appended when showing the value (' BPM', ' dB', ' ms'). */\r\n unit?: string\r\n /** Names of the numbers of an 'XX:YY' pair (['delay', 'repeat']). */\r\n parts?: string[]\r\n /** Explanations of the options a choice cell can show, keyed by option name. */\r\n options?: Record<string, string>\r\n}\r\n\r\ninterface RowFieldDef {\r\n x: number\r\n width: number\r\n parse: 'hex' | 'decimal'\r\n type: M8FieldType\r\n key: string\r\n description?: string\r\n}\r\n\r\ninterface ViewDef {\r\n title: string\r\n type: 'grid' | 'parameter'\r\n description: string\r\n rowField?: RowFieldDef\r\n /** Column definitions for grid views. Parameter views have no columns. */\r\n columns?: ColumnDef[]\r\n}\r\n\r\n// Public result types \r\n\r\n/**\r\n * A single parsed field value extracted from the currentLine.\r\n */\r\nexport interface M8ParsedField {\r\n /** Column key as defined in the schema (e.g. 'fx1cmd', 'note', 'track3'). */\r\n key: string\r\n /** Human-readable label (e.g. 'FX1 Command', 'Note (N)'). */\r\n label: string\r\n /** Semantic type of this field. */\r\n type: M8FieldType\r\n /** Raw text extracted from currentLine (trimmed). */\r\n rawValue: string\r\n /**\r\n * Parsed integer (from hex) when the field holds a numeric value.\r\n * `null` for empty cells ('--', '---') and text-only types (note name, fxCommand).\r\n */\r\n hexValue: number | null\r\n /** True when the cell is empty/unused ('--', '---', '---00'). */\r\n isEmpty: boolean\r\n /** Extra view-specific metadata (e.g. { trackIndex: 2 } for song view track columns). */\r\n meta?: Record<string, unknown>\r\n /** What this field is, from the schema (parameter views). */\r\n description?: string\r\n /**\r\n * Parameter views: the non-numeric part of the value - option name ('CHORUS'),\r\n * button caption ('LOAD'), instrument type ('WAVSYNTH'), name text. null otherwise.\r\n */\r\n text?: string | null\r\n /** Parameter views: the value as a number in its natural base (hex, decimal or fractional). */\r\n numericValue?: number | null\r\n /** Parameter views: both numbers of an 'XX:YY' pair (e.g. delay time left/right). */\r\n values?: number[] | null\r\n /** Names of the numbers in `values`, e.g. ['left time', 'right time']. */\r\n parts?: string[]\r\n /** Unit of the value, e.g. ' BPM'. */\r\n unit?: string\r\n /** What the currently selected option does (choice cells that list their options). */\r\n valueDescription?: string\r\n /** File browser rows: what the entry is. */\r\n fileKind?: M8FileKind\r\n}\r\n\r\n/**\r\n * All fields parsed from the current row (line at cursorPos.y).\r\n */\r\nexport interface M8ParsedRow {\r\n /** Row index (hex-parsed) derived from the leftmost chars of currentLine. null if not parseable. */\r\n rowIndex: number | null\r\n /** Schema key for the row index field (e.g. 'songRow', 'step', 'chainPos'). */\r\n rowKey: string\r\n /** Parameter views: id of the schema row that matched this line ('tempo', 'gain', …); null for grid views. */\r\n rowId: string | null\r\n /** Map of column key → parsed field for every column in this view. */\r\n fields: Record<string, M8ParsedField>\r\n}\r\n\r\n/**\r\n * The field currently under the cursor, plus row context.\r\n */\r\nexport interface M8ActiveField extends M8ParsedField {\r\n /** Repetition of the view name for convenience. */\r\n viewName: string\r\n /** Row index of the line containing this field (null if not parseable). */\r\n rowIndex: number | null\r\n}\r\n\r\n/**\r\n * Full semantic context for the current M8State.\r\n */\r\nexport interface M8SemanticContext {\r\n /** M8 view name (lowercased), e.g. 'song', 'phrase'. */\r\n viewName: string\r\n /** Human-readable view title from schema ('Song View', 'Phrase View', …). */\r\n viewTitle: string | null\r\n /** View description from the schema. */\r\n viewDescription: string | null\r\n /**\r\n * The numeric ID of the current view item, parsed from the screen title.\r\n * e.g. 'F2' from ' CHAIN F2*', '03' from ' PHRASE 03'.\r\n * null for views that have no item number (song, instrument pool).\r\n */\r\n viewId: string | null\r\n /** True when the title carries a '*' suffix, indicating unsaved changes. */\r\n isUnsaved: boolean\r\n /** Whether this view is a structured grid (song/chain/phrase/table/groove). */\r\n isGridView: boolean\r\n /** Whether this is a label/value parameter view (instrument, mixer, project, effects, EQ, …). */\r\n isParameterView: boolean\r\n /** Effect Settings View: the block the cursor is in ('MODFX', 'DELAY', 'REVERB'). */\r\n section: string | null\r\n /**\r\n * Instrument View: the engine on the TYPE row as displayed ('WAVSYNTH'), and its\r\n * normalised key ('wavsynth', 'macrosynth', 'sampler', 'fmsynth', 'hypersynth',\r\n * 'external', 'midiout'). Needs the screen; null otherwise.\r\n */\r\n instrumentType: { text: string; key: string | null } | null\r\n /** All fields on the line at cursorPos.y (null for non-grid views or missing line). */\r\n row: M8ParsedRow | null\r\n /** The specific field under the cursor (null when cursor is out of any column range). */\r\n activeField: M8ActiveField | null\r\n /** Text of the cursor row with the playback marker removed (null when there is none). */\r\n lineText: string | null\r\n /**\r\n * Every step of the current phrase (index = step 0-F), so an FX cell can be read\r\n * against the cells above it. Only filled for the phrase view, and only when a\r\n * screen matching the cursor row was passed to getSemanticContext().\r\n */\r\n steps: M8ParsedRow[] | null\r\n /**\r\n * FX lane (1-3) that shows the '^^' marker: a REP from a previous phrase is still\r\n * running there. null when no marker is on screen or there is no screen.\r\n */\r\n carriedRepLane: number | null\r\n /** What the active FX cell means in the light of its lane (phrase view with `steps`). */\r\n fxLane: M8FxLaneTrace | null\r\n /**\r\n * Relative instrument commands (VOL, CUT…) in force after the active step, with their\r\n * net change since the last note trigger (an instrument number in the I column or a RET).\r\n * Phrase view with `steps` only; null otherwise.\r\n */\r\n relativeFx: Record<string, M8RelativeFxState> | null\r\n /**\r\n * When the cursor step triggers the instrument again (an instrument number in the I\r\n * column, or a RET), the relative commands in force just before it: the trigger puts\r\n * their parameters back. Entries can have a net change of 0. Phrase view with `steps`,\r\n * from step 1 on; null otherwise.\r\n */\r\n resetsRelative: Record<string, M8RelativeFxState> | null\r\n /** Scale View: the interval row under the cursor. Null on the other rows and views. */\r\n scaleNote: M8ScaleNote | null\r\n /** Scale View only, when the whole screen was passed: the 12 intervals interpreted together. */\r\n scale: M8ScaleInfo | null\r\n}\r\n\r\n/** What a part of the description says, so a consumer can keep, drop, reorder or style it. */\r\nexport type M8DescriptionPartKind =\r\n /** Label and value of the field under the cursor: \"FX1 Command: KIL\", \"Tempo (TEMPO): 120.00 BPM\". */\r\n | 'field'\r\n /** What the FX command does in general: \"(Kill: stops the playing instrument after XX ticks)\". */\r\n | 'fxInfo'\r\n /** On an FX value, the command it belongs to: \"for KIL (…)\". */\r\n | 'fxTarget'\r\n /** What the value does here: \"value 03: stops the note after 3 ticks\", \"E is detuned by −0.50 st\". */\r\n | 'meaning'\r\n /** The FX lane read down the phrase: a running REP, its RTO limit, REP00… */\r\n | 'fxLane'\r\n /** Relative commands: their net change since the last trigger, or what a trigger resets. */\r\n | 'relative'\r\n /** What the selected option does. */\r\n | 'optionInfo'\r\n /** What the field is. */\r\n | 'fieldInfo'\r\n /** File browser: what the entry is and what [EDIT] does. */\r\n | 'fileInfo'\r\n /** Instrument View: the engine (\"Wavsynth instrument\"). */\r\n | 'instrument'\r\n /** Effect Settings block or modulator slot (\"Delay\", \"MOD2\"). */\r\n | 'section'\r\n /** Scale View: the scale the 12 intervals spell. */\r\n | 'scale'\r\n /** Song View: the track of the cell. */\r\n | 'track'\r\n /** Where the cursor is: view, item number, row (\"Phrase View row 07\", \"Mixer View\"). */\r\n | 'location'\r\n /** Text of the cursor row, when no field on it is recognised. */\r\n | 'line'\r\n\r\n/** One piece of describeContext(); a kind can appear more than once. */\r\nexport interface M8DescriptionPart {\r\n kind: M8DescriptionPartKind\r\n text: string\r\n}\r\n\r\n// Internal helpers \r\n\r\nconst EMPTY_PATTERNS = new Set(['--', '---', '---00', '----', '-'])\r\n\r\nfunction isEmptyRaw(raw: string): boolean {\r\n if (!raw) return true\r\n // Also count strings that are all dashes\r\n return EMPTY_PATTERNS.has(raw) || /^-+$/.test(raw)\r\n}\r\n\r\nfunction parseHex(raw: string): number | null {\r\n const trimmed = raw.trim()\r\n if (!trimmed || isEmptyRaw(trimmed)) return null\r\n const n = parseInt(trimmed, 16)\r\n return Number.isNaN(n) ? null : n\r\n}\r\n\r\nfunction extractFromLine(line: string, x: number, width: number): string {\r\n // Guard: x may exceed line length (line might be shorter than full 40-char row)\r\n if (x >= line.length) return ''\r\n return line.slice(x, Math.min(x + width, line.length))\r\n}\r\n\r\n/** The item number of a screen title and the marker after it ('PHRASE 1F*', 'PHRASE 27^'). */\r\nexport function parseViewTitle(rawTitle: string | null): { viewId: string | null; isUnsaved: boolean; marker: '*' | '^' | null } {\r\n return extractViewId(rawTitle)\r\n}\r\n\r\n/**\r\n * Extracts the item ID and the marker after it from a raw M8 screen title.\r\n * Titles follow the pattern ' VIEWNAME ID* ' where ID is 1–2 hex chars, followed by\r\n * an optional '*' (used elsewhere / unsaved) or '^' (seen on phrases on a Model:02,\r\n * firmware 6.5). Returns { viewId: null, ... } for views without an item number.\r\n */\r\nfunction extractViewId(rawTitle: string | null): { viewId: string | null; isUnsaved: boolean; marker: '*' | '^' | null } {\r\n if (!rawTitle) return { viewId: null, isUnsaved: false, marker: null }\r\n // The M8 font draws a slashed zero as '{' / '}' in some titles (e.g. 'PHRASE {2').\r\n const trimmed = rawTitle.replace(/[{}]/g, '0').trim()\r\n const markerMatch = /\\s*([*^])$/.exec(trimmed)\r\n const marker = (markerMatch?.[1] as '*' | '^' | undefined) ?? null\r\n const withoutMarker = markerMatch ? trimmed.slice(0, markerMatch.index) : trimmed\r\n const parts = withoutMarker.split(/\\s+/)\r\n const last = parts[parts.length - 1] ?? ''\r\n // Accept 1–2 uppercase hex digits as a view ID\r\n const viewId = /^[0-9A-Fa-f]{1,2}$/.test(last) ? last.toUpperCase() : null\r\n return { viewId, isUnsaved: marker === '*', marker }\r\n}\r\n\r\n/**\r\n * When a row is currently playing, the M8 prepends a single non-alphanumeric\r\n * indicator character (e.g. '<' or '>') to currentLine, shifting all data\r\n * positions right by 1. Strip it so column offsets remain consistent.\r\n * Returns the clean line and the number of stripped characters (0 or 1).\r\n */\r\nfunction stripPlaybackIndicator(line: string): { line: string; offset: number } {\r\n if (line.length > 0 && /^[^a-zA-Z0-9 ]/.test(line[0])) {\r\n return { line: line.slice(1), offset: 1 }\r\n }\r\n return { line, offset: 0 }\r\n}\r\n\r\nfunction parseColumn(col: ColumnDef, line: string): M8ParsedField {\r\n let raw = extractFromLine(line, col.x, col.width).trim()\r\n // The '^^' marker (a REP still running from a previous phrase) is not cell content.\r\n if (raw.includes('^') && (col.type === 'fxCommand' || col.type === 'fxValue')) raw = ''\r\n const empty = isEmptyRaw(raw)\r\n\r\n // Parameter views say how to read each cell; grid views use the type's built-in reading below.\r\n if (col.format) {\r\n const v = empty ? null : parseValue(raw, col.format)\r\n // Option names and captions come from a fixed vocabulary and are shown upper case;\r\n // names the user typed and file names keep the case the device sent.\r\n const keepCase = col.type === 'name' || col.type === 'fileEntry'\r\n const text = v?.text != null && !keepCase ? v.text.toUpperCase() : (v?.text ?? null)\r\n return {\r\n key: col.key,\r\n label: col.label,\r\n type: col.type,\r\n rawValue: raw,\r\n hexValue: v?.hexValue ?? null,\r\n isEmpty: empty,\r\n meta: col.meta,\r\n description: col.description,\r\n text,\r\n numericValue: v?.numericValue ?? null,\r\n values: v?.values ?? null,\r\n parts: col.parts,\r\n unit: col.unit,\r\n valueDescription: (!empty && describeOption(col.options, text ?? raw)) || undefined,\r\n fileKind: col.type === 'fileEntry' ? fileKindOf(raw) : undefined,\r\n }\r\n }\r\n\r\n // Numeric types we try to parse as hex\r\n const numericTypes: M8FieldType[] = [\r\n 'chainRef', 'phraseRef', 'velocity', 'instrumentRef',\r\n 'fxValue', 'transpose', 'ticks', 'volume', 'ppq', 'eqSlot', 'rowIndex',\r\n ]\r\n // Types that are decimal (not hex)\r\n const decimalTypes: M8FieldType[] = ['ppq']\r\n let hexValue: number | null = null\r\n if (!empty && numericTypes.includes(col.type)) {\r\n if (decimalTypes.includes(col.type)) {\r\n const n = parseInt(raw, 10)\r\n hexValue = Number.isNaN(n) ? null : n\r\n } else {\r\n hexValue = parseHex(raw)\r\n }\r\n }\r\n\r\n return {\r\n key: col.key,\r\n label: col.label,\r\n type: col.type,\r\n rawValue: raw,\r\n hexValue,\r\n isEmpty: empty,\r\n meta: col.meta,\r\n }\r\n}\r\n\r\nfunction fileKindOf(name: string): M8FileKind {\r\n if (name === '/..') return 'parent'\r\n if (name.startsWith('/')) return 'directory'\r\n const ext = /\\.([a-z0-9]+)$/i.exec(name)?.[1]?.toLowerCase()\r\n if (ext === 'm8i') return 'instrumentPreset'\r\n if (ext === 'wav') return 'sample'\r\n return ext?.startsWith('m8') ? 'm8file' : 'file'\r\n}\r\n\r\nfunction parseRowIndex(line: string, rf: RowFieldDef): number | null {\r\n const raw = extractFromLine(line, rf.x, rf.width).trim()\r\n if (!raw) return null\r\n if (rf.parse === 'hex') return parseHex(raw)\r\n const n = parseInt(raw, 10)\r\n return Number.isNaN(n) ? null : n\r\n}\r\n\r\n/**\r\n * Find the column whose x range contains cursorX.\r\n * Falls back to the closest column within 1-character tolerance.\r\n */\r\nfunction findColumn(cols: ColumnDef[], cursorX: number): ColumnDef | null {\r\n // Exact range hit\r\n for (const col of cols) {\r\n if (cursorX >= col.x && cursorX < col.x + col.width) return col\r\n }\r\n // 1-char tolerance (cursor border may be 1 px off)\r\n let best: ColumnDef | null = null\r\n let bestDist = 2 // reject anything > 1 char away\r\n for (const col of cols) {\r\n const dist = Math.min(\r\n Math.abs(cursorX - col.x),\r\n Math.abs(cursorX - (col.x + col.width - 1)),\r\n )\r\n if (dist < bestDist) {\r\n bestDist = dist\r\n best = col\r\n }\r\n }\r\n return best\r\n}\r\n\r\nfunction parseRow(viewDef: ViewDef, line: string): M8ParsedRow {\r\n const fields: Record<string, M8ParsedField> = {}\r\n for (const col of viewDef.columns ?? []) {\r\n fields[col.key] = parseColumn(col, line)\r\n }\r\n // '---00': an FX lane without a command shows a filler value that means nothing.\r\n for (const key of Object.keys(fields)) {\r\n const lane = /^fx(\\d)cmd$/.exec(key)?.[1]\r\n const value = lane ? fields[`fx${lane}val`] : undefined\r\n if (value && fields[key].isEmpty) fields[`fx${lane}val`] = { ...value, isEmpty: true }\r\n }\r\n return {\r\n rowIndex: viewDef.rowField ? parseRowIndex(line, viewDef.rowField) : null,\r\n rowKey: viewDef.rowField?.key ?? 'row',\r\n rowId: null,\r\n fields,\r\n }\r\n}\r\n\r\nconst squash = (s: string): string => s.replace(/\\s+/g, '')\r\n\r\n/**\r\n * Every screen row has a one-column left margin (the playback marker '<' / '>' is drawn\r\n * in it), so the schema's x positions are counted from column 1, and `cursorPos.x` is\r\n * one more than the schema x of the cell it covers. `currentLine` is trimmed and so\r\n * already starts at the schema's column 0.\r\n */\r\nexport const SCREEN_MARGIN = 1\r\n\r\n/** A screen row with the margin removed: columns then match the schema's x positions. */\r\nconst screenDataLine = (raw: string): string => raw.slice(SCREEN_MARGIN)\r\n\r\n/**\r\n * The screen can lag behind the state (they arrive through separate messages), so\r\n * only trust it when its row under the cursor is the very line we were given.\r\n * `currentLine` is trimmed while screen lines keep their columns, hence squash().\r\n */\r\nfunction screenMatchesCursor(state: M8State, screen: M8Screen | null | undefined): screen is M8Screen {\r\n if (!screen || !state.cursorPos || !state.currentLine) return false\r\n const onScreen = screen.lines[state.cursorPos.y]\r\n return onScreen != null && squash(onScreen) === squash(state.currentLine)\r\n}\r\n\r\nconst PHRASE_STEPS = 16\r\n\r\n/** Screen rows that can hold grid content (titles are on row 3, content starts on row 5). */\r\nconst GRID_FIRST_ROW = 4\r\nconst GRID_LAST_ROW = 21\r\n\r\n/** A grid row read off the whole screen, with the screen row it was found on. */\r\nexport interface M8ScreenGridRow extends M8ParsedRow {\r\n screenRow: number\r\n}\r\n\r\n/**\r\n * Every content row of a grid view (song, chain, phrase, table, groove, instrument pool)\r\n * read off the whole screen, top to bottom. Rows whose index column does not parse are\r\n * skipped, so check `rowIndex` continuity when you need a full page.\r\n *\r\n * @example\r\n * ```ts\r\n * const rows = parseGridScreen('phrase', await m8.getScreen())\r\n * rows[0].fields.note.rawValue // 'C-4'\r\n * ```\r\n */\r\nexport function parseGridScreen(viewName: string, screen: M8Screen): M8ScreenGridRow[] {\r\n const viewDef = (schema.views as Record<string, ViewDef>)[viewName]\r\n if (viewDef?.type !== 'grid' || !viewDef.rowField) return []\r\n const rows: M8ScreenGridRow[] = []\r\n const last = Math.min(GRID_LAST_ROW, screen.lines.length - 1)\r\n for (let y = GRID_FIRST_ROW; y <= last; y += 1) {\r\n const line = screenDataLine(screen.lines[y] ?? '')\r\n const rf = viewDef.rowField\r\n // The index must stand alone: a label such as 'FE' in a longer word is not a row. While\r\n // the song plays, a play marker ('>') can take the blank after it (Song view).\r\n if (line.length <= rf.x + rf.width || !isSeparator(line[rf.x + rf.width])) continue\r\n const row = parseRow(viewDef, line)\r\n if (row.rowIndex == null) continue\r\n rows.push({ ...row, screenRow: y })\r\n }\r\n return rows\r\n}\r\n\r\nconst isSeparator = (ch: string): boolean => ch === ' ' || /^[^a-zA-Z0-9-]$/.test(ch)\r\n\r\n/** Where the song is playing, as far as the screen shows it. */\r\nexport interface M8PlayPositions {\r\n /** Song view: the song row each track's play marker is on (null when its marker is not on screen). */\r\n tracks: (number | null)[] | null\r\n /** Chain, phrase, table, groove views: the row with the play marker after its index. */\r\n row: number | null\r\n}\r\n\r\n/**\r\n * Reads the play markers off a grid view. On the Song view the M8 draws a marker in the\r\n * blank just left of each playing chain (`00>00>01>02`); on the other grid views in the\r\n * blank after the row index of the playing row (`F>--- -- --`), both seen on a Model:02,\r\n * firmware 6.5. The '<' drawn in the margin marks the cursor row, not playback. Returns\r\n * null for views without a grid; all nulls when nothing is playing.\r\n */\r\nexport function readPlayPositions(viewName: string, screen: M8Screen): M8PlayPositions | null {\r\n const viewDef = (schema.views as Record<string, ViewDef>)[viewName]\r\n if (viewDef?.type !== 'grid') return null\r\n const rows = parseGridScreen(viewName, screen)\r\n const isMarker = (ch: string | undefined) => !!ch && ch !== ' ' && isSeparator(ch)\r\n if (viewName === 'song') {\r\n const cells = (viewDef.columns ?? []).filter((c) => c.type === 'chainRef')\r\n const tracks = cells.map((cell) => {\r\n const hit = rows.find((r) => isMarker(screenDataLine(screen.lines[r.screenRow] ?? '')[cell.x - 1]))\r\n return hit?.rowIndex ?? null\r\n })\r\n return { tracks, row: null }\r\n }\r\n const rf = viewDef.rowField\r\n if (!rf) return { tracks: null, row: null }\r\n const hit = rows.find((r) => isMarker(screenDataLine(screen.lines[r.screenRow] ?? '')[rf.x + rf.width]))\r\n return { tracks: null, row: hit?.rowIndex ?? null }\r\n}\r\n\r\n/**\r\n * All 16 steps of the phrase on screen, plus the FX lane showing the '^^' marker\r\n * (a REP carried over from a previous phrase). Null when any step does not line up.\r\n */\r\nfunction readPhraseSteps(\r\n viewDef: ViewDef,\r\n state: M8State,\r\n screen: M8Screen,\r\n cursorStep: number,\r\n): { steps: M8ParsedRow[]; carriedRepLane: number | null } | null {\r\n const y0 = (state.cursorPos?.y ?? -1) - cursorStep\r\n if (y0 < 0) return null\r\n\r\n const steps: M8ParsedRow[] = []\r\n let carriedRepLane: number | null = null\r\n for (let i = 0; i < PHRASE_STEPS; i += 1) {\r\n const raw = screen.lines[y0 + i]\r\n if (raw == null) return null\r\n const line = screenDataLine(raw)\r\n const row = parseRow(viewDef, line)\r\n if (row.rowIndex !== i) return null\r\n steps.push(row)\r\n\r\n // '^^' sits next to the FX lane a previous phrase's REP is still driving.\r\n const caret = line.indexOf('^^')\r\n if (carriedRepLane == null && caret >= 0) carriedRepLane = nearestFxLane(viewDef, caret)\r\n }\r\n return { steps, carriedRepLane }\r\n}\r\n\r\n/**\r\n * FX lane (1-3) that text column `x` belongs to: inside a lane's cells, or at most one\r\n * column outside exactly one lane. Null when it is out of reach or ambiguous - the\r\n * manual doesn't say exactly where the '^^' is drawn, so don't guess.\r\n */\r\nfunction nearestFxLane(viewDef: ViewDef, x: number): number | null {\r\n const dists: { lane: number; dist: number }[] = []\r\n for (let lane = 1; lane <= 3; lane += 1) {\r\n const cmd = viewDef.columns?.find((c) => c.key === `fx${lane}cmd`)\r\n const val = viewDef.columns?.find((c) => c.key === `fx${lane}val`)\r\n if (!cmd || !val) continue\r\n const to = val.x + val.width - 1\r\n dists.push({ lane, dist: x < cmd.x ? cmd.x - x : x > to ? x - to : 0 })\r\n }\r\n const min = Math.min(...dists.map((d) => d.dist))\r\n const closest = dists.filter((d) => d.dist === min)\r\n return min <= 1 && closest.length === 1 ? closest[0].lane : null\r\n}\r\n\r\n/** A Scale View row as an interval; null when its first column is not an interval label. */\r\nfunction parseScaleNote(row: M8ParsedRow): M8ScaleNote | null {\r\n const interval = normalizeInterval(row.fields.interval?.rawValue ?? '')\r\n if (!interval) return null\r\n const en = row.fields.en?.rawValue.toUpperCase()\r\n const offset = parseFloat(row.fields.offset?.rawValue ?? '')\r\n return {\r\n interval,\r\n semitone: (SCALE_INTERVALS as readonly string[]).indexOf(interval),\r\n // The device (fw 6.5) draws a note out of the scale as '-- -- --'.\r\n enabled: en === 'ON' ? true : en === 'OFF' || en === '--' ? false : null,\r\n offset: Number.isNaN(offset) ? null : offset,\r\n }\r\n}\r\n\r\n/** Reads the 12 interval rows of the Scale View off the screen. Null unless all 12 are found. */\r\nfunction readScaleNotes(viewDef: ViewDef, screen: M8Screen): M8ScaleNote[] | null {\r\n const found = new Map<string, M8ScaleNote>()\r\n for (const raw of screen.lines) {\r\n const row = parseRow(viewDef, screenDataLine(raw))\r\n const note = parseScaleNote(row)\r\n // An interval row reads ON, OFF or '--' in its EN column.\r\n const en = row.fields.en?.rawValue.toUpperCase()\r\n if (!note || (en !== 'ON' && en !== 'OFF' && en !== '--')) continue\r\n if (!found.has(note.interval)) found.set(note.interval, note)\r\n }\r\n if (found.size !== SCALE_INTERVALS.length) return null\r\n return SCALE_INTERVALS.map((i) => found.get(i) as M8ScaleNote)\r\n}\r\n\r\n/**\r\n * Context of a label/value parameter view. The line is read from the screen when a\r\n * matching one is available (it keeps leading blanks, which the trimmed `currentLine`\r\n * loses), and the section / instrument type are found by looking at the lines around it.\r\n */\r\nfunction getParameterContext(\r\n state: M8State,\r\n screen: M8Screen | null | undefined,\r\n layout: ParameterLayout,\r\n head: Pick<M8SemanticContext, 'viewName' | 'viewTitle' | 'viewDescription' | 'viewId' | 'isUnsaved'>,\r\n): M8SemanticContext {\r\n const empty: M8SemanticContext = {\r\n ...head,\r\n isGridView: false,\r\n isParameterView: true,\r\n section: null,\r\n instrumentType: null,\r\n row: null,\r\n activeField: null,\r\n lineText: null,\r\n steps: null,\r\n carriedRepLane: null,\r\n fxLane: null,\r\n relativeFx: null,\r\n resetsRelative: null,\r\n scaleNote: null,\r\n scale: null,\r\n }\r\n\r\n const cursor = state.cursorPos\r\n const cursorX = cursor ? cursor.x - SCREEN_MARGIN : 0\r\n const trusted = cursor && screenMatchesCursor(state, screen) ? screen : null\r\n let line = trusted && cursor ? screenDataLine(trusted.lines[cursor.y] ?? '') : (state.currentLine ?? '')\r\n if (!line.trim()) return empty\r\n\r\n const linesUpward = trusted && cursor ? trusted.lines.slice(0, cursor.y + 1).reverse().map(screenDataLine) : [line]\r\n const instrumentType =\r\n head.viewName === 'inst' ? instrumentTypeFromLines(trusted ? trusted.lines.map(screenDataLine) : [line]) : null\r\n const opts = { instrumentKey: instrumentType?.key ?? null, entryText: state.textUnderCursor }\r\n\r\n // `currentLine` is trimmed, so a row that starts with blanks lost its column offsets.\r\n // Without the screen, put blanks back until the cursor lands inside a recognised cell.\r\n if (!trusted && cursor && layout.kind == null) {\r\n for (let pad = 0; pad <= Math.min(cursorX, 24); pad += 1) {\r\n const padded = ' '.repeat(pad) + line\r\n const found = readParameterCells(layout, padded, { ...opts, section: detectSection(layout, [padded]) })\r\n if (found.cells.some((c) => cursorX >= c.x && cursorX < c.x + c.width)) {\r\n line = padded\r\n break\r\n }\r\n }\r\n }\r\n\r\n let section = detectSection(layout, trusted ? linesUpward : [line])\r\n const { rowId, cells } = readParameterCells(layout, line, { ...opts, section })\r\n const fields: Record<string, M8ParsedField> = {}\r\n for (const cell of cells) fields[cell.key] = parseColumn(cell, line)\r\n const row: M8ParsedRow = { rowIndex: null, rowKey: 'param', rowId, fields }\r\n\r\n let activeField: M8ActiveField | null = null\r\n const cell = cursor ? findColumn(cells, cursorX) : null\r\n if (cell) activeField = { ...fields[cell.key], viewName: head.viewName, rowIndex: null }\r\n\r\n // Modulation view: which of MOD1-MOD4 the cell belongs to (the heading of its block).\r\n const zoneId = cell?.meta?.zone\r\n if (trusted && typeof zoneId === 'string') section = detectSlot(layout, zoneId, linesUpward) ?? section\r\n\r\n return { ...empty, section, instrumentType, row, activeField, lineText: line.trim() }\r\n}\r\n\r\n// Public API\r\n\r\n/**\r\n * Derives a full semantic context from the current M8State.\r\n *\r\n * Pass the whole `screen` (see M8Client.getScreen) to let the context look beyond the\r\n * cursor row: the other steps of a phrase (so an FX cell can be read against the\r\n * commands above it) and the other rows of the Scale View. Without a screen, or with\r\n * one that no longer matches the cursor row, only the cursor row is interpreted.\r\n *\r\n * @example\r\n * ```ts\r\n * const ctx = getSemanticContext(state)\r\n * if (ctx?.activeField?.type === 'chainRef') {\r\n * const track = ctx.activeField.meta?.trackIndex // 1–8\r\n * const chainNum = ctx.activeField.hexValue // 0–255 or null if empty\r\n * const songRow = ctx.row?.rowIndex\r\n * }\r\n * ```\r\n */\r\nexport function getSemanticContext(state: M8State, screen?: M8Screen | null): M8SemanticContext | null {\r\n const viewName = state.viewName\r\n if (!viewName) return null\r\n\r\n const viewDefs = schema.views as Record<string, ViewDef>\r\n const viewDef = viewDefs[viewName] ?? null\r\n\r\n const isGridView = viewDef?.type === 'grid'\r\n const viewTitle = viewDef?.title ?? null\r\n const viewDescription = viewDef?.description ?? null\r\n const { viewId, isUnsaved } = extractViewId(state.viewTitle)\r\n const head = { viewName, viewTitle, viewDescription, viewId, isUnsaved }\r\n const base = { ...head, isParameterView: false, section: null, instrumentType: null, lineText: null, steps: null, carriedRepLane: null, fxLane: null, relativeFx: null, resetsRelative: null, scaleNote: null, scale: null }\r\n\r\n const paramLayout = viewDef?.type === 'parameter' ? getParameterLayout(viewName) : null\r\n if (paramLayout) return getParameterContext(state, screen, paramLayout, head)\r\n\r\n if (!isGridView || !viewDef) {\r\n return { ...base, isGridView: false, row: null, activeField: null }\r\n }\r\n\r\n const rawLine = state.currentLine\r\n if (!rawLine) {\r\n return { ...base, isGridView: true, row: null, activeField: null }\r\n }\r\n\r\n // Strip playback indicator prefix ('>' or '<') emitted when the row is playing.\r\n const { line } = stripPlaybackIndicator(rawLine)\r\n const cursorX = state.cursorPos ? state.cursorPos.x - SCREEN_MARGIN : 0\r\n\r\n const row = parseRow(viewDef, line)\r\n\r\n // The rows under the 12 scale intervals (KEY, TUNE, NAME, LOAD/SAVE) are label/value rows.\r\n const scaleLayout = viewName === 'scale' && !normalizeInterval(row.fields.interval?.rawValue ?? '') ? getParameterLayout(viewName) : null\r\n if (scaleLayout) return getParameterContext(state, screen, scaleLayout, head)\r\n\r\n // Determine active field from cursor X position\r\n let activeField: M8ActiveField | null = null\r\n if (state.cursorPos) {\r\n const col = findColumn(viewDef.columns ?? [], cursorX)\r\n if (col) {\r\n const field = row.fields[col.key]\r\n if (field) {\r\n activeField = { ...field, viewName, rowIndex: row.rowIndex }\r\n }\r\n }\r\n }\r\n\r\n // Look beyond the cursor row when a screen that still matches it was supplied.\r\n let steps: M8ParsedRow[] | null = null\r\n let carriedRepLane: number | null = null\r\n let fxLane: M8FxLaneTrace | null = null\r\n let relativeFx: Record<string, M8RelativeFxState> | null = null\r\n let resetsRelative: Record<string, M8RelativeFxState> | null = null\r\n let scale: M8ScaleInfo | null = null\r\n if (screenMatchesCursor(state, screen)) {\r\n if (viewName === 'phrase' && row.rowIndex != null) {\r\n const phrase = readPhraseSteps(viewDef, state, screen, row.rowIndex)\r\n if (phrase) {\r\n steps = phrase.steps\r\n carriedRepLane = phrase.carriedRepLane\r\n const lane = activeField ? /^fx(\\d)(cmd|val)$/.exec(activeField.key)?.[1] : undefined\r\n if (lane) fxLane = analyzeFxLane(steps, Number(lane), row.rowIndex, carriedRepLane === Number(lane))\r\n relativeFx = analyzeRelativeFx(steps, row.rowIndex, carriedRepLane)\r\n if (row.rowIndex > 0 && relativeResetBy(row)) resetsRelative = analyzeRelativeFx(steps, row.rowIndex - 1, carriedRepLane)\r\n }\r\n } else if (viewName === 'scale') {\r\n const notes = readScaleNotes(viewDef, screen)\r\n if (notes) scale = analyzeScale(notes)\r\n }\r\n }\r\n const scaleNote = viewName === 'scale' ? parseScaleNote(row) : null\r\n\r\n return {\r\n ...base,\r\n isGridView: true,\r\n row,\r\n activeField,\r\n lineText: line.trim() || null,\r\n steps,\r\n carriedRepLane,\r\n fxLane,\r\n relativeFx,\r\n resetsRelative,\r\n scaleNote,\r\n scale,\r\n }\r\n}\r\n\r\n/**\r\n * What the cursor is on, as ordered, tagged parts. Every part is built from\r\n * `ctx` alone, so a consumer can keep, drop, reorder or restyle them, or ignore them\r\n * and write its own text from the context.\r\n *\r\n * @example\r\n * ```ts\r\n * describeContextParts(ctx)\r\n * // [{ kind: 'field', text: 'FX2 Command: KIL' },\r\n * // { kind: 'fxInfo', text: '(Kill: stops the playing instrument after XX ticks)' },\r\n * // { kind: 'meaning', text: 'value 03: stops the note after 3 ticks' },\r\n * // { kind: 'location', text: 'Phrase View row 01' }]\r\n * ```\r\n */\r\nexport function describeContextParts(ctx: M8SemanticContext): M8DescriptionPart[] {\r\n if (ctx.isParameterView) return describeParameterContext(ctx)\r\n if (ctx.viewName === 'scale') {\r\n const scaleParts = describeScaleContext(ctx)\r\n if (scaleParts) return scaleParts\r\n }\r\n if (!ctx.activeField) return partsOf(['location', ctx.viewTitle ?? ctx.viewName])\r\n\r\n const { activeField: f, row } = ctx\r\n const parts = partsOf(['field', `${f.label}: ${formatFieldValue(f)}`])\r\n\r\n // What the FX does here, read against its surroundings (value meaning, running REP…)\r\n const isFx = f.type === 'fxCommand' || f.type === 'fxValue'\r\n const meaning = isFx && row ? describeFxCell(ctx, f, row) : [...describeInstrumentCell(ctx, f), ...describeTableVolume(ctx, f)]\r\n // The generic REP/RTO blurb only repeats what the specific explanation already says.\r\n const explained = meaning.length > 0\r\n\r\n // FX command extra info\r\n if (f.type === 'fxCommand' && !f.isEmpty) {\r\n const info = explained && /^(REP|RTO)$/i.test(f.rawValue) ? undefined : lookupFxCommand(f.rawValue)\r\n if (info) parts.push({ kind: 'fxInfo', text: `(${fxInfoText(info)})` })\r\n }\r\n\r\n // FX value context: include sibling command if we're on a value column\r\n if (f.type === 'fxValue' && row) {\r\n const cmdKey = f.key.replace('val', 'cmd') // fx1val → fx1cmd, fx2val → fx2cmd …\r\n const cmdField = row.fields[cmdKey]\r\n if (cmdField && !cmdField.isEmpty) {\r\n const info = explained && /^(REP|RTO)$/i.test(cmdField.rawValue) ? undefined : lookupFxCommand(cmdField.rawValue)\r\n const label = info ? `${cmdField.rawValue} (${fxInfoText(info)})` : cmdField.rawValue\r\n parts.push({ kind: 'fxTarget', text: `for ${label}` })\r\n }\r\n }\r\n\r\n parts.push(...meaning)\r\n\r\n // Track index for song view\r\n if (f.meta?.trackIndex != null) parts.push({ kind: 'track', text: `Track ${f.meta.trackIndex}` })\r\n\r\n // Row index\r\n if (row?.rowIndex != null) {\r\n const rowHex = row.rowIndex.toString(16).toUpperCase().padStart(2, '0')\r\n parts.push({ kind: 'location', text: `${ctx.viewTitle ?? ctx.viewName} row ${rowHex}` })\r\n }\r\n\r\n return parts\r\n}\r\n\r\n/**\r\n * Returns a short human-readable description of what the cursor is currently on:\r\n * the text of describeContextParts(), joined with ' — '.\r\n *\r\n * @example\r\n * \"Track 3: 0A — Track 3 — Song View row 01\"\r\n * \"Note (N): Note C-4 — Phrase View row 00\"\r\n * \"FX2 Command: KIL — (Kill: stops the playing instrument after XX ticks) — value 03: stops the note after 3 ticks — Phrase View row 01\"\r\n */\r\nexport function describeContext(ctx: M8SemanticContext): string {\r\n return describeContextParts(ctx)\r\n .map((p) => p.text)\r\n .join(' — ')\r\n}\r\n\r\n/** Parts in the order given, leaving out those with no text. */\r\nfunction partsOf(...entries: [M8DescriptionPartKind, string | null | undefined][]): M8DescriptionPart[] {\r\n return entries.flatMap(([kind, text]) => (text ? [{ kind, text }] : []))\r\n}\r\n\r\n/**\r\n * Meaning of the FX cell under the cursor. Uses the lane trace (REP/RTO, empty cells\r\n * under a running REP) when the phrase was read off the screen, else explains the\r\n * value of the command in this row.\r\n */\r\nfunction describeFxCell(ctx: M8SemanticContext, f: M8ActiveField, row: M8ParsedRow): M8DescriptionPart[] {\r\n const lane = /^fx(\\d)/.exec(f.key)?.[1]\r\n if (!lane) return []\r\n const cmd = row.fields[`fx${lane}cmd`]\r\n const val = row.fields[`fx${lane}val`]\r\n\r\n // Empty cell: only interesting when a REP is still running through it.\r\n if (!cmd || cmd.isEmpty) {\r\n if (ctx.fxLane?.event !== 'continue') return []\r\n const repeated = ctx.fxLane.source?.cmd\r\n const relative = repeated && isRelativeFx(repeated) ? describeRelativeFx(ctx.relativeFx?.[repeated]) : null\r\n return partsOf(['fxLane', describeFxLane(ctx.fxLane)], ['relative', relative])\r\n }\r\n\r\n const name = cmd.rawValue.toUpperCase()\r\n const laneText = ctx.fxLane && ctx.fxLane.event !== 'none' && ctx.fxLane.event !== 'set' ? describeFxLane(ctx.fxLane) : null\r\n // In a phrase REP / RTO are explained entirely by the lane; other commands, and REP / RTO\r\n // in a table (a repeat count there), add their value meaning.\r\n if ((name === 'REP' || name === 'RTO') && ctx.viewName !== 'table') {\r\n const repeated = ctx.fxLane?.source?.cmd\r\n const relative = name === 'REP' && ctx.fxLane?.active && repeated && isRelativeFx(repeated) ? describeRelativeFx(ctx.relativeFx?.[repeated]) : null\r\n return partsOf(['fxLane', laneText], ['relative', relative])\r\n }\r\n if (!val || val.isEmpty || val.hexValue == null) return partsOf(['fxLane', laneText])\r\n\r\n const shown = f.type === 'fxCommand' ? `value ${formatFxValue(name, val.hexValue)}: ` : ''\r\n const described = describeFxValue(name, val.hexValue, {\r\n note: row.fields.note?.isEmpty ? null : row.fields.note?.rawValue,\r\n inTable: ctx.viewName === 'table',\r\n row: row.rowIndex,\r\n })\r\n // Relative commands stay applied until a note with an instrument number or a RET resets them.\r\n let relative: string | null = null\r\n if (isRelativeFx(name)) relative = describeRelativeFx(ctx.relativeFx?.[name])\r\n else if (name === 'RET' && ctx.resetsRelative) relative = describeRelativeReset(ctx.resetsRelative)\r\n return partsOf(['meaning', described ? `${shown}${described}` : null], ['fxLane', laneText], ['relative', relative])\r\n}\r\n\r\n/** Command description, plus the instrument types that offer it when not all do. */\r\nfunction fxInfoText(info: M8FxCommandInfo): string {\r\n const text = info.description.replace(/\\.$/, '')\r\n if (!info.instrumentTypes) return text\r\n return `${text}; offered by ${info.instrumentTypes.map((t) => INSTRUMENT_TYPE_LABELS[t] ?? t).join(', ')} instruments`\r\n}\r\n\r\nconst INSTRUMENT_TYPE_LABELS: Record<string, string> = {\r\n sampler: 'Sampler',\r\n macrosynth: 'Macrosynth',\r\n fmsynth: 'FM Synth',\r\n hypersynth: 'Hypersynth',\r\n wavsynth: 'Wavsynth',\r\n external: 'External',\r\n midiout: 'MIDI Out',\r\n}\r\n\r\n/**\r\n * Instrument number typed on a step of a phrase: it retriggers the instrument, which\r\n * wipes the relative FX changes that were still in force.\r\n */\r\nfunction describeInstrumentCell(ctx: M8SemanticContext, f: M8ActiveField): M8DescriptionPart[] {\r\n if (f.type !== 'instrumentRef' || f.isEmpty || !ctx.resetsRelative) return []\r\n return partsOf(['relative', describeRelativeReset(ctx.resetsRelative)])\r\n}\r\n\r\n/** Table V column: a share of the velocity of the phrase step that plays the table (7F = all of it). */\r\nfunction describeTableVolume(ctx: M8SemanticContext, f: M8ActiveField): M8DescriptionPart[] {\r\n if (ctx.viewName !== 'table' || f.key !== 'volume' || f.isEmpty || f.hexValue == null || f.hexValue > 0x7f) return []\r\n return partsOf(['meaning', `plays at ${Math.round((f.hexValue / 0x7f) * 100)}% of the step's velocity`])\r\n}\r\n\r\nconst SECTION_NAMES: Record<string, string> = { MODFX: 'ModFX', DELAY: 'Delay', REVERB: 'Reverb' }\r\n\r\nconst FILE_KIND_TEXT: Record<M8FileKind, string> = {\r\n parent: 'goes up to the parent directory',\r\n directory: 'directory: [EDIT] opens it',\r\n instrumentPreset: 'instrument preset: [EDIT] selects it',\r\n sample: 'sample: [EDIT] selects it',\r\n m8file: 'M8 file: [EDIT] selects it',\r\n file: 'file: [EDIT] selects it',\r\n}\r\n\r\n/**\r\n * Parameter views: \"<label>: <value> — <what the option does> — <what the field is>\r\n * — <where>\". The instrument type and effect section are added when they change what\r\n * the field means.\r\n */\r\nfunction describeParameterContext(ctx: M8SemanticContext): M8DescriptionPart[] {\r\n const title = `${ctx.viewTitle ?? ctx.viewName}${ctx.viewId ? ` ${ctx.viewId}` : ''}`\r\n const f = ctx.activeField\r\n if (!f) return partsOf(['location', title], ['line', ctx.lineText])\r\n\r\n const type = ctx.instrumentType?.key ? getInstrumentType(ctx.instrumentType.key) : null\r\n return partsOf(\r\n ['field', `${f.label}: ${formatFieldValue(f)}`],\r\n ['optionInfo', f.valueDescription],\r\n ['fieldInfo', f.description],\r\n ['fileInfo', f.fileKind && FILE_KIND_TEXT[f.fileKind]],\r\n ['instrument', ctx.instrumentType && `${type?.name ?? ctx.instrumentType.text} instrument`],\r\n ['section', ctx.section && (SECTION_NAMES[ctx.section] ?? ctx.section)],\r\n ['location', title],\r\n )\r\n}\r\n\r\n/**\r\n * Scale View interval rows: what the row means and, when the whole screen was\r\n * available, which scale the 12 intervals spell together. Null → fall back to generic.\r\n * (KEY / TUNE / NAME / LOAD / SAVE rows are parameter rows.)\r\n */\r\nfunction describeScaleContext(ctx: M8SemanticContext): M8DescriptionPart[] | null {\r\n const title = `${ctx.viewTitle ?? ctx.viewName}${ctx.viewId ? ` ${ctx.viewId}` : ''}`\r\n const f = ctx.activeField\r\n const note = ctx.scaleNote\r\n if (!f || !note) return ctx.lineText ? partsOf(['location', title], ['line', ctx.lineText]) : null\r\n\r\n const { interval, enabled, offset } = note\r\n let parts: M8DescriptionPart[]\r\n if (f.key === 'offset') {\r\n const detune = offset == null ? `detune of ${interval}` : offset === 0 ? `${interval} is not detuned` : `${interval} is detuned by ${formatOffset(offset)}`\r\n parts = partsOf(['field', `${f.label}: ${f.rawValue || '(empty)'}`], ['meaning', detune], ['meaning', enabled === false ? '(note is OFF)' : null])\r\n } else {\r\n const state = enabled === true ? 'is in' : enabled === false ? 'is not in' : 'is not yet set in'\r\n parts = partsOf(\r\n ['field', `${f.label}: ${f.key === 'en' ? f.rawValue.toUpperCase() || '(unset)' : interval}`],\r\n ['meaning', interval],\r\n ['meaning', describeInterval(interval)],\r\n ['meaning', `${state} the scale`],\r\n )\r\n }\r\n\r\n if (ctx.scale) {\r\n const [notes, shape] = scaleSummaryParts(ctx.scale)\r\n parts.push(...partsOf(['scale', `Scale: ${notes}`], ['scale', shape]))\r\n }\r\n return [...parts, ...partsOf(['location', `${title} row ${interval}`])]\r\n}\r\n\r\n/** 'FF' → 'FFh (dec 255)', '40:80' → '40:80 (mod depth 40, mod frequency 80)', '120' → '120 BPM'. */\r\nfunction formatParameterValue(field: M8ParsedField): string {\r\n const unit = field.unit ?? ''\r\n if (field.values && field.parts) {\r\n // Hex pairs carry a hexValue ('40:80'); decimal pairs don't ('150:45').\r\n const hex = field.hexValue != null\r\n const shown = field.values.map((v) => (hex ? v.toString(16).toUpperCase().padStart(2, '0') : String(v)))\r\n const named = field.parts.map((p, i) => `${p} ${shown[i] ?? '?'}${unit}`).join(', ')\r\n return `${shown.join(field.rawValue.includes('/') ? '/' : ':')} (${named})`\r\n }\r\n if (field.hexValue != null) {\r\n return `${field.rawValue.slice(0, 2)}h (dec ${field.hexValue})${field.text ? ` ${field.text}` : ''}`\r\n }\r\n if (field.numericValue != null) return `${field.rawValue}${unit}`\r\n return field.text ?? field.rawValue\r\n}\r\n\r\n/**\r\n * Formats a parsed field value as a human-readable string.\r\n */\r\nexport function formatFieldValue(input: M8ParsedField): string {\r\n if (input.isEmpty) return '(empty)'\r\n // The device sends lower case ('arp', 'b-4', 'ff'); show it the way the M8 draws it.\r\n // Text the user typed and file names keep their case.\r\n const keepCase = input.type === 'name' || input.type === 'fileEntry'\r\n const field = keepCase ? input : { ...input, rawValue: input.rawValue.toUpperCase() }\r\n\r\n switch (field.type) {\r\n case 'note':\r\n if (field.rawValue.toUpperCase() === 'OFF') return 'Note Off'\r\n return `Note ${field.rawValue}`\r\n\r\n case 'transpose': {\r\n if (field.hexValue == null) return field.rawValue\r\n // Relative hex: 00=none, 01–7F=positive, 80–FF=negative (two's complement)\r\n if (field.hexValue === 0) return '±0 st'\r\n const signed = field.hexValue > 0x7f ? field.hexValue - 0x100 : field.hexValue\r\n return `${signed > 0 ? '+' : ''}${signed} st`\r\n }\r\n\r\n case 'velocity':\r\n if (field.hexValue == null) return field.rawValue\r\n return `${field.rawValue}h (${field.hexValue} / 127 max)`\r\n\r\n case 'chainRef':\r\n case 'phraseRef':\r\n case 'instrumentRef':\r\n return field.rawValue\r\n\r\n case 'fxCommand':\r\n return field.rawValue\r\n\r\n case 'fxValue':\r\n if (field.hexValue == null) return field.rawValue\r\n return `${field.rawValue}h (dec ${field.hexValue})`\r\n\r\n case 'ticks':\r\n if (field.rawValue === '00') return '00 (skip step)'\r\n if (field.hexValue == null) return field.rawValue\r\n return `${field.rawValue}h (${field.hexValue} ticks)`\r\n\r\n case 'volume':\r\n return field.rawValue\r\n\r\n case 'instrumentType':\r\n case 'action':\r\n case 'fileEntry':\r\n return field.text ?? field.rawValue\r\n\r\n case 'name':\r\n return `\"${field.rawValue}\"`\r\n\r\n case 'option':\r\n // '00CHORUS' → '00 CHORUS'; text-only options ('BELL') stay as they are.\r\n if (field.hexValue != null && field.text) return `${field.rawValue.slice(0, 2)} ${field.text}`\r\n return field.text ?? field.rawValue\r\n\r\n case 'parameter':\r\n return formatParameterValue(field)\r\n\r\n case 'noteInterval':\r\n return `Interval ${normalizeInterval(field.rawValue) ?? field.rawValue}`\r\n\r\n case 'semitoneOffset':\r\n return field.isEmpty ? '(empty)' : `${field.rawValue} st`\r\n\r\n default:\r\n return field.rawValue\r\n }\r\n}\r\n","// @ts-expect-error - post-me types are incomplete for the generic handshake signatures used here.\r\nimport { ChildHandshake, DebugMessenger, WindowMessenger } from 'post-me'\r\n// @ts-expect-error - post-me types are incomplete for the generic handshake signatures used here.\r\nimport type { Connection, Messenger } from 'post-me'\r\nimport { type ConnectedBus, connection } from './engine/connection'\r\nimport { connectWithRetry } from './engine/connectWithRetry'\r\nimport { createM8Audio, type M8Audio } from './engine/audio'\r\nimport { createM8Engine, type M8Engine } from './engine/engine'\r\nimport type {\r\n M8ClientEvents,\r\n M8ClientMethods,\r\n M8ClientMode,\r\n M8DeviceBus,\r\n M8DeviceConnectOptions,\r\n M8DeviceSupport,\r\n M8DeviceTransport,\r\n M8HostEvents,\r\n M8HostMethods,\r\n M8KeyName,\r\n M8Screen,\r\n M8ScreenCells,\r\n M8SdkConfig,\r\n M8State,\r\n M8TransportEvent,\r\n} from './types'\r\nimport { getSemanticContext, describeContext, describeContextParts } from './viewContext'\r\nimport type { M8DescriptionPart, M8SemanticContext } from './viewContext'\r\n\r\nexport type { CursorPos, CursorRect, M8KeyName, M8Screen, M8ScreenCells, M8State, M8TransportEvent, RGB, SystemInfos } from './types'\r\nexport type { M8DescriptionPart, M8SemanticContext } from './viewContext'\r\n\r\n/**\r\n * The iframe name yam8d gives its External Apps frames. A client that sees it waits for the\r\n * host handshake however long it takes, instead of falling back to standalone mode.\r\n */\r\nexport const YAM8D_FRAME_NAME = 'yam8d-external-app'\r\n\r\nconst DEFAULT_HANDSHAKE_TIMEOUT_MS = 3000\r\n\r\n/**\r\n * Everything the SDK knows about the moment: raw state, semantic context, its\r\n * human-readable description and the whole screen. Plain JSON, safe to persist.\r\n */\r\nexport interface M8Snapshot {\r\n capturedAt: number\r\n state: Pick<M8State, 'viewName' | 'viewTitle' | 'cursorPos' | 'selectionMode' | 'textUnderCursor' | 'currentLine' | 'deviceModel'>\r\n semantic: M8SemanticContext | null\r\n /** describeContext() output, e.g. \"Note (N): Note C-4 — Phrase View row 00\". */\r\n description: string | null\r\n /** The same description as tagged parts (describeContextParts()). */\r\n descriptionParts: M8DescriptionPart[] | null\r\n screen: M8Screen\r\n}\r\n\r\nexport interface M8Client {\r\n readonly state: M8State\r\n /**\r\n * True when calls reach an M8: in host mode once the yam8d handshake is done, in\r\n * standalone mode while a device is connected.\r\n */\r\n readonly isConnected: boolean\r\n /** 'host' inside yam8d, 'standalone' otherwise; null until connect() has decided. */\r\n readonly mode: M8ClientMode | null\r\n /** Standalone: the API the device is connected through, null while disconnected (always null in host mode). */\r\n readonly deviceTransport: M8DeviceTransport | null\r\n navigateToView(viewName: string): Promise<boolean>\r\n navigateTo(x: number, y: number): Promise<boolean>\r\n setValueToHex(targetHex: number): Promise<boolean>\r\n setValueToInt(targetInt: number): Promise<boolean>\r\n setValueFloat(targetFloat: number): Promise<boolean>\r\n setNote(noteString: string): Promise<boolean>\r\n setValueToString(targetString: string, exact?: boolean, searchInCurrentLine?: boolean): Promise<boolean>\r\n browseFile(targetText: string, exact?: boolean): Promise<boolean>\r\n sendKeyPress(keys: M8KeyName[]): Promise<void>\r\n sendKeyDown(keys: M8KeyName[]): Promise<void>\r\n sendKeyUp(): Promise<void>\r\n getState(): M8State\r\n fetchState(): Promise<M8State>\r\n /**\r\n * Whole screen as text rows, read directly from the M8 character stream.\r\n * No cursor movement involved, so it is instant and side-effect free.\r\n */\r\n getScreen(): Promise<M8Screen>\r\n /**\r\n * The screen plus the foreground/background colour of every character cell ('#rrggbb').\r\n * Rejects when the host predates this method (yam8d before SDK 0.5).\r\n */\r\n getScreenCells(): Promise<M8ScreenCells>\r\n /** Fresh state + semantic context + description + whole screen in one call. */\r\n captureSnapshot(): Promise<M8Snapshot>\r\n onStateChange(callback: (state: M8State) => void): () => void\r\n onViewChange(callback: (viewName: string | null, viewTitle: string | null) => void): () => void\r\n onCursorMove(callback: (pos: M8State['cursorPos'], rect: M8State['cursorRect'], selectionMode: boolean) => void): () => void\r\n onTextUpdate(callback: (textUnderCursor: string | null, currentLine: string | null) => void): () => void\r\n onKeyPress(callback: (keys: number) => void): () => void\r\n /**\r\n * Starts forwarding the M8's MIDI clock and transport (needs SYNC OUT set in the M8's\r\n * MIDI Settings, and Web MIDI permission in the page that owns the device). Hosts before\r\n * SDK 0.5 reject the call.\r\n */\r\n enableMidiTransport(): Promise<{ ok: boolean; error?: string }>\r\n /** MIDI start / stop / continue, the clock once per sixteenth, and song positions. */\r\n onTransport(callback: (event: M8TransportEvent) => void): () => void\r\n /**\r\n * Standalone: connects to the M8 from this page. Without a previously granted device the\r\n * browser shows its device picker, which only opens from a user gesture (call this from a\r\n * click handler). Replaces the current device. In host mode yam8d owns the device: no-op.\r\n */\r\n connectDevice(options?: M8DeviceConnectOptions): Promise<void>\r\n /** Standalone: releases the device so another page (yam8d, another app) can open it. */\r\n disconnectDevice(): Promise<void>\r\n /** Fires when isConnected changes (handshake done, device connected, lost or released). */\r\n onConnectionChange(callback: (connected: boolean) => void): () => void\r\n /** Standalone: raw protocol events and device commands; null in host mode or while disconnected. */\r\n getDevice(): M8DeviceBus | null\r\n /** Standalone: whether the M8's USB audio is playing through this page. */\r\n readonly isAudioConnected: boolean\r\n /**\r\n * Standalone: plays the M8's USB audio input through this page (asks for audio permission;\r\n * rejects when no input named M8 is found). In host mode yam8d plays it: no-op.\r\n */\r\n connectAudio(): Promise<void>\r\n disconnectAudio(): Promise<void>\r\n disconnect(): void\r\n /**\r\n * Derives semantic context from the current state: view, row, and the typed\r\n * field under the cursor. Works for the five grid views (song, chain, phrase,\r\n * table, groove). Returns null when no view is active.\r\n */\r\n getSemanticContext(): M8SemanticContext | null\r\n /**\r\n * Returns a short human-readable description of what the cursor is on,\r\n * e.g. \"Track 3: 0A — Track 3 — Song View row 01\".\r\n *\r\n * It also reads the cursor against its surroundings when that changes the meaning:\r\n * an FX cell under a running REP says which command is repeating and where it has\r\n * got to, FX values are explained (ARP 37 → \"+3 and +7 semitones\"), and the Scale\r\n * View names the scale the enabled notes spell. The surroundings come from a copy of\r\n * the screen the client keeps fresh in the background; call getScreen() (or await\r\n * captureSnapshot()) first if you need it guaranteed current.\r\n */\r\n describeContext(): string | null\r\n /**\r\n * The description as ordered, tagged parts ({ kind: 'field' | 'meaning' | 'location' | …, text }),\r\n * for consumers that want to keep, drop, reorder or style some of them.\r\n */\r\n describeContextParts(): M8DescriptionPart[] | null\r\n}\r\n\r\n/** Where calls and events go: the yam8d host over post-me, or an engine in this page. */\r\ninterface Backend {\r\n call<K extends keyof M8HostMethods>(method: K, ...args: Parameters<M8HostMethods[K]>): ReturnType<M8HostMethods[K]>\r\n on<K extends keyof M8HostEvents>(event: K, callback: (payload: M8HostEvents[K]) => void): () => void\r\n close(): void\r\n}\r\n\r\nconst hostBackend = (connection: Connection<M8ClientMethods, M8HostEvents, M8HostMethods, M8ClientEvents>): Backend => {\r\n const remote = connection.remoteHandle()\r\n return {\r\n call: (method, ...args) => remote.call(method, ...args),\r\n on: (event, callback) => {\r\n remote.addEventListener(event, callback)\r\n return () => remote.removeEventListener(event, callback)\r\n },\r\n close: () => connection.close(),\r\n }\r\n}\r\n\r\nconst engineBackend = (engine: M8Engine): Backend => ({\r\n call: (method, ...args) => (engine.methods[method] as (...a: unknown[]) => ReturnType<M8HostMethods[typeof method]>)(...args),\r\n on: (event, callback) => engine.on(event, callback),\r\n close: () => engine.dispose(),\r\n})\r\n\r\n/** A messenger whose listeners can all be removed, to abandon a handshake that timed out. */\r\nconst disposableMessenger = (inner: Messenger) => {\r\n const removers: (() => void)[] = []\r\n const messenger: Messenger = {\r\n postMessage: (message: unknown, transfer?: Transferable[]) => inner.postMessage(message, transfer),\r\n addMessageListener: (listener: (event: MessageEvent) => void) => {\r\n const remove = inner.addMessageListener(listener)\r\n removers.push(remove)\r\n return remove\r\n },\r\n }\r\n const dispose = () => {\r\n for (const remove of removers.splice(0)) remove()\r\n }\r\n return { messenger, dispose }\r\n}\r\n\r\nconst inIframe = () => {\r\n if (typeof window === 'undefined') return false\r\n try {\r\n return window.parent !== window\r\n } catch {\r\n return true\r\n }\r\n}\r\n\r\nconst isUserCancel = (error: unknown) => error instanceof DOMException && (error.name === 'NotFoundError' || error.name === 'AbortError')\r\n\r\n/** Which device APIs the browser offers for standalone mode. */\r\nexport const getDeviceSupport = (): M8DeviceSupport => {\r\n const nav = typeof navigator === 'undefined' ? null : navigator\r\n return {\r\n serial: !!nav && 'serial' in nav,\r\n usb: !!nav && 'usb' in nav,\r\n midi: !!nav && 'requestMIDIAccess' in nav,\r\n }\r\n}\r\n\r\nconst getDefaultState = (): M8State => ({\r\n viewName: null,\r\n viewTitle: null,\r\n minimapKey: null,\r\n cursorPos: null,\r\n cursorRect: null,\r\n selectionMode: false,\r\n highlightColor: null,\r\n titleColor: null,\r\n backgroundColor: null,\r\n cursorColor: null,\r\n textUnderCursor: null,\r\n currentLine: null,\r\n deviceModel: null,\r\n fontMode: null,\r\n systemInfo: null,\r\n macroRunning: false,\r\n})\r\n\r\nclass M8ClientImpl implements M8Client {\r\n private backend: Backend | null = null\r\n private backendUnsubscribers: (() => void)[] = []\r\n private device: ConnectedBus | null = null\r\n private _mode: M8ClientMode | null = null\r\n private audio: M8Audio | null = null\r\n private connecting: Promise<void> | null = null\r\n private readonly stateCallbacks = new Set<(state: M8State) => void>()\r\n private readonly viewCallbacks = new Set<(viewName: string | null, viewTitle: string | null) => void>()\r\n private readonly cursorCallbacks = new Set<(pos: M8State['cursorPos'], rect: M8State['cursorRect'], selectionMode: boolean) => void>()\r\n private readonly textCallbacks = new Set<(textUnderCursor: string | null, currentLine: string | null) => void>()\r\n private readonly keyCallbacks = new Set<(keys: number) => void>()\r\n private readonly transportCallbacks = new Set<(event: M8TransportEvent) => void>()\r\n private readonly connectionCallbacks = new Set<(connected: boolean) => void>()\r\n private _state: M8State = getDefaultState()\r\n private _screen: M8Screen | null = null\r\n private screenRefreshing = false\r\n private screenDirty = false\r\n private readonly config: M8SdkConfig\r\n\r\n constructor(config: M8SdkConfig = {}) {\r\n this.config = config\r\n }\r\n\r\n get state(): M8State {\r\n return this._state\r\n }\r\n\r\n get isConnected(): boolean {\r\n return this.backend !== null\r\n }\r\n\r\n get mode(): M8ClientMode | null {\r\n return this._mode\r\n }\r\n\r\n get isAudioConnected(): boolean {\r\n return this.audio?.connected ?? false\r\n }\r\n\r\n get deviceTransport(): M8DeviceTransport | null {\r\n return (this.device?.transport as M8DeviceTransport | undefined) ?? null\r\n }\r\n\r\n async connect(): Promise<void> {\r\n this.connecting ??= this.resolveMode()\r\n return this.connecting\r\n }\r\n\r\n /** Picks host or standalone mode (see M8SdkConfig.mode) and sets it up. */\r\n private async resolveMode(): Promise<void> {\r\n const mode = this.config.mode ?? 'auto'\r\n if (mode === 'standalone' || (mode === 'auto' && !inIframe())) {\r\n await this.startStandalone()\r\n return\r\n }\r\n\r\n // An unmarked iframe may be some other page embedding the app: give up after a while\r\n const markedByYam8d = typeof window !== 'undefined' && window.name === YAM8D_FRAME_NAME\r\n const timeoutMs = mode === 'host' || markedByYam8d ? null : (this.config.handshakeTimeoutMs ?? DEFAULT_HANDSHAKE_TIMEOUT_MS)\r\n const connected = await this.handshake(timeoutMs)\r\n if (!connected) {\r\n if (this.config.debug) console.log('[M8 SDK] No yam8d host answered, switching to standalone mode')\r\n await this.startStandalone()\r\n }\r\n }\r\n\r\n private async handshake(timeoutMs: number | null): Promise<boolean> {\r\n let messenger: Messenger = new WindowMessenger({\r\n localWindow: window,\r\n remoteWindow: window.parent,\r\n remoteOrigin: '*',\r\n })\r\n\r\n if (this.config.debug) {\r\n messenger = DebugMessenger(messenger, (msg: string, ...args: unknown[]) => {\r\n console.log('[M8 SDK]', msg, ...args)\r\n })\r\n }\r\n\r\n const disposable = disposableMessenger(messenger)\r\n const handshake = ChildHandshake<M8ClientMethods, M8HostEvents, M8HostMethods, M8ClientEvents>(disposable.messenger, {\r\n ping: async () => 'pong',\r\n })\r\n\r\n let timer: ReturnType<typeof setTimeout> | undefined\r\n const timedOut = new Promise<null>((resolve) => {\r\n if (timeoutMs !== null) timer = setTimeout(() => resolve(null), timeoutMs)\r\n })\r\n const connection = await Promise.race([handshake, timedOut])\r\n clearTimeout(timer)\r\n if (!connection) {\r\n disposable.dispose()\r\n return false\r\n }\r\n\r\n this._mode = 'host'\r\n await this.attach(hostBackend(connection))\r\n connection.localHandle().emit('ready', undefined)\r\n return true\r\n }\r\n\r\n private async startStandalone(): Promise<void> {\r\n this._mode = 'standalone'\r\n if (this.config.autoConnect === false) return\r\n try {\r\n await this.openDevice('auto', false)\r\n } catch (error) {\r\n // Nothing granted yet (or the device is busy, e.g. open in yam8d): wait for connectDevice()\r\n if (this.config.debug) console.log('[M8 SDK] No device reopened automatically:', error)\r\n }\r\n }\r\n\r\n /**\r\n * Opens the device and runs an engine on it. 'auto' without a prompt tries each granted\r\n * API in turn; with a prompt it picks the first API the browser supports.\r\n */\r\n private async openDevice(transport: 'auto' | M8DeviceTransport, prompt: boolean): Promise<void> {\r\n const conn = connection()\r\n if (!conn.browserSupport) throw new Error('This browser has no Web Serial, WebUSB or Web MIDI: the M8 cannot be reached from this page')\r\n\r\n const candidates: ('auto' | M8DeviceTransport)[] =\r\n transport === 'auto' && !prompt ? (['serial', 'usb', 'midi'] as const).filter((t) => conn.available[t]) : [transport]\r\n\r\n // The same port cannot be opened twice: release the current device first\r\n await this.closeDevice()\r\n\r\n let bus: ConnectedBus | undefined\r\n let lastError: unknown = new Error('No M8 device available')\r\n for (const candidate of candidates) {\r\n try {\r\n bus = await conn.connect({ transport: candidate, prompt })\r\n break\r\n } catch (error) {\r\n lastError = error\r\n // The M8 Headless can need a moment after being picked: retry on the granted device\r\n if (prompt && !isUserCancel(error)) {\r\n bus = await connectWithRetry(() => conn.connect({ transport: candidate, prompt: false }), { maxAttempts: 2 })\r\n if (bus) break\r\n }\r\n }\r\n }\r\n if (!bus) throw lastError\r\n\r\n const engine = createM8Engine(bus, { debug: this.config.debug })\r\n this.device = bus\r\n await this.attach(engineBackend(engine))\r\n // The extractor listens now: ask for a full redraw so the screen state is complete\r\n bus.commands.resetScreen()\r\n\r\n const opened = bus\r\n void bus.closed.then(() => {\r\n if (this.device !== opened) return\r\n if (this.config.debug) console.log('[M8 SDK] Device connection ended')\r\n this.device = null\r\n this.detach()\r\n })\r\n }\r\n\r\n private async closeDevice(): Promise<void> {\r\n const bus = this.device\r\n if (!bus) return\r\n this.device = null\r\n this.detach()\r\n await bus.close()\r\n }\r\n\r\n /** Routes calls and events through `backend`. */\r\n private async attach(backend: Backend): Promise<void> {\r\n this.detach()\r\n this.backend = backend\r\n const subs = this.backendUnsubscribers\r\n\r\n subs.push(\r\n backend.on('stateChanged', (state) => {\r\n this._state = state\r\n this.refreshScreen()\r\n this.stateCallbacks.forEach((cb) => {\r\n cb(state)\r\n })\r\n }),\r\n )\r\n\r\n subs.push(\r\n backend.on('viewChanged', ({ viewName, viewTitle }) => {\r\n this._state = { ...this._state, viewName, viewTitle }\r\n this.refreshScreen()\r\n this.viewCallbacks.forEach((cb) => {\r\n cb(viewName, viewTitle)\r\n })\r\n }),\r\n )\r\n\r\n subs.push(\r\n backend.on('cursorMoved', ({ pos, rect, selectionMode }) => {\r\n this._state = { ...this._state, cursorPos: pos, cursorRect: rect, selectionMode }\r\n this.refreshScreen()\r\n this.cursorCallbacks.forEach((cb) => {\r\n cb(pos, rect, selectionMode)\r\n })\r\n }),\r\n )\r\n\r\n subs.push(\r\n backend.on('textUpdated', ({ textUnderCursor, currentLine }) => {\r\n this._state = { ...this._state, textUnderCursor, currentLine }\r\n this.refreshScreen()\r\n this.textCallbacks.forEach((cb) => {\r\n cb(textUnderCursor, currentLine)\r\n })\r\n }),\r\n )\r\n\r\n subs.push(\r\n backend.on('transport', (event) => {\r\n this.transportCallbacks.forEach((cb) => {\r\n cb(event)\r\n })\r\n }),\r\n )\r\n\r\n subs.push(\r\n backend.on('keyPressed', ({ keys }) => {\r\n this.keyCallbacks.forEach((cb) => {\r\n cb(keys)\r\n })\r\n }),\r\n )\r\n\r\n this.connectionCallbacks.forEach((cb) => {\r\n cb(true)\r\n })\r\n await this.fetchState()\r\n this.refreshScreen()\r\n }\r\n\r\n private detach(): void {\r\n const backend = this.backend\r\n if (!backend) return\r\n this.backend = null\r\n this.backendUnsubscribers.splice(0).forEach((unsubscribe) => {\r\n unsubscribe()\r\n })\r\n backend.close()\r\n this._screen = null\r\n this.connectionCallbacks.forEach((cb) => {\r\n cb(false)\r\n })\r\n }\r\n\r\n private requireBackend(): Backend {\r\n if (this.backend) return this.backend\r\n if (this._mode === 'standalone') throw new Error('No M8 device connected: call connectDevice() first')\r\n throw new Error('M8 SDK client is not connected')\r\n }\r\n\r\n /**\r\n * Keeps the cached screen close to the state without a round-trip per event: at most\r\n * one request in flight, and one more queued if events arrived meanwhile.\r\n */\r\n private refreshScreen(): void {\r\n const backend = this.backend\r\n if (!backend) return\r\n if (this.screenRefreshing) {\r\n this.screenDirty = true\r\n return\r\n }\r\n this.screenRefreshing = true\r\n backend\r\n .call('getScreen')\r\n .then((screen: M8Screen) => {\r\n if (this.backend === backend) this._screen = screen\r\n })\r\n .catch(() => {\r\n // Descriptions simply fall back to the cursor row alone.\r\n })\r\n .finally(() => {\r\n this.screenRefreshing = false\r\n if (this.screenDirty) {\r\n this.screenDirty = false\r\n this.refreshScreen()\r\n }\r\n })\r\n }\r\n\r\n async connectDevice(options: M8DeviceConnectOptions = {}): Promise<void> {\r\n await this.connect()\r\n if (this._mode === 'host') return\r\n await this.openDevice(options.transport ?? 'auto', true)\r\n if (options.audio) {\r\n // The device is usable without sound: report and carry on\r\n await this.connectAudio().catch((error: unknown) => console.warn('[M8 SDK] M8 audio not started:', error))\r\n }\r\n }\r\n\r\n async disconnectDevice(): Promise<void> {\r\n await this.disconnectAudio()\r\n await this.closeDevice()\r\n }\r\n\r\n async connectAudio(): Promise<void> {\r\n await this.connect()\r\n if (this._mode === 'host') return\r\n this.audio ??= createM8Audio()\r\n // Never fall back to another input: playing a microphone through the speakers feeds back\r\n await this.audio.connect({ requireM8: true })\r\n }\r\n\r\n async disconnectAudio(): Promise<void> {\r\n await this.audio?.disconnect()\r\n }\r\n\r\n getDevice(): M8DeviceBus | null {\r\n const bus = this.device\r\n if (!bus) return null\r\n return {\r\n transport: bus.transport as M8DeviceTransport,\r\n events: bus.protocol.eventBus as unknown as M8DeviceBus['events'],\r\n commands: bus.commands,\r\n }\r\n }\r\n\r\n onConnectionChange(callback: (connected: boolean) => void): () => void {\r\n this.connectionCallbacks.add(callback)\r\n return () => this.connectionCallbacks.delete(callback)\r\n }\r\n\r\n async navigateToView(viewName: string): Promise<boolean> {\r\n return this.requireBackend().call('navigateToView', viewName)\r\n }\r\n\r\n async navigateTo(x: number, y: number): Promise<boolean> {\r\n return this.requireBackend().call('navigateTo', x, y)\r\n }\r\n\r\n async setValueToHex(targetHex: number): Promise<boolean> {\r\n return this.requireBackend().call('setValueToHex', targetHex)\r\n }\r\n\r\n async setValueToInt(targetInt: number): Promise<boolean> {\r\n return this.requireBackend().call('setValueToInt', targetInt)\r\n }\r\n\r\n async setValueFloat(targetFloat: number): Promise<boolean> {\r\n return this.requireBackend().call('setValueFloat', targetFloat)\r\n }\r\n\r\n async setNote(noteString: string): Promise<boolean> {\r\n return this.requireBackend().call('setNote', noteString)\r\n }\r\n\r\n async setValueToString(targetString: string, exact = true, searchInCurrentLine = false): Promise<boolean> {\r\n return this.requireBackend().call('setValueToString', targetString, exact, searchInCurrentLine)\r\n }\r\n\r\n async browseFile(targetText: string, exact = true): Promise<boolean> {\r\n return this.requireBackend().call('browseFile', targetText, exact)\r\n }\r\n\r\n async sendKeyPress(keys: M8KeyName[]): Promise<void> {\r\n return this.requireBackend().call('sendKeyPress', keys)\r\n }\r\n\r\n async sendKeyDown(keys: M8KeyName[]): Promise<void> {\r\n return this.requireBackend().call('sendKeyDown', keys)\r\n }\r\n\r\n async sendKeyUp(): Promise<void> {\r\n return this.requireBackend().call('sendKeyUp')\r\n }\r\n\r\n getState(): M8State {\r\n return this._state\r\n }\r\n\r\n async fetchState(): Promise<M8State> {\r\n const state = await this.requireBackend().call('getState')\r\n this._state = state\r\n return state\r\n }\r\n\r\n async getScreen(): Promise<M8Screen> {\r\n const screen = await this.requireBackend().call('getScreen')\r\n this._screen = screen\r\n return screen\r\n }\r\n\r\n async getScreenCells(): Promise<M8ScreenCells> {\r\n const cells = await this.requireBackend().call('getScreenCells')\r\n this._screen = { width: cells.width, height: cells.height, lines: cells.lines }\r\n return cells\r\n }\r\n\r\n async captureSnapshot(): Promise<M8Snapshot> {\r\n const [state, screen] = await Promise.all([this.fetchState(), this.getScreen()])\r\n const semantic = getSemanticContext(state, screen)\r\n return {\r\n capturedAt: Date.now(),\r\n state: {\r\n viewName: state.viewName,\r\n viewTitle: state.viewTitle,\r\n cursorPos: state.cursorPos,\r\n selectionMode: state.selectionMode,\r\n textUnderCursor: state.textUnderCursor,\r\n currentLine: state.currentLine,\r\n deviceModel: state.deviceModel,\r\n },\r\n semantic,\r\n description: semantic ? describeContext(semantic) : null,\r\n descriptionParts: semantic ? describeContextParts(semantic) : null,\r\n screen,\r\n }\r\n }\r\n\r\n onStateChange(callback: (state: M8State) => void): () => void {\r\n this.stateCallbacks.add(callback)\r\n return () => this.stateCallbacks.delete(callback)\r\n }\r\n\r\n onViewChange(callback: (viewName: string | null, viewTitle: string | null) => void): () => void {\r\n this.viewCallbacks.add(callback)\r\n return () => this.viewCallbacks.delete(callback)\r\n }\r\n\r\n onCursorMove(callback: (pos: M8State['cursorPos'], rect: M8State['cursorRect'], selectionMode: boolean) => void): () => void {\r\n this.cursorCallbacks.add(callback)\r\n return () => this.cursorCallbacks.delete(callback)\r\n }\r\n\r\n onTextUpdate(callback: (textUnderCursor: string | null, currentLine: string | null) => void): () => void {\r\n this.textCallbacks.add(callback)\r\n return () => this.textCallbacks.delete(callback)\r\n }\r\n\r\n onKeyPress(callback: (keys: number) => void): () => void {\r\n this.keyCallbacks.add(callback)\r\n return () => this.keyCallbacks.delete(callback)\r\n }\r\n\r\n async enableMidiTransport(): Promise<{ ok: boolean; error?: string }> {\r\n return this.requireBackend().call('enableMidiTransport')\r\n }\r\n\r\n onTransport(callback: (event: M8TransportEvent) => void): () => void {\r\n this.transportCallbacks.add(callback)\r\n return () => this.transportCallbacks.delete(callback)\r\n }\r\n\r\n disconnect(): void {\r\n void this.audio?.disconnect()\r\n void this.closeDevice()\r\n this.detach()\r\n this.stateCallbacks.clear()\r\n this.viewCallbacks.clear()\r\n this.cursorCallbacks.clear()\r\n this.textCallbacks.clear()\r\n this.keyCallbacks.clear()\r\n this.transportCallbacks.clear()\r\n this.connectionCallbacks.clear()\r\n }\r\n\r\n getSemanticContext(): M8SemanticContext | null {\r\n return getSemanticContext(this._state, this._screen)\r\n }\r\n\r\n describeContext(): string | null {\r\n const ctx = getSemanticContext(this._state, this._screen)\r\n return ctx ? describeContext(ctx) : null\r\n }\r\n\r\n describeContextParts(): M8DescriptionPart[] | null {\r\n const ctx = getSemanticContext(this._state, this._screen)\r\n return ctx ? describeContextParts(ctx) : null\r\n }\r\n}\r\n\r\n/**\r\n * Creates a client and connects it. Inside a yam8d External Apps iframe it talks to yam8d;\r\n * anywhere else it runs standalone (see M8SdkConfig.mode) and reopens a device the page was\r\n * granted before. A first standalone connection needs connectDevice() from a click.\r\n */\r\nexport async function createM8Client(config: M8SdkConfig = {}): Promise<M8Client> {\r\n const client = new M8ClientImpl(config)\r\n await client.connect()\r\n return client\r\n}\r\n\r\nexport function createM8ClientSync(config: M8SdkConfig = {}): { client: M8Client; connect: () => Promise<void> } {\r\n const client = new M8ClientImpl(config)\r\n return {\r\n client,\r\n connect: async () => {\r\n await client.connect()\r\n },\r\n }\r\n}\r\n\r\nexport default {\r\n createM8Client,\r\n createM8ClientSync,\r\n}\r\n","/**\r\n * theme.ts\r\n *\r\n * The device theme: the 13 colours of the Theme View (System Settings > EDIT THEME),\r\n * read off its screen, and the few colours any screen shows on its own.\r\n */\r\n\r\nimport type { M8Screen, M8State, RGB } from './types'\r\n\r\nexport type M8ThemeRole =\r\n | 'background'\r\n | 'textEmpty'\r\n | 'textInfo'\r\n | 'textDefault'\r\n | 'textValue'\r\n | 'textTitles'\r\n | 'playMarkers'\r\n | 'cursor'\r\n | 'selection'\r\n | 'scopeSlider'\r\n | 'meterLow'\r\n | 'meterMid'\r\n | 'meterPeak'\r\n\r\n/** The Theme View rows, top to bottom, with the label the device shows. */\r\nexport const M8_THEME_ROLES: readonly { role: M8ThemeRole; label: string }[] = [\r\n { role: 'background', label: 'BACKGROUND' },\r\n { role: 'textEmpty', label: 'TEXT:EMPTY' },\r\n { role: 'textInfo', label: 'TEXT:INFO' },\r\n { role: 'textDefault', label: 'TEXT:DEFAULT' },\r\n { role: 'textValue', label: 'TEXT:VALUE' },\r\n { role: 'textTitles', label: 'TEXT:TITLES' },\r\n { role: 'playMarkers', label: 'PLAY MARKERS' },\r\n { role: 'cursor', label: 'CURSOR' },\r\n { role: 'selection', label: 'SELECTION' },\r\n { role: 'scopeSlider', label: 'SCOPE/SLIDER' },\r\n { role: 'meterLow', label: 'METER LOW' },\r\n { role: 'meterMid', label: 'METER MID' },\r\n { role: 'meterPeak', label: 'METER PEAK' },\r\n]\r\n\r\n/** View name the SDK reports on the Theme View (title \"THEME SETTINGS\"). */\r\nexport const M8_THEME_VIEW = 'themesettings'\r\n\r\nexport interface M8Theme {\r\n /** Every theme colour as '#rrggbb'. */\r\n colors: Record<M8ThemeRole, string>\r\n /** Colour model the Theme View was showing. HSV values are converted to RGB (H, S and V read as 00-FF). */\r\n mode: 'rgb' | 'hsv'\r\n /** THEME NAME, or null when unnamed. */\r\n name: string | null\r\n}\r\n\r\nconst squash = (s: string): string => s.replace(/\\s+/g, '').toUpperCase()\r\n\r\nconst hex2 = (n: number): string => Math.max(0, Math.min(255, Math.round(n))).toString(16).padStart(2, '0')\r\n\r\nconst rgbToHex = (r: number, g: number, b: number): string => `#${hex2(r)}${hex2(g)}${hex2(b)}`\r\n\r\n/** HSV with every channel 0-255 (the Theme View shows them as hex bytes). */\r\nconst hsvToHex = (h: number, s: number, v: number): string => {\r\n const hue = (h / 256) * 6\r\n const sat = s / 255\r\n const val = v / 255\r\n const i = Math.floor(hue) % 6\r\n const f = hue - Math.floor(hue)\r\n const p = val * (1 - sat)\r\n const q = val * (1 - f * sat)\r\n const t = val * (1 - (1 - f) * sat)\r\n const [r, g, b] = [\r\n [val, t, p],\r\n [q, val, p],\r\n [p, val, t],\r\n [p, q, val],\r\n [t, p, val],\r\n [val, p, q],\r\n ][i]\r\n return rgbToHex(r * 255, g * 255, b * 255)\r\n}\r\n\r\nconst ROLE_BY_LABEL = new Map(M8_THEME_ROLES.map(({ role, label }) => [squash(label), role]))\r\n\r\n/**\r\n * Reads the theme off the Theme View screen (from getScreen()). Returns null unless\r\n * all 13 colour rows are found, so a half-drawn screen is never taken for a theme.\r\n *\r\n * @example\r\n * ```ts\r\n * if (m8.state.viewName === M8_THEME_VIEW) {\r\n * const theme = parseThemeScreen(await m8.getScreen())\r\n * theme?.colors.textValue // '#ffffff'\r\n * }\r\n * ```\r\n */\r\nexport function parseThemeScreen(screen: M8Screen): M8Theme | null {\r\n let mode: M8Theme['mode'] = 'rgb'\r\n let name: string | null = null\r\n const colors: Partial<Record<M8ThemeRole, string>> = {}\r\n\r\n for (const raw of screen.lines) {\r\n const line = raw.replace(/[{}]/g, '0')\r\n const header = /^\\s*MODE\\s+([A-Z])\\s+([A-Z])\\s+([A-Z])\\b/i.exec(line)\r\n if (header) {\r\n mode = squash(header.slice(1).join('')) === 'HSV' ? 'hsv' : 'rgb'\r\n continue\r\n }\r\n const named = /^\\s*THEME\\s+NAME\\s+(\\S.*?)\\s*$/i.exec(line)\r\n if (named) {\r\n // The track panel can share the row on the right: the name ends at the first wide gap.\r\n const text = named[1].split(/\\s{2,}/)[0]\r\n name = /^-+$/.test(text) ? null : text\r\n continue\r\n }\r\n const row = /^\\s*([A-Z][A-Z:/. ]*?)\\s+([0-9A-F]{2})\\s+([0-9A-F]{2})\\s+([0-9A-F]{2})(?:\\s|$)/i.exec(line)\r\n if (!row) continue\r\n const role = ROLE_BY_LABEL.get(squash(row[1]))\r\n if (!role || colors[role]) continue\r\n const [a, b, c] = row.slice(2, 5).map((v) => parseInt(v, 16))\r\n colors[role] = mode === 'hsv' ? hsvToHex(a, b, c) : rgbToHex(a, b, c)\r\n }\r\n\r\n if (M8_THEME_ROLES.some(({ role }) => !colors[role])) return null\r\n return { colors: colors as Record<M8ThemeRole, string>, mode, name }\r\n}\r\n\r\nconst rgbHex = (c: RGB | null | undefined): string | undefined => (c ? rgbToHex(c.r, c.g, c.b) : undefined)\r\n\r\n/**\r\n * The theme colours any screen gives away without opening the Theme View: the\r\n * background and the title colour. (The cursor rectangle is animated on the device, so\r\n * `state.cursorColor` is not the theme's CURSOR colour.)\r\n */\r\nexport function observeThemeColors(state: M8State): Partial<Record<M8ThemeRole, string>> {\r\n const out: Partial<Record<M8ThemeRole, string>> = {}\r\n const background = rgbHex(state.backgroundColor)\r\n const titles = rgbHex(state.titleColor)\r\n if (background) out.background = background\r\n if (titles) out.textTitles = titles\r\n return out\r\n}\r\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["overlap","version","description","notes","views","fxCommands","viewSchema","MAX_STEPS","views","graphJson","viewListJson","rgbHex","TICKS_PER_STEP","instrumentTypes","instrumentTypeNames","paramSchema","instrumentTypes","schema","hex2","squash","schema","createEventBus","eventMap","args","event","cb","callback","littleEndian","parseRectCommand","x","data","y","length","bytes","i","parseCharacterCommand","parseWaveCommand","wave","parseKeyCommand","device","parseSystemCommand","protocol","lastSystemInfo","eventBus","cachedSystemInfos","computeLayoutMetrics","spacingX","spacingY","offY","screenWidth","screenHeight","rectOffset","sys","isV2","fm","dataLength","buffer","command","metrics","createSlipParser","view","state","finalizePacket","out","dv","pushByte","byte","queuedUsbReader","parser","messages","terminated","resolver","result","maybe","msg","resolve","queuedSerialReader","reader","done","value","chunk","vendorId","productId","waitTime","usbWriter","serialWriter","writer","midiCommands","commands","interval","output","press","keys","msb","velocity","note","safeChannel","channel","safeProgram","program","statusByte","M8_SYSEX_HEADER","SYSEX_END","isM8Sysex","decode7BitPacked","msbByte","remaining","groupLen","bit","low7","decodeM8Sysex","encoded","midiReader","pending","findHeaderIndex","header","start","buf","match","j","appendBytes","merged","a","b","processChunk","hdrIdx","endIdx","decoded","handler","evt","message","stopped","stop","input","onStateChange","beginRead","read","transportLabel","connection","webMidiAvailable","webSerialAvailable","webUsbAvailable","usbConnect","devices","d","prompt","deviceCommands","serialConnect","ports","port","p","rawReader","portCommands","midiConnect","access","disconnectM8Devices","inputs","outputs","outputCommands","available","chosen","transport","protocolHandler","closing","closed","error","connectWithRetry","attempt","maxAttempts","connect","err","retryDelayMs","M8_AUDIO_CAPTURE_CONSTRAINTS","findM8Input","getM8AudioInputDevice","getM8InputStream","probe","requireM8","track","createM8Audio","ctx","connecting","resumeOnGesture","context","resume","disconnect","open","stream","source","destination","tracks","DIRS","MAX_LAYOUTS","cellId","rect","parseCellId","w","h","id","emptyLayout","createCursorMap","layouts","changed","options","touch","layout","key","oldest","addCell","saved","from","dir","to","moves","text","fromId","toId","textKey","wentBackwards","GUESS_COST","overlap$1","a1","b1","a0","b0","guessMove","cx","cy","best","cell","ox","oy","along","across","findPath","isGoal","cost","previous","lowest","candidate","c","path","at","step","skip","forbid","learned","guessed","toRect","next","m8_view_context_default","\"fontMode\"","\"playbackIndicator\"","\"screenGeometry\"","\"emptyCell\"","\"relativeHex\"","\"rowIndex\"","\"titleAsterisk\"","\"chainRef\"","\"phraseRef\"","\"note\"","\"velocity\"","\"instrumentRef\"","\"fxCommand\"","\"fxValue\"","\"transpose\"","\"ticks\"","\"volume\"","\"ppq\"","\"instrumentName\"","\"eqSlot\"","\"noteInterval\"","\"onOff\"","\"semitoneOffset\"","\"swing\"","\"instrumentType\"","\"name\"","\"parameter\"","\"option\"","\"action\"","\"fileEntry\"","\"_note\"","\"types\"","\"sampler\"","\"verified\"","\"commands\"","\"macrosynth\"","\"fmsynth\"","\"hypersynth\"","\"midiout\"","\"modulation\"","\"envelope\"","\"lfo\"","\"relativeCommands\"","\"description\"","OVERHANG_PX","ROW_WEIGHT","SAME_ROW_REACH","PANEL_COLUMN","ROW_NUMBER_WIDTH","name","gridRows","width","viewName","first","last","lines","line","row","FIXED_GRID_VIEWS","GRID_COLUMNS","hasFixedGrid","fixedGridLayout","columns","grid","idAt","column","snapToGrid","col","chars","rowOf","panelColumnFor","layoutSignature","token","panelColumn","charRect","textRuns","runs","visible","ch","covered","cellHasText","overlap","coversX","cursor","target","coversY","covers","center","rectDistance","ca","gapX","gapY","pointOffset","point","pointGoal","goalReached","goal","candidates","inGrid","cells","char","isRowNumber","run","pickGoal","goals","textAt","runEnd","besideClick","area","dy","dx","anyBeside","anyKnown","knownFirst","rightOfClick","score","tie","M8KeyMask","pressKeys","left","right","up","down","shift","play","opt","edit","keyMap","DIRECTION_MASK","MODIFIER_MASK","NOT_HELD_MASK","MAX_STEPS$1","MAX_EXPLORE","MAX_RETURN_STEPS","MAX_GOALS","MOVE_TIMEOUT_MS","MOVE_GRACE_MS","PASSIVE_SETTLE_MS","sameRect","singleDirection","dirs","createCursorNavigator","currentKeysMask","holdMask","waitingForKeyAck","moveTimeout","passive","store","getRect","current","layoutKey","system","getScreenLines","clearMoveTimeout","scheduleMoveTimeout","advance","ms","finish","reached","tap","bus","isGoalCell","r","towardGoal","leavesGrid","planLayout","cursorMap","chooseMove","known","here","retry","move","giveUp","localMinimum","onRow","leftOfCursor","cursorRow","towardRow","blockedNextToRow","clicked","settle","reason","onKey","onRect","unsubscribeRect","carried","fixed","normalizeForSearch","FILE_BROWSER_EXTENSION_SUFFIX_REGEX","getFileBrowserSnapshotFingerprint","snapshot","getSelectedFileBrowserEntry","isFileBrowserView","normalizedViewName","normalizedViewTitle","isParentDirectoryEntry","entry","isDirectoryEntry","isM8FileEntry","getFileEntrySearchTerms","trimmedEntry","normalizedFullName","normalizedBaseName","classifyFileBrowserEntry","parseFileBrowserScreen","rowText","title","entries","cursorText","mergeFileBrowserOrder","screen","order","anchor","offset","index","position","tokenize","TITLE_STOP_WORDS","getDirectoryHintTokens","targetText","viewTitle","scoreDirectoryRelevance","directoryKey","related","hints","wait","createHostIO","hasBus","getKeySink","sendKeys","pressAndRelease","delayMs","waitForTextUnderCursorChange","settled","unsubscribe","timeout","currentValue","previousValue","timeoutMs","getFileBrowserSnapshot","unsubscribeText","unsubscribeLine","unsubscribeCursor","unsubscribeViewName","unsubscribeViewTitle","previousFingerprint","previousSnapshot","checkForChange","currentSnapshot","browseLog","details","JUMP_ROWS","MIN_FULL_WINDOW","MAX_DEPTH","MAX_SCREENS_PER_LEVEL","createLevel","opposite","direction","createFileBrowserSearch","io","fullWindowRows","jumpClampsAtEnd","foldersFirst","normalizedTarget","targetIncludesExtension","isActive","stepFileBrowser","keyMask","nextSnapshot","waitForFileBrowserSnapshotChange","stepMask","jumpMask","applyAlphabeticalSort","entryMatchesTarget","exact","term","comparableName","trimmed","primarySearchTerm","typedTarget","readScreen","absorb","level","viewKey","pageLevel","jumped","moved","reachedEnd","fromIndex","toIndex","travelled","directionToward","moveCursorToKey","startScreen","reversed","delta","way","selectVisibleMatch","levelFiles","reviewSortedLevel","files","file","firstFile","folder","nothingLeftBelow","alphabeticalHint","pickDirection","pickNextDirectory","cursorIndex","kind","onScreen","distance","rank","climbToParent","searchLevel","depth","pages","picked","directory","entered","outcome","parseHexValue","cleaned","parsed","parseIntValue","FLOAT_TOKEN_REGEX","parseFloatTokens","tokens","raw","negative","unsigned","dotIndex","intDigits","decDigits","intValue","decValue","decScale","tickHundredths","valueHundredths","pickFloatTokenIndex","hint","hintText","detectFloatCursorFocus","unsignedRaw","inIntPart","inDecPart","floatHintMatchesToken","isCursorOnForeignFloat","ownIndex","formatFloatFromTicks","hundredths","ticks","sign","absolute","intPart","NOTE_ORDER","parseNoteValue","label","noteName","octave","noteOffset","createFloatEditor","targetFloat","tokenIndex","lastTokens","readFloatState","initialTokens","debugLog","initialToken","targetTicks","makeFocusStats","statsByFocus","UNIT_IN_TICKS","startedAt","focus","sideControlDisabled","driftedToForeignField","previousTicks","valueTwoStepsAgo","bestTicks","bestDistance","fruitlessStreak","reachedTarget","gaveUp","getMaskForChannel","positiveKey","negativeKey","effectiveDirection","applyBurst","stats","mask","beforeLine","presses","waitForCurrentLineChange","ensureFloatCursorSide","currentFocusOf","side","beforeHint","towardDot","awayFromDot","landed","diffTicks","isOscillating","crossesTarget","desiredSide","focusStats","upDownProbeGateTicks","beforeTicks","focusAtPress","channelAtPress","nextState","deltaTicks","pressedStats","snapStartedAt","snapState","gap","snapDistance","snapStats","snapChannel","snapPresses","beforeSnapTicks","afterSnap","finalToken","success","m8Graph_generated_default","\"to\"","\"dir\"","\"keys\"","\"weight\"","\"back\"","\"via\"","\"cursor\"","\"label\"","viewlist_default","buildGraph","graph","edgeKeys","rawKey","list","json","edges","perFrom","e","viewGraph","viewNames","getViewGraph","getViewNames","s","STABILIZE_MS","defaultCursor","AssemblyCursorExtractor","now","completedCursor","assembly","newAssembly","selectionCursor","incrementalSelection","isHorizontal","prev","top","bottom","lineLeft","lineRight","prevSpan","lineTop","lineBottom","outerW","outerH","sample","horizontals","verticals","refCursor","prevCenterX","prevCenterY","v","rightX","latestTs","centerX","centerY","tlX","tlRect","tlY","corner","height","rgbHex$1","registerViewExtractor","CursorAssembly","AssemblyViewNameExtractor","noSpaces","titleRaw","normalized","fg","bg","upto","currentGx","partialRaw","partial","pick","gx","cmd","offX","cellW","gy","cellH","extractor","CharacterGridTracker","sortedKeys","toRemove","cursorRect","pos","startGx","endGx","textUnderCursor","textHighlightColor","currentLine","maxX","fgRow","bgRow","charGridTracker","debugRectEndTime","win","cursorExtractor","cursorColor","prevPos","prevSelectionMode","selectionMode","prevRect","modeJustChanged","systemInfoHandler","info","onText","events","getCurrentState","macroStatus","textGridToPixel","cellMetrics","gridX","gridY","keyNamesToMask","createHostMethods","guarded","fn","fallback","navigateToView","navigateTo","hex","editors","targetInt","setValueFloat","noteString","targetString","searchInCurrentLine","browseFile","getScreenCellColors","enableMidiTransport","TICKS_PER_STEP$1","createMidiTransport","lastTickAt","msPerTick","epoch","timeStamp","onMessage","emit","spp","generation","started","m8","opening","calculateKeySequence","sequence","targetValue","diff","absDiff","steps16","stepsToTake","calculateNoteKeySequence","currentIndex","targetIndex","createValueEditors","targetHex","currentText","isInitialDashDash","startValue","beforePrime","newText","newValue","beforeEnterEdit","keySequence","finalText","finalValue","startTime","beforeStepText","bestValue","newDistance","diffToBest","absDiffToBest","directionToBest","beforeSnapStepText","snappedValue","targetNoteString","currentNormalized","afterFirst","parsedTarget","currentNote","precomputedSequence","precomputedFinal","seen","useOctaveStep","positiveDirection","reverseKey","previousNote","previousDistance","newNote","beforeReverseStep","steppedBack","getSearchSource","matches","normalizedValue","initialCursorText","bottomNoChangeCount","bottomStartTime","bottomSeedValues","normalizedCurrent","nextText","searchSource","normalizedText","createMacroRunner","queue","lastSent","active","onKeyDown","cancel","activate","onKeyEcho","cancelOnKeydown","deactivate","echoed","sent","initialStoreState","createM8Store","initial","listeners","allListeners","listener","set","zeroHeuristic","defaultEdgeCost","aStar","g","openSet","cameFrom","gScore","fScore","node","heuristic","lowestF","bestVal","val","cur","link","neighbors","edgeCost","tentativeG","MAX_STEPS","STEP_TIMEOUT_MS","SETTLE_MS","CURSOR_TIMEOUT_MS","CURSOR_SETTLE_MS","toKeyMasks","withShift","opts","map","seq","directions","FALLBACK_EDGES","edgeSignature","withFinalRelease","frames","GOAL_STATE","stateId","createViewNavigation","viewSet","steps","pendingResolve","session","outcomes","lastView","unsubscribeHistory","timer","clearTimer","resolveTarget","wanted","outcomeKey","movesFrom","groups","moveCursor","sig","group","back","others","stateGraph","before","nav","nextEdge","res","buttonCell","cursorCovers","complete","finishStep","reachedView","edge","executeNextStep","runButton","mySession","runner","goalTitle","startTitle","onChange","running","unsubscribeView","unsubscribeMacro","targetViewTitle","STATE_KEYS","createM8Engine","payload","macro","cursorNavigator","viewNavigation","midiTransport","methods","dirty","flushScheduled","lastCursor","lastText","flush","unsubscribeStore","m8_parameter_views_default","\"rows\"","\"zones\"","\"formats\"","\"unverified\"","\"WAVS\"","\"MACRO\"","\"SAMPL\"","\"FM\"","\"HYPER\"","\"EXTERN\"","\"MIDI\"","rawViews","instrumentTypes$1","instrumentTypeNames","def","parent","own","getParameterLayout","slice","cond","detectSection","sections","linesUpward","upper","hit","detectSlot","zone","z","zoneId","pattern","instrumentKeyFromText","letters","prefix","instrumentTypeFromLines","getInstrumentType","lookupVocabulary","norm","instrumentKey","k","camel","LABEL_SHAPE","OPERATOR_ROWS","OPERATOR_MOD_VALUE","operatorCells","cellOf","letter","rowKey","vocab","modSlotEntry","values","dest","slot","CC_LABEL","CC_VALUE_WIDTH","readParameterCells","m","found","zones","valueX","valueWidth","cc","nextIsLabel","base","NONE","detectFormat","parseValue","format","n","describeOption","t","fxCommands","instrumentTypes","VERIFIED_TYPES","typesOffering","offering","lookupFxCommand","category","mod","isRelativeFx","getInstrumentFxCommands","type","RELATIVE_FX_RULE","hex2$1","hex1","times","signed8","fmtSigned","formatSignedFxValue","QUARTERS","MAX_QUARTERS","repStepQuarters","formatRepStep","PITCH_CLASSES","noteToIndex","pc","indexToNote","KEY_NAMES","TICKS_PER_STEP","rowsPerStep","describeTic","where","describeFxValue","semis","eighths","relative","q","held","limit","bound","analyzeFxLane","inc","carriedIn","repStart","rows","cmdCell","lane","valCell","describeFxLane","src","relativeResetBy","inst","analyzeRelativeFx","states","resetStep","resetBy","trigger","trace","carriedLane","applied","viaRep","st","fmtOffset","sinceText","RESET_HINT","describeRelativeFx","rep","describeRelativeReset","SCALE_INTERVALS","SCALES","INTERVAL_NAMES","normalizeInterval","describeInterval","semitone","matchScaleShapes","enabledSemitones","hits","scale","root","analyzeScale","enabledNotes","notes","enabled","shapes","detuned","scaleSummaryParts","shape","formatOffset","EMPTY_PATTERNS","isEmptyRaw","parseHex","extractFromLine","parseViewTitle","extractViewId","rawTitle","markerMatch","marker","parts","stripPlaybackIndicator","parseColumn","empty","keepCase","fileKindOf","numericTypes","decimalTypes","hexValue","ext","parseRowIndex","rf","findColumn","cols","cursorX","bestDist","dist","parseRow","fields","viewDef","squash$1","SCREEN_MARGIN","screenDataLine","screenMatchesCursor","PHRASE_STEPS","GRID_FIRST_ROW","GRID_LAST_ROW","parseGridScreen","isSeparator","readPlayPositions","isMarker","readPhraseSteps","y0","cursorStep","carriedRepLane","caret","nearestFxLane","dists","min","closest","parseScaleNote","en","readScaleNotes","getParameterContext","head","trusted","instrumentType","pad","padded","section","activeField","getSemanticContext","isGridView","viewDescription","paramLayout","rawLine","scaleLayout","field","fxLane","relativeFx","resetsRelative","phrase","scaleNote","describeContextParts","describeParameterContext","scaleParts","describeScaleContext","partsOf","f","fieldValueText","meaning","describeFxCell","describeInstrumentCell","describeTableVolume","explained","fxInfoText","cmdKey","cmdField","rowHex","formatFieldValue","describeContext","repeated","laneText","shown","described","INSTRUMENT_TYPE_LABELS","SECTION_NAMES","FILE_KIND_TEXT","detune","formatParameterValue","unit","named","signed","YAM8D_FRAME_NAME","DEFAULT_HANDSHAKE_TIMEOUT_MS","hostBackend","remote","method","engineBackend","engine","disposableMessenger","removers","inner","transfer","remove","inIframe","isUserCancel","getDeviceSupport","getDefaultState","M8ClientImpl","config","mode","markedByYam8d","messenger","WindowMessenger","DebugMessenger","disposable","handshake","ChildHandshake","timedOut","conn","lastError","opened","backend","subs","semantic","createM8Client","client","createM8ClientSync","client_default","M8_THEME_ROLES","M8_THEME_VIEW","squash","hex2","rgbToHex","hsvToHex","hue","sat","ROLE_BY_LABEL","role","parseThemeScreen","colors","rgbHex","observeThemeColors","background","titles"],"sources":["../src/engine/eventBus.ts","../src/engine/protocol.ts","../src/engine/queuedReaders.ts","../src/engine/connection.ts","../src/engine/connectWithRetry.ts","../src/engine/audio.ts","../src/engine/cursorMap.ts","../src/m8-view-context.json","../src/engine/cursorTargets.ts","../src/engine/keys.ts","../src/engine/cursorNavigator.ts","../src/engine/host/fileBrowserHelpers.ts","../src/engine/host/hostIO.ts","../src/engine/host/fileBrowserSearch.ts","../src/engine/host/valueParsers.ts","../src/engine/host/floatEditor.ts","../src/engine/data/m8Graph.generated.json","../src/engine/data/viewlist.json","../src/engine/viewGraph.ts","../src/engine/viewExtractor.ts","../src/engine/host/hostState.ts","../src/engine/host/hostMethods.ts","../src/engine/host/midiTransport.ts","../src/engine/host/keySequences.ts","../src/engine/host/valueEditors.ts","../src/engine/macroRunner.ts","../src/engine/store.ts","../src/engine/astar.ts","../src/engine/viewNavigation.ts","../src/engine/engine.ts","../src/m8-parameter-views.json","../src/parameterViews.ts","../src/fxCommands.ts","../src/fxInsight.ts","../src/scaleInsight.ts","../src/viewContext.ts","../src/client.ts","../src/theme.ts"],"sourcesContent":["// A minimal typed event emitter.\n\n// biome-ignore lint/suspicious/noExplicitAny: library code, allowed to do that\nexport const createEventBus = <T extends Record<string, (...args: any[]) => void>>() => {\n // biome-ignore lint/suspicious/noExplicitAny: library code, allowed to do that\n const eventMap = {} as Record<keyof T, Set<(...args: any[]) => void>>\n\n return {\n emit: <K extends keyof T>(event: K, ...args: Parameters<T[K]>) => {\n if (!(event in eventMap)) {\n return\n }\n // Iterate a copy: a listener added while this event is dispatched (e.g. a macro\n // started from a key echo) must not receive the same event.\n for (const cb of [...eventMap[event]]) {\n cb(...args)\n }\n },\n\n on: <K extends keyof T>(event: K, callback: T[K]) => {\n if (!eventMap[event]) {\n eventMap[event] = new Set()\n }\n eventMap[event].add(callback)\n },\n\n off: <K extends keyof T>(event: K, callback: T[K]) => {\n if (!eventMap[event]) {\n return\n }\n eventMap[event].delete(callback)\n },\n }\n}\n","// Parses M8 display protocol packets (draw rect / character / wave, key echo, system info)\n// and dispatches them as typed events.\n\nimport { createEventBus } from './eventBus'\n\nconst littleEndian = true\nexport type RectCommand = ReturnType<typeof parseRectCommand>\nconst parseRectCommand = (data: DataView, length: number) => {\n const x = data.getUint16(1, littleEndian)\n const y = data.getUint16(3, littleEndian)\n switch (length) {\n // 1b: command | 2b: x | 2b: y\n case 5:\n return {\n type: 1,\n pos: { x, y },\n size: { width: 1, height: 1 },\n } as const\n // 1b: command | 2b: x | 2b: y | 1b: r | 1b: g | 1b: b\n case 8:\n return {\n type: 2,\n pos: { x, y },\n size: { width: 1, height: 1 },\n color: { r: data.getUint8(5), g: data.getUint8(6), b: data.getUint8(7) },\n } as const\n // 1b: command | 2b: x | 2b: y | 2b: width | 2b: height\n case 9:\n return {\n type: 3,\n pos: { x, y },\n size: { width: data.getUint16(5, littleEndian), height: data.getUint16(7, littleEndian) },\n } as const\n // 1b: command | 2b: x | 2b: y | 2b: width | 2b: height | 1b: r | 1b: g | 1b: b\n case 12:\n return {\n type: 4,\n pos: { x, y },\n size: { width: data.getUint16(5, littleEndian), height: data.getUint16(7, littleEndian) },\n color: { r: data.getUint8(9), g: data.getUint8(10), b: data.getUint8(11) },\n } as const\n }\n const bytes = []\n for (let i = 0; i < length; i += 1) {\n bytes.push(data.getUint8(i).toString(16).padStart(2, '0'.toUpperCase()))\n }\n console.error(`Invalid length (${length}) of rect command data: ${bytes.join(' ')}`)\n // throw new Error(`Invalid length (${length}) of rect command data`)\n // never undefined\n return {\n type: 0,\n pos: { x, y },\n size: { width: 0, height: 0 },\n } as const\n}\n\nexport type CharacterCommand = ReturnType<typeof parseCharacterCommand>\nconst parseCharacterCommand = (data: DataView, length: number) => {\n if (length !== 12) {\n throw new Error(`Invalid length (${length}) of char command data`)\n }\n const char = {\n character: String.fromCharCode(data.getUint8(1)),\n pos: { x: data.getUint16(2, littleEndian), y: data.getUint16(4, littleEndian) },\n foreground: { r: data.getUint8(6), g: data.getUint8(7), b: data.getUint8(8) },\n background: { r: data.getUint8(9), g: data.getUint8(10), b: data.getUint8(11) },\n }\n return char\n}\n\nexport type WaveCommand = ReturnType<typeof parseWaveCommand>\nconst parseWaveCommand = (data: DataView, length: number) => {\n if (length < 4 || length > 484) {\n throw new Error(`Invalid length (${length}) of wave command data`)\n }\n const wave: number[] = []\n for (let i = 4; i < length; i += 1) {\n wave.push(data.getUint8(i))\n }\n return {\n color: { r: data.getUint8(1), g: data.getUint8(2), b: data.getUint8(3) },\n wave,\n }\n}\n\nexport type KeyCommand = ReturnType<typeof parseKeyCommand>\nconst parseKeyCommand = (data: DataView, length: number) => {\n if (length !== 3) {\n throw new Error(`Invalid length (${length}) of key command data`)\n }\n return {\n keys: data.getUint8(1),\n }\n}\n\nexport type SystemCommand = ReturnType<typeof parseSystemCommand>\nconst device = ['Headless', 'Beta M8', 'M8 Model:01', 'M8 Model:02'] as const\nconst parseSystemCommand = (data: DataView, length: number) => {\n if (length !== 6) {\n throw new Error(`Invalid length (${length}) of system command data`)\n }\n\n return {\n model: device[data.getUint8(1)],\n version: `v${data.getUint8(2)}.${data.getUint8(3)}.${data.getUint8(4)}`,\n fontMode: data.getUint8(5) as 0 | 1 | 2,\n }\n}\n\n/** System command plus the layout metrics derived from the model and font mode. */\nexport type SystemInfos = {\n model: SystemCommand['model']\n fontMode: SystemCommand['fontMode']\n spacingX: number\n spacingY: number\n offX: number\n offY: number\n screenWidth: number\n screenHeight: number\n rectOffset: number\n}\n\nexport type ProtocolEvents = {\n rect: (data: RectCommand) => void\n text: (data: CharacterCommand) => void\n wave: (data: WaveCommand) => void\n key: (data: KeyCommand) => void\n system: (data: SystemCommand) => void\n systemInfo: (data: SystemInfos) => void\n any: (data: Uint8Array<ArrayBufferLike>) => void\n}\n\nexport const protocol = () => {\n let lastSystemInfo: SystemCommand | undefined\n const eventBus = createEventBus<ProtocolEvents>()\n\n // Cached enriched system infos (includes layout metrics)\n let cachedSystemInfos: SystemInfos | undefined\n\n const computeLayoutMetrics = (sys: SystemCommand | undefined) => {\n // Defaults assume Model:02 small font\n let spacingX = 12\n let spacingY = 14\n const offX = 0\n let offY = 0\n let screenWidth = 480\n let screenHeight = 320\n let rectOffset = 0\n\n if (!sys) return { spacingX, spacingY, offX, offY, screenWidth, screenHeight, rectOffset }\n\n const isV2 = sys.model === 'M8 Model:02' || sys.model === 'Beta M8'\n const fm = sys.fontMode // 0 small, 1 bold, 2 large/no-scope\n\n if (isV2) {\n screenWidth = 480\n screenHeight = 320\n if (fm === 0) {\n spacingX = 12\n spacingY = 14\n offY = 0\n rectOffset = 0\n } else if (fm === 1) {\n spacingX = 12\n spacingY = 14\n offY = 0\n rectOffset = 0\n } else {\n spacingX = 15\n spacingY = 16\n offY = 0\n rectOffset = -45\n }\n } else {\n // Model:01\n screenWidth = 320\n screenHeight = 240\n if (fm === 0) {\n spacingX = 8\n spacingY = 10\n offY = 0\n rectOffset = 0\n } else {\n spacingX = 10\n spacingY = 12\n offY = 0\n rectOffset = -40\n }\n }\n return { spacingX, spacingY, offX, offY, screenWidth, screenHeight, rectOffset }\n }\n\n const dispatch = (data: DataView, dataLength: number) => {\n try {\n if (dataLength <= 0) {\n return\n }\n\n const buffer = new Uint8Array(data.buffer, data.byteOffset, dataLength)\n eventBus.emit('any', buffer)\n\n switch (data.getUint8(0)) {\n case 0xfe: {\n // draw rect\n const command = parseRectCommand(data, dataLength)\n if (command) {\n eventBus.emit('rect', command)\n }\n break\n }\n case 0xfd: // draw character\n eventBus.emit('text', parseCharacterCommand(data, dataLength))\n break\n case 0xfc: // draw wave\n eventBus.emit('wave', parseWaveCommand(data, dataLength))\n break\n case 0xfb: // key events\n eventBus.emit('key', parseKeyCommand(data, dataLength))\n break\n case 0xff: {\n // system info\n lastSystemInfo = parseSystemCommand(data, dataLength)\n eventBus.emit('system', lastSystemInfo)\n const metrics = computeLayoutMetrics(lastSystemInfo)\n cachedSystemInfos = {\n model: lastSystemInfo.model,\n fontMode: lastSystemInfo.fontMode,\n ...metrics,\n }\n eventBus.emit('systemInfo', cachedSystemInfos)\n break\n }\n default:\n console.warn(`Unknown byte 0x${data.getUint8(0).toString(16).padStart(2, '0')} as command`)\n break\n }\n } catch (_e) {}\n }\n\n return {\n eventBus,\n dispatch,\n getSystemInfo: () => lastSystemInfo,\n getSystemInfos: () => {\n // Build from last system if not yet computed\n if (!cachedSystemInfos) {\n const metrics = computeLayoutMetrics(lastSystemInfo)\n if (lastSystemInfo) {\n cachedSystemInfos = {\n model: lastSystemInfo.model,\n fontMode: lastSystemInfo.fontMode,\n ...metrics,\n }\n } else {\n // Unknown yet; return defaults\n cachedSystemInfos = {\n model: 'Headless',\n fontMode: 0,\n ...metrics,\n }\n }\n }\n return cachedSystemInfos\n },\n }\n}\n","export type SlipState = 'read' | 'escape' | 'error'\n\nexport const createSlipParser = () => {\n const buffer = new Uint8Array(1024)\n const view = new DataView(buffer.buffer)\n let dataLength = 0\n let state: SlipState = 'read'\n\n const finalizePacket = (): DataView | undefined => {\n if (dataLength <= 0) {\n dataLength = 0\n return undefined\n }\n const out = new Uint8Array(dataLength)\n out.set(buffer.subarray(0, dataLength))\n const dv = new DataView(out.buffer)\n dataLength = 0\n return dv\n }\n\n const pushByte = (byte: number) => {\n if (dataLength >= buffer.length) {\n // overflow safeguard: drop packet and reset\n dataLength = 0\n state = 'error'\n return\n }\n view.setUint8(dataLength++, byte)\n }\n\n const feed = (byte: number): DataView | undefined => {\n switch (state) {\n case 'read':\n switch (byte) {\n case 0xc0:\n return finalizePacket()\n case 0xdb:\n state = 'escape'\n return undefined\n default:\n pushByte(byte)\n return undefined\n }\n case 'escape':\n switch (byte) {\n case 0xdc: // escaped 0xc0\n pushByte(0xc0)\n state = 'read'\n return undefined\n case 0xdd: // escaped 0xdb\n pushByte(0xdb)\n state = 'read'\n return undefined\n default:\n state = 'error'\n return undefined\n }\n case 'error':\n if (byte === 0xc0) {\n // reset on end marker\n state = 'read'\n dataLength = 0\n }\n return undefined\n }\n }\n\n return { feed }\n}\n\n// USB reader with SLIP framing -> queued DataViews\nexport const queuedUsbReader = (device: USBDevice) => {\n const parser = createSlipParser()\n const messages: DataView[] = []\n let terminated = false\n let resolver: ((value: { terminated: true } | { terminated: false; data: DataView }) => void) | undefined\n\n const pump = async () => {\n try {\n while (true) {\n const result = await device.transferIn(3, 512)\n if (result.status === 'stall') {\n terminated = true\n if (resolver && messages.length === 0) {\n resolver({ terminated: true })\n resolver = undefined\n }\n break\n }\n const data = result.data\n if (!data) continue\n for (let i = 0; i < data.byteLength; i++) {\n const maybe = parser.feed(data.getUint8(i))\n if (maybe) {\n messages.push(maybe)\n if (resolver) {\n const msg = messages.shift()\n if (msg) {\n resolver({ terminated: false, data: msg })\n resolver = undefined\n }\n }\n }\n }\n }\n } catch (_e) {\n terminated = true\n if (resolver && messages.length === 0) {\n resolver({ terminated: true })\n resolver = undefined\n }\n }\n }\n\n pump()\n\n return async () => {\n if (terminated && messages.length === 0) {\n return { terminated: true as const }\n }\n if (messages.length > 0) {\n const msg = messages.shift()\n if (msg) {\n return { terminated: false as const, data: msg }\n }\n }\n return new Promise<{ terminated: true } | { terminated: false; data: DataView }>((resolve) => {\n resolver = resolve\n })\n }\n}\n\n// Serial reader with SLIP framing -> queued DataViews\nexport const queuedSerialReader = (reader: ReadableStreamDefaultReader<Uint8Array<ArrayBufferLike>>) => {\n const parser = createSlipParser()\n const messages: DataView[] = []\n let terminated = false\n let resolver: ((value: { terminated: true } | { terminated: false; data: DataView }) => void) | undefined\n\n const pump = async () => {\n try {\n while (true) {\n const { value, done } = await reader.read()\n if (done) {\n terminated = true\n if (resolver && messages.length === 0) {\n resolver({ terminated: true })\n resolver = undefined\n }\n break\n }\n if (!value) continue\n const chunk = value as unknown as Uint8Array\n for (let i = 0; i < chunk.byteLength; i++) {\n const maybe = parser.feed(chunk[i])\n if (maybe) {\n messages.push(maybe)\n if (resolver) {\n const msg = messages.shift()\n if (msg) {\n resolver({ terminated: false, data: msg })\n resolver = undefined\n }\n }\n }\n }\n }\n } catch (_e) {\n terminated = true\n if (resolver && messages.length === 0) {\n resolver({ terminated: true })\n resolver = undefined\n }\n }\n }\n\n pump()\n\n return async () => {\n if (terminated && messages.length === 0) {\n return { terminated: true as const }\n }\n if (messages.length > 0) {\n const msg = messages.shift()\n if (msg) {\n return { terminated: false as const, data: msg }\n }\n }\n return new Promise<{ terminated: true } | { terminated: false; data: DataView }>((resolve) => {\n resolver = resolve\n })\n }\n}\n","// Connects to the M8 over Web Serial, WebUSB or Web MIDI (SysEx), reads its display\n// stream into the protocol parser and sends key / note / program change commands.\n\nimport { protocol } from './protocol'\nimport { queuedSerialReader, queuedUsbReader } from './queuedReaders'\n\nexport type M8DeviceTransport = 'serial' | 'usb' | 'midi'\n\nexport interface M8ConnectOptions {\n /** Which browser API to use. 'auto' (default) picks Web Serial, then WebUSB, then Web MIDI, by availability. */\n transport?: M8DeviceTransport | 'auto'\n /**\n * false: only reuse a device the page was already granted, never open the browser's device\n * picker (which needs a user gesture). Rejects when there is none. Default true.\n */\n prompt?: boolean\n}\n\nconst vendorId = 0x16c0\nconst productId = 0x048a\n// the device processes frame by frame ingress messages - which means each button press needs to wait for ~5ms currently\n// doubling the time is a good safe margin\nconst waitTime = 20\n\nconst usbWriter = (device: USBDevice) => (data: Uint8Array<ArrayBuffer>) => device.transferOut(3, data)\nconst serialWriter = (writer: WritableStreamDefaultWriter<Uint8Array<ArrayBufferLike>>) => (data: Uint8Array<ArrayBuffer>) => writer.write(data)\n\nconst midiCommands = (output: MIDIOutput) => {\n const commands: Uint8Array<ArrayBuffer>[] = []\n const frameCheck = () => {\n const command = commands.shift()\n if (command) {\n output.send(command)\n }\n }\n\n const interval = setInterval(frameCheck, waitTime)\n\n return {\n stop: () => clearInterval(interval),\n resetScreen: () => {\n commands.push(new Uint8Array([0xf0, 0x00, 0x02, 0x61, 0x00, 0x00, 0x44, 0xf7]))\n commands.push(new Uint8Array([0xf0, 0x00, 0x02, 0x61, 0x00, 0x00, 0x45, 0x52, 0xf7]))\n },\n sendKeys: (keys: number) => {\n const press = keys\n const msb = 0 | (press & 0x80 ? 1 << 2 : 0)\n\n commands.push(new Uint8Array([0xf0, 0x00, 0x02, 0x61, 0x00, msb, 0x00, 0x43, press & 0x7f, 0xf7]))\n },\n sendNoteOn: (note: number, velocity: number) => {\n velocity = Math.min(0x7f, Math.max(0x00, velocity))\n const msb = 0 | (note & 0x80 ? 1 << 2 : 0) | (velocity & 0x80 ? 1 << 3 : 0)\n commands.push(new Uint8Array([0xf0, 0x00, 0x02, 0x61, 0x00, msb, 0x00, 0x48, note & 0x7f, velocity & 0x7f, 0xf7]))\n },\n sendNoteOff: () => {\n const msb = 0 | (1 << 2)\n commands.push(new Uint8Array([0xf0, 0x00, 0x02, 0x61, 0x00, msb, 0x48, 0x7f, 0xf7]))\n },\n sendProgramChange: (program: number, channel: number = 0) => {\n // MIDI Program Change: 0xC0-0xCF (C = 12 in decimal, shifted left 4 bits)\n // Program change message: status byte = 0xC0 + channel, data byte = program number (0-127)\n const safeChannel = Math.min(15, Math.max(0, channel))\n const safeProgram = Math.min(127, Math.max(0, program))\n const statusByte = 0xc0 | safeChannel\n commands.push(new Uint8Array([statusByte, safeProgram]))\n },\n }\n}\n\nconst commands = (writer: ReturnType<typeof usbWriter> | ReturnType<typeof serialWriter>) => {\n const commands: Uint8Array<ArrayBuffer>[] = []\n\n const frameCheck = () => {\n const command = commands.shift()\n if (command) {\n void Promise.resolve(writer(command)).catch(() => {\n // The device went away; the reader reports the termination.\n })\n }\n }\n\n const interval = setInterval(frameCheck, waitTime)\n\n return {\n stop: () => clearInterval(interval),\n resetScreen: async () => {\n commands.push(new Uint8Array([0x44]))\n commands.push(new Uint8Array([0x45, 0x52]))\n },\n sendKeys: (keys: number) => {\n const press = keys\n commands.push(new Uint8Array([0x43, press]))\n },\n sendNoteOn: async (note: number, velocity: number) => {\n commands.push(new Uint8Array([0x4b, note, velocity]))\n },\n sendNoteOff: async () => {\n commands.push(new Uint8Array([0x4b, 255]))\n },\n sendProgramChange: async (program: number, _channel: number = 0) => {\n // USB/Serial: Use 0x4c prefix for program change (similar to 0x4b for note on/off)\n const safeProgram = Math.min(127, Math.max(0, program))\n commands.push(new Uint8Array([0x4c, safeProgram]))\n },\n }\n}\n\nconst M8_SYSEX_HEADER = [0xf0, 0x00, 0x02, 0x61, 0x00]\nconst SYSEX_END = 0xf7\n\nfunction isM8Sysex(data: Uint8Array<ArrayBuffer>) {\n if (data.length < 5) return false\n for (let i = 0; i < M8_SYSEX_HEADER.length; i++) {\n if (data[i] !== M8_SYSEX_HEADER[i]) return false\n }\n return true\n}\n\nexport function decode7BitPacked(data: Uint8Array): Uint8Array {\n const out: number[] = []\n\n let i = 0\n while (i < data.length) {\n const msbByte = data[i++]\n if (i >= data.length) break // truncated group, bail\n\n const remaining = data.length - i\n const groupLen = Math.min(7, remaining)\n\n for (let bit = 0; bit < groupLen; bit++) {\n const low7 = data[i++] // 0x00–0x7F\n const msb = (msbByte >> bit) & 0x01\n out.push((msb << 7) | low7) // reattach MSB -> full 8-bit\n }\n }\n\n return new Uint8Array(out)\n}\n\nfunction decodeM8Sysex(encoded: Uint8Array<ArrayBuffer>): Uint8Array | undefined {\n if (!isM8Sysex(encoded)) {\n console.log('not M8Sysex')\n return undefined\n }\n\n if (encoded[encoded.length - 1] !== SYSEX_END) {\n console.log('Unterminated SysEx')\n return undefined\n }\n const locs = [...encoded].map((x, i) => (x === SYSEX_END ? `${i}` : undefined)).filter((x) => !!x)\n if (locs.length > 1) {\n console.warn(encoded)\n }\n const packed = encoded.slice(M8_SYSEX_HEADER.length, encoded.length - 1)\n return decode7BitPacked(packed)\n}\n\nconst midiReader = (input: MIDIInput) => {\n const messages: DataView[] = []\n\n // Pending buffer of raw MIDI bytes across events to safely reassemble SysEx\n let pending = new Uint8Array(0) as unknown as Uint8Array<ArrayBufferLike>\n\n let resolver:\n | ((\n value:\n | {\n terminated: true\n }\n | {\n terminated: false\n data: DataView\n },\n ) => void)\n | undefined\n\n // Find the first occurrence of the M8 SysEx header in a buffer\n const findHeaderIndex = (buf: Uint8Array<ArrayBufferLike>, start = 0): number => {\n const header = M8_SYSEX_HEADER\n for (let i = start; i <= buf.length - header.length; i++) {\n let match = true\n for (let j = 0; j < header.length; j++) {\n if (buf[i + j] !== header[j]) {\n match = false\n break\n }\n }\n if (match) return i\n }\n return -1\n }\n\n const appendBytes = (a: Uint8Array<ArrayBufferLike>, b: Uint8Array<ArrayBufferLike>): Uint8Array<ArrayBufferLike> => {\n const merged = new Uint8Array(a.length + b.length) as unknown as Uint8Array<ArrayBufferLike>\n merged.set(a, 0)\n merged.set(b, a.length)\n return merged\n }\n\n const processChunk = (chunk: Uint8Array<ArrayBufferLike>) => {\n // Accumulate incoming bytes\n pending = appendBytes(pending, chunk)\n\n while (true) {\n // Align to header if present; if not, keep a short tail to catch split headers\n let hdrIdx = findHeaderIndex(pending, 0)\n if (hdrIdx === -1) {\n // Keep only the last header.length - 1 bytes in case the header is split across chunks\n if (pending.length > M8_SYSEX_HEADER.length - 1) {\n pending = pending.slice(pending.length - (M8_SYSEX_HEADER.length - 1))\n }\n break\n }\n\n // Drop any leading noise before header\n if (hdrIdx > 0) {\n pending = pending.slice(hdrIdx)\n hdrIdx = 0\n }\n\n // Look for SysEx end after header\n const endIdx = pending.indexOf(SYSEX_END, M8_SYSEX_HEADER.length)\n if (endIdx === -1) {\n // Need more bytes\n break\n }\n\n // Extract a single, complete M8 SysEx message [header ... 0xF7]\n const oneMessage = pending.slice(0, endIdx + 1)\n const decoded = decodeM8Sysex(oneMessage)\n if (decoded) {\n messages.push(new DataView(decoded.buffer, decoded.byteOffset, decoded.byteLength))\n }\n\n // Remove processed message and continue scanning in case of concatenated messages\n pending = pending.slice(endIdx + 1)\n }\n }\n\n const handler = (evt: MIDIMessageEvent) => {\n if (!evt.data || evt.data.length === 0) return\n // Process all bytes; this handles split and concatenated SysEx packets\n processChunk(evt.data as unknown as Uint8Array<ArrayBufferLike>)\n\n if (resolver && messages.length > 0) {\n const message = messages.shift()\n if (message) {\n resolver({\n terminated: false,\n data: message,\n })\n resolver = undefined\n }\n }\n }\n\n input.addEventListener('midimessage', handler)\n\n let stopped = false\n const stop = () => {\n if (stopped) return\n stopped = true\n input.removeEventListener('midimessage', handler)\n input.removeEventListener('statechange', onStateChange)\n if (resolver) {\n resolver({ terminated: true })\n resolver = undefined\n }\n }\n const onStateChange = () => {\n if (input.state === 'disconnected') stop()\n }\n input.addEventListener('statechange', onStateChange)\n\n const read = async () => {\n if (stopped || input.state === 'disconnected') {\n return { terminated: true }\n }\n\n if (messages.length > 0) {\n return {\n terminated: false,\n data: messages.shift(),\n }\n }\n return new Promise<{ terminated: true } | { terminated: false; data: DataView }>((resolve) => {\n resolver = resolve\n })\n }\n\n return { read, stop }\n}\n\ntype ReadFn = () => Promise<{ terminated: boolean; data?: DataView }>\n\n/** Pumps packets into the protocol handler; resolves once the reader terminates. */\nconst beginRead = async (read: ReadFn, handler: ReturnType<typeof protocol>) => {\n while (true) {\n const { terminated, data } = await read()\n if (data) {\n handler.dispatch(data, data.byteLength)\n }\n if (terminated) {\n break\n }\n }\n}\n\nconst transportLabel: Record<M8DeviceTransport, string> = { serial: 'Web Serial', usb: 'WebUSB', midi: 'Web MIDI' }\n\nexport const connection = () => {\n const webMidiAvailable = 'requestMIDIAccess' in navigator\n const webSerialAvailable = 'serial' in navigator\n const webUsbAvailable = 'usb' in navigator\n if (!webMidiAvailable && !webSerialAvailable && !webUsbAvailable) {\n return {\n browserSupport: false,\n } as const\n }\n\n const usbConnect = async (prompt: boolean) => {\n const devices = (await navigator.usb.getDevices()).filter((d) => d /* d.vendorId === vendorId && d.productId === productId */)\n // TODO: check out what happens with two devices attached\n if (devices.length > 1) {\n console.warn('Suspicious: there is more than one device, are multiple devices connected?')\n }\n if (devices.length <= 0 && !prompt) {\n throw new Error('No M8 USB device was granted to this page yet')\n }\n const device =\n devices.length <= 0\n ? await navigator.usb.requestDevice({\n filters: [{ vendorId: vendorId, productId: productId }],\n })\n : (devices.find((d) => d.vendorId === vendorId) ?? devices[0])\n\n if (!device) {\n throw new Error('No devices found')\n }\n await device.open()\n await device.selectConfiguration(1)\n await device.claimInterface(1)\n await device.controlTransferOut({\n requestType: 'class',\n recipient: 'interface',\n request: 0x22,\n value: 0x03,\n index: 0x01,\n })\n await device.controlTransferOut(\n {\n requestType: 'class',\n recipient: 'interface',\n request: 0x20,\n value: 0x00,\n index: 0x01,\n },\n //new Uint8Array([0x80, 0x25, 0x00, 0x00, 0x00, 0x00, 0x08]),\n )\n\n const deviceCommands = commands(usbWriter(device))\n return {\n transport: 'usb' as M8DeviceTransport,\n commands: deviceCommands,\n reader: queuedUsbReader(device),\n close: async () => {\n deviceCommands.stop()\n await device.close()\n },\n }\n }\n\n const serialConnect = async (prompt: boolean) => {\n const ports = (await navigator.serial.getPorts()).filter(() => {\n // const info = x.getInfo()\n return true // info.usbVendorId === vendorId // && info.usbProductId === productId\n })\n if (ports.length > 1) {\n console.warn('Suspicious: there are more than one port, when expecting just one.')\n }\n if (ports.length <= 0 && !prompt) {\n throw new Error('No M8 serial port was granted to this page yet')\n }\n\n const port =\n ports.length <= 0\n ? await navigator.serial.requestPort({ filters: [{ usbVendorId: vendorId /*, usbProductId: productId */ }] })\n : (ports.find((p) => p.getInfo().usbVendorId === vendorId) ?? ports[0])\n\n await port.open({\n baudRate: 115200,\n dataBits: 8,\n stopBits: 1,\n parity: 'none',\n bufferSize: 4096,\n })\n\n if (!port.readable || !port.writable) {\n throw new Error(\"Ports should've been read/writeable, but apparently they're not\")\n }\n\n const writer = port.writable.getWriter()\n const rawReader = port.readable.getReader()\n const portCommands = commands(serialWriter(writer))\n return {\n transport: 'serial' as M8DeviceTransport,\n commands: portCommands,\n reader: queuedSerialReader(rawReader),\n close: async () => {\n portCommands.stop()\n await rawReader.cancel().catch(() => undefined)\n rawReader.releaseLock()\n writer.releaseLock()\n await port.close()\n },\n }\n }\n\n const midiConnect = async (prompt: boolean) => {\n if (!prompt) {\n const permission = await navigator.permissions?.query({ name: 'midi', sysex: true } as unknown as PermissionDescriptor).catch(() => null)\n if (permission?.state !== 'granted') {\n throw new Error('Web MIDI SysEx access was not granted to this page yet')\n }\n }\n const access = await navigator.requestMIDIAccess({ sysex: true })\n\n // Disconnect any existing M8 connections first\n const disconnectM8Devices = (devices: MIDIPort[]) => {\n devices.forEach((device) => {\n // Check if device is M8 and open, then close it\n if (device.name === 'M8' && device.state === 'connected') {\n device.close()\n console.log(`Disconnected existing M8: ${device.name} (${device.id})`)\n }\n })\n }\n\n // Disconnect existing M8 inputs and outputs\n disconnectM8Devices([...access.inputs.values()])\n disconnectM8Devices([...access.outputs.values()])\n\n // Find M8 devices with better error handling\n const inputs = [...access.inputs.values()].filter((input) => input.name === 'M8')\n const outputs = [...access.outputs.values()].filter((output) => output.name === 'M8')\n\n if (inputs.length === 0 || outputs.length === 0) {\n throw new Error('M8 device not found. Please ensure your M8 is connected and powered on.')\n }\n\n // Handle multiple devices with clearer messaging\n if (inputs.length > 1) {\n console.warn(`Multiple M8 inputs detected. Using first device: ${inputs[0].name}`)\n }\n if (outputs.length > 1) {\n console.warn(`Multiple M8 outputs detected. Using first device: ${outputs[0].name}`)\n }\n\n const input = inputs[0]\n const output = outputs[0]\n\n // Check if devices are already open\n if (input.connection === 'open') {\n console.log('M8 input is already open')\n } else {\n await input.open()\n }\n\n if (output.connection === 'open') {\n console.log('M8 output is already open')\n } else {\n await output.open()\n }\n\n // Optional: Log connection info\n console.log(`Connected to M8 - Input: ${input.id}, Output: ${output.id}`)\n\n const outputCommands = midiCommands(output)\n const reader = midiReader(input)\n return {\n transport: 'midi' as M8DeviceTransport,\n commands: outputCommands,\n reader: reader.read,\n close: async () => {\n outputCommands.stop()\n reader.stop()\n await input.close()\n await output.close()\n },\n }\n }\n\n const available: Record<M8DeviceTransport, boolean> = { serial: webSerialAvailable, usb: webUsbAvailable, midi: webMidiAvailable }\n\n const connect = async ({ transport = 'auto', prompt = true }: M8ConnectOptions = {}) => {\n const chosen: M8DeviceTransport = transport !== 'auto' ? transport : webSerialAvailable ? 'serial' : webUsbAvailable ? 'usb' : 'midi'\n if (!available[chosen]) {\n throw new Error(`This browser does not support ${transportLabel[chosen]}`)\n }\n const connection = await (chosen === 'serial' ? serialConnect(prompt) : chosen === 'usb' ? usbConnect(prompt) : midiConnect(prompt))\n // resetScreen is intentionally NOT called here — it must be called by the\n // screen component AFTER it has registered its event listeners, so the\n // M8's initial screen dump is not lost before anyone is listening.\n const protocolHandler = protocol()\n let closing = false\n const closed = beginRead(connection.reader as ReadFn, protocolHandler)\n return {\n transport: connection.transport,\n commands: connection.commands,\n reader: connection.reader,\n protocol: protocolHandler,\n /** Resolves when the device stops sending (unplugged, or close() was called). */\n closed,\n /** Releases the device so another page (or yam8d) can open it. */\n close: async () => {\n if (closing) return closed\n closing = true\n await connection.close().catch((error: unknown) => console.warn('[M8] close failed:', error))\n await closed\n },\n }\n }\n\n return {\n browserSupport: true,\n available,\n connect,\n } as const\n}\n\nexport type ConnectedBus = Awaited<ReturnType<Exclude<ReturnType<typeof connection>['connect'], undefined>>>\n","/**\n * Runs `connect` until it succeeds. The M8 Headless is slower to enumerate and may not be\n * immediately ready when the user clicks Connect, so failures are retried after a delay.\n * Resolves to undefined once all attempts have failed.\n */\nexport const connectWithRetry = async <T>(\n connect: () => Promise<T>,\n { maxAttempts = 3, retryDelayMs = 1500 }: { maxAttempts?: number; retryDelayMs?: number } = {},\n): Promise<T | undefined> => {\n for (let attempt = 1; attempt <= maxAttempts; attempt++) {\n try {\n return await connect()\n } catch (err) {\n console.warn(`Connection attempt ${attempt}/${maxAttempts} failed:`, err)\n if (attempt < maxAttempts) {\n await new Promise<void>((resolve) => setTimeout(resolve, retryDelayMs))\n } else {\n console.error('Could not connect to M8 after all attempts:', err)\n }\n }\n }\n return undefined\n}\n","// The M8's USB audio: it shows up as an audio input (\"M8\" in its label). Playing it through\n// the page lets you hear the M8 on the computer; the same graph gives a capture stream for\n// recording.\n\nexport const M8_AUDIO_CAPTURE_CONSTRAINTS = {\n autoGainControl: false,\n channelCount: { ideal: 2 },\n echoCancellation: false,\n noiseSuppression: false,\n} satisfies MediaTrackConstraints\n\nconst findM8Input = async (): Promise<MediaDeviceInfo | null> => {\n const devices = (await navigator.mediaDevices.enumerateDevices()).filter(\n (x) => x.kind === 'audioinput' && x.label.includes('M8') && x.deviceId !== 'default' && x.deviceId !== 'communications',\n )\n if (devices.length > 1) {\n console.warn('Suspicious: More than one M8 audio input found')\n }\n return devices[0] ?? null\n}\n\n/** The M8's audio input, or null (device labels are only visible once audio permission was given). */\nexport const getM8AudioInputDevice = async (): Promise<MediaDeviceInfo | null> => {\n if (!navigator.mediaDevices?.enumerateDevices) return null\n return findM8Input()\n}\n\n/**\n * A stream from the M8's audio input. Without a labelled M8 input yet, it asks for audio\n * permission first and looks again. Still none: `requireM8` rejects, otherwise the default\n * input is returned (yam8d's recorder relies on that fallback).\n */\nexport const getM8InputStream = async ({ requireM8 = false }: { requireM8?: boolean } = {}): Promise<MediaStream> => {\n if (!navigator.mediaDevices?.getUserMedia) throw new Error('Audio capture is not supported in this browser')\n\n let device = await getM8AudioInputDevice()\n if (!device) {\n // Labels are hidden until the page has audio permission: get it, then look again\n const probe = await navigator.mediaDevices.getUserMedia({ audio: M8_AUDIO_CAPTURE_CONSTRAINTS })\n device = await getM8AudioInputDevice()\n if (!device) {\n if (!requireM8) return probe\n for (const track of probe.getTracks()) track.stop()\n throw new Error('No M8 audio input found (is the M8 connected over USB?)')\n }\n for (const track of probe.getTracks()) track.stop()\n }\n\n return navigator.mediaDevices.getUserMedia({\n audio: {\n ...M8_AUDIO_CAPTURE_CONSTRAINTS,\n deviceId: { exact: device.deviceId },\n },\n })\n}\n\nexport interface M8Audio {\n /** Starts playing the M8's audio input through the page's speakers. Idempotent. */\n connect(options?: { requireM8?: boolean }): Promise<void>\n disconnect(): Promise<void>\n readonly connected: boolean\n /** A copy of what is being played, for recording; null while not connected. */\n getOutputCaptureStream(): MediaStream | null\n}\n\nexport const createM8Audio = (): M8Audio => {\n let ctx: AudioContext | null = null\n let input: MediaStream | null = null\n let output: MediaStream | null = null\n let connecting: Promise<void> | null = null\n\n // A context created after an await may start suspended: the next click or key resumes it\n const resumeOnGesture = (context: AudioContext) => {\n if (context.state !== 'suspended' || typeof window === 'undefined') return\n const resume = () => {\n void context.resume()\n window.removeEventListener('pointerdown', resume)\n window.removeEventListener('keydown', resume)\n }\n window.addEventListener('pointerdown', resume)\n window.addEventListener('keydown', resume)\n }\n\n const disconnect = async () => {\n for (const track of input?.getTracks() ?? []) track.stop()\n const context = ctx\n ctx = null\n input = null\n output = null\n await context?.close().catch(() => undefined)\n }\n\n const open = async (requireM8: boolean) => {\n const stream = await getM8InputStream({ requireM8 })\n const context = new AudioContext()\n const source = context.createMediaStreamSource(stream)\n const destination = context.createMediaStreamDestination()\n source.connect(context.destination)\n source.connect(destination)\n ctx = context\n input = stream\n output = destination.stream\n\n for (const track of stream.getAudioTracks()) {\n // Unplugged: release the graph\n track.addEventListener('ended', () => {\n if (input === stream) void disconnect()\n })\n }\n await context.resume().catch(() => undefined)\n resumeOnGesture(context)\n }\n\n return {\n connect: async ({ requireM8 = false } = {}) => {\n if (ctx) return\n connecting ??= open(requireM8).finally(() => {\n connecting = null\n })\n return connecting\n },\n disconnect,\n get connected() {\n return ctx !== null\n },\n getOutputCaptureStream: () => {\n const tracks = output?.getAudioTracks() ?? []\n if (tracks.length <= 0) return null\n return new MediaStream(tracks.map((track) => track.clone()))\n },\n }\n}\n","// What the cursor navigator has learned about each screen layout: the cursor cells seen there,\r\n// where each arrow key went from each cell (or that it did not move), and the screen texts the\r\n// cursor could not reach (labels; once they failed in two navigations). Cells are the M8's own cursor rectangles in M8 pixels, so the\r\n// map fits any model and font mode. Layouts are keyed by the caller (view, model, font mode and\r\n// the labels on screen); the oldest are dropped past MAX_LAYOUTS.\r\n\r\nexport type CursorDir = 'up' | 'down' | 'left' | 'right'\r\nexport type CellRect = { x: number; y: number; w: number; h: number }\r\n\r\n/** The cells and moves of one layout. A move to `null` means the key did not move the cursor. */\r\nexport interface CursorLayout {\r\n readonly cells: Map<string, CellRect>\r\n readonly moves: Map<string, Partial<Record<CursorDir, string | null>>>\r\n readonly unreachable: Set<string>\r\n /** Texts that failed once: one more failure makes them unreachable. */\r\n readonly suspects: Set<string>\r\n}\r\n\r\nexport type CursorMapData = {\r\n version: 1\r\n layouts: { key: string; cells: string[]; moves: [string, CursorDir, string | null][]; unreachable: string[]; suspects?: string[] }[]\r\n}\r\n\r\nexport interface CursorMap {\r\n /** The layout, or undefined when nothing was learned for it. */\r\n get(key: string): CursorLayout | undefined\r\n recordCell(key: string, rect: CellRect): void\r\n /** `to` null: the key was pressed and the cursor did not move. */\r\n recordMove(key: string, from: CellRect, dir: CursorDir, to: CellRect | null): void\r\n /** A navigation could not reach this text: the second time, it is unreachable. */\r\n markUnreachable(key: string, textKey: string): void\r\n toJSON(): CursorMapData\r\n}\r\n\r\nexport const DIRS: readonly CursorDir[] = ['up', 'down', 'left', 'right']\r\nconst MAX_LAYOUTS = 64\r\n\r\nexport const cellId = (rect: CellRect) => `${rect.x},${rect.y},${rect.w},${rect.h}`\r\n\r\nexport const parseCellId = (id: string): CellRect | null => {\r\n const [x, y, w, h] = id.split(',').map(Number)\r\n if (![x, y, w, h].every(Number.isFinite)) return null\r\n return { x, y, w, h }\r\n}\r\n\r\nconst emptyLayout = (): CursorLayout => ({ cells: new Map(), moves: new Map(), unreachable: new Set(), suspects: new Set() })\r\n\r\nexport const createCursorMap = (options: { data?: CursorMapData | null; onChange?: () => void } = {}): CursorMap => {\r\n // Insertion order is the use order: a used layout is moved to the end\r\n const layouts = new Map<string, CursorLayout>()\r\n const changed = () => options.onChange?.()\r\n\r\n const touch = (key: string): CursorLayout => {\r\n let layout = layouts.get(key)\r\n if (layout) layouts.delete(key)\r\n else layout = emptyLayout()\r\n layouts.set(key, layout)\r\n while (layouts.size > MAX_LAYOUTS) {\r\n const oldest = layouts.keys().next().value\r\n if (oldest === undefined) break\r\n layouts.delete(oldest)\r\n }\r\n return layout\r\n }\r\n\r\n const addCell = (layout: CursorLayout, rect: CellRect) => {\r\n const id = cellId(rect)\r\n if (layout.cells.has(id)) return false\r\n layout.cells.set(id, { x: rect.x, y: rect.y, w: rect.w, h: rect.h })\r\n return true\r\n }\r\n\r\n if (options.data?.version === 1) {\r\n for (const saved of options.data.layouts ?? []) {\r\n const layout = touch(saved.key)\r\n for (const id of saved.cells ?? []) {\r\n const rect = parseCellId(id)\r\n if (rect) addCell(layout, rect)\r\n }\r\n for (const [from, dir, to] of saved.moves ?? []) {\r\n if (!DIRS.includes(dir)) continue\r\n const moves = layout.moves.get(from) ?? {}\r\n moves[dir] = to\r\n layout.moves.set(from, moves)\r\n }\r\n for (const text of saved.unreachable ?? []) layout.unreachable.add(text)\r\n for (const text of saved.suspects ?? []) layout.suspects.add(text)\r\n }\r\n }\r\n\r\n return {\r\n get: (key) => layouts.get(key),\r\n recordCell: (key, rect) => {\r\n if (addCell(touch(key), rect)) changed()\r\n },\r\n recordMove: (key, from, dir, to) => {\r\n const layout = touch(key)\r\n addCell(layout, from)\r\n if (to) addCell(layout, to)\r\n const fromId = cellId(from)\r\n const toId = to ? cellId(to) : null\r\n const moves = layout.moves.get(fromId) ?? {}\r\n if (moves[dir] === toId) return\r\n moves[dir] = toId\r\n layout.moves.set(fromId, moves)\r\n changed()\r\n },\r\n markUnreachable: (key, textKey) => {\r\n const layout = touch(key)\r\n if (layout.unreachable.has(textKey)) return\r\n if (layout.suspects.delete(textKey)) layout.unreachable.add(textKey)\r\n else layout.suspects.add(textKey)\r\n changed()\r\n },\r\n toJSON: () => ({\r\n version: 1,\r\n layouts: [...layouts.entries()].map(([key, layout]) => ({\r\n key,\r\n cells: [...layout.cells.keys()],\r\n moves: [...layout.moves.entries()].flatMap(([from, moves]) =>\r\n DIRS.filter((dir) => moves[dir] !== undefined).map((dir): [string, CursorDir, string | null] => [from, dir, moves[dir] ?? null]),\r\n ),\r\n unreachable: [...layout.unreachable],\r\n suspects: [...layout.suspects],\r\n })),\r\n }),\r\n }\r\n}\r\n\r\n/** Whether a move landed on the wrong side (Up landing lower...): a wrap, not a move on this screen. */\r\nexport const wentBackwards = (from: CellRect, dir: CursorDir, to: CellRect): boolean => {\r\n if (dir === 'up') return to.y > from.y\r\n if (dir === 'down') return to.y < from.y\r\n if (dir === 'left') return to.x > from.x\r\n return to.x < from.x\r\n}\r\n\r\n// A move not learned yet costs a hair more than a learned one: learned moves only break ties (guesses are\r\n// right on the M8 grids, and a wrong one is learned and replanned)\r\nconst GUESS_COST = 1.01\r\n\r\nconst overlap = (a0: number, a1: number, b0: number, b1: number) => Math.min(a1, b1) - Math.max(a0, b0)\r\n\r\n/**\r\n * Where a key not learned yet probably goes: Up/Down to the nearest cell above/below that shares\r\n * columns with this one, Left/Right to the nearest cell of the same row. Null when there is none.\r\n */\r\nexport const guessMove = (layout: CursorLayout, from: CellRect, dir: CursorDir): string | null => {\r\n const cx = from.x + from.w / 2\r\n const cy = from.y + from.h / 2\r\n let best: { id: string; along: number; across: number } | null = null\r\n for (const [id, cell] of layout.cells) {\r\n const ox = cell.x + cell.w / 2 - cx\r\n const oy = cell.y + cell.h / 2 - cy\r\n let along: number\r\n let across: number\r\n if (dir === 'up' || dir === 'down') {\r\n along = dir === 'down' ? oy : -oy\r\n across = Math.abs(ox)\r\n if (overlap(from.x, from.x + from.w, cell.x, cell.x + cell.w) <= 0) continue\r\n } else {\r\n along = dir === 'right' ? ox : -ox\r\n across = Math.abs(oy)\r\n if (overlap(from.y, from.y + from.h, cell.y, cell.y + cell.h) < Math.min(from.h, cell.h) / 2) continue\r\n }\r\n if (along < Math.min(from.h, cell.h) / 2) continue\r\n if (!best || along < best.along - 0.5 || (Math.abs(along - best.along) <= 0.5 && across < best.across)) best = { id, along, across }\r\n }\r\n return best?.id ?? null\r\n}\r\n\r\n/**\r\n * Cheapest key sequence from `fromId` to a cell that `isGoal` accepts, over the learned moves and,\r\n * for keys not learned yet from a cell, the move `guessMove` expects (marked `guessed`). Moves\r\n * listed in `skip` (`\"<cellId>:<dir>\"`) or refused by `forbid` are not used, learned or guessed,\r\n * nor are learned moves that went backwards (a phrase wrapping to the next one). Null when no path exists.\r\n */\r\nexport const findPath = (\r\n layout: CursorLayout,\r\n fromId: string,\r\n isGoal: (id: string) => boolean,\r\n skip: ReadonlySet<string> = new Set(),\r\n forbid: (rect: CellRect, dir: CursorDir) => boolean = () => false,\r\n): { dir: CursorDir; to: string; guessed: boolean }[] | null => {\r\n if (isGoal(fromId)) return []\r\n const cost = new Map<string, number>([[fromId, 0]])\r\n const previous = new Map<string, { from: string; dir: CursorDir; guessed: boolean }>()\r\n const done = new Set<string>()\r\n const open = new Set([fromId])\r\n while (open.size > 0) {\r\n let id = ''\r\n let lowest = Number.POSITIVE_INFINITY\r\n for (const candidate of open) {\r\n const c = cost.get(candidate) ?? Number.POSITIVE_INFINITY\r\n if (c < lowest) {\r\n lowest = c\r\n id = candidate\r\n }\r\n }\r\n open.delete(id)\r\n if (done.has(id)) continue\r\n done.add(id)\r\n if (isGoal(id)) {\r\n const path: { dir: CursorDir; to: string; guessed: boolean }[] = []\r\n for (let at = id; at !== fromId; ) {\r\n const step = previous.get(at) as { from: string; dir: CursorDir; guessed: boolean }\r\n path.unshift({ dir: step.dir, to: at, guessed: step.guessed })\r\n at = step.from\r\n }\r\n return path\r\n }\r\n const rect = layout.cells.get(id)\r\n const moves = layout.moves.get(id) ?? {}\r\n for (const dir of DIRS) {\r\n if (skip.has(`${id}:${dir}`) || (rect && forbid(rect, dir))) continue\r\n const learned = moves[dir]\r\n if (learned === null) continue\r\n const guessed = learned === undefined\r\n const to = guessed ? (rect ? guessMove(layout, rect, dir) : null) : learned\r\n if (!to || done.has(to)) continue\r\n const toRect = layout.cells.get(to)\r\n if (!guessed && rect && toRect && wentBackwards(rect, dir, toRect)) continue\r\n const next = lowest + (guessed ? GUESS_COST : 1)\r\n if (next < (cost.get(to) ?? Number.POSITIVE_INFINITY)) {\r\n cost.set(to, next)\r\n previous.set(to, { from: id, dir, guessed })\r\n open.add(to)\r\n }\r\n }\r\n }\r\n return null\r\n}\r\n","{\r\n \"version\": \"1.0\",\r\n \"description\": \"Semantic mapping of M8 tracker views: defines the structure of each grid view so cursor position + line text can be interpreted as typed field values.\",\r\n \"notes\": {\r\n \"fontMode\": \"All x/width values assume font mode 0 (standard, 12×14 px cells, 40 cols × ~22 rows). Font modes 1 (bold uppercase) and 2 (large) may shift column positions.\",\r\n \"playbackIndicator\": \"The M8 renders a '>' character one column to the left of the first data column when that step/row/track is currently playing. This character is part of the text stream; the editing cursor is a separate rect overlay.\",\r\n \"screenGeometry\": \"The screen is 40 columns x 24 rows. Every row has a one-column left margin (the playback marker is drawn in it), so x positions in this schema count from column 1 of the raw screen and cursorPos.x is the schema x + 1. Titles are on row 3, content starts on row 5. The text stream is lower case ('tempo', 'b-4', 'ff').\",\r\n \"emptyCell\": \"An empty or unused cell displays '--' (2 chars) or '---' (3 chars) or '---00' (command+value). Parsed hexValue will be null for these.\",\r\n \"relativeHex\": \"Transpose and some FX values use relative hex: 00 = no change, 01-7F = +1-127, FF-80 = -1-128. See 'Relative and Absolute FX Commands' in the manual.\",\r\n \"rowIndex\": \"Row/step indices are parsed from the leftmost characters of currentLine. They are hex for all grid views.\",\r\n \"titleAsterisk\": \"A '*' next to the item ID in the screen title (e.g. 'PHRASE 03*') indicates the item is used elsewhere in the chain or song (phrase view); on other views it marks unsaved changes. See 'An asterisk next to the phrase number' in the manual.\"\r\n },\r\n \"fieldTypes\": {\r\n \"chainRef\": \"Reference to a chain number (hex 00-FF). '--' means empty/unused.\",\r\n \"phraseRef\": \"Reference to a phrase number (hex 00-FF). '--' means empty/unused.\",\r\n \"note\": \"Musical note in M8 format: [A-G][#|-][0-9] e.g. 'C-4', 'A#3'. '---' = empty. 'OFF' = note-off.\",\r\n \"velocity\": \"Note velocity 00-7F (hex), from 00 (silence) to 7F (maximum). Default is 64 (hex). '--' = inherit.\",\r\n \"instrumentRef\": \"Instrument number 00-7F (hex). '--' = no retrigger (keep current instrument).\",\r\n \"fxCommand\": \"3-character sequencer or instrument FX command name, e.g. 'KIL', 'ARP', 'DEL'. '---' = none.\",\r\n \"fxValue\": \"2-char hex value 00-FF accompanying an FX command. Interpretation depends on the command.\",\r\n \"transpose\": \"Semitone or tick transpose. Relative: 00=none, 01-7F=+1-+127, FF-80=-1--128.\",\r\n \"ticks\": \"Number of ticks this step consumes in a groove (hex 01-FF). '00' skips the step. '--' = empty (groove loops at this point).\",\r\n \"volume\": \"Volume multiplier 00-FF (hex). Multiplied with the phrase-row velocity. '--' = no override.\", \"ppq\": \"Pulses Per Quarter note: resolution of the groove clock (decimal). One of 24 (default), 48, 96 or 192. Only present on groove row 0; absent on all other rows.\",\r\n \"instrumentName\": \"Instrument name string (up to 12 characters). Text field, no hex value.\",\r\n \"eqSlot\": \"EQ slot assignment (hex). '--' = no EQ applied to this instrument.\",\r\n \"rowIndex\": \"The positional index of this row in the current view (song row, chain position, phrase step, etc.).\",\r\n \"noteInterval\": \"Scale row label: one of the 12 note intervals C, C#, D, D#, E, F, F#, G, G#, A, A#, B (sharps use '#', naturals may render a dim '-' pad, e.g. 'C-').\",\r\n \"onOff\": \"Toggle field displayed as 'ON' or 'OFF'; the scale view 'EN' column shows '--' for a note out of the scale. Used by the scale view 'EN' column.\",\r\n \"semitoneOffset\": \"Signed semitone offset in decimal notation with two decimals (-24.00 to +24.00), e.g. '00.00', '-00.50', '12.00'. Used by the scale view 'OFFSET' column.\",\r\n \"swing\": \"Groove swing amount as a percentage with one decimal, e.g. '50.0%' = straight timing, higher = swung. Shown on every second groove row (0, 2, 4, …): the swing of that pair of steps (ticks 07,05 → 58.3%).\",\r\n \"instrumentType\": \"Instrument engine chosen with the TYPE field of the Instrument view: WAVSYNTH, MACROSYN, SAMPLER, FMSYNTH, HYPERSYNTH, EXTERNAL INST, MIDI OUT (NONE for an unused slot). Text value; changes which parameters the view shows.\",\r\n \"name\": \"Free-text name field (instrument, song, sample, directory). Up to 12 characters; a row of dashes means unnamed.\",\r\n \"parameter\": \"Editable value of a named parameter (CUTOFF, TEMPO, GAIN, …). Its 'format' says how to read it: hex byte, 'XX:YY' pair, decimal, decimal with a fraction, or free text.\",\r\n \"option\": \"Choice from a list, shown as its name, optionally preceded by its index in hex (e.g. '00CHORUS', 'STEREO', 'BELL'). hexValue holds the index when there is one, text holds the name.\",\r\n \"action\": \"Button-like text that does something when [EDIT] is pressed on it (LOAD, SAVE, RENDER, SETTINGS, VIEW INST. POOL, EQ, …). The text is the caption.\",\r\n \"fileEntry\": \"Row of a file browser: '/name' is a directory, '/..' the parent directory, anything else a file (e.g. 'Bass.m8i').\"\r\n },\r\n \"views\": {\r\n \"song\": {\r\n \"title\": \"Song View\",\r\n \"type\": \"grid\",\r\n \"description\": \"Arranges chains across 8 independent tracks to form the song structure. Each row is a song position (up to 256 rows); each column is a track. Each track advances vertically until it reaches an empty column ('--'), then loops back to row 00. Chains containing no notes are grayed out; chains '00' or 'FE' are commonly used as silent spacers. On row '00', [UP][UP] enters track reordering mode.\",\r\n \"rowField\": {\r\n \"x\": 0, \"width\": 2, \"parse\": \"hex\", \"type\": \"rowIndex\",\r\n \"key\": \"songRow\", \"description\": \"Song row 00-FF. Each track loops back to row 00 when it reaches '--'.\"\r\n },\r\n \"columns\": [\r\n { \"x\": 3, \"width\": 2, \"key\": \"track1\", \"type\": \"chainRef\", \"label\": \"Track 1\", \"meta\": { \"trackIndex\": 1 } },\r\n { \"x\": 6, \"width\": 2, \"key\": \"track2\", \"type\": \"chainRef\", \"label\": \"Track 2\", \"meta\": { \"trackIndex\": 2 } },\r\n { \"x\": 9, \"width\": 2, \"key\": \"track3\", \"type\": \"chainRef\", \"label\": \"Track 3\", \"meta\": { \"trackIndex\": 3 } },\r\n { \"x\": 12, \"width\": 2, \"key\": \"track4\", \"type\": \"chainRef\", \"label\": \"Track 4\", \"meta\": { \"trackIndex\": 4 } },\r\n { \"x\": 15, \"width\": 2, \"key\": \"track5\", \"type\": \"chainRef\", \"label\": \"Track 5\", \"meta\": { \"trackIndex\": 5 } },\r\n { \"x\": 18, \"width\": 2, \"key\": \"track6\", \"type\": \"chainRef\", \"label\": \"Track 6\", \"meta\": { \"trackIndex\": 6 } },\r\n { \"x\": 21, \"width\": 2, \"key\": \"track7\", \"type\": \"chainRef\", \"label\": \"Track 7\", \"meta\": { \"trackIndex\": 7 } },\r\n { \"x\": 24, \"width\": 2, \"key\": \"track8\", \"type\": \"chainRef\", \"label\": \"Track 8\", \"meta\": { \"trackIndex\": 8 } }\r\n ]\r\n },\r\n \"chain\": {\r\n \"title\": \"Chain View\",\r\n \"type\": \"grid\",\r\n \"description\": \"An ordered list of up to 16 phrases (256 steps). The first '--' phrase ends the chain and the song continues on to the next chain in the song view. The 'TSP' column optionally transposes every note in the phrase in semitones (relative hex).\",\r\n \"rowField\": {\r\n \"x\": 0, \"width\": 1, \"parse\": \"hex\", \"type\": \"rowIndex\",\r\n \"key\": \"chainPos\", \"description\": \"Position within the chain (0-F, max 16 entries).\"\r\n },\r\n \"columns\": [\r\n { \"x\": 2, \"width\": 2, \"key\": \"phrase\", \"type\": \"phraseRef\", \"label\": \"Phrase (PH)\", \"description\": \"Phrase number to play at this chain position.\" },\r\n { \"x\": 5, \"width\": 2, \"key\": \"transpose\", \"type\": \"transpose\", \"label\": \"Transpose (TSP)\", \"description\": \"Optional note transpose in semitones applied to every note in the phrase (relative hex).\" }\r\n ]\r\n },\r\n \"phrase\": {\r\n \"title\": \"Phrase View\",\r\n \"type\": \"grid\",\r\n \"description\": \"A 16-step sequence. Each step holds a note, velocity, instrument assignment, and up to 3 FX command+value pairs. Notes cover 11 octaves (the full 128 MIDI note range); velocity defaults to 64 (hex); an empty instrument column plays the note without retriggering the instrument. A '*' next to the phrase number indicates the phrase is used elsewhere in the chain or song.\",\r\n \"rowField\": {\r\n \"x\": 0, \"width\": 1, \"parse\": \"hex\", \"type\": \"rowIndex\",\r\n \"key\": \"step\", \"description\": \"Step index 0-F (16th note by default; altered by groove).\"\r\n },\r\n \"columns\": [\r\n { \"x\": 2, \"width\": 3, \"key\": \"note\", \"type\": \"note\", \"label\": \"Note (N)\" },\r\n { \"x\": 6, \"width\": 2, \"key\": \"vel\", \"type\": \"velocity\", \"label\": \"Velocity (V)\" },\r\n { \"x\": 9, \"width\": 2, \"key\": \"inst\", \"type\": \"instrumentRef\", \"label\": \"Instrument (I)\" },\r\n { \"x\": 12, \"width\": 3, \"key\": \"fx1cmd\", \"type\": \"fxCommand\", \"label\": \"FX1 Command\" },\r\n { \"x\": 15, \"width\": 2, \"key\": \"fx1val\", \"type\": \"fxValue\", \"label\": \"FX1 Value\" },\r\n { \"x\": 18, \"width\": 3, \"key\": \"fx2cmd\", \"type\": \"fxCommand\", \"label\": \"FX2 Command\" },\r\n { \"x\": 21, \"width\": 2, \"key\": \"fx2val\", \"type\": \"fxValue\", \"label\": \"FX2 Value\" },\r\n { \"x\": 24, \"width\": 3, \"key\": \"fx3cmd\", \"type\": \"fxCommand\", \"label\": \"FX3 Command\" },\r\n { \"x\": 27, \"width\": 2, \"key\": \"fx3val\", \"type\": \"fxValue\", \"label\": \"FX3 Value\" }\r\n ]\r\n },\r\n \"table\": {\r\n \"title\": \"Table View\",\r\n \"type\": \"grid\",\r\n \"description\": \"A 16-step mini-sequencer that runs alongside an instrument. Used for arpeggios, multi-stage envelopes, and automated FX. The 'N' column transposes the instrument for the step (relative hex); 'V' multiplies with the phrase-row velocity. Each instrument has a dedicated table plus 128 assignable tables (TBL command). FX columns can run at independent speeds via TIC; a TIC on the last table row affects all rows. HOP shortens or loops the table.\",\r\n \"rowField\": {\r\n \"x\": 0, \"width\": 1, \"parse\": \"hex\", \"type\": \"rowIndex\",\r\n \"key\": \"step\", \"description\": \"Table step 0-F. Loops from bottom unless a HOP command redirects.\"\r\n },\r\n \"columns\": [\r\n { \"x\": 2, \"width\": 2, \"key\": \"transpose\", \"type\": \"transpose\", \"label\": \"Transpose (N)\", \"description\": \"Transposes the instrument for this step (relative hex). '--' = no transpose.\" },\r\n { \"x\": 5, \"width\": 2, \"key\": \"volume\", \"type\": \"volume\", \"label\": \"Volume (V)\", \"description\": \"Volume multiplier applied on top of the phrase velocity (hex). '--' = no override.\" },\r\n { \"x\": 8, \"width\": 3, \"key\": \"fx1cmd\", \"type\": \"fxCommand\", \"label\": \"FX1 Command\", \"description\": \"3-char command name for table FX slot 1.\" },\r\n { \"x\": 11, \"width\": 2, \"key\": \"fx1val\", \"type\": \"fxValue\", \"label\": \"FX1 Value\", \"description\": \"Value for table FX1.\" },\r\n { \"x\": 14, \"width\": 3, \"key\": \"fx2cmd\", \"type\": \"fxCommand\", \"label\": \"FX2 Command\", \"description\": \"3-char command name for table FX slot 2.\" },\r\n { \"x\": 17, \"width\": 2, \"key\": \"fx2val\", \"type\": \"fxValue\", \"label\": \"FX2 Value\", \"description\": \"Value for table FX2.\" },\r\n { \"x\": 20, \"width\": 3, \"key\": \"fx3cmd\", \"type\": \"fxCommand\", \"label\": \"FX3 Command\", \"description\": \"3-char command name for table FX slot 3.\" },\r\n { \"x\": 23, \"width\": 2, \"key\": \"fx3val\", \"type\": \"fxValue\", \"label\": \"FX3 Value\", \"description\": \"Value for table FX3.\" }\r\n ]\r\n },\r\n \"groove\": {\r\n \"title\": \"Groove View\",\r\n \"type\": \"grid\",\r\n \"description\": \"Defines the timing of each phrase step in ticks. Used to create swing, shuffle, triplets, and other rhythmic feels. At the default 24 PPQ a step is 6 ticks and a full phrase totals 96 ticks. The groove loops when a '--' entry is reached; a '00' entry skips the phrase step. Row 0 also holds the PPQ resolution (24/48/96/192) and the SWING percentage; editing the row 0 tick value alters rows 0 and 1 together (a common swing setting is 07,05 or 08,04).\",\r\n \"rowField\": {\r\n \"x\": 0, \"width\": 1, \"parse\": \"hex\", \"type\": \"rowIndex\",\r\n \"key\": \"step\", \"description\": \"Groove step 0-F. The groove loops when a '--' entry is reached.\"\r\n },\r\n \"columns\": [\r\n { \"x\": 2, \"width\": 2, \"key\": \"ticks\", \"type\": \"ticks\", \"label\": \"Ticks (TIC)\", \"description\": \"Number of ticks this step takes. Default is 06 (at 24 PPQN = 1 sixteenth note). '00' skips the step.\" },\r\n { \"x\": 6, \"width\": 2, \"key\": \"ppq\", \"type\": \"ppq\", \"label\": \"PPQ\", \"description\": \"Pulses Per Quarter note: 24, 48, 96 or 192. Only present on groove row 0; absent on all other rows.\" },\r\n { \"x\": 10, \"width\": 5, \"key\": \"swing\", \"type\": \"swing\", \"label\": \"Swing\", \"description\": \"Swing amount as a percentage (e.g. '50.0%' = straight). Shown on every second row (0, 2, 4, …) for that pair of steps; empty on the others.\" }\r\n ]\r\n },\r\n \"scale\": {\r\n \"title\": \"Scale View\",\r\n \"type\": \"grid\",\r\n \"description\": \"Defines and quantizes note-related events (project/chain transpose, note entry, ARP, PIT, and the MIDIOUT instrument CHD and ADD commands). Up to 16 user-defined scales per project; scale '00' is the default for all 8 tracks and can be overridden per track with SCA/SCG FX commands. Each of the 12 note intervals can be enabled ('EN' = ON) with an optional detuning offset (-24.00 to +24.00 semitones); a note out of the scale reads '-- -- --'. Rows that are not column-mapped (Model:02, fw 6.5): KEY under the title (global root note for the default scale, the same on every scale), then after the intervals TUNE (A4 reference in Hz, e.g. '440.00'), NAME (up to 16 characters, 92 factory scales load from /Scales/Factory), and the LOAD/SAVE actions.\",\r\n \"columns\": [\r\n { \"x\": 0, \"width\": 2, \"key\": \"interval\", \"type\": \"noteInterval\", \"label\": \"Note Interval\", \"description\": \"Note interval row label: C, C#, D, D#, E, F, F#, G, G#, A, A#, B.\" },\r\n { \"x\": 3, \"width\": 3, \"key\": \"en\", \"type\": \"onOff\", \"label\": \"Enabled (EN)\", \"description\": \"Enables this note interval in the scale: 'ON', or '--' when the note is out of the scale (the device draws the row '-- -- --').\" },\r\n { \"x\": 6, \"width\": 6, \"key\": \"offset\", \"type\": \"semitoneOffset\", \"label\": \"Offset (OFFSET)\", \"description\": \"Detune offset in semitones, decimal with two decimals (-24.00 to +24.00), e.g. '00.00'.\" }\r\n ]\r\n },\r\n \"instrumentpool\": {\r\n \"title\": \"Instrument Pool\",\r\n \"type\": \"grid\",\r\n \"description\": \"Lists all 128 instrument slots with their mix routing. Each row shows the instrument name (up to 12 chars, '------------' = empty slot) and its dry/FX send levels. Mixer-related settings can be adjusted per instrument here; [EDIT] on an empty slot loads an instrument or sample.\",\r\n \"rowField\": {\r\n \"x\": 0, \"width\": 2, \"parse\": \"hex\", \"type\": \"rowIndex\",\r\n \"key\": \"instIndex\", \"description\": \"Instrument slot index 00-7F.\"\r\n },\r\n \"columns\": [\r\n { \"x\": 3, \"width\": 12, \"key\": \"name\", \"type\": \"instrumentName\", \"label\": \"Instrument Name\", \"description\": \"Name of the instrument in this slot (up to 12 chars). '------------' = empty slot.\" },\r\n { \"x\": 16, \"width\": 2, \"key\": \"dry\", \"type\": \"volume\", \"label\": \"Dry (DRY)\", \"description\": \"Direct output volume for this instrument (hex 00-FF).\" },\r\n { \"x\": 19, \"width\": 2, \"key\": \"mx\", \"type\": \"volume\", \"label\": \"ModFX (MX)\", \"description\": \"ModFX send level (hex 00-FF).\" },\r\n { \"x\": 22, \"width\": 2, \"key\": \"de\", \"type\": \"volume\", \"label\": \"Delay (DE)\", \"description\": \"Delay send level (hex 00-FF).\" },\r\n { \"x\": 25, \"width\": 2, \"key\": \"rv\", \"type\": \"volume\", \"label\": \"Reverb (RV)\", \"description\": \"Reverb send level (hex 00-FF).\" },\r\n { \"x\": 28, \"width\": 2, \"key\": \"eq\", \"type\": \"eqSlot\", \"label\": \"EQ Slot (EQ)\", \"description\": \"EQ slot assignment. '--' = no EQ.\" }\r\n ]\r\n },\r\n \"inst\": {\r\n \"title\": \"Instrument View\",\r\n \"type\": \"parameter\",\r\n \"description\": \"Per-instrument parameters for the selected slot: TYPE (Wavsynth, Macrosynth, Sampler, FM Synth, Hypersynth, External Instrument, MIDI Out), LOAD/SAVE presets, NAME, TRANSP. (enable/disable transposition from scales and transpose columns), TABLE TIC (default table speed), and EQ slot, plus the synth engine parameters. Almost every parameter has an associated FX command; highlighting a parameter and navigating to the phrase or table view sets that parameter's FX command as default.\"\r\n },\r\n \"instmods\": {\r\n \"title\": \"Instrument Modulation View\",\r\n \"type\": \"parameter\",\r\n \"description\": \"Four configurable modulation slots per instrument: AHD envelope, ADSR envelope, DRUM envelope, LFO, TRIG envelope, or TRACKING, each with a destination parameter (DEST) and optional source (SRC).\"\r\n },\r\n \"mixer\": {\r\n \"title\": \"Mixer View\",\r\n \"type\": \"parameter\",\r\n \"description\": \"Mix levels for the 8 tracks, the 3 send effects (MX/DE/RE), the analog input (INPUT, with dual-mono mode) and USB input, the DJ filter (DJF), OTT compression, main EQ, limiter (LIM) and main volume (MIX). All signals are summed through OTT, main EQ, limiter, DJ filter and main volume. Most values are also exposed as FX commands (VMV, VMX, VDE, VRE, VT1-8, DJC/DJR/DJT, IVO/IMX/IDE/IRV, USB, ...).\"\r\n },\r\n \"project\": {\r\n \"title\": \"Project View\",\r\n \"type\": \"parameter\",\r\n \"description\": \"Song-wide settings such as tempo, project transpose (identical to the TSP FX command), and LIVE QUANTIZE behavior for cueing tracks in live mode. [SHIFT]+[OPTION] stores a song snapshot; [SHIFT]+[EDIT] recalls it.\"\r\n },\r\n \"effectsettings\": {\r\n \"title\": \"Effect Settings View\",\r\n \"type\": \"parameter\",\r\n \"description\": \"Settings for the 3 send effects: ModFX type/depth/frequency/width, Delay time/feedback/width, and Reverb size/decay/modulation/width/freeze — all addressable via the XMT-XMW, XDT-XDR and XRS-XRZ FX commands.\"\r\n },\r\n \"mixeq\": {\r\n \"title\": \"Mix EQ View\",\r\n \"type\": \"parameter\",\r\n \"description\": \"EQ editor for the main mix EQ slot (assign with the EQM FX command). Selecting EQ in the Mixer view and pressing [EDIT] or [SHIFT]+[RIGHT] opens this view.\"\r\n },\r\n \"modfxeq\": {\r\n \"title\": \"ModFX EQ View\",\r\n \"type\": \"parameter\",\r\n \"description\": \"EQ editor for the ModFX send path.\"\r\n },\r\n \"delayeq\": {\r\n \"title\": \"Delay EQ View\",\r\n \"type\": \"parameter\",\r\n \"description\": \"EQ editor for the Delay send path.\"\r\n },\r\n \"reverbeq\": {\r\n \"title\": \"Reverb EQ View\",\r\n \"type\": \"parameter\",\r\n \"description\": \"EQ editor for the Reverb send path.\"\r\n },\r\n \"mixscope\": {\r\n \"title\": \"Mix Scope View\",\r\n \"type\": \"parameter\",\r\n \"description\": \"Graphical scope of the main mix activity. Opened from the Mixer view with MIX selected via [EDIT] or [SHIFT]+[RIGHT].\"\r\n },\r\n \"limiterscope\": {\r\n \"title\": \"Limiter Scope View\",\r\n \"type\": \"parameter\",\r\n \"description\": \"Graphical scope of the limiter's compression activity (the white line on the MIX indicator). Opened from the Mixer view with LIM selected via [EDIT] or [SHIFT]+[RIGHT].\"\r\n },\r\n \"systemsettings\": {\r\n \"title\": \"System Settings View\",\r\n \"type\": \"parameter\",\r\n \"description\": \"System-level settings for the device.\"\r\n },\r\n \"loadinstrument\": {\r\n \"title\": \"Load Instrument\",\r\n \"type\": \"parameter\",\r\n \"description\": \"File browser for loading an instrument preset from the SD card ([EDIT] on an empty instrument slot, or LOAD in the Instrument view).\"\r\n },\r\n \"selectsavedirectory\": {\r\n \"title\": \"Select Save Directory\",\r\n \"type\": \"parameter\",\r\n \"description\": \"File browser directory-selection step when saving a preset or project.\"\r\n },\r\n \"createdirectory\": {\r\n \"title\": \"Create Directory\",\r\n \"type\": \"parameter\",\r\n \"description\": \"File browser dialog for creating a new directory on the SD card.\"\r\n }\r\n },\r\n \"fxCommands\": {\r\n \"sequencer\": {\r\n \"ARP\": { \"description\": \"Arpeggio: plays 3 notes rapidly. X=first interval semitones, Y=second interval semitones.\" },\r\n \"ARC\": { \"description\": \"Arpeggio Config: X=direction and number of octaves, Y=speed in ticks.\" },\r\n \"CHA\": { \"description\": \"Chance: X=probability left of command (0=never, F=always), Y=probability right. In a table: probability for everything to the left of CHA from 00 (never) to FF (always).\" },\r\n \"DEL\": { \"description\": \"Delay: in phrase delays entire row by XX ticks; in table delays table playhead.\" },\r\n \"GRV\": { \"description\": \"Groove: sets groove number XX for the current track.\" },\r\n \"GGR\": { \"description\": \"Global Groove: sets groove XX for all tracks.\" },\r\n \"HOP\": { \"description\": \"Hop: in phrase jumps to row Y of next phrase; HOPFF stops track. In table loops to row Y X times.\" },\r\n \"INS\": { \"description\": \"Instrument: changes active instrument to XX as an FX command.\" },\r\n \"KIL\": { \"description\": \"Kill: stops the playing instrument after XX ticks.\" },\r\n \"RND\": { \"description\": \"Random: randomises the preceding FX command. X=left range, Y=right range (0=none, F=full).\" },\r\n \"RNL\": { \"description\": \"Randomise Left: randomises the FX command to the left (X=left range, Y=right range). In the first column RNL randomises the note and instrument number in phrase, or note and velocity in table.\" },\r\n \"RET\": { \"description\": \"Retrig: retriggers current row. Y≠0: retrig every Y ticks with X volume ramp. Y=0: single retrig after X ticks.\" },\r\n \"REP\": { \"description\": \"Repeat: repeats and increments the last FX command by XX each step. Active until a new command or REP00 stops it; it does not need to appear on every row. In a table: loops the last row XX times. A double-caret '^^' appears next to an FX lane while a REP from a previous phrase is active.\" },\r\n \"RTO\": { \"description\": \"Repeat To: use after REP to hold at the given value (the min/max REP stops at). In a table, used alone: loops until the relative value is reached.\" },\r\n \"RMX\": { \"description\": \"Remix: repositions the phrase playhead (Y) on tracks to the left (selected by X).\" },\r\n \"NTH\": { \"description\": \"Nth Trigger: conditional trigger on the loop count. X=affects what is left of the command, Y=what is right (in a table: what is left).\" },\r\n \"PSL\": { \"description\": \"Pitch Slide (portamento): slides to the given note over XX ticks.\" },\r\n \"PBN\": { \"description\": \"Pitch Bend: continuous pitch slide up (00-7F) or down (80-FF).\" },\r\n \"PVB\": { \"description\": \"Vibrato: X=speed, Y=depth.\" },\r\n \"PVX\": { \"description\": \"Extreme Vibrato: like PVB but with higher range.\" },\r\n \"SCA\": { \"description\": \"Track Scale: X=key signature, Y=scale number for this track.\" },\r\n \"SCG\": { \"description\": \"Global Scale: X=key, Y=scale number for the whole song.\" },\r\n \"SNG\": { \"description\": \"Song Hop: moves this track's song position by XX rows relative to current. Ignored if the resulting song position is not playable.\" },\r\n \"SED\": { \"description\": \"Random Seed: sets the random seed XX for this track.\" },\r\n \"TBL\": { \"description\": \"Table: assigns table number XX to the current instrument.\" },\r\n \"THO\": { \"description\": \"Table Hop: jumps all table columns to position 0X.\" },\r\n \"TIC\": { \"description\": \"Table Tick: sets the table tick rate. 00 = increment row on each instrument trigger; 01-FB = ticks per row; FC = Octave Map (playing octave maps to table row); FD = Velocity Map; FE = Note Map (pair with HOP00 on row 0C to limit to 12 notes/octave); FF = increment at 200 Hz. In a table each FX column can run independently; a TIC on the last table row affects all rows.\" },\r\n \"TBX\": { \"description\": \"Aux Table: runs table XX in parallel with the instrument table. 00=stop aux table. A TIC in the same row as a TBX affects the auxiliary table's tick rate.\" },\r\n \"TPO\": { \"description\": \"Tempo: sets song BPM to XX. In a table the tempo is relative.\" },\r\n \"TSP\": { \"description\": \"Global Song Transpose: transposes the entire song by XX semitones. Identical to the transpose value in the Project View.\" },\r\n \"NXT\": { \"description\": \"Next Track Trigger: plays instrument XX on the track to the right using the current note/velocity. Does not work on track 8.\" },\r\n \"OFF\": { \"description\": \"Note Off: stops the playing instrument after XX ticks. Triggers ADSR release stage.\" },\r\n \"MTT\": { \"description\": \"Micro Time: adjusts row playback timing in positive or negative sub-tick (1/8 tick) amounts. Negative amounts do not work on the first phrase row. Currently has no action in tables.\" }\r\n },\r\n \"instrument\": {\r\n \"VOL\": { \"description\": \"Volume (relative).\", \"relative\": true },\r\n \"PIT\": { \"description\": \"Pitch in semitones (relative).\", \"relative\": true },\r\n \"FIN\": { \"description\": \"Fine tune: pitch shift of up to about one semitone (relative).\", \"relative\": true },\r\n \"PLY\": { \"description\": \"Sampler play mode (FWD, REV, loop modes…).\" },\r\n \"STA\": { \"description\": \"Sampler start position.\" },\r\n \"LOP\": { \"description\": \"Sampler loop start (relative).\", \"relative\": true },\r\n \"LEN\": { \"description\": \"Sampler length (relative).\", \"relative\": true },\r\n \"DEG\": { \"description\": \"Degrade amount (relative).\", \"relative\": true },\r\n \"SLI\": { \"description\": \"Sampler slice to play.\" },\r\n \"ERR\": { \"description\": \"Error: randomizes pitch and parameters by the given amount.\" },\r\n \"FLT\": { \"description\": \"Filter type.\" },\r\n \"FIL\": { \"description\": \"Filter type.\" },\r\n \"ALG\": { \"description\": \"FM algorithm.\" },\r\n \"FM1\": { \"description\": \"FM modulation amount 1 (relative).\", \"relative\": true },\r\n \"FM2\": { \"description\": \"FM modulation amount 2 (relative).\", \"relative\": true },\r\n \"FM3\": { \"description\": \"FM modulation amount 3 (relative).\", \"relative\": true },\r\n \"FM4\": { \"description\": \"FM modulation amount 4 (relative).\", \"relative\": true },\r\n \"SNC\": { \"description\": \"Sync: on an FM synth the FM sync phase position; on a Hypersynth it offsets the sync of the oscillators by the given amount.\" },\r\n \"CRD\": { \"description\": \"Hypersynth chord set.\" },\r\n \"CVO\": { \"description\": \"Hypersynth chord voice offset: X=voice, Y=offset.\" },\r\n \"SWM\": { \"description\": \"Hypersynth oscillator swarm (relative).\", \"relative\": true },\r\n \"WID\": { \"description\": \"Hypersynth stereo width (relative).\", \"relative\": true },\r\n \"SUB\": { \"description\": \"Hypersynth sub-oscillator amount (relative).\", \"relative\": true },\r\n \"OSC\": { \"description\": \"Macrosynth oscillator shape.\" },\r\n \"TBR\": { \"description\": \"Macrosynth timbre (relative).\", \"relative\": true },\r\n \"COL\": { \"description\": \"Macrosynth color (relative).\", \"relative\": true },\r\n \"RED\": { \"description\": \"Macrosynth bit reduction (relative).\", \"relative\": true },\r\n \"TRG\": { \"description\": \"Macrosynth trigger/strike.\" },\r\n \"CUT\": { \"description\": \"Filter cutoff (relative).\", \"relative\": true },\r\n \"RES\": { \"description\": \"Filter resonance (relative).\", \"relative\": true },\r\n \"AMP\": { \"description\": \"Amp level (relative).\", \"relative\": true },\r\n \"LIM\": { \"description\": \"Limit mode: how the amp stage clips.\" },\r\n \"PAN\": { \"description\": \"Pan (relative).\", \"relative\": true },\r\n \"DRY\": { \"description\": \"Dry volume: the instrument's level in the main mix.\" },\r\n \"SMX\": { \"description\": \"Send to ModFX.\" },\r\n \"SDL\": { \"description\": \"Send to Delay.\" },\r\n \"SRV\": { \"description\": \"Send to Reverb.\" },\r\n \"CHD\": { \"description\": \"Chord (MIDIOUT instrument): triggers a chord. Quantized by the active scale.\" },\r\n \"ADD\": { \"description\": \"Add Note (MIDIOUT instrument): adds a note to the chord. Quantized by the active scale.\" }\r\n },\r\n \"modulation\": {\r\n \"EA\": { \"description\": \"Envelope amount of modulator N (relative).\", \"relative\": true },\r\n \"AT\": { \"description\": \"Envelope attack of modulator N.\" },\r\n \"HO\": { \"description\": \"Envelope hold of modulator N.\" },\r\n \"DE\": { \"description\": \"Envelope decay of modulator N.\" },\r\n \"ET\": { \"description\": \"Envelope trigger of modulator N: retriggers the envelope.\" },\r\n \"LA\": { \"description\": \"LFO amount of modulator N (relative).\", \"relative\": true },\r\n \"LO\": { \"description\": \"LFO oscillator shape of modulator N.\" },\r\n \"LS\": { \"description\": \"LFO trigger settings of modulator N.\" },\r\n \"LF\": { \"description\": \"LFO frequency of modulator N.\" },\r\n \"LT\": { \"description\": \"LFO trigger of modulator N: at the given offset, resets the LFO to that position.\" }\r\n },\r\n \"mixer\": {\r\n \"VMV\": { \"description\": \"Main Volume: sets the main song output volume.\" },\r\n \"VMX\": { \"description\": \"ModFX Volume.\" },\r\n \"VDE\": { \"description\": \"Delay Volume.\" },\r\n \"VRE\": { \"description\": \"Reverb Volume.\" },\r\n \"VT1\": { \"description\": \"Track 1 Volume.\" },\r\n \"VT2\": { \"description\": \"Track 2 Volume.\" },\r\n \"VT3\": { \"description\": \"Track 3 Volume.\" },\r\n \"VT4\": { \"description\": \"Track 4 Volume.\" },\r\n \"VT5\": { \"description\": \"Track 5 Volume.\" },\r\n \"VT6\": { \"description\": \"Track 6 Volume.\" },\r\n \"VT7\": { \"description\": \"Track 7 Volume.\" },\r\n \"VT8\": { \"description\": \"Track 8 Volume.\" },\r\n \"DJC\": { \"description\": \"DJ Filter Cutoff.\" },\r\n \"DJR\": { \"description\": \"DJ Filter Resonance.\" },\r\n \"DJT\": { \"description\": \"DJ Filter Type.\" },\r\n \"EQM\": { \"description\": \"Assign main mix EQ slot.\" },\r\n \"EQI\": { \"description\": \"Assign current instrument EQ slot.\" },\r\n \"XMT\": { \"description\": \"Send Effect: ModFX mod type and phase position (Effect Settings view).\" },\r\n \"XMM\": { \"description\": \"Send Effect: ModFX mod depth (Effect Settings view).\" },\r\n \"XMF\": { \"description\": \"Send Effect: ModFX mod frequency (Effect Settings view).\" },\r\n \"XMW\": { \"description\": \"Send Effect: ModFX stereo width (Effect Settings view).\" },\r\n \"XMR\": { \"description\": \"Send Effect: ModFX to reverb mix (Effect Settings view).\" },\r\n \"XDT\": { \"description\": \"Send Effect: Delay time — X = left, Y = right, in large increments (Effect Settings view).\" },\r\n \"XDF\": { \"description\": \"Send Effect: Delay feedback (Effect Settings view).\" },\r\n \"XDW\": { \"description\": \"Send Effect: Delay stereo width (Effect Settings view).\" },\r\n \"XDR\": { \"description\": \"Send Effect: Delay to reverb mix (Effect Settings view).\" },\r\n \"XRS\": { \"description\": \"Send Effect: Reverb room size (Effect Settings view).\" },\r\n \"XRD\": { \"description\": \"Send Effect: Reverb decay (Effect Settings view).\" },\r\n \"XRM\": { \"description\": \"Send Effect: Reverb mod depth (Effect Settings view).\" },\r\n \"XRF\": { \"description\": \"Send Effect: Reverb mod frequency (Effect Settings view).\" },\r\n \"XRW\": { \"description\": \"Send Effect: Reverb stereo width (Effect Settings view).\" },\r\n \"XRZ\": { \"description\": \"Send Effect: Reverb freeze — enabled with a value greater than 00, disabled at 00.\" },\r\n \"VIN\": { \"description\": \"Line Input Mixer: analog input volume (Mixer view).\" },\r\n \"XRH\": { \"description\": \"Send Effect: Reverb shimmer (Effect Settings view).\" },\r\n \"OTT\": { \"description\": \"OTT mix amount (Mixer OTT settings; OTC and OTI default to 80).\" },\r\n \"OTC\": { \"description\": \"OTT color (default 80).\" },\r\n \"OTI\": { \"description\": \"OTT time (default 80).\" },\r\n \"IVO\": { \"description\": \"Alias of VIN as named in the manual: analog input volume (Mixer view).\" },\r\n \"IMX\": { \"description\": \"Line Input Mixer: analog input ModFX send (Mixer view).\" },\r\n \"IDE\": { \"description\": \"Line Input Mixer: analog input delay send (Mixer view).\" },\r\n \"IRV\": { \"description\": \"Line Input Mixer: analog input reverb send (Mixer view). Also referenced as 'IRE' in the manual text.\" },\r\n \"IRE\": { \"description\": \"Alias of IRV: analog input reverb send (Mixer view).\" },\r\n \"IV2\": { \"description\": \"Line Input Mixer: volume for the 2nd input (right channel in dual-mono input mode, set in the Mixer view).\" },\r\n \"IM2\": { \"description\": \"Line Input Mixer: ModFX send for the 2nd input (right channel in dual-mono input mode).\" },\r\n \"ID2\": { \"description\": \"Line Input Mixer: delay send for the 2nd input (right channel in dual-mono input mode).\" },\r\n \"IR2\": { \"description\": \"Line Input Mixer: reverb send for the 2nd input (right channel in dual-mono input mode).\" },\r\n \"USB\": { \"description\": \"USB input volume (Mixer view).\" }\r\n }\r\n },\r\n \"instrumentFx\": {\r\n \"_note\": \"Instrument commands offered by [EDIT]+[UP/DOWN] (Effect Command Help view) in the Current Instrument section depend on the instrument type; the Instrument Mods section depends on the modulators of that instrument, and Sequencer FX and Mixer/Effects are the same for every type. Lists marked verified were read from a real M8 (Model:02). Wavsynth, External and MIDI Out were not available on that device: their lists are not known here.\",\r\n \"types\": {\r\n \"sampler\": {\r\n \"verified\": true,\r\n \"commands\": [\"VOL\", \"PIT\", \"FIN\", \"PLY\", \"STA\", \"LOP\", \"LEN\", \"DEG\", \"FLT\", \"CUT\", \"RES\", \"AMP\", \"LIM\", \"PAN\", \"SLI\", \"ERR\", \"DRY\", \"SMX\", \"SDL\", \"SRV\"]\r\n },\r\n \"macrosynth\": {\r\n \"verified\": true,\r\n \"commands\": [\"VOL\", \"PIT\", \"FIN\", \"OSC\", \"TBR\", \"COL\", \"DEG\", \"RED\", \"FIL\", \"CUT\", \"RES\", \"AMP\", \"LIM\", \"PAN\", \"TRG\", \"ERR\", \"DRY\", \"SMX\", \"SDL\", \"SRV\"]\r\n },\r\n \"fmsynth\": {\r\n \"verified\": true,\r\n \"commands\": [\"VOL\", \"PIT\", \"FIN\", \"ALG\", \"FM1\", \"FM2\", \"FM3\", \"FM4\", \"FIL\", \"CUT\", \"RES\", \"AMP\", \"LIM\", \"PAN\", \"SNC\", \"ERR\", \"DRY\", \"SMX\", \"SDL\", \"SRV\"]\r\n },\r\n \"hypersynth\": {\r\n \"verified\": true,\r\n \"commands\": [\"VOL\", \"PIT\", \"FIN\", \"CRD\", \"CVO\", \"SWM\", \"WID\", \"SUB\", \"FIL\", \"CUT\", \"RES\", \"AMP\", \"LIM\", \"PAN\", \"SNC\", \"ERR\", \"DRY\", \"SMX\", \"SDL\", \"SRV\"]\r\n },\r\n \"midiout\": {\r\n \"verified\": false,\r\n \"commands\": [\"CHD\", \"ADD\"]\r\n }\r\n },\r\n \"modulation\": {\r\n \"_note\": \"Modulator slot N (1-4) offers the commands of its kind. Seen: an envelope slot (AHD) offers EA AT HO DE ET, an LFO slot offers LA LO LS LF LT. Slot kinds vary per instrument (e.g. Sampler 00: env, env, LFO, LFO; Hypersynth 01: env, LFO, LFO, LFO). Other modulator kinds were not captured.\",\r\n \"envelope\": [\"EA\", \"AT\", \"HO\", \"DE\", \"ET\"],\r\n \"lfo\": [\"LA\", \"LO\", \"LS\", \"LF\", \"LT\"]\r\n },\r\n \"relativeCommands\": {\r\n \"description\": \"Relative commands add to the instrument parameter's current value (01-7F add, 80-FF subtract) and keep accumulating until a note is triggered with a RET command or with an instrument number in the I column; that resets every parameter to the instrument's own value.\"\r\n }\r\n }\r\n}\r\n","// Turns a point on the M8 screen into the cell the cursor should go to, and the geometry the\r\n// cursor navigator measures with. Candidates are the learned cursor cells that have text on the\r\n// current screen, plus the runs of text no learned cell covers yet (minus those the cursor could\r\n// not reach before). A click goes to the nearest candidate on its own row when one is close, else to\r\n// the nearest one on the screen. Pure functions.\r\n\r\nimport viewSchema from '../m8-view-context.json'\r\nimport { type CellRect, type CursorDir, type CursorLayout, cellId } from './cursorMap'\r\nimport type { M8CellMetrics } from './store'\r\n\r\nexport type Point = { x: number; y: number }\r\n\r\n/**\r\n * Where the cursor should go. `cell`: a learned cursor cell (reached when the cursor is that cell).\r\n * `text`: text no learned cell covers yet; `rect` is the character aimed at (reached when the\r\n * cursor covers it), `run` the whole run (one run can be two cells, as an FX command and value).\r\n * `point`: no text near the point, or selection mode (reached when the cursor covers the point).\r\n */\r\nexport type CursorGoal =\r\n | { kind: 'cell'; id: string; rect: CellRect }\r\n | { kind: 'text'; key: string; rect: CellRect; run: CellRect; row: number }\r\n | { kind: 'point'; rect: CellRect }\r\n\r\nexport type TextRun = { key: string; rect: CellRect; row: number; col: number; text: string }\r\n\r\n// The 1–2 px corners a cursor rectangle draws outside its cell\r\nconst OVERHANG_PX = 3\r\n// Between rows, vertical distance counts more: a column is easier to aim at than a row\r\nconst ROW_WEIGHT = 1.5\r\n// A candidate of the clicked row this close (in characters), with no other text between, beats every\r\n// other candidate: a label goes to the value beside it (Project, Instrument). Otherwise the nearest wins: a\r\n// label under its value goes up to it (Mixer).\r\nconst SAME_ROW_REACH = 12\r\n// On 40-column layouts the track panel starts here; the cursor never goes there\r\nconst PANEL_COLUMN = 34\r\n\r\n// Grid views with a row number column (song rows, chain, phrase, table and groove steps, instrument\r\n// slots), and its width. Up on their first row and Down on their last scroll the view or move to\r\n// the neighbouring chain or phrase (and Up Up on song row 00 starts track reordering), so a\r\n// navigation stays between them.\r\nconst ROW_NUMBER_WIDTH: Record<string, number> = Object.fromEntries(\r\n Object.entries(viewSchema.views as Record<string, { rowField?: { width: number } }>).flatMap(([name, view]) =>\r\n view.rowField ? [[name, view.rowField.width]] : [],\r\n ),\r\n)\r\n\r\nexport type GridRows = { first: number; last: number; numberWidth: number }\r\n\r\n/** The first and last rows that start with a row number, on grid views that have one; else null. */\r\nexport const gridRows = (viewName: string | null, lines: readonly string[]): GridRows | null => {\r\n const width = viewName ? ROW_NUMBER_WIDTH[viewName] : undefined\r\n if (!width) return null\r\n let first = -1\r\n let last = -1\r\n lines.forEach((line, row) => {\r\n if (/^[0-9a-f]+$/i.test(line.slice(1, 1 + width)) && (line[1 + width] ?? ' ') === ' ') {\r\n if (first < 0) first = row\r\n last = row\r\n }\r\n })\r\n return first < 0 ? null : { first, last, numberWidth: width }\r\n}\r\n\r\n// Grid views whose cursor cells are their schema columns on every numbered row, one character right of\r\n// the schema's x (measured on Model:02 fw 6.5, small and large font). The navigator plans over this\r\n// grid instead of learning it: learned moves there went wrong, as the M8 draws the new view's cursor\r\n// before its title, so cells of one view were learned in another's layout.\r\nconst FIXED_GRID_VIEWS = ['song', 'chain', 'phrase']\r\nconst GRID_COLUMNS: Record<string, { x: number; width: number }[]> = Object.fromEntries(\r\n Object.entries(viewSchema.views as Record<string, { columns?: { x: number; width: number }[] }>).flatMap(([name, view]) =>\r\n FIXED_GRID_VIEWS.includes(name) && view.columns ? [[name, view.columns]] : [],\r\n ),\r\n)\r\n\r\n/** Whether the navigator knows this view's grid and learns nothing there. */\r\nexport const hasFixedGrid = (viewName: string | null) => !!viewName && viewName in GRID_COLUMNS\r\n\r\n/**\r\n * The cells and moves of a fixed grid view's numbered rows (Up/Down keep the column, Left/Right keep the\r\n * row); null on other views. Moves past the grid's edges are left out.\r\n */\r\nexport const fixedGridLayout = (viewName: string | null, grid: GridRows | null, metrics: M8CellMetrics): CursorLayout | null => {\r\n const columns = viewName ? GRID_COLUMNS[viewName] : undefined\r\n if (!columns || !grid) return null\r\n const layout: CursorLayout = { cells: new Map(), moves: new Map(), unreachable: new Set(), suspects: new Set() }\r\n const idAt = (row: number, column: { x: number; width: number }) => {\r\n const rect = {\r\n x: (column.x + 1) * metrics.cellW + metrics.offX,\r\n y: row * metrics.cellH + metrics.offY,\r\n w: column.width * metrics.cellW,\r\n h: metrics.cellH,\r\n }\r\n const id = cellId(rect)\r\n layout.cells.set(id, rect)\r\n return id\r\n }\r\n for (let row = grid.first; row <= grid.last; row++) {\r\n columns.forEach((column, i) => {\r\n const moves: Partial<Record<CursorDir, string>> = {}\r\n if (row > grid.first) moves.up = idAt(row - 1, column)\r\n if (row < grid.last) moves.down = idAt(row + 1, column)\r\n if (i > 0) moves.left = idAt(row, columns[i - 1])\r\n if (i < columns.length - 1) moves.right = idAt(row, columns[i + 1])\r\n layout.moves.set(idAt(row, column), moves)\r\n })\r\n }\r\n return layout\r\n}\r\n\r\n/** A cursor rectangle as the character cells it covers (column, row, width in characters). */\r\nexport const snapToGrid = (rect: CellRect, metrics: M8CellMetrics): CellRect => {\r\n const col = Math.round((rect.x - metrics.offX) / metrics.cellW)\r\n const row = Math.round((rect.y - metrics.offY) / metrics.cellH)\r\n const chars = Math.max(1, Math.round(rect.w / metrics.cellW))\r\n return { x: col * metrics.cellW + metrics.offX, y: row * metrics.cellH + metrics.offY, w: chars * metrics.cellW, h: metrics.cellH }\r\n}\r\n\r\n/** The screen row of a cursor cell. */\r\nexport const rowOf = (rect: CellRect, metrics: M8CellMetrics) => Math.round((rect.y - metrics.offY) / metrics.cellH)\r\n\r\n/** First column of the track panel, or null when the layout has no 40 columns. */\r\nexport const panelColumnFor = (screenWidth: number, metrics: M8CellMetrics): number | null =>\r\n Math.round(screenWidth / metrics.cellW) >= 40 ? PANEL_COLUMN : null\r\n\r\n/**\r\n * The labels that start the rows (words with a letter outside hex digits, two characters or more).\r\n * Values, row numbers and markers are left out, so the signature changes with the layout\r\n * (instrument type, modulator type) but not with the song.\r\n */\r\nexport const layoutSignature = (lines: readonly string[], panelColumn: number | null): string =>\r\n lines\r\n .map((line, row) => {\r\n const token = (panelColumn === null ? line : line.slice(0, panelColumn)).trim().split(/\\s+/)[0] ?? ''\r\n return token.length >= 2 && /[g-z]/i.test(token) ? `${row}${token.toLowerCase()}` : ''\r\n })\r\n .filter(Boolean)\r\n .join(' ')\r\n\r\nconst charRect = (row: number, col: number, metrics: M8CellMetrics): CellRect => ({\r\n x: col * metrics.cellW + metrics.offX,\r\n y: row * metrics.cellH + metrics.offY,\r\n w: metrics.cellW,\r\n h: metrics.cellH,\r\n})\r\n\r\n/**\r\n * Runs of text on the screen, in M8 pixels. Column 0 (the playback marker margin) is left out, and\r\n * so are the characters `covered` says belong to a known cell.\r\n */\r\nexport const textRuns = (\r\n lines: readonly string[],\r\n metrics: M8CellMetrics,\r\n panelColumn: number | null,\r\n covered: (row: number, col: number) => boolean = () => false,\r\n): TextRun[] => {\r\n const runs: TextRun[] = []\r\n lines.forEach((line, row) => {\r\n const chars = [...` ${(panelColumn === null ? line : line.slice(0, panelColumn)).slice(1)}`]\r\n const visible = chars.map((ch, col) => (ch !== ' ' && covered(row, col) ? ' ' : ch)).join('')\r\n for (const match of visible.matchAll(/\\S+/g)) {\r\n const col = match.index ?? 0\r\n const text = match[0]\r\n runs.push({\r\n key: `${row}:${col}:${text.toLowerCase()}`,\r\n rect: { x: col * metrics.cellW + metrics.offX, y: row * metrics.cellH + metrics.offY, w: text.length * metrics.cellW, h: metrics.cellH },\r\n row,\r\n col,\r\n text,\r\n })\r\n }\r\n })\r\n return runs\r\n}\r\n\r\n/** Whether the screen has text under a cursor cell (the same columns as textUnderCursor). */\r\nexport const cellHasText = (rect: CellRect, lines: readonly string[], metrics: M8CellMetrics): boolean => {\r\n const row = rowOf(rect, metrics)\r\n const first = Math.round((rect.x - metrics.offX) / metrics.cellW)\r\n const last = Math.floor((rect.x + rect.w - 1 - metrics.offX) / metrics.cellW)\r\n return (lines[row] ?? '').slice(Math.max(0, first), last + 1).trim().length > 0\r\n}\r\n\r\nconst overlap = (a0: number, a1: number, b0: number, b1: number) => Math.max(0, Math.min(a1, b1) - Math.max(a0, b0))\r\n\r\n/** Whether `cursor` covers `target` horizontally (at least half a character, or half the target). */\r\nexport const coversX = (cursor: CellRect, target: CellRect, metrics: M8CellMetrics) =>\r\n overlap(cursor.x, cursor.x + cursor.w, target.x, target.x + target.w) >= Math.min(metrics.cellW, target.w) / 2\r\n\r\n/** Whether `cursor` covers `target` vertically (at least half a row, or half the target). */\r\nexport const coversY = (cursor: CellRect, target: CellRect, metrics: M8CellMetrics) =>\r\n overlap(cursor.y, cursor.y + cursor.h, target.y, target.y + target.h) >= Math.min(metrics.cellH, target.h) / 2\r\n\r\nexport const covers = (cursor: CellRect, target: CellRect, metrics: M8CellMetrics) =>\r\n coversX(cursor, target, metrics) && coversY(cursor, target, metrics)\r\n\r\nconst center = (rect: CellRect): Point => ({ x: rect.x + rect.w / 2, y: rect.y + rect.h / 2 })\r\n\r\n/** How far the cursor is from a target: the gap between the rectangles, plus a small centre term to break ties. */\r\nexport const rectDistance = (a: CellRect, b: CellRect): number => {\r\n const ca = center(a)\r\n const cb = center(b)\r\n const gapX = Math.max(0, Math.abs(ca.x - cb.x) - (a.w + b.w) / 2 + OVERHANG_PX)\r\n const gapY = Math.max(0, Math.abs(ca.y - cb.y) - (a.h + b.h) / 2 + OVERHANG_PX)\r\n return Math.hypot(gapX, gapY) + 0.01 * Math.hypot(ca.x - cb.x, ca.y - cb.y)\r\n}\r\n\r\nconst pointOffset = (point: Point, rect: CellRect) => ({\r\n dx: Math.max(0, rect.x - point.x, point.x - (rect.x + rect.w)),\r\n dy: Math.max(0, rect.y - point.y, point.y - (rect.y + rect.h)),\r\n})\r\n\r\nexport const pointGoal = (point: Point): CursorGoal => ({ kind: 'point', rect: { x: point.x, y: point.y, w: 1, h: 1 } })\r\n\r\n/** Whether the cursor rectangle has reached the goal. */\r\nexport const goalReached = (goal: CursorGoal, cursor: CellRect, metrics: M8CellMetrics): boolean => {\r\n if (goal.kind === 'cell') return cellId(cursor) === goal.id || covers(cursor, goal.rect, metrics)\r\n return covers(cursor, goal.rect, metrics)\r\n}\r\n\r\n/**\r\n * What a click can go to: the learned cells with text on screen, and the runs of the text they do\r\n * not cover (an FX value stays a candidate once only its command is learned).\r\n */\r\nexport const candidates = (\r\n layout: CursorLayout | undefined,\r\n lines: readonly string[],\r\n metrics: M8CellMetrics,\r\n panelColumn: number | null,\r\n grid: GridRows | null = null,\r\n): { cells: { id: string; rect: CellRect }[]; runs: TextRun[] } => {\r\n // On grid views only the numbered rows hold cells (not the headers or the title)\r\n const inGrid = (row: number) => !grid || (row >= grid.first && row <= grid.last)\r\n const cells: { id: string; rect: CellRect }[] = []\r\n for (const [id, rect] of layout?.cells ?? []) {\r\n if (inGrid(rowOf(rect, metrics)) && cellHasText(rect, lines, metrics)) cells.push({ id, rect })\r\n }\r\n const covered = (row: number, col: number) => {\r\n const char = charRect(row, col, metrics)\r\n return cells.some((cell) => covers(cell.rect, char, metrics))\r\n }\r\n // The row numbers are not fields either\r\n const isRowNumber = (run: TextRun) => !!grid && run.col === 1 && run.text.length <= grid.numberWidth\r\n return { cells, runs: textRuns(lines, metrics, panelColumn, covered).filter((run) => inGrid(run.row) && !isRowNumber(run)) }\r\n}\r\n\r\n/** The candidate nearest to `point`, or a point goal when the screen offers none. */\r\nexport const pickGoal = (\r\n point: Point,\r\n layout: CursorLayout | undefined,\r\n lines: readonly string[],\r\n metrics: M8CellMetrics,\r\n panelColumn: number | null,\r\n grid: GridRows | null = null,\r\n /** Texts to leave out besides the unreachable ones (those that failed in this navigation). */\r\n skip: ReadonlySet<string> = new Set(),\r\n /** After a text failed: only learned cells and texts beside the click (not the next label down the column). */\r\n knownFirst = false,\r\n): CursorGoal => {\r\n const { cells, runs } = candidates(layout, lines, metrics, panelColumn, grid)\r\n const goals: { goal: CursorGoal; area: CellRect }[] = cells.map((cell) => ({ goal: { kind: 'cell', ...cell }, area: cell.rect }))\r\n for (const run of runs) {\r\n // On grid views the text is song data (it differs per song) and every cell is reachable by arrows: a failure there was\r\n // a slow or missed press, so nothing is ever remembered as unreachable\r\n if ((!grid && layout?.unreachable.has(run.key)) || skip.has(run.key)) continue\r\n // Aim at the clicked character of the run, or its end nearest the click\r\n const col = Math.min(run.col + run.text.length - 1, Math.max(run.col, Math.floor((point.x - metrics.offX) / metrics.cellW)))\r\n goals.push({ goal: { kind: 'text', key: run.key, rect: charRect(run.row, col, metrics), run: run.rect, row: run.row }, area: run.rect })\r\n }\r\n\r\n // A candidate of the clicked row is \"beside\" the click when it is close and, when it lies to the right,\r\n // no other text lies between them (the value after the next label belongs to that label). Text between\r\n // the click and a candidate on its left is that candidate's own display ('00C CHROMATIC' is the cell 00).\r\n // On a tie, the candidate on the right wins: a value follows its label.\r\n const line = lines[Math.floor((point.y - metrics.offY) / metrics.cellH)] ?? ''\r\n const textAt = (col: number) => col > 0 && (line[col] ?? ' ') !== ' '\r\n const clickCol = Math.floor((point.x - metrics.offX) / metrics.cellW)\r\n // The end of the clicked text (the click itself when it is on a space). A single space does not end it:\r\n // label words are one space apart ('NOTE PREVIEW'), a label and its value two or more.\r\n let runEnd = clickCol\r\n while (textAt(runEnd) && (textAt(runEnd + 1) || textAt(runEnd + 2))) runEnd += textAt(runEnd + 1) ? 1 : 2\r\n const besideClick = (area: CellRect) => {\r\n const { dx, dy } = pointOffset(point, area)\r\n if (dy > 0 || dx > SAME_ROW_REACH * metrics.cellW) return false\r\n const first = Math.round((area.x - metrics.offX) / metrics.cellW)\r\n if (first <= runEnd) return true\r\n for (let col = runEnd + 1; col < first; col++) if (textAt(col)) return false\r\n return true\r\n }\r\n const anyBeside = goals.some(({ area }) => besideClick(area))\r\n const anyKnown = goals.some(({ goal }) => goal.kind === 'cell')\r\n\r\n let best: { goal: CursorGoal; score: number; tie: number } | null = null\r\n for (const { goal, area } of goals) {\r\n const { dx, dy } = pointOffset(point, area)\r\n if (anyBeside && !besideClick(area)) continue\r\n if (knownFirst && !anyBeside && anyKnown && goal.kind !== 'cell') continue\r\n // A learned cell wins a tie with a text run\r\n const rightOfClick = area.x > point.x\r\n const score = Math.hypot(dx, dy * ROW_WEIGHT) - (goal.kind === 'cell' ? 0.001 : 0) - (rightOfClick ? 0.5 : 0)\r\n const c = center(area)\r\n const tie = Math.hypot(c.x - point.x, c.y - point.y)\r\n if (!best || score < best.score - 1e-6 || (Math.abs(score - best.score) <= 1e-6 && tie < best.tie)) best = { goal, score, tie }\r\n }\r\n return best?.goal ?? pointGoal(point)\r\n}\r\n","export const M8KeyMask = {\n Left: 0b10000000,\n Up: 0b01000000,\n Down: 0b00100000,\n Shift: 0b00010000,\n Play: 0b00001000,\n Right: 0b00000100,\n Opt: 0b00000010,\n Edit: 0b00000001,\n} as const\n\nexport const isLeft = (frame: number) => !!(frame & M8KeyMask.Left)\nexport const isUp = (frame: number) => !!(frame & M8KeyMask.Up)\nexport const isDown = (frame: number) => !!(frame & M8KeyMask.Down)\nexport const isShift = (frame: number) => !!(frame & M8KeyMask.Shift)\nexport const isPlay = (frame: number) => !!(frame & M8KeyMask.Play)\nexport const isRight = (frame: number) => !!(frame & M8KeyMask.Right)\nexport const isOpt = (frame: number) => !!(frame & M8KeyMask.Opt)\nexport const isEdit = (frame: number) => !!(frame & M8KeyMask.Edit)\n\nexport const pressKeys = ({\n left,\n right,\n up,\n down,\n shift,\n play,\n opt,\n edit,\n}: {\n left?: boolean\n right?: boolean\n up?: boolean\n down?: boolean\n shift?: boolean\n play?: boolean\n opt?: boolean\n edit?: boolean\n}) => {\n let state = 0x00\n if (left) {\n state |= M8KeyMask.Left\n }\n if (right) {\n state |= M8KeyMask.Right\n }\n if (up) {\n state |= M8KeyMask.Up\n }\n if (down) {\n state |= M8KeyMask.Down\n }\n if (shift) {\n state |= M8KeyMask.Shift\n }\n if (play) {\n state |= M8KeyMask.Play\n }\n if (opt) {\n state |= M8KeyMask.Opt\n }\n if (edit) {\n state |= M8KeyMask.Edit\n }\n return state\n}\n","// Moves the M8 cursor to a point on the screen with arrow presses. The point is first turned into\r\n// a goal cell (cursorTargets.ts: the nearest learned cursor cell or text run), so a click between\r\n// cells goes to the nearest one. Each key is pressed alone and its effect checked on the cursor\r\n// rectangle; what it did is learned (cursorMap.ts) per layout, and later moves follow the shortest\r\n// learned path. Plain arrow presses made outside a navigation (the user's own) are learned too.\r\n// A text the cursor cannot reach (a label) is remembered and the next nearest cell is tried; when\r\n// nothing works the cursor goes to the learned cell nearest the click, checking each step. On the\r\n// Song, Chain and Phrase grids the cells and moves are known (fixedGridLayout) and nothing is learned.\r\n\r\nimport type { EngineBus } from './bus'\r\nimport { type CellRect, type CursorDir, type CursorLayout, type CursorMap, cellId, createCursorMap, findPath, wentBackwards } from './cursorMap'\r\nimport {\r\n type CursorGoal,\r\n candidates,\r\n covers,\r\n coversX,\r\n coversY,\r\n cellHasText,\r\n fixedGridLayout,\r\n goalReached,\r\n type GridRows,\r\n gridRows,\r\n hasFixedGrid,\r\n layoutSignature,\r\n panelColumnFor,\r\n pickGoal,\r\n type Point,\r\n pointGoal,\r\n rectDistance,\r\n rowOf,\r\n snapToGrid,\r\n} from './cursorTargets'\r\nimport { M8KeyMask } from './keys'\r\nimport type { KeyCommand } from './protocol'\r\nimport type { M8Store } from './store'\r\n\r\nexport type { Point } from './cursorTargets'\r\n\r\nconst keyMap: Record<CursorDir, number> = {\r\n up: M8KeyMask.Up,\r\n down: M8KeyMask.Down,\r\n left: M8KeyMask.Left,\r\n right: M8KeyMask.Right,\r\n}\r\nconst DIRECTION_MASK = M8KeyMask.Up | M8KeyMask.Down | M8KeyMask.Left | M8KeyMask.Right\r\nconst MODIFIER_MASK = M8KeyMask.Edit | M8KeyMask.Opt | M8KeyMask.Shift | M8KeyMask.Play\r\n// Never kept down during a move: Edit+arrow edits the value, Option+arrow pages, Shift+arrow changes view. The\r\n// M8 does not always echo a release (an Edit that opens another view), so the last echo can still show one.\r\nconst NOT_HELD_MASK = DIRECTION_MASK | M8KeyMask.Edit | M8KeyMask.Opt | M8KeyMask.Shift\r\n\r\n// Key presses per navigation, and how many of them may be tries of moves not learned yet\r\nconst MAX_STEPS = 48\r\nconst MAX_EXPLORE = 40\r\n// Key presses allowed to go back to the closest cell after a goal turned out unreachable\r\nconst MAX_RETURN_STEPS = 16\r\n// Text runs tried in turn when the nearest one is a label the cursor cannot reach\r\nconst MAX_GOALS = 4\r\n// No cursor move this long after the key echo: probably blocked. It is only learned as blocked when the\r\n// cursor still has not moved after the grace: a busy M8 (playing, redrawing) can answer late, and a key\r\n// wrongly learned as blocked would hide the cells behind it.\r\nconst MOVE_TIMEOUT_MS = 150\r\nconst MOVE_GRACE_MS = 300\r\n// A move drawn this long after the release of the user's own arrow press still belongs to it\r\nconst PASSIVE_SETTLE_MS = 200\r\nconst DEBUG_NAVIGATOR = false\r\n\r\nconst log = (message: string, data?: unknown) => {\r\n if (!DEBUG_NAVIGATOR) return\r\n if (data !== undefined) console.log(`[cursorNavigator] ${message}`, data)\r\n else console.log(`[cursorNavigator] ${message}`)\r\n}\r\n\r\nconst sameRect = (a: CellRect | null, b: CellRect | null) => !!a && !!b && a.x === b.x && a.y === b.y && a.w === b.w && a.h === b.h\r\n\r\nconst singleDirection = (keys: number): CursorDir | null => {\r\n if (keys & MODIFIER_MASK) return null\r\n const dirs = (Object.keys(keyMap) as CursorDir[]).filter((dir) => keys & keyMap[dir])\r\n return dirs.length === 1 ? dirs[0] : null\r\n}\r\n\r\nexport interface CursorNavigator {\r\n /**\r\n * Moves the cursor to the cell at or nearest to `target` (M8 pixel coordinates). Resolves true\r\n * when it got there, false when it stopped on the closest cell it could reach (or was cancelled).\r\n */\r\n navigateTo(target: Point): Promise<boolean>\r\n dispose(): void\r\n}\r\n\r\nexport interface CursorNavigatorDeps {\r\n bus: EngineBus\r\n store: M8Store\r\n /** The screen text (one string per row, columns kept), used to find the cells near a click. */\r\n getScreenLines?: () => string[]\r\n /** Where the learned layouts are kept; a new empty map by default. */\r\n cursorMap?: CursorMap\r\n}\r\n\r\ntype PendingMove = {\r\n from: CellRect\r\n dir: CursorDir\r\n /** Cell the map expects (undefined: no expectation). */\r\n expected: string | null | undefined\r\n /** The expectation is a guess, not a learned move: a miss is only learned, not distrusted. */\r\n guessed: boolean\r\n /** The first timeout passed: waiting the grace before learning the key as blocked. */\r\n graced?: boolean\r\n}\r\n\r\ntype Run = {\r\n point: Point\r\n /** First and last numbered rows on grid views: the cursor never leaves them. */\r\n grid: GridRows | null\r\n /** The known grid of a Song, Chain or Phrase view, planned over instead of the learned layout. */\r\n fixed: CursorLayout | null\r\n goal: CursorGoal\r\n goals: number\r\n phase: 'seek' | 'return'\r\n viewName: string | null\r\n /** Null while nothing may be learned (selection mode, unknown view). */\r\n layoutKey: string | null\r\n steps: number\r\n explored: number\r\n /** `\"<cellId>:<dir>\"` pressed in this phase. */\r\n tried: Set<string>\r\n /** Presses sent again after the M8 did not answer them (grid views). */\r\n retried: Set<string>\r\n /** Learned moves that did something else during this run. */\r\n distrust: Set<string>\r\n visited: Map<string, CellRect>\r\n /** Texts that failed in this navigation. */\r\n failedTexts: Set<string>\r\n pending: PendingMove | null\r\n resolve: (reached: boolean) => void\r\n}\r\n\r\nexport const createCursorNavigator = ({\r\n bus,\r\n store,\r\n getScreenLines = () => [],\r\n cursorMap = createCursorMap(),\r\n}: CursorNavigatorDeps): CursorNavigator => {\r\n let run: Run | null = null\r\n let currentKeysMask = 0\r\n let holdMask = 0\r\n let waitingForKeyAck = false\r\n let moveTimeout: ReturnType<typeof setTimeout> | null = null\r\n // The user's own arrow press being learned\r\n let passive: { dir: CursorDir; from: CellRect | null; layoutKey: string | null; releasedAt: number | null } | null = null\r\n\r\n const metrics = () => store.get('cellMetrics')\r\n const panelColumn = () => panelColumnFor(store.get('systemInfo')?.screenWidth ?? 480, metrics())\r\n\r\n // The cursor rectangle snapped to the character grid: the M8 draws the same cell a pixel or two\r\n // differently at times (x 332 w 28 h 16, then x 333 w 26 h 14), which must stay one cell\r\n const getRect = (): CellRect | null => {\r\n const current = store.get('cursorRect')\r\n return current ? snapToGrid(current, metrics()) : null\r\n }\r\n\r\n // Layouts are told apart by view, model, font mode and the row labels on screen\r\n const layoutKey = (): string | null => {\r\n const viewName = store.get('viewName')\r\n if (!viewName || store.get('selectionMode')) return null\r\n const system = store.get('systemInfo')\r\n return [\r\n viewName,\r\n system?.model ?? store.get('deviceModel') ?? '?',\r\n system?.fontMode ?? store.get('fontMode') ?? '?',\r\n layoutSignature(getScreenLines(), panelColumn()),\r\n ].join('|')\r\n }\r\n\r\n const clearMoveTimeout = () => {\r\n if (moveTimeout) {\r\n clearTimeout(moveTimeout)\r\n moveTimeout = null\r\n }\r\n }\r\n\r\n const scheduleMoveTimeout = (ms = MOVE_TIMEOUT_MS) => {\r\n clearMoveTimeout()\r\n moveTimeout = setTimeout(() => {\r\n moveTimeout = null\r\n log('move timeout fired', { waitingForKeyAck })\r\n advance('timeout')\r\n }, ms)\r\n }\r\n\r\n const finish = (reached: boolean) => {\r\n if (!run) return\r\n const { resolve } = run\r\n log('finish', { reached, goal: run.goal, rect: getRect() })\r\n run = null\r\n clearMoveTimeout()\r\n waitingForKeyAck = false\r\n holdMask = currentKeysMask & ~NOT_HELD_MASK\r\n resolve(reached)\r\n }\r\n\r\n const tap = (dir: CursorDir) => {\r\n bus.commands.sendKeys(holdMask | keyMap[dir])\r\n bus.commands.sendKeys(holdMask)\r\n }\r\n\r\n const isGoalCell = (r: Run, id: string, rect: CellRect | undefined) => {\r\n if (r.goal.kind === 'cell') return id === r.goal.id\r\n return !!rect && goalReached(r.goal, rect, metrics())\r\n }\r\n\r\n // Arrow keys that bring the cursor closer to the goal on an axis it does not cover yet, vertical first\r\n // (vertical moves keep the column on the M8's grid views).\r\n const towardGoal = (rect: CellRect, goal: CursorGoal): CursorDir[] => {\r\n const dirs: CursorDir[] = []\r\n const cy = rect.y + rect.h / 2\r\n const cx = rect.x + rect.w / 2\r\n if (!coversY(rect, goal.rect, metrics())) dirs.push(goal.rect.y + goal.rect.h / 2 < cy ? 'up' : 'down')\r\n if (!coversX(rect, goal.rect, metrics())) dirs.push(goal.rect.x + goal.rect.w / 2 < cx ? 'left' : 'right')\r\n return dirs\r\n }\r\n\r\n type Move = { dir: CursorDir; expected: string | null | undefined; guessed: boolean }\r\n\r\n // Up on a grid's first row or Down on its last would scroll or change the phrase or chain shown\r\n const leavesGrid = (r: Run, rect: CellRect, dir: CursorDir) => {\r\n if (!r.grid) return false\r\n const row = rowOf(rect, metrics())\r\n return (dir === 'up' && row <= r.grid.first) || (dir === 'down' && row >= r.grid.last)\r\n }\r\n\r\n const planLayout = (r: Run) => r.fixed ?? (r.layoutKey ? cursorMap.get(r.layoutKey) : undefined)\r\n\r\n const chooseMove = (r: Run, rect: CellRect): Move | null => {\r\n const id = cellId(rect)\r\n const layout = planLayout(r)\r\n\r\n if (layout) {\r\n const path = findPath(\r\n layout,\r\n id,\r\n (cell) => isGoalCell(r, cell, layout.cells.get(cell)),\r\n r.distrust,\r\n (cell, dir) => leavesGrid(r, cell, dir),\r\n )\r\n const first = path?.[0]\r\n if (first && !(first.guessed && (r.explored >= MAX_EXPLORE || r.tried.has(`${id}:${first.dir}`)))) {\r\n return { dir: first.dir, expected: first.to, guessed: first.guessed }\r\n }\r\n }\r\n\r\n const known = layout?.moves.get(id) ?? {}\r\n const here = rectDistance(rect, r.goal.rect)\r\n // A key learned as blocked is tried again when nothing else is left: it may have been a late answer\r\n let retry: Move | null = null\r\n for (const dir of towardGoal(rect, r.goal)) {\r\n const move = `${id}:${dir}`\r\n if (r.tried.has(move) || leavesGrid(r, rect, dir)) continue\r\n const to = r.distrust.has(move) ? undefined : known[dir]\r\n if (to === null) {\r\n retry ??= { dir, expected: null, guessed: false }\r\n continue\r\n }\r\n if (to !== undefined) {\r\n // Learned, but no learned path goes on from it: take it only when it gets closer\r\n const toRect = layout?.cells.get(to)\r\n if (toRect && !wentBackwards(rect, dir, toRect) && rectDistance(toRect, r.goal.rect) < here) return { dir, expected: to, guessed: false }\r\n continue\r\n }\r\n if (r.explored >= MAX_EXPLORE) return null\r\n return { dir, expected: undefined, guessed: true }\r\n }\r\n return retry\r\n }\r\n\r\n const press = (r: Run, rect: CellRect, move: Move) => {\r\n tap(move.dir)\r\n r.steps += 1\r\n if (move.guessed) r.explored += 1\r\n r.tried.add(`${cellId(rect)}:${move.dir}`)\r\n r.pending = { from: rect, dir: move.dir, expected: move.expected, guessed: move.guessed }\r\n waitingForKeyAck = true\r\n log('step sent', { dir: move.dir, expected: move.expected, rect })\r\n scheduleMoveTimeout()\r\n }\r\n\r\n // The goal cannot be reached: go back to the visited cell closest to it, then stop\r\n const giveUp = (r: Run, rect: CellRect, localMinimum: boolean) => {\r\n if (r.phase === 'seek') {\r\n // On another part of the same text (two cells drawn as one run): that is the field\r\n if (r.goal.kind === 'text' && covers(rect, r.goal.run, metrics())) {\r\n finish(true)\r\n return\r\n }\r\n if (localMinimum && r.goal.kind === 'text' && r.layoutKey) {\r\n // Not reachable from here: on the goal's row, the texts past the cursor on the goal's side as well\r\n // (labels under or beside values); the whole row when the cursor is next to it and the key toward\r\n // it is blocked (a header); else only the goal (on views where Up/Down jump columns, a row can be\r\n // passed by). Marked once, they are only skipped for this navigation; a second failure in another\r\n // one makes them unreachable.\r\n const { row, run } = r.goal\r\n const onRow = coversY(rect, run, metrics())\r\n const leftOfCursor = run.x + run.w / 2 < rect.x + rect.w / 2\r\n const cursorRow = rowOf(rect, metrics())\r\n const towardRow = row < cursorRow ? 'up' : 'down'\r\n const blockedNextToRow = Math.abs(row - cursorRow) === 1 && planLayout(r)?.moves.get(cellId(rect))?.[towardRow] === null\r\n const keys =\r\n onRow || blockedNextToRow\r\n ? candidates(planLayout(r), getScreenLines(), metrics(), panelColumn(), r.grid)\r\n .runs.filter((text) => text.row === row)\r\n .filter((text) => !onRow || (leftOfCursor ? text.rect.x + text.rect.w <= rect.x + 3 : text.rect.x >= rect.x + rect.w - 3))\r\n .map((text) => text.key)\r\n : []\r\n if (!keys.includes(r.goal.key)) keys.push(r.goal.key)\r\n for (const key of keys) {\r\n r.failedTexts.add(key)\r\n if (!r.grid) cursorMap.markUnreachable(r.layoutKey, key)\r\n }\r\n const next = pickGoal(r.point, planLayout(r), getScreenLines(), metrics(), panelColumn(), r.grid, r.failedTexts, true)\r\n if (r.goals < MAX_GOALS && next.kind !== 'point' && next.rect !== r.goal.rect) {\r\n log('text not reachable, trying the next nearest cell', { from: r.goal, to: next })\r\n r.goal = next\r\n r.goals += 1\r\n r.explored = 0\r\n r.tried.clear()\r\n step(r)\r\n return\r\n }\r\n }\r\n // Out of goals: the learned cell nearest the click, else the visited cell nearest the goal\r\n const clicked = { x: r.point.x, y: r.point.y, w: 1, h: 1 }\r\n const known = candidates(planLayout(r), getScreenLines(), metrics(), panelColumn(), r.grid).cells\r\n let best = rect\r\n if (known.length > 0) {\r\n for (const cell of known) if (rectDistance(cell.rect, clicked) < rectDistance(best, clicked) - 1e-6) best = cell.rect\r\n } else {\r\n for (const cell of r.visited.values()) if (rectDistance(cell, r.goal.rect) < rectDistance(best, r.goal.rect) - 1e-6) best = cell\r\n }\r\n if (!sameRect(best, rect)) {\r\n log('goal not reached, going to the closest cell', { goal: r.goal, best })\r\n r.phase = 'return'\r\n r.goal = { kind: 'cell', id: cellId(best), rect: best }\r\n r.steps = 0\r\n r.explored = 0\r\n r.tried.clear()\r\n step(r)\r\n return\r\n }\r\n }\r\n finish(false)\r\n }\r\n\r\n function step(r: Run) {\r\n const rect = getRect()\r\n if (!rect) {\r\n finish(false)\r\n return\r\n }\r\n if (goalReached(r.goal, rect, metrics())) {\r\n finish(r.phase === 'seek')\r\n return\r\n }\r\n if (r.steps >= (r.phase === 'seek' ? MAX_STEPS : MAX_RETURN_STEPS)) {\r\n giveUp(r, rect, false)\r\n return\r\n }\r\n const move = chooseMove(r, rect)\r\n if (!move) {\r\n giveUp(r, rect, r.explored < MAX_EXPLORE)\r\n return\r\n }\r\n press(r, rect, move)\r\n }\r\n\r\n // A pressed key moved the cursor to `to`, or did not move it (`to` null)\r\n const settle = (r: Run, pending: PendingMove, to: CellRect | null) => {\r\n r.pending = null\r\n clearMoveTimeout()\r\n waitingForKeyAck = false\r\n const key = layoutKey()\r\n if (r.layoutKey && key === r.layoutKey && !r.fixed) cursorMap.recordMove(r.layoutKey, pending.from, pending.dir, to)\r\n const toId = to ? cellId(to) : null\r\n if (!pending.guessed && pending.expected !== undefined && toId !== pending.expected) {\r\n log('learned move did something else', { pending, to })\r\n r.distrust.add(`${cellId(pending.from)}:${pending.dir}`)\r\n }\r\n if (to) r.visited.set(cellId(to), to)\r\n }\r\n\r\n function advance(reason: 'rect' | 'timeout') {\r\n const r = run\r\n if (!r) return\r\n const rect = getRect()\r\n if (!rect) {\r\n finish(false)\r\n return\r\n }\r\n if (r.pending) {\r\n if (!sameRect(r.pending.from, rect)) settle(r, r.pending, rect)\r\n else if (reason === 'timeout' && !r.pending.graced) {\r\n r.pending.graced = true\r\n scheduleMoveTimeout(MOVE_GRACE_MS)\r\n return\r\n } else if (reason === 'timeout') {\r\n // The M8 drops keys pressed right after a view change, and on a grid view no key is ever blocked\r\n // inside the rows: press once more before the key is learned as blocked.\r\n const move = `${cellId(r.pending.from)}:${r.pending.dir}`\r\n if (r.grid && !r.retried.has(move)) {\r\n log('no move after the grace, pressing again', { move })\r\n r.retried.add(move)\r\n r.tried.delete(move)\r\n r.pending = null\r\n waitingForKeyAck = false\r\n } else settle(r, r.pending, null)\r\n } else return\r\n }\r\n // A plain arrow never changes the view; if it did (a dialog), stop\r\n if (store.get('viewName') !== r.viewName) {\r\n finish(false)\r\n return\r\n }\r\n step(r)\r\n }\r\n\r\n const onKey = (data: KeyCommand) => {\r\n currentKeysMask = data.keys ?? 0\r\n if (run) {\r\n passive = null\r\n if (waitingForKeyAck) {\r\n waitingForKeyAck = false\r\n if (run.pending) scheduleMoveTimeout()\r\n }\r\n return\r\n }\r\n holdMask = currentKeysMask & ~NOT_HELD_MASK\r\n const dir = singleDirection(currentKeysMask)\r\n if (dir) {\r\n if (!passive || passive.dir !== dir || passive.releasedAt !== null) passive = { dir, from: getRect(), layoutKey: layoutKey(), releasedAt: null }\r\n } else if (currentKeysMask === 0 && passive) {\r\n passive.releasedAt ??= Date.now()\r\n } else {\r\n passive = null\r\n }\r\n }\r\n\r\n const onRect = () => {\r\n if (run) {\r\n advance('rect')\r\n return\r\n }\r\n const rect = getRect()\r\n const key = layoutKey()\r\n // Nothing to learn on the known grids\r\n if (!rect || !key || hasFixedGrid(store.get('viewName'))) {\r\n passive = null\r\n return\r\n }\r\n // Only moves made by an arrow are learned: a rectangle drawn while the view changes can be the old view's\r\n const p = passive\r\n if (p?.from && p.layoutKey === key && (p.releasedAt === null || Date.now() - p.releasedAt <= PASSIVE_SETTLE_MS)) {\r\n const lines = getScreenLines()\r\n if (!sameRect(p.from, rect) && cellHasText(p.from, lines, metrics()) && cellHasText(rect, lines, metrics())) {\r\n cursorMap.recordMove(key, p.from, p.dir, rect)\r\n }\r\n // Auto-repeat goes on from here\r\n p.from = rect\r\n } else {\r\n passive = null\r\n }\r\n }\r\n\r\n bus.protocol.eventBus.on('key', onKey)\r\n const unsubscribeRect = store.sub('cursorRect', onRect)\r\n\r\n return {\r\n navigateTo: (point: Point): Promise<boolean> =>\r\n new Promise((resolve) => {\r\n const rect = getRect()\r\n if (!store.get('cursorPos') || !rect) {\r\n resolve(false)\r\n return\r\n }\r\n\r\n // A move still on its way belongs to the previous navigation: wait for it before planning\r\n const carried = run?.pending ?? null\r\n if (run) {\r\n const previous = run\r\n run = null\r\n previous.resolve(false)\r\n }\r\n\r\n const key = layoutKey()\r\n const lines = getScreenLines()\r\n const grid = gridRows(store.get('viewName'), lines)\r\n // The known grid, unless the cursor is on none of its cells (another model or firmware): then it is learned\r\n const known = key ? fixedGridLayout(store.get('viewName'), grid, metrics()) : null\r\n const fixed = known?.cells.has(cellId(rect)) ? known : null\r\n const layout = fixed ?? (key ? cursorMap.get(key) : undefined)\r\n const goal = store.get('selectionMode') ? pointGoal(point) : pickGoal(point, layout, lines, metrics(), panelColumn(), grid)\r\n if (key && !fixed && cellHasText(rect, lines, metrics())) cursorMap.recordCell(key, rect)\r\n if (!carried) holdMask = currentKeysMask & ~NOT_HELD_MASK\r\n passive = null\r\n log('navigateTo start', { point, goal })\r\n\r\n const r: Run = {\r\n point,\r\n grid,\r\n fixed,\r\n goal,\r\n goals: 1,\r\n phase: 'seek',\r\n viewName: store.get('viewName'),\r\n layoutKey: key,\r\n steps: 0,\r\n explored: 0,\r\n tried: new Set(),\r\n retried: new Set(),\r\n distrust: new Set(),\r\n visited: new Map([[cellId(rect), rect]]),\r\n failedTexts: new Set(),\r\n pending: carried ? { ...carried, expected: undefined, guessed: true } : null,\r\n resolve,\r\n }\r\n run = r\r\n if (carried) {\r\n if (!moveTimeout) scheduleMoveTimeout()\r\n return\r\n }\r\n step(r)\r\n }),\r\n dispose: () => {\r\n finish(false)\r\n bus.protocol.eventBus.off('key', onKey)\r\n unsubscribeRect()\r\n },\r\n }\r\n}\r\n","import type { M8State } from '../../types'\n\n// Pure helpers to read the M8 file browser (load/save views) from screen snapshots.\n\nexport const normalizeForSearch = (text: string | null): string => {\n return text?.trim().toLowerCase() ?? ''\n}\n\n// M8 browser files use short extensions such as .MBN, .MNS, .MNT...\n// Accept \".M\" followed by two alphanumeric characters so base-name matching\n// works across file browser types without hardcoding each one.\nexport const FILE_BROWSER_EXTENSION_SUFFIX_REGEX = /\\.m[a-z0-9]{2}$/i\n\nexport type FileBrowserSnapshot = {\n viewName: string | null\n viewTitle: string | null\n cursorPos: M8State['cursorPos']\n textUnderCursor: string | null\n currentLine: string | null\n}\n\nexport const getFileBrowserSnapshotFingerprint = (snapshot: FileBrowserSnapshot): string => {\n return [\n snapshot.viewName ?? '',\n snapshot.viewTitle ?? '',\n snapshot.cursorPos?.x ?? '',\n snapshot.cursorPos?.y ?? '',\n snapshot.textUnderCursor ?? '',\n snapshot.currentLine ?? '',\n ].join('|')\n}\n\nexport const getSelectedFileBrowserEntry = (snapshot: FileBrowserSnapshot): string | null => {\n return snapshot.textUnderCursor?.trim() || snapshot.currentLine?.trim() || null\n}\n\nexport const isFileBrowserView = (snapshot: Pick<FileBrowserSnapshot, 'viewName' | 'viewTitle'>): boolean => {\n const normalizedViewName = normalizeForSearch(snapshot.viewName)\n const normalizedViewTitle = normalizeForSearch(snapshot.viewTitle)\n return (\n normalizedViewName.includes('load') ||\n normalizedViewName.includes('save') ||\n normalizedViewTitle.includes('load') ||\n normalizedViewTitle.includes('save')\n )\n}\n\nexport const isParentDirectoryEntry = (entry: string | null): boolean => normalizeForSearch(entry) === '/..'\nexport const isDirectoryEntry = (entry: string | null): boolean => !!entry?.trim().startsWith('/')\nexport const isM8FileEntry = (entry: string | null): boolean =>\n !!entry && !isDirectoryEntry(entry) && FILE_BROWSER_EXTENSION_SUFFIX_REGEX.test(entry.trim())\nexport const getFileEntrySearchTerms = (entry: string | null): string[] => {\n const trimmedEntry = entry?.trim() ?? ''\n if (!trimmedEntry) return []\n\n const normalizedFullName = normalizeForSearch(trimmedEntry)\n const normalizedBaseName = normalizeForSearch(trimmedEntry.replace(FILE_BROWSER_EXTENSION_SUFFIX_REGEX, ''))\n return Array.from(new Set([normalizedFullName, normalizedBaseName].filter(Boolean)))\n}\n\n// --- Whole-screen reading -------------------------------------------------------------\n// The browser lists one entry per screen row, so a single screen read shows the visible\n// window of the current directory: directories (\"/name\"), the parent entry (\"/..\") and\n// files (\"name.m8s\"). Only entries with an M8 extension can be a search result; the rest\n// (\"other\") is still a row the cursor can stand on.\n\nexport type FileBrowserEntryKind = 'parent' | 'dir' | 'file' | 'other'\n\nexport type FileBrowserScreenEntry = {\n /** Screen row, equal to the cursor grid row when the cursor stands on it. */\n row: number\n text: string\n /** Normalized text, unique inside a directory. */\n key: string\n kind: FileBrowserEntryKind\n}\n\nexport type FileBrowserScreen = {\n entries: FileBrowserScreenEntry[]\n cursorRow: number | null\n cursorKey: string | null\n /** The \"/..\" entry is visible, so the top of the directory is on screen. */\n hasParent: boolean\n /** False when the cursor row disagrees with the cursor atoms: the screen is mid-redraw. */\n consistent: boolean\n}\n\nexport const PARENT_ENTRY_KEY = '/..'\n\nexport const classifyFileBrowserEntry = (text: string): FileBrowserEntryKind => {\n if (isParentDirectoryEntry(text)) return 'parent'\n // A directory name never contains a slash, so \"/a/b\" is a path shown in a header.\n if (isDirectoryEntry(text)) return /^\\/[^/]+$/.test(text.trim()) ? 'dir' : 'other'\n return isM8FileEntry(text) ? 'file' : 'other'\n}\n\nexport const parseFileBrowserScreen = (\n lines: string[],\n snapshot: Pick<FileBrowserSnapshot, 'cursorPos' | 'viewTitle' | 'textUnderCursor'>,\n): FileBrowserScreen => {\n const rowText = (row: number): string => lines[row]?.trim() ?? ''\n const cursorRow = snapshot.cursorPos?.y ?? null\n const title = normalizeForSearch(snapshot.viewTitle)\n\n // The list is the run of non-empty rows around the cursor; anything separated from it\n // by a blank row (footer, hints) is not part of it.\n let first = 0\n let last = lines.length - 1\n if (cursorRow !== null && rowText(cursorRow)) {\n first = cursorRow\n while (first > 0 && rowText(first - 1)) first -= 1\n last = cursorRow\n while (last < lines.length - 1 && rowText(last + 1)) last += 1\n }\n\n const entries: FileBrowserScreenEntry[] = []\n for (let row = first; row <= last; row += 1) {\n const text = rowText(row)\n if (!text || (title && normalizeForSearch(text) === title)) continue\n entries.push({ row, text, key: normalizeForSearch(text), kind: classifyFileBrowserEntry(text) })\n }\n\n const cursorText = snapshot.textUnderCursor?.trim()\n return {\n entries,\n cursorRow,\n cursorKey: cursorRow === null ? null : (entries.find((entry) => entry.row === cursorRow)?.key ?? null),\n hasParent: entries.some((entry) => entry.kind === 'parent'),\n consistent: !cursorText || cursorRow === null || rowText(cursorRow).includes(cursorText),\n }\n}\n\n/**\n * Merge the entry keys of one screen into the display order accumulated for a directory.\n * Screens overlap, so the first known entry anchors the new ones. When the screen\n * contradicts the accumulated order (listing re-sorted or reloaded) the order restarts from\n * the screen and `consistent` is false.\n */\nexport const mergeFileBrowserOrder = (order: string[], screen: string[]): { order: string[]; consistent: boolean } => {\n if (screen.length === 0) return { order, consistent: true }\n if (order.length === 0) return { order: [...screen], consistent: true }\n\n const anchor = screen.findIndex((key) => order.includes(key))\n if (anchor === -1) return { order: [...screen], consistent: false }\n\n const start = order.indexOf(screen[anchor]) - anchor\n const merged = start < 0 ? [...new Array<string>(-start).fill(''), ...order] : [...order]\n const offset = Math.max(0, start)\n for (let index = 0; index < screen.length; index += 1) {\n const position = offset + index\n const known = position < merged.length && merged[position] !== ''\n if (known) {\n if (merged[position] !== screen[index]) return { order: [...screen], consistent: false }\n continue\n }\n // A new entry that is already known elsewhere means the listing was reordered.\n if (merged.includes(screen[index])) return { order: [...screen], consistent: false }\n if (position >= merged.length) merged.push(screen[index])\n else merged[position] = screen[index]\n }\n return { order: merged, consistent: true }\n}\n\nconst tokenize = (text: string): string[] => text.split(/[^a-z0-9]+/).filter((token) => token.length >= 3)\nconst TITLE_STOP_WORDS = new Set(['load', 'save', 'select', 'create', 'directory', 'new'])\n\n/** Words of the search target (without its extension) and of the view title, to rank directories. */\nexport const getDirectoryHintTokens = (targetText: string, viewTitle: string | null) => ({\n target: tokenize(normalizeForSearch(targetText).replace(FILE_BROWSER_EXTENSION_SUFFIX_REGEX, '')),\n title: tokenize(normalizeForSearch(viewTitle)).filter((token) => !TITLE_STOP_WORDS.has(token)),\n})\n\n/**\n * 2: the folder name shares a word with the target (\"/Bass\" for \"bassline\"),\n * 1: it shares a word with the view title (\"/Instruments\" in \"LOAD INSTRUMENT\"), else 0.\n * Only used to decide which folder to open first, never to skip one.\n */\nexport const scoreDirectoryRelevance = (directoryKey: string, hints: ReturnType<typeof getDirectoryHintTokens>): number => {\n const name = directoryKey.replace(/^\\//, '')\n if (name.length < 3) return 0\n const related = (token: string) => token.includes(name) || name.includes(token)\n if (hints.target.some(related)) return 2\n if (hints.title.some(related)) return 1\n return 0\n}\n","import type { M8Store } from '../store'\nimport { type FileBrowserSnapshot, getFileBrowserSnapshotFingerprint } from './fileBrowserHelpers'\n\n// Wait for a specific duration\nexport const wait = (ms: number) => new Promise((resolve) => setTimeout(resolve, ms))\n\n/** Where key frames go: a connected bus's commands, or nothing while disconnected. */\nexport type KeySink = { sendKeys: (keys: number) => void }\n\n/**\n * What the value editors and the file browser search need from the engine: key output\n * to the M8 and observation of the screen state.\n */\nexport type HostIO = {\n store: M8Store\n debugLog: (...args: unknown[]) => void\n hasBus: () => boolean\n sendKeys: (keys: number) => void\n pressAndRelease: (keys: number, delayMs?: number) => Promise<void>\n waitForTextUnderCursorChange: (previousValue: string | null, timeoutMs?: number) => Promise<string | null>\n waitForCurrentLineChange: (previousValue: string | null, timeoutMs?: number) => Promise<string | null>\n getFileBrowserSnapshot: () => FileBrowserSnapshot\n waitForFileBrowserSnapshotChange: (previousSnapshot: FileBrowserSnapshot, timeoutMs?: number) => Promise<FileBrowserSnapshot>\n /** Whole M8 screen as text rows (row index === cursor grid row). */\n getScreenLines: () => string[]\n}\n\nexport const createHostIO = ({\n getKeySink,\n store,\n debugLog,\n getScreenLines,\n}: {\n getKeySink: () => KeySink | undefined\n store: M8Store\n debugLog: (...args: unknown[]) => void\n getScreenLines: () => string[]\n}): HostIO => {\n const hasBus = () => !!getKeySink()\n\n // Send keys helper - reads the sink lazily so a reconnected device is picked up\n const sendKeys = (keys: number) => {\n getKeySink()?.sendKeys(keys)\n }\n\n // Send keys and release\n const pressAndRelease = async (keys: number, delayMs: number = 50) => {\n sendKeys(keys)\n await wait(delayMs)\n sendKeys(0)\n await wait(delayMs)\n }\n\n // Prefer atom-change synchronization over fixed sleeps when reading post-key values.\n const waitForTextUnderCursorChange = (previousValue: string | null, timeoutMs: number = 500): Promise<string | null> => {\n return new Promise((resolve) => {\n let settled = false\n let unsubscribe: (() => void) | null = null\n let timeout: ReturnType<typeof setTimeout> | null = null\n\n const finish = (value: string | null) => {\n if (settled) return\n settled = true\n if (timeout !== null) {\n clearTimeout(timeout)\n }\n unsubscribe?.()\n resolve(value)\n }\n\n // If the value already changed before we started waiting (race with key press timing),\n // resolve immediately instead of subscribing and timing out.\n const currentValue = store.get('textUnderCursor')\n if (currentValue !== previousValue) {\n finish(currentValue)\n return\n }\n\n unsubscribe = store.sub('textUnderCursor', () => {\n const next = store.get('textUnderCursor')\n if (next !== previousValue) {\n finish(next)\n }\n })\n\n timeout = setTimeout(() => {\n finish(store.get('textUnderCursor'))\n }, timeoutMs)\n })\n }\n\n const getFileBrowserSnapshot = (): FileBrowserSnapshot => {\n return {\n viewName: store.get('viewName'),\n viewTitle: store.get('viewTitle'),\n cursorPos: store.get('cursorPos'),\n textUnderCursor: store.get('textUnderCursor'),\n currentLine: store.get('currentLine'),\n }\n }\n\n const waitForFileBrowserSnapshotChange = (previousSnapshot: FileBrowserSnapshot, timeoutMs: number = 500): Promise<FileBrowserSnapshot> => {\n return new Promise((resolve) => {\n let settled = false\n let unsubscribeText: (() => void) | null = null\n let unsubscribeLine: (() => void) | null = null\n let unsubscribeCursor: (() => void) | null = null\n let unsubscribeViewName: (() => void) | null = null\n let unsubscribeViewTitle: (() => void) | null = null\n let timeout: ReturnType<typeof setTimeout> | null = null\n const previousFingerprint = getFileBrowserSnapshotFingerprint(previousSnapshot)\n\n const finish = (snapshot: FileBrowserSnapshot) => {\n if (settled) return\n settled = true\n if (timeout !== null) {\n clearTimeout(timeout)\n }\n unsubscribeText?.()\n unsubscribeLine?.()\n unsubscribeCursor?.()\n unsubscribeViewName?.()\n unsubscribeViewTitle?.()\n resolve(snapshot)\n }\n\n const checkForChange = () => {\n const snapshot = getFileBrowserSnapshot()\n if (getFileBrowserSnapshotFingerprint(snapshot) !== previousFingerprint) {\n finish(snapshot)\n }\n }\n\n const currentSnapshot = getFileBrowserSnapshot()\n if (getFileBrowserSnapshotFingerprint(currentSnapshot) !== previousFingerprint) {\n finish(currentSnapshot)\n return\n }\n\n unsubscribeText = store.sub('textUnderCursor', checkForChange)\n unsubscribeLine = store.sub('currentLine', checkForChange)\n unsubscribeCursor = store.sub('cursorPos', checkForChange)\n unsubscribeViewName = store.sub('viewName', checkForChange)\n unsubscribeViewTitle = store.sub('viewTitle', checkForChange)\n\n timeout = setTimeout(() => {\n finish(getFileBrowserSnapshot())\n }, timeoutMs)\n })\n }\n\n // Prefer atom-change synchronization over fixed sleeps when reading post-key values.\n const waitForCurrentLineChange = (previousValue: string | null, timeoutMs: number = 500): Promise<string | null> => {\n return new Promise((resolve) => {\n let settled = false\n let unsubscribe: (() => void) | null = null\n let timeout: ReturnType<typeof setTimeout> | null = null\n\n const finish = (value: string | null) => {\n if (settled) return\n settled = true\n if (timeout !== null) {\n clearTimeout(timeout)\n }\n unsubscribe?.()\n resolve(value)\n }\n\n // If the value already changed before we started waiting (race with key press timing),\n // resolve immediately instead of subscribing and timing out.\n const currentValue = store.get('currentLine')\n if (currentValue !== previousValue) {\n finish(currentValue)\n return\n }\n\n unsubscribe = store.sub('currentLine', () => {\n const next = store.get('currentLine')\n if (next !== previousValue) {\n finish(next)\n }\n })\n\n timeout = setTimeout(() => {\n finish(store.get('currentLine'))\n }, timeoutMs)\n })\n }\n\n return {\n store,\n debugLog,\n hasBus,\n sendKeys,\n pressAndRelease,\n waitForTextUnderCursorChange,\n waitForCurrentLineChange,\n getFileBrowserSnapshot,\n waitForFileBrowserSnapshotChange,\n getScreenLines,\n }\n}\n","import { M8KeyMask } from '../keys'\nimport {\n FILE_BROWSER_EXTENSION_SUFFIX_REGEX,\n type FileBrowserEntryKind,\n type FileBrowserScreen,\n type FileBrowserSnapshot,\n getDirectoryHintTokens,\n getFileBrowserSnapshotFingerprint,\n getFileEntrySearchTerms,\n getSelectedFileBrowserEntry,\n isFileBrowserView,\n isM8FileEntry,\n mergeFileBrowserOrder,\n normalizeForSearch,\n PARENT_ENTRY_KEY,\n parseFileBrowserScreen,\n scoreDirectoryRelevance,\n} from './fileBrowserHelpers'\nimport { type HostIO, wait } from './hostIO'\n\nconst browseLog = (message: string, details?: unknown) => {\n if (details === undefined) {\n console.log('[M8SDK browseFile]', message)\n return\n }\n console.log('[M8SDK browseFile]', message, details)\n}\n\ntype Direction = 'up' | 'down'\ntype SearchOutcome = 'found' | 'missed' | 'aborted'\n\n/** Rows moved by Opt+Up / Opt+Down. */\nconst JUMP_ROWS = 8\n/**\n * A window showing fewer entries than this cannot be scrolled, so it is the whole\n * directory. Deliberately below any real list height (the screen has 24 rows): the\n * actual height is learned from the first scrolled window.\n */\nconst MIN_FULL_WINDOW = 8\nconst MAX_DEPTH = 64\nconst MAX_SCREENS_PER_LEVEL = 512\n\n/** What is known about one directory while it is being searched. */\ntype Level = {\n /** Entry keys in display order, merged from every screen seen. */\n order: string[]\n kinds: Map<string, FileBrowserEntryKind>\n /** Entry text as displayed. */\n names: Map<string, string>\n /** The first / last entry of the directory has been on screen. */\n top: boolean\n bottom: boolean\n /** \"/..\" exists, i.e. this is not the SD card root. */\n hasParent: boolean\n /** Alphabetical sort was applied to this listing and the order seen so far agrees with it. */\n sorted: boolean\n visited: Set<string>\n /** Matches the cursor could not be moved onto. */\n rejected: Set<string>\n}\n\nconst createLevel = (): Level => ({\n order: [],\n kinds: new Map(),\n names: new Map(),\n top: false,\n bottom: false,\n hasParent: false,\n sorted: false,\n visited: new Set(),\n rejected: new Set(),\n})\n\nconst opposite = (direction: Direction): Direction => (direction === 'up' ? 'down' : 'up')\n\n/**\n * Navigate the M8 file browser (load/save views) to a file by name.\n *\n * The whole screen is readable, so every move is decided from what is visible:\n * - a match anywhere on screen is reached directly;\n * - a directory is read page by page (Opt+Up/Down) before any sub-directory is opened,\n * and a window that is not full is the whole directory (nothing to page);\n * - sub-directories are only opened once the files of the level have been checked, and\n * after coming back the cursor already sits on the folder just visited, so the next\n * folder is a few rows away instead of a trip to the top of the list.\n */\nexport const createFileBrowserSearch = (io: HostIO) => {\n const { hasBus, pressAndRelease, getFileBrowserSnapshot, waitForFileBrowserSnapshotChange, getScreenLines } = io\n\n // Layout facts learned from the device, reused by later searches on this host.\n // Rows of a window known to be scrolled (so full), per view.\n const fullWindowRows = new Map<string, number>()\n // Whether Opt+Up/Down moves the remaining rows near an end (true) or refuses to move (false).\n let jumpClampsAtEnd: boolean | null = null\n // Whether a sorted listing puts every folder before the first file. Unknown until a sorted\n // listing shows a folder before a file it would follow alphabetically (true), or a folder\n // after a file (false).\n let foldersFirst: boolean | null = null\n\n const browseFileImpl = async (targetText: string, exact: boolean = true): Promise<boolean> => {\n if (!hasBus()) return false\n\n const normalizedTarget = normalizeForSearch(targetText)\n if (!normalizedTarget) return false\n const targetIncludesExtension = FILE_BROWSER_EXTENSION_SUFFIX_REGEX.test(normalizedTarget)\n const hints = getDirectoryHintTokens(targetText, getFileBrowserSnapshot().viewTitle)\n\n browseLog('start', { targetText, normalizedTarget, exact })\n\n const isActive = (): boolean => isFileBrowserView(getFileBrowserSnapshot())\n\n const stepFileBrowser = async (keyMask: number, timeoutMs: number = 350): Promise<{ changed: boolean; snapshot: FileBrowserSnapshot }> => {\n const previousSnapshot = getFileBrowserSnapshot()\n await pressAndRelease(keyMask, 35)\n const nextSnapshot = await waitForFileBrowserSnapshotChange(previousSnapshot, timeoutMs)\n const changed = getFileBrowserSnapshotFingerprint(nextSnapshot) !== getFileBrowserSnapshotFingerprint(previousSnapshot)\n return { changed, snapshot: nextSnapshot }\n }\n\n const stepMask = (direction: Direction): number => (direction === 'up' ? M8KeyMask.Up : M8KeyMask.Down)\n const jumpMask = (direction: Direction): number => M8KeyMask.Opt | stepMask(direction)\n\n const applyAlphabeticalSort = async (): Promise<void> => {\n const previousSnapshot = getFileBrowserSnapshot()\n browseLog('apply alphabetical sort')\n await pressAndRelease(M8KeyMask.Shift | M8KeyMask.Opt, 35)\n // Sorting the browser can take a moment; give the UI time to settle\n // before relying on the next cursor/text snapshot.\n await wait(180)\n await waitForFileBrowserSnapshotChange(previousSnapshot, 300)\n }\n\n const entryMatchesTarget = (entry: string | null): boolean => {\n if (!isM8FileEntry(entry)) return false\n const searchTerms = getFileEntrySearchTerms(entry)\n return searchTerms.some((term) => (exact ? term === normalizedTarget : term.includes(normalizedTarget)))\n }\n\n // Name of a file as compared with the target: with its extension only if the target has one.\n const comparableName = (entry: string): string => {\n const trimmed = entry.trim()\n return targetIncludesExtension ? trimmed : trimmed.replace(FILE_BROWSER_EXTENSION_SUFFIX_REGEX, '')\n }\n const primarySearchTerm = (entry: string): string => normalizeForSearch(comparableName(entry))\n const typedTarget = comparableName(targetText)\n\n // --- Reading the screen ---------------------------------------------------------\n\n const readScreen = async (): Promise<FileBrowserScreen> => {\n for (let attempt = 0; ; attempt += 1) {\n const screen = parseFileBrowserScreen(getScreenLines(), getFileBrowserSnapshot())\n // Cursor atoms and screen text are updated separately: re-read when they disagree.\n if (screen.consistent || attempt >= 2) return screen\n await wait(40)\n }\n }\n\n // Fold a screen into what is known about the directory.\n const absorb = (level: Level, screen: FileBrowserScreen): void => {\n const keys = screen.entries.map((entry) => entry.key)\n const merged = mergeFileBrowserOrder(level.order, keys)\n level.order = merged.order\n if (!merged.consistent) {\n level.top = false\n level.bottom = false\n }\n for (const entry of screen.entries) {\n level.kinds.set(entry.key, entry.kind)\n level.names.set(entry.key, entry.text)\n }\n\n const viewKey = normalizeForSearch(getFileBrowserSnapshot().viewName)\n if (screen.hasParent) {\n level.hasParent = true\n level.top = true\n } else if (level.hasParent && keys.length > 0) {\n // \"/..\" is above the window: the list is scrolled, so the window is full.\n const known = fullWindowRows.get(viewKey)\n if (known === undefined || keys.length < known) fullWindowRows.set(viewKey, keys.length)\n }\n\n // A window that is not full cannot be scrolled: it already is the whole directory.\n if (keys.length < Math.max(MIN_FULL_WINDOW, fullWindowRows.get(viewKey) ?? 0)) {\n level.top = true\n level.bottom = true\n }\n }\n\n // --- Moving the cursor ----------------------------------------------------------\n\n // Move one page toward `direction`. Returns the new screen, or null at the end of the list.\n const pageLevel = async (level: Level, screen: FileBrowserScreen, direction: Direction): Promise<FileBrowserScreen | null> => {\n const jumped = (await stepFileBrowser(jumpMask(direction), 280)).changed\n let moved = jumped\n let reachedEnd = false\n\n if (!jumped && jumpClampsAtEnd !== true) {\n // Opt+Up/Down may refuse to move when less than a page remains, or the cursor may\n // already be on the last entry. Plain steps tell the two apart and walk the rest of\n // the way; the window follows the cursor, so one read at the end shows it all.\n for (let step = 0; step < JUMP_ROWS; step += 1) {\n if (!(await stepFileBrowser(stepMask(direction), 250)).changed) {\n reachedEnd = true\n break\n }\n moved = true\n jumpClampsAtEnd = false\n }\n } else if (!jumped) {\n reachedEnd = true\n }\n\n if (reachedEnd) {\n if (direction === 'up') level.top = true\n else level.bottom = true\n browseLog('end of list', { direction })\n }\n if (!moved) return null\n\n const next = await readScreen()\n absorb(level, next)\n // Indexes are taken after the merge, which may have inserted entries above.\n const fromIndex = screen.cursorKey ? level.order.indexOf(screen.cursorKey) : -1\n const toIndex = next.cursorKey ? level.order.indexOf(next.cursorKey) : -1\n // A jump that moved fewer rows than a full page was cut short by the end of the list.\n const travelled = Math.abs(toIndex - fromIndex)\n if (jumped && fromIndex >= 0 && toIndex >= 0 && travelled > 0 && travelled < JUMP_ROWS) {\n jumpClampsAtEnd = true\n }\n return next\n }\n\n const directionToward = (level: Level, screen: FileBrowserScreen, key: string): Direction => {\n if (key === PARENT_ENTRY_KEY) return 'up'\n const target = level.order.indexOf(key)\n const cursor = screen.cursorKey ? level.order.indexOf(screen.cursorKey) : -1\n if (target >= 0 && cursor >= 0) return target < cursor ? 'up' : 'down'\n return level.top && !level.bottom ? 'down' : 'up'\n }\n\n // Put the cursor on `key`. Returns the screen it ends on, or null if it cannot get there.\n const moveCursorToKey = async (level: Level, key: string, startScreen: FileBrowserScreen): Promise<FileBrowserScreen | null> => {\n let screen = startScreen\n let direction: Direction | null = null\n let reversed = false\n\n for (let i = 0; i < 96; i += 1) {\n const entry = screen.entries.find((candidate) => candidate.key === key)\n if (entry) {\n if (screen.cursorRow === null) return null\n const delta = entry.row - screen.cursorRow\n if (delta === 0) return screen\n // Screen row delta === rows to travel; jump when far, then fine steps.\n const way: Direction = delta < 0 ? 'up' : 'down'\n const result = await stepFileBrowser(Math.abs(delta) >= JUMP_ROWS ? jumpMask(way) : stepMask(way), 250)\n if (!result.changed) return null\n screen = await readScreen()\n absorb(level, screen)\n continue\n }\n\n // Not on screen: page toward it, turning round once if that end is reached.\n direction ??= directionToward(level, screen, key)\n const next = await pageLevel(level, screen, direction)\n if (next) {\n screen = next\n } else if (reversed) {\n return null\n } else {\n reversed = true\n direction = opposite(direction)\n }\n }\n return null\n }\n\n // --- Searching ------------------------------------------------------------------\n\n // 'failed': a match is visible but the cursor never landed on it (e.g. stale screen).\n const selectVisibleMatch = async (level: Level, screen: FileBrowserScreen): Promise<'found' | 'none' | 'failed'> => {\n const cursorRow = screen.cursorRow ?? 0\n const candidate = screen.entries\n .filter((entry) => entry.kind === 'file' && !level.rejected.has(entry.key) && entryMatchesTarget(entry.text))\n .sort((a, b) => Math.abs(a.row - cursorRow) - Math.abs(b.row - cursorRow))[0]\n if (!candidate) return 'none'\n\n const reached = await moveCursorToKey(level, candidate.key, screen)\n const entry = getSelectedFileBrowserEntry(getFileBrowserSnapshot())\n if (reached && entryMatchesTarget(entry)) {\n browseLog('match found', { entry, y: reached.cursorRow })\n await stepFileBrowser(M8KeyMask.Edit, 500)\n return 'found'\n }\n\n browseLog('match not reachable', { candidate: candidate.text })\n level.rejected.add(candidate.key)\n return 'failed'\n }\n\n // The files of a level in display order, as (case-insensitive, as-displayed) comparable names.\n const levelFiles = (level: Level): { ci: string; raw: string }[] =>\n level.order\n .filter((key) => level.kinds.get(key) === 'file')\n .map((key) => {\n const text = level.names.get(key) ?? ''\n return { ci: primarySearchTerm(text), raw: comparableName(text) }\n })\n\n // Called on a listing we sorted: checks the order is really alphabetical (by name with or\n // without case, since the firmware's rule is not known) and learns where folders go.\n const reviewSortedLevel = (level: Level): void => {\n if (!level.sorted) return\n const files = levelFiles(level)\n if (files.some((file, index) => index > 0 && (files[index - 1].ci > file.ci || files[index - 1].raw > file.raw))) {\n level.sorted = false\n browseLog('listing is not alphabetical, order hints disabled')\n return\n }\n\n const entries = level.order.map((key) => {\n const text = (level.names.get(key) ?? '').trim()\n return { kind: level.kinds.get(key), ci: normalizeForSearch(text).replace(/^\\//, ''), raw: text.replace(/^\\//, '') }\n })\n const firstFile = entries.findIndex((entry) => entry.kind === 'file')\n if (firstFile < 0) return\n\n if (entries.slice(firstFile).some((entry) => entry.kind === 'dir')) {\n foldersFirst = false\n return\n }\n // A folder listed before a file it follows alphabetically cannot come from merging\n // folders and files by name, so folders really are listed first.\n const proof = entries.some(\n (folder, index) =>\n folder.kind === 'dir' && entries.slice(index + 1).some((file) => file.kind === 'file' && folder.ci > file.ci && folder.raw > file.raw),\n )\n if (proof && foldersFirst === null) foldersFirst = true\n }\n\n // The listing is sorted, folders precede files, the top is on screen and a visible file\n // already follows the target alphabetically: whatever is below is a later file, so neither\n // the target nor a folder can be there. Only used when the device has shown folders-first.\n const nothingLeftBelow = (level: Level): boolean => {\n if (!exact || !level.sorted || foldersFirst !== true || !level.top || level.bottom) return false\n return levelFiles(level).some(\n (file) => /^[a-z0-9]/.test(file.ci) && file.ci > normalizedTarget && file.raw > typedTarget && file.raw > normalizedTarget,\n )\n }\n\n // In an alphabetical listing the visible files tell where the target is. This only\n // chooses which end to read first.\n const alphabeticalHint = (level: Level, screen: FileBrowserScreen): Direction | null => {\n if (!exact || !level.sorted) return null\n const visible = screen.entries.filter((entry) => entry.kind === 'file').map((entry) => primarySearchTerm(entry.text))\n const first = visible[0]\n const last = visible[visible.length - 1]\n if (first === undefined || last === undefined) return null\n if (normalizedTarget < first) return 'up'\n if (normalizedTarget > last) return 'down'\n return null\n }\n\n const pickDirection = (level: Level, screen: FileBrowserScreen): Direction => {\n if (level.top !== level.bottom) return level.top ? 'down' : 'up'\n return alphabeticalHint(level, screen) ?? 'up'\n }\n\n // Next folder to open: most relevant by name first, then the nearest to the cursor.\n const pickNextDirectory = (level: Level, screen: FileBrowserScreen): string | null => {\n const cursorIndex = screen.cursorKey ? level.order.indexOf(screen.cursorKey) : -1\n let best: { key: string; rank: number } | null = null\n for (const [key, kind] of level.kinds) {\n if (kind !== 'dir' || level.visited.has(key)) continue\n const onScreen = screen.entries.find((entry) => entry.key === key)\n const index = level.order.indexOf(key)\n const distance =\n onScreen && screen.cursorRow !== null\n ? Math.abs(onScreen.row - screen.cursorRow)\n : 100 + (cursorIndex >= 0 && index >= 0 ? Math.abs(index - cursorIndex) : 500)\n const rank = scoreDirectoryRelevance(key, hints) * 10000 - distance\n if (!best || rank > best.rank) best = { key, rank }\n }\n return best?.key ?? null\n }\n\n // \"/..\" is the first entry of its directory: jump up until it is on screen, then onto it.\n const climbToParent = async (level: Level, screen: FileBrowserScreen): Promise<boolean> => {\n const reached = await moveCursorToKey(level, PARENT_ENTRY_KEY, screen)\n if (!reached) {\n browseLog('parent directory entry unreachable')\n return false\n }\n browseLog('navigate to parent directory')\n const result = await stepFileBrowser(M8KeyMask.Edit, 500)\n return result.changed && isActive()\n }\n\n const searchLevel = async (depth: number): Promise<SearchOutcome> => {\n if (depth > MAX_DEPTH || !isActive()) {\n browseLog('search aborted', { depth, active: isActive() })\n return 'aborted'\n }\n\n const level = createLevel()\n let screen = await readScreen()\n absorb(level, screen)\n browseLog('search directory', { depth, entries: screen.entries.length, complete: level.top && level.bottom })\n\n // A long listing is worth sorting: the visible names then say which end to read first.\n if (exact && !(level.top && level.bottom)) {\n await applyAlphabeticalSort()\n // Whatever was read before the sort is in an order that no longer holds.\n level.order = []\n level.top = false\n level.bottom = false\n level.sorted = true\n screen = await readScreen()\n absorb(level, screen)\n }\n\n // 1. Every file of this level, before going deeper.\n for (let pages = 0; ; pages += 1) {\n const picked = await selectVisibleMatch(level, screen)\n if (picked === 'found') return 'found'\n if (picked === 'failed') {\n screen = await readScreen()\n absorb(level, screen)\n }\n reviewSortedLevel(level)\n if (nothingLeftBelow(level)) {\n browseLog('rest of listing is past the target', { depth })\n level.bottom = true\n }\n if ((level.top && level.bottom) || pages >= MAX_SCREENS_PER_LEVEL) break\n\n const next = await pageLevel(level, screen, pickDirection(level, screen))\n if (next) screen = next\n }\n\n // 2. Sub-directories. The listing is already known, so folders are taken in the\n // order that costs the fewest moves.\n for (;;) {\n const directory = pickNextDirectory(level, screen)\n if (!directory) break\n level.visited.add(directory)\n\n const reached = await moveCursorToKey(level, directory, screen)\n if (!reached) {\n browseLog('directory unreachable', { directory })\n screen = await readScreen()\n absorb(level, screen)\n continue\n }\n\n browseLog('enter directory', { directory, depth })\n const entered = await stepFileBrowser(M8KeyMask.Edit, 500)\n if (!isActive()) return 'aborted'\n if (!entered.changed) {\n screen = await readScreen()\n absorb(level, screen)\n continue\n }\n\n const outcome = await searchLevel(depth + 1)\n if (outcome !== 'missed') return outcome\n\n // Back in this directory with the cursor on the folder just left.\n screen = await readScreen()\n absorb(level, screen)\n }\n\n if (depth === 0) return 'missed'\n // Nothing here: leave straight away, the parent continues with the next folder.\n return (await climbToParent(level, screen)) ? 'missed' : 'aborted'\n }\n\n if (!isActive()) {\n console.warn('[M8SDK] browseFile requires a file browser view (load/save)')\n return false\n }\n if (getScreenLines().length === 0) {\n console.warn('[M8SDK] browseFile needs the screen reader, which is not available')\n return false\n }\n\n const outcome = await searchLevel(0)\n if (outcome !== 'found') browseLog('search failed', { targetText, outcome })\n return outcome === 'found'\n }\n\n return browseFileImpl\n}\n","// Pure text parsers for values rendered on the M8 screen (hex, int, float, note).\n\n// Parse hex value from text (handles formats like \"3F\", \"0x3F\", \"3f\", \"--\")\n// Returns 0 for '--' which represents 00 in the M8 UI\nexport const parseHexValue = (text: string | null): number | null => {\n if (!text) return null\n const cleaned = text.trim()\n // Handle '--' as 00\n if (cleaned === '--') return 0\n const hexCleaned = cleaned.replace(/^0x/i, '')\n const match = hexCleaned.match(/^[0-9A-Fa-f]+/)\n if (!match) return null\n const parsed = parseInt(match[0], 16)\n return Number.isNaN(parsed) ? null : parsed\n}\n\n// Parse integer value from text (handles formats like \"123\", \"-12\", \"+7\", \"--\")\n// Returns 0 for '--' which is commonly used as an empty numeric placeholder in M8 UI\nexport const parseIntValue = (text: string | null): number | null => {\n if (!text) return null\n const cleaned = text.trim()\n if (cleaned === '--') return 0\n const match = cleaned.match(/^[+-]?\\d+/)\n if (!match) return null\n const parsed = Number.parseInt(match[0], 10)\n return Number.isNaN(parsed) ? null : parsed\n}\n\n// Parse every float value token from a full line. M8 float parameters are rendered\n// as two linked groups separated by a dot, e.g. \"TUNE 440.00 G\" or \"C ON-10.69 7 ---\".\n// The cursor can rest either on the integer part (\"440\") or the decimal part (\"00\"),\n// and changing the decimal part past its bounds carries into the integer part\n// (e.g. 1.99 -> 2.00), so the whole signed token behaves like a single number.\nconst FLOAT_TOKEN_REGEX = /[+-]?\\d+\\.\\d+/g\n\nexport type ParsedFloatToken = {\n // Full token as displayed, e.g. \"-10.69\"\n raw: string\n negative: boolean\n intDigits: string\n decDigits: string\n // 10 ** decDigits.length (100 for two displayed decimals)\n decScale: number\n // Smallest displayable step expressed in hundredths (1 for two decimals, 10 for one)\n tickHundredths: number\n // Signed value measured in tick steps: (-10.69 with two decimals) => -1069\n ticks: number\n}\n\nexport const parseFloatTokens = (line: string | null): ParsedFloatToken[] => {\n if (!line) return []\n const tokens: ParsedFloatToken[] = []\n\n FLOAT_TOKEN_REGEX.lastIndex = 0\n let match: RegExpExecArray | null = FLOAT_TOKEN_REGEX.exec(line)\n while (match !== null) {\n const raw = match[0]\n const negative = raw.startsWith('-')\n const unsigned = negative || raw.startsWith('+') ? raw.slice(1) : raw\n const dotIndex = unsigned.indexOf('.')\n const intDigits = unsigned.slice(0, dotIndex)\n const decDigits = unsigned.slice(dotIndex + 1)\n const intValue = Number.parseInt(intDigits || '0', 10)\n const decValue = Number.parseInt(decDigits || '0', 10)\n const decScale = 10 ** decDigits.length\n const tickHundredths = decScale > 0 ? 100 / decScale : 1\n const valueHundredths = intValue * 100 + (decValue * 100) / decScale\n\n if (!Number.isNaN(intValue) && !Number.isNaN(decValue)) {\n tokens.push({\n raw,\n negative,\n intDigits,\n decDigits,\n decScale,\n tickHundredths,\n ticks: (negative ? -1 : 1) * Math.round(valueHundredths / tickHundredths),\n })\n }\n\n match = FLOAT_TOKEN_REGEX.exec(line)\n }\n\n return tokens\n}\n\n// When several float tokens exist on a line, prefer the one the cursor is on,\n// deduced from the highlighted text. The highlight can be the whole float —\n// possibly WITHOUT its sign (\"19.75\" for a \"-19.75\" field) — or just one digit\n// group (\"440\", \"00\", \"-10\"...).\nexport const pickFloatTokenIndex = (tokens: ParsedFloatToken[], hintText: string | null): number => {\n if (tokens.length <= 1) return 0\n const hint = hintText?.trim() ?? ''\n if (!hint) return 0\n\n const index = tokens.findIndex(\n (token) =>\n token.raw === hint ||\n token.raw.replace(/^[+-]/, '') === hint ||\n `${token.negative ? '-' : ''}${token.intDigits}` === hint ||\n token.intDigits === hint ||\n token.decDigits === hint,\n )\n return index >= 0 ? index : 0\n}\n\n// What the cursor currently highlights on a float field:\n// - 'full': the whole float, e.g. \"19.75\" (the sign may be excluded from the highlight)\n// - 'int' / 'dec': only one digit group (\"440\" / \"00\")\n// Returns null when the hint cannot be attributed confidently; callers must cope\n// with that instead of assuming.\nexport type FloatCursorFocus = 'full' | 'int' | 'dec'\n\nexport const detectFloatCursorFocus = (token: ParsedFloatToken | null, hintText: string | null): FloatCursorFocus | null => {\n if (!token) return null\n const hint = hintText?.trim() ?? ''\n if (!hint) return null\n\n const unsignedRaw = token.raw.replace(/^[+-]/, '')\n if (token.raw === hint || unsignedRaw === hint) return 'full'\n\n const signedIntGroup = `${token.negative ? '-' : ''}${token.intDigits}`\n if (signedIntGroup === hint || token.intDigits === hint) return 'int'\n if (token.decDigits === hint) return 'dec'\n\n // Single-digit highlight: attribute it only when the digit belongs to exactly one group.\n if (hint.length === 1) {\n const inIntPart = token.intDigits.includes(hint)\n const inDecPart = token.decDigits.includes(hint)\n if (inIntPart && !inDecPart) return 'int'\n if (inDecPart && !inIntPart) return 'dec'\n }\n\n return null\n}\n\n// Does a highlighted text match one token (whole float, with or without sign, or\n// a whole digit group)? Single-character hints are only trusted for full-token\n// matches because a lone digit is too ambiguous.\nconst floatHintMatchesToken = (token: ParsedFloatToken, hint: string): boolean => {\n if (hint.length === 1) {\n return token.raw === hint || token.raw.replace(/^[+-]/, '') === hint\n }\n return (\n token.raw === hint ||\n token.raw.replace(/^[+-]/, '') === hint ||\n `${token.negative ? '-' : ''}${token.intDigits}` === hint ||\n token.intDigits === hint ||\n token.decDigits === hint\n )\n}\n\n// Detect whether the highlighted text now belongs to a DIFFERENT float token on\n// the same line (the cursor drifted to another field mid-run). The hint must not\n// match the edited token at all while matching another one, so ambiguous cases\n// never trigger a false positive.\nexport const isCursorOnForeignFloat = (tokens: ParsedFloatToken[], ownIndex: number, hintText: string | null): boolean => {\n const hint = hintText?.trim() ?? ''\n if (!hint || !tokens[ownIndex]) return false\n if (floatHintMatchesToken(tokens[ownIndex], hint)) return false\n return tokens.some((token, index) => index !== ownIndex && floatHintMatchesToken(token, hint))\n}\n\n// Pretty-print a parsed token value for logs, e.g. ticks=-1069 with 2 decimals => \"-10.69\"\nexport const formatFloatFromTicks = (ticks: number, tickHundredths: number): string => {\n const hundredths = Math.round(ticks * tickHundredths)\n const sign = hundredths < 0 ? '-' : ''\n const absolute = Math.abs(hundredths)\n const intPart = Math.floor(absolute / 100)\n const fracPart = absolute - intPart * 100\n return `${sign}${intPart}.${fracPart.toString().padStart(2, '0')}`\n}\n\nconst NOTE_ORDER = ['C-', 'C#', 'D-', 'D#', 'E-', 'F-', 'F#', 'G-', 'G#', 'A-', 'A#', 'B-'] as const\n\nexport type ParsedNote = {\n label: string\n noteName: string\n octave: number\n semitoneIndex: number\n}\n\nexport const parseNoteValue = (text: string | null): ParsedNote | null => {\n if (!text) return null\n const match = text\n .trim()\n .toUpperCase()\n .match(/([A-G][#-][0-9A-F])/)\n if (!match) return null\n\n const label = match[1]\n const noteName = label.slice(0, 2)\n const octave = Number.parseInt(label.slice(2, 3), 16)\n const noteOffset = NOTE_ORDER.indexOf(noteName as (typeof NOTE_ORDER)[number])\n\n if (noteOffset < 0 || Number.isNaN(octave)) {\n return null\n }\n\n return {\n label,\n noteName,\n octave,\n semitoneIndex: octave * 12 + noteOffset,\n }\n}\n","import { M8KeyMask } from '../keys'\nimport { type HostIO, wait } from './hostIO'\nimport {\n detectFloatCursorFocus,\n type FloatCursorFocus,\n formatFloatFromTicks,\n isCursorOnForeignFloat,\n type ParsedFloatToken,\n parseFloatTokens,\n pickFloatTokenIndex,\n} from './valueParsers'\n\n// Edit a float field (e.g. \"TUNE 440.00 G\") using edit+arrow key combos.\nexport const createFloatEditor = (io: HostIO) => {\n const { store, debugLog, hasBus, sendKeys, pressAndRelease, waitForTextUnderCursorChange, waitForCurrentLineChange } = io\n\n // Implementation of setValueFloat using edit+navigation keys.\n //\n // Float fields (e.g. \"TUNE 440.00 G\" or \"GAIN 05.25 -19.75 -15.00\") are rendered\n // as two linked digit groups separated by a dot: the integer part and the decimal\n // part. Stepping the decimal part past its bounds carries into the integer part\n // (1.99 -> 2.00), so the whole signed token behaves like a single number that the\n // closed loop below steers through measured edit+arrow key steps.\n //\n // Cursor focus modes (deduced from textUnderCursor):\n // - 'full': the cursor spans the whole float (\"19.75\", sign often excluded).\n // There, edit+up/down moves the INTEGER part and edit+left/right the DECIMAL\n // part, so both granularities are directly available and no cursor relocation\n // is needed (attempting it could even drift to a neighboring field).\n // - 'int' / 'dec': the cursor highlights a single digit group. The cursor is then\n // relocated with bare left/right onto whichever group is most efficient:\n // integer part for whole-unit travel, decimal part for sub-unit finishing.\n //\n // Step sizes and arrow polarities are measured from real device feedback\n // (\"learned\") per focus mode and per arrow channel (up/down vs left/right),\n // then reused for fast press bursts.\n // Negative targets are supported (fields display like \"-10.69\").\n // Note: opt+edit resets the field to its default value (0.00 / 440.00 / ...).\n const setValueFloatImpl = async (targetFloat: number): Promise<boolean> => {\n if (!hasBus()) return false\n if (!Number.isFinite(targetFloat)) return false\n\n let tokenIndex = 0\n let tickHundredths = 1\n\n let lastTokens: ParsedFloatToken[] = []\n\n const readFloatState = (): ParsedFloatToken | null => {\n const tokens = parseFloatTokens(store.get('currentLine'))\n lastTokens = tokens\n if (!tokens.length) return null\n tokenIndex = Math.min(tokenIndex, tokens.length - 1)\n const token = tokens[tokenIndex]\n if (token.tickHundredths > 0) tickHundredths = token.tickHundredths\n return token\n }\n\n const initialTokens = parseFloatTokens(store.get('currentLine'))\n if (!initialTokens.length) {\n console.warn('[M8SDK] Could not find a float value like \"440.00\" or \"-10.69\" on the current line:', store.get('currentLine'))\n return false\n }\n\n tokenIndex = pickFloatTokenIndex(initialTokens, store.get('textUnderCursor'))\n if (initialTokens.length > 1) {\n debugLog('[M8SDK] Multiple float values on line, editing:', initialTokens[tokenIndex]?.raw)\n }\n\n const initialToken = initialTokens[tokenIndex]\n tickHundredths = initialToken.tickHundredths\n // Caller-provided floats are quantized to the precision actually displayed.\n const targetTicks = Math.round(Math.round(targetFloat * 100) / tickHundredths)\n\n if (initialToken.ticks === targetTicks) {\n debugLog('[M8SDK] Already at target float value:', formatFloatFromTicks(targetTicks, tickHundredths))\n return true\n }\n\n debugLog(\n '[M8SDK] Setting float value from',\n formatFloatFromTicks(initialToken.ticks, tickHundredths),\n 'to',\n formatFloatFromTicks(targetTicks, tickHundredths),\n )\n\n // Enter edit mode. Numeric rows do not necessarily change their text when\n // entering edit mode, so a short settle wait is enough here.\n await pressAndRelease(M8KeyMask.Edit)\n await wait(80)\n\n // Measured step size and arrow polarity per arrow channel (up/down vs\n // left/right), tracked PER focus mode: 'full' has up/down -> integer part\n // and left/right -> decimal part, while 'int'/'dec' group focus changes\n // what each arrow channel steps. Measured magnitudes are in ticks.\n type FloatChannel = 'upDown' | 'leftRight'\n type FloatChannelStats = {\n mag: number | null\n bias: number\n }\n type FloatFocusStats = Record<FloatChannel, FloatChannelStats>\n const makeFocusStats = (): FloatFocusStats => ({\n upDown: { mag: null, bias: 1 },\n leftRight: { mag: null, bias: 1 },\n })\n const statsByFocus: Record<FloatCursorFocus, FloatFocusStats> = {\n full: makeFocusStats(),\n int: makeFocusStats(),\n dec: makeFocusStats(),\n }\n\n // Ticks contained in one whole unit (100 for two decimals, 10 for one).\n const UNIT_IN_TICKS = initialToken.decScale\n\n const COARSE_MIN_TICKS = 32\n const MAX_RUNTIME_MS = 15000\n const MAX_PRESSES_PER_BURST = 96\n const startedAt = Date.now()\n\n // Trust textUnderCursor to tell which part of the field is highlighted;\n // when unclear, assume integer-group focus (self-corrects via measurement).\n let focus: FloatCursorFocus = detectFloatCursorFocus(initialToken, store.get('textUnderCursor')) ?? 'int'\n debugLog('[M8SDK] Float cursor focus:', focus)\n // Set when cursor relocation proves unreliable (layout quirks); the value\n // closed-loop below keeps working without side management.\n let sideControlDisabled = false\n // Set when the cursor drifted onto another field: editing must stop at once.\n let driftedToForeignField = false\n\n let previousTicks = initialToken.ticks\n let valueTwoStepsAgo: number | null = null\n let bestTicks = initialToken.ticks\n let bestDistance = Math.abs(initialToken.ticks - targetTicks)\n let fruitlessStreak = 0\n let reachedTarget = false\n let gaveUp = false\n\n // Build an edit+arrow mask for an arrow channel.\n const getMaskForChannel = (channel: FloatChannel, effectiveDirection: number): number => {\n const positiveKey = channel === 'upDown' ? M8KeyMask.Up : M8KeyMask.Right\n const negativeKey = channel === 'upDown' ? M8KeyMask.Down : M8KeyMask.Left\n return M8KeyMask.Edit | (effectiveDirection >= 0 ? positiveKey : negativeKey)\n }\n\n const applyBurst = async (channel: FloatChannel, direction: number, presses: number): Promise<void> => {\n const stats = statsByFocus[focus][channel]\n const mask = getMaskForChannel(channel, direction * stats.bias)\n const beforeLine = store.get('currentLine')\n for (let i = 0; i < presses; i++) {\n sendKeys(mask)\n await wait(22)\n }\n await waitForCurrentLineChange(beforeLine, 350)\n }\n\n // Hop the cursor onto the wanted digit group using bare left/right (no edit):\n // the decimal part sits right of the dot, the integer part left of it. The\n // landing is verified through textUnderCursor; if the first arrow did not\n // reach the wanted group, the opposite arrow is tried before giving up.\n // Only ever called from 'int'/'dec' focus — never from 'full', where a bare\n // arrow could leave the field entirely.\n const ensureFloatCursorSide = async (side: 'int' | 'dec'): Promise<boolean> => {\n const currentFocusOf = (): FloatCursorFocus | null => detectFloatCursorFocus(readFloatState(), store.get('textUnderCursor'))\n\n const detected = currentFocusOf()\n if (detected === side) {\n focus = side\n return true\n }\n\n const beforeHint = store.get('textUnderCursor')\n const towardDot = side === 'dec' ? M8KeyMask.Right : M8KeyMask.Left\n const awayFromDot = side === 'dec' ? M8KeyMask.Left : M8KeyMask.Right\n\n await pressAndRelease(towardDot, 35)\n await waitForTextUnderCursorChange(beforeHint, 250)\n if (currentFocusOf() === side) {\n focus = side\n return true\n }\n\n // Unexpected layout: try the reverse arrow instead of getting stuck.\n await pressAndRelease(awayFromDot, 35)\n await waitForTextUnderCursorChange(beforeHint, 250)\n const landed = currentFocusOf() === side\n if (landed) focus = side\n return landed\n }\n\n while (!reachedTarget && !gaveUp && Date.now() - startedAt < MAX_RUNTIME_MS) {\n const state = readFloatState()\n if (!state) {\n console.warn('[M8SDK] Float value no longer parseable while editing:', store.get('currentLine'))\n gaveUp = true\n break\n }\n\n // Never keep editing when the cursor moved onto another field of the line.\n if (isCursorOnForeignFloat(lastTokens, tokenIndex, store.get('textUnderCursor'))) {\n console.warn('[M8SDK] Float cursor drifted to another field, aborting to avoid editing the wrong value')\n driftedToForeignField = true\n gaveUp = true\n break\n }\n\n const diffTicks = targetTicks - state.ticks\n if (diffTicks === 0) {\n reachedTarget = true\n break\n }\n\n const distance = Math.abs(diffTicks)\n if (distance < bestDistance) {\n bestDistance = distance\n bestTicks = state.ticks\n }\n\n // Detect 2-value bounce (A -> B -> A), especially when crossing the target.\n const isOscillating = valueTwoStepsAgo !== null && state.ticks === valueTwoStepsAgo\n const crossesTarget = (previousTicks - targetTicks) * (state.ticks - targetTicks) < 0\n if (isOscillating && crossesTarget) {\n debugLog('[M8SDK] Float adjustment bouncing around target, stopping')\n break\n }\n valueTwoStepsAgo = previousTicks\n previousTicks = state.ticks\n\n // Route the cursor between digit groups for efficiency, but only when a\n // single group is focused. With 'full' focus, up/down already drives the\n // integer part and left/right the decimal part, so there is nothing to\n // relocate — and a bare arrow there could leave the field entirely.\n if (focus !== 'full' && !sideControlDisabled) {\n const desiredSide: 'int' | 'dec' = distance >= UNIT_IN_TICKS ? 'int' : 'dec'\n if (desiredSide !== focus) {\n const hopped = await ensureFloatCursorSide(desiredSide)\n if (!hopped) {\n // The cursor may have landed on another field during the\n // failed relocation: verify before pressing edit combos.\n if (isCursorOnForeignFloat(lastTokens, tokenIndex, store.get('textUnderCursor'))) {\n console.warn('[M8SDK] Float cursor drifted to another field, aborting to avoid editing the wrong value')\n driftedToForeignField = true\n gaveUp = true\n break\n }\n debugLog('[M8SDK] Could not relocate float cursor, continuing on current digit group')\n sideControlDisabled = true\n }\n }\n }\n\n // Choose the arrow channel and burst size for this round, using only\n // measurements gathered in the current focus mode. up/down probing is\n // gated by focus: on the integer group a jump spans several whole units\n // and would badly overshoot short distances; spanning the whole float it\n // moves whole units too; on the decimal group jumps are sub-unit only,\n // so probing pays off much sooner.\n const focusStats = statsByFocus[focus]\n const upDownProbeGateTicks =\n focus === 'int'\n ? Math.max(COARSE_MIN_TICKS, 3 * UNIT_IN_TICKS)\n : focus === 'full'\n ? UNIT_IN_TICKS\n : Math.max(8, Math.floor(COARSE_MIN_TICKS / 4))\n\n let channel: FloatChannel\n let presses = 1\n if (focusStats.upDown.mag !== null && focusStats.upDown.mag > 0 && distance >= focusStats.upDown.mag) {\n channel = 'upDown'\n presses = Math.min(Math.max(1, Math.floor(distance / focusStats.upDown.mag)), MAX_PRESSES_PER_BURST)\n } else if (focusStats.upDown.mag === null && distance >= upDownProbeGateTicks) {\n // One probe press measures the actual up/down step in this focus mode.\n channel = 'upDown'\n presses = 1\n } else if (focusStats.leftRight.mag !== null && focusStats.leftRight.mag > 0) {\n channel = 'leftRight'\n presses = Math.min(Math.max(1, Math.floor(distance / focusStats.leftRight.mag)), MAX_PRESSES_PER_BURST)\n } else {\n // left/right magnitude unknown (or not yet usable): probe it once.\n channel = 'leftRight'\n presses = 1\n }\n\n const direction = diffTicks > 0 ? 1 : -1\n const beforeTicks = state.ticks\n const focusAtPress = focus\n const channelAtPress = channel\n await applyBurst(channel, direction, presses)\n\n const nextState = readFloatState()\n if (!nextState) {\n gaveUp = true\n break\n }\n const deltaTicks = nextState.ticks - beforeTicks\n\n if (deltaTicks === 0) {\n fruitlessStreak += 1\n if (fruitlessStreak >= 4) {\n console.warn(\n `[M8SDK] Float value stopped reacting at ${formatFloatFromTicks(nextState.ticks, tickHundredths)}` +\n ` (target ${formatFloatFromTicks(targetTicks, tickHundredths)} may be out of range)`,\n )\n gaveUp = true\n }\n continue\n }\n fruitlessStreak = 0\n\n // Learn/refresh the measured magnitude and polarity for the channel that\n // was used, in the focus mode it was used from.\n const pressedStats = statsByFocus[focusAtPress][channelAtPress]\n pressedStats.mag = Math.max(1, Math.round(Math.abs(deltaTicks) / presses))\n\n // If the pressed arrows moved away from the target despite our assumed\n // mapping, flip that channel's arrow expectation for next rounds.\n if (Math.sign(deltaTicks) !== Math.sign(diffTicks)) {\n debugLog(`[M8SDK] Float ${channelAtPress} arrows move oppositely in '${focusAtPress}' focus, inverting mapping`)\n pressedStats.bias *= -1\n valueTwoStepsAgo = null\n }\n }\n\n // If the exact target was never reached, walk back to the closest observed\n // value so the parameter is never left worse off than necessary. Skipped\n // after a foreign-field drift — recovery presses would edit the wrong value.\n if (!reachedTarget && !driftedToForeignField) {\n const snapStartedAt = Date.now()\n while (Date.now() - snapStartedAt < 1800) {\n const snapState = readFloatState()\n if (!snapState || snapState.ticks === bestTicks) break\n\n const gap = bestTicks - snapState.ticks\n const snapDistance = Math.abs(gap)\n\n // Same digit-group routing as the main loop for efficient recovery.\n if (focus !== 'full' && !sideControlDisabled && snapDistance >= UNIT_IN_TICKS !== (focus === 'int')) {\n const hoppedBack = await ensureFloatCursorSide(snapDistance >= UNIT_IN_TICKS ? 'int' : 'dec')\n if (!hoppedBack) sideControlDisabled = true\n }\n\n const snapStats = statsByFocus[focus]\n let snapChannel: FloatChannel\n let snapPresses = 1\n if (snapStats.upDown.mag !== null && snapStats.upDown.mag > 0 && snapDistance >= snapStats.upDown.mag) {\n snapChannel = 'upDown'\n snapPresses = Math.min(Math.max(1, Math.floor(snapDistance / snapStats.upDown.mag)), 16)\n } else if (snapStats.leftRight.mag !== null && snapStats.leftRight.mag > 0) {\n snapChannel = 'leftRight'\n snapPresses = Math.min(Math.max(1, Math.floor(snapDistance / snapStats.leftRight.mag)), 16)\n } else {\n snapChannel = 'leftRight'\n }\n\n const beforeSnapTicks = snapState.ticks\n await applyBurst(snapChannel, gap > 0 ? 1 : -1, snapPresses)\n const afterSnap = readFloatState()\n if (!afterSnap || afterSnap.ticks === beforeSnapTicks) break\n }\n }\n\n // Exit edit mode and verify the final value against the parsed line.\n // After a foreign-field drift, pressing edit could open edit mode on the\n // wrong field, so skip it and just report failure.\n if (!driftedToForeignField) {\n await pressAndRelease(M8KeyMask.Edit)\n }\n\n const finalToken = readFloatState()\n const success = !!finalToken && finalToken.ticks === targetTicks\n debugLog(\n `[M8SDK] Float setting ${success ? 'succeeded' : 'failed'}: \"${finalToken?.raw ?? 'N/A'}\"` +\n ` (${finalToken ? formatFloatFromTicks(finalToken.ticks, tickHundredths) : 'N/A'})`,\n )\n return success\n }\n\n return setValueFloatImpl\n}\n","{\n \"song\": [\n {\n \"to\": \"project\",\n \"dir\": \"up\",\n \"keys\": [80, 0]\n },\n {\n \"to\": \"mixer\",\n \"dir\": \"down\",\n \"keys\": [48, 0],\n \"weight\": 10\n },\n {\n \"to\": \"chain\",\n \"dir\": \"right\",\n \"keys\": [20, 0]\n }\n ],\n \"project\": [\n {\n \"to\": \"song\",\n \"dir\": \"down\",\n \"keys\": [48, 0],\n \"back\": true\n },\n {\n \"to\": \"chain\",\n \"dir\": \"down\",\n \"keys\": [48, 0],\n \"back\": true\n },\n {\n \"to\": \"midisettings\",\n \"via\": \"button\",\n \"cursor\": [14, 11],\n \"label\": \"SETTINGS\",\n \"keys\": [1, 0],\n \"weight\": 2\n },\n {\n \"to\": \"midimapping\",\n \"via\": \"button\",\n \"cursor\": [23, 11],\n \"label\": \"MAPPINGS\",\n \"keys\": [1, 0],\n \"weight\": 2\n },\n {\n \"to\": \"renderaudio\",\n \"via\": \"button\",\n \"cursor\": [14, 15],\n \"label\": \"RENDER\",\n \"keys\": [1, 0],\n \"weight\": 2\n },\n {\n \"to\": \"instrumentpool\",\n \"via\": \"button\",\n \"cursor\": [14, 17],\n \"label\": \"VIEW INST. POOL\",\n \"keys\": [1, 0],\n \"weight\": 2\n },\n {\n \"to\": \"timestats\",\n \"via\": \"button\",\n \"cursor\": [14, 19],\n \"label\": \"VIEW TIME STATS\",\n \"keys\": [1, 0],\n \"weight\": 2\n },\n {\n \"to\": \"systemsettings\",\n \"via\": \"button\",\n \"cursor\": [14, 20],\n \"label\": \"SETTINGS\",\n \"keys\": [1, 0],\n \"weight\": 2\n }\n ],\n \"mixer\": [\n {\n \"to\": \"effectsettings\",\n \"dir\": \"down\",\n \"keys\": [48, 0]\n },\n {\n \"to\": \"song\",\n \"dir\": \"up\",\n \"keys\": [80, 0],\n \"weight\": 10,\n \"back\": true\n },\n {\n \"to\": \"chain\",\n \"dir\": \"up\",\n \"keys\": [80, 0],\n \"weight\": 10,\n \"back\": true\n },\n {\n \"to\": \"phrase\",\n \"dir\": \"up\",\n \"keys\": [80, 0],\n \"weight\": 10,\n \"back\": true\n },\n {\n \"to\": \"inst\",\n \"dir\": \"up\",\n \"keys\": [80, 0],\n \"weight\": 10,\n \"back\": true\n },\n {\n \"to\": \"table\",\n \"dir\": \"up\",\n \"keys\": [80, 0],\n \"weight\": 10,\n \"back\": true\n },\n {\n \"to\": \"mixeq\",\n \"via\": \"button\",\n \"cursor\": [28, 17],\n \"keys\": [64, 0, 20, 0],\n \"weight\": 2\n },\n {\n \"to\": \"mixscope\",\n \"via\": \"button\",\n \"cursor\": [28, 17],\n \"keys\": [20, 0],\n \"weight\": 2\n },\n {\n \"to\": \"limiterscope\",\n \"via\": \"button\",\n \"cursor\": [28, 18],\n \"keys\": [20, 0],\n \"weight\": 2\n }\n ],\n \"effectsettings\": [\n {\n \"to\": \"mixer\",\n \"dir\": \"up\",\n \"keys\": [80, 0]\n },\n {\n \"to\": \"modfxeq\",\n \"via\": \"button\",\n \"cursor\": [22, 6],\n \"label\": \"EQ\",\n \"keys\": [1, 0],\n \"weight\": 2\n },\n {\n \"to\": \"delayeq\",\n \"via\": \"button\",\n \"cursor\": [22, 11],\n \"label\": \"EQ\",\n \"keys\": [1, 0],\n \"weight\": 2\n },\n {\n \"to\": \"reverbeq\",\n \"via\": \"button\",\n \"cursor\": [22, 17],\n \"label\": \"EQ\",\n \"keys\": [1, 0],\n \"weight\": 2\n }\n ],\n \"inst\": [\n {\n \"to\": \"table\",\n \"dir\": \"right\",\n \"keys\": [20, 0]\n },\n {\n \"to\": \"phrase\",\n \"dir\": \"left\",\n \"keys\": [144, 0]\n },\n {\n \"to\": \"instmods\",\n \"dir\": \"up\",\n \"keys\": [80, 0]\n },\n {\n \"to\": \"mixer\",\n \"dir\": \"down\",\n \"keys\": [48, 0],\n \"weight\": 10\n },\n {\n \"to\": \"loadinstrument\",\n \"via\": \"button\",\n \"cursor\": [23, 5],\n \"label\": \"LOAD\",\n \"keys\": [1, 0],\n \"weight\": 2\n },\n {\n \"to\": \"instrumentpool\",\n \"dir\": \"left\",\n \"keys\": [144, 0],\n \"back\": true\n }\n ],\n \"table\": [\n {\n \"to\": \"inst\",\n \"dir\": \"left\",\n \"keys\": [144, 0]\n },\n {\n \"to\": \"instmods\",\n \"dir\": \"up\",\n \"keys\": [80, 0]\n },\n {\n \"to\": \"mixer\",\n \"dir\": \"down\",\n \"keys\": [48, 0],\n \"weight\": 10\n }\n ],\n \"phrase\": [\n {\n \"to\": \"inst\",\n \"dir\": \"right\",\n \"keys\": [20, 0]\n },\n {\n \"to\": \"chain\",\n \"dir\": \"left\",\n \"keys\": [144, 0]\n },\n {\n \"to\": \"groove\",\n \"dir\": \"up\",\n \"keys\": [80, 0]\n },\n {\n \"to\": \"mixer\",\n \"dir\": \"down\",\n \"keys\": [48, 0],\n \"weight\": 10\n }\n ],\n \"chain\": [\n {\n \"to\": \"phrase\",\n \"dir\": \"right\",\n \"keys\": [20, 0]\n },\n {\n \"to\": \"song\",\n \"dir\": \"left\",\n \"keys\": [144, 0]\n },\n {\n \"to\": \"project\",\n \"dir\": \"up\",\n \"keys\": [80, 0]\n },\n {\n \"to\": \"mixer\",\n \"dir\": \"down\",\n \"keys\": [48, 0],\n \"weight\": 10\n }\n ],\n \"groove\": [\n {\n \"to\": \"scale\",\n \"dir\": \"up\",\n \"keys\": [80, 0]\n },\n {\n \"to\": \"phrase\",\n \"dir\": \"down\",\n \"keys\": [48, 0]\n }\n ],\n \"scale\": [\n {\n \"to\": \"groove\",\n \"dir\": \"down\",\n \"keys\": [48, 0]\n }\n ],\n \"instmods\": [\n {\n \"to\": \"instrumentpool\",\n \"dir\": \"up\",\n \"keys\": [80, 0]\n },\n {\n \"to\": \"inst\",\n \"dir\": \"down\",\n \"keys\": [48, 0],\n \"back\": true\n },\n {\n \"to\": \"table\",\n \"dir\": \"down\",\n \"keys\": [48, 0],\n \"back\": true\n }\n ],\n \"instrumentpool\": [\n {\n \"to\": \"instmods\",\n \"dir\": \"down\",\n \"keys\": [48, 0]\n },\n {\n \"to\": \"inst\",\n \"dir\": \"right\",\n \"keys\": [20, 0]\n },\n {\n \"to\": \"phrase\",\n \"dir\": \"left\",\n \"keys\": [144, 0],\n \"weight\": 4\n }\n ],\n \"systemsettings\": [\n {\n \"to\": \"themesettings\",\n \"via\": \"button\",\n \"cursor\": [16, 7],\n \"label\": \"EDIT THEME\",\n \"keys\": [1, 0],\n \"weight\": 2\n },\n {\n \"to\": \"project\",\n \"via\": \"exit\",\n \"keys\": [2, 0]\n },\n {\n \"to\": \"project\",\n \"dir\": \"up\",\n \"keys\": [80, 0],\n \"weight\": 2\n }\n ],\n \"midisettings\": [\n {\n \"to\": \"project\",\n \"via\": \"exit\",\n \"keys\": [2, 0]\n },\n {\n \"to\": \"project\",\n \"dir\": \"up\",\n \"keys\": [80, 0],\n \"weight\": 3\n }\n ],\n \"midimapping\": [\n {\n \"to\": \"project\",\n \"via\": \"exit\",\n \"keys\": [2, 0]\n },\n {\n \"to\": \"project\",\n \"dir\": \"up\",\n \"keys\": [80, 0],\n \"weight\": 3\n }\n ],\n \"renderaudio\": [\n {\n \"to\": \"project\",\n \"via\": \"exit\",\n \"keys\": [2, 0]\n },\n {\n \"to\": \"project\",\n \"dir\": \"up\",\n \"keys\": [80, 0],\n \"weight\": 3\n }\n ],\n \"timestats\": [\n {\n \"to\": \"project\",\n \"via\": \"exit\",\n \"keys\": [2, 0]\n },\n {\n \"to\": \"project\",\n \"dir\": \"up\",\n \"keys\": [80, 0],\n \"weight\": 3\n }\n ],\n \"loadproject\": [\n {\n \"to\": \"project\",\n \"via\": \"exit\",\n \"keys\": [2, 0]\n }\n ],\n \"themesettings\": [\n {\n \"to\": \"systemsettings\",\n \"via\": \"exit\",\n \"keys\": [2, 0]\n }\n ],\n \"mixeq\": [\n {\n \"to\": \"mixer\",\n \"via\": \"exit\",\n \"keys\": [2, 0]\n },\n {\n \"to\": \"mixer\",\n \"dir\": \"left\",\n \"keys\": [144, 0],\n \"weight\": 3\n }\n ],\n \"mixscope\": [\n {\n \"to\": \"mixer\",\n \"via\": \"exit\",\n \"keys\": [2, 0]\n },\n {\n \"to\": \"mixer\",\n \"dir\": \"left\",\n \"keys\": [144, 0],\n \"weight\": 3\n }\n ],\n \"limiterscope\": [\n {\n \"to\": \"mixer\",\n \"via\": \"exit\",\n \"keys\": [2, 0]\n },\n {\n \"to\": \"mixer\",\n \"dir\": \"left\",\n \"keys\": [144, 0],\n \"weight\": 3\n }\n ],\n \"modfxeq\": [\n {\n \"to\": \"effectsettings\",\n \"via\": \"exit\",\n \"keys\": [2, 0]\n }\n ],\n \"delayeq\": [\n {\n \"to\": \"effectsettings\",\n \"via\": \"exit\",\n \"keys\": [2, 0]\n }\n ],\n \"reverbeq\": [\n {\n \"to\": \"effectsettings\",\n \"via\": \"exit\",\n \"keys\": [2, 0]\n }\n ],\n \"loadinstrument\": [\n {\n \"to\": \"inst\",\n \"via\": \"exit\",\n \"keys\": [2, 0]\n }\n ]\n}\n","{\n \"views\": [\n \"chain\",\n \"effectsettings\",\n \"groove\",\n \"instmods\",\n \"inst\",\n \"instrumentpool\",\n \"mixer\",\n \"phrase\",\n \"project\",\n \"scale\",\n \"song\",\n \"table\",\n \"mixeq\",\n \"limiterscope\",\n \"mixscope\",\n \"modfxeq\",\n \"delayeq\",\n \"reverbeq\",\n \"loadinstrument\",\n \"selectsavedirectory\",\n \"createdirectory\",\n \"systemsettings\",\n \"midisettings\",\n \"midimapping\",\n \"renderaudio\",\n \"timestats\",\n \"themesettings\",\n \"loadproject\"\n ]\n}\n","// The M8 view graph used to navigate between views, and the list of view names. Both are\n// bundled copies of yam8d's public/m8Graph.generated.json and public/viewlist.json (a yam8d\n// test keeps them identical).\n//\n// Edges come in three kinds:\n// - shift: Shift+direction (`dir`), the views of the main grid.\n// - button: put the cursor on a cell of the screen (`cursor`, moved to the nearest place\n// where `label` is written when it is on screen), then press `keys`. Sub-views such as\n// MIDI Settings or the Mixer EQ sit behind buttons.\n// - exit: Option, which leaves a sub-view. Where it lands is not always known, so the\n// navigation checks the view after each step.\n//\n// Some keys go back to the view the M8 came from (`back`): the Mixer's Shift+Up, the\n// Instrument's Shift+Left after the Instrument Pool. Among the edges of one key, a `back` edge\n// is taken only when its target is the previous view; otherwise the other edges stand.\n\nimport type { Direction, Edge, Graph } from './astar'\nimport graphJson from './data/m8Graph.generated.json'\nimport viewListJson from './data/viewlist.json'\n\ntype EdgeJson = {\n to: string\n dir?: Direction\n via?: 'button' | 'exit'\n cursor?: [number, number]\n label?: string\n keys?: number[]\n weight?: number\n back?: boolean\n}\n\nexport type NavEdgeKind = 'shift' | 'button' | 'exit'\n\nexport interface NavEdge extends Edge {\n from: string\n kind: NavEdgeKind\n /** Key frames to play (after the cursor move, for buttons). Empty: Shift+dir. */\n keys: number[]\n /** Button cell, in text grid coordinates. */\n cursor?: { x: number; y: number }\n /** Button text, used to find the button where the layout differs from `cursor`. */\n label?: string\n /** Where this key goes when the M8 came from `to` (it goes back). */\n back?: boolean\n}\n\nexport interface M8ViewGraph {\n graph: Graph<NavEdge>\n /** Explicit key frame sequences per edge: from -> to -> frames. */\n edgeKeys: Map<string, Map<string, number[]>>\n}\n\nconst buildGraph = (json: Record<string, EdgeJson[]>): M8ViewGraph => {\n const graph: Graph<NavEdge> = new Map()\n const edgeKeys = new Map<string, Map<string, number[]>>()\n for (const [rawKey, list] of Object.entries(json)) {\n const from = rawKey.toLowerCase()\n const edges: NavEdge[] = []\n const perFrom = new Map<string, number[]>()\n for (const e of list) {\n const kind: NavEdgeKind = e.via ?? 'shift'\n if (kind === 'shift' && !e.dir) continue\n if (kind === 'button' && !e.cursor) continue\n const keys = e.keys?.slice() ?? []\n edges.push({\n from,\n to: e.to.toLowerCase(),\n dir: e.dir,\n weight: e.weight,\n kind,\n keys,\n cursor: e.cursor ? { x: e.cursor[0], y: e.cursor[1] } : undefined,\n label: e.label,\n back: e.back,\n })\n if (kind === 'shift' && keys.length) perFrom.set(e.to, keys)\n }\n graph.set(from, edges)\n if (perFrom.size) edgeKeys.set(from, perFrom)\n }\n // Views that are only targets still need a node\n for (const edges of [...graph.values()]) {\n for (const e of edges) if (!graph.has(e.to)) graph.set(e.to, [])\n }\n return { graph, edgeKeys }\n}\n\nlet viewGraph: M8ViewGraph | null = null\nlet viewNames: Set<string> | null = null\n\nexport const getViewGraph = (): M8ViewGraph => {\n viewGraph ??= buildGraph(graphJson as unknown as Record<string, EdgeJson[]>)\n return viewGraph\n}\n\n/** Names of the M8 views, lowercased (as viewName reports them). */\nexport const getViewNames = (): Set<string> => {\n if (!viewNames) {\n const list = Array.isArray((viewListJson as { views?: unknown }).views) ? (viewListJson as { views: unknown[] }).views : []\n viewNames = new Set(list.map((s) => String(s).trim().toLowerCase()).filter((s) => !!s))\n }\n return viewNames\n}\n","// Turns the M8 draw stream into screen state: the character grid (whole screen text and\n// colours), the view title, and the cursor assembled from its corner rectangles.\n\nimport type { CursorRect, RGB } from '../types'\nimport type { CharacterCommand, RectCommand, SystemInfos } from './protocol'\nimport type { M8Store } from './store'\nimport { getViewNames } from './viewGraph'\n\n// Heuristics aligned with existing extraction logic\nconst STABILIZE_MS = 50\n\ninterface CornerCandidate {\n x: number\n y: number\n // Expected positions for verification pieces\n expectedVert: { x: number; y: number } | null // 1x2 vertical edge\n expectedTip: { x: number; y: number } | null // 1x1 tip\n foundVert: boolean\n foundTip: boolean\n}\n\ninterface CursorAssembly {\n tl: CornerCandidate // Top-left (colored, type 4)\n tr: CornerCandidate | null // Top-right\n bl: CornerCandidate | null // Bottom-left\n br: CornerCandidate | null // Bottom-right\n startTime: number\n state: 'waiting' | 'building' | 'complete'\n}\n\nconst defaultCursor: CursorRect = { x: 0, y: 0, w: 1, h: 1 }\n\ninterface SelectionRectSample {\n x: number\n y: number\n w: number\n h: number\n ts: number\n hasColor: boolean\n}\n\nclass AssemblyCursorExtractor {\n // Active cursor assemblies being built\n private assemblies: CursorAssembly[] = []\n\n // Recent 1px-border segments used to detect selection rectangles\n private selectionRectSamples: SelectionRectSample[] = []\n\n // Last cursor from the corner-based assembly. NEVER written by selection detection.\n private lastCursor: CursorRect = defaultCursor\n\n // Last selection rectangle (separate from lastCursor to avoid cross-contamination)\n private lastSelectionCursor: CursorRect | null = null\n\n // Last detected cursor color (from type 4 rectangles)\n private lastCursorColor: RGB | null = null\n\n // Selection mode tracking\n private lastSelectionMode = false\n private lastSelectionSeenAt = 0\n\n // Timeout for stale assemblies (ms)\n private readonly ASSEMBLY_TIMEOUT = 100\n // Max age of segment samples considered part of the same selection draw call\n private readonly SELECTION_SAMPLE_WINDOW = 50\n // How long to stay in selection mode after the last fresh detection\n private readonly SELECTION_HOLD_DURATION = 150\n\n /**\n * Process a rectangle command.\n * Strategy: run normal corner assembly FIRST (unchanged), only attempt\n * selection detection as a fallback. Selection detection NEVER writes\n * lastCursor, so false positives cannot corrupt normal cursor tracking.\n */\n processRect(rect: RectCommand): { cursor: CursorRect; cursorColor: RGB | null; selectionMode: boolean } {\n if (!rect) {\n const cursorOut = this.lastSelectionMode && this.lastSelectionCursor ? this.lastSelectionCursor : this.lastCursor\n return { cursor: cursorOut, cursorColor: this.lastCursorColor, selectionMode: this.lastSelectionMode }\n }\n\n const now = Date.now()\n\n // ----- 1. Normal corner-based cursor assembly (original, unchanged logic) -----\n this.assemblies = this.assemblies.filter((a) => now - a.startTime < this.ASSEMBLY_TIMEOUT)\n\n let completedCursor: CursorRect | null = null\n for (const assembly of this.assemblies) {\n if (assembly.state === 'complete') continue\n this.processRectAgainstAssembly(rect, assembly)\n if (this.isAssemblyComplete(assembly)) {\n completedCursor = this.buildCursorFromAssembly(assembly)\n assembly.state = 'complete'\n }\n }\n\n if (completedCursor) {\n this.lastCursor = completedCursor\n this.assemblies = []\n // Normal cursor confirmed → exit selection mode immediately\n this.lastSelectionMode = false\n this.lastSelectionCursor = null\n this.selectionRectSamples = []\n return { cursor: completedCursor, cursorColor: this.lastCursorColor, selectionMode: false }\n }\n\n // Start a new assembly from a type-4 TL corner candidate\n if (rect.type === 4 && rect.size.width === 3 && rect.size.height === 1) {\n const newAssembly = this.createNewAssembly(rect)\n this.assemblies.push(newAssembly)\n if ('color' in rect && rect.color) {\n this.lastCursorColor = rect.color\n }\n if (this.assemblies.length > 2) {\n this.assemblies = this.assemblies.slice(-2)\n }\n }\n\n // ----- 2. Selection border detection (only if normal assembly didn't complete) -----\n this.recordSelectionSample(rect, now)\n const selectionCursor = this.detectSelectionCursor(now)\n\n if (selectionCursor) {\n this.lastSelectionCursor = selectionCursor\n this.lastSelectionMode = true\n this.lastSelectionSeenAt = now\n // Clear after a full detection so next cycle starts with a clean buffer.\n // This prevents old edges (still within the 50ms window) from polluting\n // the next detection and making the old position win the score race.\n this.selectionRectSamples = []\n return { cursor: selectionCursor, cursorColor: this.lastCursorColor, selectionMode: true }\n }\n\n // Incremental update path: while in selection mode, M8 can redraw only one\n // border segment at a time. Keep rect tracking independent from key events\n // by updating the previous selection rectangle from partial edge draws.\n const incrementalSelection = this.updateSelectionFromPartialRect(rect)\n if (incrementalSelection) {\n this.lastSelectionCursor = incrementalSelection\n this.lastSelectionMode = true\n this.lastSelectionSeenAt = now\n return { cursor: incrementalSelection, cursorColor: this.lastCursorColor, selectionMode: true }\n }\n\n // Stay in selection mode for SELECTION_HOLD_DURATION after last fresh detection\n if (this.lastSelectionMode) {\n if (now - this.lastSelectionSeenAt > this.SELECTION_HOLD_DURATION) {\n this.lastSelectionMode = false\n this.lastSelectionCursor = null\n } else if (this.lastSelectionCursor) {\n return { cursor: this.lastSelectionCursor, cursorColor: this.lastCursorColor, selectionMode: true }\n }\n }\n\n return { cursor: this.lastCursor, cursorColor: this.lastCursorColor, selectionMode: false }\n }\n\n private updateSelectionFromPartialRect(rect: RectCommand): CursorRect | null {\n if (!this.lastSelectionMode || !this.lastSelectionCursor) return null\n\n const w = rect.size.width\n const h = rect.size.height\n const isHorizontal = h === 1 && w >= 5\n const isVertical = w === 1 && h >= 3\n if (!isHorizontal && !isVertical) return null\n\n const prev = this.lastSelectionCursor\n let left = prev.x - 1\n let top = prev.y - 1\n let right = prev.x + prev.w\n let bottom = prev.y + prev.h\n\n if (isHorizontal) {\n const lineLeft = rect.pos.x\n const lineRight = rect.pos.x + w - 1\n const prevSpan = right - left + 1\n const overlap = Math.min(lineRight, right) - Math.max(lineLeft, left) + 1\n\n // Reject unrelated horizontal lines.\n if (overlap < Math.max(3, Math.floor(prevSpan * 0.6))) return null\n\n const y = rect.pos.y\n if (Math.abs(y - top) <= Math.abs(y - bottom)) {\n top = y\n } else {\n bottom = y\n }\n\n // Keep lateral bounds coherent with the longest observed edge.\n if (lineRight - lineLeft > right - left) {\n left = lineLeft\n right = lineRight\n }\n } else {\n const lineTop = rect.pos.y\n const lineBottom = rect.pos.y + h - 1\n const prevSpan = bottom - top + 1\n const overlap = Math.min(lineBottom, bottom) - Math.max(lineTop, top) + 1\n\n // Reject unrelated vertical lines.\n if (overlap < Math.max(3, Math.floor(prevSpan * 0.6))) return null\n\n const x = rect.pos.x\n if (Math.abs(x - left) <= Math.abs(x - right)) {\n left = x\n } else {\n right = x\n }\n\n // Keep vertical bounds coherent with the longest observed edge.\n if (lineBottom - lineTop > bottom - top) {\n top = lineTop\n bottom = lineBottom\n }\n }\n\n const outerW = right - left + 1\n const outerH = bottom - top + 1\n if (outerW < 3 || outerH < 3) return null\n\n return {\n x: left + 1,\n y: top + 1,\n w: outerW - 2,\n h: outerH - 2,\n }\n }\n\n private recordSelectionSample(rect: RectCommand, now: number): void {\n const w = rect.size.width\n const h = rect.size.height\n\n // w >= 5 excludes the normal corner cursor's 3px-wide pieces (TL/TR/BL/BR are all 3x1)\n // h >= 3 excludes the 1x2 corner-edge pieces\n const isHorizontal = h === 1 && w >= 5\n const isVertical = w === 1 && h >= 3\n if (!isHorizontal && !isVertical) return\n\n this.selectionRectSamples.push({\n x: rect.pos.x,\n y: rect.pos.y,\n w,\n h,\n ts: now,\n hasColor: rect.type === 2 || rect.type === 4,\n })\n\n this.selectionRectSamples = this.selectionRectSamples.filter((sample) => now - sample.ts <= this.SELECTION_SAMPLE_WINDOW)\n }\n\n private detectSelectionCursor(now: number): CursorRect | null {\n this.selectionRectSamples = this.selectionRectSamples.filter((sample) => now - sample.ts <= this.SELECTION_SAMPLE_WINDOW)\n if (this.selectionRectSamples.length < 4) return null\n\n const horizontals = this.selectionRectSamples.filter((sample) => sample.h === 1 && sample.w >= 3)\n const verticals = this.selectionRectSamples.filter((sample) => sample.w === 1 && sample.h >= 3)\n if (horizontals.length === 0 || verticals.length === 0) return null\n\n const refCursor = this.lastSelectionMode && this.lastSelectionCursor ? this.lastSelectionCursor : this.lastCursor\n const prevCenterX = refCursor.x + refCursor.w / 2\n const prevCenterY = refCursor.y + refCursor.h / 2\n\n let best: { cursor: CursorRect; score: number; latestTs: number } | null = null\n\n for (const top of horizontals) {\n for (const bottom of horizontals) {\n if (bottom.y <= top.y) continue\n if (bottom.x !== top.x || bottom.w !== top.w) continue\n\n const outerH = bottom.y - top.y + 1\n if (outerH < 3) continue\n\n // M8 may draw verticals starting 1px inside the corners:\n // left side: x=top.x, y=top.y+1, h=outerH-2 (skipping corners)\n // OR: x=top.x, y=top.y, h=outerH (covering corners)\n // Both are valid – check that the segment spans from near top to near bottom.\n const left = verticals.find((v) => v.x === top.x && v.y >= top.y && v.y <= top.y + 2 && v.y + v.h - 1 >= bottom.y - 1)\n if (!left) continue\n\n const rightX = top.x + top.w - 1\n const right = verticals.find((v) => v.x === rightX && v.y >= top.y && v.y <= top.y + 2 && v.y + v.h - 1 >= bottom.y - 1)\n if (!right) continue\n\n const cursor = {\n x: top.x + 1,\n y: top.y + 1,\n w: top.w - 2,\n h: outerH - 2,\n }\n\n if (cursor.w <= 0 || cursor.h <= 0) continue\n\n const latestTs = Math.max(top.ts, bottom.ts, left.ts, right.ts)\n\n // Tiebreak by distance from previous cursor center (prefer spatially close)\n const centerX = cursor.x + cursor.w / 2\n const centerY = cursor.y + cursor.h / 2\n const dx = centerX - prevCenterX\n const dy = centerY - prevCenterY\n const score = dx * dx + dy * dy\n\n // Primary sort: freshest quad wins (avoids stale previous-frame edges\n // shadowing new ones when both are within the SELECTION_SAMPLE_WINDOW).\n // Secondary sort: closest to previous center.\n if (!best || latestTs > best.latestTs || (latestTs === best.latestTs && score < best.score)) {\n best = { cursor, score, latestTs }\n }\n }\n }\n\n return best?.cursor ?? null\n }\n\n /**\n * Create a new cursor assembly from TL starter\n */\n private createNewAssembly(tlRect: RectCommand): CursorAssembly {\n const tlX = tlRect?.pos.x ?? 0\n const tlY = tlRect?.pos.y ?? 0\n\n return {\n tl: {\n x: tlX,\n y: tlY,\n expectedVert: { x: tlX, y: tlY + 1 }, // 1x2 below the 3x1\n expectedTip: { x: tlX + 1, y: tlY + 1 }, // 1x1 inside corner\n foundVert: false,\n foundTip: false,\n },\n tr: null,\n bl: null,\n br: null,\n startTime: Date.now(),\n state: 'building',\n }\n }\n\n /**\n * Process a rectangle against a specific assembly\n */\n private processRectAgainstAssembly(rect: RectCommand, assembly: CursorAssembly): void {\n // Check if rectangle matches any expected pattern in the assembly\n\n // 1. Check TL corner verification pieces\n this.checkCornerVerification(rect, assembly.tl)\n\n // 2. Look for TR corner (3x1, type 3, same y as TL)\n if (\n !assembly.tr &&\n rect.type === 3 &&\n rect.size.width === 3 &&\n rect.size.height === 1 &&\n rect.pos.y === assembly.tl.y &&\n rect.pos.x > assembly.tl.x\n ) {\n // Must be to the right\n\n assembly.tr = {\n x: rect.pos.x,\n y: rect.pos.y,\n expectedVert: { x: rect.pos.x + 2, y: rect.pos.y + 1 }, // 1x2 right of the 3x1\n expectedTip: { x: rect.pos.x + 1, y: rect.pos.y + 1 }, // 1x1 inside corner\n foundVert: false,\n foundTip: false,\n }\n }\n\n // 3. Look for BL corner (3x1, type 3, same x as TL)\n if (\n !assembly.bl &&\n rect.type === 3 &&\n rect.size.width === 3 &&\n rect.size.height === 1 &&\n rect.pos.x === assembly.tl.x &&\n rect.pos.y > assembly.tl.y\n ) {\n // Must be below\n\n assembly.bl = {\n x: rect.pos.x,\n y: rect.pos.y,\n expectedVert: { x: rect.pos.x, y: rect.pos.y - 2 }, // 1x2 above the 3x1\n expectedTip: { x: rect.pos.x + 1, y: rect.pos.y - 1 }, // 1x1 inside corner\n foundVert: false,\n foundTip: false,\n }\n }\n\n // 4. Look for BR corner (3x1, type 3)\n if (\n assembly.tr &&\n assembly.bl &&\n !assembly.br &&\n rect.type === 3 &&\n rect.size.width === 3 &&\n rect.size.height === 1 &&\n rect.pos.x === assembly.tr.x &&\n rect.pos.y === assembly.bl.y\n ) {\n assembly.br = {\n x: rect.pos.x,\n y: rect.pos.y,\n expectedVert: { x: rect.pos.x + 2, y: rect.pos.y - 2 }, // 1x2 above-right\n expectedTip: { x: rect.pos.x + 1, y: rect.pos.y - 1 }, // 1x1 inside corner\n foundVert: false,\n foundTip: false,\n }\n }\n\n // 5. Check verification pieces for other corners\n if (assembly.tr) this.checkCornerVerification(rect, assembly.tr)\n if (assembly.bl) this.checkCornerVerification(rect, assembly.bl)\n if (assembly.br) this.checkCornerVerification(rect, assembly.br)\n }\n\n /**\n * Check if a rectangle verifies a corner pattern\n */\n private checkCornerVerification(rect: RectCommand, corner: CornerCandidate): void {\n if (!corner.expectedVert || !corner.expectedTip) return\n\n // Check for 1x2 vertical edge\n if (\n !corner.foundVert &&\n rect.type === 3 &&\n rect.size.width === 1 &&\n rect.size.height === 2 &&\n rect.pos.x === corner.expectedVert.x &&\n rect.pos.y === corner.expectedVert.y\n ) {\n corner.foundVert = true\n }\n\n // Check for 1x1 tip\n if (\n !corner.foundTip &&\n rect.type === 1 &&\n rect.size.width === 1 &&\n rect.size.height === 1 &&\n rect.pos.x === corner.expectedTip.x &&\n rect.pos.y === corner.expectedTip.y\n ) {\n corner.foundTip = true\n }\n }\n\n /**\n * Check if assembly has enough evidence to be a complete cursor\n */\n private isAssemblyComplete(assembly: CursorAssembly): boolean {\n // Must have all 4 corners\n if (!assembly.tr || !assembly.bl || !assembly.br) {\n return false\n }\n\n // Basic cursor geometry checks\n const width = assembly.tr.x - assembly.tl.x\n const height = assembly.bl.y - assembly.tl.y\n\n // Width and height should be positive\n if (width <= 0 || height <= 0) {\n return false\n }\n\n // Check if corners form a proper rectangle\n if (assembly.br.x !== assembly.tr.x || assembly.br.y !== assembly.bl.y) {\n return false\n }\n\n // We need some verification evidence to be confident\n // Count how many corners have at least one verification piece\n const corners = [assembly.tl, assembly.tr, assembly.bl, assembly.br]\n const verifiedCorners = corners.filter((c) => c.foundVert || c.foundTip).length\n\n // Require at least 2 corners to have verification\n return verifiedCorners >= 2\n }\n\n /**\n * Build final cursor rectangle from complete assembly\n */\n private buildCursorFromAssembly(assembly: CursorAssembly): CursorRect {\n // Must have all 4 corners\n if (!assembly.tr || !assembly.bl || !assembly.br) {\n return defaultCursor\n }\n\n // Basic cursor geometry checks\n const width = assembly.tr.x - assembly.tl.x - 1\n const height = assembly.bl.y - assembly.tl.y - 1\n\n // Cursor is 1 pixel inside the 3x1 edges\n const x = assembly.tl.x + 1\n const y = assembly.tl.y + 1\n const w = width\n const h = height\n\n return { x, y, w, h }\n }\n\n /**\n * Reset detection\n */\n reset(): void {\n this.assemblies = []\n this.selectionRectSamples = []\n this.lastCursor = { x: 0, y: 0, w: 1, h: 1 }\n this.lastSelectionCursor = null\n this.lastSelectionMode = false\n this.lastSelectionSeenAt = 0\n }\n}\n\nexport const SCREEN_ROWS = 24\nexport const SCREEN_COLUMNS = 40\n\n/** Colour of every character cell, row by row ('#rrggbb', null where nothing was drawn). */\nexport type ScreenCellColors = { fg: (string | null)[][]; bg: (string | null)[][] }\n\n/** The protocol events the extractor listens to (a ConnectedBus's protocol.eventBus fits). */\nexport interface DrawEventSource {\n on(event: 'text', callback: (data: CharacterCommand) => void): void\n on(event: 'rect', callback: (data: RectCommand) => void): void\n on(event: 'systemInfo', callback: (data: SystemInfos) => void): void\n off(event: 'text', callback: (data: CharacterCommand) => void): void\n off(event: 'rect', callback: (data: RectCommand) => void): void\n off(event: 'systemInfo', callback: (data: SystemInfos) => void): void\n}\n\nexport interface ViewExtractor {\n /** Whole M8 screen as text rows, one string per row (columns preserved, trailing spaces trimmed). */\n getScreenLines(): string[]\n /** Colours of the whole M8 screen, same grid as getScreenLines. */\n getScreenCellColors(): ScreenCellColors\n dispose(): void\n}\n\nconst rgbHex = (c: RGB | null | undefined): string | null => (c ? `#${[c.r, c.g, c.b].map((v) => v.toString(16).padStart(2, '0')).join('')}` : null)\n\n/** Listens to the draw stream and keeps `store` up to date. */\nexport function registerViewExtractor(events: DrawEventSource, store: M8Store): ViewExtractor {\n const CursorAssembly = new AssemblyCursorExtractor()\n // Title extractor with early acceptance\n class AssemblyViewNameExtractor {\n private rowChars: Map<number, { ch: string; fg: RGB | null; bg: RGB | null }> = new Map()\n //private lastChange = 0\n private timer: ReturnType<typeof setTimeout> | null = null\n private knownViews: string[] = []\n private earlyAccepted = false\n\n init() {\n this.knownViews = Array.from(getViewNames())\n }\n\n private normForCompare(s: string) {\n s = s.includes('live') ? 'song' : s\n return s.toLowerCase().trim().replace(/[{}]/g, '0') //.replace(/[^a-z0-9]/g, '')\n }\n\n private normalizeFinal(titleRaw: string) {\n // Trim, remove trailing hex token and optional ^/*, strip spaces/punct, lower, cap 20\n const trimmed = titleRaw.trim()\n\n // biome-ignore lint/complexity/noUselessEscapeInRegex: needed for * or ^\n const noTrail = trimmed.replace(/[{}]/g, '0').replace(/\\s[0-9a-fA-F]{1,2}[\\^\\*]?\\s*$/, '')\n const cleaned = noTrail\n .replace(/[^a-z0-9 ]/gi, '')\n .replace(/\\s+/g, ' ')\n .trim()\n const noSpaces = cleaned.replace(/\\s+/g, '').toLowerCase().slice(0, 20)\n return noSpaces.includes('live') ? 'song' : noSpaces\n }\n\n private pickColors() {\n // pick first non-space char colors\n const entries = Array.from(this.rowChars.entries()).sort((a, b) => a[0] - b[0])\n for (const [, v] of entries) {\n if (v.ch?.trim()) return { fg: v.fg, bg: v.bg }\n }\n return { fg: null as RGB | null, bg: null as RGB | null }\n }\n\n private emitStable() {\n const entries = Array.from(this.rowChars.entries()).sort((a, b) => a[0] - b[0])\n const raw = entries.map(([, v]) => v.ch || ' ').join('')\n const normalized = this.normalizeFinal(raw)\n const { fg, bg } = this.pickColors()\n store.set('viewTitle', raw || null)\n store.set('titleColor', fg)\n store.set('backgroundColor', bg)\n store.set('viewName', normalized || null)\n }\n\n private tryEarlyAccept(currentGx: number) {\n if (this.earlyAccepted) return true\n if (!this.knownViews || this.knownViews.length === 0) return false\n // Build partial from 0..currentGx (inclusive), preserving spaces\n const upto = Math.max(0, Math.min(currentGx, 39))\n const partialRaw = Array.from({ length: upto + 1 }, (_, i) => this.rowChars.get(i)?.ch || ' ').join('')\n const partial = this.normForCompare(partialRaw)\n if (!partial || partial.length < 2) return false\n const candidates = this.knownViews.filter((v) => v.startsWith(partial))\n if (candidates.length === 1) {\n const pick = candidates[0].slice(0, 20)\n const { fg, bg } = this.pickColors()\n store.set('viewTitle', partial)\n store.set('titleColor', fg)\n store.set('backgroundColor', bg)\n store.set('viewName', pick)\n this.earlyAccepted = true\n return true\n }\n return false\n }\n\n processChar(cmd: CharacterCommand) {\n const { cellW, cellH, offX, offY } = store.get('cellMetrics')\n // Use floor to consistently map pixel positions to grid positions\n const gx = Math.floor((cmd.pos.x - offX) / cellW)\n const gy = Math.floor((cmd.pos.y - offY) / cellH)\n\n // If we observe rows beyond the title area, finalize immediately\n if (gy > 4) {\n if (this.rowChars.size > 0) {\n if (this.timer) {\n clearTimeout(this.timer)\n this.timer = null\n }\n this.emitStable()\n }\n return\n }\n\n // Title row detection: based on debug logs, title consistently at gy==3\n // across font modes (Model:02 + Headless). Narrow acceptance to row 3\n // to reduce noise and speed early acceptance.\n if (gy !== 3) return\n\n if (gx >= 30) return // cap title length to 20\n\n this.rowChars.set(gx, { ch: cmd.character, fg: cmd.foreground, bg: cmd.background })\n //this.lastChange = Date.now()\n\n // Early acceptance attempt\n if (this.tryEarlyAccept(gx)) {\n // continue to collect for full colors/raw title\n }\n\n if (this.timer) clearTimeout(this.timer)\n this.timer = setTimeout(() => {\n // If no match yet, accept stabilized buffer\n this.emitStable()\n }, STABILIZE_MS)\n }\n\n reset() {\n this.rowChars.clear()\n if (this.timer) {\n clearTimeout(this.timer)\n this.timer = null\n }\n this.earlyAccepted = false\n }\n }\n\n const extractor = new AssemblyViewNameExtractor()\n extractor.init()\n\n // Character grid tracker for text extraction\n class CharacterGridTracker {\n // Grid: y -> x -> { char, fg, bg }\n private grid: Map<number, Map<number, { ch: string; fg: RGB; bg: RGB }>> = new Map()\n\n processChar(cmd: CharacterCommand) {\n const { cellW, cellH, offX, offY } = store.get('cellMetrics')\n const gx = Math.floor((cmd.pos.x - offX) / cellW)\n const gy = Math.floor((cmd.pos.y - offY) / cellH)\n\n if (!this.grid.has(gy)) {\n this.grid.set(gy, new Map())\n }\n this.grid.get(gy)?.set(gx, { ch: cmd.character.replace(/[{}]/g, '0'), fg: cmd.foreground, bg: cmd.background })\n\n // Clean up old rows (keep only last 30 rows)\n if (this.grid.size > 30) {\n console.log('too much rows', this.grid.size)\n const sortedKeys = Array.from(this.grid.keys()).sort((a, b) => a - b)\n const toRemove = sortedKeys.slice(0, sortedKeys.length - 30)\n for (const key of toRemove) {\n this.grid.delete(key)\n }\n }\n // Update highlight info for new cursor position\n this.updateHighlightAtCursor()\n }\n\n updateHighlightAtCursor() {\n const cursorRect = store.get('cursorRect')\n const pos = store.get('cursorPos')\n\n if (!pos || !cursorRect) {\n store.set('textUnderCursor', null)\n store.set('currentLine', null)\n store.set('highlightColor', null)\n return\n }\n\n const { cellW, offX } = store.get('cellMetrics')\n\n // Convert pixel cursor bounds to grid coordinates.\n // startGx uses Math.round to absorb the 1–2px leftward overhang of the cursor\n // border so we don't pick up the character in the column to the left.\n // endGx uses Math.floor: the cursor's right pixel (tr.x - 1) falls exactly at\n // the last cell's rightmost pixel, so floor is already correct there.\n const startGx = Math.round((cursorRect.x - offX) / cellW)\n const endGx = Math.floor((cursorRect.x + cursorRect.w - 1 - offX) / cellW)\n const gy = pos.y // Use cursor's grid Y position\n\n // Extract text within cursor bounds (no hue filtering)\n const textUnderCursor = this.getTextUnderCursor(startGx, endGx, gy)\n\n // Extract highlight color from text under cursor (stable, not pulsing)\n const textHighlightColor = this.getHighlightColorFromText(startGx, endGx, gy)\n\n const currentLine = this.getCurrentLine(gy)\n\n store.set('textUnderCursor', textUnderCursor)\n store.set('currentLine', currentLine)\n\n // Use stable text color as highlight color\n if (textHighlightColor) {\n store.set('highlightColor', textHighlightColor)\n }\n }\n\n getTextUnderCursor(startGx: number, endGx: number, gy: number): string | null {\n const row = this.grid.get(gy)\n if (!row) return null\n\n // Extract all characters within cursor bounds without hue filtering\n const text: string[] = []\n for (let x = startGx; x <= endGx; x++) {\n const cell = row.get(x)\n if (cell) {\n text.push(cell.ch)\n }\n }\n\n return text.join('').trim() || null\n }\n\n /**\n * Get the stable highlight color from text under cursor\n * This is the foreground color of the text, which doesn't pulse\n */\n getHighlightColorFromText(startGx: number, endGx: number, gy: number): RGB | null {\n const row = this.grid.get(gy)\n if (!row) return null\n\n // Find first non-space character within cursor bounds\n for (let x = startGx; x <= endGx; x++) {\n const cell = row.get(x)\n if (cell?.ch.trim()) {\n return cell.fg\n }\n }\n return null\n }\n\n getCurrentLine(gy: number): string | null {\n const row = this.grid.get(gy)\n if (!row || row.size === 0) return null\n\n // Get all characters in the row, sorted by x position\n const sortedEntries = Array.from(row.entries()).sort((a, b) => a[0] - b[0])\n const line = sortedEntries.map(([, v]) => v.ch).join('')\n return line.trim() || null\n }\n\n /**\n * Whole screen as text, one string per row. Unlike getCurrentLine, columns are\n * preserved (missing cells become spaces) so x offsets stay meaningful.\n */\n getScreenLines(height: number): string[] {\n const lines: string[] = []\n for (let gy = 0; gy < height; gy += 1) {\n const row = this.grid.get(gy)\n if (!row || row.size === 0) {\n lines.push('')\n continue\n }\n let maxX = 0\n for (const x of row.keys()) if (x > maxX) maxX = x\n const chars: string[] = []\n for (let x = 0; x <= maxX; x += 1) chars.push(row.get(x)?.ch ?? ' ')\n lines.push(chars.join('').trimEnd())\n }\n return lines\n }\n\n /** Foreground/background colour of every cell, `width` entries per row. */\n getCellColors(height: number, width: number): ScreenCellColors {\n const fg: (string | null)[][] = []\n const bg: (string | null)[][] = []\n for (let gy = 0; gy < height; gy += 1) {\n const row = this.grid.get(gy)\n const fgRow: (string | null)[] = []\n const bgRow: (string | null)[] = []\n for (let x = 0; x < width; x += 1) {\n const cell = row?.get(x)\n fgRow.push(rgbHex(cell?.fg))\n bgRow.push(rgbHex(cell?.bg))\n }\n fg.push(fgRow)\n bg.push(bgRow)\n }\n return { fg, bg }\n }\n\n reset() {\n this.grid.clear()\n }\n }\n\n const charGridTracker = new CharacterGridTracker()\n\n // Temporary debug: set window.__debugRects = true in the console for 1 second of rect logging\n let debugRectEndTime = 0\n if (typeof window !== 'undefined') {\n const win = window as unknown as Record<string, unknown>\n win.__debugRects = false\n Object.defineProperty(win, '__debugRects', {\n configurable: true,\n set(v: unknown) {\n if (v) debugRectEndTime = Date.now() + 1000\n },\n })\n }\n\n const cursorExtractor = (data: RectCommand) => {\n if (!data) return\n\n if (Date.now() < debugRectEndTime) {\n console.log(`[rect] type=${data.type} pos=(${data.pos.x},${data.pos.y}) size=${data.size.width}x${data.size.height}`)\n }\n\n const { cellW, cellH, offX, offY } = store.get('cellMetrics')\n const { cursor, cursorColor, selectionMode } = CursorAssembly.processRect(data)\n if (cursorColor && rgbHex(cursorColor) !== rgbHex(store.get('cursorColor'))) {\n store.set('cursorColor', cursorColor)\n }\n const { x, y, w, h } = cursor\n // Use top-left of cursor to determine grid position (same as characters).\n // Math.round instead of Math.floor absorbs the 1–2px leftward overhang of the\n // cursor border corners so gx maps to the correct cell (not the one to its left).\n const gx = Math.round((x - offX) / cellW)\n const gy = Math.round((y - offY) / cellH)\n\n const prevPos = store.get('cursorPos')\n const prevSelectionMode = store.get('selectionMode')\n\n if (!selectionMode) {\n // Normal cursor: update when grid position changed OR when\n // transitioning out of selection mode (rect size changes dramatically).\n const modeJustChanged = prevSelectionMode !== selectionMode\n if (!prevPos || prevPos.x !== gx || prevPos.y !== gy || modeJustChanged) {\n store.set('cursorPos', { x: gx, y: gy })\n store.set('cursorRect', { x, y, w, h })\n }\n } else {\n // Selection mode: update independently — selection can grow at the same\n // anchor grid cell (SHIFT+direction extends rect without moving anchor).\n const prevRect = store.get('cursorRect')\n if (!prevPos || prevPos.x !== gx || prevPos.y !== gy) {\n store.set('cursorPos', { x: gx, y: gy })\n }\n if (!prevRect || prevRect.x !== x || prevRect.y !== y || prevRect.w !== w || prevRect.h !== h) {\n store.set('cursorRect', { x, y, w, h })\n }\n }\n\n if (prevSelectionMode !== selectionMode) {\n store.set('selectionMode', selectionMode)\n }\n }\n\n const systemInfoHandler = (info: SystemInfos) => {\n store.set('deviceModel', info.model)\n store.set('fontMode', info.fontMode)\n store.set('cellMetrics', { cellW: info.spacingX, cellH: info.spacingY, offX: info.offX, offY: info.offY })\n store.set('systemInfo', {\n model: info.model,\n fontMode: info.fontMode,\n spacingX: info.spacingX,\n spacingY: info.spacingY,\n offX: info.offX,\n offY: info.offY,\n screenWidth: info.screenWidth ?? 480,\n screenHeight: info.screenHeight ?? 320,\n rectOffset: info.rectOffset,\n })\n }\n\n const onText = (d: CharacterCommand) => {\n extractor.processChar(d)\n charGridTracker.processChar(d)\n }\n events.on('text', onText)\n events.on('rect', cursorExtractor)\n events.on('systemInfo', systemInfoHandler)\n\n return {\n getScreenLines: () => charGridTracker.getScreenLines(SCREEN_ROWS),\n getScreenCellColors: () => charGridTracker.getCellColors(SCREEN_ROWS, SCREEN_COLUMNS),\n dispose: () => {\n events.off('text', onText)\n events.off('rect', cursorExtractor)\n events.off('systemInfo', systemInfoHandler)\n extractor.reset()\n charGridTracker.reset()\n },\n }\n}\n","import type { M8State } from '../../types'\nimport type { M8Store } from '../store'\n\n// The state exposed to SDK clients, read from the engine store\nexport const getCurrentState = (store: M8Store): M8State => {\n const macroStatus = store.get('macroStatus')\n\n return {\n viewName: store.get('viewName'),\n viewTitle: store.get('viewTitle'),\n minimapKey: store.get('minimapKey'),\n cursorPos: store.get('cursorPos'),\n cursorRect: store.get('cursorRect'),\n selectionMode: store.get('selectionMode'),\n highlightColor: store.get('highlightColor'),\n titleColor: store.get('titleColor'),\n backgroundColor: store.get('backgroundColor'),\n cursorColor: store.get('cursorColor'),\n textUnderCursor: store.get('textUnderCursor'),\n currentLine: store.get('currentLine'),\n deviceModel: store.get('deviceModel'),\n fontMode: store.get('fontMode'),\n systemInfo: store.get('systemInfo') as M8State['systemInfo'],\n macroRunning: macroStatus.running,\n macroCurrentStep: macroStatus.currentStep,\n macroSequenceLength: macroStatus.sequenceLength,\n }\n}\n\n// Convert text grid coordinates to pixel coordinates\n// Text grid: x (0-39), y (0-23) - independent of font size\n// Pixel: depends on current cell metrics (cellW, cellH, offX, offY)\n// Send the center of the target cell so M8 places the cursor exactly there.\n// gx extraction in viewExtractor uses Math.round to absorb the cursor border\n// overhang, so no additional x compensation is needed here.\nexport const textGridToPixel = (store: M8Store, gridX: number, gridY: number): { x: number; y: number } => {\n const cellMetrics = store.get('cellMetrics')\n\n // Center of cell (gridX, gridY) in raw M8 protocol pixel space\n const pixelX = gridX * cellMetrics.cellW + cellMetrics.offX + cellMetrics.cellW / 2\n const pixelY = gridY * cellMetrics.cellH + cellMetrics.offY + cellMetrics.cellH / 2\n\n return { x: pixelX, y: pixelY }\n}\n","import type { M8HostMethods, M8KeyName } from '../../types'\r\nimport { pressKeys } from '../keys'\r\nimport { SCREEN_COLUMNS, SCREEN_ROWS, type ScreenCellColors } from '../viewExtractor'\r\nimport type { HostIO } from './hostIO'\r\nimport { getCurrentState, textGridToPixel } from './hostState'\r\nimport type { createValueEditors } from './valueEditors'\r\n\r\nconst keyNamesToMask = (keys: M8KeyName[]) =>\r\n pressKeys({\r\n left: keys.includes('left'),\r\n right: keys.includes('right'),\r\n up: keys.includes('up'),\r\n down: keys.includes('down'),\r\n shift: keys.includes('shift'),\r\n play: keys.includes('play'),\r\n opt: keys.includes('opt'),\r\n edit: keys.includes('edit'),\r\n })\r\n\r\nexport type HostMethodsDeps = {\r\n io: HostIO\r\n editors: ReturnType<typeof createValueEditors>\r\n setValueFloat: (targetFloat: number) => Promise<boolean>\r\n browseFile: (targetText: string, exact: boolean) => Promise<boolean>\r\n navigateToView: (viewName: string) => Promise<boolean>\r\n navigateTo: (point: { x: number; y: number }) => Promise<boolean>\r\n enableMidiTransport: () => Promise<{ ok: boolean; error?: string }>\r\n getScreenCellColors: () => ScreenCellColors\r\n}\r\n\r\n/** The methods an SDK client can call on the engine (transport-agnostic). */\r\nexport const createHostMethods = ({\r\n io,\r\n editors,\r\n setValueFloat,\r\n browseFile,\r\n navigateToView,\r\n navigateTo,\r\n enableMidiTransport,\r\n getScreenCellColors,\r\n}: HostMethodsDeps): M8HostMethods => {\r\n const { debugLog, hasBus, sendKeys, pressAndRelease, store, getScreenLines } = io\r\n\r\n // Wrap a method so it warns and returns `fallback` when no M8 is connected\r\n const guarded =\r\n <A extends unknown[], R>(name: string, fallback: R, fn: (...args: A) => Promise<R>) =>\r\n async (...args: A): Promise<R> => {\r\n if (!hasBus()) {\r\n console.warn(`[M8SDK] ${name}: no bus connection`)\r\n return fallback\r\n }\r\n return fn(...args)\r\n }\r\n\r\n return {\r\n navigateToView: guarded('navigateToView', false, async (viewName: string): Promise<boolean> => {\r\n debugLog('[M8SDK] Executing navigateToView:', viewName)\r\n return navigateToView(viewName)\r\n }),\r\n navigateTo: guarded('navigateTo', false, async (gridX: number, gridY: number): Promise<boolean> => {\r\n // Convert text grid coordinates (0-39, 0-23) to pixel coordinates\r\n return navigateTo(textGridToPixel(store, gridX, gridY))\r\n }),\r\n setValueToHex: guarded('setValueToHex', false, async (hex: number): Promise<boolean> => {\r\n debugLog('[M8SDK] Executing setValueToHex:', hex)\r\n return editors.setValueToHex(hex)\r\n }),\r\n setValueToInt: guarded('setValueToInt', false, async (targetInt: number): Promise<boolean> => {\r\n debugLog('[M8SDK] Executing setValueToInt:', targetInt)\r\n return editors.setValueToInt(targetInt)\r\n }),\r\n setValueFloat: guarded('setValueFloat', false, async (targetFloat: number): Promise<boolean> => {\r\n debugLog('[M8SDK] Executing setValueFloat:', targetFloat)\r\n return setValueFloat(targetFloat)\r\n }),\r\n setNote: guarded('setNote', false, async (noteString: string): Promise<boolean> => {\r\n debugLog('[M8SDK] Executing setNote:', noteString)\r\n return editors.setNote(noteString)\r\n }),\r\n setValueToString: guarded(\r\n 'setValueToString',\r\n false,\r\n async (targetString: string, exact: boolean = true, searchInCurrentLine: boolean = false): Promise<boolean> => {\r\n debugLog('[M8SDK] Executing setValueToString:', { targetString, exact, searchInCurrentLine })\r\n return editors.setValueToString(targetString, exact, searchInCurrentLine)\r\n },\r\n ),\r\n browseFile: guarded('browseFile', false, async (targetText: string, exact: boolean = true): Promise<boolean> => {\r\n debugLog('[M8SDK] Executing browseFile:', { targetText, exact })\r\n return browseFile(targetText, exact)\r\n }),\r\n sendKeyDown: guarded('sendKeyDown', undefined, async (keys: M8KeyName[]): Promise<void> => {\r\n debugLog('[M8SDK] Executing sendKeyDown:', keys)\r\n sendKeys(keyNamesToMask(keys))\r\n }),\r\n sendKeyUp: guarded('sendKeyUp', undefined, async (): Promise<void> => {\r\n debugLog('[M8SDK] Executing sendKeyUp')\r\n sendKeys(0)\r\n }),\r\n sendKeyPress: guarded('sendKeyPress', undefined, async (keys: M8KeyName[]): Promise<void> => {\r\n debugLog('[M8SDK] Executing sendKeyPress:', keys)\r\n await pressAndRelease(keyNamesToMask(keys))\r\n }),\r\n getState: async () => getCurrentState(store),\r\n getScreen: async () => ({ width: SCREEN_COLUMNS, height: SCREEN_ROWS, lines: getScreenLines() }),\r\n getScreenCells: async () => ({ width: SCREEN_COLUMNS, height: SCREEN_ROWS, lines: getScreenLines(), ...getScreenCellColors() }),\r\n enableMidiTransport: async () => enableMidiTransport(),\r\n }\r\n}\r\n","// MIDI clock and transport from the M8's USB MIDI output, for SDK clients that follow the\n// song while it plays. The M8 sends them when Project > MIDI Settings > SYNC OUT includes\n// CLOCK / TRANSPORT / SPP (a per-song setting). Independent of React.\n\nimport type { M8TransportEvent } from '../../types'\n\n/** Clock ticks per sixteenth note at 24 PPQ: one phrase step with the default groove. */\nconst TICKS_PER_STEP = 6\n\nexport type MidiTransport = {\n /** Opens the M8 MIDI input (asks for Web MIDI permission the first time). */\n enable: () => Promise<{ ok: boolean; error?: string }>\n disable: () => void\n}\n\nexport const createMidiTransport = (emit: (event: M8TransportEvent) => void): MidiTransport => {\n let input: MIDIInput | null = null\n let ticks = 0\n let lastTickAt: number | null = null\n let msPerTick: number | null = null\n\n const epoch = (timeStamp: number) => performance.timeOrigin + timeStamp\n\n const onMessage = (e: MIDIMessageEvent) => {\n const data = e.data\n if (!data || data.length === 0) return\n const at = epoch(e.timeStamp)\n switch (data[0]) {\n case 0xf8: {\n // Clock: estimate the tick length, report once per step.\n if (lastTickAt != null) {\n const gap = at - lastTickAt\n if (gap > 0 && gap < 200) msPerTick = msPerTick == null ? gap : msPerTick * 0.9 + gap * 0.1\n }\n lastTickAt = at\n // The M8 can send clock without transport (SYNC OUT = CLOCK) and keeps it running\n // while stopped: the count is a monotonic time base, never reset.\n ticks += 1\n if (ticks % TICKS_PER_STEP === 0) emit({ kind: 'clock', ticks, at, msPerTick })\n return\n }\n case 0xfa:\n emit({ kind: 'start', ticks, at, msPerTick })\n return\n case 0xfb:\n emit({ kind: 'continue', ticks, at, msPerTick })\n return\n case 0xfc:\n emit({ kind: 'stop', ticks, at, msPerTick })\n return\n case 0xf2: {\n // Song Position Pointer: sixteenths since the song start.\n const spp = (data[1] ?? 0) | ((data[2] ?? 0) << 7)\n emit({ kind: 'position', ticks, at, msPerTick, spp })\n return\n }\n }\n }\n\n type Result = { ok: boolean; error?: string }\n\n let pending: Promise<Result> | null = null\n /** Counts disables, so an open that one overtook adds no listener. */\n let generation = 0\n\n const open = async (): Promise<Result> => {\n const started = generation\n if (!('requestMIDIAccess' in navigator)) return { ok: false, error: 'Web MIDI is not available in this browser' }\n try {\n const access = await navigator.requestMIDIAccess()\n const m8 = [...access.inputs.values()].find((i) => i.name === 'M8')\n if (!m8) return { ok: false, error: 'no MIDI input named M8' }\n await m8.open()\n // A call that was disabled meanwhile must not leave a listener behind.\n if (generation !== started) return { ok: false, error: 'disabled' }\n m8.addEventListener('midimessage', onMessage)\n input = m8\n return { ok: true }\n } catch (error) {\n return { ok: false, error: error instanceof Error ? error.message : String(error) }\n }\n }\n\n // Calls that come while the input is still opening share its answer: Chrome gives every\n // requestMIDIAccess() its own MIDIInput objects, so two opens would each add the listener and\n // count every message twice (the tick count then runs at twice the clock).\n return {\n enable: () => {\n if (input) return Promise.resolve({ ok: true })\n if (pending) return pending\n const opening = open()\n pending = opening\n void opening.then((result) => {\n // A failure is not kept: the next call tries again.\n if (pending === opening && !result.ok) pending = null\n })\n return opening\n },\n disable: () => {\n generation += 1\n pending = null\n input?.removeEventListener('midimessage', onMessage)\n input = null\n },\n }\n}\n","import { M8KeyMask } from '../keys'\n\n// Pure key-press sequence planners (press/release frames) for editing values.\n\n// Precalculate the key sequence needed to reach target value from current value\n// Uses edit+left/right for ±1 and edit+up/down for ±16\nexport const calculateKeySequence = (currentValue: number, targetValue: number): number[] => {\n const sequence: number[] = []\n let current = currentValue\n const target = targetValue\n\n while (current !== target) {\n const diff = target - current\n const absDiff = Math.abs(diff)\n const direction = diff > 0 ? 1 : -1\n\n if (absDiff >= 16) {\n // Use large steps (±16) - edit+up for +16, edit+down for -16\n const steps16 = Math.floor(absDiff / 16)\n const key = direction > 0 ? M8KeyMask.Up : M8KeyMask.Down\n const keys = M8KeyMask.Edit | key\n\n // Take as many large steps as possible (capped at keep-alive limit)\n const stepsToTake = Math.min(steps16, 10)\n for (let i = 0; i < stepsToTake; i++) {\n // Press key\n sequence.push(keys)\n // Release key\n sequence.push(0)\n current += direction * 16\n if (current === target) break\n }\n } else if (absDiff > 0) {\n // Use fine adjustment (±1) - edit+right for +1, edit+left for -1\n const key = direction > 0 ? M8KeyMask.Right : M8KeyMask.Left\n const keys = M8KeyMask.Edit | key\n\n // Press key\n sequence.push(keys)\n // Release key\n sequence.push(0)\n current += direction * 1\n } else {\n break\n }\n }\n\n return sequence\n}\n\n// Precalculate note moves in semitone space.\n// Uses edit+up/down for octave jumps (±12) and edit+left/right for semitone steps (±1).\nexport const calculateNoteKeySequence = (currentIndex: number, targetIndex: number): number[] => {\n const sequence: number[] = []\n let current = currentIndex\n\n while (current !== targetIndex) {\n const diff = targetIndex - current\n const direction = diff > 0 ? 1 : -1\n\n if (Math.abs(diff) >= 12) {\n const key = direction > 0 ? M8KeyMask.Up : M8KeyMask.Down\n sequence.push(M8KeyMask.Edit | key)\n sequence.push(0)\n current += direction * 12\n } else {\n const key = direction > 0 ? M8KeyMask.Right : M8KeyMask.Left\n sequence.push(M8KeyMask.Edit | key)\n sequence.push(0)\n current += direction\n }\n\n // Safety cap for malformed input.\n if (sequence.length > 2048) {\n break\n }\n }\n\n return sequence\n}\n","import { M8KeyMask } from '../keys'\nimport { normalizeForSearch } from './fileBrowserHelpers'\nimport { type HostIO, wait } from './hostIO'\nimport { calculateKeySequence, calculateNoteKeySequence } from './keySequences'\nimport { parseHexValue, parseIntValue, parseNoteValue } from './valueParsers'\n\n// Edit the value under the M8 cursor using edit+arrow key combos.\nexport const createValueEditors = (io: HostIO) => {\n const { store, debugLog, hasBus, sendKeys, pressAndRelease, waitForTextUnderCursorChange } = io\n\n // Implementation of setValueToHex using edit+navigation keys\n // Precalculates the entire key sequence and sends it to the macroRunner\n const setValueToHexImpl = async (targetHex: number): Promise<boolean> => {\n if (!hasBus()) return false\n\n // Validate target\n targetHex = Math.max(0, Math.min(255, targetHex))\n\n // Read current value using the atom directly (no wait needed)\n const currentText = store.get('textUnderCursor')\n const currentValue = parseHexValue(currentText)\n const isInitialDashDash = currentText?.trim() === '--'\n\n if (currentValue === null) {\n console.warn('[M8SDK] Could not parse current value under cursor:', currentText)\n return false\n }\n\n if (currentValue === targetHex && !isInitialDashDash) {\n debugLog('[M8SDK] Already at target value:', targetHex.toString(16).padStart(2, '0').toUpperCase())\n return true\n }\n\n debugLog(\n `[M8SDK] Setting value from \"${currentText?.trim() ?? 'N/A'}\" (${currentValue.toString(16).padStart(2, '0').toUpperCase()}) to ${targetHex.toString(16).padStart(2, '0').toUpperCase()}`,\n )\n\n // Starting value after entering edit mode\n let startValue = currentValue\n\n // If initial value is '--', we need to press edit first to recall the last value\n if (isInitialDashDash) {\n debugLog('[M8SDK] Initial value is \"--\", pressing edit to recall last value')\n const beforePrime = currentText\n await pressAndRelease(M8KeyMask.Edit)\n await waitForTextUnderCursorChange(beforePrime, 250)\n\n // Read the actual value after pressing edit (not always 00!)\n const newText = store.get('textUnderCursor')\n const newValue = parseHexValue(newText)\n debugLog(\n `[M8SDK] After edit press, value is now: \"${newText?.trim() ?? 'N/A'}\" (${newValue?.toString(16).padStart(2, '0').toUpperCase() ?? 'N/A'})`,\n )\n\n if (newValue === null) {\n console.warn('[M8SDK] Failed to read value after edit press on \"--\"')\n // Try to exit edit mode and return failure\n await pressAndRelease(M8KeyMask.Edit)\n return false\n }\n\n // Use the actual value after edit press as starting point\n startValue = newValue\n } else {\n // Normal case: enter edit mode\n const beforeEnterEdit = currentText\n await pressAndRelease(M8KeyMask.Edit)\n await waitForTextUnderCursorChange(beforeEnterEdit, 250)\n }\n\n // Precalculate the key sequence (starting from the actual current value after edit mode)\n const keySequence = calculateKeySequence(startValue, targetHex)\n debugLog(\n `[M8SDK] Precalculated ${keySequence.length / 2} key presses from ${startValue.toString(16).padStart(2, '0').toUpperCase()} to ${targetHex.toString(16).padStart(2, '0').toUpperCase()}`,\n )\n\n // Execute the key sequence\n for (const keys of keySequence) {\n sendKeys(keys)\n await wait(30) // Short delay between key presses\n }\n\n // Wait for final value to settle\n await waitForTextUnderCursorChange(currentText, 250)\n\n // Read final value using the atom\n const finalText = store.get('textUnderCursor')\n let finalValue = parseHexValue(finalText)\n\n // Check if precomputation succeeded, if not try iterative mode\n if (finalValue !== targetHex) {\n debugLog('[M8SDK] Precomputation missed target, falling back to iterative mode')\n\n // Continue from current position using iterative approach\n let current = finalValue ?? startValue\n const timeoutMs = 5000\n const startTime = Date.now()\n\n while (current !== targetHex && Date.now() - startTime < timeoutMs) {\n const diff = targetHex - current\n const absDiff = Math.abs(diff)\n const direction = diff > 0 ? 1 : -1\n const beforeStepText = store.get('textUnderCursor')\n\n if (absDiff >= 16) {\n // Large step\n const key = direction > 0 ? M8KeyMask.Up : M8KeyMask.Down\n const keys = M8KeyMask.Edit | key\n await pressAndRelease(keys)\n } else if (absDiff > 0) {\n // Fine adjustment\n const key = direction > 0 ? M8KeyMask.Right : M8KeyMask.Left\n const keys = M8KeyMask.Edit | key\n await pressAndRelease(keys)\n }\n\n await waitForTextUnderCursorChange(beforeStepText, 250)\n const newText = store.get('textUnderCursor')\n const newValue = parseHexValue(newText)\n if (newValue !== null) {\n current = newValue\n debugLog(`[M8SDK] Iterative: current value: ${current.toString(16).padStart(2, '0').toUpperCase()}`)\n }\n }\n\n // Read final value after iterative mode\n finalValue = parseHexValue(store.get('textUnderCursor'))\n }\n\n // Exit edit mode\n await pressAndRelease(M8KeyMask.Edit)\n\n const success = finalValue === targetHex\n debugLog(\n `[M8SDK] Value setting ${success ? 'succeeded' : 'failed'}. Final value: \"${finalText?.trim() ?? 'N/A'}\" (${finalValue?.toString(16).padStart(2, '0').toUpperCase() ?? 'N/A'})`,\n )\n\n return success\n }\n\n // Implementation of setValueToInt using edit+navigation keys\n const setValueToIntImpl = async (targetInt: number): Promise<boolean> => {\n if (!hasBus()) return false\n\n targetInt = Math.floor(targetInt)\n\n const currentText = store.get('textUnderCursor')\n const currentValue = parseIntValue(currentText)\n if (currentValue === null) {\n console.warn('[M8SDK] Could not parse current integer value under cursor:', currentText)\n return false\n }\n\n if (currentValue === targetInt) {\n return true\n }\n\n // Enter edit mode\n await pressAndRelease(M8KeyMask.Edit)\n await waitForTextUnderCursorChange(currentText, 250)\n\n let current = currentValue\n let bestValue = currentValue\n let bestDistance = Math.abs(currentValue - targetInt)\n let valueTwoStepsAgo: number | null = null\n const timeoutMs = 5000\n const startedAt = Date.now()\n\n while (current !== targetInt && Date.now() - startedAt < timeoutMs) {\n const diff = targetInt - current\n const absDiff = Math.abs(diff)\n const direction = diff > 0 ? 1 : -1\n const beforeStepText = store.get('textUnderCursor')\n const previousValue = current\n\n if (absDiff >= 16) {\n const key = direction > 0 ? M8KeyMask.Up : M8KeyMask.Down\n await pressAndRelease(M8KeyMask.Edit | key, 35)\n } else {\n const key = direction > 0 ? M8KeyMask.Right : M8KeyMask.Left\n await pressAndRelease(M8KeyMask.Edit | key, 35)\n }\n\n await waitForTextUnderCursorChange(beforeStepText, 250)\n const newValue = parseIntValue(store.get('textUnderCursor'))\n if (newValue === null || newValue === current) {\n break\n }\n\n const newDistance = Math.abs(newValue - targetInt)\n if (newDistance < bestDistance) {\n bestDistance = newDistance\n bestValue = newValue\n }\n\n // Detect 2-value bounce (A -> B -> A), especially when crossing target.\n const isOscillating = valueTwoStepsAgo !== null && newValue === valueTwoStepsAgo\n const crossesTarget = (previousValue - targetInt) * (newValue - targetInt) < 0\n\n current = newValue\n if (isOscillating && crossesTarget) {\n break\n }\n\n valueTwoStepsAgo = previousValue\n }\n\n // If exact target was not reached, move back to the nearest value observed.\n if (current !== bestValue) {\n const snapStartedAt = Date.now()\n while (current !== bestValue && Date.now() - snapStartedAt < 1500) {\n const diffToBest = bestValue - current\n const absDiffToBest = Math.abs(diffToBest)\n const directionToBest = diffToBest > 0 ? 1 : -1\n const beforeSnapStepText = store.get('textUnderCursor')\n\n if (absDiffToBest >= 16) {\n const key = directionToBest > 0 ? M8KeyMask.Up : M8KeyMask.Down\n await pressAndRelease(M8KeyMask.Edit | key, 35)\n } else {\n const key = directionToBest > 0 ? M8KeyMask.Right : M8KeyMask.Left\n await pressAndRelease(M8KeyMask.Edit | key, 35)\n }\n\n await waitForTextUnderCursorChange(beforeSnapStepText, 250)\n const snappedValue = parseIntValue(store.get('textUnderCursor'))\n if (snappedValue === null || snappedValue === current) {\n break\n }\n current = snappedValue\n }\n }\n\n await pressAndRelease(M8KeyMask.Edit)\n\n const finalValue = parseIntValue(store.get('textUnderCursor'))\n return finalValue === targetInt\n }\n\n // Implementation of setNote using edit+up/down for octave and edit+left/right for semitone\n // If the exact note is unreachable (for example due to scale), this stops on the closest note found.\n const setNoteImpl = async (targetNoteString: string): Promise<boolean> => {\n if (!hasBus()) return false\n\n const normalizedTarget = targetNoteString.trim().toUpperCase()\n\n // Handle special values: '---' (no note / delete) and 'OFF' (note off)\n // opt+edit cycles: regular note → '---' → 'OFF' → '---' → ...\n if (normalizedTarget === '---' || normalizedTarget === 'OFF') {\n const currentText = store.get('textUnderCursor')\n const currentNormalized = currentText?.trim().toUpperCase() ?? ''\n\n if (currentNormalized === normalizedTarget) return true\n\n // If current is a regular note, one opt+edit lands on '---'\n // then a second opt+edit lands on 'OFF'\n await pressAndRelease(M8KeyMask.Opt | M8KeyMask.Edit)\n await waitForTextUnderCursorChange(currentText, 250)\n\n if (normalizedTarget === 'OFF' && currentNormalized !== '---') {\n // Was a regular note: first press gave '---', need a second press for 'OFF'\n const afterFirst = store.get('textUnderCursor')\n if (afterFirst?.trim().toUpperCase() === '---') {\n await pressAndRelease(M8KeyMask.Opt | M8KeyMask.Edit)\n await waitForTextUnderCursorChange(afterFirst, 250)\n }\n }\n\n const finalText = store.get('textUnderCursor')?.trim().toUpperCase()\n debugLog('[M8SDK] setNote special value result:', finalText)\n return finalText === normalizedTarget\n }\n\n const parsedTarget = parseNoteValue(targetNoteString)\n if (!parsedTarget) {\n console.warn('[M8SDK] Invalid note format. Expected like C-1, C#1, D#A, ---, OFF:', targetNoteString)\n return false\n }\n\n let currentText = store.get('textUnderCursor')\n if (currentText?.trim() === '---' || currentText?.trim().toUpperCase() === 'OFF') {\n debugLog('[M8SDK] Initial note is \"---\" or \"OFF\", priming with edit key')\n await pressAndRelease(M8KeyMask.Edit)\n await waitForTextUnderCursorChange(currentText, 250)\n currentText = store.get('textUnderCursor')\n }\n\n let currentNote = parseNoteValue(currentText)\n if (!currentNote) {\n console.warn('[M8SDK] Could not parse current note under cursor:', currentText)\n return false\n }\n\n if (currentNote.semitoneIndex === parsedTarget.semitoneIndex) {\n return true\n }\n\n // First attempt: precomputed semitone/octave path (fast path).\n await pressAndRelease(M8KeyMask.Edit)\n await waitForTextUnderCursorChange(currentText, 250)\n\n const precomputedSequence = calculateNoteKeySequence(currentNote.semitoneIndex, parsedTarget.semitoneIndex)\n if (precomputedSequence.length > 0) {\n for (const keys of precomputedSequence) {\n sendKeys(keys)\n await wait(25)\n }\n\n await waitForTextUnderCursorChange(currentText, 280)\n const precomputedFinal = parseNoteValue(store.get('textUnderCursor'))\n if (precomputedFinal?.semitoneIndex === parsedTarget.semitoneIndex) {\n await pressAndRelease(M8KeyMask.Edit)\n return true\n }\n\n // Precomputed path can miss when scale quantization makes exact steps unreachable.\n currentNote = precomputedFinal ?? currentNote\n }\n\n // Fallback: iterative closest-note search.\n\n const seen = new Set<string>()\n seen.add(currentNote.label)\n\n const timeoutMs = 7000\n const startedAt = Date.now()\n\n while (Date.now() - startedAt < timeoutMs) {\n const diff = parsedTarget.semitoneIndex - currentNote.semitoneIndex\n if (diff === 0) {\n break\n }\n\n const useOctaveStep = Math.abs(diff) >= 12\n const positiveDirection = diff > 0\n const key = useOctaveStep ? (positiveDirection ? M8KeyMask.Up : M8KeyMask.Down) : positiveDirection ? M8KeyMask.Right : M8KeyMask.Left\n const reverseKey = useOctaveStep ? (positiveDirection ? M8KeyMask.Down : M8KeyMask.Up) : positiveDirection ? M8KeyMask.Left : M8KeyMask.Right\n\n const previousNote = currentNote\n const previousDistance = Math.abs(previousNote.semitoneIndex - parsedTarget.semitoneIndex)\n\n const beforeStepText = store.get('textUnderCursor')\n await pressAndRelease(M8KeyMask.Edit | key, 35)\n await waitForTextUnderCursorChange(beforeStepText, 250)\n\n const newNote = parseNoteValue(store.get('textUnderCursor'))\n if (!newNote) {\n break\n }\n if (newNote.label === previousNote.label) {\n break\n }\n\n currentNote = newNote\n const currentDistance = Math.abs(currentNote.semitoneIndex - parsedTarget.semitoneIndex)\n\n if (currentDistance > previousDistance) {\n // Overshot into a worse candidate, step back and stop on the closer one.\n const beforeReverseStep = store.get('textUnderCursor')\n await pressAndRelease(M8KeyMask.Edit | reverseKey, 35)\n await waitForTextUnderCursorChange(beforeReverseStep, 250)\n const steppedBack = parseNoteValue(store.get('textUnderCursor'))\n if (steppedBack) {\n currentNote = steppedBack\n }\n break\n }\n\n if (seen.has(currentNote.label)) {\n break\n }\n seen.add(currentNote.label)\n }\n\n await pressAndRelease(M8KeyMask.Edit)\n\n const finalNote = parseNoteValue(store.get('textUnderCursor'))\n return finalNote?.semitoneIndex === parsedTarget.semitoneIndex\n }\n\n // Implementation of setValueToString by anchoring at bottom and scanning forward one step at a time.\n const setValueToStringImpl = async (targetString: string, exact: boolean = true, searchInCurrentLine: boolean = false): Promise<boolean> => {\n if (!hasBus()) return false\n\n const normalizedTarget = normalizeForSearch(targetString)\n if (!normalizedTarget) {\n return false\n }\n\n const getSearchSource = (): string | null => {\n return searchInCurrentLine ? store.get('currentLine') : store.get('textUnderCursor')\n }\n\n const matches = (value: string | null): boolean => {\n const normalizedValue = normalizeForSearch(value)\n if (!normalizedValue) return false\n return exact ? normalizedValue === normalizedTarget : normalizedValue.includes(normalizedTarget)\n }\n\n // 0) Check if we're already on the right value before doing anything.\n if (matches(getSearchSource())) {\n return true\n }\n\n // Some fields start at \"---\" and need one edit press before they expose real values.\n const initialCursorText = store.get('textUnderCursor')\n if (initialCursorText?.trim() === '---') {\n debugLog('[M8SDK] Initial string value is \"---\", priming with edit key')\n await pressAndRelease(M8KeyMask.Edit)\n await waitForTextUnderCursorChange(initialCursorText, 250)\n }\n\n const beforeEnterEdit = store.get('textUnderCursor')\n await pressAndRelease(M8KeyMask.Edit)\n await waitForTextUnderCursorChange(beforeEnterEdit, 250)\n\n // Check again right after entering edit mode before going to the bottom.\n if (matches(getSearchSource())) {\n await pressAndRelease(M8KeyMask.Edit)\n return true\n }\n\n // 1) Go to the bottom: keep sending edit+down until value stops changing.\n // Guard against circular lists (no bottom) using a seed of the first few observed\n // values, and a hard timeout.\n let currentText = store.get('textUnderCursor')\n const maxBottomConfirmationRetries = 1\n let bottomNoChangeCount = 0\n const bottomStartTime = Date.now()\n const BOTTOM_TIMEOUT_MS = 2000\n const CIRCULAR_SEED_COUNT = 4\n const bottomSeedValues: string[] = []\n for (let i = 0; i < 512; i++) {\n if (Date.now() - bottomStartTime > BOTTOM_TIMEOUT_MS) {\n debugLog('[M8SDK] Timeout reached while seeking bottom of list')\n break\n }\n\n const normalizedCurrent = normalizeForSearch(currentText)\n if (normalizedCurrent) {\n if (i < CIRCULAR_SEED_COUNT) {\n bottomSeedValues.push(normalizedCurrent)\n } else if (bottomSeedValues.includes(normalizedCurrent)) {\n debugLog('[M8SDK] Circular list detected while seeking bottom, treating current position as start')\n break\n }\n }\n\n const beforeStepText = currentText\n await pressAndRelease(M8KeyMask.Edit | M8KeyMask.Down, 35)\n const nextText = await waitForTextUnderCursorChange(beforeStepText, 250)\n if (normalizeForSearch(nextText) === normalizeForSearch(currentText)) {\n bottomNoChangeCount += 1\n } else {\n bottomNoChangeCount = 0\n currentText = nextText\n }\n\n // Once bottom is detected (no change), allow only one confirmation retry.\n if (bottomNoChangeCount > maxBottomConfirmationRetries) {\n break\n }\n }\n\n // 2) Walk forward with edit+right and compare text at each step.\n const seen = new Set<string>()\n for (let i = 0; i < 1024; i++) {\n const searchSource = getSearchSource()\n if (matches(searchSource)) {\n await pressAndRelease(M8KeyMask.Edit)\n return true\n }\n\n const normalizedText = normalizeForSearch(searchSource)\n if (normalizedText) {\n if (seen.has(normalizedText)) {\n break\n }\n seen.add(normalizedText)\n }\n\n const beforeStepText = store.get('textUnderCursor')\n await pressAndRelease(M8KeyMask.Edit | M8KeyMask.Right, 35)\n const nextText = await waitForTextUnderCursorChange(beforeStepText, 250)\n if (normalizeForSearch(nextText) === normalizedText) {\n break\n }\n }\n\n await pressAndRelease(M8KeyMask.Edit)\n return false\n }\n\n return {\n setValueToHex: setValueToHexImpl,\n setValueToInt: setValueToIntImpl,\n setNote: setNoteImpl,\n setValueToString: setValueToStringImpl,\n }\n}\n","// Plays a sequence of key frames on the M8, one frame per key echo: the next frame is sent\n// when the M8 echoes the previous one. An unexpected echo (someone else pressed a key)\n// cancels the macro. Progress is published in the store's macroStatus.\n\nimport type { EngineBus } from './bus'\nimport type { KeyCommand } from './protocol'\nimport type { M8Store } from './store'\n\nexport interface MacroRunner {\n /** Starts a sequence. While one is running, the new frames replace the remaining ones. */\n start(sequence: number[]): void\n cancel(reason?: string): void\n readonly running: boolean\n dispose(): void\n}\n\nexport const createMacroRunner = ({\n bus,\n store,\n cancelOnKeydown = false,\n}: {\n bus: EngineBus\n store: M8Store\n /** Cancel the running macro on any keydown in this window (yam8d does, for its keyboard input). */\n cancelOnKeydown?: boolean\n}): MacroRunner => {\n let queue: number[] = []\n let lastSent: number | null = null\n let active = false\n\n const onKeyDown = () => {\n if (active) cancel('preempted by keyboard')\n }\n\n const activate = () => {\n if (active) return\n active = true\n bus.protocol.eventBus.on('key', onKeyEcho)\n if (cancelOnKeydown && typeof window !== 'undefined') window.addEventListener('keydown', onKeyDown)\n }\n\n const deactivate = () => {\n if (!active) return\n active = false\n bus.protocol.eventBus.off('key', onKeyEcho)\n if (cancelOnKeydown && typeof window !== 'undefined') window.removeEventListener('keydown', onKeyDown)\n }\n\n function onKeyEcho(data: KeyCommand) {\n if (!active) return\n const echoed = data.keys\n // Preempt on unexpected echo (likely external input)\n if (lastSent !== null && echoed !== lastSent) {\n cancel('external key event')\n return\n }\n const next = queue.shift()\n if (next !== undefined) {\n lastSent = next\n bus.commands.sendKeys(next)\n const sent = (store.get('macroStatus').currentStep ?? 0) + 1\n store.set('macroStatus', { running: true, currentStep: sent, sequenceLength: queue.length + sent })\n } else {\n deactivate()\n lastSent = null\n store.set('macroStatus', { running: false })\n }\n }\n\n function cancel(_reason?: string) {\n if (!active && queue.length === 0 && !store.get('macroStatus').running) return\n // Never leave a key down: the release frame was still queued\n if (lastSent) bus.commands.sendKeys(0)\n queue = []\n lastSent = null\n deactivate()\n store.set('macroStatus', { running: false })\n }\n\n const start = (sequence: number[]) => {\n queue = Array.isArray(sequence) ? sequence.slice() : []\n if (queue.length === 0) return\n store.set('macroStatus', { running: true, currentStep: 0, sequenceLength: queue.length })\n // Already running: the next echo picks up the new frames\n if (active) return\n\n activate()\n const first = queue.shift() as number\n lastSent = first\n bus.commands.sendKeys(first)\n }\n\n return {\n start,\n cancel,\n get running() {\n return active\n },\n dispose: () => {\n queue = []\n lastSent = null\n deactivate()\n },\n }\n}\n","// Observable state that the engine derives from the M8 screen stream: view, cursor, text\n// under the cursor, colours, device info and macro status. Framework-free; yam8d mirrors\n// it into jotai atoms, the standalone client reads it directly.\n\nimport type { CursorPos, CursorRect, RGB } from '../types'\n\n/** Model, font mode and the layout metrics derived from them (from the M8's system command). */\nexport interface M8SystemInfo {\n model: string\n fontMode: number\n spacingX: number\n spacingY: number\n offX: number\n offY: number\n screenWidth: number\n screenHeight: number\n rectOffset: number\n}\n\nexport interface M8CellMetrics {\n cellW: number\n cellH: number\n offX: number\n offY: number\n}\n\nexport interface M8MacroStatus {\n running: boolean\n currentStep?: number\n sequenceLength?: number\n}\n\nexport interface M8StoreState {\n /** Current view name (lowercased, cleaned). */\n viewName: string | null\n /** Raw title row text. */\n viewTitle: string | null\n minimapKey: string | null\n /** Cursor position in character grid coordinates (0..39, 0..23). */\n cursorPos: CursorPos | null\n /** Cursor rectangle in M8 pixel space. */\n cursorRect: CursorRect | null\n /** Whether the cursor is drawn as a 1px selection rectangle. */\n selectionMode: boolean\n /** Foreground colour of the text under the cursor. */\n highlightColor: RGB | null\n /** Colour of the cursor rectangle corners as last drawn. */\n cursorColor: RGB | null\n textUnderCursor: string | null\n /** The whole screen row the cursor is on. */\n currentLine: string | null\n titleColor: RGB | null\n backgroundColor: RGB | null\n deviceModel: string | null\n fontMode: number | null\n /** Pixel size and offset of one character cell (model and font dependent). */\n cellMetrics: M8CellMetrics\n systemInfo: M8SystemInfo | null\n macroStatus: M8MacroStatus\n}\n\nexport type M8StoreKey = keyof M8StoreState\n\nexport interface M8Store {\n get<K extends M8StoreKey>(key: K): M8StoreState[K]\n /** Sets a value; listeners run synchronously, and only when the value changed (by identity). */\n set<K extends M8StoreKey>(key: K, value: M8StoreState[K]): void\n /** Listens to one key. Returns the unsubscribe function. */\n sub(key: M8StoreKey, listener: () => void): () => void\n /** Listens to every key. Returns the unsubscribe function. */\n subAll(listener: (key: M8StoreKey) => void): () => void\n snapshot(): M8StoreState\n}\n\nexport const initialStoreState = (): M8StoreState => ({\n viewName: null,\n viewTitle: null,\n minimapKey: null,\n cursorPos: null,\n cursorRect: null,\n selectionMode: false,\n highlightColor: null,\n cursorColor: null,\n textUnderCursor: null,\n currentLine: null,\n titleColor: null,\n backgroundColor: null,\n deviceModel: null,\n fontMode: null,\n cellMetrics: { cellW: 12, cellH: 14, offX: 0, offY: 0 },\n systemInfo: null,\n macroStatus: { running: false },\n})\n\nexport const createM8Store = (initial: Partial<M8StoreState> = {}): M8Store => {\n const state: M8StoreState = { ...initialStoreState(), ...initial }\n const listeners = new Map<M8StoreKey, Set<() => void>>()\n const allListeners = new Set<(key: M8StoreKey) => void>()\n\n return {\n get: (key) => state[key],\n set: (key, value) => {\n if (Object.is(state[key], value)) return\n state[key] = value\n for (const listener of [...(listeners.get(key) ?? [])]) listener()\n for (const listener of [...allListeners]) listener(key)\n },\n sub: (key, listener) => {\n let set = listeners.get(key)\n if (!set) {\n set = new Set()\n listeners.set(key, set)\n }\n set.add(listener)\n return () => {\n set.delete(listener)\n }\n },\n subAll: (listener) => {\n allListeners.add(listener)\n return () => {\n allListeners.delete(listener)\n }\n },\n snapshot: () => ({ ...state }),\n }\n}\n","// A* shortest path over a directed graph of views. Edges carry their own data (direction,\n// key frames...), which the path returns step by step.\n\nexport type Direction = 'up' | 'down' | 'left' | 'right'\n\nexport type Edge = { to: string; dir?: Direction; weight?: number }\nexport type Graph<E extends Edge = Edge> = Map<string, E[]>\n\nexport type Heuristic = (a: string, b: string) => number\nexport type EdgeCostFn = (from: string, to: string, dir?: Direction) => number\n\nconst zeroHeuristic: Heuristic = () => 0\n\n// Default edge cost function: uniform cost for all edges\n// Edge-specific weights should be defined in the graph JSON file\nconst defaultEdgeCost: EdgeCostFn = () => 1\n\nexport interface PathResult<E extends Edge = Edge> {\n pages: string[] // includes start and goal\n directions: (Direction | undefined)[] // one per step\n edges: E[] // one per step\n cost: number // sum of the edge costs\n}\n\nexport function aStar<E extends Edge = Edge>(\n graph: Graph<E>,\n start: string,\n goal: string,\n heuristic: Heuristic = zeroHeuristic,\n edgeCost: EdgeCostFn = defaultEdgeCost,\n): PathResult<E> | null {\n const s = start.trim()\n const g = goal.trim()\n if (!graph.has(s) || !graph.has(g)) return null\n\n const openSet = new Set<string>([s])\n const cameFrom = new Map<string, { prev: string; via: E }>()\n const gScore = new Map<string, number>()\n const fScore = new Map<string, number>()\n\n for (const node of graph.keys()) {\n gScore.set(node, Infinity)\n fScore.set(node, Infinity)\n }\n gScore.set(s, 0)\n fScore.set(s, heuristic(s, g))\n\n function lowestF(): string | null {\n let best: string | null = null\n let bestVal = Infinity\n for (const node of openSet) {\n const val = fScore.get(node) ?? Infinity\n if (val < bestVal) {\n bestVal = val\n best = node\n }\n }\n return best\n }\n\n while (openSet.size) {\n const current = lowestF()\n if (!current) break\n if (current === g) {\n // reconstruct\n const pages: string[] = [g]\n const edges: E[] = []\n let cur = g\n while (cur !== s) {\n const link = cameFrom.get(cur)\n if (!link) break // safety\n edges.unshift(link.via)\n pages.unshift(link.prev)\n cur = link.prev\n }\n return { pages, directions: edges.map((e) => e.dir), edges, cost: gScore.get(g) ?? edges.length }\n }\n openSet.delete(current)\n const neighbors: E[] = graph.get(current) ?? []\n for (const e of neighbors) {\n const cost = e.weight ?? edgeCost(current, e.to, e.dir)\n const tentativeG = (gScore.get(current) ?? Infinity) + cost\n if (tentativeG < (gScore.get(e.to) ?? Infinity)) {\n cameFrom.set(e.to, { prev: current, via: e })\n gScore.set(e.to, tentativeG)\n fScore.set(e.to, tentativeG + heuristic(e.to, g))\n if (!openSet.has(e.to)) openSet.add(e.to)\n }\n }\n }\n\n return null\n}\n","// Navigates between M8 views by name. Each step finds a path in the view graph with A* (Shift+\r\n// direction edges, buttons that open sub-views, Option to leave them), takes its first edge and\r\n// waits to see which view the M8 shows. That outcome is remembered for the rest of the\r\n// navigation and the path is recomputed from wherever the M8 landed:\r\n// - an edge that changed nothing is blocked (Shift+Right on an empty chain row has no phrase\r\n// to go to), and the planner goes around it (Project > View Inst. Pool > Instrument);\r\n// - an edge that landed elsewhere (Option, or the Mixer's Shift+Up, which goes back to the\r\n// last view) now leads where it went.\r\n// Where a key leads can depend on the view the M8 came from (the Instrument's Shift+Left goes\r\n// back to the Instrument Pool when it was entered from there), so the search runs over\r\n// (view, previous view) pairs and outcomes are remembered per pair.\r\n// On a view that is not in the graph, Option is tried first, then Shift+Up.\r\n\r\nimport { aStar, type Direction, type Edge, type Graph } from './astar'\r\nimport { M8KeyMask } from './keys'\r\nimport type { MacroRunner } from './macroRunner'\r\nimport type { M8Store } from './store'\r\nimport { getViewGraph, getViewNames, type NavEdge } from './viewGraph'\r\n\r\nconst MAX_STEPS = 20 // Safety limit to prevent infinite loops\r\n/** After the keys of a step, no view change in this time means the edge is blocked. */\r\nconst STEP_TIMEOUT_MS = 700\r\n/** A new view name must hold this long: the title is guessed early while it is drawn. */\r\nconst SETTLE_MS = 80\r\n/** Longest cursor move to a button. */\r\nconst CURSOR_TIMEOUT_MS = 4000\r\n/** After a cursor move, before the button keys: the move's last release is still on its way (20 ms command slots). */\r\nconst CURSOR_SETTLE_MS = 120\r\n\r\n/** Shift+direction frames, used for edges without an explicit key sequence. */\r\nexport const toKeyMasks = (directions: Direction[], opts?: { shift?: boolean }) => {\r\n const withShift = !!opts?.shift\r\n const s = M8KeyMask.Shift\r\n const map: Record<Direction, number> = {\r\n up: (withShift ? s : 0) | M8KeyMask.Up,\r\n down: (withShift ? s : 0) | M8KeyMask.Down,\r\n left: (withShift ? s : 0) | M8KeyMask.Left,\r\n right: (withShift ? s : 0) | M8KeyMask.Right,\r\n }\r\n const seq: number[] = []\r\n if (withShift) seq.push(s)\r\n for (const d of directions) {\r\n seq.push(map[d], withShift ? s : 0)\r\n }\r\n // final release\r\n seq.push(0)\r\n return seq\r\n}\r\n\r\nexport interface ViewNavigation {\r\n /** Resolves true when the view is reached; false when no path is left, the step limit is hit or another navigation starts. */\r\n navigateToView(viewName: string): Promise<boolean>\r\n dispose(): void\r\n}\r\n\r\nexport interface ViewNavigationDeps {\r\n store: M8Store\r\n runner: MacroRunner\r\n /** The 24 text rows of the screen, to find buttons by their label. */\r\n getScreenLines?: () => string[]\r\n /** Moves the cursor to a text grid cell. Without it, button edges are not used. */\r\n moveCursor?: (x: number, y: number) => Promise<unknown>\r\n}\r\n\r\n// Tried in order on a view the graph does not know (or that has no way left)\r\nconst FALLBACK_EDGES = (from: string): NavEdge[] => [\r\n { from, to: '', kind: 'exit', keys: [M8KeyMask.Opt, 0] },\r\n { from, to: '', kind: 'shift', dir: 'up', keys: [M8KeyMask.Shift | M8KeyMask.Up, 0] },\r\n]\r\n\r\nconst edgeSignature = (e: NavEdge) => `${e.kind}:${e.dir ?? ''}:${e.keys.join(',')}:${e.cursor ? `${e.cursor.x},${e.cursor.y}` : ''}`\r\n\r\nconst withFinalRelease = (frames: number[]) => (frames[frames.length - 1] === 0 ? frames.slice() : [...frames, 0])\r\n\r\n/** An edge of the search over (view, previous view) states. */\r\ntype StateEdge = Edge & { nav: NavEdge }\r\nconst GOAL_STATE = '#goal'\r\nconst stateId = (view: string, prev: string) => `${view}<${prev}`\r\n\r\nconst timeout = (ms: number) => new Promise<void>((resolve) => setTimeout(resolve, ms))\r\n\r\nexport const createViewNavigation = ({ store, runner, getScreenLines, moveCursor }: ViewNavigationDeps): ViewNavigation => {\r\n const viewSet = getViewNames()\r\n let goal: string | null = null\r\n let steps = 0\r\n let pendingResolve: ((reached: boolean) => void) | null = null\r\n // Bumped by every navigation, so a step of an older one stops after its cursor move\r\n let session = 0\r\n /**\r\n * What each edge did during this navigation: `${from}<${previous view}|${signature}` -> the view\r\n * it reached. A blocked edge (it changed nothing) is kept under the previous view '*': that\r\n * depends on what the view shows, not on where the M8 came from.\r\n */\r\n let outcomes = new Map<string, string>()\r\n // The view the M8 showed before the current one ('' when not known), followed at all times\r\n let cameFrom = ''\r\n let lastView = store.get('viewName') || ''\r\n const unsubscribeHistory = store.sub('viewName', () => {\r\n const view = store.get('viewName') || ''\r\n if (view && view !== lastView) {\r\n if (lastView) cameFrom = lastView\r\n lastView = view\r\n }\r\n })\r\n\r\n type Step = { from: string; prev: string; edge: NavEdge; phase: 'cursor' | 'keys' }\r\n let step: Step | null = null\r\n let timer: ReturnType<typeof setTimeout> | null = null\r\n\r\n const clearTimer = () => {\r\n if (timer) clearTimeout(timer)\r\n timer = null\r\n }\r\n\r\n const resolveTarget = (name: string) => {\r\n const wanted = (name || '').toLowerCase().trim()\r\n if (!viewSet.has(wanted)) console.warn('Target not in viewlist', { goal: wanted })\r\n return wanted\r\n }\r\n\r\n const outcomeKey = (e: NavEdge, prev: string) => `${e.from}<${prev}|${edgeSignature(e)}`\r\n\r\n // The moves from `view` when the M8 came from `prev`, one per key: what this navigation saw\r\n // that key do, else the back edge to `prev`, else the key's other edges.\r\n const movesFrom = (view: string, prev: string): NavEdge[] => {\r\n const groups = new Map<string, NavEdge[]>()\r\n for (const e of getViewGraph().graph.get(view) ?? []) {\r\n if (e.kind === 'button' && !moveCursor) continue\r\n const sig = edgeSignature(e)\r\n groups.set(sig, [...(groups.get(sig) ?? []), e])\r\n }\r\n const moves: NavEdge[] = []\r\n for (const group of groups.values()) {\r\n if (outcomes.get(outcomeKey(group[0], '*')) === view) continue\r\n const seen = outcomes.get(outcomeKey(group[0], prev))\r\n if (seen !== undefined) {\r\n if (seen !== view) moves.push({ ...group[0], to: seen })\r\n continue\r\n }\r\n const back = group.find((e) => e.back && e.to === prev)\r\n if (back) {\r\n moves.push(back)\r\n continue\r\n }\r\n const others = group.filter((e) => !e.back)\r\n moves.push(...(others.length ? others : group))\r\n }\r\n return moves\r\n }\r\n\r\n // The (view, previous view) states reachable from the current one, with a goal node\r\n const stateGraph = (from: string, prev: string, target: string): Graph<StateEdge> => {\r\n const graph: Graph<StateEdge> = new Map([[GOAL_STATE, []]])\r\n const queue: [string, string][] = [[from, prev]]\r\n graph.set(stateId(from, prev), [])\r\n while (queue.length) {\r\n const [view, before] = queue.shift() as [string, string]\r\n const edges = graph.get(stateId(view, before)) as StateEdge[]\r\n if (view === target) {\r\n edges.push({ to: GOAL_STATE, weight: 0, nav: { from: view, to: view, kind: 'shift', keys: [] } })\r\n continue\r\n }\r\n for (const nav of movesFrom(view, before)) {\r\n const id = stateId(nav.to, view)\r\n edges.push({ to: id, weight: nav.weight, nav })\r\n if (!graph.has(id)) {\r\n graph.set(id, [])\r\n queue.push([nav.to, view])\r\n }\r\n }\r\n }\r\n return graph\r\n }\r\n\r\n const nextEdge = (from: string, target: string): NavEdge | null => {\r\n const res = aStar(stateGraph(from, cameFrom, target), stateId(from, cameFrom), GOAL_STATE)\r\n if (res?.edges.length) return res.edges[0].nav\r\n // A view without edges, or a sub-view with no way left: try to leave it. Main views get\r\n // no Option press (it is the mute/solo modifier there).\r\n const edges = getViewGraph().graph.get(from) ?? []\r\n if (edges.length && !edges.some((e) => e.kind === 'exit')) return null\r\n return FALLBACK_EDGES(from).find((e) => !outcomes.has(outcomeKey(e, cameFrom)) && !outcomes.has(outcomeKey(e, '*'))) ?? null\r\n }\r\n\r\n // Where to put the cursor for a button: the label nearest to the measured cell, else the cell\r\n const buttonCell = (e: NavEdge) => {\r\n const cell = e.cursor as { x: number; y: number }\r\n if (!e.label || !getScreenLines) return cell\r\n let best: { x: number; y: number; d: number } | null = null\r\n const lines = getScreenLines()\r\n // Skip the title rows and the track panel on the right\r\n for (let row = 4; row < lines.length; row++) {\r\n const line = (lines[row] ?? '').slice(0, 33)\r\n for (let i = line.indexOf(e.label); i >= 0; i = line.indexOf(e.label, i + 1)) {\r\n const d = Math.hypot(i - cell.x, (row - cell.y) * 1.5)\r\n if (!best || d < best.d) best = { x: i, y: row, d }\r\n }\r\n }\r\n return best ? { x: best.x, y: best.y } : cell\r\n }\r\n\r\n const cursorCovers = (cell: { x: number; y: number }) => {\r\n const pos = store.get('cursorPos')\r\n const rect = store.get('cursorRect')\r\n if (!pos || pos.y !== cell.y) return false\r\n const width = rect ? Math.max(1, Math.round(rect.w / store.get('cellMetrics').cellW)) : 1\r\n return cell.x >= pos.x && cell.x < pos.x + width\r\n }\r\n\r\n const complete = (reached: boolean) => {\r\n goal = null\r\n steps = 0\r\n step = null\r\n clearTimer()\r\n if (pendingResolve) {\r\n const resolve = pendingResolve\r\n pendingResolve = null\r\n resolve(reached)\r\n }\r\n }\r\n\r\n const finishStep = (reachedView: string) => {\r\n if (!step) return\r\n const { edge, from, prev } = step\r\n const moved = !!reachedView && reachedView !== from\r\n outcomes.set(outcomeKey(edge, moved ? prev : '*'), moved ? reachedView : from)\r\n step = null\r\n clearTimer()\r\n if (reachedView && reachedView !== from && edge.to && reachedView !== edge.to) {\r\n console.log('Navigation step landed elsewhere', { from, expected: edge.to, reached: reachedView })\r\n } else if (!reachedView || reachedView === from) {\r\n console.log('Navigation step blocked', { from, to: edge.to || '?', kind: edge.kind, dir: edge.dir })\r\n }\r\n executeNextStep()\r\n }\r\n\r\n const runButton = async (s: Step, mySession: number) => {\r\n const cell = buttonCell(s.edge)\r\n if (!cursorCovers(cell)) {\r\n await Promise.race([(moveCursor as NonNullable<typeof moveCursor>)(cell.x, cell.y), timeout(CURSOR_TIMEOUT_MS)])\r\n // Its release echo would otherwise reach the macro runner as a foreign key and cancel the macro\r\n await timeout(CURSOR_SETTLE_MS)\r\n }\r\n if (mySession !== session || step !== s) return\r\n // Never press a key on the wrong cell: on the Project view, Edit on TEMPO taps the tempo\r\n if (store.get('viewName') !== s.from || !cursorCovers(cell)) {\r\n console.warn('Navigation: button not reached', { from: s.from, to: s.edge.to, cell, cursor: store.get('cursorPos') })\r\n finishStep(store.get('viewName') !== s.from ? store.get('viewName') || '' : s.from)\r\n return\r\n }\r\n s.phase = 'keys'\r\n runner.cancel('navigation button')\r\n runner.start(withFinalRelease(s.edge.keys))\r\n }\r\n\r\n const executeNextStep = () => {\r\n const goalTitle = goal\r\n const startTitle = store.get('viewName') || ''\r\n\r\n if (!goalTitle) return\r\n if (!startTitle) {\r\n console.warn('Navigation aborted: no view name (a dialog?)', { goal: goalTitle })\r\n complete(false)\r\n return\r\n }\r\n if (startTitle === goalTitle) {\r\n console.log('Navigation complete:', { goal: goalTitle, steps })\r\n complete(true)\r\n return\r\n }\r\n if (steps >= MAX_STEPS) {\r\n console.warn('Navigation aborted: max steps reached', { startTitle, goalTitle, steps })\r\n complete(false)\r\n return\r\n }\r\n\r\n const edge = nextEdge(startTitle, goalTitle)\r\n if (!edge) {\r\n console.warn('No path found', { startTitle, goalTitle, step: steps })\r\n complete(false)\r\n return\r\n }\r\n\r\n steps += 1\r\n console.log('Navigation step', { step: steps, from: startTitle, to: edge.to || '?', kind: edge.kind, dir: edge.dir, goal: goalTitle })\r\n\r\n const s: Step = { from: startTitle, prev: cameFrom, edge, phase: edge.kind === 'button' ? 'cursor' : 'keys' }\r\n step = s\r\n if (edge.kind === 'button') {\r\n void runButton(s, session)\r\n return\r\n }\r\n const frames = edge.keys.length ? withFinalRelease(edge.keys) : toKeyMasks([edge.dir as Direction], { shift: true })\r\n runner.cancel('iterative navigation step')\r\n runner.start(frames)\r\n }\r\n\r\n // Once the keys of a step are played: wait for the view to change and settle, or time out\r\n const onChange = () => {\r\n if (step?.phase !== 'keys') return\r\n const running = store.get('macroStatus').running\r\n const view = store.get('viewName') || ''\r\n const changed = !!view && view !== step.from\r\n if (running && !changed) return\r\n clearTimer()\r\n if (running) {\r\n // The view changed but the macro still waits for an echo: the M8 does not always echo the\r\n // release of a key that opened another view. Release explicitly and go on.\r\n timer = setTimeout(() => {\r\n runner.cancel('navigation: no release echo')\r\n runner.start([0])\r\n finishStep(store.get('viewName') || '')\r\n }, STEP_TIMEOUT_MS)\r\n return\r\n }\r\n timer = setTimeout(() => finishStep(store.get('viewName') || ''), changed ? SETTLE_MS : STEP_TIMEOUT_MS)\r\n }\r\n const unsubscribeView = store.sub('viewName', onChange)\r\n const unsubscribeMacro = store.sub('macroStatus', onChange)\r\n\r\n return {\r\n navigateToView: (targetViewTitle: string): Promise<boolean> =>\r\n new Promise((resolve) => {\r\n const goalTitle = resolveTarget(targetViewTitle)\r\n if (!goalTitle) {\r\n resolve(false)\r\n return\r\n }\r\n\r\n // A navigation already running ends here\r\n complete(false)\r\n session += 1\r\n outcomes = new Map()\r\n pendingResolve = resolve\r\n goal = goalTitle\r\n steps = 0\r\n\r\n console.log('Starting iterative navigation', { goal: goalTitle, from: store.get('viewName') || 'unknown' })\r\n executeNextStep()\r\n }),\r\n dispose: () => {\r\n unsubscribeHistory()\r\n unsubscribeView()\r\n unsubscribeMacro()\r\n session += 1\r\n complete(false)\r\n },\r\n }\r\n}\r\n","// Everything that runs next to a connected M8, without a framework: screen state extraction,\r\n// cursor and view navigation, value editors, file browsing, MIDI clock, and the SDK host\r\n// methods and events built on them. yam8d runs one per bus and exposes it to iframes; the\r\n// standalone client runs one in the app's own page.\r\n\r\nimport type { M8HostEvents, M8HostMethods, M8State } from '../types'\r\nimport type { EngineBus } from './bus'\r\nimport { type CursorMap, createCursorMap } from './cursorMap'\r\nimport { type CursorNavigator, createCursorNavigator, type Point } from './cursorNavigator'\r\nimport { createFileBrowserSearch } from './host/fileBrowserSearch'\r\nimport { createFloatEditor } from './host/floatEditor'\r\nimport { createHostIO } from './host/hostIO'\r\nimport { createHostMethods } from './host/hostMethods'\r\nimport { getCurrentState, textGridToPixel } from './host/hostState'\r\nimport { createMidiTransport } from './host/midiTransport'\r\nimport { createValueEditors } from './host/valueEditors'\r\nimport { createMacroRunner, type MacroRunner } from './macroRunner'\r\nimport type { KeyCommand } from './protocol'\r\nimport { createM8Store, type M8Store, type M8StoreKey } from './store'\r\nimport { registerViewExtractor, type ScreenCellColors } from './viewExtractor'\r\nimport { createViewNavigation } from './viewNavigation'\r\n\r\nexport interface M8EngineOptions {\r\n debug?: boolean\r\n /** Cancel a running macro (view navigation) on any keydown in this window. Default false. */\r\n cancelMacroOnKeydown?: boolean\r\n /** Use this store instead of a new one (for tests, or to keep state across reconnects). */\r\n store?: M8Store\r\n /** Where the cursor navigator keeps the layouts it learns (share one to keep them across reconnects). */\r\n cursorMap?: CursorMap\r\n}\r\n\r\nexport interface M8Engine {\r\n readonly bus: EngineBus\r\n readonly store: M8Store\r\n /** The SDK methods, as an iframe client or the standalone client calls them. */\r\n readonly methods: M8HostMethods\r\n readonly macro: MacroRunner\r\n /** Moves the cursor to the cell at or nearest to a point in M8 pixel coordinates. Resolves true when it got there. */\r\n navigateTo(point: Point): Promise<boolean>\r\n /** Resolves true when the view is reached. */\r\n navigateToView(viewName: string): Promise<boolean>\r\n getState(): M8State\r\n getScreenLines(): string[]\r\n getScreenCellColors(): ScreenCellColors\r\n /** Listens to an SDK event. Returns the unsubscribe function. */\r\n on<K extends keyof M8HostEvents>(event: K, callback: (payload: M8HostEvents[K]) => void): () => void\r\n dispose(): void\r\n}\r\n\r\n// Store keys whose change is reported as a whole stateChanged event\r\nconst STATE_KEYS = new Set<M8StoreKey>(['macroStatus', 'deviceModel', 'fontMode', 'systemInfo', 'titleColor', 'backgroundColor', 'minimapKey'])\r\n\r\nexport const createM8Engine = (bus: EngineBus, options: M8EngineOptions = {}): M8Engine => {\r\n const store = options.store ?? createM8Store()\r\n const debugLog = (...args: unknown[]) => {\r\n if (options.debug) console.log(...args)\r\n }\r\n\r\n const listeners = new Map<keyof M8HostEvents, Set<(payload: never) => void>>()\r\n const emit = <K extends keyof M8HostEvents>(event: K, payload: M8HostEvents[K]) => {\r\n for (const cb of [...(listeners.get(event) ?? [])]) (cb as (p: M8HostEvents[K]) => void)(payload)\r\n }\r\n\r\n const extractor = registerViewExtractor(bus.protocol.eventBus, store)\r\n const macro = createMacroRunner({ bus, store, cancelOnKeydown: options.cancelMacroOnKeydown })\r\n const cursorNavigator: CursorNavigator = createCursorNavigator({\r\n bus,\r\n store,\r\n getScreenLines: extractor.getScreenLines,\r\n cursorMap: options.cursorMap ?? createCursorMap(),\r\n })\r\n const viewNavigation = createViewNavigation({\r\n store,\r\n runner: macro,\r\n getScreenLines: extractor.getScreenLines,\r\n moveCursor: (x, y) => cursorNavigator.navigateTo(textGridToPixel(store, x, y)),\r\n })\r\n const midiTransport = createMidiTransport((event) => emit('transport', event))\r\n\r\n const io = createHostIO({ getKeySink: () => bus.commands, store, debugLog, getScreenLines: extractor.getScreenLines })\r\n const methods = createHostMethods({\r\n io,\r\n editors: createValueEditors(io),\r\n setValueFloat: createFloatEditor(io),\r\n browseFile: createFileBrowserSearch(io),\r\n navigateToView: (viewName) => viewNavigation.navigateToView(viewName),\r\n navigateTo: (point) => cursorNavigator.navigateTo(point),\r\n enableMidiTransport: () => midiTransport.enable(),\r\n getScreenCellColors: extractor.getScreenCellColors,\r\n })\r\n\r\n // Store changes are collected and reported once per microtask, so the several values\r\n // the extractor sets for one cursor move become one cursorMoved event.\r\n const dirty = new Set<M8StoreKey>()\r\n let flushScheduled = false\r\n let lastView: M8HostEvents['viewChanged'] | null = null\r\n let lastCursor: M8HostEvents['cursorMoved'] | null = null\r\n let lastText: M8HostEvents['textUpdated'] | null = null\r\n\r\n const flush = () => {\r\n flushScheduled = false\r\n const keys = new Set(dirty)\r\n dirty.clear()\r\n\r\n if (keys.has('viewName') || keys.has('viewTitle')) {\r\n const view = { viewName: store.get('viewName'), viewTitle: store.get('viewTitle') }\r\n if (!lastView || lastView.viewName !== view.viewName || lastView.viewTitle !== view.viewTitle) {\r\n lastView = view\r\n debugLog('[M8SDK] View changed:', view.viewName, view.viewTitle)\r\n emit('viewChanged', view)\r\n }\r\n }\r\n\r\n if (keys.has('cursorPos') || keys.has('cursorRect') || keys.has('selectionMode')) {\r\n const cursor = { pos: store.get('cursorPos'), rect: store.get('cursorRect'), selectionMode: store.get('selectionMode') }\r\n const prev = lastCursor\r\n const same =\r\n prev &&\r\n prev.pos?.x === cursor.pos?.x &&\r\n prev.pos?.y === cursor.pos?.y &&\r\n prev.rect?.x === cursor.rect?.x &&\r\n prev.rect?.y === cursor.rect?.y &&\r\n prev.rect?.w === cursor.rect?.w &&\r\n prev.rect?.h === cursor.rect?.h &&\r\n prev.selectionMode === cursor.selectionMode\r\n if (!same) {\r\n lastCursor = cursor\r\n debugLog('[M8SDK] Cursor moved:', cursor.pos, cursor.rect, 'selection:', cursor.selectionMode)\r\n emit('cursorMoved', cursor)\r\n }\r\n }\r\n\r\n if (keys.has('textUnderCursor') || keys.has('currentLine')) {\r\n const text = { textUnderCursor: store.get('textUnderCursor'), currentLine: store.get('currentLine') }\r\n if (!lastText || lastText.textUnderCursor !== text.textUnderCursor || lastText.currentLine !== text.currentLine) {\r\n lastText = text\r\n debugLog('[M8SDK] Text updated:', text.textUnderCursor, text.currentLine)\r\n emit('textUpdated', text)\r\n }\r\n }\r\n\r\n if ([...keys].some((key) => STATE_KEYS.has(key))) {\r\n emit('stateChanged', getCurrentState(store))\r\n }\r\n }\r\n\r\n const unsubscribeStore = store.subAll((key) => {\r\n dirty.add(key)\r\n if (!flushScheduled) {\r\n flushScheduled = true\r\n queueMicrotask(flush)\r\n }\r\n })\r\n\r\n // Key echoes from the M8 (including releases, keys === 0)\r\n const onKey = (data: KeyCommand) => {\r\n debugLog('[M8SDK] Key event:', data.keys)\r\n emit('keyPressed', { keys: data.keys })\r\n }\r\n bus.protocol.eventBus.on('key', onKey)\r\n\r\n return {\r\n bus,\r\n store,\r\n methods,\r\n macro,\r\n navigateTo: (point) => cursorNavigator.navigateTo(point),\r\n navigateToView: (viewName) => viewNavigation.navigateToView(viewName),\r\n getState: () => getCurrentState(store),\r\n getScreenLines: extractor.getScreenLines,\r\n getScreenCellColors: extractor.getScreenCellColors,\r\n on: (event, callback) => {\r\n let set = listeners.get(event)\r\n if (!set) {\r\n set = new Set()\r\n listeners.set(event, set)\r\n }\r\n set.add(callback as (payload: never) => void)\r\n return () => {\r\n set.delete(callback as (payload: never) => void)\r\n }\r\n },\r\n dispose: () => {\r\n bus.protocol.eventBus.off('key', onKey)\r\n unsubscribeStore()\r\n viewNavigation.dispose()\r\n cursorNavigator.dispose()\r\n macro.dispose()\r\n midiTransport.disable()\r\n extractor.dispose()\r\n listeners.clear()\r\n },\r\n }\r\n}\r\n","{\n \"version\": \"1.0\",\n \"description\": \"Layouts of the non-grid ('parameter') views: which text at which column is an instrument type, name, parameter, option or action. Column positions were measured on the manual's screenshots (font mode 0, 40 columns) and cross-checked with the phrase view grid; descriptions come from the manual.\",\n \"notes\": {\n \"rows\": \"A row is recognised by 'match' conditions on the line text (x = column, text = exact text, regex = pattern on the value.width chars). The first matching row wins; its cells give the field columns.\",\n \"zones\": \"Views whose labels change (instrument parameters depend on the instrument type, modulation slots on the modulation type) use two label/value zones instead: the label text is read at labelX and the value at x. The label is then looked up in the vocabulary.\",\n \"formats\": \"hex = 2 hex digits; hexOption = 2 hex digits followed by the option name ('00CHORUS'); hexPair = 'XX:YY' (or 'XX/YY'); decimal / decimalPair = base-10 numbers ('120', '150:45'); float = decimal with a fraction ('-05.00'); text = free text or option name; auto = detect from the text.\",\n \"unverified\": \"Positions are read off the manual's screenshots, not from a device capture. Instrument-type specific layouts other than the Wavsynth header/parameter grid (Sampler SAMPLE row, FM operators, Hypersynth chord, MIDI CC rows) are only reached through the generic label/value zones.\"\n },\n \"views\": {\n \"project\": {\n \"rows\": [\n { \"id\": \"tempo\", \"match\": [{ \"x\": 0, \"text\": \"TEMPO\" }], \"cells\": [\n { \"x\": 13, \"width\": 6, \"key\": \"tempo\", \"type\": \"parameter\", \"format\": \"float\", \"unit\": \" BPM\", \"label\": \"Tempo (TEMPO)\", \"description\": \"Song speed in beats per minute. [EDIT] pressed rhythmically sets the tempo by tapping.\" }\n ] },\n { \"id\": \"transpose\", \"match\": [{ \"x\": 0, \"text\": \"TRANSPOSE\" }], \"cells\": [\n { \"x\": 13, \"width\": 2, \"key\": \"transpose\", \"type\": \"transpose\", \"format\": \"hex\", \"label\": \"Project Transpose (TRANSPOSE)\", \"description\": \"Transposes the whole song in semitones (same as the TSP command). Only affects instruments with TRANSP. enabled; applied when [EDIT] is released.\" }\n ] },\n { \"id\": \"groove\", \"match\": [{ \"x\": 0, \"text\": \"GROOVE\" }], \"cells\": [\n { \"x\": 13, \"width\": 14, \"key\": \"groove\", \"type\": \"option\", \"format\": \"hexOption\", \"label\": \"Default Groove (GROOVE)\", \"description\": \"Groove used when the song plays (number and name); applied when [EDIT] is released. See Groove View.\" }\n ] },\n { \"id\": \"scale\", \"match\": [{ \"x\": 0, \"text\": \"SCALE\" }], \"cells\": [\n { \"x\": 13, \"width\": 16, \"key\": \"scale\", \"type\": \"option\", \"format\": \"hexOption\", \"label\": \"Default Scale (SCALE)\", \"description\": \"Scale (number, key and name) used when the song plays; applied when [EDIT] is released. See Scale View.\" }\n ] },\n { \"id\": \"liveQuantize\", \"match\": [{ \"x\": 0, \"text\": \"LIVE QUANTIZ\" }], \"cells\": [\n { \"x\": 13, \"width\": 14, \"key\": \"liveQuantize\", \"type\": \"option\", \"format\": \"hexOption\", \"label\": \"Live Quantize (LIVE QUANTIZE)\", \"description\": \"How long a cued chain waits before starting when playing in live mode.\" }\n ] },\n { \"id\": \"midi\", \"match\": [{ \"x\": 0, \"text\": \"MIDI\" }, { \"x\": 13, \"text\": \"SETTINGS\" }], \"cells\": [\n { \"x\": 13, \"width\": 8, \"key\": \"midiSettings\", \"type\": \"action\", \"format\": \"text\", \"label\": \"MIDI Settings\", \"description\": \"Open the MIDI Settings View.\" },\n { \"x\": 22, \"width\": 8, \"key\": \"midiMappings\", \"type\": \"action\", \"format\": \"text\", \"label\": \"MIDI Mappings\", \"description\": \"Open the MIDI Mappings View.\" }\n ] },\n { \"id\": \"name\", \"match\": [{ \"x\": 0, \"text\": \"NAME\" }], \"cells\": [\n { \"x\": 13, \"width\": 12, \"key\": \"name\", \"type\": \"name\", \"format\": \"text\", \"label\": \"Song Name (NAME)\", \"description\": \"Name of the current song, entered before saving.\" }\n ] },\n { \"id\": \"file\", \"match\": [{ \"x\": 0, \"text\": \"PROJECT\" }, { \"x\": 13, \"text\": \"LOAD\" }], \"cells\": [\n { \"x\": 13, \"width\": 4, \"key\": \"load\", \"type\": \"action\", \"format\": \"text\", \"label\": \"Load Song\", \"description\": \"Load a song from the SD card.\" },\n { \"x\": 18, \"width\": 4, \"key\": \"save\", \"type\": \"action\", \"format\": \"text\", \"label\": \"Save Song\", \"description\": \"Save the current song.\" },\n { \"x\": 23, \"width\": 3, \"key\": \"new\", \"type\": \"action\", \"format\": \"text\", \"label\": \"New Song\", \"description\": \"Create a new song.\" }\n ] },\n { \"id\": \"export\", \"match\": [{ \"x\": 0, \"text\": \"EXPORT/SHARE\" }], \"cells\": [\n { \"x\": 13, \"width\": 6, \"key\": \"render\", \"type\": \"action\", \"format\": \"text\", \"label\": \"Render\", \"description\": \"Open the Render View to render audio.\" },\n { \"x\": 20, \"width\": 6, \"key\": \"bundle\", \"type\": \"action\", \"format\": \"text\", \"label\": \"Bundle\", \"description\": \"Bundle the song with the instruments and samples it uses.\" }\n ] },\n { \"id\": \"clearUnused\", \"match\": [{ \"x\": 0, \"text\": \"CLEAR UNUSED\" }], \"cells\": [\n { \"x\": 13, \"width\": 7, \"key\": \"clearPhrases\", \"type\": \"action\", \"format\": \"text\", \"label\": \"Clear Unused Phrases\", \"description\": \"Frees chains and phrases that are not used in the song.\" },\n { \"x\": 21, \"width\": 8, \"key\": \"clearInstTbl\", \"type\": \"action\", \"format\": \"text\", \"label\": \"Clear Unused Instruments/Tables\", \"description\": \"Frees instruments, EQs and tables that are not used in the song.\" }\n ] },\n { \"id\": \"instPool\", \"match\": [{ \"x\": 0, \"text\": \"INST. POOL\" }], \"cells\": [\n { \"x\": 13, \"width\": 15, \"key\": \"viewInstPool\", \"type\": \"action\", \"format\": \"text\", \"label\": \"View Instrument Pool\", \"description\": \"Go to the Instrument Pool View.\" }\n ] },\n { \"id\": \"timeStats\", \"match\": [{ \"x\": 0, \"text\": \"TIME STATS\" }], \"cells\": [\n { \"x\": 13, \"width\": 15, \"key\": \"viewTimeStats\", \"type\": \"action\", \"format\": \"text\", \"label\": \"View Time Stats\", \"description\": \"Open the Time Stats View (time spent on the song).\" }\n ] },\n { \"id\": \"system\", \"match\": [{ \"x\": 0, \"text\": \"SYSTEM\" }, { \"x\": 13, \"text\": \"SETTINGS\" }], \"cells\": [\n { \"x\": 13, \"width\": 8, \"key\": \"systemSettings\", \"type\": \"action\", \"format\": \"text\", \"label\": \"System Settings\", \"description\": \"Go to the System Settings View.\" },\n { \"x\": 22, \"width\": 9, \"key\": \"usbDrive\", \"type\": \"action\", \"format\": \"text\", \"label\": \"USB Drive\", \"description\": \"Puts the M8 in USB drive mode so SD card files can be transferred to a computer.\" }\n ] }\n ]\n },\n\n \"systemsettings\": {\n \"rows\": [\n { \"id\": \"backlight\", \"match\": [{ \"x\": 0, \"text\": \"BACKLIGHT\" }], \"cells\": [\n { \"x\": 15, \"width\": 2, \"key\": \"backlight\", \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Backlight (BACKLIGHT)\", \"description\": \"Backlight LED brightness of the display.\" }\n ] },\n { \"id\": \"fontOptions\", \"match\": [{ \"x\": 0, \"text\": \"FONT OPTIONS\" }], \"cells\": [\n { \"x\": 15, \"width\": 15, \"key\": \"fontOptions\", \"type\": \"option\", \"format\": \"text\", \"label\": \"Font Options (FONT OPTIONS)\", \"description\": \"Font settings, including a large font mode for readability.\" }\n ] },\n { \"id\": \"theme\", \"match\": [{ \"x\": 0, \"text\": \"THEME\" }], \"cells\": [\n { \"x\": 15, \"width\": 10, \"key\": \"editTheme\", \"type\": \"action\", \"format\": \"text\", \"label\": \"Theme (EDIT THEME)\", \"description\": \"Open the Theme View.\" }\n ] },\n { \"id\": \"notePreview\", \"match\": [{ \"x\": 0, \"text\": \"NOTE PREVIEW\" }], \"cells\": [\n { \"x\": 15, \"width\": 3, \"key\": \"notePreview\", \"type\": \"onOff\", \"format\": \"text\", \"label\": \"Note Preview (NOTE PREVIEW)\", \"description\": \"Plays the note when editing notes in the phrase view.\" }\n ] },\n { \"id\": \"recCountIn\", \"match\": [{ \"x\": 0, \"regex\": \"^REC\\\\.\\\\s?(COUNT IN|METRONOME)\\\\s*$\", \"width\": 13 }], \"cells\": [\n { \"x\": 15, \"width\": 6, \"key\": \"recCountIn\", \"type\": \"option\", \"format\": \"hexOption\", \"label\": \"Recording Count-in (REC. COUNT IN)\", \"description\": \"Number of phrases/measures of metronome count-in before recording audio in the Sample Editor or Selection to Sample.\" }\n ] },\n { \"id\": \"metronomeVol\", \"match\": [{ \"x\": 0, \"text\": \"METRONOME VOL\" }], \"cells\": [\n { \"x\": 15, \"width\": 2, \"key\": \"metronomeVol\", \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Metronome Volume (METRONOME VOL)\", \"description\": \"Volume of the metronome sound (edit /System/Metronome.m8i to change the sound).\" }\n ] },\n { \"id\": \"songTemplate\", \"match\": [{ \"x\": 0, \"text\": \"SONG TEMPLATE\" }], \"cells\": [\n { \"x\": 15, \"width\": 4, \"key\": \"songTemplateSave\", \"type\": \"action\", \"format\": \"text\", \"label\": \"Song Template (SAVE)\", \"description\": \"Use the currently opened song as the template for new songs.\" },\n { \"x\": 20, \"width\": 5, \"key\": \"songTemplateClear\", \"type\": \"action\", \"format\": \"text\", \"label\": \"Song Template (CLEAR)\", \"description\": \"Clear the song template.\" }\n ] },\n { \"id\": \"usbAudioMode\", \"match\": [{ \"x\": 0, \"text\": \"USB AUDIO MODE\" }], \"cells\": [\n { \"x\": 15, \"width\": 14, \"key\": \"usbAudioMode\", \"type\": \"option\", \"format\": \"text\", \"label\": \"USB Audio Mode (USB AUDIO MODE)\", \"description\": \"USB output as stereo or multi-channel (multi-channel sends all 8 tracks and the effects individually, 12 stereo channels).\",\n \"options\": { \"STEREO OUT\": \"Stereo mix over USB.\" } }\n ] },\n { \"id\": \"usbMainOut\", \"match\": [{ \"x\": 0, \"text\": \"USB MAIN OUT\" }], \"cells\": [\n { \"x\": 15, \"width\": 6, \"key\": \"usbMainOut\", \"type\": \"option\", \"format\": \"text\", \"label\": \"USB Main Out (USB MAIN OUT)\", \"description\": \"Routing behaviour of USB audio (see the USB audio routing options).\" }\n ] },\n { \"id\": \"lineInGate\", \"match\": [{ \"x\": 0, \"text\": \"LINE-IN GATE\" }], \"cells\": [\n { \"x\": 15, \"width\": 6, \"key\": \"lineInGate\", \"type\": \"option\", \"format\": \"text\", \"label\": \"Line-in Gate (LINE-IN GATE)\", \"description\": \"Noise gate threshold for the line input, adjustable from -70 dB to -10 dB (OFF disables it).\" }\n ] },\n { \"id\": \"splashScreen\", \"match\": [{ \"x\": 0, \"text\": \"SPLASH SCREEN\" }], \"cells\": [\n { \"x\": 15, \"width\": 3, \"key\": \"splashScreen\", \"type\": \"onOff\", \"format\": \"text\", \"label\": \"Splash Screen (SPLASH SCREEN)\", \"description\": \"Show the M8 logo and play the start-up sound when turning on the device.\" }\n ] },\n { \"id\": \"hpProtection\", \"match\": [{ \"x\": 0, \"text\": \"HP PROTECTION\" }], \"cells\": [\n { \"x\": 15, \"width\": 3, \"key\": \"hpProtection\", \"type\": \"onOff\", \"format\": \"text\", \"label\": \"Headphone Protection (HP PROTECTION)\", \"description\": \"Limits the headphone output volume to F0 when a pack is unplugged and re-inserted.\" }\n ] },\n { \"id\": \"keyDelayRep\", \"match\": [{ \"x\": 0, \"text\": \"KEY DELAY:REP\" }], \"cells\": [\n { \"x\": 15, \"width\": 7, \"key\": \"keyDelayRep\", \"type\": \"parameter\", \"format\": \"decimalPair\", \"unit\": \" ms\", \"parts\": [\"delay\", \"repeat\"], \"label\": \"Key Delay:Repeat (KEY DELAY:REP)\", \"description\": \"Key repeat timing in milliseconds: delay before a held key starts repeating, then the time between repeats.\" }\n ] },\n { \"id\": \"battCharging\", \"match\": [{ \"x\": 0, \"regex\": \"^BATT\\\\.\\\\s?(CHARGING|STATUS)\\\\s*$\", \"width\": 14 }], \"cells\": [\n { \"x\": 15, \"width\": 14, \"key\": \"battCharging\", \"type\": \"option\", \"format\": \"text\", \"label\": \"Battery Charging (BATT. CHARGING / STATUS)\", \"description\": \"Whether the battery charges while the M8 is on and USB is connected (turn off to reduce power drawn from the host); also shows the current charging status.\" }\n ] }\n ]\n },\n\n \"effectsettings\": {\n \"sections\": [\"MODFX\", \"DELAY\", \"REVERB\"],\n \"rows\": [\n { \"id\": \"modfxType\", \"section\": \"MODFX\", \"match\": [{ \"x\": 7, \"text\": \"MOD TYPE\" }], \"cells\": [\n { \"x\": 21, \"width\": 10, \"key\": \"modfxType\", \"type\": \"option\", \"format\": \"hexOption\", \"label\": \"ModFX Type (MOD TYPE)\", \"description\": \"ModFX effect: Chorus, Phaser or Flanger (also sets the phase position).\",\n \"options\": { \"CHORUS\": \"Stereo chorus.\", \"PHASER\": \"Stereo phaser.\", \"FLANGER\": \"Stereo flanger.\" } }\n ] },\n { \"id\": \"modfxInputEq\", \"section\": \"MODFX\", \"match\": [{ \"x\": 7, \"text\": \"INPUT EQ\" }], \"cells\": [\n { \"x\": 21, \"width\": 2, \"key\": \"modfxInputEq\", \"type\": \"action\", \"format\": \"text\", \"label\": \"Input EQ (INPUT EQ)\", \"description\": \"Open the EQ editor for this effect's input.\" }\n ] },\n { \"id\": \"modfxDepthFreq\", \"section\": \"MODFX\", \"match\": [{ \"x\": 7, \"text\": \"MOD DEPTH:FRQ\" }], \"cells\": [\n { \"x\": 21, \"width\": 5, \"key\": \"modfxDepthFreq\", \"type\": \"parameter\", \"format\": \"hexPair\", \"parts\": [\"mod depth\", \"mod frequency\"], \"label\": \"ModFX Mod Depth:Frequency (MOD DEPTH:FRQ)\", \"description\": \"Amount of modulation in the stereo signal (first value) and speed of the modulation (second value).\" }\n ] },\n { \"id\": \"modfxWidth\", \"section\": \"MODFX\", \"match\": [{ \"x\": 7, \"text\": \"STEREO WIDTH\" }], \"cells\": [\n { \"x\": 21, \"width\": 2, \"key\": \"modfxWidth\", \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Stereo Width (STEREO WIDTH)\", \"description\": \"Stereo width: 00 is mono, FF is stereo.\" }\n ] },\n { \"id\": \"modfxReverbSend\", \"section\": \"MODFX\", \"match\": [{ \"x\": 7, \"text\": \"REVERB SEND\" }], \"cells\": [\n { \"x\": 21, \"width\": 2, \"key\": \"modfxReverbSend\", \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Reverb Send (REVERB SEND)\", \"description\": \"Amount of this effect sent to the reverb.\" }\n ] },\n\n { \"id\": \"delayInputEq\", \"section\": \"DELAY\", \"match\": [{ \"x\": 7, \"text\": \"INPUT EQ\" }], \"cells\": [\n { \"x\": 21, \"width\": 2, \"key\": \"delayInputEq\", \"type\": \"action\", \"format\": \"text\", \"label\": \"Input EQ (INPUT EQ)\", \"description\": \"Open the EQ editor for this effect's input.\" }\n ] },\n { \"id\": \"delayTime\", \"section\": \"DELAY\", \"match\": [{ \"x\": 7, \"text\": \"TIME L:R\" }], \"cells\": [\n { \"x\": 21, \"width\": 5, \"key\": \"delayTime\", \"type\": \"parameter\", \"format\": \"hexPair\", \"parts\": [\"left time\", \"right time\"], \"label\": \"Delay Time L:R (TIME L:R)\", \"description\": \"Delay time for the left and right channels: first digit in beats, second digit in fractions of a beat (40 is 4 beats).\" }\n ] },\n { \"id\": \"delayFeedback\", \"section\": \"DELAY\", \"match\": [{ \"x\": 7, \"text\": \"FEEDBACK\" }], \"cells\": [\n { \"x\": 21, \"width\": 2, \"key\": \"delayFeedback\", \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Feedback (FEEDBACK)\", \"description\": \"Amount of the delayed signal fed back into the delay.\" }\n ] },\n { \"id\": \"delayWidth\", \"section\": \"DELAY\", \"match\": [{ \"x\": 7, \"text\": \"STEREO WIDTH\" }], \"cells\": [\n { \"x\": 21, \"width\": 2, \"key\": \"delayWidth\", \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Stereo Width (STEREO WIDTH)\", \"description\": \"Stereo width: 00 is mono, FF is stereo.\" }\n ] },\n { \"id\": \"delayReverbSend\", \"section\": \"DELAY\", \"match\": [{ \"x\": 7, \"text\": \"REVERB SEND\" }], \"cells\": [\n { \"x\": 21, \"width\": 2, \"key\": \"delayReverbSend\", \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Reverb Send (REVERB SEND)\", \"description\": \"Amount of this effect sent to the reverb.\" }\n ] },\n\n { \"id\": \"reverbInputEq\", \"section\": \"REVERB\", \"match\": [{ \"x\": 7, \"text\": \"INPUT EQ\" }], \"cells\": [\n { \"x\": 21, \"width\": 2, \"key\": \"reverbInputEq\", \"type\": \"action\", \"format\": \"text\", \"label\": \"Input EQ (INPUT EQ)\", \"description\": \"Open the EQ editor for this effect's input.\" }\n ] },\n { \"id\": \"reverbRoomSize\", \"section\": \"REVERB\", \"match\": [{ \"x\": 7, \"text\": \"ROOM SIZE\" }], \"cells\": [\n { \"x\": 21, \"width\": 2, \"key\": \"reverbRoomSize\", \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Room Size (ROOM SIZE)\", \"description\": \"Size of the reverb room.\" }\n ] },\n { \"id\": \"reverbDecayShimmer\", \"section\": \"REVERB\", \"match\": [{ \"x\": 7, \"text\": \"DECAY:SHIMMER\" }], \"cells\": [\n { \"x\": 21, \"width\": 5, \"key\": \"reverbDecayShimmer\", \"type\": \"parameter\", \"format\": \"hexPair\", \"parts\": [\"decay\", \"shimmer\"], \"label\": \"Reverb Decay:Shimmer (DECAY:SHIMMER)\", \"description\": \"Reverb decay time (first value) and shimmer amount (second value).\" }\n ] },\n { \"id\": \"reverbDepthFreq\", \"section\": \"REVERB\", \"match\": [{ \"x\": 7, \"text\": \"MOD DEPTH:FRQ\" }], \"cells\": [\n { \"x\": 21, \"width\": 5, \"key\": \"reverbDepthFreq\", \"type\": \"parameter\", \"format\": \"hexPair\", \"parts\": [\"mod depth\", \"mod frequency\"], \"label\": \"Reverb Mod Depth:Frequency (MOD DEPTH:FRQ)\", \"description\": \"Reverb modulation depth (first value) and frequency (second value).\" }\n ] },\n { \"id\": \"reverbWidth\", \"section\": \"REVERB\", \"match\": [{ \"x\": 7, \"text\": \"STEREO WIDTH\" }], \"cells\": [\n { \"x\": 21, \"width\": 2, \"key\": \"reverbWidth\", \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Stereo Width (STEREO WIDTH)\", \"description\": \"Stereo width: 00 is mono, FF is stereo.\" }\n ] }\n ]\n },\n\n \"mixer\": {\n \"rows\": [\n { \"id\": \"speaker\", \"match\": [{ \"x\": 0, \"text\": \"SPEAKER\" }], \"cells\": [\n { \"x\": 12, \"width\": 2, \"key\": \"speakerVol\", \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Speaker Volume (SPEAKER VOL)\", \"description\": \"Volume of the built-in speaker.\" }\n ] },\n { \"id\": \"outputVol\", \"match\": [{ \"x\": 0, \"text\": \"OUTPUT VOL\" }], \"cells\": [\n { \"x\": 12, \"width\": 2, \"key\": \"outputVol\", \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Output Volume (OUTPUT VOL)\", \"description\": \"Volume of the audio output (headphones / line out).\" }\n ] },\n { \"id\": \"tracks\", \"match\": [{ \"x\": 0, \"regex\": \"^[0-9A-F]{2}$\", \"width\": 2 }, { \"x\": 21, \"regex\": \"^[0-9A-F]{2}$\", \"width\": 2 }], \"cells\": [\n { \"x\": 0, \"width\": 2, \"key\": \"track1\", \"type\": \"volume\", \"format\": \"hex\", \"label\": \"Track 1 Volume\", \"meta\": { \"trackIndex\": 1 }, \"description\": \"Volume of track 1. Instrument volumes are applied on top; the track's send levels are set in the instruments.\" },\n { \"x\": 3, \"width\": 2, \"key\": \"track2\", \"type\": \"volume\", \"format\": \"hex\", \"label\": \"Track 2 Volume\", \"meta\": { \"trackIndex\": 2 }, \"description\": \"Volume of track 2.\" },\n { \"x\": 6, \"width\": 2, \"key\": \"track3\", \"type\": \"volume\", \"format\": \"hex\", \"label\": \"Track 3 Volume\", \"meta\": { \"trackIndex\": 3 }, \"description\": \"Volume of track 3.\" },\n { \"x\": 9, \"width\": 2, \"key\": \"track4\", \"type\": \"volume\", \"format\": \"hex\", \"label\": \"Track 4 Volume\", \"meta\": { \"trackIndex\": 4 }, \"description\": \"Volume of track 4.\" },\n { \"x\": 12, \"width\": 2, \"key\": \"track5\", \"type\": \"volume\", \"format\": \"hex\", \"label\": \"Track 5 Volume\", \"meta\": { \"trackIndex\": 5 }, \"description\": \"Volume of track 5.\" },\n { \"x\": 15, \"width\": 2, \"key\": \"track6\", \"type\": \"volume\", \"format\": \"hex\", \"label\": \"Track 6 Volume\", \"meta\": { \"trackIndex\": 6 }, \"description\": \"Volume of track 6.\" },\n { \"x\": 18, \"width\": 2, \"key\": \"track7\", \"type\": \"volume\", \"format\": \"hex\", \"label\": \"Track 7 Volume\", \"meta\": { \"trackIndex\": 7 }, \"description\": \"Volume of track 7.\" },\n { \"x\": 21, \"width\": 2, \"key\": \"track8\", \"type\": \"volume\", \"format\": \"hex\", \"label\": \"Track 8 Volume\", \"meta\": { \"trackIndex\": 8 }, \"description\": \"Volume of track 8.\" }\n ] },\n { \"id\": \"eq\", \"match\": [{ \"x\": 27, \"text\": \"EQ\" }], \"cells\": [\n { \"x\": 27, \"width\": 2, \"key\": \"mixEq\", \"type\": \"action\", \"format\": \"text\", \"label\": \"Main Mix EQ (EQ)\", \"description\": \"Open the main mix EQ editor ([EDIT] or [SHIFT]+[RIGHT]).\" }\n ] },\n { \"id\": \"levels\", \"match\": [{ \"x\": 0, \"regex\": \"^[0-9A-F]{2}$\", \"width\": 2 }, { \"x\": 3, \"regex\": \"^[0-9A-F]{2}$\", \"width\": 2 }, { \"x\": 6, \"regex\": \"^[0-9A-F]{2}$\", \"width\": 2 }, { \"x\": 12, \"regex\": \"^[0-9A-F]{2}$\", \"width\": 2 }], \"cells\": [\n { \"x\": 0, \"width\": 2, \"key\": \"modfxVol\", \"type\": \"volume\", \"format\": \"hex\", \"label\": \"ModFX Volume (MX)\", \"description\": \"Output level of the ModFX send effect.\" },\n { \"x\": 3, \"width\": 2, \"key\": \"delayVol\", \"type\": \"volume\", \"format\": \"hex\", \"label\": \"Delay Volume (DE)\", \"description\": \"Output level of the delay send effect.\" },\n { \"x\": 6, \"width\": 2, \"key\": \"reverbVol\", \"type\": \"volume\", \"format\": \"hex\", \"label\": \"Reverb Volume (RE)\", \"description\": \"Output level of the reverb send effect.\" },\n { \"x\": 12, \"width\": 2, \"key\": \"inputVol\", \"type\": \"volume\", \"format\": \"hex\", \"label\": \"Analog Input Volume (INPUT)\", \"description\": \"Volume of the analog input for monitoring. In dual-mono mode this is the left channel.\" },\n { \"x\": 15, \"width\": 2, \"key\": \"inputVol2\", \"type\": \"volume\", \"format\": \"hex\", \"label\": \"Analog Input Volume, 2nd channel\", \"description\": \"Enables dual-mono input mode when set (right channel volume); clear with [EDIT]+[OPTION] to disable.\" },\n { \"x\": 18, \"width\": 2, \"key\": \"usbVol\", \"type\": \"volume\", \"format\": \"hex\", \"label\": \"USB Input Volume (USB)\", \"description\": \"Volume of anything the USB host computer sends to the M8.\" },\n { \"x\": 27, \"width\": 2, \"key\": \"mainVol\", \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Main Volume (MIX)\", \"description\": \"Main song volume, applied after the limiter, DJ filter and OTT. [EDIT] or [SHIFT]+[RIGHT] opens the Mix Scope.\" }\n ] },\n { \"id\": \"limiter\", \"match\": [{ \"x\": 0, \"text\": \"MX\" }, { \"x\": 3, \"text\": \"DE\" }, { \"x\": 6, \"text\": \"RE\" }], \"cells\": [\n { \"x\": 27, \"width\": 2, \"key\": \"limiter\", \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Limiter (LIM)\", \"description\": \"Main mix limiter amount; the limiter is engaged when the value is above 00. [EDIT] or [SHIFT]+[RIGHT] opens the Limiter Scope.\" }\n ] },\n { \"id\": \"sendModfx\", \"match\": [{ \"x\": 9, \"text\": \"MX\" }], \"cells\": [\n { \"x\": 12, \"width\": 2, \"key\": \"inputModfx\", \"type\": \"volume\", \"format\": \"hex\", \"label\": \"Analog Input ModFX Send (INPUT MX)\", \"description\": \"How much of the analog input goes to the ModFX effect.\" },\n { \"x\": 18, \"width\": 2, \"key\": \"usbModfx\", \"type\": \"volume\", \"format\": \"hex\", \"label\": \"USB Input ModFX Send (USB MX)\", \"description\": \"How much of the USB input goes to the ModFX effect.\" },\n { \"x\": 27, \"width\": 2, \"key\": \"djFilter\", \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"DJ Filter (DJF)\", \"description\": \"DJ filter cutoff: 80 is off, below 80 low-pass (or band-stop), above 80 high-pass. Mode and options are in the Scope View.\" }\n ] },\n { \"id\": \"sendDelay\", \"match\": [{ \"x\": 9, \"text\": \"DE\" }], \"cells\": [\n { \"x\": 12, \"width\": 2, \"key\": \"inputDelay\", \"type\": \"volume\", \"format\": \"hex\", \"label\": \"Analog Input Delay Send (INPUT DE)\", \"description\": \"How much of the analog input goes to the delay effect.\" },\n { \"x\": 18, \"width\": 2, \"key\": \"usbDelay\", \"type\": \"volume\", \"format\": \"hex\", \"label\": \"USB Input Delay Send (USB DE)\", \"description\": \"How much of the USB input goes to the delay effect.\" },\n { \"x\": 27, \"width\": 2, \"key\": \"ott\", \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"OTT (OTT)\", \"description\": \"Over The Top multi-band compression mixed in parallel: 00 to 80 mixes it in; from 80 and above the main mix is faded out.\" }\n ] },\n { \"id\": \"sendReverb\", \"match\": [{ \"x\": 9, \"text\": \"RE\" }], \"cells\": [\n { \"x\": 12, \"width\": 2, \"key\": \"inputReverb\", \"type\": \"volume\", \"format\": \"hex\", \"label\": \"Analog Input Reverb Send (INPUT RE)\", \"description\": \"How much of the analog input goes to the reverb effect.\" },\n { \"x\": 18, \"width\": 2, \"key\": \"usbReverb\", \"type\": \"volume\", \"format\": \"hex\", \"label\": \"USB Input Reverb Send (USB RE)\", \"description\": \"How much of the USB input goes to the reverb effect.\" }\n ] }\n ]\n },\n\n \"mixeq\": {\n \"rows\": [\n { \"id\": \"gain\", \"match\": [{ \"x\": 0, \"text\": \"GAIN\" }], \"cells\": [\n { \"x\": 5, \"width\": 6, \"key\": \"lowGain\", \"type\": \"parameter\", \"format\": \"float\", \"unit\": \" dB\", \"label\": \"LOW Gain\", \"description\": \"Boost (positive) or cut (negative) applied by the LOW band, in decibels.\" },\n { \"x\": 16, \"width\": 6, \"key\": \"midGain\", \"type\": \"parameter\", \"format\": \"float\", \"unit\": \" dB\", \"label\": \"MID Gain\", \"description\": \"Boost (positive) or cut (negative) applied by the MID band, in decibels.\" },\n { \"x\": 27, \"width\": 6, \"key\": \"highGain\", \"type\": \"parameter\", \"format\": \"float\", \"unit\": \" dB\", \"label\": \"HIGH Gain\", \"description\": \"Boost (positive) or cut (negative) applied by the HIGH band, in decibels.\" }\n ] },\n { \"id\": \"freq\", \"match\": [{ \"x\": 0, \"text\": \"FREQ\" }], \"cells\": [\n { \"x\": 6, \"width\": 5, \"key\": \"lowFreq\", \"type\": \"parameter\", \"format\": \"decimal\", \"unit\": \" Hz\", \"label\": \"LOW Frequency\", \"description\": \"Centre (or corner) frequency of the LOW band.\" },\n { \"x\": 17, \"width\": 5, \"key\": \"midFreq\", \"type\": \"parameter\", \"format\": \"decimal\", \"unit\": \" Hz\", \"label\": \"MID Frequency\", \"description\": \"Centre (or corner) frequency of the MID band.\" },\n { \"x\": 28, \"width\": 5, \"key\": \"highFreq\", \"type\": \"parameter\", \"format\": \"decimal\", \"unit\": \" Hz\", \"label\": \"HIGH Frequency\", \"description\": \"Centre (or corner) frequency of the HIGH band.\" }\n ] },\n { \"id\": \"q\", \"match\": [{ \"x\": 0, \"text\": \"Q\" }, { \"x\": 1, \"regex\": \"^\\\\s?$\", \"width\": 1 }], \"cells\": [\n { \"x\": 6, \"width\": 3, \"key\": \"lowQ\", \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"LOW Q\", \"description\": \"Quality factor (bandwidth) of the LOW band: higher values narrow it.\" },\n { \"x\": 17, \"width\": 3, \"key\": \"midQ\", \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"MID Q\", \"description\": \"Quality factor (bandwidth) of the MID band: higher values narrow it.\" },\n { \"x\": 28, \"width\": 3, \"key\": \"highQ\", \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"HIGH Q\", \"description\": \"Quality factor (bandwidth) of the HIGH band: higher values narrow it.\" }\n ] },\n { \"id\": \"type\", \"match\": [{ \"x\": 0, \"text\": \"TYPE\" }], \"cells\": [\n { \"x\": 6, \"width\": 9, \"key\": \"lowType\", \"type\": \"option\", \"format\": \"text\", \"label\": \"LOW Type\", \"description\": \"Filter shape of the LOW band.\", \"options\": { \"LOWCUT\": \"Removes frequencies below the cutoff.\", \"LOWSHELF\": \"Boosts or cuts frequencies below the shelf frequency.\", \"BELL\": \"Peaking filter: boosts or cuts around the centre frequency.\", \"BANDPASS\": \"Passes frequencies around the centre frequency, attenuating both sides.\", \"HI.SHELF\": \"Boosts or cuts frequencies above the shelf frequency.\", \"HI.CUT\": \"Attenuates or removes frequencies above the cutoff.\", \"ALLPASS\": \"Passes all frequencies at equal gain while shifting their phase.\" } },\n { \"x\": 17, \"width\": 9, \"key\": \"midType\", \"type\": \"option\", \"format\": \"text\", \"label\": \"MID Type\", \"description\": \"Filter shape of the MID band.\", \"options\": { \"LOWCUT\": \"Removes frequencies below the cutoff.\", \"LOWSHELF\": \"Boosts or cuts frequencies below the shelf frequency.\", \"BELL\": \"Peaking filter: boosts or cuts around the centre frequency.\", \"BANDPASS\": \"Passes frequencies around the centre frequency, attenuating both sides.\", \"HI.SHELF\": \"Boosts or cuts frequencies above the shelf frequency.\", \"HI.CUT\": \"Attenuates or removes frequencies above the cutoff.\", \"ALLPASS\": \"Passes all frequencies at equal gain while shifting their phase.\" } },\n { \"x\": 28, \"width\": 9, \"key\": \"highType\", \"type\": \"option\", \"format\": \"text\", \"label\": \"HIGH Type\", \"description\": \"Filter shape of the HIGH band.\", \"options\": { \"LOWCUT\": \"Removes frequencies below the cutoff.\", \"LOWSHELF\": \"Boosts or cuts frequencies below the shelf frequency.\", \"BELL\": \"Peaking filter: boosts or cuts around the centre frequency.\", \"BANDPASS\": \"Passes frequencies around the centre frequency, attenuating both sides.\", \"HI.SHELF\": \"Boosts or cuts frequencies above the shelf frequency.\", \"HI.CUT\": \"Attenuates or removes frequencies above the cutoff.\", \"ALLPASS\": \"Passes all frequencies at equal gain while shifting their phase.\" } }\n ] },\n { \"id\": \"mode\", \"match\": [{ \"x\": 0, \"text\": \"MODE\" }], \"cells\": [\n { \"x\": 6, \"width\": 6, \"key\": \"lowMode\", \"type\": \"option\", \"format\": \"text\", \"label\": \"LOW Mode\", \"description\": \"How the LOW band is applied to the stereo signal.\", \"options\": { \"STEREO\": \"Processes left and right identically.\", \"MID\": \"Affects only the mid (centre) information.\", \"SIDE\": \"Affects only the side information (difference between left and right).\", \"LEFT\": \"Affects only the left channel.\", \"RIGHT\": \"Affects only the right channel.\" } },\n { \"x\": 17, \"width\": 6, \"key\": \"midMode\", \"type\": \"option\", \"format\": \"text\", \"label\": \"MID Mode\", \"description\": \"How the MID band is applied to the stereo signal.\", \"options\": { \"STEREO\": \"Processes left and right identically.\", \"MID\": \"Affects only the mid (centre) information.\", \"SIDE\": \"Affects only the side information (difference between left and right).\", \"LEFT\": \"Affects only the left channel.\", \"RIGHT\": \"Affects only the right channel.\" } },\n { \"x\": 28, \"width\": 6, \"key\": \"highMode\", \"type\": \"option\", \"format\": \"text\", \"label\": \"HIGH Mode\", \"description\": \"How the HIGH band is applied to the stereo signal.\", \"options\": { \"STEREO\": \"Processes left and right identically.\", \"MID\": \"Affects only the mid (centre) information.\", \"SIDE\": \"Affects only the side information (difference between left and right).\", \"LEFT\": \"Affects only the left channel.\", \"RIGHT\": \"Affects only the right channel.\" } }\n ] }\n ]\n },\n \"modfxeq\": { \"extends\": \"mixeq\" },\n \"delayeq\": { \"extends\": \"mixeq\" },\n \"reverbeq\": { \"extends\": \"mixeq\" },\n\n \"mixscope\": {\n \"rows\": [\n { \"id\": \"limiterAtkRel\", \"match\": [{ \"x\": 9, \"text\": \"ATK\" }, { \"x\": 16, \"text\": \"REL\" }, { \"x\": 23, \"text\": \"LIM\" }], \"cells\": [\n { \"x\": 13, \"width\": 2, \"key\": \"attack\", \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Limiter Attack (ATK)\", \"description\": \"Limiter attack time from 0 to 100 ms (see the on-screen help text for the conversion to milliseconds).\" },\n { \"x\": 20, \"width\": 2, \"key\": \"release\", \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Limiter Release (REL)\", \"description\": \"Limiter release time from 4 to 1000 ms; 00 activates AUTO mode (100-900 ms depending on the compression amount).\" },\n { \"x\": 27, \"width\": 2, \"key\": \"limiter\", \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Limiter (LIM)\", \"description\": \"Limiter amount.\" }\n ] },\n { \"id\": \"zoomPeak\", \"match\": [{ \"x\": 0, \"text\": \"ZOOM\" }], \"cells\": [\n { \"x\": 5, \"width\": 5, \"key\": \"zoom\", \"type\": \"parameter\", \"format\": \"text\", \"label\": \"Zoom (ZOOM)\", \"description\": \"Lower limit of the scope histogram, in decibels.\" },\n { \"x\": 20, \"width\": 6, \"key\": \"peak\", \"type\": \"parameter\", \"format\": \"float\", \"unit\": \" dB\", \"label\": \"Peak (PEAK)\", \"description\": \"Current peak level of the main mix in dB. Resets when MIX or LIM change; clear it with [OPT]+[EDIT].\" }\n ] },\n { \"id\": \"eq\", \"match\": [{ \"x\": 27, \"text\": \"EQ\" }], \"cells\": [\n { \"x\": 27, \"width\": 2, \"key\": \"mixEq\", \"type\": \"action\", \"format\": \"text\", \"label\": \"Main Mix EQ (EQ)\", \"description\": \"Open the main mix EQ editor.\" }\n ] },\n { \"id\": \"softClip\", \"match\": [{ \"x\": 9, \"text\": \"SOFT CLIP\" }], \"cells\": [\n { \"x\": 19, \"width\": 3, \"key\": \"softClip\", \"type\": \"onOff\", \"format\": \"text\", \"label\": \"Soft Clip (SOFT CLIP)\", \"description\": \"Gentle saturation after the limiter to prevent harsh clipping; raise MIX to exaggerate it.\" },\n { \"x\": 27, \"width\": 2, \"key\": \"mainVol\", \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Main Volume (MIX)\", \"description\": \"Main mix / song volume, applied after the limiter stage.\" }\n ] },\n { \"id\": \"mix\", \"match\": [{ \"x\": 23, \"text\": \"MIX\" }], \"cells\": [\n { \"x\": 27, \"width\": 2, \"key\": \"mainVol\", \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Main Volume (MIX)\", \"description\": \"Main mix / song volume, applied after the limiter stage.\" }\n ] },\n { \"id\": \"lim\", \"match\": [{ \"x\": 23, \"text\": \"LIM\" }], \"cells\": [\n { \"x\": 27, \"width\": 2, \"key\": \"limiter\", \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Limiter (LIM)\", \"description\": \"Limiter amount.\" }\n ] },\n { \"id\": \"djf\", \"match\": [{ \"x\": 23, \"text\": \"DJF\" }], \"cells\": [\n { \"x\": 27, \"width\": 2, \"key\": \"djFilter\", \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"DJ Filter (DJF)\", \"description\": \"DJ filter cutoff: 80 is off, 00-80 low-pass, 80-FF high-pass (mode set with TYPE).\" }\n ] },\n { \"id\": \"ott\", \"match\": [{ \"x\": 23, \"text\": \"OTT\" }], \"cells\": [\n { \"x\": 27, \"width\": 2, \"key\": \"ott\", \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"OTT (OTT)\", \"description\": \"OTT mix amount; from 80 and above the main mix volume is faded out.\" }\n ] }\n ],\n \"zones\": [{ \"id\": \"right\", \"labelX\": 23, \"labelWidth\": 4, \"x\": 27, \"width\": 6 }],\n \"vocabulary\": {\n \"ATK\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Limiter Attack (ATK)\", \"description\": \"Limiter attack time from 0 to 100 ms (see the on-screen help text for the conversion).\" },\n \"REL\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Limiter Release (REL)\", \"description\": \"Limiter release time from 4 to 1000 ms; 00 activates AUTO mode (100-900 ms depending on the compression amount).\" },\n \"TYPE\": { \"type\": \"option\", \"format\": \"hexOption\", \"label\": \"DJ Filter Type (TYPE)\", \"description\": \"DJ filter type: low-pass/high-pass, low-pass/band-stop or band-stop/high-pass.\" },\n \"RES\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"DJ Filter Resonance (RES)\", \"description\": \"Resonance of the DJ filter.\" },\n \"TIME\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"OTT Time (TIME)\", \"description\": \"OTT compressor envelope time as a percentage from 10% to 1000%.\" },\n \"COLOR\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"OTT Color (COLOR)\", \"description\": \"OTT compressor bands and low/high gains: above 80 shifts toward treble, below 80 toward bass.\" }\n }\n },\n \"limiterscope\": { \"extends\": \"mixscope\" },\n\n \"inst\": {\n \"rows\": [\n { \"id\": \"type\", \"match\": [{ \"x\": 0, \"text\": \"TYPE\" }], \"cells\": [\n { \"x\": 8, \"width\": 13, \"key\": \"instrumentType\", \"type\": \"instrumentType\", \"format\": \"text\", \"label\": \"Instrument Type (TYPE)\", \"description\": \"Kind of instrument in this slot: Wavsynth, Macrosynth, Sampler, FM Synth, Hypersynth, External Instrument, MIDI Out (or NONE for an unused slot). Changing it changes the parameters below.\" },\n { \"x\": 22, \"width\": 4, \"key\": \"load\", \"type\": \"action\", \"format\": \"text\", \"label\": \"Load Preset (LOAD)\", \"description\": \"Load an instrument preset from the SD card.\" },\n { \"x\": 27, \"width\": 4, \"key\": \"save\", \"type\": \"action\", \"format\": \"text\", \"label\": \"Save Preset (SAVE)\", \"description\": \"Save this instrument as a preset on the SD card.\" }\n ] },\n { \"id\": \"name\", \"match\": [{ \"x\": 0, \"text\": \"NAME\" }], \"cells\": [\n { \"x\": 8, \"width\": 12, \"key\": \"name\", \"type\": \"name\", \"format\": \"text\", \"label\": \"Instrument Name (NAME)\", \"description\": \"Name of the instrument: helps keep the song organised when browsing, saving or bundling instruments.\" }\n ] },\n { \"id\": \"transp\", \"match\": [{ \"x\": 0, \"text\": \"TRANSP.\" }], \"cells\": [\n { \"x\": 8, \"width\": 3, \"key\": \"transpose\", \"type\": \"onOff\", \"format\": \"text\", \"label\": \"Transpose Enabled (TRANSP.)\", \"description\": \"Enable or disable note transposition from scales, chain transpose columns and the project transpose for this instrument (turn off for percussive or effect instruments).\" },\n { \"x\": 21, \"width\": 2, \"key\": \"tableTic\", \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Table Tick (TBL. TIC)\", \"description\": \"Default speed at which this instrument's table advances: 01 is one tick per step, 02 two ticks per step. Can be overridden by the TIC command.\" },\n { \"x\": 29, \"width\": 2, \"key\": \"eq\", \"type\": \"eqSlot\", \"format\": \"hex\", \"label\": \"EQ Slot (EQ)\", \"description\": \"EQ bank assigned to this instrument (-- for none). [EDIT] or [SHIFT]+[RIGHT] opens the EQ editor.\" }\n ] }\n ],\n \"zones\": [\n { \"id\": \"left\", \"labelX\": 0, \"labelWidth\": 8, \"x\": 8, \"width\": 24 },\n { \"id\": \"right\", \"labelX\": 18, \"labelWidth\": 4, \"x\": 22, \"width\": 11 }\n ],\n \"operators\": { \"instrument\": \"fmsynth\", \"x\": 8, \"step\": 6, \"width\": 5, \"letters\": [\"A\", \"B\", \"C\", \"D\"] },\n \"vocabularyFrom\": \"instrumentTypes\"\n },\n\n \"instmods\": {\n \"slotLabel\": \"MOD\\\\d\",\n \"zones\": [\n { \"id\": \"left\", \"labelX\": 0, \"labelWidth\": 4, \"x\": 5, \"width\": 10 },\n { \"id\": \"right\", \"labelX\": 16, \"labelWidth\": 4, \"x\": 21, \"width\": 10 }\n ],\n \"vocabulary\": {\n \"MOD#\": { \"type\": \"option\", \"format\": \"hexOption\", \"label\": \"Modulator Type\", \"description\": \"Type of this modulation slot: AHD envelope, ADSR envelope, DRUM envelope, LFO, TRIG envelope or TRACKING. Slots can modulate their neighbour (Mod 1 affects Mod 2, 2→3, 3→4, 4→1).\",\n \"options\": { \"AHD ENV\": \"Attack Hold Decay envelope based on tempo.\", \"ADSR ENV\": \"Traditional Attack Decay Sustain Release envelope.\", \"DRUM ENV\": \"Sharp peak and swelling hold envelope for percussive elements.\", \"LFO\": \"Tempo-synced low frequency oscillator.\", \"TRIG ENV\": \"AHD envelope triggered by another playing track or instrument.\", \"TRACKING\": \"Modulates an instrument parameter from notes or velocities.\", \"OFF\": \"Slot unused.\" } },\n \"DEST\": { \"type\": \"option\", \"format\": \"hexOption\", \"label\": \"Destination (DEST)\", \"description\": \"Instrument parameter this modulator controls. MOD AMT, MOD RATE or MOD BOTH modulate the neighbouring modulator's amount, rate or both.\" },\n \"SRC\": { \"type\": \"parameter\", \"format\": \"hexOption\", \"label\": \"Source (SRC)\", \"description\": \"Modulation source. Trig envelope: an instrument number (00-7F) or a whole track (80-87 = tracks 1-8). Tracking: NOTE, VELOCITY or VELOCITY TAKEOVER (velocity no longer affects instrument volume).\" },\n \"AMT\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Amount (AMT)\", \"description\": \"How strongly the modulator is applied to the destination.\" },\n \"ATK\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Attack (ATK)\", \"description\": \"Time to reach the amount (in ticks, relative to tempo).\" },\n \"HOLD\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Hold (HOLD)\", \"description\": \"Time to wait after the attack before decaying (in ticks).\" },\n \"DEC\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Decay (DEC)\", \"description\": \"Time to fall back (AHD/Drum: to zero; ADSR: to the sustain level), in ticks.\" },\n \"SUS\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Sustain (SUS)\", \"description\": \"Level held while the note is playing (ADSR).\" },\n \"REL\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Release (REL)\", \"description\": \"Time to fall back to zero after the note is released (ADSR); needs an OFF note or FX command.\" },\n \"PEAK\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Peak (PEAK)\", \"description\": \"Shape of the initial transient (drum envelope); modifies the duck amount and peak time.\" },\n \"BODY\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Body (BODY)\", \"description\": \"Time to hold the modulation before decaying (drum envelope).\" },\n \"OSC\": { \"type\": \"option\", \"format\": \"hexOption\", \"label\": \"LFO Shape (OSC)\", \"description\": \"LFO shape: triangle, sine, ramp down/up, exponential down/up, square down/up, random, drunk. The 'T' variants run in ticks (much faster).\",\n \"options\": { \"TRI\": \"Triangle.\", \"SIN\": \"Sine.\", \"RAMP DN\": \"Ramp down.\", \"RAMP UP\": \"Ramp up.\", \"EXP DN\": \"Exponential down.\", \"EXP UP\": \"Exponential up.\", \"SQU DN\": \"Square down.\", \"SQU UP\": \"Square up.\", \"RANDOM\": \"Random values.\", \"DRUNK\": \"Random walk.\" } },\n \"TRIG\": { \"type\": \"option\", \"format\": \"hexOption\", \"label\": \"LFO Trigger (TRIG)\", \"description\": \"How the LFO reacts when the instrument triggers and when the shape ends.\",\n \"options\": { \"FREE\": \"Loop the shape and don't reset on new notes.\", \"RETRIG\": \"Loop the shape and reset on each instrument trigger.\", \"HOLD\": \"Hold the last value of the shape.\", \"ONCE\": \"Play the shape once and reset to the start value.\" } },\n \"FREQ\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"LFO Frequency (FREQ)\", \"description\": \"Rate of the LFO in steps (16th notes), or in ticks for the 'T' shapes.\" },\n \"LVAL\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Low Value (LVAL)\", \"description\": \"Lowest value of the source range scaled to the destination (tracking). Can be higher than HVAL to invert the range.\" },\n \"HVAL\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"High Value (HVAL)\", \"description\": \"Highest value of the source range scaled to the destination (tracking).\" }\n }\n },\n\n \"scale\": {\n \"notes\": \"Only the rows below the 12 interval rows; the intervals themselves are grid columns in m8-view-context.json.\",\n \"rows\": [\n { \"id\": \"key\", \"match\": [{ \"x\": 0, \"text\": \"KEY\" }], \"cells\": [\n { \"x\": 6, \"width\": 2, \"key\": \"key\", \"type\": \"option\", \"format\": \"text\", \"label\": \"Key (KEY)\", \"description\": \"Global key: the root note of the default scale (can be overridden per track with SCA / SCG).\" }\n ] },\n { \"id\": \"tune\", \"match\": [{ \"x\": 0, \"text\": \"TUNE\" }], \"cells\": [\n { \"x\": 6, \"width\": 6, \"key\": \"tune\", \"type\": \"parameter\", \"format\": \"float\", \"unit\": \" Hz\", \"label\": \"Tuning (TUNE)\", \"description\": \"Reference pitch of A4 in Hz (440.00 is standard).\" }\n ] },\n { \"id\": \"name\", \"match\": [{ \"x\": 0, \"text\": \"NAME\" }], \"cells\": [\n { \"x\": 6, \"width\": 16, \"key\": \"name\", \"type\": \"name\", \"format\": \"text\", \"label\": \"Scale Name (NAME)\", \"description\": \"Name of this scale (up to 16 characters); 92 factory scales load from /Scales/Factory.\" }\n ] },\n { \"id\": \"file\", \"match\": [{ \"x\": 6, \"text\": \"LOAD\" }, { \"x\": 11, \"text\": \"SAVE\" }], \"cells\": [\n { \"x\": 6, \"width\": 4, \"key\": \"load\", \"type\": \"action\", \"format\": \"text\", \"label\": \"Load Scale (LOAD)\", \"description\": \"Load a scale from the SD card.\" },\n { \"x\": 11, \"width\": 4, \"key\": \"save\", \"type\": \"action\", \"format\": \"text\", \"label\": \"Save Scale (SAVE)\", \"description\": \"Save this scale to the SD card.\" }\n ] }\n ]\n },\n\n \"loadinstrument\": { \"kind\": \"browser\", \"browserFor\": \"instrument preset\" },\n \"selectsavedirectory\": { \"kind\": \"browser\", \"browserFor\": \"save location\" },\n \"createdirectory\": { \"kind\": \"text\", \"fieldLabel\": \"Directory Name\", \"description\": \"Name of the directory to create on the SD card.\" }\n },\n\n \"instrumentTypes\": {\n \"common\": {\n \"name\": \"Common\",\n \"description\": \"Parameters shared by most instrument types.\",\n \"params\": {\n \"FILTER\": { \"type\": \"option\", \"format\": \"hexOption\", \"label\": \"Filter Type (FILTER)\", \"description\": \"Multi-mode filter type: low-pass, high-pass, band-pass, band-stop, LP>HP (low-pass with RES controlling a 1-pole high-pass), ZDF low-pass, ZDF high-pass.\" },\n \"CUTOFF\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Filter Cutoff (CUTOFF)\", \"description\": \"Filter cutoff frequency.\" },\n \"RES\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Filter Resonance (RES)\", \"description\": \"Filter resonance amount.\" },\n \"AMP\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Amplification (AMP)\", \"description\": \"Amplifies the signal; values above the maximum level are handled according to LIM.\" },\n \"LIM\": { \"type\": \"option\", \"format\": \"hexOption\", \"label\": \"Limit Mode (LIM)\", \"description\": \"Limit / clipping behaviour of the amplified signal.\",\n \"options\": { \"CLIP\": \"Values above the maximum level are clipped.\", \"SIN\": \"The waveform is fed through a sine function.\", \"FOLD\": \"Values above the maximum level are folded back into themselves.\", \"WRAP\": \"Values above the maximum level are wrapped vertically.\", \"POST\": \"Amplification applied with hard-clipping after the filter stage.\", \"POST:AD\": \"Post filter amplification with soft-clipping.\", \"POST:W1\": \"Post filter folding distortion.\", \"POST:W2\": \"Post filter folding distortion.\", \"POST:W3\": \"Post filter folding distortion.\" } },\n \"PAN\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Pan (PAN)\", \"description\": \"Stereo position of the instrument (80 is centre).\" },\n \"DRY\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Dry Volume (DRY)\", \"description\": \"Dry volume sent to the mixer channel.\" },\n \"MFX\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"ModFX Send (MFX)\", \"description\": \"ModFX send effect volume.\" },\n \"DEL\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Delay Send (DEL)\", \"description\": \"Delay send effect volume.\" },\n \"REV\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Reverb Send (REV)\", \"description\": \"Reverb send effect volume.\" }\n }\n },\n \"wavsynth\": {\n \"name\": \"Wavsynth\",\n \"description\": \"Wave table synthesizer and noise generator: an 8-bit sample buffer generated in real time from one of 9 basic shapes (repeated, skewed, mirrored, filtered, overdriven) or one of 61 built-in wave tables of 64 waveforms each.\",\n \"params\": {\n \"SHAPE\": { \"type\": \"option\", \"format\": \"hexOption\", \"label\": \"Shape (SHAPE)\", \"description\": \"Base waveform: PULSE 12% to 75%, SAW, TRIANGLE, SINE, NOISE PITCHED, NOISE, or WT wave tables. For PWM set PULSE 50% and adjust MIRROR (smoothness set by SIZE).\" },\n \"SIZE\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Size (SIZE)\", \"description\": \"Horizontal size of the waveform (number of samples).\" },\n \"MULT\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Multiply (MULT)\", \"description\": \"Multiplies the waveform by adding repeats of the shape; can sound like hard sync.\" },\n \"WARP\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Warp (WARP)\", \"description\": \"Pushes the shape to one side of the wave table.\" },\n \"SCAN\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Scan / Mirror (SCAN)\", \"description\": \"Shapes 0-8: mirrors the waveform at the given position (0-200%). Wave tables: scans through the 64 waveforms.\" },\n \"FILTER\": { \"type\": \"option\", \"format\": \"hexOption\", \"label\": \"Filter Type (FILTER)\", \"description\": \"Multi-mode filter type, plus 4 modes that apply the filter into the waveform: WAV LP, WAV HP, WAV BP, WAV BS.\" }\n }\n },\n \"macrosynth\": {\n \"name\": \"Macrosynth\",\n \"description\": \"44 engines ('shapes') from basic analog to 2-op FM, physical modelling and percussion; TIMBRE and COLOR change meaning per shape.\",\n \"params\": {\n \"SHAPE\": { \"type\": \"option\", \"format\": \"hexOption\", \"label\": \"Shape (SHAPE)\", \"description\": \"One of the 44 Macrosynth engines (see the Macrosynth Models appendix).\" },\n \"TIMBRE\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Timbre (TIMBRE)\", \"description\": \"First parameter of the selected shape.\" },\n \"COLOR\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Color (COLOR)\", \"description\": \"Second parameter of the selected shape.\" },\n \"DEGRADE\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Degrade (DEGRADE)\", \"description\": \"Sample rate reduction effect.\" },\n \"REDUX\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Redux (REDUX)\", \"description\": \"Bit rate reduction effect.\" }\n }\n },\n \"sampler\": {\n \"name\": \"Sampler\",\n \"description\": \"Sample player, editor and recorder for 1 or 2 channel PCM .wav files.\",\n \"params\": {\n \"SAMPLE\": { \"type\": \"name\", \"format\": \"text\", \"label\": \"Sample (SAMPLE)\", \"description\": \"The loaded sample; use LOAD to browse for one, REC. to record or EDIT to open the Sample Editor.\" },\n \"SLICE\": { \"type\": \"option\", \"format\": \"hexOption\", \"label\": \"Slice (SLICE)\", \"description\": \"Slices the sample into equal sections, mapped from the lowest octave (C-1); 01 FILE uses the markers stored in the file.\" },\n \"PLAY\": { \"type\": \"option\", \"format\": \"hexOption\", \"label\": \"Play Mode (PLAY)\", \"description\": \"Play direction and loop mode.\",\n \"options\": { \"FWD\": \"Forward, no loop.\", \"REV\": \"Reverse, no loop.\", \"FWDLOOP\": \"Looping forward.\", \"REVLOOP\": \"Looping reverse.\", \"FWD PP\": \"Ping-pong looping, forward first.\", \"REV PP\": \"Ping-pong looping, reverse first.\", \"OSC\": \"Oscillator mode: ignores the start position and doesn't restart on retrigger.\", \"OSC REV\": \"Oscillator mode, reverse.\", \"OSC PP\": \"Oscillator mode, ping-pong.\", \"REPITCH\": \"Pitches the sample from the song tempo (adjust with STEPS/BPM).\" } },\n \"START\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Start (START)\", \"description\": \"Sample start position (ignored in OSC play modes).\" },\n \"LOOP ST.\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Loop Start (LOOP ST.)\", \"description\": \"Where the sample loops back to when it reaches the end in a looping play mode.\" },\n \"LOOP ST\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Loop Start (LOOP ST)\", \"description\": \"Where the sample loops back to when it reaches the end in a looping play mode.\" },\n \"LEN\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Length (LEN)\", \"description\": \"Sample length.\" },\n \"LENGTH\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Length (LENGTH)\", \"description\": \"Sample length.\" },\n \"DETUNE\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Detune (DETUNE)\", \"description\": \"Detune with 80 as the centre frequency: first digit steps semitones, second digit 1/16 semitone.\" },\n \"DEGRADE\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Degrade (DEGRADE)\", \"description\": \"Sample rate reduction; any value above 00 disables sample interpolation.\" }\n }\n },\n \"fmsynth\": {\n \"name\": \"FM Synth\",\n \"description\": \"Four-operator, 12-algorithm FM synthesizer. Each operator has a base shape, ratio, level, feedback and two modulation slots.\",\n \"operatorDestinations\": {\n \"LEV\": \"controls the level from 00 to the currently set level.\",\n \"RAT\": \"adds 0.00 to +16.00 to the current ratio.\",\n \"PIT\": \"adds to the note pitch, in semitones, for this operator.\",\n \"FBK\": \"controls the feedback from 00 to the currently set feedback.\"\n },\n \"params\": {\n \"ALGO\": { \"type\": \"option\", \"format\": \"hexOption\", \"label\": \"Algorithm (ALGO)\", \"description\": \"One of 12 algorithms (oscillator routing); the last one is additive mode.\" },\n \"A\": { \"type\": \"option\", \"format\": \"hexOption\", \"label\": \"Operator A (A)\", \"description\": \"Operator letter with optional shape (SIN, SW2-SW6, SAW, SQU, PUL, IMP, TRI, NOISE, then wave tables).\" },\n \"B\": { \"type\": \"option\", \"format\": \"hexOption\", \"label\": \"Operator B (B)\", \"description\": \"Operator letter with optional shape (SIN, SW2-SW6, SAW, SQU, PUL, IMP, TRI, NOISE, then wave tables).\" },\n \"C\": { \"type\": \"option\", \"format\": \"hexOption\", \"label\": \"Operator C (C)\", \"description\": \"Operator letter with optional shape (SIN, SW2-SW6, SAW, SQU, PUL, IMP, TRI, NOISE, then wave tables).\" },\n \"D\": { \"type\": \"option\", \"format\": \"hexOption\", \"label\": \"Operator D (D)\", \"description\": \"Operator letter with optional shape (SIN, SW2-SW6, SAW, SQU, PUL, IMP, TRI, NOISE, then wave tables).\" },\n \"RATIO\": { \"type\": \"parameter\", \"format\": \"float\", \"label\": \"Ratio (RATIO)\", \"description\": \"Operator frequency ratio relative to the played note (2.00 is an octave up, 0.50 an octave down).\" },\n \"LEV/FB\": { \"type\": \"parameter\", \"format\": \"hexPair\", \"parts\": [\"level\", \"feedback\"], \"label\": \"Level / Feedback (LEV/FB)\", \"description\": \"Operator volume and feedback.\" },\n \"MOD\": { \"type\": \"option\", \"format\": \"hexOption\", \"label\": \"Modulation Slot (MOD)\", \"description\": \"Assigns one of MOD1-MOD4 to control a destination on the operator.\" },\n \"LEV\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Level Mod (LEV)\", \"description\": \"Controls the level from 00 to the currently set level.\" },\n \"RAT\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Ratio Mod (RAT)\", \"description\": \"Adds 0.00 to +16.00 to the current ratio.\" },\n \"PIT\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Pitch Mod (PIT)\", \"description\": \"Adds to the note pitch, in semitones, for the operator.\" },\n \"FBK\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Feedback Mod (FBK)\", \"description\": \"Controls feedback from 00 to the currently set feedback.\" },\n \"MOD#\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Modulation Source\", \"description\": \"Modulation source value freely assigned to operators through the MOD slots.\" }\n }\n },\n \"hypersynth\": {\n \"name\": \"Hypersynth\",\n \"description\": \"Six-note saw wave chord synthesizer with 16 chord banks of up to 6 intervals, detune (swarm), stereo width and a square sub-oscillator.\",\n \"params\": {\n \"SCALE\": { \"type\": \"option\", \"format\": \"hexOption\", \"label\": \"Scale (SCALE)\", \"description\": \"One of the song's scales, or 00 to use the song/track scale settings.\" },\n \"CHORD\": { \"type\": \"option\", \"format\": \"hexOption\", \"label\": \"Chord (CHORD)\", \"description\": \"Chord bank and its intervals (copy with [SHIFT]+[OPTION], paste with [SHIFT]+[EDIT]).\" },\n \"SHIFT\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Shift (SHIFT)\", \"description\": \"Cross-fades volume between the first 3 intervals and the second 3.\" },\n \"SHAPE\": { \"type\": \"option\", \"format\": \"hexOption\", \"label\": \"Shape (SHAPE)\", \"description\": \"Waveform of the chord oscillators (SAW by default; not in the manual, seen on a Model:02, firmware 6.5).\" },\n \"SWARM\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Swarm (SWARM)\", \"description\": \"Detunes the oscillators relative to their interval.\" },\n \"WIDTH\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Stereo Width (WIDTH)\", \"description\": \"Stereo width of the oscillators, from mono 00 to full stereo FF.\" },\n \"SUBOSC\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Sub Oscillator (SUBOSC)\", \"description\": \"Mixes in a sub-oscillator 2 octaves below the root (below 80) or 1 octave below (above 80).\" }\n }\n },\n \"external\": {\n \"name\": \"External Instrument\",\n \"description\": \"Hybrid MIDI output and audio input device: notes and MIDI FX are sent to the chosen port and channel, audio returns through its filter and amp stage.\",\n \"params\": {\n \"INPUT\": { \"type\": \"option\", \"format\": \"hexOption\", \"label\": \"Audio Input (INPUT)\", \"description\": \"Audio input: line-in (stereo, left, right), USB (stereo, left, right), or line-in and USB.\" },\n \"PORT\": { \"type\": \"option\", \"format\": \"hexOption\", \"label\": \"MIDI Port (PORT)\", \"description\": \"MIDI output port: MIDI and USB, MIDI only, USB only, or Internal (track MIDI input / global MIDI CCs from the MIDI Settings View).\" },\n \"CHANNEL\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"MIDI Channel (CHANNEL)\", \"description\": \"MIDI channel, 1 to 16.\" },\n \"BANK:PG\": { \"type\": \"parameter\", \"format\": \"hexPair\", \"parts\": [\"bank\", \"program\"], \"label\": \"Bank / Program (BANK:PG)\", \"description\": \"Optional MIDI bank select and program change (program change can also be sent as an FX command).\" },\n \"CC#\": { \"type\": \"parameter\", \"format\": \"text\", \"label\": \"Custom CC (CC:VAL)\", \"description\": \"A custom MIDI CC number, then an optional default value (-- for none), sent with the note-on after program change messages.\" }\n }\n },\n \"midiout\": {\n \"name\": \"MIDI Out\",\n \"description\": \"Controls external devices over USB MIDI or the TRS (type A) MIDI output.\",\n \"params\": {\n \"PORT\": { \"type\": \"option\", \"format\": \"hexOption\", \"label\": \"MIDI Port (PORT)\", \"description\": \"MIDI output port: MIDI and USB, MIDI only, USB only, or Internal (track MIDI input / global MIDI CCs from the MIDI Settings View).\" },\n \"CHANNEL\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"MIDI Channel (CHANNEL)\", \"description\": \"MIDI channel, 1 to 16.\" },\n \"BANK:PG\": { \"type\": \"parameter\", \"format\": \"hexPair\", \"parts\": [\"bank\", \"program\"], \"label\": \"Bank / Program (BANK:PG)\", \"description\": \"Optional MIDI bank select and program change (program change can also be sent as an FX command).\" },\n \"CC#\": { \"type\": \"parameter\", \"format\": \"text\", \"label\": \"Custom CC (CC:VAL)\", \"description\": \"One of up to 10 custom MIDI CC numbers per instrument, then an optional default value (-- for none), sent with the note-on after program change messages.\" }\n }\n }\n },\n\n \"instrumentTypeNames\": {\n \"WAVS\": \"wavsynth\",\n \"MACRO\": \"macrosynth\",\n \"SAMPL\": \"sampler\",\n \"FM\": \"fmsynth\",\n \"HYPER\": \"hypersynth\",\n \"EXTERN\": \"external\",\n \"MIDI\": \"midiout\"\n }\n}\n","/**\r\n * parameterViews.ts\r\n *\r\n * Maps the non-grid (\"parameter\") views - instrument, modulation, mixer, project,\r\n * effects, EQ, scope, system settings, file browsers - onto typed field cells,\r\n * using m8-parameter-views.json.\r\n *\r\n * These views are label/value screens, so a row is recognised from its text (its\r\n * label), not from a row number, and the cells of that row come from the schema. Views\r\n * whose labels change with the instrument or modulation type are read through two\r\n * label/value \"zones\" and the label is looked up in a vocabulary.\r\n *\r\n * Pure text in, cell definitions out; viewContext.ts turns them into parsed fields.\r\n */\r\n\r\nimport paramSchema from './m8-parameter-views.json'\r\nimport type { ColumnDef } from './viewContext'\r\n\r\nexport type ValueFormat = 'hex' | 'hexOption' | 'hexPair' | 'decimal' | 'decimalPair' | 'float' | 'text' | 'auto'\r\n\r\ninterface MatchDef {\r\n x: number\r\n text?: string\r\n regex?: string\r\n width?: number\r\n}\r\n\r\ninterface RowDef {\r\n id: string\r\n section?: string\r\n match: MatchDef[]\r\n cells: ColumnDef[]\r\n}\r\n\r\ninterface ZoneDef {\r\n id: string\r\n labelX: number\r\n labelWidth: number\r\n x: number\r\n width: number\r\n}\r\n\r\ntype VocabEntry = Pick<ColumnDef, 'type' | 'format' | 'label' | 'description' | 'parts' | 'options' | 'unit'>\r\n\r\ninterface InstrumentTypeDef {\r\n name: string\r\n description: string\r\n params: Record<string, VocabEntry>\r\n /** FM: what each destination of an operator's MOD slot does ('LEV', 'RAT'...). */\r\n operatorDestinations?: Record<string, string>\r\n}\r\n\r\n/** Rows with one value per operator (FM): the columns of the values. */\r\ninterface OperatorsDef {\r\n instrument: string\r\n x: number\r\n step: number\r\n width: number\r\n letters: string[]\r\n}\r\n\r\ninterface LayoutDef {\r\n extends?: string\r\n /** Pattern of the heading label that names the block a cell belongs to ('MOD\\\\d'): read upward in the cell's zone. */\r\n slotLabel?: string\r\n kind?: 'browser' | 'text'\r\n browserFor?: string\r\n fieldLabel?: string\r\n description?: string\r\n sections?: string[]\r\n rows?: RowDef[]\r\n zones?: ZoneDef[]\r\n operators?: OperatorsDef\r\n vocabulary?: Record<string, VocabEntry>\r\n vocabularyFrom?: 'instrumentTypes'\r\n}\r\n\r\nexport type ParameterLayout = Omit<LayoutDef, 'extends'>\r\n\r\nconst rawViews = paramSchema.views as unknown as Record<string, LayoutDef>\r\nconst instrumentTypes = paramSchema.instrumentTypes as unknown as Record<string, InstrumentTypeDef>\r\nconst instrumentTypeNames = paramSchema.instrumentTypeNames as Record<string, string>\r\n\r\nconst layouts: Record<string, ParameterLayout> = {}\r\nfor (const [name, def] of Object.entries(rawViews)) {\r\n const { extends: parent, ...own } = def\r\n layouts[name] = parent ? { ...rawViews[parent], ...own } : own\r\n}\r\n\r\n/** Layout of a parameter view, or null when the view has none. */\r\nexport function getParameterLayout(viewName: string): ParameterLayout | null {\r\n return layouts[viewName] ?? null\r\n}\r\n\r\n// text helpers \r\n\r\nconst slice = (line: string, x: number, width: number): string => (x >= line.length ? '' : line.slice(x, x + width))\r\n\r\nfunction matches(cond: MatchDef, line: string): boolean {\r\n const width = cond.width ?? cond.text?.length ?? 1\r\n // The device's text stream is lower case ('tempo', 'b-4'); schema text is upper case.\r\n const text = slice(line, cond.x, width)\r\n if (cond.text != null) return text.toUpperCase() === cond.text.toUpperCase()\r\n if (cond.regex != null) return new RegExp(cond.regex, 'i').test(text)\r\n return false\r\n}\r\n\r\n// sections and instrument type \r\n\r\n/**\r\n * Section heading ('MODFX', 'DELAY', 'REVERB') the cursor line belongs to: the\r\n * nearest heading at column 0 on this line or above it. `linesUpward` starts with the\r\n * cursor line, then the lines above it.\r\n */\r\nexport function detectSection(layout: ParameterLayout, linesUpward: string[]): string | null {\r\n const sections = layout.sections\r\n if (!sections) return null\r\n for (const line of linesUpward) {\r\n const upper = line.toUpperCase()\r\n const hit = sections.find((s) => upper.startsWith(s))\r\n if (hit) return hit\r\n }\r\n return null\r\n}\r\n\r\n/**\r\n * Block heading ('MOD1'…'MOD4') a cell in `zoneId` belongs to: the nearest line at or\r\n * above the cursor with a heading label in that zone.\r\n */\r\nexport function detectSlot(layout: ParameterLayout, zoneId: string, linesUpward: string[]): string | null {\r\n const zone = layout.zones?.find((z) => z.id === zoneId)\r\n if (!layout.slotLabel || !zone) return null\r\n const pattern = new RegExp(`^${layout.slotLabel}$`, 'i')\r\n for (const line of linesUpward) {\r\n const label = slice(line, zone.labelX, zone.labelWidth).trim()\r\n if (pattern.test(label)) return label.toUpperCase()\r\n }\r\n return null\r\n}\r\n\r\n/** 'WAVSYNTH' → 'wavsynth', 'MIDI OUT' → 'midiout', unknown text → null. */\r\nexport function instrumentKeyFromText(text: string): string | null {\r\n const letters = text.toUpperCase().replace(/[^A-Z]/g, '')\r\n if (!letters) return null\r\n const prefix = Object.keys(instrumentTypeNames).find((p) => letters.startsWith(p))\r\n return prefix ? instrumentTypeNames[prefix] : null\r\n}\r\n\r\n/** Instrument type shown on the TYPE row of the Instrument View, read off the screen lines. */\r\nexport function instrumentTypeFromLines(lines: string[]): { text: string; key: string | null } | null {\r\n for (const line of lines) {\r\n if (line.toUpperCase().startsWith('TYPE')) {\r\n const text = slice(line, 8, 13).trim()\r\n return text ? { text, key: instrumentKeyFromText(text) } : null\r\n }\r\n }\r\n return null\r\n}\r\n\r\n/** Display name and description of an instrument type key ('wavsynth' → Wavsynth). */\r\nexport function getInstrumentType(key: string): { name: string; description: string } | null {\r\n const t = instrumentTypes[key]\r\n return t ? { name: t.name, description: t.description } : null\r\n}\r\n\r\n// vocabulary \r\n\r\nfunction lookupVocabulary(layout: ParameterLayout, label: string, instrumentKey: string | null): VocabEntry | null {\r\n // 'MOD1'..'MOD4' and 'CCA'..'CCJ' share one entry.\r\n const norm = label.replace(/^MOD\\d$/, 'MOD#').replace(/^CC[A-J]$/, 'CC#')\r\n if (layout.vocabulary) return layout.vocabulary[norm] ?? null\r\n if (layout.vocabularyFrom !== 'instrumentTypes') return null\r\n const order = instrumentKey ? [instrumentKey, 'common'] : ['common', ...Object.keys(instrumentTypes).filter((k) => k !== 'common')]\r\n for (const key of order) {\r\n const hit = instrumentTypes[key]?.params[norm]\r\n if (hit) return hit\r\n }\r\n return null\r\n}\r\n\r\nconst camel = (label: string): string =>\r\n label\r\n .toLowerCase()\r\n .replace(/[^a-z0-9]+(.)?/g, (_, c: string | undefined) => (c ? c.toUpperCase() : ''))\r\n .replace(/^[^a-z]+/, '')\r\n\r\nconst LABEL_SHAPE = /^[A-Z][A-Z0-9.:#/ -]*$/\r\n\r\n// reading a line \r\n\r\n/** Operator rows of the Instrument view: label to the vocabulary key and key prefix of its cells. */\r\nconst OPERATOR_ROWS: Record<string, string> = { RATIO: 'ratio', 'LEV/FB': 'levFb', MOD: 'mod' }\r\nconst OPERATOR_MOD_VALUE = /^(?:-----|[1-4]>[A-Z]{3})$/i\r\n\r\n/**\r\n * The cells of an FM row with one value per operator (RATIO, LEV/FB, the two MOD rows, the\r\n * shapes under ALGO), or null when the line is none of them. Each cell names its operator, so\r\n * the four columns of a row are described alike.\r\n */\r\nfunction operatorCells(def: OperatorsDef, line: string, vocab: InstrumentTypeDef): ColumnDef[] | null {\r\n const label = slice(line, 0, def.x).trim().toUpperCase()\r\n const columns = def.letters.map((letter, i) => ({ letter, x: def.x + i * def.step }))\r\n const cellOf = (key: string, x: number, width: number, entry: Pick<ColumnDef, 'type' | 'format' | 'label' | 'description'> & Partial<ColumnDef>, letter: string): ColumnDef => ({\r\n ...entry,\r\n x,\r\n width,\r\n key: `${key}${letter}`,\r\n label: `${entry.label} · operator ${letter}`,\r\n meta: { paramLabel: label || key, operator: letter },\r\n })\r\n\r\n const rowKey = OPERATOR_ROWS[label]\r\n if (rowKey) {\r\n const entry = vocab.params[label]\r\n if (!entry) return null\r\n return columns.map(({ letter, x }) => cellOf(rowKey, x, def.width, label === 'MOD' ? modSlotEntry(entry, 1, vocab) : entry, letter))\r\n }\r\n if (label) return null\r\n\r\n // The shapes under ALGO: 'A SW4 B W0F C SIN D SIN', each as wide as its name.\r\n if (/^\\s+[A-D] \\S/i.test(line)) {\r\n const cells: ColumnDef[] = []\r\n for (const match of line.matchAll(/([A-D]) (\\S+)/gi)) {\r\n const letter = match[1].toUpperCase()\r\n const entry = vocab.params[letter]\r\n if (!entry || !def.letters.includes(letter)) continue\r\n cells.push({ ...entry, type: 'option', format: 'text', x: (match.index ?? 0) + 2, width: match[2].length, key: `shape${letter}`, label: `Shape · operator ${letter}`, meta: { paramLabel: 'SHAPE', operator: letter } })\r\n }\r\n return cells.length ? cells : null\r\n }\r\n\r\n // The second MOD row has no label: four slots, each '-----' or 'n>DEST'.\r\n const values = columns.map(({ x }) => slice(line, x, def.width).trim())\r\n const entry = vocab.params.MOD\r\n if (entry && values.every((value) => OPERATOR_MOD_VALUE.test(value)) && !slice(line, 0, def.x).trim()) {\r\n return columns.map(({ letter, x }) => cellOf('mod2', x, def.width, modSlotEntry(entry, 2, vocab), letter))\r\n }\r\n return null\r\n}\r\n\r\n/** The vocabulary entry of an operator's MOD slot `slot` (1 or 2), with what each 'n>DEST' value does. */\r\nfunction modSlotEntry(entry: VocabEntry, slot: number, vocab: InstrumentTypeDef): VocabEntry {\r\n const options: Record<string, string> = {}\r\n for (const [dest, text] of Object.entries(vocab.operatorDestinations ?? {})) {\r\n for (let source = 1; source <= 4; source += 1) options[`${source}>${dest}`] = `MOD${source} ${dest}: ${text}`\r\n }\r\n return { ...entry, type: 'option', format: 'text', label: `Modulation Slot ${slot} (MOD)`, options }\r\n}\r\n\r\n/** The custom CC row of MIDI OUT ('CCA CC:VAL 074:7F') or EXTERNAL ('CCA 074:7F'): its label and where its value starts. */\r\nconst CC_LABEL = /^(CC[A-J])(?: CC:VAL)? +(?=\\S)/i\r\nconst CC_VALUE_WIDTH = 6\r\n\r\nexport interface ParameterLineOptions {\r\n section: string | null\r\n instrumentKey: string | null\r\n /** Text under the cursor; the entry of a file-browser row. */\r\n entryText?: string | null\r\n}\r\n\r\n/**\r\n * The cells present on `line`: the schema row whose label text matches, or else the\r\n * label/value zones. Empty when nothing on the line is recognised.\r\n */\r\nexport function readParameterCells(\r\n layout: ParameterLayout,\r\n line: string,\r\n opts: ParameterLineOptions,\r\n): { rowId: string | null; cells: ColumnDef[] } {\r\n if (layout.kind === 'browser') {\r\n const entry = opts.entryText?.trim() || line.trim()\r\n if (!entry) return { rowId: null, cells: [] }\r\n const x = Math.max(0, line.indexOf(entry))\r\n return {\r\n rowId: 'entry',\r\n cells: [{ x, width: entry.length, key: 'entry', type: 'fileEntry', format: 'text', label: 'File Browser Entry', meta: { browserFor: layout.browserFor } }],\r\n }\r\n }\r\n if (layout.kind === 'text') {\r\n if (!line.trim()) return { rowId: null, cells: [] }\r\n const x = Math.max(0, line.search(/\\S/))\r\n return {\r\n rowId: 'text',\r\n cells: [{ x, width: Math.max(12, line.length - x), key: 'name', type: 'name', format: 'text', label: layout.fieldLabel ?? 'Name', description: layout.description }],\r\n }\r\n }\r\n\r\n for (const row of layout.rows ?? []) {\r\n if (row.section && opts.section && row.section !== opts.section) continue\r\n if (row.match.every((m) => matches(m, line))) return { rowId: row.id, cells: row.cells }\r\n }\r\n\r\n if (layout.operators && opts.instrumentKey === layout.operators.instrument) {\r\n const found = operatorCells(layout.operators, line, instrumentTypes[layout.operators.instrument])\r\n if (found) return { rowId: 'operators', cells: found }\r\n }\r\n\r\n const zones = layout.zones ?? []\r\n const cells: ColumnDef[] = []\r\n zones.forEach((zone, i) => {\r\n let label = slice(line, zone.labelX, zone.labelWidth).trim().toUpperCase()\r\n let valueX = zone.x\r\n let valueWidth: number | null = null\r\n // Custom CC rows have a longer label and their own value column.\r\n const cc = i === 0 ? CC_LABEL.exec(line) : null\r\n if (cc) {\r\n label = cc[1].toUpperCase()\r\n valueX = cc[0].length\r\n valueWidth = CC_VALUE_WIDTH\r\n }\r\n if (!LABEL_SHAPE.test(label)) return\r\n // The value ends where the next zone's label begins, when that label is present.\r\n const next = zones[i + 1]\r\n // A label is set off by a blank; letters spilling out of a long value ('16wt-osc:tube')\r\n // are not one.\r\n const nextIsLabel =\r\n next != null &&\r\n slice(line, next.labelX - 1, 1).trim() === '' &&\r\n LABEL_SHAPE.test(slice(line, next.labelX, next.labelWidth).trim().toUpperCase())\r\n const width = valueWidth ?? (next && nextIsLabel ? Math.max(1, next.labelX - 1 - valueX) : zone.width)\r\n if (!slice(line, valueX, width).trim()) return\r\n const vocab = lookupVocabulary(layout, label, opts.instrumentKey)\r\n // The same label can sit in both zones (two modulators side by side): keep keys unique.\r\n const base = camel(label) || zone.id\r\n const key = cells.some((c) => c.key === base) ? `${base}${zone.id[0].toUpperCase()}${zone.id.slice(1)}` : base\r\n cells.push({\r\n x: valueX,\r\n width,\r\n key,\r\n type: vocab?.type ?? 'parameter',\r\n format: vocab?.format ?? 'auto',\r\n label: vocab?.label ?? label,\r\n description: vocab?.description,\r\n parts: vocab?.parts,\r\n options: vocab?.options,\r\n unit: vocab?.unit,\r\n meta: { paramLabel: label, zone: zone.id },\r\n })\r\n })\r\n return { rowId: cells.length ? 'params' : null, cells }\r\n}\r\n\r\n// values \r\n\r\nexport interface ParsedValue {\r\n hexValue: number | null\r\n /** Natural number of the value: the hex value, decimal integer or float. */\r\n numericValue: number | null\r\n /** Both numbers of an 'XX:YY' pair. */\r\n values: number[] | null\r\n /** The non-numeric part: option name, caption, name text. */\r\n text: string | null\r\n}\r\n\r\nconst NONE: ParsedValue = { hexValue: null, numericValue: null, values: null, text: null }\r\n\r\nfunction detectFormat(s: string): ValueFormat {\r\n if (/^[0-9A-F]{2}$/i.test(s)) return 'hex'\r\n if (/^[0-9A-F]{2}:[0-9A-F]{2}$/i.test(s)) return 'hexPair'\r\n if (/^[+-]?\\d+([.,]\\d+)?$/.test(s)) return 'float'\r\n if (/^[0-9A-F]{2}\\S/i.test(s)) return 'hexOption'\r\n return 'text'\r\n}\r\n\r\n/** Reads a cell's text according to its format. Empty text gives all-null. */\r\nexport function parseValue(raw: string, format: ValueFormat): ParsedValue {\r\n const s = raw.trim()\r\n if (!s) return NONE\r\n const fmt = format === 'auto' ? detectFormat(s) : format\r\n\r\n switch (fmt) {\r\n case 'hex': {\r\n const m = /^([0-9A-F]{2})/i.exec(s)\r\n const n = m ? parseInt(m[1], 16) : null\r\n return { hexValue: n, numericValue: n, values: null, text: m ? null : s }\r\n }\r\n case 'hexOption': {\r\n const m = /^([0-9A-F]{2})\\s?(.*)$/i.exec(s)\r\n if (!m) return { ...NONE, text: s }\r\n const n = parseInt(m[1], 16)\r\n return { hexValue: n, numericValue: n, values: null, text: m[2].trim() || null }\r\n }\r\n case 'hexPair': {\r\n const m = /^([0-9A-F]{2})[:/]([0-9A-F]{2})/i.exec(s)\r\n if (!m) return { ...NONE, text: s }\r\n const values = [parseInt(m[1], 16), parseInt(m[2], 16)]\r\n return { hexValue: values[0], numericValue: values[0], values, text: null }\r\n }\r\n case 'decimal': {\r\n const n = parseInt(s, 10)\r\n return Number.isNaN(n) ? { ...NONE, text: s } : { hexValue: null, numericValue: n, values: null, text: null }\r\n }\r\n case 'decimalPair': {\r\n const m = /^(\\d+):(\\d+)/.exec(s)\r\n if (!m) return { ...NONE, text: s }\r\n const values = [parseInt(m[1], 10), parseInt(m[2], 10)]\r\n return { hexValue: null, numericValue: values[0], values, text: null }\r\n }\r\n case 'float': {\r\n const n = parseFloat(s.replace(',', '.'))\r\n return Number.isNaN(n) ? { ...NONE, text: s } : { hexValue: null, numericValue: n, values: null, text: null }\r\n }\r\n default:\r\n return { ...NONE, text: s }\r\n }\r\n}\r\n\r\n/** Description of the option currently selected ('CLIP' → \"Values above the maximum level are clipped.\"). */\r\nexport function describeOption(options: Record<string, string> | undefined, text: string | null): string | null {\r\n if (!options || !text) return null\r\n const t = text.toUpperCase().trim()\r\n if (options[t]) return options[t]\r\n const key = Object.keys(options).find((k) => k.length >= 3 && t.startsWith(k))\r\n return key ? options[key] : null\r\n}\r\n","/**\r\n * fxCommands.ts\r\n *\r\n * Lookup of FX commands (the 3-letter names in the FX columns) with what is known\r\n * about them: description, whether they are relative, and which instrument types\r\n * offer them. Data lives in m8-view-context.json (`fxCommands`, `instrumentFx`).\r\n */\r\n\r\nimport schema from './m8-view-context.json'\r\n\r\nexport type FxCategory = 'sequencer' | 'instrument' | 'mixer' | 'modulation'\r\n\r\nexport interface M8FxCommandInfo {\r\n description: string\r\n category: FxCategory\r\n /**\r\n * Relative instrument commands add to the parameter's current value (01-7F add,\r\n * 80-FF subtract) and accumulate until a note is triggered with RET or with an\r\n * instrument number in the I column.\r\n */\r\n relative: boolean\r\n /**\r\n * Instrument types that offer the command, when it is not offered by all of them\r\n * (`null` = every type, or not an instrument command). Only types whose command\r\n * list was read from a device are considered.\r\n */\r\n instrumentTypes: string[] | null\r\n /** Modulator slot 1-4 for envelope/LFO commands (EA1, LF3…). */\r\n slot: number | null\r\n}\r\n\r\ninterface FxEntry {\r\n description: string\r\n relative?: boolean\r\n}\r\n\r\ntype FxCommandMap = Record<string, FxEntry>\r\n\r\nconst fxCommands = schema.fxCommands as unknown as Record<FxCategory, FxCommandMap>\r\nconst instrumentTypes = schema.instrumentFx.types as Record<string, { verified: boolean; commands: string[] }>\r\n\r\nconst VERIFIED_TYPES = Object.entries(instrumentTypes)\r\n .filter(([, t]) => t.verified)\r\n .map(([name]) => name)\r\n\r\n/** Verified instrument types that offer `cmd`, or null when all of them do (or none does). */\r\nfunction typesOffering(cmd: string): string[] | null {\r\n const offering = VERIFIED_TYPES.filter((t) => instrumentTypes[t].commands.includes(cmd))\r\n return offering.length === 0 || offering.length === VERIFIED_TYPES.length ? null : offering\r\n}\r\n\r\n/**\r\n * Look up a 3-character FX command name. Modulator commands carry their slot\r\n * number as the third character (EA1, LF3…). Returns undefined for unknown commands.\r\n */\r\nexport function lookupFxCommand(cmd: string): M8FxCommandInfo | undefined {\r\n const upper = cmd.toUpperCase()\r\n\r\n for (const category of ['sequencer', 'instrument', 'mixer'] as const) {\r\n const entry = fxCommands[category][upper]\r\n if (entry) {\r\n return {\r\n description: entry.description,\r\n category,\r\n relative: entry.relative === true,\r\n instrumentTypes: category === 'instrument' ? typesOffering(upper) : null,\r\n slot: null,\r\n }\r\n }\r\n }\r\n\r\n const mod = /^([A-Z]{2})([1-4])$/.exec(upper)\r\n const entry = mod ? fxCommands.modulation[mod[1]] : undefined\r\n if (mod && entry) {\r\n return {\r\n description: entry.description.replace('modulator N', `modulator ${mod[2]}`),\r\n category: 'modulation',\r\n relative: entry.relative === true,\r\n instrumentTypes: null,\r\n slot: Number(mod[2]),\r\n }\r\n }\r\n return undefined\r\n}\r\n\r\n/**\r\n * True for relative instrument commands: values 01-7F add to the parameter, 80-FF\r\n * subtract (see \"Relative and Absolute FX Commands\" in the manual).\r\n */\r\nexport function isRelativeFx(cmd: string): boolean {\r\n return lookupFxCommand(cmd)?.relative === true\r\n}\r\n\r\nexport interface M8InstrumentFxList {\r\n /** Type key as used in the schema: sampler, macrosynth, fmsynth, hypersynth, midiout. */\r\n type: string\r\n /** Read from a real device (false = taken from documentation). */\r\n verified: boolean\r\n commands: string[]\r\n}\r\n\r\n/** Instrument command names offered for an instrument type, or null when unknown. */\r\nexport function getInstrumentFxCommands(type: string): M8InstrumentFxList | null {\r\n const t = instrumentTypes[type.toLowerCase()]\r\n return t ? { type: type.toLowerCase(), verified: t.verified, commands: [...t.commands] } : null\r\n}\r\n\r\n/** What the manual says about relative commands (for tooltips and descriptions). */\r\nexport const RELATIVE_FX_RULE: string = schema.instrumentFx.relativeCommands.description\r\n","/**\n * fxInsight.ts\n *\n * Explains what an FX value does and follows FX \"lanes\" (FX1/FX2/FX3 columns)\n * down a phrase, so a REP command - or an empty cell sitting under one - can be\n * described in terms of the command it shifts.\n *\n * REP does not type the command above it again: it shifts the value that command\n * computed, on every row, by its own value counted in quarters (01 = +0.25,\n * 04 = +1, FC = -1). The value stops at 00 and FF, or at the RTO value (absolute);\n * it never wraps.\n *\n * Purely structural: works on already-parsed cells, no dependency on viewContext.\n */\n\nimport { isRelativeFx } from './fxCommands'\n\nexport { isRelativeFx }\n\n/** The subset of a parsed cell this module needs. */\nexport interface FxCell {\n rawValue: string\n hexValue: number | null\n isEmpty: boolean\n}\n\n/** One phrase step, indexed by column key ('note', 'fx1cmd', 'fx1val', …). */\nexport interface FxStepRow {\n fields: Record<string, FxCell | undefined>\n}\n\nexport type FxLaneEvent =\n /** Nothing FX-related happens in this lane at this step. */\n | 'none'\n /** A regular command starts a new base value; any REP ends. */\n | 'set'\n /** A regular command replaces a REP that was running. */\n | 'replace'\n /** REP XX (XX>0) starts repeating. */\n | 'rep'\n /** REP XX while another REP was already running (new step). */\n | 'repChange'\n /** REP 00 stops the repeat. */\n | 'repStop'\n /** REP with no earlier command in this lane and none carried over. */\n | 'orphan'\n /** RTO sets the limit for the running REP. */\n | 'rto'\n /** Empty cell while a REP is active: the value is shifted once more. */\n | 'continue'\n\nexport interface M8FxLaneTrace {\n /** FX lane, 1-3. */\n lane: number\n /** Phrase step this trace was computed for. */\n step: number\n event: FxLaneEvent\n /** Last regular command in the lane at or before `step`. */\n source: { cmd: string; value: number | null; step: number } | null\n /** True when a REP is running after this step. */\n active: boolean\n /** The running REP's step as typed (0x01-0x7F up, 0x80-0xFF down), in quarters: 04 = +1 per row. */\n increment: number | null\n /** Value set with RTO, if any: an absolute value, not a shift. */\n limit: number | null\n /**\n * Value of the command at this step while a REP runs, whole part (00-FF). Null for a\n * relative command (the instrument's own value is not on this screen: see `offset`),\n * and when unknown.\n */\n value: number | null\n /**\n * While a REP runs on a relative command: how far the parameter is from the instrument's\n * own value (the command's signed value plus the REP's steps; 0.25 units). The stop at\n * 00, FF or the RTO value is not applied, since the instrument's value is unknown here.\n */\n offset: number | null\n /** Why the REP no longer moves `value`: it reached FF, 00 or the RTO value. */\n held: 'max' | 'min' | 'limit' | null\n /** True when `value` is held at the RTO value. */\n atLimit: boolean\n /** Step where the running REP began. */\n repStartStep: number | null\n /** True when a REP from a previous phrase is running here (value unknown). */\n carried: boolean\n}\n\n// formatting helpers \n\nexport const hex2 = (n: number): string => n.toString(16).toUpperCase().padStart(2, '0')\nconst hex1 = (n: number): string => n.toString(16).toUpperCase()\nconst times = (n: number): string => (n === 1 ? 'once' : n === 2 ? 'twice' : `${n} times`)\nconst signed8 = (n: number): number => (n > 0x7f ? n - 0x100 : n)\nconst fmtSigned = (n: number): string => `${n > 0 ? '+' : n < 0 ? '−' : '±'}${Math.abs(n)}`\n\n/** Signed decimal reading of a relative value: 70 -> '+112', F0 -> '−16', 00 -> '±0'. */\nexport const formatSignedFxValue = (value: number): string => fmtSigned(signed8(value))\n\n/** Hex text of a value, with its signed decimal reading when the command is relative: '70 (+112)', 'F0 (−16)'. */\nexport function formatFxValue(cmd: string, value: number): string {\n if (isRelativeFx(cmd)) return `${hex2(value)} (${formatSignedFxValue(value)})`\n return hex2(value)\n}\n\n/** REP steps count in quarters. */\nconst QUARTERS = 4\nconst MAX_QUARTERS = 0xff * QUARTERS\n\n/** A REP value in quarters: 00-7F up, 80-FF down. */\nexport const repStepQuarters = (step: number): number => signed8(step)\n\n/** A REP value as the decimal step it is: 01 -> '+0.25', 04 -> '+1', FC -> '−1'. */\nexport function formatRepStep(step: number): string {\n return fmtSigned(repStepQuarters(step) / QUARTERS)\n}\n\n// notes (for ARP) \n\nconst PITCH_CLASSES = ['C-', 'C#', 'D-', 'D#', 'E-', 'F-', 'F#', 'G-', 'G#', 'A-', 'A#', 'B-']\n\nfunction noteToIndex(note: string | null | undefined): number | null {\n const m = /^([A-G])([#-])(\\d)$/.exec((note ?? '').trim().toUpperCase())\n if (!m) return null\n const pc = PITCH_CLASSES.indexOf(`${m[1]}${m[2]}`)\n return pc < 0 ? null : Number(m[3]) * 12 + pc\n}\n\nfunction indexToNote(i: number): string {\n return `${PITCH_CLASSES[((i % 12) + 12) % 12]}${Math.floor(i / 12)}`\n}\n\n// value interpretation \n\nexport interface FxValueContext {\n /** Note on the same step (used by ARP to spell out the notes). */\n note?: string | null\n /** Table view: HOP, CHA, DEL, TIC, REP, RTO and MTT mean something different there. */\n inTable?: boolean\n /** Row (phrase step or table row) the command is on: a HOP onto its own row, MTT on the first step. */\n row?: number | null\n}\n\nconst KEY_NAMES = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B']\n\n/** Ticks in a step with the default groove. */\nconst TICKS_PER_STEP = 6\n\n/** How a table rate of `ticks` per row reads in steps: \"2 rows per step\", \"one row every 2 steps\", or ''. */\nfunction rowsPerStep(ticks: number): string {\n if (ticks === TICKS_PER_STEP) return 'one row per step'\n if (TICKS_PER_STEP % ticks === 0) return `${TICKS_PER_STEP / ticks} rows per step`\n if (ticks % TICKS_PER_STEP === 0) return `one row every ${ticks / TICKS_PER_STEP} steps`\n return ''\n}\n\n/** TIC value meaning; `where` is 'this column' in a table, \"the instrument's table\" in a phrase. */\nfunction describeTic(value: number, where: string): string | null {\n if (value === 0) return `${where} moves one row each time the instrument is triggered`\n if (value <= 0xfb) {\n const steps = rowsPerStep(value)\n return `${where} moves one row every ${value} tick${value === 1 ? '' : 's'}${steps ? ` (${steps})` : ''}`\n }\n // FC to FF\n const modes = [\n 'octave map: the octave played picks the table row (instrument tables only)',\n 'velocity map: the velocity played picks the table row (instrument tables only)',\n 'note map: the note played picks the table row (instrument tables only)',\n `${where} moves one row every 5 ms (200 Hz)`,\n ]\n return modes[value - 0xfc] ?? null\n}\n\n/**\n * Short explanation of what `value` does for `cmd` (phrase view), or null when\n * the command has no interpretation worth adding beyond its name.\n */\nexport function describeFxValue(cmd: string, value: number, ctx: FxValueContext = {}): string | null {\n const c = cmd.toUpperCase()\n const x = value >> 4\n const y = value & 0xf\n const ticks = (n: number) => `${n} tick${n === 1 ? '' : 's'}`\n\n switch (c) {\n case 'ARP': {\n const base = noteToIndex(ctx.note)\n const semis = `+${x} and +${y} semitones`\n if (base == null) return `plays the note, then ${semis}`\n return `${semis}: ${indexToNote(base)} ${indexToNote(base + x)} ${indexToNote(base + y)}`\n }\n case 'ARC':\n return `arp mode ${x}, speed ${ticks(y)}`\n case 'CHA':\n if (ctx.inTable) return `everything left of it plays with probability ${value}/255 (00 never, FF always)`\n return `${x}/15 chance for what is left of it, ${y}/15 for what is right (0 never, F always)`\n case 'DEL':\n if (ctx.inTable) return `holds the table's playhead for ${ticks(value)}`\n return `delays this row by ${ticks(value)}`\n case 'KIL':\n return `stops the note after ${ticks(value)}`\n case 'OFF':\n return `note off after ${ticks(value)}`\n case 'PSL':\n return `portamento over ${ticks(value)}`\n case 'GRV':\n return `track uses groove ${hex2(value)}`\n case 'GGR':\n return `all tracks use groove ${hex2(value)}`\n case 'INS':\n return `switches to instrument ${hex2(value)}`\n case 'TBL':\n return `instrument uses table ${hex2(value)}`\n case 'TBX':\n return value === 0 ? 'stops the auxiliary table' : `plays table ${hex2(value)} alongside the instrument table`\n case 'THO':\n if (ctx.inTable) return `every column of the table jumps to row ${hex1(y)}`\n return `the instrument's table jumps to row ${hex1(y)} (the aux table when a TBX is on this step)`\n case 'HOP':\n if (ctx.inTable) {\n // A HOP that lands on its own row keeps landing there: the column stops.\n if (y === ctx.row) return x === 0 ? `this column stops on row ${hex1(y)}` : `holds this column on row ${hex1(y)} ${times(x)}`\n if (x === 0) return `always jumps back to row ${hex1(y)}: the rows in between loop`\n return `jumps to row ${hex1(y)} ${times(x)}, then the column runs on past it`\n }\n return value === 0xff ? 'stops this track' : `jumps to row ${hex1(y)} of the next phrase`\n case 'REP':\n if (!ctx.inTable || value === 0) return null\n return `plays the command above it ${value} more time${value === 1 ? '' : 's'}, one row each`\n case 'RTO': {\n if (!ctx.inTable) return null\n const total = value > 0x7f ? `${hex2(value)} (${signed8(value)})` : hex2(value)\n return `repeats the relative command above it until it has added ${total} in total`\n }\n case 'RMX':\n return `tracks to its left (selection ${hex1(x)}) jump to phrase row ${hex1(y)}`\n case 'RND':\n return `randomises the previous FX: left range ${x}, right range ${y} (0 none, F full)`\n case 'RNL':\n return `randomises the FX to its left: left range ${x}, right range ${y} (0 none, F full)`\n case 'RET':\n if (y === 0) return `single retrig after ${ticks(x)}`\n return `retrigs every ${ticks(y)}, volume ${x < 8 ? 'falls' : 'rises'} on each retrig (ramp ${x})`\n case 'PVB':\n case 'PVX':\n return `vibrato speed ${x}, depth ${y}`\n case 'PBN':\n return value < 0x80 ? `pitch slides up by ${value}` : `pitch slides down by ${0x100 - value}`\n case 'SCA':\n case 'SCG': {\n const key = KEY_NAMES[x]\n return `${c === 'SCA' ? 'track' : 'song'} scale ${y.toString(16).toUpperCase()}${key ? `, key ${key}` : ''}`\n }\n case 'SNG':\n return `moves the song position by ${fmtSigned(signed8(value))} row(s)`\n case 'SED':\n return `random seed ${hex2(value)}`\n case 'NXT':\n return `plays instrument ${hex2(value)} on the next track`\n case 'MTT': {\n if (ctx.inTable) return 'no effect in a table'\n const eighths = signed8(value)\n if (eighths < 0 && ctx.row === 0) return 'no effect: a row cannot be moved earlier on the first step'\n return eighths === 0 ? 'no timing shift' : `shifts the row by ${fmtSigned(eighths)}/8 tick`\n }\n case 'TPO':\n return `${value} BPM`\n case 'TSP':\n return `transposes the song by ${fmtSigned(signed8(value))} semitones`\n case 'TIC':\n return describeTic(value, ctx.inTable ? 'this column' : \"the instrument's table\")\n case 'XRZ':\n return value > 0 ? 'reverb freeze on' : 'reverb freeze off'\n default:\n break\n }\n\n if (isRelativeFx(c)) {\n const s = signed8(value)\n if (c === 'PIT') return s === 0 ? 'no change' : `${fmtSigned(s)} semitones`\n return s === 0 ? 'no change' : fmtSigned(s)\n }\n return null\n}\n\n// FX lane tracing (REP / RTO) \n\ntype Held = M8FxLaneTrace['held']\n\n/**\n * Moves a value (in quarters) by one REP step. An absolute value stops at 00, FF and the\n * RTO value and never wraps; a relative offset just adds up (its stops depend on the\n * instrument's own value).\n */\nfunction advance(q: number, relative: boolean, delta: number, limit: number | null): { q: number; held: Held } {\n if (relative) return { q: q + delta, held: null }\n let next = q + delta\n let held: Held = null\n if (limit != null) {\n const bound = limit * QUARTERS\n if ((delta > 0 && next >= bound && q <= bound) || (delta < 0 && next <= bound && q >= bound)) {\n next = bound\n held = 'limit'\n }\n }\n if (next >= MAX_QUARTERS && delta > 0) {\n next = MAX_QUARTERS\n held = 'max'\n } else if (next <= 0 && delta < 0) {\n next = 0\n held = 'min'\n }\n return { q: next, held }\n}\n\n/**\n * Walks FX lane `lane` (1-3) from step 0 to `step` and reports what is in effect\n * there. `rows` must hold every step of the phrase, index = step.\n *\n * Model: REP XX shifts the value the lane's last command computed by XX quarters on\n * each row (its own row included), keeps going through empty cells, and stops on a\n * new command or REP00. RTO XX in the same lane sets the absolute value it stops at.\n * `carriedIn` says a REP from a previous phrase is running (the '^^' marker).\n */\nexport function analyzeFxLane(rows: FxStepRow[], lane: number, step: number, carriedIn = false): M8FxLaneTrace {\n let source: M8FxLaneTrace['source'] = null\n let relative = false\n // Quarters: the command's value, or a relative command's signed offset.\n let level: number | null = null\n let inc: number | null = null\n let limit: number | null = null\n let active = carriedIn\n let held: Held = null\n let repStart: number | null = null\n let event: FxLaneEvent = 'none'\n\n const last = Math.min(step, rows.length - 1)\n for (let i = 0; i <= last; i += 1) {\n const cmdCell = rows[i].fields[`fx${lane}cmd`]\n const valCell = rows[i].fields[`fx${lane}val`]\n event = 'none'\n\n if (cmdCell && !cmdCell.isEmpty) {\n const cmd = cmdCell.rawValue.toUpperCase()\n const val = valCell && !valCell.isEmpty ? valCell.hexValue : null\n\n if (cmd === 'REP') {\n if (!val) {\n event = 'repStop'\n active = false\n inc = null\n held = null\n } else if (!source && !carriedIn) {\n event = 'orphan'\n } else {\n event = active ? 'repChange' : 'rep'\n if (!active) repStart = i\n active = true\n inc = val\n if (level != null) ({ q: level, held } = advance(level, relative, repStepQuarters(inc), limit))\n }\n } else if (cmd === 'RTO') {\n event = 'rto'\n limit = val\n } else {\n event = active ? 'replace' : 'set'\n source = { cmd, value: val, step: i }\n relative = isRelativeFx(cmd)\n level = val == null ? null : (relative ? signed8(val) : val) * QUARTERS\n active = false\n inc = null\n limit = null\n held = null\n repStart = null\n }\n } else if (active) {\n event = 'continue'\n if (level != null && inc != null) ({ q: level, held } = advance(level, relative, repStepQuarters(inc), limit))\n }\n }\n\n const running = active && level != null\n return {\n lane,\n step,\n event,\n source,\n active,\n increment: inc,\n limit,\n value: running && !relative && level != null ? Math.floor(level / QUARTERS) : null,\n offset: running && relative && level != null ? level / QUARTERS : null,\n held: active ? held : null,\n atLimit: active && held === 'limit',\n repStartStep: repStart,\n carried: active && !source,\n }\n}\n\n/**\n * One-sentence explanation of a lane trace, or null when the lane has nothing to\n * say at this step. Meant to be appended to the cell description.\n */\nexport function describeFxLane(t: M8FxLaneTrace): string | null {\n const src = t.source\n const inc = t.increment != null ? formatRepStep(t.increment) : ''\n const stop = t.limit != null ? `, or at the RTO value ${hex2(t.limit)} (${t.limit})` : ''\n // Where the value stands on this row.\n const now = (): string => {\n if (t.offset != null) return `${src?.cmd} is ${fmtSigned(t.offset)} from the instrument's own value on this row`\n if (t.value == null) return `${src?.cmd} has no value to shift`\n const held = t.held === 'limit' ? ', held at the RTO value' : t.held === 'max' ? ', it cannot go above FF' : t.held === 'min' ? ', it cannot go below 00' : ''\n return `${src?.cmd} is ${hex2(t.value)} (${t.value}) on this row${held}`\n }\n\n switch (t.event) {\n case 'rep':\n case 'repChange': {\n if (t.carried) return `REP ${inc} per row continues the shift from the previous phrase`\n const verb = t.event === 'rep' ? 'shifts' : 'now shifts'\n return `${verb} ${src?.cmd} by ${inc} per row: ${now()}; it stops at 00 and FF${stop}, without wrapping`\n }\n case 'continue':\n if (t.carried) return 'REP from a previous phrase is still active in this lane (^^)'\n return `REP still active (${inc} per row since step ${t.repStartStep?.toString(16).toUpperCase()}): ${now()}`\n case 'repStop':\n return src ? `REP00 stops the shifting of ${src.cmd}` : 'REP00 stops the shifting'\n case 'orphan':\n return 'REP has no earlier command in this lane of the phrase to shift'\n case 'rto':\n return t.limit != null ? `sets the value a REP stops at: ${hex2(t.limit)} (${t.limit}), an absolute value, not a shift` : 'sets the value a REP stops at'\n case 'replace':\n return src ? `replaces the running REP (${src.cmd} is no longer shifted)` : null\n default:\n return null\n }\n}\n\n// relative commands \n\n/**\n * What a relative instrument command (VOL, CUT, PIT…) has done to its parameter\n * since the last note trigger.\n */\nexport interface M8RelativeFxState {\n cmd: string\n /** Net change applied to the parameter (signed, in 0.25 units when a REP is involved; the device stops at the parameter's range). */\n total: number\n /** Rows that moved the parameter (the rows of a running REP included). */\n count: number\n /** Step of the last trigger that reset it, or null when none happened in this phrase. */\n resetStep: number | null\n /** What reset it: a row with an instrument number in the I column, or a RET command. */\n resetBy: 'instrument' | 'ret' | null\n /** True when some of the change came from a running REP. */\n viaRep: boolean\n}\n\n/**\n * What makes step `row` trigger the instrument again, which resets every relative change:\n * an instrument number in the I column, a RET command, or nothing (null).\n */\nexport function relativeResetBy(row: FxStepRow): M8RelativeFxState['resetBy'] {\n const inst = row.fields.inst\n if (inst && !inst.isEmpty) return 'instrument'\n const retriggers = [1, 2, 3].some((lane) => {\n const c = row.fields[`fx${lane}cmd`]\n return c != null && !c.isEmpty && c.rawValue.toUpperCase() === 'RET'\n })\n return retriggers ? 'ret' : null\n}\n\n/**\n * Follows every relative instrument command through the phrase up to `step` (inclusive).\n *\n * Model (manual: \"Relative and Absolute FX Commands\"): a relative command adds its\n * value (01-7F) or subtracts it (80-FF) from the instrument's parameter, and the change\n * stays until a note is triggered with a RET command or with an instrument number in\n * the I column, which puts every parameter back to the instrument's assigned value.\n * A row that triggers resets first, then applies its own commands. A REP under a\n * relative command adds its own step (in quarters) on each row. State from a\n * previous phrase isn't visible, so totals only cover this phrase.\n */\nexport function analyzeRelativeFx(\n rows: FxStepRow[],\n step: number,\n carriedLane: number | null = null,\n): Record<string, M8RelativeFxState> {\n const states: Record<string, M8RelativeFxState> = {}\n let resetStep: number | null = null\n let resetBy: M8RelativeFxState['resetBy'] = null\n\n const last = Math.min(step, rows.length - 1)\n for (let i = 0; i <= last; i += 1) {\n const trigger = relativeResetBy(rows[i])\n if (trigger) {\n for (const k of Object.keys(states)) delete states[k]\n resetStep = i\n resetBy = trigger\n }\n\n for (const lane of [1, 2, 3]) {\n const trace = analyzeFxLane(rows, lane, i, carriedLane === lane)\n const src = trace.source\n if (!src || trace.carried || !isRelativeFx(src.cmd)) continue\n\n let applied: number | null = null\n let viaRep = false\n if (trace.event === 'set' || trace.event === 'replace') applied = src.value == null ? null : signed8(src.value)\n else if ((trace.event === 'rep' || trace.event === 'repChange' || trace.event === 'continue') && trace.increment != null && src.value != null) {\n applied = repStepQuarters(trace.increment) / QUARTERS\n viaRep = true\n }\n if (applied == null) continue\n\n let st = states[src.cmd]\n if (!st) {\n st = { cmd: src.cmd, total: 0, count: 0, resetStep, resetBy, viaRep: false }\n states[src.cmd] = st\n }\n st.total += applied\n st.count += 1\n st.viaRep ||= viaRep\n }\n }\n return states\n}\n\n/** \"+10\", \"−31\", \"+1.25\": the net change of a relative command, in signed decimal. */\nconst fmtOffset = (n: number): string => fmtSigned(n)\n\n/** Where the accumulation started, for messages. */\nfunction sinceText(s: M8RelativeFxState): string {\n if (s.resetStep == null) return 'the start of this phrase (earlier state unknown)'\n const at = s.resetStep.toString(16).toUpperCase()\n return s.resetBy === 'ret' ? `the RET on step ${at}` : `the instrument number on step ${at}`\n}\n\nconst RESET_HINT = 'stays until a note with an instrument number (I) or a RET resets it'\n\n/** Sentence about a relative command's accumulated effect, for the cell that applies it. */\nexport function describeRelativeFx(s: M8RelativeFxState | null | undefined): string {\n if (!s || s.count <= 1) return `relative: ${RESET_HINT}`\n const rep = s.viaRep ? ', REP steps included' : ''\n const moved = s.total === 0 ? `has had no net effect` : `has moved by ${fmtOffset(s.total)}`\n return `relative: ${s.cmd} ${moved} over ${s.count} rows since ${sinceText(s)}${rep}; ${RESET_HINT}`\n}\n\n/**\n * For an instrument number typed on a step: which relative changes are still in force\n * and will be wiped by the note it triggers. Null when nothing relative is active.\n */\nexport function describeRelativeReset(active: Record<string, M8RelativeFxState>): string | null {\n const list = Object.values(active).filter((s) => s.total !== 0)\n if (!list.length) return null\n return `triggers the instrument again: resets the relative changes still in force (${list.map((s) => `${s.cmd} ${fmtOffset(s.total)}`).join(', ')})`\n}\n","/**\r\n * scaleInsight.ts\r\n *\r\n * Interprets the Scale View: which of the 12 note intervals are enabled, what\r\n * well-known scale that shape spells, and what a given row means musically.\r\n */\r\n\r\n/** Interval row labels in screen order. */\r\nexport const SCALE_INTERVALS = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'] as const\r\n\r\nexport interface M8ScaleNote {\r\n /** Row label, e.g. 'D#'. */\r\n interval: string\r\n /** Semitones above the first row ('C'), 0-11. */\r\n semitone: number\r\n /** true = ON, false = out of the scale ('--' on the device, 'OFF' if shown), null = not read. */\r\n enabled: boolean | null\r\n /** Detune in semitones (-24..+24), null when unreadable. */\r\n offset: number | null\r\n}\r\n\r\nexport interface M8ScaleInfo {\r\n /** The 12 rows, in screen order. */\r\n notes: M8ScaleNote[]\r\n /** Labels of the enabled intervals, e.g. ['C', 'D', 'E', …]. */\r\n enabled: string[]\r\n /** Well-known scales this set of notes spells, most natural reading first. */\r\n shapes: string[]\r\n /** Intervals carrying a non-zero detune. */\r\n detuned: { interval: string; offset: number }[]\r\n /** One-line summary, e.g. \"C D E F G A B — Major (Ionian)\". */\r\n summary: string\r\n}\r\n\r\nconst SCALES: { name: string; steps: number[] }[] = [\r\n { name: 'Major (Ionian)', steps: [0, 2, 4, 5, 7, 9, 11] },\r\n { name: 'Natural minor (Aeolian)', steps: [0, 2, 3, 5, 7, 8, 10] },\r\n { name: 'Harmonic minor', steps: [0, 2, 3, 5, 7, 8, 11] },\r\n { name: 'Melodic minor', steps: [0, 2, 3, 5, 7, 9, 11] },\r\n { name: 'Dorian', steps: [0, 2, 3, 5, 7, 9, 10] },\r\n { name: 'Phrygian', steps: [0, 1, 3, 5, 7, 8, 10] },\r\n { name: 'Lydian', steps: [0, 2, 4, 6, 7, 9, 11] },\r\n { name: 'Mixolydian', steps: [0, 2, 4, 5, 7, 9, 10] },\r\n { name: 'Locrian', steps: [0, 1, 3, 5, 6, 8, 10] },\r\n { name: 'Phrygian dominant', steps: [0, 1, 4, 5, 7, 8, 10] },\r\n { name: 'Double harmonic', steps: [0, 1, 4, 5, 7, 8, 11] },\r\n { name: 'Major pentatonic', steps: [0, 2, 4, 7, 9] },\r\n { name: 'Minor pentatonic', steps: [0, 3, 5, 7, 10] },\r\n { name: 'Blues', steps: [0, 3, 5, 6, 7, 10] },\r\n { name: 'Whole tone', steps: [0, 2, 4, 6, 8, 10] },\r\n { name: 'Diminished (whole-half)', steps: [0, 2, 3, 5, 6, 8, 9, 11] },\r\n { name: 'Diminished (half-whole)', steps: [0, 1, 3, 4, 6, 7, 9, 10] },\r\n { name: 'Chromatic', steps: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] },\r\n]\r\n\r\nconst INTERVAL_NAMES = [\r\n 'the root',\r\n 'a minor 2nd',\r\n 'a major 2nd',\r\n 'a minor 3rd',\r\n 'a major 3rd',\r\n 'a perfect 4th',\r\n 'a tritone',\r\n 'a perfect 5th',\r\n 'a minor 6th',\r\n 'a major 6th',\r\n 'a minor 7th',\r\n 'a major 7th',\r\n]\r\n\r\n/** 'C-' → 'C', 'C#' → 'C#'. Returns null when the text is not an interval label. */\r\nexport function normalizeInterval(raw: string): string | null {\r\n const s = raw.replace(/[-\\s]/g, '').toUpperCase()\r\n return (SCALE_INTERVALS as readonly string[]).includes(s) ? s : null\r\n}\r\n\r\n/** \"a major 3rd (+4 semitones above C)\" for a given interval label. */\r\nexport function describeInterval(interval: string): string {\r\n const semitone = (SCALE_INTERVALS as readonly string[]).indexOf(interval)\r\n if (semitone <= 0) return semitone === 0 ? 'the first row (the scale root)' : interval\r\n return `${INTERVAL_NAMES[semitone]} (+${semitone} semitones above C)`\r\n}\r\n\r\n/** Names of well-known scales whose notes equal the enabled set, C-rooted reading first. */\r\nexport function matchScaleShapes(enabledSemitones: number[]): string[] {\r\n const set = new Set(enabledSemitones)\r\n if (set.size === 0) return []\r\n const hits: { root: number; name: string }[] = []\r\n for (const scale of SCALES) {\r\n if (scale.steps.length !== set.size) continue\r\n for (let root = 0; root < 12; root += 1) {\r\n if (scale.steps.every((s) => set.has((s + root) % 12))) hits.push({ root, name: scale.name })\r\n }\r\n }\r\n // Tonic on the first row ('C') is the natural reading; the rest are modes of the same notes.\r\n hits.sort((a, b) => a.root - b.root)\r\n return hits.map((h) => (h.root === 0 ? h.name : `${h.name} from ${SCALE_INTERVALS[h.root]}`))\r\n}\r\n\r\nexport function analyzeScale(notes: M8ScaleNote[]): M8ScaleInfo {\r\n const enabledNotes = notes.filter((n) => n.enabled === true)\r\n const enabled = enabledNotes.map((n) => n.interval)\r\n const shapes = matchScaleShapes(enabledNotes.map((n) => n.semitone))\r\n const detuned = notes\r\n .filter((n) => n.enabled === true && n.offset != null && n.offset !== 0)\r\n .map((n) => ({ interval: n.interval, offset: n.offset as number }))\r\n\r\n const summary = scaleSummaryParts({ enabled, shapes, detuned }).join(' — ')\r\n return { notes, enabled, shapes, detuned, summary }\r\n}\r\n\r\n/**\r\n * The two halves of the summary: the enabled notes (\"C D E F G A B\"), then the scale\r\n * they spell with any detune (\"Major (Ionian); detuned: E −0.50 st\"), when notes are enabled.\r\n */\r\nexport function scaleSummaryParts(info: Pick<M8ScaleInfo, 'enabled' | 'shapes' | 'detuned'>): string[] {\r\n const { enabled, shapes, detuned } = info\r\n if (!enabled.length) return ['no notes enabled']\r\n let shape = shapes[0] ?? `${enabled.length} notes, custom scale`\r\n if (detuned.length) shape += `; detuned: ${detuned.map((d) => `${d.interval} ${formatOffset(d.offset)}`).join(', ')}`\r\n return [enabled.join(' '), shape]\r\n}\r\n\r\n/** '+0.50 st' style detune text. */\r\nexport function formatOffset(offset: number): string {\r\n const sign = offset > 0 ? '+' : offset < 0 ? '−' : ''\r\n return `${sign}${Math.abs(offset).toFixed(2)} st`\r\n}\r\n","/**\n * viewContext.ts\n *\n * Derives semantic meaning from raw M8State by mapping cursor position and\n * currentLine content to typed, labelled fields using the m8-view-context.json schema.\n *\n * Every view listed in viewlist.json is mapped: the grid views (song, chain, phrase,\n * table, groove, scale, instrument pool) through their columns, the parameter views\n * (instrument, modulation, mixer, project, effects, EQ, scope, system settings, file\n * browsers) through the label/value layouts in m8-parameter-views.json.\n */\n\nimport type { M8Screen, M8State } from './types'\nimport schema from './m8-view-context.json'\nimport {\n describeOption,\n detectSection,\n detectSlot,\n getInstrumentType,\n getParameterLayout,\n instrumentTypeFromLines,\n parseValue,\n readParameterCells,\n type ParameterLayout,\n type ValueFormat,\n} from './parameterViews'\nimport {\n analyzeFxLane,\n analyzeRelativeFx,\n describeFxLane,\n describeFxValue,\n describeRelativeFx,\n describeRelativeReset,\n formatSignedFxValue,\n isRelativeFx,\n relativeResetBy,\n type M8FxLaneTrace,\n type M8RelativeFxState,\n} from './fxInsight'\nimport { lookupFxCommand, type M8FxCommandInfo } from './fxCommands'\nimport {\n analyzeScale,\n describeInterval,\n formatOffset,\n normalizeInterval,\n SCALE_INTERVALS,\n scaleSummaryParts,\n type M8ScaleInfo,\n type M8ScaleNote,\n} from './scaleInsight'\n\nexport type { M8FxLaneTrace, M8RelativeFxState } from './fxInsight'\nexport { getInstrumentFxCommands, lookupFxCommand, RELATIVE_FX_RULE } from './fxCommands'\nexport type { M8FxCommandInfo, M8InstrumentFxList } from './fxCommands'\nexport type { M8ScaleInfo, M8ScaleNote } from './scaleInsight'\n\n// Public field type union \n\nexport type M8FieldType =\n | 'chainRef'\n | 'phraseRef'\n | 'note'\n | 'velocity'\n | 'instrumentRef'\n | 'fxCommand'\n | 'fxValue'\n | 'transpose'\n | 'ticks'\n | 'volume'\n | 'ppq'\n | 'instrumentName'\n | 'eqSlot'\n | 'rowIndex'\n | 'noteInterval'\n | 'onOff'\n | 'semitoneOffset'\n | 'swing'\n /** Instrument engine of the TYPE field (WAVSYNTH, SAMPLER, …). */\n | 'instrumentType'\n /** Free-text name field (instrument, song, directory). */\n | 'name'\n /** Named, editable value (CUTOFF, TEMPO, GAIN, …). */\n | 'parameter'\n /** Choice from a list, e.g. '00CHORUS', 'BELL'. */\n | 'option'\n /** Button-like caption (LOAD, SAVE, RENDER, SETTINGS, …). */\n | 'action'\n /** Row of a file browser ('/folder', '/..', 'Bass.m8i'). */\n | 'fileEntry'\n\n/** What a file browser entry is: '/..', '/folder', 'Bass.m8i', 'kick.wav', 'song.m8s', anything else. */\nexport type M8FileKind = 'parent' | 'directory' | 'instrumentPreset' | 'sample' | 'm8file' | 'file'\n\n// Schema shapes (mirror m8-view-context.json / m8-parameter-views.json)\n\nexport interface ColumnDef {\n x: number\n width: number\n key: string\n type: M8FieldType\n label: string\n description?: string\n meta?: Record<string, unknown>\n /** How to read the text (parameter views); grid views use the type's built-in reading. */\n format?: ValueFormat\n /** Unit appended when showing the value (' BPM', ' dB', ' ms'). */\n unit?: string\n /** Names of the numbers of an 'XX:YY' pair (['delay', 'repeat']). */\n parts?: string[]\n /** Explanations of the options a choice cell can show, keyed by option name. */\n options?: Record<string, string>\n}\n\ninterface RowFieldDef {\n x: number\n width: number\n parse: 'hex' | 'decimal'\n type: M8FieldType\n key: string\n description?: string\n}\n\ninterface ViewDef {\n title: string\n type: 'grid' | 'parameter'\n description: string\n rowField?: RowFieldDef\n /** Column definitions for grid views. Parameter views have no columns. */\n columns?: ColumnDef[]\n}\n\n// Public result types \n\n/**\n * A single parsed field value extracted from the currentLine.\n */\nexport interface M8ParsedField {\n /** Column key as defined in the schema (e.g. 'fx1cmd', 'note', 'track3'). */\n key: string\n /** Human-readable label (e.g. 'FX1 Command', 'Note (N)'). */\n label: string\n /** Semantic type of this field. */\n type: M8FieldType\n /** Raw text extracted from currentLine (trimmed). */\n rawValue: string\n /**\n * Parsed integer (from hex) when the field holds a numeric value.\n * `null` for empty cells ('--', '---') and text-only types (note name, fxCommand).\n */\n hexValue: number | null\n /** True when the cell is empty/unused ('--', '---', '---00'). */\n isEmpty: boolean\n /** Extra view-specific metadata (e.g. { trackIndex: 2 } for song view track columns). */\n meta?: Record<string, unknown>\n /** What this field is, from the schema (parameter views). */\n description?: string\n /**\n * Parameter views: the non-numeric part of the value - option name ('CHORUS'),\n * button caption ('LOAD'), instrument type ('WAVSYNTH'), name text. null otherwise.\n */\n text?: string | null\n /** Parameter views: the value as a number in its natural base (hex, decimal or fractional). */\n numericValue?: number | null\n /** Parameter views: both numbers of an 'XX:YY' pair (e.g. delay time left/right). */\n values?: number[] | null\n /** Names of the numbers in `values`, e.g. ['left time', 'right time']. */\n parts?: string[]\n /** Unit of the value, e.g. ' BPM'. */\n unit?: string\n /** What the currently selected option does (choice cells that list their options). */\n valueDescription?: string\n /** File browser rows: what the entry is. */\n fileKind?: M8FileKind\n}\n\n/**\n * All fields parsed from the current row (line at cursorPos.y).\n */\nexport interface M8ParsedRow {\n /** Row index (hex-parsed) derived from the leftmost chars of currentLine. null if not parseable. */\n rowIndex: number | null\n /** Schema key for the row index field (e.g. 'songRow', 'step', 'chainPos'). */\n rowKey: string\n /** Parameter views: id of the schema row that matched this line ('tempo', 'gain', …); null for grid views. */\n rowId: string | null\n /** Map of column key → parsed field for every column in this view. */\n fields: Record<string, M8ParsedField>\n}\n\n/**\n * The field currently under the cursor, plus row context.\n */\nexport interface M8ActiveField extends M8ParsedField {\n /** Repetition of the view name for convenience. */\n viewName: string\n /** Row index of the line containing this field (null if not parseable). */\n rowIndex: number | null\n}\n\n/**\n * Full semantic context for the current M8State.\n */\nexport interface M8SemanticContext {\n /** M8 view name (lowercased), e.g. 'song', 'phrase'. */\n viewName: string\n /** Human-readable view title from schema ('Song View', 'Phrase View', …). */\n viewTitle: string | null\n /** View description from the schema. */\n viewDescription: string | null\n /**\n * The numeric ID of the current view item, parsed from the screen title.\n * e.g. 'F2' from ' CHAIN F2*', '03' from ' PHRASE 03'.\n * null for views that have no item number (song, instrument pool).\n */\n viewId: string | null\n /** True when the title carries a '*' suffix, indicating unsaved changes. */\n isUnsaved: boolean\n /** Whether this view is a structured grid (song/chain/phrase/table/groove). */\n isGridView: boolean\n /** Whether this is a label/value parameter view (instrument, mixer, project, effects, EQ, …). */\n isParameterView: boolean\n /** Effect Settings View: the block the cursor is in ('MODFX', 'DELAY', 'REVERB'). */\n section: string | null\n /**\n * Instrument View: the engine on the TYPE row as displayed ('WAVSYNTH'), and its\n * normalised key ('wavsynth', 'macrosynth', 'sampler', 'fmsynth', 'hypersynth',\n * 'external', 'midiout'). Needs the screen; null otherwise.\n */\n instrumentType: { text: string; key: string | null } | null\n /** All fields on the line at cursorPos.y (null for non-grid views or missing line). */\n row: M8ParsedRow | null\n /** The specific field under the cursor (null when cursor is out of any column range). */\n activeField: M8ActiveField | null\n /** Text of the cursor row with the playback marker removed (null when there is none). */\n lineText: string | null\n /**\n * Every step of the current phrase (index = step 0-F), so an FX cell can be read\n * against the cells above it. Only filled for the phrase view, and only when a\n * screen matching the cursor row was passed to getSemanticContext().\n */\n steps: M8ParsedRow[] | null\n /**\n * FX lane (1-3) that shows the '^^' marker: a REP from a previous phrase is still\n * running there. null when no marker is on screen or there is no screen.\n */\n carriedRepLane: number | null\n /** What the active FX cell means in the light of its lane (phrase view with `steps`). */\n fxLane: M8FxLaneTrace | null\n /**\n * Relative instrument commands (VOL, CUT…) in force after the active step, with their\n * net change since the last note trigger (an instrument number in the I column or a RET).\n * Phrase view with `steps` only; null otherwise.\n */\n relativeFx: Record<string, M8RelativeFxState> | null\n /**\n * When the cursor step triggers the instrument again (an instrument number in the I\n * column, or a RET), the relative commands in force just before it: the trigger puts\n * their parameters back. Entries can have a net change of 0. Phrase view with `steps`,\n * from step 1 on; null otherwise.\n */\n resetsRelative: Record<string, M8RelativeFxState> | null\n /** Scale View: the interval row under the cursor. Null on the other rows and views. */\n scaleNote: M8ScaleNote | null\n /** Scale View only, when the whole screen was passed: the 12 intervals interpreted together. */\n scale: M8ScaleInfo | null\n}\n\n/** What a part of the description says, so a consumer can keep, drop, reorder or style it. */\nexport type M8DescriptionPartKind =\n /** Label and value of the field under the cursor: \"FX1 Command: KIL\", \"Tempo (TEMPO): 120.00 BPM\". */\n | 'field'\n /** What the FX command does in general: \"(Kill: stops the playing instrument after XX ticks)\". */\n | 'fxInfo'\n /** On an FX value, the command it belongs to: \"for KIL (…)\". */\n | 'fxTarget'\n /** What the value does here: \"value 03: stops the note after 3 ticks\", \"E is detuned by −0.50 st\". */\n | 'meaning'\n /** The FX lane read down the phrase: a running REP, its RTO limit, REP00… */\n | 'fxLane'\n /** Relative commands: their net change since the last trigger, or what a trigger resets. */\n | 'relative'\n /** What the selected option does. */\n | 'optionInfo'\n /** What the field is. */\n | 'fieldInfo'\n /** File browser: what the entry is and what [EDIT] does. */\n | 'fileInfo'\n /** Instrument View: the engine (\"Wavsynth instrument\"). */\n | 'instrument'\n /** Effect Settings block or modulator slot (\"Delay\", \"MOD2\"). */\n | 'section'\n /** Scale View: the scale the 12 intervals spell. */\n | 'scale'\n /** Song View: the track of the cell. */\n | 'track'\n /** Where the cursor is: view, item number, row (\"Phrase View row 07\", \"Mixer View\"). */\n | 'location'\n /** Text of the cursor row, when no field on it is recognised. */\n | 'line'\n\n/** One piece of describeContext(); a kind can appear more than once. */\nexport interface M8DescriptionPart {\n kind: M8DescriptionPartKind\n text: string\n}\n\n// Internal helpers \n\nconst EMPTY_PATTERNS = new Set(['--', '---', '---00', '----', '-'])\n\nfunction isEmptyRaw(raw: string): boolean {\n if (!raw) return true\n // Also count strings that are all dashes\n return EMPTY_PATTERNS.has(raw) || /^-+$/.test(raw)\n}\n\nfunction parseHex(raw: string): number | null {\n const trimmed = raw.trim()\n if (!trimmed || isEmptyRaw(trimmed)) return null\n const n = parseInt(trimmed, 16)\n return Number.isNaN(n) ? null : n\n}\n\nfunction extractFromLine(line: string, x: number, width: number): string {\n // Guard: x may exceed line length (line might be shorter than full 40-char row)\n if (x >= line.length) return ''\n return line.slice(x, Math.min(x + width, line.length))\n}\n\n/** The item number of a screen title and the marker after it ('PHRASE 1F*', 'PHRASE 27^'). */\nexport function parseViewTitle(rawTitle: string | null): { viewId: string | null; isUnsaved: boolean; marker: '*' | '^' | null } {\n return extractViewId(rawTitle)\n}\n\n/**\n * Extracts the item ID and the marker after it from a raw M8 screen title.\n * Titles follow the pattern ' VIEWNAME ID* ' where ID is 1–2 hex chars, followed by\n * an optional '*' (used elsewhere / unsaved) or '^' (seen on phrases on a Model:02,\n * firmware 6.5). Returns { viewId: null, ... } for views without an item number.\n */\nfunction extractViewId(rawTitle: string | null): { viewId: string | null; isUnsaved: boolean; marker: '*' | '^' | null } {\n if (!rawTitle) return { viewId: null, isUnsaved: false, marker: null }\n // The M8 font draws a slashed zero as '{' / '}' in some titles (e.g. 'PHRASE {2').\n const trimmed = rawTitle.replace(/[{}]/g, '0').trim()\n const markerMatch = /\\s*([*^])$/.exec(trimmed)\n const marker = (markerMatch?.[1] as '*' | '^' | undefined) ?? null\n const withoutMarker = markerMatch ? trimmed.slice(0, markerMatch.index) : trimmed\n const parts = withoutMarker.split(/\\s+/)\n const last = parts[parts.length - 1] ?? ''\n // Accept 1–2 uppercase hex digits as a view ID\n const viewId = /^[0-9A-Fa-f]{1,2}$/.test(last) ? last.toUpperCase() : null\n return { viewId, isUnsaved: marker === '*', marker }\n}\n\n/**\n * When a row is currently playing, the M8 prepends a single non-alphanumeric\n * indicator character (e.g. '<' or '>') to currentLine, shifting all data\n * positions right by 1. Strip it so column offsets remain consistent.\n * Returns the clean line and the number of stripped characters (0 or 1).\n */\nfunction stripPlaybackIndicator(line: string): { line: string; offset: number } {\n if (line.length > 0 && /^[^a-zA-Z0-9 ]/.test(line[0])) {\n return { line: line.slice(1), offset: 1 }\n }\n return { line, offset: 0 }\n}\n\nfunction parseColumn(col: ColumnDef, line: string): M8ParsedField {\n let raw = extractFromLine(line, col.x, col.width).trim()\n // The '^^' marker (a REP still running from a previous phrase) is not cell content.\n if (raw.includes('^') && (col.type === 'fxCommand' || col.type === 'fxValue')) raw = ''\n const empty = isEmptyRaw(raw)\n\n // Parameter views say how to read each cell; grid views use the type's built-in reading below.\n if (col.format) {\n const v = empty ? null : parseValue(raw, col.format)\n // Option names and captions come from a fixed vocabulary and are shown upper case;\n // names the user typed and file names keep the case the device sent.\n const keepCase = col.type === 'name' || col.type === 'fileEntry'\n const text = v?.text != null && !keepCase ? v.text.toUpperCase() : (v?.text ?? null)\n return {\n key: col.key,\n label: col.label,\n type: col.type,\n rawValue: raw,\n hexValue: v?.hexValue ?? null,\n isEmpty: empty,\n meta: col.meta,\n description: col.description,\n text,\n numericValue: v?.numericValue ?? null,\n values: v?.values ?? null,\n parts: col.parts,\n unit: col.unit,\n valueDescription: (!empty && describeOption(col.options, text ?? raw)) || undefined,\n fileKind: col.type === 'fileEntry' ? fileKindOf(raw) : undefined,\n }\n }\n\n // Numeric types we try to parse as hex\n const numericTypes: M8FieldType[] = [\n 'chainRef', 'phraseRef', 'velocity', 'instrumentRef',\n 'fxValue', 'transpose', 'ticks', 'volume', 'ppq', 'eqSlot', 'rowIndex',\n ]\n // Types that are decimal (not hex)\n const decimalTypes: M8FieldType[] = ['ppq']\n let hexValue: number | null = null\n if (!empty && numericTypes.includes(col.type)) {\n if (decimalTypes.includes(col.type)) {\n const n = parseInt(raw, 10)\n hexValue = Number.isNaN(n) ? null : n\n } else {\n hexValue = parseHex(raw)\n }\n }\n\n return {\n key: col.key,\n label: col.label,\n type: col.type,\n rawValue: raw,\n hexValue,\n isEmpty: empty,\n meta: col.meta,\n }\n}\n\nfunction fileKindOf(name: string): M8FileKind {\n if (name === '/..') return 'parent'\n if (name.startsWith('/')) return 'directory'\n const ext = /\\.([a-z0-9]+)$/i.exec(name)?.[1]?.toLowerCase()\n if (ext === 'm8i') return 'instrumentPreset'\n if (ext === 'wav') return 'sample'\n return ext?.startsWith('m8') ? 'm8file' : 'file'\n}\n\nfunction parseRowIndex(line: string, rf: RowFieldDef): number | null {\n const raw = extractFromLine(line, rf.x, rf.width).trim()\n if (!raw) return null\n if (rf.parse === 'hex') return parseHex(raw)\n const n = parseInt(raw, 10)\n return Number.isNaN(n) ? null : n\n}\n\n/**\n * Find the column whose x range contains cursorX.\n * Falls back to the closest column within 1-character tolerance.\n */\nfunction findColumn(cols: ColumnDef[], cursorX: number): ColumnDef | null {\n // Exact range hit\n for (const col of cols) {\n if (cursorX >= col.x && cursorX < col.x + col.width) return col\n }\n // 1-char tolerance (cursor border may be 1 px off)\n let best: ColumnDef | null = null\n let bestDist = 2 // reject anything > 1 char away\n for (const col of cols) {\n const dist = Math.min(\n Math.abs(cursorX - col.x),\n Math.abs(cursorX - (col.x + col.width - 1)),\n )\n if (dist < bestDist) {\n bestDist = dist\n best = col\n }\n }\n return best\n}\n\nfunction parseRow(viewDef: ViewDef, line: string): M8ParsedRow {\n const fields: Record<string, M8ParsedField> = {}\n for (const col of viewDef.columns ?? []) {\n fields[col.key] = parseColumn(col, line)\n }\n // '---00': an FX lane without a command shows a filler value that means nothing.\n for (const key of Object.keys(fields)) {\n const lane = /^fx(\\d)cmd$/.exec(key)?.[1]\n const value = lane ? fields[`fx${lane}val`] : undefined\n if (value && fields[key].isEmpty) fields[`fx${lane}val`] = { ...value, isEmpty: true }\n }\n return {\n rowIndex: viewDef.rowField ? parseRowIndex(line, viewDef.rowField) : null,\n rowKey: viewDef.rowField?.key ?? 'row',\n rowId: null,\n fields,\n }\n}\n\nconst squash = (s: string): string => s.replace(/\\s+/g, '')\n\n/**\n * Every screen row has a one-column left margin (the playback marker '<' / '>' is drawn\n * in it), so the schema's x positions are counted from column 1, and `cursorPos.x` is\n * one more than the schema x of the cell it covers. `currentLine` is trimmed and so\n * already starts at the schema's column 0.\n */\nexport const SCREEN_MARGIN = 1\n\n/** A screen row with the margin removed: columns then match the schema's x positions. */\nconst screenDataLine = (raw: string): string => raw.slice(SCREEN_MARGIN)\n\n/**\n * The screen can lag behind the state (they arrive through separate messages), so\n * only trust it when its row under the cursor is the very line we were given.\n * `currentLine` is trimmed while screen lines keep their columns, hence squash().\n */\nfunction screenMatchesCursor(state: M8State, screen: M8Screen | null | undefined): screen is M8Screen {\n if (!screen || !state.cursorPos || !state.currentLine) return false\n const onScreen = screen.lines[state.cursorPos.y]\n return onScreen != null && squash(onScreen) === squash(state.currentLine)\n}\n\nconst PHRASE_STEPS = 16\n\n/** Screen rows that can hold grid content (titles are on row 3, content starts on row 5). */\nconst GRID_FIRST_ROW = 4\nconst GRID_LAST_ROW = 21\n\n/** A grid row read off the whole screen, with the screen row it was found on. */\nexport interface M8ScreenGridRow extends M8ParsedRow {\n screenRow: number\n}\n\n/**\n * Every content row of a grid view (song, chain, phrase, table, groove, instrument pool)\n * read off the whole screen, top to bottom. Rows whose index column does not parse are\n * skipped, so check `rowIndex` continuity when you need a full page.\n *\n * @example\n * ```ts\n * const rows = parseGridScreen('phrase', await m8.getScreen())\n * rows[0].fields.note.rawValue // 'C-4'\n * ```\n */\nexport function parseGridScreen(viewName: string, screen: M8Screen): M8ScreenGridRow[] {\n const viewDef = (schema.views as Record<string, ViewDef>)[viewName]\n if (viewDef?.type !== 'grid' || !viewDef.rowField) return []\n const rows: M8ScreenGridRow[] = []\n const last = Math.min(GRID_LAST_ROW, screen.lines.length - 1)\n for (let y = GRID_FIRST_ROW; y <= last; y += 1) {\n const line = screenDataLine(screen.lines[y] ?? '')\n const rf = viewDef.rowField\n // The index must stand alone: a label such as 'FE' in a longer word is not a row. While\n // the song plays, a play marker ('>') can take the blank after it (Song view).\n if (line.length <= rf.x + rf.width || !isSeparator(line[rf.x + rf.width])) continue\n const row = parseRow(viewDef, line)\n if (row.rowIndex == null) continue\n rows.push({ ...row, screenRow: y })\n }\n return rows\n}\n\nconst isSeparator = (ch: string): boolean => ch === ' ' || /^[^a-zA-Z0-9-]$/.test(ch)\n\n/** Where the song is playing, as far as the screen shows it. */\nexport interface M8PlayPositions {\n /** Song view: the song row each track's play marker is on (null when its marker is not on screen). */\n tracks: (number | null)[] | null\n /** Chain, phrase, table, groove views: the row with the play marker after its index. */\n row: number | null\n}\n\n/**\n * Reads the play markers off a grid view. On the Song view the M8 draws a marker in the\n * blank just left of each playing chain (`00>00>01>02`); on the other grid views in the\n * blank after the row index of the playing row (`F>--- -- --`), both seen on a Model:02,\n * firmware 6.5. The '<' drawn in the margin marks the cursor row, not playback. Returns\n * null for views without a grid; all nulls when nothing is playing.\n */\nexport function readPlayPositions(viewName: string, screen: M8Screen): M8PlayPositions | null {\n const viewDef = (schema.views as Record<string, ViewDef>)[viewName]\n if (viewDef?.type !== 'grid') return null\n const rows = parseGridScreen(viewName, screen)\n const isMarker = (ch: string | undefined) => !!ch && ch !== ' ' && isSeparator(ch)\n if (viewName === 'song') {\n const cells = (viewDef.columns ?? []).filter((c) => c.type === 'chainRef')\n const tracks = cells.map((cell) => {\n const hit = rows.find((r) => isMarker(screenDataLine(screen.lines[r.screenRow] ?? '')[cell.x - 1]))\n return hit?.rowIndex ?? null\n })\n return { tracks, row: null }\n }\n const rf = viewDef.rowField\n if (!rf) return { tracks: null, row: null }\n const hit = rows.find((r) => isMarker(screenDataLine(screen.lines[r.screenRow] ?? '')[rf.x + rf.width]))\n return { tracks: null, row: hit?.rowIndex ?? null }\n}\n\n/**\n * All 16 steps of the phrase on screen, plus the FX lane showing the '^^' marker\n * (a REP carried over from a previous phrase). Null when any step does not line up.\n */\nfunction readPhraseSteps(\n viewDef: ViewDef,\n state: M8State,\n screen: M8Screen,\n cursorStep: number,\n): { steps: M8ParsedRow[]; carriedRepLane: number | null } | null {\n const y0 = (state.cursorPos?.y ?? -1) - cursorStep\n if (y0 < 0) return null\n\n const steps: M8ParsedRow[] = []\n let carriedRepLane: number | null = null\n for (let i = 0; i < PHRASE_STEPS; i += 1) {\n const raw = screen.lines[y0 + i]\n if (raw == null) return null\n const line = screenDataLine(raw)\n const row = parseRow(viewDef, line)\n if (row.rowIndex !== i) return null\n steps.push(row)\n\n // '^^' sits next to the FX lane a previous phrase's REP is still driving.\n const caret = line.indexOf('^^')\n if (carriedRepLane == null && caret >= 0) carriedRepLane = nearestFxLane(viewDef, caret)\n }\n return { steps, carriedRepLane }\n}\n\n/**\n * FX lane (1-3) that text column `x` belongs to: inside a lane's cells, or at most one\n * column outside exactly one lane. Null when it is out of reach or ambiguous - the\n * manual doesn't say exactly where the '^^' is drawn, so don't guess.\n */\nfunction nearestFxLane(viewDef: ViewDef, x: number): number | null {\n const dists: { lane: number; dist: number }[] = []\n for (let lane = 1; lane <= 3; lane += 1) {\n const cmd = viewDef.columns?.find((c) => c.key === `fx${lane}cmd`)\n const val = viewDef.columns?.find((c) => c.key === `fx${lane}val`)\n if (!cmd || !val) continue\n const to = val.x + val.width - 1\n dists.push({ lane, dist: x < cmd.x ? cmd.x - x : x > to ? x - to : 0 })\n }\n const min = Math.min(...dists.map((d) => d.dist))\n const closest = dists.filter((d) => d.dist === min)\n return min <= 1 && closest.length === 1 ? closest[0].lane : null\n}\n\n/** A Scale View row as an interval; null when its first column is not an interval label. */\nfunction parseScaleNote(row: M8ParsedRow): M8ScaleNote | null {\n const interval = normalizeInterval(row.fields.interval?.rawValue ?? '')\n if (!interval) return null\n const en = row.fields.en?.rawValue.toUpperCase()\n const offset = parseFloat(row.fields.offset?.rawValue ?? '')\n return {\n interval,\n semitone: (SCALE_INTERVALS as readonly string[]).indexOf(interval),\n // The device (fw 6.5) draws a note out of the scale as '-- -- --'.\n enabled: en === 'ON' ? true : en === 'OFF' || en === '--' ? false : null,\n offset: Number.isNaN(offset) ? null : offset,\n }\n}\n\n/** Reads the 12 interval rows of the Scale View off the screen. Null unless all 12 are found. */\nfunction readScaleNotes(viewDef: ViewDef, screen: M8Screen): M8ScaleNote[] | null {\n const found = new Map<string, M8ScaleNote>()\n for (const raw of screen.lines) {\n const row = parseRow(viewDef, screenDataLine(raw))\n const note = parseScaleNote(row)\n // An interval row reads ON, OFF or '--' in its EN column.\n const en = row.fields.en?.rawValue.toUpperCase()\n if (!note || (en !== 'ON' && en !== 'OFF' && en !== '--')) continue\n if (!found.has(note.interval)) found.set(note.interval, note)\n }\n if (found.size !== SCALE_INTERVALS.length) return null\n return SCALE_INTERVALS.map((i) => found.get(i) as M8ScaleNote)\n}\n\n/**\n * Context of a label/value parameter view. The line is read from the screen when a\n * matching one is available (it keeps leading blanks, which the trimmed `currentLine`\n * loses), and the section / instrument type are found by looking at the lines around it.\n */\nfunction getParameterContext(\n state: M8State,\n screen: M8Screen | null | undefined,\n layout: ParameterLayout,\n head: Pick<M8SemanticContext, 'viewName' | 'viewTitle' | 'viewDescription' | 'viewId' | 'isUnsaved'>,\n): M8SemanticContext {\n const empty: M8SemanticContext = {\n ...head,\n isGridView: false,\n isParameterView: true,\n section: null,\n instrumentType: null,\n row: null,\n activeField: null,\n lineText: null,\n steps: null,\n carriedRepLane: null,\n fxLane: null,\n relativeFx: null,\n resetsRelative: null,\n scaleNote: null,\n scale: null,\n }\n\n const cursor = state.cursorPos\n const cursorX = cursor ? cursor.x - SCREEN_MARGIN : 0\n const trusted = cursor && screenMatchesCursor(state, screen) ? screen : null\n let line = trusted && cursor ? screenDataLine(trusted.lines[cursor.y] ?? '') : (state.currentLine ?? '')\n if (!line.trim()) return empty\n\n const linesUpward = trusted && cursor ? trusted.lines.slice(0, cursor.y + 1).reverse().map(screenDataLine) : [line]\n const instrumentType =\n head.viewName === 'inst' ? instrumentTypeFromLines(trusted ? trusted.lines.map(screenDataLine) : [line]) : null\n const opts = { instrumentKey: instrumentType?.key ?? null, entryText: state.textUnderCursor }\n\n // `currentLine` is trimmed, so a row that starts with blanks lost its column offsets.\n // Without the screen, put blanks back until the cursor lands inside a recognised cell.\n if (!trusted && cursor && layout.kind == null) {\n for (let pad = 0; pad <= Math.min(cursorX, 24); pad += 1) {\n const padded = ' '.repeat(pad) + line\n const found = readParameterCells(layout, padded, { ...opts, section: detectSection(layout, [padded]) })\n if (found.cells.some((c) => cursorX >= c.x && cursorX < c.x + c.width)) {\n line = padded\n break\n }\n }\n }\n\n let section = detectSection(layout, trusted ? linesUpward : [line])\n const { rowId, cells } = readParameterCells(layout, line, { ...opts, section })\n const fields: Record<string, M8ParsedField> = {}\n for (const cell of cells) fields[cell.key] = parseColumn(cell, line)\n const row: M8ParsedRow = { rowIndex: null, rowKey: 'param', rowId, fields }\n\n let activeField: M8ActiveField | null = null\n const cell = cursor ? findColumn(cells, cursorX) : null\n if (cell) activeField = { ...fields[cell.key], viewName: head.viewName, rowIndex: null }\n\n // Modulation view: which of MOD1-MOD4 the cell belongs to (the heading of its block).\n const zoneId = cell?.meta?.zone\n if (trusted && typeof zoneId === 'string') section = detectSlot(layout, zoneId, linesUpward) ?? section\n\n return { ...empty, section, instrumentType, row, activeField, lineText: line.trim() }\n}\n\n// Public API\n\n/**\n * Derives a full semantic context from the current M8State.\n *\n * Pass the whole `screen` (see M8Client.getScreen) to let the context look beyond the\n * cursor row: the other steps of a phrase (so an FX cell can be read against the\n * commands above it) and the other rows of the Scale View. Without a screen, or with\n * one that no longer matches the cursor row, only the cursor row is interpreted.\n *\n * @example\n * ```ts\n * const ctx = getSemanticContext(state)\n * if (ctx?.activeField?.type === 'chainRef') {\n * const track = ctx.activeField.meta?.trackIndex // 1–8\n * const chainNum = ctx.activeField.hexValue // 0–255 or null if empty\n * const songRow = ctx.row?.rowIndex\n * }\n * ```\n */\nexport function getSemanticContext(state: M8State, screen?: M8Screen | null): M8SemanticContext | null {\n const viewName = state.viewName\n if (!viewName) return null\n\n const viewDefs = schema.views as Record<string, ViewDef>\n const viewDef = viewDefs[viewName] ?? null\n\n const isGridView = viewDef?.type === 'grid'\n const viewTitle = viewDef?.title ?? null\n const viewDescription = viewDef?.description ?? null\n const { viewId, isUnsaved } = extractViewId(state.viewTitle)\n const head = { viewName, viewTitle, viewDescription, viewId, isUnsaved }\n const base = { ...head, isParameterView: false, section: null, instrumentType: null, lineText: null, steps: null, carriedRepLane: null, fxLane: null, relativeFx: null, resetsRelative: null, scaleNote: null, scale: null }\n\n const paramLayout = viewDef?.type === 'parameter' ? getParameterLayout(viewName) : null\n if (paramLayout) return getParameterContext(state, screen, paramLayout, head)\n\n if (!isGridView || !viewDef) {\n return { ...base, isGridView: false, row: null, activeField: null }\n }\n\n const rawLine = state.currentLine\n if (!rawLine) {\n return { ...base, isGridView: true, row: null, activeField: null }\n }\n\n // Strip playback indicator prefix ('>' or '<') emitted when the row is playing.\n const { line } = stripPlaybackIndicator(rawLine)\n const cursorX = state.cursorPos ? state.cursorPos.x - SCREEN_MARGIN : 0\n\n const row = parseRow(viewDef, line)\n\n // The rows under the 12 scale intervals (KEY, TUNE, NAME, LOAD/SAVE) are label/value rows.\n const scaleLayout = viewName === 'scale' && !normalizeInterval(row.fields.interval?.rawValue ?? '') ? getParameterLayout(viewName) : null\n if (scaleLayout) return getParameterContext(state, screen, scaleLayout, head)\n\n // Determine active field from cursor X position\n let activeField: M8ActiveField | null = null\n if (state.cursorPos) {\n const col = findColumn(viewDef.columns ?? [], cursorX)\n if (col) {\n const field = row.fields[col.key]\n if (field) {\n activeField = { ...field, viewName, rowIndex: row.rowIndex }\n }\n }\n }\n\n // Look beyond the cursor row when a screen that still matches it was supplied.\n let steps: M8ParsedRow[] | null = null\n let carriedRepLane: number | null = null\n let fxLane: M8FxLaneTrace | null = null\n let relativeFx: Record<string, M8RelativeFxState> | null = null\n let resetsRelative: Record<string, M8RelativeFxState> | null = null\n let scale: M8ScaleInfo | null = null\n if (screenMatchesCursor(state, screen)) {\n if (viewName === 'phrase' && row.rowIndex != null) {\n const phrase = readPhraseSteps(viewDef, state, screen, row.rowIndex)\n if (phrase) {\n steps = phrase.steps\n carriedRepLane = phrase.carriedRepLane\n const lane = activeField ? /^fx(\\d)(cmd|val)$/.exec(activeField.key)?.[1] : undefined\n if (lane) fxLane = analyzeFxLane(steps, Number(lane), row.rowIndex, carriedRepLane === Number(lane))\n relativeFx = analyzeRelativeFx(steps, row.rowIndex, carriedRepLane)\n if (row.rowIndex > 0 && relativeResetBy(row)) resetsRelative = analyzeRelativeFx(steps, row.rowIndex - 1, carriedRepLane)\n }\n } else if (viewName === 'scale') {\n const notes = readScaleNotes(viewDef, screen)\n if (notes) scale = analyzeScale(notes)\n }\n }\n const scaleNote = viewName === 'scale' ? parseScaleNote(row) : null\n\n return {\n ...base,\n isGridView: true,\n row,\n activeField,\n lineText: line.trim() || null,\n steps,\n carriedRepLane,\n fxLane,\n relativeFx,\n resetsRelative,\n scaleNote,\n scale,\n }\n}\n\n/**\n * What the cursor is on, as ordered, tagged parts. Every part is built from\n * `ctx` alone, so a consumer can keep, drop, reorder or restyle them, or ignore them\n * and write its own text from the context.\n *\n * @example\n * ```ts\n * describeContextParts(ctx)\n * // [{ kind: 'field', text: 'FX2 Command: KIL' },\n * // { kind: 'fxInfo', text: '(Kill: stops the playing instrument after XX ticks)' },\n * // { kind: 'meaning', text: 'value 03: stops the note after 3 ticks' },\n * // { kind: 'location', text: 'Phrase View row 01' }]\n * ```\n */\nexport function describeContextParts(ctx: M8SemanticContext): M8DescriptionPart[] {\n if (ctx.isParameterView) return describeParameterContext(ctx)\n if (ctx.viewName === 'scale') {\n const scaleParts = describeScaleContext(ctx)\n if (scaleParts) return scaleParts\n }\n if (!ctx.activeField) return partsOf(['location', ctx.viewTitle ?? ctx.viewName])\n\n const { activeField: f, row } = ctx\n const parts = partsOf(['field', `${f.label}: ${fieldValueText(f, row)}`])\n\n // What the FX does here, read against its surroundings (value meaning, running REP…)\n const isFx = f.type === 'fxCommand' || f.type === 'fxValue'\n const meaning = isFx && row ? describeFxCell(ctx, f, row) : [...describeInstrumentCell(ctx, f), ...describeTableVolume(ctx, f)]\n // The generic REP/RTO blurb only repeats what the specific explanation already says.\n const explained = meaning.length > 0\n\n // FX command extra info\n if (f.type === 'fxCommand' && !f.isEmpty) {\n const info = explained && /^(REP|RTO)$/i.test(f.rawValue) ? undefined : lookupFxCommand(f.rawValue)\n if (info) parts.push({ kind: 'fxInfo', text: `(${fxInfoText(info)})` })\n }\n\n // FX value context: include sibling command if we're on a value column\n if (f.type === 'fxValue' && row) {\n const cmdKey = f.key.replace('val', 'cmd') // fx1val → fx1cmd, fx2val → fx2cmd …\n const cmdField = row.fields[cmdKey]\n if (cmdField && !cmdField.isEmpty) {\n const info = explained && /^(REP|RTO)$/i.test(cmdField.rawValue) ? undefined : lookupFxCommand(cmdField.rawValue)\n const label = info ? `${cmdField.rawValue} (${fxInfoText(info)})` : cmdField.rawValue\n parts.push({ kind: 'fxTarget', text: `for ${label}` })\n }\n }\n\n parts.push(...meaning)\n\n // Track index for song view\n if (f.meta?.trackIndex != null) parts.push({ kind: 'track', text: `Track ${f.meta.trackIndex}` })\n\n // Row index\n if (row?.rowIndex != null) {\n const rowHex = row.rowIndex.toString(16).toUpperCase().padStart(2, '0')\n parts.push({ kind: 'location', text: `${ctx.viewTitle ?? ctx.viewName} row ${rowHex}` })\n }\n\n return parts\n}\n\n/** The field's value; the value of a relative FX command reads as the signed number it is ('F0h (dec −16)'). */\nfunction fieldValueText(f: M8ActiveField, row: M8ParsedRow | null | undefined): string {\n if (f.type === 'fxValue' && !f.isEmpty && f.hexValue != null && row) {\n const cmd = row.fields[f.key.replace('val', 'cmd')]\n if (cmd && !cmd.isEmpty && isRelativeFx(cmd.rawValue)) return `${f.rawValue.toUpperCase()}h (dec ${formatSignedFxValue(f.hexValue)})`\n }\n return formatFieldValue(f)\n}\n\n/**\n * Returns a short human-readable description of what the cursor is currently on:\n * the text of describeContextParts(), joined with ' — '.\n *\n * @example\n * \"Track 3: 0A — Track 3 — Song View row 01\"\n * \"Note (N): Note C-4 — Phrase View row 00\"\n * \"FX2 Command: KIL — (Kill: stops the playing instrument after XX ticks) — value 03: stops the note after 3 ticks — Phrase View row 01\"\n */\nexport function describeContext(ctx: M8SemanticContext): string {\n return describeContextParts(ctx)\n .map((p) => p.text)\n .join(' — ')\n}\n\n/** Parts in the order given, leaving out those with no text. */\nfunction partsOf(...entries: [M8DescriptionPartKind, string | null | undefined][]): M8DescriptionPart[] {\n return entries.flatMap(([kind, text]) => (text ? [{ kind, text }] : []))\n}\n\n/**\n * Meaning of the FX cell under the cursor. Uses the lane trace (REP/RTO, empty cells\n * under a running REP) when the phrase was read off the screen, else explains the\n * value of the command in this row.\n */\nfunction describeFxCell(ctx: M8SemanticContext, f: M8ActiveField, row: M8ParsedRow): M8DescriptionPart[] {\n const lane = /^fx(\\d)/.exec(f.key)?.[1]\n if (!lane) return []\n const cmd = row.fields[`fx${lane}cmd`]\n const val = row.fields[`fx${lane}val`]\n\n // Empty cell: only interesting when a REP is still running through it.\n if (!cmd || cmd.isEmpty) {\n if (ctx.fxLane?.event !== 'continue') return []\n const repeated = ctx.fxLane.source?.cmd\n const relative = repeated && isRelativeFx(repeated) ? describeRelativeFx(ctx.relativeFx?.[repeated]) : null\n return partsOf(['fxLane', describeFxLane(ctx.fxLane)], ['relative', relative])\n }\n\n const name = cmd.rawValue.toUpperCase()\n const laneText = ctx.fxLane && ctx.fxLane.event !== 'none' && ctx.fxLane.event !== 'set' ? describeFxLane(ctx.fxLane) : null\n // In a phrase REP / RTO are explained entirely by the lane; other commands, and REP / RTO\n // in a table (a repeat count there), add their value meaning.\n if ((name === 'REP' || name === 'RTO') && ctx.viewName !== 'table') {\n const repeated = ctx.fxLane?.source?.cmd\n const relative = name === 'REP' && ctx.fxLane?.active && repeated && isRelativeFx(repeated) ? describeRelativeFx(ctx.relativeFx?.[repeated]) : null\n return partsOf(['fxLane', laneText], ['relative', relative])\n }\n if (!val || val.isEmpty || val.hexValue == null) return partsOf(['fxLane', laneText])\n\n // A relative value's meaning is its signed reading already.\n const shown = f.type === 'fxCommand' ? `value ${val.rawValue.toUpperCase()}: ` : ''\n const described = describeFxValue(name, val.hexValue, {\n note: row.fields.note?.isEmpty ? null : row.fields.note?.rawValue,\n inTable: ctx.viewName === 'table',\n row: row.rowIndex,\n })\n // Relative commands stay applied until a note with an instrument number or a RET resets them.\n let relative: string | null = null\n if (isRelativeFx(name)) relative = describeRelativeFx(ctx.relativeFx?.[name])\n else if (name === 'RET' && ctx.resetsRelative) relative = describeRelativeReset(ctx.resetsRelative)\n return partsOf(['meaning', described ? `${shown}${described}` : null], ['fxLane', laneText], ['relative', relative])\n}\n\n/** Command description, plus the instrument types that offer it when not all do. */\nfunction fxInfoText(info: M8FxCommandInfo): string {\n const text = info.description.replace(/\\.$/, '')\n if (!info.instrumentTypes) return text\n return `${text}; offered by ${info.instrumentTypes.map((t) => INSTRUMENT_TYPE_LABELS[t] ?? t).join(', ')} instruments`\n}\n\nconst INSTRUMENT_TYPE_LABELS: Record<string, string> = {\n sampler: 'Sampler',\n macrosynth: 'Macrosynth',\n fmsynth: 'FM Synth',\n hypersynth: 'Hypersynth',\n wavsynth: 'Wavsynth',\n external: 'External',\n midiout: 'MIDI Out',\n}\n\n/**\n * Instrument number typed on a step of a phrase: it retriggers the instrument, which\n * wipes the relative FX changes that were still in force.\n */\nfunction describeInstrumentCell(ctx: M8SemanticContext, f: M8ActiveField): M8DescriptionPart[] {\n if (f.type !== 'instrumentRef' || f.isEmpty || !ctx.resetsRelative) return []\n return partsOf(['relative', describeRelativeReset(ctx.resetsRelative)])\n}\n\n/** Table V column: a share of the velocity of the phrase step that plays the table (7F = all of it). */\nfunction describeTableVolume(ctx: M8SemanticContext, f: M8ActiveField): M8DescriptionPart[] {\n if (ctx.viewName !== 'table' || f.key !== 'volume' || f.isEmpty || f.hexValue == null || f.hexValue > 0x7f) return []\n return partsOf(['meaning', `plays at ${Math.round((f.hexValue / 0x7f) * 100)}% of the step's velocity`])\n}\n\nconst SECTION_NAMES: Record<string, string> = { MODFX: 'ModFX', DELAY: 'Delay', REVERB: 'Reverb' }\n\nconst FILE_KIND_TEXT: Record<M8FileKind, string> = {\n parent: 'goes up to the parent directory',\n directory: 'directory: [EDIT] opens it',\n instrumentPreset: 'instrument preset: [EDIT] selects it',\n sample: 'sample: [EDIT] selects it',\n m8file: 'M8 file: [EDIT] selects it',\n file: 'file: [EDIT] selects it',\n}\n\n/**\n * Parameter views: \"<label>: <value> — <what the option does> — <what the field is>\n * — <where>\". The instrument type and effect section are added when they change what\n * the field means.\n */\nfunction describeParameterContext(ctx: M8SemanticContext): M8DescriptionPart[] {\n const title = `${ctx.viewTitle ?? ctx.viewName}${ctx.viewId ? ` ${ctx.viewId}` : ''}`\n const f = ctx.activeField\n if (!f) return partsOf(['location', title], ['line', ctx.lineText])\n\n const type = ctx.instrumentType?.key ? getInstrumentType(ctx.instrumentType.key) : null\n return partsOf(\n ['field', `${f.label}: ${formatFieldValue(f)}`],\n ['optionInfo', f.valueDescription],\n ['fieldInfo', f.description],\n ['fileInfo', f.fileKind && FILE_KIND_TEXT[f.fileKind]],\n ['instrument', ctx.instrumentType && `${type?.name ?? ctx.instrumentType.text} instrument`],\n ['section', ctx.section && (SECTION_NAMES[ctx.section] ?? ctx.section)],\n ['location', title],\n )\n}\n\n/**\n * Scale View interval rows: what the row means and, when the whole screen was\n * available, which scale the 12 intervals spell together. Null → fall back to generic.\n * (KEY / TUNE / NAME / LOAD / SAVE rows are parameter rows.)\n */\nfunction describeScaleContext(ctx: M8SemanticContext): M8DescriptionPart[] | null {\n const title = `${ctx.viewTitle ?? ctx.viewName}${ctx.viewId ? ` ${ctx.viewId}` : ''}`\n const f = ctx.activeField\n const note = ctx.scaleNote\n if (!f || !note) return ctx.lineText ? partsOf(['location', title], ['line', ctx.lineText]) : null\n\n const { interval, enabled, offset } = note\n let parts: M8DescriptionPart[]\n if (f.key === 'offset') {\n const detune = offset == null ? `detune of ${interval}` : offset === 0 ? `${interval} is not detuned` : `${interval} is detuned by ${formatOffset(offset)}`\n parts = partsOf(['field', `${f.label}: ${f.rawValue || '(empty)'}`], ['meaning', detune], ['meaning', enabled === false ? '(note is OFF)' : null])\n } else {\n const state = enabled === true ? 'is in' : enabled === false ? 'is not in' : 'is not yet set in'\n parts = partsOf(\n ['field', `${f.label}: ${f.key === 'en' ? f.rawValue.toUpperCase() || '(unset)' : interval}`],\n ['meaning', interval],\n ['meaning', describeInterval(interval)],\n ['meaning', `${state} the scale`],\n )\n }\n\n if (ctx.scale) {\n const [notes, shape] = scaleSummaryParts(ctx.scale)\n parts.push(...partsOf(['scale', `Scale: ${notes}`], ['scale', shape]))\n }\n return [...parts, ...partsOf(['location', `${title} row ${interval}`])]\n}\n\n/** 'FF' → 'FFh (dec 255)', '40:80' → '40:80 (mod depth 40, mod frequency 80)', '120' → '120 BPM'. */\nfunction formatParameterValue(field: M8ParsedField): string {\n const unit = field.unit ?? ''\n if (field.values && field.parts) {\n // Hex pairs carry a hexValue ('40:80'); decimal pairs don't ('150:45').\n const hex = field.hexValue != null\n const shown = field.values.map((v) => (hex ? v.toString(16).toUpperCase().padStart(2, '0') : String(v)))\n const named = field.parts.map((p, i) => `${p} ${shown[i] ?? '?'}${unit}`).join(', ')\n return `${shown.join(field.rawValue.includes('/') ? '/' : ':')} (${named})`\n }\n if (field.hexValue != null) {\n return `${field.rawValue.slice(0, 2)}h (dec ${field.hexValue})${field.text ? ` ${field.text}` : ''}`\n }\n if (field.numericValue != null) return `${field.rawValue}${unit}`\n return field.text ?? field.rawValue\n}\n\n/**\n * Formats a parsed field value as a human-readable string.\n */\nexport function formatFieldValue(input: M8ParsedField): string {\n if (input.isEmpty) return '(empty)'\n // The device sends lower case ('arp', 'b-4', 'ff'); show it the way the M8 draws it.\n // Text the user typed and file names keep their case.\n const keepCase = input.type === 'name' || input.type === 'fileEntry'\n const field = keepCase ? input : { ...input, rawValue: input.rawValue.toUpperCase() }\n\n switch (field.type) {\n case 'note':\n if (field.rawValue.toUpperCase() === 'OFF') return 'Note Off'\n return `Note ${field.rawValue}`\n\n case 'transpose': {\n if (field.hexValue == null) return field.rawValue\n // Relative hex: 00=none, 01–7F=positive, 80–FF=negative (two's complement)\n if (field.hexValue === 0) return '±0 st'\n const signed = field.hexValue > 0x7f ? field.hexValue - 0x100 : field.hexValue\n return `${signed > 0 ? '+' : ''}${signed} st`\n }\n\n case 'velocity':\n if (field.hexValue == null) return field.rawValue\n return `${field.rawValue}h (${field.hexValue} / 127 max)`\n\n case 'chainRef':\n case 'phraseRef':\n case 'instrumentRef':\n return field.rawValue\n\n case 'fxCommand':\n return field.rawValue\n\n case 'fxValue':\n if (field.hexValue == null) return field.rawValue\n return `${field.rawValue}h (dec ${field.hexValue})`\n\n case 'ticks':\n if (field.rawValue === '00') return '00 (skip step)'\n if (field.hexValue == null) return field.rawValue\n return `${field.rawValue}h (${field.hexValue} ticks)`\n\n case 'volume':\n return field.rawValue\n\n case 'instrumentType':\n case 'action':\n case 'fileEntry':\n return field.text ?? field.rawValue\n\n case 'name':\n return `\"${field.rawValue}\"`\n\n case 'option':\n // '00CHORUS' → '00 CHORUS'; text-only options ('BELL') stay as they are.\n if (field.hexValue != null && field.text) return `${field.rawValue.slice(0, 2)} ${field.text}`\n return field.text ?? field.rawValue\n\n case 'parameter':\n return formatParameterValue(field)\n\n case 'noteInterval':\n return `Interval ${normalizeInterval(field.rawValue) ?? field.rawValue}`\n\n case 'semitoneOffset':\n return field.isEmpty ? '(empty)' : `${field.rawValue} st`\n\n default:\n return field.rawValue\n }\n}\n","// @ts-expect-error - post-me types are incomplete for the generic handshake signatures used here.\r\nimport { ChildHandshake, DebugMessenger, WindowMessenger } from 'post-me'\r\n// @ts-expect-error - post-me types are incomplete for the generic handshake signatures used here.\r\nimport type { Connection, Messenger } from 'post-me'\r\nimport { type ConnectedBus, connection } from './engine/connection'\r\nimport { connectWithRetry } from './engine/connectWithRetry'\r\nimport { createM8Audio, type M8Audio } from './engine/audio'\r\nimport { createM8Engine, type M8Engine } from './engine/engine'\r\nimport type {\r\n M8ClientEvents,\r\n M8ClientMethods,\r\n M8ClientMode,\r\n M8DeviceBus,\r\n M8DeviceConnectOptions,\r\n M8DeviceSupport,\r\n M8DeviceTransport,\r\n M8HostEvents,\r\n M8HostMethods,\r\n M8KeyName,\r\n M8Screen,\r\n M8ScreenCells,\r\n M8SdkConfig,\r\n M8State,\r\n M8TransportEvent,\r\n} from './types'\r\nimport { getSemanticContext, describeContext, describeContextParts } from './viewContext'\r\nimport type { M8DescriptionPart, M8SemanticContext } from './viewContext'\r\n\r\nexport type { CursorPos, CursorRect, M8KeyName, M8Screen, M8ScreenCells, M8State, M8TransportEvent, RGB, SystemInfos } from './types'\r\nexport type { M8DescriptionPart, M8SemanticContext } from './viewContext'\r\n\r\n/**\r\n * The iframe name yam8d gives its External Apps frames. A client that sees it waits for the\r\n * host handshake however long it takes, instead of falling back to standalone mode.\r\n */\r\nexport const YAM8D_FRAME_NAME = 'yam8d-external-app'\r\n\r\nconst DEFAULT_HANDSHAKE_TIMEOUT_MS = 3000\r\n\r\n/**\r\n * Everything the SDK knows about the moment: raw state, semantic context, its\r\n * human-readable description and the whole screen. Plain JSON, safe to persist.\r\n */\r\nexport interface M8Snapshot {\r\n capturedAt: number\r\n state: Pick<M8State, 'viewName' | 'viewTitle' | 'cursorPos' | 'selectionMode' | 'textUnderCursor' | 'currentLine' | 'deviceModel'>\r\n semantic: M8SemanticContext | null\r\n /** describeContext() output, e.g. \"Note (N): Note C-4 — Phrase View row 00\". */\r\n description: string | null\r\n /** The same description as tagged parts (describeContextParts()). */\r\n descriptionParts: M8DescriptionPart[] | null\r\n screen: M8Screen\r\n}\r\n\r\nexport interface M8Client {\r\n readonly state: M8State\r\n /**\r\n * True when calls reach an M8: in host mode once the yam8d handshake is done, in\r\n * standalone mode while a device is connected.\r\n */\r\n readonly isConnected: boolean\r\n /** 'host' inside yam8d, 'standalone' otherwise; null until connect() has decided. */\r\n readonly mode: M8ClientMode | null\r\n /** Standalone: the API the device is connected through, null while disconnected (always null in host mode). */\r\n readonly deviceTransport: M8DeviceTransport | null\r\n navigateToView(viewName: string): Promise<boolean>\r\n navigateTo(x: number, y: number): Promise<boolean>\r\n setValueToHex(targetHex: number): Promise<boolean>\r\n setValueToInt(targetInt: number): Promise<boolean>\r\n setValueFloat(targetFloat: number): Promise<boolean>\r\n setNote(noteString: string): Promise<boolean>\r\n setValueToString(targetString: string, exact?: boolean, searchInCurrentLine?: boolean): Promise<boolean>\r\n browseFile(targetText: string, exact?: boolean): Promise<boolean>\r\n sendKeyPress(keys: M8KeyName[]): Promise<void>\r\n sendKeyDown(keys: M8KeyName[]): Promise<void>\r\n sendKeyUp(): Promise<void>\r\n getState(): M8State\r\n fetchState(): Promise<M8State>\r\n /**\r\n * Whole screen as text rows, read directly from the M8 character stream.\r\n * No cursor movement involved, so it is instant and side-effect free.\r\n */\r\n getScreen(): Promise<M8Screen>\r\n /**\r\n * The screen plus the foreground/background colour of every character cell ('#rrggbb').\r\n * Rejects when the host predates this method (yam8d before SDK 0.5).\r\n */\r\n getScreenCells(): Promise<M8ScreenCells>\r\n /** Fresh state + semantic context + description + whole screen in one call. */\r\n captureSnapshot(): Promise<M8Snapshot>\r\n onStateChange(callback: (state: M8State) => void): () => void\r\n onViewChange(callback: (viewName: string | null, viewTitle: string | null) => void): () => void\r\n onCursorMove(callback: (pos: M8State['cursorPos'], rect: M8State['cursorRect'], selectionMode: boolean) => void): () => void\r\n onTextUpdate(callback: (textUnderCursor: string | null, currentLine: string | null) => void): () => void\r\n onKeyPress(callback: (keys: number) => void): () => void\r\n /**\r\n * Starts forwarding the M8's MIDI clock and transport (needs SYNC OUT set in the M8's\r\n * MIDI Settings, and Web MIDI permission in the page that owns the device). Hosts before\r\n * SDK 0.5 reject the call.\r\n */\r\n enableMidiTransport(): Promise<{ ok: boolean; error?: string }>\r\n /** MIDI start / stop / continue, the clock once per sixteenth, and song positions. */\r\n onTransport(callback: (event: M8TransportEvent) => void): () => void\r\n /**\r\n * Standalone: connects to the M8 from this page. Without a previously granted device the\r\n * browser shows its device picker, which only opens from a user gesture (call this from a\r\n * click handler). Replaces the current device. In host mode yam8d owns the device: no-op.\r\n */\r\n connectDevice(options?: M8DeviceConnectOptions): Promise<void>\r\n /** Standalone: releases the device so another page (yam8d, another app) can open it. */\r\n disconnectDevice(): Promise<void>\r\n /** Fires when isConnected changes (handshake done, device connected, lost or released). */\r\n onConnectionChange(callback: (connected: boolean) => void): () => void\r\n /** Standalone: raw protocol events and device commands; null in host mode or while disconnected. */\r\n getDevice(): M8DeviceBus | null\r\n /** Standalone: whether the M8's USB audio is playing through this page. */\r\n readonly isAudioConnected: boolean\r\n /**\r\n * Standalone: plays the M8's USB audio input through this page (asks for audio permission;\r\n * rejects when no input named M8 is found). In host mode yam8d plays it: no-op.\r\n */\r\n connectAudio(): Promise<void>\r\n disconnectAudio(): Promise<void>\r\n disconnect(): void\r\n /**\r\n * Derives semantic context from the current state: view, row, and the typed\r\n * field under the cursor. Works for the five grid views (song, chain, phrase,\r\n * table, groove). Returns null when no view is active.\r\n */\r\n getSemanticContext(): M8SemanticContext | null\r\n /**\r\n * Returns a short human-readable description of what the cursor is on,\r\n * e.g. \"Track 3: 0A — Track 3 — Song View row 01\".\r\n *\r\n * It also reads the cursor against its surroundings when that changes the meaning:\r\n * an FX cell under a running REP says which command is repeating and where it has\r\n * got to, FX values are explained (ARP 37 → \"+3 and +7 semitones\"), and the Scale\r\n * View names the scale the enabled notes spell. The surroundings come from a copy of\r\n * the screen the client keeps fresh in the background; call getScreen() (or await\r\n * captureSnapshot()) first if you need it guaranteed current.\r\n */\r\n describeContext(): string | null\r\n /**\r\n * The description as ordered, tagged parts ({ kind: 'field' | 'meaning' | 'location' | …, text }),\r\n * for consumers that want to keep, drop, reorder or style some of them.\r\n */\r\n describeContextParts(): M8DescriptionPart[] | null\r\n}\r\n\r\n/** Where calls and events go: the yam8d host over post-me, or an engine in this page. */\r\ninterface Backend {\r\n call<K extends keyof M8HostMethods>(method: K, ...args: Parameters<M8HostMethods[K]>): ReturnType<M8HostMethods[K]>\r\n on<K extends keyof M8HostEvents>(event: K, callback: (payload: M8HostEvents[K]) => void): () => void\r\n close(): void\r\n}\r\n\r\nconst hostBackend = (connection: Connection<M8ClientMethods, M8HostEvents, M8HostMethods, M8ClientEvents>): Backend => {\r\n const remote = connection.remoteHandle()\r\n return {\r\n call: (method, ...args) => remote.call(method, ...args),\r\n on: (event, callback) => {\r\n remote.addEventListener(event, callback)\r\n return () => remote.removeEventListener(event, callback)\r\n },\r\n close: () => connection.close(),\r\n }\r\n}\r\n\r\nconst engineBackend = (engine: M8Engine): Backend => ({\r\n call: (method, ...args) => (engine.methods[method] as (...a: unknown[]) => ReturnType<M8HostMethods[typeof method]>)(...args),\r\n on: (event, callback) => engine.on(event, callback),\r\n close: () => engine.dispose(),\r\n})\r\n\r\n/** A messenger whose listeners can all be removed, to abandon a handshake that timed out. */\r\nconst disposableMessenger = (inner: Messenger) => {\r\n const removers: (() => void)[] = []\r\n const messenger: Messenger = {\r\n postMessage: (message: unknown, transfer?: Transferable[]) => inner.postMessage(message, transfer),\r\n addMessageListener: (listener: (event: MessageEvent) => void) => {\r\n const remove = inner.addMessageListener(listener)\r\n removers.push(remove)\r\n return remove\r\n },\r\n }\r\n const dispose = () => {\r\n for (const remove of removers.splice(0)) remove()\r\n }\r\n return { messenger, dispose }\r\n}\r\n\r\nconst inIframe = () => {\r\n if (typeof window === 'undefined') return false\r\n try {\r\n return window.parent !== window\r\n } catch {\r\n return true\r\n }\r\n}\r\n\r\nconst isUserCancel = (error: unknown) => error instanceof DOMException && (error.name === 'NotFoundError' || error.name === 'AbortError')\r\n\r\n/** Which device APIs the browser offers for standalone mode. */\r\nexport const getDeviceSupport = (): M8DeviceSupport => {\r\n const nav = typeof navigator === 'undefined' ? null : navigator\r\n return {\r\n serial: !!nav && 'serial' in nav,\r\n usb: !!nav && 'usb' in nav,\r\n midi: !!nav && 'requestMIDIAccess' in nav,\r\n }\r\n}\r\n\r\nconst getDefaultState = (): M8State => ({\r\n viewName: null,\r\n viewTitle: null,\r\n minimapKey: null,\r\n cursorPos: null,\r\n cursorRect: null,\r\n selectionMode: false,\r\n highlightColor: null,\r\n titleColor: null,\r\n backgroundColor: null,\r\n cursorColor: null,\r\n textUnderCursor: null,\r\n currentLine: null,\r\n deviceModel: null,\r\n fontMode: null,\r\n systemInfo: null,\r\n macroRunning: false,\r\n})\r\n\r\nclass M8ClientImpl implements M8Client {\r\n private backend: Backend | null = null\r\n private backendUnsubscribers: (() => void)[] = []\r\n private device: ConnectedBus | null = null\r\n private _mode: M8ClientMode | null = null\r\n private audio: M8Audio | null = null\r\n private connecting: Promise<void> | null = null\r\n private readonly stateCallbacks = new Set<(state: M8State) => void>()\r\n private readonly viewCallbacks = new Set<(viewName: string | null, viewTitle: string | null) => void>()\r\n private readonly cursorCallbacks = new Set<(pos: M8State['cursorPos'], rect: M8State['cursorRect'], selectionMode: boolean) => void>()\r\n private readonly textCallbacks = new Set<(textUnderCursor: string | null, currentLine: string | null) => void>()\r\n private readonly keyCallbacks = new Set<(keys: number) => void>()\r\n private readonly transportCallbacks = new Set<(event: M8TransportEvent) => void>()\r\n private readonly connectionCallbacks = new Set<(connected: boolean) => void>()\r\n private _state: M8State = getDefaultState()\r\n private _screen: M8Screen | null = null\r\n private screenRefreshing = false\r\n private screenDirty = false\r\n private readonly config: M8SdkConfig\r\n\r\n constructor(config: M8SdkConfig = {}) {\r\n this.config = config\r\n }\r\n\r\n get state(): M8State {\r\n return this._state\r\n }\r\n\r\n get isConnected(): boolean {\r\n return this.backend !== null\r\n }\r\n\r\n get mode(): M8ClientMode | null {\r\n return this._mode\r\n }\r\n\r\n get isAudioConnected(): boolean {\r\n return this.audio?.connected ?? false\r\n }\r\n\r\n get deviceTransport(): M8DeviceTransport | null {\r\n return (this.device?.transport as M8DeviceTransport | undefined) ?? null\r\n }\r\n\r\n async connect(): Promise<void> {\r\n this.connecting ??= this.resolveMode()\r\n return this.connecting\r\n }\r\n\r\n /** Picks host or standalone mode (see M8SdkConfig.mode) and sets it up. */\r\n private async resolveMode(): Promise<void> {\r\n const mode = this.config.mode ?? 'auto'\r\n if (mode === 'standalone' || (mode === 'auto' && !inIframe())) {\r\n await this.startStandalone()\r\n return\r\n }\r\n\r\n // An unmarked iframe may be some other page embedding the app: give up after a while\r\n const markedByYam8d = typeof window !== 'undefined' && window.name === YAM8D_FRAME_NAME\r\n const timeoutMs = mode === 'host' || markedByYam8d ? null : (this.config.handshakeTimeoutMs ?? DEFAULT_HANDSHAKE_TIMEOUT_MS)\r\n const connected = await this.handshake(timeoutMs)\r\n if (!connected) {\r\n if (this.config.debug) console.log('[M8 SDK] No yam8d host answered, switching to standalone mode')\r\n await this.startStandalone()\r\n }\r\n }\r\n\r\n private async handshake(timeoutMs: number | null): Promise<boolean> {\r\n let messenger: Messenger = new WindowMessenger({\r\n localWindow: window,\r\n remoteWindow: window.parent,\r\n remoteOrigin: '*',\r\n })\r\n\r\n if (this.config.debug) {\r\n messenger = DebugMessenger(messenger, (msg: string, ...args: unknown[]) => {\r\n console.log('[M8 SDK]', msg, ...args)\r\n })\r\n }\r\n\r\n const disposable = disposableMessenger(messenger)\r\n const handshake = ChildHandshake<M8ClientMethods, M8HostEvents, M8HostMethods, M8ClientEvents>(disposable.messenger, {\r\n ping: async () => 'pong',\r\n })\r\n\r\n let timer: ReturnType<typeof setTimeout> | undefined\r\n const timedOut = new Promise<null>((resolve) => {\r\n if (timeoutMs !== null) timer = setTimeout(() => resolve(null), timeoutMs)\r\n })\r\n const connection = await Promise.race([handshake, timedOut])\r\n clearTimeout(timer)\r\n if (!connection) {\r\n disposable.dispose()\r\n return false\r\n }\r\n\r\n this._mode = 'host'\r\n await this.attach(hostBackend(connection))\r\n connection.localHandle().emit('ready', undefined)\r\n return true\r\n }\r\n\r\n private async startStandalone(): Promise<void> {\r\n this._mode = 'standalone'\r\n if (this.config.autoConnect === false) return\r\n try {\r\n await this.openDevice('auto', false)\r\n } catch (error) {\r\n // Nothing granted yet (or the device is busy, e.g. open in yam8d): wait for connectDevice()\r\n if (this.config.debug) console.log('[M8 SDK] No device reopened automatically:', error)\r\n }\r\n }\r\n\r\n /**\r\n * Opens the device and runs an engine on it. 'auto' without a prompt tries each granted\r\n * API in turn; with a prompt it picks the first API the browser supports.\r\n */\r\n private async openDevice(transport: 'auto' | M8DeviceTransport, prompt: boolean): Promise<void> {\r\n const conn = connection()\r\n if (!conn.browserSupport) throw new Error('This browser has no Web Serial, WebUSB or Web MIDI: the M8 cannot be reached from this page')\r\n\r\n const candidates: ('auto' | M8DeviceTransport)[] =\r\n transport === 'auto' && !prompt ? (['serial', 'usb', 'midi'] as const).filter((t) => conn.available[t]) : [transport]\r\n\r\n // The same port cannot be opened twice: release the current device first\r\n await this.closeDevice()\r\n\r\n let bus: ConnectedBus | undefined\r\n let lastError: unknown = new Error('No M8 device available')\r\n for (const candidate of candidates) {\r\n try {\r\n bus = await conn.connect({ transport: candidate, prompt })\r\n break\r\n } catch (error) {\r\n lastError = error\r\n // The M8 Headless can need a moment after being picked: retry on the granted device\r\n if (prompt && !isUserCancel(error)) {\r\n bus = await connectWithRetry(() => conn.connect({ transport: candidate, prompt: false }), { maxAttempts: 2 })\r\n if (bus) break\r\n }\r\n }\r\n }\r\n if (!bus) throw lastError\r\n\r\n const engine = createM8Engine(bus, { debug: this.config.debug })\r\n this.device = bus\r\n await this.attach(engineBackend(engine))\r\n // The extractor listens now: ask for a full redraw so the screen state is complete\r\n bus.commands.resetScreen()\r\n\r\n const opened = bus\r\n void bus.closed.then(() => {\r\n if (this.device !== opened) return\r\n if (this.config.debug) console.log('[M8 SDK] Device connection ended')\r\n this.device = null\r\n this.detach()\r\n })\r\n }\r\n\r\n private async closeDevice(): Promise<void> {\r\n const bus = this.device\r\n if (!bus) return\r\n this.device = null\r\n this.detach()\r\n await bus.close()\r\n }\r\n\r\n /** Routes calls and events through `backend`. */\r\n private async attach(backend: Backend): Promise<void> {\r\n this.detach()\r\n this.backend = backend\r\n const subs = this.backendUnsubscribers\r\n\r\n subs.push(\r\n backend.on('stateChanged', (state) => {\r\n this._state = state\r\n this.refreshScreen()\r\n this.stateCallbacks.forEach((cb) => {\r\n cb(state)\r\n })\r\n }),\r\n )\r\n\r\n subs.push(\r\n backend.on('viewChanged', ({ viewName, viewTitle }) => {\r\n this._state = { ...this._state, viewName, viewTitle }\r\n this.refreshScreen()\r\n this.viewCallbacks.forEach((cb) => {\r\n cb(viewName, viewTitle)\r\n })\r\n }),\r\n )\r\n\r\n subs.push(\r\n backend.on('cursorMoved', ({ pos, rect, selectionMode }) => {\r\n this._state = { ...this._state, cursorPos: pos, cursorRect: rect, selectionMode }\r\n this.refreshScreen()\r\n this.cursorCallbacks.forEach((cb) => {\r\n cb(pos, rect, selectionMode)\r\n })\r\n }),\r\n )\r\n\r\n subs.push(\r\n backend.on('textUpdated', ({ textUnderCursor, currentLine }) => {\r\n this._state = { ...this._state, textUnderCursor, currentLine }\r\n this.refreshScreen()\r\n this.textCallbacks.forEach((cb) => {\r\n cb(textUnderCursor, currentLine)\r\n })\r\n }),\r\n )\r\n\r\n subs.push(\r\n backend.on('transport', (event) => {\r\n this.transportCallbacks.forEach((cb) => {\r\n cb(event)\r\n })\r\n }),\r\n )\r\n\r\n subs.push(\r\n backend.on('keyPressed', ({ keys }) => {\r\n this.keyCallbacks.forEach((cb) => {\r\n cb(keys)\r\n })\r\n }),\r\n )\r\n\r\n this.connectionCallbacks.forEach((cb) => {\r\n cb(true)\r\n })\r\n await this.fetchState()\r\n this.refreshScreen()\r\n }\r\n\r\n private detach(): void {\r\n const backend = this.backend\r\n if (!backend) return\r\n this.backend = null\r\n this.backendUnsubscribers.splice(0).forEach((unsubscribe) => {\r\n unsubscribe()\r\n })\r\n backend.close()\r\n this._screen = null\r\n this.connectionCallbacks.forEach((cb) => {\r\n cb(false)\r\n })\r\n }\r\n\r\n private requireBackend(): Backend {\r\n if (this.backend) return this.backend\r\n if (this._mode === 'standalone') throw new Error('No M8 device connected: call connectDevice() first')\r\n throw new Error('M8 SDK client is not connected')\r\n }\r\n\r\n /**\r\n * Keeps the cached screen close to the state without a round-trip per event: at most\r\n * one request in flight, and one more queued if events arrived meanwhile.\r\n */\r\n private refreshScreen(): void {\r\n const backend = this.backend\r\n if (!backend) return\r\n if (this.screenRefreshing) {\r\n this.screenDirty = true\r\n return\r\n }\r\n this.screenRefreshing = true\r\n backend\r\n .call('getScreen')\r\n .then((screen: M8Screen) => {\r\n if (this.backend === backend) this._screen = screen\r\n })\r\n .catch(() => {\r\n // Descriptions simply fall back to the cursor row alone.\r\n })\r\n .finally(() => {\r\n this.screenRefreshing = false\r\n if (this.screenDirty) {\r\n this.screenDirty = false\r\n this.refreshScreen()\r\n }\r\n })\r\n }\r\n\r\n async connectDevice(options: M8DeviceConnectOptions = {}): Promise<void> {\r\n await this.connect()\r\n if (this._mode === 'host') return\r\n await this.openDevice(options.transport ?? 'auto', true)\r\n if (options.audio) {\r\n // The device is usable without sound: report and carry on\r\n await this.connectAudio().catch((error: unknown) => console.warn('[M8 SDK] M8 audio not started:', error))\r\n }\r\n }\r\n\r\n async disconnectDevice(): Promise<void> {\r\n await this.disconnectAudio()\r\n await this.closeDevice()\r\n }\r\n\r\n async connectAudio(): Promise<void> {\r\n await this.connect()\r\n if (this._mode === 'host') return\r\n this.audio ??= createM8Audio()\r\n // Never fall back to another input: playing a microphone through the speakers feeds back\r\n await this.audio.connect({ requireM8: true })\r\n }\r\n\r\n async disconnectAudio(): Promise<void> {\r\n await this.audio?.disconnect()\r\n }\r\n\r\n getDevice(): M8DeviceBus | null {\r\n const bus = this.device\r\n if (!bus) return null\r\n return {\r\n transport: bus.transport as M8DeviceTransport,\r\n events: bus.protocol.eventBus as unknown as M8DeviceBus['events'],\r\n commands: bus.commands,\r\n }\r\n }\r\n\r\n onConnectionChange(callback: (connected: boolean) => void): () => void {\r\n this.connectionCallbacks.add(callback)\r\n return () => this.connectionCallbacks.delete(callback)\r\n }\r\n\r\n async navigateToView(viewName: string): Promise<boolean> {\r\n return this.requireBackend().call('navigateToView', viewName)\r\n }\r\n\r\n async navigateTo(x: number, y: number): Promise<boolean> {\r\n return this.requireBackend().call('navigateTo', x, y)\r\n }\r\n\r\n async setValueToHex(targetHex: number): Promise<boolean> {\r\n return this.requireBackend().call('setValueToHex', targetHex)\r\n }\r\n\r\n async setValueToInt(targetInt: number): Promise<boolean> {\r\n return this.requireBackend().call('setValueToInt', targetInt)\r\n }\r\n\r\n async setValueFloat(targetFloat: number): Promise<boolean> {\r\n return this.requireBackend().call('setValueFloat', targetFloat)\r\n }\r\n\r\n async setNote(noteString: string): Promise<boolean> {\r\n return this.requireBackend().call('setNote', noteString)\r\n }\r\n\r\n async setValueToString(targetString: string, exact = true, searchInCurrentLine = false): Promise<boolean> {\r\n return this.requireBackend().call('setValueToString', targetString, exact, searchInCurrentLine)\r\n }\r\n\r\n async browseFile(targetText: string, exact = true): Promise<boolean> {\r\n return this.requireBackend().call('browseFile', targetText, exact)\r\n }\r\n\r\n async sendKeyPress(keys: M8KeyName[]): Promise<void> {\r\n return this.requireBackend().call('sendKeyPress', keys)\r\n }\r\n\r\n async sendKeyDown(keys: M8KeyName[]): Promise<void> {\r\n return this.requireBackend().call('sendKeyDown', keys)\r\n }\r\n\r\n async sendKeyUp(): Promise<void> {\r\n return this.requireBackend().call('sendKeyUp')\r\n }\r\n\r\n getState(): M8State {\r\n return this._state\r\n }\r\n\r\n async fetchState(): Promise<M8State> {\r\n const state = await this.requireBackend().call('getState')\r\n this._state = state\r\n return state\r\n }\r\n\r\n async getScreen(): Promise<M8Screen> {\r\n const screen = await this.requireBackend().call('getScreen')\r\n this._screen = screen\r\n return screen\r\n }\r\n\r\n async getScreenCells(): Promise<M8ScreenCells> {\r\n const cells = await this.requireBackend().call('getScreenCells')\r\n this._screen = { width: cells.width, height: cells.height, lines: cells.lines }\r\n return cells\r\n }\r\n\r\n async captureSnapshot(): Promise<M8Snapshot> {\r\n const [state, screen] = await Promise.all([this.fetchState(), this.getScreen()])\r\n const semantic = getSemanticContext(state, screen)\r\n return {\r\n capturedAt: Date.now(),\r\n state: {\r\n viewName: state.viewName,\r\n viewTitle: state.viewTitle,\r\n cursorPos: state.cursorPos,\r\n selectionMode: state.selectionMode,\r\n textUnderCursor: state.textUnderCursor,\r\n currentLine: state.currentLine,\r\n deviceModel: state.deviceModel,\r\n },\r\n semantic,\r\n description: semantic ? describeContext(semantic) : null,\r\n descriptionParts: semantic ? describeContextParts(semantic) : null,\r\n screen,\r\n }\r\n }\r\n\r\n onStateChange(callback: (state: M8State) => void): () => void {\r\n this.stateCallbacks.add(callback)\r\n return () => this.stateCallbacks.delete(callback)\r\n }\r\n\r\n onViewChange(callback: (viewName: string | null, viewTitle: string | null) => void): () => void {\r\n this.viewCallbacks.add(callback)\r\n return () => this.viewCallbacks.delete(callback)\r\n }\r\n\r\n onCursorMove(callback: (pos: M8State['cursorPos'], rect: M8State['cursorRect'], selectionMode: boolean) => void): () => void {\r\n this.cursorCallbacks.add(callback)\r\n return () => this.cursorCallbacks.delete(callback)\r\n }\r\n\r\n onTextUpdate(callback: (textUnderCursor: string | null, currentLine: string | null) => void): () => void {\r\n this.textCallbacks.add(callback)\r\n return () => this.textCallbacks.delete(callback)\r\n }\r\n\r\n onKeyPress(callback: (keys: number) => void): () => void {\r\n this.keyCallbacks.add(callback)\r\n return () => this.keyCallbacks.delete(callback)\r\n }\r\n\r\n async enableMidiTransport(): Promise<{ ok: boolean; error?: string }> {\r\n return this.requireBackend().call('enableMidiTransport')\r\n }\r\n\r\n onTransport(callback: (event: M8TransportEvent) => void): () => void {\r\n this.transportCallbacks.add(callback)\r\n return () => this.transportCallbacks.delete(callback)\r\n }\r\n\r\n disconnect(): void {\r\n void this.audio?.disconnect()\r\n void this.closeDevice()\r\n this.detach()\r\n this.stateCallbacks.clear()\r\n this.viewCallbacks.clear()\r\n this.cursorCallbacks.clear()\r\n this.textCallbacks.clear()\r\n this.keyCallbacks.clear()\r\n this.transportCallbacks.clear()\r\n this.connectionCallbacks.clear()\r\n }\r\n\r\n getSemanticContext(): M8SemanticContext | null {\r\n return getSemanticContext(this._state, this._screen)\r\n }\r\n\r\n describeContext(): string | null {\r\n const ctx = getSemanticContext(this._state, this._screen)\r\n return ctx ? describeContext(ctx) : null\r\n }\r\n\r\n describeContextParts(): M8DescriptionPart[] | null {\r\n const ctx = getSemanticContext(this._state, this._screen)\r\n return ctx ? describeContextParts(ctx) : null\r\n }\r\n}\r\n\r\n/**\r\n * Creates a client and connects it. Inside a yam8d External Apps iframe it talks to yam8d;\r\n * anywhere else it runs standalone (see M8SdkConfig.mode) and reopens a device the page was\r\n * granted before. A first standalone connection needs connectDevice() from a click.\r\n */\r\nexport async function createM8Client(config: M8SdkConfig = {}): Promise<M8Client> {\r\n const client = new M8ClientImpl(config)\r\n await client.connect()\r\n return client\r\n}\r\n\r\nexport function createM8ClientSync(config: M8SdkConfig = {}): { client: M8Client; connect: () => Promise<void> } {\r\n const client = new M8ClientImpl(config)\r\n return {\r\n client,\r\n connect: async () => {\r\n await client.connect()\r\n },\r\n }\r\n}\r\n\r\nexport default {\r\n createM8Client,\r\n createM8ClientSync,\r\n}\r\n","/**\r\n * theme.ts\r\n *\r\n * The device theme: the 13 colours of the Theme View (System Settings > EDIT THEME),\r\n * read off its screen, and the few colours any screen shows on its own.\r\n */\r\n\r\nimport type { M8Screen, M8State, RGB } from './types'\r\n\r\nexport type M8ThemeRole =\r\n | 'background'\r\n | 'textEmpty'\r\n | 'textInfo'\r\n | 'textDefault'\r\n | 'textValue'\r\n | 'textTitles'\r\n | 'playMarkers'\r\n | 'cursor'\r\n | 'selection'\r\n | 'scopeSlider'\r\n | 'meterLow'\r\n | 'meterMid'\r\n | 'meterPeak'\r\n\r\n/** The Theme View rows, top to bottom, with the label the device shows. */\r\nexport const M8_THEME_ROLES: readonly { role: M8ThemeRole; label: string }[] = [\r\n { role: 'background', label: 'BACKGROUND' },\r\n { role: 'textEmpty', label: 'TEXT:EMPTY' },\r\n { role: 'textInfo', label: 'TEXT:INFO' },\r\n { role: 'textDefault', label: 'TEXT:DEFAULT' },\r\n { role: 'textValue', label: 'TEXT:VALUE' },\r\n { role: 'textTitles', label: 'TEXT:TITLES' },\r\n { role: 'playMarkers', label: 'PLAY MARKERS' },\r\n { role: 'cursor', label: 'CURSOR' },\r\n { role: 'selection', label: 'SELECTION' },\r\n { role: 'scopeSlider', label: 'SCOPE/SLIDER' },\r\n { role: 'meterLow', label: 'METER LOW' },\r\n { role: 'meterMid', label: 'METER MID' },\r\n { role: 'meterPeak', label: 'METER PEAK' },\r\n]\r\n\r\n/** View name the SDK reports on the Theme View (title \"THEME SETTINGS\"). */\r\nexport const M8_THEME_VIEW = 'themesettings'\r\n\r\nexport interface M8Theme {\r\n /** Every theme colour as '#rrggbb'. */\r\n colors: Record<M8ThemeRole, string>\r\n /** Colour model the Theme View was showing. HSV values are converted to RGB (H, S and V read as 00-FF). */\r\n mode: 'rgb' | 'hsv'\r\n /** THEME NAME, or null when unnamed. */\r\n name: string | null\r\n}\r\n\r\nconst squash = (s: string): string => s.replace(/\\s+/g, '').toUpperCase()\r\n\r\nconst hex2 = (n: number): string => Math.max(0, Math.min(255, Math.round(n))).toString(16).padStart(2, '0')\r\n\r\nconst rgbToHex = (r: number, g: number, b: number): string => `#${hex2(r)}${hex2(g)}${hex2(b)}`\r\n\r\n/** HSV with every channel 0-255 (the Theme View shows them as hex bytes). */\r\nconst hsvToHex = (h: number, s: number, v: number): string => {\r\n const hue = (h / 256) * 6\r\n const sat = s / 255\r\n const val = v / 255\r\n const i = Math.floor(hue) % 6\r\n const f = hue - Math.floor(hue)\r\n const p = val * (1 - sat)\r\n const q = val * (1 - f * sat)\r\n const t = val * (1 - (1 - f) * sat)\r\n const [r, g, b] = [\r\n [val, t, p],\r\n [q, val, p],\r\n [p, val, t],\r\n [p, q, val],\r\n [t, p, val],\r\n [val, p, q],\r\n ][i]\r\n return rgbToHex(r * 255, g * 255, b * 255)\r\n}\r\n\r\nconst ROLE_BY_LABEL = new Map(M8_THEME_ROLES.map(({ role, label }) => [squash(label), role]))\r\n\r\n/**\r\n * Reads the theme off the Theme View screen (from getScreen()). Returns null unless\r\n * all 13 colour rows are found, so a half-drawn screen is never taken for a theme.\r\n *\r\n * @example\r\n * ```ts\r\n * if (m8.state.viewName === M8_THEME_VIEW) {\r\n * const theme = parseThemeScreen(await m8.getScreen())\r\n * theme?.colors.textValue // '#ffffff'\r\n * }\r\n * ```\r\n */\r\nexport function parseThemeScreen(screen: M8Screen): M8Theme | null {\r\n let mode: M8Theme['mode'] = 'rgb'\r\n let name: string | null = null\r\n const colors: Partial<Record<M8ThemeRole, string>> = {}\r\n\r\n for (const raw of screen.lines) {\r\n const line = raw.replace(/[{}]/g, '0')\r\n const header = /^\\s*MODE\\s+([A-Z])\\s+([A-Z])\\s+([A-Z])\\b/i.exec(line)\r\n if (header) {\r\n mode = squash(header.slice(1).join('')) === 'HSV' ? 'hsv' : 'rgb'\r\n continue\r\n }\r\n const named = /^\\s*THEME\\s+NAME\\s+(\\S.*?)\\s*$/i.exec(line)\r\n if (named) {\r\n // The track panel can share the row on the right: the name ends at the first wide gap.\r\n const text = named[1].split(/\\s{2,}/)[0]\r\n name = /^-+$/.test(text) ? null : text\r\n continue\r\n }\r\n const row = /^\\s*([A-Z][A-Z:/. ]*?)\\s+([0-9A-F]{2})\\s+([0-9A-F]{2})\\s+([0-9A-F]{2})(?:\\s|$)/i.exec(line)\r\n if (!row) continue\r\n const role = ROLE_BY_LABEL.get(squash(row[1]))\r\n if (!role || colors[role]) continue\r\n const [a, b, c] = row.slice(2, 5).map((v) => parseInt(v, 16))\r\n colors[role] = mode === 'hsv' ? hsvToHex(a, b, c) : rgbToHex(a, b, c)\r\n }\r\n\r\n if (M8_THEME_ROLES.some(({ role }) => !colors[role])) return null\r\n return { colors: colors as Record<M8ThemeRole, string>, mode, name }\r\n}\r\n\r\nconst rgbHex = (c: RGB | null | undefined): string | undefined => (c ? rgbToHex(c.r, c.g, c.b) : undefined)\r\n\r\n/**\r\n * The theme colours any screen gives away without opening the Theme View: the\r\n * background and the title colour. (The cursor rectangle is animated on the device, so\r\n * `state.cursorColor` is not the theme's CURSOR colour.)\r\n */\r\nexport function observeThemeColors(state: M8State): Partial<Record<M8ThemeRole, string>> {\r\n const out: Partial<Record<M8ThemeRole, string>> = {}\r\n const background = rgbHex(state.backgroundColor)\r\n const titles = rgbHex(state.titleColor)\r\n if (background) out.background = background\r\n if (titles) out.textTitles = titles\r\n return out\r\n}\r\n"],"mappings":"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"}