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/