MidiSource: Fix some channels being mute after looping

This commit is contained in:
Jonas Kulla 2015-01-21 16:51:00 +01:00
parent 64a3ac3769
commit e339964076
2 changed files with 71 additions and 9 deletions

View File

@ -87,16 +87,12 @@ The syntax is: `--<option>=<value>`
Example: `./mkxp --gameFolder="my game" --vsync=true --fixedFramerate=60`
## Midi music (*ALPHA STATUS*)
## Midi music
mkxp doesn't come with a soundfont by default, so you will have to supply it yourself (set its path in the config). Playback has been tested and should work reasonably well with all RTP assets.
You can use this public domain soundfont: [GMGSx.sf2](https://www.dropbox.com/s/qxdvoxxcexsvn43/GMGSx.sf2?dl=0)
Known issues with midi playback:
* Some songs' instruments become mute after looping
## Fonts
In the RMXP version of RGSS, fonts are loaded directly from system specific search paths (meaning they must be installed to be available to games). Because this whole thing is a giant platform-dependent headache, I decided to implement the behavior Enterbrain thankfully added in VX Ace: loading fonts will automatically search a folder called "Fonts", which obeys the default searchpath behavior (ie. it can be located directly in the game folder, or an RTP).

View File

@ -55,7 +55,13 @@
#define TICK_FRAMES 32
#define BUF_TICKS (STREAM_BUF_SIZE / TICK_FRAMES)
#define DEFAULT_BPM 120
#define LOOP_MARKER 111
#define MAX_CHANNELS 16
#define CC_CTRL_VOLUME 7
#define CC_CTRL_EXPRESSION 11
#define CC_CTRL_LOOP 111
#define CC_VAL_DEFAULT 127
enum MidiEventType
{
@ -522,6 +528,59 @@ struct Track
}
};
/* Some songs use CC events for effects like fade-out,
* slowly decreasing a channel's volume to 0. The problem is that
* for looped songs, events are continuously fed into the synth
* without restoring those controls back to their default value.
* We can't reset them at the very beginning of tracks because it
* might cause audible interactions with notes which are still decaying
* past the end of the song. Therefore, we insert a fake CC event right
* after the first NoteOn event for each channel which resets the
* control to its default state while avoiding audible glitches.
* If there is already a CC event for this control before the first
* NoteOn event, we don't clobber it by not inserting our fake event. */
template<uint8_t ctrl>
struct CCResetter
{
bool chanHandled[MAX_CHANNELS];
CCResetter()
{
memset(&chanHandled, 0, sizeof(chanHandled));
}
void handleEvent(const MidiEvent &e, Track &track)
{
if (e.type != NoteOn && e.type != CC)
return;
uint8_t chan = e.e.chan.chan;
if (chanHandled[chan])
return;
if (e.type == CC && e.e.cc.ctrl == ctrl)
{
/* Don't clobber the existing CC value */
chanHandled[chan] = true;
return;
}
else if (e.type == NoteOn)
{
chanHandled[chan] = true;
MidiEvent re;
re.delta = 0;
re.type = CC;
re.e.cc.chan = chan;
re.e.cc.ctrl = ctrl;
re.e.cc.val = CC_VAL_DEFAULT;
track.appendEvent(re);
}
}
};
struct MidiSource : ALDataSource, MidiReadHandler
{
const uint16_t freq;
@ -530,6 +589,8 @@ struct MidiSource : ALDataSource, MidiReadHandler
int16_t synthBuf[BUF_TICKS*TICK_FRAMES*2];
std::vector<Track> tracks;
CCResetter<CC_CTRL_VOLUME> volReset;
CCResetter<CC_CTRL_EXPRESSION> expReset;
/* Index of longest track */
uint8_t longestI;
@ -631,7 +692,7 @@ struct MidiSource : ALDataSource, MidiReadHandler
playbackSpeed = TICK_FRAMES / (deltaLength * freq);
}
void activateEvent(MidiEvent &e)
void activateEvent(const MidiEvent &e)
{
int16_t key = e.e.note.key;
@ -705,9 +766,14 @@ struct MidiSource : ALDataSource, MidiReadHandler
void onMidiEvent(const MidiEvent &e, uint32_t absDelta)
{
assert(curTrack >= 0 && curTrack < (int16_t) tracks.size());
tracks[curTrack].appendEvent(e);
if (e.type == CC && e.e.cc.ctrl == LOOP_MARKER)
Track &track = tracks[curTrack];
track.appendEvent(e);
volReset.handleEvent(e, track);
expReset.handleEvent(e, track);
if (e.type == CC && e.e.cc.ctrl == CC_CTRL_LOOP)
loopDelta = absDelta;
}