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; }
+};