2024-11-12 14:53:44 -08:00
|
|
|
#include "main_window.h"
|
|
|
|
#include <Button.h>
|
|
|
|
#include <Slider.h>
|
|
|
|
#include <Message.h>
|
|
|
|
#include <GroupView.h>
|
|
|
|
#include <Messenger.h>
|
|
|
|
#include <MessageRunner.h>
|
|
|
|
#include <MenuBar.h>
|
|
|
|
#include <Path.h>
|
|
|
|
#include <Menu.h>
|
|
|
|
#include <MessageFilter.h>
|
|
|
|
#include <functional>
|
|
|
|
#include <util.hpp>
|
|
|
|
#include <MenuItem.h>
|
|
|
|
#include <thread>
|
|
|
|
#include <log.hpp>
|
|
|
|
#include <cmath>
|
|
|
|
#include <fmt/core.h>
|
|
|
|
#include <fmt/format.h>
|
|
|
|
#include <SDL.h>
|
|
|
|
#include "icons.h"
|
|
|
|
#include "utils.h"
|
|
|
|
#include "aboutwindow.h"
|
|
|
|
#define CMD_MOUSE_DOWN_KEY "catmeow:pressed"
|
|
|
|
#define CMD_PAUSE_RESUME 0x10
|
|
|
|
#define CMD_RESTART 0x11
|
|
|
|
#define CMD_STOP 0x12
|
|
|
|
#define CMD_OPEN 0x13
|
|
|
|
#define CMD_ABOUT 0x14
|
|
|
|
#define CMD_PREFS 0x15
|
|
|
|
#define CMD_SEEK 0x20
|
|
|
|
#define CMD_VOLUME 0x21
|
|
|
|
#define CMD_SPEED 0x22
|
|
|
|
#define CMD_PITCH 0x23
|
|
|
|
#define CMD_TEMPO 0x24
|
|
|
|
#define CMD_UPDATE 0x80
|
|
|
|
#define SLIDER_SCALE 1000
|
|
|
|
#define D(x) ((double)(x))
|
|
|
|
std::vector<Subwindow*> Subwindow::windows;
|
|
|
|
BMessage *make_slider_msg(uint32_t what, bool down) {
|
|
|
|
BMessage *msg = new BMessage(what);
|
|
|
|
msg->SetBool(CMD_MOUSE_DOWN_KEY, down);
|
2024-11-20 08:15:45 -08:00
|
|
|
return msg;
|
2024-11-12 14:53:44 -08:00
|
|
|
}
|
|
|
|
bool is_slider_down_msg(BMessage *msg) {
|
|
|
|
return msg->HasBool(CMD_MOUSE_DOWN_KEY) && msg->GetBool(CMD_MOUSE_DOWN_KEY);
|
|
|
|
}
|
|
|
|
void LooperWindow::UpdateViewFlags(BView *view) {
|
|
|
|
if (view == NULL) return;
|
|
|
|
view->SetFlags(view->Flags()|B_SUPPORTS_LAYOUT|B_FRAME_EVENTS);
|
|
|
|
view->SetResizingMode(B_FOLLOW_ALL_SIDES);
|
|
|
|
}
|
|
|
|
void LooperWindow::UpdateViewFlags(BLayout *layout) {
|
|
|
|
BView *owner = layout->Owner();
|
|
|
|
if (owner == NULL) {
|
|
|
|
owner = layout->View();
|
|
|
|
if (owner == NULL) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
UpdateViewFlags(owner);
|
|
|
|
}
|
|
|
|
LooperWindow::LooperWindow(Playback *playback) : BWindow(BRect(100, 100, 500, 100), "Looper", B_TITLED_WINDOW, 0) {
|
|
|
|
pause_bitmap = load_icon(ICON_PAUSE);
|
|
|
|
resume_bitmap = load_icon(ICON_PLAY);
|
|
|
|
stop_bitmap = load_icon(ICON_STOP);
|
|
|
|
refresh_bitmap = load_icon(ICON_REFRESH);
|
|
|
|
ref_handler = new LooperRefHandler(this);
|
|
|
|
this->playback = playback;
|
|
|
|
float minW, minH, maxW, maxH;
|
|
|
|
GetSizeLimits(&minW, &maxW, &minH, &maxH);
|
|
|
|
minW = 600;
|
|
|
|
minH = 200;
|
|
|
|
SetSizeLimits(minW, maxW, minH, maxH);
|
|
|
|
ResizeTo(minW, minH);
|
|
|
|
DisableUpdates();
|
|
|
|
auto about_window = new AboutWindow();
|
|
|
|
layout = new BGroupLayout(B_VERTICAL);
|
|
|
|
SetLayout(layout);
|
|
|
|
layout->SetSpacing(0.0);
|
|
|
|
layout->SetInsets(0, 0, 0, 0);
|
|
|
|
BMenuBar *menu_bar = new BMenuBar("main_menu");
|
|
|
|
file_menu = new BMenu("File");
|
|
|
|
open_item = new BMenuItem("Open...", new BMessage(CMD_OPEN));
|
|
|
|
prefs_item = new BMenuItem("Preferences...", new BMessage(CMD_PREFS));
|
|
|
|
quit_item = new BMenuItem("Quit", new BMessage(B_QUIT_REQUESTED));
|
|
|
|
help_menu = new BMenu("Help");
|
|
|
|
about_item = new BMenuItem("About...", new BMessage(CMD_ABOUT));
|
|
|
|
file_menu->AddItem(open_item);
|
|
|
|
file_menu->AddItem(prefs_item);
|
|
|
|
file_menu->AddItem(quit_item);
|
|
|
|
help_menu->AddItem(about_item);
|
|
|
|
menu_bar->AddItem(file_menu);
|
|
|
|
menu_bar->AddItem(help_menu);
|
|
|
|
layout->AddView(menu_bar);
|
|
|
|
BView *spacer = new BView("spacer", B_SUPPORTS_LAYOUT|B_FRAME_EVENTS);
|
|
|
|
spacer->SetExplicitMinSize(BSize(0, 0));
|
|
|
|
layout->AddView(spacer);
|
|
|
|
BGroupView *top_row = new BGroupView(B_HORIZONTAL);
|
|
|
|
BGroupView *bottom_row = new BGroupView(B_HORIZONTAL);
|
|
|
|
BLayoutItem *top_item = layout->AddView(top_row);
|
|
|
|
BLayoutItem *bottom_item = layout->AddView(bottom_row);
|
|
|
|
pause_resume_btn = new BButton(NULL, new BMessage(CMD_PAUSE_RESUME));
|
|
|
|
pause_resume_btn->SetTarget(this);
|
|
|
|
top_row->AddChild(pause_resume_btn);
|
|
|
|
restart_btn = new BButton(NULL, new BMessage(CMD_RESTART));
|
|
|
|
restart_btn->SetTarget(this);
|
|
|
|
top_row->AddChild(restart_btn);
|
|
|
|
slider = new BSlider("seek_slider", "Seek", make_slider_msg(CMD_SEEK, false), 0, INT32_MAX, B_HORIZONTAL);
|
|
|
|
slider->SetModificationMessage(make_slider_msg(CMD_SEEK, true));
|
|
|
|
slider->SetTarget(this);
|
|
|
|
top_row->AddChild(slider);
|
|
|
|
stop_btn = new BButton(NULL, new BMessage(CMD_STOP));
|
|
|
|
stop_btn->SetTarget(this);
|
|
|
|
top_row->AddChild(stop_btn);
|
|
|
|
volume_slider = new LooperSlider("volume", "Volume", new BMessage(CMD_VOLUME), 0, 0, 100, 1, false);
|
|
|
|
volume_slider->SetLimitLabels("Muted", "Full Volume");
|
|
|
|
volume_slider->SetTarget(this);
|
|
|
|
top_row->AddChild(volume_slider);
|
|
|
|
speed_slider = new LooperSlider("speed", "Speed", new BMessage(CMD_SPEED), 0, 0.25, 4.0, 0.01, true);
|
|
|
|
speed_slider->SetLimitLabels("0.25x", "4.00x");
|
|
|
|
speed_slider->SetTarget(this);
|
|
|
|
bottom_row->AddChild(speed_slider);
|
|
|
|
tempo_slider = new LooperSlider("tempo", "Tempo", new BMessage(CMD_TEMPO), 0, 0.25, 4.0, 0.01, true);
|
|
|
|
tempo_slider->SetLimitLabels("0.25x", "4.00x");
|
|
|
|
tempo_slider->SetTarget(this);
|
|
|
|
bottom_row->AddChild(tempo_slider);
|
|
|
|
pitch_slider = new LooperSlider("pitch", "Pitch", new BMessage(CMD_PITCH), 0, 0.25, 4.0, 0.01, true);
|
|
|
|
pitch_slider->SetLimitLabels("0.25x", "4.00x");
|
|
|
|
pitch_slider->SetTarget(this);
|
|
|
|
bottom_row->AddChild(pitch_slider);
|
|
|
|
file_panel = new BFilePanel(B_OPEN_PANEL, new BMessenger(this));
|
|
|
|
auto prefs_window = new PrefsWindow(this);
|
|
|
|
prefs_subwindow = Subwindow::Add(prefs_window);
|
|
|
|
about_subwindow = Subwindow::Add(about_window);
|
|
|
|
EnableUpdates();
|
|
|
|
InvalidateLayout(true);
|
|
|
|
BRect top_rect = top_item->Frame();
|
|
|
|
top_item->SetExplicitMinSize(top_rect.Size());
|
|
|
|
BRect bottom_rect = bottom_item->Frame();
|
|
|
|
bottom_item->SetExplicitMinSize(bottom_rect.Size());
|
|
|
|
Pulse();
|
|
|
|
this->update_thread = new std::thread(std::mem_fn(&LooperWindow::ThreadFunc), this);
|
|
|
|
UpdateIfNeeded();
|
|
|
|
StartWatching(prefs_window, CMD_UPDATE_LABEL_SETTING);
|
|
|
|
auto *msg = new BMessage(CMD_UPDATE_LABEL_SETTING);
|
|
|
|
MessageReceived(msg);
|
|
|
|
delete msg;
|
|
|
|
}
|
|
|
|
LooperWindow::~LooperWindow() {
|
|
|
|
delete ref_handler;
|
|
|
|
delete pause_bitmap;
|
|
|
|
delete resume_bitmap;
|
|
|
|
delete stop_bitmap;
|
|
|
|
delete refresh_bitmap;
|
|
|
|
playback->Stop();
|
|
|
|
done = true;
|
|
|
|
update_thread->join();
|
|
|
|
delete update_thread;
|
|
|
|
}
|
|
|
|
void LooperWindow::MessageReceived(BMessage *msg) {
|
|
|
|
SDL_Event ev;
|
|
|
|
while (SDL_PollEvent(&ev)) {
|
|
|
|
if (ev.type == SDL_DROPFILE) {
|
|
|
|
playback->Start(ev.drop.file);
|
|
|
|
} else if (ev.type == SDL_QUIT) {
|
|
|
|
Hide();
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (msg->what == B_REFS_RECEIVED) {
|
|
|
|
entry_ref ref;
|
|
|
|
if (msg->FindRef("refs", &ref) != B_OK) { // Don't crash when failing to get ref - This is probably an error in the system or another application
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
BEntry entry(&ref, true);
|
|
|
|
BPath path;
|
|
|
|
if (entry.GetPath(&path) != B_OK) { // Don't know how this could cause an error, but don't crash if it does.
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
playback->Start(path.Path());
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (msg->IsSystem()) {
|
|
|
|
switch (msg->what) {
|
|
|
|
default: {
|
|
|
|
msg->PrintToStream();
|
|
|
|
} break;
|
2024-11-20 08:15:45 -08:00
|
|
|
}
|
2024-11-12 14:53:44 -08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
switch (msg->what) {
|
|
|
|
case CMD_PAUSE_RESUME: {
|
|
|
|
playback->Pause();
|
|
|
|
} break;
|
|
|
|
case CMD_RESTART: {
|
|
|
|
playback->Seek(0.0);
|
|
|
|
} break;
|
|
|
|
case CMD_VOLUME: {
|
|
|
|
playback->SetVolume(msg->GetDouble("be:value", 100));
|
|
|
|
volume_clicked = is_slider_down_msg(msg);
|
|
|
|
} break;
|
|
|
|
case CMD_SPEED: {
|
|
|
|
playback->SetSpeed(msg->GetDouble("be:value", 1.0));
|
|
|
|
speed_clicked = is_slider_down_msg(msg);
|
|
|
|
} break;
|
|
|
|
case CMD_TEMPO: {
|
|
|
|
playback->SetTempo(msg->GetDouble("be:value", 1.0));
|
|
|
|
tempo_clicked = is_slider_down_msg(msg);
|
|
|
|
} break;
|
|
|
|
case CMD_PITCH: {
|
|
|
|
playback->SetPitch(msg->GetDouble("be:value", 1.0));
|
|
|
|
pitch_clicked = is_slider_down_msg(msg);
|
|
|
|
} break;
|
|
|
|
case CMD_SEEK: {
|
|
|
|
playback->Seek((((double)msg->GetInt32("be:value", 0)) / INT32_MAX) * playback->GetLength());
|
|
|
|
seek_clicked = is_slider_down_msg(msg);
|
|
|
|
} break;
|
|
|
|
case CMD_UPDATE: {
|
|
|
|
Pulse();
|
|
|
|
} break;
|
|
|
|
case CMD_STOP: {
|
|
|
|
playback->Stop();
|
|
|
|
} break;
|
|
|
|
case CMD_OPEN: {
|
|
|
|
file_panel->Show();
|
|
|
|
} break;
|
|
|
|
case CMD_ABOUT: {
|
|
|
|
about_subwindow->Show();
|
|
|
|
} break;
|
|
|
|
case CMD_PREFS: {
|
|
|
|
prefs_subwindow->Show();
|
|
|
|
} break;
|
|
|
|
case CMD_UPDATE_LABEL_SETTING: {
|
|
|
|
volume_slider->MessageReceived(msg);
|
|
|
|
speed_slider->MessageReceived(msg);
|
|
|
|
tempo_slider->MessageReceived(msg);
|
|
|
|
pitch_slider->MessageReceived(msg);
|
|
|
|
if (show_labels) {
|
|
|
|
stop_btn->SetLabel("Stop");
|
|
|
|
restart_btn->SetLabel("Restart");
|
|
|
|
} else {
|
|
|
|
stop_btn->SetLabel("");
|
|
|
|
restart_btn->SetLabel("");
|
|
|
|
}
|
|
|
|
if (show_icons) {
|
|
|
|
stop_btn->SetIcon(stop_bitmap);
|
|
|
|
restart_btn->SetIcon(refresh_bitmap);
|
|
|
|
} else {
|
|
|
|
stop_btn->SetIcon(get_empty_icon());
|
|
|
|
restart_btn->SetIcon(get_empty_icon());
|
|
|
|
}
|
|
|
|
} break;
|
|
|
|
case 'DATA': {
|
|
|
|
status_t err = B_OK;
|
|
|
|
entry_ref ref;
|
|
|
|
if ((err = msg->FindRef("refs", &ref)) == B_OK) {
|
|
|
|
BEntry entry(&ref);
|
|
|
|
BPath path;
|
|
|
|
if ((err = entry.GetPath(&path)) == B_OK) {
|
|
|
|
playback->Start(path.Path());
|
|
|
|
} else {
|
|
|
|
WARNING.writefln("entry.GetPath(&path) == %d", (int32)(err));
|
|
|
|
msg->PrintToStream();
|
|
|
|
}
|
2024-11-20 08:15:45 -08:00
|
|
|
} else {
|
2024-11-12 14:53:44 -08:00
|
|
|
WARNING.writefln("msg->FindRef(\"refs\", &ref) == %d", (int32)(err));
|
|
|
|
msg->PrintToStream();
|
|
|
|
}
|
|
|
|
} break;
|
|
|
|
default: {
|
|
|
|
if (msg->HasPointer("catmeow:target")) {
|
|
|
|
BHandler *ptr = (BHandler*)msg->GetPointer("catmeow:target");
|
|
|
|
ptr->MessageReceived(msg);
|
|
|
|
} else {
|
|
|
|
WARNING.writeln("Unrecognized message type");
|
|
|
|
msg->PrintToStream();
|
|
|
|
}
|
|
|
|
} break;
|
|
|
|
};
|
|
|
|
}
|
|
|
|
void LooperWindow::Pulse() {
|
|
|
|
auto len = playback->GetLength();
|
|
|
|
auto pos = playback->GetPosition();
|
|
|
|
auto pos_milliseconds = pos * 1000.0;
|
|
|
|
int32_t pos_int32 = (int32_t)((((int64_t)pos_milliseconds) * INT32_MAX) / 1000 / len);
|
|
|
|
if (!seek_clicked) slider->SetValue(pos_int32);
|
|
|
|
auto component_count = TimeToComponentCount(len);
|
|
|
|
bool enable_ui = !playback->IsStopped();
|
|
|
|
if (enable_ui) {
|
|
|
|
slider->SetLabel(fmt::format("Position: {}", TimeToString(pos, component_count)).c_str());
|
|
|
|
slider->SetLimitLabels(TimeToString(0, component_count).c_str(), TimeToString(len, component_count).c_str());
|
|
|
|
if (show_icons) pause_resume_btn->SetIcon(playback->IsPaused() ? resume_bitmap : pause_bitmap);
|
|
|
|
if (show_labels) pause_resume_btn->SetLabel(playback->IsPaused() ? "Resume" : "Pause");
|
|
|
|
} else {
|
|
|
|
slider->SetLabel("Position");
|
|
|
|
slider->SetLimitLabels("N/A", "N/A");
|
|
|
|
if (show_icons) pause_resume_btn->SetIcon(pause_bitmap);
|
|
|
|
if (show_labels) pause_resume_btn->SetLabel("Pause");
|
|
|
|
}
|
|
|
|
if (!show_icons) pause_resume_btn->SetIcon(get_empty_icon());
|
|
|
|
if (!show_labels) pause_resume_btn->SetLabel("");
|
|
|
|
slider->SetEnabled(enable_ui);
|
|
|
|
pause_resume_btn->SetEnabled(enable_ui);
|
|
|
|
auto volume = playback->GetVolume();
|
|
|
|
auto pitch = playback->GetPitch();
|
|
|
|
auto speed = playback->GetSpeed();
|
|
|
|
auto tempo = playback->GetTempo();
|
|
|
|
if (!volume_clicked) volume_slider->SetValue(volume);
|
2024-11-20 08:15:45 -08:00
|
|
|
volume_slider->SetLabel(fmt::format("Volume: {}%", (int)volume).c_str());
|
2024-11-12 14:53:44 -08:00
|
|
|
if (!pitch_clicked) pitch_slider->SetValueDouble(pitch);
|
|
|
|
pitch_slider->SetLabel(fmt::format("Pitch {:.02f}x", pitch).c_str());
|
|
|
|
if (!speed_clicked) speed_slider->SetValueDouble(speed);
|
|
|
|
speed_slider->SetLabel(fmt::format("Speed: {:.02f}x", speed).c_str());
|
|
|
|
if (!tempo_clicked) tempo_slider->SetValueDouble(tempo);
|
|
|
|
tempo_slider->SetLabel(fmt::format("Tempo: {:.02f}x", tempo).c_str());
|
|
|
|
UpdateIfNeeded();
|
|
|
|
}
|
|
|
|
void LooperWindow::FrameResized(float newWidth, float newHeight) {
|
|
|
|
InvalidateLayout(true);
|
|
|
|
Layout(true);
|
|
|
|
layout->SetExplicitSize(BSize(newWidth, newHeight));
|
|
|
|
}
|
|
|
|
void LooperWindow::ThreadFunc() {
|
|
|
|
while (true) {
|
|
|
|
if (done) return;
|
|
|
|
if (Lock()) {
|
|
|
|
if (!IsHidden()) {
|
|
|
|
Unlock();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
Unlock();
|
|
|
|
} else {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
while (true) {
|
|
|
|
if (done) return;
|
|
|
|
if (Lock()) {
|
|
|
|
if (IsHidden()) {
|
|
|
|
Unlock();
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
PostMessage(CMD_UPDATE);
|
|
|
|
Unlock();
|
|
|
|
} else {
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
std::this_thread::sleep_for(1s / 60.0);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
LooperRefHandler::LooperRefHandler(LooperWindow *win) {
|
|
|
|
this->win = win;
|
|
|
|
be_app->Lock();
|
|
|
|
this->next_handler = be_app->PreferredHandler();
|
|
|
|
be_app->AddHandler(this);
|
|
|
|
be_app->SetPreferredHandler(this);
|
|
|
|
if (next_handler != NULL) {
|
|
|
|
SetNextHandler(next_handler);
|
|
|
|
}
|
|
|
|
AddFilter(new BMessageFilter(B_REFS_RECEIVED));
|
|
|
|
be_app->Unlock();
|
|
|
|
}
|
|
|
|
void LooperRefHandler::MessageReceived(BMessage *msg) {
|
|
|
|
if (msg->what == B_REFS_RECEIVED) {
|
|
|
|
win->PostMessage(msg);
|
|
|
|
}
|
|
|
|
}
|