npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@yam8d/m8-sdk

v0.2.0

Published

Client SDK for iframe applications that communicate with yam8d M8 host.

Downloads

205

Readme

@yam8d/m8-sdk

Client SDK for iframe applications that communicate with an M8 tracker host via yam8d.

License: MIT — © kronsilds


Table of contents


Install

npm install @yam8d/m8-sdk

For local development before publishing:

npm install ../yam8d/packages/m8-sdk

The app must run inside the yam8d host iframe. Opening it directly in a standalone browser tab will fail the SDK handshake.


Quick start

import { createM8Client } from '@yam8d/m8-sdk'

const m8 = await createM8Client()

console.log(m8.state.viewName) // e.g. 'song'

await m8.sendKeyPress(['right'])

const off = m8.onStateChange((state) => {
  console.log(state.cursorPos)
})

off()         // unsubscribe
m8.disconnect()

Factory functions

createM8Client(config?): Promise<M8Client>

Creates and connects an M8Client in one step using top-level await.

const m8 = await createM8Client({ debug: true })

createM8ClientSync(config?): { client: M8Client; connect: () => Promise<void> }

Returns a client instance and a deferred connect() call. Useful in React or other frameworks where top-level await is inconvenient.

import { createM8ClientSync } from '@yam8d/m8-sdk'

const { client: m8, connect } = createM8ClientSync()

useEffect(() => {
  connect().then(() => console.log('connected'))
  return () => m8.disconnect()
}, [])

M8Client

Properties

| Property | Type | Description | |---|---|---| | state | M8State | Last known full device state (updated on every host event). | | isConnected | boolean | Whether the SDK handshake has completed. |


Navigation

navigateToView(viewName: string): Promise<boolean>

Asks the host to navigate to a named view (e.g. 'song', 'phrase'). Returns true on success.

await m8.navigateToView('phrase')

navigateTo(x: number, y: number): Promise<void>

Moves the cursor to the given character-grid position.

await m8.navigateTo(3, 5)

Value setters

All setters act on the field currently under the cursor.

setValueToHex(targetHex: number): Promise<boolean>

Sets the current field to the given hex integer (e.g. 0x0A). Returns true if the value was successfully applied.

setValueToInt(targetInt: number): Promise<boolean>

Sets the current field to the given decimal integer. Returns true on success.

setNote(noteString: string): Promise<boolean>

Sets a note field using M8 note format, e.g. 'C-4', 'A#3', 'OFF'. Returns true on success.

setValueToString(targetString: string, exact?: boolean, searchInCurrentLine?: boolean): Promise<boolean>

Sets the current field by navigating to match targetString.

  • exact (default true): require an exact string match.
  • searchInCurrentLine (default false): restrict the search to the current row.

Returns true if the target was reached.

browseFile(targetText: string, exact?: boolean): Promise<boolean>

Navigates a file-browse dialog to an entry matching targetText. Returns true on success.


Key input

sendKeyPress(keys: M8KeyName[]): Promise<void>

Sends a simultaneous key press + release for the given keys.

await m8.sendKeyPress(['shift', 'play'])

sendKeyDown(keys: M8KeyName[]): Promise<void>

Holds the given keys down without releasing. Pair with sendKeyUp().

await m8.sendKeyDown(['shift'])
await m8.sendKeyPress(['right'])
await m8.sendKeyUp()

sendKeyUp(): Promise<void>

Releases all currently held keys.


State

getState(): M8State

Returns the in-memory cached state synchronously. Same as reading m8.state.

fetchState(): Promise<M8State>

Requests a fresh state snapshot from the host and updates the internal cache.

const state = await m8.fetchState()

Event subscriptions

Every on* method returns an unsubscribe function — call it to stop listening.

onStateChange(callback: (state: M8State) => void): () => void

Fires whenever any part of the device state changes.

const off = m8.onStateChange((state) => {
  console.log(state.viewName, state.cursorPos)
})
off() // unsubscribe

onViewChange(callback: (viewName: string | null, viewTitle: string | null) => void): () => void

Fires when the active view changes.

m8.onViewChange((name, title) => {
  console.log(`Switched to ${name}: ${title}`)
})

onCursorMove(callback: (pos: CursorPos | null, rect: CursorRect | null, selectionMode: boolean) => void): () => void

Fires when the cursor position or selection mode changes.

m8.onCursorMove((pos, rect, sel) => {
  console.log(`Cursor at (${pos?.x}, ${pos?.y}), selecting: ${sel}`)
})

onTextUpdate(callback: (textUnderCursor: string | null, currentLine: string | null) => void): () => void

Fires when the text under the cursor or the full current line changes.

m8.onTextUpdate((text, line) => {
  console.log('Under cursor:', text)
  console.log('Full line:', line)
})

onKeyPress(callback: (keys: number) => void): () => void

Fires when a physical key event occurs on the device. keys is a bitmask of active keys.


Semantic context on the client

getSemanticContext(): M8SemanticContext | null

Parses m8.state into structured field data. Returns null when no view is active.

const ctx = m8.getSemanticContext()

if (ctx?.activeField?.type === 'chainRef') {
  const track = ctx.activeField.meta?.trackIndex  // 1–8
  const chain = ctx.activeField.hexValue           // 0–255 or null if empty
  const row   = ctx.row?.rowIndex
}

describeContext(): string | null

Returns a human-readable one-liner, e.g.:

  • "Track 3 — Chain 0A — Song View row 02"
  • "Note A#4 — Step 7 (Phrase View)"
  • "FX1 Command: KIL (Kill Note) — Step 0 — Phrase View row 03"

Returns null when not connected or no view is active.


Lifecycle

disconnect(): void

Closes the host connection and clears all event listeners and internal state.


Standalone semantic context functions

These are re-exported from @yam8d/m8-sdk for use without an M8Client instance.

getSemanticContext(state: M8State): M8SemanticContext | null

Parses an M8State snapshot into a semantic context. Returns null if state.viewName is not set.

import { getSemanticContext } from '@yam8d/m8-sdk'

const ctx = getSemanticContext(state)
if (ctx?.isGridView && ctx.row) {
  const note = ctx.row.fields['note']
  console.log(note?.rawValue) // e.g. 'C-4'
}

describeContext(ctx: M8SemanticContext): string

Returns a human-readable summary of the context (see examples above).

formatFieldValue(field: M8ParsedField): string

Formats a single parsed field as a display string:

| Type | Example output | |---|---| | note | "Note C-4", "Note Off" | | transpose | "+3 st", "-12 st", "±0 st" | | velocity | "57h (87 / 127 max)" | | fxValue | "1Ah (dec 26)" | | ticks | "06h (6 ticks)", "00 (skip step)" | | chainRef / phraseRef / fxCommand / volume | raw value, e.g. "0A", "KIL" | | empty cell | "(empty)" |

lookupFxCommand(cmd: string): { description: string } | undefined

Looks up a 3-character FX command name (case-insensitive) across all categories (sequencer, instrument, mixer).

import { lookupFxCommand } from '@yam8d/m8-sdk'

const info = lookupFxCommand('KIL')
// → { description: 'Kill: stops the playing instrument after XX ticks.' }

Types reference

M8State

The complete snapshot of the device at a point in time.

interface M8State {
  viewName: string | null          // active view key, e.g. 'song', 'phrase'
  viewTitle: string | null         // raw title string from screen, e.g. ' SONG   '
  minimapKey: string | null        // current minimap position key
  cursorPos: CursorPos | null      // character-grid position of the cursor
  cursorRect: CursorRect | null    // pixel rectangle of the cursor overlay
  selectionMode: boolean           // true when a selection is active
  highlightColor: RGB | null       // cursor highlight colour
  titleColor: RGB | null           // view title text colour
  backgroundColor: RGB | null      // screen background colour
  textUnderCursor: string | null   // characters covered by the cursor rect
  currentLine: string | null       // full text of the row at cursorPos.y
  deviceModel: string | null       // e.g. 'Headless', 'Model:02'
  fontMode: number | null          // 0=standard, 1=bold, 2=large
  systemInfo: SystemInfos | null   // device-reported system properties
  macroRunning: boolean            // true while a macro is executing
  macroCurrentStep?: number        // current macro step index
  macroSequenceLength?: number     // total steps in the running macro
}

CursorPos / CursorRect / RGB / SystemInfos

interface CursorPos  { x: number; y: number }
interface CursorRect { x: number; y: number; w: number; h: number }
interface RGB        { r: number; g: number; b: number }
interface SystemInfos { [key: string]: string | number | boolean | undefined }

M8SemanticContext

interface M8SemanticContext {
  viewName: string               // e.g. 'song', 'phrase', 'instrumentpool'
  viewTitle: string | null       // e.g. 'Song View'
  viewDescription: string | null // long description from schema
  viewId: string | null          // item ID parsed from screen title, e.g. 'F2', '03'; null for song/instrumentpool
  isUnsaved: boolean             // true when the title carries a '*' suffix (unsaved changes)
  isGridView: boolean            // true for song/chain/phrase/table/groove/instrumentpool
  row: M8ParsedRow | null        // all columns on the line at cursorPos.y
  activeField: M8ActiveField | null  // the column currently under the cursor
}

M8ParsedRow

interface M8ParsedRow {
  rowIndex: number | null                   // hex row index from leftmost chars; null if unparseable
  rowKey: string                            // schema key, e.g. 'songRow', 'step', 'chainPos'
  fields: Record<string, M8ParsedField>     // column key → parsed field
}

M8ActiveField / M8ParsedField

M8ActiveField extends M8ParsedField with two extra convenience fields.

interface M8ParsedField {
  key: string                        // schema key, e.g. 'fx1cmd', 'track3', 'note'
  label: string                      // human label, e.g. 'FX1 Command', 'Track 3'
  type: M8FieldType
  rawValue: string                   // trimmed text sliced from currentLine
  hexValue: number | null            // parsed integer; null for empty cells and text types
  isEmpty: boolean                   // true when '--', '---', '---00', etc.
  meta?: Record<string, unknown>     // view-specific extras (e.g. { trackIndex: 3 })
}

interface M8ActiveField extends M8ParsedField {
  viewName: string         // repeated for convenience
  rowIndex: number | null  // row index of the line containing this field
}

M8FieldType

type M8FieldType =
  | 'chainRef'       // chain number hex 00–FF; '--' = empty
  | 'phraseRef'      // phrase number hex 00–FF; '--' = empty
  | 'note'           // note name e.g. 'C-4', 'A#3'; '---' = empty; 'OFF' = note-off
  | 'velocity'       // velocity 00–7F hex; '--' = inherit
  | 'instrumentRef'  // instrument slot 00–7F hex; '--' = keep current
  | 'fxCommand'      // 3-char command name e.g. 'KIL'; '---' = none
  | 'fxValue'        // FX parameter 00–FF hex
  | 'transpose'      // relative semitones: 00=none, 01–7F=+1–+127, FF–80=−1–−128
  | 'ticks'          // groove ticks per step hex; '00'=skip; '--'=end of groove
  | 'volume'         // level 00–FF hex; '--' = no override
  | 'ppq'            // pulses-per-quarter-note decimal; groove row 0 only
  | 'instrumentName' // instrument name text ≤12 chars; no hexValue
  | 'eqSlot'         // EQ slot assignment hex; '--' = none
  | 'rowIndex'       // row/step index hex

M8KeyName

type M8KeyName = 'left' | 'right' | 'up' | 'down' | 'shift' | 'play' | 'opt' | 'edit'

M8SdkConfig

interface M8SdkConfig {
  debug?: boolean  // enables verbose postMessage logging via DebugMessenger
}

Host events

These events are emitted by the host and consumed internally by M8Client. They are listed here for reference if you use the post-me connection directly.

| Event | Payload | Fired when | |---|---|---| | stateChanged | M8State | Any state field changes | | viewChanged | { viewName, viewTitle } | The active view changes | | cursorMoved | { pos, rect, selectionMode } | Cursor position or selection mode changes | | textUpdated | { textUnderCursor, currentLine } | Text under cursor or current line changes | | keyPressed | { keys: number } | A physical key event fires on the device |


Supported views

The semantic context parser supports these viewName values:

| viewName | Columns | |---|---| | song | track1track8 (chainRef, meta: trackIndex 1–8) | | chain | phrase (phraseRef), transpose | | phrase | note, vel, inst, fx1cmd, fx1val, fx2cmd, fx2val, fx3cmd, fx3val | | table | transpose, volume, fx1cmd, fx1val, fx2cmd, fx2val, fx3cmd, fx3val | | groove | ticks, ppq (row 0 only) | | instrumentpool | name, dry, mx, de, rv, eq |

All other views (isGridView: false) return a context with row: null and activeField: null.


Notes

  • Playback indicator — When a row is actively playing the M8 prepends < or > to currentLine. The SDK strips this automatically before parsing so column offsets remain consistent.
  • Font mode — All column offsets assume font mode 0 (Headless: 8×10 px cells; Model:02: 12×14 px cells). Font modes 1 (bold) and 2 (large) may shift positions.
  • Schema file — View and column definitions live in src/m8-view-context.json and are bundled into dist/index.js at build time.
  • Requirement — The app must run inside the yam8d host iframe. The ChildHandshake from post-me will never resolve in a standalone tab.

Client SDK for iframe applications that communicate with an M8 host.

Install

npm install @yam8d/m8-sdk

For local development before the package is published:

npm install ../yam8d/packages/m8-sdk

Usage

import { createM8Client } from '@yam8d/m8-sdk'

const m8 = await createM8Client()

console.log(m8.state.viewName)
await m8.sendKeyPress(['play'])

const unsubscribe = m8.onStateChange((state) => {
  console.log(state.cursorPos)
})

unsubscribe()
m8.disconnect()

For React or other environments where top-level await is not convenient:

import { createM8ClientSync } from '@yam8d/m8-sdk'

const { client, connect } = createM8ClientSync()
await connect()

The app must run inside the host iframe. Opening it directly in a standalone browser tab cannot establish the SDK handshake.


Semantic Context

The SDK can interpret the current cursor position and row text as typed, labelled field values. This works for the five grid views (song, chain, phrase, table, groove) and the instrumentpool view.

Via the client

const ctx = m8.getSemanticContext()
// or as a human-readable string:
const description = m8.describeContext()
// e.g. "Track 3 — Chain 0A — Song View row 02"

Standalone functions

import { getSemanticContext, describeContext, formatFieldValue, lookupFxCommand } from '@yam8d/m8-sdk'

const ctx = getSemanticContext(state)

if (ctx?.activeField) {
  const field = ctx.activeField

  console.log(field.key)      // e.g. 'track3', 'note', 'fx1cmd'
  console.log(field.label)    // e.g. 'Track 3', 'Note (N)', 'FX1 Command'
  console.log(field.type)     // e.g. 'chainRef', 'note', 'fxCommand'
  console.log(field.rawValue) // raw text from the line, e.g. '0A', 'C-4', 'KIL'
  console.log(field.hexValue) // parsed integer or null (null for empty cells and text types)
  console.log(field.isEmpty)  // true when '--' / '---' / '---00'

  // Song view: which track is the cursor on?
  if (field.type === 'chainRef') {
    console.log(field.meta?.trackIndex) // 1–8
  }

  // FX command: look up its description
  if (field.type === 'fxCommand' && !field.isEmpty) {
    const info = lookupFxCommand(field.rawValue)
    console.log(info?.description)
  }
}

// Row-level fields (all columns on the current line)
if (ctx?.row) {
  console.log(ctx.row.rowIndex)        // hex-parsed row index, e.g. 2
  console.log(ctx.row.fields['note'])  // M8ParsedField for the note column
}

Returned types

interface M8SemanticContext {
  viewName: string               // e.g. 'song', 'phrase'
  viewTitle: string | null       // e.g. 'Song View'
  viewDescription: string | null
  isGridView: boolean
  row: M8ParsedRow | null        // all columns on the cursor's line
  activeField: M8ActiveField | null  // the column under the cursor
}

interface M8ParsedField {
  key: string          // schema key, e.g. 'fx1cmd', 'track3'
  label: string        // human label, e.g. 'FX1 Command', 'Track 3'
  type: M8FieldType
  rawValue: string     // trimmed text from currentLine
  hexValue: number | null
  isEmpty: boolean
  meta?: Record<string, unknown>
}

Supported field types (M8FieldType)

| Type | Description | |---|---| | chainRef | Chain number (hex). -- = empty. | | phraseRef | Phrase number (hex). -- = empty. | | note | Note name, e.g. C-4, A#3. --- = empty. | | velocity | Note velocity 00–7F (hex). | | instrumentRef | Instrument slot 00–7F (hex). -- = keep current. | | fxCommand | 3-char FX command name, e.g. KIL, ARP. --- = none. | | fxValue | FX value 00–FF (hex). | | transpose | Relative transpose (hex): 00=none, 017F=+1–+127, FF80=−1–−128. | | ticks | Groove ticks per step (hex). | | volume | Volume level 00–FF (hex). -- = no override. | | ppq | Pulses Per Quarter note (decimal). Groove row 0 only. | | instrumentName | Instrument name text (up to 12 chars). No hexValue. | | eqSlot | EQ slot assignment (hex). -- = none. | | rowIndex | Row/step index (hex). |

Playback indicator

When a row is actively playing, the M8 prepends a < or > character to currentLine. The SDK strips this automatically before parsing, so column offsets are always consistent.

Notes file

View/column definitions live in src/m8-view-context.json and are bundled into dist/index.js at build time. All x/width values assume font mode 0 (8×10 px cells on Headless, 12×14 px on Model:02). Font modes 1 and 2 may shift column positions.