diff --git a/components/bl00mbox/CMakeLists.txt b/components/bl00mbox/CMakeLists.txt index 826632d60540ab1505e085346dd3630c43353f67..752296ebd7f2eb8bd092ecf20f1133e895a6618b 100644 --- a/components/bl00mbox/CMakeLists.txt +++ b/components/bl00mbox/CMakeLists.txt @@ -7,6 +7,7 @@ idf_component_register( bl00mbox_user.c bl00mbox_plugin_registry.c bl00mbox_radspa_requirements.c + radspa/standard_plugin_lib/osc.c radspa/standard_plugin_lib/osc_fm.c radspa/standard_plugin_lib/env_adsr.c radspa/standard_plugin_lib/ampliverter.c @@ -19,6 +20,7 @@ idf_component_register( radspa/standard_plugin_lib/noise_burst.c radspa/standard_plugin_lib/distortion.c radspa/standard_plugin_lib/lowpass.c + radspa/standard_plugin_lib/filter.c radspa/standard_plugin_lib/mixer.c radspa/standard_plugin_lib/range_shifter.c radspa/standard_plugin_lib/poly_squeeze.c diff --git a/components/bl00mbox/README.md b/components/bl00mbox/README.md new file mode 100644 index 0000000000000000000000000000000000000000..fbe3b15a523535130ce42817c0c72e6eec5e24ea --- /dev/null +++ b/components/bl00mbox/README.md @@ -0,0 +1,319 @@ +*current stage: pre-alpha* + +bl00mbox is a modular synthesizer engine designed for 32bit microcontrollers. at this point in time it is running exclusively on the ccc2023 badge "flow3r", but we intend to branch out to other devices soon. + +# table of contents + + +1. [quickstart](#quickstart) +2. [plugins](#plugins) + 1. [tone generators](#tone_generators) + 1. [noise](#noise) + 2. [noise\_burst](#noise_burst) + 3. [osc](#osc) + 4. [sampler](#sampler) + 2. [signal processors](#signal_processors) + 1. [env\_adsr](#env_adsr) + 2. [mixer](#mixer) + 3. [filter](#filter) + 4. [distortion](#distortion) + 5. [delay\_static](#delay_static) + 6. [flanger](#flanger) + 3. [control signal utilities](#ctrl_utils) + 1. [range\_shifter](#range_shifter) + 2. [multipitch](#multipitch) + 4. [trigger handlers](#trigger_handlers) + 1. [sequencer](#sequencer) + 2. [poly\_squeeze](#poly_squeeze) + 5. [deprecated plugins](#deprecated_plugins) + 1. [osc\_fm](#osc_fm) + 2. [ampliverter](#ampliverter) + 3. [lowpass](#lowpass) + 4. [slew\_rate\_limiter](#slew_rate_limiter) + 5. [delay](#delay) +3. [patches](#patches) + +# quickstart <a name="quickstart"></a> + +TODO (for an outdated version see https://docs.flow3r.garden/badge/bl00mbox.html, most basic concepts still apply) + +# plugins <a name="plugins"></a> + +## tone generators <a name="tone_generators"></a> + +### noise <a name="noise"></a> + +provides flat-ish pseudorandom data from a xoroshiro generator. + +###### signals + +- `output [output]`: full range random data signal. +- `speed [input/switched]` *(default: AUDIO)*: **LFO** (-32767): `output` provides constant buffers; **AUDIO** (32767): `output` provides random data for each individual sample. + +--- + +### noise\_burst <a name="noise_burst"></a> + + +similar to **noise**, but stops after a specified amount of time. low-cpu alternative to hooking up a full **env_adsr**. *might add a lightweight filter here at some point depending on how the yet-unfinished 1st order mode of* **filter** *performs, there's a good amount of common use cases.* + +###### signals + +- `output [output]`: full range random data signal. +- `trigger [input/trigger]`: upon receiving a start signal: plays flat-ish noise at max sample rate until a stop signal is received or the time given by `length` has passed. +- `length [input]` *(unit: ms, default: 100ms)*: specifies length of noise burst. the value is only read when `trigger` receives a start signal, but sends render request to source regardless. values above 0 result in resetting `output` to 0 when the burst is over, else the last random `output` value is being held until `trigger` receives the next start signal. At a value of 0 a single random datapoint is produced. + +--- + +### osc <a name="osc"></a> + + +basic oscillator building block with modulation options. + +###### signals + +- `output [output]`: continuously outputs a full range wave. *note: thru-zero fm and hard sync may result in outputs that do not cover the full value range in some cases* +- `pitch [input/pitch]` *(default: 440Hz)*: sets the basic pitch of the oscillator. +- `waveform [input/semi-switched]` *(default: TRI)*: linearily blends between different waveforms. available waveforms: SINE (-32767), TRI (-10922), SQUARE (10922), SAW (32767). +- `morph [input]` *(default: 0)*: stretches/compresses the waveform in each "half" so that the midpoint is shifted relative to the signal value.For the square wave is equivalent to PWM, for example a value of -32767/2 is equivalent to 25% pulse width. at this point in time the signal is clamped to a value that rises with `pitch` to avoid aliasing issues. this is not the ideal solution (and can be bypassed with `fm`), we will probably reconsider this at some point. +- `fm [input]` *(default: 0)*: allows thru-zero linear frequency modulation. the frequency multiplier is ((*signal value*)/8192) + 1 so that the full range represents a multiplication range of ]-3..5[. +- `sync_input [input/trigger]`: used for hard syncing: upon receiving a start signal the oscillator phase is reset to whichever value is provided by `sync_input_phase`. at this point in time there is no anti-aliasing, but we intend to change this in the next major upgrade. stop signals are ignored. +- `sync_input_phase [input]` *(default: 0)*: phase used by `sync_input`. ignored if no triggers are received, but always sends a render request to its source. A phase of 0 is equivalent to the "middle point" of a sawtooth. *note: the other waveforms are generally aligned so that the fundamental is in phase.* +- `sync_output [output/trigger]`: sends a trigger signal each time the oscillator passes the equivalent of the sawtooth midpoint. can be used to hard sync other oscillators or periodically trigger other events. at this point in time always outputs a max volume trigger, but we intend to encode the subsample phase for antialiasing purposes in the last few bits at some point in the future so there will be hopefully inaudible volume variations. keep this in mind when using this signal for more numerical purposes. +- `speed [input/switched]` *(default: AUTO)*: **LFO** (-32767): the oscillator generates a constant sample for each buffer. useful for CPU-efficient modulators or bass sounds, ideally with subsequent low pass filtering. **AUTO** (0): switches between LFO and AUDIO based on `pitch`. the switching point is around 20Hz-ish. **AUDIO** (32767): generates samples at the full audio sample rate. + +###### members + +- `.wave`: read-only 64-tuple of total waveshape (`morph` + `waveform` + antialiasing), kinda. currently rigged as a lightweight debug output: during each buffer, a single sample is written to this output, so that the tuple not only takes some time to catch up with the actual waveshape, but also in some conditions (harmonic relationship between buffer rate and osc period, hardsync, thru-zero fm) some parts of the tuple **may never be updated** and contain garbage data. when modulating the waveform very fast you'll see a lot of noise. we could replicate the waveform generator in python, but that's not ideal and we're not gonna go for it. at some point radspa will provide some sort of variadic function interface to micropython which will solve this very elegantly, so we rather focus on getting there instead even though it'll take some time. + +--- + +### sampler <a name="sampler"></a> + + +*note: unlike the old sampler patch this one does not have flow3r-specifc paths built in, you need to pass the full path for every file operation. also we renamed the signals to be more consistent* + +a simple fixed buffer size sampler that can load and save int16 *.wav* files. can be initialized either with a sample buffer length in milliseconds at 48kHz sample rate or a filename, in which case the sample buffer length is automatically is set to just so fit the file: + +```python +sampler_empty_3seconds = blm.new(bl00mbox.plugins.sampler, 3000) +sampler_preloaded = blm.new(bl00mbox.plugins.sampler, "/flash/sys/samples/kick.wav") +``` + +the sample buffer size does **not** change size dynamically. when loading a file into an already initialized sample buffer it will be cropped if it doesn't fit. when recording time exceeds the sample buffer length it keeps the most recent data only. + +###### signals + +- `playback_output [output]`: outputs the sample buffer contents if playback is active, else 0. provides constant buffers if idle or the sample rate is below 1kHz, else buffers are never constant. +- `playback_trigger [input/trigger]`: replay trigger, handles start, stop and restart events as expected and honors volume of trigger event. +- `playback_speed [input/pitch]` *(default: 0 semitones): replay speed of playback. `.tone = 0` is original pitch. does not affect recording. requests source render only if playback is active. +- `record_input [input]` *(default: 0)*: requests source render only when recording. +- `record_trigger [input/trigger]`: handles start, stop and restart events as expected but ignores volume of trigger event. there's one caveat to be aware of: as input processing is part of rendering, the sampler cannot record if no other plugin or the channel mixer requests rendering. in practice, this means you should connect `output` to the channel mixer in some "uninterruptable" way (like, no **env_adsr** or the likes that can choose to not render their sources when idle). the sampler idles with fairly low load, so this is typically fine. this will be nicer somewhere down the line, please bear with it for a little while. + +###### members + +- `.load(filename: str)`: attempts to load a mono or stereo int16 *.wav* file from the absolute path and sets the appropriate `.sample_rate`. it supports up/downsampling, but audio quality is best if the file has the native sample rate. if the buffer can't contain it completely the end is cropped. raises a `Bl00mboxError` if the file format can be handled by the wave library but can't be processed by the plugin. +- `.save(filename: str)`: attempts to save the contents of its buffer as delimited by the start/end flags to an int16 *.wav* file at the absolute path. +- `.sample_rate: int` *(unit: Hz, default: 48000)*: read/write sample rate that the plugin uses to interpret its buffer contents during playback and recording. All signals switch into constant buffer mode if the sample rate is below 100Hz so that the sampler can be used to efficiently store and replay modulation data. Gets clamped below 1. +- `.sample_length: int`: length of last recorded/loaded sample buffer contents, read-only. +- `.buffer_length: int`: length of sample buffer, read-only. +- `.playback_progress: None/float`: read-only. `None` if sampler is not playing, else position of read head relative to `sample_length`, range [0..1[ +- `.playback_loop: bool` *(default: False)*: read/write. if false playback automatically stops after reaching the end of the sample, else it loops the sample forever. +- `.record_progress: None/float`: read-only. `None` if sampler is not recording, else position of write head relative to `sample_length`, range [0..1[ +- `.record_overflow: bool` *(default: True)*: read/write. if false recording automatically stops when the sample buffer is full, else the oldest data is overwritten. + +## signal processors <a name="signal_processors"></a> + + +### env\_adsr <a name="env_adsr"></a> + +generates a linear ADSR envelope and optionally applies it directly to a signal. + +###### signals + +- `output [output]`: provides a version of `input` with `env_output` applied. lerps between `env_output` cornerpoints to minimize zipper noise. constant buffer if `input` is a constant buffer or if the envelope is idling. +- `input [input]`: signal to be sent to `output` with `env_output` applied. source render is only requested if envelope is not idling and `gain` is not 0. +- `env_output [output/lazy/gain]`: provides a constant buffer with the current gain value of the envelope scaled by last event volume received by `trigger` as well as `gain`. +- `trigger [input/lazy/trigger]`: processes start, stop and restart events as expected and honors event volume. +- `attack [input/lazy]` *(unit: ms, default: 100)*: linear attack time of the envelope. sign is dropped. +- `decay [input/lazy]` *(unit: ms, default: 250)*: linear decay time of the envelope. sign is dropped. +- `sustain [input/lazy]` *(unit: x1/32768, default: 16000)*: sustain target of the envelope. sign is dropped. nonzero sustain means a note keeps ringing forever until receiving a stop event, zero sustain means it mutes after the attack and decay phases. +- `release [input/lazy]` *(unit: ms, default: 50)*: linear release time of the envelope. sign is dropped. +- `gain [input/lazy]` *(default: 0dB)*: overall gain of the envelope. + +--- + +### mixer <a name="mixer"></a> + +mixes several input signals together. initialize with `num_channels` as addtional argument (defaults to 4 if not provided, allowed range [1..127]): + +```python +mixer = blm.new(bl00mbox.plugins.mixer, 2) +``` + +###### signals + +- `output [output]`: mix of all inputs with corresponding gain applied. +- `gain [input/gain]` *(default: x1/num\_channels)*: global gain of mixer. +- `block_dc [input/switched]` *(default: OFF)*: **ON** (32767): block dc below audio frequencies (10Hz-ish, we forgot); **OFF** (-32767): don't apply any filtering. +- `input[num_channels] [input]` *(default: 0)*: signals to be mixed together. +- `input_gain[num_channels] [input/gain]` *(default: 0dB)*: gains of individual input signals. + +--- + +### filter <a name="filter"></a> + + +a collection of second-order filters. + +*note: if you're unfamiliar with filters, here's a visualization aid: https://www.earlevel.com/main/2021/09/02/biquad-calculator-v3/* + +###### signals + +- `output [output]`: outputs a mix of the filtered and unfiltered `input` signal. +- `input [input]` *(default: 0)*: input for signal to be filtered. +- `cutoff [input/pitch]` *(default: 440Hz)*: sets cutoff frequency of the filter type. see `mode` for details. +- `reso [input]` *(unit: 4096\*Q, default: 1Q)*: resonance of the filter. low values (<0.7Q) result in a soft attenuation knee, medium values (<3Q) result in a boost around cutoff and a sharper transition into the attenuation zone, high values may cause self-oscillation. at negative values the filter switches into allpass mode. *note: absolute values below 684 are clamped at this point in time. we are considering using this region for first order filters in the future, so please avoid actively using this clamp if reasonably possible.* +- `gain [input/gain]` *(default: 0dB)*: gain of both wet and dry output of the filter. loud signals in combination with high Q can lead to clipping which can be solved by reducing this value. +- `mix [input]` *(default: 32767)*: blends between the original "dry" input signal (fully dry at 0) and the filtered "wet" signal (fully wet at 32767). for negative values the wet signal is inverted while the dry signal isn't, allowing for phase cancellation experiments. +- `mode [input/switched]` *(default: LOWPASS)*: selects between different filter types. **LOWPASS** (-32767) barely affects frequencies far below the cutoff and progressively attenuates above the resonant peak, **HIGHPASS** (32767) does the same with higher/lower frequencies flipped. **BANDPASS** (0) only allows signals around the cutoff to pass. A bandblock can be achieved by setting `mix` to -16384. If `reso` is negative the transformation *wet = dry - 2\*wet* is applied to create an equivalent allpass. *note: at this point in time only discrete settings exist, but we intend to add a continous blend to this parameter, please avoid using in-between values for future compatibility* + +--- + +### distortion <a name="distortion"></a> + + +TODO + +--- + +### delay_static <a name="delay_static"></a> + + +simple delay. is currently also available as `delay`, but since we want to switch it to use proper gain-type signals in the next update the `delay` plugin will be overwritten while the `delay_static` will remain stable for a few more versions for a smoother transition, so don't use the `delay` plugin for now. the suffix is chosen as it currently the time setting is very coarse and doesn't allow for smooth modulation. this will be fixed in the upcoming `delay` plugin as well. + +initialize with the maximum desired delay time in ms (range: 1-10000, default: 500): + +###### signals + +- `output [output]`: provides a mix of a delayed "wet" and the original "dry" version of `input`. never a constant buffer at this point in time, but will be in the future. +- `input [input]` *(default: 0)*: input to be delayed. +- `time [input]` *(unit: ms, default: 200)*: delay time. +- `feedback [input]` *(unit: 1/32768, default: 16000)*: how much of the delayed signal is mixed back into the delay generator's input. sadly not a gain-type signal so the unit is weird. +- `level [input]` *(unit: 1/32768, default: 16000)*: output volume applied to the wet signal. weird unit again. +- `dry_vol [input]` *(unit: 1/32768, default: 32767)*: output volume applied to the dry signal. actually *ampliverter* uses the same bad unit. +- `rec_vol [input]` *(unit: 1/32768, default: 32767)*: volume applied to the dry signal before it is mixed with the feedbacked wet signal and fed to the delay generator. we were under a lot of time pressure back then, if this doesn't have unity gain it's because neither did we personally when we wrote it. + +--- + +### flanger <a name="flanger"></a> + +basic flanger with subsample interpolation and karplus-strong resonator support. not ideal for chorus due to a lack of time-linear modulators, but might add one in the future (or make a new one to get rid of the silly `resonance` unit). + +###### signals + +- `output [output]`: provides a mix of a resonant comb filtered "wet" and the original "dry" version of `input`. never a constant buffer at this point in time, but will be in the future. +- `input [input]` *(default: 0)*: input to be filtered. +- `manual [input/lazy/pitch]` *(default: 440Hz)*: sets delay time of flanger to match one period of the desired frequency. +- `resonance [input/lazy]` *(unit: x1/32768, default: 2048)*: base feedback applied to flanger, may be modulated by `manual` via `decay`. stops just before self-oscillation. was tagged as a gain signal but doesn't act as one, our bad. gonna stick with it for now tho. +- `decay [input/lazy]` *(unit: ms/6dB, default: 0)*: if `resonance` is set to 0 this adjusts resonance depending on `manual` to result in a roughly constant pulse decay. negative values do not result in volume growth but instead flip the sign of feedback applied, results may vary. may be used in conjuction with `resonance` to create custom tapers. +- `level [input/lazy/gain]` *(default: 0dB)*: overall gain of the flanger. +- `mix [input/lazy]` *(default: 16384)*: blends between dry and wet. at 0 the signal is fully dry, negative values flip the phase of the wet signal but not of the dry signal. + +## control signal utilities <a name="ctrl_utils"></a> + +### range\_shifter <a name="range_shifter"></a> + + +takes an arbitrary non-trigger `input` and applies a linear transformation so that the `input_range[0,1]` points are mapped to their respective and `output_range[0,1]` counterparts. + +###### signals + +- `input [input]` *(default: 0)*: input to which the linear transformation is applied. +- `input_range[2] [input]` *(default: [0] = -32767, [1] = 32767)*: defines 2 points that are mapped to their `output_range[]` counterparts. if both values are identical `output` is the average of `output_range[]`. +- `output [output]`: linear transformation of input. not clamped to `output_range[]`. is a constant buffer if all other signals are constant buffers or treated as such (see `speed`). +- `output_range[2] [input]` *(default: [0] = -32767, [1] = 32767)*: defines 2 points that are mapped to their `input_range[]` counterparts. if both values are identical `output` is always this value. +- `speed [input/switched]` *(default: FAST)*: may force `output` to provide a constant buffer. **FAST** (32767): treat all other signals as they are. **SLOW_RANGE** (0): treat `input_range[]` and `output_range[]` as constant buffers and `input` as it is. **SLOW** (-32767): treat all other signals as constant buffers. + +--- + +### multipitch <a name="multipitch"></a> + + +generates pitch-shifted and -limited copies of a pitch input. all outputs are constant buffers. initialize with `num_channels` as a positional argument (defaults to 0 if not provided, allowed range [0..127]): + +###### signals + +- `input [input/pitch]` *(default: 440Hz)*: input pitch which is processed and forwarded to `thru` and `output[]`. +- `thru [output/pitch]`: copy of `input`, but reduced to a constant buffer and octave-shifted to fit in the range given by `max_pitch` and `min_pitch`. +- `trigger_in [input/trigger]`: gets forwarded to `trigger thru` and reduced to a constant buffer +- `trigger_thru [output/trigger]`: copy of `trigger in` and reduced to a constant buffer if +- `mod_in [input]` *(default: 0)*: pitch modulation input that first gets reduced to a constant buffer and scaled according to `mod_sens` and then applied to `output` and `thru`. +- `mod_sens [input]` *(unit: +-1oct/fullswing/4096 default: +-1oct/fullswing)*: sensitivity of `mod_in`. +- `max_pitch [input/pitch]` *(default: 7040Hz, 4 octaves above A440)*: maximum pitch for `thru` and `output[]`. doesn't clamp but only shifts by octaves. if smaller than `min_pitch` they get flipped in the entire calculation. +- `min_pitch [input/pitch]` *(default: 27.5Hz, 4 octaves below A440)*: minimum pitch for `thru` and `output[]`. if less than an octave below `max_pitch` the limit does not apply so that pitch limiting always results in octave shifts. +- `output[num_channels] [output/pitch]`: outputs a copy of `input` shifted by the pitch indicated by `shift[]` of the same index and octave-shifted to fit in the range `max_pitch and `min_pitch`. +- `shift[num_channels]` [input/pitch]` *(default: 0 semitones)*: determines the amount of pitch shifting applied to the respective output. + +## trigger handlers <a name="trigger_handlers"></a> + + +### sequencer <a name="sequencer"></a> + + +(no changes from old version pending. will be replaced sometime in the future tho.) + +--- + +### poly\_squeeze <a name="poly_squeeze"></a> + + +Multiplexes a number of trigger and pitch inputs into a lesser number of trigger pitch output pairs. In the typical use case all outputs are connected to identical signal generators. + +Initialize with `num_outputs` (range: [1..16], default: 3) and `num_inputs` (range: [`num_outputs`..32], default 10) as positional arguments: + +###### signals + +- `pitch_in[num_inputs] [input/pitch]` *(default: A440)*: pitch of the respective input. if the input is internally connected to an output the pitch data is constantly streamed, allowing for modulation. +- `trigger_in[num_inputs] [input/trigger]`: the latest triggered inputs are internally connected to a disconnected output, or, if none exists, to the output with the oldest internal connection. if such an input receives a stop trigger and another input is in triggered state but not internally connected it will be internally connected to that output and the output is triggered. *note: if more signals are triggered during a single buffer than are available as outputs the ones with the lowest indices will be dropped. in a future version this will mitigated to only occur when it's during a single sample instead.* +- `pitch_out[num_outputs] [output/pitch]`: pitch of a respective output. if the output is not connected the last connected pitch will be held. Always constant buffer. +- `trigger_out[num_outputs] [output/trigger]`: sends out a start event if the output is internally being connected to a new source or a stop event if it is being disconnected. Always constant buffer. + +## deprecated plugins <a name="deprecated_plugins"></a> + + +all of these will be removed with the flow3r 2.0 firmware release. + +### osc\_fm <a name="osc_fm"></a> + + +deprecation reason: was created before switched signals were a thing, some application examples have used a "shorthand" of using say -1 and 0 to switch between triangle and square, can't be unified with wave blending approach. also the whole fm\_thru thing was kinda weird. + +### ampliverter <a name="ampliverter"></a> + + +deprecation reason: was created before the `gain` signal type existed, its gain signal is not a `gain` signal, can't change it without breaking everything that uses it. replacement: `range_shifter` (more for control signal kinda stuff), `mixer` (more for audio signal kinda stuff, can be used as volume control with a single channel). + +### lowpass <a name="lowpass"></a> + + +deprecation reason: didn't use `pitch` input signal type for cutoff, and also a more universal filter plugin made more sense and the name just doesn't work for that. replacement: `filter`. + +### slew\_rate\_limiter <a name="slew_rate_limiter"></a> + + +deprecation reason: early hack for very low cpu load filtering, used only a few times but didn't bother do delete it, not very well made, aliasing issues and sample rate dependent slew rate signal. no replacement yet, but nobody uses it anyways. will probably make a nonlinear filter that can do the same trick some day, but not sure yet what's a good feature set. **filter** should have you covered to get close enough. + +### delay <a name="delay"></a> + + +still exists as `delay_static` for now but has issues as described there. + +# patches <a name="patches"></a> + + +confession time: when this was very young software but a release was necessary we did not have the infrastructure to attach arbitrary functions to individual plugins, so we misappropriated patches for this. the situation has been fixed, but many existing patches are deprecated now for this reason, specifically `fuzz`, `sampler` and `sequencer`. + +furthermore we made the mistake of not specifying stable/unstable surfaces so that we find ourselves in the sad situation where we would be able to improve patches but find ourselves unable to do so since users may have hooked up signals to the internal structure. + +with this in mind, you could say that from a general point of view **all existing patches are deprecated**. the only exceptions at this point are `tinysynth` and `tinysynth_fm`, but their internal structure will be modified in the next major update - if your application accesses anything in the `.plugins` attribute, please create a local copy of the patch in your application, else it's destined to break. diff --git a/components/bl00mbox/bl00mbox_audio.c b/components/bl00mbox/bl00mbox_audio.c index d24fdb4e38aee072e6bb4871c9a371ef0396d6b6..a2a4ef54caefeebe5575e1836e3632634d984d6d 100644 --- a/components/bl00mbox/bl00mbox_audio.c +++ b/components/bl00mbox/bl00mbox_audio.c @@ -71,7 +71,9 @@ bool bl00mbox_audio_do_pointer_change(){ } void bl00mbox_channel_event(uint8_t chan){ +#ifdef BL00MBOX_AUTO_FOREGROUNDING last_chan_event = chan; +#endif } bl00mbox_channel_t * bl00mbox_get_channel(uint8_t channel_index){ @@ -85,7 +87,7 @@ uint8_t bl00mbox_channel_get_foreground_index(){ void bl00mbox_channel_set_foreground_index(uint8_t channel_index){ if(channel_index >= BL00MBOX_CHANNELS) return; - bl00mbox_channel_foreground = channel_index; + last_chan_event = channel_index; } bool bl00mbox_channel_get_free(uint8_t channel_index){ @@ -164,8 +166,13 @@ int16_t bl00mbox_channel_get_volume(uint8_t chan){ void bl00mbox_audio_bud_render(bl00mbox_bud_t * bud){ if(bud->render_pass_id == render_pass_id) return; +#ifdef BL00MBOX_LOOPS_ENABLE + if(bud->is_being_rendered) return; +#endif + bud->is_being_rendered = true; bud->plugin->render(bud->plugin, full_buffer_len, render_pass_id); bud->render_pass_id = render_pass_id; + bud->is_being_rendered = false; } static bool bl00mbox_audio_channel_render(bl00mbox_channel_t * chan, int16_t * out, bool adding){ diff --git a/components/bl00mbox/bl00mbox_plugin_registry.c b/components/bl00mbox/bl00mbox_plugin_registry.c index 1092b3f5afc36920e25f95eb5074062dcb70ad6a..21a5cb3254421a0f5c89cf993b9fcd0ce7c3fed3 100644 --- a/components/bl00mbox/bl00mbox_plugin_registry.c +++ b/components/bl00mbox/bl00mbox_plugin_registry.c @@ -88,10 +88,12 @@ radspa_descriptor_t * bl00mbox_plugin_registry_get_id_from_index(uint32_t index) */ #include "osc_fm.h" +#include "osc.h" #include "env_adsr.h" #include "ampliverter.h" #include "delay.h" #include "lowpass.h" +#include "filter.h" #include "sequencer.h" #include "sampler.h" #include "flanger.h" @@ -107,21 +109,25 @@ radspa_descriptor_t * bl00mbox_plugin_registry_get_id_from_index(uint32_t index) void bl00mbox_plugin_registry_init(void){ if(bl00mbox_plugin_registry_is_initialized) return; - plugin_add(&osc_fm_desc); - plugin_add(&liverter_desc); - plugin_add(&env_adsr_desc); - plugin_add(&delay_desc); - plugin_add(&lowpass_desc); + plugin_add(&osc_desc); + plugin_add(&filter_desc); plugin_add(&sequencer_desc); plugin_add(&sampler_desc); + plugin_add(&multipitch_desc); + plugin_add(&bl00mbox_line_in_desc); + plugin_add(&distortion_desc); + plugin_add(&mixer_desc); plugin_add(&flanger_desc); plugin_add(&noise_desc); plugin_add(&noise_burst_desc); - plugin_add(&distortion_desc); - plugin_add(&mixer_desc); + plugin_add(&env_adsr_desc); + plugin_add(&delay_desc); + plugin_add(&range_shifter_desc); plugin_add(&poly_squeeze_desc); plugin_add(&slew_rate_limiter_desc); - plugin_add(&multipitch_desc); - plugin_add(&bl00mbox_line_in_desc); + plugin_add(&liverter_desc); + + plugin_add(&osc_fm_desc); + plugin_add(&lowpass_desc); } diff --git a/components/bl00mbox/bl00mbox_user.c b/components/bl00mbox/bl00mbox_user.c index 7c604583bb8685403bc802ea8c1782cec4531d47..b2944c75bd7f38e164c06088280a3aa9f2b70fb0 100644 --- a/components/bl00mbox/bl00mbox_user.c +++ b/components/bl00mbox/bl00mbox_user.c @@ -3,14 +3,7 @@ // get signal struct from a signal index radspa_signal_t * bl00mbox_signal_get_by_index(radspa_t * plugin, uint16_t signal_index){ - radspa_signal_t * ret = NULL; - if(plugin == NULL) return ret; - ret = plugin->signals; - for(uint16_t i = 0; i < signal_index; i++){ - ret = ret->next; - if(ret == NULL) break; - } - return ret; + return &(plugin->signals[signal_index]); } static uint64_t bl00mbox_bud_index = 1; @@ -281,9 +274,11 @@ bl00mbox_bud_t * bl00mbox_channel_new_bud(uint8_t channel, uint32_t id, uint32_t radspa_t * plugin = desc->create_plugin_instance(init_var); if(plugin == NULL){ free(bud); return NULL; } + bud->init_var = init_var; bud->plugin = plugin; bud->channel = channel; - //TODO: look for empty indices + bud->is_being_rendered = false; + //TODO: look for empty indices? maybe? bud->index = bl00mbox_bud_index; bl00mbox_bud_index++; bud->chan_next = NULL; @@ -648,6 +643,14 @@ uint32_t bl00mbox_channel_bud_get_plugin_id(uint8_t channel, uint32_t bud_index) return bud->plugin->descriptor->id; } +uint32_t bl00mbox_channel_bud_get_init_var(uint8_t channel, uint32_t bud_index){ + bl00mbox_channel_t * chan = bl00mbox_get_channel(channel); + if(chan == NULL) return false; + bl00mbox_bud_t * bud = bl00mbox_channel_get_bud_by_index(channel, bud_index); + if(bud == NULL) return false; + return bud->init_var; +} + uint16_t bl00mbox_channel_bud_get_num_signals(uint8_t channel, uint32_t bud_index){ bl00mbox_channel_t * chan = bl00mbox_get_channel(channel); if(chan == NULL) return false; @@ -703,6 +706,7 @@ bool bl00mbox_channel_bud_set_signal_value(uint8_t channel, uint32_t bud_index, if(bud == NULL) return false; radspa_signal_t * sig = bl00mbox_signal_get_by_index(bud->plugin, bud_signal_index); if(sig == NULL) return false; + while(bud->is_being_rendered) {}; if(value == -32678){ sig->value = 0; @@ -715,16 +719,18 @@ bool bl00mbox_channel_bud_set_signal_value(uint8_t channel, uint32_t bud_index, int16_t bl00mbox_channel_bud_get_signal_value(uint8_t channel, uint32_t bud_index, uint32_t bud_signal_index){ bl00mbox_channel_t * chan = bl00mbox_get_channel(channel); - if(chan == NULL) return false; + if(chan == NULL) return -32768; bl00mbox_bud_t * bud = bl00mbox_channel_get_bud_by_index(channel, bud_index); - if(bud == NULL) return false; + if(bud == NULL) return -32768; radspa_signal_t * sig = bl00mbox_signal_get_by_index(bud->plugin, bud_signal_index); - if(sig == NULL) return false; + if(sig == NULL) return -32768; + while(bud->is_being_rendered) {}; - if((sig->hints & RADSPA_SIGNAL_HINT_OUTPUT) && (sig->buffer != NULL)){ + if(sig->buffer != NULL){ return sig->buffer[0]; + } else { + return sig->value; } - return sig->value; } uint32_t bl00mbox_channel_bud_get_signal_hints(uint8_t channel, uint32_t bud_index, uint32_t bud_signal_index){ @@ -745,6 +751,7 @@ bool bl00mbox_channel_bud_set_table_value(uint8_t channel, uint32_t bud_index, u if(bud == NULL) return false; if(bud->plugin->plugin_table == NULL) return false; if(table_index >= bud->plugin->plugin_table_len) return false; + while(bud->is_being_rendered) {}; bud->plugin->plugin_table[table_index] = value; bl00mbox_channel_event(channel); return true; @@ -757,6 +764,7 @@ int16_t bl00mbox_channel_bud_get_table_value(uint8_t channel, uint32_t bud_index if(bud == NULL) return false; if(bud->plugin->plugin_table == NULL) return false; if(table_index >= bud->plugin->plugin_table_len) return false; + while(bud->is_being_rendered) {}; return bud->plugin->plugin_table[table_index]; } diff --git a/components/bl00mbox/include/bl00mbox_audio.h b/components/bl00mbox/include/bl00mbox_audio.h index 8f5112baa22636bafa876c4093f3a0e733495126..01059f8697c9862c98e33869906f130f08dac049 100644 --- a/components/bl00mbox/include/bl00mbox_audio.h +++ b/components/bl00mbox/include/bl00mbox_audio.h @@ -6,11 +6,14 @@ #define BL00MBOX_DEFAULT_CHANNEL_VOLUME 8000 #define BL00MBOX_CHANNELS 32 #define BL00MBOX_BACKGROUND_MUTE_OVERRIDE_ENABLE +#define BL00MBOX_AUTO_FOREGROUNDING +#define BL00MBOX_LOOPS_ENABLE #include <stdio.h> #include <math.h> #include <string.h> #include "radspa.h" +#include "radspa_helpers.h" struct _bl00mbox_bud_t; struct _bl00mbox_connection_source_t; @@ -24,7 +27,9 @@ typedef struct _bl00mbox_bud_t{ char * name; uint64_t index; // unique index number for bud uint32_t render_pass_id; // may be used by host to determine whether recomputation is necessary + uint32_t init_var; // init var that was used for plugin creation uint8_t channel; // index of channel that owns the plugin + volatile bool is_being_rendered; // true if rendering the plugin is in progress, else false. struct _bl00mbox_bud_t * chan_next; //for linked list in bl00mbox_channel_t } bl00mbox_bud_t; diff --git a/components/bl00mbox/include/bl00mbox_user.h b/components/bl00mbox/include/bl00mbox_user.h index 9239408ff87f6b3541e54ac6855caeef180fe867..82d21de655518421ab3d9fd305cd0fed79b80958 100644 --- a/components/bl00mbox/include/bl00mbox_user.h +++ b/components/bl00mbox/include/bl00mbox_user.h @@ -33,6 +33,7 @@ bool bl00mbox_channel_bud_exists(uint8_t channel, uint32_t bud_index); char * bl00mbox_channel_bud_get_name(uint8_t channel, uint32_t bud_index); char * bl00mbox_channel_bud_get_description(uint8_t channel, uint32_t bud_index); uint32_t bl00mbox_channel_bud_get_plugin_id(uint8_t channel, uint32_t bud_index); +uint32_t bl00mbox_channel_bud_get_init_var(uint8_t channel, uint32_t bud_index); uint16_t bl00mbox_channel_bud_get_num_signals(uint8_t channel, uint32_t bud_index); char * bl00mbox_channel_bud_get_signal_name(uint8_t channel, uint32_t bud_index, uint32_t bud_signal_index); diff --git a/components/bl00mbox/micropython/bl00mbox/_helpers.py b/components/bl00mbox/micropython/bl00mbox/_helpers.py index 2359652b26f4d1b888a1a2ec136055f6859e487f..9ae8b1f621596dfa98ec132dbfeb95dfd1a9bbf5 100644 --- a/components/bl00mbox/micropython/bl00mbox/_helpers.py +++ b/components/bl00mbox/micropython/bl00mbox/_helpers.py @@ -11,7 +11,7 @@ def terminal_scope( fun=None, fun_ms=None, ): - """give it a signal and show it on terminal uwu""" + """give it a signal and show it on terminal""" if signal_max <= signal_min: return ms_counter = 0 @@ -24,15 +24,19 @@ def terminal_scope( if fun_ms <= fun_counter: fun() fun_counter = fun_counter % fun_ms - val = int((width * (signal.value - signal_min)) / (signal_max - signal_min)) - if val > width: - val = width - if val < 0: - val = 0 + raw_val = signal.value ret = f"{ms_counter:06d}" - ret += " [" + "X" * val + "." * (width - val) + "]" - percent = int(100 * val / width) - ret += f" {percent:02d}%" + if raw_val == -32768: + ret += " [" + "?" * width + "] INVALID" + else: + val = int((width * (raw_val - signal_min)) / (signal_max - signal_min)) + if val > width: + val = width + if val < 0: + val = 0 + ret += " [" + "X" * val + "." * (width - val) + "]" + percent = int(100 * val / width) + ret += f" {percent:02d}%" print(ret) time.sleep_ms(delay_ms) ms_counter += delay_ms diff --git a/components/bl00mbox/micropython/bl00mbox/_patches.py b/components/bl00mbox/micropython/bl00mbox/_patches.py index 4c875568ebddb9f61030f42aa8a9db9ce2ecdd4a..34f76868c7b0e2514f39d8a520c7fd11345420b3 100644 --- a/components/bl00mbox/micropython/bl00mbox/_patches.py +++ b/components/bl00mbox/micropython/bl00mbox/_patches.py @@ -104,32 +104,38 @@ class sampler(_Patch): requires a wave file (str) or max sample length in milliseconds (int). default path: /sys/samples/ """ + # DEPRECATED + _READ_HEAD_POS = 0 // 2 + _WRITE_HEAD_POS = 2 // 2 + _SAMPLE_START = 4 // 2 + _SAMPLE_LEN = 6 // 2 + _SAMPLE_RATE = 8 // 2 + _STATUS = 10 + _BUFFER = 11 + def __init__(self, chan, init_var): # init can be filename to load into ram super().__init__(chan) - self.buffer_offset_i16 = 9 + self.buffer_offset_i16 = 11 self._filename = "" if type(init_var) == str: - filename = init_var - f = wave.open(self._convert_filename(filename), "r") - - self._len_frames = f.getnframes() self.plugins.sampler = chan.new( - bl00mbox.plugins._sampler_ram, self.memory_len + bl00mbox.plugins.sampler, self._convert_filename(init_var) ) - - f.close() - self.load(filename) else: - self._len_frames = int(48 * init_var) - self.plugins.sampler = chan.new( - bl00mbox.plugins._sampler_ram, self.memory_len - ) + self.plugins.sampler = chan.new(bl00mbox.plugins.sampler, init_var) - self.signals.trigger = self.plugins.sampler.signals.trigger - self.signals.output = self.plugins.sampler.signals.output - self.signals.rec_in = self.plugins.sampler.signals.rec_in - self.signals.rec_trigger = self.plugins.sampler.signals.rec_trigger + self.signals.trigger = self.plugins.sampler.signals.playback_trigger + self.signals.output = self.plugins.sampler.signals.playback_output + self.signals.rec_in = self.plugins.sampler.signals.record_input + self.signals.rec_trigger = self.plugins.sampler.signals.record_trigger + + # terrible backwards compatibility hack, never do that IRL + del self.plugins.sampler.signals._setattr_allowed + self.plugins.sampler.signals.pitch_shift = ( + self.plugins.sampler.signals.playback_speed + ) + self.plugins.sampler.signals._setattr_allowed = True def _convert_filename(self, filename): # TODO: waht if filename doesn't exist? @@ -142,73 +148,20 @@ class sampler(_Patch): def load(self, filename): filename = self._convert_filename(filename) - if not os.path.exists(filename): - return False try: - f = wave.open(filename, "r") - assert f.getsampwidth() == 2 - assert f.getnchannels() in (1, 2) - assert f.getcomptype() == "NONE" + self.plugins.sampler.load(filename) except: + # no proper exception catching bc backwards compat return False - - frames = f.getnframes() - if frames > self.memory_len: - frames = self.memory_len - self.sample_len = frames - - self.sample_rate = f.getframerate() - - BUFFER_SIZE = int(48000 * 2.5) - - if f.getnchannels() == 1: - # fast path for mono - table = self.plugins.sampler.table_bytearray - for i in range( - 2 * self.buffer_offset_i16, - (self.sample_len + self.buffer_offset_i16) * 2, - BUFFER_SIZE * 2, - ): - table[i : i + BUFFER_SIZE * 2] = f.readframes(BUFFER_SIZE) - else: - # somewhat fast path for stereo - table = self.plugins.sampler.table_int16_array - for i in range( - self.buffer_offset_i16, - self.sample_len + self.buffer_offset_i16, - BUFFER_SIZE, - ): - frame = f.readframes(BUFFER_SIZE) - for j in range(0, len(frame) // 4): - value = int.from_bytes(frame[4 * j : 4 * j + 2], "little") - table[i + j] = value - f.close() return True def save(self, filename, overwrite=False): filename = self._convert_filename(filename) - if os.path.exists(filename): - if overwrite: - os.remove(filename) - else: - return False - f = wave.open(filename, "w") - f.setnchannels(1) - f.setsampwidth(2) - f.setframerate(self.sample_rate) - start = self._offset_index(0) - end = self._offset_index(self.sample_len) - print("start: " + str(start)) - print("end: " + str(end)) - print("memory_len: " + str(self.memory_len)) - - table = self.plugins.sampler.table_bytearray - if end > start: - f.writeframes(table[2 * start : 2 * end]) - else: - f.writeframes(table[2 * start :]) - f.writeframes(table[2 * self.buffer_offset_i16 : 2 * end]) - f.close() + try: + self.plugins.sampler.save(filename) + except: + # no proper exception catching bc backwards compat + return False return True def _offset_index(self, index): @@ -220,79 +173,54 @@ class sampler(_Patch): @property def memory_len(self): - return self._len_frames - - def _decode_uint32(self, pos): - table = self.plugins.sampler.table_int16_array - lsb = table[pos] - msb = table[pos + 1] - if lsb < 0: - lsb += 65536 - if msb < 0: - msb += 65536 - return lsb + (msb * (1 << 16)) - - def _encode_uint32(self, pos, num): - if num >= (1 << 32): - num = (1 << 32) - 1 - if num < 0: - num = 0 - msb = (num // (1 << 16)) & 0xFFFF - lsb = num & 0xFFFF - if lsb > 32767: - lsb -= 65536 - if msb > 32767: - msb -= 65536 - table = self.plugins.sampler.table_int16_array - table[pos] = lsb - table[pos + 1] = msb + return self.plugins.sampler.buffer_length @property def read_head_position(self): table = self.plugins.sampler.table_uint32_array - return table[0] + return table[self._READ_HEAD_POS] @read_head_position.setter def read_head_position(self, val): if val >= self.memory_len: val = self.memory_len - 1 table = self.plugins.sampler.table_uint32_array - table[0] = int(val) + table[self._READ_HEAD_POS] = int(val) @property def sample_start(self): table = self.plugins.sampler.table_uint32_array - return table[1] + return table[self._SAMPLE_START] @sample_start.setter def sample_start(self, val): if val >= self.memory_len: val = self.memory_len - 1 table = self.plugins.sampler.table_uint32_array - table[1] = int(val) + table[self._SAMPLE_START] = int(val) @property def sample_len(self): table = self.plugins.sampler.table_uint32_array - return table[2] + return table[self._SAMPLE_LEN] @sample_len.setter def sample_len(self, val): if val > self.memory_len: val = self.memory_len table = self.plugins.sampler.table_uint32_array - table[2] = int(val) + table[self._SAMPLE_LEN] = int(val) @property def sample_rate(self): table = self.plugins.sampler.table_uint32_array - return table[3] + return table[self._SAMPLE_RATE] @sample_rate.setter def sample_rate(self, val): table = self.plugins.sampler.table_uint32_array if val < 0: - table[3] = int(val) + table[self._SAMPLE_RATE] = int(val) @property def filename(self): @@ -303,9 +231,10 @@ class sampler(_Patch): """ returns true once after a record cycle has been completed. useful for checking whether a save is necessary if nothing else has modified the table. """ - - if self.plugins.sampler_table[8]: - self.plugins.sampler_table[8] = 0 + a = self.plugins.sampler_table[10] + if a & (1 << 4) & 0xFF: + a = a & ~(1 << 4) & 0xFF + self.plugins.sampler_table[10] = a return True return False @@ -316,8 +245,8 @@ class sequencer(_Patch): self.num_steps = num_steps self.num_tracks = num_tracks init_var = (self.num_steps * 256) + (self.num_tracks) # magic + self.plugins.seq = chan.new(bl00mbox.plugins.sequencer, init_var) - self.plugins.seq = chan.new(bl00mbox.plugins._sequencer, init_var) self.signals.bpm = self.plugins.seq.signals.bpm self.signals.beat_div = self.plugins.seq.signals.beat_div self.signals.step = self.plugins.seq.signals.step @@ -339,9 +268,9 @@ class sequencer(_Patch): ) ret += ( " step: " - + str(self.signals.step.value) + + str(self.signals.step.value - self.signals.step_start.value) + "/" - + str(self.signals.step_len.value) + + str(self.signals.step_end.value - self.signals.step_start.value) ) ret += "\n [tracks]" """ @@ -387,6 +316,7 @@ class sequencer(_Patch): class fuzz(_Patch): + # DEPRECATED def __init__(self, chan): super().__init__(chan) self.plugins.dist = chan.new(bl00mbox.plugins._distortion) @@ -460,6 +390,7 @@ class karplus_strong(_Patch): self.signals.output = self.plugins.flanger.signals.output self.signals.level = self.plugins.flanger.signals.level + self.signals.decay = self.plugins.flanger.signals.decay self.decay = 1000 @property diff --git a/components/bl00mbox/micropython/bl00mbox/_plugins.py b/components/bl00mbox/micropython/bl00mbox/_plugins.py index c164b3312a54f7b0f00bb0851aa5c9ff21f0ef42..c3eb2b41b82ce7394047804665388c9b9a8eb9da 100644 --- a/components/bl00mbox/micropython/bl00mbox/_plugins.py +++ b/components/bl00mbox/micropython/bl00mbox/_plugins.py @@ -1,9 +1,13 @@ # SPDX-License-Identifier: CC0-1.0 import sys_bl00mbox +import bl00mbox +import uctypes +import os +import cpython.wave as wave -class _Plugin: +class _PluginDescriptor: def __init__(self, index): self.index = index self.plugin_id = sys_bl00mbox.plugin_index_get_id(self.index) @@ -21,20 +25,571 @@ class _Plugin: ) -class _Plugins: +class _PluginDescriptors: pass -plugins = _Plugins() +plugins = _PluginDescriptors() def _fill(): plugins_list = {} for i in range(sys_bl00mbox.plugin_registry_num_plugins()): plugins_list[sys_bl00mbox.plugin_index_get_name(i).replace(" ", "_")] = i - for name, value in plugins_list.items(): - setattr(plugins, name, _Plugin(value)) + setattr(plugins, name, _PluginDescriptor(value)) + # legacy + if name == "sequencer": + setattr(plugins, "_" + name, _PluginDescriptor(value)) + if name == "sampler": + setattr(plugins, "_sampler_ram", _PluginDescriptor(value)) _fill() + + +class _Plugin: + def __init__(self, channel, plugin_id, bud_num=None, init_var=0): + self._channel_num = channel.channel_num + if bud_num == None: + self._plugin_id = plugin_id + self._bud_num = sys_bl00mbox.channel_new_bud( + self.channel_num, self.plugin_id, init_var + ) + if self._bud_num == None: + raise bl00mbox.Bl00mboxError("plugin init failed") + else: + self._bud_num = bud_num + self._check_existence() + self._plugin_id = sys_bl00mbox.channel_bud_get_plugin_id( + self.channel_num, self.bud_num + ) + self._name = sys_bl00mbox.channel_bud_get_name(self.channel_num, self.bud_num) + self._signals = bl00mbox.SignalList(self) + + def __repr__(self): + self._check_existence() + ret = "[plugin " + str(self.bud_num) + "] " + self.name + for sig in self.signals._list: + ret += "\n " + "\n ".join(sig._no_desc().split("\n")) + return ret + + def __del__(self): + self._check_existence() + sys_bl00mbox.channel_delete_bud(self.channel_num, self.bud_num) + + def _check_existence(self): + if not sys_bl00mbox.channel_bud_exists(self.channel_num, self.bud_num): + raise bl00mbox.Bl00mboxError("plugin has been deleted") + + @property + def init_var(self): + return sys_bl00mbox.channel_bud_get_init_var(self.channel_num, self.bud_num) + + @property + def table_len(self): + return sys_bl00mbox.channel_bud_get_table_len(self.channel_num, self.bud_num) + + @property + def channel_num(self): + return self._channel_num + + @property + def plugin_id(self): + return self._plugin_id + + @property + def name(self): + return self._name + + @property + def bud_num(self): + return self._bud_num + + @property + def signals(self): + return self._signals + + @property + def table(self): + ret = [] + for x in range( + sys_bl00mbox.channel_bud_get_table_len(self.channel_num, self.bud_num) + ): + ret += [ + sys_bl00mbox.channel_bud_get_table_value( + self.channel_num, self.bud_num, x + ) + ] + return ret + + @table.setter + def table(self, stuff): + max_len = sys_bl00mbox.channel_bud_get_table_len(self.channel_num, self.bud_num) + if len(stuff) > max_len: + stuff = stuff[:max_len] + for x, y in enumerate(stuff): + sys_bl00mbox.channel_bud_set_table_value( + self.channel_num, self.bud_num, x, y + ) + + @property + def table_pointer(self): + pointer = sys_bl00mbox.channel_bud_get_table_pointer( + self.channel_num, self.bud_num + ) + max_len = sys_bl00mbox.channel_bud_get_table_len(self.channel_num, self.bud_num) + return (pointer, max_len) + + @property + def table_bytearray(self): + pointer, max_len = self.table_pointer + bytes_len = max_len * 2 + return uctypes.bytearray_at(pointer, bytes_len) + + @property + def table_int8_array(self): + pointer, max_len = self.table_pointer + descriptor = {"table": (0 | uctypes.ARRAY, max_len * 2 | uctypes.INT8)} + struct = uctypes.struct(pointer, descriptor) + return struct.table + + @property + def table_int16_array(self): + pointer, max_len = self.table_pointer + descriptor = {"table": (0 | uctypes.ARRAY, max_len | uctypes.INT16)} + struct = uctypes.struct(pointer, descriptor) + return struct.table + + @property + def table_uint32_array(self): + pointer, max_len = self.table_pointer + descriptor = {"table": (0 | uctypes.ARRAY, (max_len // 2) | uctypes.UINT32)} + struct = uctypes.struct(pointer, descriptor) + return struct.table + + +_plugin_subclasses = {} + + +def _make_new_plugin(channel, plugin_id, bud_num, *args, **kwargs): + if bud_num is not None: + plugin_id = sys_bl00mbox.channel_bud_get_plugin_id(channel.channel_num, bud_num) + if plugin_id in _plugin_subclasses: + return _plugin_subclasses[plugin_id]( + channel, plugin_id, bud_num, *args, **kwargs + ) + else: + init_var = kwargs.get("init_var", None) + if init_var is None and len(args) == 1: + init_var = args[0] + try: + init_var = int(init_var) + except: + return _Plugin(channel, plugin_id, bud_num) + return _Plugin(channel, plugin_id, bud_num, init_var) + + +def _plugin_set_subclass(plugin_id): + def decorator(cls): + _plugin_subclasses[plugin_id] = cls + return cls + + return decorator + + +@_plugin_set_subclass(696969) +class _Sampler(_Plugin): + # divide by two if uint32_t + _READ_HEAD_POS = 0 // 2 + _WRITE_HEAD_POS = 2 // 2 + _SAMPLE_START = 4 // 2 + _SAMPLE_LEN = 6 // 2 + _SAMPLE_RATE = 8 // 2 + _STATUS = 10 + _BUFFER = 11 + + def __init__(self, channel, plugin_id, bud_num, init_var=1000): + self._filename = "" + if bud_num is not None: + super().__init__(channel, plugin_id, bud_num=bud_num) + self._memory_len = self.init_var + elif type(init_var) is str: + with wave.open(init_var, "r") as f: + self._memory_len = f.getnframes() + super().__init__(channel, plugin_id, init_var=self._memory_len) + self.load(init_var) + else: + self._memory_len = int(48 * init_var) + super().__init__(channel, plugin_id, init_var=self._memory_len) + + def __repr__(self): + ret = super().__repr__() + ret += "\n playback " + if self.playback_loop: + ret += "(looped): " + else: + ret += "(single): " + if self.playback_progress is not None: + dots = int(self.playback_progress * 20) + ret += ( + "[" + + "#" * dots + + "-" * (20 - dots) + + "] " + + str(int(self.playback_progress * 100)) + + "%" + ) + else: + ret += "idle" + + ret += "\n record " + if self.record_overflow: + ret += "(overflow): " + else: + ret += "(autostop): " + if self.record_progress is not None: + dots = int(self.record_progress * 20) + ret += ( + "[" + + "#" * dots + + "-" * (20 - dots) + + "] " + + str(int(self.record_progress * 100)) + + "%" + ) + else: + ret += "idle" + ret += "\n buffer: " + " " * 11 + rel_fill = self.sample_length / self.buffer_length + dots = int(rel_fill * 20) + if dots > 19: + dots = 19 + ret += ( + "[" + "#" * dots + "-" * (19 - dots) + "] " + str(int(rel_fill * 100)) + "%" + ) + if self.filename is not "": + ret += "\n file: " + self.filename + ret += "\n sample rate: " + " " * 6 + str(self.sample_rate) + return ret + + def load(self, filename): + with wave.open(filename, "r") as f: + try: + assert f.getsampwidth() == 2 + assert f.getnchannels() in (1, 2) + assert f.getcomptype() == "NONE" + except AssertionError: + raise Bl00mboxError("incompatible file format") + + frames = f.getnframes() + if frames > self._memory_len: + frames = self._memory_len + self._sample_len_frames = frames + + self.sample_rate = f.getframerate() + + BUFFER_SIZE = int(48000 * 2.5) + + if f.getnchannels() == 1: + # fast path for mono + table = self.table_bytearray + for i in range( + 2 * self._BUFFER, + (self._sample_len_frames + self._BUFFER) * 2, + BUFFER_SIZE * 2, + ): + table[i : i + BUFFER_SIZE * 2] = f.readframes(BUFFER_SIZE) + else: + # somewhat fast path for stereo + table = self.table_int16_array + for i in range( + self._BUFFER, + self._sample_len_frames + self._BUFFER, + BUFFER_SIZE, + ): + frame = f.readframes(BUFFER_SIZE) + for j in range(0, len(frame) // 4): + value = int.from_bytes(frame[4 * j : 4 * j + 2], "little") + table[i + j] = value + self._filename = filename + self._set_status_bit(4, 0) + + def save(self, filename): + with wave.open(filename, "w") as f: + f.setnchannels(1) + f.setsampwidth(2) + f.setframerate(self.sample_rate) + start = self._offset_index(0) + end = self._offset_index(self._sample_len_frames) + table = self.table_bytearray + if end > start: + f.writeframes(table[2 * start : 2 * end]) + else: + f.writeframes(table[2 * start :]) + f.writeframes(table[2 * self._BUFFER : 2 * end]) + if self._filename: + self._filename = filename + self._set_status_bit(4, 0) + + def _offset_index(self, index): + index += self._sample_start + if index >= self._memory_len: + index -= self._memory_len + index += self._BUFFER + return index + + def _set_status_bit(self, bit, val): + table = self.table_int16_array + if val: + table[self._STATUS] = (table[self._STATUS] | (1 << bit)) & 0xFF + else: + table[self._STATUS] = (table[self._STATUS] & ~(1 << bit)) & 0xFF + + @property + def filename(self): + if self._get_status_bit(4): + self._filename = "" + self._set_status_bit(4, 0) + return self._filename + + def _get_status_bit(self, bit): + table = self.table_int16_array + return bool(table[self._STATUS] & (1 << bit)) + + @property + def playback_loop(self): + return self._get_status_bit(1) + + @playback_loop.setter + def playback_loop(self, val): + self._set_status_bit(1, val) + + @property + def record_overflow(self): + return self._get_status_bit(3) + + @record_overflow.setter + def record_overflow(self, val): + self._set_status_bit(3, val) + + @property + def record_progress(self): + if self._get_status_bit(2): + table = self.table_uint32_array + return table[self._WRITE_HEAD_POS] / self._memory_len + return None + + @property + def playback_progress(self): + if self._get_status_bit(0): + table = self.table_uint32_array + return table[0] / table[3] + return None + + @property + def _sample_start(self): + table = self.table_uint32_array + return table[2] + + @_sample_start.setter + def _sample_start(self, val): + if val >= self._memory_len: + val = self._memory_len - 1 + table = self.table_uint32_array + table[2] = int(val) + + @property + def _sample_len_frames(self): + table = self.table_uint32_array + return table[self._SAMPLE_LEN] + + @_sample_len_frames.setter + def _sample_len_frames(self, val): + val = int(val) + if val > 0: + table = self.table_uint32_array + table[self._SAMPLE_LEN] = val + + @property + def sample_length(self): + return self._sample_len_frames + + @property + def buffer_length(self): + return self._memory_len + + @property + def sample_rate(self): + table = self.table_uint32_array + return table[self._SAMPLE_RATE] + + @sample_rate.setter + def sample_rate(self, val): + table = self.table_uint32_array + if int(val) > 0: + table[self._SAMPLE_RATE] = int(val) + + +""" +@_plugin_set_subclass(9000) +class _Distortion(_Plugin): + def symmetric_power(self, power=2, volume=32767, gate=0): + table = list(range(129)) + for num in table: + if num < 64: + ret = num / 64 # scale to [0..1[ range + ret = ret**power + if ret > 1: + ret = 1 + table[num] = int(volume * (ret - 1)) + else: + ret = (128 - num) / 64 # scale to [0..1] range + ret = ret**power + table[num] = int(volume * (1 - ret)) + gate = int(gate) >> 9 + for i in range(64 - gate, 64 + gate): + table[i] = 0 + self.table = table + + def __repr__(self): + ret = super().__repr__() + wave = self.table[:129] + ret += "\n curve:\n" + ret += " " + "_"*67 + "\n" + ret += " |" + " "*67 + "|\n" + symbols = "UW" + symbol_counter = 0 + for i in range(15, -1, -1): + line = " | " + for k in range(63): + vals = wave[2*k:2*k+4] + upper = ((max(vals)>>8) + 128) >> 4 + lower = ((min(vals)>>8) + 128) >> 4 + if (i >= lower) and (i <= upper): + line += symbols[symbol_counter] + symbol_counter = (symbol_counter + 1) % len(symbols) + else: + line += " " + line += " |\n" + ret += line + ret += " |" + "_"*67 + "|" + return ret +""" + + +@_plugin_set_subclass(420) +class _Osc(_Plugin): + @property + def wave(self): + return tuple([self.table_int8_array[i] for i in range(64)]) + + def __repr__(self): + ret = super().__repr__() + wave = self.wave + ret += "\n wave debug (lazy updates, may get stuck on old data):\n" + ret += " " + "_" * 68 + "\n" + ret += " |" + " " * 68 + "|\n" + symbols = "UW" + symbol_counter = 0 + for i in range(15, -1, -1): + line = " | " + for j in range(64): + if j == 0: + upper = wave[63] + else: + upper = wave[j - 1] + upper = (upper + 128) >> 4 + lower = (wave[j] + 128) >> 4 + if lower > upper: + upper, lower = lower, upper + if (i >= lower) and (i <= upper): + line += symbols[symbol_counter] + symbol_counter = (symbol_counter + 1) % len(symbols) + else: + line += " " + line += " |\n" + ret += line + ret += " |" + "_" * 68 + "|" + return ret + + +@_plugin_set_subclass(56709) +class _Sequencer(_Plugin): + def __init__(self, channel, plugin_id, bud_num, init_var=None, tracks=4, steps=16): + if bud_num is None: + if init_var is None: + self.num_steps = steps % 256 + self.num_tracks = tracks % 256 + init_var = (self.num_steps * 256) + (self.num_tracks) + else: + self.num_tracks = init_var % 256 + self.num_steps = (init_var // 256) % 256 + + super().__init__(channel, plugin_id, init_var=init_var) + + tracktable = [-32767] + ([0] * self.num_steps) + self.table = tracktable * self.num_tracks + else: + super().__init__(channel, plugin_id, bud_num=bud_num) + self.num_tracks = self.init_var % 256 + self.num_steps = (self.init_var // 256) % 256 + + def __repr__(self): + ret = super().__repr__() + ret += ( + "\n bpm: " + + str(self.signals.bpm.value) + + " @ 1/" + + str(self.signals.beat_div.value) + ) + ret += ( + " step: " + + str(self.signals.step.value - self.signals.step_start.value) + + "/" + + str(self.signals.step_end.value - self.signals.step_start.value) + ) + ret += "\n [tracks]" + for track in range(self.num_tracks): + ret += ( + "\n " + + str(track) + + " [ " + + "".join( + [ + "X " if self.trigger_state(track, x) > 0 else ". " + for x in range(self.num_steps) + ] + ) + + "]" + ) + return ret + + def _get_table_index(self, track, step): + return step + 1 + track * (self.num_steps + 1) + + def trigger_start(self, track, step, val=32767): + if val > 32767: + val = 32767 + elif val < 1: + val = 1 + table = self.table_int16_array + table[self._get_table_index(track, step)] = val + + def trigger_stop(self, track, step): + table = self.table_int16_array + table[self._get_table_index(track, step)] = -1 + + def trigger_clear(self, track, step): + table = self.table_int16_array + table[self._get_table_index(track, step)] = 0 + + def trigger_state(self, track, step): + table = self.table_int16_array + return table[self._get_table_index(track, step)] + + def trigger_toggle(self, track, step): + if self.trigger_state(track, step) == 0: + self.trigger_start(track, step) + else: + self.trigger_clear(track, step) diff --git a/components/bl00mbox/micropython/bl00mbox/_user.py b/components/bl00mbox/micropython/bl00mbox/_user.py index 251e5748d6783813951e2c729f5048d182b97c99..06185c2eacd64648e723c705e6cd7a47756ffa4c 100644 --- a/components/bl00mbox/micropython/bl00mbox/_user.py +++ b/components/bl00mbox/micropython/bl00mbox/_user.py @@ -61,11 +61,39 @@ class ChannelMixer: s = sys_bl00mbox.channel_get_signal_by_mixer_list_pos( self._channel.channel_num, i ) - sig = Signal(Plugin(self._channel, 0, bud_num=b), s) + sig = Signal(bl00mbox._plugins._make_new_plugin(self._channel, 0, b), s) ret += [sig] return ret +class ValueSwitch: + def __init__(self, plugin, signal_num): + self._plugin = plugin + self._signal_num = signal_num + + def __setattr__(self, key, value): + if getattr(self, "_locked", False): + if not value: + return + val = getattr(self, key, None) + if val is None: + return + sys_bl00mbox.channel_bud_set_signal_value( + self._plugin.channel_num, + self._plugin.bud_num, + self._signal_num, + int(val), + ) + else: + super().__setattr__(key, value) + + def get_value_name(self, val): + d = dict((k, v) for k, v in self.__dict__.items() if not k.startswith("_")) + for k, v in d.items(): + if v == val: + return k + + class Signal: def __init__(self, plugin, signal_num): self._plugin = plugin @@ -82,6 +110,26 @@ class Signal: self._unit = sys_bl00mbox.channel_bud_get_signal_unit( plugin.channel_num, plugin.bud_num, signal_num ) + constants = {} + another_round = True + while another_round: + another_round = False + for k, char in enumerate(self._unit): + if char == "{": + for j, stopchar in enumerate(self._unit[k:]): + if stopchar == "}": + const = self._unit[k + 1 : k + j].split(":") + constants[const[0]] = int(const[1]) + self._unit = self._unit[:k] + self._unit[k + j + 1 :] + break + another_round = True + break + if constants: + self._unit = self._unit.strip() + self.switch = ValueSwitch(plugin, signal_num) + for key in constants: + setattr(self.switch, key, constants[key]) + self.switch._locked = True self._hints = "" def __repr__(self): @@ -96,20 +144,37 @@ class Signal: ret = self.name if self._mpx != -1: ret += "[" + str(self._mpx) + "]" + if len(self.unit): ret += " [" + self.unit + "]" + ret += " [" + self.hints + "]: " + conret = [] direction = " <?> " + val = self.value if isinstance(self, SignalInput): direction = " <= " if len(self.connections): - ret += str(self.connections[0].value) - else: - ret += str(self.value) + val = self.connections[0].value elif isinstance(self, SignalOutput): direction = " => " - ret += str(self.value) + + ret += str(val) + + if getattr(self, "switch", None) is not None: + value_name = self.switch.get_value_name(val) + if value_name is not None: + ret += " (" + value_name.lower() + ")" + + if isinstance(self, SignalPitchMixin): + ret += " / " + str(self.tone) + " semitones / " + str(self.freq) + "Hz" + + if isinstance(self, SignalGainMixin): + if self.mult == 0: + ret += " / (mute)" + else: + ret += " / " + str(self.dB) + "dB / x" + str(self.mult) for con in self.connections: if isinstance(con, Signal): @@ -129,15 +194,6 @@ class Signal: nl += " " ret += nl.join(conret) - if isinstance(self, SignalPitchMixin): - ret += " / " + str(self.tone) + " semitones / " + str(self.freq) + "Hz" - - if isinstance(self, SignalGainMixin): - if self.mult == 0: - ret += " / (mute)" - else: - ret += " / " + str(self.dB) + "dB / x" + str(self.mult) - return ret @property @@ -248,7 +304,11 @@ class SignalOutput(Signal): chan, bud_num, sig, i ) if (s >= 0) and (b > 0): - cons += [_makeSignal(Plugin(Channel(chan), 0, bud_num=b), s)] + cons += [ + _makeSignal( + bl00mbox._plugins._make_new_plugin(Channel(chan), 0, b), s + ) + ] elif s == -1: cons += [ChannelMixer(Channel(chan))] return cons @@ -296,7 +356,9 @@ class SignalInput(Signal): b = sys_bl00mbox.channel_get_source_bud(chan, bud, sig) s = sys_bl00mbox.channel_get_source_signal(chan, bud, sig) if (s >= 0) and (b > 0): - cons += [_makeSignal(Plugin(Channel(chan), 0, bud_num=b), s)] + cons += [ + _makeSignal(bl00mbox._plugins._make_new_plugin(Channel(chan), 0, b), s) + ] return cons def _start(self, velocity=32767): @@ -492,112 +554,6 @@ class SignalList: raise AttributeError("signal does not exist") -class Plugin: - def __init__(self, channel, plugin_id, init_var=0, bud_num=None): - self._channel_num = channel.channel_num - if bud_num == None: - self._plugin_id = plugin_id - self._bud_num = sys_bl00mbox.channel_new_bud( - self.channel_num, self.plugin_id, init_var - ) - if self._bud_num == None: - raise Bl00mboxError("plugin init failed") - else: - self._bud_num = bud_num - self._check_existence() - self._name = sys_bl00mbox.channel_bud_get_plugin_id( - self.channel_num, self.bud_num - ) - self._name = sys_bl00mbox.channel_bud_get_name(self.channel_num, self.bud_num) - self._signals = SignalList(self) - - def __repr__(self): - self._check_existence() - ret = "[plugin " + str(self.bud_num) + "] " + self.name - for sig in self.signals._list: - ret += "\n " + "\n ".join(sig._no_desc().split("\n")) - return ret - - def __del__(self): - self._check_existence() - sys_bl00mbox.channel_delete_bud(self.channel_num, self.bud_num) - - def _check_existence(self): - if not sys_bl00mbox.channel_bud_exists(self.channel_num, self.bud_num): - raise Bl00mboxError("plugin has been deleted") - - @property - def channel_num(self): - return self._channel_num - - @property - def plugin_id(self): - return self._plugin_id - - @property - def name(self): - return self._name - - @property - def bud_num(self): - return self._bud_num - - @property - def signals(self): - return self._signals - - @property - def table(self): - ret = [] - for x in range( - sys_bl00mbox.channel_bud_get_table_len(self.channel_num, self.bud_num) - ): - ret += [ - sys_bl00mbox.channel_bud_get_table_value( - self.channel_num, self.bud_num, x - ) - ] - return ret - - @table.setter - def table(self, stuff): - max_len = sys_bl00mbox.channel_bud_get_table_len(self.channel_num, self.bud_num) - if len(stuff) > max_len: - stuff = stuff[:max_len] - for x, y in enumerate(stuff): - sys_bl00mbox.channel_bud_set_table_value( - self.channel_num, self.bud_num, x, y - ) - - @property - def table_pointer(self): - pointer = sys_bl00mbox.channel_bud_get_table_pointer( - self.channel_num, self.bud_num - ) - max_len = sys_bl00mbox.channel_bud_get_table_len(self.channel_num, self.bud_num) - return (pointer, max_len) - - @property - def table_bytearray(self): - pointer, max_len = self.table_pointer - bytes_len = max_len * 2 - return uctypes.bytearray_at(pointer, bytes_len) - - @property - def table_int16_array(self): - pointer, max_len = self.table_pointer - descriptor = {"table": (0 | uctypes.ARRAY, max_len | uctypes.INT16)} - struct = uctypes.struct(pointer, descriptor) - return struct.table - - @property - def table_uint32_array(self): - pointer, max_len = self.table_pointer - descriptor = {"table": (0 | uctypes.ARRAY, (max_len // 2) | uctypes.UINT32)} - struct = uctypes.struct(pointer, descriptor) - return struct.table - - class Channel: def __init__(self, name=None): if name == None: @@ -659,16 +615,13 @@ class Channel: self.clear() sys_bl00mbox.channel_set_free(self.channel_num, val) - def _new_plugin(self, thing, init_var=None): - plugin_init_var = 0 - if (type(init_var) == int) or (type(init_var) == float): - plugin_init_var = int(init_var) - plugin = None - if isinstance(thing, bl00mbox._plugins._Plugin): - plugin = Plugin(self, thing.plugin_id, plugin_init_var) - if type(thing) == int: - plugin = Plugin(self, thing, plugin_init_var) - return plugin + def _new_plugin(self, thing, *args, **kwargs): + if isinstance(thing, bl00mbox._plugins._PluginDescriptor): + return bl00mbox._plugins._make_new_plugin( + self, thing.plugin_id, None, *args, **kwargs + ) + else: + raise TypeError("not a plugin") @staticmethod def print_overview(): @@ -692,18 +645,18 @@ class Channel: if type(thing) == type: if issubclass(thing, bl00mbox.patches._Patch): return thing(self, *args, **kwargs) - if isinstance(thing, bl00mbox._plugins._Plugin) or (type(thing) == int): + if isinstance(thing, bl00mbox._plugins._PluginDescriptor) or ( + type(thing) == int + ): return self._new_plugin(thing, *args, **kwargs) @property def plugins(self): - plugins = [] - + ret = [] for i in range(sys_bl00mbox.channel_buds_num(self.channel_num)): b = sys_bl00mbox.channel_get_bud_by_list_pos(self.channel_num, i) - plugin = Plugin(self, 0, bud_num=b) - plugins += [plugin] - return plugins + ret += [bl00mbox._plugins._make_new_plugin(self, 0, b)] + return ret @property def channel_num(self): diff --git a/components/bl00mbox/micropython/mp_sys_bl00mbox.c b/components/bl00mbox/micropython/mp_sys_bl00mbox.c index 05dc7837bf2feb21a039892343bfd55cbcdae737..38e465685f90c9a35527031de12dd95a34e8998b 100644 --- a/components/bl00mbox/micropython/mp_sys_bl00mbox.c +++ b/components/bl00mbox/micropython/mp_sys_bl00mbox.c @@ -240,6 +240,14 @@ STATIC mp_obj_t mp_channel_bud_get_plugin_id(mp_obj_t chan, mp_obj_t bud) { STATIC MP_DEFINE_CONST_FUN_OBJ_2(mp_channel_bud_get_plugin_id_obj, mp_channel_bud_get_plugin_id); +STATIC mp_obj_t mp_channel_bud_get_init_var(mp_obj_t chan, mp_obj_t bud) { + uint32_t init_var = bl00mbox_channel_bud_get_init_var( + mp_obj_get_int(chan), mp_obj_get_int(bud)); + return mp_obj_new_int(init_var); +} +STATIC MP_DEFINE_CONST_FUN_OBJ_2(mp_channel_bud_get_init_var_obj, + mp_channel_bud_get_init_var); + STATIC mp_obj_t mp_channel_bud_get_num_signals(mp_obj_t chan, mp_obj_t bud) { uint16_t ret = bl00mbox_channel_bud_get_num_signals(mp_obj_get_int(chan), mp_obj_get_int(bud)); @@ -301,11 +309,14 @@ STATIC MP_DEFINE_CONST_FUN_OBJ_3(mp_channel_bud_get_signal_hints_obj, STATIC mp_obj_t mp_channel_bud_set_signal_value(size_t n_args, const mp_obj_t *args) { + int32_t value = mp_obj_get_int(args[3]); + if(value > 32767) value = 32767; + if(value < -32767) value = -32767; bool success = bl00mbox_channel_bud_set_signal_value( mp_obj_get_int(args[0]), // chan mp_obj_get_int(args[1]), // bud_index mp_obj_get_int(args[2]), // bud_signal_index - mp_obj_get_int(args[3])); // value + value); return mp_obj_new_bool(success); } STATIC MP_DEFINE_CONST_FUN_OBJ_VAR_BETWEEN(mp_channel_bud_set_signal_value_obj, @@ -536,6 +547,8 @@ STATIC const mp_map_elem_t bl00mbox_globals_table[] = { MP_ROM_PTR(&mp_channel_bud_get_description_obj) }, { MP_ROM_QSTR(MP_QSTR_channel_bud_get_plugin_id), MP_ROM_PTR(&mp_channel_bud_get_plugin_id_obj) }, + { MP_ROM_QSTR(MP_QSTR_channel_bud_get_init_var), + MP_ROM_PTR(&mp_channel_bud_get_init_var_obj) }, { MP_ROM_QSTR(MP_QSTR_channel_bud_get_num_signals), MP_ROM_PTR(&mp_channel_bud_get_num_signals_obj) }, diff --git a/components/bl00mbox/radspa/radspa.h b/components/bl00mbox/radspa/radspa.h index c3af2f5114de8897811ec5483fb1350c799df2e9..cc17e94fb8b7e2bdc33299b14a5410233852201b 100644 --- a/components/bl00mbox/radspa/radspa.h +++ b/components/bl00mbox/radspa/radspa.h @@ -43,10 +43,6 @@ #define RADSPA_SIGNAL_HINT_TRIGGER (1<<2) #define RADSPA_SIGNAL_HINT_GAIN (1<<3) #define RADSPA_SIGNAL_HINT_SCT (1<<5) -#define RADSPSA_SIGNAL_HINT_REDUCED_RANGE (1<<6) -#define RADSPSA_SIGNAL_HINT_POS_SHIFT (1<<7) -// 6 bit number, 0 for non-stepped, else number of steps -#define RADSPSA_SIGNAL_HINT_STEPPED_LSB (1<<8) #define RADSPA_SIGNAL_VAL_SCT_A440 (INT16_MAX - 6*2400) #define RADSPA_SIGNAL_VAL_UNITY_GAIN (1<<12) @@ -83,16 +79,10 @@ typedef struct _radspa_signal_t{ int16_t value; // when the signal has last requested to render its source uint32_t render_pass_id; - // linked list pointer - struct _radspa_signal_t * next; } radspa_signal_t; typedef struct _radspa_t{ const radspa_descriptor_t * descriptor; - - // linked list of all i/o signals of the module and length of list - radspa_signal_t * signals; - uint8_t len_signals; // renders all signal outputs for num_samples if render_pass_id has changed // since the last call, else does nothing. @@ -101,53 +91,17 @@ typedef struct _radspa_t{ // stores id number of render pass. uint32_t render_pass_id; + // init var that was used for creating the plugin. if the plugin needs to modify the value to + // a valid range it may do so at any point in time. + uint32_t init_var; void * plugin_data; // internal data for the plugin to use. should not be accessed from outside. uint32_t plugin_table_len; int16_t * plugin_table; -} radspa_t; - -/* SIGNAL HELPERS - */ - -inline int16_t radspa_clip(int32_t a){ - if(a > 32767){ - return 32767; - } else if(a < -32767){ - return -32767; - } - return a; -} - -inline int16_t radspa_add_sat(int32_t a, int32_t b){ return radspa_clip(a+b); } -inline int32_t radspa_mult_shift(int32_t a, int32_t b){ return radspa_clip((a*b)>>15); } -inline int32_t radspa_gain(int32_t a, int32_t b){ return radspa_clip((a*b)>>12); } - -inline int16_t radspa_trigger_start(int16_t velocity, int16_t * hist){ - if(!velocity) velocity = 1; - if(velocity == -32768) velocity = 1; - if(velocity < 0) velocity = -velocity; - (* hist) = ((* hist) > 0) ? -velocity : velocity; - return * hist; -} - -inline int16_t radspa_trigger_stop(int16_t * hist){ - (* hist) = 0; - return * hist; -} - -inline int16_t radspa_trigger_get(int16_t trigger_signal, int16_t * hist){ - if((* hist) == trigger_signal) return 0; - (* hist) = trigger_signal; - if(!trigger_signal){ - return -1; - } else if(trigger_signal < 0 ){ - return -trigger_signal; - } else { - return trigger_signal; - } -} + uint8_t len_signals; + radspa_signal_t signals[]; +} radspa_t; /* REQUIREMENTS * Hosts must provide implementations for the following functions: diff --git a/components/bl00mbox/radspa/radspa_helpers.c b/components/bl00mbox/radspa/radspa_helpers.c index 5da777b31104c4bde573221d2b8fd921f4ebf275..f952657c26bdaf173cb64cc8c4603287730d8dd8 100644 --- a/components/bl00mbox/radspa/radspa_helpers.c +++ b/components/bl00mbox/radspa/radspa_helpers.c @@ -13,34 +13,7 @@ extern inline int32_t radspa_gain(int32_t a, int32_t b); extern inline int16_t radspa_trigger_start(int16_t velocity, int16_t * hist); extern inline int16_t radspa_trigger_stop(int16_t * hist); extern inline int16_t radspa_trigger_get(int16_t trigger_signal, int16_t * hist); - -#define RADSPA_SIGNAL_CACHING -// get signal struct from a signal index -radspa_signal_t * radspa_signal_get_by_index(radspa_t * plugin, uint16_t signal_index){ - radspa_signal_t * ret = NULL; - if(plugin == NULL) return ret; -#ifdef RADSPA_SIGNAL_CACHING - static radspa_signal_t * cache_s = NULL; - static radspa_t * cache_p = NULL; - static uint16_t cache_i = 0; - - if((plugin == cache_p) && (signal_index == cache_i + 1) && (cache_s != NULL)){ - ret = cache_s->next; - } else { -#endif - ret = plugin->signals; - for(uint16_t i = 0; i < signal_index; i++){ - ret = ret->next; - if(ret == NULL) break; - } -#ifdef RADSPA_SIGNAL_CACHING - } - cache_s = ret; - cache_p = plugin; - cache_i = signal_index; -#endif - return ret; -} +extern inline radspa_signal_t * radspa_signal_get_by_index(radspa_t * plugin, uint16_t signal_index); radspa_signal_t * radspa_signal_set(radspa_t * plugin, uint8_t signal_index, char * name, uint32_t hints, int16_t value){ radspa_signal_t * sig = radspa_signal_get_by_index(plugin, signal_index); @@ -79,39 +52,18 @@ void radspa_signal_set_group_description(radspa_t * plugin, uint8_t group_len, u } } -int16_t radspa_signal_add(radspa_t * plugin, char * name, uint32_t hints, int16_t value){ - radspa_signal_t * sig = calloc(1,sizeof(radspa_signal_t)); - if(sig == NULL) return -1; // allocation failed - sig->name = name; - sig->hints = hints; +static void radspa_signal_init(radspa_signal_t * sig){ + sig->name = "UNINITIALIZED"; + sig->hints = 0; sig->unit = ""; sig->description = ""; - sig->next = NULL; - sig->value = value; + sig->value = 0; sig->name_multiplex = -1; - sig->buffer = NULL; - - //find end of linked list - uint16_t list_index = 0; - if(plugin->signals == NULL){ - plugin->signals = sig; - } else { - radspa_signal_t * sigs = plugin->signals; - list_index++; - while(sigs->next != NULL){ - sigs = sigs->next; - list_index++; - } - sigs->next = sig; - } - if(plugin->len_signals != list_index) abort(); - plugin->len_signals++; - return list_index; } radspa_t * radspa_standard_plugin_create(radspa_descriptor_t * desc, uint8_t num_signals, size_t plugin_data_size, uint32_t plugin_table_size){ - radspa_t * ret = calloc(1, sizeof(radspa_t)); + radspa_t * ret = calloc(1, sizeof(radspa_t) + num_signals * sizeof(radspa_signal_t)); if(ret == NULL) return NULL; if(plugin_data_size){ ret->plugin_data = calloc(1,plugin_data_size); @@ -120,20 +72,16 @@ radspa_t * radspa_standard_plugin_create(radspa_descriptor_t * desc, uint8_t num return NULL; } } - ret->signals = NULL; - ret->len_signals = 0; + ret->len_signals = num_signals; ret->render = NULL; ret->descriptor = desc; ret->plugin_table_len = plugin_table_size; - - bool init_failed = false; for(uint8_t i = 0; i < num_signals; i++){ - if(radspa_signal_add(ret,"UNINITIALIZED",0,0) == -1){ - init_failed = true; - break; - } + radspa_signal_init(&(ret->signals[i])); } + bool init_failed = false; + if(ret->plugin_table_len){ ret->plugin_table = calloc(plugin_table_size, sizeof(int16_t)); if(ret->plugin_table == NULL) init_failed = true; @@ -149,12 +97,6 @@ radspa_t * radspa_standard_plugin_create(radspa_descriptor_t * desc, uint8_t num } void radspa_standard_plugin_destroy(radspa_t * plugin){ - radspa_signal_t * sig = plugin->signals; - while(sig != NULL){ - radspa_signal_t * sig_next = sig->next; - free(sig); - sig = sig_next; - } if(plugin->plugin_table != NULL) free(plugin->plugin_table); if(plugin->plugin_data != NULL) free(plugin->plugin_data); free(plugin); diff --git a/components/bl00mbox/radspa/radspa_helpers.h b/components/bl00mbox/radspa/radspa_helpers.h index a3a66c4648adedf759c4fa0525320ff42f4947d3..f2b717d6da285bc21ca575f91d1cf494da02c150 100644 --- a/components/bl00mbox/radspa/radspa_helpers.h +++ b/components/bl00mbox/radspa/radspa_helpers.h @@ -2,6 +2,10 @@ #pragma once #include "radspa.h" +#define RADSPA_SIGNAL_NONCONST (-32768) +#define RADSPA_EVENT_MASK (0b11111) +#define RADSPA_EVENT_POW (5) + // adds signal to plugin instance struct. typically used to initiate a plugin instance. int16_t radspa_signal_add(radspa_t * plugin, char * name, uint32_t hints, int16_t value); // as above, but sets parameters of an already existing signal with at list position signal_index @@ -19,10 +23,54 @@ void radspa_standard_plugin_destroy(radspa_t * plugin); // frees all signal structs. typically used to destroy a plugin instance. void radspa_signals_free(radspa_t * plugin); +inline int16_t radspa_clip(int32_t a){ + if(a > 32767){ + return 32767; + } else if(a < -32767){ + return -32767; + } + return a; +} + +inline int16_t radspa_add_sat(int32_t a, int32_t b){ return radspa_clip(a+b); } +inline int32_t radspa_mult_shift(int32_t a, int32_t b){ return radspa_clip((a*b)>>15); } +inline int32_t radspa_gain(int32_t a, int32_t b){ return radspa_clip((a*b)>>12); } + +inline int16_t radspa_trigger_start(int16_t velocity, int16_t * hist){ + if(!velocity) velocity = 1; + if(velocity == -32768) velocity = 1; + if(velocity < 0) velocity = -velocity; + (* hist) = ((* hist) > 0) ? -velocity : velocity; + return * hist; +} + +inline int16_t radspa_trigger_stop(int16_t * hist){ + (* hist) = 0; + return * hist; +} + +inline int16_t radspa_trigger_get(int16_t trigger_signal, int16_t * hist){ + if((* hist) == trigger_signal) return 0; + (* hist) = trigger_signal; + if(!trigger_signal){ + return -1; + } else if(trigger_signal < 0 ){ + return -trigger_signal; + } else { + return trigger_signal; + } +} + + +inline radspa_signal_t * radspa_signal_get_by_index(radspa_t * plugin, uint16_t signal_index){ + return &(plugin->signals[signal_index]); +} + /* returns the value that a signal has at a given moment in time. time is * represented as the buffer index. requests rendering from host and requires implementation * of radspa_host_request_buffer_render. */ + inline int16_t radspa_signal_get_value(radspa_signal_t * sig, int16_t index, uint32_t render_pass_id){ if(sig->buffer != NULL){ if(sig->render_pass_id != render_pass_id){ @@ -35,6 +83,14 @@ inline int16_t radspa_signal_get_value(radspa_signal_t * sig, int16_t index, uin return sig->value; } +inline void radspa_signal_set_value_noclip(radspa_signal_t * sig, int16_t index, int16_t val){ + if(sig->buffer != NULL){ + sig->buffer[index] = val; + } else if(!index){ + sig->value = val; + } +} + inline void radspa_signal_set_value(radspa_signal_t * sig, int16_t index, int32_t val){ if(sig->buffer != NULL){ sig->buffer[index] = radspa_clip(val); @@ -44,6 +100,10 @@ inline void radspa_signal_set_value(radspa_signal_t * sig, int16_t index, int32_ } inline void radspa_signal_set_value_check_const(radspa_signal_t * sig, int16_t index, int32_t val){ + // disabled for now, causes issues somewhere, no time to track it down + radspa_signal_set_value(sig, index, val); + return; + if(sig->buffer == NULL){ if(!index) sig->value = radspa_clip(val); return; @@ -59,6 +119,14 @@ inline void radspa_signal_set_value_check_const(radspa_signal_t * sig, int16_t i } } +inline int16_t radspa_signal_set_value_check_const_result(radspa_signal_t * sig){ + if(sig->buffer != NULL){ + if(sig->buffer[1] == -32768) return sig->buffer[0]; + return RADSPA_SIGNAL_NONCONST; + } + return sig->value; +} + inline void radspa_signal_set_const_value(radspa_signal_t * sig, int32_t val){ if(sig->buffer == NULL){ sig->value = radspa_clip(val); @@ -68,7 +136,16 @@ inline void radspa_signal_set_const_value(radspa_signal_t * sig, int32_t val){ } } -#define RADSPA_SIGNAL_NONCONST -32768 +inline void radspa_signal_set_values(radspa_signal_t * sig, uint16_t start, uint16_t stop, int32_t val){ + if(sig->buffer == NULL){ + if((!start) && stop) sig->value = radspa_clip(val); + } else { + val = radspa_clip(val); + for(uint16_t i = start; i < stop; i++){ + sig->buffer[i] = val; + } + } +} inline int16_t radspa_signal_get_const_value(radspa_signal_t * sig, uint32_t render_pass_id){ if(sig->buffer != NULL){ @@ -82,6 +159,18 @@ inline int16_t radspa_signal_get_const_value(radspa_signal_t * sig, uint32_t ren return sig->value; } -// get signal struct from a signal index -radspa_signal_t * radspa_signal_get_by_index(radspa_t * plugin, uint16_t signal_index); +inline int16_t radspa_trigger_get_const(radspa_signal_t * sig, int16_t * hist, uint16_t * index, uint16_t num_samples, uint32_t render_pass_id){ + (* index) = 0; + int16_t ret_const = radspa_signal_get_const_value(sig, render_pass_id); + if(ret_const != RADSPA_SIGNAL_NONCONST) return radspa_trigger_get(ret_const, hist); + int16_t ret = 0; + for(uint16_t i = 0; i< num_samples; i++){ + int16_t tmp = radspa_trigger_get(radspa_signal_get_value(sig, i, render_pass_id), hist); + if(tmp){ + ret = tmp; + (* index) = i; + } + } + return ret; +} diff --git a/components/bl00mbox/radspa/standard_plugin_lib/ampliverter.c b/components/bl00mbox/radspa/standard_plugin_lib/ampliverter.c index ec13b636fdc37ee0666b9951599963897e72d3aa..8d6c2da6787e8b1579c73804a5883602760e950f 100644 --- a/components/bl00mbox/radspa/standard_plugin_lib/ampliverter.c +++ b/components/bl00mbox/radspa/standard_plugin_lib/ampliverter.c @@ -5,8 +5,8 @@ radspa_t * ampliverter_create(uint32_t init_var); radspa_descriptor_t ampliverter_desc = { .name = "ampliverter", - .id = 69, - .description = "saturating multiplication and addition", + .id = 68, + .description = "[DEPRECATED, replaced by `mixer` or `range_shifter`] saturating multiplication and addition", .create_plugin_instance = ampliverter_create, // with this we can only use radspa_standard_plugin_create to allocate memory. // this restricts data layout flexibility for large buffers, but in return it offers diff --git a/components/bl00mbox/radspa/standard_plugin_lib/env_adsr.c b/components/bl00mbox/radspa/standard_plugin_lib/env_adsr.c index ebe70afc66ceeef1b6360334691bade8423815e1..51f500ad0a973b7a7531553bdb59bf7328569317 100644 --- a/components/bl00mbox/radspa/standard_plugin_lib/env_adsr.c +++ b/components/bl00mbox/radspa/standard_plugin_lib/env_adsr.c @@ -8,15 +8,16 @@ radspa_descriptor_t env_adsr_desc = { .destroy_plugin_instance = radspa_standard_plugin_destroy }; -#define ENV_ADSR_NUM_SIGNALS 8 +#define ENV_ADSR_NUM_SIGNALS 9 #define ENV_ADSR_OUTPUT 0 #define ENV_ADSR_INPUT 1 -#define ENV_ADSR_TRIGGER 2 -#define ENV_ADSR_ATTACK 3 -#define ENV_ADSR_DECAY 4 -#define ENV_ADSR_SUSTAIN 5 -#define ENV_ADSR_RELEASE 6 -#define ENV_ADSR_GAIN 7 +#define ENV_ADSR_ENV_OUTPUT 2 +#define ENV_ADSR_TRIGGER 3 +#define ENV_ADSR_ATTACK 4 +#define ENV_ADSR_DECAY 5 +#define ENV_ADSR_SUSTAIN 6 +#define ENV_ADSR_RELEASE 7 +#define ENV_ADSR_GAIN 8 #define ENV_ADSR_PHASE_OFF 0 #define ENV_ADSR_PHASE_ATTACK 1 @@ -24,184 +25,174 @@ radspa_descriptor_t env_adsr_desc = { #define ENV_ADSR_PHASE_SUSTAIN 3 #define ENV_ADSR_PHASE_RELEASE 4 -radspa_t * env_adsr_create(uint32_t init_var){ - radspa_t * env_adsr = radspa_standard_plugin_create(&env_adsr_desc, ENV_ADSR_NUM_SIGNALS, sizeof(env_adsr_data_t),0); - env_adsr->render = env_adsr_run; - radspa_signal_set(env_adsr, ENV_ADSR_OUTPUT, "output", RADSPA_SIGNAL_HINT_OUTPUT, 0); - radspa_signal_set(env_adsr, ENV_ADSR_INPUT, "input", RADSPA_SIGNAL_HINT_INPUT, 32767); - radspa_signal_set(env_adsr, ENV_ADSR_TRIGGER, "trigger", RADSPA_SIGNAL_HINT_INPUT | RADSPA_SIGNAL_HINT_TRIGGER, 0); - radspa_signal_set(env_adsr, ENV_ADSR_ATTACK, "attack", RADSPA_SIGNAL_HINT_INPUT, 100); - radspa_signal_set(env_adsr, ENV_ADSR_DECAY, "decay", RADSPA_SIGNAL_HINT_INPUT, 250); - radspa_signal_set(env_adsr, ENV_ADSR_SUSTAIN, "sustain", RADSPA_SIGNAL_HINT_INPUT, 16000); - radspa_signal_set(env_adsr, ENV_ADSR_RELEASE, "release", RADSPA_SIGNAL_HINT_INPUT, 50); - radspa_signal_set(env_adsr, ENV_ADSR_GAIN, "gain", RADSPA_SIGNAL_HINT_INPUT | RADSPA_SIGNAL_HINT_GAIN, RADSPA_SIGNAL_VAL_UNITY_GAIN); - radspa_signal_get_by_index(env_adsr, ENV_ADSR_ATTACK)->unit = "ms"; - radspa_signal_get_by_index(env_adsr, ENV_ADSR_DECAY)->unit = "ms"; - radspa_signal_get_by_index(env_adsr, ENV_ADSR_SUSTAIN)->unit = "ms"; - - env_adsr_data_t * data = env_adsr->plugin_data; - data->trigger_prev = 0; - data->env_phase = ENV_ADSR_PHASE_OFF; - data->release_prev_ms = -1; - data->release_init_val_prev = -1; - data->attack_prev_ms = -1; - data->sustain_prev = -1; - data->decay_prev_ms = -1; - data->env_counter_prev = 0; - data->env_pre_gain = 0; - - data->output_sig = radspa_signal_get_by_index(env_adsr, ENV_ADSR_OUTPUT); - data->input_sig = radspa_signal_get_by_index(env_adsr, ENV_ADSR_INPUT); - data->trigger_sig = radspa_signal_get_by_index(env_adsr, ENV_ADSR_TRIGGER); - data->attack_sig = radspa_signal_get_by_index(env_adsr, ENV_ADSR_ATTACK); - data->decay_sig = radspa_signal_get_by_index(env_adsr, ENV_ADSR_DECAY); - data->sustain_sig = radspa_signal_get_by_index(env_adsr, ENV_ADSR_SUSTAIN); - data->release_sig = radspa_signal_get_by_index(env_adsr, ENV_ADSR_RELEASE); - data->gain_sig = radspa_signal_get_by_index(env_adsr, ENV_ADSR_GAIN); - - return env_adsr; -} - #define SAMPLE_RATE_SORRY 48000 -#define ENV_ADSR_UNDERSAMPLING 5 - -static inline uint32_t env_adsr_time_ms_to_val_rise(int16_t time_ms, uint32_t val, uint16_t leftshift){ +static inline uint32_t env_adsr_time_ms_to_val_rise(int16_t time_ms, uint32_t val, uint16_t num_samples){ if(!time_ms) return UINT32_MAX; if(time_ms < 0) time_ms = -time_ms; uint32_t div = time_ms * ((SAMPLE_RATE_SORRY)/1000); uint32_t input = val/div; - if(!leftshift) return input; // nothing to do - if(input >> (32-leftshift)) return UINT32_MAX; // sat - return input << leftshift; + if(((uint64_t) input * num_samples) >> 32){ + return UINT32_MAX; // sat + } else { + return input * num_samples; + } } -void env_adsr_run(radspa_t * env_adsr, uint16_t num_samples, uint32_t render_pass_id){ +static inline void update_attack_coeffs(radspa_t * env_adsr, uint16_t num_samples, uint32_t render_pass_id){ env_adsr_data_t * data = env_adsr->plugin_data; - int16_t trigger_const = radspa_signal_get_const_value(data->trigger_sig, render_pass_id); - - bool idle = data->env_phase == ENV_ADSR_PHASE_OFF; - if((trigger_const != RADSPA_SIGNAL_NONCONST) && idle){ - int16_t tmp = data->trigger_prev; - if(!radspa_trigger_get(trigger_const, &tmp)){ - radspa_signal_set_const_value(data->output_sig, 0); - return; - } + int16_t attack = radspa_signal_get_value(&env_adsr->signals[ENV_ADSR_ATTACK], 0, render_pass_id); + if((data->attack_prev_ms != attack) || (data->num_samples_prev != num_samples)){ + data->attack_raw = env_adsr_time_ms_to_val_rise(attack, UINT32_MAX, num_samples); + data->attack_prev_ms = attack; } +} - int16_t trigger = trigger_const; - - int16_t attack = radspa_signal_get_value(data->attack_sig, 0, render_pass_id); - int16_t decay = radspa_signal_get_value(data->decay_sig, 0, render_pass_id); - int32_t sustain = radspa_signal_get_value(data->sustain_sig, 0, render_pass_id); - int16_t release = radspa_signal_get_value(data->release_sig, 0, render_pass_id); - int32_t gain = radspa_signal_get_value(data->gain_sig, 0, render_pass_id); - - if(data->attack_prev_ms != attack){ - data->attack_raw = env_adsr_time_ms_to_val_rise(attack, UINT32_MAX, ENV_ADSR_UNDERSAMPLING); - data->attack_prev_ms = attack; +static inline void update_release_coeffs(radspa_t * env_adsr, uint16_t num_samples, uint32_t render_pass_id){ + env_adsr_data_t * data = env_adsr->plugin_data; + int16_t release = radspa_signal_get_value(&env_adsr->signals[ENV_ADSR_RELEASE], 0, render_pass_id); + if((data->release_prev_ms != release) || (data->num_samples_prev != num_samples)){ + data->release_raw = env_adsr_time_ms_to_val_rise(release, data->release_init_val, num_samples); + data->release_prev_ms = release; } +} +static inline void update_sustain_coeffs(radspa_t * env_adsr, uint16_t num_samples, uint32_t render_pass_id){ + env_adsr_data_t * data = env_adsr->plugin_data; + int16_t sustain = radspa_signal_get_value(&env_adsr->signals[ENV_ADSR_SUSTAIN], 0, render_pass_id); + sustain = sustain < 0 ? -sustain : sustain; data->sustain = ((uint32_t) sustain) << 17UL; +} - if((data->decay_prev_ms != decay) || (data->sustain_prev != data->sustain)){ - data->decay_raw = env_adsr_time_ms_to_val_rise(decay, UINT32_MAX - data->sustain, ENV_ADSR_UNDERSAMPLING); +static inline void update_decay_coeffs(radspa_t * env_adsr, uint16_t num_samples, uint32_t render_pass_id){ + update_sustain_coeffs(env_adsr, num_samples, render_pass_id); + env_adsr_data_t * data = env_adsr->plugin_data; + int32_t decay = radspa_signal_get_value(&env_adsr->signals[ENV_ADSR_DECAY], 0, render_pass_id); + if((data->decay_prev_ms != decay) || (data->sustain_prev != data->sustain) || (data->num_samples_prev != num_samples)){ + data->decay_raw = env_adsr_time_ms_to_val_rise(decay, UINT32_MAX - data->sustain, num_samples); data->decay_prev_ms = decay; data->sustain_prev = data->sustain; } +} - if(data->release_prev_ms != release){ - data->release_raw = env_adsr_time_ms_to_val_rise(release, data->release_init_val, ENV_ADSR_UNDERSAMPLING); - data->release_prev_ms = release; + +void env_adsr_run(radspa_t * env_adsr, uint16_t num_samples, uint32_t render_pass_id){ + env_adsr_data_t * data = env_adsr->plugin_data; + + uint16_t throwaway; + int16_t vel = radspa_trigger_get_const(&env_adsr->signals[ENV_ADSR_TRIGGER], &data->trigger_prev, &throwaway, num_samples, render_pass_id); + if(vel > 0 ){ // start + data->env_phase = ENV_ADSR_PHASE_ATTACK; + data->velocity = vel; + } else if(vel < 0){ // stop + if(data->env_phase != ENV_ADSR_PHASE_OFF){ + data->env_phase = ENV_ADSR_PHASE_RELEASE; + data->release_init_val = data->env_counter; + } } - int32_t env = 0; - - for(uint16_t i = 0; i < num_samples; i++){ - int16_t ret = 0; - - if((trigger_const == RADSPA_SIGNAL_NONCONST) || (!i)){ - if(trigger_const == RADSPA_SIGNAL_NONCONST) trigger = radspa_signal_get_value(data->trigger_sig, i, render_pass_id); - - int16_t vel = radspa_trigger_get(trigger, &(data->trigger_prev)); - - if(vel < 0){ // stop - if(data->env_phase != ENV_ADSR_PHASE_OFF){ - data->env_phase = ENV_ADSR_PHASE_RELEASE; - data->release_init_val = data->env_counter; - data->release_raw = env_adsr_time_ms_to_val_rise(release, data->release_init_val, ENV_ADSR_UNDERSAMPLING); - } - } else if(vel > 0 ){ // start - data->env_phase = ENV_ADSR_PHASE_ATTACK; - data->velocity = ((uint32_t) vel) << 17; - if(idle){ - for(uint16_t j = 0; j < i; j++){ - radspa_signal_set_value(data->output_sig, j, 0); - } - idle = false; - } + uint32_t tmp; + switch(data->env_phase){ + case ENV_ADSR_PHASE_OFF: + data->env_counter = 0;; + break; + case ENV_ADSR_PHASE_ATTACK: + update_attack_coeffs(env_adsr, num_samples, render_pass_id); + tmp = data->env_counter + data->attack_raw; + if(tmp < data->env_counter){ // overflow + tmp = ~((uint32_t) 0); // max out + data->env_phase = ENV_ADSR_PHASE_DECAY; + } + data->env_counter = tmp; + break; + case ENV_ADSR_PHASE_DECAY: + update_decay_coeffs(env_adsr, num_samples, render_pass_id); + tmp = data->env_counter - data->decay_raw; + if(tmp > data->env_counter){ // underflow + tmp = 0; //bottom out } - } - if(idle) continue; - - if(!(i%(1<<ENV_ADSR_UNDERSAMPLING))){ - uint32_t tmp; - switch(data->env_phase){ - case ENV_ADSR_PHASE_OFF: - data->env_counter = 0;; - break; - case ENV_ADSR_PHASE_ATTACK: - tmp = data->env_counter + data->attack_raw; - if(tmp < data->env_counter){ // overflow - tmp = ~((uint32_t) 0); // max out - data->env_phase = ENV_ADSR_PHASE_DECAY; - } - data->env_counter = tmp; - break; - case ENV_ADSR_PHASE_DECAY: - tmp = data->env_counter - data->decay_raw; - if(tmp > data->env_counter){ // underflow - tmp = 0; //bottom out - } - - if(tmp <= data->sustain){ - tmp = data->sustain; - data->env_phase = ENV_ADSR_PHASE_SUSTAIN; - } - - data->env_counter = tmp; - - break; - case ENV_ADSR_PHASE_SUSTAIN: - if(data->sustain == 0) data->env_phase = ENV_ADSR_PHASE_OFF; - data->env_counter = data->sustain; - break; - case ENV_ADSR_PHASE_RELEASE: - tmp = data->env_counter - data->release_raw; - if(tmp > data->env_counter){ // underflow - tmp = 0; //bottom out - data->env_phase = ENV_ADSR_PHASE_OFF; - } - data->env_counter = tmp; - break; + if(tmp <= data->sustain){ + tmp = data->sustain; + data->env_phase = ENV_ADSR_PHASE_SUSTAIN; } - if(data->env_counter != data->env_counter_prev){ - int32_t tmp; - tmp = data->env_counter >> 17; - tmp = (tmp * (tmp + 1)) >> 11; - data->env_pre_gain = tmp; - data->env_counter_prev = data->env_counter; + + data->env_counter = tmp; + + break; + case ENV_ADSR_PHASE_SUSTAIN: + update_sustain_coeffs(env_adsr, num_samples, render_pass_id); + if(data->sustain == 0) data->env_phase = ENV_ADSR_PHASE_OFF; + data->env_counter = data->sustain; + break; + case ENV_ADSR_PHASE_RELEASE: + update_release_coeffs(env_adsr, num_samples, render_pass_id); + tmp = data->env_counter - data->release_raw; + if(tmp > data->env_counter){ // underflow + tmp = 0; //bottom out + data->env_phase = ENV_ADSR_PHASE_OFF; } - env = (data->env_pre_gain * gain) >> 16; - env = ((uint64_t) env * data->velocity) >> 32; - } - if(env){ - int16_t input = radspa_signal_get_value(data->input_sig, i, render_pass_id); - ret = radspa_mult_shift(env, input); + data->env_counter = tmp; + break; + } + data->num_samples_prev = num_samples; + + int32_t gain = radspa_signal_get_value(&env_adsr->signals[ENV_ADSR_GAIN], 0, render_pass_id); + + if((data->env_phase == ENV_ADSR_PHASE_OFF) || (!gain)){ + data->env_prev = 0; + radspa_signal_set_const_value(&env_adsr->signals[ENV_ADSR_OUTPUT], 0); + radspa_signal_set_const_value(&env_adsr->signals[ENV_ADSR_ENV_OUTPUT], 0); + return; + } + + int32_t env = data->env_counter >> 17; + env = (env * data->velocity) >> 15; + env = (env * gain) >> 15; + radspa_signal_set_const_value(&env_adsr->signals[ENV_ADSR_ENV_OUTPUT], env); + + int16_t ret = radspa_signal_get_const_value(&env_adsr->signals[ENV_ADSR_INPUT], render_pass_id); + if(ret == RADSPA_SIGNAL_NONCONST){ + int32_t env_slope = ((env - data->env_prev) << 15) / num_samples; + for(uint16_t i = 0; i < num_samples; i++){ + ret = radspa_signal_get_value(&env_adsr->signals[ENV_ADSR_INPUT], i, render_pass_id); + int32_t env_inter = data->env_prev + ((env_slope * i)>>15); + ret = (ret * env_inter) >> 12; + radspa_signal_set_value(&env_adsr->signals[ENV_ADSR_OUTPUT], i, ret); } - radspa_signal_set_value(data->output_sig, i, ret); + } else { + ret = (ret * env) >> 12; + radspa_signal_set_const_value(&env_adsr->signals[ENV_ADSR_OUTPUT], ret); } + data->env_prev = env; +} - if(idle) radspa_signal_set_const_value(data->output_sig, 0); +radspa_t * env_adsr_create(uint32_t init_var){ + radspa_t * env_adsr = radspa_standard_plugin_create(&env_adsr_desc, ENV_ADSR_NUM_SIGNALS, sizeof(env_adsr_data_t), 0); + env_adsr->render = env_adsr_run; + radspa_signal_set(env_adsr, ENV_ADSR_OUTPUT, "output", RADSPA_SIGNAL_HINT_OUTPUT, 0); + radspa_signal_set(env_adsr, ENV_ADSR_INPUT, "input", RADSPA_SIGNAL_HINT_INPUT, 32767); + radspa_signal_set(env_adsr, ENV_ADSR_ENV_OUTPUT, "env_output", RADSPA_SIGNAL_HINT_OUTPUT | RADSPA_SIGNAL_HINT_GAIN, 0); + radspa_signal_set(env_adsr, ENV_ADSR_TRIGGER, "trigger", RADSPA_SIGNAL_HINT_INPUT | RADSPA_SIGNAL_HINT_TRIGGER, 0); + radspa_signal_set(env_adsr, ENV_ADSR_ATTACK, "attack", RADSPA_SIGNAL_HINT_INPUT, 100); + radspa_signal_set(env_adsr, ENV_ADSR_DECAY, "decay", RADSPA_SIGNAL_HINT_INPUT, 250); + radspa_signal_set(env_adsr, ENV_ADSR_SUSTAIN, "sustain", RADSPA_SIGNAL_HINT_INPUT, 16000); + radspa_signal_set(env_adsr, ENV_ADSR_RELEASE, "release", RADSPA_SIGNAL_HINT_INPUT, 50); + radspa_signal_set(env_adsr, ENV_ADSR_GAIN, "gain", RADSPA_SIGNAL_HINT_INPUT | RADSPA_SIGNAL_HINT_GAIN, RADSPA_SIGNAL_VAL_UNITY_GAIN); + radspa_signal_get_by_index(env_adsr, ENV_ADSR_ATTACK)->unit = "ms"; + radspa_signal_get_by_index(env_adsr, ENV_ADSR_DECAY)->unit = "ms"; + radspa_signal_get_by_index(env_adsr, ENV_ADSR_SUSTAIN)->unit = "ms"; + + env_adsr_data_t * data = env_adsr->plugin_data; + data->trigger_prev = 0; + data->env_phase = ENV_ADSR_PHASE_OFF; + data->release_prev_ms = -1; + data->release_init_val_prev = -1; + data->attack_prev_ms = -1; + data->sustain_prev = -1; + data->decay_prev_ms = -1; + data->env_prev = 0; + + return env_adsr; } + diff --git a/components/bl00mbox/radspa/standard_plugin_lib/env_adsr.h b/components/bl00mbox/radspa/standard_plugin_lib/env_adsr.h index 5282242ab95ceeb96597485a03d9cd21519a0d4b..07e166130e6074c8b3bdbd8f4b3dd8bce295df36 100644 --- a/components/bl00mbox/radspa/standard_plugin_lib/env_adsr.h +++ b/components/bl00mbox/radspa/standard_plugin_lib/env_adsr.h @@ -4,8 +4,11 @@ typedef struct { uint32_t env_counter; - uint32_t env_counter_prev; - int32_t env_pre_gain; + uint32_t velocity; + int16_t trigger_prev; + uint16_t num_samples_prev; + int32_t env_prev; + uint8_t env_phase; int16_t attack_prev_ms; uint32_t attack_raw; int16_t decay_prev_ms; @@ -16,17 +19,6 @@ typedef struct { uint32_t release_raw; uint32_t release_init_val; uint32_t release_init_val_prev; - uint32_t velocity; - int16_t trigger_prev; - uint8_t env_phase; - radspa_signal_t * output_sig; - radspa_signal_t * input_sig; - radspa_signal_t * trigger_sig; - radspa_signal_t * attack_sig; - radspa_signal_t * decay_sig; - radspa_signal_t * sustain_sig; - radspa_signal_t * release_sig; - radspa_signal_t * gain_sig; } env_adsr_data_t; extern radspa_descriptor_t env_adsr_desc; diff --git a/components/bl00mbox/radspa/standard_plugin_lib/filter.c b/components/bl00mbox/radspa/standard_plugin_lib/filter.c new file mode 100644 index 0000000000000000000000000000000000000000..7096e527867cb6511884c33a067471e248febf70 --- /dev/null +++ b/components/bl00mbox/radspa/standard_plugin_lib/filter.c @@ -0,0 +1,248 @@ +#include "filter.h" + +radspa_t * filter_create(uint32_t init_var); +radspa_descriptor_t filter_desc = { + .name = "filter", + .id = 69420, + .description = "biquad filter. use negative q for allpass variations.", + .create_plugin_instance = filter_create, + .destroy_plugin_instance = radspa_standard_plugin_destroy +}; + +#define FILTER_NUM_SIGNALS 7 +#define FILTER_OUTPUT 0 +#define FILTER_INPUT 1 +#define FILTER_PITCH 2 +#define FILTER_RESO 3 +#define FILTER_GAIN 4 +#define FILTER_MIX 5 +#define FILTER_MODE 6 + +#define FILTER_MODE_LOWPASS 0 +#define FILTER_MODE_BANDPASS 1 +#define FILTER_MODE_HIGHPASS 2 + +static inline int32_t approx_cos(uint32_t x){ + // x: full circle: 1<<32 + // return val: 1<<30 <=> 1 + uint32_t sq = x & (~(1UL<<31)); + if(sq > (1UL<<30)) sq = (1UL<<31) - sq; + sq = ((uint64_t) sq * sq) >> 32; + + int32_t ret = (((1<<28) - (uint64_t) sq)<<32) / (((1UL<<30) + sq)); + if((x > (1UL<<30)) && (x < (3UL<<30))) ret = -ret; + return ret; +} + +static inline void get_mode_coeffs(uint8_t mode, filter_data_t * data, int32_t * coeffs){ + switch(mode){ + case FILTER_MODE_LOWPASS: + coeffs[1] = ((1L<<21) - data->cos_omega) * data->inv_norm; + coeffs[0] = coeffs[1]/2; + coeffs[2] = coeffs[0]; + break; + case FILTER_MODE_BANDPASS: + coeffs[0] = data->alpha * data->inv_norm; + coeffs[1] = 0; + coeffs[2] = -coeffs[0]; + break; + case FILTER_MODE_HIGHPASS: + coeffs[1] = -((1L<<21) + data->cos_omega) * data->inv_norm; + coeffs[0] = -coeffs[1]/2; + coeffs[2] = coeffs[0]; + break; + } +} + +static inline void get_coeffs(filter_data_t * data, int16_t pitch, int16_t reso, int16_t mode){ + if((pitch != data->pitch_prev) || (reso != data->reso_prev)){ + int32_t mqi = reso>>2; + if(mqi < 0) mqi = -mqi; + if(mqi < 171) mqi = 171; + + uint32_t freq = radspa_sct_to_rel_freq(pitch, 0); + if(freq > (3UL<<29)) freq = 3UL<<29; + + // unit: 1<<21 <=> 1, range: [0..1<<21] + data->cos_omega = approx_cos(freq)>>9; + // unit: 1<<21 <=> 1, range: [0..3<<21] + data->alpha = approx_cos(freq + (3UL<<30))/mqi; + // unit transform from 1<<21 to 1<<29 <=> 1 + data->inv_norm = (1L<<29)/((1L<<21) + data->alpha); + + data->pitch_prev = pitch; + data->reso_prev = reso; + } + if((pitch != data->pitch_prev) || (reso != data->reso_prev) || (mode != data->mode_prev)){ + // unit for {in/out}_coeffs: 1<<29 <=> 1, range: full + data->out_coeffs[1] = -2*data->cos_omega * data->inv_norm; + data->out_coeffs[2] = ((1<<21) - data->alpha) * data->inv_norm; + if(mode == -32767){ + get_mode_coeffs(FILTER_MODE_LOWPASS, data, data->in_coeffs); + return; + } else if(mode == 0){ + get_mode_coeffs(FILTER_MODE_BANDPASS, data, data->in_coeffs); + return; + } else if(mode == 32767){ + get_mode_coeffs(FILTER_MODE_HIGHPASS, data, data->in_coeffs); + return; + } + int32_t a[3]; + int32_t b[3]; + int32_t blend = mode; + if(mode < 0){ + get_mode_coeffs(FILTER_MODE_LOWPASS, data, a); + get_mode_coeffs(FILTER_MODE_BANDPASS, data, b); + blend += 32767; + } else { + get_mode_coeffs(FILTER_MODE_BANDPASS, data, a); + get_mode_coeffs(FILTER_MODE_HIGHPASS, data, b); + } + blend = blend << 16; + for(uint8_t i = 0; i<3; i++){ + data->in_coeffs[i] = ((int64_t) b[i] * blend) >> 32; + data->in_coeffs[i] += ((int64_t) a[i] * ((1L<<31) - blend)) >> 32; + data->in_coeffs[i] = data->in_coeffs[i] << 1; + } + data->const_output = RADSPA_SIGNAL_NONCONST; + } +} + +void filter_run(radspa_t * filter, uint16_t num_samples, uint32_t render_pass_id){ + filter_data_t * data = filter->plugin_data; + + radspa_signal_t * output_sig = radspa_signal_get_by_index(filter, FILTER_OUTPUT); + radspa_signal_t * mode_sig = radspa_signal_get_by_index(filter, FILTER_MODE); + radspa_signal_t * input_sig = radspa_signal_get_by_index(filter, FILTER_INPUT); + radspa_signal_t * pitch_sig = radspa_signal_get_by_index(filter, FILTER_PITCH); + radspa_signal_t * gain_sig = radspa_signal_get_by_index(filter, FILTER_GAIN); + radspa_signal_t * reso_sig = radspa_signal_get_by_index(filter, FILTER_RESO); + radspa_signal_t * mix_sig = radspa_signal_get_by_index(filter, FILTER_MIX); + + int16_t input_const = radspa_signal_get_const_value(input_sig, render_pass_id); + int16_t pitch_const = radspa_signal_get_const_value(pitch_sig, render_pass_id); + int16_t gain_const = radspa_signal_get_const_value(gain_sig, render_pass_id); + int16_t reso_const = radspa_signal_get_const_value(reso_sig, render_pass_id); + int16_t mode_const = radspa_signal_get_const_value(mode_sig, render_pass_id); + int16_t mix_const = radspa_signal_get_const_value(mix_sig, render_pass_id); + + int16_t pitch = pitch_const; + int16_t gain = gain_const; + int16_t reso = reso_const; + int16_t mode = mode_const; + int16_t mix = mix_const; + int16_t input = input_const; + bool always_update_coeffs = true; + + if((pitch_const != RADSPA_SIGNAL_NONCONST) && (reso_const != RADSPA_SIGNAL_NONCONST) && (mode_const != RADSPA_SIGNAL_NONCONST)){ + always_update_coeffs = false; + get_coeffs(data, pitch, reso, mode); + } + if((input_const != RADSPA_SIGNAL_NONCONST) && (mix_const != RADSPA_SIGNAL_NONCONST) && (gain_const != RADSPA_SIGNAL_NONCONST) + && (data->const_output != RADSPA_SIGNAL_NONCONST)){ + radspa_signal_set_const_value(output_sig, data->const_output); + return; + } + + int16_t out[num_samples]; + + for(uint16_t i = 0; i < num_samples; i++){ + if(!(i & (RADSPA_EVENT_MASK))){ + if(always_update_coeffs){ + if(pitch_const == RADSPA_SIGNAL_NONCONST) pitch = radspa_signal_get_value(pitch_sig, i, render_pass_id); + if(reso_const == RADSPA_SIGNAL_NONCONST) reso = radspa_signal_get_value(reso_sig, i, render_pass_id); + if(mode_const == RADSPA_SIGNAL_NONCONST) mode = radspa_signal_get_value(mode_sig, i, render_pass_id); + get_coeffs(data, pitch, reso, mode); + } + if(gain_const == RADSPA_SIGNAL_NONCONST) gain = radspa_signal_get_value(gain_sig, i, render_pass_id); + if(mix_const == RADSPA_SIGNAL_NONCONST) mix = radspa_signal_get_value(mix_sig, i, render_pass_id); + } + + if(input_const == RADSPA_SIGNAL_NONCONST) input = radspa_signal_get_value(input_sig, i, render_pass_id); + int32_t in = radspa_clip(radspa_gain(input, gain)); + + data->pos++; + if(data->pos >= 3) data->pos = 0; + + data->in_history[data->pos] = in << 12; + + int32_t ret = ((int64_t) data->in_coeffs[0] * data->in_history[data->pos]) >> 32; + + for(int8_t i = 1; i<3; i++){ + int8_t pos = data->pos - i; + if(pos < 0) pos += 3; + ret += ((int64_t) data->in_history[pos] * data->in_coeffs[i]) >> 32; + ret -= ((int64_t) data->out_history[pos] * data->out_coeffs[i]) >> 32; + } + if(ret >= (1L<<28)){ + ret = (1L<<28) - 1; + } else if(ret <= -(1L<<28)){ + ret = 1-(1L<<28); + } + ret = ret << 3; + data->out_history[data->pos] = ret; + + ret = ret >> 12; + + if(reso < 0) ret = input - (ret<<1); //allpass mode + if(mix == -32767){ + ret = -ret; + } else if (mix == 0){ + ret = input; + } else if(mix != 32767){ + ret *= mix; + int32_t dry_vol = mix > 0 ? 32767 - mix : 32767 + mix; + ret += radspa_gain(dry_vol, gain) * input; + ret = ret >> 15; + } + out[i] = radspa_clip(ret); + } + + if(input_const == RADSPA_SIGNAL_NONCONST){ + for(uint16_t i = 0; i < num_samples; i++){ + radspa_signal_set_value(output_sig, i, out[i]); + } + } else if(data->const_output == RADSPA_SIGNAL_NONCONST){ + for(uint16_t i = 0; i < num_samples; i++){ + radspa_signal_set_value_check_const(output_sig, i, out[i]); + } + data->const_output = radspa_signal_set_value_check_const_result(output_sig); + } else { + radspa_signal_set_const_value(output_sig, data->const_output); + } +} + +radspa_t * filter_create(uint32_t real_init_var){ + radspa_t * filter = radspa_standard_plugin_create(&filter_desc, FILTER_NUM_SIGNALS, sizeof(filter_data_t), 0); + if(filter == NULL) return NULL; + filter->render = filter_run; + filter_data_t * data = filter->plugin_data; + radspa_signal_set(filter, FILTER_OUTPUT, "output", RADSPA_SIGNAL_HINT_OUTPUT, 0); + radspa_signal_set(filter, FILTER_INPUT, "input", RADSPA_SIGNAL_HINT_INPUT, 0); + radspa_signal_set(filter, FILTER_PITCH, "cutoff", RADSPA_SIGNAL_HINT_INPUT | RADSPA_SIGNAL_HINT_SCT, + RADSPA_SIGNAL_VAL_SCT_A440); + radspa_signal_set(filter, FILTER_RESO, "reso", RADSPA_SIGNAL_HINT_INPUT, RADSPA_SIGNAL_VAL_UNITY_GAIN); + radspa_signal_set(filter, FILTER_GAIN, "gain", RADSPA_SIGNAL_HINT_INPUT | RADSPA_SIGNAL_HINT_GAIN, + RADSPA_SIGNAL_VAL_UNITY_GAIN); + radspa_signal_set(filter, FILTER_MIX, "mix", RADSPA_SIGNAL_HINT_INPUT, 32767); + radspa_signal_set(filter, FILTER_MODE, "mode", RADSPA_SIGNAL_HINT_INPUT, -32767); + + radspa_signal_t * sig; + sig = radspa_signal_get_by_index(filter, FILTER_MODE); + sig->unit = "{LOWPASS:-32767} {BANDPASS:0} {HIGHPASS:32767}"; + sig = radspa_signal_get_by_index(filter, FILTER_RESO); + sig->unit = "4096*Q"; + + data->pos = 0; + data->pitch_prev = RADSPA_SIGNAL_NONCONST; + data->mode_prev = RADSPA_SIGNAL_NONCONST; + data->reso_prev = RADSPA_SIGNAL_NONCONST; + + data->const_output = RADSPA_SIGNAL_NONCONST; + for(uint8_t i = 0; i < 3;i++){ + data->in_history[i] = 0; + data->out_history[i] = 0; + } + get_coeffs(data, RADSPA_SIGNAL_VAL_SCT_A440, RADSPA_SIGNAL_VAL_UNITY_GAIN, -32767); + return filter; +} diff --git a/components/bl00mbox/radspa/standard_plugin_lib/filter.h b/components/bl00mbox/radspa/standard_plugin_lib/filter.h new file mode 100644 index 0000000000000000000000000000000000000000..c8a2bd1943a1467b9ef33730f1317a1321e01b1c --- /dev/null +++ b/components/bl00mbox/radspa/standard_plugin_lib/filter.h @@ -0,0 +1,24 @@ +#pragma once +#include <radspa.h> +#include <radspa_helpers.h> + +typedef struct { + int32_t in_coeffs[3]; + int32_t out_coeffs[3]; // 0th entry is a dummy and never used, just for comfy indexing + int32_t in_history[3]; + int32_t out_history[3]; + int8_t pos; + + int16_t pitch_prev; + int16_t reso_prev; + int16_t mode_prev; + int16_t const_output; + + int32_t cos_omega; + int32_t alpha; + int32_t inv_norm; +} filter_data_t; + +extern radspa_descriptor_t filter_desc; +radspa_t * filter_create(uint32_t init_var); +void filter_run(radspa_t * osc, uint16_t num_samples, uint32_t render_pass_id); diff --git a/components/bl00mbox/radspa/standard_plugin_lib/flanger.c b/components/bl00mbox/radspa/standard_plugin_lib/flanger.c index 8d58efacfe254e35a106a6a6e8724c21d3e07e7b..44b21543f3bccc32a0b7d3e697dae99efda0b75c 100644 --- a/components/bl00mbox/radspa/standard_plugin_lib/flanger.c +++ b/components/bl00mbox/radspa/standard_plugin_lib/flanger.c @@ -4,8 +4,7 @@ radspa_t * flanger_create(uint32_t init_var); radspa_descriptor_t flanger_desc = { .name = "flanger", .id = 123, - .description = "flanger with subsample interpolation and negative mix/resonance capability.\ - does not come with lfo.", + .description = "flanger with subsample interpolation and negative mix/resonance capability.", .create_plugin_instance = flanger_create, .destroy_plugin_instance = radspa_standard_plugin_destroy }; @@ -14,15 +13,16 @@ radspa_descriptor_t flanger_desc = { #define FIXED_POINT_DIGITS 4 #define VARIABLE_NAME ((FLANGER_BUFFER_SIZE)<<(FIXED_POINT_DIGITS)) -#define FLANGER_NUM_SIGNALS 6 +#define FLANGER_NUM_SIGNALS 7 #define FLANGER_OUTPUT 0 #define FLANGER_INPUT 1 #define FLANGER_MANUAL 2 #define FLANGER_RESONANCE 3 -#define FLANGER_LEVEL 4 -#define FLANGER_MIX 5 +#define FLANGER_DECAY 4 +#define FLANGER_LEVEL 5 +#define FLANGER_MIX 6 -static int16_t fixed_point_list_access(int32_t * buf, uint32_t fp_index, uint32_t buf_len){ +static inline int16_t fixed_point_list_access(int32_t * buf, uint32_t fp_index, uint32_t buf_len){ uint32_t index = (fp_index) >> (FIXED_POINT_DIGITS); while(index >= buf_len) index -= buf_len; uint32_t next_index = index + 1; @@ -35,29 +35,66 @@ static int16_t fixed_point_list_access(int32_t * buf, uint32_t fp_index, uint32_ return radspa_clip(ret); } +/* delay_time = 1/freq + * delay_samples = delay_time * SAMPLE_RATE + * freq(sct) = pow(2, (sct + 2708)/2400)) + * freq = sct_to_rel_freq(sct) * SAMPLE_RATE / UINT32_MAX + * delay_samples = UINT32_MAX / sct_to_rel_freq(sct) + * delay_samples = sct_to_rel_freq(-7572-sct + 2400 * FIXED_POINT_DIGITS) + * + * decay_reso_float = 2**(-delay_time_ms/decay_ms_per_6dB) + * rho = (delay_time_ms * 48) * (1<<FIXED_POINT_DIGITS) + * delay_time_ms * 2400 = (rho >> FIXED_POINT_DIGITS) * 50 + * delay_reso_int = sct_to_rel_freq(offset - (delay_time_ms * 2400)/decay_ms_per_6dB) + */ + void flanger_run(radspa_t * flanger, uint16_t num_samples, uint32_t render_pass_id){ radspa_signal_t * output_sig = radspa_signal_get_by_index(flanger, FLANGER_OUTPUT); if(output_sig->buffer == NULL) return; flanger_data_t * data = flanger->plugin_data; - int32_t * buf = flanger->plugin_table; + int32_t * buf = (int32_t *) flanger->plugin_table; radspa_signal_t * input_sig = radspa_signal_get_by_index(flanger, FLANGER_INPUT); radspa_signal_t * manual_sig = radspa_signal_get_by_index(flanger, FLANGER_MANUAL); radspa_signal_t * reso_sig = radspa_signal_get_by_index(flanger, FLANGER_RESONANCE); + radspa_signal_t * decay_sig = radspa_signal_get_by_index(flanger, FLANGER_DECAY); radspa_signal_t * level_sig = radspa_signal_get_by_index(flanger, FLANGER_LEVEL); radspa_signal_t * mix_sig = radspa_signal_get_by_index(flanger, FLANGER_MIX); - static int16_t ret = 0; + int32_t reso = radspa_signal_get_value(reso_sig, 0, render_pass_id); + reso = reso << 16; + int32_t level = radspa_signal_get_value(level_sig, 0, render_pass_id); + int32_t mix = radspa_signal_get_value(mix_sig, 0, render_pass_id); + int32_t decay = radspa_signal_get_value(decay_sig, 0, render_pass_id); + int32_t dry_vol = (mix>0) ? (32767-mix) : (32767+mix); //always pos polarity - for(uint16_t i = 0; i < num_samples; i++){ - int32_t manual = radspa_signal_get_value(manual_sig, i, render_pass_id); - if(manual != data->manual_prev){ - // index propto 1/radspa_sct_to_rel_freq(manual) -> signflip faster - int32_t invert = ((2400*(FIXED_POINT_DIGITS)) - 7572) - manual; - uint32_t rho = radspa_sct_to_rel_freq(radspa_clip(invert), 0); - if(rho > VARIABLE_NAME) rho = VARIABLE_NAME; - data->read_head_offset = rho; + int32_t manual = radspa_signal_get_value(manual_sig, 0, render_pass_id); + if(manual != data->manual_prev){ + int32_t manual_invert = ((2400*(FIXED_POINT_DIGITS)) - 7572) - manual; // magic numbers + uint32_t rho = radspa_sct_to_rel_freq(radspa_clip(manual_invert), 0); + if(rho > VARIABLE_NAME) rho = VARIABLE_NAME; + data->read_head_offset = rho; + } + if(decay){ + int32_t sgn_decay = decay > 0 ? 1 : -1; + int32_t abs_decay = decay * sgn_decay; + if((abs_decay != data->abs_decay_prev) || (manual != data->manual_prev)){ + int32_t decay_invert = - ((data->read_head_offset * 50) >> FIXED_POINT_DIGITS)/decay; + decay_invert += 34614; // magic number + data->decay_reso = radspa_sct_to_rel_freq(radspa_clip(decay_invert), 0) >> 1; } - data->manual_prev = manual; + int32_t tmp = reso + sgn_decay * data->decay_reso; + if((sgn_decay == 1) && (tmp < reso)){ + tmp = INT32_MAX; + } else if((sgn_decay == -1) && (tmp > reso)){ + tmp = INT32_MIN; + } + reso = tmp; + data->abs_decay_prev = abs_decay; + } + data->manual_prev = manual; + + for(uint16_t i = 0; i < num_samples; i++){ + int32_t dry = radspa_signal_get_value(input_sig, i, render_pass_id); data->write_head_position++; while(data->write_head_position >= FLANGER_BUFFER_SIZE) data->write_head_position -= FLANGER_BUFFER_SIZE; @@ -65,18 +102,11 @@ void flanger_run(radspa_t * flanger, uint16_t num_samples, uint32_t render_pass_ data->read_head_position -= data->read_head_offset; while(data->read_head_position < 0) data->read_head_position += VARIABLE_NAME; //underflow - int32_t dry = radspa_signal_get_value(input_sig, i, render_pass_id); - int16_t reso = radspa_signal_get_value(reso_sig, i, render_pass_id); - int16_t level = radspa_signal_get_value(level_sig, i, render_pass_id); - int16_t mix = radspa_signal_get_value(mix_sig, i, render_pass_id); - buf[data->write_head_position] = dry; - int32_t wet = fixed_point_list_access(buf, data->read_head_position, FLANGER_BUFFER_SIZE); - buf[data->write_head_position] += radspa_mult_shift(wet, reso); - - int16_t dry_vol = (mix>0) ? (32767-mix) : (32767+mix); //always pos polarity + int32_t wet = fixed_point_list_access(buf, data->read_head_position, FLANGER_BUFFER_SIZE) << 1; + buf[data->write_head_position] += ((int64_t) wet * reso) >> 32; - ret = radspa_add_sat(radspa_mult_shift(dry, dry_vol), radspa_mult_shift(radspa_clip(wet), mix)); + int32_t ret = radspa_add_sat(radspa_mult_shift(dry, dry_vol), radspa_mult_shift(radspa_clip(wet), mix)); ret = radspa_clip(radspa_gain(ret, level)); radspa_signal_set_value(output_sig, i, ret); } @@ -92,29 +122,11 @@ radspa_t * flanger_create(uint32_t init_var){ radspa_signal_set(flanger, FLANGER_OUTPUT, "output", RADSPA_SIGNAL_HINT_OUTPUT, 0); radspa_signal_set(flanger, FLANGER_INPUT, "input", RADSPA_SIGNAL_HINT_INPUT, 0); radspa_signal_set(flanger, FLANGER_MANUAL, "manual", RADSPA_SIGNAL_HINT_INPUT | RADSPA_SIGNAL_HINT_SCT, 18367); - radspa_signal_set(flanger, FLANGER_RESONANCE, "resonance", RADSPA_SIGNAL_HINT_INPUT | RADSPA_SIGNAL_HINT_GAIN, - RADSPA_SIGNAL_VAL_UNITY_GAIN/2); + radspa_signal_set(flanger, FLANGER_RESONANCE, "resonance", RADSPA_SIGNAL_HINT_INPUT, RADSPA_SIGNAL_VAL_UNITY_GAIN/2); + radspa_signal_set(flanger, FLANGER_DECAY, "decay", RADSPA_SIGNAL_HINT_INPUT, 0); radspa_signal_set(flanger, FLANGER_LEVEL, "level", RADSPA_SIGNAL_HINT_INPUT | RADSPA_SIGNAL_HINT_GAIN, RADSPA_SIGNAL_VAL_UNITY_GAIN); radspa_signal_set(flanger, FLANGER_MIX, "mix", RADSPA_SIGNAL_HINT_INPUT, 1<<14); + flanger->signals[FLANGER_DECAY].unit = "ms/6dB"; return flanger; } - -#undef FLANGER_DLY_GAIN -#undef FLANGER_BUFFER_SIZE -#undef FLANGER_NUM_SIGNALS -#undef FLANGER_OUTPUT -#undef FLANGER_INPUT -#undef FLANGER_MANUAL -#undef FLANGER_RESONANCE -#undef FLANGER_LEVEL -#undef FLANGER_MIX -#undef FIXED_POINT_DIGITS - -/* delay_time = 1/freq - * delay_samples = delay_time * SAMPLE_RATE - * freq(sct) = pow(2, (sct + 2708)/2400)) - * freq = sct_to_rel_freq(sct) * SAMPLE_RATE / UINT32_MAX - * delay_samples = UINT32_MAX / sct_to_rel_freq(sct) - * delay_samples = sct_to_rel_freq(-7572-sct + 2400 * FIXED_POINT_DIGITS) - */ diff --git a/components/bl00mbox/radspa/standard_plugin_lib/flanger.h b/components/bl00mbox/radspa/standard_plugin_lib/flanger.h index 62be7e19cb4c46c62df9641e6aab1ed83d0866ad..717037a0baa569090818a0aa460f40214c43e886 100644 --- a/components/bl00mbox/radspa/standard_plugin_lib/flanger.h +++ b/components/bl00mbox/radspa/standard_plugin_lib/flanger.h @@ -7,6 +7,8 @@ typedef struct { int32_t read_head_position; int32_t read_head_offset; int32_t manual_prev; + int32_t decay_reso; + int16_t abs_decay_prev; } flanger_data_t; extern radspa_descriptor_t flanger_desc; diff --git a/components/bl00mbox/radspa/standard_plugin_lib/lowpass.c b/components/bl00mbox/radspa/standard_plugin_lib/lowpass.c index 906d3ce0adea38e3a26513ac7808d2f9dba5ed7f..d59421b2584cf609178e742c9f493d3c454c0b97 100644 --- a/components/bl00mbox/radspa/standard_plugin_lib/lowpass.c +++ b/components/bl00mbox/radspa/standard_plugin_lib/lowpass.c @@ -3,8 +3,8 @@ radspa_t * lowpass_create(uint32_t init_var); radspa_descriptor_t lowpass_desc = { .name = "lowpass", - .id = 69420, - .description = "2nd order lowpass lowpass, runs rly sluggish rn, sowy", + .id = 694202, + .description = "[DEPRECATED, replacement: filter]", .create_plugin_instance = lowpass_create, .destroy_plugin_instance = radspa_standard_plugin_destroy }; diff --git a/components/bl00mbox/radspa/standard_plugin_lib/mixer.c b/components/bl00mbox/radspa/standard_plugin_lib/mixer.c index 7eb81450b42777123fa5f6c018b611737717d6af..1b07a261475fd9c7696bd2e44e71c87bad31b626 100644 --- a/components/bl00mbox/radspa/standard_plugin_lib/mixer.c +++ b/components/bl00mbox/radspa/standard_plugin_lib/mixer.c @@ -14,44 +14,130 @@ void mixer_run(radspa_t * mixer, uint16_t num_samples, uint32_t render_pass_id){ int32_t ret[num_samples]; bool ret_init = false; + radspa_signal_t * input_sigs[data->num_inputs]; + radspa_signal_t * input_gain_sigs[data->num_inputs]; + + for(uint8_t j = 0; j < data->num_inputs; j++){ + input_sigs[j] = radspa_signal_get_by_index(mixer, 3+2*j); + input_gain_sigs[j] = radspa_signal_get_by_index(mixer, 4+2*j); + } + for(uint8_t j = 0; j < data->num_inputs; j++){ - if(radspa_signal_get_const_value(data->input_sigs[j], render_pass_id)){ + int32_t input_gain_const = radspa_signal_get_const_value(input_gain_sigs[j], render_pass_id); + int32_t input_const = radspa_signal_get_const_value(input_sigs[j], render_pass_id); + if(!(input_const && input_gain_const)) continue; // if either is zero there's nothing to do + + if(input_gain_const == RADSPA_SIGNAL_VAL_UNITY_GAIN){ + if(ret_init){ + for(uint16_t i = 0; i < num_samples; i++){ + ret[i] += radspa_signal_get_value(input_sigs[j], i, render_pass_id); + } + } else { + for(uint16_t i = 0; i < num_samples; i++){ + ret[i] = radspa_signal_get_value(input_sigs[j], i, render_pass_id); + } + ret_init = true; + } + } else if(input_gain_const == -RADSPA_SIGNAL_VAL_UNITY_GAIN){ + if(ret_init){ + for(uint16_t i = 0; i < num_samples; i++){ + ret[i] -= radspa_signal_get_value(input_sigs[j], i, render_pass_id); + } + } else { + for(uint16_t i = 0; i < num_samples; i++){ + ret[i] = -radspa_signal_get_value(input_sigs[j], i, render_pass_id); + } + ret_init = true; + } + } else if(input_gain_const == RADSPA_SIGNAL_NONCONST){ if(ret_init){ for(uint16_t i = 0; i < num_samples; i++){ - ret[i] += radspa_signal_get_value(data->input_sigs[j], i, render_pass_id); + int32_t input_gain = radspa_signal_get_value(input_gain_sigs[j], i, render_pass_id); + ret[i] += (input_gain * radspa_signal_get_value(input_sigs[j], i, render_pass_id)) >> 12; } } else { for(uint16_t i = 0; i < num_samples; i++){ - ret[i] = radspa_signal_get_value(data->input_sigs[j], i, render_pass_id); + int32_t input_gain = radspa_signal_get_value(input_gain_sigs[j], i, render_pass_id); + ret[i] = (input_gain * radspa_signal_get_value(input_sigs[j], i, render_pass_id)) >> 12; + } + ret_init = true; + } + } else { + if(ret_init){ + for(uint16_t i = 0; i < num_samples; i++){ + ret[i] += (input_gain_const * radspa_signal_get_value(input_sigs[j], i, render_pass_id)) >> 12; + } + } else { + for(uint16_t i = 0; i < num_samples; i++){ + ret[i] = (input_gain_const * radspa_signal_get_value(input_sigs[j], i, render_pass_id)) >> 12; } ret_init = true; } } } + + radspa_signal_t * output_sig = radspa_signal_get_by_index(mixer, 0); + radspa_signal_t * gain_sig = radspa_signal_get_by_index(mixer, 1); + radspa_signal_t * block_dc_sig = radspa_signal_get_by_index(mixer, 2); + bool block_dc = radspa_signal_get_value(block_dc_sig, 0, render_pass_id) > 0; + + if(block_dc){ + if(ret_init){ + for(uint16_t i = 0; i < num_samples; i++){ + bool invert = data->dc < 0; + if(invert) data->dc = -data->dc; + data->dc = ((uint64_t) data->dc * (((1<<12) - 1)<<20)) >> 32; + if(invert) data->dc = -data->dc; + data->dc += ret[i]; + ret[i] -= (data->dc >> 12); + } + } else { + if(data->dc){ + for(uint16_t i = 0; i < num_samples; i++){ + bool invert = data->dc < 0; + if(invert) data->dc = -data->dc; + data->dc = ((uint64_t) data->dc * (((1<<12) - 1)<<20)) >> 32; + if(invert) data->dc = -data->dc; + ret[i] = -(data->dc >> 12); + ret_init = true; + } + } + } + } + if(ret_init){ - int16_t gain_const = radspa_signal_get_const_value(data->gain_sig, render_pass_id); - int16_t gain = gain_const; + int16_t gain_const = radspa_signal_get_const_value(gain_sig, render_pass_id); + int32_t gain = gain_const; + if(gain_const != RADSPA_SIGNAL_VAL_UNITY_GAIN){ + for(uint16_t i = 0; i < num_samples; i++){ + if(gain_const == RADSPA_SIGNAL_NONCONST) gain = radspa_signal_get_value(gain_sig, i, render_pass_id); + ret[i] = (ret[i] * gain) >> 12; + } + } + for(uint16_t i = 0; i < num_samples; i++){ - if(gain_const == RADSPA_SIGNAL_NONCONST) gain = radspa_signal_get_value(data->gain_sig, i, render_pass_id); - ret[i] = radspa_clip(radspa_gain(ret[i], gain)); - radspa_signal_set_value(data->output_sig, i, ret[i]); + radspa_signal_set_value_check_const(output_sig, i, ret[i]); } } else { - radspa_signal_set_const_value(data->output_sig, 0); + radspa_signal_set_const_value(output_sig, 0); } } radspa_t * mixer_create(uint32_t init_var){ if(init_var == 0) init_var = 4; if(init_var > 127) init_var = 127; - radspa_t * mixer = radspa_standard_plugin_create(&mixer_desc, 2 + init_var, sizeof(mixer_data_t) + sizeof(size_t) * init_var, 0); + radspa_t * mixer = radspa_standard_plugin_create(&mixer_desc, 3 + 2* init_var, sizeof(mixer_data_t), 0); if(mixer == NULL) return NULL; mixer->render = mixer_run; mixer_data_t * data = mixer->plugin_data; data->num_inputs = init_var; - data->output_sig = radspa_signal_set(mixer, 0, "output", RADSPA_SIGNAL_HINT_OUTPUT, 0); - data->gain_sig = radspa_signal_set(mixer, 1, "gain", RADSPA_SIGNAL_HINT_INPUT | RADSPA_SIGNAL_HINT_GAIN, RADSPA_SIGNAL_VAL_UNITY_GAIN/init_var); - radspa_signal_set_group(mixer, init_var, 1, 2, "input", RADSPA_SIGNAL_HINT_INPUT, 0); - for(uint8_t i = 0; i < init_var; i++){ data->input_sigs[i] = radspa_signal_get_by_index(mixer, 2+i); } + + radspa_signal_set(mixer, 0, "output", RADSPA_SIGNAL_HINT_OUTPUT, 0); + radspa_signal_set(mixer, 1, "gain", RADSPA_SIGNAL_HINT_INPUT | RADSPA_SIGNAL_HINT_GAIN, RADSPA_SIGNAL_VAL_UNITY_GAIN/init_var); + radspa_signal_set(mixer, 2, "block_dc", RADSPA_SIGNAL_HINT_INPUT, -32767); + radspa_signal_set_group(mixer, init_var, 2, 3, "input", RADSPA_SIGNAL_HINT_INPUT, 0); + radspa_signal_set_group(mixer, init_var, 2, 4, "input_gain", RADSPA_SIGNAL_HINT_INPUT | RADSPA_SIGNAL_HINT_GAIN, + RADSPA_SIGNAL_VAL_UNITY_GAIN); + radspa_signal_get_by_index(mixer, 2)->unit = "{OFF:-32767} {ON:32767}"; return mixer; } diff --git a/components/bl00mbox/radspa/standard_plugin_lib/mixer.h b/components/bl00mbox/radspa/standard_plugin_lib/mixer.h index 7c11b42cc93993ba53337959b82fa7d1e99fb612..7b65c5ad2021bde287ef3430666290431a2e9137 100644 --- a/components/bl00mbox/radspa/standard_plugin_lib/mixer.h +++ b/components/bl00mbox/radspa/standard_plugin_lib/mixer.h @@ -3,10 +3,8 @@ #include <radspa_helpers.h> typedef struct { + int32_t dc; uint8_t num_inputs; - radspa_signal_t * output_sig; - radspa_signal_t * gain_sig; - radspa_signal_t * input_sigs[]; } mixer_data_t; extern radspa_descriptor_t mixer_desc; diff --git a/components/bl00mbox/radspa/standard_plugin_lib/multipitch.c b/components/bl00mbox/radspa/standard_plugin_lib/multipitch.c index e2ab5935a6ae8ebb0c498e1ece75984720efd2d8..25b579426a0ef67c567a97645c85f6ecf9d63c42 100644 --- a/components/bl00mbox/radspa/standard_plugin_lib/multipitch.c +++ b/components/bl00mbox/radspa/standard_plugin_lib/multipitch.c @@ -9,51 +9,87 @@ radspa_descriptor_t multipitch_desc = { .destroy_plugin_instance = radspa_standard_plugin_destroy }; +#define NUM_SIGNALS 8 +#define INPUT 0 +#define THRU 1 +#define TRIGGER_IN 2 +#define TRIGGER_THRU 3 +#define MOD_IN 4 +#define MOD_SENS 5 +#define MAX_PITCH 6 +#define MIN_PITCH 7 + +#define NUM_MPX 2 +#define OUTPUT 8 +#define SHIFT 9 + +typedef struct { + int16_t trigger_in_prev; + int16_t trigger_thru_prev; +} multipitch_data_t; + +static inline int32_t pitch_limit(int32_t pitch, int32_t min, int32_t max){ + if(pitch > max){ + int32_t estimate = (13981*(pitch - max))>>25; + pitch -= 2400 * estimate; + while(pitch > max) pitch -= 2400; + } else if(pitch < min){ + int32_t estimate = (13891*(min - pitch))>>25; + pitch += 2400 * estimate; + while(pitch < max) pitch += 2400; + } + return pitch; +} + void multipitch_run(radspa_t * multipitch, uint16_t num_samples, uint32_t render_pass_id){ - bool output_request = false; - radspa_signal_t * thru_sig = radspa_signal_get_by_index(multipitch, 0); - if(thru_sig->buffer != NULL) output_request = true; - radspa_signal_t * input_sig = radspa_signal_get_by_index(multipitch, 1); - - uint8_t num_outputs = (multipitch->len_signals - 2)/2; - radspa_signal_t * output_sigs[num_outputs]; - radspa_signal_t * pitch_sigs[num_outputs]; - for(uint8_t j = 0; j < num_outputs; j++){ - output_sigs[j] = radspa_signal_get_by_index(multipitch, 2 + 2 * j); - pitch_sigs[j] = radspa_signal_get_by_index(multipitch, 3 + 2 * j); - if(output_sigs[j]->buffer != NULL) output_request = true; + uint8_t num_outputs = (multipitch->len_signals - (NUM_SIGNALS))/2; + multipitch_data_t * data = multipitch->plugin_data; + + uint16_t i; + int16_t trigger_in = radspa_trigger_get_const(&multipitch->signals[TRIGGER_IN], &data->trigger_in_prev, &i, num_samples, render_pass_id); + if(trigger_in > 0) radspa_trigger_start(trigger_in, &(data->trigger_thru_prev)); + if(trigger_in < 0) radspa_trigger_stop(&(data->trigger_thru_prev)); + radspa_signal_set_const_value(&multipitch->signals[TRIGGER_THRU], data->trigger_thru_prev); + + int32_t max_pitch = radspa_signal_get_value(&multipitch->signals[MAX_PITCH], 0, render_pass_id); + int32_t min_pitch = radspa_signal_get_value(&multipitch->signals[MIN_PITCH], 0, render_pass_id); + if(max_pitch < min_pitch){ + int32_t a = max_pitch; + max_pitch = min_pitch; + min_pitch = a; } - if(!output_request) return; - - for(uint16_t i = 0; i < num_samples; i++){ - int32_t ret = 0; - int32_t input = radspa_signal_get_value(input_sig, i, render_pass_id); - ret = input; - - if(thru_sig->buffer != NULL) (thru_sig->buffer)[i] = ret; - radspa_signal_set_value(thru_sig, i, ret); - - int32_t pitch; - for(uint8_t j = 0; j < num_outputs; j++){ - pitch = radspa_signal_get_value(pitch_sigs[j], i, render_pass_id); - ret = pitch + input - RADSPA_SIGNAL_VAL_SCT_A440; - radspa_signal_set_value(output_sigs[j], i, ret); - } + + int32_t input = radspa_signal_get_value(&multipitch->signals[INPUT], i, render_pass_id); + int32_t mod = radspa_signal_get_value(&multipitch->signals[MOD_IN], i, render_pass_id); + mod *= radspa_signal_get_value(&multipitch->signals[MOD_SENS], i, render_pass_id); + mod = ((int64_t) mod * 76806) >> 32; + input += mod; + + radspa_signal_set_const_value(&multipitch->signals[THRU], pitch_limit(input, min_pitch, max_pitch)); + for(uint8_t j = 0; j < num_outputs; j++){ + int32_t shift = input + radspa_signal_get_value(&multipitch->signals[(SHIFT) + (NUM_MPX)*j], i, render_pass_id) - RADSPA_SIGNAL_VAL_SCT_A440; + radspa_signal_set_const_value(&multipitch->signals[(OUTPUT) + (NUM_MPX)*j], pitch_limit(shift, min_pitch, max_pitch)); } } radspa_t * multipitch_create(uint32_t init_var){ if(init_var > 127) init_var = 127; - radspa_t * multipitch = radspa_standard_plugin_create(&multipitch_desc, 2 + 2*init_var, sizeof(int32_t), 0); + radspa_t * multipitch = radspa_standard_plugin_create(&multipitch_desc, NUM_SIGNALS + 2*init_var, sizeof(multipitch_data_t), 0); if(multipitch == NULL) return NULL; multipitch->render = multipitch_run; - radspa_signal_set(multipitch, 0, "thru", RADSPA_SIGNAL_HINT_OUTPUT | RADSPA_SIGNAL_HINT_SCT, RADSPA_SIGNAL_VAL_SCT_A440); - radspa_signal_set(multipitch, 1, "input", RADSPA_SIGNAL_HINT_INPUT | RADSPA_SIGNAL_HINT_SCT, RADSPA_SIGNAL_VAL_SCT_A440); + radspa_signal_set(multipitch, INPUT, "input", RADSPA_SIGNAL_HINT_INPUT | RADSPA_SIGNAL_HINT_SCT, RADSPA_SIGNAL_VAL_SCT_A440); + radspa_signal_set(multipitch, THRU, "thru", RADSPA_SIGNAL_HINT_OUTPUT | RADSPA_SIGNAL_HINT_SCT, RADSPA_SIGNAL_VAL_SCT_A440); + radspa_signal_set(multipitch, TRIGGER_IN, "trigger_in", RADSPA_SIGNAL_HINT_INPUT | RADSPA_SIGNAL_HINT_TRIGGER, 0); + radspa_signal_set(multipitch, TRIGGER_THRU, "trigger_thru", RADSPA_SIGNAL_HINT_OUTPUT | RADSPA_SIGNAL_HINT_TRIGGER, 0); + radspa_signal_set(multipitch, MOD_IN, "mod_in", RADSPA_SIGNAL_HINT_INPUT, 0); + radspa_signal_set(multipitch, MOD_SENS, "mod_sens", RADSPA_SIGNAL_HINT_INPUT | RADSPA_SIGNAL_HINT_GAIN, RADSPA_SIGNAL_VAL_UNITY_GAIN); + radspa_signal_set(multipitch, MAX_PITCH, "max_pitch", RADSPA_SIGNAL_HINT_INPUT | RADSPA_SIGNAL_HINT_SCT, RADSPA_SIGNAL_VAL_SCT_A440 + 2400 * 4); + radspa_signal_set(multipitch, MIN_PITCH, "min_pitch", RADSPA_SIGNAL_HINT_INPUT | RADSPA_SIGNAL_HINT_SCT, RADSPA_SIGNAL_VAL_SCT_A440 - 2400 * 4); - radspa_signal_set_group(multipitch, init_var, 2, 2, "output", RADSPA_SIGNAL_HINT_OUTPUT | RADSPA_SIGNAL_HINT_SCT, + radspa_signal_set_group(multipitch, init_var, NUM_MPX, OUTPUT, "output", RADSPA_SIGNAL_HINT_OUTPUT | RADSPA_SIGNAL_HINT_SCT, RADSPA_SIGNAL_VAL_SCT_A440); - radspa_signal_set_group(multipitch, init_var, 2, 3, "shift", RADSPA_SIGNAL_HINT_INPUT | RADSPA_SIGNAL_HINT_SCT, + radspa_signal_set_group(multipitch, init_var, NUM_MPX, SHIFT, "shift", RADSPA_SIGNAL_HINT_INPUT | RADSPA_SIGNAL_HINT_SCT, RADSPA_SIGNAL_VAL_SCT_A440); return multipitch; diff --git a/components/bl00mbox/radspa/standard_plugin_lib/noise.c b/components/bl00mbox/radspa/standard_plugin_lib/noise.c index ecd07ffa60a9c11d1c5ed7053fc72f4e9fedf9c3..3a076cc0027838b889349ccec26fbc2843855a7e 100644 --- a/components/bl00mbox/radspa/standard_plugin_lib/noise.c +++ b/components/bl00mbox/radspa/standard_plugin_lib/noise.c @@ -9,16 +9,20 @@ radspa_descriptor_t noise_desc = { .destroy_plugin_instance = radspa_standard_plugin_destroy }; -#define NOISE_NUM_SIGNALS 1 +#define NOISE_NUM_SIGNALS 2 #define NOISE_OUTPUT 0 +#define NOISE_SPEED 1 void noise_run(radspa_t * noise, uint16_t num_samples, uint32_t render_pass_id){ radspa_signal_t * output_sig = radspa_signal_get_by_index(noise, NOISE_OUTPUT); - if(output_sig->buffer == NULL) return; + radspa_signal_t * speed_sig = radspa_signal_get_by_index(noise, NOISE_SPEED); - for(uint16_t i = 0; i < num_samples; i++){ - int16_t ret = radspa_random(); - radspa_signal_set_value(output_sig, i, ret); + if(radspa_signal_get_value(speed_sig, 0, render_pass_id) < 0){ + radspa_signal_set_const_value(output_sig, radspa_random()); + } else { + for(uint16_t i = 0; i < num_samples; i++){ + radspa_signal_set_value(output_sig, i, radspa_random()); + } } } @@ -27,5 +31,7 @@ radspa_t * noise_create(uint32_t init_var){ if(noise == NULL) return NULL; noise->render = noise_run; radspa_signal_set(noise, NOISE_OUTPUT, "output", RADSPA_SIGNAL_HINT_OUTPUT, 0); + radspa_signal_set(noise, NOISE_SPEED, "speed", RADSPA_SIGNAL_HINT_INPUT, 32767); + radspa_signal_get_by_index(noise, NOISE_SPEED)->unit = "{LFO:-32767} {AUDIO:32767}"; return noise; } diff --git a/components/bl00mbox/radspa/standard_plugin_lib/noise_burst.c b/components/bl00mbox/radspa/standard_plugin_lib/noise_burst.c index 7ccc630dd154c33f469b22ba2fce8d1289a887a5..ed73a4a2d7819370e208faf486f45f64b5f245ee 100644 --- a/components/bl00mbox/radspa/standard_plugin_lib/noise_burst.c +++ b/components/bl00mbox/radspa/standard_plugin_lib/noise_burst.c @@ -20,11 +20,6 @@ radspa_t * noise_burst_create(uint32_t init_var){ radspa_signal_set(noise_burst, NOISE_BURST_TRIGGER, "trigger", RADSPA_SIGNAL_HINT_INPUT | RADSPA_SIGNAL_HINT_TRIGGER, 0); radspa_signal_set(noise_burst, NOISE_BURST_LENGTH_MS, "length", RADSPA_SIGNAL_HINT_INPUT, 100); radspa_signal_get_by_index(noise_burst, NOISE_BURST_LENGTH_MS)->unit = "ms"; - - noise_burst_data_t * data = noise_burst->plugin_data; - data->counter = 15; - data->limit = 15; - return noise_burst; } @@ -33,27 +28,62 @@ radspa_t * noise_burst_create(uint32_t init_var){ void noise_burst_run(radspa_t * noise_burst, uint16_t num_samples, uint32_t render_pass_id){ noise_burst_data_t * plugin_data = noise_burst->plugin_data; radspa_signal_t * output_sig = radspa_signal_get_by_index(noise_burst, NOISE_BURST_OUTPUT); - if(output_sig->buffer == NULL) return; radspa_signal_t * trigger_sig = radspa_signal_get_by_index(noise_burst, NOISE_BURST_TRIGGER); radspa_signal_t * length_ms_sig = radspa_signal_get_by_index(noise_burst, NOISE_BURST_LENGTH_MS); - for(uint16_t i = 0; i < num_samples; i++){ - int16_t ret = 0; + int16_t trigger = radspa_signal_get_const_value(trigger_sig, render_pass_id); + bool trigger_const = trigger != RADSPA_SIGNAL_NONCONST; + int16_t vel = radspa_trigger_get(trigger, &(plugin_data->trigger_prev)); + + bool output_const = plugin_data->counter == plugin_data->limit; - int16_t trigger = radspa_signal_get_value(trigger_sig, i, render_pass_id); - int16_t vel = radspa_trigger_get(trigger, &(plugin_data->trigger_prev)); + // using output_const as proxy if system is running (will no longer be true further down) + if((trigger_const) && ((vel < 0) || ((!vel) && output_const))){ + if(!plugin_data->hold) plugin_data->last_out = 0; + plugin_data->counter = plugin_data->limit; + radspa_signal_set_const_value(output_sig, plugin_data->last_out); + return; + } + + int16_t out = plugin_data->last_out; + + for(uint16_t i = 0; i < num_samples; i++){ + if(!(i && trigger_const)){ + if(!trigger_const){ + trigger = radspa_signal_get_value(trigger_sig, i, render_pass_id); + vel = radspa_trigger_get(trigger, &(plugin_data->trigger_prev)); + } - if(vel < 0){ // stop - plugin_data->counter = plugin_data->limit; - } else if(vel > 0 ){ // start - plugin_data->counter = 0; - int32_t length_ms = radspa_signal_get_value(length_ms_sig, i, render_pass_id); - plugin_data->limit = length_ms * 48; + if(vel < 0){ // stop + plugin_data->counter = plugin_data->limit; + } else if(vel > 0 ){ // start + if(output_const){ + output_const = false; + radspa_signal_set_values(output_sig, 0, i, out); + } + plugin_data->counter = 0; + int32_t length_ms = radspa_signal_get_value(length_ms_sig, i, render_pass_id); + if(length_ms > 0){ + plugin_data->hold = false; + plugin_data->limit = length_ms * ((SAMPLE_RATE_SORRY) / 1000); + } else { + plugin_data->hold = true; + if(length_ms){ + plugin_data->limit = -length_ms * ((SAMPLE_RATE_SORRY) / 1000); + } else { + plugin_data->limit = 1; + } + } + } } if(plugin_data->counter < plugin_data->limit){ - ret = radspa_random(); + out = radspa_random(); plugin_data->counter++; + } else if(!plugin_data->hold){ + out = 0; } - radspa_signal_set_value(output_sig, i, ret); + if(!output_const) radspa_signal_set_value(output_sig, i, out); } + if(output_const) radspa_signal_set_const_value(output_sig, out); + plugin_data->last_out = out; } diff --git a/components/bl00mbox/radspa/standard_plugin_lib/noise_burst.h b/components/bl00mbox/radspa/standard_plugin_lib/noise_burst.h index 6eded45728d6e9744416554887fec457332cdd9a..6653858f69ebf767978518fc55bd5e5d5d3c543c 100644 --- a/components/bl00mbox/radspa/standard_plugin_lib/noise_burst.h +++ b/components/bl00mbox/radspa/standard_plugin_lib/noise_burst.h @@ -6,6 +6,8 @@ typedef struct { int32_t counter; int32_t limit; int16_t trigger_prev; + int16_t last_out; + bool hold; } noise_burst_data_t; extern radspa_descriptor_t noise_burst_desc; diff --git a/components/bl00mbox/radspa/standard_plugin_lib/osc.c b/components/bl00mbox/radspa/standard_plugin_lib/osc.c new file mode 100644 index 0000000000000000000000000000000000000000..39dc244b2b33e4a691bce56f70acc9b5c1a5533a --- /dev/null +++ b/components/bl00mbox/radspa/standard_plugin_lib/osc.c @@ -0,0 +1,451 @@ +#include "osc.h" + +radspa_descriptor_t osc_desc = { + .name = "osc", + .id = 420, + .description = "simple oscillator with waveshaping, linear fm and hardsync", + .create_plugin_instance = osc_create, + .destroy_plugin_instance = radspa_standard_plugin_destroy +}; + +#define OSC_NUM_SIGNALS 9 +#define OSC_OUT 0 +#define OSC_PITCH 1 +#define OSC_WAVEFORM 2 +#define OSC_MORPH 3 +#define OSC_FM 4 +#define OSC_SYNC_IN 5 +#define OSC_SYNC_IN_PHASE 6 +#define OSC_SYNC_OUT 7 +#define OSC_SPEED 8 + +#pragma GCC push_options +#pragma GCC optimize ("-O3") + +/* The oscillator receives a lot of control signals that + * can be useful either as constant buffers or at full + * resolution. This results in a lot of if(_const) in the + * for loop, dragging down performance. We could split + * the oscillator up in seperate less general purpose + * plugins, but we wouldn't change the UI anyways so + * instead we allow ourselves to generate a bunch of + * code here by manually unswitching the for loop. + * + * If all conditionals are removed the code size explodes + * a bit too much, but we can group them into blocks so that + * if either element of a block receives a fast signal it takes + * the performance hit for all of them: + * + * pitch, morph and waveform are grouped together since they + * typically are constant and only need fast signals for ring + * modulation like effects. We call this the RINGMOD group. + * + * fm and sync_in are grouped together for similar reasons + * into the OSCMOD group. + * + * out and sync_out should be switched seperately, but we can + * ignore the option of them both being disconnected and switch + * to LFO mode in this case instead. This generates slightly + * different antialiasing but that is okay, especially since + * hosts are advised to not render a plugin at all if no + * output is connected to anything. + * + * the index for OSC_MEGASWITCH is defined as follows: + * ringmod_const + (oscmod_const<<1) + (out_const<<2) + (sync_out_const<<3); + * + * this limits the index range to [0..11], a reasonable amount + * of extra code size in our opinion given the importance of a + * fast and flexible basic oscillator building block. + */ + +#define OSC_MEGASWITCH \ + case 0: \ + for(; i < num_samples; i++){ \ + RINGMOD_READ \ + FM_READ \ + OSCILLATE \ + SYNC_IN_READ \ + OUT_WRITE \ + SYNC_OUT_WRITE \ + } \ + break; \ + case 1: \ + for(; i < num_samples; i++){ \ + FM_READ \ + OSCILLATE \ + SYNC_IN_READ \ + OUT_WRITE \ + SYNC_OUT_WRITE \ + } \ + break; \ + case 2: \ + for(; i < num_samples; i++){ \ + RINGMOD_READ \ + OSCILLATE \ + OUT_WRITE \ + SYNC_OUT_WRITE \ + } \ + break; \ + case 3: \ + for(; i < num_samples; i++){ \ + OSCILLATE \ + OUT_WRITE \ + SYNC_OUT_WRITE \ + } \ + break; \ + case 4: \ + for(; i < num_samples; i++){ \ + RINGMOD_READ \ + FM_READ \ + OSCILLATE \ + SYNC_IN_READ \ + SYNC_OUT_WRITE \ + } \ + break; \ + case 5: \ + for(; i < num_samples; i++){ \ + FM_READ \ + OSCILLATE \ + SYNC_IN_READ \ + SYNC_OUT_WRITE \ + } \ + break; \ + case 6: \ + for(; i < num_samples; i++){ \ + RINGMOD_READ \ + OSCILLATE \ + SYNC_OUT_WRITE \ + } \ + break; \ + case 7: \ + for(; i < num_samples; i++){ \ + OSCILLATE \ + SYNC_OUT_WRITE \ + } \ + break; \ + case 8: \ + for(; i < num_samples; i++){ \ + RINGMOD_READ \ + FM_READ \ + OSCILLATE \ + SYNC_IN_READ \ + OUT_WRITE \ + } \ + break; \ + case 9: \ + for(; i < num_samples; i++){ \ + FM_READ \ + OSCILLATE \ + SYNC_IN_READ \ + OUT_WRITE \ + } \ + break; \ + case 10: \ + for(; i < num_samples; i++){ \ + RINGMOD_READ \ + OSCILLATE \ + OUT_WRITE \ + } \ + break; \ + case 11: \ + for(; i < num_samples; i++){ \ + OSCILLATE \ + OUT_WRITE \ + } \ + break; \ + +/* +int16_t poly_blep_saw(int32_t input, int32_t incr){ + input += 32767; + incr = incr >> 16; + incr_rec = (1<<(16+14)) / incr; + + if (input < incr){ + input = ((input * incr_rec) >> (14-1); + input -= ((input*input)>>18); + return input - 65536; + } else if(input > 65535 - incr) { + input = input - 65535; + input = ((input * incr_rec) >> (14-1); + input += ((input*input)>>18); + return input + 65536; + } + return 0; +} +*/ + +static inline void get_ringmod_coeffs(osc_data_t * data, int16_t pitch, int32_t morph, int32_t waveform){ + int32_t morph_gate = data->morph_gate_prev; + bool morph_no_pwm = data->morph_no_pwm_prev; + if(pitch != data->pitch_prev){ + data->pitch_coeffs[0] = radspa_sct_to_rel_freq(pitch, 0); + morph_gate = 30700 - (data->pitch_coeffs[0]>>12); // "anti" "aliasing" + if(morph_gate < 0) morph_gate = 0; + data->pitch_prev = pitch; + } + if(waveform != data->waveform_prev){ + data->waveform_prev = waveform; + data->waveform_coeffs[0] = waveform + 32767; + //if(waveform_coeffs[0] == (32767*2)){ data->waveform_coeffs[1] = UINT32_MAX; return; } + data->waveform_coeffs[1] = (((uint32_t) data->waveform_coeffs[0]) * 196616); + morph_no_pwm = waveform != 10922; + } + if(morph != data->morph_prev || (morph_gate != data->morph_gate_prev)){ + if(morph > morph_gate){ + morph = morph_gate; + } else if(morph < -morph_gate){ + morph = -morph_gate; + } + data->morph_gate_prev = morph_gate; + if((morph != data->morph_prev) || (morph_no_pwm != data->morph_no_pwm_prev)){ + data->morph_prev = morph; + data->morph_no_pwm_prev = morph_no_pwm; + data->morph_coeffs[0] = morph + 32767; + if(morph && morph_no_pwm){; + data->morph_coeffs[1] = (1L<<26)/((1L<<15) + morph); + data->morph_coeffs[2] = (1L<<26)/((1L<<15) - morph); + } else { + data->morph_coeffs[1] = 0; //always valid for pwm, speeds up modulation + data->morph_coeffs[2] = 0; + } + } + } +} + +static inline int16_t triangle(int32_t saw){ + saw -= 16384; + saw += 65535 * (saw < -32767); + saw -= 2*saw * (saw > 0); + return saw * 2 + 32767; +} + +static inline int16_t fake_sine(int16_t tri){ + int16_t sign = 2 * (tri > 0) - 1; + tri *= sign; + tri = 32767 - tri; + tri = (tri*tri)>>15; + tri = 32767 - tri; + tri *= sign; + return tri; +} + +// <bad code> +// blepping is awfully slow atm but we don't have time to fix it before the next release. +// we believe it's important enough to keep it in even though it temporarily drags down +// performance. we see some low hanging fruits but we can't justify spending any more time +// on this until flow3r 1.3 is done. + +static inline int16_t saw(int32_t saw, osc_data_t * data){ + int16_t saw_sgn = saw > 0 ? 1 : -1; + int16_t saw_abs = saw * saw_sgn; + if(saw_abs > data->blep_coeffs[0]){ + int32_t reci = (1<<15) / (32767 - data->blep_coeffs[0]); + int32_t blep = (saw_abs - data->blep_coeffs[0]) * reci; + blep = (blep * blep) >> 15; + return saw - (blep * saw_sgn); + } + return saw; +} + +static inline int16_t square(int16_t saw, osc_data_t * data){ + int16_t saw_sgn = saw >= 0 ? 1 : -1; + return 32767 * saw_sgn; + int16_t saw_abs = saw * saw_sgn; + saw_abs = (saw_abs >> 14) ? saw_abs : 32767 - saw_abs; + if(saw_abs > data->blep_coeffs[0]){ + int32_t reci = (1<<15) / (32767 - data->blep_coeffs[0]); + int32_t blep = (saw_abs - data->blep_coeffs[0]) * reci; + blep = (blep * blep) >> 15; + return (32767 - blep) * saw_sgn; + } + return 32767 * saw_sgn; +} + +static inline void get_blep_coeffs(osc_data_t * data, int32_t incr){ + if(incr == data->incr_prev) return; + int32_t incr_norm = ((int64_t) incr * 65535) >> 32; // max 65534 + incr_norm = incr_norm > 0 ? incr_norm : -incr_norm; + data->blep_coeffs[0] = 32767 - incr_norm; + data->incr_prev = incr; +} +// </bad code> + +static inline int16_t apply_morph(osc_data_t * data, uint32_t counter){ + counter = counter << 1; + int32_t input = ((uint64_t) counter * 65535) >> 32; // max 65534 + if(data->morph_coeffs[0] == 32767) return input - 32767; // fastpass + + if(input < data->morph_coeffs[0]){ + input = ((input * data->morph_coeffs[1]) >> 11); + input -= 32767; + } else { + input -= data->morph_coeffs[0]; + input = ((input * data->morph_coeffs[2]) >> 11); + } + return input; +} + +static inline int16_t apply_waveform(osc_data_t * data, int16_t input, int32_t incr){ + int32_t a, b; + if(data->waveform_coeffs[0] < (32767-10922)){ + b = triangle(input); + a = fake_sine(b); + } else if(data->waveform_coeffs[0] < (32767+10922)){ + a = triangle(input); + if(data->waveform_coeffs[0] == 32767-10922) return a; + get_blep_coeffs(data, incr); + b = square(input, data); + } else if(data->waveform_coeffs[0] < (32767+32767)){ + get_blep_coeffs(data, incr); + a = square(input, data); + if(data->waveform_coeffs[0] == 32767+10922) return a; + b = saw(input, data); + } else { + get_blep_coeffs(data, incr); + return saw(input, data); + } + int32_t ret = (((int64_t) (b-a) * data->waveform_coeffs[1]) >> 32); + return ret + a; +} + +#define RINGMOD_READ { \ + if(!pitch_const) pitch = radspa_signal_get_value(pitch_sig, i, render_pass_id); \ + if(!morph_const) morph = radspa_signal_get_value(morph_sig, i, render_pass_id); \ + if(!waveform_const) waveform = radspa_signal_get_value(waveform_sig, i, render_pass_id); \ + get_ringmod_coeffs(data, pitch, morph, waveform); \ +} + +#define FM_READ { \ + if(!fm_const) fm_mult = (((int32_t)radspa_signal_get_value(fm_sig, i, render_pass_id)) << 15) + (1L<<28); \ +} + +#define OSCILLATE \ + int32_t incr = ((int64_t) data->pitch_coeffs[0] * (fm_mult)) >> 32; \ + data->counter += incr << 3; + +#define SYNC_IN_APPLY { \ + data->counter = data->counter & (1<<31); \ + data->counter += (sync_in_phase + 32767) * 32769; \ +} + +#define SYNC_IN_READ { \ + if(!sync_in_const){ \ + sync_in = radspa_signal_get_value(sync_in_sig, i, render_pass_id); \ + if(radspa_trigger_get(sync_in, &(data->sync_in)) > 0){ \ + if(!sync_in_phase_const){ \ + sync_in_phase = radspa_signal_get_value(sync_in_phase_sig, i, render_pass_id); \ + } \ + SYNC_IN_APPLY \ + }\ + }\ +} + +#define OUT_APPLY \ + int32_t out = apply_morph(data, data->counter); \ + out = apply_waveform(data, out, incr); \ + +#define OUT_WRITE { \ + OUT_APPLY \ + radspa_signal_set_value_noclip(out_sig, i, out); \ +} + +#define SYNC_OUT_WRITE { \ + radspa_signal_set_value_noclip(sync_out_sig, i, data->counter > (1UL<<31) ? 32767 : -32767); \ +} + +void osc_run(radspa_t * osc, uint16_t num_samples, uint32_t render_pass_id){ + osc_data_t * data = osc->plugin_data; + int8_t * table = (int8_t * ) osc->plugin_table; + radspa_signal_t * speed_sig = radspa_signal_get_by_index(osc, OSC_SPEED); + radspa_signal_t * out_sig = radspa_signal_get_by_index(osc, OSC_OUT); + radspa_signal_t * pitch_sig = radspa_signal_get_by_index(osc, OSC_PITCH); + radspa_signal_t * waveform_sig = radspa_signal_get_by_index(osc, OSC_WAVEFORM); + radspa_signal_t * morph_sig = radspa_signal_get_by_index(osc, OSC_MORPH); + radspa_signal_t * fm_sig = radspa_signal_get_by_index(osc, OSC_FM); + radspa_signal_t * sync_in_sig = radspa_signal_get_by_index(osc, OSC_SYNC_IN); + radspa_signal_t * sync_in_phase_sig = radspa_signal_get_by_index(osc, OSC_SYNC_IN_PHASE); + radspa_signal_t * sync_out_sig = radspa_signal_get_by_index(osc, OSC_SYNC_OUT); + + int16_t pitch = radspa_signal_get_const_value(pitch_sig, render_pass_id); + int16_t sync_in = radspa_signal_get_const_value(sync_in_sig, render_pass_id); + int32_t sync_in_phase = radspa_signal_get_const_value(sync_in_phase_sig, render_pass_id); + int16_t morph = radspa_signal_get_const_value(morph_sig, render_pass_id); + int16_t waveform = radspa_signal_get_const_value(waveform_sig, render_pass_id); + int16_t fm = radspa_signal_get_const_value(fm_sig, render_pass_id); + int32_t fm_mult = (((int32_t) fm) << 15) + (1L<<28); + + int16_t speed = radspa_signal_get_value(speed_sig, 0, render_pass_id); + + bool out_const = out_sig->buffer == NULL; + bool sync_out_const = sync_out_sig->buffer == NULL; + + bool lfo = speed < -10922; // manual setting + lfo = lfo || (out_const && sync_out_const); // unlikely, host should ideally prevent that case + + { + get_ringmod_coeffs(data, pitch, morph, waveform); + lfo = lfo || ((speed < 10922) && (data->pitch_coeffs[0] < 1789569)); // auto mode below 20Hz + + OSCILLATE + + if(lfo) data->counter += incr * (num_samples-1); + + if(radspa_trigger_get(sync_in, &(data->sync_in)) > 0){ + SYNC_IN_APPLY + } + + OUT_APPLY + + radspa_signal_set_const_value(out_sig, out); + // future blep concept for hard sync: encode subsample progress in the last 5 bit of the + // trigger signal? would result in "auto-humanize" when attached to any other consumer + // but that's fine we think. + radspa_signal_set_const_value(sync_out_sig, data->counter > (1UL<<31) ? 32767 : -32767); + table[(data->counter<<1)>>(32-6)] = out >> 8; + } + + if(!lfo){ + uint16_t i = 1; // incrementing variable for megaswitch for loop + bool waveform_const = waveform != RADSPA_SIGNAL_NONCONST; + bool morph_const = morph != RADSPA_SIGNAL_NONCONST; + bool sync_in_phase_const = sync_in_phase != RADSPA_SIGNAL_NONCONST; + bool fm_const = fm != RADSPA_SIGNAL_NONCONST; + bool pitch_const = pitch != RADSPA_SIGNAL_NONCONST; + bool sync_in_const = sync_in != RADSPA_SIGNAL_NONCONST; + + bool ringmod_const = pitch_const && morph_const && waveform_const; + bool oscmod_const = fm_const && sync_in_const; + + { // generate rest of samples with megaswitch + uint8_t fun_index = ringmod_const + (oscmod_const<<1) + (out_const<<2) + (sync_out_const<<3); + switch(fun_index){ + OSC_MEGASWITCH + } + } + } +} + +#pragma GCC pop_options + +radspa_t * osc_create(uint32_t init_var){ + radspa_t * osc = radspa_standard_plugin_create(&osc_desc, OSC_NUM_SIGNALS, sizeof(osc_data_t), 32); + osc->render = osc_run; + radspa_signal_set(osc, OSC_OUT, "output", RADSPA_SIGNAL_HINT_OUTPUT, 0); + radspa_signal_set(osc, OSC_PITCH, "pitch", RADSPA_SIGNAL_HINT_INPUT | RADSPA_SIGNAL_HINT_SCT, RADSPA_SIGNAL_VAL_SCT_A440); + radspa_signal_set(osc, OSC_WAVEFORM, "waveform", RADSPA_SIGNAL_HINT_INPUT, -10922); + radspa_signal_set(osc, OSC_MORPH, "morph", RADSPA_SIGNAL_HINT_INPUT, 0); + radspa_signal_set(osc, OSC_FM, "fm", RADSPA_SIGNAL_HINT_INPUT, 0); + radspa_signal_set(osc, OSC_SYNC_IN, "sync_input", RADSPA_SIGNAL_HINT_INPUT | RADSPA_SIGNAL_HINT_TRIGGER, 0); + radspa_signal_set(osc, OSC_SYNC_IN_PHASE, "sync_input_phase", RADSPA_SIGNAL_HINT_INPUT, 0); + radspa_signal_set(osc, OSC_SYNC_OUT, "sync_output", RADSPA_SIGNAL_HINT_OUTPUT | RADSPA_SIGNAL_HINT_TRIGGER, 0); + radspa_signal_set(osc, OSC_SPEED, "speed", RADSPA_SIGNAL_HINT_INPUT, 0); + + radspa_signal_get_by_index(osc, OSC_WAVEFORM)->unit = "{SINE:-32767} {TRI:-10922} {SQUARE:10922} {SAW:32767}"; + radspa_signal_get_by_index(osc, OSC_SPEED)->unit = "{LFO:-32767} {AUTO:0} {AUDIO:32767}"; + osc_data_t * data = osc->plugin_data; + data->pitch_prev = -32768; + data->morph_prev = -32768; + data->waveform_prev = -32768; + data->morph_gate_prev = -32768; + return osc; +} diff --git a/components/bl00mbox/radspa/standard_plugin_lib/osc.h b/components/bl00mbox/radspa/standard_plugin_lib/osc.h new file mode 100644 index 0000000000000000000000000000000000000000..607c9e83e8052b5e58b1d0ecd5214bcc59de68f9 --- /dev/null +++ b/components/bl00mbox/radspa/standard_plugin_lib/osc.h @@ -0,0 +1,23 @@ +#pragma once +#include "radspa.h" +#include "radspa_helpers.h" + +typedef struct { + uint32_t counter; + int16_t sync_in; + int16_t sync_out; + int16_t pitch_prev; + uint32_t pitch_coeffs[1]; + int32_t waveform_prev; + uint32_t waveform_coeffs[2]; + int32_t blep_coeffs[1]; + int32_t incr_prev; + int32_t morph_prev; + int32_t morph_coeffs[3]; + int16_t morph_gate_prev; + bool morph_no_pwm_prev; +} osc_data_t; + +extern radspa_descriptor_t osc_desc; +radspa_t * osc_create(uint32_t init_var); +void osc_run(radspa_t * osc, uint16_t num_samples, uint32_t render_pass_id); diff --git a/components/bl00mbox/radspa/standard_plugin_lib/osc_fm.c b/components/bl00mbox/radspa/standard_plugin_lib/osc_fm.c index 68c366c5e591c40a29546837bd76bdfe106df620..972fa17e844c8b412dd9de3541325f9f0226c583 100644 --- a/components/bl00mbox/radspa/standard_plugin_lib/osc_fm.c +++ b/components/bl00mbox/radspa/standard_plugin_lib/osc_fm.c @@ -2,8 +2,8 @@ radspa_descriptor_t osc_fm_desc = { .name = "osc_fm", - .id = 420, - .description = "simple audio band oscillator with classic waveforms and linear fm input", + .id = 4202, + .description = "[DEPRECATED, replacement: osc] simple audio band oscillator with classic waveforms and linear fm input", .create_plugin_instance = osc_fm_create, .destroy_plugin_instance = radspa_standard_plugin_destroy }; @@ -33,6 +33,7 @@ radspa_t * osc_fm_create(uint32_t init_var){ data->lin_fm_sig = radspa_signal_get_by_index(osc_fm, OSC_FM_LIN_FM); data->fm_pitch_thru_sig = radspa_signal_get_by_index(osc_fm, OSC_FM_PITCH_THRU); data->fm_pitch_offset_sig = radspa_signal_get_by_index(osc_fm, OSC_FM_PITCH_OFFSET); + data->waveform_sig->unit = "{SINE:-32767} {TRI:-10922} {SQUARE:10922} {SAW:32767}"; return osc_fm; } diff --git a/components/bl00mbox/radspa/standard_plugin_lib/poly_squeeze.c b/components/bl00mbox/radspa/standard_plugin_lib/poly_squeeze.c index 2d23836a5c5cc429da2f53f7dce57bb633d3f779..f1df9a6b33eba1a0871b963eb4158da6913cb460 100644 --- a/components/bl00mbox/radspa/standard_plugin_lib/poly_squeeze.c +++ b/components/bl00mbox/radspa/standard_plugin_lib/poly_squeeze.c @@ -3,7 +3,7 @@ radspa_descriptor_t poly_squeeze_desc = { .name = "poly_squeeze", .id = 172, - .description = "Multiplexes a number of triggerand pitch inputs into a lesser number of trigger pitch output pairs. " + .description = "Multiplexes a number of trigger and pitch inputs into a lesser number of trigger pitch output pairs. " "The latest triggered inputs are forwarded to the output. If such an input receives a stop trigger it is disconnected " "from its output. If another inputs is in triggered state but not forwarded at the same time it will be connected to that " "output and the output is triggered. Pitch is constantly streamed to the outputs if they are connected, else the last " @@ -13,13 +13,13 @@ radspa_descriptor_t poly_squeeze_desc = { .destroy_plugin_instance = radspa_standard_plugin_destroy }; - +#define NUM_MPX 2 // mpx block 1 -#define POLY_SQUEEZE_TRIGGER_INPUT 0 -#define POLY_SQUEEZE_PITCH_INPUT 1 +#define TRIGGER_INPUT 0 +#define PITCH_INPUT 1 // mpx block 2 -#define POLY_SQUEEZE_TRIGGER_OUTPUT 0 -#define POLY_SQUEEZE_PITCH_OUTPUT 1 +#define TRIGGER_OUTPUT 0 +#define PITCH_OUTPUT 1 static void assign_note_voices(poly_squeeze_data_t * data){ poly_squeeze_note_t * note = data->active_notes_top; @@ -49,7 +49,6 @@ static void assign_note_voices(poly_squeeze_data_t * data){ voice = v; break; } - } note->voice = voice; } @@ -122,53 +121,39 @@ void poly_squeeze_run(radspa_t * poly_squeeze, uint16_t num_samples, uint32_t re poly_squeeze_input_t * inputs = (void *) (&(notes[data->num_notes])); poly_squeeze_voice_t * voices = (void *) (&(inputs[data->num_inputs])); - radspa_signal_t * trigger_input_sigs[data->num_inputs]; - radspa_signal_t * pitch_input_sigs[data->num_inputs]; - radspa_signal_t * trigger_output_sigs[data->num_voices]; - radspa_signal_t * pitch_output_sigs[data->num_voices]; - for(uint8_t j = 0; j < data->num_inputs; j++){ - trigger_input_sigs[j] = radspa_signal_get_by_index(poly_squeeze, POLY_SQUEEZE_TRIGGER_INPUT + 2*j); - pitch_input_sigs[j] = radspa_signal_get_by_index(poly_squeeze, POLY_SQUEEZE_PITCH_INPUT + 2*j); - } - for(uint8_t j = 0; j < data->num_voices; j++){ - trigger_output_sigs[j] = radspa_signal_get_by_index(poly_squeeze, POLY_SQUEEZE_TRIGGER_OUTPUT + 2*(data->num_inputs+j)); - pitch_output_sigs[j] = radspa_signal_get_by_index(poly_squeeze, POLY_SQUEEZE_PITCH_OUTPUT + 2*(data->num_inputs+j)); - } - - for(uint16_t i = 0; i < num_samples; i++){ - for(uint8_t j = 0; j < data->num_inputs; j++){ - notes[j].pitch = radspa_signal_get_value(pitch_input_sigs[j], i, render_pass_id); - int16_t trigger_in = radspa_trigger_get(radspa_signal_get_value(trigger_input_sigs[j], i, render_pass_id), - &(inputs[j].trigger_in_hist)); - - if(trigger_in > 0){ - notes[j].vol = trigger_in; - int8_t voice = put_note_on_top(data, &(notes[j])); - if(voice >= 0) voice_start(&(voices[voice]), notes[j].pitch, notes[j].vol); - } else if(trigger_in < 0){ - int8_t voice = free_note(data, &(notes[j])); - if(voice >= 0){ - poly_squeeze_note_t * note = get_note_with_voice(data, voice); - if(note == NULL){ - voice_stop(&(voices[voice])); - } else { - voice_start(&(voices[voice]), note->pitch, note->vol); - } + uint16_t pitch_index; + int16_t trigger_in = radspa_trigger_get_const(&poly_squeeze->signals[TRIGGER_INPUT * NUM_MPX*j], + &inputs[j].trigger_in_hist, &pitch_index, num_samples, render_pass_id); + notes[j].pitch = radspa_signal_get_value(&poly_squeeze->signals[PITCH_INPUT + NUM_MPX*j], pitch_index, render_pass_id); + // should order events by pitch index some day maybe + if(trigger_in > 0){ + notes[j].vol = trigger_in; + int8_t voice = put_note_on_top(data, &(notes[j])); + if(voice >= 0) voice_start(&(voices[voice]), notes[j].pitch, notes[j].vol); + } else if(trigger_in < 0){ + int8_t voice = free_note(data, &(notes[j])); + if(voice >= 0){ + poly_squeeze_note_t * note = get_note_with_voice(data, voice); + if(note == NULL){ + voice_stop(&(voices[voice])); + } else { + voice_start(&(voices[voice]), note->pitch, note->vol); } } } - for(uint8_t j = 0; j < data->num_inputs; j++){ - if((notes[j].voice != -1) && (notes[j].voice < data->num_voices)){ - voices[notes[j].voice].pitch_out = notes[j].pitch; - } - } - for(uint8_t j = 0; j < data->num_voices; j++){ - radspa_signal_set_value_check_const(trigger_output_sigs[j], i, voices[j].trigger_out); - radspa_signal_set_value_check_const(pitch_output_sigs[j], i, voices[j].pitch_out); - voices[j]._start_trigger = voices[j].trigger_out; + } + for(uint8_t j = 0; j < data->num_inputs; j++){ + if((notes[j].voice != -1) && (notes[j].voice < data->num_voices)){ + voices[notes[j].voice].pitch_out = notes[j].pitch; } } + for(uint8_t j = 0; j < data->num_voices; j++){ + uint8_t k = data->num_inputs + j; + radspa_signal_set_const_value(&poly_squeeze->signals[TRIGGER_OUTPUT + NUM_MPX * k], voices[j].trigger_out); + radspa_signal_set_const_value(&poly_squeeze->signals[PITCH_OUTPUT + NUM_MPX * k], voices[j].pitch_out); + voices[j]._start_trigger = voices[j].trigger_out; + } } radspa_t * poly_squeeze_create(uint32_t init_var){ @@ -184,7 +169,7 @@ radspa_t * poly_squeeze_create(uint32_t init_var){ uint8_t num_notes = num_inputs; - uint32_t num_signals = num_voices * 2 + num_inputs * 2; + uint32_t num_signals = num_voices * NUM_MPX + num_inputs * NUM_MPX; size_t data_size = sizeof(poly_squeeze_data_t); data_size += sizeof(poly_squeeze_voice_t) * num_voices; data_size += sizeof(poly_squeeze_note_t) * num_notes; @@ -194,13 +179,13 @@ radspa_t * poly_squeeze_create(uint32_t init_var){ poly_squeeze->render = poly_squeeze_run; - radspa_signal_set_group(poly_squeeze, num_inputs, 2, POLY_SQUEEZE_TRIGGER_INPUT, "trigger_in", + radspa_signal_set_group(poly_squeeze, num_inputs, NUM_MPX, TRIGGER_INPUT, "trigger_in", RADSPA_SIGNAL_HINT_INPUT | RADSPA_SIGNAL_HINT_TRIGGER, 0); - radspa_signal_set_group(poly_squeeze, num_inputs, 2, POLY_SQUEEZE_PITCH_INPUT, "pitch_in", + radspa_signal_set_group(poly_squeeze, num_inputs, NUM_MPX, PITCH_INPUT, "pitch_in", RADSPA_SIGNAL_HINT_INPUT | RADSPA_SIGNAL_HINT_SCT, RADSPA_SIGNAL_VAL_SCT_A440); - radspa_signal_set_group(poly_squeeze, num_voices, 2, POLY_SQUEEZE_TRIGGER_OUTPUT + 2*num_inputs, "trigger_out", + radspa_signal_set_group(poly_squeeze, num_voices, NUM_MPX, TRIGGER_OUTPUT + NUM_MPX*num_inputs, "trigger_out", RADSPA_SIGNAL_HINT_OUTPUT | RADSPA_SIGNAL_HINT_TRIGGER, 0); - radspa_signal_set_group(poly_squeeze, num_voices, 2, POLY_SQUEEZE_PITCH_OUTPUT + 2*num_inputs, "pitch_out", + radspa_signal_set_group(poly_squeeze, num_voices, NUM_MPX, PITCH_OUTPUT + NUM_MPX*num_inputs, "pitch_out", RADSPA_SIGNAL_HINT_OUTPUT | RADSPA_SIGNAL_HINT_SCT, RADSPA_SIGNAL_VAL_SCT_A440); poly_squeeze_data_t * data = poly_squeeze->plugin_data; diff --git a/components/bl00mbox/radspa/standard_plugin_lib/range_shifter.c b/components/bl00mbox/radspa/standard_plugin_lib/range_shifter.c index baf57c6842bedf37c078e5727aabf62b48d2f339..ad2c0202c743151f0a094498c36fa61cf6d1c669 100644 --- a/components/bl00mbox/radspa/standard_plugin_lib/range_shifter.c +++ b/components/bl00mbox/radspa/standard_plugin_lib/range_shifter.c @@ -3,62 +3,104 @@ radspa_t * range_shifter_create(uint32_t init_var); radspa_descriptor_t range_shifter_desc = { .name = "range_shifter", - .id = 68, + .id = 69, .description = "saturating multiplication and addition", .create_plugin_instance = range_shifter_create, .destroy_plugin_instance = radspa_standard_plugin_destroy }; -#define RANGE_SHIFTER_NUM_SIGNALS 6 +#define RANGE_SHIFTER_NUM_SIGNALS 7 #define RANGE_SHIFTER_OUTPUT 0 -#define RANGE_SHIFTER_INPUT 1 -#define RANGE_SHIFTER_OUTPUT_A 2 -#define RANGE_SHIFTER_OUTPUT_B 3 -#define RANGE_SHIFTER_INPUT_A 2 -#define RANGE_SHIFTER_INPUT_B 3 +#define RANGE_SHIFTER_OUTPUT_A 1 +#define RANGE_SHIFTER_OUTPUT_B 2 +#define RANGE_SHIFTER_INPUT 3 +#define RANGE_SHIFTER_INPUT_A 4 +#define RANGE_SHIFTER_INPUT_B 5 +#define RANGE_SHIFTER_SPEED 6 + + +#define GET_GAIN { \ + output_a = radspa_signal_get_value(output_a_sig, k, render_pass_id); \ + output_b = radspa_signal_get_value(output_b_sig, k, render_pass_id); \ + input_a = radspa_signal_get_value(input_a_sig, k, render_pass_id); \ + input_b = radspa_signal_get_value(input_b_sig, k, render_pass_id); \ + output_span = output_b - output_a; \ + input_span = input_b - input_a; \ + gain = (output_span << 14) / input_span; \ +} + +#define APPLY_GAIN { \ + if(ret == input_a){ \ + ret = output_a; \ + } else if(ret == input_b){ \ + ret = output_b; \ + } else { \ + ret -= input_a; \ + ret = (ret * gain) >> 14; \ + ret += output_a; \ + } \ +} void range_shifter_run(radspa_t * range_shifter, uint16_t num_samples, uint32_t render_pass_id){ radspa_signal_t * output_sig = radspa_signal_get_by_index(range_shifter, RANGE_SHIFTER_OUTPUT); - if(output_sig->buffer == NULL) return; + radspa_signal_t * output_a_sig = radspa_signal_get_by_index(range_shifter, RANGE_SHIFTER_OUTPUT_A); + radspa_signal_t * output_b_sig = radspa_signal_get_by_index(range_shifter, RANGE_SHIFTER_OUTPUT_B); radspa_signal_t * input_sig = radspa_signal_get_by_index(range_shifter, RANGE_SHIFTER_INPUT); - int32_t output_a = radspa_signal_get_value(radspa_signal_get_by_index(range_shifter, RANGE_SHIFTER_OUTPUT_A), 0, render_pass_id); - int32_t output_b = radspa_signal_get_value(radspa_signal_get_by_index(range_shifter, RANGE_SHIFTER_OUTPUT_B), 0, render_pass_id); - int32_t input_a = radspa_signal_get_value(radspa_signal_get_by_index(range_shifter, RANGE_SHIFTER_INPUT_A), 0, render_pass_id); - int32_t input_b = radspa_signal_get_value(radspa_signal_get_by_index(range_shifter, RANGE_SHIFTER_INPUT_B), 0, render_pass_id); - int32_t output_span = output_b - output_a; - if(!output_span){ - for(uint16_t i = 0; i < num_samples; i++){ - radspa_signal_set_value(output_sig, i, output_a); - } - return; + radspa_signal_t * input_a_sig = radspa_signal_get_by_index(range_shifter, RANGE_SHIFTER_INPUT_A); + radspa_signal_t * input_b_sig = radspa_signal_get_by_index(range_shifter, RANGE_SHIFTER_INPUT_B); + radspa_signal_t * speed_sig = radspa_signal_get_by_index(range_shifter, RANGE_SHIFTER_SPEED); + int16_t speed = radspa_signal_get_value(speed_sig, 0, render_pass_id); + int32_t output_a = radspa_signal_get_const_value(output_a_sig, render_pass_id); + int32_t output_b = radspa_signal_get_const_value(output_b_sig, render_pass_id); + int32_t input_a = radspa_signal_get_const_value(input_a_sig, render_pass_id); + int32_t input_b = radspa_signal_get_const_value(input_b_sig, render_pass_id); + int32_t input = radspa_signal_get_const_value(input_sig, render_pass_id); + + bool range_const = true; + if(speed >= 10922){ + range_const = range_const && (output_a != RADSPA_SIGNAL_NONCONST) && (output_b != RADSPA_SIGNAL_NONCONST); + range_const = range_const && (input_a != RADSPA_SIGNAL_NONCONST) && (input_b != RADSPA_SIGNAL_NONCONST); } - int32_t input_span = input_b - input_a; - if(!input_span){ - int32_t avg = (output_b - output_a)/2; - for(uint16_t i = 0; i < num_samples; i++){ - radspa_signal_set_value(output_sig, i, avg); - } - return; + bool input_const = true; + if(speed > -10922){ + bool input_const = input != RADSPA_SIGNAL_NONCONST; } - int32_t gain = (output_span << 14) / input_span; - for(uint16_t i = 0; i < num_samples; i++){ - int32_t ret = radspa_signal_get_value(input_sig, i, render_pass_id); - if(ret == input_a){ - ret = output_a; - } else if(ret == input_b){ - ret = output_b; + int32_t output_span; + int32_t input_span; + int32_t gain; + if(range_const){ + uint16_t k = 0; + GET_GAIN + if(!output_span){ + radspa_signal_set_const_value(output_sig, output_a); + } else if(!input_span){ + radspa_signal_set_const_value(output_sig, (output_b - output_a)>>1); + } else if(input_const){ + int32_t ret = radspa_signal_get_value(input_sig, 0, render_pass_id); + APPLY_GAIN + radspa_signal_set_const_value(output_sig, ret); } else { - ret -= input_a; - ret = (ret * gain) >> 14; - ret += output_a; - if(ret > 32767){ - ret = 32767; - } else if(ret < -32767){ - ret = -32767; + for(uint16_t i = 0; i < num_samples; i++){ + int32_t ret = radspa_signal_get_value(input_sig, i, render_pass_id); + APPLY_GAIN + radspa_signal_set_value(output_sig, i, ret); + } + } + } else { + for(uint16_t i = 0; i < num_samples; i++){ + uint16_t k = i; + GET_GAIN + if(!output_span){ + radspa_signal_set_value(output_sig, i, output_a); + } else if(!input_span){ + radspa_signal_set_value(output_sig, i, (output_b - output_a)>>1); + } else { + int32_t ret = radspa_signal_get_value(input_sig, i, render_pass_id); + APPLY_GAIN + radspa_signal_set_value(output_sig, i, ret); } } - radspa_signal_set_value(output_sig, i, ret); } } @@ -67,10 +109,12 @@ radspa_t * range_shifter_create(uint32_t init_var){ if(range_shifter == NULL) return NULL; range_shifter->render = range_shifter_run; radspa_signal_set(range_shifter, RANGE_SHIFTER_OUTPUT, "output", RADSPA_SIGNAL_HINT_OUTPUT, 0); + radspa_signal_set_group(range_shifter, 2, 1, RANGE_SHIFTER_OUTPUT_A, "output_range", RADSPA_SIGNAL_HINT_INPUT, -32767); radspa_signal_set(range_shifter, RANGE_SHIFTER_INPUT, "input", RADSPA_SIGNAL_HINT_INPUT, 0); - radspa_signal_set(range_shifter, RANGE_SHIFTER_OUTPUT_A, "output_a", RADSPA_SIGNAL_HINT_INPUT, -32767); - radspa_signal_set(range_shifter, RANGE_SHIFTER_OUTPUT_B, "output_b", RADSPA_SIGNAL_HINT_INPUT, 32767); - radspa_signal_set(range_shifter, RANGE_SHIFTER_INPUT_A, "input_a", RADSPA_SIGNAL_HINT_INPUT, -32767); - radspa_signal_set(range_shifter, RANGE_SHIFTER_INPUT_B, "input_b", RADSPA_SIGNAL_HINT_INPUT, 32767); + radspa_signal_set_group(range_shifter, 2, 1, RANGE_SHIFTER_INPUT_A, "input_range", RADSPA_SIGNAL_HINT_INPUT, -32767); + radspa_signal_set(range_shifter, RANGE_SHIFTER_SPEED, "speed", RADSPA_SIGNAL_HINT_INPUT, 32767); + radspa_signal_get_by_index(range_shifter, RANGE_SHIFTER_SPEED)->unit = "{SLOW:-32767} {SLOW_RANGE:0} {FAST:32767}"; + range_shifter->signals[RANGE_SHIFTER_OUTPUT_B].value = 32767; + range_shifter->signals[RANGE_SHIFTER_INPUT_B].value = 32767; return range_shifter; } diff --git a/components/bl00mbox/radspa/standard_plugin_lib/sampler.c b/components/bl00mbox/radspa/standard_plugin_lib/sampler.c index 3de9e4349e8877bf2e3a500be25259ddd9c47a81..e30dd8a996d6b6ba2a7cdc57124e6b0e8bf48e3e 100644 --- a/components/bl00mbox/radspa/standard_plugin_lib/sampler.c +++ b/components/bl00mbox/radspa/standard_plugin_lib/sampler.c @@ -2,11 +2,12 @@ radspa_t * sampler_create(uint32_t init_var); radspa_descriptor_t sampler_desc = { - .name = "_sampler_ram", + .name = "sampler", .id = 696969, .description = "simple sampler that stores a copy of the sample in ram and has basic recording functionality." - "\ninit_var: length of pcm sample memory\ntable layout: [0:2] read head position (uint32_t), [2:4] sample start (uint32_t), " - "[4:6] sample length (uint32_t), [6:8] sample rate (uint32_t, [8] new record event (bool), [9:init_var+9] pcm sample data (int16_t)", + "\ninit_var: length of pcm sample memory\ntable layout: [0:2] read head position (uint32_t), [2:4] write head position (uint32_t), " + "[4:6] sample start (uint32_t), [6:8] sample length (uint32_t), [8:10] sample rate (uint32_t), [10] sampler status " + "(int16_t bitmask), , [11:init_var+11] pcm sample data (int16_t)", .create_plugin_instance = sampler_create, .destroy_plugin_instance = radspa_standard_plugin_destroy }; @@ -14,164 +15,225 @@ radspa_descriptor_t sampler_desc = { #define SAMPLER_NUM_SIGNALS 5 #define SAMPLER_OUTPUT 0 #define SAMPLER_TRIGGER 1 -#define SAMPLER_REC_TRIGGER 2 -#define SAMPLER_REC_IN 3 -#define SAMPLER_PITCH_SHIFT 4 +#define SAMPLER_PITCH_SHIFT 2 +#define SAMPLER_REC_TRIGGER 3 +#define SAMPLER_REC_IN 4 #define READ_HEAD_POS 0 -#define SAMPLE_START 2 -#define SAMPLE_LEN 4 -#define SAMPLE_RATE 6 -#define BUFFER_OFFSET 9 +#define WRITE_HEAD_POS 2 +#define SAMPLE_START 4 +#define SAMPLE_LEN 6 +#define SAMPLE_RATE 8 +#define STATUS 10 +#define STATUS_PLAYBACK_ACTIVE 0 +#define STATUS_PLAYBACK_LOOP 1 +#define STATUS_RECORD_ACTIVE 2 +#define STATUS_RECORD_OVERFLOW 3 +#define STATUS_RECORD_NEW_EVENT 4 +#define BUFFER_OFFSET 11 void sampler_run(radspa_t * sampler, uint16_t num_samples, uint32_t render_pass_id){ radspa_signal_t * output_sig = radspa_signal_get_by_index(sampler, SAMPLER_OUTPUT); - if(output_sig->buffer == NULL) return; - - sampler_data_t * data = sampler->plugin_data; - int16_t * buf = sampler->plugin_table; - uint32_t * buf32 = (uint32_t *) buf; - data->read_head_pos = buf32[READ_HEAD_POS/2]; - uint32_t sample_len = buf32[SAMPLE_LEN/2]; - uint32_t buffer_size = sampler->plugin_table_len - BUFFER_OFFSET; - if(sample_len >= buffer_size) sample_len = buffer_size - 1; - if(data->read_head_pos >= buffer_size) data->read_head_pos = buffer_size - 1; - radspa_signal_t * trigger_sig = radspa_signal_get_by_index(sampler, SAMPLER_TRIGGER); radspa_signal_t * rec_trigger_sig = radspa_signal_get_by_index(sampler, SAMPLER_REC_TRIGGER); + radspa_signal_t * rec_in_sig = radspa_signal_get_by_index(sampler, SAMPLER_REC_IN); + radspa_signal_t * pitch_shift_sig = radspa_signal_get_by_index(sampler, SAMPLER_PITCH_SHIFT); + sampler_data_t * data = sampler->plugin_data; - int16_t trigger_const = radspa_signal_get_const_value(trigger_sig, render_pass_id); - int16_t rec_trigger_const = radspa_signal_get_const_value(rec_trigger_sig, render_pass_id); + int16_t trigger = radspa_signal_get_const_value(trigger_sig, render_pass_id); + bool trigger_const = trigger != RADSPA_SIGNAL_NONCONST; + if(trigger_const) trigger = radspa_trigger_get(trigger, &(data->trigger_prev)); + int16_t rec_trigger = radspa_signal_get_const_value(rec_trigger_sig, render_pass_id); + bool rec_trigger_const = rec_trigger != RADSPA_SIGNAL_NONCONST; + if(rec_trigger_const) rec_trigger = radspa_trigger_get(rec_trigger, &(data->rec_trigger_prev)); - bool output_mute = (data->read_head_pos >= sample_len); - if( output_mute && (!data->rec_active) && (trigger_const == data->trigger_prev) && (rec_trigger_const == data->rec_trigger_prev)){ + /* + if((!data->playback_active) && (!data->rec_active) && (trigger_const) && (rec_trigger_const) && (!rec_trigger) && (!trigger)){ radspa_signal_set_const_value(output_sig, 0); return; } + */ + int16_t * buf = sampler->plugin_table; + uint32_t * buf32 = (uint32_t *) buf; + uint32_t sample_len = buf32[SAMPLE_LEN/2]; uint32_t sample_start = buf32[SAMPLE_START/2]; uint32_t sample_rate = buf32[SAMPLE_RATE/2]; + if(!sample_rate){ + sample_rate = 1; + buf32[SAMPLE_RATE/2] = 1; + } + uint32_t buffer_size = sampler->plugin_table_len - BUFFER_OFFSET; + uint64_t buffer_size_long = buffer_size * 48000; - radspa_signal_t * rec_in_sig = radspa_signal_get_by_index(sampler, SAMPLER_REC_IN); - radspa_signal_t * pitch_shift_sig = radspa_signal_get_by_index(sampler, SAMPLER_PITCH_SHIFT); - + if(sample_len >= buffer_size) sample_len = buffer_size - 1; if(sample_start >= buffer_size) sample_start = buffer_size - 1; - + + bool output_mute = !data->playback_active; + bool output_const = sample_rate < 100; + if(output_const){ + sample_rate *= num_samples; + num_samples = 1; + } + + int32_t ret = 0; for(uint16_t i = 0; i < num_samples; i++){ - int32_t ret; - if((rec_trigger_const == -32768) || (!i)){ - int16_t rec_trigger = radspa_trigger_get(radspa_signal_get_value(rec_trigger_sig, i, render_pass_id), &(data->rec_trigger_prev)); + if((!rec_trigger_const) || (!i)){ + if(!rec_trigger_const) rec_trigger = radspa_trigger_get(radspa_signal_get_value(rec_trigger_sig, i, render_pass_id), &(data->rec_trigger_prev)); if(rec_trigger > 0){ - if(!(data->rec_active)){ - data->read_head_pos = sample_len; - data->write_head_pos = 0; - data->write_head_pos_long = 0; - sample_len = 0; - data->rec_active = true; - } - } else if(rec_trigger < 0){ - if(data->rec_active){ - if(sample_len == buffer_size){ - sample_start = data->write_head_pos; - } else { - sample_start = 0; - } - buf[8] = 1; - data->rec_active = false; - } + data->rec_active = true; + data->write_head_pos_long = 0; + data->write_steps = 0; + data->write_head_pos_prev = -1; + data->write_overflow = false; + buf[STATUS] |= 1<<(STATUS_RECORD_NEW_EVENT); + } else if((rec_trigger < 0) && data->rec_active){ + data->rec_active = false; } } - if(data->rec_active){ int16_t rec_in = radspa_signal_get_value(rec_in_sig, i, render_pass_id); - buf[data->write_head_pos + BUFFER_OFFSET] = rec_in; - - data->write_head_pos_long += sample_rate; - if(sample_rate == 48000){ - data->write_head_pos++; + uint32_t write_head_pos = (data->write_head_pos_long * 699) >> 25; // equiv to x/48000 (acc 0.008%) + if(data->write_head_pos_prev == write_head_pos){ + if(data->write_steps){ + data->rec_acc += rec_in; + } else { + data->rec_acc = buf[write_head_pos + BUFFER_OFFSET]; + } + data->write_steps++; } else { - data->write_head_pos = (data->write_head_pos_long * 699) >> 25; // equiv to _/48000 (acc 0.008%) - } - if(data->write_head_pos >= buffer_size){ - data->write_head_pos = 0; - data->write_head_pos_long = 0; - } - if(sample_len < buffer_size){ - sample_len++; - buf32[SAMPLE_LEN] = sample_len; - } - } else { - if((trigger_const == -32768) || (!i)){ - int16_t trigger = radspa_trigger_get(radspa_signal_get_value(trigger_sig, i, render_pass_id), &(data->trigger_prev)); - if(trigger > 0){ - data->read_head_pos_long = 0; - data->read_head_pos = 0; - data->volume = trigger; - if(output_mute){ - for(uint8_t j = 0; j < i; j++){ - radspa_signal_set_value(output_sig, j, 0); - } - output_mute = false; + if(data->write_steps) buf[data->write_head_pos_prev + BUFFER_OFFSET] = data->rec_acc/data->write_steps; + data->write_steps = 0; + if(write_head_pos > data->write_head_pos_prev){ + for(uint32_t j = data->write_head_pos_prev + 1; j <= write_head_pos; j++){ + buf[j + BUFFER_OFFSET] = rec_in; + } + } else { + uint32_t write_head_max = write_head_pos + buffer_size; + for(uint32_t j = data->write_head_pos_prev + 1; j <= write_head_max; j++){ + uint32_t index = j; + if(index >= buffer_size) index -= buffer_size; + buf[index + BUFFER_OFFSET] = rec_in; } - } else if(trigger < 0){ - data->read_head_pos = sample_len; } + } + if(!data->write_overflow) data->write_overflow = write_head_pos < data->write_head_pos_prev; + data->write_head_pos_prev = write_head_pos; - if(data->read_head_pos < sample_len){ - uint32_t sample_offset_pos = data->read_head_pos + sample_start; - if(sample_offset_pos >= sample_len) sample_offset_pos -= sample_len; - ret = radspa_mult_shift(buf[sample_offset_pos + BUFFER_OFFSET], data->volume); - radspa_signal_set_value(output_sig, i, ret); - - int32_t pitch_shift = radspa_signal_get_value(pitch_shift_sig, i, render_pass_id); - if(pitch_shift != data->pitch_shift_prev){ - data->pitch_shift_mult = radspa_sct_to_rel_freq(radspa_clip(pitch_shift - 18376 - 10986 - 4800), 0); - if(data->pitch_shift_mult > (1<<13)) data->pitch_shift_mult = (1<<13); - - data->pitch_shift_prev = pitch_shift; + if(data->write_overflow & (!(buf[STATUS] & (1<<(STATUS_RECORD_OVERFLOW))))){ + data->rec_active = false; + } else { + if(data->write_overflow){ + sample_start = (data->write_head_pos_long * 699) >> 25; + sample_len = buffer_size; + } else { + sample_start = 0; + sample_len = (data->write_head_pos_long * 699) >> 25; } + data->write_head_pos_long += sample_rate; + while(data->write_head_pos_long >= buffer_size_long) data->write_head_pos_long -= buffer_size_long; + } + } - if(sample_rate == 48000 && data->pitch_shift_mult == (1<<11)){ - data->read_head_pos_long += sample_rate; - data->read_head_pos++; - } else { - data->read_head_pos_long += (sample_rate * data->pitch_shift_mult) >> 11; - data->read_head_pos = (data->read_head_pos_long * 699) >> 25; // equiv to _/48000 (acc 0.008%) + if((!trigger_const) || (!i)){ + if(!trigger_const) trigger = radspa_trigger_get(radspa_signal_get_value(trigger_sig, i, render_pass_id), &(data->trigger_prev)); + if(trigger > 0){ + data->playback_active = true; + data->read_head_pos_long = 0; + data->volume = trigger; + if(output_mute){ + radspa_signal_set_values(output_sig, 0, i, 0); + output_mute = false; } - } else if(!output_mute){ - radspa_signal_set_value(output_sig, i, 0); + } else if(trigger < 0){ + data->playback_active = false; } + } + int32_t read_head_pos; + int8_t read_head_pos_subsample; + if(data->playback_active){ + read_head_pos = (data->read_head_pos_long * 699) >> (25-6); // equiv to (x<<6)/48000 (acc 0.008%) + read_head_pos_subsample = read_head_pos & 0b111111; + read_head_pos = read_head_pos >> 6; + if(read_head_pos >= sample_len){ + if(buf[STATUS] & (1<<(STATUS_PLAYBACK_LOOP))){ + while(read_head_pos > sample_len){ + data->read_head_pos_long -= (uint64_t) sample_len * 48000; + read_head_pos -= sample_len; + } + } else { + data->playback_active = false; + } + } + } + if(data->playback_active){ + uint32_t sample_offset_pos = read_head_pos + sample_start; + while(sample_offset_pos >= sample_len) sample_offset_pos -= sample_len; + ret = buf[sample_offset_pos + BUFFER_OFFSET]; + if(read_head_pos_subsample){ + ret *= (64 - read_head_pos_subsample); + sample_offset_pos++; + if(sample_offset_pos >= sample_len) sample_offset_pos -= sample_len; + ret += buf[sample_offset_pos + BUFFER_OFFSET] * read_head_pos_subsample; + ret = ret >> 6; + } + ret = radspa_mult_shift(ret, data->volume); + radspa_signal_set_value(output_sig, i, ret); + + int32_t pitch_shift = radspa_signal_get_value(pitch_shift_sig, i, render_pass_id); + if(pitch_shift != data->pitch_shift_prev){ + data->pitch_shift_mult = radspa_sct_to_rel_freq(radspa_clip(pitch_shift - 18376 - 10986 - 4800), 0); + if(data->pitch_shift_mult > (1<<13)) data->pitch_shift_mult = (1<<13); + + data->pitch_shift_prev = pitch_shift; + } + data->read_head_pos_long += (sample_rate * data->pitch_shift_mult) >> 11; + } else { + if(!output_mute) radspa_signal_set_value(output_sig, i, 0); } } - if(output_mute) radspa_signal_set_const_value(output_sig, 0); - buf32[READ_HEAD_POS/2] = data->read_head_pos; + if(output_mute || output_const) radspa_signal_set_const_value(output_sig, ret); buf32[SAMPLE_START/2] = sample_start; buf32[SAMPLE_LEN/2] = sample_len; + if(data->playback_active){ + buf[STATUS] |= 1<<(STATUS_PLAYBACK_ACTIVE); + buf32[READ_HEAD_POS/2] = (data->read_head_pos_long * 699) >> 25;; + } else { + buf[STATUS] &= ~(1<<(STATUS_PLAYBACK_ACTIVE)); + buf32[READ_HEAD_POS/2] = 0; + } + if(data->rec_active){ + buf[STATUS] |= 1<<(STATUS_RECORD_ACTIVE); + buf32[WRITE_HEAD_POS/2] = (data->write_head_pos_long * 699) >> 25;; + } else { + buf[STATUS] &= ~(1<<(STATUS_RECORD_ACTIVE)); + buf32[WRITE_HEAD_POS/2] = 0; + } } +#define MAX_SAMPLE_LEN (48000UL*300) + radspa_t * sampler_create(uint32_t init_var){ if(init_var == 0) return NULL; //doesn't make sense + if(init_var > MAX_SAMPLE_LEN) init_var = MAX_SAMPLE_LEN; uint32_t buffer_size = init_var; radspa_t * sampler = radspa_standard_plugin_create(&sampler_desc, SAMPLER_NUM_SIGNALS, sizeof(sampler_data_t), buffer_size + BUFFER_OFFSET); if(sampler == NULL) return NULL; sampler->render = sampler_run; - radspa_signal_set(sampler, SAMPLER_OUTPUT, "output", RADSPA_SIGNAL_HINT_OUTPUT, 0); - radspa_signal_set(sampler, SAMPLER_TRIGGER, "trigger", RADSPA_SIGNAL_HINT_INPUT | RADSPA_SIGNAL_HINT_TRIGGER, 0); - radspa_signal_set(sampler, SAMPLER_REC_IN, "rec_in", RADSPA_SIGNAL_HINT_INPUT, 0); - radspa_signal_set(sampler, SAMPLER_REC_TRIGGER, "rec_trigger", RADSPA_SIGNAL_HINT_INPUT | RADSPA_SIGNAL_HINT_TRIGGER, 0); - radspa_signal_set(sampler, SAMPLER_PITCH_SHIFT, "pitch_shift", RADSPA_SIGNAL_HINT_INPUT | RADSPA_SIGNAL_HINT_SCT, 18367); + radspa_signal_set(sampler, SAMPLER_OUTPUT, "playback_output", RADSPA_SIGNAL_HINT_OUTPUT, 0); + radspa_signal_set(sampler, SAMPLER_TRIGGER, "playback_trigger", RADSPA_SIGNAL_HINT_INPUT | RADSPA_SIGNAL_HINT_TRIGGER, 0); + radspa_signal_set(sampler, SAMPLER_PITCH_SHIFT, "playback_speed", RADSPA_SIGNAL_HINT_INPUT | RADSPA_SIGNAL_HINT_SCT, 18367); + radspa_signal_set(sampler, SAMPLER_REC_IN, "record_input", RADSPA_SIGNAL_HINT_INPUT, 0); + radspa_signal_set(sampler, SAMPLER_REC_TRIGGER, "record_trigger", RADSPA_SIGNAL_HINT_INPUT | RADSPA_SIGNAL_HINT_TRIGGER, 0); sampler_data_t * data = sampler->plugin_data; - data->trigger_prev = 0; - data->rec_trigger_prev = 0; - data->rec_active = false; data->pitch_shift_mult = 1<<11; - uint32_t * buf32 = (uint32_t *) sampler->plugin_table; + + int16_t * buf = sampler->plugin_table; + uint32_t * buf32 = (uint32_t *) buf; buf32[SAMPLE_RATE/2] = 48000; - //int16_t * buf = sampler->plugin_table; - //write_uint32_to_buffer_pos(buf, SAMPLE_START, 0); - //write_uint32_to_buffer_pos(buf, SAMPLE_LEN, sampler->plugin_table_len); + buf[STATUS] = 1<<(STATUS_RECORD_OVERFLOW); return sampler; } diff --git a/components/bl00mbox/radspa/standard_plugin_lib/sampler.h b/components/bl00mbox/radspa/standard_plugin_lib/sampler.h index da8d3a7c9ed5851e6abf7c775b2d3f8fc63630b6..2088bea8438991ac85369d9e7766815212763e0e 100644 --- a/components/bl00mbox/radspa/standard_plugin_lib/sampler.h +++ b/components/bl00mbox/radspa/standard_plugin_lib/sampler.h @@ -3,17 +3,19 @@ #include <radspa_helpers.h> typedef struct { - uint64_t write_head_pos_long; - uint64_t read_head_pos_long; - uint32_t write_head_pos; - uint32_t read_head_pos; + int64_t write_head_pos_long; + int64_t read_head_pos_long; int16_t pitch_shift_prev; int16_t trigger_prev; int16_t rec_trigger_prev; int16_t volume; uint32_t pitch_shift_mult; + int32_t rec_acc; + int32_t write_head_pos_prev; + int16_t write_steps; bool rec_active; - bool buffer_all_zeroes; + bool write_overflow; + bool playback_active; } sampler_data_t; extern radspa_descriptor_t sampler_desc; diff --git a/components/bl00mbox/radspa/standard_plugin_lib/sequencer.c b/components/bl00mbox/radspa/standard_plugin_lib/sequencer.c index 083e43d66cff3b63090af2bccc24509d76a45e56..01de28d7b7af2a8b95b4b2ff6577ddac9a83db97 100644 --- a/components/bl00mbox/radspa/standard_plugin_lib/sequencer.c +++ b/components/bl00mbox/radspa/standard_plugin_lib/sequencer.c @@ -1,7 +1,7 @@ #include "sequencer.h" radspa_descriptor_t sequencer_desc = { - .name = "_sequencer", + .name = "sequencer", .id = 56709, .description = "sequencer that can output triggers or general control signals, best enjoyed through the " "'sequencer' patch.\ninit_var: 1st byte (lsb): number of tracks, 2nd byte: number of steps"