sdl

FORK: Simple Directmedia Layer
git clone https://git.neptards.moe/neptards/sdl.git
Log | Files | Refs

mixer.c (11217B)


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