fork of PCE focusing on macplus, supporting DaynaPort SCSI network emulation
1/*****************************************************************************
2 * pce *
3 *****************************************************************************/
4
5/*****************************************************************************
6 * File name: src/devices/speaker.c *
7 * Created: 2022-02-08 by Hampa Hug <hampa@hampa.ch> *
8 * Copyright: (C) 2021-2024 Hampa Hug <hampa@hampa.ch> *
9 *****************************************************************************/
10
11/*****************************************************************************
12 * This program is free software. You can redistribute it and / or modify it *
13 * under the terms of the GNU General Public License version 2 as published *
14 * by the Free Software Foundation. *
15 * *
16 * This program is distributed in the hope that it will be useful, but *
17 * WITHOUT ANY WARRANTY, without even the implied warranty of *
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General *
19 * Public License for more details. *
20 *****************************************************************************/
21
22
23#include <config.h>
24
25#include <devices/speaker.h>
26
27#include <stdlib.h>
28
29#include <drivers/sound/sound.h>
30
31
32#ifndef DEBUG_SPEAKER
33#define DEBUG_SPEAKER 0
34#endif
35
36
37static void spk_flush (speaker_t *spk);
38
39
40void spk_init (speaker_t *spk, int level)
41{
42 spk->drv = NULL;
43
44 spk->srate = 44100;
45 spk->input_clock = 1000000;
46 spk->speed_mul = 1;
47
48 spk->enabled = 0;
49
50 spk->speaker_inp = level;
51 spk->speaker_val = 0;
52 spk->sample_acc = 0;
53
54 spk->timeout_val = 0;
55 spk->timeout_clk = 0;
56 spk->timeout_max = 5 * spk->input_clock;
57
58 spk->clk = 0;
59 spk->rem = 0;
60
61 spk->lowpass_freq = 0;
62
63 snd_iir2_init (&spk->iir);
64
65 spk->buf_cnt = 0;
66
67 spk_set_volume (spk, 500);
68}
69
70void spk_free (speaker_t *spk)
71{
72 spk_flush (spk);
73
74 if (spk->drv != NULL) {
75 snd_close (spk->drv);
76 }
77}
78
79speaker_t *spk_new (int level)
80{
81 speaker_t *spk;
82
83 if ((spk = malloc (sizeof (speaker_t))) == NULL) {
84 return (NULL);
85 }
86
87 spk_init (spk, level);
88
89 return (spk);
90}
91
92void spk_del (speaker_t *spk)
93{
94 if (spk != NULL) {
95 spk_free (spk);
96 free (spk);
97 }
98}
99
100void spk_set_clock_fct (speaker_t *spk, void *ext, void *fct)
101{
102 spk->get_clock_ext = ext;
103 spk->get_clock = fct;
104}
105
106void spk_set_input_clock (speaker_t *spk, unsigned long clk)
107{
108 spk->input_clock = clk;
109 spk->timeout_max = 5 * clk;
110}
111
112void spk_set_speed (speaker_t *spk, unsigned mul)
113{
114 if (mul > 0) {
115 spk->speed_mul = mul;
116 }
117}
118
119int spk_set_driver (speaker_t *spk, const char *driver, unsigned long srate)
120{
121 if (spk->drv != NULL) {
122 snd_close (spk->drv);
123 }
124
125 if ((spk->drv = snd_open (driver)) == NULL) {
126 return (1);
127 }
128
129 spk->srate = srate;
130
131 snd_iir2_set_lowpass (&spk->iir, spk->lowpass_freq, spk->srate);
132
133 if (snd_set_params (spk->drv, 1, srate, 1)) {
134 snd_close (spk->drv);
135 spk->drv = NULL;
136 return (1);
137 }
138
139 return (0);
140}
141
142void spk_set_lowpass (speaker_t *spk, unsigned long freq)
143{
144 spk->lowpass_freq = freq;
145
146 snd_iir2_set_lowpass (&spk->iir, spk->lowpass_freq, spk->srate);
147}
148
149void spk_set_volume (speaker_t *spk, unsigned vol)
150{
151 if (vol > 1000) {
152 vol = 1000;
153 }
154
155 vol = (32768UL * vol) / 1000;
156
157 if (vol > 32767) {
158 vol = 32767;
159 }
160
161 spk->val_p = 0x8000 + vol;
162 spk->val_n = 0x8000 - vol;
163}
164
165void spk_set_input (speaker_t *spk, int val)
166{
167 spk_check (spk);
168
169 val = (val != 0);
170
171 if (spk->speaker_inp == val) {
172 return;
173 }
174
175 spk->speaker_inp = val;
176 spk->speaker_val = val ? spk->val_p : spk->val_n;
177
178#if DEBUG_SPEAKER >= 1
179 fprintf (stderr, "speaker input %d (%04X)\n",
180 spk->speaker_inp, spk->speaker_val
181 );
182#endif
183}
184
185static
186void spk_write (speaker_t *spk, uint16_t *buf, unsigned cnt)
187{
188 if (spk->lowpass_freq > 0) {
189 snd_iir2_filter (&spk->iir, buf, buf, cnt, 1, 1);
190 }
191
192 snd_write (spk->drv, buf, cnt);
193}
194
195static
196void spk_put_sample (speaker_t *spk, uint16_t val, unsigned long cnt)
197{
198 unsigned idx;
199
200 if (spk->drv == NULL) {
201 return;
202 }
203
204 idx = spk->buf_cnt;
205
206 while (cnt > 0) {
207 spk->buf[idx++] = val;
208
209 if (idx >= SPEAKER_BUF) {
210 spk_write (spk, spk->buf, idx);
211
212 idx = 0;
213 }
214
215 cnt -= 1;
216 }
217
218 spk->buf_cnt = idx;
219}
220
221static
222void spk_flush (speaker_t *spk)
223{
224 if (spk->buf_cnt == 0) {
225 return;
226 }
227
228 spk_write (spk, spk->buf, spk->buf_cnt);
229
230 snd_iir2_reset (&spk->iir);
231
232 spk->buf_cnt = 0;
233}
234
235static
236void spk_on (speaker_t *spk)
237{
238#if DEBUG_SPEAKER >= 1
239 fprintf (stderr, "speaker on (%lu)\n", spk->input_clock);
240#endif
241
242 spk->enabled = 1;
243 spk->sample_acc = 0;
244 spk->timeout_val = spk->speaker_val;
245 spk->timeout_clk = 0;
246 spk->rem = 0;
247
248 /* Fill the sound buffer a bit so we don't underrun immediately */
249 spk_put_sample (spk, 0, spk->srate / 16);
250}
251
252static
253void spk_off (speaker_t *spk)
254{
255#if DEBUG_SPEAKER >= 1
256 fprintf (stderr, "speaker off\n");
257#endif
258
259 spk->enabled = 0;
260
261 spk_flush (spk);
262}
263
264void spk_check (speaker_t *spk)
265{
266 unsigned long clk, tmp, acc;
267
268 tmp = spk->get_clock (spk->get_clock_ext);
269 clk = tmp - spk->clk;
270 spk->clk = tmp;
271
272 if (spk->enabled == 0) {
273 if (spk->timeout_val != spk->speaker_val) {
274 spk_on (spk);
275 }
276 else {
277 return;
278 }
279 }
280
281 if (spk->timeout_val == spk->speaker_val) {
282 spk->timeout_clk += clk;
283
284 if (spk->timeout_clk > spk->timeout_max) {
285 spk_off (spk);
286 return;
287 }
288 }
289 else {
290 spk->timeout_val = spk->speaker_val;
291 spk->timeout_clk = clk;
292 }
293
294 acc = spk->sample_acc;
295
296 while (clk >= spk->speed_mul) {
297 acc = ((256 - 6) * acc + 6 * spk->speaker_val) / 256;
298
299 spk->rem += spk->srate;
300
301 if (spk->rem >= spk->input_clock) {
302 spk_put_sample (spk, acc ^ 0x8000, 1);
303 spk->rem -= spk->input_clock;
304
305 if (spk->speaker_val < 0x8000) {
306 spk->speaker_val = (255UL * spk->speaker_val + 0x8000 + 255) / 256;
307 }
308 else {
309 spk->speaker_val = (255UL * spk->speaker_val + 0x8000) / 256;
310 }
311 }
312
313 clk -= spk->speed_mul;
314 }
315
316 spk->clk -= clk;
317
318 spk->sample_acc = acc;
319}