-
Notifications
You must be signed in to change notification settings - Fork 31
/
slang-grammar.js
118 lines (96 loc) · 2.93 KB
/
slang-grammar.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
module.exports = `
Sound {
Line = Graph | Play | Comment
/*
A comment is any line that begins with #
*/
Comment = "#" any+
/*
SOUND OBJECTS
A sound object is a '@synth' variable. We
can also access part of the sound's graph
using dot notation, e.g. '@synth.osc1'.
We'll only accept soundAccessors in a few
places.
*/
sound = "@" alnum+
propertyAccessor = "." alnum+
soundAccessor = sound propertyAccessor?
/*
FUNCTIONS
A function is anything in parentheses. These
will power blocks and arbitrary tools that
might spit out lists, numbers, etc. The syntax
is inspired by Clojure. Notice that the first
type of sound argument is... a function! This
enables is to write nested functions. Imagine:
(notes (random (chord major E3)))
*/
function = "(" listOf<soundArgument, delimiter> ")"
soundArgument = function -- function
| range -- range
| list -- list
| rhythm -- rhythm
| float -- float
| note -- note
/*
GRAPH LINES
A graph is a sound declaration followed by
one or more pipes that configure it. Graph
declarations will be additive, e.g. two
line with '@synth ~ (osc)' will create two
oscillators.
This definition is slightly longer than it
needs to be so that we can make the first
tilde optional. Either '@synth (osc tri)'
or '@synth ~ (osc tri)' will be valid.
*/
Graph = soundAccessor "~"? PolySoundBlock Pipe?
/*
Sound blocks look like functions, e.g.
'(osc sine 1)'. You can string several
together using pipes, which will literally
pipe the sounds together.
*/
PolySoundBlock = MonoSoundBlock ("+" MonoSoundBlock)*
MonoSoundBlock = "(" listOf<soundArgument, delimiter> ")" name?
name = ":" alnum+
Pipe = ("~" PolySoundBlock)+
/*
PLAY LINES
A play line is a play keyword (either 'play'
or '>'), followed by the sound we want to play,
followed by a pattern. Each pattern uses a
different enclosing bracket. They could also
use a SoundBlock-like definition I guess.
*/
Play = PlayKeyword sound Pattern
PlayKeyword = "play" | ">"
/*
PATTERNS
The play line is expecting one or more function
calls that determine what the sound does. Those
might be things like (rhythm xox), (notes E3 D3),
and (times 0.2 0.3 0.5). Determining what tools
are possible should be a *runtime* concern, not
a grammar-level concern.
*/
Pattern = listOf<function, delimiter>
/*
PRIMITIVES
Here are the primitive types we're working with.
*/
list = "[" listOf<soundArgument, delimiter> "]"
range = "[" int ".." int "]" -- number
| "[" note ".." note "]" -- note
delimiter = " "
float = "-"? digit* "." digit+ -- fullFloat
| "-"? digit "." -- dot
| "-"? digit+ -- int
int = "-"? digit+
note = letter "#" digit+ -- sharp
| letter "b" digit+ -- flat
| alnum+ -- major
rhythm = "r"? digit+ letter
}
`;