1 | // This file is part of SmallBASIC |
2 | // |
3 | // Copyright(C) 2001-2020 Chris Warren-Smith. |
4 | // |
5 | // This program is distributed under the terms of the GPL v2.0 or later |
6 | // Download the GNU Public License (GPL) from www.gnu.org |
7 | // |
8 | |
9 | #define MINIAUDIO_IMPLEMENTATION |
10 | #define DR_WAV_IMPLEMENTATION |
11 | |
12 | #include "config.h" |
13 | #include <stdio.h> |
14 | #include <stdint.h> |
15 | #include <unistd.h> |
16 | #include "include/osd.h" |
17 | #include "ui/strlib.h" |
18 | #include "ui/audio.h" |
19 | #include "lib/miniaudio/extras/dr_wav.h" |
20 | #include "lib/miniaudio/miniaudio.h" |
21 | |
22 | extern "C" { |
23 | uint32_t dev_get_millisecond_count(); |
24 | void err_throw(const char *fmt, ...); |
25 | } |
26 | |
27 | #define DEFAULT_FORMAT ma_format_f32 |
28 | #define DEFAULT_SAMPLE_RATE 44100 |
29 | #define DEFAULT_CHANNELS 1 |
30 | #define MILLIS_TO_MICROS(n) (n * 1000) |
31 | |
32 | struct Sound; |
33 | static ma_context context; |
34 | static ma_device device; |
35 | static ma_device_config config; |
36 | static strlib::Queue<Sound *> queue; |
37 | static int queuePos = 0; |
38 | |
39 | struct Sound { |
40 | Sound(int frequency, int millis, int volume); |
41 | Sound(); |
42 | virtual ~Sound(); |
43 | |
44 | ma_result construct(const char *path); |
45 | |
46 | ma_waveform *_tone; |
47 | ma_decoder *_decoder; |
48 | uint32_t _start; |
49 | uint32_t _duration; |
50 | }; |
51 | |
52 | Sound::Sound() : |
53 | _tone(nullptr), |
54 | _decoder(nullptr), |
55 | _start(0), |
56 | _duration(0) { |
57 | } |
58 | |
59 | Sound::Sound(int frequency, int millis, int volume) : |
60 | _tone(nullptr), |
61 | _decoder(nullptr), |
62 | _start(0), |
63 | _duration(millis) { |
64 | _tone = (ma_waveform *)malloc(sizeof(ma_waveform)); |
65 | ma_waveform_config config = |
66 | ma_waveform_config_init(DEFAULT_FORMAT, DEFAULT_CHANNELS, DEFAULT_SAMPLE_RATE, |
67 | ma_waveform_type_sine, volume / 100.0, frequency); |
68 | ma_waveform_init(&config, _tone); |
69 | } |
70 | |
71 | Sound::~Sound() { |
72 | if (_decoder) { |
73 | ma_decoder_uninit(_decoder); |
74 | free(_decoder); |
75 | _decoder = nullptr; |
76 | } |
77 | free(_tone); |
78 | _tone = nullptr; |
79 | } |
80 | |
81 | ma_result Sound::construct(const char *path) { |
82 | _decoder = (ma_decoder *)malloc(sizeof(ma_decoder)); |
83 | return _decoder == nullptr ? MA_OUT_OF_MEMORY : ma_decoder_init_file(path, nullptr, _decoder); |
84 | } |
85 | |
86 | static void data_callback(ma_device *device, void *output, const void *input, ma_uint32 frameCount) { |
87 | if (queue.empty()) { |
88 | usleep(MILLIS_TO_MICROS(10)); |
89 | } else { |
90 | queuePos = (queuePos + 1) % queue.size(); |
91 | Sound *sound = queue[queuePos]; |
92 | if (sound->_decoder != nullptr) { |
93 | // play audio track |
94 | ma_uint64 framesRead = ma_decoder_read_pcm_frames(sound->_decoder, output, frameCount); |
95 | if (framesRead == 0) { |
96 | // finished playing |
97 | queue.pop(false); |
98 | } |
99 | } else if (sound->_start == 0) { |
100 | // start new sound |
101 | sound->_start = dev_get_millisecond_count(); |
102 | ma_waveform_read_pcm_frames(sound->_tone, (float *)output, frameCount); |
103 | } else if (dev_get_millisecond_count() - sound->_start > sound->_duration) { |
104 | // sound has timed out |
105 | queue.pop(false); |
106 | } else { |
107 | // continue sound |
108 | ma_waveform_read_pcm_frames(sound->_tone, (float *)output, frameCount); |
109 | } |
110 | } |
111 | } |
112 | |
113 | static void setup_config(ma_format format, ma_uint32 channels, ma_uint32 sampleRate) { |
114 | config = ma_device_config_init(ma_device_type_playback); |
115 | config.playback.format = format; |
116 | config.playback.channels = channels; |
117 | config.sampleRate = sampleRate; |
118 | config.dataCallback = data_callback; |
119 | config.pUserData = nullptr; |
120 | } |
121 | |
122 | static void setup_format(ma_format format, ma_uint32 channels, ma_uint32 sampleRate) { |
123 | if (config.playback.format != format || |
124 | config.playback.channels != channels || |
125 | config.sampleRate != sampleRate) { |
126 | audio_close(); |
127 | setup_config(format, channels, sampleRate); |
128 | ma_result result = ma_device_init(&context, &config, &device); |
129 | if (result != MA_SUCCESS) { |
130 | err_throw("Failed to prepare sound device [%d]" , result); |
131 | } |
132 | } |
133 | } |
134 | |
135 | static void device_start() { |
136 | if (ma_device_get_state(&device) != MA_STATE_STARTED) { |
137 | ma_result result = ma_device_start(&device); |
138 | if (result != MA_SUCCESS) { |
139 | err_throw("Failed to start audio [%d]" , result); |
140 | } |
141 | } |
142 | } |
143 | |
144 | bool audio_open() { |
145 | bool result; |
146 | ma_backend backends[] = { |
147 | ma_backend_alsa, |
148 | ma_backend_jack, |
149 | ma_backend_pulseaudio, |
150 | ma_backend_wasapi, |
151 | ma_backend_dsound |
152 | }; |
153 | if (ma_context_init(backends, sizeof(backends)/sizeof(backends[0]), NULL, &context) != MA_SUCCESS) { |
154 | result = false; |
155 | } else { |
156 | queuePos = 0; |
157 | setup_config(DEFAULT_FORMAT, DEFAULT_CHANNELS, DEFAULT_SAMPLE_RATE); |
158 | result = (ma_device_init(&context, &config, &device) == MA_SUCCESS); |
159 | } |
160 | return result; |
161 | } |
162 | |
163 | void audio_close() { |
164 | osd_clear_sound_queue(); |
165 | ma_device_uninit(&device); |
166 | ma_context_uninit(&context); |
167 | } |
168 | |
169 | void osd_audio(const char *path) { |
170 | Sound *sound = new Sound(); |
171 | ma_result result = sound->construct(path); |
172 | if (result != MA_SUCCESS) { |
173 | delete sound; |
174 | err_throw("Failed to open sound file [%d]" , result); |
175 | } else { |
176 | setup_format(sound->_decoder->outputFormat, |
177 | sound->_decoder->outputChannels, |
178 | sound->_decoder->outputSampleRate); |
179 | ma_mutex_lock(&device.startStopLock); |
180 | queue.push(sound); |
181 | ma_mutex_unlock(&device.startStopLock); |
182 | device_start(); |
183 | } |
184 | } |
185 | |
186 | void osd_beep() { |
187 | osd_sound(1000, 30, 100, 0); |
188 | osd_sound(500, 30, 100, 0); |
189 | } |
190 | |
191 | void osd_clear_sound_queue() { |
192 | ma_device_stop(&device); |
193 | |
194 | // remove any cached sounds from the queue |
195 | List_each(Sound *, it, queue) { |
196 | Sound *next = *it; |
197 | if (next != NULL && next->_decoder != nullptr) { |
198 | queue.remove(it); |
199 | it--; |
200 | } |
201 | } |
202 | |
203 | queue.removeAll(); |
204 | queuePos = 0; |
205 | } |
206 | |
207 | void osd_sound(int frequency, int millis, int volume, int background) { |
208 | setup_format(DEFAULT_FORMAT, DEFAULT_CHANNELS, DEFAULT_SAMPLE_RATE); |
209 | ma_mutex_lock(&device.startStopLock); |
210 | queue.push(new Sound(frequency, millis, volume)); |
211 | ma_mutex_unlock(&device.startStopLock); |
212 | |
213 | if (!background) { |
214 | device_start(); |
215 | usleep(MILLIS_TO_MICROS(millis)); |
216 | ma_device_stop(&device); |
217 | ma_mutex_lock(&device.startStopLock); |
218 | queue.pop(); |
219 | ma_mutex_unlock(&device.startStopLock); |
220 | } else if (queue.size() == 1) { |
221 | device_start(); |
222 | } |
223 | } |
224 | |