]> 4ch.mooo.com Git - 16.git/blob - 16/PCGPE10/SPEAKER.TXT
modified: 16/DOS_GFX.EXE
[16.git] / 16 / PCGPE10 / SPEAKER.TXT
1                      ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿\r
2                      ³ Programming the PC Speaker ³\r
3                      ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ\r
4 \r
5             Written for the PC-GPE by Mark Feldman\r
6             e-mail address : u914097@student.canberra.edu.au\r
7                              myndale@cairo.anu.edu.au\r
8 \r
9               ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿\r
10               ³      THIS FILE MAY NOT BE DISTRIBUTED     ³\r
11               ³ SEPARATE TO THE ENTIRE PC-GPE COLLECTION. ³\r
12               ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ\r
13 \r
14 \r
15 ÚÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ\r
16 ³ Disclaimer ³\r
17 ÀÄÄÄÄÄÄÄÄÄÄÄÄÙ\r
18 \r
19 I assume no responsibility whatsoever for any effect that this file, the\r
20 information contained therein or the use thereof has on you, your sanity,\r
21 computer, spouse, children, pets or anything else related to you or your\r
22 existance. No warranty is provided nor implied with this information.\r
23 \r
24 ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ\r
25 ³ Basic Programming Info ³\r
26 ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ\r
27 \r
28 The PC speaker has two states, in and out (0 and 1, on and off, Adam and\r
29 Eve etc). You can directly set the state of the PC speaker or you can hook\r
30 the speaker up to the output of PIT timer 2 to get various effects.\r
31 \r
32 Port 61h controls how the speaker will operate as follows:\r
33 \r
34 Bit 0    Effect\r
35 ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ\r
36   0      The state of the speaker will follow bit 1 of port 61h\r
37   1      The speaker will be connected to PIT channel 2, bit 1 is\r
38          used as switch ie 0 = not connected, 1 = connected.\r
39 \r
40 Playing around with the bits in port 61h can prevent the Borland BC++ and\r
41 Pascal sound() procedures from working properly. When you are done using the\r
42 speaker make sure you set bit's 0 and 1 of port 61h to 0.\r
43 \r
44 ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ\r
45 ³ Your First Tone ³\r
46 ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ\r
47 \r
48 Ok, so lets generate a simple tone. We'll send a string of 0's and 1's to the\r
49 PC speaker to generate a square wave. Here's the Pascal routine:\r
50 \r
51 \r
52 Uses Crt;\r
53 \r
54 const SPEAKER_PORT = $61;\r
55 \r
56 var portval : byte;\r
57 \r
58 begin\r
59 \r
60   portval := Port[SPEAKER_PORT] and $FC;\r
61 \r
62   while not KeyPressed do\r
63     begin\r
64       Port[SPEAKER_PORT] := portval or 2;\r
65       Delay(5);\r
66       Port[SPEAKER_PORT] := portval;\r
67       Delay(5);\r
68     end;\r
69   ReadKey;\r
70 end.\r
71 \r
72 On my 486SX33 this generates a tone of around about 100Hz.\r
73 \r
74 First this routine grabs the value from the speaker port, sets the lower two\r
75 bits to 0 and stores it. The loop first sets the speaker to "on", waits a\r
76 short while, sets it to "off" and waits another short while. I write the loop\r
77 to do it in this order so that when a key is pressed and the program exits\r
78 the loop the lower two bits in the speaker port will both be 0 so it won't\r
79 prevent other programs which then use the speaker from working properly.\r
80 \r
81 This is a really bad way of generating a tone. While the program is running\r
82 interrupts are continually occurring in the PC and this prevents the timing\r
83 from being accurate. Try running the program and moving the mouse around.\r
84 You can get a nicer tone by disabling interrupts first, but this would\r
85 prevent the KeyPressed function from working. In any case we want to\r
86 generate a nice tone of a given frequency, and using the Delay procedure\r
87 doesn't really allow us to do this. To top it all off, this procedure uses\r
88 all of the CPU's time so we can't do anything in the background while the\r
89 tone is playing.\r
90 \r
91 \r
92 ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ\r
93 ³ Using PIT Channel 2 ³\r
94 ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ\r
95 \r
96 Connecting the PC speaker to PIT channel 2 is simply a matter of programming\r
97 the channel to generate a square wave of a given frequency and then setting\r
98 the lower two bits in the speaker port to a 1. Detailed information on\r
99 programming the PIT chip can be found in the file PIT.TXT, but here is\r
100 the pascal source you'll need to do the job:\r
101 \r
102 const SPEAKER_PORT = $61;\r
103        PIT_CONTROL = $43;\r
104      PIT_CHANNEL_2 = $42;\r
105           PIT_FREQ = $1234DD;\r
106 \r
107 procedure Sound(frequency : word);\r
108 var counter : word;\r
109 begin\r
110 \r
111   { Program the PIT chip }\r
112   counter := PIT_FREQ div frequency;\r
113   Port[PIT_CONTROL] := $B6;\r
114   Port[PIT_CHANNEL_2] := Lo(counter);\r
115   Port[PIT_CHANNEL_2] := Hi(counter);\r
116 \r
117   { Connect the speaker to the PIT }\r
118   Port[SPEAKER_PORT] := Port[SPEAKER_PORT] or 3;\r
119 end;\r
120 \r
121 procedure NoSound;\r
122 begin\r
123   Port[SPEAKER_PORT] := Port[SPEAKER_PORT] and $FC;\r
124 end;\r
125 \r
126 \r
127 ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÂÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ\r
128 ³ Playing 8-bit Sound Through the PC Speaker ³\r
129 ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ\r
130 \r
131 Terminolgy\r
132 ÄÄÄÄÄÄÄÄÄÄ\r
133 \r
134 To clear up any confusion, here's my own definition of some words I'll be\r
135 using in this section:\r
136 \r
137 sample : A single value in the range 0-255 representing the input level of\r
138          the microphone at any given moment.\r
139 \r
140 volume : A sort of generic version of sample, not limited to the 0-255 range.\r
141 \r
142   song : A bunch of samples in a row representing a continuous sound.\r
143 \r
144 string : A bunch of binary values (0-1) in a row.\r
145 \r
146 \r
147 \r
148 Programs like the legendary "Magic Mushroom" demo do a handly little trick\r
149 to play 8-bit sound from the PC speaker by sending binary strings to the\r
150 PC speaker for every sample they play. If the bits are all 0's, then the\r
151 speaker will be "off". If they are all 1's the speaker will be "on". If they\r
152 alternate 0's and 1's then the speaker will behave as if it's "half" on, and\r
153 so forth.\r
154 \r
155                 ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿\r
156                 ³ Bit string     Time speaker is on ³\r
157                 ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´\r
158                 ³ 11111111              100%        ³\r
159                 ³ 11101110               75%        ³\r
160                 ³ 10101010               50%        ³\r
161                 ³ 10001000               25%        ³\r
162                 ³ 00000000                0%        ³\r
163                 ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ\r
164 \r
165 Note that in this table I've used strings which are 8 bits long meaning that\r
166 there can only be 9 discrete volume levels (since anywhere from 0 to 8 of\r
167 them can be set to 1). In reality the strings would be longer.\r
168 \r
169 The problem with using bit strings such as this is getting accurate timing\r
170 between each bit you send. One way around this is to put all the 1's at the\r
171 front of the string and all the 0's at the end, like so:\r
172 \r
173                 ÚÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ¿\r
174                 ³ Bit string     Time speaker is on ³\r
175                 ÃÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ´\r
176                 ³ 11111111              100%        ³\r
177                 ³ 11111100               75%        ³\r
178                 ³ 11110000               50%        ³\r
179                 ³ 11000000               25%        ³\r
180                 ³ 00000000                0%        ³\r
181                 ÀÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÙ\r
182 \r
183 This way you can send all the 1's as a single pulse and your timing doesn't\r
184 have to be quite as accurate. The sound isn't quite as good, but I've found\r
185 it to be pretty reasonable. A real advantage in using this method is that\r
186 you can program the PIT chip for "interrupt on terminal count" mode, this\r
187 mode is similar to the one-shot mode, but counting starts as soon as you\r
188 load the PIT counter. So if you are playing an 11kHz song you simply load the\r
189 PIT counter 11000 times a second with a value that's proportional to the\r
190 sample value and trigger it. The speaker output will go low for the set time\r
191 and then remain high until the next time you trigger it (in practise it\r
192 doesn't matter whether the string of 1's make the speaker go "low" or\r
193 "high", just so long as it's consistent). I've managed to get good results\r
194 using PIT channel 2 to handle the one-shot for each sample and PIT channel 0\r
195 to handle when to trigger channel 2 (ie 11000 times a second). *PLUS* I was\r
196 able to have a program drawing stuff on the screen while all this was going\r
197 on in the background!\r
198 \r
199 Incidently I should mention here that the "interrupt on terminal count" mode\r
200 does not generate an actual interrupt on the Intel CPU. The mode was given\r
201 this name since the PIT can can be hooked up to a CPU to generate an\r
202 interrupt. As far as I can tell IBM didn't do it like this.\r
203 \r
204 This technique does have one nasty side-effect though. If you are playing an\r
205 11kHz tone for example then the PC speaker will be being turned on and off\r
206 exactly 11000 times a second, in other words you'll hear a nice 11kHz sine\r
207 wave superimposed over the song (do any of you math weirdo's want to do\r
208 a FFT to prove this for me?). A way around this is to play the song back\r
209 at 22kHz and play each sample twice. This will result in a 22kHz sine wave\r
210 which will pretty much be filtered out by the tiny PC speaker and the simple\r
211 low-pass filter circuit that it's usually connected to on the motherboard.\r
212 \r
213 The PIT chip runs at a frequency of 1193181 Hz (1234DDh). If you are playing\r
214 an 11kHz song at 22kHz then 1193181 / 22000 = 54 clocks per second, so\r
215 you'll have to program the PIT to count a maximum of 54 clocks for each\r
216 sample. What I'm getting at is that you'll only be able to play 54 discreet\r
217 sample levels using this method, so you'll have to scale the 256 different\r
218 levels in an 8-bit song to fit into this range which will also result in\r
219 futher loss of sound quality. I sped things up considerably by pre-\r
220 calculating a lookup table like so:\r
221 \r
222 var count_values : array[0..255] of byte;\r
223 \r
224 for level := 0 to 255 do\r
225   count_values[level] := level * 54 div 255;\r
226 \r
227 Then for each sample I just look up what it's counter value is and send\r
228 that to the PIT chip. Since each value is of byte size you can program the\r
229 PIT chip to accept the LSB only (see PIT.TXT for more info). The following\r
230 pascal code will set the PIT chip up for "interrupt on terminal count" mode\r
231 where only the LSB of the count needs to be loaded:\r
232 \r
233 Port[PIT_CONTROL] := $90;\r
234 Port[SPEAKER_PORT] := Port[SPEAKER_PORT] or 3;\r
235 \r
236 And the following line will trigger the one-shot for a given sample value\r
237 from 0-255:\r
238 \r
239 Port[PIT_CHANNEL_2] := count_values[sample_value];\r
240 \r
241 Do that 22000 times a second and whaddaya know, you'll hear "8-bit" sound\r
242 from your PC speaker! Here's a bit of code which works ok on my machine:\r
243 \r
244 \r
245 ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ\r
246 \r
247 const SPEAKER_PORT = $61;\r
248        PIT_CONTROL = $43;\r
249      PIT_CHANNEL_2 = $42;\r
250           PIT_FREQ = $1234DD;\r
251 \r
252      DELAY_LENGTH = 100;\r
253 \r
254 procedure PlaySound(sound : PChar; length : word);\r
255 var count_values : array[0..255] of byte;\r
256     i, loop : word;\r
257 begin\r
258 \r
259   { Set up the count table }\r
260   for i := 0 to 255 do\r
261     count_values[i] := i * 54 div 255;\r
262 \r
263   { Set up the PIT and connect the speaker to it }\r
264   Port[PIT_CONTROL] := $90;\r
265   Port[SPEAKER_PORT] := Port[SPEAKER_PORT] or 3;\r
266 \r
267   { Play the sound }\r
268 \r
269   asm cli end;\r
270   for i := 0 to length - 1 do\r
271     begin\r
272       Port[PIT_CHANNEL_2] := count_values[byte(sound^)];\r
273       for loop := 0 to DELAY_LENGTH do;\r
274       Port[PIT_CHANNEL_2] := count_values[byte(sound^)];\r
275       for loop := 0 to DELAY_LENGTH do;\r
276       sound := sound + 1;\r
277     end;\r
278   asm sti end;\r
279 \r
280   { Reprogram the speaker for normal operation }\r
281   Port[SPEAKER_PORT] := Port[SPEAKER_PORT] and $FC;\r
282   Port[PIT_CONTROL] := $B6;\r
283 end;\r
284 \r
285 ÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄÄ\r
286 \r
287 \r
288 Note that in this simple example I used a loop of DELAY_LENGTH to get the\r
289 timing between samples. I had to fiddle around to get the right value for my\r
290 machine and it varies from machine to machine. I also disable interrupts\r
291 while the inner loop is playing, otherwise you hear the 18.2Hz timer tick\r
292 while the sound was playing.\r
293 \r
294 \r
295 Both of these techniques suffer from two drawbacks. The first is that\r
296 samples played from the speaker do not sound very loud. You can make them\r
297 louder by making the song you are playing louder, but this eventually means\r
298 the sample values will start falling outside the 0-255 range and you'll have\r
299 to clip them which starts distorting the sound. The second problem is that\r
300 this technique doesn't work on the psezio-electric "speakers" inside lap-top\r
301 computers.\r
302 \r