From 8ea759d11e258c9eae8f1abc181e7e548ce97b45 Mon Sep 17 00:00:00 2001 From: kevin Date: Fri, 27 Dec 2024 15:32:58 -0500 Subject: [PATCH] notes.md : Updates --- notes.md | 255 ++++++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 204 insertions(+), 51 deletions(-) diff --git a/notes.md b/notes.md index 81f9334..a173363 100644 --- a/notes.md +++ b/notes.md @@ -941,28 +941,40 @@ resolvable without more information. ### TODO: -- Memory allocation of all non-integral types should be the responsibility of the the processor instances -this includes strings. Change the built-in string type to be a const string and -make it the responsibility of the proc instances with 'string' var's to handle -alloc and dealloc of strings. +- The following two tasks need more consideration. As it is variables assume that aggregate +types are destroyed on exit. This is very convenient. Consider using 'symbols' to represent +strings, consider adding a 'const-string' type to eliminate memory allocation of string assignment + - Memory allocation of all non-integral types should be the responsibility of the the processor instances + this includes strings. Change the built-in string type to be a const string and + make it the responsibility of the proc instances with 'string' var's to handle + alloc and dealloc of strings. + + - String assignment is allocating memory: + See: `rc_t _val_set( value_t* val, const char* v ) cwFlowTypes.cpp line:464.` + +- DONE Presets do not work for hetergenous networks. - Add a proc class flag: 'top-level-only-fl' to indicate that a processor cannot run as part of a poly. Any processor that calls a global function, like 'network_apply_preset()' must run a the top level only. -- Eliminate the value() custom proc_t function and replace it by setting a 'delta flag' on the +- Consider eliminating the value() custom proc_t function and replace it by setting a 'delta flag' on the variables that change. Optionally a linked list of changed variables could be implemented to avoid having to search for changed variable values - although this list might have to be implemented as a thread safe linked list. -- Allow proc's to send messages to the UI. Implementation: During exec() the proc builds a global list of variables whose values +- value() should return a special return-code value to indicate that the +value should not be updated and distinguish it from an error code - which should stop the system. + + +- DONE: Allow proc's to send messages to the UI. Implementation: During exec() the proc builds a global list of variables whose values should be passed to the UI. Adding to the list must be done atomically, but removing can be non-atomic because it will happen at the end of the network 'exec' cycle when no proc's are being executed. See cwMpScNbQueue push() for an example of how to do this. - Allow min/max limits on numeric variables. -- Add a 'doc' string-list to the class desc. +- DONE: Add a 'doc' string-list to the class desc. -- Try using adding 'time' type and 'cfg' types to a 'log:{...}' stmt. +- Add 'time' type and 'cfg' types to a 'log:{...}' stmt. - print_network_fl and print_proc_dict_fl should be given from the command line. (but it's ok to leave them as cfg flags also) @@ -971,16 +983,19 @@ at the end of the network 'exec' cycle when no proc's are being executed. See c - Add 'doc' strings to user-defined proc data structure. -- It is an error to specify a suffix_id on a poly network proc because the suffix_id's are generated automatically. +- DONE: It is an error to specify a suffix_id on a poly network proc because the suffix_id's are generated automatically. This error should be caught by the compiler. - How do user defined procedures handle suffix id's? -- Add a 'preset' arg to 'poly' so that a preset can be selected via the owning network. +- DONE: Add a 'preset' arg to 'poly' so that a preset can be selected via the owning network. Currently it is not possible to select a preset for a poly. -- Automatic assignment of sfx_id's should only occur when the network is a 'poly'. - This should be easy to detect. +- DONE: Automatic assignment of sfx_id's should only occur when the network is a 'poly'. + This should be easy to detect. + +- DONE: If a proc, inside a poly, is given a numeric suffix then that suffix will +overwrite the label_sfx_id assigned by the system. This case should be detected. - When a var value is given to var_create() it does not appear to channelize the var if value is a list. Is a value ever given directly to `var_create()`? @@ -988,15 +1003,12 @@ Look at all the places `var_create()` is called can the value arg. be removed? - var_channelize() should never be called at runtime. -- Re-write the currawong circuit with caw. +- DONE: Re-write the currawong circuit with caw. - Finish audio feedback example - this will probably involve writing an `audio_silence` class. - Issue a warning if memory is allocated during runtime. -- String assignment is allocating memory: - See: `rc_t _val_set( value_t* val, const char* v ) cwFlowTypes.cpp line:464.` - - cwMpScNbQueue is allocating memory. This makes it blocking. - Check for illegal variable names in class descriptions. (no periods, trailing digits, or trailing underscores) @@ -1022,16 +1034,19 @@ Look at all the places `var_create()` is called can the value arg. be removed? + Disabled "disp_str" should turn grey. + + mult var's with more than 3 values should be put into a list or use a 'disclose' button + + + mult proc's with more than 3 instances should be put into a list or use a 'disclose' button + + - Class presets cannot address 'mult' variables. Maybe this is ok since 'mult' variables are generally connected to a source? ... although 'gain' mult variables are not necessarily connected to a source see: `audio_split` or `audio_mix`. Has this problem been addressed by allowing mult variables to be instantiated in the 'args' statement? -- Documentation w/ examples. +- Write processor development documentation w/ examples. + Write the rules for each implementing member functions. -- value() should return a special return-code value to indicate that the -value should not be updated and distinguish it from an error code - which should stop the system. - flow classes and variable should have a consistent naming style: camelCase or snake_case. @@ -1044,7 +1059,6 @@ value should not be updated and distinguish it from an error code - which should - Reduce runtime overhead for var get/set operations. -- Implement matrix types. - Should the `object_t` be used in place of `value_t`? @@ -1057,6 +1071,10 @@ value should not be updated and distinguish it from an error code - which should - Audio inputs should be able to be initialized with a channel count and srate without actually connecting an input. This will allow feedback connections to be attached to them at a later stage of the network instantiation. + +- The signal srate should determine the sample rate used by a given processor. +The system sample rate should only be used a default/fallback value. +Processors that have mandatory signal inputs should never need to also have an srate parameter. - Implement user-defined-proc preset application. @@ -1070,11 +1088,13 @@ value should not be updated and distinguish it from an error code - which should - Implement a debug mode to aid in building networks and user-defined-procs (or is logging good enough) -- Implement multi-field messages. +- DONE: Implement multi-field messages. - Implement user defined data types. -- Look more closely at the way of identify an in-stmt src-net or a out-stmt in-net. +- Implement matrix types. + +- Look more closely at the way to identify an in-stmt src-net or a out-stmt in-net. It's not clear there is a difference between specifying `_` and the default behaviour. Is there a way to tell it to search the entire network from the root? Isn't that what '_' is supposed to do? @@ -1082,8 +1102,6 @@ what '_' is supposed to do? - cwAudioFile cannot convert float or double input samples to 24 bit output samples See audiofile::writeFloat() and audiofile::writeDouble(). -- If a proc, inside a poly, is given a numeric suffix then that suffix will -overwrite the label_sfx_id assigned by the system. This case should be detected. Host Environments: ------------------ @@ -1186,7 +1204,7 @@ These callbacks occur asynchronously on the IO system audio processing thread. Network Architecture and Theory of Operation --------------------------------------------- -A _caw_ network is an ordered set of processors where a given +A _caw_ graph is an ordered set of processors where a given processor may contain a set of internal networks. This leads to a heirarchy of networks and processors that can be depicted like this. @@ -1196,43 +1214,104 @@ that can be depicted like this. This diagram shows a two level network, where the internal network contains an array of networks. -Networks are executed one processor at a time from top to bottom. -The networks that are members of network arrays, sibling networks, -may execute concurrently. For this reason sibling networks -may not contain any inter-connnections. +Networks are executed sequentially, one processor at a time, from top +to bottom. Networks that are members of the same network array, +referred to as silbing networks, may however execute concurrently to +one another. To avoid concurrency hazards sibling networks may +therefore not contain any inter-connnections, and the language +precludes them. +There are two primary thread hazards to this arrangment: +1. Processors executing outside of the top level +should not write to global data or interact with +the global system API. There is no way for the top level of a network to be a sibling -network, therefore it's processors are guaranteed to run in sequence -and be the only processor running while they are executing. +network, therefore processors that are part of this network are +guaranteed to run in sequence and be the only processor running while +they are executing. -Network presets can only be applied between execution cycles (by the -control application) or by a top-level processor. This is the case -because it guarantees that no processors are running when the preset -values are set. +For example, network presets can only be applied between execution +cycles (by the control application) or by a top-level processor. This +is the case because it guarantees that no processors are running when +the preset values are set. +2. If a processor receives data from a sibling network it is possible +that the processors value() function is called from multiple +concurrent threads. Processors which receive data from sibling +networks (e.g. audio_mixer, poly_xform_ctl) should either not +implement value() functions or be thread aware in their handling of +calls to value(). + +Depending on the nature of the processing in the value() function +this may not be particularly problematic since a given variable may +only be connected to a single source. While the value() function +may be called from multiple overlapping threads the arguments +to each thread will refer to a unique variable. The built-in variable update +process is carefully designed to exploit this invariant and not modify +any process state with the exception of the targetted variable itself. + +The danger in value() function processing is in writing to any process +state, or any other variable than the one being reported as changing. +Likewise it should be recognized that even reading the value of other +variables should be done with caution. Reading other variables is +thread safe in the sense that the internal state of the processor will +be safe to traverse. The actual value of other variables however may be +inconsistent relative to one another, and not the same as when the +processors exec() function eventually runs - since they too may be in +the process of being updated. + +The purpose of the value() function is to provide a single +easy way of picking up changed incoming values without +having to test for changed values in the exec() function. +It shouldn't be used as an alternate exec() function. + +Note that the create() and destroy() calls for all processors +in the entire graph occur in a single thread and therefore do +not need to take multi-thread precautions - at least relative +to other _caw_ based execution. + Records ------- The primary reason to use a 'record' data type is to allow multiple -data values to be transmitted during a single cycle. In effect a -record allows a table of values to be transmitted, one row at a time, -during a single execution cycle. For example let's say that a the -output of a processor is an irregular pulse whose rate might be faster -than the audio frequency. This would require multiple pairs of value -(delta-time,amplitude) to be generated during a given cycle. The -receiving processor would then iterate through the list of pairs and -processes them each. +data values to be transmitted during a single cycle and be received as +a single incoming value. In effect a record allows a structured table +of values to be transmitted during a single execution cycle. For +example let's say that the output of a processor (G) is an irregular pulse +whose rate might be faster than the audio frequency. This would +require multiple pairs of value (delta-time,amplitude) to be generated +during a given cycle. + +Without the use of the record data type this would require that the generating +processor have two output variables 'dtime' and 'amplitude' which +would be updated multiple times during a single execution cycle. The +receiving processor (R) would then need to respond to each of those +changes by implementing a value() function and storing the incoming +values in an internal array. The stored values could then be acted +upon during the receiving processors exec() function. + +If R didn't take this approach, and simply read +the incoming variables at the beginning of it's own execution cycle, +it would only see the value of the two output variables as they were left at the +end of the G execution cycle. The previous values +transmitted during the execution cycle would be lost. + +By explicitely transmitting a record G makes clear that multiple +values may be transmitted during a single execution cycle, while +providing the convenience R of automatically storing those value. +Furthermore the tranmission overhead is minimized by only transmitting +a single aggregate value rather than multiple individual values. Another example, would be MIDI values that contain some additional side information. The MIDI data type already has the feature that it -can generate multiple messages per execution cycle. It's format, -however, is fixed. There is no way to add addtional information, like -score location, to each message. The fields of the record data type, -however, can hold any of the other data types. +can generate multiple messages per execution cycle. It's format +however is fixed. There is no way to add addtional information, like +score location, to each message. The fields of the record data type +however can hold any of the other data types. -The secondary reasone to use the record data type is to simplify +Another reason to use the record data type is to simplify the output and input interfaces to a processor and thereby decrease the number of connections between processors. They also make clear that a set of values is synchronized in time. @@ -1240,9 +1319,9 @@ For example a set of (x,y) coordinates placed in a record make it clear that the two values belong together in a way that two input variables may not. -Records are also very efficient. Given that the field index is +Finally, records are also very efficient. Given that the field index is computed in advance setting and getting the field variable -is very fast. Setting a record field avoids the overhead +is very fast. As mentioned above transmitting a record avoids the overhead of notifying receiving processors of every new value. The receiving processor is only notified that the record as a whole has changed. @@ -1251,6 +1330,7 @@ Records are also implented in such a way that appending a additition fields to an existing record is very fast. The new record effectively inherits the contents of the existing record by reference. No data is copied. +For an example of this see the `vel_table` implementation. Optional Variables ------------------- @@ -1412,7 +1492,80 @@ the processors contained by 'osc_poly'. --- Presets with hetergenous poly networks +``` + example_03: + { + network: { + procs: { + + osc_poly: { + class: poly, + + // For het-poly networks the 'count' value given + // in the top level proc is the default value for + // the poly-count for following networks. + // This value may be overriden in the network + // definition itself - as it is in this example. + args: { count:2, parallel_fl:true }, + + network: { + net_a: { + count: 4, // override higher level 'count' + + procs: { + osc: { class: sine_tone, args:{ hz: 100 }}, + }, + + presets: + { + a: { osc:{ hz:110 } }, + b: { osc:{ hz:120 } }, + } + }, + + net_b: { + count 3, // override higher level 'count' + procs: { + osc: { class: sine_tone, args:{ hz: 200 }}, + }, + + presets: + { + a: { osc:{hz:220} }, + b: { osc:{hz:230} } + } + } + }, + + presets: { + aa: { net_a:a, net_b:a }, + bb: { net_a:b, net_b:b }, + } + + } + + // Iterate over the instances of `osc_poly.osc_.out` to create one `audio_merge` + // input for every output from the polyphonic network. + merge: { class: audio_merge, in:{ in_:osc_poly.osc_.out}, args:{ gain:1, out_gain:0.5 } + presets: { + a:{ gain:0.3 } + b:{ gain:0.2 } + } + }, + aout: { class: audio_out, in:{ in:merge.out } args:{ dev_label:"main"} } + + } + + presets: { + a:{ osc_poly1:aa, merge:a }, + b:{ osc_poly0_2:bb, merge:b }, + c:{ osc_poly:bb, merge:{ out_gain:0.1 } }, + d:{ osc_poly0:bb, merge:{ out_gain:0.05} } + } + } + } +``` --- Dual Presets @@ -1433,7 +1586,7 @@ Presets with user defined processors containing poly networks. --- -Use 'interface' objects to intercept preset values so that the +Use 'interface' objects to intercept preset values so that they can be processed before being passed on to a the object that they represent.