@collabhut/plugin-sdk 0.1.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.
Files changed (39) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +1046 -0
  3. package/dist/helpers/note-utils.d.ts +128 -0
  4. package/dist/helpers/note-utils.d.ts.map +1 -0
  5. package/dist/helpers/note-utils.js +155 -0
  6. package/dist/index.d.ts +13 -0
  7. package/dist/index.d.ts.map +1 -0
  8. package/dist/index.js +2 -0
  9. package/dist/types/audio-effect.d.ts +70 -0
  10. package/dist/types/audio-effect.d.ts.map +1 -0
  11. package/dist/types/audio-effect.js +1 -0
  12. package/dist/types/context.d.ts +39 -0
  13. package/dist/types/context.d.ts.map +1 -0
  14. package/dist/types/context.js +1 -0
  15. package/dist/types/events.d.ts +119 -0
  16. package/dist/types/events.d.ts.map +1 -0
  17. package/dist/types/events.js +19 -0
  18. package/dist/types/instrument.d.ts +83 -0
  19. package/dist/types/instrument.d.ts.map +1 -0
  20. package/dist/types/instrument.js +1 -0
  21. package/dist/types/licensing.d.ts +118 -0
  22. package/dist/types/licensing.d.ts.map +1 -0
  23. package/dist/types/licensing.js +27 -0
  24. package/dist/types/manifest.d.ts +90 -0
  25. package/dist/types/manifest.d.ts.map +1 -0
  26. package/dist/types/manifest.js +1 -0
  27. package/dist/types/midi-effect.d.ts +101 -0
  28. package/dist/types/midi-effect.d.ts.map +1 -0
  29. package/dist/types/midi-effect.js +1 -0
  30. package/dist/types/parameters.d.ts +76 -0
  31. package/dist/types/parameters.d.ts.map +1 -0
  32. package/dist/types/parameters.js +1 -0
  33. package/dist/types/shader.d.ts +110 -0
  34. package/dist/types/shader.d.ts.map +1 -0
  35. package/dist/types/shader.js +1 -0
  36. package/dist/types/vocal-preset.d.ts +149 -0
  37. package/dist/types/vocal-preset.d.ts.map +1 -0
  38. package/dist/types/vocal-preset.js +54 -0
  39. package/package.json +50 -0
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Note utility helpers for @collabhut/plugin-sdk.
3
+ *
4
+ * All functions are pure and zero-dependency. Safe to call from a Worker.
5
+ *
6
+ * ## MIDI note numbers
7
+ * Middle C (C4) = MIDI 60. A4 (concert pitch) = MIDI 69 = 440 Hz.
8
+ */
9
+ /**
10
+ * Convert a MIDI note number to its frequency in Hz.
11
+ *
12
+ * Uses equal temperament at A4 = 440 Hz.
13
+ *
14
+ * @example
15
+ * noteToHz(69) // 440
16
+ * noteToHz(60) // 261.63 (middle C)
17
+ * noteToHz(57) // 220 (A3)
18
+ */
19
+ export declare function noteToHz(midi: number): number;
20
+ /**
21
+ * Convert a frequency in Hz to the nearest MIDI note number.
22
+ *
23
+ * The result is rounded to the nearest integer.
24
+ *
25
+ * @example
26
+ * hzToNote(440) // 69
27
+ * hzToNote(261.63)// 60
28
+ */
29
+ export declare function hzToNote(hz: number): number;
30
+ /**
31
+ * Convert a frequency in Hz to a precise (fractional) MIDI note number.
32
+ *
33
+ * Useful for pitch detection where you want the exact pitch, not just the
34
+ * nearest semitone.
35
+ *
36
+ * @example
37
+ * hzToNoteFractional(450) // ~69.39
38
+ */
39
+ export declare function hzToNoteFractional(hz: number): number;
40
+ /** Note names in order (chromatic, starting from C) */
41
+ declare const NOTE_NAMES: readonly ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"];
42
+ export type NoteName = (typeof NOTE_NAMES)[number];
43
+ /**
44
+ * Convert a MIDI note number to a human-readable note name with octave.
45
+ *
46
+ * @example
47
+ * midiToName(60) // "C4"
48
+ * midiToName(69) // "A4"
49
+ * midiToName(57) // "A3"
50
+ */
51
+ export declare function midiToName(midi: number): string;
52
+ /**
53
+ * Convert a note name + octave to a MIDI note number.
54
+ *
55
+ * @example
56
+ * nameToMidi("C", 4) // 60
57
+ * nameToMidi("A", 4) // 69
58
+ * nameToMidi("A#", 3) // 58
59
+ */
60
+ export declare function nameToMidi(note: NoteName, octave: number): number;
61
+ /**
62
+ * Return the number of semitones between two MIDI note numbers.
63
+ * Result is signed — positive if `b > a`.
64
+ *
65
+ * @example
66
+ * semitonesBetween(60, 67) // 7 (perfect fifth up)
67
+ * semitonesBetween(69, 60) // -9
68
+ */
69
+ export declare function semitonesBetween(a: number, b: number): number;
70
+ /**
71
+ * Transpose a MIDI note by a number of semitones, clamped to 0–127.
72
+ *
73
+ * @example
74
+ * transpose(60, 7) // 67 (C4 → G4)
75
+ * transpose(60, -12) // 48 (C4 → C3)
76
+ */
77
+ export declare function transpose(midi: number, semitones: number): number;
78
+ /**
79
+ * Convert beats to seconds given a BPM.
80
+ *
81
+ * @example
82
+ * beatsToSeconds(1, 120) // 0.5
83
+ * beatsToSeconds(4, 90) // 2.666...
84
+ */
85
+ export declare function beatsToSeconds(beats: number, bpm: number): number;
86
+ /**
87
+ * Convert seconds to beats given a BPM.
88
+ *
89
+ * @example
90
+ * secondsToBeats(0.5, 120) // 1
91
+ * secondsToBeats(2, 90) // 3
92
+ */
93
+ export declare function secondsToBeats(seconds: number, bpm: number): number;
94
+ /**
95
+ * Convert a linear gain value (0–∞) to decibels.
96
+ * Returns `-Infinity` for gain = 0.
97
+ *
98
+ * @example
99
+ * gainToDb(1) // 0
100
+ * gainToDb(0.5) // -6.02
101
+ */
102
+ export declare function gainToDb(gain: number): number;
103
+ /**
104
+ * Convert decibels to a linear gain value.
105
+ *
106
+ * @example
107
+ * dbToGain(0) // 1
108
+ * dbToGain(-6) // ~0.5012
109
+ */
110
+ export declare function dbToGain(db: number): number;
111
+ /**
112
+ * Clamp a value between a minimum and maximum.
113
+ *
114
+ * @example
115
+ * clamp(1.5, 0, 1) // 1
116
+ * clamp(-0.5, 0, 1) // 0
117
+ */
118
+ export declare function clamp(value: number, min: number, max: number): number;
119
+ /**
120
+ * Linear interpolation between two values.
121
+ *
122
+ * @param t Blend factor, 0–1
123
+ * @example
124
+ * lerp(0, 100, 0.5) // 50
125
+ */
126
+ export declare function lerp(a: number, b: number, t: number): number;
127
+ export {};
128
+ //# sourceMappingURL=note-utils.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"note-utils.d.ts","sourceRoot":"","sources":["../../src/helpers/note-utils.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH;;;;;;;;;GASG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAE7C;AAED;;;;;;;;GAQG;AACH,wBAAgB,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAE3C;AAED;;;;;;;;GAQG;AACH,wBAAgB,kBAAkB,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAErD;AAED,uDAAuD;AACvD,QAAA,MAAM,UAAU,4EAA6E,CAAA;AAE7F,MAAM,MAAM,QAAQ,GAAG,CAAC,OAAO,UAAU,CAAC,CAAC,MAAM,CAAC,CAAA;AAElD;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAI/C;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,CAAC,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAEjE;AAED;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAE7D;AAED;;;;;;GAMG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,MAAM,CAEjE;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAEjE;AAED;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAEnE;AAED;;;;;;;GAOG;AACH,wBAAgB,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAG7C;AAED;;;;;;GAMG;AACH,wBAAgB,QAAQ,CAAC,EAAE,EAAE,MAAM,GAAG,MAAM,CAE3C;AAED;;;;;;GAMG;AACH,wBAAgB,KAAK,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,GAAG,MAAM,CAErE;AAED;;;;;;GAMG;AACH,wBAAgB,IAAI,CAAC,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAE5D"}
@@ -0,0 +1,155 @@
1
+ /**
2
+ * Note utility helpers for @collabhut/plugin-sdk.
3
+ *
4
+ * All functions are pure and zero-dependency. Safe to call from a Worker.
5
+ *
6
+ * ## MIDI note numbers
7
+ * Middle C (C4) = MIDI 60. A4 (concert pitch) = MIDI 69 = 440 Hz.
8
+ */
9
+ /**
10
+ * Convert a MIDI note number to its frequency in Hz.
11
+ *
12
+ * Uses equal temperament at A4 = 440 Hz.
13
+ *
14
+ * @example
15
+ * noteToHz(69) // 440
16
+ * noteToHz(60) // 261.63 (middle C)
17
+ * noteToHz(57) // 220 (A3)
18
+ */
19
+ export function noteToHz(midi) {
20
+ return 440 * 2 ** ((midi - 69) / 12);
21
+ }
22
+ /**
23
+ * Convert a frequency in Hz to the nearest MIDI note number.
24
+ *
25
+ * The result is rounded to the nearest integer.
26
+ *
27
+ * @example
28
+ * hzToNote(440) // 69
29
+ * hzToNote(261.63)// 60
30
+ */
31
+ export function hzToNote(hz) {
32
+ return Math.round(69 + 12 * Math.log2(hz / 440));
33
+ }
34
+ /**
35
+ * Convert a frequency in Hz to a precise (fractional) MIDI note number.
36
+ *
37
+ * Useful for pitch detection where you want the exact pitch, not just the
38
+ * nearest semitone.
39
+ *
40
+ * @example
41
+ * hzToNoteFractional(450) // ~69.39
42
+ */
43
+ export function hzToNoteFractional(hz) {
44
+ return 69 + 12 * Math.log2(hz / 440);
45
+ }
46
+ /** Note names in order (chromatic, starting from C) */
47
+ const NOTE_NAMES = ["C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B"];
48
+ /**
49
+ * Convert a MIDI note number to a human-readable note name with octave.
50
+ *
51
+ * @example
52
+ * midiToName(60) // "C4"
53
+ * midiToName(69) // "A4"
54
+ * midiToName(57) // "A3"
55
+ */
56
+ export function midiToName(midi) {
57
+ const octave = Math.floor(midi / 12) - 1;
58
+ const name = NOTE_NAMES[midi % 12];
59
+ return `${name}${octave}`;
60
+ }
61
+ /**
62
+ * Convert a note name + octave to a MIDI note number.
63
+ *
64
+ * @example
65
+ * nameToMidi("C", 4) // 60
66
+ * nameToMidi("A", 4) // 69
67
+ * nameToMidi("A#", 3) // 58
68
+ */
69
+ export function nameToMidi(note, octave) {
70
+ return NOTE_NAMES.indexOf(note) + (octave + 1) * 12;
71
+ }
72
+ /**
73
+ * Return the number of semitones between two MIDI note numbers.
74
+ * Result is signed — positive if `b > a`.
75
+ *
76
+ * @example
77
+ * semitonesBetween(60, 67) // 7 (perfect fifth up)
78
+ * semitonesBetween(69, 60) // -9
79
+ */
80
+ export function semitonesBetween(a, b) {
81
+ return b - a;
82
+ }
83
+ /**
84
+ * Transpose a MIDI note by a number of semitones, clamped to 0–127.
85
+ *
86
+ * @example
87
+ * transpose(60, 7) // 67 (C4 → G4)
88
+ * transpose(60, -12) // 48 (C4 → C3)
89
+ */
90
+ export function transpose(midi, semitones) {
91
+ return Math.max(0, Math.min(127, midi + semitones));
92
+ }
93
+ /**
94
+ * Convert beats to seconds given a BPM.
95
+ *
96
+ * @example
97
+ * beatsToSeconds(1, 120) // 0.5
98
+ * beatsToSeconds(4, 90) // 2.666...
99
+ */
100
+ export function beatsToSeconds(beats, bpm) {
101
+ return (beats / bpm) * 60;
102
+ }
103
+ /**
104
+ * Convert seconds to beats given a BPM.
105
+ *
106
+ * @example
107
+ * secondsToBeats(0.5, 120) // 1
108
+ * secondsToBeats(2, 90) // 3
109
+ */
110
+ export function secondsToBeats(seconds, bpm) {
111
+ return (seconds * bpm) / 60;
112
+ }
113
+ /**
114
+ * Convert a linear gain value (0–∞) to decibels.
115
+ * Returns `-Infinity` for gain = 0.
116
+ *
117
+ * @example
118
+ * gainToDb(1) // 0
119
+ * gainToDb(0.5) // -6.02
120
+ */
121
+ export function gainToDb(gain) {
122
+ if (gain <= 0)
123
+ return -Infinity;
124
+ return 20 * Math.log10(gain);
125
+ }
126
+ /**
127
+ * Convert decibels to a linear gain value.
128
+ *
129
+ * @example
130
+ * dbToGain(0) // 1
131
+ * dbToGain(-6) // ~0.5012
132
+ */
133
+ export function dbToGain(db) {
134
+ return 10 ** (db / 20);
135
+ }
136
+ /**
137
+ * Clamp a value between a minimum and maximum.
138
+ *
139
+ * @example
140
+ * clamp(1.5, 0, 1) // 1
141
+ * clamp(-0.5, 0, 1) // 0
142
+ */
143
+ export function clamp(value, min, max) {
144
+ return Math.max(min, Math.min(max, value));
145
+ }
146
+ /**
147
+ * Linear interpolation between two values.
148
+ *
149
+ * @param t Blend factor, 0–1
150
+ * @example
151
+ * lerp(0, 100, 0.5) // 50
152
+ */
153
+ export function lerp(a, b, t) {
154
+ return a + (b - a) * t;
155
+ }
@@ -0,0 +1,13 @@
1
+ export type { PluginManifest, PluginType, PluginPricing, SemVer, PluginAuthor } from "./types/manifest";
2
+ export type { PluginParam, RangeParam, BoolParam, ChoiceParam, ParamCurve, ParamValues } from "./types/parameters";
3
+ export type { PluginContext, InstrumentContext } from "./types/context";
4
+ export type { AudioEffectIO, AudioEffectFactory, AudioEffectModule, } from "./types/audio-effect";
5
+ export type { MidiEvent, MidiEventType, MidiNoteOnEvent, MidiNoteOffEvent, MidiControlChangeEvent, MidiPitchBendEvent, MidiAftertouchEvent, MidiPolyAftertouchEvent, MidiProgramChangeEvent, MidiEffectPlugin, MidiEffectFactory, MidiEffectModule, } from "./types/midi-effect";
6
+ export type { InstrumentVoice, InstrumentPlugin, InstrumentFactory, InstrumentModule, } from "./types/instrument";
7
+ export type { EqBand, EqBandType, VocalEqSettings, VocalCompressorSettings, VocalDeEsserSettings, VocalSaturationSettings, VocalPitchCorrectionSettings, VocalChorusSettings, VocalReverbSettings, VocalDelaySettings, VocalPreset, VocalPresetModule, } from "./types/vocal-preset";
8
+ export type { UniformType, ShaderUniform, ShaderDefinition, ShaderModule, } from "./types/shader";
9
+ export type { CollaborationAccess, LicenseToken, LicenseGranted, LicenseDenied, LicenseCheckResult, LicenseChecker, } from "./types/licensing";
10
+ export type { HostToPluginMessage, HostToPluginExtended, PluginToHostEvent, PluginInitMessage, PluginParamsUpdateMessage, PluginProcessMessage, PluginMidiMessage, PluginNoteOnMessage, PluginNoteOffMessage, PluginDisposeMessage, PluginReadyEvent, PluginAudioOutputEvent, PluginMidiOutputEvent, PluginErrorEvent, PluginFetchRequestEvent, PluginFetchResponseMessage, } from "./types/events";
11
+ export { noteToHz, hzToNote, hzToNoteFractional, midiToName, nameToMidi, semitonesBetween, transpose, beatsToSeconds, secondsToBeats, gainToDb, dbToGain, clamp, lerp, } from "./helpers/note-utils";
12
+ export type { NoteName } from "./helpers/note-utils";
13
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,YAAY,EAAE,cAAc,EAAE,UAAU,EAAE,aAAa,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AACvG,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,SAAS,EAAE,WAAW,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,oBAAoB,CAAA;AAClH,YAAY,EAAE,aAAa,EAAE,iBAAiB,EAAE,MAAM,iBAAiB,CAAA;AAGvE,YAAY,EACR,aAAa,EACb,kBAAkB,EAClB,iBAAiB,GACpB,MAAM,sBAAsB,CAAA;AAG7B,YAAY,EACR,SAAS,EACT,aAAa,EACb,eAAe,EACf,gBAAgB,EAChB,sBAAsB,EACtB,kBAAkB,EAClB,mBAAmB,EACnB,uBAAuB,EACvB,sBAAsB,EACtB,gBAAgB,EAChB,iBAAiB,EACjB,gBAAgB,GACnB,MAAM,qBAAqB,CAAA;AAG5B,YAAY,EACR,eAAe,EACf,gBAAgB,EAChB,iBAAiB,EACjB,gBAAgB,GACnB,MAAM,oBAAoB,CAAA;AAG3B,YAAY,EACR,MAAM,EACN,UAAU,EACV,eAAe,EACf,uBAAuB,EACvB,oBAAoB,EACpB,uBAAuB,EACvB,4BAA4B,EAC5B,mBAAmB,EACnB,mBAAmB,EACnB,kBAAkB,EAClB,WAAW,EACX,iBAAiB,GACpB,MAAM,sBAAsB,CAAA;AAG7B,YAAY,EACR,WAAW,EACX,aAAa,EACb,gBAAgB,EAChB,YAAY,GACf,MAAM,gBAAgB,CAAA;AAGvB,YAAY,EACR,mBAAmB,EACnB,YAAY,EACZ,cAAc,EACd,aAAa,EACb,kBAAkB,EAClB,cAAc,GACjB,MAAM,mBAAmB,CAAA;AAG1B,YAAY,EACR,mBAAmB,EACnB,oBAAoB,EACpB,iBAAiB,EACjB,iBAAiB,EACjB,yBAAyB,EACzB,oBAAoB,EACpB,iBAAiB,EACjB,mBAAmB,EACnB,oBAAoB,EACpB,oBAAoB,EACpB,gBAAgB,EAChB,sBAAsB,EACtB,qBAAqB,EACrB,gBAAgB,EAChB,uBAAuB,EACvB,0BAA0B,GAC7B,MAAM,gBAAgB,CAAA;AAGvB,OAAO,EACH,QAAQ,EACR,QAAQ,EACR,kBAAkB,EAClB,UAAU,EACV,UAAU,EACV,gBAAgB,EAChB,SAAS,EACT,cAAc,EACd,cAAc,EACd,QAAQ,EACR,QAAQ,EACR,KAAK,EACL,IAAI,GACP,MAAM,sBAAsB,CAAA;AAC7B,YAAY,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAA"}
package/dist/index.js ADDED
@@ -0,0 +1,2 @@
1
+ // Helpers (runtime values, not just types)
2
+ export { noteToHz, hzToNote, hzToNoteFractional, midiToName, nameToMidi, semitonesBetween, transpose, beatsToSeconds, secondsToBeats, gainToDb, dbToGain, clamp, lerp, } from "./helpers/note-utils";
@@ -0,0 +1,70 @@
1
+ import type { PluginContext } from "./context";
2
+ import type { PluginParam } from "./parameters";
3
+ import type { PluginManifest } from "./manifest";
4
+ /** The stereo or mono AudioNode pair exposed by an audio-effect plugin */
5
+ export interface AudioEffectIO {
6
+ /** Plugin receives audio here — connect the host's source to this node */
7
+ readonly input: AudioNode;
8
+ /** Plugin outputs processed audio from here — host connects this to the chain */
9
+ readonly output: AudioNode;
10
+ /**
11
+ * Optional side-chain input for compressors, gates, etc.
12
+ * Declare `sidechain: true` in the manifest to activate this input.
13
+ */
14
+ readonly sidechain?: AudioNode;
15
+ /**
16
+ * Named AudioParams made available for automation.
17
+ * Keys must match the `id` fields of `manifest.params` entries.
18
+ * If omitted, the DAW uses param-polling via `getParamValue()` instead.
19
+ */
20
+ readonly automationTargets?: Readonly<Record<string, AudioParam>>;
21
+ }
22
+ /**
23
+ * Audio effect plugin interface.
24
+ *
25
+ * Return an `AudioEffectIO` from your factory function. The host wires
26
+ * `input` and `output` into the track processing chain automatically.
27
+ *
28
+ * @example
29
+ * ```ts
30
+ * import type { AudioEffectFactory, PluginContext } from "@collabhut/plugin-sdk"
31
+ *
32
+ * export const manifest = {
33
+ * id: "com.myorg.warm-compressor",
34
+ * // ...
35
+ * params: [
36
+ * { type: "range", id: "threshold", label: "Threshold",
37
+ * min: -60, max: 0, default: -18, unit: "dB" },
38
+ * { type: "range", id: "ratio", label: "Ratio",
39
+ * min: 1, max: 20, default: 4, decimals: 1 },
40
+ * ],
41
+ * } satisfies PluginManifest
42
+ *
43
+ * export const factory: AudioEffectFactory<typeof manifest.params> =
44
+ * (context, params) => {
45
+ * const compressor = context.audioContext.createDynamicsCompressor()
46
+ * compressor.threshold.value = params.threshold
47
+ * compressor.ratio.value = params.ratio
48
+ * return {
49
+ * input: compressor,
50
+ * output: compressor,
51
+ * automationTargets: {
52
+ * threshold: compressor.threshold,
53
+ * ratio: compressor.ratio,
54
+ * },
55
+ * }
56
+ * }
57
+ * ```
58
+ */
59
+ export type AudioEffectFactory<TParams extends ReadonlyArray<PluginParam> = ReadonlyArray<PluginParam>> = (context: PluginContext, params: Readonly<Record<string, number | boolean | string>>, manifest: PluginManifest) => AudioEffectIO;
60
+ /**
61
+ * Full audio-effect module shape.
62
+ * Your plugin file must export both `manifest` and `factory`.
63
+ */
64
+ export interface AudioEffectModule {
65
+ readonly manifest: PluginManifest & {
66
+ readonly type: "audio-effect";
67
+ };
68
+ readonly factory: AudioEffectFactory;
69
+ }
70
+ //# sourceMappingURL=audio-effect.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"audio-effect.d.ts","sourceRoot":"","sources":["../../src/types/audio-effect.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,WAAW,CAAA;AAC9C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAC/C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAEhD,0EAA0E;AAC1E,MAAM,WAAW,aAAa;IAC1B,0EAA0E;IAC1E,QAAQ,CAAC,KAAK,EAAE,SAAS,CAAA;IACzB,iFAAiF;IACjF,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAA;IAC1B;;;OAGG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,SAAS,CAAA;IAC9B;;;;OAIG;IACH,QAAQ,CAAC,iBAAiB,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC,CAAA;CACpE;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,MAAM,MAAM,kBAAkB,CAC1B,OAAO,SAAS,aAAa,CAAC,WAAW,CAAC,GAAG,aAAa,CAAC,WAAW,CAAC,IACvE,CACA,OAAO,EAAE,aAAa,EACtB,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC,CAAC,EAC3D,QAAQ,EAAE,cAAc,KACvB,aAAa,CAAA;AAElB;;;GAGG;AACH,MAAM,WAAW,iBAAiB;IAC9B,QAAQ,CAAC,QAAQ,EAAE,cAAc,GAAG;QAAE,QAAQ,CAAC,IAAI,EAAE,cAAc,CAAA;KAAE,CAAA;IACrE,QAAQ,CAAC,OAAO,EAAE,kBAAkB,CAAA;CACvC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,39 @@
1
+ /**
2
+ * Runtime context provided to every plugin factory.
3
+ *
4
+ * The context object is the plugin's only gateway to the host environment.
5
+ * All properties are read-only — plugins cannot modify host state directly.
6
+ */
7
+ export interface PluginContext {
8
+ /** Web Audio API context. Use this to create nodes and schedule events. */
9
+ readonly audioContext: AudioContext;
10
+ /**
11
+ * Current sample rate in Hz.
12
+ * Always equal to `audioContext.sampleRate`.
13
+ */
14
+ readonly sampleRate: number;
15
+ /**
16
+ * Current tempo in beats per minute.
17
+ * Reactive: you should re-read this in every `process()` call for DAW sync.
18
+ */
19
+ readonly bpm: number;
20
+ /** Current transport position in beats (fractions of a beat allowed) */
21
+ readonly positionBeats: number;
22
+ /** `true` when the DAW transport is rolling */
23
+ readonly isPlaying: boolean;
24
+ /**
25
+ * Fetch a resource from CollabHut CDN.
26
+ * Only HTTPS URLs on `cdn.collabhut.com` are allowed.
27
+ * Arbitrary network access is blocked inside the plugin sandbox.
28
+ */
29
+ fetchAsset(url: string): Promise<ArrayBuffer>;
30
+ }
31
+ /**
32
+ * Runtime context provided exclusively to instrument plugins.
33
+ * Extends PluginContext with polyphonic voice management.
34
+ */
35
+ export interface InstrumentContext extends PluginContext {
36
+ /** Maximum number of simultaneous voices the host will attempt */
37
+ readonly maxPolyphony: number;
38
+ }
39
+ //# sourceMappingURL=context.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"context.d.ts","sourceRoot":"","sources":["../../src/types/context.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,MAAM,WAAW,aAAa;IAC1B,4EAA4E;IAC5E,QAAQ,CAAC,YAAY,EAAE,YAAY,CAAA;IACnC;;;OAGG;IACH,QAAQ,CAAC,UAAU,EAAE,MAAM,CAAA;IAC3B;;;OAGG;IACH,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,wEAAwE;IACxE,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAA;IAC9B,+CAA+C;IAC/C,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAA;IAC3B;;;;OAIG;IACH,UAAU,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC,CAAA;CAChD;AAED;;;GAGG;AACH,MAAM,WAAW,iBAAkB,SAAQ,aAAa;IACpD,kEAAkE;IAClE,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;CAChC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,119 @@
1
+ /**
2
+ * Typed message protocol for plugin ↔ host communication.
3
+ *
4
+ * Plugins execute inside a sandboxed Web Worker. All communication with the
5
+ * host DAW happens via `postMessage` with these discriminated-union types.
6
+ *
7
+ * ## Architecture
8
+ * ```
9
+ * Host (main thread) Plugin (Worker)
10
+ * │ │
11
+ * │──── HostToPluginMessage ────────►│ host → plugin commands
12
+ * │◄─── PluginToHostMessage ─────────│ plugin → host events
13
+ * ```
14
+ *
15
+ * Plugin authors **do not** import or use these types directly — they are
16
+ * consumed by the DAW runtime. They are documented here for those building
17
+ * plugin host environments or tooling.
18
+ */
19
+ /** Initialize the plugin. Sent once immediately after the Worker starts. */
20
+ export interface PluginInitMessage {
21
+ readonly type: "init";
22
+ /** Transferable SharedArrayBuffer for lock-free transport state reads */
23
+ readonly transportBuffer: SharedArrayBuffer;
24
+ /** Plugin parameter initial values */
25
+ readonly params: Readonly<Record<string, number | boolean | string>>;
26
+ readonly sampleRate: number;
27
+ readonly blockSize: number;
28
+ }
29
+ /** Update one or more parameter values */
30
+ export interface PluginParamsUpdateMessage {
31
+ readonly type: "params-update";
32
+ readonly params: Readonly<Record<string, number | boolean | string>>;
33
+ }
34
+ /** Process one audio block (audio-effect plugins only) */
35
+ export interface PluginProcessMessage {
36
+ readonly type: "process";
37
+ /** Interleaved float32 samples, transferred (zero-copy) */
38
+ readonly inputBuffer: Float32Array;
39
+ readonly numChannels: number;
40
+ readonly numFrames: number;
41
+ }
42
+ /** Send MIDI events for this block (midi-effect / instrument plugins) */
43
+ export interface PluginMidiMessage {
44
+ readonly type: "midi";
45
+ readonly events: ReadonlyArray<{
46
+ readonly channel: number;
47
+ readonly beatTime: number;
48
+ readonly data: readonly number[];
49
+ }>;
50
+ }
51
+ /** Trigger note on (instrument plugins only) */
52
+ export interface PluginNoteOnMessage {
53
+ readonly type: "note-on";
54
+ readonly note: number;
55
+ readonly velocity: number;
56
+ readonly time: number;
57
+ }
58
+ /** Trigger note off (instrument plugins only) */
59
+ export interface PluginNoteOffMessage {
60
+ readonly type: "note-off";
61
+ readonly note: number;
62
+ readonly velocity: number;
63
+ readonly time: number;
64
+ }
65
+ /** Dispose and terminate the plugin */
66
+ export interface PluginDisposeMessage {
67
+ readonly type: "dispose";
68
+ }
69
+ export type HostToPluginMessage = PluginInitMessage | PluginParamsUpdateMessage | PluginProcessMessage | PluginMidiMessage | PluginNoteOnMessage | PluginNoteOffMessage | PluginDisposeMessage;
70
+ /** Plugin is ready after init */
71
+ export interface PluginReadyEvent {
72
+ readonly type: "ready";
73
+ readonly manifestId: string;
74
+ readonly apiVersion: string;
75
+ }
76
+ /** Processed audio output (audio-effect plugins) */
77
+ export interface PluginAudioOutputEvent {
78
+ readonly type: "audio-output";
79
+ /** Interleaved float32 samples, transferred (zero-copy) */
80
+ readonly outputBuffer: Float32Array;
81
+ readonly numChannels: number;
82
+ readonly numFrames: number;
83
+ }
84
+ /** MIDI output events (midi-effect plugins) */
85
+ export interface PluginMidiOutputEvent {
86
+ readonly type: "midi-output";
87
+ readonly events: ReadonlyArray<{
88
+ readonly channel: number;
89
+ readonly beatTime: number;
90
+ readonly data: readonly number[];
91
+ }>;
92
+ }
93
+ /** Plugin encountered an error */
94
+ export interface PluginErrorEvent {
95
+ readonly type: "error";
96
+ /** Human-readable description of the error */
97
+ readonly message: string;
98
+ /** Optional stack trace (stripped in production builds) */
99
+ readonly stack?: string;
100
+ }
101
+ /** Plugin requests to fetch a CDN asset (host performs the fetch and replies) */
102
+ export interface PluginFetchRequestEvent {
103
+ readonly type: "fetch-request";
104
+ readonly requestId: string;
105
+ /** Must be a URL on cdn.collabhut.com — rejected otherwise */
106
+ readonly url: string;
107
+ }
108
+ /** Host response to a `fetch-request` */
109
+ export interface PluginFetchResponseMessage {
110
+ readonly type: "fetch-response";
111
+ readonly requestId: string;
112
+ readonly ok: boolean;
113
+ /** Transferred ArrayBuffer on success */
114
+ readonly data?: ArrayBuffer;
115
+ readonly error?: string;
116
+ }
117
+ export type PluginToHostEvent = PluginReadyEvent | PluginAudioOutputEvent | PluginMidiOutputEvent | PluginErrorEvent | PluginFetchRequestEvent;
118
+ export type HostToPluginExtended = HostToPluginMessage | PluginFetchResponseMessage;
119
+ //# sourceMappingURL=events.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../../src/types/events.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,19 @@
1
+ /**
2
+ * Typed message protocol for plugin ↔ host communication.
3
+ *
4
+ * Plugins execute inside a sandboxed Web Worker. All communication with the
5
+ * host DAW happens via `postMessage` with these discriminated-union types.
6
+ *
7
+ * ## Architecture
8
+ * ```
9
+ * Host (main thread) Plugin (Worker)
10
+ * │ │
11
+ * │──── HostToPluginMessage ────────►│ host → plugin commands
12
+ * │◄─── PluginToHostMessage ─────────│ plugin → host events
13
+ * ```
14
+ *
15
+ * Plugin authors **do not** import or use these types directly — they are
16
+ * consumed by the DAW runtime. They are documented here for those building
17
+ * plugin host environments or tooling.
18
+ */
19
+ export {};
@@ -0,0 +1,83 @@
1
+ import type { InstrumentContext } from "./context";
2
+ import type { MidiEvent } from "./midi-effect";
3
+ import type { PluginManifest } from "./manifest";
4
+ /**
5
+ * An individual voice instance returned by `noteOn`.
6
+ *
7
+ * The DAW keeps a reference per active voice and calls `stop` when the
8
+ * corresponding note-off arrives (or when voices are stolen for polyphony).
9
+ */
10
+ export interface InstrumentVoice {
11
+ /** AudioNode carrying this voice's audio signal — host connects it to the track bus */
12
+ readonly output: AudioNode;
13
+ /**
14
+ * Stop (release) the voice.
15
+ * @param velocity Release velocity, 0–127
16
+ * @param time AudioContext time at which the release should begin
17
+ */
18
+ stop(velocity: number, time: number): void;
19
+ /**
20
+ * Kill the voice immediately with no release tail.
21
+ * Used for voice stealing or when the project stops.
22
+ */
23
+ kill(): void;
24
+ }
25
+ /**
26
+ * Polyphonic instrument plugin.
27
+ *
28
+ * The DAW calls `noteOn` for every note and stores the returned voice.
29
+ * When the note ends it calls `voice.stop()`. If polyphony is exceeded it
30
+ * calls `voice.kill()` on the oldest voice before starting a new one.
31
+ *
32
+ * @example
33
+ * ```ts
34
+ * import type { InstrumentFactory, InstrumentVoice } from "@collabhut/plugin-sdk"
35
+ *
36
+ * export const factory: InstrumentFactory = (context) => ({
37
+ * noteOn(note, velocity, time) {
38
+ * const osc = context.audioContext.createOscillator()
39
+ * const gain = context.audioContext.createGain()
40
+ * osc.frequency.value = 440 * 2 ** ((note - 69) / 12)
41
+ * gain.gain.setValueAtTime(velocity / 127, time)
42
+ * osc.connect(gain)
43
+ * osc.start(time)
44
+ * return {
45
+ * output: gain,
46
+ * stop(_vel, releaseTime) {
47
+ * gain.gain.setTargetAtTime(0, releaseTime, 0.1)
48
+ * osc.stop(releaseTime + 0.5)
49
+ * },
50
+ * kill() { osc.stop(); gain.disconnect() },
51
+ * }
52
+ * },
53
+ * onMidi(_event) {},
54
+ * dispose() {},
55
+ * })
56
+ * ```
57
+ */
58
+ export interface InstrumentPlugin {
59
+ /**
60
+ * Start a new voice for the given MIDI note.
61
+ * @param note MIDI note number (0–127)
62
+ * @param velocity Attack velocity (0–127)
63
+ * @param time AudioContext time for the note start
64
+ * @returns A voice object the DAW will manage
65
+ */
66
+ noteOn(note: number, velocity: number, time: number): InstrumentVoice;
67
+ /**
68
+ * Handle non-note MIDI events (CC, pitch bend, aftertouch, etc.).
69
+ * Called alongside the voice lifecycle — useful for mod-wheel, portamento, etc.
70
+ */
71
+ onMidi(event: MidiEvent): void;
72
+ /** Called when the plugin is removed or the project closes */
73
+ dispose(): void;
74
+ }
75
+ export type InstrumentFactory = (context: InstrumentContext, params: Readonly<Record<string, number | boolean | string>>, manifest: PluginManifest) => InstrumentPlugin;
76
+ /** Full instrument module shape */
77
+ export interface InstrumentModule {
78
+ readonly manifest: PluginManifest & {
79
+ readonly type: "instrument";
80
+ };
81
+ readonly factory: InstrumentFactory;
82
+ }
83
+ //# sourceMappingURL=instrument.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"instrument.d.ts","sourceRoot":"","sources":["../../src/types/instrument.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,WAAW,CAAA;AAClD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,eAAe,CAAA;AAC9C,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AAEhD;;;;;GAKG;AACH,MAAM,WAAW,eAAe;IAC5B,uFAAuF;IACvF,QAAQ,CAAC,MAAM,EAAE,SAAS,CAAA;IAC1B;;;;OAIG;IACH,IAAI,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1C;;;OAGG;IACH,IAAI,IAAI,IAAI,CAAA;CACf;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,MAAM,WAAW,gBAAgB;IAC7B;;;;;;OAMG;IACH,MAAM,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,GAAG,eAAe,CAAA;IACrE;;;OAGG;IACH,MAAM,CAAC,KAAK,EAAE,SAAS,GAAG,IAAI,CAAA;IAC9B,8DAA8D;IAC9D,OAAO,IAAI,IAAI,CAAA;CAClB;AAED,MAAM,MAAM,iBAAiB,GAAG,CAC5B,OAAO,EAAE,iBAAiB,EAC1B,MAAM,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,GAAG,MAAM,CAAC,CAAC,EAC3D,QAAQ,EAAE,cAAc,KACvB,gBAAgB,CAAA;AAErB,mCAAmC;AACnC,MAAM,WAAW,gBAAgB;IAC7B,QAAQ,CAAC,QAAQ,EAAE,cAAc,GAAG;QAAE,QAAQ,CAAC,IAAI,EAAE,YAAY,CAAA;KAAE,CAAA;IACnE,QAAQ,CAAC,OAAO,EAAE,iBAAiB,CAAA;CACtC"}