mixer.c 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353
  1. /*
  2. * mixer.c
  3. * written by Holmes Futrell
  4. * use however you want
  5. */
  6. #import "SDL.h"
  7. #import "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. #define MILLESECONDS_PER_FRAME 16 /* about 60 frames per second */
  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();
  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()
  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. buttonRect.x = spacing;
  55. buttonRect.y = spacing;
  56. buttonRect.w = SCREEN_WIDTH - 2 * spacing;
  57. buttonRect.h = (SCREEN_HEIGHT - (NUM_DRUMS + 1) * spacing) / NUM_DRUMS;
  58. /* setup each button */
  59. for (i = 0; i < NUM_DRUMS; i++) {
  60. buttons[i].rect = buttonRect;
  61. buttons[i].isPressed = 0;
  62. buttons[i].upColor = upColor;
  63. buttons[i].downColor = downColor;
  64. buttonRect.y += spacing + buttonRect.h; /* setup y coordinate for next drum */
  65. }
  66. }
  67. /*
  68. loads a wav file (stored in 'file'), converts it to the mixer's output format,
  69. and stores the resulting buffer and length in the sound structure
  70. */
  71. void
  72. loadSound(const char *file, struct sound *s)
  73. {
  74. SDL_AudioSpec spec; /* the audio format of the .wav file */
  75. SDL_AudioCVT cvt; /* used to convert .wav to output format when formats differ */
  76. int result;
  77. if (SDL_LoadWAV(file, &spec, &s->buffer, &s->length) == NULL) {
  78. fatalError("could not load .wav");
  79. }
  80. /* build the audio converter */
  81. result = SDL_BuildAudioCVT(&cvt, spec.format, spec.channels, spec.freq,
  82. mixer.outputSpec.format,
  83. mixer.outputSpec.channels,
  84. mixer.outputSpec.freq);
  85. if (result == -1) {
  86. fatalError("could not build audio CVT");
  87. } else if (result != 0) {
  88. /*
  89. this happens when the .wav format differs from the output format.
  90. we convert the .wav buffer here
  91. */
  92. cvt.buf = (Uint8 *) SDL_malloc(s->length * cvt.len_mult); /* allocate conversion buffer */
  93. cvt.len = s->length; /* set conversion buffer length */
  94. SDL_memcpy(cvt.buf, s->buffer, s->length); /* copy sound to conversion buffer */
  95. if (SDL_ConvertAudio(&cvt) == -1) { /* convert the sound */
  96. fatalError("could not convert .wav");
  97. }
  98. SDL_free(s->buffer); /* free the original (unconverted) buffer */
  99. s->buffer = cvt.buf; /* point sound buffer to converted buffer */
  100. s->length = cvt.len_cvt; /* set sound buffer's new length */
  101. }
  102. }
  103. /* called from main event loop */
  104. void
  105. handleMouseButtonDown(SDL_Event * event)
  106. {
  107. int x, y, mouseIndex, i, drumIndex;
  108. mouseIndex = 0;
  109. drumIndex = -1;
  110. SDL_GetMouseState(&x, &y);
  111. /* check if we hit any of the drum buttons */
  112. for (i = 0; i < NUM_DRUMS; i++) {
  113. if (x >= buttons[i].rect.x
  114. && x < buttons[i].rect.x + buttons[i].rect.w
  115. && y >= buttons[i].rect.y
  116. && y < buttons[i].rect.y + buttons[i].rect.h) {
  117. drumIndex = i;
  118. break;
  119. }
  120. }
  121. if (drumIndex != -1) {
  122. /* if we hit a button */
  123. buttons[drumIndex].touchIndex = mouseIndex;
  124. buttons[drumIndex].isPressed = 1;
  125. playSound(&drums[drumIndex]);
  126. }
  127. }
  128. /* called from main event loop */
  129. void
  130. handleMouseButtonUp(SDL_Event * event)
  131. {
  132. int i;
  133. int mouseIndex = 0;
  134. /* check if this should cause any of the buttons to become unpressed */
  135. for (i = 0; i < NUM_DRUMS; i++) {
  136. if (buttons[i].touchIndex == mouseIndex) {
  137. buttons[i].isPressed = 0;
  138. }
  139. }
  140. }
  141. /* draws buttons to screen */
  142. void
  143. render(SDL_Renderer *renderer)
  144. {
  145. int i;
  146. SDL_SetRenderDrawColor(renderer, 50, 50, 50, 255);
  147. SDL_RenderClear(renderer); /* draw background (gray) */
  148. /* draw the drum buttons */
  149. for (i = 0; i < NUM_DRUMS; i++) {
  150. SDL_Color color =
  151. buttons[i].isPressed ? buttons[i].downColor : buttons[i].upColor;
  152. SDL_SetRenderDrawColor(renderer, color.r, color.g, color.b, color.a);
  153. SDL_RenderFillRect(renderer, &buttons[i].rect);
  154. }
  155. /* update the screen */
  156. SDL_RenderPresent(renderer);
  157. }
  158. /*
  159. finds a sound channel in the mixer for a sound
  160. and sets it up to start playing
  161. */
  162. int
  163. playSound(struct sound *s)
  164. {
  165. /*
  166. find an empty channel to play on.
  167. if no channel is available, use oldest channel
  168. */
  169. int i;
  170. int selected_channel = -1;
  171. int oldest_channel = 0;
  172. if (mixer.numSoundsPlaying == 0) {
  173. /* we're playing a sound now, so start audio callback back up */
  174. SDL_PauseAudio(0);
  175. }
  176. /* find a sound channel to play the sound on */
  177. for (i = 0; i < NUM_CHANNELS; i++) {
  178. if (mixer.channels[i].position == NULL) {
  179. /* if no sound on this channel, select it */
  180. selected_channel = i;
  181. break;
  182. }
  183. /* if this channel's sound is older than the oldest so far, set it to oldest */
  184. if (mixer.channels[i].timestamp <
  185. mixer.channels[oldest_channel].timestamp)
  186. oldest_channel = i;
  187. }
  188. /* no empty channels, take the oldest one */
  189. if (selected_channel == -1)
  190. selected_channel = oldest_channel;
  191. else
  192. mixer.numSoundsPlaying++;
  193. /* point channel data to wav data */
  194. mixer.channels[selected_channel].position = s->buffer;
  195. mixer.channels[selected_channel].remaining = s->length;
  196. mixer.channels[selected_channel].timestamp = SDL_GetTicks();
  197. return selected_channel;
  198. }
  199. /*
  200. Called from SDL's audio system. Supplies sound input with data by mixing together all
  201. currently playing sound effects.
  202. */
  203. void
  204. audioCallback(void *userdata, Uint8 * stream, int len)
  205. {
  206. int i;
  207. int copy_amt;
  208. SDL_memset(stream, mixer.outputSpec.silence, len); /* initialize buffer to silence */
  209. /* for each channel, mix in whatever is playing on that channel */
  210. for (i = 0; i < NUM_CHANNELS; i++) {
  211. if (mixer.channels[i].position == NULL) {
  212. /* if no sound is playing on this channel */
  213. continue; /* nothing to do for this channel */
  214. }
  215. /* copy len bytes to the buffer, unless we have fewer than len bytes remaining */
  216. copy_amt =
  217. mixer.channels[i].remaining <
  218. len ? mixer.channels[i].remaining : len;
  219. /* mix this sound effect with the output */
  220. SDL_MixAudioFormat(stream, mixer.channels[i].position,
  221. mixer.outputSpec.format, copy_amt, 150);
  222. /* update buffer position in sound effect and the number of bytes left */
  223. mixer.channels[i].position += copy_amt;
  224. mixer.channels[i].remaining -= copy_amt;
  225. /* did we finish playing the sound effect ? */
  226. if (mixer.channels[i].remaining == 0) {
  227. mixer.channels[i].position = NULL; /* indicates no sound playing on channel anymore */
  228. mixer.numSoundsPlaying--;
  229. if (mixer.numSoundsPlaying == 0) {
  230. /* if no sounds left playing, pause audio callback */
  231. SDL_PauseAudio(1);
  232. }
  233. }
  234. }
  235. }
  236. int
  237. main(int argc, char *argv[])
  238. {
  239. int done; /* has user tried to quit ? */
  240. SDL_Window *window; /* main window */
  241. SDL_Renderer *renderer;
  242. SDL_Event event;
  243. Uint32 startFrame; /* holds when frame started processing */
  244. Uint32 endFrame; /* holds when frame ended processing */
  245. Uint32 delay; /* calculated delay, how long should we wait before next frame? */
  246. if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO) < 0) {
  247. fatalError("could not initialize SDL");
  248. }
  249. window =
  250. SDL_CreateWindow(NULL, 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT,
  251. SDL_WINDOW_OPENGL | SDL_WINDOW_BORDERLESS);
  252. renderer = SDL_CreateRenderer(window, 0, 0);
  253. /* initialize the mixer */
  254. SDL_memset(&mixer, 0, sizeof(mixer));
  255. /* setup output format */
  256. mixer.outputSpec.freq = 44100;
  257. mixer.outputSpec.format = AUDIO_S16LSB;
  258. mixer.outputSpec.channels = 2;
  259. mixer.outputSpec.samples = 256;
  260. mixer.outputSpec.callback = audioCallback;
  261. mixer.outputSpec.userdata = NULL;
  262. /* open audio for output */
  263. if (SDL_OpenAudio(&mixer.outputSpec, NULL) != 0) {
  264. fatalError("Opening audio failed");
  265. }
  266. /* load our drum noises */
  267. loadSound("ds_kick_big_amb.wav", &drums[3]);
  268. loadSound("ds_brush_snare.wav", &drums[2]);
  269. loadSound("ds_loose_skin_mute.wav", &drums[1]);
  270. loadSound("ds_china.wav", &drums[0]);
  271. /* setup positions, colors, and state of buttons */
  272. initializeButtons();
  273. /* enter main loop */
  274. done = 0;
  275. while (!done) {
  276. startFrame = SDL_GetTicks();
  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. endFrame = SDL_GetTicks();
  292. /* figure out how much time we have left, and then sleep */
  293. delay = MILLESECONDS_PER_FRAME - (endFrame - startFrame);
  294. if (delay < 0) {
  295. delay = 0;
  296. } else if (delay > MILLESECONDS_PER_FRAME) {
  297. delay = MILLESECONDS_PER_FRAME;
  298. }
  299. SDL_Delay(delay);
  300. }
  301. /* cleanup code, let's free up those sound buffers */
  302. int i;
  303. for (i = 0; i < NUM_DRUMS; i++) {
  304. SDL_free(drums[i].buffer);
  305. }
  306. /* let SDL do its exit code */
  307. SDL_Quit();
  308. return 0;
  309. }