gion

package module
v0.0.5 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 22, 2026 License: MIT Imports: 5 Imported by: 0

README

gion

gion

8-bit sounds and music for games.

gion (擬音) makes retro game audio in two forms. It is a workbench where you sculpt sound effects and chiptune loops by ear, and it is a Go library your game imports to regenerate that exact audio at runtime. Inspired by sfxr and bfxr, written from scratch in Go, not a port.

The premise: a sound is not a file; it is a small deterministic recipe. The same seed produces the same samples on every platform. Forever. Ship the recipe and render on the fly, or bake WAVs at build time. Both work; pick per game.

Written with Ebitengine. One executable, nothing to install alongside it.

Try it in the browser: the whole workbench runs there, and what you make comes home as a file.

The workbench

Effects start from seven families: pickup, laser, explosion, powerup, hit, jump, blip. Roll re-derives a fresh variation, Mutate drifts the current sound without losing its character, and fifteen sliders expose the whole synth: waveform, envelope, frequency slide, vibrato, arpeggio, low-pass, bit crush.

Music comes as perfectly-looping chiptune tracks in six moods: upbeat, heroic, dark, chill, battle, boss. Each roll picks its own tempo, key, timbre, chord progression, song form and grooves. All of it derives from the seed. A per-instrument mixer and mute switches set the arrangement. A track often sounds better with a voice removed; gion treats that as an arrangement decision, saves it in the document, and honors it everywhere the track is regenerated.

The view is a 3D spectral waterfall (time, frequency, amplitude) that follows the sliders live while you drag. Spin it with the mouse, or switch to the plain 2D waveform.

Everything lands in a .gion document: a small Filo s-expression file with one named effect or track per line. It diffs cleanly in git and survives hand edits.

Download (no Go required)

Grab a prebuilt binary from the latest release. You do not need Go or any developer tools.

System File to download
macOS (Intel or Apple Silicon) gion-darwin-universal.zip
Windows (64-bit, most common) gion-windows-amd64.exe
Windows (ARM) gion-windows-arm64.exe
Linux (Intel/AMD 64-bit) gion-linux-amd64.gz
Linux (ARM 64-bit) gion-linux-arm64.gz
macOS

With Homebrew, one command:

brew install --cask crgimenes/tap/gion

Or by hand: download gion-darwin-universal.zip, unzip, move gion.app to Applications, double-click. The app is signed and notarized by Apple, so it opens normally.

If macOS says the app "is damaged and can't be opened" or complains about an unidentified developer:

  • Make sure the download finished, and unzip before opening. Re-download if in doubt.

  • Right-click gion.app, choose Open, then confirm with Open.

  • If it still refuses, open Terminal and run:

    xattr -dr com.apple.quarantine /Applications/gion.app
    
Windows

Download the .exe for your machine and double-click it. Windows SmartScreen may warn you because the app is not from the Microsoft Store: click More info → Run anyway.

Linux

Download the matching .gz, then decompress and run:

gunzip gion-linux-amd64.gz
chmod +x gion-linux-amd64
./gion-linux-amd64

Run from source

With Go installed:

go run github.com/crgimenes/gion/cmd/gion@latest

On macOS and Windows there is nothing else to install. On Linux, Ebitengine needs Cgo and the system development libraries, so build with CGO_ENABLED=1 after installing the packages from the Ebitengine install guide (on Debian/Ubuntu: libgl1-mesa-dev, libasound2-dev, libxcursor-dev, libxi-dev, libxinerama-dev, libxrandr-dev, libxxf86vm-dev, pkg-config).

Using the sounds in your game

Regenerate at runtime: load the document your team saved in the workbench and render when you need the samples. An effect renders in under a millisecond; a full music loop takes a few dozen milliseconds. Same bytes on every platform.

import (
	"github.com/crgimenes/gion"
	"github.com/crgimenes/gion/effects"
)

doc, err := effects.Load("sounds.gion")
// doc.Effects[i].Params and doc.Music[i].Params render on demand:
samples := doc.Effects[0].Params.Render(gion.DefaultRate) // []int16, mono

effects.Parse does the same over a //go:embed-ed document. The single-executable property survives.

gion.Mutate derives a sibling of an effect: same character, slightly different body. Mutate a base explosion with a varying seed and repeated explosions stop sounding identical. The asset cost is zero:

boom := doc.Effects[0].Params
samples := gion.Mutate(boom, gameTick).Render(gion.DefaultRate)

For engines that just want files, the gion-render CLI bakes a document into one WAV per entry:

go run github.com/crgimenes/gion/cmd/gion-render@latest -o assets/ sounds.gion

It also renders single presets (gion-render laser) and standalone music (gion-render music battle -seed 7).

The .gion format

A document is a Filo script. Each line declares one named entry, and only non-default fields are written:

(effect "coin"
  (tuple "Freq" 1100)
  (tuple "ArpMult" 1.5)
  (tuple "Decay" 0.25)
  (tuple "Gain" 0.5))
(music "stage 1"
  (tuple "Mood" 4)
  (tuple "Seed" 77)
  (tuple "Mute" 3)
  (tuple "Gain" 0.6))

Unknown fields are ignored on load, so an older binary reads documents written by a newer one. That Mute 3 is an arrangement decision: this track plays without its lead anywhere it is rendered.

License

MIT

Documentation

Overview

Package gion synthesizes 8-bit style game sound effects from a small, serializable set of parameters. Rendering is deterministic: the same Params always produce the same samples, so a game can ship tiny presets and generate the audio at runtime instead of shipping files. Inspired by sfxr/bfxr, not a port.

Index

Constants

View Source
const DefaultRate = 44100

DefaultRate is the sample rate used when a caller passes a rate <= 0.

Variables

View Source
var ErrTooLong = errors.New("gion: too many samples for a WAV file")

ErrTooLong reports a sample slice whose WAV encoding would overflow the format's 32-bit sizes.

View Source
var Presets = map[string]func(seed int64) Params{
	"blip":      Blip,
	"explosion": Explosion,
	"hit":       Hit,
	"jump":      Jump,
	"laser":     Laser,
	"pickup":    Pickup,
	"powerup":   Powerup,
}

Presets maps each preset name to its generator, so CLIs and UIs can offer the catalog without hardcoding it.

Functions

func Spectrogram added in v0.0.2

func Spectrogram(samples []int16, frames, bins int) [][]float64

Spectrogram slices the samples into frames spread evenly across the sound and returns a frames x bins grid of spectral magnitudes, normalized to 0..1 and log-compressed so quiet detail stays visible next to the peaks. Each bin keeps the peak of its frequency range, which preserves narrow tones. It is the data behind the app's waterfall view, exported so a game can drive its own visualizer from the same sounds.

func WriteWAV

func WriteWAV(w io.Writer, rate int, samples []int16) error

WriteWAV writes the samples as a mono 16-bit PCM WAV stream at the given rate (DefaultRate when rate <= 0).

Types

type Params

type Params struct {
	Wave Wave

	Freq      float64 // starting frequency in Hz
	FreqSlide float64 // frequency change in Hz per second
	FreqLimit float64 // stop the sound when a downward slide crosses this; 0 = never

	Attack  float64 // seconds fading in
	Sustain float64 // seconds at full level
	Punch   float64 // extra level at the start of the sustain, fading across it (0..1)
	Decay   float64 // seconds fading out

	Duty      float64 // square duty cycle (0..1]; 0 means 0.5
	Vibrato   float64 // vibrato depth in Hz
	VibratoHz float64 // vibrato speed in cycles per second
	ArpMult   float64 // multiply the frequency by this once, after ArpDelay; 0 = off
	ArpDelay  float64 // seconds before the arpeggio jump
	LowPass   float64 // one-pole low-pass cutoff in Hz; 0 = off
	Bits      int     // quantize the output to this many bits (1..15); 0 = off

	Gain float64 // output level (0..1)
	Seed int64   // noise stream seed
}

Params describes one sound effect. The zero value renders silence; start from a preset and tweak. All fields are plain data, so a Params can be serialized (e.g. JSON) and shipped with a game.

func Blip

func Blip(seed int64) Params

Blip is the minimal UI tick: a very short steady tone.

func Explosion

func Explosion(seed int64) Params

Explosion is low-passed noise sliding down with a long tail.

func Hit

func Hit(seed int64) Params

Hit is a short burst of falling noise for impacts and damage.

func Jump

func Jump(seed int64) Params

Jump is a soft square sweeping up, longer than a blip and without punch.

func Laser

func Laser(seed int64) Params

Laser is a fast downward sweep on a thin pulse.

func Mutate added in v0.0.2

func Mutate(p Params, seed int64) Params

Mutate returns a deterministic sibling of p: each non-zero numeric field has a coin-flip chance of drifting up to ±10%, relative to its value, so the sound keeps its character — a mutated laser is still that laser. Fields that are off (zero) stay off, and the wave, bit depth, gain and noise seed are left alone. The same p and seed always produce the same sibling.

Besides sculpting sounds in the editor, this is useful at runtime: mutate a base effect with a varying seed and repeated explosions, hits or footsteps stop sounding identical, at zero asset cost.

func Pickup

func Pickup(seed int64) Params

Pickup is the classic coin: a short square blip that jumps up a musical interval after a moment.

func Powerup

func Powerup(seed int64) Params

Powerup rises: a square sweeping up with a light vibrato.

func (Params) Render

func (p Params) Render(rate int) []int16

Render synthesizes the sound as mono 16-bit samples at the given rate (DefaultRate when rate <= 0). The output depends only on Params, never on the clock or global randomness.

type Wave

type Wave int

Wave selects the oscillator shape.

const (
	Square Wave = iota
	Saw
	Triangle
	Sine
	Noise
)

Directories

Path Synopsis
cmd
gion command
Command gion is the sound-effect workbench over the gion library: preset buttons roll seeded variations, sliders shape the parameters, the result plays as soon as a drag is released, and the current sound can be saved as a WAV file.
Command gion is the sound-effect workbench over the gion library: preset buttons roll seeded variations, sliders shape the parameters, the result plays as soon as a drag is released, and the current sound can be saved as a WAV file.
gion-render command
Command gion-render writes sounds to WAV files: the ear-first check of the synth core, and the baked-assets path for games that skip runtime synthesis.
Command gion-render writes sounds to WAV files: the ear-first check of the synth core, and the baked-assets path for games that skip runtime synthesis.
Package effects reads and writes gion documents.
Package effects reads and writes gion documents.
Package music generates short, perfectly-looping chiptune tracks from a small deterministic parameter set, rendered entirely through the gion synthesis core: every note is a gion.Params scheduled on a step grid and mixed into one buffer.
Package music generates short, perfectly-looping chiptune tracks from a small deterministic parameter set, rendered entirely through the gion synthesis core: every note is a gion.Params scheduled on a step grid and mixed into one buffer.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL