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