@yam8d/m8-sdk 0.3.3 → 0.4.0

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":[],"sources":["../src/m8-view-context.json","../src/viewContext.ts","../src/client.ts"],"sourcesContent":["{\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 \"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' ('--' = unset). 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. Only present on groove row 0; absent on all other rows.\"\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). Only present on groove row 0; absent on all other rows.\" }\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). Rows below the 12 intervals (not column-mapped) hold: KEY (global root note for the default scale), 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 'OFF' ('--' = unset).\" },\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=mode, 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. A double-caret '^^' appears next to an FX lane while a REP from a previous phrase is active.\" },\r\n \"RTO\": { \"description\": \"Repeat To: sets the min/max value that REP stops at.\" },\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 based on loop count.\" },\r\n \"PSL\": { \"description\": \"Pitch Slide (portamento): enables portamento for 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 (hex → decimal conversion shown in help text).\" },\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 offset (relative).\" },\r\n \"PIT\": { \"description\": \"Pitch offset in semitones (relative).\" },\r\n \"FIN\": { \"description\": \"Fine tune: pitch offset -1 to +1 semitones (relative).\" },\r\n \"EA1\": { \"description\": \"Envelope 1 Amount offset.\" },\r\n \"EA2\": { \"description\": \"Envelope 2 Amount offset.\" },\r\n \"AT1\": { \"description\": \"Envelope 1 Attack time offset.\" },\r\n \"AT2\": { \"description\": \"Envelope 2 Attack time offset.\" },\r\n \"HO1\": { \"description\": \"Envelope 1 Hold time offset.\" },\r\n \"HO2\": { \"description\": \"Envelope 2 Hold time offset.\" },\r\n \"DE1\": { \"description\": \"Envelope 1 Decay time offset.\" },\r\n \"DE2\": { \"description\": \"Envelope 2 Decay time offset.\" },\r\n \"ET1\": { \"description\": \"Retrigger Envelope 1 (any value > 00 retriggers).\" },\r\n \"ET2\": { \"description\": \"Retrigger Envelope 2.\" },\r\n \"LA1\": { \"description\": \"LFO 1 Amount offset.\" },\r\n \"LA2\": { \"description\": \"LFO 2 Amount offset.\" },\r\n \"LF1\": { \"description\": \"LFO 1 Frequency offset.\" },\r\n \"LF2\": { \"description\": \"LFO 2 Frequency offset.\" },\r\n \"LT1\": { \"description\": \"Retrigger LFO 1 (value sets phase offset).\" },\r\n \"LT2\": { \"description\": \"Retrigger LFO 2.\" },\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 \"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 \"IVO\": { \"description\": \"Line Input Mixer: 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}\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 * The five grid views (song, chain, phrase, table, groove) are fully supported.\r\n * Parameter views (inst, mixer, project, …) return a minimal context with viewName only.\r\n */\r\n\r\nimport type { M8State } from './types'\r\nimport schema from './m8-view-context.json'\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\r\n// Schema shapes (mirror m8-view-context.json) \r\n\r\ninterface 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}\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}\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 /** 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 /** 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}\r\n\r\n// FX command lookup \r\n\r\ntype FxCategory = 'sequencer' | 'instrument' | 'mixer'\r\ntype FxCommandMap = Record<string, { description: string }>\r\n\r\nconst fxCommands = schema.fxCommands as Record<FxCategory, FxCommandMap>\r\n\r\n/**\r\n * Look up the description of a 3-character FX command name.\r\n * Returns undefined if the command is unknown.\r\n */\r\nexport function lookupFxCommand(cmd: string): { description: string } | undefined {\r\n const upper = cmd.toUpperCase()\r\n for (const cat of Object.values(fxCommands)) {\r\n if (upper in cat) return cat[upper]\r\n }\r\n return undefined\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/**\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\n/**\r\n * Extracts the item ID and unsaved flag from a raw M8 screen title.\r\n * Titles follow the pattern ' VIEWNAME ID* ' where ID is 1–2 hex chars\r\n * and '*' is an optional unsaved-changes marker.\r\n * Returns { viewId: null, isUnsaved: false } for views without an item number.\r\n */\r\nfunction extractViewId(rawTitle: string | null): { viewId: string | null; isUnsaved: boolean } {\r\n if (!rawTitle) return { viewId: null, isUnsaved: false }\r\n const trimmed = rawTitle.trim()\r\n const isUnsaved = trimmed.endsWith('*')\r\n const withoutStar = isUnsaved ? trimmed.slice(0, -1).trimEnd() : trimmed\r\n const parts = withoutStar.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 }\r\n}\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 const raw = extractFromLine(line, col.x, col.width).trim()\r\n const empty = isEmptyRaw(raw)\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 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\n// Public API \r\n\r\n/**\r\n * Derives a full semantic context from the current M8State.\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): 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\r\n if (!isGridView || !viewDef) {\r\n return { viewName, viewTitle, viewDescription, viewId, isUnsaved, isGridView: false, row: null, activeField: null }\r\n }\r\n\r\n const rawLine = state.currentLine\r\n if (!rawLine) {\r\n return { viewName, viewTitle, viewDescription, viewId, isUnsaved, 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, offset } = stripPlaybackIndicator(rawLine)\r\n const cursorX = state.cursorPos ? state.cursorPos.x - offset : 0\r\n\r\n // Parse row index\r\n const rowIndex = viewDef.rowField ? parseRowIndex(line, viewDef.rowField) : null\r\n\r\n // Parse every column defined for this view\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\r\n const row: M8ParsedRow = {\r\n rowIndex,\r\n rowKey: viewDef.rowField?.key ?? 'row',\r\n fields,\r\n }\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 = fields[col.key]\r\n if (field) {\r\n activeField = { ...field, viewName, rowIndex }\r\n }\r\n }\r\n }\r\n\r\n return { viewName, viewTitle, viewDescription, viewId, isUnsaved, isGridView: true, row, activeField }\r\n}\r\n\r\n/**\r\n * Returns a short human-readable description of what the cursor is currently on.\r\n *\r\n * @example\r\n * \"Track 3 — Chain 0A — Song Row 02\"\r\n * \"Note A#4 — Step 7 (Phrase)\"\r\n * \"FX1 Command: KIL (Kill Note) — Step 0\"\r\n */\r\nexport function describeContext(ctx: M8SemanticContext): string {\r\n if (!ctx.activeField) return ctx.viewTitle ?? ctx.viewName\r\n\r\n const { activeField: f, row } = ctx\r\n const parts: string[] = []\r\n\r\n // Label + value\r\n const val = formatFieldValue(f)\r\n parts.push(`${f.label}: ${val}`)\r\n\r\n // FX command extra info\r\n if (f.type === 'fxCommand' && !f.isEmpty) {\r\n const info = lookupFxCommand(f.rawValue)\r\n if (info) parts.push(`(${info.description})`)\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 = lookupFxCommand(cmdField.rawValue)\r\n const label = info ? `${cmdField.rawValue} (${info.description})` : cmdField.rawValue\r\n parts.push(`for ${label}`)\r\n }\r\n }\r\n\r\n // Track index for song view\r\n if (f.meta?.trackIndex != null) parts.push(`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(`${ctx.viewTitle ?? ctx.viewName} row ${rowHex}`)\r\n }\r\n\r\n return parts.join(' — ')\r\n}\r\n\r\n/**\r\n * Formats a parsed field value as a human-readable string.\r\n */\r\nexport function formatFieldValue(field: M8ParsedField): string {\r\n if (field.isEmpty) return '(empty)'\r\n\r\n switch (field.type) {\r\n case 'note':\r\n if (field.rawValue === '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 'noteInterval':\r\n return `Interval ${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, LocalHandle, RemoteHandle } from 'post-me'\r\nimport type { M8ClientEvents, M8ClientMethods, M8HostEvents, M8HostMethods, M8KeyName, M8SdkConfig, M8State } from './types'\r\nimport { getSemanticContext, describeContext } from './viewContext'\r\nimport type { M8SemanticContext } from './viewContext'\r\n\r\nexport type { CursorPos, CursorRect, M8KeyName, M8State, RGB, SystemInfos } from './types'\r\nexport type { M8SemanticContext } from './viewContext'\r\n\r\nexport interface M8Client {\r\n readonly state: M8State\r\n readonly isConnected: boolean\r\n navigateToView(viewName: string): Promise<boolean>\r\n navigateTo(x: number, y: number): Promise<void>\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 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 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 — Chain 0A — Song View row 02\".\r\n */\r\n describeContext(): string | null\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 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 connection: Connection<M8ClientMethods, M8HostEvents, M8HostMethods, M8ClientEvents> | null = null\r\n private remoteHandle: RemoteHandle<M8HostMethods, M8HostEvents> | null = null\r\n private localHandle: LocalHandle<M8ClientMethods, M8ClientEvents> | 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 _state: M8State = getDefaultState()\r\n private _isConnected = 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._isConnected\r\n }\r\n\r\n async connect(): Promise<void> {\r\n if (this.connection) {\r\n return\r\n }\r\n\r\n let 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 connection = await ChildHandshake<M8ClientMethods, M8HostEvents, M8HostMethods, M8ClientEvents>(messenger, {\r\n ping: async () => 'pong',\r\n })\r\n\r\n this.connection = connection\r\n this.remoteHandle = connection.remoteHandle()\r\n this.localHandle = connection.localHandle()\r\n this._isConnected = true\r\n\r\n this.remoteHandle.addEventListener('stateChanged', (state: M8State) => {\r\n this._state = state\r\n this.stateCallbacks.forEach((cb) => {\r\n cb(state)\r\n })\r\n })\r\n\r\n this.remoteHandle.addEventListener('viewChanged', ({ viewName, viewTitle }: { viewName: string | null; viewTitle: string | null }) => {\r\n this._state = { ...this._state, viewName, viewTitle }\r\n this.viewCallbacks.forEach((cb) => {\r\n cb(viewName, viewTitle)\r\n })\r\n })\r\n\r\n this.remoteHandle.addEventListener('cursorMoved', ({ pos, rect, selectionMode }: { pos: M8State['cursorPos']; rect: M8State['cursorRect']; selectionMode: boolean }) => {\r\n this._state = { ...this._state, cursorPos: pos, cursorRect: rect, selectionMode }\r\n this.cursorCallbacks.forEach((cb) => {\r\n cb(pos, rect, selectionMode)\r\n })\r\n })\r\n\r\n this.remoteHandle.addEventListener('textUpdated', ({ textUnderCursor, currentLine }: { textUnderCursor: string | null; currentLine: string | null }) => {\r\n this._state = { ...this._state, textUnderCursor, currentLine }\r\n this.textCallbacks.forEach((cb) => {\r\n cb(textUnderCursor, currentLine)\r\n })\r\n })\r\n\r\n this.remoteHandle.addEventListener('keyPressed', ({ keys }: { keys: number }) => {\r\n this.keyCallbacks.forEach((cb) => {\r\n cb(keys)\r\n })\r\n })\r\n\r\n await this.fetchState()\r\n this.localHandle.emit('ready', undefined)\r\n }\r\n\r\n async navigateToView(viewName: string): Promise<boolean> {\r\n if (!this.remoteHandle) throw new Error('M8 SDK client is not connected')\r\n return this.remoteHandle.call('navigateToView', viewName)\r\n }\r\n\r\n async navigateTo(x: number, y: number): Promise<void> {\r\n if (!this.remoteHandle) throw new Error('M8 SDK client is not connected')\r\n return this.remoteHandle.call('navigateTo', x, y)\r\n }\r\n\r\n async setValueToHex(targetHex: number): Promise<boolean> {\r\n if (!this.remoteHandle) throw new Error('M8 SDK client is not connected')\r\n return this.remoteHandle.call('setValueToHex', targetHex)\r\n }\r\n\r\n async setValueToInt(targetInt: number): Promise<boolean> {\r\n if (!this.remoteHandle) throw new Error('M8 SDK client is not connected')\r\n return this.remoteHandle.call('setValueToInt', targetInt)\r\n }\r\n\r\n async setValueFloat(targetFloat: number): Promise<boolean> {\r\n if (!this.remoteHandle) throw new Error('M8 SDK client is not connected')\r\n return this.remoteHandle.call('setValueFloat', targetFloat)\r\n }\r\n\r\n async setNote(noteString: string): Promise<boolean> {\r\n if (!this.remoteHandle) throw new Error('M8 SDK client is not connected')\r\n return this.remoteHandle.call('setNote', noteString)\r\n }\r\n\r\n async setValueToString(targetString: string, exact = true, searchInCurrentLine = false): Promise<boolean> {\r\n if (!this.remoteHandle) throw new Error('M8 SDK client is not connected')\r\n return this.remoteHandle.call('setValueToString', targetString, exact, searchInCurrentLine)\r\n }\r\n\r\n async browseFile(targetText: string, exact = true): Promise<boolean> {\r\n if (!this.remoteHandle) throw new Error('M8 SDK client is not connected')\r\n return this.remoteHandle.call('browseFile', targetText, exact)\r\n }\r\n\r\n async sendKeyPress(keys: M8KeyName[]): Promise<void> {\r\n if (!this.remoteHandle) throw new Error('M8 SDK client is not connected')\r\n return this.remoteHandle.call('sendKeyPress', keys)\r\n }\r\n\r\n async sendKeyDown(keys: M8KeyName[]): Promise<void> {\r\n if (!this.remoteHandle) throw new Error('M8 SDK client is not connected')\r\n return this.remoteHandle.call('sendKeyDown', keys)\r\n }\r\n\r\n async sendKeyUp(): Promise<void> {\r\n if (!this.remoteHandle) throw new Error('M8 SDK client is not connected')\r\n return this.remoteHandle.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 if (!this.remoteHandle) throw new Error('M8 SDK client is not connected')\r\n const state = await this.remoteHandle.call('getState')\r\n this._state = state\r\n return state\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 disconnect(): void {\r\n this.connection?.close()\r\n this.connection = null\r\n this.remoteHandle = null\r\n this.localHandle = null\r\n this._isConnected = false\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 }\r\n\r\n getSemanticContext(): M8SemanticContext | null {\r\n return getSemanticContext(this._state)\r\n }\r\n\r\n describeContext(): string | null {\r\n const ctx = getSemanticContext(this._state)\r\n return ctx ? describeContext(ctx) : null\r\n }\r\n}\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"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/m8-view-context.json","../src/m8-parameter-views.json","../src/parameterViews.ts","../src/fxCommands.ts","../src/fxInsight.ts","../src/scaleInsight.ts","../src/viewContext.ts","../src/client.ts"],"sourcesContent":["{\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' ('--' = unset). 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). Rows below the 12 intervals (not column-mapped) hold: KEY (global root note for the default scale), 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 'OFF' ('--' = unset).\" },\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","{\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'; 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\": 20 },\n { \"id\": \"right\", \"labelX\": 18, \"labelWidth\": 4, \"x\": 22, \"width\": 11 }\n ],\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 \"LEN\": { \"type\": \"parameter\", \"format\": \"hex\", \"label\": \"Length (LEN)\", \"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 \"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.\" },\n \"C\": { \"type\": \"option\", \"format\": \"hexOption\", \"label\": \"Operator C (C)\", \"description\": \"Operator letter with optional shape.\" },\n \"D\": { \"type\": \"option\", \"format\": \"hexOption\", \"label\": \"Operator D (D)\", \"description\": \"Operator letter with optional shape.\" },\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\": \"hex\", \"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 \"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\": \"hexPair\", \"parts\": [\"CC number\", \"value\"], \"label\": \"Custom CC (CC:VAL)\", \"description\": \"A custom MIDI CC number with an optional default value, 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\": \"hexPair\", \"parts\": [\"CC number\", \"value\"], \"label\": \"Custom CC (CC:VAL)\", \"description\": \"One of up to 10 custom MIDI CC numbers per instrument with an optional default value, 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","/**\n * parameterViews.ts\n *\n * Maps the non-grid (\"parameter\") views - instrument, modulation, mixer, project,\n * effects, EQ, scope, system settings, file browsers - onto typed field cells,\n * using m8-parameter-views.json.\n *\n * These views are label/value screens, so a row is recognised from its text (its\n * label), not from a row number, and the cells of that row come from the schema. Views\n * whose labels change with the instrument or modulation type are read through two\n * label/value \"zones\" and the label is looked up in a vocabulary.\n *\n * Pure text in, cell definitions out; viewContext.ts turns them into parsed fields.\n */\n\nimport paramSchema from './m8-parameter-views.json'\nimport type { ColumnDef } from './viewContext'\n\nexport type ValueFormat = 'hex' | 'hexOption' | 'hexPair' | 'decimal' | 'decimalPair' | 'float' | 'text' | 'auto'\n\ninterface MatchDef {\n x: number\n text?: string\n regex?: string\n width?: number\n}\n\ninterface RowDef {\n id: string\n section?: string\n match: MatchDef[]\n cells: ColumnDef[]\n}\n\ninterface ZoneDef {\n id: string\n labelX: number\n labelWidth: number\n x: number\n width: number\n}\n\ntype VocabEntry = Pick<ColumnDef, 'type' | 'format' | 'label' | 'description' | 'parts' | 'options' | 'unit'>\n\ninterface InstrumentTypeDef {\n name: string\n description: string\n params: Record<string, VocabEntry>\n}\n\ninterface LayoutDef {\n extends?: string\n /** Pattern of the heading label that names the block a cell belongs to ('MOD\\\\d'): read upward in the cell's zone. */\n slotLabel?: string\n kind?: 'browser' | 'text'\n browserFor?: string\n fieldLabel?: string\n description?: string\n sections?: string[]\n rows?: RowDef[]\n zones?: ZoneDef[]\n vocabulary?: Record<string, VocabEntry>\n vocabularyFrom?: 'instrumentTypes'\n}\n\nexport type ParameterLayout = Omit<LayoutDef, 'extends'>\n\nconst rawViews = paramSchema.views as unknown as Record<string, LayoutDef>\nconst instrumentTypes = paramSchema.instrumentTypes as unknown as Record<string, InstrumentTypeDef>\nconst instrumentTypeNames = paramSchema.instrumentTypeNames as Record<string, string>\n\nconst layouts: Record<string, ParameterLayout> = {}\nfor (const [name, def] of Object.entries(rawViews)) {\n const { extends: parent, ...own } = def\n layouts[name] = parent ? { ...rawViews[parent], ...own } : own\n}\n\n/** Layout of a parameter view, or null when the view has none. */\nexport function getParameterLayout(viewName: string): ParameterLayout | null {\n return layouts[viewName] ?? null\n}\n\n// text helpers \n\nconst slice = (line: string, x: number, width: number): string => (x >= line.length ? '' : line.slice(x, x + width))\n\nfunction matches(cond: MatchDef, line: string): boolean {\n const width = cond.width ?? cond.text?.length ?? 1\n // The device's text stream is lower case ('tempo', 'b-4'); schema text is upper case.\n const text = slice(line, cond.x, width)\n if (cond.text != null) return text.toUpperCase() === cond.text.toUpperCase()\n if (cond.regex != null) return new RegExp(cond.regex, 'i').test(text)\n return false\n}\n\n// sections and instrument type \n\n/**\n * Section heading ('MODFX', 'DELAY', 'REVERB') the cursor line belongs to: the\n * nearest heading at column 0 on this line or above it. `linesUpward` starts with the\n * cursor line, then the lines above it.\n */\nexport function detectSection(layout: ParameterLayout, linesUpward: string[]): string | null {\n const sections = layout.sections\n if (!sections) return null\n for (const line of linesUpward) {\n const upper = line.toUpperCase()\n const hit = sections.find((s) => upper.startsWith(s))\n if (hit) return hit\n }\n return null\n}\n\n/**\n * Block heading ('MOD1'…'MOD4') a cell in `zoneId` belongs to: the nearest line at or\n * above the cursor with a heading label in that zone.\n */\nexport function detectSlot(layout: ParameterLayout, zoneId: string, linesUpward: string[]): string | null {\n const zone = layout.zones?.find((z) => z.id === zoneId)\n if (!layout.slotLabel || !zone) return null\n const pattern = new RegExp(`^${layout.slotLabel}$`, 'i')\n for (const line of linesUpward) {\n const label = slice(line, zone.labelX, zone.labelWidth).trim()\n if (pattern.test(label)) return label.toUpperCase()\n }\n return null\n}\n\n/** 'WAVSYNTH' → 'wavsynth', 'MIDI OUT' → 'midiout', unknown text → null. */\nexport function instrumentKeyFromText(text: string): string | null {\n const letters = text.toUpperCase().replace(/[^A-Z]/g, '')\n if (!letters) return null\n const prefix = Object.keys(instrumentTypeNames).find((p) => letters.startsWith(p))\n return prefix ? instrumentTypeNames[prefix] : null\n}\n\n/** Instrument type shown on the TYPE row of the Instrument View, read off the screen lines. */\nexport function instrumentTypeFromLines(lines: string[]): { text: string; key: string | null } | null {\n for (const line of lines) {\n if (line.toUpperCase().startsWith('TYPE')) {\n const text = slice(line, 8, 13).trim()\n return text ? { text, key: instrumentKeyFromText(text) } : null\n }\n }\n return null\n}\n\n/** Display name and description of an instrument type key ('wavsynth' → Wavsynth). */\nexport function getInstrumentType(key: string): { name: string; description: string } | null {\n const t = instrumentTypes[key]\n return t ? { name: t.name, description: t.description } : null\n}\n\n// vocabulary \n\nfunction lookupVocabulary(layout: ParameterLayout, label: string, instrumentKey: string | null): VocabEntry | null {\n // 'MOD1'..'MOD4' and 'CCA'..'CCJ' share one entry.\n const norm = label.replace(/^MOD\\d$/, 'MOD#').replace(/^CC[A-J]$/, 'CC#')\n if (layout.vocabulary) return layout.vocabulary[norm] ?? null\n if (layout.vocabularyFrom !== 'instrumentTypes') return null\n const order = instrumentKey ? [instrumentKey, 'common'] : ['common', ...Object.keys(instrumentTypes).filter((k) => k !== 'common')]\n for (const key of order) {\n const hit = instrumentTypes[key]?.params[norm]\n if (hit) return hit\n }\n return null\n}\n\nconst camel = (label: string): string =>\n label\n .toLowerCase()\n .replace(/[^a-z0-9]+(.)?/g, (_, c: string | undefined) => (c ? c.toUpperCase() : ''))\n .replace(/^[^a-z]+/, '')\n\nconst LABEL_SHAPE = /^[A-Z][A-Z0-9.:#/ -]*$/\n\n// reading a line \n\nexport interface ParameterLineOptions {\n section: string | null\n instrumentKey: string | null\n /** Text under the cursor; the entry of a file-browser row. */\n entryText?: string | null\n}\n\n/**\n * The cells present on `line`: the schema row whose label text matches, or else the\n * label/value zones. Empty when nothing on the line is recognised.\n */\nexport function readParameterCells(\n layout: ParameterLayout,\n line: string,\n opts: ParameterLineOptions,\n): { rowId: string | null; cells: ColumnDef[] } {\n if (layout.kind === 'browser') {\n const entry = opts.entryText?.trim() || line.trim()\n if (!entry) return { rowId: null, cells: [] }\n const x = Math.max(0, line.indexOf(entry))\n return {\n rowId: 'entry',\n cells: [{ x, width: entry.length, key: 'entry', type: 'fileEntry', format: 'text', label: 'File Browser Entry', meta: { browserFor: layout.browserFor } }],\n }\n }\n if (layout.kind === 'text') {\n if (!line.trim()) return { rowId: null, cells: [] }\n const x = Math.max(0, line.search(/\\S/))\n return {\n rowId: 'text',\n cells: [{ x, width: Math.max(12, line.length - x), key: 'name', type: 'name', format: 'text', label: layout.fieldLabel ?? 'Name', description: layout.description }],\n }\n }\n\n for (const row of layout.rows ?? []) {\n if (row.section && opts.section && row.section !== opts.section) continue\n if (row.match.every((m) => matches(m, line))) return { rowId: row.id, cells: row.cells }\n }\n\n const zones = layout.zones ?? []\n const cells: ColumnDef[] = []\n zones.forEach((zone, i) => {\n const label = slice(line, zone.labelX, zone.labelWidth).trim().toUpperCase()\n if (!LABEL_SHAPE.test(label)) return\n // The value ends where the next zone's label begins, when that label is present.\n const next = zones[i + 1]\n // A label is set off by a blank; letters spilling out of a long value ('16wt-osc:tube')\n // are not one.\n const nextIsLabel =\n next != null &&\n slice(line, next.labelX - 1, 1).trim() === '' &&\n LABEL_SHAPE.test(slice(line, next.labelX, next.labelWidth).trim().toUpperCase())\n const width = next && nextIsLabel ? Math.max(1, next.labelX - 1 - zone.x) : zone.width\n if (!slice(line, zone.x, width).trim()) return\n const vocab = lookupVocabulary(layout, label, opts.instrumentKey)\n // The same label can sit in both zones (two modulators side by side): keep keys unique.\n const base = camel(label) || zone.id\n const key = cells.some((c) => c.key === base) ? `${base}${zone.id[0].toUpperCase()}${zone.id.slice(1)}` : base\n cells.push({\n x: zone.x,\n width,\n key,\n type: vocab?.type ?? 'parameter',\n format: vocab?.format ?? 'auto',\n label: vocab?.label ?? label,\n description: vocab?.description,\n parts: vocab?.parts,\n options: vocab?.options,\n unit: vocab?.unit,\n meta: { paramLabel: label, zone: zone.id },\n })\n })\n return { rowId: cells.length ? 'params' : null, cells }\n}\n\n// values \n\nexport interface ParsedValue {\n hexValue: number | null\n /** Natural number of the value: the hex value, decimal integer or float. */\n numericValue: number | null\n /** Both numbers of an 'XX:YY' pair. */\n values: number[] | null\n /** The non-numeric part: option name, caption, name text. */\n text: string | null\n}\n\nconst NONE: ParsedValue = { hexValue: null, numericValue: null, values: null, text: null }\n\nfunction detectFormat(s: string): ValueFormat {\n if (/^[0-9A-F]{2}$/i.test(s)) return 'hex'\n if (/^[0-9A-F]{2}:[0-9A-F]{2}$/i.test(s)) return 'hexPair'\n if (/^[+-]?\\d+([.,]\\d+)?$/.test(s)) return 'float'\n if (/^[0-9A-F]{2}\\S/i.test(s)) return 'hexOption'\n return 'text'\n}\n\n/** Reads a cell's text according to its format. Empty text gives all-null. */\nexport function parseValue(raw: string, format: ValueFormat): ParsedValue {\n const s = raw.trim()\n if (!s) return NONE\n const fmt = format === 'auto' ? detectFormat(s) : format\n\n switch (fmt) {\n case 'hex': {\n const m = /^([0-9A-F]{2})/i.exec(s)\n const n = m ? parseInt(m[1], 16) : null\n return { hexValue: n, numericValue: n, values: null, text: m ? null : s }\n }\n case 'hexOption': {\n const m = /^([0-9A-F]{2})\\s?(.*)$/i.exec(s)\n if (!m) return { ...NONE, text: s }\n const n = parseInt(m[1], 16)\n return { hexValue: n, numericValue: n, values: null, text: m[2].trim() || null }\n }\n case 'hexPair': {\n const m = /^([0-9A-F]{2}):([0-9A-F]{2})/i.exec(s)\n if (!m) return { ...NONE, text: s }\n const values = [parseInt(m[1], 16), parseInt(m[2], 16)]\n return { hexValue: values[0], numericValue: values[0], values, text: null }\n }\n case 'decimal': {\n const n = parseInt(s, 10)\n return Number.isNaN(n) ? { ...NONE, text: s } : { hexValue: null, numericValue: n, values: null, text: null }\n }\n case 'decimalPair': {\n const m = /^(\\d+):(\\d+)/.exec(s)\n if (!m) return { ...NONE, text: s }\n const values = [parseInt(m[1], 10), parseInt(m[2], 10)]\n return { hexValue: null, numericValue: values[0], values, text: null }\n }\n case 'float': {\n const n = parseFloat(s.replace(',', '.'))\n return Number.isNaN(n) ? { ...NONE, text: s } : { hexValue: null, numericValue: n, values: null, text: null }\n }\n default:\n return { ...NONE, text: s }\n }\n}\n\n/** Description of the option currently selected ('CLIP' → \"Values above the maximum level are clipped.\"). */\nexport function describeOption(options: Record<string, string> | undefined, text: string | null): string | null {\n if (!options || !text) return null\n const t = text.toUpperCase().trim()\n if (options[t]) return options[t]\n const key = Object.keys(options).find((k) => k.length >= 3 && t.startsWith(k))\n return key ? options[key] : null\n}\n","/**\n * fxCommands.ts\n *\n * Lookup of FX commands (the 3-letter names in the FX columns) with what is known\n * about them: description, whether they are relative, and which instrument types\n * offer them. Data lives in m8-view-context.json (`fxCommands`, `instrumentFx`).\n */\n\nimport schema from './m8-view-context.json'\n\nexport type FxCategory = 'sequencer' | 'instrument' | 'mixer' | 'modulation'\n\nexport interface M8FxCommandInfo {\n description: string\n category: FxCategory\n /**\n * Relative instrument commands add to the parameter's current value (01-7F add,\n * 80-FF subtract) and accumulate until a note is triggered with RET or with an\n * instrument number in the I column.\n */\n relative: boolean\n /**\n * Instrument types that offer the command, when it is not offered by all of them\n * (`null` = every type, or not an instrument command). Only types whose command\n * list was read from a device are considered.\n */\n instrumentTypes: string[] | null\n /** Modulator slot 1-4 for envelope/LFO commands (EA1, LF3…). */\n slot: number | null\n}\n\ninterface FxEntry {\n description: string\n relative?: boolean\n}\n\ntype FxCommandMap = Record<string, FxEntry>\n\nconst fxCommands = schema.fxCommands as unknown as Record<FxCategory, FxCommandMap>\nconst instrumentTypes = schema.instrumentFx.types as Record<string, { verified: boolean; commands: string[] }>\n\nconst VERIFIED_TYPES = Object.entries(instrumentTypes)\n .filter(([, t]) => t.verified)\n .map(([name]) => name)\n\n/** Verified instrument types that offer `cmd`, or null when all of them do (or none does). */\nfunction typesOffering(cmd: string): string[] | null {\n const offering = VERIFIED_TYPES.filter((t) => instrumentTypes[t].commands.includes(cmd))\n return offering.length === 0 || offering.length === VERIFIED_TYPES.length ? null : offering\n}\n\n/**\n * Look up a 3-character FX command name. Modulator commands carry their slot\n * number as the third character (EA1, LF3…). Returns undefined for unknown commands.\n */\nexport function lookupFxCommand(cmd: string): M8FxCommandInfo | undefined {\n const upper = cmd.toUpperCase()\n\n for (const category of ['sequencer', 'instrument', 'mixer'] as const) {\n const entry = fxCommands[category][upper]\n if (entry) {\n return {\n description: entry.description,\n category,\n relative: entry.relative === true,\n instrumentTypes: category === 'instrument' ? typesOffering(upper) : null,\n slot: null,\n }\n }\n }\n\n const mod = /^([A-Z]{2})([1-4])$/.exec(upper)\n const entry = mod ? fxCommands.modulation[mod[1]] : undefined\n if (mod && entry) {\n return {\n description: entry.description.replace('modulator N', `modulator ${mod[2]}`),\n category: 'modulation',\n relative: entry.relative === true,\n instrumentTypes: null,\n slot: Number(mod[2]),\n }\n }\n return undefined\n}\n\n/**\n * True for relative instrument commands: values 01-7F add to the parameter, 80-FF\n * subtract (see \"Relative and Absolute FX Commands\" in the manual).\n */\nexport function isRelativeFx(cmd: string): boolean {\n return lookupFxCommand(cmd)?.relative === true\n}\n\nexport interface M8InstrumentFxList {\n /** Type key as used in the schema: sampler, macrosynth, fmsynth, hypersynth, midiout. */\n type: string\n /** Read from a real device (false = taken from documentation). */\n verified: boolean\n commands: string[]\n}\n\n/** Instrument command names offered for an instrument type, or null when unknown. */\nexport function getInstrumentFxCommands(type: string): M8InstrumentFxList | null {\n const t = instrumentTypes[type.toLowerCase()]\n return t ? { type: type.toLowerCase(), verified: t.verified, commands: [...t.commands] } : null\n}\n\n/** What the manual says about relative commands (for tooltips and descriptions). */\nexport const RELATIVE_FX_RULE: string = schema.instrumentFx.relativeCommands.description\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 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 and CHA mean something different there. */\n inTable?: boolean\n}\n\nconst KEY_NAMES = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B']\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 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 return `table jumps to position ${y.toString(16).toUpperCase()}`\n case 'HOP':\n if (ctx.inTable) return `jumps to row ${y.toString(16).toUpperCase()}, ${x} time${x === 1 ? '' : 's'}`\n return value === 0xff ? 'stops this track' : `jumps to row ${y.toString(16).toUpperCase()} of the next phrase`\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 const eighths = signed8(value)\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 if (value === 0) return 'table advances on each instrument trigger'\n if (value <= 0xfb) return `table advances every ${ticks(value)}`\n return (\n { 0xfc: 'octave map: playing octave picks the table row', 0xfd: 'velocity map', 0xfe: 'note map', 0xff: 'table advances at 200 Hz' } as Record<\n number,\n string\n >\n )[value] ?? null\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 const st = (states[src.cmd] ??= { cmd: src.cmd, total: 0, count: 0, resetStep, resetBy, viaRep: false })\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","/**\n * scaleInsight.ts\n *\n * Interprets the Scale View: which of the 12 note intervals are enabled, what\n * well-known scale that shape spells, and what a given row means musically.\n */\n\n/** Interval row labels in screen order. */\nexport const SCALE_INTERVALS = ['C', 'C#', 'D', 'D#', 'E', 'F', 'F#', 'G', 'G#', 'A', 'A#', 'B'] as const\n\nexport interface M8ScaleNote {\n /** Row label, e.g. 'D#'. */\n interval: string\n /** Semitones above the first row ('C'), 0-11. */\n semitone: number\n /** true = ON, false = OFF, null = unset ('--'). */\n enabled: boolean | null\n /** Detune in semitones (-24..+24), null when unreadable. */\n offset: number | null\n}\n\nexport interface M8ScaleInfo {\n /** The 12 rows, in screen order. */\n notes: M8ScaleNote[]\n /** Labels of the enabled intervals, e.g. ['C', 'D', 'E', …]. */\n enabled: string[]\n /** Well-known scales this set of notes spells, most natural reading first. */\n shapes: string[]\n /** Intervals carrying a non-zero detune. */\n detuned: { interval: string; offset: number }[]\n /** One-line summary, e.g. \"C D E F G A B — Major (Ionian)\". */\n summary: string\n}\n\nconst SCALES: { name: string; steps: number[] }[] = [\n { name: 'Major (Ionian)', steps: [0, 2, 4, 5, 7, 9, 11] },\n { name: 'Natural minor (Aeolian)', steps: [0, 2, 3, 5, 7, 8, 10] },\n { name: 'Harmonic minor', steps: [0, 2, 3, 5, 7, 8, 11] },\n { name: 'Melodic minor', steps: [0, 2, 3, 5, 7, 9, 11] },\n { name: 'Dorian', steps: [0, 2, 3, 5, 7, 9, 10] },\n { name: 'Phrygian', steps: [0, 1, 3, 5, 7, 8, 10] },\n { name: 'Lydian', steps: [0, 2, 4, 6, 7, 9, 11] },\n { name: 'Mixolydian', steps: [0, 2, 4, 5, 7, 9, 10] },\n { name: 'Locrian', steps: [0, 1, 3, 5, 6, 8, 10] },\n { name: 'Phrygian dominant', steps: [0, 1, 4, 5, 7, 8, 10] },\n { name: 'Double harmonic', steps: [0, 1, 4, 5, 7, 8, 11] },\n { name: 'Major pentatonic', steps: [0, 2, 4, 7, 9] },\n { name: 'Minor pentatonic', steps: [0, 3, 5, 7, 10] },\n { name: 'Blues', steps: [0, 3, 5, 6, 7, 10] },\n { name: 'Whole tone', steps: [0, 2, 4, 6, 8, 10] },\n { name: 'Diminished (whole-half)', steps: [0, 2, 3, 5, 6, 8, 9, 11] },\n { name: 'Diminished (half-whole)', steps: [0, 1, 3, 4, 6, 7, 9, 10] },\n { name: 'Chromatic', steps: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] },\n]\n\nconst INTERVAL_NAMES = [\n 'the root',\n 'a minor 2nd',\n 'a major 2nd',\n 'a minor 3rd',\n 'a major 3rd',\n 'a perfect 4th',\n 'a tritone',\n 'a perfect 5th',\n 'a minor 6th',\n 'a major 6th',\n 'a minor 7th',\n 'a major 7th',\n]\n\n/** 'C-' → 'C', 'C#' → 'C#'. Returns null when the text is not an interval label. */\nexport function normalizeInterval(raw: string): string | null {\n const s = raw.replace(/[-\\s]/g, '').toUpperCase()\n return (SCALE_INTERVALS as readonly string[]).includes(s) ? s : null\n}\n\n/** \"a major 3rd (+4 semitones above C)\" for a given interval label. */\nexport function describeInterval(interval: string): string {\n const semitone = (SCALE_INTERVALS as readonly string[]).indexOf(interval)\n if (semitone <= 0) return semitone === 0 ? 'the first row (the scale root)' : interval\n return `${INTERVAL_NAMES[semitone]} (+${semitone} semitones above C)`\n}\n\n/** Names of well-known scales whose notes equal the enabled set, C-rooted reading first. */\nexport function matchScaleShapes(enabledSemitones: number[]): string[] {\n const set = new Set(enabledSemitones)\n if (set.size === 0) return []\n const hits: { root: number; name: string }[] = []\n for (const scale of SCALES) {\n if (scale.steps.length !== set.size) continue\n for (let root = 0; root < 12; root += 1) {\n if (scale.steps.every((s) => set.has((s + root) % 12))) hits.push({ root, name: scale.name })\n }\n }\n // Tonic on the first row ('C') is the natural reading; the rest are modes of the same notes.\n hits.sort((a, b) => a.root - b.root)\n return hits.map((h) => (h.root === 0 ? h.name : `${h.name} from ${SCALE_INTERVALS[h.root]}`))\n}\n\nexport function analyzeScale(notes: M8ScaleNote[]): M8ScaleInfo {\n const enabledNotes = notes.filter((n) => n.enabled === true)\n const enabled = enabledNotes.map((n) => n.interval)\n const shapes = matchScaleShapes(enabledNotes.map((n) => n.semitone))\n const detuned = notes\n .filter((n) => n.enabled === true && n.offset != null && n.offset !== 0)\n .map((n) => ({ interval: n.interval, offset: n.offset as number }))\n\n const summary = scaleSummaryParts({ enabled, shapes, detuned }).join(' — ')\n return { notes, enabled, shapes, detuned, summary }\n}\n\n/**\n * The two halves of the summary: the enabled notes (\"C D E F G A B\"), then the scale\n * they spell with any detune (\"Major (Ionian); detuned: E −0.50 st\"), when notes are enabled.\n */\nexport function scaleSummaryParts(info: Pick<M8ScaleInfo, 'enabled' | 'shapes' | 'detuned'>): string[] {\n const { enabled, shapes, detuned } = info\n if (!enabled.length) return ['no notes enabled']\n let shape = shapes[0] ?? `${enabled.length} notes, custom scale`\n if (detuned.length) shape += `; detuned: ${detuned.map((d) => `${d.interval} ${formatOffset(d.offset)}`).join(', ')}`\n return [enabled.join(' '), shape]\n}\n\n/** '+0.50 st' style detune text. */\nexport function formatOffset(offset: number): string {\n const sign = offset > 0 ? '+' : offset < 0 ? '−' : ''\n return `${sign}${Math.abs(offset).toFixed(2)} st`\n}\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 formatFxValue,\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/**\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 */\n/**\n * Extracts the item ID and unsaved flag from a raw M8 screen title.\n * Titles follow the pattern ' VIEWNAME ID* ' where ID is 1–2 hex chars\n * and '*' is an optional unsaved-changes marker.\n * Returns { viewId: null, isUnsaved: false } for views without an item number.\n */\nfunction extractViewId(rawTitle: string | null): { viewId: string | null; isUnsaved: boolean } {\n if (!rawTitle) return { viewId: null, isUnsaved: false }\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 isUnsaved = trimmed.endsWith('*')\n const withoutStar = isUnsaved ? trimmed.slice(0, -1).trimEnd() : trimmed\n const parts = withoutStar.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 }\n}\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/**\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 enabled: en === 'ON' ? true : en === 'OFF' ? 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}: ${formatFieldValue(f)}`])\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)\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/**\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 // REP / RTO are explained entirely by the lane; other commands add their value meaning.\n if (name === 'REP' || name === 'RTO') {\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 const shown = f.type === 'fxCommand' ? `value ${formatFxValue(name, val.hexValue)}: ` : ''\n const described = describeFxValue(name, val.hexValue, {\n note: row.fields.note?.isEmpty ? null : row.fields.note?.rawValue,\n inTable: ctx.viewName === 'table',\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\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(':')} (${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.\nimport { ChildHandshake, DebugMessenger, WindowMessenger } from 'post-me'\n// @ts-expect-error - post-me types are incomplete for the generic handshake signatures used here.\nimport type { Connection, LocalHandle, RemoteHandle } from 'post-me'\nimport type { M8ClientEvents, M8ClientMethods, M8HostEvents, M8HostMethods, M8KeyName, M8Screen, M8SdkConfig, M8State } from './types'\nimport { getSemanticContext, describeContext, describeContextParts } from './viewContext'\nimport type { M8DescriptionPart, M8SemanticContext } from './viewContext'\n\nexport type { CursorPos, CursorRect, M8KeyName, M8Screen, M8State, RGB, SystemInfos } from './types'\nexport type { M8DescriptionPart, M8SemanticContext } from './viewContext'\n\n/**\n * Everything the SDK knows about the moment: raw state, semantic context, its\n * human-readable description and the whole screen. Plain JSON, safe to persist.\n */\nexport interface M8Snapshot {\n capturedAt: number\n state: Pick<M8State, 'viewName' | 'viewTitle' | 'cursorPos' | 'selectionMode' | 'textUnderCursor' | 'currentLine' | 'deviceModel'>\n semantic: M8SemanticContext | null\n /** describeContext() output, e.g. \"Note (N): Note C-4 — Phrase View row 00\". */\n description: string | null\n /** The same description as tagged parts (describeContextParts()). */\n descriptionParts: M8DescriptionPart[] | null\n screen: M8Screen\n}\n\nexport interface M8Client {\n readonly state: M8State\n readonly isConnected: boolean\n navigateToView(viewName: string): Promise<boolean>\n navigateTo(x: number, y: number): Promise<void>\n setValueToHex(targetHex: number): Promise<boolean>\n setValueToInt(targetInt: number): Promise<boolean>\n setValueFloat(targetFloat: number): Promise<boolean>\n setNote(noteString: string): Promise<boolean>\n setValueToString(targetString: string, exact?: boolean, searchInCurrentLine?: boolean): Promise<boolean>\n browseFile(targetText: string, exact?: boolean): Promise<boolean>\n sendKeyPress(keys: M8KeyName[]): Promise<void>\n sendKeyDown(keys: M8KeyName[]): Promise<void>\n sendKeyUp(): Promise<void>\n getState(): M8State\n fetchState(): Promise<M8State>\n /**\n * Whole screen as text rows, read directly from the M8 character stream.\n * No cursor movement involved, so it is instant and side-effect free.\n */\n getScreen(): Promise<M8Screen>\n /** Fresh state + semantic context + description + whole screen in one call. */\n captureSnapshot(): Promise<M8Snapshot>\n onStateChange(callback: (state: M8State) => void): () => void\n onViewChange(callback: (viewName: string | null, viewTitle: string | null) => void): () => void\n onCursorMove(callback: (pos: M8State['cursorPos'], rect: M8State['cursorRect'], selectionMode: boolean) => void): () => void\n onTextUpdate(callback: (textUnderCursor: string | null, currentLine: string | null) => void): () => void\n onKeyPress(callback: (keys: number) => void): () => void\n disconnect(): void\n /**\n * Derives semantic context from the current state: view, row, and the typed\n * field under the cursor. Works for the five grid views (song, chain, phrase,\n * table, groove). Returns null when no view is active.\n */\n getSemanticContext(): M8SemanticContext | null\n /**\n * Returns a short human-readable description of what the cursor is on,\n * e.g. \"Track 3: 0A — Track 3 — Song View row 01\".\n *\n * It also reads the cursor against its surroundings when that changes the meaning:\n * an FX cell under a running REP says which command is repeating and where it has\n * got to, FX values are explained (ARP 37 → \"+3 and +7 semitones\"), and the Scale\n * View names the scale the enabled notes spell. The surroundings come from a copy of\n * the screen the client keeps fresh in the background; call getScreen() (or await\n * captureSnapshot()) first if you need it guaranteed current.\n */\n describeContext(): string | null\n /**\n * The description as ordered, tagged parts ({ kind: 'field' | 'meaning' | 'location' | …, text }),\n * for consumers that want to keep, drop, reorder or style some of them.\n */\n describeContextParts(): M8DescriptionPart[] | null\n}\n\nconst getDefaultState = (): M8State => ({\n viewName: null,\n viewTitle: null,\n minimapKey: null,\n cursorPos: null,\n cursorRect: null,\n selectionMode: false,\n highlightColor: null,\n titleColor: null,\n backgroundColor: null,\n textUnderCursor: null,\n currentLine: null,\n deviceModel: null,\n fontMode: null,\n systemInfo: null,\n macroRunning: false,\n})\n\nclass M8ClientImpl implements M8Client {\n private connection: Connection<M8ClientMethods, M8HostEvents, M8HostMethods, M8ClientEvents> | null = null\n private remoteHandle: RemoteHandle<M8HostMethods, M8HostEvents> | null = null\n private localHandle: LocalHandle<M8ClientMethods, M8ClientEvents> | null = null\n private readonly stateCallbacks = new Set<(state: M8State) => void>()\n private readonly viewCallbacks = new Set<(viewName: string | null, viewTitle: string | null) => void>()\n private readonly cursorCallbacks = new Set<(pos: M8State['cursorPos'], rect: M8State['cursorRect'], selectionMode: boolean) => void>()\n private readonly textCallbacks = new Set<(textUnderCursor: string | null, currentLine: string | null) => void>()\n private readonly keyCallbacks = new Set<(keys: number) => void>()\n private _state: M8State = getDefaultState()\n private _screen: M8Screen | null = null\n private screenRefreshing = false\n private screenDirty = false\n private _isConnected = false\n private readonly config: M8SdkConfig\n\n constructor(config: M8SdkConfig = {}) {\n this.config = config\n }\n\n get state(): M8State {\n return this._state\n }\n\n get isConnected(): boolean {\n return this._isConnected\n }\n\n async connect(): Promise<void> {\n if (this.connection) {\n return\n }\n\n let messenger = new WindowMessenger({\n localWindow: window,\n remoteWindow: window.parent,\n remoteOrigin: '*',\n })\n\n if (this.config.debug) {\n messenger = DebugMessenger(messenger, (msg: string, ...args: unknown[]) => {\n console.log('[M8 SDK]', msg, ...args)\n })\n }\n\n const connection = await ChildHandshake<M8ClientMethods, M8HostEvents, M8HostMethods, M8ClientEvents>(messenger, {\n ping: async () => 'pong',\n })\n\n this.connection = connection\n this.remoteHandle = connection.remoteHandle()\n this.localHandle = connection.localHandle()\n this._isConnected = true\n\n this.remoteHandle.addEventListener('stateChanged', (state: M8State) => {\n this._state = state\n this.refreshScreen()\n this.stateCallbacks.forEach((cb) => {\n cb(state)\n })\n })\n\n this.remoteHandle.addEventListener('viewChanged', ({ viewName, viewTitle }: { viewName: string | null; viewTitle: string | null }) => {\n this._state = { ...this._state, viewName, viewTitle }\n this.refreshScreen()\n this.viewCallbacks.forEach((cb) => {\n cb(viewName, viewTitle)\n })\n })\n\n this.remoteHandle.addEventListener('cursorMoved', ({ pos, rect, selectionMode }: { pos: M8State['cursorPos']; rect: M8State['cursorRect']; selectionMode: boolean }) => {\n this._state = { ...this._state, cursorPos: pos, cursorRect: rect, selectionMode }\n this.refreshScreen()\n this.cursorCallbacks.forEach((cb) => {\n cb(pos, rect, selectionMode)\n })\n })\n\n this.remoteHandle.addEventListener('textUpdated', ({ textUnderCursor, currentLine }: { textUnderCursor: string | null; currentLine: string | null }) => {\n this._state = { ...this._state, textUnderCursor, currentLine }\n this.refreshScreen()\n this.textCallbacks.forEach((cb) => {\n cb(textUnderCursor, currentLine)\n })\n })\n\n this.remoteHandle.addEventListener('keyPressed', ({ keys }: { keys: number }) => {\n this.keyCallbacks.forEach((cb) => {\n cb(keys)\n })\n })\n\n await this.fetchState()\n this.refreshScreen()\n this.localHandle.emit('ready', undefined)\n }\n\n /**\n * Keeps the cached screen close to the state without a round-trip per event: at most\n * one request in flight, and one more queued if events arrived meanwhile.\n */\n private refreshScreen(): void {\n if (!this.remoteHandle) return\n if (this.screenRefreshing) {\n this.screenDirty = true\n return\n }\n this.screenRefreshing = true\n this.remoteHandle\n .call('getScreen')\n .then((screen: M8Screen) => {\n this._screen = screen\n })\n .catch(() => {\n // Descriptions simply fall back to the cursor row alone.\n })\n .finally(() => {\n this.screenRefreshing = false\n if (this.screenDirty) {\n this.screenDirty = false\n this.refreshScreen()\n }\n })\n }\n\n async navigateToView(viewName: string): Promise<boolean> {\n if (!this.remoteHandle) throw new Error('M8 SDK client is not connected')\n return this.remoteHandle.call('navigateToView', viewName)\n }\n\n async navigateTo(x: number, y: number): Promise<void> {\n if (!this.remoteHandle) throw new Error('M8 SDK client is not connected')\n return this.remoteHandle.call('navigateTo', x, y)\n }\n\n async setValueToHex(targetHex: number): Promise<boolean> {\n if (!this.remoteHandle) throw new Error('M8 SDK client is not connected')\n return this.remoteHandle.call('setValueToHex', targetHex)\n }\n\n async setValueToInt(targetInt: number): Promise<boolean> {\n if (!this.remoteHandle) throw new Error('M8 SDK client is not connected')\n return this.remoteHandle.call('setValueToInt', targetInt)\n }\n\n async setValueFloat(targetFloat: number): Promise<boolean> {\n if (!this.remoteHandle) throw new Error('M8 SDK client is not connected')\n return this.remoteHandle.call('setValueFloat', targetFloat)\n }\n\n async setNote(noteString: string): Promise<boolean> {\n if (!this.remoteHandle) throw new Error('M8 SDK client is not connected')\n return this.remoteHandle.call('setNote', noteString)\n }\n\n async setValueToString(targetString: string, exact = true, searchInCurrentLine = false): Promise<boolean> {\n if (!this.remoteHandle) throw new Error('M8 SDK client is not connected')\n return this.remoteHandle.call('setValueToString', targetString, exact, searchInCurrentLine)\n }\n\n async browseFile(targetText: string, exact = true): Promise<boolean> {\n if (!this.remoteHandle) throw new Error('M8 SDK client is not connected')\n return this.remoteHandle.call('browseFile', targetText, exact)\n }\n\n async sendKeyPress(keys: M8KeyName[]): Promise<void> {\n if (!this.remoteHandle) throw new Error('M8 SDK client is not connected')\n return this.remoteHandle.call('sendKeyPress', keys)\n }\n\n async sendKeyDown(keys: M8KeyName[]): Promise<void> {\n if (!this.remoteHandle) throw new Error('M8 SDK client is not connected')\n return this.remoteHandle.call('sendKeyDown', keys)\n }\n\n async sendKeyUp(): Promise<void> {\n if (!this.remoteHandle) throw new Error('M8 SDK client is not connected')\n return this.remoteHandle.call('sendKeyUp')\n }\n\n getState(): M8State {\n return this._state\n }\n\n async fetchState(): Promise<M8State> {\n if (!this.remoteHandle) throw new Error('M8 SDK client is not connected')\n const state = await this.remoteHandle.call('getState')\n this._state = state\n return state\n }\n\n async getScreen(): Promise<M8Screen> {\n if (!this.remoteHandle) throw new Error('M8 SDK client is not connected')\n const screen = await this.remoteHandle.call('getScreen')\n this._screen = screen\n return screen\n }\n\n async captureSnapshot(): Promise<M8Snapshot> {\n const [state, screen] = await Promise.all([this.fetchState(), this.getScreen()])\n const semantic = getSemanticContext(state, screen)\n return {\n capturedAt: Date.now(),\n state: {\n viewName: state.viewName,\n viewTitle: state.viewTitle,\n cursorPos: state.cursorPos,\n selectionMode: state.selectionMode,\n textUnderCursor: state.textUnderCursor,\n currentLine: state.currentLine,\n deviceModel: state.deviceModel,\n },\n semantic,\n description: semantic ? describeContext(semantic) : null,\n descriptionParts: semantic ? describeContextParts(semantic) : null,\n screen,\n }\n }\n\n onStateChange(callback: (state: M8State) => void): () => void {\n this.stateCallbacks.add(callback)\n return () => this.stateCallbacks.delete(callback)\n }\n\n onViewChange(callback: (viewName: string | null, viewTitle: string | null) => void): () => void {\n this.viewCallbacks.add(callback)\n return () => this.viewCallbacks.delete(callback)\n }\n\n onCursorMove(callback: (pos: M8State['cursorPos'], rect: M8State['cursorRect'], selectionMode: boolean) => void): () => void {\n this.cursorCallbacks.add(callback)\n return () => this.cursorCallbacks.delete(callback)\n }\n\n onTextUpdate(callback: (textUnderCursor: string | null, currentLine: string | null) => void): () => void {\n this.textCallbacks.add(callback)\n return () => this.textCallbacks.delete(callback)\n }\n\n onKeyPress(callback: (keys: number) => void): () => void {\n this.keyCallbacks.add(callback)\n return () => this.keyCallbacks.delete(callback)\n }\n\n disconnect(): void {\n this.connection?.close()\n this.connection = null\n this.remoteHandle = null\n this.localHandle = null\n this._isConnected = false\n this._screen = null\n this.stateCallbacks.clear()\n this.viewCallbacks.clear()\n this.cursorCallbacks.clear()\n this.textCallbacks.clear()\n this.keyCallbacks.clear()\n }\n\n getSemanticContext(): M8SemanticContext | null {\n return getSemanticContext(this._state, this._screen)\n }\n\n describeContext(): string | null {\n const ctx = getSemanticContext(this._state, this._screen)\n return ctx ? describeContext(ctx) : null\n }\n\n describeContextParts(): M8DescriptionPart[] | null {\n const ctx = getSemanticContext(this._state, this._screen)\n return ctx ? describeContextParts(ctx) : null\n }\n}\n\nexport async function createM8Client(config: M8SdkConfig = {}): Promise<M8Client> {\n const client = new M8ClientImpl(config)\n await client.connect()\n return client\n}\n\nexport function createM8ClientSync(config: M8SdkConfig = {}): { client: M8Client; connect: () => Promise<void> } {\n const client = new M8ClientImpl(config)\n return {\n client,\n connect: async () => {\n await client.connect()\n },\n }\n}\n\nexport default {\n createM8Client,\n createM8ClientSync,\n}\n"],"mappings":"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"}