Skip to content

Commit dbffc61

Browse files
author
Kyle Stetz
committed
Start readme
1 parent 36db8db commit dbffc61

File tree

3 files changed

+322
-2
lines changed

3 files changed

+322
-2
lines changed

README.md

Lines changed: 320 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,320 @@
1+
<img
2+
align="center"
3+
src="https://github.com/kylestetz/slang/logo.png"
4+
width="468"
5+
/>
6+
7+
# Slang — An audio programming language built in JS
8+
9+
[Play with Slang](http://slang.kylestetz.com)
10+
11+
Slang was created to explore implementing a programming language entirely in the browser. Parsing is handled by [Ohm.js](https://github.com/harc/ohm) using a [custom grammar](./slang-grammar.js), the editor uses CodeMirror with a simple syntax definition, and the runtime itself is written in JS using the Web Audio API.
12+
13+
### Goals of this project
14+
15+
I have always wanted to write a programming language from scratch, but as someone who didn't study computer science I find it incredibly intimidating. Discovering [Ohm.js](https://github.com/harc/ohm) changed my mind; its incredible editor and approachable JS API make it possible to experiment quickly with a lot of feedback. This project is my first pass at build a language and runtime environment from start to finish.
16+
17+
This is not meant to be a great or comprehensive language itself, but I do hope this project can serve as a roadmap if you'd like to build your own!
18+
19+
You'll notice a distinct lack of in-context error handling, inline docs, helpful UI, etc. Creating a great editor experience was not a goal of this project and it would take a lot of work to get there. I did my best to make it pleasant to use.
20+
21+
# How to write Slang
22+
23+
Slang consists of **sound lines** and **play lines**. Sound lines build up a synthesizer (or drum machine), then play lines tell those synthesizers or drum machines what to play.
24+
25+
It turns out that explaining your own programming language is ridiculously hard, so I suggest skipping to the **Examples** section below and trying those out before reading all of these docs.
26+
27+
## Sound Lines
28+
29+
A sound establishes a variable (which always starts with `@`) that contains a **chain of sounds**.
30+
31+
## Play Lines
32+
33+
A play line starts with the word `play`, followed by the variable you want to play, and then declares a rhythm and notes to use. You can have multiple play lines referencing a single synth and they will all play independently (e.g. if you want to play polyphonic melodies).
34+
35+
`rhythm` accepts a list of rhythm values or a function that returns rhythm values, while `notes` accepts a list of notes or a function that returns notes. Let's look at a simple example and then see how we can take advantage of the more advanced functions.
36+
37+
A simple synth:
38+
```
39+
@synth (adsr (osc sine) 64n 8n 0 8n)
40+
play @synth (rhythm [8n]) (notes [e3 e4 e5])
41+
```
42+
43+
Now let's make a synth that plays a scale using the `(chord)` function. Chord takes a type as its second argument (e.g. `major`, `chromatic`, `phrygian`, etc.) and a root note as its third argument.
44+
```
45+
@synth (adsr (osc tri) 64n 8n 0 8n)
46+
play @synth (rhythm [8n]) (notes (chord lydian e3))
47+
```
48+
49+
Taking it one step further, let's put that `chord` function call within the `random` function, which will randomly pick one of the notes from the chord each time it's called.
50+
```
51+
@synth (adsr (osc tri) 64n 8n 0 8n)
52+
play @synth
53+
(rhythm [8n])
54+
(notes (random (chord lydian e3)))
55+
```
56+
57+
The `flatten` and `repeat` functions, when used inside of `notes`, are a powerful way to create repeating phrases. Since `notes` only takes a single list we use the `flatten` function to take a few different calls and flatten them down. The `repeat` function will take the list we give it and repeat it a number of times, saving us some copying & pasting.
58+
```
59+
@synth (adsr (osc sine) 64n 8n 0 8n)
60+
play @synth
61+
(rhythm [8n])
62+
(notes (flatten [
63+
(repeat 3 (chord lydian e4 4))
64+
(chord lydian d4 4)
65+
]))
66+
```
67+
68+
## Syntax
69+
70+
Functions are contained within parentheses, much like in Clojure. The first keyword in a function is the **functio name**, which is followed by all of its arguments. Any argument can be a primitive value or a list (neat!); if it's a list, Slang will take one value at a time and loop back to the beginning when it reaches the end. Check out the Reference section for lots of usage examples.
71+
72+
# Reference
73+
74+
In Slang every argument can be either a static value (such as `8n`, `e3`, `1`, etc.) or a list of values. If you provide a list as an argument to a function it will take the next value in the list every time it is called, looping back around when it reaches the end. As an example, the oscillator can accept a list of types: `(osc [sine tri saw])`. Every time a note is hit, it will use the next type in the list.
75+
76+
## Sound Functions
77+
78+
### `(osc <type: sine> <pitchOffset: 0>)`
79+
80+
Creates an oscillator with an optional pitchOffset in semitones. Filters and effects can be chained off of the oscillator using the `+` sign.
81+
82+
`type`:
83+
- `sine`
84+
- `saw` or `sawtooth`
85+
- `tri` or `triangle`
86+
- `square`
87+
88+
`pitchOffset`: how many semitones to shift the pitch.
89+
90+
Usage:
91+
```
92+
# Creates a synth with two sine oscillators, one pitched 7 semitones above the root note
93+
@synth (osc sine)
94+
@synth (osc sine 7)
95+
96+
# Creates a synth that chooses a random oscillator for each note that is hit.
97+
@melody (osc (random [sine saw tri square]))
98+
```
99+
100+
### `(drums)`
101+
102+
Creates a drum machine. It does not accept any arguments.
103+
104+
When writing a play line, the notes 0 - 11 represent the 12 drum sounds.
105+
106+
_Pro tip_: Any number above 11 will wrap around using modulus, so for example 25 will trigger sound 1 since `25 % 12 == 1`. This allows you to pass in note values (e.g. `e3`) as well since they correspond to number values.
107+
108+
### `(adsr <osc> <attack: 0.05> <decay: 0> <sustain: 1> <release: 0.05>)`
109+
110+
Creates an amp envelope which contains an oscillator followed by ADSR values. The attack, decay, and release arguments can be numbers or rhythm values (e.g. `8n`, `8t`, `4n`, etc.). Sustain is a number from 0 - 1.
111+
112+
Usage:
113+
```
114+
# Creates a sine wave oscillator with an amp envelope.
115+
@synth (adsr (osc sine) 8n 8n 0.5 4n)
116+
```
117+
118+
### `+ (filter <type: lp> <frequency: 100> <resonance: 1>)`
119+
120+
Creates a filter. This should be chained off of a oscillator or envelope.
121+
122+
`type`:
123+
- `lp` (lowpass)
124+
- `hp` (highpass)
125+
- `bp` (bandpass)
126+
- `n` (notch)
127+
128+
`frequency`: A value from 0 - 127 representing the frequencies 0 - 11,025.
129+
130+
`resonance`: A number from 0 - 100 representing the amount of resonance (Q) to apply.
131+
132+
Usage:
133+
```
134+
@synth (osc sine) + (filter lp 20)
135+
```
136+
```
137+
# Make a lowpass filter that loops through the
138+
# numbers 10 to 50 one at a time.
139+
@melody (osc saw) + (filter lp [10..50])
140+
```
141+
142+
### `+ (gain <value>)`
143+
144+
Creates a gain (volume). This should be part of a sound chain.
145+
146+
`value`: A number from 0 - 1.
147+
148+
Usage:
149+
```
150+
@synth (osc sine) + (gain 0.5)
151+
@melody (osc sine) + (gain [0 0.25 0.5 0.75 1])
152+
```
153+
154+
### `+ (pan <value>)`
155+
156+
Creates a stereo panner. This should be part of a sound chain.
157+
158+
`value`: A number from -1 (left) to 0 (center) to 1 (right).
159+
160+
Usage:
161+
```
162+
@synth (osc sine) + (pan -1)
163+
@synth (osc sine 12) + (pan 1)
164+
```
165+
166+
### `+ (delay <time: 8n> <feedback: 0.1> <wet: 0.5> <dry: 0.5> <cutoff: 11025>)`
167+
168+
Creates a delay effect. This should be part of a sound chain.
169+
170+
`time`: A rhythm value or a number in seconds.
171+
172+
`feedback`: A number from 0 - 1 representing the amount of feedback to apply.
173+
174+
`wet`: A number from 0 - 1 representing the wet level.
175+
176+
`dry`: A number from 0 - 1 representing the dry level.
177+
178+
`cutoff`: A number from 0 - 11025 representing the frequency of a cutoff filter on the delay.
179+
180+
Usage:
181+
```
182+
@synth (adsr (osc saw) 64n 8n 0 8n) + (delay 8t 0.4 1 1)
183+
```
184+
185+
Creates
186+
187+
## Utility Functions
188+
189+
### `(chord <type> <root> <length>)`
190+
191+
Returns a list of notes belonging to a chord.
192+
193+
`type`: A text value representing a chord type, e.g. `major`, `bebop`, `phrygian`. The list of possible chords is taken from [this library](https://github.com/danigb/tonal/blob/master/packages/dictionary/data/scales.json), but with spaces and `#` symbols removed (e.g. `minor #7M pentatonic` becomes `minor7Mpentatonic` in Slang).
194+
195+
`root`: A note, e.g. `e3`.
196+
197+
`length` (optional): a number representing exactly how many notes to return in the list. If unspecified, the length of the list will vary from chord to chord.
198+
199+
Usage:
200+
```
201+
@synth (adsr (osc sine) 64n)
202+
play @synth (notes (chord phrygian e3))
203+
```
204+
205+
### `(random <list>)`
206+
207+
Selects a random item from the list each time it is called. The list can be a range such as `[1..10]` or the output of any other utility function, such as `chord` or `flatten`.
208+
209+
Usage:
210+
```
211+
@synth (adsr (osc (random [saw tri])) 64n)
212+
+ (filter lp [10..50])
213+
play @synth
214+
(rhythm (random [8n 8t 4n]))
215+
(notes (random (chord phrygian e3)))
216+
```
217+
218+
### `(flatten <list>)`
219+
220+
Takes a list of lists and flattens it.
221+
222+
Usage:
223+
```
224+
@synth (adsr (osc sine) 64n)
225+
play @synth (notes (flatten [[e3..e4] [d#4..e#3]]))
226+
```
227+
```
228+
@synth (adsr (osc sine) 64n)
229+
play @synth (notes (flatten [
230+
(repeat 2 [e3 e4 e5])
231+
(repeat 2 [d3 d4 d5])
232+
(repeat 2 [a3 a4 a5])
233+
[g3 g4 g5]
234+
[f3 f4 f5]
235+
]))
236+
```
237+
238+
### `(repeat <amount> <list>)`
239+
240+
Takes a list and repeats it `amount` times. Useful when used inside of `flatten`.
241+
242+
Usage:
243+
```
244+
@perc (drums)
245+
play @perc (notes (flatten [
246+
(repeat 2 [0 6 3 6])
247+
(repeat 2 [6 0 3 6])
248+
]))
249+
```
250+
251+
### `(reverse <list>)`
252+
253+
Reverses the list.
254+
255+
Usage:
256+
```
257+
@synth (adsr (osc sine) 64n) + (gain 0.5)
258+
play @synth (notes (reverse (chord lydian e4)))
259+
play @synth (notes (chord lydian e5))
260+
```
261+
262+
---
263+
264+
Primite values:
265+
- **numbers** - integers and floats (`0`, `0.25`, `10000`, etc.)
266+
- **lists** (space-separated) - `[0 1 2 3 4 5 6]`
267+
- **notes** - `e3`, `d#4`, `f2`, etc.
268+
- **rhythm** - `32t`, `32n`, `16t`, `16n`, `8t`, `8n`, `4t`, `4n`, `2n`, and `1n`
269+
- **rests** - `r32t`, `r32n`, `r16t`, `r16n`, `r8t`, `r8n`, `r4t`, `r4n`, `r2n`, and `r1n`
270+
- **special strings** - some functions take string arguments, such as `filter` and `osc`
271+
272+
---
273+
274+
# Examples
275+
276+
A simple synthesizer
277+
```
278+
# This is a sound line that establishes a synthesizer called @melody
279+
@melody (adsr (osc sine) 64n 8n 0)
280+
# This is a play line that plays @melody using a rhythm and a list of notes
281+
play @melody (rhythm [8n]) (notes [e3 d3 g3 f3])
282+
```
283+
284+
A drum machine
285+
```
286+
# Drums don't accept any arguments (at the moment!)
287+
@percussion (drums)
288+
289+
# Hi-hats
290+
play @percussion (rhythm [16n r16n 16n 16n]) (notes [6 7 8])
291+
# Kick and snare
292+
play @percussion (rhythm [8n]) (notes [0 3 11 0 3 0 3 11])
293+
```
294+
295+
A randomized synth & bassline with drums
296+
```
297+
@synth (adsr (osc saw) 64n)
298+
+ (filter lp (random [5..30]))
299+
+ (delay 8n 0.7 0.5 1)
300+
+ (gain 0.25)
301+
302+
@bass (adsr (osc tri) 64n 2n 0.4 4n)
303+
304+
@drums (drums) + (gain 2)
305+
306+
play @synth
307+
(rhythm [8t])
308+
(notes (random (chord phrygian e5)))
309+
310+
play @bass
311+
(rhythm [1n])
312+
(notes (random (chord phrygian e2)))
313+
314+
play @drums
315+
(rhythm [8t r8t 8t 8t 8t r8t])
316+
(notes [6])
317+
play @drums
318+
(rhythm [4n 4n 4n r8t 8t 8t])
319+
(notes [0 3 0 11 11])
320+
```

logo.png

23.9 KB
Loading

notes.todo

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,8 @@ Design:
3636

3737
todos:
3838
✔ get rid of commas as separators @done (18-06-02 07:15)
39-
expose errors
40-
play/pause button
39+
expose errors @done (18-09-29 15:44)
40+
play/pause button @done (18-09-29 15:44)
4141
✔ drums @done (18-09-29 11:48)
4242
✔ @percussion (drums <set>) + (filter lp 10) @done (18-09-29 11:48)
4343
✔ play @percussion (rhythm [8n]) (notes [0 1 2 3 4 5]) @done (18-09-29 11:48)

0 commit comments

Comments
 (0)