A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita
audio
rust
zig
deno
mpris
rockbox
mpd
1/***************************************************************************
2 * __________ __ ___.
3 * Open \______ \ ____ ____ | | _\_ |__ _______ ___
4 * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
5 * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
6 * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
7 * \/ \/ \/ \/ \/
8 * $Id$
9 *
10 * Copyright (C) 2005 Stepan Moskovchenko
11 *
12 * This program is free software; you can redistribute it and/or
13 * modify it under the terms of the GNU General Public License
14 * as published by the Free Software Foundation; either version 2
15 * of the License, or (at your option) any later version.
16 *
17 * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
18 * KIND, either express or implied.
19 *
20 ****************************************************************************/
21#include "plugin.h"
22#include "guspat.h"
23#include "midiutil.h"
24#include "synth.h"
25
26static void readTextBlock(int file, char * buf)
27{
28 char c = 0;
29 do
30 {
31 c = readChar(file);
32 } while(c == '\n' || c == ' ' || c=='\t');
33
34 rb->lseek(file, -1, SEEK_CUR);
35 int cp = 0;
36 do
37 {
38 c = readChar(file);
39 buf[cp] = c;
40 cp++;
41 } while (c != '\n' && c != ' ' && c != '\t' && !eof(file));
42 buf[cp-1]=0;
43 rb->lseek(file, -1, SEEK_CUR);
44}
45
46void resetControllers()
47{
48 int a=0;
49 for(a=0; a<MAX_VOICES; a++)
50 {
51 voices[a].cp=0;
52 voices[a].vol=0;
53 voices[a].ch=0;
54 voices[a].isUsed=false;
55 voices[a].note=0;
56 }
57
58 for(a=0; a<16; a++)
59 {
60 chVol[a]=100; /* Default, not quite full blast.. */
61 chPan[a]=64; /* Center */
62 chPat[a]=0; /* Ac Gr Piano */
63 chPW[a]=256; /* .. not .. bent ? */
64 chPBDepth[a]=2; /* Default bend value is 2 */
65 chPBNoteOffset[a]=0; /* No offset */
66 chPBFractBend[a]=65536; /* Center.. no bend */
67 chLastCtrlMSB[a]=0; /* Set to pitch bend depth */
68 chLastCtrlLSB[a]=0; /* Set to pitch bend depth */
69 }
70}
71
72/* Filename is the name of the config file */
73/* The MIDI file should have been loaded at this point */
74int initSynth(struct MIDIfile * mf, char * filename, char * drumConfig)
75{
76 char patchUsed[128];
77 char drumUsed[128];
78 int a=0;
79
80 resetControllers();
81
82 for(a=0; a<128; a++)
83 {
84 patchSet[a]=NULL;
85 drumSet[a]=NULL;
86 patchUsed[a]=0;
87 drumUsed[a]=0;
88 }
89
90 /*
91 * Always load the piano.
92 * Some files will assume its loaded without specifically
93 * issuing a Patch command... then we wonder why we can't hear anything
94 */
95 patchUsed[0]=1;
96
97 /* Scan the file to see what needs to be loaded */
98 if(mf != NULL)
99 {
100 for(a=0; a<mf->numTracks; a++)
101 {
102 unsigned int ts=0;
103
104 if(mf->tracks[a] == NULL)
105 {
106 midi_debug("NULL TRACK !!!");
107 rb->splash(HZ*2, "Null Track in loader.");
108 return -1;
109 }
110
111 for(ts=0; ts<mf->tracks[a]->numEvents; ts++)
112 {
113
114 if((getEvent(mf->tracks[a], ts)->status) == (MIDI_NOTE_ON+9))
115 drumUsed[getEvent(mf->tracks[a], ts)->d1]=1;
116
117 if( (getEvent(mf->tracks[a], ts)->status & 0xF0) == MIDI_PRGM)
118 patchUsed[getEvent(mf->tracks[a], ts)->d1]=1;
119 }
120 }
121 } else
122 {
123 /* Initialize the whole drum set */
124 for(a=0; a<128; a++)
125 drumUsed[a]=1;
126
127 }
128
129 int file = rb->open(filename, O_RDONLY);
130 if(file < 0)
131 {
132 midi_debug("");
133 midi_debug("No MIDI patchset found.");
134 midi_debug("Please install the instruments.");
135 midi_debug("See Rockbox page for more info.");
136
137 rb->splash(HZ*2, "No Instruments");
138 return -1;
139 }
140
141 char name[40];
142 char fn[40];
143
144 /* Scan our config file and load the right patches as needed */
145 int c = 0;
146 name[0] = '\0';
147 midi_debug("Loading instruments");
148 for(a=0; a<128; a++)
149 {
150 while(readChar(file)!=' ' && !eof(file));
151 readTextBlock(file, name);
152
153 rb->snprintf(fn, 40, ROCKBOX_DIR "/patchset/%s.pat", name);
154/* midi_debug("\nLOADING: <%s> ", fn); */
155
156 if(patchUsed[a]==1)
157 {
158 patchSet[a]=gusload(fn);
159
160 if(patchSet[a] == NULL) /* There was an error loading it */
161 return -1;
162 }
163
164 while((c != '\n'))
165 c = readChar(file);
166 }
167 rb->close(file);
168
169 file = rb->open(drumConfig, O_RDONLY);
170 if(file < 0)
171 {
172 rb->splash(HZ*2, "Bad drum config. Did you install the patchset?");
173 return -1;
174 }
175
176 /* Scan our config file and load the drum data */
177 int idx=0;
178 char number[30];
179 midi_debug("Loading drums");
180 while(!eof(file))
181 {
182 readTextBlock(file, number);
183 readTextBlock(file, name);
184 rb->snprintf(fn, 40, ROCKBOX_DIR "/patchset/%s.pat", name);
185
186 idx = rb->atoi(number);
187 if(idx == 0)
188 break;
189
190 if(drumUsed[idx]==1)
191 {
192 drumSet[idx]=gusload(fn);
193 if(drumSet[idx] == NULL) /* Error loading patch */
194 return -1;
195 }
196
197 while((c != '\n') && (c != 255) && (!eof(file)))
198 c = readChar(file);
199 }
200 rb->close(file);
201 return 0;
202}
203
204void setPoint(struct SynthObject * so, int pt) ICODE_ATTR;
205void setPoint(struct SynthObject * so, int pt)
206{
207 if(so->ch==9) /* Drums, no ADSR */
208 {
209 so->curOffset = 1<<27;
210 so->curRate = 1;
211 return;
212 }
213
214 if(so->wf==NULL)
215 {
216 midi_debug("Crap... null waveform...");
217 exit(1);
218 }
219 if(so->wf->envRate==NULL)
220 {
221 midi_debug("Waveform has no envelope set");
222 exit(1);
223 }
224
225 so->curPoint = pt;
226
227 int r;
228 int rate = so->wf->envRate[pt];
229
230 r=3-((rate>>6) & 0x3); /* Some blatant Timidity code for rate conversion... */
231 r*=3;
232 r = (rate & 0x3f) << r;
233
234 /*
235 * Okay. This is the rate shift. Timidity defaults to 9, and sets
236 * it to 10 if you use the fast decay option. Slow decay sounds better
237 * on some files, except on some other files... you get chords that aren't
238 * done decaying yet.. and they dont harmonize with the next chord and it
239 * sounds like utter crap. Yes, even Timitidy does that. So I'm going to
240 * default this to 10, and maybe later have an option to set it to 9
241 * for longer decays.
242 */
243 so->curRate = r<<10;
244
245 /*
246 * Do this here because the patches assume a 44100 sampling rate
247 * We've halved our sampling rate, ergo the ADSR code will be
248 * called half the time. Ergo, double the rate to keep stuff
249 * sounding right.
250 *
251 * Or just move the 1 up one line to optimize a tiny bit.
252 */
253 /* so->curRate = so->curRate << 1; */
254
255
256 so->targetOffset = so->wf->envOffset[pt]<<(20);
257 if(pt==0)
258 so->curOffset = 0;
259}
260
261static inline void synthVoice(struct SynthObject * so, int32_t * out, unsigned int samples)
262{
263 struct GWaveform * wf;
264 register int s1;
265 register int s2;
266
267 register unsigned int cp_temp = so->cp;
268
269 wf = so->wf;
270 const int16_t *sample_data = wf->data;
271
272 const unsigned int pan = chPan[so->ch];
273 const int volscale = so->volscale;
274
275 const int mode_mask24 = wf->mode&24;
276 const int mode_mask28 = wf->mode&28;
277 const int mode_mask_looprev = wf->mode&LOOP_REVERSE;
278
279 const unsigned int num_samples = (wf->numSamples-1) << FRACTSIZE;
280
281 const unsigned int end_loop = wf->endLoop << FRACTSIZE;
282 const unsigned int start_loop = wf->startLoop << FRACTSIZE;
283 const int diff_loop = end_loop-start_loop;
284
285 bool rampdown = (so->state == STATE_RAMPDOWN);
286 const bool ch_9 = (so->ch == 9);
287
288 while(LIKELY(samples-- > 0))
289 {
290 /* Is voice being ramped? */
291 if(UNLIKELY(rampdown))
292 {
293 if(so->decay != 0) /* Ramp has been started */
294 {
295 so->decay = so->decay / 2;
296
297 if(so->decay < 10 && so->decay > -10)
298 so->isUsed = false;
299
300 s1 = so->decay;
301 s2 = s1 * pan;
302 s1 = (s1 << 7) -s2;
303 *(out++) += s1;
304 *(out++) += s2;
305 continue;
306 }
307 } else /* OK to advance voice */
308 {
309 cp_temp += so->delta;
310 }
311
312 s2 = sample_data[(cp_temp >> FRACTSIZE)+1];
313
314 if(LIKELY(mode_mask28))
315 {
316 /* LOOP_REVERSE|LOOP_PINGPONG = 24 */
317 if(UNLIKELY(mode_mask24 && so->loopState == STATE_LOOPING && (cp_temp < start_loop)))
318 {
319 if(UNLIKELY(mode_mask_looprev))
320 {
321 cp_temp += diff_loop;
322 s2 = sample_data[cp_temp >> FRACTSIZE];
323 }
324 else
325 {
326 so->delta = -so->delta; /* At this point cp_temp is wrong. We need to take a step */
327 }
328 }
329
330 if(UNLIKELY(cp_temp >= end_loop))
331 {
332 so->loopState = STATE_LOOPING;
333 if(UNLIKELY(!mode_mask24))
334 {
335 cp_temp -= diff_loop;
336 s2 = sample_data[cp_temp >> FRACTSIZE];
337 }
338 else
339 {
340 so->delta = -so->delta;
341 }
342 }
343 }
344
345 /* Have we overrun? */
346 if(UNLIKELY(cp_temp >= num_samples))
347 {
348 cp_temp -= so->delta;
349 s2 = sample_data[(cp_temp >> FRACTSIZE)+1];
350
351 if (!rampdown) /* stop voice */
352 {
353 rampdown = true;
354 so->decay = 0;
355 }
356 }
357
358 /* Better, working, linear interpolation */
359 s1 = sample_data[cp_temp >> FRACTSIZE];
360
361 s1 +=((signed)((s2 - s1) * (cp_temp & ((1<<FRACTSIZE)-1)))>>FRACTSIZE);
362
363 if(UNLIKELY(so->curRate == 0))
364 {
365 if (!rampdown) /* stop voice */
366 {
367 rampdown = true;
368 so->decay = 0;
369 }
370// so->isUsed = false;
371 }
372
373 if(LIKELY(!ch_9 && !rampdown)) /* Stupid ADSR code... and don't do ADSR for drums */
374 {
375 if(UNLIKELY(so->curOffset < so->targetOffset))
376 {
377 so->curOffset += (so->curRate);
378 if(UNLIKELY(so -> curOffset > so->targetOffset && so->curPoint != 2))
379 {
380 if(UNLIKELY(so->curPoint != 5))
381 {
382 setPoint(so, so->curPoint+1);
383 }
384 else if (!rampdown) /* stop voice */
385 {
386 rampdown = true;
387 so->decay = 0;
388 }
389 }
390 } else
391 {
392 so->curOffset -= (so->curRate);
393 if(UNLIKELY(so -> curOffset < so->targetOffset && so->curPoint != 2))
394 {
395 if(UNLIKELY(so->curPoint != 5))
396 {
397 setPoint(so, so->curPoint+1);
398 }
399 else if (!rampdown) /* stop voice */
400 {
401 rampdown = true;
402 so->decay = 0;
403 }
404
405 }
406 }
407 }
408
409 if(UNLIKELY(so->curOffset < 0))
410 {
411 so->curOffset = so->targetOffset;
412 if (!rampdown)
413 {
414 rampdown = true;
415 so->decay = 0;
416 }
417 }
418
419 s1 = s1 * (so->curOffset >> 22) >> 8;
420
421 /* Scaling by channel volume and note volume is done in sequencer.c */
422 /* That saves us some multiplication and pointer operations */
423 s1 = s1 * volscale >> 14;
424
425 /* need to set ramp beginning */
426 if(UNLIKELY(rampdown && so->decay == 0))
427 {
428 so->decay = s1;
429 if(so->decay == 0)
430 so->decay = 1; /* stupid junk.. */
431 }
432
433 s2 = s1 * pan;
434 s1 = (s1 << 7) - s2;
435 *(out++) += s1;
436 *(out++) += s2;
437 }
438
439 /* store these again */
440 if (rampdown)
441 so->state = STATE_RAMPDOWN;
442 so->cp = cp_temp;
443 return;
444}
445
446/* synth num_samples samples and write them to the */
447/* buffer pointed to by buf_ptr */
448size_t synthSamples(int32_t *buf_ptr, size_t num_samples) ICODE_ATTR;
449size_t synthSamples(int32_t *buf_ptr, size_t num_samples)
450{
451 unsigned int i;
452 struct SynthObject *voicept;
453 size_t nsamples = MIN(num_samples, MAX_SAMPLES);
454
455 rb->memset(buf_ptr, 0, nsamples * 2 * sizeof(int32_t));
456
457 for(i=0; i < MAX_VOICES; i++)
458 {
459 voicept=&voices[i];
460 if(voicept->isUsed)
461 {
462 synthVoice(voicept, buf_ptr, nsamples);
463 }
464 }
465
466 /* TODO: Automatic Gain Control, anyone? */
467 /* Or, should this be implemented on the DSP's output volume instead? */
468
469 return nsamples; /* No more ghetto lowpass filter. Linear interpolation works well. */
470}
471