From 67b8c7fa2b57f2f5ad08d1ba136c6c66227389c5 Mon Sep 17 00:00:00 2001 From: =?utf8?q?Jarom=C3=ADr=20Mike=C5=A1?= Date: Fri, 2 Sep 2016 01:34:23 +0200 Subject: [PATCH] New upstream version 0.13.0~dfsg1 --- .travis.yml | 14 +- ChangeLog | 18 ++ Makefile.am | 39 +++- configure.ac | 4 +- src/core/channel.h | 10 +- src/core/conf.cpp | 54 +++-- src/core/conf.h | 7 +- src/core/const.h | 19 +- src/core/midiChannel.h | 10 +- src/core/midiMapConf.cpp | 2 +- src/core/mixerHandler.cpp | 3 +- src/core/patch_DEPR_.cpp | 12 +- src/core/plugin.cpp | 19 +- src/core/plugin.h | 34 +-- src/core/pluginHost.cpp | 8 +- src/core/pluginHost.h | 11 +- src/core/recorder.cpp | 80 +++---- src/core/recorder.h | 28 +-- src/core/sampleChannel.cpp | 11 +- src/core/wave.cpp | 20 +- src/core/wave.h | 11 +- src/glue/glue.cpp | 4 +- src/glue/storage.cpp | 188 ++++++++++++--- src/glue/storage.h | 8 +- src/gui/dialogs/gd_about.cpp | 16 +- src/gui/dialogs/gd_actionEditor.cpp | 44 ++-- src/gui/dialogs/gd_browser.cpp | 312 ++++++++++--------------- src/gui/dialogs/gd_browser.h | 123 ++++++---- src/gui/dialogs/gd_config.cpp | 8 +- src/gui/dialogs/gd_editor.cpp | 4 +- src/gui/dialogs/gd_mainWindow.cpp | 18 +- src/gui/dialogs/gd_pluginList.cpp | 32 --- src/gui/dialogs/gd_pluginWindowGUI.cpp | 17 +- src/gui/elems/ge_actionChannel.cpp | 262 ++++++++++----------- src/gui/elems/ge_actionChannel.h | 21 +- src/gui/elems/ge_browser.cpp | 310 +++++++----------------- src/gui/elems/ge_browser.h | 57 +++-- src/gui/elems/ge_mixed.cpp | 5 +- src/gui/elems/ge_sampleChannel.cpp | 18 +- src/utils/log.cpp | 6 +- src/utils/string.cpp | 56 +++++ src/utils/string.h | 45 ++++ src/utils/utils.cpp | 48 ++-- src/utils/utils.h | 10 +- tests/resources/test.wav | Bin 0 -> 81316 bytes tests/wave.cpp | 14 +- 46 files changed, 1079 insertions(+), 961 deletions(-) create mode 100644 src/utils/string.cpp create mode 100644 src/utils/string.h create mode 100644 tests/resources/test.wav diff --git a/.travis.yml b/.travis.yml index 8ea13bf..5c770bc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -14,10 +14,12 @@ notifications: on_failure: always before_install: + - sudo add-apt-repository ppa:ubuntu-toolchain-r/test -y # for gcc 6 - sudo apt-get update -qq - - sudo apt-get install -y libsndfile1-dev libsamplerate0-dev libfltk1.3-dev libasound2-dev libxpm-dev libpulse-dev libjack-dev libxrandr-dev libx11-dev libxinerama-dev libxcursor-dev + - sudo apt-get install -y gcc-6 g++-6 libsndfile1-dev libsamplerate0-dev libfltk1.3-dev libasound2-dev libxpm-dev libpulse-dev libjack-dev libxrandr-dev libx11-dev libxinerama-dev libxcursor-dev before_script: + - sudo ln -f -s /usr/bin/g++-6 /usr/bin/g++ # Download and build latest version of RtMidi @@ -34,10 +36,6 @@ before_script: - sudo ldconfig - cd .. - # Download wav file for testing purposes - - - wget http://download.wavetlan.com/SVV/Media/HTTP/WAV/Media-Convert/Media-Convert_test3_PCM_Stereo_VBR_16SS_11025Hz.wav -O test.wav - # Download midimaps package for testing purposes - wget https://github.com/monocasual/giada-midimaps/archive/master.zip -O giada-midimaps-master.zip @@ -47,9 +45,9 @@ before_script: # Download vst plugin for testing purposes - - wget 'http://downloads.sourceforge.net/project/distrho/2014-08-26/dexed-linux64bit-fixed.tar.xz?r=http%3A%2F%2Fdistrho.sourceforge.net%2Fports&ts=1454876566&use_mirror=freefr' -O dexed.tar.xz - - tar xf dexed.tar.xz dexed-linux64bit/Dexed.so - - cp dexed-linux64bit/Dexed.so . + - wget http://www.discodsp.com/download/?id=18 -O bliss-linux.zip + - unzip bliss-linux.zip -d bliss-linux + - cp bliss-linux/64-bit/Bliss64Demo.so . # Build Giada. Note the CFLAGS: optimization levels are set to O1 in order # not to throw up nasty errors from JUCE modules. diff --git a/ChangeLog b/ChangeLog index c39f012..abcbc3f 100644 --- a/ChangeLog +++ b/ChangeLog @@ -12,6 +12,24 @@ -------------------------------------------------------------------------------- +0.13.0 --- +- Deep file browser refactoring +- Save browser's scroll position and last item selected on opening +- Load patches/projects/samples on double click +- 64 bit builds for Windows +- Prevent deprecated patch from crashing if a plugin is not found in the stack +- Force logger to flush to file on Windows +- Add more default values for windows' dimensions and positions +- Avoid crashes on Configuration panel if no midimaps were selected +- Fix missing keyRelease actions in action editor +- Update JUCE to version 4.2.3 +- Don't include JUCE on tests without VST support (GitHub #75) +- Fix compilation errors on GCC 6 (GitHub #82) +- Fix wrong channel's actions count that prevented "R" button to be toggled + properly +- Fixed a bug that prevented actions on frame 0 to being properly reproduced + + 0.12.2 --- 2016 . 06 . 02 - Update RtAudio to version 4.1.2 - Add WASAPI support on Windows diff --git a/Makefile.am b/Makefile.am index 5d43fcd..a78f094 100644 --- a/Makefile.am +++ b/Makefile.am @@ -135,7 +135,9 @@ src/utils/gui_utils.h \ src/utils/gui_utils.cpp \ src/utils/gvector.h \ src/utils/utils.h \ -src/utils/utils.cpp +src/utils/utils.cpp \ +src/utils/string.h \ +src/utils/string.cpp if WITH_VST giada_SOURCES += \ @@ -164,8 +166,17 @@ giada_CPPFLAGS = # if WITH_VST -giada_CPPFLAGS += -I./src/deps/juce -I./src/deps/vst -I/usr/include \ - -I/usr/include/freetype2 +giada_CPPFLAGS += \ + -I./src/deps/juce \ + -I./src/deps/vst \ + -I/usr/include \ + -I/usr/include/freetype2 \ + -DJUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1 \ + -DJUCE_STANDALONE_APPLICATION=1 \ + -DJUCE_PLUGINHOST_VST=1 \ + -DJUCE_PLUGINHOST_VST3=0 \ + -DJUCE_PLUGINHOST_AU=0 +giada_CXXFLAGS += -Wno-error=misleading-indentation endif if LINUX @@ -182,7 +193,7 @@ if WINDOWS giada_LDADD = -lrtaudio -ldsound -lwsock32 -lm -lfltk -lwininet -lgdi32 \ -lshell32 -lvfw32 -lrpcrt4 -luuid -lcomctl32 -lole32 -lws2_32 -lsndfile \ -lsamplerate -lrtmidi -lwinmm -lsetupapi -lksuser -lpthreadGC2 -ljansson \ - -limm32 -lglu32 -lshell32 -lversion -lopengl32 -loleaut32 -lshlwapi + -limm32 -lglu32 -lshell32 -lversion -lopengl32 -loleaut32 -lshlwapi -lcomdlg32 giada_LDFLAGS = -mwindows -static giada_SOURCES += resource.rc endif @@ -227,7 +238,10 @@ src/core/pluginHost.cpp \ src/core/dataStorageIni.cpp \ src/core/dataStorageJson.cpp \ src/utils/utils.cpp \ -src/utils/log.cpp \ +src/utils/log.cpp + +if WITH_VST +giada_tests_SOURCES += \ src/deps/juce/juce_audio_basics/juce_audio_basics.cpp \ src/deps/juce/juce_audio_processors/juce_audio_processors.cpp \ src/deps/juce/juce_core/juce_core.cpp \ @@ -236,6 +250,7 @@ src/deps/juce/juce_events/juce_events.cpp \ src/deps/juce/juce_graphics/juce_graphics.cpp \ src/deps/juce/juce_gui_basics/juce_gui_basics.cpp \ src/deps/juce/juce_gui_extra/juce_gui_extra.cpp +endif giada_tests_LDADD = -ljansson -lsndfile -lsamplerate -lfltk -lXext -lX11 -lXft \ -lXpm -lm -ljack -lasound -lpthread -ldl -lpulse-simple -lpulse -lrtmidi \ @@ -243,8 +258,18 @@ giada_tests_LDADD = -ljansson -lsndfile -lsamplerate -lfltk -lXext -lX11 -lXft \ giada_tests_CXXFLAGS = -std=c++11 -giada_tests_CPPFLAGS = -I./src/deps/juce -I./src/deps/vst -I/usr/include \ - -I/usr/include/freetype2 +if WITH_VST +giada_tests_CPPFLAGS = \ + -I./src/deps/juce \ + -I./src/deps/vst \ + -I/usr/include \ + -I/usr/include/freetype2 \ + -DJUCE_GLOBAL_MODULE_SETTINGS_INCLUDED=1 \ + -DJUCE_STANDALONE_APPLICATION=1 \ + -DJUCE_PLUGINHOST_VST=1 \ + -DJUCE_PLUGINHOST_VST3=0 \ + -DJUCE_PLUGINHOST_AU=0 +endif # make rename ------------------------------------------------------------------ diff --git a/configure.ac b/configure.ac index 8257cf4..16fd0c3 100644 --- a/configure.ac +++ b/configure.ac @@ -4,7 +4,7 @@ # prereq & init AC_PREREQ(2.60) -AC_INIT([giada], [0.12], [giadaloopmachine@gmail.com]) +AC_INIT([giada], [0.13], [giadaloopmachine@gmail.com]) AC_CONFIG_SRCDIR([src/main.cpp]) AM_INIT_AUTOMAKE([subdir-objects]) @@ -60,7 +60,7 @@ AC_ARG_ENABLE( # test if files needed for Travis CI are present. If so, define a new macro # RUN_TESTS_WITH_LOCAL_FILES used during the test suite -if test -f "./test.wav" && test -f "giada-midimaps-master.zip" && test -f "dexed.tar.xz" ; then +if test -f "giada-midimaps-master.zip" && test -f "dexed.tar.xz" ; then AC_DEFINE(RUN_TESTS_WITH_LOCAL_FILES) fi diff --git a/src/core/channel.h b/src/core/channel.h index 201c6a4..e0ee899 100644 --- a/src/core/channel.h +++ b/src/core/channel.h @@ -38,15 +38,7 @@ #include "recorder.h" #ifdef WITH_VST -// TODO - can we move this stuff to a separate file? -#include "../deps/juce/juce_audio_basics/juce_audio_basics.h" -#include "../deps/juce/juce_audio_processors/juce_audio_processors.h" -#include "../deps/juce/juce_core/juce_core.h" -#include "../deps/juce/juce_data_structures/juce_data_structures.h" -#include "../deps/juce/juce_events/juce_events.h" -#include "../deps/juce/juce_graphics/juce_graphics.h" -#include "../deps/juce/juce_gui_basics/juce_gui_basics.h" -#include "../deps/juce/juce_gui_extra/juce_gui_extra.h" + #include "../deps/juce/config.h" #endif using std::vector; diff --git a/src/core/conf.cpp b/src/core/conf.cpp index 8475a8a..eadbce2 100644 --- a/src/core/conf.cpp +++ b/src/core/conf.cpp @@ -44,8 +44,8 @@ Conf::Conf() #if defined(__linux__) || defined(__APPLE__) - confFilePath = gGetHomePath() + gGetSlash() + CONF_FILENAME; - confDirPath = gGetHomePath() + gGetSlash(); + confFilePath = gGetHomePath() + G_SLASH + CONF_FILENAME; + confDirPath = gGetHomePath() + G_SLASH; #elif defined(_WIN32) @@ -102,12 +102,12 @@ void Conf::init() limitOutput = false; rsmpQuality = 0; - midiPortIn = DEFAULT_MIDI_PORT_IN; - noNoteOff = false; - midiMapPath = ""; - midiPortOut = DEFAULT_MIDI_PORT_OUT; - midiSync = MIDI_SYNC_NONE; - midiTCfps = 25.0f; + midiPortIn = DEFAULT_MIDI_PORT_IN; + noNoteOff = false; + midiMapPath = ""; + midiPortOut = DEFAULT_MIDI_PORT_OUT; + midiSync = MIDI_SYNC_NONE; + midiTCfps = 25.0f; midiInRewind = 0x0; midiInStartStop = 0x0; @@ -129,24 +129,40 @@ void Conf::init() resizeRecordings = true; - actionEditorZoom = 100; - actionEditorGridOn = false; - actionEditorGridVal = 1; - mainWindowX = 0; mainWindowY = 0; mainWindowW = GUI_WIDTH; mainWindowH = GUI_HEIGHT; + browserX = 0; + browserY = 0; + browserW = 640; + browserH = 480; + browserPosition = 0; + browserLastValue = 0; + + actionEditorX = 0; + actionEditorY = 0; + actionEditorW = 640; + actionEditorH = 480; + actionEditorZoom = 100; + actionEditorGridOn = false; + actionEditorGridVal = 1; + + sampleEditorX = 0; + sampleEditorY = 0; + sampleEditorW = 640; + sampleEditorH = 480; + pianoRollY = -1; pianoRollH = 422; #ifdef WITH_VST - pluginChooserX = 0; - pluginChooserY = 0; - pluginChooserW = 640; - pluginChooserH = 480; + pluginChooserX = 0; + pluginChooserY = 0; + pluginChooserW = 640; + pluginChooserH = 480; pluginSortMethod = 0; #endif @@ -216,6 +232,9 @@ int Conf::read() if (!setInt(jRoot, CONF_KEY_BROWSER_Y, browserY)) return 0; if (!setInt(jRoot, CONF_KEY_BROWSER_W, browserW)) return 0; if (!setInt(jRoot, CONF_KEY_BROWSER_H, browserH)) return 0; + if (!setInt(jRoot, CONF_KEY_BROWSER_POSITION, browserPosition)) return 0; + if (!setString(jRoot, CONF_KEY_BROWSER_LAST_PATH, browserLastPath)) return 0; + if (!setInt(jRoot, CONF_KEY_BROWSER_LAST_VALUE, browserLastValue)) return 0; if (!setInt(jRoot, CONF_KEY_ACTION_EDITOR_X, actionEditorX)) return 0; if (!setInt(jRoot, CONF_KEY_ACTION_EDITOR_Y, actionEditorY)) return 0; if (!setInt(jRoot, CONF_KEY_ACTION_EDITOR_W, actionEditorW)) return 0; @@ -314,6 +333,9 @@ int Conf::write() json_object_set_new(jRoot, CONF_KEY_BROWSER_Y, json_integer(browserY)); json_object_set_new(jRoot, CONF_KEY_BROWSER_W, json_integer(browserW)); json_object_set_new(jRoot, CONF_KEY_BROWSER_H, json_integer(browserH)); + json_object_set_new(jRoot, CONF_KEY_BROWSER_POSITION, json_integer(browserPosition)); + json_object_set_new(jRoot, CONF_KEY_BROWSER_LAST_PATH, json_string(browserLastPath.c_str())); + json_object_set_new(jRoot, CONF_KEY_BROWSER_LAST_VALUE, json_integer(browserLastValue)); json_object_set_new(jRoot, CONF_KEY_ACTION_EDITOR_X, json_integer(actionEditorX)); json_object_set_new(jRoot, CONF_KEY_ACTION_EDITOR_Y, json_integer(actionEditorY)); json_object_set_new(jRoot, CONF_KEY_ACTION_EDITOR_W, json_integer(actionEditorW)); diff --git a/src/core/conf.h b/src/core/conf.h index 095440c..f96265d 100644 --- a/src/core/conf.h +++ b/src/core/conf.h @@ -109,13 +109,18 @@ public: string samplePath; int mainWindowX, mainWindowY, mainWindowW, mainWindowH; - int browserX, browserY, browserW, browserH; + + int browserX, browserY, browserW, browserH, browserPosition, browserLastValue; + string browserLastPath; + int actionEditorX, actionEditorY, actionEditorW, actionEditorH, actionEditorZoom; int actionEditorGridVal; int actionEditorGridOn; + int sampleEditorX, sampleEditorY, sampleEditorW, sampleEditorH; int sampleEditorGridVal; int sampleEditorGridOn; + int pianoRollY, pianoRollH; int pluginListX, pluginListY; int configX, configY; diff --git a/src/core/const.h b/src/core/const.h index c6e1101..90b362f 100644 --- a/src/core/const.h +++ b/src/core/const.h @@ -32,16 +32,24 @@ /* -- version --------------------------------------------------------------- */ -#define G_VERSION_STR "0.12.2" +#define G_VERSION_STR "0.13.0" #define G_APP_NAME "Giada" #define G_VERSION_MAJOR 0 -#define G_VERSION_MINOR 12 -#define G_VERSION_PATCH 2 +#define G_VERSION_MINOR 13 +#define G_VERSION_PATCH 0 #define CONF_FILENAME "giada.conf" #ifndef BUILD_DATE -# define BUILD_DATE __DATE__ + #define BUILD_DATE __DATE__ +#endif + +#ifdef _WIN32 + #define G_SLASH '\\' + #define G_SLASH_STR "\\" +#else + #define G_SLASH '/' + #define G_SLASH_STR "/" #endif @@ -420,6 +428,9 @@ const int MIDI_CHANS[16] = { #define CONF_KEY_BROWSER_Y "browser_y" #define CONF_KEY_BROWSER_W "browser_w" #define CONF_KEY_BROWSER_H "browser_h" +#define CONF_KEY_BROWSER_POSITION "browser_position" +#define CONF_KEY_BROWSER_LAST_PATH "browser_last_path" +#define CONF_KEY_BROWSER_LAST_VALUE "browser_last_value" #define CONF_KEY_ACTION_EDITOR_X "action_editor_x" #define CONF_KEY_ACTION_EDITOR_Y "action_editor_y" #define CONF_KEY_ACTION_EDITOR_W "action_editor_w" diff --git a/src/core/midiChannel.h b/src/core/midiChannel.h index e0f50b4..011ae2f 100644 --- a/src/core/midiChannel.h +++ b/src/core/midiChannel.h @@ -32,15 +32,7 @@ #ifdef WITH_VST -// TODO - can we move this stuff to a separate file? -#include "../deps/juce/juce_audio_basics/juce_audio_basics.h" -#include "../deps/juce/juce_audio_processors/juce_audio_processors.h" -#include "../deps/juce/juce_core/juce_core.h" -#include "../deps/juce/juce_data_structures/juce_data_structures.h" -#include "../deps/juce/juce_events/juce_events.h" -#include "../deps/juce/juce_graphics/juce_graphics.h" -#include "../deps/juce/juce_gui_basics/juce_gui_basics.h" -#include "../deps/juce/juce_gui_extra/juce_gui_extra.h" + #include "../deps/juce/config.h" #endif #include "channel.h" diff --git a/src/core/midiMapConf.cpp b/src/core/midiMapConf.cpp index bba90bd..41d3706 100644 --- a/src/core/midiMapConf.cpp +++ b/src/core/midiMapConf.cpp @@ -46,7 +46,7 @@ using std::vector; void MidiMapConf::init() { - midimapsPath = gGetHomePath() + gGetSlash() + "midimaps" + gGetSlash(); + midimapsPath = gGetHomePath() + G_SLASH + "midimaps" + G_SLASH; /* scan dir of midi maps and load the filenames into <>maps. */ diff --git a/src/core/mixerHandler.cpp b/src/core/mixerHandler.cpp index 5d3867b..f965e02 100644 --- a/src/core/mixerHandler.cpp +++ b/src/core/mixerHandler.cpp @@ -131,7 +131,8 @@ void mh_loadPatch_DEPR_(bool isProject, const char *projPath) int numChans = G_Patch_DEPR_.getNumChans(); for (int i=0; ireadPatch_DEPR_(samplePath.c_str(), i, &G_Patch_DEPR_, G_Conf.samplerate, G_Conf.rsmpQuality); } diff --git a/src/core/patch_DEPR_.cpp b/src/core/patch_DEPR_.cpp index e25874e..7c30284 100644 --- a/src/core/patch_DEPR_.cpp +++ b/src/core/patch_DEPR_.cpp @@ -552,11 +552,13 @@ int Patch_DEPR_::readPlugins() int nparam = atoi(getValue(tmp).c_str()); Plugin *pPlugin = G_PluginHost.getPluginByIndex(j, PluginHost::CHANNEL, ch); sprintf(tmp, "chan%d_p%dbypass", ch->index, j); - pPlugin->setBypass(atoi(getValue(tmp).c_str())); - for (int k=0; kindex, j, k); - float pval = atof(getValue(tmp).c_str()); - pPlugin->setParameter(k, pval); + if (pPlugin) { + pPlugin->setBypass(atoi(getValue(tmp).c_str())); + for (int k=0; kindex, j, k); + float pval = atof(getValue(tmp).c_str()); + pPlugin->setParameter(k, pval); + } } globalOut &= 1; } diff --git a/src/core/plugin.cpp b/src/core/plugin.cpp index 61f8261..740bb70 100644 --- a/src/core/plugin.cpp +++ b/src/core/plugin.cpp @@ -41,17 +41,24 @@ int Plugin::idGenerator = 1; /* -------------------------------------------------------------------------- */ -void Plugin::initEditor() +void Plugin::init() { + ui = NULL; + id = idGenerator++;; + bypass = false; + status = 1; + if (getActiveEditor() != NULL) { - gLog("[Plugin::initEditor] plugin has an already active editor!\n"); + gLog("[Plugin::init] plugin has an already active editor!\n"); return; } - ui = createEditor(); + ui = createEditorIfNeeded(); if (ui == NULL) { - gLog("[Plugin::initEditor] unable to create editor!\n"); + gLog("[Plugin::init] unable to create editor, the plugin might be GUI-less!\n"); return; } + + gLog("[Plugin::init] editor initialized and ready\n"); } @@ -65,7 +72,6 @@ void Plugin::showEditor(void *parent) return; } ui->setOpaque(true); - ui->setVisible(true); ui->addToDesktop(0, parent); } @@ -75,7 +81,7 @@ void Plugin::showEditor(void *parent) bool Plugin::isEditorOpen() { - return ui->isVisible(); + return ui->isVisible() && ui->isOnDesktop(); } @@ -95,7 +101,6 @@ void Plugin::closeEditor() { if (ui == NULL) return; - ui->setVisible(false); if (ui->isOnDesktop()) ui->removeFromDesktop(); } diff --git a/src/core/plugin.h b/src/core/plugin.h index 30b79e6..38e411c 100644 --- a/src/core/plugin.h +++ b/src/core/plugin.h @@ -32,14 +32,7 @@ #define __PLUGIN_H__ -#include "../deps/juce/juce_audio_basics/juce_audio_basics.h" -#include "../deps/juce/juce_audio_processors/juce_audio_processors.h" -#include "../deps/juce/juce_core/juce_core.h" -#include "../deps/juce/juce_data_structures/juce_data_structures.h" -#include "../deps/juce/juce_events/juce_events.h" -#include "../deps/juce/juce_graphics/juce_graphics.h" -#include "../deps/juce/juce_gui_basics/juce_gui_basics.h" -#include "../deps/juce/juce_gui_extra/juce_gui_extra.h" +#include "../deps/juce/config.h" using std::string; @@ -58,11 +51,7 @@ private: public: - /* initEditor - * Prepare plugin GUI. 'parent' is a void pointer to the parent window that - * will contain it. */ - - void initEditor(); + void init(); void showEditor(void *parent); @@ -80,17 +69,14 @@ public: string getUniqueId(); - inline void setId() { id = idGenerator++; } - inline int getId() { return id; } - inline bool getStatus() { return status; } - inline bool isBypassed() { return bypass; } - inline int getEditorW() { return ui->getWidth(); } - inline int getEditorH() { return ui->getHeight(); } - - inline void toggleBypass() { bypass = !bypass; } - inline void setStatus(int s) { status = s; } - inline void setBypass(bool b) { bypass = b; } - + int getId() { return id; } + bool getStatus() { return status; } + bool isBypassed() { return bypass; } + int getEditorW() { return ui->getWidth(); } + int getEditorH() { return ui->getHeight(); } + void toggleBypass() { bypass = !bypass; } + void setStatus(int s) { status = s; } + void setBypass(bool b) { bypass = b; } }; #endif diff --git a/src/core/pluginHost.cpp b/src/core/pluginHost.cpp index 0f7b60b..9f31f2e 100644 --- a/src/core/pluginHost.cpp +++ b/src/core/pluginHost.cpp @@ -62,7 +62,7 @@ void PluginHost::init(int _buffersize, int _samplerate) buffersize = _buffersize; missingPlugins = false; //unknownPluginList.empty(); - loadList(gGetHomePath() + gGetSlash() + "plugins.xml"); + loadList(gGetHomePath() + G_SLASH + "plugins.xml"); } @@ -151,8 +151,10 @@ Plugin *PluginHost::addPlugin(const string &fid, int stackType, return NULL; } - p->setStatus(1); - p->setId(); + //p->setStatus(1); + //p->setId(); + //p->initEditor(); + p->init(); p->prepareToPlay(samplerate, buffersize); gLog("[PluginHost::addPlugin] plugin instance with fid=%s created\n", fid.c_str()); diff --git a/src/core/pluginHost.h b/src/core/pluginHost.h index feb7f38..ac741e2 100644 --- a/src/core/pluginHost.h +++ b/src/core/pluginHost.h @@ -33,16 +33,7 @@ #include -//#include "../deps/juce/AppConfig.h" -// TODO - can we move this stuff to a separate file? -#include "../deps/juce/juce_audio_basics/juce_audio_basics.h" -#include "../deps/juce/juce_audio_processors/juce_audio_processors.h" -#include "../deps/juce/juce_core/juce_core.h" -#include "../deps/juce/juce_data_structures/juce_data_structures.h" -#include "../deps/juce/juce_events/juce_events.h" -#include "../deps/juce/juce_graphics/juce_graphics.h" -#include "../deps/juce/juce_gui_basics/juce_gui_basics.h" -#include "../deps/juce/juce_gui_extra/juce_gui_extra.h" +#include "../deps/juce/config.h" using std::string; diff --git a/src/core/recorder.cpp b/src/core/recorder.cpp index 87a4791..53864b0 100644 --- a/src/core/recorder.cpp +++ b/src/core/recorder.cpp @@ -1,11 +1,11 @@ -/* --------------------------------------------------------------------- +/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * recorder * Action recorder. * - * --------------------------------------------------------------------- + * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual * @@ -25,7 +25,7 @@ * along with Giada - Your Hardcore Loopmachine. If not, see * . * - * ------------------------------------------------------------------ */ + * -------------------------------------------------------------------------- */ #include @@ -43,8 +43,6 @@ #include "../utils/utils.h" - - extern Mixer G_Mixer; extern Patch_DEPR_ f_patch; extern Conf G_Conf; @@ -62,7 +60,7 @@ bool sortedActions = false; composite cmp; -/* ------------------------------------------------------------------ */ +/* -------------------------------------------------------------------------- */ void init() @@ -73,7 +71,7 @@ void init() } -/* ------------------------------------------------------------------ */ +/* -------------------------------------------------------------------------- */ bool canRec(Channel *ch) @@ -90,7 +88,7 @@ bool canRec(Channel *ch) } -/* ------------------------------------------------------------------ */ +/* -------------------------------------------------------------------------- */ void rec(int index, int type, int frame, uint32_t iValue, float fValue) @@ -162,7 +160,7 @@ void rec(int index, int type, int frame, uint32_t iValue, float fValue) } -/* ------------------------------------------------------------------ */ +/* -------------------------------------------------------------------------- */ void clearChan(int index) @@ -175,12 +173,6 @@ void clearChan(int index) if (j == global.at(i).size()) break; // for each action j of frame i action *a = global.at(i).at(j); if (a->chan == index) { -#ifdef WITH_VST -#if 0 - if (a->type == ACTION_MIDI) - free(a->event); -#endif -#endif free(a); global.at(i).erase(global.at(i).begin() + j); } @@ -196,7 +188,7 @@ void clearChan(int index) } -/* ------------------------------------------------------------------ */ +/* -------------------------------------------------------------------------- */ void clearAction(int index, char act) @@ -217,14 +209,14 @@ void clearAction(int index, char act) } } Channel *ch = G_Mixer.getChannelByIndex(index); - ch->hasActions = false; /// FIXME - why this? Isn't it useless if we call chanHasActions? + ch->hasActions = false; /// FIXME - why this? Isn't it useless if we call setChanHasActionsStatus? optimize(); - chanHasActions(index); /// FIXME + setChanHasActionsStatus(index); /// FIXME //print(); } -/* ------------------------------------------------------------------ */ +/* -------------------------------------------------------------------------- */ void deleteAction(int chan, int frame, char type, bool checkValues, uint32_t iValue, float fValue) @@ -256,12 +248,6 @@ void deleteAction(int chan, int frame, char type, bool checkValues, uint32_t iVa while (lockStatus == 0) { lockStatus = pthread_mutex_trylock(&G_Mixer.mutex_recs); if (lockStatus == 0) { -#ifdef WITH_VST -#if 0 - if (type == ACTION_MIDI) - free(a->event); -#endif -#endif free(a); global.at(i).erase(global.at(i).begin() + j); pthread_mutex_unlock(&G_Mixer.mutex_recs); @@ -277,7 +263,7 @@ void deleteAction(int chan, int frame, char type, bool checkValues, uint32_t iVa } if (found) { optimize(); - chanHasActions(chan); + setChanHasActionsStatus(chan); gLog("[REC] action deleted, type=%d frame=%d chan=%d iValue=%d (%X) fValue=%f\n", type, frame, chan, iValue, iValue, fValue); } @@ -287,7 +273,7 @@ void deleteAction(int chan, int frame, char type, bool checkValues, uint32_t iVa } -/* ------------------------------------------------------------------ */ +/* -------------------------------------------------------------------------- */ void deleteActions(int chan, int frame_a, int frame_b, char type) @@ -304,7 +290,7 @@ void deleteActions(int chan, int frame_a, int frame_b, char type) } -/* ------------------------------------------------------------------ */ +/* -------------------------------------------------------------------------- */ void clearAll() @@ -336,7 +322,7 @@ void clearAll() } -/* ------------------------------------------------------------------ */ +/* -------------------------------------------------------------------------- */ void optimize() @@ -358,7 +344,7 @@ void optimize() } -/* ------------------------------------------------------------------ */ +/* -------------------------------------------------------------------------- */ void sortActions() @@ -376,7 +362,7 @@ void sortActions() } -/* ------------------------------------------------------------------ */ +/* -------------------------------------------------------------------------- */ void updateBpm(float oldval, float newval, int oldquanto) @@ -417,7 +403,7 @@ void updateBpm(float oldval, float newval, int oldquanto) } -/* ------------------------------------------------------------------ */ +/* -------------------------------------------------------------------------- */ void updateSamplerate(int systemRate, int patchRate) @@ -457,7 +443,7 @@ void updateSamplerate(int systemRate, int patchRate) } -/* ------------------------------------------------------------------ */ +/* -------------------------------------------------------------------------- */ void expand(int old_fpb, int new_fpb) @@ -488,7 +474,7 @@ void expand(int old_fpb, int new_fpb) } -/* ------------------------------------------------------------------ */ +/* -------------------------------------------------------------------------- */ void shrink(int new_fpb) @@ -515,26 +501,29 @@ void shrink(int new_fpb) } -/* ------------------------------------------------------------------ */ +/* -------------------------------------------------------------------------- */ -void chanHasActions(int index) +void setChanHasActionsStatus(int index) { Channel *ch = G_Mixer.getChannelByIndex(index); if (global.size() == 0) { ch->hasActions = false; return; } - for (unsigned i=0; ihasActions; i++) { - for (unsigned j=0; jhasActions; j++) { - if (global.at(i).at(j)->chan == index) + for (unsigned i=0; ichan == index) { ch->hasActions = true; + return; + } } } + ch->hasActions = false; } -/* ------------------------------------------------------------------ */ +/* -------------------------------------------------------------------------- */ int getNextAction(int chan, char type, int frame, action **out, uint32_t iValue) @@ -562,7 +551,7 @@ int getNextAction(int chan, char type, int frame, action **out, uint32_t iValue) } -/* ------------------------------------------------------------------ */ +/* -------------------------------------------------------------------------- */ int getAction(int chan, char action, int frame, struct action **out) @@ -580,7 +569,7 @@ int getAction(int chan, char action, int frame, struct action **out) } -/* ------------------------------------------------------------------ */ +/* -------------------------------------------------------------------------- */ void startOverdub(int index, char actionMask, int frame) @@ -622,7 +611,7 @@ void startOverdub(int index, char actionMask, int frame) } -/* ------------------------------------------------------------------ */ +/* -------------------------------------------------------------------------- */ void stopOverdub(int frame) @@ -651,9 +640,10 @@ void stopOverdub(int frame) /* remove any nested action between keypress----keyrel, then record */ - if (!nullLoop) + if (!nullLoop) { deleteActions(cmp.a2.chan, cmp.a1.frame, cmp.a2.frame, cmp.a1.type); deleteActions(cmp.a2.chan, cmp.a1.frame, cmp.a2.frame, cmp.a2.type); + } if (!ringLoop && !nullLoop) { rec(cmp.a2.chan, cmp.a2.type, cmp.a2.frame); @@ -673,7 +663,7 @@ void stopOverdub(int frame) } -/* ------------------------------------------------------------------ */ +/* -------------------------------------------------------------------------- */ void print() diff --git a/src/core/recorder.h b/src/core/recorder.h index 363323f..95a8b65 100644 --- a/src/core/recorder.h +++ b/src/core/recorder.h @@ -36,20 +36,6 @@ #include "const.h" #include "mixer.h" -#ifdef WITH_VST - -/* before including aeffetx(x).h we must define __cdecl, otherwise VST - * headers can't be compiled correctly. In windows __cdecl is already - * defined. */ - - #ifdef __GNUC__ - #ifndef _WIN32 - #define __cdecl - #endif - #endif - #include "../deps/vst/aeffectx.h" -#endif - using std::vector; @@ -61,14 +47,15 @@ using std::vector; * [global3]-->[vector<_action*>3]-->[a0][a1][a2] 3[frames4] * */ -namespace recorder { - +namespace recorder +{ /* action * struct containing fields to describe an atomic action. Note from * VST sdk: parameter values, like all VST parameters, are declared as * floats with an inclusive range of 0.0 to 1.0 (fValue). */ -struct action { +struct action +{ int chan; // channel index, i.e. Channel->index int type; int frame; // redundant info, used by helper functions @@ -80,7 +67,8 @@ struct action { * a group of two actions (keypress+keyrel, muteon+muteoff) used during * the overdub process */ -struct composite { +struct composite +{ action a1; action a2; }; @@ -97,11 +85,11 @@ extern bool sortedActions; // are actions sorted via sortAction void init(); -/* chanHasActions +/* setChanHasActionsStatus * Check if the channel has at least one action recorded. If false, sets * ch->hasActions = false. Used after an action deletion. */ -void chanHasActions(int chan); +void setChanHasActionsStatus(int chan); /* canRec * can a channel rec an action? Call this one BEFORE rec(). */ diff --git a/src/core/sampleChannel.cpp b/src/core/sampleChannel.cpp index 48ee17b..3b7d5e1 100644 --- a/src/core/sampleChannel.cpp +++ b/src/core/sampleChannel.cpp @@ -713,7 +713,7 @@ bool SampleChannel::allocEmpty(int frames, int samplerate, int takeId) return false; w->name = "TAKE-" + gItoa(takeId); - w->pathfile = gGetCurrentPath() + gGetSlash() + w->name; + w->pathfile = gGetCurrentPath() + G_SLASH + w->name; wave = w; status = STATUS_OFF; begin = 0; @@ -975,11 +975,16 @@ void SampleChannel::start(int frame, bool doQuantize, int quantize, else { /* fillChan only if frame != 0. If you call fillChan on frame == 0 - * a duplicate call to fillChan occurs with loss of data. */ + a duplicate call to fillChan occurs with loss of data. Yeah, but + what happens if an action really occurs on frame 0 (and it happens, + for example when you start the sequencer from the firt beat)? Cheat + time! Shift a little bit the frame, so that it's no longer zero. */ status = STATUS_PLAY; sendMidiLplay(); - if (frame != 0) + if (frame == 0) + frame = 2; + //if (frame != 0) tracker = fillChan(vChan, tracker, frame); } } diff --git a/src/core/wave.cpp b/src/core/wave.cpp index 28b3a42..4bdffc9 100644 --- a/src/core/wave.cpp +++ b/src/core/wave.cpp @@ -33,8 +33,12 @@ #include #include "../utils/utils.h" #include "../utils/log.h" -#include "wave.h" #include "init.h" +#include "const.h" +#include "wave.h" + + +using std::string; Wave::Wave() @@ -239,12 +243,12 @@ int Wave::resample(int quality, int newRate) /* -------------------------------------------------------------------------- */ -std::string Wave::basename() const +string Wave::basename(bool ext) const { - return gStripExt(gBasename(pathfile.c_str()).c_str()); + return ext ? gBasename(pathfile) : gStripExt(gBasename(pathfile)); } -std::string Wave::extension() const +string Wave::extension() const { return gGetExt(pathfile.c_str()); } @@ -255,10 +259,10 @@ std::string Wave::extension() const void Wave::updateName(const char *n) { - std::string ext = gGetExt(pathfile.c_str()); - name = gStripExt(gBasename(n).c_str()); - pathfile = gDirname(pathfile.c_str()) + gGetSlash() + name + "." + ext; - isLogical = true; + string ext = gGetExt(pathfile.c_str()); + name = gStripExt(gBasename(n).c_str()); + pathfile = gDirname(pathfile.c_str()) + G_SLASH + name + "." + ext; + isLogical = true; /* a wave with updated name must become logical, since the underlying * file does not exist yet. */ diff --git a/src/core/wave.h b/src/core/wave.h index 237e52f..f602667 100644 --- a/src/core/wave.h +++ b/src/core/wave.h @@ -36,6 +36,9 @@ #include +using std::string; + + class Wave { private: @@ -52,8 +55,8 @@ public: ~Wave(); Wave(const Wave &other); - std::string pathfile; // full path + sample name - std::string name; // sample name (changeable) + string pathfile; // full path + sample name + string name; // sample name (changeable) float *data; int size; // wave size (size in stereo: size / 2) @@ -67,8 +70,8 @@ public: inline void channels(int v) { inHeader.channels = v; } inline void frames (int v) { inHeader.frames = v; } - std::string basename () const; - std::string extension() const; + string basename (bool ext=false) const; + string extension() const; void updateName(const char *n); int open (const char *f); diff --git a/src/glue/glue.cpp b/src/glue/glue.cpp index 7acd5fb..ff1670c 100644 --- a/src/glue/glue.cpp +++ b/src/glue/glue.cpp @@ -711,7 +711,7 @@ void glue_setPanning(class gdEditor *win, SampleChannel *ch, float val) ch->panRight= 0.0f + val; char buf[8]; - sprintf(buf, "%d L", abs((ch->panRight * 100.0f) - 100)); + sprintf(buf, "%d L", (int) abs((ch->panRight * 100.0f) - 100)); win->panNum->value(buf); } else if (val == 1.0f) { @@ -724,7 +724,7 @@ void glue_setPanning(class gdEditor *win, SampleChannel *ch, float val) ch->panRight= 1.0f; char buf[8]; - sprintf(buf, "%d R", abs((ch->panLeft * 100.0f) - 100)); + sprintf(buf, "%d R", (int) abs((ch->panLeft * 100.0f) - 100)); win->panNum->value(buf); } win->panNum->redraw(); diff --git a/src/glue/storage.cpp b/src/glue/storage.cpp index daa2fc7..d517736 100644 --- a/src/glue/storage.cpp +++ b/src/glue/storage.cpp @@ -32,6 +32,7 @@ #include "../gui/elems/ge_keyboard.h" #include "../gui/dialogs/gd_mainWindow.h" #include "../gui/dialogs/gd_warnings.h" +#include "../gui/dialogs/gd_browser.h" #include "../core/mixer.h" #include "../core/mixerHandler.h" #include "../core/channel.h" @@ -62,17 +63,6 @@ extern PluginHost G_PluginHost; #endif -static void __glue_setProgressBar__(class gProgress *status, float v) -{ - status->value(status->value() + v); - //Fl::check(); - Fl::wait(0); -} - - -/* -------------------------------------------------------------------------- */ - - #ifdef WITH_VST static void __glue_fillPatchGlobalsPlugins__(vector *host, vector *patch) @@ -160,7 +150,8 @@ static void __glue_fillPatchGlobals__(const string &name) /* -------------------------------------------------------------------------- */ -int glue_savePatch(const string &fullPath, const string &name, bool isProject) +static bool __glue_savePatch__(const string &fullPath, const string &name, + bool isProject) { G_Patch.init(); @@ -170,38 +161,88 @@ int glue_savePatch(const string &fullPath, const string &name, bool isProject) if (G_Patch.write(fullPath)) { gu_update_win_label(name.c_str()); - gLog("[glue] patch saved as %s\n", fullPath.c_str()); - return 1; + gLog("[glue_savePatch] patch saved as %s\n", fullPath.c_str()); + return true; } - return 0; + return false; } /* -------------------------------------------------------------------------- */ -int glue_loadPatch(const string &fullPath, class gProgress *status, bool isProject) +void glue_savePatch(void *data) { - /* try to load the new JSON-based patch. If it fails, fall back to deprecated - * one. */ + gdSaveBrowser *browser = (gdSaveBrowser*) data; + string name = browser->getName(); + string fullPath = browser->getCurrentPath() + G_SLASH + gStripExt(name) + ".gptc"; + + if (name == "") { + gdAlert("Please choose a file name."); + return; + } + + if (gFileExists(fullPath.c_str())) + if (!gdConfirmWin("Warning", "File exists: overwrite?")) + return; + + if (__glue_savePatch__(fullPath, name, false)) { // false == not a project + G_Conf.patchPath = gDirname(fullPath); + browser->do_callback(); + } + else + gdAlert("Unable to save the patch!"); +} + + +/* -------------------------------------------------------------------------- */ + + +void glue_loadPatch(void *data) +{ + gdLoadBrowser *browser = (gdLoadBrowser*) data; + string fullPath = browser->getSelectedItem(); + bool isProject = gIsProject(browser->getSelectedItem()); + + browser->showStatusBar(); gLog("[glue] loading %s...\n", fullPath.c_str()); string fileToLoad = fullPath; // patch file to read from string basePath = ""; // base path, in case of reading from a project if (isProject) { - fileToLoad = fullPath + gGetSlash() + gStripExt(gBasename(fullPath)) + ".gptc"; - basePath = fullPath.c_str() + gGetSlash(); + fileToLoad = fullPath + G_SLASH + gStripExt(gBasename(fullPath)) + ".gptc"; + basePath = fullPath + G_SLASH; } - int res = G_Patch.read(fileToLoad); + /* try to load the new JSON-based patch. If it fails, fall back to deprecated + * one. */ + + int res = G_Patch.read(fileToLoad); + bool deprecated = false; if (res == PATCH_UNREADABLE) { gLog("[glue] failed reading JSON-based patch. Trying with the deprecated method\n"); - return glue_loadPatch__DEPR__(gBasename(fileToLoad).c_str(), fileToLoad.c_str(), status, isProject); + deprecated = true; + res = glue_loadPatch__DEPR__(gBasename(fileToLoad).c_str(), fileToLoad.c_str(), + browser->getStatusBar(), isProject); + } + + if (res != PATCH_READ_OK) { + if (res == PATCH_UNREADABLE) + isProject ? gdAlert("This project is unreadable.") : gdAlert("This patch is unreadable."); + else + if (res == PATCH_INVALID) + isProject ? gdAlert("This project is not valid.") : gdAlert("This patch is not valid."); + + browser->hideStatusBar(); + return; + } + else + if (deprecated) { + browser->do_callback(); + return; } - if (res != PATCH_READ_OK) - return res; /* close all other windows. This prevents segfault if plugin * windows GUIs are on. */ @@ -213,7 +254,8 @@ int glue_loadPatch(const string &fullPath, class gProgress *status, bool isProje glue_resetToInitState(false, false); - __glue_setProgressBar__(status, 0.1f); + browser->setStatusBar(0.1f); + //__glue_setProgressBar__(status, 0.1f); /* Add common stuff, columns and channels. Also increment the progress bar * by 0.8 / total_channels steps. */ @@ -229,7 +271,8 @@ int glue_loadPatch(const string &fullPath, class gProgress *status, bool isProje ch->readPatch(basePath, k, &G_Patch, &G_Mixer.mutex_plugins, G_Conf.samplerate, G_Conf.rsmpQuality); } - __glue_setProgressBar__(status, steps); + //__glue_setProgressBar__(status, steps); + browser->setStatusBar(steps); } } @@ -245,14 +288,15 @@ int glue_loadPatch(const string &fullPath, class gProgress *status, bool isProje /* save patchPath by taking the last dir of the broswer, in order to * reuse it the next time */ - G_Conf.patchPath = gDirname(fullPath.c_str()); + G_Conf.patchPath = gDirname(fullPath); /* refresh GUI */ gu_updateControls(); gu_update_win_label(G_Patch.name.c_str()); - __glue_setProgressBar__(status, 1.0f); + browser->setStatusBar(0.1f); + //__glue_setProgressBar__(status, 1.0f); gLog("[glue] patch loaded successfully\n"); @@ -263,7 +307,7 @@ int glue_loadPatch(const string &fullPath, class gProgress *status, bool isProje #endif - return res; + browser->do_callback(); } @@ -359,13 +403,28 @@ int glue_loadPatch__DEPR__(const char *fname, const char *fpath, gProgress *stat /* -------------------------------------------------------------------------- */ -int glue_saveProject(const string &folderPath, const string &projName) +void glue_saveProject(void *data) { - if (!gDirExists(folderPath.c_str()) && !gMkdir(folderPath.c_str())) { - gLog("[glue] unable to make project directory!\n"); - return 0; + gdSaveBrowser *browser = (gdSaveBrowser*) data; + string name = browser->getName(); + string folderPath = browser->getCurrentPath(); //browser->getSelectedItem(); + string fullPath = folderPath + G_SLASH + gStripExt(name) + ".gprj"; + + if (name == "") { + gdAlert("Please choose a project name."); + return; + } + + if (gIsProject(fullPath.c_str()) && !gdConfirmWin("Warning", "Project exists: overwrite?")) + return; + + if (!gDirExists(fullPath.c_str()) && !gMkdir(fullPath.c_str())) { + gLog("[glue_saveProject] unable to make project directory!\n"); + return; } + gLog("[glue_saveProject] project dir created: %s\n", fullPath.c_str()); + /* copy all samples inside the folder. Takes and logical ones are saved * via glue_saveSample() */ @@ -382,7 +441,7 @@ int glue_saveProject(const string &folderPath, const string &projName) /* update the new samplePath: everything now comes from the project * folder (folderPath). Also remove any existing file. */ - string samplePath = folderPath + gGetSlash() + ch->wave->basename() + "." + ch->wave->extension(); + string samplePath = fullPath + G_SLASH + ch->wave->basename(true); if (gFileExists(samplePath.c_str())) remove(samplePath.c_str()); @@ -390,8 +449,63 @@ int glue_saveProject(const string &folderPath, const string &projName) ch->wave->pathfile = samplePath; } - string gptcPath = folderPath + gGetSlash() + gStripExt(projName.c_str()) + ".gptc"; - glue_savePatch(gptcPath, projName, true); // true == it's a project + string gptcPath = fullPath + G_SLASH + gStripExt(name.c_str()) + ".gptc"; + if (__glue_savePatch__(gptcPath, name, true)) // true == it's a project + browser->do_callback(); + else + gdAlert("Unable to save the project!"); +} + + +/* -------------------------------------------------------------------------- */ + + +void glue_loadSample(void *data) +{ + gdLoadBrowser *browser = (gdLoadBrowser*) data; + string fullPath = browser->getSelectedItem(); + + if (fullPath.empty()) + return; + + int res = glue_loadChannel((SampleChannel*) browser->getChannel(), fullPath.c_str()); - return 1; + if (res == SAMPLE_LOADED_OK) { + G_Conf.samplePath = gDirname(fullPath); + browser->do_callback(); + mainWin->delSubWindow(WID_SAMPLE_EDITOR); // if editor is open + } + else + mainWin->keyboard->printChannelMessage(res); +} + + +/* -------------------------------------------------------------------------- */ + + +void glue_saveSample(void *data) +{ + gdSaveBrowser *browser = (gdSaveBrowser*) data; + string name = browser->getName(); + string folderPath = browser->getSelectedItem(); + + if (name == "") { + gdAlert("Please choose a file name."); + return; + } + + /* bruteforce check extension. */ + + string filePath = folderPath + G_SLASH + gStripExt(name) + ".wav"; + + if (gFileExists(filePath)) + if (!gdConfirmWin("Warning", "File exists: overwrite?")) + return; + + if (((SampleChannel*)browser->getChannel())->save(filePath.c_str())) { + G_Conf.samplePath = gDirname(folderPath); + browser->do_callback(); + } + else + gdAlert("Unable to save this sample!"); } diff --git a/src/glue/storage.h b/src/glue/storage.h index f7d05c1..98be8e8 100644 --- a/src/glue/storage.h +++ b/src/glue/storage.h @@ -40,9 +40,11 @@ using std::string; using std::vector; -int glue_loadPatch (const string &fullPath, class gProgress *status, bool isProject); +void glue_loadPatch (void *data); int glue_loadPatch__DEPR__(const char *fname, const char *fpath, class gProgress *status, bool isProject); -int glue_savePatch (const string &fullPath, const string &name, bool isProject); -int glue_saveProject(const string &folderPath, const string &projName); +void glue_savePatch (void *data); +void glue_saveProject(void *data); +void glue_saveSample (void *data); +void glue_loadSample (void *data); #endif diff --git a/src/gui/dialogs/gd_about.cpp b/src/gui/dialogs/gd_about.cpp index 246a36e..3e0e614 100644 --- a/src/gui/dialogs/gd_about.cpp +++ b/src/gui/dialogs/gd_about.cpp @@ -73,8 +73,13 @@ gdAbout::gdAbout() "Version " G_VERSION_STR " (" BUILD_DATE ")\n\n" "Developed by Monocasual\n" "Based on FLTK (%d.%d.%d), RtAudio (%s),\n" - "RtMidi (%s), libsamplerate, Jansson (%s) \n" - "and libsndfile\n\n" + "RtMidi (%s), Libsamplerate, Jansson (%s),\n" + "Libsndfile" +#ifdef WITH_VST + ", JUCE (%d.%d.%d)\n\n" +#else + "\n\n" +#endif "Released under the terms of the GNU General\n" "Public License (GPL v3)\n\n" "News, infos, contacts and documentation:\n" @@ -82,7 +87,12 @@ gdAbout::gdAbout() FL_MAJOR_VERSION, FL_MINOR_VERSION, FL_PATCH_VERSION, kernelAudio::getRtAudioVersion().c_str(), kernelMidi::getRtMidiVersion().c_str(), - JANSSON_VERSION); + JANSSON_VERSION +#ifdef WITH_VST + , JUCE_MAJOR_VERSION, JUCE_MINOR_VERSION, JUCE_BUILDNUMBER); +#else + ); +#endif int tw = 0; int th = 0; diff --git a/src/gui/dialogs/gd_actionEditor.cpp b/src/gui/dialogs/gd_actionEditor.cpp index 544e83f..ddfcdaf 100644 --- a/src/gui/dialogs/gd_actionEditor.cpp +++ b/src/gui/dialogs/gd_actionEditor.cpp @@ -1,10 +1,10 @@ -/* --------------------------------------------------------------------- +/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * gd_actionEditor * - * --------------------------------------------------------------------- + * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual * @@ -24,7 +24,7 @@ * along with Giada - Your Hardcore Loopmachine. If not, see * . * - * ------------------------------------------------------------------ */ + * -------------------------------------------------------------------------- */ #include @@ -147,7 +147,7 @@ gdActionEditor::gdActionEditor(Channel *chan) } -/* ------------------------------------------------------------------ */ +/* -------------------------------------------------------------------------- */ gdActionEditor::~gdActionEditor() { @@ -162,14 +162,14 @@ gdActionEditor::~gdActionEditor() { } -/* ------------------------------------------------------------------ */ +/* -------------------------------------------------------------------------- */ void gdActionEditor::cb_zoomIn(Fl_Widget *w, void *p) { ((gdActionEditor*)p)->__cb_zoomIn(); } void gdActionEditor::cb_zoomOut(Fl_Widget *w, void *p) { ((gdActionEditor*)p)->__cb_zoomOut(); } -/* ------------------------------------------------------------------ */ +/* -------------------------------------------------------------------------- */ void gdActionEditor::__cb_zoomIn() { @@ -209,7 +209,7 @@ void gdActionEditor::__cb_zoomIn() { } -/* ------------------------------------------------------------------ */ +/* -------------------------------------------------------------------------- */ void gdActionEditor::__cb_zoomOut() { @@ -245,7 +245,7 @@ void gdActionEditor::__cb_zoomOut() { } -/* ------------------------------------------------------------------ */ +/* -------------------------------------------------------------------------- */ void gdActionEditor::update() { @@ -258,7 +258,7 @@ void gdActionEditor::update() { } -/* ------------------------------------------------------------------ */ +/* -------------------------------------------------------------------------- */ int gdActionEditor::handle(int e) { @@ -274,7 +274,7 @@ int gdActionEditor::handle(int e) { } -/* ------------------------------------------------------------------ */ +/* -------------------------------------------------------------------------- */ int gdActionEditor::getActionType() { @@ -291,9 +291,9 @@ int gdActionEditor::getActionType() { } -/* ------------------------------------------------------------------ */ -/* ------------------------------------------------------------------ */ -/* ------------------------------------------------------------------ */ +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ gGridTool::gGridTool(int x, int y, gdActionEditor *parent) @@ -320,7 +320,7 @@ gGridTool::gGridTool(int x, int y, gdActionEditor *parent) } -/* ------------------------------------------------------------------ */ +/* -------------------------------------------------------------------------- */ gGridTool::~gGridTool() { @@ -329,13 +329,13 @@ gGridTool::~gGridTool() { } -/* ------------------------------------------------------------------ */ +/* -------------------------------------------------------------------------- */ void gGridTool::cb_changeType(Fl_Widget *w, void *p) { ((gGridTool*)p)->__cb_changeType(); } -/* ------------------------------------------------------------------ */ +/* -------------------------------------------------------------------------- */ void gGridTool::__cb_changeType() { @@ -344,7 +344,7 @@ void gGridTool::__cb_changeType() { } -/* ------------------------------------------------------------------ */ +/* -------------------------------------------------------------------------- */ bool gGridTool::isOn() { @@ -352,7 +352,7 @@ bool gGridTool::isOn() { } -/* ------------------------------------------------------------------ */ +/* -------------------------------------------------------------------------- */ int gGridTool::getValue() { @@ -370,7 +370,7 @@ int gGridTool::getValue() { } -/* ------------------------------------------------------------------ */ +/* -------------------------------------------------------------------------- */ void gGridTool::calc() { @@ -413,7 +413,7 @@ void gGridTool::calc() { } -/* ------------------------------------------------------------------ */ +/* -------------------------------------------------------------------------- */ int gGridTool::getSnapPoint(int v) { @@ -435,7 +435,7 @@ int gGridTool::getSnapPoint(int v) { } -/* ------------------------------------------------------------------ */ +/* -------------------------------------------------------------------------- */ int gGridTool::getSnapFrame(int v) { @@ -464,7 +464,7 @@ int gGridTool::getSnapFrame(int v) { } -/* ------------------------------------------------------------------ */ +/* -------------------------------------------------------------------------- */ int gGridTool::getCellSize() { diff --git a/src/gui/dialogs/gd_browser.cpp b/src/gui/dialogs/gd_browser.cpp index f427abb..448ad74 100644 --- a/src/gui/dialogs/gd_browser.cpp +++ b/src/gui/dialogs/gd_browser.cpp @@ -27,111 +27,65 @@ * -------------------------------------------------------------------------- */ -#include "../../core/mixer.h" #include "../../core/graphics.h" #include "../../core/wave.h" #include "../../core/channel.h" #include "../../core/sampleChannel.h" -#include "../../core/patch_DEPR_.h" -#include "../../core/patch.h" #include "../../core/conf.h" #include "../../glue/glue.h" #include "../../glue/channel.h" #include "../../glue/storage.h" +#include "../../utils/gui_utils.h" #include "../elems/ge_browser.h" #include "../elems/ge_channel.h" -#include "../elems/ge_keyboard.h" #include "gd_browser.h" -#include "gd_mainWindow.h" -#include "gd_warnings.h" using std::string; -extern Patch_DEPR_ G_Patch_DEPR_; -extern Patch G_Patch; -extern Conf G_Conf; -extern Mixer G_Mixer; -extern gdMainWindow *mainWin; +extern Conf G_Conf; -gdBrowser::gdBrowser(const char *title, const char *initPath, Channel *ch, int type, int stackType) - : gWindow (396, 302, title), - ch (ch), - type (type), - stackType(stackType) +gdBaseBrowser::gdBaseBrowser(int x, int y, int w, int h, const string &title, + const string &path, void (*callback)(void*)) + : gWindow(x, y, w, h, title.c_str()), callback(callback) { set_non_modal(); - browser = new gBrowser(8, 36, 380, 230); - Fl_Group *group_btn = new Fl_Group(8, 274, 380, 20); - gBox *b = new gBox(8, 274, 204, 20); // spacer window border <-> buttons - ok = new gClick(308, 274, 80, 20); - cancel = new gClick(220, 274, 80, 20, "Cancel"); - status = new gProgress(8, 274, 204, 20); - status->minimum(0); - status->maximum(1); - status->hide(); // show the bar only if necessary - group_btn->resizable(b); - group_btn->end(); - - Fl_Group *group_upd = new Fl_Group(8, 8, 380, 25); - if (type == BROWSER_SAVE_PATCH || type == BROWSER_SAVE_SAMPLE || type == BROWSER_SAVE_PROJECT) /// bitmask please! - name = new gInput(208, 8, 152, 20); - if (type == BROWSER_SAVE_PATCH || type == BROWSER_SAVE_SAMPLE || type == BROWSER_SAVE_PROJECT) /// bitmask please! - where = new gInput(8, 8, 192, 20); - else - where = new gInput(8, 8, 352, 20); - updir = new gClick(368, 8, 20, 20, "", updirOff_xpm, updirOn_xpm); - group_upd->resizable(where); - group_upd->end(); - - end(); - - resizable(browser); - size_range(w(), h(), 0, 0); + groupTop = new Fl_Group(8, 8, w-16, 20); + where = new gInput(groupTop->x(), groupTop->y(), 152, 20); + updir = new gClick(groupTop->x()+groupTop->w()-20, groupTop->y(), 20, 20, "", updirOff_xpm, updirOn_xpm); + groupTop->end(); + groupTop->resizable(where); where->readonly(true); where->cursor_color(COLOR_BG_DARK); + where->value(path.c_str()); - if (type == BROWSER_SAVE_PATCH || type == BROWSER_SAVE_SAMPLE || type == BROWSER_SAVE_PROJECT) /// bitmask please! - ok->label("Save"); - else - ok->label("Load"); + updir->callback(cb_up, (void*) this); - if (type == BROWSER_LOAD_PATCH) - ok->callback(cb_load_patch, (void*)this); - else - if (type == BROWSER_LOAD_SAMPLE) - ok->callback(cb_load_sample, (void*)this); - else - if (type == BROWSER_SAVE_PATCH) { - ok->callback(cb_save_patch, (void*)this); - name->value(G_Patch.name == "" ? "my_patch.gptc" : G_Patch.name.c_str()); - name->maximum_size(MAX_PATCHNAME_LEN+5); // +5 for ".gptc" - } - else - if (type == BROWSER_SAVE_SAMPLE) { - ok->callback(cb_save_sample, (void*)this); - name->value(((SampleChannel*)ch)->wave->name.c_str()); - } - else - if (type == BROWSER_SAVE_PROJECT) { - ok->callback(cb_save_project, (void*)this); - name->value(gStripExt(G_Patch.name).c_str()); - } + browser = new gBrowser(8, groupTop->y()+groupTop->h()+8, w-16, h-73); + browser->loadDir(path); + if (path == G_Conf.browserLastPath) + browser->preselect(G_Conf.browserPosition, G_Conf.browserLastValue); - ok->shortcut(FL_Enter); + Fl_Group *groupButtons = new Fl_Group(8, browser->y()+browser->h()+8, w-16, 20); + ok = new gClick(w-88, groupButtons->y(), 80, 20); + cancel = new gClick(w-ok->w()-96, groupButtons->y(), 80, 20, "Cancel"); + status = new gProgress(8, groupButtons->y(), cancel->x()-16, 20); + status->minimum(0); + status->maximum(1); + status->hide(); // show the bar only if necessary + groupButtons->resizable(status); + groupButtons->end(); + + end(); - updir->callback(cb_up, (void*)this); - cancel->callback(cb_close, (void*)this); - browser->callback(cb_down, this); - browser->path_obj = where; - browser->init(initPath); + cancel->callback(cb_close, (void*) this); - if (G_Conf.browserW) - resize(G_Conf.browserX, G_Conf.browserY, G_Conf.browserW, G_Conf.browserH); + resizable(browser); + size_range(320, 200); gu_setFavicon(this); show(); @@ -141,189 +95,175 @@ gdBrowser::gdBrowser(const char *title, const char *initPath, Channel *ch, int t /* -------------------------------------------------------------------------- */ -gdBrowser::~gdBrowser() { +gdBaseBrowser::~gdBaseBrowser() +{ G_Conf.browserX = x(); G_Conf.browserY = y(); G_Conf.browserW = w(); G_Conf.browserH = h(); + G_Conf.browserPosition = browser->position(); + G_Conf.browserLastPath = gDirname(browser->getSelectedItem()); + G_Conf.browserLastValue = browser->value(); } /* -------------------------------------------------------------------------- */ -void gdBrowser::cb_load_patch (Fl_Widget *v, void *p) { ((gdBrowser*)p)->__cb_load_patch(); } -void gdBrowser::cb_load_sample (Fl_Widget *v, void *p) { ((gdBrowser*)p)->__cb_load_sample(); } -void gdBrowser::cb_save_sample (Fl_Widget *v, void *p) { ((gdBrowser*)p)->__cb_save_sample(); } -void gdBrowser::cb_save_patch (Fl_Widget *v, void *p) { ((gdBrowser*)p)->__cb_save_patch(); } -void gdBrowser::cb_save_project(Fl_Widget *v, void *p) { ((gdBrowser*)p)->__cb_save_project(); } -void gdBrowser::cb_down (Fl_Widget *v, void *p) { ((gdBrowser*)p)->__cb_down(); } -void gdBrowser::cb_up (Fl_Widget *v, void *p) { ((gdBrowser*)p)->__cb_up(); } -void gdBrowser::cb_close (Fl_Widget *v, void *p) { ((gdBrowser*)p)->__cb_close(); } +void gdBaseBrowser::cb_up (Fl_Widget *v, void *p) { ((gdBaseBrowser*)p)->__cb_up(); } +void gdBaseBrowser::cb_close(Fl_Widget *v, void *p) { ((gdBaseBrowser*)p)->__cb_close(); } /* -------------------------------------------------------------------------- */ -void gdBrowser::__cb_load_patch() { +void gdBaseBrowser::__cb_up() { + string dir = browser->getCurrentDir(); + dir = dir.substr(0, dir.find_last_of(G_SLASH_STR)); // remove up to the next slash + browser->loadDir(dir); + where->value(browser->getCurrentDir().c_str()); +} - if (browser->text(browser->value()) == NULL) - return; - bool isProject = gIsProject(browser->get_selected_item()); - int res = glue_loadPatch(browser->get_selected_item(), status, isProject); +/* -------------------------------------------------------------------------- */ + - if (res == PATCH_UNREADABLE) { - status->hide(); - if (isProject) - gdAlert("This project is unreadable."); - else - gdAlert("This patch is unreadable."); - } - else - if (res == PATCH_INVALID) { - status->hide(); - if (isProject) - gdAlert("This project is not valid."); - else - gdAlert("This patch is not valid."); - } - else - do_callback(); +void gdBaseBrowser::__cb_close() { + do_callback(); } /* -------------------------------------------------------------------------- */ -void gdBrowser::__cb_save_sample() { - - if (strcmp(name->value(), "") == 0) { /// FIXME glue business - gdAlert("Please choose a file name."); - return; - } +void gdBaseBrowser::setStatusBar(float v) +{ + status->value(status->value() + v); + Fl::wait(0); +} - /* bruteforce check extension. */ - string filename = gStripExt(name->value()); - char fullpath[PATH_MAX]; - sprintf(fullpath, "%s/%s.wav", where->value(), filename.c_str()); +/* -------------------------------------------------------------------------- */ - if (gFileExists(fullpath)) - if (!gdConfirmWin("Warning", "File exists: overwrite?")) - return; - if (((SampleChannel*)ch)->save(fullpath)) - do_callback(); - else - gdAlert("Unable to save this sample!"); +string gdBaseBrowser::getSelectedItem() +{ + return browser->getSelectedItem(); } +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */ -void gdBrowser::__cb_load_sample() { - if (browser->text(browser->value()) == NULL) - return; +gdSaveBrowser::gdSaveBrowser(int x, int y, int w, int h, const string &title, + const string &path, const string &_name, void (*cb)(void*), Channel *ch) + : gdBaseBrowser(x, y, w, h, title, path, cb) +{ + channel = ch; - int res = glue_loadChannel((SampleChannel*) ch, browser->get_selected_item()); + where->size(groupTop->w()-236, 20); - if (res == SAMPLE_LOADED_OK) { - do_callback(); - mainWin->delSubWindow(WID_SAMPLE_EDITOR); // if editor is open - } - else - mainWin->keyboard->printChannelMessage(res); + name = new gInput(where->x()+where->w()+8, 8, 200, 20); + name->value(_name.c_str()); + groupTop->add(name); + + browser->callback(cb_down, (void*) this); + + ok->label("Save"); + ok->callback(cb_save, (void*) this); + ok->shortcut(FL_ENTER); } /* -------------------------------------------------------------------------- */ -void gdBrowser::__cb_down() { - const char *path = browser->get_selected_item(); - if (!path) // when click on an empty area - return; - if (!gIsDir(path)) { - - /* set the name of the patch/sample/project as the selected item */ - - if (type == BROWSER_SAVE_PATCH || type == BROWSER_SAVE_SAMPLE || type == BROWSER_SAVE_PROJECT) { - if (gIsProject(path)) { - string tmp = browser->text(browser->value()); - tmp.erase(0, 4); - name->value(tmp.c_str()); - } - else - name->value(browser->text(browser->value())); - } +void gdSaveBrowser::cb_save(Fl_Widget *v, void *p) { ((gdSaveBrowser*)p)->__cb_save(); } +void gdSaveBrowser::cb_down(Fl_Widget *v, void *p) { ((gdSaveBrowser*)p)->__cb_down(); } + + +/* -------------------------------------------------------------------------- */ + + +void gdSaveBrowser::__cb_down() +{ + string path = browser->getSelectedItem(); + + if (path.empty()) // when click on an empty area return; + + /* if the selected item is a directory just load its content. If it's a file + * use it as the file name (i.e. fill name->value()). */ + + if (gIsDir(path)) { + browser->loadDir(path); + where->value(browser->getCurrentDir().c_str()); } - browser->clear(); - browser->down_dir(path); - browser->sort(); + else + name->value(browser->getSelectedItem(false).c_str()); } /* -------------------------------------------------------------------------- */ -void gdBrowser::__cb_up() { - browser->clear(); - browser->up_dir(); - browser->sort(); +void gdSaveBrowser::__cb_save() +{ + callback((void*) this); } +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ /* -------------------------------------------------------------------------- */ -void gdBrowser::__cb_save_patch() +gdLoadBrowser::gdLoadBrowser(int x, int y, int w, int h, const string &title, + const string &path, void (*cb)(void*), Channel *ch) + : gdBaseBrowser(x, y, w, h, title, path, cb) { - if (strcmp(name->value(), "") == 0) { /// FIXME glue business - gdAlert("Please choose a file name."); - return; - } + channel = ch; - string fullpath = where->value() + gGetSlash() + gStripExt(name->value()) + ".gptc"; + where->size(groupTop->w()-updir->w()-8, 20); - if (gFileExists(fullpath.c_str())) - if (!gdConfirmWin("Warning", "File exists: overwrite?")) - return; + browser->callback(cb_down, (void*) this); - if (glue_savePatch(fullpath, name->value(), false)) // false == not a project - do_callback(); - else - gdAlert("Unable to save the patch!"); + ok->label("Load"); + ok->callback(cb_load, (void*) this); + ok->shortcut(FL_ENTER); } + + /* -------------------------------------------------------------------------- */ -void gdBrowser::__cb_save_project() -{ - if (strcmp(name->value(), "") == 0) { /// FIXME glue business - gdAlert("Please choose a project name."); - return; - } +void gdLoadBrowser::cb_load(Fl_Widget *v, void *p) { ((gdLoadBrowser*)p)->__cb_load(); } +void gdLoadBrowser::cb_down(Fl_Widget *v, void *p) { ((gdLoadBrowser*)p)->__cb_down(); } - string fullpath = where->value() + gGetSlash() + gStripExt(name->value()) + ".gprj"; - if (gIsProject(fullpath.c_str()) && !gdConfirmWin("Warning", "Project exists: overwrite?")) - return; +/* -------------------------------------------------------------------------- */ - if (glue_saveProject(fullpath, name->value())) - do_callback(); - else - gdAlert("Unable to save the project!"); + +void gdLoadBrowser::__cb_load() +{ + callback((void*) this); } /* -------------------------------------------------------------------------- */ -void gdBrowser::__cb_close() { - do_callback(); +void gdLoadBrowser::__cb_down() +{ + string path = browser->getSelectedItem(); + + if (path.empty() || !gIsDir(path)) // when click on an empty area or not a dir + return; + + browser->loadDir(path); + where->value(browser->getCurrentDir().c_str()); } diff --git a/src/gui/dialogs/gd_browser.h b/src/gui/dialogs/gd_browser.h index 7700fab..bf797ec 100644 --- a/src/gui/dialogs/gd_browser.h +++ b/src/gui/dialogs/gd_browser.h @@ -1,10 +1,10 @@ -/* --------------------------------------------------------------------- +/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * gd_browser * - * --------------------------------------------------------------------- + * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual * @@ -24,7 +24,7 @@ * along with Giada - Your Hardcore Loopmachine. If not, see * . * - * ------------------------------------------------------------------ */ + * -------------------------------------------------------------------------- */ #ifndef GD_BROWSER_H @@ -33,64 +33,103 @@ #include #include +#include "../elems/ge_mixed.h" #include "../elems/ge_window.h" -/* TODO - this class must be subclassed into gdPluginBrowser, gdFileBrowser, - * and so on. It's a real mess right now. */ +class gdBaseBrowser : public gWindow +{ +protected: -class gdBrowser : public gWindow { - -private: - static void cb_down(Fl_Widget *v, void *p); - static void cb_up (Fl_Widget *v, void *p); - static void cb_load_sample (Fl_Widget *v, void *p); - static void cb_save_sample (Fl_Widget *v, void *p); - static void cb_load_patch (Fl_Widget *v, void *p); - static void cb_save_patch (Fl_Widget *v, void *p); - static void cb_save_project(Fl_Widget *v, void *p); - static void cb_close (Fl_Widget *w, void *p); - - inline void __cb_down(); - inline void __cb_up(); - inline void __cb_load_sample(); - inline void __cb_save_sample(); - inline void __cb_save_project(); - inline void __cb_load_patch(); - inline void __cb_save_patch(); - inline void __cb_close(); - + class Fl_Group *groupTop; class gBrowser *browser; class gClick *ok; class gClick *cancel; class gInput *where; - class gInput *name; class gClick *updir; class gProgress *status; - class Channel *ch; + static void cb_up (Fl_Widget *v, void *p); + static void cb_close(Fl_Widget *w, void *p); + + inline void __cb_up (); + inline void __cb_close(); + + /* Callback + * Fired when the save/load button is pressed. */ + + void (*callback)(void*); + + class Channel *channel; + +public: + + gdBaseBrowser(int x, int y, int w, int h, const string &title, + const string &path, void (*callback)(void*)); + + ~gdBaseBrowser(); + + /* getSelectedItem + * Return the full path of the selected file. */ + + string getSelectedItem(); + + /* setStatusBar + * Increment status bar for progress tracking. */ + + void setStatusBar(float v); + + inline gProgress *getStatusBar() { return status; } // TODO - remove with Patch_DEPR_ + inline void showStatusBar() { status->show(); } + inline void hideStatusBar() { status->hide(); } + inline Channel *getChannel() { return channel; } + inline void fireCallback() { callback((void*) this); } + inline string getCurrentPath() { return where->value(); } +}; + + +/* -------------------------------------------------------------------------- */ + + +class gdSaveBrowser : public gdBaseBrowser +{ +private: + + class gInput *name; + + static void cb_down(Fl_Widget *v, void *p); + static void cb_save(Fl_Widget *w, void *p); + + inline void __cb_down(); + inline void __cb_save(); + +public: + + gdSaveBrowser(int x, int y, int w, int h, const string &title, + const string &path, const string &name, void (*callback)(void*), + class Channel *ch); + + string getName() { return name->value(); } +}; - /* browser type: see const.h */ - /** FIXME internal enum: - * enum browserType { - * TYPE_A, - * TYPE_B, - * .... - * }; */ - int type; +/* -------------------------------------------------------------------------- */ - /* PluginHost_DEPR_ stack type. Used only when loading plugins */ - int stackType; +class gdLoadBrowser : public gdBaseBrowser +{ +private: - char selectedFile[FILENAME_MAX]; + static void cb_load(Fl_Widget *w, void *p); + static void cb_down(Fl_Widget *v, void *p); + + inline void __cb_load(); + inline void __cb_down(); public: - gdBrowser(const char *title, const char *initPath, class Channel *ch, int type, int stackType=0); - ~gdBrowser(); - char* SelectedFile(); + gdLoadBrowser(int x, int y, int w, int h, const string &title, + const string &path, void (*callback)(void*), class Channel *ch); }; #endif diff --git a/src/gui/dialogs/gd_config.cpp b/src/gui/dialogs/gd_config.cpp index 8e47cc3..5d7da03 100644 --- a/src/gui/dialogs/gd_config.cpp +++ b/src/gui/dialogs/gd_config.cpp @@ -669,12 +669,18 @@ void gTabMidi::fetchMidiMaps() midiMap->deactivate(); return; } + for (unsigned i=0; iadd(imap); if (G_Conf.midiMapPath == imap) midiMap->value(i); } + + /* Preselect the 0 midimap if nothing is selected but midimaps exist. */ + + if (midiMap->value() == -1 && G_MidiMap.maps.size() > 0) + midiMap->value(0); } @@ -910,7 +916,7 @@ void gTabPlugins::__cb_scan(Fl_Widget *w) { info->show(); G_PluginHost.scanDir(folderPath->value(), cb_onScan, (void*) this); - G_PluginHost.saveList(gGetHomePath() + gGetSlash() + "plugins.xml"); + G_PluginHost.saveList(gGetHomePath() + G_SLASH + "plugins.xml"); info->hide(); updateCount(); } diff --git a/src/gui/dialogs/gd_editor.cpp b/src/gui/dialogs/gd_editor.cpp index b6d4d5c..501edfe 100644 --- a/src/gui/dialogs/gd_editor.cpp +++ b/src/gui/dialogs/gd_editor.cpp @@ -202,7 +202,7 @@ gdEditor::gdEditor(SampleChannel *ch) if (ch->panRight < 1.0f) { char buf[8]; - sprintf(buf, "%d L", abs((ch->panRight * 100.0f) - 100)); + sprintf(buf, "%d L", (int) abs((ch->panRight * 100.0f) - 100)); pan->value(ch->panRight); panNum->value(buf); } @@ -212,7 +212,7 @@ gdEditor::gdEditor(SampleChannel *ch) } else { char buf[8]; - sprintf(buf, "%d R", abs((ch->panLeft * 100.0f) - 100)); + sprintf(buf, "%d R", (int) abs((ch->panLeft * 100.0f) - 100)); pan->value(2.0f - ch->panLeft); panNum->value(buf); } diff --git a/src/gui/dialogs/gd_mainWindow.cpp b/src/gui/dialogs/gd_mainWindow.cpp index c10021b..b2012c2 100644 --- a/src/gui/dialogs/gd_mainWindow.cpp +++ b/src/gui/dialogs/gd_mainWindow.cpp @@ -43,6 +43,7 @@ #include "../../core/conf.h" #include "../../core/pluginHost.h" #include "../../glue/glue.h" +#include "../../glue/storage.h" #include "../elems/ge_keyboard.h" #include "gd_warnings.h" #include "gd_bpmInput.h" @@ -57,6 +58,7 @@ extern Mixer G_Mixer; extern Patch_DEPR_ G_Patch_DEPR_; +extern Patch G_Patch; extern Conf G_Conf; extern gdMainWindow *mainWin; extern bool G_quit; @@ -67,6 +69,7 @@ gdMainWindow::gdMainWindow(int W, int H, const char *title, int argc, char **arg : gWindow(W, H, title) { Fl::visible_focus(0); + Fl::background(25, 25, 25); Fl::set_boxtype(G_BOX, gDrawBox, 1, 1, 2, 2); // custom box G_BOX @@ -109,6 +112,7 @@ gdMainWindow::gdMainWindow(int W, int H, const char *title, int argc, char **arg add(keyboard); callback(cb_endprogram); gu_setFavicon(this); + show(argc, argv); } @@ -317,7 +321,11 @@ void gMenu::__cb_file() if (strcmp(m->label(), "Open patch or project...") == 0) { - gWindow *childWin = new gdBrowser("Load Patch", G_Conf.patchPath.c_str(), 0, BROWSER_LOAD_PATCH); + //gWindow *childWin = new gdBrowser("Load Patch", G_Conf.patchPath.c_str(), 0, BROWSER_LOAD_PATCH); + //gu_openSubWindow(mainWin, childWin, WID_FILE_BROWSER); + gWindow *childWin = new gdLoadBrowser(G_Conf.browserX, G_Conf.browserY, + G_Conf.browserW, G_Conf.browserH, "Load patch or project", + G_Conf.patchPath, glue_loadPatch, NULL); gu_openSubWindow(mainWin, childWin, WID_FILE_BROWSER); return; } @@ -325,12 +333,16 @@ void gMenu::__cb_file() if (G_Mixer.hasLogicalSamples() || G_Mixer.hasEditedSamples()) if (!gdConfirmWin("Warning", "You should save a project in order to store\nyour takes and/or processed samples.")) return; - gWindow *childWin = new gdBrowser("Save Patch", G_Conf.patchPath.c_str(), 0, BROWSER_SAVE_PATCH); + gWindow *childWin = new gdSaveBrowser(G_Conf.browserX, G_Conf.browserY, + G_Conf.browserW, G_Conf.browserH, "Save patch", + G_Conf.patchPath, G_Patch.name, glue_savePatch, NULL); gu_openSubWindow(mainWin, childWin, WID_FILE_BROWSER); return; } if (strcmp(m->label(), "Save project...") == 0) { - gWindow *childWin = new gdBrowser("Save Project", G_Conf.patchPath.c_str(), 0, BROWSER_SAVE_PROJECT); + gWindow *childWin = new gdSaveBrowser(G_Conf.browserX, G_Conf.browserY, + G_Conf.browserW, G_Conf.browserH, "Save project", + G_Conf.patchPath, G_Patch.name, glue_saveProject, NULL); gu_openSubWindow(mainWin, childWin, WID_FILE_BROWSER); return; } diff --git a/src/gui/dialogs/gd_pluginList.cpp b/src/gui/dialogs/gd_pluginList.cpp index 9ea9d22..433284f 100644 --- a/src/gui/dialogs/gd_pluginList.cpp +++ b/src/gui/dialogs/gd_pluginList.cpp @@ -147,12 +147,6 @@ void gdPluginList::__cb_addPlugin() { stackType, ch); addSubWindow(pc); pc->callback(cb_refreshList, (void*)this); // 'this' refers to gdPluginList - -#if 0 - gdBrowser *b = new gdBrowser("Browse Plugin_DEPR_", G_Conf.pluginPath.c_str(), ch, BROWSER_LOAD_PLUGIN, stackType); - addSubWindow(b); - b->callback(cb_refreshList, (void*)this); // 'this' refers to gdPluginList -#endif } @@ -363,32 +357,6 @@ void gdPlugin::__cb_openPluginWindow() else { w = new gdPluginWindow(pPlugin); } -#if 0 - - /* TODO - at the moment you can open a window for each plugin in the stack. - * This is not consistent with the rest of the gui. You can avoid this by - * calling - * - * gu_openSubWindow(this, new gdPluginWindow(pPlugin), WID_FX); - * - * instead of the following code. - * - * EDIT 2 - having only 1 plugin window would be very uncomfortable */ - - if (!pParent->hasWindow(pPlugin->getId()+1)) { - gWindow *w; - if (pPlugin->hasEditor()) -#ifdef __APPLE__ - w = new gdPluginWindowGUImac(pPlugin); -#else - w = new gdPluginWindowGUI(pPlugin); -#endif - else - w = new gdPluginWindow(pPlugin); - w->setId(pPlugin->getId()+1); - pParent->addSubWindow(w); - } -#endif } diff --git a/src/gui/dialogs/gd_pluginWindowGUI.cpp b/src/gui/dialogs/gd_pluginWindowGUI.cpp index 01fe5fa..b50b1f4 100644 --- a/src/gui/dialogs/gd_pluginWindowGUI.cpp +++ b/src/gui/dialogs/gd_pluginWindowGUI.cpp @@ -52,27 +52,36 @@ gdPluginWindowGUI::gdPluginWindowGUI(Plugin *pPlugin) show(); #ifndef __APPLE__ + Fl::check(); + #endif gLog("[gdPluginWindowGUI] opening GUI, this=%p, xid=%p\n", (void*) this, (void*) fl_xid(this)); - pPlugin->initEditor(); - #if defined(__APPLE__) + void *cocoaWindow = (void*) fl_xid(this); cocoa_setWindowSize(cocoaWindow, pPlugin->getEditorW(), pPlugin->getEditorH()); pPlugin->showEditor(cocoa_getViewFromWindow(cocoaWindow)); + #else + pPlugin->showEditor((void*) fl_xid(this)); + #endif - resize(0, 0, pPlugin->getEditorW(), pPlugin->getEditorH()); + int pluginW = pPlugin->getEditorW(); + int pluginH = pPlugin->getEditorH(); + + printf("w=%d h=%d\n", Fl::w(), Fl::h()); + + resize((Fl::w() - pluginW) / 2, (Fl::h() - pluginH) / 2, pluginW, pluginH); Fl::add_timeout(GUI_PLUGIN_RATE, cb_refresh, (void*) this); - copy_label(pPlugin->getName().toStdString().c_str()); + copy_label(pPlugin->getName().toRawUTF8()); } diff --git a/src/gui/elems/ge_actionChannel.cpp b/src/gui/elems/ge_actionChannel.cpp index fdedf08..81b453a 100644 --- a/src/gui/elems/ge_actionChannel.cpp +++ b/src/gui/elems/ge_actionChannel.cpp @@ -1,10 +1,10 @@ -/* --------------------------------------------------------------------- +/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ge_actionChannel * - * --------------------------------------------------------------------- + * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual * @@ -24,7 +24,7 @@ * along with Giada - Your Hardcore Loopmachine. If not, see * . * - * ------------------------------------------------------------------ */ + * -------------------------------------------------------------------------- */ #include @@ -44,11 +44,13 @@ extern Mixer G_Mixer; extern Conf G_Conf; -/* ------------------------------------------------------------------ */ +/* -------------------------------------------------------------------------- */ gActionChannel::gActionChannel(int x, int y, gdActionEditor *pParent, SampleChannel *ch) - : gActionWidget(x, y, 200, 40, pParent), ch(ch), selected(NULL) + : gActionWidget(x, y, 200, 40, pParent), + ch (ch), + selected (NULL) { size(pParent->totalWidth, h()); @@ -58,49 +60,49 @@ gActionChannel::gActionChannel(int x, int y, gdActionEditor *pParent, SampleChan for (unsigned i=0; ichan == pParent->chan->index) { - - /* don't show actions > than the grey area */ - - if (recorder::frames.at(i) > G_Mixer.totalFrames) - continue; - - /* skip the killchan actions in a singlepress channel. They cannot be recorded - * in such mode, but they can exist if you change from another mode to singlepress */ - - if (ra->type == ACTION_KILLCHAN && ch->mode == SINGLE_PRESS) - continue; - - /* also filter out ACTION_KEYREL: it's up to gAction to find the other piece - * (namely frame_b) */ - - if (ra->type & (ACTION_KEYPRESS | ACTION_KILLCHAN)) { - int ax = x+(ra->frame/pParent->zoom); - gAction *a = new gAction( - ax, // x - y+4, // y - h()-8, // h - ra->frame, // frame_a - i, // n. of recordings - pParent, // pointer to the pParent window - ch, // pointer to SampleChannel - false, // record = false: don't record it, we are just displaying it! - ra->type); // type of action - add(a); - } - } + recorder::action *action = recorder::global.at(i).at(j); + + /* Don't show actions: + - that don't belong to the displayed channel (!= pParent->chan->index); + - that are covered by the grey area (> G_Mixer.totalFrames); + - of type ACTION_KILLCHAN in a SINGLE_PRESS channel. They cannot be + recorded in such mode, but they can exist if you change from another + mode to singlepress; + - of type ACTION_KEYREL in a SINGLE_PRESS channel. It's up to gAction to + find the other piece (namely frame_b) + - not of types ACTION_KEYPRESS | ACTION_KEYREL | ACTION_KILLCHAN */ + + if ((action->chan != pParent->chan->index) || + (recorder::frames.at(i) > G_Mixer.totalFrames) || + (action->type == ACTION_KILLCHAN && ch->mode == SINGLE_PRESS) || + (action->type == ACTION_KEYREL && ch->mode == SINGLE_PRESS) || + (action->type & ~(ACTION_KEYPRESS | ACTION_KEYREL | ACTION_KILLCHAN)) + ) + continue; + + int ax = x + (action->frame / pParent->zoom); + gAction *a = new gAction( + ax, // x + y + 4, // y + h() - 8, // h + action->frame, // frame_a + i, // n. of recordings + pParent, // pointer to the pParent window + ch, // pointer to SampleChannel + false, // record = false: don't record it, we are just displaying it! + action->type); // type of action + add(a); } } end(); // mandatory when you add widgets to a fl_group, otherwise mega malfunctions } -/* ------------------------------------------------------------------ */ +/* -------------------------------------------------------------------------- */ -gAction *gActionChannel::getSelectedAction() { +gAction *gActionChannel::getSelectedAction() +{ for (int i=0; ix(); int action_w = ((gAction*)child(i))->w(); @@ -111,11 +113,11 @@ gAction *gActionChannel::getSelectedAction() { } -/* ------------------------------------------------------------------ */ +/* -------------------------------------------------------------------------- */ -void gActionChannel::updateActions() { - +void gActionChannel::updateActions() +{ /* when zooming, don't delete and re-add actions, just MOVE them. This * function shifts the action by a zoom factor. Those singlepress are * stretched, as well */ @@ -138,11 +140,11 @@ void gActionChannel::updateActions() { } -/* ------------------------------------------------------------------ */ +/* -------------------------------------------------------------------------- */ -void gActionChannel::draw() { - +void gActionChannel::draw() +{ /* draw basic boundaries (+ beat bars) and hide the unused area. Then * draw the children (the actions) */ @@ -161,11 +163,11 @@ void gActionChannel::draw() { } -/* ------------------------------------------------------------------ */ +/* -------------------------------------------------------------------------- */ -int gActionChannel::handle(int e) { - +int gActionChannel::handle(int e) +{ int ret = Fl_Group::handle(e); /* do nothing if the widget is deactivated. It could happen for loopmode @@ -177,69 +179,72 @@ int gActionChannel::handle(int e) { switch (e) { case FL_DRAG: { - if (selected != NULL) { // if you don't drag an empty area - /* if onLeftEdge o onRightEdge are true it means that you're resizing - * an action. Otherwise move the widget. */ + if (selected == NULL) { // if you drag an empty area + ret = 1; + break; + } - if (selected->onLeftEdge || selected->onRightEdge) { + /* if onLeftEdge o onRightEdge are true it means that you're resizing + * an action. Otherwise move the widget. */ - /* some checks: a) cannot resize an action < N pixels, b) no beyond zero, - * c) no beyond bar maxwidth. Checks for overlap are done in FL_RELEASE */ + if (selected->onLeftEdge || selected->onRightEdge) { - if (selected->onRightEdge) { + /* some checks: a) cannot resize an action < N pixels, b) no beyond zero, + * c) no beyond bar maxwidth. Checks for overlap are done in FL_RELEASE */ - int aw = Fl::event_x()-selected->x(); - int ah = selected->h(); + if (selected->onRightEdge) { - if (Fl::event_x() < selected->x()+gAction::MIN_WIDTH) - aw = gAction::MIN_WIDTH; - else - if (Fl::event_x() > pParent->coverX) - aw = pParent->coverX-selected->x(); + int aw = Fl::event_x()-selected->x(); + int ah = selected->h(); - selected->size(aw, ah); - } - else { + if (Fl::event_x() < selected->x()+gAction::MIN_WIDTH) + aw = gAction::MIN_WIDTH; + else + if (Fl::event_x() > pParent->coverX) + aw = pParent->coverX-selected->x(); - int ax = Fl::event_x(); - int ay = selected->y(); - int aw = selected->x()-Fl::event_x()+selected->w(); - int ah = selected->h(); + selected->size(aw, ah); + } + else { - if (Fl::event_x() < x()) { - ax = x(); - aw = selected->w()+selected->x()-x(); - } - else - if (Fl::event_x() > selected->x()+selected->w()-gAction::MIN_WIDTH) { - ax = selected->x()+selected->w()-gAction::MIN_WIDTH; - aw = gAction::MIN_WIDTH; - } - selected->resize(ax, ay, aw, ah); + int ax = Fl::event_x(); + int ay = selected->y(); + int aw = selected->x()-Fl::event_x()+selected->w(); + int ah = selected->h(); + + if (Fl::event_x() < x()) { + ax = x(); + aw = selected->w()+selected->x()-x(); } + else + if (Fl::event_x() > selected->x()+selected->w()-gAction::MIN_WIDTH) { + ax = selected->x()+selected->w()-gAction::MIN_WIDTH; + aw = gAction::MIN_WIDTH; + } + selected->resize(ax, ay, aw, ah); } + } - /* move the widget around */ + /* move the widget around */ + else { + int real_x = Fl::event_x() - actionPickPoint; + if (real_x < x()) // don't go beyond the left border + selected->position(x(), selected->y()); + else + if (real_x+selected->w() > pParent->coverX+x()) // don't go beyond the right border + selected->position(pParent->coverX+x()-selected->w(), selected->y()); else { - int real_x = Fl::event_x() - actionPickPoint; - if (real_x < x()) // don't go beyond the left border - selected->position(x(), selected->y()); - else - if (real_x+selected->w() > pParent->coverX+x()) // don't go beyond the right border - selected->position(pParent->coverX+x()-selected->w(), selected->y()); - else { - if (pParent->gridTool->isOn()) { - int snpx = pParent->gridTool->getSnapPoint(real_x-x()) + x() -1; - selected->position(snpx, selected->y()); - } - else - selected->position(real_x, selected->y()); + if (pParent->gridTool->isOn()) { + int snpx = pParent->gridTool->getSnapPoint(real_x-x()) + x() -1; + selected->position(snpx, selected->y()); } + else + selected->position(real_x, selected->y()); } - redraw(); } + redraw(); ret = 1; break; } @@ -399,23 +404,23 @@ int gActionChannel::handle(int e) { } -/* ------------------------------------------------------------------ */ +/* -------------------------------------------------------------------------- */ -bool gActionChannel::actionCollides(int frame) { - +bool gActionChannel::actionCollides(int frame) +{ /* if SINGLE_PRESS we check that the tail (frame_b) of the action doesn't * overlap the head (frame) of the new one. First the general case, yet. */ bool collision = false; for (int i=0; iframe_a == frame) + if (((gAction*) child(i))->frame_a == frame) collision = true; if (ch->mode == SINGLE_PRESS) { for (int i=0; iframe_b && frame >= c->frame_a) collision = true; } @@ -425,15 +430,9 @@ bool gActionChannel::actionCollides(int frame) { } -/* ------------------------------------------------------------------ */ -/* ------------------------------------------------------------------ */ -/* ------------------------------------------------------------------ */ - - -const int gAction::MIN_WIDTH = 8; - - -/* ------------------------------------------------------------------ */ +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ +/* -------------------------------------------------------------------------- */ /** TODO - index is useless? @@ -485,11 +484,11 @@ gAction::gAction(int X, int Y, int H, int frame_a, unsigned index, gdActionEdito } -/* ------------------------------------------------------------------ */ - +/* -------------------------------------------------------------------------- */ -void gAction::draw() { +void gAction::draw() +{ int color; if (selected) /// && gActionChannel !disabled color = COLOR_BD_1; @@ -515,11 +514,11 @@ void gAction::draw() { } -/* ------------------------------------------------------------------ */ - +/* -------------------------------------------------------------------------- */ -int gAction::handle(int e) { +int gAction::handle(int e) +{ /* ret = 0 sends the event to the parent window. */ int ret = 0; @@ -565,11 +564,11 @@ int gAction::handle(int e) { } -/* ------------------------------------------------------------------ */ - +/* -------------------------------------------------------------------------- */ -void gAction::addAction() { +void gAction::addAction() +{ /* always check frame parity */ if (frame_a % 2 != 0) @@ -597,11 +596,11 @@ void gAction::addAction() { } -/* ------------------------------------------------------------------ */ +/* -------------------------------------------------------------------------- */ -void gAction::delAction() { - +void gAction::delAction() +{ /* if SINGLE_PRESS you must delete both the keypress and the keyrelease * actions. */ @@ -619,11 +618,11 @@ void gAction::delAction() { } -/* ------------------------------------------------------------------ */ +/* -------------------------------------------------------------------------- */ -void gAction::moveAction(int frame_a) { - +void gAction::moveAction(int frame_a) +{ /* easy one: delete previous action and record the new ones. As usual, * SINGLE_PRESS requires two jobs. If frame_a is valid, use that frame * value. */ @@ -652,25 +651,28 @@ void gAction::moveAction(int frame_a) { } -/* ------------------------------------------------------------------ */ +/* -------------------------------------------------------------------------- */ -int gAction::absx() { +int gAction::absx() +{ return x() - parent->ac->x(); } -/* ------------------------------------------------------------------ */ +/* -------------------------------------------------------------------------- */ -int gAction::xToFrame_a() { +int gAction::xToFrame_a() +{ return (absx()) * parent->zoom; } -/* ------------------------------------------------------------------ */ +/* -------------------------------------------------------------------------- */ -int gAction::xToFrame_b() { +int gAction::xToFrame_b() +{ return (absx() + w()) * parent->zoom; } diff --git a/src/gui/elems/ge_actionChannel.h b/src/gui/elems/ge_actionChannel.h index 38bc8d8..051feea 100644 --- a/src/gui/elems/ge_actionChannel.h +++ b/src/gui/elems/ge_actionChannel.h @@ -1,10 +1,10 @@ -/* --------------------------------------------------------------------- +/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ge_actionChannel * - * --------------------------------------------------------------------- + * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual * @@ -24,11 +24,13 @@ * along with Giada - Your Hardcore Loopmachine. If not, see * . * - * ------------------------------------------------------------------ */ + * -------------------------------------------------------------------------- */ + #ifndef GE_ACTIONCHANNEL_H #define GE_ACTIONCHANNEL_H + #include #include #include "../../utils/gui_utils.h" @@ -37,8 +39,8 @@ #include "ge_actionWidget.h" -class gAction : public Fl_Box { - +class gAction : public Fl_Box +{ private: bool selected; @@ -48,6 +50,7 @@ private: char type; // type of action public: + gAction(int x, int y, int h, int frame_a, unsigned index, gdActionEditor *parent, class SampleChannel *ch, bool record, char type); @@ -80,14 +83,15 @@ public: bool onRightEdge; bool onLeftEdge; - static const int MIN_WIDTH; + static const int MIN_WIDTH = 8; }; -/* ------------------------------------------------------------------ */ +/* -------------------------------------------------------------------------- */ -class gActionChannel : public gActionWidget { +class gActionChannel : public gActionWidget +{ private: @@ -125,6 +129,7 @@ private: bool actionCollides(int frame); public: + gActionChannel(int x, int y, gdActionEditor *pParent, class SampleChannel *ch); void draw(); int handle(int e); diff --git a/src/gui/elems/ge_browser.cpp b/src/gui/elems/ge_browser.cpp index 8ae7d09..be64cc8 100644 --- a/src/gui/elems/ge_browser.cpp +++ b/src/gui/elems/ge_browser.cpp @@ -1,10 +1,10 @@ -/* --------------------------------------------------------------------- +/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * gd_browser * - * --------------------------------------------------------------------- + * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual * @@ -24,24 +24,27 @@ * along with Giada - Your Hardcore Loopmachine. If not, see * . * - * ------------------------------------------------------------------ */ + * -------------------------------------------------------------------------- */ #include #include "../../core/const.h" #include "../../utils/utils.h" +#include "../../utils/string.h" #include "../../utils/log.h" +#include "../dialogs/gd_browser.h" #include "ge_browser.h" -gBrowser::gBrowser(int x, int y, int w, int h, const char *L) - : Fl_Hold_Browser(x, y, w, h, L) +gBrowser::gBrowser(int x, int y, int w, int h) + : Fl_File_Browser(x, y, w, h) { box(G_BOX); textsize(GUI_FONT_SIZE_BASE); textcolor(COLOR_TEXT_0); selection_color(COLOR_BG_1); color(COLOR_BG_0); + type(FL_SELECT_BROWSER); this->scrollbar.color(COLOR_BG_0); this->scrollbar.selection_color(COLOR_BG_1); @@ -52,256 +55,117 @@ gBrowser::gBrowser(int x, int y, int w, int h, const char *L) this->hscrollbar.selection_color(COLOR_BG_1); this->hscrollbar.labelcolor(COLOR_BD_1); this->hscrollbar.slider(G_BOX); -} - - -/* ------------------------------------------------------------------ */ - - -gBrowser::~gBrowser() {} - - -/* ------------------------------------------------------------------ */ - -void gBrowser::init(const char *init_path) { - - gLog("[gBrowser] init path = '%s'\n", init_path); - - if (init_path == NULL || !gIsDir(init_path)) { -#if defined(__linux__) || defined(__APPLE__) - path_obj->value("/home"); -#elif defined(_WIN32) - - /* SHGetFolderPath is deprecated. We should use SHGetKnownFolderPath - * but that would break compatibility with XP. On Vista, GetFolderPath - * is a wrapper of GetKnownFolderPath, so no problem. */ - - char winRoot[1024]; - SHGetFolderPath(NULL, CSIDL_COMMON_DESKTOPDIRECTORY, NULL, 0, winRoot); // si parte dal Desktop - path_obj->value(winRoot); -#endif - gLog("[gBrowser] init_path null or invalid, using default\n"); - } - else - path_obj->value(init_path); - - refresh(); - sort(); + take_focus(); // let it have focus on startup } -/* ------------------------------------------------------------------ */ - - -void gBrowser::refresh() { - DIR *dp; - struct dirent *ep; - dp = opendir(path_obj->value()); - if (dp != NULL) { - while ((ep = readdir(dp))) { - - /* skip: - * - "." e ".." - * - hidden files */ - - if (strcmp(ep->d_name, ".") != 0 && strcmp(ep->d_name, "..") != 0) { - if (ep->d_name[0] != '.') { - - /* is it a folder? add square brackets. Is it a file? Append - * a '/' (on Windows seems useless, though) */ - - std::string file = path_obj->value(); - file.insert(file.size(), gGetSlash()); - file += ep->d_name; - - if (gIsDir(file.c_str())) { - char name[PATH_MAX]; - sprintf(name, "@b[%s]", ep->d_name); - add(name); - } - else - if (gIsProject(file.c_str())) { - char name[PATH_MAX]; - sprintf(name, "@i@b%s", ep->d_name); - add(name); - } - else - add(ep->d_name); - } - } - } - closedir(dp); - } - else - gLog("[gBrowser] Couldn't open the directory '%s'\n", path_obj->value()); -} +/* -------------------------------------------------------------------------- */ -/* ------------------------------------------------------------------ */ +void gBrowser::loadDir(const string &dir) +{ + currentDir = dir; + load(currentDir.c_str()); + /* hide "../", it just screws up things */ -void gBrowser::sort() { - for (int t=1; t<=size(); t++) - for (int r=t+1; r<=size(); r++) - if (strcmp(text(t), text(r)) > 0) - swap(t,r); + if (text(1) != NULL && strcmp(text(1), "../") == 0) + remove(1); } -/* ------------------------------------------------------------------ */ - - -void gBrowser::up_dir() { - - /* updir = remove last folder from the path. Start from strlen(-1) to - * skip the trailing slash */ - - int i = strlen(path_obj->value())-1; +/* -------------------------------------------------------------------------- */ - /* on Windows an updir from the path "X:\" (3 chars long) must redirect - * to the list of available devices. */ - -#if defined(_WIN32) - if (i <= 3 || !strcmp(path_obj->value(), "All drives")) { - path_obj->value("All drives"); - showDrives(); - return; - } - else { - while (i >= 0) { - if (path_obj->value()[i] == '\\') - break; - i--; - } - - /* delete the last part of the string, from i to len-i, ie everything - * after the "/" */ - - std::string tmp = path_obj->value(); - tmp.erase(i, tmp.size()-i); - - /* if tmp.size == 2 we have something like 'C:'. Add a trailing - * slash */ - - if (tmp.size() == 2) - tmp += "\\"; - - path_obj->value(tmp.c_str()); - refresh(); - } -#elif defined(__linux__) || defined (__APPLE__) - while (i >= 0) { - if (path_obj->value()[i] == '/') +int gBrowser::handle(int e) +{ + int ret = Fl_File_Browser::handle(e); + switch (e) { + case FL_FOCUS: + case FL_UNFOCUS: + ret = 1; // enables receiving Keyboard events break; - i--; - } - - /* i == 0 means '/', the root dir. It's meaningless to go updir */ - - if (i==0) - path_obj->value("/"); - else { - - /* delete the last part of the string, from i to len-i, ie everything - * after the "/" */ - - std::string tmp = path_obj->value(); - tmp.erase(i, tmp.size()-i); - path_obj->value(tmp.c_str()); - } - refresh(); -#endif + case FL_KEYDOWN: // keyboard + if (Fl::event_key(FL_Down)) + select(value() + 1); + else + if (Fl::event_key(FL_Up)) + select(value() - 1); + else + if (Fl::event_key(FL_Enter)) + ((gdBaseBrowser*) parent())->fireCallback(); + ret = 1; + break; + case FL_PUSH: // mouse + if (Fl::event_clicks() > 0) // double click + ((gdBaseBrowser*) parent())->fireCallback(); + ret = 1; + break; + case FL_RELEASE: // mouse + /* nasty trick to keep the selection on mouse release */ + if (value() > 1) { + select(value() - 1); + select(value() + 1); + } + else { + select(value() + 1); + select(value() - 1); + } + ret = 1; + break; + } + return ret; } +/* -------------------------------------------------------------------------- */ -/* ------------------------------------------------------------------ */ - -void gBrowser::down_dir(const char *path) { - path_obj->value(path); - refresh(); +string gBrowser::getCurrentDir() +{ + return normalize(gGetRealPath(currentDir)); } -/* ------------------------------------------------------------------ */ - - -const char *gBrowser::get_selected_item() { - - /* click on an empty line */ - - if (text(value()) == NULL) - return NULL; - - selected_item = text(value()); - - /* @ = formatting marks. - * @b = bold, i.e. a directory. Erease '@b[' and ']' */ - - if (selected_item[0] == '@') { - if (selected_item[1] == 'b') { - selected_item.erase(0, 3); - selected_item.erase(selected_item.size()-1, 1); - } - else - if (selected_item[1] == 'i') - selected_item.erase(0, 4); - } +/* -------------------------------------------------------------------------- */ -#if defined(__linux__) || defined(__APPLE__) - /* add path to file name, to get an absolute path. Avoid double - * slashes like '//' */ - - if (strcmp("/", path_obj->value())) - selected_item.insert(0, "/"); - - selected_item.insert(0, path_obj->value()); - return selected_item.c_str(); -#elif defined(_WIN32) - - /* if path is 'All drives' we are in the devices list and the user - * has clicked on a device such as 'X:\' */ +string gBrowser::getSelectedItem(bool fullPath) +{ + if (!fullPath) // no full path requested? return the selected text + return normalize(text(value())); + else + if (value() == 0) // no rows selected? return current directory + return normalize(currentDir); + else + return normalize(gGetRealPath(currentDir + G_SLASH + normalize(text(value())))); +} - if (strcmp(path_obj->value(), "All drives") == 0) - return selected_item.c_str(); - else { - /* add '\' if the path is like 'X:\' */ +/* -------------------------------------------------------------------------- */ - if (strlen(path_obj->value()) > 3) /// shouln't it be == 3? - selected_item.insert(0, "\\"); - selected_item.insert(0, path_obj->value()); - return selected_item.c_str(); - } -#endif +void gBrowser::preselect(int pos, int line) +{ + position(pos); + select(line); } -/* ------------------------------------------------------------------ */ +/* -------------------------------------------------------------------------- */ -#ifdef _WIN32 -void gBrowser::showDrives() { - - /* GetLogicalDriveStrings fills drives like that: - * - * a:\[null]b:\[null]c:\[null]...[null][null] - * - * where [null] stands for \0. */ +string gBrowser::normalize(const string &s) +{ + string out = s; - char drives[64]; - char *i = drives; // pointer to 0th element in drives - GetLogicalDriveStrings(64, drives); + /* our crappy version of Clang doesn't seem to support std::string::back() */ - /* code stolen from the web, still unknown. (Jan 09, 2012). */ +#ifdef __APPLE__ + if (out[out.length() - 1] == G_SLASH) +#else + if (out.back() == G_SLASH) +#endif - while (*i) { - add(i); - i = &i[strlen(i) + 1]; - } + out = out.substr(0, out.size()-1); + return out; } - -#endif diff --git a/src/gui/elems/ge_browser.h b/src/gui/elems/ge_browser.h index 0fbc1b7..e338143 100644 --- a/src/gui/elems/ge_browser.h +++ b/src/gui/elems/ge_browser.h @@ -1,10 +1,10 @@ -/* --------------------------------------------------------------------- +/* ----------------------------------------------------------------------------- * * Giada - Your Hardcore Loopmachine * * ge_browser * - * --------------------------------------------------------------------- + * ----------------------------------------------------------------------------- * * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual * @@ -24,45 +24,52 @@ * along with Giada - Your Hardcore Loopmachine. If not, see * . * - * ------------------------------------------------------------------ */ + * -------------------------------------------------------------------------- */ + #ifndef GE_BROWSER_H #define GE_BROWSER_H #include -#include +#include #include #include "ge_mixed.h" -class gBrowser : public Fl_Hold_Browser { + +using std::string; + + +class gBrowser : public Fl_File_Browser +{ +private: + + string currentDir; + + /* normalize + * Make sure the string never ends with a trailing slash. */ + + string normalize(const string &s); + public: - gBrowser(int x, int y, int w, int h, const char *L=0); - ~gBrowser(); - void init(const char *init_path=NULL); - void refresh(); - void sort(); - void up_dir(); - void down_dir(const char *path); - const char *get_selected_item(); - /* path_obj - * the actual path*/ + gBrowser(int x, int y, int w, int h); - class gInput *path_obj; + /* init + * Initialize browser and show 'dir' as initial directory. */ - /* selected_item - * choosen item */ + void loadDir(const string &dir); - std::string selected_item; + /* getSelectedItem + * Return the full path or just the displayed name of the i-th selected item. + * Always with the trailing slash! */ -#ifdef _WIN32 -private: + string getSelectedItem(bool fullPath=true); - /* showDrives [WIN32 only] - * lists all the available drivers */ + string getCurrentDir(); - void showDrives(); -#endif + void preselect(int position, int line); + + int handle(int e); }; #endif diff --git a/src/gui/elems/ge_mixed.cpp b/src/gui/elems/ge_mixed.cpp index 7069e6c..60abf9e 100644 --- a/src/gui/elems/ge_mixed.cpp +++ b/src/gui/elems/ge_mixed.cpp @@ -612,9 +612,10 @@ void gBaseButton::trimLabel() len--; } } - else + else { out = ""; - copy_label(out.c_str()); + } + copy_label(out.c_str()); } diff --git a/src/gui/elems/ge_sampleChannel.cpp b/src/gui/elems/ge_sampleChannel.cpp index d098faa..3838a9e 100644 --- a/src/gui/elems/ge_sampleChannel.cpp +++ b/src/gui/elems/ge_sampleChannel.cpp @@ -38,6 +38,7 @@ #include "../../core/midiChannel.h" #include "../../glue/glue.h" #include "../../glue/channel.h" +#include "../../glue/storage.h" #include "../../utils/gui_utils.h" #include "../dialogs/gd_mainWindow.h" #include "../dialogs/gd_keyGrabber.h" @@ -366,20 +367,21 @@ void gSampleChannel::__cb_readActions() void gSampleChannel::openBrowser(int type) { - const char *title = ""; + gWindow *childWin = NULL; switch (type) { case BROWSER_LOAD_SAMPLE: - title = "Browse Sample"; + childWin = new gdLoadBrowser(G_Conf.browserX, G_Conf.browserY, + G_Conf.browserW, G_Conf.browserH, "Browse sample", + G_Conf.samplePath.c_str(), glue_loadSample, ch); break; case BROWSER_SAVE_SAMPLE: - title = "Save Sample"; - break; - case -1: - title = "Edit Sample"; + childWin = new gdSaveBrowser(G_Conf.browserX, G_Conf.browserY, + G_Conf.browserW, G_Conf.browserH, "Save sample", \ + G_Conf.samplePath.c_str(), "", glue_saveSample, ch); break; } - gWindow *childWin = new gdBrowser(title, G_Conf.samplePath.c_str(), ch, type); - gu_openSubWindow(mainWin, childWin, WID_FILE_BROWSER); + if (childWin) + gu_openSubWindow(mainWin, childWin, WID_FILE_BROWSER); } diff --git a/src/utils/log.cpp b/src/utils/log.cpp index 0834d63..1ebef9f 100644 --- a/src/utils/log.cpp +++ b/src/utils/log.cpp @@ -72,8 +72,12 @@ void gLog(const char *format, ...) { return; va_list args; va_start(args, format); - if (mode == LOG_MODE_FILE && stat == true) + if (mode == LOG_MODE_FILE && stat == true) { vfprintf(f, format, args); +#ifdef _WIN32 + fflush(f); +#endif + } else vprintf(format, args); va_end(args); diff --git a/src/utils/string.cpp b/src/utils/string.cpp new file mode 100644 index 0000000..167ed83 --- /dev/null +++ b/src/utils/string.cpp @@ -0,0 +1,56 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * utils + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine 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. + * + * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#include "string.h" +#include + + +using std::string; + + +string gGetRealPath(const string &path) +{ + string out = ""; + +#if defined(__linux__) || defined(__APPLE__) + + char *buf = realpath(path.c_str(), NULL); + +#else // Windows + + char *buf = _fullpath(NULL, path.c_str(), PATH_MAX); + +#endif + + if (buf) { + out = buf; + free(buf); + } + return out; +} diff --git a/src/utils/string.h b/src/utils/string.h new file mode 100644 index 0000000..758b742 --- /dev/null +++ b/src/utils/string.h @@ -0,0 +1,45 @@ +/* ----------------------------------------------------------------------------- + * + * Giada - Your Hardcore Loopmachine + * + * utils + * + * ----------------------------------------------------------------------------- + * + * Copyright (C) 2010-2016 Giovanni A. Zuliani | Monocasual + * + * This file is part of Giada - Your Hardcore Loopmachine. + * + * Giada - Your Hardcore Loopmachine 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. + * + * Giada - Your Hardcore Loopmachine 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 Giada - Your Hardcore Loopmachine. If not, see + * . + * + * -------------------------------------------------------------------------- */ + + +#ifndef __UTILS_STRING_H__ +#define __UTILS_STRING_H__ + + +#include +#include +#include +#include "log.h" + + +using std::string; + + +string gGetRealPath(const string &path); + +#endif diff --git a/src/utils/utils.cpp b/src/utils/utils.cpp index 1f7a117..16e00f4 100644 --- a/src/utils/utils.cpp +++ b/src/utils/utils.cpp @@ -27,7 +27,6 @@ * -------------------------------------------------------------------------- */ -#include "utils.h" #if defined(_WIN32) // getcwd (unix) or __getcwd (win) #include #include @@ -48,13 +47,16 @@ #include // basename unix #include // getpwuid #endif +#include "../core/const.h" +#include "utils.h" using std::string; using std::vector; -bool gFileExists(const char *filename) { +bool gFileExists(const char *filename) +{ FILE *fh = fopen(filename, "rb"); if (!fh) { return 0; @@ -66,6 +68,12 @@ bool gFileExists(const char *filename) { } +bool gFileExists(const string &filename) +{ + return gFileExists(filename.c_str()); +} + + /* -------------------------------------------------------------------------- */ @@ -111,6 +119,11 @@ bool gIsDir(const char *path) } +bool gIsDir(const string &path) +{ + return gIsDir(path.c_str()); +} + /* -------------------------------------------------------------------------- */ @@ -152,18 +165,11 @@ bool gMkdir(const string &path) /* -------------------------------------------------------------------------- */ -/* TODO - avoid this shit, just wrap the other call */ -string gBasename(const char *path) -{ - string out = path; - out.erase(0, out.find_last_of(gGetSlash().c_str())+1); - return out; -} string gBasename(const string &s) { string out = s; - out.erase(0, out.find_last_of(gGetSlash().c_str())+1); + out.erase(0, out.find_last_of(G_SLASH_STR) + 1); return out; } @@ -171,10 +177,12 @@ string gBasename(const string &s) /* -------------------------------------------------------------------------- */ -string gDirname(const char *path) +string gDirname(const string &path) { + if (path.empty()) + return ""; string out = path; - out.erase(out.find_last_of(gGetSlash().c_str())); + out.erase(out.find_last_of(G_SLASH_STR)); return out; } @@ -241,11 +249,11 @@ string gStripExt(const string &s) /* -------------------------------------------------------------------------- */ -bool gIsProject(const char *path) +bool gIsProject(const string &path) { /** FIXME - checks too weak */ - if (gGetExt(path) == "gprj" && gDirExists(path)) + if (gGetExt(path.c_str()) == "gprj" && gDirExists(path)) return 1; return 0; } @@ -286,18 +294,6 @@ string gGetProjectName(const char *path) } -/* -------------------------------------------------------------------------- */ - - -string gGetSlash() -{ -#if defined(_WIN32) - return "\\"; -#else - return "/"; -#endif -} - /* -------------------------------------------------------------------------- */ diff --git a/src/utils/utils.h b/src/utils/utils.h index c8da493..dd6ecd0 100644 --- a/src/utils/utils.h +++ b/src/utils/utils.h @@ -42,25 +42,27 @@ using std::vector; bool gFileExists(const char *path); +bool gFileExists(const string &path); bool gDirExists(const char *path); bool gDirExists(const string &path); bool gIsDir(const char *path); +bool gIsDir(const string &path); -bool gIsProject(const char *path); +bool gIsProject(const string &path); bool gIsPatch(const char *path); +bool gIsPatch(const string &path); bool gMkdir(const char *path); bool gMkdir(const string &path); -string gBasename(const char *path); string gBasename(const string &s); string gReplace(string in, const string& search, const string& replace); -string gDirname(const char *path); +string gDirname(const string &s); string gTrim(const char *path); string gTrim(const string &s); @@ -78,8 +80,6 @@ string gStripExt(const string &s); string gGetProjectName(const char *path); // TODO - useless! -string gGetSlash(); - string gItoa(int i); void gSplit(string in, string sep, vector *v); diff --git a/tests/resources/test.wav b/tests/resources/test.wav new file mode 100644 index 0000000000000000000000000000000000000000..1e52d36421348915a78897bc55c8aadffaf0b30f GIT binary patch literal 81316 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 #V&R!zxR8Bw;YU-+GTF}|ID=S;hcnC=AOSr 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

SOSYJfMsp~ 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