From af929cecc2b79297a093a63c5f50d3be90c195ab Mon Sep 17 00:00:00 2001 From: apoorv569 Date: Mon, 2 Aug 2021 09:11:00 +0530 Subject: [PATCH] Waveform panel moved to its own class, add ability to change waveform color and can seek the playhead now. --- assets/waveform.svg | 1 - meson.build | 1 + src/ControlID_Enums.hpp | 1 + src/Database.cpp | 1 - src/MainFrame.cpp | 267 ++------------------------------- src/MainFrame.hpp | 11 +- src/Serialize.cpp | 66 +++++++++ src/Serialize.hpp | 2 + src/SettingsDialog.cpp | 32 +++- src/SettingsDialog.hpp | 8 + src/WaveformViewer.cpp | 320 ++++++++++++++++++++++++++++++++++++++++ src/WaveformViewer.hpp | 77 ++++++++++ 12 files changed, 525 insertions(+), 262 deletions(-) delete mode 100644 assets/waveform.svg create mode 100644 src/WaveformViewer.cpp create mode 100644 src/WaveformViewer.hpp diff --git a/assets/waveform.svg b/assets/waveform.svg deleted file mode 100644 index 809715d..0000000 --- a/assets/waveform.svg +++ /dev/null @@ -1 +0,0 @@ - \ No newline at end of file diff --git a/meson.build b/meson.build index 45b9e71..52f0b5d 100755 --- a/meson.build +++ b/meson.build @@ -57,6 +57,7 @@ src = [ 'src/Sample.cpp', 'src/Serialize.cpp', 'src/Tags.cpp', + 'src/WaveformViewer.cpp', ] diff --git a/src/ControlID_Enums.hpp b/src/ControlID_Enums.hpp index f393e79..459b0ce 100644 --- a/src/ControlID_Enums.hpp +++ b/src/ControlID_Enums.hpp @@ -61,6 +61,7 @@ enum ControlIDs SD_FontType, SD_FontSize, SD_FontBrowseButton, + SD_WaveformColourPickerCtrl, // ------------------------------------------------------------------- // App Menu items diff --git a/src/Database.cpp b/src/Database.cpp index 6950c0c..c1d5162 100644 --- a/src/Database.cpp +++ b/src/Database.cpp @@ -30,7 +30,6 @@ #include #include "Database.hpp" -#include "SettingsDialog.hpp" Database::Database(wxInfoBar& infoBar) : m_InfoBar(infoBar) diff --git a/src/MainFrame.cpp b/src/MainFrame.cpp index bdf866b..3560859 100644 --- a/src/MainFrame.cpp +++ b/src/MainFrame.cpp @@ -31,9 +31,6 @@ #include #include #include -#include -#include -#include #include #include #include @@ -52,20 +49,15 @@ #include #include #include -#include #include #include #include -#include #include #include #include #include #include #include -#include - -#include #include "MainFrame.hpp" #include "ControlID_Enums.hpp" @@ -86,7 +78,6 @@ #define ICON_HIVE_256px SAMPLEHIVE_DATADIR "/assets/icons/icon-hive_256x256.png" #define ICON_STAR_FILLED_16px SAMPLEHIVE_DATADIR "/assets/icons/icon-star_filled_16x16.png" #define ICON_STAR_EMPTY_16px SAMPLEHIVE_DATADIR "/assets/icons/icon-star_empty_16x16.png" -#define WAVEFORM_SVG SAMPLEHIVE_DATADIR "/assets/waveform.svg" #define APP_CONFIG_DIR wxStandardPaths::Get().GetUserConfigDir() + "/.config/SampleHive" #define APP_DATA_DIR wxStandardPaths::Get().GetDocumentsDir() + "/.local/share/SampleHive" #define CONFIG_FILEPATH APP_CONFIG_DIR + "/config.yaml" @@ -254,7 +245,6 @@ MainFrame::MainFrame() m_TopSplitter->SplitHorizontally(m_TopPanel, m_BottomSplitter); m_BottomSplitter->SplitVertically(m_BottomLeftPanel, m_BottomRightPanel); - m_TopWaveformPanel = new wxPanel(m_TopPanel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxNO_BORDER); m_TopControlsPanel = new wxPanel(m_TopPanel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxNO_BORDER); // Looping region controls @@ -283,11 +273,6 @@ MainFrame::MainFrame() m_SamplePosition = new wxStaticText(m_TopControlsPanel, BC_SamplePosition, "--:--/--:--", wxDefaultPosition, wxDefaultSize, wxALIGN_CENTRE_HORIZONTAL); - // Temporary widget to show a waveform sample image - // TODO: Replace with actual waveform display - // m_WaveformBitmap.LoadFile(WAVEFORM_SVG); - // m_WaveformViewer = new wxStaticBitmap(m_TopWaveformPanel, wxID_ANY, m_WaveformBitmap); - // Initialize browser control buttons m_PlayButton = new wxButton(m_TopControlsPanel, BC_Play, _("Play"), wxDefaultPosition, wxDefaultSize, 0); m_PlayButton->SetToolTip(_("Play")); @@ -394,6 +379,8 @@ MainFrame::MainFrame() // Intializing wxTimer m_Timer = new wxTimer(this); + m_TopWaveformPanel = new WaveformViewer(this, m_TopPanel, *m_Library, *m_MediaCtrl, *m_Timer, *m_InfoBar, m_ConfigFilepath, m_DatabaseFilepath); + // Binding events. Bind(wxEVT_MENU, &MainFrame::OnSelectAddFile, this, MN_AddFile); Bind(wxEVT_MENU, &MainFrame::OnSelectAddDirectory, this, MN_AddDirectory); @@ -446,9 +433,6 @@ MainFrame::MainFrame() Bind(wxEVT_BUTTON, &MainFrame::OnClickAddHive, this, BC_HiveAdd); Bind(wxEVT_BUTTON, &MainFrame::OnClickRemoveHive, this, BC_HiveRemove); - m_TopWaveformPanel->Bind(wxEVT_PAINT, &MainFrame::OnPaint, this); - m_TopWaveformPanel->Bind(wxEVT_MOTION, &MainFrame::OnHoverPlayhead, this); - // Adding widgets to their sizers m_MainSizer->Add(m_MainPanel, 1, wxALL | wxEXPAND, 0); @@ -469,8 +453,6 @@ MainFrame::MainFrame() m_BrowserControlSizer->Add(m_VolumeSlider, 1, wxALL | wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL, 2); m_BrowserControlSizer->Add(m_AutoPlayCheck, 0, wxALL | wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL, 2); - // m_WaveformDisplaySizer->Add(m_WaveformViewer, 1, wxALL | wxEXPAND, 2); - m_TopPanelMainSizer->Add(m_TopWaveformPanel, 1, wxALL | wxEXPAND, 2); m_TopPanelMainSizer->Add(m_TopControlsPanel, 0, wxALL | wxEXPAND, 2); @@ -569,6 +551,11 @@ void MainFrame::OnClickSettings(wxCommandEvent& event) OnAutoImportDir(settings->GetImportDirPath()); RefreshDatabase(); } + if (settings->IsWaveformColourChanged()) + { + m_TopWaveformPanel->SetBitmapDirty(true); + m_TopWaveformPanel->Refresh(); + } break; case wxID_CANCEL: break; @@ -957,6 +944,10 @@ void MainFrame::OnClickPlay(wxCommandEvent& event) PushStatusText(wxString::Format(_("Now playing: %s"), selection), 1); + // Update waveform bitmap + m_TopWaveformPanel->SetBitmapDirty(true); + m_TopWaveformPanel->Refresh(); + m_MediaCtrl->Play(); m_Timer->Start(100, wxTIMER_CONTINUOUS); @@ -1045,7 +1036,7 @@ void MainFrame::UpdateElapsedTime(wxTimerEvent& event) m_SamplePosition->SetLabel(wxString::Format(wxT("%s/%s"), position.c_str(), duration.c_str())); - Refresh(); + m_TopWaveformPanel->Refresh(); } void MainFrame::OnCheckAutoplay(wxCommandEvent& event) @@ -1105,6 +1096,10 @@ void MainFrame::OnClickLibrary(wxDataViewEvent& event) return; } + // Update the waveform bitmap + m_TopWaveformPanel->SetBitmapDirty(true); + m_TopWaveformPanel->Refresh(); + wxString selection = m_Library->GetTextValue(selected_row, 1); // Get curremt column @@ -2947,234 +2942,4 @@ void MainFrame::OnEnterLoopPoints(wxCommandEvent& event) wxLogDebug("Loop point text changed"); } -void MainFrame::OnPaint(wxPaintEvent& event) -{ - wxPaintDC dc(m_TopWaveformPanel); - - const wxSize size = m_TopWaveformPanel->GetClientSize(); - - if(!m_WaveformBitmap.IsOk() - || m_WaveformBitmap.IsNull() - || m_WaveformBitmap.GetWidth() != size.x - || m_WaveformBitmap.GetHeight() != size.y) - { - m_WaveformBitmap = wxBitmap(wxImage(size.x, size.y), 32); - // m_WaveformBitmap.Create(size.x, size.y, 32); - wxLogDebug("Updating waveform bitmap.."); - UpdateWaveformBitmap(); - } - else - wxLogDebug("Cannot update waveform bitmap.."); - - dc.DrawBitmap(m_WaveformBitmap, 0, 0, false); - // m_WaveformBitmap.SaveFile("waveform.png", wxBITMAP_TYPE_PNG); - - RenderPlayhead(dc); -} - -void MainFrame::RenderPlayhead(wxDC& dc) -{ - int selected_row = m_Library->GetSelectedRow(); - - if (selected_row < 0) - return; - - wxString selected = m_Library->GetTextValue(selected_row, 1); - std::string path = GetFilenamePathAndExtension(selected).Path.ToStdString(); - - Tags tags(path); - - int length = tags.GetAudioInfo().length; - wxLogDebug("Len: %d", length); - - double position = m_MediaCtrl->Tell(); - wxLogDebug("Pos: %f", position); - - m_Timer->Start(1, wxTIMER_CONTINUOUS); - - int panel_width = m_TopWaveformPanel->GetSize().GetWidth(); - double line_pos = panel_width * (position / length); - - wxLogDebug("Drawing at: %f", line_pos); - - // Draw the triangle - dc.SetPen(wxPen(wxColor(255, 0, 0, 255), 8, wxPENSTYLE_SOLID)); - dc.DrawLine(line_pos - 5, m_TopWaveformPanel->GetSize().GetHeight() - (m_TopWaveformPanel->GetSize().GetHeight() - 1), - line_pos, m_TopWaveformPanel->GetSize().GetHeight() - (m_TopWaveformPanel->GetSize().GetHeight() - 1) + 5); - dc.DrawLine(line_pos + 5, m_TopWaveformPanel->GetSize().GetHeight() - (m_TopWaveformPanel->GetSize().GetHeight() - 1), - line_pos, m_TopWaveformPanel->GetSize().GetHeight() - (m_TopWaveformPanel->GetSize().GetHeight()- 1) + 5); - - // Draw the line - dc.SetPen(wxPen(wxColor(255, 0, 0, 255), 2, wxPENSTYLE_SOLID)); - dc.DrawLine(line_pos, m_TopWaveformPanel->GetSize().GetHeight() - (m_TopWaveformPanel->GetSize().GetHeight() - 1), - line_pos, m_TopWaveformPanel->GetSize().GetHeight() - 1); - - // For testing only - // dc.DrawLine(100 - 5, m_TopWaveformPanel->GetSize().GetHeight() - (m_TopWaveformPanel->GetSize().GetHeight() - 1), - // 100, m_TopWaveformPanel->GetSize().GetHeight() - (m_TopWaveformPanel->GetSize().GetHeight() - 1) + 5); - // dc.DrawLine(100 + 5, m_TopWaveformPanel->GetSize().GetHeight() - (m_TopWaveformPanel->GetSize().GetHeight() - 1), - // 100, m_TopWaveformPanel->GetSize().GetHeight() - (m_TopWaveformPanel->GetSize().GetHeight()- 1) + 5); - - // // Draw the line - // dc.SetPen(wxPen(wxColor(255, 0, 0, 255), 2, wxPENSTYLE_SOLID)); - // dc.DrawLine(100, m_TopWaveformPanel->GetSize().GetHeight() - (m_TopWaveformPanel->GetSize().GetHeight() - 1), - // 100, m_TopWaveformPanel->GetSize().GetHeight() - 1); -} - -void MainFrame::UpdateWaveformBitmap() -{ - Database db(*m_InfoBar); - Settings settings(this, m_ConfigFilepath, m_DatabaseFilepath); - - int selected_row = m_Library->GetSelectedRow(); - - if (selected_row < 0) - return; - - wxString selection = m_Library->GetTextValue(selected_row, 1); - - wxString filepath_with_extension = db.GetSamplePathByFilename(static_cast(DATABASE_FILEPATH), - selection.BeforeLast('.').ToStdString()); - wxString filepath_without_extension = db.GetSamplePathByFilename(static_cast(DATABASE_FILEPATH), - selection.ToStdString()); - - std::string extension = settings.ShouldShowFileExtension() ? - db.GetSampleFileExtension(static_cast(DATABASE_FILEPATH), selection.ToStdString()) : - db.GetSampleFileExtension(static_cast(DATABASE_FILEPATH), selection.BeforeLast('.').ToStdString()); - - wxString path = selection.Contains(wxString::Format(".%s", extension)) ? - filepath_with_extension : filepath_without_extension; - - SndfileHandle snd_file(path); - - int channels = snd_file.channels(); - double sample_rate = snd_file.samplerate(); - sf_count_t frames = snd_file.frames(); - - std::vector sample; - sample.resize(frames * channels); - - std::vector waveform; - - snd_file.read(&sample.at(0), frames * channels); - - float display_width = m_TopWaveformPanel->GetSize().GetWidth(); - float display_height = m_TopWaveformPanel->GetSize().GetHeight(); - - double max_value; - snd_file.command(SFC_CALC_NORM_SIGNAL_MAX, &max_value, sizeof(max_value)); - - float normalized_value = max_value > 1.0f ? 1.0f : 1.0f / max_value; - - float samples_per_pixel = static_cast(frames) / (float)display_width; - // float samples_per_pixel = waveform.size() / float(display_width); - - if (channels == 2) - { - for (int i = 0, j = 0 ; i < frames; i++) - { - float sum = (((sample[j] + sample[j + 1]) * 0.5f) * normalized_value) * float(display_height / 2.0f); - waveform.push_back(sum); - j += 2; - } - } - else - { - waveform.resize(frames); - - for (int i = 0; i < frames; i++) - { - waveform[i] = (sample[i] * normalized_value) * float(display_height / 2.0f); - } - } - - // float minimap_height = 0.0f; - // // make minimap - // std::vector miniMap; - // miniMap.resize(display_width); - // // float samples_per_pixel = static_cast(sampleLength) / (float)display_width; - // float fIndex; - // uint iIndex; - // for (uint16_t i = 0; i < display_width; i++) - // { - // fIndex = float(i) * samples_per_pixel; - // iIndex = fIndex; - // auto minmax = std::minmax_element(waveform.begin() + iIndex, waveform.begin() + iIndex + int(samples_per_pixel)); - // signed char min = std::abs(*minmax.first); - // signed char max = std::abs(*minmax.second); - // signed char maxValue = std::max(min, max); - // miniMap[i] = (float)maxValue / (float)(display_height / 2) * (float)minimap_height; - // } - - /*===========================Draw code============================*/ - wxMemoryDC mdc(m_WaveformBitmap); - - if (!mdc.IsOk()) - { - wxLogDebug("MDC NOT OK"); - return; - } - else - wxLogDebug("MDC OK"); - - mdc.SetPen(wxPen(wxColour(255, 255, 255, 255), 2, wxPENSTYLE_SOLID)); - - for(int i = 0; i < waveform.size() - 1; i++) - { - // dc.DrawPoint((display_width * static_cast(i)) / waveform.size(), - // (waveform[i] / samples_per_pixel) + float(display_height / 2.0f)); - - // dc.DrawLine((display_width * static_cast(i)) / waveform.size(), - // waveform[i] + float(display_height / 2.0f), - // (display_width * static_cast(i)) / waveform.size(), - // waveform[i] + float(display_height / 2.0f)); - - mdc.DrawLine((display_width * i) / waveform.size(), waveform[i] + display_height / 2.0f, - (display_width * i) / waveform.size(), (waveform[i] / samples_per_pixel) + display_height / 2.0f); - } - - // if (!mdc.IsOk()) - // return; - - // dc.Blit(m_TopWaveformPanel->GetSize().GetWidth() - (m_TopWaveformPanel->GetSize().GetWidth() - 1), - // m_TopWaveformPanel->GetSize().GetHeight() - (m_TopWaveformPanel->GetSize().GetHeight() - 1), - // m_WaveformBitmap.GetWidth(), m_WaveformBitmap.GetHeight(), &mdc, 0, 0); -} - -void MainFrame::OnHoverPlayhead(wxMouseEvent& event) -{ - int selected_row = m_Library->GetSelectedRow(); - - if (selected_row < 0) - return; - - wxString selected = m_Library->GetTextValue(selected_row, 1); - std::string path = GetFilenamePathAndExtension(selected).Path.ToStdString(); - - Tags tags(path); - - int length = tags.GetAudioInfo().length; - - double position = m_MediaCtrl->Tell(); - - int panel_width = m_TopWaveformPanel->GetSize().GetWidth(); - double line_pos = panel_width * (position / length); - - wxPoint pos = event.GetPosition(); - - if (pos.x >= line_pos - (line_pos - 5) && pos.x >= line_pos - (line_pos + 5) && pos.y <= line_pos + 5 && pos.y >= line_pos - 5) - { - this->SetCursor(wxCursor(wxCURSOR_CROSS)); - wxLogDebug("Capturing mouse.."); - // SetBackgroundColour("Green"); - } - else - { - SetCursor(wxCursor(wxCURSOR_ARROW)); - // SetBackgroundColour("Brown"); - } - - wxLogDebug("Mouse at: '(%d, %d)'", pos.x, pos.y); -} - MainFrame::~MainFrame(){} diff --git a/src/MainFrame.hpp b/src/MainFrame.hpp index 846cfd1..fb63dcb 100644 --- a/src/MainFrame.hpp +++ b/src/MainFrame.hpp @@ -55,6 +55,8 @@ #include #include +#include "WaveformViewer.hpp" + struct FileInfo { wxString Path; @@ -106,12 +108,11 @@ class MainFrame : public wxFrame // ------------------------------------------------------------------- // Top panel controls wxPanel* m_TopPanel; - wxPanel* m_TopWaveformPanel; + WaveformViewer* m_TopWaveformPanel; wxPanel* m_TopControlsPanel; wxBoxSizer* m_TopSizer; wxBoxSizer* m_TopPanelMainSizer; wxBoxSizer* m_WaveformDisplaySizer; - wxBitmap m_WaveformBitmap ; wxBoxSizer* m_BrowserControlSizer; wxButton* m_PlayButton; wxToggleButton* m_LoopButton; @@ -272,12 +273,6 @@ class MainFrame : public wxFrame // Call after frame creation void SetAfterFrameCreate(); - // Drawing playhead and waveform - void OnPaint(wxPaintEvent& event); - void RenderPlayhead(wxDC& dc); - void UpdateWaveformBitmap(); - void OnHoverPlayhead(wxMouseEvent& event); - // ------------------------------------------------------------------- friend class App; }; diff --git a/src/Serialize.cpp b/src/Serialize.cpp index 8c1386f..93835bb 100644 --- a/src/Serialize.cpp +++ b/src/Serialize.cpp @@ -21,6 +21,7 @@ #include #include +#include #include #include #include @@ -39,6 +40,8 @@ Serializer::Serializer(const std::string& filepath) std::string system_font_face = font.GetFaceName().ToStdString(); int system_font_size = font.GetPointSize(); + wxColour colour = "#FE9647"; + std::string dir = wxStandardPaths::Get().GetDocumentsDir().ToStdString(); if (!ifstrm) @@ -73,6 +76,11 @@ Serializer::Serializer(const std::string& filepath) m_Emitter << YAML::EndMap; m_Emitter << YAML::EndMap << YAML::Newline; + m_Emitter << YAML::Newline << YAML::Key << "Waveform"; + m_Emitter << YAML::BeginMap; + m_Emitter << YAML::Key << "Colour" << YAML::Value << colour.GetAsString().ToStdString(); + m_Emitter << YAML::EndMap << YAML::Newline; + m_Emitter << YAML::Newline << YAML::Key << "Collection"; m_Emitter << YAML::BeginMap; m_Emitter << YAML::Key << "AutoImport" << YAML::Value << false; @@ -249,6 +257,64 @@ FontType Serializer::DeserializeDisplaySettings() const return { face, size }; } +void Serializer::SerializeWaveformColour(wxColour& colour) +{ + YAML::Emitter out; + + std::string colour_string = colour.GetAsString(wxC2S_HTML_SYNTAX).ToStdString(); + + try + { + YAML::Node config = YAML::LoadFile(m_Filepath); + + if (auto waveform = config["Waveform"]) + { + wxLogDebug("Changing waveform colour"); + wxLogDebug("Waveform colour: %s", colour_string); + + waveform["Colour"] = colour_string; + + out << config; + + std::ofstream ofstrm(m_Filepath); + ofstrm << out.c_str(); + } + else + { + wxLogDebug("Error! Cannot store waveform colour."); + } + } + catch(const YAML::ParserException& ex) + { + std::cout << ex.what() << std::endl; + } +} + +wxColour Serializer::DeserializeWaveformColour() const +{ + std::string colour; + + try + { + YAML::Node config = YAML::LoadFile(m_Filepath); + + if (auto waveform = config["Waveform"]) + { + colour = waveform["Colour"].as(); + } + else + { + wxLogDebug("Error! Cannot fetch waveform colour."); + } + } + catch(const YAML::ParserException& ex) + { + std::cout << ex.what() << std::endl; + } + + return static_cast(colour); +} + void Serializer::SerializeAutoImportSettings(wxTextCtrl& textCtrl, wxCheckBox& checkBox) { YAML::Emitter out; diff --git a/src/Serialize.hpp b/src/Serialize.hpp index 20ed427..2a98218 100644 --- a/src/Serialize.hpp +++ b/src/Serialize.hpp @@ -76,6 +76,8 @@ class Serializer // Display settings void SerializeDisplaySettings(wxFont& font); FontType DeserializeDisplaySettings() const; + void SerializeWaveformColour(wxColour& colour); + wxColour DeserializeWaveformColour() const; // ------------------------------------------------------------------- // Auto import settings diff --git a/src/SettingsDialog.cpp b/src/SettingsDialog.cpp index dc1aab0..dfc3a9d 100644 --- a/src/SettingsDialog.cpp +++ b/src/SettingsDialog.cpp @@ -51,8 +51,10 @@ Settings::Settings(wxWindow* window, const std::string& configFilepath, const st m_DisplayTopSizer = new wxBoxSizer(wxVERTICAL); m_DisplayFontSizer = new wxBoxSizer(wxHORIZONTAL); + m_WaveformColourSizer = new wxBoxSizer(wxHORIZONTAL); wxString fontChoices[] = {"System default"}; + Serializer serializer(m_ConfigFilepath); // m_RowHeightText = new wxStaticText(); m_FontTypeText = new wxStaticText(m_DisplaySettingPanel, wxID_ANY, "Font", wxDefaultPosition, wxDefaultSize, 0); // m_RowHeight = new wxChoice(); @@ -61,6 +63,9 @@ Settings::Settings(wxWindow* window, const std::string& configFilepath, const st m_FontSize = new wxSpinCtrl(m_DisplaySettingPanel, SD_FontSize, "Default", wxDefaultPosition, wxDefaultSize); m_FontSize->SetValue(window->GetFont().GetPointSize()); m_FontBrowseButton = new wxButton(m_DisplaySettingPanel, SD_FontBrowseButton, "Select font", wxDefaultPosition, wxDefaultSize, 0); + m_WaveformColourLabel = new wxStaticText(m_DisplaySettingPanel, wxID_ANY, "Waveform colour", wxDefaultPosition, wxDefaultSize, 0); + m_WaveformColourPickerCtrl = new wxColourPickerCtrl(m_DisplaySettingPanel, SD_WaveformColourPickerCtrl, serializer.DeserializeWaveformColour(), + wxDefaultPosition, wxDefaultSize, wxCLRP_DEFAULT_STYLE); m_CollectionSettingPanel = new wxPanel(m_Notebook, wxID_ANY, wxDefaultPosition, wxDefaultSize); @@ -108,6 +113,7 @@ Settings::Settings(wxWindow* window, const std::string& configFilepath, const st Bind(wxEVT_BUTTON, &Settings::OnClickBrowseAutoImportDir, this, SD_BrowseAutoImportDir); Bind(wxEVT_BUTTON, &Settings::OnClickConfigBrowse, this, SD_BrowseConfigDir); Bind(wxEVT_BUTTON, &Settings::OnClickDatabaseBrowse, this, SD_BrowseDatabaseDir); + Bind(wxEVT_COLOURPICKER_CHANGED, &Settings::OnChangeWaveformColour, this, SD_WaveformColourPickerCtrl); // Adding controls to sizers m_NotebookSizer->Add(m_Notebook, 1, wxALL | wxEXPAND, 2); @@ -124,8 +130,11 @@ Settings::Settings(wxWindow* window, const std::string& configFilepath, const st m_DisplayFontSizer->Add(m_FontType, 1, wxALL | wxALIGN_CENTER_VERTICAL, 2); m_DisplayFontSizer->Add(m_FontSize, 0, wxALL | wxALIGN_CENTER_VERTICAL, 2); m_DisplayFontSizer->Add(m_FontBrowseButton, 0, wxALL | wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT, 2); + m_WaveformColourSizer->Add(m_WaveformColourLabel, 0, wxALL | wxALIGN_CENTER_VERTICAL, 2); + m_WaveformColourSizer->Add(m_WaveformColourPickerCtrl, 0, wxALL | wxALIGN_CENTER_VERTICAL, 2); - m_DisplayTopSizer->Add(m_DisplayFontSizer, 1, wxALL | wxEXPAND, 2); + m_DisplayTopSizer->Add(m_DisplayFontSizer, 0, wxALL | wxEXPAND, 2); + m_DisplayTopSizer->Add(m_WaveformColourSizer, 0, wxALL | wxEXPAND, 2); m_CollectionImportDirSizer->Add(m_AutoImportCheck, 0, wxALL | wxALIGN_CENTER_VERTICAL | wxALIGN_LEFT, 2); m_CollectionImportDirSizer->Add(m_ImportDirLocation, 1, wxALL | wxALIGN_CENTER_VERTICAL, 2); @@ -426,4 +435,25 @@ wxString Settings::GetImportDirPath() return dir; } +void Settings::OnChangeWaveformColour(wxColourPickerEvent& event) +{ + Serializer serializer(m_ConfigFilepath); + wxColour colour = m_WaveformColourPickerCtrl->GetColour(); + + wxColour wave_colour = serializer.DeserializeWaveformColour(); + + if (colour != wave_colour) + { + wxLogDebug("Waveform colour changed."); + bWaveformColourChanged = true; + + serializer.SerializeWaveformColour(colour); + } + else + { + wxLogDebug("Waveform colour not changed."); + bWaveformColourChanged = false; + } +} + Settings::~Settings(){} diff --git a/src/SettingsDialog.hpp b/src/SettingsDialog.hpp index c05313a..27edfe2 100644 --- a/src/SettingsDialog.hpp +++ b/src/SettingsDialog.hpp @@ -23,6 +23,7 @@ #include #include +#include #include #include #include @@ -86,6 +87,9 @@ class Settings : public wxDialog wxFontDialog* m_FontDialog; wxButton* m_FontBrowseButton; wxSpinCtrl* m_FontSize; + wxBoxSizer* m_WaveformColourSizer; + wxStaticText* m_WaveformColourLabel; + wxColourPickerCtrl* m_WaveformColourPickerCtrl; // ------------------------------------------------------------------- // Collection page @@ -121,6 +125,7 @@ class Settings : public wxDialog // ------------------------------------------------------------------- bool bAutoImport = false; bool bShowExtension = true; + bool bWaveformColourChanged = false; private: // ------------------------------------------------------------------- @@ -131,6 +136,7 @@ class Settings : public wxDialog void OnClickBrowseAutoImportDir(wxCommandEvent& event); void OnChangeFontSize(wxSpinEvent& event); void OnSelectFont(wxCommandEvent& event); + void OnChangeWaveformColour(wxColourPickerEvent& event); // ------------------------------------------------------------------- void SetCustomFont(); @@ -147,4 +153,6 @@ class Settings : public wxDialog inline wxFont GetFontType() { return m_Font; }; inline bool CanAutoImport() { return bAutoImport; }; inline bool ShouldShowFileExtension() { return bShowExtension; }; + inline bool IsWaveformColourChanged() { return bWaveformColourChanged; } + inline wxColour GetWaveformColour() { return m_WaveformColourPickerCtrl->GetColour(); } }; diff --git a/src/WaveformViewer.cpp b/src/WaveformViewer.cpp new file mode 100644 index 0000000..05eb5c7 --- /dev/null +++ b/src/WaveformViewer.cpp @@ -0,0 +1,320 @@ +/* SampleHive + * Copyright (C) 2021 Apoorv Singh + * A simple, modern audio sample browser/manager for GNU/Linux. + * + * This file is a part of SampleHive + * + * SampleHive is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * SampleHive is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include + +#include +#include +#include +#include + +#include + +#include "WaveformViewer.hpp" +#include "Database.hpp" +#include "SettingsDialog.hpp" +#include "Serialize.hpp" +#include "Tags.hpp" +#include "wx/filefn.h" + +WaveformViewer::WaveformViewer(wxWindow* parentFrame, wxWindow* window, wxDataViewListCtrl& library, wxMediaCtrl& mediaCtrl, + wxTimer& timer, wxInfoBar& infoBar, const std::string& configFilepath, const std::string& databaseFilepath) + : wxPanel(window, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxNO_BORDER | wxFULL_REPAINT_ON_RESIZE), + m_ParentFrame(parentFrame), m_Window(window), m_Library(library), m_MediaCtrl(mediaCtrl), m_Timer(timer), + m_InfoBar(infoBar), m_ConfigFilepath(configFilepath), m_DatabaseFilepath(databaseFilepath) +{ + Bind(wxEVT_PAINT, &WaveformViewer::OnPaint, this); + Bind(wxEVT_MOTION, &WaveformViewer::OnHoverPlayhead, this); + Bind(wxEVT_LEFT_DOWN, &WaveformViewer::OnGrabPlayhead, this); + Bind(wxEVT_LEFT_UP, &WaveformViewer::OnReleasePlayhead, this); +} + +WaveformViewer::~WaveformViewer() +{ + +} + +void WaveformViewer::OnPaint(wxPaintEvent& event) +{ + wxPaintDC dc(this); + + const wxSize& size = m_Window->GetClientSize(); + + if(!m_WaveformBitmap.IsOk() + || m_WaveformBitmap.GetWidth() != size.x + || m_WaveformBitmap.GetHeight() != size.y + || bBitmapDirty) + { + wxLogDebug("Updating waveform bitmap.."); + + m_WaveformBitmap = wxBitmap(wxImage(size.x, size.y), 32); + // m_WaveformBitmap.Create(size.x, size.y, 32); + + UpdateWaveformBitmap(); + + bBitmapDirty = false; + } + + dc.DrawBitmap(m_WaveformBitmap, 0, 0, false); + // m_WaveformBitmap.SaveFile("waveform.png", wxBITMAP_TYPE_PNG); + + RenderPlayhead(dc); +} + +void WaveformViewer::RenderPlayhead(wxDC& dc) +{ + Database db(m_InfoBar); + + int selected_row = m_Library.GetSelectedRow(); + + if (selected_row < 0) + return; + + wxString selected = m_Library.GetTextValue(selected_row, 1); + std::string path = db.GetSamplePathByFilename(m_DatabaseFilepath, selected.BeforeLast('.').ToStdString()); + + Tags tags(path); + + int length = tags.GetAudioInfo().length; + // wxLogDebug("Sample length: %d", length); + + double position = m_MediaCtrl.Tell(); + // wxLogDebug("Current Sample Position: %f", position); + + m_Timer.Start(5, wxTIMER_CONTINUOUS); + + int panel_width = this->GetSize().GetWidth(); + double line_pos = panel_width * (position / length); + + // wxLogDebug("Drawing playhead at: %f", line_pos); + + m_PlayheadColour = wxColor(255, 0, 0, 255); + + // Draw the triangle + dc.SetPen(wxPen(m_PlayheadColour, 8, wxPENSTYLE_SOLID)); + dc.DrawLine(line_pos - 5, this->GetSize().GetHeight() - (this->GetSize().GetHeight() - 1), + line_pos, this->GetSize().GetHeight() - (this->GetSize().GetHeight() - 1) + 5); + dc.DrawLine(line_pos + 5, this->GetSize().GetHeight() - (this->GetSize().GetHeight() - 1), + line_pos, this->GetSize().GetHeight() - (this->GetSize().GetHeight()- 1) + 5); + + // Draw the line + dc.SetPen(wxPen(m_PlayheadColour, 2, wxPENSTYLE_SOLID)); + dc.DrawLine(line_pos, this->GetSize().GetHeight() - (this->GetSize().GetHeight() - 1), + line_pos, this->GetSize().GetHeight() - 1); +} + +void WaveformViewer::UpdateWaveformBitmap() +{ + Database db(m_InfoBar); + Settings settings(m_ParentFrame, m_ConfigFilepath, m_DatabaseFilepath); + Serializer serializer(m_ConfigFilepath); + + int selected_row = m_Library.GetSelectedRow(); + + if (selected_row < 0) + return; + + wxString selection = m_Library.GetTextValue(selected_row, 1); + + wxString filepath_with_extension = db.GetSamplePathByFilename(m_DatabaseFilepath, selection.BeforeLast('.').ToStdString()); + wxString filepath_without_extension = db.GetSamplePathByFilename(m_DatabaseFilepath, selection.ToStdString()); + + std::string extension = settings.ShouldShowFileExtension() ? + db.GetSampleFileExtension(m_DatabaseFilepath, selection.ToStdString()) : + db.GetSampleFileExtension(m_DatabaseFilepath, selection.BeforeLast('.').ToStdString()); + + wxString path = selection.Contains(wxString::Format(".%s", extension)) ? + filepath_with_extension : filepath_without_extension; + + SndfileHandle snd_file(path); + + int channels = snd_file.channels(); + double sample_rate = snd_file.samplerate(); + sf_count_t frames = snd_file.frames(); + + std::vector sample; + sample.resize(frames * channels); + + std::vector waveform; + + snd_file.read(&sample.at(0), frames * channels); + + float display_width = this->GetSize().GetWidth(); + float display_height = this->GetSize().GetHeight(); + + double max_value; + snd_file.command(SFC_CALC_NORM_SIGNAL_MAX, &max_value, sizeof(max_value)); + + float normalized_value = max_value > 1.0f ? 1.0f : 1.0f / max_value; + + float samples_per_pixel = static_cast(frames) / (float)display_width; + + if (channels == 2) + { + for (int i = 0, j = 0 ; i < frames; i++) + { + float sum = (((sample[j] + sample[j + 1]) * 0.5f) * normalized_value) * float(display_height / 2.0f); + waveform.push_back(sum); + j += 2; + } + } + else + { + waveform.resize(frames); + + for (int i = 0; i < frames; i++) + { + waveform[i] = (sample[i] * normalized_value) * float(display_height / 2.0f); + } + } + + // Draw code + wxMemoryDC mdc(m_WaveformBitmap); + + mdc.SetBrush(*wxBLACK); + mdc.Clear(); + + m_WaveformColour = serializer.DeserializeWaveformColour(); + + wxLogDebug("Changing waveform colour to: %s", m_WaveformColour.GetAsString()); + mdc.SetPen(wxPen(wxColour(m_WaveformColour), 2, wxPENSTYLE_SOLID)); + + wxLogDebug("Drawing bitmap.."); + + for(int i = 0; i < waveform.size() - 1; i++) + { + mdc.DrawLine((display_width * i) / waveform.size(), waveform[i] + display_height / 2.0f, + (display_width * i) / waveform.size(), (waveform[i] / samples_per_pixel) + display_height / 2.0f); + } + + wxLogDebug("Done drawing bitmap.."); +} + +void WaveformViewer::OnHoverPlayhead(wxMouseEvent& event) +{ + Database db(m_InfoBar); + + int selected_row = m_Library.GetSelectedRow(); + + if (selected_row < 0) + return; + + wxString selected = m_Library.GetTextValue(selected_row, 1); + std::string path = db.GetSamplePathByFilename(m_DatabaseFilepath, selected.BeforeLast('.').ToStdString()); + + Tags tags(path); + + int length = tags.GetAudioInfo().length; + // wxLogDebug("Sample length: %d", length); + + double position = m_MediaCtrl.Tell(); + // wxLogDebug("Current Sample Position: %f", position); + + int panel_width = this->GetSize().GetWidth(); + double line_pos = panel_width * (position / length); + + wxPoint pos = event.GetPosition(); + // wxLogDebug("PosX: %d", pos.x); + + double seek_to = ((double)pos.x / panel_width) * length; + // wxLogDebug("Seeking to: %f", seek_to); + + if (abs(pos.x - line_pos) <= 5 && pos.y <= 5) + { + SetCursor(wxCursor(wxCURSOR_CLOSED_HAND)); + wxLogDebug("Cursor on playhead.."); + } + else + { + SetCursor(wxCURSOR_ARROW); + return; + } + + wxLogDebug("Mouse at: '(%d, %d)'", pos.x, pos.y); +} + +void WaveformViewer::OnGrabPlayhead(wxMouseEvent& event) +{ + Database db(m_InfoBar); + int selected_row = m_Library.GetSelectedRow(); + + if (selected_row < 0) + return; + + wxString selected = m_Library.GetTextValue(selected_row, 1); + std::string path = db.GetSamplePathByFilename(m_DatabaseFilepath, selected.BeforeLast('.').ToStdString()); + + Tags tags(path); + + int length = tags.GetAudioInfo().length; + + double position = m_MediaCtrl.Tell(); + + int panel_width = this->GetSize().GetWidth(); + double line_pos = panel_width * (position / length); + + wxPoint pos = event.GetPosition(); + + if (abs(pos.x - line_pos) <= 5 && pos.y <= 5) + { + wxWindow::CaptureMouse(); + + SetCursor(wxCURSOR_CLOSED_HAND); + wxLogDebug("Mouse Captured playhead.."); + } + else + { + SetCursor(wxCURSOR_ARROW); + return; + } +} + +void WaveformViewer::OnReleasePlayhead(wxMouseEvent& event) +{ + Database db(m_InfoBar); + + int selected_row = m_Library.GetSelectedRow(); + + if (selected_row < 0) + return; + + wxString selected = m_Library.GetTextValue(selected_row, 1); + std::string path = db.GetSamplePathByFilename(m_DatabaseFilepath, selected.BeforeLast('.').ToStdString()); + + Tags tags(path); + + int length = tags.GetAudioInfo().length; + + double position = m_MediaCtrl.Tell(); + + int panel_width = this->GetSize().GetWidth(); + double line_pos = panel_width * (position / length); + + wxPoint pos = event.GetPosition(); + + double seek_to = ((double)pos.x / panel_width) * length; + + wxWindow::ReleaseMouse(); + SetCursor(wxCURSOR_ARROW); + wxLogDebug("Mouse released playhead.."); + + m_MediaCtrl.Seek(seek_to, wxFromCurrent); + // m_MediaCtrl.Play(); +} diff --git a/src/WaveformViewer.hpp b/src/WaveformViewer.hpp new file mode 100644 index 0000000..1ab8cdb --- /dev/null +++ b/src/WaveformViewer.hpp @@ -0,0 +1,77 @@ +/* SampleHive + * Copyright (C) 2021 Apoorv Singh + * A simple, modern audio sample browser/manager for GNU/Linux. + * + * This file is a part of SampleHive + * + * SampleHive is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * SampleHive is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#pragma once + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +class WaveformViewer : public wxPanel +{ + public: + WaveformViewer(wxWindow* parentFrame, wxWindow* window, wxDataViewListCtrl& library, wxMediaCtrl& mediaCtrl, + wxTimer& timer, wxInfoBar& infoBar, const std::string& configFilepath, const std::string& databaseFilepath); + ~WaveformViewer(); + + private: + // ------------------------------------------------------------------- + wxWindow* m_ParentFrame; + wxWindow* m_Window; + + wxDataViewListCtrl& m_Library; + wxInfoBar& m_InfoBar; + wxMediaCtrl& m_MediaCtrl; + wxTimer& m_Timer; + + const std::string& m_ConfigFilepath; + const std::string& m_DatabaseFilepath; + + private: + // ------------------------------------------------------------------- + wxBitmap m_WaveformBitmap; + wxColour m_PlayheadColour; + wxColour m_WaveformColour; + + private: + // ------------------------------------------------------------------- + bool bBitmapDirty; + + private: + // ------------------------------------------------------------------- + void OnPaint(wxPaintEvent& event); + void RenderPlayhead(wxDC& dc); + void UpdateWaveformBitmap(); + void OnHoverPlayhead(wxMouseEvent& event); + void OnGrabPlayhead(wxMouseEvent& event); + void OnReleasePlayhead(wxMouseEvent& event); + + public: + inline bool IsBitmapDirty() { return bBitmapDirty; } + inline void SetBitmapDirty(bool dirty) { bBitmapDirty = dirty; } +};