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

@signalsandsorcery/plugin-sdk

v2.0.2

Published

Plugin SDK for Signals & Sorcery — types, components, and hooks for building generator plugins

Readme

@signalsandsorcery/plugin-sdk

Plugin SDK for building custom generator plugins for Signals & Sorcery — an AI-powered music production workstation.

Part of the Signals & Sorcery ecosystem.

Plugins extend the Loop Workstation with custom input generators that create MIDI patterns, manage audio samples, generate AI audio textures, or combine all three. Each plugin gets its own accordion section in the workstation UI and a scoped PluginHost API for interacting with tracks, MIDI, audio, and more.

Installation

npm install @signalsandsorcery/plugin-sdk

Documentation

Full documentation is available at signalsandsorcery.com/plugin-sdk:

Reference Plugins

These built-in plugins serve as reference implementations:

| Plugin | Type | Description | Source | |--------|------|-------------|--------| | Synth Generator | midi | AI-powered MIDI generation with Surge XT presets | sas-synth-plugin | | Sample Player | sample | Sample library browser with time-stretching | sas-sample-plugin | | Audio Texture | audio | AI audio texture generation | sas-audio-plugin |

What's in the SDK

Types

The core plugin contract — everything you need to implement a generator plugin:

import type {
  GeneratorPlugin,    // Interface your plugin class implements
  PluginHost,         // Scoped API surface (tracks, MIDI, audio, LLM, etc.)
  PluginUIProps,      // Props passed to your React component
  PluginManifest,     // plugin.json schema
  MusicalContext,     // Key, mode, BPM, bars, chords
  MidiClipData,       // MIDI clip payload
  PluginMidiNote,     // Individual MIDI note
  PluginTrackHandle,  // Track identity returned by createTrack()
  PluginError,        // Typed error class with error codes
} from '@signalsandsorcery/plugin-sdk';

UI Components

Pre-built components that match the host app's visual style:

| Component | Description | |-----------|-------------| | TrackRow | Full-featured track row with prompt input, generate/shuffle/copy buttons, mute/solo, volume/pan, FX drawer, instrument drawer, and progress overlay | | VolumeSlider | Compact horizontal volume slider (0-1) with dB tooltip | | PanSlider | Compact horizontal pan slider (-1 to +1) with double-click to center | | FxToggleBar | Per-track FX control panel with 6 categories, 5 presets each, and dry/wet sliders | | SorceryProgressBar | Animated progress bar with time-based pacing for long operations | | InstrumentDrawer | Searchable grid of available VST3/AU instrument plugins |

import { TrackRow, VolumeSlider, PanSlider, FxToggleBar, SorceryProgressBar } from '@signalsandsorcery/plugin-sdk';

Hooks

import { useSceneState } from '@signalsandsorcery/plugin-sdk';

// Maintains separate state per scene — preserved across scene switches
const [prompts, setPrompts, setPromptsForScene] = useSceneState(activeSceneId, {});

Constants

import {
  VALID_INSTRUMENT_ROLES,  // ['bass', 'kick', 'snare', 'lead', 'pad', ...]
  PLUGIN_SDK_VERSION,      // '1.0.0'
  FX_CATEGORIES,           // ['eq', 'compressor', 'chorus', 'phaser', 'delay', 'reverb']
  FX_PRESET_CONFIGS,       // Preset definitions for all 6 FX categories
} from '@signalsandsorcery/plugin-sdk';

Quick Start

1. Create the manifest (plugin.json)

{
  "id": "@my-org/my-plugin",
  "displayName": "My Plugin",
  "version": "1.0.0",
  "description": "A custom generator plugin",
  "generatorType": "midi",
  "main": "index.js",
  "minHostVersion": "1.0.0",
  "capabilities": {
    "requiresLLM": true,
    "requiresSurgeXT": true
  }
}

Generator types: midi | audio | sample | hybrid

2. Implement the plugin class

import type { GeneratorPlugin, PluginHost, PluginUIProps, PluginSettingsSchema, MusicalContext } from '@signalsandsorcery/plugin-sdk';
import { MyPanel } from './components/Panel';

export class MyPlugin implements GeneratorPlugin {
  readonly id = '@my-org/my-plugin';
  readonly displayName = 'My Plugin';
  readonly version = '1.0.0';
  readonly description = 'A custom generator plugin';
  readonly generatorType = 'midi' as const;

  private host: PluginHost | null = null;

  async activate(host: PluginHost): Promise<void> {
    this.host = host;
  }

  async deactivate(): Promise<void> {
    this.host = null;
  }

  getUIComponent() {
    return MyPanel;
  }

  getSettingsSchema(): PluginSettingsSchema | null {
    return null;
  }

  // Optional lifecycle hooks
  async onSceneChanged(sceneId: string | null): Promise<void> { }
  onContextChanged(context: MusicalContext): void { }
}

3. Build the UI

import type { PluginUIProps } from '@signalsandsorcery/plugin-sdk';

export function MyPanel({ host, activeSceneId, isAuthenticated, isConnected }: PluginUIProps) {
  const handleGenerate = async () => {
    if (!activeSceneId) {
      host.showToast('warning', 'No Scene', 'Select a scene first');
      return;
    }

    // Create a track
    const track = await host.createTrack({ name: 'My Track', role: 'lead', loadSynth: true });

    // Get musical context
    const context = await host.getMusicalContext();

    // Write MIDI
    await host.writeMidiClip(track.id, {
      startTime: 0,
      endTime: (context.bars * 4 * 60) / context.bpm,
      tempo: context.bpm,
      notes: [
        { pitch: 60, startBeat: 0, durationBeats: 1, velocity: 100 },
        { pitch: 64, startBeat: 1, durationBeats: 1, velocity: 90 },
        { pitch: 67, startBeat: 2, durationBeats: 1, velocity: 85 },
        { pitch: 72, startBeat: 3, durationBeats: 1, velocity: 100 },
      ],
    });

    host.showToast('success', 'Done', 'Pattern generated');
  };

  return (
    <div>
      <button onClick={handleGenerate} disabled={!isConnected}>
        Generate
      </button>
    </div>
  );
}

4. Install the plugin

Place the compiled plugin in:

~/.signals-and-sorcery/plugins/my-plugin/
  plugin.json
  index.js
  ...

Restart Signals & Sorcery. The plugin appears in the workstation accordion.

PluginHost API Overview

All methods are available on the host object your plugin receives in activate() and via PluginUIProps.host. Methods marked with ownership can only modify tracks the calling plugin created.

Track Management

| Method | Description | |--------|-------------| | createTrack(options) | Create a track with name, role, synth, instrument | | deleteTrack(trackId) | Delete an owned track | | getPluginTracks() | List all tracks this plugin owns in the active scene | | getTrackInfo(trackId) | Detailed track state (mute, volume, pan, plugins) | | adoptSceneTracks() | Re-claim unowned tracks matching generator type | | setTrackMute/Volume/Pan/Solo/Name | Track property setters | | shufflePreset(trackId) | Randomize Surge XT preset (keeps MIDI) | | duplicateTrack(trackId) | Clone track with MIDI + new preset |

MIDI Operations

| Method | Description | |--------|-------------| | writeMidiClip(trackId, clip) | Write MIDI notes (replaces existing) | | clearMidi(trackId) | Clear all MIDI from a track | | postProcessMidi(notes, options) | Quantize, swing, scale enforcement, humanization | | auditionNote(trackId, pitch, velocity, durationMs) | Preview a single note |

Audio Operations

| Method | Description | |--------|-------------| | writeAudioClip(trackId, filePath, position?) | Place audio file on track | | generateAudioTexture(request) | AI audio generation from text prompt |

Plugin/Synth Operations

| Method | Description | |--------|-------------| | loadSynthPlugin(trackId, pluginName) | Load VST3/AU plugin | | setPluginState/getPluginState | Save/restore base64-encoded preset data | | getTrackPlugins(trackId) | List loaded plugins | | getAvailableInstruments() | Get scanned VST3/AU instruments | | setTrackInstrument(trackId, pluginId) | Change instrument (preserves MIDI) |

FX Operations

Six categories in signal chain order: eq > compressor > chorus > phaser > delay > reverb

| Method | Description | |--------|-------------| | getTrackFxState(trackId) | Get enabled/preset/dryWet per category | | toggleTrackFx(trackId, category, enabled) | Enable/disable FX category | | setTrackFxPreset(trackId, category, presetIndex) | Set FX preset (0-4) | | setTrackFxDryWet(trackId, category, value) | Set dry/wet mix (0.0-1.0) |

Scene Context

| Method | Description | |--------|-------------| | getGenerationContext(excludeTrackId?) | Full context + concurrent track MIDI data | | getMusicalContext() | Key, mode, BPM, bars, genre, chords | | getActiveSceneId() | Current scene ID (synchronous) | | getSceneList() | All scenes in the project |

Transport & Events

| Method | Description | |--------|-------------| | onTrackStateChange(listener) | Real-time mute, solo, volume, pan updates | | onTransportEvent(listener) | Play, stop, BPM change, position | | onDeckBoundary(listener) | Loop boundary events (bar, beat, loopCount) | | onSceneChange(listener) | Scene selection changes | | onEngineReady(listener) | Engine finished loading tracks | | getTransportState() | Current playback state snapshot |

LLM Access

| Method | Description | |--------|-------------| | generateWithLLM(request) | Generate text/JSON (metered, requires auth) | | isLLMAvailable() | Check auth + gateway reachability |

Preset System

| Method | Description | |--------|-------------| | getPresetCategories(pluginName) | Available Surge XT categories | | getRandomPreset(category) | Random preset from category | | getPresetByName(category, name) | Specific preset lookup | | classifyPresetCategory(description) | LLM-based text-to-category |

Scene Composition

| Method | Description | |--------|-------------| | composeScene(options) | Bulk LLM arrangement generation | | onComposeProgress(listener) | Progress events (planning, generating, complete) |

Data Persistence

| Method | Description | |--------|-------------| | getSceneData/setSceneData/getAllSceneData/deleteSceneData | Per-scene key-value storage | | getProjectData/setProjectData | Project-wide storage | | settings.get/set/getAll/onChange | Global settings (cross-project) | | getDataDirectory() | Plugin's isolated data directory path |

Plugin Presets

| Method | Description | |--------|-------------| | getPluginPresets(category?) | Get saved presets for this plugin | | savePluginPreset(options) | Save a preset (name, category, data) | | deletePluginPreset(id) | Delete a preset |

File System & Network

| Method | Description | |--------|-------------| | showOpenDialog/showSaveDialog | Native file dialogs (requires fileDialog capability) | | downloadFile/importFile | Download/copy files to plugin data directory | | httpRequest(options) | HTTP requests (requires network capability with allowedHosts) |

Secure Storage

| Method | Description | |--------|-------------| | storeSecret/getSecret/deleteSecret | OS keychain encryption, per-plugin scoped |

Sample Library

| Method | Description | |--------|-------------| | getSamples/getSampleById | Query sample library | | importSamples(filePaths) | Import audio files | | createSampleTrack/deleteSampleTrack | Manage sample tracks | | getPluginSampleTracks() | List owned sample tracks | | timeStretchSample(sampleId, targetBpm) | Time-stretch to target BPM |

Notifications & Progress

| Method | Description | |--------|-------------| | showToast(type, title, message?) | Toast notification (info/success/warning/error) | | setProgress(trackId, progress) | Track progress bar (0-100, -1 to hide) | | setStatusMessage(message) | Accordion header status text | | confirmAction(title, message) | Confirmation dialog |

Error Codes

All errors are PluginError instances with a typed code property:

| Code | Description | |------|-------------| | NOT_OWNED | Tried to modify a track not owned by this plugin | | TRACK_NOT_FOUND | Track ID doesn't exist in engine | | TRACK_LIMIT_EXCEEDED | Plugin has too many tracks (default: 16 per scene) | | NO_ACTIVE_SCENE | No scene is selected | | ENGINE_ERROR | Audio engine call failed | | INVALID_MIDI | Malformed MIDI data | | FILE_NOT_FOUND | Referenced file doesn't exist | | INVALID_FORMAT | Unsupported audio format | | PLUGIN_NOT_FOUND | VST/AU plugin not installed | | LLM_BUDGET_EXCEEDED | Over daily token limit | | LLM_UNAVAILABLE | LLM gateway unreachable | | NOT_AUTHENTICATED | User not logged in | | TIMEOUT | Operation timed out | | CANCELLED | User cancelled the operation | | INCOMPATIBLE | Plugin requires newer SDK version | | CAPABILITY_DENIED | Plugin lacks required capability in manifest | | SECRET_NOT_FOUND | Secret key doesn't exist |

Security Model

  • Ownership scoping — Plugins can only modify tracks they created (enforced at runtime)
  • Capability gating — Network and file system access require manifest declarations
  • Secret isolation — Each plugin's secrets are encrypted and scoped per plugin
  • Track limits — 16 tracks per plugin per scene (configurable)

The Signals & Sorcery Ecosystem

License

MIT