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