Handel is a small procedural programming language for writting songs in browser.
The Handel Interpreter interprets Handel programs and plays compositions in browser, thanks to Tone.js.
Try the Handel Web Editor here: Handel Web Editor
Join the Handel forum to ask questions and showcase compositions. Also check out the Contributing guidelines.
soli deo gloria
Add the below to your html document:
<script src="https://unpkg.com/handel-pl"></script>
You're all set!
Alternatively
Install Handel with the following:
npm i handel-pl
And import Handel with the following:
import * as Handel from 'handel-pl';
start
chunk example
play E3, C3, G3 for 2b
endchunk
run example with sound piano, loop for 5
finish
See the Examples folder here for example Handel programs and inspiration.
function clicked(){
Handel.RunHandel(`
start
chunk example using somePlayable
play somePlayable
rest for 1b
endchunk
save myPlayable = Eb3 for 1b
run example using myPlayable with sound piano, loop for 5
finish
`)
}
document.addEventListener("click", clicked);
Note that you pass the Handel code into the RunHandel function Handel.RunHandel(someHandelCode).
To compile to midi, pass a config object to the RunHandel function with outputMidi set to true.
const config = {outputMidi: true};
Handel.RunHandel(`start play E4 for 1b finish`, config);
Additionally, you can use the StopHandel function to stop a running Handel program. Handel.StopHandel()
Handel programs are contained within the start and finish keywords. Below is a complete Handel program:
start
play E4 for 1b
finish
The program above only plays 1 note. But it's a start!
You can be play notes and chords using the play command. Below is an example program that plays a note, then a chord:
start
play C#3 for 1b
play E3, G3, A4 for 1b
finish
Note the syntax above. A play command begins with the play keyword, then a note or chord (a list of notes separated by commas) follows.
Lastly play commands need a duration. The play commands above end with 'for 1b'. This states how long the particular note or notelist (chord) should be held.
Phew! We're getting somewhere.
Similar to the play command, a rest can played using the rest command. Below is an example program that rests for 1 beat then plays a note for 2 beats.
start
rest for 1b
play G5 for 2b
finish
tl;dr Here is a code snippet showing variables in Handel
start
save mynotelist = Cb3, D3
save myduration = for 1b
save myplayable = E4, F4, G3 for 3b
save myotherplayable = mynotelist for myduration
play myplayable
rest myduration
play myotherplayable
finish
You can declare Variables in Handel. Variables store three builtin types in Handel: Notelists, Durations, Playables.
A Notelist is a single note name, or a list of note names separated by commas.
For example:
Bb3
G#2, A2
Next, a Duration is the keyword for followed by a beat.
A beat is any whole number followed by the letter 'b'
All together, here are some example durations:
for 1b
for 2b
for 16b
for 32b
Lastly, we've already seen Playables above. Playables are a note or notelist (chord) followed by a duration. Here are some example playables.
Bb3 for 1b
D#6, E#6, G3 for 1b
no promises that the above chord sounds pleasing to the ear :p
Finally variables!
To store a notelist, playable or a duration use the save keyword, followed by a variable name, an equal sign and a notelist, playable, duration (or another variable which stores on of these values).
Variable names must contain only lowercase letters, and no numbers. Variable names must also not be any of the reserved keywords in Handel. (See the Reserved Keywords section below).
Below is an example program using variables.
start
save mynote = E2
save myplayablenote = mynote for 2b
save myrest = for 2b
play myplayablenote
rest myrest
play myplayablenote
rest myrest
finish
OK! So far so good!
Handel (v0.5.3) and greater now supports variable reassignment. Variables can be reassigned using the update keyword.
For example:
save mynotelist = B3
update mynotelist = Bb3
Variables which store Notelists can have their notes shifted left or right by a number of semitones.
The following example reassigns (or shifts) ```mynotelist`` down/left by one semitone. Then up/right by two semitones.
start
save mynotelist = B3
update mynotelist lshift 1
play mynotelist for 1b
update mynotelist rshift 2
play mynotelist for 1b
finish
Handel (v0.2.0 and greater) supports block loops. Block loops begin with the block keyword and end with the endblock keyword and the amount of times a block should loop (a loop customization).
Here is an example of a block loop in Handel.
start
block
play C3, E3, G3 for 1b
play D3, F3, A3 for 1b
endblock loop for 10
finish
Block loops use the global scope, if used globally, or use the scope of the chunk they are in.
Block loops are blocking (no pun intended), and should not be confused with Handel's procedures (chunks).
More on procedures below.
Procedures in Handel are called chunks. You can conceptualize a chunk as a song track. When ran, chunks play at the same time as other run chunks and the global track. Chunks must begin with the chunk keyword and end with the endchunk keyword.
Below is an example program with a kick drum and a piano, playing together.
start
chunk backbeat using myplayable
play myplayable
endchunk
chunk mykeys
play E3, G3, A3 for 1b
play G3, A2, C3 for 1b
play F3, A3, C3 for 1b
play D3, F2, A3 for 1b
endchunk
run mykeys with sound piano, loop for 2
save myplayable = A1 for 1b
run backbeat using myplayable with sound kick, loop for 8
finish
Both the 'backbeat' chunk and the 'mykeys' chunk above play together (not one after the other). This behavior allows multitrack songs to be created with Handel.
Note that each chunk has its own scope.
As noted above you can create chunks with the chunk keyword. The name of the chunk (the chunk name) follows the keyword.
This chunk name must be all lowercase letters, no numbers and cannot be one of Handel's reserved keywords. (See the Reserved Keywords section below).
After the chunk name, you can optionally add parameters. A list of comma separated parameters can follow the using keyword.
Together you get the following: chunk somechunkname using someparam, anotherparam
After the optional parameter list, you can add a body to the chunk. This is a function body (what you would like to happen when the chunk is ran).
Lastly the chunk must be ended with the endchunk keyword.
You can run a chunk using the run keyword.
To run a chunk use the run command followed by the name of the chunk.
If the chunk has parameters, a you must use a matching number of comma separated arguments.
Here is an example running two chunks. One chunk requires arguments the other does not.
start
chunk noargs
play C3 for 1b
endchunk
chunk withtwoargs using argone, argtwo
play argone
play argtwo
endchunk
run noargs
save somevar = Cb4 for 1b
run withtwoargs using E3 for 1b, somevar
finish
Note that saved variables, playables, or durations, can be used as arguments when running a chunk.
OK! Now to configuring a run of a chunk.
You can configure a run of chunk by adding the with keyword and a comma separated list of customizations to the end of a run command.
There are three main customizations: bpm, sound, and loop.
You can use bpm keyword to set the bpm of a run of a chunk.
For example bpm 120
You can use the sound keyword to set the instrument of a run of a chunk.
For example sound piano
The current available sounds to choose from are: piano, synth, casio, kick, snare, hihat
You can use the loop keyword to set the amount of times the run of a chunk shoud loop for.
For example loop for 10
All together you can configure a run of a chunk as follows:
start
chunk withargs using somechord
play somechord
endchunk
run withargs using E3, G3, F3 for 1b with bpm 100, loop for 8, sound piano
finish
Above we've got a chord, played with a piano, looping 8 times, with a bpm of 100!
(see reference below for addition customizations)
Handel (v0.4.0 and up) allows custom instruments to be loaded into Handel Programs. Instruments can be created and added to a run of a Handel program as follows.
let myinst = Handel.MakeInstrument({
A1: 'https://tonejs.github.io/audio/casio/A1.mp3',
A2: 'https://tonejs.github.io/audio/casio/A2.mp3'
})
let config = {}
config.instruments = {funkyinst: myinst}
Handel.RunHandel(`
start
load funkyinst as funky
chunk example
play E4 for 4b
enchunk
run example with sound funky
finish
`, config)
The MakeInstrument function wraps Tone.js's sampler constructor. It takes a urls object as its argument. This urls object, maps note names matched to their location (locally or not). One or more mappings can be used.
After making an instrument above, we add it to our config object and run the Handel program with that config.
Within the Handel program we load the instrument as follows: load configInstrumentName as nameOfInstrumentWithinHandel
Note: this feature makes your Handel program less portable but gives you the freedom of using arbitrary instruments in your Handel program.
Note: A musical note. 7 octaves are available.
C3
Eb2
G#7
Notelist: A list of notes. Together this list of notes forms a chord. (6 notes maximum).
C2, E2, G2
C4, Eb4, G#3
Beat: A number of beats. A whole number followed by the character 'b'.
1b
20b
Duration: An expression that represents the amount of beats to play or hold for.
for 1b
for 2b
Playable: A note or a notelist, followed by a duration.
C1, F1, E1, D1, for 1b
D1 for 2b
play: A command to play a note or notelist for a given duration. Play commands can also take a loop customization.
play C1, F1, E1, D1, for 1b
play D1 for 2b
play E3 for 2b loop for 5
rest: A command to rest for a given duration.
rest for 1b
rest for 2b
save: A command to save a variable (see above section on variables for more details). Only playabes and durations can be saved in variables.
save myplayable = E2 for 1b
save myduration = for 1b
update: A command for reassigning variables (see above section on variable reassignment for more details). Only playabes and durations can be saved in variables.
save myplayable = E2 for 1b
update myplayble = Eb2 for 2b
save mynotelist = G#4, Cb4
update mynotelist lshift 2
lshift: Shift a notelist left a number of semitones.
rshift: Shift a notelist right a number of semitones.
save mynotelist = D#4, C4
update mynotelist lshift 2
update mynotelist rshift 4
load: A command to load custom instruments into Handel (see above section on custom instruments for more details).
load someinst as instname
block: Used to declare a loop. (see above section on block loops for more details)
block
play Gb3 for 2b
rest for 2b
endblock loop for 10
chunk: Used to declare a procedure. (see above section on procedures for more details)
chunk mybassline
play G1 for 2b
endchunk
using: Used to prepend a list of paramaters for a chunk.
chunk mypiano using nicechord
play nicechord
endchunk
run: Used to run a chunk. (see above section on running procedures for more details).
run mypiano using E2, C#2 for 1b
with: Used to customize a given run of a chunk. (see section on procedures above for more details)
run mybassline with bpm 115
bpm: beats per minute (bpms are best synced with values under 1000).
loop: amount of times to loop
sound: instrument to use.
volume: percentage volume of a chunk (0 - 100).
reverb: reverberation duration in miliseconds (>= 1).
pan: amount to pan the sound of a chunk (0 - 100). Note: 0 = hard-left, 50 = center, 100 = hard-right.
run mybassline with bpm 90, loop for 2, sound kick, volume 50, pan 25, reverb 10000
Sounds: possible sounds that can be used to customize a run of a chunk.
piano
guitar
synth
casio
kick
snare
hihat
All note names are reserved keywords, ex. C4, Bb1. For this reason use only lowercase letters with no numbers in variable names.