1/*
2    SDL - Simple DirectMedia Layer
3    Copyright (C) 1997-2012 Sam Lantinga
4
5    This library is free software; you can redistribute it and/or
6    modify it under the terms of the GNU Lesser General Public
7    License as published by the Free Software Foundation; either
8    version 2.1 of the License, or (at your option) any later version.
9
10    This library is distributed in the hope that it will be useful,
11    but WITHOUT ANY WARRANTY; without even the implied warranty of
12    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13    Lesser General Public License for more details.
14
15    You should have received a copy of the GNU Lesser General Public
16    License along with this library; if not, write to the Free Software
17    Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
18
19    Sam Lantinga
20    slouken@libsdl.org
21
22*/
23#include "SDL_config.h"
24
25/* Output dreamcast aica */
26
27#include "SDL_timer.h"
28#include "SDL_audio.h"
29#include "../SDL_audiomem.h"
30#include "../SDL_audio_c.h"
31#include "../SDL_audiodev_c.h"
32#include "SDL_dcaudio.h"
33
34#include "aica.h"
35#include <dc/spu.h>
36
37/* Audio driver functions */
38static int DCAUD_OpenAudio(_THIS, SDL_AudioSpec *spec);
39static void DCAUD_WaitAudio(_THIS);
40static void DCAUD_PlayAudio(_THIS);
41static Uint8 *DCAUD_GetAudioBuf(_THIS);
42static void DCAUD_CloseAudio(_THIS);
43
44/* Audio driver bootstrap functions */
45static int DCAUD_Available(void)
46{
47	return 1;
48}
49
50static void DCAUD_DeleteDevice(SDL_AudioDevice *device)
51{
52	SDL_free(device->hidden);
53	SDL_free(device);
54}
55
56static SDL_AudioDevice *DCAUD_CreateDevice(int devindex)
57{
58	SDL_AudioDevice *this;
59
60	/* Initialize all variables that we clean on shutdown */
61	this = (SDL_AudioDevice *)SDL_malloc(sizeof(SDL_AudioDevice));
62	if ( this ) {
63		SDL_memset(this, 0, (sizeof *this));
64		this->hidden = (struct SDL_PrivateAudioData *)
65				SDL_malloc((sizeof *this->hidden));
66	}
67	if ( (this == NULL) || (this->hidden == NULL) ) {
68		SDL_OutOfMemory();
69		if ( this ) {
70			SDL_free(this);
71		}
72		return(0);
73	}
74	SDL_memset(this->hidden, 0, (sizeof *this->hidden));
75
76	/* Set the function pointers */
77	this->OpenAudio = DCAUD_OpenAudio;
78	this->WaitAudio = DCAUD_WaitAudio;
79	this->PlayAudio = DCAUD_PlayAudio;
80	this->GetAudioBuf = DCAUD_GetAudioBuf;
81	this->CloseAudio = DCAUD_CloseAudio;
82
83	this->free = DCAUD_DeleteDevice;
84
85	spu_init();
86
87	return this;
88}
89
90AudioBootStrap DCAUD_bootstrap = {
91	"dcaudio", "Dreamcast AICA audio",
92	DCAUD_Available, DCAUD_CreateDevice
93};
94
95/* This function waits until it is possible to write a full sound buffer */
96static void DCAUD_WaitAudio(_THIS)
97{
98	if (this->hidden->playing) {
99		/* wait */
100		while(aica_get_pos(0)/this->spec.samples == this->hidden->nextbuf) {
101			thd_pass();
102		}
103	}
104}
105
106#define	SPU_RAM_BASE	0xa0800000
107
108static void spu_memload_stereo8(int leftpos,int rightpos,void *src0,size_t size)
109{
110	uint8 *src = src0;
111	uint32 *left  = (uint32*)(leftpos +SPU_RAM_BASE);
112	uint32 *right = (uint32*)(rightpos+SPU_RAM_BASE);
113	size = (size+7)/8;
114	while(size--) {
115		unsigned lval,rval;
116		lval = *src++;
117		rval = *src++;
118		lval|= (*src++)<<8;
119		rval|= (*src++)<<8;
120		lval|= (*src++)<<16;
121		rval|= (*src++)<<16;
122		lval|= (*src++)<<24;
123		rval|= (*src++)<<24;
124		g2_write_32(left++,lval);
125		g2_write_32(right++,rval);
126		g2_fifo_wait();
127	}
128}
129
130static void spu_memload_stereo16(int leftpos,int rightpos,void *src0,size_t size)
131{
132	uint16 *src = src0;
133	uint32 *left  = (uint32*)(leftpos +SPU_RAM_BASE);
134	uint32 *right = (uint32*)(rightpos+SPU_RAM_BASE);
135	size = (size+7)/8;
136	while(size--) {
137		unsigned lval,rval;
138		lval = *src++;
139		rval = *src++;
140		lval|= (*src++)<<16;
141		rval|= (*src++)<<16;
142		g2_write_32(left++,lval);
143		g2_write_32(right++,rval);
144		g2_fifo_wait();
145	}
146}
147
148static void DCAUD_PlayAudio(_THIS)
149{
150	SDL_AudioSpec *spec = &this->spec;
151	unsigned int offset;
152
153	if (this->hidden->playing) {
154		/* wait */
155		while(aica_get_pos(0)/spec->samples == this->hidden->nextbuf) {
156			thd_pass();
157		}
158	}
159
160	offset = this->hidden->nextbuf*spec->size;
161	this->hidden->nextbuf^=1;
162	/* Write the audio data, checking for EAGAIN on broken audio drivers */
163	if (spec->channels==1) {
164		spu_memload(this->hidden->leftpos+offset,this->hidden->mixbuf,this->hidden->mixlen);
165	} else {
166		offset/=2;
167		if ((this->spec.format&255)==8) {
168			spu_memload_stereo8(this->hidden->leftpos+offset,this->hidden->rightpos+offset,this->hidden->mixbuf,this->hidden->mixlen);
169		} else {
170			spu_memload_stereo16(this->hidden->leftpos+offset,this->hidden->rightpos+offset,this->hidden->mixbuf,this->hidden->mixlen);
171		}
172	}
173
174	if (!this->hidden->playing) {
175		int mode;
176		this->hidden->playing = 1;
177		mode = (spec->format==AUDIO_S8)?SM_8BIT:SM_16BIT;
178		if (spec->channels==1) {
179			aica_play(0,mode,this->hidden->leftpos,0,spec->samples*2,spec->freq,255,128,1);
180		} else {
181			aica_play(0,mode,this->hidden->leftpos ,0,spec->samples*2,spec->freq,255,0,1);
182			aica_play(1,mode,this->hidden->rightpos,0,spec->samples*2,spec->freq,255,255,1);
183		}
184	}
185}
186
187static Uint8 *DCAUD_GetAudioBuf(_THIS)
188{
189	return(this->hidden->mixbuf);
190}
191
192static void DCAUD_CloseAudio(_THIS)
193{
194	aica_stop(0);
195	if (this->spec.channels==2) aica_stop(1);
196	if ( this->hidden->mixbuf != NULL ) {
197		SDL_FreeAudioMem(this->hidden->mixbuf);
198		this->hidden->mixbuf = NULL;
199	}
200}
201
202static int DCAUD_OpenAudio(_THIS, SDL_AudioSpec *spec)
203{
204    Uint16 test_format = SDL_FirstAudioFormat(spec->format);
205    int valid_datatype = 0;
206    while ((!valid_datatype) && (test_format)) {
207        spec->format = test_format;
208        switch (test_format) {
209            /* only formats Dreamcast accepts... */
210            case AUDIO_S8:
211            case AUDIO_S16LSB:
212                valid_datatype = 1;
213                break;
214
215            default:
216                test_format = SDL_NextAudioFormat();
217                break;
218        }
219    }
220
221    if (!valid_datatype) {  /* shouldn't happen, but just in case... */
222        SDL_SetError("Unsupported audio format");
223        return (-1);
224    }
225
226    if (spec->channels > 2)
227        spec->channels = 2;  /* no more than stereo on the Dreamcast. */
228
229	/* Update the fragment size as size in bytes */
230	SDL_CalculateAudioSpec(spec);
231
232	/* Allocate mixing buffer */
233	this->hidden->mixlen = spec->size;
234	this->hidden->mixbuf = (Uint8 *) SDL_AllocAudioMem(this->hidden->mixlen);
235	if ( this->hidden->mixbuf == NULL ) {
236		return(-1);
237	}
238	SDL_memset(this->hidden->mixbuf, spec->silence, spec->size);
239	this->hidden->leftpos = 0x11000;
240	this->hidden->rightpos = 0x11000+spec->size;
241	this->hidden->playing = 0;
242	this->hidden->nextbuf = 0;
243
244	/* We're ready to rock and roll. :-) */
245	return(0);
246}
247