Mercurial > SDL_sound_CoreAudio
diff 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 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/decoders/timidity/playmidi.c Fri Jan 04 06:49:49 2002 +0000 @@ -0,0 +1,800 @@ +/* + + 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 + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software + Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + + playmidi.c -- random stuff in need of rearrangement + +*/ + +#if HAVE_CONFIG_H +# include <config.h> +#endif + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "SDL_sound.h" + +#define __SDL_SOUND_INTERNAL__ +#include "SDL_sound_internal.h" + +#include "timidity.h" +#include "options.h" +#include "instrum.h" +#include "playmidi.h" +#include "output.h" +#include "mix.h" +#include "tables.h" + +static void adjust_amplification(MidiSong *song) +{ + song->master_volume = (float)(song->amplification) / (float)100.0; +} + +static void reset_voices(MidiSong *song) +{ + int i; + for (i=0; i<MAX_VOICES; i++) + song->voice[i].status=VOICE_FREE; +} + +/* Process the Reset All Controllers event */ +static void reset_controllers(MidiSong *song, int c) +{ + 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 */ +} + +static void reset_midi(MidiSong *song) +{ + int i; + for (i=0; i<16; i++) + { + reset_controllers(song, i); + /* The rest of these are unaffected by the Reset All Controllers event */ + 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 */ + } + reset_voices(song); +} + +static void select_sample(MidiSong *song, int v, Instrument *ip) +{ + Sint32 f, cdiff, diff; + int s,i; + Sample *sp, *closest; + + s=ip->samples; + sp=ip->sample; + + if (s==1) + { + song->voice[v].sample=sp; + return; + } + + f=song->voice[v].orig_frequency; + for (i=0; i<s; i++) + { + if (sp->low_freq <= f && sp->high_freq >= f) + { + song->voice[v].sample=sp; + return; + } + sp++; + } + + /* + 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++; + } + song->voice[v].sample=closest; + return; +} + +static void recompute_freq(MidiSong *song, int v) +{ + int + sign=(song->voice[v].sample_increment < 0), /* for bidirectional loops */ + pb=song->channel[song->voice[v].channel].pitchbend; + double a; + + if (!song->voice[v].sample->sample_rate) + return; + + if (song->voice[v].vibrato_control_ratio) + { + /* This instrument has vibrato. Invalidate any precomputed + sample_increments. */ + + int i=VIBRATO_SAMPLE_INCREMENTS; + while (i--) + song->voice[v].vibrato_sample_increment[i]=0; + } + + if (pb==0x2000 || pb<0 || pb>0x3FFF) + song->voice[v].frequency = song->voice[v].orig_frequency; + else + { + pb-=0x2000; + if (!(song->channel[song->voice[v].channel].pitchfactor)) + { + /* Damn. Somebody bent the pitch. */ + Sint32 i=pb*song->channel[song->voice[v].channel].pitchsens; + if (pb<0) + i=-i; + song->channel[song->voice[v].channel].pitchfactor= + (float)(bend_fine[(i>>5) & 0xFF] * bend_coarse[i>>13]); + } + if (pb>0) + song->voice[v].frequency= + (Sint32)(song->channel[song->voice[v].channel].pitchfactor * + (double)(song->voice[v].orig_frequency)); + else + song->voice[v].frequency= + (Sint32)((double)(song->voice[v].orig_frequency) / + song->channel[song->voice[v].channel].pitchfactor); + } + + a = FSCALE(((double)(song->voice[v].sample->sample_rate) * + (double)(song->voice[v].frequency)) / + ((double)(song->voice[v].sample->root_freq) * + (double)(song->rate)), + FRACTION_BITS); + + if (sign) + a = -a; /* need to preserve the loop direction */ + + song->voice[v].sample_increment = (Sint32)(a); +} + +static void recompute_amp(MidiSong *song, int v) +{ + Sint32 tempamp; + + /* TODO: use fscale */ + + 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)) + { + if (song->voice[v].panning > 60 && song->voice[v].panning < 68) + { + song->voice[v].panned=PANNED_CENTER; + + song->voice[v].left_amp= + FSCALENEG((double)(tempamp) * song->voice[v].sample->volume * song->master_volume, + 21); + } + else if (song->voice[v].panning<5) + { + song->voice[v].panned = PANNED_LEFT; + + song->voice[v].left_amp= + FSCALENEG((double)(tempamp) * song->voice[v].sample->volume * song->master_volume, + 20); + } + else if (song->voice[v].panning>123) + { + song->voice[v].panned = PANNED_RIGHT; + + song->voice[v].left_amp= /* left_amp will be used */ + FSCALENEG((double)(tempamp) * song->voice[v].sample->volume * song->master_volume, + 20); + } + else + { + song->voice[v].panned = PANNED_MYSTERY; + + 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); + } + } + else + { + song->voice[v].panned = PANNED_CENTER; + + song->voice[v].left_amp= + FSCALENEG((double)(tempamp) * song->voice[v].sample->volume * song->master_volume, + 21); + } +} + +static void start_note(MidiSong *song, MidiEvent *e, int i) +{ + Instrument *ip; + int j; + + if (ISDRUMCHANNEL(song, e->channel)) + { + if (!(ip=song->drumset[song->channel[e->channel].bank]->instrument[e->a])) + { + if (!(ip=song->drumset[0]->instrument[e->a])) + return; /* No instrument? Then we can't play. */ + } + if (ip->samples != 1) + { + SNDDBG(("Strange: percussion instrument with %d samples!", + ip->samples)); + } + + if (ip->sample->note_to_use) /* Do we have a fixed pitch? */ + song->voice[i].orig_frequency = freq_table[(int)(ip->sample->note_to_use)]; + else + song->voice[i].orig_frequency = freq_table[e->a & 0x7F]; + + /* drums are supposed to have only one sample */ + song->voice[i].sample = ip->sample; + } + else + { + 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])) + { + if (!(ip=song->tonebank[0]->instrument[song->channel[e->channel].program])) + return; /* No instrument? Then we can't play. */ + } + + if (ip->sample->note_to_use) /* Fixed-pitch instrument? */ + song->voice[i].orig_frequency = freq_table[(int)(ip->sample->note_to_use)]; + else + song->voice[i].orig_frequency = freq_table[e->a & 0x7F]; + select_sample(song, i, ip); + } + + 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; + for (j=0; j<VIBRATO_SAMPLE_INCREMENTS; j++) + song->voice[i].vibrato_sample_increment[j] = 0; + + if (song->channel[e->channel].panning != NO_PANNING) + song->voice[i].panning = song->channel[e->channel].panning; + else + song->voice[i].panning = song->voice[i].sample->panning; + + recompute_freq(song, i); + recompute_amp(song, i); + if (song->voice[i].sample->modes & MODES_ENVELOPE) + { + /* Ramp up from 0 */ + 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); + } + else + { + song->voice[i].envelope_increment = 0; + apply_envelope_to_amp(song, i); + } +} + +static void kill_note(MidiSong *song, int i) +{ + song->voice[i].status = VOICE_DIE; +} + +/* Only one instance of a note can be playing on a single channel. */ +static void note_on(MidiSong *song) +{ + int i = song->voices, lowest=-1; + Sint32 lv=0x7FFFFFFF, v; + MidiEvent *e = song->current_event; + + while (i--) + { + if (song->voice[i].status == VOICE_FREE) + lowest=i; /* Can't get a lower volume than silence */ + 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); + } + + if (lowest != -1) + { + /* Found a free voice. */ + start_note(song,e,lowest); + return; + } + + /* Look for the decaying note with the lowest volume */ + i = song->voices; + while (i--) + { + if ((song->voice[i].status != VOICE_ON) && + (song->voice[i].status != VOICE_DIE)) + { + 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; + 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. */ + + song->cut_notes++; + song->voice[lowest].status=VOICE_FREE; + start_note(song,e,lowest); + } + else + song->lost_notes++; +} + +static void finish_note(MidiSong *song, int i) +{ + if (song->voice[i].sample->modes & MODES_ENVELOPE) + { + /* We need to get the envelope out of Sustain stage */ + song->voice[i].envelope_stage = 3; + song->voice[i].status = VOICE_OFF; + recompute_envelope(song, i); + apply_envelope_to_amp(song, i); + } + 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). */ + song->voice[i].status = VOICE_OFF; + } +} + +static void note_off(MidiSong *song) +{ + int i = song->voices; + MidiEvent *e = song->current_event; + + while (i--) + if (song->voice[i].status == VOICE_ON && + song->voice[i].channel == e->channel && + song->voice[i].note == e->a) + { + if (song->channel[e->channel].sustain) + { + song->voice[i].status = VOICE_SUSTAINED; + } + else + finish_note(song, i); + return; + } +} + +/* Process the All Notes Off event */ +static void all_notes_off(MidiSong *song) +{ + int i = song->voices; + int c = song->current_event->channel; + + SNDDBG(("All notes off on channel %d", c)); + while (i--) + if (song->voice[i].status == VOICE_ON && + song->voice[i].channel == c) + { + if (song->channel[c].sustain) + song->voice[i].status = VOICE_SUSTAINED; + else + finish_note(song, i); + } +} + +/* Process the All Sounds Off event */ +static void all_sounds_off(MidiSong *song) +{ + int i = song->voices; + int c = song->current_event->channel; + + while (i--) + if (song->voice[i].channel == c && + song->voice[i].status != VOICE_FREE && + song->voice[i].status != VOICE_DIE) + { + kill_note(song, i); + } +} + +static void adjust_pressure(MidiSong *song) +{ + MidiEvent *e = song->current_event; + int i = song->voices; + + while (i--) + if (song->voice[i].status == VOICE_ON && + song->voice[i].channel == e->channel && + song->voice[i].note == e->a) + { + song->voice[i].velocity = e->b; + recompute_amp(song, i); + apply_envelope_to_amp(song, i); + return; + } +} + +static void drop_sustain(MidiSong *song) +{ + int i = song->voices; + int c = song->current_event->channel; + + while (i--) + if (song->voice[i].status == VOICE_SUSTAINED && song->voice[i].channel == c) + finish_note(song, i); +} + +static void adjust_pitchbend(MidiSong *song) +{ + int c = song->current_event->channel; + int i = song->voices; + + while (i--) + if (song->voice[i].status != VOICE_FREE && song->voice[i].channel == c) + { + recompute_freq(song, i); + } +} + +static void adjust_volume(MidiSong *song) +{ + int c = song->current_event->channel; + int i = song->voices; + + while (i--) + if (song->voice[i].channel == c && + (song->voice[i].status==VOICE_ON || song->voice[i].status==VOICE_SUSTAINED)) + { + recompute_amp(song, i); + apply_envelope_to_amp(song, i); + } +} + +static void seek_forward(MidiSong *song, Sint32 until_time) +{ + reset_voices(song); + while (song->current_event->time < until_time) + { + switch(song->current_event->type) + { + /* All notes stay off. Just handle the parameter changes. */ + + case ME_PITCH_SENS: + song->channel[song->current_event->channel].pitchsens = + song->current_event->a; + song->channel[song->current_event->channel].pitchfactor = 0; + break; + + case ME_PITCHWHEEL: + song->channel[song->current_event->channel].pitchbend = + song->current_event->a + song->current_event->b * 128; + song->channel[song->current_event->channel].pitchfactor = 0; + break; + + case ME_MAINVOLUME: + song->channel[song->current_event->channel].volume = + song->current_event->a; + break; + + case ME_PAN: + song->channel[song->current_event->channel].panning = + song->current_event->a; + break; + + case ME_EXPRESSION: + song->channel[song->current_event->channel].expression = + song->current_event->a; + break; + + case ME_PROGRAM: + if (ISDRUMCHANNEL(song, song->current_event->channel)) + /* Change drum set */ + song->channel[song->current_event->channel].bank = + song->current_event->a; + else + song->channel[song->current_event->channel].program = + song->current_event->a; + break; + + case ME_SUSTAIN: + song->channel[song->current_event->channel].sustain = + song->current_event->a; + break; + + case ME_RESET_CONTROLLERS: + reset_controllers(song, song->current_event->channel); + break; + + case ME_TONE_BANK: + song->channel[song->current_event->channel].bank = + song->current_event->a; + break; + + case ME_EOT: + song->current_sample = song->current_event->time; + return; + } + song->current_event++; + } + /*song->current_sample=song->current_event->time;*/ + if (song->current_event != song->events) + song->current_event--; + song->current_sample=until_time; +} + +static void skip_to(MidiSong *song, Sint32 until_time) +{ + if (song->current_sample > until_time) + song->current_sample = 0; + + reset_midi(song); + song->buffered_count = 0; + song->buffer_pointer = song->common_buffer; + song->current_event = song->events; + + if (until_time) + seek_forward(song, until_time); +} + +static void do_compute_data(MidiSong *song, Sint32 count) +{ + int i; + memset(song->buffer_pointer, 0, + (song->encoding & PE_MONO) ? (count * 4) : (count * 8)); + for (i = 0; i < song->voices; i++) + { + if(song->voice[i].status != VOICE_FREE) + mix_voice(song, song->buffer_pointer, i, count); + } + song->current_sample += count; +} + +/* count=0 means flush remaining buffered data to output device, then + flush the device itself */ +static void compute_data(MidiSong *song, void *stream, Sint32 count) +{ + int channels; + + if ( song->encoding & PE_MONO ) + channels = 1; + else + channels = 2; + + if (!count) + { + 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; + } + + while ((count + song->buffered_count) >= song->buffer_size) + { + 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; + } + if (count>0) + { + do_compute_data(song, count); + song->buffered_count += count; + song->buffer_pointer += (song->encoding & PE_MONO) ? count : count*2; + } +} + +void Timidity_Start(MidiSong *song) +{ + song->playing = 1; + adjust_amplification(song); + skip_to(song, 0); +} + +int Timidity_PlaySome(MidiSong *song, void *stream, Sint32 len) +{ + Sint32 start_sample, end_sample, samples; + int bytes_per_sample; + + if (!song->playing) + return 0; + + bytes_per_sample = + ((song->encoding & PE_MONO) ? 1 : 2) + * ((song->encoding & PE_16BIT) ? 2 : 1); + samples = len / bytes_per_sample; + + start_sample = song->current_sample; + end_sample = song->current_sample+samples; + while ( song->current_sample < end_sample ) { + /* Handle all events that should happen at this time */ + while (song->current_event->time <= song->current_sample) { + switch(song->current_event->type) { + + /* Effects affecting a single note */ + + case ME_NOTEON: + if (!(song->current_event->b)) /* Velocity 0? */ + note_off(song); + else + note_on(song); + break; + + case ME_NOTEOFF: + note_off(song); + break; + + case ME_KEYPRESSURE: + adjust_pressure(song); + break; + + /* Effects affecting a single channel */ + + case ME_PITCH_SENS: + song->channel[song->current_event->channel].pitchsens = + song->current_event->a; + song->channel[song->current_event->channel].pitchfactor = 0; + break; + + case ME_PITCHWHEEL: + song->channel[song->current_event->channel].pitchbend = + song->current_event->a + song->current_event->b * 128; + song->channel[song->current_event->channel].pitchfactor = 0; + /* Adjust pitch for notes already playing */ + adjust_pitchbend(song); + break; + + case ME_MAINVOLUME: + song->channel[song->current_event->channel].volume = + song->current_event->a; + adjust_volume(song); + break; + + case ME_PAN: + song->channel[song->current_event->channel].panning = + song->current_event->a; + break; + + case ME_EXPRESSION: + song->channel[song->current_event->channel].expression = + song->current_event->a; + adjust_volume(song); + break; + + case ME_PROGRAM: + if (ISDRUMCHANNEL(song, song->current_event->channel)) { + /* Change drum set */ + song->channel[song->current_event->channel].bank = + song->current_event->a; + } + else + song->channel[song->current_event->channel].program = + song->current_event->a; + break; + + case ME_SUSTAIN: + song->channel[song->current_event->channel].sustain = + song->current_event->a; + if (!song->current_event->a) + drop_sustain(song); + break; + + case ME_RESET_CONTROLLERS: + reset_controllers(song, song->current_event->channel); + break; + + case ME_ALL_NOTES_OFF: + all_notes_off(song); + break; + + case ME_ALL_SOUNDS_OFF: + all_sounds_off(song); + break; + + case ME_TONE_BANK: + song->channel[song->current_event->channel].bank = + song->current_event->a; + break; + + case ME_EOT: + /* Give the last notes a couple of seconds to decay */ + 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; + } + song->current_event++; + } + if (song->current_event->time > end_sample) + compute_data(song, stream, end_sample-song->current_sample); + else + compute_data(song, stream, song->current_event->time-song->current_sample); + } + return samples * bytes_per_sample; +} + +void Timidity_SetVolume(MidiSong *song, int volume) +{ + int i; + if (volume > MAX_AMPLIFICATION) + song->amplification = MAX_AMPLIFICATION; + else + if (volume < 0) + song->amplification = 0; + else + song->amplification = volume; + adjust_amplification(song); + for (i = 0; i < song->voices; i++) + if (song->voice[i].status != VOICE_FREE) + { + recompute_amp(song, i); + apply_envelope_to_amp(song, i); + } +}