View Issue Details
ID | Project | Category | View Status | Date Submitted | Last Update |
---|---|---|---|---|---|
0000741 | OpenMPT | File Format Support | public | 2016-01-09 19:41 | 2016-07-31 12:47 |
Reporter | ncovert | Assigned To | Saga Musix | ||
Priority | normal | Severity | feature | Reproducibility | N/A |
Status | resolved | Resolution | fixed | ||
Platform | x64 | OS | Windows | OS Version | 7 |
Product Version | OpenMPT 1.25.04.00 / libopenmpt 0.2-beta16 (upgrade first) | ||||
Target Version | OpenMPT 1.26.01.00 / libopenmpt 0.2-beta17 (upgrade first) | Fixed in Version | OpenMPT 1.26.01.00 / libopenmpt 0.2-beta17 (upgrade first) | ||
Summary | 0000741: Add support for SoundFX modules | ||||
Description | SoundFX is a Soundtracker clone. It saves in the SFX file format. It is similar to MOD, though I believe the C-5 sample rate is different and it allows for a default speed/tempo other than 125/6, so it may need to be converted to S3M when opened in MPT. | ||||
Tags | No tags attached. | ||||
Attached Files | sfx_r4.patch (15,283 bytes)
diff --git OpenMPT/build/android_ndk/Android-minimp3-stbvorbis.mk OpenMPT/build/android_ndk/Android-minimp3-stbvorbis.mk index 7ec6552..4c0be60 100644 --- OpenMPT/build/android_ndk/Android-minimp3-stbvorbis.mk +++ OpenMPT/build/android_ndk/Android-minimp3-stbvorbis.mk @@ -73,6 +73,7 @@ LOCAL_SRC_FILES := \ soundlib/Load_psm.cpp \ soundlib/Load_ptm.cpp \ soundlib/Load_s3m.cpp \ + soundlib/Load_sfx.cpp \ soundlib/Load_stm.cpp \ soundlib/Load_ult.cpp \ soundlib/Load_umx.cpp \ diff --git OpenMPT/build/android_ndk/Android-mpg123-vorbis.mk OpenMPT/build/android_ndk/Android-mpg123-vorbis.mk index 0c09b5e..0b1d8e6 100644 --- OpenMPT/build/android_ndk/Android-mpg123-vorbis.mk +++ OpenMPT/build/android_ndk/Android-mpg123-vorbis.mk @@ -71,6 +71,7 @@ LOCAL_SRC_FILES := \ soundlib/Load_psm.cpp \ soundlib/Load_ptm.cpp \ soundlib/Load_s3m.cpp \ + soundlib/Load_sfx.cpp \ soundlib/Load_stm.cpp \ soundlib/Load_ult.cpp \ soundlib/Load_umx.cpp \ diff --git OpenMPT/build/android_ndk/Android-unmo3.mk OpenMPT/build/android_ndk/Android-unmo3.mk index f28d829..0f89ca3 100644 --- OpenMPT/build/android_ndk/Android-unmo3.mk +++ OpenMPT/build/android_ndk/Android-unmo3.mk @@ -104,6 +104,7 @@ LOCAL_SRC_FILES := \ soundlib/Load_psm.cpp \ soundlib/Load_ptm.cpp \ soundlib/Load_s3m.cpp \ + soundlib/Load_sfx.cpp \ soundlib/Load_stm.cpp \ soundlib/Load_ult.cpp \ soundlib/Load_umx.cpp \ diff --git OpenMPT/build/android_ndk/Android.mk OpenMPT/build/android_ndk/Android.mk index 6ef628b..f5214ca 100644 --- OpenMPT/build/android_ndk/Android.mk +++ OpenMPT/build/android_ndk/Android.mk @@ -72,6 +72,7 @@ LOCAL_SRC_FILES := \ soundlib/Load_psm.cpp \ soundlib/Load_ptm.cpp \ soundlib/Load_s3m.cpp \ + soundlib/Load_sfx.cpp \ soundlib/Load_stm.cpp \ soundlib/Load_ult.cpp \ soundlib/Load_umx.cpp \ diff --git OpenMPT/build/autotools/Makefile.am OpenMPT/build/autotools/Makefile.am index 86dbdd2..afdb965 100644 --- OpenMPT/build/autotools/Makefile.am +++ OpenMPT/build/autotools/Makefile.am @@ -184,6 +184,7 @@ libopenmpt_la_SOURCES += soundlib/Load_plm.cpp libopenmpt_la_SOURCES += soundlib/Load_psm.cpp libopenmpt_la_SOURCES += soundlib/Load_ptm.cpp libopenmpt_la_SOURCES += soundlib/Load_s3m.cpp +libopenmpt_la_SOURCES += soundlib/Load_sfx.cpp libopenmpt_la_SOURCES += soundlib/Load_stm.cpp libopenmpt_la_SOURCES += soundlib/Load_ult.cpp libopenmpt_la_SOURCES += soundlib/Load_umx.cpp @@ -417,6 +418,7 @@ libopenmpttest_SOURCES += soundlib/Load_plm.cpp libopenmpttest_SOURCES += soundlib/Load_psm.cpp libopenmpttest_SOURCES += soundlib/Load_ptm.cpp libopenmpttest_SOURCES += soundlib/Load_s3m.cpp +libopenmpttest_SOURCES += soundlib/Load_sfx.cpp libopenmpttest_SOURCES += soundlib/Load_stm.cpp libopenmpttest_SOURCES += soundlib/Load_ult.cpp libopenmpttest_SOURCES += soundlib/Load_umx.cpp diff --git OpenMPT/libopenmpt/foo_openmpt.cpp OpenMPT/libopenmpt/foo_openmpt.cpp index ac7ce34..4517eff 100644 --- OpenMPT/libopenmpt/foo_openmpt.cpp +++ OpenMPT/libopenmpt/foo_openmpt.cpp @@ -319,6 +319,9 @@ DECLARE_FILE_TYPE("OpenMPT compatible module files", "*.imf" ";" "*.j2b" ";" "*.plm" ";" + "*.sfx" ";" + "*.sfx2" ";" + "*.mms" ";" "*.gdm" ";" "*.umx" ";" "*.mo3" ";" diff --git OpenMPT/soundlib/Load_sfx.cpp OpenMPT/soundlib/Load_sfx.cpp new file mode 100644 index 0000000..884dc8c --- /dev/null +++ OpenMPT/soundlib/Load_sfx.cpp @@ -0,0 +1,381 @@ +/* + * Load_sfx.cpp + * ------------ + * Purpose: SFX / MMS (SoundFX / MultiMedia Sound) module loader + * Notes : Mostly based on the Soundtracker loader, some effect behavior is based on Flod's implementation. + * Authors: Devin Acker + * OpenMPT Devs + * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. + */ + +#include "stdafx.h" +#include "Loaders.h" +#include "Tables.h" + +OPENMPT_NAMESPACE_BEGIN + +#ifdef NEEDS_PRAGMA_PACK +#pragma pack(push, 1) +#endif + +// File Header +struct PACKED SFXFileHeader +{ + uint8 numOrders; + uint8 restartPos; + uint8 orderList[128]; +}; + +STATIC_ASSERT(sizeof(SFXFileHeader) == 130); + +// Sample Header +struct PACKED SFXSampleHeader +{ + char name[22]; + uint16 dummy; // supposedly sample length, but almost always incorrect + uint8 finetune; + uint8 volume; + uint16 loopStart; + uint16 loopLength; + + // Convert all multi-byte numeric values to current platform's endianness or vice versa. + void ConvertEndianness() + { + SwapBytesBE(loopStart); + SwapBytesBE(loopLength); + } + + // Convert an MOD sample header to OpenMPT's internal sample header. + void ConvertToMPT(ModSample &mptSmp, uint32 length) const + { + mptSmp.Initialize(MOD_TYPE_MOD); + mptSmp.nLength = static_cast<SmpLength>(length); + mptSmp.nFineTune = MOD2XMFineTune(finetune & 0x0F); + mptSmp.nVolume = 4 * MIN(volume, 64); + + SmpLength lStart = loopStart; + SmpLength lLength = loopLength * 2; + + if(mptSmp.nLength) + { + mptSmp.nLoopStart = lStart; + mptSmp.nLoopEnd = lStart + lLength; + + if(mptSmp.nLoopStart >= mptSmp.nLength) + { + mptSmp.nLoopStart = mptSmp.nLength - 1; + } + if(mptSmp.nLoopEnd > mptSmp.nLength) + { + mptSmp.nLoopEnd = mptSmp.nLength; + } + if(mptSmp.nLoopStart > mptSmp.nLoopEnd || mptSmp.nLoopEnd < 4 || mptSmp.nLoopEnd - mptSmp.nLoopStart < 4) + { + mptSmp.nLoopStart = 0; + mptSmp.nLoopEnd = 0; + } + + if(mptSmp.nLoopEnd > mptSmp.nLoopStart) + { + mptSmp.uFlags.set(CHN_LOOP); + } + } + } +}; + +STATIC_ASSERT(sizeof(SFXSampleHeader) == 30); + +#ifdef NEEDS_PRAGMA_PACK +#pragma pack(pop) +#endif + +static uint32 ReadSample(FileReader &file, SFXSampleHeader &sampleHeader, uint32 length, ModSample &sample, char (&sampleName)[MAX_SAMPLENAME]) +//------------------------------------------------------------------------------------------------------------------------------ +{ + file.ReadConvertEndianness(sampleHeader); + sampleHeader.ConvertToMPT(sample, length); + + mpt::String::Read<mpt::String::spacePadded>(sampleName, sampleHeader.name); + // Get rid of weird characters in sample names. + uint32 invalidChars = 0; + for(uint32 i = 0; i < CountOf(sampleName); i++) + { + if(sampleName[i] > 0 && sampleName[i] < ' ') + { + sampleName[i] = ' '; + invalidChars++; + } + } + return invalidChars; +} + +bool CSoundFile::ReadSFX(FileReader &file, ModLoadingFlags loadFlags) +//------------------------------------------------------------------- +{ + if(file.Seek(0x3C), file.ReadMagic("SONG")) + { + InitializeGlobals(MOD_TYPE_SFX); + m_nSamples = 15; + } else if(file.Seek(0x7C), file.ReadMagic("SO31")) + { + InitializeGlobals(MOD_TYPE_SFX); + m_nSamples = 31; + } else + { + return false; + } + + size_t totalSampleLen = 0; + uint32 sampleLen[31]; + + file.Rewind(); + for(SAMPLEINDEX smp = 0; smp < m_nSamples; smp++) + { + sampleLen[smp] = file.ReadUint32BE(); + if(sampleLen[smp] > 65535) + return false; + + totalSampleLen += sampleLen[smp]; + } + + m_nChannels = 4; + m_nInstruments = 0; + m_nDefaultSpeed = 6; + m_nMinPeriod = 14 * 4; + m_nMaxPeriod = 3424 * 4; + m_nSamplePreAmp = 64; + m_SongFlags.reset(); + + // Setup channel pan positions and volume + SetupMODPanning(true); + + file.Skip(4); + uint16 speed = file.ReadUint16BE(); + m_nDefaultTempo = TEMPO(14565.0 * 122 / speed); + + file.Skip(14); + + for(SAMPLEINDEX smp = 1; smp <= m_nSamples; smp++) + { + SFXSampleHeader sampleHeader; + ReadSample(file, sampleHeader, sampleLen[smp-1], Samples[smp], m_szNames[smp]); + } + + SFXFileHeader fileHeader; + file.ReadStruct(fileHeader); + + if(fileHeader.numOrders > 128) + return false; + + PATTERNINDEX numPatterns = 0; + for(ORDERINDEX ord = 0; ord < fileHeader.numOrders; ord++) + { + numPatterns = MAX(numPatterns, fileHeader.orderList[ord]+1); + } + + if(loadFlags == onlyVerifyHeader) + return true; + + if(fileHeader.restartPos < fileHeader.numOrders) + Order.SetRestartPos(fileHeader.restartPos); + else + Order.SetRestartPos(0); + + Order.ReadFromArray(fileHeader.orderList); + Order.resize(fileHeader.numOrders); + + // SFX v2 / MMS modules have 4 extra bytes here for some reason + if(m_nSamples == 31) + file.Skip(4); + + uint8 lastNote[4] = {0}; + uint8 slideTo[4] = {0}; + uint8 slideRate[4] = {0}; + + // Reading patterns + for(PATTERNINDEX pat = 0; pat < numPatterns; pat++) + { + if(!(loadFlags & loadPatternData) || !Patterns.Insert(pat, 64)) + { + file.Skip(64 * 4 * 4); + continue; + } + + for(ROWINDEX row = 0; row < 64; row++) + { + PatternRow rowBase = Patterns[pat].GetpModCommand(row, 0); + for(CHANNELINDEX chn = 0; chn < 4; chn++) + { + ModCommand &m = rowBase[chn]; + uint8 data[4]; + file.ReadArray(data); + + if(data[0] == 0xFF) + { + m.note = NOTE_NONE; + m.instr = m.command = m.param = 0; + lastNote[chn] = slideRate[chn] = 0; + + switch(data[1]) + { + case 0xFE: // STP (note cut) + m.command = CMD_VOLUME; + continue; + + case 0xFD: // PIC (null) + continue; + + case 0xFC: // BRK (pattern break) + m.command = CMD_PATTERNBREAK; + continue; + } + } + + // Read Period + uint16 period = (((static_cast<uint16>(data[0]) & 0x0F) << 8) | data[1]); + m.note = NOTE_NONE; + if(period > 0 && period != 0xFFF) + { + m.note = 6 * 12 + 35 + NOTE_MIN; + for(int i = 0; i < 6 * 12; i++) + { + if(period >= ProTrackerPeriodTable[i]) + { + if(period != ProTrackerPeriodTable[i] && i != 0) + { + uint16 p1 = ProTrackerPeriodTable[i - 1]; + uint16 p2 = ProTrackerPeriodTable[i]; + if(p1 - period < (period - p2)) + { + m.note = static_cast<ModCommand::NOTE>(i + 35 + NOTE_MIN); + break; + } + } + m.note = static_cast<ModCommand::NOTE>(i + 36 + NOTE_MIN); + break; + } + } + } + // Read Instrument + m.instr = (data[2] >> 4) | (data[0] & 0x10); + // Read Effect + m.command = data[2] & 0x0F; + m.param = data[3]; + + if(m.note) + { + lastNote[chn] = m.note; + slideRate[chn] = 0; + } + + if(m.command || m.param) + { + switch(m.command) + { + case 0x1: // arpeggio + m.command = CMD_ARPEGGIO; + break; + + case 0x2: // portamento (like Ultimate Soundtracker) + if(m.param & 0xF0) + { + m.command = CMD_PORTAMENTODOWN; + m.param >>= 4; + } else if(m.param & 0xF) + { + m.command = CMD_PORTAMENTOUP; + m.param &= 0x0F; + } else + { + m.command = m.param = 0; + } + break; + + case 0x3: // enable filter/LED + m.command = CMD_MODCMDEX; + m.param = 0; + break; + + case 0x4: // disable filter/LED + m.command = CMD_MODCMDEX; + m.param = 1; + break; + + case 0x5: // increase volume + if(m.instr) + { + m.command = CMD_VOLUME; + m.param = MIN(0x3F, (Samples[m.instr].nVolume >> 2) + m.param); + } else + { + m.command = m.param = 0; + } + break; + + case 0x6: // decrease volume + if(m.instr) + { + m.command = CMD_VOLUME; + if((Samples[m.instr].nVolume >> 2) >= m.param) + m.param = (Samples[m.instr].nVolume >> 2) - m.param; + else + m.param = 0; + } else + { + m.command = m.param = 0; + } + break; + + case 0x7: // 7xy: slide down x semitones at speed y + slideTo[chn] = lastNote[chn] - (m.param >> 4); + + m.command = CMD_PORTAMENTODOWN; + m.param = slideRate[chn] = m.param & 0xF; + break; + + case 0x8: // 8xy: slide up x semitones at speed y + slideTo[chn] = lastNote[chn] + (m.param >> 4); + + m.command = CMD_PORTAMENTOUP; + m.param = slideRate[chn] = m.param & 0xF; + break; + + default: + m.command = m.param = 0; + break; + } + } + + // continue 7xy/8xy slides if needed + if(!m.command && slideRate[chn]) + { + if(slideTo[chn]) + { + m.note = lastNote[chn] = slideTo[chn]; + m.param = slideRate[chn]; + slideTo[chn] = 0; + } + m.command = CMD_TONEPORTAMENTO; + } + } + } + } + + // Reading samples + if(loadFlags & loadSampleData) + { + for(SAMPLEINDEX smp = 1; smp <= m_nSamples; smp++) if(Samples[smp].nLength) + { + SampleIO( + SampleIO::_8bit, + SampleIO::mono, + SampleIO::littleEndian, + SampleIO::signedPCM) + .ReadSample(Samples[smp], file); + } + } + + return true; +} + +OPENMPT_NAMESPACE_END diff --git OpenMPT/soundlib/Snd_defs.h OpenMPT/soundlib/Snd_defs.h index 4499f0b..398132e 100644 --- OpenMPT/soundlib/Snd_defs.h +++ OpenMPT/soundlib/Snd_defs.h @@ -97,6 +97,7 @@ enum MODTYPE MOD_TYPE_DIGI = 0x8000000, MOD_TYPE_UAX = 0x10000000, // sampleset as module MOD_TYPE_PLM = 0x20000000, + MOD_TYPE_SFX = 0x40000000, }; DECLARE_FLAGSET(MODTYPE) diff --git OpenMPT/soundlib/Sndfile.cpp OpenMPT/soundlib/Sndfile.cpp index d85039d..8c61101 100644 --- OpenMPT/soundlib/Sndfile.cpp +++ OpenMPT/soundlib/Sndfile.cpp @@ -313,6 +313,7 @@ bool CSoundFile::Create(FileReader file, ModLoadingFlags loadFlags) && !ReadMod(file, loadFlags) && !Read669(file, loadFlags) && !ReadICE(file, loadFlags) + && !ReadSFX(file, loadFlags) && !ReadM15(file, loadFlags)) { m_nType = MOD_TYPE_NONE; @@ -1041,6 +1042,7 @@ MODTYPE CSoundFile::GetBestSaveFormat() const return GetType(); case MOD_TYPE_AMF0: case MOD_TYPE_DIGI: + case MOD_TYPE_SFX: return MOD_TYPE_MOD; case MOD_TYPE_MED: if(m_nDefaultTempo == TEMPO(125, 0) && m_nDefaultSpeed == 6 && !m_nInstruments) diff --git OpenMPT/soundlib/Sndfile.h OpenMPT/soundlib/Sndfile.h index eee6e13..ab655e9 100644 --- OpenMPT/soundlib/Sndfile.h +++ OpenMPT/soundlib/Sndfile.h @@ -669,6 +669,7 @@ public: bool ReadDIGI(FileReader &file, ModLoadingFlags loadFlags = loadCompleteModule); bool ReadPLM(FileReader &file, ModLoadingFlags loadFlags = loadCompleteModule); bool ReadMID(FileReader &file, ModLoadingFlags loadFlags = loadCompleteModule); + bool ReadSFX(FileReader &file, ModLoadingFlags loadFlags = loadCompleteModule); static std::vector<const char *> GetSupportedExtensions(bool otherFormats); static mpt::Charset GetCharsetFromModType(MODTYPE modtype); diff --git OpenMPT/soundlib/Tables.cpp OpenMPT/soundlib/Tables.cpp index abf91a9..ab1dd15 100644 --- OpenMPT/soundlib/Tables.cpp +++ OpenMPT/soundlib/Tables.cpp @@ -87,6 +87,9 @@ static const ModFormatInfo modFormatInfo[] = { MOD_TYPE_IMF, "Imago Orpheus", "imf" }, { MOD_TYPE_J2B, "Galaxy Sound System", "j2b" }, { MOD_TYPE_PLM, "Disorder Tracker 2", "plm" }, + { MOD_TYPE_SFX, "SoundFX", "sfx" }, + { MOD_TYPE_SFX, "SoundFX", "sfx2" }, + { MOD_TYPE_SFX, "MultiMedia Sound", "mms" }, #ifndef NO_ARCHIVE_SUPPORT // Compressed modules @@ -147,6 +150,7 @@ static const ModCharsetInfo ModCharsetInfos[] = { MOD_TYPE_OKT , mpt::CharsetISO8859_1 }, { MOD_TYPE_DBM , mpt::CharsetISO8859_1 }, { MOD_TYPE_DIGI, mpt::CharsetISO8859_1 }, + { MOD_TYPE_SFX , mpt::CharsetISO8859_1 }, // Amiga // DOS { MOD_TYPE_MOD , mpt::CharsetISO8859_1 }, { MOD_TYPE_MED , mpt::CharsetISO8859_1 }, | ||||
Has the bug occurred in previous versions? | |||||
Tested code revision (in case you know it) | |||||
Since I'm on a bit of a file-format kick lately, I decided to take this one on as well. I gleaned some info about effects from Flod's SoundFX player, but otherwise this is largely based on the Soundtracker loader. Both 15- and 31-sample modules are supported (apparently the latter often use the name "MultiMedia Sound" which as far as I can tell is just SoundFX 2.0). This is a work in progress, so some things might not sound 100% right (but the modules I've tried so far sound pretty much correct). In addition to the patch, here's a (64-bit) Windows build of openmpt123 with the SoundFX support added: (edit: reuploaded new patch to correct a sloppy mistake I caught immediately after uploading the first one, whoops) |
|
The openmpt123 build works perfectly with SoundFX modules! How would I go about applying the PATCH file? |
|
Uploaded a new patch (and build), changes:
There are a few ways to apply patch files on Windows. If you use TortoiseSVN then it should work if you do this: Note that if you build with Visual Studio, you'll have to add soundlib/Load_sfx.cpp to the project yourself. |
|
I'm not a programmer, and even with those directions, I have no clue how to use TortoiseSVN to patch the file. As a result, I will have to wait until an actual release arrives. |
|
Ah, all right. Anyway, added yet another revision of the patch, which just fixes another little mistake after I reread Flod's .sfx player and the actual SoundFX 1.8 replay source (but it shouldn't affect very many, if any, existing modules). New build is in the same place. Hopefully this should be the last revision but I still haven't tried every single .sfx/.mms file that I have (or listened to most of them in the original tracker for that matter). (Apologies for uploading so many minor revisions of the same patch this time. Would be nice if I could delete old attachments, but there doesn't seem to be a way to...) |
|
Just a quick question (I'm finally looking at the patch in more depth)... Did you encounter lots of modules with weird sample names or did you just blindly copy over the invalid chars stuff from the MOD loaders? It's only really needed there because of the lack of magic bytes and because there are actually many Ultimate SoundTracker MODs with weird binary characters in their names, but if this is not the case for SFX modules, it really should not be done there. |
|
Yeah, that's basically just a leftover from the MOD loader (which this was based directly on, to a degree). Likewise with the STP2 loader I wrote that includes the same function; I don't think it's actually necessary for either of them. |
|
Yeah, though so. I refactored the loaders a bit, hopefully I will be able to merge them "soon". :) |
|
Patch finally applied in r6354. Yay. |
|
Date Modified | Username | Field | Change |
---|---|---|---|
2016-01-09 19:41 | ncovert | New Issue | |
2016-03-09 03:14 | Revenant | Note Added: 0002277 | |
2016-03-09 03:14 | Revenant | File Added: sfx.patch | |
2016-03-09 03:16 | Revenant | Note Edited: 0002277 | |
2016-03-09 03:38 | Revenant | File Added: sfx_r2.patch | |
2016-03-09 03:38 | Revenant | Note Edited: 0002277 | |
2016-03-09 20:14 | ncovert | Note Added: 0002278 | |
2016-03-09 20:15 | ncovert | Note Edited: 0002278 | |
2016-03-09 20:16 | ncovert | Note Edited: 0002278 | |
2016-03-10 01:52 | Revenant | File Added: sfx_r3.patch | |
2016-03-10 01:58 | Revenant | Note Added: 0002279 | |
2016-03-10 01:58 | Revenant | Note Edited: 0002279 | |
2016-03-10 02:29 | ncovert | Note Added: 0002280 | |
2016-03-10 03:51 | Revenant | Note Added: 0002281 | |
2016-03-10 03:51 | Revenant | File Added: sfx_r4.patch | |
2016-03-10 05:41 | manx | Assigned To | => Saga Musix |
2016-03-10 05:41 | manx | Status | new => assigned |
2016-03-13 16:30 | Saga Musix | File Deleted: sfx.patch | |
2016-03-13 16:30 | Saga Musix | File Deleted: sfx_r2.patch | |
2016-03-13 16:30 | Saga Musix | File Deleted: sfx_r3.patch | |
2016-04-11 01:30 | Saga Musix | Note Added: 0002325 | |
2016-04-13 06:24 | Revenant | Note Added: 0002332 | |
2016-04-13 14:39 | Saga Musix | Note Added: 0002333 | |
2016-04-24 00:29 | Saga Musix | Target Version | => OpenMPT 1.26.01.00 / libopenmpt 0.2-beta17 (upgrade first) |
2016-05-12 14:34 | Saga Musix | Note Added: 0002365 | |
2016-05-12 14:34 | Saga Musix | Status | assigned => resolved |
2016-05-12 14:34 | Saga Musix | Resolution | open => fixed |
2016-05-12 14:34 | Saga Musix | Fixed in Version | => OpenMPT 1.26.01.00 / libopenmpt 0.2-beta17 (upgrade first) |