mixer.c 11 KB

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