MidiSource: Fix some channels being mute after looping
This commit is contained in:
parent
64a3ac3769
commit
e339964076
|
@ -87,16 +87,12 @@ The syntax is: `--<option>=<value>`
|
||||||
|
|
||||||
Example: `./mkxp --gameFolder="my game" --vsync=true --fixedFramerate=60`
|
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.
|
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)
|
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
|
## 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).
|
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).
|
||||||
|
|
|
@ -55,7 +55,13 @@
|
||||||
#define TICK_FRAMES 32
|
#define TICK_FRAMES 32
|
||||||
#define BUF_TICKS (STREAM_BUF_SIZE / TICK_FRAMES)
|
#define BUF_TICKS (STREAM_BUF_SIZE / TICK_FRAMES)
|
||||||
#define DEFAULT_BPM 120
|
#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
|
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
|
struct MidiSource : ALDataSource, MidiReadHandler
|
||||||
{
|
{
|
||||||
const uint16_t freq;
|
const uint16_t freq;
|
||||||
|
@ -530,6 +589,8 @@ struct MidiSource : ALDataSource, MidiReadHandler
|
||||||
int16_t synthBuf[BUF_TICKS*TICK_FRAMES*2];
|
int16_t synthBuf[BUF_TICKS*TICK_FRAMES*2];
|
||||||
|
|
||||||
std::vector<Track> tracks;
|
std::vector<Track> tracks;
|
||||||
|
CCResetter<CC_CTRL_VOLUME> volReset;
|
||||||
|
CCResetter<CC_CTRL_EXPRESSION> expReset;
|
||||||
|
|
||||||
/* Index of longest track */
|
/* Index of longest track */
|
||||||
uint8_t longestI;
|
uint8_t longestI;
|
||||||
|
@ -631,7 +692,7 @@ struct MidiSource : ALDataSource, MidiReadHandler
|
||||||
playbackSpeed = TICK_FRAMES / (deltaLength * freq);
|
playbackSpeed = TICK_FRAMES / (deltaLength * freq);
|
||||||
}
|
}
|
||||||
|
|
||||||
void activateEvent(MidiEvent &e)
|
void activateEvent(const MidiEvent &e)
|
||||||
{
|
{
|
||||||
int16_t key = e.e.note.key;
|
int16_t key = e.e.note.key;
|
||||||
|
|
||||||
|
@ -705,9 +766,14 @@ struct MidiSource : ALDataSource, MidiReadHandler
|
||||||
void onMidiEvent(const MidiEvent &e, uint32_t absDelta)
|
void onMidiEvent(const MidiEvent &e, uint32_t absDelta)
|
||||||
{
|
{
|
||||||
assert(curTrack >= 0 && curTrack < (int16_t) tracks.size());
|
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;
|
loopDelta = absDelta;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue