From f7602ed50cd4ad569bdd82b6bc87cc5069a90553 Mon Sep 17 00:00:00 2001 From: Mitchell Hudson Date: Sun, 16 Mar 2025 16:06:59 -0700 Subject: [PATCH 1/5] created bytebeats --- patch/ByteShift/Makefile | 62 ++++++++++ patch/ByteShift/README.md | 47 +++++++ patch/ByteShift/bytebeat.cpp | 18 +++ patch/ByteShift/bytebeat_synth.cpp | 133 ++++++++++++++++++++ patch/ByteShift/bytebeat_synth.h | 43 +++++++ patch/ByteShift/byteshift.cpp | 22 ++++ patch/ByteShift/byteshift.h | 19 +++ patch/ByteShift/control_manager.cpp | 21 ++++ patch/ByteShift/control_manager.h | 26 ++++ patch/ByteShift/main.cpp | 55 +++++++++ patch/bytebeat/Makefile | 25 ++++ patch/bytebeat/README.md | 47 +++++++ patch/bytebeat/bytebeat.cpp | 18 +++ patch/bytebeat/bytebeat_synth.cpp | 183 ++++++++++++++++++++++++++++ patch/bytebeat/bytebeat_synth.h | 38 ++++++ 15 files changed, 757 insertions(+) create mode 100644 patch/ByteShift/Makefile create mode 100644 patch/ByteShift/README.md create mode 100644 patch/ByteShift/bytebeat.cpp create mode 100644 patch/ByteShift/bytebeat_synth.cpp create mode 100644 patch/ByteShift/bytebeat_synth.h create mode 100644 patch/ByteShift/byteshift.cpp create mode 100644 patch/ByteShift/byteshift.h create mode 100644 patch/ByteShift/control_manager.cpp create mode 100644 patch/ByteShift/control_manager.h create mode 100644 patch/ByteShift/main.cpp create mode 100644 patch/bytebeat/Makefile create mode 100644 patch/bytebeat/README.md create mode 100644 patch/bytebeat/bytebeat.cpp create mode 100644 patch/bytebeat/bytebeat_synth.cpp create mode 100644 patch/bytebeat/bytebeat_synth.h diff --git a/patch/ByteShift/Makefile b/patch/ByteShift/Makefile new file mode 100644 index 000000000..e43eaaa16 --- /dev/null +++ b/patch/ByteShift/Makefile @@ -0,0 +1,62 @@ +# Project Name +TARGET = byteshift + +# Library Locations +LIBDAISY_DIR = ../../libDaisy +DAISYSP_DIR = ../../DaisySP + +# Core location and generic Makefile. +SYSTEM_FILES_DIR = $(LIBDAISY_DIR)/core + +# Source files +CPP_SOURCES = main.cpp byteshift.cpp + +# Include the Daisy system Makefile +include $(SYSTEM_FILES_DIR)/Makefile + +# Define the compiled program binary +PROGRAM = byteshift.bin +FLASH_ADDR = 0x08000000 + +# Convert ELF to BIN +build/$(PROGRAM): build/$(TARGET).elf + arm-none-eabi-objcopy -O binary build/$(TARGET).elf build/$(PROGRAM) + +# Flash to Daisy Patch using ST-LINK V3 Mini +program: build/$(PROGRAM) + st-flash --reset write build/$(PROGRAM) $(FLASH_ADDR) + + + + + + +# # Project Name +# TARGET = byteshift + +# # Use LGPL version of DaisySP (optional, set to 1 if needed) +# USE_DAISYSP_LGPL=1 + +# # Library Locations +# LIBDAISY_DIR = ../../libDaisy +# DAISYSP_DIR = ../../DaisySP + +# # Core location and generic Makefile. +# SYSTEM_FILES_DIR = $(LIBDAISY_DIR)/core + +# # Source files +# CPP_SOURCES = main.cpp byteshift.cpp bytebeat_synth.cpp + +# # Include the Daisy system Makefile +# include $(SYSTEM_FILES_DIR)/Makefile + +# # Ensure .bin file is created from .elf +# build/byteshift.bin: build/byteshift.elf +# arm-none-eabi-objcopy -O binary build/byteshift.elf build/byteshift.bin + +# # Flashing using STLINK-V3MINI +# PROGRAM = byteshift.bin # Ensure we're flashing the .bin, not .elf +# FLASH_ADDR = 0x08000000 + +# program: build/byteshift.bin # Make sure .bin exists before flashing +# st-flash --reset write build/byteshift.bin $(FLASH_ADDR) \ No newline at end of file diff --git a/patch/ByteShift/README.md b/patch/ByteShift/README.md new file mode 100644 index 000000000..7102f54f6 --- /dev/null +++ b/patch/ByteShift/README.md @@ -0,0 +1,47 @@ +# 🎡 BytebeatSynth + +BytebeatSynth is an experimental music synthesizer that generates sound using mathematical formulas known as bytebeat equations. It runs on the Daisy Patch platform, using control voltage (CV) and knob-based inputs to modify parameters in real time. + +## πŸ”₯ Features +- Bytebeat Audio Synthesis: Uses a selection of classic bytebeat formulas. +- Dynamic Formula Parameters: Modify parameters a, b, and c in real-time. +- Adjusts how fast the bytebeat algorithm runs, affecting pitch and timbre, with higher values creating higher tones and lower values slowing it down, sometimes to silence. +- Built-in Formula Switching: Cycle through multiple equations with the encoder. +- Analog & CV Input Controls: Adjust speed, parameters, and pitch using hardware knobs or external control voltages. + +## πŸŽ›οΈ Controls + +### Control Function +CTRL 1 (Speed/Pitch) Controls the pitch/speed of the bytebeat equation. Accepts 0-5V CV input. +CTRL 2 (Parameter A) Adjusts the first variable (a) in the bytebeat formula. +CTRL 3 (Parameter B) Adjusts the second variable (b) in the bytebeat formula. +CTRL 4 (Parameter C) Adjusts the third variable (c) in the bytebeat formula. +Encoder (Press) Cycles through different bytebeat formulas. +OLED Display Shows the current formula, pitch, and values of a, b, and c. + +## ⚑ How to Use +1. Turn CTRL 1 to change the pitch and speed of the bytebeat synthesis. +2. Adjust CTRL 2, 3, and 4 to manipulate different formula parameters. +3. Press the encoder to switch to a different bytebeat equation. +4. Use CV input on CTRL 1 to modulate pitch from an external sequencer or LFO. +5. Observe the OLED display to see current formula and parameter values. + +## πŸ› οΈ Installation & Compilation + +To build and flash BytebeatSynth onto your Daisy Patch: +1. Clone or copy the project files. +2. Run `make` to compile the firmware. +3. Flash the binary using: `st-flash --reset write build/bytebeat.bin 0x08000000` + +## 🎚️ Example Patching Ideas +- Sequenced Rhythms: Use an external CV sequencer on CTRL 1 for evolving patterns. +- Glitchy Drones: Set CTRL 1 low and tweak CTRL 2, 3, 4 for texture shifts. +- Noise Percussion: Use a gate signal on CTRL 1 to trigger bursts of sound. + +## 🎡 What is a Bytebeat? + +A bytebeat is a type of generative music where sound is created using simple mathematical formulas instead of traditional synthesis or sampling. These formulas operate on a time variable (t) to generate an audio waveform in real time, often resulting in glitchy, chiptune-like, and chaotic sounds. + +Bytebeats emerged in 2011 when Finnish developer viznut (aka Ville-Matias HeikkilΓ€) discovered that short arithmetic expressions could create rich and evolving sound patterns. This led to an explosion of interest in algorithmic music, with musicians and programmers experimenting with different formulas to produce everything from rhythmic patterns to bizarre noise textures. + +Since bytebeat synthesis is entirely formula-driven, modifying the equations can create new sonic landscapes, making it an ideal playground for experimentation in digital sound generation. diff --git a/patch/ByteShift/bytebeat.cpp b/patch/ByteShift/bytebeat.cpp new file mode 100644 index 000000000..76976f5fb --- /dev/null +++ b/patch/ByteShift/bytebeat.cpp @@ -0,0 +1,18 @@ +#include "bytebeat_synth.h" + +int main(void) { + // Create an instance of DaisyPatch assigned to local var patch + DaisyPatch patch; + // Creat an instance of BytebeatSynth assigned to local var synth + BytebeatSynth synth; + // Initialize synth pass patch. Is this a reference what is & ? + synth.Init(&patch); + + // Create a main program loop + while (1) { + // Update display + synth.UpdateDisplay(); + // Chill for 50ms + System::Delay(50); + } +} \ No newline at end of file diff --git a/patch/ByteShift/bytebeat_synth.cpp b/patch/ByteShift/bytebeat_synth.cpp new file mode 100644 index 000000000..2deee1e9c --- /dev/null +++ b/patch/ByteShift/bytebeat_synth.cpp @@ -0,0 +1,133 @@ +#include "bytebeat_synth.h" + +// Global instance pointer for static callback +static BytebeatSynth* synthInstance = nullptr; + +// Bytebeat Function Table +BytebeatSynth::BytebeatFunc BytebeatSynth::formulaTable[] = { + &BytebeatSynth::BytebeatFormula0, + &BytebeatSynth::BytebeatFormula1, + &BytebeatSynth::BytebeatFormula2, + &BytebeatSynth::BytebeatFormula3, + &BytebeatSynth::BytebeatFormula4, + &BytebeatSynth::BytebeatFormula5 +}; + +// Bytebeat Equation Definitions +uint8_t BytebeatSynth::BytebeatFormula0(uint32_t t, BytebeatSynth* synth) { + int a = synth->a; + int b = synth->b; + return (t * (t >> a) & 42) | (t * (t >> b) & 84); +} + +uint8_t BytebeatSynth::BytebeatFormula1(uint32_t t, BytebeatSynth* synth) { + int a = synth->a; + int b = synth->b; + int c = synth->c; + return (t >> a) | (t << b) | (t * (t >> c)); +} + +uint8_t BytebeatSynth::BytebeatFormula2(uint32_t t, BytebeatSynth* synth) { + int a = synth->a; // Stored in a register (if optimized) + int b = synth->b; + return (t * (t >> a) | (t * (t >> b) & 50)); +} + +uint8_t BytebeatSynth::BytebeatFormula3(uint32_t t, BytebeatSynth* synth) { + int b = synth->b; + int c = synth->c; + return ((t * 3) & (t >> b)) | (t * (t >> c)); +} + +uint8_t BytebeatSynth::BytebeatFormula4(uint32_t t, BytebeatSynth* synth) { + int a = synth->a; // Stored in a register (if optimized) + int b = synth->b; + return (t * 5 & (t >> a)) | (t * (t >> b) & 123); +} + +uint8_t BytebeatSynth::BytebeatFormula5(uint32_t t, BytebeatSynth* synth) { + int a = synth->a; // Stored in a register (if optimized) + int b = synth->b; + return (t >> a) ^ (t * (t >> b) & 32); +} + +// Initialize Synth +void BytebeatSynth::Init(DaisyPatch* p) { + patch = p; + t = 0; + formula_index = 0; + synthInstance = this; + + // Initialize Parameters + a = 4; + b = 6; + c = 8; + + patch->Init(); + patch->StartAdc(); +} + +// Update Controls (Knobs & Encoder) +void BytebeatSynth::UpdateControls() { + patch->ProcessAnalogControls(); + patch->ProcessDigitalControls(); + + float pitchCV = patch->GetKnobValue(DaisyPatch::CTRL_1) * 5.0f; + float frequency = 16.0f * powf(2.0f, pitchCV - 1.0f); + tScale = 48000.0f / frequency; + + a = (int)(patch->GetKnobValue(DaisyPatch::CTRL_2) * 16) + 1; + b = (int)(patch->GetKnobValue(DaisyPatch::CTRL_3) * 32) + 1; + c = (int)(patch->GetKnobValue(DaisyPatch::CTRL_4) * 64) + 1; + + if (patch->encoder.RisingEdge()) { + formula_index = (formula_index + 1) % formula_count; + } +} + +// Generate a single sample for external processing +float BytebeatSynth::GenerateSample() { + BytebeatFunc currentFormula = formulaTable[formula_index]; + + static float tAccumulator = 0; + tAccumulator += 1.0f; + if (tAccumulator >= tScale) { + t += (uint32_t)(tScale); + tAccumulator -= tScale; + } + + uint8_t sample = currentFormula(t, this); + return ((float)sample / 255.0f) * 2.0f - 1.0f; +} + +void BytebeatSynth::NextFormula() { + formula_index = (formula_index + 1) % formula_count; +} + +// OLED Display Update +void BytebeatSynth::UpdateDisplay() { + static int last_formula = -1; + static int last_a = -1, last_b = -1, last_c = -1; + + if (formula_index != last_formula || a != last_a || b != last_b || c != last_c) { + patch->display.Fill(false); + patch->display.SetCursor(0, 0); + patch->display.WriteString("Bytebeat Synth", Font_7x10, true); + + char buffer[32]; + snprintf(buffer, sizeof(buffer), "Formula: %d", formula_index); + patch->display.SetCursor(0, 12); + patch->display.WriteString(buffer, Font_7x10, true); + + snprintf(buffer, sizeof(buffer), "a: %d b: %d c: %d", a, b, c); + patch->display.SetCursor(0, 24); + patch->display.WriteString(buffer, Font_7x10, true); + + patch->display.Update(); + + last_formula = formula_index; + last_a = a; + last_b = b; + last_c = c; + } +} \ No newline at end of file diff --git a/patch/ByteShift/bytebeat_synth.h b/patch/ByteShift/bytebeat_synth.h new file mode 100644 index 000000000..5429bb62e --- /dev/null +++ b/patch/ByteShift/bytebeat_synth.h @@ -0,0 +1,43 @@ +#ifndef BYTEBEAT_SYNTH_H +#define BYTEBEAT_SYNTH_H + +#include "daisy_patch.h" +#include "daisysp.h" + +using namespace daisy; +using namespace daisysp; + +class BytebeatSynth { +public: + void Init(DaisyPatch* p); + void UpdateControls(); + float GenerateSample(); // Now returns a single sample for processing + void UpdateDisplay(); + float ProcessSample(); + void NextFormula(); + int GetFormulaIndex() const { return formula_index; } + +private: + DaisyPatch* patch; + uint32_t t; + int formula_index; + static const int formula_count = 6; + + // Dynamic formula parameters + int a, b, c; + + // Scaling factor for musically tuned timing + float tScale; + + using BytebeatFunc = uint8_t (*)(uint32_t, BytebeatSynth*); + static BytebeatFunc formulaTable[]; + + static uint8_t BytebeatFormula0(uint32_t t, BytebeatSynth* synth); + static uint8_t BytebeatFormula1(uint32_t t, BytebeatSynth* synth); + static uint8_t BytebeatFormula2(uint32_t t, BytebeatSynth* synth); + static uint8_t BytebeatFormula3(uint32_t t, BytebeatSynth* synth); + static uint8_t BytebeatFormula4(uint32_t t, BytebeatSynth* synth); + static uint8_t BytebeatFormula5(uint32_t t, BytebeatSynth* synth); +}; + +#endif // BYTEBEAT_SYNTH_H \ No newline at end of file diff --git a/patch/ByteShift/byteshift.cpp b/patch/ByteShift/byteshift.cpp new file mode 100644 index 000000000..9f9da9d2c --- /dev/null +++ b/patch/ByteShift/byteshift.cpp @@ -0,0 +1,22 @@ +#include "byteshift.h" + +void ByteShift::Init(daisy::DaisyPatch* p) { + patch = p; + speed = paramA = paramB = paramC = 0.0f; + encoderValue = 0; +} + +void ByteShift::SetControlValues(float c1, float c2, float c3, float c4, int enc) { + speed = c1; + paramA = c2; + paramB = c3; + paramC = c4; + encoderValue = enc; +} + +void ByteShift::ProcessAudio(float** out, size_t size) { + for (size_t i = 0; i < size; i++) { + out[0][i] = speed * 0.01f; + out[1][i] = paramA * 0.01f; + } +} \ No newline at end of file diff --git a/patch/ByteShift/byteshift.h b/patch/ByteShift/byteshift.h new file mode 100644 index 000000000..cb1a34473 --- /dev/null +++ b/patch/ByteShift/byteshift.h @@ -0,0 +1,19 @@ +#ifndef BYTESHIFT_H +#define BYTESHIFT_H + +#include "daisy_patch.h" +#include "control_manager.h" + +class ByteShift { +public: + void Init(daisy::DaisyPatch* p); + void ProcessAudio(float** out, size_t size); + void SetControlValues(float c1, float c2, float c3, float c4, int enc); + +private: + daisy::DaisyPatch* patch; + float speed, paramA, paramB, paramC; + int encoderValue; +}; + +#endif \ No newline at end of file diff --git a/patch/ByteShift/control_manager.cpp b/patch/ByteShift/control_manager.cpp new file mode 100644 index 000000000..3771244e9 --- /dev/null +++ b/patch/ByteShift/control_manager.cpp @@ -0,0 +1,21 @@ +#include "control_manager.h" + +void ControlManager::Init(daisy::DaisyPatch* p) { + patch = p; + ctrl1 = ctrl2 = ctrl3 = ctrl4 = 0.0f; + encoderCount = 0; + encoderPressed = false; +} + +void ControlManager::Update() { + patch->ProcessAnalogControls(); + patch->ProcessDigitalControls(); + + ctrl1 = patch->GetKnobValue(daisy::DaisyPatch::CTRL_1); + ctrl2 = patch->GetKnobValue(daisy::DaisyPatch::CTRL_2); + ctrl3 = patch->GetKnobValue(daisy::DaisyPatch::CTRL_3); + ctrl4 = patch->GetKnobValue(daisy::DaisyPatch::CTRL_4); + + encoderCount += patch->encoder.Increment(); + encoderPressed = patch->encoder.RisingEdge(); +} \ No newline at end of file diff --git a/patch/ByteShift/control_manager.h b/patch/ByteShift/control_manager.h new file mode 100644 index 000000000..aeeb60b95 --- /dev/null +++ b/patch/ByteShift/control_manager.h @@ -0,0 +1,26 @@ +#ifndef CONTROL_MANAGER_H +#define CONTROL_MANAGER_H + +#include "daisy_patch.h" + +class ControlManager { +public: + void Init(daisy::DaisyPatch* p); + void Update(); + + float GetCtrl1() const { return ctrl1; } + float GetCtrl2() const { return ctrl2; } + float GetCtrl3() const { return ctrl3; } + float GetCtrl4() const { return ctrl4; } + int GetEncoderCount() const { return encoderCount; } + bool IsEncoderPressed() const { return encoderPressed; } + +private: + daisy::DaisyPatch* patch; + + float ctrl1, ctrl2, ctrl3, ctrl4; + int encoderCount; + bool encoderPressed; +}; + +#endif \ No newline at end of file diff --git a/patch/ByteShift/main.cpp b/patch/ByteShift/main.cpp new file mode 100644 index 000000000..4b64ba3f6 --- /dev/null +++ b/patch/ByteShift/main.cpp @@ -0,0 +1,55 @@ +#include "daisy_patch.h" +#include "control_manager.h" +#include "byteshift.h" + +daisy::DaisyPatch patch; +ControlManager controlManager; +ByteShift synth; + +static ByteShift* synthInstance = nullptr; // Pointer to ByteShift instance + +void AudioCallbackWrapper(daisy::AudioHandle::InputBuffer in, + daisy::AudioHandle::OutputBuffer out, + size_t size) { + if (synthInstance) { + synthInstance->ProcessAudio(out, size); + } +} + +void AudioCallbackWrapper(daisy::AudioHandle::InputBuffer in, + daisy::AudioHandle::OutputBuffer out, + size_t size); + +int main(void) { + patch.Init(); + controlManager.Init(&patch); + synth.Init(&patch); + + synthInstance = &synth; // Assign instance before calling StartAudio + patch.StartAudio(AudioCallbackWrapper); + + while (1) { + controlManager.Update(); + synth.SetControlValues(controlManager.GetCtrl1(), + controlManager.GetCtrl2(), + controlManager.GetCtrl3(), + controlManager.GetCtrl4(), + controlManager.GetEncoderCount()); + + patch.display.Fill(false); + patch.display.SetCursor(0, 0); + patch.display.WriteString("ByteShift v0.1", Font_7x10, true); + + char buffer[32]; + snprintf(buffer, sizeof(buffer), "C1: %d", (int)(controlManager.GetCtrl1() * 100)); + patch.display.SetCursor(0, 12); + patch.display.WriteString(buffer, Font_7x10, true); + + snprintf(buffer, sizeof(buffer), "Enc: %d", controlManager.GetEncoderCount()); + patch.display.SetCursor(0, 60); + patch.display.WriteString(buffer, Font_7x10, true); + + patch.display.Update(); + daisy::System::Delay(5); + } +} diff --git a/patch/bytebeat/Makefile b/patch/bytebeat/Makefile new file mode 100644 index 000000000..028332aef --- /dev/null +++ b/patch/bytebeat/Makefile @@ -0,0 +1,25 @@ +# Project Name +TARGET = bytebeat + +# Use LGPL version of DaisySP (optional, set to 1 if needed) +USE_DAISYSP_LGPL=1 + +# Library Locations +LIBDAISY_DIR = ../../libDaisy +DAISYSP_DIR = ../../DaisySP + +# Core location, and generic Makefile. +SYSTEM_FILES_DIR = $(LIBDAISY_DIR)/core + +# Source files +# CPP_SOURCES = bytebeat.cpp +CPP_SOURCES = bytebeat.cpp bytebeat_synth.cpp + +# Include the Daisy system Makefile +include $(SYSTEM_FILES_DIR)/Makefile + + + + + + diff --git a/patch/bytebeat/README.md b/patch/bytebeat/README.md new file mode 100644 index 000000000..7102f54f6 --- /dev/null +++ b/patch/bytebeat/README.md @@ -0,0 +1,47 @@ +# 🎡 BytebeatSynth + +BytebeatSynth is an experimental music synthesizer that generates sound using mathematical formulas known as bytebeat equations. It runs on the Daisy Patch platform, using control voltage (CV) and knob-based inputs to modify parameters in real time. + +## πŸ”₯ Features +- Bytebeat Audio Synthesis: Uses a selection of classic bytebeat formulas. +- Dynamic Formula Parameters: Modify parameters a, b, and c in real-time. +- Adjusts how fast the bytebeat algorithm runs, affecting pitch and timbre, with higher values creating higher tones and lower values slowing it down, sometimes to silence. +- Built-in Formula Switching: Cycle through multiple equations with the encoder. +- Analog & CV Input Controls: Adjust speed, parameters, and pitch using hardware knobs or external control voltages. + +## πŸŽ›οΈ Controls + +### Control Function +CTRL 1 (Speed/Pitch) Controls the pitch/speed of the bytebeat equation. Accepts 0-5V CV input. +CTRL 2 (Parameter A) Adjusts the first variable (a) in the bytebeat formula. +CTRL 3 (Parameter B) Adjusts the second variable (b) in the bytebeat formula. +CTRL 4 (Parameter C) Adjusts the third variable (c) in the bytebeat formula. +Encoder (Press) Cycles through different bytebeat formulas. +OLED Display Shows the current formula, pitch, and values of a, b, and c. + +## ⚑ How to Use +1. Turn CTRL 1 to change the pitch and speed of the bytebeat synthesis. +2. Adjust CTRL 2, 3, and 4 to manipulate different formula parameters. +3. Press the encoder to switch to a different bytebeat equation. +4. Use CV input on CTRL 1 to modulate pitch from an external sequencer or LFO. +5. Observe the OLED display to see current formula and parameter values. + +## πŸ› οΈ Installation & Compilation + +To build and flash BytebeatSynth onto your Daisy Patch: +1. Clone or copy the project files. +2. Run `make` to compile the firmware. +3. Flash the binary using: `st-flash --reset write build/bytebeat.bin 0x08000000` + +## 🎚️ Example Patching Ideas +- Sequenced Rhythms: Use an external CV sequencer on CTRL 1 for evolving patterns. +- Glitchy Drones: Set CTRL 1 low and tweak CTRL 2, 3, 4 for texture shifts. +- Noise Percussion: Use a gate signal on CTRL 1 to trigger bursts of sound. + +## 🎡 What is a Bytebeat? + +A bytebeat is a type of generative music where sound is created using simple mathematical formulas instead of traditional synthesis or sampling. These formulas operate on a time variable (t) to generate an audio waveform in real time, often resulting in glitchy, chiptune-like, and chaotic sounds. + +Bytebeats emerged in 2011 when Finnish developer viznut (aka Ville-Matias HeikkilΓ€) discovered that short arithmetic expressions could create rich and evolving sound patterns. This led to an explosion of interest in algorithmic music, with musicians and programmers experimenting with different formulas to produce everything from rhythmic patterns to bizarre noise textures. + +Since bytebeat synthesis is entirely formula-driven, modifying the equations can create new sonic landscapes, making it an ideal playground for experimentation in digital sound generation. diff --git a/patch/bytebeat/bytebeat.cpp b/patch/bytebeat/bytebeat.cpp new file mode 100644 index 000000000..76976f5fb --- /dev/null +++ b/patch/bytebeat/bytebeat.cpp @@ -0,0 +1,18 @@ +#include "bytebeat_synth.h" + +int main(void) { + // Create an instance of DaisyPatch assigned to local var patch + DaisyPatch patch; + // Creat an instance of BytebeatSynth assigned to local var synth + BytebeatSynth synth; + // Initialize synth pass patch. Is this a reference what is & ? + synth.Init(&patch); + + // Create a main program loop + while (1) { + // Update display + synth.UpdateDisplay(); + // Chill for 50ms + System::Delay(50); + } +} \ No newline at end of file diff --git a/patch/bytebeat/bytebeat_synth.cpp b/patch/bytebeat/bytebeat_synth.cpp new file mode 100644 index 000000000..67f4667b2 --- /dev/null +++ b/patch/bytebeat/bytebeat_synth.cpp @@ -0,0 +1,183 @@ +#include "bytebeat_synth.h" + +// Global instance pointer for static callback +static BytebeatSynth* synthInstance = nullptr; + +// Bytebeat Formulas (Function Pointer Table) +BytebeatSynth::BytebeatFunc BytebeatSynth::formulaTable[] = { + &BytebeatSynth::BytebeatFormula0, + &BytebeatSynth::BytebeatFormula1, + &BytebeatSynth::BytebeatFormula2, + &BytebeatSynth::BytebeatFormula3, + &BytebeatSynth::BytebeatFormula4, + &BytebeatSynth::BytebeatFormula5 +}; + +// Bytebeat Equation Definitions (Now use a, b, c) +uint8_t BytebeatSynth::BytebeatFormula0(uint32_t t, BytebeatSynth* synth) { + int a = synth->a; // Stored in a register (if optimized) + int b = synth->b; + + uint8_t result = (t * (t >> a) & 42) | (t * (t >> b) & 84); + return result; +} + +uint8_t BytebeatSynth::BytebeatFormula1(uint32_t t, BytebeatSynth* synth) { + int a = synth->a; // Stored in a register (if optimized) + int b = synth->b; + int c = synth->c; + return (t >> a) | (t << b) | (t * (t >> c)); +} + +uint8_t BytebeatSynth::BytebeatFormula2(uint32_t t, BytebeatSynth* synth) { + int a = synth->a; // Stored in a register (if optimized) + int b = synth->b; + return (t * (t >> a) | (t * (t >> b) & 50)); +} + +uint8_t BytebeatSynth::BytebeatFormula3(uint32_t t, BytebeatSynth* synth) { + int b = synth->b; + int c = synth->c; + return ((t * 3) & (t >> b)) | (t * (t >> c)); +} + +uint8_t BytebeatSynth::BytebeatFormula4(uint32_t t, BytebeatSynth* synth) { + int a = synth->a; // Stored in a register (if optimized) + int b = synth->b; + return (t * 5 & (t >> a)) | (t * (t >> b) & 123); +} + +uint8_t BytebeatSynth::BytebeatFormula5(uint32_t t, BytebeatSynth* synth) { + int a = synth->a; // Stored in a register (if optimized) + int b = synth->b; + return (t >> a) ^ (t * (t >> b) & 32); +} + +// Static Wrapper for Audio Callback +static void AudioCallbackWrapper(AudioHandle::InputBuffer in, AudioHandle::OutputBuffer out, size_t size) { + synthInstance->ProcessAudio(in, out, size); +} + + +// ************************************** +// +// Initialize Synth +// +// ************************************** +void BytebeatSynth::Init(DaisyPatch* p) { + patch = p; + speed = 0.1f; + t = 0; + tScale = 1.0f; // βœ… Initialize tScale + formula_index = 0; + synthInstance = this; + + // Initialize Parameters (scaled) + a = 4; b = 6; c = 8; + + patch->Init(); + patch->StartAdc(); + patch->StartAudio(AudioCallbackWrapper); +} + +// Update Controls (Knobs & Encoder) +void BytebeatSynth::UpdateControls() { + patch->ProcessAnalogControls(); + patch->ProcessDigitalControls(); + + // Read Speed (Knob 1) - scaled to 0.1 to 4.1 + speed = patch->GetKnobValue(DaisyPatch::CTRL_1) * 4.0f + 0.1f; + + // Read dynamic parameters (CTRL_2, CTRL_3, CTRL_4) + // Map Knobs 2, 3, and 4 to parameters a, b, and c + a = (int)(patch->GetKnobValue(DaisyPatch::CTRL_2) * 16) + 1; // Range: 1 - 16 + b = (int)(patch->GetKnobValue(DaisyPatch::CTRL_3) * 32) + 1; // Range: 1 - 32 + c = (int)(patch->GetKnobValue(DaisyPatch::CTRL_4) * 64) + 1; // Range: 1 - 64 + + // Check encoder button for formula switching + if (patch->encoder.RisingEdge()) { + formula_index = (formula_index + 1) % formula_count; + } +} + +// Audio Processing Callback +// void BytebeatSynth::ProcessAudio(AudioHandle::InputBuffer in, AudioHandle::OutputBuffer out, size_t size) { +// UpdateControls(); + +// BytebeatFunc currentFormula = formulaTable[formula_index]; +// int speed_int = static_cast(speed * 10); + +// for (size_t i = 0; i < size; i++) { +// uint8_t sample = currentFormula(t, synthInstance); +// float output = ((float)sample / 255.0f) * 2.0f - 1.0f; +// out[0][i] = output; +// out[1][i] = output; +// t += 1 + speed_int; +// } +// } + +// Constants for min/max frequency +constexpr float MIN_FREQUENCY = 24.0f; // 8.0 Lower limit (adjust as needed) +constexpr float MAX_FREQUENCY = 112.0f; // Upper limit (adjust as needed) + +void BytebeatSynth::ProcessAudio(AudioHandle::InputBuffer in, AudioHandle::OutputBuffer out, size_t size) { + UpdateControls(); + + BytebeatFunc currentFormula = formulaTable[formula_index]; + + // Read the pitch CV and scale it within the min/max frequency range + float pitchCV = (1.0f - patch->GetKnobValue(DaisyPatch::CTRL_1)) * 5.0f; // Map 0-5V + float frequency = MIN_FREQUENCY * powf(2.0f, pitchCV * log2f(MAX_FREQUENCY / MIN_FREQUENCY)); // Constrain range + + // Convert frequency to a time step + uint32_t tScale = static_cast(48000.0f / frequency); + + for (size_t i = 0; i < size; i++) { + t += tScale; // Step `t` according to the musical pitch scaling + + // Generate bytebeat sample at updated `t` + uint8_t sample = currentFormula(t, this); + float output = ((float)sample / 255.0f) * 2.0f - 1.0f; + + // Send output + out[0][i] = output; + out[1][i] = output; + } +} + +// OLED Display Update (Now Shows a, b, c) +void BytebeatSynth::UpdateDisplay() { + static int last_speed = -1; + static int last_formula = -1; + static int last_a = -1, last_b = -1, last_c = -1; + + int speed_int = static_cast(speed * 100); + + if (speed_int != last_speed || formula_index != last_formula || a != last_a || b != last_b || c != last_c) { + patch->display.Fill(false); + patch->display.SetCursor(0, 0); + patch->display.WriteString("Bytebeat Synth", Font_7x10, true); + + char buffer[32]; + snprintf(buffer, sizeof(buffer), "Speed: %d", speed_int); + patch->display.SetCursor(0, 12); + patch->display.WriteString(buffer, Font_7x10, true); + + snprintf(buffer, sizeof(buffer), "Formula: %d", formula_index); + patch->display.SetCursor(0, 24); + patch->display.WriteString(buffer, Font_7x10, true); + + // Display a, b, and c values + snprintf(buffer, sizeof(buffer), "a: %d b: %d c: %d", a, b, c); + patch->display.SetCursor(0, 36); + patch->display.WriteString(buffer, Font_7x10, true); + + patch->display.Update(); + + last_speed = speed_int; + last_formula = formula_index; + last_a = a; + last_b = b; + last_c = c; + } +} \ No newline at end of file diff --git a/patch/bytebeat/bytebeat_synth.h b/patch/bytebeat/bytebeat_synth.h new file mode 100644 index 000000000..452ddc6e9 --- /dev/null +++ b/patch/bytebeat/bytebeat_synth.h @@ -0,0 +1,38 @@ +#ifndef BYTEBEAT_SYNTH_H +#define BYTEBEAT_SYNTH_H + +#include "daisy_patch.h" +#include "daisysp.h" + +using namespace daisy; +using namespace daisysp; + +class BytebeatSynth { +public: + void Init(DaisyPatch* p); + void UpdateControls(); + void ProcessAudio(AudioHandle::InputBuffer in, AudioHandle::OutputBuffer out, size_t size); + void UpdateDisplay(); + +private: + DaisyPatch* patch; + float speed; + uint32_t t; + float tScale; // βœ… Declare tScale + int formula_index; + static const int formula_count = 6; + + int a, b, c; // Bytebeat parameters + + using BytebeatFunc = uint8_t (*)(uint32_t, BytebeatSynth*); + static BytebeatFunc formulaTable[]; + + static uint8_t BytebeatFormula0(uint32_t t, BytebeatSynth* synth); + static uint8_t BytebeatFormula1(uint32_t t, BytebeatSynth* synth); + static uint8_t BytebeatFormula2(uint32_t t, BytebeatSynth* synth); + static uint8_t BytebeatFormula3(uint32_t t, BytebeatSynth* synth); + static uint8_t BytebeatFormula4(uint32_t t, BytebeatSynth* synth); + static uint8_t BytebeatFormula5(uint32_t t, BytebeatSynth* synth); +}; + +#endif // BYTEBEAT_SYNTH_H \ No newline at end of file From 5de146501d3c46cfac8fd877e1fdbf3465562eed Mon Sep 17 00:00:00 2001 From: Mitchell Hudson Date: Sun, 16 Mar 2025 16:28:34 -0700 Subject: [PATCH 2/5] test control manager --- patch/ByteShift/Makefile | 2 +- patch/ByteShift/control_manager.cpp | 2 ++ patch/ByteShift/main.cpp | 7 ++----- patch/bytebeat/bytebeat_synth.cpp | 13 +++++++++++++ 4 files changed, 18 insertions(+), 6 deletions(-) diff --git a/patch/ByteShift/Makefile b/patch/ByteShift/Makefile index e43eaaa16..b282f1a6f 100644 --- a/patch/ByteShift/Makefile +++ b/patch/ByteShift/Makefile @@ -9,7 +9,7 @@ DAISYSP_DIR = ../../DaisySP SYSTEM_FILES_DIR = $(LIBDAISY_DIR)/core # Source files -CPP_SOURCES = main.cpp byteshift.cpp +CPP_SOURCES = main.cpp byteshift.cpp control_manager.cpp # Include the Daisy system Makefile include $(SYSTEM_FILES_DIR)/Makefile diff --git a/patch/ByteShift/control_manager.cpp b/patch/ByteShift/control_manager.cpp index 3771244e9..e820b0744 100644 --- a/patch/ByteShift/control_manager.cpp +++ b/patch/ByteShift/control_manager.cpp @@ -2,6 +2,8 @@ void ControlManager::Init(daisy::DaisyPatch* p) { patch = p; + patch->StartAdc(); + ctrl1 = ctrl2 = ctrl3 = ctrl4 = 0.0f; encoderCount = 0; encoderPressed = false; diff --git a/patch/ByteShift/main.cpp b/patch/ByteShift/main.cpp index 4b64ba3f6..8574765a8 100644 --- a/patch/ByteShift/main.cpp +++ b/patch/ByteShift/main.cpp @@ -16,12 +16,9 @@ void AudioCallbackWrapper(daisy::AudioHandle::InputBuffer in, } } -void AudioCallbackWrapper(daisy::AudioHandle::InputBuffer in, - daisy::AudioHandle::OutputBuffer out, - size_t size); - int main(void) { patch.Init(); + controlManager.Init(&patch); synth.Init(&patch); @@ -46,7 +43,7 @@ int main(void) { patch.display.WriteString(buffer, Font_7x10, true); snprintf(buffer, sizeof(buffer), "Enc: %d", controlManager.GetEncoderCount()); - patch.display.SetCursor(0, 60); + patch.display.SetCursor(0, 24); patch.display.WriteString(buffer, Font_7x10, true); patch.display.Update(); diff --git a/patch/bytebeat/bytebeat_synth.cpp b/patch/bytebeat/bytebeat_synth.cpp index 67f4667b2..57133784c 100644 --- a/patch/bytebeat/bytebeat_synth.cpp +++ b/patch/bytebeat/bytebeat_synth.cpp @@ -116,6 +116,12 @@ void BytebeatSynth::UpdateControls() { // } // } +// ***************************************************** +// +// Process Audio +// +// ***************************************************** + // Constants for min/max frequency constexpr float MIN_FREQUENCY = 24.0f; // 8.0 Lower limit (adjust as needed) constexpr float MAX_FREQUENCY = 112.0f; // Upper limit (adjust as needed) @@ -145,6 +151,13 @@ void BytebeatSynth::ProcessAudio(AudioHandle::InputBuffer in, AudioHandle::Outpu } } + +// ************************************************* +// +// Update Display +// +// ************************************************* + // OLED Display Update (Now Shows a, b, c) void BytebeatSynth::UpdateDisplay() { static int last_speed = -1; From d50aaa48bf4a511b763f50178f107217834f343a Mon Sep 17 00:00:00 2001 From: Mitchell Hudson Date: Sun, 16 Mar 2025 16:33:11 -0700 Subject: [PATCH 3/5] fixed encoder --- patch/ByteShift/control_manager.cpp | 5 ++++- patch/ByteShift/main.cpp | 15 ++++++++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/patch/ByteShift/control_manager.cpp b/patch/ByteShift/control_manager.cpp index e820b0744..1d0bf7f1f 100644 --- a/patch/ByteShift/control_manager.cpp +++ b/patch/ByteShift/control_manager.cpp @@ -3,7 +3,7 @@ void ControlManager::Init(daisy::DaisyPatch* p) { patch = p; patch->StartAdc(); - + ctrl1 = ctrl2 = ctrl3 = ctrl4 = 0.0f; encoderCount = 0; encoderPressed = false; @@ -20,4 +20,7 @@ void ControlManager::Update() { encoderCount += patch->encoder.Increment(); encoderPressed = patch->encoder.RisingEdge(); + if (encoderPressed) { + encoderCount = 0; + } } \ No newline at end of file diff --git a/patch/ByteShift/main.cpp b/patch/ByteShift/main.cpp index 8574765a8..0383ebd16 100644 --- a/patch/ByteShift/main.cpp +++ b/patch/ByteShift/main.cpp @@ -38,14 +38,27 @@ int main(void) { patch.display.WriteString("ByteShift v0.1", Font_7x10, true); char buffer[32]; + snprintf(buffer, sizeof(buffer), "C1: %d", (int)(controlManager.GetCtrl1() * 100)); patch.display.SetCursor(0, 12); patch.display.WriteString(buffer, Font_7x10, true); - snprintf(buffer, sizeof(buffer), "Enc: %d", controlManager.GetEncoderCount()); + snprintf(buffer, sizeof(buffer), "C2: %d", (int)(controlManager.GetCtrl2() * 100)); patch.display.SetCursor(0, 24); patch.display.WriteString(buffer, Font_7x10, true); + snprintf(buffer, sizeof(buffer), "C3: %d", (int)(controlManager.GetCtrl3() * 100)); + patch.display.SetCursor(0, 36); + patch.display.WriteString(buffer, Font_7x10, true); + + snprintf(buffer, sizeof(buffer), "C4: %d", (int)(controlManager.GetCtrl4() * 100)); + patch.display.SetCursor(0, 48); + patch.display.WriteString(buffer, Font_7x10, true); + + snprintf(buffer, sizeof(buffer), "Enc: %d", controlManager.GetEncoderCount()); + patch.display.SetCursor(60, 12); + patch.display.WriteString(buffer, Font_7x10, true); + patch.display.Update(); daisy::System::Delay(5); } From 67c5aa237020fe676efe899fedec59f8cc081028 Mon Sep 17 00:00:00 2001 From: Mitchell Hudson Date: Mon, 17 Mar 2025 10:40:38 -0700 Subject: [PATCH 4/5] updated v1.0 --- patch/ByteShift/.vscode/settings.json | 5 + patch/ByteShift/Makefile | 2 +- patch/ByteShift/README.md | 42 ++++----- patch/ByteShift/bytebeat.cpp | 18 ---- patch/ByteShift/bytebeat_synth.cpp | 128 +++++++++----------------- patch/ByteShift/bytebeat_synth.h | 35 ++++--- patch/ByteShift/byteshift.cpp | 30 +++--- patch/ByteShift/byteshift.h | 19 ++-- patch/ByteShift/control_manager.cpp | 13 ++- patch/ByteShift/control_manager.h | 9 ++ patch/ByteShift/main.cpp | 83 ++++++++++------- patch/bytebeat/Makefile | 12 +++ patch/bytebeat/bytebeat.cpp | 8 +- patch/bytebeat/bytebeat_synth.h | 8 +- 14 files changed, 211 insertions(+), 201 deletions(-) create mode 100644 patch/ByteShift/.vscode/settings.json delete mode 100644 patch/ByteShift/bytebeat.cpp diff --git a/patch/ByteShift/.vscode/settings.json b/patch/ByteShift/.vscode/settings.json new file mode 100644 index 000000000..57c203072 --- /dev/null +++ b/patch/ByteShift/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "files.associations": { + "algorithm": "cpp" + } +} \ No newline at end of file diff --git a/patch/ByteShift/Makefile b/patch/ByteShift/Makefile index b282f1a6f..25768a012 100644 --- a/patch/ByteShift/Makefile +++ b/patch/ByteShift/Makefile @@ -9,7 +9,7 @@ DAISYSP_DIR = ../../DaisySP SYSTEM_FILES_DIR = $(LIBDAISY_DIR)/core # Source files -CPP_SOURCES = main.cpp byteshift.cpp control_manager.cpp +CPP_SOURCES = main.cpp byteshift.cpp control_manager.cpp bytebeat_synth.cpp # Include the Daisy system Makefile include $(SYSTEM_FILES_DIR)/Makefile diff --git a/patch/ByteShift/README.md b/patch/ByteShift/README.md index 7102f54f6..af25d92d4 100644 --- a/patch/ByteShift/README.md +++ b/patch/ByteShift/README.md @@ -1,42 +1,38 @@ -# 🎡 BytebeatSynth +# 🎡 ByteShift -BytebeatSynth is an experimental music synthesizer that generates sound using mathematical formulas known as bytebeat equations. It runs on the Daisy Patch platform, using control voltage (CV) and knob-based inputs to modify parameters in real time. +ByteShift is an experimental music synthesizer that generates sound using mathematical formulas known as bytebeat equations. It runs on the Daisy Patch platform, using control voltage (CV) and knob-based inputs to modify parameters in real time. ## πŸ”₯ Features - Bytebeat Audio Synthesis: Uses a selection of classic bytebeat formulas. -- Dynamic Formula Parameters: Modify parameters a, b, and c in real-time. -- Adjusts how fast the bytebeat algorithm runs, affecting pitch and timbre, with higher values creating higher tones and lower values slowing it down, sometimes to silence. -- Built-in Formula Switching: Cycle through multiple equations with the encoder. -- Analog & CV Input Controls: Adjust speed, parameters, and pitch using hardware knobs or external control voltages. +- Dynamic Formula Parameters: Modify parameters a, b, and c in real-time. Not all formulas use all all three controls, some use only two of the controls. +- Built-in Formula Switching: Cycle through 6 bytebeat equations. +- CV Input: Adjust parameters. +- Adjust Pitch in Semitones: ByteShift uses PitchShifter from the DaisySP Lib to change the pitch of output. This works positive and negative. PitcheShifter seems to have some limits when lowering pitch. At -12 semitones the sound disappears, and beyond that the picth rises. +- The OLED display shows the values for a, b, c, pitch, and current formula. ## πŸŽ›οΈ Controls ### Control Function -CTRL 1 (Speed/Pitch) Controls the pitch/speed of the bytebeat equation. Accepts 0-5V CV input. -CTRL 2 (Parameter A) Adjusts the first variable (a) in the bytebeat formula. -CTRL 3 (Parameter B) Adjusts the second variable (b) in the bytebeat formula. -CTRL 4 (Parameter C) Adjusts the third variable (c) in the bytebeat formula. -Encoder (Press) Cycles through different bytebeat formulas. -OLED Display Shows the current formula, pitch, and values of a, b, and c. + +| Control | Function | +| :------ | :------- | +| CTRL 1 | (Parameter A) Adjusts the first variable (a) in the bytebeat formula. | +| CTRL 2 | (Parameter B) Adjusts the second variable (b) in the bytebeat formula. | +| CTRL 3 | (Parameter C) Adjusts the third variable (c) in the bytebeat formula. | +| Encoder (Press) | Cycles through different bytebeat formulas. | +| Encoder (Rotate) | changes the pitch it semitones. | ## ⚑ How to Use -1. Turn CTRL 1 to change the pitch and speed of the bytebeat synthesis. -2. Adjust CTRL 2, 3, and 4 to manipulate different formula parameters. -3. Press the encoder to switch to a different bytebeat equation. -4. Use CV input on CTRL 1 to modulate pitch from an external sequencer or LFO. -5. Observe the OLED display to see current formula and parameter values. +1. Audio is sent to out1 and out 2. +2. Use CTRL1-3 to adjust parameters affecting bytebeat formula. +3. Rotate the encoder to change the pitch in semitones. +4. Press the encoder to change the bytebeat formula. ## πŸ› οΈ Installation & Compilation To build and flash BytebeatSynth onto your Daisy Patch: 1. Clone or copy the project files. 2. Run `make` to compile the firmware. -3. Flash the binary using: `st-flash --reset write build/bytebeat.bin 0x08000000` - -## 🎚️ Example Patching Ideas -- Sequenced Rhythms: Use an external CV sequencer on CTRL 1 for evolving patterns. -- Glitchy Drones: Set CTRL 1 low and tweak CTRL 2, 3, 4 for texture shifts. -- Noise Percussion: Use a gate signal on CTRL 1 to trigger bursts of sound. ## 🎡 What is a Bytebeat? diff --git a/patch/ByteShift/bytebeat.cpp b/patch/ByteShift/bytebeat.cpp deleted file mode 100644 index 76976f5fb..000000000 --- a/patch/ByteShift/bytebeat.cpp +++ /dev/null @@ -1,18 +0,0 @@ -#include "bytebeat_synth.h" - -int main(void) { - // Create an instance of DaisyPatch assigned to local var patch - DaisyPatch patch; - // Creat an instance of BytebeatSynth assigned to local var synth - BytebeatSynth synth; - // Initialize synth pass patch. Is this a reference what is & ? - synth.Init(&patch); - - // Create a main program loop - while (1) { - // Update display - synth.UpdateDisplay(); - // Chill for 50ms - System::Delay(50); - } -} \ No newline at end of file diff --git a/patch/ByteShift/bytebeat_synth.cpp b/patch/ByteShift/bytebeat_synth.cpp index 2deee1e9c..e306552ab 100644 --- a/patch/ByteShift/bytebeat_synth.cpp +++ b/patch/ByteShift/bytebeat_synth.cpp @@ -1,9 +1,12 @@ #include "bytebeat_synth.h" -// Global instance pointer for static callback -static BytebeatSynth* synthInstance = nullptr; +// ******************************************************************* +// +// Bytebeat formulas +// +// ******************************************************************* -// Bytebeat Function Table +// Bytebeat Formulas (Function Pointer Table) BytebeatSynth::BytebeatFunc BytebeatSynth::formulaTable[] = { &BytebeatSynth::BytebeatFormula0, &BytebeatSynth::BytebeatFormula1, @@ -13,22 +16,25 @@ BytebeatSynth::BytebeatFunc BytebeatSynth::formulaTable[] = { &BytebeatSynth::BytebeatFormula5 }; -// Bytebeat Equation Definitions + +// Bytebeat Equation Definitions (Now use a, b, c) uint8_t BytebeatSynth::BytebeatFormula0(uint32_t t, BytebeatSynth* synth) { - int a = synth->a; + int a = synth->a; int b = synth->b; - return (t * (t >> a) & 42) | (t * (t >> b) & 84); + + uint8_t result = (t * (t >> a) & 42) | (t * (t >> b) & 84); + return result; } uint8_t BytebeatSynth::BytebeatFormula1(uint32_t t, BytebeatSynth* synth) { - int a = synth->a; + int a = synth->a; int b = synth->b; int c = synth->c; return (t >> a) | (t << b) | (t * (t >> c)); } uint8_t BytebeatSynth::BytebeatFormula2(uint32_t t, BytebeatSynth* synth) { - int a = synth->a; // Stored in a register (if optimized) + int a = synth->a; int b = synth->b; return (t * (t >> a) | (t * (t >> b) & 50)); } @@ -40,94 +46,50 @@ uint8_t BytebeatSynth::BytebeatFormula3(uint32_t t, BytebeatSynth* synth) { } uint8_t BytebeatSynth::BytebeatFormula4(uint32_t t, BytebeatSynth* synth) { - int a = synth->a; // Stored in a register (if optimized) + int a = synth->a; int b = synth->b; return (t * 5 & (t >> a)) | (t * (t >> b) & 123); } uint8_t BytebeatSynth::BytebeatFormula5(uint32_t t, BytebeatSynth* synth) { - int a = synth->a; // Stored in a register (if optimized) + int a = synth->a; int b = synth->b; return (t >> a) ^ (t * (t >> b) & 32); } -// Initialize Synth -void BytebeatSynth::Init(DaisyPatch* p) { - patch = p; - t = 0; - formula_index = 0; - synthInstance = this; - - // Initialize Parameters - a = 4; - b = 6; - c = 8; - - patch->Init(); - patch->StartAdc(); -} - -// Update Controls (Knobs & Encoder) -void BytebeatSynth::UpdateControls() { - patch->ProcessAnalogControls(); - patch->ProcessDigitalControls(); - - float pitchCV = patch->GetKnobValue(DaisyPatch::CTRL_1) * 5.0f; - float frequency = 16.0f * powf(2.0f, pitchCV - 1.0f); - tScale = 48000.0f / frequency; - - a = (int)(patch->GetKnobValue(DaisyPatch::CTRL_2) * 16) + 1; - b = (int)(patch->GetKnobValue(DaisyPatch::CTRL_3) * 32) + 1; - c = (int)(patch->GetKnobValue(DaisyPatch::CTRL_4) * 64) + 1; - if (patch->encoder.RisingEdge()) { - formula_index = (formula_index + 1) % formula_count; - } -} +// ******************************************************************* +// +// Bytebeat Synth +// +// ******************************************************************* -// Generate a single sample for external processing float BytebeatSynth::GenerateSample() { - BytebeatFunc currentFormula = formulaTable[formula_index]; - - static float tAccumulator = 0; - tAccumulator += 1.0f; - if (tAccumulator >= tScale) { - t += (uint32_t)(tScale); - tAccumulator -= tScale; - } - - uint8_t sample = currentFormula(t, this); - return ((float)sample / 255.0f) * 2.0f - 1.0f; -} - -void BytebeatSynth::NextFormula() { - formula_index = (formula_index + 1) % formula_count; + static uint32_t t = 0; + t++; // Increment counter each sample + + // Get the current formula + BytebeatFunc currentFormula = formulaTable[formulaIndex]; + // Calculate the formula + uint8_t output = currentFormula(t, this); + float sample = ((float)output / 255.0f) * 2.0f - 1.0f; + + // Basic Bytebeat formula + // Don't lose this formula it sounded good. + // float sample = ((t * (t >> a | t >> b) & c & t >> 8)) / 255.0f; + + return sample; } -// OLED Display Update -void BytebeatSynth::UpdateDisplay() { - static int last_formula = -1; - static int last_a = -1, last_b = -1, last_c = -1; +void BytebeatSynth::UpdateControls(ControlManager& controlManager) { + // Read control values + a = (int)(controlManager.GetCtrl1() * 16) + 1; + b = (int)(controlManager.GetCtrl2() * 32) + 1; + c = (int)(controlManager.GetCtrl3() * 64) + 1; - if (formula_index != last_formula || a != last_a || b != last_b || c != last_c) { - patch->display.Fill(false); - patch->display.SetCursor(0, 0); - patch->display.WriteString("Bytebeat Synth", Font_7x10, true); - - char buffer[32]; - snprintf(buffer, sizeof(buffer), "Formula: %d", formula_index); - patch->display.SetCursor(0, 12); - patch->display.WriteString(buffer, Font_7x10, true); - - snprintf(buffer, sizeof(buffer), "a: %d b: %d c: %d", a, b, c); - patch->display.SetCursor(0, 24); - patch->display.WriteString(buffer, Font_7x10, true); - - patch->display.Update(); - - last_formula = formula_index; - last_a = a; - last_b = b; - last_c = c; + // Advance the formula index if the encoder is pressed. + if (controlManager.IsEncoderPressed()) { + formulaIndex = (formulaIndex + 1) % FORMULA_COUNT; } -} \ No newline at end of file + +} diff --git a/patch/ByteShift/bytebeat_synth.h b/patch/ByteShift/bytebeat_synth.h index 5429bb62e..dee4d014e 100644 --- a/patch/ByteShift/bytebeat_synth.h +++ b/patch/ByteShift/bytebeat_synth.h @@ -2,33 +2,29 @@ #define BYTEBEAT_SYNTH_H #include "daisy_patch.h" -#include "daisysp.h" +#include "control_manager.h" -using namespace daisy; -using namespace daisysp; + +// ******************************************************************* +// +// Bytebeat Synth h +// +// ******************************************************************* class BytebeatSynth { public: - void Init(DaisyPatch* p); - void UpdateControls(); - float GenerateSample(); // Now returns a single sample for processing - void UpdateDisplay(); - float ProcessSample(); - void NextFormula(); - int GetFormulaIndex() const { return formula_index; } + void Init(daisy::DaisyPatch* p) { patch = p; } + float GenerateSample(); + void UpdateControls(ControlManager& controlManager); -private: - DaisyPatch* patch; - uint32_t t; - int formula_index; - static const int formula_count = 6; + int a, b, c, formulaIndex; - // Dynamic formula parameters - int a, b, c; + static const int FORMULA_COUNT = 6; - // Scaling factor for musically tuned timing - float tScale; +private: + daisy::DaisyPatch* patch; + // Bytebeat formulas using BytebeatFunc = uint8_t (*)(uint32_t, BytebeatSynth*); static BytebeatFunc formulaTable[]; @@ -38,6 +34,7 @@ class BytebeatSynth { static uint8_t BytebeatFormula3(uint32_t t, BytebeatSynth* synth); static uint8_t BytebeatFormula4(uint32_t t, BytebeatSynth* synth); static uint8_t BytebeatFormula5(uint32_t t, BytebeatSynth* synth); + }; #endif // BYTEBEAT_SYNTH_H \ No newline at end of file diff --git a/patch/ByteShift/byteshift.cpp b/patch/ByteShift/byteshift.cpp index 9f9da9d2c..b13bc230c 100644 --- a/patch/ByteShift/byteshift.cpp +++ b/patch/ByteShift/byteshift.cpp @@ -1,22 +1,22 @@ #include "byteshift.h" +#include -void ByteShift::Init(daisy::DaisyPatch* p) { - patch = p; - speed = paramA = paramB = paramC = 0.0f; - encoderValue = 0; +// ******************************************************************* +// +// Byte Shift +// +// ******************************************************************* + +void ByteShift::Init(float sampleRate) { + pitchShifter.Init(sampleRate); + pitchShifter.SetTransposition(0.0f); // Default: No pitch shift } -void ByteShift::SetControlValues(float c1, float c2, float c3, float c4, int enc) { - speed = c1; - paramA = c2; - paramB = c3; - paramC = c4; - encoderValue = enc; +void ByteShift::SetPitchShift(int encoderCount) { + float semitoneShift = static_cast(encoderCount); + pitchShifter.SetTransposition(semitoneShift); } -void ByteShift::ProcessAudio(float** out, size_t size) { - for (size_t i = 0; i < size; i++) { - out[0][i] = speed * 0.01f; - out[1][i] = paramA * 0.01f; - } +float ByteShift::ProcessSample(float inSample) { + return pitchShifter.Process(inSample); } \ No newline at end of file diff --git a/patch/ByteShift/byteshift.h b/patch/ByteShift/byteshift.h index cb1a34473..442038ce8 100644 --- a/patch/ByteShift/byteshift.h +++ b/patch/ByteShift/byteshift.h @@ -1,19 +1,22 @@ #ifndef BYTESHIFT_H #define BYTESHIFT_H -#include "daisy_patch.h" -#include "control_manager.h" +#include "daisysp.h" + +// ******************************************************************* +// +// Byte shift +// +// ******************************************************************* class ByteShift { public: - void Init(daisy::DaisyPatch* p); - void ProcessAudio(float** out, size_t size); - void SetControlValues(float c1, float c2, float c3, float c4, int enc); + void Init(float sampleRate); + float ProcessSample(float inSample); // Takes input sample from BytebeatSynth + void SetPitchShift(int encoderCount); private: - daisy::DaisyPatch* patch; - float speed, paramA, paramB, paramC; - int encoderValue; + daisysp::PitchShifter pitchShifter; }; #endif \ No newline at end of file diff --git a/patch/ByteShift/control_manager.cpp b/patch/ByteShift/control_manager.cpp index 1d0bf7f1f..2c7ed3dca 100644 --- a/patch/ByteShift/control_manager.cpp +++ b/patch/ByteShift/control_manager.cpp @@ -1,8 +1,14 @@ #include "control_manager.h" +// ******************************************************************* +// +// Control Manager +// +// ******************************************************************* + void ControlManager::Init(daisy::DaisyPatch* p) { patch = p; - patch->StartAdc(); + patch->StartAdc(); // Must start ADC before reading controls! ctrl1 = ctrl2 = ctrl3 = ctrl4 = 0.0f; encoderCount = 0; @@ -10,16 +16,21 @@ void ControlManager::Init(daisy::DaisyPatch* p) { } void ControlManager::Update() { + // Read control values patch->ProcessAnalogControls(); patch->ProcessDigitalControls(); + // Store control values ctrl1 = patch->GetKnobValue(daisy::DaisyPatch::CTRL_1); ctrl2 = patch->GetKnobValue(daisy::DaisyPatch::CTRL_2); ctrl3 = patch->GetKnobValue(daisy::DaisyPatch::CTRL_3); ctrl4 = patch->GetKnobValue(daisy::DaisyPatch::CTRL_4); + // Read the encoder encoderCount += patch->encoder.Increment(); encoderPressed = patch->encoder.RisingEdge(); + + // Reset pitch shift when changing formula if (encoderPressed) { encoderCount = 0; } diff --git a/patch/ByteShift/control_manager.h b/patch/ByteShift/control_manager.h index aeeb60b95..24b48ea01 100644 --- a/patch/ByteShift/control_manager.h +++ b/patch/ByteShift/control_manager.h @@ -3,15 +3,24 @@ #include "daisy_patch.h" +// ******************************************************************* +// +// Control Manager h +// +// ******************************************************************* + class ControlManager { public: void Init(daisy::DaisyPatch* p); void Update(); + // Getters return raw control values float GetCtrl1() const { return ctrl1; } float GetCtrl2() const { return ctrl2; } float GetCtrl3() const { return ctrl3; } float GetCtrl4() const { return ctrl4; } + + // Getter returns the encoder count and isPressed int GetEncoderCount() const { return encoderCount; } bool IsEncoderPressed() const { return encoderPressed; } diff --git a/patch/ByteShift/main.cpp b/patch/ByteShift/main.cpp index 0383ebd16..e1c12906e 100644 --- a/patch/ByteShift/main.cpp +++ b/patch/ByteShift/main.cpp @@ -1,65 +1,86 @@ + +// An app for Daisy Patch that creates a Bytebeat synthesizer. + #include "daisy_patch.h" #include "control_manager.h" +#include "bytebeat_synth.h" #include "byteshift.h" -daisy::DaisyPatch patch; +using namespace daisy; + +DaisyPatch patch; ControlManager controlManager; -ByteShift synth; +BytebeatSynth bytebeat; +ByteShift byteshift; + +// ******************************************************************* +// +// ⚑ Audio callback function +// +// ******************************************************************* -static ByteShift* synthInstance = nullptr; // Pointer to ByteShift instance +void AudioCallback(const float* const* in, float** out, size_t size) { + for (size_t i = 0; i < size; i++) { + // TODO: pass the control values to Generate sample to decouple controlManager + float rawSample = bytebeat.GenerateSample(); // Bytebeat audio + float shiftedSample = byteshift.ProcessSample(rawSample); // Apply pitch shifting -void AudioCallbackWrapper(daisy::AudioHandle::InputBuffer in, - daisy::AudioHandle::OutputBuffer out, - size_t size) { - if (synthInstance) { - synthInstance->ProcessAudio(out, size); + out[0][i] = shiftedSample; // Left Channel + out[1][i] = shiftedSample; // Right Channel } } + +// ******************************************************************* +// +// Main +// +// ******************************************************************* + int main(void) { + // initialize classes patch.Init(); - controlManager.Init(&patch); - synth.Init(&patch); + bytebeat.Init(&patch); // TODO: Does this need patch? + byteshift.Init(patch.AudioSampleRate()); - synthInstance = &synth; // Assign instance before calling StartAudio - patch.StartAudio(AudioCallbackWrapper); + patch.StartAudio(AudioCallback); - while (1) { + while (true) { controlManager.Update(); - synth.SetControlValues(controlManager.GetCtrl1(), - controlManager.GetCtrl2(), - controlManager.GetCtrl3(), - controlManager.GetCtrl4(), - controlManager.GetEncoderCount()); + bytebeat.UpdateControls(controlManager); // Update Bytebeat with controls + byteshift.SetPitchShift(controlManager.GetEncoderCount()); + // Clear the display and show the app name patch.display.Fill(false); patch.display.SetCursor(0, 0); - patch.display.WriteString("ByteShift v0.1", Font_7x10, true); + patch.display.WriteString("ByteShift v1.0", Font_7x10, true); char buffer[32]; - - snprintf(buffer, sizeof(buffer), "C1: %d", (int)(controlManager.GetCtrl1() * 100)); + + // Display control values + snprintf(buffer, sizeof(buffer), "a: %d", bytebeat.a); patch.display.SetCursor(0, 12); patch.display.WriteString(buffer, Font_7x10, true); - snprintf(buffer, sizeof(buffer), "C2: %d", (int)(controlManager.GetCtrl2() * 100)); - patch.display.SetCursor(0, 24); + snprintf(buffer, sizeof(buffer), "b: %d", (int)(controlManager.GetCtrl2() * 100)); + patch.display.SetCursor(42, 12); patch.display.WriteString(buffer, Font_7x10, true); - snprintf(buffer, sizeof(buffer), "C3: %d", (int)(controlManager.GetCtrl3() * 100)); - patch.display.SetCursor(0, 36); + snprintf(buffer, sizeof(buffer), "c: %d", (int)(controlManager.GetCtrl3() * 100)); + patch.display.SetCursor(84, 12); patch.display.WriteString(buffer, Font_7x10, true); - snprintf(buffer, sizeof(buffer), "C4: %d", (int)(controlManager.GetCtrl4() * 100)); - patch.display.SetCursor(0, 48); + // Display pitche shift + snprintf(buffer, sizeof(buffer), "Pitch: %d", controlManager.GetEncoderCount()); + patch.display.SetCursor(0, 24); patch.display.WriteString(buffer, Font_7x10, true); - snprintf(buffer, sizeof(buffer), "Enc: %d", controlManager.GetEncoderCount()); - patch.display.SetCursor(60, 12); + // Display Bytebeat formula + snprintf(buffer, sizeof(buffer), "Formula: %d", bytebeat.formulaIndex); + patch.display.SetCursor(0, 36); patch.display.WriteString(buffer, Font_7x10, true); patch.display.Update(); - daisy::System::Delay(5); } -} +} \ No newline at end of file diff --git a/patch/bytebeat/Makefile b/patch/bytebeat/Makefile index 028332aef..61106b23b 100644 --- a/patch/bytebeat/Makefile +++ b/patch/bytebeat/Makefile @@ -18,6 +18,18 @@ CPP_SOURCES = bytebeat.cpp bytebeat_synth.cpp # Include the Daisy system Makefile include $(SYSTEM_FILES_DIR)/Makefile +# Define the compiled program binary +PROGRAM = bytebeat.bin +FLASH_ADDR = 0x08000000 + +# Convert ELF to BIN +build/$(PROGRAM): build/$(TARGET).elf + arm-none-eabi-objcopy -O binary build/$(TARGET).elf build/$(PROGRAM) + +# Flash to Daisy Patch using ST-LINK V3 Mini +program: build/$(PROGRAM) + st-flash --reset write build/$(PROGRAM) $(FLASH_ADDR) + diff --git a/patch/bytebeat/bytebeat.cpp b/patch/bytebeat/bytebeat.cpp index 76976f5fb..834ce3a34 100644 --- a/patch/bytebeat/bytebeat.cpp +++ b/patch/bytebeat/bytebeat.cpp @@ -1,5 +1,11 @@ #include "bytebeat_synth.h" +// ******************************************************************* +// +// Bytebeat Synth main +// +// ******************************************************************* + int main(void) { // Create an instance of DaisyPatch assigned to local var patch DaisyPatch patch; @@ -15,4 +21,4 @@ int main(void) { // Chill for 50ms System::Delay(50); } -} \ No newline at end of file +} diff --git a/patch/bytebeat/bytebeat_synth.h b/patch/bytebeat/bytebeat_synth.h index 452ddc6e9..d88e2e3f9 100644 --- a/patch/bytebeat/bytebeat_synth.h +++ b/patch/bytebeat/bytebeat_synth.h @@ -7,6 +7,12 @@ using namespace daisy; using namespace daisysp; +// ******************************************************************* +// +// Bytebeat Synth h +// +// ******************************************************************* + class BytebeatSynth { public: void Init(DaisyPatch* p); @@ -18,7 +24,7 @@ class BytebeatSynth { DaisyPatch* patch; float speed; uint32_t t; - float tScale; // βœ… Declare tScale + float tScale; int formula_index; static const int formula_count = 6; From ab438b9fd09d151a2ef057a788bba011287e5169 Mon Sep 17 00:00:00 2001 From: Mitchell Hudson Date: Mon, 17 Mar 2025 14:34:17 -0700 Subject: [PATCH 5/5] fixed --- patch/ByteShift/byteshift.cpp | 2 +- patch/ByteShift/byteshift.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/patch/ByteShift/byteshift.cpp b/patch/ByteShift/byteshift.cpp index b13bc230c..c6d47405a 100644 --- a/patch/ByteShift/byteshift.cpp +++ b/patch/ByteShift/byteshift.cpp @@ -19,4 +19,4 @@ void ByteShift::SetPitchShift(int encoderCount) { float ByteShift::ProcessSample(float inSample) { return pitchShifter.Process(inSample); -} \ No newline at end of file +} diff --git a/patch/ByteShift/byteshift.h b/patch/ByteShift/byteshift.h index 442038ce8..626216c2f 100644 --- a/patch/ByteShift/byteshift.h +++ b/patch/ByteShift/byteshift.h @@ -19,4 +19,4 @@ class ByteShift { daisysp::PitchShifter pitchShifter; }; -#endif \ No newline at end of file +#endif