dials
Dials, sliders and a piano keyboard, one line each

dials is designed to bring the joy back into programming, for any age and any level: you write one line and a dial appears, and you turn it and something changes. Dials, sliders, buttons, switches, knobs that click, a piano keyboard, a scope and a level meter go on a scene with nothing to position, nothing to draw and nothing to wire up, and they work with a finger, the mouse and the computer's keys. It is dials.js for pictures.js and pydials for Pyctures, and a control is tied to a setting of a synth in one line.

Free and open source, MIT licence. Made for projects in Circuitry Studio; runs anywhere a browser does.

A synth panel: a select, two dials and two sliders in a row, a scope showing the wave, and a piano keyboard with three keys held

Try dials in your browser

Six lines: a synth, two dials and a slider that are its settings, the wave it is making, and a piano keyboard. Play the keys with the mouse, with fingers, or with A S D F G H J, and turn a dial while a note sounds.

Open Circuitry

A browser keeps a page silent until you click or tap it, so the first touch on the scene turns the sound on. Change the code and press Run (or Cmd/Ctrl+Enter).

A control is a number

You say what you want and it appears. A dial or a slider is a number: speed.value is the number it is on now, and in a sum it stands for its number. A button is asked in update: fire.pressed is true once for each press. A toggle is a switch, and a select is one of a list. The controls line themselves up in a row in the order you make them.

JavaScript
const speed = dial("speed", 0, 10, 3)       // from 0 to 10, starting at 3
const size = slider("size", 10, 150, 60)
const fire = button("fire")
const trail = toggle("trail")
const level = select("level", ["easy", "hard"])
Python
speed = dial("speed", 0, 10, 3)       # from 0 to 10, starting at 3
size = slider("size", 10, 150, 60)
fire = button("fire")
trail = toggle("trail")
level = select("level", ["easy", "hard"])

Every control, with its options.

Or a setting of a synth

A control can be tied to a setting of a synth. The dial then is that setting: it takes its range, its unit and where it starts from the synth, and turning it sets it. A scope draws the wave the synth is making, a meter shows how loud it is, and a keyboard plays it: with fingers (several at once), with the mouse, and with the keys A S D F G H J.

JavaScript
const lead = makeSynth("lead")

dial("cutoff", lead, "cutoff")       // the dial takes its range from the synth, and turning it sets it
select("wave", lead, "wave")         // the choices come from the synth too
scope(lead)                          // the wave it is making
meter(lead)                          // how loud it is
keyboard(lead)                       // piano keys that play it, and light up as it plays
Python
lead = make_synth("lead")

dial("cutoff", lead, "cutoff")       # the dial takes its range from the synth, and turning it sets it
select("wave", lead, "wave")         # the choices come from the synth too
scope(lead)                          # the wave it is making
meter(lead)                          # how loud it is
keyboard(lead)                       # piano keys that play it, and light up as it plays

A key sounds the moment it is touched and a dial is heard as it turns. Tying a control to a synth. Every setting is tied by its name: Every setting lists them with their ranges and the control that suits each.

Make a control of your own

When none of the controls is the one you want, custom makes one from three parts: what it looks like, what a finger does to it, and what it listens to. It sits in the layout like any other, so a synth or an instrument of your own gets its own ribbon, pad, meter, scope or sequencer, and what it draws can follow any sound: give it a synth as its source and it is handed how loud the sound is and the wave it is making, every frame.

JavaScript
const lead = makeSynth("lead")

const ribbon = custom("ribbon", {
  width: 320, height: 100, value: -1,
  draw(c) {                                      // what it looks like, in its own points
    c.rect(0, 0, c.width, c.height, c.theme.well, 10)
    for (let i = 0; i < 8; i++) c.rect(i * c.width / 8 + 3, 4, c.width / 8 - 6, c.height - 8, i === c.value ? c.accent : c.theme.knob, 6)
  },
  onDown(p, c) {                                 // what a finger does to it
    ribbon.set(Math.floor(p.x / c.width * 8))
    lead.play(57 + ribbon.value, 0.4)
  },
  onUp() { ribbon.value = -1 },
})
Python
lead = make_synth("lead")

def paint(c):                                    # what it looks like, in its own points
    c.rect(0, 0, c.width, c.height, c.theme["well"], 10)
    for i in range(8):
        c.rect(i * c.width / 8 + 3, 4, c.width / 8 - 6, c.height - 8, c.accent if i == c.value else c.theme["knob"], 6)

def down(p, c):                                  # what a finger does to it
    ribbon.set(int(p.x / c.width * 8))
    lead.play(57 + ribbon.value, 0.4)

def up(p, c):
    ribbon.value = -1

ribbon = custom("ribbon", width=320, height=100, value=-1, draw=paint, on_down=down, on_up=up)

Your own controls has a ribbon, a ring that follows the sound and a row of steps that plays itself. A control of your own is drawn at a size you choose, scaled to fit its box and cut at its edge, so it never draws over its neighbours.

See the sound

spectrum shows the frequencies in a sound as bars, the low notes at the left, with ticks that hold the peaks, beside a scope for the wave and a meter for how loud it is. A control of your own is given the same bands as c.spectrum, to draw an analyser your own way.

JavaScript
const lead = makeSynth("lead")

spectrum(lead, { bands: 48 })       // the frequencies in the sound, as bars: low notes at the left
scope(lead)                         // its wave
meter(lead)                         // how loud it is
keyboard(lead, { octave: 3, octaves: 2 })
Python
lead = make_synth("lead")

spectrum(lead, bands=48)            # the frequencies in the sound, as bars: low notes at the left
scope(lead)                         # its wave
meter(lead)                         # how loud it is
keyboard(lead, octave=3, octaves=2)

Every control has its options, and the Synth Workshop demo builds an analyser of its own with a level in dB, a spectrum and the wave.

Your voice, and a real knob

The microphone is a synth to the controls too: its voice is a choice, its pitch is a dial, hearing yourself is a switch, and a scope draws your voice.

JavaScript
const mic = useMicrophone()

select("voice", mic, "voice")          // robot, chipmunk, monster and the rest
dial("pitch", mic, "pitch")
toggle("listen", mic, "listening")     // wear headphones first
scope(mic)                             // your voice, as a wave
Python
mic = use_microphone()

select("voice", mic, "voice")          # robot, chipmunk, monster and the rest
dial("pitch", mic, "pitch")
toggle("listen", mic, "listening")     # wear headphones first
scope(mic)                             # your voice, as a wave

A keyboard can have a MIDI button: tap it to connect, then choose which keyboard to listen to and which channel.

JavaScript
const lead = makeSynth("lead")

scope(lead)
keyboard(lead, { octave: 4, midi: true })      // a MIDI button on the keyboard: tap it to choose the device and channel
Python
lead = make_synth("lead")

scope(lead)
keyboard(lead, octave=4, midi=True)            # a MIDI button on the keyboard: tap it to choose the device and channel

With a MIDI controller plugged in, learn ties the next knob you turn to a setting, and the dial on the screen turns with it.

JavaScript
const lead = makeSynth("lead")
const cutoff = dial("cutoff", lead, "cutoff")

lead.learn("cutoff", (value) => { cutoff.value = value })    // the next knob you turn on your controller
Python
lead = make_synth("lead")
dial("cutoff", lead, "cutoff")

lead.learn("cutoff")                   # the next knob you turn on your controller

The microphone and MIDI work on a web page of your own and in a scene run with pictures run or pyctures run; in Circuitry's own preview they are not connected yet, and there the controls work by hand. Tying a control to a synth has both.

It lays itself out, or you do

There is nothing to do: controls line up in the order you make them, wrap evenly on a narrow screen, and are a little bigger under a finger. When you want more, a label before some controls is their heading, a group is a plate with a title, and row, column, grid and page arrange what is inside. One line of layout keeps your rows and fits them to the width, or lays the panel out at a set size and scales the whole of it. A rotary is a knob that clicks between a few choices.

A front panel in seven groups: oscillator, unison, filter, envelope, delay, reverb and output plates above a piano keyboard
JavaScript
label("oscillator")                 // a label before controls is their heading
rotary("wave", lead, "wave")        // a knob that clicks between choices
dial("width", lead, "width")

group("filter", () => {             // or say it out loud
  dial("cutoff", lead, "cutoff", { size: "large" })
  dial("resonance", lead, "resonance")
})

layout({ wrap: false })             // keep my rows, and fit them to the width
Python
label("oscillator")                 # a label before controls is their heading
rotary("wave", lead, "wave")        # a knob that clicks between choices
dial("width", lead, "width")

with group("filter"):               # or say it out loud
    dial("cutoff", lead, "cutoff", size="large")
    dial("resonance", lead, "resonance")

layout(wrap=False)                  # keep my rows, and fit them to the width

When there is more than fits, the plates move under a finger while the keyboard and the scope stay put. Laying out a panel has all of it, with pictures.

A control for anything

A control can be tied to any object, not only a synth. With nothing else to go on it reads and writes the property you name.

JavaScript
const ball = { radius: 40 }
dial("size", ball, "radius", { min: 5, max: 120 })

function draw() {
  background("midnightblue")
  circle(width / 2, height / 2, ball.radius, "gold")
}
Python
ball = {"radius": 40}
dial("size", ball, "radius", min=5, max=120)

def draw():
    background("midnightblue")
    circle(width / 2, height / 2, ball["radius"], "gold")

Made for hands

  • Drag up or to the right on a dial to turn it up. The drag is the dial's until you let go, wherever the pointer wanders.
  • Hold Shift to turn a tenth as far. On a touch screen, pull your finger well away from the dial for a quarter.
  • Tap twice to put a dial or slider back where it started.
  • Each finger on the keyboard holds its own key, and sliding along plays each key in turn.
  • A press on a control is the control's alone: a game that fires on a click does not also fire when a dial is turned.

The controls are drawn on a canvas of their own, laid exactly over the scene's, so they sit on whatever the scene draws, in 2D or 3D, and cost the scene nothing. Layout and touch.

How to get it

In Circuitry Studio there is nothing to install. A JavaScript scene has the names ready, with nothing to import. A Python scene starts with from pyctures import * and from pydials import *. Both run in the preview beside your code, on a computer, an iPad or an iPhone.

Outside Circuitry, dials.js comes with pictures.js and pydials comes with Pyctures: pictures run panel.js or pyctures run panel.py opens your browser on the scene. Both are on npm and PyPI as betas (npm install dials.js, pip install pydials), and on GitHub. Getting started says how to use them on a page of your own.