This repository was archived by the owner on Jul 31, 2025. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathprint.html
More file actions
2117 lines (1934 loc) · 143 KB
/
print.html
File metadata and controls
2117 lines (1934 loc) · 143 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
<!DOCTYPE HTML>
<html lang="en" class="sidebar-visible no-js light">
<head>
<!-- Book generated using mdBook -->
<meta charset="UTF-8">
<title>The Synthizer Manual</title>
<meta name="robots" content="noindex" />
<!-- Custom HTML head -->
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
<meta name="description" content="">
<meta name="viewport" content="width=device-width, initial-scale=1">
<meta name="theme-color" content="#ffffff" />
<link rel="icon" href="favicon.svg">
<link rel="shortcut icon" href="favicon.png">
<link rel="stylesheet" href="css/variables.css">
<link rel="stylesheet" href="css/general.css">
<link rel="stylesheet" href="css/chrome.css">
<link rel="stylesheet" href="css/print.css" media="print">
<!-- Fonts -->
<link rel="stylesheet" href="FontAwesome/css/font-awesome.css">
<link rel="stylesheet" href="fonts/fonts.css">
<!-- Highlight.js Stylesheets -->
<link rel="stylesheet" href="highlight.css">
<link rel="stylesheet" href="tomorrow-night.css">
<link rel="stylesheet" href="ayu-highlight.css">
<!-- Custom theme stylesheets -->
</head>
<body>
<!-- Provide site root to javascript -->
<script type="text/javascript">
var path_to_root = "";
var default_theme = window.matchMedia("(prefers-color-scheme: dark)").matches ? "navy" : "light";
</script>
<!-- Work around some values being stored in localStorage wrapped in quotes -->
<script type="text/javascript">
try {
var theme = localStorage.getItem('mdbook-theme');
var sidebar = localStorage.getItem('mdbook-sidebar');
if (theme.startsWith('"') && theme.endsWith('"')) {
localStorage.setItem('mdbook-theme', theme.slice(1, theme.length - 1));
}
if (sidebar.startsWith('"') && sidebar.endsWith('"')) {
localStorage.setItem('mdbook-sidebar', sidebar.slice(1, sidebar.length - 1));
}
} catch (e) { }
</script>
<!-- Set the theme before any content is loaded, prevents flash -->
<script type="text/javascript">
var theme;
try { theme = localStorage.getItem('mdbook-theme'); } catch(e) { }
if (theme === null || theme === undefined) { theme = default_theme; }
var html = document.querySelector('html');
html.classList.remove('no-js')
html.classList.remove('light')
html.classList.add(theme);
html.classList.add('js');
</script>
<!-- Hide / unhide sidebar before it is displayed -->
<script type="text/javascript">
var html = document.querySelector('html');
var sidebar = 'hidden';
if (document.body.clientWidth >= 1080) {
try { sidebar = localStorage.getItem('mdbook-sidebar'); } catch(e) { }
sidebar = sidebar || 'visible';
}
html.classList.remove('sidebar-visible');
html.classList.add("sidebar-" + sidebar);
</script>
<nav id="sidebar" class="sidebar" aria-label="Table of contents">
<div class="sidebar-scrollbox">
<ol class="chapter"><li class="chapter-item expanded "><a href="introduction.html"><strong aria-hidden="true">1.</strong> Introduction</a></li><li class="chapter-item expanded "><a href="python.html"><strong aria-hidden="true">2.</strong> Where are the Python Bindings?</a></li><li class="chapter-item expanded affix "><li class="part-title">Concepts</li><li class="chapter-item expanded "><a href="concepts/c_api.html"><strong aria-hidden="true">3.</strong> Introduction to the C API</a></li><li class="chapter-item expanded "><a href="concepts/initialization.html"><strong aria-hidden="true">4.</strong> Logging, Initialization, and Shutdown</a></li><li class="chapter-item expanded "><a href="concepts/handles.html"><strong aria-hidden="true">5.</strong> Handles and userdata</a></li><li class="chapter-item expanded "><a href="concepts/audio_in.html"><strong aria-hidden="true">6.</strong> Basics of Audio In Synthizer</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="concepts/context.html"><strong aria-hidden="true">6.1.</strong> The Context</a></li><li class="chapter-item expanded "><a href="concepts/generators.html"><strong aria-hidden="true">6.2.</strong> Introduction to Generators</a></li><li class="chapter-item expanded "><a href="concepts/sources.html"><strong aria-hidden="true">6.3.</strong> Introduction to Sources</a></li><li class="chapter-item expanded "><a href="concepts/properties.html"><strong aria-hidden="true">6.4.</strong> Controlling Object Properties</a></li><li class="chapter-item expanded "><a href="concepts/gain.html"><strong aria-hidden="true">6.5.</strong> Setting Gain/volume</a></li><li class="chapter-item expanded "><a href="concepts/pausing.html"><strong aria-hidden="true">6.6.</strong> Pausing and Resuming Playback</a></li><li class="chapter-item expanded "><a href="concepts/lingering.html"><strong aria-hidden="true">6.7.</strong> Configuring Objects to Continue Playing Until Silent</a></li><li class="chapter-item expanded "><a href="concepts/decoding.html"><strong aria-hidden="true">6.8.</strong> Streams and Decoding Audio Data</a></li><li class="chapter-item expanded "><a href="concepts/libsndfile.html"><strong aria-hidden="true">6.9.</strong> Loading Libsndfile</a></li><li class="chapter-item expanded "><a href="concepts/custom_streams.html"><strong aria-hidden="true">6.10.</strong> Implementing Custom Streams and Custom Stream Protocols</a></li><li class="chapter-item expanded "><a href="concepts/channel_mixing.html"><strong aria-hidden="true">6.11.</strong> Channel Upmixing and Downmixing </a></li></ol></li><li class="chapter-item expanded "><a href="concepts/3d_audio.html"><strong aria-hidden="true">7.</strong> 3D Audio, Panning, and HRTF</a></li><li class="chapter-item expanded "><a href="concepts/filters_and_effects.html"><strong aria-hidden="true">8.</strong> Filters and Effects</a></li><li><ol class="section"><li class="chapter-item expanded "><a href="concepts/filters.html"><strong aria-hidden="true">8.1.</strong> Filters</a></li><li class="chapter-item expanded "><a href="concepts/effects.html"><strong aria-hidden="true">8.2.</strong> Effects and Effect Routing</a></li></ol></li><li class="chapter-item expanded "><a href="concepts/events.html"><strong aria-hidden="true">9.</strong> Events</a></li><li class="chapter-item expanded "><a href="concepts/automation.html"><strong aria-hidden="true">10.</strong> The Automation API</a></li><li class="chapter-item expanded "><a href="concepts/stability.html"><strong aria-hidden="true">11.</strong> Stability Guarantees</a></li><li class="chapter-item expanded affix "><li class="part-title">The Object Reference</li><li class="chapter-item expanded "><a href="object_reference/context.html"><strong aria-hidden="true">12.</strong> Context</a></li><li class="chapter-item expanded "><a href="object_reference/buffer.html"><strong aria-hidden="true">13.</strong> Buffer</a></li><li class="spacer"></li><li class="chapter-item expanded "><a href="object_reference/source.html"><strong aria-hidden="true">14.</strong> Operations Common to All Sources</a></li><li class="chapter-item expanded "><a href="object_reference/direct_source.html"><strong aria-hidden="true">15.</strong> DirectSource</a></li><li class="chapter-item expanded "><a href="object_reference/panned_sources.html"><strong aria-hidden="true">16.</strong> AngularPannedSource and ScalarPannedSource</a></li><li class="chapter-item expanded "><a href="object_reference/source_3d.html"><strong aria-hidden="true">17.</strong> Source3D</a></li><li class="spacer"></li><li class="chapter-item expanded "><a href="object_reference/generator.html"><strong aria-hidden="true">18.</strong> Operations Common to All Generators</a></li><li class="chapter-item expanded "><a href="object_reference/buffer_generator.html"><strong aria-hidden="true">19.</strong> BufferGenerator</a></li><li class="chapter-item expanded "><a href="object_reference/fast_sine_bank.html"><strong aria-hidden="true">20.</strong> FastSineBankGenerator</a></li><li class="chapter-item expanded "><a href="object_reference/noise_generator.html"><strong aria-hidden="true">21.</strong> NoiseGenerator</a></li><li class="chapter-item expanded "><a href="object_reference/streaming_generator.html"><strong aria-hidden="true">22.</strong> StreamingGenerator</a></li><li class="spacer"></li><li class="chapter-item expanded "><a href="object_reference/global_effect.html"><strong aria-hidden="true">23.</strong> Operations Common to All Effects</a></li><li class="chapter-item expanded "><a href="object_reference/global_echo.html"><strong aria-hidden="true">24.</strong> GlobalEcho</a></li><li class="chapter-item expanded "><a href="object_reference/global_fdn_reverb.html"><strong aria-hidden="true">25.</strong> GlobalFdnReverb</a></li><li class="chapter-item expanded affix "><li class="part-title">Appendecies</li><li class="chapter-item expanded "><a href="appendices/audio_eq_cookbook.html"><strong aria-hidden="true">26.</strong> Audio EQ Cookbook</a></li></ol>
</div>
<div id="sidebar-resize-handle" class="sidebar-resize-handle"></div>
</nav>
<div id="page-wrapper" class="page-wrapper">
<div class="page">
<div id="menu-bar-hover-placeholder"></div>
<div id="menu-bar" class="menu-bar sticky bordered">
<div class="left-buttons">
<button id="sidebar-toggle" class="icon-button" type="button" title="Toggle Table of Contents" aria-label="Toggle Table of Contents" aria-controls="sidebar">
<i class="fa fa-bars"></i>
</button>
<button id="theme-toggle" class="icon-button" type="button" title="Change theme" aria-label="Change theme" aria-haspopup="true" aria-expanded="false" aria-controls="theme-list">
<i class="fa fa-paint-brush"></i>
</button>
<ul id="theme-list" class="theme-popup" aria-label="Themes" role="menu">
<li role="none"><button role="menuitem" class="theme" id="light">Light (default)</button></li>
<li role="none"><button role="menuitem" class="theme" id="rust">Rust</button></li>
<li role="none"><button role="menuitem" class="theme" id="coal">Coal</button></li>
<li role="none"><button role="menuitem" class="theme" id="navy">Navy</button></li>
<li role="none"><button role="menuitem" class="theme" id="ayu">Ayu</button></li>
</ul>
<button id="search-toggle" class="icon-button" type="button" title="Search. (Shortkey: s)" aria-label="Toggle Searchbar" aria-expanded="false" aria-keyshortcuts="S" aria-controls="searchbar">
<i class="fa fa-search"></i>
</button>
</div>
<h1 class="menu-title">The Synthizer Manual</h1>
<div class="right-buttons">
<a href="print.html" title="Print this book" aria-label="Print this book">
<i id="print-button" class="fa fa-print"></i>
</a>
</div>
</div>
<div id="search-wrapper" class="hidden">
<form id="searchbar-outer" class="searchbar-outer">
<input type="search" name="search" id="searchbar" name="searchbar" placeholder="Search this book ..." aria-controls="searchresults-outer" aria-describedby="searchresults-header">
</form>
<div id="searchresults-outer" class="searchresults-outer hidden">
<div id="searchresults-header" class="searchresults-header"></div>
<ul id="searchresults">
</ul>
</div>
</div>
<!-- Apply ARIA attributes after the sidebar and the sidebar toggle button are added to the DOM -->
<script type="text/javascript">
document.getElementById('sidebar-toggle').setAttribute('aria-expanded', sidebar === 'visible');
document.getElementById('sidebar').setAttribute('aria-hidden', sidebar !== 'visible');
Array.from(document.querySelectorAll('#sidebar a')).forEach(function(link) {
link.setAttribute('tabIndex', sidebar === 'visible' ? 0 : -1);
});
</script>
<div id="content" class="content">
<main>
<h1 id="introduction"><a class="header" href="#introduction">Introduction</a></h1>
<p>This is the manual for <a href="https://github.com/synthizer/synthizer">Synthizer</a>, a library for 3D audio and synthesis with a
focus on games and VR applications.</p>
<p>There are 3 ways to learn Synthizer:</p>
<ul>
<li>This manual acts as a conceptual overview, as well as an API and object reference. If you have worked with audio
libraries before, you can likely read the concepts section and hit the ground running. In particular, Synthizer is
much like a more limited (but faster) form of WebAudio.</li>
<li>The Python bindings are no longer officially extended without community contribution, but <a href="https://github.com/synthizer/synthizer-python">their
repository</a> contains a number of examples and tutorials.</li>
<li>Finally, the <a href="https://github.com/synthizer/synthizer">official repository</a> contains a number of C examples.</li>
</ul>
<h1 id="where-are-the-python-bindings"><a class="header" href="#where-are-the-python-bindings">Where are the Python Bindings?</a></h1>
<p>The Python bindings are found at <a href="https://github.com/synthizer/synthizer-python">this repository</a> which now also
contains the docs and a set of examples. They are now maintained separately.</p>
<h1 id="the-synthizer-c-api"><a class="header" href="#the-synthizer-c-api">The Synthizer C API</a></h1>
<p>Synthizer has the following headers:</p>
<ul>
<li><code>synthizer.h</code>: All library functions</li>
<li><code>synthizer_constants.h</code>: Constants, i.e. the very large property enum.</li>
</ul>
<p>These headers are separated because it is easier in some cases to machine parse
<code>synthizer_constants.h</code> when writing bindings for languages which don't have
good automatic generation, then manually handle <code>synthizer.h</code>.</p>
<p>The Synthizer C API returns errors and writes results to out parameters. Out
parameters are always the first parameters of a function, and errors are always
nonzero. Note that error codes are currently not defined; they will be, once
things are more stable.</p>
<p>It is possible to get information on the last error using these functions:</p>
<pre><code>SYZ_CAPI syz_ErrorCode syz_getLastErrorCode(void);
SYZ_CAPI const char *syz_getLastErrorMessage(void);
</code></pre>
<p>These are thread-local functions. The last error message is valid until the
next call into Synthizer.</p>
<h1 id="logging-initialization-and-shutdown"><a class="header" href="#logging-initialization-and-shutdown">Logging, Initialization, and Shutdown</a></h1>
<p>The following excerpts from <code>synthizer.h</code> specify the logging and initialization
API. Explanation follows:</p>
<pre><code>enum SYZ_LOGGING_BACKEND {
SYZ_LOGGING_BACKEND_NONE,
SYZ_LOGGING_BACKEND_STDERR,
};
enum SYZ_LOG_LEVEL {
SYZ_LOG_LEVEL_ERROR = 0,
SYZ_LOG_LEVEL_WARN = 10,
SYZ_LOG_LEVEL_INFO = 20,
SYZ_LOG_LEVEL_DEBUG = 30,
};
struct syz_LibraryConfig {
unsigned int log_level;
unsigned int logging_backend;
const char *libsndfile_path;
};
SYZ_CAPI void syz_libraryConfigSetDefaults(struct syz_LibraryConfig *config);
SYZ_CAPI syz_ErrorCode syz_initialize(void);
SYZ_CAPI syz_ErrorCode syz_initializeWithConfig(const struct syz_LibraryConfig *config);
SYZ_CAPI syz_ErrorCode syz_shutdown();
</code></pre>
<p>Synthizer can be initialized in two ways. The simplest is <code>syz_initialize</code>
which will use reasonable library defaults for most apps. The second is
(excluding error checking):</p>
<pre><code>struct syz_LibraryConfig cfg;
syz_libraryConfigSetDefaults(&config);
syz_initializeWithConfig(&config);
</code></pre>
<p>In particular, the latter approach allows for enabling logging and loading
Libsndfile.</p>
<p>Currently Synthizer can only log to stderr and logging is slow enough that it
shouldn't be enabled in production. It mostly exists for debugging. In future
these restrictions will be lifted.</p>
<p>For more information on Libsndfile support, see <a href="concepts/./libsndfile.html">the dedicated
chapter</a>.</p>
<h1 id="handles-and-userdata"><a class="header" href="#handles-and-userdata">Handles and userdata</a></h1>
<p>Synthizer objects are referred to via reference-counted handles, with an optional <code>void *</code> userdata pointer that can be
associated to link them to application state:</p>
<pre><code>SYZ_CAPI syz_ErrorCode syz_handleIncRef(syz_Handle handle);
SYZ_CAPI syz_ErrorCode syz_handleDecRef(syz_Handle handle);
SYZ_CAPI syz_ErrorCode syz_handleGetObjectType(int *out, syz_Handle handle);
SYZ_CAPI syz_ErrorCode syz_handleGetUserdata(void **out, syz_Handle handle);
typedef void syz_UserdataFreeCallback(void *);
SYZ_CAPI syz_ErrorCode syz_handleSetUserdata(syz_Handle handle, void *userdata, syz_UserdataFreeCallback *free_callback);
</code></pre>
<h2 id="basics-of-handles"><a class="header" href="#basics-of-handles">basics of Handles</a></h2>
<p>All Synthizer handles start with a reference count of 1. When the reference count reaches 0, the object is scheduled
for deletion, but may not be deleted immediately. Uniquely among Synthizer functions, <code>syz_handleIncRef</code> and
<code>syz_handleDecRef</code> can be called after library shutdown in order to allow languages like Rust to implement infallible
cloning and freeing. The issues introduced with respect to object lifetimes due to the fact that Synthizer objects may
stay around for a while can be dealt with userdata support, as described below.</p>
<p>No interface except for <code>syz_handleDecRef</code> decrements reference counts in a way which should be visible to the
application provided that the application is itself using reference counts correctly.</p>
<p>Synthizer objects are like classes. They have "methods" and "bases". For example all generators support a common set
of operations named with a <code>syz_generatorXXX</code> prefix.</p>
<h3 id="the-reserved-config-argument"><a class="header" href="#the-reserved-config-argument">The reserved config argument</a></h3>
<p>Many constructors take a <code>void *config</code> argument. This must be set to NULL, and is reserved for future use.</p>
<h3 id="userdata"><a class="header" href="#userdata">Userdata</a></h3>
<p>Synthizer makes it possible to associate application data via a <code>void *</code> pointer which will share the object's actual
lifetime rather than the lifetime of the handle to the object. This is useful for allowing applications to store state,
but also helps to deal with the lifetime issues introduced by the mismatch between the last reference count of the
object dying and the object actually dying. For example, the Rust and Python bindings use userdata to attach buffers to
objects when streaming from memory, so that the actual underlying resource stays around until Synthizer is guaranteed to
no longer use it.</p>
<p>Getting and setting userdata pointers is done in one of two ways. All Synthizer constructors take two additional
parameters to set the userdata and the free callback. Alternatively, an application can go through <code>syz_getUserdata</code>
and <code>syz_setUserdata</code>. These are a threadsafe interface which will associate a <code>void *</code> argument with the object. This
interface acts as if the operations were wrapped in a mutex internally, though they complete with no syscalls in all
reasonable cases of library usage.</p>
<p>The <code>free_callback</code> parameter to <code>syz_setUserdata</code> is optional. If present, it will be called on the userdata pointer
when the object is destroyed or when a new userdata pointer is set. Due to limitations of efficient audio programming,
this free happens in a background thread and may occur up to hundreds of milliseconds after the object no longer exists.</p>
<p>Most bindings will bind userdata support in a more friendly way. For example, Python provides a <code>get_userdata</code> and
<code>set_userdata</code> pair which work on normal Python objects.</p>
<h1 id="basics-of-audio-in-synthizer"><a class="header" href="#basics-of-audio-in-synthizer">basics of Audio in Synthizer</a></h1>
<p>This section explains how to get audio into and out of Synthizer. The following
objects must be used by every application:</p>
<ul>
<li>Generators produce audio, for example by reading a buffer of audio data.</li>
<li>Sources play audio from one or more generators.</li>
<li>Contexts represent audio devices and group objects for the same device
together.</li>
</ul>
<p>The most basic flow of Synthizer is to create a context, source, and generator,
then connect the generator to the source. For example, you might combine
<a href="concepts/../object_reference/buffer_generator.html">BufferGenerator</a> and
<a href="concepts/../object_reference/direct_source.html">DirectSource</a> to play a stereo audio file
to the speakers, or swap <code>DirectSource</code> for
<a href="concepts/../object_reference/source_3d.html">Source3D</a> to place the sound in the 3D
environment.</p>
<h1 id="the-context"><a class="header" href="#the-context">The Context</a></h1>
<p><a href="concepts/../object_reference/context.html">Contexts</a> represent audio devices and the
listener in 3D space. They:</p>
<ul>
<li>Figure out the output channel format necessary for the current audio device
and convert audio to it.</li>
<li>Offer the ability to globally set gain.</li>
<li>Let users set the position of the listener in 3D space.</li>
<li>Let users set defaults for other objects, primarily the distance model and
panning strategies.
<ul>
<li>If your application wants HRTF on by default, it is done on the context by
setting <code>SYZ_P_PANNER_STRATEGY</code>.</li>
</ul>
</li>
</ul>
<p>For more information on 3D audio, see <a href="concepts/./3d_audio.html">the dedicated section</a>.</p>
<p>Almost all objects in Synthizer require a context to be created and must be used
only with the context they're associated with.</p>
<p>A common question is whether an app should ever have more than one context.
Though this is possible, contexts are very expensive objects that directly
correspond to audio devices. Having 2 or 3 is the upper limit of what is
reasonable, but it is by far easiest to only have one as this prevents running
into issues where you mix objects from different contexts together.</p>
<h1 id="introduction-to-generators"><a class="header" href="#introduction-to-generators">Introduction to Generators</a></h1>
<p>generators are how audio first enters Synthizer. They can do things like <a href="concepts/../object_reference/buffer_generator.html">play
a buffer</a>, <a href="concepts/../object_reference/noise_generator.html">generate
noise</a>, or <a href="concepts/../object_reference/streaming_generator.html">stream audio
data</a>. By themselves, they're
silent and don't do anything, so they must be <a href="concepts/./sources.html">connected to
sources</a> via <code>syz_sourceAddGenerator</code>.</p>
<p>Generators are like a stereo without speakers: you have to plug them into
something else before they're audible. In this case the "something else" is a
source. Synthizer only supports using a generator with one source at a time,
but every source can have multiple generators. That is, given generators <code>g1</code>
and <code>g2</code> and sources <code>s1</code> and <code>s2</code>, then <code>g1</code> and <code>g2</code> could be connected to
<code>s1</code>, <code>g1</code> to <code>s1</code> and <code>g2</code> to <code>s2</code>, but not <code>g1</code> to both <code>s1</code> and <code>s2</code> at the
same time.</p>
<h1 id="introduction-to-sources"><a class="header" href="#introduction-to-sources">Introduction to Sources</a></h1>
<p>Sources are how generators are made audible. Synthizer offers 3 main kinds of
source:</p>
<ul>
<li>The <a href="concepts/../object_reference/direct_source.html">DirectSource</a> plays audio directly,
and can be used for things like background music. This is the only source
type which won't convert audio to mono before using it.</li>
<li>The <a href="concepts/../object_reference/panned_sources.html">ScalarPannedSource and AngularPannedSource</a> allow for manual
control over pan, either by azimuth/elevation or via a scalar from -1 to 1
where -1 is all left and 1 is all right.</li>
<li>The <a href="concepts/../object_reference/source_3d.html">Source3D</a> allows for positioning audio
in 3D space.</li>
</ul>
<p>Every source offers the following functions:</p>
<pre><code>SYZ_CAPI syz_ErrorCode syz_sourceAddGenerator(syz_Handle source, syz_Handle generator);
SYZ_CAPI syz_ErrorCode syz_sourceRemoveGenerator(syz_Handle source, syz_Handle generator);
</code></pre>
<p>Every source will mix audio from as many generators as are connected to it and
then feed the audio through to the output of the source and to effects. See the
section on <a href="concepts/./channel_mixing.html">channel mixing</a> for how audio is converted to
various different output formats, and <a href="concepts/./filters_and_effects.html">effects and
filters</a> for information on how to do more with this
API than simply playing audio.</p>
<h1 id="controlling-object-properties"><a class="header" href="#controlling-object-properties">Controlling Object Properties</a></h1>
<h2 id="basics"><a class="header" href="#basics">Basics</a></h2>
<p>most interesting audio control happens through properties, which are like knobs
on hardware controllers or dials in your DAW. Synthizer picks the values of
properties up on the next audio tick and automatically handles crossfading and
graceful changes as your app drives the values. Every property is identified
with a <code>SYZ_P</code> constant in <code>synthizer_constants.h</code>. IN bindings,
<code>SYZ_P_MY_PROPERTY</code> will generally become <code>my_property</code> or <code>MyProperty</code> or etc.
depending on the dominant style of the language, and then either become an
actual settable property or a <code>get_property</code> and <code>set_property</code> pair depending
on if the language in question supports customized properties that aren't just
member variables (e.g. <code>@property</code> in Python, properties in C#).</p>
<p>All properties are of one of the following types:</p>
<ul>
<li><code>int</code> or <code>double</code>, identified by a <code>i</code> and <code>d</code> suffix in the property API, the
standard C primitive types.</li>
<li><code>double3</code> and <code>double6</code>, identified by <code>d3</code> and <code>d6</code> suffixes, vectors of 3
doubles and 6 doubles respectively. Primarily used to set position and
orientation.</li>
<li><code>object</code>, identified by a <code>o</code> suffix, used to set object properties such as
the buffer to use for a buffer generator.</li>
<li><code>biquad</code>, configuration for a biquad filter. Used on effects and sources to
allow filtering audio.</li>
</ul>
<p>No property constant represents a property of two types. For example
<code>SYZ_P_POSITION</code> is both on <code>Context</code> and <code>Source3D</code> but is a <code>d3</code> in both
cases. Generators use <code>SYZ_P_PLAYBACK_POSITION</code>, which is always a double.
Synthizer will always maintain this constraint.</p>
<p>The Property API is as follows:</p>
<pre><code>SYZ_CAPI syz_ErrorCode syz_getI(int *out, syz_Handle target, int property);
SYZ_CAPI syz_ErrorCode syz_setI(syz_Handle target, int property, int value);
SYZ_CAPI syz_ErrorCode syz_getD(double *out, syz_Handle target, int property);
SYZ_CAPI syz_ErrorCode syz_setD(syz_Handle target, int property, double value);
SYZ_CAPI syz_ErrorCode syz_setO(syz_Handle target, int property, syz_Handle value);
SYZ_CAPI syz_ErrorCode syz_getD3(double *x, double *y, double *z, syz_Handle target, int property);
SYZ_CAPI syz_ErrorCode syz_setD3(syz_Handle target, int property, double x, double y, double z);
SYZ_CAPI syz_ErrorCode syz_getD6(double *x1, double *y1, double *z1, double *x2, double *y2, double *z2, syz_Handle target, int property);
SYZ_CAPI syz_ErrorCode syz_setD6(syz_Handle handle, int property, double x1, double y1, double z1, double x2, double y2, double z2);
SYZ_CAPI syz_ErrorCode syz_getBiquad(struct syz_BiquadConfig *filter, syz_Handle target, int property);
SYZ_CAPI syz_ErrorCode syz_setBiquad(syz_Handle target, int property, const struct syz_BiquadConfig *filter);
</code></pre>
<p>Property accesses happen without syscalls and are usually atomic operations and
enqueues on a lockfree queue.</p>
<h2 id="object-properties-are-weak"><a class="header" href="#object-properties-are-weak">Object Properties Are Weak</a></h2>
<p>Object properties do not increment the reference count of the handle associated
with them. There isn't much to say here, but it is important enough that it's
worth calling out with a section. For example, if you set the buffer on a
buffer generator and then decrement the buffer's reference count to 0, the
generator will stop playing audio rather than keeping the buffer alive.</p>
<h2 id="a-note-on-reading"><a class="header" href="#a-note-on-reading">A Note on Reading</a></h2>
<p>Property reads need to be further explained. Because audio programming requires
not blocking the audio thread, Synthizer internally uses queues for property
writes. This means that any read may be outdated by some amount, even if the
thread making the read just set the value. Typically, reads should be reserved
for properties that Synthizer also sets (e.g. <code>SYZ_P_PLAYBAKCK_POSITION</code>) or
used for debugging purposes.</p>
<p><code>syz_getO</code> is not offered by this API because it requires a mutex, which the
audio thread also can't handle. Additionally, object lifetime concerns make it
more difficult for such an interface to do something sane.</p>
<p>Though the above limitations prevent this anyway, it is in general an
antipattern to store application state in your audio library. Even if reads
were always up to date, it would still be slow to get data back out.
Applications should keep things like object position around and update
Synthizer, rather than asking Synthizer what the last value was.</p>
<h1 id="setting-gainvolume"><a class="header" href="#setting-gainvolume">Setting Gain/volume</a></h1>
<h2 id="syz_p_gain"><a class="header" href="#syz_p_gain"><code>SYZ_P_GAIN</code></a></h2>
<p>All objects which play audio (generators, sources, contexts) offer a
<code>SYZ_P_GAIN</code> property, a double scalar between 0.0 and infinity which controls
object volume. For example <code>2.0</code> is twice the amplitude and <code>0.5</code> is half the
amplitude. This works as you'd expect: if set on a generator it only affects
that generator, if on a source it affects everything connected to the source,
and so on. If a generator is set to 0.5 and the source that it's on is also
0.5, the output volume of the generator is 0.25 because both gains apply in
order.</p>
<p>This means that it is possible to control the volume of generators relative to
each other when all connected to the same source, then control the overall
volume of the source.</p>
<h2 id="a-note-on-human-perception"><a class="header" href="#a-note-on-human-perception">A Note on Human Perception</a></h2>
<p>Humans don't perceive amplitude changes as you'd expect. For example, moving
from 1.0 to 2.0 will generally sound like a large gap in volume, but from 2.0 to
3.0 much less so, and so on. Most audio applications that expose volume sliders
to humans will expose them as decibels and convert to an amplitude factor
internally. If you're just writing a game, you can mostly ignore this, but if
you're doing something more complicated a proper understanding of decibels is
important. In decibals, a gain of 1.0 is at 0 dB and every increase and/or
decrease by 1 decibel sounds like the same amount of loudness as any other. The
specific formulas to get to and/or from a gain are as follows:</p>
<pre><code>decibels = 20 * log10(gain)
gain = 10**(db/20)
</code></pre>
<p>Where <code>**</code> is exponentiation.</p>
<p>The obvious question is of course "why not expose this as decibels?" The
problem with decibels is that gains over 1.0 will clip in most applications. But
a gain of 1.0 in decibels is 0 dB. If there are two incredibly loud sounds both
with a gain of 1.0 playing at the same time, the overall gain is effectively
2.0, which can clip in the same way. But 0 dB + 0 dB is still 0 dB even though
the correct gain is 2.0. This gets worse for gains below 0. Consider 0.5,
which is equivalent to roughly -6 dB. 0.5 + 0.5 is 1, but -6 + -6 is -12 dB.
Which isn't only wrong, it even moved in the wrong direction all together.</p>
<p>As a consequence Synthizer always uses multiplicative factors on the amplitude,
not decibels. Unless you know what you're doing, you should convert to gain as
soon as possible and reason about how this works as a multiplier.</p>
<h1 id="pausing-and-resuming-playback"><a class="header" href="#pausing-and-resuming-playback">Pausing and Resuming Playback</a></h1>
<p>All objects which play audio offer the following two functions:</p>
<pre><code>SYZ_CAPI syz_ErrorCode syz_pause(syz_Handle object);
SYZ_CAPI syz_ErrorCode syz_play(syz_Handle object);
</code></pre>
<p>Which do exactly what they seem like they do.</p>
<p>In bindings, these are usually bound as instance methods, e.g. <code>myobj.pause()</code>.</p>
<h1 id="configuring-objects-to-continue-playing-until-silent"><a class="header" href="#configuring-objects-to-continue-playing-until-silent">Configuring Objects to Continue Playing Until Silent</a></h1>
<p>By default, Synthizer objects become silent when their reference counts go to 0, but this isn't always what you want.
Sometimes, it is desirable to be able to continue playing audio until the object is "finished", for example for gunshots
or other one-off effects. Synthizer calls this lingering, and offers the following API to configure it:</p>
<pre><code>struct syz_DeleteBehaviorConfig {
int linger;
double linger_timeout;
};
SYZ_CAPI void syz_initDeleteBehaviorConfig(struct syz_DeleteBehaviorConfig *cfg);
SYZ_CAPI syz_ErrorCode syz_configDeleteBehavior(syz_Handle object, struct syz_DeleteBehaviorConfig *cfg);
</code></pre>
<p>To use it, call <code>syz_initDeleteBehaviorConfig</code> on an empty <code>syz_DeleteBehaviorConfig</code> struct, fill out the struct, and
call <code>syz_configDeleteBehavior</code>. The fields have the following meaning:</p>
<ul>
<li><code>linger</code>: if 0, die immediately, which is the default. If 1, keep the object around until it "finishes". What this
means depends on the object and is documented in the object reference, but it generally "does what you'd expect". For
some examples:
<ul>
<li><code>BufferGenerator</code> will stop any looping and play until the end of the buffer, or die immediately if paused.</li>
<li>All sources will keep going until all their generators are no longer around.</li>
</ul>
</li>
<li><code>linger_timeout</code>: if nonzero, set an upper bound on the amount of time an object may linger for. This is useful as a
sanity check in your application.</li>
</ul>
<p>These functions only <em>configure</em> what happens when the last reference to an object goes away and do <em>not</em> destroy the
object or manipulate the reference count in any other way. It is valid to call them immediately after object creation
if desired. No Synthizer interface besides <code>syz_handleDecRef</code> will destroy an object unless otherwise explicitly
documented.</p>
<p>Lingering doesn't keep related objects alive. For example a <code>BufferGenerator</code> that is lingering still goes silent if
the buffer attached to it is destroyed.</p>
<p>As with pausing, bindings usually make this an instance method.</p>
<h1 id="decoding-audio-data"><a class="header" href="#decoding-audio-data">Decoding Audio Data</a></h1>
<h2 id="the-quick-overview"><a class="header" href="#the-quick-overview">The Quick Overview</a></h2>
<p>Synthizer supports mp3, wav, and flac. If you need more formats, then you can
<a href="concepts/./libsndfile.html">load Libsndfile</a> or decode the data yourself.</p>
<p>If you need to read from a file, use e.g. <code>syz_createBufferFromFile</code>. If you
need to read from memory, use e.g. <code>syz_createBufferFromEncodedData</code>. If you
need to just shove floats at Synthizer, use <code>syz_creatBufferFromFloatArray</code>.</p>
<p><a href="concepts/../object_reference/streaming_generator.html">StreamingGenerator</a> has a similar
set of methods. In general you can find out what methods are available in the
object reference. Everything supports some function that's equivalent to
<code>syz_createBufferFromFile</code>.</p>
<p>These functions are the most stable interface because they can be easily
supported across incompatible library versions. If your app can use them, it
should do so.</p>
<h2 id="streams"><a class="header" href="#streams">Streams</a></h2>
<p>Almost all of these methods wrap and hide something called a stream handle,
which can be created with e.g. <code>syz_createStreamHandleFromFile</code>, then used with
e.g. <code>syz_createBufferFromStreamHandle</code>. Bindings expose this to you, usually
with classes or your language's equivalent (e.g. in Python this is
<code>StreamHandle</code>). This is used to get data from custom sources, for example the
network or encrypted asset stores. For info on writing your own streams, see
<a href="concepts/./custom_streams.html">the dedicated section</a>.</p>
<p>In addition to get streams via specific methods, Synthizer also exposes a
generic interface:</p>
<pre><code>SYZ_CAPI syz_ErrorCode syz_createStreamHandleFromStreamParams(syz_Handle *out, const char *protocol, const char *path, void *param);
</code></pre>
<p>Using the generic interface, streams are referred to with:</p>
<ul>
<li>A protocol, for example<code>"file"</code>, which specifies the kind of stream it is.
Users can register their own protocols.</li>
<li>A path, for example to a file on disk. This is protocol-specific.</li>
<li>And a <code>void *</code> param, which is passed through to the underlying stream
implementation, and currently ignored by Synthizer.</li>
</ul>
<p>So, for example, you might get a file by:</p>
<pre><code>syz_createStreamHandleFromStreamParams("file", path, NULL);
</code></pre>
<p>Streams don't support raw data. They're always an encoded asset. So for
example mp3 streams are a thing, but floats at 44100 streams aren't. Synthizer
will offer a better interface for raw audio data pending there being enough
demand and a reason to go beyond <code>syz_createBufferFromFloatArray</code>.</p>
<h1 id="loading-libsndfile"><a class="header" href="#loading-libsndfile">Loading Libsndfile</a></h1>
<p>Synthizer supports 3 built-in audio formats: wav, MP3, and Flac. For apps which
need more, Synthizer supports loading
<a href="http://www.mega-nerd.com/libsndfile/">Libsndfile</a>. To do so, use
<code>syz_initializeWithConfig</code> and configure <code>libsndfile_path</code> to be the absolute
path to a Libsnddfile shared object (<code>.dll</code>, <code>.so</code>, etc). Libsndfile will then
automatically be used where possible, replacing the built-in decoders.</p>
<p>Unfortunately, due to Libsndfile limitations, Libsndfile can only be used on
seekable streams of known length. All Synthizer-provided methods of decoding
currently support this, but custom streams may opt not to do so, for example if
they're reading from the network. In this case, Libsndfile will be skipped. To
see if this is happening, enable debug logging at library initialization and
Synthizer will log what decoders it's trying to use.</p>
<p>Because of licensing incompatibilities, Libsndfile cannot be statically linked
with Synthizer without effectively changing Synthizer's license to LGPL.
Consequently dynamic linking with explicit configuration is the only way to use
it. Your app will need to arrange to distribute a Libsndfile binary as well and
use the procedure described above to load it.</p>
<h1 id="implementing-custom-streams-and-custom-stream-protocols"><a class="header" href="#implementing-custom-streams-and-custom-stream-protocols">Implementing Custom Streams and Custom Stream Protocols</a></h1>
<p>Synthizer supports implementing custom streams in order to read from places that
aren't files or memory: encrypted asset stores, the network, and so on. This
section explains how to implement them.</p>
<p>before continuing, carefully consider whether you need this. Implementing a
stream in a higher level language and forcing Synthizer to go through it has a
small but likely noticeable performance hit. It'll work fine, but the built-in
functionality will certainly be faster and more scalable. Implementing a stream
in C is a complex process. If your app can use the already-existing
funtionality, it is encouraged to do so.</p>
<h2 id="a-complete-python-example"><a class="header" href="#a-complete-python-example">A Complete Python Example</a></h2>
<p>The rest of this section will explain in detail how streams work from the C
API, but this is a very complex topic and most of the infrastructure which
exists for it exists to make it possible to write convenient bindings.
Consequently, here is a complete and simple custom stream which wraps a Python
file object, registered as a custom protocol:</p>
<pre><code>class CustomStream:
def __init__(self, path):
self.file = open(path, "rb")
def read(self, size):
return self.file.read(size)
def seek(self, position):
self.file.seek(position)
def close(self):
self.file.close()
def get_length(self):
pos = self.file.tell()
len = self.file.seek(0, 2)
self.file.seek(pos)
return len
def factory(protocol, path, param):
return CustomStream(path)
synthizer.register_stream_protocol("custom", factory)
gen = synthizer.StreamingGenerator.from_stream_params(ctx, "custom", sys.argv[1])
</code></pre>
<p>Your bindings will document how to do this, for example in Python see
<code>help(synthizer.register_stream_protocol)</code>. it's usually going to be this level
of complexity when doing it from a binding. The rest of this section explains
what's going on from the C perspective, but non-C users are still encouraged to
read it because it explains the general idea and offers best practices for
efficient and stable stream usage.</p>
<p>It's important to note that though this example demonstrates using
<code>StreamingGenerator</code>, buffers have similar methods to decode themselves from
streams. Since <code>StreamingGenerator</code> has a large latency for anything but the
initial start-up, the primary use case is actually likely to be buffers.</p>
<h2 id="the-c-interface"><a class="header" href="#the-c-interface">The C Interface</a></h2>
<p>To define a custom stream, the following types are used:</p>
<pre><code>typedef int syz_StreamReadCallback(unsigned long long *read, unsigned long long requested, char *destination, void *userdata, const char ** err_msg);
typedef int syz_StreamSeekCallback(unsigned long long pos, void *userdata, const char **err_msg);
typedef int syz_StreamCloseCallback(void *userdata, const char **err_msg);
typedef void syz_StreamDestroyCallback(void *userdata);
struct syz_CustomStreamDef {
syz_StreamReadCallback *read_cb;
syz_StreamSeekCallback *seek_cb;
syz_StreamCloseCallback *close_cb;
syz_StreamDestroyCallback *destroy_cb;
long long length;
void *userdata;
};
SYZ_CAPI syz_ErrorCode syz_createStreamHandleFromCustomStream(syz_Handle *out, struct syz_CustomStreamDef *callbacks);
typedef int syz_StreamOpenCallback(struct syz_CustomStreamDef *callbacks, const char *protocol, const char *path, void *param, void *userdata, const char **err_msg);
SYZ_CAPI syz_ErrorCode syz_registerStreamProtocol(const char *protocol, syz_StreamOpenCallback *callback, void *userdata);
</code></pre>
<p>The following sections explain how these functions work.</p>
<h2 id="ways-to-get-a-custom-stream"><a class="header" href="#ways-to-get-a-custom-stream">Ways To Get A Custom Stream</a></h2>
<p>There are two ways to get a custom stream. You can:</p>
<ul>
<li>Fill out the callbacks in <code>syz_CustomStreamDef</code> and use
<code>syz_createStreamHandleFromCustomStream</code>.</li>
<li>Write a function which will fill out <code>syz_CustomStreamDef</code> from the standard
stream parameters, and register a protocol with <code>syz_registerStreamProtocol</code>.</li>
</ul>
<p>The difference between these is the scope: if you don't register a protocol,
only your app can access the custom stream, presumably via a module that
produces them. This is good because it keeps things modular. If registering a
protocol, however, the protocol can be used from anywhere in the process,
including other libraries and modules. For example, writing a
<code>encrypted_sqlite3</code> protocol C library could then be used to add the
<code>"encrypted_sqlite3"</code> protocol to any language.</p>
<p>Protocol names must be unique. The behavior is undefined if they aren't. A
good way of ensuring this is to namespace them. For example,
<code>"ahicks92.my_super_special_protocol"</code>.</p>
<p>The <code>void *param</code> parameter is reserved for your implementation, and passed to
the factory callback if using the stream parameters approach. It's assumed that
implementations going through <code>syz_createStreamHandleFromCustomStreamDef</code>
already have a way to move this information around.</p>
<h2 id="non-callback-syz_customstreamdef-parameters"><a class="header" href="#non-callback-syz_customstreamdef-parameters">Non-callback <code>syz_CustomStreamDef</code> parameters</a></h2>
<p>These are:</p>
<ul>
<li><code>length</code>, which must be set and known for seekable streams. If the length of
the stream is unknown, set it to -1.</li>
<li><code>userdata</code>, which is passed as the userdata parameter to all stream callbacks.</li>
</ul>
<h2 id="the-stream-callbacks"><a class="header" href="#the-stream-callbacks">The Stream Callbacks</a></h2>
<p>Streams have the following callbacks, with mostly self-explanatory parameters:</p>
<ul>
<li>If going through the protocol interface, the open callback is called when the
stream is first opened. If going through
<code>syz_createStreamHandleFromCustomStream</code>, it is assumed that the app already
opened the stream and has put whatever it is going to need into the <code>userdata</code>
field.</li>
<li>After that, the read and (if present) seek callbacks are called until the
stream is no longer needed. The seek callback is optional.</li>
<li>The close callback is called when Synthizer will no longer use the underlying
asset.</li>
<li>The destroy callback, optional, is called when it is safe to free all
resources the stream is using.</li>
</ul>
<p>For more information on why we offer both the close and destroy callback, see
below on error handling.</p>
<p>All callbacks should return 0 on success, and (if necessary) write to their out
parameters.</p>
<p>The read callback must always read exactly as many bytes are requested, never
more. If it reads less bytes than were requested, Synthizer treats this as an
end-of-stream condition. If the end of the stream has already been reached, the
read callback should claim it read no bytes.</p>
<p>The seek callback is optional. Streams don't need to support seeking, but this disables seeking in <code>StreamingGenerator</code>. it also disables support for
Libsndfile if Libsndfile was loaded, and additionally support for decoding wav files. In order to be seekable, a stream must:</p>
<ul>
<li>Have a seek callback; and</li>
<li>Fill out the <code>length</code> field with a positive value, the length of the stream in
bytes.</li>
</ul>
<h2 id="error-handling"><a class="header" href="#error-handling">Error Handling</a></h2>
<p>To indicate an error, callbacks should return a non-zero return value and
(optionally) set their <code>err_msg</code> parameter to a string representation of the
error. Synthizer will log these errors if logging is enabled. For more complex
error handling, apps are encouraged to ferry the information from streams to
their main threads themselves. If a stream callback fails, Synthizer will
generally stop the stream all together. Consequently, apps should do their best
to recover and never fail the stream. Synthizer takes the approach of assuming
that any error is likely unrecoverable and expects that implementations already
did their best to succeed.</p>
<p>if the read callback fails, the position of the stream isn't updated. If the
seek callback fails, Synthizer assumes that the position didn't move.</p>
<p>the reason that Synthizer offers a destroy callback in addition to one for
closing is so that streams may use non-static strings as error messages.
Synthizer may not be done logging these when the stream is closed, so apps doing
this should make sure that they live at least as long as the destroy callback,
after which Synthizer promises to never use anything related to this stream
again.</p>
<p>The simplest way to handle error messages for C users is to just use string
constants, but for other languages such as Python it is useful to be able to
convert errors to strings and attach them to the binding's object so that these
can be logged. The destroy callback primarily exists for this use case.</p>
<p>Synthizer makes one more guarantee on the lifetime required of <code>err_msg</code>
strings: they need only live as long as the next time a stream callback is
called. This means that, for example, the Python binding only keeps the most
recent error string around and replaces it as necessary.</p>
<h2 id="thread-safety"><a class="header" href="#thread-safety">Thread Safety</a></h2>
<p>Streams will only ever be used by one thread at a time, but may be moved between
threads.</p>
<h1 id="channel-upmixing-and-downmixing"><a class="header" href="#channel-upmixing-and-downmixing">Channel Upmixing and Downmixing</a></h1>
<p>Synthizer has built-in understanding of mono (1 channel) and stereo (2 channels)
audio formats. It will mix other formats to these as necessary. Specifically,
we:</p>
<ul>
<li>If converting from mono to any other format, broadcast the mono channel to all
of those in the other format.</li>
<li>If going to mono, sum and normalize the channels in the other format.</li>
<li>Otherwise, either drop extra channels or fill extra channels with silence.</li>
</ul>
<p>Synthizer will be extended to support surround sound in future, which will give
it a proper understanding of 4, 6, and 8 channels. Since Synthizer is aimed at
non-experimental home media applications, we assume that the channel count is
sufficient to know what the format is going to be. For example, there is no
real alternative to 5.1 audio in the home environment if the audio has 6
channels. If you need more complex multichannel handling, you can pre-convert
your audio to something Synthizer understands. Otherwise, other libraries may
be a better option.</p>
<h1 id="3d-audio-panning-and-hrtf"><a class="header" href="#3d-audio-panning-and-hrtf">3D Audio, Panning, and HRTF</a></h1>
<h2 id="introduction-1"><a class="header" href="#introduction-1">Introduction</a></h2>
<p>Synthizer supports panning audio through two interfaces.</p>
<p>First is <a href="concepts/../object_reference/panned_sources.html">AngularPannedSource and ScalarPannedSource</a>, which provides
simple azimuth/elevation controls and the ability to pan based off a scalar, a
value between -1 (all left) and 1 (all right). In this case the user
application must compute these values itself.</p>
<p>The second way is to use <a href="concepts/../object_reference/source_3d.html">Source3D</a>, which
simulates a 3D environment when fed positional data. This ection concerns
itself with proper use of <code>Source3D</code>, which is less straightforward for those
who haven't had prior exposure to these concepts.</p>
<h2 id="introduction-2"><a class="header" href="#introduction-2">Introduction</a></h2>
<p>There are two mandatory steps to using <code>Source3D</code> as well as a few optional
ones. The two mandatory steps are these:</p>
<ul>
<li>On the context, update <code>SYZ_P_POSITION</code> and <code>SYZ_P_ORIENTATION</code> with the
listener's position and orientation</li>
<li>On the source, update <code>SYZ_P_POSITION</code> with the source's position.</li>
</ul>
<p>And optionally:</p>
<ul>
<li>Configure the default distance model to control how rapidly sources become
quiet.</li>
<li>Emphasize that sources have become close to the listener with the focus boost.</li>
<li>Add effects (covered in <a href="concepts/./filters_and_effects.html">a dedicated section</a>).</li>
</ul>
<h2 id="dont-move-sources-through-the-head"><a class="header" href="#dont-move-sources-through-the-head">Don't Move Sources Through the Head</a></h2>
<p>People frequently pick up Synthizer, then try to move the source through the
center of the listener's head, then ask why it's weird and didn't work. It is
important to realize that this is a physical simulation of reality, and that the
reason you can move the source through the listener's head in the first place is
that this isn't an easily detectable case. If you aren't driving Synthizer in a
way connected to physical reality--for example if you are attempting to use <code>x</code>
as a way to pan sources from left to right and not linked to a position--then
you probably want one of the raw panned sources instead.</p>
<h2 id="setting-global-defaults"><a class="header" href="#setting-global-defaults">Setting Global Defaults</a></h2>
<p>In addition to controlling the listener, the context offers the ability to set
defaults for all values discussed below. This is done through a set of
<code>SYZ_P_DEFAULT_*</code> properties which match the names of those on sources. This is
of particular interest to those wishing to use HRTF, which is off by default.</p>
<h2 id="synthizers-coordinate-system-and-orientations"><a class="header" href="#synthizers-coordinate-system-and-orientations">Synthizer's Coordinate System and Orientations</a></h2>
<p>The short version for those who are familiar with libraries that need position
and orientation data: Synthizer uses a right-handed coordinate system and
configures orientation through <a href="concepts/./properties.html">double6 properties</a> so that it
is possible to atomically set all 6 values at once. This is represented as a
packed at and up vector pair in the format <code>(at_x, at_y, at_z, up_x, up_y, up_z)</code> on the context under the <code>SYZ_P_ORIENTATION</code> property. As with every
other library doing a similar thing, these are unit vectors.</p>
<p>The short version for those who want a 2D coordinate system where positive y is
north, positive x is east, and player orientations are represented as degrees
clockwise of north: use only x and y of <code>SYZ_P_POSITION</code>, and set
<code>SYZ_P_ORIENTATION</code> as follows:</p>
<pre><code>(sin(angle * PI / 180), cos(angle * PI / 180), 0, 0, 0, 1)
</code></pre>
<p>The longer version is as follows.</p>
<p>Listener positions are represented through 3 values: the position, the at
vector, and the up vector. The position is self-explanatory. The at vector
points in the direction the listener is looking at all times, and the up vector
points out the top of the listener's head, as if there were a pole up the spine.
By driving these 3 values, it is possible to represent any position and
orientation a listener might assume.</p>
<p>Synthizer uses a right-handed coordinate system, which means that if the at
vector is pointed at positive x and the up vector at positive y, positive z
moves sources to the right. This is called a right-handed coordinate system
because of the right-hand rule: if you point your fingers along positive x and
curl them toward positive y, your finger points at positive z. This isn't a
standard. Every library that does something with object positions tends to
choose a different value. If combining Synthizer with other non-2D components,
it may be necessary to convert between coordinate systems. Resources on how to
do this may easily be found through Google.</p>
<p>The at and up vectors must always be orthogonal, that is forming a right angle
with each other. In order to facilitate this, Synthizer uses double6 properties
so that both values can and must be set at the same time. If we didn't, then
there would be a brief period where one was set and the other wasn't, in which
case they would temporarily be invalid. Synthizer doesn't try to validate that
these vectors are orthogonal and generally tries to do its best when they
aren't, but nonetheless behavior in this case is undefined.</p>
<p>Finally, the at and up vectors must be unit vectors: vectors of length 1.</p>
<h2 id="panning-strategies-and-hrtf"><a class="header" href="#panning-strategies-and-hrtf">Panning strategies and HRTF.</a></h2>
<p>The panning strategy specifies how sources are to be panned. Synthizer supports
the following panning strategies:</p>
<table><thead><tr><th>Strategy</th><th>Channels</th><th>Description</th></tr></thead><tbody>
<tr><td>SYZ_PANNER_STRATEGY_HRTF</td><td>2</td><td>An HRTF implementation, intended for use via headphones.</td></tr>
<tr><td>SYZ_PANNER_STRATEGY_STEREO</td><td>2</td><td>A simple stereo panning strategy assuming speakers are at -90 and 90.</td></tr>
</tbody></table>
<p>When a source is created, the panning strategy it is to use is passed via the constructor function and cannot be
changed. A special value, <code>SYZ_PANNER_STRATEGY_DELEGATE</code> allows the source to delegate this to the context, and can be
used in cases where the context's configuration should be preferred. A vast majority of applications will do this
configuration via the context and <code>SYZ_PANNER_STRATEGY_DELEGATE</code>; other values should be safed for cases in which you
wish to mix panning types.</p>
<p>By default Synthizer is configured to use a stereo panning strategy, which
simply pans between two speakers. This is because stereo panning strategies
work on all devices from headphones to 5.1 surround sound systems, and it is not
possible for Synthizer to reliably determine if the user is using headphones or
not. HRTF provides a much better experience for headphone users but must be
enabled by your application through setting the default panner strategy or doing
so on individual sources.</p>
<p>Since panning strategies are per source, it is possible to have sources using
different panning strategies. This is useful for two reasons: HRTF is expensive
enough that you may wish to disable it if dealing with hundreds or thousands of
sources, and it is sometimes useful to let UI elements use a different panning
strategy. An example of this latter case is an audio gauge which pans from left
to right.</p>
<h2 id="distance-models"><a class="header" href="#distance-models">Distance Models</a></h2>
<p>The distance model controls how quickly sources become quiet as they move away
from the listener. This is controlled through the following properties:</p>
<ul>
<li><code>SYZ_P_DISTANCE_MODEL</code>: which of the distance model formulas to use.</li>
<li><code>SYZ_P_DISTANCE_MAX</code>: the maximum distance at which the source will be
audible.</li>
<li><code>SYZ_P_DISTANCE_REF</code>: if you assume your source is a sphere, what's the radius
of it?</li>
<li><code>SYZ_P_ROLLOFF</code>: with some formulas, how rapidly does the sound get quieter?
Generally, configuring this to a higher value makes the sound drop off more
immediately near the head, then have more subtle changes at further distances.</li>
</ul>
<p>It is not possible to provide generally applicable advice for what you should
set the distance model to. A game using meters needs very different settings
than one using feet or light years. Furthermore, these don't have concrete
physical correspondances. Of the things Synthizer offers, this is possibly the
least physically motivated and the most artistic from a game design perspective.
In other words: play with different values and see what you like.</p>
<p>The concrete formulas for the distance models are as follows. Let <code>d</code> be the
distance to the source, <code>d_ref</code> the reference distance, <code>d_max</code> the max
distance, <code>r</code> the roll-off factor. Then the gain of the source is computed as a
linear scalar using one of the following formulas:</p>
<table><thead><tr><th>Model</th><th>Formula</th></tr></thead><tbody>
<tr><td>SYZ_DISTANCE_MODEL_NONE</td><td><code>1.0</code></td></tr>
<tr><td>SYZ_DISTANCE_MODEL_LINEAR</td><td>1 - r * (clamp(d, d_ref, d_max) - d_ref) / (d_max - d_ref);</td></tr>
<tr><td>SYZ_DISTANCE_MODEL_EXPONENTIAL when <code>d_ref == 0.0</code></td><td>0.0</td></tr>
<tr><td>SYZ_DISTANCE_MODEL_EXPONENTIAL when <code>d_ref > 0.0</code></td><td><code>(max(d_ref, d) / d_ref) ** -r</code></td></tr>
<tr><td>SYZ_DISTANCE_MODEL_INVERSE when <code>d_ref = 0.0</code></td><td><code>0.0</code></td></tr>
<tr><td>SYZ_DISTANCE_MODEL_INVERSE when <code>d_ref > 0.0</code></td><td><code>d_ref / (d_ref + r * max(d, d_ref) - d_ref)</code></td></tr>
</tbody></table>
<h2 id="the-closeness-boost"><a class="header" href="#the-closeness-boost">The Closeness Boost</a></h2>
<p>Sometimes, it is desirable to make sources "pop out" of the background
environment. For example, if the player approaches an object with which they
can interact, making it noticeably louder as the boundary is crossed can be
useful. This is of primary interest to audiogame designers, a type of game for
the blind, as it can be used to emphasize features of the environment in
non-realistic but informative ways.</p>
<p>This is controlled through two properties:</p>
<ul>
<li><code>SYZ_P_CLOSENESS_BOOST</code>: a value in DB controlling how much louder to make the
sound. Negative values are allowed.</li>
<li><code>SYZ_P_CLOSENESS_BOOST_DISTANCE</code>: when the source is closer than this
distance, begin applying the closeness boost.</li>
</ul>
<p>When the source is closer than the configured distance, the normal gain
computation still applies, but an additional factor, the number of DB in the
closeness boost, is added. This means that it is still possible for players to
know if they are getting closer to the source.</p>
<p>The reason that the closeness boost is specified inDB is that otherwise it
would require values greater than 1.0, and it is primarily going to be fed from
artists and map developers. If we discover that this is a problem in future, it
will be patched in a major Synthizer version bump.</p>
<p>Note that closeness boost has not gotten a lot of use yet. Though we are
unlikely to remove the interface, the internal algorithms backing it might
change.</p>
<h1 id="filters-and-effects"><a class="header" href="#filters-and-effects">Filters and Effects</a></h1>
<p>Synthizer supports filters and effects in order to add environmental audio and
do more than just playing sources in a vacuum. These sections explain how this
works.</p>
<h1 id="filters"><a class="header" href="#filters">Filters</a></h1>
<p>Synthizer supports a filter property type, as well as filters on effect sends.
The API for this is as follows:</p>
<pre><code>struct syz_BiquadConfig {
...
};
SYZ_CAPI syz_ErrorCode syz_getBiquad(struct syz_BiquadConfig *filter, syz_Handle target, int property);
SYZ_CAPI syz_ErrorCode syz_setBiquad(syz_Handle target, int property, const struct syz_BiquadConfig *filter);
SYZ_CAPI syz_ErrorCode syz_biquadDesignIdentity(struct syz_BiquadConfig *filter);
SYZ_CAPI syz_ErrorCode syz_biquadDesignLowpass(struct syz_BiquadConfig *filter, double frequency, double q);
SYZ_CAPI syz_ErrorCode syz_biquadDesignHighpass(struct syz_BiquadConfig *filter, double frequency, double q);
SYZ_CAPI syz_ErrorCode syz_biquadDesignBandpass(struct syz_BiquadConfig *filter, double frequency, double bandwidth);
</code></pre>
<p>See <a href="concepts/./properties.html">properties</a> for how to set filter properties and
<a href="concepts/./effects.html">effects</a> for how to apply filters to effect sends.</p>
<p>The struct <code>syz_BiquadConfig</code> is an opaque struct whose fields are only exposed
to allow allocating them on the stack. It represents configuration for a biquad
filter, designed using the <a href="concepts/../appendices/audio_eq_cookbook.html">Audio EQ
Cookbook</a>. It's initialized with one of the
above design functions.</p>
<p>A suggested default for <code>q</code> is <code>0.7071135624381276</code>, which gives Buttererworth
lowpass and highpass filters. For those not already familiar with biquad
filters, <code>q</code> controls resonance: higher values of <code>q</code> will cause the filter to
ring for some period of time.</p>
<p>All sources support filters, which may be installed in 3 places:</p>
<ul>
<li><code>SYZ_P_FILTER</code>: applies to all audio traveling through the source.</li>
<li><code>SYZ_P_FILTER_DIRECT</code>: applied after <code>SYZ_P_FILTER</code> to audio going directly to
the speakers/through panners.</li>
<li><code>SYZ_P_FILTER_EFFECTS</code>: Applied after <code>SYZ_P_FILTER</code> to audio going to
effects.</li>
</ul>
<p>This allows filtering the audio to effects separately, for example to cut high
frequencies out of reverb on a source-by-source basis.</p>
<p>Additionally, all effects support a <code>SYZ_P_FILTER_INPUT</code>, which applies to all
input audio to the effect. So, you can either have:</p>
<pre><code>source filter -> direct path filter -> speakers
</code></pre>
<p>Or:</p>
<pre><code>source filter -> effects filter outgoing from source -> filter on effect send -> input filter to effect -> effect
</code></pre>
<p>In future, Synthizer will stabilize the <code>syz_BiquadConfig</code> struct and use it to
expose more options, e.g. automated filter modulation.</p>
<h1 id="effects-and-effect-routing"><a class="header" href="#effects-and-effect-routing">Effects and Effect Routing</a></h1>
<p>users of the Synthizer API can route any number of sources to any number of
global effects, for example <a href="concepts/../object_reference/global_echo.html">echo</a>. This is
done through the following C API:</p>
<pre><code>struct syz_RouteConfig {
double gain;
double fade_time;
syz_BiquadConfig filter;
};
SYZ_CAPI syz_ErrorCode syz_initRouteConfig(struct syz_RouteConfig *cfg);
SYZ_CAPI syz_ErrorCode syz_routingConfigRoute(syz_Handle context, syz_Handle output, syz_Handle input, struct syz_RouteConfig *config);
SYZ_CAPI syz_ErrorCode syz_routingRemoveRoute(syz_Handle context, syz_Handle output, syz_Handle input, double fade_out);
SYZ_CAPI syz_ErrorCode syz_routingRemoveAllRoutes(syz_Handle context, syz_Handle output, double fade_out);
</code></pre>
<p>Routes are uniquely identified by the output object (Source3D, etc) and input
object (Echo, etc). There is no route handle type, nor is it possible to form
duplicate routes.</p>
<p>In order to establish or update the parameters of a route, use
<code>syz_routingConfigRoute</code>. This will form a route if there wasn't already one,
and update the parameters as necessary.</p>
<p>It is necessary to initialize <code>syz_RouteConfig</code> with <code>syz_initRouteConfig</code>
before using it, but this need only be done once. After that, reusing the same
<code>syz_RouteConfig</code> for a route without reinitializing it is encouraged.</p>
<p>Gains are per route and apply after the gain of the source. For example, you
might feed 70% of a source's output to something (gain = 0.7).</p>
<p>Filters are also per route and apply after any filters on sources. For example,
this can be used to change the filter on a per-reverb basis for a reverb zone
algorithm that feeds sources to more than one reverb at a time.</p>
<p>In order to remove a route, use <code>syz_routingRemoveRoute</code>. Alternatively, <code>syz_routingRemoveAllRoutes</code> can remove all
routes from a source.</p>
<p>Many effects involve feedback and/or other long-running audio as part of their
intended function. But while in development, it is often useful to reset an
effect. Synthizer exposes a function for this purpose:</p>
<pre><code>SYZ_CAPI syz_ErrorCode syz_effectReset(syz_Handle effect);
</code></pre>
<p>Which will work on any effect (at most, it does nothing). As with things like
property access this is slow, and it's also not going to sound good, but it can
do things like clear out the feedback paths of a reverb at the Python shell for
interactive experimentation purposes.</p>
<h1 id="events"><a class="header" href="#events">Events</a></h1>
<p>Synthizer supports receiving events. Currently, this is limited to knowing when
buffer/streaming generators have looped and/or finished. Note that the use case
of destroying objects only after they have stopped playing is better handled
with <a href="concepts/./lingering.html">lingering</a>.</p>
<p>The API for this is as follows:</p>
<pre><code>struct syz_Event {