diff --git a/README.org b/README.org
index 0bfe8d0..4685a0e 100644
--- a/README.org
+++ b/README.org
@@ -48,18 +48,18 @@ SampleHive let's you manage your audio samples in a nice and simple way, just ad
On Arch based distributions,
#+begin_example
-sudo pacman -S wxgtk3 wxsvg sqlite taglib yaml-cpp
+sudo pacman -S wxgtk3 wxsvg sqlite taglib yaml-cpp libsndfile
#+end_example
On Debian, Ubuntu and distributions based the on two,
#+begin_example
-sudo apt install libwxbase3.0-dev libwxgtk-media3.0-gtk3-dev libwxgtk3.0-gtk3-dev wx3.0-headers libsqlite3-dev libyaml-cpp-dev libtagc0-dev libtag1-dev libtagc0 libexif-dev libpango1.0-dev
+sudo apt install libwxbase3.0-dev libwxgtk-media3.0-gtk3-dev libwxgtk3.0-gtk3-dev wx3.0-headers libsqlite3-dev libyaml-cpp-dev libtagc0-dev libtag1-dev libtagc0 libexif-dev libpango1.0-dev libsndfile1-dev libgstreamer-plugins-base1.0-dev libgstreamer-plugins-bad1.0-dev
#+end_example
-You might also need to install =git=, =meson= and =g++= as well, if you don't already have them installed in order to build SampleHive.
+You might also need to install =git=, =cmake=, =meson= and =g++= as well, if you don't already have them installed in order to build SampleHive.
-/NOTE:/ On Debian and Debian based distributions you also have to install =libwxgtk-media3.0-dev=
+*NOTE:* On Debian and Debian based distributions you also have to install =libwxgtk-media3.0-dev=
** How to build SampleHive?
:PROPERTIES:
diff --git a/assets/icons/icon-ab-dark_16x16.png b/assets/icons/icon-ab-dark_16x16.png
new file mode 100644
index 0000000..a20475a
Binary files /dev/null and b/assets/icons/icon-ab-dark_16x16.png differ
diff --git a/assets/icons/icon-ab-light_16x16.png b/assets/icons/icon-ab-light_16x16.png
new file mode 100644
index 0000000..2b8a070
Binary files /dev/null and b/assets/icons/icon-ab-light_16x16.png differ
diff --git a/assets/icons/icon-loop-dark_16x16.png b/assets/icons/icon-loop-dark_16x16.png
new file mode 100644
index 0000000..e693992
Binary files /dev/null and b/assets/icons/icon-loop-dark_16x16.png differ
diff --git a/assets/icons/icon-loop-light_16x16.png b/assets/icons/icon-loop-light_16x16.png
new file mode 100644
index 0000000..29a87ee
Binary files /dev/null and b/assets/icons/icon-loop-light_16x16.png differ
diff --git a/assets/icons/icon-mute-dark_16x16.png b/assets/icons/icon-mute-dark_16x16.png
new file mode 100644
index 0000000..947cf99
Binary files /dev/null and b/assets/icons/icon-mute-dark_16x16.png differ
diff --git a/assets/icons/icon-mute-light_16x16.png b/assets/icons/icon-mute-light_16x16.png
new file mode 100644
index 0000000..964a997
Binary files /dev/null and b/assets/icons/icon-mute-light_16x16.png differ
diff --git a/assets/icons/icon-play-dark_16x16.png b/assets/icons/icon-play-dark_16x16.png
new file mode 100644
index 0000000..86c52d3
Binary files /dev/null and b/assets/icons/icon-play-dark_16x16.png differ
diff --git a/assets/icons/icon-play-light_16x16.png b/assets/icons/icon-play-light_16x16.png
new file mode 100644
index 0000000..6646268
Binary files /dev/null and b/assets/icons/icon-play-light_16x16.png differ
diff --git a/assets/icons/icon-stop-dark_16x16.png b/assets/icons/icon-stop-dark_16x16.png
new file mode 100644
index 0000000..39f0cc9
Binary files /dev/null and b/assets/icons/icon-stop-dark_16x16.png differ
diff --git a/assets/icons/icon-stop-light_16x16.png b/assets/icons/icon-stop-light_16x16.png
new file mode 100644
index 0000000..110767c
Binary files /dev/null and b/assets/icons/icon-stop-light_16x16.png differ
diff --git a/assets/screenshots/screenshot-samplehive-dark.png b/assets/screenshots/screenshot-samplehive-dark.png
index b7292ed..e845616 100644
Binary files a/assets/screenshots/screenshot-samplehive-dark.png and b/assets/screenshots/screenshot-samplehive-dark.png differ
diff --git a/assets/screenshots/screenshot-samplehive-light.png b/assets/screenshots/screenshot-samplehive-light.png
index d652f72..182a7fb 100644
Binary files a/assets/screenshots/screenshot-samplehive-light.png and b/assets/screenshots/screenshot-samplehive-light.png differ
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 e81a9e8..6d7fe65 100755
--- a/meson.build
+++ b/meson.build
@@ -17,13 +17,15 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
-project('SampleHive', 'cpp',
+project('SampleHive',
+ 'cpp',
version : 'v0.8.4_alpha.1',
license : 'GPL v3',
default_options : ['warning_level=1',
'cpp_std=c++11'])
meson_src_root = meson.current_source_dir()
+meson_build_root = meson.current_build_dir()
# Save important directories
prefix = get_option('prefix')
@@ -40,11 +42,39 @@ config_data.set_quoted('LIBDIR', libdir)
config_data.set_quoted('DATADIR', datadir)
config_data.set_quoted('SAMPLEHIVE_DATADIR', samplehive_datadir)
-# Create samplehive-config.h based on configuration
-config_h = configure_file(
- output: 'SampleHiveConfig.hpp',
- configuration: config_data,
-)
+# Import CMake
+cmake = import('cmake')
+
+wx_opts = cmake.subproject_options()
+wx_opts.add_cmake_defines({'CMAKE_POSITION_INDEPENDENT_CODE': 'ON',
+ 'CMAKE_INSTALL_PREFIX': prefix,
+ 'CMAKE_BUILD_TYPE': 'Release',
+ 'CMAKE_CXX_COMPILER': 'g++',
+ 'wxBUILD_SHARED': 'ON',
+ 'wxBUILD_TESTS': 'OFF',
+ 'wxBUILD_SAMPLES': 'OFF',
+ 'wxBUILD_DEMOS': 'OFF',
+ 'wxBUILD_COMPATIBILITY': '3.0',
+ 'wxUSE_UNICODE': 'ON',
+ 'wxUSE_AUI': 'OFF',
+ 'wxUSE_XML': 'OFF',
+ 'wxUSE_XRC': 'ON',
+ 'wxUSE_HTML': 'ON',
+ 'wxUSE_QA': 'ON',
+ 'wxUSE_PROPGRID': 'OFF',
+ 'wxUSE_RIBBON': 'OFF',
+ 'wxUSE_MDI': 'OFF',
+ 'wxUSE_MDI_ARCHITECTURE': 'OFF',
+ 'wxUSE_RICHTEXT': 'OFF',
+ 'wxUSE_WEBVIEW': 'OFF',
+ 'wxUSE_LIBSDL': 'OFF',
+ 'wxUSE_MEDIACTRL': 'ON'})
+
+taglib_opts = cmake.subproject_options()
+taglib_opts.add_cmake_defines({'CMAKE_POSITION_INDEPENDENT_CODE': 'ON',
+ 'CMAKE_INSTALL_PREFIX': prefix,
+ 'CMAKE_BUILD_TYPE': 'Release',
+ 'CMAKE_CXX_COMPILER': 'g++'})
# Source files to be compiled
src = [
@@ -57,34 +87,88 @@ src = [
'src/Sample.cpp',
'src/Serialize.cpp',
'src/Tags.cpp',
+ 'src/WaveformViewer.cpp',
+ 'src/SH_Event.cpp',
]
-wxconfig = find_program(['wx-config-gtk3', 'wx-config'])
-wx_modules = ['media', 'std']
-wx_cxx_flags = []
-wx_libs = []
-
-foreach module : wx_modules
- wx_cxx_flags += run_command(wxconfig, '--cxxflags', module).stdout().strip().split()
- wx_libs += run_command(wxconfig, '--libs', module).stdout().strip().split()
-endforeach
-
# Dependencies
-wx = dependency('wxwidgets', version: '>=3.0.4')
-taglib = dependency('taglib', version: '>=1.11')
-sqlite3 = dependency('sqlite3')
-yaml = dependency('yaml-cpp')
+wx = dependency('wxwidgets', version: '>=3.1.5', required: false)
-install_subdir(
- 'assets',
- install_dir: samplehive_datadir,
- exclude_directories: 'screenshots'
-)
+wx_found = false
-executable('SampleHive',
- sources: src,
- cpp_args: [wx_cxx_flags],
- link_args: [wx_libs],
- dependencies: [wx, taglib, sqlite3, yaml],
- install: true)
+if not wx.found()
+ wx_found = false
+
+ wx_subproject = cmake.subproject('wxwidgets', options: wx_opts)
+ wx_base = wx_subproject.dependency('wxbase')
+ wx_core = wx_subproject.dependency('wxcore')
+ wx_media = wx_subproject.dependency('wxmedia')
+ wx = [wx_core, wx_base, wx_media]
+else
+ wx_found = true
+
+ wxconfig = find_program(['wx-config-gtk3', 'wx-config'])
+ wx_modules = ['media', 'std']
+ wx_cxx_flags = []
+ wx_libs = []
+
+ foreach module : wx_modules
+ wx_cxx_flags += run_command(wxconfig, '--cxxflags', module).stdout().strip().split()
+ wx_libs += run_command(wxconfig, '--libs', module).stdout().strip().split()
+ endforeach
+endif
+
+taglib = dependency('taglib', version: '>=1.12', required: false)
+
+if not taglib.found()
+ taglib_subproject = cmake.subproject('taglib', options: taglib_opts)
+ taglib = taglib_subproject.dependency('tag')
+else
+ config_data.set('USE_SYSTEM_INCLUDE_PATH', 1)
+endif
+
+sqlite3 = dependency('sqlite3', required: true)
+yaml = dependency('yaml-cpp', required: true)
+snd = dependency('sndfile', required: true)
+
+# Create samplehive-config.h based on configuration
+config_h = configure_file(output: 'SampleHiveConfig.hpp',
+ configuration: config_data,)
+
+install_subdir('assets',
+ install_dir: samplehive_datadir,
+ exclude_directories: 'screenshots')
+
+if wx_found
+ executable('SampleHive',
+ sources: src,
+ cpp_args: [wx_cxx_flags],
+ link_args: [wx_libs],
+ dependencies: [wx, taglib, sqlite3, yaml, snd],
+ install: true,
+ install_rpath: prefix / 'lib')
+else
+ executable('SampleHive',
+ sources: src,
+ dependencies: [wx, taglib, sqlite3, yaml, snd],
+ install: true,
+ install_rpath: prefix / 'lib')
+endif
+
+summary(
+ {
+ 'Debug': get_option('debug'),
+ 'Optimization': get_option('optimization'),
+ },
+ section: 'General')
+
+summary(
+ {
+ 'prefix': prefix,
+ 'bindir': bindir,
+ 'libdir': libdir,
+ 'datadir': datadir,
+ 'samplehive_datadir': samplehive_datadir,
+ },
+ section: 'Directories')
diff --git a/src/App.cpp b/src/App.cpp
index fc58dda..98be309 100644
--- a/src/App.cpp
+++ b/src/App.cpp
@@ -79,7 +79,7 @@ bool App::OnCmdLineParsed(wxCmdLineParser& parser)
if (parser.Found("version"))
{
- std::cout << "SampleHive v0.8.4_alpha.1" << std::endl;
+ std::cout << "SampleHive v0.9.0_alpha.1" << std::endl;
return false;
}
diff --git a/src/ControlID_Enums.hpp b/src/ControlID_Enums.hpp
index efb11dd..272af52 100644
--- a/src/ControlID_Enums.hpp
+++ b/src/ControlID_Enums.hpp
@@ -35,6 +35,7 @@ enum ControlIDs
BC_Settings,
BC_Loop,
BC_Stop,
+ BC_LoopABButton,
BC_Mute,
BC_Autoplay,
BC_Volume,
@@ -59,6 +60,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..bd0b1bc 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)
@@ -383,7 +382,7 @@ void Database::UpdateHive(const std::string& dbPath, const std::string& hiveOldN
}
else
{
- wxLogInfo("Hive updated successfully. %s", m_ErrMsg);
+ wxLogDebug("Hive updated successfully. %s", m_ErrMsg);
}
sqlite3_close(m_Database);
@@ -566,7 +565,7 @@ std::string Database::GetSampleType(const std::string& dbPath, const std::string
if (sqlite3_step(m_Stmt) == SQLITE_ROW)
{
- wxLogInfo("Record found, fetching..");
+ wxLogDebug("Record found, fetching..");
type = std::string(reinterpret_cast(sqlite3_column_text(m_Stmt, 0)));
}
@@ -582,7 +581,7 @@ std::string Database::GetSampleType(const std::string& dbPath, const std::string
}
else
{
- wxLogInfo("Selected data from table successfully.");
+ wxLogDebug("Selected data from table successfully.");
}
sqlite3_close(m_Database);
@@ -611,7 +610,7 @@ int Database::GetFavoriteColumnValueByFilename(const std::string& dbPath, const
if (sqlite3_step(m_Stmt) == SQLITE_ROW)
{
- wxLogInfo("Record found, fetching..");
+ wxLogDebug("Record found, fetching..");
value = sqlite3_column_int(m_Stmt, 0);
}
@@ -626,7 +625,7 @@ int Database::GetFavoriteColumnValueByFilename(const std::string& dbPath, const
}
else
{
- wxLogInfo("Selected data from table successfully.");
+ wxLogDebug("Selected data from table successfully.");
}
sqlite3_close(m_Database);
@@ -655,7 +654,7 @@ std::string Database::GetHiveByFilename(const std::string& dbPath, const std::st
if (sqlite3_step(m_Stmt) == SQLITE_ROW)
{
- wxLogInfo("Record found, fetching..");
+ wxLogDebug("Record found, fetching..");
hive = std::string(reinterpret_cast(sqlite3_column_text(m_Stmt, 0)));
}
@@ -671,7 +670,7 @@ std::string Database::GetHiveByFilename(const std::string& dbPath, const std::st
}
else
{
- wxLogInfo("Selected data from table successfully.");
+ wxLogDebug("Selected data from table successfully.");
}
sqlite3_close(m_Database);
@@ -778,7 +777,7 @@ std::string Database::GetSamplePathByFilename(const std::string& dbPath, const s
if (sqlite3_step(m_Stmt) == SQLITE_ROW)
{
- wxLogInfo("Record found, fetching..");
+ wxLogDebug("Record found, fetching..");
path = std::string(reinterpret_cast(sqlite3_column_text(m_Stmt, 0)));
}
@@ -793,7 +792,7 @@ std::string Database::GetSamplePathByFilename(const std::string& dbPath, const s
}
else
{
- wxLogInfo("Selected data from table successfully.");
+ wxLogDebug("Selected data from table successfully.");
}
sqlite3_close(m_Database);
@@ -822,7 +821,7 @@ std::string Database::GetSampleFileExtension(const std::string& dbPath, const st
if (sqlite3_step(m_Stmt) == SQLITE_ROW)
{
- wxLogInfo("Record found, fetching..");
+ wxLogDebug("Record found, fetching..");
extension = std::string(reinterpret_cast(sqlite3_column_text(m_Stmt, 0)));
}
@@ -837,7 +836,7 @@ std::string Database::GetSampleFileExtension(const std::string& dbPath, const st
}
else
{
- wxLogInfo("Selected data from table successfully.");
+ wxLogDebug("Selected data from table successfully.");
}
sqlite3_close(m_Database);
@@ -1039,7 +1038,7 @@ Database::FilterDatabaseBySampleName(const std::string& dbPath, wxVector(sqlite3_column_text(m_Stmt, 1))));
wxString sample_pack = wxString(std::string(reinterpret_cast(sqlite3_column_text(m_Stmt, 2))));
@@ -1141,7 +1140,7 @@ Database::FilterDatabaseByHiveName(const std::string& dbPath, wxVector(sqlite3_column_text(m_Stmt, 1))));
wxString sample_pack = wxString(std::string(reinterpret_cast(sqlite3_column_text(m_Stmt, 2))));
@@ -1234,7 +1233,7 @@ void Database::LoadHivesDatabase(const std::string& dbPath, wxDataViewTreeCtrl&
{
while (SQLITE_ROW == sqlite3_step(m_Stmt))
{
- wxLogInfo("Record found, fetching..");
+ wxLogDebug("Record found, fetching..");
wxString hive = wxString(std::string(reinterpret_cast(sqlite3_column_text(m_Stmt, 0))));
treeCtrl.AppendContainer(wxDataViewItem(wxNullPtr), hive);
@@ -1314,7 +1313,7 @@ bool Database::IsTrashed(const std::string& dbPath, const std::string& filename)
if (sqlite3_step(m_Stmt) == SQLITE_ROW)
{
- wxLogInfo("Record found, fetching..");
+ wxLogDebug("Record found, fetching..");
if (sqlite3_column_int(m_Stmt, 0) == 1)
return true;
@@ -1331,7 +1330,7 @@ bool Database::IsTrashed(const std::string& dbPath, const std::string& filename)
}
else
{
- wxLogInfo("Selected data from table successfully.");
+ wxLogDebug("Selected data from table successfully.");
}
sqlite3_close(m_Database);
diff --git a/src/MainFrame.cpp b/src/MainFrame.cpp
index 442dca8..f6f8f9f 100644
--- a/src/MainFrame.cpp
+++ b/src/MainFrame.cpp
@@ -20,8 +20,10 @@
#include
#include
+#include
#include
#include
+#include
#include
#include
@@ -48,14 +50,12 @@
#include
#include
#include
-#include
#include
#include
#include
#include
#include
#include
-#include
#include "MainFrame.hpp"
#include "ControlID_Enums.hpp"
@@ -76,9 +76,18 @@
#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 ICON_PLAY_DARK_16px SAMPLEHIVE_DATADIR "/assets/icons/icon-play-dark_16x16.png"
+#define ICON_STOP_DARK_16px SAMPLEHIVE_DATADIR "/assets/icons/icon-stop-dark_16x16.png"
+#define ICON_AB_DARK_16px SAMPLEHIVE_DATADIR "/assets/icons/icon-ab-dark_16x16.png"
+#define ICON_LOOP_DARK_16px SAMPLEHIVE_DATADIR "/assets/icons/icon-loop-dark_16x16.png"
+#define ICON_MUTE_DARK_16px SAMPLEHIVE_DATADIR "/assets/icons/icon-mute-dark_16x16.png"
+#define ICON_PLAY_LIGHT_16px SAMPLEHIVE_DATADIR "/assets/icons/icon-play-light_16x16.png"
+#define ICON_STOP_LIGHT_16px SAMPLEHIVE_DATADIR "/assets/icons/icon-stop-light_16x16.png"
+#define ICON_AB_LIGHT_16px SAMPLEHIVE_DATADIR "/assets/icons/icon-ab-light_16x16.png"
+#define ICON_LOOP_LIGHT_16px SAMPLEHIVE_DATADIR "/assets/icons/icon-loop-light_16x16.png"
+#define ICON_MUTE_LIGHT_16px SAMPLEHIVE_DATADIR "/assets/icons/icon-mute-light_16x16.png"
+#define APP_CONFIG_DIR wxGetHomeDir() + "/.config/SampleHive"
+#define APP_DATA_DIR wxGetHomeDir() + "/.local/share/SampleHive"
#define CONFIG_FILEPATH APP_CONFIG_DIR + "/config.yaml"
#define DATABASE_FILEPATH APP_DATA_DIR "/sample.hive"
@@ -196,7 +205,7 @@ MainFrame::MainFrame()
_("All files|*|Ogg files (*.ogg)|*.ogg|Wav files (*.wav)|*.wav|"
"Flac files (*.flac)|*.flac"), 0);
- wxString path = wxStandardPaths::Get().GetDocumentsDir();
+ wxString path = wxGetHomeDir();
m_DirCtrl->SetPath(path);
// This panel will hold 2nd page of wxNotebook
@@ -244,32 +253,58 @@ MainFrame::MainFrame()
m_TopSplitter->SplitHorizontally(m_TopPanel, m_BottomSplitter);
m_BottomSplitter->SplitVertically(m_BottomLeftPanel, m_BottomRightPanel);
+ m_TopControlsPanel = new wxPanel(m_TopPanel, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxTAB_TRAVERSAL | wxNO_BORDER);
+
+ // Looping region controls
+ if (m_Theme.IsDark())
+ m_LoopABButton = new wxBitmapToggleButton(m_TopControlsPanel, BC_LoopABButton, static_cast(ICON_AB_LIGHT_16px), wxDefaultPosition, wxDefaultSize, 0);
+ else
+ m_LoopABButton = new wxBitmapToggleButton(m_TopControlsPanel, BC_LoopABButton, static_cast(ICON_AB_DARK_16px), wxDefaultPosition, wxDefaultSize, 0);
+
+ m_LoopABButton->SetToolTip(_("Loop selected region"));
+
// Initializing browser controls on top panel.
- m_AutoPlayCheck = new wxCheckBox(m_TopPanel, BC_Autoplay, _("Autoplay"), wxDefaultPosition, wxDefaultSize, wxCHK_2STATE);
+ m_AutoPlayCheck = new wxCheckBox(m_TopControlsPanel, BC_Autoplay, _("Autoplay"), wxDefaultPosition, wxDefaultSize, wxCHK_2STATE);
m_AutoPlayCheck->SetToolTip(_("Autoplay"));
- m_VolumeSlider = new wxSlider(m_TopPanel, BC_Volume, 100, 0, 100, wxDefaultPosition, wxDefaultSize, wxSL_HORIZONTAL);
+ m_VolumeSlider = new wxSlider(m_TopControlsPanel, BC_Volume, 100, 0, 100, wxDefaultPosition, wxDefaultSize, wxSL_HORIZONTAL);
m_VolumeSlider->SetToolTip(_("Volume"));
m_VolumeSlider->SetMinSize(wxSize(120, -1));
m_VolumeSlider->SetMaxSize(wxSize(120, -1));
- m_SamplePosition = new wxStaticText(m_TopPanel, BC_SamplePosition, "--:--/--:--",
+ 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_WaveformViewer = new wxStaticBitmap(m_TopPanel, wxID_ANY, wxBitmap(WAVEFORM_SVG));
-
// Initialize browser control buttons
- m_PlayButton = new wxButton(m_TopPanel, BC_Play, _("Play"), wxDefaultPosition, wxDefaultSize, 0);
+ if (m_Theme.IsDark())
+ {
+ m_PlayButton = new wxBitmapButton(m_TopControlsPanel, BC_Play, static_cast(ICON_PLAY_LIGHT_16px),
+ wxDefaultPosition, wxDefaultSize, 0);
+ m_LoopButton = new wxBitmapToggleButton(m_TopControlsPanel, BC_Loop, static_cast(ICON_LOOP_LIGHT_16px),
+ wxDefaultPosition, wxDefaultSize, 0);
+ m_StopButton = new wxBitmapButton(m_TopControlsPanel, BC_Stop, static_cast(ICON_STOP_LIGHT_16px),
+ wxDefaultPosition, wxDefaultSize, 0);
+ m_MuteButton = new wxBitmapToggleButton(m_TopControlsPanel, BC_Mute, static_cast(ICON_MUTE_LIGHT_16px),
+ wxDefaultPosition, wxDefaultSize, 0);
+ }
+ else
+ {
+ m_PlayButton = new wxBitmapButton(m_TopControlsPanel, BC_Play, static_cast(ICON_PLAY_DARK_16px),
+ wxDefaultPosition, wxDefaultSize, 0);
+ m_LoopButton = new wxBitmapToggleButton(m_TopControlsPanel, BC_Loop, static_cast(ICON_LOOP_DARK_16px),
+ wxDefaultPosition, wxDefaultSize, 0);
+ m_StopButton = new wxBitmapButton(m_TopControlsPanel, BC_Stop, static_cast(ICON_STOP_DARK_16px),
+ wxDefaultPosition, wxDefaultSize, 0);
+ m_MuteButton = new wxBitmapToggleButton(m_TopControlsPanel, BC_Mute, static_cast(ICON_MUTE_DARK_16px),
+ wxDefaultPosition, wxDefaultSize, 0);
+ }
+
m_PlayButton->SetToolTip(_("Play"));
- m_LoopButton = new wxToggleButton(m_TopPanel, BC_Loop, _("Loop"), wxDefaultPosition, wxDefaultSize, 0);
m_LoopButton->SetToolTip(_("Loop"));
- m_StopButton = new wxButton(m_TopPanel, BC_Stop, _("Stop"), wxDefaultPosition, wxDefaultSize, 0);
m_StopButton->SetToolTip(_("Stop"));
- m_SettingsButton = new wxButton(m_TopPanel, BC_Settings, _("Settings"), wxDefaultPosition, wxDefaultSize, 0);
- m_SettingsButton->SetToolTip(_("Settings"));
- m_MuteButton = new wxToggleButton(m_TopPanel, BC_Mute, _("Mute"), wxDefaultPosition, wxDefaultSize, 0);
m_MuteButton->SetToolTip(_("Mute"));
+ m_SettingsButton = new wxButton(m_TopControlsPanel, BC_Settings, _("Settings"), wxDefaultPosition, wxDefaultSize, 0);
+ m_SettingsButton->SetToolTip(_("Settings"));
+
// Initializing wxSearchCtrl on bottom panel.
m_SearchBox = new wxSearchCtrl(m_BottomRightPanel, BC_Search, _("Search for samples.."), wxDefaultPosition,
wxDefaultSize, wxTE_PROCESS_ENTER);
@@ -364,6 +399,9 @@ MainFrame::MainFrame()
// Intializing wxTimer
m_Timer = new wxTimer(this);
+ m_TopWaveformPanel = new WaveformViewer(this, m_TopPanel, *m_Library, *m_MediaCtrl, *m_InfoBar,
+ m_ConfigFilepath, m_DatabaseFilepath);
+
// Binding events.
Bind(wxEVT_MENU, &MainFrame::OnSelectAddFile, this, MN_AddFile);
Bind(wxEVT_MENU, &MainFrame::OnSelectAddDirectory, this, MN_AddDirectory);
@@ -412,26 +450,28 @@ MainFrame::MainFrame()
Bind(wxEVT_BUTTON, &MainFrame::OnClickAddHive, this, BC_HiveAdd);
Bind(wxEVT_BUTTON, &MainFrame::OnClickRemoveHive, this, BC_HiveRemove);
+ Bind(SampleHive::SH_EVT_LOOP_POINTS_UPDATED, &MainFrame::OnRecieveLoopPoints, this);
+ Bind(SampleHive::SH_EVT_STATUSBAR_MESSAGE_UPDATED, &MainFrame::OnRecieveStatusBarStatus, this);
+
// Adding widgets to their sizers
m_MainSizer->Add(m_MainPanel, 1, wxALL | wxEXPAND, 0);
m_TopSizer->Add(m_TopSplitter, 1, wxALL | wxEXPAND, 0);
- m_BrowserControlSizer->Add(m_PlayButton, 0, wxALL | wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, 2);
- m_BrowserControlSizer->Add(m_LoopButton, 0, wxALL | wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, 2);
- m_BrowserControlSizer->Add(m_StopButton, 0, wxALL | wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, 2);
- m_BrowserControlSizer->Add(m_SettingsButton, 0, wxALL | wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, 2);
+ m_BrowserControlSizer->Add(m_PlayButton, 0, wxALL | wxALIGN_CENTER_VERTICAL, 2);
+ m_BrowserControlSizer->Add(m_StopButton, 0, wxALL | wxALIGN_CENTER_VERTICAL, 2);
+ m_BrowserControlSizer->Add(m_LoopButton, 0, wxALL | wxALIGN_CENTER_VERTICAL, 2);
+ m_BrowserControlSizer->Add(m_LoopABButton, 0, wxALL | wxALIGN_CENTER_VERTICAL, 2);
+ m_BrowserControlSizer->Add(m_SettingsButton, 0, wxALL | wxALIGN_CENTER_VERTICAL, 2);
m_BrowserControlSizer->Add(0,0,1, wxALL | wxEXPAND, 0);
- m_BrowserControlSizer->Add(m_SamplePosition, 0, wxALL | wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL, 2);
+ m_BrowserControlSizer->Add(m_SamplePosition, 0, wxALL | wxALIGN_CENTER_VERTICAL, 2);
m_BrowserControlSizer->Add(30,0,0, wxALL | wxEXPAND, 0);
- m_BrowserControlSizer->Add(m_MuteButton, 0, wxALL | wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL, 2);
- 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_BrowserControlSizer->Add(m_MuteButton, 0, wxALL | wxALIGN_CENTER_VERTICAL, 2);
+ m_BrowserControlSizer->Add(m_VolumeSlider, 1, wxALL | wxALIGN_CENTER_VERTICAL, 2);
+ m_BrowserControlSizer->Add(m_AutoPlayCheck, 0, wxALL | wxALIGN_CENTER_VERTICAL, 2);
- m_WaveformDisplaySizer->Add(m_WaveformViewer, 1, wxALL | wxEXPAND, 2);
-
- m_TopPanelMainSizer->Add(m_WaveformDisplaySizer, 1, wxALL | wxEXPAND, 2);
- m_TopPanelMainSizer->Add(m_BrowserControlSizer, 0, wxALL | wxEXPAND, 2);
+ m_TopPanelMainSizer->Add(m_TopWaveformPanel, 1, wxALL | wxEXPAND, 2);
+ m_TopPanelMainSizer->Add(m_TopControlsPanel, 0, wxALL | wxEXPAND, 2);
m_BottomLeftPanelMainSizer->Add(m_Notebook, 1, wxALL | wxEXPAND, 0);
@@ -464,6 +504,16 @@ MainFrame::MainFrame()
m_TopSizer->SetSizeHints(m_MainPanel);
m_TopSizer->Layout();
+ m_TopControlsPanel->SetSizer(m_BrowserControlSizer);
+ m_BrowserControlSizer->Fit(m_TopControlsPanel);
+ m_BrowserControlSizer->SetSizeHints(m_TopControlsPanel);
+ m_BrowserControlSizer->Layout();
+
+ m_TopWaveformPanel->SetSizer(m_WaveformDisplaySizer);
+ m_WaveformDisplaySizer->Fit(m_TopWaveformPanel);
+ m_WaveformDisplaySizer->SetSizeHints(m_TopWaveformPanel);
+ m_WaveformDisplaySizer->Layout();
+
// Sizer for TopPanel
m_TopPanel->SetSizer(m_TopPanelMainSizer);
m_TopPanelMainSizer->Fit(m_TopPanel);
@@ -518,6 +568,10 @@ void MainFrame::OnClickSettings(wxCommandEvent& event)
OnAutoImportDir(settings->GetImportDirPath());
RefreshDatabase();
}
+ if (settings->IsWaveformColourChanged())
+ {
+ m_TopWaveformPanel->ResetDC();
+ }
break;
case wxID_CANCEL:
break;
@@ -885,7 +939,8 @@ void MainFrame::OnClickPlay(wxCommandEvent& event)
int selected_row = m_Library->GetSelectedRow();
- if (selected_row < 0) return;
+ if (selected_row < 0)
+ return;
wxString selection = m_Library->GetTextValue(selected_row, 1);
@@ -901,25 +956,18 @@ void MainFrame::OnClickPlay(wxCommandEvent& event)
wxString sample_path = GetFilenamePathAndExtension(selection).Path;
- m_MediaCtrl->Load(sample_path);
-
- PushStatusText(wxString::Format(_("Now playing: %s"), selection), 1);
-
- m_MediaCtrl->Play();
-
- m_Timer->Start(100, wxTIMER_CONTINUOUS);
+ if (bLoopPointsSet && m_LoopABButton->GetValue())
+ PlaySample(sample_path.ToStdString(), selection.ToStdString(), true, m_LoopA.ToDouble(), wxFromStart);
+ else
+ PlaySample(sample_path.ToStdString(), selection.ToStdString());
}
void MainFrame::OnClickLoop(wxCommandEvent& event)
{
if (m_LoopButton->GetValue())
- {
bLoop = true;
- }
else
- {
bLoop = false;
- }
}
void MainFrame::OnClickStop(wxCommandEvent& event)
@@ -927,7 +975,9 @@ void MainFrame::OnClickStop(wxCommandEvent& event)
m_MediaCtrl->Stop();
bStopped = true;
- m_Timer->Stop();
+ if (m_Timer->IsRunning())
+ m_Timer->Stop();
+
m_SamplePosition->SetLabel("--:--/--:--");
this->SetStatusText(_("Stopped"), 1);
@@ -957,14 +1007,16 @@ void MainFrame::OnMediaFinished(wxMediaEvent& event)
msgDialog.ShowModal();
}
else
- {
m_MediaCtrl->Play();
- m_Timer->Start(100, wxTIMER_CONTINUOUS);
- }
}
else
{
- m_Timer->Stop();
+ if (m_Timer->IsRunning())
+ {
+ m_Timer->Stop();
+ wxLogDebug("TIMER STOPPED");
+ }
+
m_SamplePosition->SetLabel("--:--/--:--");
PopStatusText(1);
this->SetStatusText(_("Stopped"), 1);
@@ -973,6 +1025,8 @@ void MainFrame::OnMediaFinished(wxMediaEvent& event)
void MainFrame::UpdateElapsedTime(wxTimerEvent& event)
{
+ wxLogDebug("TIMER IS RUNNING..");
+
wxString duration, position;
wxLongLong llLength, llTell;
@@ -988,6 +1042,13 @@ void MainFrame::UpdateElapsedTime(wxTimerEvent& event)
position.Printf(wxT("%2i:%02i"), current_min, current_sec);
m_SamplePosition->SetLabel(wxString::Format(wxT("%s/%s"), position.c_str(), duration.c_str()));
+
+ m_TopControlsPanel->Refresh();
+ m_TopWaveformPanel->Refresh();
+
+ if (bLoopPointsSet && m_LoopABButton->GetValue())
+ if (static_cast(m_MediaCtrl->Tell()) >= m_LoopB.ToDouble())
+ m_MediaCtrl->Seek(m_LoopA.ToDouble(), wxFromStart);
}
void MainFrame::OnCheckAutoplay(wxCommandEvent& event)
@@ -1047,6 +1108,17 @@ void MainFrame::OnClickLibrary(wxDataViewEvent& event)
return;
}
+ // Update the waveform bitmap
+ m_TopWaveformPanel->ResetDC();
+
+ m_LoopABButton->SetValue(false);
+
+ if (m_Timer->IsRunning())
+ {
+ m_Timer->Stop();
+ wxLogDebug("TIMER STOPPED");
+ }
+
wxString selection = m_Library->GetTextValue(selected_row, 1);
// Get curremt column
@@ -1084,15 +1156,17 @@ void MainFrame::OnClickLibrary(wxDataViewEvent& event)
if (CurrentColumn != FavoriteColumn)
{
- m_MediaCtrl->Load(sample_path);
+ ClearLoopPoints();
if (bAutoplay)
{
- PushStatusText(wxString::Format(_("Now playing: %s"), selection), 1);
-
- m_MediaCtrl->Play();
- m_Timer->Start(100, wxTIMER_CONTINUOUS);
+ if (bLoopPointsSet && m_LoopABButton->GetValue())
+ PlaySample(sample_path.ToStdString(), selection.ToStdString(), true, m_LoopA.ToDouble(), wxFromStart);
+ else
+ PlaySample(sample_path.ToStdString(), selection.ToStdString());
}
+ else
+ m_MediaCtrl->Stop();
}
else
{
@@ -2568,7 +2642,7 @@ void MainFrame::LoadConfigFile()
this->CenterOnScreen(wxBOTH);
this->SetIcon(wxIcon(ICON_HIVE_256px, wxICON_DEFAULT_TYPE, -1, -1));
this->SetTitle("SampleHive");
- this->SetStatusText("SampleHive v0.8.4_alpha.1", 3);
+ this->SetStatusText("SampleHive v0.9.0_alpha.1", 3);
this->SetStatusText(_("Stopped"), 1);
}
@@ -2646,7 +2720,7 @@ void MainFrame::OnHiveStartEditing(wxDataViewEvent &event)
void MainFrame::OnSelectAddFile(wxCommandEvent& event)
{
- wxFileDialog file_dialog(this, wxFileSelectorPromptStr, wxStandardPaths::Get().GetDocumentsDir(),
+ wxFileDialog file_dialog(this, wxFileSelectorPromptStr, wxGetHomeDir(),
wxEmptyString, wxFileSelectorDefaultWildcardStr,
wxFD_DEFAULT_STYLE | wxFD_FILE_MUST_EXIST | wxFD_MULTIPLE | wxFD_PREVIEW,
wxDefaultPosition, wxDefaultSize);
@@ -2669,7 +2743,7 @@ void MainFrame::OnSelectAddFile(wxCommandEvent& event)
void MainFrame::OnSelectAddDirectory(wxCommandEvent& event)
{
- wxDirDialog dir_dialog(this, wxDirSelectorPromptStr, wxStandardPaths::Get().GetDocumentsDir(),
+ wxDirDialog dir_dialog(this, wxDirSelectorPromptStr, wxGetHomeDir(),
wxDD_DEFAULT_STYLE | wxDD_DIR_MUST_EXIST, wxDefaultPosition, wxDefaultSize);
switch (dir_dialog.ShowModal())
@@ -2813,21 +2887,21 @@ void MainFrame::OnSelectAbout(wxCommandEvent& event)
aboutInfo.SetName("SampleHive");
aboutInfo.SetIcon(wxIcon(ICON_HIVE_64px));
aboutInfo.AddArtist("Apoorv");
- aboutInfo.SetVersion("v0.8.4_alpha.1", _("Version 0.8.4_alpha.1"));
+ aboutInfo.SetVersion("v0.9.0_alpha.1", _("Version 0.9.0_alpha.1"));
aboutInfo.SetDescription(_("A simple, modern audio sample browser/manager for GNU/Linux."));
aboutInfo.SetCopyright("(C) 2020-2021");
aboutInfo.SetWebSite("http://samplehive.gitlab.io");
aboutInfo.AddDeveloper("Apoorv");
aboutInfo.SetLicence(wxString::FromAscii(
- "SampleHive v0.8.4_alpha.1\n"
+ "SampleHive v0.9.0_alpha.1\n"
"Copyright (C) 2021 Apoorv Singh\n"
"\n"
- "This program is free software: you can redistribute it and/or modify\n"
+ "SampleHive is free software: you can redistribute it and/or modify\n"
"it under the terms of the GNU General Public License as published by\n"
"the Free Software Foundation, either version 3 of the License, or\n"
"(at your option) any later version.\n"
"\n"
- "This program is distributed in the hope that it will be useful,\n"
+ "SampleHive is distributed in the hope that it will be useful,\n"
"but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
"MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\n"
"GNU General Public License for more details.\n"
@@ -2858,4 +2932,64 @@ void MainFrame::SetAfterFrameCreate()
m_BottomSplitter->SetSashPosition(300);
}
+void MainFrame::OnRecieveLoopPoints(SampleHive::SH_LoopPointsEvent& event)
+{
+ wxLogDebug("%s called and recieved loop points", __FUNCTION__);
+
+ std::pair loop_points = event.GetLoopPoints();
+
+ m_LoopA = wxLongLong(loop_points.first);
+ m_LoopB = wxLongLong(loop_points.second);
+
+ int loopA_min = static_cast((m_LoopA / 60000).GetValue());
+ int loopA_sec = static_cast(((m_LoopA % 60000) / 1000).GetValue());
+ int loopB_min = static_cast((m_LoopB / 60000).GetValue());
+ int loopB_sec = static_cast(((m_LoopB % 60000) / 1000).GetValue());
+
+ wxLogDebug(wxString::Format("LoopA: %2i:%02i, LoopB: %2i:%02i",
+ loopA_min, loopA_sec, loopB_min, loopB_sec));
+
+ m_LoopABButton->SetValue(true);
+
+ bLoopPointsSet = true;
+
+ wxLogDebug("%s Event processed successfully..", __FUNCTION__);
+}
+
+void MainFrame::OnRecieveStatusBarStatus(SampleHive::SH_SetStatusBarMessageEvent& event)
+{
+ std::pair status = event.GetMessageAndSection();
+
+ m_StatusBar->PushStatusText(status.first, status.second);
+}
+
+void MainFrame::ClearLoopPoints()
+{
+ m_LoopA = 0;
+ m_LoopB = 0;
+
+ bLoopPointsSet = false;
+}
+
+void MainFrame::PlaySample(const std::string& filepath, const std::string& sample, bool seek, wxFileOffset where, wxSeekMode mode)
+{
+ wxLogDebug("TIMER STARTING FROM %s", __FUNCTION__);
+
+ if (m_MediaCtrl->Load(filepath))
+ {
+ if (seek)
+ m_MediaCtrl->Seek(where, mode);
+
+ if (!m_MediaCtrl->Play())
+ wxLogDebug(_("Error! Cannot play sample."));
+
+ PushStatusText(wxString::Format(_("Now playing: %s"), sample), 1);
+
+ if (!m_Timer->IsRunning())
+ m_Timer->Start(20, wxTIMER_CONTINUOUS);
+ }
+ else
+ wxLogDebug(_("Error! Cannot load sample."));
+}
+
MainFrame::~MainFrame(){}
diff --git a/src/MainFrame.hpp b/src/MainFrame.hpp
index dfa4bfa..5e18559 100644
--- a/src/MainFrame.hpp
+++ b/src/MainFrame.hpp
@@ -20,9 +20,14 @@
#pragma once
+#include "WaveformViewer.hpp"
+#include "SampleHiveConfig.hpp"
+#include "SH_Event.hpp"
+
#include
#include
+#include
#include
#include
#include
@@ -40,6 +45,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -51,9 +57,14 @@
#include
#include
-#include
+#include
#include
-#include
+
+#ifndef USE_SYSTEM_INCLUDE_PATH
+ #include
+#else
+ #include
+#endif
struct FileInfo
{
@@ -106,16 +117,18 @@ class MainFrame : public wxFrame
// -------------------------------------------------------------------
// Top panel controls
wxPanel* m_TopPanel;
+ WaveformViewer* m_TopWaveformPanel;
+ wxPanel* m_TopControlsPanel;
wxBoxSizer* m_TopSizer;
wxBoxSizer* m_TopPanelMainSizer;
wxBoxSizer* m_WaveformDisplaySizer;
- wxStaticBitmap* m_WaveformViewer;
wxBoxSizer* m_BrowserControlSizer;
- wxButton* m_PlayButton;
- wxToggleButton* m_LoopButton;
- wxButton* m_StopButton;
+ wxBitmapButton* m_PlayButton;
+ wxBitmapToggleButton* m_LoopButton;
+ wxBitmapButton* m_StopButton;
wxButton* m_SettingsButton;
- wxToggleButton* m_MuteButton;
+ wxBitmapToggleButton* m_MuteButton;
+ wxBitmapToggleButton* m_LoopABButton;
wxStaticText* m_SamplePosition;
wxSlider* m_VolumeSlider;
wxCheckBox* m_AutoPlayCheck;
@@ -162,6 +175,12 @@ class MainFrame : public wxFrame
// FileSystemWatcher
wxFileSystemWatcher* m_FsWatcher;
+ // -------------------------------------------------------------------
+ wxLongLong m_LoopA, m_LoopB;
+
+ // -------------------------------------------------------------------
+ wxSystemAppearance m_Theme = wxSystemSettings::GetAppearance();
+
private:
// -------------------------------------------------------------------
bool bAutoplay = false;
@@ -169,6 +188,7 @@ class MainFrame : public wxFrame
bool bMuted = false;
bool bStopped = false;
bool bFiltered = false;
+ bool bLoopPointsSet = false;
// -------------------------------------------------------------------
const std::string m_ConfigFilepath;
@@ -243,6 +263,15 @@ class MainFrame : public wxFrame
void AddSamples(wxArrayString& files);
void OnAutoImportDir(const wxString& pathToDirectory);
+ // -------------------------------------------------------------------
+ void PlaySample(const std::string& filepath, const std::string& sample, bool seek = false,
+ wxFileOffset where = NULL, wxSeekMode mode = wxFromStart);
+
+ // Recieve custom events
+ // -------------------------------------------------------------------
+ void OnRecieveLoopPoints(SampleHive::SH_LoopPointsEvent& event);
+ void OnRecieveStatusBarStatus(SampleHive::SH_SetStatusBarMessageEvent& event);
+
// -------------------------------------------------------------------
void LoadDatabase();
void RefreshDatabase();
@@ -264,6 +293,9 @@ class MainFrame : public wxFrame
// Call after frame creation
void SetAfterFrameCreate();
+ // -------------------------------------------------------------------
+ void ClearLoopPoints();
+
// -------------------------------------------------------------------
friend class App;
};
diff --git a/src/SH_Event.cpp b/src/SH_Event.cpp
new file mode 100644
index 0000000..b3e9741
--- /dev/null
+++ b/src/SH_Event.cpp
@@ -0,0 +1,69 @@
+#include "SH_Event.hpp"
+
+namespace SampleHive
+{
+ SH_LoopPointsEvent::SH_LoopPointsEvent(wxEventType eventType, int winId)
+ : wxCommandEvent(eventType, winId)
+ {
+
+ }
+
+ SH_LoopPointsEvent::~SH_LoopPointsEvent()
+ {
+
+ }
+
+ wxDEFINE_EVENT(SH_EVT_LOOP_POINTS_UPDATED, SH_LoopPointsEvent);
+
+ // SH_AddSampleEvent::SH_AddSampleEvent(wxEventType eventType, int winId)
+ // : wxCommandEvent(eventType, winId)
+ // {
+
+ // }
+
+ // SH_AddSampleEvent::~SH_AddSampleEvent()
+ // {
+
+ // }
+
+ // wxDEFINE_EVENT(SH_EVT_STATUS_ADD_SAMPLE, SH_AddSampleEvent);
+
+ // SH_MediaEvent::SH_MediaEvent(wxEventType eventType, int winId)
+ // : wxCommandEvent(eventType, winId)
+ // {
+
+ // }
+
+ // SH_MediaEvent::~SH_MediaEvent()
+ // {
+
+ // }
+
+ // wxDEFINE_EVENT(SH_EVT_MEDIA_STATUS_UPDATED, SH_MediaEvent);
+
+ SH_SetStatusBarMessageEvent::SH_SetStatusBarMessageEvent(wxEventType eventType, int winId)
+ : wxCommandEvent(eventType, winId)
+ {
+
+ }
+
+ SH_SetStatusBarMessageEvent::~SH_SetStatusBarMessageEvent()
+ {
+
+ }
+
+ wxDEFINE_EVENT(SH_EVT_STATUSBAR_MESSAGE_UPDATED, SH_SetStatusBarMessageEvent);
+
+// SH_TimerEvent::SH_TimerEvent(wxEventType eventType, int winId)
+// : wxCommandEvent(eventType, winId)
+// {
+
+// }
+
+// SH_TimerEvent::~SH_TimerEvent()
+// {
+
+// }
+
+// wxDEFINE_EVENT(SH_EVT_TIMER_STATUS_UPDATED, SH_TimerEvent);
+}
diff --git a/src/SH_Event.hpp b/src/SH_Event.hpp
new file mode 100644
index 0000000..d62f609
--- /dev/null
+++ b/src/SH_Event.hpp
@@ -0,0 +1,106 @@
+#pragma once
+
+#include
+
+#include
+
+namespace SampleHive
+{
+ class SH_LoopPointsEvent : public wxCommandEvent
+ {
+ public:
+ SH_LoopPointsEvent(wxEventType eventType, int winId);
+ ~SH_LoopPointsEvent();
+
+ public:
+ virtual wxEvent* Clone() const { return new SH_LoopPointsEvent(*this); }
+
+ public:
+ std::pair GetLoopPoints() const { return { m_LoopA, m_LoopB }; };
+ void SetLoopPoints(std::pair loopPoints)
+ { m_LoopA = loopPoints.first; m_LoopB = loopPoints.second; };
+
+ private:
+ double m_LoopA, m_LoopB;
+ };
+
+ wxDECLARE_EVENT(SH_EVT_LOOP_POINTS_UPDATED, SH_LoopPointsEvent);
+
+ // class SH_AddSampleEvent : public wxCommandEvent
+ // {
+ // public:
+ // SH_AddSampleEvent(wxEventType eventType, int winId);
+ // ~SH_AddSampleEvent();
+
+ // public:
+ // virtual wxEvent* Clone() const { return new SH_AddSampleEvent(*this); }
+
+ // public:
+ // wxArrayString GetArrayString() const { return m_Files; };
+ // void SetArrayString(const wxArrayString& files) { m_Files = files; };
+
+ // private:
+ // wxArrayString m_Files;
+ // };
+
+ // wxDECLARE_EVENT(SH_EVT_STATUS_ADD_SAMPLE, SH_AddSampleEvent);
+
+ // class SH_MediaEvent : public wxCommandEvent
+ // {
+ // public:
+ // SH_MediaEvent(wxEventType eventType, int winId);
+ // ~SH_MediaEvent();
+
+ // public:
+ // virtual wxEvent* Clone() const { return new SH_MediaEvent(*this); }
+
+ // public:
+ // void SetPath(const wxString& path) { m_Path = path; }
+ // wxString GetPath() const { return m_Path; }
+
+ // private:
+ // wxString m_Path;
+ // };
+
+ // wxDECLARE_EVENT(SH_EVT_MEDIA_STATUS_UPDATED, SH_MediaEvent);
+
+ class SH_SetStatusBarMessageEvent : public wxCommandEvent
+ {
+ public:
+ SH_SetStatusBarMessageEvent(wxEventType eventType, int winId);
+ ~SH_SetStatusBarMessageEvent();
+
+ public:
+ virtual wxEvent* Clone() const { return new SH_SetStatusBarMessageEvent(*this); }
+
+ public:
+ std::pair GetMessageAndSection() const { return { m_Msg, m_Section }; }
+ void SetMessageAndSection(std::pair status) { m_Msg = status.first; m_Section = status.second; }
+
+ private:
+ wxString m_Msg;
+ int m_Section;
+ };
+
+ wxDECLARE_EVENT(SH_EVT_STATUSBAR_MESSAGE_UPDATED, SH_SetStatusBarMessageEvent);
+
+// class SH_TimerEvent : public wxCommandEvent
+// {
+// public:
+// SH_TimerEvent(wxEventType eventType, int winId);
+// ~SH_TimerEvent();
+
+// public:
+// virtual wxEvent* Clone() const { return new SH_TimerEvent(*this); }
+
+// public:
+// std::pair GetSecondsAndMode() const { return { m_Seconds, m_Mode }; }
+// void SetSecondsAndMode(std::pair timerStatus) { m_Seconds = timerStatus.first; m_Mode = timerStatus.second; }
+
+// private:
+// int m_Seconds;
+// bool m_Mode;
+// };
+
+// wxDECLARE_EVENT(SH_EVT_TIMER_STATUS_UPDATED, SH_TimerEvent);
+}
diff --git a/src/Serialize.cpp b/src/Serialize.cpp
index 8c1386f..93568a8 100644
--- a/src/Serialize.cpp
+++ b/src/Serialize.cpp
@@ -21,8 +21,9 @@
#include
#include
+#include
#include
-#include
+// #include
#include
#include
@@ -39,7 +40,9 @@ Serializer::Serializer(const std::string& filepath)
std::string system_font_face = font.GetFaceName().ToStdString();
int system_font_size = font.GetPointSize();
- std::string dir = wxStandardPaths::Get().GetDocumentsDir().ToStdString();
+ wxColour colour = "#FE9647";
+
+ std::string dir = wxGetHomeDir().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..c8a7f0e 100644
--- a/src/SettingsDialog.cpp
+++ b/src/SettingsDialog.cpp
@@ -21,7 +21,7 @@
#include
#include
#include
-#include
+// #include
#include
#include "ControlID_Enums.hpp"
@@ -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);
@@ -68,7 +73,7 @@ Settings::Settings(wxWindow* window, const std::string& configFilepath, const st
m_CollectionImportDirSizer = new wxBoxSizer(wxHORIZONTAL);
m_ShowFileExtensionSizer = new wxBoxSizer(wxHORIZONTAL);
- wxString defaultDir = wxStandardPaths::Get().GetDocumentsDir();
+ wxString defaultDir = wxGetHomeDir();
m_AutoImportCheck = new wxCheckBox(m_CollectionSettingPanel, SD_AutoImport, "Auto import", wxDefaultPosition, wxDefaultSize, 0);
m_ImportDirLocation = new wxTextCtrl(m_CollectionSettingPanel, wxID_ANY, defaultDir, wxDefaultPosition, wxDefaultSize, 0);
@@ -108,30 +113,34 @@ 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);
- m_GeneralMainSizer->Add(m_ConfigLabel, 0, wxALL | wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, 2);
+ m_GeneralMainSizer->Add(m_ConfigLabel, 0, wxALL | wxALIGN_CENTER_VERTICAL, 2);
m_GeneralMainSizer->Add(m_ConfigText, 1, wxALL | wxALIGN_CENTER_VERTICAL | wxEXPAND, 2);
- m_GeneralMainSizer->Add(m_ConfigBrowse, 0, wxALL | wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL, 2);
+ m_GeneralMainSizer->Add(m_ConfigBrowse, 0, wxALL | wxALIGN_CENTER_VERTICAL, 2);
- m_GeneralMainSizer->Add(m_DatabaseLabel, 0, wxALL | wxALIGN_LEFT | wxALIGN_CENTER_VERTICAL, 2);
+ m_GeneralMainSizer->Add(m_DatabaseLabel, 0, wxALL | wxALIGN_CENTER_VERTICAL, 2);
m_GeneralMainSizer->Add(m_DatabaseText, 1, wxALL | wxALIGN_CENTER_VERTICAL | wxEXPAND, 2);
- m_GeneralMainSizer->Add(m_DatabaseBrowse, 0, wxALL | wxALIGN_RIGHT | wxALIGN_CENTER_VERTICAL, 2);
+ m_GeneralMainSizer->Add(m_DatabaseBrowse, 0, wxALL | wxALIGN_CENTER_VERTICAL, 2);
m_DisplayFontSizer->Add(m_FontTypeText, 0, wxALL | wxALIGN_CENTER_VERTICAL, 2);
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_DisplayFontSizer->Add(m_FontBrowseButton, 0, wxALL | wxALIGN_CENTER_VERTICAL, 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_AutoImportCheck, 0, wxALL | wxALIGN_CENTER_VERTICAL, 2);
m_CollectionImportDirSizer->Add(m_ImportDirLocation, 1, wxALL | wxALIGN_CENTER_VERTICAL, 2);
- m_CollectionImportDirSizer->Add(m_BrowseAutoImportDirButton, 0, wxALL | wxALIGN_CENTER_VERTICAL | wxALIGN_RIGHT, 2);
+ m_CollectionImportDirSizer->Add(m_BrowseAutoImportDirButton, 0, wxALL | wxALIGN_CENTER_VERTICAL, 2);
- m_ShowFileExtensionSizer->Add(m_ShowFileExtensionCheck, 0, wxALL | wxALIGN_LEFT, 2);
+ m_ShowFileExtensionSizer->Add(m_ShowFileExtensionCheck, 0, wxALL, 2);
m_CollectionTopSizer->Add(m_CollectionImportDirSizer, 0, wxALL | wxEXPAND, 2);
m_CollectionTopSizer->Add(m_ShowFileExtensionSizer, 0, wxALL | wxEXPAND, 2);
@@ -169,7 +178,7 @@ Settings::Settings(wxWindow* window, const std::string& configFilepath, const st
void Settings::OnClickConfigBrowse(wxCommandEvent& event)
{
- wxString initial_dir = wxStandardPaths::Get().GetDocumentsDir();
+ wxString initial_dir = wxGetHomeDir();
m_DirDialog = new wxDirDialog(this, "Select a directory..", initial_dir,
wxDD_DEFAULT_STYLE |
@@ -192,7 +201,7 @@ void Settings::OnClickConfigBrowse(wxCommandEvent& event)
void Settings::OnClickDatabaseBrowse(wxCommandEvent& event)
{
- wxString initial_dir = wxStandardPaths::Get().GetDocumentsDir();
+ wxString initial_dir = wxGetHomeDir();
m_DirDialog = new wxDirDialog(this, "Select a directory..", initial_dir,
wxDD_DEFAULT_STYLE |
@@ -255,7 +264,7 @@ void Settings::OnClickBrowseAutoImportDir(wxCommandEvent& event)
{
Serializer serializer(m_ConfigFilepath);
- wxString initial_dir = wxStandardPaths::Get().GetDocumentsDir();
+ wxString initial_dir = wxGetHomeDir();
m_DirDialog = new wxDirDialog(this, "Select a directory..", initial_dir,
wxDD_DEFAULT_STYLE |
@@ -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/Tags.cpp b/src/Tags.cpp
index 69089d9..35d1ae5 100644
--- a/src/Tags.cpp
+++ b/src/Tags.cpp
@@ -18,11 +18,17 @@
* along with this program. If not, see .
*/
-#include
-#include
-#include
-
#include "Tags.hpp"
+#include "SampleHiveConfig.hpp"
+
+#include
+#include
+
+#ifndef USE_SYSTEM_INCLUDE_PATH
+ #include
+#else
+ #include
+#endif
Tags::Tags(const std::string& filename)
: m_Filepath(filename)
diff --git a/src/WaveformViewer.cpp b/src/WaveformViewer.cpp
new file mode 100644
index 0000000..f50342f
--- /dev/null
+++ b/src/WaveformViewer.cpp
@@ -0,0 +1,496 @@
+/* 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
+#include
+#include
+
+#include
+
+#include "WaveformViewer.hpp"
+#include "Database.hpp"
+#include "SettingsDialog.hpp"
+#include "Serialize.hpp"
+#include "Tags.hpp"
+#include "SH_Event.hpp"
+
+WaveformViewer::WaveformViewer(wxWindow* parentFrame, wxWindow* window, wxDataViewListCtrl& library,
+ wxMediaCtrl& mediaCtrl, 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_InfoBar(infoBar), m_MediaCtrl(mediaCtrl),
+ m_ConfigFilepath(configFilepath), m_DatabaseFilepath(databaseFilepath)
+{
+ this->SetDoubleBuffered(true);
+
+ Bind(wxEVT_PAINT, &WaveformViewer::OnPaint, this);
+ Bind(wxEVT_MOTION, &WaveformViewer::OnMouseMotion, this);
+ Bind(wxEVT_LEFT_DOWN, &WaveformViewer::OnMouseLeftButtonDown, this);
+ Bind(wxEVT_LEFT_UP, &WaveformViewer::OnMouseLeftButtonUp, this);
+ // Bind(wxEVT_KEY_DOWN, &WaveformViewer::OnControlKeyDown, this);
+ Bind(wxEVT_KEY_UP, &WaveformViewer::OnControlKeyUp, 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);
+
+ UpdateWaveformBitmap();
+
+ bBitmapDirty = false;
+ }
+
+ dc.DrawBitmap(m_WaveformBitmap, 0, 0, false);
+
+ RenderPlayhead(dc);
+
+ // Draw selection range
+ if (bSelectRange)
+ {
+ wxRect rect(m_CurrentPoint, m_AnchorPoint);
+
+ dc.SetPen(wxPen(wxColour(200, 200, 200), 2, wxPENSTYLE_SOLID));
+ dc.SetBrush(wxBrush(wxColour(200, 200, 200, 80), wxBRUSHSTYLE_SOLID));
+ dc.DrawRectangle(rect);
+ }
+
+ // Draw selected area
+ if (!bSelectRange && bDrawSelectedArea && !bBitmapDirty)
+ {
+ dc.SetPen(wxPen(wxColour(200, 200, 200, 255), 4, wxPENSTYLE_SOLID));
+ dc.SetBrush(wxBrush(wxColour(200, 200, 200, 80), wxBRUSHSTYLE_SOLID));
+ dc.DrawRectangle(wxRect(m_AnchorPoint.x, -2, m_CurrentPoint.x - m_AnchorPoint.x, this->GetSize().GetHeight() + 5));
+
+ bAreaSelected = true;
+ SendLoopPoints();
+ }
+ else
+ bAreaSelected = false;
+}
+
+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);
+
+ 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;
+
+ // TODO, FIXME: Don't reload file on every window resize
+ snd_file.read(&sample.at(0), frames * channels);
+
+ float display_width = this->GetSize().GetWidth();
+ float display_height = this->GetSize().GetHeight();
+
+ wxLogDebug("Calculating Waveform bars RMS..");
+
+ float chunk_size = (float)(frames) / (float)display_width;
+ int number_of_chunks = static_cast(static_cast(frames) / chunk_size);
+
+ // Start with low non-zero value
+ float normalize = 0.00001;
+
+ for (int i = 0; i < number_of_chunks; i++)
+ {
+ double sum = 0, mono = 0;
+
+ int start_point = static_cast(i * chunk_size * channels);
+
+ // Iterate on the chunk, get the square of sum of monos
+ for (int j = 0; j < chunk_size; j++)
+ {
+ if (channels == 2)
+ mono = 0.5f * (sample[start_point + (2 * j)] + sample[start_point + (2 * j) + 1]);
+ else
+ mono = sample[start_point + j];
+
+ sum += mono * mono; // Square
+ }
+
+ sum /= chunk_size; // Mean
+ sum = pow(sum, 0.5); // Root
+
+ // We might bleed a bit on the end and get some near infs, dunno
+ // what is causing astronomically big numbers from sample[]
+ if ((sum < 200.0) && (sum > normalize))
+ normalize = sum;
+
+ waveform.push_back(sum);
+ }
+
+ // Actually normalize
+ for (int i = 0; i < waveform.size(); i++)
+ waveform[i] /= normalize;
+
+ // Draw code
+ wxMemoryDC mdc(m_WaveformBitmap);
+
+ mdc.SetBackground(wxBrush(wxColour(0, 0, 0, 150), wxBRUSHSTYLE_SOLID));
+ mdc.Clear();
+
+ m_WaveformColour = serializer.DeserializeWaveformColour();
+
+ mdc.SetPen(wxPen(wxColour(m_WaveformColour), 2, wxPENSTYLE_SOLID));
+
+ wxLogDebug("Drawing bitmap..");
+
+ for (int i = 0; i < waveform.size() - 1; i++)
+ {
+ float half_display_height = static_cast(display_height) / 2.0f;
+
+ // X is percentage of i relative to waveform.size() multiplied by
+ // the width, Y is the half height times the value up or down
+ float X = display_width * ((float)i / waveform.size());
+ float Y = waveform[i] * half_display_height;
+
+ mdc.DrawLine(X, half_display_height + Y, X, half_display_height - Y);
+ }
+
+ wxLogDebug("Done drawing bitmap..");
+}
+
+void WaveformViewer::OnControlKeyDown(wxKeyEvent &event)
+{
+ switch (event.GetKeyCode())
+ {
+ case WXK_CONTROL:
+ SetCursor(wxCURSOR_IBEAM);
+ break;
+ default:
+ SetCursor(wxCURSOR_ARROW);
+ break;
+ }
+
+ event.Skip();
+}
+
+void WaveformViewer::OnControlKeyUp(wxKeyEvent &event)
+{
+ switch (event.GetKeyCode())
+ {
+ case WXK_CONTROL:
+ if (bSelectRange)
+ {
+ SetCursor(wxCURSOR_ARROW);
+ bSelectRange = false;
+ bDrawSelectedArea = false;
+ ReleaseMouse();
+ return;
+ }
+ break;
+ default:
+ break;
+ }
+
+ event.Skip();
+}
+
+void WaveformViewer::OnMouseMotion(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;
+
+ if (abs(pos.x - line_pos) <= 5 && pos.y <= 5)
+ {
+ SetCursor(wxCursor(wxCURSOR_HAND));
+ wxLogDebug("Cursor on playhead..");
+ }
+ else if (bSelectRange)
+ {
+ m_CurrentPoint = wxPoint(pos.x , pos.y);
+
+ Refresh();
+
+ wxLogDebug("CTRL pressed, pressing LMB will draw selection range at %d, %d", pos.x, pos.y);
+ }
+ else
+ return;
+}
+
+void WaveformViewer::OnMouseLeftButtonDown(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)
+ {
+ SetCursor(wxCURSOR_CLOSED_HAND);
+ CaptureMouse();
+
+ wxLogDebug("Mouse Captured playhead..");
+ }
+ else if (event.ControlDown())
+ {
+ wxLogDebug("LMB pressed");
+
+ SetCursor(wxCURSOR_CLOSED_HAND);
+ CaptureMouse();
+
+ bSelectRange = true;
+
+ m_AnchorPoint = wxPoint(pos.x, pos.y);
+ m_CurrentPoint = m_AnchorPoint;
+ }
+ else
+ {
+ SetCursor(wxCURSOR_ARROW);
+ return;
+ }
+
+ event.Skip();
+}
+
+void WaveformViewer::OnMouseLeftButtonUp(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;
+
+ if (!wxWindow::HasCapture())
+ {
+ wxLogDebug("Window doesn't have capture skipping..");
+ return;
+ }
+
+ if (bSelectRange)
+ {
+ wxLogDebug("LMB released");
+
+ m_CurrentPoint = wxPoint(pos.x, pos.y);
+
+ ReleaseMouse();
+ SetCursor(wxCURSOR_ARROW);
+
+ Refresh();
+
+ bSelectRange = false;
+
+ if (!bSelectRange)
+ bDrawSelectedArea = true;
+ }
+ else
+ {
+ ReleaseMouse();
+ SetCursor(wxCURSOR_ARROW);
+
+ m_MediaCtrl.Seek(seek_to, wxFromStart);
+ SendStatusBarStatus(wxString::Format(_("Now playing: %s"), selected), 1);
+ m_MediaCtrl.Play();
+ }
+}
+
+void WaveformViewer::ResetDC()
+{
+ bBitmapDirty = true;
+ bSelectRange = false;
+ bDrawSelectedArea = false;
+
+ Refresh();
+}
+
+void WaveformViewer::SendLoopPoints()
+{
+ wxLogDebug("%s Called", __FUNCTION__);
+
+ SampleHive::SH_LoopPointsEvent event(SampleHive::SH_EVT_LOOP_POINTS_UPDATED, this->GetId());
+ event.SetEventObject(this);
+
+ 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;
+
+ int panel_width = this->GetSize().GetWidth();
+
+ int a = m_AnchorPoint.x, b = m_CurrentPoint.x;
+
+ double loopA = ((double)a / panel_width) * length;
+ double loopB = ((double)b / panel_width) * length;
+
+ event.SetLoopPoints({ loopA, loopB });
+
+ HandleWindowEvent(event);
+
+ wxLogDebug("%s processed event, sending loop points..", __FUNCTION__);
+}
+
+void WaveformViewer::SendStatusBarStatus(const wxString& msg, int section)
+{
+ SampleHive::SH_SetStatusBarMessageEvent event(SampleHive::SH_EVT_STATUSBAR_MESSAGE_UPDATED, this->GetId());
+ event.SetEventObject(this);
+
+ event.SetMessageAndSection({ msg, section });
+
+ HandleWindowEvent(event);
+}
diff --git a/src/WaveformViewer.hpp b/src/WaveformViewer.hpp
new file mode 100644
index 0000000..a1f6a67
--- /dev/null
+++ b/src/WaveformViewer.hpp
@@ -0,0 +1,97 @@
+/* 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
+#include
+
+class WaveformViewer : public wxPanel
+{
+ public:
+ WaveformViewer(wxWindow* parentFrame, wxWindow* window, wxDataViewListCtrl& library,
+ wxMediaCtrl& mediaCtrl, 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;
+
+ const std::string& m_ConfigFilepath;
+ const std::string& m_DatabaseFilepath;
+
+ private:
+ // -------------------------------------------------------------------
+ wxBitmap m_WaveformBitmap;
+ wxColour m_PlayheadColour;
+ wxColour m_WaveformColour;
+
+ // -------------------------------------------------------------------
+ // Selection area coordinates
+ wxPoint m_AnchorPoint;
+ wxPoint m_CurrentPoint;
+
+ private:
+ // -------------------------------------------------------------------
+ bool bBitmapDirty = false;
+ bool bSelectRange = false;
+ bool bDrawSelectedArea = false;
+ bool bAreaSelected = false;
+
+ private:
+ // -------------------------------------------------------------------
+ void OnPaint(wxPaintEvent& event);
+ void RenderPlayhead(wxDC& dc);
+ void UpdateWaveformBitmap();
+
+ // -------------------------------------------------------------------
+ void OnMouseMotion(wxMouseEvent& event);
+ void OnMouseLeftButtonDown(wxMouseEvent& event);
+ void OnMouseLeftButtonUp(wxMouseEvent& event);
+
+ // -------------------------------------------------------------------
+ void OnControlKeyUp(wxKeyEvent& event);
+ void OnControlKeyDown(wxKeyEvent& event);
+
+ // -------------------------------------------------------------------
+ // Send custom events
+ void SendLoopPoints();
+ void SendStatusBarStatus(const wxString& msg, int section);
+
+ public:
+ // -------------------------------------------------------------------
+ void ResetDC();
+};
diff --git a/subprojects/taglib.wrap b/subprojects/taglib.wrap
new file mode 100644
index 0000000..503feef
--- /dev/null
+++ b/subprojects/taglib.wrap
@@ -0,0 +1,3 @@
+[wrap-git]
+url = https://github.com/taglib/taglib.git
+revision = master
diff --git a/subprojects/wxwidgets.wrap b/subprojects/wxwidgets.wrap
new file mode 100644
index 0000000..301cf22
--- /dev/null
+++ b/subprojects/wxwidgets.wrap
@@ -0,0 +1,3 @@
+[wrap-git]
+url = https://github.com/wxWidgets/wxWidgets
+revision = master