winamp/Src/Plugins/Input/in_wv/in_wv.cpp

2062 lines
55 KiB
C++

/*
** .WV input plug-in for WavPack
** Copyright (c) 2000 - 2006, Conifer Software, All Rights Reserved
*/
#include <windows.h>
#include <fcntl.h>
#include <stdio.h>
#include <mmreg.h>
#include <msacm.h>
#include <math.h>
#include <sys/stat.h>
#include <io.h>
#include <strsafe.h>
#include "in2.h"
#include "wavpack.h"
#include "resource.h"
#include "wasabi/winamp/wa_ipc.h"
#include "wasabi/wasabi.h"
#include "wasabi/nu/autochar.h"
#include "wasabi/nu/autowide.h"
#define fileno _fileno
static float calculate_gain(WavpackContext *wpc, bool allowDefault=true);
#define PLUGIN_VERSION "2.8.1"
//#define DEBUG_CONSOLE
//#define ANSI_METADATA
#define UNICODE_METADATA
//#define OLD_INFO_DIALOG
// post this to the main window at end of file (after playback as stopped)
#define WM_WA_MPEG_EOF WM_USER+2
#define MAX_NCH 8
static struct wpcnxt {
WavpackContext *wpc; // WavPack context provided by library
float play_gain; // playback gain (for replaygain support)
//int soft_clipping; // soft clipping active for playback
int output_bits; // 16, 24, or 32 bits / sample
long sample_buffer[576*MAX_NCH*2]; // sample buffer
float error [MAX_NCH]; // error term for noise shaping
char lastfn[MAX_PATH]; // filename stored for comparisons only
wchar_t w_lastfn[MAX_PATH]; // w_filename stored for comparisons only
FILE *wv_id, *wvc_id; // file pointer when we use reader callbacks
} curr, edit, info;
int decode_pos_ms; // current decoding position, in milliseconds
int paused; // are we paused?
int seek_needed; // if != -1, it is the point that the decode thread should seek to, in ms.
#define ALLOW_WVC 0x1
#define REPLAYGAIN_TRACK 0x2
#define REPLAYGAIN_ALBUM 0x4
#define SOFTEN_CLIPPING 0x8
#define PREVENT_CLIPPING 0x10
#define ALWAYS_16BIT 0x20 // new flags added for version 2.5
#define ALLOW_MULTICHANNEL 0x40
#define REPLAYGAIN_24BIT 0x80
int config_bits = ALLOW_WVC | ALLOW_MULTICHANNEL; // all configuration goes here
int killDecodeThread=0; // the kill switch for the decode thread
HANDLE thread_handle=INVALID_HANDLE_VALUE; // the handle to the decode thread
DWORD WINAPI __stdcall DecodeThread(void *b); // the decode thread procedure
static api_service* WASABI_API_SVC;
static api_language* WASABI_API_LNG;
static api_config *AGAVE_API_CONFIG;
static api_memmgr *WASABI_API_MEMMGR;
HINSTANCE WASABI_API_LNG_HINST = 0, WASABI_API_ORIG_HINST = 0;
static char file_extensions[128] = {"WV\0WavPack Files (*.WV)\0"};
// function definitions for the In_Module stucture
void about (HWND hwndParent);
void init();
void quit();
void getfileinfo(const char *filename, char *title, int *length_in_ms);
int infoDlg(const char *fn, HWND hwnd);
int isourfile(const char *fn);
int play(const char *fn);
void pause();
void unpause();
int ispaused();
void stop();
int getlength();
int getoutputtime();
void setoutputtime(int time_in_ms);
void setvolume(int volume);
void setpan(int pan);
void eq_set(int on, char data [10], int preamp);
In_Module mod = // the output module
{
IN_VER,
"WavPack Decoder v"PLUGIN_VERSION,
0, // hMainWindow
0, // hDllInstance
file_extensions,
1, // is_seekable
1, // uses output
about,
about,
init,
quit,
getfileinfo,
infoDlg,
isourfile,
play,
pause,
unpause,
ispaused,
stop,
getlength,
getoutputtime,
setoutputtime,
setvolume,
setpan,
0,0,0,0,0,0,0,0,0, // vis stuff
0,0, // dsp
eq_set,
NULL, // setinfo
0 // out_mod
};
static BOOL CALLBACK WavPackDlgProc(HWND, UINT, WPARAM, LPARAM);
extern long dump_alloc (void);
int DoAboutMessageBox(HWND parent, wchar_t* title, wchar_t* message)
{
MSGBOXPARAMSW msgbx = {sizeof(MSGBOXPARAMSW),0};
msgbx.lpszText = message;
msgbx.lpszCaption = title;
msgbx.lpszIcon = MAKEINTRESOURCEW(102);
msgbx.hInstance = GetModuleHandle(0);
msgbx.dwStyle = MB_USERICON;
msgbx.hwndOwner = parent;
return MessageBoxIndirectW(&msgbx);
}
void about(HWND hwndParent)
{
wchar_t about_string[512];
#ifdef DEBUG_ALLOC
sprintf (about_string, "alloc_count = %d", dump_alloc ());
#else
StringCchPrintfW(about_string, 512, WASABI_API_LNGSTRINGW(IDS_ABOUT_MESSAGE), PLUGIN_VERSION, "1998-2010", __DATE__);
#endif
DoAboutMessageBox(hwndParent, WASABI_API_LNGSTRINGW(IDS_ABOUT), about_string);
}
void init() /* any one-time initialization goes here (configuration reading, etc) */
{
if (mod.hMainWindow)
{
// load all of the required wasabi services from the winamp client
WASABI_API_SVC = (api_service *)SendMessage(mod.hMainWindow, WM_WA_IPC, 0, IPC_GET_API_SERVICE);
if (WASABI_API_SVC == (api_service *)1)
WASABI_API_SVC=0;
WASABI_API_SVC->service_register(&albumArtFactory);
}
ServiceBuild(AGAVE_API_CONFIG, AgaveConfigGUID);
ServiceBuild(WASABI_API_LNG, languageApiGUID);
ServiceBuild(WASABI_API_MEMMGR, memMgrApiServiceGuid);
// need to have this initialised before we try to do anything with localisation features
WASABI_API_START_LANG(mod.hDllInstance,InWvLangGuid);
static char szDescription[256];
StringCchPrintfA(szDescription,256,WASABI_API_LNGSTRING(IDS_DESCRIPTION),PLUGIN_VERSION);
mod.description = szDescription;
// set the file extension to the localised version
char tmp [64], *tmp_ptr = tmp, *fex_ptr = file_extensions;
WASABI_API_LNGSTRING_BUF(IDS_FILETYPE, tmp, sizeof (tmp));
*fex_ptr++ = 'W';
*fex_ptr++ = 'V';
*fex_ptr++ = 0;
while (*tmp_ptr)
*fex_ptr++ = *tmp_ptr++;
*fex_ptr++ = 0;
*fex_ptr++ = 0;
}
#ifdef DEBUG_CONSOLE
HANDLE debug_console=INVALID_HANDLE_VALUE; // debug console
void debug_write (char *str)
{
static int cant_debug;
if (cant_debug)
return;
if (debug_console == INVALID_HANDLE_VALUE) {
AllocConsole ();
#if 1
debug_console = GetStdHandle (STD_OUTPUT_HANDLE);
#else
debug_console = CreateConsoleScreenBuffer (GENERIC_WRITE, FILE_SHARE_WRITE,
NULL, CONSOLE_TEXTMODE_BUFFER, NULL);
#endif
if (debug_console == INVALID_HANDLE_VALUE) {
MessageBox(NULL, "Can't get a console handle", "WavPack",MB_OK);
cant_debug = 1;
return;
}
else if (!SetConsoleActiveScreenBuffer (debug_console)) {
MessageBox(NULL, "Can't activate console buffer", "WavPack",MB_OK);
cant_debug = 1;
return;
}
}
WriteConsole (debug_console, str, strlen (str), NULL, NULL);
}
#endif
void quit() /* one-time deinit, such as memory freeing */
{
#ifdef DEBUG_CONSOLE
if (debug_console != INVALID_HANDLE_VALUE) {
FreeConsole ();
if (debug_console != GetStdHandle (STD_OUTPUT_HANDLE))
CloseHandle (debug_console);
debug_console = INVALID_HANDLE_VALUE;
}
#endif
ServiceRelease(AGAVE_API_CONFIG, AgaveConfigGUID);
ServiceRelease(WASABI_API_LNG, languageApiGUID);
ServiceRelease(WASABI_API_MEMMGR, memMgrApiServiceGuid);
WASABI_API_SVC->service_deregister(&albumArtFactory);
}
// used for detecting URL streams.. unused here. strncmp(fn,"http://",7) to detect HTTP streams, etc
int isourfile(const char *fn)
{
return 0;
}
int play(const char *fn)
{
int num_chans, sample_rate;
char error[128];
int maxlatency;
int thread_id;
int open_flags;
#ifdef DEBUG_CONSOLE
sprintf (error, "play (%s)\n", fn);
debug_write (error);
#endif
open_flags = OPEN_TAGS | OPEN_NORMALIZE;
if (config_bits & ALLOW_WVC)
open_flags |= OPEN_WVC;
if (!(config_bits & ALLOW_MULTICHANNEL))
open_flags |= OPEN_2CH_MAX;
curr.wpc = WavpackOpenFileInput(fn, error, open_flags, 0);
if (!curr.wpc) // error opening file, just return error
return -1;
num_chans = WavpackGetReducedChannels(curr.wpc);
sample_rate = WavpackGetSampleRate(curr.wpc);
curr.output_bits = WavpackGetBitsPerSample(curr.wpc) > 16 ? 24 : 16;
if (config_bits & ALWAYS_16BIT)
curr.output_bits = 16;
else if ((config_bits & (REPLAYGAIN_TRACK | REPLAYGAIN_ALBUM)) &&
(config_bits & REPLAYGAIN_24BIT))
curr.output_bits = 24;
if (num_chans > MAX_NCH) // don't allow too many channels!
{
WavpackCloseFile(curr.wpc);
return -1;
}
curr.play_gain = calculate_gain(curr.wpc);
lstrcpyn(curr.lastfn, fn, MAX_PATH);
paused = 0;
decode_pos_ms = 0;
seek_needed = -1;
maxlatency = mod.outMod->Open(sample_rate, num_chans, curr.output_bits, -1, -1);
if (maxlatency < 0) // error opening device
{
curr.wpc = WavpackCloseFile(curr.wpc);
return -1;
}
// dividing by 1000 for the first parameter of setinfo makes it
// display 'H'... for hundred.. i.e. 14H Kbps.
mod.SetInfo(0, (sample_rate + 500) / 1000, num_chans, 1);
// initialize vis stuff
mod.SAVSAInit(maxlatency, sample_rate);
mod.VSASetInfo(sample_rate, num_chans);
mod.outMod->SetVolume(-666); // set the output plug-ins default volume
killDecodeThread=0;
thread_handle = (HANDLE)CreateThread (NULL, 0, (LPTHREAD_START_ROUTINE) DecodeThread, (void *) &killDecodeThread, 0, (LPDWORD)&thread_id);
if (SetThreadPriority(thread_handle, THREAD_PRIORITY_HIGHEST) == 0) {
curr.wpc = WavpackCloseFile(curr.wpc);
return -1;
}
return 0;
}
void pause()
{
#ifdef DEBUG_CONSOLE
debug_write ("pause ()\n");
#endif
paused = 1;
mod.outMod->Pause(1);
}
void unpause()
{
#ifdef DEBUG_CONSOLE
debug_write ("unpause ()\n");
#endif
paused = 0;
mod.outMod->Pause(0);
}
int ispaused()
{
return paused;
}
void stop()
{
#ifdef DEBUG_CONSOLE
debug_write ("stop ()\n");
#endif
if (thread_handle != INVALID_HANDLE_VALUE)
{
killDecodeThread = 1;
if (WaitForSingleObject(thread_handle, INFINITE) == WAIT_TIMEOUT)
{
MessageBox(mod.hMainWindow,"error asking thread to die!\n", "error killing decode thread", 0);
TerminateThread(thread_handle,0);
}
CloseHandle(thread_handle);
thread_handle = INVALID_HANDLE_VALUE;
}
if (curr.wpc)
curr.wpc = WavpackCloseFile(curr.wpc);
mod.outMod->Close();
mod.SAVSADeInit();
}
int getlength()
{
return (int)(WavpackGetNumSamples (curr.wpc) * 1000.0 / WavpackGetSampleRate (curr.wpc));
}
int getoutputtime()
{
if (seek_needed == -1)
return decode_pos_ms + (mod.outMod->GetOutputTime () - mod.outMod->GetWrittenTime ());
else
return seek_needed;
}
void setoutputtime (int time_in_ms)
{
#ifdef DEBUG_CONSOLE
char str [40];
sprintf (str, "setoutputtime (%d)\n", time_in_ms);
debug_write (str);
#endif
seek_needed = time_in_ms;
}
void setvolume (int volume)
{
mod.outMod->SetVolume(volume);
}
void setpan (int pan)
{
mod.outMod->SetPan(pan);
}
static void generate_format_string(WavpackContext *wpc, wchar_t *string, int maxlen, int wide);
static void AnsiToUTF8(char *string, int len);
static int UTF8ToWideChar(const char *pUTF8, wchar_t *pWide);
static void UTF8ToAnsi(char *string, int len);
int infoDlg(const char *fn, HWND hwnd)
{
#ifdef OLD_INFO_DIALOG
char string[2048];
wchar_t w_string[2048];
WavpackContext *wpc;
int open_flags;
open_flags = OPEN_TAGS | OPEN_NORMALIZE;
if (config_bits & ALLOW_WVC)
open_flags |= OPEN_WVC;
if (!(config_bits & ALLOW_MULTICHANNEL))
open_flags |= OPEN_2CH_MAX;
wpc = WavpackOpenFileInput(fn, string, open_flags, 0);
if (wpc)
{
int mode = WavpackGetMode(wpc);
//generate_format_string(wpc, string, sizeof (string), 1);
wchar_t *temp = (wchar_t *)malloc(sizeof(string) * sizeof(wchar_t));
generate_format_string(wpc, temp, sizeof(string), 0);
lstrcpyn(string, AutoChar(temp, CP_UTF8), sizeof(string));
free(temp);
if (WavpackGetMode(wpc) & MODE_VALID_TAG)
{
char value [128];
if (config_bits & (REPLAYGAIN_TRACK | REPLAYGAIN_ALBUM))
{
int local_clipping = 0;
float local_gain;
local_gain = calculate_gain(wpc);
if (local_gain != 1.0)
StringCchPrintf(string + strlen (string), 2048, "Gain: %+.2f dB %s\n",
log10 (local_gain) * 20.0, local_clipping ? "(w/soft clipping)" : "");
}
if (WavpackGetTagItem(wpc, "title", value, sizeof (value)))
{
if (!(mode & MODE_APETAG))
AnsiToUTF8(value, sizeof (value));
StringCchPrintf(string + strlen (string), 2048, "\nTitle: %s", value);
}
if (WavpackGetTagItem(wpc, "artist", value, sizeof (value)))
{
if (!(mode & MODE_APETAG))
AnsiToUTF8(value, sizeof (value));
StringCchPrintf(string + strlen (string), 2048, "\nArtist: %s", value);
}
if (WavpackGetTagItem (wpc, "album", value, sizeof (value)))
{
if (!(mode & MODE_APETAG))
AnsiToUTF8(value, sizeof (value));
StringCchPrintf (string + strlen (string), 2048, "\nAlbum: %s", value);
}
if (WavpackGetTagItem (wpc, "genre", value, sizeof (value)))
{
if (!(mode & MODE_APETAG))
AnsiToUTF8(value, sizeof (value));
StringCchPrintf(string + strlen (string), 2048, "\nGenre: %s", value);
}
if (WavpackGetTagItem (wpc, "comment", value, sizeof (value)))
{
if (!(mode & MODE_APETAG))
AnsiToUTF8(value, sizeof (value));
StringCchPrintf(string + strlen (string), 2048, "\nComment: %s", value);
}
if (WavpackGetTagItem(wpc, "year", value, sizeof (value)))
StringCchPrintf(string + strlen (string), 2048, "\nYear: %s", value);
if (WavpackGetTagItem(wpc, "track", value, sizeof (value)))
StringCchPrintf(string + strlen (string), 2048, "\nTrack: %s", value);
StringCchCat(string, 2048, "\n");
}
UTF8ToWideChar(string, w_string);
MessageBoxW(hwnd, w_string, L"WavPack File Info Box", MB_OK);
wpc = WavpackCloseFile(wpc);
}
else
MessageBox(hwnd, string, "WavPack Decoder", MB_OK);
return 0;
#else
return 1;
#endif
}
void getfileinfo(const char *filename, char *title, int *length_in_ms)
{
if (!filename || !*filename) // currently playing file
{
if (length_in_ms)
*length_in_ms = getlength ();
if (title)
{
if (WavpackGetTagItem(curr.wpc, "title", NULL, 0))
{
char art [128], ttl [128];
WavpackGetTagItem(curr.wpc, "title", ttl, sizeof (ttl));
if (WavpackGetMode(curr.wpc) & MODE_APETAG)
UTF8ToAnsi(ttl, sizeof (ttl));
if (WavpackGetTagItem(curr.wpc, "artist", art, sizeof (art)))
{
if (WavpackGetMode(curr.wpc) & MODE_APETAG)
UTF8ToAnsi(art, sizeof (art));
StringCchPrintf(title, GETFILEINFO_TITLE_LENGTH, "%s - %s", art, ttl);
}
else
lstrcpyn(title, ttl, GETFILEINFO_TITLE_LENGTH);
}
else
{
char *p = curr.lastfn + strlen (curr.lastfn);
while (*p != '\\' && p >= curr.lastfn)
p--;
lstrcpyn(title, ++p, GETFILEINFO_TITLE_LENGTH);
}
}
}
else // some other file
{
WavpackContext *wpc;
char error [128];
int open_flags;
if (length_in_ms)
*length_in_ms = -1000;
if (title)
*title = 0;
open_flags = OPEN_TAGS | OPEN_NORMALIZE;
if (config_bits & ALLOW_WVC)
open_flags |= OPEN_WVC;
if (!(config_bits & ALLOW_MULTICHANNEL))
open_flags |= OPEN_2CH_MAX;
wpc = WavpackOpenFileInput(filename, error, open_flags, 0);
if (wpc)
{
if (length_in_ms)
*length_in_ms = (int)(WavpackGetNumSamples(wpc) * 1000.0 / WavpackGetSampleRate(wpc));
if (title && WavpackGetTagItem(wpc, "title", NULL, 0))
{
char art [128], ttl [128];
WavpackGetTagItem(wpc, "title", ttl, sizeof (ttl));
if (WavpackGetMode(wpc) & MODE_APETAG)
UTF8ToAnsi(ttl, sizeof (ttl));
if (WavpackGetTagItem(wpc, "artist", art, sizeof (art)))
{
if (WavpackGetMode(wpc) & MODE_APETAG)
UTF8ToAnsi(art, sizeof (art));
StringCchPrintf(title, GETFILEINFO_TITLE_LENGTH, "%s - %s", art, ttl);
}
else
lstrcpyn(title, ttl, GETFILEINFO_TITLE_LENGTH);
}
wpc = WavpackCloseFile(wpc);
}
if (title && !*title)
{
char *p = (char*)filename + strlen (filename);
while (*p != '\\' && p >= filename) p--;
lstrcpyn(title, ++p, GETFILEINFO_TITLE_LENGTH);
}
}
}
void eq_set(int on, char data [10], int preamp)
{
// most plug-ins can't even do an EQ anyhow.. I'm working on writing
// a generic PCM EQ, but it looks like it'll be a little too CPU
// consuming to be useful :)
}
static int read_samples(struct wpcnxt *cnxt, int num_samples);
DWORD WINAPI __stdcall DecodeThread(void *b)
{
int num_chans, sample_rate;
int done = 0;
memset(curr.error, 0, sizeof (curr.error));
num_chans = WavpackGetReducedChannels(curr.wpc);
sample_rate = WavpackGetSampleRate(curr.wpc);
while (!*((int *)b) )
{
if (seek_needed != -1)
{
int seek_position = seek_needed;
int bc = 0;
seek_needed = -1;
if (seek_position > getlength() - 1000 && getlength() > 1000)
seek_position = getlength() - 1000; // don't seek to last second
mod.outMod->Flush(decode_pos_ms = seek_position);
if (WavpackSeekSample(curr.wpc, (int)(sample_rate / 1000.0 * seek_position))) {
decode_pos_ms = (int)(WavpackGetSampleIndex(curr.wpc) * 1000.0 / sample_rate);
mod.outMod->Flush(decode_pos_ms);
continue;
}
else
done = 1;
}
if (done) {
mod.outMod->CanWrite();
if (!mod.outMod->IsPlaying()) {
PostMessage(mod.hMainWindow, WM_WA_MPEG_EOF, 0, 0);
return 0;
}
Sleep(10);
}
else if (mod.outMod->CanWrite() >= ((576 * num_chans * (curr.output_bits / 8)) << (mod.dsp_isactive () ? 1 : 0)))
{
int tsamples = read_samples (&curr, 576) * num_chans;
int tbytes = tsamples * (curr.output_bits/8);
if (tsamples)
{
mod.SAAddPCMData((char *) curr.sample_buffer, num_chans, curr.output_bits, decode_pos_ms);
mod.VSAAddPCMData((char *) curr.sample_buffer, num_chans, curr.output_bits, decode_pos_ms);
decode_pos_ms = (int)(WavpackGetSampleIndex(curr.wpc) * 1000.0 / sample_rate);
if (mod.dsp_isactive())
tbytes = mod.dsp_dosamples ((short *) curr.sample_buffer,
tsamples / num_chans, curr.output_bits, num_chans, sample_rate) * (num_chans * (curr.output_bits/8));
mod.outMod->Write ((char *) curr.sample_buffer, tbytes);
}
else
done = 1;
}
else
{
mod.SetInfo((int) ((WavpackGetInstantBitrate (curr.wpc) + 500.0) / 1000.0), -1, -1, 1);
Sleep(20);
}
}
return 0;
}
/********* These functions provide the "transcoding" mode of winamp. *********/
extern "C" __declspec (dllexport) intptr_t winampGetExtendedRead_open (const char *fn, int *size, int *bps, int *nch, int *srate)
{
struct wpcnxt *cnxt = (struct wpcnxt *)malloc(sizeof (struct wpcnxt));
int num_chans, sample_rate, open_flags;
char error[128];
#ifdef DEBUG_CONSOLE
sprintf (error, "Read_open (%s)\n", fn);
debug_write (error);
#endif
if (!cnxt)
return 0;
memset(cnxt, 0, sizeof (struct wpcnxt));
open_flags = OPEN_NORMALIZE | OPEN_WVC;
if (!(config_bits & ALLOW_MULTICHANNEL) || *nch == 2)
open_flags |= OPEN_2CH_MAX;
if (config_bits & (REPLAYGAIN_TRACK | REPLAYGAIN_ALBUM))
open_flags |= OPEN_TAGS;
cnxt->wpc = WavpackOpenFileInput(fn, error, open_flags, 0);
if (!cnxt->wpc) // error opening file, just return error
{
free (cnxt);
return 0;
}
num_chans = WavpackGetReducedChannels(cnxt->wpc);
sample_rate = WavpackGetSampleRate(cnxt->wpc);
if (num_chans > MAX_NCH)
{
WavpackCloseFile(cnxt->wpc);
free (cnxt);
return 0;
}
if (*bps != 16 && *bps != 24 && *bps != 32)
{
cnxt->output_bits = WavpackGetBitsPerSample(cnxt->wpc) > 16 ? 24 : 16;
if (config_bits & ALWAYS_16BIT)
cnxt->output_bits = 16;
else if ((config_bits & (REPLAYGAIN_TRACK | REPLAYGAIN_ALBUM)) &&
(config_bits & REPLAYGAIN_24BIT))
cnxt->output_bits = 24;
}
else
cnxt->output_bits = *bps;
if (num_chans > MAX_NCH) // don't allow too many channels!
{
WavpackCloseFile(cnxt->wpc);
free (cnxt);
return 0;
}
*nch = num_chans;
*srate = sample_rate;
*bps = cnxt->output_bits;
*size = WavpackGetNumSamples(cnxt->wpc) * (*bps / 8) * (*nch);
cnxt->play_gain = calculate_gain(cnxt->wpc);
#ifdef DEBUG_CONSOLE
sprintf (error, "Read_open success! nch=%d, srate=%d, bps=%d, size=%d\n",
*nch, *srate, *bps, *size);
debug_write (error);
#endif
return (intptr_t) cnxt;
}
extern "C" __declspec (dllexport) intptr_t winampGetExtendedRead_getData (intptr_t handle, char *dest, int len, int *killswitch)
{
struct wpcnxt *cnxt = (struct wpcnxt *)handle;
int num_chans = WavpackGetReducedChannels(cnxt->wpc);
int bytes_per_sample = num_chans * cnxt->output_bits / 8;
int used = 0;
#ifdef DEBUG_CONSOLE
char error [128];
#endif
while (used < len && !*killswitch)
{
int nsamples = (len - used) / bytes_per_sample, tsamples;
if (!nsamples)
break;
else if (nsamples > 576)
nsamples = 576;
tsamples = read_samples(cnxt, nsamples) * num_chans;
if (tsamples)
{
int tbytes = tsamples * (cnxt->output_bits/8);
memcpy (dest + used, cnxt->sample_buffer, tbytes);
used += tbytes;
}
else
break;
}
#ifdef DEBUG_CONSOLE
sprintf (error, "Read_getData (%d), actualy read %d\n", len, used);
debug_write (error);
#endif
return used;
}
extern "C" __declspec (dllexport) int winampGetExtendedRead_setTime (intptr_t handle, int millisecs)
{
struct wpcnxt *cnxt = (struct wpcnxt *) handle;
int sample_rate = WavpackGetSampleRate(cnxt->wpc);
return WavpackSeekSample(cnxt->wpc, (int)(sample_rate / 1000.0 * millisecs));
}
extern "C" __declspec (dllexport) void winampGetExtendedRead_close (intptr_t handle)
{
struct wpcnxt *cnxt = (struct wpcnxt *) handle;
#ifdef DEBUG_CONSOLE
char error [128];
sprintf (error, "Read_close ()\n");
debug_write (error);
#endif
WavpackCloseFile(cnxt->wpc);
free (cnxt);
}
/* This is a generic function to read WavPack samples and convert them to a
* form usable by winamp. It includes conversion of any WavPack format
* (including ieee float) to 16, 24, or 32-bit integers (with noise shaping
* for the 16-bit case) and replay gain implementation (with optional soft
* clipping). It is used by both the regular "play" code and the newer
* transcoding functions.
*
* The num_samples parameter is the number of "composite" samples to
* convert and is limited currently to 576 samples for legacy reasons. The
* return value is the number of samples actually converted and will be
* equal to the number requested unless an error occurs or the end-of-file
* is encountered. The converted samples are stored (interleaved) at
* cnxt->sample_buffer[].
*/
static int read_samples (struct wpcnxt *cnxt, int num_samples)
{
int num_chans = WavpackGetReducedChannels(cnxt->wpc), samples, tsamples;
samples = WavpackUnpackSamples(cnxt->wpc, (int32_t*) cnxt->sample_buffer, num_samples);
tsamples = samples * num_chans;
if (tsamples)
{
if (!(WavpackGetMode(cnxt->wpc) & MODE_FLOAT))
{
float scaler = (float) (1.0 / ((unsigned long) 1 << (WavpackGetBytesPerSample(cnxt->wpc) * 8 - 1)));
float *fptr = (float *) cnxt->sample_buffer;
long *lptr = cnxt->sample_buffer;
int cnt = tsamples;
while (cnt--)
*fptr++ = *lptr++ * scaler;
}
if (cnxt->play_gain != 1.0)
{
float *fptr = (float *) cnxt->sample_buffer;
int cnt = tsamples;
double outval;
while (cnt--)
{
outval = *fptr * cnxt->play_gain;
/*if (cnxt->soft_clipping)
{
if (outval > 0.75)
outval = 1.0 - (0.0625 / (outval - 0.5));
else if (outval < -0.75)
outval = -1.0 - (0.0625 / (outval + 0.5));
}*/
*fptr++ = (float) outval;
}
}
if (cnxt->output_bits == 16)
{
float *fptr = (float *) cnxt->sample_buffer;
short *sptr = (short *) cnxt->sample_buffer;
int cnt = samples, ch;
while (cnt--)
for (ch = 0; ch < num_chans; ++ch)
{
int dst;
*fptr -= cnxt->error [ch];
if (*fptr >= 1.0)
dst = 32767;
else if (*fptr <= -1.0)
dst = -32768;
else
dst = (int) floor (*fptr * 32768.0);
cnxt->error [ch] = (float)(dst / 32768.0 - *fptr++);
*sptr++ = dst;
}
}
else if (cnxt->output_bits == 24)
{
unsigned char *cptr = (unsigned char *) cnxt->sample_buffer;
float *fptr = (float *) cnxt->sample_buffer;
int cnt = tsamples;
long outval;
while (cnt--) {
if (*fptr >= 1.0)
outval = 8388607;
else if (*fptr <= -1.0)
outval = -8388608;
else
outval = (int) floor (*fptr * 8388608.0);
*cptr++ = (unsigned char) outval;
*cptr++ = (unsigned char) (outval >> 8);
*cptr++ = (unsigned char) (outval >> 16);
fptr++;
}
}
else if (cnxt->output_bits == 32)
{
float *fptr = (float *) cnxt->sample_buffer;
long *sptr = (long *) cnxt->sample_buffer;
int cnt = tsamples;
while (cnt--)
{
if (*fptr >= 1.0)
*sptr++ = 8388607 << 8;
else if (*fptr <= -1.0)
*sptr++ = -8388608 << 8;
else
*sptr++ = ((int) floor (*fptr * 8388608.0)) << 8;
fptr++;
}
}
}
return samples;
}
extern "C" __declspec (dllexport) In_Module * winampGetInModule2()
{
return &mod;
}
// This code provides an interface between the reader callback mechanism that
// WavPack uses internally and the standard fstream C library.
static int32_t read_bytes(void *id, void *data, int32_t bcount)
{
FILE *file = id ? *(FILE**)id : NULL;
if (file)
return (int32_t) fread(data, 1, bcount, file);
else
return 0;
}
static uint32_t get_pos(void *id)
{
FILE *file = id ? *(FILE**)id : NULL;
if (file)
return ftell(file);
else
return -1;
}
static int set_pos_abs(void *id, uint32_t pos)
{
FILE *file = id ? *(FILE**)id : NULL;
if (file)
return fseek(file, pos, SEEK_SET);
else
return 0;
}
static int set_pos_rel(void *id, int32_t delta, int mode)
{
FILE *file = id ? *(FILE**)id : NULL;
if (file)
return fseek(file, delta, mode);
else
return -1;
}
static int push_back_byte(void *id, int c)
{
FILE *file = id ? *(FILE**)id : NULL;
if (file)
return ungetc(c, file);
else
return EOF;
}
static uint32_t get_length(void *id)
{
FILE *file = id ? *(FILE**)id : NULL;
struct stat statbuf;
if (!file || fstat (fileno (file), &statbuf) || !(statbuf.st_mode & S_IFREG))
return 0;
else
return statbuf.st_size;
}
static int can_seek(void *id)
{
FILE *file = id ? *(FILE**)id : NULL;
struct stat statbuf;
return file && !fstat (fileno (file), &statbuf) && (statbuf.st_mode & S_IFREG);
}
static int32_t write_bytes(void *id, void *data, int32_t bcount)
{
FILE *file = id ? *(FILE**)id : NULL;
if (file)
return (int32_t) fwrite (data, 1, bcount, file);
else
return 0;
}
static WavpackStreamReader freader = {
read_bytes, get_pos, set_pos_abs,
set_pos_rel, push_back_byte,
get_length, can_seek, write_bytes
};
/* These functions provide UNICODE support for the winamp media library */
static int metadata_we_can_write(const char *metadata);
static void close_context(struct wpcnxt *cxt)
{
if (cxt->wpc)
WavpackCloseFile(cxt->wpc);
if (cxt->wv_id)
fclose(cxt->wv_id);
if (cxt->wvc_id)
fclose(cxt->wvc_id);
memset(cxt, 0, sizeof (*cxt));
}
#ifdef ANSI_METADATA
extern "C" __declspec (dllexport) int winampGetExtendedFileInfo(char *filename, char *metadata, char *ret, int retlen)
{
int open_flags = OPEN_TAGS;
char error[128];
int retval = 0;
#ifdef DEBUG_CONSOLE
sprintf (error, "winampGetExtendedFileInfo (%s)\n", metadata);
debug_write (error);
#endif
if (!_stricmp(metadata, "type"))
{
ret[0] = '0';
ret[1] = 0;
return 1;
}
else if (!_stricmp(metadata, "family"))
{
int len;
const char *p;
if (!filename || !filename[0]) return 0;
len = lstrlen(filename);
if (len < 3 || '.' != filename[len - 3]) return 0;
p = &filename[len - 2];
if (!_stricmp(p, "wv")) { WASABI_API_LNGSTRING_BUF(IDS_FAMILY_STRING, ret, retlen); return 1; }
return 0;
}
if (!filename || !*filename)
return retval;
if (!_stricmp(metadata, "length")) { /* even if no file, return a 1 and write "0" */
StringCchPrintf(ret, retlen, "%d", 0);
retval = 1;
}
if (!info.wpc || strcmp(filename, info.lastfn) || !_stricmp(metadata, "formatinformation"))
{
close_context(&info);
if (!(info.wv_id = fopen(filename, "rb")))
return retval;
if (config_bits & ALLOW_WVC)
{
int length = strlen(filename) + 10;
char *wvc_name = (char *)malloc(length);
if (wvc_name)
{
lstrcpyn(wvc_name, filename, length);
StringCchCat(wvc_name, length, "c");
info.wvc_id = fopen(wvc_name, "rb");
free(wvc_name);
}
}
info.wpc = WavpackOpenFileInputEx(&freader, &info.wv_id, info.wvc_id ? &info.wvc_id : NULL, error, open_flags, 0);
if (!info.wpc)
{
close_context(&info);
return retval;
}
lstrcpyn(info.lastfn, filename, MAX_PATH);
info.w_lastfn [0] = 0;
}
if (!_stricmp(metadata, "formatinformation"))
{
wchar_t *temp = (wchar_t *)malloc(retlen * sizeof(wchar_t));
generate_format_string(info.wpc, temp, retlen, 0);
lstrcpyn(ret, AutoChar(temp), retlen);
free(temp);
retval = 1;
}
else if (!_stricmp(metadata, "length"))
{
StringCchPrintf(ret, retlen, "%d", (int)(WavpackGetNumSamples(info.wpc) * 1000.0 / WavpackGetSampleRate(info.wpc)));
retval = 1;
}
else if (!_stricmp(metadata, "lossless"))
{
StringCchPrintf (ret, retlen, "%d", (WavpackGetMode(info.wpc) & MODE_LOSSLESS) ? 1 : 0);
retval = 1;
}
else if (!_stricmp(metadata, "numsamples"))
{
StringCchPrintf(ret, retlen, "%d", WavpackGetNumSamples(info.wpc));
retval = 1;
}
else if (!_stricmp(metadata, "mime"))
{
lstrcpyn(ret, L"audio/x-wavpack", retlen);
retval = 1;
}
else if (!_stricmp(metadata, "gain"))
{
StringCchPrintf(ret, retlen, "%-+.2f dB", calculate_gain(info.wpc, false));
retval = 1;
}
else if (WavpackGetTagItem(info.wpc, metadata, ret, retlen))
{
if (!_stricmp(metadata, "rating"))
{
int rating = atoi(ret);
// appears to be generally 0-5 or 0-100
if (rating > 10) {
rating /= 20;
}
// or maybe we're dealing with a 1-10 range
else if (rating > 5) {
rating /= 2;
}
// otherwise it is hopefully in the 0-5 range
StringCchPrintf(ret, retlen, "%u", rating);
}
else
{
if (WavpackGetMode(info.wpc) & MODE_APETAG)
{
UTF8ToAnsi(ret, retlen);
}
}
retval = 1;
}
else if (metadata_we_can_write(metadata))
{
if (retlen)
*ret = 0;
retval = 1;
}
// This is a little ugly, but since the WavPack library has read the tags off the
// files, we can close the files (but not the WavPack context) now so that we don't
// leave handles open. We may access the file again for the "formatinformation"
// field, so we reopen the file if we get that one.
if (info.wv_id)
{
fclose(info.wv_id);
info.wv_id = NULL;
}
if (info.wvc_id)
{
fclose(info.wvc_id);
info.wvc_id = NULL;
}
return retval;
}
#endif
#ifdef UNICODE_METADATA
extern "C" __declspec (dllexport) int winampGetExtendedFileInfoW (wchar_t *filename, char *metadata, wchar_t *ret, int retlen)
{
char error[128], res[256];
int open_flags = OPEN_TAGS;
int retval = 0;
#ifdef DEBUG_CONSOLE
sprintf (error, "winampGetExtendedFileInfoW (%s)\n", metadata);
debug_write (error);
#endif
if (!_stricmp(metadata, "type"))
{
ret[0] = '0';
ret[1] = 0;
return 1;
}
else if (!_stricmp(metadata, "family"))
{
int len;
const wchar_t *p;
if (!filename || !filename[0]) return 0;
len = lstrlenW(filename);
if (len < 3 || L'.' != filename[len - 3]) return 0;
p = &filename[len - 2];
if (!_wcsicmp(p, L"wv")) { WASABI_API_LNGSTRINGW_BUF(IDS_FAMILY_STRING, ret, retlen); return 1; }
return 0;
}
if (!filename || !*filename)
return retval;
if (!_stricmp(metadata, "length")) /* even if no file, return a 1 and write "0" */
{
StringCchPrintfW(ret, retlen, L"%d", 0);
retval = 1;
}
if (!info.wpc || wcscmp(filename, info.w_lastfn) || !_stricmp(metadata, "formatinformation"))
{
close_context(&info);
if (!(info.wv_id = _wfopen(filename, L"rb")))
return retval;
if (config_bits & ALLOW_WVC) {
int length = wcslen(filename) + 10;
wchar_t *wvc_name = (wchar_t *)malloc(length * sizeof(wchar_t));
if (wvc_name) {
lstrcpynW(wvc_name, filename, length);
StringCchCatW(wvc_name, length, L"c");
info.wvc_id = _wfopen(wvc_name, L"rb");
free(wvc_name);
}
}
info.wpc = WavpackOpenFileInputEx(&freader, &info.wv_id, info.wvc_id ? &info.wvc_id : NULL, error, open_flags, 0);
if (!info.wpc)
{
close_context(&info);
return retval;
}
lstrcpynW(info.w_lastfn, filename, MAX_PATH);
info.lastfn[0] = 0;
}
if (!_stricmp(metadata, "formatinformation"))
{
generate_format_string(info.wpc, ret, retlen, 0);
retval = 1;
}
else if (!_stricmp (metadata, "length"))
{
StringCchPrintfW(ret, retlen, L"%d", (int)(WavpackGetNumSamples(info.wpc) * 1000.0 / WavpackGetSampleRate(info.wpc)));
retval = 1;
}
else if (!_stricmp(metadata, "lossless"))
{
StringCchPrintfW(ret, retlen, L"%d", (WavpackGetMode(info.wpc) & MODE_LOSSLESS) ? 1 : 0);
retval = 1;
}
else if (!_stricmp(metadata, "gain"))
{
StringCchPrintfW(ret, retlen, L"%-+.2f dB", calculate_gain(info.wpc, false));
retval = 1;
}
else if (!_stricmp(metadata, "numsamples"))
{
StringCchPrintfW(ret, retlen, L"%d", WavpackGetNumSamples(info.wpc));
retval = 1;
}
else if (!_stricmp(metadata, "mime"))
{
lstrcpynW(ret, L"audio/x-wavpack", retlen);
retval = 1;
}
else if (WavpackGetTagItem(info.wpc, metadata, res, sizeof (res)))
{
if (!_stricmp(metadata, "rating"))
{
int rating = atoi(res);
// appears to be generally 0-5 or 0-100
if (rating > 10) {
rating /= 20;
}
// or maybe we're dealing with a 1-10 range
else if (rating > 5) {
rating /= 2;
}
// otherwise it is hopefully in the 0-5 range
StringCchPrintfW(ret, retlen, L"%u", rating);
}
else
{
if (!(WavpackGetMode(info.wpc) & MODE_APETAG))
lstrcpynW(ret, AutoWide(res), retlen);
else
lstrcpynW(ret, AutoWide(res, CP_UTF8), retlen);
}
retval = 1;
}
else if (metadata_we_can_write (metadata))
{
if (retlen)
*ret = 0;
retval = 1;
}
// This is a little ugly, but since the WavPack library has read the tags off the
// files, we can close the files (but not the WavPack context) now so that we don't
// leave handles open. We may access the file again for the "formatinformation"
// field, so we reopen the file if we get that one.
if (info.wv_id)
{
fclose (info.wv_id);
info.wv_id = NULL;
}
if (info.wvc_id)
{
fclose (info.wvc_id);
info.wvc_id = NULL;
}
return retval;
}
#endif
#ifdef ANSI_METADATA
extern "C" int __declspec (dllexport) winampSetExtendedFileInfo(const char *filename, const char *metadata, char *val)
{
char error[128];
#ifdef DEBUG_CONSOLE
sprintf (error, "winampSetExtendedFileInfo (%s=%s)\n", metadata, val);
debug_write (error);
#endif
if (!filename || !*filename || !metadata_we_can_write(metadata))
return 0;
if (!edit.wpc || strcmp(filename, edit.lastfn))
{
if (edit.wpc)
WavpackCloseFile(edit.wpc);
edit.wpc = WavpackOpenFileInput(filename, error, OPEN_TAGS | OPEN_EDIT_TAGS, 0);
if (!edit.wpc)
return 0;
lstrcpyn(edit.lastfn, filename, MAX_PATH);
edit.w_lastfn [0] = 0;
}
if (strlen(val))
return WavpackAppendTagItem(edit.wpc, metadata, val, strlen (val));
else
return WavpackDeleteTagItem(edit.wpc, metadata);
}
#endif
#ifdef UNICODE_METADATA
extern "C" int __declspec (dllexport) winampSetExtendedFileInfoW(const wchar_t *filename, const char *metadata, wchar_t *val)
{
char error[128], utf8_val[256];
lstrcpyn(utf8_val,AutoChar(val, CP_UTF8),sizeof(utf8_val) - 1);
#ifdef DEBUG_CONSOLE
sprintf (error, "winampSetExtendedFileInfoW (%s=%s)\n", metadata, utf8_val);
debug_write (error);
#endif
if (!filename || !*filename || !metadata_we_can_write(metadata))
return 0;
if (!edit.wpc || wcscmp(filename, edit.w_lastfn))
{
if (edit.wpc)
{
WavpackCloseFile(edit.wpc);
edit.wpc = NULL;
}
if (edit.wv_id)
fclose(edit.wv_id);
if (!(edit.wv_id = _wfopen(filename, L"r+b")))
return 0;
edit.wpc = WavpackOpenFileInputEx(&freader, &edit.wv_id, NULL, error, OPEN_TAGS | OPEN_EDIT_TAGS, 0);
if (!edit.wpc)
{
fclose(edit.wv_id);
return 0;
}
lstrcpynW(edit.w_lastfn, filename, MAX_PATH);
edit.lastfn [0] = 0;
}
if (strlen(utf8_val))
return WavpackAppendTagItem(edit.wpc, metadata, utf8_val, strlen (utf8_val));
else
return WavpackDeleteTagItem(edit.wpc, metadata);
}
#endif
extern "C" int __declspec (dllexport) winampWriteExtendedFileInfo(void)
{
#ifdef DEBUG_CONSOLE
debug_write ("winampWriteExtendedFileInfo ()\n");
#endif
if (edit.wpc)
{
WavpackWriteTag(edit.wpc);
WavpackCloseFile(edit.wpc);
edit.wpc = NULL;
}
if (edit.wv_id)
{
fclose(edit.wv_id);
edit.wv_id = NULL;
}
close_context(&info); // make sure we re-read info on any open files
return 1;
}
// return 1 if you want winamp to show it's own file info dialogue, 0 if you want to show your own (via In_Module.InfoBox)
// if returning 1, remember to implement winampGetExtendedFileInfo("formatinformation")!
extern "C" __declspec(dllexport) int winampUseUnifiedFileInfoDlg(const wchar_t * fn)
{
return 1;
}
static const char *writable_metadata [] = {
"track", "genre", "year", "comment", "artist",
"album", "title", "albumartist", "composer",
"publisher", "disc", "tool", "encoder", "bpm",
"category", "rating",
"replaygain_track_gain", "replaygain_track_peak",
"replaygain_album_gain", "replaygain_album_peak"
};
#define NUM_KNOWN_METADATA (sizeof (writable_metadata) / sizeof (writable_metadata [0]))
static int metadata_we_can_write(const char *metadata)
{
if (!metadata || !*metadata)
return 0;
for (int i = 0; i < NUM_KNOWN_METADATA; ++i)
if (!_stricmp(metadata, writable_metadata[i]))
return 1;
return 0;
}
static void generate_format_string(WavpackContext *wpc, wchar_t *string, int maxlen, int wide)
{
int mode = WavpackGetMode(wpc);
unsigned char md5_sum[16];
wchar_t modes[256];
wchar_t fmt[256];
WASABI_API_LNGSTRINGW_BUF(IDS_ENCODER_VERSION, fmt, sizeof(fmt));
StringCchPrintfW(string, maxlen, fmt, WavpackGetVersion(wpc));
while (*string && string++ && maxlen--);
WASABI_API_LNGSTRINGW_BUF(IDS_SOURCE, fmt, sizeof (fmt));
StringCchPrintfW(string, maxlen, fmt, WavpackGetBitsPerSample(wpc),
WASABI_API_LNGSTRINGW((WavpackGetMode(wpc) & MODE_FLOAT) ? IDS_FLOATS : IDS_INTS),
WavpackGetSampleRate(wpc));
while (*string && string++ && maxlen--);
if (WavpackGetNumChannels(wpc) > 2)
{
WASABI_API_LNGSTRINGW_BUF(IDS_MULTICHANNEL, fmt, sizeof (fmt));
StringCchPrintfW(string, maxlen, fmt, WavpackGetNumChannels(wpc));
while (*string && string++ && maxlen--);
}
else if (WavpackGetNumChannels(wpc) == 2)
{
WASABI_API_LNGSTRINGW_BUF(IDS_STEREO, fmt, sizeof (fmt));
StringCchPrintfW(string, maxlen, fmt);
while (*string && string++ && maxlen--);
}
else
{
WASABI_API_LNGSTRINGW_BUF(IDS_MONO, fmt, sizeof (fmt));
StringCchPrintfW(string, maxlen, fmt);
while (*string && string++ && maxlen--);
}
modes [0] = 0;
if (WavpackGetMode(wpc) & MODE_HYBRID)
{
StringCchCatW(modes, 256, WASABI_API_LNGSTRINGW(IDS_HYBRID));
StringCchCatW(modes, 256, L" ");
}
StringCchCatW(modes, 256, WASABI_API_LNGSTRINGW((WavpackGetMode(wpc) & MODE_LOSSLESS) ? IDS_LOSSLESS : IDS_LOSSY));
if (WavpackGetMode(wpc) & MODE_FAST)
StringCchCatW(modes, 256, WASABI_API_LNGSTRINGW(IDS_FAST));
else if (WavpackGetMode(wpc) & MODE_VERY_HIGH)
StringCchCatW(modes, 256, WASABI_API_LNGSTRINGW(IDS_VHIGH));
else if (WavpackGetMode(wpc) & MODE_HIGH)
StringCchCatW(modes, 256, WASABI_API_LNGSTRINGW(IDS_HIGH));
if (WavpackGetMode(wpc) & MODE_EXTRA)
StringCchCatW(modes, 256, WASABI_API_LNGSTRINGW(IDS_EXTRA));
StringCchPrintfW(string, maxlen, L"%s:%s %s\n",
WASABI_API_LNGSTRINGW(IDS_MODES),
(wide || lstrlenW(modes) < 24) ? L"" : L"\n", modes);
while (*string && string++ && maxlen--);
if (WavpackGetRatio(wpc) != 0.0)
{
wchar_t str_kbps[32];
StringCchPrintfW(string, maxlen, L"%s: %d %s \n",
WASABI_API_LNGSTRINGW(IDS_BITRATE),
(int)((WavpackGetAverageBitrate(wpc, TRUE) + 500.0) / 1000.0),
WASABI_API_LNGSTRINGW_BUF(IDS_KBPS, str_kbps, sizeof(str_kbps)));
while (*string && string++ && maxlen--);
StringCchPrintfW(string, maxlen, L"%s: %.2f : 1 \n",
WASABI_API_LNGSTRINGW(IDS_RATIO), 1.0 / WavpackGetRatio(wpc));
while (*string && string++ && maxlen--);
}
if (WavpackGetMD5Sum(wpc, md5_sum))
{
wchar_t md5s1 [17], md5s2 [17];
int i;
for (i = 0; i < 8; ++i)
{
StringCchPrintfW(md5s1 + i * 2, sizeof(md5s1), L"%02x", md5_sum [i]);
StringCchPrintfW(md5s2 + i * 2, sizeof(md5s2), L"%02x", md5_sum [i+8]);
}
StringCchPrintfW(string, maxlen, (wide ? L"%s: %s%s\n" : L"%s:\n %s\n %s\n"),
WASABI_API_LNGSTRINGW(IDS_MD5), md5s1, md5s2);
}
}
///////////////////// native "C" functions required for AlbumArt support ///////////////////////////
#ifdef DEBUG_CONSOLE
static char temp_buff [256];
static char *wide2char (const wchar_t *src)
{
char *dst = temp_buff;
while (*src)
*dst++ = (char) *src++;
*dst = 0;
return temp_buff;
}
#endif
int WavPack_HandlesExtension(const wchar_t *extension)
{
return !_wcsicmp(extension, L"wv");
}
int WavPack_GetAlbumArt(const wchar_t *filename, const wchar_t *type, void **bits, size_t *len, wchar_t **mime_type)
{
char *buffer;
char error[128];
int tag_size, i;
int retval = 1;
#ifdef DEBUG_CONSOLE
sprintf (error, "WavPack_GetAlbumArt (%s)\n", wide2char (type));
debug_write (error);
#endif
if (!filename || !*filename || _wcsicmp(type, L"cover"))
return retval;
if (!info.wpc || wcscmp(filename, info.w_lastfn))
{
close_context(&info);
if (!(info.wv_id = _wfopen(filename, L"rb")))
return retval;
if (config_bits & ALLOW_WVC)
{
int length = wcslen(filename) + 10;
wchar_t *wvc_name = (wchar_t *)malloc(length * sizeof(wchar_t));
if (wvc_name)
{
lstrcpynW(wvc_name, filename, length);
StringCchCatW(wvc_name, length, L"c");
info.wvc_id = _wfopen(wvc_name, L"rb");
free(wvc_name);
}
}
info.wpc = WavpackOpenFileInputEx(&freader, &info.wv_id, info.wvc_id ? &info.wvc_id : NULL, error, OPEN_TAGS, 0);
if (!info.wpc)
{
close_context(&info);
return retval;
}
lstrcpynW(info.w_lastfn, filename, MAX_PATH);
info.lastfn[0] = 0;
}
tag_size = WavpackGetBinaryTagItem(info.wpc, "Cover Art (Front)", NULL, 0);
if (!tag_size)
return retval;
buffer = (char*)WASABI_API_MEMMGR->sysMalloc(tag_size);
WavpackGetBinaryTagItem(info.wpc, "Cover Art (Front)", buffer, tag_size);
for (i = 0; i < tag_size - 1; ++i)
if (!buffer[i] && strrchr(buffer, '.')) {
char *ext = strrchr(buffer, '.') + 1;
wchar_t *wcptr;
wcptr = *mime_type = (wchar_t*)WASABI_API_MEMMGR->sysMalloc(strlen(ext) * 2 + 2);
while (*ext)
*wcptr++ = *ext++;
*wcptr = 0;
*bits = buffer;
*len = tag_size - i - 1;
memmove(buffer, buffer + i + 1, *len);
retval = 0;
#ifdef DEBUG_CONSOLE
sprintf (error, "WavPack_GetAlbumArt (\"%s\", %d) success!\n", wide2char (*mime_type), *len);
debug_write (error);
#endif
}
if (retval)
WASABI_API_MEMMGR->sysFree(buffer);
// This is a little ugly, but since the WavPack library has read the tags off the
// files, we can close the files (but not the WavPack context) now so that we don't
// leave handles open. We may access the file again for the "formatinformation"
// field, so we reopen the file if we get that one.
if (info.wv_id)
{
fclose(info.wv_id);
info.wv_id = NULL;
}
if (info.wvc_id)
{
fclose(info.wvc_id);
info.wvc_id = NULL;
}
return retval;
}
int WavPack_SetAlbumArt(const wchar_t *filename, const wchar_t *type, void *bits, size_t len, const wchar_t *mime_type)
{
#if 1
return 2; // return 2 to indicate "read-only" cover art for now
#else
char error [128], name [50], *cp;
int tag_size, retval = 0;
unsigned char *buffer;
#ifdef DEBUG_CONSOLE
sprintf (error, "WavPack_SetAlbumArt (%s)\n", wide2char (mime_type));
debug_write (error);
#endif
if (!filename || !*filename || _wcsicmp (type, L"cover") || wcslen (mime_type) > 16)
return 1;
strcpy (name, "Cover Art (Front)");
cp = name + strlen (name);
*cp++ = '.';
while (*mime_type)
*cp++ = (char) *mime_type++;
*cp = 0;
tag_size = strlen (name) + 1 + len;
buffer = malloc (tag_size);
strcpy (buffer, name);
memcpy (buffer + strlen (buffer) + 1, bits, len);
if (!edit.wpc || wcscmp (filename, edit.w_lastfn)) {
if (edit.wpc) {
WavpackCloseFile (edit.wpc);
edit.wpc = NULL;
}
if (edit.wv_id)
fclose (edit.wv_id);
if (!(edit.wv_id = _wfopen (filename, L"r+b"))) {
free (buffer);
return 1;
}
edit.wpc = WavpackOpenFileInputEx (&freader, &edit.wv_id, NULL, error, OPEN_TAGS | OPEN_EDIT_TAGS, 0);
if (!edit.wpc) {
fclose (edit.wv_id);
free (buffer);
return 1;
}
wcscpy (edit.w_lastfn, filename);
edit.lastfn [0] = 0;
}
retval = WavpackAppendTagItem (edit.wpc, "Cover Art (Front)", buffer, tag_size);
free (buffer);
if (retval) {
winampWriteExtendedFileInfo ();
return 0;
}
else {
close_context (&edit);
return 1;
}
#endif
}
int WavPack_DeleteAlbumArt(const wchar_t *filename, const wchar_t *type)
{
#if 1
return 2; // return 2 to indicate "read-only" cover art for now
#else
char error [128];
#ifdef DEBUG_CONSOLE
sprintf (error, "WavPack_DeleteAlbumArt ()\n");
debug_write (error);
#endif
if (!filename || !*filename || _wcsicmp (type, L"cover"))
return 0;
if (!edit.wpc || wcscmp (filename, edit.w_lastfn)) {
if (edit.wpc) {
WavpackCloseFile (edit.wpc);
edit.wpc = NULL;
}
if (edit.wv_id)
fclose (edit.wv_id);
if (!(edit.wv_id = _wfopen (filename, L"r+b")))
return 1;
edit.wpc = WavpackOpenFileInputEx (&freader, &edit.wv_id, NULL, error, OPEN_TAGS | OPEN_EDIT_TAGS, 0);
if (!edit.wpc) {
fclose (edit.wv_id);
return 1;
}
wcscpy (edit.w_lastfn, filename);
edit.lastfn [0] = 0;
}
if (WavpackDeleteTagItem (edit.wpc, "Cover Art (Front)")) {
winampWriteExtendedFileInfo ();
return 0;
}
else {
close_context (&edit);
return 1;
}
#endif
}
//////////////////////////////////////////////////////////////////////////////
// This function uses the ReplayGain mode selected by the user and the info //
// stored in the specified tag to determine the gain value used to play the //
// file and whether "soft clipping" is required. Note that the gain is in //
// voltage scaling (not dB), so a value of 1.0 (not 0.0) is unity gain. //
//////////////////////////////////////////////////////////////////////////////
static float calculate_gain(WavpackContext *wpc, bool allowDefault)
{
if (AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"replaygain", false))
{
float dB = 0, peak = 1.0f;
char gain[128] = "", peakVal[128] = "";
_locale_t C_locale = WASABI_API_LNG->Get_C_NumericLocale();
switch (AGAVE_API_CONFIG->GetUnsigned(playbackConfigGroupGUID, L"replaygain_source", 0))
{
case 0: // track
if ((!WavpackGetTagItem(wpc, "replaygain_track_gain", gain, sizeof(gain)) || !gain[0])
&& !AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"replaygain_preferred_only", false))
WavpackGetTagItem(wpc, "replaygain_album_gain", gain, sizeof(gain));
if ((!WavpackGetTagItem(wpc, "replaygain_track_peak", peakVal, sizeof(peakVal)) || !peakVal[0])
&& !AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"replaygain_preferred_only", false))
WavpackGetTagItem(wpc, "replaygain_album_peak", peakVal, sizeof(peakVal));
break;
case 1:
if ((!WavpackGetTagItem(wpc, "replaygain_album_gain", gain, sizeof(gain)) || !gain[0])
&& !AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"replaygain_preferred_only", false))
WavpackGetTagItem(wpc, "replaygain_track_gain", gain, sizeof(gain));
if ((!WavpackGetTagItem(wpc, "replaygain_album_peak", peakVal, sizeof(peakVal)) || !peakVal[0])
&& !AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"replaygain_preferred_only", false))
WavpackGetTagItem(wpc, "replaygain_track_peak", peakVal, sizeof(peakVal));
break;
}
if (gain[0])
{
if (gain[0] == L'+')
dB = (float)_atof_l(&gain[1],C_locale);
else
dB = (float)_atof_l(gain,C_locale);
}
else if (allowDefault)
{
dB = AGAVE_API_CONFIG->GetFloat(playbackConfigGroupGUID, L"non_replaygain", -6.0);
return powf(10.0f, dB / 20.0f);
}
if (peakVal[0])
{
peak = (float)_atof_l(peakVal,C_locale);
}
switch (AGAVE_API_CONFIG->GetUnsigned(playbackConfigGroupGUID, L"replaygain_mode", 1))
{
case 0: // apply gain
return powf(10.0f, dB / 20.0f);
case 1: // apply gain, but don't clip
return min(powf(10.0f, dB / 20.0f), 1.0f / peak);
case 2: // normalize
return 1.0f / peak;
case 3: // prevent clipping
if (peak > 1.0f)
return 1.0f / peak;
else
return 1.0f;
}
}
return 1.0f; // no gain
}
// Convert a Ansi string into its Unicode UTF-8 format equivalent. The
// conversion is done in-place so the maximum length of the string buffer must
// be specified because the string may become longer or shorter. If the
// resulting string will not fit in the specified buffer size then it is
// truncated.
#ifdef OLD_INFO_DIALOG
static void AnsiToUTF8(char *string, int len)
{
int max_chars = (int) strlen(string);
wchar_t *temp = (wchar_t *) malloc((max_chars + 1) * 2);
MultiByteToWideChar(CP_ACP, 0, string, -1, temp, max_chars + 1);
lstrcpyn(string, AutoChar(temp, CP_UTF8), len);
free(temp);
}
#endif
// Convert Unicode UTF-8 string to wide format. UTF-8 string must be NULL
// terminated. Resulting wide string must be able to fit in provided space
// and will also be NULL terminated. The number of characters converted will
// be returned (not counting terminator).
static int UTF8ToWideChar(const char *pUTF8, wchar_t *pWide)
{
int trail_bytes = 0;
int chrcnt = 0;
while (*pUTF8)
{
if (*pUTF8 & 0x80)
{
if (*pUTF8 & 0x40)
{
if (trail_bytes)
{
trail_bytes = 0;
chrcnt++;
}
else
{
char temp = *pUTF8;
while (temp & 0x80)
{
trail_bytes++;
temp <<= 1;
}
pWide [chrcnt] = temp >> trail_bytes--;
}
}
else if (trail_bytes)
{
pWide [chrcnt] = (pWide [chrcnt] << 6) | (*pUTF8 & 0x3f);
if (!--trail_bytes)
chrcnt++;
}
}
else
pWide [chrcnt++] = *pUTF8;
pUTF8++;
}
pWide [chrcnt] = 0;
return chrcnt;
}
// Convert a Unicode UTF-8 format string into its Ansi equivalent. The
// conversion is done in-place so the maximum length of the string buffer must
// be specified because the string may become longer or shorter. If the
// resulting string will not fit in the specified buffer size then it is
// truncated.
static void UTF8ToAnsi(char *string, int len)
{
int max_chars = (int)strlen(string);
wchar_t *temp = (wchar_t *)malloc((max_chars + 1) * 2);
int act_chars = UTF8ToWideChar(string, temp);
while (act_chars)
{
memset (string, 0, len);
if (WideCharToMultiByte(CP_ACP, 0, temp, act_chars, string, len - 1, NULL, NULL))
break;
else
act_chars--;
}
if (!act_chars)
*string = 0;
free (temp);
}
extern "C" __declspec(dllexport) int winampUninstallPlugin(HINSTANCE hDllInst, HWND hwndDlg, int param)
{
// as we're not hooking anything and have no settings we can support an on-the-fly uninstall action
return IN_PLUGIN_UNINSTALL_NOW;
}