Skip to main content

Potatoslicer — chiptune audio engine with tracker-style sequencer

Project description

Potatoslicer Sequencer — Audio Client

Chiptune-style audio engine and player. No audio files — all sound is generated procedurally from JSON song data.

Architecture

SoundSystem (facade)
  → PotatoslicerEngine
    → Sequencer (tracker-style pattern playback)
    → Synth (voice pool + instruments)
      → Voice (oscillator + envelope per note)
    → Mixer (sum + soft clip)
  → AudioDevice (sounddevice callback → speakers)

Voice Model

  • Fixed pool of 20 voices
  • Each note_on allocates a voice from the pool
  • Chords allocate one voice per note in the chord
  • Detune allocates an additional voice per note (±pitch offset)
  • Voice stealing: inactive → released → oldest active
  • A channel can own multiple voices (for chords)
  • note_off / --- releases all voices on that channel

Signal Path

Oscillator (pulse/triangle/saw/noise)
  → ADSR Envelope
  → Voice output
    → Mixer (sum all active voices → tanh soft clip → master volume)
      → sounddevice callback → speakers

Quick Start

from potatoslicer.sound_system import SoundSystem

sound = SoundSystem()
sound.start()
sound.play_song('groove')  # loads from songs/groove.json
sound.play_sfx('confirm')           # trigger a preset sound effect
sound.stop()

Keyboard Controls (in installer)

  • M — toggle audio on/off
  • Audio state shown in top-right status bar

Song Format

Songs are JSON files in potatoslicer/songs/.

Example

{
  "title": "My Song",
  "tempo": 122,
  "rows_per_beat": 4,
  "order": [0, 0, 1, 1, 2, 3],
  "instruments": {
    "stab": {
      "waveform": "pulse",
      "duty_cycle": 0.25,
      "attack": 0.001,
      "decay": 0.04,
      "sustain": 0.0,
      "release": 0.03,
      "volume": 0.13,
      "detune": 0.003
    },
    "bass": {
      "waveform": "triangle",
      "attack": 0.001,
      "decay": 0.05,
      "sustain": 0.5,
      "release": 0.04,
      "volume": 0.52
    }
  },
  "patterns": {
    "0": [
      [
        {"channel": 0, "note": "C-1", "instrument": "kick"},
        {"channel": 4, "note": ["D-4", "F-4", "A-4"], "instrument": "stab"}
      ],
      [],
      [{"channel": 2, "note": "C-5", "instrument": "hat"}],
      []
    ]
  }
}

Top-Level Fields

Field Description
title Song name (display only)
tempo BPM (beats per minute)
rows_per_beat Rows per beat. 4 = 16th note resolution
order Pattern play sequence, loops when finished
instruments Named instrument definitions
patterns Numbered patterns, each a list of rows

Instrument Fields

Field Default Description
waveform pulse pulse, square, triangle, saw, noise
duty_cycle 0.5 Pulse width (0.0–1.0). Only affects pulse waveform
attack 0.01 Seconds to reach full volume
decay 0.05 Seconds to drop from full to sustain
sustain 0.6 Hold level (0.0–1.0)
release 0.1 Seconds to fade after note off
volume 0.7 Output volume (0.0–1.0)
vibrato_depth 0.0 Pitch modulation depth (0.002 = subtle)
vibrato_rate 5.0 Vibrato speed in Hz
detune 0.0 Spawns detuned voice pair for thickness. 0.003–0.005 = subtle widening

Pattern Events

Each row is a list of events (or empty [] for silence):

{"channel": 0, "note": "C-5", "instrument": "lead", "volume": 0.8}
Field Description
channel Voice channel (0–6 music, 7 reserved for SFX)
note Single note "C-5", chord ["C-4","E-4","G-4"], or "---" for note off
instrument Instrument name
volume Optional, 0.0–1.0

Chords

note can be a single string or an array of note strings:

{"channel": 4, "note": ["D-4", "F-4", "A-4"], "instrument": "stab"}

This triggers all notes simultaneously on the same channel. "---" releases all voices on the channel.

Voice cost: Each note in a chord uses one voice. With detune > 0, each note uses two voices. A 3-note chord with detune = 6 voices.

Note Format

  • C-5 — C in octave 5
  • C#4 — C sharp in octave 4
  • D#3 — D sharp in octave 3
  • --- — note off (releases all voices on channel)

Timing

seconds_per_row = 60 / tempo / rows_per_beat
Tempo Rows/beat Row duration
120 4 125ms
122 4 123ms
140 4 107ms

Current Song: groove.json

"Chorus Lift" at 122 BPM.

Structure: [0, 0, 1, 1, 2, 3, 1, 0] — 8 patterns, ~32 bars

Pattern Character Description
0 Intro/groove Kick + bass + hats, stab chord enters halfway
1 Main Full groove with clap, stab chords (Dm, Bb)
2 Breakdown Hats only + single stab hit
3 Build Accelerating kicks, claps, stab chords

Instruments: kick (triangle), hat (noise), clap (noise), bass (triangle), stab (detuned pulse chords)

SFX Presets

Defined in presets.py:

Name Sound Use
confirm High bleep Button press, selection
cancel Low tone Back, cancel
error Noise burst Error state
click Short tick UI interaction
deploy_start Ascending tone Deploy begins
deploy_done Sustained tone Deploy success
deploy_fail Descending buzz Deploy failure

Song Tool

CLI for editing songs.

cd potatoslicer-client-python

# Show song metadata
python -m potatoslicer.song_tool info groove

# Export to editable tracker text format
python -m potatoslicer.song_tool export groove
# Creates: groove_edit.txt

# Edit the file, then reimport
python -m potatoslicer.song_tool import groove_edit.txt

# Preview (plays for 10s, Ctrl-C to stop)
python -m potatoslicer.song_tool play groove

# Change tempo
python -m potatoslicer.song_tool tempo groove 130

Tracker Text Format

The export format is human-readable and editable:

TITLE: Chorus Lift
TEMPO: 122
ROWS_PER_BEAT: 4
ORDER: 0 0 1 1 2 3 1 0

INST kick: triangle 0.5 0.001 0.09 0.0 0.02 0.75
INST stab: pulse 0.25 0.001 0.04 0.0 0.03 0.13

PATTERN 0
  000 | 0:C-1:kick 1:D-2:bass
  001 | ---
  002 | 2:C-5:hat
  003 | ---

Event format: channel:note:instrument[:volume]

Dependencies

Package Required Purpose
numpy Yes Sample generation
sounddevice Optional Audio output
libportaudio2 Optional System lib for sounddevice

Audio is gracefully disabled if sounddevice/portaudio are missing.

Install all: make installer-deps

Waveform Reference

Waveform Character Best for
pulse Bright, buzzy Leads, stabs, arps
square Hollow Leads (pulse duty=0.5)
triangle Soft, warm Bass, kicks
saw Harsh, edgy Bass, aggressive leads
noise White noise Hi-hats, snares, claps

Pulse Width

duty_cycle Sound
0.5 Square wave (most hollow)
0.25 Classic lead
0.125 Thin, nasal
0.0625 Very thin, almost click

Detune

Adding "detune": 0.003 to an instrument:

  • Spawns a second voice per note, slightly pitch-shifted
  • Creates stereo-like width and harmonic thickness
  • Essential for chord stabs to sound full rather than thin
  • Costs 1 extra voice per note (budget accordingly with 20-voice pool)

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

potatoslicer_client-0.1.0.tar.gz (71.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

potatoslicer_client-0.1.0-py3-none-any.whl (82.9 kB view details)

Uploaded Python 3

File details

Details for the file potatoslicer_client-0.1.0.tar.gz.

File metadata

  • Download URL: potatoslicer_client-0.1.0.tar.gz
  • Upload date:
  • Size: 71.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.13.5

File hashes

Hashes for potatoslicer_client-0.1.0.tar.gz
Algorithm Hash digest
SHA256 ae0c0e4b94759e2d003dd56117d9d09cb550938c2b40bc81dbb9c0b05c1f84a6
MD5 1854388f81dcf43b31f457294a7e20ee
BLAKE2b-256 66a3a261b0a584cea924d5e6053864273254be71378a5862bc44cdbffa118bea

See more details on using hashes here.

File details

Details for the file potatoslicer_client-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for potatoslicer_client-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 6576057dc8622d44414256c904144eb682207b1742ca0084fe5ceec5cf8d788a
MD5 25fd977dfd2e1c21b9d70637943a0505
BLAKE2b-256 f593e3238b4c076c8aa9776dc5e7fe2abcca5a83c90b95fca64ec6da26dadce2

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page