mirror of
https://github.com/vgmstream/vgmstream.git
synced 2024-11-14 18:47:39 +01:00
931 lines
31 KiB
C
931 lines
31 KiB
C
/**
|
|
* vgmstream for Winamp
|
|
*/
|
|
|
|
/* Normally Winamp opens unicode files by their DOS 8.3 name. #define this to use wchar_t filenames,
|
|
* which must be opened with _wfopen in a WINAMP_STREAMFILE (needed for dual files like .pos).
|
|
* Only for Winamp paths, other parts would need #define UNICODE for Windows. */
|
|
#define UNICODE_INPUT_PLUGIN
|
|
|
|
|
|
#ifdef _MSC_VER
|
|
#define _CRT_SECURE_NO_DEPRECATE
|
|
#endif
|
|
#include <windows.h>
|
|
#include <windowsx.h>
|
|
#include <commctrl.h>
|
|
#include <stdio.h>
|
|
#include <io.h>
|
|
#include <string.h>
|
|
#include <ctype.h>
|
|
|
|
#include "../src/formats.h"
|
|
#include "../src/vgmstream.h"
|
|
#include "in2.h"
|
|
#include "wa_ipc.h"
|
|
#include "ipc_pe.h"
|
|
#include "resource.h"
|
|
|
|
|
|
#ifndef VERSION
|
|
#include "../version.h"
|
|
#endif
|
|
#ifndef VERSION
|
|
#define VERSION "(unknown version)"
|
|
#endif
|
|
|
|
#define PLUGIN_DESCRIPTION "vgmstream plugin " VERSION " " __DATE__
|
|
|
|
/* ************************************* */
|
|
|
|
/* config */
|
|
#define CONFIG_APP_NAME "vgmstream plugin"
|
|
#define CONFIG_INI_NAME "plugin.ini"
|
|
|
|
#define DEFAULT_FADE_SECONDS "10.00"
|
|
#define DEFAULT_FADE_DELAY_SECONDS "0.00"
|
|
#define DEFAULT_LOOP_COUNT "2.00"
|
|
#define DEFAULT_THREAD_PRIORITY 3
|
|
#define DEFAULT_LOOP_FOREVER 0
|
|
#define DEFAULT_IGNORE_LOOP 0
|
|
|
|
#define FADE_SECONDS_INI_ENTRY "fade_seconds"
|
|
#define FADE_DELAY_SECONDS_INI_ENTRY "fade_delay"
|
|
#define LOOP_COUNT_INI_ENTRY "loop_count"
|
|
#define THREAD_PRIORITY_INI_ENTRY "thread_priority"
|
|
#define LOOP_FOREVER_INI_ENTRY "loop_forever"
|
|
#define IGNORE_LOOP_INI_ENTRY "ignore_loop"
|
|
|
|
char *priority_strings[] = {"Idle","Lowest","Below Normal","Normal","Above Normal","Highest (not recommended)","Time Critical (not recommended)"};
|
|
int priority_values[] = {THREAD_PRIORITY_IDLE,THREAD_PRIORITY_LOWEST,THREAD_PRIORITY_BELOW_NORMAL,THREAD_PRIORITY_NORMAL,THREAD_PRIORITY_ABOVE_NORMAL,THREAD_PRIORITY_HIGHEST,THREAD_PRIORITY_TIME_CRITICAL};
|
|
|
|
/* ************************************* */
|
|
|
|
/* plugin main (declared at the bottom of this file) */
|
|
In_Module input_module;
|
|
DWORD WINAPI __stdcall decode(void *arg);
|
|
|
|
/* Winamp Play extension list, needed to accept/play and associate extensions in Windows */
|
|
#define EXTENSION_LIST_SIZE VGM_EXTENSION_LIST_CHAR_SIZE * 6
|
|
#define EXT_BUFFER_SIZE 200
|
|
char working_extension_list[EXTENSION_LIST_SIZE] = {0};
|
|
|
|
/* plugin config */
|
|
double fade_seconds;
|
|
double fade_delay_seconds;
|
|
double loop_count;
|
|
int thread_priority;
|
|
int loop_forever;
|
|
int ignore_loop;
|
|
|
|
/* plugin state */
|
|
VGMSTREAM * vgmstream = NULL;
|
|
HANDLE decode_thread_handle = INVALID_HANDLE_VALUE;
|
|
short sample_buffer[576*2*2]; /* 576 16-bit samples, stereo, possibly doubled in size for DSP */
|
|
|
|
int paused = 0;
|
|
int decode_abort = 0;
|
|
int seek_needed_samples = -1;
|
|
int decode_pos_ms = 0;
|
|
int decode_pos_samples = 0;
|
|
int stream_length_samples = 0;
|
|
int fade_samples = 0;
|
|
|
|
in_char lastfn[PATH_LIMIT] = {0}; /* name of the currently playing file */
|
|
|
|
/* ************************************* */
|
|
//todo safe ops
|
|
|
|
#ifdef UNICODE_INPUT_PLUGIN
|
|
#define wa_strcpy wcscpy
|
|
#define wa_strncpy wcsncpy
|
|
#define wa_strlen wcslen
|
|
#else
|
|
#define wa_strcpy strcpy
|
|
#define wa_strncpy strncpy
|
|
#define wa_strlen strlen
|
|
#endif
|
|
|
|
/* converts from utf16 to utf8 (if unicode is active) */
|
|
static void wa_wchar_to_char(char *dst, size_t dstsize, const in_char *wsrc) {
|
|
#ifdef UNICODE_INPUT_PLUGIN
|
|
/* converto to UTF8 codepage, default separate bytes, source wstr, wstr lenght, */
|
|
//int size_needed = WideCharToMultiByte(CP_UTF8,0, src,-1, NULL,0, NULL, NULL);
|
|
WideCharToMultiByte(CP_UTF8,0, wsrc,-1, dst,dstsize, NULL, NULL);
|
|
#else
|
|
strcpy(dst,wsrc);
|
|
#endif
|
|
}
|
|
|
|
/* converts from utf8 to utf16 (if unicode is active) */
|
|
static void wa_char_to_wchar(in_char *wdst, size_t wdstsize, const char *src) {
|
|
#ifdef UNICODE_INPUT_PLUGIN
|
|
//int size_needed = MultiByteToWideChar(CP_UTF8,0, src,-1, NULL,0);
|
|
MultiByteToWideChar(CP_UTF8,0, src,-1, wdst,wdstsize);
|
|
#else
|
|
strcpy(wdst,src);
|
|
#endif
|
|
}
|
|
|
|
/* opens a utf16 (unicode) path */
|
|
static FILE* wa_fopen(const in_char *wpath) {
|
|
#ifdef UNICODE_INPUT_PLUGIN
|
|
return _wfopen(wpath,L"rb");
|
|
#else
|
|
return fopen(wpath,"rb");
|
|
#endif
|
|
}
|
|
|
|
/* dupes a utf16 (unicode) file */
|
|
static FILE* wa_fdopen(int fd) {
|
|
#ifdef UNICODE_INPUT_PLUGIN
|
|
return _wfdopen(fd,L"rb");
|
|
#else
|
|
return fdopen(fd,"rb");
|
|
#endif
|
|
}
|
|
|
|
|
|
|
|
/* a STREAMFILE that operates via STDIOSTREAMFILE but handles Winamp's unicode (in_char) paths */
|
|
typedef struct _WINAMP_STREAMFILE {
|
|
STREAMFILE sf;
|
|
STREAMFILE *stdiosf;
|
|
FILE *infile_ref; /* pointer to the infile in stdiosf */
|
|
} WINAMP_STREAMFILE;
|
|
|
|
static STREAMFILE *open_winamp_streamfile_by_file(FILE *infile, const char * path);
|
|
static STREAMFILE *open_winamp_streamfile_by_wpath(const in_char *wpath);
|
|
|
|
static size_t wasf_read(WINAMP_STREAMFILE *streamfile, uint8_t *dest, off_t offset, size_t length) {
|
|
return streamfile->stdiosf->read(streamfile->stdiosf,dest,offset,length);
|
|
}
|
|
|
|
static off_t wasf_get_size(WINAMP_STREAMFILE *streamfile) {
|
|
return streamfile->stdiosf->get_size(streamfile->stdiosf);
|
|
}
|
|
|
|
static off_t wasf_get_offset(WINAMP_STREAMFILE *streamfile) {
|
|
return streamfile->stdiosf->get_offset(streamfile->stdiosf);
|
|
}
|
|
|
|
static void wasf_get_name(WINAMP_STREAMFILE *streamfile, char *buffer, size_t length) {
|
|
return streamfile->stdiosf->get_name(streamfile->stdiosf, buffer, length);
|
|
}
|
|
|
|
static void wasf_get_realname(WINAMP_STREAMFILE *streamfile, char *buffer, size_t length) {
|
|
return streamfile->stdiosf->get_realname(streamfile->stdiosf, buffer, length);
|
|
}
|
|
|
|
static STREAMFILE *wasf_open(WINAMP_STREAMFILE *streamFile, const char *const filename, size_t buffersize) {
|
|
int newfd;
|
|
FILE *newfile;
|
|
STREAMFILE *newstreamFile;
|
|
in_char wpath[PATH_LIMIT];
|
|
char name[PATH_LIMIT];
|
|
|
|
if (!filename)
|
|
return NULL;
|
|
|
|
/* if same name, duplicate the file pointer we already have open */ //unsure if all this is needed
|
|
streamFile->stdiosf->get_name(streamFile->stdiosf, name, PATH_LIMIT);
|
|
if (!strcmp(name,filename)) {
|
|
if (((newfd = dup(fileno(streamFile->infile_ref))) >= 0) &&
|
|
(newfile = wa_fdopen(newfd)))
|
|
{
|
|
newstreamFile = open_winamp_streamfile_by_file(newfile,filename);
|
|
if (newstreamFile) {
|
|
return newstreamFile;
|
|
}
|
|
// failure, close it and try the default path (which will probably fail a second time)
|
|
fclose(newfile);
|
|
}
|
|
}
|
|
|
|
/* STREAMFILEs carry char/UTF8 names, convert to wchar for Winamp */
|
|
wa_char_to_wchar(wpath,PATH_LIMIT, filename);
|
|
return open_winamp_streamfile_by_wpath(wpath);
|
|
}
|
|
|
|
static void wasf_close(WINAMP_STREAMFILE *streamfile) {
|
|
/* closes infile_ref + frees in the internal STDIOSTREAMFILE (fclose for wchar is not needed) */
|
|
streamfile->stdiosf->close(streamfile->stdiosf);
|
|
free(streamfile); /* and the current struct */
|
|
}
|
|
|
|
static STREAMFILE *open_winamp_streamfile_by_file(FILE *infile, const char * path) {
|
|
WINAMP_STREAMFILE *streamfile = NULL;
|
|
STREAMFILE *stdiosf = NULL;
|
|
|
|
streamfile = calloc(1,sizeof(WINAMP_STREAMFILE));
|
|
if (!streamfile) goto fail;
|
|
|
|
stdiosf = open_stdio_streamfile_by_file(infile,path);
|
|
if (!stdiosf) goto fail;
|
|
|
|
streamfile->sf.read = (void*)wasf_read;
|
|
streamfile->sf.get_size = (void*)wasf_get_size;
|
|
streamfile->sf.get_offset = (void*)wasf_get_offset;
|
|
streamfile->sf.get_name = (void*)wasf_get_name;
|
|
streamfile->sf.get_realname = (void*)wasf_get_realname;
|
|
streamfile->sf.open = (void*)wasf_open;
|
|
streamfile->sf.close = (void*)wasf_close;
|
|
|
|
streamfile->stdiosf = stdiosf;
|
|
streamfile->infile_ref = infile;
|
|
|
|
return &streamfile->sf; /* pointer to STREAMFILE start = rest of the custom data follows */
|
|
|
|
fail:
|
|
close_streamfile(stdiosf);
|
|
free(streamfile);
|
|
return NULL;
|
|
}
|
|
|
|
|
|
static STREAMFILE *open_winamp_streamfile_by_wpath(const in_char *wpath) {
|
|
FILE *infile = NULL;
|
|
STREAMFILE *streamFile;
|
|
char path[PATH_LIMIT];
|
|
|
|
/* open a FILE from a Winamp (possibly UTF-16) path */
|
|
infile = wa_fopen(wpath);
|
|
if (!infile) return NULL;
|
|
|
|
/* convert to UTF-8 if needed for internal use */
|
|
wa_wchar_to_char(path,PATH_LIMIT, wpath);
|
|
|
|
streamFile = open_winamp_streamfile_by_file(infile,path);
|
|
if (!streamFile) {
|
|
fclose(infile);
|
|
}
|
|
|
|
return streamFile;
|
|
}
|
|
|
|
/* opens vgmstream for winamp */
|
|
static VGMSTREAM* init_vgmstream_winamp(const in_char *fn) {
|
|
VGMSTREAM * vgmstream = NULL;
|
|
|
|
//return init_vgmstream(fn);
|
|
|
|
/* manually init streamfile to pass the stream index */
|
|
STREAMFILE *streamFile = open_winamp_streamfile_by_wpath(fn); //open_stdio_streamfile(fn);
|
|
if (streamFile) {
|
|
vgmstream = init_vgmstream_from_STREAMFILE(streamFile);
|
|
close_streamfile(streamFile);
|
|
}
|
|
|
|
return vgmstream;
|
|
}
|
|
|
|
/* ************************************* */
|
|
|
|
/* Winamp INI reader */
|
|
static void GetINIFileName(char * iniFile) {
|
|
/* if we're running on a newer winamp version that better supports
|
|
* saving of settings to a per-user directory, use that directory - if not
|
|
* then just revert to the old behaviour */
|
|
|
|
if(IsWindow(input_module.hMainWindow) && SendMessage(input_module.hMainWindow, WM_WA_IPC,0,IPC_GETVERSION) >= 0x5000) {
|
|
char * iniDir = (char *)SendMessage(input_module.hMainWindow, WM_WA_IPC, 0, IPC_GETINIDIRECTORY);
|
|
strncpy(iniFile, iniDir, PATH_LIMIT);
|
|
|
|
strncat(iniFile, "\\Plugins\\", PATH_LIMIT);
|
|
/* can't be certain that \Plugins already exists in the user dir */
|
|
CreateDirectory(iniFile,NULL);
|
|
strncat(iniFile, CONFIG_INI_NAME, PATH_LIMIT);
|
|
}
|
|
else {
|
|
char * lastSlash;
|
|
|
|
GetModuleFileName(NULL, iniFile, PATH_LIMIT);
|
|
lastSlash = strrchr(iniFile, '\\');
|
|
|
|
*(lastSlash + 1) = 0;
|
|
strncat(iniFile, "Plugins\\" CONFIG_INI_NAME,PATH_LIMIT);
|
|
}
|
|
}
|
|
|
|
/* Adds ext to Winamp's extension list */
|
|
static void add_extension(int length, char * dst, const char * ext) {
|
|
char buf[EXT_BUFFER_SIZE];
|
|
char ext_upp[EXT_BUFFER_SIZE];
|
|
int ext_len, written;
|
|
int i,j;
|
|
if (length <= 1)
|
|
return;
|
|
|
|
ext_len = strlen(ext);
|
|
|
|
/* find end of dst (double \0), saved in i */
|
|
for (i=0; i<length-2 && (dst[i] || dst[i+1]); i++)
|
|
;
|
|
|
|
/* check if end reached or not enough room to add */
|
|
if (i == length-2 || i + EXT_BUFFER_SIZE+2 > length-2 || ext_len * 3 + 20+2 > EXT_BUFFER_SIZE) {
|
|
dst[i]='\0';
|
|
dst[i+1]='\0';
|
|
return;
|
|
}
|
|
|
|
if (i > 0)
|
|
i++;
|
|
|
|
/* uppercase ext */
|
|
for (j=0; j < ext_len; j++)
|
|
ext_upp[j] = toupper(ext[j]);
|
|
ext_upp[j] = '\0';
|
|
|
|
/* copy new extension + double null terminate */
|
|
written = sprintf(buf, "%s%c%s Audio File (*.%s)%c", ext,'\0',ext_upp,ext_upp,'\0'); /*ex: "vgmstream\0vgmstream Audio File (*.VGMSTREAM)\0" */
|
|
for (j=0; j < written; i++,j++)
|
|
dst[i] = buf[j];
|
|
dst[i]='\0';
|
|
dst[i+1]='\0';
|
|
}
|
|
|
|
/* Creates Winamp's extension list, a single string that ends with \0\0.
|
|
* Each extension must be in this format: "extension\0Description\0" */
|
|
static void build_extension_list() {
|
|
const char ** ext_list;
|
|
int ext_list_len;
|
|
int i;
|
|
|
|
working_extension_list[0]='\0';
|
|
working_extension_list[1]='\0';
|
|
|
|
ext_list = vgmstream_get_formats();
|
|
ext_list_len = vgmstream_get_formats_length();
|
|
|
|
for (i=0; i < ext_list_len; i++) {
|
|
add_extension(EXTENSION_LIST_SIZE, working_extension_list, ext_list[i]);
|
|
}
|
|
}
|
|
|
|
/* unicode utils */
|
|
static void copy_title(in_char * dst, int dst_size, const in_char * src) {
|
|
in_char *p = (in_char*)src + wa_strlen(src); /* find end */
|
|
while (*p != '\\' && p >= src) /* and find last "\" */
|
|
p--;
|
|
p++;
|
|
wa_strcpy(dst,p); /* copy filename only */
|
|
}
|
|
|
|
/* ***************************************** */
|
|
|
|
/* about dialog */
|
|
void winamp_About(HWND hwndParent) {
|
|
MessageBox(hwndParent,
|
|
PLUGIN_DESCRIPTION "\n"
|
|
"by hcs, FastElbja, manakoAT, bxaimc, snakemeat, soneek, kode54, bnnm and many others\n"
|
|
"\n"
|
|
"Winamp plugin by hcs, others\n"
|
|
"\n"
|
|
"https://github.com/kode54/vgmstream/\n"
|
|
"https://sourceforge.net/projects/vgmstream/ (original)"
|
|
,"about in_vgmstream",MB_OK);
|
|
}
|
|
|
|
/* called at program init */
|
|
void winamp_Init() {
|
|
char iniFile[PATH_LIMIT];
|
|
char buf[256];
|
|
int consumed;
|
|
|
|
|
|
GetINIFileName(iniFile);
|
|
|
|
thread_priority = GetPrivateProfileInt(CONFIG_APP_NAME,THREAD_PRIORITY_INI_ENTRY,DEFAULT_THREAD_PRIORITY,iniFile);
|
|
if (thread_priority < 0 || thread_priority > 6) {
|
|
sprintf(buf,"%d",DEFAULT_THREAD_PRIORITY);
|
|
WritePrivateProfileString(CONFIG_APP_NAME,THREAD_PRIORITY_INI_ENTRY,buf,iniFile);
|
|
thread_priority = DEFAULT_THREAD_PRIORITY;
|
|
}
|
|
|
|
GetPrivateProfileString(CONFIG_APP_NAME,FADE_SECONDS_INI_ENTRY,DEFAULT_FADE_SECONDS,buf,sizeof(buf),iniFile);
|
|
if (sscanf(buf,"%lf%n",&fade_seconds,&consumed)<1 || consumed!=strlen(buf) || fade_seconds < 0) {
|
|
WritePrivateProfileString(CONFIG_APP_NAME,FADE_SECONDS_INI_ENTRY,DEFAULT_FADE_SECONDS,iniFile);
|
|
sscanf(DEFAULT_FADE_SECONDS,"%lf",&fade_seconds);
|
|
}
|
|
|
|
GetPrivateProfileString(CONFIG_APP_NAME,FADE_DELAY_SECONDS_INI_ENTRY,DEFAULT_FADE_DELAY_SECONDS,buf,sizeof(buf),iniFile);
|
|
if (sscanf(buf,"%lf%n",&fade_delay_seconds,&consumed)<1 || consumed!=strlen(buf)) {
|
|
WritePrivateProfileString(CONFIG_APP_NAME,FADE_DELAY_SECONDS_INI_ENTRY,DEFAULT_FADE_DELAY_SECONDS,iniFile);
|
|
sscanf(DEFAULT_FADE_DELAY_SECONDS,"%lf",&fade_delay_seconds);
|
|
}
|
|
|
|
GetPrivateProfileString(CONFIG_APP_NAME,LOOP_COUNT_INI_ENTRY,DEFAULT_LOOP_COUNT,buf,sizeof(buf),iniFile);
|
|
if (sscanf(buf,"%lf%n",&loop_count,&consumed)!=1 || consumed!=strlen(buf) || loop_count < 0) {
|
|
WritePrivateProfileString(CONFIG_APP_NAME,LOOP_COUNT_INI_ENTRY,DEFAULT_LOOP_COUNT,iniFile);
|
|
sscanf(DEFAULT_LOOP_COUNT,"%lf",&loop_count);
|
|
}
|
|
|
|
loop_forever = GetPrivateProfileInt(CONFIG_APP_NAME,LOOP_FOREVER_INI_ENTRY,DEFAULT_LOOP_FOREVER,iniFile);
|
|
ignore_loop = GetPrivateProfileInt(CONFIG_APP_NAME,IGNORE_LOOP_INI_ENTRY,DEFAULT_IGNORE_LOOP,iniFile);
|
|
if (loop_forever && ignore_loop) {
|
|
sprintf(buf,"%d",DEFAULT_LOOP_FOREVER);
|
|
WritePrivateProfileString(CONFIG_APP_NAME,LOOP_FOREVER_INI_ENTRY,buf,iniFile);
|
|
loop_forever = DEFAULT_LOOP_FOREVER;
|
|
|
|
sprintf(buf,"%d",DEFAULT_IGNORE_LOOP);
|
|
WritePrivateProfileString(CONFIG_APP_NAME,IGNORE_LOOP_INI_ENTRY,buf,iniFile);
|
|
ignore_loop = DEFAULT_IGNORE_LOOP;
|
|
}
|
|
|
|
/* dynamically make a list of supported extensions */
|
|
build_extension_list();
|
|
}
|
|
|
|
/* called at program quit */
|
|
void winamp_Quit() {
|
|
}
|
|
|
|
/* called before extension checks, to allow detection of mms://, etc */
|
|
int winamp_IsOurFile(const in_char *fn) {
|
|
return 0; /* we don't recognize protocols */
|
|
}
|
|
|
|
/* request to start playing a file */
|
|
int winamp_Play(const in_char *fn) {
|
|
int max_latency;
|
|
|
|
if (vgmstream)
|
|
return 1; // TODO: this should either pop up an error box or close the file
|
|
|
|
/* open the stream */
|
|
vgmstream = init_vgmstream_winamp(fn);
|
|
if (!vgmstream)
|
|
return 1;
|
|
|
|
/* config */
|
|
if (ignore_loop)
|
|
vgmstream->loop_flag = 0;
|
|
|
|
/* save original name */
|
|
wa_strncpy(lastfn,fn,PATH_LIMIT);
|
|
|
|
/* open the output plugin */
|
|
max_latency = input_module.outMod->Open(vgmstream->sample_rate,vgmstream->channels, 16, 0, 0);
|
|
if (max_latency < 0) {
|
|
close_vgmstream(vgmstream);
|
|
vgmstream = NULL;
|
|
return 1;
|
|
}
|
|
|
|
/* set info display */ //TODO: actual bitrate
|
|
input_module.SetInfo(get_vgmstream_average_bitrate(vgmstream)/1000,vgmstream->sample_rate/1000,vgmstream->channels,1);
|
|
|
|
/* setup visualization */
|
|
input_module.SAVSAInit(max_latency,vgmstream->sample_rate);
|
|
input_module.VSASetInfo(vgmstream->sample_rate,vgmstream->channels);
|
|
|
|
/* reset internals */
|
|
decode_abort = 0;
|
|
seek_needed_samples = -1;
|
|
decode_pos_ms = 0;
|
|
decode_pos_samples = 0;
|
|
paused = 0;
|
|
stream_length_samples = get_vgmstream_play_samples(loop_count,fade_seconds,fade_delay_seconds,vgmstream);
|
|
fade_samples = (int)(fade_seconds * vgmstream->sample_rate);
|
|
|
|
/* start */
|
|
decode_thread_handle = CreateThread(
|
|
NULL, /* handle cannot be inherited */
|
|
0, /* stack size, 0=default */
|
|
decode, /* thread start routine */
|
|
NULL, /* no parameter to start routine */
|
|
0, /* run thread immediately */
|
|
NULL); /* don't keep track of the thread id */
|
|
|
|
SetThreadPriority(decode_thread_handle,priority_values[thread_priority]);
|
|
|
|
return 0; /* success */
|
|
}
|
|
|
|
/* pause stream */
|
|
void winamp_Pause() {
|
|
paused = 1;
|
|
input_module.outMod->Pause(1);
|
|
}
|
|
|
|
/* unpause stream */
|
|
void winamp_UnPause() {
|
|
paused = 0;
|
|
input_module.outMod->Pause(0);
|
|
}
|
|
|
|
/* return 1 if paused, 0 if not */
|
|
int winamp_IsPaused() {
|
|
return paused;
|
|
}
|
|
|
|
/* stop (unload) stream */
|
|
void winamp_Stop() {
|
|
if (decode_thread_handle != INVALID_HANDLE_VALUE) {
|
|
decode_abort = 1;
|
|
|
|
/* arbitrary wait length */
|
|
if (WaitForSingleObject(decode_thread_handle,1000) == WAIT_TIMEOUT) {
|
|
TerminateThread(decode_thread_handle,0); // TODO: error?
|
|
}
|
|
CloseHandle(decode_thread_handle);
|
|
decode_thread_handle = INVALID_HANDLE_VALUE;
|
|
}
|
|
|
|
|
|
close_vgmstream(vgmstream);
|
|
vgmstream = NULL;
|
|
|
|
input_module.outMod->Close();
|
|
input_module.SAVSADeInit();
|
|
}
|
|
|
|
/* get length in ms */
|
|
int winamp_GetLength() {
|
|
return stream_length_samples * 1000LL / vgmstream->sample_rate;
|
|
}
|
|
|
|
/* current output time in ms */
|
|
int winamp_GetOutputTime() {
|
|
return decode_pos_ms + (input_module.outMod->GetOutputTime()-input_module.outMod->GetWrittenTime());
|
|
}
|
|
|
|
/* seeks to point in stream (in ms) */
|
|
void winamp_SetOutputTime(int time_in_ms) {
|
|
if (vgmstream)
|
|
seek_needed_samples = (long long)time_in_ms * vgmstream->sample_rate / 1000LL;
|
|
}
|
|
|
|
/* pass these commands through */
|
|
void winamp_SetVolume(int volume) {
|
|
input_module.outMod->SetVolume(volume);
|
|
}
|
|
void winamp_SetPan(int pan) {
|
|
input_module.outMod->SetPan(pan);
|
|
}
|
|
|
|
/* display info box (ALT+3) */
|
|
int winamp_InfoBox(const in_char *fn, HWND hwnd) {
|
|
char description[1024] = {0};
|
|
|
|
|
|
concatn(sizeof(description),description,PLUGIN_DESCRIPTION "\n\n");
|
|
|
|
if (!fn || !*fn) {
|
|
/* no filename = current playing file */
|
|
if (!vgmstream)
|
|
return 0;
|
|
|
|
describe_vgmstream(vgmstream,description,sizeof(description));
|
|
}
|
|
else {
|
|
/* some other file in playlist given by filename */
|
|
VGMSTREAM * infostream = NULL;
|
|
|
|
infostream = init_vgmstream_winamp(fn);
|
|
if (!infostream)
|
|
return 0;
|
|
|
|
describe_vgmstream(infostream,description,sizeof(description));
|
|
|
|
close_vgmstream(infostream);
|
|
infostream = NULL;
|
|
}
|
|
|
|
MessageBox(hwnd,description,"Stream info",MB_OK);
|
|
return 0;
|
|
}
|
|
|
|
/* retrieve title (playlist name) and time on the current or other file in the playlist */
|
|
void winamp_GetFileInfo(const in_char *fn, in_char *title, int *length_in_ms) {
|
|
|
|
if (!fn || !*fn) {
|
|
/* no filename = current playing file */
|
|
|
|
if (!vgmstream)
|
|
return;
|
|
|
|
if (title) {
|
|
copy_title(title,GETFILEINFO_TITLE_LENGTH, lastfn);
|
|
}
|
|
|
|
if (length_in_ms) {
|
|
*length_in_ms = winamp_GetLength();
|
|
}
|
|
}
|
|
else {
|
|
/* some other file in playlist given by filename */
|
|
VGMSTREAM * infostream = NULL;
|
|
|
|
infostream = init_vgmstream_winamp(fn);
|
|
|
|
if (title) {
|
|
copy_title(title,GETFILEINFO_TITLE_LENGTH, fn);
|
|
}
|
|
|
|
if (length_in_ms) {
|
|
*length_in_ms = -1000;
|
|
if (infostream) {
|
|
int num_samples = get_vgmstream_play_samples(loop_count,fade_seconds,fade_delay_seconds,infostream);
|
|
*length_in_ms = num_samples * 1000LL /infostream->sample_rate;
|
|
}
|
|
}
|
|
|
|
close_vgmstream(infostream);
|
|
infostream = NULL;
|
|
}
|
|
}
|
|
|
|
/* eq stuff */
|
|
void winamp_EQSet(int on, char data[10], int preamp) {
|
|
}
|
|
|
|
/* the decode thread */
|
|
DWORD WINAPI __stdcall decode(void *arg) {
|
|
/* channel count shouldn't change during decode */
|
|
int max_buffer_samples = sizeof(sample_buffer)/sizeof(sample_buffer[0])/2/vgmstream->channels;
|
|
|
|
while (!decode_abort) {
|
|
int samples_to_do;
|
|
int l;
|
|
|
|
if (decode_pos_samples + max_buffer_samples > stream_length_samples
|
|
&& (!loop_forever || !vgmstream->loop_flag))
|
|
samples_to_do = stream_length_samples - decode_pos_samples;
|
|
else
|
|
samples_to_do = max_buffer_samples;
|
|
|
|
/* play 'till the end of this seek, or note if we're done seeking */
|
|
if (seek_needed_samples != -1) {
|
|
/* reset if we need to seek backwards */
|
|
if (seek_needed_samples < decode_pos_samples) {
|
|
reset_vgmstream(vgmstream);
|
|
|
|
if (ignore_loop)
|
|
vgmstream->loop_flag = 0;
|
|
|
|
decode_pos_samples = 0;
|
|
decode_pos_ms = 0;
|
|
}
|
|
|
|
if (decode_pos_samples < seek_needed_samples) {
|
|
samples_to_do = seek_needed_samples-decode_pos_samples;
|
|
if (samples_to_do > max_buffer_samples)
|
|
samples_to_do = max_buffer_samples;
|
|
} else
|
|
seek_needed_samples = -1;
|
|
|
|
input_module.outMod->Flush((int)decode_pos_ms);
|
|
}
|
|
|
|
l = (samples_to_do*vgmstream->channels*2) << (input_module.dsp_isactive()?1:0);
|
|
|
|
if (samples_to_do == 0) {
|
|
input_module.outMod->CanWrite(); /* ? */
|
|
if (!input_module.outMod->IsPlaying()) {
|
|
PostMessage(input_module.hMainWindow, WM_WA_MPEG_EOF, 0,0); /* end */
|
|
return 0;
|
|
}
|
|
Sleep(10);
|
|
}
|
|
else if (seek_needed_samples != -1) {
|
|
render_vgmstream(sample_buffer,samples_to_do,vgmstream);
|
|
|
|
decode_pos_samples += samples_to_do;
|
|
decode_pos_ms = decode_pos_samples * 1000LL / vgmstream->sample_rate;
|
|
}
|
|
else if (input_module.outMod->CanWrite() >= l) {
|
|
/* let vgmstream do its thing */
|
|
render_vgmstream(sample_buffer,samples_to_do,vgmstream);
|
|
|
|
/* fade! */
|
|
if (vgmstream->loop_flag && fade_samples > 0 && !loop_forever) {
|
|
int samples_into_fade = decode_pos_samples - (stream_length_samples - fade_samples);
|
|
if (samples_into_fade + samples_to_do > 0) {
|
|
int j,k;
|
|
for (j=0; j < samples_to_do; j++, samples_into_fade++) {
|
|
if (samples_into_fade > 0) {
|
|
double fadedness = (double)(fade_samples-samples_into_fade)/fade_samples;
|
|
for (k=0; k < vgmstream->channels; k++) {
|
|
sample_buffer[j*vgmstream->channels+k] =
|
|
(short)(sample_buffer[j*vgmstream->channels+k]*fadedness);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
input_module.SAAddPCMData((char*)sample_buffer,vgmstream->channels,16,decode_pos_ms);
|
|
input_module.VSAAddPCMData((char*)sample_buffer,vgmstream->channels,16,decode_pos_ms);
|
|
decode_pos_samples += samples_to_do;
|
|
decode_pos_ms = decode_pos_samples*1000LL/vgmstream->sample_rate;
|
|
if (input_module.dsp_isactive())
|
|
l = input_module.dsp_dosamples(sample_buffer,samples_to_do,16,vgmstream->channels,vgmstream->sample_rate) * 2 * vgmstream->channels;
|
|
|
|
input_module.outMod->Write((char*)sample_buffer,l);
|
|
} /* if we can write enough */
|
|
else {
|
|
Sleep(20);
|
|
}
|
|
} /* main loop */
|
|
|
|
return 0;
|
|
}
|
|
|
|
/* config dialog handler */
|
|
INT_PTR CALLBACK configDlgProc(HWND hDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) {
|
|
char buf[256];
|
|
char iniFile[PATH_LIMIT];
|
|
static int mypri;
|
|
HANDLE hSlider;
|
|
|
|
switch (uMsg) {
|
|
case WM_CLOSE:
|
|
EndDialog(hDlg,TRUE);
|
|
return TRUE;
|
|
case WM_INITDIALOG:
|
|
GetINIFileName(iniFile);
|
|
|
|
/* set CPU Priority slider */
|
|
hSlider=GetDlgItem(hDlg,IDC_THREAD_PRIORITY_SLIDER);
|
|
SendMessage(hSlider, TBM_SETRANGE,
|
|
(WPARAM) TRUE, /* redraw flag */
|
|
(LPARAM) MAKELONG(1, 7)); /* min. & max. positions */
|
|
SendMessage(hSlider, TBM_SETPOS,
|
|
(WPARAM) TRUE, /* redraw flag */
|
|
(LPARAM) thread_priority+1);
|
|
mypri=thread_priority;
|
|
SetDlgItemText(hDlg,IDC_THREAD_PRIORITY_TEXT,priority_strings[thread_priority]);
|
|
|
|
sprintf(buf,"%.2lf",fade_seconds);
|
|
SetDlgItemText(hDlg,IDC_FADE_SECONDS,buf);
|
|
sprintf(buf,"%.2lf",fade_delay_seconds);
|
|
SetDlgItemText(hDlg,IDC_FADE_DELAY_SECONDS,buf);
|
|
sprintf(buf,"%.2lf",loop_count);
|
|
SetDlgItemText(hDlg,IDC_LOOP_COUNT,buf);
|
|
|
|
if (loop_forever)
|
|
CheckDlgButton(hDlg,IDC_LOOP_FOREVER,BST_CHECKED);
|
|
else if (ignore_loop)
|
|
CheckDlgButton(hDlg,IDC_IGNORE_LOOP,BST_CHECKED);
|
|
else
|
|
CheckDlgButton(hDlg,IDC_LOOP_NORMALLY,BST_CHECKED);
|
|
|
|
break;
|
|
case WM_COMMAND:
|
|
switch (GET_WM_COMMAND_ID(wParam, lParam)) {
|
|
case IDOK:
|
|
{
|
|
double temp_fade_seconds;
|
|
double temp_fade_delay_seconds;
|
|
double temp_loop_count;
|
|
int consumed;
|
|
|
|
/* read and verify */
|
|
GetDlgItemText(hDlg,IDC_FADE_SECONDS,buf,sizeof(buf));
|
|
if (sscanf(buf,"%lf%n",&temp_fade_seconds,&consumed)<1
|
|
|| consumed!=strlen(buf) ||
|
|
temp_fade_seconds<0) {
|
|
MessageBox(hDlg,
|
|
"Invalid value for Fade Length\n"
|
|
"Must be a number greater than or equal to zero",
|
|
"Error",MB_OK|MB_ICONERROR);
|
|
break;
|
|
}
|
|
|
|
GetDlgItemText(hDlg,IDC_FADE_DELAY_SECONDS,buf,sizeof(buf));
|
|
if (sscanf(buf,"%lf%n",&temp_fade_delay_seconds,
|
|
&consumed)<1 || consumed!=strlen(buf)) {
|
|
MessageBox(hDlg,
|
|
"Invalid valid for Fade Delay\n"
|
|
"Must be a number",
|
|
"Error",MB_OK|MB_ICONERROR);
|
|
break;
|
|
}
|
|
|
|
GetDlgItemText(hDlg,IDC_LOOP_COUNT,buf,sizeof(buf));
|
|
if (sscanf(buf,"%lf%n",&temp_loop_count,&consumed)<1 ||
|
|
consumed!=strlen(buf) ||
|
|
temp_loop_count<0) {
|
|
MessageBox(hDlg,
|
|
"Invalid value for Loop Count\n"
|
|
"Must be a number greater than or equal to zero",
|
|
"Error",MB_OK|MB_ICONERROR);
|
|
break;
|
|
}
|
|
|
|
GetINIFileName(iniFile);
|
|
|
|
thread_priority=mypri;
|
|
sprintf(buf,"%d",thread_priority);
|
|
WritePrivateProfileString(CONFIG_APP_NAME,THREAD_PRIORITY_INI_ENTRY,buf,iniFile);
|
|
|
|
fade_seconds = temp_fade_seconds;
|
|
sprintf(buf,"%.2lf",fade_seconds);
|
|
WritePrivateProfileString(CONFIG_APP_NAME,FADE_SECONDS_INI_ENTRY,buf,iniFile);
|
|
|
|
fade_delay_seconds = temp_fade_delay_seconds;
|
|
sprintf(buf,"%.2lf",fade_delay_seconds);
|
|
WritePrivateProfileString(CONFIG_APP_NAME,FADE_DELAY_SECONDS_INI_ENTRY,buf,iniFile);
|
|
|
|
loop_count = temp_loop_count;
|
|
sprintf(buf,"%.2lf",loop_count);
|
|
WritePrivateProfileString(CONFIG_APP_NAME,LOOP_COUNT_INI_ENTRY,buf,iniFile);
|
|
|
|
loop_forever = (IsDlgButtonChecked(hDlg,IDC_LOOP_FOREVER) == BST_CHECKED);
|
|
sprintf(buf,"%d",loop_forever);
|
|
WritePrivateProfileString(CONFIG_APP_NAME,LOOP_FOREVER_INI_ENTRY,buf,iniFile);
|
|
|
|
ignore_loop = (IsDlgButtonChecked(hDlg,IDC_IGNORE_LOOP) == BST_CHECKED);
|
|
sprintf(buf,"%d",ignore_loop);
|
|
WritePrivateProfileString(CONFIG_APP_NAME,IGNORE_LOOP_INI_ENTRY,buf,iniFile);
|
|
}
|
|
|
|
EndDialog(hDlg,TRUE);
|
|
break;
|
|
case IDCANCEL:
|
|
EndDialog(hDlg,TRUE);
|
|
break;
|
|
case IDC_DEFAULT_BUTTON:
|
|
/* set CPU Priority slider */
|
|
hSlider=GetDlgItem(hDlg,IDC_THREAD_PRIORITY_SLIDER);
|
|
SendMessage(hSlider, TBM_SETRANGE,
|
|
(WPARAM) TRUE, /* redraw flag */
|
|
(LPARAM) MAKELONG(1, 7)); /* min. & max. positions */
|
|
SendMessage(hSlider, TBM_SETPOS,
|
|
(WPARAM) TRUE, /* redraw flag */
|
|
(LPARAM) DEFAULT_THREAD_PRIORITY+1);
|
|
mypri=DEFAULT_THREAD_PRIORITY;
|
|
SetDlgItemText(hDlg,IDC_THREAD_PRIORITY_TEXT,priority_strings[mypri]);
|
|
|
|
SetDlgItemText(hDlg,IDC_FADE_SECONDS,DEFAULT_FADE_SECONDS);
|
|
SetDlgItemText(hDlg,IDC_FADE_DELAY_SECONDS,DEFAULT_FADE_DELAY_SECONDS);
|
|
SetDlgItemText(hDlg,IDC_LOOP_COUNT,DEFAULT_LOOP_COUNT);
|
|
|
|
CheckDlgButton(hDlg,IDC_LOOP_FOREVER,BST_UNCHECKED);
|
|
CheckDlgButton(hDlg,IDC_IGNORE_LOOP,BST_UNCHECKED);
|
|
CheckDlgButton(hDlg,IDC_LOOP_NORMALLY,BST_CHECKED);
|
|
break;
|
|
default:
|
|
return FALSE;
|
|
}
|
|
case WM_HSCROLL:
|
|
if ((struct HWND__ *)lParam==GetDlgItem(hDlg,IDC_THREAD_PRIORITY_SLIDER)) {
|
|
if (LOWORD(wParam)==TB_THUMBPOSITION || LOWORD(wParam)==TB_THUMBTRACK) mypri=HIWORD(wParam)-1;
|
|
else mypri=SendMessage(GetDlgItem(hDlg,IDC_THREAD_PRIORITY_SLIDER),TBM_GETPOS,0,0)-1;
|
|
SetDlgItemText(hDlg,IDC_THREAD_PRIORITY_TEXT,priority_strings[mypri]);
|
|
}
|
|
break;
|
|
default:
|
|
return FALSE;
|
|
}
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/* configuration dialog */
|
|
void winamp_Config(HWND hwndParent) {
|
|
/* defined in resource.rc */
|
|
DialogBox(input_module.hDllInstance, (const char *)IDD_CONFIG, hwndParent, configDlgProc);
|
|
}
|
|
|
|
/* *********************************** */
|
|
|
|
/* main plugin def */
|
|
In_Module input_module = {
|
|
IN_VER,
|
|
PLUGIN_DESCRIPTION,
|
|
0, /* hMainWindow (filled in by Winamp) */
|
|
0, /* hDllInstance (filled in by Winamp) */
|
|
working_extension_list,
|
|
1, /* is_seekable flag */
|
|
1, /* UsesOutputPlug flag */
|
|
winamp_Config,
|
|
winamp_About,
|
|
winamp_Init,
|
|
winamp_Quit,
|
|
winamp_GetFileInfo,
|
|
winamp_InfoBox,
|
|
winamp_IsOurFile,
|
|
winamp_Play,
|
|
winamp_Pause,
|
|
winamp_UnPause,
|
|
winamp_IsPaused,
|
|
winamp_Stop,
|
|
winamp_GetLength,
|
|
winamp_GetOutputTime,
|
|
winamp_SetOutputTime,
|
|
winamp_SetVolume,
|
|
winamp_SetPan,
|
|
0,0,0,0,0,0,0,0,0, /* vis stuff */
|
|
0,0, /* dsp stuff */
|
|
winamp_EQSet,
|
|
NULL, /* SetInfo */
|
|
0 /* outMod */
|
|
};
|
|
|
|
__declspec( dllexport ) In_Module * winampGetInModule2() {
|
|
return &input_module;
|
|
}
|