Mercurial > foo_out_sdl
diff foosdk/sdk/foobar2000/foo_sample/playback_stream_capture.cpp @ 1:20d02a178406 default tip
*: check in everything else
yay
| author | Paper <paper@tflc.us> |
|---|---|
| date | Mon, 05 Jan 2026 02:15:46 -0500 |
| parents | |
| children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/foosdk/sdk/foobar2000/foo_sample/playback_stream_capture.cpp Mon Jan 05 02:15:46 2026 -0500 @@ -0,0 +1,149 @@ +#include "stdafx.h" +#include <helpers/writer_wav.h> +#include <SDK/playback_stream_capture.h> + +namespace { + // private classes in anon namespace + + typedef CWavWriter wav_writer; + typedef wavWriterSetup_t wav_writer_setup; + + static pfc::string8 g_outputPath; + static wav_writer g_wav_writer; + + class playback_stream_capture_callback_impl : public playback_stream_capture_callback { + public: + void on_chunk(const audio_chunk & chunk) override { + PFC_ASSERT(core_api::is_main_thread()); + + try { + // writing files in main thread is not pretty, but good enough for our demo + + auto & abort = fb2k::noAbort; + + if (g_wav_writer.is_open() && g_wav_writer.get_spec() != chunk.get_spec()) { + g_wav_writer.finalize(abort); + } + if (!g_wav_writer.is_open() && ! core_api::is_shutting_down() ) { + wav_writer_setup setup; setup.initialize(chunk, 16, false, false); + + pfc::string_formatter fn; + fn << "capture-" << pfc::print_guid(pfc::createGUID()) << ".wav"; + pfc::string_formatter path = g_outputPath; + path.add_filename( fn ); // pretty method to add file path components with auto inserted delimiter + g_wav_writer.open(path, setup, abort); + } + g_wav_writer.write(chunk, abort); + } catch(std::exception const & e) { + FB2K_console_formatter() << "Playback stream capture error: " << e; + // FIX ME handle this in a pretty manner, likely inaccessible output folder or out of disk space + } + } + }; + static playback_stream_capture_callback_impl g_callback; + static bool g_active = false; + + static void FlushCapture() { + if (g_active) { + g_wav_writer.finalize(fb2k::noAbort); + } + } + static void StopCapture() { + if ( g_active ) { + g_active = false; + playback_stream_capture::get()->remove_callback(&g_callback); + g_wav_writer.finalize(fb2k::noAbort); + } + } + static void StartCapture() { + PFC_ASSERT( g_outputPath.length() > 0 ); + if (!g_active && !core_api::is_shutting_down()) { + g_active = true; + playback_stream_capture::get()->add_callback(&g_callback); + } + } + + // Forcibly stop capture when fb2k is shutting down + class initquit_psc : public initquit { + public: + void on_quit() override { + PFC_ASSERT( core_api::is_shutting_down() ); + StopCapture(); + } + }; + + // Handle playback stop events to split output WAV files + class play_callback_psc : public play_callback_static { + public: + unsigned get_flags() override { + return flag_on_playback_stop; + } + void on_playback_stop(play_control::t_stop_reason p_reason) override { + // Terminate the current stream + FlushCapture(); + } + void on_playback_starting(play_control::t_track_command p_command,bool p_paused) override {} + void on_playback_new_track(metadb_handle_ptr p_track) override {} + void on_playback_seek(double p_time) override {} + void on_playback_pause(bool p_state) override {} + void on_playback_edited(metadb_handle_ptr p_track) override {} + void on_playback_dynamic_info(const file_info & p_info) override {} + void on_playback_dynamic_info_track(const file_info & p_info) override {} + void on_playback_time(double p_time) override {} + void on_volume_change(float p_new_val) override {} + }; + + // pretty modern macro for service_factory_single_t<> + FB2K_SERVICE_FACTORY( initquit_psc ); + FB2K_SERVICE_FACTORY( play_callback_psc ); +} + +static void startCaptureDialogReply( fb2k::arrayRef location ) { + if ( g_active ) return; // already capturing + if ( location.is_empty() ) return; + if ( location->size() != 1 ) return; + auto obj = location->itemAt(0); + fsItemFolderPtr folder; + fb2k::stringRef strFolder; + if ( strFolder &= obj ) { + // OK + } else if ( folder &= obj ) { + strFolder = folder->canonicalPath(); + } + if ( strFolder.is_valid() ) { + g_outputPath = strFolder->c_str(); + StartCapture(); + } +} +void ToggleCapture() { +#ifdef _WIN32 + // Block modal dialog recursions. + // Folder picker below is a modal dialog, don't ever call it if there's another modal dialog in progress. + // Also prevents this function from recursing into itself if someone manages to hit the menu item while already picking folder. + // This will bump whatever modal dialog already exists, so the user has some idea why this was refused. + if ( !ModalDialogPrologue() ) return; + + if (g_active) { + StopCapture(); + } else { + const HWND wndParent = core_api::get_main_window(); + modal_dialog_scope scope(wndParent); // we can't have a handle to the modal dialog, but parent handle is good enough + if (uBrowseForFolder(wndParent, "Choose output folder", g_outputPath)) { + StartCapture(); + } + } +#else + if ( g_active ) { + StopCapture(); + } else { + auto dlg = fb2k::fileDialog::get()->setupOpenFolder(); + dlg->setTitle( "Choose output folder" ); + dlg->run( fb2k::fileDialogNotify::create( startCaptureDialogReply ) ); + } + +#endif +} + +bool IsCaptureRunning() { + return g_active; +}
