Skip to content

Repository files navigation

Touch-Based Audio Synthesizer

Touch-Based Audio Synthesizer

Polyphonic touch-based synthesizer running bare-metal on STM32. 4 track looper, 43 instrument, 5 FX chain, WAV export to SD card. Inspired by KORG KAOSSILATOR PRO.

EECS 373 Final Project, University of Michigan, Winter 2025.

Team

  • Kai Kin Sou
  • Tong Sing Wu
  • Linglong Qian
  • Ethan Kocheril

Demo

demo preview

Short preview above (no sound). Full demo video with sound: docs/demo.mp4 (stored with Git LFS, about 340 MB, use "View raw" to download).

Table of Contents

Quick Start

  1. Power on, hear silence (or VU meter idle on the 8 digit SSD)
  2. R1 turn: pick instrument (43 to choose from)
  3. Touch screen: x = pitch, y = filter cutoff
  4. R2 turn for BPM, or tap TEMPO 4 time to set
  5. Press REC, play some note, press REC again to stop. Use TRK 1-4 to switch track and layer
  6. Shift + REC: export current loop to SD as expNN.wav (48 kHz, 16 bit mono)

If something weird happen, see docs/CONTROLS.md for what every button do in each mode.

Features

  • 4 track looper, each track up to 384 note, per-track loop length, gain, mute
  • 43 instrument (lead, drum, bass, string, chord)
  • 6 waveform oscillator (sine, saw, square, soft saw, piano, pulse) from a 65536 entry wavetable
  • 5 effect chain: bitcrush, tremolo, ringmod, delay, compressor, plus a separate pitch shift (a distortion stage is in the code but left off in the final build)
  • 103 preset song (game, anime, classical, pop), browse with Shift+FX+R1
  • 20 metronome pattern (rock, jazz, bossa, dubstep, etc.)
  • Touch screen with x = pitch, y = filter cutoff, with octave shift
  • Tap tempo, BPM 30 to 400
  • WAV export to SD card (48 kHz, 16 bit mono)
  • 8 digit 7-seg display with VU meter when idle
  • Per-note "shift" snapshot for slide / glide effect inside a single note

How It Works

The STM32 run at 120 MHz, and a separate PLLSAI1 clock make the 48 kHz audio rate so the sample rate stay stable no matter the CPU load. Audio go out over I2S to a MAX98357A class D amp.

  • The SAI DMA use a double buffer (ping-pong). Every 2667 us it fire an interrupt and DMA_fill() run mixer_next() once per sample to sum all active voice. While the DMA play one half, the CPU fill the other half, so SD card write do not cause glitch.
  • TIM7 fire at the BPM tick rate and drive the looper. When a track pointer reach a Record, the looper spawn a Sound into a fixed pool of 8 voice.
  • A Record is a small note descriptor (pitch, start tick, duration, and up to 8 Shift snapshot for the x/y slide). A live Sound is the heavy DSP state, around 20 KB with the Karplus-Strong ring buffer. Hundreds of Record can sit in RAM but only 8 Sound exist at once, so the pool work like a priority scheduler: when it is full, the lowest priority voice get evicted.
  • Writing a new voice into the pool is wrapped in a __disable_irq() critical section, so the 48 kHz DMA interrupt never read a half-built Sound.

Full math and code walk through is in docs/DSP.md.

Touch Input

LCD touch grid is 12 col x 8 row. The x axis map to pitch (1 octave per 480 px), y axis map to filter cutoff (200 Hz to 8000 Hz, top = bright).

touch input mapping

R4 turn (Shift held) shift the octave by 1.

Effect Rack

5 effect in series, each can be toggle on/off independently. Order in the chain (after mixer):

bitcrush -> tremolo -> ringmod -> delay -> compressor

Pitch FX is separate, apply pre osc (multiply the freq by the pitch ratio when create new sound). A distortion stage also exist in the code but is left off in the final build.

FX Param 1 (R3) Param 2 (R4) Param 3 (R2)
Delay feedback time wet mix
Tremolo depth rate n/a
RingMod freq depth n/a
Bitcrush bits level n/a
Compressor threshold ratio makeup
Pitch semitone shift (-24 to +24) n/a n/a

Hold FX and turn R1 to pick which effect to edit, hold FX+REC to toggle that effect on/off.

Hardware

Quick summary, full pin map and CubeMX config in docs/HARDWARE.md.

Part Connection
MCU STM32 Nucleo-L4R5ZI-P, 120 MHz, 2 MB Flash, 640 KB SRAM
LCD 4" TFT 480x320 ILI9488, SPI2
Touch XPT2046 resistive ADC, SPI1
Amp Adafruit MAX98357A I2S Class D, SAI1
SD card Adafruit Micro SD SPI breakout, SPI3, FATFS
SSD HT16K33 8 digit 7-seg, I2C1
Encoder x4 quadrature on GPIO
Button x9 active low on GPIO (+ 1 Nucleo debug button)
ADC master gain pot (single channel)

Build & Flash

  1. Open eecs373_project.ioc in STM32CubeIDE (the .ioc regenerate the HAL init code if needed)
  2. Target: STM32L4R5ZITxP
  3. Project -> Build All
  4. Run -> Run / Debug, ST-Link auto detect via the on board programmer

Note: regenerating from the .ioc needs you to sign in to an ST account inside the IDE. Without it CubeMX will quietly skip the codegen and you keep running the old pin map (this cost us two days, see Known Issue).

Controls

Five mode (PLAY, REC, EDIT, SHIFT_EDIT, EXPORT), each with own button mapping. Quick summary:

Mode How to enter What it do
PLAY default play loop, touch screen, knob to change instr/BPM/octave
REC press REC record touched note into selected track
EXPORT Shift+REC render all track to wav on SD
EDIT Shift+TEMPO (track has note) edit individual note pitch / start / end
SHIFT_EDIT R1 click in EDIT edit per-shift x / y / gain inside the note

Full reference for every control combo in docs/CONTROLS.md.

Folder Structure

eecs373_project/
├── Core/
│   ├── Inc/             # header
│   │   └── songs/       # 103 preset song + metro.h
│   ├── Src/             # source (30 file)
│   └── Startup/
├── Drivers/             # STM32 HAL + CMSIS (vendor)
├── FATFS/               # FATFS middleware (vendor)
├── Middlewares/         # ST middleware (vendor)
├── tools/               # python helper script (midi2record, wav2lut, ...)
├── docs/                # extended doc (DSP, CONTROLS, HARDWARE) + demo video
├── img/                 # README image
├── eecs373_project.ioc  # CubeMX config
└── README.md

Tools

Python script in tools/ for adding song / instrument / lut. See tools/README.md for detail.

Script What it do
midi2record.py convert .mid to song header
song_index.py scan songs folder, print #include block for pattern.h
instrument_creator.py print template C code for new instrument
pattern_visualize.py ASCII piano roll for a song
wav2lut.py bake wav into int16 LUT
envelope_tune.py matplotlib slider for ADSR tuning
memory_report.py top RAM / Flash consumer from .map

Memory & CPU Footprint

Run python3 tools/memory_report.py after a build for live number. Approx from final firmware:

Resource Used Total %
Flash ~580 KB 2 MB 28%
RAM ~145 KB 640 KB 22%

Polyphony / CPU at 120 MHz, 48 kHz, budget 2667 us per fill:

Voice FX off FX on (all)
1 ~150us ~280us
4 ~600us ~750us
8 ~1200us ~1500us

Stay under budget at max polyphony (8 voice) with all FX on, with margin for SD card export inline. These number are approximate and a bit optimistic, the real cost per voice depends on the instrument (Karplus-Strong string and heavy filter sweep cost more than a plain oscillator), the point is it stay real time at 8 voice with FX on.

Gallery

3D printed enclosure Demo day at the EECS 373 expo In-house printed PCB attempt

Left to right: the 3D printed enclosure, demo day at the expo, and the in-house PCB attempt (we ended up on a breadboard, see Known Issue).

Known Issue / WIP

  • WAV export to SD play back faster than real time and have some noise. The inline capture pipeline work but the playback rate is off, this was not fix before the deadline
  • LED pin assignment in control.c control_set_led is placeholder (commented out) to avoid pin conflict with current setup
  • Power: when many peripheral run at full draw (LCD + touch + SSD + SD), USB 5V can sag and cause SD export to drop chunk. Use external 5V supply for stable export

License

MIT License. Educational use, EECS 373 final project.

Acknowledgments

  • KORG KAOSSILATOR PRO for the original design idea
  • Hal Chamberlin, Musical Applications of Microprocessors, for the SVF topology
  • Karplus & Strong (1983), Digital synthesis of plucked-string and drum timbres
  • music-dsp.org and EarLevel Engineering for SVF reference
  • Julius O. Smith III, Physical Audio Signal Processing (CCRMA Stanford)
  • Udo Zölzer, DAFX: Digital Audio Effects, for FX algorithm
  • ChaN's FatFs SD card file system
  • ST Microelectronics for STM32 HAL / CMSIS
  • EECS 373 course staff at University of Michigan

About

Polyphonic touch-screen synthesizer on STM32, with a 4-track looper and WAV export. EECS 373 final project.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages