#include "main_window.hpp" #include #include "my_slider.hpp" #include #include void MainWindow::set_song_loaded(bool loaded) { bool unloaded = !loaded; seek_bar.set_sensitive(loaded); pause_btn.set_sensitive(loaded); restart_btn.set_sensitive(loaded); stop_btn.set_sensitive(loaded); } void MainWindow::update_file(optional new_file) { playback_file = new_file; bool song_loaded = new_file.has_value(); set_song_loaded(song_loaded); if (song_loaded) { length = playback->GetLength(); length_component_count = TimeToComponentCount(length); length_label.set_text(TimeToString(length, length_component_count).c_str()); std::filesystem::path file_path(new_file.value()); song_name = file_path.stem().string(); set_title((song_name + std::string(" - Looper")).c_str()); seek_bar.set_range(0, length); } else { length = 0; length_component_count = 2; length_label.set_text("00:00"); song_name = ""; set_title("Looper"); seek_bar.set_value(0.0); } bool length_valid = length > 0; seek_bar.set_visible(length_valid); seek_indeterminate.set_visible(!length_valid); time_label.set_visible(length_valid); length_label.set_visible(length_valid); } struct signals_split_by_expected { uint16_t expected; uint16_t unexpected; inline uint16_t all_signals() { return expected|unexpected; } inline uint16_t filter(uint16_t signal) { return all_signals() & signal; } inline bool emitted(uint16_t signal) { return filter(signal) != 0; } inline uint16_t filter_expected(uint16_t signal) { return expected & signal; } inline uint16_t filter_unexpected(uint16_t signal) { return unexpected & signal; } inline bool signal_expected(uint16_t signal) { return filter_expected(signal) == filter(signal); } inline bool signal_unexpected(uint16_t signal) { return filter_unexpected(signal) == filter(signal); } signals_split_by_expected(uint16_t expected, uint16_t unexpected) : expected(expected) , unexpected(unexpected) {} }; bool MainWindow::update() { uint16_t signals_expected_and_emitted = playback->handle_signals(expected_signals); uint16_t unexpected_signals = PlaybackSignalNone; expected_signals &= ~signals_expected_and_emitted; auto handle_signal = [this,signals_expected_and_emitted,&unexpected_signals](uint16_t signals) { uint16_t output = signals_expected_and_emitted & signals; uint16_t unexpected_output = PlaybackSignalNone; uint16_t remaining_signals = signals & ~output; if (remaining_signals != PlaybackSignalNone) { unexpected_output |= playback->handle_signals(remaining_signals); } unexpected_signals |= unexpected_output; return signals_split_by_expected(output, unexpected_output); }; auto signals_handleable = handle_signal(PlaybackSignalFileChanged|PlaybackSignalErrorOccurred|PlaybackSignalPaused|PlaybackSignalResumed|PlaybackSignalPitchChanged|PlaybackSignalSeeked|PlaybackSignalSpeedChanged|PlaybackSignalStarted|PlaybackSignalStopped|PlaybackSignalTempoChanged); if (signals_handleable.emitted(PlaybackSignalFileChanged)) { auto new_file = playback->get_current_file(); if (new_file != playback_file) { update_file(new_file); } } bool disambiguate_running = false; bool stopped = signals_handleable.emitted(PlaybackSignalStopped); bool started = signals_handleable.emitted(PlaybackSignalStarted); if (stopped && started) { disambiguate_running = true; started = playback->get_current_file().has_value(); stopped = !started; } if (stopped) { update_file({}); } double position = playback->GetPosition(); if (!signals_handleable.emitted(PlaybackSignalSeeked) || signals_handleable.signal_unexpected(PlaybackSignalSeeked)) { seek_bar.set_value(position); } time_label.set_text(TimeToString(position, length_component_count)); pause_btn.set_icon_name(playback->IsPaused() ? "media-playback-start-symbolic" : "media-playback-pause-symbolic"); if (signals_handleable.signal_unexpected(PlaybackSignalPitchChanged)) { pitch_slider.set_value(playback->pitch); } if (signals_handleable.signal_unexpected(PlaybackSignalSpeedChanged)) { speed_slider.set_value(playback->speed); } if (signals_handleable.signal_unexpected(PlaybackSignalTempoChanged)) { tempo_slider.set_value(playback->tempo); } if (!running) { timer_stopped = true; g_mutex_lock(timer_mutex); g_cond_signal(timer_stopped_condition); g_mutex_unlock(timer_mutex); return false; } else { return true; } } void MainWindow::expect_signal(uint16_t signal) { expected_signals |= signal; } MainWindow::MainWindow(Playback *playback) : playback(playback), open_dialog("Open some music...") { open_dialog.set_transient_for(*this); open_dialog.set_modal(true); open_dialog.signal_response().connect([this](int response) { if (response == Gtk::ResponseType::OK) { this->expect_signal(PlaybackSignalStarted|PlaybackSignalFileChanged); this->playback->Start(this->open_dialog.get_file().get()->get_path()); } open_dialog.set_visible(false); }); open_dialog.add_button("_Cancel", Gtk::ResponseType::CANCEL); open_dialog.add_button("_Open", Gtk::ResponseType::OK); auto filter_text = Gtk::FileFilter::create(); filter_text->set_name("Music files"); filter_text->add_mime_type("audio/*"); open_dialog.add_filter(filter_text); set_title("Looper"); set_icon_name("looper"); set_default_size(400, 400); openBtn.set_label("Open..."); openBtn.set_icon_name("folder-open-symbolic"); openBtn.signal_clicked().connect([this]() { this->open_dialog.present(); }); topToolbar.set_orientation(Gtk::Orientation::HORIZONTAL); running = true; timer_stopped = false; timer_stopped_condition = (GCond*)malloc(sizeof(GCond)); timer_mutex = (GMutex*)malloc(sizeof(GMutex)); g_cond_init(timer_stopped_condition); g_mutex_init(timer_mutex); top_box.set_orientation(Gtk::Orientation::HORIZONTAL); bottom_box.set_orientation(Gtk::Orientation::HORIZONTAL); main_box.set_orientation(Gtk::Orientation::VERTICAL); //main_box.set_valign(Gtk::Align::END); main_box.set_vexpand(true); top_box.insert_at_end(main_box); bottom_box.insert_at_end(main_box); pause_btn.signal_clicked().connect([this]() { this->expect_signal((this->playback->IsPaused() ? PlaybackSignalResumed : PlaybackSignalPaused)); this->playback->Pause(); }); pause_btn.insert_at_end(top_box); restart_btn.set_icon_name("view-refresh-symbolic"); restart_btn.signal_clicked().connect([this]() { this->expect_signal(PlaybackSignalSeeked); this->playback->Seek(0.0); }); restart_btn.insert_at_end(top_box); time_label.insert_at_end(top_box); seek_indeterminate.set_text(""); seek_indeterminate.set_hexpand(true); seek_indeterminate.insert_at_end(top_box); seek_bar.signal_value_changed().connect([this]() { this->expect_signal(PlaybackSignalSeeked); this->playback->Seek(seek_bar.get_value()); }); seek_bar.set_hexpand(); seek_bar.insert_at_end(top_box); length_label.insert_at_end(top_box); stop_btn.signal_clicked().connect([this]() { this->expect_signal(PlaybackSignalStopped); this->playback->Stop(); }); stop_btn.set_icon_name("media-playback-stop-symbolic"); stop_btn.insert_at_end(top_box); volume_slider.set_min_value(0.0); volume_slider.set_max_value(100.0); volume_slider.set_digits_after_decimal(0); volume_slider.set_suffix("%"); volume_slider.set_prefix("Volume: "); volume_slider.value_changed.connect([this](double value) { this->playback->volume = value; this->playback->Update(); }); volume_slider.insert_at_end(top_box); speed_slider.set_hexpand(); speed_slider.set_digits_after_decimal(2); speed_slider.set_prefix("Speed: "); speed_slider.set_suffix("x"); speed_slider.set_max_value(playback->MaxSpeed); speed_slider.set_min_value(playback->MinSpeed); speed_slider.set_value(playback->speed); speed_slider.value_changed.connect([this](double value) { this->expect_signal(PlaybackSignalSpeedChanged); this->playback->speed = value; this->playback->Update(); }); speed_slider.insert_at_end(bottom_box); tempo_slider.set_hexpand(); tempo_slider.set_digits_after_decimal(2); tempo_slider.set_prefix("Tempo: "); tempo_slider.set_suffix("x"); tempo_slider.set_max_value(playback->MaxTempo); tempo_slider.set_min_value(playback->MinTempo); tempo_slider.set_value(playback->tempo); tempo_slider.value_changed.connect([this](double value) { this->expect_signal(PlaybackSignalTempoChanged); this->playback->tempo = value; this->playback->Update(); }); tempo_slider.insert_at_end(bottom_box); pitch_slider.set_hexpand(); pitch_slider.set_digits_after_decimal(2); pitch_slider.set_prefix("Pitch: "); pitch_slider.set_suffix("x"); pitch_slider.set_max_value(playback->MaxPitch); pitch_slider.set_min_value(playback->MinPitch); pitch_slider.set_value(playback->pitch); pitch_slider.value_changed.connect([this](double value) { this->expect_signal(PlaybackSignalPitchChanged); this->playback->pitch = value; this->playback->Update(); }); pitch_slider.insert_at_end(bottom_box); dropSpaceLabel.set_text("Drop a music file here to play it..."); dropSpaceLabel.set_can_focus(false); dropSpaceLabel.set_can_target(false); dropSpace.set_can_target(true); dropTarget = Gtk::DropTarget::create(G_TYPE_FILE, Gdk::DragAction::LINK); dropSpace.add_controller(dropTarget); dropSpace.set_child(dropSpaceLabel); dropSpace.set_vexpand(true); dropSpace.set_hexpand(true); dropSpace.set_vexpand_set(); dropTarget->signal_drop().connect([this](const Glib::ValueBase &value, double a, double b) { auto obj = g_value_get_object(value.gobj()); g_object_ref(obj); Gio::File file((GFile*)obj); this->expect_signal(PlaybackSignalStarted|PlaybackSignalFileChanged); this->playback->Start(file.get_path()); return false; }, false); dropSpace.insert_at_start(main_box); topToolbar.insert_at_start(main_box); set_child(main_box); update_file(playback->get_current_file()); pitch_slider.set_value(playback->pitch); speed_slider.set_value(playback->speed); tempo_slider.set_value(playback->tempo); volume_slider.set_value(playback->volume); optionsWindow.set_visible(false); optionsWindow.set_transient_for(*this); optionsWindow.set_hide_on_close(true); optionsWindow.set_modal(); settingsBtn.set_icon_name("preferences-other-symbolic"); settingsBtn.signal_clicked().connect([this]() { optionsWindow.present(); }); headerBar.pack_start(openBtn); headerBar.pack_end(settingsBtn); set_titlebar(headerBar); Glib::signal_timeout().connect(sigc::mem_fun(*this, &MainWindow::update), 20); } MainWindow::~MainWindow() { running = false; while (!timer_stopped) { g_cond_wait(timer_stopped_condition, timer_mutex); } free(timer_stopped_condition); free(timer_mutex); }