node-osc 11.1.1 → 11.2.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 (69) hide show
  1. package/.gitattributes +11 -0
  2. package/.github/workflows/bump-version.yml +2 -0
  3. package/.github/workflows/nodejs.yml +3 -0
  4. package/LICENSE +201 -165
  5. package/README.md +135 -42
  6. package/dist/lib/Bundle.js +66 -0
  7. package/dist/lib/Client.js +137 -22
  8. package/dist/lib/Message.js +87 -1
  9. package/dist/lib/Server.js +117 -6
  10. package/dist/lib/index.js +3 -0
  11. package/dist/lib/internal/decode.js +4 -4
  12. package/dist/lib/{internal/osc.js → osc.js} +70 -5
  13. package/dist/test/lib/osc.js +395 -0
  14. package/dist/test/test-client.js +152 -0
  15. package/dist/test/test-e2e.js +9 -3
  16. package/dist/test/test-encode-decode.js +849 -0
  17. package/dist/test/test-error-handling.js +116 -0
  18. package/dist/test/test-osc-internal.js +399 -41
  19. package/dist/test/test-promises.js +250 -0
  20. package/dist/test/test-types.js +42 -0
  21. package/dist/test/util.js +15 -8
  22. package/docs/API.md +477 -0
  23. package/docs/GUIDE.md +605 -0
  24. package/examples/README.md +119 -0
  25. package/examples/async-await.mjs +57 -0
  26. package/examples/bundle-example.mjs +92 -0
  27. package/examples/client.js +22 -5
  28. package/examples/error-handling.mjs +152 -0
  29. package/examples/esm.mjs +21 -0
  30. package/examples/server.js +16 -0
  31. package/jsdoc.json +16 -0
  32. package/lib/Bundle.mjs +66 -0
  33. package/lib/Client.mjs +137 -22
  34. package/lib/Message.mjs +87 -1
  35. package/lib/Server.mjs +117 -6
  36. package/lib/index.mjs +1 -0
  37. package/lib/internal/decode.mjs +4 -4
  38. package/lib/{internal/osc.mjs → osc.mjs} +71 -4
  39. package/package.json +12 -10
  40. package/rollup.config.mjs +48 -41
  41. package/scripts/generate-docs.mjs +229 -0
  42. package/test/fixtures/types/test-cjs-types.ts +19 -0
  43. package/test/fixtures/types/test-esm-types.ts +35 -0
  44. package/test/fixtures/types/tsconfig-cjs.test.json +17 -0
  45. package/test/fixtures/types/tsconfig-esm.test.json +17 -0
  46. package/test/test-bundle.mjs +0 -1
  47. package/test/test-client.mjs +152 -0
  48. package/test/test-e2e.mjs +9 -3
  49. package/test/test-encode-decode.mjs +847 -0
  50. package/test/test-error-handling.mjs +115 -0
  51. package/test/test-osc-internal.mjs +400 -42
  52. package/test/test-promises.mjs +249 -0
  53. package/test/test-types.mjs +39 -0
  54. package/test/util.mjs +15 -8
  55. package/tsconfig.json +45 -0
  56. package/types/Bundle.d.mts +70 -0
  57. package/types/Bundle.d.mts.map +1 -0
  58. package/types/Client.d.mts +101 -0
  59. package/types/Client.d.mts.map +1 -0
  60. package/types/Message.d.mts +84 -0
  61. package/types/Message.d.mts.map +1 -0
  62. package/types/Server.d.mts +98 -0
  63. package/types/Server.d.mts.map +1 -0
  64. package/types/index.d.mts +6 -0
  65. package/types/index.d.mts.map +1 -0
  66. package/types/internal/decode.d.mts +4 -0
  67. package/types/internal/decode.d.mts.map +1 -0
  68. package/types/osc.d.mts +66 -0
  69. package/types/osc.d.mts.map +1 -0
@@ -1,5 +1,5 @@
1
1
  import { test } from 'tap';
2
- import { toBuffer, fromBuffer } from '#osc';
2
+ import { encode, decode } from '../lib/osc.mjs';
3
3
 
4
4
  test('osc: timetag encoding with non-number value', (t) => {
5
5
  // Test the else branch in writeTimeTag that writes zeros for non-number values
@@ -15,8 +15,8 @@ test('osc: timetag encoding with non-number value', (t) => {
15
15
  ]
16
16
  };
17
17
 
18
- const buffer = toBuffer(bundle);
19
- const decoded = fromBuffer(buffer);
18
+ const buffer = encode(bundle);
19
+ const decoded = decode(buffer);
20
20
 
21
21
  t.equal(decoded.oscType, 'bundle', 'should decode as bundle');
22
22
  t.equal(decoded.timetag, 0, 'should decode timetag as 0 for immediate execution');
@@ -37,8 +37,8 @@ test('osc: timetag with immediate execution values', (t) => {
37
37
  ]
38
38
  };
39
39
 
40
- const buffer = toBuffer(bundle);
41
- const decoded = fromBuffer(buffer);
40
+ const buffer = encode(bundle);
41
+ const decoded = decode(buffer);
42
42
 
43
43
  t.equal(decoded.timetag, 0, 'should handle immediate execution timetag');
44
44
  t.end();
@@ -58,7 +58,7 @@ test('osc: argument encoding with unknown type', (t) => {
58
58
  };
59
59
 
60
60
  t.throws(() => {
61
- toBuffer(message);
61
+ encode(message);
62
62
  }, /Unknown argument type: unknown/, 'should throw error for unknown argument type');
63
63
  t.end();
64
64
  });
@@ -76,8 +76,8 @@ test('osc: argument encoding with boolean false', (t) => {
76
76
  ]
77
77
  };
78
78
 
79
- const buffer = toBuffer(message);
80
- const decoded = fromBuffer(buffer);
79
+ const buffer = encode(message);
80
+ const decoded = decode(buffer);
81
81
 
82
82
  t.equal(decoded.args[0].value, false, 'should encode and decode false boolean');
83
83
  t.end();
@@ -94,7 +94,7 @@ test('osc: argument encoding with unsupported object', (t) => {
94
94
  };
95
95
 
96
96
  t.throws(() => {
97
- toBuffer(message);
97
+ encode(message);
98
98
  }, /Don't know how to encode argument/, 'should throw error for unsupported object');
99
99
  t.end();
100
100
  });
@@ -108,7 +108,7 @@ test('osc: argument encoding with undefined value', (t) => {
108
108
  };
109
109
 
110
110
  t.throws(() => {
111
- toBuffer(message);
111
+ encode(message);
112
112
  }, /Don't know how to encode argument/, 'should throw error for undefined argument');
113
113
  t.end();
114
114
  });
@@ -121,7 +121,7 @@ test('osc: argument decoding with unknown type tag', (t) => {
121
121
  const buffer = Buffer.from(addressPart + typeTagPart);
122
122
 
123
123
  t.throws(() => {
124
- fromBuffer(buffer);
124
+ decode(buffer);
125
125
  }, /I don't understand the argument code X/, 'should throw error for unknown type tag');
126
126
  t.end();
127
127
  });
@@ -134,7 +134,7 @@ test('osc: null argument encoding and decoding', (t) => {
134
134
  const typeTagPart = ',N\0\0'; // N is null type tag
135
135
  const buffer = Buffer.from(addressPart + typeTagPart);
136
136
 
137
- const decoded = fromBuffer(buffer);
137
+ const decoded = decode(buffer);
138
138
  t.equal(decoded.args[0].value, null, 'should decode null argument');
139
139
  t.end();
140
140
  });
@@ -152,8 +152,8 @@ test('osc: double type argument encoding', (t) => {
152
152
  ]
153
153
  };
154
154
 
155
- const buffer = toBuffer(message);
156
- const decoded = fromBuffer(buffer);
155
+ const buffer = encode(message);
156
+ const decoded = decode(buffer);
157
157
 
158
158
  t.ok(Math.abs(decoded.args[0].value - 3.14159) < 0.001, 'should encode double as float');
159
159
  t.end();
@@ -168,8 +168,8 @@ test('osc: blob argument with Buffer', (t) => {
168
168
  args: [testBuffer] // Direct Buffer without type wrapper
169
169
  };
170
170
 
171
- const buffer = toBuffer(message);
172
- const decoded = fromBuffer(buffer);
171
+ const buffer = encode(message);
172
+ const decoded = decode(buffer);
173
173
 
174
174
  t.ok(Buffer.isBuffer(decoded.args[0].value), 'should decode as Buffer');
175
175
  t.equal(decoded.args[0].value.toString(), 'test data', 'should preserve blob content');
@@ -184,8 +184,8 @@ test('osc: float number encoding', (t) => {
184
184
  args: [3.14159] // Non-integer number should be encoded as float
185
185
  };
186
186
 
187
- const buffer = toBuffer(message);
188
- const decoded = fromBuffer(buffer);
187
+ const buffer = encode(message);
188
+ const decoded = decode(buffer);
189
189
 
190
190
  t.ok(typeof decoded.args[0].value === 'number', 'should decode as number');
191
191
  t.ok(!Number.isInteger(decoded.args[0].value), 'should be float, not integer');
@@ -206,8 +206,8 @@ test('osc: explicit integer type encoding', (t) => {
206
206
  ]
207
207
  };
208
208
 
209
- const buffer = toBuffer(message);
210
- const decoded = fromBuffer(buffer);
209
+ const buffer = encode(message);
210
+ const decoded = decode(buffer);
211
211
 
212
212
  t.equal(decoded.args[0].value, 42, 'should encode and decode integer');
213
213
  t.end();
@@ -226,8 +226,8 @@ test('osc: explicit float type encoding', (t) => {
226
226
  ]
227
227
  };
228
228
 
229
- const buffer = toBuffer(message);
230
- const decoded = fromBuffer(buffer);
229
+ const buffer = encode(message);
230
+ const decoded = decode(buffer);
231
231
 
232
232
  t.ok(Math.abs(decoded.args[0].value - 3.14) < 0.001, 'should encode and decode float');
233
233
  t.end();
@@ -246,8 +246,8 @@ test('osc: explicit string type encoding', (t) => {
246
246
  ]
247
247
  };
248
248
 
249
- const buffer = toBuffer(message);
250
- const decoded = fromBuffer(buffer);
249
+ const buffer = encode(message);
250
+ const decoded = decode(buffer);
251
251
 
252
252
  t.equal(decoded.args[0].value, 'hello', 'should encode and decode string');
253
253
  t.end();
@@ -267,8 +267,8 @@ test('osc: explicit blob type encoding', (t) => {
267
267
  ]
268
268
  };
269
269
 
270
- const buffer = toBuffer(message);
271
- const decoded = fromBuffer(buffer);
270
+ const buffer = encode(message);
271
+ const decoded = decode(buffer);
272
272
 
273
273
  t.ok(Buffer.isBuffer(decoded.args[0].value), 'should decode as Buffer');
274
274
  t.equal(decoded.args[0].value.toString(), 'blob data', 'should preserve blob data');
@@ -288,8 +288,8 @@ test('osc: explicit boolean true type encoding', (t) => {
288
288
  ]
289
289
  };
290
290
 
291
- const buffer = toBuffer(message);
292
- const decoded = fromBuffer(buffer);
291
+ const buffer = encode(message);
292
+ const decoded = decode(buffer);
293
293
 
294
294
  t.equal(decoded.args[0].value, true, 'should encode and decode boolean true');
295
295
  t.end();
@@ -309,8 +309,8 @@ test('osc: MIDI type encoding with Buffer', (t) => {
309
309
  ]
310
310
  };
311
311
 
312
- const buffer = toBuffer(message);
313
- const decoded = fromBuffer(buffer);
312
+ const buffer = encode(message);
313
+ const decoded = decode(buffer);
314
314
 
315
315
  t.ok(Buffer.isBuffer(decoded.args[0].value), 'should decode as Buffer');
316
316
  t.equal(decoded.args[0].value.length, 4, 'should be 4 bytes');
@@ -339,8 +339,8 @@ test('osc: MIDI type encoding with object', (t) => {
339
339
  ]
340
340
  };
341
341
 
342
- const buffer = toBuffer(message);
343
- const decoded = fromBuffer(buffer);
342
+ const buffer = encode(message);
343
+ const decoded = decode(buffer);
344
344
 
345
345
  t.ok(Buffer.isBuffer(decoded.args[0].value), 'should decode as Buffer');
346
346
  t.equal(decoded.args[0].value[0], 2, 'port should match');
@@ -364,7 +364,7 @@ test('osc: MIDI type with invalid buffer length', (t) => {
364
364
  };
365
365
 
366
366
  t.throws(() => {
367
- toBuffer(message);
367
+ encode(message);
368
368
  }, /MIDI message must be exactly 4 bytes/, 'should throw error for wrong buffer length');
369
369
  t.end();
370
370
  });
@@ -383,7 +383,7 @@ test('osc: MIDI type with invalid value type', (t) => {
383
383
  };
384
384
 
385
385
  t.throws(() => {
386
- toBuffer(message);
386
+ encode(message);
387
387
  }, /MIDI value must be a 4-byte Buffer or object/, 'should throw error for invalid value type');
388
388
  t.end();
389
389
  });
@@ -405,8 +405,8 @@ test('osc: MIDI type with partial object', (t) => {
405
405
  ]
406
406
  };
407
407
 
408
- const buffer = toBuffer(message);
409
- const decoded = fromBuffer(buffer);
408
+ const buffer = encode(message);
409
+ const decoded = decode(buffer);
410
410
 
411
411
  t.equal(decoded.args[0].value[0], 0, 'port should default to 0');
412
412
  t.equal(decoded.args[0].value[1], 0x90, 'status should match');
@@ -434,7 +434,7 @@ test('osc: MIDI type decoding with insufficient buffer data', (t) => {
434
434
  const malformedBuffer = Buffer.concat([address, typeTags, insufficientMidiData]);
435
435
 
436
436
  t.throws(() => {
437
- fromBuffer(malformedBuffer);
437
+ decode(malformedBuffer);
438
438
  }, /Not enough bytes for MIDI message/, 'should throw error when MIDI data is truncated');
439
439
  t.end();
440
440
  });
@@ -456,8 +456,8 @@ test('osc: MIDI type with falsy status and data1 values', (t) => {
456
456
  ]
457
457
  };
458
458
 
459
- const buffer = toBuffer(message);
460
- const decoded = fromBuffer(buffer);
459
+ const buffer = encode(message);
460
+ const decoded = decode(buffer);
461
461
 
462
462
  t.equal(decoded.args[0].value[0], 5, 'port should match');
463
463
  t.equal(decoded.args[0].value[1], 0, 'status should default to 0');
@@ -484,8 +484,8 @@ test('osc: MIDI type with explicit zero status and data1 values', (t) => {
484
484
  ]
485
485
  };
486
486
 
487
- const buffer = toBuffer(message);
488
- const decoded = fromBuffer(buffer);
487
+ const buffer = encode(message);
488
+ const decoded = decode(buffer);
489
489
 
490
490
  t.equal(decoded.args[0].value[0], 3, 'port should match');
491
491
  t.equal(decoded.args[0].value[1], 0, 'status should be 0');
@@ -493,4 +493,362 @@ test('osc: MIDI type with explicit zero status and data1 values', (t) => {
493
493
  t.equal(decoded.args[0].value[3], 0x60, 'data2 should match');
494
494
  t.end();
495
495
 
496
- });
496
+ });
497
+ test('osc: timetag encoding with numeric value and fractions', (t) => {
498
+ // Test writeTimeTag with actual numeric timetag (lines 70-74)
499
+ const bundle = {
500
+ oscType: 'bundle',
501
+ timetag: 1234567890.5, // Numeric value with fractional part
502
+ elements: [
503
+ {
504
+ oscType: 'message',
505
+ address: '/test',
506
+ args: []
507
+ }
508
+ ]
509
+ };
510
+
511
+ const buffer = encode(bundle);
512
+ const decoded = decode(buffer);
513
+
514
+ t.equal(decoded.oscType, 'bundle', 'should decode as bundle');
515
+ t.ok(Math.abs(decoded.timetag - 1234567890.5) < 0.01, 'should preserve numeric timetag with fractions');
516
+ t.end();
517
+ });
518
+
519
+ test('osc: timetag decoding with actual timestamp', (t) => {
520
+ // Test readTimeTag with non-zero, non-immediate values (lines 92-96)
521
+ // We encode a bundle with a real timestamp, then decode it
522
+ const bundle = {
523
+ oscType: 'bundle',
524
+ timetag: 1609459200.25, // A real timestamp: 2021-01-01 00:00:00.25
525
+ elements: [
526
+ {
527
+ oscType: 'message',
528
+ address: '/timestamp',
529
+ args: [{ type: 'i', value: 123 }]
530
+ }
531
+ ]
532
+ };
533
+
534
+ const buffer = encode(bundle);
535
+ const decoded = decode(buffer);
536
+
537
+ t.equal(decoded.oscType, 'bundle', 'should decode as bundle');
538
+ t.ok(decoded.timetag > 0, 'timetag should be positive');
539
+ t.ok(Math.abs(decoded.timetag - 1609459200.25) < 0.01, 'should preserve timestamp value');
540
+ t.end();
541
+ });
542
+
543
+ test('osc: inferred integer encoding from raw number', (t) => {
544
+ // Test line 167: encoding raw integer without type wrapper
545
+ const message = {
546
+ oscType: 'message',
547
+ address: '/test',
548
+ args: [42] // Raw integer, not wrapped
549
+ };
550
+
551
+ const buffer = encode(message);
552
+ const decoded = decode(buffer);
553
+
554
+ t.equal(decoded.args[0].value, 42, 'should encode and decode raw integer');
555
+ t.end();
556
+ });
557
+
558
+ test('osc: inferred float encoding from raw number', (t) => {
559
+ // Test line 169: encoding raw float without type wrapper
560
+ const message = {
561
+ oscType: 'message',
562
+ address: '/test',
563
+ args: [3.14159] // Raw float, not wrapped
564
+ };
565
+
566
+ const buffer = encode(message);
567
+ const decoded = decode(buffer);
568
+
569
+ t.ok(Math.abs(decoded.args[0].value - 3.14159) < 0.001, 'should encode and decode raw float');
570
+ t.end();
571
+ });
572
+
573
+ test('osc: inferred string encoding from raw string', (t) => {
574
+ // Test line 172: encoding raw string without type wrapper
575
+ const message = {
576
+ oscType: 'message',
577
+ address: '/test',
578
+ args: ['hello world'] // Raw string, not wrapped
579
+ };
580
+
581
+ const buffer = encode(message);
582
+ const decoded = decode(buffer);
583
+
584
+ t.equal(decoded.args[0].value, 'hello world', 'should encode and decode raw string');
585
+ t.end();
586
+ });
587
+
588
+ test('osc: inferred boolean true encoding from raw boolean', (t) => {
589
+ // Test line 174 (true branch): encoding raw boolean true
590
+ const message = {
591
+ oscType: 'message',
592
+ address: '/test',
593
+ args: [true] // Raw boolean true, not wrapped
594
+ };
595
+
596
+ const buffer = encode(message);
597
+ const decoded = decode(buffer);
598
+
599
+ t.equal(decoded.args[0].value, true, 'should encode and decode raw boolean true');
600
+ t.end();
601
+ });
602
+
603
+ test('osc: inferred boolean false encoding from raw boolean', (t) => {
604
+ // Test line 174 (false branch): encoding raw boolean false
605
+ const message = {
606
+ oscType: 'message',
607
+ address: '/test',
608
+ args: [false] // Raw boolean false, not wrapped
609
+ };
610
+
611
+ const buffer = encode(message);
612
+ const decoded = decode(buffer);
613
+
614
+ t.equal(decoded.args[0].value, false, 'should encode and decode raw boolean false');
615
+ t.end();
616
+ });
617
+
618
+ test('osc: bundle with only message elements (no nested bundles)', (t) => {
619
+ // Test line 252: encoding message elements in bundle (else branch)
620
+ const bundle = {
621
+ oscType: 'bundle',
622
+ timetag: 0,
623
+ elements: [
624
+ {
625
+ oscType: 'message',
626
+ address: '/msg1',
627
+ args: [{ type: 'i', value: 1 }]
628
+ },
629
+ {
630
+ oscType: 'message',
631
+ address: '/msg2',
632
+ args: [{ type: 'i', value: 2 }]
633
+ }
634
+ ]
635
+ };
636
+
637
+ const buffer = encode(bundle);
638
+ const decoded = decode(buffer);
639
+
640
+ t.equal(decoded.oscType, 'bundle', 'should decode as bundle');
641
+ t.equal(decoded.elements.length, 2, 'should have 2 elements');
642
+ t.equal(decoded.elements[0].oscType, 'message', 'first element should be message');
643
+ t.equal(decoded.elements[1].oscType, 'message', 'second element should be message');
644
+ t.end();
645
+ });
646
+
647
+ test('osc: malformed packet with missing comma in type tags', (t) => {
648
+ // Test lines 292-293: decoding malformed packet without comma in type tags
649
+ const addressBuf = Buffer.from('/test\0\0\0', 'utf8');
650
+ const malformedTypeTagsBuf = Buffer.from('iXX\0', 'utf8'); // Missing leading comma
651
+ const buffer = Buffer.concat([addressBuf, malformedTypeTagsBuf]);
652
+
653
+ t.throws(() => {
654
+ decode(buffer);
655
+ }, /Malformed Packet/, 'should throw on malformed type tags');
656
+ t.end();
657
+ });
658
+
659
+ test('osc: bundle with nested bundle element', (t) => {
660
+ // Test line 252: encoding bundle elements in bundle (if branch)
661
+ const innerBundle = {
662
+ oscType: 'bundle',
663
+ timetag: 0,
664
+ elements: [
665
+ {
666
+ oscType: 'message',
667
+ address: '/inner',
668
+ args: [{ type: 'i', value: 99 }]
669
+ }
670
+ ]
671
+ };
672
+
673
+ const outerBundle = {
674
+ oscType: 'bundle',
675
+ timetag: 0,
676
+ elements: [
677
+ {
678
+ oscType: 'message',
679
+ address: '/outer',
680
+ args: [{ type: 's', value: 'test' }]
681
+ },
682
+ innerBundle
683
+ ]
684
+ };
685
+
686
+ const buffer = encode(outerBundle);
687
+ const decoded = decode(buffer);
688
+
689
+ t.equal(decoded.oscType, 'bundle', 'should decode as bundle');
690
+ t.equal(decoded.elements.length, 2, 'should have 2 elements');
691
+ t.equal(decoded.elements[0].oscType, 'message', 'first element should be message');
692
+ t.equal(decoded.elements[1].oscType, 'bundle', 'second element should be bundle');
693
+ t.equal(decoded.elements[1].elements[0].address, '/inner', 'nested bundle should have correct message');
694
+ t.end();
695
+ });
696
+
697
+ test('osc: explicit integer type name', (t) => {
698
+ const message = {
699
+ oscType: 'message',
700
+ address: '/test',
701
+ args: [{ type: 'integer', value: 999 }]
702
+ };
703
+
704
+ const buffer = encode(message);
705
+ const decoded = decode(buffer);
706
+
707
+ t.equal(decoded.args[0].value, 999);
708
+ t.end();
709
+ });
710
+
711
+ test('osc: explicit float type name', (t) => {
712
+ const message = {
713
+ oscType: 'message',
714
+ address: '/test',
715
+ args: [{ type: 'float', value: 1.414 }]
716
+ };
717
+
718
+ const buffer = encode(message);
719
+ const decoded = decode(buffer);
720
+
721
+ t.ok(Math.abs(decoded.args[0].value - 1.414) < 0.001);
722
+ t.end();
723
+ });
724
+
725
+ test('osc: explicit string type name', (t) => {
726
+ const message = {
727
+ oscType: 'message',
728
+ address: '/test',
729
+ args: [{ type: 'string', value: 'test string' }]
730
+ };
731
+
732
+ const buffer = encode(message);
733
+ const decoded = decode(buffer);
734
+
735
+ t.equal(decoded.args[0].value, 'test string');
736
+ t.end();
737
+ });
738
+
739
+ test('osc: explicit blob type name', (t) => {
740
+ const message = {
741
+ oscType: 'message',
742
+ address: '/test',
743
+ args: [{ type: 'blob', value: Buffer.from([0xAA, 0xBB]) }]
744
+ };
745
+
746
+ const buffer = encode(message);
747
+ const decoded = decode(buffer);
748
+
749
+ t.ok(Buffer.isBuffer(decoded.args[0].value));
750
+ t.same(decoded.args[0].value, Buffer.from([0xAA, 0xBB]));
751
+ t.end();
752
+ });
753
+
754
+ test('osc: explicit double type name', (t) => {
755
+ const message = {
756
+ oscType: 'message',
757
+ address: '/test',
758
+ args: [{ type: 'double', value: 2.718281828 }]
759
+ };
760
+
761
+ const buffer = encode(message);
762
+ const decoded = decode(buffer);
763
+
764
+ t.ok(Math.abs(decoded.args[0].value - 2.718281828) < 0.001);
765
+ t.end();
766
+ });
767
+
768
+ test('osc: blob padding when length is multiple of 4', (t) => {
769
+ // Test writeBlob line 52: padding === 4 branch (should use 0)
770
+ const message = {
771
+ oscType: 'message',
772
+ address: '/test',
773
+ args: [{ type: 'b', value: Buffer.from([1, 2, 3, 4]) }] // length 4
774
+ };
775
+
776
+ const buffer = encode(message);
777
+ const decoded = decode(buffer);
778
+
779
+ t.same(decoded.args[0].value, Buffer.from([1, 2, 3, 4]));
780
+ t.end();
781
+ });
782
+
783
+ test('osc: blob padding when length is not multiple of 4', (t) => {
784
+ // Test writeBlob line 52: padding !== 4 branch (should use padding value)
785
+ const message = {
786
+ oscType: 'message',
787
+ address: '/test',
788
+ args: [{ type: 'b', value: Buffer.from([1, 2, 3]) }] // length 3
789
+ };
790
+
791
+ const buffer = encode(message);
792
+ const decoded = decode(buffer);
793
+
794
+ t.same(decoded.args[0].value, Buffer.from([1, 2, 3]));
795
+ t.end();
796
+ });
797
+
798
+ test('osc: boolean type true value', (t) => {
799
+ // Test boolean case ternary: true branch
800
+ const message = {
801
+ oscType: 'message',
802
+ address: '/test',
803
+ args: [{ type: 'boolean', value: true }]
804
+ };
805
+
806
+ const buffer = encode(message);
807
+ const decoded = decode(buffer);
808
+
809
+ t.equal(decoded.args[0].value, true);
810
+ t.end();
811
+ });
812
+
813
+ test('osc: boolean type false value', (t) => {
814
+ // Test boolean case ternary: false branch
815
+ const message = {
816
+ oscType: 'message',
817
+ address: '/test',
818
+ args: [{ type: 'boolean', value: false }]
819
+ };
820
+
821
+ const buffer = encode(message);
822
+ const decoded = decode(buffer);
823
+
824
+ t.equal(decoded.args[0].value, false);
825
+ t.end();
826
+ });
827
+
828
+ test('osc: explicit T type', (t) => {
829
+ const message = {
830
+ oscType: 'message',
831
+ address: '/test',
832
+ args: [{ type: 'T', value: true }]
833
+ };
834
+
835
+ const buffer = encode(message);
836
+ const decoded = decode(buffer);
837
+
838
+ t.equal(decoded.args[0].value, true);
839
+ t.end();
840
+ });
841
+
842
+ test('osc: explicit F type', (t) => {
843
+ const message = {
844
+ oscType: 'message',
845
+ address: '/test',
846
+ args: [{ type: 'F', value: false }]
847
+ };
848
+
849
+ const buffer = encode(message);
850
+ const decoded = decode(buffer);
851
+
852
+ t.equal(decoded.args[0].value, false);
853
+ t.end();
854
+ });