7 Mikmod driver for output on Ensoniq Soundscape / Soundscape ELITE
\r
11 MSDOS: BC(y) Watcom(y) DJGPP(y)
\r
17 (n) - no (not possible or not useful)
\r
18 (?) - may be possible, but not tested
\r
32 /***************************************************************************
\r
33 >>>>>>>>>>>>>>>>>>>>>>>>> Lowlevel SS stuff <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
\r
34 ***************************************************************************/
\r
36 /* Ensoniq gate-array chip defines ... */
\r
38 #define ODIE 0 /* ODIE gate array */
\r
39 #define OPUS 1 /* OPUS gate array */
\r
40 #define MMIC 2 /* MiMIC gate array */
\r
42 /* relevant direct register defines - offsets from base address */
\r
43 #define GA_HOSTCTL_OFF 2 /* host port ctrl/stat reg */
\r
44 #define GA_ADDR_OFF 4 /* indirect address reg */
\r
45 #define GA_DATA_OFF 5 /* indirect data reg */
\r
46 #define GA_CODEC_OFF 8 /* for some boards CoDec is fixed from base */
\r
48 /* relevant indirect register defines */
\r
49 #define GA_DMAB_REG 3 /* DMA chan B assign reg */
\r
50 #define GA_INTCFG_REG 4 /* interrupt configuration reg */
\r
51 #define GA_DMACFG_REG 5 /* DMA configuration reg */
\r
52 #define GA_CDCFG_REG 6 /* CD-ROM/CoDec config reg */
\r
53 #define GA_HMCTL_REG 9 /* host master control reg */
\r
56 /* AD-1848 or compatible CoDec defines ... */
\r
57 /* relevant direct register defines - offsets from base */
\r
58 #define CD_ADDR_OFF 0 /* indirect address reg */
\r
59 #define CD_DATA_OFF 1 /* indirect data reg */
\r
60 #define CD_STATUS_OFF 2 /* status register */
\r
62 /* relevant indirect register defines */
\r
63 #define CD_ADCL_REG 0 /* left DAC input control reg */
\r
64 #define CD_ADCR_REG 1 /* right DAC input control reg */
\r
65 #define CD_CDAUXL_REG 2 /* left DAC output control reg */
\r
66 #define CD_CDAUXR_REG 3 /* right DAC output control reg */
\r
67 #define CD_DACL_REG 6 /* left DAC output control reg */
\r
68 #define CD_DACR_REG 7 /* right DAC output control reg */
\r
69 #define CD_FORMAT_REG 8 /* clock and data format reg */
\r
70 #define CD_CONFIG_REG 9 /* interface config register */
\r
71 #define CD_PINCTL_REG 10 /* external pin control reg */
\r
72 #define CD_UCOUNT_REG 14 /* upper count reg */
\r
73 #define CD_LCOUNT_REG 15 /* lower count reg */
\r
74 #define CD_XFORMAT_REG 28 /* extended format reg - 1845 record */
\r
75 #define CD_XUCOUNT_REG 30 /* extended upper count reg - 1845 record */
\r
76 #define CD_XLCOUNT_REG 31 /* extended lower count reg - 1845 record */
\r
78 #define CD_MODE_CHANGE 0x40 /* mode change mask for addr reg */
\r
80 /****************************************************************************
\r
81 hardware config info ...
\r
82 ****************************************************************************/
\r
84 static UWORD BasePort; /* Gate Array/MPU-401 base port */
\r
85 static UWORD MidiIrq; /* the MPU-401 IRQ */
\r
86 static UWORD WavePort; /* the AD-1848 base port */
\r
87 static UWORD WaveIrq; /* the PCM IRQ */
\r
88 static UWORD DmaChan; /* the PCM DMA channel */
\r
90 /****************************************************************************
\r
91 all kinds of stuff ...
\r
92 ****************************************************************************/
\r
94 static UWORD Windx; /* Wave IRQ index - for reg writes */
\r
95 static UWORD Mindx; /* MIDI IRQ index - for reg writes */
\r
97 static UBYTE IcType; /* the Ensoniq chip type */
\r
98 static UBYTE CdCfgSav; /* gate array register save area */
\r
99 static UBYTE DmaCfgSav; /* gate array register save area */
\r
100 static UBYTE IntCfgSav; /* gate array register save area */
\r
102 static UWORD const SsIrqs[4] = { 9, 5, 7, 10 }; /* Soundscape IRQs */
\r
103 static UWORD const RsIrqs[4] = { 9, 7, 5, 15 }; /* an older IRQ set */
\r
104 static UWORD const *Irqs; /* pointer to one of the IRQ sets */
\r
106 static UBYTE DacSavL; /* DAC left volume save */
\r
107 static UBYTE DacSavR; /* DAC right volume save */
\r
108 static UBYTE CdxSavL; /* CD/Aux left volume save */
\r
109 static UBYTE CdxSavR; /* CD/Aux right volume save */
\r
110 static UBYTE AdcSavL; /* ADC left volume save */
\r
111 static UBYTE AdcSavR; /* ADC right volume save */
\r
113 static DMAMEM *SS_DMAMEM;
\r
114 static char *SS_DMABUF;
\r
117 /***************************************************************************
\r
118 >>>>>>>>>>>>>>>>>>>>>>>>> The actual SS driver <<<<<<<<<<<<<<<<<<<<<<<<<<<<<
\r
119 ***************************************************************************/
\r
123 UBYTE GaRead(UWORD rnum)
\r
125 This function is used to read the indirect addressed registers in the
\r
126 Ensoniq Soundscape gate array.
\r
129 rnum - the numner of the indirect register to be read
\r
132 the contents of the indirect register are returned
\r
135 outportb(BasePort + GA_ADDR_OFF, rnum);
\r
136 return inportb(BasePort + GA_DATA_OFF);
\r
141 void GaWrite(UWORD rnum,UBYTE value)
\r
143 This function is used to write the indirect addressed registers in the
\r
144 Ensoniq Soundscape gate array.
\r
147 rnum - the numner of the indirect register to be read
\r
148 value - the byte value to be written to the indirect register
\r
154 outportb(BasePort + GA_ADDR_OFF, rnum);
\r
155 outportb(BasePort + GA_DATA_OFF, value);
\r
159 UBYTE CdRead(UWORD rnum)
\r
161 This function is used to read the indirect addressed registers in the
\r
162 AD-1848 or compatible CoDec. It will preserve the special function bits
\r
163 in the upper-nibble of the indirect address register.
\r
166 rnum - the numner of the indirect register to be read
\r
169 the contents of the indirect register are returned
\r
173 outportb(WavePort + CD_ADDR_OFF,
\r
174 (inportb(WavePort + CD_ADDR_OFF) & 0xf0) | rnum);
\r
175 return inportb(WavePort+CD_DATA_OFF);
\r
180 void CdWrite(UWORD rnum,UBYTE value)
\r
182 This function is used to write the indirect addressed registers in the
\r
183 Ad-1848 or compatible CoDec. It will preserve the special function bits
\r
184 in the upper-nibble of the indirect address register.
\r
187 rnum - the numner of the indirect register to be read
\r
188 value - the byte value to be written to the indirect register
\r
194 outportb(WavePort + CD_ADDR_OFF,
\r
195 (inportb(WavePort + CD_ADDR_OFF) & 0xf0) | rnum);
\r
196 outportb(WavePort + CD_DATA_OFF, value);
\r
202 void SetDacVol(UBYTE lvol,UBYTE rvol)
\r
204 This function sets the left and right DAC output level in the CoDec.
\r
207 lvol - left volume, 0-127
\r
208 rvol - right volume, 0-127
\r
215 CdWrite(CD_DACL_REG, ~(lvol >> 1) & 0x3f);
\r
216 CdWrite(CD_DACR_REG, ~(rvol >> 1) & 0x3f);
\r
221 void SetCdRomVol(UBYTE lvol,UBYTE rvol)
\r
223 This function sets the left and right CD-ROM output level in the CoDec.
\r
226 lvol - left volume, 0-127
\r
227 rvol - right volume, 0-127
\r
233 CdWrite(CD_CDAUXL_REG, ~(lvol >> 2) & 0x1f);
\r
234 CdWrite(CD_CDAUXR_REG, ~(rvol >> 2) & 0x1f);
\r
238 void SetAdcVol(UBYTE lvol,UBYTE rvol)
\r
240 This function sets the left and right ADC input level in the CoDec.
\r
243 lvol - left volume, 0-127
\r
244 rvol - right volume, 0-127
\r
251 CdWrite(CD_ADCL_REG, (CdRead(CD_ADCL_REG) & 0xf0) | (lvol & 0x7f) >> 3);
\r
252 CdWrite(CD_ADCR_REG, (CdRead(CD_ADCR_REG) & 0xf0) | (rvol & 0x7f) >> 3);
\r
256 void StopCoDec(void)
\r
260 CdWrite(CD_CONFIG_REG,CdRead(CD_CONFIG_REG)&0xfc);
\r
262 /* Let the CoDec receive its last DACK(s). The DMAC must not be */
\r
263 /* masked while the CoDec has DRQs pending. */
\r
264 /* for(i=0; i<256; ++i )
\r
265 if(!(inportb(DmacRegP->status) & (0x10 << DmaChan))) break;
\r
271 BOOL GetConfigEntry(char *entry, char *dest, FILE *fp)
\r
273 This function parses a file (SNDSCAPE.INI) for a left-hand string and,
\r
274 if found, writes its associated right-hand value to a destination buffer.
\r
275 This function is case-insensitive.
\r
278 fp - a file pointer to the open SNDSCAPE.INI config file
\r
279 dst - the destination buffer pointer
\r
280 lhp - a pointer to the right-hand string
\r
284 0 - if the right-hand string is not found or has no equate
\r
287 char static str[83];
\r
288 char static tokstr[33];
\r
291 /* make a local copy of the entry, upper-case it */
\r
292 strcpy(tokstr, entry);
\r
295 /* rewind the file and try to find it ... */
\r
299 /* get the next string from the file */
\r
301 fgets(str, 83, fp);
\r
302 if(feof(fp)) return 0;
\r
304 /* properly terminate the string */
\r
305 for( p = str; *p != '\0'; ++p ) {
\r
306 if( *p == ' ' || *p == '\t' || *p == 0x0a || *p == 0x0d ) {
\r
312 /* see if it's an 'equate' string; if so, zero the '=' */
\r
313 if( !(p = strchr(str, '=')) ) continue;
\r
316 /* upper-case the current string and test it */
\r
318 if( strcmp(str, tokstr) )
\r
321 /* it's our string - copy the right-hand value to buffer */
\r
322 for( p = str + strlen(str) + 1; (*dest++ = *p++) != '\0'; );
\r
329 static BOOL SS_IsThere(void)
\r
331 static char str[78];
\r
336 if((envptr=getenv("SNDSCAPE"))==NULL) return 0;
\r
338 strcpy(str, envptr);
\r
339 if( str[strlen(str) - 1] == '\\' )
\r
340 str[strlen(str) - 1] = '\0';
\r
342 strcat(str, "\\SNDSCAPE.INI");
\r
344 if(!(fp=fopen(str, "r"))) return 0;
\r
346 /* read all of the necessary config info ... */
\r
347 if(!GetConfigEntry("Product",str,fp)){
\r
352 /* if an old product name is read, set the IRQs accordingly */
\r
354 if(strstr(str,"SOUNDFX") || strstr(str,"MEDIA_FX"))
\r
359 if(!GetConfigEntry("Port", str, fp)){
\r
364 BasePort=strtol(str,NULL,16);
\r
366 if(!GetConfigEntry("WavePort",str,fp)){
\r
371 WavePort=strtol(str,NULL,16);
\r
373 if(!GetConfigEntry("IRQ",str,fp)){
\r
378 MidiIrq=strtol(str,NULL,10);
\r
379 if(MidiIrq==2) MidiIrq = 9;
\r
381 if(!GetConfigEntry("SBIRQ",str,fp)){
\r
386 WaveIrq=strtol(str,NULL,10);
\r
387 if(WaveIrq==2) WaveIrq=9;
\r
389 if(!GetConfigEntry("DMA",str,fp)){
\r
394 DmaChan=strtol(str,NULL,10);
\r
398 /* see if Soundscape is there by reading HW ... */
\r
399 if((inportb(BasePort+GA_HOSTCTL_OFF)&0x78) != 0x00) return 0;
\r
400 if((inportb(BasePort+GA_ADDR_OFF)&0xf0)==0xf0) return 0;
\r
402 outportb(BasePort+GA_ADDR_OFF,0xf5);
\r
404 tmp=inportb(BasePort+GA_ADDR_OFF);
\r
406 if((tmp & 0xf0)==0xf0) return 0;
\r
407 if((tmp & 0x0f)!=0x05) return 0;
\r
409 /* formulate the chip ID */
\r
410 if( (tmp & 0x80) != 0x00 )
\r
412 else if((tmp & 0x70) != 0x00)
\r
417 /* now do a quick check to make sure the CoDec is there too */
\r
418 if((inportb(WavePort)&0x80)!=0x00) return 0;
\r
425 static void interrupt newhandler(void)
\r
428 outportb(0x20,0x0b);
\r
430 if(!(inportb(0x20)&0x80)) return;
\r
432 else if(WaveIrq==15){
\r
433 outportb(0xa0,0x0b);
\r
435 if(!(inportb(0xa0)&0x80)) return;
\r
440 /* if the CoDec is interrupting clear the AD-1848 interrupt */
\r
441 if(inportb(WavePort+CD_STATUS_OFF)&0x01)
\r
442 outportb(WavePort+CD_STATUS_OFF,0x00);
\r
448 static PVI oldhandler;
\r
453 static UWORD codecfreqs[14]={
\r
454 5512, 6615, 8000, 9600,11025,16000,18900,
\r
455 22050,27428,32000,33075,37800,44100,48000
\r
459 static UWORD codecformats[14]={
\r
460 0x01, 0x0f, 0x00, 0x0e, 0x03, 0x02, 0x05,
\r
461 0x07, 0x04, 0x06, 0x0d, 0x09, 0x0b, 0x0c
\r
465 static UBYTE codecformat;
\r
468 static BOOL SS_Init(void)
\r
473 myerr="No such hardware detected, check your 'SNDSCAPE' env. variable";
\r
477 printf("Ensoniq Soundscape at port 0x%x, irq %d, dma %d\n",WavePort,WaveIrq,DmaChan);
\r
479 /* find closest codec frequency */
\r
482 if(t==13 || md_mixfreq<=codecfreqs[t]){
\r
483 md_mixfreq=codecfreqs[t];
\r
488 codecformat=codecformats[t];
\r
489 if(md_mode & DMODE_STEREO) codecformat|=0x10;
\r
490 if(md_mode & DMODE_16BITS) codecformat|=0x40;
\r
492 if(!VC_Init()) return 0;
\r
494 SS_DMAMEM=MDma_AllocMem(md_dmabufsize);
\r
496 if(SS_DMAMEM==NULL){
\r
498 myerr="Couldn't allocate page-contiguous dma-buffer";
\r
502 SS_DMABUF=(char *)MDma_GetPtr(SS_DMAMEM);
\r
504 /* In case the CoDec is running, stop it */
\r
507 /* Clear possible CoDec and SoundBlaster emulation interrupts */
\r
508 outportb(WavePort+CD_STATUS_OFF,0x00);
\r
511 /* If necessary, save some regs, do some resource re-routing */
\r
512 if( IcType != MMIC) {
\r
514 /* derive the MIDI and Wave IRQ indices (0-3) for reg writes */
\r
515 for( Mindx = 0; Mindx < 4; ++Mindx )
\r
516 if( MidiIrq == *(Irqs + Mindx) )
\r
518 for( Windx = 0; Windx < 4; ++Windx )
\r
519 if( WaveIrq == *(Irqs + Windx) )
\r
522 /* setup the CoDec DMA polarity */
\r
523 GaWrite(GA_DMACFG_REG, 0x50);
\r
525 /* give the CoDec control of the DMA and Wave IRQ resources */
\r
526 CdCfgSav = GaRead(GA_CDCFG_REG);
\r
527 GaWrite(GA_CDCFG_REG, 0x89 | (DmaChan << 4) | (Windx << 1));
\r
529 /* pull the Sound Blaster emulation off of those resources */
\r
530 DmaCfgSav = GaRead(GA_DMAB_REG);
\r
531 GaWrite(GA_DMAB_REG, 0x20);
\r
532 IntCfgSav = GaRead(GA_INTCFG_REG);
\r
533 GaWrite(GA_INTCFG_REG, 0xf0 | (Mindx << 2) | Mindx);
\r
536 /* Save all volumes that we might use, init some levels */
\r
537 DacSavL = CdRead(CD_DACL_REG);
\r
538 DacSavR = CdRead(CD_DACR_REG);
\r
539 CdxSavL = CdRead(CD_CDAUXL_REG);
\r
540 CdxSavR = CdRead(CD_CDAUXL_REG);
\r
541 AdcSavL = CdRead(CD_ADCL_REG);
\r
542 AdcSavR = CdRead(CD_ADCR_REG);
\r
544 SetDacVol(127, 127);
\r
547 /* Select the mic/line input to the record mux; */
\r
548 /* if not ODIE, set the mic gain bit too */
\r
549 CdWrite(CD_ADCL_REG, (CdRead(CD_ADCL_REG) & 0x3f) |
\r
550 (IcType == ODIE ? 0x80 : 0xa0));
\r
551 CdWrite(CD_ADCR_REG, (CdRead(CD_ADCR_REG) & 0x3f) |
\r
552 (IcType == ODIE ? 0x80 : 0xa0));
\r
554 /* Put the CoDec into mode change state */
\r
555 outportb(WavePort + CD_ADDR_OFF, 0x40);
\r
557 /* Setup CoDec mode - single DMA chan, AutoCal on */
\r
558 CdWrite(CD_CONFIG_REG, 0x0c);
\r
561 /* enable the CoDec interrupt pin */
\r
562 CdWrite(CD_PINCTL_REG, CdRead(CD_PINCTL_REG) | 0x02);
\r
563 oldhandler=MIrq_SetHandler(WaveIrq,newhandler);
\r
564 MIrq_OnOff(WaveIrq,1);
\r
566 /* disable the interrupt for mikmod */
\r
567 CdWrite(CD_PINCTL_REG, CdRead(CD_PINCTL_REG) & 0xfd);
\r
574 static void SS_Exit(void)
\r
576 /* in case the CoDec is running, stop it */
\r
579 /* mask the PC DMA Controller */
\r
580 /* outportb(DmacRegP->mask, 0x04 | DmaChan); */
\r
583 /* disable the CoDec interrupt pin */
\r
584 CdWrite(CD_PINCTL_REG, CdRead(CD_PINCTL_REG) & 0xfd);
\r
585 MIrq_OnOff(WaveIrq,0);
\r
586 MIrq_SetHandler(WaveIrq,oldhandler);
\r
589 /* restore all volumes ... */
\r
590 CdWrite(CD_DACL_REG, DacSavL);
\r
591 CdWrite(CD_DACR_REG, DacSavR);
\r
592 CdWrite(CD_CDAUXL_REG, CdxSavL);
\r
593 CdWrite(CD_CDAUXL_REG, CdxSavR);
\r
594 CdWrite(CD_ADCL_REG, AdcSavL);
\r
595 CdWrite(CD_ADCR_REG, AdcSavR);
\r
597 /* if necessary, restore gate array resource registers */
\r
599 GaWrite(GA_INTCFG_REG, IntCfgSav);
\r
600 GaWrite(GA_DMAB_REG, DmaCfgSav);
\r
601 GaWrite(GA_CDCFG_REG, CdCfgSav);
\r
604 MDma_FreeMem(SS_DMAMEM);
\r
609 static UWORD last=0;
\r
610 static UWORD curr=0;
\r
613 static void SS_Update(void)
\r
617 curr=(md_dmabufsize-MDma_Todo(DmaChan))&0xfffc;
\r
619 if(curr>=md_dmabufsize) return;
\r
620 if(curr==last) return;
\r
623 todo=curr-last; index=last;
\r
624 last+=VC_WriteBytes(&SS_DMABUF[index],todo);
\r
625 MDma_Commit(SS_DMAMEM,index,todo);
\r
626 if(last>=md_dmabufsize) last=0;
\r
629 todo=md_dmabufsize-last;
\r
630 VC_WriteBytes(&SS_DMABUF[last],todo);
\r
631 MDma_Commit(SS_DMAMEM,last,todo);
\r
632 last=VC_WriteBytes(SS_DMABUF,curr);
\r
633 MDma_Commit(SS_DMAMEM,0,curr);
\r
638 static void SS_PlayStart(void)
\r
646 /* make sure the the CoDec is in mode change state */
\r
647 outportb(WavePort + CD_ADDR_OFF, 0x40);
\r
649 /* and write the format register */
\r
650 CdWrite(CD_FORMAT_REG, codecformat);
\r
652 /* if not using ODIE and recording, setup extended format register */
\r
653 if( IcType != ODIE && direction )
\r
654 CdWrite(CD_XFORMAT_REG, codecformat & 0x70);
\r
656 /* delay for internal re-synch */
\r
657 for( i = 0; i < 200000UL; ++i )
\r
658 inportb(BasePort + GA_ADDR_OFF);
\r
660 /* clear the dma buffer */
\r
662 VC_SilenceBytes(SS_DMABUF,md_dmabufsize);
\r
663 MDma_Commit(SS_DMAMEM,0,md_dmabufsize);
\r
665 /* Write the CoDec interrupt count - sample frames per half-buffer. */
\r
666 /* If not using ODIE and recording, use extended count regs */
\r
667 tmp = md_dmabufsize;
\r
668 if(md_mode&DMODE_STEREO) tmp>>=1;
\r
669 if(md_mode&DMODE_16BITS) tmp>>=1;
\r
672 if( IcType != ODIE && direction ) {
\r
673 CdWrite(CD_XLCOUNT_REG, tmp);
\r
674 CdWrite(CD_XUCOUNT_REG, tmp >> 8);
\r
677 CdWrite(CD_LCOUNT_REG, tmp);
\r
678 CdWrite(CD_UCOUNT_REG, tmp >> 8);
\r
681 if(!MDma_Start(DmaChan,SS_DMAMEM,md_dmabufsize,INDEF_WRITE)){
\r
685 /* disable mode change state and start the CoDec */
\r
686 outportb(WavePort + CD_ADDR_OFF, 0x00);
\r
687 CdWrite(CD_CONFIG_REG, direction ? 0x02 : 0x01);
\r
691 static void SS_PlayStop(void)
\r
700 "Ensoniq Soundscape",
\r
701 "MikMod Ensoniq Soundscape Driver v0.0 - Thanks to CyberCerus",
\r
711 VC_VoiceSetFrequency,
\r
712 VC_VoiceSetPanning,
\r