soundblaster.c 12.3 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
/*
 * Soundblaster Emulation
 *
 * Copyright 2002 Christian Costa
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
18
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
19 20 21 22
 */

#include "config.h"

23 24
#include <stdarg.h>

25
#include "windef.h"
26
#include "winbase.h"
27 28
#include "dosexe.h"
#include "wine/debug.h"
29 30
#include "wingdi.h"
#include "mmsystem.h"
31 32 33 34 35
#include "dsound.h"

WINE_DEFAULT_DEBUG_CHANNEL(sblaster);

/* Board Configuration */
36
/* FIXME: Should be in a config file */
37 38 39 40 41 42 43 44 45 46
#define SB_IRQ 5
#define SB_IRQ_PRI 11
#define SB_DMA 1

/* Soundblaster state */
static int SampleMode;         /* Mono / Stereo */
static int SampleRate;
static int SamplesCount;
static BYTE DSP_Command[256];  /* Store param numbers in bytes for each command */
static BYTE DSP_InBuffer[10];  /* Store DSP command bytes parameters from host */
47
static int InSize;             /* Nb of bytes in InBuffer */
48
static BYTE DSP_OutBuffer[10]; /* Store DSP information bytes to host */
49
static int OutSize;            /* Nb of bytes in InBuffer */
50 51 52 53 54
static int command;            /* Current command */
static int end_sound_loop = 0;
static int dma_enable = 0;

/* The maximum size of a dma transfer can be 65536 */
55
#define DMATRFSIZE 1024
56 57 58 59 60 61 62 63 64 65

/* DMA can perform 8 or 16-bit transfer */
static BYTE dma_buffer[DMATRFSIZE*2];

/* Direct Sound buffer config */
#define DSBUFLEN 4096 /* FIXME: Only this value seems to work */

/* Direct Sound playback stuff */
static HMODULE hmodule;
typedef HRESULT (WINAPI* fnDirectSoundCreate) (LPGUID,LPDIRECTSOUND*,LPUNKNOWN);
66
static fnDirectSoundCreate lpDirectSoundCreate;
67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
static LPDIRECTSOUND lpdsound;
static LPDIRECTSOUNDBUFFER lpdsbuf;
static DSBUFFERDESC buf_desc;
static WAVEFORMATEX wav_fmt;
static HANDLE SB_Thread;
static UINT buf_off;
extern HWND vga_hwnd;

/* SB_Poll performs DMA transfers and fills the Direct Sound Buffer */
static DWORD CALLBACK SB_Poll( void *dummy )
{
    HRESULT result;
    LPBYTE lpbuf1 = NULL;
    LPBYTE lpbuf2 = NULL;
    DWORD dwsize1 = 0;
    DWORD dwsize2 = 0;
    DWORD dwbyteswritten1 = 0;
    DWORD dwbyteswritten2 = 0;
    int size;

87
    /* FIXME: this loop must be improved */
88 89 90 91 92 93 94 95 96
    while(!end_sound_loop)
    {
        Sleep(10);

        if (dma_enable) {
            size = DMA_Transfer(SB_DMA,min(DMATRFSIZE,SamplesCount),dma_buffer);
        } else
            continue;

97
        result = IDirectSoundBuffer_Lock(lpdsbuf,buf_off,size,(LPVOID *)&lpbuf1,&dwsize1,(LPVOID *)&lpbuf2,&dwsize2,0);
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
        if (result != DS_OK) {
	  ERR("Unable to lock sound buffer !\n");
          continue;
        }

        dwbyteswritten1 = min(size,dwsize1);
        memcpy(lpbuf1,dma_buffer,dwbyteswritten1);
        if (size>dwsize1) {
            dwbyteswritten2 = min(size - dwbyteswritten1,dwsize2);
            memcpy(lpbuf2,dma_buffer+dwbyteswritten1,dwbyteswritten2);
        }
        buf_off = (buf_off + dwbyteswritten1 + dwbyteswritten2) % DSBUFLEN;

        result = IDirectSoundBuffer_Unlock(lpdsbuf,lpbuf1,dwbyteswritten1,lpbuf2,dwbyteswritten2);
        if (result!=DS_OK)
	    ERR("Unable to unlock sound buffer !\n");

        SamplesCount -= size;
        if (!SamplesCount) {
            DOSVM_QueueEvent(SB_IRQ,SB_IRQ_PRI,NULL,NULL);
            dma_enable = 0;
        }
    }
    return 0;
}

124
static BOOL SB_Init(void)
125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141
{
    HRESULT result;

    if (!lpdsound) {
        hmodule = LoadLibraryA("dsound.dll");
        if (!hmodule) {
            ERR("Can't load dsound.dll !\n");
            return 0;
        }
        lpDirectSoundCreate = (fnDirectSoundCreate)GetProcAddress(hmodule,"DirectSoundCreate");
        if (!lpDirectSoundCreate) {
            /* CloseHandle(hmodule); */
            ERR("Can't find DirectSoundCreate function !\n");
            return 0;
        }
        result = (*lpDirectSoundCreate)(NULL,&lpdsound,NULL);
        if (result != DS_OK) {
142
            ERR("Unable to initialize Sound Subsystem err = %x !\n",result);
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
            return 0;
        }

        /* FIXME: To uncomment when :
           - SetCooperative level is correctly implemented
           - an always valid and non changing handle to a windows  (vga_hwnd) is available
             (this surely needs some work in vga.c)
        result = IDirectSound_SetCooperativeLevel(lpdsound,vga_hwnd,DSSCL_EXCLUSIVE|DSSCL_PRIORITY);
        if (result != DS_OK) {
            ERR("Can't set cooperative level !\n");
            return 0;
        }
        */

        /* Default format */
        wav_fmt.wFormatTag = WAVE_FORMAT_PCM;
        wav_fmt.nChannels = 1;
        wav_fmt.nSamplesPerSec = 22050;
        wav_fmt.nAvgBytesPerSec = 22050;
        wav_fmt.nBlockAlign = 1;
        wav_fmt.wBitsPerSample = 8;
        wav_fmt.cbSize = 0;

        memset(&buf_desc,0,sizeof(DSBUFFERDESC));
        buf_desc.dwSize = sizeof(DSBUFFERDESC);
        buf_desc.dwBufferBytes = DSBUFLEN;
        buf_desc.lpwfxFormat = &wav_fmt;
        result = IDirectSound_CreateSoundBuffer(lpdsound,&buf_desc,&lpdsbuf,NULL);
        if (result != DS_OK) {
            ERR("Can't create sound buffer !\n");
            return 0;
        }

        result = IDirectSoundBuffer_Play(lpdsbuf,0, 0, DSBPLAY_LOOPING);
        if (result != DS_OK) {
            ERR("Can't start playing !\n");
            return 0;
        }

        buf_off = 0;
        end_sound_loop = 0;
        SB_Thread = CreateThread(NULL, 0, SB_Poll, NULL, 0, NULL);
        TRACE("thread\n");
        if (!SB_Thread) {
            ERR("Can't create thread !\n");
            return 0;
        }
    }
    return 1;
}

194
static void SB_Reset(void)
195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219
{
    int i;

    for(i=0;i<256;i++)
        DSP_Command[i]=0;

    /* Set Time Constant */
    DSP_Command[0x40]=1;
    /* Generate IRQ */
    DSP_Command[0xF2]=0;
    /* DMA DAC 8-bits */
    DSP_Command[0x14]=2;
    /* Generic DAC/ADC DMA (16-bit, 8-bit) */
    for(i=0xB0;i<=0xCF;i++)
        DSP_Command[i]=3;
    /* DSP Indentification */
    DSP_Command[0xE0]=1;

    /* Clear command and input buffer */
    command = -1;
    InSize = 0;

    /* Put a garbage value in the output buffer */
    OutSize = 1;
    if (SB_Init())
220
        /* All right, let's put the magic value for autodetection */
221 222
        DSP_OutBuffer[0] = 0xaa;
    else
Austin English's avatar
Austin English committed
223
        /* Something is wrong, put 0 to failed autodetection */
224
        DSP_OutBuffer[0] = 0x00;
225 226 227
}

/* Find a standard sampling rate for DirectSound */
228
static int SB_StdSampleRate(int SampleRate)
229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261
{
  if (SampleRate>((44100+48000)/2)) return 48000;
  if (SampleRate>((32000+44100)/2)) return 44100;
  if (SampleRate>((24000+32000)/2)) return 32000;
  if (SampleRate>((22050+24000)/2)) return 24000;
  if (SampleRate>((16000+22050)/2)) return 22050;
  if (SampleRate>((12000+16000)/2)) return 16000;
  if (SampleRate>((11025+12000)/2)) return 12000;
  if (SampleRate>((8000+11025)/2))  return 11025;
  return 8000;
}

void SB_ioport_out( WORD port, BYTE val )
{
    switch(port)
    {
    /* DSP - Reset */
    case 0x226:
        TRACE("Resetting DSP.\n");
        SB_Reset();
        break;
    /* DSP - Write Data or Command */
    case 0x22c:
        TRACE("val=%x\n",val);
        if (command == -1) {
          /* Clear input buffer and set the current command */
          command = val;
          InSize = 0;
        }
        if (InSize!=DSP_Command[command])
	   /* Fill the input buffer the command parameters if any */
           DSP_InBuffer[InSize++]=val;
        else {
262
	    /* Process command */
263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309
            switch(command)
            {
            case 0x10: /* SB */
                FIXME("Direct DAC (8-bit) - Not Implemented\n");
                break;
            case 0x14: /* SB */
                SamplesCount = DSP_InBuffer[1]+(val<<8)+1;
                TRACE("DMA DAC (8-bit) for %x samples\n",SamplesCount);
                dma_enable = 1;
                break;
            case 0x20:
                FIXME("Direct ADC (8-bit) - Not Implemented\n");
                break;
            case 0x24: /* SB */
                FIXME("DMA ADC (8-bit) - Not Implemented\n");
                break;
            case 0x40: /* SB */
                SampleRate = 1000000/(256-val);
                TRACE("Set Time Constant (%d <-> %d Hz => %d Hz)\n",DSP_InBuffer[0],
                    SampleRate,SB_StdSampleRate(SampleRate));
                SampleRate = SB_StdSampleRate(SampleRate);
                wav_fmt.nSamplesPerSec = SampleRate;
                wav_fmt.nAvgBytesPerSec = SampleRate;
                IDirectSoundBuffer_SetFormat(lpdsbuf,&wav_fmt);
                break;
	    /* case 0xBX/0xCX -> See below */
            case 0xD0: /* SB */
                TRACE("Halt DMA operation (8-bit)\n");
                dma_enable = 0;
                break;
            case 0xD1: /* SB */
                FIXME("Enable Speaker - Not Implemented\n");
                break;
            case 0xD3: /* SB */
                FIXME("Disable Speaker - Not Implemented\n");
                break;
            case 0xD4: /* SB */
                FIXME("Continue DMA operation (8-bit) - Not Implemented\n");
                break;
            case 0xD8: /* SB */
                FIXME("Speaker Status - Not Implemented\n");
                break;
	    case 0xE0: /* SB 2.0 */
                TRACE("DSP Identification\n");
                DSP_OutBuffer[OutSize++] = ~val;
                break;
            case 0xE1: /* SB */
310 311 312 313
               TRACE("DSP Version\n");
               OutSize=2;
               DSP_OutBuffer[0]=0; /* returns version 1.0 */
               DSP_OutBuffer[1]=1;
314
                break;
315
            case 0xF2: /* SB */
316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332
                TRACE("IRQ Request (8-bit)\n");
                DOSVM_QueueEvent(SB_IRQ,SB_IRQ_PRI,NULL,NULL);
                break;
            default:
	      if (((command&0xF0)==0xB0)||((DSP_InBuffer[0]&0xF0)==0xC0)) {
		    /* SB16 */
                    FIXME("Generic DAC/ADC DMA (16-bit, 8-bit) - %d % d\n",command,DSP_InBuffer[1]);
                    if (command&0x02)
		        FIXME("Generic DAC/ADC fifo mode not supported\n");
                    if (command&0x04)
		        FIXME("Generic DAC/ADC autoinit dma mode not supported\n");
                    if (command&0x08)
		        FIXME("Generic DAC/ADC adc mode not supported\n");
                    switch(command>>4) {
                    case 0xB:
		        FIXME("Generic DAC/ADC 8-bit not supported\n");
                        SampleMode = 0;
333
                        break;
334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371
                    case 0xC:
		        FIXME("Generic DAC/ADC 16-bit not supported\n");
                        SampleMode = 1;
                        break;
                    default:
		        ERR("Generic DAC/ADC resolution unknown\n");
                        break;
                    }
                    if (DSP_InBuffer[1]&0x010)
		        FIXME("Generic DAC/ADC signed sample mode not supported\n");
                    if (DSP_InBuffer[1]&0x020)
		        FIXME("Generic DAC/ADC stereo mode not supported\n");
                    SamplesCount = DSP_InBuffer[2]+(val<<8)+1;
                    TRACE("Generic DMA for %x samples\n",SamplesCount);
                    dma_enable = 1;
	        }
                else
                    FIXME("DSP command %x not supported\n",val);
            }
            /* Empty the input buffer and end the command */
            InSize = 0;
            command = -1;
        }
    }
}

BYTE SB_ioport_in( WORD port )
{
    BYTE res = 0;

    switch(port)
    {
    /* DSP Read Data */
    case 0x22a:
        /* Value in the read buffer */
      if (OutSize)
          res = DSP_OutBuffer[--OutSize];
      else
372
	  /* return the last byte */
373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391
	  res = DSP_OutBuffer[0];
      break;
    /* DSP - Write Buffer Status */
    case 0x22c:
        /* DSP always ready for writing */
        res = 0x00;
        break;
    /* DSP - Data Available Status */
    /* DSP - IRQ Acknowledge, 8-bit */
    case 0x22e:
        /* DSP data availability check */
        if (OutSize)
            res = 0x80;
        else
	    res = 0x00;
        break;
    }
    return res;
}