295 lines
12 KiB
C++
295 lines
12 KiB
C++
|
#include "main_window.hpp"
|
||
|
#include <gtkmm.h>
|
||
|
#include "my_slider.hpp"
|
||
|
#include <filesystem>
|
||
|
#include <util.hpp>
|
||
|
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<std::string> 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);
|
||
|
}
|