Initial commit
This commit is contained in:
321
audio/soundfont/vab/psxspu.cpp
Normal file
321
audio/soundfont/vab/psxspu.cpp
Normal file
@@ -0,0 +1,321 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
/*
|
||||
* VGMTrans (c) 2002-2019
|
||||
* Licensed under the zlib license,
|
||||
* refer to the included VGMTrans_LICENSE.txt file
|
||||
*/
|
||||
|
||||
#include "common/debug.h"
|
||||
#include "psxspu.h"
|
||||
|
||||
// A lot of games use a simple linear amplitude decay/release for their envelope.
|
||||
// In other words, the envelope level drops at a constant rate (say from
|
||||
// 0xFFFF to 0 (cps2) ), and to get the attenuation we multiply by this
|
||||
// percent value (env_level / 0xFFFF). This means the attenuation will be
|
||||
// -20*log10( env_level / 0xFFFF ) decibels. Wonderful, but SF2 and DLS have
|
||||
// the a linear decay in decibels - not amplitude - for their decay/release slopes.
|
||||
// So if you were to graph it, the SF2/DLS attenuation over time graph would be
|
||||
// a simple line.
|
||||
|
||||
// (Note these are obviously crude ASCII drawings and in no way accurate!)
|
||||
// 100db
|
||||
// | /
|
||||
// | /
|
||||
// | /
|
||||
// | /
|
||||
// | /
|
||||
// 10db / - half volume
|
||||
// |/
|
||||
// |--------------------TIME
|
||||
|
||||
// But games using linear amplitude have a convex curve
|
||||
// 100db
|
||||
// | -
|
||||
// | -
|
||||
// | -
|
||||
// | -
|
||||
// | -
|
||||
// 10db x - half volume
|
||||
// |- -
|
||||
// |-------------------TIME
|
||||
|
||||
// Now keep in mind that 10db of attenuation is half volume to the human ear.
|
||||
// What this mean is that SF2/DLS are going to sound like they have much shorter
|
||||
// decay/release rates if we simply plug in a time value from 0 atten to full atten
|
||||
// from a linear amplitude game.
|
||||
|
||||
// My approach at the moment is to calculate the time it takes to get to half volume
|
||||
// and then use that value accordingly with the SF2/DLS decay time. In other words
|
||||
// Take the second graph, find where y = 10db, and the draw a line from the origin
|
||||
// through it to get your DLS/SF2 decay/release line
|
||||
// (the actual output value is time where y = 100db for sf2 or 96db for DLS, SynthFile class uses
|
||||
// 100db).
|
||||
|
||||
// This next function converts seconds to full attenuation in a linear amplitude decay scale
|
||||
// and approximates the time to full attenuation in a linear DB decay scale.
|
||||
double LinAmpDecayTimeToLinDBDecayTime(double secondsToFullAtten, int linearVolumeRange) {
|
||||
double expMinDecibel = -100.0;
|
||||
double linearMinDecibel = log10(1.0 / linearVolumeRange) * 20.0;
|
||||
double linearToExpScale = log(linearMinDecibel - expMinDecibel) / log(2.0);
|
||||
return secondsToFullAtten * linearToExpScale;
|
||||
}
|
||||
|
||||
/*
|
||||
* PSX's PSU analysis was done by Neill Corlett.
|
||||
* Thanks to Antires for his ADPCM decompression routine.
|
||||
*/
|
||||
|
||||
PSXSampColl::PSXSampColl(VGMInstrSet *instrset, uint32 offset,
|
||||
uint32 length, const Common::Array<SizeOffsetPair> &vagLocations)
|
||||
: VGMSampColl(instrset->GetRawFile(), instrset, offset, length),
|
||||
_vagLocations(vagLocations) {}
|
||||
|
||||
bool PSXSampColl::GetSampleInfo() {
|
||||
if (_vagLocations.empty()) {
|
||||
/*
|
||||
* We scan through the sample section, and determine the offsets and size of each sample
|
||||
* We do this by searching for series of 16 0x00 value bytes. These indicate the beginning
|
||||
* of a sample, and they will never be found at any other point within the adpcm sample
|
||||
* data.
|
||||
*/
|
||||
uint32 nEndOffset = _dwOffset + _unLength;
|
||||
if (_unLength == 0) {
|
||||
nEndOffset = GetEndOffset();
|
||||
}
|
||||
|
||||
uint32 i = _dwOffset;
|
||||
while (i + 32 <= nEndOffset) {
|
||||
bool isSample = false;
|
||||
|
||||
if (GetWord(i) == 0 && GetWord(i + 4) == 0 && GetWord(i + 8) == 0 &&
|
||||
GetWord(i + 12) == 0) {
|
||||
// most of samples starts with 0s
|
||||
isSample = true;
|
||||
} else {
|
||||
// some sample blocks may not start with 0.
|
||||
// so here is a dirty hack for it.
|
||||
// (Dragon Quest VII, for example)
|
||||
int countOfContinue = 0;
|
||||
uint8 continueByte = 0xff;
|
||||
bool badBlock = false;
|
||||
while (i + (countOfContinue * 16) + 16 <= nEndOffset) {
|
||||
uint8 keyFlagByte = GetByte(i + (countOfContinue * 16) + 1);
|
||||
|
||||
if ((keyFlagByte & 0xF8) != 0) {
|
||||
badBlock = true;
|
||||
break;
|
||||
}
|
||||
|
||||
if (continueByte == 0xff) {
|
||||
if (keyFlagByte == 0 || keyFlagByte == 2) {
|
||||
continueByte = keyFlagByte;
|
||||
}
|
||||
}
|
||||
|
||||
if (keyFlagByte != continueByte) {
|
||||
if (keyFlagByte == 0 || keyFlagByte == 2) {
|
||||
badBlock = true;
|
||||
}
|
||||
break;
|
||||
}
|
||||
countOfContinue++;
|
||||
}
|
||||
if (!badBlock && ((continueByte == 0 && countOfContinue >= 16) ||
|
||||
(continueByte == 2 && countOfContinue >= 3))) {
|
||||
isSample = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (isSample) {
|
||||
uint32 extraGunkLength = 0;
|
||||
//uint8 filterRangeByte = GetByte(i + 16);
|
||||
uint8 keyFlagByte = GetByte(i + 16 + 1);
|
||||
if ((keyFlagByte & 0xF8) != 0)
|
||||
break;
|
||||
|
||||
// if (filterRangeByte == 0 && keyFlagByte == 0) // Breaking on FFXII 309 -
|
||||
// Eruyt Village at 61D50 of the WD
|
||||
if (GetWord(i + 16) == 0 && GetWord(i + 20) == 0 && GetWord(i + 24) == 0 &&
|
||||
GetWord(i + 28) == 0)
|
||||
break;
|
||||
|
||||
uint32 beginOffset = i;
|
||||
i += 16;
|
||||
|
||||
// skip through until we reach the chunk with the end flag set
|
||||
bool loopEnd = false;
|
||||
while (i + 16 <= nEndOffset && !loopEnd) {
|
||||
loopEnd = ((GetByte(i + 1) & 1) != 0);
|
||||
i += 16;
|
||||
}
|
||||
|
||||
// deal with exceptional cases where we see 00 07 77 77 77 77 77 etc.
|
||||
while (i + 16 <= nEndOffset) {
|
||||
loopEnd = ((GetByte(i + 1) & 1) != 0);
|
||||
if (!loopEnd) {
|
||||
break;
|
||||
}
|
||||
extraGunkLength += 16;
|
||||
i += 16;
|
||||
}
|
||||
|
||||
PSXSamp *samp = new PSXSamp(this, beginOffset, i - beginOffset, beginOffset,
|
||||
i - beginOffset - extraGunkLength, 1, 16, 44100,
|
||||
Common::String::format("Sample %d", _samples.size()));
|
||||
_samples.push_back(samp);
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
_unLength = i - _dwOffset;
|
||||
} else {
|
||||
uint32 sampleIndex = 0;
|
||||
for (auto &vag : _vagLocations) {
|
||||
uint32 offSampStart = _dwOffset + vag.offset;
|
||||
uint32 offDataEnd = offSampStart + vag.size;
|
||||
uint32 offSampEnd = offSampStart;
|
||||
|
||||
// detect loop end and ignore garbages like 00 07 77 77 77 77 77 etc.
|
||||
bool lastBlock;
|
||||
do {
|
||||
if (offSampEnd + 16 > offDataEnd) {
|
||||
offSampEnd = offDataEnd;
|
||||
break;
|
||||
}
|
||||
|
||||
lastBlock = ((GetByte(offSampEnd + 1) & 1) != 0);
|
||||
offSampEnd += 16;
|
||||
} while (!lastBlock);
|
||||
|
||||
PSXSamp *samp = new PSXSamp(this, _dwOffset + vag.offset, vag.size,
|
||||
_dwOffset + vag.offset, offSampEnd - offSampStart, 1, 16,
|
||||
44100, Common::String::format("Sample %d", sampleIndex));
|
||||
_samples.push_back(samp);
|
||||
sampleIndex++;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// *******
|
||||
// PSXSamp
|
||||
// *******
|
||||
|
||||
PSXSamp::PSXSamp(VGMSampColl *sampColl, uint32 offset, uint32 length, uint32 dataOffset,
|
||||
uint32 dataLen, uint8 nChannels, uint16 theBPS, uint32 theRate,
|
||||
Common::String name, bool bSetloopOnConversion)
|
||||
: VGMSamp(sampColl, offset, length, dataOffset, dataLen, nChannels, theBPS, theRate, name),
|
||||
_setLoopOnConversion(bSetloopOnConversion) {
|
||||
_bPSXLoopInfoPrioritizing = true;
|
||||
}
|
||||
|
||||
double PSXSamp::GetCompressionRatio() {
|
||||
return ((28.0 / 16.0) * 2); // aka 3.5;
|
||||
}
|
||||
|
||||
void PSXSamp::ConvertToStdWave(uint8 *buf) {
|
||||
int16 *uncompBuf = (int16 *) buf;
|
||||
VAGBlk theBlock;
|
||||
f32 prev1 = 0;
|
||||
f32 prev2 = 0;
|
||||
|
||||
if (this->_setLoopOnConversion)
|
||||
SetLoopStatus(0); // loopStatus is initiated to -1. We should default it now to not loop
|
||||
|
||||
bool addrOutOfVirtFile = false;
|
||||
for (uint32 k = 0; k < _dataLength; k += 0x10) // for every adpcm chunk
|
||||
{
|
||||
if (_dwOffset + k + 16 > _vgmfile->GetEndOffset()) {
|
||||
debug("Unexpected EOF (%s)", _name.c_str());
|
||||
break;
|
||||
} else if (!addrOutOfVirtFile && k + 16 > _unLength) {
|
||||
debug("Unexpected end of PSXSamp (%s)", _name.c_str());
|
||||
addrOutOfVirtFile = true;
|
||||
}
|
||||
|
||||
theBlock.range = GetByte(_dwOffset + k) & 0xF;
|
||||
theBlock.filter = (GetByte(_dwOffset + k) & 0xF0) >> 4;
|
||||
theBlock.flag.end = GetByte(_dwOffset + k + 1) & 1;
|
||||
theBlock.flag.looping = (GetByte(_dwOffset + k + 1) & 2) > 0;
|
||||
|
||||
// this can be the loop point, but in wd, this info is stored in the instrset
|
||||
theBlock.flag.loop = (GetByte(_dwOffset + k + 1) & 4) > 0;
|
||||
if (this->_setLoopOnConversion) {
|
||||
if (theBlock.flag.loop) {
|
||||
this->SetLoopOffset(k);
|
||||
this->SetLoopLength(_dataLength - k);
|
||||
}
|
||||
if (theBlock.flag.end && theBlock.flag.looping) {
|
||||
SetLoopStatus(1);
|
||||
}
|
||||
}
|
||||
|
||||
GetRawFile()->GetBytes(_dwOffset + k + 2, 14, theBlock.brr);
|
||||
|
||||
// each decompressed pcm block is 52 bytes EDIT: (wait, isn't it 56 bytes? or is it 28?)
|
||||
DecompVAGBlk(uncompBuf + ((k * 28) / 16), &theBlock, &prev1, &prev2);
|
||||
}
|
||||
}
|
||||
|
||||
// This next function is taken from Antires's work
|
||||
void PSXSamp::DecompVAGBlk(int16 *pSmp, VAGBlk *pVBlk, f32 *prev1, f32 *prev2) {
|
||||
uint32 i, shift; // Shift amount for compressed samples
|
||||
f32 t; // Temporary sample
|
||||
f32 f1, f2;
|
||||
f32 p1, p2;
|
||||
static const f32 Coeff[5][2] = {{0.0, 0.0},
|
||||
{60.0 / 64.0, 0.0},
|
||||
{115.0 / 64.0, 52.0 / 64.0},
|
||||
{98.0 / 64.0, 55.0 / 64.0},
|
||||
{122.0 / 64.0, 60.0 / 64.0}};
|
||||
|
||||
// Expand samples ---------------------------
|
||||
shift = pVBlk->range + 16;
|
||||
|
||||
for (i = 0; i < 14; i++) {
|
||||
pSmp[i * 2] = ((int32) pVBlk->brr[i] << 28) >> shift;
|
||||
pSmp[i * 2 + 1] = ((int32) (pVBlk->brr[i] & 0xF0) << 24) >> shift;
|
||||
}
|
||||
|
||||
// Apply ADPCM decompression ----------------
|
||||
i = pVBlk->filter;
|
||||
|
||||
if (i) {
|
||||
f1 = Coeff[i][0];
|
||||
f2 = Coeff[i][1];
|
||||
p1 = *prev1;
|
||||
p2 = *prev2;
|
||||
|
||||
for (i = 0; i < 28; i++) {
|
||||
t = pSmp[i] + (p1 * f1) - (p2 * f2);
|
||||
pSmp[i] = (int16) t;
|
||||
p2 = p1;
|
||||
p1 = t;
|
||||
}
|
||||
|
||||
*prev1 = p1;
|
||||
*prev2 = p2;
|
||||
} else {
|
||||
*prev2 = pSmp[26];
|
||||
*prev1 = pSmp[27];
|
||||
}
|
||||
}
|
||||
428
audio/soundfont/vab/psxspu.h
Normal file
428
audio/soundfont/vab/psxspu.h
Normal file
@@ -0,0 +1,428 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
/*
|
||||
* VGMTrans (c) 2002-2019
|
||||
* Licensed under the zlib license,
|
||||
* refer to the included VGMTrans_LICENSE.txt file
|
||||
*/
|
||||
#ifndef AUDIO_SOUNDFONT_PSXSPU_H
|
||||
#define AUDIO_SOUNDFONT_PSXSPU_H
|
||||
|
||||
#include "audio/soundfont/common.h"
|
||||
#include "common/str.h"
|
||||
#include "common/util.h"
|
||||
#include "audio/soundfont/vgminstrset.h"
|
||||
#include "audio/soundfont/vgmsamp.h"
|
||||
#include "audio/soundfont/vgmitem.h"
|
||||
|
||||
// All of the ADSR calculations herein (except where inaccurate) are derived from Neill Corlett's
|
||||
// work in reverse-engineering the Playstation 1/2 SPU unit.
|
||||
|
||||
//**************************************************************************************************
|
||||
// Type Redefinitions
|
||||
|
||||
typedef void v0;
|
||||
|
||||
#ifdef __cplusplus
|
||||
#if defined __BORLANDC__
|
||||
typedef bool b8;
|
||||
#else
|
||||
typedef unsigned char b8;
|
||||
#endif
|
||||
#else
|
||||
typedef char b8;
|
||||
#endif
|
||||
|
||||
typedef float f32;
|
||||
//***********************************************************************************************
|
||||
|
||||
static unsigned long RateTable[160];
|
||||
static bool bRateTableInitialized = 0;
|
||||
|
||||
// VAG format -----------------------------------
|
||||
|
||||
// Sample Block
|
||||
typedef struct _VAGBlk {
|
||||
uint8 range;
|
||||
uint8 filter;
|
||||
|
||||
struct {
|
||||
b8 end: 1; // End block
|
||||
b8 looping: 1; // VAG loops
|
||||
b8 loop: 1; // Loop start point
|
||||
} flag;
|
||||
|
||||
int8 brr[14]; // Compressed samples
|
||||
} VAGBlk;
|
||||
|
||||
double LinAmpDecayTimeToLinDBDecayTime(double secondsToFullAtten, int linearVolumeRange);
|
||||
|
||||
// InitADSR is shamelessly ripped from P.E.Op.S
|
||||
static inline void InitADSR() {
|
||||
unsigned long r, rs, rd;
|
||||
int i;
|
||||
|
||||
// build the rate table according to Neill's rules
|
||||
memset(RateTable, 0, sizeof(unsigned long) * 160);
|
||||
|
||||
r = 3;
|
||||
rs = 1;
|
||||
rd = 0;
|
||||
|
||||
// we start at pos 32 with the real values... everything before is 0
|
||||
for (i = 32; i < 160; i++) {
|
||||
if (r < 0x3FFFFFFF) {
|
||||
r += rs;
|
||||
rd++;
|
||||
if (rd == 5) {
|
||||
rd = 1;
|
||||
rs *= 2;
|
||||
}
|
||||
}
|
||||
if (r > 0x3FFFFFFF)
|
||||
r = 0x3FFFFFFF;
|
||||
|
||||
RateTable[i] = r;
|
||||
}
|
||||
}
|
||||
|
||||
inline int RoundToZero(int val) {
|
||||
if (val < 0)
|
||||
val = 0;
|
||||
return val;
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void PSXConvADSR(T *realADSR, unsigned short ADSR1, unsigned short ADSR2, bool bPS2) {
|
||||
uint8 Am = (ADSR1 & 0x8000) >> 15; // if 1, then Exponential, else linear
|
||||
uint8 Ar = (ADSR1 & 0x7F00) >> 8;
|
||||
uint8 Dr = (ADSR1 & 0x00F0) >> 4;
|
||||
uint8 Sl = ADSR1 & 0x000F;
|
||||
uint8 Rm = (ADSR2 & 0x0020) >> 5;
|
||||
uint8 Rr = ADSR2 & 0x001F;
|
||||
|
||||
// The following are unimplemented in conversion (because DLS and SF2 do not support Sustain
|
||||
// Rate)
|
||||
uint8 Sm = (ADSR2 & 0x8000) >> 15;
|
||||
uint8 Sd = (ADSR2 & 0x4000) >> 14;
|
||||
uint8 Sr = (ADSR2 >> 6) & 0x7F;
|
||||
|
||||
PSXConvADSR(realADSR, Am, Ar, Dr, Sl, Sm, Sd, Sr, Rm, Rr, bPS2);
|
||||
}
|
||||
|
||||
template<class T>
|
||||
void PSXConvADSR(T *realADSR, uint8 Am, uint8 Ar, uint8 Dr, uint8 Sl, uint8 Sm,
|
||||
uint8 Sd, uint8 Sr, uint8 Rm, uint8 Rr, bool bPS2) {
|
||||
// Make sure all the ADSR values are within the valid ranges
|
||||
if (((Am & ~0x01) != 0) || ((Ar & ~0x7F) != 0) || ((Dr & ~0x0F) != 0) || ((Sl & ~0x0F) != 0) ||
|
||||
((Rm & ~0x01) != 0) || ((Rr & ~0x1F) != 0) || ((Sm & ~0x01) != 0) || ((Sd & ~0x01) != 0) ||
|
||||
((Sr & ~0x7F) != 0)) {
|
||||
error("ADSR parameter(s) out of range");
|
||||
}
|
||||
|
||||
// PS1 games use 44k, PS2 uses 48k
|
||||
double sampleRate = bPS2 ? 48000 : 44100;
|
||||
|
||||
long envelope_level;
|
||||
double samples = 0.0;
|
||||
unsigned long rate;
|
||||
unsigned long remainder;
|
||||
double timeInSecs;
|
||||
int l;
|
||||
|
||||
if (!bRateTableInitialized) {
|
||||
InitADSR();
|
||||
bRateTableInitialized = true;
|
||||
}
|
||||
|
||||
// to get the dls 32 bit time cents, take log base 2 of number of seconds * 1200 * 65536
|
||||
// (dls1v11a.pdf p25).
|
||||
|
||||
// if (RateTable[(Ar^0x7F)-0x10 + 32] == 0)
|
||||
// realADSR->attack_time = 0;
|
||||
// else
|
||||
// {
|
||||
if ((Ar ^ 0x7F) < 0x10)
|
||||
Ar = 0;
|
||||
// if linear Ar Mode
|
||||
if (Am == 0) {
|
||||
rate = RateTable[RoundToZero((Ar ^ 0x7F) - 0x10) + 32];
|
||||
samples = ceil(0x7FFFFFFF / (double) rate);
|
||||
} else if (Am == 1) {
|
||||
rate = RateTable[RoundToZero((Ar ^ 0x7F) - 0x10) + 32];
|
||||
samples = (unsigned long)(0x60000000 / rate);
|
||||
remainder = 0x60000000 % rate;
|
||||
rate = RateTable[RoundToZero((Ar ^ 0x7F) - 0x18) + 32];
|
||||
samples += ceil(MAX<double>(0, 0x1FFFFFFF - (long) remainder) / (double) rate);
|
||||
}
|
||||
timeInSecs = samples / sampleRate;
|
||||
realADSR->_attack_time = timeInSecs;
|
||||
// }
|
||||
|
||||
// Decay Time
|
||||
|
||||
envelope_level = 0x7FFFFFFF;
|
||||
|
||||
bool bSustainLevFound = false;
|
||||
uint32 realSustainLevel = 0x7FFFFFFF;
|
||||
// DLS decay rate value is to -96db (silence) not the sustain level
|
||||
for (l = 0; envelope_level > 0; l++) {
|
||||
if (4 * (Dr ^ 0x1F) < 0x18)
|
||||
Dr = 0;
|
||||
switch ((envelope_level >> 28) & 0x7) {
|
||||
case 0:
|
||||
envelope_level -= RateTable[RoundToZero((4 * (Dr ^ 0x1F)) - 0x18 + 0) + 32];
|
||||
break;
|
||||
case 1:
|
||||
envelope_level -= RateTable[RoundToZero((4 * (Dr ^ 0x1F)) - 0x18 + 4) + 32];
|
||||
break;
|
||||
case 2:
|
||||
envelope_level -= RateTable[RoundToZero((4 * (Dr ^ 0x1F)) - 0x18 + 6) + 32];
|
||||
break;
|
||||
case 3:
|
||||
envelope_level -= RateTable[RoundToZero((4 * (Dr ^ 0x1F)) - 0x18 + 8) + 32];
|
||||
break;
|
||||
case 4:
|
||||
envelope_level -= RateTable[RoundToZero((4 * (Dr ^ 0x1F)) - 0x18 + 9) + 32];
|
||||
break;
|
||||
case 5:
|
||||
envelope_level -= RateTable[RoundToZero((4 * (Dr ^ 0x1F)) - 0x18 + 10) + 32];
|
||||
break;
|
||||
case 6:
|
||||
envelope_level -= RateTable[RoundToZero((4 * (Dr ^ 0x1F)) - 0x18 + 11) + 32];
|
||||
break;
|
||||
case 7:
|
||||
envelope_level -= RateTable[RoundToZero((4 * (Dr ^ 0x1F)) - 0x18 + 12) + 32];
|
||||
break;
|
||||
}
|
||||
if (!bSustainLevFound && ((envelope_level >> 27) & 0xF) <= Sl) {
|
||||
realSustainLevel = envelope_level;
|
||||
bSustainLevFound = true;
|
||||
}
|
||||
}
|
||||
samples = l;
|
||||
timeInSecs = samples / sampleRate;
|
||||
realADSR->_decay_time = timeInSecs;
|
||||
|
||||
// Sustain Rate
|
||||
|
||||
envelope_level = 0x7FFFFFFF;
|
||||
// increasing... we won't even bother
|
||||
if (Sd == 0) {
|
||||
realADSR->_sustain_time = -1;
|
||||
} else {
|
||||
if (Sr == 0x7F)
|
||||
realADSR->_sustain_time = -1; // this is actually infinite
|
||||
else {
|
||||
// linear
|
||||
if (Sm == 0) {
|
||||
rate = RateTable[RoundToZero((Sr ^ 0x7F) - 0x0F) + 32];
|
||||
samples = ceil(0x7FFFFFFF / (double) rate);
|
||||
} else {
|
||||
l = 0;
|
||||
// DLS decay rate value is to -96db (silence) not the sustain level
|
||||
while (envelope_level > 0) {
|
||||
long envelope_level_diff;
|
||||
long envelope_level_target;
|
||||
|
||||
switch ((envelope_level >> 28) & 0x7) {
|
||||
case 0:
|
||||
default:
|
||||
envelope_level_target = 0x00000000;
|
||||
envelope_level_diff =
|
||||
RateTable[RoundToZero((Sr ^ 0x7F) - 0x1B + 0) + 32];
|
||||
break;
|
||||
case 1:
|
||||
envelope_level_target = 0x0fffffff;
|
||||
envelope_level_diff =
|
||||
RateTable[RoundToZero((Sr ^ 0x7F) - 0x1B + 4) + 32];
|
||||
break;
|
||||
case 2:
|
||||
envelope_level_target = 0x1fffffff;
|
||||
envelope_level_diff =
|
||||
RateTable[RoundToZero((Sr ^ 0x7F) - 0x1B + 6) + 32];
|
||||
break;
|
||||
case 3:
|
||||
envelope_level_target = 0x2fffffff;
|
||||
envelope_level_diff =
|
||||
RateTable[RoundToZero((Sr ^ 0x7F) - 0x1B + 8) + 32];
|
||||
break;
|
||||
case 4:
|
||||
envelope_level_target = 0x3fffffff;
|
||||
envelope_level_diff =
|
||||
RateTable[RoundToZero((Sr ^ 0x7F) - 0x1B + 9) + 32];
|
||||
break;
|
||||
case 5:
|
||||
envelope_level_target = 0x4fffffff;
|
||||
envelope_level_diff =
|
||||
RateTable[RoundToZero((Sr ^ 0x7F) - 0x1B + 10) + 32];
|
||||
break;
|
||||
case 6:
|
||||
envelope_level_target = 0x5fffffff;
|
||||
envelope_level_diff =
|
||||
RateTable[RoundToZero((Sr ^ 0x7F) - 0x1B + 11) + 32];
|
||||
break;
|
||||
case 7:
|
||||
envelope_level_target = 0x6fffffff;
|
||||
envelope_level_diff =
|
||||
RateTable[RoundToZero((Sr ^ 0x7F) - 0x1B + 12) + 32];
|
||||
break;
|
||||
}
|
||||
|
||||
long steps =
|
||||
(envelope_level - envelope_level_target + (envelope_level_diff - 1)) /
|
||||
envelope_level_diff;
|
||||
envelope_level -= (envelope_level_diff * steps);
|
||||
l += steps;
|
||||
}
|
||||
samples = l;
|
||||
}
|
||||
timeInSecs = samples / sampleRate;
|
||||
realADSR->_sustain_time =
|
||||
/*Sm ? timeInSecs : */ LinAmpDecayTimeToLinDBDecayTime(timeInSecs, 0x800);
|
||||
}
|
||||
}
|
||||
|
||||
// Sustain Level
|
||||
// realADSR->sustain_level =
|
||||
// (double)envelope_level/(double)0x7FFFFFFF;//(long)ceil((double)envelope_level *
|
||||
// 0.030517578139210854); //in DLS, sustain level is measured as a percentage
|
||||
if (Sl == 0)
|
||||
realSustainLevel = 0x07FFFFFF;
|
||||
realADSR->_sustain_level = realSustainLevel / (double) 0x7FFFFFFF;
|
||||
|
||||
// If decay is going unused, and there's a sustain rate with sustain level close to max...
|
||||
// we'll put the sustain_rate in place of the decay rate.
|
||||
if ((realADSR->_decay_time < 2 || (Dr == 0x0F && Sl >= 0x0C)) && Sr < 0x7E && Sd == 1) {
|
||||
realADSR->_sustain_level = 0;
|
||||
realADSR->_decay_time = realADSR->_sustain_time;
|
||||
// realADSR->decay_time = 0.5;
|
||||
}
|
||||
|
||||
// Release Time
|
||||
|
||||
// sustain_envelope_level = envelope_level;
|
||||
|
||||
// We do this because we measure release time from max volume to 0, not from sustain level to 0
|
||||
envelope_level = 0x7FFFFFFF;
|
||||
|
||||
// if linear Rr Mode
|
||||
if (Rm == 0) {
|
||||
rate = RateTable[RoundToZero((4 * (Rr ^ 0x1F)) - 0x0C) + 32];
|
||||
|
||||
if (rate != 0)
|
||||
samples = ceil((double) envelope_level / (double) rate);
|
||||
else
|
||||
samples = 0;
|
||||
} else if (Rm == 1) {
|
||||
if ((Rr ^ 0x1F) * 4 < 0x18)
|
||||
Rr = 0;
|
||||
for (l = 0; envelope_level > 0; l++) {
|
||||
switch ((envelope_level >> 28) & 0x7) {
|
||||
case 0:
|
||||
envelope_level -= RateTable[RoundToZero((4 * (Rr ^ 0x1F)) - 0x18 + 0) + 32];
|
||||
break;
|
||||
case 1:
|
||||
envelope_level -= RateTable[RoundToZero((4 * (Rr ^ 0x1F)) - 0x18 + 4) + 32];
|
||||
break;
|
||||
case 2:
|
||||
envelope_level -= RateTable[RoundToZero((4 * (Rr ^ 0x1F)) - 0x18 + 6) + 32];
|
||||
break;
|
||||
case 3:
|
||||
envelope_level -= RateTable[RoundToZero((4 * (Rr ^ 0x1F)) - 0x18 + 8) + 32];
|
||||
break;
|
||||
case 4:
|
||||
envelope_level -= RateTable[RoundToZero((4 * (Rr ^ 0x1F)) - 0x18 + 9) + 32];
|
||||
break;
|
||||
case 5:
|
||||
envelope_level -= RateTable[RoundToZero((4 * (Rr ^ 0x1F)) - 0x18 + 10) + 32];
|
||||
break;
|
||||
case 6:
|
||||
envelope_level -= RateTable[RoundToZero((4 * (Rr ^ 0x1F)) - 0x18 + 11) + 32];
|
||||
break;
|
||||
case 7:
|
||||
envelope_level -= RateTable[RoundToZero((4 * (Rr ^ 0x1F)) - 0x18 + 12) + 32];
|
||||
break;
|
||||
}
|
||||
}
|
||||
samples = l;
|
||||
}
|
||||
timeInSecs = samples / sampleRate;
|
||||
|
||||
// theRate = timeInSecs / sustain_envelope_level;
|
||||
// timeInSecs = 0x7FFFFFFF * theRate; //the release time value is more like a rate. It is the
|
||||
// time from max value to 0, not from sustain level. if (Rm == 0) // if it's linear timeInSecs *=
|
||||
//LINEAR_RELEASE_COMPENSATION;
|
||||
|
||||
realADSR->_release_time =
|
||||
/*Rm ? timeInSecs : */ LinAmpDecayTimeToLinDBDecayTime(timeInSecs, 0x800);
|
||||
|
||||
// We need to compensate the decay and release times to represent them as the time from full vol
|
||||
// to -100db where the drop in db is a fixed amount per time unit (SoundFont2 spec for vol
|
||||
// envelopes, pg44.)
|
||||
// We assume the psx envelope is using a linear scale wherein envelope_level / 2 == half
|
||||
// loudness. For a linear release mode (Rm == 0), the time to reach half volume is simply half
|
||||
// the time to reach 0.
|
||||
// Half perceived loudness is -10db. Therefore, time_to_half_vol * 10 == full_time * 5 == the
|
||||
// correct SF2 time
|
||||
// realADSR->decay_time = LinAmpDecayTimeToLinDBDecayTime(realADSR->decay_time, 0x800);
|
||||
// realADSR->sustain_time = LinAmpDecayTimeToLinDBDecayTime(realADSR->sustain_time, 0x800);
|
||||
// realADSR->release_time = LinAmpDecayTimeToLinDBDecayTime(realADSR->release_time, 0x800);
|
||||
|
||||
// Calculations are done, so now add the articulation data
|
||||
// artic->AddADSR(attack_time, Am, decay_time, sustain_lev, release_time, 0);
|
||||
}
|
||||
|
||||
class PSXSampColl : public VGMSampColl {
|
||||
public:
|
||||
PSXSampColl(VGMInstrSet *instrset, uint32 offset, uint32 length,
|
||||
const Common::Array<SizeOffsetPair> &vagLocations);
|
||||
|
||||
virtual bool
|
||||
GetSampleInfo(); // retrieve sample info, including pointers to data, # channels, rate, etc.
|
||||
|
||||
protected:
|
||||
Common::Array<SizeOffsetPair> _vagLocations;
|
||||
};
|
||||
|
||||
class PSXSamp : public VGMSamp {
|
||||
public:
|
||||
PSXSamp(VGMSampColl *sampColl, uint32 offset, uint32 length, uint32 dataOffset,
|
||||
uint32 dataLen, uint8 nChannels, uint16 theBPS, uint32 theRate,
|
||||
Common::String name, bool bSetLoopOnConversion = true);
|
||||
|
||||
~PSXSamp() override {}
|
||||
|
||||
// ratio of space conserved. should generally be > 1
|
||||
// used to calculate both uncompressed sample size and loopOff after conversion
|
||||
double GetCompressionRatio() override;
|
||||
|
||||
void ConvertToStdWave(uint8 *buf) override;
|
||||
|
||||
private:
|
||||
void DecompVAGBlk(int16 *pSmp, VAGBlk *pVBlk, f32 *prev1, f32 *prev2);
|
||||
|
||||
public:
|
||||
|
||||
bool _setLoopOnConversion;
|
||||
};
|
||||
|
||||
#endif // AUDIO_SOUNDFONT_PSXSPU_H
|
||||
263
audio/soundfont/vab/vab.cpp
Normal file
263
audio/soundfont/vab/vab.cpp
Normal file
@@ -0,0 +1,263 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
/*
|
||||
* VGMTrans (c) 2002-2019
|
||||
* Licensed under the zlib license,
|
||||
* refer to the included VGMTrans_LICENSE.txt file
|
||||
*/
|
||||
|
||||
#include "common/debug.h"
|
||||
#include "common/scummsys.h"
|
||||
#include "vab.h"
|
||||
#include "psxspu.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
Vab::Vab(RawFile *file, uint32 offset) : VGMInstrSet(file, offset) {}
|
||||
|
||||
Vab::~Vab() {}
|
||||
|
||||
bool Vab::GetHeaderInfo() {
|
||||
uint32 nEndOffset = GetEndOffset();
|
||||
uint32 nMaxLength = nEndOffset - _dwOffset;
|
||||
|
||||
if (nMaxLength < 0x20) {
|
||||
return false;
|
||||
}
|
||||
|
||||
_name = "VAB";
|
||||
|
||||
VGMHeader *vabHdr = AddHeader(_dwOffset, 0x20, "VAB Header");
|
||||
vabHdr->AddSimpleItem(_dwOffset + 0x00, 4, "ID");
|
||||
vabHdr->AddSimpleItem(_dwOffset + 0x04, 4, "Version");
|
||||
vabHdr->AddSimpleItem(_dwOffset + 0x08, 4, "VAB ID");
|
||||
vabHdr->AddSimpleItem(_dwOffset + 0x0c, 4, "Total Size");
|
||||
vabHdr->AddSimpleItem(_dwOffset + 0x10, 2, "Reserved");
|
||||
vabHdr->AddSimpleItem(_dwOffset + 0x12, 2, "Number of Programs");
|
||||
vabHdr->AddSimpleItem(_dwOffset + 0x14, 2, "Number of Tones");
|
||||
vabHdr->AddSimpleItem(_dwOffset + 0x16, 2, "Number of VAGs");
|
||||
vabHdr->AddSimpleItem(_dwOffset + 0x18, 1, "Master Volume");
|
||||
vabHdr->AddSimpleItem(_dwOffset + 0x19, 1, "Master Pan");
|
||||
vabHdr->AddSimpleItem(_dwOffset + 0x1a, 1, "Bank Attributes 1");
|
||||
vabHdr->AddSimpleItem(_dwOffset + 0x1b, 1, "Bank Attributes 2");
|
||||
vabHdr->AddSimpleItem(_dwOffset + 0x1c, 4, "Reserved");
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool Vab::GetInstrPointers() {
|
||||
uint32 nEndOffset = GetEndOffset();
|
||||
|
||||
uint32 offProgs = _dwOffset + 0x20;
|
||||
uint32 offToneAttrs = offProgs + (16 * 128);
|
||||
|
||||
uint16 numPrograms = GetShort(_dwOffset + 0x12);
|
||||
uint16 numVAGs = GetShort(_dwOffset + 0x16);
|
||||
|
||||
uint32 offVAGOffsets = offToneAttrs + (32 * 16 * numPrograms);
|
||||
|
||||
VGMHeader *progsHdr = AddHeader(offProgs, 16 * 128, "Program Table");
|
||||
VGMHeader *toneAttrsHdr = AddHeader(offToneAttrs, 32 * 16, "Tone Attributes Table");
|
||||
|
||||
if (numPrograms > 128) {
|
||||
debug("Too many programs %x, offset %x", numPrograms, _dwOffset);
|
||||
return false;
|
||||
}
|
||||
if (numVAGs > 255) {
|
||||
debug("Too many VAGs %x, offset %x", numVAGs, _dwOffset);
|
||||
return false;
|
||||
}
|
||||
|
||||
// Load each instruments.
|
||||
//
|
||||
// Rule 1. Valid instrument pointers are not always sequentially located from 0 to (numProgs -
|
||||
// 1). Number of tones can be 0. That's an empty instrument. We need to ignore it. See Clock
|
||||
// Tower PSF for example.
|
||||
//
|
||||
// Rule 2. Do not load programs more than number of programs. Even if a program table value is
|
||||
// provided. Otherwise an out-of-order access can be caused in Tone Attributes Table. See the
|
||||
// swimming event BGM of Aitakute... ~your smiles in my heart~ for example. (github issue #115)
|
||||
uint32 numProgramsLoaded = 0;
|
||||
for (uint32 progIndex = 0; progIndex < 128 && numProgramsLoaded < numPrograms; progIndex++) {
|
||||
uint32 offCurrProg = offProgs + (progIndex * 16);
|
||||
uint32 offCurrToneAttrs = offToneAttrs + (uint32) (_aInstrs.size() * 32 * 16);
|
||||
|
||||
if (offCurrToneAttrs + (32 * 16) > nEndOffset) {
|
||||
break;
|
||||
}
|
||||
|
||||
uint8 numTonesPerInstr = GetByte(offCurrProg);
|
||||
if (numTonesPerInstr > 32) {
|
||||
debug("Program %x contains too many tones (%d)", progIndex, numTonesPerInstr);
|
||||
} else if (numTonesPerInstr != 0) {
|
||||
VabInstr *newInstr = new VabInstr(this, offCurrToneAttrs, 0x20 * 16, 0, progIndex);
|
||||
_aInstrs.push_back(newInstr);
|
||||
newInstr->_tones = GetByte(offCurrProg + 0);
|
||||
|
||||
VGMHeader *progHdr = progsHdr->AddHeader(offCurrProg, 0x10, "Program");
|
||||
progHdr->AddSimpleItem(offCurrProg + 0x00, 1, "Number of Tones");
|
||||
progHdr->AddSimpleItem(offCurrProg + 0x01, 1, "Volume");
|
||||
progHdr->AddSimpleItem(offCurrProg + 0x02, 1, "Priority");
|
||||
progHdr->AddSimpleItem(offCurrProg + 0x03, 1, "Mode");
|
||||
progHdr->AddSimpleItem(offCurrProg + 0x04, 1, "Pan");
|
||||
progHdr->AddSimpleItem(offCurrProg + 0x05, 1, "Reserved");
|
||||
progHdr->AddSimpleItem(offCurrProg + 0x06, 2, "Attribute");
|
||||
progHdr->AddSimpleItem(offCurrProg + 0x08, 4, "Reserved");
|
||||
progHdr->AddSimpleItem(offCurrProg + 0x0c, 4, "Reserved");
|
||||
|
||||
newInstr->_masterVol = GetByte(offCurrProg + 0x01);
|
||||
|
||||
toneAttrsHdr->_unLength = offCurrToneAttrs + (32 * 16) - offToneAttrs;
|
||||
|
||||
numProgramsLoaded++;
|
||||
}
|
||||
}
|
||||
|
||||
if ((offVAGOffsets + 2 * 256) <= nEndOffset) {
|
||||
char name[256];
|
||||
Common::Array<SizeOffsetPair> vagLocations;
|
||||
uint32 totalVAGSize = 0;
|
||||
VGMHeader *vagOffsetHdr = AddHeader(offVAGOffsets, 2 * 256, "VAG Pointer Table");
|
||||
|
||||
uint32 vagStartOffset = GetShort(offVAGOffsets) * 8;
|
||||
vagOffsetHdr->AddSimpleItem(offVAGOffsets, 2, "VAG Size /8 #0");
|
||||
totalVAGSize = vagStartOffset;
|
||||
|
||||
for (uint32 i = 0; i < numVAGs; i++) {
|
||||
uint32 vagOffset;
|
||||
uint32 vagSize;
|
||||
|
||||
if (i == 0) {
|
||||
vagOffset = vagStartOffset;
|
||||
vagSize = GetShort(offVAGOffsets + (i + 1) * 2) * 8;
|
||||
} else {
|
||||
vagOffset = vagStartOffset + vagLocations[i - 1].offset + vagLocations[i - 1].size;
|
||||
vagSize = GetShort(offVAGOffsets + (i + 1) * 2) * 8;
|
||||
}
|
||||
|
||||
snprintf(name, sizeof(name), "VAG Size /8 #%u", i + 1);
|
||||
vagOffsetHdr->AddSimpleItem(offVAGOffsets + (i + 1) * 2, 2, name);
|
||||
|
||||
if (vagOffset + vagSize <= nEndOffset) {
|
||||
vagLocations.push_back(SizeOffsetPair(vagOffset, vagSize));
|
||||
totalVAGSize += vagSize;
|
||||
} else {
|
||||
debug("VAG #%d at %x with size %x) is invalid", i + 1, vagOffset, vagSize);
|
||||
}
|
||||
}
|
||||
_unLength = (offVAGOffsets + 2 * 256) - _dwOffset;
|
||||
|
||||
// single VAB file?
|
||||
uint32 offVAGs = offVAGOffsets + 2 * 256;
|
||||
if (_dwOffset == 0 && vagLocations.size() != 0) {
|
||||
// load samples as well
|
||||
PSXSampColl *newSampColl =
|
||||
new PSXSampColl(this, offVAGs, totalVAGSize, vagLocations);
|
||||
if (newSampColl->LoadVGMFile()) {
|
||||
this->_sampColl = newSampColl;
|
||||
} else {
|
||||
delete newSampColl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
// ********
|
||||
// VabInstr
|
||||
// ********
|
||||
|
||||
VabInstr::VabInstr(VGMInstrSet *instrSet, uint32 offset, uint32 length, uint32 theBank,
|
||||
uint32 theInstrNum, const Common::String &name)
|
||||
: VGMInstr(instrSet, offset, length, theBank, theInstrNum, name), _tones(0), _masterVol(127) {}
|
||||
|
||||
VabInstr::~VabInstr() {}
|
||||
|
||||
bool VabInstr::LoadInstr() {
|
||||
int8 numRgns = _tones;
|
||||
for (int i = 0; i < numRgns; i++) {
|
||||
VabRgn *rgn = new VabRgn(this, _dwOffset + i * 0x20);
|
||||
if (!rgn->LoadRgn()) {
|
||||
delete rgn;
|
||||
return false;
|
||||
}
|
||||
_aRgns.push_back(rgn);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// ******
|
||||
// VabRgn
|
||||
// ******
|
||||
|
||||
VabRgn::VabRgn(VabInstr *instr, uint32 offset) : _ADSR1(0), _ADSR2(0), VGMRgn(instr, offset) {}
|
||||
|
||||
bool VabRgn::LoadRgn() {
|
||||
VabInstr *instr = (VabInstr *) _parInstr;
|
||||
_unLength = 0x20;
|
||||
|
||||
AddGeneralItem(_dwOffset, 1, "Priority");
|
||||
AddGeneralItem(_dwOffset + 1, 1, "Mode (use reverb?)");
|
||||
AddVolume((GetByte(_dwOffset + 2) * instr->_masterVol) / (127.0 * 127.0), _dwOffset + 2, 1);
|
||||
AddPan(GetByte(_dwOffset + 3), _dwOffset + 3);
|
||||
AddUnityKey(GetByte(_dwOffset + 4), _dwOffset + 4);
|
||||
AddGeneralItem(_dwOffset + 5, 1, "Pitch Tune");
|
||||
AddKeyLow(GetByte(_dwOffset + 6), _dwOffset + 6);
|
||||
AddKeyHigh(GetByte(_dwOffset + 7), _dwOffset + 7);
|
||||
AddGeneralItem(_dwOffset + 8, 1, "Vibrato Width");
|
||||
AddGeneralItem(_dwOffset + 9, 1, "Vibrato Time");
|
||||
AddGeneralItem(_dwOffset + 10, 1, "Portamento Width");
|
||||
AddGeneralItem(_dwOffset + 11, 1, "Portamento Holding Time");
|
||||
AddGeneralItem(_dwOffset + 12, 1, "Pitch Bend Min");
|
||||
AddGeneralItem(_dwOffset + 13, 1, "Pitch Bend Max");
|
||||
AddGeneralItem(_dwOffset + 14, 1, "Reserved");
|
||||
AddGeneralItem(_dwOffset + 15, 1, "Reserved");
|
||||
AddGeneralItem(_dwOffset + 16, 2, "ADSR1");
|
||||
AddGeneralItem(_dwOffset + 18, 2, "ADSR2");
|
||||
AddGeneralItem(_dwOffset + 20, 2, "Parent Program");
|
||||
AddSampNum(GetShort(_dwOffset + 22) - 1, _dwOffset + 22, 2);
|
||||
AddGeneralItem(_dwOffset + 24, 2, "Reserved");
|
||||
AddGeneralItem(_dwOffset + 26, 2, "Reserved");
|
||||
AddGeneralItem(_dwOffset + 28, 2, "Reserved");
|
||||
AddGeneralItem(_dwOffset + 30, 2, "Reserved");
|
||||
_ADSR1 = GetShort(_dwOffset + 16);
|
||||
_ADSR2 = GetShort(_dwOffset + 18);
|
||||
if ((int) _sampNum < 0)
|
||||
_sampNum = 0;
|
||||
|
||||
if (_keyLow > _keyHigh) {
|
||||
debug("Low key higher than high key %d > %d (at %x)", _keyLow, _keyHigh, _dwOffset);
|
||||
return false;
|
||||
}
|
||||
|
||||
// gocha: AFAIK, the valid range of pitch is 0-127. It must not be negative.
|
||||
// If it exceeds 127, driver clips the value and it will become 127. (In Hokuto no Ken, at
|
||||
// least) I am not sure if the interpretation of this value depends on a driver or VAB version.
|
||||
// The following code takes the byte as signed, since it could be a typical extended
|
||||
// implementation.
|
||||
int8 ft = (int8) GetByte(_dwOffset + 5);
|
||||
double cents = ft * 100.0 / 128.0;
|
||||
SetFineTune((int16) cents);
|
||||
|
||||
PSXConvADSR<VabRgn>(this, _ADSR1, _ADSR2, false);
|
||||
return true;
|
||||
}
|
||||
75
audio/soundfont/vab/vab.h
Normal file
75
audio/soundfont/vab/vab.h
Normal file
@@ -0,0 +1,75 @@
|
||||
/* ScummVM - Graphic Adventure Engine
|
||||
*
|
||||
* ScummVM is the legal property of its developers, whose names
|
||||
* are too numerous to list here. Please refer to the COPYRIGHT
|
||||
* file distributed with this source distribution.
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
/*
|
||||
* VGMTrans (c) 2002-2019
|
||||
* Licensed under the zlib license,
|
||||
* refer to the included VGMTrans_LICENSE.txt file
|
||||
*/
|
||||
#ifndef AUDIO_SOUNDFONT_VAB_H
|
||||
#define AUDIO_SOUNDFONT_VAB_H
|
||||
|
||||
#include "audio/soundfont/common.h"
|
||||
#include "common/str.h"
|
||||
#include "audio/soundfont/vgminstrset.h"
|
||||
#include "audio/soundfont/vgmsamp.h"
|
||||
|
||||
class Vab : public VGMInstrSet {
|
||||
public:
|
||||
Vab(RawFile *file, uint32 offset);
|
||||
virtual ~Vab(void);
|
||||
|
||||
virtual bool GetHeaderInfo();
|
||||
virtual bool GetInstrPointers();
|
||||
};
|
||||
|
||||
// ********
|
||||
// VabInstr
|
||||
// ********
|
||||
|
||||
class VabInstr : public VGMInstr {
|
||||
public:
|
||||
VabInstr(VGMInstrSet *instrSet, uint32 offset, uint32 length, uint32 theBank,
|
||||
uint32 theInstrNum, const Common::String &name = "Instrument");
|
||||
virtual ~VabInstr();
|
||||
|
||||
virtual bool LoadInstr();
|
||||
|
||||
public:
|
||||
uint8 _tones;
|
||||
uint8 _masterVol;
|
||||
};
|
||||
|
||||
// ******
|
||||
// VabRgn
|
||||
// ******
|
||||
|
||||
class VabRgn : public VGMRgn {
|
||||
public:
|
||||
VabRgn(VabInstr *instr, uint32 offset);
|
||||
|
||||
virtual bool LoadRgn();
|
||||
|
||||
public:
|
||||
uint16 _ADSR1; // raw ps2 ADSR1 value (articulation data)
|
||||
uint16 _ADSR2; // raw ps2 ADSR2 value (articulation data)
|
||||
};
|
||||
|
||||
#endif // AUDIO_SOUNDFONT_VAB_H
|
||||
Reference in New Issue
Block a user