@raphiiko/wavelink-ts 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/PROTOCOL.md ADDED
@@ -0,0 +1,1126 @@
1
+ # Elgato Wave Link 3.0 Protocol Documentation
2
+
3
+ This document describes the protocol for Wave Link 3.0's RPC.
4
+
5
+ ## Overview
6
+
7
+ Wave Link 3.0 uses JSON-RPC 2.0 over WebSocket for all communication.
8
+
9
+ **Connection Details:**
10
+
11
+ - **Protocol**: JSON-RPC 2.0 over WebSocket
12
+ - **Host**: `ws://127.0.0.1`
13
+ - **Port**: 1884 (default, with fallback to ports 1885-1893)
14
+ - **Origin Header**: `streamdeck://`
15
+ - **Authentication**: None required
16
+
17
+ ## JSON-RPC 2.0 Format
18
+
19
+ ### Request Structure
20
+
21
+ All requests follow the JSON-RPC 2.0 specification:
22
+
23
+ ```json
24
+ {
25
+ "id": 1,
26
+ "jsonrpc": "2.0",
27
+ "method": "methodName",
28
+ "params": {}
29
+ }
30
+ ```
31
+
32
+ - `id`: Incrementing number for tracking requests/responses
33
+ - `jsonrpc`: Always `"2.0"`
34
+ - `method`: The RPC method name
35
+ - `params`: Method parameters (can be `null` or `{}` for methods with no parameters)
36
+
37
+ ### Response Structure
38
+
39
+ Successful responses:
40
+
41
+ ```json
42
+ {
43
+ "jsonrpc": "2.0",
44
+ "id": 1,
45
+ "result": {}
46
+ }
47
+ ```
48
+
49
+ Error responses:
50
+
51
+ ```json
52
+ {
53
+ "jsonrpc": "2.0",
54
+ "id": 1,
55
+ "error": {
56
+ "code": -32600,
57
+ "message": "Error description",
58
+ "data": {}
59
+ }
60
+ }
61
+ ```
62
+
63
+ ### Notification Structure
64
+
65
+ Notifications are server-initiated messages (no `id` field):
66
+
67
+ ```json
68
+ {
69
+ "jsonrpc": "2.0",
70
+ "method": "notificationMethod",
71
+ "params": {}
72
+ }
73
+ ```
74
+
75
+ ## RPC Methods
76
+
77
+ These methods are sent from the client to Wave Link.
78
+
79
+ ### `getApplicationInfo`
80
+
81
+ Get application information and verify connection.
82
+
83
+ **Request:**
84
+
85
+ ```json
86
+ {
87
+ "id": 1,
88
+ "jsonrpc": "2.0",
89
+ "method": "getApplicationInfo",
90
+ "params": null
91
+ }
92
+ ```
93
+
94
+ **Response:**
95
+
96
+ ```json
97
+ {
98
+ "jsonrpc": "2.0",
99
+ "id": 1,
100
+ "result": {
101
+ "appID": "EWL",
102
+ "name": "Elgato Wave Link",
103
+ "interfaceRevision": 1
104
+ }
105
+ }
106
+ ```
107
+
108
+ **Notes:**
109
+
110
+ - `interfaceRevision` must be >= 1 for compatibility
111
+ - Use this method first to verify connection
112
+
113
+ ---
114
+
115
+ ### `getInputDevices`
116
+
117
+ Get all audio input devices and their properties.
118
+
119
+ **Request:**
120
+
121
+ ```json
122
+ {
123
+ "id": 2,
124
+ "jsonrpc": "2.0",
125
+ "method": "getInputDevices",
126
+ "params": null
127
+ }
128
+ ```
129
+
130
+ **Response:**
131
+
132
+ ```json
133
+ {
134
+ "jsonrpc": "2.0",
135
+ "id": 2,
136
+ "result": {
137
+ "inputDevices": [
138
+ {
139
+ "id": "wave3_usb_microphone",
140
+ "isWaveDevice": true,
141
+ "inputs": [
142
+ {
143
+ "id": "wave3_input_1",
144
+ "isMuted": false,
145
+ "gain": {
146
+ "value": 0.5,
147
+ "maxRange": 100
148
+ },
149
+ "micPcMix": {
150
+ "value": 0.5
151
+ },
152
+ "effects": [
153
+ {
154
+ "id": "clipguard",
155
+ "isEnabled": true
156
+ }
157
+ ]
158
+ }
159
+ ]
160
+ }
161
+ ]
162
+ }
163
+ }
164
+ ```
165
+
166
+ **Field Descriptions:**
167
+
168
+ - `id`: Unique device identifier
169
+ - `isWaveDevice`: Whether this is an Elgato Wave device (has special features)
170
+ - `inputs[]`: Array of input channels on this device
171
+ - `isMuted`: Whether input is muted
172
+ - `gain.value`: Gain level (0.0-1.0, normalized by maxRange)
173
+ - `gain.maxRange`: Maximum gain range for the device
174
+ - `micPcMix.value`: Mix between microphone and PC audio (0.0-1.0)
175
+ - `effects[]`: Available effects and their states (Wave devices only)
176
+ - `dspEffects[]`: Alternative effects array (some devices)
177
+
178
+ ---
179
+
180
+ ### `getOutputDevices`
181
+
182
+ Get all audio output devices and their properties.
183
+
184
+ **Request:**
185
+
186
+ ```json
187
+ {
188
+ "id": 3,
189
+ "jsonrpc": "2.0",
190
+ "method": "getOutputDevices",
191
+ "params": null
192
+ }
193
+ ```
194
+
195
+ **Response:**
196
+
197
+ ```json
198
+ {
199
+ "jsonrpc": "2.0",
200
+ "id": 3,
201
+ "result": {
202
+ "mainOutput": "speakers_main",
203
+ "outputDevices": [
204
+ {
205
+ "id": "speakers_main",
206
+ "outputs": [
207
+ {
208
+ "id": "output_1",
209
+ "level": 0.8,
210
+ "isMuted": false,
211
+ "mixId": "stream_mix"
212
+ }
213
+ ]
214
+ }
215
+ ]
216
+ }
217
+ }
218
+ ```
219
+
220
+ **Field Descriptions:**
221
+
222
+ - `mainOutput`: ID of the main output device
223
+ - `outputDevices[]`: Array of output devices
224
+ - `outputs[]`: Output channels on this device
225
+ - `level`: Output volume (0.0-1.0)
226
+ - `isMuted`: Whether output is muted
227
+ - `mixId`: Currently selected mix for this output
228
+
229
+ ---
230
+
231
+ ### `getChannels`
232
+
233
+ Get all channels (audio sources) in the mixer.
234
+
235
+ **Request:**
236
+
237
+ ```json
238
+ {
239
+ "id": 4,
240
+ "jsonrpc": "2.0",
241
+ "method": "getChannels",
242
+ "params": null
243
+ }
244
+ ```
245
+
246
+ **Response:**
247
+
248
+ ```json
249
+ {
250
+ "jsonrpc": "2.0",
251
+ "id": 4,
252
+ "result": {
253
+ "channels": [
254
+ {
255
+ "id": "spotify",
256
+ "type": "Software",
257
+ "isMuted": false,
258
+ "level": 0.75,
259
+ "image": {
260
+ "name": "spotify",
261
+ "imgData": "data:image/png;base64,..."
262
+ },
263
+ "apps": [
264
+ {
265
+ "id": "com.spotify.music",
266
+ "name": "Spotify"
267
+ }
268
+ ],
269
+ "mixes": [
270
+ {
271
+ "id": "stream_mix",
272
+ "level": 0.8,
273
+ "isMuted": false
274
+ },
275
+ {
276
+ "id": "monitor_mix",
277
+ "level": 0.6,
278
+ "isMuted": false
279
+ }
280
+ ]
281
+ }
282
+ ]
283
+ }
284
+ }
285
+ ```
286
+
287
+ **Field Descriptions:**
288
+
289
+ - `id`: Channel identifier
290
+ - `type`: `"Software"` or `"Hardware"`
291
+ - `isMuted`: Overall channel mute
292
+ - `level`: Overall channel volume (0.0-1.0)
293
+ - `image`: Icon/image for the channel
294
+ - `apps[]`: Applications assigned to this channel
295
+ - `effects[]`: Effects available for this channel (optional)
296
+ - `mixes[]`: Per-mix settings for this channel
297
+ - Each channel has separate volume and mute per mix
298
+
299
+ ---
300
+
301
+ ### `getMixes`
302
+
303
+ Get all mixer configurations (Stream Mix, Monitor Mix, etc.).
304
+
305
+ **Request:**
306
+
307
+ ```json
308
+ {
309
+ "id": 5,
310
+ "jsonrpc": "2.0",
311
+ "method": "getMixes",
312
+ "params": null
313
+ }
314
+ ```
315
+
316
+ **Response:**
317
+
318
+ ```json
319
+ {
320
+ "jsonrpc": "2.0",
321
+ "id": 5,
322
+ "result": {
323
+ "mixes": [
324
+ {
325
+ "id": "stream_mix",
326
+ "name": "Stream Mix",
327
+ "level": 1.0,
328
+ "isMuted": false,
329
+ "image": {
330
+ "name": "stream"
331
+ }
332
+ },
333
+ {
334
+ "id": "monitor_mix",
335
+ "name": "Monitor Mix",
336
+ "level": 0.9,
337
+ "isMuted": false,
338
+ "image": {
339
+ "name": "monitor"
340
+ }
341
+ }
342
+ ]
343
+ }
344
+ }
345
+ ```
346
+
347
+ **Field Descriptions:**
348
+
349
+ - `id`: Mix identifier
350
+ - `name`: Human-readable mix name
351
+ - `level`: Master output level for this mix (0.0-1.0)
352
+ - `isMuted`: Master mute for this mix
353
+
354
+ ---
355
+
356
+ ### `setInputDevice`
357
+
358
+ Modify input device properties.
359
+
360
+ **Request:**
361
+
362
+ ```json
363
+ {
364
+ "id": 6,
365
+ "jsonrpc": "2.0",
366
+ "method": "setInputDevice",
367
+ "params": {
368
+ "id": "wave3_usb_microphone",
369
+ "inputs": [
370
+ {
371
+ "id": "wave3_input_1",
372
+ "isMuted": true
373
+ }
374
+ ]
375
+ }
376
+ }
377
+ ```
378
+
379
+ **Example: Adjust Gain**
380
+
381
+ ```json
382
+ {
383
+ "id": 7,
384
+ "jsonrpc": "2.0",
385
+ "method": "setInputDevice",
386
+ "params": {
387
+ "id": "wave3_usb_microphone",
388
+ "inputs": [
389
+ {
390
+ "id": "wave3_input_1",
391
+ "gain": {
392
+ "value": 0.75
393
+ }
394
+ }
395
+ ]
396
+ }
397
+ }
398
+ ```
399
+
400
+ **Example: Toggle Effect**
401
+
402
+ ```json
403
+ {
404
+ "id": 8,
405
+ "jsonrpc": "2.0",
406
+ "method": "setInputDevice",
407
+ "params": {
408
+ "id": "wave3_usb_microphone",
409
+ "inputs": [
410
+ {
411
+ "id": "wave3_input_1",
412
+ "effects": [
413
+ {
414
+ "id": "clipguard",
415
+ "isEnabled": false
416
+ }
417
+ ]
418
+ }
419
+ ]
420
+ }
421
+ }
422
+ ```
423
+
424
+ **Notes:**
425
+
426
+ - Only include properties you want to change
427
+ - Response is typically empty on success
428
+
429
+ ---
430
+
431
+ ### `setOutputDevice`
432
+
433
+ Modify output device properties.
434
+
435
+ **Request:**
436
+
437
+ ```json
438
+ {
439
+ "id": 9,
440
+ "jsonrpc": "2.0",
441
+ "method": "setOutputDevice",
442
+ "params": {
443
+ "outputDevice": {
444
+ "id": "speakers_main",
445
+ "outputs": [
446
+ {
447
+ "id": "output_1",
448
+ "level": 0.5
449
+ }
450
+ ]
451
+ }
452
+ }
453
+ }
454
+ ```
455
+
456
+ **Example: Switch Mix**
457
+
458
+ ```json
459
+ {
460
+ "id": 10,
461
+ "jsonrpc": "2.0",
462
+ "method": "setOutputDevice",
463
+ "params": {
464
+ "outputDevice": {
465
+ "id": "speakers_main",
466
+ "outputs": [
467
+ {
468
+ "id": "output_1",
469
+ "mixId": "monitor_mix"
470
+ }
471
+ ]
472
+ }
473
+ }
474
+ }
475
+ ```
476
+
477
+ **Example: Mute Output**
478
+
479
+ ```json
480
+ {
481
+ "id": 11,
482
+ "jsonrpc": "2.0",
483
+ "method": "setOutputDevice",
484
+ "params": {
485
+ "outputDevice": {
486
+ "id": "speakers_main",
487
+ "outputs": [
488
+ {
489
+ "id": "output_1",
490
+ "isMuted": true
491
+ }
492
+ ]
493
+ }
494
+ }
495
+ }
496
+ ```
497
+
498
+ ---
499
+
500
+ ### `setChannel`
501
+
502
+ Modify channel (audio source) properties.
503
+
504
+ **Request:**
505
+
506
+ ```json
507
+ {
508
+ "id": 12,
509
+ "jsonrpc": "2.0",
510
+ "method": "setChannel",
511
+ "params": {
512
+ "id": "spotify",
513
+ "isMuted": true
514
+ }
515
+ }
516
+ ```
517
+
518
+ **Example: Set Volume for Specific Mix**
519
+
520
+ ```json
521
+ {
522
+ "id": 13,
523
+ "jsonrpc": "2.0",
524
+ "method": "setChannel",
525
+ "params": {
526
+ "id": "spotify",
527
+ "mixes": [
528
+ {
529
+ "id": "stream_mix",
530
+ "level": 0.5
531
+ }
532
+ ]
533
+ }
534
+ }
535
+ ```
536
+
537
+ **Example: Set Overall Volume**
538
+
539
+ ```json
540
+ {
541
+ "id": 14,
542
+ "jsonrpc": "2.0",
543
+ "method": "setChannel",
544
+ "params": {
545
+ "id": "spotify",
546
+ "level": 0.8
547
+ }
548
+ }
549
+ ```
550
+
551
+ **Example: Mute in Specific Mix**
552
+
553
+ ```json
554
+ {
555
+ "id": 15,
556
+ "jsonrpc": "2.0",
557
+ "method": "setChannel",
558
+ "params": {
559
+ "id": "spotify",
560
+ "mixes": [
561
+ {
562
+ "id": "stream_mix",
563
+ "isMuted": true
564
+ }
565
+ ]
566
+ }
567
+ }
568
+ ```
569
+
570
+ **Notes:**
571
+
572
+ - `level`: Overall channel volume
573
+ - `mixes[]`: Per-mix volume and mute settings
574
+ - Only include the properties/mixes you want to change
575
+
576
+ ---
577
+
578
+ ### `setMix`
579
+
580
+ Modify mixer configuration properties.
581
+
582
+ **Request:**
583
+
584
+ ```json
585
+ {
586
+ "id": 16,
587
+ "jsonrpc": "2.0",
588
+ "method": "setMix",
589
+ "params": {
590
+ "id": "stream_mix",
591
+ "level": 0.9
592
+ }
593
+ }
594
+ ```
595
+
596
+ **Example: Mute Mix**
597
+
598
+ ```json
599
+ {
600
+ "id": 17,
601
+ "jsonrpc": "2.0",
602
+ "method": "setMix",
603
+ "params": {
604
+ "id": "stream_mix",
605
+ "isMuted": true
606
+ }
607
+ }
608
+ ```
609
+
610
+ ---
611
+
612
+ ### `addToChannel`
613
+
614
+ Add an application to a specific channel.
615
+
616
+ **Request:**
617
+
618
+ ```json
619
+ {
620
+ "id": 18,
621
+ "jsonrpc": "2.0",
622
+ "method": "addToChannel",
623
+ "params": {
624
+ "appId": "com.discord.app",
625
+ "channelId": "comms"
626
+ }
627
+ }
628
+ ```
629
+
630
+ ---
631
+
632
+ ### `setSubscription`
633
+
634
+ Subscribe to notifications/events.
635
+
636
+ **Request: Subscribe to Focused App Changes**
637
+
638
+ ```json
639
+ {
640
+ "id": 19,
641
+ "jsonrpc": "2.0",
642
+ "method": "setSubscription",
643
+ "params": {
644
+ "focusedAppChanged": {
645
+ "isEnabled": true
646
+ }
647
+ }
648
+ }
649
+ ```
650
+
651
+ **Request: Subscribe to Level Meter Updates**
652
+
653
+ ```json
654
+ {
655
+ "id": 20,
656
+ "jsonrpc": "2.0",
657
+ "method": "setSubscription",
658
+ "params": {
659
+ "levelMeterChanged": {
660
+ "type": "channel",
661
+ "id": "spotify",
662
+ "isEnabled": true
663
+ }
664
+ }
665
+ }
666
+ ```
667
+
668
+ **Level Meter Types:**
669
+
670
+ - `"input"`: Input device level meters
671
+ - `"output"`: Output device level meters
672
+ - `"channel"`: Channel level meters
673
+ - `"mix"`: Mix level meters
674
+
675
+ **Response:**
676
+
677
+ ```json
678
+ {
679
+ "jsonrpc": "2.0",
680
+ "id": 20,
681
+ "result": {
682
+ "levelMeterChanged": {
683
+ "type": "channel",
684
+ "id": "spotify",
685
+ "isEnabled": true
686
+ }
687
+ }
688
+ }
689
+ ```
690
+
691
+ ---
692
+
693
+ ## Notifications
694
+
695
+ Notifications are sent from Wave Link to the client without a request `id`.
696
+
697
+ ### `inputDevicesChanged`
698
+
699
+ All input devices changed (complete list).
700
+
701
+ **Notification:**
702
+
703
+ ```json
704
+ {
705
+ "jsonrpc": "2.0",
706
+ "method": "inputDevicesChanged",
707
+ "params": {
708
+ "inputDevices": [
709
+ // Same structure as getInputDevices response
710
+ ]
711
+ }
712
+ }
713
+ ```
714
+
715
+ ---
716
+
717
+ ### `inputDeviceChanged`
718
+
719
+ A specific input device changed.
720
+
721
+ **Notification:**
722
+
723
+ ```json
724
+ {
725
+ "jsonrpc": "2.0",
726
+ "method": "inputDeviceChanged",
727
+ "params": {
728
+ "id": "wave3_usb_microphone",
729
+ "inputs": [
730
+ {
731
+ "id": "wave3_input_1",
732
+ "isMuted": true
733
+ // Only changed properties are included
734
+ }
735
+ ]
736
+ }
737
+ }
738
+ ```
739
+
740
+ ---
741
+
742
+ ### `outputDevicesChanged`
743
+
744
+ All output devices changed (complete list).
745
+
746
+ **Notification:**
747
+
748
+ ```json
749
+ {
750
+ "jsonrpc": "2.0",
751
+ "method": "outputDevicesChanged",
752
+ "params": [
753
+ "speakers_main",
754
+ [
755
+ // Array of output devices (same structure as getOutputDevices)
756
+ ]
757
+ ]
758
+ }
759
+ ```
760
+
761
+ **Note:** The params is an array: `[mainOutput, outputDevices]`
762
+
763
+ ---
764
+
765
+ ### `outputDeviceChanged`
766
+
767
+ A specific output device changed.
768
+
769
+ **Notification:**
770
+
771
+ ```json
772
+ {
773
+ "jsonrpc": "2.0",
774
+ "method": "outputDeviceChanged",
775
+ "params": {
776
+ "id": "speakers_main"
777
+ // Only changed properties are included
778
+ }
779
+ }
780
+ ```
781
+
782
+ ---
783
+
784
+ ### `channelsChanged`
785
+
786
+ All channels changed (complete list).
787
+
788
+ **Notification:**
789
+
790
+ ```json
791
+ {
792
+ "jsonrpc": "2.0",
793
+ "method": "channelsChanged",
794
+ "params": {
795
+ "channels": [
796
+ // Same structure as getChannels response
797
+ ]
798
+ }
799
+ }
800
+ ```
801
+
802
+ ---
803
+
804
+ ### `channelChanged`
805
+
806
+ A specific channel changed.
807
+
808
+ **Notification:**
809
+
810
+ ```json
811
+ {
812
+ "jsonrpc": "2.0",
813
+ "method": "channelChanged",
814
+ "params": {
815
+ "id": "spotify",
816
+ "isMuted": true
817
+ // Only changed properties are included
818
+ }
819
+ }
820
+ ```
821
+
822
+ ---
823
+
824
+ ### `mixesChanged`
825
+
826
+ All mixes changed (complete list).
827
+
828
+ **Notification:**
829
+
830
+ ```json
831
+ {
832
+ "jsonrpc": "2.0",
833
+ "method": "mixesChanged",
834
+ "params": {
835
+ "mixes": [
836
+ // Same structure as getMixes response
837
+ ]
838
+ }
839
+ }
840
+ ```
841
+
842
+ ---
843
+
844
+ ### `mixChanged`
845
+
846
+ A specific mix changed.
847
+
848
+ **Notification:**
849
+
850
+ ```json
851
+ {
852
+ "jsonrpc": "2.0",
853
+ "method": "mixChanged",
854
+ "params": {
855
+ "id": "stream_mix",
856
+ "level": 0.8
857
+ // Only changed properties are included
858
+ }
859
+ }
860
+ ```
861
+
862
+ ---
863
+
864
+ ### `levelMeterChanged`
865
+
866
+ Level meter values updated (requires subscription via `setSubscription`).
867
+
868
+ **Notification:**
869
+
870
+ ```json
871
+ {
872
+ "jsonrpc": "2.0",
873
+ "method": "levelMeterChanged",
874
+ "params": [
875
+ [], // Input devices meters
876
+ [], // Output devices meters
877
+ [
878
+ // Channel meters
879
+ {
880
+ "id": "spotify",
881
+ "left": 0.5,
882
+ "right": 0.5
883
+ }
884
+ ],
885
+ [] // Mix meters
886
+ ]
887
+ }
888
+ ```
889
+
890
+ **Notes:**
891
+
892
+ - The params is an array of 4 arrays: `[inputs, outputs, channels, mixes]`
893
+ - Each meter object format varies by type
894
+ - This notification fires frequently (real-time audio levels)
895
+
896
+ ---
897
+
898
+ ### `focusedAppChanged`
899
+
900
+ The currently focused application changed (requires subscription).
901
+
902
+ **Notification:**
903
+
904
+ ```json
905
+ {
906
+ "jsonrpc": "2.0",
907
+ "method": "focusedAppChanged",
908
+ "params": [
909
+ "com.spotify.music",
910
+ "Spotify",
911
+ {
912
+ "id": "spotify"
913
+ }
914
+ ]
915
+ }
916
+ ```
917
+
918
+ **Note:** The params is an array: `[appId, appName, { id: channelId }]`
919
+
920
+ ---
921
+
922
+ ## Common Patterns
923
+
924
+ ### Initial Connection
925
+
926
+ 1. Connect to WebSocket: `ws://127.0.0.1:1884`
927
+ 2. Call `getApplicationInfo` to verify connection
928
+ 3. Call `getInputDevices`, `getOutputDevices`, `getChannels`, `getMixes` to get current state
929
+ 4. Subscribe to relevant notifications
930
+
931
+ **Example Connection Flow:**
932
+
933
+ ```javascript
934
+ const ws = new WebSocket("ws://127.0.0.1:1884", { origin: "streamdeck://" });
935
+
936
+ ws.on("open", () => {
937
+ // 1. Verify connection
938
+ send({ id: 1, jsonrpc: "2.0", method: "getApplicationInfo", params: null });
939
+
940
+ // 2. Get initial state
941
+ send({ id: 2, jsonrpc: "2.0", method: "getInputDevices", params: null });
942
+ send({ id: 3, jsonrpc: "2.0", method: "getOutputDevices", params: null });
943
+ send({ id: 4, jsonrpc: "2.0", method: "getChannels", params: null });
944
+ send({ id: 5, jsonrpc: "2.0", method: "getMixes", params: null });
945
+
946
+ // 3. Subscribe to changes
947
+ send({
948
+ id: 6,
949
+ jsonrpc: "2.0",
950
+ method: "setSubscription",
951
+ params: { focusedAppChanged: { isEnabled: true } },
952
+ });
953
+ });
954
+ ```
955
+
956
+ ### Volume Control
957
+
958
+ All volume/level values are in the range 0.0 to 1.0:
959
+
960
+ - `0.0` = Silent (0%)
961
+ - `0.5` = 50%
962
+ - `1.0` = Maximum (100%)
963
+
964
+ ### Gain Control
965
+
966
+ Input gain is normalized by the device's `maxRange`:
967
+
968
+ - Get current gain: `gain.value` (0.0-1.0)
969
+ - Get max range: `gain.maxRange`
970
+ - Actual dB or raw value: `gain.value * gain.maxRange`
971
+
972
+ ### Partial Updates
973
+
974
+ When calling setter methods (`setChannel`, `setMix`, etc.), you only need to include the properties you want to change. Other properties remain unchanged.
975
+
976
+ **Example: Only mute a channel without changing volume**
977
+
978
+ ```json
979
+ {
980
+ "id": 100,
981
+ "jsonrpc": "2.0",
982
+ "method": "setChannel",
983
+ "params": {
984
+ "id": "spotify",
985
+ "isMuted": true
986
+ }
987
+ }
988
+ ```
989
+
990
+ ### Per-Mix Control
991
+
992
+ Channels have both overall settings and per-mix settings:
993
+
994
+ - `level`: Overall volume (affects all mixes proportionally)
995
+ - `isMuted`: Overall mute (mutes in all mixes)
996
+ - `mixes[].level`: Volume for specific mix
997
+ - `mixes[].isMuted`: Mute for specific mix
998
+
999
+ **Example: Channel at 100% overall, but 50% in Stream Mix, 80% in Monitor Mix**
1000
+
1001
+ ```json
1002
+ {
1003
+ "id": 101,
1004
+ "jsonrpc": "2.0",
1005
+ "method": "setChannel",
1006
+ "params": {
1007
+ "id": "spotify",
1008
+ "level": 1.0,
1009
+ "mixes": [
1010
+ { "id": "stream_mix", "level": 0.5 },
1011
+ { "id": "monitor_mix", "level": 0.8 }
1012
+ ]
1013
+ }
1014
+ }
1015
+ ```
1016
+
1017
+ ---
1018
+
1019
+ ## Error Handling
1020
+
1021
+ ### Connection Errors
1022
+
1023
+ - If port 1884 is unavailable, try ports 1885-1893
1024
+ - Use the origin header `streamdeck://` for compatibility
1025
+ - Wave Link must be running for connection to succeed
1026
+
1027
+ ### Common Error Codes
1028
+
1029
+ JSON-RPC 2.0 standard error codes:
1030
+
1031
+ - `-32700`: Parse error (invalid JSON)
1032
+ - `-32600`: Invalid request
1033
+ - `-32601`: Method not found
1034
+ - `-32602`: Invalid params
1035
+ - `-32603`: Internal error
1036
+
1037
+ ---
1038
+
1039
+ ## Implementation Notes
1040
+
1041
+ 1. **Request IDs**: Should increment sequentially for easier tracking
1042
+ 2. **Throttling**: Some notifications are throttled (100ms for channel/input changes)
1043
+ 3. **Reconnection**: Implement automatic reconnection with exponential backoff
1044
+ 4. **State Management**: Cache the state from `get*` methods and update with notifications
1045
+ 5. **Level Meters**: Subscribe only when needed (generates high-frequency notifications)
1046
+ 6. **Focus Tracking**: `focusedAppChanged` is useful for auto-switching channel volumes
1047
+
1048
+ ---
1049
+
1050
+ ## Example Use Cases
1051
+
1052
+ ### Simple Channel Mute Toggle
1053
+
1054
+ ```javascript
1055
+ // Get current state
1056
+ const channelsResponse = await call("getChannels", null);
1057
+ const channel = channelsResponse.result.channels.find(
1058
+ (c) => c.id === "spotify",
1059
+ );
1060
+
1061
+ // Toggle mute
1062
+ await call("setChannel", {
1063
+ id: "spotify",
1064
+ isMuted: !channel.isMuted,
1065
+ });
1066
+ ```
1067
+
1068
+ ### Stream Mix vs Monitor Mix
1069
+
1070
+ Many users have two mixes:
1071
+
1072
+ - **Stream Mix**: What viewers hear (game loud, music quiet)
1073
+ - **Monitor Mix**: What you hear (game quiet, music loud, comms loud)
1074
+
1075
+ ```javascript
1076
+ // Make music loud in headphones, quiet in stream
1077
+ await call("setChannel", {
1078
+ id: "music",
1079
+ mixes: [
1080
+ { id: "stream_mix", level: 0.2 }, // 20% in stream
1081
+ { id: "monitor_mix", level: 0.8 }, // 80% in headphones
1082
+ ],
1083
+ });
1084
+ ```
1085
+
1086
+ ### Auto-Duck Music When Discord is Active
1087
+
1088
+ ```javascript
1089
+ // Subscribe to focused app
1090
+ await call('setSubscription', {
1091
+ focusedAppChanged: { isEnabled: true }
1092
+ });
1093
+
1094
+ // Listen for notifications
1095
+ ws.on('message', (data) => {
1096
+ const msg = JSON.parse(data);
1097
+
1098
+ if (msg.method === 'focusedAppChanged') {
1099
+ const [appId, appName, channel] = msg.params;
1100
+
1101
+ if (appId === 'com.discord.app') {
1102
+ // Duck music when Discord is active
1103
+ await call('setChannel', {
1104
+ id: 'music',
1105
+ mixes: [{ id: 'stream_mix', level: 0.3 }]
1106
+ });
1107
+ } else {
1108
+ // Restore music
1109
+ await call('setChannel', {
1110
+ id: 'music',
1111
+ mixes: [{ id: 'stream_mix', level: 0.7 }]
1112
+ });
1113
+ }
1114
+ }
1115
+ });
1116
+ ```
1117
+
1118
+ ---
1119
+
1120
+ ## Protocol Version
1121
+
1122
+ This documentation is based on Wave Link 3.0 Beta. The `interfaceRevision` from `getApplicationInfo` indicates protocol compatibility.
1123
+
1124
+ **Compatibility:**
1125
+
1126
+ - `interfaceRevision >= 1`: This protocol is supported