And if you want to test out the code, here's an overly simlified AUD decoder. It'll only play one song and then stop playing music. (But it does stop gracefully with silence and doesn't just stutter continuously at the end). Basically, it lacks any sort of playlist control. Well, here's the 6 files that do it.
AudPlayer.h
--------------
#include "MusicHook.h"
int __stdcall FillMusicBuffer(char *musicBuffer, int bufferSize);
AudPlayer.cpp
-----------------
#include "MusicHook.h"
#include "AudFileReader.h"
#include "AudDecoder.h"
// Note: This function fills the music buffer
// with 0x8000 bytes worth of PCM data
// Note: Data format is 16 bit signed, mono, at 22050 Hz
// Return: Nonzero on success, zero on error. If an error occurs,
// the music buffer is cleared to zero.
int __stdcall FillMusicBuffer(char *musicBuffer, int bufferSize)
{
DWORD numDecoded1;
DWORD numAudBytesRead;
static AudFileReader audReader;
static AudDecoder audDecoder;
static bool playNewAudFile = true;
const DWORD audBufferSize = 0x2000;
char audBuffer[audBufferSize];
// Check if a new Aud file needs to be decoded
if (playNewAudFile == true)
{
// **TODO** Make this work in the right way
audReader.Open("ARAKATAK.AUD");
audDecoder = AudDecoder();
playNewAudFile = false;
}
// Read a chunk of Aud data
numAudBytesRead = audReader.ReadAudData(audBuffer, audBufferSize);
// Fill sound buffer
// *****************
numDecoded1 = audDecoder.Decode(musicBuffer, bufferSize, audBuffer, numAudBytesRead);
memset(musicBuffer + numDecoded1, 0, bufferSize - numDecoded1); // Zero fill if short on data
// Return success
return 1;
}
AudFileReader.h
--------------------
#define WIN32_LEAN_AND_MEAN
#include <windows.h>
#include <fstream.h>
class AudFileReader
{
public:
AudFileReader();
~AudFileReader();
int Open(char *fileName); // Opens file and reads in the header
int ReadAudData(char *buffer, unsigned int bufferSize); // Returns a chunk of AUD data
// Wave format info
int GetFrequency();
int GetOutputSize();
int GetCompressionType();
int GetBitsPerSample();
int GetNumChannels();
private:
// Private data types
#pragma pack(push, 1)
// AUD File header
struct AudHeader
{
short samplesPerSec; // Frequency
int fileSize; // Size of file (without header)
int outputSize; // Size of output data
char flags; // Bit 0 = Stereo, Bit 1 = 16 bit
char type; // 1 = WW compressed, 99 = IMA ADPCM
};
// Chunk header
struct ChunkHeader
{
short size;
short outputSize;
int id;
};
#pragma pack(pop)
enum AudFlags
{
FlagStereo = 1,
Flag16Bit = 2,
};
// Private functions
int ReadChunk(char *buffer, int bufferSize);
// Class variables
//ifstream audFile;
HANDLE hFile;
AudHeader audHeader;
ChunkHeader currentChunkHeader;
int currentChunkOffset;
};
AudFileReader.cpp
----------------------
#include "AudFileReader.h"
AudFileReader::AudFileReader()
{
hFile = INVALID_HANDLE_VALUE;
currentChunkOffset = 0;
memset(&audHeader, 0, sizeof(audHeader));
memset(¤tChunkHeader, 0, sizeof(currentChunkHeader));
}
AudFileReader::~AudFileReader()
{
// Close the file if it is open
if (hFile != INVALID_HANDLE_VALUE)
CloseHandle(hFile);
}
// Opens an AUD file and reads in the file header
// Return: 0 on success, nonzero on failure.
int AudFileReader::Open(char *fileName)
{
DWORD numBytesRead;
// Try to open the file
hFile = CreateFile(fileName, GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, NULL);
// Check for errors
if (hFile == INVALID_HANDLE_VALUE)
{
// Error opening the file
return 1;
}
// Read the file header
ReadFile(hFile, &audHeader, sizeof(audHeader), &numBytesRead, NULL);
// Check for errors
if (numBytesRead != sizeof(audHeader))
{
// Error reading AUD header
CloseHandle(hFile);
return 2;
}
// Return success
return 0;
}
// Fills the passed in buffer with AUD data, up to the maximum size of the buffer.
// Return: The number of bytes actually written to the buffer
int AudFileReader::ReadAudData(char *buffer, unsigned int bufferSize)
{
DWORD numBytesRead;
DWORD numBytesWritten = 0;
DWORD readSize;
// Try to fill the buffer with AUD data
while(bufferSize > numBytesWritten)
{
// Check if the current chunk has no more data left
if (currentChunkHeader.size <= currentChunkOffset)
{
// We need to read in a new chunk
ReadFile(hFile, ¤tChunkHeader, sizeof(currentChunkHeader), &numBytesRead, NULL);
// Check for errors
if (numBytesRead != sizeof(currentChunkHeader))
{
// Error reading new chunk header. Abort.
return numBytesWritten;
}
// Reset current chunk offset
currentChunkOffset = 0;
}
// Determine how many bytes to read from the current chunk
readSize = currentChunkHeader.size - currentChunkOffset;
if (readSize > bufferSize - numBytesWritten)
readSize = bufferSize - numBytesWritten;
// Read the chunk data
ReadFile(hFile, &buffer[numBytesWritten], readSize, &numBytesRead, NULL);
// Adjust the counters
numBytesWritten += readSize;
currentChunkOffset += readSize;
// Check for errors
if (numBytesRead != readSize)
{
// Error reading chunk data. Abort.
return numBytesWritten;
}
}
// Return the number of bytes written
return numBytesWritten;
}
int AudFileReader::GetFrequency()
{
return audHeader.samplesPerSec;
}
int AudFileReader::GetOutputSize()
{
return audHeader.outputSize;
}
int AudFileReader::GetCompressionType()
{
return audHeader.type;
}
int AudFileReader::GetBitsPerSample()
{
if ((audHeader.flags & Flag16Bit) == Flag16Bit)
return 16;
else
return 8;
}
int AudFileReader::GetNumChannels()
{
if ((audHeader.flags & FlagStereo) == FlagStereo)
return 2;
else
return 1;
}
AudDecoder.h
-----------------
class AudDecoder
{
public:
AudDecoder();
int Decode(void *pcmBuffer, int pcmBufferSize, char *audBuffer, int audBufferSize);
private:
// Audio decoding state
int index;
int curSample;
// Private static constant tables used during decompression
static const int indexAdjust[];
static const int stepTable[];
};
AudDecoder.cpp
-------------------
#include "AudDecoder.h"
// Define the static tables used during decompression
const int AudDecoder::indexAdjust[] = {-1,-1,-1,-1,2,4,6,8};
const int AudDecoder::stepTable[] = {
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
};
// Constructor
AudDecoder::AudDecoder()
{
// Initialize the decoding state
index = 0;
curSample = 0;
}
// Decompresses the data from the audBuffer into the pcmBuffer
// Return value: The number of bytes written to the pcm buffer.
// Parameters:
// pcmBuffer - a pointer to the buffer to fill with signed 16 bit PCM samples
// pcmBufferSize - size of the pcmBuffer in bytes. (not the size in shorts)
// audBuffer - a pointer to the input aud data (consisting of 4 bit samples)
// audBufferSize - size of the audBuffer in bytes. (not the number of samples)
// Note: The aud data is in 4 bit samples, and the pcm data is 16 bit signed samples
// Note: Data is assumed to be mono
int AudDecoder::Decode(void *pcmBuffer, int pcmBufferSize, char *audBuffer, int audBufferSize)
{
int numSamples;
int i;
int code;
bool signBit;
int delta;
// Determine maximum amount of data that can be processed
if (pcmBufferSize/2 >= audBufferSize*2)
numSamples = audBufferSize*2; // Number of samples is implicity even
else
numSamples = (pcmBufferSize/2) & ~1; // Round down to even number of samples
// Decode the chunk of data
for (i = 0; i < numSamples; i++)
{
// Get the current code (full byte)
code = audBuffer[i/2];
// Adjust for correct nibble
if ((i & 1) == 1)
code >>= 4;
// Get the sign of the nibble
signBit = ((code & 8) == 8);
// Truncate code to remaining bits
code &= 7;
// Calculate delta. Last part minimizes errors
delta =(stepTable[index]*code)/4 + stepTable[index]/8;
// Adjust for sign
if (signBit)
delta = -delta;
// Calculate the new sample
curSample += delta;
if (curSample < -32768) curSample = -32768;
else if (curSample > 32767) curSample = 32767;
// Write out the current sample
((short*)pcmBuffer)[i] = curSample;
// Adjust the index
index += indexAdjust[code];
if (index < 0) index = 0;
else if (index > 88) index = 88;
}
// Return the number of bytes written
return numSamples*2;
}
Ohh, but now I've gone and spilled the beans!
I'm not sure if I even told Eddy what I was doing with my hooking code. But there it is. My custom built .Aud decoder. It was based off of some file I found on the internet about the music in Dune 2000. It used AD-PCM (Adaptive Pulse Code Modulation) to encode each 16 bit sample as 4 bits. Lossy compression of course. Anyways, I wanted to make a "sandbox" level using the Dune 2000 maps, and figured it wouldn't be complete without the music (and sand worms!
).
Anyways, I imported the Dune 2000 tile sets into Outpost2 format and a bunch of the multiplayer maps as well. I think there was around 25 something odd maps or so. I can't remember anymore. I think there were bugs in at least two maps that caused crashes. Anyways, I did that well over a year ago, and still haven't got around to making any levels, so I guess it's safe to say I'll never get around to it. So don't get your hopes up and expect this to ever be finished. The project has essentially been abandoned for a rather long time now. Oh, and I never added OP2 specific tiles to the Dune2000 tile set. Namely the tubes, walls, and bulldozed terrain, along with rubble (common and rare) and marks left on the ground from vehicle explosions. (Yes Haxtor, that's the secret project I tried to get you to help me with so very long ago).
Oh, and my work on importing the Dune 2000 tileset is what lead me to start on the project that became the map editor backend. I was mainly interested in the tile sets at the time, but the tile sets and the map format are so closely tied together in OP2 that I ended up doing so much work on the map files that I figured Hacker could make use of it for a map editor.
Of course adding new units into OP2 is damn near impossible, so adding sand worms just never came close. Although I did learn a fair bit about the OP2 unit system and I'd say it's somewhat possible to add new units in limited ways if you really wanted to work hard. I never quite learned everything I would have needed, but it was enough that I'd have some idea where to go.
Anyways, the music part worked, aside from the playlist. (And the base tileset was imported nicely). Enjoy.