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