From 93a4fa2b81d44cc35b0e0b2c51e0c7ac04a466fa Mon Sep 17 00:00:00 2001 From: eh2k Date: Sat, 30 Nov 2024 13:10:59 +0100 Subject: [PATCH] dev sync bbdf4c7 #97 #110 #109 #108 --- .pio/builder/main.py | 93 +---- README.md | 9 +- app/M-OSC/Waveforms.bin | Bin 104284 -> 111532 bytes app/M-OSC/Waveforms.cpp | 34 +- app/NOISE/WhitePink.cpp | 2 +- app/SEQ/EuclidArp.bin | Bin 10772 -> 10820 bytes app/SEQ/EuclidArp.cpp | 4 +- app/SEQ/TuringMachine.bin | Bin 3680 -> 3920 bytes app/SEQ/TuringMachine.cpp | 52 ++- app/build.sh | 5 +- app/squares-and-circles-api.h | 11 +- app/upload.py | 45 ++- lib/braids/README.md | 130 +++++++ lib/braids/chords_stack.cc | 606 +++++++++++++++++++++++++++++++ lib/braids/digital_oscillator.cc | 13 +- lib/braids/digital_oscillator.h | 44 ++- lib/braids/macro_oscillator.cc | 10 + lib/braids/quantizer.cc | 28 +- lib/braids/quantizer.h | 15 +- lib/braids/settings.cc | 37 +- lib/braids/settings.h | 15 + platformio.ini | 2 +- 22 files changed, 1005 insertions(+), 150 deletions(-) create mode 100644 lib/braids/README.md create mode 100644 lib/braids/chords_stack.cc mode change 100644 => 100755 lib/braids/digital_oscillator.cc mode change 100644 => 100755 lib/braids/digital_oscillator.h mode change 100644 => 100755 lib/braids/macro_oscillator.cc mode change 100644 => 100755 lib/braids/settings.cc 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 e57e7cb8f8a004f1ac4cc45e9ae458573b9bf461..f4d5fc4949f27380e1d738558fd37831dd7a639d 100644 GIT binary patch delta 22430 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 MkKOt44yO2j070)ZiU0rr delta 15272 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 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 3fa36d0d262abb509d570641698be315faa5258b..05f1b2a2e7e2940855b7a80dd04a8977fa99caac 100644 GIT binary patch delta 844 zcmXZaO-K}B9LMqhGwV{hNS1{8lDaR2wGtcoR_eB;uI|omO=+YN?2F2p5o@|$CPbm2 zgO^}D*rhIELUpLcV5>ui1%(YdsMSH*g2+p=5qj8zQooV^z;`}8&&)G3%*kLiJt|&)zy{fi>?KxVFSA$J9(E1ZqXzUskc$Y9{#K9@sU}*2YgI(+Fa`g>HJq8ekaf_1VjY$qFJ`&caTOeV_;2E>s&YG`yU zJ}@*sPN7f&#U~P@6N%7R=;oI-UrUKFqD&qlJkqM5A?LBD)oe~wv`lGQyU&fyiERIi-RZK!11$Q@G;cyY|N zgK=r@Vc}fY9_+Jv(}>&O=rhiW<>uA&?iD20Y3-gh=zgMCsz>Em?tyXDVq+!QvG&Bl z*ZYQ}oBK`C&Fc0lza=b(XTq7VB_c;=BAJLKDo1CcnW*;5dQ#lg4%uSjhc;=uCKi)x zwnsud*5Zx|F>map&;PXVj&kYijzRL9%!}@# zFwQO}2z6CP2FW6_y6D0v@TO$ZjWD9Xn?0)_Xpp0RkNgjO&xdoK=Wrf)R>lfrE$0H! zn42P$G7!x#wM>iryq#u_5?0X19%Fmi;QX`9b|{t02^da5#_fo9_EeH zY={lB5jM)k*fZ=18)p;jS$35D4116lEei4w;nQCUihK7Hy@!wY5v{{J@CR(b-|#Ek zgciKlq)_}F*7{hK%Su`lrQPtlugyR_wKthBcXz} zpib0s*qPOS+nneXv xL=Vscs-mB$jy4gwh>WNS?L=m@2U(E~xsV%qkPrEhKAOKcOG5`6!kEA>{SQya$(), 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 1d294cbddb6eaf337da2bda3813028e53694ee7b..b47aec746932441c7cc87e3bc1c9b7dd26afd0b5 100644 GIT binary patch literal 3920 zcmaKvdu&tJ9mkJ-9mk{wOae3^JRCa-A?A^U0EVz}afr=qXv|Z*HrvF;*Tp!l?J!oV z#VxHAO|3*VDuqHjM!SwpT|CD?kA>AP>N1`Je!h_Z%Qk3&hw-ZVg7DIjk;Aa4dBZzdpb79dX# z$SVWn8Gs%b0eR)TT*1qgP%6&`RIcLXIlNpArLqYu0CPbNm(U(Qp!_c zQc zakifL9@jr`ZR7eMu5WSefxf6@x(wR?oa-U3zu|gTDK9tnH?HToUgmm(>n*NoAzObE z*XdjYdtMeAmn2%S0EIKxjd1G=Vu7^1iS&4 zZ?7*Db4i|%H|UGNYvR%1qKI&4w(2{R5`Eb-#RYjIsq*!EQl ztl64Ti62^Z=iKmUkAK|L!83QO9V#o`x5hff+G(q{Dy({I&wyF%PSL)u>DHWP#u96D zS}|e59pPKWLf4%G-xoBxo06w)UCgi2CUE!K=Cr6CXCH@oDD~8rZofu!uWGM4k!aV8 zr#fw#9tC<3wKq~Uw_-54%5d@WfG$1EQaCsM&A!6!bcUT^N7+GkfbC;J=3?ua75kzY zdvP`P|GoCTa{mQx8liN*knRO;{CmYOeeCi&1K*6h+n$@Nd9LRY`4b3VcRzQx}1!V zJ};a%btnIq+Guy=WQx+~C;qOlKhLHB+y};EO6^5~qb)!=p)LyRnPX3TrVX#g{-KJ5ULp`KLUG(ry)@vBm zIy7Z)YScqI)@c(?_6(%Ozh9-ZGY06s_kmYH9J~%XK)QPy&D6PD`hB2Fde?H`BujV1 zH`_4}@lZz6v+4y&h2eFS{(4QlgjS(Y(`j@5r9X7Ml~w0mDqhB_kNdw#FoVI*Oo$tp zsecz@V~gL)YpaIB%Hp!Ik$d+VA2=~M**OkfJubcIsg|leU#6DxJgZuptd=(DOm4G7 zv7&jH$vcPjPX5UAVyA7c+2?JiO_DpL_@uP3)@qE7jR>Q) zR>3+T8T~YFWvkndUsCjaOP+OpUpXH|y{b}n-?L2Xo5j7nRsA8K|6qzO_-CI4KuP=1$^k=`7Mos^&sO*h*7`P{Dj`_d9 z+MD&p;5vF1^c>y<2f-oG366qe;6pI)T6$%AF~-~O!vZ8W>R0$XEQaF z>9i>gGs}Xg5tVZ#4{|}uRj~5-?EEWRGZW&9`d*s`D^HhK(pS6xaz?xYdTn=9-%heL zr?tW~?3wcwRn~!|*0?pJjZd@AFMHVLY7#5y)f1Q-&HZC=5}W}v*AfS#RVT&`qETZ# zW}E6zRv^a8k5@i9`cjO(9HTGqwdRbj&lz3+#ON$>7UR+Q_s0%*>+JM2ONM!DWhso)OXrGvfNG<*bAPS5BHoyf4$%{@Hv=wjaiguE@yPQWQHA7o*Nqpf8R5} zUPDU%WNzOxa<}e1y`6RR={Yv`^xW!aW6K84`PZ~;_Juaix1*#{cjLcy=)(y?)(1o`qd}40{pc!htW%8f%h?u;`j*u)=dxN8y%#|%WtIIp#C4{6*{`dV zRrVLMY`c1{i%_rLMDIM%N?GN4ixHb@>*|+4E3+#5OIYJ(*P_j+|3dS|rg>-}D_)*t u#mkhe7*JNcY>CcD>&}7-Zv|cq#WnbE8j}#A+}swcEvSg1E4f_9%l`*t`7R>> literal 3680 zcmaKv3v5%@9mbD+9miybT#`}pAmG?Z2r+>qq!1o$FAmuB8i;vSgi>td++c%aJlo0A zF$vQaDbmmc8c1%-Hd;_yt<@3`snA4}OxmPMqf>&$RHh=TQE(tqbu%&&%1HM8uWv#O zf+PL>o%8?B^Pb~->>cYiZmMOqtchbRY+&pkjIkil02EmUm;v-43uJ80yqSQ! zS%AE3Kpqdsn+?dz0p#TZ@_q`)n*+#u36M7zke3I@%Ln8Y0P+d}dGi2yMS#5dfV>5O zJQJW;F`$?kn1BUPtVGS1s`)Y~LLK})i<5D`E66JYOmgY-|=0r(QJ{jAf zs!P?Ks(MuQs~T3dUDdd%9jYEzHKD31!dgQrcTNp-sd`z}uTm;}om!7@Th%S8c0#49 zkJb20wOj-hwT!3r?@=|T>QPk_+SKwtRrP|Zy{cYO^;=a3RlTq3xT=|4svlm}0#z5O zx*5l0s2$8|p@-SZf$9!V&37w{9ua^h_)o^G0HbK2vR4 zV6u)B+J~v4(}UsYwCON!Q2?wR9J1j+%_H?Xt&c-pWs=%M?ua`Yst|#nZy%axUTipNA5Ho%; ztTpo8mxp!P4fl5IaxQRo-LOWNd%Zzsv-aN4p4D(~yK(RR1)JH>F=PFhZpgR((Ljl; z;ZEDwP(7Q`&)F?wTI>5ZT|Za!zRecT>wPEA_kIPR8(OOwP13k^-3Dbg zMgAS<^-6A@|5nGvhdPtD!_@Ja!xX9Op?41*Z%*;w^mFI)9MqQL{6%hK(b(!?BZP33j%~{noDcP%Ui))W5bM&S-{?2|2|nGE=-Gaat@bcJv`Fhc_O)3G^KgFG7tYZ*m*R$P%KD` z=gL>JUmJ0EwkOSEZkof%SGlX`ZA<6=;!UmnOH_+C_DacR~62W1!XSGN-~G} zj72y}n%arc8{@MVXAJ8y9t;#YSqXf`PMYo2MNO88(a*>8Mg6crgI*BjEgJL0=ttvo zoYY>ac`9>wyT)wiQTNn1@6-<)Q5QX&O2vLPZgd)o5vkG-^DN=ux_Sm>A^*;9%+CX8 zolU?Gf*=fHK<<8vX6icN|3jeD|AFn@E+&5*Zxk^P;pEs(6RQy9U&l4(_mKbVUHyt} zRJ$tSVCB2}w@l>)`FOm-zm2zgHaRt~HHVmeuld_vX1OJuor;`QB9`U_>gsgEyR~`66Qe&oE}V*Z5J^4Zf8YMtzu#No zukapBvZbf6ruq?9Z}pr=-V6N6c6el0r}EYk_9n00)mhGZf_TR*I6Ab2_J#JO9sCl+ z!F!+s90SL}Nl4y}29aS%0@VJ-R_ z`BPqyyi!&YUzqt_}4i$D6Un`IMok3 z`liZMV!eNPs{GYdIaU7(%2Y4aN5V6C2jgaa9cQ45+wg&3^GM6cnc1Ar-1kFkXb)LD z^*i5yEd5WSR8n7~nNX9fH5ipvRWsMBYAgL8fR-t$#A{HlvDPZ_nleR|_%fDiSF7sF zs8?&H{}|9RMU{HXQMOjs)UJS*rBsQpVD)b>Q2F|1sNjkm+?!R}REj@J!bbeoi}_JP SzM(Z%omCn|w@SW7&Ho?Iy69#A 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/