Music
Back in Sound and music, a MUSIC block played a little song with two
tracks. Now that you can make instruments, it’s time to give them a whole
band to play in.
The parts of a song
A song in a MUSIC block is made of a few things:
TEMPO: how fast it goes, in beats a minute.TIMESIG: how many beats a bar has, and which note is a beat.4, 4is four quarter notes, and3, 4is a waltz.PATTERNs: a few bars each, with a name. A pattern’slengthis how many beats it lasts.TRACKs, inside a pattern: each one a line of notes, all playing at once.- The
ARRANGEMENT: which patterns play, in what order.
A track says everything about itself after !!: its notes, the
instrument that plays them, the channel it plays on (give each track of a
pattern its own), how loud it is (volume, 0 to 100), where it sits from left
to right (pan, -1 to 1), and a name, if you’d like to find it again.
SCREEN 320, 240
MUSIC "waltz" !! loop
TEMPO 150
TIMESIG 3, 4
PATTERN "a" !! length(6)
TRACK !! name("tune") channel(1) instrument(0) notes("o5 l4 c e g f2 d4")
TRACK !! name("bass") channel(2) instrument(7) volume(70) notes("o2 l4 c e g g b > d")
END PATTERN
PATTERN "b" !! length(6)
TRACK !! name("tune") channel(1) instrument(0) notes("o4 l4 b > d f e2.")
TRACK !! name("bass") channel(2) instrument(7) volume(70) notes("o2 l4 g b > d < c e g")
END PATTERN
ARRANGEMENT
PLAY "a"
PLAY "b"
END ARRANGEMENT
END MUSIC
PRINT musictempo("waltz"), "beats a minute,", musicbeats("waltz"), "to a bar"
PRINT musicpatterncount("waltz"), "patterns,", trackcount("waltz", "a"), "tracks in each"
// Once through: twelve beats at 150 a minute is under five seconds.
PLAYMUSIC "waltz"
ticks = 0
DO WHILE ticks < 300
IF COOLDOWN(1) THEN ticks = ticks + 1
LOOP
150 beats a minute, 3 to a bar
2 patterns, 2 tracks in each
!! loop on MUSIC starts the song again when it gets to the end, which is
what music under a game usually wants.
A beat
Here’s the drum kit from the last lesson, playing in a song. The kick, the snare and the hats each get a track, so they play at the same time.
repeat on a track plays its notes again and again until the pattern ends,
so the hats only need writing once. And in the arrangement, with plays
patterns alongside each other: the drums go on under the bass, then under
the bass and the tune together.
SCREEN 320, 240
INSTRUMENT 210
PERCUSSION 1
LAYER "sine"
SAMPLE "kick.wav" !! basenote(60)
END LAYER
END INSTRUMENT
INSTRUMENT 211
PERCUSSION 1
LAYER "sine"
SAMPLE "snare.wav" !! basenote(60)
END LAYER
END INSTRUMENT
INSTRUMENT 212
PERCUSSION 1
LAYER "sine"
SAMPLE "hat.wav" !! basenote(60)
END LAYER
END INSTRUMENT
MUSIC "groove" !! loop
TEMPO 110
PATTERN "drums" !! length(4)
TRACK !! name("kick") channel(10) instrument(210) notes("o4 l4 c r c r")
TRACK !! name("snare") channel(11) instrument(211) notes("o4 l4 r c r c")
TRACK !! name("hat") channel(12) instrument(212) volume(50) repeat notes("o4 l8 c")
END PATTERN
PATTERN "bass" !! length(4)
TRACK !! channel(2) instrument(53) notes("o2 l8 c c > c < c e e g g")
END PATTERN
PATTERN "tune" !! length(4)
TRACK !! channel(1) instrument(51) volume(60) notes("o5 l8 g e c e g4 a4")
END PATTERN
ARRANGEMENT
PLAY "drums" !! repeat(2)
PLAY "drums" !! repeat(2) with("bass")
PLAY "drums" !! repeat(4) with("bass", "tune")
END ARRANGEMENT
END MUSIC
PLAYMUSIC "groove"
PRINT "playing:", musicplaying()
PAUSE
Playing, stopping and pausing
PLAYMUSIC starts a song, from the beginning, and your program carries on
while it plays. !! loop (or !! loop(FALSE)) on PLAYMUSIC says whether
it loops this time, whatever the song says. STOPMUSIC stops it.
PAUSEMUSIC stops the song where it is, and RESUMEMUSIC carries on from
there, which is just what a game’s pause screen needs. musicplaying() is
TRUE while a song is playing, and FALSE once it’s stopped or paused.
SCREEN 320, 240
MUSIC "theme" !! loop
TEMPO 140
PATTERN "a"
TRACK !! channel(1) instrument(0) notes("o4 l8 c e g e c e g e")
TRACK !! channel(2) instrument(7) notes("o2 l2 c g")
END PATTERN
ARRANGEMENT
PLAY "a"
END ARRANGEMENT
END MUSIC
// A second of each, to hear it.
FUNCTION hold(ticks)
t = 0
DO WHILE t < ticks
IF COOLDOWN(1) THEN t = t + 1
LOOP
END FUNCTION
PLAYMUSIC "theme"
PRINT musicplaying()
hold(60)
PAUSEMUSIC
PRINT musicplaying()
hold(60)
RESUMEMUSIC
PRINT musicplaying()
hold(60)
STOPMUSIC
PRINT musicplaying()
1
0
1
0
Crash-tan’s Tip: A song that doesn’t loop still counts as playing once it’s come to its end, until
STOPMUSIC. If you need to know it’s done, ask where it’s got to instead.
Where’s it got to?
musicposition() is how far into the song it is, and musiclength("theme")
how long the whole song is, both in ticks of the song, four to a beat. One
divided by the other is how far along it is, from 0 to 1. (A length can be
0, and nobody divides by 0, so the loop checks first.) Here it is as a bar
across the screen:
SCREEN 320, 240
MUSIC "theme"
TEMPO 160
PATTERN "a"
TRACK !! channel(1) instrument(0) notes("o4 l8 c e g e c e g e d f a f d f a f")
END PATTERN
ARRANGEMENT
PLAY "a" !! repeat(4)
END ARRANGEMENT
END MUSIC
PLAYMUSIC "theme"
length = musiclength("theme")
DO
IF COOLDOWN(1)
done = 0
IF length > 0 THEN done = min(musicposition() / length, 1)
MEMORYBUFFER "bar"
LINE (20, 110) -> (300, 130), 8 !! box
LINE (20, 110) -> (20 + 280 * done, 130), 10 !! box fill
END MEMORYBUFFER
FRAME
CLS
PUTIMAGE (0, 0), "bar"
LOCATE 1, 1
PRINT round(done * 100), "%"
END FRAME
END IF
LOOP
Fading
FADEMUSIC "in" starts a song from silence and brings it up over as many
seconds as you say. FADEMUSIC "out" takes it down to nothing. It’s a gentle
way to start a level, and a gentler way to end one than STOPMUSIC.
SCREEN 320, 240
MUSIC "theme" !! loop
TEMPO 120
PATTERN "a"
TRACK !! channel(1) instrument(3) notes("o4 l2 c e g e")
END PATTERN
ARRANGEMENT
PLAY "a"
END ARRANGEMENT
END MUSIC
FADEMUSIC "in", "theme", 2
ticks = 0
DO
IF COOLDOWN(1)
ticks = ticks + 1
IF ticks = 300 THEN FADEMUSIC "out", "theme", 2
FRAME
CLS
LOCATE 1, 1
IF ticks < 300
PRINT "here it comes..."
ELSE
PRINT "and away it goes"
END IF
END FRAME
END IF
LOOP
Changing a song as the game goes
A song isn’t fixed once it’s written. A few words change it while the game runs:
SET MUSIC "theme" !! tempo(180)changes its tempo, whether it loops, or itstimesig.SET TRACK "theme", "a", 0 !! notes("...")changes a track: the song, the pattern, then which track, counting from 0. Only what you name changes.MUTE TRACK "theme", "a", 1silences a track, and!! muted(FALSE)brings it back.CREATE MUSIC PATTERNadds a new pattern,CREATE TRACKgives it tracks, andADD ARRANGEMENTputs it on the end of the arrangement.
I play a song from a recording I make of it when it starts, so a song that’s already playing goes on as it was. What you change is heard the next time it starts. That makes the moment between two levels the time to change it: the next level’s music is the same song, faster.
SCREEN 320, 240
MUSIC "theme" !! loop
TEMPO 120
PATTERN "a"
TRACK !! channel(1) instrument(0) notes("o4 l8 c e g e c e g e")
TRACK !! channel(2) instrument(7) notes("o2 l2 c g")
END PATTERN
ARRANGEMENT
PLAY "a"
END ARRANGEMENT
END MUSIC
// Two seconds of each level's music.
FUNCTION hold(ticks)
t = 0
DO WHILE t < ticks
IF COOLDOWN(1) THEN t = t + 1
LOOP
END FUNCTION
FUNCTION start_level(level)
SET MUSIC "theme" !! tempo(100 + level * 20)
IF level >= 3
SET TRACK "theme", "a", 1 !! notes("o2 l4 c c g g")
END IF
PLAYMUSIC "theme"
END FUNCTION
FOR level = 1 ... 3
start_level(level)
PRINT "level", level, "at", musictempo("theme")
hold(120)
NEXT
PRINT tracknotes("theme", "a", 1)
STOPMUSIC
level 1 at 120
level 2 at 140
level 3 at 160
o2 l4 c c g g
Here’s a song built from nothing, as the game goes, the way a game might add a part for each boss you beat:
SCREEN 320, 240
MUSIC "boss" !! loop
TEMPO 150
PATTERN "base" !! length(4)
TRACK !! channel(2) instrument(61) notes("o2 l8 c c c c d# d# f f")
END PATTERN
ARRANGEMENT
PLAY "base"
END ARRANGEMENT
END MUSIC
CREATE MUSIC PATTERN "boss", "rage", 4
CREATE TRACK "boss", "rage" !! track_name("lead") channel(1) instrument(59) notes("o5 l8 c d# g > c < g d# c4")
CREATE TRACK "boss", "rage" !! track_name("bass") channel(2) instrument(61) notes("o2 l8 c c c c d# d# f f")
ADD ARRANGEMENT "boss", "rage" !! repeat(2)
APPLY MUSIC "boss"
PRINT musicpatterncount("boss"), "patterns"
FOR step = 0 ... arrangementcount("boss") - 1
PRINT arrangementpattern("boss", step), "x", arrangementrepeat("boss", step)
NEXT
// Once through, the base and the rage twice, then stop.
PLAYMUSIC "boss"
ticks = 0
DO WHILE ticks < 300
IF COOLDOWN(1) THEN ticks = ticks + 1
LOOP
STOPMUSIC
2 patterns
base x 1
rage x 2
Changes wait in your program until the song is next played. APPLY MUSIC
hands them to my audio engine straight away instead, without playing
anything.