• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
initializeButtons(SDL_Renderer * renderer)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
loadSound(const char * file,struct sound * s)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
handleMouseButtonDown(SDL_Event * event)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
handleMouseButtonUp(SDL_Event * event)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
render(SDL_Renderer * renderer)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
playSound(struct sound * s)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
audioCallback(void * userdata,Uint8 * stream,int len)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, 150);
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
main(int argc,char * argv[])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 }
345