mixer.c 11 KB

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