SASS
A Music-Oriented
Programming Language
Contents
o
Comments
o
Timing
o
Macro Instruments and Sound Effects
Sound ASSembly is an audio programming language
designed for use with embedded platforms, particularly game consoles.
As the capabilities
of SASS targets may vary wildly, the SASS standard only encompasses basic macro
instrument structure and music script decoding (note on, note off, loops,
calls, etc.). Many of the richer features (and inconvenient limitations) are
platform-specific, so it’s best to check the manual of your target’s SASS
compiler for more details.
SASS may be
viewed as an alternative to existing music languages such as MML (Music Macro Language) and is a derivative of Atari’s SPL (Sound Programming Language). Users coming from an MML background
should pay careful attention to the macro instrument structure, music blocks,
and timing as these differ significantly from how MML is usually written.
SASS does
not have to be exclusively written by hand. Music scripts can be generated from
a MIDI source using MID2SASS, which may serve
as a helpful starting point for the language.
SASS is
available for use with the platforms listed below, more details are available
in the each of their drivers’ respective documentation.
Platform |
Type |
Sound Driver |
Microsoft
Windows |
Wavetable |
|
Atari
Lynx |
PSG + PCM |
|
NEC
TurboGrafx-16 |
WSG + PCM |
Values may
be entered in decimal, hexadecimal, and binary using the following syntax…
32 ; Decimal (No prefix) -32 ; Negative Decimal (Leading “-”) $20 ; Hexadecimal (Leading “$”) %100000 ; Binary (Leading “%”) |
As most
SASS targets use mixed precision values (i.e. 16.16, 16.8, or 8.8), fractions can
be declared after a decimal point…
32.16 ; Decimal -32.16 ; Negative
Decimal f-32.0625 ; Negative Float $20.10 ;
Hexadecimal %100000.10000 ; Binary |
The fraction’s
format must match that of its leading integer - this can be somewhat convoluted
for decimals, as a value of 31/2 would be written as 3.128 in 16.8 precision or 3.32768 in 16.16. Compilers which support
the gfloat parameter can optionally disable
this behavior.
Follow a
semicolon “;” and encompass the remainder of the
current line…
; This is a
comment spanning an entire line as4
60 ; Comments may also follow commands and values |
All values quantifying
time are given in driver ticks, the exact rate of which is dependent upon the
current target and driver implementation. Most targets use a 60Hz base tick, using
this assumption we can write the note sequence below…
f.3
60 ; F-Natural
3rd octave, one second rest
20 ; Note
off and rest, 1/3 second as5
40 ; A-Sharp
5th octave, 2/3 second |
If the
driver tick was 240Hz, the example above would have to be changed to…
f.3
240 ; F-Natural
3rd octave, one second rest
80 ; Note
off and rest, 1/3 second as5
160 ; A-Sharp
5th octave, 2/3 second |
Macro Instruments and Sound Effects
Instruments
are intended for use in music tracks under the control of the driver’s parser,
whereas sound effects are spontaneously dispatched by the game software. Both
feature unique attributes based upon these applications - but are mostly
identical during processing. Drivers usually merge both of these into one
entity as macros or patches and their declarations reflect
this unity…
name priority ;
<- Label volume value ;
<- Header frequency value tuning value { rest
ticks ;
<- Body volume value frequency value loop
count ; ( endloop noteoff end } |
All declarations
begin with a label, which contains a
name
and priority (if a sound effect). Names
consist of one or more characters (such as select_item,
explosion, or poof)
and are used to select instruments in music scripts or generate equates for
sound effects. Priorities are only used
for sound effects and specify their importance relative to both music tracks
and other sound effects - higher priorities win out during channel contention.
Instruments have no inherent priority as this is set within their attached music
track.
Following
the label is a header; this
specifies the initial state of the
instrument or sound effect through various parameter adjustments. Nearly all of these are
platform specific, so it is best to check the documentation for your target’s
driver. Common parameters are volume, frequency, and tuning.
After the header is the macro body, which specifies how the sound will
change over time and is composed of one or more note, flow control, and parameter
adjustment commands within {
Curly Braces }.
Select targets
support multi-channel macros, these
resemble a normal sound effect definition but feature a { Curly Brace } region
enclosing one or more headers and bodies after the label…
name priority ;
<- Label volume value ;
<- 1st Channel Header frequency value { rest
ticks ;
<- 1st Channel Body loop
count ; ( endloop end } volume value ;
<- 2nd Channel Header frequency value { rest
ticks ;
<- 2nd Channel Body end } |
Commands are
divided into three types : note, parameter adjustment, and flow control. Note
commands encompass delays and region tagging. Parameter adjustments control
how the instrument or sound effect will create its sound - these are the only
commands which can be used in the header. Flow control commands
include loops and end markers.
Some
commands available to instruments and sound effects are listed below…
o
noteoff,n : note off
Only applicable to instruments - tags the region following it as the note off
(or release) portion of its script.
If an instrument is playing and the music script hits a rest command, this region of the script will start decoding on the
next update cycle.
... ;
When a rest command is encountered in noteoff ; the music script while an
instrument rest 8 ;
is playing, the instrument will start end ; executing the portion of its
script ... ; directly following the noteoff command. |
o
rest,r ticks : rest
Pause for the specified number of ticks before advancing to the next command, the
instrument or sound effect will continue to play during this time.
rest 14 ; Wait 14 ticks end ; Stop |
o
volume,v value: set the current volume
o
frequency,f value: set the current frequency offset
Patch volume and frequency controls (platform-specific).
o
tuning,t value: set instrument tuning
Only applicable to instruments, specifies the tuning / waveform period (platform-specific).
o
loop,l count : loop start
Specifies the start of a loop region, and the number of times it will repeat.
If negative values are used, the loop will continue indefinitely.
... loop 10 ;
Inner loop 10 times rest 6 endloop endloop end ; Stop |
o
endloop,el : loop end
Specifies the end of a loop region.
... rest 18 endloop ; Marks end of above loop end ; Stop |
o
end,e : sound end
Halts decoding of the instrument or sound effect, then frees its pertinent
channel(s).
... |
Each music
track consists of one or more script blocks which contain commands representing
how and when to play instruments or dispatch samples and sound effects. An
example of their format is below…
name
priority ; <- Main Block { using instrument priority value call name loop
count ; ( endloop rest ticks as3 ticks wait ticks end } name ;
<- Sub Block { ... return or break } |
All blocks
start with a label, which at minimum
contains the block’s name. Each music track must contain a single main block which is the first to be played and also contains an initial
priority in its label. Names consist of one or more characters used
to identify the block while priorities are an integer value. Higher
priorities indicate a more important track relative to sound effects - using a
priority of zero will cause the track to never decode.
Following
the label, and enclosed between two { Curly Braces } are one or more commands composing the block body. As with macro instruments, the music command set may be
divided into three categories : notes, parameter adjustment, and flow control. Note
commands specify when to play particular note (key on and key off). Parameter adjustments
select which instrument is used and how it will be played. Flow control commands change how
the script will decode - such as loops, calls to different blocks, and
termination of playback.
Some
commands available to music scripts are listed below…
o
notes
A key on event is specified with a three
letter note command followed by a duration in driver ticks. The note command’s
three characters are arranged as…
( Letter ) (
Natural / Sharp / Flat ) ( Octave )
Note letters may be c, b, d, e, f, g, and a. Natural, Sharp, and Flat for the
given note may be specified using “.” (period), “s”,
and “b” respectively. The octave number may range from 0-9.
... c.4 60 ; C-Natural 4th octave, 60 ticks rest 10 bs4 20 ; B-Sharp 4th octave, 20 ticks rest 10 bb2 80 ; B-Flat 2nd octave, 80 ticks rest 10 ... |
o
rest,r ticks : rest
Acts as a key off event if following
a note, or a general delay if used on its own.
... as4 20 rest 30 ; Key off, wait for 30 ticks ... rest 60 ; Wait for 60 ticks ... |
o
frest,fr ticks : force rest
Always acts as a key off event
regardless of whether it follows a note. This can be used to repeatedly snap an
instrument back to the note off section
of its script.
... as4 20 frest 30 ; Key off, wait for 30 ticks ... frest 60 ; Key off, wait for 60 ticks ... |
o
wait,w ticks : wait
Pauses decoding for the specified number of ticks. Useful for delays where note
off behavior is not desired.
... wait 60 ; Wait for 60 ticks ... |
o
using,u instrument : set current instrument
Select the instrument used for playing notes, applies only to the current
channel.
... using piano ; Using
instrument “piano” g.3 20 ; The following notes will be played
with rest 10 ; “piano,” with each note request b.3 20 ; specifying a key on, and each rest rest 10 ; indicating a key off. a.3 20 rest 10 ... |
o
priority,pr value : set track priority
Adjust the current priority of the music track, this can be used to give
sections of a piece more or less importance relative to sound effects. Setting
the priority to zero will cause the track to cease decoding.
... priority 120 ; Set priority to 120 ... priority 240 ; Set priority to 240
(higher) ... |
o
loop,l count : loop start
Specifies the start of a loop, and the number of times it will repeat. If
negative values are used, the loop will continue indefinitely.
loop -1 ;
Repeat outer loop forever loop 10 ; Repeat inner loop 10 times as4 20 rest 20 endloop gs3 30 rest 20 endloop end ; Stop |
o
endloop,el : loop end
Specifies the end of a given loop.
loop -1 ;
Repeat forever fs4 20 rest 30 call drumSolo endloop ; Marks end of above loop end ; Stop |
o
call,c label : call script block
Begin decoding a given script block with the name specified by label.
... call pianoSolo ; Call the script block below ... } pianoSolo { ... return } |
o
return,rt : return from
called script block
Resume decoding from where a given script block was called.
... call pianoSolo ... } pianoSolo { ... return ; Resume decoding after “call
pianoSolo” } |
o
break,b : pattern
break
Returns all music tracks to the lowest entry in their call stack. Effectively,
all tracks will be snapped back to the “main” script if they’re not already
there.
mainTrack
120 { ... call pianoSolo ... } pianoSolo { ... call drumminThang } drumminThang { ... break ; Resumes decoding after “call
pianoSolo” } |
o
end,e
Stops decoding of the music track and frees the channel.
... end ; Stop |