@blibliki/engine 0.1.27 → 0.3.1

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 (123) hide show
  1. package/README.md +252 -76
  2. package/dist/index.cjs +2 -0
  3. package/dist/index.cjs.map +1 -0
  4. package/dist/index.d.cts +765 -0
  5. package/dist/index.d.ts +765 -0
  6. package/dist/index.js +2 -19484
  7. package/dist/index.js.map +1 -0
  8. package/package.json +16 -27
  9. package/src/Engine.ts +158 -177
  10. package/src/core/IO/AudioIO.ts +72 -0
  11. package/src/core/IO/Base.ts +118 -0
  12. package/src/core/IO/Collection.ts +123 -47
  13. package/src/core/IO/MidiIO.ts +43 -0
  14. package/src/core/IO/PolyAudioIO.ts +115 -0
  15. package/src/core/IO/index.ts +7 -61
  16. package/src/core/Note/frequencyTable.ts +144 -144
  17. package/src/core/Note/index.ts +49 -59
  18. package/src/core/Route.ts +79 -0
  19. package/src/core/Timing/Scheduler.ts +37 -0
  20. package/src/core/Timing/Time.ts +103 -0
  21. package/src/core/Timing/Transport.ts +104 -0
  22. package/src/core/Timing/index.ts +16 -0
  23. package/src/core/index.ts +36 -0
  24. package/src/core/midi/{ComputerKeyboardInput.ts → ComputerKeyboardDevice.ts} +31 -11
  25. package/src/core/midi/MidiDevice.ts +38 -31
  26. package/src/core/midi/MidiDeviceManager.ts +54 -55
  27. package/src/core/midi/MidiEvent.ts +36 -60
  28. package/src/core/module/Module.ts +233 -0
  29. package/src/core/module/PolyModule.ts +246 -0
  30. package/src/core/module/VoiceScheduler.ts +121 -0
  31. package/src/core/module/index.ts +3 -0
  32. package/src/core/schema.ts +41 -0
  33. package/src/index.ts +31 -9
  34. package/src/modules/BiquadFilter.ts +162 -0
  35. package/src/modules/Constant.ts +72 -0
  36. package/src/modules/Envelope.ts +178 -0
  37. package/src/modules/Filter.ts +109 -104
  38. package/src/modules/Gain.ts +78 -0
  39. package/src/modules/Inspector.ts +59 -0
  40. package/src/modules/Master.ts +18 -21
  41. package/src/modules/MidiSelector.ts +50 -50
  42. package/src/modules/Oscillator.ts +203 -158
  43. package/src/modules/Scale.ts +79 -0
  44. package/src/modules/StepSequencer.ts +61 -0
  45. package/src/modules/VirtualMidi.ts +33 -49
  46. package/src/modules/index.ts +159 -74
  47. package/src/nodePolyfill.ts +25 -0
  48. package/src/processors/filter-processor.ts +82 -0
  49. package/src/processors/index.ts +28 -0
  50. package/src/processors/scale-processor.ts +81 -0
  51. package/dist/index.umd.cjs +0 -227
  52. package/dist/src/Engine.d.ts +0 -83
  53. package/dist/src/core/IO/AudioNode.d.ts +0 -36
  54. package/dist/src/core/IO/Collection.d.ts +0 -14
  55. package/dist/src/core/IO/ForwardNode/Base.d.ts +0 -19
  56. package/dist/src/core/IO/ForwardNode/index.d.ts +0 -28
  57. package/dist/src/core/IO/MidiNode.d.ts +0 -31
  58. package/dist/src/core/IO/Node.d.ts +0 -41
  59. package/dist/src/core/IO/index.d.ts +0 -22
  60. package/dist/src/core/Module/MonoModule.d.ts +0 -68
  61. package/dist/src/core/Module/PolyModule.d.ts +0 -62
  62. package/dist/src/core/Module/index.d.ts +0 -7
  63. package/dist/src/core/Note/frequencyTable.d.ts +0 -4
  64. package/dist/src/core/Note/index.d.ts +0 -28
  65. package/dist/src/core/midi/ComputerKeyboardInput.d.ts +0 -12
  66. package/dist/src/core/midi/MidiDevice.d.ts +0 -30
  67. package/dist/src/core/midi/MidiDeviceManager.d.ts +0 -15
  68. package/dist/src/core/midi/MidiEvent.d.ts +0 -22
  69. package/dist/src/core/midi/index.d.ts +0 -5
  70. package/dist/src/index.d.ts +0 -9
  71. package/dist/src/main.d.ts +0 -0
  72. package/dist/src/modules/BitCrusher.d.ts +0 -17
  73. package/dist/src/modules/Delay.d.ts +0 -20
  74. package/dist/src/modules/Distortion.d.ts +0 -17
  75. package/dist/src/modules/Effect.d.ts +0 -20
  76. package/dist/src/modules/Envelope/AmpEnvelope.d.ts +0 -19
  77. package/dist/src/modules/Envelope/Base.d.ts +0 -67
  78. package/dist/src/modules/Envelope/FreqEnvelope.d.ts +0 -26
  79. package/dist/src/modules/Envelope/index.d.ts +0 -3
  80. package/dist/src/modules/Filter.d.ts +0 -43
  81. package/dist/src/modules/LFO.d.ts +0 -45
  82. package/dist/src/modules/Master.d.ts +0 -12
  83. package/dist/src/modules/MidiSelector.d.ts +0 -22
  84. package/dist/src/modules/Oscillator.d.ts +0 -56
  85. package/dist/src/modules/Reverb.d.ts +0 -20
  86. package/dist/src/modules/Sequencer.d.ts +0 -43
  87. package/dist/src/modules/VirtualMidi.d.ts +0 -33
  88. package/dist/src/modules/VoiceScheduler.d.ts +0 -45
  89. package/dist/src/modules/Volume.d.ts +0 -27
  90. package/dist/src/modules/index.d.ts +0 -18
  91. package/dist/src/routes.d.ts +0 -19
  92. package/dist/src/types.d.ts +0 -5
  93. package/dist/src/utils.d.ts +0 -1
  94. package/dist/test/MockingModules.d.ts +0 -22
  95. package/dist/test/Module/Oscillator.test.d.ts +0 -1
  96. package/dist/test/core/IO.test.d.ts +0 -1
  97. package/src/core/IO/AudioNode.ts +0 -82
  98. package/src/core/IO/ForwardNode/Base.ts +0 -99
  99. package/src/core/IO/ForwardNode/index.ts +0 -60
  100. package/src/core/IO/MidiNode.ts +0 -67
  101. package/src/core/IO/Node.ts +0 -118
  102. package/src/core/Module/MonoModule.ts +0 -219
  103. package/src/core/Module/PolyModule.ts +0 -218
  104. package/src/core/Module/index.ts +0 -15
  105. package/src/core/midi/index.ts +0 -5
  106. package/src/main.ts +0 -1
  107. package/src/modules/BitCrusher.ts +0 -45
  108. package/src/modules/Delay.ts +0 -53
  109. package/src/modules/Distortion.ts +0 -45
  110. package/src/modules/Effect.ts +0 -46
  111. package/src/modules/Envelope/AmpEnvelope.ts +0 -23
  112. package/src/modules/Envelope/Base.ts +0 -176
  113. package/src/modules/Envelope/FreqEnvelope.ts +0 -64
  114. package/src/modules/Envelope/index.ts +0 -3
  115. package/src/modules/LFO.ts +0 -149
  116. package/src/modules/Reverb.ts +0 -53
  117. package/src/modules/Sequencer.ts +0 -178
  118. package/src/modules/VoiceScheduler.ts +0 -145
  119. package/src/modules/Volume.ts +0 -72
  120. package/src/routes.ts +0 -49
  121. package/src/types.ts +0 -3
  122. package/src/utils.ts +0 -18
  123. package/src/vite-env.d.ts +0 -1
package/README.md CHANGED
@@ -1,49 +1,127 @@
1
- # Blibliki Engine
1
+ # Blibliki Web Audio Engine
2
2
 
3
- Blibliki Engine is a data-driven, non-UI JavaScript library written in TypeScript that serves as a framework for building synthesizers.
4
- Built on top of ToneJS, it aims to provide a streamlined interface for creating custom synthesizers.
3
+ A modular synthesis engine for building audio applications.
5
4
 
6
- ## Approach
5
+ <p>
6
+ <img src="https://img.shields.io/badge/typescript-%23007ACC.svg?style=for-the-badge&logo=typescript&logoColor=white" alt="TypeScript">
7
+ <img src="https://img.shields.io/badge/Web%20Audio%20API-FF7F00?style=for-the-badge&logo=javascript&logoColor=white" alt="Web Audio API">
8
+ <img src="https://img.shields.io/badge/Node.js-43853D?style=for-the-badge&logo=node.js&logoColor=white" alt="Node.js Compatible">
9
+ </p>
7
10
 
8
- The engine operates in a data-driven manner, allowing developers to provide changes to the current module configuration rather than directly accessing the modules themselves.
9
- This approach facilitates seamless integration with state management libraries like Redux, making it easy to build applications with centralized state management.
11
+ ## What is Blibliki Engine?
10
12
 
11
- One notable advantage of the data-driven approach is the ability to easily save and recall patches. By representing the synthesizer configuration as data, developers can effortlessly store and load patches, enabling users to save and share their custom presets.
13
+ Blibliki Engine is a data-driven JavaScript library written in TypeScript that serves as a framework for building synthesizers. The current version is built directly on the Web Audio API, replacing a previous version that was built on top of Tone.js. This change gives better control and understanding of the underlying audio processing.
12
14
 
13
- Blibliki Engine offers polyphony support and provides access to essential audio modules such as oscillators, filters, envelopes, and effects. It's important to note that the list of available audio modules will be extended as the development of the engine continues.
15
+ This project started as an educational initiative but has grown into a practical tool for creating synthesizers and audio applications. It runs in both browsers and Node.js environments through [node-web-audio-api](https://github.com/ircam-ismm/node-web-audio-api).
14
16
 
15
- ## Installing
17
+ ## How it Works
18
+
19
+ The engine is data-driven, meaning you provide changes to the module configuration rather than directly manipulating the modules. This approach works well with state management libraries like Redux and makes it easy to save and load synthesizer patches.
20
+
21
+ By representing the synthesizer configuration as data, you can easily store and load patches, enabling users to save and share their custom presets.
22
+
23
+ ## Main Features
24
+
25
+ - **Modular Design**: Connect audio modules to build sound chains
26
+ - **MIDI Support**: Connect to MIDI devices and process MIDI events
27
+ - **Polyphony**: Play multiple notes at the same time
28
+ - **Node.js Support**: Run in server environments
29
+ - **TypeScript**: Full type definitions for better development
30
+ - **Patch System**: Save and recall synth configurations as data
31
+
32
+ ## Blog Posts
33
+
34
+ I've written some blog posts about the engine's development:
35
+
36
+ - [Part 1: Introduction to Web Audio and Engine Design](https://mikezaby.com/posts/web-audio-engine-part1)
37
+ - [Part 2: Building a Modular Architecture](https://mikezaby.com/posts/web-audio-engine-part2)
38
+ - [Part 3: Implementing the Transport System](https://mikezaby.com/posts/web-audio-engine-part3)
39
+ - [Part 4: Creating a Step Sequencer](https://mikezaby.com/posts/web-audio-engine-part4)
40
+
41
+ ## Installation
16
42
 
17
43
  ```bash
44
+ # Using npm
18
45
  npm install @blibliki/engine
19
- ```
20
46
 
21
- ## Usage
47
+ # Using yarn
48
+ yarn add @blibliki/engine
22
49
 
23
- ```JavaScript
24
- import Engine from "@blibliki/engine";
50
+ # Using pnpm
51
+ pnpm add @blibliki/engine
25
52
  ```
26
53
 
27
- #### Initialize
54
+ ## Quick Start
28
55
 
29
- `Engine.initialize` returns a promise that resolves when the engine is initialized and ready for use.
56
+ Here's a simple example to get you started:
30
57
 
31
- ```JavaScript
32
- Engine.initialize({ context: { lookAhead: 0.03 } });
58
+ ```typescript
59
+ import { Engine, ModuleType, OscillatorWave } from "@blibliki/engine";
60
+
61
+ // Create a new audio context
62
+ const context = new AudioContext();
63
+ const engine = new Engine(context);
64
+
65
+ // Initialize the engine
66
+ await engine.initialize();
67
+
68
+ // Create an oscillator
69
+ const oscillator = engine.addModule({
70
+ name: "Oscillator",
71
+ moduleType: ModuleType.Oscillator,
72
+ props: {
73
+ wave: OscillatorWave.sine,
74
+ frequency: 440,
75
+ },
76
+ });
77
+
78
+ // Create a gain module
79
+ const gain = engine.addModule({
80
+ name: "Gain",
81
+ moduleType: ModuleType.Gain,
82
+ props: { gain: 0.5 },
83
+ });
84
+
85
+ // Create a master output
86
+ const master = engine.addModule({
87
+ name: "Master",
88
+ moduleType: ModuleType.Master,
89
+ props: {},
90
+ });
91
+
92
+ // Connect the modules
93
+ engine.addRoute({
94
+ source: { moduleId: oscillator.id, ioName: "out" },
95
+ destination: { moduleId: gain.id, ioName: "in" },
96
+ });
97
+
98
+ engine.addRoute({
99
+ source: { moduleId: gain.id, ioName: "out" },
100
+ destination: { moduleId: master.id, ioName: "in" },
101
+ });
102
+
103
+ // Start the engine
104
+ engine.start();
33
105
  ```
34
106
 
35
- #### Start / Stop
107
+ ## Usage
108
+
109
+ ### Initializing
36
110
 
37
- This methods are triggering all audio modules to start or stop
111
+ ```typescript
112
+ import { Engine, ModuleType, OscillatorWave } from "@blibliki/engine";
38
113
 
39
- ```JavaScript
40
- Engine.start();
41
- Engine.stop();
114
+ // Create a new audio context
115
+ const context = new AudioContext();
116
+ const engine = new Engine(context);
117
+
118
+ // Initialize the engine
119
+ await engine.initialize();
42
120
  ```
43
121
 
44
- #### Modules
122
+ ### Modules
45
123
 
46
- ##### AudioModule structure
124
+ #### AudioModule structure
47
125
 
48
126
  All audio modules share the shame structure.
49
127
  The props structure vary per audioModule.
@@ -51,89 +129,187 @@ The props structure vary per audioModule.
51
129
  ```JavaScript
52
130
  {
53
131
  id: string,
54
- name: string,
55
- props: Object,
132
+ name: string',
133
+ moduleType: ModuleType,
134
+ props: object,
56
135
  inputs: [{
57
136
  id: string,
58
137
  name: string,
59
- moduleId: string,
60
- moduleName: string
138
+ ioType: IOType,
139
+ moduleId: string
61
140
  }],
62
141
  outputs: [{
63
142
  id: string,
64
143
  name: string,
65
- moduleId: string,
66
- moduleName: string
67
- }],
144
+ ioType: IOType,
145
+ moduleId: string
146
+ }]
68
147
  }
69
148
  ```
70
149
 
71
- ##### Create audio module
150
+ Each module comes with a schema that defines the types and accepted values for its properties. These schemas provide TypeScript type safety and validation.
72
151
 
73
- ```JavaScript
74
- const master = Engine.master;
75
- const osc = Engine.addModule({ name: "Osc", type: "Oscillator" });
76
- const volume = Engine.addModule({
77
- name: "Vol",
78
- type: "Volume",
79
- props: { volume: -10 },
152
+ #### Oscillator
153
+
154
+ Generates sound waves with different shapes.
155
+
156
+ ```typescript
157
+ const oscillator = engine.addModule({
158
+ name: "Oscillator",
159
+ moduleType: ModuleType.Oscillator,
160
+ props: {
161
+ wave: OscillatorWave.sine,
162
+ frequency: 440,
163
+ fine: 0,
164
+ coarse: 0,
165
+ octave: 0,
166
+ },
80
167
  });
81
168
  ```
82
169
 
83
- ##### Update props
170
+ #### Gain
84
171
 
85
- ```JavaScript
86
- // Update props
87
- Engine.updateModule({ id: volume.id, changes: { props: { volume: -20 } } });
88
- Engine.updateModule({ id: osc.id, changes: { props: { wave: "square", fine: -10 } } });
172
+ Controls the volume of audio signals.
173
+
174
+ ```typescript
175
+ const gain = engine.addModule({
176
+ name: "Gain",
177
+ moduleType: ModuleType.Gain,
178
+ props: {
179
+ gain: 0.5,
180
+ },
181
+ });
89
182
  ```
90
183
 
91
- ##### Remove audio module
184
+ #### Filter
92
185
 
93
- ```JavaScript
94
- Engine.removeModule(osc.id);
186
+ Filters frequencies from audio signals.
187
+
188
+ ```typescript
189
+ const filter = engine.addModule({
190
+ name: "Filter",
191
+ moduleType: ModuleType.Filter,
192
+ props: {
193
+ type: "lowpass",
194
+ frequency: 1000,
195
+ Q: 1,
196
+ envelopeAmount: 0, // amount of envelope modulation (0 to 1)
197
+ },
198
+ });
95
199
  ```
96
200
 
97
- #### Routes
201
+ #### Envelope
98
202
 
99
- ##### Route structure
203
+ Shapes the volume of sounds over time (ADSR).
100
204
 
101
- ```JavaScript
102
- {
103
- id: string,
104
- sourceId: string,
105
- outputName: string,
106
- destinationId: string,
107
- inputName: string,
108
- }
205
+ ```typescript
206
+ const envelope = engine.addModule({
207
+ name: "Envelope",
208
+ moduleType: ModuleType.Envelope,
209
+ props: {
210
+ attack: 0.01,
211
+ decay: 0.2,
212
+ sustain: 0.7,
213
+ release: 0.5,
214
+ },
215
+ });
109
216
  ```
110
217
 
111
- ##### Add Route
218
+ #### Master
112
219
 
113
- ```JavaScript
114
- // Connect oscillator out to volume input
115
- const oscVolRoute = Engine.addRoute({
116
- sourceId: osc.id,
117
- outputName: "output",
118
- destinationId: volume.id,
119
- inputName: "input",
220
+ Sends audio to your speakers.
221
+
222
+ ```typescript
223
+ const master = engine.addModule({
224
+ name: "Master",
225
+ moduleType: ModuleType.Master,
226
+ props: {},
120
227
  });
228
+ ```
121
229
 
122
- // Connect oscillator out to volume input
123
- const volToMaster = Engine.addRoute({
124
- sourceId: volume.id,
125
- outputName: "output",
126
- destinationId: master.id,
127
- inputName: "input",
230
+ #### MidiSelector
231
+
232
+ Handles MIDI input from keyboards or controllers.
233
+
234
+ ```typescript
235
+ const midi = engine.addModule({
236
+ name: "MIDI",
237
+ moduleType: ModuleType.MidiSelector,
238
+ props: {
239
+ selectedId: "your-midi-device-id",
240
+ },
128
241
  });
129
242
  ```
130
243
 
131
- ##### Remove route
244
+ #### Constant
132
245
 
133
- ```JavaScript
134
- Engine.removeRoute(oscVolRoute.id);
246
+ Creates a fixed value for modulation.
247
+
248
+ ```typescript
249
+ const constant = engine.addModule({
250
+ name: "Constant",
251
+ moduleType: ModuleType.Constant,
252
+ props: {
253
+ value: 1,
254
+ },
255
+ });
256
+ ```
257
+
258
+ #### Scale
259
+
260
+ Converts values from one range to another.
261
+
262
+ ```typescript
263
+ const scale = engine.addModule({
264
+ name: "Scale",
265
+ moduleType: ModuleType.Scale,
266
+ props: {
267
+ min: 0,
268
+ max: 1,
269
+ current: 0.5,
270
+ },
271
+ });
272
+ ```
273
+
274
+ #### Inspector
275
+
276
+ Shows signal values for debugging.
277
+
278
+ ```typescript
279
+ const inspector = engine.addModule({
280
+ name: "Inspector",
281
+ moduleType: ModuleType.Inspector,
282
+ props: {},
283
+ });
284
+ ```
285
+
286
+ #### VirtualMidi
287
+
288
+ Creates MIDI events from code.
289
+
290
+ ```typescript
291
+ const virtualMidi = engine.addModule({
292
+ name: "Virtual MIDI",
293
+ moduleType: ModuleType.VirtualMidi,
294
+ props: {},
295
+ });
296
+ ```
297
+
298
+ ## Development
299
+
300
+ To contribute to the project:
301
+
302
+ ```bash
303
+ # Clone the repository
304
+ git clone https://github.com/mikezaby/blibliki.git
305
+
306
+ # Install dependencies
307
+ pnpm install
308
+
309
+ # Build the project
310
+ pnpm dev
135
311
  ```
136
312
 
137
- ## Contributing
313
+ ## License
138
314
 
139
- As a work-in-progress project, Blibliki Engine actively welcomes contributions and feedback from the community. Whether it's reporting issues, suggesting new features, or submitting code changes, contributors are encouraged to get involved and help shape the future of Blibliki Engine.
315
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";var he=Object.defineProperty;var ct=Object.getOwnPropertyDescriptor;var mt=Object.getOwnPropertyNames;var ht=Object.prototype.hasOwnProperty;var ft=(r,e)=>{for(var t in e)he(r,t,{get:e[t],enumerable:!0})},Mt=(r,e,t,o)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of mt(e))!ht.call(r,i)&&i!==t&&he(r,i,{get:()=>e[i],enumerable:!(o=ct(e,i))||o.enumerable});return r};var yt=r=>Mt(he({},"__esModule",{value:!0}),r);var wt={};ft(wt,{Engine:()=>M,MidiDevice:()=>O,MidiPortState:()=>K,ModuleType:()=>G,Note:()=>p,OscillatorWave:()=>se,TransportState:()=>W,moduleSchemas:()=>lt});module.exports=yt(wt);var F=require("@blibliki/utils");var Q=require("@blibliki/utils");var Fe=require("@blibliki/utils");var de=require("@blibliki/utils");var l=class{id;engineId;moduleType;audioModules;inputs;outputs;monoModuleConstructor;_props;superInitialized=!1;_voices;_name;constructor(e,t){let{id:o,name:i,moduleType:s,voices:n,monoModuleConstructor:m,props:h}=t;this.audioModules=[],this.monoModuleConstructor=m,this.id=o??(0,de.uuidv4)(),this.engineId=e,this.name=i,this.moduleType=s,this.voices=n||1,this._props={},this.props=h,this.inputs=new S(this),this.outputs=new b(this),this.superInitialized=!0}get name(){return this._name}set name(e){this._name=e,this.audioModules.forEach(t=>t.name=e)}get props(){return this._props}set props(e){this._props={...this._props,...e},this.audioModules.forEach(t=>t.props=e)}get voices(){return this._voices}set voices(e){this._voices=e,this.adjustNumberOfModules(),this.rePlugAll()}start(e){this.audioModules.forEach(t=>{t.start(e)})}stop(e){this.audioModules.forEach(t=>{t.stop(e)})}serialize(){return{id:this.id,name:this.name,moduleType:this.moduleType,voices:this.voices,props:this.props,inputs:this.inputs.serialize(),outputs:this.outputs.serialize()}}plug({audioModule:e,from:t,to:o}){let i=this.outputs.findByName(t),s=e.inputs.findByName(o);i.plug(s)}rePlugAll(e){this.superInitialized&&(this.inputs.rePlugAll(e),this.outputs.rePlugAll(e))}unPlugAll(){this.inputs.unPlugAll(),this.outputs.unPlugAll()}dispose(){this.inputs.unPlugAll(),this.outputs.unPlugAll(),this.audioModules.forEach(e=>{e.dispose()})}onMidiEvent=e=>{let t=e.voiceNo??0;this.findVoice(t).onMidiEvent(e)};findVoice(e){let t=this.audioModules.find(o=>o.voiceNo===e);if(!t)throw Error(`Voice ${e} on module ${this.name} not found`);return t}registerDefaultIOs(e="both"){this.registerMidiInput({name:"midi in",onMidiEvent:this.onMidiEvent}),(e==="in"||e==="both")&&this.registerAudioInput({name:"in"}),(e==="out"||e==="both")&&this.registerAudioOutput({name:"out"})}registerAudioInput(e){return this.inputs.add({...e,ioType:"polyAudioInput"})}registerAudioOutput(e){return this.outputs.add({...e,ioType:"polyAudioOutput"})}registerMidiInput(e){return this.inputs.add({...e,ioType:"midiInput"})}registerMidiOutput(e){return this.outputs.add({...e,ioType:"midiOutput"})}adjustNumberOfModules(){if(this.audioModules.length!==this.voices){if(this.audioModules.length>this.voices)this.audioModules.pop()?.dispose();else{let e=this.audioModules.length,t=(0,de.deterministicId)(this.id,e.toString()),o=this.monoModuleConstructor(this.engineId,{id:t,name:this.name,moduleType:this.moduleType,voiceNo:e,props:{...this.props}});this.audioModules.push(o)}this.adjustNumberOfModules()}}get engine(){return M.getById(this.engineId)}get context(){return this.engine.context}};var we=require("@blibliki/utils");var Me=class{id;ioType;name;module;connections;constructor(e,t){this.module=e,this.name=t.name,this.ioType=t.ioType,this.id=(0,we.deterministicId)(this.module.id,this.name),this.connections=[]}plug(e,t=!0){this.connections.push(e),t&&e.plug(this,!1)}unPlug(e,t=!0){this.connections=this.connections.filter(o=>o.id!==e.id),t&&e.unPlug(this,!1)}rePlugAll(e){let t=this.connections;this.unPlugAll(),e&&e(),t.forEach(o=>{this.plug(o)})}unPlugAll(){this.connections.forEach(e=>{this.unPlug(e)})}isAudio(){return this.ioType==="audioInput"||this.ioType==="audioOutput"||this.ioType==="polyAudioInput"||this.ioType==="polyAudioOutput"}isMidi(){return this.ioType==="midiInput"||this.ioType==="midiOutput"}serialize(){return{id:this.id,name:this.name,ioType:this.ioType,moduleId:this.module.id}}},y=class extends Me{plug(e,t){super.plug(e,t)}unPlug(e,t){super.unPlug(e,t)}};var g=class extends y{plug(e,t=!0){super.plug(e,t),!(!t&&e instanceof N)&&ae(this,e,!0)}unPlug(e,t=!0){super.unPlug(e,t),!(!t&&e instanceof N)&&ae(this,e,!1)}findIOByVoice(e){return this.module.findVoice(e).inputs.findByName(this.name)}},N=class extends y{plug(e,t=!0){super.plug(e,t),!(!t&&e instanceof g)&&ae(this,e,!0)}unPlug(e,t=!0){super.unPlug(e,t),!(!t&&e instanceof g)&&ae(this,e,!1)}findIOByVoice(e){return this.module.findVoice(e).outputs.findByName(this.name)}};function ae(r,e,t){if(e instanceof g||e instanceof N){let o=Math.max(r.module.voices,e.module.voices);for(let i=0;i<o;i++){let s=r.findIOByVoice(i%r.module.voices),n=e.findIOByVoice(i%e.module.voices);t?s.plug(n):s.unPlug(n)}}else for(let o=0;o<r.module.voices;o++){let i=r.findIOByVoice(o);t?i.plug(e):i.unPlug(e)}}var R=class extends y{getAudioNode;constructor(e,t){super(e,t),this.getAudioNode=t.getAudioNode}},_=class extends y{getAudioNode;constructor(e,t){super(e,t),this.getAudioNode=t.getAudioNode}plug(e,t=!0){if(super.plug(e,t),e instanceof g)return;let o=e.getAudioNode();o instanceof AudioParam?this.getAudioNode().connect(o):this.getAudioNode().connect(o)}unPlug(e,t=!0){if(super.unPlug(e,t),e instanceof g)return;let o=e.getAudioNode();try{o instanceof AudioParam?this.getAudioNode().disconnect(o):this.getAudioNode().disconnect(o)}catch{}}};var D=class extends y{onMidiEvent;constructor(e,t){super(e,t),this.onMidiEvent=t.onMidiEvent}},L=class extends y{onMidiEvent=e=>{this.midiConnections.forEach(t=>{t.onMidiEvent(e)})};get midiConnections(){return this.connections.filter(e=>e instanceof D)}};var U=class{module;collection=[];collectionType;constructor(e,t){this.collectionType=e,this.module=t}add(e){let t;switch(this.validateUniqName(e.name),e.ioType){case"audioInput":if(this.module instanceof l)throw Error("Not compatible");t=new R(this.module,e);break;case"audioOutput":if(this.module instanceof l)throw Error("Not compatible");t=new _(this.module,e);break;case"polyAudioInput":if(this.module instanceof u)throw Error("Not compatible");t=new g(this.module,e);break;case"polyAudioOutput":if(this.module instanceof u)throw Error("Not compatible");t=new N(this.module,e);break;case"midiInput":t=new D(this.module,e);break;case"midiOutput":t=new L(this.module,e);break;default:(0,Fe.assertNever)(e)}return this.collection.push(t),t}unPlugAll(){this.collection.forEach(e=>{e.unPlugAll()})}rePlugAll(e){this.collection.forEach(t=>{t.rePlugAll(e)})}find(e){let t=this.collection.find(o=>o.id===e);if(!t)throw Error(`The io with id ${e} is not exists`);return t}findByName(e){let t=this.collection.find(o=>o.name===e);if(!t)throw Error(`The io with name ${e} is not exists`);return t}serialize(){return this.collection.map(e=>e.serialize())}validateUniqName(e){if(this.collection.some(t=>t.name===e))throw Error(`An io with name ${e} is already exists`)}},S=class extends U{constructor(e){super("Input",e)}},b=class extends U{constructor(e){super("Output",e)}};var ge=require("webmidi");var Tt=new Map([["C0",16.35],["C#0",17.32],["Db0",17.32],["D0",18.35],["D#0",19.45],["Eb0",19.45],["E0",20.6],["F0",21.83],["F#0",23.12],["Gb0",23.12],["G0",24.5],["G#0",25.96],["Ab0",25.96],["A0",27.5],["A#0",29.14],["Bb0",29.14],["B0",30.87],["C1",32.7],["C#1",34.65],["Db1",34.65],["D1",36.71],["D#1",38.89],["Eb1",38.89],["E1",41.2],["F1",43.65],["F#1",46.25],["Gb1",46.25],["G1",49],["G#1",51.91],["Ab1",51.91],["A1",55],["A#1",58.27],["Bb1",58.27],["B1",61.74],["C2",65.41],["C#2",69.3],["Db2",69.3],["D2",73.42],["D#2",77.78],["Eb2",77.78],["E2",82.41],["F2",87.31],["F#2",92.5],["Gb2",92.5],["G2",98],["G#2",103.83],["Ab2",103.83],["A2",110],["A#2",116.54],["Bb2",116.54],["B2",123.47],["C3",130.81],["C#3",138.59],["Db3",138.59],["D3",146.83],["D#3",155.56],["Eb3",155.56],["E3",164.81],["F3",174.61],["F#3",185],["Gb3",185],["G3",196],["G#3",207.65],["Ab3",207.65],["A3",220],["A#3",233.08],["Bb3",233.08],["B3",246.94],["C4",261.63],["C#4",277.18],["Db4",277.18],["D4",293.66],["D#4",311.13],["Eb4",311.13],["E4",329.63],["F4",349.23],["F#4",369.99],["Gb4",369.99],["G4",392],["G#4",415.3],["Ab4",415.3],["A4",440],["A#4",466.16],["Bb4",466.16],["B4",493.88],["C5",523.25],["C#5",554.37],["Db5",554.37],["D5",587.33],["D#5",622.25],["Eb5",622.25],["E5",659.26],["F5",698.46],["F#5",739.99],["Gb5",739.99],["G5",783.99],["G#5",830.61],["Ab5",830.61],["A5",880],["A#5",932.33],["Bb5",932.33],["B5",987.77],["C6",1046.5],["C#6",1108.73],["Db6",1108.73],["D6",1174.66],["D#6",1244.51],["Eb6",1244.51],["E6",1318.51],["F6",1396.91],["F#6",1479.98],["Gb6",1479.98],["G6",1567.98],["G#6",1661.22],["Ab6",1661.22],["A6",1760],["A#6",1864.66],["Bb6",1864.66],["B6",1975.53],["C7",2093],["C#7",2217.46],["Db7",2217.46],["D7",2349.32],["D#7",2489.02],["Eb7",2489.02],["E7",2637.02],["F7",2793.83],["F#7",2959.96],["Gb7",2959.96],["G7",3135.96],["G#7",3322.44],["Ab7",3322.44],["A7",3520],["A#7",3729.31],["Bb7",3729.31],["B7",3951.07],["C8",4186.01],["C#8",4434.92],["Db8",4434.92],["D8",4698.64],["D#8",4978.03],["Eb8",4978.03]]),ye=Tt;var Te=["C","C#","D","D#","E","F","F#","G","G#","A","A#","B"],It=2,p=class r{static _notes;name;octave;velocity=1;duration;static fromFrequency(e){let t;for(let[o,i]of ye)if(i===e){t=o;break}if(!t)throw Error("Not matching frequency with a note");return new r(t)}static fromEvent(e){let t=Te[e.data[1]%12],o=Math.floor(e.data[1]/12)-2;return new r(`${t}${o}`)}static notes(e=3){return Te.map(t=>new r(`${t}${e}`))}constructor(e){typeof e=="string"?this.fromString(e):this.fromProps(e)}get isSemi(){return this.name.endsWith("#")}get fullName(){return`${this.name}${this.octave}`}get frequency(){return ye.get(`${this.name}${this.octave}`)}midiData(e=!0){let t=e?144:128;return new Uint8Array([t,this.midiNumber,this.velocity*100])}get midiNumber(){return(this.octave+It)*12+this.noteIndex}get noteIndex(){return Te.indexOf(this.name)}valueOf(){return this.fullName}serialize(){return{name:this.name,octave:this.octave,frequency:this.frequency,velocity:this.velocity,duration:this.duration}}fromString(e){let t=/(\w#?)(\d)?/.exec(e)??[];this.name=t[1],this.octave=t[2]?parseInt(t[2]):1}fromProps(e){Object.assign(this,e)}};var Ie=require("@blibliki/utils");var ke=require("@ircam/sc-scheduling");var $=class{transport;internalScheduler;constructor(e){this.transport=e,this.internalScheduler=new ke.Scheduler(T,{currentTimeToProcessorTimeFunction:()=>this.transport.playhead})}start(e,t){this.internalScheduler.add(this.processor,a(e)),this.defer(t,e)}stop(e,t){this.defer(()=>{this.internalScheduler.remove(this.processor),t()},e)}defer(e,t){this.internalScheduler.defer(e,a(t))}processor=(e,t)=>(console.log(`playhead: ${c(t).toNotation()}`),e+.5)};var W=(o=>(o.playing="playing",o.stopped="stopped",o.paused="paused",o))(W||{}),C=class{bpm=120;timeSignature=[4,4];loopStart;loopEnd;state="stopped";offset=0;startTime=0;onStart;onStop;scheduler;constructor(e){this.onStart=e.onStart,this.onStop=e.onStop,this.loopStart=c("0:0:0"),this.scheduler=new $(this)}start({offset:e=this.offset,actionAt:t=T()}){if(this.state!=="playing")return this.validateFutureTime(t),this.scheduler.start(t,()=>{this.state="playing",this.offset=e,this.startTime=c(t).subtrack(this.offset)}),this.onStart?.(t),t}stop({actionAt:e=T()}){if(this.state!=="stopped")return this.validateFutureTime(e),this.scheduler.stop(e,()=>{this.state="stopped",this.offset=0}),this.onStop?.(e),e}pause({actionAt:e=T()}){if(this.state!=="paused")return this.validateFutureTime(e),this.scheduler.stop(e,()=>{this.state="paused",this.offset=c(e).subtrack(this.startTime)}),this.onStop?.(e),e}get playhead(){return this.state==="stopped"?c(0):this.state==="paused"?c(this.offset):c(T()).subtrack(this.startTime)}validateFutureTime(e){if(c(e).isBefore(T()))throw Error("Past time not allowed")}};function T(){return M.current.context.currentTime}function De(r){let e=performance.now()/1e3-T();return r/1e3-e}var c=r=>(r??=T(),r instanceof E?r:new E(r)),a=r=>(r??=T(),typeof r=="number"?r:c(r).toNumber()),E=class r{value;_notation;_number;constructor(e){this.value=e instanceof r?e.value:e,(0,Ie.isNumber)(this.value)?this._number=this.value:this._notation=this.value}add(e){return c(this.toNumber()+c(e).toNumber())}subtrack(e){return c(this.toNumber()-a(e))}isBefore(e){return this.toNumber()<a(e)}isAfter(e){return this.toNumber()>a(e)}isEqual(e){return this.toNumber()>a(e)}toNotation(){if(this._notation)return this._notation;let[e,t]=this.transport.timeSignature,o=16/t,i=e*o,s=60/this.transport.bpm,n=Math.floor(this.value/s*o),m=Math.floor(n/i),h=Math.floor(n%i/o),I=n%o;return this._notation=`${m}:${h}:${I}`,this._notation}toNumber(){if((0,Ie.isNumber)(this._number))return this._number;let[e,t]=this.transport.timeSignature,o=60/this.transport.bpm,[i,s,n]=this.value.split(":").map(Number),m=i*e+s+n/(t/4);return this._number=m*o,this._number}get transport(){return M.current.transport}};var f=class r{note;voiceNo;triggeredAt;message;static fromNote(e,t=!0,o){let i=e instanceof p?e:new p(e);return new r(new ge.Message(i.midiData(t)),o)}static fromCC(e,t,o){return new r(new ge.Message(new Uint8Array([176,e,t])),o)}constructor(e,t){this.message=e,this.triggeredAt=t??c(),this.defineNotes()}get type(){return this.message.type}get isNote(){return this.type==="noteon"||this.type==="noteoff"}defineNotes(){this.isNote&&(this.note||(this.note=p.fromEvent(this.message)))}get rawMessage(){return this.message}clone(e){let t=new r(this.message,this.triggeredAt);return t.voiceNo=e,t}};var u=class{id;engineId;name;moduleType;voiceNo;audioNode;inputs;outputs;_props;superInitialized=!1;activeNotes;constructor(e,t){let{id:o,name:i,moduleType:s,voiceNo:n,audioNodeConstructor:m,props:h}=t;this.id=o??(0,Q.uuidv4)(),this.engineId=e,this.name=i,this.moduleType=s,this.voiceNo=n??0,this.activeNotes=[],this.audioNode=m?.(this.context),this._props={},this.props=h,this.inputs=new S(this),this.outputs=new b(this),this.superInitialized=!0}get props(){return this._props}set props(e){Object.keys(e).forEach(t=>{let o=`onSet${(0,Q.upperFirst)(t)}`;this[o]?.(e[t])}),this._props={...this._props,...e},Object.keys(e).forEach(t=>{let o=`onAfterSet${(0,Q.upperFirst)(t)}`;this[o]?.(e[t])})}serialize(){return{id:this.id,name:this.name,moduleType:this.moduleType,voiceNo:this.voiceNo,props:this.props,inputs:this.inputs.serialize(),outputs:this.outputs.serialize()}}plug({audioModule:e,from:t,to:o}){let i=this.outputs.findByName(t),s=e.inputs.findByName(o);i.plug(s)}rePlugAll(e){this.inputs.rePlugAll(e),this.outputs.rePlugAll(e)}unPlugAll(){this.inputs.unPlugAll(),this.outputs.unPlugAll()}start(e){}stop(e){}triggerAttack(e,t){this.activeNotes.some(o=>o.fullName===e.fullName)||this.activeNotes.push(e)}triggerRelease(e,t){this.activeNotes=this.activeNotes.filter(o=>o.fullName!==e.fullName)}onMidiEvent=e=>{let{note:t,triggeredAt:o}=e;switch(e.type){case"noteon":{this.triggerAttack(t,o);break}case"noteoff":this.triggerRelease(t,o);break;default:throw Error("This type is not a note")}};triggerPropsUpdate(){this.engine._triggerPropsUpdate({id:this.id,moduleType:this.moduleType,voiceNo:this.voiceNo,name:this.name,props:this.props})}dispose(){this.inputs.unPlugAll(),this.outputs.unPlugAll()}registerDefaultIOs(e="both"){this.registerMidiInput({name:"midi in",onMidiEvent:this.onMidiEvent}),this.audioNode&&((e==="in"||e==="both")&&this.registerAudioInput({name:"in",getAudioNode:()=>this.audioNode}),(e==="out"||e==="both")&&this.registerAudioOutput({name:"out",getAudioNode:()=>this.audioNode}))}registerAudioInput(e){return this.inputs.add({...e,ioType:"audioInput"})}registerAudioOutput(e){return this.outputs.add({...e,ioType:"audioOutput"})}registerMidiInput(e){return this.inputs.add({...e,ioType:"midiInput"})}registerMidiOutput(e){return this.outputs.add({...e,ioType:"midiOutput"})}get engine(){return M.getById(this.engineId)}get context(){return this.engine.context}};var qe=require("@blibliki/utils"),j=class{engine;routes;constructor(e){this.engine=e,this.routes=new Map}addRoute(e){let t=e.id??(0,qe.uuidv4)(),o={...e,id:t};return this.routes.set(t,o),this.plug(t),o}removeRoute(e){this.unPlug(e),this.routes.delete(e)}clear(){for(let e in this.routes)this.removeRoute(e)}plug(e){let{sourceIO:t,destinationIO:o}=this.getIOs(e);t.plug(o)}unPlug(e){let{sourceIO:t,destinationIO:o}=this.getIOs(e);t.unPlug(o)}find(e){let t=this.routes.get(e);if(!t)throw Error(`Route with id ${e} not found`);return t}getIOs(e){let t=this.find(e),{source:o,destination:i}=t,s=this.engine.findIO(o.moduleId,o.ioName,"output"),n=this.engine.findIO(i.moduleId,i.ioName,"input");return{sourceIO:s,destinationIO:n}}};var x=require("webmidi");var K=(t=>(t.connected="connected",t.disconnected="disconnected",t))(K||{}),O=class{id;name;eventListerCallbacks=[];input;constructor(e){this.id=e.id,this.name=e.name||`Device ${e.id}`,this.input=e,this.connect()}get state(){return this.input.state}connect(){this.input.addListener("midimessage",e=>{this.processEvent(e)})}disconnect(){this.input.removeListener()}serialize(){let{id:e,name:t,state:o}=this;return{id:e,name:t,state:o}}addEventListener(e){this.eventListerCallbacks.push(e)}removeEventListener(e){this.eventListerCallbacks=this.eventListerCallbacks.filter(t=>t!==e)}processEvent(e){let t=new f(e.message,De(e.timestamp));switch(t.type){case"noteon":case"noteoff":this.eventListerCallbacks.forEach(o=>{o(t)})}}};var gt={a:new p("C3"),s:new p("D3"),d:new p("E3"),f:new p("F3"),g:new p("G3"),h:new p("A3"),j:new p("B3"),k:new p("C4"),l:new p("D4"),w:new p("C#3"),e:new p("D#3"),t:new p("F#3"),y:new p("G#3"),u:new p("A#3"),o:new p("C#4"),p:new p("D#4")},Pt=()=>({id:"computer_keyboard",name:"Computer Keyboard",state:"connected"}),q=class{id;name;state;eventListerCallbacks=[];constructor(){let{id:e,name:t,state:o}=Pt();this.id=e,this.name=t,this.state=o,document.addEventListener("keydown",this.onKeyTrigger(!0)),document.addEventListener("keyup",this.onKeyTrigger(!1))}addEventListener(e){this.eventListerCallbacks.push(e)}removeEventListener(e){this.eventListerCallbacks=this.eventListerCallbacks.filter(t=>t!==e)}serialize(){let{id:e,name:t,state:o}=this;return{id:e,name:t,state:o}}onKeyTrigger=e=>t=>{let o=this.extractNote(t);if(!o)return;let i=f.fromNote(o,e);this.eventListerCallbacks.forEach(s=>{s(i)})};extractNote(e){if(!e.repeat)return gt[e.key]}};var B=class{devices=new Map;initialized=!1;listeners=[];constructor(){let e=new q;this.devices.set(e.id,e)}async initialize(){await this.initializeDevices(),this.listenChanges(),this.initialized=!0}find(e){return this.devices.get(e)}addListener(e){this.listeners.push(e)}async initializeDevices(){if(!this.initialized)try{await x.WebMidi.enable(),x.WebMidi.inputs.forEach(e=>{this.devices.has(e.id)||this.devices.set(e.id,new O(e))})}catch(e){console.error("Error enabling WebMidi:",e)}}listenChanges(){x.WebMidi.addListener("connected",e=>{let t=e.port;if(t instanceof x.Output||this.devices.has(t.id))return;let o=new O(t);this.devices.set(o.id,o),this.listeners.forEach(i=>{i(o)})}),x.WebMidi.addListener("disconnected",e=>{let t=e.port;if(t instanceof x.Output)return;let o=this.devices.get(t.id);o&&(o instanceof q||(o.disconnect(),this.devices.delete(o.id),this.listeners.forEach(i=>{i(o)})))})}};var at=require("@blibliki/utils");var Be={},Ge={},Ae=class extends u{activeNote=null;triggeredAt=c(0);constructor(e,t){let o={...Ge,...t.props};super(e,{...t,props:o})}midiTriggered=e=>{let{triggeredAt:t,note:o,type:i}=e;if(!o)return;let s=o.fullName;switch(i){case"noteon":this.activeNote=s,this.triggeredAt=t;break;case"noteoff":this.activeNote=null;break;default:throw Error("This type is not a note")}}},H=class extends l{midiOutput;constructor(e,t){let o={...Ge,...t.props},i=(s,n)=>new Ae(s,n);super(e,{...t,props:o,monoModuleConstructor:i}),this.registerInputs(),this.registerOutputs()}onMidiEvent=e=>{let t;switch(e.type){case"noteon":t=this.findFreeVoice();break;case"noteoff":t=this.audioModules.find(o=>o.activeNote===e.note.fullName);break;default:throw Error("This type is not a note")}t&&(t.midiTriggered(e),e.voiceNo=t.voiceNo,this.midiOutput.onMidiEvent(e))};findFreeVoice(){let e=this.audioModules.find(t=>!t.activeNote);return e??=this.audioModules.sort((t,o)=>a(t.triggeredAt)-a(o.triggeredAt))[0],e}registerInputs(){this.registerMidiInput({name:"midi in",onMidiEvent:this.onMidiEvent})}registerOutputs(){this.midiOutput=this.registerMidiOutput({name:"midi out"})}};var Ve={gain:{kind:"number",min:0,max:1/0,step:.01,label:"Gain"}},ze={gain:1},w=class extends u{constructor(e,t){let o={...ze,...t.props},i=s=>new GainNode(s);super(e,{...t,audioNodeConstructor:i,props:o}),this.registerDefaultIOs(),this.registerAdditionalInputs()}onSetGain(e){this.audioNode.gain.value=e}registerAdditionalInputs(){this.registerAudioInput({name:"gain",getAudioNode:()=>this.audioNode.gain})}},X=class extends l{constructor(e,t){let o={...ze,...t.props},i=(s,n)=>new w(s,n);super(e,{...t,props:o,monoModuleConstructor:i}),this.registerAdditionalInputs(),this.registerDefaultIOs()}registerAdditionalInputs(){this.registerAudioInput({name:"gain"})}};var Re=20,Oe=2e4,_e={cutoff:Oe,envelopeAmount:0,type:"lowpass",Q:1},Le={cutoff:{kind:"number",min:Re,max:Oe,step:1,label:"Cutoff"},envelopeAmount:{kind:"number",min:-1,max:1,step:.01,label:"Envelope Amount"},type:{kind:"enum",options:["lowpass","highpass","bandpass"],label:"Type"},Q:{kind:"number",min:-100,max:100,step:.1,label:"Q"}},ve=class extends u{scale;amount;constructor(e,t){let o={..._e,...t.props},i=s=>new BiquadFilterNode(s,{type:o.type,frequency:o.cutoff,Q:o.Q});super(e,{...t,props:o,audioNodeConstructor:i}),this.amount=new w(e,{name:"amount",moduleType:"Gain",props:{gain:o.envelopeAmount}}),this.scale=V(e,{name:"scale",moduleType:"Scale",props:{min:Re,max:Oe,current:this.props.cutoff}}),this.amount.plug({audioModule:this.scale,from:"out",to:"in"}),this.scale.audioNode.connect(this.audioNode.frequency),this.registerDefaultIOs(),this.registerInputs()}onSetType(e){this.audioNode.type=e}onSetCutoff(e){this.superInitialized&&(this.scale.props={current:e})}onSetQ(e){this.audioNode.Q.value=e}onSetEnvelopeAmount(e){this.superInitialized&&(this.amount.props={gain:e})}registerInputs(){this.registerAudioInput({name:"cutoff",getAudioNode:()=>this.audioNode.frequency}),this.registerAudioInput({name:"cutoffMod",getAudioNode:()=>this.amount.audioNode}),this.registerAudioInput({name:"Q",getAudioNode:()=>this.audioNode.Q})}},Y=class extends l{constructor(e,t){let o={..._e,...t.props},i=(s,n)=>new ve(s,n);super(e,{...t,props:o,monoModuleConstructor:i}),this.registerInputs(),this.registerDefaultIOs()}registerInputs(){this.registerAudioInput({name:"cutoff"}),this.registerAudioInput({name:"cutoffMod"}),this.registerAudioInput({name:"Q"})}};var Ue={value:{kind:"number",min:-1/0,max:1/0,step:.01,label:"Value"}},At={value:1},J=class extends u{isStated=!1;constructor(e,t){let o={...At,...t.props},i=s=>new ConstantSourceNode(s);super(e,{...t,props:o,audioNodeConstructor:i}),this.registerDefaultIOs("out")}onSetValue(e){this.audioNode.offset.value=e}start(e){this.isStated||(this.isStated=!0,this.audioNode.start(a(e)))}stop(e){this.audioNode.stop(a(e)),this.rePlugAll(()=>{this.audioNode=new ConstantSourceNode(this.context,{offset:this.props.value})}),this.isStated=!1}triggerAttack=(e,t)=>{this.audioNode.offset.setValueAtTime(e.frequency,a(t)),this.start(t)};triggerRelease=()=>{}};var Se=require("@blibliki/utils");var We={attack:.1,decay:.2,sustain:0,release:.3},Qe={attack:{kind:"number",min:1e-4,max:1,step:.01,label:"Attack"},decay:{kind:"number",min:0,max:1,step:.01,label:"Decay"},sustain:{kind:"number",min:0,max:1,step:.01,label:"Sustain"},release:{kind:"number",min:0,max:1,step:.01,label:"Release"}},$e=(0,Se.createScaleNormalized)({min:.001,max:10}),vt=(0,Se.createScaleNormalized)({min:.001,max:5}),xe=class extends u{constructor(e,t){let o={...We,...t.props},i=s=>{let n=new GainNode(s);return n.gain.value=0,n};super(e,{...t,props:o,audioNodeConstructor:i}),this.registerDefaultIOs()}triggerAttack(e,t){super.triggerAttack(e,t);let o=this.scaledAttack(),i=this.scaledDecay(),s=this.props.sustain,n=a(t);this.audioNode.gain.cancelAndHoldAtTime(n),this.audioNode.gain.value===0&&this.audioNode.gain.setValueAtTime(.001,n),this.audioNode.gain.exponentialRampToValueAtTime(1,n+o),s>0?this.audioNode.gain.exponentialRampToValueAtTime(s,n+o+i):this.audioNode.gain.exponentialRampToValueAtTime(.001,n+o+i)}triggerRelease(e,t){if(super.triggerRelease(e,t),this.activeNotes.length>0)return;let o=this.scaledRelease(),i=a(t);this.audioNode.gain.cancelAndHoldAtTime(i);let s=this.audioNode.gain.value;s>=1e-4&&(this.audioNode.gain.setValueAtTime(s,i),this.audioNode.gain.exponentialRampToValueAtTime(1e-4,i+o-1e-4)),this.audioNode.gain.setValueAtTime(0,i+o)}scaledAttack(){return $e(this.props.attack)}scaledDecay(){return vt(this.props.decay)}scaledRelease(){return $e(this.props.release)}},Z=class extends l{constructor(e,t){let o={...We,...t.props},i=(s,n)=>new xe(s,n);super(e,{...t,props:o,monoModuleConstructor:i}),this.registerDefaultIOs()}};var He=require("@blibliki/utils");var je=URL.createObjectURL(new Blob(["(",(()=>{class r extends AudioWorkletProcessor{s0;s1;constructor(){super(),this.s0=0,this.s1=0}static get parameterDescriptors(){return[{name:"cutoff",defaultValue:1e3,minValue:20,maxValue:2e4},{name:"resonance",defaultValue:0,minValue:0,maxValue:4}]}process(t,o,i){let s=t[0],n=o[0],m=i.cutoff,h=i.resonance;for(let I=0;I<s.length;I++){let P=s[I],k=n[I];for(let A=0;A<P.length;A++){let d=P[A],z=m.length>1?m[A]:m[0],ce=Math.max(20,Math.min(2e4,z)),me=Math.log(ce/20)/Math.log(2e4/20),v=Math.pow(.5,(1-me)/.125),Ee=1-Math.pow(.5,((h.length>1?h[A]:h[0])+.125)/.125)*v;this.s0=Ee*this.s0-v*this.s1+v*d,this.s1=Ee*this.s1+v*this.s0,k[A]=this.s1}}return!0}}registerProcessor("filter-processor",r)}).toString(),")()"],{type:"application/javascript"}));var Ke=URL.createObjectURL(new Blob(["(",(()=>{class r extends AudioWorkletProcessor{static get parameterDescriptors(){return[{name:"min",defaultValue:1e-10},{name:"max",defaultValue:1},{name:"current",defaultValue:.5}]}process(t,o,i){let s=t[0],n=o[0],m=i.min,h=i.max,I=i.current;if(!s.length||s[0].length===0){for(let P of n){let k=(i.current.length>1,i.current[0]);P.fill(k)}return!0}for(let P=0;P<s.length;P++){let k=s[P],A=n[P];for(let d=0;d<k.length;d++){let z=k[d],ce=m.length>1?m[d]:m[0],me=h.length>1?h[d]:h[0],v=I.length>1?I[d]:I[0];z<0?A[d]=v*Math.pow(ce/v,-z):A[d]=v*Math.pow(me/v,z)}}return!0}}registerProcessor("scale-processor",r)}).toString(),")()"],{type:"application/javascript"}));async function Xe(r){await r.audioWorklet.addModule(Ke),await r.audioWorklet.addModule(je)}function le(r,e){switch(e){case"ScaleProcessor":return new AudioWorkletNode(r,"scale-processor");case"FilterProcessor":return new AudioWorkletNode(r,"filter-processor");default:(0,He.assertNever)(e)}}var Ye=20,Ne=22050,Je={cutoff:Ne,envelopeAmount:0,resonance:0},Ze={cutoff:{kind:"number",min:Ye,max:Ne,step:1e-4,label:"Cutoff"},envelopeAmount:{kind:"number",min:-1,max:1,step:.01,label:"Envelope Amount"},resonance:{kind:"number",min:0,max:4,step:.01,label:"resonance"}},be=class extends u{scale;amount;constructor(e,t){let o={...Je,...t.props},i=s=>le(s,"FilterProcessor");super(e,{...t,props:o,audioNodeConstructor:i}),this.amount=new w(e,{name:"amount",moduleType:"Gain",props:{gain:o.envelopeAmount}}),this.scale=V(e,{name:"scale",moduleType:"Scale",props:{min:Ye,max:Ne,current:this.props.cutoff}}),this.amount.plug({audioModule:this.scale,from:"out",to:"in"}),this.scale.audioNode.connect(this.cutoff),this.registerDefaultIOs(),this.registerInputs()}get cutoff(){return this.audioNode.parameters.get("cutoff")}get resonance(){return this.audioNode.parameters.get("resonance")}onSetCutoff(e){this.superInitialized&&(this.scale.props={current:e})}onSetResonance(e){this.resonance.value=e}onSetEnvelopeAmount(e){this.superInitialized&&(this.amount.props={gain:e})}registerInputs(){this.registerAudioInput({name:"cutoff",getAudioNode:()=>this.scale.audioNode}),this.registerAudioInput({name:"cutoffMod",getAudioNode:()=>this.amount.audioNode}),this.registerAudioInput({name:"Q",getAudioNode:()=>this.resonance})}},ee=class extends l{constructor(e,t){let o={...Je,...t.props},i=(s,n)=>new be(s,n);super(e,{...t,props:o,monoModuleConstructor:i}),this.registerInputs(),this.registerDefaultIOs()}registerInputs(){this.registerAudioInput({name:"cutoff"}),this.registerAudioInput({name:"cutoffMod"}),this.registerAudioInput({name:"Q"})}};var et={fftSize:{kind:"enum",options:[32,64,128,256,512,1024,2048,4096,8192,16384,32768],label:"FFT size"}},xt={fftSize:512},te=class extends u{_buffer;constructor(e,t){let o={...xt,...t.props},i=s=>new AnalyserNode(s);super(e,{...t,props:o,audioNodeConstructor:i}),this.registerDefaultIOs("in")}onSetFftSize(e){this._buffer=new Float32Array(e)}get buffer(){return this._buffer?this._buffer:(this._buffer=new Float32Array(this.props.fftSize),this._buffer)}getValue(){return this.getValues()[0]}getValues(){return this.audioNode.getFloatTimeDomainData(this.buffer),this.buffer}};var St={},tt={},oe=class extends u{constructor(e,t){let o={...St,...t.props},i=s=>s.destination;super(e,{...t,audioNodeConstructor:i,props:o}),this.registerDefaultIOs("in")}};var ot={selectedId:{kind:"string",label:"Midi device ID"}},bt={selectedId:void 0},ie=class extends u{midiOutput;_forwardMidiEvent;constructor(e,t){let o={...bt,...t.props};super(e,{...t,props:o}),this.addEventListener(this.props.selectedId),this.registerOutputs()}onSetSelectedId(e){this.superInitialized&&(this.removeEventListener(),this.addEventListener(e))}get forwardMidiEvent(){return this._forwardMidiEvent?this._forwardMidiEvent:(this._forwardMidiEvent=e=>{this.midiOutput.onMidiEvent(e)},this._forwardMidiEvent)}addEventListener(e){if(!e)return;this.engine.findMidiDevice(e)?.addEventListener(this.forwardMidiEvent)}removeEventListener(){if(!this.props.selectedId)return;this.engine.findMidiDevice(this.props.selectedId)?.removeEventListener(this.forwardMidiEvent)}registerOutputs(){this.midiOutput=this.registerMidiOutput({name:"midi out"})}};var it=require("@blibliki/utils");var rt=-18,se=(i=>(i.sine="sine",i.triangle="triangle",i.square="square",i.sawtooth="sawtooth",i))(se||{}),st={wave:{kind:"enum",options:Object.values(se),label:"Waveform"},frequency:{kind:"number",min:0,max:25e3,step:1,label:"Frequency"},fine:{kind:"number",min:-1,max:1,step:.01,label:"Fine"},coarse:{kind:"number",min:-12,max:12,step:1,label:"Coarse"},octave:{kind:"number",min:-4,max:4,step:1,label:"Octave"},lowGain:{kind:"boolean",label:`Use ${rt}db Gain`}},nt={wave:"sine",frequency:440,fine:0,coarse:0,octave:0,lowGain:!1},Ce=class extends u{isStated=!1;lowOutputGain;detuneGain;constructor(e,t){let o={...nt,...t.props},i=s=>new OscillatorNode(s);super(e,{...t,props:o,audioNodeConstructor:i}),this.lowOutputGain=new GainNode(this.context,{gain:(0,it.dbToGain)(rt)}),this.applyOutputGain(),this.initializeGainDetune(),this.registerInputs(),this.registerOutputs()}onAfterSetWave(e){this.audioNode.type=e}onAfterSetFrequency(){this.updateFrequency()}onAfterSetFine(){this.updateFrequency()}onAfterSetCoarse(){this.updateFrequency()}onAfterSetOctave(){this.updateFrequency()}onAfterSetLowGain(){this.superInitialized&&this.rePlugAll()}start(e){this.isStated||(this.isStated=!0,this.audioNode.start(a(e)))}stop(e){this.audioNode.stop(a(e)),this.rePlugAll(()=>{this.audioNode=new OscillatorNode(this.context,{type:this.props.wave,frequency:this.finalFrequency}),this.applyOutputGain(),this.detuneGain.connect(this.audioNode.detune)}),this.isStated=!1}triggerAttack=(e,t)=>{super.triggerAttack(e,t),this.props={frequency:e.frequency},this.updateFrequency(t),this.start(t)};triggerRelease(e,t){super.triggerRelease(e,t);let o=this.activeNotes.length?this.activeNotes[this.activeNotes.length-1]:null;o&&(this.props={frequency:o.frequency},this.updateFrequency(t))}get finalFrequency(){let{frequency:e,coarse:t,octave:o,fine:i}=this.props;return this.superInitialized?e*Math.pow(2,t/12+o+i/12):void 0}updateFrequency(e){this.finalFrequency!==void 0&&(e?this.audioNode.frequency.setValueAtTime(this.finalFrequency,a(e)):this.audioNode.frequency.value=this.finalFrequency)}applyOutputGain(){this.audioNode.connect(this.lowOutputGain)}initializeGainDetune(){this.detuneGain=new GainNode(this.context,{gain:100}),this.detuneGain.connect(this.audioNode.detune)}registerInputs(){this.registerAudioInput({name:"detune",getAudioNode:()=>this.detuneGain})}registerOutputs(){this.registerAudioOutput({name:"out",getAudioNode:()=>this.props.lowGain?this.lowOutputGain:this.audioNode})}},re=class extends l{constructor(e,t){let o={...nt,...t.props},i=(s,n)=>new Ce(s,n);super(e,{...t,props:o,monoModuleConstructor:i}),this.registerInputs(),this.registerDefaultIOs("out")}start(e){this.audioModules.forEach(t=>{t.start(e)})}stop(e){this.audioModules.forEach(t=>{t.stop(e)})}registerInputs(){this.registerAudioInput({name:"detune"})}};var ut={min:{kind:"number",min:-1/0,max:1/0,step:.01,label:"Min"},max:{kind:"number",min:-1/0,max:1/0,step:.01,label:"Max"},current:{kind:"number",min:-1/0,max:1/0,step:.01,label:"Current"}},Nt={min:0,max:1,current:.5},ne=class extends u{constructor(e,t){let o={...Nt,...t.props},i=s=>le(s,"ScaleProcessor");super(e,{...t,props:o,audioNodeConstructor:i}),this.registerDefaultIOs()}get current(){return this.audioNode.parameters.get("current")}get min(){return this.audioNode.parameters.get("min")}get max(){return this.audioNode.parameters.get("max")}onSetMin(e){this.min.value=e}onSetMax(e){this.max.value=e}onSetCurrent(e){this.current.value=e}};var pt={steps:{kind:"number",min:1,max:16,step:1,label:"Steps"},bars:{kind:"number",min:1,max:16,step:1,label:"Steps"}},Ct={sequences:[],steps:16,bars:1},ue=class extends u{midiOutput;constructor(e,t){let o={...Ct,...t.props};super(e,{...t,props:o})}};var dt={activeNotes:{kind:"array",label:"Active notes"}},Et={activeNotes:[]},pe=class extends u{midiOutput;constructor(e,t){let o={...Et,...t.props};super(e,{...t,props:o}),this.registerInputs(),this.registerOutputs()}sendMidi(e){this.midiOutput.onMidiEvent(e)}triggerAttack=(e,t)=>{this.props={activeNotes:[...this.props.activeNotes,e.fullName]},this.triggerPropsUpdate(),this.sendMidi(f.fromNote(e,!0,t))};triggerRelease=(e,t)=>{this.props={activeNotes:this.props.activeNotes.filter(o=>o!==e.fullName)},this.triggerPropsUpdate(),this.sendMidi(f.fromNote(e,!1,t))};registerInputs(){this.registerMidiInput({name:"midi in",onMidiEvent:this.onMidiEvent})}registerOutputs(){this.midiOutput=this.registerMidiOutput({name:"midi out"})}};var G=(d=>(d.Master="Master",d.Oscillator="Oscillator",d.Gain="Gain",d.MidiSelector="MidiSelector",d.Envelope="Envelope",d.Filter="Filter",d.BiquadFilter="BiquadFilter",d.Scale="Scale",d.Inspector="Inspector",d.Constant="Constant",d.VirtualMidi="VirtualMidi",d.StepSequencer="StepSequencer",d.VoiceScheduler="VoiceScheduler",d))(G||{}),lt={Oscillator:st,Gain:Ve,Master:tt,MidiSelector:ot,Envelope:Qe,Filter:Ze,BiquadFilter:Le,Scale:ut,Inspector:et,Constant:Ue,VirtualMidi:dt,StepSequencer:pt,VoiceScheduler:Be};function V(r,e){switch(e.moduleType){case"Oscillator":return new re(r,e);case"Gain":return new X(r,e);case"Master":return new oe(r,e);case"MidiSelector":return new ie(r,e);case"Envelope":return new Z(r,e);case"Filter":return new ee(r,e);case"BiquadFilter":return new Y(r,e);case"Scale":return new ne(r,e);case"Inspector":return new te(r,e);case"Constant":return new J(r,e);case"VirtualMidi":return new pe(r,e);case"StepSequencer":return new ue(r,e);case"VoiceScheduler":return new H(r,e);default:(0,at.assertNever)(e)}}var M=class r{static _engines=new Map;static _currentId;propsUpdateCallbacks=[];id;context;isInitialized=!1;routes;transport;modules;midiDeviceManager;static getById(e){let t=r._engines.get(e);return(0,F.assertDefined)(t),t}static get current(){return(0,F.assertDefined)(this._currentId),this.getById(this._currentId)}constructor(e){this.id=(0,F.uuidv4)(),this.context=e,this.transport=new C({onStart:this.onStart,onStop:this.onStop}),this.routes=new j(this),this.modules=new Map,this.midiDeviceManager=new B,r._engines.set(this.id,this),r._currentId=this.id}get state(){return this.transport.state}async initialize(){this.isInitialized||(await Xe(this.context),await this.midiDeviceManager.initialize(),this.isInitialized=!0)}addModule(e){let t=V(this.id,e);return this.modules.set(t.id,t),t.serialize()}updateModule(e){let t=this.findModule(e.id);if(t.moduleType!==e.moduleType)throw Error(`The module id ${e.id} isn't moduleType ${e.moduleType}`);let o=(0,F.pick)(e.changes,["name","props"]);return Object.assign(t,o),t instanceof l&&e.changes.voices!==void 0&&(t.voices=e.changes.voices),t.serialize()}removeModule(e){this.modules.delete(e)}addRoute(e){return this.routes.addRoute(e)}removeRoute(e){this.routes.removeRoute(e)}validRoute(e){let{source:t,destination:o}=e,i=this.findIO(t.moduleId,t.ioName,"output"),s=this.findIO(o.moduleId,o.ioName,"input");return i.isMidi()&&s.isMidi()||i.isAudio()&&s.isAudio()}start(e={}){this.transport.start(e)}stop(e={}){this.transport.stop(e)}pause(e={}){this.transport.pause(e)}get bpm(){return this.transport.bpm}set bpm(e){this.transport.bpm=e}async resume(){await this.context.resume()}dispose(){this.stop(),this.routes.clear(),this.modules.forEach(e=>{e.dispose()}),this.modules.clear()}findModule(e){let t=this.modules.get(e);if(!t)throw Error(`The module with id ${e} is not exists`);return t}findIO(e,t,o){return this.findModule(e)[`${o}s`].findByName(t)}findMidiDevice(e){return this.midiDeviceManager.find(e)}onPropsUpdate(e){this.propsUpdateCallbacks.push(e)}_triggerPropsUpdate(e){this.propsUpdateCallbacks.forEach(t=>{t(e)})}triggerVirtualMidi(e,t,o){let i=this.findModule(e);if(i.moduleType!=="VirtualMidi")throw Error("This is not a virtual mid");i.sendMidi(f.fromNote(t,o==="noteOn"))}onStart=e=>{this.modules.forEach(t=>{t.start(e)})};onStop=e=>{this.modules.forEach(t=>{t.stop(e)})}};
2
+ //# sourceMappingURL=index.cjs.map