-
Notifications
You must be signed in to change notification settings - Fork 3
/
flsynth.c
275 lines (220 loc) · 8.04 KB
/
flsynth.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
#include <alloca.h>// To make 'clock_gettime' and 'usleep' work
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#include "flsynth.h"
#include "fluidlite.h"
#ifndef __ANDROID__
#include <SDL2/SDL.h>
#else
#include <SLES/OpenSLES.h>
#include "opensl_stream/opensl_stream.h"
#include <pthread.h>
#include <semaphore.h>
#include <errno.h>
#include <android/log.h>
// Redirect printf functions to android log
#define printf(...) __android_log_print(ANDROID_LOG_INFO, "flsynth-printf", __VA_ARGS__)
// We need "_init" on android to make UPX work, but on other platforms it cause "multiple definitio" error
#define init _init
#define fini _fini
#endif
void init() __attribute__((constructor));
void fini() __attribute__((destructor));
static inline void synthesize(synth_t *synth) {
// stereo
if (synth->channels == 2) {
fluid_synth_write_s16(synth->fluid_synth, FRAME_PER_CYCLE, synth->buff, 0, 2, synth->buff, 1, 2);
}
// mono
else {
fluid_synth_write_s16(synth->fluid_synth, FRAME_PER_CYCLE, synth->buff, 0, 1,
synth->buff + FRAME_PER_CYCLE * 2, 0, 1);
// Average the 2 channels
int16_t *s16buff = synth->buff;
for (int i = 0; i < FRAME_PER_CYCLE; i++) {
s16buff[i] = (s16buff[i] >> 1) + (s16buff[i + FRAME_PER_CYCLE] >> 1);
}
}
}
#ifndef __ANDROID__
static int synthesize_thread(synth_t *synth) {
SDL_SetThreadPriority(SDL_THREAD_PRIORITY_HIGH);
int sem_res = 0;
SDL_sem *synth_sem = synth->synth_sem;
SDL_sem *cb_sem = synth->cb_sem;
while (true) {
sem_res = SDL_SemWaitTimeout(synth_sem, 50);
// Stop thread on timeout or if synth is stopped
if (!synth->playing || sem_res == SDL_MUTEX_TIMEDOUT) break;
synthesize(synth);
// Allow callback to copy buffer
SDL_SemPost(cb_sem);
}
return 0;
}
static void audio_callback(synth_t *synth, unsigned char *stream, size_t stream_length) {
SDL_SemWaitTimeout(synth->cb_sem, 15);
// Just copy precalculated buffer
memcpy(stream, synth->buff, stream_length);
// Start next calculation
SDL_SemPost(synth->synth_sem);
}
#else // __ANDROID__
static void *synthesize_thread(synth_t *synth) {
sem_t *synth_sem = synth->synth_sem;
sem_t *cb_sem = synth->cb_sem;
while (true) {
int sem_res = sem_wait(synth_sem);
if (!synth->playing || (sem_res != 0 && errno == ETIMEDOUT)) break;
synthesize(synth);
// Allow callback to copy buffer
sem_post(cb_sem);
}
// We need to destroy semaphores here
return NULL;
}
static void audio_callback(synth_t *synth, int __unused sample_rate, int buffer_frames,
int __unused input_channels, const short __unused *input_buffer,
int output_channels, short *output_buffer) {
// Wait for buffer ready
sem_wait(synth->cb_sem);
// Just copy precalculated buffer
memcpy(output_buffer, synth->buff, (size_t) (buffer_frames * output_channels * 2));
// Start next calculation
sem_post(synth->synth_sem);
}
#endif
synth_t *flsynth_create(unsigned int sample_rate, unsigned char channels) {
synth_t *synth = calloc(sizeof(synth_t), 1);
synth->settings = new_fluid_settings();
synth->fluid_synth = new_fluid_synth(synth->settings);
synth->buff = malloc((size_t) (FRAME_PER_CYCLE * 4)); // 4 -> 16 bit * 2 channel (stereo)
fluid_synth_set_sample_rate(synth->fluid_synth, sample_rate);
synth->sample_rate = sample_rate;
synth->channels = channels;
#ifndef __ANDROID__
SDL_InitSubSystem(SDL_INIT_AUDIO);
#else
synth->synth_thread = calloc(sizeof(pthread_t), 1);
synth->synth_sem = calloc(sizeof(sem_t), 1);
synth->cb_sem = calloc(sizeof(sem_t), 1);
#endif
return synth;
}
void flsynth_free(synth_t *synth) {
delete_fluid_synth(synth->fluid_synth);
delete_fluid_settings(synth->settings);
free(synth->buff);
#ifndef __ANDROID__
SDL_QuitSubSystem(SDL_INIT_AUDIO);
#else
free(synth->synth_thread);
free(synth->synth_sem);
free(synth->cb_sem);
#endif
free(synth);
}
int flsynth_sfload(synth_t *synth, const char *filename, bool program_reset) {
int res = fluid_synth_sfload(synth->fluid_synth, filename, program_reset);
if (res > 0) synth->last_sfid = (unsigned int) res;
return synth->last_sfid;
}
bool flsynth_start(synth_t *synth) {
if (synth->playing) return false;
synth->playing = true;
// Initial buffer calculation
synthesize(synth);
#ifndef __ANDROID__
// Create semaphores for thread synchronization
synth->synth_sem = SDL_CreateSemaphore(0);
synth->cb_sem = SDL_CreateSemaphore(0);
// Create audio specification
SDL_AudioSpec audioSpec;
audioSpec.freq = synth->sample_rate;
audioSpec.format = AUDIO_S16LSB;
audioSpec.channels = synth->channels;
audioSpec.samples = FRAME_PER_CYCLE;
audioSpec.size = 0;
audioSpec.callback = (SDL_AudioCallback) audio_callback;
audioSpec.userdata = synth;
// Open audio device
synth->audio_device = 1; SDL_OpenAudio(&audioSpec, NULL);
// synth->audio_device = SDL_OpenAudioDevice(NULL, 0, &audioSpec, NULL, SDL_AUDIO_ALLOW_FORMAT_CHANGE);
// Start playing
SDL_PauseAudioDevice(synth->audio_device, 0);
// Start background calculation
synth->synth_thread = SDL_CreateThread((SDL_ThreadFunction) synthesize_thread, "flsynth_processing_thread", synth);
#else // __ANDROID__
// Create a semaphore for thread synchronization
sem_init(synth->synth_sem, 0, 0);
sem_init(synth->cb_sem, 0, 1);
// Start background calculation
pthread_create(synth->synth_thread, NULL, (void *(*)(void *)) synthesize_thread, synth);
// Initialize audio device and start playback
synth->audio_device = opensl_open(synth->sample_rate, 0, synth->channels,
FRAME_PER_CYCLE, (opensl_process_t) audio_callback, synth);
opensl_start(synth->audio_device);
#endif
return true;
}
bool flsynth_stop(synth_t *synth) {
if (!synth->playing) return false;
synth->playing = false;
#ifndef __ANDROID__
SDL_SemPost(synth->synth_sem);
SDL_WaitThread(synth->synth_thread, NULL);
memset(synth->buff, 0, FRAME_PER_CYCLE * 4); // Silence buffer
SDL_SemPost(synth->cb_sem);
SDL_CloseAudioDevice(synth->audio_device);
SDL_DestroySemaphore(synth->synth_sem);
SDL_DestroySemaphore(synth->cb_sem);
#else // __ANDROID__
sem_post(synth->synth_sem);
pthread_join(*((pthread_t *)synth->synth_thread), NULL);
memset(synth->buff, 0, FRAME_PER_CYCLE * 4); // Silence buffer
sem_post(synth->cb_sem);
opensl_close(synth->audio_device);
sem_destroy(synth->synth_sem);
sem_destroy(synth->cb_sem);
#endif
return true;
}
bool flsynth_program_select_sfid(synth_t *synth, int chan, unsigned int sfid, unsigned int bank, unsigned int preset) {
return fluid_synth_program_select(synth->fluid_synth, chan, sfid, bank, preset) == 0;
}
bool flsynth_program_select(synth_t *synth, int chan, unsigned int bank, unsigned int preset) {
return flsynth_program_select_sfid(synth, chan, synth->last_sfid, bank, preset);
}
bool flsynth_noteon(synth_t *synth, int chan, int key, int velocity) {
return fluid_synth_noteon(synth->fluid_synth, chan, key, velocity) == 0;
}
bool flsynth_noteoff(synth_t *synth, int chan, int key) {
return fluid_synth_noteoff(synth->fluid_synth, chan, key) == 0;
}
bool flsynth_system_reset(synth_t *synth) {
return fluid_synth_system_reset(synth->fluid_synth) == 0;
}
bool flsynth_cc(synth_t *synth, int chan, int ctrl, int val) {
return fluid_synth_cc(synth->fluid_synth, chan, ctrl, val) == 0;
}
/*
* Setup
*/
static void _dummy_log() {}
void init() {
// Initialize SDL
#ifndef __ANDROID__
SDL_Init(0);
#endif
// Remove unnecessary log messages and warnings
fluid_set_log_function(FLUID_DBG, _dummy_log, NULL);
fluid_set_log_function(FLUID_INFO, _dummy_log, NULL);
fluid_set_log_function(FLUID_WARN, _dummy_log, NULL);
}
void fini() {
#ifndef __ANDROID__
SDL_Quit();
#endif
}