-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathMicroMusicWithRTTTL.bs2
212 lines (173 loc) · 11 KB
/
MicroMusicWithRTTTL.bs2
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
' {$STAMP BS2}
' {$PBASIC 2.5}
' MicroMusicWithRTTTL.bs2: Finally, some really smart parsing!
' This is based off of Parallax's example, but I've thrown in some light optimizations and corrections.
' There are probably more ways to improve the code, but I'm feeling a bit lazy right now and don't want to find them all.
DEBUG "Program Running!"
' -----[ I/O Definitions ]-------------------------------------------------
SpeakerPin PIN 9 ' Piezospeaker connected to P9.
' -----[ Variables ]-------------------------------------------------------
counter VAR Word ' General purpose counter.
char VAR Byte ' Variable stores characters.
index VAR Word ' Index for pointing at data.
noteLetter VAR Byte ' Stores note character.
noteFreq VAR Word ' Stores note frequency.
noteOctave VAR Word ' Stores note octave.
duration VAR Word ' Stores note duration.
tempo VAR Word ' Stores tempo.
default_d VAR Byte ' Stores default duration.
default_o VAR Byte ' Stores default octave.
default_b VAR Word ' Stores default beats/min.
' -----[ EEPROM Data ]-----------------------------------------------------
RTTTL_File DATA "Reveille:d=4,o=7,b=140:8g6,8c,16e,16c,8g6,8e,",
"8c,16e,16c,8g6,8e,8c,16e,16c,8a6,8c,e,8c,8g6,",
"8c,16e,16c,8g6,8e,8c,16e,16c,8g6,8e,8c,16e,",
"16c,8g6,8e,c,p,8e,8e,8e,8e,g,8e,8c,8e,8c,8e,8c,",
"e,8c,8e,8e,8e,8e,8e,g,8e,8c,8e,8c,8g6,8g6,c."
Done DATA ",q,"
Notes DATA "p", "a", "#", "b",
"c", "#", "d", "#",
"e", "f", "#", "g",
"#"
Octave8 DATA Word 0, Word 3520, Word 3729, Word 3951,
Word 4186, Word 4435, Word 4699, Word 4978,
Word 5274, Word 5588, Word 5920, Word 6272,
Word 6645
' -----[ Initialization ]--------------------------------------------------
counter = 0 ' Initialize counter.
GOSUB FindEquals ' Find first '=' in file.
GOSUB ProcessDuration ' Get default duration.
GOSUB FindEquals ' Find next '='.
GOSUB ProcessOctave ' Get default octave.
GOSUB FindEquals ' Find last '='.
GOSUB GetTempo ' Get default tempo.
' -----[ Program Code ]----------------------------------------------------
DO UNTIL char = "q" ' Loop until 'q' in DATA.
GOSUB ProcessDuration ' Get note duration.
GOSUB ProcessNote ' Get index value of note.
GOSUB CheckForDot ' If dot, 3/2 duration.
GOSUB ProcessOctave ' Get octave.
GOSUB PlayNote ' Get freq, play note, next.
LOOP ' End of main loop.
END ' End of program.
' -----[ Subroutine - Find Equals Character ]-----------------------------
FindEquals: ' Go through characters in
' RTTTL file looking for
DO ' '='. Increment counter
READ RTTTL_File + counter, char ' until '=' is found, then
counter = counter + 1 ' return.
LOOP UNTIL char = "="
RETURN
' -----[ Subroutine - Read Tempo from RTTTL Header ]----------------------
' Each keyboard character has a unique number called an ASCII value.
' The characters 0, 1, 2,...9 have ASCII values of 48, 49, 50,...57.
' You can always convert from the character representing a digit to
' to its value by subtracting 48 from the variable storing the digit.
' You can examine this by comparing DEBUG DEC 49 and DEBUG 49.
GetTempo: ' Parse RTTTL file for Tempo.
' Convert characters to
default_b = 0 ' digits by subtracting 48
DO ' from each character's ASCII
READ RTTTL_File + counter, char ' value. Iteratively multiply
IF char = ":" THEN ' each digit by 10 if there
default_b = default_b / 10 ' is another digit, then add
counter = counter + 1 ' the most recent digit to
EXIT ' one's column.
ENDIF ' For example, the string
default_b = default_b + char - 48 ' "120" is (1 X 10 X 10)
counter = counter + 1 ' + (2 X 10) + 0. The '1'
default_b = default_b * 10 ' is converted first, then
LOOP UNTIL char = ":" ' multiplied by 10. The '2'
' is then converted/added.
RETURN ' 0 is converted/added, done.
' -----[ Subroutine - Look up Octave ]------------------------------------
ProcessOctave: ' Octave may or may not be
' included in a given note
READ RTTTL_File + counter, char ' because any note that is
SELECT char ' played in the default
CASE "5" TO "8" ' octave does not specify
noteOctave = char - "0" ' the octave. If a char
counter = counter + 1 ' from '5' to '8' then use
CASE ELSE ' it, else use default_o.
noteOctave = default_o ' Characters are converted
ENDSELECT ' to digits by subtracting
IF default_o = 0 THEN ' '0', which is the same as
default_o = noteOctave ' subtracting 48. The first
ENDIF ' time this subroutine is
' called, default_o is 0.
RETURN ' If 0, then set default_o.
' -----[ Subroutine - Find Index of Note ]--------------------------------
ProcessNote: ' Set index value for lookup
' of note frequency based on
READ RTTTL_File + counter, char ' note character. If 'p',
SELECT char ' index is 0. If 'a' to 'g',
CASE "p" ' read character values in
index = 0 ' DATA table and find match.
counter = counter + 1 ' Record index value when
CASE "a" TO "g" ' match is found. If next
FOR index = 1 TO 12 ' char is a sharp (#), add
READ Notes + index, noteLetter ' 1 to the index value to
IF noteLetter = char THEN EXIT ' increase the index (and
NEXT ' frequency) by 1 notch.
counter = counter + 1 ' As with other subroutines,
READ RTTTL_File + counter, char ' increment counter for each
SELECT char ' character that is processed.
CASE "#"
index = index + 1
counter = counter + 1
ENDSELECT
ENDSELECT
RETURN
' -----[ Subroutine - Determine Note Duration ]---------------------------
ProcessDuration: ' Check to see if characters 'I've modified Parallax's default code here.
' form 1, 2, 4, 8, 16 or 32. 'First of all, "CASE ELSE" was tabbed too far over to the right-fixed.
READ RTTTL_File + counter, char ' If yes, then convert from 'Next, I added a separate case for "2", "4", and "8" to save the Stamp
' ASCII character to a value 'from searching for a nonexistant 26th note or 48th note, etc.
SELECT char ' by subtracting 48. In the
CASE "1", "3" ' case of 16 or 32, multiply
duration = char - 48 ' by 10 and add the next
counter = counter + 1 ' digit to the ones column.
READ RTTTL_File + counter, char
SELECT char
CASE "6", "2"
duration = duration * 10 + char - 48
counter = counter + 1
ENDSELECT
CASE "2", "4", "8"
duration=char-48
counter=counter+1
CASE ELSE ' If no duration, use
duration = default_d ' use default.
ENDSELECT
IF default_d <> 0 THEN ' If default_d not defined
duration = 60000/default_b/duration*3 ' (if default_d = 0), then
ELSE ' set default_d = to the
default_d = duration ' duration from the d=#.
ENDIF
RETURN
' -----[ Subroutine - Check For '.' Indicating 1.5 Duration ]-------------
CheckForDot: ' Check for dot indicating 'I've modified Parallax's default code here.
' multiply duration by 3/2. 'First of all, Parallax used a select with only one case-silly!
READ RTTTL_File + counter, char ' If dot found, multiply by 'It's much smarter to use a single IF statement.
IF char="." THEN ' 3/2 and increment counter, 'I've also changed "duration=duration*3/2" to "duration=duration+(duration/2)",
duration=duration+(duration/2) ' else, do nothing and 'which, as I've noted before, speeds up the math by reducing dependence on
counter=counter+1 ' return. 'slow software multiplication.
ENDIF
RETURN
' -----[ Subroutine - Find Comma and Play Note/Duration ]-----------------
PlayNote: ' Find last comma in the
' current note entry. Then,
READ RTTTL_File + counter, char ' fetch the note frequency
SELECT char ' from data, and play it, or
CASE "," ' pause if frequency = 0.
counter = counter + 1
READ Octave8 + (index * 2), Word noteFreq
noteOctave = 8 - noteOctave
noteFreq = noteFreq / (DCD noteOctave)
IF noteFreq = 0 THEN
PAUSE duration
ELSE
FREQOUT SpeakerPin, duration, noteFreq
ENDIF
ENDSELECT
RETURN