OVR: Make Benson (control panel) closer to oberver
[mercenary-reloaded.git] / src / libsound / sound.c
1 /* sound emulation
2  *
3  * (C) 2018 by Andreas Eversberg <jolly@eversberg.eu>
4  * All Rights Reserved
5  *
6  * This program is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
18  */
19
20 #include <stdio.h>
21 #include <stdint.h>
22 #include <string.h>
23 #include <math.h>
24 #include "../libsdl/print.h"
25 #include "sound.h"
26 #include "../libcpu/m68kcpu.h"
27
28 //#define DEBUG_SOUND
29
30 #define CLOCK           3546895.0
31 #define DMACON          0xdff096
32 #define INTENA          0xdff09a
33 #define IOBASE          0xdff000
34 #define AUDIOBASE       0xdff0a0
35 #define AUDxLCH         0x0
36 #define AUDxLCL         0x2
37 #define AUDxLEN         0x4
38 #define AUDxPER         0x6
39 #define AUDxVOL         0x8
40
41 static struct audio_channel {
42         int             dma_on;         /* set if dma is running */
43         int             irq_on;         /* set if irq is enabled */
44         uint32_t        start;          /* sample address to start from */
45         int             length;         /* length of samples (not words) */
46         uint32_t        pointer;        /* pointer of renderer */
47         uint32_t        end;            /* end of current sample */
48         double          offset;         /* offset between two (input) samples */
49         double          step;           /* number of input samples per output sample */
50         double          volume;         /* volume of channel in the result sample */
51         float           sample;         /* current (last read) sample */
52 } channel[4] = {
53         { 0, 0, 0xffffffff, 0, 0, 0, 0.0, 0.0, 0.0, 0.0 },
54         { 0, 0, 0xffffffff, 0, 0, 0, 0.0, 0.0, 0.0, 0.0 },
55         { 0, 0, 0xffffffff, 0, 0, 0, 0.0, 0.0, 0.0, 0.0 },
56         { 0, 0, 0xffffffff, 0, 0, 0, 0.0, 0.0, 0.0, 0.0 },
57 };
58
59 double filter_alpha = 0.0, filter_last = 0.0;
60
61 static void (*sound_irq)(void) = NULL;
62
63 static int start_dma(int c, struct audio_channel *chan)
64 {
65         /* fetch sample start and length and use it for rendering */
66         chan->pointer = chan->start;
67         chan->end = chan->start + chan->length;
68         /* check ranges */
69         if (chan->length == 0) {
70                 print_error("Sample length of channel %d is 0, please fix!\n", c);
71                 chan->dma_on = 0;
72                 return -1;
73         }
74         if (chan->pointer >= 0x80000) {
75                 print_error("Sample pointer 0x%08x of channel %d is outside memory, please fix!\n", chan->pointer, c);
76                 chan->dma_on = 0;
77                 return -1;
78         }
79         if (chan->end > 0x80000) {
80                 print_error("Sample end (pointer 0x%08x + length 0x%x) of channel %d is outside memory, please fix!\n", chan->pointer, chan->length, c);
81                 chan->dma_on = 0;
82                 return -1;
83         }
84
85         return 0;
86 }
87
88 static void do_irq(int __attribute__((unused)) c, struct audio_channel *chan)
89 {
90         if (chan->irq_on) {
91 #ifdef DEBUG_SOUND
92                 printf("Audio channel %d: IRQ is triggered\n", c);
93 #endif
94                 /* IRQ is used for geiger counter */
95                 sound_irq();
96         }
97 }
98
99 void emulate_sound_write(uint32_t address, uint16_t value, int samplerate)
100 {
101         int c;
102
103         switch (address) {
104         case DMACON:
105                 /* handle DMA flag and start DMA, if required */
106                 for (c = 0; c < 4; c++) {
107                         /* if nothing should be changed */
108                         if (!(value & (1 << c)))
109                                 continue;
110                         if (!(value & 0x8000)) {
111                                 channel[c].dma_on = 0;
112 #ifdef DEBUG_SOUND
113                                 printf("Audio channel %d: Turning DMA off\n", c);
114 #endif
115                                 continue;
116                         }
117                         if (channel[c].dma_on == 1)
118                                 continue;
119                         if (start_dma(c, &channel[c]) < 0) {
120 #ifdef DEBUG_SOUND
121                                 printf("Audio channel %d: Turning DMA on failed\n", c);
122 #endif
123                                 continue;
124                         }
125 #ifdef DEBUG_SOUND
126                         printf("Audio channel %d: Turning DMA on\n", c);
127 #endif
128                         channel[c].dma_on = 1;
129                 }
130                 return;
131         case INTENA:
132                 /* handle IRQ flag */
133                 for (c = 0; c < 4; c++) {
134                         /* if nothing should be changed */
135                         if (!(value & (0x0080 << c)))
136                                 continue;
137                         if (!(value & 0x8000)) {
138                                 channel[c].irq_on = 0;
139 #ifdef DEBUG_SOUND
140                                 printf("Audio channel %d: Turning IRQ off\n", c);
141 #endif
142                                 continue;
143                         } else {
144                                 channel[c].irq_on = 1;
145 #ifdef DEBUG_SOUND
146                                 printf("Audio channel %d: Turning IRQ on\n", c);
147 #endif
148                                 continue;
149                         }
150                 }
151                 return;
152         }
153
154         /* return if not register OR get channel from address */
155         if (address < AUDIOBASE || address >= AUDIOBASE + 0x03f)
156                 return;
157         c = (address - AUDIOBASE) >> 4;
158
159         switch (address & 0xf) {
160         case AUDxLCH:
161 #ifdef DEBUG_SOUND
162                 printf("Audio channel %d: Setting high word of sample start to 0x%04x\n", c, value);
163 #endif
164                 channel[c].start = (channel[c].start & 0x0000ffff) | (value << 16);
165                 break;
166         case AUDxLCL:
167 #ifdef DEBUG_SOUND
168                 printf("Audio channel %d: Setting low word of sample start to 0x%04x\n", c, value);
169 #endif
170                 channel[c].start = (channel[c].start & 0xffff0000) | (value & 0xfffe);
171                 break;
172         case AUDxLEN:
173 #ifdef DEBUG_SOUND
174                 printf("Audio channel %d: Setting sample length to %d words\n", c, value);
175 #endif
176                 channel[c].length = value << 1;
177                 break;
178         case AUDxPER:
179                 if (value == 0)
180                         value = 1;
181 #ifdef DEBUG_SOUND
182                 printf("Audio channel %d: Setting sample frequency to %.2f Hz\n", c, CLOCK / (double)value);
183 #endif
184                 channel[c].step = CLOCK / (double)value / (double)samplerate;
185                 /* sample rate never at or above output rate */
186                 if (channel[c].step > 0.999)
187                         channel[c].step = 0.999;
188                 break;
189         case AUDxVOL:
190                 if (value > 64)
191                         value = 64;
192 #ifdef DEBUG_SOUND
193                 printf("Audio channel %d: Setting volume to %d (0..64)\n", c, value);
194 #endif
195                 channel[c].volume = (double)value / 64.0 / 4.0; /* 1/4th volume per channel */
196                 break;
197         }
198 }
199
200 /* render sound from memory and add each channel to ring buffer array */
201 void render_sound(uint8_t *memory, stereo_t *sample, int buffer_size, int buffer_pos, int length, int filter)
202 {
203         int c, s, pos;
204         struct audio_channel *chan;
205         double new_offset;
206         float new_sample;
207
208         /* clear buffer first */
209         pos = buffer_pos;
210         for (s = 0; s < length; s++) {
211                 sample[pos].left = 0.0;
212                 pos = (pos + 1) % buffer_size;
213         }
214
215         /* Note: We must render sound, even if volume is 0.0,
216          * because the sample pointers must continue, as it would happen in a real Amiga.
217          * But this may have no practical use in this game.
218          */
219         pos = buffer_pos;
220         for (s = 0; s < length; s++) {
221                 /* render each channel and sum the result */
222                 for (c = 0; c < 4; c++) {
223                         chan = &channel[c];
224                         /* skip if DMA is disabled */
225                         if (!chan->dma_on)
226                                 continue;
227                         new_offset = chan->offset + chan->step;
228                         if (new_offset < 1.0)
229                                 /* no interpolation: use last input sample, because there is no change */
230                                 sample[pos].left += chan->sample * chan->volume;
231                         else {
232                                 /* there is a change of input sample, so read new sample */
233                                 new_offset -= 1.0;
234                                 new_sample = (double)(((int8_t *)memory)[chan->pointer++]) / 128.0;
235                                 if (chan->pointer == chan->end) {
236                                         start_dma(c, chan);
237                                         do_irq(c, chan);
238                                 }
239                                 /* linear interpolation: add portion of last sample to portion of new sample */
240                                 sample[pos].left += (chan->sample * (1.0 - chan->offset) + new_sample * new_offset) / chan->step * chan->volume;
241                                 chan->sample = new_sample;
242                         }
243                         chan->offset = new_offset;
244                 }
245                 pos = (pos + 1) % buffer_size;
246         }
247
248         if (filter) {
249                 /* do low-pass filtering */
250                 pos = buffer_pos;
251                 for (s = 0; s < length; s++) {
252                         /* y[i] := y[i-1] + alpha * (x[i] - y[i-1]) */
253                         filter_last = sample[pos].left = filter_last + filter_alpha * (sample[pos].left - filter_last);
254                         pos = (pos + 1) % buffer_size;
255                 }
256         }
257
258         /* copy left to right */
259         pos = buffer_pos;
260         for (s = 0; s < length; s++) {
261                 sample[pos].right = sample[pos].left;
262                 pos = (pos + 1) % buffer_size;
263         }
264 }
265
266 void sound_init(int samplerate, void (*_sound_irq)(void))
267 {
268         double dt = 1.0 / (double)samplerate;
269         double C = 0.0000001;
270         double R = 360;
271         double alpha = dt / (R*C + dt);
272 #ifdef DEBUG_SOUND
273         double cutoff = 1.0 / (2 * M_PI * R * C);
274 #endif
275
276 #ifdef DEBUG_SOUND
277         printf("filter-cutoff: %.0f Hz (alpha = %.4f)\n", cutoff, alpha);
278 #endif
279         filter_alpha = alpha;
280
281         sound_irq = _sound_irq;
282 }
283