| /* |
| * mixer.c |
| * written by Holmes Futrell |
| * use however you want |
| */ |
| |
| #include "SDL.h" |
| #include "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 */ |
| |
| 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 initializeButtons(SDL_Renderer *); |
| 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(SDL_Renderer *renderer) |
| { |
| 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 */ |
| int renderW, renderH; |
| |
| SDL_RenderGetLogicalSize(renderer, &renderW, &renderH); |
| |
| buttonRect.x = spacing; |
| buttonRect.y = spacing; |
| buttonRect.w = renderW - 2 * spacing; |
| buttonRect.h = (renderH - (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 = 0; |
| drumIndex = -1; |
| |
| 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 = 0; |
| /* 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(SDL_Renderer *renderer) |
| { |
| int i; |
| SDL_SetRenderDrawColor(renderer, 50, 50, 50, 255); |
| SDL_RenderClear(renderer); /* 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_SetRenderDrawColor(renderer, color.r, color.g, color.b, color.a); |
| SDL_RenderFillRect(renderer, &buttons[i].rect); |
| } |
| /* update the screen */ |
| SDL_RenderPresent(renderer); |
| } |
| |
| /* |
| 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, SDL_MIX_MAXVOLUME); |
| |
| /* 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_Window *window; /* main window */ |
| SDL_Renderer *renderer; |
| SDL_Event event; |
| int i; |
| int width; |
| int height; |
| |
| if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0) { |
| fatalError("could not initialize SDL"); |
| } |
| window = SDL_CreateWindow(NULL, 0, 0, 320, 480, SDL_WINDOW_BORDERLESS | SDL_WINDOW_ALLOW_HIGHDPI); |
| renderer = SDL_CreateRenderer(window, 0, 0); |
| |
| SDL_GetWindowSize(window, &width, &height); |
| SDL_RenderSetLogicalSize(renderer, width, height); |
| |
| /* 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(renderer); |
| |
| /* enter main loop */ |
| done = 0; |
| while (!done) { |
| 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(renderer); /* draw buttons */ |
| |
| SDL_Delay(1); |
| } |
| |
| /* cleanup code, let's free up those sound buffers */ |
| for (i = 0; i < NUM_DRUMS; i++) { |
| SDL_free(drums[i].buffer); |
| } |
| /* let SDL do its exit code */ |
| SDL_Quit(); |
| |
| return 0; |
| } |