View Issue Details

IDProjectCategoryView StatusLast Update
0001519OpenMPTFile Format Supportpublic2021-11-26 17:39
Reporterteimoso Assigned ToSaga Musix  
Status resolvedResolutionfixed 
Platformx64OSWindowsOS Version10
Product VersionOpenMPT 1.30.00.* (old testing) 
Target VersionOpenMPT / libopenmpt 0.6.0 (upgrade first)Fixed in VersionOpenMPT / libopenmpt 0.6.0 (upgrade first) 
Summary0001519: OPL Export: vgm/vgz files don't loop if Bxx/Cxx command goes to 1st pattern, 1st row of song

"Song" refers to the only song in a module file as well as one of multiple subsongs in a module file.

A song exported via "Export OPL Register Dump..." to the vgm or vgz formats doesn't loop if the Position Jump (Bxx) or Pattern Break (Cxx) command(s) causing the loop sends playback to the first row of the first pattern of the song. If the Bxx/Cxx command(s) go to any later position (e.g. first pattern & second row, or second pattern & first row) of the song, the exported file loops accordingly.

Steps To Reproduce
  1. Open an MPTM file with a song containing a loop. The loop should be done with Bxx and/or Cxx commands that send playback to the first pattern and first row of the song.
  2. Export the song to vgm or vgz format with "Export OPL Register Dump...".
  3. Play the file in a vgm/vgz player. The song should end at its last pattern and not loop.

A module file and matching set of vgm files are attached with this issue.

Additional Information

Module info

The included MPTM file demonstrates the issue and two workarounds across three subsongs. I don't know if exporting from an S3M file also exhibits the problem.

Subsong 1: One pattern, loop to 1st pattern & 1st row; bad export

The first subsong is a "standard" loop. It's a 24-row pattern, and C00 and B00 commands are placed at the last row. When exported, it doesn't loop.

Subsong 2: Two patterns, loop to 2nd pattern & 1st row; okay export

The second subsong is similar, but has two patterns: the first is an empty 1-row pattern; the second is a 24-row pattern, and a B03 command is placed at the last row (03h is the position of the song's second pattern). When exported, it loops.

Subsong 3: One pattern, loop to 1st pattern & 2nd row; okay export

The third subsong is similar to the first, but is a 25-row pattern; it has an additional, empty row at the start, and has C01 and B05 commands at the last row (05h is the position of the song's first pattern). When exported, it loops.

vgm/vgz player info

"VGMPlay" version 0.50.1 and "in_vgm" version 0.50.1 are affected (on my system). As of writing these are the latest releases available of each, as far as I know.

Both are here:

TagsNo tags attached.
Has the bug occurred in previous versions?
Tested code revision (in case you know it)r15998




2021-11-21 02:22

reporter   ~0004904 (2,042 bytes)
Saga Musix

Saga Musix

2021-11-21 11:21

administrator   ~0004905

Yes, this was a deliberate design decision because there isn't really a way for the engine to know right now if a song looped back to the first pattern, first row becuase it "legitimately" reach the end of the song or if there's a loop going back. Rather than having all the songs that are not even supposed to loop have a bogus loop (which I expect to be the majority of songs), I decided that it's easier for the people that need their full song to loop change the exported VGM file instead.

Saga Musix

Saga Musix

2021-11-21 11:30

administrator   ~0004906

Thinking about it again, we could probably query the SONG_BREAKTOROW flag after the export has finished to figre out if the song ended naturally. I'll look into that.



2021-11-21 11:31

reporter   ~0004907

I've also noticed incorrect sounding playback after exported files loop, like notes not being released or instruments' timbre/volume/pitch changing; is that expected? They're loops exported by OpenMPT, not manually edited ones.

Saga Musix

Saga Musix

2021-11-21 11:41

administrator   ~0004908

That may be a side effect of OpenMPT only writing a diff of registers (otherwise the exported files would be much bigger). I guess this could be worked around by emitting the complete current register state at the loop start.

Saga Musix

Saga Musix

2021-11-21 11:45

administrator   ~0004909

r16003 writes the loop data now even if a song loops back to its first position if the break-to-row flag was set.

I will look what I can do about the register state at the loop point, but it might not be quite as simple.

Saga Musix

Saga Musix

2021-11-21 11:55

administrator   ~0004910

r16004 contains an experimental change that dumps the complete register state on loop. I really don't know if that's better than the previous situation because you just replace one sort of artifact with another, like with the attached song. (285 bytes)


2021-11-21 12:00

reporter   ~0004911

In case it's helpful, here's a MPTM file with exported vgms demonstrating some loop anomalies. First subsong shows "Note Off" note commands on the first row seemingly not being processed; second subsong shows timbre corruption of a percussive instrument. (1,964 bytes)
Saga Musix

Saga Musix

2021-11-21 12:04

administrator   ~0004912

Yes, those would be fixed by r16004, because all channels will be reset to exactly the same state they were in at the first iteration of the loop. This is a compromise - as mentioned, it will cause some artifacts in the module I attached that weren't there previously, but there is no perfect way to handle this because VGM loops don't work like MOD loops where you can have an instrument keep playing across the loop boundary and then start modulating it (e.g. fade it out) after the loop start, unless this fade-out was already there on the first iteration of the loop.

Saga Musix

Saga Musix

2021-11-21 12:14

administrator   ~0004913

Okay, it doesn't quite fix the first subsong because there simply is no pre-existing register state on channels 2-4... This is more compelex to solve.

Saga Musix

Saga Musix

2021-11-21 12:47

administrator   ~0004914

I think I have a workaround for the first subsong. One not-so-nice thing about it is that it now increases the size of any looped VGM by 702 bytes for a full register dump.

Saga Musix

Saga Musix

2021-11-21 14:29

administrator   ~0004915

r16006 now writes the current state of all registers at loop start - to be precise, only those registers that are ever used in the song at all. So if the song only uses one channel, the register dump for the remaining 17 channels is omitted.

Saga Musix

Saga Musix

2021-11-22 18:24

administrator   ~0004917

r16005 now made non-looped songs loop as well, this was corrected in r16007.



2021-11-22 20:11

reporter   ~0004919

Meant to respond yesterday, sorry. Using r16007 (auto-update is very convenient)...

vgm-loop-sample (initial issue attachment) subsong 1 seems to loop properly, as do 2 and 3 and other song exports I tried, so I think this particular issue is resolved.

vgm-loop-sample2's subsongs also loop okay but sound abnormal at loop start, which is probably expected. Other songs I exported either sound fine or sound unusual for only a very brief time at loop start; I imagine the latter could be worked around with simple pattern modifications.

Thanks for looking into this.

Saga Musix

Saga Musix

2021-11-22 20:25

administrator   ~0004920

Last edited: 2021-11-22 20:26

Yes, that's the "you can't have your cake and eat it too" scenario: OpenMPT can either let the user take care of resetting all registers on loop start so that notes lingering from the loop end can keep playing (which is how it was before), or it can make sure that all registers are always in the same state at loop start as they are on the first run of the loop (the new behaviour). This is simply a limitation of how loops work in VGM. Either of the two scenarios can work but not both at the same time. The only remaining alternative would be to only reset the registers of each channel once there is a new note being hit on that channel, but that would make the code considerably more complex for very little gain (and you could still break it in various ways, such as executing a pitch slide at the start of the loop with different notes right before the loop start and right before the loop end - the pitch slide would always use the same pitch as the note before the loop start). I'd rather teach users to make sure that there's no instrument change across the loop point (or at least the instrument change has to be consistent before the loop start and before the loop end).

Issue History

Date Modified Username Field Change
2021-11-21 02:21 teimoso New Issue
2021-11-21 02:22 teimoso Note Added: 0004904
2021-11-21 02:22 teimoso File Added:
2021-11-21 09:56 teimoso Steps to Reproduce Updated
2021-11-21 09:56 teimoso Additional Information Updated
2021-11-21 11:21 Saga Musix Note Added: 0004905
2021-11-21 11:30 Saga Musix Note Added: 0004906
2021-11-21 11:31 teimoso Note Added: 0004907
2021-11-21 11:41 Saga Musix Note Added: 0004908
2021-11-21 11:45 Saga Musix Note Added: 0004909
2021-11-21 11:55 Saga Musix Note Added: 0004910
2021-11-21 11:55 Saga Musix File Added:
2021-11-21 11:55 Saga Musix Assigned To => Saga Musix
2021-11-21 11:55 Saga Musix Status new => feedback
2021-11-21 12:00 teimoso Note Added: 0004911
2021-11-21 12:00 teimoso File Added:
2021-11-21 12:00 teimoso Status feedback => assigned
2021-11-21 12:04 Saga Musix Note Added: 0004912
2021-11-21 12:04 Saga Musix Status assigned => feedback
2021-11-21 12:14 Saga Musix Note Added: 0004913
2021-11-21 12:47 Saga Musix Note Added: 0004914
2021-11-21 14:29 Saga Musix Note Added: 0004915
2021-11-22 18:24 Saga Musix Note Added: 0004917
2021-11-22 20:11 teimoso Note Added: 0004919
2021-11-22 20:11 teimoso Status feedback => assigned
2021-11-22 20:25 Saga Musix Status assigned => feedback
2021-11-22 20:25 Saga Musix Note Added: 0004920
2021-11-22 20:26 Saga Musix Note Edited: 0004920
2021-11-26 17:39 Saga Musix Status feedback => resolved
2021-11-26 17:39 Saga Musix Resolution open => fixed
2021-11-26 17:39 Saga Musix Fixed in Version => OpenMPT / libopenmpt 0.6.0 (upgrade first)
2021-11-26 17:39 Saga Musix Target Version => OpenMPT / libopenmpt 0.6.0 (upgrade first)