spaceaceiigs/tools/packsound/source/packsound.cpp

199 lines
5.8 KiB
C++

/***************************************
Tool to process data for Space Ace IIgs
Copyright (c) 1995-2015 by Rebecca Ann Heineman <becky@burgerbecky.com>
It is released under an MIT Open Source license. Please see LICENSE
for license details. Yes, you can use it in a
commercial title without paying anything, just give me a credit.
Please? It's not like I'm asking you for money!
***************************************/
#include "packsound.h"
#define DOC_28MHZ 28636360.0f // Master Ensoniq clock rate
#define DOC_RATE (DOC_28MHZ/32.0f) // Ensoniq clock rate
#define SCAN_RATE (DOC_RATE/34.0f) // All oscillators are enabled
struct SpaceAceAudioFile_t {
Word16 m_uDOCRate; // Value to put into the Ensoniq DOC for sample rate
Word16 m_uBytesPerTick; // Number of bytes consumed per 1/60th of a second tick
Word8 m_Data[1]; // Raw audio data
};
static const Word8 g_Lookup[16] = {0x00,0x11,0x22,0x33,0x44,0x55,0x66,0x77,0x88,0x99,0xAA,0xBB,0xCC,0xDD,0xEE,0xFF};
/***************************************
Process a sound file into 4 bits per sample
***************************************/
static Word ExtractSound(OutputMemoryStream *pOutput,const Word8 *pInput,WordPtr uInputLength)
{
if (uInputLength<4) {
printf("Input is too small\n");
return 1;
}
if (MemoryCompare("RIFF",pInput,4)) {
printf("Not a sound file\n");
return 1;
}
Word32 uFileLength = LittleEndian::Load(reinterpret_cast<const Word32 *>(pInput+4));
if (uFileLength!=uInputLength) {
printf("Sound file length mismatch\n");
return 1;
}
// Calculate the DOC rate
int iSampleRate = LittleEndian::Load(reinterpret_cast<const Word32 *>(pInput+24));
// Convert from a IIgs step rate to a samples per second rate
int iDOCRate = static_cast<int>(((static_cast<float>(iSampleRate)*512.0f)/SCAN_RATE)+0.5f);
// Output the Space Ace audio header
pOutput->Append(static_cast<Word16>(iDOCRate));
pOutput->Append(static_cast<Word16>((iSampleRate+59)/60));
// WGet the number of samples
Word uSoundLength = LittleEndian::Load(reinterpret_cast<const Word32 *>(pInput+40));
uSoundLength>>=1U;
if (uSoundLength) {
const Word8 *pTemp = pInput+44;
do {
// Convert to 4 bits per sample audio
pOutput->Append(static_cast<Word8>((pTemp[0]&0xF0U)+(pTemp[1]>>4U)));
pTemp+=2;
} while (--uSoundLength);
}
return 0;
}
/***************************************
Convert a Space Ace file to a WAV file
***************************************/
static Word EncapsulateToWAV(OutputMemoryStream *pOutput,const Word8 *pInput,WordPtr uInputLength)
{
// Too small?
if (uInputLength<4) {
return 10;
}
const Word8 *pWork = pInput+4;
WordPtr uCounter = uInputLength-4;
int iSampleRate = LittleEndian::Load(&reinterpret_cast<const SpaceAceAudioFile_t *>(pInput)->m_uDOCRate);
// Convert from a IIgs step rate to a samples per second rate
iSampleRate = static_cast<int>((static_cast<float>(iSampleRate)/512.0f)*SCAN_RATE);
// Trim excess data if needed
if (uCounter) {
do {
if (pWork[uCounter-1]==0x88) {
break;
}
} while (--uCounter);
}
pOutput->Append("RIFF");
pOutput->Append(static_cast<Word32>((uCounter*2)+44));
pOutput->Append("WAVEfmt ");
pOutput->Append(static_cast<Word32>(16)); // Chunk size (Minimum)
pOutput->Append(static_cast<Word16>(1)); // Format code (PCM)
pOutput->Append(static_cast<Word16>(1)); // Mono
pOutput->Append(static_cast<Word32>(iSampleRate)); // Samples per second
pOutput->Append(static_cast<Word32>(iSampleRate)); // Bytes per second (Same as samples)
pOutput->Append(static_cast<Word16>(1)); // Byte aligned
pOutput->Append(static_cast<Word16>(8)); // Bits per sample
pOutput->Append("data");
pOutput->Append(static_cast<Word32>(uCounter*2));
// No diff
do {
pOutput->Append(g_Lookup[(pWork[0]>>4)&0xF]);
pOutput->Append(g_Lookup[pWork[0]&0xF]);
++pWork;
} while (--uCounter);
return 0;
}
/***************************************
Main dispatcher
***************************************/
int BURGER_ANSIAPI main(int argc,const char **argv)
{
ConsoleApp MyApp(argc,argv);
CommandParameterBooleanTrue DoSound("Process Sound","s");
CommandParameterBooleanTrue DoWave("Convert to Wave","w");
const CommandParameter *MyParms[] = {
&DoSound,
&DoWave
};
argc = MyApp.GetArgc();
argv = MyApp.GetArgv();
argc = CommandParameter::Process(argc,argv,MyParms,sizeof(MyParms)/sizeof(MyParms[0]),
"Usage: packsound InputFile OutputFile\n\n"
"Preprocess data for Space Ace IIgs.\nCopyright by Rebecca Ann Heineman\n",3);
if (argc<0) {
Globals::SetErrorCode(10);
} else {
MyApp.SetArgc(argc);
Filename InputName;
InputName.SetFromNative(argv[1]);
WordPtr uInputLength;
Word8 *pInput = static_cast<Word8 *>(FileManager::LoadFile(&InputName,&uInputLength));
if (!pInput) {
printf("Can't open %s!\n",argv[1]);
Globals::SetErrorCode(10);
} else {
// Convert wave to data
if (DoSound.GetValue()) {
OutputMemoryStream Output;
if (ExtractSound(&Output,pInput,uInputLength)) {
printf("Can't convert %s!\n",argv[1]);
Globals::SetErrorCode(10);
} else {
Filename OutputName;
OutputName.SetFromNative(argv[2]);
if (Output.SaveFile(&OutputName)) {
printf("Can't save %s!\n",argv[2]);
Globals::SetErrorCode(10);
}
}
// Convert raw audio to WAV
} else if (DoWave.GetValue()) {
OutputMemoryStream Output;
if (EncapsulateToWAV(&Output,pInput,uInputLength)) {
printf("Can't convert %s!\n",argv[1]);
Globals::SetErrorCode(10);
} else {
Filename OutputName;
OutputName.SetFromNative(argv[2]);
if (Output.SaveFile(&OutputName)) {
printf("Can't save %s!\n",argv[2]);
Globals::SetErrorCode(10);
}
}
} else {
printf("No conversion selected for %s!\n",argv[1]);
Globals::SetErrorCode(10);
}
Free(pInput);
}
}
return Globals::GetErrorCode();
}