Thanks brizio, will do.
Quick Turbo update. Per an idea I got from SamIAm's Amp thread and my experiences with discovering/understanding the really low preamp level practice of the industry when it came to redbook audio tracks in CDs in the 80's when I was adding an
Ys MP3 player to my website, I added a basic auto-normalizing/max-amplifying feature to TurboRip which is now fully tested and working! Works very fast too, helps to use the OS's memory map features over traditional ReadFile/WriteFile APIs! Most especially compared to when the code was written in Visual Basic 6 and I had to wait 3 minutes per wave file for completion, heh.
Shared some of "C" source code below for the curious after converting the algorithm from VB6. Man, it was VERY hard to find useful code for this, I looked at Audacity's source and it exemplifies what I hate about C++, everything had been 'classed' up, dependencies all over the friggin' place, would be extremely hard to extract just the algorithm out of the damn thing for easy drop'n'use for your own project... :/ Granted, their algorithm is more complex than the basic one I found and used here, but I honestly just hate excessive use of C++... Added complexity AND plus doggedly much slower execution of code too... When it comes to that, you just want their compiled DLL and a competent API/interface to make use of the functionality, that is, if you're willing to bloat your software just for one little feature...
#include <global.h>
#include <NumbersASM.h>
#include "WAVE.h"
BOOLEAN WAVE_Normalize(LPSTR lpFileName) {
// VB6 norm/amplify generic algorithm by piuskerala
short LowestDataValue, HighestDataValue, *lp16BitSample, SampleValue;
WAVE_FILE_HEADER *WaveHeader;
MappedFile mFile;
long NewSampleValue;
float VolumeLevel;
DWORD Count;
// 1) Open wave file memory mapping style
if ( !MapFile(lpFileName, &mFile, FALSE, FALSE) )
return FALSE;
WaveHeader = (WAVE_FILE_HEADER*)mFile.lpMapAddress;
if ( WaveHeader->riffFORMAT != 'EVAW' ) // Verify it's a wave file
return FALSE;
// 2) Find highest and lowest value in wave data
LowestDataValue = HighestDataValue = 0;
lp16BitSample = MakePtr(short*, mFile.lpMapAddress, 44);
for ( Count = WaveHeader->dataSIZE / 2; Count; Count-- ) {
SampleValue = *lp16BitSample;
if ( SampleValue > HighestDataValue )
HighestDataValue = SampleValue;
if ( SampleValue < LowestDataValue )
LowestDataValue = SampleValue;
lp16BitSample++;
}
// 3) Normalize wave a sample at a time
VolumeLevel = ((HighestDataValue + -LowestDataValue) / 2) / (float)32767;
VolumeLevel = 1 / VolumeLevel; // For maximum amplitude/normalizing
lp16BitSample = MakePtr(short*, mFile.lpMapAddress, 44);
// 32767 is the highest integer(signed) value
for ( Count = WaveHeader->dataSIZE / 2; Count; Count-- ) {
NewSampleValue = RoundValue(*lp16BitSample * VolumeLevel);
if ( NewSampleValue > 32767 ) NewSampleValue = 32767;
if ( NewSampleValue < -32768 ) NewSampleValue = -32768;
*lp16BitSample = (short)NewSampleValue;
lp16BitSample++;
}
// 4) Close wave file, we're done!
CloseMap(mFile);
return TRUE;
}
// Library stuff/functions copied/pasted for reference
#define CloseMap(hMap) {UnmapViewOfFile((hMap).lpMapAddress); CloseHandle((hMap).hFileMapping); CloseHandle((hMap).hFile);}
// Quickly takes a float value and returns it as a rounded 32-bit integer
// Use Agner Fog's quicker way with 2 FPU instructions
DWORD __fastcall RoundValue(float ftNumber) {
DWORD dwIntValue;
__asm {
LEA EBX, dwIntValue ;// EBX gets the address of our local variable
FLD DWORD PTR [ftNumber] ;// Load floating point input on FPU stack
FISTP DWORD PTR [EBX] ;// Store INT result at dwIntValue's address
XOR EAX, EAX ;// Intentional delay between FPU write and read
NOP
NOP
MOV EAX, [EBX] ;// Read rounded integer from @dwIntValue, return in EAX
}
}
BOOLEAN MapFile(LPSTR lpFileName, MappedFile *mFile, BOOLEAN ReadOnly, BOOLEAN SequentialScanOnRead) {
DWORD dwFlagsAndAttributes, CreateFileAccess, CreateFileMappingAccess, MapViewOfFileAccess;
dwFlagsAndAttributes = FILE_ATTRIBUTE_NORMAL;
if ( ReadOnly ) {
CreateFileAccess = GENERIC_READ;
CreateFileMappingAccess = PAGE_READONLY;
MapViewOfFileAccess = FILE_MAP_READ;
if ( SequentialScanOnRead )
dwFlagsAndAttributes |= FILE_FLAG_SEQUENTIAL_SCAN;
} else {
CreateFileAccess = GENERIC_READ | GENERIC_WRITE;
CreateFileMappingAccess = PAGE_READWRITE;
MapViewOfFileAccess = FILE_MAP_ALL_ACCESS;
}
mFile->hFile = CreateFileA(lpFileName, CreateFileAccess, 0, NULL,
OPEN_EXISTING, dwFlagsAndAttributes, NULL);
if ( mFile->hFile == INVALID_HANDLE_VALUE ) {
PrintLastError("Error: Cannot open '%s'", lpFileName);
return FALSE;
}
//mFile->qwFileSize.LowPart = GetFileSize(mFile->hFile, (LPDWORD)&mFile->qwFileSize.HighPart);
mFile->dwFileSize = GetFileSize(mFile->hFile, NULL);
// Create a file-mapping object for the file.
mFile->hFileMapping = CreateFileMappingA(mFile->hFile, // current file handle
NULL, // default security
CreateFileMappingAccess, // read/write permission
0, // size of mapping object, high
0, // size of mapping object, low
NULL); // name of mapping object
if ( mFile->hFileMapping == NULL ) {
PrintLastError("Error: File mapping object creation failed");
return FALSE;
}
// Map the view and test the results.
mFile->lpMapAddress = (LPSTR)MapViewOfFile(mFile->hFileMapping, // handle to mapping object
MapViewOfFileAccess, // read/write permission
0, // high-order 32 bits of file offset
0, // low-order 32 bits of file offset
0); // number of bytes to map
if ( mFile->lpMapAddress == NULL ) {
PrintLastError("Error: File mapping view failed");
return FALSE;
}
return TRUE;
}
So, simply adding /normalize to your TurboRip extraction command now does the trick:
And now here is a sample result of auto-normalization/amplify, which is basically an automatic amplifying of the wave file based on picking the largest and smallest values and computing an amplification factor to minimize clipping.
The above is track 3 of "Ys IV: Dawn of Ys," the composition known as "Field." Ys IV is a prime example of the REALLY low preamp volume level found on CDs of that era which makes it REALLY hard to amplify it on your CD sound system without normalization processing. Those 2 red lines show abruptness or technical clipping, and with only 2 cases it's really minimal so you'll never notice or should care. Technical clipping will vary with every track, but it should be negligible to the human ear.
Now, it might be worth allowing your own amplification factor so that all tracks of a CD get amplified the same exact rate and show consistency in volume level though instead of only an automatic determination per track per the norm. I could adjust the parameter by allowing an equal sign and a value, so "/normalize=2" would simply double the volume level of all tracks without regard to clipping if you wanna be aggressive like SamIAm suggests in his thread and go heavy-metal loud preamp levels... But anyway, auto-normalization/amplify gets the ball rolling for something that should be pretty useful for now.
As I'm still behind on updates to the ReadMe, I'm still not doing a formal version update release, but in case anything happens between then and now,
here is the best version of TurboRip so far with this new feature along with all the bug fixes/enhancements thanks to johnnykonami/Sam, and Chris Covell:
http://www.ysutopia.net/software/TurboRipTest.zip