SDL

Форк
0
/
testaudio.c 
1288 строк · 43.0 Кб
1
/*
2
  Copyright (C) 1997-2024 Sam Lantinga <slouken@libsdl.org>
3

4
  This software is provided 'as-is', without any express or implied
5
  warranty.  In no event will the authors be held liable for any damages
6
  arising from the use of this software.
7

8
  Permission is granted to anyone to use this software for any purpose,
9
  including commercial applications, and to alter it and redistribute it
10
  freely.
11
*/
12

13
#define SDL_MAIN_USE_CALLBACKS 1
14
#include <SDL3/SDL_test.h>
15
#include <SDL3/SDL_test_common.h>
16
#include <SDL3/SDL_main.h>
17
#include "testutils.h"
18

19
#define POOF_LIFETIME 250
20
#define VISUALIZER_WIDTH 100
21
#define VISUALIZER_HEIGHT 50
22

23
typedef struct Texture
24
{
25
    SDL_Texture *texture;
26
    float w;
27
    float h;
28
} Texture;
29

30
typedef enum ThingType
31
{
32
    THING_NULL,
33
    THING_PHYSDEV,
34
    THING_PHYSDEV_RECORDING,
35
    THING_LOGDEV,
36
    THING_LOGDEV_RECORDING,
37
    THING_TRASHCAN,
38
    THING_STREAM,
39
    THING_POOF,
40
    THING_WAV
41
} ThingType;
42

43

44
typedef struct Thing Thing;
45

46
struct Thing
47
{
48
    ThingType what;
49

50
    union {
51
        struct {
52
            SDL_AudioDeviceID devid;
53
            SDL_bool recording;
54
            SDL_AudioSpec spec;
55
            char *name;
56
        } physdev;
57
        struct {
58
            SDL_AudioDeviceID devid;
59
            SDL_bool recording;
60
            SDL_AudioSpec spec;
61
            Thing *physdev;
62
            SDL_bool visualizer_enabled;
63
            SDL_bool visualizer_updated;
64
            SDL_Texture *visualizer;
65
            SDL_Mutex *postmix_lock;
66
            float *postmix_buffer;
67
            int postmix_buflen;
68
            int postmix_allocated;
69
            SDL_AudioSpec postmix_spec;
70
            SDL_AtomicInt postmix_updated;
71
        } logdev;
72
        struct {
73
            SDL_AudioSpec spec;
74
            Uint8 *buf;
75
            Uint32 buflen;
76
        } wav;
77
        struct {
78
            float startw;
79
            float starth;
80
            float centerx;
81
            float centery;
82
        } poof;
83
        struct {
84
            SDL_AudioStream *stream;
85
            int total_bytes;
86
            Uint64 next_level_update;
87
            Uint8 levels[5];
88
        } stream;
89
    } data;
90

91
    Thing *line_connected_to;
92
    char *titlebar;
93
    SDL_FRect rect;
94
    float z;
95
    Uint8 r, g, b, a;
96
    float progress;
97
    float meter;
98
    float scale;
99
    Uint64 createticks;
100
    Texture *texture;
101
    const ThingType *can_be_dropped_onto;
102

103
    void (*ontick)(Thing *thing, Uint64 now);
104
    void (*ondrag)(Thing *thing, int button, float x, float y);
105
    void (*ondrop)(Thing *thing, int button, float x, float y);
106
    void (*ondraw)(Thing *thing, SDL_Renderer *renderer);
107
    void (*onmousewheel)(Thing *thing, float y);
108

109
    Thing *prev;
110
    Thing *next;
111
};
112

113

114
static Uint64 app_ready_ticks = 0;
115
static SDLTest_CommonState *state = NULL;
116

117
static Thing *things = NULL;
118

119
static Thing *mouseover_thing = NULL;
120
static Thing *droppable_highlighted_thing = NULL;
121
static Thing *dragging_thing = NULL;
122
static int dragging_button = -1;
123
static int dragging_button_real = -1;
124
static SDL_bool ctrl_held = SDL_FALSE;
125
static SDL_bool alt_held = SDL_FALSE;
126

127
static Texture *physdev_texture = NULL;
128
static Texture *logdev_texture = NULL;
129
static Texture *audio_texture = NULL;
130
static Texture *trashcan_texture = NULL;
131
static Texture *soundboard_texture = NULL;
132
static Texture *soundboard_levels_texture = NULL;
133

134

135
static void SetTitleBar(const char *fmt, ...)
136
{
137
    char *title = NULL;
138
    va_list ap;
139
    va_start(ap, fmt);
140
    SDL_vasprintf(&title, fmt, ap);
141
    va_end(ap);
142

143
    SDL_SetWindowTitle(state->windows[0], title);
144
    SDL_free(title);
145
}
146

147
static void SetDefaultTitleBar(void)
148
{
149
    SetTitleBar("testaudio: %s", SDL_GetCurrentAudioDriver());
150
}
151

152
static Thing *FindThingAtPoint(const float x, const float y)
153
{
154
    const SDL_FPoint pt = { x, y };
155
    Thing *retval = NULL;
156
    Thing *i;
157
    for (i = things; i; i = i->next) {
158
        if ((i != dragging_thing) && SDL_PointInRectFloat(&pt, &i->rect)) {
159
            retval = i;  /* keep going, though, because things drawn on top are later in the list. */
160
        }
161
    }
162
    return retval;
163
}
164

165
static Thing *UpdateMouseOver(const float x, const float y)
166
{
167
    Thing *thing;
168

169
    if (dragging_thing) {
170
        thing = dragging_thing;
171
    } else {
172
        thing = FindThingAtPoint(x, y);
173
    }
174

175
    mouseover_thing = thing;
176

177
    if (!thing) {
178
        SetDefaultTitleBar();
179
    } else if (thing->titlebar) {
180
        SetTitleBar("%s", thing->titlebar);
181
    }
182

183
    return thing;
184
}
185

186
static Thing *CreateThing(ThingType what, float x, float y, float z, float w, float h, Texture *texture, const char *titlebar)
187
{
188
    Thing *last = NULL;
189
    Thing *i;
190
    Thing *thing;
191

192
    thing = (Thing *) SDL_calloc(1, sizeof (Thing));
193
    if (!thing) {
194
        SDL_Log("Out of memory!");
195
        return NULL;
196
    }
197

198
    if ((w < 0) || (h < 0)) {
199
        SDL_assert(texture != NULL);
200
        if (w < 0) {
201
            w = texture->w;
202
        }
203
        if (h < 0) {
204
            h = texture->h;
205
        }
206
    }
207

208
    thing->what = what;
209
    thing->rect.x = x;
210
    thing->rect.y = y;
211
    thing->rect.w = w;
212
    thing->rect.h = h;
213
    thing->z = z;
214
    thing->r = 255;
215
    thing->g = 255;
216
    thing->b = 255;
217
    thing->a = 255;
218
    thing->scale = 1.0f;
219
    thing->createticks = SDL_GetTicks();
220
    thing->texture = texture;
221
    thing->titlebar = titlebar ? SDL_strdup(titlebar) : NULL;  /* if allocation fails, oh well. */
222

223
    /* insert in list by Z order (furthest from the "camera" first, so they get drawn over; negative Z is not drawn at all). */
224
    if (!things) {
225
        things = thing;
226
        return thing;
227
    }
228

229
    for (i = things; i; i = i->next) {
230
        if (z > i->z) {  /* insert here. */
231
            thing->next = i;
232
            thing->prev = i->prev;
233

234
            SDL_assert(i->prev == last);
235

236
            if (i->prev) {
237
                i->prev->next = thing;
238
            } else {
239
                SDL_assert(i == things);
240
                things = thing;
241
            }
242
            i->prev = thing;
243
            return thing;
244
        }
245
        last = i;
246
    }
247

248
    if (last) {
249
        last->next = thing;
250
        thing->prev = last;
251
    }
252
    return thing;
253
}
254

255
static void DestroyThing(Thing *thing)
256
{
257
    if (!thing) {
258
        return;
259
    }
260

261
    if (mouseover_thing == thing) {
262
        mouseover_thing = NULL;
263
    }
264

265
    if (droppable_highlighted_thing == thing) {
266
        droppable_highlighted_thing = NULL;
267
    }
268

269
    if (dragging_thing == thing) {
270
        dragging_thing = NULL;
271
    }
272

273
    switch (thing->what) {
274
        case THING_POOF: break;
275
        case THING_NULL: break;
276
        case THING_TRASHCAN: break;
277

278
        case THING_LOGDEV:
279
        case THING_LOGDEV_RECORDING:
280
            SDL_CloseAudioDevice(thing->data.logdev.devid);
281
            if (state->renderers[0] != NULL) {
282
                SDL_DestroyTexture(thing->data.logdev.visualizer);
283
            }
284
            SDL_DestroyMutex(thing->data.logdev.postmix_lock);
285
            SDL_free(thing->data.logdev.postmix_buffer);
286
            break;
287

288
        case THING_PHYSDEV:
289
        case THING_PHYSDEV_RECORDING:
290
            SDL_free(thing->data.physdev.name);
291
            break;
292

293
        case THING_WAV:
294
            SDL_free(thing->data.wav.buf);
295
            break;
296

297
        case THING_STREAM:
298
            SDL_DestroyAudioStream(thing->data.stream.stream);
299
            break;
300
    }
301

302
    if (thing->prev) {
303
        thing->prev->next = thing->next;
304
    } else {
305
        SDL_assert(thing == things);
306
        things = thing->next;
307
    }
308

309
    if (thing->next) {
310
        thing->next->prev = thing->prev;
311
    }
312

313
    SDL_free(thing->titlebar);
314
    SDL_free(thing);
315
}
316

317
static void DrawOneThing(SDL_Renderer *renderer, Thing *thing)
318
{
319
    SDL_FRect dst;
320
    SDL_memcpy(&dst, &thing->rect, sizeof (SDL_FRect));
321
    if (thing->scale != 1.0f) {
322
        const float centerx = thing->rect.x + (thing->rect.w / 2);
323
        const float centery = thing->rect.y + (thing->rect.h / 2);
324
        const int w = thing->texture ? (int) thing->texture->w : 128;
325
        const int h = thing->texture ? (int) thing->texture->h : 128;
326
        dst.w = w * thing->scale;
327
        dst.h = h * thing->scale;
328
        dst.x = centerx - (dst.w / 2);
329
        dst.y = centery - (dst.h / 2);
330
    }
331

332
    if (thing->texture) {
333
        if (droppable_highlighted_thing == thing) {
334
            SDL_SetRenderDrawColor(renderer, 255, 0, 255, 100);
335
            SDL_RenderFillRect(renderer, &dst);
336
        }
337
        SDL_SetRenderDrawColor(renderer, thing->r, thing->g, thing->b, thing->a);
338
        SDL_RenderTexture(renderer, thing->texture->texture, NULL, &dst);
339
    } else {
340
        SDL_SetRenderDrawColor(renderer, thing->r, thing->g, thing->b, thing->a);
341
        SDL_RenderFillRect(renderer, &dst);
342
    }
343

344
    if (thing->ondraw) {
345
        thing->ondraw(thing, renderer);
346
    }
347

348
    if (thing->progress > 0.0f) {
349
        SDL_FRect r = { thing->rect.x, thing->rect.y + (thing->rect.h + 2.0f), 0.0f, 10.0f };
350
        r.w = thing->rect.w * ((thing->progress > 1.0f) ? 1.0f : thing->progress);
351
        SDL_SetRenderDrawColor(renderer, 255, 255, 255, 128);
352
        SDL_RenderFillRect(renderer, &r);
353
    }
354

355
    if (thing->meter > 0.0f) {
356
        SDL_FRect r;
357
        r.w = 10.0f;
358
        r.h = thing->rect.h * ((thing->meter > 1.0f) ? 1.0f : thing->meter);
359
        r.x = thing->rect.x + thing->rect.w + 2.0f;
360
        r.y = (thing->rect.y + thing->rect.h) - r.h;
361
        SDL_SetRenderDrawColor(renderer, 255, 255, 255, 128);
362
        SDL_RenderFillRect(renderer, &r);
363
    }
364
}
365

366
static void DrawThings(SDL_Renderer *renderer)
367
{
368
    Thing *i;
369

370
    /* draw connecting lines first, so they're behind everything else. */
371
    for (i = things; i && (i->z >= 0.0f); i = i->next) {
372
        Thing *dst = i->line_connected_to;
373
        if (dst) {
374
            SDL_SetRenderDrawColor(renderer, 255, 0, 0, 255);
375
            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));
376
        }
377
    }
378

379
    /* Draw the actual things. */
380
    for (i = things; i && (i->z >= 0.0f); i = i->next) {
381
        if (i != dragging_thing) {
382
            DrawOneThing(renderer, i);
383
        }
384
    }
385

386
    if (dragging_thing) {
387
        DrawOneThing(renderer, dragging_thing);  /* draw last so it's always on top. */
388
    }
389
}
390

391
static void Draw(void)
392
{
393
    SDL_Renderer *renderer = state->renderers[0];
394
    if (renderer) {  /* might be NULL if we're shutting down. */
395
        SDL_SetRenderDrawBlendMode(renderer, SDL_BLENDMODE_BLEND);
396
        SDL_SetRenderDrawColor(renderer, 64, 0, 64, 255);
397
        SDL_RenderClear(renderer);
398
        DrawThings(renderer);
399
        SDL_RenderPresent(renderer);
400
    }
401
}
402

403
static void RepositionRowOfThings(const ThingType what, const float y)
404
{
405
    int total_things = 0;
406
    float texw = 0.0f;
407
    float texh = 0.0f;
408
    Thing *i;
409

410
    for (i = things; i; i = i->next) {
411
        if (i->what == what) {
412
            texw = i->rect.w;
413
            texh = i->rect.h;
414
            total_things++;
415
        }
416
    }
417

418
    if (total_things > 0) {
419
        int w, h;
420
        SDL_GetWindowSize(state->windows[0], &w, &h);
421
        const float spacing = w / ((float) total_things);
422
        float x = (spacing - texw) / 2.0f;
423
        for (i = things; i; i = i->next) {
424
            if (i->what == what) {
425
                i->rect.x = x;
426
                i->rect.y = (y >= 0.0f) ? y : ((h + y) - texh);
427
                x += spacing;
428
            }
429
        }
430
    }
431
}
432

433
static const char *AudioFmtToString(const SDL_AudioFormat fmt)
434
{
435
    switch (fmt) {
436
        #define FMTCASE(x) case SDL_AUDIO_##x: return #x
437
        FMTCASE(U8);
438
        FMTCASE(S8);
439
        FMTCASE(S16LE);
440
        FMTCASE(S16BE);
441
        FMTCASE(S32LE);
442
        FMTCASE(S32BE);
443
        FMTCASE(F32LE);
444
        FMTCASE(F32BE);
445
        #undef FMTCASE
446
    }
447
    return "?";
448
}
449

450
static const char *AudioChansToStr(const int channels)
451
{
452
    switch (channels) {
453
        case 1: return "mono";
454
        case 2: return "stereo";
455
        case 3: return "2.1";
456
        case 4: return "quad";
457
        case 5: return "4.1";
458
        case 6: return "5.1";
459
        case 7: return "6.1";
460
        case 8: return "7.1";
461
        default: break;
462
    }
463
    return "?";
464
}
465

466
static void PoofThing_ondrag(Thing *thing, int button, float x, float y)
467
{
468
    dragging_thing = NULL;   /* refuse to be dragged. */
469
}
470

471
static void PoofThing_ontick(Thing *thing, Uint64 now)
472
{
473
    const int lifetime = POOF_LIFETIME;
474
    const int elasped = (int) (now - thing->createticks);
475
    if (elasped > lifetime) {
476
        DestroyThing(thing);
477
    } else {
478
        const float pct = ((float) elasped) / ((float) lifetime);
479
        thing->a = (Uint8) (int) (255.0f - (pct * 255.0f));
480
        thing->scale = 1.0f - pct;  /* shrink to nothing! */
481
    }
482
}
483

484
static Thing *CreatePoofThing(Thing *poofing_thing)
485
{
486
    const float centerx = poofing_thing->rect.x + (poofing_thing->rect.w / 2);
487
    const float centery = poofing_thing->rect.y + (poofing_thing->rect.h / 2);
488
    const float z = poofing_thing->z;
489
    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);
490
    if (thing) {
491
        thing->data.poof.startw = poofing_thing->rect.w;
492
        thing->data.poof.starth = poofing_thing->rect.h;
493
        thing->data.poof.centerx = centerx;
494
        thing->data.poof.centery = centery;
495
        thing->ontick = PoofThing_ontick;
496
        thing->ondrag = PoofThing_ondrag;
497
    }
498
    return thing;
499
}
500

501
static void DestroyThingInPoof(Thing *thing)
502
{
503
    if (thing) {
504
        if (thing->what != THING_POOF) {
505
            CreatePoofThing(thing);
506
        }
507
        DestroyThing(thing);
508
    }
509
}
510

511
/* this poofs a thing and additionally poofs all things connected to the thing. */
512
static void TrashThing(Thing *thing)
513
{
514
    Thing *i, *next;
515
    for (i = things; i; i = next) {
516
        next = i->next;
517
        if (i->line_connected_to == thing) {
518
            TrashThing(i);
519
            next = things;  /* start over in case this blew up the list. */
520
        }
521
    }
522
    DestroyThingInPoof(thing);
523
}
524

525
static void StreamThing_ontick(Thing *thing, Uint64 now)
526
{
527
    if (!thing->line_connected_to) {
528
        return;
529
    }
530

531
    /* are we playing? See if we're done, or update state. */
532
    if (thing->line_connected_to->what == THING_LOGDEV) {
533
        const int available = SDL_GetAudioStreamAvailable(thing->data.stream.stream);
534
        if (!available) {
535
            DestroyThingInPoof(thing);
536
            return;
537
        } else {
538
            thing->progress = 1.0f - (thing->data.stream.total_bytes ? (((float) (available)) / ((float) thing->data.stream.total_bytes)) : 0.0f);
539
        }
540
    }
541

542
    if (thing->data.stream.next_level_update <= now) {
543
        Uint64 perf = SDL_GetPerformanceCounter();
544
        int i;
545
        for (i = 0; i < SDL_arraysize(thing->data.stream.levels); i++) {
546
            thing->data.stream.levels[i] = (Uint8) (perf % 6);
547
            perf >>= 3;
548
        }
549
        thing->data.stream.next_level_update += 150;
550
    }
551
}
552

553
static void StreamThing_ondrag(Thing *thing, int button, float x, float y)
554
{
555
    if (button == SDL_BUTTON_RIGHT) {  /* this is kinda hacky, but use this to disconnect from a playing source. */
556
        if (thing->line_connected_to) {
557
            SDL_UnbindAudioStream(thing->data.stream.stream); /* unbind from current device */
558
            if (thing->line_connected_to->what == THING_LOGDEV_RECORDING) {
559
                SDL_FlushAudioStream(thing->data.stream.stream);
560
            }
561
            thing->line_connected_to = NULL;
562
        }
563
    }
564
}
565

566
static void StreamThing_ondrop(Thing *thing, int button, float x, float y)
567
{
568
    if (droppable_highlighted_thing) {
569
        if (droppable_highlighted_thing->what == THING_TRASHCAN) {
570
            TrashThing(thing);
571
        } else if (((droppable_highlighted_thing->what == THING_LOGDEV) || (droppable_highlighted_thing->what == THING_LOGDEV_RECORDING)) && (droppable_highlighted_thing != thing->line_connected_to)) {
572
            /* connect to a logical device! */
573
            SDL_Log("Binding audio stream ('%s') to logical device %u", thing->titlebar, (unsigned int) droppable_highlighted_thing->data.logdev.devid);
574
            if (thing->line_connected_to) {
575
                SDL_UnbindAudioStream(thing->data.stream.stream); /* unbind from current device */
576
                if (thing->line_connected_to->what == THING_LOGDEV_RECORDING) {
577
                    SDL_FlushAudioStream(thing->data.stream.stream);
578
                }
579
            }
580

581
            SDL_BindAudioStream(droppable_highlighted_thing->data.logdev.devid, thing->data.stream.stream); /* bind to new device! */
582
            thing->data.stream.total_bytes = SDL_GetAudioStreamAvailable(thing->data.stream.stream);
583
            thing->progress = 0.0f;  /* ontick will adjust this if we're on a playback device.*/
584
            thing->data.stream.next_level_update = SDL_GetTicks() + 100;
585
            thing->line_connected_to = droppable_highlighted_thing;
586
        }
587
    }
588
}
589

590
static void StreamThing_onmousewheel(Thing *thing, float y)
591
{
592
    thing->meter += y * 0.01f;
593
    thing->meter = SDL_clamp(thing->meter, 0.0f, 1.0f);
594
    SDL_SetAudioStreamGain(thing->data.stream.stream, thing->meter);
595
}
596

597
static void StreamThing_ondraw(Thing *thing, SDL_Renderer *renderer)
598
{
599
    if (thing->line_connected_to) {  /* are we playing? Update progress bar, and bounce the levels a little. */
600
        static const float xlocs[5] = { 18, 39, 59, 79, 99 };
601
        static const float ylocs[5] = { 49, 39, 29, 19, 10 };
602
        const float blockw = soundboard_levels_texture->w;
603
        const float blockh = soundboard_levels_texture->h / 5.0f;
604
        int i, j;
605
        SDL_SetRenderDrawColor(renderer, thing->r, thing->g, thing->b, thing->a);
606
        for (i = 0; i < SDL_arraysize(thing->data.stream.levels); i++) {
607
            const int level = (int) thing->data.stream.levels[i];
608
            const float x = xlocs[i];
609
            for (j = 0; j < level; j++) {
610
                const SDL_FRect src = { 0, soundboard_levels_texture->h - ((j+1) * blockh), blockw, blockh };
611
                const SDL_FRect dst = { thing->rect.x + x, thing->rect.y + ylocs[j], blockw, blockh };
612
                SDL_RenderTexture(renderer, soundboard_levels_texture->texture, &src, &dst);
613
            }
614
        }
615
    }
616
}
617

618
static Thing *CreateStreamThing(const SDL_AudioSpec *spec, const Uint8 *buf, const Uint32 buflen, const char *fname, const float x, const float y)
619
{
620
    static const ThingType can_be_dropped_onto[] = { THING_TRASHCAN, THING_LOGDEV, THING_LOGDEV_RECORDING, THING_NULL };
621
    Thing *thing = CreateThing(THING_STREAM, x, y, 0, -1, -1, soundboard_texture, fname);
622
    if (thing) {
623
        SDL_Log("Adding audio stream for %s", fname ? fname : "(null)");
624
        thing->data.stream.stream = SDL_CreateAudioStream(spec, spec);
625
        if (buf && buflen) {
626
            SDL_PutAudioStreamData(thing->data.stream.stream, buf, (int) buflen);
627
            SDL_FlushAudioStream(thing->data.stream.stream);
628
            thing->data.stream.total_bytes = SDL_GetAudioStreamAvailable(thing->data.stream.stream);
629
        }
630
        thing->ontick = StreamThing_ontick;
631
        thing->ondrag = StreamThing_ondrag;
632
        thing->ondrop = StreamThing_ondrop;
633
        thing->ondraw = StreamThing_ondraw;
634
        thing->onmousewheel = StreamThing_onmousewheel;
635
        thing->can_be_dropped_onto = can_be_dropped_onto;
636
        thing->meter = 1.0f;  /* gain. */
637
    }
638
    return thing;
639
}
640

641
static void WavThing_ondrag(Thing *thing, int button, float x, float y)
642
{
643
    if (button == SDL_BUTTON_RIGHT) {  /* drag out a new audio stream. */
644
        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));
645
    }
646
}
647

648
static void WavThing_ondrop(Thing *thing, int button, float x, float y)
649
{
650
    if (droppable_highlighted_thing) {
651
        if (droppable_highlighted_thing->what == THING_TRASHCAN) {
652
            TrashThing(thing);
653
        }
654
    }
655
}
656

657
static Thing *LoadWavThing(const char *fname, float x, float y)
658
{
659
    Thing *thing = NULL;
660
    char *path;
661
    SDL_AudioSpec spec;
662
    Uint8 *buf = NULL;
663
    Uint32 buflen = 0;
664

665
    path = GetNearbyFilename(fname);
666
    if (path) {
667
        fname = path;
668
    }
669

670
    if (SDL_LoadWAV(fname, &spec, &buf, &buflen) == 0) {
671
        static const ThingType can_be_dropped_onto[] = { THING_TRASHCAN, THING_NULL };
672
        char *titlebar = NULL;
673
        const char *nodirs = SDL_strrchr(fname, '/');
674
        #ifdef SDL_PLATFORM_WINDOWS
675
        const char *nodirs2 = SDL_strrchr(nodirs ? nodirs : fname, '\\');
676
        if (nodirs2) {
677
            nodirs = nodirs2;
678
        }
679
        #endif
680

681
        SDL_Log("Adding WAV file '%s'", fname);
682

683
        if (nodirs) {
684
            nodirs++;
685
        } else {
686
            nodirs = fname;
687
        }
688

689
        SDL_asprintf(&titlebar, "WAV file (\"%s\", %s, %s, %uHz)", nodirs, AudioFmtToString(spec.format), AudioChansToStr(spec.channels), (unsigned int) spec.freq);
690
        thing = CreateThing(THING_WAV, x - (audio_texture->w / 2), y - (audio_texture->h / 2), 5, -1, -1, audio_texture, titlebar);
691
        if (thing) {
692
            SDL_free(titlebar);
693
            SDL_memcpy(&thing->data.wav.spec, &spec, sizeof (SDL_AudioSpec));
694
            thing->data.wav.buf = buf;
695
            thing->data.wav.buflen = buflen;
696
            thing->can_be_dropped_onto = can_be_dropped_onto;
697
            thing->ondrag = WavThing_ondrag;
698
            thing->ondrop = WavThing_ondrop;
699
        }
700
    }
701

702
    SDL_free(path);
703

704
    return thing;
705
}
706

707
static Thing *LoadStockWavThing(const char *fname)
708
{
709
    char *path = GetNearbyFilename(fname);
710
    Thing *thing = LoadWavThing(path ? path : fname, 0.0f, 0.0f);  /* will reposition in a moment. */
711
    SDL_free(path);
712
    return thing;
713
}
714

715
static void LoadStockWavThings(void)
716
{
717
    LoadStockWavThing("sample.wav");
718
    RepositionRowOfThings(THING_WAV, -10.0f);
719
}
720

721
static void DestroyTexture(Texture *tex)
722
{
723
    if (tex) {
724
        if (state->renderers[0] != NULL) {  /* if the renderer went away, this pointer is already bogus. */
725
            SDL_DestroyTexture(tex->texture);
726
        }
727
        SDL_free(tex);
728
    }
729
}
730

731
static Texture *CreateTexture(const char *fname)
732
{
733
    Texture *tex = (Texture *) SDL_calloc(1, sizeof (Texture));
734
    if (!tex) {
735
        SDL_Log("Out of memory!");
736
    } else {
737
        int texw, texh;
738
        tex->texture = LoadTexture(state->renderers[0], fname, SDL_TRUE, &texw, &texh);
739
        if (!tex->texture) {
740
            SDL_Log("Failed to load '%s': %s", fname, SDL_GetError());
741
            SDL_free(tex);
742
            return NULL;
743
        }
744
        SDL_SetTextureBlendMode(tex->texture, SDL_BLENDMODE_BLEND);
745
        tex->w = (float) texw;
746
        tex->h = (float) texh;
747
    }
748
    return tex;
749
}
750

751
static Thing *CreateLogicalDeviceThing(Thing *parent, const SDL_AudioDeviceID which, const float x, const float y);
752

753
static void DeviceThing_ondrag(Thing *thing, int button, float x, float y)
754
{
755
    if ((button == SDL_BUTTON_MIDDLE) && (thing->what == THING_LOGDEV_RECORDING)) {  /* drag out a new stream. This is a UX mess. :/ */
756
        dragging_thing = CreateStreamThing(&thing->data.logdev.spec, NULL, 0, NULL, x, y);
757
        if (dragging_thing) {
758
            dragging_thing->data.stream.next_level_update = SDL_GetTicks() + 100;
759
            SDL_BindAudioStream(thing->data.logdev.devid, dragging_thing->data.stream.stream); /* bind to new device! */
760
            dragging_thing->line_connected_to = thing;
761
        }
762
    } else if (button == SDL_BUTTON_RIGHT) {  /* drag out a new logical device. */
763
        const SDL_AudioDeviceID which = ((thing->what == THING_LOGDEV) || (thing->what == THING_LOGDEV_RECORDING)) ? thing->data.logdev.devid : thing->data.physdev.devid;
764
        const SDL_AudioDeviceID devid = SDL_OpenAudioDevice(which, NULL);
765
        dragging_thing = devid ? CreateLogicalDeviceThing(thing, devid, x - (thing->rect.w / 2), y - (thing->rect.h / 2)) : NULL;
766
    }
767
}
768

769
static void SetLogicalDeviceTitlebar(Thing *thing)
770
{
771
    SDL_AudioSpec *spec = &thing->data.logdev.spec;
772
    int frames = 0;
773
    SDL_GetAudioDeviceFormat(thing->data.logdev.devid, spec, &frames);
774
    SDL_free(thing->titlebar);
775
    SDL_asprintf(&thing->titlebar, "Logical device #%u (%s, %s, %s, %uHz, %d frames)", (unsigned int) thing->data.logdev.devid, thing->data.logdev.recording ? "RECORDING" : "PLAYBACK", AudioFmtToString(spec->format), AudioChansToStr(spec->channels), (unsigned int) spec->freq, frames);
776
}
777

778
static void LogicalDeviceThing_ondrop(Thing *thing, int button, float x, float y)
779
{
780
    if (droppable_highlighted_thing) {
781
        if (droppable_highlighted_thing->what == THING_TRASHCAN) {
782
            TrashThing(thing);
783
        }
784
    }
785
}
786

787
static void SDLCALL PostmixCallback(void *userdata, const SDL_AudioSpec *spec, float *buffer, int buflen)
788
{
789
    Thing *thing = (Thing *) userdata;
790

791
    SDL_LockMutex(thing->data.logdev.postmix_lock);
792

793
    if (thing->data.logdev.postmix_allocated < buflen) {
794
        void *ptr = SDL_realloc(thing->data.logdev.postmix_buffer, buflen);
795
        if (!ptr) {
796
            SDL_UnlockMutex(thing->data.logdev.postmix_lock);
797
            return;  /* oh well. */
798
        }
799
        thing->data.logdev.postmix_buffer = (float *) ptr;
800
        thing->data.logdev.postmix_allocated = buflen;
801
    }
802

803
    SDL_copyp(&thing->data.logdev.postmix_spec, spec);
804
    SDL_memcpy(thing->data.logdev.postmix_buffer, buffer, buflen);
805
    thing->data.logdev.postmix_buflen = buflen;
806
    SDL_AtomicSet(&thing->data.logdev.postmix_updated, 1);
807

808
    SDL_UnlockMutex(thing->data.logdev.postmix_lock);
809
}
810

811
static void UpdateVisualizer(SDL_Renderer *renderer, SDL_Texture *visualizer, const int channels, const float *buffer, const int buflen)
812
{
813
    static const SDL_Color channel_colors[8] = {
814
        { 255, 255, 255, 255 },
815
        { 255, 0, 0, 255 },
816
        { 0, 255, 0, 255 },
817
        { 0, 0, 255, 255 },
818
        { 255, 255, 0, 255 },
819
        { 0, 255, 255, 255 },
820
        { 255, 0, 255, 255 },
821
        { 127, 127, 127, 255 }
822
    };
823

824
    SDL_SetRenderTarget(renderer, visualizer);
825
    SDL_SetRenderDrawColor(renderer, 0, 0, 0, 0);
826
    SDL_RenderClear(renderer);
827

828
    if (buffer && buflen) {
829
        const int frames = (buflen / sizeof (float)) / channels;
830
        const int skip = frames / (VISUALIZER_WIDTH * 2);
831
        int i, j;
832

833
        for (i = channels - 1; i >= 0; i--) {
834
            const SDL_Color *color = &channel_colors[i % SDL_arraysize(channel_colors)];
835
            SDL_FPoint points[VISUALIZER_WIDTH + 2];
836
            float prevx = 0.0f;
837
            int pointidx = 1;
838

839
            points[0].x = 0.0f;
840
            points[0].y = VISUALIZER_HEIGHT * 0.5f;
841

842
            for (j = 0; j < (SDL_arraysize(points)-1); j++) {
843
                const float val = buffer[((j * skip) * channels) + i];
844
                const float x = prevx + 2;
845
                const float y = (VISUALIZER_HEIGHT * 0.5f) - (VISUALIZER_HEIGHT * (val * 0.5f));
846
                SDL_assert(pointidx < SDL_arraysize(points));
847
                points[pointidx].x = x;
848
                points[pointidx].y = y;
849
                pointidx++;
850
                prevx = x;
851
            }
852

853
            SDL_SetRenderDrawColor(renderer, color->r, color->g, color->b, 255);
854
            SDL_RenderLines(renderer, points, pointidx);
855
        }
856
    }
857

858
    SDL_SetRenderTarget(renderer, NULL);
859
}
860

861
static void LogicalDeviceThing_ontick(Thing *thing, Uint64 now)
862
{
863
    const SDL_bool ismousedover = (thing == mouseover_thing);
864

865
    if (!thing->data.logdev.visualizer || !thing->data.logdev.postmix_lock) {  /* need these to work, skip if they failed. */
866
        return;
867
    }
868

869
    if (thing->data.logdev.visualizer_enabled != ismousedover) {
870
        thing->data.logdev.visualizer_enabled = ismousedover;
871
        if (!ismousedover) {
872
            SDL_SetAudioPostmixCallback(thing->data.logdev.devid, NULL, NULL);
873
        } else {
874
            if (thing->data.logdev.postmix_buffer) {
875
                SDL_memset(thing->data.logdev.postmix_buffer, '\0', thing->data.logdev.postmix_buflen);
876
            }
877
            SDL_AtomicSet(&thing->data.logdev.postmix_updated, 1);  /* so this will at least clear the texture later. */
878
            SDL_SetAudioPostmixCallback(thing->data.logdev.devid, PostmixCallback, thing);
879
        }
880
    }
881
}
882

883
static void LogicalDeviceThing_ondraw(Thing *thing, SDL_Renderer *renderer)
884
{
885
    if (thing->data.logdev.visualizer_enabled) {
886
        SDL_FRect dst;
887
        dst.w = thing->rect.w;
888
        dst.h = thing->rect.h;
889
        dst.x = thing->rect.x + ((thing->rect.w - dst.w) / 2);
890
        dst.y = thing->rect.y + ((thing->rect.h - dst.h) / 2);
891

892
        if (SDL_AtomicGet(&thing->data.logdev.postmix_updated)) {
893
            float *buffer;
894
            int channels;
895
            int buflen;
896

897
            SDL_LockMutex(thing->data.logdev.postmix_lock);
898
            channels = thing->data.logdev.postmix_spec.channels;
899
            buflen = thing->data.logdev.postmix_buflen;
900
            buffer = (float *) SDL_malloc(thing->data.logdev.postmix_buflen);
901
            if (buffer) {
902
                SDL_memcpy(buffer, thing->data.logdev.postmix_buffer, thing->data.logdev.postmix_buflen);
903
                SDL_AtomicSet(&thing->data.logdev.postmix_updated, 0);
904
            }
905
            SDL_UnlockMutex(thing->data.logdev.postmix_lock);
906

907
            UpdateVisualizer(renderer, thing->data.logdev.visualizer, channels, buffer, buflen);
908
            SDL_free(buffer);
909
        }
910

911
        SDL_SetRenderDrawColor(renderer, 255, 255, 255, 30);
912
        SDL_RenderTexture(renderer, thing->data.logdev.visualizer, NULL, &dst);
913
    }
914
}
915

916
static void LogicalDeviceThing_onmousewheel(Thing *thing, float y)
917
{
918
    thing->meter += y * 0.01f;
919
    thing->meter = SDL_clamp(thing->meter, 0.0f, 1.0f);
920
    SDL_SetAudioDeviceGain(thing->data.logdev.devid, thing->meter);
921
}
922

923
static Thing *CreateLogicalDeviceThing(Thing *parent, const SDL_AudioDeviceID which, const float x, const float y)
924
{
925
    static const ThingType can_be_dropped_onto[] = { THING_TRASHCAN, THING_NULL };
926
    Thing *physthing = ((parent->what == THING_LOGDEV) || (parent->what == THING_LOGDEV_RECORDING)) ? parent->data.logdev.physdev : parent;
927
    const SDL_bool recording = physthing->data.physdev.recording;
928
    Thing *thing;
929

930
    SDL_Log("Adding logical audio device %u", (unsigned int) which);
931
    thing = CreateThing(recording ? THING_LOGDEV_RECORDING : THING_LOGDEV, x, y, 5, -1, -1, logdev_texture, NULL);
932
    if (thing) {
933
        thing->data.logdev.devid = which;
934
        thing->data.logdev.recording = recording;
935
        thing->data.logdev.physdev = physthing;
936
        thing->data.logdev.visualizer = SDL_CreateTexture(state->renderers[0], SDL_PIXELFORMAT_RGBA8888, SDL_TEXTUREACCESS_TARGET, VISUALIZER_WIDTH, VISUALIZER_HEIGHT);
937
        thing->data.logdev.postmix_lock = SDL_CreateMutex();
938
        if (thing->data.logdev.visualizer) {
939
            SDL_SetTextureBlendMode(thing->data.logdev.visualizer, SDL_BLENDMODE_BLEND);
940
        }
941
        thing->line_connected_to = physthing;
942
        thing->meter = 1.0f;
943
        thing->ontick = LogicalDeviceThing_ontick;
944
        thing->ondrag = DeviceThing_ondrag;
945
        thing->ondrop = LogicalDeviceThing_ondrop;
946
        thing->ondraw = LogicalDeviceThing_ondraw;
947
        thing->onmousewheel = LogicalDeviceThing_onmousewheel;
948
        thing->can_be_dropped_onto = can_be_dropped_onto;
949

950
        SetLogicalDeviceTitlebar(thing);
951
    }
952
    return thing;
953
}
954

955
static void SetPhysicalDeviceTitlebar(Thing *thing)
956
{
957
    int frames = 0;
958
    SDL_AudioSpec *spec = &thing->data.physdev.spec;
959
    SDL_GetAudioDeviceFormat(thing->data.physdev.devid, spec, &frames);
960
    SDL_free(thing->titlebar);
961
    if (thing->data.physdev.devid == SDL_AUDIO_DEVICE_DEFAULT_RECORDING) {
962
        SDL_asprintf(&thing->titlebar, "Default system device (RECORDING, %s, %s, %uHz, %d frames)", AudioFmtToString(spec->format), AudioChansToStr(spec->channels), (unsigned int) spec->freq, frames);
963
    } else if (thing->data.physdev.devid == SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK) {
964
        SDL_asprintf(&thing->titlebar, "Default system device (PLAYBACK, %s, %s, %uHz, %d frames)", AudioFmtToString(spec->format), AudioChansToStr(spec->channels), (unsigned int) spec->freq, frames);
965
    } else {
966
        SDL_asprintf(&thing->titlebar, "Physical device #%u (%s, \"%s\", %s, %s, %uHz, %d frames)", (unsigned int) thing->data.physdev.devid, thing->data.physdev.recording ? "RECORDING" : "PLAYBACK", thing->data.physdev.name, AudioFmtToString(spec->format), AudioChansToStr(spec->channels), (unsigned int) spec->freq, frames);
967
    }
968
}
969

970
static void PhysicalDeviceThing_ondrop(Thing *thing, int button, float x, float y)
971
{
972
    if (droppable_highlighted_thing) {
973
        if (droppable_highlighted_thing->what == THING_TRASHCAN) {
974
            TrashThing(thing);
975
        }
976
    }
977
}
978

979
static void PhysicalDeviceThing_ontick(Thing *thing, Uint64 now)
980
{
981
    const int lifetime = POOF_LIFETIME;
982
    const int elasped = (int) (now - thing->createticks);
983
    if (elasped > lifetime) {
984
        thing->scale = 1.0f;
985
        thing->a = 255;
986
        thing->ontick = NULL;  /* no more ticking. */
987
    } else {
988
        const float pct = ((float) elasped) / ((float) lifetime);
989
        thing->a = (Uint8) (int) (pct * 255.0f);
990
        thing->scale = pct;  /* grow to normal size */
991
    }
992
}
993

994

995
static Thing *CreatePhysicalDeviceThing(const SDL_AudioDeviceID which, const SDL_bool recording)
996
{
997
    static const ThingType can_be_dropped_onto[] = { THING_TRASHCAN, THING_NULL };
998
    static float next_physdev_x = 0;
999
    Thing *thing;
1000
    int winw, winh;
1001

1002
    SDL_GetWindowSize(state->windows[0], &winw, &winh);
1003
    if (next_physdev_x > (winw-physdev_texture->w)) {
1004
        next_physdev_x = 0;
1005
    }
1006

1007
    SDL_Log("Adding physical audio device %u", (unsigned int) which);
1008
    thing = CreateThing(recording ? THING_PHYSDEV_RECORDING : THING_PHYSDEV, next_physdev_x, 170, 5, -1, -1, physdev_texture, NULL);
1009
    if (thing) {
1010
        const char *name = SDL_GetAudioDeviceName(which);
1011
        thing->data.physdev.devid = which;
1012
        thing->data.physdev.recording = recording;
1013
        thing->data.physdev.name = name ? SDL_strdup(name) : NULL;
1014
        thing->ondrag = DeviceThing_ondrag;
1015
        thing->ondrop = PhysicalDeviceThing_ondrop;
1016
        thing->ontick = PhysicalDeviceThing_ontick;
1017
        thing->can_be_dropped_onto = can_be_dropped_onto;
1018

1019
        SetPhysicalDeviceTitlebar(thing);
1020
        if (SDL_GetTicks() <= (app_ready_ticks + 2000)) {  /* assume this is the initial batch if it happens in the first two seconds. */
1021
            RepositionRowOfThings(THING_PHYSDEV, 10.0f);  /* don't rearrange them after the initial add. */
1022
            RepositionRowOfThings(THING_PHYSDEV_RECORDING, 170.0f);  /* don't rearrange them after the initial add. */
1023
            next_physdev_x = 0.0f;
1024
        } else {
1025
            next_physdev_x += physdev_texture->w * 1.5f;
1026
        }
1027
    }
1028

1029
    return thing;
1030
}
1031

1032
static Thing *CreateTrashcanThing(void)
1033
{
1034
    int winw, winh;
1035
    SDL_GetWindowSize(state->windows[0], &winw, &winh);
1036
    return CreateThing(THING_TRASHCAN, winw - trashcan_texture->w, winh - trashcan_texture->h, 10, -1, -1, trashcan_texture, "Drag things here to remove them.");
1037
}
1038

1039
static Thing *CreateDefaultPhysicalDevice(const SDL_bool recording)
1040
{
1041
    return CreatePhysicalDeviceThing(recording ? SDL_AUDIO_DEVICE_DEFAULT_RECORDING : SDL_AUDIO_DEVICE_DEFAULT_PLAYBACK, recording);
1042
}
1043

1044
static void TickThings(void)
1045
{
1046
    Thing *i;
1047
    Thing *next;
1048
    const Uint64 now = SDL_GetTicks();
1049
    for (i = things; i; i = next) {
1050
        next = i->next;  /* in case this deletes itself. */
1051
        if (i->ontick) {
1052
            i->ontick(i, now);
1053
        }
1054
    }
1055
}
1056

1057
static void WindowResized(const int newwinw, const int newwinh)
1058
{
1059
    Thing *i;
1060
    const float neww = (float) newwinw;
1061
    const float newh = (float) newwinh;
1062
    const float oldw = (float) state->window_w;
1063
    const float oldh = (float) state->window_h;
1064
    for (i = things; i; i = i->next) {
1065
        const float halfw = i->rect.w / 2.0f;
1066
        const float halfh = i->rect.h / 2.0f;
1067
        const float x = (i->rect.x + halfw) / oldw;
1068
        const float y = (i->rect.y + halfh) / oldh;
1069
        i->rect.x = (x * neww) - halfw;
1070
        i->rect.y = (y * newh) - halfh;
1071
    }
1072
    state->window_w = newwinw;
1073
    state->window_h = newwinh;
1074
}
1075

1076
SDL_AppResult SDL_AppInit(void **appstate, int argc, char *argv[])
1077
{
1078
    int i;
1079

1080
    state = SDLTest_CommonCreateState(argv, SDL_INIT_VIDEO | SDL_INIT_AUDIO);
1081
    if (!state) {
1082
        return SDL_APP_FAILURE;
1083
    }
1084

1085
    state->window_flags |= SDL_WINDOW_RESIZABLE;
1086

1087
    for (i = 1; i < argc;) {
1088
        int consumed = SDLTest_CommonArg(state, i);
1089
        if (consumed == 0) {
1090
            consumed = -1;
1091
            /* add our own command lines here. */
1092
        }
1093
        if (consumed < 0) {
1094
            static const char *options[] = {
1095
                /* add our own command lines here. */
1096
                /*"[--blend none|blend|add|mod|mul|sub]",*/
1097
                NULL
1098
            };
1099
            SDLTest_CommonLogUsage(state, argv[0], options);
1100
            return SDL_APP_FAILURE;
1101
        }
1102
        i += consumed;
1103
    }
1104

1105
    if (!SDLTest_CommonInit(state)) {
1106
        return SDL_APP_FAILURE;
1107
    }
1108

1109
    if (state->audio_id) {
1110
        SDL_CloseAudioDevice(state->audio_id);
1111
        state->audio_id = 0;
1112
    }
1113

1114
    SetDefaultTitleBar();
1115

1116
    if ((physdev_texture = CreateTexture("physaudiodev.bmp")) == NULL) { return SDL_APP_FAILURE; }
1117
    if ((logdev_texture = CreateTexture("logaudiodev.bmp")) == NULL) { return SDL_APP_FAILURE; }
1118
    if ((audio_texture = CreateTexture("audiofile.bmp")) == NULL) { return SDL_APP_FAILURE; }
1119
    if ((trashcan_texture = CreateTexture("trashcan.bmp")) == NULL) { return SDL_APP_FAILURE; }
1120
    if ((soundboard_texture = CreateTexture("soundboard.bmp")) == NULL) { return SDL_APP_FAILURE; }
1121
    if ((soundboard_levels_texture = CreateTexture("soundboard_levels.bmp")) == NULL) { return SDL_APP_FAILURE; }
1122

1123
    LoadStockWavThings();
1124
    CreateTrashcanThing();
1125
    CreateDefaultPhysicalDevice(SDL_FALSE);
1126
    CreateDefaultPhysicalDevice(SDL_TRUE);
1127

1128
    return SDL_APP_CONTINUE;
1129
}
1130

1131

1132
static SDL_bool saw_event = SDL_FALSE;
1133

1134
SDL_AppResult SDL_AppEvent(void *appstate, const SDL_Event *event)
1135
{
1136
    Thing *thing = NULL;
1137

1138
    saw_event = SDL_TRUE;
1139

1140
    switch (event->type) {
1141
        case SDL_EVENT_MOUSE_MOTION:
1142
            thing = UpdateMouseOver(event->motion.x, event->motion.y);
1143
            if ((dragging_button == -1) && event->motion.state) {
1144
                if (event->motion.state & SDL_BUTTON_LMASK) {
1145
                    /* for people that don't have all three buttons... */
1146
                    if (ctrl_held) {
1147
                        dragging_button = SDL_BUTTON_RIGHT;
1148
                    } else if (alt_held) {
1149
                        dragging_button = SDL_BUTTON_MIDDLE;
1150
                    } else {
1151
                        dragging_button = SDL_BUTTON_LEFT;
1152
                    }
1153
                    dragging_button_real = SDL_BUTTON_LEFT;
1154
                } else if (event->motion.state & SDL_BUTTON_RMASK) {
1155
                    dragging_button = SDL_BUTTON_RIGHT;
1156
                    dragging_button_real = SDL_BUTTON_RIGHT;
1157
                } else if (event->motion.state & SDL_BUTTON_MMASK) {
1158
                    dragging_button = SDL_BUTTON_MIDDLE;
1159
                    dragging_button_real = SDL_BUTTON_MIDDLE;
1160
                }
1161

1162
                if (dragging_button != -1) {
1163
                    dragging_thing = thing;
1164
                    if (thing && thing->ondrag) {
1165
                        thing->ondrag(thing, dragging_button, event->motion.x, event->motion.y);
1166
                    }
1167
                }
1168
            }
1169

1170
            droppable_highlighted_thing = NULL;
1171
            if (dragging_thing) {
1172
                dragging_thing->rect.x = event->motion.x - (dragging_thing->rect.w / 2);
1173
                dragging_thing->rect.y = event->motion.y - (dragging_thing->rect.h / 2);
1174
                if (dragging_thing->can_be_dropped_onto) {
1175
                    thing = FindThingAtPoint(event->motion.x, event->motion.y);
1176
                    if (thing) {
1177
                        int i;
1178
                        for (i = 0; dragging_thing->can_be_dropped_onto[i]; i++) {
1179
                            if (dragging_thing->can_be_dropped_onto[i] == thing->what) {
1180
                                droppable_highlighted_thing = thing;
1181
                                break;
1182
                            }
1183
                        }
1184
                    }
1185
                }
1186
            }
1187
            break;
1188

1189
        case SDL_EVENT_MOUSE_BUTTON_DOWN:
1190
            thing = UpdateMouseOver(event->button.x, event->button.y);
1191
            break;
1192

1193
        case SDL_EVENT_MOUSE_BUTTON_UP:
1194
            if (dragging_button_real == event->button.button) {
1195
                Thing *dropped_thing = dragging_thing;
1196
                dragging_thing = NULL;
1197
                dragging_button = -1;
1198
                dragging_button_real = -1;
1199
                if (dropped_thing && dropped_thing->ondrop) {
1200
                    dropped_thing->ondrop(dropped_thing, event->button.button, event->button.x, event->button.y);
1201
                }
1202
                droppable_highlighted_thing = NULL;
1203
            }
1204
            thing = UpdateMouseOver(event->button.x, event->button.y);
1205
            break;
1206

1207
        case SDL_EVENT_MOUSE_WHEEL:
1208
            thing = UpdateMouseOver(event->wheel.mouse_x, event->wheel.mouse_y);
1209
            if (thing && thing->onmousewheel) {
1210
                thing->onmousewheel(thing, event->wheel.y * ((event->wheel.direction == SDL_MOUSEWHEEL_FLIPPED) ? -1.0f : 1.0f));
1211
            }
1212
            break;
1213

1214
        case SDL_EVENT_KEY_DOWN:
1215
        case SDL_EVENT_KEY_UP:
1216
            ctrl_held = ((event->key.mod & SDL_KMOD_CTRL) != 0);
1217
            alt_held = ((event->key.mod & SDL_KMOD_ALT) != 0);
1218
            break;
1219

1220
        case SDL_EVENT_DROP_FILE:
1221
            SDL_Log("Drop file! '%s'", event->drop.data);
1222
            LoadWavThing(event->drop.data, event->drop.x, event->drop.y);
1223
            /* SDL frees event->drop.data for you when you use SDL_AppEvent(). */
1224
            break;
1225

1226
        case SDL_EVENT_WINDOW_RESIZED:
1227
            WindowResized(event->window.data1, event->window.data2);
1228
            break;
1229

1230
        case SDL_EVENT_AUDIO_DEVICE_ADDED:
1231
            CreatePhysicalDeviceThing(event->adevice.which, event->adevice.recording);
1232
            break;
1233

1234
        case SDL_EVENT_AUDIO_DEVICE_REMOVED: {
1235
            const SDL_AudioDeviceID which = event->adevice.which;
1236
            Thing *i, *next;
1237
            SDL_Log("Removing audio device %u", (unsigned int) which);
1238
            for (i = things; i; i = next) {
1239
                next = i->next;
1240
                if (((i->what == THING_PHYSDEV) || (i->what == THING_PHYSDEV_RECORDING)) && (i->data.physdev.devid == which)) {
1241
                    TrashThing(i);
1242
                    next = things;  /* in case we mangled the list. */
1243
                } else if (((i->what == THING_LOGDEV) || (i->what == THING_LOGDEV_RECORDING)) && (i->data.logdev.devid == which)) {
1244
                    TrashThing(i);
1245
                    next = things;  /* in case we mangled the list. */
1246
                }
1247
            }
1248
            break;
1249
        }
1250

1251
        default: break;
1252
    }
1253

1254
    return SDLTest_CommonEventMainCallbacks(state, event);
1255
}
1256

1257
SDL_AppResult SDL_AppIterate(void *appstate)
1258
{
1259
    if (app_ready_ticks == 0) {
1260
        app_ready_ticks = SDL_GetTicks();
1261
    }
1262

1263
    TickThings();
1264
    Draw();
1265

1266
    if (saw_event) {
1267
        saw_event = SDL_FALSE;  /* reset this so we know when SDL_AppEvent() runs again */
1268
    } else {
1269
        SDL_Delay(10);
1270
    }
1271

1272
    return SDL_APP_CONTINUE;
1273
}
1274

1275
void SDL_AppQuit(void *appstate)
1276
{
1277
    while (things) {
1278
        DestroyThing(things);  /* make sure all the audio devices are closed, etc. */
1279
    }
1280

1281
    DestroyTexture(physdev_texture);
1282
    DestroyTexture(logdev_texture);
1283
    DestroyTexture(audio_texture);
1284
    DestroyTexture(trashcan_texture);
1285
    DestroyTexture(soundboard_texture);
1286
    DestroyTexture(soundboard_levels_texture);
1287
    SDLTest_CommonQuit(state);
1288
}
1289

1290

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.