Yesterday I was going to find how to write this program:
GOSUB SET-UP-SID
# we set up the continue variables here
# we want to have them visible in the main loop
CO% = 1 #
# we can also initialize further variables here
[...]
LOOP:
# we can also initialize further variables here
[...]
LOOP:
# this is how we can emulate a while-true cycle in basic
IF CO% = 0 GOTO END-LOOP
# now we will read the notes and upload them to the correct channel
[...]
# now we will wait 1/4 of a second
[...]
# and after it we jump back
[...]
# and after it we jump back
GOTO LOOP
END-LOOP:
GOSUB RESET-SID
END
SET-UP-SID:
# rem here we will set up the channels with the correct instruments
# We already did this
# We already did this
RETURN
RESET-SID:
# rem here we will clear the SID
# We already did this
# We already did this
RETURN
And now I am going to continue it.
Please note that this formalism has nothing to do with how someone else would approach compuer programming. In the good old Basic Days planning the software was done using Flow charts. Nowadays Flow charts aren't neither taught nor used, and someone expects you to just fill random methods provided by your framework or choice in random points of the application.
And now I am going to continue it.
Please note that this formalism has nothing to do with how someone else would approach compuer programming. In the good old Basic Days planning the software was done using Flow charts. Nowadays Flow charts aren't neither taught nor used, and someone expects you to just fill random methods provided by your framework or choice in random points of the application.
How do we wait for one second?
I'm choosing to tackle this problem next because I need to have a sort of slowdown in place before reading the notes and putting them into the SID registers. The Commodore 64 basic gives two commands to read the time: TIME and TIME$. Due to architectural limitations they will give the time from the last system boot (including when you soft-reset the computer with SYS 64738). In addition to it the TIME is stopped when the computer is serializing data. As a last limitation I can think is that TIME is given pace from the current video signal coming out from the Commodore (an NTSC commodore will have a different timing from a PAL commodore, but I have to check).
The difference between the two instructions is that TIME$ will print the time as seconds and it should be text while TIME will print the time as 60th of seconds and will be a number.
We can type this program in and see the two commands in action.
10 FOR I = 1 TO 24
20 PRINT TIME ; TIME$
30 REM WE WANT A SLOWDOWN BEFORE
31 REM PRINTING THE NEXT VALUE OF TIME
35 FOR J = 1 TO 100 : NEXT J
40 NEXT I
Armed with this insight we can speculate that we can store the current time in a variable, test if 60 jiffies have passed and print the new time.
10 PRINT "{CLR/HOME}" ; TIME$ , TIME
20 TT = TIME + 60
30 IF TT =< TIME GOTO 10
40 REM IF YOU PRESS ANY KEY THE PROGRAM WILL END
41 GET A$ : IF A$ <> "" THEN END
50 GOTO 30
This is not perfect but for now it will do.
You can see that we have to change the central loop
# since this is a value that does not change we calculate it
# here, just once
DT = INT(60 / 4)
LOOP:
TT = TIME + DT
# this is how we can emulate a while-true cycle in basic
IF CO% = 0 GOTO END-LOOP
# now we will read the notes and upload them to the correct
# channel
# channel
[...]
# now we will wait 1/4 of a second
# and after it we jump back
CHECK-TIME:
IF TT =< TIME GOTO LOOP
# and after it we jump back
CHECK-TIME:
IF TT =< TIME GOTO LOOP
GOTO CHECK-TIME
This does not complete all the reasoning about time because now we will have to test if the time has come for reading our notes in the SID.
Reading the documentation for the SID we see that for playing a single note we need to insert two values for each channel, one for the LOW frequency and the other for the HIGH frequency. The two frequencies will be combined into a single note.
We also decide that the delay, for now, will be "quarters of second", so when the program jumps to loop we will need to check if the delay has stopped. Remember also that we want to test if the channel is still active.
So we need to change this representation:
[note-1-pitch],[note-1-delay]
to this one:
[note-1-low],[note-1-high],[note-1-delay]
And when we read the data we will just do
READ LO
READ HI
READ DL
But before reading it we have to decide if we need to read a note, and after reading that note we need to decide when we have to read it next.
Each channel will have a timer. Each will be initialized, before entering the loop, with the current time.
T1 = TIME
If T1 is lower or equals to the current TIME then we will make the program read its values
IF T1 > TIME SKIP
READ LO # read the low frequency
READ HI # read the high frequency
READ DL # read the delay.
POKE L1,LO # we put in the Sid 1 low register the value read by low
POKE H1,HL # same but with the high value
T1 = TT + DT * (DL - 1) # this is when we will read the next note.
SKIP:
# we skipped here and here we will test channel 2
[...]
What does it happen if we want to shut off the channel? Or if the channel was shut off before?
We use another variable: C1, that we will initialize to 1 at the beginning of the program. So our micro-block becomes:
IF C1 = 0 SKIP
IF T1 > TIME SKIP
READ LO # read the low frequency
READ HI # read the high frequency
READ DL # read the delay.
IF DL = -1 THEN C1 = 0
IF DL = -1 THEN SKIP
POKE L1,LO # we put in the Sid 1 low register the value read by low
POKE H1,HL # same but with the high value
T1 = TT + DT * (DL - 1) # this is when we will read the next note.
SKIP:
[...] #we skipped here and here we will test for C2 and C3
Now we could copy the block and do the same things again
IF C1 = 0 SKIP_1
IF T1 > TIME SKIP_1
READ LO
READ HI
READ DL
IF DL = -1 THEN C1 = 0
IF DL = -1 THEN SKIP_1
POKE L1,LO
POKE H1,HL
T1 = TT + DT * (DL - 1)
SKIP_1:
IF C2 = 0 SKIP_2
IF T2 > TIME SKIP_2
READ LO
READ HI
READ DL
IF DL = -1 THEN C2 = 0
IF DL = -1 THEN SKIP_2
POKE L2,LO
POKE H2,HL
T2 = TT + DT * (DL - 1)
SKIP_2:
IF C3 = 0 SKIP_3
IF T3 > TIME SKIP_3
READ LO
READ HI
READ DL
IF DL = -1 THEN C3 = 0
IF DL = -1 THEN SKIP_3
POKE L3,LO
POKE H3,HL
T3 = TT + DT * (DL - 1)
SKIP_3:
However as you see this wall of text is unreadable, and as such it's prone to inserting errors. It's better to have a subroutine to handle the common parts of this block of code.
# channel 1
CS = C1 # status of the channel
TS = T1 # time of the channel
LT = L1 # low frequency register of the channel
HT = H1 # high frequency register of the channel
GOSUB COMMON_LOADER
C1 = CS
T1 = TS
# channel 2
CS = C2
TS = T2
LT = L2
HT = H1
GOSUB COMMON_LOADER
C2 = CS
T2 = TS
# channel 3
CS = C3
TS = T3
LT = L3
HT = H3
GOSUB COMMON_LOADER
C3 = CS
T3 = TS
[..]
COMMON_LOADER:
IF CS = 0 SKIP
IF TS > TIME SKIP
READ LO
READ HI
READ DL #note that we set the "off" channel in CS
IF DL = -1 THEN CS = 0
IF DL = -1 THEN SKIP
# the values of LT and HT will be L1 and H1
# in the first channel, L2, H2 and L3, H3
# in the other two
POKE LT,LO
# we put in the Sid 1 low register the value read by low
POKE HT,HL
# same but with the high value
TS = TT + DT * (DL - 1) # this is when we will read the next note.
SKIP:
RETURN
Tomorrow I will type in the program.
This does not complete all the reasoning about time because now we will have to test if the time has come for reading our notes in the SID.
Reading and playing the note data.
The following part is tricky, because we still need to do the following things: finalize the data structure, understand about which channel we are reading the following note, and understandReading the documentation for the SID we see that for playing a single note we need to insert two values for each channel, one for the LOW frequency and the other for the HIGH frequency. The two frequencies will be combined into a single note.
We also decide that the delay, for now, will be "quarters of second", so when the program jumps to loop we will need to check if the delay has stopped. Remember also that we want to test if the channel is still active.
So we need to change this representation:
[note-1-pitch],[note-1-delay]
to this one:
[note-1-low],[note-1-high],[note-1-delay]
And when we read the data we will just do
READ LO
READ HI
READ DL
But before reading it we have to decide if we need to read a note, and after reading that note we need to decide when we have to read it next.
Each channel will have a timer. Each will be initialized, before entering the loop, with the current time.
T1 = TIME
If T1 is lower or equals to the current TIME then we will make the program read its values
IF T1 > TIME SKIP
READ LO # read the low frequency
READ HI # read the high frequency
READ DL # read the delay.
POKE L1,LO # we put in the Sid 1 low register the value read by low
POKE H1,HL # same but with the high value
T1 = TT + DT * (DL - 1) # this is when we will read the next note.
SKIP:
# we skipped here and here we will test channel 2
[...]
What does it happen if we want to shut off the channel? Or if the channel was shut off before?
We use another variable: C1, that we will initialize to 1 at the beginning of the program. So our micro-block becomes:
IF C1 = 0 SKIP
IF T1 > TIME SKIP
READ LO # read the low frequency
READ HI # read the high frequency
READ DL # read the delay.
IF DL = -1 THEN C1 = 0
IF DL = -1 THEN SKIP
POKE L1,LO # we put in the Sid 1 low register the value read by low
POKE H1,HL # same but with the high value
T1 = TT + DT * (DL - 1) # this is when we will read the next note.
SKIP:
[...] #we skipped here and here we will test for C2 and C3
Now we could copy the block and do the same things again
IF C1 = 0 SKIP_1
IF T1 > TIME SKIP_1
READ LO
READ HI
READ DL
IF DL = -1 THEN C1 = 0
IF DL = -1 THEN SKIP_1
POKE L1,LO
POKE H1,HL
T1 = TT + DT * (DL - 1)
SKIP_1:
IF C2 = 0 SKIP_2
IF T2 > TIME SKIP_2
READ LO
READ HI
READ DL
IF DL = -1 THEN C2 = 0
IF DL = -1 THEN SKIP_2
POKE L2,LO
POKE H2,HL
T2 = TT + DT * (DL - 1)
SKIP_2:
IF C3 = 0 SKIP_3
IF T3 > TIME SKIP_3
READ LO
READ HI
READ DL
IF DL = -1 THEN C3 = 0
IF DL = -1 THEN SKIP_3
POKE L3,LO
POKE H3,HL
T3 = TT + DT * (DL - 1)
SKIP_3:
However as you see this wall of text is unreadable, and as such it's prone to inserting errors. It's better to have a subroutine to handle the common parts of this block of code.
# channel 1
CS = C1 # status of the channel
TS = T1 # time of the channel
LT = L1 # low frequency register of the channel
HT = H1 # high frequency register of the channel
GOSUB COMMON_LOADER
C1 = CS
T1 = TS
# channel 2
CS = C2
TS = T2
LT = L2
HT = H1
GOSUB COMMON_LOADER
C2 = CS
T2 = TS
# channel 3
CS = C3
TS = T3
LT = L3
HT = H3
GOSUB COMMON_LOADER
C3 = CS
T3 = TS
[..]
COMMON_LOADER:
IF CS = 0 SKIP
IF TS > TIME SKIP
READ LO
READ HI
READ DL #note that we set the "off" channel in CS
IF DL = -1 THEN CS = 0
IF DL = -1 THEN SKIP
# the values of LT and HT will be L1 and H1
# in the first channel, L2, H2 and L3, H3
# in the other two
POKE LT,LO
# we put in the Sid 1 low register the value read by low
POKE HT,HL
# same but with the high value
TS = TT + DT * (DL - 1) # this is when we will read the next note.
SKIP:
RETURN
Tomorrow I will type in the program.
No comments:
Post a Comment