diff --git a/.github/workflows/build-lte.yml b/.github/workflows/build-lte.yml new file mode 100644 index 00000000..16ad1bb3 --- /dev/null +++ b/.github/workflows/build-lte.yml @@ -0,0 +1,99 @@ +name: Build Lumatone Editor + +on: [push, pull_request] + +env: {} + +jobs: + build-lte-linux: + name: Build Lumatone Editor (Linux) + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Install JUCE to $HOME + run: | + wget https://github.com/juce-framework/JUCE/releases/download/6.1.6/juce-6.1.6-linux.zip + unzip -d ~ juce-6.1.6-linux.zip + - name: Install APT dependencies + run: | + sudo apt update + sudo apt install libssh2-1-dev libasound2-dev libjack-jackd2-dev ladspa-sdk libcurl4-openssl-dev libfreetype6-dev libx11-dev libxcomposite-dev libxcursor-dev libxcursor-dev libxext-dev libxinerama-dev libxrandr-dev libxrender-dev libwebkit2gtk-4.0-dev libglu1-mesa-dev mesa-common-dev + - name: Build Lumatone Editor + run: | + ~/JUCE/Projucer --resave TerpstraSysEx.jucer + cd Builds/Linux + make CONFIG=Release + - name: List build directory + run: | + cd Builds/Linux + find + - name: Upload Lumatone Editor + uses: actions/upload-artifact@v2 + with: + name: LumatoneEditor-Linux + path: Builds/Linux/build/Lumatone\ Editor + if-no-files-found: error + + build-lte-macos: + name: Build Lumatone Editor (macOS) + + runs-on: macos-latest + + steps: + - uses: actions/checkout@v2 + - name: Install JUCE to $HOME + run: | + wget https://github.com/juce-framework/JUCE/releases/download/6.1.6/juce-6.1.6-osx.zip + unzip -d ~ juce-6.1.6-osx.zip + - name: Build Lumatone Editor + run: | + ~/JUCE/Projucer.app/Contents/MacOS/Projucer --resave TerpstraSysEx.jucer + cd Builds/MacOSX + xcodebuild -configuration Release + - name: List build directory + run: | + cd Builds/MacOSX + find . + - name: Upload Lumatone Editor + uses: actions/upload-artifact@v2 + with: + name: LumatoneEditor-macOS + path: Builds/MacOSX/build/Release + if-no-files-found: error + + build-lte-windows: + name: Build Lumatone Editor (Windows) + + runs-on: windows-latest + + steps: + - uses: actions/checkout@v2 + - uses: microsoft/setup-msbuild@v1.1 + - name: Install JUCE to C:\ + run: | + Invoke-WebRequest https://github.com/juce-framework/JUCE/releases/download/6.1.6/juce-6.1.6-windows.zip -O juce-6.1.6-windows.zip + 7z x juce-6.1.6-windows.zip -oC:\ + - name: Build Lumatone Editor + run: | + bash -c '/c/JUCE/Projucer --resave TerpstraSysEx.jucer' + cd Builds/VisualStudio2019 + msbuild "Lumatone Editor.sln" /p:configuration=Release + - name: List build directory + run: | + cd Builds/VisualStudio2019 + Get-ChildItem -Recurse + - name: Upload Lumatone Editor + uses: actions/upload-artifact@v2 + with: + name: LumatoneEditor-Windows + path: "Builds/VisualStudio2019/x64/Release/App/Lumatone Editor.exe" + if-no-files-found: error + - name: Upload dynamic libraries + uses: actions/upload-artifact@v2 + with: + name: LumatoneEditor-Windows + path: Libraries/win64/bin/*.dll + if-no-files-found: error + diff --git a/.gitignore b/.gitignore index 1299bd6b..a7128209 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,5 @@ /Installers/InnoSetup/Install* /Installers/InnoSetup/archive* /Installers/Packages/build* +.vscode +/Releases diff --git a/BinaryData/Localisation/en-gb.txt b/BinaryData/Localisation/en-gb.txt index 16400c33..c513df86 100644 --- a/BinaryData/Localisation/en-gb.txt +++ b/BinaryData/Localisation/en-gb.txt @@ -73,4 +73,7 @@ countries: ca gb au "FreeDrawing" = "Free Drawing" "Linear" = "Linear" "Quadratic" = "Quadratic" -"Ticks" = "ticks" \ No newline at end of file +"Ticks" = "ticks" +"EditButtonTip" = "Edit palette colours" +"CloneButtonTip" = "Clone palette" +"TrashButtonTip" = "Delete palette (currently irreversible)" \ No newline at end of file diff --git a/Installers/InnoSetup/LumatoneEditorWinInstallerScript.iss b/Installers/InnoSetup/LumatoneEditorWinInstallerScript.iss index c80a46d9..d5db9199 100644 --- a/Installers/InnoSetup/LumatoneEditorWinInstallerScript.iss +++ b/Installers/InnoSetup/LumatoneEditorWinInstallerScript.iss @@ -2,10 +2,11 @@ ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! #define MyAppName "Lumatone Editor" -#define MyAppVersion "1.0.0" +#define MyAppVersion "1.0.2" #define MyAppPublisher "Lumatone" #define MyAppURL "https://www.lumatone.io/" #define MyAppExeName "Lumatone Editor.exe" +#define MyAppExeNameDest "Lumatone Editor.exe" #define MappingAssocName MyAppName + " Mapping" #define MappingAssocExt ".ltn" #define MappingAssocKey StringChange(MappingAssocName, " ", "") + MappingAssocExt @@ -23,6 +24,7 @@ AppPublisher={#MyAppPublisher} AppPublisherURL={#MyAppURL} AppSupportURL={#MyAppURL} AppUpdatesURL={#MyAppURL} +AllowNoIcons=yes DefaultDirName={autopf64}\{#MyAppName} ChangesAssociations=yes DisableProgramGroupPage=yes @@ -33,7 +35,7 @@ OutputDir=.\ OutputBaseFilename=Install {#MyAppName} {#MyAppVersion} Compression=lzma SolidCompression=yes -UsePreviousAppDir=no +UsePreviousAppDir=yes WizardStyle=modern [Languages] @@ -42,8 +44,8 @@ Name: "english"; MessagesFile: "compiler:Default.isl" [Tasks] Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; Flags: unchecked -[Files] -Source: "..\..\Builds\VisualStudio2019\x64\Release\App\{#MyAppExeName}"; DestDir: "{app}"; Flags: ignoreversion +[Files] +Source: "..\..\Builds\VisualStudio2019\x64\Release\App\{#MyAppExeName}"; DestDir: "{app}"; DestName: "{#MyAppExeNameDest}"; Flags: ignoreversion Source: "..\..\Presets\Mappings\*"; DestDir: "{userdocs}\{#MyAppName}\Mappings"; Flags: ignoreversion recursesubdirs createallsubdirs uninsneveruninstall Source: "..\..\Presets\Palettes\*"; DestDir: "{userdocs}\{#MyAppName}\Palettes"; Flags: ignoreversion recursesubdirs createallsubdirs uninsneveruninstall Source: "..\..\Libraries\win64\bin\libssh2-x64.dll"; DestDir: "{app}"; Flags: ignoreversion @@ -57,14 +59,14 @@ Source: "vc_redist.x64.exe"; DestDir: "{tmp}"; Flags: ignoreversion deleteafteri Root: HKA; Subkey: "Software\Classes\{#MappingAssocExt}\OpenWithProgIds"; ValueType: string; ValueName: "{#MappingAssocKey}"; ValueData: "%"; Flags: uninsdeletevalue Root: HKA; Subkey: "Software\Classes\{#MappingAssocExt}"; ValueType: string; ValueName: ""; ValueData: "{#MappingAssocName}"; Flags: uninsdeletekey Root: HKA; Subkey: "Software\Classes\{#MappingAssocExt}\DefaultIcon"; ValueType: string; ValueName: ""; ValueData: "{app}\ltn.ico" -Root: HKA; Subkey: "Software\Classes\{#MappingAssocExt}\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#MyAppExeName}"" ""%1""" -Root: HKA; Subkey: "Software\Classes\Applications\{#MyAppExeName}\SupportedTypes"; ValueType: string; ValueName: {#MappingAssocExt}; ValueData: "" +Root: HKA; Subkey: "Software\Classes\{#MappingAssocExt}\shell\open\command"; ValueType: string; ValueName: ""; ValueData: """{app}\{#MyAppExeNameDest}"" ""%1""" +Root: HKA; Subkey: "Software\Classes\Applications\{#MyAppExeNameDest}\SupportedTypes"; ValueType: string; ValueName: {#MappingAssocExt}; ValueData: "" Root: HKA; Subkey: "Software\Classes\{#PaletteAssocExt}"; ValueType: string; ValueName: ""; ValueData: "{#PaletteAssocName}"; Flags: uninsdeletekey Root: HKA; Subkey: "Software\Classes\{#PaletteAssocExt}\DefaultIcon"; ValueType: string; ValueName: ""; ValueData:"{app}\ltp.ico" [Icons] -Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}" -Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon +Name: "{autoprograms}\{#MyAppName}"; Filename: "{app}\{#MyAppExeNameDest}" +Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeNameDest}"; Tasks: desktopicon [Code] // Pulled from https://stackoverflow.com/questions/24574035/how-to-install-microsoft-vc-redistributables-silently-in-inno-setup @@ -94,5 +96,5 @@ end; [Run] Filename: "{tmp}\vc_redist.x64.exe"; Description: "Install VC Redistributable package if necessary"; Parameters: "/q /norestart /c:""msiexec /q /i vcredist.msi"""; \ Check: VCRedistNeedsInstall; StatusMsg: "Installing Microsoft Visual C++ 2015, may take a few minutes..." -Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent +Filename: "{app}\{#MyAppExeNameDest}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent diff --git a/Installers/Packages/Lumatone Editor/LICENSE.txt b/Installers/Packages/Lumatone Editor/LICENSE.txt new file mode 100644 index 00000000..e478e419 --- /dev/null +++ b/Installers/Packages/Lumatone Editor/LICENSE.txt @@ -0,0 +1,11 @@ +Copyright 2022 Lumatone Inc. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + +2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. \ No newline at end of file diff --git a/Installers/Packages/Lumatone Editor/Lumatone Editor.pkgproj b/Installers/Packages/Lumatone Editor/Lumatone Editor.pkgproj index ec3e3450..9a49d3ac 100644 --- a/Installers/Packages/Lumatone Editor/Lumatone Editor.pkgproj +++ b/Installers/Packages/Lumatone Editor/Lumatone Editor.pkgproj @@ -38,7 +38,7 @@ GID 80 PATH - ../../../Builds/MacOSX/build/Release/Lumatone Editor.app + ../../../Releases/macOS/Latest/Lumatone Editor.app PATH_TYPE 1 PERMISSIONS @@ -48,6 +48,22 @@ UID 0 + + CHILDREN + + GID + 80 + PATH + Utilities + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + -1 + UID + 0 + GID 80 @@ -62,6 +78,22 @@ UID 0 + + CHILDREN + + GID + 0 + PATH + bin + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + -1 + UID + 0 + CHILDREN @@ -81,6 +113,22 @@ UID 0 + + CHILDREN + + GID + 0 + PATH + Audio + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + CHILDREN @@ -97,6 +145,22 @@ UID 0 + + CHILDREN + + GID + 0 + PATH + ColorPickers + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + CHILDREN @@ -145,6 +209,22 @@ UID 0 + + CHILDREN + + GID + 80 + PATH + Fonts + PATH_TYPE + 0 + PERMISSIONS + 1021 + TYPE + 1 + UID + 0 + CHILDREN @@ -399,6 +479,71 @@ UID 0 + + CHILDREN + + + CHILDREN + + GID + 0 + PATH + etc + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + -1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + var + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + -1 + UID + 0 + + + GID + 0 + PATH + private + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + -1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + sbin + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + -1 + UID + 0 + CHILDREN @@ -407,50 +552,17 @@ CHILDREN - - - CHILDREN - - GID - 0 - PATH - /Users/vincenzo/Programming/Projects/TerpstraSysEx.2014/Presets/Mappings - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 3 - UID - 0 - - - CHILDREN - - GID - 0 - PATH - /Users/vincenzo/Programming/Projects/TerpstraSysEx.2014/Presets/Palettes - PATH_TYPE - 0 - PERMISSIONS - 493 - TYPE - 3 - UID - 0 - - + GID 0 PATH - Lumatone Editor + Extensions PATH_TYPE - 2 + 0 PERMISSIONS - 509 + 493 TYPE - 2 + 1 UID 0 @@ -458,6 +570,39 @@ GID 0 PATH + Library + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + GID + 0 + PATH + System + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + 1 + UID + 0 + + + CHILDREN + + + CHILDREN + + GID + 0 + PATH Shared PATH_TYPE 0 @@ -482,6 +627,136 @@ UID 0 + + CHILDREN + + + CHILDREN + + GID + 0 + PATH + bin + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + -1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + include + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + -1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + lib + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + -1 + UID + 0 + + + CHILDREN + + + CHILDREN + + GID + 0 + PATH + bin + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + -1 + UID + 0 + + + GID + 0 + PATH + local + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + -1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + sbin + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + -1 + UID + 0 + + + CHILDREN + + GID + 0 + PATH + share + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + -1 + UID + 0 + + + GID + 0 + PATH + usr + PATH_TYPE + 0 + PERMISSIONS + 493 + TYPE + -1 + UID + 0 + GID 0 @@ -501,7 +776,7 @@ PRESERVE_EXTENDED_ATTRIBUTES SHOW_INVISIBLE - + SPLIT_FORKS TREAT_MISSING_FILES_AS_WARNING @@ -513,8 +788,10 @@ POSTINSTALL_PATH + PATH + ../../../Scripts/pkg-install-presets.sh PATH_TYPE - 0 + 1 PREINSTALL_PATH @@ -522,7 +799,24 @@ 0 RESOURCES - + + + CHILDREN + + GID + 0 + PATH + ../../../Presets + PATH_TYPE + 1 + PERMISSIONS + 493 + TYPE + 3 + UID + 0 + + PACKAGE_SETTINGS @@ -531,7 +825,7 @@ CONCLUSION_ACTION 0 FOLLOW_SYMBOLIC_LINKS - + IDENTIFIER com.Lumatone.pkg.LumatoneEditor LOCATION @@ -539,7 +833,7 @@ NAME Lumatone Editor OVERWRITE_PERMISSIONS - + PAYLOAD_SIZE -1 REFERENCE_PATH @@ -549,7 +843,7 @@ USE_HFS+_COMPRESSION VERSION - 0.9.9 + 1.0.2 TYPE 0 @@ -579,6 +873,43 @@ SHARED_SETTINGS_FOR_ALL_APPAREANCES + INSTALLATION TYPE + + HIERARCHIES + + INSTALLER + + LIST + + + CHILDREN + + DESCRIPTION + + OPTIONS + + HIDDEN + + STATE + 1 + + PACKAGE_UUID + 63444C20-3865-4E17-B60F-C117465E6F52 + TITLE + + TYPE + 0 + UUID + E63EC134-3C02-43B0-B1DA-1F438C914E56 + + + REMOVED + + + + MODE + 0 + INSTALLATION_STEPS @@ -646,7 +977,19 @@ LICENSE LOCALIZATIONS - + + + LANGUAGE + English + VALUE + + PATH + /Users/vito/Programming/Projects/TerpstraSysEx.2014/Installers/Packages/Lumatone Editor/LICENSE.txt + PATH_TYPE + 1 + + + MODE 0 @@ -655,6 +998,11 @@ LOCALIZATIONS + SUMMARY + + LOCALIZATIONS + + TITLE LOCALIZATIONS @@ -684,7 +1032,7 @@ BUILD_PATH PATH - build + ../../.. PATH_TYPE 1 @@ -859,7 +1207,9 @@ NAME Lumatone Editor PAYLOAD_ONLY - + + REFERENCE_FOLDER_PATH + /Users/soundtoys/Programming/Vito/TerpstraSysEx.2014 TREAT_MISSING_PRESENTATION_DOCUMENTS_AS_WARNING diff --git a/Libraries/mac/include/gcrypt.h b/Libraries/mac/include/gcrypt.h index 084bb286..50be8242 100644 --- a/Libraries/mac/include/gcrypt.h +++ b/Libraries/mac/include/gcrypt.h @@ -62,11 +62,11 @@ extern "C" { return the same version. The purpose of this macro is to let autoconf (using the AM_PATH_GCRYPT macro) check that this header matches the installed library. */ -#define GCRYPT_VERSION "1.9.2" +#define GCRYPT_VERSION "1.9.4-beta25" /* The version number of this header. It may be used to handle minor API incompatibilities. */ -#define GCRYPT_VERSION_NUMBER 0x010902 +#define GCRYPT_VERSION_NUMBER 0x010904 /* Internal: We can't use the convenience macros for the multi @@ -1274,7 +1274,7 @@ enum gcry_md_algos GCRY_MD_BLAKE2S_128 = 325, GCRY_MD_SM3 = 326, GCRY_MD_SHA512_256 = 327, - GCRY_MD_SHA512_224 = 328, + GCRY_MD_SHA512_224 = 328 }; /* Flags used with the open function. */ diff --git a/Libraries/mac/include/gpg-error.h b/Libraries/mac/include/gpg-error.h index 5643b87a..1750d58c 100644 --- a/Libraries/mac/include/gpg-error.h +++ b/Libraries/mac/include/gpg-error.h @@ -18,7 +18,7 @@ * SPDX-License-Identifier: LGPL-2.1+ * * Do not edit. Generated from gpg-error.h.in for: - x86_64-apple-darwin19.6.0 + arm-apple-darwin21.4.0 */ /* The GnuPG project consists of many components. Error codes are @@ -66,12 +66,12 @@ #include /* The version string of this header. */ -#define GPG_ERROR_VERSION "1.41" -#define GPGRT_VERSION "1.41" +#define GPG_ERROR_VERSION "1.43-beta11" +#define GPGRT_VERSION "1.43-beta11" /* The version number of this header. */ -#define GPG_ERROR_VERSION_NUMBER 0x012900 -#define GPGRT_VERSION_NUMBER 0x012900 +#define GPG_ERROR_VERSION_NUMBER 0x012b00 +#define GPGRT_VERSION_NUMBER 0x012b00 #ifdef __GNUC__ @@ -122,6 +122,7 @@ typedef enum GPG_ERR_SOURCE_KLEO = 13, GPG_ERR_SOURCE_G13 = 14, GPG_ERR_SOURCE_ASSUAN = 15, + GPG_ERR_SOURCE_TPM2D = 16, GPG_ERR_SOURCE_TLS = 17, GPG_ERR_SOURCE_ANY = 31, GPG_ERR_SOURCE_USER_1 = 32, diff --git a/Libraries/mac/include/gpgrt.h b/Libraries/mac/include/gpgrt.h index 5643b87a..1750d58c 100644 --- a/Libraries/mac/include/gpgrt.h +++ b/Libraries/mac/include/gpgrt.h @@ -18,7 +18,7 @@ * SPDX-License-Identifier: LGPL-2.1+ * * Do not edit. Generated from gpg-error.h.in for: - x86_64-apple-darwin19.6.0 + arm-apple-darwin21.4.0 */ /* The GnuPG project consists of many components. Error codes are @@ -66,12 +66,12 @@ #include /* The version string of this header. */ -#define GPG_ERROR_VERSION "1.41" -#define GPGRT_VERSION "1.41" +#define GPG_ERROR_VERSION "1.43-beta11" +#define GPGRT_VERSION "1.43-beta11" /* The version number of this header. */ -#define GPG_ERROR_VERSION_NUMBER 0x012900 -#define GPGRT_VERSION_NUMBER 0x012900 +#define GPG_ERROR_VERSION_NUMBER 0x012b00 +#define GPGRT_VERSION_NUMBER 0x012b00 #ifdef __GNUC__ @@ -122,6 +122,7 @@ typedef enum GPG_ERR_SOURCE_KLEO = 13, GPG_ERR_SOURCE_G13 = 14, GPG_ERR_SOURCE_ASSUAN = 15, + GPG_ERR_SOURCE_TPM2D = 16, GPG_ERR_SOURCE_TLS = 17, GPG_ERR_SOURCE_ANY = 31, GPG_ERR_SOURCE_USER_1 = 32, diff --git a/Libraries/mac/include/libssh2.h b/Libraries/mac/include/libssh2.h index d33df03c..d064b316 100644 --- a/Libraries/mac/include/libssh2.h +++ b/Libraries/mac/include/libssh2.h @@ -1,5 +1,5 @@ /* Copyright (c) 2004-2009, Sara Golemon - * Copyright (c) 2009-2015 Daniel Stenberg + * Copyright (c) 2009-2021 Daniel Stenberg * Copyright (c) 2010 Simon Josefsson * All rights reserved. * @@ -40,19 +40,19 @@ #ifndef LIBSSH2_H #define LIBSSH2_H 1 -#define LIBSSH2_COPYRIGHT "2004-2019 The libssh2 project and its contributors." +#define LIBSSH2_COPYRIGHT "2004-2021 The libssh2 project and its contributors." /* We use underscore instead of dash when appending DEV in dev versions just to make the BANNER define (used by src/session.c) be a valid SSH banner. Release versions have no appended strings and may of course not have dashes either. */ -#define LIBSSH2_VERSION "1.9.0" +#define LIBSSH2_VERSION "1.10.1_DEV" /* The numeric version number is also available "in parts" by using these defines: */ -#define LIBSSH2_VERSION_MAJOR 1 -#define LIBSSH2_VERSION_MINOR 9 -#define LIBSSH2_VERSION_PATCH 0 +#define LIBSSH2_VERSION_MAJOR 1 +#define LIBSSH2_VERSION_MINOR 10 +#define LIBSSH2_VERSION_PATCH 1 /* This is the numeric version of the libssh2 version number, meant for easier parsing and comparions by programs. The LIBSSH2_VERSION_NUM define will @@ -69,7 +69,7 @@ and it is always a greater number in a more recent release. It makes comparisons with greater than and less than work. */ -#define LIBSSH2_VERSION_NUM 0x010900 +#define LIBSSH2_VERSION_NUM 0x010a01 /* * This is the date and time when the full source package was created. The @@ -80,7 +80,7 @@ * * "Mon Feb 12 11:35:33 UTC 2007" */ -#define LIBSSH2_TIMESTAMP "Thu Jun 20 06:19:26 UTC 2019" +#define LIBSSH2_TIMESTAMP "DEV" #ifndef RC_INVOKED @@ -100,7 +100,7 @@ extern "C" { /* Allow alternate API prefix from CFLAGS or calling app */ #ifndef LIBSSH2_API # ifdef LIBSSH2_WIN32 -# ifdef _WINDLL +# if defined(_WINDLL) || defined(libssh2_EXPORTS) # ifdef LIBSSH2_LIBRARY # define LIBSSH2_API __declspec(dllexport) # else @@ -235,9 +235,11 @@ typedef off_t libssh2_struct_stat_size; /* Default generate and safe prime sizes for diffie-hellman-group-exchange-sha1 */ -#define LIBSSH2_DH_GEX_MINGROUP 1024 -#define LIBSSH2_DH_GEX_OPTGROUP 1536 -#define LIBSSH2_DH_GEX_MAXGROUP 2048 +#define LIBSSH2_DH_GEX_MINGROUP 2048 +#define LIBSSH2_DH_GEX_OPTGROUP 4096 +#define LIBSSH2_DH_GEX_MAXGROUP 8192 + +#define LIBSSH2_DH_MAX_MODULUS_BITS 16384 /* Defaults for pty requests */ #define LIBSSH2_TERM_WIDTH 80 @@ -503,6 +505,7 @@ typedef struct _LIBSSH2_POLLFD { #define LIBSSH2_ERROR_KNOWN_HOSTS -46 #define LIBSSH2_ERROR_CHANNEL_WINDOW_FULL -47 #define LIBSSH2_ERROR_KEYFILE_AUTH_FAILED -48 +#define LIBSSH2_ERROR_RANDGEN -49 /* this is a define to provide the old (<= 1.2.7) name */ #define LIBSSH2_ERROR_BANNER_NONE LIBSSH2_ERROR_BANNER_RECV @@ -545,7 +548,7 @@ LIBSSH2_API void libssh2_free(LIBSSH2_SESSION *session, void *ptr); * * Fills algs with a list of supported acryptographic algorithms. Returns a * non-negative number (number of supported algorithms) on success or a - * negative number (an eror code) on failure. + * negative number (an error code) on failure. * * NOTE: on success, algs must be deallocated (by calling libssh2_free) when * not needed anymore @@ -688,7 +691,7 @@ libssh2_userauth_publickey_frommemory(LIBSSH2_SESSION *session, * response_callback is provided with filled by library prompts array, * but client must allocate and fill individual responses. Responses * array is already allocated. Responses data will be freed by libssh2 - * after callback return, but before subsequent callback invokation. + * after callback return, but before subsequent callback invocation. */ LIBSSH2_API int libssh2_userauth_keyboard_interactive_ex(LIBSSH2_SESSION* session, @@ -718,7 +721,7 @@ LIBSSH2_API int libssh2_poll(LIBSSH2_POLLFD *fds, unsigned int nfds, #define SSH_EXTENDED_DATA_STDERR 1 -/* Returned by any function that would block during a read/write opperation */ +/* Returned by any function that would block during a read/write operation */ #define LIBSSH2CHANNEL_EAGAIN LIBSSH2_ERROR_EAGAIN LIBSSH2_API LIBSSH2_CHANNEL * @@ -761,6 +764,8 @@ LIBSSH2_API int libssh2_channel_setenv_ex(LIBSSH2_CHANNEL *channel, (unsigned int)strlen(varname), (value), \ (unsigned int)strlen(value)) +LIBSSH2_API int libssh2_channel_request_auth_agent(LIBSSH2_CHANNEL *channel); + LIBSSH2_API int libssh2_channel_request_pty_ex(LIBSSH2_CHANNEL *channel, const char *term, unsigned int term_len, @@ -987,7 +992,7 @@ libssh2_knownhost_init(LIBSSH2_SESSION *session); #define LIBSSH2_KNOWNHOST_KEYENC_RAW (1<<16) #define LIBSSH2_KNOWNHOST_KEYENC_BASE64 (2<<16) -/* type of key (3 bits) */ +/* type of key (4 bits) */ #define LIBSSH2_KNOWNHOST_KEY_MASK (15<<18) #define LIBSSH2_KNOWNHOST_KEY_SHIFT 18 #define LIBSSH2_KNOWNHOST_KEY_RSA1 (1<<18) @@ -1165,7 +1170,7 @@ libssh2_knownhost_writefile(LIBSSH2_KNOWNHOSTS *hosts, * libssh2_knownhost_get() * * Traverse the internal list of known hosts. Pass NULL to 'prev' to get - * the first one. Or pass a poiner to the previously returned one to get the + * the first one. Or pass a pointer to the previously returned one to get the * next. * * Returns: @@ -1221,7 +1226,7 @@ libssh2_agent_list_identities(LIBSSH2_AGENT *agent); * libssh2_agent_get_identity() * * Traverse the internal list of public keys. Pass NULL to 'prev' to get - * the first one. Or pass a poiner to the previously returned one to get the + * the first one. Or pass a pointer to the previously returned one to get the * next. * * Returns: diff --git a/Libraries/mac/include/libssh2_sftp.h b/Libraries/mac/include/libssh2_sftp.h index 4a750b3e..476ea870 100644 --- a/Libraries/mac/include/libssh2_sftp.h +++ b/Libraries/mac/include/libssh2_sftp.h @@ -189,32 +189,32 @@ struct _LIBSSH2_SFTP_STATVFS { #define LIBSSH2_FXF_EXCL 0x00000020 /* SFTP Status Codes (returned by libssh2_sftp_last_error() ) */ -#define LIBSSH2_FX_OK 0 -#define LIBSSH2_FX_EOF 1 -#define LIBSSH2_FX_NO_SUCH_FILE 2 -#define LIBSSH2_FX_PERMISSION_DENIED 3 -#define LIBSSH2_FX_FAILURE 4 -#define LIBSSH2_FX_BAD_MESSAGE 5 -#define LIBSSH2_FX_NO_CONNECTION 6 -#define LIBSSH2_FX_CONNECTION_LOST 7 -#define LIBSSH2_FX_OP_UNSUPPORTED 8 -#define LIBSSH2_FX_INVALID_HANDLE 9 -#define LIBSSH2_FX_NO_SUCH_PATH 10 -#define LIBSSH2_FX_FILE_ALREADY_EXISTS 11 -#define LIBSSH2_FX_WRITE_PROTECT 12 -#define LIBSSH2_FX_NO_MEDIA 13 -#define LIBSSH2_FX_NO_SPACE_ON_FILESYSTEM 14 -#define LIBSSH2_FX_QUOTA_EXCEEDED 15 -#define LIBSSH2_FX_UNKNOWN_PRINCIPLE 16 /* Initial mis-spelling */ -#define LIBSSH2_FX_UNKNOWN_PRINCIPAL 16 -#define LIBSSH2_FX_LOCK_CONFlICT 17 /* Initial mis-spelling */ -#define LIBSSH2_FX_LOCK_CONFLICT 17 -#define LIBSSH2_FX_DIR_NOT_EMPTY 18 -#define LIBSSH2_FX_NOT_A_DIRECTORY 19 -#define LIBSSH2_FX_INVALID_FILENAME 20 -#define LIBSSH2_FX_LINK_LOOP 21 - -/* Returned by any function that would block during a read/write opperation */ +#define LIBSSH2_FX_OK 0UL +#define LIBSSH2_FX_EOF 1UL +#define LIBSSH2_FX_NO_SUCH_FILE 2UL +#define LIBSSH2_FX_PERMISSION_DENIED 3UL +#define LIBSSH2_FX_FAILURE 4UL +#define LIBSSH2_FX_BAD_MESSAGE 5UL +#define LIBSSH2_FX_NO_CONNECTION 6UL +#define LIBSSH2_FX_CONNECTION_LOST 7UL +#define LIBSSH2_FX_OP_UNSUPPORTED 8UL +#define LIBSSH2_FX_INVALID_HANDLE 9UL +#define LIBSSH2_FX_NO_SUCH_PATH 10UL +#define LIBSSH2_FX_FILE_ALREADY_EXISTS 11UL +#define LIBSSH2_FX_WRITE_PROTECT 12UL +#define LIBSSH2_FX_NO_MEDIA 13UL +#define LIBSSH2_FX_NO_SPACE_ON_FILESYSTEM 14UL +#define LIBSSH2_FX_QUOTA_EXCEEDED 15UL +#define LIBSSH2_FX_UNKNOWN_PRINCIPLE 16UL /* Initial mis-spelling */ +#define LIBSSH2_FX_UNKNOWN_PRINCIPAL 16UL +#define LIBSSH2_FX_LOCK_CONFlICT 17UL /* Initial mis-spelling */ +#define LIBSSH2_FX_LOCK_CONFLICT 17UL +#define LIBSSH2_FX_DIR_NOT_EMPTY 18UL +#define LIBSSH2_FX_NOT_A_DIRECTORY 19UL +#define LIBSSH2_FX_INVALID_FILENAME 20UL +#define LIBSSH2_FX_LINK_LOOP 21UL + +/* Returned by any function that would block during a read/write operation */ #define LIBSSH2SFTP_EAGAIN LIBSSH2_ERROR_EAGAIN /* SFTP API */ diff --git a/Libraries/mac/lib/libgcrypt.20.dylib b/Libraries/mac/lib/libgcrypt.20.dylib old mode 100644 new mode 100755 index a332c6d2..9e574876 Binary files a/Libraries/mac/lib/libgcrypt.20.dylib and b/Libraries/mac/lib/libgcrypt.20.dylib differ diff --git a/Libraries/mac/lib/libgcrypt.dylib b/Libraries/mac/lib/libgcrypt.dylib new file mode 120000 index 00000000..5984cb08 --- /dev/null +++ b/Libraries/mac/lib/libgcrypt.dylib @@ -0,0 +1 @@ +libgcrypt.20.dylib \ No newline at end of file diff --git a/Libraries/mac/lib/libgpg-error.0.dylib b/Libraries/mac/lib/libgpg-error.0.dylib old mode 100644 new mode 100755 index 9afab543..771b7136 Binary files a/Libraries/mac/lib/libgpg-error.0.dylib and b/Libraries/mac/lib/libgpg-error.0.dylib differ diff --git a/Libraries/mac/lib/libgpg-error.dylib b/Libraries/mac/lib/libgpg-error.dylib new file mode 120000 index 00000000..36cbccca --- /dev/null +++ b/Libraries/mac/lib/libgpg-error.dylib @@ -0,0 +1 @@ +libgpg-error.0.dylib \ No newline at end of file diff --git a/Libraries/mac/lib/libssh2.1.dylib b/Libraries/mac/lib/libssh2.1.dylib index 581001fc..9b7bf2da 100755 Binary files a/Libraries/mac/lib/libssh2.1.dylib and b/Libraries/mac/lib/libssh2.1.dylib differ diff --git a/Libraries/mac/lib/libssh2.dylib b/Libraries/mac/lib/libssh2.dylib new file mode 120000 index 00000000..235229a5 --- /dev/null +++ b/Libraries/mac/lib/libssh2.dylib @@ -0,0 +1 @@ +libssh2.1.dylib \ No newline at end of file diff --git a/Scripts/install.sh b/Scripts/install.sh new file mode 100644 index 00000000..96e5001e --- /dev/null +++ b/Scripts/install.sh @@ -0,0 +1,50 @@ +#!/bin/bash + +USAGE="Please run in the same directory as the Presets folder without sudo." + +if [[ $EUID == 0 ]]; then + echo You are running this script as root. + echo $USAGE + exit 1 +fi + +if [[ ! -d "./Presets" ]]; then + echo "No ./Presets folder found." + echo $USAGE + exit 1 +fi + +PRESETS=`pwd`/Presets + +cd ~/Documents + +if [[ ! -d "Lumatone Editor" ]]; then + echo 'Creating "~/Documents/Lumatone Editor/"' + mkdir "Lumatone Editor" && chmod 777 "Lumatone Editor" && FOLDERCREATED=1 + if [[ ! $FOLDERCREATED ]]; then + echo "Error creating '~/Documents/Lumatone Editor', try creating it manually and run this again." + exit 1 + fi +fi + +echo "Copying preset files..." +cp -n -r ${PRESETS}/* "Lumatone Editor" && FILESCOPIED=1 +if [[ ! $FILESCOPIED ]]; then + echo 'Sorry, there was a problem copying files. Just move the "Mappings" & "Palettes" folders from ./Presets into "~/Documents/Lumatone Editor/" to manually install.' + exit 1 +fi + +chmod -R 777 "Lumatone Editor" + +echo "Preset installation successful! Feel free to delete the local Presets folder." +echo +echo "Before running Lumatone Editor, you must install the dependency \"libssh2\"." +echo +echo "For debian-based distros use:" +echo " sudo apt install libssh2" +echo +echo "For arch-based distros use:" +echo " sudo pacman -S libssh2" +echo +echo "Or alternatively whatever equivalent command for the package manager you use." +exit 0 diff --git a/Scripts/package-linux-release.sh b/Scripts/package-linux-release.sh new file mode 100644 index 00000000..8f4c88c8 --- /dev/null +++ b/Scripts/package-linux-release.sh @@ -0,0 +1,57 @@ +#!/bin/bash + +USAGE="usage: package-linux-release [ version_id ] ?tidy" + +LTE_BIN=`pwd`/../Builds/Linux/build/Lumatone\ Editor + +if [[ ! $LTE_BIN ]]; then + echo $USAGE + echo No binary found. + exit 1 +fi + +[[ $1 ]] && VERSION=$1 +[[ ! $VERSION ]] && echo "No version specified" && exit 1 +echo "preparing zip for version ${VERSION}" + +[[ -d `pwd`/../Releases/ ]] || mkdir `pwd`/../Releases + +PARENT_DIR=`pwd`/../Releases/Linux +TARGET=$PARENT_DIR/$VERSION + +([[ -d $PARENT_DIR ]] && rm -r $TARGET/) || mkdir $PARENT_DIR +mkdir $TARGET + +echo copying files... && \ + cp "${LTE_BIN}" $TARGET/ && \ + cp ./install.sh $TARGET/ && \ + cp ../LICENSE $TARGET/ && \ + cp -r ../Presets $TARGET/ && \ + # todo install deps + # todo readme + COPIED=1 + +[[ ! $COPIED ]] && exit 1 + +cd ${TARGET} + +echo "Run the install.sh script to install presets, or move the Presets child folders to ~/Documents/Lumatone Editor/" > INSTALL +echo "You will also need to install the libssh2 dependency." >> INSTALL +echo "Then simply launch 'Lumatone Editor' to get started!" >> INSTALL + +ZIP_OUT="LumatoneEditor-${VERSION}-Linux.zip" + +zip -r $ZIP_OUT ./* && ZIPPED=1 +if [[ $ZIPPED ]]; then + echo wrote $ZIP_OUT +else + echo "Error zipping files for ${ZIP_OUT}" + exit 1 +fi + +if [[ $2 == "tidy" ]]; then + echo cleaning residuals... + find ${TARGET}/* \( ! -name "*.zip" \) -delete +elif [[ $2 ]]; then + echo unknown argument \"${2}\" +fi diff --git a/Scripts/pkg-install-presets.sh b/Scripts/pkg-install-presets.sh new file mode 100644 index 00000000..03d5213d --- /dev/null +++ b/Scripts/pkg-install-presets.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +PRESETS=`pwd`/Presets + +cd ~/Documents + +[[ -d "Lumatone Editor" ]] || mkdir "Lumatone Editor" && chmod 777 "Lumatone Editor" + +cp -n -r ${PRESETS}/* "Lumatone Editor" + +chmod -R 777 "Lumatone Editor" + +exit 0 diff --git a/Source/AllKeysOverview.cpp b/Source/AllKeysOverview.cpp index 3b6cc172..0e418bf4 100644 --- a/Source/AllKeysOverview.cpp +++ b/Source/AllKeysOverview.cpp @@ -40,12 +40,12 @@ KeyMiniDisplayInsideAllKeysOverview::KeyMiniDisplayInsideAllKeysOverview(int new boardIndex = newBoardIndex; keyIndex = newKeyIndex; - TerpstraSysExApplication::getApp().getLumatoneController().addMidiListener(this); +// TerpstraSysExApplication::getApp().getLumatoneController()->addMidiListener(this); } KeyMiniDisplayInsideAllKeysOverview::~KeyMiniDisplayInsideAllKeysOverview() { - TerpstraSysExApplication::getApp().getLumatoneController().removeMidiListener(this); +// TerpstraSysExApplication::getApp().getLumatoneController()->removeMidiListener(this); } void KeyMiniDisplayInsideAllKeysOverview::paint(Graphics& g) @@ -61,8 +61,8 @@ void KeyMiniDisplayInsideAllKeysOverview::paint(Graphics& g) if (colourGraphic && shadowGraphic) { - int x = round((getWidth() - colourGraphic->getWidth()) * 0.5f); - int y = round((getHeight() - colourGraphic->getHeight()) * 0.5f); + int x = roundToInt((getWidth() - colourGraphic->getWidth()) * 0.5f); + int y = roundToInt((getHeight() - colourGraphic->getHeight()) * 0.5f); g.drawImageAt(*colourGraphic, x, y, true); g.drawImageAt(*shadowGraphic, x, y); @@ -92,7 +92,7 @@ void KeyMiniDisplayInsideAllKeysOverview::mouseDown(const MouseEvent& e) // Right mouse click: popup menu PopupMenu menu; TerpstraSysExApplication::getApp().getMainMenu()->createEditMenu(menu); - menu.show(); + menu.showMenuAsync(PopupMenu::Options()); } // TODO integrate interaction through LumatoneController @@ -175,43 +175,49 @@ void KeyMiniDisplayInsideAllKeysOverview::setKeyGraphics(Image& colourGraphicIn, //[/MiscUserDefs] //============================================================================== -AllKeysOverview::AllKeysOverview() - : Component("AllKeysOverview") +AllKeysOverview::AllKeysOverview () { - //[Constructor_pre] You can add your own custom stuff here.. - //[/Constructor_pre] + //[Constructor_pre] You can add your own custom stuff here.. + //[/Constructor_pre] - btnLoadFile.reset(new juce::TextButton("btnLoadFile")); - addAndMakeVisible(btnLoadFile.get()); + setName ("AllKeysOverview"); + btnLoadFile.reset (new juce::TextButton ("btnLoadFile")); + addAndMakeVisible (btnLoadFile.get()); + btnLoadFile->setButtonText (TRANS("LoadFile")); + btnLoadFile->addListener (this); - btnLoadFile->setButtonText(translate("LoadFile")); - btnLoadFile->getProperties().set(LumatoneEditorStyleIDs::textButtonIconHashCode, LumatoneEditorIcon::LoadIcon); - btnLoadFile->addListener(this); + btnLoadFile->setBounds (368, 8, 96, 24); - btnSaveFile.reset(new juce::TextButton("btnSaveFile")); - addAndMakeVisible(btnSaveFile.get()); - btnSaveFile->setButtonText(translate("SaveFile")); - btnSaveFile->getProperties().set(LumatoneEditorStyleIDs::textButtonIconHashCode, LumatoneEditorIcon::SaveIcon); - btnSaveFile->addListener(this); + btnSaveFile.reset (new juce::TextButton ("btnSaveFile")); + addAndMakeVisible (btnSaveFile.get()); + btnSaveFile->setButtonText (TRANS("SaveFile")); + btnSaveFile->addListener (this); - buttonReceive.reset(new juce::TextButton("buttonReceive")); - addAndMakeVisible(buttonReceive.get()); - buttonReceive->setTooltip(translate("ImportTooltip")); - buttonReceive->setButtonText(translate("Import from Lumatone")); - buttonReceive->getProperties().set(LumatoneEditorStyleIDs::textButtonIconHashCode, LumatoneEditorIcon::ArrowUp); - buttonReceive->getProperties().set(LumatoneEditorStyleIDs::textButtonIconPlacement, LumatoneEditorStyleIDs::TextButtonIconPlacement::RightOfText); - buttonReceive->addListener(this); + btnSaveFile->setBounds (472, 8, 96, 24); - tilingGeometry.setColumnAngle(LUMATONEGRAPHICCOLUMNANGLE); - tilingGeometry.setRowAngle(LUMATONEGRAPHICROWANGLE); + buttonReceive.reset (new juce::TextButton ("buttonReceive")); + addAndMakeVisible (buttonReceive.get()); + buttonReceive->setTooltip (TRANS("ImportTooltip")); + buttonReceive->setButtonText (TRANS("Import from Lumatone")); + buttonReceive->addListener (this); + + buttonReceive->setBounds (584, 8, 176, 24); //[UserPreSize] + btnLoadFile->getProperties().set(LumatoneEditorStyleIDs::textButtonIconHashCode, LumatoneEditorIcon::LoadIcon); + btnSaveFile->getProperties().set(LumatoneEditorStyleIDs::textButtonIconHashCode, LumatoneEditorIcon::SaveIcon); + buttonReceive->getProperties().set(LumatoneEditorStyleIDs::textButtonIconHashCode, LumatoneEditorIcon::ArrowUp); + buttonReceive->getProperties().set(LumatoneEditorStyleIDs::textButtonIconPlacement, LumatoneEditorStyleIDs::TextButtonIconPlacement::RightOfText); lblFirmwareVersion.reset(new Label("FirmwareVersionLabel")); addChildComponent(lblFirmwareVersion.get()); - TerpstraSysExApplication::getApp().getLumatoneController().addFirmwareListener(this); + tilingGeometry.setColumnAngle(LUMATONEGRAPHICCOLUMNANGLE); + tilingGeometry.setRowAngle(LUMATONEGRAPHICROWANGLE); + + TerpstraSysExApplication::getApp().getLumatoneController()->addStatusListener(this); + TerpstraSysExApplication::getApp().getLumatoneController()->addFirmwareListener(this); resetOctaveSize(); @@ -222,7 +228,7 @@ AllKeysOverview::AllKeysOverview() //[Constructor] You can add your own custom stuff here.. currentSetSelection = -1; - + buttonReceive->setVisible(false); showDeveloperMode(TerpstraSysExApplication::getApp().getPropertiesFile()->getBoolValue("DeveloperMode", false)); //[/Constructor] } @@ -283,27 +289,27 @@ void AllKeysOverview::resized() graphicWidth, graphicHeight ); - int btnHeight = round(getHeight() * saveLoadH); - int btnMargin = round(getWidth() * saveloadMarginW); - int saveLoadWidth = round(getWidth() * saveLoadW); - int btnY = lumatoneBounds.getY() - round(getHeight() * btnYFromImageTop); + int btnHeight = roundToInt(getHeight() * saveLoadH); + int btnMargin = roundToInt(getWidth() * saveloadMarginW); + int saveLoadWidth = roundToInt(getWidth() * saveLoadW); + int btnY = lumatoneBounds.getY() - roundToInt(getHeight() * btnYFromImageTop); - int halfWidthX = round(getWidth() * 0.5f); + int halfWidthX = roundToInt(getWidth() * 0.5f); btnLoadFile->setBounds(halfWidthX - btnMargin - saveLoadWidth, btnY, saveLoadWidth, btnHeight); btnSaveFile->setBounds(halfWidthX + btnMargin, btnY, saveLoadWidth, btnHeight); - octaveLineY = lumatoneBounds.getBottom() + round(getHeight() * octaveLineYRatio); + octaveLineY = lumatoneBounds.getBottom() + roundToInt(getHeight() * octaveLineYRatio); - int importY = lumatoneBounds.getY() - round(getHeight() * importYFromImageTop); - int importWidth = round(getWidth() * importW); + int importY = lumatoneBounds.getY() - roundToInt(getHeight() * importYFromImageTop); + int importWidth = roundToInt(getWidth() * importW); buttonReceive->setBounds(lumatoneBounds.getRight() - importWidth, importY, importWidth, btnHeight); resizeLabelWithHeight(lblFirmwareVersion.get(), btnHeight * 0.6f); lblFirmwareVersion->setTopLeftPosition(lumatoneBounds.getX(), lumatoneBounds.getY() - btnHeight * 0.6f); - int keyWidth = round(lumatoneBounds.getWidth() * keyW); - int keyHeight = round(lumatoneBounds.getHeight() * keyH); + int keyWidth = roundToInt(lumatoneBounds.getWidth() * keyW); + int keyHeight = roundToInt(lumatoneBounds.getHeight() * keyH); // Scale key graphics once lumatoneGraphic = imageProcessor->resizeImage(ImageCache::getFromHashCode(LumatoneEditorAssets::LumatoneGraphic), lumatoneBounds.getWidth(), lumatoneBounds.getHeight()); @@ -388,12 +394,16 @@ void AllKeysOverview::setFirmwareVersion(FirmwareVersion versionIn) { if (versionIn.revision == 55) { - lblFirmwareVersion->setText("Prototype 55-keys", NotificationType::dontSendNotification); + lblFirmwareVersion->setText("55-keys Prototype", NotificationType::dontSendNotification); } } else { +#if JUCE_DEBUG lblFirmwareVersion->setText("Firmware version: " + versionIn.toString(), NotificationType::dontSendNotification); +#else + lblFirmwareVersion->setText("Firmware version: " + versionIn.toDisplayString(), NotificationType::dontSendNotification); +#endif } lblFirmwareVersion->setVisible(true); @@ -409,9 +419,22 @@ void AllKeysOverview::setFirmwareVersion(FirmwareVersion versionIn) void AllKeysOverview::showDeveloperMode(bool developerModeOn) { + if (developerModeOn) + buttonReceive->setVisible(true); + repaint(); } +void AllKeysOverview::connectionEstablished(int, int) +{ + buttonReceive->setVisible(true); +} + +void AllKeysOverview::connectionLost() +{ + buttonReceive->setVisible(false); +} + void AllKeysOverview::firmwareRevisionReceived(FirmwareVersion version) { setFirmwareVersion(version); @@ -456,8 +479,8 @@ void AllKeysOverview::resetOctaveSize() BEGIN_JUCER_METADATA - @@ -469,7 +492,7 @@ BEGIN_JUCER_METADATA virtualName="" explicitFocusOrder="0" pos="472 8 96 24" buttonText="Save File" connectedEdges="0" needsCallback="1" radioGroupId="0"/> diff --git a/Source/AllKeysOverview.h b/Source/AllKeysOverview.h index 31ee5975..ed37f17e 100644 --- a/Source/AllKeysOverview.h +++ b/Source/AllKeysOverview.h @@ -31,7 +31,7 @@ // Representation of a key inside the overview -class KeyMiniDisplayInsideAllKeysOverview : public Component, public LumatoneController::MidiListener +class KeyMiniDisplayInsideAllKeysOverview : public Component, public LumatoneEditor::MidiListener { public: KeyMiniDisplayInsideAllKeysOverview(int newBoardIndex, int newKeyIndex); @@ -82,7 +82,8 @@ class KeyMiniDisplayInsideAllKeysOverview : public Component, public LumatoneCon //[/Comments] */ class AllKeysOverview : public juce::Component, - public LumatoneController::FirmwareListener, + public LumatoneEditor::StatusListener, + public LumatoneEditor::FirmwareListener, public juce::Button::Listener { public: @@ -101,6 +102,12 @@ class AllKeysOverview : public juce::Component, void setFirmwareVersion(FirmwareVersion versionIn); void resetOctaveSize(); + + // LumatoneEditor::StatusListener + void connectionEstablished(int, int) override; + void connectionLost() override; + + // LumatoneEditor::FirmwareListener implementation void firmwareRevisionReceived(FirmwareVersion version) override; //[/UserMethods] diff --git a/Source/ApplicationListeners.h b/Source/ApplicationListeners.h new file mode 100644 index 00000000..d6d8ddca --- /dev/null +++ b/Source/ApplicationListeners.h @@ -0,0 +1,112 @@ +/* + ============================================================================== + + ApplicationListeners.h + Created: 22 Mar 2022 10:11:10pm + Author: Vincenzo Sicurella + + Various listener and enums that should be shared throughout the app + + ============================================================================== +*/ + +#pragma once +#include +#include "LumatoneFirmwareDefinitions.h" + +enum sysExSendingMode +{ + liveEditor = 0, + offlineEditor = 1, + firmwareUpdate = 2 +}; + +namespace LumatoneEditor +{ + //============================================================================ + // Public interface for Lumatone connection status + + class StatusListener + { + public: + + virtual ~StatusListener() {} + + virtual void connectionFailed() {} + virtual void connectionEstablished(int inputMidiDevice, int outputMidiDevice) {} + virtual void connectionLost() {} + }; + + //============================================================================ + // Public interface for Lumatone firmware communication + class FirmwareListener + { + public: + + virtual ~FirmwareListener() {} + + // rgbFlag uses 0 for red, 1 for green, 2 for blue + virtual void octaveColourConfigReceived(int octaveIndex, uint8 rgbFlag, const int* colourData) {}; + + virtual void octaveChannelConfigReceived(int octaveIndex, const int* channelData) {}; + + virtual void octaveNoteConfigReceived(int octaveIndex, const int* noteData) {}; + + virtual void keyTypeConfigReceived(int octaveIndex, const int* keyTypeData) {}; + + virtual void velocityConfigReceived(const int* velocityData) {}; + + virtual void aftertouchConfigReceived(const int* aftertouch) {}; + + virtual void velocityIntervalConfigReceived(const int* velocityData) {}; + + virtual void faderConfigReceived(const int* faderData) {}; + + virtual void faderTypeConfigReceived(int octaveIndex, const int* faderTypeData) {}; + + virtual void serialIdentityReceived(int inputDeviceIndex, const int* serialBytes) {}; + + virtual void calibratePitchModWheelAnswer(TerpstraMIDIAnswerReturnCode code) {}; + + virtual void lumatouchConfigReceived(const int* lumatouchData) {}; + + virtual void firmwareRevisionReceived(FirmwareVersion version) {}; + + virtual void pingResponseReceived(int inputDeviceIndex, unsigned int pingValue) {}; + + virtual void peripheralMidiChannelsReceived(PeripheralChannelSettings channelSettings) {}; + + virtual void pedalCalibrationDataReceived(int minBound, int maxBound, bool pedalIsActive) {}; + + virtual void wheelsCalibrationDataReceived(WheelsCalibrationData calibrationData) {}; + + virtual void presetFlagsReceived(PresetFlags presetFlags) {}; + + virtual void expressionPedalSensitivityReceived(int sensitivity) {}; + + virtual void noAnswerToCommand(int cmd) {}; + }; + + + class EditorListener + { + public: + + virtual ~EditorListener() {} + + // TODO - change this to "status" and use "disconnected", "offline", "live", "firmware" + virtual void editorModeChanged(sysExSendingMode newEditorMode) {} + // virtual void keyFunctionConfigurationChanged(int octaveNumber, int keyNumber, int noteOrCC, int midiChannel, LumatoneKeyType keyType, bool faderUpIsNull) {}; + // virtual void keyColourConfigurationChanged(int octaveNumber, int keyNumber, Colour keyColour) {}; + }; + + + class MidiListener + { + public: + + virtual ~MidiListener() {} + + virtual void handleMidiMessage(const MidiMessage& msg) = 0; + }; +} diff --git a/Source/BoardGeometry.cpp b/Source/BoardGeometry.cpp index c66791f9..43c5c034 100644 --- a/Source/BoardGeometry.cpp +++ b/Source/BoardGeometry.cpp @@ -47,6 +47,9 @@ TerpstraBoardGeometry::TerpstraBoardGeometry() this->rightUpwardLines.add(StraightLine({ 54, 53 })); this->firstColumnOffsets = Array({ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 5 }); + this->rowOffsets = Array({-4, -3, -3, -2, -2, -1, -1, 0, 0, 2, 6}); + this->boardXOffset = 7; + this->boardYOffset = -2; } else { @@ -77,8 +80,11 @@ TerpstraBoardGeometry::TerpstraBoardGeometry() this->rightUpwardLines.add(StraightLine({ 55, 53 })); this->firstColumnOffsets = Array({ 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 4 }); + this->rowOffsets = Array({-4, -3, -3, -2, -2, -1, -1, 0, 0, 2, 5}); + this->boardXOffset = 7; + this->boardYOffset = -2; } - + maxHorizontalLineSize = 0; for (auto line : horizontalLines) @@ -87,6 +93,21 @@ TerpstraBoardGeometry::TerpstraBoardGeometry() } +// Given a board number and key index, compute Cartesian coordinates for the given key, +// the x axis is the shallow diagonal, and the y axis is the right-upward diagonal. +// (0,0) is at the top left, so most coordinates are negative, but that's okay for our purposes. +Point TerpstraBoardGeometry::coordinatesForKey (int boardIndex, int keyIndex) const { + for (int lineIx = 0; lineIx < this->horizontalLines.size(); lineIx++) { + int rowIx = this->horizontalLines[lineIx].indexOf(keyIndex); + if (rowIx != -1) { + return (Point(this->boardXOffset * boardIndex + rowIx + this->rowOffsets[lineIx], this->boardYOffset * boardIndex - lineIx)); + } + } + // If we get here, the requested key was out of range. + jassert(false); + return Point(0,0); +} + // returns the unique straight line that contains the given field TerpstraBoardGeometry::StraightLine TerpstraBoardGeometry::getLineOfField(int fieldIndex, StraightLineSet lineSet) const { @@ -259,4 +280,4 @@ Array> TerpstraBoardGeometry::getOctaveCoordinates(int boardIndex) co } return pointsOut; -} \ No newline at end of file +} diff --git a/Source/BoardGeometry.h b/Source/BoardGeometry.h index 3e7aa071..36b1520d 100644 --- a/Source/BoardGeometry.h +++ b/Source/BoardGeometry.h @@ -51,12 +51,17 @@ class TerpstraBoardGeometry Array> getOctaveCoordinates(int boardIndex) const; + Point coordinatesForKey (int boardIndex, int keyIndex) const; + // Attributes private: StraightLineSet horizontalLines; StraightLineSet rightUpwardLines; - Array firstColumnOffsets; - int maxHorizontalLineSize; + Array firstColumnOffsets; + Array rowOffsets; // Horizonal offset of each horizontal line on the board, relative to the lower left key. + int boardXOffset; // Offset along shallow diagonals between analogous keys from one board to the next (typically 7) + int boardYOffset; // Offset along up/right diagonals between analogous keys from one board to the next (typically -2) + int maxHorizontalLineSize; }; #endif // BOARDGEOMETRY_H_INCLUDED diff --git a/Source/ColourPaletteComponent.cpp b/Source/ColourPaletteComponent.cpp index da1e752c..00b8fc10 100644 --- a/Source/ColourPaletteComponent.cpp +++ b/Source/ColourPaletteComponent.cpp @@ -103,11 +103,21 @@ void ColourPaletteComponent::deselectColour() PaletteControlGroup::PaletteControlGroup(LumatoneEditorColourPalette newPaletteIn) : palette(ColourPaletteComponent(newPaletteIn)), - editButton("EditButton_" + newPaletteIn.getName(), translate("EditButtonTip")), + editButton("EditButton_" + newPaletteIn.getName()), + cloneButton("CloneButton_" + newPaletteIn.getName()), trashButton("TrashButton_" + newPaletteIn.getName()) { editButton.setButtonText("Edit"); editButton.getProperties().set(LumatoneEditorStyleIDs::textButtonHyperlinkFlag, 1); + editButton.setTooltip(translate("EditButtonTip")); + + const Image cloneIcon = getCachedCloneImage(); + cloneButton.setImages(false, true, true, + cloneIcon, 1.0f, Colour(), + cloneIcon, 1.0f, Colours::white.withAlpha(0.4f), + cloneIcon, 1.0f, Colour() + ); + cloneButton.setTooltip(translate("CloneButtonTip")); const Image trashIcon = ImageCache::getFromHashCode(LumatoneEditorAssets::TrashCanIcon); trashButton.setImages(false, true, true, @@ -115,4 +125,5 @@ PaletteControlGroup::PaletteControlGroup(LumatoneEditorColourPalette newPaletteI trashIcon, 1.0f, Colours::white.withAlpha(0.4f), trashIcon, 1.0f, Colour() ); + trashButton.setTooltip(translate("TrashButtonTip")); } diff --git a/Source/ColourPaletteComponent.h b/Source/ColourPaletteComponent.h index 298afe90..49463f72 100644 --- a/Source/ColourPaletteComponent.h +++ b/Source/ColourPaletteComponent.h @@ -64,11 +64,14 @@ class PaletteControlGroup TextButton* getEditButton() { return &editButton; } + ImageButton* getCloneButton() { return &cloneButton; } + ImageButton* getTrashButton() { return &trashButton; } private: ColourPaletteComponent palette; TextButton editButton; + ImageButton cloneButton; ImageButton trashButton; -}; \ No newline at end of file +}; diff --git a/Source/ColourPaletteDataStructure.h b/Source/ColourPaletteDataStructure.h index 528eb432..c72a04c5 100644 --- a/Source/ColourPaletteDataStructure.h +++ b/Source/ColourPaletteDataStructure.h @@ -51,12 +51,11 @@ class LumatoneEditorColourPalette LumatoneEditorColourPalette(Array colourPaletteIn, String paletteName, String paletteAuthor, String paletteNotes="") : LumatoneEditorColourPalette(colourPaletteIn, paletteName) { - name = paletteName; author = paletteAuthor; notes = paletteNotes; - - setColours(colourPaletteIn); } + + LumatoneEditorColourPalette clone() const { return LumatoneEditorColourPalette(colourPalette, name, author, notes); } int size() const { return colourPalette.size(); } @@ -127,6 +126,10 @@ class LumatoneEditorColourPalette return false; } + bool saveToFile() + { + return saveToFile(File(pathToFile)); + } ValueTree toValueTree() const { diff --git a/Source/ColourPaletteWindow.cpp b/Source/ColourPaletteWindow.cpp index a7304dc8..53dd9e69 100644 --- a/Source/ColourPaletteWindow.cpp +++ b/Source/ColourPaletteWindow.cpp @@ -15,13 +15,16 @@ // ColourPaletteWindow Definitions ColourPaletteWindow::ColourPaletteWindow(Array& colourPalettesIn) - : colourPalettes(colourPalettesIn) + : lookAndFeel(TerpstraSysExApplication::getApp().getLookAndFeel()), + colourPalettes(colourPalettesIn) { + setLookAndFeel(&lookAndFeel); + setName("ColourPaletteWindow"); - paletteGroup.reset(new ColourSelectionGroup()); + colourSelectorGroup.reset(new ColourSelectionGroup()); - palettePanel.reset(new ColourPalettesPanel(colourPalettes, paletteGroup.get())); + palettePanel.reset(new ColourPalettesPanel(colourPalettes, colourSelectorGroup.get())); palettePanel->addListener(this); palettePanelViewport.reset(new Viewport("PalettePanelViewport")); @@ -30,7 +33,8 @@ ColourPaletteWindow::ColourPaletteWindow(Array& col palettePanelViewport->getVerticalScrollBar().setColour(ScrollBar::ColourIds::thumbColourId, Colour(0xff2d3135)); customPickerPanel.reset(new CustomPickerPanel()); - paletteGroup->addSelector(customPickerPanel.get()); + colourSelectorGroup->addSelector(customPickerPanel.get()); + colourSelectorGroup->addColourSelectionListener(customPickerPanel.get()); colourToolTabs.reset(new TabbedComponent(TabbedButtonBar::Orientation::TabsAtTop)); colourToolTabs->setName("ColourSelectionToolTabs"); @@ -39,19 +43,25 @@ ColourPaletteWindow::ColourPaletteWindow(Array& col colourToolTabs->setColour(TabbedComponent::ColourIds::outlineColourId, Colour()); colourToolTabs->getTabbedButtonBar().getProperties().set(LumatoneEditorStyleIDs::fontHeightScalar, 0.9f); addAndMakeVisible(*colourToolTabs); - + + const int firstTabIndex = TerpstraSysExApplication::getApp().getPropertiesFile()->getIntValue("LastColourPopupTabIndex"); + colourToolTabs->setCurrentTabIndex(firstTabIndex); colourToolTabs->getTabbedButtonBar().addChangeListener(this); } ColourPaletteWindow::~ColourPaletteWindow() { - paletteGroup->removeSelector(customPickerPanel.get()); - - palettePanelViewport = nullptr; + paletteEditPanel = nullptr; colourToolTabs = nullptr; - palettePanel = nullptr; + + colourSelectorGroup->removeSelector(customPickerPanel.get()); customPickerPanel = nullptr; - paletteEditPanel = nullptr; + + palettePanelViewport = nullptr; + palettePanel = nullptr; + colourSelectorGroup = nullptr; + + setLookAndFeel(nullptr); } void ColourPaletteWindow::resized() @@ -62,11 +72,7 @@ void ColourPaletteWindow::resized() if (paletteEditPanel.get()) paletteEditPanel->setBounds(getLocalBounds()); - palettePanel->setViewUnits( - palettePanelViewport->getMaximumVisibleWidth(), - palettePanelViewport->getMaximumVisibleHeight() - ); - palettePanel->rebuildPanel(colourPalettes); + palettePanel->rebuildPanel(colourPalettes, palettePanelViewport->getMaximumVisibleWidth()); } void ColourPaletteWindow::startEditingPalette(int paletteIndexIn, int selectedSwatchIndex) @@ -83,6 +89,14 @@ void ColourPaletteWindow::startEditingPalette(int paletteIndexIn, int selectedSw paletteEditPanel->setSelectedSwatch(selectedSwatchIndex); } +void ColourPaletteWindow::duplicatePalette(int paletteIndexIn) +{ + auto copiedPalette = colourPalettes[paletteIndexIn].clone(); + colourPalettes.insert(paletteIndexIn + 1, copiedPalette); + TerpstraSysExApplication::getApp().saveColourPalette(copiedPalette); + palettePanel->rebuildPanel(colourPalettes); +} + void ColourPaletteWindow::removePalette(int paletteIndexToRemove) { // Remove loaded colour palette @@ -104,6 +118,16 @@ void ColourPaletteWindow::editPaletteRequested(int paletteIndex, int selectedSwa jassert(true); // Something bad happened! } +void ColourPaletteWindow::clonePaletteRequested(int paletteIndex) +{ + if (paletteIndex >= 0 && paletteIndex < colourPalettes.size()) + { + duplicatePalette(paletteIndex); + } + else + jassert(true); // Something bad happened! +} + void ColourPaletteWindow::deletePaletteRequested(int paletteIndex) { if (paletteIndex >= 0 && paletteIndex < colourPalettes.size()) @@ -118,19 +142,13 @@ void ColourPaletteWindow::newPaletteRequested() { paletteEditingIsNew = true; colourPalettes.insert(0, LumatoneEditorColourPalette()); - startEditingPalette(0); + startEditingPalette(0, 0); } void ColourPaletteWindow::changeListenerCallback(ChangeBroadcaster* source) { - // Custom picker colour changed - if (source == &colourToolTabs->getTabbedButtonBar()) - { - customPickerPanel->setCurrentColour(paletteGroup->getSelectedColour()); - } - // Palette editing finished - else if (source == paletteEditPanel.get()) + if (source == paletteEditPanel.get()) { if (paletteEditPanel->wasSaveRequested()) { @@ -140,34 +158,9 @@ void ColourPaletteWindow::changeListenerCallback(ChangeBroadcaster* source) palette.setColours(paletteEditPanel->getCurrentPalette()); String newName = paletteEditPanel->getPaletteName(); - - // TODO: Move this elsewhere? - String paletteFilePath = palette.getPathToFile(); - File paletteFile; - // File name handling if palette already exists - if (File::isAbsolutePath(paletteFilePath)) - { - paletteFile = paletteFilePath; - - if (palette.getName() != newName) - { - // Delete previous version to ensure name is up to date. - // This is optional - palettes can retain file name and have palette - // name changed if that's preferred behavior - if (paletteFile.existsAsFile()) - { - paletteFile.deleteFile(); - - // Rename file with new palette name - paletteFile = paletteFile.getSiblingFile(newName).withFileExtension(PALETTEFILEEXTENSION); - } - } - } - palette.setName(newName); - // Save to properties - TerpstraSysExApplication::getApp().saveColourPalette(palette, paletteFile); + TerpstraSysExApplication::getApp().saveColourPalette(palette); } else jassert(true); // Something bad happened! @@ -182,4 +175,10 @@ void ColourPaletteWindow::changeListenerCallback(ChangeBroadcaster* source) paletteEditingIsNew = false; paletteEditPanel = nullptr; } + + else if (source == &colourToolTabs->getTabbedButtonBar()) + { + const int newTab = colourToolTabs->getCurrentTabIndex(); + TerpstraSysExApplication::getApp().getPropertiesFile()->setValue("LastColourPopupTabIndex", newTab); + } } diff --git a/Source/ColourPaletteWindow.h b/Source/ColourPaletteWindow.h index 8c5496c0..b57d42ae 100644 --- a/Source/ColourPaletteWindow.h +++ b/Source/ColourPaletteWindow.h @@ -37,7 +37,26 @@ class ColourPaletteWindow : public juce::Component, /// Registers a listener to receive the colour the user's input resolves to /// /// - void listenToColourSelection(ColourSelectionListener* listenerIn) { paletteGroup->addColourSelectionListener(listenerIn); } + void listenToColourSelection(ColourSelectionListener* listenerIn) { colourSelectorGroup->addColourSelectionListener(listenerIn); } + + /// + /// Adds a colour selector component to the colour selection group + /// + /// + void addColourSelectorToGroup(ColourSelectionBroadcaster* broadcaster) { colourSelectorGroup->addSelector(broadcaster); } + + /// + /// Force a colour selector (added to the group) to be selected + /// + /// + void setCurrentColourSelector(ColourSelectionBroadcaster* newSelector) + { + // This statement is kind of a kludge - vsicurella + if (colourSelectorGroup->getIndexOfSelector(newSelector) < 0) + colourSelectorGroup->addSelector(newSelector); + + colourSelectorGroup->setCurrentSelector(newSelector); + } private: @@ -47,6 +66,12 @@ class ColourPaletteWindow : public juce::Component, /// void startEditingPalette(int paletteIndexIn, int selectedSwatchIndex = -1); + /// + /// Creates a new palette with the same name and swatches + /// + /// + void duplicatePalette(int paletteIndexIn); + /// /// Removes a palette and associated buttons /// @@ -57,11 +82,15 @@ class ColourPaletteWindow : public juce::Component, void editPaletteRequested(int paletteIndex, int selectedSwatchIndex) override; + void clonePaletteRequested(int paletteIndex) override; + void deletePaletteRequested(int paletteIndex) override; void newPaletteRequested() override; private: + + LumatoneEditorLookAndFeel lookAndFeel; Array& colourPalettes; @@ -72,7 +101,7 @@ class ColourPaletteWindow : public juce::Component, std::unique_ptr palettePanelViewport; - std::unique_ptr paletteGroup; + std::unique_ptr colourSelectorGroup; int paletteIndexEditing = -1; bool paletteEditingIsNew = false; diff --git a/Source/ColourSelectionGroup.h b/Source/ColourSelectionGroup.h index 6e9cdac5..3521ad78 100644 --- a/Source/ColourSelectionGroup.h +++ b/Source/ColourSelectionGroup.h @@ -18,6 +18,7 @@ class ColourSelectionBroadcaster; class ColourSelectionListener { public: + virtual ~ColourSelectionListener() {} virtual void colourChangedCallback(ColourSelectionBroadcaster* source, Colour newColour) = 0; }; @@ -26,14 +27,14 @@ class ColourSelectionBroadcaster public: ColourSelectionBroadcaster() {}; - virtual ~ColourSelectionBroadcaster() {}; + virtual ~ColourSelectionBroadcaster() {} virtual Colour getSelectedColour() = 0; virtual void deselectColour() = 0; void addColourSelectionListener(ColourSelectionListener* listenerIn) { selectorListeners.add(listenerIn); } - void removeColourSelectionListener(ColourSelectionListener* listenerIn) { selectorListeners.add(listenerIn); } + void removeColourSelectionListener(ColourSelectionListener* listenerIn) { selectorListeners.remove(listenerIn); } protected: @@ -45,6 +46,12 @@ class ColourSelectionGroup : public ColourSelectionBroadcaster, { public: + ~ColourSelectionGroup() + { + for (auto selector : colourSelectors) + selector->removeColourSelectionListener(this); + } + /// /// Adds a ColourPaletteComponent and returns the palette's group index /// @@ -60,12 +67,12 @@ class ColourSelectionGroup : public ColourSelectionBroadcaster, return colourSelectors.indexOf(selectorToAdd); } - void removeSelector(ColourSelectionBroadcaster* paletteComponentToRemove) + void removeSelector(ColourSelectionBroadcaster* selectorToRemove) { - int removed = colourSelectors.removeAllInstancesOf(paletteComponentToRemove); + int removed = colourSelectors.removeAllInstancesOf(selectorToRemove); if (removed > 0) - paletteComponentToRemove->removeColourSelectionListener(this); + selectorToRemove->removeColourSelectionListener(this); } /// @@ -105,6 +112,23 @@ class ColourSelectionGroup : public ColourSelectionBroadcaster, return colourSelectors.indexOf(selectorIn); } + void setCurrentSelector(ColourSelectionBroadcaster* selector) + { + auto index = colourSelectors.indexOf(selector); + + if (index >= 0 && index < colourSelectors.size()) + { + if (selectedBroadcasterIndex >= 0 && getSelectedBroadcaster() != selector) + { + getSelectedBroadcaster()->deselectColour(); + } + + selectedBroadcasterIndex = index; + + selectorListeners.call(&ColourSelectionListener::colourChangedCallback, selector, selector->getSelectedColour()); + } + } + //========================================================================= /// @@ -114,14 +138,7 @@ class ColourSelectionGroup : public ColourSelectionBroadcaster, /// void colourChangedCallback(ColourSelectionBroadcaster* source, Colour newColour) override { - if (getSelectedBroadcaster() && getSelectedBroadcaster() != source) - { - getSelectedBroadcaster()->deselectColour(); - } - - selectedBroadcasterIndex = colourSelectors.indexOf(source); - - selectorListeners.call(&ColourSelectionListener::colourChangedCallback, source, newColour); + setCurrentSelector(source); } private: @@ -129,4 +146,4 @@ class ColourSelectionGroup : public ColourSelectionBroadcaster, Array colourSelectors; int selectedBroadcasterIndex = -1; -}; \ No newline at end of file +}; diff --git a/Source/ColourSelectionPanels.h b/Source/ColourSelectionPanels.h index 17b88eb6..9ff0bafb 100644 --- a/Source/ColourSelectionPanels.h +++ b/Source/ColourSelectionPanels.h @@ -32,88 +32,116 @@ class ColourPalettesPanel : public Component newPaletteBtn->setButtonText(translate("NewPalette")); newPaletteBtn->getProperties().set(LumatoneEditorStyleIDs::textButtonHyperlinkFlag, 1); newPaletteBtn->onClick = [&] { listeners.call(&ColourPalettesPanel::Listener::newPaletteRequested); }; - - flexBox.flexWrap = FlexBox::Wrap::wrap; - flexBox.justifyContent = FlexBox::JustifyContent::flexStart; + }; - // Used to make this component resize itself depending on how many swatches there are - void setViewUnits(int widthIn, int heightIn) + ~ColourPalettesPanel() { - viewableWidth = widthIn; - viewableHeight = heightIn; + for (auto palette : allPalettes) + selectionGroup->removeSelector(palette); - setSize(viewableWidth, getHeightFromNumRows(numRows)); + paletteLabels.clear(); + controlGroups.clear(); + newPaletteBtn = nullptr; + newPalette = nullptr; } - int getHeightFromNumRows(int numRowsIn) + // Used to make this component resize itself depending on how many swatches there are + //void setViewUnits(int widthIn, int heightIn) + //{ + // viewableWidth = widthIn; + // viewableHeight = heightIn; + + // setSize(viewableWidth, getHeightFromNumRows(numRows)); + //} + + int getHeightFromNumRows(int widthIn, int numRowsIn) { - return round(viewableHeight * 0.5f * numRowsIn); + float rowHeight = widthIn * (itemHeightScalar + topMarginScalar + bottomMarginScalar); + auto height = roundToInt(numRowsIn * rowHeight); + return height; } void paint(Graphics& g) override { - ////Draws rectangles around items and margins -// for (auto item : flexBox.items) -// { -// g.setColour(Colours::red); -// g.drawRect(item.currentBounds); -// -// g.setColour(Colours::green); -// g.drawRect(item.currentBounds.getX(), item.currentBounds.getBottom(), item.width, item.margin.bottom, 1.0f); -// -// g.setColour(Colours::yellow); -// g.drawRect(item.currentBounds.getX() - item.margin.left - 1, item.currentBounds.getY(), item.margin.left - 1, item.currentBounds.getHeight(), 1.0f); -// g.drawRect(item.currentBounds.getRight() + 1, item.currentBounds.getY(), item.margin.right - 1, item.currentBounds.getHeight(), 1.0f); -// -// g.setColour(Colours::violet); -// g.drawRect(item.currentBounds.getX(), item.currentBounds.getY() - item.margin.top, item.width, item.margin.top); -// } + //Draws rectangles around items and margins + //for (auto item : dbgItems) + //{ + // g.setColour(Colours::red); + // g.drawRect(item.currentBounds); + + // g.setColour(Colours::green); + // g.drawRect(item.currentBounds.getX(), item.currentBounds.getBottom(), item.width, item.margin.bottom, 1.0f); + + // g.setColour(Colours::yellow); + // g.drawRect(item.currentBounds.getX() - item.margin.left - 1, item.currentBounds.getY(), item.margin.left - 1, item.currentBounds.getHeight(), 1.0f); + // g.drawRect(item.currentBounds.getRight() + 1, item.currentBounds.getY(), item.margin.right - 1, item.currentBounds.getHeight(), 1.0f); + + // g.setColour(Colours::violet); + // g.drawRect(item.currentBounds.getX(), item.currentBounds.getY() - item.margin.top, item.width, item.margin.top); + //} }; void resized() override { - Rectangle viewportBounds(viewableWidth, viewableHeight); + Rectangle viewportBounds(getWidth(), viewableHeight); + FlexBox flexBox(FlexBox::Direction::row, FlexBox::Wrap::wrap, FlexBox::AlignContent::flexStart, FlexBox::AlignItems::center, FlexBox::JustifyContent::flexStart); - float horizontalMargin = viewportBounds.proportionOfWidth(horizontalMarginScalar); float itemWidth = viewportBounds.proportionOfWidth(itemWidthScalar); float itemHeight = viewportBounds.proportionOfWidth(itemHeightScalar); - float topMargin = viewportBounds.proportionOfHeight(topMarginScalar); - float bottomMargin = viewportBounds.proportionOfHeight(bottomMarginScalar); + float topMargin = viewportBounds.proportionOfWidth(topMarginScalar); + float bottomMargin = viewportBounds.proportionOfWidth(bottomMarginScalar); + float horizontalMargin = (viewportBounds.getWidth() - (3 * itemWidth)) * 0.143f; - for (int i = 0; i < flexBox.items.size(); i++) + for (auto palette : allPalettes) { - FlexItem& item = flexBox.items.getReference(i); - item.width = itemWidth; - item.height = itemHeight; + FlexItem item(itemWidth, itemHeight, *palette); item.margin = FlexItem::Margin(topMargin, horizontalMargin, bottomMargin, horizontalMargin); + flexBox.items.add(item); } flexBox.performLayout(viewportBounds); - float bottomMarginControlHeight = roundToInt(viewportBounds.proportionOfHeight(btmMarginCtrlScalar)); + float bottomMarginControlHeight = roundToInt(viewportBounds.proportionOfWidth(btmMarginCtrlScalar)); float bottomMarginControlSpace = (bottomMargin - bottomMarginControlHeight) * 0.5f; + float labelYItemOffset = itemHeight * 0.8f; for (int i = 0; i < controlGroups.size(); i++) { FlexItem& item = flexBox.items.getReference(i + 1); Rectangle bottomMarginBounds(item.currentBounds.getX(), item.currentBounds.getBottom(), itemWidth, bottomMargin); - int halfItemWidth = bottomMarginBounds.proportionOfWidth(0.5f); + int fourthWidthItem = bottomMarginBounds.proportionOfWidth(0.25f); - auto label = paletteLabels[i]; - label->setBounds(item.currentBounds.withTrimmedTop(itemHeight * 0.8f).toNearestInt()); + Rectangle labelBounds = item.currentBounds.withTrimmedTop(labelYItemOffset); + Point controlsPosition; + if (paletteLabels[i]->getText().isNotEmpty()) + { + auto label = paletteLabels[i]; + label->setBounds(labelBounds.toNearestInt()); + controlsPosition = bottomMarginBounds.getPosition().translated(0, bottomMarginControlSpace); + } + else + { + controlsPosition = labelBounds.getPosition(); + } auto group = controlGroups.getUnchecked(i); - group->getEditButton()->setSize(halfItemWidth, bottomMarginControlHeight); - group->getEditButton()->setTopLeftPosition(bottomMarginBounds.getPosition().translated(0, bottomMarginControlSpace).roundToInt()); - group->getTrashButton()->setBounds(group->getEditButton()->getBounds().translated(halfItemWidth, 0)); + group->getEditButton()->setSize(fourthWidthItem, bottomMarginControlHeight); + group->getEditButton()->setTopLeftPosition(controlsPosition.roundToInt().translated(bottomMarginBounds.proportionOfWidth(0.125f), 0)); + group->getCloneButton()->setBounds(group->getEditButton()->getBounds().translated(fourthWidthItem, 0)); + group->getTrashButton()->setBounds(group->getEditButton()->getBounds().translated(fourthWidthItem * 2.0f, 0)); - Rectangle controlBounds = Rectangle(item.currentBounds.getTopLeft().translated(-horizontalMargin, -topMargin), bottomMarginBounds.getBottomRight()); - controlGroupHitBoxes.set(i, controlBounds.toNearestInt()); + auto hitBox = Rectangle(item.currentBounds.getTopLeft().translated(-horizontalMargin, -topMargin), bottomMarginBounds.getBottomRight()).toNearestInt(); + controlGroupHitBoxes.set(i, hitBox); } + dbgItems = flexBox.items; + newPaletteBtn->setSize(itemWidth, bottomMarginControlHeight); - newPaletteBtn->setTopLeftPosition(newPalette->getX(), newPalette->getBottom() + bottomMarginControlSpace); + newPaletteBtn->setTopLeftPosition(newPalette->getX(), newPalette->getY() + labelYItemOffset); + newPaletteBtn->toFront(false); + + needsResize = false; } void mouseMove(const MouseEvent& mouse) override @@ -125,7 +153,7 @@ class ColourPalettesPanel : public Component } else { - for (int i = 0; i < controlGroupHitBoxes.size(); i++) + for (int i = 0; i < controlGroups.size(); i++) { if (controlGroupHitBoxes[i].contains(mouse.getEventRelativeTo(this).position.roundToInt())) { @@ -146,25 +174,19 @@ class ColourPalettesPanel : public Component } // Setup panels from scratch - void rebuildPanel(Array palettesIn, bool resize = true) + void rebuildPanel(Array palettesIn, int width = 0, bool resize = true) { - removeAllChildren(); - flexBox.items.clear(); - - for (auto label : paletteLabels) - { - removeChildComponent(label); - } + for (auto group : controlGroups) + selectionGroup->removeSelector(group->getPaletteComponent()); + removeAllChildren(); + addAndMakeVisible(newPaletteBtn.get()); addAndMakeVisible(newPalette.get()); - flexBox.items.add(*newPalette); - addAndMakeVisible(newPaletteBtn.get()); - - // Remove old ones from colour selection group? controlGroups.clear(); paletteLabels.clear(); - controlGroupHitBoxes.clear(); + + allPalettes = Array(newPalette.get()); // Palettes with colour for (int i = 0; i < palettesIn.size(); i++) @@ -174,7 +196,8 @@ class ColourPalettesPanel : public Component auto paletteComponent = group->getPaletteComponent(); paletteComponent->getProperties().set("index", i); addAndMakeVisible(paletteComponent); - flexBox.items.add(*paletteComponent); + + allPalettes.add(paletteComponent); if (selectionGroup) selectionGroup->addSelector(paletteComponent); @@ -183,8 +206,12 @@ class ColourPalettesPanel : public Component group->getEditButton()->onClick = [&, i, paletteComponent] { listeners.call(&ColourPalettesPanel::Listener::editPaletteRequested, i, paletteComponent->getSelectedSwatchNumber()); }; addChildComponent(group->getEditButton()); + group->getCloneButton()->getProperties().set("index", i); + group->getCloneButton()->onClick = [&, i, paletteComponent] { listeners.call(&ColourPalettesPanel::Listener::clonePaletteRequested, i); }; + addChildComponent(group->getCloneButton()); + group->getTrashButton()->getProperties().set("index", i); - group->getTrashButton()->onClick = [&, i] { listeners.call(&ColourPalettesPanel::Listener::deletePaletteRequested, i); }; + group->getTrashButton()->onClick = [&, group, i] { listeners.call(&ColourPalettesPanel::Listener::deletePaletteRequested, i); }; addChildComponent(group->getTrashButton()); String name = palettesIn[i].getName(); @@ -194,21 +221,29 @@ class ColourPalettesPanel : public Component label->getProperties().set(LumatoneEditorStyleIDs::labelMaximumLineCount, 2); addAndMakeVisible(label); - controlGroupHitBoxes.add(Rectangle()); + controlGroupHitBoxes.set(i, Rectangle()); } int rows = ceil((palettesIn.size() + 1) * 0.333333f); + int w = getWidth(); + // Set height depending on how many rows + width = (width < 1) ? w : width; + viewableHeight = getHeightFromNumRows(width, rows); + if (resize) { - if (rows != numRows) - setSize(viewableWidth, getHeightFromNumRows(rows)); - else + needsResize = true; + setSize(width, viewableHeight); + + // Force resize + if (needsResize) resized(); } numRows = rows; + } private: @@ -217,6 +252,7 @@ class ColourPalettesPanel : public Component { auto group = controlGroups.getUnchecked(paletteIndex); group->getEditButton()->setVisible(areVisible); + group->getCloneButton()->setVisible(areVisible); group->getTrashButton()->setVisible(areVisible); } @@ -224,35 +260,34 @@ class ColourPalettesPanel : public Component ColourSelectionGroup* selectionGroup; - FlexBox flexBox; - std::unique_ptr newPalette; std::unique_ptr newPaletteBtn; OwnedArray controlGroups; + Array allPalettes; OwnedArray paletteLabels; + Array dbgItems; + int numRows = 1; - int viewableWidth = 0; int viewableHeight = 0; + bool needsResize = false; Array> controlGroupHitBoxes; int lastPaletteMouseOver = -1; - const float itemWidthScalar = 0.28f; - const float itemHeightScalar = 0.25f; + const float itemWidthScalar = 0.265f; + const float itemHeightScalar = 0.24f; - const float topMarginScalar = 0.042f; - const float horizontalMarginScalar = 0.025f; - const float bottomMarginScalar = 0.075f; - const float btmMarginCtrlScalar = 0.06f; + const float topMarginScalar = 0.04f; + const float horizontalMarginScalar = 0.0367f; + const float bottomMarginScalar = 0.06f; + const float btmMarginCtrlScalar = 0.04f; const float buttonWidthScalar = 0.333333f; const float buttonHeightScalar = 0.166667f; const float panelLeftMarginWidth = 0.020833f; - - //============================================================================== public: @@ -260,8 +295,10 @@ class ColourPalettesPanel : public Component class Listener { public: + virtual ~Listener() {} virtual void editPaletteRequested(int paletteIndex, int selectedSwatchIndex) = 0; + virtual void clonePaletteRequested(int paletteIndex) = 0; virtual void deletePaletteRequested(int paletteIndex) = 0; virtual void newPaletteRequested() = 0; }; @@ -280,7 +317,8 @@ class ColourPalettesPanel : public Component */ class CustomPickerPanel : public Component, public ChangeListener, - public ColourSelectionBroadcaster + public ColourSelectionBroadcaster, + public ColourSelectionListener { public: @@ -292,7 +330,7 @@ class CustomPickerPanel : public Component, + ColourSelector::ColourSelectorOptions::showColourspace )); - + colourPicker->setName("ColourPicker"); addAndMakeVisible(*colourPicker); colourPicker->addChangeListener(this); } @@ -319,6 +357,12 @@ class CustomPickerPanel : public Component, selectorListeners.call(&ColourSelectionListener::colourChangedCallback, this, colourPicker->getCurrentColour()); } + void colourChangedCallback(ColourSelectionBroadcaster* source, Colour newColour) override + { + if (this != source) + colourPicker->setCurrentColour(newColour, dontSendNotification); + } + //============================================================================== Colour getSelectedColour() override @@ -401,19 +445,19 @@ class PaletteEditPanel : public Component, float leftCenter = leftWidth * 0.5f; resizeLabelWithHeight(editPaletteLabel.get(), proportionOfHeight(editPaletteHeight)); - editPaletteLabel->setCentrePosition(leftCenter, round(editPaletteLabel->getHeight() * 0.5f + proportionOfHeight(editPaletteLabelY))); + editPaletteLabel->setCentrePosition(leftCenter, roundToInt(editPaletteLabel->getHeight() * 0.5f + proportionOfHeight(editPaletteLabelY))); float paletteWidth = proportionOfWidth(paletteWidthScalar); float paletteHeight = proportionOfHeight(paletteHeightScalar); paletteControl->setSize(paletteWidth, paletteHeight); - paletteControl->setCentrePosition(leftCenter, round(paletteHeight * 0.5f + proportionOfHeight(paletteY))); + paletteControl->setCentrePosition(leftCenter, roundToInt(paletteHeight * 0.5f + proportionOfHeight(paletteY))); saveButton->setSize(proportionOfWidth(buttonWidth), proportionOfHeight(buttonHeight)); - saveButton->setCentrePosition(leftCenter, round(saveButton->getHeight() * 0.5f + proportionOfHeight(buttonY))); + saveButton->setCentrePosition(leftCenter, roundToInt(saveButton->getHeight() * 0.5f + proportionOfHeight(buttonY))); cancelButton->setBounds(saveButton->getBounds().translated(0, saveButton->getHeight() * 1.125f)); colourPicker->setSize(proportionOfWidth(pickerWidth), proportionOfHeight(pickerHeight)); - colourPicker->setTopLeftPosition(leftWidth, round((getHeight() - colourPicker->getHeight()) * 0.5f)); + colourPicker->setTopLeftPosition(leftWidth, roundToInt((getHeight() - colourPicker->getHeight()) * 0.5f)); float leftMargin = colourPicker->getRight() * 0.03f * 0.5f; paletteNameEditor->setBounds(Rectangle( @@ -542,7 +586,7 @@ class PaletteEditPanel : public Component, const float editPaletteHeight = 0.0606f; const float paletteY = 0.26f; - const float paletteWidthScalar = 0.27f; + const float paletteWidthScalar = 0.25f; const float paletteHeightScalar = 0.25f; const float buttonY = 0.6739f; diff --git a/Source/CurvesArea.cpp b/Source/CurvesArea.cpp index 90a29859..73b75183 100644 --- a/Source/CurvesArea.cpp +++ b/Source/CurvesArea.cpp @@ -7,7 +7,7 @@ the "//[xyz]" and "//[/xyz]" sections will be retained when the file is loaded and re-saved. - Created with Projucer version: 6.0.5 + Created with Projucer version: 6.0.8 ------------------------------------------------------------------------------ @@ -54,23 +54,27 @@ void CurvesArea::CurvesTabComponent::resized() //============================================================================== CurvesArea::CurvesArea () - : Component("CurvesArea") { //[Constructor_pre] You can add your own custom stuff here.. showDeveloperMode = TerpstraSysExApplication::getApp().getPropertiesFile()->getBoolValue("DeveloperMode", false); - //[/Constructor_pre] + //[/Constructor_pre] - labelWindowTitle.reset (new juce::Label ("labelWindowTitle", translate("Curves"))); + setName ("CurvesArea"); + labelWindowTitle.reset (new juce::Label ("labelWindowTitle", + TRANS("Curves"))); addAndMakeVisible (labelWindowTitle.get()); - labelWindowTitle->setFont(TerpstraSysExApplication::getApp().getAppFont(LumatoneEditorFont::UniviaProBold)); - labelWindowTitle->setColour (Label::backgroundColourId, Colour()); + labelWindowTitle->setFont (juce::Font (18.00f, juce::Font::plain).withTypefaceStyle ("Regular")); + labelWindowTitle->setJustificationType (juce::Justification::centredLeft); + labelWindowTitle->setEditable (false, false, false); + labelWindowTitle->setColour (juce::TextEditor::textColourId, juce::Colours::black); + labelWindowTitle->setColour (juce::TextEditor::backgroundColourId, juce::Colour (0x00000000)); + + labelWindowTitle->setBounds (8, 8, 150, 24); curvesTab.reset (new CurvesTabComponent (juce::TabbedButtonBar::TabsAtTop)); addAndMakeVisible (curvesTab.get()); curvesTab->setTabBarDepth (30); - curvesTab->setColour(TabbedComponent::ColourIds::outlineColourId, Colour()); - curvesTab->setColour(TabbedComponent::ColourIds::backgroundColourId, Colour()); - curvesTab->addTab (TRANS("Note Velocity"), Colour(), new NoteOnOffVelocityCurveDialog(), true); + curvesTab->addTab (TRANS("Note Velocity"), juce::Colours::lightgrey, new NoteOnOffVelocityCurveDialog(), true); curvesTab->setCurrentTabIndex (0); curvesTab->setBounds (8, 40, 464, 200); @@ -85,9 +89,27 @@ CurvesArea::CurvesArea () //[UserPreSize] setDeveloperMode(showDeveloperMode); + + labelWindowTitle->setFont(TerpstraSysExApplication::getApp().getAppFont(LumatoneEditorFont::UniviaProBold)); + labelWindowTitle->setColour(Label::backgroundColourId, Colour()); + + curvesTab->setColour(TabbedComponent::ColourIds::outlineColourId, Colour()); + curvesTab->setColour(TabbedComponent::ColourIds::backgroundColourId, Colour()); + //curvesTab->addTab(TRANS("Note Velocity"), Colour(), new NoteOnOffVelocityCurveDialog(), true); + curvesTab->setTabBackgroundColour(0, Colour()); + + + /* Don't want to resize here + * //[/UserPreSize] + setSize (472, 240); + + //[Constructor] You can add your own custom stuff here.. + */ + + //[/Constructor] } @@ -109,33 +131,39 @@ CurvesArea::~CurvesArea() void CurvesArea::paint (juce::Graphics& g) { //[UserPrePaint] Add your own custom painting code here.. + /* //[/UserPrePaint] - //g.fillAll (juce::Colour (0xff323e44)); + g.fillAll (juce::Colour (0xff323e44)); //[UserPaint] Add your own custom painting code here.. - + */ //[/UserPaint] } void CurvesArea::resized() { + //[UserPreResize] Add your own custom resize code here.. int tabBarDepth = roundToInt(getHeight() * tabDepth); int tabY = proportionOfHeight(tabYScalar); + //[/UserPreResize] - btnDeveloperMode->setBounds( - getWidth() - btnDeveloperMode->getWidth(), - proportionOfHeight(0.0f), - btnDeveloperMode->getWidth(), - tabY); + //[UserResized] Add your own custom resize handling here.. + + btnDeveloperMode->setBounds( + getWidth() - btnDeveloperMode->getWidth(), + proportionOfHeight(0.0f), + btnDeveloperMode->getWidth(), + tabY); curvesTab->setTabBarDepth(tabBarDepth); curvesTab->setTabsIndent(roundToInt(getWidth() * tabXScalar)); - + curvesTab->setBounds(0, tabY, getWidth(), getHeight() - tabY); resizeLabelWithHeight(labelWindowTitle.get(), tabBarDepth * 0.9f); labelWindowTitle->setTopLeftPosition(roundToInt(getWidth() * 0.01f), tabY); + //[/UserResized] } void CurvesArea::buttonClicked (juce::Button* buttonThatWasClicked) @@ -204,7 +232,7 @@ void CurvesArea::setDeveloperMode(bool devModeOn) BEGIN_JUCER_METADATA - diff --git a/Source/CurvesArea.h b/Source/CurvesArea.h index 4a5024d9..ebab1030 100644 --- a/Source/CurvesArea.h +++ b/Source/CurvesArea.h @@ -7,7 +7,7 @@ the "//[xyz]" and "//[/xyz]" sections will be retained when the file is loaded and re-saved. - Created with Projucer version: 6.0.5 + Created with Projucer version: 6.0.8 ------------------------------------------------------------------------------ @@ -68,15 +68,17 @@ class CurvesArea : public juce::Component, void sendConfigToController(); void setDeveloperMode(bool devModeOn); - //[/UserMethods] + //[/UserMethods] void paint (juce::Graphics& g) override; void resized() override; void buttonClicked (juce::Button* buttonThatWasClicked) override; + + private: //[UserVariables] -- You can add your own custom variables in this section. - + bool showDeveloperMode = false; //============================================================================== @@ -87,7 +89,7 @@ class CurvesArea : public juce::Component, const float tabYScalar = 0.06f; const float tabWidth = 0.65f; const float tabFontHeight = 0.058f; - + //[/UserVariables] //============================================================================== diff --git a/Source/DeviceActivityMonitor.cpp b/Source/DeviceActivityMonitor.cpp index b0e66cd6..c7d09497 100644 --- a/Source/DeviceActivityMonitor.cpp +++ b/Source/DeviceActivityMonitor.cpp @@ -13,14 +13,25 @@ DeviceActivityMonitor::DeviceActivityMonitor(TerpstraMidiDriver& midiDriverIn) - : midiDriver(midiDriverIn) + : midiDriver(midiDriverIn), readQueueSize(0) { - midiDriver.addListener(this); + detectDevicesIfDisconnected = TerpstraSysExApplication::getApp().getPropertiesFile()->getBoolValue("DetectDeviceIfDisconnected", true); + checkConnectionOnInactivity = TerpstraSysExApplication::getApp().getPropertiesFile()->getBoolValue("CheckConnectionIfInactive", true); + responseTimeoutMs = TerpstraSysExApplication::getApp().getPropertiesFile()->getIntValue("DetectDevicesTimeout", responseTimeoutMs); + +// midiDriver.addListener(this); + reset(readBlockSize); + midiDriver.addMessageCollector(this); + + // avoid resizing during communication + ensureStorageAllocated(2000); + testResponseDeviceIndices.resize(2000); } DeviceActivityMonitor::~DeviceActivityMonitor() { - + removeAllChangeListeners(); + midiDriver.removeMessageCollector(this); } void DeviceActivityMonitor::setDetectDeviceIfDisconnected(bool doDetection) @@ -45,7 +56,9 @@ void DeviceActivityMonitor::setCheckForInactivity(bool monitorActivity) TerpstraSysExApplication::getApp().getPropertiesFile()->setValue("CheckConnectionIfInactive", checkConnectionOnInactivity); if (checkConnectionOnInactivity && isConnectionEstablished()) + { startTimer(inactivityTimeoutMs); + } } void DeviceActivityMonitor::pingAllDevices() @@ -58,11 +71,10 @@ void DeviceActivityMonitor::pingAllDevices() waitingForTestResponse = true; - // Impossible...but why not limit anyway? - const unsigned int maxDevices = jmin(outputDevices.size(), (1 << 28) - 1); - for (unsigned int i = 0; i < maxDevices; i++) + int maxDevices = jmin(outputDevices.size(), 128); + for (int i = 0; i < maxDevices; i++) { - const unsigned int id = i + 1; + unsigned int id = (unsigned int)i + 1; midiDriver.ping(id, i); outputPingIds.add(id); } @@ -128,7 +140,11 @@ void DeviceActivityMonitor::testNextOutput() if (testOutputIndex >= 0 && testOutputIndex < outputDevices.size()) { DBG("Testing " + outputDevices[testOutputIndex].name); - midiDriver.sendGetSerialIdentityRequest(testOutputIndex); + if (sendCalibratePitchModOff) + midiDriver.sendCalibratePitchModWheel(false, testOutputIndex); + else + midiDriver.sendGetSerialIdentityRequest(testOutputIndex); + waitingForTestResponse = true; startTimer(responseTimeoutMs); } @@ -181,10 +197,9 @@ void DeviceActivityMonitor::stopMonitoringDevice() bool DeviceActivityMonitor::initializeConnectionTest() { - TerpstraSysExApplication::getApp().getLumatoneController().testCurrentDeviceConnection(); - + TerpstraSysExApplication::getApp().getLumatoneController()->testCurrentDeviceConnection(); waitingForTestResponse = true; - + startTimer(inactivityTimeoutMs); return true; } @@ -193,8 +208,9 @@ void DeviceActivityMonitor::onSerialIdentityResponse(const MidiMessage& msg, int { waitingForTestResponse = false; - if (deviceConnectionMode == DetectConnectionMode::lookingForDevice) + switch (deviceConnectionMode) { + case DetectConnectionMode::lookingForDevice: if (midiDriver.hasDevicesDefined()) { confirmedOutputIndex = midiDriver.getMidiOutputIndex(); @@ -206,11 +222,15 @@ void DeviceActivityMonitor::onSerialIdentityResponse(const MidiMessage& msg, int confirmedInputIndex = deviceIndexResponded; } - startTimer(10); - } - else if (deviceConnectionMode == DetectConnectionMode::waitingForInactivity) - { + break; + + case DetectConnectionMode::waitingForInactivity: startTimer(inactivityTimeoutMs); + break; + + default: + // TODO review + break; } } @@ -245,7 +265,7 @@ void DeviceActivityMonitor::onPingResponse(const MidiMessage& msg, int deviceInd { confirmedOutputIndex = pingId - 1; confirmedInputIndex = deviceIndexResponded; - startTimer(10); + // startTimer(10); } } @@ -257,9 +277,14 @@ void DeviceActivityMonitor::onPingResponse(const MidiMessage& msg, int deviceInd void DeviceActivityMonitor::onTestResponseReceived() { - stopTimer(); - waitingForTestResponse = false; + + if (sendCalibratePitchModOff) + { + sendCalibratePitchModOff = false; + checkDetectionStatus(); + return; + } if (deviceConnectionMode == DetectConnectionMode::lookingForDevice) { @@ -337,67 +362,84 @@ void DeviceActivityMonitor::timerCallback() { stopTimer(); - if (deviceConnectionMode < DetectConnectionMode::noDeviceMonitoring) + MidiBuffer readBuffer; + removeNextBlockOfMessages(readBuffer, readBlockSize); + + handleMessageQueue(readBuffer, testResponseDeviceIndices); + auto size = readQueueSize.load(); + readQueueSize.store(jlimit(0, 999999, size - readBlockSize)); + + switch (deviceConnectionMode) { + case DetectConnectionMode::noDeviceActivity: + case DetectConnectionMode::lookingForDevice: if (detectDevicesIfDisconnected) { checkDetectionStatus(); + break; } - else + + stopDeviceDetection(); + break; + + case DetectConnectionMode::noDeviceMonitoring: + case DetectConnectionMode::waitingForInactivity: + jassert(isConnectionEstablished() && midiDriver.hasDevicesDefined()); + if (!checkConnectionOnInactivity) { - stopDeviceDetection(); + stopMonitoringDevice(); + break; } - } - else if (deviceConnectionMode >= DetectConnectionMode::noDeviceMonitoring) - { - jassert(isConnectionEstablished() && midiDriver.hasDevicesDefined()); - if (checkConnectionOnInactivity) + + if (!waitingForTestResponse) { - if (!waitingForTestResponse) + if (sentQueueSize > 0) { - if (midiQueueSize == 0) - { - if (deviceConnectionMode == DetectConnectionMode::noDeviceMonitoring) - deviceConnectionMode = DetectConnectionMode::waitingForInactivity; - - initializeConnectionTest(); - } - else - { - startTimer(inactivityTimeoutMs); - } + startTimer(inactivityTimeoutMs); + break; } + + if (deviceConnectionMode == DetectConnectionMode::noDeviceMonitoring) + deviceConnectionMode = DetectConnectionMode::waitingForInactivity; + + initializeConnectionTest(); } else { - stopMonitoringDevice(); + DBG("waiting for test response..."); } - } - else + + break; + + default: jassertfalse; + } } -//========================================================================= -// TerpstraMidiDriver::Listener Implementation - -void DeviceActivityMonitor::midiMessageReceived(MidiInput* source, const MidiMessage& msg) +void DeviceActivityMonitor::handleResponse(int inputDeviceIndex, const MidiMessage& msg) { - stopTimer(); - if (msg.isSysEx()) { - if (waitingForTestResponse) + auto sysExData = msg.getSysExData(); + auto cmd = sysExData[CMD_ID]; + + if (cmd == PERIPHERAL_CALBRATION_DATA && !isConnectionEstablished()) { - auto sysExData = msg.getSysExData(); - auto cmd = sysExData[CMD_ID]; + sendCalibratePitchModOff = true; + // startTimer(100); + return; + } + if (waitingForTestResponse) switch (sysExData[MSG_STATUS]) + { // Skip echos, or mark as a failed ping - if (sysExData[MSG_STATUS] == TEST_ECHO) + case TEST_ECHO: { switch (cmd) { case LUMA_PING: { + DBG("Ignoring Ping Echo"); onFailedPing(msg); break; } @@ -405,65 +447,110 @@ void DeviceActivityMonitor::midiMessageReceived(MidiInput* source, const MidiMes break; case GET_FIRMWARE_REVISION: - onTestResponseReceived(); + DBG("Ignoring Firmware Echo"); + // onTestResponseReceived(); break; - + default: return; } - - waitingForTestResponse = false; - startTimer(10); + break; } - else if (sysExData[MSG_STATUS] == TerpstraMIDIAnswerReturnCode::ACK) - { - int deviceIndex = midiDriver.getMidiInputList().indexOf(source->getDeviceInfo()); + case TerpstraMIDIAnswerReturnCode::ACK: + { switch (cmd) { case GET_SERIAL_IDENTITY: - onSerialIdentityResponse(msg, deviceIndex); + onSerialIdentityResponse(msg, inputDeviceIndex); + break; + + case CALIBRATE_PITCH_MOD_WHEEL: + if (sysExData[PAYLOAD_INIT] != TEST_ECHO) + { + if (!isConnectionEstablished()) + { + sendCalibratePitchModOff = true; + } + } break; case GET_FIRMWARE_REVISION: - // TODO break; case LUMA_PING: - onPingResponse(msg, deviceIndex); + onPingResponse(msg, inputDeviceIndex); break; default: break; } + + waitingForTestResponse = false; + break; + } + + // Consider a response from a different firmware state as successful + case TerpstraMIDIAnswerReturnCode::STATE: + { + // Find 'off' message if possible + //onTestResponseReceived(); } } - + // Edge case if we're disconnected but get a response else if (!isConnectionEstablished()) { confirmedInputIndex = midiDriver.getMidiInputIndex(); confirmedOutputIndex = midiDriver.getMidiOutputIndex(); - + // Maybe not best solution? deviceConnectionMode = DetectConnectionMode::lookingForDevice; - startTimer(10); } else { startTimer(inactivityTimeoutMs); } } + } -void DeviceActivityMonitor::noAnswerToMessage(const MidiMessage& midiMessage) +void DeviceActivityMonitor::handleMessageQueue(const MidiBuffer& readBuffer, const Array& devices) +{ + int smpl = 0; + for (auto event : readBuffer) + { + auto msg = event.getMessage(); + handleResponse(devices[smpl], msg); + smpl++; + } +} + +//========================================================================= +// TerpstraMidiDriver::Listener Implementation + +void DeviceActivityMonitor::midiMessageReceived(MidiInput* source, const MidiMessage& msg) +{ + if (!msg.isSysEx()) + return; + + int deviceIndex = (source == nullptr) ? -1 + : midiDriver.getMidiInputList().indexOf(source->getDeviceInfo()); + + addMessageToQueue(msg); + + auto size = readQueueSize.load(); + testResponseDeviceIndices.set(size, deviceIndex); + readQueueSize.store(size + 1); +} + +void DeviceActivityMonitor::noAnswerToMessage(MidiInput* expectedDevice, const MidiMessage& midiMessage) { stopTimer(); if (waitingForTestResponse && deviceConnectionMode < DetectConnectionMode::noDeviceMonitoring) { waitingForTestResponse = false; - auto sysExData = midiMessage.getSysExData(); if (sysExData[CMD_ID] == LUMA_PING && outputPingIds.size() > 0) { diff --git a/Source/DeviceActivityMonitor.h b/Source/DeviceActivityMonitor.h index 5fcd526b..4435d282 100644 --- a/Source/DeviceActivityMonitor.h +++ b/Source/DeviceActivityMonitor.h @@ -19,7 +19,9 @@ #include "TerpstraMidiDriver.h" -class DeviceActivityMonitor : public juce::Timer, public juce::ChangeBroadcaster, protected TerpstraMidiDriver::Listener +class DeviceActivityMonitor : public juce::Timer, + public juce::ChangeBroadcaster, + protected TerpstraMidiDriver::Collector { public: @@ -106,11 +108,20 @@ class DeviceActivityMonitor : public juce::Timer, public juce::ChangeBroadcaster /// Returns false if devices are not valid, and true if it an attempt to connect was made bool initializeConnectionTest(); + /// + /// Handle a response from a test or confirmed MidiInput device + /// + /// + /// + void handleResponse(int inputDeviceIndex, const MidiMessage& midiMessage); + private: //========================================================================= // Callback functions + void handleMessageQueue(const MidiBuffer& readBuffer, const Array& devices); + void onSerialIdentityResponse(const MidiMessage& msg, int deviceIndexResponded); void onFailedPing(const MidiMessage& msg); @@ -121,17 +132,17 @@ class DeviceActivityMonitor : public juce::Timer, public juce::ChangeBroadcaster void onSuccessfulDetection(); void onDisconnection(); - + protected: //========================================================================= // TerpstraMidiDriver::Listener Implementation void midiMessageReceived(MidiInput* source, const MidiMessage& midiMessage) override; - void midiMessageSent(const MidiMessage& midiMessage) override {}; - void midiSendQueueSize(int queueSizeIn) override { midiQueueSize = queueSizeIn; }; - void generalLogMessage(String textMessage, HajuErrorVisualizer::ErrorLevel errorLevel) override {}; - void noAnswerToMessage(const MidiMessage& midiMessage) override; + void midiMessageSent(MidiOutput* target, const MidiMessage& midiMessage) override {} + void midiSendQueueSize(int queueSizeIn) override { sentQueueSize = queueSizeIn; } + void generalLogMessage(String textMessage, HajuErrorVisualizer::ErrorLevel errorLevel) override {} + void noAnswerToMessage(MidiInput* expectedDevice, const MidiMessage& midiMessage) override; private: @@ -141,10 +152,15 @@ class DeviceActivityMonitor : public juce::Timer, public juce::ChangeBroadcaster bool deviceDetectInProgress = false; bool waitingForTestResponse = false; - int responseTimeoutMs = 500; - int detectRoutineTimeoutMs = 500; + int responseTimeoutMs = 600; + int detectRoutineTimeoutMs = 1000; int inactivityTimeoutMs = 1500; - + + Array testResponseDeviceIndices; + std::atomic readQueueSize; + const int readBlockSize = 64; + int sentQueueSize = 0; + int testOutputIndex = -1; Array outputDevices; Array inputDevices; @@ -153,10 +169,10 @@ class DeviceActivityMonitor : public juce::Timer, public juce::ChangeBroadcaster int confirmedInputIndex = -1; int confirmedOutputIndex = -1; - int midiQueueSize = 0; - bool detectDevicesIfDisconnected = true; bool checkConnectionOnInactivity = true; + bool sendCalibratePitchModOff = false; + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(DeviceActivityMonitor) }; diff --git a/Source/EditActions.cpp b/Source/EditActions.cpp index c58e781a..2eeb6ca2 100644 --- a/Source/EditActions.cpp +++ b/Source/EditActions.cpp @@ -13,6 +13,8 @@ namespace Lumatone { + // ============================================================================== + // Implementation of SingleNoteAssignAction SingleNoteAssignAction::SingleNoteAssignAction( int setSelection, int keySelection, @@ -20,13 +22,15 @@ namespace Lumatone { bool setChannel, bool setNote, bool setColour, + bool setCCPolarity, LumatoneKeyType newKeyType, int newChannelNumber, int newNoteNumber, - TerpstraKey::COLOURTYPE newColour) + TerpstraKey::COLOURTYPE newColour, + bool newCCFaderIsDefault) : setSelection(setSelection), keySelection(keySelection) - , setKeyType(setKeyType), setChannel(setChannel), setNote(setNote), setColour(setColour) - , newData(newKeyType, newChannelNumber, newNoteNumber, newColour) + , setKeyType(setKeyType), setChannel(setChannel), setNote(setNote), setColour(setColour), setCCFaderPolarity(setCCPolarity) + , newData(newKeyType, newChannelNumber, newNoteNumber, newColour, newCCFaderIsDefault) { auto mainComponent = TerpstraSysExApplication::getApp().getMainContentComponent(); jassert(mainComponent != nullptr); @@ -43,7 +47,7 @@ namespace Lumatone { { if (setSelection >= 0 && setSelection < NUMBEROFBOARDS && keySelection >= 0 && keySelection < TerpstraSysExApplication::getApp().getOctaveBoardSize()) { - if (setKeyType || setChannel || setNote || setColour) + if (setKeyType || setChannel || setNote || setColour || setCCFaderPolarity) { auto mainComponent = TerpstraSysExApplication::getApp().getMainContentComponent(); jassert(mainComponent != nullptr); @@ -65,9 +69,13 @@ namespace Lumatone { { mappingInEdit.sets[setSelection].theKeys[keySelection].colour = newData.colour; } + if (setCCFaderPolarity) + { + mappingInEdit.sets[setSelection].theKeys[keySelection].ccFaderDefault = newData.ccFaderDefault; + } // Send to device - TerpstraSysExApplication::getApp().getLumatoneController().sendKeyParam( + TerpstraSysExApplication::getApp().getLumatoneController()->sendKeyParam( setSelection + 1, keySelection, mappingInEdit.sets[setSelection].theKeys[keySelection]); @@ -93,7 +101,7 @@ namespace Lumatone { { if (setSelection >= 0 && setSelection < NUMBEROFBOARDS && keySelection >= 0 && keySelection < TerpstraSysExApplication::getApp().getOctaveBoardSize()) { - if (setKeyType || setChannel || setNote || setColour) + if (setKeyType || setChannel || setNote || setColour || setCCFaderPolarity) { auto mainComponent = TerpstraSysExApplication::getApp().getMainContentComponent(); jassert(mainComponent != nullptr); @@ -115,14 +123,19 @@ namespace Lumatone { { mappingInEdit.sets[setSelection].theKeys[keySelection].colour = previousData.colour; } + if (setCCFaderPolarity) + { + mappingInEdit.sets[setSelection].theKeys[keySelection].ccFaderDefault = previousData.ccFaderDefault; + } + // Send to device - TerpstraSysExApplication::getApp().getLumatoneController().sendKeyParam( + TerpstraSysExApplication::getApp().getLumatoneController()->sendKeyParam( setSelection + 1, keySelection, mappingInEdit.sets[setSelection].theKeys[keySelection]); - // Notfy that there are changes: in calling function + // Notify that there are changes: in calling function } else { @@ -139,9 +152,195 @@ namespace Lumatone { } } - int SingleNoteAssignAction::getSizeInUnits() + + // ============================================================================== + // Implementation of SectionEditAction + + SectionEditAction::SectionEditAction(int setSelection, TerpstraKeys& newSectionValue) + : setSelection(setSelection) + , newData(newSectionValue) + { + auto mainComponent = TerpstraSysExApplication::getApp().getMainContentComponent(); + jassert(mainComponent != nullptr); + + previousData = mainComponent->getMappingInEdit().sets[setSelection]; + } + + bool SectionEditAction::isValid() const + { + return setSelection >= 0 && setSelection < NUMBEROFBOARDS; + } + + bool SectionEditAction::perform() + { + if (setSelection >= 0 && setSelection < NUMBEROFBOARDS) + { + auto mainComponent = TerpstraSysExApplication::getApp().getMainContentComponent(); + jassert(mainComponent != nullptr); + TerpstraKeyMapping& mappingInEdit = mainComponent->getMappingInEdit(); + + mappingInEdit.sets[setSelection] = newData; + + // Send to device + TerpstraSysExApplication::getApp().getLumatoneController()->sendAllParamsOfBoard(setSelection + 1, mappingInEdit.sets[setSelection]); + + // Notify that there are changes: in calling function + return true; + } + else + { + jassertfalse; + return false; + } + } + + bool SectionEditAction::undo() + { + if (setSelection >= 0 && setSelection < NUMBEROFBOARDS) + { + auto mainComponent = TerpstraSysExApplication::getApp().getMainContentComponent(); + jassert(mainComponent != nullptr); + TerpstraKeyMapping& mappingInEdit = mainComponent->getMappingInEdit(); + + mappingInEdit.sets[setSelection] = previousData; + + // Send to device + TerpstraSysExApplication::getApp().getLumatoneController()->sendAllParamsOfBoard(setSelection + 1, mappingInEdit.sets[setSelection]); + + // Notify that there are changes: in calling function + return true; + } + else + { + jassertfalse; + return false; + } + + } + + // ============================================================================== + // Implementation of InvertFootControllerEditAction + + InvertFootControllerEditAction::InvertFootControllerEditAction(bool newValue) + : newData(newValue) { - return 2 * sizeof(int) + 6 * sizeof(bool) + 2 * sizeof(TerpstraKey); + auto mainComponent = TerpstraSysExApplication::getApp().getMainContentComponent(); + jassert(mainComponent != nullptr); + + previousData = mainComponent->getMappingInEdit().invertExpression; } -} \ No newline at end of file + bool InvertFootControllerEditAction::perform() + { + auto mainComponent = TerpstraSysExApplication::getApp().getMainContentComponent(); + jassert(mainComponent != nullptr); + TerpstraKeyMapping& mappingInEdit = mainComponent->getMappingInEdit(); + + mappingInEdit.invertExpression = newData; + + // Send to device + TerpstraSysExApplication::getApp().getLumatoneController()->sendInvertFootController(newData); + + // Notify that there are changes: in calling function + return true; + } + + bool InvertFootControllerEditAction::undo() + { + auto mainComponent = TerpstraSysExApplication::getApp().getMainContentComponent(); + jassert(mainComponent != nullptr); + TerpstraKeyMapping& mappingInEdit = mainComponent->getMappingInEdit(); + + mappingInEdit.invertExpression = previousData; + + // Send to device + TerpstraSysExApplication::getApp().getLumatoneController()->sendInvertFootController(previousData); + + // Notify that there are changes: in calling function + return true; + } + + // ============================================================================== + // Implementation of ExprPedalSensivityEditAction + + ExprPedalSensivityEditAction::ExprPedalSensivityEditAction(int newValue) + : newData(newValue) + { + auto mainComponent = TerpstraSysExApplication::getApp().getMainContentComponent(); + jassert(mainComponent != nullptr); + + previousData = mainComponent->getMappingInEdit().expressionControllerSensivity; + } + + bool ExprPedalSensivityEditAction::perform() + { + auto mainComponent = TerpstraSysExApplication::getApp().getMainContentComponent(); + jassert(mainComponent != nullptr); + TerpstraKeyMapping& mappingInEdit = mainComponent->getMappingInEdit(); + + mappingInEdit.expressionControllerSensivity = newData; + + // Send to device + TerpstraSysExApplication::getApp().getLumatoneController()->sendExpressionPedalSensivity(newData); + + // Notify that there are changes: in calling function + return true; + } + + bool ExprPedalSensivityEditAction::undo() + { + auto mainComponent = TerpstraSysExApplication::getApp().getMainContentComponent(); + jassert(mainComponent != nullptr); + TerpstraKeyMapping& mappingInEdit = mainComponent->getMappingInEdit(); + + mappingInEdit.expressionControllerSensivity = previousData; + + // Send to device + TerpstraSysExApplication::getApp().getLumatoneController()->sendExpressionPedalSensivity(previousData); + + // Notify that there are changes: in calling function + return true; + } + + // ============================================================================== + // Implementation of InvertSustainEditAction + + InvertSustainEditAction::InvertSustainEditAction(bool newValue) + : newData(newValue) + { + auto mainComponent = TerpstraSysExApplication::getApp().getMainContentComponent(); + jassert(mainComponent != nullptr); + + previousData = mainComponent->getMappingInEdit().invertSustain; + } + + bool InvertSustainEditAction::perform() + { + auto mainComponent = TerpstraSysExApplication::getApp().getMainContentComponent(); + jassert(mainComponent != nullptr); + TerpstraKeyMapping& mappingInEdit = mainComponent->getMappingInEdit(); + + mappingInEdit.invertSustain = newData; + + // Send to device + TerpstraSysExApplication::getApp().getLumatoneController()->invertSustainPedal(newData); + + // Notify that there are changes: in calling function + return true; + } + + bool InvertSustainEditAction::undo() + { + auto mainComponent = TerpstraSysExApplication::getApp().getMainContentComponent(); + jassert(mainComponent != nullptr); + TerpstraKeyMapping& mappingInEdit = mainComponent->getMappingInEdit(); + + mappingInEdit.invertSustain = previousData; + + // Send to device + TerpstraSysExApplication::getApp().getLumatoneController()->invertSustainPedal(previousData); + + // Notify that there are changes: in calling function + return true; + } +} diff --git a/Source/EditActions.h b/Source/EditActions.h index aea31e55..ce03210e 100644 --- a/Source/EditActions.h +++ b/Source/EditActions.h @@ -25,18 +25,21 @@ namespace Lumatone { bool setChannel, bool setNote, bool setColour, + bool ccFaderDefault, LumatoneKeyType newKeyType = LumatoneKeyType::noteOnNoteOff, int newChannelNumber = 0, int newNoteNumber = 0, - TerpstraKey::COLOURTYPE newColour = juce::Colour()); + TerpstraKey::COLOURTYPE newColour = juce::Colour(), + bool newCCFaderDefault = true); - SingleNoteAssignAction(SingleNoteAssignAction& second) + SingleNoteAssignAction(const SingleNoteAssignAction& second) : setSelection(second.setSelection) , keySelection(second.keySelection) , setKeyType(second.setKeyType) , setChannel(second.setChannel) , setNote(second.setNote) , setColour(second.setColour) + , setCCFaderPolarity(second.setCCFaderPolarity) , previousData(second.previousData) , newData(second.newData) {} @@ -45,7 +48,8 @@ namespace Lumatone { virtual bool perform() override; virtual bool undo() override; - int getSizeInUnits() override; + + int getSizeInUnits() override { return sizeof(SingleNoteAssignAction); } private: int setSelection = - 1; @@ -55,9 +59,88 @@ namespace Lumatone { bool setChannel = false; bool setNote = false; bool setColour = false; + bool setCCFaderPolarity = false; TerpstraKey previousData; TerpstraKey newData; }; -} \ No newline at end of file + class SectionEditAction : public UndoableAction + { + public: + SectionEditAction(int setSelection, TerpstraKeys& newSectionValue); + + SectionEditAction(const SectionEditAction& second) + : setSelection(second.setSelection) + , previousData(second.previousData) + , newData(second.newData) + {} + + bool isValid() const; + + virtual bool perform() override; + virtual bool undo() override; + int getSizeInUnits() override { return sizeof(SectionEditAction); } + + private: + int setSelection = -1; + + TerpstraKeys previousData; + TerpstraKeys newData; + }; + + class InvertFootControllerEditAction : public UndoableAction + { + public: + InvertFootControllerEditAction(bool newValue); + + InvertFootControllerEditAction(const InvertFootControllerEditAction& second) + : previousData(second.previousData), newData(second.newData) + {} + + virtual bool perform() override; + virtual bool undo() override; + int getSizeInUnits() override { return sizeof(InvertFootControllerEditAction); } + + private: + bool previousData; + bool newData; + }; + + class ExprPedalSensivityEditAction : public UndoableAction + { + public: + ExprPedalSensivityEditAction(int newValue); + + ExprPedalSensivityEditAction(const ExprPedalSensivityEditAction& second) + : previousData(second.previousData), newData(second.newData) + {} + + virtual bool perform() override; + virtual bool undo() override; + int getSizeInUnits() override { return sizeof(ExprPedalSensivityEditAction); } + + private: + int previousData; + int newData;; + }; + + class InvertSustainEditAction : public UndoableAction + { + public: + InvertSustainEditAction(bool newValue); + + InvertSustainEditAction(const InvertSustainEditAction& second) + : previousData(second.previousData), newData(second.newData) + {} + + virtual bool perform() override; + virtual bool undo() override; + int getSizeInUnits() override { return sizeof(InvertSustainEditAction); } + + private: + int previousData; + int newData;; + }; + +} diff --git a/Source/FileBrowserComponent.h b/Source/FileBrowserComponent.h index 798d1979..a96816af 100644 --- a/Source/FileBrowserComponent.h +++ b/Source/FileBrowserComponent.h @@ -18,11 +18,14 @@ class PathBrowserComponent : public Component, public Button::Listener { public: - PathBrowserComponent(const String dialogBoxTitle, const File& fileIn = File::getSpecialLocation(File::SpecialLocationType::userDocumentsDirectory), String fileTypeFilter = "") - : chooser(dialogBoxTitle, File(), fileTypeFilter) + PathBrowserComponent(const String dialogBoxTitle, + String fileTypeFilter, + File fileIn = File::getSpecialLocation(File::SpecialLocationType::userDocumentsDirectory)) { setName(dialogBoxTitle); + chooser = std::make_unique(dialogBoxTitle, fileIn, fileTypeFilter); + editor.reset(new TextEditor(dialogBoxTitle + "Editor")); editor->setMultiLine(false, false); editor->setReadOnly(true); @@ -51,11 +54,16 @@ class PathBrowserComponent : public Component, public Button::Listener void buttonClicked(Button* buttonThatWasClicked) override { - if (chooser.browseForFileToOpen()) - { - editor->setText(chooser.getResult().getFullPathName(), false); - listeners.call(&PathBrowserComponent::Listener::fileChanged, this, chooser.getResult()); - } + chooser->launchAsync(FileBrowserComponent::FileChooserFlags::openMode | FileBrowserComponent::FileChooserFlags::canSelectFiles, + [&](const FileChooser& chooser) + { + auto result = chooser.getResult(); + if (result.existsAsFile()) + { + editor->setText(chooser.getResult().getFullPathName(), false); + listeners.call(&PathBrowserComponent::Listener::fileChanged, this, chooser.getResult()); + } + }); } TextEditor* getEditor() { return editor.get(); } @@ -93,7 +101,7 @@ class PathBrowserComponent : public Component, public Button::Listener private: - FileChooser chooser; + std::unique_ptr chooser; std::unique_ptr editor; std::unique_ptr openButton; diff --git a/Source/FirmwareTransfer.cpp b/Source/FirmwareTransfer.cpp index a64fb632..7b287610 100644 --- a/Source/FirmwareTransfer.cpp +++ b/Source/FirmwareTransfer.cpp @@ -97,7 +97,7 @@ bool FirmwareTransfer::requestFirmwareUpdate(String firmwareFilePath) selectedFileToTransfer = firmwareFilePath; transferRequested = true; - runThread(); + launchThread(); return true; } @@ -142,7 +142,7 @@ void FirmwareTransfer::run() else if (transferRequested) { - prepareForUpdate(); + prepareAndRunUpdate(); transferRequested = false; } } @@ -193,12 +193,15 @@ static FirmwareTransfer::StatusCode shutdownSSHSession(LIBSSH2_SESSION* session, fclose(localFile); DBG("All done."); - libssh2_exit(); - return returnCode; } -bool FirmwareTransfer::prepareForUpdate() +void FirmwareTransfer::exitLibSsh2() +{ + libssh2_exit(); +} + +bool FirmwareTransfer::prepareAndRunUpdate() { StatusCode returnStatus = StatusCode::Initialize; listeners.call(&FirmwareTransfer::ProcessListener::firmwareTransferUpdate, returnStatus, statusCodeToMessage(returnStatus)); @@ -314,9 +317,10 @@ FirmwareTransfer::StatusCode FirmwareTransfer::performFirmwareUpdate() return StatusCode::StartupErr; } + // Make sure we release libssh2 before app is shutdown + TerpstraSysExApplication::getApp().setFirmwareUpdatePerformed(true); - -#ifdef JUCE_WIN +#if JUCE_WINDOWS // Create socket and connect to port 22 sock = socket(AF_INET, SOCK_STREAM, 0); @@ -339,11 +343,11 @@ FirmwareTransfer::StatusCode FirmwareTransfer::performFirmwareUpdate() { DBG("failed to connect!"); -#if WIN32 + #if WIN32 closesocket(sock); -#else + #else close(sock); -#endif + #endif return StatusCode::HostConnectErr; } diff --git a/Source/FirmwareTransfer.h b/Source/FirmwareTransfer.h index f3017bcb..d09ebc77 100644 --- a/Source/FirmwareTransfer.h +++ b/Source/FirmwareTransfer.h @@ -74,11 +74,18 @@ class FirmwareTransfer : public juce::ThreadWithProgressWindow // TODO use error codes static bool checkFirmwareFileIntegrity(String filePathIn); + +public: + + // Deinitialize libssh2 library + static void exitLibSsh2(); + public: class ProcessListener { public: + virtual ~ProcessListener() {} virtual void firmwareTransferUpdate(FirmwareTransfer::StatusCode statusCode, String msg)=0; }; @@ -94,7 +101,7 @@ class FirmwareTransfer : public juce::ThreadWithProgressWindow private: // Return true if update was successful - bool prepareForUpdate(); + bool prepareAndRunUpdate(); StatusCode performFirmwareUpdate(); // header only in .cpp @@ -122,7 +129,7 @@ class FirmwareTransfer : public juce::ThreadWithProgressWindow int numberOfWaitIncrements = 0; // Estimation based on boot time of ~85 seconds, plus transfer time, and overhead - const int maxUpdateIncrements = 300000 / UPDATETIMEOUT; + const int maxUpdateIncrements = 250000 / UPDATETIMEOUT; public: @@ -158,7 +165,8 @@ class FirmwareTransfer : public juce::ThreadWithProgressWindow return translate("Error: Could not prepare device communication protool"); case FirmwareTransfer::StatusCode::HostConnectErr: - return translate("Error: Could not communicate with Lumatone"); + return translate("Error: Could not communicate with Lumatone" + "\nPlease make sure you are connected over USB."); case FirmwareTransfer::StatusCode::SessionEstErr: return translate("Error: Could not verify connection with Lumatone"); diff --git a/Source/GeneralOptionsDlg.cpp b/Source/GeneralOptionsDlg.cpp index 2e0ffe6c..06c45f15 100644 --- a/Source/GeneralOptionsDlg.cpp +++ b/Source/GeneralOptionsDlg.cpp @@ -7,7 +7,7 @@ the "//[xyz]" and "//[/xyz]" sections will be retained when the file is loaded and re-saved. - Created with Projucer version: 6.0.5 + Created with Projucer version: 6.0.8 ------------------------------------------------------------------------------ @@ -29,29 +29,46 @@ //============================================================================== GeneralOptionsDlg::GeneralOptionsDlg () - : Component("GeneralOptionsDlg") { //[Constructor_pre] You can add your own custom stuff here.. //[/Constructor_pre] - labelGeneralSettingslTitle.reset (new juce::Label ("labelGeneralSettingslTitle", translate("GeneralSettings"))); + labelGeneralSettingslTitle.reset (new juce::Label ("labelGeneralSettingslTitle", + TRANS("General Settings"))); addAndMakeVisible (labelGeneralSettingslTitle.get()); - labelGeneralSettingslTitle->setFont(TerpstraSysExApplication::getApp().getAppFont(LumatoneEditorFont::UniviaProBold)); + labelGeneralSettingslTitle->setFont (juce::Font (18.00f, juce::Font::plain).withTypefaceStyle ("Regular")); + labelGeneralSettingslTitle->setJustificationType (juce::Justification::centredLeft); + labelGeneralSettingslTitle->setEditable (false, false, false); + labelGeneralSettingslTitle->setColour (juce::Label::textColourId, juce::Colour (0xff61acc8)); + labelGeneralSettingslTitle->setColour (juce::TextEditor::textColourId, juce::Colours::black); + labelGeneralSettingslTitle->setColour (juce::TextEditor::backgroundColourId, juce::Colour (0x00000000)); + labelGeneralSettingslTitle->setBounds (8, 0, 104, 24); buttonAfterTouchActive.reset (new juce::ToggleButton ("buttonAfterTouchActive")); addAndMakeVisible (buttonAfterTouchActive.get()); - buttonAfterTouchActive->setButtonText (translate("PolyphonicAftertouch")); + buttonAfterTouchActive->setButtonText (TRANS("Polyphonic Aftertouch")); buttonAfterTouchActive->addListener (this); + buttonAfterTouchActive->setBounds (8, 32, 176, 24); + buttonLightOnKeyStrokes.reset (new juce::ToggleButton ("buttonLightOnKeyStrokes")); addAndMakeVisible (buttonLightOnKeyStrokes.get()); - buttonLightOnKeyStrokes->setButtonText (translate("LightOnKeystrokes")); + buttonLightOnKeyStrokes->setButtonText (TRANS("Light on Keystrokes")); buttonLightOnKeyStrokes->addListener (this); + buttonLightOnKeyStrokes->setBounds (8, 64, 176, 24); + + //[UserPreSize] + labelGeneralSettingslTitle->setFont(TerpstraSysExApplication::getApp().getAppFont(LumatoneEditorFont::UniviaProBold)); + + TerpstraSysExApplication::getApp().getLumatoneController()->addFirmwareListener(this); //[/UserPreSize] + setSize (188, 96); + + //[Constructor] You can add your own custom stuff here.. //[/Constructor] } @@ -59,6 +76,7 @@ GeneralOptionsDlg::GeneralOptionsDlg () GeneralOptionsDlg::~GeneralOptionsDlg() { //[Destructor_pre]. You can add your own custom destruction code here.. + TerpstraSysExApplication::getApp().getLumatoneController()->removeFirmwareListener(this); //[/Destructor_pre] labelGeneralSettingslTitle = nullptr; @@ -74,11 +92,11 @@ GeneralOptionsDlg::~GeneralOptionsDlg() void GeneralOptionsDlg::paint (juce::Graphics& g) { //[UserPrePaint] Add your own custom painting code here.. - //[/UserPrePaint] - //g.fillAll (juce::Colour (0xffbad0de)); + //[/UserPrePaint] //[UserPaint] Add your own custom painting code here.. + g.setColour(Colour(0xff212626)); g.fillRoundedRectangle(getLocalBounds().toFloat().withTop(proportionOfHeight(SETTINGSAREAMARGINHEIGHT)), roundedCornerSize); //[/UserPaint] @@ -91,7 +109,7 @@ void GeneralOptionsDlg::resized() //[UserResized] Add your own custom resize handling here.. - roundedCornerSize = round(getParentHeight() * ROUNDEDCORNERTOAPPHEIGHT); + roundedCornerSize = roundToInt(getParentHeight() * ROUNDEDCORNERTOAPPHEIGHT); resizeLabelWithHeight(labelGeneralSettingslTitle.get(), roundToInt(getHeight() * SETTINGSLABELHEIGHT)); labelGeneralSettingslTitle->setTopLeftPosition(roundToInt(getWidth() * SETTINGSLABELMARGINWIDTH), 0); @@ -119,7 +137,7 @@ void GeneralOptionsDlg::buttonClicked (juce::Button* buttonThatWasClicked) //[UserButtonCode_buttonAfterTouchActive] -- add your button handler code here.. ((MainContentComponent*)getParentComponent())->getMappingInEdit().afterTouchActive = buttonAfterTouchActive->getToggleState(); TerpstraSysExApplication::getApp().setHasChangesToSave(true); - TerpstraSysExApplication::getApp().getLumatoneController().setAftertouchEnabled(buttonAfterTouchActive->getToggleState()); + TerpstraSysExApplication::getApp().getLumatoneController()->setAftertouchEnabled(buttonAfterTouchActive->getToggleState()); //[/UserButtonCode_buttonAfterTouchActive] } else if (buttonThatWasClicked == buttonLightOnKeyStrokes.get()) @@ -127,7 +145,7 @@ void GeneralOptionsDlg::buttonClicked (juce::Button* buttonThatWasClicked) //[UserButtonCode_buttonLightOnKeyStrokes] -- add your button handler code here.. ((MainContentComponent*)getParentComponent())->getMappingInEdit().lightOnKeyStrokes = buttonLightOnKeyStrokes->getToggleState(); TerpstraSysExApplication::getApp().setHasChangesToSave(true); - TerpstraSysExApplication::getApp().getLumatoneController().sendLightOnKeyStrokes(buttonLightOnKeyStrokes->getToggleState()); + TerpstraSysExApplication::getApp().getLumatoneController()->sendLightOnKeyStrokes(buttonLightOnKeyStrokes->getToggleState()); //[/UserButtonCode_buttonLightOnKeyStrokes] } @@ -155,6 +173,12 @@ void GeneralOptionsDlg::loadFromMapping() buttonLightOnKeyStrokes->setToggleState(mappingInEdit.lightOnKeyStrokes, juce::NotificationType::dontSendNotification); } +void GeneralOptionsDlg::presetFlagsReceived(PresetFlags presetFlags) +{ + buttonAfterTouchActive->setToggleState(presetFlags.polyphonicAftertouch, dontSendNotification); + buttonLightOnKeyStrokes->setToggleState(presetFlags.lightsOnKeystroke, dontSendNotification); +} + //[/MiscUserCode] @@ -168,10 +192,11 @@ void GeneralOptionsDlg::loadFromMapping() BEGIN_JUCER_METADATA - + parentClasses="public Component, public LumatoneEditor::FirmwareListener" + constructorParams="" variableInitialisers="" snapPixels="8" snapActive="1" + snapShown="1" overlayOpacity="0.330" fixedSize="1" initialWidth="188" + initialHeight="96"> + void cacheImages() { - //ImageCache::addImageToCache(ImageCache::getFromMemory(BinaryData::ImportIcon4x_png, BinaryData::ImportIcon4x_pngSize), LumatoneEditorAssets::ImportIcon); - //ImageCache::addImageToCache(ImageCache::getFromMemory(BinaryData::SaveIcon4x_png, BinaryData::SaveIcon4x_pngSize), LumatoneEditorAssets::SaveIcon); - //ImageCache::addImageToCache(ImageCache::getFromMemory(BinaryData::LoadIcon4x_png, BinaryData::LoadIcon4x_pngSize), LumatoneEditorAssets::LoadIcon); ImageCache::addImageToCache(ImageCache::getFromMemory(BinaryData::KeyboardBase_png, BinaryData::KeyboardBase_pngSize), LumatoneEditorAssets::LumatoneGraphic); ImageCache::addImageToCache(ImageCache::getFromMemory(BinaryData::KeybedShadows_png, BinaryData::KeybedShadows_pngSize), LumatoneEditorAssets::KeybedShadows); ImageCache::addImageToCache(ImageCache::getFromMemory(BinaryData::KeyShape2x_png, BinaryData::KeyShape2x_pngSize), LumatoneEditorAssets::KeyShape); @@ -1271,9 +1317,6 @@ class LumatoneEditorLookAndFeel : public LookAndFeel_V4 setColour(AlertWindow::ColourIds::outlineColourId, findColour(LumatoneEditorColourIDs::MediumBackground)); } -public: - LumatoneEditorCompactWindow compactWindowStyle; - private: LumatoneEditorFonts::Library appFonts; @@ -1289,6 +1332,10 @@ class LumatoneEditorLookAndFeel : public LookAndFeel_V4 const float comboBoxRoundedCornerScalar = 0.304878f; - const float rotaryAngleStart = float_Pi * -0.64f; // pi * -2/3 + const float rotaryAngleStart = MathConstants::pi * -0.64f; // pi * -2/3 const float rotaryAngleEnd = -rotaryAngleStart; + +public: + LumatoneEditorCompactWindow compactWindowStyle; + }; diff --git a/Source/LumatoneEditorStyleCommon.h b/Source/LumatoneEditorStyleCommon.h index 8f722e5e..f23654da 100644 --- a/Source/LumatoneEditorStyleCommon.h +++ b/Source/LumatoneEditorStyleCommon.h @@ -32,7 +32,7 @@ #define SETTINGSAREAMARGINHEIGHT 0.1714f -#define SETTINGSLABELHEIGHT 0.14f +#define SETTINGSLABELHEIGHT 0.13f #define SETTINGSLABELMARGINWIDTH 0.01f #define SETTINGSCONTROLMARGINTOAPPWIDTH 0.01171875f @@ -40,7 +40,7 @@ #if JUCE_MAC #define GLOBALFONTSCALAR 0.9f - #define CONTROLBOXFONTHEIGHTSCALAR 0.66f + #define CONTROLBOXFONTHEIGHTSCALAR 0.7f #elif JUCE_WINDOWS #define GLOBALFONTSCALAR 1.0f #define CONTROLBOXFONTHEIGHTSCALAR 0.8f @@ -101,7 +101,7 @@ static Path getConnectedRoundedRectPath(Rectangle bounds, float roundedCo { yTo = endpoint.y - roundedCornerSize; rect.lineTo(endpoint.x, yTo); - rect.addArc(endpoint.x - roundedCornerSize, yTo, roundedCornerSize, roundedCornerSize, PATH_PI_2_CW, float_Pi); + rect.addArc(endpoint.x - roundedCornerSize, yTo, roundedCornerSize, roundedCornerSize, PATH_PI_2_CW, MathConstants::pi); } if (connectedFlags & Button::ConnectedEdgeFlags::ConnectedOnBottom || connectedFlags & Button::ConnectedEdgeFlags::ConnectedOnLeft) @@ -111,7 +111,7 @@ static Path getConnectedRoundedRectPath(Rectangle bounds, float roundedCo else { rect.lineTo(roundedCornerSize, endpoint.y); - rect.addArc(origin.x, endpoint.y - roundedCornerSize, roundedCornerSize, roundedCornerSize, -float_Pi, PATH_PI_2_CCW); + rect.addArc(origin.x, endpoint.y - roundedCornerSize, roundedCornerSize, roundedCornerSize, -MathConstants::pi, PATH_PI_2_CCW); } rect.closeSubPath(); @@ -143,7 +143,7 @@ static Path getDiagonalRoundedCornersPath(Rectangle bounds, float rounded { xTo += roundedCornerSize; rect.lineTo(xTo, yTo); - rect.addArc(xTo - roundedCornerSize, yTo - roundedCornerSize, roundedCornerSize, roundedCornerSize, -float_Pi, PATH_PI_2_CCW); + rect.addArc(xTo - roundedCornerSize, yTo - roundedCornerSize, roundedCornerSize, roundedCornerSize, -MathConstants::pi, PATH_PI_2_CCW); } else { @@ -161,7 +161,7 @@ static Path getDiagonalRoundedCornersPath(Rectangle bounds, float rounded { yTo -= roundedCornerSize; rect.lineTo(xTo, yTo); - rect.addArc(xTo - roundedCornerSize, yTo, roundedCornerSize, roundedCornerSize, PATH_PI_2_CW, float_Pi); + rect.addArc(xTo - roundedCornerSize, yTo, roundedCornerSize, roundedCornerSize, PATH_PI_2_CW, MathConstants::pi); } else { @@ -271,10 +271,10 @@ static void addArcToPath(Path& pathIn, Rectangle& ellipseBounds, float fr /// /// /// -static void setWidthRetainingAspectRatio(Component* component, const Image& image, int widthIn) -{ - component->setSize(widthIn, round(image.getHeight() / (float) image.getWidth() * widthIn)); -} +//static void setWidthRetainingAspectRatio(Component* component, const Image& image, int widthIn) +//{ +// component->setSize(widthIn, round(image.getHeight() / (float) image.getWidth() * widthIn)); +//} /// /// Sets the height of a component while retaining the aspect ratio of a given image @@ -282,10 +282,10 @@ static void setWidthRetainingAspectRatio(Component* component, const Image& imag /// /// /// -static void setHeightRetainingAspectRatio(Component* component, const Image& image, int heightIn) -{ - component->setSize(round(image.getWidth() / (float) image.getHeight() * heightIn), heightIn); -} +//static void setHeightRetainingAspectRatio(Component* component, const Image& image, int heightIn) +//{ +// component->setSize(round(image.getWidth() / (float) image.getHeight() * heightIn), heightIn); +//} /// /// Sets the width of an ImageComponent while retaining the aspect ratio of its image @@ -304,10 +304,10 @@ static void setHeightRetainingAspectRatio(Component* component, const Image& ima /// /// /// -static void setHeightRetainingAspectRatio(ImageComponent* component, int heightIn) -{ - setHeightRetainingAspectRatio(component, component->getImage(), heightIn); -} +//static void setHeightRetainingAspectRatio(ImageComponent* component, int heightIn) +//{ +// setHeightRetainingAspectRatio(component, component->getImage(), heightIn); +//} /// /// Sets the width of an ImageButton while retaining the aspect ratio of its normal image @@ -315,10 +315,10 @@ static void setHeightRetainingAspectRatio(ImageComponent* component, int heightI /// /// /// -static void setWidthRetainingAspectRatio(ImageButton* component, int widthIn) -{ - setWidthRetainingAspectRatio(component, component->getNormalImage(), widthIn); -} +//static void setWidthRetainingAspectRatio(ImageButton* component, int widthIn) +//{ +// setWidthRetainingAspectRatio(component, component->getNormalImage(), widthIn); +//} /// /// Sets the height of an ImageButton while retaining the aspect ratio of its normal image @@ -326,10 +326,10 @@ static void setWidthRetainingAspectRatio(ImageButton* component, int widthIn) /// /// /// -static void setHeightRetainingAspectRatio(ImageButton* component, int heightIn) -{ - setHeightRetainingAspectRatio(component, component->getNormalImage(), heightIn); -} +//static void setHeightRetainingAspectRatio(ImageButton* component, int heightIn) +//{ +// setHeightRetainingAspectRatio(component, component->getNormalImage(), heightIn); +//} static void resizeLabelWithHeight(Label* label, int height, float fontHeightScalar = 1.0f, String textSuffix = "_") { @@ -381,7 +381,7 @@ static float scalarToFitString(Label& labelIn) static void resizeToggleButtonWithHeight(ToggleButton* btn, Font font, int heightIn, String textSuffix = "_") { font.setHeight(font.getHeight() * GLOBALFONTSCALAR); - btn->setSize(btn->getHeight() + round(font.getStringWidth(btn->getButtonText() + textSuffix)), heightIn); + btn->setSize(btn->getHeight() + roundToInt(font.getStringWidth(btn->getButtonText() + textSuffix)), heightIn); } static void drawPathToFillBounds(Graphics& g, const Path& path, Rectangle boundsToFill) @@ -403,7 +403,7 @@ static Path createLogomark() float phi2 = PHI * 2; float innerRad = 1.0f / phi2; float outerRad = phi2 * 0.125f; - float ang = float_Pi * 0.083333f; + float ang = MathConstants::pi * 0.083333f; float angOff = ang * 0.5f; logo.addPolygon(center, 6, innerRad, ang - angOff); @@ -550,8 +550,32 @@ static void getCCPolarityIconPath(bool inverted, Path& arrowPath, Path& faderPat } } -//static void drawSaveIconAt(Graphics& g, int x, int y) +static Path getCloneIconPath() +{ + // side-by-side +// float yMargin = 0.1f; +// float height = 1.0f - yMargin * 2.0f; +// +// float width = 5.0f/12.0f; +// +// auto leftRect = Rectangle(1.0/12.0f, yMargin, width, height); +// auto rightRect = Rectangle(width, yMargin, width, height); + + // bottom left overlapping top right + + float xMargin = 0.1f; + float yMargin = 0.1f; + float size = 0.5f; + + auto leftRect = Rectangle(xMargin, 1.0f - yMargin - size, size, size); + auto rightRect = Rectangle(1.0f - xMargin - size, yMargin, size, size); + + auto path = Path(); + path.addRoundedRectangle(leftRect, 0.1f, 0.1f); + path.addRoundedRectangle(rightRect, 0.1f, 0.1f); + return path; +} // Hash codes for use with ImageCache::getFromHashCode() enum LumatoneEditorAssets @@ -566,9 +590,31 @@ enum LumatoneEditorAssets TickBox = 0x0003100, SavePalette = 0x0005000, CancelPalette = 0x0005001, - TrashCanIcon = 0x0005002 + TrashCanIcon = 0x0005002, + CloneIcon = 0x0005003 }; +// TODO: clean up / make a better routine with ImageCache usage +static Image getCachedCloneImage() +{ + auto cloneImg = ImageCache::getFromHashCode(LumatoneEditorAssets::CloneIcon); + if (cloneImg.isValid()) + return cloneImg; + + // Create duplicate icon + auto cloneIcon = getCloneIconPath(); + cloneIcon.scaleToFit(0, 0, 80, 80, true); + + cloneImg = Image(Image::PixelFormat::ARGB, 100, 100, true); + Graphics cloneG(cloneImg); + cloneG.setColour(Colours::white.darker(0.1f)); + cloneG.setOrigin(Point(10, 10)); + auto stroke = PathStrokeType(8.0f, PathStrokeType::JointStyle::curved); + cloneG.strokePath(cloneIcon, stroke); + ImageCache::addImageToCache(cloneImg, LumatoneEditorAssets::CloneIcon); + return cloneImg; +} + enum LumatoneEditorIcon { Checkmark = 0x01, @@ -650,3 +696,32 @@ namespace LumatoneEditorStyleIDs static Identifier roundedDiagonalCorners = Identifier("RoundedDiagonalCorners"); } + +// LookAndFeel doesn't have Slider IncDec button access in drawIncDecButtonsBackground +class TextButtonMouseHighlight : public TextButton +{ + + Colour highlightColour; + +public: + + TextButtonMouseHighlight(Colour highlightColourIn = Colour()) + : highlightColour(highlightColourIn) {} + + ~TextButtonMouseHighlight() {} + + void paint(Graphics& g) override + { + TextButton::paint(g); + + if (isEnabled() && isMouseOver()) + { + auto bounds = getLocalBounds().toFloat(); + + Colour c = (isMouseButtonDown()) ? highlightColour.overlaidWith(Colours::white.withAlpha(0.1f)) + : highlightColour; + g.setColour(c); + g.fillRoundedRectangle(bounds, getHeight() * 0.25f); + } + } +}; diff --git a/Source/LumatoneFirmwareDefinitions.h b/Source/LumatoneFirmwareDefinitions.h index 86cfb332..e5efb480 100644 --- a/Source/LumatoneFirmwareDefinitions.h +++ b/Source/LumatoneFirmwareDefinitions.h @@ -146,25 +146,35 @@ System exclusive command bytes #define CALLIBRATE_EXPRESSION_PEDAL 0x38 #define RESET_EXPRESSION_PEDAL_BOUNDS 0x39 -// Firmware Version 1.0.12 +// Firmware Version 1.0.12 / 1.1.0 #define GET_BOARD_THRESHOLD_VALUES 0x3A #define GET_BOARD_SENSITIVITY_VALUES 0x3B -// Firmware Version 1.0.13 #define SET_PERIPHERAL_CHANNELS 0x3C #define GET_PERIPHERAL_CHANNELS 0x3D #define PERIPHERAL_CALBRATION_DATA 0x3E -// Firmware Version 1.0.14 #define SET_AFTERTOUCH_TRIGGER_DELAY 0x3F #define GET_AFTERTOUCH_TRIGGER_DELAY 0x40 -// Firmware Version 1.0.15 #define SET_LUMATOUCH_NOTE_OFF_DELAY 0x41 #define GET_LUMATOUCH_NOTE_OFF_DELAY 0x42 #define SET_EXPRESSION_PEDAL_THRESHOLD 0x43 #define GET_EXPRESSION_PEDAL_THRESHOLD 0x44 #define INVERT_SUSTAIN_PEDAL 0x45 +#define RESET_DEFAULT_PRESETS 0x46 +#define GET_PRESET_FLAGS 0x47 +#define GET_EXPRESSION_PEDAL_SENSITIVIY 0x48 + +#define GET_MACRO_LIGHT_INTENSITY 0x49 +#define RESET_MACRO_LIGHT_INTENSITY 0x4A + +#define RESET_BOARD_KEYS 0x4B +#define RESET_AFTERTOUCH_TRIGGER_DELAY 0x4C + +#define RESET_LUMATOUCH_NOTE_OFF_DELAY 0x4D +#define GET_PITCH_AND_MOD_BOUNDS 0x4E +#define GET_EXPRESSION_PEDAL_BOUNDS 0x4F typedef enum { @@ -177,12 +187,19 @@ typedef enum typedef enum { + disabledDefault = 0, noteOnNoteOff = 1, continuousController = 2, lumaTouch = 3, disabled = 4 } LumatoneKeyType; +typedef enum +{ + ExpressionPedal = 0, + PitchAndModWheels +} PeripheralCalibrationDataMode; + enum class LumatoneFirmwareVersion { NO_VERSION = 0, // Used for instantiation @@ -198,10 +215,9 @@ enum class LumatoneFirmwareVersion VERSION_1_0_10, VERSION_1_0_11, VERSION_1_0_12, - VERSION_1_0_13, - VERSION_1_0_14, - VERSION_1_0_15, - LAST_VERSION = VERSION_1_0_15, + VERSION_1_1_0 = VERSION_1_0_12, + VERSION_1_2_0, + LAST_VERSION = VERSION_1_2_0, FUTURE_VERSION = 0xFFFF // Used when version is nonnegative and below 9.9.999 } ; @@ -218,6 +234,15 @@ struct FirmwareVersion String toString() const { return String(major) + "." + String(minor) + "." + String(revision); } + String toDisplayString() const + { + String str = String(major) + "." + String(minor); + if (revision > 0) + str += ("." + String(revision)); + + return str; + } + //============================================================================ static FirmwareVersion fromString(String firmwareVersion) @@ -289,7 +314,8 @@ struct FirmwareSupport messageIsNotResponseToCommand, messageIsNotSysEx, unknownCommand, - externalError + externalError, + commandNotImplemented }; String errorToString(Error err) @@ -322,6 +348,8 @@ struct FirmwareSupport return "Unknown command / Not Acknowledged"; case Error::externalError: return "Error from device"; + case Error::commandNotImplemented: + return "Command handling not implemented"; default: return "Undefined error.."; } @@ -335,21 +363,40 @@ struct FirmwareSupport else if ((versionIn.major < 0) | (versionIn.minor < 0) | (versionIn.revision < 0)) return LumatoneFirmwareVersion::UNKNOWN_VERSION; + // MAJOR: 1 else if (versionIn.major == 1) { + // MINOR: 0 if (versionIn.minor == 0) { if (versionIn.revision < 3) return LumatoneFirmwareVersion::VERSION_55_KEYS; + // Computing is probably not the best thing to do but edge cases are extremely unlikely here else if (versionIn.revision - 3 > (int)LumatoneFirmwareVersion::LAST_VERSION - (int)LumatoneFirmwareVersion::VERSION_1_0_3) return LumatoneFirmwareVersion::FUTURE_VERSION; else if (versionIn.revision >= 3) return (LumatoneFirmwareVersion)((int)LumatoneFirmwareVersion::VERSION_1_0_3 + (versionIn.revision - 3)); } + + // MINOR: 1 + else if (versionIn.minor == 1) + { + if (versionIn.revision == 0) + return LumatoneFirmwareVersion::VERSION_1_1_0; + } + + else if (versionIn.minor == 2) + { + if (versionIn.revision == 0) + return LumatoneFirmwareVersion::VERSION_1_2_0; + } + + return LumatoneFirmwareVersion::FUTURE_VERSION; } + // Unsure if this is needed, or if returning FUTURE_VERSION without a condition is better else if (versionIn.major < 9 && versionIn.minor < 9 && versionIn.revision < 999) return LumatoneFirmwareVersion::FUTURE_VERSION; @@ -361,22 +408,22 @@ struct FirmwareSupport LumatoneFirmwareVersion getLowestVersionAcknowledged(unsigned int CMD) { if (CMD < CHANGE_KEY_NOTE) // 0x00 - LumatoneFirmwareVersion::UNKNOWN_VERSION; + return LumatoneFirmwareVersion::UNKNOWN_VERSION; else if (CMD <= GET_SERIAL_IDENTITY) // 0x23 - LumatoneFirmwareVersion::NO_VERSION; + return LumatoneFirmwareVersion::NO_VERSION; else if (CMD <= DEMO_MODE) // 0x25 - LumatoneFirmwareVersion::VERSION_1_0_5; + return LumatoneFirmwareVersion::VERSION_1_0_5; else if (CMD <= CALIBRATE_PITCH_MOD_WHEEL) // 0x26 - LumatoneFirmwareVersion::VERSION_1_0_6; + return LumatoneFirmwareVersion::VERSION_1_0_6; else if (CMD <= SET_KEY_MAX_THRESHOLD) // 0x29 - LumatoneFirmwareVersion::VERSION_1_0_7; + return LumatoneFirmwareVersion::VERSION_1_0_7; else if (CMD <= GET_FIRMWARE_REVISION) // 0x31 - LumatoneFirmwareVersion::VERSION_1_0_8; + return LumatoneFirmwareVersion::VERSION_1_0_8; else if (CMD <= LUMA_PING) // 0x33 return LumatoneFirmwareVersion::VERSION_1_0_9; @@ -387,17 +434,8 @@ struct FirmwareSupport else if (CMD <= RESET_EXPRESSION_PEDAL_BOUNDS) // 0x39 return LumatoneFirmwareVersion::VERSION_1_0_11; - else if (CMD <= GET_BOARD_SENSITIVITY_VALUES) // 0x3B - return LumatoneFirmwareVersion::VERSION_1_0_12; - - else if (CMD <= PERIPHERAL_CALBRATION_DATA) // 0x3E - return LumatoneFirmwareVersion::VERSION_1_0_13; - - else if (CMD <= SET_AFTERTOUCH_TRIGGER_DELAY) // 0x3F - return LumatoneFirmwareVersion::VERSION_1_0_14; - - else if (CMD <= INVERT_SUSTAIN_PEDAL) //0x45 - return LumatoneFirmwareVersion::VERSION_1_0_15; + else if (CMD <= GET_EXPRESSION_PEDAL_BOUNDS) // 0x4F + return LumatoneFirmwareVersion::VERSION_1_1_0; else return LumatoneFirmwareVersion::FUTURE_VERSION; @@ -434,3 +472,104 @@ struct FirmwareSupport } }; + +typedef enum +{ + PitchWheel = 0, + ModWheel, + Expression, + Sustain +} PeripheralChannel; + +struct PeripheralChannelSettings +{ + int pitchWheel = 1; + int modWheel = 1; + int expressionPedal = 1; + int sustainPedal = 1; + + void setChannel(PeripheralChannel controlId, int channelIn) + { + if (channelIn < 1) + channelIn = 1; + + if (channelIn > 16) + channelIn = 16; + + switch (controlId) + { + case PeripheralChannel::PitchWheel: + pitchWheel = channelIn; + break; + + case PeripheralChannel::ModWheel: + modWheel = channelIn; + break; + + case PeripheralChannel::Expression: + expressionPedal = channelIn; + break; + + case PeripheralChannel::Sustain: + sustainPedal = channelIn; + break; + } + } + + int getChannel(PeripheralChannel controlId) + { + switch (controlId) + { + case PeripheralChannel::PitchWheel: + return pitchWheel; + + case PeripheralChannel::ModWheel: + return modWheel; + + case PeripheralChannel::Expression: + return expressionPedal; + + case PeripheralChannel::Sustain: + return sustainPedal; + } + + return 0; + } +}; + +#define ADCSCALAR 2.44140625e-4; + +struct WheelsCalibrationData +{ + int centerPitch = 0; + int minPitch = 0; + int maxPitch = 0; + + int minMod = 0; + int maxMod = 0; + + float getCentrePitchNorm() const { return centerPitch * ADCSCALAR; } + float getMinPitchNorm() const { return minPitch * ADCSCALAR; } + float getMaxPitchNorm() const { return maxPitch * ADCSCALAR; } + float getMinModNorm() const { return minMod * ADCSCALAR; } + float getMaxModNorm() const { return maxMod * ADCSCALAR; } + + String toString() const + { + String str; + str += ("Center Pitch: " + String(centerPitch) + newLine); + str += (" Min Pitch: " + String(minPitch) + newLine); + str += (" Max Pitch: " + String(maxPitch) + newLine); + str += (" Min Mod: " + String(minMod) + newLine); + str += (" Max Mod: " + String(maxMod) + newLine); + return str; + } +}; + +struct PresetFlags +{ + bool expressionPedalInverted = false; + bool lightsOnKeystroke = false; + bool polyphonicAftertouch = false; + bool sustainPedalInverted = false; +}; diff --git a/Source/LumatoneMenu.cpp b/Source/LumatoneMenu.cpp index 655244d6..85173e40 100644 --- a/Source/LumatoneMenu.cpp +++ b/Source/LumatoneMenu.cpp @@ -51,6 +51,10 @@ namespace Lumatone { menu.addCommandItem(theManager, deleteOctaveBoard); menu.addCommandItem(theManager, copyOctaveBoard); menu.addCommandItem(theManager, pasteOctaveBoard); + menu.addCommandItem(theManager, pasteOctaveBoardChannels); + menu.addCommandItem(theManager, pasteOctaveBoardNotes); + menu.addCommandItem(theManager, pasteOctaveBoardColours); + menu.addCommandItem(theManager, pasteOctaveBoardTypes); menu.addCommandItem(theManager, undo); menu.addCommandItem(theManager, redo); } @@ -82,4 +86,4 @@ namespace Lumatone { } } -} \ No newline at end of file +} diff --git a/Source/LumatoneMenu.h b/Source/LumatoneMenu.h index 19589155..6ed939bd 100644 --- a/Source/LumatoneMenu.h +++ b/Source/LumatoneMenu.h @@ -21,15 +21,20 @@ namespace Lumatone { saveSysExMappingAs = 0x200012, resetSysExMapping = 0x200013, - deleteOctaveBoard = 0x200017, - copyOctaveBoard = 0x200018, - pasteOctaveBoard = 0x200019, - undo = 0x200020, - redo = 0x200021, - - recentFilesBaseID = 0x200100, - - aboutSysEx = 0x200040 + deleteOctaveBoard = 0x200100, + copyOctaveBoard = 0x200101, + pasteOctaveBoard = 0x200102, + pasteOctaveBoardNotes = 0x200103, + pasteOctaveBoardChannels = 0x200104, + pasteOctaveBoardColours = 0x200105, + pasteOctaveBoardTypes = 0x200106, + + undo = 0x200200, + redo = 0x200201, + + recentFilesBaseID = 0x200300, + + aboutSysEx = 0x200400 }; class MainMenuModel : public MenuBarModel @@ -55,4 +60,4 @@ namespace Lumatone { toggleDeveloperMode = 0xA00001 }; } -} \ No newline at end of file +} diff --git a/Source/Main.cpp b/Source/Main.cpp index 701fbba1..3f4db957 100644 --- a/Source/Main.cpp +++ b/Source/Main.cpp @@ -17,7 +17,7 @@ //============================================================================== -MainContentComponent* TerpstraSysExApplication::getMainContentComponent() +MainContentComponent* TerpstraSysExApplication::getMainContentComponent() const { jassert(mainWindow != nullptr); return (MainContentComponent*)(mainWindow->getContentComponent()); @@ -38,16 +38,13 @@ TerpstraSysExApplication::TerpstraSysExApplication() propertiesFile = new PropertiesFile(options); jassert(propertiesFile != nullptr); + lumatoneController = std::make_unique(); + // Localisation String localisation = getLocalisation(SystemStats::getDisplayLanguage()); LocalisedStrings::setCurrentMappings(new LocalisedStrings(localisation, false)); LocalisedStrings::getCurrentMappings()->setFallback(new LocalisedStrings(BinaryData::engb_txt, false)); - // Window aspect ratio - boundsConstrainer.reset(new ComponentBoundsConstrainer()); - boundsConstrainer->setFixedAspectRatio(DEFAULTMAINWINDOWASPECT); - boundsConstrainer->setMinimumSize(800, round(800 / DEFAULTMAINWINDOWASPECT)); - // Colour scheme //lookAndFeel.setColourScheme(lookAndFeel.getDarkColourScheme()); @@ -77,7 +74,7 @@ TerpstraSysExApplication::TerpstraSysExApplication() userDocumentsDirectory = File::getSpecialLocation(File::userDocumentsDirectory).getChildFile("Lumatone Editor"); userDocumentsDirectory.createDirectory(); } - + possibleDirectory = propertiesFile->getValue("UserMappingsDirectory"); if (File::isAbsolutePath(possibleDirectory)) { @@ -88,7 +85,7 @@ TerpstraSysExApplication::TerpstraSysExApplication() userMappingsDirectory = userDocumentsDirectory.getChildFile("Mappings"); userMappingsDirectory.createDirectory(); } - + possibleDirectory = propertiesFile->getValue("UserPalettesDirectory"); if (File::isAbsolutePath(possibleDirectory)) { @@ -102,9 +99,6 @@ TerpstraSysExApplication::TerpstraSysExApplication() reloadColourPalettes(); - lumatoneController.setDeviceDetectionTimeout(propertiesFile->getIntValue("DetectDevicesTimeout", 500)); - lumatoneController.checkConnectionWhenInactive(propertiesFile->getBoolValue("CheckConnectionIfInactive", true)); - // State of main window will be read from properties file when main window is created } @@ -123,10 +117,10 @@ void TerpstraSysExApplication::initialise(const String& commandLine) // ToDo switch on/off isomorphic mass assign mode // Try to open a config file - if (File::isAbsolutePath(commandLineParameter)) - { - currentFile = File(commandLineParameter); - } + if (File::isAbsolutePath(commandLineParameter)) + { + currentFile = File(commandLineParameter); + } else { // If file name is with quotes, try removing the quotes @@ -143,7 +137,9 @@ void TerpstraSysExApplication::initialise(const String& commandLine) commandManager->registerAllCommandsForTarget(this); menuModel.reset(new Lumatone::Menu::MainMenuModel(commandManager.get())); - mainWindow.reset(new MainWindow()); + boundsConstrainer = std::make_unique(); + + mainWindow.reset(new MainWindow(boundsConstrainer.get())); mainWindow->addKeyListener(commandManager->getKeyMappings()); mainWindow->restoreStateFromPropertiesFile(propertiesFile); @@ -163,12 +159,12 @@ void TerpstraSysExApplication::initialise(const String& commandLine) void TerpstraSysExApplication::shutdown() { - // Add your application's shutdown code here.. - + // Add your application's shutdown code here.. + // Save documents directories (Future: provide option to change them and save after changed by user) propertiesFile->setValue("UserDocumentsDirectory", userDocumentsDirectory.getFullPathName()); - propertiesFile->setValue("UserMappingsDirectory", userMappingsDirectory.getFullPathName()); - propertiesFile->setValue("UserPalettesDirectory", userPalettesDirectory.getFullPathName()); + propertiesFile->setValue("UserMappingsDirectory", userMappingsDirectory.getFullPathName()); + propertiesFile->setValue("UserPalettesDirectory", userPalettesDirectory.getFullPathName()); // Save recent files list recentFiles.removeNonExistentFiles(); @@ -186,35 +182,54 @@ void TerpstraSysExApplication::shutdown() #if JUCE_MAC MenuBarModel::setMacMainMenu(nullptr); +#else + mainWindow->setMenuBarComponent(nullptr); #endif menuModel = nullptr; mainWindow = nullptr; // (deletes our window) + + if (firmwareUpdateWasPerformed) + FirmwareTransfer::exitLibSsh2(); + // commandManager = nullptr; } //============================================================================== void TerpstraSysExApplication::systemRequestedQuit() { - // This is called when the app is being asked to quit: you can ignore this - // request and let the app carry on running, or call quit() to allow the app to close. + // This is called when the app is being asked to quit: you can ignore this + // request and let the app carry on running, or call quit() to allow the app to close. // If there are changes: ask for save if (hasChangesToSave) { - int retc = AlertWindow::showYesNoCancelBox(AlertWindow::AlertIconType::QuestionIcon, "Quitting the application", "Do you want to save your changes?"); - if (retc == 0) - { - // "Cancel". Do not quit. - return; - } - else if (retc == 1) - { - // "Yes". Try to save. Cancel if unsuccessful - if (!saveSysExMapping()) - return; - } - // retc == 2: "No" -> end without saving + AlertWindow::showYesNoCancelBox( + AlertWindow::AlertIconType::QuestionIcon, + "Quitting the application", + "Do you want to save your changes?", + "Yes", "No", "Cancel", nullptr, + ModalCallbackFunction::create([&](int retc) + { + if (retc == 0) + { + // "Cancel". Do not quit. + return; + } + else if (retc == 1) + { + // "Yes". Try to save. Cancel if unsuccessful + saveSysExMapping([&](bool success) { if (success) quit(); }); + } + else + { + // retc == 2: "No" -> end without saving + quit(); + } + }) + ); + + return; } quit(); @@ -222,9 +237,9 @@ void TerpstraSysExApplication::systemRequestedQuit() void TerpstraSysExApplication::anotherInstanceStarted(const String& commandLine) { - // When another instance of the app is launched while this one is running, - // this method is invoked, and the commandLine parameter tells you what - // the other instance's command-line arguments were. + // When another instance of the app is launched while this one is running, + // this method is invoked, and the commandLine parameter tells you what + // the other instance's command-line arguments were. } void TerpstraSysExApplication::reloadColourPalettes() @@ -250,27 +265,41 @@ bool TerpstraSysExApplication::saveColourPalette(LumatoneEditorColourPalette& pa { ValueTree paletteNode = palette.toValueTree(); + if (pathToFile == File()) + pathToFile = File(palette.getPathToFile()); + + // If name changed, delete the old one + if (pathToFile.exists()) + { + auto currentName = pathToFile.getFileName(); + if (currentName != palette.getName()) + { + pathToFile.deleteFile(); + } + } + // New file if (!pathToFile.existsAsFile()) { - if (palette.getName() != String()) - pathToFile = userPalettesDirectory.getChildFile(palette.getName()); - else - pathToFile = userPalettesDirectory.getChildFile("UnnamedPalette"); + String fileName = "UnnamedPalette"; + + if (palette.getName().isNotEmpty()) + fileName = palette.getName(); + + pathToFile = userPalettesDirectory.getChildFile(fileName); // Make sure filename is unique since saving happens automatically - int nameId = 0; - - // One thousand should be enough...right? - while (pathToFile.withFileExtension(PALETTEFILEEXTENSION).existsAsFile() && nameId < 1000) + // Sorry programmers, we're using cardinal numbers here, and the original is implicitly #1 ;) + int nameId = 1; + while (pathToFile.withFileExtension(PALETTEFILEEXTENSION).existsAsFile() && nameId < 999999) { - nameId++; - pathToFile = userPalettesDirectory.getChildFile("UnnamedPalette" + String(nameId)); + auto fileNameToSave = fileName + "_" + String(++nameId); + pathToFile = userPalettesDirectory.getChildFile(fileNameToSave); } } - success = palette.saveToFile(pathToFile); - + success = palette.saveToFile(pathToFile); + // TODO error handling? } @@ -305,6 +334,11 @@ void TerpstraSysExApplication::getAllCommands(Array & commands) Lumatone::Menu::commandIDs::deleteOctaveBoard, Lumatone::Menu::commandIDs::copyOctaveBoard, Lumatone::Menu::commandIDs::pasteOctaveBoard, + Lumatone::Menu::commandIDs::pasteOctaveBoardChannels, + Lumatone::Menu::commandIDs::pasteOctaveBoardNotes, + Lumatone::Menu::commandIDs::pasteOctaveBoardColours, + Lumatone::Menu::commandIDs::pasteOctaveBoardTypes, + Lumatone::Menu::commandIDs::undo, Lumatone::Menu::commandIDs::redo, @@ -322,22 +356,22 @@ void TerpstraSysExApplication::getCommandInfo(CommandID commandID, ApplicationCo { case Lumatone::Menu::commandIDs::openSysExMapping: result.setInfo("Load file mapping", "Open a Lumatone key mapping", "File", 0); - result.addDefaultKeypress('o', ModifierKeys::ctrlModifier); + result.addDefaultKeypress('o', ModifierKeys::commandModifier); break; case Lumatone::Menu::commandIDs::saveSysExMapping: result.setInfo("Save mapping", "Save the current mapping to file", "File", 0); - result.addDefaultKeypress('s', ModifierKeys::ctrlModifier); + result.addDefaultKeypress('s', ModifierKeys::commandModifier); break; case Lumatone::Menu::commandIDs::saveSysExMappingAs: result.setInfo("Save mapping as...", "Save the current mapping to new file", "File", 0); - result.addDefaultKeypress('a', ModifierKeys::ctrlModifier); + result.addDefaultKeypress('a', ModifierKeys::commandModifier); break; case Lumatone::Menu::commandIDs::resetSysExMapping: result.setInfo("New", "Start new mapping. Clear all edit fields, do not save current edits.", "File", 0); - result.addDefaultKeypress('n', ModifierKeys::ctrlModifier); + result.addDefaultKeypress('n', ModifierKeys::commandModifier); break; case Lumatone::Menu::commandIDs::deleteOctaveBoard: @@ -346,24 +380,50 @@ void TerpstraSysExApplication::getCommandInfo(CommandID commandID, ApplicationCo break; case Lumatone::Menu::commandIDs::copyOctaveBoard: - result.setInfo("Copy", "Copy section data", "Edit", 0); - result.addDefaultKeypress('c', ModifierKeys::ctrlModifier); + result.setInfo("Copy section", "Copy current octave board data", "Edit", 0); + result.addDefaultKeypress('c', ModifierKeys::commandModifier); break; case Lumatone::Menu::commandIDs::pasteOctaveBoard: - result.setInfo("Paste", "Paste section data", "Edit", 0); - result.addDefaultKeypress('v', ModifierKeys::ctrlModifier); + result.setInfo("Paste section", "Paste copied section data", "Edit", 0); + result.addDefaultKeypress('v', ModifierKeys::commandModifier); + result.setActive(canPasteSubBoardData()); break; + case Lumatone::Menu::commandIDs::pasteOctaveBoardNotes: + result.setInfo("Paste notes", "Paste copied section notes", "Edit", 0); + result.addDefaultKeypress('v', ModifierKeys::commandModifier | ModifierKeys::shiftModifier); + result.setActive(canPasteSubBoardData()); + break; + + case Lumatone::Menu::commandIDs::pasteOctaveBoardChannels: + result.setInfo("Paste channels", "Paste copied section channels", "Edit", 0); + result.addDefaultKeypress('v', ModifierKeys::commandModifier | ModifierKeys::altModifier); + result.setActive(canPasteSubBoardData()); + break; + + case Lumatone::Menu::commandIDs::pasteOctaveBoardColours: + result.setInfo("Paste colours", "Paste copied section colours", "Edit", 0); + result.addDefaultKeypress('v', ModifierKeys::altModifier); + result.setActive(canPasteSubBoardData()); + break; + + case Lumatone::Menu::commandIDs::pasteOctaveBoardTypes: + result.setInfo("Paste types", "Paste copied section key types", "Edit", 0); + result.addDefaultKeypress('v', ModifierKeys::altModifier | ModifierKeys::shiftModifier); + result.setActive(canPasteSubBoardData()); + break; + case Lumatone::Menu::commandIDs::undo: result.setInfo("Undo", "Undo latest edit", "Edit", 0); - result.addDefaultKeypress('z', ModifierKeys::ctrlModifier); + result.addDefaultKeypress('z', ModifierKeys::commandModifier); result.setActive(undoManager.canUndo()); break; case Lumatone::Menu::commandIDs::redo: result.setInfo("Redo", "Redo latest edit", "Edit", 0); - result.addDefaultKeypress('y', ModifierKeys::ctrlModifier); + result.addDefaultKeypress('y', ModifierKeys::commandModifier); + result.addDefaultKeypress('z', ModifierKeys::commandModifier + ModifierKeys::shiftModifier); result.setActive(undoManager.canRedo()); break; @@ -373,7 +433,9 @@ void TerpstraSysExApplication::getCommandInfo(CommandID commandID, ApplicationCo case Lumatone::Debug::commandIDs::toggleDeveloperMode: result.setInfo("Toggle Developer Mode", "Show/hide controls for tweaking internal parameters", "Edit", 0); - result.addDefaultKeypress('m', juce::ModifierKeys::allKeyboardModifiers); + result.addDefaultKeypress('m', + juce::ModifierKeys::ctrlModifier + juce::ModifierKeys::altModifier + juce::ModifierKeys::shiftModifier); + result.setActive(true); break; default: @@ -394,13 +456,18 @@ bool TerpstraSysExApplication::perform(const InvocationInfo& info) return saveSysExMappingAs(); case Lumatone::Menu::commandIDs::resetSysExMapping: return resetSysExMapping(); - case Lumatone::Menu::commandIDs::deleteOctaveBoard: + case Lumatone::Menu::commandIDs::deleteOctaveBoard: return deleteSubBoardData(); case Lumatone::Menu::commandIDs::copyOctaveBoard: return copySubBoardData(); case Lumatone::Menu::commandIDs::pasteOctaveBoard: return pasteSubBoardData(); + case Lumatone::Menu::commandIDs::pasteOctaveBoardNotes: + case Lumatone::Menu::commandIDs::pasteOctaveBoardChannels: + case Lumatone::Menu::commandIDs::pasteOctaveBoardColours: + case Lumatone::Menu::commandIDs::pasteOctaveBoardTypes: + return pasteModifiedSubBoardData(info.commandID); case Lumatone::Menu::commandIDs::undo: return undo(); @@ -420,39 +487,44 @@ bool TerpstraSysExApplication::perform(const InvocationInfo& info) bool TerpstraSysExApplication::openSysExMapping() { - FileChooser chooser("Open a Lumatone key mapping", recentFiles.getFile(0).getParentDirectory(), "*.ltn;*.tsx"); - if (chooser.browseForFileToOpen()) - { - currentFile = chooser.getResult(); - return openFromCurrentFile(); - } + chooser = std::make_unique("Open a Lumatone key mapping", recentFiles.getFile(0).getParentDirectory(), "*.ltn;*.tsx"); + chooser->launchAsync(FileBrowserComponent::FileChooserFlags::canSelectFiles | FileBrowserComponent::FileChooserFlags::openMode, + [&](const FileChooser& chooser) + { + currentFile = chooser.getResult(); + openFromCurrentFile(); + }); + return true; } -bool TerpstraSysExApplication::saveSysExMapping() +bool TerpstraSysExApplication::saveSysExMapping(std::function saveFileCallback) { if (currentFile.getFileName().isEmpty()) - return saveSysExMappingAs(); + return saveSysExMappingAs(saveFileCallback); else - return saveCurrentFile(); + return saveCurrentFile(saveFileCallback); } -bool TerpstraSysExApplication::saveSysExMappingAs() +bool TerpstraSysExApplication::saveSysExMappingAs(std::function saveFileCallback) { - FileChooser chooser("Lumatone Key Mapping Files", recentFiles.getFile(0).getParentDirectory(), "*.ltn"); - if (chooser.browseForFileToSave(true)) - { - currentFile = chooser.getResult(); - if (saveCurrentFile() ) + chooser = std::make_unique("Lumatone Key Mapping Files", recentFiles.getFile(0).getParentDirectory(), "*.ltn"); + chooser->launchAsync(FileBrowserComponent::FileChooserFlags::saveMode | FileBrowserComponent::FileChooserFlags::warnAboutOverwriting, + [this, saveFileCallback](const FileChooser& chooser) { - // Window title - updateMainTitle(); - return true; - } - } + currentFile = chooser.getResult(); + bool saved = saveCurrentFile(); + if (saved) + { + // Window title + updateMainTitle(); + } - return false; + saveFileCallback(saved); + }); + + return true; } bool TerpstraSysExApplication::resetSysExMapping() @@ -478,8 +550,7 @@ bool TerpstraSysExApplication::resetSysExMapping() bool TerpstraSysExApplication::deleteSubBoardData() { - return ((MainContentComponent*)(mainWindow->getContentComponent()))->deleteCurrentSubBoardData(); - // ToDo Add to undo history + return performUndoableAction(((MainContentComponent*)(mainWindow->getContentComponent()))->createDeleteCurrentSectionAction()); } bool TerpstraSysExApplication::copySubBoardData() @@ -489,19 +560,45 @@ bool TerpstraSysExApplication::copySubBoardData() bool TerpstraSysExApplication::pasteSubBoardData() { - return ((MainContentComponent*)(mainWindow->getContentComponent()))->pasteCurrentSubBoardData(); - // ToDo Add to undo history + return performUndoableAction(((MainContentComponent*)(mainWindow->getContentComponent()))->createPasteCurrentSectionAction()); +} + +bool TerpstraSysExApplication::pasteModifiedSubBoardData(CommandID commandID) +{ + switch (commandID) + { + case Lumatone::Menu::pasteOctaveBoardNotes: + case Lumatone::Menu::pasteOctaveBoardColours: + case Lumatone::Menu::pasteOctaveBoardChannels: + case Lumatone::Menu::pasteOctaveBoardTypes: + return performUndoableAction(((MainContentComponent*)(mainWindow->getContentComponent()))->createModifiedPasteCurrentSectionAction(commandID)); + default: + jassertfalse; + return false; + } +} + +bool TerpstraSysExApplication::canPasteSubBoardData() const +{ + if (mainWindow != nullptr) + return getMainContentComponent()->canPasteCopiedSubBoard(); + return false; } bool TerpstraSysExApplication::performUndoableAction(UndoableAction* editAction) { - if (undoManager.perform(editAction)) // UndoManager will check for nullptr and also for disposing of the object + if (editAction != nullptr) { - setHasChangesToSave(true); - ((MainContentComponent*)(mainWindow->getContentComponent()))->refreshKeyDataFields(); + undoManager.beginNewTransaction(); + if (undoManager.perform(editAction)) // UndoManager will check for nullptr and also for disposing of the object + { + setHasChangesToSave(true); + ((MainContentComponent*)(mainWindow->getContentComponent()))->refreshAllFields(); + return true; + } } - else - return false; + + return false; } bool TerpstraSysExApplication::undo() @@ -509,7 +606,8 @@ bool TerpstraSysExApplication::undo() if (undoManager.undo()) { setHasChangesToSave(true); - ((MainContentComponent*)(mainWindow->getContentComponent()))->refreshKeyDataFields(); + ((MainContentComponent*)(mainWindow->getContentComponent()))->refreshAllFields(); + return true; } else return false; @@ -520,7 +618,8 @@ bool TerpstraSysExApplication::redo() if (undoManager.redo()) { setHasChangesToSave(true); - ((MainContentComponent*)(mainWindow->getContentComponent()))->refreshKeyDataFields(); + ((MainContentComponent*)(mainWindow->getContentComponent()))->refreshAllFields(); + return true; } else return false; @@ -533,6 +632,11 @@ bool TerpstraSysExApplication::toggleDeveloperMode() return ((MainContentComponent*)(mainWindow->getContentComponent()))->setDeveloperMode(newMode); } +void TerpstraSysExApplication::setEditMode(sysExSendingMode editMode) +{ + lumatoneController->setSysExSendingMode(editMode); +} + bool TerpstraSysExApplication::generalOptionsDialog() { GeneralOptionsDlg* optionsWindow = new GeneralOptionsDlg(); @@ -667,15 +771,21 @@ bool TerpstraSysExApplication::openFromCurrentFile() else { // Show error message - AlertWindow::showMessageBox(AlertWindow::AlertIconType::WarningIcon, "Open File Error", "The file " + currentFile.getFullPathName() + " could not be opened."); + AlertWindow::showMessageBoxAsync(AlertWindow::AlertIconType::WarningIcon, "Open File Error", "The file " + currentFile.getFullPathName() + " could not be opened."); // XXX Update Window title in any case? Make file name empty/make data empty in case of error? return false; } } +bool TerpstraSysExApplication::setCurrentFile(File fileToOpen) +{ + currentFile = fileToOpen; + return openFromCurrentFile(); +} + // Saves the current mapping to file, specified in currentFile. -bool TerpstraSysExApplication::saveCurrentFile() +bool TerpstraSysExApplication::saveCurrentFile(std::function saveFileCallback) { if (currentFile.existsAsFile()) currentFile.deleteFile(); @@ -685,11 +795,13 @@ bool TerpstraSysExApplication::saveCurrentFile() TerpstraKeyMapping keyMapping; ((MainContentComponent*)(mainWindow->getContentComponent()))->getData(keyMapping); + bool appendSuccess = true; StringArray stringArray = keyMapping.toStringArray(); for (int i = 0; i < stringArray.size(); i++) - currentFile.appendText(stringArray[i] + "\n"); - - setHasChangesToSave(false); + appendSuccess = appendSuccess && currentFile.appendText(stringArray[i] + "\n"); + + setHasChangesToSave(!appendSuccess); + saveFileCallback(appendSuccess); // ToDo undo history? @@ -702,18 +814,19 @@ bool TerpstraSysExApplication::saveCurrentFile() void TerpstraSysExApplication::sendCurrentConfigurationToDevice() { auto theConfig = ((MainContentComponent*)(mainWindow->getContentComponent()))->getMappingInEdit(); - + // MIDI channel, MIDI note, colour and key type config for all keys - getLumatoneController().sendCompleteMapping(theConfig); + getLumatoneController()->sendCompleteMapping(theConfig); // General options - getLumatoneController().setAftertouchEnabled(theConfig.afterTouchActive); - getLumatoneController().sendLightOnKeyStrokes(theConfig.lightOnKeyStrokes); - getLumatoneController().sendInvertFootController(theConfig.invertExpression); - getLumatoneController().sendExpressionPedalSensivity(theConfig.expressionControllerSensivity); + getLumatoneController()->setAftertouchEnabled(theConfig.afterTouchActive); + getLumatoneController()->sendLightOnKeyStrokes(theConfig.lightOnKeyStrokes); + getLumatoneController()->sendInvertFootController(theConfig.invertExpression); + getLumatoneController()->sendExpressionPedalSensivity(theConfig.expressionControllerSensivity); + getLumatoneController()->invertSustainPedal(theConfig.invertSustain); // Velocity curve config - getLumatoneController().setVelocityIntervalConfig(theConfig.velocityIntervalTableValues); + getLumatoneController()->setVelocityIntervalConfig(theConfig.velocityIntervalTableValues); ((MainContentComponent*)(mainWindow->getContentComponent()))->getCurvesArea()->sendConfigToController(); } @@ -723,46 +836,65 @@ void TerpstraSysExApplication::requestConfigurationFromDevice() // if editing operations were done that have not been saved, give the possibility to save them if (hasChangesToSave) { - int retc = AlertWindow::showYesNoCancelBox( + AlertWindow::showYesNoCancelBox( AlertWindow::AlertIconType::QuestionIcon, "Request configuration from device", - "The controller's current configuration will be received now. This will overwrite all edits you have done. Do you want to save them first?"); - if (retc == 0) - { - // "Cancel". Do not receive config - return; - } - else if (retc == 1) - { - // "Yes". Try to save. Cancel if unsuccessful - if (!saveSysExMapping()) - return; - } - // retc == 2: "No" -> no saving, overwrite + "Lumatone's layout will now be imported. This will overwrite your unsaved changes. Do you want to save them first?", + "Save to file", "Import anyway", "Cancel import", nullptr, + ModalCallbackFunction::create([&](int retc) + { + if (retc == 0) + { + // "Cancel". Do not receive config, go offline + DBG("Layout import cancelled"); + setEditMode(sysExSendingMode::offlineEditor); + return; + } + else if (retc == 1) + { + // "Yes". Try to save. Cancel if unsuccessful + saveSysExMapping([this](bool success) + { + if (success) + this->requestConfigurationFromDevice(); + else + DBG("Cancelled layout import"); + }); + } + else + { + // retc == 2: "No" -> no saving, overwrite + DBG("Overwriting current edits"); + setHasChangesToSave(false); + requestConfigurationFromDevice(); + } + }) + ); + + return; } TerpstraSysExApplication::getApp().resetSysExMapping(); // Request MIDI channel, MIDI note, colour and key type config for all keys - getLumatoneController().sendGetCompleteMappingRequest(); + getLumatoneController()->sendGetCompleteMappingRequest(); // General options - // ToDo AfterTouchActive - // ToDo LightOnKeyStrokes - // ToDo invertFootController - // ToDO expressionControllerSensivity + getLumatoneController()->getPresetFlags(); + getLumatoneController()->getExpressionPedalSensitivity(); // Velocity curve config - getLumatoneController().sendVelocityIntervalConfigRequest(); - getLumatoneController().sendVelocityConfigRequest(); - getLumatoneController().sendFaderConfigRequest(); - getLumatoneController().sendAftertouchConfigRequest(); + getLumatoneController()->sendVelocityIntervalConfigRequest(); + getLumatoneController()->sendVelocityConfigRequest(); + getLumatoneController()->sendFaderConfigRequest(); + getLumatoneController()->sendAftertouchConfigRequest(); + } void TerpstraSysExApplication::updateMainTitle() { String windowTitle("Lumatone Editor"); - if (!currentFile.getFileName().isEmpty() ) + if (!currentFile.getFileName().isEmpty()) windowTitle << " - " << currentFile.getFileName(); if (hasChangesToSave) windowTitle << "*"; @@ -778,37 +910,54 @@ void TerpstraSysExApplication::setHasChangesToSave(bool value) } } +//https://forum.juce.com/t/closing-dialog-windows-on-shutdown/27326/6 +void TerpstraSysExApplication::setOpenDialogWindow(DialogWindow* dialogWindowIn) +{ + dialogWindow.reset(dialogWindowIn); + + // attach callback to window to release std::unique_ptr when the window is closed + ModalComponentManager::getInstance()->attachCallback(dialogWindow.get(), ModalCallbackFunction::create([&](int r) + { + dialogWindow.release(); + })); +} + bool TerpstraSysExApplication::aboutTerpstraSysEx() { String m; m << "Lumatone Editor" << newLine + << "Version " << getApplicationVersion() << newLine << newLine - << "Version " << String((JUCE_APP_VERSION_HEX >> 16) & 0xff) << "." - << String((JUCE_APP_VERSION_HEX >> 8) & 0xff) << "." - << String(JUCE_APP_VERSION_HEX & 0xff) << newLine - - << "@ Hans Straub, Vincenzo Sicurella 2014 - 2021" << newLine + << "@ Hans Straub 2014 - 2021," << newLine + << "& Vincenzo Sicurella 2020 - 2022" << newLine << newLine << "Based on the program 'TerpstraSysEx' @ Dylan Horvath 2007" << newLine << newLine - << "For help on using this program, or any questions relating to the Lumatone keyboard, go to" << newLine - << "http://lumatone.io"; - //<< "or"; - //<< newLine - //<< "http://terpstrakeyboard.com"; + << "For help on using this program, or any questions relating to the Lumatone keyboard, go to:" << newLine + << newLine + << "https://www.lumatone.io/" << newLine + << newLine + << "Built " << Time::getCompilationDate().toString(true, true, false, true) + << " with " << SystemStats::getOperatingSystemName() << newLine + << "on " << SystemStats::getCpuModel() << newLine + << SystemStats::getJUCEVersion(); DialogWindow::LaunchOptions options; - Label* label = new Label(); - label->setLookAndFeel(&lookAndFeel); - label->setText(m, dontSendNotification); - label->setFont(lookAndFeel.getAppFont(LumatoneEditorFont::FranklinGothic)); - options.content.setOwned(label); + auto textDisplay = new TextEditor(); + //textDisplay->setLookAndFeel(&lookAndFeel); + lookAndFeel.setupTextEditor(*textDisplay); + textDisplay->setMultiLine(true, true); + textDisplay->setText(m, dontSendNotification); + textDisplay->setFont(lookAndFeel.getAppFont(LumatoneEditorFont::FranklinGothic)); + textDisplay->setReadOnly(true); + textDisplay->setCaretVisible(false); + options.content.setOwned(textDisplay); juce::Rectangle area(0, 0, 400, 200); options.content->setSize(area.getWidth(), area.getHeight()); - resizeLabelWithHeight(label, roundToInt(area.getHeight() * 0.24f)); + //resizeLabelWithHeight(label, roundToInt(area.getHeight() * 0.24f)); options.dialogTitle = "About Lumatone Editor"; options.dialogBackgroundColour = lookAndFeel.findColour(LumatoneEditorColourIDs::DarkBackground); @@ -818,13 +967,15 @@ bool TerpstraSysExApplication::aboutTerpstraSysEx() options.resizable = false; - DialogWindow* dw = options.launchAsync(); - dw->setLookAndFeel(&lookAndFeel); + auto dw = options.launchAsync(); + //dw->setLookAndFeel(&lookAndFeel); dw->centreWithSize(400, 260); + setOpenDialogWindow(dw); + return true; } //============================================================================== // This macro generates the main() routine that launches the app. -START_JUCE_APPLICATION (TerpstraSysExApplication) +START_JUCE_APPLICATION(TerpstraSysExApplication) diff --git a/Source/Main.h b/Source/Main.h index 14025d05..2312d758 100644 --- a/Source/Main.h +++ b/Source/Main.h @@ -10,9 +10,9 @@ #pragma once -#include "../JuceLibraryCode/JuceHeader.h" +#include #include "LumatoneMenu.h" -#include "MainComponent.h" +#include "MainWindow.h" #include "LumatoneController.h" #include "ViewConstants.h" @@ -21,6 +21,8 @@ #include "LocalisationMap.h" #include "FirmwareTransfer.h" +#define CHOOSE_FILE_NOOP [](bool) -> void {} + //============================================================================== class TerpstraSysExApplication : public JUCEApplication { @@ -28,14 +30,14 @@ class TerpstraSysExApplication : public JUCEApplication //============================================================================== TerpstraSysExApplication(); - const String getApplicationName() { return ProjectInfo::projectName; } - const String getApplicationVersion() { return ProjectInfo::versionString; } - bool moreThanOneInstanceAllowed() { return true; } + const String getApplicationName() override { return ProjectInfo::projectName; } + const String getApplicationVersion() override { return ProjectInfo::versionString; } + bool moreThanOneInstanceAllowed() override { return true; } - void initialise(const String& commandLine); - void shutdown(); - void systemRequestedQuit(); - void anotherInstanceStarted(const String& commandLine); + void initialise(const String& commandLine) override; + void shutdown() override; + void systemRequestedQuit() override; + void anotherInstanceStarted(const String& commandLine) override; static TerpstraSysExApplication& getApp() { @@ -48,16 +50,19 @@ class TerpstraSysExApplication : public JUCEApplication LumatoneEditorLookAndFeel& getLookAndFeel() { return lookAndFeel; } ComponentBoundsConstrainer* getBoundsConstrainer() { return boundsConstrainer.get(); }; RecentlyOpenedFilesList& getRecentFileList() { return recentFiles; } - LumatoneController& getLumatoneController() { return lumatoneController; } + LumatoneController* getLumatoneController() { return lumatoneController.get(); } Array& getColourPalettes() { return colourPalettes; } Font getAppFont(LumatoneEditorFont fontIdIn, float height = 12.0f) { return appFonts.getFont(fontIdIn, height); } - int getOctaveBoardSize() const { return lumatoneController.getOctaveSize(); } + int getOctaveBoardSize() const { return lumatoneController->getOctaveSize(); } + int getNumBoards() const { return lumatoneController->getNumBoards(); } + + FirmwareVersion getFirmwareVersion() const { return lumatoneController->getFirmwareVersion(); } + String getFirmwareVersionStr() const { return lumatoneController->getFirmwareVersion().toDisplayString(); } - FirmwareVersion getFirmwareVersion() const { return lumatoneController.getFirmwareVersion(); } - String getFirmwareVersionStr() const { return lumatoneController.getFirmwareVersion().toString(); } + void setFirmwareUpdatePerformed(bool updateWasRun) { firmwareUpdateWasPerformed = true; } void reloadColourPalettes(); - bool saveColourPalette(LumatoneEditorColourPalette& palette, File pathToPalette); + bool saveColourPalette(LumatoneEditorColourPalette& palette, File pathToPalette=File()); bool deletePaletteFile(File pathToPalette); // Menu functionality @@ -67,13 +72,17 @@ class TerpstraSysExApplication : public JUCEApplication bool perform(const InvocationInfo& info) override; bool openSysExMapping(); - bool saveSysExMapping(); - bool saveSysExMappingAs(); + bool saveSysExMapping(std::function saveFileCallback = CHOOSE_FILE_NOOP); + bool saveSysExMappingAs(std::function saveFileCallback = CHOOSE_FILE_NOOP); bool resetSysExMapping(); bool deleteSubBoardData(); bool copySubBoardData(); bool pasteSubBoardData(); + bool pasteModifiedSubBoardData(CommandID commandID); + bool canPasteSubBoardData() const; + + void setEditMode(sysExSendingMode editMode); void setCalibrationMode(bool calibrationStarted) { inCalibrationMode = calibrationStarted; } bool getInCalibrationMode() const { return inCalibrationMode; } @@ -91,86 +100,23 @@ class TerpstraSysExApplication : public JUCEApplication bool openRecentFile(int recentFileIndex); bool openFromCurrentFile(); - bool saveCurrentFile(); + bool setCurrentFile(File fileToOpen); + bool saveCurrentFile(std::function saveFileCallback = CHOOSE_FILE_NOOP); void sendCurrentConfigurationToDevice(); void requestConfigurationFromDevice(); - void updateMainTitle(); bool getHasChangesToSave() const { return hasChangesToSave; } void setHasChangesToSave(bool value); + void setOpenDialogWindow(DialogWindow* dialogWindowIn); + bool aboutTerpstraSysEx(); - //============================================================================== - /* - This class implements the desktop window that contains an instance of - our MainContentComponent class. - */ - class MainWindow : public DocumentWindow - { - public: - MainWindow() : DocumentWindow("Lumatone Editor", - TerpstraSysExApplication::getApp().getLookAndFeel().findColour(LumatoneEditorColourIDs::MediumBackground), - DocumentWindow::minimiseButton + DocumentWindow::closeButton) - { - setContentOwned(new MainContentComponent(), true); - - setResizable(true, true); - setConstrainer(TerpstraSysExApplication::getApp().getBoundsConstrainer()); - centreWithSize(getWidth(), getHeight()); -#if JUCE_ANDROID - setFullScreen(true); -#endif - setLookAndFeel(&TerpstraSysExApplication::getApp().getLookAndFeel()); - setVisible(true); - } - - void closeButtonPressed() override - { - // This is called when the user tries to close this window. Here, we'll just - // ask the app to quit when this happens, but you can change this to do - // whatever you need. - JUCEApplication::getInstance()->systemRequestedQuit(); - } - - BorderSize getBorderThickness() override - { - return BorderSize (1); - } - - /* Note: Be careful if you override any DocumentWindow methods - the base - class uses a lot of them, so by overriding you might break its functionality. - It's best to do all your work in your content component instead, but if - you really have to override any DocumentWindow methods, make sure your - subclass also calls the superclass's method. - */ - - void saveStateToPropertiesFile(PropertiesFile* propertiesFile) - { - // Save state of main window - propertiesFile->setValue("MainWindowState", getWindowStateAsString()); - ((MainContentComponent*)(getContentComponent()))->saveStateToPropertiesFile(propertiesFile); - } - - void restoreStateFromPropertiesFile(PropertiesFile* propertiesFile) - { - if (!restoreWindowStateFromString(propertiesFile->getValue("MainWindowState"))) - { - // Default window state - setSize(DEFAULTMAINWINDOWWIDTH, DEFAULTMAINWINDOWHEIGHT); - } - - ((MainContentComponent*)(getContentComponent()))->restoreStateFromPropertiesFile(propertiesFile); - } - - private: - JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MainWindow) - }; - - MainContentComponent* getMainContentComponent(); + + MainContentComponent* getMainContentComponent() const; private: std::unique_ptr mainWindow; @@ -199,7 +145,13 @@ class TerpstraSysExApplication : public JUCEApplication Array colourPalettes; + // Make sure an open dialog window is deleted on shutdown + std::unique_ptr dialogWindow; + // Communication with Lumatone - LumatoneController lumatoneController; -}; + std::unique_ptr lumatoneController; + std::unique_ptr chooser; + + bool firmwareUpdateWasPerformed = false; // Allows us to deinitialize libssh2 a single time +}; diff --git a/Source/MainComponent.cpp b/Source/MainComponent.cpp index a075d759..99c3c827 100644 --- a/Source/MainComponent.cpp +++ b/Source/MainComponent.cpp @@ -11,6 +11,7 @@ #include "MainComponent.h" #include "ViewConstants.h" #include "Main.h" +#include "EditActions.h" //============================================================================== @@ -46,7 +47,7 @@ MainContentComponent::MainContentComponent() addAndMakeVisible(globalSettingsArea.get()); globalSettingsArea->listenToColourEditButtons(this); - TerpstraSysExApplication::getApp().getLumatoneController().addFirmwareListener(this); + TerpstraSysExApplication::getApp().getLumatoneController()->addFirmwareListener(this); //lblAppName.reset(new Label("lblAppName", TerpstraSysExApplication::getApp().getApplicationName())); lblAppName.reset(new Label("lblAppName", "lumatone editor")); @@ -109,11 +110,7 @@ void MainContentComponent::setData(TerpstraKeyMapping& newData, bool withRefresh if (withRefresh) { - refreshKeyDataFields(); - generalOptionsArea->loadFromMapping(); - pedalSensitivityDlg->loadFromMapping(); - curvesArea->loadFromMapping(); - curvesArea->repaint(); + refreshAllFields(); } } @@ -129,24 +126,16 @@ void MainContentComponent::getData(TerpstraKeyMapping& newData) newData = mappingData; } -bool MainContentComponent::deleteCurrentSubBoardData() +UndoableAction* MainContentComponent::createDeleteCurrentSectionAction() { auto currentSetSelection = noteEditArea->getOctaveBoardSelectorTab()->getCurrentTabIndex(); if (currentSetSelection >= 0 && currentSetSelection < TerpstraSysExApplication::getApp().getOctaveBoardSize()) - { - // Delete subboard data - mappingData.sets[currentSetSelection] = TerpstraKeys(); - - // Refresh display - refreshKeyDataFields(); - - // Mark that there are changes - TerpstraSysExApplication::getApp().setHasChangesToSave(true); - - return true; + { + auto keySet = TerpstraKeys(); + return new Lumatone::SectionEditAction(currentSetSelection, keySet); } else - return false; + return nullptr; } bool MainContentComponent::copyCurrentSubBoardData() @@ -161,25 +150,66 @@ bool MainContentComponent::copyCurrentSubBoardData() return false; } -bool MainContentComponent::pasteCurrentSubBoardData() +UndoableAction* MainContentComponent::createPasteCurrentSectionAction() { auto currentSetSelection = noteEditArea->getOctaveBoardSelectorTab()->getCurrentTabIndex(); - if (currentSetSelection >= 0 && currentSetSelection < TerpstraSysExApplication::getApp().getOctaveBoardSize()) - { - if (!copiedSubBoardData.isEmpty()) - { - mappingData.sets[currentSetSelection] = copiedSubBoardData; - - // Refresh display - refreshKeyDataFields(); - - // Mark that there are changes - TerpstraSysExApplication::getApp().setHasChangesToSave(true); - } - return true; + if (currentSetSelection >= 0 && currentSetSelection < TerpstraSysExApplication::getApp().getNumBoards() + && !copiedSubBoardData.isEmpty()) + { + return new Lumatone::SectionEditAction(currentSetSelection, copiedSubBoardData); } else - return false; + return nullptr; +} + +UndoableAction* MainContentComponent::createModifiedPasteCurrentSectionAction(CommandID commandID) +{ + auto currentSetSelectionIndex = noteEditArea->getOctaveBoardSelectorTab()->getCurrentTabIndex(); + if (currentSetSelectionIndex >= 0 && currentSetSelectionIndex < TerpstraSysExApplication::getApp().getNumBoards() + && !copiedSubBoardData.isEmpty()) + { + auto modifiedSubBoardData = copiedSubBoardData; + auto octaveSize = TerpstraSysExApplication::getApp().getOctaveBoardSize(); + + for (int i = 0; i < octaveSize; i++) + { + auto currentSectionKey = mappingData.sets[currentSetSelectionIndex].theKeys[i]; + auto modifiedKey = modifiedSubBoardData.theKeys[i]; + + switch (commandID) + { + case Lumatone::Menu::commandIDs::pasteOctaveBoardNotes: + modifiedKey = currentSectionKey.withNoteOrCC(modifiedKey.noteNumber); + break; + + case Lumatone::Menu::commandIDs::pasteOctaveBoardChannels: + modifiedKey = currentSectionKey.withChannelNumber(modifiedKey.channelNumber); + break; + + case Lumatone::Menu::commandIDs::pasteOctaveBoardColours: + modifiedKey = currentSectionKey.withColour(modifiedKey.colour); + break; + + case Lumatone::Menu::commandIDs::pasteOctaveBoardTypes: + modifiedKey = currentSectionKey.withKeyType(modifiedKey.keyType).withInvertCCFader(modifiedKey.ccFaderDefault); + break; + + default: + jassertfalse; + } + + modifiedSubBoardData.theKeys[i] = modifiedKey; + } + + return new Lumatone::SectionEditAction(currentSetSelectionIndex, modifiedSubBoardData); + } + else + return nullptr; +} + +bool MainContentComponent::canPasteCopiedSubBoard() const +{ + return !copiedSubBoardData.isEmpty(); } bool MainContentComponent::setDeveloperMode(bool developerModeOn) @@ -277,6 +307,14 @@ void MainContentComponent::faderConfigReceived(const int* faderData) curvesArea->loadFromMapping(); } +void MainContentComponent::faderTypeConfigReceived(int octaveIndex, const int* faderTypeData) +{ + for (int keyIndex = 0; keyIndex < TerpstraSysExApplication::getApp().getOctaveBoardSize(); keyIndex++) + { + this->mappingData.sets[octaveIndex - 1].theKeys[keyIndex].ccFaderDefault = faderTypeData[keyIndex]; + } +} + void MainContentComponent::lumatouchConfigReceived(const int* lumatouchData) { this->mappingData.lumaTouchConfig.editStrategy = TerpstraVelocityCurveConfig::EDITSTRATEGYINDEX::freeDrawing; @@ -315,11 +353,19 @@ void MainContentComponent::buttonClicked(Button* btn) { colourEdit = noteEditArea->getColourEditComponent(); paletteWindow->listenToColourSelection(noteEditArea->getSingleNoteColourTextEditor()); + + // Shouldn't be necessary when Isomorphic is moved from dev to public + auto isomorphicPanel = noteEditArea->getIsomorphicMassAssignPanel(); + if (isomorphicPanel != nullptr) + paletteWindow->listenToColourSelection(isomorphicPanel); + + paletteWindow->addColourSelectorToGroup(noteEditArea.get()); + paletteWindow->setCurrentColourSelector(noteEditArea->getSingleNoteColourTextEditor()); } Rectangle componentArea = colourEdit->getScreenBounds().translated(-getScreenX(), -getScreenY()); - CallOutBox& popupBox = CallOutBox::launchAsynchronously( + CallOutBox::launchAsynchronously( std::unique_ptr(paletteWindow), componentArea, this @@ -327,7 +373,6 @@ void MainContentComponent::buttonClicked(Button* btn) // else, a preset button colour button was pressed paletteWindow->listenToColourSelection(colourEdit); - popupBox.setLookAndFeel(&getLookAndFeel()); // TODO: Set swatch # or custom colour as current colour } } @@ -358,10 +403,6 @@ void MainContentComponent::resized() controlsArea = getBounds().withTop(proportionOfHeight(controlsAreaY)).withBottom(footerY); // All keys overview/virtual keyboard playing - // New height of subset field area, with minimal value - int noteEditAreaWidth = noteEditArea->getWidth(); - int noteEditAreaHeight = noteEditArea->getHeight(); - int newKeysOverviewAreaHeight = jmax(controlsArea.getY() - midiAreaHeight, MINIMALTERPSTRAKEYSETAREAHEIGHT); allKeysOverview->setBounds(0, midiAreaHeight, newWidth, newKeysOverviewAreaHeight); @@ -392,3 +433,12 @@ void MainContentComponent::refreshKeyDataFields() noteEditArea->refreshKeyFields(); } +void MainContentComponent::refreshAllFields() +{ + refreshKeyDataFields(); + generalOptionsArea->loadFromMapping(); + pedalSensitivityDlg->loadFromMapping(); + curvesArea->loadFromMapping(); + curvesArea->repaint(); +} + diff --git a/Source/MainComponent.h b/Source/MainComponent.h index 528d22b5..61368d46 100644 --- a/Source/MainComponent.h +++ b/Source/MainComponent.h @@ -33,7 +33,7 @@ your controls and content. */ class MainContentComponent : public Component, - public LumatoneController::FirmwareListener, + public LumatoneEditor::FirmwareListener, public ChangeListener, public Button::Listener { @@ -56,9 +56,11 @@ class MainContentComponent : public Component, CurvesArea* getCurvesArea() { return curvesArea.get(); } // Board edit operations - bool deleteCurrentSubBoardData(); + UndoableAction* createDeleteCurrentSectionAction(); bool copyCurrentSubBoardData(); - bool pasteCurrentSubBoardData(); + UndoableAction* createPasteCurrentSectionAction(); + UndoableAction* createModifiedPasteCurrentSectionAction(CommandID commandID); + bool canPasteCopiedSubBoard() const; bool setDeveloperMode(bool developerModeOn); @@ -69,12 +71,13 @@ class MainContentComponent : public Component, void buttonClicked(Button* btn) override; // GUI implementation - void paint (Graphics&); - void resized(); + void paint (Graphics&) override; + void resized() override; void refreshKeyDataFields(); + void refreshAllFields(); - // Implementation of LumatoneController::FirmwareListener + // Implementation of LumatoneEditor::FirmwareListener void octaveColourConfigReceived(int octaveIndex, uint8 rgbFlag, const int* colourData) override; @@ -92,6 +95,8 @@ class MainContentComponent : public Component, void faderConfigReceived(const int* faderData) override; + void faderTypeConfigReceived(int octaveIndex, const int* faderTypeData) override; + void lumatouchConfigReceived(const int* lumatouchData) override; void firmwareRevisionReceived(FirmwareVersion version) override; @@ -148,8 +153,8 @@ class MainContentComponent : public Component, const float footerAreaY = 0.96f; - const float popupWidth = 0.2879f; - const float popupHeight = 0.2615f; + const float popupWidth = 0.4f; + const float popupHeight = 0.333f; const float lumatoneVersionMarginX = 0.02f; const float lumatoneVersionWidth = 0.2f; @@ -166,4 +171,4 @@ class MainContentComponent : public Component, const Rectangle pedalSettingsBounds = { 0.777778f, settingsAreaY, 0.18f, settingsAreaHeight }; const Rectangle curvesAreaBounds = { settingsColumnX, 0.7174f, 0.3626f, 0.21f }; -}; \ No newline at end of file +}; diff --git a/Source/MainWindow.cpp b/Source/MainWindow.cpp new file mode 100644 index 00000000..ca1ecfb9 --- /dev/null +++ b/Source/MainWindow.cpp @@ -0,0 +1,147 @@ +/* + ============================================================================== + + MainWindow.cpp + Created: 2 Apr 2022 3:24:02pm + + ============================================================================== +*/ + +#include "MainWindow.h" +#include "Main.h" + +MainWindow::MainWindow(ComponentBoundsConstrainer* constrainerIn) : DocumentWindow("Lumatone Editor", + TerpstraSysExApplication::getApp().getLookAndFeel().findColour(LumatoneEditorColourIDs::MediumBackground), + DocumentWindow::minimiseButton + DocumentWindow::closeButton), + constrainer(constrainerIn) +{ + setContentOwned(new MainContentComponent(), true); + + setResizable(true, true); +#if JUCE_ANDROID + setFullScreen(true); +#else + // Window aspect ratio + constrainer->setFixedAspectRatio(DEFAULTMAINWINDOWASPECT); + constrainer->setMinimumSize(800, round(800 / DEFAULTMAINWINDOWASPECT)); + + setConstrainer(constrainer); + updateBounds(); + startTimer(2000); +#endif + + setLookAndFeel(&TerpstraSysExApplication::getApp().getLookAndFeel()); +} + +MainWindow::~MainWindow() +{ + stopTimer(); + setConstrainer(nullptr); +} + +void MainWindow::closeButtonPressed() +{ + // This is called when the user tries to close this window. Here, we'll just + // ask the app to quit when this happens, but you can change this to do + // whatever you need. + JUCEApplication::getInstance()->systemRequestedQuit(); +} + +BorderSize MainWindow::getBorderThickness() +{ + return BorderSize (1); +} + +bool MainWindow::isLargerThanCurrentScreen() const +{ + if (maxWindowHeight > 0) + return getHeight() > maxWindowHeight; + + return false; // shouldn't happen +} + +bool MainWindow::isOutOfVerticalBounds() const +{ + return getScreenY() < 0 + || getScreenY() >= (maxWindowHeight - verticalBoundsThreshold); +} + +bool MainWindow::isOutOfHorizontalBounds() const +{ + return getScreenBounds().getRight() < horizontalBoundsThreshold + || getScreenX() >= (maxWindowWidth - horizontalBoundsThreshold); +} + +void MainWindow::saveStateToPropertiesFile(PropertiesFile* propertiesFile) +{ + // Save state of main window + propertiesFile->setValue("MainWindowState", getWindowStateAsString()); + ((MainContentComponent*)(getContentComponent()))->saveStateToPropertiesFile(propertiesFile); +} + +void MainWindow::restoreStateFromPropertiesFile(PropertiesFile* propertiesFile) +{ + bool useSavedState = restoreWindowStateFromString(propertiesFile->getValue("MainWindowState")); + + fixWindowPositionAndSize(!useSavedState); + + setVisible(true); + + ((MainContentComponent*)(getContentComponent()))->restoreStateFromPropertiesFile(propertiesFile); +} + +void MainWindow::updateBounds() +{ + auto thisDisplay = Desktop::getInstance().getDisplays().getDisplayForRect(getScreenBounds()); + + if (thisDisplay != nullptr) + { + int screenWidth = thisDisplay->userArea.getWidth(); + int screenHeight = thisDisplay->userArea.getHeight(); + if (screenWidth != maxWindowWidth || screenHeight != maxWindowHeight) + { + maxWindowWidth = screenWidth; + maxWindowHeight = screenHeight; + constrainer->setMaximumHeight(maxWindowHeight); + + fixWindowPositionAndSize(); + return; + } + } + + if (isOutOfVerticalBounds()) + fixWindowPositionAndSize(); +} + +void MainWindow::fixWindowPositionAndSize(bool setToDefault) +{ + if (setToDefault || isLargerThanCurrentScreen()) + { + // Default window state + setSize(DEFAULTMAINWINDOWWIDTH, DEFAULTMAINWINDOWHEIGHT); + return; + } + + if (isOutOfVerticalBounds()) + { + int correctedY = (getScreenY() < 0) ? 0 + : maxWindowHeight - verticalBoundsThreshold; + setTopLeftPosition(getScreenX(), correctedY); + } + + if (isOutOfHorizontalBounds()) + { + auto bounds = getScreenBounds(); + int correctedX = (bounds.getX() < 0) ? horizontalBoundsThreshold - maxWindowWidth + : maxWindowWidth - horizontalBoundsThreshold; + setTopLeftPosition(correctedX, getScreenY()); + } +} + +void MainWindow::timerCallback() +{ + // Set threshold to be a quarter of the window handle height + verticalBoundsThreshold = round(getTitleBarHeight() * 0.25f); + + updateBounds(); +} diff --git a/Source/MainWindow.h b/Source/MainWindow.h new file mode 100644 index 00000000..35fe7e52 --- /dev/null +++ b/Source/MainWindow.h @@ -0,0 +1,67 @@ +/* + ============================================================================== + + MainWindow.h + Created: 2 Apr 2022 3:24:02pm + + ============================================================================== +*/ + +#pragma once +#include "MainComponent.h" + +//============================================================================== +/* +This class implements the desktop window that contains an instance of +our MainContentComponent class. +*/ +class MainWindow : public DocumentWindow, public Timer +{ +public: + MainWindow(ComponentBoundsConstrainer* constrainerIn); + + virtual ~MainWindow(); + + void closeButtonPressed() override; + + BorderSize getBorderThickness() override; + + /* Note: Be careful if you override any DocumentWindow methods - the base + class uses a lot of them, so by overriding you might break its functionality. + It's best to do all your work in your content component instead, but if + you really have to override any DocumentWindow methods, make sure your + subclass also calls the superclass's method. + */ + + // Returns true if the height is larger than current screen + bool isLargerThanCurrentScreen() const; + + // Returns true if the top of the window is out of the screen + bool isOutOfVerticalBounds() const; + + // Returns true if left side of window is too far right, or if right side of window is too far left + bool isOutOfHorizontalBounds() const; + + void saveStateToPropertiesFile(PropertiesFile* propertiesFile); + + void restoreStateFromPropertiesFile(PropertiesFile* propertiesFile); + + // Checks size of current display and updates constraint + void updateBounds(); + + void fixWindowPositionAndSize(bool setToDefault=false); + + void timerCallback() override; + +private: + + ComponentBoundsConstrainer* constrainer; + + int maxWindowWidth = 0; + int maxWindowHeight = 0; + + int horizontalBoundsThreshold = 10; + int verticalBoundsThreshold = 10; + + JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR(MainWindow) +}; diff --git a/Source/MappingLogic.cpp b/Source/MappingLogic.cpp index d2f0125d..818ffc76 100644 --- a/Source/MappingLogic.cpp +++ b/Source/MappingLogic.cpp @@ -61,6 +61,7 @@ juce::Colour MappingLogicBase::indexToColour(int inx) const void MappingLogicBase::indexToTerpstraKey(int inx, TerpstraKey& keyData) const { + keyData.keyType = LumatoneKeyType::noteOnNoteOff; keyData.channelNumber = indexToMIDIChannel(inx); keyData.noteNumber = indexToMIDINote(inx); @@ -126,7 +127,7 @@ void IncrMidiNotesMappingLogic::setValues(int newPeriodSize, int newChannelInCas int IncrMidiNotesMappingLogic::globalMappingSize() const { if (isSingleChannel()) - return this->periodSize; // notes start at 0 and go until periodSize-1 + return 128; // notes start at 0 and go until periodSize-1 else return this->periodSize * 16; } diff --git a/Source/MappingLogic.h b/Source/MappingLogic.h index bad55821..739d4aaf 100644 --- a/Source/MappingLogic.h +++ b/Source/MappingLogic.h @@ -21,10 +21,13 @@ class MappingLogicBase { public: MappingLogicBase(ScaleStructure& scaleStructureIn, Array& colourTableIn); + virtual ~MappingLogicBase() {} void setPeriodSize(int newPeriodSize, bool forceRefresh = false); void setAssignColours(bool value) { assignColours = value; }; + void setColourTable(Array& colourTableIn) { colourTable = colourTableIn; } + // Global number of notes in the mapping virtual int globalMappingSize() const = 0; @@ -134,7 +137,8 @@ class KBMFilesMappingLogic: public MappingLogicBase public: KBMFilesMappingLogic(ScaleStructure& scaleStructureIn, Array& colourTableIn); - + virtual ~KBMFilesMappingLogic() {} + //=============================== // Set parameters diff --git a/Source/MidiEditArea.cpp b/Source/MidiEditArea.cpp index 599abbaa..e3286ba1 100644 --- a/Source/MidiEditArea.cpp +++ b/Source/MidiEditArea.cpp @@ -28,7 +28,7 @@ //[MiscUserDefs] You can add your own user definitions and misc code here... // Index in edit mode tab coincides with sysExSendingMode. In case that changes in the future, modify this here. -LumatoneController::sysExSendingMode editModeTabIndexToMidiSysExSendingMode(int tabIndex) { return static_cast(tabIndex); } +sysExSendingMode editModeTabIndexToMidiSysExSendingMode(int tabIndex) { return static_cast(tabIndex); } //[/MiscUserDefs] @@ -167,14 +167,15 @@ MidiEditArea::MidiEditArea (LumatoneEditorLookAndFeel& lookAndFeelIn) //[Constructor] You can add your own custom stuff here.. - TerpstraSysExApplication::getApp().getLumatoneController().addStatusListener(this); - auto inputs = TerpstraSysExApplication::getApp().getLumatoneController().getMidiInputList(); - auto outputs = TerpstraSysExApplication::getApp().getLumatoneController().getMidiOutputList(); + TerpstraSysExApplication::getApp().getLumatoneController()->addStatusListener(this); + TerpstraSysExApplication::getApp().getLumatoneController()->addEditorListener(this); + auto inputs = TerpstraSysExApplication::getApp().getLumatoneController()->getMidiInputList(); + auto outputs = TerpstraSysExApplication::getApp().getLumatoneController()->getMidiOutputList(); refreshInputMenuAndSetSelected(0, dontSendNotification); refreshOutputMenuAndSetSelected(0, dontSendNotification); setConnectivity(false); - btnAutoConnect->setToggleState(TerpstraSysExApplication::getApp().getLumatoneController().isDetectingLumatone(), sendNotificationSync); + btnAutoConnect->setToggleState(TerpstraSysExApplication::getApp().getLumatoneController()->isDetectingLumatone(), sendNotificationSync); //[/Constructor] } @@ -198,7 +199,7 @@ MidiEditArea::~MidiEditArea() //[Destructor]. You can add your own custom destruction code here.. //deviceMonitor.stopThread(100); - TerpstraSysExApplication::getApp().getLumatoneController().removeStatusListener(this); + TerpstraSysExApplication::getApp().getLumatoneController()->removeStatusListener(this); //[/Destructor] } @@ -213,7 +214,7 @@ void MidiEditArea::paint (juce::Graphics& g) //[UserPaint] Add your own custom painting code here.. g.fillAll(lookAndFeel.findColour(LumatoneEditorColourIDs::LightBackground)); - // Dark backrgound for title and logomark + // Dark background for title and logomark g.setColour(lookAndFeel.findColour(LumatoneEditorColourIDs::DarkBackground)); g.fillRect(lumatoneLabelBounds); g.fillRect(connectivityArea); @@ -222,10 +223,10 @@ void MidiEditArea::paint (juce::Graphics& g) if (!isConnected) { g.setColour(lookAndFeel.findColour(LumatoneEditorColourIDs::LightBackground)); - g.fillRoundedRectangle(ioBounds, round(getHeight() * controlBoundsCornerRadius)); + g.fillRoundedRectangle(ioBounds, roundToInt(getHeight() * controlBoundsCornerRadius)); } - g.setColour(connectedColours[isConnected]); + g.setColour(connectedColours[(int)(isConnected && liveEditorBtn->getToggleState())]); drawPathToFillBounds(g, logomarkPath, logomarkBounds); //[/UserPaint] } @@ -239,52 +240,54 @@ void MidiEditArea::resized() //[UserResized] Add your own custom resize handling here.. - lumatoneLabelBounds = getBounds().withRight(round(w * lumatoneLabelAreaWidth)); + lumatoneLabelBounds = getBounds().withRight(roundToInt(w * lumatoneLabelAreaWidth)); resizeLabelWithWidth(lumatoneLabel.get(), lumatoneLabelBounds.proportionOfWidth(lumatoneLabelWidthInArea)); lumatoneLabel->setCentrePosition(lumatoneLabelBounds.getCentre()); // Also used to position logomark ioBounds.setBounds( - round(w * controlBoundsX), round(h * controlBoundsY), - round(w * controlBoundsWidth), round(h * controlBoundsHeight) + roundToInt(w * controlBoundsX), roundToInt(h * controlBoundsY), + roundToInt(w * controlBoundsWidth), roundToInt(h * controlBoundsHeight) ); + int logomarkSize = roundToInt(h * logomarkHeight); + logomarkBounds.setSize(logomarkSize, logomarkSize); + logomarkBounds.setCentre(ioBounds.getRight() + roundToInt((getWidth() - ioBounds.getRight()) * 0.5f), roundToInt(h * 0.5f)); + if (isConnected) { - resizeLabelWithHeight(lblEditMode.get(), round(h* editModeHeight)); + int lblHeight = roundToInt(h * editModeHeight); + resizeLabelWithHeight(lblEditMode.get(), lblHeight); lblEditMode->setTopLeftPosition( - lumatoneLabelBounds.getRight() + round(w * editModeX), - round((h - lblEditMode->getHeight()) * 0.5f) + lumatoneLabelBounds.getRight() + roundToInt(w * editModeX), + roundToInt((h - lblEditMode->getHeight()) * 0.5f) ); - liveEditorBtn->setSize(round(w * liveEditButtonWidth), round(h* editModeButtonHeight)); + liveEditorBtn->setSize(roundToInt(w * liveEditButtonWidth), roundToInt(h* editModeButtonHeight)); liveEditorBtn->setTopLeftPosition( lblEditMode->getRight(), - round((h - liveEditorBtn->getHeight()) * 0.5f) + roundToInt((h - liveEditorBtn->getHeight()) * 0.5f) ); offlineEditorBtn->setBounds( - liveEditorBtn->getRight(), liveEditorBtn->getY(), round(w * offlineEditButtonWidth), liveEditorBtn->getHeight() + liveEditorBtn->getRight(), liveEditorBtn->getY(), roundToInt(w * offlineEditButtonWidth), liveEditorBtn->getHeight() ); + connectivityArea = getBounds().toFloat().withLeft(roundToInt(w * connectedAreaX)); - connectivityArea = getBounds().toFloat().withLeft(round(w * connectedAreaX)); - - lblConnectionState->setJustificationType (juce::Justification::centredLeft); - resizeLabelWithHeight(lblConnectionState.get(), round(h * connectivityHeight)); - lblConnectionState->setTopLeftPosition( - round(w * connectedX), - round((h - lblConnectionState->getHeight()) * 0.5f) - ); + int logoMargin = w - logomarkBounds.getRight(); + lblConnectionState->setTopLeftPosition(connectivityArea.getX(), roundToInt((h - lblHeight) * 0.5f)); + lblConnectionState->setSize(logomarkBounds.getX() - connectivityArea.getX() - logoMargin, lblHeight); + lblConnectionState->setJustificationType (juce::Justification::centredRight); } else { int controlHeight = roundToInt(ioBounds.getHeight() * midiDeviceControlBoundsHeight); - connectivityArea = getBounds().toFloat().withLeft(round(w * disconnectedAreaX)); + connectivityArea = getBounds().toFloat().withLeft(roundToInt(w * disconnectedAreaX)); - int lblMarginX = round(ioBounds.getWidth() * controlBoundsMarginScalar); - int lblMarginY = round((ioBounds.getHeight() - h * connectivityHeight) * 0.5f); + int lblMarginX = roundToInt(ioBounds.getWidth() * controlBoundsMarginScalar); + int lblMarginY = roundToInt((ioBounds.getHeight() - h * connectivityHeight) * 0.5f); ioAreaFlexBox.items.clear(); ioAreaFlexBox.items.add(FlexItem(*btnAutoConnect).withFlex(0).withWidth(controlHeight * 1.6f).withHeight(controlHeight)); @@ -315,18 +318,13 @@ void MidiEditArea::resized() ioBounds.reduced(lblMarginX, lblMarginY) ); - pleaseConnectLabel->setTopLeftPosition(round(w * pleaseConnectX), round(h * pleaseConnectY)); - resizeLabelWithHeight(pleaseConnectLabel.get(), round(h * pleaseConnectHeight)); + pleaseConnectLabel->setTopLeftPosition(roundToInt(w * pleaseConnectX), roundToInt(h * pleaseConnectY)); + resizeLabelWithHeight(pleaseConnectLabel.get(), roundToInt(h * pleaseConnectHeight)); - offlineMsgLabel->setTopLeftPosition(round(w * connectionDirectionsX), round(h * connectionDirectionsY)); - offlineMsgLabel->setSize(connectivityArea.getX() - pleaseConnectLabel->getX(), round(h * connectionDirectionsHeight)); + offlineMsgLabel->setTopLeftPosition(roundToInt(w * connectionDirectionsX), roundToInt(h * connectionDirectionsY)); + offlineMsgLabel->setSize(connectivityArea.getX() - pleaseConnectLabel->getX(), roundToInt(h * connectionDirectionsHeight)); offlineMsgLabel->setFont(offlineMsgLabel->getFont().withHeight(offlineMsgLabel->getHeight())); } - - - int logomarkSize = round(h * logomarkHeight); - logomarkBounds.setSize(logomarkSize, logomarkSize); - logomarkBounds.setCentre(ioBounds.getRight() + roundToInt((getWidth() - ioBounds.getRight()) * 0.5f), round(h* 0.5f)); //[/UserResized] } @@ -339,7 +337,7 @@ void MidiEditArea::comboBoxChanged (juce::ComboBox* comboBoxThatHasChanged) { //[UserComboBoxCode_cbMidiInput] -- add your combo box handling code here.. if (cbMidiInput->getSelectedItemIndex() >= 0) - TerpstraSysExApplication::getApp().getLumatoneController().setMidiInput(cbMidiInput->getSelectedItemIndex()); + TerpstraSysExApplication::getApp().getLumatoneController()->setMidiInput(cbMidiInput->getSelectedItemIndex()); if (cbMidiInput->getSelectedItemIndex() < 0 || cbMidiOutput->getSelectedItemIndex() < 0) { @@ -351,7 +349,8 @@ void MidiEditArea::comboBoxChanged (juce::ComboBox* comboBoxThatHasChanged) } else { - attemptDeviceConnection(); + jassert(!isConnected); + lblConnectionState->setText("Connecting...", NotificationType::dontSendNotification); } //[/UserComboBoxCode_cbMidiInput] } @@ -359,7 +358,7 @@ void MidiEditArea::comboBoxChanged (juce::ComboBox* comboBoxThatHasChanged) { //[UserComboBoxCode_cbMidiOutput] -- add your combo box handling code here.. if (cbMidiOutput->getSelectedItemIndex() >= 0) - TerpstraSysExApplication::getApp().getLumatoneController().setMidiOutput(cbMidiOutput->getSelectedItemIndex()); + TerpstraSysExApplication::getApp().getLumatoneController()->setMidiOutput(cbMidiOutput->getSelectedItemIndex()); if (cbMidiInput->getSelectedItemIndex() < 0 || cbMidiOutput->getSelectedItemIndex() < 0) { @@ -371,7 +370,8 @@ void MidiEditArea::comboBoxChanged (juce::ComboBox* comboBoxThatHasChanged) } else { - attemptDeviceConnection(); + jassert(!isConnected); + lblConnectionState->setText("Connecting...", NotificationType::dontSendNotification); } //[/UserComboBoxCode_cbMidiOutput] } @@ -394,7 +394,7 @@ void MidiEditArea::buttonClicked (juce::Button* buttonThatWasClicked) if (btnAutoConnect->getToggleState()) { - TerpstraSysExApplication::getApp().getLumatoneController().detectAndConnectToLumatone(); + TerpstraSysExApplication::getApp().getLumatoneController()->detectAndConnectToLumatone(); lblConnectionState->setText(translate("Searching for Lumatone..."), dontSendNotification); //errorVisualizer.setErrorLevel( // *lblConnectionState.get(), @@ -403,7 +403,7 @@ void MidiEditArea::buttonClicked (juce::Button* buttonThatWasClicked) } else { - TerpstraSysExApplication::getApp().getLumatoneController().stopAutoConnection(); + TerpstraSysExApplication::getApp().getLumatoneController()->stopAutoConnection(); lblConnectionState->setText(translate("Disconnected"), dontSendNotification); startTimer(deviceRefreshTimeoutMs); } @@ -417,26 +417,7 @@ void MidiEditArea::buttonClicked (juce::Button* buttonThatWasClicked) { auto sysExSendingMode = editModeTabIndexToMidiSysExSendingMode((int)!liveEditorBtn->getToggleState()); - TerpstraSysExApplication::getApp().getLumatoneController().setSysExSendingMode(sysExSendingMode); - - switch (sysExSendingMode) - { - case LumatoneController::sysExSendingMode::liveEditor: - onOpenConnectionToDevice(); - break; - - case LumatoneController::sysExSendingMode::offlineEditor: - lblConnectionState->setText(translate("Offline"), NotificationType::dontSendNotification); - //errorVisualizer.setErrorLevel( - // *lblConnectionState.get(), - // HajuErrorVisualizer::ErrorLevel::noError, - // "Offline"); - break; - - default: - jassertfalse; - break; - } + TerpstraSysExApplication::getApp().setEditMode(sysExSendingMode); } //[/UserbuttonClicked_Post] } @@ -452,7 +433,7 @@ void MidiEditArea::lookAndFeelChanged() connectedColours.add(getLookAndFeel().findColour(LumatoneEditorColourIDs::ConnectedGreen)); } -void MidiEditArea::setConnectivity(bool isConnectedIn) +void MidiEditArea::setConnectivity(bool isConnectedIn, String connectionStatus) { bool isNotConnected = !isConnectedIn; @@ -471,20 +452,32 @@ void MidiEditArea::setConnectivity(bool isConnectedIn) if (isConnected) { - stopTimer(); - if (liveEditorBtn->getToggleState()) - lblConnectionState->setText(translate("Connected"), dontSendNotification); + { + if (connectionStatus.isEmpty()) + connectionStatus = "Connected"; + lblConnectionState->setText(translate(connectionStatus), dontSendNotification); + } else - lblConnectionState->setText(translate("Offline"), dontSendNotification); + { + if (connectionStatus.isEmpty()) + connectionStatus = "Offline"; + lblConnectionState->setText(translate(connectionStatus), dontSendNotification); + } } else { if (btnAutoConnect->getToggleState()) - lblConnectionState->setText(translate("Searching for Lumatone..."), dontSendNotification); + { + if (connectionStatus.isEmpty()) + connectionStatus = "Searching for Lumatone..."; + lblConnectionState->setText(translate(connectionStatus), dontSendNotification); + } else { - lblConnectionState->setText(translate("Disconnected"), dontSendNotification); + if (connectionStatus.isEmpty()) + connectionStatus = "Disconnected"; + lblConnectionState->setText(translate(connectionStatus), dontSendNotification); startTimer(deviceRefreshTimeoutMs); } } @@ -495,17 +488,16 @@ void MidiEditArea::setConnectivity(bool isConnectedIn) repaint(); } -// Called when DeviceActivityMonitor detects a change in devices - -//void MidiEditArea::connectionChanged(bool hasConnection) -//{ -// if (hasConnection && btnAutoConnect->getToggleState() == false) -// { -// DBG("MIDIAREA SETTING DEVICES"); -// TerpstraSysExApplication::getApp().getLumatoneController().setMidiInput(cbMidiInput->getSelectedId() - 1); -// TerpstraSysExApplication::getApp().getLumatoneController().setMidiOutput(cbMidiOutput->getSelectedId() - 1); -// } -//} +void MidiEditArea::connectionFailed() +{ + setConnectivity(false, "No answer"); + //errorVisualizer.setErrorLevel( + // *lblConnectionState.get(), + // HajuErrorVisualizer::ErrorLevel::error, + // "No answer..."); + TerpstraSysExApplication::getApp().getLumatoneController()->setMidiInput(-1); + TerpstraSysExApplication::getApp().getLumatoneController()->setMidiOutput(-1); +} void MidiEditArea::connectionEstablished(int inputDevice, int outputDevice) { @@ -527,102 +519,105 @@ void MidiEditArea::connectionEstablished(int inputDevice, int outputDevice) void MidiEditArea::connectionLost() { + // Lost should only happen after connection is established + jassert(isConnected); + if (!btnAutoConnect->getToggleState()) startTimer(deviceRefreshTimeoutMs); - if (isWaitingForConnectionTest) - { - lblConnectionState->setText(translate("No answer"), NotificationType::dontSendNotification); - //errorVisualizer.setErrorLevel( - // *lblConnectionState.get(), - // HajuErrorVisualizer::ErrorLevel::error, - // "No answer"); - } else - { - setConnectivity(false); - } + { + refreshInputMenuAndSetSelected(0, NotificationType::dontSendNotification); + refreshOutputMenuAndSetSelected(0, NotificationType::sendNotificationAsync); + } + + setConnectivity(false); } -void MidiEditArea::attemptDeviceConnection() +void MidiEditArea::editorModeChanged(sysExSendingMode editMode) { - jassert(!isConnected); - - lblConnectionState->setText("Connecting...", NotificationType::dontSendNotification); - //errorVisualizer.setErrorLevel( - // *lblConnectionState.get(), - // HajuErrorVisualizer::ErrorLevel::noError, - // "Connecting..."); + switch (editMode) + { + case sysExSendingMode::liveEditor: + liveEditorBtn->setToggleState(true, NotificationType::dontSendNotification); + if (TerpstraSysExApplication::getApp().getHasChangesToSave()) + onOpenConnectionToDevice(translate("Switch to Live Mode with unsaved changes")); + break; + + case sysExSendingMode::offlineEditor: + offlineEditorBtn->setToggleState(true, NotificationType::dontSendNotification); + lblConnectionState->setText(translate("Offline"), NotificationType::dontSendNotification); + //errorVisualizer.setErrorLevel( + // *lblConnectionState.get(), + // HajuErrorVisualizer::ErrorLevel::noError, + // "Offline"); + break; + + default: + jassertfalse; + break; + } - if (TerpstraSysExApplication::getApp().getLumatoneController().isConnected()) - { - onOpenConnectionToDevice(); - } - else - { - //isWaitingForConnectionTest = true; - //TerpstraSysExApplication::getApp().getLumatoneController().testCurrentDeviceConnection(); - //jassert(isWaitingForConnectionTest); // Triggered if a test is requested before opening any devices - } + lblConnectionState->setColour(Label::ColourIds::textColourId, connectedColours[(int)liveEditorBtn->getToggleState()]); + repaint(); } -void MidiEditArea::onOpenConnectionToDevice() +void MidiEditArea::onOpenConnectionToDevice(String dialogTitle) { jassert(cbMidiInput->getSelectedItemIndex() >= 0 && cbMidiOutput->getSelectedItemIndex() >= 0); - jassert(alert == nullptr); - // This can get spammed, and needs a real solution, but for now this will prevent it in releases - Vito - if (alert == nullptr) - { - // Get firmware version so we can use the correct commands - TerpstraSysExApplication::getApp().getLumatoneController().sendGetFirmwareRevisionRequest(); - - alert.reset(new AlertWindow("Connection established!", translate("Do you want to send the current setup to your Lumatone?"), AlertWindow::AlertIconType::QuestionIcon, getParentComponent())); - alert->addButton("Send Editor layout", 1); - alert->addButton("Keep Editing Offline", 0); - alert->addButton("Import From Lumatone", 2); - alert->setLookAndFeel(&lookAndFeel); - -/* alert = lookAndFeel.createAlertWindow("Connection established!", translate("Do you want to send the current setup to your Lumatone?"), - "Import from Lumatone", - "Send layout", - "Edit Offline", - AlertWindow::AlertIconType::WarningIcon, - 3, getParentComponent())*/; - - auto retc = alert->runModalLoop(); + if (dialogTitle.length() == 0) + dialogTitle = translate("Connection Established!"); - if (retc == 1) - { - TerpstraSysExApplication::getApp().requestConfigurationFromDevice(); - liveEditorBtn->setToggleState(true, NotificationType::dontSendNotification); - lblConnectionState->setText("Connected", NotificationType::dontSendNotification); - } - else if (retc == 2) - { - TerpstraSysExApplication::getApp().sendCurrentConfigurationToDevice(); - liveEditorBtn->setToggleState(true, dontSendNotification); - lblConnectionState->setText("Connected", NotificationType::dontSendNotification); - } - else + jassert(!isWaitingForUserChoice); + if (isWaitingForUserChoice) + { + DBG("Bad connection loop detected"); + return; + } + + isWaitingForUserChoice = true; + + auto alertOptions = MessageBoxOptions().withTitle(dialogTitle) + .withMessage(translate("Do you want to send the current setup to your Lumatone?")) + .withIconType(AlertWindow::AlertIconType::QuestionIcon) + .withAssociatedComponent(getParentComponent()) + .withButton("Send Editor Layout") + .withButton("Keep Editing Offline") + .withButton("Import From Lumatone"); + + AlertWindow::showAsync(alertOptions, [&](int retc) { - offlineEditorBtn->setToggleState(true, NotificationType::sendNotification); - lblConnectionState->setText("Offline", NotificationType::dontSendNotification); - } - - //alert->setLookAndFeel(nullptr); - alert = nullptr; - } + isWaitingForUserChoice = false; + + if (retc == 0) // Import + { + TerpstraSysExApplication::getApp().requestConfigurationFromDevice(); + liveEditorBtn->setToggleState(true, NotificationType::sendNotification); + lblConnectionState->setText("Connected", NotificationType::dontSendNotification); + } + else if (retc == 1) // Send + { + TerpstraSysExApplication::getApp().sendCurrentConfigurationToDevice(); + liveEditorBtn->setToggleState(true, NotificationType::sendNotification); + lblConnectionState->setText("Connected", NotificationType::dontSendNotification); + } + else if (retc == 2) // Offline + { + offlineEditorBtn->setToggleState(true, NotificationType::sendNotification); + lblConnectionState->setText("Offline", NotificationType::dontSendNotification); + } + }); } void MidiEditArea::refreshInputMenuAndSetSelected(int inputDeviceIndex, juce::NotificationType notificationType) { cbMidiInput->clear(NotificationType::dontSendNotification); int i = 1; - for (auto device : TerpstraSysExApplication::getApp().getLumatoneController().getMidiInputList()) + for (auto device : TerpstraSysExApplication::getApp().getLumatoneController()->getMidiInputList()) cbMidiInput->addItem(device.name, i++); - if (inputDeviceIndex > 0) + if (inputDeviceIndex >= 0) cbMidiInput->setSelectedId(inputDeviceIndex, notificationType); } @@ -630,10 +625,10 @@ void MidiEditArea::refreshOutputMenuAndSetSelected(int outputDeviceIndex, juce:: { cbMidiOutput->clear(NotificationType::dontSendNotification); int i = 1; - for (auto device : TerpstraSysExApplication::getApp().getLumatoneController().getMidiOutputList()) + for (auto device : TerpstraSysExApplication::getApp().getLumatoneController()->getMidiOutputList()) cbMidiOutput->addItem(device.name, i++); - if (outputDeviceIndex > 0) + if (outputDeviceIndex >= 0) cbMidiOutput->setSelectedId(outputDeviceIndex, notificationType); } @@ -645,15 +640,15 @@ void MidiEditArea::timerCallback() } else { - TerpstraSysExApplication::getApp().getLumatoneController().refreshAvailableMidiDevices(); + TerpstraSysExApplication::getApp().getLumatoneController()->refreshAvailableMidiDevices(); refreshInputMenuAndSetSelected( - TerpstraSysExApplication::getApp().getLumatoneController().getMidiInputIndex() + 1, + TerpstraSysExApplication::getApp().getLumatoneController()->getMidiInputIndex() + 1, juce::NotificationType::dontSendNotification ); refreshOutputMenuAndSetSelected( - TerpstraSysExApplication::getApp().getLumatoneController().getMidiOutputIndex() + 1, + TerpstraSysExApplication::getApp().getLumatoneController()->getMidiOutputIndex() + 1, juce::NotificationType::dontSendNotification ); } diff --git a/Source/MidiEditArea.h b/Source/MidiEditArea.h index 2ce58a49..dba95248 100644 --- a/Source/MidiEditArea.h +++ b/Source/MidiEditArea.h @@ -21,10 +21,9 @@ //[Headers] -- You can add your own extra header files here -- #include "JuceHeader.h" -#include "TerpstraMidiDriver.h" #include "HajuLib/HajuErrorVisualizer.h" +#include "ApplicationListeners.h" #include "LumatoneEditorLookAndFeel.h" -#include "DeviceActivityMonitor.h" //[/Headers] @@ -38,7 +37,8 @@ //[/Comments] */ class MidiEditArea : public Component, - public LumatoneController::StatusListener, + public LumatoneEditor::StatusListener, + public LumatoneEditor::EditorListener, public juce::ComboBox::Listener, public juce::Button::Listener, public juce::Timer @@ -52,24 +52,25 @@ class MidiEditArea : public Component, //[UserMethods] -- You can add your own custom methods in this section. void lookAndFeelChanged() override; - void attemptDeviceConnection(); - void onOpenConnectionToDevice(); + void onOpenConnectionToDevice(String dialogTitle = ""); - // For now, preserve connection functionality and make sure internal combo boxes are up to date void refreshInputMenuAndSetSelected(int inputDeviceIndex, juce::NotificationType notificationType = NotificationType::sendNotification); void refreshOutputMenuAndSetSelected(int outputDeviceIndex, juce::NotificationType notificationType = NotificationType::sendNotification); - // Implementation of LumatoneController::StatusListener - //void availableDevicesChanged(const Array& inputDevices, int lastInputDevice, const Array& outputDevices, int lastOutputDevice) override; + // Implementation of LumatoneEditor::StatusListener + void connectionFailed() override; void connectionEstablished(int inputDevice, int outputDevice) override; void connectionLost() override; + + // Implementation of LumatoneEditor::EditorListener + void editorModeChanged(sysExSendingMode editMode) override; void timerCallback() override; private: - void setConnectivity(bool isConnected); + void setConnectivity(bool isConnected, String connectionStatus=String()); public: //[/UserMethods] @@ -92,6 +93,7 @@ class MidiEditArea : public Component, bool isConnected = false; bool isWaitingForConnectionTest = false; + bool isWaitingForUserChoice = false; LumatoneEditorLookAndFeel& lookAndFeel; @@ -111,7 +113,7 @@ class MidiEditArea : public Component, //============================================================================== // Helpers - std::unique_ptr alert; + //std::unique_ptr alert; FlexBox ioAreaFlexBox; @@ -169,11 +171,8 @@ class MidiEditArea : public Component, const float connectedAreaX = 0.8387f; const float controlBoundsMarginScalar = 0.0325f; - const float connectedX = 0.871f; const float connectivityHeight = 0.1957f; - const float logomarkX = 0.9559f; - const float logomarkY = 0.2222f; const float logomarkHeight = 0.5f; //[/UserVariables] diff --git a/Source/NoteEditArea.cpp b/Source/NoteEditArea.cpp index 847ab694..e601d3d8 100644 --- a/Source/NoteEditArea.cpp +++ b/Source/NoteEditArea.cpp @@ -7,7 +7,7 @@ the "//[xyz]" and "//[/xyz]" sections will be retained when the file is loaded and re-saved. - Created with Projucer version: 6.0.5 + Created with Projucer version: 6.0.8 ------------------------------------------------------------------------------ @@ -19,8 +19,6 @@ //[Headers] You can add your own extra header files here... #include "ViewConstants.h" -#include "SingleNoteAssign.h" -#include "IsomorphicMassAssign.h" #include "Main.h" //[/Headers] @@ -33,28 +31,42 @@ //============================================================================== NoteEditArea::NoteEditArea () - : Component("NoteEditArea"), - currentSingleKeySelection(-1) + : currentSingleKeySelection(-1) { - //[Constructor_pre] You can add your own custom stuff here..; + //[Constructor_pre] You can add your own custom stuff here.. showIsomorphicMassAssign = TerpstraSysExApplication::getApp().getPropertiesFile()->getBoolValue("IsomorphicMassAssign", false); //[/Constructor_pre] - editFunctionsTab.reset(new juce::TabbedComponent(juce::TabbedButtonBar::TabsAtTop)); - editFunctionsTab->setName("EditFunctionsTab"); - editFunctionsTab->setColour(TabbedComponent::ColourIds::outlineColourId, Colour()); - editFunctionsTab->setColour(TabbedComponent::ColourIds::backgroundColourId, Colour()); - addAndMakeVisible(editFunctionsTab.get()); - editFunctionsTab->addTab(translate("ManualAssign"), juce::Colours::lightgrey, new SingleNoteAssign(), true); - editFunctionsTab->setCurrentTabIndex(0); + setName ("NoteEditArea"); + editFunctionsTab.reset (new juce::TabbedComponent (juce::TabbedButtonBar::TabsAtTop)); + addAndMakeVisible (editFunctionsTab.get()); + editFunctionsTab->setTabBarDepth (30); + editFunctionsTab->addTab (TRANS("Manual Assign"), juce::Colours::lightgrey, new SingleNoteAssign(), true); + editFunctionsTab->setCurrentTabIndex (0); + + editFunctionsTab->setBounds (8, 48, 320, 422); + + labelWindowTitle.reset (new juce::Label ("labelWindowTitle", + TRANS("Assign Keys"))); + addAndMakeVisible (labelWindowTitle.get()); + labelWindowTitle->setFont (juce::Font (18.00f, juce::Font::plain).withTypefaceStyle ("Regular")); + labelWindowTitle->setJustificationType (juce::Justification::centredLeft); + labelWindowTitle->setEditable (false, false, false); + labelWindowTitle->setColour (juce::Label::textColourId, juce::Colour (0xff61acc8)); + labelWindowTitle->setColour (juce::TextEditor::textColourId, juce::Colours::black); + labelWindowTitle->setColour (juce::TextEditor::backgroundColourId, juce::Colour (0x00000000)); + + labelWindowTitle->setBounds (8, 8, 104, 24); + + + //[UserPreSize] editFunctionsTab->setIndent(0); editFunctionsTab->setOutline(0); - labelWindowTitle.reset (new juce::Label ("labelWindowTitle", translate("AssignKeys"))); - labelWindowTitle->setFont(TerpstraSysExApplication::getApp().getAppFont(LumatoneEditorFont::UniviaProBold)); - addAndMakeVisible (labelWindowTitle.get()); + editFunctionsTab->setColour(TabbedComponent::ColourIds::outlineColourId, Colour()); + editFunctionsTab->setColour(TabbedComponent::ColourIds::backgroundColourId, Colour()); - //[UserPreSize] + labelWindowTitle->setFont(TerpstraSysExApplication::getApp().getAppFont(LumatoneEditorFont::UniviaProBold)); if (showIsomorphicMassAssign) editFunctionsTab->addTab(TRANS("Isomorphic Assign"), juce::Colours::lightgrey, new IsomorphicMassAssign(), true); @@ -73,13 +85,24 @@ NoteEditArea::NoteEditArea () // Single Key fields resetOctaveSize(false); + /* Don't want to resize now + /* //[/UserPreSize] + setSize (760, 470); + //[Constructor] You can add your own custom stuff here.. + */ // First octaveboard selection, selection on first key: see MainComponent (Has to be done after change listener has been established) + auto singleNoteAssign = dynamic_cast(editFunctionsTab->getTabContentComponent(0)); + if (singleNoteAssign != nullptr) + { + addColourSelectionListener(singleNoteAssign); + } + //[/Constructor] } @@ -108,9 +131,15 @@ NoteEditArea::~NoteEditArea() void NoteEditArea::paint (juce::Graphics& g) { //[UserPrePaint] Add your own custom painting code here.. + + /* //[/UserPrePaint] + g.fillAll (juce::Colour (0xffbad0de)); + //[UserPaint] Add your own custom painting code here.. + */ + g.setColour(backgroundColour); g.fillRoundedRectangle(contentBackground, roundedCornerLayout); @@ -153,7 +182,7 @@ void NoteEditArea::resized() // Single Key fields - keyEditBounds = contentBackground.withLeft(assignControlsBounds.getRight() + assignControlsBounds.getX() / 2); + keyEditBounds = contentBackground.withLeft(assignControlsBounds.getRight() + assignControlsBounds.getX() * 0.5f); tilingGeometry.fitTilingTo( keyEditBounds, @@ -167,7 +196,7 @@ void NoteEditArea::resized() jassert(keyCentres.size() == TerpstraSysExApplication::getApp().getOctaveBoardSize()); float keySize = tilingGeometry.getKeySize(); - + int keyIndex = 0; for (keyIndex = 0; keyIndex < keyCentres.size(); keyIndex++) { @@ -193,35 +222,44 @@ void NoteEditArea::mouseDown (const juce::MouseEvent& e) // Select field changeSingleKeySelection(keyIndex); - // Perform the edit, according to edit mode. Including sending to device - auto setSelection = octaveBoardSelectorTab->getCurrentTabIndex(); - jassert(setSelection >= 0 && setSelection < NUMBEROFBOARDS && keyIndex >= 0 && keyIndex < TerpstraSysExApplication::getApp().getOctaveBoardSize()); - - int editMode = editFunctionsTab->getCurrentTabIndex(); - switch (editMode) - { - case noteEditMode::SingleNoteAssignMode: + // Grab key colour - may be replaced with eyedropper tool + if (e.mods.isAltDown()) { - auto editAction = dynamic_cast(editFunctionsTab->getTabContentComponent(editMode))->createEditAction(setSelection, keyIndex); - TerpstraSysExApplication::getApp().performUndoableAction(editAction); - break; + auto colour = terpstraKeyFields[keyIndex]->getValue().colour; + selectorListeners.call(&ColourSelectionListener::colourChangedCallback, this, colour); } - case noteEditMode::IsomorphicMassAssignMode: + // Standard assign action + else { - bool mappingChanged = dynamic_cast(editFunctionsTab->getTabContentComponent(editMode))->performMouseDown(setSelection, keyIndex); - if (mappingChanged) - { - TerpstraSysExApplication::getApp().setHasChangesToSave(true); + // Perform the edit, according to edit mode. Including sending to device + auto setSelection = octaveBoardSelectorTab->getCurrentTabIndex(); + jassert(setSelection >= 0 && setSelection < NUMBEROFBOARDS&& keyIndex >= 0 && keyIndex < TerpstraSysExApplication::getApp().getOctaveBoardSize()); - // Refresh key fields (all may be affected) - ((MainContentComponent*)getParentComponent())->refreshKeyDataFields(); + int editMode = editFunctionsTab->getCurrentTabIndex(); + switch (editMode) + { + case noteEditMode::SingleNoteAssignMode: + { + auto editAction = dynamic_cast(editFunctionsTab->getTabContentComponent(noteEditMode::SingleNoteAssignMode))->createEditAction(setSelection, keyIndex); + TerpstraSysExApplication::getApp().performUndoableAction(editAction); + break; + } + case noteEditMode::IsomorphicMassAssignMode: + { + bool mappingChanged = dynamic_cast(editFunctionsTab->getTabContentComponent(editMode))->performMouseDown(setSelection, keyIndex); + if (mappingChanged) + { + TerpstraSysExApplication::getApp().setHasChangesToSave(true); + + // Refresh key fields (all may be affected) + ((MainContentComponent*)getParentComponent())->refreshKeyDataFields(); + } + break; + } + default: + break; } - break; - } - default: - break; } - break; } } @@ -249,9 +287,11 @@ void NoteEditArea::setControlsTopLeftPosition(int controlsAreaX, int controlsAre void NoteEditArea::restoreStateFromPropertiesFile(PropertiesFile* propertiesFile) { dynamic_cast(editFunctionsTab->getTabContentComponent(noteEditMode::SingleNoteAssignMode))->restoreStateFromPropertiesFile(propertiesFile); - + if (showIsomorphicMassAssign) dynamic_cast(editFunctionsTab->getTabContentComponent(noteEditMode::IsomorphicMassAssignMode))->restoreStateFromPropertiesFile(propertiesFile); + + resized(); } void NoteEditArea::saveStateToPropertiesFile(PropertiesFile* propertiesFile) @@ -291,7 +331,7 @@ ColourEditComponent* NoteEditArea::getColourEditComponent() return dynamic_cast(editFunctionsTab->getTabContentComponent(noteEditMode::SingleNoteAssignMode))->getColourEditComponent(); } -ColourTextEditor* NoteEditArea::getSingleNoteColourTextEditor() +ColourTextEditor* NoteEditArea::getSingleNoteColourTextEditor() { return dynamic_cast(editFunctionsTab->getTabContentComponent(noteEditMode::SingleNoteAssignMode))->getColourTextEditor();; } @@ -344,6 +384,23 @@ void NoteEditArea::resetOctaveSize(bool refreshAndResize) } } } + +Colour NoteEditArea::getSelectedColour() +{ + if (currentSingleKeySelection >= 0 && currentSingleKeySelection < TerpstraSysExApplication::getApp().getOctaveBoardSize()) + { + return terpstraKeyFields[currentSingleKeySelection]->getValue().colour; + } + + auto singleNoteAssign = dynamic_cast(editFunctionsTab->getTabContentComponent(SingleNoteAssignMode)); + if (singleNoteAssign != nullptr) + { + return singleNoteAssign->getColourEditComponent()->getColourAsObject(); + } + + return Colour(); +} + //[/MiscUserCode] @@ -356,12 +413,11 @@ void NoteEditArea::resetOctaveSize(bool refreshAndResize) BEGIN_JUCER_METADATA - + diff --git a/Source/NoteEditArea.h b/Source/NoteEditArea.h index c07e950e..b85431c9 100644 --- a/Source/NoteEditArea.h +++ b/Source/NoteEditArea.h @@ -7,7 +7,7 @@ the "//[xyz]" and "//[/xyz]" sections will be retained when the file is loaded and re-saved. - Created with Projucer version: 6.0.5 + Created with Projucer version: 6.0.8 ------------------------------------------------------------------------------ @@ -31,6 +31,9 @@ #include "BoardGeometry.h" +#include "SingleNoteAssign.h" +#include "IsomorphicMassAssign.h" + //[/Headers] @@ -44,7 +47,8 @@ //[/Comments] */ class NoteEditArea : public Component, - public ChangeListener + public ChangeListener, + public ColourSelectionBroadcaster { public: //============================================================================== @@ -71,6 +75,8 @@ class NoteEditArea : public Component, ColourTextEditor* getSingleNoteColourTextEditor(); + IsomorphicMassAssign* getIsomorphicMassAssignPanel() { return dynamic_cast(editFunctionsTab->getTabContentComponent(1)); } + void changeSingleKeySelection(int newSelection); void refreshKeyFields(); @@ -82,6 +88,10 @@ class NoteEditArea : public Component, void resetOctaveSize(bool refreshAndResize=true); + // ColourSelectionBroadcaster Implementation + Colour getSelectedColour() override; + void deselectColour() override {}; + //[/UserMethods] void paint (juce::Graphics& g) override; @@ -89,6 +99,7 @@ class NoteEditArea : public Component, void mouseDown (const juce::MouseEvent& e) override; + private: //[UserVariables] -- You can add your own custom variables in this section. enum noteEditMode diff --git a/Source/NoteOnOffVelocityCurveDialog.cpp b/Source/NoteOnOffVelocityCurveDialog.cpp index 28e2857d..ad361dee 100644 --- a/Source/NoteOnOffVelocityCurveDialog.cpp +++ b/Source/NoteOnOffVelocityCurveDialog.cpp @@ -17,32 +17,3 @@ NoteOnOffVelocityCurveDialog::NoteOnOffVelocityCurveDialog() : VelocityCurveDlgBase(TerpstraVelocityCurveConfig::VelocityCurveType::noteOnNoteOff) { } - -//NoteOnOffVelocityCurveDialog::~NoteOnOffVelocityCurveDialog() -//{ -//} - -//float NoteOnOffVelocityCurveDialog::beamWidth(int xPos) -//{ -// auto mappingInEdit = getMappingInEdit(); -// if ( mappingInEdit == nullptr) // Security at start of program -// return 1; // ad-hoc -// -// if (xPos == 0) -// { -// return (getWidth() - 2.0f * cbEditMode->getX()) * mappingInEdit->velocityIntervalTableValues[0] / 2048.0f; -// } -// else if (xPos < VELOCITYINTERVALTABLESIZE) -// { -// return (getWidth() - 2.0f * cbEditMode->getX()) * (mappingInEdit->velocityIntervalTableValues[xPos] - mappingInEdit->velocityIntervalTableValues[xPos-1]) / 2048.0f; -// } -// else if (xPos == VELOCITYINTERVALTABLESIZE) -// { -// return (getWidth() - 2.0f * cbEditMode->getX()) * (2048.0f - mappingInEdit->velocityIntervalTableValues[xPos-1]) / 2048.0f; -// } -// else -// { -// jassertfalse; -// return 1; -// } -//} \ No newline at end of file diff --git a/Source/NoteOnOffVelocityCurveDialog.h b/Source/NoteOnOffVelocityCurveDialog.h index cfa7a2b0..47e5292b 100644 --- a/Source/NoteOnOffVelocityCurveDialog.h +++ b/Source/NoteOnOffVelocityCurveDialog.h @@ -15,13 +15,7 @@ // Note on/on velocity curve dialog. Horizontal axis stands for ticks class NoteOnOffVelocityCurveDialog : public VelocityCurveDlgBase { public: - //============================================================================== NoteOnOffVelocityCurveDialog(); - //~NoteOnOffVelocityCurveDialog() override; - - //protected: - // virtual float beamWidth(int xPos) override; - }; class FaderVelocityCurveDialog : public VelocityCurveDlgBase { diff --git a/Source/Palette.h b/Source/Palette.h index c92148c6..526903fd 100644 --- a/Source/Palette.h +++ b/Source/Palette.h @@ -26,17 +26,11 @@ class Palette : public juce::Component, protected juce::ChangeListener // Setup paths needed to draw swatches } - ~Palette() override - { - } + virtual ~Palette() {} //====================================================================== // Implementation of juce::Component - virtual void paint(Graphics& g) = 0; - - virtual void resized() = 0; - virtual void mouseDown(const MouseEvent& e) override { if (isEnabled()) @@ -46,12 +40,6 @@ class Palette : public juce::Component, protected juce::ChangeListener if (s.contains(e.position)) { setSelectedSwatchNumber(swatchPaths.indexOf(s)); - - if (selector) - { - selector->setCurrentColour(palette[selectedSwatch], dontSendNotification); - } - return; } } @@ -99,6 +87,10 @@ class Palette : public juce::Component, protected juce::ChangeListener virtual void setSelectedSwatchNumber(int swatchIndex) { selectedSwatch = swatchIndex; + if (selector) + { + selector->setCurrentColour(palette[selectedSwatch], dontSendNotification); + } repaint(); } diff --git a/Source/PedalSensitivityDlg.cpp b/Source/PedalSensitivityDlg.cpp index d94e8f93..4bdf1844 100644 --- a/Source/PedalSensitivityDlg.cpp +++ b/Source/PedalSensitivityDlg.cpp @@ -19,6 +19,7 @@ //[Headers] You can add your own extra header files here... #include "Main.h" +#include "EditActions.h" //[/Headers] #include "PedalSensitivityDlg.h" @@ -34,7 +35,7 @@ PedalSensitivityDlg::PedalSensitivityDlg () //[/Constructor_pre] labelExprContrSensitivity.reset (new juce::Label ("new label", - TRANS("Sensitivity:"))); + TRANS("Sensitivity"))); addAndMakeVisible (labelExprContrSensitivity.get()); labelExprContrSensitivity->setFont (juce::Font (15.00f, juce::Font::plain).withTypefaceStyle ("Regular")); labelExprContrSensitivity->setJustificationType (juce::Justification::centredLeft); @@ -42,49 +43,62 @@ PedalSensitivityDlg::PedalSensitivityDlg () labelExprContrSensitivity->setColour (juce::TextEditor::textColourId, juce::Colours::black); labelExprContrSensitivity->setColour (juce::TextEditor::backgroundColourId, juce::Colour (0x00000000)); - labelExprContrSensitivity->setBounds (6, 35, 64, 24); + labelExprContrSensitivity->setBounds (13, 152, 74, 24); btnInvertExpression.reset (new juce::ToggleButton ("btnInvertExpression")); addAndMakeVisible (btnInvertExpression.get()); - btnInvertExpression->setButtonText (TRANS("Invert Expression")); + btnInvertExpression->setButtonText (TRANS("Invert")); btnInvertExpression->addListener (this); - btnInvertExpression->setBounds (8, 96, 162, 24); + btnInvertExpression->setBounds (10, 32, 99, 24); - labelPedalTitle.reset (new juce::Label ("labelPedalTitle", - TRANS("Pedal Settings"))); - addAndMakeVisible (labelPedalTitle.get()); - labelPedalTitle->setFont (juce::Font (18.00f, juce::Font::plain).withTypefaceStyle ("Regular")); - labelPedalTitle->setJustificationType (juce::Justification::centredLeft); - labelPedalTitle->setEditable (false, false, false); - labelPedalTitle->setColour (juce::Label::textColourId, juce::Colour (0xff61acc8)); - labelPedalTitle->setColour (juce::TextEditor::textColourId, juce::Colours::black); - labelPedalTitle->setColour (juce::TextEditor::backgroundColourId, juce::Colour (0x00000000)); + lblExpression.reset (new juce::Label ("lblExpression", + TRANS("Expression"))); + addAndMakeVisible (lblExpression.get()); + lblExpression->setFont (juce::Font (18.00f, juce::Font::plain).withTypefaceStyle ("Regular")); + lblExpression->setJustificationType (juce::Justification::centredLeft); + lblExpression->setEditable (false, false, false); + lblExpression->setColour (juce::Label::textColourId, juce::Colour (0xff61acc8)); + lblExpression->setColour (juce::TextEditor::textColourId, juce::Colours::black); + lblExpression->setColour (juce::TextEditor::backgroundColourId, juce::Colour (0x00000000)); - labelPedalTitle->setBounds (6, 3, 104, 24); + lblExpression->setBounds (6, 3, 104, 24); sldExprCtrlSensitivity.reset (new juce::Slider ("sldExprCtrlSensitivity")); addAndMakeVisible (sldExprCtrlSensitivity.get()); sldExprCtrlSensitivity->setRange (0, 127, 1); sldExprCtrlSensitivity->setSliderStyle (juce::Slider::RotaryHorizontalVerticalDrag); - sldExprCtrlSensitivity->setTextBoxStyle (juce::Slider::TextBoxLeft, false, 60, 20); + sldExprCtrlSensitivity->setTextBoxStyle (juce::Slider::TextBoxBelow, false, 60, 20); sldExprCtrlSensitivity->addListener (this); - sldExprCtrlSensitivity->setBounds (77, 12, 160, 72); + sldExprCtrlSensitivity->setBounds (-32, 49, 160, 97); btnInvertSustain.reset (new juce::ToggleButton ("btnInvertSustain")); addAndMakeVisible (btnInvertSustain.get()); - btnInvertSustain->setButtonText (TRANS("Invert Sustain")); + btnInvertSustain->setButtonText (TRANS("Invert")); btnInvertSustain->addListener (this); - btnInvertSustain->setBounds (8, 72, 150, 24); + btnInvertSustain->setBounds (125, 32, 86, 24); + + lblSustain.reset (new juce::Label ("lblSustain", + TRANS("Sustain"))); + addAndMakeVisible (lblSustain.get()); + lblSustain->setFont (juce::Font (18.00f, juce::Font::plain).withTypefaceStyle ("Regular")); + lblSustain->setJustificationType (juce::Justification::centredLeft); + lblSustain->setEditable (false, false, false); + lblSustain->setColour (juce::Label::textColourId, juce::Colour (0xff61acc8)); + lblSustain->setColour (juce::TextEditor::textColourId, juce::Colours::black); + lblSustain->setColour (juce::TextEditor::backgroundColourId, juce::Colour (0x00000000)); + + lblSustain->setBounds (121, 3, 99, 24); //[UserPreSize] - labelPedalTitle->setFont(TerpstraSysExApplication::getApp().getAppFont(LumatoneEditorFont::UniviaProBold)); + lblExpression->setFont(TerpstraSysExApplication::getApp().getAppFont(LumatoneEditorFont::UniviaProBold)); + lblSustain->setFont(TerpstraSysExApplication::getApp().getAppFont(LumatoneEditorFont::UniviaProBold)); labelExprContrSensitivity->setFont(TerpstraSysExApplication::getApp().getAppFont(LumatoneEditorFont::GothamNarrowMedium)); - - TerpstraSysExApplication::getApp().getLumatoneController().addFirmwareListener(this); + labelExprContrSensitivity->setJustificationType(Justification::centred); + TerpstraSysExApplication::getApp().getLumatoneController()->addFirmwareListener(this); //[/UserPreSize] setSize (134, 96); @@ -101,9 +115,10 @@ PedalSensitivityDlg::~PedalSensitivityDlg() labelExprContrSensitivity = nullptr; btnInvertExpression = nullptr; - labelPedalTitle = nullptr; + lblExpression = nullptr; sldExprCtrlSensitivity = nullptr; btnInvertSustain = nullptr; + lblSustain = nullptr; //[Destructor]. You can add your own custom destruction code here.. @@ -118,7 +133,8 @@ void PedalSensitivityDlg::paint (juce::Graphics& g) //[UserPaint] Add your own custom painting code here.. g.setColour(Colour(0xff212626)); - g.fillRoundedRectangle(getLocalBounds().toFloat().withTop(proportionOfHeight(SETTINGSAREAMARGINHEIGHT)), roundedCornerSize); + g.fillRoundedRectangle(expressionBounds, roundedCornerSize); + g.fillRoundedRectangle(sustainBounds, roundedCornerSize); //[/UserPaint] } @@ -131,20 +147,38 @@ void PedalSensitivityDlg::resized() //[UserResized] Add your own custom resize handling here.. - roundedCornerSize = round(getParentHeight() * ROUNDEDCORNERTOAPPHEIGHT); + roundedCornerSize = roundToInt(getParentHeight() * ROUNDEDCORNERTOAPPHEIGHT); - resizeLabelWithHeight(labelPedalTitle.get(), roundToInt(getHeight() * SETTINGSLABELHEIGHT)); - labelPedalTitle->setTopLeftPosition(roundToInt(getWidth() * SETTINGSLABELMARGINWIDTH), 0); + int areaMarginWidth = roundToInt(w * sectionMarginWidth) * 0.5f; + int areaMarginHeight = roundToInt(h * SETTINGSAREAMARGINHEIGHT); - int marginX = roundToInt(getParentWidth() * SETTINGSCONTROLMARGINTOAPPWIDTH); - int buttonHeight = roundToInt(h * SETTINGSTOGGLEHEIGHTSCALAR); - btnInvertExpression->setBounds(marginX, proportionOfHeight(0.3f), w, buttonHeight); - btnInvertSustain->setBounds(marginX, proportionOfHeight(0.5), w, buttonHeight); + expressionBounds = getLocalBounds().toFloat().withTop(areaMarginHeight).withRight(roundToInt(w * 0.5f - areaMarginWidth)); + sustainBounds = getLocalBounds().toFloat().withTop(areaMarginHeight).withLeft(roundToInt(w * 0.5f + areaMarginWidth)); - sldExprCtrlSensitivity->setBounds(getLocalBounds().toFloat().getProportion(sliderBoundsProps).toNearestInt()); + int lblMarginX = roundToInt(w * SETTINGSLABELMARGINWIDTH); + int lblWidth = roundToInt(w * 0.5f); + int lblHeight = roundToInt(h * SETTINGSLABELHEIGHT); + lblExpression->setBounds(expressionBounds.getX() + lblMarginX, 0, lblWidth, lblHeight); + lblSustain->setBounds(sustainBounds.getX() + lblMarginX, 0, lblWidth, lblHeight); - resizeLabelWithHeight(labelExprContrSensitivity.get(), buttonHeight * 1.2f, 1.0f, ""); - labelExprContrSensitivity->setCentrePosition(sldExprCtrlSensitivity->getBounds().getCentreX(), btnInvertExpression->getBounds().getCentreY()); + int controlMargin = roundToInt(getParentWidth() * SETTINGSCONTROLMARGINTOAPPWIDTH); + int buttonHeight = roundToInt(h * SETTINGSTOGGLEHEIGHTSCALAR); + int buttonY = roundToInt(h * 0.3f); + btnInvertExpression->setBounds(expressionBounds.withTrimmedLeft(controlMargin).withTop(buttonY).withHeight(buttonHeight).toNearestInt()); + btnInvertSustain->setBounds(sustainBounds.withTrimmedLeft(controlMargin).withTop(buttonY).withHeight(buttonHeight).toNearestInt()); + + sldExprCtrlSensitivity->setBounds( + expressionBounds.reduced(expressionBounds.getWidth() * 0.2f, 0) + .withTop(btnInvertExpression->getBottom() + buttonHeight) + .withTrimmedBottom(buttonHeight * 1.5f) + .toNearestInt() + ); + + labelExprContrSensitivity->setBounds( + expressionBounds.withTop(sldExprCtrlSensitivity->getBottom() + buttonHeight * 0.1f) + .withTrimmedBottom(buttonHeight * 0.5f) + .toNearestInt() + ); //[/UserResized] } @@ -156,19 +190,13 @@ void PedalSensitivityDlg::buttonClicked (juce::Button* buttonThatWasClicked) if (buttonThatWasClicked == btnInvertExpression.get()) { //[UserButtonCode_btnInvertExpression] -- add your button handler code here.. - bool invert = btnInvertExpression->getToggleState(); - ((MainContentComponent*)getParentComponent())->getMappingInEdit().invertExpression = invert; - TerpstraSysExApplication::getApp().setHasChangesToSave(true); - TerpstraSysExApplication::getApp().getLumatoneController().sendInvertFootController(invert); + TerpstraSysExApplication::getApp().performUndoableAction(new Lumatone::InvertFootControllerEditAction(btnInvertExpression->getToggleState())); //[/UserButtonCode_btnInvertExpression] } else if (buttonThatWasClicked == btnInvertSustain.get()) { //[UserButtonCode_btnInvertSustain] -- add your button handler code here.. - bool invert = btnInvertSustain->getToggleState(); - ((MainContentComponent*)getParentComponent())->getMappingInEdit().invertSustain = invert; - TerpstraSysExApplication::getApp().setHasChangesToSave(true); - TerpstraSysExApplication::getApp().getLumatoneController().invertSustainPedal(invert); + TerpstraSysExApplication::getApp().performUndoableAction(new Lumatone::InvertSustainEditAction(btnInvertSustain->getToggleState())); //[/UserButtonCode_btnInvertSustain] } @@ -184,6 +212,24 @@ void PedalSensitivityDlg::sliderValueChanged (juce::Slider* sliderThatWasMoved) if (sliderThatWasMoved == sldExprCtrlSensitivity.get()) { //[UserSliderCode_sldExprCtrlSensitivity] -- add your slider handling code here.. + int newSensitvity = sldExprCtrlSensitivity->getValue(); + // ToDo value checking: encapsulate in keyboard data structure? + if (newSensitvity < 0) + { + newSensitvity = 0; + sldExprCtrlSensitivity->setValue(newSensitvity); + } + + if (newSensitvity > 0x7f) + { + newSensitvity = 0x7f; + sldExprCtrlSensitivity->setValue(newSensitvity); + } + + ((MainContentComponent*)getParentComponent())->getMappingInEdit().expressionControllerSensivity = newSensitvity; + TerpstraSysExApplication::getApp().setHasChangesToSave(true); + TerpstraSysExApplication::getApp().getLumatoneController()->sendExpressionPedalSensivity(newSensitvity); + //[/UserSliderCode_sldExprCtrlSensitivity] } @@ -195,40 +241,13 @@ void PedalSensitivityDlg::sliderValueChanged (juce::Slider* sliderThatWasMoved) //[MiscUserCode] You can add your own definitions of your custom methods or any other code here... - -//void PedalSensitivityDlg::textEditorTextChanged(TextEditor& textEdit) -//{ -//} -// -//void PedalSensitivityDlg::textEditorFocusLost(TextEditor& textEdit) -//{ -// if (&textEdit == txtExprCtrlSensivity.get()) -// { -// int newSensitvity = textEdit.getText().getIntValue(); -// if (newSensitvity < 0) -// { -// newSensitvity = 0; -// textEdit.setText(String(newSensitvity)); -// } -// -// if (newSensitvity > 0x7f) -// { -// newSensitvity = 0x7f; -// textEdit.setText(String(newSensitvity)); -// } -// -// ((MainContentComponent*)getParentComponent())->getMappingInEdit().expressionControllerSensivity = newSensitvity; -// TerpstraSysExApplication::getApp().setHasChangesToSave(true); -// TerpstraSysExApplication::getApp().getMidiDriver().sendExpressionPedalSensivity(newSensitvity); -// } -//} - void PedalSensitivityDlg::lookAndFeelChanged() { auto newLookAndFeel = dynamic_cast(&getLookAndFeel()); if (newLookAndFeel) { - labelPedalTitle->setColour(Label::ColourIds::textColourId, newLookAndFeel->findColour(LumatoneEditorColourIDs::LabelBlue)); + lblExpression->setColour(Label::ColourIds::textColourId, newLookAndFeel->findColour(LumatoneEditorColourIDs::LabelBlue)); + lblSustain->setColour(Label::ColourIds::textColourId, newLookAndFeel->findColour(LumatoneEditorColourIDs::LabelBlue)); labelExprContrSensitivity->setColour(Label::ColourIds::textColourId, newLookAndFeel->findColour(LumatoneEditorColourIDs::DescriptionText)); } } @@ -248,14 +267,27 @@ void PedalSensitivityDlg::firmwareRevisionReceived(FirmwareVersion version) if (firmwareSupport.versionAcknowledgesCommand(version, INVERT_SUSTAIN_PEDAL)) { - btnInvertSustain->setVisible(true); + btnInvertSustain->setEnabled(true); + btnInvertSustain->setTooltip(""); } else { - btnInvertSustain->setVisible(false); + btnInvertSustain->setEnabled(false); + btnInvertSustain->setTooltip("This feature is not supported by the firmware version of your Lumatone."); } } +void PedalSensitivityDlg::presetFlagsReceived(PresetFlags presetFlags) +{ + btnInvertExpression->setToggleState(presetFlags.expressionPedalInverted, dontSendNotification); + btnInvertSustain->setToggleState(presetFlags.sustainPedalInverted, dontSendNotification); +} + +void PedalSensitivityDlg::expressionPedalSensitivityReceived(int sensitivity) +{ + sldExprCtrlSensitivity->setValue(sensitivity, dontSendNotification); +} + //[/MiscUserCode] @@ -269,33 +301,37 @@ void PedalSensitivityDlg::firmwareRevisionReceived(FirmwareVersion version) BEGIN_JUCER_METADATA END_JUCER_METADATA diff --git a/Source/PedalSensitivityDlg.h b/Source/PedalSensitivityDlg.h index 784f5826..1adf63a1 100644 --- a/Source/PedalSensitivityDlg.h +++ b/Source/PedalSensitivityDlg.h @@ -36,7 +36,7 @@ //[/Comments] */ class PedalSensitivityDlg : public juce::Component, - public LumatoneController::FirmwareListener, + public LumatoneEditor::FirmwareListener, public juce::Button::Listener, public juce::Slider::Listener { @@ -52,8 +52,10 @@ class PedalSensitivityDlg : public juce::Component, void lookAndFeelChanged() override; - // LumatoneController::FirmwareListener implementation + // LumatoneEditor::FirmwareListener implementation void firmwareRevisionReceived(FirmwareVersion version) override; + void presetFlagsReceived(PresetFlags presetFlags) override; + void expressionPedalSensitivityReceived(int sensitivity) override; //[/UserMethods] @@ -66,20 +68,20 @@ class PedalSensitivityDlg : public juce::Component, private: //[UserVariables] -- You can add your own custom variables in this section. - int roundedCornerSize; - Rectangle controlBounds; - - // Style Constants - const Rectangle sliderBoundsProps = { 0.54f, 0.38f, 0.4f, 0.52f }; + int roundedCornerSize = 0; + Rectangle expressionBounds; + Rectangle sustainBounds; + const float sectionMarginWidth = 0.05f; //[/UserVariables] //============================================================================== std::unique_ptr labelExprContrSensitivity; std::unique_ptr btnInvertExpression; - std::unique_ptr labelPedalTitle; + std::unique_ptr lblExpression; std::unique_ptr sldExprCtrlSensitivity; std::unique_ptr btnInvertSustain; + std::unique_ptr lblSustain; //============================================================================== diff --git a/Source/PolygonPalette.h b/Source/PolygonPalette.h index b92c386b..f38563f3 100644 --- a/Source/PolygonPalette.h +++ b/Source/PolygonPalette.h @@ -19,7 +19,7 @@ class PolygonPalette : public Palette { public: - PolygonPalette(int numSidesIn = 6, float angleOffsetIn = float_Pi / 12.0f) + PolygonPalette(int numSidesIn = 6, float angleOffsetIn = MathConstants::pi / 12.0f) : Palette(numSidesIn), angleOffset(angleOffsetIn) { createSwatches(); @@ -76,7 +76,7 @@ class PolygonPalette : public Palette void createSwatches() { - float angInc = 2 * float_Pi / getNumberOfSwatches(); + float angInc = 2 * MathConstants::pi / getNumberOfSwatches(); float angMargin = angInc * margin * 0.5f; for (int i = 0; i < getNumberOfSwatches(); i++) diff --git a/Source/ScaleStructureController/GroupHandle.cpp b/Source/ScaleStructureController/GroupHandle.cpp index 2cc05dcd..ff5dd7f7 100644 --- a/Source/ScaleStructureController/GroupHandle.cpp +++ b/Source/ScaleStructureController/GroupHandle.cpp @@ -80,7 +80,7 @@ Path GroupHandle::getLine(float lineThickness) const Path diamond; if (size > 0) - diamond.addPolygon(line.getPointAlongLineProportionally(1/size), 4, lineThickness, position.x + float_Pi / 2); + diamond.addPolygon(line.getPointAlongLineProportionally(1/size), 4, lineThickness, position.x + MathConstants::pi / 2); return diamond; } diff --git a/Source/ScaleStructureController/GroupingCircle.cpp b/Source/ScaleStructureController/GroupingCircle.cpp index 7a7060fd..d1d2f90a 100644 --- a/Source/ScaleStructureController/GroupingCircle.cpp +++ b/Source/ScaleStructureController/GroupingCircle.cpp @@ -284,7 +284,7 @@ void GroupingCircle::resized() groupInnerCircleBounds = groupOuterCircleBounds.reduced(groupRingWidth); degreeInnerCircleBounds = groupInnerCircleBounds.reduced(degreeRingWidth); - angleIncrement = 2 * double_Pi / groupChain.size(); + angleIncrement = 2 * MathConstants::pi / groupChain.size(); angleHalf = angleIncrement / 2.0f; handleDragThreshold = angleIncrement * 2.0 / 3.0f; diff --git a/Source/ScaleStructureController/GroupingCircle.h b/Source/ScaleStructureController/GroupingCircle.h index 72d271f2..d37aabf1 100644 --- a/Source/ScaleStructureController/GroupingCircle.h +++ b/Source/ScaleStructureController/GroupingCircle.h @@ -22,7 +22,7 @@ class GroupingCircle : public Component { public: GroupingCircle(const ScaleStructure& structureIn, Array& colourTableIn); - ~GroupingCircle(); + virtual ~GroupingCircle(); float getInnerRadius() const; float getMiddleRadius() const; @@ -57,7 +57,7 @@ class GroupingCircle : public Component class Listener { public: - ~Listener() {}; + virtual ~Listener() {} virtual void offsetChanged(int newOffset) = 0; @@ -123,7 +123,7 @@ class GroupingCircle : public Component Array highlightedDegreeEdges; // TODO: turn this into Array> to differentiate symmetric edges Array> highlightedEdgeLines; - const float handleDotAngRatio = float_Pi / 100.0f; + const float handleDotAngRatio = MathConstants::pi / 100.0f; float handleDotRadius; float handleHighlightMult = 1.5f; float handlePlacementRadius; @@ -150,7 +150,7 @@ class GroupingCircle : public Component float degreeMiddleRadius; Point center; - float circleOffset = float_Pi / 2.0f; + float circleOffset = MathConstants::pi / 2.0f; float groupRingWidth; float degreeRingWidth; @@ -161,8 +161,8 @@ class GroupingCircle : public Component double angleIncrement; double angleHalf; - const float float_HalfPi = float_Pi / 2; - const float float_Tau = float_Pi * 2; + const float float_HalfPi = MathConstants::pi / 2; + const float float_Tau = MathConstants::pi * 2; Array> radiLines; Array degreeArcPaths; diff --git a/Source/ScaleStructureController/ScaleStructure.cpp b/Source/ScaleStructureController/ScaleStructure.cpp index cf7cc202..87a7b64c 100644 --- a/Source/ScaleStructureController/ScaleStructure.cpp +++ b/Source/ScaleStructureController/ScaleStructure.cpp @@ -573,7 +573,7 @@ bool ScaleStructure::setChromaAlterations(Array> chromaAlterationsIn) void ScaleStructure::calculateProperties() { - DBG("~~~ ScaleStructure is calculating properties ~~~"); + //DBG("~~~ ScaleStructure is calculating properties ~~~") // Clear all data dependent on Generator and Size choices scaleSizes.clear(); keyboardTypes.clear(); @@ -711,7 +711,7 @@ void ScaleStructure::calculateGeneratorChain() } } - DBG("SS Generator Chain: " + dbgstr); +// DBG("SS Generator Chain: " + dbgstr); calculateIntervalScales(); } @@ -758,32 +758,32 @@ void ScaleStructure::fillSymmetricGrouping(bool applyAlterations) // DEBUG PRINTING - String dbgstr = ""; - int size, sum = 0; - for (int i = 0; i < degreeGroupIndexedSizes.size(); i++) { - size = degreeGroupScaleSizes[i]; - dbgstr += String(size) + ", "; - sum += size; - } - dbgstr += " = " + String(sum); - DBG("SS Updated grouping: " + dbgstr); - - dbgstr = "\t"; - for (int group = 0; group < degreeGroupIndexedSizes.size(); group++) - { - Array degreeGroup = degreeGroupings[group]; - dbgstr += "Tier " + String(group) + ": "; - for (int deg = 0; deg < degreeGroup.size(); deg++) - { - dbgstr += String(degreeGroup[deg]) + ", "; - } - - if (group + 1 < degreeGroupIndexedSizes.size()) - dbgstr += "\n\t"; - } - - DBG("SS Degree groupings: "); - DBG(dbgstr); +// String dbgstr = ""; +// int size, sum = 0; +// for (int i = 0; i < degreeGroupIndexedSizes.size(); i++) { +// size = degreeGroupScaleSizes[i]; +// dbgstr += String(size) + ", "; +// sum += size; +// } +// dbgstr += " = " + String(sum); +// DBG("SS Updated grouping: " + dbgstr); + +// dbgstr = "\t"; +// for (int group = 0; group < degreeGroupIndexedSizes.size(); group++) +// { +// Array degreeGroup = degreeGroupings[group]; +// dbgstr += "Tier " + String(group) + ": "; +// for (int deg = 0; deg < degreeGroup.size(); deg++) +// { +// dbgstr += String(degreeGroup[deg]) + ", "; +// } +// +// if (group + 1 < degreeGroupIndexedSizes.size()) +// dbgstr += "\n\t"; +// } +// +// DBG("SS Degree groupings: "); +// DBG(dbgstr); } void ScaleStructure::applyChromaAlterations() @@ -876,16 +876,16 @@ void ScaleStructure::applyChromaAlterations() groupChain.add(deg); } - String dbgstr = ""; - - for (auto alteration : chromaAlterations) - { - dbgstr += "(" + alteration.toString() + "), "; - } - dbgstr = alterationsAttachedToDegree - ? "SS MODMOS Properties by degree:\t" + dbgstr - : "SS MODMOS Properties by gIndex:\t" + dbgstr; - DBG(dbgstr); +// String dbgstr = ""; +// +// for (auto alteration : chromaAlterations) +// { +// dbgstr += "(" + alteration.toString() + "), "; +// } +// dbgstr = alterationsAttachedToDegree +// ? "SS MODMOS Properties by degree:\t" + dbgstr +// : "SS MODMOS Properties by gIndex:\t" + dbgstr; +// DBG(dbgstr); } int ScaleStructure::getSymmetricGroup(int groupIndexIn) const @@ -1742,7 +1742,9 @@ String ScaleStructure::getIntervalSteps(Point& stepSizesOut, bool withModif for (int i = 1; i <= getScaleSize() * periodFactorSelected; i++) { sizes.set(i - 1, sizes[i] - sizes[i - 1]); +#if JUCE_DEBUG steps += String(sizes[i - 1]) + " "; +#endif } // Extract step sizes diff --git a/Source/ScaleStructureController/ScaleStructureComponent.cpp b/Source/ScaleStructureController/ScaleStructureComponent.cpp index 942e4c71..7a51f3ae 100644 --- a/Source/ScaleStructureController/ScaleStructureComponent.cpp +++ b/Source/ScaleStructureController/ScaleStructureComponent.cpp @@ -165,7 +165,7 @@ void ScaleStructureComponent::resized() periodSlider->setCentrePosition(circle->getIntPointFromCenter(circle->getInnerRadius() * 0.4f, 0)); generatorSlider->setSize(proportionOfWidth(0.2f), proportionOfHeight(sliderHeight)); - generatorSlider->setCentrePosition(circle->getIntPointFromCenter(circle->getInnerRadius() * 0.125f, float_Pi)); + generatorSlider->setCentrePosition(circle->getIntPointFromCenter(circle->getInnerRadius() * 0.125f, MathConstants::pi)); offsetLabel->setFont(Font().withHeight(offsetFontHeightScalar)); offsetLabel->setSize(offsetLabel->getFont().getStringWidth("Offset") * 2, offsetLabel->getFont().getHeight() * 3); @@ -190,11 +190,11 @@ void ScaleStructureComponent::resized() resizeLabelWithWidth(generatorValueLbl.get(), proportionOfWidth(0.25f)); generatorValueLbl->setSize(proportionOfWidth(0.4f), generatorValueLbl->getHeight()); - generatorValueLbl->setCentrePosition(circle->getIntPointFromCenter(circle->getInnerRadius() * 3.0f * controlUnit, float_Pi)); + generatorValueLbl->setCentrePosition(circle->getIntPointFromCenter(circle->getInnerRadius() * 3.0f * controlUnit, MathConstants::pi)); resizeLabelWithWidth(stepSizePatternLbl.get(), proportionOfWidth(0.25f)); stepSizePatternLbl->setSize(proportionOfWidth(0.4f), stepSizePatternLbl->getHeight()); - stepSizePatternLbl->setCentrePosition(circle->getIntPointFromCenter(circle->getInnerRadius() * 4.5 * controlUnit, float_Pi)); + stepSizePatternLbl->setCentrePosition(circle->getIntPointFromCenter(circle->getInnerRadius() * 4.5 * controlUnit, MathConstants::pi)); //[/UserResized] } diff --git a/Source/ScaleStructureController/ScaleStructureComponent.h b/Source/ScaleStructureController/ScaleStructureComponent.h index 866d473b..160ac381 100644 --- a/Source/ScaleStructureController/ScaleStructureComponent.h +++ b/Source/ScaleStructureController/ScaleStructureComponent.h @@ -163,9 +163,9 @@ class ScaleStructureComponent : public Component, const float sizeRadiusScalar = 0.493827f; const float offsetFontHeightScalar = 0.0208333f; - const float offsetArrowAngle1 = float_Pi * 0.083333f; + const float offsetArrowAngle1 = MathConstants::pi * 0.083333f; const float offsetArrowAngle2 = offsetArrowAngle1 * 0.5f; - const float offsetArrowAngle3 = float_Pi * 0.071429f; + const float offsetArrowAngle3 = MathConstants::pi * 0.071429f; const float offsetArrowRadScalar0 = 0.0769231f; const float offsetArrowRadScalar1 = 0.92857f; diff --git a/Source/Settings/CalibrationDlg.cpp b/Source/Settings/CalibrationDlg.cpp index aed8eb4d..31ba2297 100644 --- a/Source/Settings/CalibrationDlg.cpp +++ b/Source/Settings/CalibrationDlg.cpp @@ -7,7 +7,7 @@ the "//[xyz]" and "//[/xyz]" sections will be retained when the file is loaded and re-saved. - Created with Projucer version: 6.0.5 + Created with Projucer version: 6.0.8 ------------------------------------------------------------------------------ @@ -52,7 +52,7 @@ CalibrationDlg::CalibrationDlg () //[UserPreSize] - updateCalibrationStatus(); + updateWheelCalibrationStatus(); // Calibration type selector calibrationSelectorTab.reset(new TabbedButtonBar(TabbedButtonBar::Orientation::TabsAtTop)); @@ -60,10 +60,12 @@ CalibrationDlg::CalibrationDlg () calibrationSelectorTab->addTab(translate("Keys"), juce::Colours::lightgrey, 1); calibrationSelectorTab->addTab(translate("Aftertouch"), juce::Colours::lightgrey, 2); - calibrationSelectorTab->addTab(translate("Modulation Wheel"), juce::Colours::lightgrey, 3); + calibrationSelectorTab->addTab(translate("Pitch & Mod Wheels"), juce::Colours::lightgrey, 3); calibrationSelectorTab->addChangeListener(this); + TerpstraSysExApplication::getApp().getLumatoneController()->addFirmwareListener(this); + //[/UserPreSize] setSize (524, 212); @@ -79,6 +81,7 @@ CalibrationDlg::CalibrationDlg () CalibrationDlg::~CalibrationDlg() { //[Destructor_pre]. You can add your own custom destruction code here.. + TerpstraSysExApplication::getApp().getLumatoneController()->removeFirmwareListener(this); //[/Destructor_pre] btnStart = nullptr; @@ -87,6 +90,7 @@ CalibrationDlg::~CalibrationDlg() //[Destructor]. You can add your own custom destruction code here.. calibrationSelectorTab = nullptr; + wheelsCalibrationComponent = nullptr; //[/Destructor] } @@ -113,22 +117,45 @@ void CalibrationDlg::resized() //[/UserPreResize] //[UserResized] Add your own custom resize handling here.. - calibrationSelectorTab->setBounds(0, 0, getWidth() - generalRim, OCTAVEBOARDTABHEIGHT); + auto currentTab = calibrationSelectorTab->getCurrentTabIndex(); - instructionsBounds.setBounds( - generalRim, - calibrationSelectorTab->getBottom() + generalRim, - getWidth() - 2 * generalRim, - btnStart->getY() - calibrationSelectorTab->getBottom() - 2 * generalRim); - instructionsFont.setHeight(instructionsBounds.getHeight() * fontHeightInBounds); + calibrationSelectorTab->setBounds(0, 0, getWidth() - generalRim, OCTAVEBOARDTABHEIGHT); int buttonWidth = proportionOfWidth(0.3f); int buttonHeight = proportionOfHeight(0.125f); + btnStart->setSize(buttonWidth, buttonHeight); btnStart->setTopLeftPosition(generalRim, getHeight() - generalRim - buttonHeight); - btnStop->setBounds(btnStart->getBounds().withX(getWidth() - generalRim - buttonWidth)); + if (currentTab == calibrateModulationWheel && wheelsCalibrationComponent != nullptr) + { + int panelHeight = btnStart->getY() - calibrationSelectorTab->getBottom() - 2 * generalRim; + + wheelsCalibrationComponent->setBounds( + generalRim, + calibrationSelectorTab->getBottom() + generalRim, + proportionOfWidth(wheelsGraphicWidthScalar), + panelHeight); + + instructionsBounds.setBounds( + wheelsCalibrationComponent->getRight() + generalRim, + calibrationSelectorTab->getBottom() + generalRim, + getWidth() - wheelsCalibrationComponent->getRight() - 2 * generalRim, + panelHeight); + } + + else + { + instructionsBounds.setBounds( + generalRim, + calibrationSelectorTab->getBottom() + generalRim, + getWidth() - 2 * generalRim, + btnStart->getY() - calibrationSelectorTab->getBottom() - 2 * generalRim); + instructionsFont.setHeight(instructionsBounds.getHeight() * fontHeightInBounds); + } + + //[/UserResized] } @@ -146,19 +173,16 @@ void CalibrationDlg::buttonClicked (juce::Button* buttonThatWasClicked) switch (tabSelection) { case calibrateKeys: - TerpstraSysExApplication::getApp().getLumatoneController().startCalibrateKeys(); + TerpstraSysExApplication::getApp().getLumatoneController()->startCalibrateKeys(); break; case calibrateAftertouch: - TerpstraSysExApplication::getApp().getLumatoneController().startCalibrateAftertouch(); + TerpstraSysExApplication::getApp().getLumatoneController()->startCalibrateAftertouch(); break; case calibrateModulationWheel: - TerpstraSysExApplication::getApp().getLumatoneController().setCalibratePitchModWheel(true); - - // Todo - wait for ack - TerpstraSysExApplication::getApp().setCalibrationMode(true); - updateCalibrationStatus(); + TerpstraSysExApplication::getApp().getLumatoneController()->setCalibratePitchModWheel(true); + startCalibration = true; break; default: @@ -182,10 +206,8 @@ void CalibrationDlg::buttonClicked (juce::Button* buttonThatWasClicked) jassertfalse; break; case calibrateModulationWheel: - TerpstraSysExApplication::getApp().getLumatoneController().setCalibratePitchModWheel(false); - // Todo - wait for ack - TerpstraSysExApplication::getApp().setCalibrationMode(false); - updateCalibrationStatus(); + TerpstraSysExApplication::getApp().getLumatoneController()->setCalibratePitchModWheel(false); + startCalibration = false; break; default: jassertfalse; @@ -219,6 +241,7 @@ void CalibrationDlg::changeListenerCallback(ChangeBroadcaster *source) { if (source == calibrationSelectorTab.get()) { + wheelsCalibrationComponent = nullptr; btnStart->setEnabled(true); // Instructions depending on tab selection auto tabSelection = calibrationSelectorTab->getCurrentTabIndex(); @@ -230,7 +253,7 @@ void CalibrationDlg::changeListenerCallback(ChangeBroadcaster *source) << newLine << translate("To return to normal operating state, the five submodule boards must exit out calibration mode by pressing their corresponding macro buttons to save or cancel calibration."); btnStop->setVisible(false); - repaint(); + resized(); break; case calibrateAftertouch: @@ -239,7 +262,7 @@ void CalibrationDlg::changeListenerCallback(ChangeBroadcaster *source) << newLine << translate("To return to normal operating state, the five submodule boards must exit out calibration mode by pressing their corresponding macro buttons to save or cancel calibration."); btnStop->setVisible(false); - repaint(); + resized(); break; case calibrateModulationWheel: @@ -248,23 +271,60 @@ void CalibrationDlg::changeListenerCallback(ChangeBroadcaster *source) << newLine << translate("Click \'End calibration\' to stop."); btnStop->setVisible(true); - updateCalibrationStatus(); - repaint(); + setupWheelCalibrationLayout(); + updateWheelCalibrationStatus(); break; + default: jassertfalse; break; } + repaint(); + } +} + +void CalibrationDlg::setupWheelCalibrationLayout() +{ + FirmwareSupport support; + if (support.versionAcknowledgesCommand(TerpstraSysExApplication::getApp().getFirmwareVersion(), PERIPHERAL_CALBRATION_DATA)) + { + wheelsCalibrationComponent.reset(new WheelsCalibrationComponent()); + addAndMakeVisible(wheelsCalibrationComponent.get()); + resized(); } } -void CalibrationDlg::updateCalibrationStatus() + +void CalibrationDlg::updateWheelCalibrationStatus() { bool inCalibration = TerpstraSysExApplication::getApp().getInCalibrationMode(); btnStart->setEnabled(!inCalibration); btnStop->setEnabled(inCalibration); } +void CalibrationDlg::calibratePitchModWheelAnswer(TerpstraMIDIAnswerReturnCode code) +{ + if (code == TerpstraMIDIAnswerReturnCode::ACK) + { + if (startCalibration) + TerpstraSysExApplication::getApp().setCalibrationMode(true); + else + TerpstraSysExApplication::getApp().setCalibrationMode(false); + updateWheelCalibrationStatus(); + startCalibration = false; + } +} + +void CalibrationDlg::wheelsCalibrationDataReceived(WheelsCalibrationData calibrationData) +{ + if (wheelsCalibrationComponent != nullptr) + { + wheelsCalibrationComponent->updateCalibrationData(calibrationData); + } + //else + //jassertfalse; +} + //[/MiscUserCode] @@ -278,7 +338,7 @@ void CalibrationDlg::updateCalibrationStatus() BEGIN_JUCER_METADATA diff --git a/Source/Settings/CalibrationDlg.h b/Source/Settings/CalibrationDlg.h index 2c42f9bd..cc8c0c4d 100644 --- a/Source/Settings/CalibrationDlg.h +++ b/Source/Settings/CalibrationDlg.h @@ -7,7 +7,7 @@ the "//[xyz]" and "//[/xyz]" sections will be retained when the file is loaded and re-saved. - Created with Projucer version: 6.0.5 + Created with Projucer version: 6.0.8 ------------------------------------------------------------------------------ @@ -20,7 +20,8 @@ #pragma once //[Headers] -- You can add your own extra header files here -- -#include +#include "../LumatoneController.h" +#include "WheelsCalibrationComponent.h" //[/Headers] @@ -35,6 +36,7 @@ */ class CalibrationDlg : public juce::Component, public ChangeListener, + public LumatoneEditor::FirmwareListener, public juce::Button::Listener { public: @@ -48,7 +50,17 @@ class CalibrationDlg : public juce::Component, // Implementation of ChangeListener void changeListenerCallback(ChangeBroadcaster *source) override; - void updateCalibrationStatus(); + void setupWheelCalibrationLayout(); + void updateWheelCalibrationStatus(); + + //============================================================================== + // LumatoneEditor::FirmwareListener Implementation + + void calibratePitchModWheelAnswer(TerpstraMIDIAnswerReturnCode code) override; + + void wheelsCalibrationDataReceived(WheelsCalibrationData calibrationData) override; + + //[/UserMethods] void paint (juce::Graphics& g) override; @@ -68,6 +80,9 @@ class CalibrationDlg : public juce::Component, }; std::unique_ptr calibrationSelectorTab; + std::unique_ptr wheelsCalibrationComponent; + + bool startCalibration = false; String instructionText; @@ -77,6 +92,8 @@ class CalibrationDlg : public juce::Component, const float fontHeightInBounds = 0.125f; const int generalRim = 12; + const float wheelsGraphicWidthScalar = 0.15f; + //[/UserVariables] //============================================================================== diff --git a/Source/Settings/FirmwareDlg.cpp b/Source/Settings/FirmwareDlg.cpp index 38ef9573..ba284f2e 100644 --- a/Source/Settings/FirmwareDlg.cpp +++ b/Source/Settings/FirmwareDlg.cpp @@ -21,7 +21,14 @@ FirmwareDlg::FirmwareDlg() auto properties = TerpstraSysExApplication::getApp().getPropertiesFile(); File lastFirmwareLocation = properties->getValue("LastFirmwareBinPath", properties->getValue("UserDocumentsLocation", File::getSpecialLocation(File::SpecialLocationType::userDocumentsDirectory).getFullPathName())); - fileBrowser.reset(new PathBrowserComponent("Lumatone Firmware Update", lastFirmwareLocation)); + String openFileType = +#if JUCE_DEBUG + "" +#else + "*.tgz" +#endif + ; + fileBrowser.reset(new PathBrowserComponent("Lumatone Firmware Update", openFileType, lastFirmwareLocation)); fileBrowser->getEditor()->setColour(TextEditor::ColourIds::backgroundColourId, TerpstraSysExApplication::getApp().getLookAndFeel().findColour(LumatoneEditorColourIDs::ControlBoxBackground)); fileBrowser->getEditor()->setColour(TextEditor::ColourIds::textColourId, TerpstraSysExApplication::getApp().getLookAndFeel().findColour(LumatoneEditorColourIDs::DescriptionText)); fileBrowser->getEditor()->getProperties().set(LumatoneEditorStyleIDs::connectedEdgeFlags, Button::ConnectedEdgeFlags::ConnectedOnRight); @@ -37,7 +44,6 @@ FirmwareDlg::FirmwareDlg() infoBox->setMouseClickGrabsKeyboardFocus(false); infoBox->setReadOnly(true); infoBox->setMultiLine(true); - infoBox->insertTextAtCaret(translate("Select a firmware file and then click \"Begin Update\"")); infoBox->setColour(TextEditor::ColourIds::backgroundColourId, TerpstraSysExApplication::getApp().getLookAndFeel().findColour(LumatoneEditorColourIDs::ControlBoxBackground)); infoBox->setColour(TextEditor::ColourIds::textColourId, TerpstraSysExApplication::getApp().getLookAndFeel().findColour(LumatoneEditorColourIDs::DescriptionText)); infoBox->setColour(ScrollBar::ColourIds::thumbColourId, Colour(0xff2d3135)); @@ -48,7 +54,9 @@ FirmwareDlg::FirmwareDlg() addAndMakeVisible(firmwareStatusLabel.get()); updateFirmwareVersionLabel(); - TerpstraSysExApplication::getApp().getLumatoneController().addFirmwareListener(this); + TerpstraSysExApplication::getApp().getLumatoneController()->addFirmwareListener(this); + + postMessage(translate("Select a firmware file and then click \"Begin Update\"")); //if (!updateIsAvailable) //{ @@ -58,7 +66,7 @@ FirmwareDlg::FirmwareDlg() FirmwareDlg::~FirmwareDlg() { - TerpstraSysExApplication::getApp().getLumatoneController().removeFirmwareListener(this); + TerpstraSysExApplication::getApp().getLumatoneController()->removeFirmwareListener(this); } void FirmwareDlg::paint(Graphics& g) @@ -68,10 +76,7 @@ void FirmwareDlg::paint(Graphics& g) void FirmwareDlg::resized() { - int margin = 12; - int doubleMargin = margin * 2; - int buttonWidth = proportionOfWidth(0.3f); - int buttonHeight = 30; + int buttonWidth = proportionOfWidth(buttonWidthScalar); //checkUpdateBtn->setBounds(margin, margin, buttonWidth, buttonHeight); @@ -92,9 +97,9 @@ void FirmwareDlg::buttonClicked(Button* btn) //} if (btn == doUpdateBtn.get()) { - if (TerpstraSysExApplication::getApp().getLumatoneController().getMidiInputIndex() < 0 || TerpstraSysExApplication::getApp().getLumatoneController().getMidiOutputIndex() < 0) + if (TerpstraSysExApplication::getApp().getLumatoneController()->getMidiInputIndex() < 0 || TerpstraSysExApplication::getApp().getLumatoneController()->getMidiOutputIndex() < 0) { - AlertWindow::showMessageBox(AlertWindow::AlertIconType::NoIcon, "Not connected", "Please connect the Lumatone via USB before performing a firmware update.", "Ok", this); + AlertWindow::showMessageBoxAsync(AlertWindow::AlertIconType::NoIcon, "Not connected", "Please connect the Lumatone via USB before performing a firmware update.", "Ok", this); return; } @@ -103,11 +108,12 @@ void FirmwareDlg::buttonClicked(Button* btn) if (firmwareFileSelected.existsAsFile()) { TerpstraSysExApplication::getApp().getPropertiesFile()->setValue("LastFirmwareBinPath", firmwareFileSelected.getParentDirectory().getFullPathName()); - TerpstraSysExApplication::getApp().getLumatoneController().requestFirmwareUpdate(firmwareFileSelected, this); + TerpstraSysExApplication::getApp().getLumatoneController()->requestFirmwareUpdate(firmwareFileSelected, this); } else { - infoBox->setText(infoBox->getText() + "Error: Not a valid firmware file..."); + postMessage("Error: Not a valid firmware file..."); + //infoBox->setText(); } } } @@ -138,11 +144,9 @@ void FirmwareDlg::firmwareTransferUpdate(FirmwareTransfer::StatusCode statusCode firmwareUpdateInProgress = false; } - if (msg!= "") + if (msg != "") { - msgLog += (msg + "\n"); - infoNeedsUpdate = true; - startTimer(infoUpdateTimeoutMs); + postMessage(msg); } } @@ -158,7 +162,7 @@ void FirmwareDlg::timerCallback() void FirmwareDlg::firmwareRevisionReceived(FirmwareVersion version) { updateFirmwareVersionLabel(); - postMessage("Firmware update complete! Lumatone is now running firmware version " + version.toString()); + postMessage("Firmware update complete! Lumatone is now running firmware version " + version.toDisplayString()); } void FirmwareDlg::postMessage(String msg) @@ -166,4 +170,4 @@ void FirmwareDlg::postMessage(String msg) msgLog += msg + '\n'; infoNeedsUpdate = true; startTimer(infoUpdateTimeoutMs); -} \ No newline at end of file +} diff --git a/Source/Settings/FirmwareDlg.h b/Source/Settings/FirmwareDlg.h index 677932e4..64993aab 100644 --- a/Source/Settings/FirmwareDlg.h +++ b/Source/Settings/FirmwareDlg.h @@ -18,7 +18,7 @@ class FirmwareDlg : public Component, protected Button::Listener, protected PathBrowserComponent::Listener, protected FirmwareTransfer::ProcessListener, - protected LumatoneController::FirmwareListener, + protected LumatoneEditor::FirmwareListener, private Timer { public: @@ -45,7 +45,7 @@ class FirmwareDlg : public Component, void firmwareTransferUpdate(FirmwareTransfer::StatusCode statusCode, String msg) override; //========================================================================= - // LumatoneController::FirmwareListener implementation + // LumatoneEditor::FirmwareListener implementation void firmwareRevisionReceived(FirmwareVersion version) override; @@ -70,4 +70,10 @@ class FirmwareDlg : public Component, String msgLog; bool infoNeedsUpdate = false; const int infoUpdateTimeoutMs = 100; + + // Style helpers + int margin = 12; + int doubleMargin = margin * 2; + float buttonWidthScalar = 0.3f; + int buttonHeight = 30; }; diff --git a/Source/Settings/MidiSettingsDlg.cpp b/Source/Settings/MidiSettingsDlg.cpp new file mode 100644 index 00000000..ddd01c71 --- /dev/null +++ b/Source/Settings/MidiSettingsDlg.cpp @@ -0,0 +1,176 @@ +/* + ============================================================================== + + MidiSettingsDlg.cpp + Created: 29 Jun 2021 10:02:43pm + Author: Vincenzo + + ============================================================================== +*/ + +#include "MidiSettingsDlg.h" +#include "../Main.h" + +MidiSettingsDlg::MidiSettingsDlg() +{ + setMidiChannelHeader.reset(new Label("SetMidiChannelHeader", "Set Controller MIDI Channel")); + setMidiChannelHeader->setJustificationType(Justification::centred); + setMidiChannelHeader->setFont(TerpstraSysExApplication::getApp().getAppFont(LumatoneEditorFont::UniviaPro)); + addAndMakeVisible(setMidiChannelHeader.get()); + + controlLabelFont = TerpstraSysExApplication::getApp().getAppFont(LumatoneEditorFont::GothamNarrowMedium); + + for (auto controlName : ControlNames) + { + auto lbl = setMidiChannelLabels.add(new Label(controlName + " Label", controlName + ": ")); + lbl->setJustificationType(Justification::centredRight); + lbl->setFont(controlLabelFont); + addAndMakeVisible(*lbl); + + auto sld = setMidiChannelSliders.add(new Slider(Slider::SliderStyle::IncDecButtons, Slider::TextEntryBoxPosition::TextBoxLeft)); + sld->setRange(1, 16, 1); + sld->setName(controlName); + sld->setTooltip("Set MIDI Channel for " + controlName + " messages."); + sld->addListener(this); + addAndMakeVisible(*sld); + + // Help with resizing + if (controlName.length() > longestControlName.length()) + longestControlName = controlName; + } + + flexBox.flexWrap = FlexBox::Wrap::noWrap; + flexBox.flexDirection = FlexBox::Direction::column; + flexBox.justifyContent = FlexBox::JustifyContent::flexStart; + flexBox.alignContent = FlexBox::AlignContent::flexStart; + + TerpstraSysExApplication::getApp().getLumatoneController()->addFirmwareListener(this); + + setSupportedControls(TerpstraSysExApplication::getApp().getFirmwareVersion()); +} + +MidiSettingsDlg::~MidiSettingsDlg() +{ + // Not good when app closes with this window open... + TerpstraSysExApplication::getApp().getLumatoneController()->removeFirmwareListener(this); + + setMidiChannelHeader = nullptr; + setMidiChannelLabels.clear(); + setMidiChannelSliders.clear(); +} + +void MidiSettingsDlg::paint(Graphics& g) +{ + +} + +void MidiSettingsDlg::resized() +{ + int rowHeight = proportionOfHeight(fontHeightInBounds); + + auto setMidiChannelControlBounds = getLocalBounds().withTrimmedTop(rowHeight + margin * 2); + + controlLabelFont.setHeight(rowHeight); + int labelWidth = controlLabelFont.getStringWidth(longestControlName); + int sldWidth = labelWidth * 0.667f; + + flexRows.clear(); + flexBox.items.clear(); + + auto controlMargin = FlexItem::Margin(0, margin * 0.5f, 0, 0); + int toggleMargins = margin * 0.2f; + + for (int i = 0; i < ControlNames.size(); i++) + { + flexRows.add(FlexBox( + FlexBox::Direction::row, + FlexBox::Wrap::noWrap, + FlexBox::AlignContent::center, + FlexBox::AlignItems::center, + FlexBox::JustifyContent::center + )); + + FlexBox& flexRow = flexRows.getReference(i); + + auto lbl = setMidiChannelLabels[i]; + auto lblItem = FlexItem(labelWidth, rowHeight, *lbl).withMargin(controlMargin); + flexRow.items.add(lblItem); + + auto sld = setMidiChannelSliders[i]; + sld->setTextBoxStyle(Slider::TextEntryBoxPosition::TextBoxLeft, false, sldWidth * 0.5f, rowHeight); + auto sldItem = FlexItem(labelWidth, rowHeight, *sld).withMargin(controlMargin); + flexRow.items.add(sldItem); + + auto rowItem = FlexItem(getWidth(), rowHeight); + rowItem.associatedFlexBox = &flexRow; + rowItem.margin = FlexItem::Margin(0, 0, margin, 0); + + flexBox.items.add(rowItem); + } + + flexBox.performLayout(setMidiChannelControlBounds); + + setMidiChannelHeader->setBounds(setMidiChannelControlBounds.withY(margin).withHeight(rowHeight)); +} + +void MidiSettingsDlg::sliderValueChanged(Slider* sld) +{ + int controlIndex = setMidiChannelSliders.indexOf(sld); + + if (controlIndex >= 0 && controlIndex < ControlNames.size()) + { + channelSettings.setChannel((PeripheralChannel)controlIndex, sld->getValue()); + sendChannelSettings(); + } +} + +void MidiSettingsDlg::setSupportedControls(FirmwareVersion version) +{ + bool setChannelsEnabled = false; + + FirmwareSupport support; + if (support.versionAcknowledgesCommand(version, SET_PERIPHERAL_CHANNELS)) + { + setChannelsEnabled = true; + TerpstraSysExApplication::getApp().getLumatoneController()->getPeripheralChannels(); + } + + for (int i = 0; i < ControlNames.size(); i++) + { + setMidiChannelSliders[i]->setEnabled(setChannelsEnabled); + + if (!setChannelsEnabled) + { + setMidiChannelSliders[i]->setTooltip(translate("This feature is not supported by your Lumatone's firmware version.")); + } + } +} + +void MidiSettingsDlg::updateChannelSettings(PeripheralChannelSettings channelSettingsIn) +{ + channelSettings = channelSettingsIn; + + for (int i = 0; i < ControlNames.size(); i++) + { + auto channel = channelSettings.getChannel((PeripheralChannel)i); + setMidiChannelSliders[i]->setValue(channel, dontSendNotification); + } +} + +void MidiSettingsDlg::sendChannelSettings() +{ + TerpstraSysExApplication::getApp().getLumatoneController()->setPeripheralChannels(channelSettings); +} + +//========================================================================= +// LumatoneEditor::FirmwareListener implementation + +void MidiSettingsDlg::firmwareRevisionReceived(FirmwareVersion version) +{ + setSupportedControls(version); +} + +void MidiSettingsDlg::peripheralMidiChannelsReceived(PeripheralChannelSettings channelSettings) +{ + updateChannelSettings(channelSettings); +} diff --git a/Source/Settings/MidiSettingsDlg.h b/Source/Settings/MidiSettingsDlg.h new file mode 100644 index 00000000..4f847afe --- /dev/null +++ b/Source/Settings/MidiSettingsDlg.h @@ -0,0 +1,75 @@ +/* + ============================================================================== + + MidiSettingsDlg.h + Created: 29 Jun 2021 10:02:43pm + Author: Vincenzo + + ============================================================================== +*/ + +#pragma once + +#include "../LumatoneController.h" + +class MidiSettingsDlg : public Component, + protected Slider::Listener, + protected LumatoneEditor::FirmwareListener +{ +public: + + MidiSettingsDlg(); + ~MidiSettingsDlg(); + + void paint(Graphics & g) override; + + void resized() override; + + void sliderValueChanged(Slider* sld) override; + + void setSupportedControls(FirmwareVersion version); + + void updateChannelSettings(PeripheralChannelSettings channelSettings); + + void sendChannelSettings(); + + //========================================================================= + // LumatoneEditor::FirmwareListener implementation + + void firmwareRevisionReceived(FirmwareVersion version) override; + + void peripheralMidiChannelsReceived(PeripheralChannelSettings channelSettings) override; + + + +private: + //========================================================================= + + const StringArray ControlNames = + { + "Pitch Wheel", + "Mod Wheel", + "Expression Pedal", + "Sustain Pedal" + }; + +private: + + std::unique_ptr