Skip to content

Latest commit

 

History

42 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Pirate Audio Go Module

Go module to control Pimoroni's Pirate Audio sound output, LCD, and buttons.

⚠️ Highly Experimental, API subject to change, may set your device on fire ⚠️

gadget.jpg

Raspberry Pi OS setup

The display uses SPI0 with chip select 1 (/dev/spidev0.1). Enable SPI and reboot before running the examples:

sudo raspi-config nonint do_spi 0
sudo reboot

After reboot, verify that the required device exists:

ls -l /dev/spidev0.1

If configuring SPI manually, add dtparam=spi=on to /boot/firmware/config.txt on current Raspberry Pi OS releases, or /boot/config.txt on older releases.

The DAC is exposed as an ALSA device by the hifiberry-dac overlay. Add the following lines to the same configuration file and reboot:

dtoverlay=hifiberry-dac
gpio=25=op,dh
dtparam=audio=off

dtparam=audio=off is optional, but prevents the built-in Raspberry Pi audio device from being selected as the default output. See the official Pirate Audio repository for more hardware configuration details.

The audio package accesses ALSA directly without cgo or development headers. It finds the sndrpihifiberry card automatically and uses device 0. To select a card number explicitly, clear audio.Options.CardName and set Card.

The textview package loads Roboto Medium or falls back to DejaVu Sans Mono and DejaVu Sans. Install the packages that provide those fonts on Raspberry Pi OS:

sudo apt update
sudo apt install fonts-roboto-unhinted fonts-dejavu-core

Alternatively, set textview.Options.FontPath to another TrueType font file.

Raspberry Pi Zero builds

The original Raspberry Pi Zero and Zero W use an ARMv6 CPU with hardware floating point. Build specifically for it so waveform and graphics math use the VFP unit instead of software floating point:

GOOS=linux GOARCH=arm GOARM=6 CGO_ENABLED=0 go build -trimpath -o pirate-synth ./examples/synth

Use GOARM=5 only for ARM targets without VFP support.

Audio

The audio package plays beep.Streamer values and provides oscillators, white noise, and ADSR envelopes. Use Beep for gain, panning, mixing, sequencing, duration limits, and decoded audio. The engine opens the selected ALSA hardware device at 48 kHz, S32_LE stereo. Only one engine may be created at a time because direct hardware access is exclusive.

package main

import (
	"log"
	"time"

	"github.com/gopxl/beep/v2/effects"
	"github.com/rubiojr/go-pirateaudio/audio"
)

func main() {
	engine, err := audio.New()
	if err != nil {
		log.Fatal(err)
	}
	defer engine.Close()

	tone, err := audio.Saw(engine.SampleRate(), 220)
	if err != nil {
		log.Fatal(err)
	}
	sound, err := audio.Envelope(
		&effects.Gain{Streamer: tone, Gain: -0.5},
		engine.SampleRate(),
		300*time.Millisecond,
		audio.ADSR{
			Attack:  10 * time.Millisecond,
			Decay:   80 * time.Millisecond,
			Sustain: 0.5,
			Release: 200 * time.Millisecond,
		},
	)
	if err != nil {
		log.Fatal(err)
	}
	voice, err := engine.Play(sound)
	if err != nil {
		log.Fatal(err)
	}
	if err := voice.Wait(); err != nil {
		log.Fatal(err)
	}
}

Beep streamers are stateful. Construct a fresh oscillator, envelope, decoder, or other stream graph for every call to Engine.Play; do not replay the same Streamer value.

Streaming runs under Beep's mixer lock. A Streamer or beep.Callback must not call Engine or Voice methods synchronously; hand that work to another goroutine instead. Custom Streamer.Stream methods must also return promptly so they cannot stall the real-time mixer or shutdown.

The default master volume is deliberately limited to 20 percent. Copy and modify audio.DefaultOptions, then call audio.Open(options) to change it. The same options expose CardName, Card, Device, and BufferSize for ALSA configuration. Mixing many simultaneous voices can exceed the normalized range; the final PCM output is hard-clipped to protect the DAC from numeric overflow.

Run the four-button synthesizer with:

go run ./examples/synth

The display shows a live triangle-wave scope and four color-coded note pads. Button callbacks enqueue notes, audio starts independently of display updates, and the display redraws only while a note animation is active.

ST7789

The driver package for the 240x240px Pirate Audio display.

The controller and drawing logic is adapted from the current TinyGo ST7789 driver for mainline Go and periph.io. Initialization and wiring use Pimoroni's Pirate Audio defaults.

Also used the Python driver by Philip Howard as a reference.

Drawing an image on the display

// Display a rotated image the display
package main

import (
	"fmt"
	"image/color"
	"log"
	"os"

	"github.com/rubiojr/go-pirateaudio/display"
)

func main() {
	if len(os.Args) < 2 {
		fmt.Fprintf(os.Stderr, "Usage: %s <img-path>\n", os.Args[0])
		os.Exit(1)
	}

	dsp, err := display.Init()
	if err != nil {
		log.Fatal(err)
	}
	defer dsp.Close()

	// Set the screen color to white
	if err := dsp.FillScreen(color.RGBA{R: 255, G: 255, B: 255, A: 255}); err != nil {
		log.Fatal(err)
	}

	img, err := os.Open(os.Args[1])
	if err != nil {
		log.Fatal(err)
	}
	defer img.Close()

	// Rotate before pushing pixels, so the image appears rotated
	if err := dsp.Rotate(display.ROTATION_270); err != nil {
		log.Fatal(err)
	}
	if err := dsp.DrawImage(img); err != nil {
		log.Fatal(err)
	}
}

Interactive graphics demo

The interactive example draws a geometric dashboard and uses all four buttons:

  • A changes the color palette.
  • B changes the number of shapes.
  • X rotates the display.
  • Y toggles display inversion.
go run ./examples/interactive

Button callbacks only enqueue input; drawing remains serialized in the main event loop.

Controlling the hardware buttons (A,B,X,Y)

Button registrations locate their GPIO<n> line across the available GPIO chips, which supports both pre-Pi 5 and Pi 5 layouts. On systems without named GPIO lines, pass buttons.WithChip("gpiochip0") (or the appropriate chip). Hardware debounce defaults to 100 ms; use buttons.WithDebounce(0) to disable it. Kernels using GPIO uAPI v1 automatically fall back to no debounce.

package main

import (
	"fmt"
	"log"

	"github.com/rubiojr/go-pirateaudio/buttons"
)

func main() {
	buttonA, err := buttons.OnButtonAPressed(func() {
		fmt.Println("Yo Dawg, A pressed")
	})
	if err != nil {
		log.Fatal(err)
	}
	defer buttonA.Close()

	buttonX, err := buttons.OnButtonXPressed(func() {
		fmt.Println("Yo Dawg, X pressed")
	})
	if err != nil {
		log.Fatal(err)
	}
	defer buttonX.Close()

	buttonY, err := buttons.OnButtonYPressed(func() {
		fmt.Println("Yo Dawg, Y pressed")
	})
	if err != nil {
		log.Fatal(err)
	}
	defer buttonY.Close()

	buttonB, err := buttons.OnButtonBPressed(func() {
		fmt.Println("Yo Dawg, B pressed")
	})
	if err != nil {
		log.Fatal(err)
	}
	defer buttonB.Close()

	select {}
}

Combining HW buttons and image display

The rotate example redraws an image whenever A is pressed:

go run ./examples/rotate path/to/240x240.png

Drawing text

package main

import (
	"log"
	"time"

	"github.com/rubiojr/go-pirateaudio/textview"
)

func main() {
	opts := textview.DefaultOpts
	opts.FontSize = 20
	opts.FGColor = textview.GREEN
	tv, err := textview.OpenWithOptions(opts)
	if err != nil {
		log.Fatal(err)
	}
	defer tv.Close()
	if err := tv.Draw(""); err != nil {
		log.Fatal(err)
	}
	time.Sleep(3 * time.Second)
	if err := tv.DrawChars("Wake up, Neo..."); err != nil {
		log.Fatal(err)
	}
	time.Sleep(3 * time.Second)
	if err := tv.DrawChars("The Matrix has you..."); err != nil {
		log.Fatal(err)
	}
	time.Sleep(3 * time.Second)
	if err := tv.DrawChars("Follow the white rabbit."); err != nil {
		log.Fatal(err)
	}
}

About

Go module to control Pimoroni's Pirate Audio LCD and buttons.

Topics

Resources

Stars

7 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages