Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions patch/ByteShift/.vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"files.associations": {
"algorithm": "cpp"
}
}
62 changes: 62 additions & 0 deletions patch/ByteShift/Makefile
Original file line number Diff line number Diff line change
@@ -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)
43 changes: 43 additions & 0 deletions patch/ByteShift/README.md
Original file line number Diff line number Diff line change
@@ -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.
95 changes: 95 additions & 0 deletions patch/ByteShift/bytebeat_synth.cpp
Original file line number Diff line number Diff line change
@@ -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;
}

}
40 changes: 40 additions & 0 deletions patch/ByteShift/bytebeat_synth.h
Original file line number Diff line number Diff line change
@@ -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
22 changes: 22 additions & 0 deletions patch/ByteShift/byteshift.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
#include "byteshift.h"
#include <cmath>

// *******************************************************************
//
// 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<float>(encoderCount);
pitchShifter.SetTransposition(semitoneShift);
}

float ByteShift::ProcessSample(float inSample) {
return pitchShifter.Process(inSample);
}
22 changes: 22 additions & 0 deletions patch/ByteShift/byteshift.h
Original file line number Diff line number Diff line change
@@ -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
37 changes: 37 additions & 0 deletions patch/ByteShift/control_manager.cpp
Original file line number Diff line number Diff line change
@@ -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;
}
}
35 changes: 35 additions & 0 deletions patch/ByteShift/control_manager.h
Original file line number Diff line number Diff line change
@@ -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
Loading