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 new file mode 100644 index 000000000..25768a012 --- /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 control_manager.cpp bytebeat_synth.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..af25d92d4 --- /dev/null +++ b/patch/ByteShift/README.md @@ -0,0 +1,43 @@ +# 🎵 ByteShift + +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. 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 + +| 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. 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. + +## 🎵 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_synth.cpp b/patch/ByteShift/bytebeat_synth.cpp new file mode 100644 index 000000000..e306552ab --- /dev/null +++ b/patch/ByteShift/bytebeat_synth.cpp @@ -0,0 +1,95 @@ +#include "bytebeat_synth.h" + +// ******************************************************************* +// +// Bytebeat formulas +// +// ******************************************************************* + +// 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; + 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; + 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; + 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; + 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; + int b = synth->b; + return (t >> a) ^ (t * (t >> b) & 32); +} + + +// ******************************************************************* +// +// Bytebeat Synth +// +// ******************************************************************* + +float BytebeatSynth::GenerateSample() { + 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; +} + +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; + + // Advance the formula index if the encoder is pressed. + if (controlManager.IsEncoderPressed()) { + formulaIndex = (formulaIndex + 1) % FORMULA_COUNT; + } + +} diff --git a/patch/ByteShift/bytebeat_synth.h b/patch/ByteShift/bytebeat_synth.h new file mode 100644 index 000000000..dee4d014e --- /dev/null +++ b/patch/ByteShift/bytebeat_synth.h @@ -0,0 +1,40 @@ +#ifndef BYTEBEAT_SYNTH_H +#define BYTEBEAT_SYNTH_H + +#include "daisy_patch.h" +#include "control_manager.h" + + +// ******************************************************************* +// +// Bytebeat Synth h +// +// ******************************************************************* + +class BytebeatSynth { +public: + void Init(daisy::DaisyPatch* p) { patch = p; } + float GenerateSample(); + void UpdateControls(ControlManager& controlManager); + + int a, b, c, formulaIndex; + + static const int FORMULA_COUNT = 6; + +private: + daisy::DaisyPatch* patch; + + // Bytebeat formulas + 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..c6d47405a --- /dev/null +++ b/patch/ByteShift/byteshift.cpp @@ -0,0 +1,22 @@ +#include "byteshift.h" +#include + +// ******************************************************************* +// +// Byte Shift +// +// ******************************************************************* + +void ByteShift::Init(float sampleRate) { + pitchShifter.Init(sampleRate); + pitchShifter.SetTransposition(0.0f); // Default: No pitch shift +} + +void ByteShift::SetPitchShift(int encoderCount) { + float semitoneShift = static_cast(encoderCount); + pitchShifter.SetTransposition(semitoneShift); +} + +float ByteShift::ProcessSample(float inSample) { + return pitchShifter.Process(inSample); +} diff --git a/patch/ByteShift/byteshift.h b/patch/ByteShift/byteshift.h new file mode 100644 index 000000000..626216c2f --- /dev/null +++ b/patch/ByteShift/byteshift.h @@ -0,0 +1,22 @@ +#ifndef BYTESHIFT_H +#define BYTESHIFT_H + +#include "daisysp.h" + +// ******************************************************************* +// +// Byte shift +// +// ******************************************************************* + +class ByteShift { +public: + void Init(float sampleRate); + float ProcessSample(float inSample); // Takes input sample from BytebeatSynth + void SetPitchShift(int encoderCount); + +private: + daisysp::PitchShifter pitchShifter; +}; + +#endif diff --git a/patch/ByteShift/control_manager.cpp b/patch/ByteShift/control_manager.cpp new file mode 100644 index 000000000..2c7ed3dca --- /dev/null +++ b/patch/ByteShift/control_manager.cpp @@ -0,0 +1,37 @@ +#include "control_manager.h" + +// ******************************************************************* +// +// Control Manager +// +// ******************************************************************* + +void ControlManager::Init(daisy::DaisyPatch* p) { + patch = p; + patch->StartAdc(); // Must start ADC before reading controls! + + ctrl1 = ctrl2 = ctrl3 = ctrl4 = 0.0f; + encoderCount = 0; + encoderPressed = false; +} + +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; + } +} \ 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..24b48ea01 --- /dev/null +++ b/patch/ByteShift/control_manager.h @@ -0,0 +1,35 @@ +#ifndef CONTROL_MANAGER_H +#define CONTROL_MANAGER_H + +#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; } + +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..e1c12906e --- /dev/null +++ b/patch/ByteShift/main.cpp @@ -0,0 +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" + +using namespace daisy; + +DaisyPatch patch; +ControlManager controlManager; +BytebeatSynth bytebeat; +ByteShift byteshift; + +// ******************************************************************* +// +// ⚡ Audio callback function +// +// ******************************************************************* + +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 + + out[0][i] = shiftedSample; // Left Channel + out[1][i] = shiftedSample; // Right Channel + } +} + + +// ******************************************************************* +// +// Main +// +// ******************************************************************* + +int main(void) { + // initialize classes + patch.Init(); + controlManager.Init(&patch); + bytebeat.Init(&patch); // TODO: Does this need patch? + byteshift.Init(patch.AudioSampleRate()); + + patch.StartAudio(AudioCallback); + + while (true) { + controlManager.Update(); + 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 v1.0", Font_7x10, true); + + char buffer[32]; + + // 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), "b: %d", (int)(controlManager.GetCtrl2() * 100)); + patch.display.SetCursor(42, 12); + patch.display.WriteString(buffer, Font_7x10, true); + + snprintf(buffer, sizeof(buffer), "c: %d", (int)(controlManager.GetCtrl3() * 100)); + patch.display.SetCursor(84, 12); + patch.display.WriteString(buffer, Font_7x10, true); + + // Display pitche shift + snprintf(buffer, sizeof(buffer), "Pitch: %d", controlManager.GetEncoderCount()); + patch.display.SetCursor(0, 24); + patch.display.WriteString(buffer, Font_7x10, true); + + // 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(); + } +} \ No newline at end of file diff --git a/patch/bytebeat/Makefile b/patch/bytebeat/Makefile new file mode 100644 index 000000000..61106b23b --- /dev/null +++ b/patch/bytebeat/Makefile @@ -0,0 +1,37 @@ +# 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 + +# 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/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..834ce3a34 --- /dev/null +++ b/patch/bytebeat/bytebeat.cpp @@ -0,0 +1,24 @@ +#include "bytebeat_synth.h" + +// ******************************************************************* +// +// Bytebeat Synth main +// +// ******************************************************************* + +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); + } +} diff --git a/patch/bytebeat/bytebeat_synth.cpp b/patch/bytebeat/bytebeat_synth.cpp new file mode 100644 index 000000000..57133784c --- /dev/null +++ b/patch/bytebeat/bytebeat_synth.cpp @@ -0,0 +1,196 @@ +#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; +// } +// } + +// ***************************************************** +// +// 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) + +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; + } +} + + +// ************************************************* +// +// Update Display +// +// ************************************************* + +// 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..d88e2e3f9 --- /dev/null +++ b/patch/bytebeat/bytebeat_synth.h @@ -0,0 +1,44 @@ +#ifndef BYTEBEAT_SYNTH_H +#define BYTEBEAT_SYNTH_H + +#include "daisy_patch.h" +#include "daisysp.h" + +using namespace daisy; +using namespace daisysp; + +// ******************************************************************* +// +// Bytebeat Synth h +// +// ******************************************************************* + +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; + 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