cscg22-gearboy

CSCG 2022 Challenge 'Gearboy'
git clone https://git.sinitax.com/sinitax/cscg22-gearboy
Log | Files | Refs | sfeed.txt

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}