summaryrefslogtreecommitdiff
path: root/apps/plugins/xworld/mixer.c
diff options
context:
space:
mode:
Diffstat (limited to 'apps/plugins/xworld/mixer.c')
-rw-r--r--apps/plugins/xworld/mixer.c199
1 files changed, 199 insertions, 0 deletions
diff --git a/apps/plugins/xworld/mixer.c b/apps/plugins/xworld/mixer.c
new file mode 100644
index 0000000000..de7536cd36
--- /dev/null
+++ b/apps/plugins/xworld/mixer.c
@@ -0,0 +1,199 @@
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2014 Franklin Wei, Benjamin Brown
11 * Copyright (C) 2004 Gregory Montoir
12 *
13 * This program is free software; you can redistribute it and/or
14 * modify it under the terms of the GNU General Public License
15 * as published by the Free Software Foundation; either version 2
16 * of the License, or (at your option) any later version.
17 *
18 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
19 * KIND, either express or implied.
20 *
21 ***************************************************************************/
22
23#include "mixer.h"
24#include "serializer.h"
25#include "sys.h"
26
27static int8_t ICODE_ATTR addclamp(int a, int b) {
28 int add = a + b;
29 if (add < -128) {
30 add = -128;
31 }
32 else if (add > 127) {
33 add = 127;
34 }
35 return (int8_t)add;
36}
37
38void mixer_create(struct Mixer* mx, struct System *stub)
39{
40 mx->sys = stub;
41}
42
43static void mixer_mixCallback(void *param, uint8_t *buf, int len);
44
45void mixer_init(struct Mixer* mx) {
46 rb->memset(mx->_channels, 0, sizeof(mx->_channels));
47 if(!mx->sys)
48 {
49 error("in mixer sys is NULL");
50 }
51 mx->_mutex = sys_createMutex(mx->sys);
52 sys_startAudio(mx->sys, mixer_mixCallback, mx);
53}
54
55void mixer_free(struct Mixer* mx) {
56 mixer_stopAll(mx);
57 sys_stopAudio(mx->sys);
58 sys_destroyMutex(mx->sys, mx->_mutex);
59}
60
61void mixer_playChannel(struct Mixer* mx, uint8_t channel, const struct MixerChunk *mc, uint16_t freq, uint8_t volume) {
62 debug(DBG_SND, "mixer_playChannel(%d, %d, %d)", channel, freq, volume);
63 assert(channel < AUDIO_NUM_CHANNELS);
64
65 /* FW: the mutex code was converted 1:1 from C++ to C, leading to the ugly calls */
66 /* to constructors/destructors as seen here */
67
68 struct MutexStack_t ms;
69 MutexStack(&ms, mx->sys, mx->_mutex);
70
71 struct MixerChannel *ch = &mx->_channels[channel];
72 ch->active = true;
73 ch->volume = volume;
74 ch->chunk = *mc;
75 ch->chunkPos = 0;
76 ch->chunkInc = (freq << 8) / sys_getOutputSampleRate(mx->sys);
77
78 MutexStack_destroy(&ms);
79}
80
81void mixer_stopChannel(struct Mixer* mx, uint8_t channel) {
82 debug(DBG_SND, "mixer_stopChannel(%d)", channel);
83 assert(channel < AUDIO_NUM_CHANNELS);
84
85 struct MutexStack_t ms;
86 MutexStack(&ms, mx->sys, mx->_mutex);
87
88 mx->_channels[channel].active = false;
89
90 MutexStack_destroy(&ms);
91}
92
93void mixer_setChannelVolume(struct Mixer* mx, uint8_t channel, uint8_t volume) {
94 debug(DBG_SND, "mixer_setChannelVolume(%d, %d)", channel, volume);
95 assert(channel < AUDIO_NUM_CHANNELS);
96
97 struct MutexStack_t ms;
98 MutexStack(&ms, mx->sys, mx->_mutex);
99
100 mx->_channels[channel].volume = volume;
101
102 MutexStack_destroy(&ms);
103}
104
105void mixer_stopAll(struct Mixer* mx) {
106 debug(DBG_SND, "mixer_stopAll()");
107
108 struct MutexStack_t ms;
109 MutexStack(&ms, mx->sys, mx->_mutex);
110
111 for (uint8_t i = 0; i < AUDIO_NUM_CHANNELS; ++i) {
112 mx->_channels[i].active = false;
113 }
114
115 MutexStack_destroy(&ms);
116}
117
118/* Mx is SDL callback. Called in order to populate the buf with len bytes. */
119/* The mixer iterates through all active channels and combine all sounds. */
120
121/* Since there is no way to know when SDL will ask for a buffer fill, we need */
122/* to synchronize with a mutex so the channels remain stable during the execution */
123/* of this method. */
124static void ICODE_ATTR mixer_mix(struct Mixer* mx, int8_t *buf, int len) {
125 int8_t *pBuf;
126
127 struct MutexStack_t ms;
128 MutexStack(&ms, mx->sys, mx->_mutex);
129
130 /* Clear the buffer since nothing guarantees we are receiving clean memory. */
131 rb->memset(buf, 0, len);
132
133 for (uint8_t i = 0; i < AUDIO_NUM_CHANNELS; ++i) {
134 struct MixerChannel *ch = &mx->_channels[i];
135 if (!ch->active)
136 continue;
137
138 pBuf = buf;
139 for (int j = 0; j < len; ++j, ++pBuf) {
140
141 uint16_t p1, p2;
142 uint16_t ilc = (ch->chunkPos & 0xFF);
143 p1 = ch->chunkPos >> 8;
144 ch->chunkPos += ch->chunkInc;
145
146 if (ch->chunk.loopLen != 0) {
147 if (p1 == ch->chunk.loopPos + ch->chunk.loopLen - 1) {
148 debug(DBG_SND, "Looping sample on channel %d", i);
149 ch->chunkPos = p2 = ch->chunk.loopPos;
150 } else {
151 p2 = p1 + 1;
152 }
153 } else {
154 if (p1 == ch->chunk.len - 1) {
155 debug(DBG_SND, "Stopping sample on channel %d", i);
156 ch->active = false;
157 break;
158 } else {
159 p2 = p1 + 1;
160 }
161 }
162 /* interpolate */
163 int8_t b1 = *(int8_t *)(ch->chunk.data + p1);
164 int8_t b2 = *(int8_t *)(ch->chunk.data + p2);
165 int8_t b = (int8_t)((b1 * (0xFF - ilc) + b2 * ilc) >> 8);
166
167 /* set volume and clamp */
168 *pBuf = addclamp(*pBuf, (int)b * ch->volume / 0x40); /* 0x40=64 */
169 }
170
171 }
172
173 MutexStack_destroy(&ms);
174}
175
176static void ICODE_ATTR mixer_mixCallback(void *param, uint8_t *buf, int len) {
177 debug(DBG_SND, "mixer_mixCallback");
178 mixer_mix((struct Mixer*)param, (int8_t *)buf, len);
179}
180
181void mixer_saveOrLoad(struct Mixer* mx, struct Serializer *ser) {
182 sys_lockMutex(mx->sys, mx->_mutex);
183 for (int i = 0; i < AUDIO_NUM_CHANNELS; ++i) {
184 struct MixerChannel *ch = &mx->_channels[i];
185 struct Entry entries[] = {
186 SE_INT(&ch->active, SES_BOOL, VER(2)),
187 SE_INT(&ch->volume, SES_INT8, VER(2)),
188 SE_INT(&ch->chunkPos, SES_INT32, VER(2)),
189 SE_INT(&ch->chunkInc, SES_INT32, VER(2)),
190 SE_PTR(&ch->chunk.data, VER(2)),
191 SE_INT(&ch->chunk.len, SES_INT16, VER(2)),
192 SE_INT(&ch->chunk.loopPos, SES_INT16, VER(2)),
193 SE_INT(&ch->chunk.loopLen, SES_INT16, VER(2)),
194 SE_END()
195 };
196 ser_saveOrLoadEntries(ser, entries);
197 }
198 sys_unlockMutex(mx->sys, mx->_mutex);
199};