Skip to content

Commit

Permalink
Add KonamiPS1Seq parser (thanks goes to @Nisto)
Browse files Browse the repository at this point in the history
It might not be perfect, but it looked working somehow. Closes #105
  • Loading branch information
loveemu committed May 13, 2018
1 parent f6ee876 commit f3be79b
Show file tree
Hide file tree
Showing 4 changed files with 197 additions and 17 deletions.
2 changes: 2 additions & 0 deletions README.md
Expand Up @@ -15,6 +15,7 @@ VGMTrans converts a music files used in console video games into standard midi a
- Tamsoft's PS1 sequence and instrument formats (.tsq, .tvb)
- Capcom's QSound sequence and instrument formats used in CPS1/CPS2 arcade games
- Squaresoft's PS1 format used in certain PS1 games like Final Fantasy Tactics (smds/dwds)
- Konami's PS1 sequence format known as KDT1
- Nintendo's Gameboy Advance sequence format
- Nintendo's SNES sequence and instrument format known as N-SPC (.spc)
- Squaresoft's SNES sequence and instrument format (AKAO/SUZUKI) (.spc)
Expand Down Expand Up @@ -60,6 +61,7 @@ Contributors
### Special Thanks

- Bregalad: Author of [GBAMusRiper](http://www.romhacking.net/utilities/881/), great reference of MP2k interpretation.
- Nisto: Author of [kdt-tool](https://github.com/Nisto/kdt-tool), thank you for your approval of porting to VGMTrans.

Contact
-------
Expand Down
2 changes: 2 additions & 0 deletions src/main/formats/KonamiPS1Format.h
Expand Up @@ -13,3 +13,5 @@ BEGIN_FORMAT(KonamiPS1)
USING_SCANNER(KonamiPS1Scanner)
USING_MATCHER(FilegroupMatcher)
END_FORMAT()

// [TODO] Use a matcher which can associate VAB properly.
198 changes: 182 additions & 16 deletions src/main/formats/KonamiPS1Seq.cpp
Expand Up @@ -11,7 +11,6 @@ DECLARE_FORMAT(KonamiPS1);
KonamiPS1Seq::KonamiPS1Seq(RawFile *file, uint32_t offset, const std::wstring &name)
: VGMSeq(KonamiPS1Format::name, file, offset, kHeaderSize + file->GetWord(offset + 4), name) {
bLoadTickByTick = true;
bUseLinearAmplitudeScale = true;

UseReverb();
}
Expand All @@ -27,12 +26,12 @@ bool KonamiPS1Seq::GetHeaderInfo(void) {

VGMHeader *header = AddHeader(dwOffset, kHeaderSize);
header->AddSig(dwOffset, 4);
header->AddSimpleItem(dwOffset + 4, 4, L"Size");
header->AddSimpleItem(dwOffset + 8, 4, L"Timebase");
SetPPQN(GetWord(dwOffset + 8));
header->AddSimpleItem(dwOffset + 12, 4, L"Number Of Tracks");
header->AddSimpleItem(dwOffset + kOffsetToFileSize, 4, L"Size");
header->AddSimpleItem(dwOffset + kOffsetToTimebase, 4, L"Timebase");
SetPPQN(GetWord(dwOffset + kOffsetToTimebase));
header->AddSimpleItem(dwOffset + kOffsetToTrackCount, 4, L"Number Of Tracks");

uint32_t numTracks = GetWord(dwOffset + 12);
uint32_t numTracks = GetWord(dwOffset + kOffsetToTrackCount);
VGMHeader *trackSizeHeader = AddHeader(dwOffset + kHeaderSize, 2 * numTracks, L"Track Size");
for (size_t trackIndex = 0; trackIndex < numTracks; trackIndex++) {
std::wstringstream itemName;
Expand All @@ -44,7 +43,7 @@ bool KonamiPS1Seq::GetHeaderInfo(void) {
}

bool KonamiPS1Seq::GetTrackPointers(void) {
uint32_t numTracks = GetWord(dwOffset + 12);
uint32_t numTracks = GetWord(dwOffset + kOffsetToTrackCount);
uint32_t trackStart = dwOffset + kHeaderSize + (numTracks * 2);
for (size_t trackIndex = 0; trackIndex < numTracks; trackIndex++) {
uint16_t trackSize = GetShort(dwOffset + kHeaderSize + (trackIndex * 2));
Expand All @@ -66,13 +65,13 @@ bool KonamiPS1Seq::IsKDT1Seq(RawFile *file, uint32_t offset) {
return false;
}

uint32_t dataSize = file->GetWord(offset + 4);
uint32_t dataSize = file->GetWord(offset + kOffsetToFileSize);
uint32_t fileSize = kHeaderSize + dataSize;
if (offset + fileSize >= file->size()) {
return false;
}

uint32_t numTracks = file->GetWord(offset + 12);
uint32_t numTracks = file->GetWord(offset + kOffsetToTrackCount);
if (numTracks == 0 || offset + kHeaderSize + (numTracks * 2) >= file->size()) {
return false;
}
Expand All @@ -92,10 +91,6 @@ bool KonamiPS1Seq::IsKDT1Seq(RawFile *file, uint32_t offset) {
return true;
}

uint32_t KonamiPS1Seq::GetKDT1FileSize(RawFile *file, uint32_t offset) {
return kHeaderSize + file->GetWord(offset + 4);
}

// ***************
// KonamiPS1Track
// ***************
Expand All @@ -111,6 +106,7 @@ void KonamiPS1Track::ResetVars(void) {
SeqTrack::ResetVars();

vel = 100;
skipDeltaTime = false;
}

bool KonamiPS1Track::ReadEvent(void) {
Expand All @@ -121,11 +117,181 @@ bool KonamiPS1Track::ReadEvent(void) {
return false;
}

if (!skipDeltaTime) {
uint32_t delta = ReadVarLen(curOffset);
AddTime(delta);

std::wstringstream description;
description << L"Duration: " << delta;
AddGenericEvent(beginOffset, curOffset - beginOffset, L"Delta Time", description.str(), CLR_REST, ICON_REST);

skipDeltaTime = true;
return true;
}

uint8_t statusByte = GetByte(curOffset++);
bool bContinue = false;
uint8_t command = statusByte & 0x7f;
bool note = (statusByte & 0x80) == 0;

bool bContinue = true;
if (note) {
uint8_t noteNumber = command;
uint8_t paramByte = GetByte(curOffset++);
uint8_t velocity = paramByte & 0x7f;
skipDeltaTime = (paramByte & 0x80) != 0;

AddNoteOn(beginOffset, curOffset - beginOffset, noteNumber, velocity);
prevKey = noteNumber;
prevVel = velocity;
}
else {
std::wstringstream description;

uint8_t paramByte;
if (command == 0x4a) {
// Note Off (Reset Running Status)
skipDeltaTime = false;
}
else if (command == 0x4b) {
// Note Off (Sustain Running Status)
skipDeltaTime = true; // has already set? Nobody cares!
}
else {
// Commands excluding note off are two bytes length.
paramByte = GetByte(curOffset++);
skipDeltaTime = (paramByte & 0x80) != 0;
paramByte &= 0x7f;
}

switch (command) {
case 1:
AddModulation(beginOffset, curOffset - beginOffset, paramByte);
break;

case 6:
description << L"Parameter: " << paramByte;
AddGenericEvent(beginOffset, curOffset - beginOffset, L"NRPN Data Entry", description.str(), CLR_MISC);
if (readMode == READMODE_CONVERT_TO_MIDI) {
pMidiTrack->AddControllerEvent(channel, command, paramByte);
}
break;

case 7:
AddVol(beginOffset, curOffset - beginOffset, paramByte);
break;

case 10:
AddPan(beginOffset, curOffset - beginOffset, paramByte);
break;

case 11:
AddExpression(beginOffset, curOffset - beginOffset, paramByte);
break;

case 15:
description << L"Parameter: " << paramByte;
AddGenericEvent(beginOffset, curOffset - beginOffset, L"Stereo Widening (?)", description.str(), CLR_PAN, ICON_CONTROL);
if (readMode == READMODE_CONVERT_TO_MIDI) {
pMidiTrack->AddControllerEvent(channel, command, paramByte);
}
break;

// TODO KDT1 parser
AddUnknownItem(beginOffset, 1);
case 64:
description << L"Parameter: " << paramByte;
AddSustainEvent(beginOffset, curOffset - beginOffset, paramByte);
break;

case 70:
description << L"Parameter: " << paramByte;
AddGenericEvent(beginOffset, curOffset - beginOffset, L"Set Channel", description.str(), CLR_PAN, ICON_CONTROL);
if (readMode == READMODE_CONVERT_TO_MIDI) {
pMidiTrack->AddControllerEvent(channel, command, paramByte);
}
break;

case 71: {
uint8_t bpm = static_cast<uint8_t>(std::min<unsigned int>(10 + paramByte * 2, 255));
AddTempoBPM(beginOffset, curOffset - beginOffset, bpm, L"Tempo (10-255 BPM, divisible by two)");
break;
}

case 72:
AddPitchBend(beginOffset, curOffset - beginOffset, int16_t{paramByte << 7} - 8192);
break;

case 73:
AddProgramChange(beginOffset, curOffset - beginOffset, paramByte);
break;

case 74:
// Not sure how it will work for a chord (polyphonic track)
AddNoteOff(beginOffset, curOffset - beginOffset, prevKey, L"Note Off (Reset Running Status)");
break;

case 75:
// Not sure how it will work for a chord (polyphonic track)
AddNoteOff(beginOffset, curOffset - beginOffset, prevKey, L"Note Off (Sustain Running Status)");
break;

case 76:
AddTempoBPM(beginOffset, curOffset - beginOffset, paramByte, L"Tempo (0-127 BPM)");
break;

case 77:
AddTempoBPM(beginOffset, curOffset - beginOffset, paramByte + 128, L"Tempo (128-255 BPM)");
break;

case 91:
AddReverb(beginOffset, curOffset - beginOffset, paramByte);
break;

case 99:
description << L"Parameter: " << paramByte;
AddGenericEvent(beginOffset, curOffset - beginOffset, L"NRPN (LSB)", description.str(), CLR_MISC);
if (readMode == READMODE_CONVERT_TO_MIDI) {
pMidiTrack->AddControllerEvent(channel, command, paramByte);
}
break;

case 100:
if (paramByte == 20) {
AddGenericEvent(beginOffset, curOffset - beginOffset, L"Loop Start", L"", CLR_LOOP);
}
else if (paramByte == 30) {
AddGenericEvent(beginOffset, curOffset - beginOffset, L"Loop End", L"", CLR_LOOP);
}
else {
description << L"Parameter: " << paramByte;
AddGenericEvent(beginOffset, curOffset - beginOffset, L"NRPN (LSB)", description.str(), CLR_MISC);
}
if (readMode == READMODE_CONVERT_TO_MIDI) {
pMidiTrack->AddControllerEvent(channel, command, paramByte);
}
break;

case 118:
description << L"Parameter: " << paramByte;
AddGenericEvent(beginOffset, curOffset - beginOffset, L"Seq Beat", description.str(), CLR_MISC);
if (readMode == READMODE_CONVERT_TO_MIDI) {
pMidiTrack->AddControllerEvent(channel, command, paramByte);
}
break;

case 127:
AddEndOfTrack(beginOffset, curOffset - beginOffset);
bContinue = false;
break;

default:
AddUnknown(beginOffset, curOffset - beginOffset);
if (readMode == READMODE_CONVERT_TO_MIDI) {
pMidiTrack->AddControllerEvent(channel, command, paramByte);
}

//bContinue = false;
break;
}
}

return bContinue;
}
12 changes: 11 additions & 1 deletion src/main/formats/KonamiPS1Seq.h
Expand Up @@ -7,6 +7,10 @@
class KonamiPS1Seq : public VGMSeq {
public:
static constexpr uint32_t kHeaderSize = 16;
static constexpr uint32_t kOffsetToFileSize = 4;
static constexpr uint32_t kOffsetToTimebase = 8;
static constexpr uint32_t kOffsetToTrackCount = 0x0c;
static constexpr uint32_t kOffsetToTrackSizes = 0x10;

KonamiPS1Seq(RawFile *file, uint32_t offset, const std::wstring &name = L"KonamiPS1Seq");

Expand All @@ -18,7 +22,10 @@ class KonamiPS1Seq : public VGMSeq {
virtual void ResetVars(void);

static bool IsKDT1Seq(RawFile *file, uint32_t offset);
static uint32_t GetKDT1FileSize(RawFile *file, uint32_t offset);

static uint32_t GetKDT1FileSize(RawFile *file, uint32_t offset) {
return kHeaderSize + file->GetWord(offset + kOffsetToFileSize);
}
};

class KonamiPS1Track : public SeqTrack {
Expand All @@ -27,4 +34,7 @@ class KonamiPS1Track : public SeqTrack {

virtual void ResetVars(void);
virtual bool ReadEvent(void);

private:
bool skipDeltaTime;
};

0 comments on commit f3be79b

Please sign in to comment.