testaudio.c 34 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069
  1. #include <stdlib.h>
  2. #ifdef __EMSCRIPTEN__
  3. #include <emscripten/emscripten.h>
  4. #endif
  5. #include <SDL3/SDL_test.h>
  6. #include <SDL3/SDL_test_common.h>
  7. #include <SDL3/SDL_main.h>
  8. #include "testutils.h"
  9. #define POOF_LIFETIME 250
  10. typedef struct Texture
  11. {
  12. SDL_Texture *texture;
  13. float w;
  14. float h;
  15. } Texture;
  16. typedef enum ThingType
  17. {
  18. THING_NULL,
  19. THING_PHYSDEV,
  20. THING_PHYSDEV_CAPTURE,
  21. THING_LOGDEV,
  22. THING_LOGDEV_CAPTURE,
  23. THING_TRASHCAN,
  24. THING_STREAM,
  25. THING_POOF,
  26. THING_WAV
  27. } ThingType;
  28. typedef struct Thing Thing;
  29. struct Thing
  30. {
  31. ThingType what;
  32. union {
  33. struct {
  34. SDL_AudioDeviceID devid;
  35. SDL_bool iscapture;
  36. SDL_AudioSpec spec;
  37. char *name;
  38. } physdev;
  39. struct {
  40. SDL_AudioDeviceID devid;
  41. SDL_bool iscapture;
  42. SDL_AudioSpec spec;
  43. Thing *physdev;
  44. } logdev;
  45. struct {
  46. SDL_AudioSpec spec;
  47. Uint8 *buf;
  48. Uint32 buflen;
  49. } wav;
  50. struct {
  51. float startw;
  52. float starth;
  53. float centerx;
  54. float centery;
  55. } poof;
  56. struct {
  57. SDL_AudioStream *stream;
  58. int total_ticks;
  59. Uint64 next_level_update;
  60. Uint8 levels[5];
  61. } stream;
  62. } data;
  63. Thing *line_connected_to;
  64. char *titlebar;
  65. SDL_FRect rect;
  66. float z;
  67. Uint8 r, g, b, a;
  68. float progress;
  69. float scale;
  70. Uint64 createticks;
  71. Texture *texture;
  72. const ThingType *can_be_dropped_onto;
  73. void (*ontick)(Thing *thing, Uint64 now);
  74. void (*ondrag)(Thing *thing, int button, float x, float y);
  75. void (*ondrop)(Thing *thing, int button, float x, float y);
  76. void (*ondraw)(Thing *thing, SDL_Renderer *renderer);
  77. Thing *prev;
  78. Thing *next;
  79. };
  80. static Uint64 app_ready_ticks = 0;
  81. static int done = 0;
  82. static SDLTest_CommonState *state = NULL;
  83. static Thing *things = NULL;
  84. static char *current_titlebar = NULL;
  85. static Thing *droppable_highlighted_thing = NULL;
  86. static Thing *dragging_thing = NULL;
  87. static int dragging_button = -1;
  88. static Texture *physdev_texture = NULL;
  89. static Texture *logdev_texture = NULL;
  90. static Texture *audio_texture = NULL;
  91. static Texture *trashcan_texture = NULL;
  92. static Texture *soundboard_texture = NULL;
  93. static Texture *soundboard_levels_texture = NULL;
  94. static void DestroyTexture(Texture *tex);
  95. /* Call this instead of exit(), so we can clean up SDL: atexit() is evil. */
  96. static void Quit(int rc)
  97. {
  98. DestroyTexture(physdev_texture);
  99. DestroyTexture(logdev_texture);
  100. DestroyTexture(audio_texture);
  101. DestroyTexture(trashcan_texture);
  102. DestroyTexture(soundboard_texture);
  103. DestroyTexture(soundboard_levels_texture);
  104. SDLTest_CommonQuit(state);
  105. /* Let 'main()' return normally */
  106. if (rc != 0) {
  107. exit(rc);
  108. }
  109. }
  110. static char *xstrdup(const char *str)
  111. {
  112. char *ptr = SDL_strdup(str);
  113. if (!ptr) {
  114. SDL_Log("Out of memory!");
  115. Quit(1);
  116. }
  117. return ptr;
  118. }
  119. static void *xalloc(const size_t len)
  120. {
  121. void *ptr = SDL_calloc(1, len);
  122. if (!ptr) {
  123. SDL_Log("Out of memory!");
  124. Quit(1);
  125. }
  126. return ptr;
  127. }
  128. static void SetTitleBar(const char *fmt, ...)
  129. {
  130. char *newstr = NULL;
  131. va_list ap;
  132. va_start(ap, fmt);
  133. SDL_vasprintf(&newstr, fmt, ap);
  134. va_end(ap);
  135. if (newstr && (!current_titlebar || (SDL_strcmp(current_titlebar, newstr) != 0))) {
  136. SDL_SetWindowTitle(state->windows[0], newstr);
  137. SDL_free(current_titlebar);
  138. current_titlebar = newstr;
  139. } else {
  140. SDL_free(newstr);
  141. }
  142. }
  143. static void SetDefaultTitleBar(void)
  144. {
  145. SetTitleBar("testaudio: %s", SDL_GetCurrentAudioDriver());
  146. }
  147. static Thing *FindThingAtPoint(const float x, const float y)
  148. {
  149. const SDL_FPoint pt = { x, y };
  150. Thing *retval = NULL;
  151. Thing *i;
  152. for (i = things; i != NULL; i = i->next) {
  153. if ((i != dragging_thing) && SDL_PointInRectFloat(&pt, &i->rect)) {
  154. retval = i; /* keep going, though, because things drawn on top are later in the list. */
  155. }
  156. }
  157. return retval;
  158. }
  159. static Thing *UpdateMouseOver(const float x, const float y)
  160. {
  161. Thing *thing;
  162. if (dragging_thing) {
  163. thing = dragging_thing;
  164. } else {
  165. thing = FindThingAtPoint(x, y);
  166. }
  167. if (!thing) {
  168. SetDefaultTitleBar();
  169. } else if (thing->titlebar) {
  170. SetTitleBar("%s", thing->titlebar);
  171. }
  172. return thing;
  173. }
  174. static Thing *CreateThing(ThingType what, float x, float y, float z, float w, float h, Texture *texture, char *titlebar)
  175. {
  176. Thing *last = NULL;
  177. Thing *i;
  178. Thing *thing;
  179. thing = (Thing *) xalloc(sizeof (Thing));
  180. if ((w < 0) || (h < 0)) {
  181. SDL_assert(texture != NULL);
  182. if (w < 0) {
  183. w = texture->w;
  184. }
  185. if (h < 0) {
  186. h = texture->h;
  187. }
  188. }
  189. thing->what = what;
  190. thing->rect.x = x;
  191. thing->rect.y = y;
  192. thing->rect.w = w;
  193. thing->rect.h = h;
  194. thing->z = z;
  195. thing->r = 255;
  196. thing->g = 255;
  197. thing->b = 255;
  198. thing->a = 255;
  199. thing->scale = 1.0f;
  200. thing->createticks = SDL_GetTicks();
  201. thing->texture = texture;
  202. thing->titlebar = titlebar;
  203. /* insert in list by Z order (furthest from the "camera" first, so they get drawn over; negative Z is not drawn at all). */
  204. if (things == NULL) {
  205. things = thing;
  206. return thing;
  207. }
  208. for (i = things; i != NULL; i = i->next) {
  209. if (z > i->z) { /* insert here. */
  210. thing->next = i;
  211. thing->prev = i->prev;
  212. SDL_assert(i->prev == last);
  213. if (i->prev) {
  214. i->prev->next = thing;
  215. } else {
  216. SDL_assert(i == things);
  217. things = thing;
  218. }
  219. i->prev = thing;
  220. return thing;
  221. }
  222. last = i;
  223. }
  224. if (last) {
  225. last->next = thing;
  226. thing->prev = last;
  227. }
  228. return thing;
  229. }
  230. static void DestroyThing(Thing *thing)
  231. {
  232. if (!thing) {
  233. return;
  234. }
  235. switch (thing->what) {
  236. case THING_POOF: break;
  237. case THING_NULL: break;
  238. case THING_TRASHCAN: break;
  239. case THING_LOGDEV:
  240. case THING_LOGDEV_CAPTURE:
  241. SDL_CloseAudioDevice(thing->data.logdev.devid);
  242. break;
  243. case THING_PHYSDEV:
  244. case THING_PHYSDEV_CAPTURE:
  245. SDL_free(thing->data.physdev.name);
  246. break;
  247. case THING_WAV:
  248. SDL_free(thing->data.wav.buf);
  249. break;
  250. case THING_STREAM:
  251. SDL_DestroyAudioStream(thing->data.stream.stream);
  252. break;
  253. }
  254. if (thing->prev) {
  255. thing->prev->next = thing->next;
  256. } else {
  257. SDL_assert(thing == things);
  258. things = thing->next;
  259. }
  260. if (thing->next) {
  261. thing->next->prev = thing->prev;
  262. }
  263. SDL_free(thing->titlebar);
  264. SDL_free(thing);
  265. }
  266. static void DrawOneThing(SDL_Renderer *renderer, Thing *thing)
  267. {
  268. SDL_FRect dst;
  269. SDL_memcpy(&dst, &thing->rect, sizeof (SDL_FRect));
  270. if (thing->scale != 1.0f) {
  271. const float centerx = thing->rect.x + (thing->rect.w / 2);
  272. const float centery = thing->rect.y + (thing->rect.h / 2);
  273. SDL_assert(thing->texture != NULL);
  274. dst.w = thing->texture->w * thing->scale;
  275. dst.h = thing->texture->h * thing->scale;
  276. dst.x = centerx - (dst.w / 2);
  277. dst.y = centery - (dst.h / 2);
  278. }
  279. if (thing->texture) {
  280. if (droppable_highlighted_thing == thing) {
  281. SDL_SetRenderDrawColor(renderer, 255, 0, 255, 100);
  282. SDL_RenderFillRect(renderer, &dst);
  283. }
  284. SDL_SetRenderDrawColor(renderer, thing->r, thing->g, thing->b, thing->a);
  285. SDL_RenderTexture(renderer, thing->texture->texture, NULL, &dst);
  286. } else {
  287. SDL_SetRenderDrawColor(renderer, thing->r, thing->g, thing->b, thing->a);
  288. SDL_RenderFillRect(renderer, &dst);
  289. }
  290. if (thing->ondraw) {
  291. thing->ondraw(thing, renderer);
  292. }
  293. if (thing->progress > 0.0f) {
  294. SDL_FRect r = { thing->rect.x, thing->rect.y + (thing->rect.h + 2.0f), 0.0f, 10.0f };
  295. r.w = thing->rect.w * ((thing->progress > 1.0f) ? 1.0f : thing->progress);
  296. SDL_SetRenderDrawColor(renderer, 255, 255, 255, 128);
  297. SDL_RenderFillRect(renderer, &r);
  298. }
  299. }
  300. static void DrawThings(SDL_Renderer *renderer)
  301. {
  302. Thing *i;
  303. /* draw connecting lines first, so they're behind everything else. */
  304. for (i = things; i && (i->z >= 0.0f); i = i->next) {
  305. Thing *dst = i->line_connected_to;
  306. if (dst) {
  307. SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255);
  308. SDL_RenderLine(renderer, i->rect.x + (i->rect.w / 2), i->rect.y + (i->rect.h / 2), dst->rect.x + (dst->rect.w / 2), dst->rect.y + (dst->rect.h / 2));
  309. }
  310. }
  311. /* Draw the actual things. */
  312. for (i = things; i && (i->z >= 0.0f); i = i->next) {
  313. if (i != dragging_thing) {
  314. DrawOneThing(renderer, i);
  315. }
  316. }
  317. if (dragging_thing) {
  318. DrawOneThing(renderer, dragging_thing); /* draw last so it's always on top. */
  319. }
  320. }
  321. static void Draw(void)
  322. {
  323. SDL_Renderer *renderer = state->renderers[0];
  324. SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
  325. SDL_SetRenderDrawColor(renderer, 64, 0, 64, 255);
  326. SDL_RenderClear(renderer);
  327. DrawThings(renderer);
  328. SDL_RenderPresent(renderer);
  329. }
  330. static void RepositionRowOfThings(const ThingType what, const float y)
  331. {
  332. int total_things = 0;
  333. float texw = 0.0f;
  334. float texh = 0.0f;
  335. Thing *i;
  336. for (i = things; i != NULL; i = i->next) {
  337. if (i->what == what) {
  338. texw = i->rect.w;
  339. texh = i->rect.h;
  340. total_things++;
  341. }
  342. }
  343. if (total_things > 0) {
  344. int w, h;
  345. SDL_GetWindowSize(state->windows[0], &w, &h);
  346. const float spacing = w / ((float) total_things);
  347. float x = (spacing - texw) / 2.0f;
  348. for (i = things; i != NULL; i = i->next) {
  349. if (i->what == what) {
  350. i->rect.x = x;
  351. i->rect.y = (y >= 0.0f) ? y : ((h + y) - texh);
  352. x += spacing;
  353. }
  354. }
  355. }
  356. }
  357. static const char *AudioFmtToString(const SDL_AudioFormat fmt)
  358. {
  359. switch (fmt) {
  360. #define FMTCASE(x) case SDL_AUDIO_##x: return #x
  361. FMTCASE(U8);
  362. FMTCASE(S8);
  363. FMTCASE(S16LSB);
  364. FMTCASE(S16MSB);
  365. FMTCASE(S32LSB);
  366. FMTCASE(S32MSB);
  367. FMTCASE(F32LSB);
  368. FMTCASE(F32MSB);
  369. #undef FMTCASE
  370. }
  371. return "?";
  372. }
  373. static const char *AudioChansToStr(const int channels)
  374. {
  375. switch (channels) {
  376. case 1: return "mono";
  377. case 2: return "stereo";
  378. case 3: return "2.1";
  379. case 4: return "quad";
  380. case 5: return "4.1";
  381. case 6: return "5.1";
  382. case 7: return "6.1";
  383. case 8: return "7.1";
  384. default: break;
  385. }
  386. return "?";
  387. }
  388. static void PoofThing_ondrag(Thing *thing, int button, float x, float y)
  389. {
  390. dragging_thing = NULL; /* refuse to be dragged. */
  391. }
  392. static void PoofThing_ontick(Thing *thing, Uint64 now)
  393. {
  394. const int lifetime = POOF_LIFETIME;
  395. const int elasped = (int) (now - thing->createticks);
  396. if (elasped > lifetime) {
  397. DestroyThing(thing);
  398. } else {
  399. const float pct = ((float) elasped) / ((float) lifetime);
  400. thing->a = (Uint8) (int) (255.0f - (pct * 255.0f));
  401. thing->scale = 1.0f - pct; /* shrink to nothing! */
  402. }
  403. }
  404. static Thing *CreatePoofThing(Thing *poofing_thing)
  405. {
  406. const float centerx = poofing_thing->rect.x + (poofing_thing->rect.w / 2);
  407. const float centery = poofing_thing->rect.y + (poofing_thing->rect.h / 2);
  408. const float z = poofing_thing->z;
  409. Thing *thing = CreateThing(THING_POOF, poofing_thing->rect.x, poofing_thing->rect.y, z, poofing_thing->rect.w, poofing_thing->rect.h, poofing_thing->texture, NULL);
  410. thing->data.poof.startw = poofing_thing->rect.w;
  411. thing->data.poof.starth = poofing_thing->rect.h;
  412. thing->data.poof.centerx = centerx;
  413. thing->data.poof.centery = centery;
  414. thing->ontick = PoofThing_ontick;
  415. thing->ondrag = PoofThing_ondrag;
  416. return thing;
  417. }
  418. static void DestroyThingInPoof(Thing *thing)
  419. {
  420. if (thing) {
  421. if (thing->what != THING_POOF) {
  422. CreatePoofThing(thing);
  423. }
  424. DestroyThing(thing);
  425. }
  426. }
  427. /* this poofs a thing and additionally poofs all things connected to the thing. */
  428. static void TrashThing(Thing *thing)
  429. {
  430. Thing *i, *next;
  431. for (i = things; i != NULL; i = next) {
  432. next = i->next;
  433. if (i->line_connected_to == thing) {
  434. TrashThing(i);
  435. next = things; /* start over in case this blew up the list. */
  436. }
  437. }
  438. DestroyThingInPoof(thing);
  439. }
  440. static void StreamThing_ontick(Thing *thing, Uint64 now)
  441. {
  442. if (!thing->line_connected_to) {
  443. return;
  444. }
  445. /* are we playing? See if we're done, or update state. */
  446. if (thing->line_connected_to->what == THING_LOGDEV) {
  447. const int available = SDL_GetAudioStreamAvailable(thing->data.stream.stream);
  448. SDL_AudioSpec spec;
  449. if (!available || (SDL_GetAudioStreamFormat(thing->data.stream.stream, NULL, &spec) < 0)) {
  450. DestroyThingInPoof(thing);
  451. } else {
  452. const int ticksleft = (int) ((((Uint64) ((available / (SDL_AUDIO_BITSIZE(spec.format) / 8)) / spec.channels)) * 1000) / spec.freq);
  453. const float pct = thing->data.stream.total_ticks ? (((float) (ticksleft)) / ((float) thing->data.stream.total_ticks)) : 0.0f;
  454. thing->progress = 1.0f - pct;
  455. }
  456. }
  457. if (thing->data.stream.next_level_update <= now) {
  458. Uint64 perf = SDL_GetPerformanceCounter();
  459. int i;
  460. for (i = 0; i < SDL_arraysize(thing->data.stream.levels); i++) {
  461. thing->data.stream.levels[i] = (Uint8) (perf % 6);
  462. perf >>= 3;
  463. }
  464. thing->data.stream.next_level_update += 150;
  465. }
  466. }
  467. static void StreamThing_ondrag(Thing *thing, int button, float x, float y)
  468. {
  469. if (button == SDL_BUTTON_RIGHT) { /* this is kinda hacky, but use this to disconnect from a playing source. */
  470. if (thing->line_connected_to) {
  471. SDL_UnbindAudioStream(thing->data.stream.stream); /* unbind from current device */
  472. thing->line_connected_to = NULL;
  473. }
  474. }
  475. }
  476. static void StreamThing_ondrop(Thing *thing, int button, float x, float y)
  477. {
  478. if (droppable_highlighted_thing) {
  479. if (droppable_highlighted_thing->what == THING_TRASHCAN) {
  480. TrashThing(thing);
  481. } else if (((droppable_highlighted_thing->what == THING_LOGDEV) || (droppable_highlighted_thing->what == THING_LOGDEV_CAPTURE)) && (droppable_highlighted_thing != thing->line_connected_to)) {
  482. /* connect to a logical device! */
  483. SDL_Log("Binding audio stream ('%s') to logical device %u", thing->titlebar, (unsigned int) droppable_highlighted_thing->data.logdev.devid);
  484. if (thing->line_connected_to) {
  485. const SDL_AudioSpec *spec = &droppable_highlighted_thing->data.logdev.spec;
  486. SDL_UnbindAudioStream(thing->data.stream.stream); /* unbind from current device */
  487. if (thing->line_connected_to->what == THING_LOGDEV_CAPTURE) {
  488. SDL_FlushAudioStream(thing->data.stream.stream);
  489. thing->data.stream.total_ticks = (int) (((((Uint64) (SDL_GetAudioStreamAvailable(thing->data.stream.stream) / (SDL_AUDIO_BITSIZE(spec->format) / 8))) / spec->channels) * 1000) / spec->freq);
  490. }
  491. }
  492. SDL_BindAudioStream(droppable_highlighted_thing->data.logdev.devid, thing->data.stream.stream); /* bind to new device! */
  493. thing->progress = 0.0f; /* ontick will adjust this if we're on an output device.*/
  494. thing->data.stream.next_level_update = SDL_GetTicks() + 100;
  495. thing->line_connected_to = droppable_highlighted_thing;
  496. }
  497. }
  498. }
  499. static void StreamThing_ondraw(Thing *thing, SDL_Renderer *renderer)
  500. {
  501. if (thing->line_connected_to) { /* are we playing? Update progress bar, and bounce the levels a little. */
  502. static const float xlocs[5] = { 18, 39, 59, 79, 99 };
  503. static const float ylocs[5] = { 49, 39, 29, 19, 10 };
  504. const float blockw = soundboard_levels_texture->w;
  505. const float blockh = soundboard_levels_texture->h / 5.0f;
  506. int i, j;
  507. SDL_SetRenderDrawColor(renderer, thing->r, thing->g, thing->b, thing->a);
  508. for (i = 0; i < SDL_arraysize(thing->data.stream.levels); i++) {
  509. const int level = (int) thing->data.stream.levels[i];
  510. const float x = xlocs[i];
  511. for (j = 0; j < level; j++) {
  512. const SDL_FRect src = { 0, soundboard_levels_texture->h - ((j+1) * blockh), blockw, blockh };
  513. const SDL_FRect dst = { thing->rect.x + x, thing->rect.y + ylocs[j], blockw, blockh };
  514. SDL_RenderTexture(renderer, soundboard_levels_texture->texture, &src, &dst);
  515. }
  516. }
  517. }
  518. }
  519. static Thing *CreateStreamThing(const SDL_AudioSpec *spec, const Uint8 *buf, const Uint32 buflen, const char *fname, const float x, const float y)
  520. {
  521. static const ThingType can_be_dropped_onto[] = { THING_TRASHCAN, THING_LOGDEV, THING_LOGDEV_CAPTURE, THING_NULL };
  522. Thing *thing = CreateThing(THING_STREAM, x, y, 0, -1, -1, soundboard_texture, fname ? xstrdup(fname) : NULL);
  523. SDL_Log("Adding audio stream for %s", fname ? fname : "(null)");
  524. thing->data.stream.stream = SDL_CreateAudioStream(spec, spec);
  525. if (buf && buflen) {
  526. SDL_PutAudioStreamData(thing->data.stream.stream, buf, (int) buflen);
  527. SDL_FlushAudioStream(thing->data.stream.stream);
  528. thing->data.stream.total_ticks = (int) (((((Uint64) (SDL_GetAudioStreamAvailable(thing->data.stream.stream) / (SDL_AUDIO_BITSIZE(spec->format) / 8))) / spec->channels) * 1000) / spec->freq);
  529. }
  530. thing->ontick = StreamThing_ontick;
  531. thing->ondrag = StreamThing_ondrag;
  532. thing->ondrop = StreamThing_ondrop;
  533. thing->ondraw = StreamThing_ondraw;
  534. thing->can_be_dropped_onto = can_be_dropped_onto;
  535. return thing;
  536. }
  537. static void WavThing_ondrag(Thing *thing, int button, float x, float y)
  538. {
  539. if (button == SDL_BUTTON_RIGHT) { /* drag out a new audio stream. */
  540. dragging_thing = CreateStreamThing(&thing->data.wav.spec, thing->data.wav.buf, thing->data.wav.buflen, thing->titlebar, x - (thing->rect.w / 2), y - (thing->rect.h / 2));
  541. }
  542. }
  543. static void WavThing_ondrop(Thing *thing, int button, float x, float y)
  544. {
  545. if (droppable_highlighted_thing) {
  546. if (droppable_highlighted_thing->what == THING_TRASHCAN) {
  547. TrashThing(thing);
  548. }
  549. }
  550. }
  551. static Thing *LoadWavThing(const char *fname, float x, float y)
  552. {
  553. Thing *thing = NULL;
  554. char *path;
  555. SDL_AudioSpec spec;
  556. Uint8 *buf = NULL;
  557. Uint32 buflen = 0;
  558. path = GetNearbyFilename(fname);
  559. if (path) {
  560. fname = path;
  561. }
  562. if (SDL_LoadWAV(fname, &spec, &buf, &buflen) == 0) {
  563. static const ThingType can_be_dropped_onto[] = { THING_TRASHCAN, THING_NULL };
  564. char *titlebar = NULL;
  565. const char *nodirs = SDL_strrchr(fname, '/');
  566. #ifdef __WINDOWS__
  567. const char *nodirs2 = SDL_strrchr(nodirs ? nodirs : fname, '\\');
  568. if (nodirs2) {
  569. nodirs = nodirs2;
  570. }
  571. #endif
  572. SDL_Log("Adding WAV file '%s'", fname);
  573. if (nodirs) {
  574. nodirs++;
  575. } else {
  576. nodirs = fname;
  577. }
  578. SDL_asprintf(&titlebar, "WAV file (\"%s\", %s, %s, %uHz)", nodirs, AudioFmtToString(spec.format), AudioChansToStr(spec.channels), (unsigned int) spec.freq);
  579. thing = CreateThing(THING_WAV, x - (audio_texture->w / 2), y - (audio_texture->h / 2), 5, -1, -1, audio_texture, titlebar);
  580. SDL_memcpy(&thing->data.wav.spec, &spec, sizeof (SDL_AudioSpec));
  581. thing->data.wav.buf = buf;
  582. thing->data.wav.buflen = buflen;
  583. thing->can_be_dropped_onto = can_be_dropped_onto;
  584. thing->ondrag = WavThing_ondrag;
  585. thing->ondrop = WavThing_ondrop;
  586. }
  587. SDL_free(path);
  588. return thing;
  589. }
  590. static Thing *LoadStockWavThing(const char *fname)
  591. {
  592. char *path = GetNearbyFilename(fname);
  593. Thing *thing = LoadWavThing(path ? path : fname, 0.0f, 0.0f); /* will reposition in a moment. */
  594. SDL_free(path);
  595. return thing;
  596. }
  597. static void LoadStockWavThings(void)
  598. {
  599. LoadStockWavThing("sample.wav");
  600. RepositionRowOfThings(THING_WAV, -10.0f);
  601. }
  602. static void DestroyTexture(Texture *tex)
  603. {
  604. if (tex) {
  605. SDL_DestroyTexture(tex->texture);
  606. SDL_free(tex);
  607. }
  608. }
  609. static Texture *CreateTexture(const char *fname)
  610. {
  611. Texture *tex = (Texture *) xalloc(sizeof (Texture));
  612. int texw, texh;
  613. tex->texture = LoadTexture(state->renderers[0], fname, SDL_TRUE, &texw, &texh);
  614. if (!tex->texture) {
  615. SDL_Log("Failed to load '%s': %s", fname, SDL_GetError());
  616. SDL_free(tex);
  617. Quit(1);
  618. }
  619. SDL_SetTextureBlendMode(tex->texture, SDL_BLENDMODE_BLEND);
  620. tex->w = (float) texw;
  621. tex->h = (float) texh;
  622. return tex;
  623. }
  624. static Thing *CreateLogicalDeviceThing(Thing *parent, const SDL_AudioDeviceID which, const float x, const float y);
  625. static void DeviceThing_ondrag(Thing *thing, int button, float x, float y)
  626. {
  627. if ((button == SDL_BUTTON_MIDDLE) && (thing->what == THING_LOGDEV_CAPTURE)) { /* drag out a new stream. This is a UX mess. :/ */
  628. dragging_thing = CreateStreamThing(&thing->data.logdev.spec, NULL, 0, NULL, x, y);
  629. dragging_thing->data.stream.next_level_update = SDL_GetTicks() + 100;
  630. SDL_BindAudioStream(thing->data.logdev.devid, dragging_thing->data.stream.stream); /* bind to new device! */
  631. dragging_thing->line_connected_to = thing;
  632. } else if (button == SDL_BUTTON_RIGHT) { /* drag out a new logical device. */
  633. const SDL_AudioDeviceID which = ((thing->what == THING_LOGDEV) || (thing->what == THING_LOGDEV_CAPTURE)) ? thing->data.logdev.devid : thing->data.physdev.devid;
  634. const SDL_AudioDeviceID devid = SDL_OpenAudioDevice(which, NULL);
  635. dragging_thing = devid ? CreateLogicalDeviceThing(thing, devid, x - (thing->rect.w / 2), y - (thing->rect.h / 2)) : NULL;
  636. }
  637. }
  638. static void SetLogicalDeviceTitlebar(Thing *thing)
  639. {
  640. SDL_AudioSpec *spec = &thing->data.logdev.spec;
  641. SDL_GetAudioDeviceFormat(thing->data.logdev.devid, spec);
  642. SDL_asprintf(&thing->titlebar, "Logical device #%u (%s, %s, %s, %uHz)", (unsigned int) thing->data.logdev.devid, thing->data.logdev.iscapture ? "CAPTURE" : "OUTPUT", AudioFmtToString(spec->format), AudioChansToStr(spec->channels), (unsigned int) spec->freq);
  643. }
  644. static void LogicalDeviceThing_ondrop(Thing *thing, int button, float x, float y)
  645. {
  646. if (droppable_highlighted_thing) {
  647. if (droppable_highlighted_thing->what == THING_TRASHCAN) {
  648. TrashThing(thing);
  649. }
  650. }
  651. }
  652. static Thing *CreateLogicalDeviceThing(Thing *parent, const SDL_AudioDeviceID which, const float x, const float y)
  653. {
  654. static const ThingType can_be_dropped_onto[] = { THING_TRASHCAN, THING_NULL };
  655. Thing *physthing = ((parent->what == THING_LOGDEV) || (parent->what == THING_LOGDEV_CAPTURE)) ? parent->data.logdev.physdev : parent;
  656. const SDL_bool iscapture = physthing->data.physdev.iscapture;
  657. Thing *thing;
  658. SDL_Log("Adding logical audio device %u", (unsigned int) which);
  659. thing = CreateThing(iscapture ? THING_LOGDEV_CAPTURE : THING_LOGDEV, x, y, 5, -1, -1, logdev_texture, NULL);
  660. thing->data.logdev.devid = which;
  661. thing->data.logdev.iscapture = iscapture;
  662. thing->data.logdev.physdev = physthing;
  663. thing->line_connected_to = physthing;
  664. thing->ondrag = DeviceThing_ondrag;
  665. thing->ondrop = LogicalDeviceThing_ondrop;
  666. thing->can_be_dropped_onto = can_be_dropped_onto;
  667. SetLogicalDeviceTitlebar(thing);
  668. return thing;
  669. }
  670. static void SetPhysicalDeviceTitlebar(Thing *thing)
  671. {
  672. SDL_AudioSpec *spec = &thing->data.physdev.spec;
  673. SDL_GetAudioDeviceFormat(thing->data.physdev.devid, spec);
  674. if (thing->data.physdev.devid == SDL_AUDIO_DEVICE_DEFAULT_CAPTURE) {
  675. SDL_asprintf(&thing->titlebar, "Default system device (CAPTURE, %s, %s, %uHz)", AudioFmtToString(spec->format), AudioChansToStr(spec->channels), (unsigned int) spec->freq);
  676. } else if (thing->data.physdev.devid == SDL_AUDIO_DEVICE_DEFAULT_OUTPUT) {
  677. SDL_asprintf(&thing->titlebar, "Default system device (OUTPUT, %s, %s, %uHz)", AudioFmtToString(spec->format), AudioChansToStr(spec->channels), (unsigned int) spec->freq);
  678. } else {
  679. SDL_asprintf(&thing->titlebar, "Physical device #%u (%s, \"%s\", %s, %s, %uHz)", (unsigned int) thing->data.physdev.devid, thing->data.physdev.iscapture ? "CAPTURE" : "OUTPUT", thing->data.physdev.name, AudioFmtToString(spec->format), AudioChansToStr(spec->channels), (unsigned int) spec->freq);
  680. }
  681. }
  682. static void PhysicalDeviceThing_ondrop(Thing *thing, int button, float x, float y)
  683. {
  684. if (droppable_highlighted_thing) {
  685. if (droppable_highlighted_thing->what == THING_TRASHCAN) {
  686. TrashThing(thing);
  687. }
  688. }
  689. }
  690. static void PhysicalDeviceThing_ontick(Thing *thing, Uint64 now)
  691. {
  692. const int lifetime = POOF_LIFETIME;
  693. const int elasped = (int) (now - thing->createticks);
  694. if (elasped > lifetime) {
  695. thing->scale = 1.0f;
  696. thing->a = 255;
  697. thing->ontick = NULL; /* no more ticking. */
  698. } else {
  699. const float pct = ((float) elasped) / ((float) lifetime);
  700. thing->a = (Uint8) (int) (pct * 255.0f);
  701. thing->scale = pct; /* grow to normal size */
  702. }
  703. }
  704. static Thing *CreatePhysicalDeviceThing(const SDL_AudioDeviceID which, const SDL_bool iscapture)
  705. {
  706. static const ThingType can_be_dropped_onto[] = { THING_TRASHCAN, THING_NULL };
  707. static float next_physdev_x = 0;
  708. Thing *thing;
  709. int winw, winh;
  710. SDL_GetWindowSize(state->windows[0], &winw, &winh);
  711. if (next_physdev_x > (winw-physdev_texture->w)) {
  712. next_physdev_x = 0;
  713. }
  714. SDL_Log("Adding physical audio device %u", (unsigned int) which);
  715. thing = CreateThing(iscapture ? THING_PHYSDEV_CAPTURE : THING_PHYSDEV, next_physdev_x, 170, 5, -1, -1, physdev_texture, NULL);
  716. thing->data.physdev.devid = which;
  717. thing->data.physdev.iscapture = iscapture;
  718. thing->data.physdev.name = SDL_GetAudioDeviceName(which);
  719. thing->ondrag = DeviceThing_ondrag;
  720. thing->ondrop = PhysicalDeviceThing_ondrop;
  721. thing->ontick = PhysicalDeviceThing_ontick;
  722. thing->can_be_dropped_onto = can_be_dropped_onto;
  723. SetPhysicalDeviceTitlebar(thing);
  724. if (SDL_GetTicks() <= (app_ready_ticks + 2000)) { /* assume this is the initial batch if it happens in the first two seconds. */
  725. RepositionRowOfThings(THING_PHYSDEV, 10.0f); /* don't rearrange them after the initial add. */
  726. RepositionRowOfThings(THING_PHYSDEV_CAPTURE, 170.0f); /* don't rearrange them after the initial add. */
  727. next_physdev_x = 0.0f;
  728. } else {
  729. next_physdev_x += physdev_texture->w * 1.5f;
  730. }
  731. return thing;
  732. }
  733. static Thing *CreateTrashcanThing(void)
  734. {
  735. int winw, winh;
  736. SDL_GetWindowSize(state->windows[0], &winw, &winh);
  737. return CreateThing(THING_TRASHCAN, winw - trashcan_texture->w, winh - trashcan_texture->h, 10, -1, -1, trashcan_texture, "Drag things here to remove them.");
  738. }
  739. static Thing *CreateDefaultPhysicalDevice(const SDL_bool iscapture)
  740. {
  741. return CreatePhysicalDeviceThing(iscapture ? SDL_AUDIO_DEVICE_DEFAULT_CAPTURE : SDL_AUDIO_DEVICE_DEFAULT_OUTPUT, iscapture);
  742. }
  743. static void TickThings(void)
  744. {
  745. Thing *i;
  746. Thing *next;
  747. const Uint64 now = SDL_GetTicks();
  748. for (i = things; i != NULL; i = next) {
  749. next = i->next; /* in case this deletes itself. */
  750. if (i->ontick) {
  751. i->ontick(i, now);
  752. }
  753. }
  754. }
  755. static void WindowResized(const int newwinw, const int newwinh)
  756. {
  757. Thing *i;
  758. const float neww = (float) newwinw;
  759. const float newh = (float) newwinh;
  760. const float oldw = (float) state->window_w;
  761. const float oldh = (float) state->window_h;
  762. for (i = things; i != NULL; i = i->next) {
  763. const float halfw = i->rect.w / 2.0f;
  764. const float halfh = i->rect.h / 2.0f;
  765. const float x = (i->rect.x + halfw) / oldw;
  766. const float y = (i->rect.y + halfh) / oldh;
  767. i->rect.x = (x * neww) - halfw;
  768. i->rect.y = (y * newh) - halfh;
  769. }
  770. state->window_w = newwinw;
  771. state->window_h = newwinh;
  772. }
  773. static void Loop(void)
  774. {
  775. SDL_Event event;
  776. SDL_bool saw_event = SDL_FALSE;
  777. if (app_ready_ticks == 0) {
  778. app_ready_ticks = SDL_GetTicks();
  779. }
  780. while (SDL_PollEvent(&event)) {
  781. Thing *thing = NULL;
  782. saw_event = SDL_TRUE;
  783. switch (event.type) {
  784. case SDL_EVENT_MOUSE_MOTION:
  785. thing = UpdateMouseOver(event.motion.x, event.motion.y);
  786. if ((dragging_button == -1) && event.motion.state) {
  787. if (event.motion.state & SDL_BUTTON_LMASK) {
  788. dragging_button = SDL_BUTTON_LEFT;
  789. } else if (event.motion.state & SDL_BUTTON_RMASK) {
  790. dragging_button = SDL_BUTTON_RIGHT;
  791. } else if (event.motion.state & SDL_BUTTON_MMASK) {
  792. dragging_button = SDL_BUTTON_MIDDLE;
  793. }
  794. if (dragging_button != -1) {
  795. dragging_thing = thing;
  796. if (thing && thing->ondrag) {
  797. thing->ondrag(thing, dragging_button, event.motion.x, event.motion.y);
  798. }
  799. }
  800. }
  801. droppable_highlighted_thing = NULL;
  802. if (dragging_thing) {
  803. dragging_thing->rect.x = event.motion.x - (dragging_thing->rect.w / 2);
  804. dragging_thing->rect.y = event.motion.y - (dragging_thing->rect.h / 2);
  805. if (dragging_thing->can_be_dropped_onto) {
  806. thing = FindThingAtPoint(event.motion.x, event.motion.y);
  807. if (thing) {
  808. int i;
  809. for (i = 0; dragging_thing->can_be_dropped_onto[i]; i++) {
  810. if (dragging_thing->can_be_dropped_onto[i] == thing->what) {
  811. droppable_highlighted_thing = thing;
  812. break;
  813. }
  814. }
  815. }
  816. }
  817. }
  818. break;
  819. case SDL_EVENT_MOUSE_BUTTON_DOWN:
  820. thing = UpdateMouseOver(event.button.x, event.button.y);
  821. break;
  822. case SDL_EVENT_MOUSE_BUTTON_UP:
  823. if (dragging_button == event.button.button) {
  824. Thing *dropped_thing = dragging_thing;
  825. dragging_thing = NULL;
  826. dragging_button = -1;
  827. if (dropped_thing && dropped_thing->ondrop) {
  828. dropped_thing->ondrop(dropped_thing, event.button.button, event.button.x, event.button.y);
  829. }
  830. droppable_highlighted_thing = NULL;
  831. }
  832. thing = UpdateMouseOver(event.button.x, event.button.y);
  833. break;
  834. case SDL_EVENT_MOUSE_WHEEL:
  835. UpdateMouseOver(event.wheel.mouseX, event.wheel.mouseY);
  836. break;
  837. case SDL_EVENT_DROP_FILE:
  838. SDL_Log("Drop file! '%s'", event.drop.file);
  839. LoadWavThing(event.drop.file, event.drop.x, event.drop.y);
  840. /* SDLTest_CommonEvent will free the string, below. */
  841. break;
  842. case SDL_EVENT_WINDOW_RESIZED:
  843. WindowResized(event.window.data1, event.window.data2);
  844. break;
  845. case SDL_EVENT_AUDIO_DEVICE_ADDED:
  846. CreatePhysicalDeviceThing(event.adevice.which, event.adevice.iscapture);
  847. break;
  848. case SDL_EVENT_AUDIO_DEVICE_REMOVED: {
  849. const SDL_AudioDeviceID which = event.adevice.which;
  850. Thing *i, *next;
  851. SDL_Log("Removing audio device %u", (unsigned int) which);
  852. for (i = things; i != NULL; i = next) {
  853. next = i->next;
  854. if (((i->what == THING_PHYSDEV) || (i->what == THING_PHYSDEV_CAPTURE)) && (i->data.physdev.devid == which)) {
  855. TrashThing(i);
  856. next = things; /* in case we mangled the list. */
  857. } else if (((i->what == THING_LOGDEV) || (i->what == THING_LOGDEV_CAPTURE)) && (i->data.logdev.devid == which)) {
  858. TrashThing(i);
  859. next = things; /* in case we mangled the list. */
  860. }
  861. }
  862. break;
  863. }
  864. default: break;
  865. }
  866. SDLTest_CommonEvent(state, &event, &done);
  867. }
  868. TickThings();
  869. Draw();
  870. if (!saw_event) {
  871. SDL_Delay(10);
  872. }
  873. #ifdef __EMSCRIPTEN__
  874. if (done) {
  875. emscripten_cancel_main_loop();
  876. }
  877. #endif
  878. }
  879. int main(int argc, char *argv[])
  880. {
  881. int i;
  882. state = SDLTest_CommonCreateState(argv, SDL_INIT_VIDEO | SDL_INIT_AUDIO);
  883. if (state == NULL) {
  884. Quit(1);
  885. }
  886. state->window_flags |= SDL_WINDOW_RESIZABLE;
  887. for (i = 1; i < argc;) {
  888. int consumed = SDLTest_CommonArg(state, i);
  889. if (consumed == 0) {
  890. consumed = -1;
  891. /* add our own command lines here. */
  892. }
  893. if (consumed < 0) {
  894. static const char *options[] = {
  895. /* add our own command lines here. */
  896. /*"[--blend none|blend|add|mod|mul|sub]",*/
  897. NULL
  898. };
  899. SDLTest_CommonLogUsage(state, argv[0], options);
  900. Quit(1);
  901. }
  902. i += consumed;
  903. }
  904. if (!SDLTest_CommonInit(state)) {
  905. Quit(2);
  906. }
  907. if (state->audio_id) {
  908. SDL_CloseAudioDevice(state->audio_id);
  909. state->audio_id = 0;
  910. }
  911. SetDefaultTitleBar();
  912. physdev_texture = CreateTexture("physaudiodev.bmp");
  913. logdev_texture = CreateTexture("logaudiodev.bmp");
  914. audio_texture = CreateTexture("audiofile.bmp");
  915. trashcan_texture = CreateTexture("trashcan.bmp");
  916. soundboard_texture = CreateTexture("soundboard.bmp");
  917. soundboard_levels_texture = CreateTexture("soundboard_levels.bmp");
  918. LoadStockWavThings();
  919. CreateTrashcanThing();
  920. CreateDefaultPhysicalDevice(SDL_FALSE);
  921. CreateDefaultPhysicalDevice(SDL_TRUE);
  922. #ifdef __EMSCRIPTEN__
  923. emscripten_set_main_loop(Loop, 0, 1);
  924. #else
  925. while (!done) {
  926. Loop();
  927. }
  928. #endif
  929. Quit(0);
  930. return 0;
  931. }