├── .gitattributes ├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── LICENCE.txt ├── README.md ├── include └── Kaixo │ ├── SpectralRotator │ ├── Controller.hpp │ ├── Gui │ │ ├── EditorView.hpp │ │ ├── FileDragHandle.hpp │ │ ├── MainView.hpp │ │ ├── NonAudioLoadPopupView.hpp │ │ ├── NotificationPopupView.hpp │ │ ├── SettingsView.hpp │ │ ├── SpectralFileViewer.hpp │ │ └── SpectralViewer.hpp │ └── Processing │ │ ├── FileHandler.hpp │ │ ├── Interfaces │ │ ├── EditorInterface.hpp │ │ └── FileInterface.hpp │ │ ├── Processor.hpp │ │ ├── Rotator.hpp │ │ ├── SpectralEditor.hpp │ │ └── Utils │ │ ├── AudioBuffer.hpp │ │ ├── AudioBufferSpectralInformation.hpp │ │ ├── AudioFrame.hpp │ │ ├── ComplexBuffer.hpp │ │ ├── Fft.hpp │ │ └── Resampler.hpp │ └── Utils │ ├── AudioFile.hpp │ └── Decoders │ ├── Decoder.hpp │ ├── mp3.hpp │ └── wav.hpp ├── resources └── Parameters.xml ├── source └── Kaixo │ ├── SpectralRotator │ ├── Controller.cpp │ ├── Gui │ │ ├── EditorView.cpp │ │ ├── FileDragHandle.cpp │ │ ├── MainView.cpp │ │ ├── NonAudioLoadPopupView.cpp │ │ ├── NotificationPopupView.cpp │ │ ├── SettingsView.cpp │ │ ├── SpectralFileViewer.cpp │ │ └── SpectralViewer.cpp │ └── Processing │ │ ├── FileHandler.cpp │ │ ├── Interfaces │ │ ├── EditorInterface.cpp │ │ └── FileInterface.cpp │ │ ├── Processor.cpp │ │ ├── Rotator.cpp │ │ ├── SpectralEditor.cpp │ │ └── Utils │ │ ├── AudioBufferSpectralInformation.cpp │ │ ├── Fft.cpp │ │ └── Resampler.cpp │ └── Utils │ └── AudioFile.cpp └── theme ├── background.png ├── flip.png ├── map.json ├── map.png ├── open-folder.png ├── reverse.png ├── rotate-270.png ├── rotate-90.png ├── rotate-background.png ├── schema.json ├── settings-background.png ├── settings.png ├── source-background.png └── theme.json /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | build/ 2 | bin/ 3 | generated/ -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "core"] 2 | path = core 3 | url = https://github.com/KaixoCode/SynthCore.git 4 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | 2 | # ============================================== 3 | 4 | cmake_minimum_required(VERSION 3.15) 5 | 6 | # ============================================== 7 | 8 | set(NAME "SpectralRotator") 9 | set(CODE "Sprt") 10 | set(COMPANY "Kaixo") 11 | set(COMPANY_CODE "Kaix") 12 | set(WEBSITE "https://kaixo.me") 13 | set(BUNDLE_IDENTIFIER "me.kaixo.spectralrotator") 14 | set(VERSION_TYPE "SNAPSHOT") 15 | set(VERSION "1.1.1") 16 | set(INITIAL_SIZE "500, 500") 17 | set(IS_SYNTH true) 18 | 19 | # ============================================== 20 | 21 | set(THEME "theme/theme.json") 22 | set(THEME_SCHEMA "theme/schema.json") 23 | set(PARAMETERS "resources/Parameters.xml") 24 | 25 | # ============================================== 26 | 27 | set (CMAKE_CXX_STANDARD 23) 28 | 29 | project(${NAME} VERSION ${VERSION}) 30 | 31 | add_subdirectory(core) 32 | 33 | # ============================================== 34 | -------------------------------------------------------------------------------- /LICENCE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2024-2024 Kaixo 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining 4 | a copy of this software and associated documentation files (the 5 | "Software"), to deal in the Software without restriction, including 6 | without limitation the rights to use, copy, modify, merge, publish, 7 | distribute, sublicense, and/or sell copies of the Software, and to 8 | permit persons to whom the Software is furnished to do so, subject to 9 | the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be 12 | included in all copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 15 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 16 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 17 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 18 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 19 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 20 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SpectralRotator 2 | SpectralRotator is a VST plugin that can rotate the frequency spectrum of any sound. It is an offline processor, which means it does not work on live audio. Instead, you drop a sample into its UI, rotate its frequency spectrum in any direction, and then drag the result back into the DAW. 3 | 4 | 5 | 6 | ## Installation Guide 7 | Installing this VST is very straightforward. Go to the [Releases](https://github.com/KaixoCode/SpectralRotator/releases) tab, select the latest release, and simply download the VST3 file. You then put this VST3 file into your VST3 folder, it is usually located somewhere like `C:\Program Files\Common Files\VST3`. 8 | 9 | Unfortunately, I do not have access to a Mac OS device to compile it for Mac, so **there is currently only a Windows version available**. 10 | 11 | ## How To Use The Plugin 12 | When you open the plugin you are faced with two simple samplers: *source* and *rotate*. Start by dropping a sample into the *source* sampler, this will load the sample in both *source* and *rotate*. You can then press the buttons on the *rotate* sampler to rotate the frequency spectrum into any orientation. Once you've rotated the sample, you can drag the result back into your DAW by hovering over the "rotate" text and dragging. 13 | 14 | ![rotated sample](https://assets.kaixo.me/SpectralRotator/rotated-sample-ui-1.1.0.png) 15 | 16 | After you've applied effects to your rotated sample, you can drop it back into the *rotate* sampler. Make sure you still have the original source sample in the *source* sampler. This is important because we need to know how long the original sample was to rotate it back properly, as the length of your edited sample might have changed. Once the edited sample has loaded, you can rotate it back to its original orientation, and drag it back into your DAW. 17 | 18 | You can also listen to the loaded samples by clicking on the spectrum view, and pressing space bar. This will play/pause playback of this sampler. You can skip to any position in the sample by clicking anywhere on the spectrum view. 19 | 20 | Any samples you've dragged out of this plugin are stored in the output folder, which defaults to a folder somewhere in the user's application data directory. You can find the exact path by clicking on the settings icon and looking at the "output folder". You can change this path by clicking on it and selecting a different folder. Clicking on the icon to the right of the path opens the folder in the default file explorer. You can close the settings by clicking on the settings icon again. 21 | 22 | ![settings](https://assets.kaixo.me/SpectralRotator/settings-ui-1.1.0.png) 23 | 24 | The spectrum view is also fully customizable, you can adjust the FFT size, resolution, block size, and range. The FFT size changes the vertical resolution, the FFT resolution changes the horizontal resolution, the block size determines the length of the block that is used for every FFT, and the FFT range adjusts how many decibels are visible. 25 | 26 | **An important note** when dropping modified rotated samples back into SpectralRotator is that you align the bottom of your spectrum properly. For example, when you have rotated your spectrum such that the lower frequencies are on the left, you must make sure when you import your edited sample, that the start of your sample is aligned (If it was rotated such that the lower frequencies are on the right, you would have to align the end of your sample). Otherwise a small frequency shift will occur, depending on how many audio samples of delay there are. You do not have to worry about making sure the sample is exactly the same length though, as it will take this information from the *source* sampler. 27 | 28 | ## Questions 29 | If you have any questions or suggestions about this plugin you can contact me on Discord `@kaixo`. 30 | -------------------------------------------------------------------------------- /include/Kaixo/SpectralRotator/Controller.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // ------------------------------------------------ 4 | 5 | #include "Kaixo/Core/Controller.hpp" 6 | 7 | // ------------------------------------------------ 8 | 9 | namespace Kaixo { 10 | 11 | // ------------------------------------------------ 12 | 13 | class SpectralRotatorController : public Controller { 14 | public: 15 | 16 | // ------------------------------------------------ 17 | 18 | SpectralRotatorController(); 19 | 20 | // ------------------------------------------------ 21 | 22 | }; 23 | 24 | // ------------------------------------------------ 25 | 26 | } 27 | 28 | // ------------------------------------------------ 29 | -------------------------------------------------------------------------------- /include/Kaixo/SpectralRotator/Gui/EditorView.hpp: -------------------------------------------------------------------------------- 1 | 2 | // ------------------------------------------------ 3 | 4 | #pragma once 5 | 6 | // ------------------------------------------------ 7 | 8 | #include "Kaixo/Core/Theme/Theme.hpp" 9 | #include "Kaixo/Core/Gui/View.hpp" 10 | #include "Kaixo/Core/Gui/Views/ScrollView.hpp" 11 | #include "Kaixo/Core/Gui/Views/ImageView.hpp" 12 | 13 | // ------------------------------------------------ 14 | 15 | #include "Kaixo/Utils/AudioFile.hpp" 16 | #include "Kaixo/SpectralRotator/Gui/SpectralViewer.hpp" 17 | #include "Kaixo/SpectralRotator/Gui/SpectralFileViewer.hpp" 18 | #include "Kaixo/SpectralRotator/Processing/Interfaces/EditorInterface.hpp" 19 | 20 | // ------------------------------------------------ 21 | 22 | namespace Kaixo::Gui { 23 | 24 | // ------------------------------------------------ 25 | 26 | class EditorViewLayer : public View { 27 | public: 28 | 29 | // ------------------------------------------------ 30 | 31 | struct Settings { 32 | 33 | // ------------------------------------------------ 34 | 35 | std::size_t index; 36 | 37 | // ------------------------------------------------ 38 | 39 | } settings; 40 | 41 | // ------------------------------------------------ 42 | 43 | EditorViewLayer(Context c, Settings s); 44 | 45 | // ------------------------------------------------ 46 | 47 | }; 48 | 49 | // ------------------------------------------------ 50 | 51 | class SpectralEditor : public View { 52 | public: 53 | 54 | // ------------------------------------------------ 55 | 56 | struct Settings { 57 | 58 | // ------------------------------------------------ 59 | 60 | Processing::InterfaceStorage editor; 61 | 62 | // ------------------------------------------------ 63 | 64 | } settings; 65 | 66 | // ------------------------------------------------ 67 | 68 | SpectralEditor(Context c, Settings s); 69 | 70 | // ------------------------------------------------ 71 | 72 | void mouseDown(const juce::MouseEvent& event) override; 73 | void mouseUp(const juce::MouseEvent& event) override; 74 | void mouseDrag(const juce::MouseEvent& event) override; 75 | 76 | // ------------------------------------------------ 77 | 78 | void mouseWheelMove(const juce::MouseEvent& event, const juce::MouseWheelDetails& wheel); 79 | 80 | // ------------------------------------------------ 81 | 82 | void paintOverChildren(juce::Graphics& g) override; 83 | 84 | // ------------------------------------------------ 85 | 86 | void onIdle() override; 87 | 88 | // ------------------------------------------------ 89 | 90 | enum class State { 91 | Waiting, Selecting, Moving, Child, Brush 92 | } state = State::Selecting; 93 | 94 | // ------------------------------------------------ 95 | 96 | SpectralViewer* spectralViewer; 97 | 98 | // ------------------------------------------------ 99 | 100 | Point dragStart; 101 | Point dragEnd; 102 | Point moved; 103 | 104 | Rect<> selectedRect(); 105 | 106 | void move(Point amount, bool remove = true); 107 | 108 | // ------------------------------------------------ 109 | 110 | std::future editFuture; 111 | 112 | // ------------------------------------------------ 113 | 114 | }; 115 | 116 | // ------------------------------------------------ 117 | 118 | class EditorView : public View, public FileDropTarget { 119 | public: 120 | 121 | // ------------------------------------------------ 122 | 123 | struct Settings { 124 | 125 | // ------------------------------------------------ 126 | 127 | } settings; 128 | 129 | // ------------------------------------------------ 130 | 131 | EditorView(Context c, Settings s); 132 | 133 | // ------------------------------------------------ 134 | 135 | void tryingToOpenFile() override; 136 | void fileOpened(FileLoadStatus status) override; 137 | 138 | // ------------------------------------------------ 139 | 140 | bool keyPressed(const juce::KeyPress& event); 141 | 142 | // ------------------------------------------------ 143 | 144 | void onIdle() override; 145 | 146 | // ------------------------------------------------ 147 | 148 | SpectralViewer& spectralViewer() { return *spectralEditor->spectralViewer; } 149 | 150 | // ------------------------------------------------ 151 | 152 | SpectralEditor* spectralEditor; 153 | 154 | // ------------------------------------------------ 155 | 156 | private: 157 | std::vector m_Layers{}; 158 | ScrollView* m_LayersScrollView; 159 | 160 | // ------------------------------------------------ 161 | 162 | void addLayer(); 163 | 164 | // ------------------------------------------------ 165 | 166 | }; 167 | 168 | // ------------------------------------------------ 169 | 170 | } 171 | 172 | // ------------------------------------------------ 173 | -------------------------------------------------------------------------------- /include/Kaixo/SpectralRotator/Gui/FileDragHandle.hpp: -------------------------------------------------------------------------------- 1 | 2 | // ------------------------------------------------ 3 | 4 | #pragma once 5 | 6 | // ------------------------------------------------ 7 | 8 | #include "Kaixo/Core/Definitions.hpp" 9 | #include "Kaixo/Core/Gui/View.hpp" 10 | 11 | // ------------------------------------------------ 12 | 13 | #include "Kaixo/SpectralRotator/Processing/Interfaces/FileInterface.hpp" 14 | 15 | // ------------------------------------------------ 16 | 17 | namespace Kaixo::Gui { 18 | 19 | // ------------------------------------------------ 20 | 21 | class FileDragHandle : public View { 22 | public: 23 | 24 | // ------------------------------------------------ 25 | 26 | struct Settings { 27 | 28 | // ------------------------------------------------ 29 | 30 | Processing::InterfaceStorage file; 31 | 32 | // ------------------------------------------------ 33 | 34 | } settings; 35 | 36 | // ------------------------------------------------ 37 | 38 | FileDragHandle(Context c, Settings s); 39 | 40 | // ------------------------------------------------ 41 | 42 | void mouseDown(const juce::MouseEvent& event) override; 43 | 44 | // ------------------------------------------------ 45 | 46 | }; 47 | 48 | // ------------------------------------------------ 49 | 50 | } 51 | 52 | // ------------------------------------------------ 53 | -------------------------------------------------------------------------------- /include/Kaixo/SpectralRotator/Gui/MainView.hpp: -------------------------------------------------------------------------------- 1 | 2 | // ------------------------------------------------ 3 | 4 | #pragma once 5 | 6 | // ------------------------------------------------ 7 | 8 | #include "Kaixo/Core/Definitions.hpp" 9 | #include "Kaixo/Core/Gui/View.hpp" 10 | 11 | // ------------------------------------------------ 12 | 13 | #include "Kaixo/SpectralRotator/Processing/Interfaces/FileInterface.hpp" 14 | 15 | // ------------------------------------------------ 16 | 17 | namespace Kaixo::Gui { 18 | 19 | // ------------------------------------------------ 20 | 21 | class MainView : public View { 22 | public: 23 | 24 | // ------------------------------------------------ 25 | 26 | MainView(Context c); 27 | 28 | // ------------------------------------------------ 29 | 30 | void resized() override; 31 | 32 | // ------------------------------------------------ 33 | 34 | Processing::InterfaceStorage inputFileInterface; 35 | Processing::InterfaceStorage rotatedFileInterface; 36 | 37 | // ------------------------------------------------ 38 | 39 | private: 40 | constexpr static std::size_t DefaultUI = 1; 41 | constexpr static std::size_t SettingsOnTheSideUI = 2; 42 | 43 | std::size_t m_UIType = 0; 44 | 45 | std::function m_ResizedCallback; 46 | 47 | // ------------------------------------------------ 48 | 49 | }; 50 | 51 | // ------------------------------------------------ 52 | 53 | } 54 | 55 | // ------------------------------------------------ 56 | -------------------------------------------------------------------------------- /include/Kaixo/SpectralRotator/Gui/NonAudioLoadPopupView.hpp: -------------------------------------------------------------------------------- 1 | 2 | // ------------------------------------------------ 3 | 4 | #pragma once 5 | 6 | // ------------------------------------------------ 7 | 8 | #include "Kaixo/Core/Definitions.hpp" 9 | #include "Kaixo/Core/Gui/View.hpp" 10 | #include "Kaixo/Core/Gui/Knob.hpp" 11 | 12 | // ------------------------------------------------ 13 | 14 | namespace Kaixo::Gui { 15 | 16 | // ------------------------------------------------ 17 | 18 | class NonAudioLoadPopupView : public View { 19 | public: 20 | 21 | // ------------------------------------------------ 22 | 23 | NonAudioLoadPopupView(Context c); 24 | 25 | // ------------------------------------------------ 26 | 27 | void open(std::function c); 28 | 29 | // ------------------------------------------------ 30 | 31 | private: 32 | std::function m_Callback; 33 | std::size_t m_Requests = 0; 34 | Knob* m_BitDepth; 35 | Knob* m_SampleRate; 36 | 37 | // ------------------------------------------------ 38 | 39 | }; 40 | 41 | // ------------------------------------------------ 42 | 43 | } 44 | 45 | // ------------------------------------------------ 46 | -------------------------------------------------------------------------------- /include/Kaixo/SpectralRotator/Gui/NotificationPopupView.hpp: -------------------------------------------------------------------------------- 1 | 2 | // ------------------------------------------------ 3 | 4 | #pragma once 5 | 6 | // ------------------------------------------------ 7 | 8 | #include "Kaixo/Core/Definitions.hpp" 9 | #include "Kaixo/Core/Gui/View.hpp" 10 | #include "Kaixo/Core/Gui/Views/TextView.hpp" 11 | 12 | // ------------------------------------------------ 13 | 14 | namespace Kaixo::Gui { 15 | 16 | // ------------------------------------------------ 17 | 18 | class NotificationPopupView : public View { 19 | public: 20 | 21 | // ------------------------------------------------ 22 | 23 | NotificationPopupView(Context c); 24 | 25 | // ------------------------------------------------ 26 | 27 | void open(std::string_view text); 28 | 29 | // ------------------------------------------------ 30 | 31 | private: 32 | std::size_t m_Requests = 0; 33 | TextView* m_Message; 34 | 35 | // ------------------------------------------------ 36 | 37 | }; 38 | 39 | // ------------------------------------------------ 40 | 41 | } 42 | 43 | // ------------------------------------------------ 44 | -------------------------------------------------------------------------------- /include/Kaixo/SpectralRotator/Gui/SettingsView.hpp: -------------------------------------------------------------------------------- 1 | 2 | // ------------------------------------------------ 3 | 4 | #pragma once 5 | 6 | // ------------------------------------------------ 7 | 8 | #include "Kaixo/Core/Definitions.hpp" 9 | #include "Kaixo/Core/Gui/View.hpp" 10 | #include "Kaixo/Core/Theme/Theme.hpp" 11 | 12 | // ------------------------------------------------ 13 | 14 | namespace Kaixo::Gui { 15 | 16 | // ------------------------------------------------ 17 | 18 | class SettingsView : public View { 19 | public: 20 | 21 | // ------------------------------------------------ 22 | 23 | class Entry : public View { 24 | public: 25 | 26 | // ------------------------------------------------ 27 | 28 | struct Settings { 29 | 30 | // ------------------------------------------------ 31 | 32 | std::string name; 33 | std::string value; 34 | 35 | // ------------------------------------------------ 36 | 37 | std::function click; 38 | 39 | // ------------------------------------------------ 40 | 41 | Theme::Drawable graphics = T.settings.entry; 42 | 43 | // ------------------------------------------------ 44 | 45 | } settings; 46 | 47 | // ------------------------------------------------ 48 | 49 | Entry(Context c, Settings s); 50 | 51 | // ------------------------------------------------ 52 | 53 | void mouseDown(const juce::MouseEvent& event) override; 54 | 55 | // ------------------------------------------------ 56 | 57 | void paint(juce::Graphics& g) override; 58 | 59 | // ------------------------------------------------ 60 | 61 | void changeValue(std::string_view val) { settings.value = val; repaint(); } 62 | 63 | // ------------------------------------------------ 64 | 65 | }; 66 | 67 | // ------------------------------------------------ 68 | 69 | struct Settings { 70 | 71 | // ------------------------------------------------ 72 | 73 | std::function fftSizeChanged; 74 | std::function fftResolutionChanged; 75 | std::function fftBlockSizeChanged; 76 | std::function fftDbDepthChanged; 77 | 78 | // ------------------------------------------------ 79 | 80 | } settings; 81 | 82 | // ------------------------------------------------ 83 | 84 | SettingsView(Context c, Settings s); 85 | 86 | // ------------------------------------------------ 87 | 88 | std::unique_ptr generationDirectoryChooser{}; 89 | 90 | // ------------------------------------------------ 91 | 92 | }; 93 | 94 | // ------------------------------------------------ 95 | 96 | } 97 | 98 | // ------------------------------------------------ 99 | -------------------------------------------------------------------------------- /include/Kaixo/SpectralRotator/Gui/SpectralFileViewer.hpp: -------------------------------------------------------------------------------- 1 | 2 | // ------------------------------------------------ 3 | 4 | #pragma once 5 | 6 | // ------------------------------------------------ 7 | 8 | #include "Kaixo/Core/Definitions.hpp" 9 | #include "Kaixo/Core/Gui/View.hpp" 10 | 11 | // ------------------------------------------------ 12 | 13 | #include "Kaixo/SpectralRotator/Processing/Interfaces/FileInterface.hpp" 14 | #include "Kaixo/SpectralRotator/Gui/SpectralViewer.hpp" 15 | #include "Kaixo/SpectralRotator/Gui/FileDragHandle.hpp" 16 | #include "Kaixo/SpectralRotator/Gui/NonAudioLoadPopupView.hpp" 17 | #include "Kaixo/SpectralRotator/Gui/NotificationPopupView.hpp" 18 | 19 | // ------------------------------------------------ 20 | 21 | namespace Kaixo::Gui { 22 | 23 | // ------------------------------------------------ 24 | 25 | class FileDropTarget : public FileDragAndDropTarget { 26 | public: 27 | 28 | // ------------------------------------------------ 29 | 30 | FileDropTarget(Processing::InterfaceStorage file); 31 | 32 | // ------------------------------------------------ 33 | 34 | Processing::InterfaceStorage file; 35 | 36 | // ------------------------------------------------ 37 | 38 | bool isInterestedInFileDrag(const StringArray& files) override; 39 | void filesDropped(const StringArray& files, int x, int y) override; 40 | 41 | // ------------------------------------------------ 42 | 43 | virtual void tryingToOpenFile() = 0; 44 | virtual void fileOpened(FileLoadStatus status) = 0; 45 | 46 | // ------------------------------------------------ 47 | 48 | void onIdle(); 49 | 50 | // ------------------------------------------------ 51 | 52 | NonAudioLoadPopupView* nonAudioLoadPopupView = nullptr; 53 | NotificationPopupView* notificationPopupView = nullptr; 54 | 55 | // ------------------------------------------------ 56 | 57 | private: 58 | std::future m_FileLoadFuture{}; 59 | 60 | // ------------------------------------------------ 61 | 62 | }; 63 | 64 | // ------------------------------------------------ 65 | 66 | class SpectralFileViewer : public View, public FileDropTarget { 67 | public: 68 | 69 | // ------------------------------------------------ 70 | 71 | struct Settings { 72 | 73 | // ------------------------------------------------ 74 | 75 | bool rotatable = false; 76 | Theme::Drawable background; 77 | Processing::InterfaceStorage file; 78 | SpectralFileViewer* childView = nullptr; 79 | 80 | // ------------------------------------------------ 81 | 82 | } settings; 83 | 84 | // ------------------------------------------------ 85 | 86 | SpectralFileViewer(Context c, Settings s); 87 | 88 | // ------------------------------------------------ 89 | 90 | void tryingToOpenFile() override; 91 | void fileOpened(FileLoadStatus status) override; 92 | 93 | // ------------------------------------------------ 94 | 95 | bool keyPressed(const juce::KeyPress& event); 96 | 97 | // ------------------------------------------------ 98 | 99 | void onIdle() override; 100 | 101 | // ------------------------------------------------ 102 | 103 | SpectralViewer& spectralViewer() { return *m_SpectralViewer; } 104 | 105 | // ------------------------------------------------ 106 | 107 | private: 108 | SpectralViewer* m_SpectralViewer; 109 | std::future m_RotateFuture{}; 110 | 111 | // ------------------------------------------------ 112 | 113 | }; 114 | 115 | // ------------------------------------------------ 116 | 117 | } 118 | 119 | // ------------------------------------------------ 120 | -------------------------------------------------------------------------------- /include/Kaixo/SpectralRotator/Gui/SpectralViewer.hpp: -------------------------------------------------------------------------------- 1 | 2 | // ------------------------------------------------ 3 | 4 | #pragma once 5 | 6 | // ------------------------------------------------ 7 | 8 | #include "Kaixo/Core/Definitions.hpp" 9 | #include "Kaixo/Core/Gui/View.hpp" 10 | #include "Kaixo/Core/Theme/Theme.hpp" 11 | 12 | // ------------------------------------------------ 13 | 14 | #include "Kaixo/SpectralRotator/Processing/Interfaces/FileInterface.hpp" 15 | #include "Kaixo/SpectralRotator/Processing/Utils/AudioBufferSpectralInformation.hpp" 16 | 17 | // ------------------------------------------------ 18 | 19 | namespace Kaixo::Gui { 20 | 21 | // ------------------------------------------------ 22 | 23 | class SpectralViewer : public View { 24 | public: 25 | 26 | // ------------------------------------------------ 27 | 28 | struct Settings { 29 | 30 | // ------------------------------------------------ 31 | 32 | Processing::InterfaceStorage file; 33 | 34 | // ------------------------------------------------ 35 | 36 | } settings; 37 | 38 | // ------------------------------------------------ 39 | 40 | SpectralViewer(Context c, Settings s); 41 | ~SpectralViewer(); 42 | 43 | // ------------------------------------------------ 44 | 45 | void onIdle() override; 46 | 47 | // ------------------------------------------------ 48 | 49 | void resized() override; 50 | 51 | // ------------------------------------------------ 52 | 53 | void paint(juce::Graphics& g) override; 54 | 55 | // ------------------------------------------------ 56 | 57 | void mouseDown(const juce::MouseEvent& event) override; 58 | void mouseDrag(const juce::MouseEvent& event) override; 59 | 60 | void mouseWheelMove(const juce::MouseEvent& event, const juce::MouseWheelDetails& wheel) override; 61 | 62 | // ------------------------------------------------ 63 | 64 | void reGenerateImage(bool withAnalyze, bool inBackground = false); 65 | void fileWillProbablyChangeSoon() { m_FileWillProbablyChange = true; }; 66 | void fileDidNotChange() { m_FileWillProbablyChange = false; }; 67 | 68 | // ------------------------------------------------ 69 | 70 | void fftSize(std::size_t size); 71 | void fftResolution(std::size_t range); 72 | void fftBlockSize(std::size_t ms); 73 | void fftRange(float range); 74 | 75 | // ------------------------------------------------ 76 | 77 | Point normalizePosition(Point coord); 78 | Point denormalizePosition(Point normal); 79 | 80 | Point normalizePositionRelative(Point coord, Rect selection); 81 | Point denormalizePositionRelative(Point normal, Rect selection); 82 | 83 | // ------------------------------------------------ 84 | 85 | void select(Rect rect, bool regen = true); 86 | 87 | // ------------------------------------------------ 88 | 89 | bool showProgress = true; 90 | 91 | // ------------------------------------------------ 92 | 93 | private: 94 | mutable std::mutex m_AnalyzeResultMutex{}; 95 | juce::Image m_Image = juce::Image(juce::Image::PixelFormat::ARGB, 512, 256, true); 96 | juce::Image m_Generated = juce::Image(juce::Image::PixelFormat::ARGB, 512, 256, true); 97 | 98 | Rect m_Selection{ 0, 10, 10, 48000 }; 99 | Rect m_SelectionWhenStartedGenerating{ 0, 10, 10, 48000 }; 100 | Rect m_ImageSelection{ 0, 10, 10, 48000 }; 101 | Rect m_SelectionWhenStartedDragging{ 0, 10, 10, 48000 }; 102 | std::size_t m_FFTSize = 2048; 103 | float m_FFTResolution = 1; 104 | std::size_t m_FFTBlockSize = 50; 105 | float m_FFTRange = 48; 106 | std::size_t m_AnalyzingProgress = 0; 107 | std::size_t m_AnalyzingProgressTotal = 0; 108 | bool m_ShowingProgress = false; 109 | bool m_DidResize = false; 110 | std::atomic_bool m_NewImageReady = false; 111 | std::atomic_bool m_GeneratingImage = false; 112 | std::atomic_bool m_AnalyzeInBackground = false; 113 | std::atomic_bool m_ShouldAnalyze = false; 114 | std::atomic_bool m_FileWillProbablyChange = false; 115 | Processing::AudioBufferSpectralInformation m_AnalyzeResult; 116 | float m_PlayPosition = 0; 117 | Theme::Drawable m_Loading = T.loading; 118 | cxxpool::thread_pool m_GeneratingThreadPool{ 1 }; 119 | 120 | std::chrono::steady_clock::time_point m_LastResize; 121 | 122 | // ------------------------------------------------ 123 | 124 | }; 125 | 126 | // ------------------------------------------------ 127 | 128 | } 129 | 130 | // ------------------------------------------------ 131 | -------------------------------------------------------------------------------- /include/Kaixo/SpectralRotator/Processing/FileHandler.hpp: -------------------------------------------------------------------------------- 1 | 2 | // ------------------------------------------------ 3 | 4 | #pragma once 5 | 6 | // ------------------------------------------------ 7 | 8 | #include "Kaixo/Utils/AudioFile.hpp" 9 | 10 | // ------------------------------------------------ 11 | 12 | #include "Kaixo/SpectralRotator/Processing/Rotator.hpp" 13 | #include "Kaixo/SpectralRotator/Processing/Utils/Resampler.hpp" 14 | 15 | // ------------------------------------------------ 16 | 17 | namespace Kaixo::Processing { 18 | 19 | // ------------------------------------------------ 20 | 21 | class FileHandler : public Module { 22 | public: 23 | 24 | // ------------------------------------------------ 25 | 26 | Stereo output{}; 27 | 28 | // ------------------------------------------------ 29 | 30 | void process() override; 31 | 32 | // ------------------------------------------------ 33 | 34 | void playPause(); // play audio file 35 | void seek(float seconds); 36 | float position(); 37 | 38 | // ------------------------------------------------ 39 | 40 | void waitForReadingToFinish(); 41 | FileLoadStatus open(std::filesystem::path path, std::size_t bitDepth = 16, double sampleRate = 48000); 42 | void rotate(Rotation direction, const AudioBuffer& originalBuffer = {}); 43 | 44 | float loadingProgress(); 45 | 46 | float length(); // length of file in seconds 47 | float nyquist(); 48 | float sampleRate(); 49 | 50 | // ------------------------------------------------ 51 | 52 | std::size_t size(); 53 | 54 | // ------------------------------------------------ 55 | 56 | constexpr static int d000f = 0; 57 | constexpr static int d090f = 1; 58 | constexpr static int d180f = 2; 59 | constexpr static int d270f = 3; 60 | constexpr static int d000r = 4; 61 | constexpr static int d090r = 5; 62 | constexpr static int d180r = 6; 63 | constexpr static int d270r = 7; 64 | 65 | std::atomic_int currentRotation = d000f; 66 | std::map rotations; 67 | 68 | AudioFile* file(); 69 | 70 | int nextRotation(Rotation direction); 71 | std::pair getMostEfficientRotationTo(Rotation direction); 72 | std::string generateSaveFileName(); 73 | 74 | // ------------------------------------------------ 75 | 76 | std::atomic_bool playing = false; 77 | AudioBufferResampler resampler; 78 | std::atomic_bool modifyingFile = false; 79 | std::atomic_bool readingFile = false; 80 | std::atomic_bool newSeekPosition = false; 81 | std::size_t seekPosition = 0; 82 | mutable std::mutex fileMutex{}; 83 | Rotator rotator{}; 84 | 85 | // ------------------------------------------------ 86 | 87 | }; 88 | 89 | // ------------------------------------------------ 90 | 91 | } 92 | 93 | // ------------------------------------------------ 94 | -------------------------------------------------------------------------------- /include/Kaixo/SpectralRotator/Processing/Interfaces/EditorInterface.hpp: -------------------------------------------------------------------------------- 1 | 2 | // ------------------------------------------------ 3 | 4 | #pragma once 5 | 6 | // ------------------------------------------------ 7 | 8 | #include "Kaixo/Core/Definitions.hpp" 9 | #include "Kaixo/Core/Processing/Interface.hpp" 10 | 11 | // ------------------------------------------------ 12 | 13 | #include "Kaixo/Utils/AudioFile.hpp" 14 | #include "Kaixo/SpectralRotator/Processing/Utils/AudioBuffer.hpp" 15 | #include "Kaixo/SpectralRotator/Processing/Utils/AudioBufferSpectralInformation.hpp" 16 | 17 | // ------------------------------------------------ 18 | 19 | namespace Kaixo::Processing { 20 | 21 | // ------------------------------------------------ 22 | 23 | class EditorInterface : public Interface { 24 | public: 25 | 26 | // ------------------------------------------------ 27 | 28 | struct Settings { 29 | std::size_t index; 30 | } settings; 31 | 32 | // ------------------------------------------------ 33 | 34 | std::future finalizeEdit(); 35 | std::future cut(); 36 | std::future remove(); 37 | std::future copy(); 38 | std::future paste(); 39 | std::future select(Rect rect); 40 | std::future move(Point amount, bool remove = true); 41 | std::future brush(Point position); 42 | 43 | Rect selection(); 44 | 45 | // ------------------------------------------------ 46 | 47 | cxxpool::thread_pool asyncTaskPool{ 1 }; 48 | 49 | // ------------------------------------------------ 50 | 51 | }; 52 | 53 | // ------------------------------------------------ 54 | 55 | } 56 | 57 | // ------------------------------------------------ 58 | -------------------------------------------------------------------------------- /include/Kaixo/SpectralRotator/Processing/Interfaces/FileInterface.hpp: -------------------------------------------------------------------------------- 1 | 2 | // ------------------------------------------------ 3 | 4 | #pragma once 5 | 6 | // ------------------------------------------------ 7 | 8 | #include "Kaixo/Core/Definitions.hpp" 9 | #include "Kaixo/Core/Processing/Interface.hpp" 10 | 11 | // ------------------------------------------------ 12 | 13 | #include "Kaixo/Utils/AudioFile.hpp" 14 | #include "Kaixo/SpectralRotator/Processing/Utils/AudioBuffer.hpp" 15 | #include "Kaixo/SpectralRotator/Processing/Utils/AudioBufferSpectralInformation.hpp" 16 | 17 | // ------------------------------------------------ 18 | 19 | namespace Kaixo::Processing { 20 | 21 | // ------------------------------------------------ 22 | 23 | class FileInterface : public Interface { 24 | public: 25 | 26 | // ------------------------------------------------ 27 | 28 | struct Settings { 29 | std::size_t index; 30 | } settings; 31 | 32 | // ------------------------------------------------ 33 | 34 | void saveFile(); 35 | std::future rotate(Rotation direction); 36 | std::future openFile(std::filesystem::path path, std::size_t bitDepth = 16, double sampleRate = 48000); 37 | bool modifyingFile(); 38 | float loadingProgress(); 39 | 40 | void playPause(); 41 | void seek(float seconds); 42 | float position(); 43 | float length(); // Length of opened file in seconds 44 | float nyquist(); 45 | 46 | std::filesystem::path path(); 47 | void analyzeBuffer( 48 | AudioBufferSpectralInformation& reanalyze, 49 | std::size_t fftSize, 50 | float horizontalResolution, 51 | std::size_t blockSize, 52 | std::size_t* progress = nullptr); 53 | 54 | // ------------------------------------------------ 55 | 56 | cxxpool::thread_pool asyncTaskPool{ 1 }; 57 | 58 | // ------------------------------------------------ 59 | 60 | }; 61 | 62 | // ------------------------------------------------ 63 | 64 | } 65 | 66 | // ------------------------------------------------ 67 | -------------------------------------------------------------------------------- /include/Kaixo/SpectralRotator/Processing/Processor.hpp: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // ------------------------------------------------ 4 | 5 | #include "Kaixo/Utils/AudioFile.hpp" 6 | #include "Kaixo/Core/Processing/Processor.hpp" 7 | #include "Kaixo/Core/Processing/ParameterDatabase.hpp" 8 | 9 | // ------------------------------------------------ 10 | 11 | #include "Kaixo/SpectralRotator/Controller.hpp" 12 | #include "Kaixo/SpectralRotator/Processing/Rotator.hpp" 13 | #include "Kaixo/SpectralRotator/Processing/FileHandler.hpp" 14 | #include "Kaixo/SpectralRotator/Processing/SpectralEditor.hpp" 15 | #include "Kaixo/SpectralRotator/Processing/Utils/Resampler.hpp" 16 | 17 | // ------------------------------------------------ 18 | 19 | namespace Kaixo::Processing { 20 | 21 | // ------------------------------------------------ 22 | 23 | class SpectralRotatorProcessor : public Processor { 24 | public: 25 | 26 | // ------------------------------------------------ 27 | 28 | SpectralRotatorProcessor(); 29 | 30 | // ------------------------------------------------ 31 | 32 | void process() override; 33 | 34 | // ------------------------------------------------ 35 | 36 | FileHandler inputFile{}; // Input audio 37 | FileHandler rotatedFile{}; // Rotated audio 38 | 39 | SpectralEditor editor{}; 40 | 41 | // ------------------------------------------------ 42 | 43 | }; 44 | 45 | // ------------------------------------------------ 46 | 47 | } 48 | 49 | // ------------------------------------------------ 50 | -------------------------------------------------------------------------------- /include/Kaixo/SpectralRotator/Processing/Rotator.hpp: -------------------------------------------------------------------------------- 1 | 2 | // ------------------------------------------------ 3 | 4 | #pragma once 5 | 6 | // ------------------------------------------------ 7 | 8 | #include "Kaixo/Core/Processing/Module.hpp" 9 | 10 | // ------------------------------------------------ 11 | 12 | #include "Kaixo/SpectralRotator/Processing/Utils/Fft.hpp" 13 | #include "Kaixo/SpectralRotator/Processing/Utils/AudioBuffer.hpp" 14 | #include "Kaixo/SpectralRotator/Processing/Utils/ComplexBuffer.hpp" 15 | 16 | // ------------------------------------------------ 17 | 18 | namespace Kaixo::Processing { 19 | 20 | // ------------------------------------------------ 21 | 22 | enum Rotation { 23 | None, 24 | Rotate90, 25 | Rotate270, 26 | Reverse, 27 | Flip, 28 | }; 29 | 30 | // ------------------------------------------------ 31 | 32 | class Rotator { 33 | public: 34 | 35 | // ------------------------------------------------ 36 | 37 | AudioBuffer rotate(const AudioBuffer& buffer, Rotation direction, const AudioBuffer& originalBuffer = {}); 38 | 39 | // ------------------------------------------------ 40 | 41 | float progress() const { return static_cast(m_Progress) / m_EstimatedSteps; } 42 | 43 | // ------------------------------------------------ 44 | 45 | private: 46 | std::size_t m_Progress = 0; 47 | std::size_t m_EstimatedSteps = 0; 48 | 49 | // ------------------------------------------------ 50 | 51 | Fft m_Fft{ &m_Progress }; 52 | 53 | // ------------------------------------------------ 54 | 55 | AudioBuffer rotateOnce(const AudioBuffer& buffer, Rotation direction, const AudioBuffer& originalBuffer = {}); 56 | 57 | // ------------------------------------------------ 58 | 59 | void start() { m_Progress = 0; } 60 | void step(std::size_t weight = 1) { m_Progress += weight; } 61 | 62 | // ------------------------------------------------ 63 | 64 | void fft(ComplexBuffer& buffer); 65 | void ifft(ComplexBuffer& buffer); 66 | 67 | // ------------------------------------------------ 68 | 69 | }; 70 | 71 | // ------------------------------------------------ 72 | 73 | } 74 | 75 | // ------------------------------------------------ 76 | -------------------------------------------------------------------------------- /include/Kaixo/SpectralRotator/Processing/SpectralEditor.hpp: -------------------------------------------------------------------------------- 1 | 2 | // ------------------------------------------------ 3 | 4 | #pragma once 5 | 6 | // ------------------------------------------------ 7 | 8 | #include "Kaixo/Utils/AudioFile.hpp" 9 | 10 | // ------------------------------------------------ 11 | 12 | #include "Kaixo/SpectralRotator/Processing/Utils/Resampler.hpp" 13 | #include "Kaixo/SpectralRotator/Processing/Utils/AudioBufferSpectralInformation.hpp" 14 | 15 | // ------------------------------------------------ 16 | 17 | namespace Kaixo::Processing { 18 | 19 | // ------------------------------------------------ 20 | 21 | class SpectralEditor : public Module { 22 | public: 23 | 24 | // ------------------------------------------------ 25 | 26 | Stereo output{}; 27 | 28 | // ------------------------------------------------ 29 | 30 | void process() override; 31 | 32 | // ------------------------------------------------ 33 | 34 | void playPause(); // play audio file 35 | void seek(float seconds); 36 | float position(); 37 | 38 | // ------------------------------------------------ 39 | 40 | void waitForReadingToFinish(); 41 | FileLoadStatus open(std::filesystem::path path, std::size_t bitDepth = 16, double sampleRate = 48000); 42 | float loadingProgress(); 43 | 44 | // ------------------------------------------------ 45 | 46 | std::size_t size(); 47 | float length(); // length in seconds 48 | float nyquist(); 49 | float sampleRate(); 50 | 51 | // ------------------------------------------------ 52 | 53 | void finalizeEdit(); // Move from editing layer to selected layer 54 | void cut(); // Move from editing or selected layer to clipboard 55 | void remove(); // Remove from editing or selected layer 56 | void copy(); // Copy from selected layer to clipboard 57 | void paste(); // Move from clipboard to editing layer 58 | void select(Rect rect); // Set current selection 59 | void move(Point amount, bool remove = true); // Move selection 60 | void brush(Point position); // Use clipboard as brush 61 | 62 | // ------------------------------------------------ 63 | 64 | Rect denormalizeRect(Rect rect); 65 | Rect normalizeRect(Rect rect); 66 | 67 | // ------------------------------------------------ 68 | 69 | AudioFile file; 70 | std::size_t selectedLayer = 0; 71 | 72 | Rect selection{ 0, 0, 0, 0 }; 73 | 74 | struct Layer { 75 | Processing::AudioBuffer buffer{}; 76 | std::int64_t delay = 0; // samples 77 | float offset = 0; // frequency offset in Hz 78 | 79 | float dirtyStart = 0; 80 | float dirtyEnd = std::numeric_limits::max(); 81 | 82 | void clear(); 83 | void dirty(bool isDirty); 84 | void extendDirty(Point minmax); 85 | }; 86 | 87 | std::map layers; 88 | Layer editing{}; 89 | Layer clipboard{}; 90 | Rect clipboardSelection{}; // Original selection when moved to clipboard 91 | 92 | // ------------------------------------------------ 93 | 94 | struct Operation { 95 | // Source layer 96 | Layer* source = nullptr; 97 | // Selection in source 98 | Rect selection{ 0, 0, 0, 0 }; 99 | // Destination layer 100 | Layer* destination = nullptr; 101 | // Allowed to completely overwrite destination 102 | bool clearDestination = true; 103 | // Position in destination to put it 104 | Point destinationPosition = selection.position(); 105 | // Operation to perform 106 | enum { 107 | Move, // Remove from source, overwrite in destination 108 | Copy, // Keep in source, and overwrite in destination 109 | Remove, // Remove from source, destination is unused 110 | Add, // Add on top of destination 111 | } op; 112 | }; 113 | 114 | void frequencyShift(ComplexBuffer& buffer, std::int64_t bins, bool clearMoved = false); 115 | void toFrequencyDomain(Layer& layer, ComplexBuffer& destination, std::int64_t timeOffset, float smooth = 0, bool additive = false); 116 | void toTimeDomain(Layer& layer, ComplexBuffer& source, std::int64_t timeOffset, float smooth = 0, bool additive = false); 117 | 118 | void doOperation(Operation operation); 119 | 120 | std::size_t estimateFrequencyShiftSteps(std::size_t fftSize, std::int64_t bins); 121 | std::size_t estimateToFrequencyDomainSteps(Layer& layer, std::size_t fftSize, std::size_t timeOffset); 122 | std::size_t estimateToTimeDomainSteps(Layer& layer, std::size_t fftSize, std::size_t timeOffset); 123 | std::size_t estimateOperationSteps(Operation operation); 124 | 125 | // ------------------------------------------------ 126 | 127 | float bufferSampleRate = 48000; 128 | std::atomic_bool playing = false; 129 | std::atomic_bool modifyingFile = false; 130 | std::atomic_bool readingFile = false; 131 | std::int64_t seekPosition = 0; 132 | mutable std::mutex fileMutex{}; 133 | 134 | // ------------------------------------------------ 135 | 136 | std::size_t estimatedSteps = 1; 137 | std::size_t progress = 0; 138 | 139 | void start() { progress = 0; } 140 | void step(std::size_t weight = 1) { progress += weight; } 141 | 142 | // ------------------------------------------------ 143 | 144 | void analyze( 145 | AudioBufferSpectralInformation& reanalyze, 146 | std::size_t fftSize, 147 | float horizontalResolution, 148 | std::size_t bSizeMs, 149 | std::size_t* progress = nullptr 150 | ); 151 | 152 | // ------------------------------------------------ 153 | 154 | }; 155 | 156 | 157 | // ------------------------------------------------ 158 | 159 | } 160 | 161 | // ------------------------------------------------ 162 | -------------------------------------------------------------------------------- /include/Kaixo/SpectralRotator/Processing/Utils/AudioBuffer.hpp: -------------------------------------------------------------------------------- 1 | 2 | // ------------------------------------------------ 3 | 4 | #pragma once 5 | 6 | // ------------------------------------------------ 7 | 8 | #include "Kaixo/Core/Definitions.hpp" 9 | 10 | // ------------------------------------------------ 11 | 12 | #include "Kaixo/SpectralRotator/Processing/Utils/AudioFrame.hpp" 13 | 14 | // ------------------------------------------------ 15 | 16 | namespace Kaixo::Processing { 17 | 18 | // ------------------------------------------------ 19 | 20 | class AudioBuffer : public std::vector { 21 | public: 22 | 23 | // ------------------------------------------------ 24 | 25 | using std::vector::vector; 26 | 27 | // ------------------------------------------------ 28 | 29 | double sampleRate = 1; 30 | 31 | // ------------------------------------------------ 32 | 33 | }; 34 | 35 | } 36 | 37 | // ------------------------------------------------ 38 | -------------------------------------------------------------------------------- /include/Kaixo/SpectralRotator/Processing/Utils/AudioBufferSpectralInformation.hpp: -------------------------------------------------------------------------------- 1 | 2 | // ------------------------------------------------ 3 | 4 | #pragma once 5 | 6 | // ------------------------------------------------ 7 | 8 | #include "Kaixo/Core/Definitions.hpp" 9 | 10 | // ------------------------------------------------ 11 | 12 | #include "Kaixo/SpectralRotator/Processing/Utils/AudioBuffer.hpp" 13 | 14 | // ------------------------------------------------ 15 | 16 | namespace Kaixo::Processing { 17 | 18 | // ------------------------------------------------ 19 | 20 | struct AudioBufferSpectralInformation { 21 | 22 | // ------------------------------------------------ 23 | 24 | struct Layer { 25 | 26 | // ------------------------------------------------ 27 | 28 | float sampleRate = 1; 29 | Rect selection{}; 30 | Point offset{}; 31 | std::size_t frameSize = 1024; 32 | std::vector intensity{}; 33 | 34 | // ------------------------------------------------ 35 | 36 | float get(std::int64_t x, std::int64_t y); 37 | 38 | std::size_t frames() { return intensity.size() / frameSize; } 39 | 40 | // ------------------------------------------------ 41 | 42 | float intensityAtY(std::int64_t x, float y); 43 | 44 | // ------------------------------------------------ 45 | 46 | float intensityAt(float x, float y); 47 | 48 | // ------------------------------------------------ 49 | 50 | }; 51 | 52 | // ------------------------------------------------ 53 | 54 | std::size_t fftSize; // power of 2 55 | float horizontalResolution; // ms 56 | std::size_t blockSize; // ms 57 | std::map layers{}; 58 | 59 | // ------------------------------------------------ 60 | 61 | float intensityAt(float x, float y); 62 | 63 | // ------------------------------------------------ 64 | 65 | struct AnalyzeSettings { 66 | const Processing::AudioBuffer& buffer; 67 | std::size_t fftSize; // power of 2 68 | float horizontalResolution; // ms 69 | std::size_t blockSize; // ms 70 | std::size_t* progress = nullptr; 71 | 72 | AudioBufferSpectralInformation::Layer& reanalyze; 73 | float start = 0; 74 | float end = 1e6; 75 | }; 76 | 77 | static void analyze(AnalyzeSettings settings); 78 | 79 | // ------------------------------------------------ 80 | 81 | }; 82 | 83 | // ------------------------------------------------ 84 | 85 | } 86 | 87 | // ------------------------------------------------ 88 | -------------------------------------------------------------------------------- /include/Kaixo/SpectralRotator/Processing/Utils/AudioFrame.hpp: -------------------------------------------------------------------------------- 1 | 2 | // ------------------------------------------------ 3 | 4 | #pragma once 5 | 6 | // ------------------------------------------------ 7 | 8 | namespace Kaixo::Processing { 9 | 10 | // ------------------------------------------------ 11 | 12 | struct alignas(8) AudioFrame { 13 | 14 | // ------------------------------------------------ 15 | 16 | float l = 0; 17 | float r = 0; 18 | 19 | // ------------------------------------------------ 20 | 21 | constexpr AudioFrame& operator=(float other) { l = other, r = other; return *this; } 22 | constexpr AudioFrame& operator+=(const AudioFrame& other) { l += other.l, r += other.r; return *this; } 23 | constexpr AudioFrame& operator-=(const AudioFrame& other) { l -= other.l, r -= other.r; return *this; } 24 | constexpr AudioFrame& operator*=(const AudioFrame& other) { l *= other.l, r *= other.r; return *this; } 25 | constexpr AudioFrame& operator/=(const AudioFrame& other) { l /= other.l, r /= other.r; return *this; } 26 | constexpr AudioFrame& operator+=(float other) { l += other, r += other; return *this; } 27 | constexpr AudioFrame& operator-=(float other) { l -= other, r -= other; return *this; } 28 | constexpr AudioFrame& operator*=(float other) { l *= other, r *= other; return *this; } 29 | constexpr AudioFrame& operator/=(float other) { l /= other, r /= other; return *this; } 30 | constexpr friend AudioFrame operator-(const AudioFrame& a) { return { -a.l, -a.r }; } 31 | constexpr friend AudioFrame operator+(const AudioFrame& a, const AudioFrame& b) { return { a.l + b.l, a.r + b.r }; } 32 | constexpr friend AudioFrame operator-(const AudioFrame& a, const AudioFrame& b) { return { a.l - b.l, a.r - b.r }; } 33 | constexpr friend AudioFrame operator*(const AudioFrame& a, const AudioFrame& b) { return { a.l * b.l, a.r * b.r }; } 34 | constexpr friend AudioFrame operator/(const AudioFrame& a, const AudioFrame& b) { return { a.l / b.l, a.r / b.r }; } 35 | constexpr friend AudioFrame operator+(const AudioFrame& a, float b) { return { a.l + b, a.r + b }; } 36 | constexpr friend AudioFrame operator-(const AudioFrame& a, float b) { return { a.l - b, a.r - b }; } 37 | constexpr friend AudioFrame operator*(const AudioFrame& a, float b) { return { a.l * b, a.r * b }; } 38 | constexpr friend AudioFrame operator/(const AudioFrame& a, float b) { return { a.l / b, a.r / b }; } 39 | constexpr friend AudioFrame operator+(float a, const AudioFrame& b) { return { a + b.l, a + b.r }; } 40 | constexpr friend AudioFrame operator-(float a, const AudioFrame& b) { return { a - b.l, a - b.r }; } 41 | constexpr friend AudioFrame operator*(float a, const AudioFrame& b) { return { a * b.l, a * b.r }; } 42 | constexpr friend AudioFrame operator/(float a, const AudioFrame& b) { return { a / b.l, a / b.r }; } 43 | constexpr friend bool operator==(const AudioFrame& a, const AudioFrame& b) { return a.l == b.l && a.r == b.r; } 44 | constexpr friend bool operator!=(const AudioFrame& a, const AudioFrame& b) { return a.l != b.l && a.r != b.r; } 45 | constexpr friend bool operator==(const AudioFrame& a, float b) { return a.l == b && a.r == b; } 46 | constexpr friend bool operator!=(const AudioFrame& a, float b) { return a.l != b && a.r != b; } 47 | constexpr friend bool operator==(float a, const AudioFrame& b) { return a == b.l && a == b.r; } 48 | constexpr friend bool operator!=(float a, const AudioFrame& b) { return a != b.l && a != b.r; } 49 | constexpr float& operator[](bool value) { return value ? r : l; } 50 | constexpr const float& operator[](bool value) const { return value ? r : l; } 51 | constexpr float sum() const noexcept { return r + l; } 52 | constexpr float average() const noexcept { return sum() / 2; } 53 | }; 54 | 55 | // ------------------------------------------------ 56 | 57 | } 58 | 59 | // ------------------------------------------------ 60 | -------------------------------------------------------------------------------- /include/Kaixo/SpectralRotator/Processing/Utils/ComplexBuffer.hpp: -------------------------------------------------------------------------------- 1 | 2 | // ------------------------------------------------ 3 | 4 | #pragma once 5 | 6 | // ------------------------------------------------ 7 | 8 | #include "Kaixo/Core/Processing/Module.hpp" 9 | 10 | // ------------------------------------------------ 11 | 12 | namespace Kaixo::Processing { 13 | 14 | // ------------------------------------------------ 15 | 16 | struct ComplexBuffer { 17 | std::vector> l; 18 | std::vector> r; 19 | 20 | constexpr std::size_t size() const { return l.size(); } 21 | constexpr bool empty() const { return l.empty(); } 22 | 23 | constexpr void reserve(std::size_t size) { 24 | l.reserve(size); 25 | r.reserve(size); 26 | } 27 | 28 | constexpr void resize(std::size_t size) { 29 | l.resize(size); 30 | r.resize(size); 31 | } 32 | }; 33 | 34 | // ------------------------------------------------ 35 | 36 | } 37 | 38 | // ------------------------------------------------ 39 | -------------------------------------------------------------------------------- /include/Kaixo/SpectralRotator/Processing/Utils/Fft.hpp: -------------------------------------------------------------------------------- 1 | /* 2 | * Free FFT and convolution (C++) 3 | * 4 | * Copyright (c) 2021 Project Nayuki. (MIT License) 5 | * https://www.nayuki.io/page/free-small-fft-in-multiple-languages 6 | * 7 | * Permission is hereby granted, free of charge, to any person obtaining a copy of 8 | * this software and associated documentation files (the "Software"), to deal in 9 | * the Software without restriction, including without limitation the rights to 10 | * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 11 | * the Software, and to permit persons to whom the Software is furnished to do so, 12 | * subject to the following conditions: 13 | * - The above copyright notice and this permission notice shall be included in 14 | * all copies or substantial portions of the Software. 15 | * - The Software is provided "as is", without warranty of any kind, express or 16 | * implied, including but not limited to the warranties of merchantability, 17 | * fitness for a particular purpose and noninfringement. In no event shall the 18 | * authors or copyright holders be liable for any claim, damages or other 19 | * liability, whether in an action of contract, tort or otherwise, arising from, 20 | * out of or in connection with the Software or the use or other dealings in the 21 | * Software. 22 | */ 23 | 24 | // ------------------------------------------------ 25 | 26 | #pragma once 27 | 28 | // ------------------------------------------------ 29 | 30 | #include 31 | #include 32 | 33 | // ------------------------------------------------ 34 | 35 | namespace Kaixo::Processing { 36 | 37 | // ------------------------------------------------ 38 | 39 | struct Fft { 40 | 41 | // ------------------------------------------------ 42 | 43 | std::size_t* stepRef = nullptr; // For progress bar 44 | 45 | // ------------------------------------------------ 46 | 47 | void transform(std::vector>& vec, bool inverse); 48 | void transformRadix2(std::vector>& vec, bool inverse); 49 | void transformBluestein(std::vector>& vec, bool inverse); 50 | 51 | // ------------------------------------------------ 52 | 53 | std::vector> convolve( 54 | std::vector> xvec, 55 | std::vector> yvec); 56 | 57 | // ------------------------------------------------ 58 | 59 | std::size_t estimateSteps(std::size_t size, bool inverse); 60 | 61 | // ------------------------------------------------ 62 | 63 | void step() { if (stepRef) (*stepRef)++; } 64 | 65 | // ------------------------------------------------ 66 | 67 | }; 68 | 69 | // ------------------------------------------------ 70 | 71 | } 72 | 73 | // ------------------------------------------------ 74 | -------------------------------------------------------------------------------- /include/Kaixo/SpectralRotator/Processing/Utils/Resampler.hpp: -------------------------------------------------------------------------------- 1 | 2 | // ------------------------------------------------ 3 | 4 | #pragma once 5 | 6 | // ------------------------------------------------ 7 | 8 | #include "Kaixo/Core/Processing/Module.hpp" 9 | #include "Kaixo/Core/Processing/Filter.hpp" 10 | #include "Kaixo/SpectralRotator/Processing/Utils/AudioBuffer.hpp" 11 | 12 | // ------------------------------------------------ 13 | 14 | namespace Kaixo::Processing { 15 | 16 | // ------------------------------------------------ 17 | 18 | struct AudioBufferResampler { 19 | 20 | // ------------------------------------------------ 21 | 22 | constexpr static std::int64_t WINDOW_SIZE = 64; // Windowed-Sinc Function window size. 23 | 24 | // ------------------------------------------------ 25 | 26 | void reset() { index(0); } 27 | void index(std::int64_t i) { playindex = i; lastinserted = i; finished = false; } 28 | 29 | // ------------------------------------------------ 30 | 31 | bool reverse = false; 32 | 33 | // ------------------------------------------------ 34 | 35 | struct { 36 | double in; 37 | double out; 38 | } samplerate; 39 | 40 | // ------------------------------------------------ 41 | 42 | Stereo generate(const AudioBuffer& buffer); 43 | 44 | // ------------------------------------------------ 45 | 46 | std::int64_t position() const { return lastinserted; } 47 | bool eof() const { return finished; } 48 | 49 | // ------------------------------------------------ 50 | 51 | private: 52 | std::int64_t lastinserted = 0; 53 | double playindex = 0; // Current non-integer index in input buffer. 54 | bool finished = false; 55 | 56 | // ------------------------------------------------ 57 | 58 | struct CircleBuffer { 59 | Stereo data[WINDOW_SIZE]{}; 60 | constexpr Stereo& operator[](std::int64_t i) { return data[(i + 10 * WINDOW_SIZE) % WINDOW_SIZE]; } 61 | } buffer; 62 | 63 | EllipticFilter aaf[2]{}; 64 | EllipticParameters aafp[2]{}; 65 | 66 | // ------------------------------------------------ 67 | 68 | void applyWindowedSinc(Stereo& in); 69 | 70 | // ------------------------------------------------ 71 | 72 | }; 73 | 74 | // ------------------------------------------------ 75 | 76 | } 77 | 78 | // ------------------------------------------------ 79 | -------------------------------------------------------------------------------- /include/Kaixo/Utils/AudioFile.hpp: -------------------------------------------------------------------------------- 1 | 2 | // ------------------------------------------------ 3 | 4 | #pragma once 5 | 6 | // ------------------------------------------------ 7 | 8 | #include "Kaixo/Core/Processing/Buffer.hpp" 9 | #include "Kaixo/SpectralRotator/Processing/Rotator.hpp" 10 | 11 | // ------------------------------------------------ 12 | 13 | namespace Kaixo { 14 | 15 | // ------------------------------------------------ 16 | 17 | enum class FileLoadStatus { 18 | Success, TooLarge, Error, FailedToOpen, NotExists 19 | }; 20 | 21 | // ------------------------------------------------ 22 | 23 | struct AudioFile { 24 | 25 | // ------------------------------------------------ 26 | 27 | FileLoadStatus open(std::filesystem::path file, std::size_t bitDepth = 16, double sampleRate = 48000); 28 | 29 | // ------------------------------------------------ 30 | 31 | std::filesystem::path path{}; 32 | Processing::AudioBuffer buffer{}; 33 | std::atomic_bool changed{}; 34 | 35 | // ------------------------------------------------ 36 | 37 | void write(std::filesystem::path file); 38 | 39 | void save(std::string filename = "rotated"); 40 | 41 | // ------------------------------------------------ 42 | 43 | static std::filesystem::path generationLocation(); 44 | 45 | // ------------------------------------------------ 46 | 47 | }; 48 | 49 | // ------------------------------------------------ 50 | 51 | } 52 | 53 | // ------------------------------------------------ 54 | -------------------------------------------------------------------------------- /include/Kaixo/Utils/Decoders/Decoder.hpp: -------------------------------------------------------------------------------- 1 | 2 | // ------------------------------------------------ 3 | 4 | #pragma once 5 | 6 | // ------------------------------------------------ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | // ------------------------------------------------ 14 | 15 | #include "Kaixo/SpectralRotator/Processing/Rotator.hpp" 16 | #include "Kaixo/Utils/AudioFile.hpp" 17 | #define DR_MP3_IMPLEMENTATION 18 | #include "Kaixo/Utils/Decoders/mp3.hpp" 19 | #define DR_WAV_IMPLEMENTATION 20 | #include "Kaixo/Utils/Decoders/wav.hpp" 21 | 22 | // ------------------------------------------------ 23 | 24 | namespace Kaixo { 25 | 26 | // ------------------------------------------------ 27 | 28 | enum class Format { NONE, WAV, MP3 }; 29 | 30 | // ------------------------------------------------ 31 | 32 | inline Format getFormat(std::string extension) { 33 | for (auto& c : extension) c = std::tolower(c); // extension tolower 34 | if (extension == ".wav") return Format::WAV; 35 | if (extension == ".mp3") return Format::MP3; 36 | return Format::NONE; 37 | } 38 | 39 | // ------------------------------------------------ 40 | 41 | inline FileLoadStatus decodeMP3(Processing::AudioBuffer& buffer, const std::string& path, std::size_t maxSize = npos) { 42 | drmp3 mp3; 43 | if (!drmp3_init_file( & mp3, path.c_str(), NULL)) return FileLoadStatus::FailedToOpen; 44 | if (mp3.channels == 0) return FileLoadStatus::Error; 45 | 46 | //buffer.samplerate = mp3.sampleRate; 47 | auto frames = drmp3_get_pcm_frame_count(&mp3); 48 | if (frames == 0) return FileLoadStatus::Error; // 0 frames means error 49 | if (frames > maxSize) return FileLoadStatus::TooLarge; 50 | 51 | std::unique_ptr raw = std::make_unique(frames * mp3.channels); 52 | drmp3_read_pcm_frames_f32(&mp3, frames, raw.get()); 53 | 54 | buffer.resize(frames); 55 | for (std::size_t i = 0; i < frames; i++) { 56 | if (mp3.channels == 1) { 57 | buffer[i] = raw[i]; 58 | } else { 59 | buffer[i] = { raw[i * mp3.channels], raw[i * mp3.channels + 1] }; 60 | } 61 | } 62 | buffer.sampleRate = mp3.sampleRate; 63 | 64 | drmp3_uninit(&mp3); 65 | return FileLoadStatus::Success; 66 | } 67 | 68 | // ------------------------------------------------ 69 | 70 | inline FileLoadStatus decodeWAV(Processing::AudioBuffer& buffer, const std::string& path, std::size_t maxSize = npos) { 71 | drwav wav; 72 | if (!drwav_init_file(&wav, path.c_str(), NULL)) return FileLoadStatus::FailedToOpen; 73 | if (wav.channels == 0) return FileLoadStatus::Error; 74 | 75 | auto frames = wav.totalPCMFrameCount; 76 | if (frames == 0) return FileLoadStatus::Error; // 0 frames means error 77 | if (frames > maxSize) return FileLoadStatus::TooLarge; 78 | 79 | std::unique_ptr raw = std::make_unique(frames * wav.channels); 80 | drwav_read_pcm_frames_f32(&wav, frames, raw.get()); 81 | 82 | buffer.resize(frames); 83 | for (std::size_t i = 0; i < frames; i++) { 84 | if (wav.channels == 1) { 85 | buffer[i] = raw[i]; 86 | } else { 87 | buffer[i] = { raw[i * wav.channels], raw[i * wav.channels + 1] }; 88 | } 89 | } 90 | buffer.sampleRate = wav.sampleRate; 91 | 92 | drwav_uninit(&wav); 93 | return FileLoadStatus::Success; 94 | } 95 | 96 | // ------------------------------------------------ 97 | 98 | inline FileLoadStatus decodeAny(Processing::AudioBuffer& buffer, const std::string& path, std::size_t maxSize = npos, std::size_t bitDepth = 32, double sampleRate = 48000) { 99 | std::ifstream file{ path, std::ifstream::binary }; 100 | std::vector fileBytes(std::istreambuf_iterator(file), {}); 101 | 102 | std::size_t bytesPerFrame = bitDepth / 8; 103 | std::size_t frames = fileBytes.size() / bytesPerFrame; 104 | const std::uint8_t* data = fileBytes.data(); 105 | 106 | if (frames > maxSize) return FileLoadStatus::TooLarge; 107 | 108 | buffer.resize(frames); 109 | for (std::size_t i = 0; i < frames; ++i) { 110 | float sample = 0; 111 | switch (bytesPerFrame) { 112 | case 1: sample = 2 * static_cast(*(((std::uint8_t*)data) + i)) / std::numeric_limits::max() - 1; break; 113 | case 2: sample = 2 * static_cast(*(((std::uint16_t*)data) + i)) / std::numeric_limits::max() - 1; break; 114 | case 4: sample = 2 * static_cast(*(((std::uint32_t*)data) + i)) / std::numeric_limits::max() - 1; break; 115 | case 8: sample = 2 * static_cast(*(((std::uint64_t*)data) + i)) / std::numeric_limits::max() - 1; break; 116 | } 117 | buffer[i] = sample; 118 | } 119 | buffer.sampleRate = sampleRate; 120 | 121 | return FileLoadStatus::Success; 122 | } 123 | 124 | // ------------------------------------------------ 125 | 126 | inline FileLoadStatus decode(Processing::AudioBuffer& buffer, const std::filesystem::path& path, std::size_t maxSize = npos, std::size_t bitDepth = 32, double sampleRate = 48000) { 127 | if (!std::filesystem::exists(path)) return FileLoadStatus::NotExists; 128 | 129 | auto extension = getFormat(path.extension().string()); 130 | 131 | switch (extension) { 132 | case Format::NONE: return decodeAny(buffer, path.string(), maxSize, bitDepth, sampleRate); 133 | case Format::WAV: return decodeWAV(buffer, path.string(), maxSize); 134 | case Format::MP3: return decodeMP3(buffer, path.string(), maxSize); 135 | } 136 | } 137 | 138 | // ------------------------------------------------ 139 | 140 | inline bool write(Processing::AudioBuffer& buffer, const std::filesystem::path& path) { 141 | drwav wav; 142 | drwav_data_format format{}; 143 | format.bitsPerSample = 32; 144 | format.channels = 2; 145 | format.sampleRate = buffer.sampleRate; 146 | format.container = drwav_container_riff; 147 | format.format = DR_WAVE_FORMAT_IEEE_FLOAT; 148 | 149 | if (!drwav_init_file_write(&wav, path.string().c_str(), &format, NULL)) return false; 150 | 151 | drwav_write_pcm_frames(&wav, buffer.size(), buffer.data()); 152 | 153 | drwav_uninit(&wav); 154 | return true; 155 | } 156 | 157 | // ------------------------------------------------ 158 | 159 | } -------------------------------------------------------------------------------- /resources/Parameters.xml: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /source/Kaixo/SpectralRotator/Controller.cpp: -------------------------------------------------------------------------------- 1 | 2 | // ------------------------------------------------ 3 | 4 | #include "Kaixo/SpectralRotator/Controller.hpp" 5 | 6 | // ------------------------------------------------ 7 | 8 | #include "Kaixo/Core/Theme/Theme.hpp" 9 | 10 | // ------------------------------------------------ 11 | 12 | namespace Kaixo { 13 | 14 | // ------------------------------------------------ 15 | 16 | SpectralRotatorController::SpectralRotatorController() { 17 | 18 | // ------------------------------------------------ 19 | 20 | Gui::T.initialize(); 21 | 22 | // ------------------------------------------------ 23 | 24 | } 25 | 26 | // ------------------------------------------------ 27 | 28 | Controller* createController() { return new SpectralRotatorController; } 29 | 30 | // ------------------------------------------------ 31 | 32 | } 33 | 34 | // ------------------------------------------------ 35 | -------------------------------------------------------------------------------- /source/Kaixo/SpectralRotator/Gui/EditorView.cpp: -------------------------------------------------------------------------------- 1 | 2 | // ------------------------------------------------ 3 | 4 | #include "Kaixo/SpectralRotator/Gui/EditorView.hpp" 5 | 6 | // ------------------------------------------------ 7 | 8 | #include "Kaixo/Core/Gui/Button.hpp" 9 | 10 | // ------------------------------------------------ 11 | 12 | namespace Kaixo::Gui { 13 | 14 | // ------------------------------------------------ 15 | 16 | EditorViewLayer::EditorViewLayer(Context c, Settings s) 17 | : View(c), settings(std::move(s)) 18 | { 19 | add({ .image = T.button }); 20 | } 21 | 22 | // ------------------------------------------------ 23 | 24 | SpectralEditor::SpectralEditor(Context c, Settings s) 25 | : View(c), settings(std::move(s)) 26 | { 27 | wantsIdle(true); 28 | 29 | spectralViewer = &add({ 30 | .file = context.interface({.index = 2 }) 31 | }); 32 | 33 | spectralViewer->setInterceptsMouseClicks(false, false); 34 | } 35 | 36 | // ------------------------------------------------ 37 | 38 | void SpectralEditor::mouseDown(const juce::MouseEvent& event) { 39 | Point<> mouse{ event.x, event.y }; 40 | 41 | if (event.mods.isRightButtonDown()) { 42 | spectralViewer->showProgress = false; 43 | state = State::Brush; 44 | } else if (event.mods.isCtrlDown() || event.mods.isMiddleButtonDown()) { 45 | state = State::Child; 46 | spectralViewer->mouseDown(event.getEventRelativeTo(spectralViewer)); 47 | } else { 48 | if (editFuture.valid()) { 49 | state = State::Waiting; 50 | } else if (selectedRect().contains(mouse)) { 51 | settings.editor->move({ 0, 0 }, !event.mods.isShiftDown()); 52 | state = State::Moving; 53 | moved = { 0, 0 }; 54 | } else { 55 | state = State::Selecting; 56 | dragStart = spectralViewer->normalizePosition({ event.x, event.y }); 57 | dragEnd = dragStart; 58 | moved = { 0, 0 }; 59 | } 60 | } 61 | } 62 | 63 | void SpectralEditor::mouseUp(const juce::MouseEvent& event) { 64 | switch (state) { 65 | case State::Selecting: { 66 | Point begin = dragStart; 67 | Point end = dragEnd; 68 | auto minX = Math::min(end.x(), begin.x()); 69 | auto maxX = Math::max(end.x(), begin.x()); 70 | auto minY = Math::min(end.y(), begin.y()); 71 | auto maxY = Math::max(end.y(), begin.y()); 72 | editFuture = settings.editor->select({ minX, minY, maxX - minX, maxY - minY }); 73 | break; 74 | } 75 | case State::Moving: 76 | dragStart += moved; 77 | dragEnd += moved; 78 | moved = { 0, 0 }; 79 | break; 80 | case State::Child: 81 | spectralViewer->mouseUp(event.getEventRelativeTo(spectralViewer)); 82 | break; 83 | } 84 | 85 | spectralViewer->showProgress = true; 86 | } 87 | 88 | void SpectralEditor::mouseDrag(const juce::MouseEvent& event) { 89 | Point added{ 90 | event.getDistanceFromDragStartX(), 91 | event.getDistanceFromDragStartY(), 92 | }; 93 | 94 | switch (state) { 95 | case State::Brush: { 96 | auto curPos = spectralViewer->normalizePosition({ event.x, event.y }); 97 | editFuture = settings.editor->brush(curPos); 98 | spectralViewer->reGenerateImage(true, true); 99 | break; 100 | } 101 | case State::Selecting: 102 | dragEnd = spectralViewer->normalizePosition(spectralViewer->denormalizePosition(dragStart) + added); 103 | break; 104 | case State::Moving: { 105 | auto curMoved = spectralViewer->normalizePosition(spectralViewer->denormalizePosition(dragStart) + added) - dragStart; 106 | settings.editor->move(curMoved - moved, true); 107 | moved = curMoved; 108 | spectralViewer->reGenerateImage(true, true); 109 | break; 110 | } 111 | case State::Child: 112 | spectralViewer->mouseDrag(event.getEventRelativeTo(spectralViewer)); 113 | break; 114 | } 115 | 116 | repaint(); 117 | } 118 | 119 | // ------------------------------------------------ 120 | 121 | void SpectralEditor::mouseWheelMove(const juce::MouseEvent& event, const juce::MouseWheelDetails& wheel) { 122 | spectralViewer->mouseWheelMove(event, wheel); 123 | } 124 | 125 | // ------------------------------------------------ 126 | 127 | void SpectralEditor::paintOverChildren(juce::Graphics& g) { 128 | auto rect = selectedRect(); 129 | g.setColour(Color{ 255, 255, 255, 20 }); 130 | g.fillRect(rect); 131 | g.setColour(Color{ 255, 255, 255, 80 }); 132 | g.drawRect(rect, 1); 133 | } 134 | 135 | void SpectralEditor::onIdle() { 136 | View::onIdle(); 137 | if (editFuture.valid() && editFuture.wait_for(std::chrono::seconds(0)) == std::future_status::ready) { 138 | editFuture = {}; 139 | 140 | auto newSelect = settings.editor->selection(); 141 | dragStart = newSelect.position(); 142 | dragEnd = newSelect.position() + newSelect.size(); 143 | spectralViewer->reGenerateImage(true, true); 144 | } 145 | } 146 | 147 | Rect<> SpectralEditor::selectedRect() { 148 | Point begin = dragStart + moved; 149 | Point end = dragEnd + moved; 150 | auto beginPosition = spectralViewer->denormalizePosition(begin); 151 | auto endPosition = spectralViewer->denormalizePosition(end); 152 | auto minX = Math::min(endPosition.x(), beginPosition.x()); 153 | auto maxX = Math::max(endPosition.x(), beginPosition.x()); 154 | auto minY = Math::min(endPosition.y(), beginPosition.y()); 155 | auto maxY = Math::max(endPosition.y(), beginPosition.y()); 156 | return Rect<>{ minX, minY, maxX - minX, maxY - minY }; 157 | } 158 | 159 | void SpectralEditor::move(Point amount, bool remove) { 160 | auto move = spectralViewer->normalizePosition(spectralViewer->denormalizePosition(dragStart) + amount) - dragStart; 161 | settings.editor->move(move, remove); 162 | spectralViewer->reGenerateImage(true, true); 163 | dragStart += move; 164 | dragEnd += move; 165 | moved = { 0, 0 }; 166 | } 167 | 168 | // ------------------------------------------------ 169 | 170 | EditorView::EditorView(Context c, Settings s) 171 | : View(c), 172 | FileDropTarget(c.interface({ .index = 2 })), 173 | settings(std::move(s)) 174 | { 175 | 176 | // ------------------------------------------------ 177 | 178 | wantsIdle(true); 179 | 180 | // ------------------------------------------------ 181 | 182 | add({ .image = T.settings.background }); 183 | 184 | // ------------------------------------------------ 185 | 186 | m_LayersScrollView = &add({ Width - 74, 4, 70, Height - 8 }, { 187 | .scrollbar = T.advanced.scrollbar 188 | }); 189 | 190 | // ------------------------------------------------ 191 | 192 | spectralEditor = &add({ 4, 32, Width - 78, Height - 36 }, { 193 | .editor = context.interface() 194 | }); 195 | 196 | // ------------------------------------------------ 197 | 198 | add