Initial commit
This commit is contained in:
362
engines/vcruise/sampleloop.cpp
Normal file
362
engines/vcruise/sampleloop.cpp
Normal file
@@ -0,0 +1,362 @@
|
||||
/* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
#include "common/stream.h"
|
||||
|
||||
#include "vcruise/sampleloop.h"
|
||||
|
||||
namespace VCruise {
|
||||
|
||||
SampleLoop::SampleLoop() : identifier(0), type(0), start(0), end(0), fraction(0), playCount(0) {
|
||||
}
|
||||
|
||||
bool SampleLoop::read(Common::ReadStream &stream, uint &availableBytes) {
|
||||
if (availableBytes < 24)
|
||||
return false;
|
||||
|
||||
byte bytes[24];
|
||||
|
||||
uint32 bytesRead = stream.read(bytes, 24);
|
||||
availableBytes -= bytesRead;
|
||||
if (bytesRead != 24)
|
||||
return false;
|
||||
|
||||
this->identifier = READ_LE_UINT32(bytes + 0);
|
||||
this->type = READ_LE_UINT32(bytes + 4);
|
||||
this->start = READ_LE_UINT32(bytes + 8);
|
||||
this->end = READ_LE_UINT32(bytes + 12);
|
||||
this->fraction = READ_LE_UINT32(bytes + 16);
|
||||
this->playCount = READ_LE_UINT32(bytes + 20);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
SampleChunk::SampleChunk() : manufacturer(0), product(0), samplePeriod(0), midiUnityNote(0), midiPitchFraction(0), smpteFormat(0), smpteOffset(0) {
|
||||
}
|
||||
|
||||
bool SampleChunk::read(Common::ReadStream &stream, uint &availableBytes) {
|
||||
if (availableBytes < 36)
|
||||
return false;
|
||||
|
||||
byte bytes[36];
|
||||
|
||||
uint32 bytesRead = stream.read(bytes, 36);
|
||||
availableBytes -= bytesRead;
|
||||
if (bytesRead != 36)
|
||||
return false;
|
||||
|
||||
this->manufacturer = READ_LE_UINT32(bytes + 0);
|
||||
this->product = READ_LE_UINT32(bytes + 4);
|
||||
this->samplePeriod = READ_LE_UINT32(bytes + 8);
|
||||
this->midiUnityNote = READ_LE_UINT32(bytes + 12);
|
||||
this->midiPitchFraction = READ_LE_UINT32(bytes + 16);
|
||||
this->smpteFormat = READ_LE_UINT32(bytes + 20);
|
||||
this->smpteOffset = READ_LE_UINT32(bytes + 24);
|
||||
|
||||
uint32 numSampleLoops = READ_LE_UINT32(bytes + 28);
|
||||
uint32 sizeOfSamplerData = READ_LE_UINT32(bytes + 32);
|
||||
|
||||
loops.resize(numSampleLoops);
|
||||
samplerData.resize(sizeOfSamplerData);
|
||||
|
||||
for (uint32 i = 0; i < numSampleLoops; i++)
|
||||
if (!loops[i].read(stream, availableBytes))
|
||||
return false;
|
||||
|
||||
if (sizeOfSamplerData > 0) {
|
||||
if (availableBytes < sizeOfSamplerData)
|
||||
return false;
|
||||
|
||||
bytesRead = stream.read(&samplerData[0], sizeOfSamplerData);
|
||||
availableBytes -= bytesRead;
|
||||
if (bytesRead != sizeOfSamplerData)
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Common::SharedPtr<SoundLoopInfo> SoundLoopInfo::readFromWaveFile(Common::SeekableReadStream &stream) {
|
||||
if (!stream.seek(0))
|
||||
return nullptr;
|
||||
|
||||
int64 waveSize64 = stream.size();
|
||||
|
||||
if (waveSize64 > static_cast<int64>(0xffffffffu))
|
||||
return nullptr;
|
||||
|
||||
uint availableBytes = waveSize64;
|
||||
|
||||
if (availableBytes < 8)
|
||||
return nullptr;
|
||||
|
||||
byte riffHeader[8];
|
||||
if (stream.read(riffHeader, 8) != 8)
|
||||
return nullptr;
|
||||
|
||||
availableBytes -= 8;
|
||||
|
||||
if (READ_LE_UINT32(riffHeader + 0) != 0x46464952)
|
||||
return nullptr;
|
||||
|
||||
uint riffDataSize = READ_LE_UINT32(riffHeader + 4);
|
||||
|
||||
if (riffDataSize > availableBytes)
|
||||
return nullptr;
|
||||
|
||||
availableBytes = riffDataSize;
|
||||
|
||||
if (availableBytes < 4)
|
||||
return nullptr;
|
||||
|
||||
byte waveHeader[4];
|
||||
if (stream.read(waveHeader, 4) != 4)
|
||||
return nullptr;
|
||||
|
||||
availableBytes -= 4;
|
||||
|
||||
if (READ_LE_UINT32(waveHeader + 0) != 0x45564157)
|
||||
return nullptr;
|
||||
|
||||
while (availableBytes > 0) {
|
||||
if (availableBytes < 8)
|
||||
return nullptr;
|
||||
|
||||
byte chunkHeader[8];
|
||||
if (stream.read(chunkHeader, 8) != 8)
|
||||
return nullptr;
|
||||
|
||||
availableBytes -= 8;
|
||||
|
||||
uint32 chunkType = READ_LE_UINT32(chunkHeader + 0);
|
||||
uint32 chunkSize = READ_LE_UINT32(chunkHeader + 4);
|
||||
|
||||
if (chunkSize > availableBytes)
|
||||
return nullptr;
|
||||
|
||||
if (chunkType == 0x6c706d73) {
|
||||
Common::SharedPtr<SoundLoopInfo> sndLoop(new SoundLoopInfo());
|
||||
|
||||
uint chunkAvailableBytes = chunkSize;
|
||||
if (!sndLoop->_sampleChunk.read(stream, chunkAvailableBytes))
|
||||
return nullptr;
|
||||
|
||||
if (sndLoop->_sampleChunk.loops.size() == 0)
|
||||
return nullptr;
|
||||
|
||||
return sndLoop;
|
||||
}
|
||||
|
||||
if (!stream.seek(chunkSize, SEEK_CUR))
|
||||
return nullptr;
|
||||
|
||||
availableBytes -= chunkSize;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
SampleLoopAudioStream::LoopRange::LoopRange() : _startSampleInclusive(0), _endSampleExclusive(0), _playCount(0) {
|
||||
}
|
||||
|
||||
SampleLoopAudioStream::SampleLoopAudioStream(Audio::SeekableAudioStream *baseStream, const SoundLoopInfo *loopInfo)
|
||||
: _baseStream(baseStream), _terminated(false), _ignoreLoops(false), _currentSampleOffset(0), _currentLoop(-1), _currentLoopIteration(0), _streamFrames(0), _streamSamples(0) {
|
||||
|
||||
_streamFrames = baseStream->getLength().convertToFramerate(baseStream->getRate()).totalNumberOfFrames();
|
||||
_streamSamples = _streamFrames;
|
||||
|
||||
if (_baseStream->isStereo())
|
||||
_streamSamples *= 2;
|
||||
|
||||
if (loopInfo) {
|
||||
_loopRanges.resize(loopInfo->_sampleChunk.loops.size());
|
||||
for (uint i = 0; i < _loopRanges.size(); i++) {
|
||||
const SampleLoop &inLoop = loopInfo->_sampleChunk.loops[i];
|
||||
LoopRange &outLoop = _loopRanges[i];
|
||||
|
||||
outLoop._startSampleInclusive = inLoop.start;
|
||||
outLoop._endSampleExclusive = inLoop.end;
|
||||
outLoop._playCount = inLoop.playCount;
|
||||
}
|
||||
} else {
|
||||
_loopRanges.resize(1);
|
||||
_loopRanges[0]._startSampleInclusive = 0;
|
||||
_loopRanges[0]._endSampleExclusive = _streamFrames;
|
||||
}
|
||||
|
||||
for (LoopRange &range : _loopRanges) {
|
||||
range._restartTimestamp = Audio::Timestamp(0, range._startSampleInclusive, baseStream->getRate());
|
||||
|
||||
if (range._startSampleInclusive > static_cast<uint>(_streamFrames))
|
||||
range._startSampleInclusive = _streamFrames;
|
||||
if (range._endSampleExclusive > static_cast<uint>(_streamFrames))
|
||||
range._endSampleExclusive = _streamFrames;
|
||||
if (range._endSampleExclusive < range._startSampleInclusive)
|
||||
range._endSampleExclusive = range._startSampleInclusive;
|
||||
|
||||
if (_baseStream->isStereo()) {
|
||||
range._startSampleInclusive *= 2;
|
||||
range._endSampleExclusive *= 2;
|
||||
}
|
||||
}
|
||||
|
||||
for (uint i = 0; i < _loopRanges.size(); ) {
|
||||
const LoopRange &thisRange = _loopRanges[i];
|
||||
|
||||
bool isValid = true;
|
||||
if (thisRange._endSampleExclusive == thisRange._startSampleInclusive)
|
||||
isValid = false;
|
||||
|
||||
if (i > 0) {
|
||||
const LoopRange &prevRange = _loopRanges[i - 1];
|
||||
if (thisRange._startSampleInclusive < prevRange._endSampleExclusive)
|
||||
isValid = false;
|
||||
}
|
||||
|
||||
if (isValid)
|
||||
i++;
|
||||
else
|
||||
_loopRanges.remove_at(i);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
SampleLoopAudioStream::~SampleLoopAudioStream() {
|
||||
}
|
||||
|
||||
void SampleLoopAudioStream::stopLooping() {
|
||||
_mutex.lock();
|
||||
_ignoreLoops = true;
|
||||
_mutex.unlock();
|
||||
}
|
||||
|
||||
int SampleLoopAudioStream::readBuffer(int16 *buffer, int numSamples) {
|
||||
bool ignoreLoops = false;
|
||||
|
||||
_mutex.lock();
|
||||
ignoreLoops = _ignoreLoops;
|
||||
_mutex.unlock();
|
||||
|
||||
int totalSamplesRead = 0;
|
||||
|
||||
for (;;) {
|
||||
if (_terminated || numSamples == 0)
|
||||
return totalSamplesRead;
|
||||
|
||||
int consecutiveSamplesAvailable = 0;
|
||||
bool terminateIfReadCompletes = false;
|
||||
|
||||
if (ignoreLoops) {
|
||||
consecutiveSamplesAvailable = _streamSamples - _currentSampleOffset;
|
||||
terminateIfReadCompletes = true;
|
||||
} else if (_currentLoop < 0) {
|
||||
// Not currently in a loop
|
||||
int samplesUntilLoop = -1;
|
||||
uint scanLoopIndex = 0;
|
||||
for (const LoopRange &loopRange : _loopRanges) {
|
||||
if (static_cast<int>(loopRange._startSampleInclusive) >= _currentSampleOffset) {
|
||||
samplesUntilLoop = loopRange._startSampleInclusive - _currentSampleOffset;
|
||||
break;
|
||||
} else
|
||||
scanLoopIndex++;
|
||||
}
|
||||
|
||||
if (samplesUntilLoop < 0) {
|
||||
// Past the end of the last loop
|
||||
consecutiveSamplesAvailable = _streamSamples - _currentSampleOffset;
|
||||
terminateIfReadCompletes = true;
|
||||
} else if (samplesUntilLoop == 0) {
|
||||
// At the start of a loop
|
||||
_currentLoop = scanLoopIndex;
|
||||
_currentLoopIteration = 0;
|
||||
continue;
|
||||
} else {
|
||||
// Before a loop
|
||||
consecutiveSamplesAvailable = samplesUntilLoop;
|
||||
}
|
||||
} else {
|
||||
// In a loop
|
||||
const LoopRange &loopRange = _loopRanges[_currentLoop];
|
||||
|
||||
int samplesAvailable = loopRange._endSampleExclusive - static_cast<int>(_currentSampleOffset);
|
||||
if (samplesAvailable == 0) {
|
||||
// At the end of the loop
|
||||
if (loopRange._playCount > 0) {
|
||||
if (_currentLoopIteration == loopRange._playCount) {
|
||||
// Exit loop
|
||||
_currentLoop = -1;
|
||||
continue;
|
||||
} else
|
||||
_currentLoopIteration++;
|
||||
}
|
||||
|
||||
if (!_baseStream->seek(loopRange._restartTimestamp)) {
|
||||
_terminated = true;
|
||||
return totalSamplesRead;
|
||||
}
|
||||
|
||||
_currentSampleOffset = loopRange._startSampleInclusive;
|
||||
continue;
|
||||
} else {
|
||||
// Inside of a loop
|
||||
consecutiveSamplesAvailable = samplesAvailable;
|
||||
}
|
||||
}
|
||||
|
||||
if (consecutiveSamplesAvailable == 0)
|
||||
_terminated = true;
|
||||
else {
|
||||
int samplesDesired = numSamples;
|
||||
if (samplesDesired > consecutiveSamplesAvailable)
|
||||
samplesDesired = consecutiveSamplesAvailable;
|
||||
|
||||
int samplesRead = _baseStream->readBuffer(buffer, samplesDesired);
|
||||
|
||||
if (samplesRead > 0)
|
||||
totalSamplesRead += samplesRead;
|
||||
|
||||
if (samplesRead != samplesDesired)
|
||||
_terminated = true;
|
||||
else {
|
||||
_currentSampleOffset += samplesRead;
|
||||
buffer += samplesRead;
|
||||
numSamples -= samplesRead;
|
||||
|
||||
if (samplesRead == consecutiveSamplesAvailable && terminateIfReadCompletes)
|
||||
_terminated = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool SampleLoopAudioStream::isStereo() const {
|
||||
return _baseStream->isStereo();
|
||||
}
|
||||
|
||||
int SampleLoopAudioStream::getRate() const {
|
||||
return _baseStream->getRate();
|
||||
}
|
||||
|
||||
bool SampleLoopAudioStream::endOfData() const {
|
||||
return _terminated;
|
||||
}
|
||||
|
||||
} // namespace VCruise
|
||||
Reference in New Issue
Block a user