A modern Music Player Daemon based on Rockbox open source high quality audio player
libadwaita audio rust zig deno mpris rockbox mpd
at master 471 lines 13 kB view raw
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