Table of Contents
Status
Work in progress. no sound yet. Iterating on the basic prompt “Using dr dobbs article about david tudor neural synthesizer project using the intel Etann chip, show implementation using gen~”
Conversation with Gemini
Using dr dobbs article about david tudor neural synthesizer project using the intel Etann chip, show implementation using gen~
To recreate David Tudor’s Neural Network Synthesizer (which originally used the pioneering Intel 80170NX ETANN—Electrically Trainable Analog Neural Network—chip) within Max/MSP, we can leverage the gen~ object.
Because gen~ compiles down to C++ and operates on a per-sample basis, it allows us to build the zero-latency feedback loops and non-linear analog-style saturation that gave Tudor’s original hardware its chaotic, self-oscillating character.
Here is an implementation of a 3×3 fully interconnected neural matrix. You can paste this directly into a codebox inside a gen~ object:
Code snippet
// Inputs: in1, in2, in3 (External Audio or Control signals)
// Outputs: out1, out2, out3 (Neural Oscillations/Feedback)
// 1. Memory registers for single-sample feedback loops
History n1(0);
History n2(0);
History n3(0);
// 2. Programmable Weights (Simulating the ETANN Floating-Gate Synapses)
// Range: -2.0 to 2.0. You can expose these to the main Max patch as flonums.
Param w11(0.5), w12(-0.8), w13(0.3); // Weights feeding into Node 1
Param w21(0.9), w22(0.1), w23(-0.6); // Weights feeding into Node 2
Param w31(-0.4), w32(0.7), w33(0.2); // Weights feeding into Node 3
// Input Gains
Param in_gain(1.0);
// 3. Matrix Multiplication & Summation
// Sum the external inputs with the previous states of the nodes multiplied by their weights
Data1 = (in1 * in_gain) + (n1 * w11) + (n2 * w12) + (n3 * w13);
Data2 = (in2 * in_gain) + (n1 * w21) + (n2 * w22) + (n3 * w23);
Data3 = (in3 * in_gain) + (n1 * w31) + (n2 * w32) + (n3 * w33);
// 4. Non-Linear Activation Function
// Using tanh() simulates the ETANN's analog differential amplifier saturation
Out1 = tanh(Data1);
Out2 = tanh(Data2);
Out3 = tanh(Data3);
// 5. Update History for the very next sample iteration
n1 = Out1;
n2 = Out2;
n3 = Out3;
// 6. Route to the outside Max MSP DSP chain
out1 = Out1;
out2 = Out2;
out3 = Out3;
Core Mechanics of the System
- Analog-Style Saturation: The ETANN chip relied on analog differential amplifiers for its neurons. The
tanh()function is an excellent digital proxy here, providing smooth, musical clipping when the node is driven hard. - The Power of
History: In standard Max/MSP, feedback loops introduce a minimum delay of one signal vector (e.g., 64 samples). By using theHistoryoperator insidegen~, the feedback loop is calculated at exactly 1 sample, which is essential for stable, high-frequency chaotic synthesis. - Achieving Tudor-Style Chaos: To make the patch self-oscillate like Tudor’s original setup, turn the
in_gaindown to 0, increase the parameter weights past 1.0 or into negative values, and let the nodes cross-modulate each other.
Would you like to scale this up to a larger matrix layout (like an 8×8 configuration), or should we look into adding programmable biases (offsets) to each neuron?
adding programmable biases (offsets) to each neuron?
Adding programmable biases (offsets) to each neuron is a fantastic next step. In the original Intel ETANN chip, these biases were controlled by dedicated floating-gate synapses that injected a constant offset current into each neuron’s summing node. This allowed the network to shift the activation function (tanh) left or right, determining the baseline “firing” threshold of each node even when there was zero input or feedback.
In a chaotic feedback system, tweaking the bias is often what pushes a node out of a stable state and into a wild oscillation, acting almost like a “formant” or “timbre” control.
Here is the expanded gen~ implementation with programmable biases added to the 3×3 matrix:
Code snippet
// Inputs: in1, in2, in3 (External Audio or Control signals)
// Outputs: out1, out2, out3 (Neural Oscillations/Feedback)
// 1. Memory registers for single-sample feedback loops
History n1(0);
History n2(0);
History n3(0);
// 2. Programmable Weights (Simulating ETANN Synapses)
// Range: -2.0 to 2.0. Expose these to your main Max patch.
Param w11(0.5), w12(-0.8), w13(0.3); // Weights feeding into Node 1
Param w21(0.9), w22(0.1), w23(-0.6); // Weights feeding into Node 2
Param w31(-0.4), w32(0.7), w33(0.2); // Weights feeding into Node 3
// 3. Programmable Biases / Offsets
// Range: -1.5 to 1.5. These shift the activation thresholds.
Param bias1(0.0);
Param bias2(0.0);
Param bias3(0.0);
// Input Gains
Param in_gain(1.0);
// 4. Matrix Multiplication, Summation, and Bias Offset
// Each node sums external input + weighted feedback + its unique bias
Data1 = (in1 * in_gain) + (n1 * w11) + (n2 * w12) + (n3 * w13) + bias1;
Data2 = (in2 * in_gain) + (n1 * w21) + (n2 * w22) + (n3 * w23) + bias2;
Data3 = (in3 * in_gain) + (n1 * w31) + (n2 * w32) + (n3 * w33) + bias3;
// 5. Non-Linear Activation Function
// The bias changes where on the tanh curve the node rests by default
Out1 = tanh(Data1);
Out2 = tanh(Data2);
Out3 = tanh(Data3);
// 6. Update History for the very next sample iteration
n1 = Out1;
n2 = Out2;
n3 = Out3;
// 7. Route to the outside Max MSP DSP chain
out1 = Out1;
out2 = Out2;
out3 = Out3;
Performance Tips for the Main Max Patch
- The “Sweet Spot” Finder: Try mapping the three
biasparameters to a single 2D or 3D interpolation object (likenodesor a jitter matrix). Slowly moving through bias spaces while the network is self-oscillating (in_gain 0) creates incredibly complex, evolving rhythmic structures and drone textures. - DC Offset Warning: Because biases introduce constant offsets, the outputs can sometimes develop a significant DC offset. It is highly recommended to place a
biquad~configured as a high-pass filter (around 5–10 Hz) or a standardonepole~high-pass immediately after thegen~outputs in your main patch to protect your speakers and downstream DSP.
From here, we could scale this up to an 8×8 configuration using matrix-style indexing, or implement an automated parameter randomizer to let you quickly store and recall distinct “sonic states.” Which direction feels right?
add performance tips
Performance Tips for the Main Max Patch
When running this type of high-density neural feedback loop inside gen~ (especially if you scale past a 3×3 matrix), how you handle the surrounding Max environment is critical to maintaining low CPU overhead and preventing audio artifacts on Apple Silicon.
- Vector Size & Audio Interruptions: Because the feedback occurs per-sample inside
gen~, the patch will run stably regardless of your global I/O Vector Size. However, to minimize overall latency and CPU scheduling overhead on Apple Silicon (M1/M2/M3 chips), a vector size of 64 or 128 samples offers the best balance of low latency and processing efficiency. - The “Sweet Spot” Finder: Try mapping the
biasparameters to a single 2D or 3D interpolation object likenodesor ajit.matrixcontrolled by anodesobject. Slowly moving through bias spaces while the network is self-oscillating (in_gain 0) allows you to smoothly traverse complex, evolving rhythmic structures and drone textures without causing parameter stepping clicks. - DC Offset Warning: Because biases introduce constant offsets, the outputs can sometimes develop a significant DC offset that will saturate your signal chain or pop your speakers. Always place a
biquad~configured as a high-pass filter (around 5–10 Hz) or a standardonepole~high-pass immediately after thegen~outputs in your main patch to keep the signal centered. - Parameter Smoothing: If you map external MIDI controllers or automated modulators to the
Paramweights or biases, add aHistoryor a basic one-pole filter directly insidegen~for those parameters, or use theslideoperator to smoothly interpolate parameter changes. Rapidly changing raw weights at audio rate can otherwise cause harsh digital clicking instead of the smooth analog-style tearing characteristic of the ETANN chip.
Expanding the Matrix Dynamically
If you find yourself running out of complex behaviors with 3 nodes, the next evolutionary step is scaling this system up to an 8×8 or 16×16 configuration. Rather than typing out 64 individual parameters (w11 through w88), we can refactor the gen~ code to use a Data or Buffer object for matrix indexing. This allows you to send an entire matrix of weights from the main Max patch directly into gen~ via Jitter or JavaScript.
Would you like to look at an indexed array version using a Buffer to make scaling up easier?
indexed array version using a Buffer to make scaling up easier
To scale this system efficiently up to an 8×8, 16×16, or even larger neural matrix without creating an unmanageable wall of individual parameters, we can refactor the gen~ code to use an internal Data array (or an external Buffer) combined with a lookup loop.
Using loops inside gen~ is highly efficient because it compiles directly to a fast C++ for loop. Below is the refactored, fully scalable matrix implementation.
The Scalable Matrix gen~ Code
This code dynamically calculates the interconnected feedback using a loop. By changing the N constant at the top, you can resize the entire neural network instantly.
Code snippet
// Define the size of the neural network matrix (e.g., 8 for an 8x8 matrix)
define N 8;
// 1. Memory for single-sample feedback loops (one for each neuron)
Data nodes(N);
// 2. Main Matrix Weight Loop
// This loop runs once per sample for every single neuron
for (i = 0; i < N; i++) {
// Start with the external audio/control input for this specific node
// in1 corresponds to node 0, in2 to node 1, etc.
let external_input = in(i + 1);
// Get the dedicated programmable bias for this neuron from the outside patch
// (Exposed as bias1, bias2... up to bias8 via Param)
let bias = param(concat("bias", i + 1));
// Accumulate the weighted sum of all nodes feeding into this node
let weighted_sum = 0;
for (j = 0; j < N; j++) {
// Fetch the weight connecting node 'j' to node 'i'
// Dynamically references parameters named w11, w12, etc.
let weight = param(concat(concat("w", i + 1), j + 1));
// Read the previous state of node 'j'
let previous_state = nodes.read(j);
// Accumulate
weighted_sum += previous_state * weight;
}
// Total excitation including external signal and internal bias
let total_excitation = external_input + weighted_sum + bias;
// Non-linear activation function (ETANN analog saturation proxy)
let output_state = tanh(total_excitation);
// Write the new state back to memory for the NEXT sample's feedback loop
nodes.write(output_state, i);
// Route the current state directly to the respective audio output channel
out(i + 1, output_state);
}
Performance Tips for Managing Large Matrices
When you scale up to an 8×8 matrix (64 weights) or a 16×16 matrix (256 weights), adjusting parameters manually via message boxes becomes impractical. Here are a few ways to keep the performance fluid and responsive:
- Buffer-Based Matrix Injections: Instead of creating 64 individual
Paramobjects, you can replace the parameter lookups inside the loop with a single 2DBufferlookup usingBuffer myWeights;. You can then usemyWeights.read(j, i);to read a weight matrix directly out of a standard Maxbuffer~object. - Jitter for Visualization and Control: If you transition to the
Bufferapproach, you can usejit.matrixto generate, randomize, or morph weight configurations in real time, and then usejit.poke~orjit.buffer~to instantly copy that data into the audio rate buffer. This allows you to visually “see” the neural connection strengths as a greyscale grid or video texture. - Slew Filtering Weights: When modulating 64+ connections at once via automation, the sudden jumps can cause micro-transients. If using standard parameters, pass them through a
slideorhistorysmoothing operator insidegen~before multiplying them against the node states. This preserves the organic, “tearing” quality of the analog ETANN chip rather than introducing digital zipper noise.
show the parent max patch
To bridge the gap between the high-speed code running inside the gen~ object and the rest of your Max environment, you need a robust parent patch.
Because we are dealing with high-density feedback loops that can easily blow up into intense digital clipping, the parent patch must prioritize safety (DC blocking and limiting), smooth parameter interpolation (to avoid zipper noise), and flexible modulation control.
Here is how to structure the parent Max patch to host your scalable, indexed gen~ neural matrix:
1. Handling the Weight Matrix (The Controller)
Instead of using hundreds of individual live.dial or fpic objects, the cleanest way to control an 8×8 or 16×16 neural matrix is to use Jitter to write directly into an audio buffer that gen~ reads.
jit.cellblockorjit.pwindow: Use an 8×8jit.matrix 1 float32 8 8to visually represent the weight connections. Light pixels represent positive weights, dark pixels represent negative weights.jit.buffer~: Use this object to instantly bind your Jitter matrix to a standard Max audio buffer namedweight_matrix.- Modulation: You can send subtle video noise (
jit.noise) or low-frequency matrices into the main matrix to shift the entire neural landscape dynamically.
Plaintext
[jit.noise 1 float32 8 8] --> [jit.slide] (for smooth morphing)
│
▼
[jit.buffer~ weight_matrix]
2. The Audio I/O and Safety Core (The Parent Patch Structure)
Here is the literal layout of the patch surrounding the gen~ object. Copying this structural workflow ensures you won’t damage your audio hardware or ears when the network hits a chaotic feedback loop.
Plaintext
[buffer~ weight_matrix 1 100] <-- Stores the 64/256 weights
[buffer~ bias_array 1 10] <-- Stores the individual node biases
┌────────────────────────┐
[MC/Playlist~] or
│ [multislider] │
[adc~ 1 2 3 4 5 6 7 8]
│ (Controls node biases)│
│ └───────────┬────────────┘
│ ▼
│ [peek~ bias_array]
▼
[mc.pack~ 8]
│ ▼ ┌──────────────┐ │ mc.gen~ network_matrix <– Host your compiled loop code here └──────┬───────┘ │ (8 channels of raw neural chaos) ▼ ┌──────────────┐ │ mc.biquad~ │ <– CRITICAL: Set to High-Pass Filter @ 10Hz └──────┬───────┘ (Removes the massive DC offset built up by biases) │ ▼ ┌──────────────┐ │ mc.limiter~ │ <– CRITICAL: Lookahead brickwall limiter set to -1dB └──────┬───────┘ (Prevents unpredictable feedback explosion) │ ▼
[mc.gain~]
│ ▼
[mc.dac~ 1 2 3 4 5 6 7 8]
3. Inside the mc.gen~ Object (Reference Update)
To make the parent patch above work flawlessly with the indexed array version, ensure your gen~ code replaces the Param lookup loops with direct buffer lookups like this:
Code snippet
Buffer weight_matrix;
Buffer bias_array;
define N 8;
Data nodes(N);
for (i = 0; i < N; i++) {
let external_input = in(i + 1);
// Read bias directly from the parent patch's bias buffer
let bias = bias_array.read(i, 0);
let weighted_sum = 0;
for (j = 0; j < N; j++) {
// Read the 2D weight matrix directly from the Jitter-fed buffer
let weight = weight_matrix.read(j, i);
let previous_state = nodes.read(j);
weighted_sum += previous_state * weight;
}
let output_state = tanh(external_input + weighted_sum + bias);
nodes.write(output_state, i);
out(i + 1, output_state);
}
Pro-Performance Setup
When opening this parent patch on Apple Silicon, use the mc. (Multichannel) variations shown above. mc.gen~ automatically handles wrapped multi-channel routing natively, meaning an 8-neuron network handles all 8 inputs and outputs cleanly in a single patch cord, drastically reducing scheduling overhead on modern macOS setups.
Show the MAX Patch
To build the parent Max patch quickly, you can copy the formatted text block below and paste it directly into an empty Max patcher window (using File -> New Patcher, then pressing Cmd+V or Ctrl+V).
This script automatically generates the complete multichannel UI, the required storage buffers for the weights and biases, and the safety stages (DC block and limiter) wrapped into an mc. architecture.
Max Patcher JSON Code
JSON
{
"patcher" : {
"fileversion" : 1,
"appversion" : {
"major" : 8,
"minor" : 6,
"revision" : 0,
"architecture" : "x64",
"modernui" : 1
}
,
"classnamespace" : "box",
"rect" : [ 100.0, 100.0, 850.0, 700.0 ],
"bglocked" : 0,
"openinpresentation" : 0,
"default_fontsize" : 12.0,
"default_fontface" : 0,
"default_fontname" : "Arial",
"gridonopen" : 1,
"gridsize" : [ 15.0, 15.0 ],
"gridsnaponopen" : 1,
"objectsnaponopen" : 1,
"statusbarvisible" : 2,
"toolbarvisible" : 1,
"lefttoolbarpinned" : 0,
"toptoolbarpinned" : 0,
"righttoolbarpinned" : 0,
"bottomtoolbarpinned" : 0,
"toolbars_unpinned_last_save" : 0,
"tallnewobj" : 0,
"boxanimatetime" : 200,
"enablehscroll" : 1,
"enablevscroll" : 1,
"devicewidth" : 0.0,
"description" : "",
"digest" : "",
"tags" : "",
"style" : "",
"subpatcher_template" : "",
"assistshowasterisk" : 1,
"boxes" : [ {
"box" : {
"background" : 1,
"fontface" : 1,
"fontsize" : 16.0,
"id" : "obj-24",
"maxclass" : "comment",
"numinlets" : 1,
"numoutlets" : 0,
"patching_rect" : [ 30.0, 20.0, 480.0, 24.0 ],
"text" : "David Tudor Neural Network Synthesizer (ETANN Proxy) Host"
}
}
, {
"box" : {
"id" : "obj-1",
"maxclass" : "newobj",
"numinlets" : 1,
"numoutlets" : 2,
"outlettype" : [ "float", "bang" ],
"patching_rect" : [ 540.0, 70.0, 175.0, 22.0 ],
"text" : "buffer~ weight_matrix 1 100"
}
}
, {
"box" : {
"id" : "obj-2",
"maxclass" : "newobj",
"numinlets" : 1,
"numoutlets" : 2,
"outlettype" : [ "float", "bang" ],
"patching_rect" : [ 540.0, 105.0, 150.0, 22.0 ],
"text" : "buffer~ bias_array 1 10"
}
}
, {
"box" : {
"id" : "obj-3",
"maxclass" : "comment",
"numinlets" : 1,
"numoutlets" : 0,
"patching_rect" : [ 30.0, 70.0, 200.0, 20.0 ],
"text" : "1. Weight Generation (Matrix Control)"
}
}
, {
"box" : {
"id" : "obj-4",
"maxclass" : "newobj",
"numinlets" : 1,
"numoutlets" : 1,
"outlettype" : [ "bang" ],
"patching_rect" : [ 30.0, 100.0, 58.0, 22.0 ],
"text" : "loadbang"
}
}
, {
"box" : {
"id" : "obj-5",
"maxclass" : "message",
"numinlets" : 2,
"numoutlets" : 1,
"outlettype" : [ "" ],
"patching_rect" : [ 30.0, 130.0, 78.0, 22.0 ],
"text" : "bang"
}
}
, {
"box" : {
"id" : "obj-6",
"maxclass" : "newobj",
"numinlets" : 1,
"numoutlets" : 2,
"outlettype" : [ "jit_matrix", "" ],
"patching_rect" : [ 30.0, 165.0, 139.0, 22.0 ],
"text" : "jit.noise 1 float32 8 8"
}
}
, {
"box" : {
"id" : "obj-7",
"maxclass" : "newobj",
"numinlets" : 1,
"numoutlets" : 2,
"outlettype" : [ "jit_matrix", "" ],
"patching_rect" : [ 30.0, 200.0, 120.0, 22.0 ],
"text" : "jit.slide @slide_up 20"
}
}
, {
"box" : {
"id" : "obj-8",
"maxclass" : "newobj",
"numinlets" : 1,
"numoutlets" : 2,
"outlettype" : [ "jit_matrix", "" ],
"patching_rect" : [ 30.0, 235.0, 155.0, 22.0 ],
"text" : "jit.buffer~ weight_matrix"
}
}
, {
"box" : {
"id" : "obj-9",
"maxclass" : "comment",
"numinlets" : 1,
"numoutlets" : 0,
"patching_rect" : [ 260.0, 70.0, 180.0, 20.0 ],
"text" : "2. Bias Thresholds (Offsets)"
}
}
, {
"box" : {
"id" : "obj-10",
"maxclass" : "multislider",
"numinlets" : 1,
"numoutlets" : 2,
"outlettype" : [ "", "" ],
"parameter_enable" : 0,
"patching_rect" : [ 260.0, 100.0, 180.0, 80.0 ],
"size" : 8,
"setminmax" : [ -1.5, 1.5 ],
"candycane" : 3
}
}
, {
"box" : {
"id" : "obj-11",
"maxclass" : "newobj",
"numinlets" : 1,
"numoutlets" : 1,
"outlettype" : [ "" ],
"patching_rect" : [ 260.0, 195.0, 65.0, 22.0 ],
"text" : "prepend list"
}
}
, {
"box" : {
"id" : "obj-12",
"maxclass" : "newobj",
"numinlets" : 3,
"numoutlets" : 1,
"outlettype" : [ "float" ],
"patching_rect" : [ 260.0, 235.0, 115.0, 22.0 ],
"text" : "peek~ bias_array"
}
}
, {
"box" : {
"id" : "obj-13",
"maxclass" : "comment",
"numinlets" : 1,
"numoutlets" : 0,
"patching_rect" : [ 30.0, 300.0, 250.0, 20.0 ],
"text" : "3. Audio Routing & Matrix Processing"
}
}
, {
"box" : {
"id" : "obj-14",
"maxclass" : "newobj",
"numinlets" : 1,
"numoutlets" : 1,
"outlettype" : [ "multichansignal" ],
"patching_rect" : [ 30.0, 335.0, 130.0, 22.0 ],
"text" : "mc.noise~ @chans 8"
}
}
, {
"box" : {
"id" : "obj-15",
"maxclass" : "newobj",
"numinlets" : 1,
"numoutlets" : 1,
"outlettype" : [ "multichansignal" ],
"patching_rect" : [ 30.0, 380.0, 180.0, 22.0 ],
"text" : "mc.gen~ network_matrix"
}
}
, {
"box" : {
"background" : 0,
"bgcolor" : [ 0.85, 0.25, 0.25, 0.15 ],
"id" : "obj-16",
"maxclass" : "newobj",
"numinlets" : 2,
"numoutlets" : 1,
"outlettype" : [ "multichansignal" ],
"patching_rect" : [ 30.0, 425.0, 230.0, 22.0 ],
"text" : "mc.biquad~ @chans 8 @mode highpass 10"
}
}
, {
"box" : {
"background" : 0,
"bgcolor" : [ 0.85, 0.25, 0.25, 0.15 ],
"id" : "obj-17",
"maxclass" : "newobj",
"numinlets" : 1,
"numoutlets" : 1,
"outlettype" : [ "multichansignal" ],
"patching_rect" : [ 30.0, 470.0, 160.0, 22.0 ],
"text" : "mc.limiter~ @threshold -1"
}
}
, {
"box" : {
"id" : "obj-18",
"maxclass" : "mc.gain~",
"numinlets" : 1,
"numoutlets" : 2,
"outlettype" : [ "multichansignal", "" ],
"parameter_enable" : 0,
"patching_rect" : [ 30.0, 515.0, 140.0, 35.0 ]
}
}
, {
"box" : {
"id" : "obj-19",
"maxclass" : "newobj",
"numinlets" : 1,
"numoutlets" : 0,
"patching_rect" : [ 30.0, 575.0, 130.0, 22.0 ],
"text" : "mc.dac~ 1 2 3 4 5 6 7 8"
}
}
, {
"box" : {
"id" : "obj-20",
"maxclass" : "comment",
"numinlets" : 1,
"numoutlets" : 0,
"patching_rect" : [ 270.0, 425.0, 240.0, 20.0 ],
"text" : "<-- CRITICAL: Blocks structural DC Offsets"
}
}
, {
"box" : {
"id" : "obj-21",
"maxclass" : "comment",
"numinlets" : 1,
"numoutlets" : 0,
"patching_rect" : [ 200.0, 470.0, 240.0, 20.0 ],
"text" : "<-- CRITICAL: Prevents feedback blow-ups"
}
}
]
}
}
Step-by-Step Implementation Instructions
- Create the Objects: Paste the JSON block above into your main Max workspace. You will instantly see the array of multi-channel processors linked up.
- Open the
mc.gen~Object: Double-click the newly spawnedmc.gen~ network_matrixobject to see its inner canvas. Delete any default inlets or outlets inside it. - Insert the Scalable Core: Create a
codeboxobject inside thatgen~layer, stretch it out, and paste the buffer-driven indexing script into it:
Code snippet
Buffer weight_matrix;
Buffer bias_array;
define N 8;
Data nodes(N);
for (i = 0; i < N; i++) {
let external_input = in(i + 1);
// Grabs bias assignments sequentially from the parent patch
let bias = bias_array.read(i, 0);
let weighted_sum = 0;
for (j = 0; j < N; j++) {
// Look up the row/column weight cross-connections
let weight = weight_matrix.read(j, i);
let previous_state = nodes.read(j);
weighted_sum += previous_state * weight;
}
let output_state = tanh(external_input + weighted_sum + bias);
nodes.write(output_state, i);
out(i + 1, output_state);
}
- Connect and Monitor: Close the codebox view. The outer
mc.gen~object will automatically expose 8 hidden multi-channel routes matching the loop definitions. Turn on your audio driver, trigger thejit.noisebang to flood the matrix with synaptic paths, and slowly pull up the multislider handles to inject bias currents into individual channels.
Screen Snapshot

