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