Skoar
Skoar is a high-level language for coding music.
It runs on SuperCollider, a free and fantastic audio programming environment.
Skoarcery is a set of tools to define, test, and build the Skoar language.
<? Zelda Theme - inspired by piano arrangement by Shinobu Amayake ?>
130 => )
.alice @default => @instrument [##### ] => @amp
.bob @default => @instrument [##### ] => @amp
.bass @sawpulse => @instrument [### ] => @amp o~~~~
.hats @hats => @instrument [## ] => @amp
.snare @snare => @instrument [## ] => @amp
.kick @kick => @instrument [### ] => @amp
{! bass_end<x> !! !x ) ) ) ] ] !}
{! bass_climb !! | _e ]] _a# ]] c# ] e ]] a# ]] ~o c# ] e ) } | f ) o~ _f ]] ]] ] ) } | !}
{! bassline_a !!
<a#, g#, f#, c#, b, a#, c>.{: .) ]] ]] ] ) ) :}
!bass_end<f>
!}
{! bassline_b !!
<a#, g#, f#, f>.{: ) ]] ]] ] ) ) :}
!bass_climb !bass_climb
<b, a#, c >.{: ) ]] ]] ] ) ) :} !bass_end<f>
!}
{! intro !!
.hats {: }}} :: 4 times :}
.snare {: }}} :: 4 times :}
.kick {: }}} :: 4 times :}
.alice | _a# )) o/. ]] ]] ]] ] | ]. _g# ]] _a# ) o/. ]] ]] ]] ] |
.bob | _d )) o/. ]] ]] ]] ] | _c ]. ]] ) o/. ]] ]] ]] ] |
.bass | a# ) ]] ]] ] ) ]] ]] ] | g# ) ]] ]] ] ) ]] ]] ] |
.alice | ]. _g# ]] _a# ) o/. ]] ]] ]] ] | ] _f ]] ]] ] ]] ]] ] ]] ]] ] ] |
.bob | _c# ]. ]] ) o/. ]] ]] ]] ] | ] o~ _a ]] ]] ] ]] ]] ] ]] ]] ] ] |
.bass | f# ) ]] ]] ] ) ]] ]] ] | f ) ) ) g ] a ] |
!}
{! melody_a !!
.bass !bassline_a
.hats {: }}} :: 7 times :} | ] ] ] ] ] ] ] ] |
.snare {: }}} :: 8 times :}
.kick {: }}} :: 8 times :}
.alice | _a# ) _f )__ o/. _a# ]] ]] c ]] d ]] d# ]] |
.bob | _d ) ]] ]] _c ] _d ]. ]] ]] _d# ]] _f ]] _g ]] |
.alice | f )) o/ ] f ] f# ]] g# ]] |
.bob | _g# ]. _a# ]] ]] c ]] d ]] d# ]] f ) _g# ] _a# ]] c ]] |
.alice | a# )) o/ a# ] ] g# ]] f# ]] |
.bob | c# ]. _f# ]] ]] _g# ]] _a# ]] c ]] c# ]. ]] ] c ]] _a# ]] |
.alice | g# ]. f# ]] f )) ) |
.bob | c# ]. _g# ]] ]] ]] _f# ] _g# ]. ]] ]] _f# ]] _g# ] |
.alice | d# ] ]] f ]] f# )) f ] d# ] |
.bob | _f# ] ]] _f ]] _f# ] ]] _g# ]] _a# ) _g# ] _f# ] |
.alice | c# ] ]] d# ]] f )) d# ] c# ] |
.bob | _f ] ]] _d# ]] _f ] ]] _f# ]] _g# ) _f# ] _d# ] |
.alice | c ] ]] d ]] e )) g ) |
.bob | _e ] ]] _d ]] _e ] ]] _g ] ]] _a ]] ]] _a# ] c ] |
.alice | f ] _f ]] ]] ] ]] ]] ] ]] ]] ] ] |
.bob | _a ] o~ _a ]] ]] ] ]] ]] ] ]] ]] ] ] ~o |
!}
{! melody_b !!
.bass !bassline_b
.hats {: ] ] ] ] ] ] ] ]] ]] :: 12 times :}
.kick {: ) } ) } :: 12 times :}
.snare {: } ) } ) :: 11 times :} | ] ]] ]] ] ]] ]] ] ] |
.alice | _a# ) _f ) o/. _a# ]] ]] c ]] d ]] d# ]] |
.bob | _d ) ]] ]] _c ] _d ]. ]] ]] _d# ]] _f ]] _g ]] |
.alice | f )) o/ ] f ] f# ]] g# ]] |
.bob | _g# ]. _a# ]] ]] c ]] d ]] d# ]] f ) _g# ] _a# ]] c ]] |
.alice | a# )). ~o c# ) | c ) o~ a )) f ) | f# )). a# ) | a ) f )) ) |
.bob | c# )). e ) | d# ) c )) _a ) | _b )). c# ) | c ) _a )) ) |
.alice | f# )). a# ) | a ) f )) d ) | d# )). f# ) | f ) c# )) _a# ) |
.bob | _b )). c# ) | c ) _a )) ) | _f# )). _b ) | _a# ) _f )) _c# ) |
.alice | c ] ]] d ]] e )) g ) |
.bob | _e ] ]] _d ]] _e ] ]] _f ]] _g ] ]] _a ]] _a# ] c ] |
.alice | f ] _f ]] ]] ] ]] ]] ] ]] ]] ] ] |
.bob | _a ] o~ _a ]] ]] ] ]] ]] ] ]] ]] ] ] ~o |
!}
{! fill !!
.alice | f ] _f ]] ]] ] ]] ]] ] ]] ]] ] ] |
.bob | _a ] o~ _a ]] ]] ] ]] ]] ] ]] ]] ] ] ~o |
.snare | ] ]] ]] ] ]] ]] ] ]] ]] ] ] |
.hats | ] ] ] ] ] ] ] ] |
.kick | ) } ) ) |
.bass !bass_end<f>
!}
!intro
!melody_a
!fill
!melody_b
!fill
More examples: examples.md
See also: listen to dungeontimes
And also: listen to brand new shoes
Skoar is a language for coding music, combining a grand-staff-like notation with a flexible programming notation.
__beats_______________ __rests_______________
))) - whole }}} - whole
)) - half }} - quarter
) - quarter } - quarter
] - eighth o/ - eighth
]] - sixteenth oo/ - sixteenth
]]] - thirty secondth ooo/ - thirthy secondth
). - dotted quarter
]]. - dotted sixteenth
o/. - dotted eighth rest
.) - staccato quarter
.]]. - staccato and dotted sixteenth
)__ - quarter with a tie (ties to the next beat)
)__. - dotted quarter with a tie
We call them noats, not notes, you see, notes are already things; nor are these noats the nearly named noads, which are also totally things..
<? use # or b after the noat to sharp or flat it. Or use scale degree numbers. ?>
c ) d ) eb ) f ]] ]] g ] ] g# )
<? you get two octaves to work with, prepend _ for the lower octave. ?>
<_c, _d, _e, _f, _g, _a, _b, c, d, e, f, g, a, b>.choose ]]
{! foo !! <0,1,2,3,4,5,6,7>.{: ]] :} !}
Am => @key !foo
Fb => @key !foo
<E, @dorian> => @key !foo
<F, @minorPentatonic> => @key !foo
Just set @tranpose.
-3 => @transpose
Sort of like a metronome marker, set the bpm to a beat length:
80 => ) <? 80 quarter notes per minute ?>
80 => ] <? 80 eighth notes per minute ?>
A Am A#m Asus2 Adim etc..
Or we can use lists of noats:
<_a,c,e> ) <_a,c#,e> ) <0,2,4> ))
<? up one octave ?> <? down one ?>
~o o~
8va 8vb
ottava alta ottava bassa
<? up two ?> <? down two ?>
~~o o~~
15ma 15mb
alla quindicesima
We have to use the full word forte
, f
is a noat.
fff ffforte ppp pppiano piano mp mf ff pp p
or, use a hash level, and set @amp
:
.strings [### ] => @amp
.piano [#### ] => @amp
.bass [#### ] => @amp
Hash levels can be any length:
[ ] is 0
[#] is 1
[## ] is 0.66ish
Colons:
|: _a ]]] c ]]] e ]]] :| g ]]] ooo/ ]]] :|
Segnos and Codas: (Codas currently not implemented)
| _a ) c ) e ) | ,segno` ) ]] ]] e ]] | f D.S. al fine ) ) ) fine
| ,segno` c ]] e ]] (+) ]] ]] D.S. al Coda '.......' (+) | a) c) e) } |
Infinite repeats:
<? from the top ?>
| _a] c] e] | D.C. <? also accept Da Capo ?>
<? from the segno ?>
| _a] c] e] o/ | ,segno` _f] f] _f] o/ Dal Segno |
We can set and get values from a dictionary local to the voice. Anything set here will be copied into the resulting event every beat; which we can use to configure the voice.
<? names of things start with @ ?>
@smooth => @instrument
<0,3,5> => @detune
<? to lookup the values, we use ! in place of @.... more on ! below.. ?>
a# => @foo
!foo )
This is a simple loop:
{: ]] oo/ ]] ]] :: 8 times :}
You can send a loop to an array as a message to implement a foreach loop:
<_a, _c, c, _e, e, _a>.{: ] ]] ]] :}
If you also put a boolean condition, it will keep foreaching while the condition is true.
<_a, _c, c, _e, e, _a>.{: ] ]] ]] :: !groovy == 5 :}
An if example:
{? !x == !y ?? ]]] ?}
An if with else example:
{? !x == !y ?? ]]] ?? ooo/ ?}
Acceptable boolean operators:
== != >= <= and or xor
At the moment, don't use <
or >
The Skoarpion is like a function, but more like a subroutine, plus a magical fairy with a very short term memory.
{! name<args> !!
body
...
!}
Let's make a skoarpion:
{! drumsBasic<x> !! 4/4
.h {: ] ] ] ] ] ] ] ] :: !x times :}
.s {: } ) } ) :: !x times :}
.k {: )) )) :: !x times :}
!}
If drumsBasic
is called by the .h
voice, then {: ] ] ] ] ] ] ] ] :: !x times :}
is executed;
.s
voice: {: } ) } ) :: !x times :}
, .k
etc..
If any lines are not voiced (like that 4/4
), they will be run by every voice.
Skoarpions normally have scope, but they can be inlined with .inline
, which can be convenient:
{! alice !! ~o mp
<c#, e, _a, g#> => @favorites
<0,5,7> => @detune
@acid => @instrument
!}
{! bob<x> !! o~~ forte
@bass => @instrument
!x => @favorites
!}
<? here's an anonymous skoarpion ?>
{! <x> !! <0, 4, !x> => @detune !} => @charlie
.a !alice.inline
.b !bob<<a,e>>.inline
.c !charlie<<5,7,9>.choose>.inline
...
Skoarpions don't return values. But, every value leaves an impression on the magical fairy, and so it can be said that in a way, skoarpions "return" things. But, that's not what really going on.
{! foo<x,y> !! !x + !y !}
!foo<2,3> => @x <? x is now 5 ?>
Wait, magical fairy?
"Any sufficiently defined magic, is indistinguishable from technology."
There is a magical fairy, who is following along the skoar when it is performed.
Whenever the fairy encounters any kind of value, it leaves a short term impression on her.
Whenever you do 5 => @x
, the fairy's last impression (here, 5
) is saved to @x.
The fairy can be directly referenced with $
.
If you need to save the impression, whatever it is at the moment, you can do $ => @x
Complex expressions are handled by the fairy via arcane magic.
Values and operators will be operated on in the order they appear. SuperCollider users are familiar with this concept.
5 + 2 * 3 => @x <? x is now 21, certainly not 10 ?>
<0, $+1, $+2> => @x <? x is now <0,1,3> ?>
0 + 2 == 2
<0> + 2 == <0, 2>
<_a, c> + e == <_a, c, e>
0 * 2 == 0
2 * 2 == 4
increments and decrements:
2 => @x <? x is now 2 ?>
2 +> @x <? x is now 4 ?>
1 -> @x <? x is now 3 ?>
2 *> @x <? x is now 6 ?>
.rand
for numbers, .choose
for lists.
<? save into @food, a random number between zero and five ?>
5.rand => @food
<? choose a random note ?>
<c,d,e,f,g,a,b>.choose
These are messages passed down to supercollider.
You say .rand
because in SC, Integer
has a .rand
method, Array
has .choose
We don't have nil
in Skoar. We do, however, have cats.
=^.^= <? a cat ?>
Cats eat variables. If you need to unset a variable, use a cat.
'foo' => @x <? x is now 'foo' ?>
=^.^= => @x <? x is gone ?>
If you make a list of cats, they will stay.
< =^.^=, =^.^= > <? a list of cats. In supercollider, this will be [nil, nil] ?>
If you declare a skoarpion to take arguments, but don't supply them, cats will be supplied.
{! foo<x> !! !x + 2 } !foo => @y <? a cat eats y (because a cat + 2 is a cat) ?>
If you try to use a variable that doesn't exist, you will find a cat.
<? if x doesn't exist, set it to 3 ?>
{? !x == =^.^= ?? 3 => @x ?}
If you call a SuperCollider function, and it returns nil
, you will have a cat.
You can wake Cthulhu, crashing the skoar:
^^(;,;)^^
Cthulhu can also make assertions:
^^(;!octave == 5;)^^
If the octave isn't 5, Cthulhu will be so upset that he'll wake up and crash your skoar.
Cats definitely wake Cthulhu.
^^(;=^.^=;)^^
<? comment ?>
{: loop_part :: condition :}
{? condition ?? if_part ?? else_part ?}
{? condition ??
.voice_1 if_part ?? else_part
.voice_2 if_part ?? else_part
...
.voice_n if_part ?? else_part
?}
{! skoarpion_name<args> !! skoarpion_body !}
{! skoarpion_name !! skoarpion_body !}
{! <args> !! skoarpion_body !}
{! skoarpion_name<args> !!
.voice_1 skoarpion_body
.voice_2 skoarpion_body
...
.voice_n skoarpion_body
!}
^^(; cthulhu_condition ;)^^
< list_item_1, list_item_2, ... >
First, clone this git repo to, for example ~/GitHub/Skoarcery
Get the very latest SuperCollider 3.7
Point SuperCollider at the ~/GitHub/Skoarcery/SuperCollider/Skoar
folder: In SuperCollider's interpreter options (edit->preferences->interpreter), include the folder ~/GitHub/Skoarcery
and
restart the interpreter.
Try the examples in examples. Dungeontimes is the simplest.
If you are seeing failures about UnitTest not found, be sure you have pointed SC at ~/GitHub/Skoarcery/SuperCollider/Skoar
rather than ~/GitHub/Skoarcery
.
Skoarcery is the set of tools and tests that builds skoar compilers.
The lexical and syntactic analysers, lex.sc
and rdpp.sc
(ditto .py
) are built with Skoarcery.
They are built and written to .../SuperCollider/Skoar
.
Currently the built code is checked in, you don't need to get Skoarcery working unless you want to work on the language.
Tokens by convention are UpperCamelCase.
Tokens are defined with regexes that have to work with both SuperCollider and Python. All we do is recognise, no capture groups.
Tokens are typically converted to Skoarpuscles in the top section of decorating.sc
Defines an LL(1) grammar suitable for building recursive decent predictive parsers for skoar.
Nonterminals by convention are like_this
+ before a nonterminal indicates this is an intermediate step that can be skipped in the constructed parse tree, it will not create a new skoarnode, instead appending its noads to its parent's children list.
Semantics defined in the second half of decorating.sc
These are written as unit tests, they build our lexers and parsers. Done this way because we generate some information, test it, build on it, test that ...
The important one at the moment is Build_Sc.py, it will run tests, build files, run more tests, etc.. it builds Skoar. This one builds Skoar.
Code_Lexer_Py.py, Code_Lexer_Sc.py - Build lex.py, lex.sc
Code_Parser_Py.py, Code_Parser_Sc.py - Build rdpp.py, rdpp.sc
lex.sc - Lexical analyser, defines classes for each token, extending SkoarToke.
rdpp.sc - Recursive descent predictive parser. Builds the parse tree.