/* * 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; }