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.
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.
ReplyDelete10 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
You've got a lot of neat stuff going on here, Dusan. I'm going to try to grok it.
ReplyDelete