diff --git a/.pio/builder/main.py b/.pio/builder/main.py index ec33302..7aaabdc 100644 --- a/.pio/builder/main.py +++ b/.pio/builder/main.py @@ -1,96 +1,15 @@ from SCons.Script import DefaultEnvironment, Default import shutil, os -import subprocess -import json -import shutil -import io -import zlib -import time -import intelhex # pip install intelhex - #https://python-intelhex.readthedocs.io/en/latest/part2-2.html -import os, json, io env = DefaultEnvironment() -# if env.get("PROGNAME", "program") == "program": -# env.Replace(PROGNAME="firmware") +if env.get("PROGNAME", "program") == "program": + env.Replace(PROGNAME="firmware") # print(env.Dump()) -env.AddPlatformTarget( - "build", None, env.GetBuildPath(f"$PROJECT_DIR/app/build.sh"), "Upload" -) -env.AddPlatformTarget( - "upload", "build", env.GetBuildPath(f"$PROJECT_DIR/app/upload.py"), "Upload" -) - -def udynlink_size(file): - with open(file, "rb") as f: - f.read(4) - l = int.from_bytes(f.read(2), byteorder="little") # num_lot - r = int.from_bytes(f.read(2), byteorder="little") - # num_rels - a = int.from_bytes(f.read(4), byteorder="little") - # symt_size - b = int.from_bytes(f.read(4), byteorder="little") - # code_size - c = int.from_bytes(f.read(4), byteorder="little") - # data_size - d = int.from_bytes(f.read(4), byteorder="little") - # bss_size - h = 24 + (r * 8) + a - return h + b + c - - -def make_engines_hex(apps_json, ih=intelhex.IntelHex(), offset=int(1024 * 1024 / 2)): - - print(apps_json) - if not os.path.exists(apps_json): - print(apps_json, "not found!") - else: - with open(apps_json) as f: - apps = json.load(f) - i = 1 - for file in apps["apps"]: - bin_file = os.path.dirname(apps_json) + "/" + str(file) - if not os.path.exists(bin_file): - continue - bin_size = os.path.getsize(bin_file) - - ih.loadbin(bin_file, offset=offset) - bin_offset = offset - offset += bin_size - with open(bin_file, "rb") as f: - crc32sum = zlib.crc32(f.read()) - ih.puts(offset, crc32sum.to_bytes(4, "little")) - offset += 4 - - print( - i, - "0x%x" % bin_offset, - bin_file, - bin_size, - udynlink_size(bin_file) % 4, - "CRC32: %x" % crc32sum, - ) - i += 1 - offset += 4096 - (offset % 4096) - - ih.puts(offset, 0xFFFF.to_bytes(4, "little")) - return ih - - -def post_program_action(source, target, env): - apps_json = env.GetProjectOption("apps_json") - ahx = make_engines_hex(apps_json) - program_path = env.GetBuildPath("$PROJECT_DIR/.pio/build/$PIOENV/engines.hex") - ahx.tofile(program_path, format="hex") - loader_sha = env.GetBuildPath("$PROJECT_DIR/.pio/build/$PIOENV/loader.sha") - with open(loader_sha, "w") as f: - f.write(env.GetProjectOption("squares_and_circles_loader")) - print(program_path, len(ahx)) - - -env.AddPostAction("build", post_program_action) - -Default(["build"]) +env.AddPlatformTarget("build", None, env.GetBuildPath(f"$PROJECT_DIR/app/build.sh"), "Upload") +env.AddPlatformTarget("upload", "build", env.GetBuildPath(f"$PROJECT_DIR/app/upload.py"), "Upload") + +Default(["build"]) \ No newline at end of file diff --git a/README.md b/README.md index ceb5746..eb8d4d4 100644 --- a/README.md +++ b/README.md @@ -9,6 +9,13 @@ ChangeLog ```` +== 2024-11-30 + * Bugfix: + * Crash on patch saving/restoring (#97) + * Enhancements: + * M-OSC/Waveforms: Braids Renaissance Chords (#110) + * SEQ/TuringMachine - OutputModes: Note-3...Note-7 (#109) + * Internal signal routing: src: $1-$9 (#108) == 2024-11-07 * Bugfix: * M-OSC/Waveforms - V_OCT modulation @@ -354,7 +361,7 @@ For each parameter a modulation can be assigned: >[Long press [RIGHT]] enters the I/O-Configuration page. -The I/O-Configuration page lets you virtually patch the engine with the hardware ports. Depending on the engine interface, trigger, gate, accent and V/OCT can be configured. In addition to the trigger, which is set with a rising edge, a gate state is also provided, that can be processed by the engine. Engines like Closed/Open-HiHats have an additional accent input - this works technically like a second trigger. The V/OCT input can optionally be quantized and transposed. In addition to the Tx inputs, the Cx inputs can also be used as a source for triggers and accents. The output can be configured as mono or stereo. Several engines can share the same output - the signal is mixed. +The I/O-Configuration page lets you virtually patch the engine with the hardware ports and internal gate/cv signals ($1-$9). Depending on the engine interface, trigger, gate, accent and V/OCT can be configured. In addition to the trigger, which is set with a rising edge, a gate state is also provided, that can be processed by the engine. Engines like Closed/Open-HiHats have an additional accent input - this works technically like a second trigger. The V/OCT input can optionally be quantized and transposed. In addition to the Tx inputs, the Cx inputs can also be used as a source for triggers and accents. The output can be configured as mono or stereo. Several engines can share the same output - the signal is mixed. ### Ctrl / Inputs diff --git a/app/M-OSC/Waveforms.bin b/app/M-OSC/Waveforms.bin index e57e7cb..f4d5fc4 100644 Binary files a/app/M-OSC/Waveforms.bin and b/app/M-OSC/Waveforms.bin differ diff --git a/app/M-OSC/Waveforms.cpp b/app/M-OSC/Waveforms.cpp index c9bd4cd..1532dc0 100644 --- a/app/M-OSC/Waveforms.cpp +++ b/app/M-OSC/Waveforms.cpp @@ -36,6 +36,7 @@ #include "braids/settings.h" #include "braids/vco_jitter_source.h" +#include "braids/quantizer.h" #include using namespace braids; @@ -44,6 +45,25 @@ MacroOscillator osc1; MacroOscillator osc2; Envelope envelope; VcoJitterSource jitter_source; +braids::Quantizer quantizer; + +void Quantizer::Init() +{} + +bool Quantizer::enabled() { + return engine::qz_enabled(); +} + +int32_t Quantizer::Process(int32_t pitch, int32_t root, int8_t *note) +{ + auto ret = engine::qz_process(pitch, root + (PITCH_PER_OCTAVE * 8), note); + return ret; +} + +int16_t Quantizer::Lookup(uint8_t index) +{ + return engine::qz_lookup(index) + (PITCH_PER_OCTAVE * 8); +} uint8_t sync_samples[FRAME_BUFFER_SIZE] = {}; @@ -61,12 +81,12 @@ void engine::setup() osc2.Init(); jitter_source.Init(); envelope.Init(); + quantizer.Init(); // std::fill(&sync_samples[0], &sync_samples[FRAME_BUFFER_SIZE], 0); // settings.SetValue(SETTING_AD_VCA, true); settings.SetValue(SETTING_SAMPLE_RATE, 5); - settings.SetValue(SETTING_PITCH_OCTAVE, 4); settings.SetValue(SETTING_PITCH_RANGE, PITCH_RANGE_EXTERNAL); engine::addParam(V_OCT, &_pitch, -4 * PITCH_PER_OCTAVE, 4 * PITCH_PER_OCTAVE); // is added internal to engine::cv @@ -98,7 +118,7 @@ void engine::process() uint32_t ad_value = envelope.Render(); int32_t pitchV = engine::cv_i32(); - int32_t pitch = pitchV + (DEFAULT_NOTE + 12) * 128; + int32_t pitch = pitchV + (PITCH_PER_OCTAVE * 8); // if (!settings.meta_modulation()) // { @@ -124,7 +144,7 @@ void engine::process() osc2.set_shape((braids::MacroOscillatorShape)_shape); osc1.set_parameters(_timbre >> 1, _color >> 1); - osc1.set_pitch(pitch + settings.pitch_transposition()); + osc1.set_pitch(pitch); auto audio_samples = engine::outputBuffer_i16<0>(); osc1.Render(sync_samples, audio_samples, FRAME_BUFFER_SIZE); @@ -146,7 +166,7 @@ void engine::process() color = _color - stereo; osc2.set_parameters((timbre >> 1), (color >> 1)); - osc2.set_pitch(pitch + settings.pitch_transposition() + stereo); + osc2.set_pitch(pitch + stereo); auto audio_samples = engine::outputBuffer_i16<1>(); osc2.Render(sync_samples, audio_samples, FRAME_BUFFER_SIZE); @@ -187,4 +207,8 @@ void engine::draw() #include "braids/settings.cc" #include "braids/macro_oscillator.cc" #include "braids/resources.cc" -#include "stmlib/utils/random.cc" \ No newline at end of file +#include "stmlib/utils/random.cc" + +#define chords chords2 +#define mini_wave_line mini_wave_line2 +#include "braids/chords_stack.cc" diff --git a/app/NOISE/WhitePink.cpp b/app/NOISE/WhitePink.cpp index 6dcd4f3..3f8c884 100644 --- a/app/NOISE/WhitePink.cpp +++ b/app/NOISE/WhitePink.cpp @@ -23,7 +23,7 @@ // See http://creativecommons.org/licenses/MIT/ for more information. // -// ENGINE_NAME:NOISE/White/Pink +// ENGINE_NAME:NOISE/WhitePink #include "../squares-and-circles-api.h" #include "misc/noise.hxx" diff --git a/app/SEQ/EuclidArp.bin b/app/SEQ/EuclidArp.bin index 3fa36d0..05f1b2a 100644 Binary files a/app/SEQ/EuclidArp.bin and b/app/SEQ/EuclidArp.bin differ diff --git a/app/SEQ/EuclidArp.cpp b/app/SEQ/EuclidArp.cpp index c7a767f..77ffba2 100644 --- a/app/SEQ/EuclidArp.cpp +++ b/app/SEQ/EuclidArp.cpp @@ -417,7 +417,7 @@ void engine::process() _cv_out = _cv; } - _cv_out = (float)engine::cv_quantize(engine::cv_i32() + _cv_out * PITCH_PER_OCTAVE) / PITCH_PER_OCTAVE; + _cv_out = (float)engine::qz_process(engine::cv_i32() + _cv_out * PITCH_PER_OCTAVE, 0, nullptr) / PITCH_PER_OCTAVE; if (engine::t() % 20 == 0) { @@ -426,7 +426,7 @@ void engine::process() scope_pos -= LEN_OF(scope); } - std::fill_n(engine::outputBuffer<1>(), FRAME_BUFFER_SIZE, _cv_out); + std::fill_n(engine::outputBuffer_i16<1>(), FRAME_BUFFER_SIZE, _cv_out * PITCH_PER_OCTAVE); std::fill_n(engine::outputBuffer_i16<0>(), FRAME_BUFFER_SIZE, trig); } diff --git a/app/SEQ/TuringMachine.bin b/app/SEQ/TuringMachine.bin index 1d294cb..b47aec7 100644 Binary files a/app/SEQ/TuringMachine.bin and b/app/SEQ/TuringMachine.bin differ diff --git a/app/SEQ/TuringMachine.cpp b/app/SEQ/TuringMachine.cpp index d0bc6f9..7917f11 100644 --- a/app/SEQ/TuringMachine.cpp +++ b/app/SEQ/TuringMachine.cpp @@ -54,7 +54,20 @@ inline uint32_t Rand() return _rng_state; } -const char *pulse_modes[17] = {}; +const char *pulse_modes[21] = {}; + +int32_t note_from_scale(uint8_t bits) +{ + //origin: https://github.com/Chysn/O_C-HemisphereSuite/blob/893deeb27ecacddad638ff9180f244a411623e35/software/o_c_REV/enigma/EnigmaOutput.h#L104 + + uint8_t mask = 0; + for (uint8_t s = 0; s < bits; s++) + mask |= (0x01 << s); + int note_shift = bits == 7 ? 0 : 64; // Note types under 7-bit start at Middle C + int note_number = (_turing_byte & mask) + note_shift; + CONSTRAIN(note_number, 0, 127); + return engine::qz_lookup(note_number); +} int32_t output(uint32_t mode, const char **name) { @@ -139,13 +152,37 @@ int32_t output(uint32_t mode, const char **name) } case 15: { - *name = "CV"; + *name = "CV_5V"; return (int32_t)_turing_byte * (5 * PITCH_PER_OCTAVE / 255); } case 16: { - *name = "CV-INV"; - return (int32_t)(255-_turing_byte) * (5 * PITCH_PER_OCTAVE / 255); + *name = "NOTE-7"; + return note_from_scale(7); + } + case 17: + { + *name = "NOTE-6"; + return note_from_scale(6); + } + case 18: + { + *name = "NOTE-5"; + return note_from_scale(5); + } + case 19: + { + *name = "NOTE-4"; + return note_from_scale(4); + } + case 20: + { + *name = "NOTE-3"; + return note_from_scale(3); + } + case LEN_OF(pulse_modes): + { + break; } } @@ -224,15 +261,16 @@ void engine::process() cv1 = 5 * PITCH_PER_OCTAVE; trig_pulse1 = clock::samples_per_step() / 2; } + else + cv1 += engine::cv_i32(); if (cv2 == INT16_MAX) { cv2 = 5 * PITCH_PER_OCTAVE; trig_pulse2 = clock::samples_per_step() / 2; } - - cv1 = engine::cv_quantize(cv1) + engine::cv_i32(); - cv2 = engine::cv_quantize(cv2) + engine::cv_i32(); + else + cv2 += engine::cv_i32(); } if (trig_pulse1 > 0) diff --git a/app/build.sh b/app/build.sh index 8844828..130c6e7 100755 --- a/app/build.sh +++ b/app/build.sh @@ -11,14 +11,13 @@ fi pip install jinja2 pyelftools elf_size_analyze --upgrade pip -if [ "$1" = "--rebuild" ]; then - find "${SCRIPT_PATH}" -type f -name *.bin -exec touch {} + +if [[ "$1" == "--rebuild" ]]; then + find "${SCRIPT_PATH}/" -type f -name "*.bin" -print0 -exec touch {} + fi for f in $(find "${SCRIPT_PATH}" -mindepth 2 -maxdepth 2 -type f -name '*.cpp'); do X="${f%.*}" - if [ -f $oo ] && [ "$(date -R -r $X.bin)" = "$(date -R -r $f)" ]; then continue fi diff --git a/app/squares-and-circles-api.h b/app/squares-and-circles-api.h index 6453cf3..1d6a30b 100644 --- a/app/squares-and-circles-api.h +++ b/app/squares-and-circles-api.h @@ -298,6 +298,11 @@ namespace engine constexpr uint32_t PARAM_MODULATION = 0x2; EXTERN_C uint32_t getParamFlags(const void *valuePtr); + EXTERN_C void selectParam(const void *val); + inline uint32_t isParamModulated(const void *valuePtr) + { + return getParamFlags(valuePtr) & PARAM_MODULATION; + } inline uint32_t isParamSelected(const void *valuePtr) { return getParamFlags(valuePtr) & PARAM_SELECTED; @@ -309,7 +314,11 @@ namespace engine EXTERN_C void *dsp_sample_Am6070(const uint8_t *data, int len, int sample_rate, int amp_mul); EXTERN_C void dsp_set_sample_pos(void *smpl, float pos, float amplitude, float decay); EXTERN_C void dsp_process_sample(void *smpl, float start, float end, float pitch, float output[FRAME_BUFFER_SIZE]); - EXTERN_C int32_t cv_quantize(int32_t cv); + + EXTERN_C bool qz_enabled(); + EXTERN_C const char* qz_name(); + EXTERN_C int32_t qz_process(int32_t pitch, int32_t root, int8_t *note); + EXTERN_C int16_t qz_lookup(int8_t note); //0-127 note-values } enum EventType : uint16_t diff --git a/app/upload.py b/app/upload.py index 85fc40d..a5b58d4 100755 --- a/app/upload.py +++ b/app/upload.py @@ -112,26 +112,46 @@ def sendFLASHDATA(name, data0): engines = json.loads(engines) +print(json.dumps(engines, indent=4)) +# exit(0) + + +def get_appid(binfile): + with open(binfile, "rb") as f: + data = f.read() + l = int.from_bytes(data[4:6], byteorder="little") # num_lot + r = int.from_bytes(data[6:8], byteorder="little") # num_rels + a = int.from_bytes(data[8:12], byteorder="little") # symt_size + b = int.from_bytes(data[12:16], byteorder="little") # code_size + c = int.from_bytes(data[16:20], byteorder="little") # data_size + d = int.from_bytes(data[20:24], byteorder="little") # bss_size + sym_off = int(int(24) + (r * 2 * 4)) + name_off = ( + int.from_bytes(data[sym_off + 4 : sym_off + 8], "little", signed=False) + & 0x0FFFFFFF + ) + sym_off + name = data[name_off : name_off + 24].decode("utf-8").split("\0")[0] + return name + apps_json = os.path.dirname(__file__) + "/index.json" with open(apps_json) as f: - end = 0 + apps = json.load(f) j = 0 for file in apps["apps"]: bin_file = os.path.dirname(apps_json) + "/" + str(file) if not os.path.exists(bin_file): continue + app_id = get_appid(bin_file) # os.path.splitext(file)[0] + print("name:", app_id) bin_size = os.path.getsize(bin_file) with open(bin_file, "rb") as f: crc32sum = zlib.crc32(f.read()) engine = next( - (e for e in engines if e["id"].startswith(os.path.splitext(file)[0])), + (e for e in engines if e["id"] == app_id), None, ) - if engine != None: - end = int(engine["addr"], 16) + int(engine["size"]) - if engine != None and engine["crc32"] == "%x" % crc32sum: print( engine["addr"], @@ -146,13 +166,18 @@ def sendFLASHDATA(name, data0): if engine == None: - offset = max((int(e["addr"], 16) + int(e["size"])) for e in engines) - offset += 4096 - (offset % 4096) + offset = int(1024 * 1024 / 2) + if len(engines) > 0: + offset = max((int(e["addr"], 16) + int(e["size"])) for e in engines) + offset += 4096 - (offset % 4096) engine = {} + engine["id"] = app_id engine["addr"] = "%x" % offset engine["size"] = "%s" % bin_size - print("TODO - add new engine...", "0x%x" % offset) + print("TODO - add new engine...", "0x%x" % offset, bin_file) + engines.append(engine) # continue + # exit(0) print( os.path.splitext(file)[0], "%x" % crc32sum, bin_size - int(engine["size"]) @@ -176,8 +201,8 @@ def sendFLASHDATA(name, data0): offset += 4 - (offset % 4) - if True: - offset = end #max((int(e["addr"], 16) + int(e["size"])) for e in engines) + if len(engines) > 0: + offset = max((int(e["addr"], 16) + int(e["size"])) for e in engines) offset += 4096 - (offset % 4096) print("END 0x%x" % offset) for chunk in chunk_bytes(bytearray(b"\xff") * 4096, 4096): diff --git a/lib/braids/README.md b/lib/braids/README.md new file mode 100644 index 0000000..30c7fab --- /dev/null +++ b/lib/braids/README.md @@ -0,0 +1,130 @@ +# Synthesis Models + +## Classic Analog Waveforms + +| # | Model | Description | Timbre | Color | +|----|-------------|-------------------------------|--------------------|---------------------| +| 0 | `CSAW` | CS-80 imperfect saw | Notch width | Notch polarity | +| 1 | `/\/\|-_-_` | Variable waveshape | Waveshape | Distortion/filter | +| 2 | `/\|/\|-_-_`| Classic saw-tooth/square | Pulse width | Saw to square | +| 3 | `FOLD` | Sine/triangle into wavefolder | Wavefolder amount | Sine to triangle | + +--- + +## Digital Synthesis + +| # | Model | Description | Timbre | Color | +|----|-------------|--------------------------------------|--------------------------|-------------------------| +| 4 | `_\|_\|_\|_`| 2 detuned harmonic combs | Smoothness | Detune | +| 5 | `SUB-_` | | | | +| 6 | `SUB/_` | | | | +| 7 | `SYN-_` | 2 VCOs with hardsync | VCO frequency ratio | VCO balance | +| 8 | `SYN/\|` | 2 VCOs with hardsync | VCO frequency ratio | VCO balance | +| 9 | `/\|/\| x3` | Triple saw waves | Osc. 2 detune | Osc. 3 detune | +| 10 | `-_ x3` | Triple square waves | Osc. 2 detune | Osc. 3 detune | +| 11 | `/\ x3` | Triple triangle waves | Osc. 2 detune | Osc. 3 detune | +| 12 | `SI x3` | Triple sine waves | Osc. 2 detune | Osc. 3 detune | +| 13 | `RING` | 3 ring-modulated sine waves | 2/1 frequency ratio | 3/1 frequency ratio | +| 14 | `/\|/\|/\|/\|` | Swarm of 7 sawtooth waves | Detune | High-pass filter | +| 15 | `/\|/\|_\|_\|` | Comb filtered sawtooth | Delay time | Neg./pos. feedback | +| 16 | `TOY*` | Low-fi circuitbent sounds | Sample reduction | Bit toggling | +| 17 | `ZLPF` | Direct synthesis of LP waveform | Cutoff frequency | Waveshape | +| 18 | `ZPKF` | Direct synthesis of Peaking waveform | Cutoff frequency | Waveshape | +| 19 | `ZBPF` | Direct synthesis of BP waveform | Cutoff frequency | Waveshape | +| 20 | `ZHPF` | Direct synthesis of HP waveform | Cutoff frequency | Waveshape | +| 21 | `VOSM` | Sawtooth with 2 formants | Formant 1 frequency | Formant 2 frequency | + +--- + +## Vocal Synthesis + +| # | Model | Description | Timbre | Color | +|----|-------------|----------------------------------|---------------|--------------------| +| 22 | `VOWL` | Vowel synthesis (a, e, i, o, u) | Vowel shape | Gender | +| 23 | `VFOF` | Hi-fi vowel synthesis | Air pressure | Instrument shape | + +--- + +## Additive Synthesis + +| # | Model | Description | Timbre | Color | +|----|-------------|----------------------------|-----------------|---------------------| +| 24 | `HARM` | Additive synth, 14 harmonics | Harmonic # | Spectral peakedness | + +--- + +## Frequency Modulation (FM) + +| # | Model | Description | Timbre | Color | +|----|-------------|----------------------------------------|-----------------------|----------------------| +| 25 | `FM` | Plain 2-operator FM | Modulation index | Frequency ratio | +| 26 | `FBFM` | Feedback 2-operator FM | Modulation index | Frequency ratio | +| 27 | `WTFM` | Chaotic 2-operator FM | Modulation index | Frequency ratio | + +--- + +## Physical Simulations + +| # | Model | Description | Timbre | Color | +|----|-------------|-----------------------------|-----------------------|---------------------| +| 28 | `PLUK` | Plucked strings | Decay | Plucking position | +| 29 | `BOWD` | Bowed string | Friction | Bowing position | +| 30 | `BLOW` | Reed simulation | Air pressure | Instrument geometry | +| 31 | `FLUT` | Flute simulation | Air pressure | Instrument geometry | + +--- + +## Percussions + +| # | Model | Description | Timbre | Color | +|----|-------------|-----------------------------|-----------------------|---------------------| +| 32 | `BELL` | Bell sound | Decay | Harmonicity | +| 33 | `DRUM` | Metallic drum sound | Decay | Harmonicity | +| 34 | `KICK` | 808 bass drum | Decay | Brightness | +| 35 | `CYMB` | Cymbal noise | Cutoff | Noisiness | +| 36 | `SNAR` | 808 snare drum | Tone | Noisiness/decay | + +--- + +## Wavetables + +| # | Model | Description | Timbre | Color | +|----|-------------|-------------------------------|-----------------------|---------------------| +| 37 | `WTBL` | 21 wavetables | Smooth wavetable position | Quantized selection | +| 38 | `WMAP` | 16x16 waves | X position | Y position | +| 39 | `WLIN` | Linear wavetable scanning | Wavetable position | Interpolation quality | +| 40 | `WTx4` | Polyphonic wavetable | Wavetable position | Chord type | + +--- + +## Noise + +| # | Model | Description | Timbre | Color | +|----|-------------|-----------------------------|-----------------------|---------------------| +| 41 | `NOIS` | Tuned noise (2-pole filter) | Filter resonance | Response (LP to HP) | +| 42 | `TWNQ` | Noise sent to 2 resonators | Resonance | Resonators freq. ratio | +| 43 | `CLKN` | Clocked digital noise | Cycle length | Quantization | +| 44 | `CLOU` | Sinusoidal granular synthesis | Grain density | Frequency dispersion | +| 45 | `PRTC` | Droplets granular synthesis | Grain density | Frequency dispersion | +| 46 | `QPSK` | Modem noises | Bit-rate | Modulated data | + +## EASTER EGG + +| # | Model | Description | Timbre | Color | +|----|-------------|-----------------------------|-----------------------|---------------------| +| 47 | `****` | | | | + +## Braids Renaissance Chords (https://burns.ca/eurorack.html) + +| # | Model | Description | Timbre | Color | +|----|-------------|-----------------------------|-----------------------|---------------------| +| 48 | `//CH` | | | | +| 49 | `-_CH` | | | | +| 50 | `/\\CH` | | | | +| 51 | `SICH` | | | | +| 52 | `WTCH` | | | | +| 53 | `//x6` | | | | +| 54 | `-_x6` | | | | +| 55 | `/\\x6` | | | | +| 56 | `SIx6` | | | | +| 57 | `WTx6` | | | | \ No newline at end of file diff --git a/lib/braids/chords_stack.cc b/lib/braids/chords_stack.cc new file mode 100644 index 0000000..37ee90f --- /dev/null +++ b/lib/braids/chords_stack.cc @@ -0,0 +1,606 @@ +//https://github.com/boourns/eurorack/blob/master/braids/stack.cc + +#include "braids/digital_oscillator.h" + +#include +#include + +#include "stmlib/utils/dsp.h" +#include "stmlib/utils/random.h" + +#include "braids/parameter_interpolation.h" +#include "braids/resources.h" +#include "braids/quantizer.h" + +extern braids::Quantizer quantizer; + +namespace braids { + +using namespace stmlib; + +const int kStackSize = 6; + +#define CALC_SINE(phase) Interpolate88(ws_sine_fold, (Interpolate824(wav_sine, phase) * gain >> 15) + 32768); + +inline void renderChordSine( + DigitalOscillatorState& state_, + int16_t parameter_[2], + const uint8_t *sync, + int16_t *buffer, + size_t size, + uint32_t *phase_increment, + uint8_t noteCount) { + + uint32_t phase_0, phase_1, phase_2, phase_3, phase_4; + int16_t gain = 2048 + (parameter_[0] * 30720 >> 15); + + phase_0 = state_.stack.phase[0]; + phase_1 = state_.stack.phase[1]; + phase_2 = state_.stack.phase[2]; + phase_3 = state_.stack.phase[3]; + phase_4 = state_.stack.phase[4]; + + while (size) { + int32_t sample = 0; + + phase_0 += phase_increment[0]; + phase_1 += phase_increment[1]; + phase_2 += phase_increment[2]; + phase_3 += phase_increment[3]; + phase_4 += phase_increment[4]; + + sample = CALC_SINE(phase_0); + + sample += CALC_SINE(phase_1); + sample += CALC_SINE(phase_2); + sample += CALC_SINE(phase_3); + + if (noteCount > 4) { + sample += CALC_SINE(phase_4); + } + + sample = (sample >> 3) + (sample >> 5); + CLIP(sample) + *buffer++ = sample; + + phase_0 += phase_increment[0]; + phase_1 += phase_increment[1]; + phase_2 += phase_increment[2]; + phase_3 += phase_increment[3]; + phase_4 += phase_increment[4]; + + sample = CALC_SINE(phase_0); + + sample += CALC_SINE(phase_1); + sample += CALC_SINE(phase_2); + sample += CALC_SINE(phase_3); + + if (noteCount > 4) { + sample += CALC_SINE(phase_4); + } + + sample = (sample >> 3) + (sample >> 5); + CLIP(sample) + *buffer++ = sample; + + size -= 2; + } + + state_.stack.phase[0] = phase_0; + state_.stack.phase[1] = phase_1; + state_.stack.phase[2] = phase_2; + state_.stack.phase[3] = phase_3; + state_.stack.phase[4] = phase_4; +} + +inline void renderChordSaw( + DigitalOscillatorState& state_, + int16_t parameter_[2], + const uint8_t *sync, + int16_t *buffer, + size_t size, + uint32_t *phase_increment, + uint8_t noteCount) { + + uint32_t phase_0, phase_1, phase_2, phase_3, phase_4, phase_5; + + uint32_t detune = 0; + + for (int i = 0; i < 2; i++) { + phase_0 = state_.stack.phase[(i*6)+0]; + phase_1 = state_.stack.phase[(i*6)+1]; + phase_2 = state_.stack.phase[(i*6)+2]; + phase_3 = state_.stack.phase[(i*6)+3]; + phase_4 = state_.stack.phase[(i*6)+4]; + phase_5 = state_.stack.phase[(i*6)+5]; + + if (i == 1) { + detune = parameter_[0]<<3; + } + + int16_t *b = buffer; + size_t s = size; + + while (s) { + int32_t sample = 0; + + phase_0 += phase_increment[0] + detune; + phase_1 += phase_increment[1] - detune; + phase_2 += phase_increment[2] + detune; + phase_3 += phase_increment[3] - detune; + phase_4 += phase_increment[4] + detune; + phase_5 += phase_increment[5] - detune; + + sample += (1 << 15) - (phase_0 >> 16); + sample += (1 << 15) - (phase_1 >> 16); + sample += (1 << 15) - (phase_2 >> 16); + sample += (1 << 15) - (phase_3 >> 16); + + if (noteCount > 4) { + sample += (1 << 15) - (phase_4 >> 16); + } + if (noteCount > 5) { + sample += (1 << 15) - (phase_5 >> 16); + } + + sample = (sample >> 2) + (sample >> 5); + CLIP(sample) + if (i == 0) { + *b++ = sample >> 1; + } else { + *b += sample >> 1; + b++; + } + + phase_0 += phase_increment[0] + detune; + phase_1 += phase_increment[1] - detune; + phase_2 += phase_increment[2] + detune; + phase_3 += phase_increment[3] - detune; + phase_4 += phase_increment[4] + detune; + phase_5 += phase_increment[5] - detune; + + sample = (1 << 15) - (phase_0 >> 16); + sample += (1 << 15) - (phase_1 >> 16); + sample += (1 << 15) - (phase_2 >> 16); + sample += (1 << 15) - (phase_3 >> 16); + + if (noteCount > 4) { + sample += (1 << 15) - (phase_4 >> 16); + } + if (noteCount > 5) { + sample += (1 << 15) - (phase_5 >> 16); + } + + sample = (sample >> 2) + (sample >> 5); + CLIP(sample) + if (i == 0) { + *b++ = sample >> 1; + } else { + *b += sample >> 1; + b++; + } + + s -= 2; + } + state_.stack.phase[(i*6)+0] = phase_0; + state_.stack.phase[(i*6)+1] = phase_1; + state_.stack.phase[(i*6)+2] = phase_2; + state_.stack.phase[(i*6)+3] = phase_3; + state_.stack.phase[(i*6)+4] = phase_4; + state_.stack.phase[(i*6)+5] = phase_5; + } +} + +// #define CALC_TRIANGLE_RAW(x) ((int16_t) ((((x >> 16) << 1) ^ (x & 0x80000000 ? 0xffff : 0x0000))) + 32768) +#define CALC_TRIANGLE(x) Interpolate88(ws_tri_fold, (calc_triangle_raw(x) * gain >> 15) + 32768) + +inline int16_t calc_triangle_raw(uint32_t phase) { + uint16_t phase_16 = phase >> 16; + int16_t triangle = (phase_16 << 1) ^ (phase_16 & 0x8000 ? 0xffff : 0x0000); + return triangle + 32768; +} + +inline void renderChordTriangle( + DigitalOscillatorState& state_, + int16_t parameter_[2], + const uint8_t *sync, + int16_t *buffer, + size_t size, + uint32_t *phase_increment, + uint8_t noteCount) { + uint32_t phase_0, phase_1, phase_2, phase_3, phase_4, phase_5; + + phase_0 = state_.stack.phase[0]; + phase_1 = state_.stack.phase[1]; + phase_2 = state_.stack.phase[2]; + phase_3 = state_.stack.phase[3]; + phase_4 = state_.stack.phase[4]; + phase_5 = state_.stack.phase[5]; + + int16_t gain = 2048 + (parameter_[0] * 30720 >> 15); + + while (size) { + int32_t sample = 0; + + phase_0 += phase_increment[0]; + phase_1 += phase_increment[1]; + phase_2 += phase_increment[2]; + phase_3 += phase_increment[3]; + phase_4 += phase_increment[4]; + phase_5 += phase_increment[5]; + + sample = CALC_TRIANGLE(phase_0); + sample += CALC_TRIANGLE(phase_1); + sample += CALC_TRIANGLE(phase_2); + sample += CALC_TRIANGLE(phase_3); + + if (noteCount > 4) { + sample += CALC_TRIANGLE(phase_4); + } + if (noteCount > 5) { + sample += CALC_TRIANGLE(phase_5); + } + + sample = (sample >> 3) + (sample >> 5); + CLIP(sample) + *buffer++ = sample; + + phase_0 += phase_increment[0]; + phase_1 += phase_increment[1]; + phase_2 += phase_increment[2]; + phase_3 += phase_increment[3]; + phase_4 += phase_increment[4]; + phase_5 += phase_increment[5]; + + sample = CALC_TRIANGLE(phase_0); + sample += CALC_TRIANGLE(phase_1); + sample += CALC_TRIANGLE(phase_2); + sample += CALC_TRIANGLE(phase_3); + + if (noteCount > 4) { + sample += CALC_TRIANGLE(phase_4); + } + if (noteCount > 5) { + sample += CALC_TRIANGLE(phase_5); + } + + sample = (sample >> 3) + (sample >> 5); + CLIP(sample) + *buffer++ = sample; + + size -= 2; + } + + state_.stack.phase[0] = phase_0; + state_.stack.phase[1] = phase_1; + state_.stack.phase[2] = phase_2; + state_.stack.phase[3] = phase_3; + state_.stack.phase[4] = phase_4; + state_.stack.phase[5] = phase_5; +} + +#define CALC_SQUARE(x, width) ((x > width) ? 5400 : -5400) + +inline void renderChordSquare( + DigitalOscillatorState& state_, + int16_t parameter_[2], + const uint8_t *sync, + int16_t *buffer, + size_t size, + uint32_t *phase_increment, + uint8_t noteCount) { + + uint32_t phase_0, phase_1, phase_2, phase_3, phase_4, phase_5; + uint32_t pw = parameter_[0] << 16; + + phase_0 = state_.stack.phase[0]; + phase_1 = state_.stack.phase[1]; + phase_2 = state_.stack.phase[2]; + phase_3 = state_.stack.phase[3]; + phase_4 = state_.stack.phase[4]; + phase_5 = state_.stack.phase[5]; + + while (size) { + int32_t sample = 0; + + phase_0 += phase_increment[0]; + phase_1 += phase_increment[1]; + phase_2 += phase_increment[2]; + phase_3 += phase_increment[3]; + + sample = CALC_SQUARE(phase_0, pw); + sample += CALC_SQUARE(phase_1, pw); + sample += CALC_SQUARE(phase_2, pw); + sample += CALC_SQUARE(phase_3, pw); + + if (noteCount > 4) { + sample += CALC_SQUARE(phase_4, pw); + } + if (noteCount > 5) { + sample += CALC_SQUARE(phase_5, pw); + } + + CLIP(sample) + *buffer++ = sample; + + phase_0 += phase_increment[0]; + phase_1 += phase_increment[1]; + phase_2 += phase_increment[2]; + phase_3 += phase_increment[3]; + + sample = CALC_SQUARE(phase_0, pw); + sample += CALC_SQUARE(phase_1, pw); + sample += CALC_SQUARE(phase_2, pw); + sample += CALC_SQUARE(phase_3, pw); + + if (noteCount > 4) { + sample += CALC_SQUARE(phase_4, pw); + } + if (noteCount > 5) { + sample += CALC_SQUARE(phase_5, pw); + } + + CLIP(sample) + *buffer++ = sample; + + size -= 2; + } + + state_.stack.phase[0] = phase_0; + state_.stack.phase[1] = phase_1; + state_.stack.phase[2] = phase_2; + state_.stack.phase[3] = phase_3; + state_.stack.phase[4] = phase_4; + state_.stack.phase[5] = phase_5; +} + +const uint8_t mini_wave_line[] = { + 157, 161, 171, 188, 189, 191, 192, 193, 196, 198, 201, 234, 232, + 229, 226, 224, 1, 2, 3, 4, 5, 8, 12, 32, 36, 42, 47, 252, 254, 141, 139, + 135, 174 +}; + +#define SEMI * 128 + +const uint16_t chords[17][3] = { + { 2, 4, 6 }, + { 16, 32, 48 }, + { 2 SEMI, 7 SEMI, 12 SEMI }, + { 3 SEMI, 7 SEMI, 10 SEMI }, + { 3 SEMI, 7 SEMI, 12 SEMI }, + { 3 SEMI, 7 SEMI, 14 SEMI }, + { 3 SEMI, 7 SEMI, 17 SEMI }, + { 7 SEMI, 12 SEMI, 19 SEMI }, + { 7 SEMI, 3 + 12 SEMI, 5 + 19 SEMI }, + { 4 SEMI, 7 SEMI, 17 SEMI }, + { 4 SEMI, 7 SEMI, 14 SEMI }, + { 4 SEMI, 7 SEMI, 12 SEMI }, + { 4 SEMI, 7 SEMI, 11 SEMI }, + { 5 SEMI, 7 SEMI, 12 SEMI }, + { 4, 7 SEMI, 12 SEMI }, + { 4, 4 + 12 SEMI, 12 SEMI }, + { 4, 4 + 12 SEMI, 12 SEMI }, +}; + +inline void renderChordWavetable( + DigitalOscillatorState& state_, + int16_t parameter_[2], + const uint8_t *sync, + int16_t *buffer, + size_t size, + uint32_t *phase_increment, + uint8_t noteCount) { + + const uint8_t* wave_1 = wt_waves + mini_wave_line[parameter_[0] >> 10] * 129; + const uint8_t* wave_2 = wt_waves + mini_wave_line[(parameter_[0] >> 10) + 1] * 129; + uint16_t wave_xfade = parameter_[0] << 6; + uint32_t phase_0, phase_1, phase_2, phase_3, phase_4; + + phase_0 = state_.stack.phase[0]; + phase_1 = state_.stack.phase[1]; + phase_2 = state_.stack.phase[2]; + phase_3 = state_.stack.phase[3]; + phase_4 = state_.stack.phase[4]; + + while (size) { + int32_t sample = 0; + + phase_0 += phase_increment[0]; + phase_1 += phase_increment[1]; + phase_2 += phase_increment[2]; + phase_3 += phase_increment[3]; + phase_4 += phase_increment[4]; + + sample = Crossfade(wave_1, wave_2, phase_0 >> 1, wave_xfade); + sample += Crossfade(wave_1, wave_2, phase_1 >> 1, wave_xfade); + sample += Crossfade(wave_1, wave_2, phase_2 >> 1, wave_xfade); + sample += Crossfade(wave_1, wave_2, phase_3 >> 1, wave_xfade); + if (noteCount > 4) { + sample += Crossfade(wave_1, wave_2, phase_4 >> 1, wave_xfade); + } + + sample = (sample >> 2); + CLIP(sample) + *buffer++ = sample; + + phase_0 += phase_increment[0]; + phase_1 += phase_increment[1]; + phase_2 += phase_increment[2]; + phase_3 += phase_increment[3]; + phase_4 += phase_increment[4]; + + sample = Crossfade(wave_1, wave_2, phase_0 >> 1, wave_xfade); + sample += Crossfade(wave_1, wave_2, phase_1 >> 1, wave_xfade); + sample += Crossfade(wave_1, wave_2, phase_2 >> 1, wave_xfade); + sample += Crossfade(wave_1, wave_2, phase_3 >> 1, wave_xfade); + if (noteCount > 4) { + sample += Crossfade(wave_1, wave_2, phase_4 >> 1, wave_xfade); + } + + sample = (sample >> 2); + CLIP(sample) + *buffer++ = sample; + + size -= 2; + } + + state_.stack.phase[0] = phase_0; + state_.stack.phase[1] = phase_1; + state_.stack.phase[2] = phase_2; + state_.stack.phase[3] = phase_3; + state_.stack.phase[4] = phase_4; +} + +extern const uint16_t chords[17][3]; + +// without the attribute this gets build as-is AND inlined into RenderStack :/ +void DigitalOscillator::renderChord( + const uint8_t *sync, + int16_t *buffer, + size_t size, + uint8_t* noteOffset, + uint8_t noteCount) { + + int32_t fm = 0; + + if (strike_) { + for (size_t i = 0; i < kStackSize; ++i) { + state_.stack.phase[i] = Random::GetWord(); + } + strike_ = false; + } + + // Do not use an array here to allow these to be kept in arbitrary registers. + uint32_t phase_increment[6]; + + // indication we should use built in chords + if (noteCount == 0) { + noteCount = 4; + uint16_t chord_integral = parameter_[1] >> 11; + uint16_t chord_fractional = parameter_[1] << 5; + if (chord_fractional < 30720) { + chord_fractional = 0; + } else if (chord_fractional >= 34816) { + chord_fractional = 65535; + } else { + chord_fractional = (chord_fractional - 30720) * 16; + } + + phase_increment[0] = phase_increment_; + for (size_t i = 0; i < 3; ++i) { + uint16_t detune_1 = chords[chord_integral][i]; + uint16_t detune_2 = chords[chord_integral + 1][i]; + uint16_t detune = detune_1 + ((detune_2 - detune_1) * chord_fractional >> 16); + phase_increment[i+1] = DigitalOscillator::ComputePhaseIncrement(pitch_ + detune); + } + } else { + if (quantizer.enabled()) { + int8_t index = 0; + fm = pitch_ - quantizer.Process(pitch_, 0, &index); + + phase_increment[0] = phase_increment_; + for (size_t i = 1; i < noteCount; i++) { + index = (index + noteOffset[i-1]); + if (index >= 128) { + noteCount = i; + break; + } + phase_increment[i] = DigitalOscillator::ComputePhaseIncrement(quantizer.Lookup(index) + fm); + } + } else { + phase_increment[0] = phase_increment_; + for (size_t i = 1; i < noteCount; i++) { + phase_increment[i] = DigitalOscillator::ComputePhaseIncrement(pitch_ + (noteOffset[i-1]<<7)); + } + } + } + + if (shape_ == OSC_SHAPE_STACK_SAW || shape_ == OSC_SHAPE_CHORD_SAW) { + renderChordSaw(state_, parameter_, sync, buffer, size, phase_increment, noteCount); + } else if (shape_ == OSC_SHAPE_STACK_WAVETABLE || shape_ == OSC_SHAPE_CHORD_WAVETABLE) { + renderChordWavetable(state_, parameter_, sync, buffer, size, phase_increment, noteCount); + } else if (shape_ == OSC_SHAPE_STACK_TRIANGLE || shape_ == OSC_SHAPE_CHORD_TRIANGLE) { + renderChordTriangle(state_, parameter_, sync, buffer, size, phase_increment, noteCount); + } else if (shape_ == OSC_SHAPE_STACK_SQUARE || shape_ == OSC_SHAPE_CHORD_SQUARE) { + renderChordSquare(state_, parameter_, sync, buffer, size, phase_increment, noteCount); + } else { + renderChordSine(state_, parameter_, sync, buffer, size, phase_increment, noteCount); + } +} + +// number of notes, followed by offsets +const uint8_t diatonic_chords[16][4] = { + {1, 7, 0, 0}, // octave + {2, 5, 7, 0}, // octave add6 + {1, 6, 0, 0}, // 7th + {2, 5, 6, 0}, // 7th add6 + {2, 6, 8, 0}, // 9th + {3, 6, 8, 10}, // 11th + {3, 5, 7, 10}, // 11th add6 + {1, 8, 0, 0}, // add9 + {2, 6, 10, 0}, // 7th add11 + {2, 6, 12, 0}, // 7th add13 + {1, 7, 0, 0}, // oct sus4 + {1, 6, 0, 0}, // 7th sus4 + {2, 6, 8, 0}, // 9th sus4 + {2, 6, 8, 0}, // 9th sus2 + {3, 8, 10, 6}, // 11th sus4 +}; + +void DigitalOscillator::RenderDiatonicChord( + const uint8_t* sync, + int16_t* buffer, + size_t size) { + + uint8_t extensionIndex = (parameter_[1] >> 12) & 0xf; + // uint8_t inversion = (parameter_[1] >> 13) & 0x7; + uint8_t offsets[6]; + uint8_t len = 0; + + if (quantizer.enabled()) { + offsets[1] = 4; + + if (extensionIndex < 11) { + offsets[0] = 2; + } else if (extensionIndex < 15) { + offsets[0] = 3; + } else { + offsets[0] = 1; + } + + len = diatonic_chords[extensionIndex][0]; + + for (size_t i = 0; i < len; i++) { + offsets[i+2] = diatonic_chords[extensionIndex][i+1]; + } + + len += 3; + } else { + // send len=0 as indication to use the phase offsets from the paraphonic chord array. + } + + renderChord(sync, buffer, size, offsets, len); +} + +void DigitalOscillator::RenderStack( + const uint8_t* sync, + int16_t* buffer, + size_t size) { + + uint8_t span = 1 + (parameter_[1] >> 11); + uint8_t offsets[kStackSize]; + uint8_t acc = 0; + uint8_t count = kStackSize-1; + uint8_t i = 0; + + for (; i < count; i++) { + acc += span; + offsets[i] = acc; + } + + // don't pass in kStackSize or gcc will render a second, optimized version of renderChord that + // knows noteCount is static. + renderChord(sync, buffer, size, offsets, i); +} + +} diff --git a/lib/braids/digital_oscillator.cc b/lib/braids/digital_oscillator.cc old mode 100644 new mode 100755 index 8e9b681..93046a3 --- a/lib/braids/digital_oscillator.cc +++ b/lib/braids/digital_oscillator.cc @@ -2562,7 +2562,18 @@ DigitalOscillator::RenderFn DigitalOscillator::fn_table_[] = { &DigitalOscillator::RenderDigitalModulation, // &DigitalOscillator::RenderYourAlgo, - &DigitalOscillator::RenderQuestionMark + &DigitalOscillator::RenderQuestionMark, + &DigitalOscillator::RenderDiatonicChord, + &DigitalOscillator::RenderDiatonicChord, + &DigitalOscillator::RenderDiatonicChord, + &DigitalOscillator::RenderDiatonicChord, + &DigitalOscillator::RenderDiatonicChord, + + &DigitalOscillator::RenderStack, + &DigitalOscillator::RenderStack, + &DigitalOscillator::RenderStack, + &DigitalOscillator::RenderStack, + &DigitalOscillator::RenderStack, }; } // namespace braids diff --git a/lib/braids/digital_oscillator.h b/lib/braids/digital_oscillator.h old mode 100644 new mode 100755 index ab9b5b1..562f033 --- a/lib/braids/digital_oscillator.h +++ b/lib/braids/digital_oscillator.h @@ -97,7 +97,21 @@ enum DigitalOscillatorShape { OSC_SHAPE_DIGITAL_MODULATION, - OSC_SHAPE_QUESTION_MARK_LAST + OSC_SHAPE_QUESTION_MARK_LAST, + + // Braids Renaissance https://burns.ca/eurorack.html + OSC_SHAPE_CHORD_SAW, + OSC_SHAPE_CHORD_SQUARE, + OSC_SHAPE_CHORD_TRIANGLE, + OSC_SHAPE_CHORD_SINE, + OSC_SHAPE_CHORD_WAVETABLE, + + OSC_SHAPE_STACK_SAW, + OSC_SHAPE_STACK_SQUARE, + OSC_SHAPE_STACK_TRIANGLE, + OSC_SHAPE_STACK_SINE, + OSC_SHAPE_STACK_WAVETABLE, + }; struct ResoSquareState { @@ -216,6 +230,11 @@ struct HatState { uint32_t rng_state; }; +struct StackState { + uint32_t phase[12]; + int16_t previous_sample; +}; + union DigitalOscillatorState { ResoSquareState res; VowelSynthesizerState vow; @@ -233,6 +252,7 @@ union DigitalOscillatorState { ClockedNoiseState clk; HatState hat; HarmonicsState hrm; + StackState stack; uint32_t modulator_phase; }; @@ -327,17 +347,27 @@ class DigitalOscillator { void RenderSnare(const uint8_t*, int16_t*, size_t); void RenderCymbal(const uint8_t*, int16_t*, size_t); void RenderQuestionMark(const uint8_t*, int16_t*, size_t); - + // void RenderYourAlgo(const uint8_t*, int16_t*, size_t); - - uint32_t ComputePhaseIncrement(int16_t midi_pitch); - uint32_t ComputeDelay(int16_t midi_pitch); - int16_t InterpolateFormantParameter( + void renderChord( + const uint8_t *sync, + int16_t *buffer, + size_t size, + uint8_t* noteOffset, + uint8_t noteCount); + void RenderStack(const uint8_t*, int16_t*, size_t); + void RenderDiatonicChord(const uint8_t*, int16_t*, size_t); + +public: + static uint32_t ComputePhaseIncrement(int16_t midi_pitch); + static uint32_t ComputeDelay(int16_t midi_pitch); + static int16_t InterpolateFormantParameter( const int16_t table[][kNumFormants][kNumFormants], int16_t x, int16_t y, uint8_t formant); - + +private: uint32_t phase_; uint32_t phase_increment_; uint32_t delay_; diff --git a/lib/braids/macro_oscillator.cc b/lib/braids/macro_oscillator.cc old mode 100644 new mode 100755 index b2d2a72..fa7313f --- a/lib/braids/macro_oscillator.cc +++ b/lib/braids/macro_oscillator.cc @@ -392,6 +392,16 @@ MacroOscillator::RenderFn MacroOscillator::fn_table_[] = { &MacroOscillator::RenderTriple, &MacroOscillator::RenderTriple, &MacroOscillator::RenderTriple, + &MacroOscillator::RenderDigital, + &MacroOscillator::RenderDigital, // Diatonic Chord 1-5 + &MacroOscillator::RenderDigital, + &MacroOscillator::RenderDigital, + &MacroOscillator::RenderDigital, + &MacroOscillator::RenderDigital, + &MacroOscillator::RenderDigital, // Stacks 1-5 + &MacroOscillator::RenderDigital, + &MacroOscillator::RenderDigital, + &MacroOscillator::RenderDigital, &MacroOscillator::RenderDigital, &MacroOscillator::RenderDigital, &MacroOscillator::RenderSawComb, diff --git a/lib/braids/quantizer.cc b/lib/braids/quantizer.cc index a94120c..84083a9 100644 --- a/lib/braids/quantizer.cc +++ b/lib/braids/quantizer.cc @@ -25,7 +25,7 @@ // ----------------------------------------------------------------------------- // // Note quantizer - +#include #include "braids/quantizer.h" #include @@ -34,7 +34,7 @@ namespace braids { void Quantizer::Init() { - enabled_ = true; + enabled_ = false; codeword_ = 0; previous_boundary_ = 0; next_boundary_ = 0; @@ -43,6 +43,14 @@ void Quantizer::Init() { } } +int16_t Quantizer::Lookup(uint8_t index) { + return codebook_[index]; +} + +bool Quantizer::enabled() { + return enabled_; +} + void Quantizer::Configure( const int16_t* notes, int16_t span, @@ -65,18 +73,16 @@ void Quantizer::Configure( ++octave; } } + } else { + Init(); } } -int32_t Quantizer::Process(int32_t pitch, int32_t root) { - if (!enabled_) { - return pitch; - } +int32_t Quantizer::Process(int32_t pitch, int32_t root, int8_t* note) { pitch -= root; if (pitch >= previous_boundary_ && pitch <= next_boundary_) { // We're still in the voronoi cell for the active codeword. - pitch = codeword_; } else { // Search for the nearest neighbour in the codebook. int16_t upper_bound_index = std::upper_bound( @@ -95,11 +101,17 @@ int32_t Quantizer::Process(int32_t pitch, int32_t root) { } } codeword_ = codebook_[q]; + last_note = q; // Enlarge the current voronoi cell a bit for hysteresis. previous_boundary_ = (9 * codebook_[q - 1] + 7 * codeword_) >> 4; next_boundary_ = (9 * codebook_[q + 1] + 7 * codeword_) >> 4; - pitch = codeword_; + if (enabled_) + pitch = codeword_; } + if (enabled_) + pitch = codeword_; + if(note != nullptr) + *note = last_note; pitch += root; return pitch; } diff --git a/lib/braids/quantizer.h b/lib/braids/quantizer.h index 9fe8327..bb78720 100644 --- a/lib/braids/quantizer.h +++ b/lib/braids/quantizer.h @@ -47,16 +47,23 @@ class Quantizer { void Init(); int32_t Process(int32_t pitch) { - return Process(pitch, 0); + return Process(pitch, 0, nullptr); } - int32_t Process(int32_t pitch, int32_t root); - + int32_t Process(int32_t pitch, int32_t root, int8_t* note); + void Configure(const Scale& scale) { Configure(scale.notes, scale.span, scale.num_notes); } - private: + + int16_t Lookup(uint8_t index); + void Configure(const int16_t* notes, int16_t span, size_t num_notes); + + bool enabled(); + + private: + int8_t last_note; bool enabled_; int16_t codebook_[128]; int32_t codeword_; diff --git a/lib/braids/settings.cc b/lib/braids/settings.cc old mode 100644 new mode 100755 index df3b3ca..9df7082 --- a/lib/braids/settings.cc +++ b/lib/braids/settings.cc @@ -145,21 +145,21 @@ const char* const zero_to_fifteen_values[] = { const char* const algo_values[] = { "CSAW", - "/\\-_", - "//-_", + "/\\-_", //"^\x88\x8D_" + "//-_", //"\x88\x8A\x8C\x8D" "FOLD", - "UUUU", - "SUB-", - "SUB/", - "SYN-", - "SYN/", - "//x3", - "-_x3", + "UUUU", //"\x8E\x8E\x8E\x8E", + "SUB-", //"SUB\x8C" + "SUB/", //"SUB\x88", + "SYN-", //"SYN\x8C" + "SYN/", //"SYN\x88", + "//x3", //"\x88\x88x3", + "-_x3", //"\x8C_x3", "/\\x3", "SIx3", "RING", - "////", - "//UU", + "////", //"\x88\x89\x88\x89", + "//UU", //"\x88\x88\x8E\x8E", "TOY*", "ZLPF", "ZPKF", @@ -191,8 +191,21 @@ const char* const algo_values[] = { "CLOU", "PRTC", "QPSK", - "****" + "****", // "NAME" // For your algorithm + + // Braids Renaissance https://burns.ca/eurorack.html + "//CH", // "\x88\x88" "CH", + "-_CH", // "\x8C_CH", + "/\\CH", + "SICH", + "WTCH", + "//x6", //"\x88\x88x6", + "-_x6", //"\x8C_x6", + "/\\x6", + "SIx6", + "WTx6", + }; const char* const bits_values[] = { diff --git a/lib/braids/settings.h b/lib/braids/settings.h index 8d68905..a13e7b5 100755 --- a/lib/braids/settings.h +++ b/lib/braids/settings.h @@ -49,6 +49,7 @@ enum MacroOscillatorShape { MACRO_OSC_SHAPE_TRIPLE_TRIANGLE, MACRO_OSC_SHAPE_TRIPLE_SINE, MACRO_OSC_SHAPE_TRIPLE_RING_MOD, + MACRO_OSC_SHAPE_SAW_SWARM, MACRO_OSC_SHAPE_SAW_COMB, MACRO_OSC_SHAPE_TOY, @@ -92,6 +93,20 @@ enum MacroOscillatorShape { MACRO_OSC_SHAPE_QUESTION_MARK, // MACRO_OSC_SHAPE_YOUR_ALGO + + // Braids Renaissance https://burns.ca/eurorack.html + MACRO_OSC_SHAPE_CHORD_SAW, + MACRO_OSC_SHAPE_CHORD_SQUARE, + MACRO_OSC_SHAPE_CHORD_TRIANGLE, + MACRO_OSC_SHAPE_CHORD_SINE, + MACRO_OSC_SHAPE_CHORD_WAVETABLE, + + MACRO_OSC_SHAPE_STACK_SAW, + MACRO_OSC_SHAPE_STACK_SQUARE, + MACRO_OSC_SHAPE_STACK_TRIANGLE, + MACRO_OSC_SHAPE_STACK_SINE, + MACRO_OSC_SHAPE_STACK_WAVETABLE, + MACRO_OSC_SHAPE_LAST, MACRO_OSC_SHAPE_LAST_ACCESSIBLE_FROM_META = MACRO_OSC_SHAPE_DIGITAL_MODULATION }; diff --git a/platformio.ini b/platformio.ini index 06cbc43..b815e7f 100644 --- a/platformio.ini +++ b/platformio.ini @@ -9,5 +9,5 @@ [env:squares-and-circles] apps_json = ./app/index.json -squares_and_circles_loader = 45ae077 ; minimum loader version +squares_and_circles_loader = bbdf4c7 ; minimum loader version platform = ./.pio/