Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
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
60 changes: 43 additions & 17 deletions src/mafm.c
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,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 +662,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 +725,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 @@ -1006,7 +1010,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 = s->chan_gain[ch] * pv * pv;
/* 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 +1039,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 +1095,35 @@ 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.
}

/* 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 +1177,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 +1220,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 +1293,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
64 changes: 56 additions & 8 deletions src/sf2.c
Original file line number Diff line number Diff line change
Expand Up @@ -104,13 +104,38 @@ int _WM_SF2_Active(void) {
return (WM_sf2 != NULL);
}

/* Channel volume, using wildmidi's own curves rather than tsf's cubic
* default, so WM_MO_LOG_VOLUME does the same thing here as it does for the
* GUS mixer. The linear curve is _WM_lin_volume[v]/1024 == v/127; the log
* curve is the MIDI2 table dBm_volume[v] == 40*log10(v/127), whose gain
* 10^(dBm/20) is just (v/127) squared. */
static void WM_SF2_ChannelVolume(tsf *f, struct _mdi *mdi, uint8_t ch,
int volume, int expression) {
float gain = (float)((volume * expression) / 127) / 127.0f;
if (mdi->extra_info.mixer_options & WM_MO_LOG_VOLUME) {
gain *= gain;
}
tsf_channel_set_volume(f, ch, gain);
}

static void WM_SF2_InitChannels(tsf *f) {
int ch;
for (ch = 0; ch < 16; ch++) {
tsf_channel_set_bank_preset(f, ch, (ch == 9) ? 128 : 0, 0);
}
}

/* (Re)apply every channel's volume from the mdi's own state. Needed after a
* reset and whenever WM_MO_LOG_VOLUME is toggled mid-playback. */
void _WM_SF2_AdjustChannelVolumes(struct _mdi *mdi) {
uint8_t ch;
if (mdi->sf2_synth == NULL) return;
for (ch = 0; ch < 16; ch++) {
WM_SF2_ChannelVolume((tsf *)mdi->sf2_synth, mdi, ch,
mdi->channel[ch].volume, mdi->channel[ch].expression);
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

void *_WM_SF2_NewSynth(uint16_t rate) {
tsf *f;
_WM_Lock(&_WM_sf2_lock);
Expand All @@ -130,14 +155,25 @@ void _WM_SF2_FreeSynth(void *synth) {
}
}

void _WM_SF2_Reset(void *synth) {
tsf *f = (tsf *)synth;
void _WM_SF2_Reset(struct _mdi *mdi) {
tsf *f = (tsf *)mdi->sf2_synth;
int ch;
if (f == NULL) return;
tsf_reset(f);
for (ch = 0; ch < 16; ch++) {
tsf_channel_midi_control(f, ch, 121, 0); /* reset controllers */
}
WM_SF2_InitChannels(f);
/* Callers reset the mdi's own channel state separately (and afterwards),
so seed the gains from _WM_do_sysex_gm_reset()'s defaults, not from
whatever mdi->channel still holds. */
for (ch = 0; ch < 16; ch++) {
WM_SF2_ChannelVolume(f, mdi, (uint8_t)ch, 100, 127);
}
}

void _WM_SF2_ReleaseAll(void *synth) {
tsf_note_off_all((tsf *)synth);
}

int _WM_SF2_ActiveVoices(void *synth) {
Expand Down Expand Up @@ -179,7 +215,8 @@ void _WM_SF2_Event(void *synth, struct _mdi *mdi, struct _event *event) {
tsf_channel_midi_control(f, ch, 6, val & 0x7F);
break;
case ev_control_channel_volume:
tsf_channel_midi_control(f, ch, 7, val & 0x7F);
/* do_event() has not run yet, so pass the new value explicitly */
WM_SF2_ChannelVolume(f, mdi, ch, val & 0x7F, mdi->channel[ch].expression);
break;
case ev_control_channel_balance:
tsf_channel_midi_control(f, ch, 8, val & 0x7F);
Expand All @@ -188,7 +225,7 @@ void _WM_SF2_Event(void *synth, struct _mdi *mdi, struct _event *event) {
tsf_channel_midi_control(f, ch, 10, val & 0x7F);
break;
case ev_control_channel_expression:
tsf_channel_midi_control(f, ch, 11, val & 0x7F);
WM_SF2_ChannelVolume(f, mdi, ch, mdi->channel[ch].volume, val & 0x7F);
break;
case ev_control_data_entry_fine:
tsf_channel_midi_control(f, ch, 38, val & 0x7F);
Expand All @@ -213,6 +250,9 @@ void _WM_SF2_Event(void *synth, struct _mdi *mdi, struct _event *event) {
break;
case ev_control_channel_controllers_off:
tsf_channel_midi_control(f, ch, 121, val & 0x7F);
/* CC121 puts tsf's own volume back to unity; restore ours. Like
_WM_do_control_channel_controllers_off(), CC7 survives, CC11 does not. */
WM_SF2_ChannelVolume(f, mdi, ch, mdi->channel[ch].volume, 127);
break;
case ev_control_channel_notes_off:
tsf_channel_midi_control(f, ch, 123, val & 0x7F);
Expand All @@ -226,23 +266,31 @@ void _WM_SF2_Event(void *synth, struct _mdi *mdi, struct _event *event) {
case ev_sysex_gm_reset:
case ev_sysex_roland_reset:
case ev_sysex_yamaha_reset:
_WM_SF2_Reset(f);
_WM_SF2_Reset(mdi);
break;
default: /* meta/timing events don't reach the synth */
break;
}
}

/* Headroom, matching VOL_DIVISOR in internal_midi.c: a soundfont renders a
* single note at full velocity close to full scale, so any busy score summed
* at unity gain clips hard. */
#define SF2_VOL_DIVISOR 4.0f

void _WM_SF2_Render(void *synth, int32_t *out, uint32_t frames) {
tsf *f = (tsf *)synth;
short buf[256 * 2];
float buf[256 * 2];
/* Render float, not short: tsf_render_short() clamps to int16 itself, so
scaling its output afterwards would only make the clipping quieter. */
const float gain = (32767.0f * (float)_WM_MasterVolume / 1024.0f) / SF2_VOL_DIVISOR;
uint32_t n, i;

while (frames) {
n = (frames > 256) ? 256 : frames;
tsf_render_short(f, buf, (int)n, 0);
tsf_render_float(f, buf, (int)n, 0);
for (i = 0; i < n * 2; i++) {
out[i] += buf[i];
out[i] += (int32_t)(buf[i] * gain);
}
out += n * 2;
frames -= n;
Expand Down
14 changes: 10 additions & 4 deletions src/wildmidi_lib.c
Original file line number Diff line number Diff line change
Expand Up @@ -2090,7 +2090,7 @@ WM_SYMBOL int WildMidi_FastSeek(midi * handle, unsigned long int *sample_pos) {
#ifdef WILDMIDI_SF2
/* Rewind TSF too so replayed events rebuild its state from scratch. */
if (mdi->sf2_synth) {
_WM_SF2_Reset(mdi->sf2_synth);
_WM_SF2_Reset(mdi);
}
#endif
#ifdef WILDMIDI_MAFM
Expand Down Expand Up @@ -2217,7 +2217,7 @@ WM_SYMBOL int WildMidi_SongSeek (midi * handle, int8_t nextsong) {
event = mdi->events;
_WM_ResetToStart((struct _mdi *) handle);
#ifdef WILDMIDI_SF2
if (mdi->sf2_synth) _WM_SF2_Reset(mdi->sf2_synth);
if (mdi->sf2_synth) _WM_SF2_Reset(mdi);
#endif
#ifdef WILDMIDI_MAFM
if (mdi->mafm_synth) _WM_MAFM_Reset(mdi->mafm_synth);
Expand Down Expand Up @@ -2254,7 +2254,7 @@ WM_SYMBOL int WildMidi_SongSeek (midi * handle, int8_t nextsong) {
event = mdi->events;
_WM_ResetToStart((struct _mdi *) handle);
#ifdef WILDMIDI_SF2
if (mdi->sf2_synth) _WM_SF2_Reset(mdi->sf2_synth);
if (mdi->sf2_synth) _WM_SF2_Reset(mdi);
#endif
#ifdef WILDMIDI_MAFM
if (mdi->mafm_synth) _WM_MAFM_Reset(mdi->mafm_synth);
Expand Down Expand Up @@ -2341,7 +2341,7 @@ static int WM_GetOutput_SF2(midi * handle, int8_t *buffer, uint32_t size) {
event->do_event(mdi, &event->event_data);
if ((mdi->extra_info.mixer_options & WM_MO_LOOP) && (event[0].evtype == ev_meta_endoftrack) && !end_encountered) {
end_encountered = 1; /* Avoid an infinite loop. */
_WM_SF2_Reset(mdi->sf2_synth);
_WM_SF2_Reset(mdi);
_WM_ResetToStart(mdi);
event = mdi->current_event;
} else {
Expand Down Expand Up @@ -2646,6 +2646,12 @@ WM_SYMBOL int WildMidi_SetOption(midi * handle, uint16_t options, uint16_t setti
if (options & WM_MO_LOG_VOLUME) {
_WM_AdjustChannelVolumes(mdi, 16); /* Settings greater than 15
adjusts all channels */
#ifdef WILDMIDI_SF2
_WM_SF2_AdjustChannelVolumes(mdi);
#endif
#ifdef WILDMIDI_MAFM
_WM_MAFM_AdjustChannelVolumes(mdi);
#endif
} else if (options & WM_MO_REVERB) {
_WM_reset_reverb(mdi->reverb);
}
Expand Down
Loading
Loading