comparison decoders/timidity/playmidi.c @ 199:2d887640d300

Initial add.
author Ryan C. Gordon <icculus@icculus.org>
date Fri, 04 Jan 2002 06:49:49 +0000
parents
children 498240aa76f1
comparison
equal deleted inserted replaced
198:f9a752f41ab6 199:2d887640d300
1 /*
2
3 TiMidity -- Experimental MIDI to WAVE converter
4 Copyright (C) 1995 Tuukka Toivonen <toivonen@clinet.fi>
5
6 This program is free software; you can redistribute it and/or modify
7 it under the terms of the GNU General Public License as published by
8 the Free Software Foundation; either version 2 of the License, or
9 (at your option) any later version.
10
11 This program is distributed in the hope that it will be useful,
12 but WITHOUT ANY WARRANTY; without even the implied warranty of
13 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 GNU General Public License for more details.
15
16 You should have received a copy of the GNU General Public License
17 along with this program; if not, write to the Free Software
18 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
19
20 playmidi.c -- random stuff in need of rearrangement
21
22 */
23
24 #if HAVE_CONFIG_H
25 # include <config.h>
26 #endif
27
28 #include <stdio.h>
29 #include <stdlib.h>
30 #include <string.h>
31
32 #include "SDL_sound.h"
33
34 #define __SDL_SOUND_INTERNAL__
35 #include "SDL_sound_internal.h"
36
37 #include "timidity.h"
38 #include "options.h"
39 #include "instrum.h"
40 #include "playmidi.h"
41 #include "output.h"
42 #include "mix.h"
43 #include "tables.h"
44
45 static void adjust_amplification(MidiSong *song)
46 {
47 song->master_volume = (float)(song->amplification) / (float)100.0;
48 }
49
50 static void reset_voices(MidiSong *song)
51 {
52 int i;
53 for (i=0; i<MAX_VOICES; i++)
54 song->voice[i].status=VOICE_FREE;
55 }
56
57 /* Process the Reset All Controllers event */
58 static void reset_controllers(MidiSong *song, int c)
59 {
60 song->channel[c].volume=90; /* Some standard says, although the SCC docs say 0. */
61 song->channel[c].expression=127; /* SCC-1 does this. */
62 song->channel[c].sustain=0;
63 song->channel[c].pitchbend=0x2000;
64 song->channel[c].pitchfactor=0; /* to be computed */
65 }
66
67 static void reset_midi(MidiSong *song)
68 {
69 int i;
70 for (i=0; i<16; i++)
71 {
72 reset_controllers(song, i);
73 /* The rest of these are unaffected by the Reset All Controllers event */
74 song->channel[i].program=song->default_program;
75 song->channel[i].panning=NO_PANNING;
76 song->channel[i].pitchsens=2;
77 song->channel[i].bank=0; /* tone bank or drum set */
78 }
79 reset_voices(song);
80 }
81
82 static void select_sample(MidiSong *song, int v, Instrument *ip)
83 {
84 Sint32 f, cdiff, diff;
85 int s,i;
86 Sample *sp, *closest;
87
88 s=ip->samples;
89 sp=ip->sample;
90
91 if (s==1)
92 {
93 song->voice[v].sample=sp;
94 return;
95 }
96
97 f=song->voice[v].orig_frequency;
98 for (i=0; i<s; i++)
99 {
100 if (sp->low_freq <= f && sp->high_freq >= f)
101 {
102 song->voice[v].sample=sp;
103 return;
104 }
105 sp++;
106 }
107
108 /*
109 No suitable sample found! We'll select the sample whose root
110 frequency is closest to the one we want. (Actually we should
111 probably convert the low, high, and root frequencies to MIDI note
112 values and compare those.) */
113
114 cdiff=0x7FFFFFFF;
115 closest=sp=ip->sample;
116 for(i=0; i<s; i++)
117 {
118 diff=sp->root_freq - f;
119 if (diff<0) diff=-diff;
120 if (diff<cdiff)
121 {
122 cdiff=diff;
123 closest=sp;
124 }
125 sp++;
126 }
127 song->voice[v].sample=closest;
128 return;
129 }
130
131 static void recompute_freq(MidiSong *song, int v)
132 {
133 int
134 sign=(song->voice[v].sample_increment < 0), /* for bidirectional loops */
135 pb=song->channel[song->voice[v].channel].pitchbend;
136 double a;
137
138 if (!song->voice[v].sample->sample_rate)
139 return;
140
141 if (song->voice[v].vibrato_control_ratio)
142 {
143 /* This instrument has vibrato. Invalidate any precomputed
144 sample_increments. */
145
146 int i=VIBRATO_SAMPLE_INCREMENTS;
147 while (i--)
148 song->voice[v].vibrato_sample_increment[i]=0;
149 }
150
151 if (pb==0x2000 || pb<0 || pb>0x3FFF)
152 song->voice[v].frequency = song->voice[v].orig_frequency;
153 else
154 {
155 pb-=0x2000;
156 if (!(song->channel[song->voice[v].channel].pitchfactor))
157 {
158 /* Damn. Somebody bent the pitch. */
159 Sint32 i=pb*song->channel[song->voice[v].channel].pitchsens;
160 if (pb<0)
161 i=-i;
162 song->channel[song->voice[v].channel].pitchfactor=
163 (float)(bend_fine[(i>>5) & 0xFF] * bend_coarse[i>>13]);
164 }
165 if (pb>0)
166 song->voice[v].frequency=
167 (Sint32)(song->channel[song->voice[v].channel].pitchfactor *
168 (double)(song->voice[v].orig_frequency));
169 else
170 song->voice[v].frequency=
171 (Sint32)((double)(song->voice[v].orig_frequency) /
172 song->channel[song->voice[v].channel].pitchfactor);
173 }
174
175 a = FSCALE(((double)(song->voice[v].sample->sample_rate) *
176 (double)(song->voice[v].frequency)) /
177 ((double)(song->voice[v].sample->root_freq) *
178 (double)(song->rate)),
179 FRACTION_BITS);
180
181 if (sign)
182 a = -a; /* need to preserve the loop direction */
183
184 song->voice[v].sample_increment = (Sint32)(a);
185 }
186
187 static void recompute_amp(MidiSong *song, int v)
188 {
189 Sint32 tempamp;
190
191 /* TODO: use fscale */
192
193 tempamp= (song->voice[v].velocity *
194 song->channel[song->voice[v].channel].volume *
195 song->channel[song->voice[v].channel].expression); /* 21 bits */
196
197 if (!(song->encoding & PE_MONO))
198 {
199 if (song->voice[v].panning > 60 && song->voice[v].panning < 68)
200 {
201 song->voice[v].panned=PANNED_CENTER;
202
203 song->voice[v].left_amp=
204 FSCALENEG((double)(tempamp) * song->voice[v].sample->volume * song->master_volume,
205 21);
206 }
207 else if (song->voice[v].panning<5)
208 {
209 song->voice[v].panned = PANNED_LEFT;
210
211 song->voice[v].left_amp=
212 FSCALENEG((double)(tempamp) * song->voice[v].sample->volume * song->master_volume,
213 20);
214 }
215 else if (song->voice[v].panning>123)
216 {
217 song->voice[v].panned = PANNED_RIGHT;
218
219 song->voice[v].left_amp= /* left_amp will be used */
220 FSCALENEG((double)(tempamp) * song->voice[v].sample->volume * song->master_volume,
221 20);
222 }
223 else
224 {
225 song->voice[v].panned = PANNED_MYSTERY;
226
227 song->voice[v].left_amp=
228 FSCALENEG((double)(tempamp) * song->voice[v].sample->volume * song->master_volume,
229 27);
230 song->voice[v].right_amp = song->voice[v].left_amp * (song->voice[v].panning);
231 song->voice[v].left_amp *= (float)(127 - song->voice[v].panning);
232 }
233 }
234 else
235 {
236 song->voice[v].panned = PANNED_CENTER;
237
238 song->voice[v].left_amp=
239 FSCALENEG((double)(tempamp) * song->voice[v].sample->volume * song->master_volume,
240 21);
241 }
242 }
243
244 static void start_note(MidiSong *song, MidiEvent *e, int i)
245 {
246 Instrument *ip;
247 int j;
248
249 if (ISDRUMCHANNEL(song, e->channel))
250 {
251 if (!(ip=song->drumset[song->channel[e->channel].bank]->instrument[e->a]))
252 {
253 if (!(ip=song->drumset[0]->instrument[e->a]))
254 return; /* No instrument? Then we can't play. */
255 }
256 if (ip->samples != 1)
257 {
258 SNDDBG(("Strange: percussion instrument with %d samples!",
259 ip->samples));
260 }
261
262 if (ip->sample->note_to_use) /* Do we have a fixed pitch? */
263 song->voice[i].orig_frequency = freq_table[(int)(ip->sample->note_to_use)];
264 else
265 song->voice[i].orig_frequency = freq_table[e->a & 0x7F];
266
267 /* drums are supposed to have only one sample */
268 song->voice[i].sample = ip->sample;
269 }
270 else
271 {
272 if (song->channel[e->channel].program == SPECIAL_PROGRAM)
273 ip=song->default_instrument;
274 else if (!(ip=song->tonebank[song->channel[e->channel].bank]->
275 instrument[song->channel[e->channel].program]))
276 {
277 if (!(ip=song->tonebank[0]->instrument[song->channel[e->channel].program]))
278 return; /* No instrument? Then we can't play. */
279 }
280
281 if (ip->sample->note_to_use) /* Fixed-pitch instrument? */
282 song->voice[i].orig_frequency = freq_table[(int)(ip->sample->note_to_use)];
283 else
284 song->voice[i].orig_frequency = freq_table[e->a & 0x7F];
285 select_sample(song, i, ip);
286 }
287
288 song->voice[i].status = VOICE_ON;
289 song->voice[i].channel = e->channel;
290 song->voice[i].note = e->a;
291 song->voice[i].velocity = e->b;
292 song->voice[i].sample_offset = 0;
293 song->voice[i].sample_increment = 0; /* make sure it isn't negative */
294
295 song->voice[i].tremolo_phase = 0;
296 song->voice[i].tremolo_phase_increment = song->voice[i].sample->tremolo_phase_increment;
297 song->voice[i].tremolo_sweep = song->voice[i].sample->tremolo_sweep_increment;
298 song->voice[i].tremolo_sweep_position = 0;
299
300 song->voice[i].vibrato_sweep = song->voice[i].sample->vibrato_sweep_increment;
301 song->voice[i].vibrato_sweep_position = 0;
302 song->voice[i].vibrato_control_ratio = song->voice[i].sample->vibrato_control_ratio;
303 song->voice[i].vibrato_control_counter = song->voice[i].vibrato_phase = 0;
304 for (j=0; j<VIBRATO_SAMPLE_INCREMENTS; j++)
305 song->voice[i].vibrato_sample_increment[j] = 0;
306
307 if (song->channel[e->channel].panning != NO_PANNING)
308 song->voice[i].panning = song->channel[e->channel].panning;
309 else
310 song->voice[i].panning = song->voice[i].sample->panning;
311
312 recompute_freq(song, i);
313 recompute_amp(song, i);
314 if (song->voice[i].sample->modes & MODES_ENVELOPE)
315 {
316 /* Ramp up from 0 */
317 song->voice[i].envelope_stage = 0;
318 song->voice[i].envelope_volume = 0;
319 song->voice[i].control_counter = 0;
320 recompute_envelope(song, i);
321 apply_envelope_to_amp(song, i);
322 }
323 else
324 {
325 song->voice[i].envelope_increment = 0;
326 apply_envelope_to_amp(song, i);
327 }
328 }
329
330 static void kill_note(MidiSong *song, int i)
331 {
332 song->voice[i].status = VOICE_DIE;
333 }
334
335 /* Only one instance of a note can be playing on a single channel. */
336 static void note_on(MidiSong *song)
337 {
338 int i = song->voices, lowest=-1;
339 Sint32 lv=0x7FFFFFFF, v;
340 MidiEvent *e = song->current_event;
341
342 while (i--)
343 {
344 if (song->voice[i].status == VOICE_FREE)
345 lowest=i; /* Can't get a lower volume than silence */
346 else if (song->voice[i].channel==e->channel &&
347 (song->voice[i].note==e->a || song->channel[song->voice[i].channel].mono))
348 kill_note(song, i);
349 }
350
351 if (lowest != -1)
352 {
353 /* Found a free voice. */
354 start_note(song,e,lowest);
355 return;
356 }
357
358 /* Look for the decaying note with the lowest volume */
359 i = song->voices;
360 while (i--)
361 {
362 if ((song->voice[i].status != VOICE_ON) &&
363 (song->voice[i].status != VOICE_DIE))
364 {
365 v = song->voice[i].left_mix;
366 if ((song->voice[i].panned == PANNED_MYSTERY)
367 && (song->voice[i].right_mix > v))
368 v = song->voice[i].right_mix;
369 if (v<lv)
370 {
371 lv=v;
372 lowest=i;
373 }
374 }
375 }
376
377 if (lowest != -1)
378 {
379 /* This can still cause a click, but if we had a free voice to
380 spare for ramping down this note, we wouldn't need to kill it
381 in the first place... Still, this needs to be fixed. Perhaps
382 we could use a reserve of voices to play dying notes only. */
383
384 song->cut_notes++;
385 song->voice[lowest].status=VOICE_FREE;
386 start_note(song,e,lowest);
387 }
388 else
389 song->lost_notes++;
390 }
391
392 static void finish_note(MidiSong *song, int i)
393 {
394 if (song->voice[i].sample->modes & MODES_ENVELOPE)
395 {
396 /* We need to get the envelope out of Sustain stage */
397 song->voice[i].envelope_stage = 3;
398 song->voice[i].status = VOICE_OFF;
399 recompute_envelope(song, i);
400 apply_envelope_to_amp(song, i);
401 }
402 else
403 {
404 /* Set status to OFF so resample_voice() will let this voice out
405 of its loop, if any. In any case, this voice dies when it
406 hits the end of its data (ofs>=data_length). */
407 song->voice[i].status = VOICE_OFF;
408 }
409 }
410
411 static void note_off(MidiSong *song)
412 {
413 int i = song->voices;
414 MidiEvent *e = song->current_event;
415
416 while (i--)
417 if (song->voice[i].status == VOICE_ON &&
418 song->voice[i].channel == e->channel &&
419 song->voice[i].note == e->a)
420 {
421 if (song->channel[e->channel].sustain)
422 {
423 song->voice[i].status = VOICE_SUSTAINED;
424 }
425 else
426 finish_note(song, i);
427 return;
428 }
429 }
430
431 /* Process the All Notes Off event */
432 static void all_notes_off(MidiSong *song)
433 {
434 int i = song->voices;
435 int c = song->current_event->channel;
436
437 SNDDBG(("All notes off on channel %d", c));
438 while (i--)
439 if (song->voice[i].status == VOICE_ON &&
440 song->voice[i].channel == c)
441 {
442 if (song->channel[c].sustain)
443 song->voice[i].status = VOICE_SUSTAINED;
444 else
445 finish_note(song, i);
446 }
447 }
448
449 /* Process the All Sounds Off event */
450 static void all_sounds_off(MidiSong *song)
451 {
452 int i = song->voices;
453 int c = song->current_event->channel;
454
455 while (i--)
456 if (song->voice[i].channel == c &&
457 song->voice[i].status != VOICE_FREE &&
458 song->voice[i].status != VOICE_DIE)
459 {
460 kill_note(song, i);
461 }
462 }
463
464 static void adjust_pressure(MidiSong *song)
465 {
466 MidiEvent *e = song->current_event;
467 int i = song->voices;
468
469 while (i--)
470 if (song->voice[i].status == VOICE_ON &&
471 song->voice[i].channel == e->channel &&
472 song->voice[i].note == e->a)
473 {
474 song->voice[i].velocity = e->b;
475 recompute_amp(song, i);
476 apply_envelope_to_amp(song, i);
477 return;
478 }
479 }
480
481 static void drop_sustain(MidiSong *song)
482 {
483 int i = song->voices;
484 int c = song->current_event->channel;
485
486 while (i--)
487 if (song->voice[i].status == VOICE_SUSTAINED && song->voice[i].channel == c)
488 finish_note(song, i);
489 }
490
491 static void adjust_pitchbend(MidiSong *song)
492 {
493 int c = song->current_event->channel;
494 int i = song->voices;
495
496 while (i--)
497 if (song->voice[i].status != VOICE_FREE && song->voice[i].channel == c)
498 {
499 recompute_freq(song, i);
500 }
501 }
502
503 static void adjust_volume(MidiSong *song)
504 {
505 int c = song->current_event->channel;
506 int i = song->voices;
507
508 while (i--)
509 if (song->voice[i].channel == c &&
510 (song->voice[i].status==VOICE_ON || song->voice[i].status==VOICE_SUSTAINED))
511 {
512 recompute_amp(song, i);
513 apply_envelope_to_amp(song, i);
514 }
515 }
516
517 static void seek_forward(MidiSong *song, Sint32 until_time)
518 {
519 reset_voices(song);
520 while (song->current_event->time < until_time)
521 {
522 switch(song->current_event->type)
523 {
524 /* All notes stay off. Just handle the parameter changes. */
525
526 case ME_PITCH_SENS:
527 song->channel[song->current_event->channel].pitchsens =
528 song->current_event->a;
529 song->channel[song->current_event->channel].pitchfactor = 0;
530 break;
531
532 case ME_PITCHWHEEL:
533 song->channel[song->current_event->channel].pitchbend =
534 song->current_event->a + song->current_event->b * 128;
535 song->channel[song->current_event->channel].pitchfactor = 0;
536 break;
537
538 case ME_MAINVOLUME:
539 song->channel[song->current_event->channel].volume =
540 song->current_event->a;
541 break;
542
543 case ME_PAN:
544 song->channel[song->current_event->channel].panning =
545 song->current_event->a;
546 break;
547
548 case ME_EXPRESSION:
549 song->channel[song->current_event->channel].expression =
550 song->current_event->a;
551 break;
552
553 case ME_PROGRAM:
554 if (ISDRUMCHANNEL(song, song->current_event->channel))
555 /* Change drum set */
556 song->channel[song->current_event->channel].bank =
557 song->current_event->a;
558 else
559 song->channel[song->current_event->channel].program =
560 song->current_event->a;
561 break;
562
563 case ME_SUSTAIN:
564 song->channel[song->current_event->channel].sustain =
565 song->current_event->a;
566 break;
567
568 case ME_RESET_CONTROLLERS:
569 reset_controllers(song, song->current_event->channel);
570 break;
571
572 case ME_TONE_BANK:
573 song->channel[song->current_event->channel].bank =
574 song->current_event->a;
575 break;
576
577 case ME_EOT:
578 song->current_sample = song->current_event->time;
579 return;
580 }
581 song->current_event++;
582 }
583 /*song->current_sample=song->current_event->time;*/
584 if (song->current_event != song->events)
585 song->current_event--;
586 song->current_sample=until_time;
587 }
588
589 static void skip_to(MidiSong *song, Sint32 until_time)
590 {
591 if (song->current_sample > until_time)
592 song->current_sample = 0;
593
594 reset_midi(song);
595 song->buffered_count = 0;
596 song->buffer_pointer = song->common_buffer;
597 song->current_event = song->events;
598
599 if (until_time)
600 seek_forward(song, until_time);
601 }
602
603 static void do_compute_data(MidiSong *song, Sint32 count)
604 {
605 int i;
606 memset(song->buffer_pointer, 0,
607 (song->encoding & PE_MONO) ? (count * 4) : (count * 8));
608 for (i = 0; i < song->voices; i++)
609 {
610 if(song->voice[i].status != VOICE_FREE)
611 mix_voice(song, song->buffer_pointer, i, count);
612 }
613 song->current_sample += count;
614 }
615
616 /* count=0 means flush remaining buffered data to output device, then
617 flush the device itself */
618 static void compute_data(MidiSong *song, void *stream, Sint32 count)
619 {
620 int channels;
621
622 if ( song->encoding & PE_MONO )
623 channels = 1;
624 else
625 channels = 2;
626
627 if (!count)
628 {
629 if (song->buffered_count)
630 song->write(stream, song->common_buffer, channels * song->buffered_count);
631 song->buffer_pointer = song->common_buffer;
632 song->buffered_count = 0;
633 return;
634 }
635
636 while ((count + song->buffered_count) >= song->buffer_size)
637 {
638 do_compute_data(song, song->buffer_size - song->buffered_count);
639 count -= song->buffer_size - song->buffered_count;
640 song->write(stream, song->common_buffer, channels * song->buffer_size);
641 song->buffer_pointer = song->common_buffer;
642 song->buffered_count = 0;
643 }
644 if (count>0)
645 {
646 do_compute_data(song, count);
647 song->buffered_count += count;
648 song->buffer_pointer += (song->encoding & PE_MONO) ? count : count*2;
649 }
650 }
651
652 void Timidity_Start(MidiSong *song)
653 {
654 song->playing = 1;
655 adjust_amplification(song);
656 skip_to(song, 0);
657 }
658
659 int Timidity_PlaySome(MidiSong *song, void *stream, Sint32 len)
660 {
661 Sint32 start_sample, end_sample, samples;
662 int bytes_per_sample;
663
664 if (!song->playing)
665 return 0;
666
667 bytes_per_sample =
668 ((song->encoding & PE_MONO) ? 1 : 2)
669 * ((song->encoding & PE_16BIT) ? 2 : 1);
670 samples = len / bytes_per_sample;
671
672 start_sample = song->current_sample;
673 end_sample = song->current_sample+samples;
674 while ( song->current_sample < end_sample ) {
675 /* Handle all events that should happen at this time */
676 while (song->current_event->time <= song->current_sample) {
677 switch(song->current_event->type) {
678
679 /* Effects affecting a single note */
680
681 case ME_NOTEON:
682 if (!(song->current_event->b)) /* Velocity 0? */
683 note_off(song);
684 else
685 note_on(song);
686 break;
687
688 case ME_NOTEOFF:
689 note_off(song);
690 break;
691
692 case ME_KEYPRESSURE:
693 adjust_pressure(song);
694 break;
695
696 /* Effects affecting a single channel */
697
698 case ME_PITCH_SENS:
699 song->channel[song->current_event->channel].pitchsens =
700 song->current_event->a;
701 song->channel[song->current_event->channel].pitchfactor = 0;
702 break;
703
704 case ME_PITCHWHEEL:
705 song->channel[song->current_event->channel].pitchbend =
706 song->current_event->a + song->current_event->b * 128;
707 song->channel[song->current_event->channel].pitchfactor = 0;
708 /* Adjust pitch for notes already playing */
709 adjust_pitchbend(song);
710 break;
711
712 case ME_MAINVOLUME:
713 song->channel[song->current_event->channel].volume =
714 song->current_event->a;
715 adjust_volume(song);
716 break;
717
718 case ME_PAN:
719 song->channel[song->current_event->channel].panning =
720 song->current_event->a;
721 break;
722
723 case ME_EXPRESSION:
724 song->channel[song->current_event->channel].expression =
725 song->current_event->a;
726 adjust_volume(song);
727 break;
728
729 case ME_PROGRAM:
730 if (ISDRUMCHANNEL(song, song->current_event->channel)) {
731 /* Change drum set */
732 song->channel[song->current_event->channel].bank =
733 song->current_event->a;
734 }
735 else
736 song->channel[song->current_event->channel].program =
737 song->current_event->a;
738 break;
739
740 case ME_SUSTAIN:
741 song->channel[song->current_event->channel].sustain =
742 song->current_event->a;
743 if (!song->current_event->a)
744 drop_sustain(song);
745 break;
746
747 case ME_RESET_CONTROLLERS:
748 reset_controllers(song, song->current_event->channel);
749 break;
750
751 case ME_ALL_NOTES_OFF:
752 all_notes_off(song);
753 break;
754
755 case ME_ALL_SOUNDS_OFF:
756 all_sounds_off(song);
757 break;
758
759 case ME_TONE_BANK:
760 song->channel[song->current_event->channel].bank =
761 song->current_event->a;
762 break;
763
764 case ME_EOT:
765 /* Give the last notes a couple of seconds to decay */
766 SNDDBG(("Playing time: ~%d seconds\n",
767 song->current_sample/song->rate+2));
768 SNDDBG(("Notes cut: %d\n", song->cut_notes));
769 SNDDBG(("Notes lost totally: %d\n", song->lost_notes));
770 song->playing = 0;
771 return (song->current_sample - start_sample) * bytes_per_sample;
772 }
773 song->current_event++;
774 }
775 if (song->current_event->time > end_sample)
776 compute_data(song, stream, end_sample-song->current_sample);
777 else
778 compute_data(song, stream, song->current_event->time-song->current_sample);
779 }
780 return samples * bytes_per_sample;
781 }
782
783 void Timidity_SetVolume(MidiSong *song, int volume)
784 {
785 int i;
786 if (volume > MAX_AMPLIFICATION)
787 song->amplification = MAX_AMPLIFICATION;
788 else
789 if (volume < 0)
790 song->amplification = 0;
791 else
792 song->amplification = volume;
793 adjust_amplification(song);
794 for (i = 0; i < song->voices; i++)
795 if (song->voice[i].status != VOICE_FREE)
796 {
797 recompute_amp(song, i);
798 apply_envelope_to_amp(song, i);
799 }
800 }