Skip to content
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions include/mafm.h
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,9 @@ void _WM_MAFM_Reset(void *synth);
/* Translate a WildMIDI event to the synth. */
void _WM_MAFM_Event(void *synth, struct _mdi *mdi, struct _event *event);

/* Re-apply every channel's gain (after a WM_MO_LOG_VOLUME toggle). */
void _WM_MAFM_AdjustChannelVolumes(struct _mdi *mdi);

/* Nonzero while notes are still sounding (release tails). */
int _WM_MAFM_ActiveVoices(void *synth);

Expand Down
8 changes: 7 additions & 1 deletion include/sf2.h
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,13 @@ extern int _WM_SF2_Active(void);
/* per-mdi synth instances (voices private, sample data shared) */
extern void *_WM_SF2_NewSynth(uint16_t rate);
extern void _WM_SF2_FreeSynth(void *synth);
extern void _WM_SF2_Reset(void *synth);
extern void _WM_SF2_Reset(struct _mdi *mdi);

/* re-apply every channel's volume (after a reset, or a WM_MO_LOG_VOLUME toggle) */
extern void _WM_SF2_AdjustChannelVolumes(struct _mdi *mdi);

/* send every sounding voice into its release stage */
extern void _WM_SF2_ReleaseAll(void *synth);

/* translate a wildmidi event to the synth */
extern void _WM_SF2_Event(void *synth, struct _mdi *mdi, struct _event *event);
Expand Down
7 changes: 6 additions & 1 deletion src/f_hmi.c
Original file line number Diff line number Diff line change
Expand Up @@ -361,7 +361,12 @@ _WM_ParseNewHmi(const uint8_t *hmi_data, uint32_t hmi_size) {
smallest_delta = note[hmi_tmp].length;
}
} else {
_WM_midi_setup_noteoff(hmi_mdi, note[hmi_tmp].channel, j, 0);
/* zero length note: release it at once, as the
* countdown above reads a length of 0 as "not
* sounding". j is the note sweep's counter, left
* at 128 - the off has to name this note. */
_WM_midi_setup_noteoff(hmi_mdi, note[hmi_tmp].channel,
(uint8_t)(hmi_tmp - (i * 128)), 0);
}

} else {
Expand Down
11 changes: 10 additions & 1 deletion src/f_xmidi.c
Original file line number Diff line number Diff line change
Expand Up @@ -382,7 +382,16 @@ struct _mdi *_WM_ParseNewXmi(const uint8_t *xmi_data, uint32_t xmi_size) {

/* store length */
xmi_notelen[128 * xmi_ch + xmi_note] = xmi_tmpdata;
if ((xmi_tmpdata > 0) && ((xmi_lowestdelta == 0) || (xmi_tmpdata < xmi_lowestdelta))) {
if (xmi_tmpdata == 0) {
/* A zero length note never reaches the countdown
* above, where 0 means "not sounding", so it would
* hang until the next note on the same key turned
* it off - the descending triplet at 38s in TES:
* Arena's SUNNYDAY.XMI rings for 1.6s that way.
* Release it at once, which is what xmi2mid.c
* writes out for the same note. */
_WM_midi_setup_noteoff(xmi_mdi, xmi_ch, xmi_note, 0);
} else if ((xmi_lowestdelta == 0) || (xmi_tmpdata < xmi_lowestdelta)) {
xmi_lowestdelta = xmi_tmpdata;
}

Expand Down
13 changes: 13 additions & 0 deletions src/internal_midi.c
Original file line number Diff line number Diff line change
Expand Up @@ -1406,7 +1406,14 @@ void _WM_do_meta_endoftrack(struct _mdi *mdi, struct _event_data *data) {
/* The FM engine keeps its own voices; a sustaining one holds its level
* until key-off, so release them here too or a score that ends without
* keying every note off rings on to the caller's cut-off. */
#ifdef WILDMIDI_MAFM
if (mdi->mafm_synth) _WM_MAFM_ReleaseAll(mdi->mafm_synth);
#endif
#ifdef WILDMIDI_SF2
/* Same for the soundfont engine: without this a score that ends on a
* still-held note sustains it until the render loop's 10s tail cap. */
if (mdi->sf2_synth) _WM_SF2_ReleaseAll(mdi->sf2_synth);
#endif
return;
}

Expand Down Expand Up @@ -2149,6 +2156,12 @@ _WM_initMDI(void) {
#endif

_WM_do_sysex_gm_reset(mdi, NULL);
#ifdef WILDMIDI_SF2
/* the reset above only touches mdi's own channel state; push its volumes
into the synth too, so a channel that never sends CC7 still plays at
wildmidi's default rather than TSF's unity gain */
_WM_SF2_AdjustChannelVolumes(mdi);
#endif

return (mdi);
}
Expand Down
79 changes: 59 additions & 20 deletions src/mafm.c
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,9 @@ struct mafm_pcm_voice {
uint32_t end_pt; /* loop/end point, clamped to len */
double pos; /* fractional read position (samples) */
double step; /* native_fs / output_rate * pitch ratio */
float gain; /* volume * expression * velocity^2 */
float gain; /* chan_gain * vel_gain, what render uses */
float vel_gain; /* velocity^2, kept apart from the channel
* gain so a later CC7/CC11 can recompute */
float pan_l, pan_r; /* per-slot L/R gains from chan_pan CC */
int channel; /* -1 for ATR one-shots (no owner ch) */
int note;
Expand Down Expand Up @@ -136,6 +138,8 @@ struct mafm_synth {
uint8_t chan_program[16];
float chan_volume[16];
float chan_expression[16]; /* CC 0x0B; multiplied with volume */
float chan_gain[16]; /* volume x expression through the mixer's
* volume curve; see mafm_apply_channel_volume */
int chan_pitch[16]; /* 14-bit pitch wheel, centred 0x2000 */
uint8_t chan_pan[16]; /* 0..127 pan CC, 64 = centre; 0xff = unset */
uint8_t chan_modulation[16]; /* CC 1 mod wheel, drives a 5Hz pitch LFO */
Expand Down Expand Up @@ -660,6 +664,7 @@ void *_WM_MAFM_NewSynth(const uint8_t *smaf, uint32_t size, uint16_t rate) {
s->chan_program[i] = 0;
s->chan_volume[i] = 1.0f;
s->chan_expression[i] = 1.0f;
s->chan_gain[i] = 1.0f;
s->chan_pitch[i] = 0x2000;
s->chan_pan[i] = 0xff; /* sentinel: use patch pan_default */
}
Expand Down Expand Up @@ -722,6 +727,7 @@ void _WM_MAFM_Reset(void *synth) {
s->chan_program[i] = 0;
s->chan_volume[i] = 1.0f;
s->chan_expression[i] = 1.0f;
s->chan_gain[i] = 1.0f;
s->chan_pitch[i] = 0x2000;
s->chan_pan[i] = 0xff; /* sentinel: use patch pan_default */
s->chan_modulation[i] = 0;
Expand Down Expand Up @@ -829,7 +835,7 @@ static double pcm_env_advance(struct mafm_pcm_voice *pv) {
* rate (drums fix it to the played note; melodic voices use 60). params is
* the voice's env + loop config, or NULL for an unenvelope one-shot. */
static void mafm_start_pcm_full(struct mafm_synth *s, struct mafm_wave *w,
float gain, int channel, int note,
float vel_gain, int channel, int note,
int base_note,
const struct mafm_pcm_params *params) {
struct mafm_pcm_voice *pv = NULL;
Expand Down Expand Up @@ -860,7 +866,10 @@ static void mafm_start_pcm_full(struct mafm_synth *s, struct mafm_wave *w,
ratio = pow(2.0, ((double)(note - base_note) + bend_semitones) / 12.0);
pv->step = (double) fs / s->rate * ratio;
}
pv->gain = gain;
/* Keep the two halves apart: an ATR one-shot (channel < 0) has no owning
* channel and stays at its own gain, everything else tracks its channel. */
pv->vel_gain = vel_gain;
pv->gain = (channel >= 0) ? s->chan_gain[channel] * vel_gain : vel_gain;
pv->channel = channel;
pv->note = note;
/* Pan. channel < 0 stays centred; channel >= 0 tracks its chan_pan CC.
Expand Down Expand Up @@ -1006,7 +1015,7 @@ static void mafm_note_on(struct mafm_synth *s, int ch, int note, int vel) {
* converter emits vel=0 to mean "no explicit velocity", which we
* treat as 100. */
float pv = (vel ? (float) vel : 100.0f) / 127.0f;
float g = s->chan_volume[ch] * s->chan_expression[ch] * pv * pv;
float g = pv * pv; /* start_pcm_full folds in the channel gain */
/* Fixed pitch for drums (drum_note != 0) means playing the wave
* at native rate regardless of the incoming note. A melodic PCM
* voice takes root note 60, matching the "root=middle C" default
Expand Down Expand Up @@ -1035,8 +1044,9 @@ static void mafm_note_on(struct mafm_synth *s, int ch, int note, int vel) {
v = mafm_alloc_voice(s);
/* Volume x expression, matching the reference mixer. A file that keeps
* volume at 100/127 and rides expression for dynamics needs both to
* combine, otherwise the swells never reach the voice. */
_WM_MAFM_VoiceSetVolume(v, s->chan_volume[ch] * s->chan_expression[ch]);
* combine, otherwise the swells never reach the voice. See
* mafm_apply_channel_volume() for how chan_gain is derived. */
_WM_MAFM_VoiceSetVolume(v, s->chan_gain[ch]);
/* Squared velocity curve. A linear map made every mid-velocity note
* nearly full-scale and constantly pushed the limiter; squaring keeps the
* musical dynamic range and matches how the chip's own velocity table
Expand Down Expand Up @@ -1090,6 +1100,43 @@ static void mafm_clear_vibrato(struct mafm_synth *s, uint8_t ch) {
}
}

/* Push a channel's CC7 x CC11 gain onto every voice sounding on it, so volume
* swells and expression rides reach in-flight notes: without this a long note
* that started quiet stays quiet, missing the crescendo the score encodes.
* WM_MO_LOG_VOLUME squares the gain, the same curve the GUS mixer's
* dBm_volume table (40*log10(v/127)) and the SF2 backend use. */
static void mafm_apply_channel_volume(struct mafm_synth *s, struct _mdi *mdi,
uint8_t ch) {
float gain = s->chan_volume[ch] * s->chan_expression[ch];
int i;
if (mdi->extra_info.mixer_options & WM_MO_LOG_VOLUME) {
gain *= gain;
}
s->chan_gain[ch] = gain; /* note-on reads this, so new notes match */
for (i = 0; i < MAFM_POLYPHONY; i++) {
struct mafm_voice *v = &s->voices[i];
if (_WM_MAFM_VoiceActive(v) && v->channel == ch)
_WM_MAFM_VoiceSetVolume(v, gain);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
/* Sampled voices hold a flattened gain rather than reading the channel
* each sample, so they need the same update or a long PCM phrase would
* ignore every CC7/CC11 that arrives after its note-on. */
for (i = 0; i < MAFM_PCM_POOL; i++) {
struct mafm_pcm_voice *pv = &s->pcm[i];
if (pv->active && pv->channel == (int)ch)
pv->gain = gain * pv->vel_gain;
}
}

/* Re-apply every channel's gain, for a WM_MO_LOG_VOLUME toggle mid-playback. */
void _WM_MAFM_AdjustChannelVolumes(struct _mdi *mdi) {
uint8_t ch;
if (mdi->mafm_synth == NULL) return;
for (ch = 0; ch < 16; ch++) {
mafm_apply_channel_volume((struct mafm_synth *)mdi->mafm_synth, mdi, ch);
}
}

void _WM_MAFM_Event(void *synth, struct _mdi *mdi, struct _event *event) {
struct mafm_synth *s = (struct mafm_synth *) synth;
uint8_t ch = event->event_data.channel;
Expand Down Expand Up @@ -1143,22 +1190,11 @@ void _WM_MAFM_Event(void *synth, struct _mdi *mdi, struct _event *event) {
} break;
case ev_control_channel_volume:
case ev_control_channel_expression: {
/* Update ALL currently-sounding voices on this channel so volume
* swells / expression rides reach in-flight notes. Without this a
* long note that started at low volume stays low forever, missing
* the crescendo the score encodes as CC 7/11 rises. */
int j;
float v_gain;
if (event->evtype == ev_control_channel_volume)
s->chan_volume[ch] = (float)(val & 0x7F) / 127.0f;
else
s->chan_expression[ch] = (float)(val & 0x7F) / 127.0f;
v_gain = s->chan_volume[ch] * s->chan_expression[ch];
for (j = 0; j < MAFM_POLYPHONY; j++) {
struct mafm_voice *vp = &s->voices[j];
if (_WM_MAFM_VoiceActive(vp) && vp->channel == ch)
_WM_MAFM_VoiceSetVolume(vp, v_gain);
}
mafm_apply_channel_volume(s, mdi, ch);
} break;
case ev_control_channel_pan:
s->chan_pan[ch] = (uint8_t)(val & 0x7F);
Expand Down Expand Up @@ -1197,6 +1233,9 @@ void _WM_MAFM_Render(void *synth, int32_t *out, uint32_t frames) {
* below the 32767 cap to leave headroom for reverb / master volume. */
const double LIM_THRESHOLD = 30000.0;
const double LIM_RELEASE = 0.9999;
/* Applied after the limiter, so turning the master volume down does not
* change how hard the limiter works - only how loud its output is. */
const double master_vol = (double)_WM_MasterVolume / 1024.0;
uint32_t f, i;
/* Cache per-voice pan gains once per Render call: pan is a mix of the
* channel's pan CC and the voice's patch pan_default, both of which are
Expand Down Expand Up @@ -1267,8 +1306,8 @@ void _WM_MAFM_Render(void *synth, int32_t *out, uint32_t frames) {
l *= gain;
r *= gain;
}
out[f * 2] += (int32_t) l;
out[f * 2 + 1] += (int32_t) r;
out[f * 2] += (int32_t) (l * master_vol);
out[f * 2 + 1] += (int32_t) (r * master_vol);
}
}

Expand Down
20 changes: 14 additions & 6 deletions src/patches.c
Original file line number Diff line number Diff line change
Expand Up @@ -80,13 +80,21 @@ _WM_get_patch_data(struct _mdi *mdi, uint16_t patchid) {
WMIDI_UNUSED(mdi);

_WM_Lock(&_WM_patch_lock);
search_patch = _find_nearest_patch(patchid);
search_patch = _find_matched_patch(patchid);
if (search_patch == NULL && (patchid & 0xff00) != 0) {
/* Nothing at all in the requested bank: fall back to bank 0 rather
* than play silence, as a hardware synth does for an unknown bank.
* SMAF needs this - its scores select Yamaha's own voice banks (0x7c
* and friends), which no GUS/SF2 patch set defines, so without the
* fallback every SMAF file that has no custom FM voices is mute. */
/* A non-zero bank in a timidity.cfg is an overlay: it lists only the
* few programs that differ from bank 0 (eawpats' "bank 8" holds a
* single sine wave, "drumset 8" a single tambourine). Fall back to
* bank 0 for everything it does not define, or the nearest-patch
* search below would answer every request from that bank with its one
* unrelated instrument. This is also what makes SMAF audible: its
* scores select Yamaha's own voice banks (0x7c and friends), which no
* GUS/SF2 patch set defines at all. */
search_patch = _find_matched_patch(patchid & 0x00ff);
}
if (search_patch == NULL) {
/* Bank 0 has no such program either - a sparse patch set. Nearest
* program is still better than silence. */
search_patch = _find_nearest_patch(patchid & 0x00ff);
}
_WM_Unlock(&_WM_patch_lock);
Expand Down
Loading
Loading