aboutsummaryrefslogtreecommitdiff
path: root/Src/Plugins/Input/in_flac/DecodeThread.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'Src/Plugins/Input/in_flac/DecodeThread.cpp')
-rw-r--r--Src/Plugins/Input/in_flac/DecodeThread.cpp719
1 files changed, 719 insertions, 0 deletions
diff --git a/Src/Plugins/Input/in_flac/DecodeThread.cpp b/Src/Plugins/Input/in_flac/DecodeThread.cpp
new file mode 100644
index 00000000..38c7fcb6
--- /dev/null
+++ b/Src/Plugins/Input/in_flac/DecodeThread.cpp
@@ -0,0 +1,719 @@
+/*
+** Copyright (C) 2007-2011 Nullsoft, Inc.
+**
+** This software is provided 'as-is', without any express or implied warranty. In no event will the authors be held
+** liable for any damages arising from the use of this software.
+**
+** Permission is granted to anyone to use this software for any purpose, including commercial applications, and to
+** alter it and redistribute it freely, subject to the following restrictions:
+**
+** 1. The origin of this software must not be misrepresented; you must not claim that you wrote the original software.
+** If you use this software in a product, an acknowledgment in the product documentation would be appreciated but is not required.
+**
+** 2. Altered source versions must be plainly marked as such, and must not be misrepresented as being the original software.
+**
+** 3. This notice may not be removed or altered from any source distribution.
+**
+** Author: Ben Allison benski@winamp.com
+** Created: March 1, 2007
+**
+*/
+#ifndef WIN32_LEAN_AND_MEAN
+#define WIN32_LEAN_AND_MEAN
+#endif
+#include "main.h"
+#include <windows.h>
+#include <math.h>
+#include <assert.h>
+#include <locale.h>
+#include <FLAC/all.h>
+#include "StreamFileWin32.h"
+#include "../Winamp/wa_ipc.h"
+#include "QuickBuf.h"
+#include "api__in_flv.h"
+#include "../nu/AudioOutput.h"
+#include "../Agave/Language/api_language.h"
+#include "FLACFileCallbacks.h"
+#include "nx/nxpath.h"
+
+int m_force_seek = -1; // el hacko grande
+const DWORD PAUSE_TIMEOUT = 100; // number of milliseconds to sleep for when paused
+
+static bool paused = false;
+
+// could probably move this inside client_data
+int realbits;
+int bps;
+int samplerate;
+int channels;
+volatile int currentSongLength=-1000;
+int averageBitrate;
+
+// if input plugins weren't single-instance only, we could put this in thread-local-storage
+static FLAC__StreamDecoder *decoder = 0;
+static uint64_t fileSize = 0;
+static FLAC__uint64 decodePosition = 0;
+
+double gain = 1.0;
+static double buffer_scale=1.0;
+
+class FLACWait
+{
+public:
+ int WaitOrAbort(int time_in_ms)
+ {
+ if (WaitForSingleObject(killswitch, time_in_ms) == WAIT_OBJECT_0)
+ return 1;
+ return 0;
+ }
+};
+
+
+volatile int bufferCount=0;
+static void Buffering(int bufStatus, const wchar_t *displayString)
+{
+ if (bufStatus < 0 || bufStatus > 100)
+ return;
+
+ char tempdata[75*2] = {0, };
+
+ int csa = plugin.SAGetMode();
+ if (csa & 1)
+ {
+ for (int x = 0; x < bufStatus*75 / 100; x ++)
+ tempdata[x] = x * 16 / 75;
+ }
+ else if (csa&2)
+ {
+ int offs = (csa & 1) ? 75 : 0;
+ int x = 0;
+ while (x < bufStatus*75 / 100)
+ {
+ tempdata[offs + x++] = -6 + x * 14 / 75;
+ }
+ while (x < 75)
+ {
+ tempdata[offs + x++] = 0;
+ }
+ }
+ else if (csa == 4)
+ {
+ tempdata[0] = tempdata[1] = (bufStatus * 127 / 100);
+ }
+ if (csa) plugin.SAAdd(tempdata, ++bufferCount, (csa == 3) ? 0x80000003 : csa);
+
+ /*
+ TODO
+ wchar_t temp[64] = {0};
+ StringCchPrintf(temp, 64, L"%s: %d%%",displayString, bufStatus);
+ SetStatus(temp);
+ */
+ //SetVideoStatusText(temp); // TODO: find a way to set the old status back
+ // videoOutput->notifyBufferState(static_cast<int>(bufStatus*2.55f));
+}
+
+static nu::AudioOutput<FLACWait> audio_output(&plugin);
+
+#pragma region Truncaters
+inline static void clip(double &x, double a, double b)
+{
+ double x1 = fabs(x - a);
+ double x2 = fabs(x - b);
+ x = x1 + (a + b);
+ x -= x2;
+ x *= 0.5;
+}
+
+static void InterleaveAndTruncate32(const FLAC__int32 *const buffer[], void *_output, int bps, int channels, int blocksize, double gain)
+{
+ // TODO: this can be sped up significantly
+ FLAC__int32 *output = (FLAC__int32 *)_output;
+
+ for (int b = 0;b < blocksize;b++)
+ {
+ for (int c = 0;c < channels;c++)
+ {
+ double appliedGain = gain * (double)buffer[c][b];
+ // TODO: add dither
+ clip(appliedGain, -2147483648., 2147483647.);
+ *output = (FLAC__int32)appliedGain;
+ output++;
+ }
+ }
+}
+
+static void InterleaveAndTruncate24(const FLAC__int32 *const buffer[], void *_output, int bps, int channels, int blocksize, double gain)
+{
+ char *output = (char *)_output;
+
+ for (int b = 0;b < blocksize;b++)
+ {
+ for (int c = 0;c < channels;c++)
+ {
+ double appliedGain = gain * (double)buffer[c][b];
+ // TODO: add dither
+ clip(appliedGain, -8388608., 8388607.);
+ FLAC__int32 sample = (FLAC__int32)appliedGain;
+
+ // little endian specific code
+ output[0] = (unsigned char)(sample);
+ output[1] = (unsigned char)(sample >> 8);
+ output[2] = (unsigned char)(sample >> 16);
+ output += 3;
+ }
+ }
+}
+
+static void InterleaveAndTruncate16(const FLAC__int32 *const buffer[], void *_output, int bps, int channels, int blocksize, double gain)
+{
+ short *output = (short *)_output;
+
+ for (int b = 0;b < blocksize;b++)
+ {
+ for (int c = 0;c < channels;c++)
+ {
+ double appliedGain = gain * (double)buffer[c][b];
+ // TODO: add dither
+ clip(appliedGain, -32768., 32767.);
+ FLAC__int32 sample = (FLAC__int32)appliedGain;
+
+ *output = (short) sample ;
+ output ++;
+ }
+ }
+}
+
+static void InterleaveAndTruncate8(const FLAC__int32 *const buffer[], void *_output, int bps, int channels, int blocksize, double gain)
+{
+ unsigned char *output = (unsigned char *)_output;
+
+ for (int b = 0;b < blocksize;b++)
+ {
+ for (int c = 0;c < channels;c++)
+ {
+ double appliedGain = gain * (double)buffer[c][b];
+ // TODO: add dither
+ clip(appliedGain, -128., 127.);
+ FLAC__int32 sample = (FLAC__int32)appliedGain;
+
+ *output = (unsigned char)(sample + 128);
+ output++;
+ }
+ }
+}
+
+/* --- Versions without gain adjustment --- */
+
+static void InterleaveAndTruncate32(const FLAC__int32 *const buffer[], void *_output, int bps, int channels, int blocksize)
+{
+ FLAC__int32 *output = (FLAC__int32 *)_output;
+
+ for (int b = 0;b < blocksize;b++)
+ {
+ for (int c = 0;c < channels;c++)
+ {
+ *output = buffer[c][b];
+ output++;
+ }
+ }
+}
+
+static void InterleaveAndTruncate24(const FLAC__int32 *const buffer[], void *_output, int bps, int channels, int blocksize)
+{
+ char *output = (char *)_output;
+
+ for (int b = 0;b < blocksize;b++)
+ {
+ for (int c = 0;c < channels;c++)
+ {
+ FLAC__int32 sample = buffer[c][b];
+
+ // little endian specific code
+ output[0] = (unsigned char)(sample);
+ output[1] = (unsigned char)(sample >> 8);
+ output[2] = (unsigned char)(sample >> 16);
+ output += 3;
+ }
+ }
+}
+
+static void InterleaveAndTruncate16(const FLAC__int32 *const buffer[], void *_output, int bps, int channels, int blocksize)
+{
+ short *output = (short *)_output;
+
+ for (int b = 0;b < blocksize;b++)
+ {
+ for (int c = 0;c < channels;c++)
+ {
+ *output = (short) buffer[c][b];
+ output ++;
+ }
+ }
+}
+
+static void InterleaveAndTruncate8(const FLAC__int32 *const buffer[], void *_output, int bps, int channels, int blocksize)
+{
+ unsigned char *output = (unsigned char *)_output;
+
+ for (int b = 0;b < blocksize;b++)
+ {
+ for (int c = 0;c < channels;c++)
+ {
+ *output = (unsigned char)(buffer[c][b] + 128);
+ output++;
+ }
+ }
+}
+
+void InterleaveAndTruncate(const FLAC__int32 *const buffer[], void *_output, int bps, int channels, int blocksize, double gain)
+{
+ if (gain == 1.0) // if it's EXACTLY 1.0, i.e. from a hardcoded value. not meant to be a "RG happens to be 1.0" check
+ {
+ switch(bps)
+ {
+ case 8:
+ InterleaveAndTruncate8(buffer, _output, bps, channels, blocksize);
+ break;
+ case 16:
+ InterleaveAndTruncate16(buffer, _output, bps, channels, blocksize);
+ break;
+ case 24:
+ InterleaveAndTruncate24(buffer, _output, bps, channels, blocksize);
+ break;
+ case 32:
+ InterleaveAndTruncate32(buffer, _output, bps, channels, blocksize);
+ break;
+ }
+ }
+ else // apply replay gain
+ {
+ switch(bps)
+ {
+ case 8:
+ InterleaveAndTruncate8(buffer, _output, bps, channels, blocksize, gain);
+ break;
+ case 16:
+ InterleaveAndTruncate16(buffer, _output, bps, channels, blocksize, gain);
+ break;
+ case 24:
+ InterleaveAndTruncate24(buffer, _output, bps, channels, blocksize, gain);
+ break;
+ case 32:
+ InterleaveAndTruncate32(buffer, _output, bps, channels, blocksize, gain);
+ break;
+ }
+ }
+}
+#pragma endregion
+
+QuickBuf output;
+FLAC__uint64 lastoutputtime;
+static FLAC__StreamDecoderWriteStatus OnAudio(const FLAC__StreamDecoder *decoder, const FLAC__Frame *frame, const FLAC__int32 *const buffer[], void *client_data)
+{
+ // TODO: if frame bps/samplerate/channels doesn't equal our own, we'll probably have to close & re-open the audio output
+ // mux buffer into interleaved samples
+ FLAC__uint64 newPosition;
+ FLAC__stream_decoder_get_decode_position(decoder, &newPosition);
+ FLAC__uint64 delta = newPosition - decodePosition;
+ decodePosition = newPosition;
+ if (!config_average_bitrate)
+ plugin.SetInfo((int)(delta / (125.*frame->header.blocksize / samplerate)), -1, -1, -1);
+ else if (fixBitrate)
+ {
+ fixBitrate = false;
+ plugin.SetInfo(averageBitrate, -1, -1, -1);
+ }
+
+ if (frame->header.number_type == FLAC__FRAME_NUMBER_TYPE_SAMPLE_NUMBER)
+ lastoutputtime = 1000ULL*frame->header.number.sample_number / (FLAC__uint64)samplerate;
+ else
+ lastoutputtime = 0;
+
+ int byteLength = (bps / 8) * channels * frame->header.blocksize;
+ output.Reserve(byteLength*2);
+ InterleaveAndTruncate(buffer, output, bps, channels, frame->header.blocksize, gain);
+
+ audio_output.Write(output, byteLength);
+ return FLAC__STREAM_DECODER_WRITE_STATUS_CONTINUE;
+}
+
+static int GetBits(int incoming)
+{
+ int bits = AGAVE_API_CONFIG->GetUnsigned(playbackConfigGroupGUID, L"bits", 16);
+ if (bits > 16 && AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"replaygain", false))
+ return bits;
+
+ return min(bits, incoming);
+}
+
+static double GetGain(const FLAC__StreamMetadata *metadata)
+{
+ if (AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"replaygain", false))
+ {
+ if (metadata)
+ {
+ int gainPos = -1, peakPos = -1;
+ switch (AGAVE_API_CONFIG->GetUnsigned(playbackConfigGroupGUID, L"replaygain_source", 0))
+ {
+ case 0: // track
+ gainPos = FLAC__metadata_object_vorbiscomment_find_entry_from(metadata, 0, "REPLAYGAIN_TRACK_GAIN");
+ if (gainPos < 0 && !AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"replaygain_preferred_only", false))
+ gainPos = FLAC__metadata_object_vorbiscomment_find_entry_from(metadata, 0, "REPLAYGAIN_ALBUM_GAIN");
+
+ peakPos = FLAC__metadata_object_vorbiscomment_find_entry_from(metadata, 0, "REPLAYGAIN_TRACK_PEAK");
+ if (peakPos < 0 && !AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"replaygain_preferred_only", false))
+ peakPos = FLAC__metadata_object_vorbiscomment_find_entry_from(metadata, 0, "REPLAYGAIN_ALBUM_PEAK");
+
+ break;
+ case 1:
+ gainPos = FLAC__metadata_object_vorbiscomment_find_entry_from(metadata, 0, "REPLAYGAIN_ALBUM_GAIN");
+ if (gainPos < 0 && !AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"replaygain_preferred_only", false))
+ gainPos = FLAC__metadata_object_vorbiscomment_find_entry_from(metadata, 0, "REPLAYGAIN_TRACK_GAIN");
+
+ peakPos = FLAC__metadata_object_vorbiscomment_find_entry_from(metadata, 0, "REPLAYGAIN_ALBUM_PEAK");
+ if (peakPos < 0 && !AGAVE_API_CONFIG->GetBool(playbackConfigGroupGUID, L"replaygain_preferred_only", false))
+ peakPos = FLAC__metadata_object_vorbiscomment_find_entry_from(metadata, 0, "REPLAYGAIN_TRACK_PEAK");
+ break;
+ }
+
+ double dB = 0, peak = 1.0;
+ _locale_t C_locale = WASABI_API_LNG->Get_C_NumericLocale();
+ if (gainPos >= 0)
+ {
+ const char *entry = (const char *)metadata->data.vorbis_comment.comments[gainPos].entry;
+ const char *value = strchr(entry, '='); // find the first equal
+ if (value++)
+ {
+ if (value[0] == '+')
+ dB = _atof_l(&value[1], C_locale);
+ else
+ dB = _atof_l(value, C_locale);
+ }
+ }
+ else
+ {
+ dB = AGAVE_API_CONFIG->GetFloat(playbackConfigGroupGUID, L"non_replaygain", -6.0);
+ return pow(10.0, dB / 20.0);
+ }
+
+ if (peakPos >= 0)
+ {
+ const char *entry = (const char *)metadata->data.vorbis_comment.comments[peakPos].entry;
+ const char *value = strchr(entry, '='); // find the first equal
+ if (value++)
+ {
+ peak = _atof_l(value, C_locale);
+ }
+ }
+
+ switch (AGAVE_API_CONFIG->GetUnsigned(playbackConfigGroupGUID, L"replaygain_mode", 1))
+ {
+ case 0: // apply gain
+ return pow(10.0, dB / 20.0);
+ break;
+ case 1: // apply gain, but don't clip
+ return min(pow(10.0, dB / 20.0), 1.0 / peak);
+ break;
+ case 2: // normalize
+ return 1.0 / peak;
+ break;
+ case 3: // prevent clipping
+ if (peak > 1.0)
+ return 1.0 / peak;
+ else
+ return 1.0;
+ }
+ }
+ else
+ {
+ double dB = AGAVE_API_CONFIG->GetFloat(playbackConfigGroupGUID, L"non_replaygain", -6.0);
+ return pow(10.0, dB / 20.0);
+ }
+ }
+
+ return 1.0;
+}
+
+static void OnMetadata(const FLAC__StreamDecoder *decoder, const FLAC__StreamMetadata *metadata, void *client_data)
+{
+ switch (metadata->type)
+ {
+ case FLAC__METADATA_TYPE_STREAMINFO:
+ {
+ realbits = metadata->data.stream_info.bits_per_sample;
+ channels = metadata->data.stream_info.channels;
+ samplerate = metadata->data.stream_info.sample_rate;
+ bps = GetBits(metadata->data.stream_info.bits_per_sample);
+ gain = GetGain(0) * pow(2., (double)(bps - realbits));
+ if (metadata->data.stream_info.total_samples)
+ {
+ currentSongLength = (int)((metadata->data.stream_info.total_samples*1000ULL)/(uint64_t)samplerate);
+ averageBitrate = (int)(fileSize / (125 * metadata->data.stream_info.total_samples / (__int64)samplerate));
+ }
+ else
+ {
+ currentSongLength=-1000;
+ averageBitrate=0;
+ }
+ }
+ break;
+ case FLAC__METADATA_TYPE_VORBIS_COMMENT:
+ gain = GetGain(metadata) * pow(2., (double)(bps - realbits));
+ break;
+ }
+}
+
+static void OnError(const FLAC__StreamDecoder *decoder, FLAC__StreamDecoderErrorStatus status, void *client_data)
+{
+ //client_data = client_data; // dummy line so i can set a breakpoint
+}
+
+void CALLBACK APCPause(ULONG_PTR data)
+{
+ paused = !!data;
+ plugin.outMod->Pause(!!paused);
+}
+
+void CALLBACK APCSeek(ULONG_PTR data)
+{
+ // TODO: break out of end-of-file handling if necessary
+ buffer_scale=1.0;
+ int time_in_ms = (int)data;
+ lastoutputtime=time_in_ms; // cheating a bit here :)
+ audio_output.Flush(time_in_ms);
+ __int64 frames = Int32x32To64(time_in_ms, samplerate) / 1000;
+ FLAC__StreamDecoderState state = FLAC__stream_decoder_get_state(decoder);
+ plugin.outMod->Pause(0);
+ FLAC__stream_decoder_seek_absolute(decoder, frames);
+ plugin.outMod->Pause(paused);
+ if (FLAC__stream_decoder_get_state(decoder) == FLAC__STREAM_DECODER_SEEK_ERROR)
+ {
+ FLAC__stream_decoder_flush(decoder);
+ }
+}
+
+static const double prebuffer_seconds = 0.5;
+static bool DoBuffering(nx_file_t file)
+{
+ // TODO: check for disconnect, etc.
+
+ // anything less than half-a-second and we'll rebuffer. but when we rebuffer, we'll get 2 seconds worth of audio
+ // also, every time we're forced to re-buffer, we'll double the buffer amount
+
+ uint64_t required;
+ if (averageBitrate > 0)
+ {
+ required = (uint64_t)((double)averageBitrate * 1000.0 * buffer_scale * prebuffer_seconds / 8.0);
+ }
+ else /* no bitrate specified, let's assume 1000kbps */
+ {
+ required = (uint64_t)(1000.0 * 1000.0 * buffer_scale * prebuffer_seconds / 8.0);
+ }
+
+ uint64_t available;
+ if (NXFileProgressiveDownloaderAvailable(file, required, &available) == NErr_True)
+ return true;
+
+ Buffering(0, 0);
+ bufferCount=lastoutputtime;
+ required *= 4;
+ buffer_scale *= 2.0;
+
+ while (NXFileProgressiveDownloaderAvailable(file, required, &available) == NErr_False)
+ {
+ int percent = (int)((double)available * 100.0 / (double)required);
+ if (percent <= 0)
+ percent=1;
+ if (percent > 99)
+ percent=99;
+
+ Buffering(percent, 0);
+ if (WaitForSingleObject(killswitch, 10) == WAIT_OBJECT_0)
+ {
+ return false;
+ }
+ }
+ Buffering(100, 0);
+ bufferCount=0;
+ return true;
+}
+
+extern HANDLE threadStarted;
+DWORD CALLBACK FLACThread(LPVOID param)
+{
+ buffer_scale=1.0;
+ bool streaming=false;
+ nx_file_t file;
+ FLACClientData state;
+ audio_output.Init(plugin.outMod);
+ paused=false;
+ SetEvent(threadStarted);
+ nx_uri_t filename = (nx_uri_t)param;
+ decodePosition = 0;
+ gain = 1.0;
+ bufferCount = 0;
+ decoder = FLAC__stream_decoder_new();
+ if (decoder == 0)
+ {
+ if (WaitForSingleObject(killswitch, 200) != WAIT_OBJECT_0)
+ PostMessage(plugin.hMainWindow, WM_WA_MPEG_EOF, 0, 0);
+
+ free(filename);
+ return 0;
+ }
+
+ FLAC__stream_decoder_set_md5_checking(decoder, false);
+ FLAC__stream_decoder_set_metadata_respond(decoder, FLAC__METADATA_TYPE_VORBIS_COMMENT);
+
+ plugin.is_seekable = 1;
+
+
+ int ret;
+
+ if (NXPathIsURL(filename) == NErr_True)
+ {
+ // Display a window to request to download
+ //
+ MessageBox(NULL, L"Cannot stream flac file, please download it", NULL, 0);
+ /*
+ char agent[256] = { 0 };
+ snprintf(agent, 256, "User-Agent: %S/%S", "Winamp", "5.9.1");
+ ret = NXFileOpenProgressiveDownloader(&file, filename, nx_file_FILE_read_binary, 0, agent); // TODO: calculate real user agent
+ */
+ return NErr_Disabled;
+ }
+ else
+ {
+ ret = NXFileOpenFile(&file, filename, nx_file_FILE_read_binary);
+ }
+
+ if (ret != NErr_Success)
+ {
+ FLAC__stream_decoder_delete(decoder);
+ if (WaitForSingleObject(killswitch, 200) != WAIT_OBJECT_0)
+ PostMessage(plugin.hMainWindow, WM_WA_MPEG_EOF, 0, 0);
+
+ NXURIRelease(filename);
+ return 0;
+ }
+
+ state.SetFile(file);
+
+ NXFileLength(file, &fileSize);
+
+ if (FLAC__stream_decoder_init_stream(
+ decoder,
+ FLAC_NXFile_Read,
+ FLAC_NXFile_Seek,
+ FLAC_NXFile_Tell,
+ FLAC_NXFile_Length,
+ FLAC_NXFile_EOF,
+ OnAudio,
+ OnMetadata, // or NULL
+ OnError,
+ &state
+ ) != FLAC__STREAM_DECODER_INIT_STATUS_OK)
+ {
+ FLAC__stream_decoder_delete(decoder);
+ NXFileRelease(file);
+ if (WaitForSingleObject(killswitch, 200) != WAIT_OBJECT_0)
+ PostMessage(plugin.hMainWindow, WM_WA_MPEG_EOF, 0, 0);
+
+ NXURIRelease(filename);
+ return 0;
+ }
+
+ FLAC__stream_decoder_process_until_end_of_metadata(decoder);
+
+
+
+ if (!audio_output.Open(0, channels, samplerate, bps))
+ {
+ FLAC__stream_decoder_finish(decoder);
+ FLAC__stream_decoder_delete(decoder);
+ NXFileRelease(file);
+ if (WaitForSingleObject(killswitch, 200) != WAIT_OBJECT_0)
+ PostMessage(plugin.hMainWindow, WM_WA_MPEG_EOF, 0, 0);
+
+ NXURIRelease(filename);
+ return 0;
+ }
+
+ plugin.SetInfo(averageBitrate, -1, -1, -1);
+ plugin.outMod->SetVolume(volume);
+ plugin.outMod->SetPan(pan);
+
+ if (streaming && !DoBuffering(file))
+ {
+ FLAC__stream_decoder_finish(decoder);
+ FLAC__stream_decoder_delete(decoder);
+ NXFileRelease(file);
+ NXURIRelease(filename);
+
+ return 0;
+ }
+
+ if (m_force_seek != -1)
+ APCSeek((ULONG_PTR)m_force_seek);
+ m_force_seek = -1;
+
+ HANDLE events[] = {killswitch};
+ DWORD timeout = 0;
+ while (true)
+ {
+ DWORD result = WaitForMultipleObjectsEx(sizeof(events) / sizeof(*events), events, FALSE, timeout, TRUE);
+ switch (result)
+ {
+ case WAIT_OBJECT_0:// kill thread
+ FLAC__stream_decoder_finish(decoder);
+ FLAC__stream_decoder_delete(decoder);
+ NXFileRelease(file);
+ NXURIRelease(filename);
+ return 0;
+
+ case WAIT_TIMEOUT:
+ timeout = paused ? PAUSE_TIMEOUT : 0;
+ if (streaming && !DoBuffering(file))
+ {
+ FLAC__stream_decoder_finish(decoder);
+ FLAC__stream_decoder_delete(decoder);
+ NXFileRelease(file);
+ NXURIRelease(filename);
+
+ return 0;
+ }
+
+ if (!paused)
+ {
+ FLAC__bool decode_successful = FLAC__stream_decoder_process_single(decoder);
+
+ FLAC__StreamDecoderState FLACstate = FLAC__stream_decoder_get_state(decoder);
+
+ if (FLACstate == FLAC__STREAM_DECODER_END_OF_STREAM)
+ {
+ audio_output.Write(0, 0);
+ if (audio_output.WaitWhilePlaying())
+ {
+ FLAC__stream_decoder_finish(decoder);
+ FLAC__stream_decoder_delete(decoder);
+ NXFileRelease(file);
+ NXURIRelease(filename);
+ return 0;
+ }
+ PostMessage(plugin.hMainWindow, WM_WA_MPEG_EOF, 0, 0);
+ timeout = INFINITE; // sit and wait for killswitch
+ }
+ else if (!decode_successful)
+ {
+ // some other error - abort playback
+ // if we can find FLAC files with errors, we might be able to gracefully handle some errors
+ PostMessage(plugin.hMainWindow, WM_WA_MPEG_EOF, 0, 0);
+ timeout = INFINITE; // sit and wait for killswitch
+ }
+ }
+ break;
+ }
+ }
+
+ return 0;
+}