mixer.c (11676B)
1/* 2 * mixer.c 3 * written by Holmes Futrell 4 * use however you want 5 */ 6 7#import "SDL.h" 8#import "common.h" 9 10#define NUM_CHANNELS 8 /* max number of sounds we can play at once */ 11#define NUM_DRUMS 4 /* number of drums in our set */ 12#define MILLESECONDS_PER_FRAME 16 /* about 60 frames per second */ 13 14static struct 15{ 16 SDL_Rect rect; /* where the button is drawn */ 17 SDL_Color upColor; /* color when button is not active */ 18 SDL_Color downColor; /* color when button is active */ 19 int isPressed; /* is the button being pressed ? */ 20 int touchIndex; /* what mouse (touch) index pressed the button ? */ 21} buttons[NUM_DRUMS]; 22 23struct sound 24{ 25 Uint8 *buffer; /* audio buffer for sound file */ 26 Uint32 length; /* length of the buffer (in bytes) */ 27}; 28 29/* this array holds the audio for the drum noises */ 30static struct sound drums[NUM_DRUMS]; 31 32/* function declarations */ 33void handleMouseButtonDown(SDL_Event * event); 34void handleMouseButtonUp(SDL_Event * event); 35int playSound(struct sound *); 36void initializeButtons(); 37void audioCallback(void *userdata, Uint8 * stream, int len); 38void loadSound(const char *file, struct sound *s); 39 40struct 41{ 42 /* channel array holds information about currently playing sounds */ 43 struct 44 { 45 Uint8 *position; /* what is the current position in the buffer of this sound ? */ 46 Uint32 remaining; /* how many bytes remaining before we're done playing the sound ? */ 47 Uint32 timestamp; /* when did this sound start playing ? */ 48 } channels[NUM_CHANNELS]; 49 SDL_AudioSpec outputSpec; /* what audio format are we using for output? */ 50 int numSoundsPlaying; /* how many sounds are currently playing */ 51} mixer; 52 53/* sets up the buttons (color, position, state) */ 54void 55initializeButtons() 56{ 57 58 int i; 59 int spacing = 10; /* gap between drum buttons */ 60 SDL_Rect buttonRect; /* keeps track of where to position drum */ 61 SDL_Color upColor = { 86, 86, 140, 255 }; /* color of drum when not pressed */ 62 SDL_Color downColor = { 191, 191, 221, 255 }; /* color of drum when pressed */ 63 64 buttonRect.x = spacing; 65 buttonRect.y = spacing; 66 buttonRect.w = SCREEN_WIDTH - 2 * spacing; 67 buttonRect.h = (SCREEN_HEIGHT - (NUM_DRUMS + 1) * spacing) / NUM_DRUMS; 68 69 /* setup each button */ 70 for (i = 0; i < NUM_DRUMS; i++) { 71 72 buttons[i].rect = buttonRect; 73 buttons[i].isPressed = 0; 74 buttons[i].upColor = upColor; 75 buttons[i].downColor = downColor; 76 77 buttonRect.y += spacing + buttonRect.h; /* setup y coordinate for next drum */ 78 79 } 80} 81 82/* 83 loads a wav file (stored in 'file'), converts it to the mixer's output format, 84 and stores the resulting buffer and length in the sound structure 85 */ 86void 87loadSound(const char *file, struct sound *s) 88{ 89 SDL_AudioSpec spec; /* the audio format of the .wav file */ 90 SDL_AudioCVT cvt; /* used to convert .wav to output format when formats differ */ 91 int result; 92 if (SDL_LoadWAV(file, &spec, &s->buffer, &s->length) == NULL) { 93 fatalError("could not load .wav"); 94 } 95 /* build the audio converter */ 96 result = SDL_BuildAudioCVT(&cvt, spec.format, spec.channels, spec.freq, 97 mixer.outputSpec.format, 98 mixer.outputSpec.channels, 99 mixer.outputSpec.freq); 100 if (result == -1) { 101 fatalError("could not build audio CVT"); 102 } else if (result != 0) { 103 /* 104 this happens when the .wav format differs from the output format. 105 we convert the .wav buffer here 106 */ 107 cvt.buf = (Uint8 *) SDL_malloc(s->length * cvt.len_mult); /* allocate conversion buffer */ 108 cvt.len = s->length; /* set conversion buffer length */ 109 SDL_memcpy(cvt.buf, s->buffer, s->length); /* copy sound to conversion buffer */ 110 if (SDL_ConvertAudio(&cvt) == -1) { /* convert the sound */ 111 fatalError("could not convert .wav"); 112 } 113 SDL_free(s->buffer); /* free the original (unconverted) buffer */ 114 s->buffer = cvt.buf; /* point sound buffer to converted buffer */ 115 s->length = cvt.len_cvt; /* set sound buffer's new length */ 116 } 117} 118 119/* called from main event loop */ 120void 121handleMouseButtonDown(SDL_Event * event) 122{ 123 124 int x, y, mouseIndex, i, drumIndex; 125 126 mouseIndex = 0; 127 drumIndex = -1; 128 129 SDL_GetMouseState(&x, &y); 130 /* check if we hit any of the drum buttons */ 131 for (i = 0; i < NUM_DRUMS; i++) { 132 if (x >= buttons[i].rect.x 133 && x < buttons[i].rect.x + buttons[i].rect.w 134 && y >= buttons[i].rect.y 135 && y < buttons[i].rect.y + buttons[i].rect.h) { 136 drumIndex = i; 137 break; 138 } 139 } 140 if (drumIndex != -1) { 141 /* if we hit a button */ 142 buttons[drumIndex].touchIndex = mouseIndex; 143 buttons[drumIndex].isPressed = 1; 144 playSound(&drums[drumIndex]); 145 } 146 147} 148 149/* called from main event loop */ 150void 151handleMouseButtonUp(SDL_Event * event) 152{ 153 int i; 154 int mouseIndex = 0; 155 /* check if this should cause any of the buttons to become unpressed */ 156 for (i = 0; i < NUM_DRUMS; i++) { 157 if (buttons[i].touchIndex == mouseIndex) { 158 buttons[i].isPressed = 0; 159 } 160 } 161} 162 163/* draws buttons to screen */ 164void 165render(SDL_Renderer *renderer) 166{ 167 int i; 168 SDL_SetRenderDrawColor(renderer, 50, 50, 50, 255); 169 SDL_RenderClear(renderer); /* draw background (gray) */ 170 /* draw the drum buttons */ 171 for (i = 0; i < NUM_DRUMS; i++) { 172 SDL_Color color = 173 buttons[i].isPressed ? buttons[i].downColor : buttons[i].upColor; 174 SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, color.a); 175 SDL_RenderFillRect(renderer, &buttons[i].rect); 176 } 177 /* update the screen */ 178 SDL_RenderPresent(renderer); 179} 180 181/* 182 finds a sound channel in the mixer for a sound 183 and sets it up to start playing 184*/ 185int 186playSound(struct sound *s) 187{ 188 /* 189 find an empty channel to play on. 190 if no channel is available, use oldest channel 191 */ 192 int i; 193 int selected_channel = -1; 194 int oldest_channel = 0; 195 196 if (mixer.numSoundsPlaying == 0) { 197 /* we're playing a sound now, so start audio callback back up */ 198 SDL_PauseAudio(0); 199 } 200 201 /* find a sound channel to play the sound on */ 202 for (i = 0; i < NUM_CHANNELS; i++) { 203 if (mixer.channels[i].position == NULL) { 204 /* if no sound on this channel, select it */ 205 selected_channel = i; 206 break; 207 } 208 /* if this channel's sound is older than the oldest so far, set it to oldest */ 209 if (mixer.channels[i].timestamp < 210 mixer.channels[oldest_channel].timestamp) 211 oldest_channel = i; 212 } 213 214 /* no empty channels, take the oldest one */ 215 if (selected_channel == -1) 216 selected_channel = oldest_channel; 217 else 218 mixer.numSoundsPlaying++; 219 220 /* point channel data to wav data */ 221 mixer.channels[selected_channel].position = s->buffer; 222 mixer.channels[selected_channel].remaining = s->length; 223 mixer.channels[selected_channel].timestamp = SDL_GetTicks(); 224 225 return selected_channel; 226} 227 228/* 229 Called from SDL's audio system. Supplies sound input with data by mixing together all 230 currently playing sound effects. 231*/ 232void 233audioCallback(void *userdata, Uint8 * stream, int len) 234{ 235 int i; 236 int copy_amt; 237 SDL_memset(stream, mixer.outputSpec.silence, len); /* initialize buffer to silence */ 238 /* for each channel, mix in whatever is playing on that channel */ 239 for (i = 0; i < NUM_CHANNELS; i++) { 240 if (mixer.channels[i].position == NULL) { 241 /* if no sound is playing on this channel */ 242 continue; /* nothing to do for this channel */ 243 } 244 245 /* copy len bytes to the buffer, unless we have fewer than len bytes remaining */ 246 copy_amt = 247 mixer.channels[i].remaining < 248 len ? mixer.channels[i].remaining : len; 249 250 /* mix this sound effect with the output */ 251 SDL_MixAudioFormat(stream, mixer.channels[i].position, 252 mixer.outputSpec.format, copy_amt, 150); 253 254 /* update buffer position in sound effect and the number of bytes left */ 255 mixer.channels[i].position += copy_amt; 256 mixer.channels[i].remaining -= copy_amt; 257 258 /* did we finish playing the sound effect ? */ 259 if (mixer.channels[i].remaining == 0) { 260 mixer.channels[i].position = NULL; /* indicates no sound playing on channel anymore */ 261 mixer.numSoundsPlaying--; 262 if (mixer.numSoundsPlaying == 0) { 263 /* if no sounds left playing, pause audio callback */ 264 SDL_PauseAudio(1); 265 } 266 } 267 } 268} 269 270int 271main(int argc, char *argv[]) 272{ 273 274 int done; /* has user tried to quit ? */ 275 SDL_Window *window; /* main window */ 276 SDL_Renderer *renderer; 277 SDL_Event event; 278 Uint32 startFrame; /* holds when frame started processing */ 279 Uint32 endFrame; /* holds when frame ended processing */ 280 Uint32 delay; /* calculated delay, how long should we wait before next frame? */ 281 282 if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0) { 283 fatalError("could not initialize SDL"); 284 } 285 window = 286 SDL_CreateWindow(NULL, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, 287 SDL_WINDOW_OPENGL | SDL_WINDOW_BORDERLESS); 288 renderer = SDL_CreateRenderer(window, 0, 0); 289 290 /* initialize the mixer */ 291 SDL_memset(&mixer, 0, sizeof(mixer)); 292 /* setup output format */ 293 mixer.outputSpec.freq = 44100; 294 mixer.outputSpec.format = AUDIO_S16LSB; 295 mixer.outputSpec.channels = 2; 296 mixer.outputSpec.samples = 256; 297 mixer.outputSpec.callback = audioCallback; 298 mixer.outputSpec.userdata = NULL; 299 300 /* open audio for output */ 301 if (SDL_OpenAudio(&mixer.outputSpec, NULL) != 0) { 302 fatalError("Opening audio failed"); 303 } 304 305 /* load our drum noises */ 306 loadSound("ds_kick_big_amb.wav", &drums[3]); 307 loadSound("ds_brush_snare.wav", &drums[2]); 308 loadSound("ds_loose_skin_mute.wav", &drums[1]); 309 loadSound("ds_china.wav", &drums[0]); 310 311 /* setup positions, colors, and state of buttons */ 312 initializeButtons(); 313 314 /* enter main loop */ 315 done = 0; 316 while (!done) { 317 startFrame = SDL_GetTicks(); 318 while (SDL_PollEvent(&event)) { 319 switch (event.type) { 320 case SDL_MOUSEBUTTONDOWN: 321 handleMouseButtonDown(&event); 322 break; 323 case SDL_MOUSEBUTTONUP: 324 handleMouseButtonUp(&event); 325 break; 326 case SDL_QUIT: 327 done = 1; 328 break; 329 } 330 } 331 render(renderer); /* draw buttons */ 332 endFrame = SDL_GetTicks(); 333 334 /* figure out how much time we have left, and then sleep */ 335 delay = MILLESECONDS_PER_FRAME - (endFrame - startFrame); 336 if (delay < 0) { 337 delay = 0; 338 } else if (delay > MILLESECONDS_PER_FRAME) { 339 delay = MILLESECONDS_PER_FRAME; 340 } 341 SDL_Delay(delay); 342 } 343 344 /* cleanup code, let's free up those sound buffers */ 345 int i; 346 for (i = 0; i < NUM_DRUMS; i++) { 347 SDL_free(drums[i].buffer); 348 } 349 /* let SDL do its exit code */ 350 SDL_Quit(); 351 352 return 0; 353}