Drum kit demo (SDL_mixer) gsoc2008_iphone
authorHolmes Futrell <hfutrell@umail.ucsb.edu>
Fri, 18 Jul 2008 20:51:59 +0000
branchgsoc2008_iphone
changeset 23846a946f3155d8
parent 2383 1cfe7fd15dad
child 2385 0705e8ebe951
Drum kit demo (SDL_mixer)
XCodeiPhoneOS/Demos/src/mixer.c
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/XCodeiPhoneOS/Demos/src/mixer.c	Fri Jul 18 20:51:59 2008 +0000
     1.3 @@ -0,0 +1,321 @@
     1.4 +/*
     1.5 + *	mixer.c
     1.6 + *	written by Holmes Futrell
     1.7 + *	use however you want
     1.8 + */
     1.9 +
    1.10 +#import "SDL.h"
    1.11 +#import "common.h"
    1.12 +
    1.13 +#define NUM_CHANNELS 8				/* max number of sounds we can play at once */
    1.14 +#define NUM_DRUMS 4					/* number of drums in our set */
    1.15 +#define MILLESECONDS_PER_FRAME 16	/* about 60 frames per second */
    1.16 +
    1.17 +static struct {
    1.18 +	SDL_Rect rect;			/* where the button is drawn */
    1.19 +	SDL_Color upColor;		/* color when button is not active */
    1.20 +	SDL_Color downColor;	/* color when button is active */
    1.21 +	int isPressed;			/* is the button being pressed ? */
    1.22 +	int touchIndex;			/* what mouse (touch) index pressed the button ? */
    1.23 +} buttons[NUM_DRUMS];
    1.24 +
    1.25 +struct sound {
    1.26 +	Uint8 *buffer;			/* audio buffer for sound file */
    1.27 +	Uint32 length;			/* length of the buffer (in bytes) */
    1.28 +};
    1.29 +
    1.30 +/* this array holds the audio for the drum noises */
    1.31 +static struct sound drums[NUM_DRUMS];
    1.32 +
    1.33 +/* function declarations */
    1.34 +void handleMouseButtonDown(SDL_Event *event);
    1.35 +void handleMouseButtonUp(SDL_Event *event);
    1.36 +int playSound(struct sound *);
    1.37 +void render(void);
    1.38 +void initializeButtons();
    1.39 +void audioCallback(void *userdata, Uint8 *stream, int len);
    1.40 +void loadSound(const char *file, struct sound *s);
    1.41 +
    1.42 +struct {
    1.43 +	/* channel array holds information about currently playing sounds */
    1.44 +	struct {
    1.45 +		Uint8 *position;		/* what is the current position in the buffer of this sound ? */
    1.46 +		Uint32 remaining;		/* how many bytes remaining before we're done playing the sound ? */
    1.47 +		Uint32 timestamp;		/* when did this sound start playing ? */
    1.48 +	} channels[NUM_CHANNELS];
    1.49 +	SDL_AudioSpec outputSpec;	/* what audio format are we using for output? */
    1.50 +	int numSoundsPlaying;		/* how many sounds are currently playing */
    1.51 +} mixer;
    1.52 +
    1.53 +/* sets up the buttons (color, position, state) */
    1.54 +void initializeButtons() {
    1.55 +	
    1.56 +	int i;
    1.57 +	int spacing = 10;														/* gap between drum buttons */
    1.58 +	SDL_Rect buttonRect;													/* keeps track of where to position drum */
    1.59 +	SDL_Color upColor = { 86, 86, 140, 255 };								/* color of drum when not pressed */
    1.60 +	SDL_Color downColor = { 191, 191, 221, 255 };							/* color of drum when pressed */
    1.61 +	
    1.62 +	buttonRect.x = spacing;
    1.63 +	buttonRect.y = spacing;
    1.64 +	buttonRect.w = SCREEN_WIDTH - 2 * spacing;
    1.65 +	buttonRect.h = ( SCREEN_HEIGHT - (NUM_DRUMS + 1) * spacing ) / NUM_DRUMS;
    1.66 +	
    1.67 +	/* setup each button */
    1.68 +	for (i=0; i<NUM_DRUMS; i++) {
    1.69 +		
    1.70 +		buttons[i].rect = buttonRect;
    1.71 +		buttons[i].isPressed = 0;
    1.72 +		buttons[i].upColor = upColor;
    1.73 +		buttons[i].downColor = downColor;
    1.74 +		
    1.75 +		buttonRect.y += spacing + buttonRect.h;								/* setup y coordinate for next drum */
    1.76 +		
    1.77 +	}	
    1.78 +}
    1.79 +/*
    1.80 + loads a wav file (stored in 'file'), converts it to the mixer's output format,
    1.81 + and stores the resulting buffer and length in the sound structure
    1.82 + */
    1.83 +void loadSound(const char *file, struct sound *s) {
    1.84 +	SDL_AudioSpec spec;		/* the audio format of the .wav file */
    1.85 +	SDL_AudioCVT cvt;		/* used to convert .wav to output format when formats differ */
    1.86 +	int result;
    1.87 +	if (SDL_LoadWAV(file, &spec, &s->buffer, &s->length) == NULL) {
    1.88 +		fatalError("could not load .wav");
    1.89 +	}
    1.90 +	/* build the audio converter */
    1.91 +	result = SDL_BuildAudioCVT(&cvt, spec.format, spec.channels, spec.freq,
    1.92 +							   mixer.outputSpec.format, mixer.outputSpec.channels, mixer.outputSpec.freq);
    1.93 +	if (result == -1) {
    1.94 +		fatalError("could not build audio CVT");
    1.95 +	}
    1.96 +	else if (result != 0) {
    1.97 +		/* 
    1.98 +		 this happens when the .wav format differs from the output format.
    1.99 +		 we convert the .wav buffer here
   1.100 +		 */
   1.101 +		cvt.buf = (Uint8 *)SDL_malloc(s->length * cvt.len_mult); /* allocate conversion buffer */
   1.102 +		cvt.len = s->length;									  /* set conversion buffer length */
   1.103 +		SDL_memcpy(cvt.buf, s->buffer, s->length);				  /* copy sound to conversion buffer */
   1.104 +		if (SDL_ConvertAudio(&cvt) == -1) {					  /* convert the sound */
   1.105 +			fatalError("could not convert .wav");
   1.106 +		}
   1.107 +		SDL_free(s->buffer);									  /* free the original (unconverted) buffer */
   1.108 +		s->buffer = cvt.buf;									  /* point sound buffer to converted buffer */
   1.109 +		s->length = cvt.len_cvt;								  /* set sound buffer's new length */
   1.110 +	}
   1.111 +}
   1.112 +
   1.113 +/* called from main event loop */
   1.114 +void handleMouseButtonDown(SDL_Event *event) {
   1.115 +
   1.116 +	int x, y, mouseIndex, i, drumIndex;
   1.117 +	
   1.118 +	mouseIndex = event->button.which;
   1.119 +	drumIndex = -1;
   1.120 +	
   1.121 +	SDL_SelectMouse(mouseIndex);
   1.122 +	SDL_GetMouseState(&x, &y);
   1.123 +	/* check if we hit any of the drum buttons */
   1.124 +	for (i=0; i<NUM_DRUMS; i++) {
   1.125 +		if (x >= buttons[i].rect.x && x < buttons[i].rect.x + buttons[i].rect.w \
   1.126 +			&& y >= buttons[i].rect.y && y < buttons[i].rect.y + buttons[i].rect.h) {
   1.127 +			drumIndex = i;
   1.128 +			break; 
   1.129 +		}
   1.130 +	}
   1.131 +	if (drumIndex != -1) {
   1.132 +		/* if we hit a button */
   1.133 +		buttons[drumIndex].touchIndex = mouseIndex;
   1.134 +		buttons[drumIndex].isPressed = 1;
   1.135 +		playSound(&drums[drumIndex]);
   1.136 +	}
   1.137 +	
   1.138 +}
   1.139 +
   1.140 +/* called from main event loop */
   1.141 +void handleMouseButtonUp(SDL_Event *event) {
   1.142 +	int i;
   1.143 +	int mouseIndex = event->button.which;
   1.144 +	/* check if this should cause any of the buttons to become unpressed */
   1.145 +	for (i=0; i<NUM_DRUMS; i++) {
   1.146 +		if (buttons[i].touchIndex == mouseIndex) {
   1.147 +			buttons[i].isPressed = 0;
   1.148 +		}
   1.149 +	}
   1.150 +}
   1.151 +
   1.152 +/* draws buttons to screen */
   1.153 +void render(void) {
   1.154 +	int i;
   1.155 +	SDL_RenderFill(50, 50, 50, 255, NULL);	/* draw background (gray) */
   1.156 +	/* draw the drum buttons */
   1.157 +	for (i=0; i<NUM_DRUMS; i++) {
   1.158 +		SDL_Color color = buttons[i].isPressed ? buttons[i].downColor : buttons[i].upColor;
   1.159 +		SDL_RenderFill(color.r, color.g, color.b, color.unused, &buttons[i].rect);
   1.160 +	}
   1.161 +	/* update the screen */
   1.162 +	SDL_RenderPresent();
   1.163 +}
   1.164 +
   1.165 +/*
   1.166 +	finds a sound channel in the mixer for a sound
   1.167 +	and sets it up to start playing
   1.168 +*/
   1.169 +int playSound(struct sound *s) {
   1.170 +	/*
   1.171 +		find an empty channel to play on.
   1.172 +		if no channel is available, use oldest channel
   1.173 +	*/
   1.174 +	int i;
   1.175 +	int selected_channel = -1;
   1.176 +	int oldest_channel = 0;
   1.177 +	
   1.178 +	if (mixer.numSoundsPlaying == 0) {
   1.179 +		/* we're playing a sound now, so start audio callback back up */
   1.180 +		SDL_PauseAudio(0);
   1.181 +	}	
   1.182 +	
   1.183 +	/* find a sound channel to play the sound on */
   1.184 +	for (i=0; i<NUM_CHANNELS; i++) {
   1.185 +		if (mixer.channels[i].position == NULL) {
   1.186 +			/* if no sound on this channel, select it */
   1.187 +			selected_channel = i;
   1.188 +			break;
   1.189 +		}
   1.190 +		/* if this channel's sound is older than the oldest so far, set it to oldest */
   1.191 +		if (mixer.channels[i].timestamp < mixer.channels[oldest_channel].timestamp)
   1.192 +			oldest_channel = i;
   1.193 +	}
   1.194 +	
   1.195 +	/* no empty channels, take the oldest one */
   1.196 +	if (selected_channel == -1) 
   1.197 +		selected_channel = oldest_channel;
   1.198 +	else 
   1.199 +		mixer.numSoundsPlaying++;
   1.200 +	
   1.201 +	/* point channel data to wav data */
   1.202 +	mixer.channels[selected_channel].position	= s->buffer;
   1.203 +	mixer.channels[selected_channel].remaining	= s->length;
   1.204 +	mixer.channels[selected_channel].timestamp  = SDL_GetTicks();
   1.205 +	
   1.206 +	return selected_channel;
   1.207 +}
   1.208 +
   1.209 +/* 
   1.210 +	Called from SDL's audio system.  Supplies sound input with data by mixing together all
   1.211 +	currently playing sound effects.
   1.212 +*/
   1.213 +void audioCallback(void *userdata, Uint8 *stream, int len) {
   1.214 +	int i;
   1.215 +	int copy_amt;
   1.216 +	SDL_memset(stream, mixer.outputSpec.silence, len); /* initialize buffer to silence */
   1.217 +	/* for each channel, mix in whatever is playing on that channel */
   1.218 +	for (i=0; i<NUM_CHANNELS; i++) {		
   1.219 +		if (mixer.channels[i].position == NULL) {
   1.220 +			/* if no sound is playing on this channel */ 
   1.221 +			continue; /* nothing to do for this channel */
   1.222 +		}
   1.223 +		
   1.224 +		/* copy len bytes to the buffer, unless we have fewer than len bytes remaining */
   1.225 +		copy_amt = mixer.channels[i].remaining < len ? mixer.channels[i].remaining : len;
   1.226 +		
   1.227 +		/* mix this sound effect with the output */
   1.228 +		SDL_MixAudioFormat(stream, mixer.channels[i].position, mixer.outputSpec.format, copy_amt, 150);
   1.229 +		
   1.230 +		/* update buffer position in sound effect and the number of bytes left */
   1.231 +		mixer.channels[i].position    += copy_amt;
   1.232 +		mixer.channels[i].remaining   -= copy_amt;
   1.233 +		
   1.234 +		/* did we finish playing the sound effect ? */
   1.235 +		if (mixer.channels[i].remaining == 0) {
   1.236 +			mixer.channels[i].position = NULL; /* indicates no sound playing on channel anymore */
   1.237 +			mixer.numSoundsPlaying--;
   1.238 +			if (mixer.numSoundsPlaying == 0) {
   1.239 +				/* if no sounds left playing, pause audio callback */
   1.240 +				SDL_PauseAudio(1);
   1.241 +			}
   1.242 +		}
   1.243 +	}
   1.244 +}
   1.245 +
   1.246 +int main(int argc, char *argv[]) {
   1.247 +	
   1.248 +	int done;				/* has user tried to quit ? */
   1.249 +	SDL_WindowID windowID;	/* our main window */
   1.250 +	SDL_Event event;	
   1.251 +	Uint32 startFrame;		/* holds when frame started processing */
   1.252 +	Uint32 endFrame;		/* holds when frame ended processing */
   1.253 +	Uint32 delay;			/* calculated delay, how long should we wait before next frame? */
   1.254 +	
   1.255 +	if (SDL_Init(SDL_INIT_VIDEO|SDL_INIT_AUDIO) < 0) {
   1.256 +		fatalError("could not initialize SDL");
   1.257 +	}
   1.258 +	windowID = SDL_CreateWindow(NULL, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_OPENGL | SDL_WINDOW_BORDERLESS);
   1.259 +	SDL_CreateRenderer(windowID, 0, 0);		
   1.260 +	
   1.261 +	/* initialize the mixer */
   1.262 +	SDL_memset(&mixer, 0, sizeof(mixer));
   1.263 +	/* setup output format */
   1.264 +	mixer.outputSpec.freq = 44100;
   1.265 +	mixer.outputSpec.format = AUDIO_S16LSB;
   1.266 +	mixer.outputSpec.channels = 2;
   1.267 +	mixer.outputSpec.samples = 256;
   1.268 +	mixer.outputSpec.callback = audioCallback;
   1.269 +	mixer.outputSpec.userdata = NULL;	
   1.270 +	
   1.271 +	/* open audio for output */
   1.272 +	if (SDL_OpenAudio(&mixer.outputSpec, NULL) != 0) {
   1.273 +		fatalError("Opening audio failed");
   1.274 +	}	
   1.275 +	
   1.276 +	/* load our drum noises */
   1.277 +	loadSound("ds_kick_big_amb.wav", &drums[3]);
   1.278 +	loadSound("ds_brush_snare.wav", &drums[2]);
   1.279 +	loadSound("ds_loose_skin_mute.wav", &drums[1]);
   1.280 +	loadSound("ds_china.wav", &drums[0]);
   1.281 +
   1.282 +	/* setup positions, colors, and state of buttons */
   1.283 +	initializeButtons();
   1.284 +	
   1.285 +	/* enter main loop */
   1.286 +	done = 0;
   1.287 +	while(!done) {
   1.288 +		startFrame = SDL_GetTicks();
   1.289 +        while (SDL_PollEvent(&event)) {
   1.290 +			switch(event.type) {
   1.291 +				case SDL_MOUSEBUTTONDOWN:
   1.292 +					handleMouseButtonDown(&event);
   1.293 +					break;
   1.294 +				case SDL_MOUSEBUTTONUP:
   1.295 +					handleMouseButtonUp(&event);
   1.296 +					break;
   1.297 +				case SDL_QUIT:
   1.298 +					done = 1;
   1.299 +					break;
   1.300 +			}
   1.301 +        }
   1.302 +		render(); /* draw buttons */
   1.303 +		endFrame = SDL_GetTicks();
   1.304 +		
   1.305 +		/* figure out how much time we have left, and then sleep */
   1.306 +		delay = MILLESECONDS_PER_FRAME - (endFrame - startFrame);
   1.307 +		if (delay < 0) {
   1.308 +			delay = 0;
   1.309 +		} else if (delay > MILLESECONDS_PER_FRAME) {
   1.310 +			delay = MILLESECONDS_PER_FRAME;
   1.311 +		}
   1.312 +		SDL_Delay(delay);
   1.313 +	}
   1.314 +	
   1.315 +	/* cleanup code, let's free up those sound buffers */
   1.316 +	int i;
   1.317 +	for (i=0; i<NUM_DRUMS; i++) {
   1.318 +		SDL_free(drums[i].buffer);
   1.319 +	}
   1.320 +	/* let SDL do its exit code */
   1.321 +	SDL_Quit();
   1.322 +	
   1.323 +	return 0;
   1.324 +}
   1.325 \ No newline at end of file