Mercurial > foo_out_sdl
comparison 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 |
comparison
equal
deleted
inserted
replaced
| 0:e9bb126753e7 | 1:20d02a178406 |
|---|---|
| 1 #include "stdafx.h" | |
| 2 #include <helpers/writer_wav.h> | |
| 3 #include <SDK/playback_stream_capture.h> | |
| 4 | |
| 5 namespace { | |
| 6 // private classes in anon namespace | |
| 7 | |
| 8 typedef CWavWriter wav_writer; | |
| 9 typedef wavWriterSetup_t wav_writer_setup; | |
| 10 | |
| 11 static pfc::string8 g_outputPath; | |
| 12 static wav_writer g_wav_writer; | |
| 13 | |
| 14 class playback_stream_capture_callback_impl : public playback_stream_capture_callback { | |
| 15 public: | |
| 16 void on_chunk(const audio_chunk & chunk) override { | |
| 17 PFC_ASSERT(core_api::is_main_thread()); | |
| 18 | |
| 19 try { | |
| 20 // writing files in main thread is not pretty, but good enough for our demo | |
| 21 | |
| 22 auto & abort = fb2k::noAbort; | |
| 23 | |
| 24 if (g_wav_writer.is_open() && g_wav_writer.get_spec() != chunk.get_spec()) { | |
| 25 g_wav_writer.finalize(abort); | |
| 26 } | |
| 27 if (!g_wav_writer.is_open() && ! core_api::is_shutting_down() ) { | |
| 28 wav_writer_setup setup; setup.initialize(chunk, 16, false, false); | |
| 29 | |
| 30 pfc::string_formatter fn; | |
| 31 fn << "capture-" << pfc::print_guid(pfc::createGUID()) << ".wav"; | |
| 32 pfc::string_formatter path = g_outputPath; | |
| 33 path.add_filename( fn ); // pretty method to add file path components with auto inserted delimiter | |
| 34 g_wav_writer.open(path, setup, abort); | |
| 35 } | |
| 36 g_wav_writer.write(chunk, abort); | |
| 37 } catch(std::exception const & e) { | |
| 38 FB2K_console_formatter() << "Playback stream capture error: " << e; | |
| 39 // FIX ME handle this in a pretty manner, likely inaccessible output folder or out of disk space | |
| 40 } | |
| 41 } | |
| 42 }; | |
| 43 static playback_stream_capture_callback_impl g_callback; | |
| 44 static bool g_active = false; | |
| 45 | |
| 46 static void FlushCapture() { | |
| 47 if (g_active) { | |
| 48 g_wav_writer.finalize(fb2k::noAbort); | |
| 49 } | |
| 50 } | |
| 51 static void StopCapture() { | |
| 52 if ( g_active ) { | |
| 53 g_active = false; | |
| 54 playback_stream_capture::get()->remove_callback(&g_callback); | |
| 55 g_wav_writer.finalize(fb2k::noAbort); | |
| 56 } | |
| 57 } | |
| 58 static void StartCapture() { | |
| 59 PFC_ASSERT( g_outputPath.length() > 0 ); | |
| 60 if (!g_active && !core_api::is_shutting_down()) { | |
| 61 g_active = true; | |
| 62 playback_stream_capture::get()->add_callback(&g_callback); | |
| 63 } | |
| 64 } | |
| 65 | |
| 66 // Forcibly stop capture when fb2k is shutting down | |
| 67 class initquit_psc : public initquit { | |
| 68 public: | |
| 69 void on_quit() override { | |
| 70 PFC_ASSERT( core_api::is_shutting_down() ); | |
| 71 StopCapture(); | |
| 72 } | |
| 73 }; | |
| 74 | |
| 75 // Handle playback stop events to split output WAV files | |
| 76 class play_callback_psc : public play_callback_static { | |
| 77 public: | |
| 78 unsigned get_flags() override { | |
| 79 return flag_on_playback_stop; | |
| 80 } | |
| 81 void on_playback_stop(play_control::t_stop_reason p_reason) override { | |
| 82 // Terminate the current stream | |
| 83 FlushCapture(); | |
| 84 } | |
| 85 void on_playback_starting(play_control::t_track_command p_command,bool p_paused) override {} | |
| 86 void on_playback_new_track(metadb_handle_ptr p_track) override {} | |
| 87 void on_playback_seek(double p_time) override {} | |
| 88 void on_playback_pause(bool p_state) override {} | |
| 89 void on_playback_edited(metadb_handle_ptr p_track) override {} | |
| 90 void on_playback_dynamic_info(const file_info & p_info) override {} | |
| 91 void on_playback_dynamic_info_track(const file_info & p_info) override {} | |
| 92 void on_playback_time(double p_time) override {} | |
| 93 void on_volume_change(float p_new_val) override {} | |
| 94 }; | |
| 95 | |
| 96 // pretty modern macro for service_factory_single_t<> | |
| 97 FB2K_SERVICE_FACTORY( initquit_psc ); | |
| 98 FB2K_SERVICE_FACTORY( play_callback_psc ); | |
| 99 } | |
| 100 | |
| 101 static void startCaptureDialogReply( fb2k::arrayRef location ) { | |
| 102 if ( g_active ) return; // already capturing | |
| 103 if ( location.is_empty() ) return; | |
| 104 if ( location->size() != 1 ) return; | |
| 105 auto obj = location->itemAt(0); | |
| 106 fsItemFolderPtr folder; | |
| 107 fb2k::stringRef strFolder; | |
| 108 if ( strFolder &= obj ) { | |
| 109 // OK | |
| 110 } else if ( folder &= obj ) { | |
| 111 strFolder = folder->canonicalPath(); | |
| 112 } | |
| 113 if ( strFolder.is_valid() ) { | |
| 114 g_outputPath = strFolder->c_str(); | |
| 115 StartCapture(); | |
| 116 } | |
| 117 } | |
| 118 void ToggleCapture() { | |
| 119 #ifdef _WIN32 | |
| 120 // Block modal dialog recursions. | |
| 121 // Folder picker below is a modal dialog, don't ever call it if there's another modal dialog in progress. | |
| 122 // Also prevents this function from recursing into itself if someone manages to hit the menu item while already picking folder. | |
| 123 // This will bump whatever modal dialog already exists, so the user has some idea why this was refused. | |
| 124 if ( !ModalDialogPrologue() ) return; | |
| 125 | |
| 126 if (g_active) { | |
| 127 StopCapture(); | |
| 128 } else { | |
| 129 const HWND wndParent = core_api::get_main_window(); | |
| 130 modal_dialog_scope scope(wndParent); // we can't have a handle to the modal dialog, but parent handle is good enough | |
| 131 if (uBrowseForFolder(wndParent, "Choose output folder", g_outputPath)) { | |
| 132 StartCapture(); | |
| 133 } | |
| 134 } | |
| 135 #else | |
| 136 if ( g_active ) { | |
| 137 StopCapture(); | |
| 138 } else { | |
| 139 auto dlg = fb2k::fileDialog::get()->setupOpenFolder(); | |
| 140 dlg->setTitle( "Choose output folder" ); | |
| 141 dlg->run( fb2k::fileDialogNotify::create( startCaptureDialogReply ) ); | |
| 142 } | |
| 143 | |
| 144 #endif | |
| 145 } | |
| 146 | |
| 147 bool IsCaptureRunning() { | |
| 148 return g_active; | |
| 149 } |
