diff options
Diffstat (limited to 'apps/plugins/midi/synth.c')
-rw-r--r-- | apps/plugins/midi/synth.c | 430 |
1 files changed, 430 insertions, 0 deletions
diff --git a/apps/plugins/midi/synth.c b/apps/plugins/midi/synth.c new file mode 100644 index 0000000000..0d07ed91d0 --- /dev/null +++ b/apps/plugins/midi/synth.c | |||
@@ -0,0 +1,430 @@ | |||
1 | /*************************************************************************** | ||
2 | * __________ __ ___. | ||
3 | * Open \______ \ ____ ____ | | _\_ |__ _______ ___ | ||
4 | * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / | ||
5 | * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < | ||
6 | * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ | ||
7 | * \/ \/ \/ \/ \/ | ||
8 | * | ||
9 | * Copyright (C) 2005 Stepan Moskovchenko | ||
10 | * | ||
11 | * All files in this archive are subject to the GNU General Public License. | ||
12 | * See the file COPYING in the source tree root for full license agreement. | ||
13 | * | ||
14 | * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY | ||
15 | * KIND, either express or implied. | ||
16 | * | ||
17 | ****************************************************************************/ | ||
18 | |||
19 | |||
20 | |||
21 | extern struct plugin_api * rb; | ||
22 | |||
23 | struct Event * getEvent(struct Track * tr, int evNum) | ||
24 | { | ||
25 | return tr->dataBlock + (evNum*sizeof(struct Event)); | ||
26 | } | ||
27 | |||
28 | void readTextBlock(int file, char * buf) | ||
29 | { | ||
30 | char c = 0; | ||
31 | do | ||
32 | { | ||
33 | c = readChar(file); | ||
34 | } while(c == '\n' || c == ' ' || c=='\t'); | ||
35 | |||
36 | rb->lseek(file, -1, SEEK_CUR); | ||
37 | int cp = 0; | ||
38 | do | ||
39 | { | ||
40 | c = readChar(file); | ||
41 | buf[cp] = c; | ||
42 | cp++; | ||
43 | } while (c != '\n' && c != ' ' && c != '\t' && !eof(file)); | ||
44 | buf[cp-1]=0; | ||
45 | rb->lseek(file, -1, SEEK_CUR); | ||
46 | } | ||
47 | |||
48 | |||
49 | |||
50 | //Filename is the name of the config file | ||
51 | //The MIDI file should have been loaded at this point | ||
52 | void initSynth(struct MIDIfile * mf, char * filename, char * drumConfig) | ||
53 | { | ||
54 | char patchUsed[128]; | ||
55 | char drumUsed[128]; | ||
56 | int a=0; | ||
57 | for(a=0; a<MAX_VOICES; a++) | ||
58 | { | ||
59 | voices[a].cp=0; | ||
60 | voices[a].vol=0; | ||
61 | voices[a].ch=0; | ||
62 | voices[a].isUsed=0; | ||
63 | voices[a].note=0; | ||
64 | } | ||
65 | |||
66 | for(a=0; a<16; a++) | ||
67 | { | ||
68 | chVol[a]=100; //Default, not quite full blast.. | ||
69 | chPanLeft[a]=64; //Center | ||
70 | chPanRight[a]=64; //Center | ||
71 | chPat[a]=0; //Ac Gr Piano | ||
72 | chPW[a]=64; // .. not .. bent ? | ||
73 | } | ||
74 | for(a=0; a<128; a++) | ||
75 | { | ||
76 | patchSet[a]=NULL; | ||
77 | drumSet[a]=NULL; | ||
78 | patchUsed[a]=0; | ||
79 | drumUsed[a]=0; | ||
80 | } | ||
81 | |||
82 | //Always load the piano. | ||
83 | //Some files will assume its loaded without specifically | ||
84 | //issuing a Patch command... then we wonder why we can't hear anything | ||
85 | patchUsed[0]=1; | ||
86 | |||
87 | //Scan the file to see what needs to be loaded | ||
88 | for(a=0; a<mf->numTracks; a++) | ||
89 | { | ||
90 | int ts=0; | ||
91 | |||
92 | if(mf->tracks[a] == NULL) | ||
93 | { | ||
94 | printf("\nNULL TRACK !!!"); | ||
95 | exit(1); | ||
96 | return; | ||
97 | } | ||
98 | |||
99 | for(ts=0; ts<mf->tracks[a]->numEvents; ts++) | ||
100 | { | ||
101 | |||
102 | if((getEvent(mf->tracks[a], ts)->status) == (MIDI_NOTE_ON+9)) | ||
103 | drumUsed[getEvent(mf->tracks[a], ts)->d1]=1; | ||
104 | |||
105 | if( (getEvent(mf->tracks[a], ts)->status & 0xF0) == MIDI_PRGM) | ||
106 | { | ||
107 | if(patchUsed[getEvent(mf->tracks[a], ts)->d1]==0) | ||
108 | printf("\nI need to load patch %d.", getEvent(mf->tracks[a], ts)->d1); | ||
109 | patchUsed[getEvent(mf->tracks[a], ts)->d1]=1; | ||
110 | } | ||
111 | } | ||
112 | } | ||
113 | |||
114 | int file = rb->open(filename, O_RDONLY); | ||
115 | |||
116 | char name[30]; | ||
117 | char fn[30]; | ||
118 | |||
119 | //Scan our config file and load the right patches as needed | ||
120 | int c = 0; | ||
121 | rb->snprintf(name, 30, ""); | ||
122 | for(a=0; a<128; a++) | ||
123 | { | ||
124 | while(readChar(file)!=' ' && !eof(file)); | ||
125 | readTextBlock(file, name); | ||
126 | |||
127 | rb->snprintf(fn, 30, "/patchset/%s.pat", name); | ||
128 | printf("\nLOADING: <%s> ", fn); | ||
129 | if(patchUsed[a]==1) | ||
130 | patchSet[a]=gusload(fn); | ||
131 | |||
132 | while((c != '\n')) | ||
133 | c = readChar(file); | ||
134 | } | ||
135 | rb->close(file); | ||
136 | |||
137 | file = rb->open(drumConfig, O_RDONLY); | ||
138 | |||
139 | //Scan our config file and load the drum data | ||
140 | int idx=0; | ||
141 | char number[30]; | ||
142 | while(!eof(file)) | ||
143 | { | ||
144 | readTextBlock(file, number); | ||
145 | readTextBlock(file, name); | ||
146 | rb->snprintf(fn, 30, "/patchset/%s.pat", name); | ||
147 | |||
148 | idx = rb->atoi(number); | ||
149 | if(idx == 0) | ||
150 | break; | ||
151 | |||
152 | if(drumUsed[idx]==1) | ||
153 | drumSet[idx]=gusload(fn); | ||
154 | while((c != '\n') && (c != 255) && (!eof(file))) | ||
155 | { | ||
156 | printf("loop"); | ||
157 | c = readChar(file); | ||
158 | } | ||
159 | } | ||
160 | rb->close(file); | ||
161 | } | ||
162 | |||
163 | |||
164 | |||
165 | inline signed short int getSample(struct GWaveform * wf, unsigned int s) | ||
166 | { | ||
167 | |||
168 | //16 bit samples | ||
169 | if(wf->mode&1) | ||
170 | { | ||
171 | |||
172 | if(s<<1 >= wf->wavSize) | ||
173 | { | ||
174 | // printf("\nSAMPLE OUT OF RANGE: s=%d 2s=%d ws=%d", s, 2*s, wf->wavSize); | ||
175 | return 0; | ||
176 | } | ||
177 | |||
178 | |||
179 | /* | ||
180 | * Probably put the signed/unsigned and and 8-16 bit conversion | ||
181 | * into the patch loader and have it run there, once. | ||
182 | */ | ||
183 | |||
184 | |||
185 | //If they are unsigned, convert them to signed | ||
186 | //or was it the other way around. Whatever, it works | ||
187 | unsigned char b1=wf->data[s<<1]+((wf->mode & 2) << 6); | ||
188 | unsigned char b2=wf->data[(s<<1)|1]+((wf->mode & 2) << 6); | ||
189 | return (b1 | (b2<<8)); | ||
190 | } | ||
191 | else | ||
192 | { //8-bit samples | ||
193 | unsigned char b1=wf->data[s]+((wf->mode & 2) << 6); | ||
194 | return b1<<8; | ||
195 | } | ||
196 | } | ||
197 | |||
198 | |||
199 | |||
200 | |||
201 | inline void setPoint(struct SynthObject * so, int pt) | ||
202 | { | ||
203 | if(so->ch==9) //Drums, no ADSR | ||
204 | { | ||
205 | so->curOffset = 1<<27; | ||
206 | so->curRate = 1; | ||
207 | return; | ||
208 | } | ||
209 | |||
210 | if(so->wf==NULL) | ||
211 | { | ||
212 | printf("\nCrap... null waveform..."); | ||
213 | exit(1); | ||
214 | } | ||
215 | if(so->wf->envRate==NULL) | ||
216 | { | ||
217 | printf("\nWaveform has no envelope set"); | ||
218 | exit(1); | ||
219 | } | ||
220 | |||
221 | so->curPoint = pt; | ||
222 | |||
223 | int r=0; | ||
224 | |||
225 | |||
226 | |||
227 | |||
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<<9; | ||
244 | |||
245 | |||
246 | so->targetOffset = so->wf->envOffset[pt]<<(20); | ||
247 | if(pt==0) | ||
248 | so->curOffset = 0; | ||
249 | } | ||
250 | |||
251 | |||
252 | long msi=0; | ||
253 | |||
254 | inline void stopVoice(struct SynthObject * so) | ||
255 | { | ||
256 | if(so->state == STATE_RAMPDOWN) | ||
257 | return; | ||
258 | so->state = STATE_RAMPDOWN; | ||
259 | so->decay = 255; | ||
260 | |||
261 | } | ||
262 | |||
263 | int rampDown = 0; | ||
264 | |||
265 | inline signed short int synthVoice(int v) | ||
266 | { | ||
267 | //Probably can combine these 2 lines into one.. | ||
268 | //But for now, this looks more readable | ||
269 | // struct GPatch * pat = patchSet[chPat[voices[v].ch]]; | ||
270 | // struct GWaveform * wf = pat->waveforms[pat->noteTable[voices[v].note]]; | ||
271 | struct SynthObject * so = &voices[v]; | ||
272 | struct GWaveform * wf = so->wf; | ||
273 | |||
274 | signed int s; | ||
275 | |||
276 | if(so->state != STATE_RAMPDOWN) | ||
277 | { | ||
278 | if(so->loopDir==LOOPDIR_FORWARD) | ||
279 | { | ||
280 | so->cp += so->delta; | ||
281 | } | ||
282 | else | ||
283 | { | ||
284 | so->cp -= so->delta; | ||
285 | } | ||
286 | } | ||
287 | |||
288 | if( (so->cp>>9 >= (wf->wavSize)) && (so->state != STATE_RAMPDOWN)) | ||
289 | stopVoice(so); | ||
290 | |||
291 | /* | ||
292 | //Original, working, no interpolation | ||
293 | s=getSample(wf, (so->cp>>10)); | ||
294 | */ | ||
295 | |||
296 | |||
297 | |||
298 | int s2=getSample(wf, (so->cp>>10)+1); | ||
299 | |||
300 | if((wf->mode & (LOOP_REVERSE|LOOP_PINGPONG)) && so->loopState == STATE_LOOPING && (so->cp>>10 <= (wf->startLoop>>1))) | ||
301 | { | ||
302 | if(wf->mode & LOOP_REVERSE) | ||
303 | { | ||
304 | so->cp = (wf->endLoop)<<9; | ||
305 | s2=getSample(wf, (so->cp>>10)); | ||
306 | } else | ||
307 | so->loopDir = LOOPDIR_FORWARD; | ||
308 | } | ||
309 | |||
310 | if((wf->mode & 28) && (so->cp>>10 >= wf->endLoop>>1)) | ||
311 | { | ||
312 | so->loopState = STATE_LOOPING; | ||
313 | if((wf->mode & (24)) == 0) | ||
314 | { | ||
315 | so->cp = (wf->startLoop)<<9; | ||
316 | s2=getSample(wf, (so->cp>>10)); | ||
317 | } else | ||
318 | so->loopDir = LOOPDIR_REVERSE; | ||
319 | } | ||
320 | |||
321 | //Better, working, linear interpolation | ||
322 | int s1=getSample(wf, (so->cp>>10)); | ||
323 | s = s1 + ((long)((s2 - s1) * (so->cp & 1023))>>10); | ||
324 | |||
325 | if(so->curRate == 0) | ||
326 | stopVoice(so); | ||
327 | |||
328 | |||
329 | if(so->ch != 9) //Stupid ADSR code... and don't do ADSR for drums | ||
330 | { | ||
331 | if(so->curOffset < so->targetOffset) | ||
332 | { | ||
333 | so->curOffset += (so->curRate); | ||
334 | if(so -> curOffset > so->targetOffset && so->curPoint != 2) | ||
335 | { | ||
336 | if(so->curPoint != 5) | ||
337 | setPoint(so, so->curPoint+1); | ||
338 | else | ||
339 | stopVoice(so); | ||
340 | } | ||
341 | } else | ||
342 | { | ||
343 | so->curOffset -= (so->curRate); | ||
344 | if(so -> curOffset < so->targetOffset && so->curPoint != 2) | ||
345 | { | ||
346 | |||
347 | if(so->curPoint != 5) | ||
348 | setPoint(so, so->curPoint+1); | ||
349 | else | ||
350 | stopVoice(so); | ||
351 | |||
352 | } | ||
353 | } | ||
354 | } | ||
355 | |||
356 | if(so->curOffset < 0) | ||
357 | so->isUsed=0; //This is OK | ||
358 | |||
359 | s = s * (so->curOffset >> 22); | ||
360 | s = s>>6; | ||
361 | |||
362 | |||
363 | if(so->state == STATE_RAMPDOWN) | ||
364 | { | ||
365 | so->decay--; | ||
366 | if(so->decay == 0) | ||
367 | so->isUsed=0; | ||
368 | } | ||
369 | |||
370 | s = s * so->decay; s = s >> 9; | ||
371 | |||
372 | return s*((signed short int)so->vol*(signed short int)chVol[so->ch])>>14; | ||
373 | } | ||
374 | |||
375 | |||
376 | |||
377 | int mhL[16]; | ||
378 | int mhR[16]; | ||
379 | int mp=0; //Mix position, for circular array | ||
380 | // Was stuff for Ghetto Lowpass Filter, now deprecated. | ||
381 | |||
382 | |||
383 | inline void synthSample(int * mixL, int * mixR) | ||
384 | { | ||
385 | int a=0; | ||
386 | signed long int leftMix=0, rightMix=0, sample=0; | ||
387 | for(a=0; a<MAX_VOICES; a++) | ||
388 | { | ||
389 | if(voices[a].isUsed==1) | ||
390 | { | ||
391 | sample = synthVoice(a); | ||
392 | |||
393 | leftMix += (sample*chPanLeft[voices[a].ch])>>7; | ||
394 | rightMix += (sample*chPanRight[voices[a].ch])>>7; | ||
395 | } | ||
396 | } | ||
397 | |||
398 | //TODO: Automatic Gain Control, anyone? | ||
399 | //Or, should this be implemented on the DSP's output volume instead? | ||
400 | *mixL = leftMix; | ||
401 | *mixR = rightMix; | ||
402 | |||
403 | return; //No more ghetto lowpass filter.. linear intrpolation works well. | ||
404 | |||
405 | |||
406 | // HACK HACK HACK | ||
407 | // This is the infamous Ghetto Lowpass Filter | ||
408 | // Now that I have linear interpolation, it should not be needed anymore. | ||
409 | /* | ||
410 | mp++; | ||
411 | if(mp==4) | ||
412 | mp=0; | ||
413 | |||
414 | mhL[mp]=leftMix; | ||
415 | mhR[mp]=rightMix; | ||
416 | |||
417 | *mixL = 0; | ||
418 | *mixR = 0; | ||
419 | |||
420 | |||
421 | for(a=0; a<4; a++) | ||
422 | { | ||
423 | *mixL += mhL[a]; | ||
424 | *mixR += mhR[a]; | ||
425 | } | ||
426 | *mixL = *mixL>>4; | ||
427 | *mixR = *mixR>>4; | ||
428 | */ | ||
429 | // END HACK END HACK END HACK | ||
430 | } | ||