Music Player Library for BASIC Programs

With this tool we will expand on the Simple Sound Effects Library that can be found here. The goal of this tool is to add music to the background of the Basic programs and frankly majority of this code can be also used with Assembly, C or any other programming language, compiled or interpreted as long as we take in consideration potential interrupt handling conflicts. Of course the scope of such integrations is beyond the scope of this post.

This is alpha version of the library so there almost certainly are bugs lurking in there so please use caution and save you BASIC programs often when using the library and I would appreciate if you report any bugs you found.

Goal

This tool is definitely going to be more complex but we want to keep it as simple as possible and as fast as possible so that it doesn’t affect the speed of execution of our BASIC programs too much. So the goals are:

  • Up to six independent channels of sound - voices
  • Using VERA PSG
  • 6 predefined instruments that can be changed
  • 2 open slots for more custom instruments
  • We can define ADSL envelope for each voice separately
  • It is run in the background without need of interaction from Basic program beyond the initialization
  • Capability to start and stop from BASIC
  • Should be able to process notes for easy translation of sheet music

Music theory

Please note that I am not a musician and this is extremely simplified view on how the music is written.

One of the goals of this library is to be able to relatively easily transfer music from music sheets into computer readable format. Of course the music theory can be very complex and we will only cover some basics to get us started. In order to tackle this let’s set a goal to reproduce a classic song :-) Twinkle Twinkle in three voices. Music sheet looks like this:



In the simplest way we can describe each note with its pitch or frequency and its duration. If we look at the picture above we can see that pitch is defined by the position of the notes. Higher it is on “the staff” the higher the pitch. The staff consists of five lines and four spaces between. We have two staffs, top one is called treble staff and lower one is called bass staff. As the names suggest the treble contains higher pitched notes, typically for main melody and bass staff contains lower pitched notes for supporting bass notes. Both staffs meet in the middle at the note called C4 or sometimes called the middle C because it is a note in the middle of the piano keyboard.
Each note has a different name and in western music we have seven notes named A, B, C, D, E, F, G which represent a full octave. In addition to those we have half notes between most of the listed notes. For example halfway between A and B is A# (A sharp) or Bb (B flat) which is the same note having the same pitch but can be written in either way. There are only two exceptions, there is no half note between B and C and between E and F. Those two pairs are already separated by half tone. So complete 12 note octave is: A, A#/Bb, B, C, C#/Db, D, D#/Eb, E, F, F#/Gb, G and G#/Ab and then it starts with A again.
Next picture shows the range of notes from both main staff and we are also introducing note IDs that we will use in our programs later. We see that C4 on the top of bass staff has exactly the same note ID as C4 at the bottom of the treble staff: Note ID = 49.




We also see that most note IDs are not consecutive numbers but have one number missing between. Of course those are IDs for those sharp and flat notes so it makes sense that for example there is a space between C4 and D4 for C4# or D4b which of course has Note ID = 50. There are naturally no spaces between B and C and every occurrence of E and F. Our library is able to play a very wide range of notes or frequencies, which is in fact much larger than a standard notes from above pictures. Complete list:

Note ID Note Frequency Hi Lo Note ID Note Frequency Hi Lo Note ID Note Frequency Hi Lo
1 C0 16.35 0 44 37 C3 130.81 1 95 73 C6 1046.50 10 249
2 C#0/Db0 17.32 0 46 38 C#3/Db3 138.59 1 116 74 C#6/Db6 1108.73 11 160
3 D0 18.35 0 49 39 D3 146.83 1 138 75 D6 1174.66 12 81
4 D#0/Eb0 19.45 0 52 40 D#3/Eb3 155.56 1 162 76 D#6/Eb6 1244.51 13 13
5 E0 20.60 0 55 41 E3 164.81 1 186 77 E6 1318.51 13 211
6 F0 21.83 0 59 42 F3 174.61 1 213 78 F6 1396.91 14 166
7 F#0/Gb0 23.12 0 62 43 F#3/Gb3 185.00 1 241 79 F#6/Gb6 1479.98 15 133
8 G0 24.50 0 66 44 G3 196.00 2 14 80 G6 1567.98 16 113
9 G#0/Ab0 25.96 0 70 45 G#3/Ab3 207.65 2 45 81 G#6/Ab6 1661.22 17 107
10 A0 27.50 0 74 46 A3 220.00 2 79 82 A6 1760.00 18 116
11 A#0/Bb0 29.14 0 78 47 A#3/Bb3 233.08 2 114 83 A#6/Bb6 1864.66 19 141
12 B0 30.87 0 83 48 B3 246.94 2 151 84 B6 1975.53 20 183
13 C1 32.70 0 88 49 C4 261.63 2 190 85 C7 2093.00 21 242
14 C#1/Db1 34.65 0 93 50 C#4/Db4 277.18 2 232 86 C#7/Db7 2217.46 23 64
15 D1 36.71 0 99 51 D4 293.66 3 20 87 D7 2349.32 24 162
16 D#1/Eb1 38.89 0 104 52 D#4/Eb4 311.13 3 67 88 D#7/Eb7 2489.02 26 25
17 E1 41.20 0 111 53 E4 329.63 3 117 89 E7 2637.02 27 167
18 F1 43.65 0 117 54 F4 349.23 3 169 90 F7 2793.83 29 76
19 F#1/Gb1 46.25 0 124 55 F#4/Gb4 369.99 3 225 91 F#7/Gb7 2959.96 31 10
20 G1 49.00 0 132 56 G4 392.00 4 28 92 G7 3135.96 32 226
21 G#1/Ab1 51.91 0 139 57 G#4/Ab4 415.30 4 91 93 G#7/Ab7 3322.44 34 215
22 A1 55.00 0 148 58 A4 440.00 4 157 94 A7 3520.00 36 233
23 A#1/Bb1 58.27 0 156 59 A#4/Bb4 466.16 4 227 95 A#7/Bb7 3729.31 39 27
24 B1 61.74 0 166 60 B4 493.88 5 46 96 B7 3951.07 41 110
25 C2 65.41 0 176 61 C5 523.25 5 125 97 C8 4186.01 43 229
26 C#2/Db2 69.30 0 186 62 C#5/Db5 554.37 5 208 98 C#8/Db8 4434.92 46 129
27 D2 73.42 0 197 63 D5 587.33 6 41 99 D8 4698.63 49 69
28 D#2/Eb2 77.78 0 209 64 D#5/Eb5 622.25 6 134 100 D#8/Eb8 4978.03 52 51
29 E2 82.41 0 221 65 E5 659.25 6 234 101 E8 5274.04 55 77
30 F2 87.31 0 234 66 F5 698.46 7 83 102 F8 5587.65 58 151
31 F#2/Gb2 92.50 0 248 67 F#5/Gb5 739.99 7 194 103 F#8/Gb8 5919.91 62 19
32 G2 98.00 1 7 68 G5 783.99 8 57 104 G8 6271.93 65 196
33 G#2/Ab2 103.83 1 23 69 G#5/Ab5 830.61 8 182 105 G#8/Ab8 6644.88 69 173
34 A2 110.00 1 39 70 A5 880.00 9 58 106 A8 7040.00 73 210
35 A#2/Bb2 116.54 1 57 71 A#5/Bb5 932.33 9 199 107 A#8/Bb8 7458.62 78 54
36 B2 123.47 1 75 72 B5 987.77 10 92 108 B8 7902.13 82 220

Above are all 108 notes or frequencies that the library is configured to play. Of course you are free to tinker with source code and increase or reduce the range of it. Commander X16 documentation describes the formula on how to get from the frequency in Hertz to the VERA settings to generate the required pitch. To make the calculations fast we created two simple lookup tables to determine the VERA settings from Note ID. One table is for High byte and the other for low byte so during the execution of IRQ music player no calculation is necessary. They are stored in a source file “Tables.inc”.
For illustration the lowest note on piano is A0 (Note ID: 10) and highest is C8 (Note ID: 97).
Guitar in standard tuning plays open string as E2 (Note ID:29), A2 (Note ID:34), D3 (Note ID: 39), G3 (Note ID: 44) B3 (Note ID: 48) and E4 (Note ID: 53).

The second main piece of information we get from the notes is their value or duration. Music in general is dictated by tempo or speed at which it is played and that is very well suited for transfer to computer format. Of course human musicians add little adjustments that make the music really come alive and add personal touch to the feel of it. We will be satisfied by computer following rules which makes it sound a bit mechanical. Anything more is well beyond the scope of this library.
It is interesting that music notes follow binary rules meaning their values are always doubled or halved, depending on the direction we look at. We will not go into beats and timing details because they are not that critical at this phase. OK let’s look at different types of notes and their values (durations) relative to beat and to each other:




We see that quarter note duration is exactly half of the half note and one quarter of the whole note but also that it is twice as long as the Eighth note and four times as long as Sixteenth note. For the purpose of preparing data for the Music player it is important to understand those relationships because we have to take them in consideration when decoding it into computer readable format.

Decoding music

We will start decoding the Twinkle Twinkle song with melody first. Let’s also focus on pitch first and duration and timing next.
Main melody is a series of notes on the treble clef. It starts with C4, followed by another C4, then we have two G4s and so on. If we look at the table of note IDs we see that those notes for the first line translate to:

Note C4 C4 G4 G4 A4 A4 G4 F4 F4 E4 E4 D4 D4 C4
Note ID 49 49 56 56 58 58 56 54 54 53 53 51 51 49

And that is it, we have our melody decoded. Timing is not much more complicated. To determine the tempo we have to first determine what is the shortest note in our music and then set the timing for all the rest. In our case we see that we have mostly quarter notes and few half notes. In order to be able to encode the durations we have to introduce two special values for Note ID:

Note ID: 0 Skip, don’t do anything just reset the timer until next note
Note ID: 255 Stop the sound

With Note ID 0 we can handle pauses and skip the beats so let’s use it to take care of half notes in our song. With it the encoding of first line becomes:

Note C4 C4 G4 G4 A4 A4 G4 F4 F4 E4 E4 D4 D4 C4
Note ID 49 49 56 56 58 58 56 0 54 54 53 53 51 51 49 0


One of the tools we can use to help us with timing are bar lines. Bar lines are the vertical lines on the music sheets. Within a song, each bar should be of the same duration regardless of how many notes are within. For example in our case our music is defined as 4 beats per bar that means that each bar can contain exactly one whole note, two half notes, one half and two quarter notes, etc. In our case the sum must always be 1.

Instruments

Next major feature of the music player is its ability to simulate different instruments. We use all the features that VERA provides: Four different waveforms Pulse (square) Sawtooth Triangle Noise Left and Right channel Pulse width for Pulse waveform Volume control What is missing is the volume envelope also known as ADSR envelope. ADSR is a way to modulate the volume of the sound through time. The sound is split into four phases:
  • Attack - time in which the sound reaches maximum volume from zero
  • Decay - time in which the sound eases from maximum volume to the sustain volume
  • Sustain - time while the volume doesn’t change at all
  • Release - time in which the sound fades away from sustain volume level to zero

We also have to define two volumes:
  • Maximum volume
  • Sustain Volume





When combining different envelopes with different waveforms we can generate very different sounds and simulate some common instruments. Of all these ADSR attributes the Sustain is slightly different because we should be able to simulate instruments that produce continuous sound and ones that have one time envelope. As examples of continuous instruments we can list violin, organ, trumpet or any other instrument where tone can be held almost indefinitely. On the other hand we have instruments like piano or guitar which at the press of the key or pluck at the string create one single sound and start fading almost immediately. In order to achieve this we have to be able to set a Continuous attribute in which case the duration of Sustain level is determined in the music decoding and overrides the setting in the instrument definition.
The Music library comes with some default instruments pre configured but it also allows for custom instruments or even modifying existing instruments by poking at appropriate locations.

Usage

To use the library we of course have to load it and provide the song information to it. The library is built for loading at memory location $9000, which is at the end of the available user RAM to leave as much space available for the BASIC program as possible. 
We can use three main function calls:

Start SYS $9000 sets up the music and starts to play. It expects address to music structure in address r0 ($02 and $03)
Stop SYS $9003 stops the music and removes the IRQ Player
Get info SYS $9006 returns the usable information for customization

Start (SYS $9000)

We need to provide an address to Music data in Zero Page register r0. We can do that from BASIC program simply by poking low byte into address 2 and high byte into address 3 for example if we want to store our data between the library and the end of available memory we can use address $9A00 we need to:

POKE 2,$00:POKE 3,$9A

So let’s tackle the data structure that the Start function of the Music library is expecting. It can be split into three logical parts:
  • Header - defining basic parameters of the music
  • Instruments - list of instruments used for each of the voices used
  • Music - sequential list of Note IDs to represent the whole song

Header:

Number of voices 1 byte Number of concurrent voices to be played, this value determines the length of next section - value N
Tempo 1 byte Defines the shortest note or number of beats in the number of IRQ calls which is 60 times per second. So tempo of 20 will play 20*1/60 of second = 3 notes or beats per second
Music length 2 bytes Length of music in beats times number of voices. If song is 50 beats long and we use 3 voices the length in this register is 150
Autorepeat 1 byte 1 for autorepeat, 0 for playing it only once and exit after the song is finished.


Voices:

Voice 0 1 byte Value between 0-7 for one of instruments to be played on voice 0
Voice 1 1 byte Value between 0-7 for one of instruments to be played on voice 1
... 1 byte ...
Voice N 1 byte Value between 0-7 for one of instruments to be played on voice N

Music:

Note ID 1 byte First note on voice 0
Note ID 1 byte First note on voice 1
Note ID 1 byte ...
Note ID 1 byte First note on voice N
Note ID 1 byte Second note on voice 0
Etc.

Stop (SYS $9003)

Function to stop the music does not require any parameters. It immediately stops the music by setting volume to all voices to zero and removes the IRQ Player from the path.

GetInfo (SYS $9006)


This function is important for additional customizations. After calling it, it returns two addresses in registers r0 (address 2 and 3) and r1 (address 4 and 5). First one is pointing to the variables the library uses and the second one to the instruments. Let’s look at the content but please note that this might be different in different versions therefore we provide the build number as the first piece of information.
Address can be read with simple formula:

SYS $9006
M = PEEK(2)+PEEK(3)*256

Following are the most important variables:
Offset Name Size Description
0 Build 1 byte Build ID to identify the version of Music library loaded
1 Running 1 byte Indicator if music player is currently running or not 1 - yes, 0 - no
2 Tempo 1 byte Current tempo or speed of music
3 Voices 1 byte How many voices are currently set up (1-6)
4 Repeat 1 byte Is loaded music continuously repeating 1 - yes, 0- no
5 Length 2 bytes Length of currently loaded music in total notes = bytes = beats * voices


The rest of variables can be seen in documentation in file “Variables.inc

Instruments are stored at address point to in r1 and that address can be retrieved using:

SYS $9006
M = PEEK(4)+PEEK(5)*256

Each instruments occupies 8 byes but currently only 7 are used:


Offset Name Size Description
0 Wave+Pulse 1 byte bit 6 and 7 determine the wave type with following values: 00 - pulse, 01 - sawtooth, 10 - triangle, 11 - noise. The lower 6 bits define width of the pulse wave
1 AD 1 byte Duration of Attack and Decay phase
2 SR 1 byte Duration of Sustain and Release phase
3 Continuous 1 byte Determines if the instrument is of continuous type like flute or organ or non continuous like piano or guitar: 0 - not continuous, 1 - continuous When set to continuous, this setting overrides the Sustain duration during music play and is defined in the music. The sound is played until replaced by a new note or 255.
4 Volume 1 byte Maximum volume after Attack phase 0 - 63
5 Sustain Volume 1 byte Volume during the Sustain phase 0 - 63
6 LR 1 byte Defines if the instrument is played on Left, Right or both channels
7 TBD 1 byte Currently not used


It is very important to understand the timing of Attack, Decay, Sustain and Release phases. This library is using interrupts that are triggered 60 times per seconds so the smallest interval we can define is 16.67 milliseconds and all timing can only be calculated in multiples of this value. We have 4 bits to determine time of each of the phases so possible durations are as follows:

Value Loops Duration
0 0 0.00 ms
1 1 16.67 ms
2 2 33.33 ms
3 3 50.00 ms
4 4 66.67 ms
5 5 83.33 ms
6 7 116.67 ms
7 10 166.67 ms
8 12 200.00 ms
9 15 250.00 ms
10 20 333.33 ms
11 30 500.00 ms
12 40 0.67 seconds
13 60 1.00 second
14 90 1.50 second
15 120 2.00 seconds


Library comes with five predefined instruments and 3 empty slots for custom ones. Of course the user is free to POKE also into existing ones to adjust them to his/her needs.

Existing Instruments are set as follows:

0 - Piano 1 - Organ 2 - Violin 3 - Trumpet 4 - Percussion 5 - Guitar
Waveform Triangle Triangle Sawtooth Square Noise Square
AD $22 $10 $10 $10 $03 $01
SR $0E $F3 $F5 $F5 $1A $5E
Continuous 0 1 1 1 0 0
Volume 63 63 63 63 63 63
Sustain 63 63 60 60 50 50
LR 11 11 11 11 11 11

Source code

Source code is split in five files and can be built using cc65 toolset using following command line:

cl65 -t cx16 Play.asm -C cx16-asm.cfg -o PLAY.PRG

Files

X16.inc
Contains constants for system registers, VERA addresses and Macro for setting VERA

Tables.inc
contains lookup tables for calculating frequencies (VERA values) from Note IDs, Delay loops for ADSR phases and configurations of instruments

Variables.inc
all the memory locations used as variables. They are mostly replicated for each voice and many contain precalculated values to speed up the IRQ Player performance

Play.asm
is the main program. It first parses the input file, performs necessary calculations to prepare variables for play routine. At the end it inserts the IRQ player address so it is called when the Interrupt is triggered.

IRQplayer.asm
Is responsible for actually playing sounds and applying the ADSR envelope to them. It is tracking the phase and taking care of timing. It is called before the system IRQ handler and does not interfere with the operation of the system.



If you are interested in designing new instrument envelopes and songs and would like to share with the community I would be happy to create a library with full credits to authors so people can spice up their games (or other programs) with your music.



Link to full source code is here.
Link to binary is here.

Comments

  1. I was playing around with your music player and wanted to add a drum voice to the twinkle twinkle... example. The thing is that when I add a drum and let it sound on a beat where the melody voice should be quiet, the melody voice plays again. - trying to add code example below.
    10 COLOR 7,0:CLS
    20 PRINT"SETTING UP THE SOUND IN THE BACKGROUND"
    30 LOAD"PLAY.PRG",8,1,$9000
    40 FOR N=0 TO 216
    50 READ A
    60 POKE $6000+N,A
    70 NEXT N
    80 POKE 2,$00
    90 POKE 3,$60
    100 SYS $9000
    120 PRINT "WE CAN CONTINUE RUNNING OUR BASIC PROGRAM NOW...":COLOR 11,0
    130 FOR N=0TO 8000:PRINT CHR$(205.5+RND(1));:NEXT N
    135 COLOR 7,0
    140 CLS:PRINT "WE CAN EVEN LOAD SIMPLE SOUND EFFECTS LIBRARY AND TEST IT":PRINT
    150 LOAD"EFFECTS.PRG",8,1,$0400
    160 GOSUB 500:PRINT "PING":SYS$400
    170 GOSUB 500:PRINT "SHOOT":SYS$403
    180 GOSUB 500:PRINT "ZAP":SYS$406
    190 GOSUB 500:PRINT "EXPLODE":SYS$409
    200 GOSUB 500:GOSUB 500:COLOR 11,0
    210 FOR N=0TO 3000:PRINT CHR$(205.5+RND(1));:NEXT N
    220 COLOR 7,0:PRINT:PRINT "END IF WE WANT TO, WE TURN IT OFF COMPLETELY"
    230 PRINT "AND EXIT THE PROGRAM":PRINT:PRINT
    235 COLOR 8,0:PRINT "I HOPE YOU ENJOYED IT"
    240 GOSUB 500:GOSUB 500
    250 SYS $9003
    260 END
    500 REM ===== PAUSE =====
    510 FOR I=1 TO 10000
    520 NEXT I
    530 RETURN
    999 REM
    1000 REM ===== HEADER DATA =====
    1001 REM
    1010 DATA 4 :REM NUMBER OF VOICES
    1020 DATA 20 :REM TEMPO
    1030 DATA 208,0 :REM LENGTH (16 BITS)
    1040 DATA 1 :REM AUTOREPEAT ON
    1049 REM
    1050 REM ===== INSTRUMENTS =====
    1051 REM
    1060 DATA 0,0,0,4 :REM ALL THREE INSTRUMENTS ARE PIANO
    1099 REM
    1100 REM ===== MUSIC DATA =====
    1101 REM
    1110 DATA 49,44,37,0
    1120 DATA 49,0,0,49
    1130 DATA 56,44,41,0
    1140 DATA 56,0,0,49
    1150 DATA 58,44,42,0
    1160 DATA 58,0,0,49
    1170 DATA 56,44,41,0
    1180 DATA 0,0,0,49
    1190 DATA 54,44,42,0
    1200 DATA 54,0,0,49
    1210 DATA 53,44,41,0
    1220 DATA 53,0,0,49
    1230 DATA 51,44,39,0
    1240 DATA 51,0,0,49
    1250 DATA 49,44,37,0
    1260 DATA 0,0,0,49
    1270 DATA 56,44,41,0
    1280 DATA 56,0,0,49
    1290 DATA 54,44,42,0
    1300 DATA 54,0,0,49
    1310 DATA 53,44,41,0
    1320 DATA 53,0,0,49
    1330 DATA 51,44,42,0
    1340 DATA 0,0,0,49
    1350 DATA 56,44,41,0
    1360 DATA 56,0,0,49
    1370 DATA 54,44,42,0
    1380 DATA 54,0,0,49
    1390 DATA 53,44,41,0
    1400 DATA 53,0,0,49
    1410 DATA 51,44,42,0
    1420 DATA 0,0,0,49
    1430 DATA 49,44,37,0
    1440 DATA 49,0,0,49
    1450 DATA 56,44,41,0
    1460 DATA 56,0,0,49
    1470 DATA 58,44,42,0
    1480 DATA 58,0,0,49
    1490 DATA 56,44,41,0
    1500 DATA 0,0,0,49
    1510 DATA 54,44,42,0
    1520 DATA 54,0,0,49
    1530 DATA 53,44,41,0
    1540 DATA 53,0,0,49
    1550 DATA 51,44,39,0
    1560 DATA 51,0,0,49
    1570 DATA 49,44,37,0
    1580 DATA 0,0,0,0
    1590 DATA 0,0,0,0
    1600 DATA 0,0,0,0
    1610 DATA 0,0,0,0
    1620 DATA 0,0,0,0

    ReplyDelete
  2. Thanks for notifying me about this bug Jimmy. Updated version is uploaded with a fix for this bug.

    ReplyDelete
  3. You've got a lot of neat stuff going on here, Dusan. I'm going to try to grok it.

    ReplyDelete

Post a Comment

Popular posts from this blog

Commander X16 Premium Keyboard

Hello VERA (BASIC vs C vs Assembly)

Default Palette