# THIS IS A WIP :)
buffer-gen is a sort of long-term project of mine. Previous to this i haven't ever really tried to program anything from scratch. I've done a little bit of coding in the past but it was mostly very small scripts for larger projects, usually in max.
for this, I'm using Rust, which i am learning allong side my progress on this project. I am also a beinner coder so my code will by no means be good. I'm using the scope of this project as a tool for myself to be motivated to learn something challenging, which is the Rust programming language, as well as just coding concepts in general.
my aim is to create some software that can process and synthesize audio using non-realtime methods by freely passing buffers between modules. Ideally, the interface would present some sort of module node-based workflow, which would then be converted (somehow... havent figured that much out yet) into a network of modules passing buffers between each other, which would then synthesize/process audio according to the indicated signal flow. The important bit is that it's non-realtime.
an example use case would be to like... idk. generate a bunch of kick samples in bulk. You could establish some network of modules that generates a kick sample, and attach that network to a module that renders whatever network is above it in the chain multiple times. if you included a random source in that repeated network, you could generatively synthesize a bunch of kick samples in bulk.
or maybe you wanna normalize a bunch of samples at the same time, or do some wild convolution or feedback shit that isn't feasable with realtime audio.. idk. that's the goal. as i'm writing this i have the structure for the way modules interact with eachother to allow for (within reason) infinite inputs, outputs, and instances of modules, although i haven't gotten much past that. I'm uploading devlogs to an alternate youtube channel, here' sthe url to that channel:
alt youtube channel w/ devlogs
oh also here's a github repository if this ever gets to a point where it's actually usable (or if you just wanna look at my bad code)
update 12/12/24 ~2:00 am
ok so i just got a pseudo preset file working! very veyr very happy about this. my code, of course, is quite ass, but i'm honestly not too worried about that at the moment. I've got it able to load toml files formatted in a particular way as a preset, and parse that, then run the code and generate the audio. which is fucking. yes. it feels so real; liek i'm actually at the point where it's a real thing that is maybe useful.
here's the toml i've been testing everything with. It generates exactly half a second of +0.5 DC offset :3
I think the next things I wanna do would be to implement the sin function (probably just pasting the dsp code from previous iterations that were structured a lto differently), implement audio file imports, and have a way to do everything in bulk.
my thinking is that this "everything in bulk" idea would just take form as a singular module that take whanever chain exists above it and runs it multiple times while incrementing some value passed to each of the param modules it contains each time it runs. I'm not sure how this would work with the contents of the buffer cache since id want to be able to both overwrite shit and add new entries dynamically per iteration. it presents a challenge. I'm pretty sure I have something in place to facilitate the passing of some incremental value between module instances, so that shouldn't be an issue. OH as i'mw riting this i'm also thinking it would be a good idea to have a param_rand module that just generates a random value between two extremes. ill maybe do this tomorrow to get into the zone or whatever and then i'll get to it. actualy maybe this is harder than i think if i want the random value to be static and not change with each sample. hmm. ok well its 2 am and i need to sleep.
gonna merge the branch i created for this particular feature and then sleep. it snowed tonight and i wanna get up somewhat early tomorrow morning and go for a walk before all the snow gets messiedup by peoples footprints.
oh also one more thing i may have already mentioned; he fact that i have an actual file *external* of the code means that trying to make a frontend for this is going to be a little bit more straightforward than I was initially thinking. I was expecting to have to be passing the module cache back and forth between a bunch of shit which would have been a nightmare with the borrow checker. since here the module cache is immutable it makes all of that soo much easier. nayways time to sleep good night meow meow
update 12/14/24 12:35 AM
ok so not as much progress as the last update but i am thinking about it so i'm just gonna write a quick one.
I got a sin function working and also the module that is able to do whatever exists above it in bulk. its called repeat. im pretty happy with it. using this I can generate say, 150 audio files, each containing a sine whos frequency increents by 50 hz for each consecutive audio file. it generates all of this reasonably quickly, so im excited to see how far i can optimize the code. to run as efficient as possible. or maybe not. idk. maybe that shouldnt be my main goal rn. but whatever, it works!
I also implemented a noise module that just outputs white noise, a scale_static module that does the same thing as the scale module but with f32s as inputs instead of 4 extra signals to take from,
and of course the param_rep module, which will return an incrementally larger number for each repetetion (or rep, as i'm calling it) thats invoked by the previously mentioned repeat module.
my next big task is to make a system that enables me to work with time-based stuff way more efficiently. with my current setup, say I wanted to have a ridiculously simple low pass filter. this requires a single-sample delay, which means I need to request two samples from the above module instead of just one, doubling the amt of work that has to be done by whatever modules exist above this hypothetical simple lpf.
ive gotta be able to memoize stuff on a per-module basis, which is difficult for me to figure out how to do since the module, because of the fact that the entire module chain is recursvie, has to go out of scope in order for another sample to be requested from it, which i think(???) means that i cant easily store values in an instance of the module.
my idea earlier today was to instead store the input buffer information in an instance of a class that also defines the input parameters, but this is kinda ughhh icky maybe. idk. but yeah i really wwant to implement this memoization system before i try and do any seriously time-based processing so that i dont have to rewrite a bunch of code once i decide to optimize stuff. idk. still learning still learning still learning.
oh also i came up with a list of modules i want to implement at some point; some way more difficult than others. i'll probably start on all these way later on in the process but for now here's a list of all the shit i at some point wanna do (and feel liek i probably could do most of these with my current skill, maybe):
crossfades
fades between two signal inputs given a value between 0 and 1 inclusive
crossfade_cg_lin
linear constant gain crossfade
crossfade_cg_cos
cosine constant gain crossfade
crossfade_cp
constant power crossfade (using sqrts)
generators/oscillators
all function the same as sin - freq signal input and phase signal input (0.0 - 1.0, but a modulo is applied so an input value of 2.0 would be 0 degrees offset, as would inputs 1, 0, 3, etc.)
triangle
simple triangle wave oscillator
square
simple square oscillator
rectangle
pulse oscillator with added pwm signal input.
pwm value of 0.0 = fully -1.0 DC, pwm value of 1.0 = fully +1.0 DC, value of 0.5 is equivalent to square
saw
simple saw wave generator. should it be ramp up or ramp down? which is better? (since you could just invert phase either way; not very useful to have both as unique modules)
tri_saw
morph between saw and triangle
not a simple crossfade. this would adjust the slopes of two line segments
morph value of 0.0 results in pure triangle, morph value of 1.0 results in pure saw
phasor
up saw that has an output range from 0. to 1.0 instead of -1. to 1.
also maybe useful to have an extra signal input where if it exceeds some value (say 1.0 exclusive), the phasor will reset upon receiving
noise_pink
pink noise generator
noise_white
white noise generator
noise_sah
sah noise generator (2nd signal input specifying the frequency)
noise_sah_smooth
smooth sah noise generator, sample freq input signal
buffer stuff
lookup_phase
input is some signal and the name of a buffer to use as a lookup table
input signal is from 0.0 to 1.0, 0.0 is first sample of lookup table and 1.0 is last sample of lookup table.
unsure if i should have diferent individual modules for diferent interpolation modes but that's not an issue for now
lookup_sample
same as lookup phase except that the input directly indexes the buffer instead of being later converted into a sample to index it with.
wavetable
input is playback frequency and a buffer to use as a wavetable
clone
creates a clone of a buffer with a new name
distortion
clip
simple clipper. 3 input signals; one being the signal to be clipped, and the other two being the lower and upper thresholds for the clipper.
fold
same as clip but it does wavefolding
wrap
same as clip but it wraps the audio around instead of folding it down or limiting it to an extreme. (input valule of -1.1 would map to an output value of 0.9. 1.1 -> -0.9, 2 -> 0, 2.9 -> 0.9, 3.4 -> -0.6)
quantize
two signal inputs; one being the signal to be quantized the other being the amt of quantization. not sure what id want the range of that other value to be tbh. not sure how the math works so. yea. i'd want it to be more nuanced than bit reduction
utility
normalize
one signal input, one f32 input (dB). normalizes the input to the db thresh specified. will have to have access to an entie buffer. ugh not sure how i'd do that with reasonable efficiency
reverse
assuming we know the length of the buffer, it just requests the opposing sample from the above module. so if you ask the reverse module for the first sample, it would ask the module above it for the last sample
might be easiest to just have a static input that says the intended length of the buffer
since_trig
reports the number of samples elapsed since the last time the input signal exceeded 1.0 inclusive
safe
returns 0 if it receives a nan
math
arithmatic
multiply
add
subtract
modulo
divide
divide_safe
exponent
log
abs
reciprocal
reciprocal_safe
trig
sin
cos
tan
tan_safe
series
tree
fib
factorial
comparison
greater_equal
less_equal
greater
less
equal
binary
and
anything below (exclusive) 1 counts as 0, anything above 1 (inclusive) counts as 1
or
not
xor
nand
constants
e
pi
tau
samplerate
permeability_of_free_space
electron_charge
avogadros_number
u32_limit
unix_timestamp
technically not constant; updates in realtime
imports
audio
-
macro
toml file containing a module macro
audio_folder
works in parallel with folder_rep, which should be placed below the render module if you want to process a bunch of audio files in bulk
audio_database
similar to audio folder, except it works on all the audio files in an entire folder heiarchy instead of just a single folder
conversions
samps_to_ms
samps_to_hz
converts period (samps) to frequency (hz)
ms_to_samps
ms_to_hz
hz_to_samps
hz_to_ms
hz_to_midi
hz_to_octave
converts hz to the octave number (float). log scale basically, but just with fine-tunes ranges and tolerances.
midi_to_hz
midi_to_octave
db_to_amp
amp_to_db
unipolar_to_bipolar
converts signals whose range is between 0 and 1 to signals whose range is between -1 and 1
bipolar_to_unipolar
control
lin_decay
linear decay envelope. output value starts at 1.0, linearly decays to 0 over teh specified suration, then stays there for the remaining whatever.
2 signal inputs; one being the length of decay in ms, the other being a trigger
trigger_start
generates a single sample impulse at when teh index of the requested sample is 0. otherwise output 1.
trigger_thresh
generate a single sample inpulse whenever the input signal exceeds some threshold signal (inclusive), but doesn't sustain that trigger for however long the input is above the thresh.
sah
upon receiving a trigger signal, the module will sample and hold the other input signal
adsr
simple linear aadsr that takes a trigger input. attack decay sustain and release parameters are signals. time in ms
adsr_static
same as adsr except the parameters (except for the trigger input) are all static.
ramp
gradually interpolates between 0.0 and 1.0 over the specified duration
param_rand
returns the same random for each requested sample, but different random values for each rep it may be contained within
function
parameter with no signal inputs; only vectors containing points to be interpolated between. vectors because the number of points is unlimited. this would only really work with a UI that's able to draw points in an editor.
other
sum_n
1 input, being a vector of names of input modules.
for each requested sample, sum_n will itterate through the vector of input modules, append the received sample from each input module to a vector, sum the vals of each sample in said vector, divide that by the length of the vector, and output a new sample value
sum_rep
similar to sum_n, only instead of taking from multiple modules it takes from one and just does it a bunch of times.
split
takes signal from a different input depending on whatever integer is closest to the primary input signal.
monitor
prints the sample value to the console each time a sample passes through it