Replace printf/fprintf with own print_info() / print_error() using SDL_log
[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 IOBASE          0xdff000
33 #define AUDIOBASE       0xdff0a0
34 #define AUDxLCH         0x0
35 #define AUDxLCL         0x2
36 #define AUDxLEN         0x4
37 #define AUDxPER         0x6
38 #define AUDxVOL         0x8
39
40 static struct audio_channel {
41         int             dma_on;         /* set if dma is running */
42         uint32_t        start;          /* sample address to start from */
43         int             length;         /* length of samples (not words) */
44         uint32_t        pointer;        /* pointer of renderer */
45         uint32_t        end;            /* end of current sample */
46         double          offset;         /* offset between two (input) samples */
47         double          step;           /* number of input samples per output sample */
48         double          volume;         /* volume of channel in the result sample */
49         float           sample;         /* current (last read) sample */
50 } channel[4] = {
51         { 0, 0xffffffff, 0, 0, 0, 0.0, 0.0, 0.0, 0.0 },
52         { 0, 0xffffffff, 0, 0, 0, 0.0, 0.0, 0.0, 0.0 },
53         { 0, 0xffffffff, 0, 0, 0, 0.0, 0.0, 0.0, 0.0 },
54         { 0, 0xffffffff, 0, 0, 0, 0.0, 0.0, 0.0, 0.0 },
55 };
56
57 double filter_alpha = 0.0, filter_last = 0.0;
58
59 static int start_dma(int c, struct audio_channel *chan)
60 {
61         /* fetch sample start and length and use it for rendering */
62         chan->pointer = chan->start;
63         chan->end = chan->start + chan->length;
64         /* check ranges */
65         if (chan->length == 0) {
66                 print_error("Sample length of channel %d is 0, please fix!\n", c);
67                 chan->dma_on = 0;
68                 return -1;
69         }
70         if (chan->pointer >= 0x80000) {
71                 print_error("Sample pointer 0x%08x of channel %d is outside memory, please fix!\n", chan->pointer, c);
72                 chan->dma_on = 0;
73                 return -1;
74         }
75         if (chan->end > 0x80000) {
76                 print_error("Sample end (pointer 0x%08x + length 0x%x) of channel %d is outside memory, please fix!\n", chan->pointer, chan->length, c);
77                 chan->dma_on = 0;
78                 return -1;
79         }
80
81         return 0;
82 }
83
84 void emulate_sound_write(uint32_t address, uint16_t value, int samplerate)
85 {
86         int c;
87
88         if (address == DMACON) {
89                 /* handle DMA flag and start DMA, if required */
90                 for (c = 0; c < 4; c++) {
91                         /* if nothing should be changed */
92                         if (!(value & (1 << c)))
93                                 continue;
94                         if (!(value & 0x8000)) {
95                                 channel[c].dma_on = 0;
96 #ifdef DEBUG_SOUND
97                                 printf("Audio channel %d: Turning DMA off\n", c);
98 #endif
99                                 continue;
100                         }
101                         if (channel[c].dma_on == 1)
102                                 continue;
103                         if (start_dma(c, &channel[c]) < 0) {
104 #ifdef DEBUG_SOUND
105                                 printf("Audio channel %d: Turning DMA on failed\n", c);
106 #endif
107                                 continue;
108                         }
109 #ifdef DEBUG_SOUND
110                         printf("Audio channel %d: Turning DMA on\n", c);
111 #endif
112                         channel[c].dma_on = 1;
113                 }
114                 return;
115         }
116
117         /* return if not register OR get channel from address */
118         if (address < AUDIOBASE || address >= AUDIOBASE + 0x03f)
119                 return;
120         c = (address - AUDIOBASE) >> 4;
121
122         switch (address & 0xf) {
123         case AUDxLCH:
124 #ifdef DEBUG_SOUND
125                 printf("Audio channel %d: Setting high word of sample start to 0x%04x\n", c, value);
126 #endif
127                 channel[c].start = (channel[c].start & 0x0000ffff) | (value << 16);
128                 break;
129         case AUDxLCL:
130 #ifdef DEBUG_SOUND
131                 printf("Audio channel %d: Setting low word of sample start to 0x%04x\n", c, value);
132 #endif
133                 channel[c].start = (channel[c].start & 0xffff0000) | (value & 0xfffe);
134                 break;
135         case AUDxLEN:
136 #ifdef DEBUG_SOUND
137                 printf("Audio channel %d: Setting sample length to %d words\n", c, value);
138 #endif
139                 channel[c].length = value << 1;
140                 break;
141         case AUDxPER:
142                 if (value == 0)
143                         value = 1;
144 #ifdef DEBUG_SOUND
145                 printf("Audio channel %d: Setting sample frequency to %.2f Hz\n", c, CLOCK / (double)value);
146 #endif
147                 channel[c].step = CLOCK / (double)value / (double)samplerate;
148                 /* sample rate never at or above output rate */
149                 if (channel[c].step > 0.999)
150                         channel[c].step = 0.999;
151                 break;
152         case AUDxVOL:
153                 if (value > 64)
154                         value = 64;
155 #ifdef DEBUG_SOUND
156                 printf("Audio channel %d: Setting volume to %d (0..64)\n", c, value);
157 #endif
158                 channel[c].volume = (double)value / 64.0 / 4.0; /* 1/4th volume per channel */
159                 break;
160         }
161 }
162
163 /* render sound from memory and add each channel to ring buffer array */
164 void render_sound(uint8_t *memory, float *sample, int buffer_size, int buffer_pos, int length, int filter)
165 {
166         int c, s, pos;
167         struct audio_channel *chan;
168         double new_offset;
169         float new_sample;
170
171         /* clear buffer first */
172         pos = buffer_pos;
173         for (s = 0; s < length; s++) {
174                 sample[pos] = 0.0;
175                 pos = (pos + 1) % buffer_size;
176         }
177
178         /* render each channel and sum the result */
179         for (c = 0; c < 4; c++) {
180                 chan = &channel[c];
181                 /* skip if DMA is disabled */
182                 if (!chan->dma_on)
183                         continue;
184                 /* Note: We must render sound, even if volume is 0.0,
185                  * because the sample pointers must continue, as it would happen in a real Amiga.
186                  * But this may have no practical use in this game.
187                  */
188                 pos = buffer_pos;
189                 for (s = 0; s < length; s++) {
190                         new_offset = chan->offset + chan->step;
191                         if (new_offset < 1.0)
192                                 /* no interpolation: use last input sample, because there is no change */
193                                 sample[pos] += chan->sample * chan->volume;
194                         else {
195                                 /* there is a change of input sample, so read new sample */
196                                 new_offset -= 1.0;
197                                 new_sample = (double)(((int8_t *)memory)[chan->pointer++]) / 128.0;
198                                 if (chan->pointer == chan->end) {
199                                         if (start_dma(c, chan) < 0)
200                                                 break;
201                                 }
202                                 /* linear interpolation: add portion of last sample to portion of new sample */
203                                 sample[pos] += (chan->sample * (1.0 - chan->offset) + new_sample * new_offset) / chan->step * chan->volume;
204                                 chan->sample = new_sample;
205                         }
206                         chan->offset = new_offset;
207                         pos = (pos + 1) % buffer_size;
208                 }
209         }
210
211         if (filter) {
212                 /* do low-pass filtering */
213                 pos = buffer_pos;
214                 for (s = 0; s < length; s++) {
215                         /* y[i] := y[i-1] + alpha * (x[i] - y[i-1]) */
216                         filter_last = sample[pos] = filter_last + filter_alpha * (sample[pos] - filter_last);
217                         pos = (pos + 1) % buffer_size;
218                 }
219         }
220 }
221
222 void sound_init_filter(int samplerate)
223 {
224         double dt = 1.0 / (double)samplerate;
225         double C = 0.0000001;
226         double R = 360;
227         double alpha = dt / (R*C + dt);
228 #ifdef DEBUG_SOUND
229         double cutoff = 1.0 / (2 * M_PI * R * C);
230 #endif
231
232 #ifdef DEBUG_SOUND
233         printf("filter-cutoff: %.0f Hz (alpha = %.4f)\n", cutoff, alpha);
234 #endif
235         filter_alpha = alpha;
236 }
237