- Reduced volume, expression, and panning controllers back to 7 bits.
- Added very basic Soundfont support to the internal TiMidity. Things missing: filter, LFOs, modulation envelope, chorus, reverb, and modulators. May or may not be compatible with TiMidity++'s soundfont extensions. - Added support for quoted strings to the TiMidity config parser. SVN r957 (trunk)
This commit is contained in:
parent
d5563fe478
commit
e64586d86f
16 changed files with 3285 additions and 674 deletions
|
|
@ -31,8 +31,6 @@
|
|||
namespace Timidity
|
||||
{
|
||||
|
||||
static const double log_of_2 = 0.69314718055994529;
|
||||
|
||||
void Renderer::reset_voices()
|
||||
{
|
||||
for (int i = 0; i < voices; i++)
|
||||
|
|
@ -44,8 +42,8 @@ void Renderer::reset_voices()
|
|||
/* Process the Reset All Controllers event */
|
||||
void Renderer::reset_controllers(int c)
|
||||
{
|
||||
channel[c].volume = (100 << 7) | 100;
|
||||
channel[c].expression = 0x3fff;
|
||||
channel[c].volume = 100;
|
||||
channel[c].expression = 127;
|
||||
channel[c].sustain = 0;
|
||||
channel[c].pitchbend = 0x2000;
|
||||
channel[c].pitchfactor = 0; /* to be computed */
|
||||
|
|
@ -68,55 +66,6 @@ void Renderer::reset_midi()
|
|||
reset_voices();
|
||||
}
|
||||
|
||||
void Renderer::select_sample(int v, Instrument *ip, int vel)
|
||||
{
|
||||
double f, cdiff, diff;
|
||||
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;
|
||||
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++;
|
||||
}
|
||||
|
||||
/*
|
||||
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 = 1e10;
|
||||
closest = sp = ip->sample;
|
||||
for (i = 0; i < s; i++)
|
||||
{
|
||||
diff = fabs(sp->root_freq - f);
|
||||
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];
|
||||
|
|
@ -210,37 +159,59 @@ void Renderer::recompute_amp(Voice *v)
|
|||
int chanvol = chan->volume;
|
||||
int chanexpr = chan->expression;
|
||||
|
||||
v->attenuation = (vol_table[(chanvol * chanexpr) / 2113407] * vol_table[v->velocity]) * ((127 + 64) / 12419775.f);
|
||||
}
|
||||
|
||||
void Renderer::compute_pan(int panning, float &left_offset, float &right_offset)
|
||||
{
|
||||
// Round the left- and right-most positions to their extremes, since
|
||||
// most songs only do coarse panning.
|
||||
if (panning < 128)
|
||||
if (v->sample->type == INST_GUS)
|
||||
{
|
||||
panning = 0;
|
||||
}
|
||||
else if (panning > 127*128)
|
||||
{
|
||||
panning = 32767;
|
||||
}
|
||||
|
||||
if (panning == 0)
|
||||
{
|
||||
left_offset = 0;
|
||||
right_offset = (float)-HUGE_VAL;
|
||||
}
|
||||
else if (panning == 32767)
|
||||
{
|
||||
left_offset = (float)-HUGE_VAL;
|
||||
right_offset = 0;
|
||||
v->attenuation = (vol_table[(chanvol * chanexpr) / 127] * vol_table[v->velocity]) * ((127 + 64) / 12419775.f);
|
||||
}
|
||||
else
|
||||
{
|
||||
double pan = panning * (1 / 32767.0);
|
||||
right_offset = (float)(log(pan) * (1 / (log_of_2 * 32)));
|
||||
left_offset = (float)(log(1 - pan) * (1 / (log_of_2 * 32)));
|
||||
// Implicit modulators from SF2 spec
|
||||
double velatten, cc7atten, cc11atten;
|
||||
|
||||
velatten = log10(127.0 / v->velocity);
|
||||
cc7atten = log10(127.0 / chanvol);
|
||||
cc11atten = log10(127.0 / chanexpr);
|
||||
v->attenuation = float(400 * (velatten + cc7atten + cc11atten)) + v->sample->initial_attenuation;
|
||||
}
|
||||
}
|
||||
|
||||
// Pan must be in the range [0,1]
|
||||
void Renderer::compute_pan(double pan, int type, float &left_offset, float &right_offset)
|
||||
{
|
||||
if (pan <= 0)
|
||||
{
|
||||
left_offset = 1;
|
||||
right_offset = 0;
|
||||
}
|
||||
else if (pan >= 127/128.0)
|
||||
{
|
||||
left_offset = 0;
|
||||
right_offset = 1;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (type == INST_GUS)
|
||||
{
|
||||
/* Original amp equation looks like this:
|
||||
* calc_gf1_amp(atten + offset)
|
||||
* which expands to:
|
||||
* 2^(16*(atten + offset) - 16)
|
||||
* Keeping in mind that 2^(x + y) == 2^x * 2^y, we can
|
||||
* rewrite this to avoid doing two pows in GF1Envelope::ApplyToAmp():
|
||||
* 2^(16*atten + 16*offset - 16)
|
||||
* 2^(16*atten - 16 + 16 * offset + 16 - 16)
|
||||
* 2^(16*atten - 16) * 2^(16*offset + 16 - 16)
|
||||
* 2^(16*atten - 16) * 2^(16*(offset + 1) - 16)
|
||||
* calc_gf1_amp(atten) * calc_gf1_amp(offset + 1)
|
||||
*/
|
||||
right_offset = (float)calc_gf1_amp((log(pan) * (1 / (log_of_2 * 32))) + 1);
|
||||
left_offset = (float)calc_gf1_amp((log(1 - pan) * (1 / (log_of_2 * 32))) + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
left_offset = (float)db_to_amp(-20 * log10(sqrt(1 - pan)));
|
||||
right_offset = (float)db_to_amp(-20 * log10(sqrt(pan)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -264,17 +235,112 @@ void Renderer::kill_key_group(int i)
|
|||
|
||||
float Renderer::calculate_scaled_frequency(Sample *sp, int note)
|
||||
{
|
||||
double scalednote = (note - sp->scale_note) * sp->scale_factor / 1024.0 + sp->scale_note;
|
||||
double scalednote = (note - sp->scale_note) * sp->scale_factor / 1024.0 + sp->scale_note + sp->tune * 0.01;
|
||||
return (float)note_to_freq(scalednote);
|
||||
}
|
||||
|
||||
void Renderer::start_note(int chan, int note, int vel, int i)
|
||||
bool Renderer::start_region(int chan, int note, int vel, Sample *sp, float f)
|
||||
{
|
||||
int voicenum;
|
||||
Voice *v;
|
||||
|
||||
voicenum = allocate_voice();
|
||||
if (voicenum < 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
v = &voice[voicenum];
|
||||
v->sample = sp;
|
||||
|
||||
if (sp->type == INST_GUS)
|
||||
{
|
||||
v->orig_frequency = f;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (sp->scale_factor != 1024)
|
||||
{
|
||||
v->orig_frequency = calculate_scaled_frequency(sp, note);
|
||||
}
|
||||
else if (sp->tune != 0)
|
||||
{
|
||||
v->orig_frequency = note_to_freq(note + sp->tune * 0.01);
|
||||
}
|
||||
else
|
||||
{
|
||||
v->orig_frequency = note_to_freq(note);
|
||||
}
|
||||
}
|
||||
|
||||
v->status = VOICE_RUNNING;
|
||||
v->channel = chan;
|
||||
v->note = note;
|
||||
v->velocity = vel;
|
||||
v->sample_offset = 0;
|
||||
v->sample_increment = 0; /* make sure it isn't negative */
|
||||
v->sample_count = 0;
|
||||
|
||||
v->tremolo_phase = 0;
|
||||
v->tremolo_phase_increment = v->sample->tremolo_phase_increment;
|
||||
v->tremolo_sweep = v->sample->tremolo_sweep_increment;
|
||||
v->tremolo_sweep_position = 0;
|
||||
|
||||
v->vibrato_sweep = v->sample->vibrato_sweep_increment;
|
||||
v->vibrato_sweep_position = 0;
|
||||
v->vibrato_control_ratio = v->sample->vibrato_control_ratio;
|
||||
v->vibrato_control_counter = v->vibrato_phase = 0;
|
||||
|
||||
kill_key_group(voicenum);
|
||||
|
||||
memset(v->vibrato_sample_increment, 0, sizeof(v->vibrato_sample_increment));
|
||||
|
||||
if (sp->type == INST_SF2)
|
||||
{
|
||||
// Channel pan is added to instrument pan.
|
||||
double pan;
|
||||
if (channel[chan].panning == NO_PANNING)
|
||||
{
|
||||
pan = (sp->panning + 500) / 1000.0;
|
||||
}
|
||||
else
|
||||
{
|
||||
pan = channel[chan].panning / 128.0 + sp->panning / 1000.0;
|
||||
}
|
||||
compute_pan(pan, sp->type, v->left_offset, v->right_offset);
|
||||
}
|
||||
else if (channel[chan].panning != NO_PANNING)
|
||||
{
|
||||
compute_pan(channel[chan].panning / 128.0, sp->type, v->left_offset, v->right_offset);
|
||||
}
|
||||
else
|
||||
{
|
||||
v->left_offset = v->sample->left_offset;
|
||||
v->right_offset = v->sample->right_offset;
|
||||
}
|
||||
|
||||
recompute_freq(voicenum);
|
||||
recompute_amp(v);
|
||||
v->control_counter = 0;
|
||||
|
||||
v->eg1.Init(this, v);
|
||||
|
||||
if (v->sample->modes & PATCH_LOOPEN)
|
||||
{
|
||||
v->status |= VOICE_LPE;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void Renderer::start_note(int chan, int note, int vel)
|
||||
{
|
||||
Instrument *ip;
|
||||
Sample *sp;
|
||||
int bank = channel[chan].bank;
|
||||
int prog = channel[chan].program;
|
||||
Voice *v = &voice[i];
|
||||
int i;
|
||||
float f;
|
||||
|
||||
note &= 0x7f;
|
||||
if (ISDRUMCHANNEL(chan))
|
||||
{
|
||||
if (NULL == drumset[bank] || NULL == (ip = drumset[bank]->instrument[note]))
|
||||
|
|
@ -282,7 +348,12 @@ void Renderer::start_note(int chan, int note, int vel, int i)
|
|||
if (!(ip = drumset[0]->instrument[note]))
|
||||
return; /* No instrument? Then we can't play. */
|
||||
}
|
||||
if (ip->type == INST_GUS && ip->samples != 1)
|
||||
assert(ip != MAGIC_LOAD_INSTRUMENT);
|
||||
if (ip == MAGIC_LOAD_INSTRUMENT)
|
||||
{
|
||||
return;
|
||||
}
|
||||
if (ip->samples != 1 && ip->sample->type == INST_GUS)
|
||||
{
|
||||
cmsg(CMSG_WARNING, VERB_VERBOSE,
|
||||
"Strange: percussion instrument with %d samples!", ip->samples);
|
||||
|
|
@ -299,62 +370,85 @@ void Renderer::start_note(int chan, int note, int vel, int i)
|
|||
if (NULL == (ip = tonebank[0]->instrument[prog]))
|
||||
return; /* No instrument? Then we can't play. */
|
||||
}
|
||||
assert(ip != MAGIC_LOAD_INSTRUMENT);
|
||||
if (ip == MAGIC_LOAD_INSTRUMENT)
|
||||
{
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (ip->sample->scale_factor != 1024)
|
||||
|
||||
if (NULL == ip->sample || ip->samples == 0)
|
||||
return; /* No samples? Then nothing to play. */
|
||||
|
||||
// For GF1 patches, scaling is based solely on the first
|
||||
// waveform in this layer.
|
||||
if (ip->sample->type == INST_GUS && ip->sample->scale_factor != 1024)
|
||||
{
|
||||
v->orig_frequency = calculate_scaled_frequency(ip->sample, note & 0x7F);
|
||||
f = calculate_scaled_frequency(ip->sample, note);
|
||||
}
|
||||
else
|
||||
{
|
||||
v->orig_frequency = note_to_freq(note & 0x7F);
|
||||
f = note_to_freq(note);
|
||||
}
|
||||
select_sample(i, ip, vel);
|
||||
|
||||
v->status = VOICE_RUNNING;
|
||||
v->channel = chan;
|
||||
v->note = note;
|
||||
v->velocity = vel;
|
||||
v->sample_offset = 0;
|
||||
v->sample_increment = 0; /* make sure it isn't negative */
|
||||
|
||||
v->tremolo_phase = 0;
|
||||
v->tremolo_phase_increment = voice[i].sample->tremolo_phase_increment;
|
||||
v->tremolo_sweep = voice[i].sample->tremolo_sweep_increment;
|
||||
v->tremolo_sweep_position = 0;
|
||||
|
||||
v->vibrato_sweep = voice[i].sample->vibrato_sweep_increment;
|
||||
v->vibrato_sweep_position = 0;
|
||||
v->vibrato_control_ratio = voice[i].sample->vibrato_control_ratio;
|
||||
v->vibrato_control_counter = voice[i].vibrato_phase = 0;
|
||||
|
||||
kill_key_group(i);
|
||||
|
||||
memset(v->vibrato_sample_increment, 0, sizeof(v->vibrato_sample_increment));
|
||||
|
||||
if (channel[chan].panning != NO_PANNING)
|
||||
if (ip->sample->type == INST_GUS)
|
||||
{
|
||||
v->left_offset = channel[chan].left_offset;
|
||||
v->right_offset = channel[chan].right_offset;
|
||||
/* We're more lenient with matching ranges for GUS patches, since the
|
||||
* official Gravis ones don't cover the full range of possible
|
||||
* frequencies for every instrument.
|
||||
*/
|
||||
if (ip->samples == 1)
|
||||
{ // If there's only one sample, definitely play it.
|
||||
start_region(chan, note, vel, ip->sample, f);
|
||||
}
|
||||
for (i = ip->samples, sp = ip->sample; i != 0; --i, ++sp)
|
||||
{
|
||||
// GUS patches don't have velocity ranges, so no need to compare against them.
|
||||
if (sp->low_freq <= f && sp->high_freq >= f)
|
||||
{
|
||||
if (i > 1 && (sp + 1)->low_freq <= f && (sp + 1)->high_freq >= f)
|
||||
{ /* If there is a range of contiguous regions that match our
|
||||
* desired frequency, the last one in that block is used.
|
||||
*/
|
||||
continue;
|
||||
}
|
||||
start_region(chan, note, vel, sp, f);
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (i == 0)
|
||||
{ /* Found nothing. Try again, but look for the one with the closest root frequency.
|
||||
* As per the suggestion in the original TiMidity function, this search uses
|
||||
* note values rather than raw frequencies.
|
||||
*/
|
||||
double cdiff = 1e10;
|
||||
double want_note = freq_to_note(f);
|
||||
Sample *closest = sp = ip->sample;
|
||||
for (i = ip->samples; i != 0; --i, ++sp)
|
||||
{
|
||||
double diff = fabs(freq_to_note(sp->root_freq) - want_note);
|
||||
if (diff < cdiff)
|
||||
{
|
||||
cdiff = diff;
|
||||
closest = sp;
|
||||
}
|
||||
}
|
||||
start_region(chan, note, vel, closest, f);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
v->left_offset = v->sample->left_offset;
|
||||
v->right_offset = v->sample->right_offset;
|
||||
}
|
||||
|
||||
recompute_freq(i);
|
||||
recompute_amp(v);
|
||||
|
||||
/* Ramp up from 0 */
|
||||
v->envelope_stage = ATTACK;
|
||||
v->envelope_volume = 0;
|
||||
v->control_counter = 0;
|
||||
recompute_envelope(v);
|
||||
apply_envelope_to_amp(v);
|
||||
|
||||
if (v->sample->modes & PATCH_LOOPEN)
|
||||
{
|
||||
v->status |= VOICE_LPE;
|
||||
for (i = ip->samples, sp = ip->sample; i != 0; --i, ++sp)
|
||||
{
|
||||
if ((sp->low_vel <= vel && sp->high_vel >= vel &&
|
||||
sp->low_freq <= f && sp->high_freq >= f))
|
||||
{
|
||||
if (!start_region(chan, note, vel, sp, f))
|
||||
{ // Ran out of voices
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -369,7 +463,53 @@ void Renderer::kill_note(int i)
|
|||
}
|
||||
}
|
||||
|
||||
/* Only one instance of a note can be playing on a single channel. */
|
||||
int Renderer::allocate_voice()
|
||||
{
|
||||
int i, lowest;
|
||||
float lv, v;
|
||||
|
||||
for (i = 0; i < voices; ++i)
|
||||
{
|
||||
if (!(voice[i].status & VOICE_RUNNING))
|
||||
{
|
||||
return i; /* Can't get a lower volume than silence */
|
||||
}
|
||||
}
|
||||
|
||||
/* Look for the decaying note with the lowest volume */
|
||||
lowest = -1;
|
||||
lv = 1e10;
|
||||
i = voices;
|
||||
while (i--)
|
||||
{
|
||||
if ((voice[i].status & VOICE_RELEASING) && !(voice[i].status & VOICE_STOPPING))
|
||||
{
|
||||
v = voice[i].attenuation;
|
||||
if (v < lv)
|
||||
{
|
||||
lv = v;
|
||||
lowest = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (lowest >= 0)
|
||||
{
|
||||
/* 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++;
|
||||
voice[lowest].status = 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
lost_notes++;
|
||||
}
|
||||
return lowest;
|
||||
}
|
||||
|
||||
void Renderer::note_on(int chan, int note, int vel)
|
||||
{
|
||||
if (vel == 0)
|
||||
|
|
@ -378,16 +518,12 @@ void Renderer::note_on(int chan, int note, int vel)
|
|||
return;
|
||||
}
|
||||
|
||||
int i = voices, lowest = -1;
|
||||
float lv = 1e10, v;
|
||||
int i = voices;
|
||||
|
||||
/* Only one instance of a note can be playing on a single channel. */
|
||||
while (i--)
|
||||
{
|
||||
if (!(voice[i].status & VOICE_RUNNING))
|
||||
{
|
||||
lowest = i; /* Can't get a lower volume than silence */
|
||||
}
|
||||
else if (voice[i].channel == chan && ((voice[i].note == note && !voice[i].sample->self_nonexclusive) || channel[chan].mono))
|
||||
if (voice[i].channel == chan && ((voice[i].note == note && !voice[i].sample->self_nonexclusive) || channel[chan].mono))
|
||||
{
|
||||
if (channel[chan].mono)
|
||||
{
|
||||
|
|
@ -400,46 +536,7 @@ void Renderer::note_on(int chan, int note, int vel)
|
|||
}
|
||||
}
|
||||
|
||||
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--)
|
||||
{
|
||||
if ((voice[i].status & VOICE_RELEASING) && !(voice[i].status & VOICE_STOPPING))
|
||||
{
|
||||
v = voice[i].attenuation;
|
||||
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. */
|
||||
|
||||
cut_notes++;
|
||||
voice[lowest].status = 0;
|
||||
start_note(chan, note, vel, lowest);
|
||||
}
|
||||
else
|
||||
{
|
||||
lost_notes++;
|
||||
}
|
||||
start_note(chan, note, vel);
|
||||
}
|
||||
|
||||
void Renderer::finish_note(int i)
|
||||
|
|
@ -455,23 +552,8 @@ void Renderer::finish_note(int i)
|
|||
{
|
||||
v->status &= ~VOICE_LPE; /* sampled release */
|
||||
}
|
||||
if (!(v->sample->modes & PATCH_NO_SRELEASE) || (v->sample->modes & PATCH_FAST_REL))
|
||||
{
|
||||
/* ramp out to minimum volume with rate from final release stage */
|
||||
v->envelope_stage = RELEASEC;
|
||||
recompute_envelope(v);
|
||||
// Get rate from the final release ramp, but force the target to 0.
|
||||
v->envelope_target = 0;
|
||||
v->envelope_increment = -v->sample->envelope_rate[RELEASEC];
|
||||
}
|
||||
else if (v->sample->modes & PATCH_SUSTAIN)
|
||||
{
|
||||
if (v->envelope_stage < RELEASE)
|
||||
{
|
||||
v->envelope_stage = RELEASE;
|
||||
}
|
||||
recompute_envelope(v);
|
||||
}
|
||||
v->eg1.Release(v);
|
||||
v->eg2.Release(v);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -554,15 +636,19 @@ void Renderer::adjust_pressure(int chan, int note, int amount)
|
|||
void Renderer::adjust_panning(int chan)
|
||||
{
|
||||
Channel *chanp = &channel[chan];
|
||||
compute_pan(chanp->panning, chanp->left_offset, chanp->right_offset);
|
||||
int i = voices;
|
||||
while (i--)
|
||||
{
|
||||
if ((voice[i].channel == chan) && (voice[i].status & VOICE_RUNNING))
|
||||
Voice *v = &voice[i];
|
||||
if ((v->channel == chan) && (v->status & VOICE_RUNNING))
|
||||
{
|
||||
voice[i].left_offset = chanp->left_offset;
|
||||
voice[i].right_offset = chanp->right_offset;
|
||||
apply_envelope_to_amp(&voice[i]);
|
||||
double pan = chanp->panning / 128.0;
|
||||
if (v->sample->type == INST_SF2)
|
||||
{ // Add instrument pan to channel pan.
|
||||
pan += v->sample->panning / 500.0;
|
||||
}
|
||||
compute_pan(pan, v->sample->type, v->left_offset, v->right_offset);
|
||||
apply_envelope_to_amp(v);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -674,32 +760,17 @@ void Renderer::HandleController(int chan, int ctrl, int val)
|
|||
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);
|
||||
channel[chan].volume = 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);
|
||||
channel[chan].expression = val;
|
||||
adjust_volume(chan);
|
||||
break;
|
||||
|
||||
case CTRL_PAN:
|
||||
channel[chan].panning = (channel[chan].panning & 0x007F) | (val << 7);
|
||||
adjust_panning(chan);
|
||||
break;
|
||||
|
||||
case CTRL_PAN+32:
|
||||
channel[chan].panning = (channel[chan].panning & 0x3F80) | (val);
|
||||
channel[chan].panning = val;
|
||||
adjust_panning(chan);
|
||||
break;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue