| 1 | /* |
| 2 | audio_ps3.c: the UI's sound effects, through cellAudio. |
| 3 |
|
| 4 | Three short sounds, made here (no files): a soft tick for moving, a rising |
| 5 | two-note blip for Cross, a falling one for Circle. A thread keeps the audio |
| 6 | port's ring of blocks filled, mixing whatever's playing; with nothing playing |
| 7 | it fills silence. Quiet, so the XMB's own music isn't drowned. |
| 8 | */ |
| 9 | #include <stdlib.h> |
| 10 | #include <unistd.h> |
| 11 | #include <string.h> |
| 12 | #include <math.h> |
| 13 | #include <ppu-types.h> |
| 14 | #include <audio/audio.h> |
| 15 | #include <sys/event_queue.h> |
| 16 | #include <sysmodule/sysmodule.h> |
| 17 | #include "../plat.h" |
| 18 |
|
| 19 | #define RATE 48000 |
| 20 | #define VOICES 4 |
| 21 |
|
| 22 | static float *pcm[SND_COUNT]; // mono, RATE a second |
| 23 | static int pcm_len[SND_COUNT]; |
| 24 | static volatile int asked[SND_COUNT]; // counted up by plat_sound; the thread plays one per step |
| 25 | static volatile int on = 1, running; |
| 26 | static u32 port; |
| 27 | static sys_event_queue_t queue; |
| 28 | static sys_ipc_key_t key; |
| 29 |
|
| 30 | // a short tone gliding from f0 to f1 Hz, with a soft attack and an exponential fade |
| 31 | static void Make(int id, float ms, float f0, float f1, float level) |
| 32 | { |
| 33 | int n = (int)(RATE * ms / 1000); |
| 34 | float *p = malloc(n * sizeof(float)), ph = 0; |
| 35 | if (!p) return; |
| 36 | for (int i = 0; i < n; i++) { |
| 37 | float t = (float)i / n, f = f0 + (f1 - f0) * t; |
| 38 | float env = (i < RATE / 1000 ? i / (RATE / 1000.0f) : 1.0f) * expf(-5.0f * t); |
| 39 | ph += 2 * (float)M_PI * f / RATE; |
| 40 | p[i] = sinf(ph) * env * level; |
| 41 | } |
| 42 | pcm[id] = p; |
| 43 | pcm_len[id] = n; |
| 44 | } |
| 45 |
|
| 46 | typedef struct { int id, pos; } voice_t; |
| 47 |
|
| 48 | static void Thread(void *arg) |
| 49 | { |
| 50 | (void)arg; |
| 51 | audioPortConfig cfg; |
| 52 | audioGetPortConfig(port, &cfg); |
| 53 | volatile u64 *read_index = (volatile u64 *)(u64)cfg.readIndex; |
| 54 | float *ring = (float *)(u64)cfg.audioDataStart; |
| 55 | int blocks = (int)cfg.numBlocks, next = 1, seen[SND_COUNT] = {0}; |
| 56 | voice_t v[VOICES]; |
| 57 | memset(v, 0, sizeof(v)); |
| 58 | for (int i = 0; i < VOICES; i++) v[i].id = -1; |
| 59 | while (running) { |
| 60 | sys_event_t ev; |
| 61 | sysEventQueueReceive(queue, &ev, 20 * 1000); |
| 62 | // new sounds asked for |
| 63 | for (int s = 0; s < SND_COUNT; s++) |
| 64 | while (seen[s] != asked[s]) { |
| 65 | seen[s]++; |
| 66 | if (!on || !pcm[s]) continue; |
| 67 | int k = 0; |
| 68 | for (int i = 1; i < VOICES; i++) // a free voice, or the one furthest along |
| 69 | if (v[i].id < 0 || (v[k].id >= 0 && v[i].pos > v[k].pos)) k = i; |
| 70 | v[k].id = s; |
| 71 | v[k].pos = 0; |
| 72 | } |
| 73 | // every block the port isn't playing yet, up to the one it is |
| 74 | while (next != (int)(*read_index % blocks)) { |
| 75 | float *out = ring + (size_t)next * AUDIO_BLOCK_SAMPLES * 2; |
| 76 | for (int i = 0; i < AUDIO_BLOCK_SAMPLES; i++) { |
| 77 | float m = 0; |
| 78 | for (int k = 0; k < VOICES; k++) { |
| 79 | if (v[k].id < 0) continue; |
| 80 | m += pcm[v[k].id][v[k].pos++]; |
| 81 | if (v[k].pos >= pcm_len[v[k].id]) v[k].id = -1; |
| 82 | } |
| 83 | out[i * 2] = out[i * 2 + 1] = m; |
| 84 | } |
| 85 | next = (next + 1) % blocks; |
| 86 | } |
| 87 | } |
| 88 | } |
| 89 |
|
| 90 | int plat_sound_init(void) |
| 91 | { |
| 92 | Make(SND_MOVE, 28, 1500, 1350, 0.05f); |
| 93 | Make(SND_OK, 70, 660, 990, 0.08f); |
| 94 | Make(SND_BACK, 70, 880, 560, 0.07f); |
| 95 | audioPortParam param; |
| 96 | memset(¶m, 0, sizeof(param)); |
| 97 | param.numChannels = AUDIO_PORT_2CH; |
| 98 | param.numBlocks = AUDIO_BLOCK_8; |
| 99 | param.level = 1.0f; |
| 100 | s32 r; |
| 101 | if ((r = sysModuleLoad(SYSMODULE_AUDIO)) != 0 && r != 0x80012003) // (already loaded is fine) |
| 102 | plat_log("sound: sysModuleLoad = 0x%08x", (unsigned)r); |
| 103 | if ((r = audioInit()) != 0 && r != (s32)0x80310701) { // (already initialised) |
| 104 | plat_log("sound: audioInit = 0x%08x: no sound effects", (unsigned)r); |
| 105 | return 0; |
| 106 | } |
| 107 | if ((r = audioPortOpen(¶m, &port)) != 0) { |
| 108 | plat_log("sound: audioPortOpen = 0x%08x: no sound effects", (unsigned)r); |
| 109 | return 0; |
| 110 | } |
| 111 | if ((r = audioCreateNotifyEventQueue(&queue, &key)) != 0 || (r = audioSetNotifyEventQueue(key)) != 0) { |
| 112 | plat_log("sound: event queue = 0x%08x: no sound effects", (unsigned)r); |
| 113 | audioPortClose(port); |
| 114 | return 0; |
| 115 | } |
| 116 | audioPortStart(port); |
| 117 | running = 1; |
| 118 | if (!plat_thread_start(Thread, NULL, 900, 64, "sound")) { // (above the UI: a late block is a click) |
| 119 | running = 0; |
| 120 | audioPortStop(port); |
| 121 | audioPortClose(port); |
| 122 | return 0; |
| 123 | } |
| 124 | plat_log("sound: on"); |
| 125 | return 1; |
| 126 | } |
| 127 |
|
| 128 | void plat_sound(int id) |
| 129 | { |
| 130 | if (id >= 0 && id < SND_COUNT && running && on) asked[id]++; |
| 131 | } |
| 132 |
|
| 133 | void plat_sound_enable(int enable) { on = enable; } |
| 134 |
|
| 135 | void plat_sound_quit(void) |
| 136 | { |
| 137 | if (!running) return; |
| 138 | running = 0; |
| 139 | usleep(60 * 1000); // (the thread's last wait) |
| 140 | audioPortStop(port); |
| 141 | audioRemoveNotifyEventQueue(key); |
| 142 | sysEventQueueDestroy(queue, 0); |
| 143 | audioPortClose(port); |
| 144 | audioQuit(); |
| 145 | } |