Drum kit demo (SDL_mixer) gsoc2008_iphone
authorHolmes Futrell <hfutrell@umail.ucsb.edu>
Fri, 18 Jul 2008 20:51:59 +0000
branchgsoc2008_iphone
changeset 2384 6a946f3155d8
parent 2383 1cfe7fd15dad
child 2385 0705e8ebe951
Drum kit demo (SDL_mixer)
XCodeiPhoneOS/Demos/src/mixer.c
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/XCodeiPhoneOS/Demos/src/mixer.c	Fri Jul 18 20:51:59 2008 +0000
@@ -0,0 +1,321 @@
+/*
+ *	mixer.c
+ *	written by Holmes Futrell
+ *	use however you want
+ */
+
+#import "SDL.h"
+#import "common.h"
+
+#define NUM_CHANNELS 8				/* max number of sounds we can play at once */
+#define NUM_DRUMS 4					/* number of drums in our set */
+#define MILLESECONDS_PER_FRAME 16	/* about 60 frames per second */
+
+static struct {
+	SDL_Rect rect;			/* where the button is drawn */
+	SDL_Color upColor;		/* color when button is not active */
+	SDL_Color downColor;	/* color when button is active */
+	int isPressed;			/* is the button being pressed ? */
+	int touchIndex;			/* what mouse (touch) index pressed the button ? */
+} buttons[NUM_DRUMS];
+
+struct sound {
+	Uint8 *buffer;			/* audio buffer for sound file */
+	Uint32 length;			/* length of the buffer (in bytes) */
+};
+
+/* this array holds the audio for the drum noises */
+static struct sound drums[NUM_DRUMS];
+
+/* function declarations */
+void handleMouseButtonDown(SDL_Event *event);
+void handleMouseButtonUp(SDL_Event *event);
+int playSound(struct sound *);
+void render(void);
+void initializeButtons();
+void audioCallback(void *userdata, Uint8 *stream, int len);
+void loadSound(const char *file, struct sound *s);
+
+struct {
+	/* channel array holds information about currently playing sounds */
+	struct {
+		Uint8 *position;		/* what is the current position in the buffer of this sound ? */
+		Uint32 remaining;		/* how many bytes remaining before we're done playing the sound ? */
+		Uint32 timestamp;		/* when did this sound start playing ? */
+	} channels[NUM_CHANNELS];
+	SDL_AudioSpec outputSpec;	/* what audio format are we using for output? */
+	int numSoundsPlaying;		/* how many sounds are currently playing */
+} mixer;
+
+/* sets up the buttons (color, position, state) */
+void initializeButtons() {
+	
+	int i;
+	int spacing = 10;														/* gap between drum buttons */
+	SDL_Rect buttonRect;													/* keeps track of where to position drum */
+	SDL_Color upColor = { 86, 86, 140, 255 };								/* color of drum when not pressed */
+	SDL_Color downColor = { 191, 191, 221, 255 };							/* color of drum when pressed */
+	
+	buttonRect.x = spacing;
+	buttonRect.y = spacing;
+	buttonRect.w = SCREEN_WIDTH - 2 * spacing;
+	buttonRect.h = ( SCREEN_HEIGHT - (NUM_DRUMS + 1) * spacing ) / NUM_DRUMS;
+	
+	/* setup each button */
+	for (i=0; i<NUM_DRUMS; i++) {
+		
+		buttons[i].rect = buttonRect;
+		buttons[i].isPressed = 0;
+		buttons[i].upColor = upColor;
+		buttons[i].downColor = downColor;
+		
+		buttonRect.y += spacing + buttonRect.h;								/* setup y coordinate for next drum */
+		
+	}	
+}
+/*
+ loads a wav file (stored in 'file'), converts it to the mixer's output format,
+ and stores the resulting buffer and length in the sound structure
+ */
+void loadSound(const char *file, struct sound *s) {
+	SDL_AudioSpec spec;		/* the audio format of the .wav file */
+	SDL_AudioCVT cvt;		/* used to convert .wav to output format when formats differ */
+	int result;
+	if (SDL_LoadWAV(file, &spec, &s->buffer, &s->length) == NULL) {
+		fatalError("could not load .wav");
+	}
+	/* build the audio converter */
+	result = SDL_BuildAudioCVT(&cvt, spec.format, spec.channels, spec.freq,
+							   mixer.outputSpec.format, mixer.outputSpec.channels, mixer.outputSpec.freq);
+	if (result == -1) {
+		fatalError("could not build audio CVT");
+	}
+	else if (result != 0) {
+		/* 
+		 this happens when the .wav format differs from the output format.
+		 we convert the .wav buffer here
+		 */
+		cvt.buf = (Uint8 *)SDL_malloc(s->length * cvt.len_mult); /* allocate conversion buffer */
+		cvt.len = s->length;									  /* set conversion buffer length */
+		SDL_memcpy(cvt.buf, s->buffer, s->length);				  /* copy sound to conversion buffer */
+		if (SDL_ConvertAudio(&cvt) == -1) {					  /* convert the sound */
+			fatalError("could not convert .wav");
+		}
+		SDL_free(s->buffer);									  /* free the original (unconverted) buffer */
+		s->buffer = cvt.buf;									  /* point sound buffer to converted buffer */
+		s->length = cvt.len_cvt;								  /* set sound buffer's new length */
+	}
+}
+
+/* called from main event loop */
+void handleMouseButtonDown(SDL_Event *event) {
+
+	int x, y, mouseIndex, i, drumIndex;
+	
+	mouseIndex = event->button.which;
+	drumIndex = -1;
+	
+	SDL_SelectMouse(mouseIndex);
+	SDL_GetMouseState(&x, &y);
+	/* check if we hit any of the drum buttons */
+	for (i=0; i<NUM_DRUMS; i++) {
+		if (x >= buttons[i].rect.x && x < buttons[i].rect.x + buttons[i].rect.w \
+			&& y >= buttons[i].rect.y && y < buttons[i].rect.y + buttons[i].rect.h) {
+			drumIndex = i;
+			break; 
+		}
+	}
+	if (drumIndex != -1) {
+		/* if we hit a button */
+		buttons[drumIndex].touchIndex = mouseIndex;
+		buttons[drumIndex].isPressed = 1;
+		playSound(&drums[drumIndex]);
+	}
+	
+}
+
+/* called from main event loop */
+void handleMouseButtonUp(SDL_Event *event) {
+	int i;
+	int mouseIndex = event->button.which;
+	/* check if this should cause any of the buttons to become unpressed */
+	for (i=0; i<NUM_DRUMS; i++) {
+		if (buttons[i].touchIndex == mouseIndex) {
+			buttons[i].isPressed = 0;
+		}
+	}
+}
+
+/* draws buttons to screen */
+void render(void) {
+	int i;
+	SDL_RenderFill(50, 50, 50, 255, NULL);	/* draw background (gray) */
+	/* draw the drum buttons */
+	for (i=0; i<NUM_DRUMS; i++) {
+		SDL_Color color = buttons[i].isPressed ? buttons[i].downColor : buttons[i].upColor;
+		SDL_RenderFill(color.r, color.g, color.b, color.unused, &buttons[i].rect);
+	}
+	/* update the screen */
+	SDL_RenderPresent();
+}
+
+/*
+	finds a sound channel in the mixer for a sound
+	and sets it up to start playing
+*/
+int playSound(struct sound *s) {
+	/*
+		find an empty channel to play on.
+		if no channel is available, use oldest channel
+	*/
+	int i;
+	int selected_channel = -1;
+	int oldest_channel = 0;
+	
+	if (mixer.numSoundsPlaying == 0) {
+		/* we're playing a sound now, so start audio callback back up */
+		SDL_PauseAudio(0);
+	}	
+	
+	/* find a sound channel to play the sound on */
+	for (i=0; i<NUM_CHANNELS; i++) {
+		if (mixer.channels[i].position == NULL) {
+			/* if no sound on this channel, select it */
+			selected_channel = i;
+			break;
+		}
+		/* if this channel's sound is older than the oldest so far, set it to oldest */
+		if (mixer.channels[i].timestamp < mixer.channels[oldest_channel].timestamp)
+			oldest_channel = i;
+	}
+	
+	/* no empty channels, take the oldest one */
+	if (selected_channel == -1) 
+		selected_channel = oldest_channel;
+	else 
+		mixer.numSoundsPlaying++;
+	
+	/* point channel data to wav data */
+	mixer.channels[selected_channel].position	= s->buffer;
+	mixer.channels[selected_channel].remaining	= s->length;
+	mixer.channels[selected_channel].timestamp  = SDL_GetTicks();
+	
+	return selected_channel;
+}
+
+/* 
+	Called from SDL's audio system.  Supplies sound input with data by mixing together all
+	currently playing sound effects.
+*/
+void audioCallback(void *userdata, Uint8 *stream, int len) {
+	int i;
+	int copy_amt;
+	SDL_memset(stream, mixer.outputSpec.silence, len); /* initialize buffer to silence */
+	/* for each channel, mix in whatever is playing on that channel */
+	for (i=0; i<NUM_CHANNELS; i++) {		
+		if (mixer.channels[i].position == NULL) {
+			/* if no sound is playing on this channel */ 
+			continue; /* nothing to do for this channel */
+		}
+		
+		/* copy len bytes to the buffer, unless we have fewer than len bytes remaining */
+		copy_amt = mixer.channels[i].remaining < len ? mixer.channels[i].remaining : len;
+		
+		/* mix this sound effect with the output */
+		SDL_MixAudioFormat(stream, mixer.channels[i].position, mixer.outputSpec.format, copy_amt, 150);
+		
+		/* update buffer position in sound effect and the number of bytes left */
+		mixer.channels[i].position    += copy_amt;
+		mixer.channels[i].remaining   -= copy_amt;
+		
+		/* did we finish playing the sound effect ? */
+		if (mixer.channels[i].remaining == 0) {
+			mixer.channels[i].position = NULL; /* indicates no sound playing on channel anymore */
+			mixer.numSoundsPlaying--;
+			if (mixer.numSoundsPlaying == 0) {
+				/* if no sounds left playing, pause audio callback */
+				SDL_PauseAudio(1);
+			}
+		}
+	}
+}
+
+int main(int argc, char *argv[]) {
+	
+	int done;				/* has user tried to quit ? */
+	SDL_WindowID windowID;	/* our main window */
+	SDL_Event event;	
+	Uint32 startFrame;		/* holds when frame started processing */
+	Uint32 endFrame;		/* holds when frame ended processing */
+	Uint32 delay;			/* calculated delay, how long should we wait before next frame? */
+	
+	if (SDL_Init(SDL_INIT_VIDEO|SDL_INIT_AUDIO) < 0) {
+		fatalError("could not initialize SDL");
+	}
+	windowID = SDL_CreateWindow(NULL, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, SDL_WINDOW_OPENGL | SDL_WINDOW_BORDERLESS);
+	SDL_CreateRenderer(windowID, 0, 0);		
+	
+	/* initialize the mixer */
+	SDL_memset(&mixer, 0, sizeof(mixer));
+	/* setup output format */
+	mixer.outputSpec.freq = 44100;
+	mixer.outputSpec.format = AUDIO_S16LSB;
+	mixer.outputSpec.channels = 2;
+	mixer.outputSpec.samples = 256;
+	mixer.outputSpec.callback = audioCallback;
+	mixer.outputSpec.userdata = NULL;	
+	
+	/* open audio for output */
+	if (SDL_OpenAudio(&mixer.outputSpec, NULL) != 0) {
+		fatalError("Opening audio failed");
+	}	
+	
+	/* load our drum noises */
+	loadSound("ds_kick_big_amb.wav", &drums[3]);
+	loadSound("ds_brush_snare.wav", &drums[2]);
+	loadSound("ds_loose_skin_mute.wav", &drums[1]);
+	loadSound("ds_china.wav", &drums[0]);
+
+	/* setup positions, colors, and state of buttons */
+	initializeButtons();
+	
+	/* enter main loop */
+	done = 0;
+	while(!done) {
+		startFrame = SDL_GetTicks();
+        while (SDL_PollEvent(&event)) {
+			switch(event.type) {
+				case SDL_MOUSEBUTTONDOWN:
+					handleMouseButtonDown(&event);
+					break;
+				case SDL_MOUSEBUTTONUP:
+					handleMouseButtonUp(&event);
+					break;
+				case SDL_QUIT:
+					done = 1;
+					break;
+			}
+        }
+		render(); /* draw buttons */
+		endFrame = SDL_GetTicks();
+		
+		/* figure out how much time we have left, and then sleep */
+		delay = MILLESECONDS_PER_FRAME - (endFrame - startFrame);
+		if (delay < 0) {
+			delay = 0;
+		} else if (delay > MILLESECONDS_PER_FRAME) {
+			delay = MILLESECONDS_PER_FRAME;
+		}
+		SDL_Delay(delay);
+	}
+	
+	/* cleanup code, let's free up those sound buffers */
+	int i;
+	for (i=0; i<NUM_DRUMS; i++) {
+		SDL_free(drums[i].buffer);
+	}
+	/* let SDL do its exit code */
+	SDL_Quit();
+	
+	return 0;
+}
\ No newline at end of file