📄 playmidi.c
字号:
/* this reduces voices while maintaining sound quality */
static int reduce_voice(void)
{
int32 lv, v;
int i, j, lowest=-0x7FFFFFFF;
i = upper_voices;
lv = 0x7FFFFFFF;
/* Look for the decaying note with the smallest volume */
/* Protect drum decays. Truncating them early sounds bad, especially on
snares and cymbals */
for(j = 0; j < i; j++)
{
if(voice[j].status & VOICE_FREE ||
(voice[j].sample->note_to_use && ISDRUMCHANNEL(voice[j].channel)))
continue;
if(voice[j].status & ~(VOICE_ON | VOICE_DIE | VOICE_SUSTAINED))
{
/* find lowest volume */
v = voice[j].left_mix;
if(voice[j].panned == PANNED_MYSTERY && voice[j].right_mix > v)
v = voice[j].right_mix;
if(v < lv)
{
lv = v;
lowest = j;
}
}
}
if(lowest != -0x7FFFFFFF)
{
/* 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. */
cut_notes++;
free_voice(lowest);
if(!prescanning_flag)
ctl_note_event(lowest);
return lowest;
}
/* try to remove VOICE_DIE before VOICE_ON */
lv = 0x7FFFFFFF;
lowest = -1;
for(j = 0; j < i; j++)
{
if(voice[j].status & VOICE_FREE)
continue;
if(voice[j].status & ~(VOICE_ON | VOICE_SUSTAINED))
{
/* continue protecting drum decays */
if (voice[j].status & ~(VOICE_DIE) &&
(voice[j].sample->note_to_use && ISDRUMCHANNEL(voice[j].channel)))
continue;
/* find lowest volume */
v = voice[j].left_mix;
if(voice[j].panned == PANNED_MYSTERY && voice[j].right_mix > v)
v = voice[j].right_mix;
if(v < lv)
{
lv = v;
lowest = j;
}
}
}
if(lowest != -1)
{
cut_notes++;
free_voice(lowest);
if(!prescanning_flag)
ctl_note_event(lowest);
return lowest;
}
/* try to remove VOICE_SUSTAINED before VOICE_ON */
lv = 0x7FFFFFFF;
lowest = -0x7FFFFFFF;
for(j = 0; j < i; j++)
{
if(voice[j].status & VOICE_FREE)
continue;
if(voice[j].status & VOICE_SUSTAINED)
{
/* find lowest volume */
v = voice[j].left_mix;
if(voice[j].panned == PANNED_MYSTERY && voice[j].right_mix > v)
v = voice[j].right_mix;
if(v < lv)
{
lv = v;
lowest = j;
}
}
}
if(lowest != -0x7FFFFFFF)
{
cut_notes++;
free_voice(lowest);
if(!prescanning_flag)
ctl_note_event(lowest);
return lowest;
}
/* try to remove chorus before VOICE_ON */
lv = 0x7FFFFFFF;
lowest = -0x7FFFFFFF;
for(j = 0; j < i; j++)
{
if(voice[j].status & VOICE_FREE)
continue;
if(voice[j].chorus_link < j)
{
/* find lowest volume */
v = voice[j].left_mix;
if(voice[j].panned == PANNED_MYSTERY && voice[j].right_mix > v)
v = voice[j].right_mix;
if(v < lv)
{
lv = v;
lowest = j;
}
}
}
if(lowest != -0x7FFFFFFF)
{
cut_notes++;
/* hack - double volume of chorus partner, fix pan */
j = voice[lowest].chorus_link;
voice[j].velocity <<= 1;
voice[j].panning = channel[voice[lowest].channel].panning;
recompute_amp(j);
apply_envelope_to_amp(j);
free_voice(lowest);
if(!prescanning_flag)
ctl_note_event(lowest);
return lowest;
}
lost_notes++;
/* remove non-drum VOICE_ON */
lv = 0x7FFFFFFF;
lowest = -0x7FFFFFFF;
for(j = 0; j < i; j++)
{
if(voice[j].status & VOICE_FREE ||
(voice[j].sample->note_to_use && ISDRUMCHANNEL(voice[j].channel)))
continue;
/* find lowest volume */
v = voice[j].left_mix;
if(voice[j].panned == PANNED_MYSTERY && voice[j].right_mix > v)
v = voice[j].right_mix;
if(v < lv)
{
lv = v;
lowest = j;
}
}
if(lowest != -0x7FFFFFFF)
{
free_voice(lowest);
if(!prescanning_flag)
ctl_note_event(lowest);
return lowest;
}
/* remove all other types of notes */
lv = 0x7FFFFFFF;
lowest = 0;
for(j = 0; j < i; j++)
{
if(voice[j].status & VOICE_FREE)
continue;
/* find lowest volume */
v = voice[j].left_mix;
if(voice[j].panned == PANNED_MYSTERY && voice[j].right_mix > v)
v = voice[j].right_mix;
if(v < lv)
{
lv = v;
lowest = j;
}
}
free_voice(lowest);
if(!prescanning_flag)
ctl_note_event(lowest);
return lowest;
}
/* Only one instance of a note can be playing on a single channel. */
static int find_voice(MidiEvent *e)
{
int i, j, lowest=-1, note, ch, status_check, mono_check;
AlternateAssign *altassign;
note = MIDI_EVENT_NOTE(e);
ch = e->channel;
if(opt_overlap_voice_allow)
status_check = (VOICE_OFF | VOICE_SUSTAINED);
else
status_check = 0xFF;
mono_check = channel[ch].mono;
altassign = find_altassign(channel[ch].altassign, note);
i = upper_voices;
for(j = 0; j < i; j++)
if(voice[j].status == VOICE_FREE)
{
lowest = j; /* lower volume */
break;
}
for(j = 0; j < i; j++)
{
if(voice[j].status != VOICE_FREE &&
voice[j].channel == ch &&
(((voice[j].status & status_check) && voice[j].note == note) ||
mono_check ||
(altassign &&
(voice[j].note == note || find_altassign(altassign, voice[j].note)))
))
kill_note(j);
}
if(lowest != -1)
{
/* Found a free voice. */
if(upper_voices <= lowest)
upper_voices = lowest + 1;
return lowest;
}
if(i < voices)
return upper_voices++;
return reduce_voice();
}
void free_voice(int v1)
{
int v2;
v2 = voice[v1].chorus_link;
if(v1 != v2)
{
/* Unlink chorus link */
voice[v1].chorus_link = v1;
voice[v2].chorus_link = v2;
}
voice[v1].status = VOICE_FREE;
}
static int find_free_voice(void)
{
int i, nv = voices, lowest;
int32 lv, v;
for(i = 0; i < nv; i++)
if(voice[i].status == VOICE_FREE)
{
if(upper_voices <= i)
upper_voices = i + 1;
return i;
}
upper_voices = voices;
/* Look for the decaying note with the lowest volume */
lv = 0x7FFFFFFF;
lowest = -1;
for(i = 0; i < nv; i++)
{
if(voice[i].status & ~(VOICE_ON | VOICE_DIE) &&
!(voice[i].sample->note_to_use && ISDRUMCHANNEL(voice[i].channel)))
{
v = voice[i].left_mix;
if((voice[i].panned==PANNED_MYSTERY) && (voice[i].right_mix>v))
v = voice[i].right_mix;
if(v<lv)
{
lv = v;
lowest = i;
}
}
}
if(lowest != -1 && !prescanning_flag)
{
free_voice(lowest);
ctl_note_event(lowest);
}
return lowest;
}
static int select_play_sample(Sample *splist, int nsp,
int note, int *vlist, MidiEvent *e)
{
int32 f, cdiff, diff;
Sample *sp, *closest;
int i, j, nv, vel;
f = freq_table[note];
if(nsp == 1)
{
j = vlist[0] = find_voice(e);
voice[j].orig_frequency = f;
MYCHECK(voice[j].orig_frequency);
voice[j].sample = splist;
voice[j].status = VOICE_ON;
return 1;
}
vel = e->b;
nv = 0;
for(i = 0, sp = splist; i < nsp; i++, sp++)
if(sp->low_vel <= vel && sp->high_vel >= vel &&
sp->low_freq <= f && sp->high_freq >= f)
{
j = vlist[nv] = find_voice(e);
voice[j].orig_frequency = f;
MYCHECK(voice[j].orig_frequency);
voice[j].sample = sp;
voice[j].status = VOICE_ON;
nv++;
}
if(nv == 0)
{
cdiff = 0x7FFFFFFF;
closest = sp = splist;
for(i = 0; i < nsp; i++, sp++)
{
diff = sp->root_freq - f;
if(diff < 0) diff = -diff;
if(diff < cdiff)
{
cdiff = diff;
closest = sp;
}
}
j = vlist[nv] = find_voice(e);
voice[j].orig_frequency = f;
voice[j].sample = closest;
voice[j].status = VOICE_ON;
nv++;
}
return nv;
}
static int find_samples(MidiEvent *e, int *vlist)
{
Instrument *ip;
int i, nv, note, ch, prog, bk;
ch = e->channel;
if(channel[ch].special_sample > 0)
{
SpecialPatch *s;
s = special_patch[channel[ch].special_sample];
if(s == NULL)
{
ctl->cmsg(CMSG_WARNING, VERB_VERBOSE,
"Strange: Special patch %d is not installed",
channel[ch].special_sample);
return 0;
}
note = e->a + note_key_offset;
if(note < 0)
note = 0;
else if(note > 127)
note = 127;
return select_play_sample(s->sample, s->samples, note, vlist, e);
}
bk = channel[ch].bank;
if(ISDRUMCHANNEL(ch))
{
note = e->a;
instrument_map(channel[ch].mapID, &bk, ¬e);
if(!(ip = play_midi_load_instrument(1, bk, note)))
return 0; /* No instrument? Then we can't play. */
if(ip->type == INST_GUS && ip->samples != 1)
ctl->cmsg(CMSG_WARNING, VERB_VERBOSE,
"Strange: percussion instrument with %d samples!",
ip->samples);
if(ip->sample->note_to_use)
note = ip->sample->note_to_use;
i = vlist[0] = find_voice(e);
voice[i].orig_frequency = freq_table[note];
/* NRPN Coarse Pitch of Drum (GS) */
if(ISDRUMCHANNEL(ch) &&
channel[ch].drums[note] != NULL &&
channel[ch].drums[note]->note) {
voice[i].orig_frequency = freq_table[channel[ch].drums[note]->note];
}
voice[i].sample = ip->sample;
voice[i].status = VOICE_ON;
return 1;
}
prog = channel[ch].program;
if(prog == SPECIAL_PROGRAM)
ip = default_instrument;
else
{
instrument_map(channel[ch].mapID, &bk, &prog);
if(!(ip = play_midi_load_instrument(0, bk, prog)))
return 0; /* No instrument? Then we can't play. */
}
if(ip->sample->note_to_use)
note = ip->sample->note_to_use + note_key_offset;
else
note = e->a + note_key_offset;
if(note < 0)
note = 0;
else if(note > 127)
note = 127;
nv = select_play_sample(ip->sample, ip->samples, note, vlist, e);
/* Replace the sample if the sample is cached. */
if(!prescanning_flag)
{
if(ip->sample->note_to_use)
note = MIDI_EVENT_NOTE(e);
for(i = 0; i < nv; i++)
{
int j;
j = vlist[i];
if(!opt_realtime_playing && allocate_cache_size > 0 &&
!channel[ch].portamento)
{
voice[j].cache = resamp_cache_fetch(voice[j].sample, note);
if(voice[j].cache) /* cache hit */
voice[j].sample = voice[j].cache->resampled;
}
else
voice[j].cache = NULL;
}
}
return nv;
}
static void start_note(MidiEvent *e, int i, int vid, int cnt)
{
int j, ch, note, pan;
ch = e->channel;
note = MIDI_EVENT_NOTE(e);
voice[i].status = VOICE_ON;
voice[i].channel = ch;
voice[i].note = note;
voice[i].velocity = e->b;
voice[i].chorus_link = i; /* No link */
j = channel[ch].special_sample;
if(j == 0 || special_patch[j] == NULL)
voice[i].sample_offset = 0;
else
{
voice[i].sample_offset =
special_patch[j]->sample_offset << FRACTION_BITS;
if(voice[i].sample->modes & MODES_LOOPING)
{
if(voice[i].sample_offset > voice[i].sample->loop_end)
voice[i].sample_offset = voice[i].sample->loop_start;
}
else if(voice[i].sample_offset > voice[i].sample->data_length)
{
free_voice(i);
return;
}
}
voice[i].sample_increment=0; /* make sure it isn't negative */
voice[i].modulation_wheel=channel[ch].modulation_wheel;
voice[i].delay=0;
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -