diff --git a/include/rew/common/directory_sink.h b/include/rew/common/directory_sink.h new file mode 100644 index 0000000000000000000000000000000000000000..0a398e5948530311db4c7473de3f31da28ac4bea --- /dev/null +++ b/include/rew/common/directory_sink.h @@ -0,0 +1,56 @@ +#ifndef REW_COMMON_DIRECTORY_SINK_H +#define REW_COMMON_DIRECTORY_SINK_H + +#include <fstream> +#include "named_raw_file.h" +#include "input.h" + +REW_NAMESPACE { + class DirectorySink: public rew::Input<rew::NamedRawFile> { + public: + DirectorySink(const std::string& destination):destination(destination) { + + } + virtual ~DirectorySink() = default; + + void process(const rew::NamedRawFile* data, const size_t length) override { + for (size_t i = 0; i < length; i++) { + const auto& namedFile = data[i]; + + auto terminator = namedFile.length; + for (size_t i = 0; i < namedFile.length; i++) { + if (namedFile.data[i] == '\0') { + terminator = i; + break; + } + } + + if (terminator != namedFile.length) { + const auto sptr = reinterpret_cast<const char*>(namedFile.data.get()); + const auto name = std::string(sptr, terminator); + + const auto contents = reinterpret_cast<const char*>(namedFile.data.get() + terminator + 1); + const auto total = namedFile.length - terminator - 1; + std::string path = destination + DELIMITER + name; + + std::cout << "Writing file: " << path << " of size: " << total << " bytes!" << std::endl; + std::fstream file(path, std::ios::out | std::ios::binary); + if (!file) { + std::cerr << "Failed to open file: " << path << " for writing!" << std::endl; + } + else { + file.write(contents, total); + } + } + else { + std::cerr << "File index: " << namedFile.index << " successfully inflated but contains bad filename!" << std::endl; + } + } + } + + private: + std::string destination; + }; +} + +#endif diff --git a/report/images/decoder-gui.png b/report/images/decoder-gui.png new file mode 100644 index 0000000000000000000000000000000000000000..a02f2850ecaf2f04a5d70f11b8aeb6f2a5fad90d Binary files /dev/null and b/report/images/decoder-gui.png differ diff --git a/src/rew/decoder-cli/main.cpp b/src/rew/decoder-cli/main.cpp index 44c6e4280473b4c4d391652858414320fc4e24ae..5e6468e805ee6e8287f602902f745fca4d038c3e 100644 --- a/src/rew/decoder-cli/main.cpp +++ b/src/rew/decoder-cli/main.cpp @@ -15,10 +15,11 @@ #include <thread> #include <future> #include <rew/decoder/decoder.h> -#include <rew/common/named_raw_file.h> +#include <rew/common/directory_sink.h> #include <rew/decoder/physical_audio_source.h> #include <rew/decoder/tcp_http_server.h> + ///===================================================================================================================== std::string getFullPath(const std::string& path) { #ifdef _WIN32 @@ -89,53 +90,6 @@ argagg::parser argparser{ { } } }; -///===================================================================================================================== -class CustomSink: public rew::Input<rew::NamedRawFile> { -public: - CustomSink(const std::string& destination):destination(destination) { - - } - virtual ~CustomSink() = default; - - void process(const rew::NamedRawFile* data, const size_t length) override { - for (size_t i = 0; i < length; i++) { - const auto& namedFile = data[i]; - - auto terminator = namedFile.length; - for (size_t i = 0; i < namedFile.length; i++) { - if (namedFile.data[i] == '\0') { - terminator = i; - break; - } - } - - if (terminator != namedFile.length) { - const auto sptr = reinterpret_cast<const char*>(namedFile.data.get()); - const auto name = std::string(sptr, terminator); - - const auto contents = reinterpret_cast<const char*>(namedFile.data.get() + terminator + 1); - const auto total = namedFile.length - terminator - 1; - std::string path = destination + DELIMITER + name; - - std::cout << "Writing file: " << path << " of size: " << total << " bytes!" << std::endl; - std::fstream file(path, std::ios::out | std::ios::binary); - if (!file) { - std::cerr << "Failed to open file: " << path << " for writing!" << std::endl; - } - else { - file.write(contents, total); - } - } - else { - std::cerr << "File index: " << namedFile.index << " successfully inflated but contains bad filename!" << std::endl; - } - } - } - -private: - std::string destination; -}; - ///===================================================================================================================== int main (const int argc, char* argv[]) { static bool terminate = false; @@ -185,7 +139,7 @@ int main (const int argc, char* argv[]) { if (!folderIsValid(outputPath)) throw std::runtime_error("Output path: \"" + outputPath + "\" is not a folder!"); - sink = std::make_shared<CustomSink>(outputPath); + sink = std::make_shared<rew::DirectorySink>(outputPath); } else if (args["serve"]) { diff --git a/src/rew/decoder-gui/main.cpp b/src/rew/decoder-gui/main.cpp index e5f20176be3ceaffb28f7f4ca2c77315cfd477b4..5f92fd2e0262701830d65f1f6412ce29fa4ae82f 100644 --- a/src/rew/decoder-gui/main.cpp +++ b/src/rew/decoder-gui/main.cpp @@ -13,11 +13,11 @@ #include <iostream> #include <thread> #include <future> +#include <functional> #include <rew/decoder/decoder.h> -#include <rew/common/named_raw_file.h> +#include <rew/common/directory_sink.h> #include <rew/decoder/physical_audio_source.h> -#include "rew/encoder/audio_sink.h" -#include "rew/encoder/wav_writer.h" +#include <rew/decoder/tcp_http_server.h> #ifdef _MSC_VER #pragma comment(linker,"/manifestdependency:\"type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"") @@ -127,6 +127,386 @@ private: std::string destination; }; +///===================================================================================================================== +static int onClosing(uiWindow *w, void *data) { + uiQuit(); + return 1; +} + +///===================================================================================================================== +static int onShouldQuit(void *data) { + uiWindow *mainwin = uiWindow(data); + + uiControlDestroy(uiControl(mainwin)); + return 1; +} + +///===================================================================================================================== +class Context; +static struct Globals { + std::shared_ptr<Context> context; + bool sourceDevice = false; + bool sourceFile = false; + bool destinationFile = false; + bool destinationServe = false; + std::string sourceFileStr; + std::string destinationFileStr; +} globals; + +///===================================================================================================================== +class Context: public std::enable_shared_from_this<Context> { +public: + Context(Globals& globals, const std::function<void()>& callback) + : globals(globals), + callback(callback) { + + if (globals.destinationFile) { + std::cout << "destination: " << globals.destinationFileStr << std::endl; + const auto outputPath = getFullPath(globals.destinationFileStr); + + if (!pathIsValid(outputPath)) + throw std::runtime_error("Output path: \"" + outputPath + "\" does not exist!"); + + if (!folderIsValid(outputPath)) + throw std::runtime_error("Output path: \"" + outputPath + "\" is not a folder!"); + + sink = std::make_shared<rew::DirectorySink>(outputPath); + } + + else if (globals.destinationServe) { + std::cout << "Starting server" << std::endl; + server = std::make_shared<rew::TcpHttpServer>("localhost", 8080); + sink = server; + server->start(); + } + + else { + throw std::runtime_error("Please select a destination"); + } + + if (globals.sourceFile) { + std::cout << "source: " << globals.sourceFileStr << std::endl; + const auto inputPath = getFullPath(globals.sourceFileStr); + + if (!pathIsValid(inputPath)) + throw std::runtime_error("Input file: \"" + inputPath + "\" does not exist!"); + + auto wav = std::make_shared<rew::WavReader>(); + auto source = std::make_shared<rew::AudioSource>(wav); + + decoder = std::make_shared<rew::Decoder>(source, sink, DEFAULT_LOW_TONE_FREQ, DEFAULT_HIGH_TONE_FREQ, DEFAULT_SAMPLE_LENGTH_MS); + + thread = std::thread([=]() -> void { + source->open(inputPath); + source->process(); + source->close(); + + if (this->callback && !this->server) { + this->stopped = true; + this->callback(); + } + }); + } + + else if (globals.sourceDevice) { + audio = std::make_shared<rew::PhysicalAudioSource>(); + + decoder = std::make_shared<rew::Decoder>(audio, sink, DEFAULT_LOW_TONE_FREQ, DEFAULT_HIGH_TONE_FREQ, DEFAULT_SAMPLE_LENGTH_MS); + + thread = std::thread([=]() -> void { + audio->start(); + audio->process(); + + if (this->callback && !this->server) { + this->stopped = true; + this->callback(); + } + }); + } + + else { + throw std::runtime_error("Please select a source"); + } + } + + ~Context() { + stop(); + } + + void stop() { + if (thread.joinable()) { + if (server) server->stop(); + if (audio) audio->close(); + thread.join(); + } + } + + inline bool isStopped() const { + return stopped; + } + +private: + Globals& globals; + std::shared_ptr<rew::Input<rew::NamedRawFile>> sink; + std::shared_ptr<rew::PhysicalAudioSource> audio; + std::shared_ptr<rew::TcpHttpServer> server; + std::shared_ptr<rew::Decoder> decoder; + std::thread thread; + std::function<void()> callback; + bool stopped = false; +}; + +///===================================================================================================================== +static void widgetsSelectSource(uiWindow* mainwin, uiBox* box) { + const auto group = uiNewGroup("Select source"); + uiGroupSetMargined(group, 1); + uiBoxAppend(box, uiControl(group), 0); + + uiBoxAppend(box, uiControl(uiNewHorizontalSeparator()), 0); + + const auto cboxDevice = uiNewCheckbox("From audio input device"); + const auto cboxFile = uiNewCheckbox("From audio file"); + + static struct CBoxData { + Globals& globals; + uiCheckbox* device; + uiCheckbox* file; + } cboxData{globals, cboxDevice, cboxFile}; + + uiBoxAppend(box, uiControl(cboxDevice), 0); + uiCheckboxOnToggled(cboxDevice, [](uiCheckbox* cbox, void* data) -> void { + auto& d = *reinterpret_cast<CBoxData*>(data); + d.globals.sourceDevice = uiCheckboxChecked(cbox); + uiCheckboxSetChecked(d.file, !d.globals.sourceDevice); + }, &cboxData); + + const auto combo = uiNewCombobox(); + const auto devices = rew::PhysicalAudioSource::getDevices(); + for (const auto& device : devices) { + if (device.inputChannels == 0) continue; + uiComboboxAppend(combo, device.name.c_str()); + } + uiBoxAppend(box, uiControl(combo), 0); + uiComboboxOnSelected(combo, [](uiCombobox* combo, void* data) -> void { + auto& d = *reinterpret_cast<CBoxData*>(data); + d.globals.sourceDevice = true; + uiCheckboxSetChecked(d.device, d.globals.sourceDevice); + uiCheckboxSetChecked(d.file, !d.globals.sourceDevice); + }, &cboxData); + + uiBoxAppend(box, uiControl(cboxFile), 0); + uiCheckboxOnToggled(cboxFile, [](uiCheckbox* cbox, void* data) -> void { + auto& d = *reinterpret_cast<CBoxData*>(data); + d.globals.sourceFile = uiCheckboxChecked(cbox); + uiCheckboxSetChecked(d.device, !d.globals.sourceFile); + }, &cboxData); + + const auto grid = uiNewGrid(); + uiGridSetPadded(grid, 1); + uiBoxAppend(box, uiControl(grid), 0); + + const auto fileButton = uiNewButton("Select Folder"); + const auto fileEntry = uiNewEntry(); + + static struct SelectFileData { + uiWindow* mainwin; + Globals& globals; + uiCheckbox* device; + uiCheckbox* file; + uiEntry* entry; + } selectFileData{mainwin, globals, cboxDevice, cboxFile, fileEntry}; + + const auto onOpenFileClicked = [](uiButton *b, void *data) -> void { + auto& d = *reinterpret_cast<SelectFileData*>(data); + + const auto filename = uiOpenFile(d.mainwin); + if (filename == NULL) { + uiEntrySetText(d.entry, ""); + return; + } + + if (!pathIsValid(filename)) { + uiMsgBoxError(d.mainwin, + "Select file error", + "You must select a valid file!"); + + uiEntrySetText(d.entry, ""); + return; + } + + uiEntrySetText(d.entry, filename); + d.globals.sourceFileStr = std::string(filename); + uiFreeText(filename); + + d.globals.sourceFile = true; + uiCheckboxSetChecked(d.device, !d.globals.sourceFile); + uiCheckboxSetChecked(d.file, d.globals.sourceFile); + }; + + uiEntryOnChanged(fileEntry, [](uiEntry* e, void* data) -> void { + auto& globals = *reinterpret_cast<Globals*>(data); + globals.sourceFileStr = std::string(uiEntryText(e)); + }, &globals); + + uiEntrySetReadOnly(fileEntry, 0); + uiButtonOnClicked(fileButton, onOpenFileClicked, &selectFileData); + uiGridAppend(grid, uiControl(fileButton), + 0, 0, 1, 1, + 0, uiAlignFill, 0, uiAlignFill); + uiGridAppend(grid, uiControl(fileEntry), + 1, 0, 1, 1, + 1, uiAlignFill, 0, uiAlignFill); +} + +///===================================================================================================================== +static void widgetsSelectDestination(uiWindow* mainwin, uiBox* box) { + const auto group = uiNewGroup("Select destination"); + uiGroupSetMargined(group, 1); + uiBoxAppend(box, uiControl(group), 0); + + uiBoxAppend(box, uiControl(uiNewHorizontalSeparator()), 0); + + const auto cboxServe = uiNewCheckbox("Serve on http://localhost:8080/"); + const auto cboxFile = uiNewCheckbox("Save to folder"); + + static struct CBoxData { + Globals& globals; + uiCheckbox* serve; + uiCheckbox* file; + } cboxData{globals, cboxServe, cboxFile}; + + uiBoxAppend(box, uiControl(cboxServe), 0); + uiCheckboxOnToggled(cboxServe, [](uiCheckbox* cbox, void* data) -> void { + auto& d = *reinterpret_cast<CBoxData*>(data); + d.globals.destinationServe = uiCheckboxChecked(cbox); + uiCheckboxSetChecked(d.file, !d.globals.destinationServe); + }, &cboxData); + + uiBoxAppend(box, uiControl(cboxFile), 0); + uiCheckboxOnToggled(cboxFile, [](uiCheckbox* cbox, void* data) -> void { + auto& d = *reinterpret_cast<CBoxData*>(data); + d.globals.destinationFile = uiCheckboxChecked(cbox); + uiCheckboxSetChecked(d.serve, !d.globals.destinationFile); + }, &cboxData); + + const auto grid = uiNewGrid(); + uiGridSetPadded(grid, 1); + uiBoxAppend(box, uiControl(grid), 0); + + const auto fileButton = uiNewButton("Select Folder"); + const auto fileEntry = uiNewEntry(); + + static struct SelectFileData { + uiWindow* mainwin; + Globals& globals; + uiCheckbox* serve; + uiCheckbox* file; + uiEntry* entry; + } selectFileData{mainwin, globals, cboxServe, cboxFile, fileEntry}; + + const auto onOpenFileClicked = [](uiButton *b, void *data) -> void { + auto& d = *reinterpret_cast<SelectFileData*>(data); + + const auto filename = uiOpenFile(d.mainwin); + if (filename == NULL) { + uiEntrySetText(d.entry, ""); + return; + } + + if (!folderIsValid(filename)) { + uiMsgBoxError(d.mainwin, + "Select folder error", + "You must select a valid folder, not a file!"); + + uiEntrySetText(d.entry, ""); + return; + } + + uiEntrySetText(d.entry, filename); + d.globals.destinationFileStr = std::string(filename); + uiFreeText(filename); + + d.globals.destinationFile = true; + uiCheckboxSetChecked(d.serve, !d.globals.destinationFile); + uiCheckboxSetChecked(d.file, d.globals.destinationFile); + }; + + uiEntryOnChanged(fileEntry, [](uiEntry* e, void* data) -> void { + auto& globals = *reinterpret_cast<Globals*>(data); + globals.destinationFileStr = std::string(uiEntryText(e)); + }, &globals); + + uiEntrySetReadOnly(fileEntry, 0); + uiButtonOnClicked(fileButton, onOpenFileClicked, &selectFileData); + uiGridAppend(grid, uiControl(fileButton), + 0, 0, 1, 1, + 0, uiAlignFill, 0, uiAlignFill); + uiGridAppend(grid, uiControl(fileEntry), + 1, 0, 1, 1, + 1, uiAlignFill, 0, uiAlignFill); +} + +///===================================================================================================================== +static void widgets(uiWindow* mainwin) { + const auto box = uiNewVerticalBox(); + uiBoxSetPadded(box, 1); + uiWindowSetChild(mainwin, uiControl(box)); + + widgetsSelectSource(mainwin, box); + widgetsSelectDestination(mainwin, box); + + uiBoxAppend(box, uiControl(uiNewHorizontalSeparator()), 0); + + auto status = uiNewLabel("Press start to begin"); + uiBoxAppend(box, uiControl(status), 0); + + auto progressbar = uiNewProgressBar(); + uiProgressBarSetValue(progressbar, 0); + uiBoxAppend(box, uiControl(progressbar), 0); + + auto buttonGrid = uiNewGrid(); + uiGridSetPadded(buttonGrid, 1); + + static struct StatusData { + uiWindow* mainwin; + Globals& globals; + uiProgressBar* progress; + uiLabel* label; + } statusData{mainwin, globals, progressbar, status}; + + auto button = uiNewButton("Start"); + uiButtonOnClicked(button, [](uiButton *b, void *data) -> void { + const auto callback = [=]() { + auto& d = *reinterpret_cast<StatusData*>(data); + + uiProgressBarSetValue(d.progress, 0); + uiButtonSetText(b, "Start"); + }; + + auto& d = *reinterpret_cast<StatusData*>(data); + + if (!d.globals.context || d.globals.context->isStopped()) { + try { + d.globals.context = std::make_unique<Context>(d.globals, callback); + uiProgressBarSetValue(d.progress, -1); + uiButtonSetText(b, "Stop"); + } catch (std::exception& e) { + uiMsgBoxError(d.mainwin, "Error", e.what()); + } + } else { + d.globals.context.reset(); + uiProgressBarSetValue(d.progress, 0); + uiButtonSetText(b, "Start"); + } + + }, &statusData); + uiGridAppend(buttonGrid, uiControl(button), + 1, 0, 1, 1, + 1, uiAlignCenter, 0, uiAlignFill); + + uiBoxAppend(box, uiControl(buttonGrid), 0); +} + ///===================================================================================================================== int main (const int argc, char* argv[]) { #ifdef _WIN32 @@ -134,6 +514,26 @@ int main (const int argc, char* argv[]) { #endif try { + uiInitOptions o = {0}; + const char* err; + if ((err = uiInit(&o)) != nullptr) { + const auto e = std::string(err); + uiFreeInitError(err); + throw std::runtime_error(e); + } + + const auto mainwin = uiNewWindow("Decoder", 640, 400, false); + uiWindowSetMargined(mainwin, 1); + uiWindowOnClosing(mainwin, onClosing, NULL); + uiOnShouldQuit(onShouldQuit, mainwin); + + widgets(mainwin); + + uiControlShow(uiControl(mainwin)); + uiMain(); + uiUninit(); + return 0; + return EXIT_SUCCESS; } catch (const std::exception& e) {