#include "main_window.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #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::windows; BMessage *make_slider_msg(uint32_t what, bool down) { BMessage *msg = new BMessage(what); msg->SetBool(CMD_MOUSE_DOWN_KEY, down); return msg; } 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; } 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(); } } else { 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; }; } LooperLogScaler::LooperLogScaler(double min, double max) { update_min_max(min, max); } void LooperLogScaler::update_min_max(double min, double max) { x0 = min; x1 = (max - min) / (exp(1.0) - 1.0); la = min; lb = (max - min); // x0 = scale_log(min); // x1 = scale_log(max); } double LooperLogScaler::scale_log(double value) { return (std::log(((value - x0) / x1) + 1.0) * lb) + la; } double LooperLogScaler::unscale_log(double value) { return ((std::exp((value - la) / lb) - 1.0) * x1) + x0; } 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); volume_slider->SetLabel(fmt::format("Volume: {}%", (uint8_t)volume).c_str()); 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); } }