/* 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 . * */ /* * VGMTrans (c) 2002-2019 * Licensed under the zlib license, * refer to the included VGMTrans_LICENSE.txt file */ #include "common/debug.h" #include "vgmcoll.h" #include "vgminstrset.h" #include "vgmsamp.h" using namespace std; double ConvertLogScaleValToAtten(double percent) { if (percent == 0) return 100.0; // assume 0 is -100.0db attenuation double atten = 20 * log10(percent) * 2; return MIN(-atten, 100.0); } // Convert a percent of volume value to it's attenuation in decibels. // ex: ConvertPercentVolToAttenDB_SF2(0.5) returns -(-6.02db) = half perceived loudness double ConvertPercentAmplitudeToAttenDB_SF2(double percent) { if (percent == 0) return 100.0; // assume 0 is -100.0db attenuation double atten = 20 * log10(percent); return MIN(-atten, 100.0); } void VGMColl::UnpackSampColl(SynthFile &synthfile, VGMSampColl *sampColl, Common::Array &finalSamps) { assert(sampColl != nullptr); size_t nSamples = sampColl->_samples.size(); for (size_t i = 0; i < nSamples; i++) { VGMSamp *samp = sampColl->_samples[i]; uint32 bufSize; if (samp->_ulUncompressedSize) bufSize = samp->_ulUncompressedSize; else bufSize = (uint32) ceil((double) samp->_dataLength * samp->GetCompressionRatio()); uint8 *uncompSampBuf = new uint8[bufSize]; // create a new memory space for the uncompressed wave samp->ConvertToStdWave(uncompSampBuf); // and uncompress into that space uint16 blockAlign = samp->_bps / 8 * samp->_channels; SynthWave *wave = synthfile.AddWave(1, samp->_channels, samp->_rate, samp->_rate * blockAlign, blockAlign, samp->_bps, bufSize, uncompSampBuf, (samp->_name)); finalSamps.push_back(samp); // If we don't have any loop information, then don't create a sampInfo structure for the // Wave if (samp->_loop.loopStatus == -1) { debug("No loop information for %s - some parameters might be incorrect", samp->_sampName.c_str()); return; } SynthSampInfo *sampInfo = wave->AddSampInfo(); if (samp->_bPSXLoopInfoPrioritizing) { if (samp->_loop.loopStart != 0 || samp->_loop.loopLength != 0) sampInfo->SetLoopInfo(samp->_loop, samp); } else sampInfo->SetLoopInfo(samp->_loop, samp); double attenuation = (samp->_volume != -1) ? ConvertLogScaleValToAtten(samp->_volume) : 0; uint8 unityKey = (samp->_unityKey != -1) ? samp->_unityKey : 0x3C; short fineTune = samp->_fineTune; sampInfo->SetPitchInfo(unityKey, fineTune, attenuation); } } SF2File *VGMColl::CreateSF2File(VGMInstrSet *theInstrSet) { SynthFile *synthfile = CreateSynthFile(theInstrSet); if (!synthfile) { debug("SF2 conversion aborted"); return nullptr; } SF2File *sf2file = new SF2File(synthfile); delete synthfile; return sf2file; } SynthFile *VGMColl::CreateSynthFile(VGMInstrSet *theInstrSet) { Common::Array instrsets; instrsets.push_back(theInstrSet); if (instrsets.empty()) { debug("No instruments found."); return nullptr; } /* FIXME: shared_ptr eventually */ SynthFile *synthfile = new SynthFile("SynthFile"); Common::Array finalSamps; Common::Array finalSampColls; for (uint32 i = 0; i < instrsets.size(); i++) { VGMSampColl *instrset_sampcoll = instrsets[i]->_sampColl; if (instrset_sampcoll) { finalSampColls.push_back(instrset_sampcoll); UnpackSampColl(*synthfile, instrset_sampcoll, finalSamps); } } if (finalSamps.empty()) { debug("No sample collection present"); delete synthfile; return nullptr; } for (size_t inst = 0; inst < instrsets.size(); inst++) { VGMInstrSet *set = instrsets[inst]; size_t nInstrs = set->_aInstrs.size(); for (size_t i = 0; i < nInstrs; i++) { VGMInstr *vgminstr = set->_aInstrs[i]; size_t nRgns = vgminstr->_aRgns.size(); if (nRgns == 0) // do not write an instrument if it has no regions continue; SynthInstr *newInstr = synthfile->AddInstr(vgminstr->_bank, vgminstr->_instrNum); for (uint32 j = 0; j < nRgns; j++) { VGMRgn *rgn = vgminstr->_aRgns[j]; // if (rgn->sampNum+1 > sampColl->samples.size()) ////does thereferenced sample exist? continue; // Determine the SampColl associated with this rgn. If there's an explicit pointer // to it, use that. VGMSampColl *sampColl = rgn->_sampCollPtr; if (!sampColl) { // If rgn is of an InstrSet with an embedded SampColl, use that SampColl. if (((VGMInstrSet *) rgn->_vgmfile)->_sampColl) sampColl = ((VGMInstrSet *) rgn->_vgmfile)->_sampColl; // If that does not exist, assume the first SampColl else sampColl = finalSampColls[0]; } // Determine the sample number within the rgn's associated SampColl size_t realSampNum = rgn->_sampNum; // Determine the sampCollNum (index into our finalSampColls vector) size_t sampCollNum = finalSampColls.size(); for (size_t k = 0; k < finalSampColls.size(); k++) { if (finalSampColls[k] == sampColl) sampCollNum = k; } if (sampCollNum == finalSampColls.size()) { debug("SampColl does not exist"); delete synthfile; return nullptr; } // now we add the number of samples from the preceding SampColls to the value to // get the real sampNum in the final DLS file. for (uint32 k = 0; k < sampCollNum; k++) realSampNum += finalSampColls[k]->_samples.size(); SynthRgn *newRgn = newInstr->AddRgn(); newRgn->SetRanges(rgn->_keyLow, rgn->_keyHigh, rgn->_velLow, rgn->_velHigh); newRgn->SetWaveLinkInfo(0, 0, 1, (uint32) realSampNum); if (realSampNum >= finalSamps.size()) { debug("Sample %u does not exist", (uint32) realSampNum); realSampNum = finalSamps.size() - 1; } VGMSamp *samp = finalSamps[realSampNum]; // sampColl->samples[rgn->sampNum]; SynthSampInfo *sampInfo = newRgn->AddSampInfo(); // This is a really loopy way of determining the loop information, pardon the pun. // However, it works. There might be a way to simplify this, but I don't want to // test out whether another method breaks anything just yet Use the sample's // loopStatus to determine if a loop occurs. If it does, see if the sample provides // loop info (gathered during ADPCM > PCM conversion. If the sample doesn't provide // loop offset info, then use the region's loop info. if (samp->_bPSXLoopInfoPrioritizing) { if (samp->_loop.loopStatus != -1) { if (samp->_loop.loopStart != 0 || samp->_loop.loopLength != 0) sampInfo->SetLoopInfo(samp->_loop, samp); else { rgn->_loop.loopStatus = samp->_loop.loopStatus; sampInfo->SetLoopInfo(rgn->_loop, samp); } } else { error("_bPSXLoopInfoPrioritizing: Invalid sample loop status"); } } // The normal method: First, we check if the rgn has loop info defined. // If it doesn't, then use the sample's loop info. else if (rgn->_loop.loopStatus == -1) { if (samp->_loop.loopStatus != -1) sampInfo->SetLoopInfo(samp->_loop, samp); else { error("Invalid sample loop status"); } } else sampInfo->SetLoopInfo(rgn->_loop, samp); int8 realUnityKey = -1; if (rgn->_unityKey == -1) realUnityKey = samp->_unityKey; else realUnityKey = rgn->_unityKey; if (realUnityKey == -1) realUnityKey = 0x3C; short realFineTune; if (rgn->_fineTune == 0) realFineTune = samp->_fineTune; else realFineTune = rgn->_fineTune; double attenuation; if (rgn->_volume != -1) attenuation = ConvertLogScaleValToAtten(rgn->_volume); else if (samp->_volume != -1) attenuation = ConvertLogScaleValToAtten(samp->_volume); else attenuation = 0; double sustainLevAttenDb; if (rgn->_sustain_level == -1) sustainLevAttenDb = 0.0; else sustainLevAttenDb = ConvertPercentAmplitudeToAttenDB_SF2(rgn->_sustain_level); SynthArt *newArt = newRgn->AddArt(); newArt->AddPan(rgn->_pan); newArt->AddADSR(rgn->_attack_time, (Transform) rgn->_attack_transform, rgn->_decay_time, sustainLevAttenDb, rgn->_sustain_time, rgn->_release_time, (Transform) rgn->_release_transform); sampInfo->SetPitchInfo(realUnityKey, realFineTune, attenuation); } } } return synthfile; }