diff --git a/engines/sword1/music.cpp b/engines/sword1/music.cpp index 209140f9a4c..f23b073b580 100644 --- a/engines/sword1/music.cpp +++ b/engines/sword1/music.cpp @@ -27,6 +27,7 @@ #include "common/system.h" #include "sword1/music.h" +#include "sound/aiff.h" #include "sound/mixer.h" #include "sound/mp3.h" #include "sound/vorbis.h" @@ -127,6 +128,97 @@ int WaveAudioStream::readBuffer(int16 *buffer, const int numSamples) { return retVal; } +class AiffAudioStream : public Audio::AudioStream { +public: + AiffAudioStream(Common::SeekableReadStream *source, bool loop); + virtual ~AiffAudioStream(); + virtual int readBuffer(int16 *buffer, const int numSamples); + virtual bool isStereo() const { return _isStereo; } + virtual bool endOfData() const { return (_samplesLeft == 0); } + virtual int getRate() const { return _rate; } +private: + Common::SeekableReadStream *_sourceStream; + uint8 *_sampleBuf; + uint32 _rate; + bool _isStereo; + uint32 _samplesLeft; + uint16 _bitsPerSample; + bool _loop; + + void rewind(); +}; + +AiffAudioStream::AiffAudioStream(Common::SeekableReadStream *source, bool loop) { + // TODO: honor the loop flag + + _sourceStream = source; + _sampleBuf = (uint8*)malloc(SMP_BUFSIZE); + + _samplesLeft = 0; + _isStereo = false; + _bitsPerSample = 16; + _rate = 22050; + _loop = loop; + + rewind(); + + if (_samplesLeft == 0) + _loop = false; +} + +AiffAudioStream::~AiffAudioStream() { + free(_sampleBuf); +} + +void AiffAudioStream::rewind() { + int rate, size; + byte flags; + + _sourceStream->seek(0); + + if (Audio::loadAIFFFromStream(*_sourceStream, size, rate, flags)) { + _isStereo = (flags & Audio::Mixer::FLAG_STEREO) != 0; + _rate = rate; + assert((uint)size <= (_sourceStream->size() - _sourceStream->pos())); + _bitsPerSample = ((flags & Audio::Mixer::FLAG_16BITS) != 0) ? 16 : 8; + _samplesLeft = (size * 8) / _bitsPerSample; + if ((_bitsPerSample != 16) && (_bitsPerSample != 8)) + error("AiffAudioStream: unknown wave type"); + } +} + +int AiffAudioStream::readBuffer(int16 *buffer, const int numSamples) { + int retVal = 0; + + while (retVal < numSamples && _samplesLeft > 0) { + int samples = MIN((int)_samplesLeft, numSamples - retVal); + retVal += samples; + _samplesLeft -= samples; + while (samples > 0) { + int readBytes = MIN(samples * (_bitsPerSample >> 3), SMP_BUFSIZE); + _sourceStream->read(_sampleBuf, readBytes); + if (_bitsPerSample == 16) { + readBytes >>= 1; + samples -= readBytes; + int16 *src = (int16*)_sampleBuf; + while (readBytes--) + *buffer++ = (int16)READ_BE_UINT16(src++); + } else { + samples -= readBytes; + int8 *src = (int8*)_sampleBuf; + while (readBytes--) + *buffer++ = (int16)*src++ << 8; + } + } + + if (!_samplesLeft && _loop) { + rewind(); + } + } + + return retVal; +} + // This means fading takes 3 seconds. #define FADE_LENGTH 3 @@ -156,6 +248,12 @@ bool MusicHandle::play(const char *fileBase, bool loop) { if (_file.open(fileName)) _audioSource = new WaveAudioStream(&_file, loop); } + + if (!_audioSource) { + sprintf(fileName, "%s.aif", fileBase); + if (_file.open(fileName)) + _audioSource = new AiffAudioStream(&_file, loop); + } if (!_audioSource) return false; diff --git a/sound/aiff.cpp b/sound/aiff.cpp new file mode 100644 index 00000000000..290ebdd9ec4 --- /dev/null +++ b/sound/aiff.cpp @@ -0,0 +1,178 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2007 The ScummVM project + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id: wave.cpp 25926 2007-03-02 15:26:22Z fingolfin $ + * + */ + +/* + * The code in this file is based on information found at + * http://www.borg.com/~jglatt/tech/aiff.htm + * + * We currently only implement uncompressed AIFF. If we ever need AIFF-C, SoX + * (http://sox.sourceforge.net) may be a good place to start from. + */ + +#include "common/stdafx.h" +#include "common/endian.h" +#include "common/util.h" +#include "common/stream.h" + +#include "sound/aiff.h" +#include "sound/audiostream.h" +#include "sound/mixer.h" + +namespace Audio { + +uint32 readExtended(Common::SeekableReadStream &stream) { + // The sample rate is stored as an "80 bit IEEE Standard 754 floating + // point number (Standard Apple Numeric Environment [SANE] data type + // Extended). + + byte buf[10]; + uint32 mantissa; + uint32 last; + byte exp; + + stream.read(buf, 10); + mantissa = READ_BE_UINT32(buf + 2); + exp = 30 - buf[1]; + + while (exp--) { + last = mantissa; + mantissa >>= 1; + } + + if (last & 0x00000001) + mantissa++; + + return mantissa; +} + +bool loadAIFFFromStream(Common::SeekableReadStream &stream, int &size, int &rate, byte &flags) { + byte buf[4]; + + stream.read(buf, 4); + if (memcmp(buf, "FORM", 4) != 0) { + warning("loadAIFFFromStream: No 'FORM' header"); + return false; + } + + stream.readUint32BE(); + + // This could be AIFC, but we don't handle that case. + + stream.read(buf, 4); + if (memcmp(buf, "AIFF", 4) != 0) { + warning("loadAIFFFromStream: No 'AIFF' header"); + return false; + } + + // From here on, we only care about the COMM and SSND chunks, which are + // the only required chunks. + + bool foundCOMM = false; + bool foundSSND = false; + + uint16 numChannels, bitsPerSample; + uint32 numSampleFrames, offset, blockSize, soundOffset; + + while ((!foundCOMM || !foundSSND) && !stream.ioFailed()) { + uint32 length, pos; + + stream.read(buf, 4); + length = stream.readUint32BE(); + pos = stream.pos(); + + if (memcmp(buf, "COMM", 4) == 0) { + foundCOMM = true; + numChannels = stream.readUint16BE(); + numSampleFrames = stream.readUint32BE(); + bitsPerSample = stream.readUint16BE(); + rate = readExtended(stream); + size = numSampleFrames * numChannels * (bitsPerSample / 8); + } else if (memcmp(buf, "SSND", 4) == 0) { + foundSSND = true; + offset = stream.readUint32BE(); + blockSize = stream.readUint32BE(); + soundOffset = stream.pos(); + } + + stream.seek(pos + length); + } + + if (!foundCOMM) { + warning("loadAIFFFromStream: Cound not find 'COMM' chunk"); + return false; + } + + if (!foundSSND) { + warning("loadAIFFFromStream: Cound not find 'SSND' chunk"); + return false; + } + + // We only implement a subset of the AIFF standard. + + if (numChannels < 1 || numChannels > 2) { + warning("loadAIFFFromStream: Only 1 or 2 channels are supported, not %d", numChannels); + return false; + } + + if (bitsPerSample != 8 && bitsPerSample != 16) { + warning("loadAIFFFromStream: Only 8 or 16 bits per sample are supported, not %d", bitsPerSample); + return false; + } + + if (offset != 0 || blockSize != 0) { + warning("loadAIFFFromStream: Block-aligned data is not supported"); + return false; + } + + // Samples are always signed, and big endian. + + flags = 0; + if (bitsPerSample == 16) + flags |= Audio::Mixer::FLAG_16BITS; + if (numChannels == 2) + flags |= Audio::Mixer::FLAG_STEREO; + + stream.seek(soundOffset); + + // Stream now points at the sample data + + return true; +} + +AudioStream *makeAIFFStream(Common::SeekableReadStream &stream) { + int size, rate; + byte *data, flags; + + if (!loadAIFFFromStream(stream, size, rate, flags)) + return 0; + + data = (byte *)malloc(size); + assert(data); + stream.read(data, size); + + // Since we allocated our own buffer for the data, we must set the autofree flag. + flags |= Audio::Mixer::FLAG_AUTOFREE; + + return makeLinearInputStream(data, size, rate, flags, 0, 0); +} + +} // End of namespace Audio diff --git a/sound/aiff.h b/sound/aiff.h new file mode 100644 index 00000000000..d7e209ac3d5 --- /dev/null +++ b/sound/aiff.h @@ -0,0 +1,54 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2007 The ScummVM project + * + * 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 2 + * 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, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * + * $URL$ + * $Id: wave.h 22231 2006-04-29 22:33:31Z fingolfin $ + * + */ + +#ifndef SOUND_AIFF_H +#define SOUND_AIFF_H + +#include "common/stdafx.h" +#include "common/scummsys.h" + +namespace Common { class SeekableReadStream; } + +namespace Audio { + +class AudioStream; + +/** + * Try to load an AIFF from the given seekable stream. Returns true if + * successful. In that case, the stream's seek position will be set to the + * start of the audio data, and size, rate and flags contain information + * necessary for playback. Currently this function only supports uncompressed + * raw PCM data as well as IMA ADPCM. + */ +extern bool loadAIFFFromStream(Common::SeekableReadStream &stream, int &size, int &rate, byte &flags); + +/** + * Try to load an AIFF from the given seekable stream and create an AudioStream + * from that data. + * + * This function uses loadAIFFFromStream() internally. + */ +AudioStream *makeAIFFStream(Common::SeekableReadStream &stream); + +} // End of namespace Audio + +#endif diff --git a/sound/module.mk b/sound/module.mk index ad25b6b31d2..dce7ace08e8 100644 --- a/sound/module.mk +++ b/sound/module.mk @@ -2,6 +2,7 @@ MODULE := sound MODULE_OBJS := \ adpcm.o \ + aiff.o \ audiocd.o \ audiostream.o \ flac.o \