scummvm/backends/platform/ds/arm9/source/cdaudio.cpp
Max Horn 75529dc402 DS: Fix various warnings and errors in the DS FS code
* Do not modify the strings passed to std_fopen anymore
* Correct signature of std_fread
* Do not cast away constness, nor perform unnecessary casts

svn-id: r50693
2010-07-05 16:12:31 +00:00

554 lines
12 KiB
C++

/* 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 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$
*
*/
#include "cdaudio.h"
#include "backends/fs/ds/ds-fs.h"
#include "common/config-manager.h"
#include "dsmain.h"
#include "NDS/scummvm_ipc.h"
#define WAV_FORMAT_IMA_ADPCM 0x14
#define BUFFER_SIZE 8192
#define BUFFER_CHUNK_SIZE (BUFFER_SIZE >> 2)
namespace DS {
namespace CD {
struct WaveHeader {
char riff[4]; // 'RIFF'
u32 size; // Size of the file
char wave[4]; // 'WAVE'
// fmt chunk
char fmt[4]; // 'fmt '
u32 fmtSize; // Chunk size
u16 fmtFormatTag; // Format of this file
u16 fmtChannels; // Num channels
u32 fmtSamPerSec; // Samples per second
u32 fmtBytesPerSec; // Bytes per second
u16 fmtBlockAlign; // Block alignment
u16 fmtBitsPerSam; // Bits per sample
u16 fmtExtraData; // Number of extra fmt bytes
u16 fmtExtra; // Samples per block (only for IMA-ADPCM files)
} __attribute__ ((packed));
struct chunkHeader {
char name[4];
u32 size;
} __attribute__ ((packed));
struct Header {
s16 firstSample;
char stepTableIndex;
char reserved;
} __attribute__ ((packed));
struct decoderFormat {
s16 initial;
unsigned char tableIndex;
unsigned char test;
unsigned char sample[1024];
} __attribute__ ((packed));
bool active = false;
WaveHeader waveHeader;
Header blockHeader;
FILE *file;
int fillPos;
bool isPlayingFlag = false;
s16 *audioBuffer;
u32 sampleNum;
s16 *decompressionBuffer;
int numLoops;
int blockCount;
int dataChunkStart;
int blocksLeft;
bool trackStartsAt2 = false;
// These are from Microsoft's document on DVI ADPCM
const int stepTab[ 89 ] = {
7, 8, 9, 10, 11, 12, 13, 14,
16, 17, 19, 21, 23, 25, 28, 31,
34, 37, 41, 45, 50, 55, 60, 66,
73, 80, 88, 97, 107, 118, 130, 143,
157, 173, 190, 209, 230, 253, 279, 307,
337, 371, 408, 449, 494, 544, 598, 658,
724, 796, 876, 963, 1060, 1166, 1282, 1411,
1552, 1707, 1878, 2066, 2272, 2499, 2749, 3024,
3327, 3660, 4026, 4428, 4871, 5358, 5894, 6484,
7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899,
15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794,
32767 };
const int indexTab[ 16 ] = { -1, -1, -1, -1, 2, 4, 6, 8,
-1, -1, -1, -1, 2, 4, 6, 8 };
void playNextBlock();
void decompressBlock();
void allocBuffers() {
}
void setActive(bool active) {
DS::CD::active = active;
}
bool getActive() {
return active;
}
void playTrack(int track, int numLoops, int startFrame, int duration) {
Common::String path = ConfMan.get("path");
if (isPlayingFlag) {
stopTrack();
}
if (trackStartsAt2) {
track++;
}
char str[100];
if (path[strlen(path.c_str()) - 1] != '/') {
path = path + "/";
}
Common::String fname;
sprintf(str, "track%d.wav", track);
fname = path + str;
file = DS::std_fopen(fname.c_str(), "rb");
if (!file) {
sprintf(str, "track%02d.wav", track);
fname = path + str;
file = DS::std_fopen(fname.c_str(), "rb");
}
if (!file) {
consolePrintf("Failed to open %s!\n", path.c_str());
return;
}
DS::std_fread(&waveHeader, sizeof(waveHeader), 1, file);
consolePrintf("File: %s\n", fname.c_str());
consolePrintf("Playing track %d\n", track);
consolePrintf("Format: %d\n", waveHeader.fmtFormatTag);
consolePrintf("Rate : %d\n", waveHeader.fmtSamPerSec);
consolePrintf("Bits : %d\n", waveHeader.fmtBitsPerSam);
consolePrintf("BlkSz : %d\n", waveHeader.fmtExtra);
if ((waveHeader.fmtFormatTag != 17) && (waveHeader.fmtFormatTag != 20)) {
consolePrintf("Wave file is in the wrong format! You must use IMA-ADPCM 4-bit mono.\n");
DS::std_fclose(file);
return;
}
for (int r = 0; r < 8; r++) {
IPC->adpcm.buffer[r] = (u8 * volatile) (decoderFormat *) malloc(waveHeader.fmtBlockAlign);
IPC->adpcm.filled[r] = false;
IPC->adpcm.arm7Dirty[r] = false;
}
// Skip chunks until we reach the data chunk
chunkHeader chunk;
DS::std_fread(&chunk, sizeof(chunkHeader), 1, file);
while (!((chunk.name[0] == 'd') && (chunk.name[1] == 'a') && (chunk.name[2] == 't') && (chunk.name[3] == 'a'))) {
DS::std_fseek(file, chunk.size, SEEK_CUR);
DS::std_fread(&chunk, sizeof(chunkHeader), 1, file);
}
dataChunkStart = DS::std_ftell(file);
static bool started = false;
sampleNum = 0;
blockCount = 0;
IPC->streamFillNeeded[0] = true;
IPC->streamFillNeeded[1] = true;
IPC->streamFillNeeded[2] = true;
IPC->streamFillNeeded[3] = true;
if (!started) {
fillPos = 0;
audioBuffer = (s16 *) malloc(BUFFER_SIZE * 2);
decompressionBuffer = (s16 *) malloc(waveHeader.fmtExtra * 2);
started = true;
// consolePrintf("****Starting buffer*****\n");
memset(audioBuffer, 0, BUFFER_SIZE * 2);
memset(decompressionBuffer, 0, waveHeader.fmtExtra * 2);
DS::playSound(audioBuffer, BUFFER_SIZE * 2, false, false, waveHeader.fmtSamPerSec);
}
fillPos = (IPC->streamPlayingSection + 1) & 3;
isPlayingFlag = true;
// Startframe is a 75Hz timer. Dunno why, since nothing else
// seems to run at that rate.
int tenths = (startFrame * 10) / 75;
// Seek to the nearest block start to the start time
int samples = (tenths * waveHeader.fmtSamPerSec) / 10;
int block = samples / waveHeader.fmtExtra;
if (duration == 0) {
blocksLeft = 0;
} else {
blocksLeft = ((((duration * 100) / 75) * (waveHeader.fmtSamPerSec)) / (waveHeader.fmtExtra) / 100) + 10;
}
// consolePrintf("Playing %d blocks (%d)\n\n", blocksLeft, duration);
// No need to seek if we're starting from the beginning
if (block != 0) {
DS::std_fseek(file, dataChunkStart + block * waveHeader.fmtBlockAlign, SEEK_SET);
// consolePrintf("Startframe: %d msec: %d (%d,%d)\n", startFrame, tenthssec, samples, block);
}
//decompressBlock();
playNextBlock();
DS::CD::numLoops = numLoops;
}
void update() {
playNextBlock();
}
#ifdef ARM_ADPCM
extern "C" void ARM_adcpm(int *block, int len, int stepTableIndex,
int firstSample, s16 *decompressionBuffer);
#endif
void decompressBlock() {
int block[2048];
bool loop = false;
blockCount++;
if (blockCount < 10) return;
do {
DS::std_fread(&blockHeader, sizeof(blockHeader), 1, file);
DS::std_fread(&block[0], waveHeader.fmtBlockAlign - sizeof(blockHeader), 1, file);
if (DS::std_feof(file) ) {
// Reached end of file, so loop
if ((numLoops == -1) || (numLoops > 1)) {
// Seek file to first packet
if (numLoops != -1) {
numLoops--;
}
DS::std_fseek(file, dataChunkStart, SEEK_SET);
loop = true;
} else {
// Fill decompression buffer with zeros to prevent glitching
for (int r = 0; r < waveHeader.fmtExtra; r++) {
decompressionBuffer[r] = 0;
}
// consolePrintf("Stopping music\n");
stopTrack();
return;
}
} else {
loop = false;
}
} while (loop);
if (blocksLeft > 0) {
blocksLeft--;
// consolePrintf("%d ", blocksLeft);
if (blocksLeft == 0) {
stopTrack();
return;
}
}
#ifdef ARM_ADPCM
ARM_adpcm(block, waveHeader.fmtExtra,
blockHeader.stepTableIndex,
blockHeader.firstSample,
decompressionBuffer);
#else
// First sample is in header
decompressionBuffer[0] = blockHeader.firstSample;
// Set up initial table indeces
int stepTableIndex = blockHeader.stepTableIndex;
int prevSample = blockHeader.firstSample;
// consolePrintf("Decompressing block step=%d fs=%d\n", stepTableIndex, prevSample);
for (int r = 0; r < waveHeader.fmtExtra - 1; r++) {
int word = block[r >> 3];
int offset = 0;
switch (7 - (r & 0x0007)) {
case 0: {
offset = (word & 0xF0000000) >> 28;
break;
}
case 1: {
offset = (word & 0x0F000000) >> 24;
break;
}
case 2: {
offset = (word & 0x00F00000) >> 20;
break;
}
case 3: {
offset = (word & 0x000F0000) >> 16;
break;
}
case 4: {
offset = (word & 0x0000F000) >> 12;
break;
}
case 5: {
offset = (word & 0x00000F00) >> 8;
break;
}
case 6: {
offset = (word & 0x000000F0) >> 4;
break;
}
case 7: {
offset = (word & 0x0000000F);
break;
}
}
int diff = 0;
if (offset & 4) {
diff = diff + stepTab[stepTableIndex];
}
if (offset & 2) {
diff = diff + (stepTab[stepTableIndex] >> 1);
}
if (offset & 1) {
diff = diff + (stepTab[stepTableIndex] >> 2);
}
diff = diff + (stepTab[stepTableIndex] >> 3);
if (offset & 8) {
diff = -diff;
}
int newSample = prevSample + diff;
if (newSample > 32767) newSample = 32767;
if (newSample < -32768) newSample = -32768;
decompressionBuffer[r + 1] = newSample;
prevSample = newSample;
stepTableIndex += indexTab[offset];
if (stepTableIndex > 88) stepTableIndex = 88;
if (stepTableIndex < 0) stepTableIndex = 0;
}
#endif
}
void playNextBlock() {
if (!isPlayingFlag) return;
int lastBlockId = -1;
while (IPC->adpcm.semaphore); // Wait for buffer to become free if needed
IPC->adpcm.semaphore = true; // Lock the buffer structure to prevent clashing with the ARM7
// DC_FlushAll();
//-8644, 25088
for (int block = fillPos + 1; block < fillPos + 4; block++) {
int blockId = block & 3;
if (IPC->streamFillNeeded[blockId]) {
IPC->streamFillNeeded[blockId] = false;
// DC_FlushAll();
/* if (!(REG_KEYINPUT & KEY_R)) {
//consolePrintf("Align: %d First: %d Step:%d Res:%d\n", waveHeader.fmtBlockAlign, blockHeader.firstSample, blockHeader.stepTableIndex, blockHeader.reserved);
consolePrintf("Filling buffer %d\n", blockId);
}*/
for (int r = blockId * BUFFER_CHUNK_SIZE; r < (blockId + 1) * BUFFER_CHUNK_SIZE; r++) {
if (isPlayingFlag) {
audioBuffer[r] = decompressionBuffer[sampleNum++];
if (sampleNum >= waveHeader.fmtExtra) {
decompressBlock();
sampleNum = 0;
}
}
}
lastBlockId = blockId;
IPC->streamFillNeeded[blockId] = false;
// DC_FlushAll();
}
}
if (lastBlockId != -1) {
fillPos = lastBlockId;
/* if (!(REG_KEYINPUT & KEY_R)) {
consolePrintf("Frame fill done\n");
}*/
}
IPC->adpcm.semaphore = false; // Release the buffer structure
// DC_FlushAll();
}
void stopTrack() {
if (!isPlayingFlag) return;
DS::std_fclose(file);
isPlayingFlag = false;
for (int r = 0; r < BUFFER_SIZE; r++) {
audioBuffer[r] = 0;
}
for (int r= 0; r < waveHeader.fmtExtra; r++) {
decompressionBuffer[r] = 0;
}
// DS::stopSound(1);
// free(audioBuffer);
// free(decompressionBuffer);
DC_FlushAll();
}
bool trackExists(int num) {
Common::String path = ConfMan.get("path");
char fname[128];
sprintf(fname, "track%d.wav", num);
if (path[strlen(path.c_str()) - 1] == '/') {
path = path + fname;
} else {
path = path + "/" + fname;
}
consolePrintf("Looking for %s...", path.c_str());
FILE *file;
if ((file = DS::std_fopen(path.c_str(), "r"))) {
consolePrintf("Success!\n");
setActive(true);
DS::std_fclose(file);
return true;
}
sprintf(fname, "track%02d.wav", num);
path = ConfMan.get("path");
if (path[strlen(path.c_str()) - 1] == '/') {
path = path + fname;
} else {
path = path + "/" + fname;
}
consolePrintf("Looking for %s...", path.c_str());
if ((file = DS::std_fopen(path.c_str(), "r"))) {
consolePrintf("Success!\n");
setActive(true);
DS::std_fclose(file);
return true;
} else {
setActive(false);
consolePrintf("Failed!\n");
return false;
}
}
bool checkCD() {
// Need to check whethe CD audio files are present - do this by trying to open Track1.wav.
consolePrintf("Attempted to open cd drive\n");
if (trackExists(1)) {
trackStartsAt2 = false;
return true;
} else if (trackExists(2)) {
trackStartsAt2 = true;
return true;
} else {
return false;
}
}
bool isPlaying() {
return isPlayingFlag;
}
}
} // End of namespace DS