zynaddsubfx

ZynAddSubFX open source synthesizer
Log | Files | Refs | Submodules | LICENSE

architecture.txt (6744B)


      1 ZynAddSubFX Architecture
      2 ========================
      3 :author: Mark McCurry
      4 
      5 In order to understand how to effectively navigate the source code and to
      6 better understand the relationships between the internal components of
      7 ZynAddSubFX.
      8 To start off, the coarsest division of the codebase can be in breaking the
      9 source into three areas:
     10 
     11 Backend::
     12     Realtime Data and Audio/Midi Handling
     13 Middware::
     14     Non-Realtime algorithms used to initialize realtime data and communication
     15     core
     16 UI::
     17     Any User Interface (graphical or otherwise) that binds to a middleware
     18     instance in order to permit modification of any parameters used in
     19     synthesis.
     20 
     21 These three components communicate to each other _almost_ exclusively through
     22 the use of OSC messages using librtosc or liblo.
     23 In this document, I hope to define all of the edge cases where communication
     24 (by design) does not use message passing as interactions are somewhat
     25 complicated lock free operations.
     26 
     27 Before getting into each layer's details, the following threads may exist:
     28 
     29 Main Thread::
     30     The original program thread, responsible for repeatedly polling events
     31     from NSM, LASH, general in-process UI, and middleware
     32 Middleware Helper Thread::
     33     Responsible for handling any procedures which may block normal event
     34     processing such as XML de-serialization
     35 Audio Output Thread::
     36     Thread responsible for performing synthesis and passing it to driver level
     37     API for the sound to be output
     38 MIDI  Input  Thread::
     39     Thread responsible for handling midi events. This thread is only active if
     40     the audio output and the midi input drivers are not the same type.
     41 
     42 Now for the meat of things:
     43 
     44 The Backend
     45 -----------
     46 
     47 Historically the realtime side of things has revolved around a single instance
     48 of the aptly named class 'Master', which is the host to numerous
     49 implementation pointers and instances of all Parts which in turn contain more
     50 parameters and notes.
     51 This instance generally assumes that it is in full control of all of its data
     52 and it gets regularly called from the IO subsystem to produce some output
     53 audio in increments of some set block size.
     54 All classes that operate under a given instance of 'Master' assume that they
     55 have a fixed:
     56 
     57 Buffer Size::
     58     Unit of time to calculate at once and interval to perform interpolations
     59     over
     60 Oscillator Size::
     61     Size of the Additive Synthesis Oscillator Interpolation Buffer
     62 Sample Rate::
     63     Number of samples per second
     64 Allocator::
     65     Source for memory allocations from a resizable memory pool
     66 
     67 Changing any of these essentially requires rebuilding all child data
     68 structures at the moment.
     69 
     70 Most of the children objects can be placed into the categories:
     71 
     72 Parameter Objects::
     73     Objects which contain serialize able parameters for synthesis and little to
     74     no complex math
     75 Synthesis Objects::
     76     Objects which initialize with parameter objects and generate output audio or
     77     control values for other synthesis objects
     78 Container Objects::
     79     Objects which are responsible for organizing a dvariety of synthesis and
     80     parameter objects and for routing the outputs of each child object to the
     81     right destination (e.g. 'Part' and 'Master')
     82 Hybrid Objects::
     83     Objects which have _Odd_ divisions between what is a parameter, and what
     84     is destined for synthesis (e.g. 'PADnoteParameters' and 'OscilGen')
     85 
     86 The normal behavior of these objects can be seen by observing a call of
     87 _OutMgr::tick_ which first flushes the midi queue possibly constructing a few
     88 new notes via _Part::NoteOn_, then _Master::AudioOut_ is called.
     89 This is the root of the synthesis calls, but before anything is synthesized,
     90 OSC messages are dispatched which typically update system parameter and
     91 coefficients which cannot be calculated in realtime such as padnote based
     92 wavetables.
     93 Most data is allocated on the initialization of the add/sub/pad synthesis
     94 engine, however anything which cannot be bounded easily then is allocated via
     95 the tlsf based allocator.
     96 
     97 
     98 The MiddleWare
     99 --------------
    100 
    101 Now in the previous section, details on how exactly messages were delivered
    102 was only vaguely mentioned.
    103 Anything unclear should hopefully be clarified here. 
    104 The primary message handling is taken care of by two ring buffers 'uToB' and
    105 'bToU' which are, respectively, the user interface to backend and the backend
    106 to user interface ringbuffers.
    107 Additionally, Middleware handles non-realtime processing, such as 'Oscilgen'
    108 and 'PADnoteParameters'.
    109 
    110 To handle these cases, any message from a user interface is intercepted.
    111 Non-realtime requests are handled in middleware itself and other messages are
    112 forwarded along.
    113 This permits some internal messages to be sent that the UI has never directly
    114 requested.
    115 A similar process occurs for messages originating from the backend.
    116 
    117 A large portion of the middleware code is designed to manage up-to-date
    118 pointers to the internal datastructures, in order to avoid directly accessing
    119 anything via the pointer to the 'Master' datastructure.
    120 
    121 Loading
    122 ~~~~~~~
    123 
    124 In order to avoid access to the backend datastructures typically a replacement
    125 object is sent to the backend to be copied from or from which a pointer swap
    126 can occur.
    127 
    128 Saving
    129 ~~~~~~
    130 
    131 This is where the nice pristine hands off approach sadly comes to an end.
    132 There simply isn't an effective means of capturing all parameters without
    133 taking a large amount of time.
    134 
    135 The master has two kinds of parameter objects:
    136  - Realtime parameters which are only ever mutable through
    137    * OSC dispatch within Master
    138  - Non realtime parameters which are only ever mutable through
    139    * OSC dispatch within Master
    140    * MiddleWare (using struct NonRtObjStore)
    141 Now, in order to permit the serialization of parameter objects, the backend is
    142 'frozen': Since the freezing message is the last one the MiddleWare
    143 sends, this essentially prevents the backend from processing further messages
    144 from the user interface. When this occurs the parameters which are to be
    145 serialized can be guaranteed to be constant and thus safe to access across
    146 threads.
    147 
    148 This class of read-only-operation can be seen as used in parameter copy/paste
    149 operations and in saving full instances as well as instruments.
    150 
    151 
    152 The User Interface
    153 ------------------
    154 
    155 From an architectural standpoint the important thing to note about the user
    156 interface is that virtual every widget has a location which is composed of a
    157 base path and a path extension.
    158 Most messages going to a widget are solely to this widget's one location
    159 (occasionally they're to a few associated paths).
    160 
    161 This structure makes it possible for a set of widgets to get relocated
    162 (rebase/reext) to another path.
    163 This occurs quite frequently (e.g. "/part0/PVolume" -> "/part1/PVolume") and
    164 it may be the occasional source of bugs.