2008-04-11 04:59:23 +00:00
|
|
|
/*
|
|
|
|
|
|
|
|
TiMidity -- Experimental MIDI to WAVE converter
|
|
|
|
Copyright (C) 1995 Tuukka Toivonen <toivonen@clinet.fi>
|
|
|
|
|
|
|
|
This library is free software; you can redistribute it and/or
|
|
|
|
modify it under the terms of the GNU Lesser General Public
|
|
|
|
License as published by the Free Software Foundation; either
|
|
|
|
version 2.1 of the License, or (at your option) any later version.
|
|
|
|
|
|
|
|
This library 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
|
|
|
|
Lesser General Public License for more details.
|
|
|
|
|
|
|
|
You should have received a copy of the GNU Lesser General Public
|
|
|
|
License along with this library; if not, write to the Free Software
|
|
|
|
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
|
|
|
|
|
|
|
|
playmidi.c -- random stuff in need of rearrangement
|
|
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <math.h>
|
|
|
|
|
|
|
|
#include "timidity.h"
|
|
|
|
|
|
|
|
namespace Timidity
|
|
|
|
{
|
|
|
|
|
|
|
|
void Renderer::reset_voices()
|
|
|
|
{
|
|
|
|
for (int i = 0; i < MAX_VOICES; i++)
|
2008-04-16 05:41:03 +00:00
|
|
|
{
|
2008-04-11 04:59:23 +00:00
|
|
|
voice[i].status = VOICE_FREE;
|
2008-04-16 05:41:03 +00:00
|
|
|
}
|
2008-04-11 04:59:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Process the Reset All Controllers event */
|
|
|
|
void Renderer::reset_controllers(int c)
|
|
|
|
{
|
|
|
|
channel[c].volume = (100 << 7) | 100;
|
|
|
|
channel[c].expression = 0x3fff;
|
|
|
|
channel[c].sustain = 0;
|
|
|
|
channel[c].pitchbend = 0x2000;
|
|
|
|
channel[c].pitchfactor = 0; /* to be computed */
|
2008-04-12 05:04:37 +00:00
|
|
|
channel[c].mono = 0;
|
|
|
|
channel[c].rpn = RPN_RESET;
|
|
|
|
channel[c].nrpn = RPN_RESET;
|
2008-04-11 04:59:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void Renderer::reset_midi()
|
|
|
|
{
|
|
|
|
for (int i = 0; i < MAXCHAN; i++)
|
|
|
|
{
|
|
|
|
reset_controllers(i);
|
|
|
|
/* The rest of these are unaffected by the Reset All Controllers event */
|
|
|
|
channel[i].program = default_program;
|
|
|
|
channel[i].panning = NO_PANNING;
|
|
|
|
channel[i].pitchsens = 200;
|
|
|
|
channel[i].bank = 0; /* tone bank or drum set */
|
|
|
|
}
|
|
|
|
reset_voices();
|
|
|
|
}
|
|
|
|
|
2008-04-13 03:23:33 +00:00
|
|
|
void Renderer::select_sample(int v, Instrument *ip, int vel)
|
2008-04-11 04:59:23 +00:00
|
|
|
{
|
2008-04-16 05:41:03 +00:00
|
|
|
double f, cdiff, diff;
|
2008-04-11 04:59:23 +00:00
|
|
|
int s, i;
|
|
|
|
Sample *sp, *closest;
|
|
|
|
|
|
|
|
s = ip->samples;
|
|
|
|
sp = ip->sample;
|
|
|
|
|
|
|
|
if (s == 1)
|
|
|
|
{
|
|
|
|
voice[v].sample = sp;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
f = voice[v].orig_frequency;
|
2008-04-13 03:23:33 +00:00
|
|
|
for (i = 0; i < s; i++)
|
|
|
|
{
|
|
|
|
if (sp->low_vel <= vel && sp->high_vel >= vel &&
|
|
|
|
sp->low_freq <= f && sp->high_freq >= f)
|
|
|
|
{
|
|
|
|
voice[v].sample = sp;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
sp++;
|
|
|
|
}
|
|
|
|
|
2008-04-11 04:59:23 +00:00
|
|
|
/*
|
2008-04-16 05:41:03 +00:00
|
|
|
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.) */
|
2008-04-11 04:59:23 +00:00
|
|
|
|
|
|
|
cdiff = 1e10;
|
|
|
|
closest = sp = ip->sample;
|
|
|
|
for (i = 0; i < s; i++)
|
|
|
|
{
|
2008-04-16 05:41:03 +00:00
|
|
|
diff = fabs(sp->root_freq - f);
|
2008-04-11 04:59:23 +00:00
|
|
|
if (diff < cdiff)
|
|
|
|
{
|
|
|
|
cdiff = diff;
|
|
|
|
closest = sp;
|
|
|
|
}
|
|
|
|
sp++;
|
|
|
|
}
|
|
|
|
voice[v].sample = closest;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
void Renderer::recompute_freq(int v)
|
|
|
|
{
|
|
|
|
Channel *ch = &channel[voice[v].channel];
|
|
|
|
int
|
|
|
|
sign = (voice[v].sample_increment < 0), /* for bidirectional loops */
|
|
|
|
pb = ch->pitchbend;
|
|
|
|
double a;
|
|
|
|
|
2008-04-16 05:41:03 +00:00
|
|
|
if (voice[v].sample->sample_rate == 0)
|
|
|
|
{
|
2008-04-11 04:59:23 +00:00
|
|
|
return;
|
2008-04-16 05:41:03 +00:00
|
|
|
}
|
2008-04-11 04:59:23 +00:00
|
|
|
|
2008-04-16 05:41:03 +00:00
|
|
|
if (voice[v].vibrato_control_ratio != 0)
|
2008-04-11 04:59:23 +00:00
|
|
|
{
|
|
|
|
/* This instrument has vibrato. Invalidate any precomputed
|
2008-04-16 05:41:03 +00:00
|
|
|
sample_increments. */
|
|
|
|
memset(voice[v].vibrato_sample_increment, 0, sizeof(voice[v].vibrato_sample_increment));
|
2008-04-11 04:59:23 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (pb == 0x2000 || pb < 0 || pb > 0x3FFF)
|
|
|
|
{
|
|
|
|
voice[v].frequency = voice[v].orig_frequency;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
pb -= 0x2000;
|
|
|
|
if (ch->pitchfactor == 0)
|
|
|
|
{
|
|
|
|
/* Damn. Somebody bent the pitch. */
|
|
|
|
ch->pitchfactor = pow(2.f, ((abs(pb) * ch->pitchsens) / (8191.f * 1200.f)));
|
|
|
|
}
|
|
|
|
if (pb < 0)
|
|
|
|
{
|
|
|
|
voice[v].frequency = voice[v].orig_frequency / ch->pitchfactor;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
voice[v].frequency = voice[v].orig_frequency * ch->pitchfactor;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
a = FSCALE(((double)(voice[v].sample->sample_rate) * voice[v].frequency) /
|
|
|
|
((double)(voice[v].sample->root_freq) * rate),
|
|
|
|
FRACTION_BITS);
|
|
|
|
|
|
|
|
if (sign)
|
|
|
|
a = -a; /* need to preserve the loop direction */
|
|
|
|
|
|
|
|
voice[v].sample_increment = (int)(a);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Renderer::recompute_amp(Voice *v)
|
|
|
|
{
|
|
|
|
int chan = v->channel;
|
|
|
|
int panning = v->panning;
|
|
|
|
double vol = channel[chan].volume / 16383.f;
|
2008-04-16 05:41:03 +00:00
|
|
|
double expr = channel[chan].expression / 16383.f;
|
2008-04-16 20:02:16 +00:00
|
|
|
double vel = v->velocity / 127.f;
|
|
|
|
double tempamp = calc_vol(vol * expr * vel) * v->sample->volume;
|
2008-04-11 04:59:23 +00:00
|
|
|
|
2008-04-16 05:41:03 +00:00
|
|
|
if (panning >= 0x1DBB && panning < 0x2244)
|
2008-04-11 04:59:23 +00:00
|
|
|
{
|
|
|
|
v->panned = PANNED_CENTER;
|
|
|
|
v->left_amp = (float)(tempamp * 0.70710678118654752440084436210485); // * sqrt(0.5)
|
|
|
|
}
|
2008-04-16 05:41:03 +00:00
|
|
|
else if (panning == 0)
|
2008-04-11 04:59:23 +00:00
|
|
|
{
|
|
|
|
v->panned = PANNED_LEFT;
|
|
|
|
v->left_amp = (float)tempamp;
|
|
|
|
}
|
2008-04-16 05:41:03 +00:00
|
|
|
else if (panning == 0x3FFF)
|
2008-04-11 04:59:23 +00:00
|
|
|
{
|
|
|
|
v->panned = PANNED_RIGHT;
|
|
|
|
v->left_amp = (float)tempamp; /* left_amp will be used */
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
double pan = panning / 16384.0;
|
|
|
|
v->panned = PANNED_MYSTERY;
|
|
|
|
v->left_amp = (float)(tempamp * sqrt(1.0 - pan));
|
|
|
|
v->right_amp = (float)(tempamp * sqrt(pan));
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-04-16 05:41:03 +00:00
|
|
|
void Renderer::kill_key_group(int i)
|
2008-04-11 04:59:23 +00:00
|
|
|
{
|
2008-04-16 05:41:03 +00:00
|
|
|
int j = voices;
|
2008-04-11 04:59:23 +00:00
|
|
|
|
2008-04-16 05:41:03 +00:00
|
|
|
if (voice[i].sample->key_group == 0)
|
2008-04-11 04:59:23 +00:00
|
|
|
{
|
2008-04-16 05:41:03 +00:00
|
|
|
return;
|
2008-04-11 04:59:23 +00:00
|
|
|
}
|
|
|
|
while (j--)
|
|
|
|
{
|
2008-04-16 05:41:03 +00:00
|
|
|
if (voice[j].status != VOICE_ON && voice[j].status != VOICE_SUSTAINED) continue;
|
2008-04-11 04:59:23 +00:00
|
|
|
if (i == j) continue;
|
|
|
|
if (voice[i].channel != voice[j].channel) continue;
|
2008-04-16 05:41:03 +00:00
|
|
|
if (voice[j].sample->key_group != voice[i].sample->key_group) continue;
|
|
|
|
kill_note(j);
|
2008-04-11 04:59:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-04-16 05:41:03 +00:00
|
|
|
float Renderer::calculate_scaled_frequency(Sample *sp, int note)
|
2008-04-11 04:59:23 +00:00
|
|
|
{
|
2008-04-16 05:41:03 +00:00
|
|
|
double scalednote = (note - sp->scale_note) * sp->scale_factor / 1024.0 + sp->scale_note;
|
|
|
|
return (float)note_to_freq(scalednote);
|
2008-04-11 04:59:23 +00:00
|
|
|
}
|
|
|
|
|
2008-04-16 05:41:03 +00:00
|
|
|
void Renderer::start_note(int chan, int note, int vel, int i)
|
2008-04-11 04:59:23 +00:00
|
|
|
{
|
|
|
|
Instrument *ip;
|
2008-04-16 05:41:03 +00:00
|
|
|
int bank = channel[chan].bank;
|
|
|
|
int prog = channel[chan].program;
|
2008-04-11 04:59:23 +00:00
|
|
|
|
2008-04-16 05:41:03 +00:00
|
|
|
if (ISDRUMCHANNEL(chan))
|
2008-04-11 04:59:23 +00:00
|
|
|
{
|
2008-04-16 05:41:03 +00:00
|
|
|
if (NULL == drumset[bank] || NULL == (ip = drumset[bank]->instrument[note]))
|
2008-04-11 04:59:23 +00:00
|
|
|
{
|
2008-04-16 05:41:03 +00:00
|
|
|
if (!(ip = drumset[0]->instrument[note]))
|
2008-04-11 04:59:23 +00:00
|
|
|
return; /* No instrument? Then we can't play. */
|
|
|
|
}
|
|
|
|
if (ip->type == INST_GUS && ip->samples != 1)
|
|
|
|
{
|
2008-04-12 05:04:37 +00:00
|
|
|
cmsg(CMSG_WARNING, VERB_VERBOSE,
|
2008-04-11 04:59:23 +00:00
|
|
|
"Strange: percussion instrument with %d samples!", ip->samples);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
2008-04-16 05:41:03 +00:00
|
|
|
if (channel[chan].program == SPECIAL_PROGRAM)
|
2008-04-11 04:59:23 +00:00
|
|
|
{
|
2008-04-16 05:41:03 +00:00
|
|
|
ip = default_instrument;
|
2008-04-11 04:59:23 +00:00
|
|
|
}
|
2008-04-16 05:41:03 +00:00
|
|
|
else if (NULL == tonebank[bank] || NULL == (ip = tonebank[bank]->instrument[prog]))
|
2008-04-11 04:59:23 +00:00
|
|
|
{
|
2008-04-16 05:41:03 +00:00
|
|
|
if (NULL == (ip = tonebank[0]->instrument[prog]))
|
2008-04-11 04:59:23 +00:00
|
|
|
return; /* No instrument? Then we can't play. */
|
|
|
|
}
|
|
|
|
}
|
2008-04-16 05:41:03 +00:00
|
|
|
if (ip->sample->scale_factor != 1024)
|
|
|
|
{
|
|
|
|
voice[i].orig_frequency = calculate_scaled_frequency(ip->sample, note & 0x7F);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
voice[i].orig_frequency = note_to_freq(note & 0x7F);
|
|
|
|
}
|
|
|
|
select_sample(i, ip, vel);
|
2008-04-11 04:59:23 +00:00
|
|
|
|
|
|
|
voice[i].status = VOICE_ON;
|
2008-04-16 05:41:03 +00:00
|
|
|
voice[i].channel = chan;
|
|
|
|
voice[i].note = note;
|
|
|
|
voice[i].velocity = vel;
|
2008-04-11 04:59:23 +00:00
|
|
|
voice[i].sample_offset = 0;
|
|
|
|
voice[i].sample_increment = 0; /* make sure it isn't negative */
|
|
|
|
|
|
|
|
voice[i].tremolo_phase = 0;
|
|
|
|
voice[i].tremolo_phase_increment = voice[i].sample->tremolo_phase_increment;
|
|
|
|
voice[i].tremolo_sweep = voice[i].sample->tremolo_sweep_increment;
|
|
|
|
voice[i].tremolo_sweep_position = 0;
|
|
|
|
|
|
|
|
voice[i].vibrato_sweep = voice[i].sample->vibrato_sweep_increment;
|
|
|
|
voice[i].vibrato_sweep_position = 0;
|
|
|
|
voice[i].vibrato_control_ratio = voice[i].sample->vibrato_control_ratio;
|
2008-04-16 05:41:03 +00:00
|
|
|
voice[i].vibrato_control_counter = voice[i].vibrato_phase = 0;
|
2008-04-11 04:59:23 +00:00
|
|
|
|
2008-04-16 05:41:03 +00:00
|
|
|
kill_key_group(i);
|
2008-04-11 04:59:23 +00:00
|
|
|
|
2008-04-16 05:41:03 +00:00
|
|
|
memset(voice[i].vibrato_sample_increment, 0, sizeof(voice[i].vibrato_sample_increment));
|
2008-04-11 04:59:23 +00:00
|
|
|
|
2008-04-16 05:41:03 +00:00
|
|
|
if (channel[chan].panning != NO_PANNING)
|
2008-04-11 04:59:23 +00:00
|
|
|
{
|
2008-04-16 05:41:03 +00:00
|
|
|
voice[i].panning = channel[chan].panning;
|
2008-04-11 04:59:23 +00:00
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
voice[i].panning = voice[i].sample->panning;
|
|
|
|
}
|
|
|
|
|
|
|
|
recompute_freq(i);
|
|
|
|
recompute_amp(&voice[i]);
|
2008-04-16 05:41:03 +00:00
|
|
|
if (voice[i].sample->modes & PATCH_NO_SRELEASE)
|
2008-04-11 04:59:23 +00:00
|
|
|
{
|
|
|
|
/* Ramp up from 0 */
|
|
|
|
voice[i].envelope_stage = ATTACK;
|
|
|
|
voice[i].envelope_volume = 0;
|
|
|
|
voice[i].control_counter = 0;
|
|
|
|
recompute_envelope(&voice[i]);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
voice[i].envelope_increment = 0;
|
|
|
|
}
|
|
|
|
apply_envelope_to_amp(&voice[i]);
|
|
|
|
}
|
|
|
|
|
|
|
|
void Renderer::kill_note(int i)
|
|
|
|
{
|
|
|
|
voice[i].status = VOICE_DIE;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Only one instance of a note can be playing on a single channel. */
|
|
|
|
void Renderer::note_on(int chan, int note, int vel)
|
|
|
|
{
|
|
|
|
int i = voices, lowest = -1;
|
|
|
|
float lv = 1e10, v;
|
|
|
|
|
|
|
|
while (i--)
|
|
|
|
{
|
|
|
|
if (voice[i].status == VOICE_FREE)
|
|
|
|
{
|
|
|
|
lowest = i; /* Can't get a lower volume than silence */
|
|
|
|
}
|
2008-04-16 05:41:03 +00:00
|
|
|
else if (voice[i].channel == chan && ((voice[i].note == note && !voice[i].sample->self_nonexclusive) || channel[chan].mono))
|
2008-04-11 04:59:23 +00:00
|
|
|
{
|
|
|
|
kill_note(i);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (lowest != -1)
|
|
|
|
{
|
|
|
|
/* Found a free voice. */
|
|
|
|
start_note(chan, note, vel, lowest);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Look for the decaying note with the lowest volume */
|
|
|
|
if (lowest == -1)
|
|
|
|
{
|
|
|
|
i = voices;
|
|
|
|
while (i--)
|
|
|
|
{
|
2008-04-16 05:41:03 +00:00
|
|
|
if (voice[i].status != VOICE_ON && voice[i].status != VOICE_DIE)
|
2008-04-11 04:59:23 +00:00
|
|
|
{
|
|
|
|
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)
|
|
|
|
{
|
|
|
|
/* This can still cause a click, but if we had a free voice to
|
2008-04-16 05:41:03 +00:00
|
|
|
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. */
|
2008-04-11 04:59:23 +00:00
|
|
|
|
|
|
|
cut_notes++;
|
|
|
|
voice[lowest].status = VOICE_FREE;
|
|
|
|
start_note(chan, note, vel, lowest);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
lost_notes++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Renderer::finish_note(int i)
|
|
|
|
{
|
2008-04-16 05:41:03 +00:00
|
|
|
if (voice[i].sample->modes & PATCH_NO_SRELEASE)
|
2008-04-11 04:59:23 +00:00
|
|
|
{
|
|
|
|
/* We need to get the envelope out of Sustain stage */
|
|
|
|
voice[i].envelope_stage = RELEASE;
|
|
|
|
voice[i].status = VOICE_OFF;
|
|
|
|
recompute_envelope(&voice[i]);
|
|
|
|
apply_envelope_to_amp(&voice[i]);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
/* Set status to OFF so resample_voice() will let this voice out
|
2008-04-16 05:41:03 +00:00
|
|
|
of its loop, if any. In any case, this voice dies when it
|
|
|
|
hits the end of its data (ofs >= data_length). */
|
2008-04-11 04:59:23 +00:00
|
|
|
voice[i].status = VOICE_OFF;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Renderer::note_off(int chan, int note, int vel)
|
|
|
|
{
|
2008-04-16 05:41:03 +00:00
|
|
|
int i = voices;
|
2008-04-11 04:59:23 +00:00
|
|
|
while (i--)
|
|
|
|
{
|
|
|
|
if (voice[i].status == VOICE_ON &&
|
|
|
|
voice[i].channel == chan &&
|
|
|
|
voice[i].note == note)
|
|
|
|
{
|
|
|
|
if (channel[chan].sustain)
|
|
|
|
{
|
|
|
|
voice[i].status = VOICE_SUSTAINED;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
finish_note(i);
|
|
|
|
}
|
2008-04-16 05:41:03 +00:00
|
|
|
if (!voice[i].sample->self_nonexclusive)
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
2008-04-11 04:59:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Process the All Notes Off event */
|
2008-04-16 05:41:03 +00:00
|
|
|
void Renderer::all_notes_off(int chan)
|
2008-04-11 04:59:23 +00:00
|
|
|
{
|
|
|
|
int i = voices;
|
|
|
|
while (i--)
|
|
|
|
{
|
2008-04-16 05:41:03 +00:00
|
|
|
if (voice[i].status == VOICE_ON && voice[i].channel == chan)
|
2008-04-11 04:59:23 +00:00
|
|
|
{
|
2008-04-16 05:41:03 +00:00
|
|
|
if (channel[chan].sustain)
|
2008-04-11 04:59:23 +00:00
|
|
|
{
|
|
|
|
voice[i].status = VOICE_SUSTAINED;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
finish_note(i);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Process the All Sounds Off event */
|
2008-04-16 05:41:03 +00:00
|
|
|
void Renderer::all_sounds_off(int chan)
|
2008-04-11 04:59:23 +00:00
|
|
|
{
|
|
|
|
int i = voices;
|
|
|
|
while (i--)
|
|
|
|
{
|
2008-04-16 05:41:03 +00:00
|
|
|
if (voice[i].channel == chan &&
|
2008-04-11 04:59:23 +00:00
|
|
|
voice[i].status != VOICE_FREE &&
|
|
|
|
voice[i].status != VOICE_DIE)
|
|
|
|
{
|
|
|
|
kill_note(i);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Renderer::adjust_pressure(int chan, int note, int amount)
|
|
|
|
{
|
|
|
|
int i = voices;
|
|
|
|
while (i--)
|
|
|
|
{
|
|
|
|
if (voice[i].status == VOICE_ON &&
|
|
|
|
voice[i].channel == chan &&
|
|
|
|
voice[i].note == note)
|
|
|
|
{
|
|
|
|
voice[i].velocity = amount;
|
|
|
|
recompute_amp(&voice[i]);
|
|
|
|
apply_envelope_to_amp(&voice[i]);
|
2008-04-16 05:41:03 +00:00
|
|
|
if (!(voice[i].sample->self_nonexclusive))
|
|
|
|
{
|
|
|
|
return;
|
|
|
|
}
|
2008-04-11 04:59:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-04-16 05:41:03 +00:00
|
|
|
void Renderer::adjust_panning(int chan)
|
2008-04-11 04:59:23 +00:00
|
|
|
{
|
|
|
|
int i = voices;
|
|
|
|
while (i--)
|
|
|
|
{
|
2008-04-16 05:41:03 +00:00
|
|
|
if ((voice[i].channel == chan) &&
|
2008-04-11 04:59:23 +00:00
|
|
|
(voice[i].status == VOICE_ON || voice[i].status == VOICE_SUSTAINED))
|
|
|
|
{
|
2008-04-16 05:41:03 +00:00
|
|
|
voice[i].panning = channel[chan].panning;
|
2008-04-11 04:59:23 +00:00
|
|
|
recompute_amp(&voice[i]);
|
|
|
|
apply_envelope_to_amp(&voice[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-04-16 05:41:03 +00:00
|
|
|
void Renderer::drop_sustain(int chan)
|
2008-04-11 04:59:23 +00:00
|
|
|
{
|
|
|
|
int i = voices;
|
|
|
|
while (i--)
|
|
|
|
{
|
2008-04-16 05:41:03 +00:00
|
|
|
if (voice[i].status == VOICE_SUSTAINED && voice[i].channel == chan)
|
|
|
|
{
|
2008-04-11 04:59:23 +00:00
|
|
|
finish_note(i);
|
2008-04-16 05:41:03 +00:00
|
|
|
}
|
2008-04-11 04:59:23 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-04-16 05:41:03 +00:00
|
|
|
void Renderer::adjust_pitchbend(int chan)
|
2008-04-11 04:59:23 +00:00
|
|
|
{
|
|
|
|
int i = voices;
|
|
|
|
while (i--)
|
|
|
|
{
|
2008-04-16 05:41:03 +00:00
|
|
|
if (voice[i].status != VOICE_FREE && voice[i].channel == chan)
|
2008-04-11 04:59:23 +00:00
|
|
|
{
|
|
|
|
recompute_freq(i);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-04-16 05:41:03 +00:00
|
|
|
void Renderer::adjust_volume(int chan)
|
2008-04-11 04:59:23 +00:00
|
|
|
{
|
|
|
|
int i = voices;
|
|
|
|
while (i--)
|
|
|
|
{
|
2008-04-16 05:41:03 +00:00
|
|
|
if (voice[i].channel == chan &&
|
2008-04-11 04:59:23 +00:00
|
|
|
(voice[i].status == VOICE_ON || voice[i].status == VOICE_SUSTAINED))
|
|
|
|
{
|
|
|
|
recompute_amp(&voice[i]);
|
|
|
|
apply_envelope_to_amp(&voice[i]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Renderer::HandleEvent(int status, int parm1, int parm2)
|
|
|
|
{
|
|
|
|
int command = status & 0xF0;
|
|
|
|
int chan = status & 0x0F;
|
|
|
|
|
|
|
|
switch (command)
|
|
|
|
{
|
|
|
|
case ME_NOTEON:
|
|
|
|
if (parm2 == 0)
|
|
|
|
{
|
|
|
|
note_off(chan, parm1, 0);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
note_on(chan, parm1, parm2);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ME_NOTEOFF:
|
|
|
|
note_off(chan, parm1, parm2);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ME_KEYPRESSURE:
|
|
|
|
adjust_pressure(chan, parm1, parm2);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ME_CONTROLCHANGE:
|
|
|
|
HandleController(chan, parm1, parm2);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ME_PROGRAM:
|
2008-04-16 05:41:03 +00:00
|
|
|
if (ISDRUMCHANNEL(chan))
|
|
|
|
{
|
2008-04-11 04:59:23 +00:00
|
|
|
/* Change drum set */
|
|
|
|
channel[chan].bank = parm1;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
channel[chan].program = parm1;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ME_CHANNELPRESSURE:
|
|
|
|
/* Unimplemented */
|
|
|
|
break;
|
|
|
|
|
|
|
|
case ME_PITCHWHEEL:
|
|
|
|
channel[chan].pitchbend = parm1 | (parm2 << 7);
|
|
|
|
channel[chan].pitchfactor = 0;
|
|
|
|
/* Adjust for notes already playing */
|
|
|
|
adjust_pitchbend(chan);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Renderer::HandleController(int chan, int ctrl, int val)
|
|
|
|
{
|
|
|
|
switch (ctrl)
|
|
|
|
{
|
|
|
|
/* These should be the SCC-1 tone bank switch
|
|
|
|
commands. I don't know why there are two, or
|
|
|
|
why the latter only allows switching to bank 0.
|
|
|
|
Also, some MIDI files use 0 as some sort of
|
|
|
|
continuous controller. This will cause lots of
|
|
|
|
warnings about undefined tone banks. */
|
|
|
|
case CTRL_BANK_SELECT:
|
2008-04-16 05:41:03 +00:00
|
|
|
channel[chan].bank = val;
|
2008-04-11 04:59:23 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case CTRL_BANK_SELECT+32:
|
2008-04-16 05:41:03 +00:00
|
|
|
if (val == 0)
|
2008-04-11 04:59:23 +00:00
|
|
|
{
|
2008-04-16 05:41:03 +00:00
|
|
|
channel[chan].bank = 0;
|
2008-04-11 04:59:23 +00:00
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case CTRL_VOLUME:
|
|
|
|
channel[chan].volume = (channel[chan].volume & 0x007F) | (val << 7);
|
|
|
|
adjust_volume(chan);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case CTRL_VOLUME+32:
|
|
|
|
channel[chan].volume = (channel[chan].volume & 0x3F80) | (val);
|
|
|
|
adjust_volume(chan);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case CTRL_EXPRESSION:
|
|
|
|
channel[chan].expression = (channel[chan].expression & 0x007F) | (val << 7);
|
|
|
|
adjust_volume(chan);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case CTRL_EXPRESSION+32:
|
|
|
|
channel[chan].expression = (channel[chan].expression & 0x3F80) | (val);
|
|
|
|
adjust_volume(chan);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case CTRL_PAN:
|
|
|
|
channel[chan].panning = (channel[chan].panning & 0x007F) | (val << 7);
|
2008-04-16 05:41:03 +00:00
|
|
|
adjust_panning(chan);
|
2008-04-11 04:59:23 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case CTRL_PAN+32:
|
|
|
|
channel[chan].panning = (channel[chan].panning & 0x3F80) | (val);
|
2008-04-16 05:41:03 +00:00
|
|
|
adjust_panning(chan);
|
2008-04-11 04:59:23 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case CTRL_SUSTAIN:
|
|
|
|
channel[chan].sustain = val;
|
|
|
|
if (val == 0)
|
|
|
|
{
|
|
|
|
drop_sustain(chan);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case CTRL_NRPN_LSB:
|
|
|
|
channel[chan].nrpn = (channel[chan].nrpn & 0x3F80) | (val);
|
|
|
|
channel[chan].nrpn_mode = true;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case CTRL_NRPN_MSB:
|
|
|
|
channel[chan].nrpn = (channel[chan].nrpn & 0x007F) | (val << 7);
|
|
|
|
channel[chan].nrpn_mode = true;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case CTRL_RPN_LSB:
|
|
|
|
channel[chan].rpn = (channel[chan].rpn & 0x3F80) | (val);
|
|
|
|
channel[chan].nrpn_mode = false;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case CTRL_RPN_MSB:
|
|
|
|
channel[chan].rpn = (channel[chan].rpn & 0x007F) | (val << 7);
|
|
|
|
channel[chan].nrpn_mode = false;
|
|
|
|
break;
|
|
|
|
|
|
|
|
case CTRL_DATA_ENTRY:
|
|
|
|
if (channel[chan].nrpn_mode)
|
|
|
|
{
|
|
|
|
DataEntryCoarseNRPN(chan, channel[chan].nrpn, val);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
DataEntryCoarseRPN(chan, channel[chan].rpn, val);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case CTRL_DATA_ENTRY+32:
|
|
|
|
if (channel[chan].nrpn_mode)
|
|
|
|
{
|
|
|
|
DataEntryFineNRPN(chan, channel[chan].nrpn, val);
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
DataEntryFineRPN(chan, channel[chan].rpn, val);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case CTRL_ALL_SOUNDS_OFF:
|
|
|
|
all_sounds_off(chan);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case CTRL_RESET_CONTROLLERS:
|
|
|
|
reset_controllers(chan);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case CTRL_ALL_NOTES_OFF:
|
|
|
|
all_notes_off(chan);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Renderer::DataEntryCoarseRPN(int chan, int rpn, int val)
|
|
|
|
{
|
|
|
|
switch (rpn)
|
|
|
|
{
|
|
|
|
case RPN_PITCH_SENS:
|
|
|
|
channel[chan].pitchsens = (channel[chan].pitchsens % 100) + (val * 100);
|
|
|
|
channel[chan].pitchfactor = 0;
|
|
|
|
break;
|
|
|
|
|
|
|
|
// TiMidity resets the pitch sensitivity when a song attempts to write to
|
|
|
|
// RPN_RESET. My docs tell me this is just a dummy value that is guaranteed
|
|
|
|
// to not cause future data entry to go anywhere until a new RPN is set.
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Renderer::DataEntryFineRPN(int chan, int rpn, int val)
|
|
|
|
{
|
|
|
|
switch (rpn)
|
|
|
|
{
|
|
|
|
case RPN_PITCH_SENS:
|
|
|
|
channel[chan].pitchsens = (channel[chan].pitchsens / 100) * 100 + val;
|
|
|
|
channel[chan].pitchfactor = 0;
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
void Renderer::DataEntryCoarseNRPN(int chan, int nrpn, int val)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
void Renderer::DataEntryFineNRPN(int chan, int nrpn, int val)
|
|
|
|
{
|
|
|
|
}
|
|
|
|
|
|
|
|
void Renderer::HandleLongMessage(const BYTE *data, int len)
|
|
|
|
{
|
|
|
|
// SysEx handling goes here.
|
|
|
|
}
|
|
|
|
|
|
|
|
void Renderer::Reset()
|
|
|
|
{
|
|
|
|
lost_notes = cut_notes = 0;
|
|
|
|
reset_midi();
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|