|
|
1.1 root 1: /*
2: * UAE - The Un*x Amiga Emulator
3: *
4: * DOS Gravis Ultrasound interface.
5: *
6: * Version 0.3
7: *
8: * (c) 1996,1997 Michael Sontheimer
9: *
10: * Please, do not change the code ! Write me an email with the bugfix,
11: * the improvement or the suggestion.
12: * My email address: [email protected]
13: *
14: */
15:
16:
17: //#define DEBUG
18:
19: #include <stdlib.h>
20: #include <stdio.h>
21: #include <string.h>
22: #include <ctype.h>
23: #include <dos.h>
24: #include <pc.h>
25: #include <sys/farptr.h>
26: #include <dpmi.h>
27: #include <go32.h>
28:
29:
30: #include "sysconfig.h"
31: #include "sysdeps.h"
32: #include "sound/gus.h"
33: #include "sound/dma.h"
34:
35:
36:
37: #ifndef TRUE
38: #define TRUE 1
39: #endif
40: #ifndef FALSE
41: #define FALSE 0
42: #endif
43:
44:
45: // GUS Ports
46:
47: #define GUS_COMMAND 0x103
48: #define GUS_DATAHI 0x105
49: #define GUS_DATALO 0x104
50: #define GUS_IRQSTATE 0x006
51: #define GUS_TIMERCTRL 0x008
52: #define GUS_TIMERDATA 0x009
53: #define GUS_VOICE 0x102
54: #define GUS_DRAM 0x107
55: #define GUS_MIXER 0x000
56: #define GUS_DMACTRL 0x00b // Beide Adressen sind identisch !!!
57: #define GUS_IRQCTRL 0x00b
58: #define GUS_REGISTERCTRL 0x00f
59:
60: // GUS Kommandos
61:
62: #define GUS_CMD_RESET 0x4c
63: #define GUS_CMD_DRAMDMACTRL 0x41
64: #define GUS_CMD_DMASTARTADR 0x42
65: #define GUS_CMD_DRAMIOADRLO 0x43
66: #define GUS_CMD_DRAMIOADRHI 0x44
67: #define GUS_CMD_TIMERCTRL 0x45
68: #define GUS_CMD_COUNTER1 0x46
69: #define GUS_CMD_COUNTER2 0x47
70: #define GUS_CMD_SAMPLEFREQ 0x48
71: #define GUS_CMD_SAMPLECTRL 0x49
72:
73: #define GUS_CMD_VOICECTRLW 0x00
74: #define GUS_CMD_FREQUCTRLW 0x01
75: #define GUS_CMD_STARTHIW 0x02
76: #define GUS_CMD_STARTLOW 0x03
77: #define GUS_CMD_ENDHIW 0x04
78: #define GUS_CMD_ENDLOW 0x05
79: #define GUS_CMD_RAMPRATEW 0x06
80: #define GUS_CMD_VOLUMEW 0x09
81: #define GUS_CMD_CURRENTHIW 0x0a
82: #define GUS_CMD_CURRENTLOW 0x0b
83: #define GUS_CMD_PANPOSW 0x0c
84: #define GUS_CMD_VOLUMECTRLW 0x0d
85: #define GUS_CMD_ACTIVEVOICESW 0x0e
86:
87: #define GUS_CMD_VOICECTRLR 0x80
88: #define GUS_CMD_FREQUCTRLR 0x81
89: #define GUS_CMD_STARTHIR 0x82
90: #define GUS_CMD_STARTLOR 0x83
91: #define GUS_CMD_ENDHIR 0x84
92: #define GUS_CMD_ENDLOR 0x85
93: #define GUS_CMD_VOLUMER 0x89
94: #define GUS_CMD_CURRENTHIR 0x8a
95: #define GUS_CMD_CURRENTLOR 0x8b
96: #define GUS_CMD_PANPOSR 0x8c
97: #define GUS_CMD_VOLUMECTRLR 0x8d
98: #define GUS_CMD_ACTIVEVOICESR 0x8e
99: #define GUS_CMD_IRQSOURCER 0x8f
100:
101: #define ADDR_HI(x) ((UWORD)((((ULONG)(x))>>7L)&0x1fffL))
102: #define ADDR_LO(x) ((UWORD)((((ULONG)(x))&0x7fL)<<9L))
103:
104:
105: #define LSW( x ) ((UWORD)(((ULONG)(x))&0xffffL))
106: #define MSW( x ) ((UWORD)(((ULONG)(x))>>16L))
107: #define LSB( x ) ((UBYTE)(((UWORD)(x))&0xffL))
108: #define MSB( x ) ((UBYTE)(((UWORD)(x))>>8))
109: #define CONVTO16B( x ) ((((x)>>1L)&0x0001ffffL)|((x)&0x000c0000L))
110: #define NUMPARAS(bytesize) (((bytesize)+15) >> 4)
111:
112:
113:
114: // Variablen
115:
116:
117: UWORD uwGUSPort;
118: UBYTE ubGUSGF1Int;
119: UBYTE ubGUSMIDIInt;
120: UBYTE ubGUSDMA1;
121: UBYTE ubGUSDMA2;
122: UBYTE ubGUSUsedBits; // 0 if 8 bit or 4 if 16 bit
123: UWORD uwGUSUsedFreq;
124: UBYTE ubGUSGF1IRQVector; // Physikalischer Vektor des GF1 IRQ's
125: UBYTE *pGUSBuffer; // Zwischenpuffer
126: ULONG nGUSBufferWriteIndex;
127: ULONG nGUSBufferReadIndex;
128: ULONG ulBytesInBuffer; // Anzahl der Bytes im Puffer
129: ULONG ulGUSSndBufSize;
130: ULONG ulDMABufMaxSize;
131:
132:
133: int bIgnoreGF1IRQ=TRUE;
134: int bDMAInProgress=FALSE;
135: int bWarnFlag=TRUE; // Flag, ob Warnungen ausgeben werden sollen
136: int nWarnCnt=0;
137: #define MAXWARNS 5 // Anzahl der maximalen Warnungen
138:
139: _go32_dpmi_seginfo old_pmintGF1; /* the old interrupt handler */
140: _go32_dpmi_seginfo pm_wrapperGF1; /* the new interrupt handler */
141:
142: UBYTE ubDummy;
143:
144:
145:
146:
147: static __inline__ void WriteGUSb( UWORD uwReg, UBYTE ubByte )
148: {
149: outportb( uwGUSPort+uwReg, ubByte );
150: }
151:
152: static __inline__ UBYTE ReadGUSb( UWORD uwReg )
153: {
154: return inportb( uwGUSPort+uwReg );
155: }
156:
157: static __inline__ void WriteGUSw( UWORD uwReg, UWORD uwWord )
158: {
159: outportw( uwGUSPort+uwReg, uwWord );
160: }
161:
162: static __inline__ UWORD ReadGUSw( UWORD uwReg )
163: {
164: return inportw( uwGUSPort+uwReg );
165: }
166:
167:
168:
169: _go32_dpmi_seginfo dma_mem; // Zeiger auf DMA-Speicher
170: ULONG dmabufadr;
171:
172: void GUS_GF1Delay( void )
173: {
174:
175: int i;
176:
177: for( i=0; i<6; i++ )
178: inportb( uwGUSPort+GUS_DATAHI );
179:
180:
181: }
182:
183: /*******************************
184:
185: void GUS_Reset( UBYTE ubGUSDMA1, UBYTE ubGUSDMA2, ubGUSGF1Int )
186:
187: *******************************/
188:
189: void GUS_Reset( UBYTE ubGUSDMA1, UBYTE ubGUSDMA2, UBYTE ubGUSGF1Int )
190: {
191: int i, j;
192: UBYTE nDMA, nInt;
193:
194: WriteGUSb( GUS_MIXER, 1|2 ); // Latches aus, Alle Aus/Eingaben aus
195:
196: // Reset
197: WriteGUSb( GUS_COMMAND, GUS_CMD_RESET );
198: WriteGUSb( GUS_DATAHI, 0 );
199: delay( 100 );
200: WriteGUSb( GUS_COMMAND, GUS_CMD_RESET );
201: WriteGUSb( GUS_DATAHI, 1 );
202: delay( 100 );
203:
204: // DMA Control Register reset
205: WriteGUSb( GUS_COMMAND, GUS_CMD_DRAMDMACTRL );
206: WriteGUSb( GUS_DATAHI, 0 );
207: delay( 10 );
208:
209: // Timer Control Register
210: WriteGUSb( GUS_COMMAND, GUS_CMD_TIMERCTRL );
211: WriteGUSb( GUS_DATAHI, 0 );
212: delay( 10 );
213:
214: // Sampling Control Register
215: WriteGUSb( GUS_COMMAND, GUS_CMD_SAMPLECTRL );
216: WriteGUSb( GUS_DATAHI, 0 );
217: delay( 10 );
218:
219: // Number of Voices
220: WriteGUSb( GUS_COMMAND, GUS_CMD_ACTIVEVOICESW );
221: WriteGUSb( GUS_DATAHI, 0xc0 | (15-1) ); // 15 Voices (left/right)
222:
223: // Evtl. anstehende DMA-IRQ's holen
224: ReadGUSb( GUS_IRQSTATE );
225: WriteGUSb( GUS_COMMAND, GUS_CMD_DRAMDMACTRL );
226: ReadGUSb( GUS_DATAHI );
227:
228: // Evtl. Sampleinterrupts lesen
229: WriteGUSb( GUS_COMMAND, GUS_CMD_SAMPLECTRL );
230: ReadGUSb( GUS_DATAHI );
231:
232: // IRQ-Status lesen
233: WriteGUSb( GUS_COMMAND, GUS_CMD_IRQSOURCER );
234: ReadGUSb( GUS_DATAHI );
235:
236: // Alle Stimmen abschalten
237:
238: for( i=0; i<32; i++ )
239: {
240: WriteGUSb( GUS_VOICE, (UBYTE)i );
241: WriteGUSb( GUS_COMMAND, GUS_CMD_VOICECTRLW );
242: WriteGUSb( GUS_DATAHI, 3 ); // Stimme stoppen
243: WriteGUSb( GUS_COMMAND, GUS_CMD_VOLUMECTRLW );
244: WriteGUSb( GUS_DATAHI, 3 ); // Lautstaerke 0
245: WriteGUSb( GUS_COMMAND, GUS_CMD_RAMPRATEW );
246: WriteGUSb( GUS_DATAHI, 0 ); // Ramp 0
247:
248: }
249:
250: // Evtl. anstehende DMA-IRQ's holen
251: ReadGUSb( GUS_IRQSTATE );
252: WriteGUSb( GUS_COMMAND, GUS_CMD_DRAMDMACTRL );
253: ReadGUSb( GUS_DATAHI );
254:
255: // Evtl. Sampleinterrupts lesen
256: WriteGUSb( GUS_COMMAND, GUS_CMD_SAMPLECTRL );
257: ReadGUSb( GUS_DATAHI );
258:
259: // IRQ-Status lesen
260: WriteGUSb( GUS_COMMAND, GUS_CMD_IRQSOURCER );
261: ReadGUSb( GUS_DATAHI );
262:
263:
264: // Reset beenden und GF1 Master IRQ einschalten
265: WriteGUSb( GUS_COMMAND, GUS_CMD_RESET );
266: WriteGUSb( GUS_DATAHI, 7 );
267:
268:
269: if( ubGUSDMA1 == 0 ) // IRQ's und DMA einstellen ?
270: return; // Nein, dann Ende
271:
272:
273: // Digital ASIC einstellen
274:
275: WriteGUSb( GUS_REGISTERCTRL, 5 ); // IRQ's-Register waehlen
276: WriteGUSb( GUS_MIXER, 1|2 ); // Alles Abschalten
277: WriteGUSb( GUS_IRQCTRL, 0 ); // IRQ's loeschen
278: WriteGUSb( GUS_REGISTERCTRL, 0 ); // Alte Registerkonfig wieder einschalten
279:
280: // DMA selektieren
281:
282: nDMA=0;
283: for( j=0; j<2; j++ )
284: {
285: switch( ((j!=0)?(ubGUSDMA1):(ubGUSDMA2)) )
286: {
287: case 1:
288: nDMA |= 1;
289: break;
290: case 3:
291: nDMA |= 2;
292: break;
293: case 5:
294: nDMA |= 3;
295: break;
296: case 6:
297: nDMA |= 4;
298: break;
299: case 7:
300: nDMA |= 5;
301: break;
302: }
303: if( j==0 )
304: nDMA=nDMA<<3;
305: }
306:
307: if( ubGUSDMA1 == ubGUSDMA2 ) // Gleichen DMA fuer beides ?
308: nDMA = (nDMA&0x3)|(1<<6);
309:
310:
311: // Interrupt der GUS setzen
312:
313: nInt=0;
314: for( j=0; j<2; j++ )
315: {
316: switch( (j!=0)?(ubGUSGF1Int):(ubGUSMIDIInt) )
317: {
318: case 2:
319: nInt |= 1;
320: break;
321: case 5:
322: nInt |= 2;
323: break;
324: case 3:
325: nInt |= 3;
326: break;
327: case 7:
328: nInt |= 4;
329: break;
330: case 11:
331: nInt |= 5;
332: break;
333: case 12:
334: nInt |= 6;
335: break;
336: case 15:
337: nInt |= 7;
338: break;
339: }
340: if( j==0 )
341: nInt=nInt<<3;
342: }
343:
344: if( ubGUSGF1Int == ubGUSMIDIInt )
345: nInt = (nInt&0x3)|(1<<6);
346:
347:
348:
349: disable();
350:
351: WriteGUSb( GUS_MIXER, 1|2 ); // DMA-Einstellung aktivieren
352: WriteGUSb( GUS_DMACTRL, nDMA | 0x80 ); // DMA-Kanal selektieren, welcher im SET angegebe wurde
353:
354:
355: WriteGUSb( GUS_MIXER, 1|2|64 ); // IRQ's selektieren
356: WriteGUSb( GUS_IRQCTRL, nInt );
357:
358: WriteGUSb( GUS_MIXER, 1|2 ); // DMA-Einstellung aktivieren
359: WriteGUSb( GUS_DMACTRL, nDMA ); // DMA-Kanal selektieren, welcher im SET angegebe wurde
360:
361: WriteGUSb( GUS_MIXER, 1|2|64 ); // IRQ's selektieren
362: WriteGUSb( GUS_IRQCTRL, nInt );
363:
364: enable();
365:
366: // IRQ/DMA locken
367:
368: WriteGUSb( GUS_VOICE, 0 );
369:
370: WriteGUSb( GUS_MIXER, 8 | ((ubGUSGF1Int==ubGUSMIDIInt)?16:0 ) );
371:
372: WriteGUSb( GUS_VOICE, 0 );
373:
374:
375:
376: }
377:
378:
379:
380: /*******************************
381:
382: int GUS_Init( void )
383:
384: *******************************/
385:
386: int GUS_Init( int *dspbits, int *rate, int *sndbufsize, unsigned int *direct_buffers, int *stereo_sound )
387: {
388:
389: char *szGUSENVStr, *pz;
390: int i, j;
391: extern void (*SND_Write)(void *buf, unsigned long size);
392:
393:
394:
395: szGUSENVStr = getenv( "ULTRASND" );
396: if( szGUSENVStr == 0 )
397: return 0;
398:
399:
400: if( (*dspbits != 8 ) && (*dspbits != 16 ) )
401: {
402: printf( "%d bits are not supported on the GUS !\n", dspbits );
403: return 0;
404: }
405:
406:
407: ubGUSUsedBits = (*dspbits==8)?0:4;
408:
409: if( (*rate < 1000) || (*rate > 44100 ))
410: {
411: fprintf( stderr, "%dHz are not supported on the GUS !\n", *rate );
412: return 0;
413: }
414:
415: uwGUSUsedFreq = *rate;
416:
417:
418:
419: ulDMABufMaxSize = 8192;
420: ulGUSSndBufSize = ((ULONG)*sndbufsize)*32;
421:
422:
423:
424: fprintf( stderr, "GUS configured for %d bits, %d Hz\n", *dspbits, *rate );
425: fprintf( stderr, "Soundbuffersize: %d bytes\n", ulGUSSndBufSize );
426: fprintf( stderr, "DMA-buffersize: %d bytes\n", ulDMABufMaxSize );
427: fprintf( stderr, "Fragmentsize: %d bytes\n", *sndbufsize );
428:
429:
430: uwGUSPort = (UWORD)strtol( szGUSENVStr, (char **)NULL, 16 );
431: pz = strchr( szGUSENVStr, ',' );
432: if( (pz == 0) || (uwGUSPort ==0) )
433: return 0;
434:
435: ubGUSDMA1 = (UBYTE)atol( pz+1 );
436: pz = strchr( pz+1, ',' );
437: if( pz == 0 )
438: return 0;
439:
440: ubGUSDMA2 = (UBYTE)atol( pz+1 );
441:
442: pz = strchr( pz+1, ',' );
443: if( pz == 0 )
444: return 0;
445:
446: ubGUSGF1Int = (UBYTE)atol( pz+1 );
447: if( ubGUSGF1Int == 0 )
448: return 0;
449:
450: pz = strchr( pz+1, ',' );
451: if( pz == 0 )
452: return 0;
453:
454: ubGUSMIDIInt = (UBYTE)atol( pz+1 );
455: if( ubGUSMIDIInt == 0 )
456: return 0;
457:
458: // Speicher fuer DMA holen
459:
460:
461: memset( &dma_mem, 0, sizeof( _go32_dpmi_seginfo ) );
462: dma_mem.size = NUMPARAS( ulDMABufMaxSize+1000 );
463: dmabufadr = DMA_AllocDMABuf( &dma_mem );
464:
465:
466: _go32_dpmi_lock_code( GUS_PlayBuffer, (ULONG)GUS_DummyFunc - (ULONG)GUS_PlayBuffer);
467: _go32_dpmi_lock_data( &uwGUSPort, (ULONG)&ubDummy - (ULONG)&uwGUSPort);
468:
469:
470: if( dmabufadr == NULL)
471: {
472: fprintf( stderr, "Cannot allocate DOS memory for DMA buffers!\n" );
473: return 0;
474: }
475:
476:
477: // Zwischenpuffer allokieren
478:
479: pGUSBuffer = (UBYTE *)malloc( ulGUSSndBufSize+1000 );
480: if( pGUSBuffer == 0 )
481: {
482: fprintf( stderr, "Cannot allocate soundbuffer !\n" );
483: return 0;
484: }
485:
486: nGUSBufferWriteIndex = nGUSBufferReadIndex = 0;
487: ulBytesInBuffer = 0;
488:
489: // GUS-Reset
490:
491: GUS_Reset( ubGUSDMA1, ubGUSDMA2, ubGUSGF1Int );
492:
493:
494: // Test, ob wir in das GUS-RAM etwas schreiben koennen
495:
496: WriteGUSb( GUS_COMMAND, GUS_CMD_DRAMIOADRLO );
497: WriteGUSw( GUS_DATALO, 0 );
498: WriteGUSb( GUS_COMMAND, GUS_CMD_DRAMIOADRHI );
499: WriteGUSw( GUS_DATAHI, 0 );
500:
501: WriteGUSb( GUS_DRAM, 0x15 );
502: delay( 100 );
503: if( ReadGUSb( GUS_DRAM ) != 0x15 ) // Bytes unterschiedlich ?
504: return 0;
505:
506: // GUS-RAM loeschen
507:
508: j=-1;
509: for( i=0; i<(int)(ulDMABufMaxSize*2+1000); i++ )
510: {
511: if( j != MSW(i) ) // Muss das Highword veraendert werden ?
512: {
513: j = MSW(i);
514:
515: WriteGUSb( GUS_COMMAND, GUS_CMD_DRAMIOADRHI );
516: WriteGUSw( GUS_DATALO, MSW( i ) );
517: }
518:
519: WriteGUSb( GUS_COMMAND, GUS_CMD_DRAMIOADRLO );
520: WriteGUSw( GUS_DATALO, LSW( i ) );
521:
522: WriteGUSb( GUS_DRAM, 0 );
523:
524: }
525:
526:
527: // Interrupthandler installieren
528:
529: memset( &pm_wrapperGF1, 0, sizeof( _go32_dpmi_seginfo ) );
530: pm_wrapperGF1.pm_offset = (int) GUS_GF1InterruptHandler;
531: if(_go32_dpmi_allocate_iret_wrapper( &pm_wrapperGF1 ) )
532: {
533: fprintf( stderr, "Cannot allocate protected mode wrapper for GUS interrupt!\n");
534: if( dmabufadr != 0 )
535: _go32_dpmi_free_dos_memory( &dma_mem );
536:
537: return 0;
538: }
539:
540:
541: ubGUSGF1IRQVector = (ubGUSGF1Int>7)?(ubGUSGF1Int+0x68):(ubGUSGF1Int+0x08); // Physik. Vektor berechnen
542:
543: _go32_dpmi_get_protected_mode_interrupt_vector( ubGUSGF1IRQVector, &old_pmintGF1 ); // Alten Interrupt merken
544: _go32_dpmi_set_protected_mode_interrupt_vector( ubGUSGF1IRQVector, &pm_wrapperGF1 ); // Neuen Interrupt merken
545:
546:
547: // Interrupt erlauben
548:
549: if( ubGUSGF1Int > 7 )
550: {
551: outportb( 0xA1, inportb( 0xA1 ) & ~(1 << ( ubGUSGF1Int-8 ) ) );
552: outportb( 0xA0, 0x20 );
553: }
554: else
555: {
556: outportb( 0x21, inportb( 0x21 ) & ~(1 << ubGUSGF1Int));
557: outportb( 0x20, 0x20 );
558: }
559:
560:
561: // Hier wird die Stimme zum erstenmal gestertet
562:
563:
564: WriteGUSb( GUS_VOICE, 0 ); // Stimme0
565:
566: WriteGUSb( GUS_COMMAND, GUS_CMD_VOLUMEW ); // Lautstaerke auf Maximum
567: WriteGUSw( GUS_DATALO, 61247 );
568:
569: WriteGUSb( GUS_COMMAND, GUS_CMD_FREQUCTRLW ); // 15 Stimmen = x / 40!
570:
571: // WriteGUSw( GUS_DATALO, (UWORD)((((((((ULONG)uwGUSUsedFreq)/1000L)<<9L)+(44100L>>1L))/44100L)<<1L)) );
572: WriteGUSw( GUS_DATALO, uwGUSUsedFreq/40 );
573:
574:
575: WriteGUSb( GUS_COMMAND, GUS_CMD_CURRENTLOW ); // Current
576: WriteGUSw( GUS_DATALO, 32 );
577: WriteGUSb( GUS_COMMAND, GUS_CMD_CURRENTHIW );
578: WriteGUSw( GUS_DATALO, 0 );
579:
580: WriteGUSb( GUS_COMMAND, GUS_CMD_STARTHIW ); // Start
581: WriteGUSw( GUS_DATALO, 0 );
582: WriteGUSb( GUS_COMMAND, GUS_CMD_STARTLOW );
583: WriteGUSw( GUS_DATALO, 32 );
584:
585: if( ubGUSUsedBits == 0 ) // 8 oder 16 Bit ?
586: {
587: WriteGUSb( GUS_COMMAND, GUS_CMD_ENDHIW ); // Ende setzen
588: WriteGUSw( GUS_DATALO, ADDR_HI( ulDMABufMaxSize+32 ) );
589: WriteGUSb( GUS_COMMAND, GUS_CMD_ENDLOW );
590: WriteGUSw( GUS_DATALO, ADDR_LO( ulDMABufMaxSize+32 ) );
591: }
592: else
593: {
594: WriteGUSb( GUS_COMMAND, GUS_CMD_ENDHIW ); // Ende
595: WriteGUSw( GUS_DATALO, ADDR_HI( CONVTO16B( ulDMABufMaxSize+32 ) ) );
596: WriteGUSb( GUS_COMMAND, GUS_CMD_ENDLOW );
597: WriteGUSw( GUS_DATALO, ADDR_LO( CONVTO16B( ulDMABufMaxSize+32 ) ) );
598: }
599:
600: WriteGUSb( GUS_COMMAND, GUS_CMD_VOLUMECTRLW );
601: WriteGUSb( GUS_DATAHI, 3|4 );
602: GUS_GF1Delay();
603: WriteGUSb( GUS_DATAHI, 3|4 ); // Rollover an
604:
605:
606: WriteGUSb( GUS_COMMAND, GUS_CMD_VOICECTRLW );
607: WriteGUSb( GUS_DATAHI, 32|ubGUSUsedBits );
608: GUS_GF1Delay();
609: WriteGUSb( GUS_DATAHI, 32|ubGUSUsedBits ); // Stimme starten, Loop aus
610:
611:
612: bIgnoreGF1IRQ = FALSE;
613:
614:
615: // GUS-Initialisierung beendet, puhhh
616:
617: fprintf( stderr, "GUS found at port: %Xh, dma1: %d, dma2: %d, GF1-int: %d, MIDI-int: %d\n", uwGUSPort, ubGUSDMA1, ubGUSDMA2, ubGUSGF1Int, ubGUSMIDIInt );
618:
619:
620: atexit( GUS_Cleanup );
621:
622:
623: SND_Write = GUS_PlayBuffer; // Zeiger auf meine Abspielroutine eintragen
624: direct_buffers[0] = 0; // Direct access not supported
625: direct_buffers[1] = 0;
626: *stereo_sound = 0;
627:
628:
629: printf( "First ....(%ld,%X)\n", ulBytesInBuffer, &ulBytesInBuffer );
630:
631:
632: return 1;
633:
634: }
635:
636:
637: /*******************************
638:
639: int GUS_Cleanup( void )
640:
641: *******************************/
642:
643:
644: void GUS_Cleanup( void )
645: {
646:
647:
648: fprintf( stderr, "Freeing up all GUS-stuff\n" );
649:
650: // Warten, bis DMA beendet ist
651:
652: if( bDMAInProgress == TRUE )
653: {
654: do
655: {
656: delay( 10 );
657: }
658: while( bDMAInProgress == TRUE );
659: }
660:
661:
662: // Stimmen anhalten
663:
664: WriteGUSb( GUS_VOICE, 0 );
665:
666: WriteGUSb( GUS_COMMAND, GUS_CMD_VOLUMECTRLW );
667: WriteGUSb( GUS_DATAHI, 3 );
668: GUS_GF1Delay();
669: WriteGUSb( GUS_DATAHI, 3 );
670:
671:
672: WriteGUSb( GUS_COMMAND, GUS_CMD_VOICECTRLW );
673: WriteGUSb( GUS_DATAHI, 3 );
674: GUS_GF1Delay();
675: WriteGUSb( GUS_DATAHI, 3 );
676:
677: // GUS resetten
678:
679: GUS_Reset( 0, 0, 0 );
680:
681: // Interrupts wieder sperren
682:
683: if( ubGUSGF1Int > 7 )
684: outportb (0xA1, inportb(0xA1) | (1 << (ubGUSGF1Int-8)));
685: else
686: outportb (0x21, inportb(0x21) | (1 << ubGUSGF1Int));
687:
688: _go32_dpmi_set_protected_mode_interrupt_vector( ubGUSGF1IRQVector, &old_pmintGF1 );
689: _go32_dpmi_free_iret_wrapper( &pm_wrapperGF1 );
690:
691:
692: // Speicher freigeben
693:
694: if( pGUSBuffer != 0 )
695: free( pGUSBuffer );
696:
697: if( dmabufadr != 0 )
698: _go32_dpmi_free_dos_memory( &dma_mem ); // Speicher fuer DMA freigeben
699:
700: GUS_Reset( 0, 0, 0 );
701:
702: }
703:
704: /*******************************
705:
706: void GUS_PlayBuffer( void *buf, unsigned long size )
707:
708: *******************************/
709:
710:
711: // Diese Routine wird vom UAE aufgerufen, wenn der Soundbuffer
712: // zu leeren ist
713:
714:
715: void GUS_PlayBuffer( void *buf, unsigned long size )
716: {
717: ULONG i, j;
718:
719:
720: j=0;
721: while( ulBytesInBuffer>ulDMABufMaxSize*2 );
722: /*
723: {
724: fprintf( stderr, "Waiting ....(%ld,%X)\n", ulBytesInBuffer, &ulBytesInBuffer );
725: fflush( stderr );
726: j++;
727: if( j>50 )
728: {
729: fprintf( stderr, "Breche ab ...\n" );
730: fflush( stderr );
731: break;
732: }
733:
734: } */
735:
736: disable();
737:
738: // Daten in den GUS-Buffer kopieren
739:
740: if( size+ulBytesInBuffer>ulGUSSndBufSize )
741: {
742: if( bWarnFlag == TRUE )
743: {
744: fprintf( stderr, "Error: GUS-soundbuffer overflow !\n" );
745: nWarnCnt++;
746: if( nWarnCnt>MAXWARNS )
747: {
748: bWarnFlag = FALSE;
749: fprintf( stderr, "GUS: No further messages !\n" );
750: }
751: }
752:
753: }
754: else
755: {
756: // GUS-Buffer fuellen
757:
758: // Koennen wir auf einmal kopieren ?
759:
760: i = ulGUSSndBufSize-nGUSBufferWriteIndex;
761: if( i < size )
762: {
763: memcpy( pGUSBuffer+nGUSBufferWriteIndex, buf, i );
764:
765: memcpy( pGUSBuffer, ((UBYTE*)buf)+i, size-i );
766:
767: }
768: else
769: {
770: memcpy( pGUSBuffer+nGUSBufferWriteIndex, buf, size );
771: }
772:
773: nGUSBufferWriteIndex = (nGUSBufferWriteIndex+size)&(ulGUSSndBufSize-1);
774:
775: ulBytesInBuffer += size;
776:
777: }
778:
779: enable();
780:
781: }
782:
783:
784: /*******************************
785:
786: void GUS_StartDMA( ULONG ulPosInGUSRAM )
787:
788: *******************************/
789:
790:
791:
792: void GUS_StartDMA( ULONG ulPosInGUSRAM, int bLoop )
793: {
794:
795: ULONG i, pDMABuf, j;
796: ULONG ulTemp;
797:
798:
799: // I hope, everything is aligned to 4 byte (32bit), otherwise
800: // the machine my crash :(
801:
802:
803: if( bDMAInProgress == TRUE )
804: {
805: fprintf( stderr, "Error in StartDMA, DMA already im progress !\n" );
806: return;
807: }
808:
809: bDMAInProgress=TRUE;
810:
811: // Kopiert die Daten aus dem Soundbuffer in den DMA-Puffer
812:
813:
814: _farsetsel( _dos_ds );
815:
816: if( ulBytesInBuffer == 0 ) // Sind ueberhaupt Daten im Puffer ?
817: { // Nein, dann DMA-Buffer loeschen
818: pDMABuf = dmabufadr;
819:
820: if( ubGUSUsedBits == 0 ) // 8 Bit ?
821: ulTemp = 0x80808080UL;
822: else
823: ulTemp = 0;
824:
825: for( i=ulDMABufMaxSize/4; i>0; i-- )
826: {
827: _farnspokel( pDMABuf, ulTemp );
828: pDMABuf+=4;
829: }
830:
831: }
832: else
833: {
834: if( ulBytesInBuffer >= ulDMABufMaxSize ) // Sind mehr Daten da, als
835: { // der Puffer gross ist ?
836:
837: pDMABuf = dmabufadr; // Ja, DMA-Puffer vollmachen
838:
839: j = nGUSBufferReadIndex;
840: for( i=ulDMABufMaxSize/4; i>0; i-- )
841: {
842: _farnspokel( pDMABuf, *((ULONG*)(pGUSBuffer+j)) ); // 32 bit-copy
843: pDMABuf+=4;
844: j = (j+4)&(ulGUSSndBufSize-1);
845: }
846:
847: nGUSBufferReadIndex = (nGUSBufferReadIndex+ulDMABufMaxSize)&(ulGUSSndBufSize-1);
848:
849: ulBytesInBuffer -= ulDMABufMaxSize;
850: }
851: else
852: {
853: pDMABuf = dmabufadr; // Nein, nur den vorhanden Teil
854: // kopieren
855:
856: j = nGUSBufferReadIndex;
857: for( i=ulBytesInBuffer/4; i>0; i-- )
858: {
859: _farnspokel( pDMABuf, *((ULONG*)(pGUSBuffer+j)) );
860: pDMABuf+=4;
861: j = (j+4)&(ulGUSSndBufSize-1);
862: }
863: nGUSBufferReadIndex = (nGUSBufferReadIndex+ulBytesInBuffer)&(ulGUSSndBufSize-1);
864:
865:
866: if( ubGUSUsedBits == 0 ) // 8 Bit ?
867: ulTemp = 0x80808080UL;
868: else
869: ulTemp = 0;
870:
871:
872: for( i=(ulDMABufMaxSize-ulBytesInBuffer)/4; i>0; i-- )
873: {
874: _farnspokel( pDMABuf, ulTemp );
875: pDMABuf+=4;
876: }
877:
878:
879: ulBytesInBuffer = 0;
880: }
881:
882: }
883:
884:
885: if( bLoop == TRUE ) // Byte/Word kopieren, um das Geklicke zu verhindern
886: {
887:
888: if( ubGUSUsedBits == 0 ) // 8 Bit ?
889: {
890:
891: WriteGUSb( GUS_COMMAND, GUS_CMD_DRAMIOADRHI );
892: WriteGUSw( GUS_DATALO, MSW( ulPosInGUSRAM-ulDMABufMaxSize-1 ) );
893:
894: WriteGUSb( GUS_COMMAND, GUS_CMD_DRAMIOADRLO );
895: WriteGUSw( GUS_DATALO, LSW( ulPosInGUSRAM-ulDMABufMaxSize-1 ) );
896:
897: WriteGUSb( GUS_DRAM, _farnspeekb( dmabufadr+ulDMABufMaxSize-1 )+0x80 );
898:
899: }
900: else
901: {
902: WriteGUSb( GUS_COMMAND, GUS_CMD_DRAMIOADRHI );
903: WriteGUSw( GUS_DATALO, MSW( ulPosInGUSRAM-ulDMABufMaxSize-2 ) );
904:
905: WriteGUSb( GUS_COMMAND, GUS_CMD_DRAMIOADRLO );
906: WriteGUSw( GUS_DATALO, LSW( ulPosInGUSRAM-ulDMABufMaxSize-2 ) );
907:
908: WriteGUSb( GUS_DRAM, _farnspeekb( dmabufadr+ulDMABufMaxSize-2 ) );
909:
910: WriteGUSb( GUS_COMMAND, GUS_CMD_DRAMIOADRHI );
911: WriteGUSw( GUS_DATALO, MSW( ulPosInGUSRAM-ulDMABufMaxSize-1 ) );
912:
913: WriteGUSb( GUS_COMMAND, GUS_CMD_DRAMIOADRLO );
914: WriteGUSw( GUS_DATALO, LSW( ulPosInGUSRAM-ulDMABufMaxSize-1 ) );
915:
916: WriteGUSb( GUS_DRAM, _farnspeekb( dmabufadr+ulDMABufMaxSize-1 ) );
917:
918: }
919:
920: }
921:
922:
923: // DMA-Controller initialisieren
924: DMA_InitTransfer( ubGUSDMA1, DMA_READ, dmabufadr, ulDMABufMaxSize );
925:
926: // GUS-Register initialisieren
927: WriteGUSb( GUS_COMMAND, GUS_CMD_DMASTARTADR );
928: if( ubGUSDMA1>3 )
929: WriteGUSw( GUS_DATALO, CONVTO16B(ulPosInGUSRAM>>4) ); // Adresse>>4 !!!!
930: else
931: WriteGUSw( GUS_DATALO, (UWORD)(ulPosInGUSRAM>>4) ); // Adresse>>4 !!!!
932:
933: if( ubGUSUsedBits == 0 )
934: { // 8 Bit
935: if( ubGUSDMA1>3 )
936: {
937: WriteGUSb( GUS_COMMAND, GUS_CMD_DRAMDMACTRL );
938: WriteGUSb( GUS_DATAHI, 0x85 | 0x20 ); // Mit signed->unsigned kopieren
939:
940: }
941: else
942: {
943: WriteGUSb( GUS_COMMAND, GUS_CMD_DRAMDMACTRL );
944: WriteGUSb( GUS_DATAHI, 0x81 | 0x20 );
945: }
946: }
947: else
948: { // 16 Bit
949: if( ubGUSDMA1>3 )
950: {
951: WriteGUSb( GUS_COMMAND, GUS_CMD_DRAMDMACTRL );
952: WriteGUSb( GUS_DATAHI, 0x05 | 0x20 ); // Ohne signed->unsigned kopieren
953:
954: }
955: else
956: {
957: WriteGUSb( GUS_COMMAND, GUS_CMD_DRAMDMACTRL );
958: WriteGUSb( GUS_DATAHI, 0x01 | 0x20 );
959: }
960:
961:
962: }
963:
964: }
965:
966:
967:
968: /*******************************
969:
970: void GUS_WorkOnVoices( void )
971:
972: *******************************/
973:
974:
975: // Hier werden die Interrupts der einzelnen Stimmen bearbeitet
976:
977: void GUS_WorkOnVoices( void )
978: {
979:
980: UBYTE ubIntArt, ubTemp1, ubTemp2;
981: int i, nVoice, nVoiceBit, nWaveIgnore=0, nVolumeIgnore=0;
982: int nIntCounts=0;
983:
984: // Alle anstehenden Interrupts bearbeiten
985:
986: for(;;)
987: {
988:
989:
990: WriteGUSb( GUS_COMMAND, GUS_CMD_IRQSOURCER ); // IRQ-Art holen
991: ubIntArt = ReadGUSb( GUS_DATAHI );
992:
993: nVoice = ubIntArt & 0x1f;
994: nVoiceBit = ((ULONG)1)<<((ULONG)nVoice);
995:
996: // Alle Interrupts abgearbeitet ?
997: if( ( ubIntArt & 192) == 192 ) // Negative Logik !!!!
998: {
999: break;
1000: }
1001:
1002: nIntCounts++;
1003:
1004: if( nIntCounts>3 )
1005: {
1006: #ifdef DEBUG
1007: fprintf( stderr, "!!!! Breche Int. ab, da mehr als 3 Anfragen !!!\n" );
1008: #endif
1009:
1010: WriteGUSb( GUS_COMMAND, GUS_CMD_VOLUMECTRLW );
1011: WriteGUSb( GUS_DATAHI, 3 );
1012: GUS_GF1Delay();
1013: WriteGUSb( GUS_DATAHI, 3 );
1014:
1015: WriteGUSb( GUS_COMMAND, GUS_CMD_VOICECTRLW );
1016: WriteGUSb( GUS_DATAHI, 3 );
1017: GUS_GF1Delay();
1018: WriteGUSb( GUS_DATAHI, 3 );
1019:
1020: return;
1021: }
1022:
1023: if( bIgnoreGF1IRQ == TRUE )
1024: {
1025: #ifdef DEBUG
1026: fprintf( stderr, "\t\tIgnoriere IRQ !\n" );
1027: #endif
1028:
1029: WriteGUSb( GUS_COMMAND, GUS_CMD_VOICECTRLW );
1030: WriteGUSb( GUS_DATAHI, 3 ); // Loop aus, IRQ aus
1031:
1032: WriteGUSb( GUS_COMMAND, GUS_CMD_VOLUMECTRLW );
1033: WriteGUSb( GUS_DATAHI, 3 ); // Rollover an, IRQ an
1034: }
1035:
1036: #ifdef DEBUG
1037: fprintf( stderr, "\tBearbeite Stimme %d, IntArt: $%X\n", nVoice, ubIntArt );
1038: #endif
1039:
1040: if( ! (ubIntArt & 128 ) ) // WAVE-Table-IRQ ?
1041: {
1042: if( !(nWaveIgnore & nVoiceBit ))
1043: {
1044: nWaveIgnore |= nVoiceBit;
1045:
1046: #ifdef DEBUG
1047: fprintf( stderr,"\t\tBearbeite WAVE-IRQ\n" );
1048: #endif
1049:
1050: WriteGUSb( GUS_VOICE, nVoice );
1051: WriteGUSb( GUS_COMMAND, GUS_CMD_VOICECTRLR );
1052: ubTemp1 = ReadGUSb( GUS_DATAHI );
1053:
1054: WriteGUSb( GUS_COMMAND, GUS_CMD_VOLUMECTRLR );
1055: ubTemp2 = ReadGUSb( GUS_DATAHI );
1056:
1057:
1058: if( ubTemp2&4 ) // Ist das Rollover an ?
1059: { // Ja
1060:
1061: // Das Ende neu setzen
1062:
1063: if( ubGUSUsedBits == 0 ) // 8 Bit ?
1064: {
1065: WriteGUSb( GUS_COMMAND, GUS_CMD_ENDHIW ); // Ende setzen
1066: WriteGUSw( GUS_DATALO, ADDR_HI( ulDMABufMaxSize*2+32 ) );
1067: WriteGUSb( GUS_COMMAND, GUS_CMD_ENDLOW );
1068: WriteGUSw( GUS_DATALO, ADDR_LO( ulDMABufMaxSize*2+32 ) );
1069: }
1070: else
1071: {
1072: WriteGUSb( GUS_COMMAND, GUS_CMD_ENDHIW ); // Ende setzen
1073: WriteGUSw( GUS_DATALO, ADDR_HI( CONVTO16B( ulDMABufMaxSize*2+32 ) ) );
1074: WriteGUSb( GUS_COMMAND, GUS_CMD_ENDLOW );
1075: WriteGUSw( GUS_DATALO, ADDR_LO( CONVTO16B( ulDMABufMaxSize*2+32 ) ) );
1076: }
1077:
1078: WriteGUSb( GUS_COMMAND, GUS_CMD_VOLUMECTRLW );
1079: WriteGUSb( GUS_DATAHI, 3 );
1080: GUS_GF1Delay();
1081: WriteGUSb( GUS_DATAHI, 3 ); // Rollover aus
1082:
1083: WriteGUSb( GUS_COMMAND, GUS_CMD_VOICECTRLW );
1084: WriteGUSb( GUS_DATAHI, 32|8|ubGUSUsedBits );
1085: GUS_GF1Delay();
1086: WriteGUSb( GUS_DATAHI, 32|8|ubGUSUsedBits ); // Loop an
1087:
1088: GUS_StartDMA( 32, FALSE );
1089:
1090: }
1091: else
1092: {
1093:
1094: // Das Ende neu setzen
1095:
1096: if( ubGUSUsedBits == 0 )
1097: {
1098: WriteGUSb( GUS_COMMAND, GUS_CMD_ENDHIW ); // Ende setzen
1099: WriteGUSw( GUS_DATALO, ADDR_HI( ulDMABufMaxSize+32 ) );
1100: WriteGUSb( GUS_COMMAND, GUS_CMD_ENDLOW );
1101: WriteGUSw( GUS_DATALO, ADDR_LO( ulDMABufMaxSize+32 ) );
1102: }
1103: else
1104: {
1105: WriteGUSb( GUS_COMMAND, GUS_CMD_ENDHIW ); // Ende setzen
1106: WriteGUSw( GUS_DATALO, ADDR_HI( CONVTO16B( ulDMABufMaxSize+32 ) ) );
1107: WriteGUSb( GUS_COMMAND, GUS_CMD_ENDLOW );
1108: WriteGUSw( GUS_DATALO, ADDR_LO( CONVTO16B( ulDMABufMaxSize+32 ) ) );
1109: }
1110:
1111: WriteGUSb( GUS_COMMAND, GUS_CMD_VOLUMECTRLW );
1112: WriteGUSb( GUS_DATAHI, 3|4 );
1113: GUS_GF1Delay();
1114: WriteGUSb( GUS_DATAHI, 3|4 ); // Rollover an
1115:
1116: WriteGUSb( GUS_COMMAND, GUS_CMD_VOICECTRLW );
1117: WriteGUSb( GUS_DATAHI, 32|ubGUSUsedBits );
1118: GUS_GF1Delay();
1119: WriteGUSb( GUS_DATAHI, 32|ubGUSUsedBits ); // Loop aus
1120:
1121: GUS_StartDMA( ulDMABufMaxSize+32, TRUE );
1122:
1123: }
1124:
1125: }
1126: }
1127:
1128: if( ! (ubIntArt & 64 ) ) // Volume-IRQ
1129: {
1130:
1131: if( !(nVolumeIgnore & nVoiceBit ))
1132: {
1133: nVolumeIgnore |= nVoiceBit;
1134: #ifdef DEBUG
1135: fprintf( stderr, "\t\tBearbeite Volume-IRQ\n" );
1136: #endif
1137:
1138: WriteGUSb( GUS_VOICE, nVoice );
1139: WriteGUSb( GUS_COMMAND, GUS_CMD_VOLUMECTRLR );
1140: ubTemp2 = ReadGUSb( GUS_DATAHI );
1141:
1142: WriteGUSb(GUS_COMMAND, GUS_CMD_VOICECTRLR );
1143: ubTemp1 = ReadGUSb( GUS_DATAHI );
1144:
1145:
1146: WriteGUSb( GUS_COMMAND, GUS_CMD_VOLUMECTRLW );
1147: WriteGUSb( GUS_DATAHI, (ubTemp2 & (~32)) | 3 );
1148:
1149: }
1150:
1151: }
1152:
1153: }
1154: }
1155:
1156:
1157:
1158: /*******************************
1159:
1160: void GUS_GF1InterruptHandler( void )
1161:
1162: *******************************/
1163:
1164:
1165: // Hier werden alle Interrupts verarbeitet
1166:
1167: void GUS_GF1InterruptHandler( void )
1168: {
1169:
1170: UBYTE ubIntArt, ubTemp;
1171:
1172:
1173:
1174:
1175: if( uwGUSPort == 0 )
1176: return;
1177:
1178: #ifdef DEBUG
1179: fprintf( stderr, "- Interrupt aufgetreten. Zeit: %lf\n", ((double)clock())/((double)CLOCKS_PER_SEC) );
1180: #endif
1181:
1182: for(;;)
1183: {
1184:
1185: ubIntArt = ReadGUSb( GUS_IRQSTATE );
1186:
1187: #ifdef DEBUG
1188: fprintf( stderr, " IRQ-Status (2x6): $%X\n", ubIntArt );
1189: #endif
1190:
1191:
1192: if( ubIntArt == 0 ) // Fertig ?
1193: break;
1194:
1195: if( ubIntArt & 128 ) // TC-IRQ ?
1196: {
1197:
1198: WriteGUSb( GUS_COMMAND, GUS_CMD_DRAMDMACTRL );
1199: if( ReadGUSb( GUS_DATAHI ) & 0x40 )
1200: {
1201: #ifdef DEBUG
1202: fprintf( stderr, " - Bearbeite DMA-Interrupt\n" );
1203: #endif
1204:
1205: bDMAInProgress = FALSE;
1206:
1207: }
1208:
1209: WriteGUSb( GUS_COMMAND, GUS_CMD_SAMPLECTRL );
1210: if( ReadGUSb( GUS_DATAHI ) & 0x40 )
1211: {
1212: #ifdef DEBUG
1213: fprintf( stderr, " - Bearbeite Sample-Interrupt\n" );
1214: #endif
1215:
1216: }
1217:
1218: }
1219:
1220: if( ubIntArt & 3 )
1221: {
1222: #ifdef DEBUG
1223: fprintf( stderr, " - Bearbeite MIDI-IRQ\n" );
1224: #endif
1225:
1226: }
1227:
1228: if( ubIntArt & 64 )
1229: {
1230: #ifdef DEBUG
1231: fprintf( stderr, " - Bearbeite VOLUME-IRQ\n" );
1232: #endif
1233: GUS_WorkOnVoices();
1234:
1235:
1236: }
1237:
1238: if( ubIntArt & 32 )
1239: {
1240: #ifdef DEBUG
1241: fprintf( stderr, " - Bearbeite WAVETABLE-IRQ\n" );
1242: #endif
1243: GUS_WorkOnVoices();
1244:
1245: }
1246:
1247:
1248: if( ubIntArt & 4 ) // Timer 1?
1249: {
1250: #ifdef DEBUG
1251: fprintf( stderr, " - Bearbeite Timer 1-IRQ\n" );
1252: #endif
1253:
1254: WriteGUSb( GUS_COMMAND, GUS_CMD_TIMERCTRL );
1255: ubTemp = ReadGUSb( GUS_DATAHI );
1256:
1257: WriteGUSb( GUS_COMMAND, GUS_CMD_TIMERCTRL );
1258: WriteGUSb( GUS_DATAHI, ubTemp & (~4) ); // Timer 1 aus
1259: }
1260:
1261: if( ubIntArt & 8 ) // Timer 2?
1262: {
1263: #ifdef DEBUG
1264: fprintf( stderr, " - Bearbeite Timer 2-IRQ\n" );
1265: #endif
1266:
1267: WriteGUSb( GUS_COMMAND, GUS_CMD_TIMERCTRL );
1268: ubTemp = ReadGUSb( GUS_DATAHI );
1269:
1270: WriteGUSb( GUS_COMMAND, GUS_CMD_TIMERCTRL );
1271: WriteGUSb( GUS_DATAHI, ubTemp & (~8) ); // Timer 2 aus
1272: }
1273: }
1274:
1275: #ifdef DEBUG
1276: fprintf( stderr, "- Interruptende\n\n" );
1277: #endif
1278:
1279: if( ubGUSGF1Int > 7 )
1280: outportb( 0xA0, 0x20 );
1281:
1282: outportb( 0x20, 0x20 );
1283:
1284: }
1285:
1286:
1287: // Dummy-Routine zum erkennen des Programmendes
1288:
1289: void GUS_DummyFunc( void )
1290: {
1291: ;
1292: }
1293:
1294:
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.