Winslow Synth - Make funny sounds with Go

Winslow Synth

Make funny sounds with Go

GIF of Michael Winslow in Spaceballs

Installation

Getting audio drivers to work properly with Golang has proven tricky.

This setup is working for me on an M1 Mac:

export LIBRARY_PATH_TO_USE="/usr/local/lib/:`pwd`/portaudio/lib/.libs"
export LIBRARY_PATH="$LIBRARY_PATH_TO_USE"
export CGO_CFLAGS="-I`pwd`/portaudio/include"
export PKG_CONFIG_PATH=`pwd`/portaudio
export CGO_ENABLED=1
export CC=gcc

go run -exec "env DYLD_LIBRARY_PATH=$LIBRARY_PATH_TO_USE" main.go

Input Devices

If you have a MIDI device connected, that will automatically be picked up. Otherwise, you can use the top two rows of keys on your QWERTY keyboard when focused on the browser.

User Interface

Winslow's User Interface

The UI is fairly simple for now. It contains:

  • A live visualization of the audio buffer
  • A visualization of the waveform (zooming in on a stable part of the audio buffer)
  • An instrument selector
  • Controls for every part of the current instrument
  • A button to randomize the values for each control

Building Synths

Generators

Everything is a Generator

A Generator is a Go interface for generating signals. The most important function a Generator must provide is:

GetValue(timeSinceAttack, releaseTime uint64) float32

which tells the caller exactly what value it takes at a given time.

Built-in Generators include:

  • Constant
  • Oscillator
  • ADSR
  • Noise
  • Harmonic
  • Delay

Often the inputs to a Generator are other Generators. For instance, the Oscillator looks like this:

type Oscillator struct {
    Info          Info
    Amplitude     Generator
    Frequency     Generator
    Phase         Generator
    Bias          Generator
    Shape         OscillatorShape
    DropOnRelease bool
}

If you're constructing a Generator with numerical inputs, it's recommended you make them Generators instead of, say, ints or float32s.

Constants

The most basic kind of Generator is a Constant. A Constant gives off a single value. But importantly, they're also used to drive user inputs.

For example, we might define a constant like this:

sineAmplitude := Constant{
  Info: {
    Name: "Amplitude",
    Group: "Sine",
  },
  Value: 1.0,
  Min: 0.0,
  Max: 1.0,
  Step: 0.01,
}

We can then use this Constant as an input to another Generator:

osc := Oscillator{
  Amplitude: sineAmplitude,
}

This will automatically create a slider in the UI which allows the user to alter the constant on-the-fly. The Name field controls the label on the slider, while the Group field controls which other Constants it's grouped with.

Example of UI controls

Frequency

Any Constant named Frequency has a special place in Winslow - it is set not via a slider in the UI, but by keys pressed on a MIDI or QWERTY keyboard.

You can place a Frequency constant anywhere in your instrument to respond to keys being hit.

TODO

Must-Have

  • Implement filters
  • FFT visualization
  • Input from MIDI files
  • Support pitch-bend MIDI input
  • Moar instruments
  • Make this consumable as an external package
  • Asynchronous .wav generation for expensive sounds

Long Term

  • YAML configuration for building instruments
  • In-browser instrument builder
  • In-browser live keyboard
  • In-browser sequencer

Implementation Notes

Winslow makes a lot of use of tricky Golang concepts, like channels, goroutines, pointers, and (gasp) reflection. Because of this, it can be a little finicky.

Architecture

The core component is the MusicPlayer, which is where input meets output.

MusicPlayer.Output contains a circular buffer where audio data is stored.

MusicPlayer.Start() takes in a chan input.InputKey, which represents a series of raw input events - for now, just keys getting attacked and released. It tracks these in a Sequence, which pairs attacks/releases together into a single Event.

MusicPlayer.Sequence.Instrument is the Generator currently being used to play music.

Each Event comes with its own copy of the Generator being used as your current instrument, with its Frequency set accordingly. You can think of each Event as a distinct physical process (e.g. a string getting plucked).

Every 10ms, the MusicPlayer asks its Sequence for the next chunk of audio data, and puts it into MusicPlayer.Output, where it is eventually read by PortAudio and sent to your speakers.

Mixing

At any point in time, Winslow has to mix data from a variable number of Events together into a single audio buffer. Each Event generates an audio buffer in [-1.0, 1.0], and these need to be combined in turn to create an audio buffer also in [-1.0, 1.0].

Currently, Winslow simply adds all the Event buffers together. In practice, this leads to some clipping (values that are greater than 1.0 or less than -1.0), but not much.

There's an experimental mixer MixBuffersNoOverflow which ensures there's no clipping, but changes the signal a bit.

There's probably something smarter we could be doing here...

Performance

On my machine, things start to get hairy at around 200 Generators. That could be one note on a big complicated instrument, or 200 rapid keypresses on a basic Sine oscillator.

You can trigger this by opening up a large instrument and playing multiple chords in quick succession - eventually you will hear the audio degrade and start to crackle (and you'll see some warning logs).

I believe this is a pretty hard limitation of the current system. I'm not sure how well it performs relative to other digital synths, but my guess is not well.

Mitigation Strategies

  • Winslow tries to degrade gracefully when its computational speed slows below the sample rate. It will begin to downsample the instrument, interpolating intermediate samples.
    • Currently the interpolation is just an average - there's probably a more sophisticated strategy out there.
  • Every Event is sampled in its own goroutine, which speeds things up considerably in multi-core environments
  • Winslow tries to be intelligent about when an Event is no longer producing sound and can be discarded. All Events are discarded after 1 second of zero values, with a 10 second maximum
    • We could be stricter about what is considered "zero" - currently it's strict equality
    • We could probably tune the time thresholds better

TODO

  • interpolation:
    • if we skip calculating sample X, intermediate values for subgenerators are not calculated
    • this means that for X+1, if a subgenerator relies on History, it's screwed :(
Similar Resources

golang feature toggle library - a library to help make golang feature toggling clean and easy

toggle supports env_variable backed toggling. It can also be updated via a pubsub interface (tested w/ redis) 2 engines for toggle backing are include

Mar 29, 2022

Conception was an experimental project, looking for ways to make software development more efficient.

Conception was an experimental project, looking for ways to make software development more efficient.

Conception Note: All future development is done in the Go version. Conception is an experimental research project, meant to become a modern IDE/Langua

Jul 21, 2022

A utility library to make use of the X Go Binding easier. (Implements EWMH and ICCCM specs, key binding support, etc.)

xgbutil is a utility library designed to work with the X Go Binding. This project's main goal is to make various X related tasks easier. For example,

Dec 10, 2022

🚧 Flexible mechanism to make execution flow interruptible.

🚧 breaker Flexible mechanism to make execution flow interruptible. 💡 Idea The breaker carries a cancellation signal to interrupt an action execution

Dec 13, 2022

Make IoT a lot more fun with data.

Eywa What is Eywa? "Eywa is the guiding force and deity of Pandora and the Na'vi. All living things on Pandora connect to Eywa." -- Avatar Wiki Projec

Nov 28, 2022

Fluent API to make it easier to create Json objects.

Jsongo Fluent API to make it easier to create Json objects. Install go get github.com/ricardolonga/jsongo Usage To create this: { "name":"Ricar

Nov 7, 2022

GoHooks make it easy to send and consume secured web-hooks from a Go application

GoHooks GoHooks make it easy to send and consume secured web-hooks from a Go application. A SHA-256 signature is created with the sent data plus an en

Nov 16, 2022

Package httpretty prints the HTTP requests you make with Go pretty on your terminal.

httpretty Package httpretty prints the HTTP requests of your Go programs pretty on your terminal screen. It is mostly inspired in curl's --verbose mod

Jan 8, 2023

libraries for various programming languages that make it easy to generate per-process trace files that can be loaded into chrome://tracing

libraries for various programming languages that make it easy to generate per-process trace files that can be loaded into chrome://tracing

chrometracing: chrome://tracing trace_event files The chrometracing directory contains libraries for various programming languages that make it easy t

Oct 6, 2022

Go package to make lightweight ASCII line graph ╭┈╯ in command line apps with no other dependencies.

Go package to make lightweight ASCII line graph ╭┈╯ in command line apps with no other dependencies.

asciigraph Go package to make lightweight ASCII line graphs ╭┈╯. Installation go get github.com/guptarohit/asciigraph Usage Basic graph package main

Jan 8, 2023

Package httpretty prints the HTTP requests you make with Go pretty on your terminal.

httpretty Package httpretty prints the HTTP requests of your Go programs pretty on your terminal screen. It is mostly inspired in curl's --verbose mod

Jan 8, 2023

a Make/rake-like dev tool using Go

a Make/rake-like dev tool using Go

About Mage is a make-like build tool using Go. You write plain-old go functions, and Mage automatically uses them as Makefile-like runnable targets. I

Jan 7, 2023

gostub is a library to make stubbing in unit tests easy

gostub gostub is a library to make stubbing in unit tests easy. Getting started Import the following package: github.com/prashantv/gostub Click here t

Dec 28, 2022

Are you forwarding DNS traffic to another server for some reason, but want to make sure only queries for certain names are passed? Say no more.

DNSFWD Redirect DNS traffic to an upstream. Get Latest: wget https://github.com/C-Sto/dnsfwd/releases/latest/download/dnsfwd_linux (replace linux with

Dec 16, 2022

Make JSON greppable!

gron transforms JSON into discrete assignments to make it easier to grep for what you want and see the absolute 'path' to it. It eases the exploration of APIs that return large blobs of JSON but have terrible documentation.

Dec 31, 2022

A collection of small Go utilities to make life easier.

The simplego package provides a collection of Go utilities for common tasks.

Jan 4, 2023

A utility library to make use of the X Go Binding easier. (Implements EWMH and ICCCM specs, key binding support, etc.)

xgbutil is a utility library designed to work with the X Go Binding. This project's main goal is to make various X related tasks easier. For example,

Dec 10, 2022

A task runner / simpler Make alternative written in Go

A task runner / simpler Make alternative written in Go

Task Task is a task runner / build tool that aims to be simpler and easier to use than, for example, GNU Make. See taskfile.dev for the documentation.

Jan 8, 2023

The easiest way to make API documents for GraphQL

Document Generator for GraphQL gqldoc is now alpha gqldoc is command line tool to generate documents from GraphQL schema or your GraphQL endpoint. the

Dec 20, 2022
this is a funny client for jsonrpc-client. it can support timeout,breaker ...

this is a funny client for jsonrpc-client. it can support timeout,breaker ...

Sep 17, 2022
A lib of golang which contains many funny api;

A lib of golang which contains many funny api; I created it cause I could not find these more-effient apis from other repo.

Oct 27, 2021
A funny utility to manage your PS1 variable.
A funny utility to manage your PS1 variable.

PSOne Introduction Are you a Veteran Unix Admin? If so, you probably know the charm of the PS1 environment variable. For a deep focus I suggest you to

Oct 23, 2022
Modern Make

Modern Make About Mmake is a small program which wraps make to provide additional functionality, such as user-friendly help output, remote includes, a

Dec 27, 2022
Concurrent task runner, developer's routine tasks automation toolkit. Simple modern alternative to GNU Make 🧰
Concurrent task runner, developer's routine tasks automation toolkit. Simple modern alternative to GNU Make 🧰

taskctl - concurrent task runner, developer's routine tasks automation toolkit Simple modern alternative to GNU Make. taskctl is concurrent task runne

Dec 14, 2022
JOB, make your short-term command as a long-term job. 将命令行规划成任务的工具

job make your short-term command as a long-term job Install Shell Install (Linux & MacOS) # binary will be $(go env GOPATH)/bin/job $: curl -sfL https

Nov 12, 2022
An easy to use menu structure for cli applications that prompts users to make choices.
An easy to use menu structure for cli applications that prompts users to make choices.

WMenu Package wmenu creates menus for cli programs. It uses wlog for its interface with the command line. It uses os.Stdin, os.Stdout, and os.Stderr w

Dec 26, 2022
Go package to make lightweight ASCII line graph ╭┈╯ in command line apps with no other dependencies.
Go package to make lightweight ASCII line graph ╭┈╯ in command line apps with no other dependencies.

asciigraph Go package to make lightweight ASCII line graphs ╭┈╯. Installation go get github.com/guptarohit/asciigraph Usage Basic graph package main

Jan 1, 2023
Make Highly Customized Boxes for your CLI
Make Highly Customized Boxes for your CLI

Box CLI Maker ?? Box CLI Maker is a Highly Customized Terminal Box Creator. Features Make Terminal Box in 8️⃣ inbuilt different styles 16 Inbuilt Colo

Jan 1, 2023
A command-line arguments parser that will make you smile.

docopt-go An implementation of docopt in the Go programming language. docopt helps you create beautiful command-line interfaces easily: package main

Jan 7, 2023