├── latest ├── include.zip ├── QAN1xEditor.rc ├── resources ├── an1x.png ├── .DS_Store ├── appIcon.png ├── icon0.png ├── icon1.png ├── icon10.png ├── icon11.png ├── icon12.png ├── icon13.png ├── icon14.png ├── icon15.png ├── icon16.png ├── icon17.png ├── icon18.png ├── icon19.png ├── icon2.png ├── icon20.png ├── icon21.png ├── icon22.png ├── icon3.png ├── icon4.png ├── icon5.png ├── icon6.png ├── icon7.png ├── icon8.png ├── icon9.png ├── AN1xEdit.an1 ├── icon_fav.png ├── icon_favgray.png └── icon_favhover.png ├── screenshots ├── scr0.png ├── scr1.png ├── scr2.png ├── scr3.png ├── scr4.png └── scr5.png ├── installer ├── win │ ├── icon.ico │ └── InstallScript.iss └── macos │ ├── icon.icns │ ├── background.png │ └── InstallScript.sh ├── src ├── View │ ├── FreeFunctions.h │ ├── VoiceNameEdit.h │ ├── DbTableView.h │ ├── GlobalWidgets.h │ ├── PianoRoll │ │ ├── PianoView.h │ │ ├── PianoKey.h │ │ ├── PianoView.cpp │ │ └── PianoKey.cpp │ ├── SettingsDialog.h │ ├── MemoryList.h │ ├── AbstractController.h │ ├── FreeEG │ │ ├── EGPath.h │ │ ├── EGPath.cpp │ │ ├── FreeEGScene.h │ │ └── FreeEGScene.cpp │ ├── SceneView.h │ ├── ControlMatrixScene.h │ ├── FreeEGWidget.h │ ├── ArpSeq.h │ ├── FxEq.h │ ├── MemoryList.cpp │ ├── SettingsDialog.cpp │ ├── GlobalWidgets.cpp │ ├── Browser.h │ ├── VoiceNameEdit.cpp │ ├── FreeFunctions.cpp │ ├── QAN1xEditor.h │ ├── BrowserTableModel.h │ ├── DbTableView.cpp │ ├── ControlMatrixScene.cpp │ ├── UiWidgets.h │ ├── FreeEGWidget.cpp │ ├── SceneView.cpp │ ├── SettingsDialog.ui │ ├── BrowserTableModel.cpp │ ├── ArpSeq.cpp │ ├── UiWidgets.cpp │ ├── Browser.ui │ └── Browser.cpp ├── Model │ ├── PatchRow.h │ ├── DragDropManager.h │ ├── ClipoboardManager.h │ ├── Settings.h │ ├── DragDropManager.cpp │ ├── PatchMemory.h │ ├── PatchDatabase.h │ ├── ClipoboardManager.cpp │ ├── An1File.h │ ├── MidiMaster.h │ ├── An1xPatch.h │ ├── PatchMemory.cpp │ ├── An1File.cpp │ ├── An1x.h │ ├── PatchDatabase.cpp │ └── MidiMaster.cpp ├── main.cpp └── Database │ ├── Database.h │ └── Database.cpp ├── resource.h ├── QAn1xEditor.sln ├── LICENSE.txt ├── QAN1xEditor.qrc ├── README.md ├── .gitattributes ├── QAN1xEditor.pro ├── .gitignore ├── QMidiAn1x.vcxproj └── QMidiAn1x.vcxproj.filters /latest: -------------------------------------------------------------------------------- 1 | 9 2 | -------------------------------------------------------------------------------- /include.zip: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thefinalcutbg/QAN1xEditor/HEAD/include.zip -------------------------------------------------------------------------------- /QAN1xEditor.rc: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thefinalcutbg/QAN1xEditor/HEAD/QAN1xEditor.rc -------------------------------------------------------------------------------- /resources/an1x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thefinalcutbg/QAN1xEditor/HEAD/resources/an1x.png -------------------------------------------------------------------------------- /resources/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thefinalcutbg/QAN1xEditor/HEAD/resources/.DS_Store -------------------------------------------------------------------------------- /resources/appIcon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thefinalcutbg/QAN1xEditor/HEAD/resources/appIcon.png -------------------------------------------------------------------------------- /resources/icon0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thefinalcutbg/QAN1xEditor/HEAD/resources/icon0.png -------------------------------------------------------------------------------- /resources/icon1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thefinalcutbg/QAN1xEditor/HEAD/resources/icon1.png -------------------------------------------------------------------------------- /resources/icon10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thefinalcutbg/QAN1xEditor/HEAD/resources/icon10.png -------------------------------------------------------------------------------- /resources/icon11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thefinalcutbg/QAN1xEditor/HEAD/resources/icon11.png -------------------------------------------------------------------------------- /resources/icon12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thefinalcutbg/QAN1xEditor/HEAD/resources/icon12.png -------------------------------------------------------------------------------- /resources/icon13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thefinalcutbg/QAN1xEditor/HEAD/resources/icon13.png -------------------------------------------------------------------------------- /resources/icon14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thefinalcutbg/QAN1xEditor/HEAD/resources/icon14.png -------------------------------------------------------------------------------- /resources/icon15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thefinalcutbg/QAN1xEditor/HEAD/resources/icon15.png -------------------------------------------------------------------------------- /resources/icon16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thefinalcutbg/QAN1xEditor/HEAD/resources/icon16.png -------------------------------------------------------------------------------- /resources/icon17.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thefinalcutbg/QAN1xEditor/HEAD/resources/icon17.png -------------------------------------------------------------------------------- /resources/icon18.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thefinalcutbg/QAN1xEditor/HEAD/resources/icon18.png -------------------------------------------------------------------------------- /resources/icon19.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thefinalcutbg/QAN1xEditor/HEAD/resources/icon19.png -------------------------------------------------------------------------------- /resources/icon2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thefinalcutbg/QAN1xEditor/HEAD/resources/icon2.png -------------------------------------------------------------------------------- /resources/icon20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thefinalcutbg/QAN1xEditor/HEAD/resources/icon20.png -------------------------------------------------------------------------------- /resources/icon21.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thefinalcutbg/QAN1xEditor/HEAD/resources/icon21.png -------------------------------------------------------------------------------- /resources/icon22.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thefinalcutbg/QAN1xEditor/HEAD/resources/icon22.png -------------------------------------------------------------------------------- /resources/icon3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thefinalcutbg/QAN1xEditor/HEAD/resources/icon3.png -------------------------------------------------------------------------------- /resources/icon4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thefinalcutbg/QAN1xEditor/HEAD/resources/icon4.png -------------------------------------------------------------------------------- /resources/icon5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thefinalcutbg/QAN1xEditor/HEAD/resources/icon5.png -------------------------------------------------------------------------------- /resources/icon6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thefinalcutbg/QAN1xEditor/HEAD/resources/icon6.png -------------------------------------------------------------------------------- /resources/icon7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thefinalcutbg/QAN1xEditor/HEAD/resources/icon7.png -------------------------------------------------------------------------------- /resources/icon8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thefinalcutbg/QAN1xEditor/HEAD/resources/icon8.png -------------------------------------------------------------------------------- /resources/icon9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thefinalcutbg/QAN1xEditor/HEAD/resources/icon9.png -------------------------------------------------------------------------------- /screenshots/scr0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thefinalcutbg/QAN1xEditor/HEAD/screenshots/scr0.png -------------------------------------------------------------------------------- /screenshots/scr1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thefinalcutbg/QAN1xEditor/HEAD/screenshots/scr1.png -------------------------------------------------------------------------------- /screenshots/scr2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thefinalcutbg/QAN1xEditor/HEAD/screenshots/scr2.png -------------------------------------------------------------------------------- /screenshots/scr3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thefinalcutbg/QAN1xEditor/HEAD/screenshots/scr3.png -------------------------------------------------------------------------------- /screenshots/scr4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thefinalcutbg/QAN1xEditor/HEAD/screenshots/scr4.png -------------------------------------------------------------------------------- /screenshots/scr5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thefinalcutbg/QAN1xEditor/HEAD/screenshots/scr5.png -------------------------------------------------------------------------------- /installer/win/icon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thefinalcutbg/QAN1xEditor/HEAD/installer/win/icon.ico -------------------------------------------------------------------------------- /resources/AN1xEdit.an1: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thefinalcutbg/QAN1xEditor/HEAD/resources/AN1xEdit.an1 -------------------------------------------------------------------------------- /resources/icon_fav.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thefinalcutbg/QAN1xEditor/HEAD/resources/icon_fav.png -------------------------------------------------------------------------------- /installer/macos/icon.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thefinalcutbg/QAN1xEditor/HEAD/installer/macos/icon.icns -------------------------------------------------------------------------------- /resources/icon_favgray.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thefinalcutbg/QAN1xEditor/HEAD/resources/icon_favgray.png -------------------------------------------------------------------------------- /resources/icon_favhover.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thefinalcutbg/QAN1xEditor/HEAD/resources/icon_favhover.png -------------------------------------------------------------------------------- /installer/macos/background.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/thefinalcutbg/QAN1xEditor/HEAD/installer/macos/background.png -------------------------------------------------------------------------------- /src/View/FreeFunctions.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | namespace FreeFn { 5 | 6 | QIcon getTypeIcon(int type); 7 | bool getUpdate(); 8 | }; -------------------------------------------------------------------------------- /src/Model/PatchRow.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | struct PatchRow 4 | { 5 | long long rowid{ 0 }; 6 | bool fav {false}; 7 | int type{ 0 }; 8 | int layer{ 0 }; 9 | int effect{ 0 }; 10 | bool arp_seq{ false }; 11 | QString name; 12 | QString file; 13 | QString comment; 14 | }; 15 | -------------------------------------------------------------------------------- /src/Model/DragDropManager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | namespace DragDropManager { 7 | 8 | void droppedToDbTable(const std::vector& selectedListItems); 9 | void droppedToMemoryList(const std::set& selectedRowids, int row); 10 | 11 | }; -------------------------------------------------------------------------------- /src/Model/ClipoboardManager.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | 5 | namespace ClipboardManager 6 | { 7 | void copyRequestFromDatabase(const std::set& rowids); 8 | void copyRequestFromMemoryList(const std::vector rows); 9 | void pasteToListRequested(int row); 10 | }; -------------------------------------------------------------------------------- /src/Model/Settings.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | 4 | struct AdvancedMidiSettings { 5 | int midi_send_channel{ 1 }; 6 | bool midi_thru = false; 7 | int device_no{ 1 }; 8 | int buffer_size{ 0 }; 9 | int msDelay{ 100 }; 10 | }; 11 | 12 | struct MidiDeviceNames { 13 | std::string midi_in{ "None" }; 14 | std::string midi_out{ "None" }; 15 | }; 16 | -------------------------------------------------------------------------------- /src/View/VoiceNameEdit.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "Model/An1x.h" 5 | 6 | class VoiceNameEdit : public QLineEdit 7 | { 8 | Q_OBJECT 9 | 10 | QString default_name = "InitNormal"; 11 | 12 | public: 13 | VoiceNameEdit(QWidget *parent); 14 | void setName(AN1x::CommonParam p, int value); 15 | unsigned char getChar(int index); 16 | ~VoiceNameEdit(); 17 | }; 18 | -------------------------------------------------------------------------------- /src/View/DbTableView.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | class DbTableView : public QTableView 4 | { 5 | Q_OBJECT 6 | 7 | void dropEvent(QDropEvent* e) override; 8 | void keyPressEvent(QKeyEvent* event) override; 9 | 10 | public: 11 | DbTableView(QWidget* parent); 12 | 13 | ~DbTableView(); 14 | 15 | signals: 16 | void dataDroped(); 17 | void deletePressed(); 18 | void copyRequested(); 19 | }; -------------------------------------------------------------------------------- /src/View/GlobalWidgets.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | class QStatusBar; 4 | class Browser; 5 | 6 | namespace GlobalWidgets { 7 | 8 | enum Answer { No, Yes, Cancel }; 9 | 10 | inline QStatusBar* statusBar{ nullptr }; 11 | inline Browser* browser{nullptr}; 12 | 13 | bool askQuestion(const char* question); 14 | void showMessage(const char* message); 15 | int YesNoCancelQuestion(const char* question); //Yes = 1, No = 0, Cancel = -1 16 | } -------------------------------------------------------------------------------- /src/View/PianoRoll/PianoView.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "PianoKey.h" 5 | 6 | class PianoView : public QGraphicsView 7 | { 8 | Q_OBJECT 9 | 10 | std::array p_keys{ nullptr }; 11 | int m_velocity{ 64 }; 12 | public: 13 | PianoView(QWidget *parent); 14 | void setNote(int note, bool pressed); 15 | void setVelocity(int velocity); 16 | void setOctave(int octave); 17 | ~PianoView(); 18 | }; 19 | -------------------------------------------------------------------------------- /src/View/SettingsDialog.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include "ui_SettingsDialog.h" 6 | 7 | #include "Model/Settings.h" 8 | 9 | class SettingsDialog : public QDialog 10 | { 11 | Q_OBJECT 12 | 13 | public: 14 | SettingsDialog(const AdvancedMidiSettings& s); 15 | ~SettingsDialog(); 16 | 17 | std::optional result; 18 | 19 | private: 20 | Ui::SettingsDialogClass ui; 21 | }; 22 | -------------------------------------------------------------------------------- /src/View/MemoryList.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | class MemoryList : public QListWidget 6 | { 7 | Q_OBJECT 8 | 9 | public: 10 | MemoryList(QWidget* parent); 11 | 12 | 13 | ~MemoryList(); 14 | 15 | signals: 16 | void dataDroped(int row); 17 | void copyRequested(); 18 | void pasteRequested(); 19 | void deleteRequested(); 20 | 21 | private: 22 | void dropEvent(QDropEvent* e) override; 23 | void keyPressEvent(QKeyEvent* event) override; 24 | 25 | }; -------------------------------------------------------------------------------- /src/View/AbstractController.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Model/An1x.h" 3 | 4 | class AbstractController 5 | { 6 | protected: 7 | 8 | ParamType type = ParamType::Unknown; 9 | unsigned char parameter; 10 | int defaultValue{ 0 }; 11 | public: 12 | 13 | void setParam(ParamType t, unsigned char p) { 14 | this->type = t; 15 | this->parameter = p; 16 | }; 17 | 18 | virtual void setCurrentValueAsDefault() = 0; 19 | virtual void setValue(int value) = 0; 20 | virtual int getValue() = 0; 21 | }; 22 | 23 | -------------------------------------------------------------------------------- /resource.h: -------------------------------------------------------------------------------- 1 | //{{NO_DEPENDENCIES}} 2 | // Microsoft Visual C++ generated include file. 3 | // Used by QAN1xEditor.rc 4 | // 5 | #define IDI_ICON1 103 6 | 7 | // Next default values for new objects 8 | // 9 | #ifdef APSTUDIO_INVOKED 10 | #ifndef APSTUDIO_READONLY_SYMBOLS 11 | #define _APS_NEXT_RESOURCE_VALUE 104 12 | #define _APS_NEXT_COMMAND_VALUE 40001 13 | #define _APS_NEXT_CONTROL_VALUE 1001 14 | #define _APS_NEXT_SYMED_VALUE 101 15 | #endif 16 | #endif 17 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include "View/QAN1xEditor.h" 3 | #include "Database/Database.h" 4 | #include "View/FreeFunctions.h" 5 | 6 | int main(int argc, char* argv[]) 7 | { 8 | Db::createIfNotExist(); 9 | 10 | QApplication a(argc, argv); 11 | 12 | a.setWindowIcon(QIcon(":/icon/resources/appIcon.png")); 13 | 14 | if (FreeFn::getUpdate()) return 0; 15 | 16 | a.setWheelScrollLines(1); 17 | 18 | QAN1xEditor w; 19 | w.setWindowTitle("QAN1xEditor v1.7.0"); 20 | w.show(); 21 | 22 | return a.exec(); 23 | 24 | } 25 | -------------------------------------------------------------------------------- /src/View/FreeEG/EGPath.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | 6 | class EGPath : public QGraphicsItem 7 | { 8 | 9 | QPolygonF eg_poly; 10 | 11 | void paint(QPainter* painter, const QStyleOptionGraphicsItem* option, QWidget* widget = nullptr) override; 12 | 13 | QRectF bounds; 14 | 15 | QColor color; 16 | 17 | public: 18 | EGPath(const QColor& color); 19 | 20 | QRectF boundingRect() const override; 21 | 22 | void setPoint(int index, int value); 23 | void setTrack(const std::array& track); 24 | }; -------------------------------------------------------------------------------- /src/View/SceneView.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "ui_SceneView.h" 5 | #include "Model/An1x.h" 6 | 7 | class SceneView : public QWidget 8 | { 9 | Q_OBJECT 10 | 11 | bool isScene2 = false; 12 | 13 | static constexpr int uiParamSize = AN1x::VariFxDW; 14 | 15 | std::array ui_controls { nullptr }; 16 | 17 | public: 18 | SceneView(QWidget *parent = nullptr); 19 | 20 | void setAsScene(bool isSceen2); 21 | 22 | void setSceneParameters(AN1x::SceneParam p, int value, bool setAsDefault = false); 23 | 24 | ~SceneView(); 25 | 26 | private: 27 | Ui::SceneViewClass ui; 28 | }; 29 | -------------------------------------------------------------------------------- /src/Model/DragDropManager.cpp: -------------------------------------------------------------------------------- 1 | #include "DragDropManager.h" 2 | #include "PatchMemory.h" 3 | #include "PatchDatabase.h" 4 | 5 | void DragDropManager::droppedToDbTable(const std::vector& selectedListItems) 6 | { 7 | for (auto idx : selectedListItems) 8 | { 9 | PatchDatabase::saveVoice(PatchMemory::getPatch(idx), 0); 10 | } 11 | } 12 | 13 | void DragDropManager::droppedToMemoryList(const std::set& selectedRowids, int row) 14 | { 15 | 16 | for (auto rowid : selectedRowids) 17 | { 18 | if (row > 127) return; 19 | 20 | PatchMemory::setPatch(PatchDatabase::getPatch(rowid), row); 21 | 22 | row++; 23 | 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/View/ControlMatrixScene.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "ui_ControlMatrixScene.h" 5 | 6 | class ControlMatrixScene : public QWidget 7 | { 8 | Q_OBJECT 9 | 10 | bool isScene2 = false; 11 | 12 | static constexpr int uiControlsSize = 3 * 16; 13 | 14 | std::array ui_controls{ nullptr }; 15 | 16 | public: 17 | ControlMatrixScene(QWidget* parent = nullptr); 18 | 19 | void setAsScene(bool isScene2); 20 | 21 | void setSceneParameters(AN1x::SceneParam p, int value, bool setAsDefault = false); 22 | 23 | 24 | ~ControlMatrixScene(); 25 | 26 | Ui::ControlMatrixSceneClass ui; 27 | }; 28 | -------------------------------------------------------------------------------- /src/Model/PatchMemory.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include "An1xPatch.h" 5 | 6 | class Browser; 7 | class An1File; 8 | 9 | namespace PatchMemory { 10 | 11 | void loadFromAn1x(const std::vector& indexes); 12 | void sendToAn1x(const std::vector& indexes); 13 | void initPatches(const std::vector& indexes); 14 | void patchRecieved(const AN1xPatch& patch); 15 | void patchSent(); 16 | void loadAn1xMemPatch(int index); 17 | void rowMoved(int from, int to); 18 | void setPatch(const AN1xPatch& patch, int row); 19 | void loadAn1File(const An1File& file); 20 | AN1xPatch& getPatch(int index); 21 | std::vector getFile(); 22 | }; 23 | -------------------------------------------------------------------------------- /src/View/FreeEGWidget.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "ui_FreeEGWidget.h" 5 | 6 | class FreeEGScene; 7 | 8 | class FreeEGWidget : public QWidget 9 | { 10 | Q_OBJECT 11 | 12 | static constexpr int ui_size = AN1x::CommonParam::FreeEgData - AN1x::CommonParam::FreeEGTrigger; 13 | 14 | std::array ui_controls{ nullptr }; 15 | 16 | FreeEGScene* scene; 17 | 18 | public: 19 | FreeEGWidget(QWidget *parent = nullptr); 20 | ~FreeEGWidget(); 21 | 22 | void setCommonParameter(AN1x::CommonParam p, int value); 23 | 24 | std::vector getFreeEGData(); 25 | void setTrackData(const std::vector& data); 26 | 27 | Ui::FreeEGWidgetClass ui; 28 | }; 29 | -------------------------------------------------------------------------------- /src/View/PianoRoll/PianoKey.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | class PianoKey : public QGraphicsRectItem 6 | { 7 | int idx; 8 | bool m_black; 9 | 10 | bool m_highlighted{ false }; 11 | bool m_pressed{ false }; 12 | 13 | void mousePressEvent(QGraphicsSceneMouseEvent* event) override; 14 | void mouseReleaseEvent(QGraphicsSceneMouseEvent* event) override; 15 | 16 | int m_velocity{ 64 }; 17 | 18 | public: 19 | PianoKey(int idx); 20 | bool isBlack() { return m_black; }; 21 | bool isWhite() { return !m_black; }; 22 | 23 | void noteOn(); 24 | void noteOff(); 25 | 26 | void highlight(bool highlighted); 27 | 28 | void setVelocity(int velocity) { m_velocity = velocity; } 29 | 30 | ~PianoKey(); 31 | }; 32 | -------------------------------------------------------------------------------- /src/View/ArpSeq.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "ui_ArpSeq.h" 5 | 6 | class ArpSeq : public QWidget 7 | { 8 | Q_OBJECT 9 | 10 | 11 | static constexpr int ui_size = AN1x::NullCommon23 - AN1x::ArpSeqOnOff; 12 | 13 | std::array ui_controls{ nullptr }; 14 | std::array seq_controls{ nullptr }; 15 | std::array p_steps; 16 | 17 | void setArpLayout(bool arp); 18 | 19 | 20 | public: 21 | ArpSeq(QWidget *parent = nullptr); 22 | void setCommonParameter(AN1x::CommonParam p, int value, bool setAsDefault = false); 23 | void setSequenceParameter(AN1x::SeqParam p, int value, bool setAsDefault = false); 24 | 25 | ~ArpSeq(); 26 | 27 | Ui::ArpSeqClass ui; 28 | 29 | private: 30 | 31 | }; 32 | -------------------------------------------------------------------------------- /src/View/FxEq.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "ui_FxEq.h" 5 | #include "Model/MidiMaster.h" 6 | #include 7 | 8 | class FxEq : public QWidget 9 | { 10 | Q_OBJECT 11 | 12 | static constexpr int ui_size = AN1x::NullCommon22 - AN1x::VariFXType; 13 | 14 | std::array ui_controls{ nullptr }; 15 | 16 | void setFxLayout(int value); 17 | void setDelayLayout(int value); 18 | void setReverbLayout(); 19 | void setEqLayout(); 20 | 21 | public: 22 | FxEq(QWidget *parent = nullptr); 23 | 24 | void setCommonParameter(AN1x::CommonParam p, int value, bool setAsDefault = false); 25 | void setSceneParameter(AN1x::SceneParam p, int value, bool isScene2, bool setAsDefault = false); 26 | 27 | Ui::FxEq ui; 28 | 29 | ~FxEq(); 30 | 31 | private: 32 | 33 | }; 34 | -------------------------------------------------------------------------------- /src/View/MemoryList.cpp: -------------------------------------------------------------------------------- 1 | #include "MemoryList.h" 2 | #include 3 | 4 | MemoryList::MemoryList(QWidget* parent) : QListWidget(parent) 5 | { 6 | 7 | } 8 | 9 | void MemoryList::dropEvent(QDropEvent* e) 10 | { 11 | if (e->source() == this) { 12 | QListWidget::dropEvent(e); 13 | return; 14 | } 15 | 16 | emit dataDroped(row(itemAt(e->position().toPoint()))); 17 | } 18 | 19 | void MemoryList::keyPressEvent(QKeyEvent* event) 20 | { 21 | switch (event->key()) 22 | { 23 | case Qt::Key_Delete: 24 | emit deleteRequested(); 25 | return; 26 | case Qt::Key_C: 27 | if (event->modifiers() & Qt::ControlModifier) { 28 | emit copyRequested(); 29 | return; 30 | } 31 | break; 32 | 33 | case Qt::Key_V: 34 | if (event->modifiers() & Qt::ControlModifier) { 35 | emit pasteRequested(); 36 | return; 37 | } 38 | break; 39 | } 40 | 41 | QListWidget::keyPressEvent(event); 42 | } 43 | 44 | 45 | MemoryList::~MemoryList() 46 | { 47 | } 48 | -------------------------------------------------------------------------------- /src/View/SettingsDialog.cpp: -------------------------------------------------------------------------------- 1 | #include "SettingsDialog.h" 2 | 3 | SettingsDialog::SettingsDialog(const AdvancedMidiSettings& s) : QDialog(nullptr) 4 | { 5 | ui.setupUi(this); 6 | 7 | setWindowTitle("MIDI Settings"); 8 | 9 | ui.deviceSpin->setMinimumWidth(40); 10 | 11 | ui.buffer->setValue(s.buffer_size); 12 | ui.thruCheck->setChecked(s.midi_thru); 13 | ui.delay->setValue(s.msDelay); 14 | ui.deviceSpin->setValue(s.device_no); 15 | ui.sendSpin->setValue(s.midi_send_channel); 16 | ui.deviceSpin->setSpecialValueText("All"); 17 | 18 | connect(ui.okButton, &QPushButton::clicked, this, [&] { 19 | 20 | result = AdvancedMidiSettings{ 21 | .midi_send_channel = ui.sendSpin->value(), 22 | .midi_thru = ui.thruCheck->isChecked(), 23 | .device_no = ui.deviceSpin->value(), 24 | .buffer_size = ui.buffer->value(), 25 | .msDelay = ui.delay->value() 26 | }; 27 | 28 | close(); 29 | }); 30 | 31 | } 32 | 33 | SettingsDialog::~SettingsDialog() 34 | {} 35 | -------------------------------------------------------------------------------- /src/View/GlobalWidgets.cpp: -------------------------------------------------------------------------------- 1 | #include "GlobalWidgets.h" 2 | #include 3 | 4 | bool GlobalWidgets::askQuestion(const char* question) 5 | { 6 | QMessageBox::StandardButton reply; 7 | 8 | reply = QMessageBox::question(nullptr, "QAN1xEditor", question, 9 | QMessageBox::Yes | QMessageBox::No); 10 | 11 | return reply != QMessageBox::No; 12 | } 13 | 14 | void GlobalWidgets::showMessage(const char* message) 15 | { 16 | QMessageBox::information(nullptr, "QAN1xEditor", message,QMessageBox::Ok); 17 | return; 18 | } 19 | 20 | 21 | int GlobalWidgets::YesNoCancelQuestion(const char* question) 22 | { 23 | QMessageBox::StandardButton reply; 24 | 25 | reply = QMessageBox::question(nullptr, "QAN1xEditor", question, 26 | QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel); 27 | 28 | switch (reply) { 29 | case QMessageBox::No: return 0; 30 | case QMessageBox::Yes: return 1; 31 | case QMessageBox::Cancel: return -1; 32 | } 33 | 34 | return -1; 35 | } 36 | -------------------------------------------------------------------------------- /src/Model/PatchDatabase.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "An1xPatch.h" 4 | #include "Settings.h" 5 | 6 | class Browser; 7 | 8 | 9 | namespace PatchDatabase 10 | { 11 | void refreshTableView(); 12 | 13 | void setVoiceAsCurrent(long long rowid); 14 | 15 | void deleteSelectedPatches(const std::set& rowids); 16 | 17 | void loadAn1FileToBuffer(const std::vector& data, const std::string& filename = {}); 18 | 19 | void setFavourite(bool fav, long long rowid); 20 | 21 | void importFileBufferToDb(); 22 | 23 | void saveVoice(const AN1xPatch& p, long long rowid); 24 | 25 | AN1xPatch getPatch(long long rowid); 26 | 27 | void updateComment(const std::string& comment, const std::set& rowids); 28 | 29 | void importExternalDb(const std::string& filepath); 30 | 31 | void setMidiSettings(const MidiDeviceNames& devices, const AdvancedMidiSettings& settings); 32 | 33 | std::pair getMidiSettings(); 34 | } 35 | -------------------------------------------------------------------------------- /src/Model/ClipoboardManager.cpp: -------------------------------------------------------------------------------- 1 | #include "ClipoboardManager.h" 2 | #include "An1xPatch.h" 3 | #include "PatchDatabase.h" 4 | #include "PatchMemory.h" 5 | 6 | std::vector s_clipboard; 7 | 8 | void ClipboardManager::copyRequestFromDatabase(const std::set& rowids) 9 | { 10 | s_clipboard.clear(); 11 | 12 | int counter = 0; 13 | 14 | for (auto rowid : rowids) { 15 | 16 | if (counter >= 128) return; 17 | 18 | s_clipboard.push_back(PatchDatabase::getPatch(rowid)); 19 | counter++; 20 | } 21 | 22 | } 23 | 24 | void ClipboardManager::copyRequestFromMemoryList(const std::vector rows) 25 | { 26 | s_clipboard.clear(); 27 | 28 | for (auto row : rows) { 29 | 30 | if (row < 0 || row > 127) continue; 31 | 32 | s_clipboard.push_back(PatchMemory::getPatch(row)); 33 | } 34 | } 35 | 36 | void ClipboardManager::pasteToListRequested(int row) 37 | { 38 | if (row < 0 || row > 127) return; 39 | 40 | for (size_t i = 0; i < s_clipboard.size(); i++) { 41 | PatchMemory::setPatch(s_clipboard[i], i + row); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/View/FreeEG/EGPath.cpp: -------------------------------------------------------------------------------- 1 | #include "EGPath.h" 2 | #include 3 | 4 | EGPath::EGPath(const QColor& color) : bounds(0, 0, 1148, 256), color(color) 5 | { 6 | for (int i = 0; i < 192; i++) 7 | { 8 | eg_poly.append(QPointF(i * 6, 128)); 9 | } 10 | } 11 | 12 | void EGPath::paint(QPainter* painter, const QStyleOptionGraphicsItem*, QWidget*) 13 | { 14 | painter->setRenderHint(QPainter::Antialiasing); 15 | 16 | painter->setOpacity(0.5); 17 | 18 | QPen pen; pen.setColor(color); pen.setWidth(5); 19 | pen.setCosmetic(true); 20 | pen.setJoinStyle(Qt::PenJoinStyle::RoundJoin); 21 | painter->setPen(pen); 22 | 23 | QPainterPath path1; path1.addPolygon(eg_poly); 24 | painter->drawPath(path1); 25 | } 26 | 27 | 28 | QRectF EGPath::boundingRect() const 29 | { 30 | return bounds; 31 | } 32 | 33 | void EGPath::setPoint(int index, int value) 34 | { 35 | eg_poly[index].setY((value-128)*-1); 36 | update(); 37 | } 38 | 39 | void EGPath::setTrack(const std::array& track) 40 | { 41 | for (size_t i = 0; i < track.size(); i++) setPoint(i, track[i]); 42 | } 43 | -------------------------------------------------------------------------------- /QAn1xEditor.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.5.33424.131 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "QMidiAn1x", "QMidiAn1x.vcxproj", "{74CB02DB-461F-455B-B145-6ECDB581FE9B}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|x64 = Debug|x64 11 | Release|x64 = Release|x64 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {74CB02DB-461F-455B-B145-6ECDB581FE9B}.Debug|x64.ActiveCfg = Debug|x64 15 | {74CB02DB-461F-455B-B145-6ECDB581FE9B}.Debug|x64.Build.0 = Debug|x64 16 | {74CB02DB-461F-455B-B145-6ECDB581FE9B}.Release|x64.ActiveCfg = Release|x64 17 | {74CB02DB-461F-455B-B145-6ECDB581FE9B}.Release|x64.Build.0 = Release|x64 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | GlobalSection(ExtensibilityGlobals) = postSolution 23 | SolutionGuid = {099DFFAE-C59F-4DAE-9C81-E3D82174192B} 24 | EndGlobalSection 25 | EndGlobal 26 | -------------------------------------------------------------------------------- /installer/macos/InstallScript.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | APP="$PWD/../files/QAN1xEditor.app" #the compiled bundle 4 | INSTALLER="$PWD/../compiled/qan1xeditor-1.7.0-macos.dmg" 5 | QDEPLOY="$PWD/../../../Qt/6.5.3/macos/bin/macdeployqt" #macdeployqt location 6 | 7 | $QDEPLOY $APP -sign-for-notarization="Developer ID Application: Hristo Konstantinov" #deploying the app 8 | 9 | #deleting the old dmg installer 10 | Rm -R $INSTALLER 11 | 12 | #creating dmg installer using create-dmg 13 | create-dmg \ 14 | --volname "QAN1xEditor Installer" \ 15 | --volicon "icon.icns" \ 16 | --background "background.png" \ 17 | --window-pos 200 110 \ 18 | --window-size 605 360 \ 19 | --icon-size 72 \ 20 | --icon "QAN1xEditor.app" 250 190 \ 21 | --app-drop-link 460 190 \ 22 | --hide-extension "QAN1xEditor.app" \ 23 | $INSTALLER \ 24 | "$PWD/../files/" 25 | 26 | codesign --options runtime --timestamp -s "Developer ID Application: Hristo Konstantinov" $INSTALLER 27 | 28 | xcrun notarytool submit $INSTALLER --keychain-profile "DeployProfile" --wait 29 | 30 | xcrun stapler staple $INSTALLER 31 | 32 | spctl --assess -vv --type install $INSTALLER 33 | 34 | rm -R $APP 35 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) [2024] [Hristo Konstaninov] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 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 | -------------------------------------------------------------------------------- /src/View/Browser.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "ui_Browser.h" 5 | #include "BrowserTableModel.h" 6 | #include 7 | #include 8 | class Browser : public QWidget 9 | { 10 | Q_OBJECT 11 | 12 | BrowserTableModel model; 13 | QSortFilterProxyModel column_sort; 14 | QSortFilterProxyModel search; 15 | public: 16 | Browser(QWidget *parent = nullptr); 17 | 18 | void setPatchToListView(int idx, const std::string& name, int type); 19 | 20 | QString generatePatchText(int index, const char* name); 21 | 22 | void setProgressBarCount(int count); 23 | void incrementProgressBar(); 24 | 25 | void setPatchesToTableView(const std::vector& patches); 26 | 27 | void scrollToBottom(); 28 | 29 | void refreshCountLabel(); 30 | 31 | ~Browser(); 32 | 33 | private: 34 | 35 | void recalculateListNames(); 36 | std::vector getSelectedListIndexes(); 37 | std::set getSelectedTableRowids(); 38 | void importAN1FileButtonClicked(); 39 | void importAN2FileButtonClicked(); 40 | void loadAN1FileToList(); 41 | void exportAN2File(); 42 | void editComment(); 43 | void disableWidgets(bool disabled); 44 | void saveAN1File(); 45 | 46 | Ui::BrowserClass ui; 47 | }; 48 | -------------------------------------------------------------------------------- /src/View/VoiceNameEdit.cpp: -------------------------------------------------------------------------------- 1 | #include "VoiceNameEdit.h" 2 | #include "Model/MidiMaster.h" 3 | 4 | VoiceNameEdit::VoiceNameEdit(QWidget *parent) 5 | : QLineEdit(parent) 6 | { 7 | setText("InitNormal"); 8 | 9 | connect(this, &QLineEdit::textChanged, 10 | [&](const QString& text) { 11 | auto result = text.toStdString(); 12 | 13 | for (size_t i = 0; i < 10; i++) 14 | { 15 | MidiMaster::parameterChanged(ParamType::Common, (AN1x::CommonParam)i, i >= result.size() ? ' ' : result[i]); 16 | } 17 | } 18 | ); 19 | } 20 | 21 | 22 | void VoiceNameEdit::setName(AN1x::CommonParam p, int value) 23 | { 24 | blockSignals(true); 25 | size_t position = (int)p; 26 | 27 | auto currentText = text().toStdString(); 28 | 29 | if (position > currentText.size()) { 30 | 31 | while (currentText.size() != position + 1) { 32 | currentText +=' '; 33 | } 34 | } 35 | 36 | currentText[p] = (char)value; 37 | 38 | setText(currentText.c_str()); 39 | 40 | blockSignals(false); 41 | } 42 | 43 | unsigned char VoiceNameEdit::getChar(int index) 44 | { 45 | auto result = text(); 46 | 47 | while (result.length() < 10) result.append(" "); 48 | 49 | return result.toStdString()[index]; 50 | } 51 | 52 | VoiceNameEdit::~VoiceNameEdit() 53 | {} 54 | -------------------------------------------------------------------------------- /src/Model/An1File.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include "An1xPatch.h" 4 | 5 | class An1File 6 | { 7 | 8 | static constexpr int fileSize = 226230; 9 | 10 | static constexpr int dataStart = 9014; //where the first common parameter of the first patch is located 11 | static constexpr int fileFreeEGsize = 768; 12 | 13 | static constexpr int fileCommonAndSceneSize = AN1xPatch::CommonSize + AN1xPatch::SceneSize * 2 - fileFreeEGsize; //FreeEG parameters are stored in 1 byte instead of 2 14 | static constexpr int fileCommonSize = AN1xPatch::CommonSize - fileFreeEGsize; 15 | static constexpr int firstPatchSeqBegin = 150326; 16 | 17 | static constexpr int firstCommentbegin = 159286; 18 | 19 | std::vector m_data; 20 | 21 | int getCommonOffset(int index) const; 22 | int getFreeEGOffset(int index) const; 23 | int getSceneOffset(int index) const; 24 | int getSequencerOffset(int index) const; 25 | int getCommentOffset(int index) const; 26 | 27 | public: 28 | 29 | const std::string filename; 30 | 31 | An1File(const std::vector bytes, const std::string& filename); 32 | An1File(); 33 | void setPatch(const AN1xPatch& p, int index); 34 | AN1xPatch getPatch(int index) const; 35 | std::string getComment(int index) const; 36 | constexpr int patchCount() const { return 128; } 37 | std::vector getFileData() const; 38 | }; -------------------------------------------------------------------------------- /src/View/FreeEG/FreeEGScene.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include "EGPath.h" 6 | #include 7 | 8 | typedef std::array EGTrack; 9 | 10 | class FreeEGScene : public QGraphicsScene 11 | { 12 | Q_OBJECT 13 | 14 | QGraphicsRectItem* rects[192]; 15 | 16 | void mouseMoveEvent(QGraphicsSceneMouseEvent* e) override; 17 | void mousePressEvent(QGraphicsSceneMouseEvent* e) override; 18 | void mouseReleaseEvent(QGraphicsSceneMouseEvent* e) override; 19 | void processPosition(QPointF pos, bool reset); 20 | 21 | int m_currentIndex {0}; 22 | 23 | bool m_resetToZero {false}; 24 | 25 | EGPath* path[4]; 26 | EGTrack track[4]{ {0},{0},{0},{0} }; 27 | EGTrack default_track[4]{ {0},{0},{0},{0} }; 28 | 29 | EGPath& currentPath() { return *path[m_currentIndex]; } 30 | EGTrack& currentTrack() { return track[m_currentIndex]; } 31 | EGTrack& defaultTrack() { return default_track[m_currentIndex]; } 32 | 33 | 34 | public: 35 | 36 | FreeEGScene(QObject *parent); 37 | 38 | void setCurrentIndex(int index); 39 | std::vector getFreeEGData(); 40 | void setResetMode(bool resetToZero) { m_resetToZero = resetToZero; }; 41 | void setTrackData(const std::vector& trackData); 42 | void quickAssign(int from, int to, int value); 43 | 44 | ~FreeEGScene(); 45 | 46 | 47 | signals: 48 | void editingFinished(); 49 | }; 50 | -------------------------------------------------------------------------------- /QAN1xEditor.qrc: -------------------------------------------------------------------------------- 1 | 2 | 3 | resources/icon0.png 4 | resources/icon1.png 5 | resources/icon2.png 6 | resources/icon3.png 7 | resources/icon4.png 8 | resources/icon5.png 9 | resources/icon6.png 10 | resources/icon7.png 11 | resources/icon8.png 12 | resources/icon9.png 13 | resources/icon10.png 14 | resources/icon11.png 15 | resources/icon12.png 16 | resources/icon13.png 17 | resources/icon14.png 18 | resources/icon15.png 19 | resources/icon16.png 20 | resources/icon17.png 21 | resources/icon18.png 22 | resources/icon19.png 23 | resources/icon20.png 24 | resources/icon21.png 25 | resources/icon22.png 26 | resources/appIcon.png 27 | resources/icon_fav.png 28 | resources/icon_favgray.png 29 | resources/icon_favhover.png 30 | 31 | 32 | resources/an1x.png 33 | resources/AN1xEdit.an1 34 | 35 | 36 | -------------------------------------------------------------------------------- /src/View/PianoRoll/PianoView.cpp: -------------------------------------------------------------------------------- 1 | #include "PianoView.h" 2 | 3 | PianoView::PianoView(QWidget *parent) 4 | : QGraphicsView(parent) 5 | { 6 | 7 | QGraphicsScene* scene = new QGraphicsScene; 8 | 9 | int xPos = 0; 10 | 11 | for (size_t i = 0; i < p_keys.size(); i++) 12 | { 13 | auto& key = p_keys[i]; 14 | 15 | key = new PianoKey(i); 16 | 17 | bool isBlack = key->isBlack(); 18 | 19 | scene->addItem(key); 20 | 21 | key->setZValue(isBlack); 22 | 23 | 24 | if (i > 0) { 25 | 26 | auto& currentKey = p_keys[i]; 27 | auto& prevKey = p_keys[i-1]; 28 | 29 | if (currentKey->isWhite() && prevKey->isWhite()) 30 | { 31 | xPos += 15; 32 | } 33 | else if (currentKey->isBlack() && prevKey->isWhite()) 34 | { 35 | xPos += 10; 36 | } 37 | else 38 | { 39 | xPos += 5; 40 | } 41 | 42 | } 43 | 44 | key->setPos(xPos, 0); 45 | } 46 | 47 | 48 | 49 | setScene(scene); 50 | } 51 | 52 | void PianoView::setNote(int note, bool pressed) 53 | { 54 | pressed ? 55 | p_keys[note]->noteOn() 56 | : 57 | p_keys[note]->noteOff(); 58 | } 59 | 60 | void PianoView::setVelocity(int velocity) 61 | { 62 | for (auto& key : p_keys) 63 | { 64 | if (key == nullptr) return; 65 | key->setVelocity(velocity); 66 | } 67 | } 68 | 69 | void PianoView::setOctave(int octave) 70 | { 71 | octave += 2; 72 | 73 | for (auto key : p_keys) { 74 | key->noteOff(); 75 | key->highlight(false); 76 | } 77 | 78 | if (octave < 0 || octave > 10) return; 79 | 80 | int begin = 12 * octave; 81 | 82 | for (size_t i = begin; i < begin + 20 && i < p_keys.size(); i++) 83 | { 84 | p_keys[i]->highlight(true); 85 | } 86 | 87 | } 88 | 89 | PianoView::~PianoView() 90 | {} 91 | -------------------------------------------------------------------------------- /src/View/FreeFunctions.cpp: -------------------------------------------------------------------------------- 1 | #include "FreeFunctions.h" 2 | #include 3 | #include 4 | #include 5 | #include "GlobalWidgets.h" 6 | #include 7 | 8 | std::vector s_icons; 9 | bool icon_arr_init = false; 10 | 11 | QIcon FreeFn::getTypeIcon(int type) { 12 | 13 | if (!icon_arr_init) { 14 | 15 | for (int i = 0; i < 23; i++) { 16 | 17 | QString qrcPath = ":/icon/resources/icon"; 18 | qrcPath += QString::number(i); 19 | qrcPath += ".png"; 20 | 21 | s_icons.push_back(QIcon(qrcPath)); 22 | } 23 | 24 | icon_arr_init = true; 25 | } 26 | 27 | if (type < 0 || type > 22) type = 0; 28 | 29 | return s_icons[type]; 30 | } 31 | 32 | 33 | 34 | 35 | 36 | bool FreeFn::getUpdate() 37 | { 38 | 39 | QNetworkAccessManager mngr; 40 | 41 | 42 | QNetworkRequest request(QUrl{ "https://raw.githubusercontent.com/thefinalcutbg/QAN1xEditor/master/latest" }); 43 | 44 | auto reply = mngr.get(request); 45 | 46 | QEventLoop loop; 47 | QObject::connect(reply, &QNetworkReply::finished, &loop, &QEventLoop::quit); 48 | loop.exec(); 49 | 50 | bool ok; 51 | 52 | int latest_ver = reply->readAll().toInt(&ok); 53 | 54 | reply->deleteLater(); 55 | 56 | //Current version 57 | constexpr int current_ver = 9; 58 | 59 | if (!ok || current_ver >= latest_ver) return false; 60 | 61 | if(!GlobalWidgets::askQuestion("A new version is available. Do you want to go to download page?")) return false; 62 | 63 | QDesktopServices::openUrl(QUrl("https://github.com/thefinalcutbg/QAN1xEditor/releases/latest", QUrl::TolerantMode)); 64 | 65 | return true; 66 | } 67 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # QAN1xEditor 2 | An editor for the Yamaha AN1x synthesizer written in C++ and Qt 3 | 4 | ## Table of Contents 5 | * [General Info](#general-information) 6 | * [Frameworks and Libraries Used](#frameworks-and-libraries-used) 7 | * [Setup](#setup) 8 | * [Project Status](#project-status) 9 | * [Contact](#contact) 10 | * [License](#license) 11 | 12 | ## General Information 13 | 14 | QAN1x is a free open-source software written in C++ and Qt. It allows users to edit most of the voice parameters from the UI and use the computer keyboard and mouse to play notes. 15 | 16 | ## Frameworks and Libraries Used 17 | - [Qt6 Framework](https://www.qt.io/) 18 | - [QMidi](https://github.com/thomasgeissl/QMidi) 19 | - [RtMidi](https://github.com/thestk/rtmidi) 20 | - [Sqlite3](https://www.sqlite.org/index.html) 21 | 22 | ## Screenshots 23 | ![Alt text](/screenshots/scr0.png?raw=true "Patch Browser") 24 | ![Alt text](/screenshots/scr5.png?raw=true "Free EG") 25 | ![Alt text](/screenshots/scr1.png?raw=true "Scene Controls") 26 | ![Alt text](/screenshots/scr2.png?raw=true "Effects section") 27 | ![Alt text](/screenshots/scr3.png?raw=true "Arpeggiator and Sequencer") 28 | ![Alt text](/screenshots/scr4.png?raw=true "Control Matrix") 29 | 30 | ## Setup 31 | First you need to download Qt6 Framework. Then be sure to extract the Include folder from Include.zip (it contains sqLite3, QMidi and RtMidi source files). On Windows use the Visual Studio solution (be sure to install the Qt plugin for Visual Studio). For Linux and MacOS - use the included *.pro file with Qt Creator. 32 | 33 | ## Contact 34 | Created by [@TheFinalCut](https://github.com/thefinalcutbg) - feel free to contact me! 35 | 36 | ## License 37 | This project is open source and available under the MIT license 38 | 39 | -------------------------------------------------------------------------------- /src/View/QAN1xEditor.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "ui_QAN1xEditor.h" 5 | #include "Model/Settings.h" 6 | 7 | class AN1xPatch; 8 | 9 | class QAN1xEditor : public QMainWindow 10 | { 11 | Q_OBJECT 12 | 13 | bool eventFilter(QObject* obj, QEvent* event); 14 | 15 | std::array ui_controls{ nullptr }; 16 | std::array system_controls{ nullptr }; 17 | 18 | void setSystemParameter(AN1x::SystemParam p, int value); 19 | void setSceneParameter(AN1x::SceneParam p, int value, bool isScene2, bool setAsDefault = false); 20 | void setCommonParameter(AN1x::CommonParam p, int value, bool setAsDefault = false); 21 | void setSequenceParameter(AN1x::SeqParam p, int value, bool setAsDefault = false); 22 | void initializeInitMenu(); 23 | void setBypass(); 24 | unsigned char layerMode(); 25 | void closeEvent(QCloseEvent* event) override; 26 | 27 | public: 28 | QAN1xEditor(QWidget *parent = nullptr); 29 | void setPatch(const AN1xPatch& patch); 30 | void setMidiDevices(const QStringList& in, const QStringList& out); 31 | void setParameter(ParamType type, unsigned char param, int value, bool setAsDefault = false); 32 | void setModWheel(int value); 33 | void setTrackData(const std::vector& trackData); 34 | void enableSaveButton(bool enable); 35 | Browser* browser(); 36 | PianoView* pianoRoll(); 37 | 38 | MidiDeviceNames getCurrentDevices() const; 39 | 40 | void setMidiDeviceNames(const MidiDeviceNames& devices); 41 | 42 | //two byte values are represented by int and 0 and has to be separated; 43 | 44 | ~QAN1xEditor(); 45 | 46 | private: 47 | Ui::QAN1xEditor ui; 48 | }; 49 | -------------------------------------------------------------------------------- /src/View/BrowserTableModel.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "Model/PatchRow.h" 5 | #include 6 | #include 7 | #include 8 | 9 | class BrowserTableModel : public QAbstractTableModel 10 | { 11 | Q_OBJECT 12 | 13 | std::vector list; 14 | 15 | QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override; 16 | 17 | QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override; 18 | 19 | Qt::ItemFlags flags(const QModelIndex& index) const override; 20 | 21 | Qt::DropActions supportedDropActions() const override; 22 | 23 | 24 | public: 25 | BrowserTableModel() {} 26 | void setPatchData(const std::vector& rows); 27 | int rowCount(const QModelIndex& = QModelIndex()) const override { return list.size(); } 28 | int columnCount(const QModelIndex& = QModelIndex()) const override { return 13; } 29 | 30 | }; 31 | 32 | class QMouseEvent; 33 | 34 | class FavButtonDelegate : public QItemDelegate 35 | { 36 | Q_OBJECT 37 | 38 | QIcon star_yellow{ ":/icon/resources/icon_fav.png" }; 39 | QIcon star_gray{ ":/icon/resources/icon_favgray.png" }; 40 | QIcon star_hover{ ":/icon/resources/icon_favhover.png" }; 41 | 42 | int m_row_hover = -1; 43 | 44 | bool mouseIsOnStar(QMouseEvent* e, const QStyleOptionViewItem& option); 45 | 46 | public: 47 | FavButtonDelegate(QObject* parent = 0); 48 | void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const; 49 | bool editorEvent(QEvent* event, QAbstractItemModel* model, const QStyleOptionViewItem& option, const QModelIndex& index); 50 | 51 | signals: 52 | void favouriteClicked(int row); 53 | void updateRequested(); 54 | }; 55 | -------------------------------------------------------------------------------- /src/View/PianoRoll/PianoKey.cpp: -------------------------------------------------------------------------------- 1 | #include "PianoKey.h" 2 | #include 3 | #include 4 | #include "Model/MidiMaster.h" 5 | #include 6 | 7 | void PianoKey::mousePressEvent(QGraphicsSceneMouseEvent* e) 8 | { 9 | if (e->button() == Qt::LeftButton) { 10 | MidiMaster::setNote(idx, true, m_velocity); 11 | } 12 | } 13 | 14 | void PianoKey::mouseReleaseEvent(QGraphicsSceneMouseEvent* e) 15 | { 16 | if (e->button() == Qt::LeftButton) { 17 | MidiMaster::setNote(idx, false, m_velocity); 18 | } 19 | } 20 | 21 | PianoKey::PianoKey(int idx) : idx(idx) 22 | { 23 | setAcceptHoverEvents(true); 24 | 25 | int position = idx % 12; 26 | 27 | if (position == 0 || position == 2 || position == 4 || position == 5 || position == 7 || position == 9 || position == 11) 28 | { 29 | m_black = false; 30 | } 31 | else 32 | { 33 | m_black = true; 34 | }; 35 | 36 | QPen pen; 37 | pen.setColor(Qt::black); 38 | 39 | setPen(pen); 40 | setBrush(m_black ? Qt::black : Qt::white); 41 | 42 | setRect(m_black ? 43 | QRect(0, 0, 10, 30) 44 | : 45 | QRect(0, 0, 15, 50) 46 | ); 47 | } 48 | 49 | 50 | void PianoKey::noteOn() 51 | { 52 | setBrush(Qt::gray); 53 | update(); 54 | } 55 | 56 | void PianoKey::noteOff() 57 | { 58 | if (m_black) { 59 | setBrush(Qt::black); 60 | return; 61 | } 62 | 63 | m_highlighted ? setBrush(QColor(214, 220, 241)) : setBrush(Qt::white); 64 | 65 | update(); 66 | } 67 | 68 | void PianoKey::highlight(bool highlighted) 69 | { 70 | m_highlighted = highlighted; 71 | 72 | if (m_black) return; 73 | 74 | m_highlighted ? setBrush(QColor(214, 220, 241)) : setBrush(Qt::white); 75 | 76 | update(); 77 | } 78 | 79 | PianoKey::~PianoKey() 80 | {} 81 | -------------------------------------------------------------------------------- /src/View/DbTableView.cpp: -------------------------------------------------------------------------------- 1 | #include "DbTableView.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | class NoFocusDelegate : public QStyledItemDelegate 9 | { 10 | protected: 11 | void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const 12 | { 13 | QStyleOptionViewItem itemOption(option); 14 | if (itemOption.state & QStyle::State_HasFocus) 15 | itemOption.state = itemOption.state ^ QStyle::State_HasFocus; 16 | QStyledItemDelegate::paint(painter, itemOption, index); 17 | } 18 | 19 | }; 20 | 21 | DbTableView::DbTableView(QWidget* parent) : QTableView(parent) 22 | { 23 | 24 | setItemDelegate(new NoFocusDelegate); 25 | setSelectionBehavior(QAbstractItemView::SelectRows); 26 | 27 | horizontalHeader()->setHighlightSections(false); 28 | 29 | verticalHeader()->setVisible(false); 30 | 31 | setEditTriggers(QAbstractItemView::EditTrigger::NoEditTriggers); 32 | viewport()->setFocusPolicy(Qt::FocusPolicy::StrongFocus); 33 | 34 | setShowGrid(true); 35 | 36 | verticalHeader()->setDefaultSectionSize(20); 37 | setVerticalScrollMode(QAbstractItemView::ScrollPerPixel); 38 | horizontalHeader()->setStretchLastSection(true); 39 | 40 | setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff); 41 | } 42 | 43 | void DbTableView::dropEvent(QDropEvent* e) 44 | { 45 | if (e->source() == this) return; 46 | 47 | emit dataDroped(); 48 | } 49 | 50 | void DbTableView::keyPressEvent(QKeyEvent* event) 51 | { 52 | switch (event->key()) 53 | { 54 | case Qt::Key_Delete: 55 | emit deletePressed(); 56 | break; 57 | case Qt::Key_C: 58 | if (event->modifiers() & Qt::ControlModifier) { 59 | emit copyRequested(); 60 | } 61 | else { 62 | QTableView::keyPressEvent(event); 63 | } 64 | 65 | default: 66 | QTableView::keyPressEvent(event); 67 | } 68 | } 69 | 70 | DbTableView::~DbTableView() 71 | { 72 | } 73 | -------------------------------------------------------------------------------- /src/Database/Database.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | struct sqlite3; 6 | struct sqlite3_stmt; 7 | 8 | class Db 9 | { 10 | sqlite3* db_connection{ nullptr }; 11 | bool m_connectionOwned{ false }; 12 | 13 | int total_bindings{ 0 }; 14 | int successful_bindings{ 0 }; 15 | 16 | sqlite3_stmt* stmt{ nullptr }; 17 | 18 | static inline bool s_showError{ false }; 19 | 20 | 21 | void finalizeStatement(); 22 | 23 | static inline std::string location;// = "default.an2"; 24 | 25 | public: 26 | static std::string getDbPath() { return location; } 27 | static int version(); 28 | static void setVersion(int version); 29 | //open new connection and execute query on the go 30 | static bool createIfNotExist(); 31 | static void showErrorDialog(bool show) {s_showError = show;} 32 | 33 | //If connection exists, db finalizes statement in destructor, but does not break connection 34 | Db(Db* existingConnection = nullptr); 35 | //Create db connection to external location: 36 | Db(const std::string& path); 37 | 38 | //returns true if there are more rows to get from database 39 | bool hasRows(); 40 | int asInt(int column); 41 | long long asRowId(int column); 42 | long long asLongLong(int column); 43 | bool asBool(int column); 44 | double asDouble(int column); 45 | const void* asBlob(int column); 46 | std::string asString(int column); 47 | int getColumnSize(int column); 48 | void newStatement(const std::string& query); 49 | bool execute(const std::string& query); 50 | 51 | long long lastInsertedRowID(); 52 | std::string getPreparedStatement(); 53 | void closeConnection(); 54 | 55 | 56 | //bindings with prepared statement: 57 | void bind(int index, const std::string& value); 58 | void bind(int index, int value); 59 | void bind(int index, double value); 60 | void bind(int index, long long value); 61 | void bind(int index, const void* ptr, int size); 62 | void bindNull(int index); 63 | //executes already prepared statement with bindings 64 | bool execute(); 65 | 66 | ~Db(); 67 | 68 | }; 69 | 70 | -------------------------------------------------------------------------------- /src/Model/MidiMaster.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "An1x.h" 4 | 5 | #include 6 | #include "Model/Settings.h" 7 | 8 | class QAN1xEditor; 9 | class AN1xPatch; 10 | 11 | struct PatchSource { 12 | 13 | enum Location { Database, SynthMemory }; 14 | 15 | Location location{ Location::Database }; 16 | long long id{ 0 }; 17 | 18 | long long getRowid() const { 19 | 20 | return location == Location::SynthMemory ? 0 : id; 21 | 22 | return id; 23 | } 24 | 25 | int getMemoryIndex() const { 26 | return location == Location::Database ? -1 : id; 27 | } 28 | 29 | bool isSameAs(const PatchSource& other) { 30 | 31 | if (location != other.location) return false; 32 | 33 | if (id != other.id) return false; 34 | 35 | if (location == Location::Database && !id) return false; 36 | 37 | return true; 38 | } 39 | }; 40 | 41 | namespace MidiMaster 42 | { 43 | void setView(QAN1xEditor* v); 44 | 45 | //MIDI connectivity functions 46 | void refreshConnection(); 47 | void connectMidiIn(int idx); 48 | void connectMidiOut(int idx); 49 | 50 | //called when parameter is changed from UI 51 | void parameterChanged(ParamType type, unsigned char parameter, int value); 52 | //called when Free EG Track change from UI 53 | void FreeEGChanged(const std::vector& trackData); 54 | 55 | void stopAllSounds(); 56 | void setAdvancedSettings(const AdvancedMidiSettings& advSettings); 57 | 58 | void requestSystem(); 59 | void sendSystem(); 60 | void restoreSystem(); 61 | 62 | //used by PatchMemory when requesting voices from AN1x 63 | void requestVoice(int index); 64 | void sendBulk(const AN1xPatch& patch, int index); 65 | 66 | //setting patch to view 67 | void setCurrentPatch(const AN1xPatch& p, PatchSource src); 68 | const AN1xPatch& currentPatch(); 69 | void notifyRowidDelete(long long rowid); 70 | void newPatch(AN1x::InitType type); 71 | 72 | //PC keyboard as Midi 73 | void modWheelChange(int value); 74 | void pitchChange(int value); 75 | void setKbdOctave(int octave); 76 | void pcKeyPress(int pcKey, bool pressed, int velocity); //pressed = false means released 77 | void setNote(int note, bool press, int velocity); //pressed = false means released 78 | void saveCurrentPatch(); 79 | 80 | 81 | bool cleanUp(); //returns false if denies close 82 | } 83 | -------------------------------------------------------------------------------- /src/Model/An1xPatch.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "An1x.h" 3 | #include 4 | 5 | 6 | 7 | class AN1xPatch { 8 | 9 | public: 10 | static constexpr int SystemSize = 28; 11 | static constexpr int CommonSize = 1640; 12 | static constexpr int SceneSize = 116; 13 | static constexpr int SeqSize = 70; 14 | 15 | static constexpr int PatchSize = CommonSize + SceneSize * 2 + SeqSize; 16 | 17 | typedef std::array PatchRawData; 18 | 19 | private: 20 | //holds the system data 21 | static std::array s_system; 22 | 23 | //holds the current patch data 24 | PatchRawData m_data {0x0}; 25 | 26 | unsigned char* getParameterAddress(ParamType type, unsigned char parameter); 27 | const unsigned char* getParameterAddress(ParamType type, unsigned char parameter) const; 28 | 29 | public: 30 | 31 | //constructs InitNormal patch 32 | AN1xPatch(AN1x::InitType = AN1x::Normal); 33 | //constructs patch from AN1x bulk message 34 | AN1xPatch(const Message bulkMsg); 35 | 36 | //constructs AN1xPatch from blob data 37 | AN1xPatch(const void* ptr); 38 | 39 | //sets the data and returns a midi message for An1x 40 | std::vector setParameter(ParamType type, unsigned char parameter, int value); 41 | 42 | //sets system data from AN1x message 43 | static bool setSystemData(const Message& bulkMessage); 44 | 45 | //creates an1x message to set system data 46 | static Message getSystemDataMsg(); 47 | 48 | //restore system data to default 49 | static void restoreSystemData(); 50 | 51 | //message to set the current patch on the AN1x 52 | Message getDataMessage(ParamType type) const; 53 | 54 | //returns value of a given parameter 55 | int getParameter(ParamType type, unsigned char param) const; 56 | 57 | void setFreeEGData(const std::vector& data); 58 | 59 | //return track data 60 | std::vector getFreeEGData() const; 61 | 62 | //returns the patch data 63 | PatchRawData& rawData() { return m_data; } 64 | const PatchRawData& rawData() const { return m_data; } 65 | 66 | //returns patch name 67 | std::string getName() const; 68 | 69 | //returns patch type 70 | int getType() const; 71 | 72 | AN1x::VariFx getEffect() const; 73 | AN1x::Layer getLayer() const; 74 | bool hasArpSeqEnabled() const; 75 | 76 | std::string getHash() const; 77 | 78 | bool operator==(const AN1xPatch& other) const 79 | { 80 | return m_data == other.m_data; 81 | } 82 | 83 | }; -------------------------------------------------------------------------------- /src/View/ControlMatrixScene.cpp: -------------------------------------------------------------------------------- 1 | #include "ControlMatrixScene.h" 2 | 3 | ControlMatrixScene::ControlMatrixScene(QWidget* parent) 4 | : QWidget(parent) 5 | { 6 | ui.setupUi(this); 7 | 8 | 9 | } 10 | 11 | void ControlMatrixScene::setAsScene(bool isScene2) 12 | { 13 | ParamType type = isScene2 ? ParamType::Scene2 : ParamType::Scene1; 14 | 15 | ui_controls = { 16 | ui.source_1, 17 | ui.param_1, 18 | ui.depth_1, 19 | ui.source_2, 20 | ui.param_2, 21 | ui.depth_2, 22 | ui.source_3, 23 | ui.param_3, 24 | ui.depth_3, 25 | ui.source_4, 26 | ui.param_4, 27 | ui.depth_4, 28 | ui.source_5, 29 | ui.param_5, 30 | ui.depth_5, 31 | ui.source_6, 32 | ui.param_6, 33 | ui.depth_6, 34 | ui.source_7, 35 | ui.param_7, 36 | ui.depth_7, 37 | ui.source_8, 38 | ui.param_8, 39 | ui.depth_8, 40 | ui.source_9, 41 | ui.param_9, 42 | ui.depth_9, 43 | ui.source_10, 44 | ui.param_10, 45 | ui.depth_10, 46 | ui.source_11, 47 | ui.param_11, 48 | ui.depth_11, 49 | ui.source_12, 50 | ui.param_12, 51 | ui.depth_12, 52 | ui.source_13, 53 | ui.param_13, 54 | ui.depth_13, 55 | ui.source_14, 56 | ui.param_14, 57 | ui.depth_14, 58 | ui.source_15, 59 | ui.param_15, 60 | ui.depth_15, 61 | ui.source_16, 62 | ui.param_16, 63 | ui.depth_16 64 | }; 65 | 66 | for (size_t i = 0; i < ui_controls.size(); i++) 67 | { 68 | auto combo = static_cast(ui_controls[i]); 69 | 70 | for (int y = 0; y < 115; y++) 71 | { 72 | combo->addItem(AN1x::getMatrixSourceName(y).c_str()); 73 | } 74 | 75 | auto param = static_cast(ui_controls[++i]); 76 | 77 | for (int y = 0; y < 46; y++) 78 | { 79 | param->addItem(AN1x::getMatrixParamName(y)); 80 | } 81 | 82 | auto dial = static_cast(ui_controls[++i]); 83 | dial->setRange(0, 127); 84 | dial->setOffset(-64); 85 | dial->setValue(64); 86 | dial->showPlusOnPositives(true); 87 | dial->setCurrentValueAsDefault(); 88 | } 89 | 90 | for (size_t i = 0; i < ui_controls.size(); i++) 91 | { 92 | auto parameter = static_cast(AN1x::CtrlMtrxSource1) + i; 93 | 94 | ui_controls[i]->setCurrentValueAsDefault(); 95 | ui_controls[i]->setParam(type, (AN1x::SceneParam)parameter); 96 | } 97 | 98 | } 99 | 100 | void ControlMatrixScene::setSceneParameters(AN1x::SceneParam p, int value, bool setAsDefault) 101 | { 102 | if (p < AN1x::CtrlMtrxSource1 || p > AN1x::SceneSize) return; 103 | 104 | auto ctrl = ui_controls[p - AN1x::CtrlMtrxSource1]; 105 | 106 | ctrl->setValue(value); 107 | 108 | if(setAsDefault){ 109 | ctrl->setCurrentValueAsDefault(); 110 | } 111 | } 112 | 113 | ControlMatrixScene::~ControlMatrixScene() 114 | {} 115 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | ############################################################################### 2 | # Set default behavior to automatically normalize line endings. 3 | ############################################################################### 4 | * text=auto 5 | 6 | ############################################################################### 7 | # Set default behavior for command prompt diff. 8 | # 9 | # This is need for earlier builds of msysgit that does not have it on by 10 | # default for csharp files. 11 | # Note: This is only used by command line 12 | ############################################################################### 13 | #*.cs diff=csharp 14 | 15 | ############################################################################### 16 | # Set the merge driver for project and solution files 17 | # 18 | # Merging from the command prompt will add diff markers to the files if there 19 | # are conflicts (Merging from VS is not affected by the settings below, in VS 20 | # the diff markers are never inserted). Diff markers may cause the following 21 | # file extensions to fail to load in VS. An alternative would be to treat 22 | # these files as binary and thus will always conflict and require user 23 | # intervention with every merge. To do so, just uncomment the entries below 24 | ############################################################################### 25 | #*.sln merge=binary 26 | #*.csproj merge=binary 27 | #*.vbproj merge=binary 28 | #*.vcxproj merge=binary 29 | #*.vcproj merge=binary 30 | #*.dbproj merge=binary 31 | #*.fsproj merge=binary 32 | #*.lsproj merge=binary 33 | #*.wixproj merge=binary 34 | #*.modelproj merge=binary 35 | #*.sqlproj merge=binary 36 | #*.wwaproj merge=binary 37 | 38 | ############################################################################### 39 | # behavior for image files 40 | # 41 | # image files are treated as binary by default. 42 | ############################################################################### 43 | #*.jpg binary 44 | #*.png binary 45 | #*.gif binary 46 | 47 | ############################################################################### 48 | # diff behavior for common document formats 49 | # 50 | # Convert binary document formats to text before diffing them. This feature 51 | # is only available from the command line. Turn it on by uncommenting the 52 | # entries below. 53 | ############################################################################### 54 | #*.doc diff=astextplain 55 | #*.DOC diff=astextplain 56 | #*.docx diff=astextplain 57 | #*.DOCX diff=astextplain 58 | #*.dot diff=astextplain 59 | #*.DOT diff=astextplain 60 | #*.pdf diff=astextplain 61 | #*.PDF diff=astextplain 62 | #*.rtf diff=astextplain 63 | #*.RTF diff=astextplain 64 | -------------------------------------------------------------------------------- /installer/win/InstallScript.iss: -------------------------------------------------------------------------------- 1 | ; Script generated by the Inno Setup Script Wizard. 2 | ; SEE THE DOCUMENTATION FOR DETAILS ON CREATING INNO SETUP SCRIPT FILES! 3 | 4 | #define MyAppName "QAN1xEditor" 5 | #define MyAppVersion "1.7.0" 6 | #define MyAppPublisher "Hristo Konstantinov" 7 | #define MyAppURL "https://github.com/thefinalcutbg/QAN1xEditor/" 8 | #define MyAppExeName "QAN1xEditor.exe" 9 | 10 | [Setup] 11 | ; NOTE: The value of AppId uniquely identifies this application. Do not use the same AppId value in installers for other applications. 12 | ; (To generate a new GUID, click Tools | Generate GUID inside the IDE.) 13 | AppId={{576F93A6-8A2C-49CA-81E1-2B75A697ECDA} 14 | AppName={#MyAppName} 15 | AppVersion={#MyAppVersion} 16 | ;AppVerName={#MyAppName} {#MyAppVersion} 17 | AppPublisher={#MyAppPublisher} 18 | AppPublisherURL={#MyAppURL} 19 | AppSupportURL={#MyAppURL} 20 | AppUpdatesURL={#MyAppURL} 21 | DefaultDirName={commonpf64}/{#MyAppName} 22 | DefaultGroupName={#MyAppName} 23 | AllowNoIcons=yes 24 | UsePreviousAppDir=yes 25 | OutputDir=..\compiled 26 | OutputBaseFilename=qan1xeditor-1.7.0-win64 27 | SetupIconFile=icon.ico 28 | Compression=lzma 29 | SolidCompression=yes 30 | WizardStyle=modern 31 | 32 | [Dir] 33 | Name: {app}; Permissions: users-full 34 | 35 | [Tasks] 36 | Name: "desktopicon"; Description: "{cm:CreateDesktopIcon}"; GroupDescription: "{cm:AdditionalIcons}"; 37 | 38 | [Files] 39 | Source: "..\files\*"; DestDir: "{app}"; Flags: ignoreversion recursesubdirs createallsubdirs 40 | 41 | ; NOTE: Don't use "Flags: ignoreversion" on any shared system files 42 | 43 | [Icons] 44 | Name: "{group}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}" 45 | Name: "{autodesktop}\{#MyAppName}"; Filename: "{app}\{#MyAppExeName}"; Tasks: desktopicon 46 | 47 | [Run] 48 | Filename: "{app}\vcredist_x64.EXE"; Parameters: "/q /norestart"; StatusMsg: "Installing VC++ redistributable 2010"; 49 | Filename: "{app}\VC_redist.x64.EXE"; Parameters: "/q /norestart"; StatusMsg: "Installing VC++ redistributable 2022"; 50 | Filename: "{app}\{#MyAppExeName}"; Description: "{cm:LaunchProgram,{#StringChange(MyAppName, '&', '&&')}}"; Flags: nowait postinstall skipifsilent 51 | 52 | [Code] 53 | function IsUpgrade: Boolean; 54 | var 55 | Value: string; 56 | UninstallKey: string; 57 | begin 58 | UninstallKey := 'Software\Microsoft\Windows\CurrentVersion\Uninstall\' + 59 | ExpandConstant('{#SetupSetting("AppId")}') + '_is1'; 60 | Result := (RegQueryStringValue(HKLM, UninstallKey, 'UninstallString', Value) or 61 | RegQueryStringValue(HKCU, UninstallKey, 'UninstallString', Value)) and (Value <> ''); 62 | end; 63 | 64 | function ShouldSkipPage(PageID: Integer): Boolean; 65 | begin 66 | if IsUpgrade then 67 | begin 68 | if PageID = wpLicense then 69 | Result := true 70 | end; 71 | begin 72 | if PageID = wpSelectTasks then 73 | Result := true 74 | end; 75 | end; -------------------------------------------------------------------------------- /src/View/UiWidgets.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include "AbstractController.h" 5 | 6 | class DialKnob : public QDial, public AbstractController 7 | { 8 | Q_OBJECT 9 | 10 | int m_offset{ 0 }; 11 | QString m_suffix = ""; 12 | bool m_showPositive{ false }; 13 | 14 | bool event(QEvent* e) override; 15 | QString getValueText(); 16 | 17 | public: 18 | 19 | enum ValueTextType { 20 | Normal, 21 | EqFrequency, 22 | Resonance, 23 | LFOFrquency, 24 | ChorusType, 25 | Milliseconds, 26 | Stage, 27 | Diffusion, 28 | PanDirection, 29 | CompressorAttack, 30 | CompressorRelease, 31 | CompressorRatio, 32 | WahCutoffFreq, 33 | AMPType, 34 | DelayInput, 35 | TempoDelay, 36 | ReverbTime, 37 | ReverbDamp, 38 | MasterTune 39 | }; 40 | 41 | private: ValueTextType m_textType{ Normal }; 42 | 43 | public: 44 | DialKnob(QWidget *parent); 45 | 46 | ~DialKnob(); 47 | 48 | // Inherited via AbstractController 49 | 50 | void setCurrentValueAsDefault() final; 51 | 52 | void setValue(int value) final; 53 | void resetValueText() { 54 | m_textType = ValueTextType::Normal; 55 | m_suffix.clear(); 56 | m_offset = 0; 57 | m_showPositive = false; 58 | } 59 | 60 | void setValueTextType(ValueTextType t) { m_textType = t; }; 61 | void setOffset(int offset) { m_offset = offset; }; 62 | void setSuffix(const char* suffix) { m_suffix = suffix; } 63 | void showPlusOnPositives(bool show) { m_showPositive = show; } 64 | int getValue() final { return isHidden() ? 0 : value(); } 65 | }; 66 | 67 | #include 68 | 69 | class ComboPicker : public QComboBox, public AbstractController 70 | { 71 | Q_OBJECT 72 | 73 | bool m_isNoteCombo{ false }; 74 | bool event(QEvent* e) override; 75 | public: 76 | ComboPicker(QWidget* parent); 77 | 78 | void setCurrentValueAsDefault() final; 79 | void setValue(int value) final; 80 | void setAsNoteCombo(); 81 | 82 | int getValue() final { return currentIndex(); } 83 | 84 | ~ComboPicker() {}; 85 | }; 86 | 87 | #include 88 | 89 | class EGSlider : public QSlider, public AbstractController 90 | { 91 | Q_OBJECT 92 | 93 | bool event(QEvent* e) override; 94 | 95 | public: 96 | EGSlider(QWidget* parent); 97 | 98 | void setCurrentValueAsDefault() final; 99 | void setValue(int value) final; 100 | int getValue() final { return value(); } 101 | ~EGSlider() {}; 102 | }; 103 | 104 | #include 105 | 106 | class CheckBox : public QCheckBox, public AbstractController 107 | { 108 | Q_OBJECT 109 | 110 | //bool event(QEvent* e) override; 111 | 112 | public: 113 | CheckBox(QWidget* parent); 114 | 115 | void setCurrentValueAsDefault() final; 116 | void setValue(int value) final; 117 | int getValue() final { return isChecked(); } 118 | ~CheckBox() {}; 119 | }; 120 | 121 | #include 122 | 123 | class SpinBox : public QSpinBox, public AbstractController 124 | { 125 | Q_OBJECT 126 | 127 | public: 128 | SpinBox(QWidget* parent); 129 | void setCurrentValueAsDefault() final; 130 | void setValue(int value) final; 131 | int getValue() final { return value(); } 132 | }; 133 | -------------------------------------------------------------------------------- /src/View/FreeEGWidget.cpp: -------------------------------------------------------------------------------- 1 | #include "FreeEGWidget.h" 2 | #include "Model/MidiMaster.h" 3 | #include "FreeEG/FreeEGScene.h" 4 | 5 | 6 | FreeEGWidget::FreeEGWidget(QWidget* parent) 7 | : QWidget(parent) 8 | { 9 | ui.setupUi(this); 10 | 11 | 12 | ui_controls = { 13 | ui.triggerCombo, 14 | ui.loopCombo, 15 | ui.lengthCombo, 16 | ui.keyTrack, 17 | ui.trackParam_1, 18 | ui.trackSceneSw_1, 19 | ui.trackParam_2, 20 | ui.trackSceneSw_2, 21 | ui.trackParam_3, 22 | ui.trackSceneSw_3, 23 | ui.trackParam_4, 24 | ui.trackSceneSw_4 25 | }; 26 | 27 | ComboPicker* paramCombo[] = { ui.trackParam_1, ui.trackParam_2, ui.trackParam_3, ui.trackParam_4 }; 28 | 29 | for (auto combo : paramCombo) { 30 | for (int i = 0; i < 57; i++) { 31 | combo->addItem(AN1x::getFreeEGParameters(i)); 32 | } 33 | } 34 | 35 | for (int i = 0; i < 95; i++) ui.lengthCombo->addItem(AN1x::getFreeEGLength(i)); 36 | 37 | ui.lengthCombo->setCurrentIndex(38); 38 | 39 | for (size_t i = 0; i < ui_controls.size(); i++) 40 | { 41 | if (ui_controls[i] == nullptr) continue; 42 | 43 | ui_controls[i]->setCurrentValueAsDefault(); 44 | ui_controls[i]->setParam(ParamType::Common, (AN1x::CommonParam)i + AN1x::FreeEGTrigger); 45 | } 46 | 47 | scene = new FreeEGScene(ui.egView); 48 | ui.egView->setScene(scene); 49 | ui.egView->viewport()->setMouseTracking(true); 50 | 51 | connect(ui.currentTrackCombo, &QComboBox::currentIndexChanged, this, [=](int index) { scene->setCurrentIndex(index); }); 52 | 53 | scene->setCurrentIndex(0); 54 | 55 | ui.groupTrack_1->setStyleSheet("QGroupBox {font-weight: bold; color: blue;}"); 56 | ui.groupTrack_2->setStyleSheet("QGroupBox {font-weight: bold; color: lightgreen;}"); 57 | ui.groupTrack_3->setStyleSheet("QGroupBox {font-weight: bold; color: red;}"); 58 | ui.groupTrack_4->setStyleSheet("QGroupBox {font-weight: bold; color: goldenrod;}"); 59 | 60 | ui.keyTrack->showPlusOnPositives(true); 61 | 62 | connect(scene, &FreeEGScene::editingFinished, this, [&] { MidiMaster::FreeEGChanged(getFreeEGData()); }); 63 | connect(ui.resetMode, &QComboBox::currentIndexChanged, this, [&](int resetToZero){ scene->setResetMode(resetToZero); }); 64 | 65 | connect(ui.fromSpin, &QSpinBox::valueChanged, this, [&](int value) { 66 | if (value > ui.toSpin->value()) ui.toSpin->setValue(value); 67 | }); 68 | 69 | connect(ui.toSpin, &QSpinBox::valueChanged, this, [&](int value) { 70 | if (value < ui.fromSpin->value()) ui.fromSpin->setValue(value); 71 | }); 72 | 73 | connect(ui.assignButton, &QPushButton::clicked, this, [&] { 74 | scene->quickAssign(ui.fromSpin->value(), ui.toSpin->value(), ui.valueSpin->value()); 75 | }); 76 | } 77 | 78 | FreeEGWidget::~FreeEGWidget() 79 | {} 80 | 81 | void FreeEGWidget::setCommonParameter(AN1x::CommonParam p, int value) 82 | { 83 | size_t idx = p - AN1x::FreeEGTrigger; 84 | 85 | if ( 86 | p == AN1x::FreeEgTrackSceneSw1 || 87 | p == AN1x::FreeEgTrackSceneSw2 || 88 | p == AN1x::FreeEgTrackSceneSw3 || 89 | p == AN1x::FreeEgTrackSceneSw4 90 | ) 91 | { 92 | value /= 5; //AN1x bug - returns 0, 5, 10, 15 instead of 0,1,2,3 93 | } 94 | 95 | if (idx >= 0 && idx < ui_controls.size() && ui_controls[idx] != nullptr) { 96 | ui_controls[idx]->setValue(value); 97 | } 98 | } 99 | 100 | std::vector FreeEGWidget::getFreeEGData() 101 | { 102 | return scene->getFreeEGData(); 103 | } 104 | 105 | void FreeEGWidget::setTrackData(const std::vector& data) 106 | { 107 | scene->setTrackData(data); 108 | } 109 | 110 | -------------------------------------------------------------------------------- /QAN1xEditor.pro: -------------------------------------------------------------------------------- 1 | QT += core widgets gui network 2 | 3 | CONFIG += c++17 4 | 5 | macx{ 6 | LIBS += -framework CoreMidi 7 | LIBS += -framework CoreAudio 8 | LIBS += -framework CoreFoundation 9 | ICON = $$PWD/installer/macos/icon.icns 10 | QMAKE_APPLE_DEVICE_ARCHS = x86_64 arm64 11 | } 12 | 13 | 14 | 15 | linux{ 16 | LIBS += -lasound 17 | ICON = $$PWD/installer/icon.ico 18 | } 19 | 20 | INCLUDEPATH += include 21 | INCLUDEPATH += src 22 | 23 | SOURCES += \ 24 | include/QMidi/RtMidi.cpp \ 25 | include/QMidi/qmidiin.cpp \ 26 | include/QMidi/qmidimapper.cpp \ 27 | include/QMidi/qmidimessage.cpp \ 28 | include/QMidi/qmidiout.cpp \ 29 | include/QMidi/qmidipianoroll.cpp \ 30 | include/sqLite3/sqlite3.c \ 31 | src/Database/Database.cpp \ 32 | src/Model/An1File.cpp \ 33 | src/Model/An1x.cpp \ 34 | src/Model/An1xPatch.cpp \ 35 | src/Model/ClipoboardManager.cpp \ 36 | src/Model/DragDropManager.cpp \ 37 | src/Model/MidiMaster.cpp \ 38 | src/Model/PatchDatabase.cpp \ 39 | src/Model/PatchMemory.cpp \ 40 | src/View/ArpSeq.cpp \ 41 | src/View/Browser.cpp \ 42 | src/View/BrowserTableModel.cpp \ 43 | src/View/ControlMatrixScene.cpp \ 44 | src/View/DbTableView.cpp \ 45 | src/View/FreeEG/EGPath.cpp \ 46 | src/View/FreeEG/FreeEGScene.cpp \ 47 | src/View/FreeEGWidget.cpp \ 48 | src/View/FreeFunctions.cpp \ 49 | src/View/FxEq.cpp \ 50 | src/View/GlobalWidgets.cpp \ 51 | src/View/MemoryList.cpp \ 52 | src/View/PianoRoll/PianoKey.cpp \ 53 | src/View/PianoRoll/PianoView.cpp \ 54 | src/View/QAN1xEditor.cpp \ 55 | src/View/SceneView.cpp \ 56 | src/View/SettingsDialog.cpp \ 57 | src/View/UiWidgets.cpp \ 58 | src/View/VoiceNameEdit.cpp \ 59 | src/main.cpp 60 | 61 | HEADERS += \ 62 | include/QMidi/RtMidi.h \ 63 | include/QMidi/qmidiin.h \ 64 | include/QMidi/qmidimapper.h \ 65 | include/QMidi/qmidimessage.h \ 66 | include/QMidi/qmidiout.h \ 67 | include/QMidi/qmidipianoroll.h \ 68 | include/sqLite3/sqlite3.h \ 69 | include/sqLite3/sqlite3ext.h \ 70 | src/Database/Database.h \ 71 | src/Model/An1File.h \ 72 | src/Model/An1x.h \ 73 | src/Model/An1xPatch.h \ 74 | src/Model/ClipoboardManager.h \ 75 | src/Model/DragDropManager.h \ 76 | src/Model/MidiMaster.h \ 77 | src/Model/PatchDatabase.h \ 78 | src/Model/PatchMemory.h \ 79 | src/Model/PatchRow.h \ 80 | src/Model/Settings.h \ 81 | src/View/AbstractController.h \ 82 | src/View/ArpSeq.h \ 83 | src/View/Browser.h \ 84 | src/View/BrowserTableModel.h \ 85 | src/View/ControlMatrixScene.h \ 86 | src/View/DbTableView.h \ 87 | src/View/FreeEG/EGPath.h \ 88 | src/View/FreeEG/FreeEGScene.h \ 89 | src/View/FreeEGWidget.h \ 90 | src/View/FreeFunctions.h \ 91 | src/View/FxEq.h \ 92 | src/View/GlobalWidgets.h \ 93 | src/View/MemoryList.h \ 94 | src/View/PianoRoll/PianoKey.h \ 95 | src/View/PianoRoll/PianoView.h \ 96 | src/View/QAN1xEditor.h \ 97 | src/View/SceneView.h \ 98 | src/View/SettingsDialog.h \ 99 | src/View/UiWidgets.h \ 100 | src/View/VoiceNameEdit.h 101 | 102 | FORMS += \ 103 | src/View/ArpSeq.ui \ 104 | src/View/Browser.ui \ 105 | src/View/ControlMatrixScene.ui \ 106 | src/View/FreeEGWidget.ui \ 107 | src/View/FxEq.ui \ 108 | src/View/QAN1xEditor.ui \ 109 | src/View/SceneView.ui \ 110 | src/View/SettingsDialog.ui 111 | 112 | RESOURCES += \ 113 | QAN1xEditor.qrc 114 | 115 | # Default rules for deployment. 116 | qnx: target.path = /tmp/$${TARGET}/bin 117 | else: unix:!android: target.path = /opt/$${TARGET}/bin 118 | !isEmpty(target.path): INSTALLS += target 119 | -------------------------------------------------------------------------------- /src/Model/PatchMemory.cpp: -------------------------------------------------------------------------------- 1 | #include "PatchMemory.h" 2 | 3 | #include 4 | 5 | #include "An1xPatch.h" 6 | #include "MidiMaster.h" 7 | #include "View/Browser.h" 8 | #include "View/GlobalWidgets.h" 9 | #include "An1File.h" 10 | 11 | constexpr std::array orderInit() { 12 | 13 | decltype(orderInit()) result{}; 14 | 15 | for (int i = 0; i < 128; i++) { 16 | result[i] = i; 17 | } 18 | 19 | return result; 20 | 21 | } 22 | 23 | std::array patch_order = orderInit(); 24 | 25 | 26 | AN1xPatch& PatchMemory::getPatch(int row) { 27 | 28 | static std::array s_patches; 29 | 30 | return s_patches[patch_order[row]]; 31 | } 32 | 33 | std::vector PatchMemory::getFile() 34 | { 35 | An1File file; 36 | 37 | for (int i = 0; i < 128; i++) 38 | { 39 | file.setPatch(getPatch(i), i); 40 | } 41 | 42 | return file.getFileData(); 43 | } 44 | ; 45 | 46 | void PatchMemory::rowMoved(int from, int to) 47 | { 48 | if(to > from) to--; 49 | 50 | int moved_val = patch_order[from]; 51 | 52 | if (to < from) { 53 | for (int i = from; i > to; i--) { 54 | patch_order[i] = patch_order[i - 1]; 55 | } 56 | } 57 | else if (from < to) { 58 | for (int i = from; i < to; i++) { 59 | patch_order[i] = patch_order[i + 1]; 60 | } 61 | } 62 | 63 | patch_order[to] = moved_val; 64 | } 65 | 66 | 67 | std::stack loadStack; 68 | 69 | void PatchMemory::loadFromAn1x(const std::vector& indexes) 70 | { 71 | loadStack = {}; 72 | 73 | if (indexes.empty()) return; 74 | 75 | for (int i = indexes.size() - 1; i >= 0; i--) { 76 | loadStack.push(indexes[i]); 77 | } 78 | 79 | GlobalWidgets::browser->setProgressBarCount(loadStack.size()); 80 | 81 | MidiMaster::requestVoice(loadStack.top()); 82 | } 83 | 84 | std::stack sendStack; 85 | 86 | void PatchMemory::sendToAn1x(const std::vector& indexes) 87 | { 88 | sendStack = {}; 89 | 90 | if (indexes.empty()) return; 91 | 92 | for (int i = indexes.size() - 1; i >= 0; i--) { 93 | sendStack.push(indexes[i]); 94 | } 95 | 96 | GlobalWidgets::browser->setProgressBarCount(indexes.size()); 97 | 98 | while (sendStack.size()) { 99 | 100 | auto index = sendStack.top(); 101 | 102 | sendStack.pop(); 103 | 104 | GlobalWidgets::browser->incrementProgressBar(); 105 | 106 | MidiMaster::sendBulk(getPatch(index), index); 107 | } 108 | } 109 | 110 | void PatchMemory::initPatches(const std::vector& indexes) 111 | { 112 | for (auto index : indexes) 113 | { 114 | auto& p = getPatch(index); 115 | p = AN1xPatch(); 116 | GlobalWidgets::browser->setPatchToListView(index, p.getName(), p.getType()); 117 | } 118 | } 119 | 120 | void PatchMemory::setPatch(const AN1xPatch& p, int index) { 121 | 122 | getPatch(index) = p; 123 | 124 | GlobalWidgets::browser->setPatchToListView(index, p.getName(), p.getType()); 125 | 126 | } 127 | 128 | void PatchMemory::loadAn1File(const An1File& file) 129 | { 130 | for (int i = 0; i < 128; i++) 131 | { 132 | auto &p = getPatch(i); 133 | 134 | p = file.getPatch(i); 135 | 136 | GlobalWidgets::browser->setPatchToListView(i, p.getName(), p.getType()); 137 | 138 | } 139 | } 140 | 141 | void PatchMemory::patchRecieved(const AN1xPatch& patch) 142 | { 143 | if (loadStack.empty()) return; 144 | 145 | int recievedIdx = loadStack.top(); 146 | loadStack.pop(); 147 | 148 | setPatch(patch, recievedIdx); 149 | 150 | GlobalWidgets::browser->incrementProgressBar(); 151 | 152 | if (loadStack.empty()) { 153 | return; 154 | } 155 | 156 | MidiMaster::requestVoice(loadStack.top()); 157 | 158 | } 159 | 160 | void PatchMemory::loadAn1xMemPatch(int index) 161 | { 162 | if (index < 0 || index > 127) return; 163 | 164 | MidiMaster::setCurrentPatch(getPatch(index), { PatchSource::SynthMemory, index }); 165 | } 166 | -------------------------------------------------------------------------------- /src/View/FreeEG/FreeEGScene.cpp: -------------------------------------------------------------------------------- 1 | #include "FreeEGScene.h" 2 | #include 3 | 4 | FreeEGScene::FreeEGScene(QObject *parent) 5 | : QGraphicsScene(parent) 6 | { 7 | 8 | int pos = 0; 9 | 10 | int counter = 0; 11 | 12 | bool stripeState{ false }; 13 | 14 | for (auto& r : rects) 15 | { 16 | 17 | if (counter % 24 == 0) { 18 | stripeState = !stripeState; 19 | } 20 | 21 | r = new QGraphicsRectItem(); 22 | r->setRect(0, 0, 6, 256); 23 | r->setX(pos); 24 | r->setPen(QPen(Qt::lightGray)); 25 | r->setBrush(stripeState ? QColor(255,255,255) : QColor(245,245,245)); 26 | this->addItem(r); 27 | pos += 6; 28 | 29 | counter++; 30 | } 31 | 32 | QColor colors[4]{ Qt::darkBlue, Qt::green, Qt::red, Qt::darkYellow }; 33 | 34 | for (int i = 3; i > -1; i--) 35 | { 36 | path[i] = new EGPath(colors[i]); 37 | addItem(path[i]); 38 | path[i]->setX(3); 39 | path[i]->setTrack(track[i]); 40 | } 41 | 42 | QGraphicsLineItem* middle = new QGraphicsLineItem(0, 128, pos, 128); 43 | middle->setPen(QPen(Qt::gray)); 44 | addItem(middle); 45 | 46 | } 47 | 48 | std::vector FreeEGScene::getFreeEGData() 49 | { 50 | std::vector result; 51 | 52 | 53 | auto lambda = [&](const EGTrack& tracks) { 54 | 55 | 56 | for (auto t : tracks) 57 | { 58 | result.push_back(t < 0 ? 0 : 1); 59 | if (t < 0) t += 128; 60 | result.push_back(t); 61 | } 62 | 63 | }; 64 | 65 | lambda(track[0]); 66 | lambda(track[1]); 67 | lambda(track[2]); 68 | lambda(track[3]); 69 | 70 | return result; 71 | } 72 | 73 | void FreeEGScene::setTrackData(const std::vector& trackData) 74 | { 75 | //if (trackData.size() != 192 * 4) return; 76 | 77 | for (size_t i = 0; i < trackData.size(); i++) 78 | { 79 | int currentTrackNo = i / 192; 80 | 81 | track[currentTrackNo][i % 192] = trackData[i]; 82 | default_track[currentTrackNo][i % 192] = trackData[i]; 83 | } 84 | 85 | for (int i = 0; i < 4; i++) { 86 | path[i]->setTrack(track[i]); 87 | } 88 | } 89 | 90 | void FreeEGScene::quickAssign(int from, int to, int value) 91 | { 92 | if (from > to) { 93 | std::swap(from, to); 94 | } 95 | 96 | for (int i = from; i <= to; i++) { 97 | currentPath().setPoint(i, value); 98 | currentTrack().at(i) = value; 99 | } 100 | emit editingFinished(); 101 | } 102 | 103 | 104 | void FreeEGScene::mouseMoveEvent(QGraphicsSceneMouseEvent* e) 105 | { 106 | if (e->buttons() == Qt::LeftButton) processPosition(e->scenePos(), false); 107 | if (e->buttons() == Qt::RightButton) processPosition(e->scenePos(), true); 108 | } 109 | 110 | void FreeEGScene::setCurrentIndex(int index) 111 | { 112 | if(index < 0 || index > 3) return; 113 | 114 | m_currentIndex = index; 115 | } 116 | 117 | void FreeEGScene::mousePressEvent(QGraphicsSceneMouseEvent* e) 118 | { 119 | if (e->buttons() == Qt::LeftButton) processPosition(e->scenePos(), false); 120 | 121 | if (e->buttons() == Qt::RightButton) processPosition(e->scenePos(), true); 122 | 123 | } 124 | 125 | void FreeEGScene::mouseReleaseEvent(QGraphicsSceneMouseEvent* e) 126 | { 127 | if (e->button() == Qt::LeftButton) emit editingFinished(); 128 | if (e->button() == Qt::RightButton) emit editingFinished(); 129 | } 130 | 131 | void FreeEGScene::processPosition(QPointF pos, bool reset) 132 | { 133 | int x = static_cast(pos.x()) / 6; 134 | if (x < 0 || x > 191) return; 135 | 136 | int y = 0; 137 | 138 | if(!reset){ 139 | y = (static_cast(pos.y()) * -1) + 128; 140 | y = std::min(128, y); 141 | y = std::max(-128, y); 142 | } 143 | else if(!m_resetToZero){ 144 | 145 | y = defaultTrack().at(x); 146 | } 147 | 148 | currentPath().setPoint(x, y); 149 | 150 | currentTrack().at(x) = y; 151 | } 152 | 153 | 154 | 155 | FreeEGScene::~FreeEGScene() 156 | {} 157 | -------------------------------------------------------------------------------- /src/Model/An1File.cpp: -------------------------------------------------------------------------------- 1 | #include "An1File.h" 2 | #include 3 | #include 4 | 5 | int An1File::getCommonOffset(int index) const 6 | { 7 | return dataStart + (index * fileCommonAndSceneSize); 8 | } 9 | 10 | int An1File::getFreeEGOffset(int index) const 11 | { 12 | return getCommonOffset(index) + AN1x::CommonParam::FreeEgData; 13 | } 14 | 15 | int An1File::getSceneOffset(int index) const 16 | { 17 | return getCommonOffset(index) + AN1x::CommonParam::FreeEgData + fileFreeEGsize; 18 | } 19 | 20 | int An1File::getSequencerOffset(int index) const 21 | { 22 | return firstPatchSeqBegin + AN1xPatch::SeqSize * index; 23 | } 24 | 25 | int An1File::getCommentOffset(int index) const 26 | { 27 | return firstCommentbegin + 256 * index; 28 | } 29 | 30 | An1File::An1File(const std::vector bytes, const std::string& filename) : m_data(bytes), filename(filename) 31 | { 32 | if (bytes.size() != fileSize) 33 | throw std::exception(); 34 | } 35 | 36 | An1File::An1File() 37 | { 38 | QFile inputFile(":/resources/AN1xEdit.an1"); 39 | inputFile.open(QIODevice::ReadOnly); 40 | auto data = inputFile.readAll(); 41 | 42 | m_data.reserve(data.size()); 43 | 44 | for (int i = 0; i < data.size(); i++) 45 | { 46 | m_data.push_back(data[i]); 47 | } 48 | } 49 | 50 | void An1File::setPatch(const AN1xPatch& p, int index) 51 | { 52 | for (int i = 0; i < AN1x::FreeEgData; i++) 53 | { 54 | m_data[getCommonOffset(index) + i] = p.rawData()[i]; 55 | } 56 | 57 | for (int i = 0; i < fileFreeEGsize*2; i += 2) 58 | { 59 | int negative = p.rawData()[AN1x::FreeEgData + i]; 60 | int value = p.rawData()[AN1x::FreeEgData + i + 1]; 61 | 62 | int fileValue = negative ? value-128 : value; 63 | 64 | m_data[getFreeEGOffset(index) + (i) / 2] = fileValue; 65 | } 66 | 67 | for (int i = 0;i < 2*AN1x::SceneSize; i++) 68 | { 69 | m_data[getSceneOffset(index) + i] = p.rawData()[i+AN1x::CommonSize]; 70 | } 71 | 72 | for (int i = 0; i < AN1x::SeqencerSize; i++) 73 | { 74 | m_data[getSequencerOffset(index) + i] = p.rawData()[i+AN1x::CommonSize + 2*AN1x::SceneSize]; 75 | } 76 | 77 | } 78 | 79 | AN1xPatch An1File::getPatch(int index) const 80 | { 81 | if (index < 0 || index > 127) return AN1xPatch(); 82 | 83 | AN1xPatch result; 84 | 85 | //getting common parameters 86 | 87 | for (int i = 0; i < AN1x::CommonParam::FreeEgData; i++) 88 | { 89 | result.rawData()[i] = m_data[i + getCommonOffset(index)]; 90 | } 91 | 92 | //getting free eg tracks 93 | int freeEgOffest = getFreeEGOffset(index); 94 | 95 | int fileIdx; 96 | int patchIdx; 97 | 98 | for ( 99 | fileIdx = freeEgOffest, 100 | patchIdx = AN1x::FreeEgData; 101 | fileIdx < freeEgOffest + fileCommonSize; fileIdx++, patchIdx +=2) 102 | { 103 | int value = m_data[fileIdx]; 104 | 105 | bool isNegative = value < 128; 106 | 107 | result.rawData()[patchIdx] = isNegative ? 0 : 1; 108 | result.rawData()[patchIdx + 1] = isNegative ? value : value - 128; 109 | 110 | } 111 | 112 | //getting scene 113 | for (int i = 0; i < AN1xPatch::SceneSize*2; i++) 114 | { 115 | int patchAddress = AN1xPatch::CommonSize + i; 116 | int fileAddress = getSceneOffset(index) + i; 117 | 118 | result.rawData()[patchAddress] = m_data[fileAddress]; 119 | } 120 | 121 | //getting sequencer 122 | for(int i = 0; i < AN1xPatch::SeqSize; i++) 123 | { 124 | int patchAddress = AN1xPatch::PatchSize - AN1xPatch::SeqSize + i; 125 | int fileAddress = getSequencerOffset(index) + i; 126 | 127 | result.rawData()[patchAddress] = m_data[fileAddress]; 128 | } 129 | 130 | return result; 131 | } 132 | 133 | std::string An1File::getComment(int index) const 134 | { 135 | if (index < 0 || index > 127) return ""; 136 | 137 | return std::string{ (const char*)&m_data[getCommentOffset(index)], 256 }; 138 | } 139 | 140 | std::vector An1File::getFileData() const 141 | { 142 | return m_data; 143 | } 144 | -------------------------------------------------------------------------------- /src/View/SceneView.cpp: -------------------------------------------------------------------------------- 1 | #include "SceneView.h" 2 | #include "Model/MidiMaster.h" 3 | #include "GlobalWidgets.h" 4 | #include 5 | #include "Model/MidiMaster.h" 6 | 7 | SceneView::SceneView(QWidget *parent) 8 | : QWidget(parent) 9 | { 10 | ui.setupUi(this); 11 | 12 | ui.fine1->showPlusOnPositives(true); 13 | ui.pitch1->showPlusOnPositives(true); 14 | ui.fine2->showPlusOnPositives(true); 15 | ui.pitch2->showPlusOnPositives(true); 16 | ui.depth1->showPlusOnPositives(true); 17 | ui.depth2->showPlusOnPositives(true); 18 | ui.VCO1PitchModDepth->showPlusOnPositives(true); 19 | ui.VCO2PitchModDepth->showPlusOnPositives(true); 20 | ui.ampVelSens->showPlusOnPositives(true); 21 | ui.filterDepth->showPlusOnPositives(true); 22 | ui.pegDepth->showPlusOnPositives(true); 23 | ui.pegDecay->showPlusOnPositives(true); 24 | ui.filterKeyTrk->showPlusOnPositives(true); 25 | } 26 | 27 | void SceneView::setAsScene(bool isScene2) 28 | { 29 | ParamType type = isScene2 ? ParamType::Scene2 : ParamType::Scene1; 30 | 31 | ui_controls = { 32 | nullptr, 33 | nullptr, 34 | nullptr, 35 | ui.pegDecay, 36 | ui.pegDepth, 37 | ui.pegSwitch, 38 | ui.portamentoType, 39 | ui.portamentoTime, 40 | nullptr, 41 | ui.LFOShape, 42 | ui.lfo1Speed, 43 | nullptr, 44 | ui.lfo1Delay, 45 | ui.lfo2Speed, 46 | nullptr, 47 | ui.algorithmCombo, 48 | ui.syncPitch, 49 | ui.syncDepth, 50 | ui.syncPitchSrc, 51 | ui.syncModSwitch, 52 | ui.FMDepth, 53 | ui.FM1srcCombo, 54 | ui.FM2srcCombo, 55 | ui.osc1wave, 56 | ui.pitch1, 57 | ui.fine1, 58 | ui.edge1, 59 | ui.PW1, 60 | ui.depth1, 61 | ui.PWSrc1, 62 | ui.VCO1PitchModDepth, 63 | nullptr, 64 | ui.osc2wave, 65 | ui.pitch2, 66 | ui.fine2, 67 | ui.edge2, 68 | ui.PW2, 69 | ui.depth2, 70 | ui.PWSrc2, 71 | ui.VCO2PitchModDepth, 72 | nullptr, 73 | ui.mixVCO1, 74 | ui.mixVCO2, 75 | ui.mixRingMod, 76 | ui.mixNoise, 77 | ui.filterAttack, 78 | ui.filterDecay, 79 | ui.filterSustain, 80 | ui.filterRelease, 81 | ui.hpf, 82 | ui.filterType, 83 | ui.filterCutoff, 84 | ui.filterResonance, 85 | ui.filterDepth, 86 | nullptr, 87 | ui.filterVelSens, 88 | ui.filterKeyTrk, 89 | ui.FilterModDepth, 90 | ui.ampAttack, 91 | ui.ampDecay, 92 | ui.ampSustain, 93 | ui.ampRelease, 94 | ui.feedback, 95 | ui.ampVolume, 96 | ui.ampVelSens, 97 | ui.AmpModDepth 98 | }; 99 | 100 | for (size_t i = 0; i < ui_controls.size(); i++) 101 | { 102 | if (ui_controls[i] == nullptr) continue; 103 | 104 | ui_controls[i]->setCurrentValueAsDefault(); 105 | ui_controls[i]->setParam(type, (AN1x::SceneParam)i); 106 | } 107 | 108 | connect(ui.polyRadio, &QRadioButton::clicked, this, [=] { 109 | MidiMaster::parameterChanged(type, AN1x::SceneParam::PolyMode, 0); 110 | ui.portamentoType->setItemText(0, "Normal"); 111 | ui.portamentoType->setItemText(1, "Sustain-key"); 112 | }); 113 | 114 | connect(ui.monoRadio, &QRadioButton::clicked, this, [=] { 115 | MidiMaster::parameterChanged(type, AN1x::SceneParam::PolyMode, 1); 116 | ui.portamentoType->setItemText(0, "Full-time"); 117 | ui.portamentoType->setItemText(1, "Fingered"); 118 | }); 119 | 120 | connect(ui.legatoRadio, &QRadioButton::clicked, this, [=] { 121 | MidiMaster::parameterChanged(type, AN1x::SceneParam::PolyMode, 2); 122 | ui.portamentoType->setItemText(0, "Full-time"); 123 | ui.portamentoType->setItemText(1, "Fingered"); 124 | }); 125 | 126 | connect(ui.lfoReset, &QCheckBox::clicked, this, [=](bool checked) { MidiMaster::parameterChanged(type, AN1x::SceneParam::LFOResetMode, checked); }); 127 | 128 | 129 | connect(ui.algorithmCombo, &QComboBox::currentIndexChanged, this, 130 | [=](int idx) { 131 | 132 | ui.syncGroup->setEnabled(idx); 133 | 134 | if (idx == 0 && ui.osc1wave->count() == 5) { 135 | 136 | ui.osc1wave->setItemText(2, "Saw 2"); 137 | ui.osc1wave->setItemText(3, "Mix"); 138 | ui.osc1wave->removeItem(4); 139 | return; 140 | } 141 | else if (idx != 0 && ui.osc1wave->count() == 4) { 142 | 143 | ui.osc1wave->setItemText(2, "Inner1"); 144 | ui.osc1wave->setItemText(3, "Inner2"); 145 | ui.osc1wave->addItem("Inner3"); 146 | return; 147 | } 148 | 149 | }); 150 | } 151 | 152 | void SceneView::setSceneParameters(AN1x::SceneParam p, int value, bool setAsDefault) 153 | { 154 | if (p == AN1x::PolyMode) { 155 | 156 | QSignalBlocker arr[] = { 157 | QSignalBlocker{ui.polyRadio}, 158 | QSignalBlocker{ui.monoRadio}, 159 | QSignalBlocker{ui.legatoRadio } 160 | }; 161 | 162 | ui.polyRadio->setChecked(value == 0); 163 | ui.monoRadio->setChecked(value == 1); 164 | ui.legatoRadio->setChecked(value == 2); 165 | 166 | if (value == 0) { 167 | ui.portamentoType->setItemText(0, "Normal"); 168 | ui.portamentoType->setItemText(1, "Sustain-key"); 169 | } 170 | else 171 | { 172 | ui.portamentoType->setItemText(0, "Full-time"); 173 | ui.portamentoType->setItemText(1, "Fingered"); 174 | } 175 | 176 | return; 177 | } 178 | 179 | if (p == AN1x::LFOResetMode) { 180 | 181 | QSignalBlocker b(ui.lfoReset); 182 | ui.lfoReset->setChecked(value); 183 | return; 184 | } 185 | 186 | if (p < ui_controls.size() && ui_controls[p] != nullptr) { 187 | ui_controls[p]->setValue(value); 188 | 189 | if(setAsDefault){ 190 | ui_controls[p]->setCurrentValueAsDefault(); 191 | } 192 | 193 | } 194 | } 195 | 196 | 197 | 198 | 199 | SceneView::~SceneView() 200 | {} 201 | -------------------------------------------------------------------------------- /src/View/SettingsDialog.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | SettingsDialogClass 4 | 5 | 6 | 7 | 0 8 | 0 9 | 266 10 | 204 11 | 12 | 13 | 14 | SettingsDialog 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | Send Chаnnel: 23 | 24 | 25 | 26 | 27 | 28 | 29 | 1 30 | 31 | 32 | 16 33 | 34 | 35 | 36 | 37 | 38 | 39 | MIDI Thru 40 | 41 | 42 | 43 | 44 | 45 | 46 | Buffer Size: 47 | 48 | 49 | 50 | 51 | 52 | 53 | Specify the number of bytes for each chunk of data sent 54 | 55 | 56 | Unlimited 57 | 58 | 59 | bytes 60 | 61 | 62 | 2048 63 | 64 | 65 | 32 66 | 67 | 68 | 0 69 | 70 | 71 | 10 72 | 73 | 74 | 75 | 76 | 77 | 78 | Buffer Delay: 79 | 80 | 81 | 82 | 83 | 84 | 85 | Set the time delay (in milliseconds) between each data chunk sent 86 | 87 | 88 | 89 | 90 | 91 | ms 92 | 93 | 94 | 0 95 | 96 | 97 | 1000 98 | 99 | 100 | 10 101 | 102 | 103 | 10 104 | 105 | 106 | 10 107 | 108 | 109 | 110 | 111 | 112 | 113 | Device Number: 114 | 115 | 116 | 117 | 118 | 119 | 120 | 1 121 | 122 | 123 | 16 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 40 132 | 20 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 20 144 | 10 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 40 156 | 20 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | OK 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 40 173 | 20 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | -------------------------------------------------------------------------------- /src/Model/An1x.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | typedef std::vector Message; 8 | 9 | enum class ParamType { 10 | System, 11 | Common, 12 | Scene1, 13 | Scene2, 14 | StepSq, 15 | Unknown 16 | }; 17 | 18 | namespace AN1x 19 | { 20 | enum InitType : unsigned char{Normal, Bass, Brass, String, EP, Organ, Sync, PWM}; 21 | enum Layer : unsigned char{ Single, Unison, Dual, DualUnison, Split, SplitUnison }; 22 | enum Pan : unsigned char{ Off, Alt, Round }; 23 | enum VariFx : unsigned char { Chorus1, Chorus2, Flagner, Symphonic, Phaser, AutoPAN, RotarySpeaker, PitchChange, AuralExciter, Compressor, WahWah, Distortion, Overdrive, AmpSimulator }; 24 | enum Delay : unsigned char { LCR, LR, Echo, Cross, TempoDelay }; 25 | enum Reverb : unsigned char { Hall1, Hall2, Room1, Room2, Room3, Stage1, Stage2, Plate }; 26 | 27 | enum SystemParam 28 | { 29 | MasterTune, 30 | NullGlobal1, 31 | KeyboardTranspose, 32 | VelocityCurve, 33 | FixedVelocity, 34 | EffectBypass, 35 | TransmitChannel, 36 | ArpSeqTransmitChannel, 37 | RecieveChannel1, 38 | RecieveChannel2, 39 | MidiDeviceNumber, 40 | MidiLocal, 41 | SceenCtrlNumber, 42 | MWCtrlNumber, 43 | FVCtrlNumber, 44 | FCCtrlNumber, 45 | FSCtrlNumber, 46 | RibXCtrlNumber, 47 | RibZCtrlNumber, 48 | AssignKnob1Number, 49 | //x7 more 50 | SystemReserved = 0x1B, 51 | SystemMaxSize 52 | }; 53 | 54 | enum CommonParam 55 | { 56 | Name1 = 0x00, 57 | Name2, 58 | Name3, 59 | Name4, 60 | Name5, 61 | Name6, 62 | Name7, 63 | Name8, 64 | Name9, 65 | Name10, 66 | VoiceCategory, 67 | SceneSelect, 68 | LayerMode, 69 | LayerPan, 70 | LayerSeparation, 71 | Detune, 72 | Tempo, 73 | NullCommon1, 74 | SplitPoint, 75 | Portamento, 76 | MatrixSrc1, 77 | MatrixParam1, 78 | MatrixDepth1, 79 | MatrixSrc2, 80 | MatrixParam2, 81 | MatrixDepth2, 82 | VariFXType, 83 | reserved, 84 | FxParam1, 85 | NullCommon3, 86 | FxParam2, 87 | NullCommon4, 88 | FxParam3, 89 | NullCommon5, 90 | FxParam4, 91 | NullCommon6, 92 | FxParam5, 93 | NullCommon7, 94 | FxParam6, 95 | NullCommon8, 96 | LowFq, 97 | LowGain, 98 | MidFq, 99 | MidGain, 100 | MidRes, 101 | HiFq, 102 | HiGain, 103 | DlyRevConnection, 104 | DlyType, 105 | DlyReturn, 106 | DlyParam1, 107 | NullCommon9, 108 | DlyParam2, 109 | NullCommon10, 110 | DlyParam3, 111 | NullCommon11, 112 | DlyParam4, 113 | NullCommon12, 114 | DlyParam5, 115 | NullCommon13, 116 | DlyParam6, 117 | NullCommon14, 118 | DlyParam7, 119 | NullCommon15, 120 | RevType, 121 | RevReturn, 122 | RevParam1, 123 | NullCommon16, 124 | RevParam2, 125 | NullCommon17, 126 | RevParam3, 127 | NullCommon18, 128 | RevParam4, 129 | NullCommon19, 130 | RevParam5, 131 | NullCommon20, 132 | RevParam6, 133 | NullCommon21, 134 | RevParam7, 135 | NullCommon22, 136 | ArpSeqOnOff, 137 | ArpSeqSelect, 138 | ArpTypeSeqNo, 139 | ArpSeqKbdMode, 140 | ArpSeqHold, 141 | ArpSeqScene, 142 | ArpSubdiv, 143 | Swing, 144 | Velocity, 145 | NullCommon23, 146 | Gate, 147 | NullCommon24, 148 | FreeEGTrigger, 149 | FreeEGLoopType, 150 | FreeEGLength, 151 | FreeEGKbdTrack, 152 | FreeEgTrackParam1, 153 | FreeEgTrackSceneSw1, 154 | FreeEgTrackParam2, 155 | FreeEgTrackSceneSw2, 156 | FreeEgTrackParam3, 157 | FreeEgTrackSceneSw3, 158 | FreeEgTrackParam4, 159 | FreeEgTrackSceneSw4, 160 | FreeEgData, //FreeEG begins from here 161 | CommonSize = 1640 162 | }; 163 | 164 | enum SceneParam : unsigned char 165 | { 166 | PolyMode = 0x00, 167 | PitchUpRange, 168 | PitchDownRange, 169 | PEGDecay, 170 | PEGDepth, 171 | PEGSwitch, 172 | PortamentoMode, 173 | PortamentoTime, 174 | LFOResetMode, 175 | LFO1Wave, 176 | LFO1Speed, 177 | NullScene1, 178 | LFODelay, 179 | LFO2Speed, 180 | NullScene2, 181 | FMAlgorithm, 182 | SyncPitch, 183 | SyncPitchDepth, 184 | SyncPitchSrc, 185 | SyncPitchModSwitch, 186 | FMDepth, 187 | FMSrc1, 188 | FMSrc2, 189 | VCO1Wave, 190 | VCO1Pitch, 191 | VCO1Fine, 192 | VCO1Edge, 193 | VCO1PW, 194 | VCO1PWMDepth, 195 | VCO1PWMSrc, 196 | VCO1PitchModDepth, 197 | NullScene3, 198 | VCO2Wave, 199 | VCO2Pitch, 200 | VCO2Fine, 201 | VCO2Edge, 202 | VCO2PW, 203 | VCO2PWMDepth, 204 | VCO2PWMSrc, 205 | VCO2PitchModDepth, 206 | NullScene4, 207 | MixerVCO1Level, 208 | MixerVCO2Level, 209 | MixerRingModLevel, 210 | MixerNoiseLevel, 211 | FilterAttack, 212 | FilterDecay, 213 | FilterSustain, 214 | FilterRelease, 215 | HPFCutoff, 216 | FilterType, 217 | FilterCutoff, 218 | FilterResonance, 219 | FEGDepth, 220 | NullScene5, 221 | FilterVelSens, 222 | FilterKeyTrack, 223 | FilterModDepth, 224 | AmpAttack, 225 | AmpDecay, 226 | AmpSustain, 227 | AmpRelease, 228 | Feedback, 229 | Volume, 230 | AmpVelSens, 231 | AmpModDepth, 232 | VariFxDW, 233 | reserve, 234 | CtrlMtrxSource1, 235 | CtrlMtrxParam1, 236 | CtrlMtrxDepth1, 237 | //repeat last 3 15 more times 238 | CtrlMtrxDepth16 = 0x73, 239 | SceneSize 240 | }; 241 | 242 | enum SeqParam 243 | { 244 | SeqBaseUnit = 0x00, 245 | SeqLength, 246 | SeqLoopType, 247 | SeqCtrlChange, 248 | SeqNull1, 249 | SeqNull2, 250 | SeqNote1 = 0x06, 251 | //15 more 252 | SeqVelocity1 = 0x16, 253 | //15 more 254 | SeqGateTime1 = 0x26, 255 | //15 more 256 | SeqCtrlChange1 = 0x36, 257 | //15 more 258 | SeqencerSize = 0x46 259 | }; 260 | 261 | unsigned char getScene(bool isScene2); 262 | 263 | 264 | Message getHeader(ParamType p); 265 | 266 | int getOffset(ParamType t, int p); 267 | bool isNull(ParamType t, int p); 268 | bool isTwoByteParameter(ParamType t, int p); 269 | 270 | const char* getFrequencyByValue(int value); 271 | const char* getLFOFreqByValue(int value); 272 | const char* getChorusTypeByValue(int value); 273 | int compressorAttack(int value); 274 | int compressorRelease(int value); 275 | const char* compressorRatio(int value); 276 | const char* wahCutoffFreq(int value); 277 | const char* reverbTime(int value); 278 | std::string getMatrixSourceName(int value); 279 | const char* getMatrixParamName(int value); 280 | const char* getMatrixCommonParamName(int value); 281 | const char* getFreeEGParameters(int value); 282 | const char* getFreeEGLength(int value); 283 | 284 | //generates MIDI message for getting perticular voice 285 | std::vector voiceRequest(int voiceNumber); 286 | void addCheckSum(std::vector& message); 287 | 288 | } -------------------------------------------------------------------------------- /src/View/BrowserTableModel.cpp: -------------------------------------------------------------------------------- 1 | #include "BrowserTableModel.h" 2 | #include "FreeFunctions.h" 3 | #include 4 | #include 5 | 6 | QString getTypeText(int type) { 7 | 8 | if (type < 1 || type > 22) type = 0; 9 | 10 | static const QString arr[] = { 11 | "", 12 | "Piano", 13 | "Chromatic Percussion", 14 | "Organ", 15 | "Guitar", 16 | "Bass", 17 | "Strings", 18 | "Ensemble", 19 | "Brass", 20 | "Reed", 21 | "Pipe", 22 | "Synth Lead", 23 | "Synth Pad", 24 | "Synth Effect", 25 | "Ethnic", 26 | "Percussion", 27 | "Sound Effect", 28 | "Drums", 29 | "Synth Comp", 30 | "Vocal", 31 | "Combination", 32 | "Material Wave", 33 | "Sequence" 34 | }; 35 | 36 | return arr[type]; 37 | } 38 | 39 | QString getLayerText(int type){ 40 | 41 | if (type < 0 || type > 5) type = 6; 42 | 43 | static const QString arr[] = { 44 | "Single","Unison","Dual","Dual Unison","Split","Split Unison","" 45 | }; 46 | 47 | return arr[type]; 48 | } 49 | 50 | QString getEffectText(int type) { 51 | 52 | if (type < 0 || type > 14) type = 14; 53 | 54 | static const QString arr[] = { 55 | "Chorus 1", "Chorus 2", "Flanger", "Symphonic", "Phaser", "Auto PAN", "Rotary Speaker", "Pitch Change", "Aural Exciter", "Compressor", "Wah Wah", "Distortion", "Overdrive", "Amp Simulator", "" 56 | }; 57 | 58 | return arr[type]; 59 | } 60 | 61 | void BrowserTableModel::setPatchData(const std::vector& rows) 62 | { 63 | beginResetModel(); 64 | 65 | list = rows; 66 | 67 | endResetModel(); 68 | 69 | emit dataChanged(index(0, 0), index(list.size(), columnCount())); 70 | } 71 | 72 | QVariant BrowserTableModel::headerData(int section, Qt::Orientation orientation, int role) const 73 | { 74 | if (role == Qt::DisplayRole) 75 | { 76 | if (orientation == Qt::Horizontal) { 77 | switch (section) 78 | { 79 | case 0: return "rowID"; 80 | case 1: return "TypeID"; 81 | case 2: return "LayerID"; 82 | case 3: return "EffectID"; 83 | case 4: return "ArpSeqID"; 84 | case 5: return ""; 85 | case 6: return "Name"; 86 | case 7: return "Type"; 87 | case 8: return "Layer"; 88 | case 9: return "Effect"; 89 | case 10: return "Arp/Seq"; 90 | case 11: return "AN1 file"; 91 | case 12: return "Comment"; 92 | } 93 | } 94 | } 95 | 96 | return QVariant();; 97 | } 98 | 99 | QVariant BrowserTableModel::data(const QModelIndex& index, int role) const 100 | { 101 | if (!index.isValid()) return QVariant(); 102 | 103 | int row = index.row(); 104 | int column = index.column(); 105 | 106 | switch (role) 107 | { 108 | 109 | case Qt::DecorationRole: 110 | { 111 | switch (column) 112 | { 113 | case 6: 114 | return FreeFn::getTypeIcon(list[row].type); 115 | 116 | break; 117 | default: 118 | return QVariant(); 119 | } 120 | } 121 | case Qt::DisplayRole: 122 | { 123 | switch (column) 124 | { 125 | case 0: return list[row].rowid; 126 | case 1: return list[row].type; 127 | case 2: return list[row].layer; 128 | case 3: return list[row].effect; 129 | case 4: return list[row].arp_seq; 130 | case 5: return !list[row].fav; //the boolean is reversed, to enable sorting! 131 | case 6: return list[row].name; 132 | case 7: return getTypeText(list[row].type); 133 | case 8: return getLayerText(list[row].layer); 134 | case 9: return getEffectText(list[row].effect); 135 | case 10: return list[row].arp_seq ? "Yes" : "No"; 136 | case 11: return list[row].file; 137 | case 12: return list[row].comment; 138 | default: return QVariant(); 139 | } 140 | 141 | } 142 | case Qt::TextAlignmentRole: return int(Qt::AlignLeft | Qt::AlignVCenter); 143 | } 144 | 145 | return QVariant(); 146 | 147 | } 148 | 149 | Qt::ItemFlags BrowserTableModel::flags(const QModelIndex&) const 150 | { 151 | auto flags = Qt::ItemIsSelectable | Qt::ItemIsEditable | Qt::ItemIsEnabled | Qt::ItemIsDragEnabled | Qt::ItemIsDropEnabled; 152 | return flags; 153 | } 154 | 155 | Qt::DropActions BrowserTableModel::supportedDropActions() const 156 | { 157 | auto flags = Qt::DropActions(); 158 | flags |= Qt::MoveAction; 159 | return flags; 160 | } 161 | 162 | 163 | bool FavButtonDelegate::mouseIsOnStar(QMouseEvent* e, const QStyleOptionViewItem& option) 164 | { 165 | auto pos = e->position(); 166 | 167 | int clickX = pos.x(); 168 | int clickY = pos.y(); 169 | 170 | QRect r = option.rect;//getting the rect of the cell 171 | int x, y, w, h; 172 | x = r.left()+5;//the X coordinate 173 | y = r.top()+5;//the Y coordinate 174 | w = 20;//button width 175 | h = 20;//button height 176 | 177 | if (clickX > x && clickX < x + w) 178 | if (clickY > y && clickY < y + h) 179 | { 180 | return true; 181 | } 182 | 183 | return false; 184 | } 185 | 186 | FavButtonDelegate::FavButtonDelegate(QObject* parent) 187 | : QItemDelegate(parent) 188 | { 189 | 190 | } 191 | #include 192 | void FavButtonDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const 193 | { 194 | //the boolean is reversed, to enable sorting! 195 | auto isFavourite = !index.data(Qt::DisplayRole).toBool(); 196 | 197 | QRect r = option.rect;//getting the rect of the cell 198 | int x, y, w, h; 199 | x = r.left()+5;//the X coordinate 200 | y = r.top()+2;//the Y coordinate 201 | w = 20;//button width 202 | h = 20;//button height 203 | auto starRect = QRect(x, y, w, h); 204 | 205 | const QIcon* px = nullptr; 206 | 207 | if (isFavourite) { 208 | px = &star_yellow; 209 | } 210 | else { 211 | px = &star_gray; 212 | } 213 | 214 | if (!px) return; 215 | 216 | px->paint(painter, starRect); 217 | 218 | if (m_row_hover == index.row() && m_row_hover != -1) 219 | { 220 | star_hover.paint(painter, starRect); 221 | } 222 | 223 | } 224 | 225 | 226 | bool FavButtonDelegate::editorEvent(QEvent* event, QAbstractItemModel*, const QStyleOptionViewItem& option, const QModelIndex& index) 227 | { 228 | 229 | 230 | if (event->type() == QEvent::MouseMove) { 231 | 232 | m_row_hover = mouseIsOnStar((QMouseEvent*)event, option) ? index.row() : -1; 233 | 234 | emit updateRequested(); 235 | 236 | return true; 237 | } 238 | 239 | if (event->type() == QEvent::MouseButtonPress) 240 | { 241 | if (!mouseIsOnStar((QMouseEvent*)event, option)) return false; 242 | 243 | emit favouriteClicked(index.row()); 244 | 245 | return false; 246 | 247 | } 248 | 249 | return false; 250 | } 251 | -------------------------------------------------------------------------------- /src/Model/PatchDatabase.cpp: -------------------------------------------------------------------------------- 1 | #include "PatchDatabase.h" 2 | 3 | #include "An1File.h" 4 | #include "PatchMemory.h" 5 | #include "View/Browser.h" 6 | #include "Database/Database.h" 7 | #include "MidiMaster.h" 8 | #include "View/GlobalWidgets.h" 9 | 10 | //private functions: 11 | void PatchDatabase::refreshTableView() { 12 | 13 | Db db; 14 | db.newStatement("SELECT rowid, fav, type, name, file, effect, layer, arp_seq, comment FROM patch"); 15 | 16 | std::vector rows; 17 | 18 | while (db.hasRows()) { 19 | rows.emplace_back(); 20 | rows.back().rowid = db.asRowId(0); 21 | rows.back().fav = db.asBool(1); 22 | rows.back().type = db.asInt(2); 23 | rows.back().name = db.asString(3).c_str(); 24 | rows.back().file = db.asString(4).c_str(); 25 | rows.back().effect = db.asInt(5); 26 | rows.back().layer = db.asInt(6); 27 | rows.back().arp_seq = db.asBool(7); 28 | rows.back().comment = db.asString(8).c_str(); 29 | } 30 | 31 | GlobalWidgets::browser->setPatchesToTableView(rows); 32 | } 33 | 34 | 35 | void PatchDatabase::setVoiceAsCurrent(long long rowid) 36 | { 37 | AN1xPatch result{}; 38 | //block controls db lifetime 39 | { 40 | Db db; 41 | db.newStatement("SELECT rowid, data FROM patch WHERE rowid=?"); 42 | 43 | db.bind(1, rowid); 44 | 45 | while (db.hasRows()) { 46 | result = db.asBlob(1); 47 | } 48 | } 49 | 50 | MidiMaster::setCurrentPatch( 51 | result, 52 | { PatchSource::Database, rowid } 53 | ); 54 | } 55 | 56 | void PatchDatabase::deleteSelectedPatches(const std::set& rowids) 57 | { 58 | Db db; 59 | 60 | db.execute("BEGIN TRANSACTION"); 61 | 62 | for (auto rowid : rowids) { 63 | 64 | MidiMaster::notifyRowidDelete(rowid); 65 | 66 | db.newStatement("DELETE FROM patch WHERE rowid=?"); 67 | 68 | db.bind(1, rowid); 69 | 70 | db.execute(); 71 | } 72 | 73 | db.execute("END TRANSACTION"); 74 | 75 | refreshTableView(); 76 | } 77 | 78 | std::vector s_fileBuffer; 79 | 80 | void PatchDatabase::loadAn1FileToBuffer(const std::vector& data, const std::string& filename) 81 | { 82 | s_fileBuffer.push_back({ data, filename }); 83 | } 84 | 85 | void PatchDatabase::importFileBufferToDb() 86 | { 87 | Db db; 88 | 89 | db.execute("BEGIN TRANSACTION"); 90 | 91 | for (auto& file : s_fileBuffer) 92 | { 93 | 94 | for (int i = 0; i < file.patchCount(); i++) { 95 | 96 | auto patch = file.getPatch(i); 97 | 98 | auto name = patch.getName(); 99 | auto type = patch.getType(); 100 | 101 | db.newStatement("INSERT INTO patch (type, name, layer, effect, arp_seq, file, comment, data, hash) VALUES (?,?,?,?,?,?,?,?,?)"); 102 | 103 | db.bind(1, type); 104 | db.bind(2, name); 105 | db.bind(3, patch.getLayer()); 106 | db.bind(4, patch.getEffect()); 107 | db.bind(5, patch.hasArpSeqEnabled()); 108 | db.bind(6, file.filename); 109 | db.bind(7, file.getComment(i)); 110 | db.bind(8, patch.rawData().data(), AN1xPatch::PatchSize); 111 | db.bind(9, patch.getHash()); 112 | 113 | db.execute(); 114 | } 115 | } 116 | 117 | db.execute("END TRANSACTION"); 118 | 119 | 120 | db.execute( 121 | "DELETE FROM patch WHERE rowid NOT IN(" 122 | "SELECT MIN(rowid) FROM patch GROUP BY hash)" 123 | ); 124 | 125 | 126 | refreshTableView(); 127 | 128 | s_fileBuffer.clear(); 129 | } 130 | 131 | void PatchDatabase::saveVoice(const AN1xPatch& p, long long rowid) 132 | { 133 | 134 | std::string query = rowid ? 135 | "UPDATE patch SET data=?, type=?, name=?, layer=?, effect=?, arp_seq=?, hash=? WHERE rowid=?" 136 | : 137 | "INSERT INTO patch (data, type, name, layer, effect, arp_seq, hash) VALUES(?,?,?,?,?,?,?)" 138 | ; 139 | 140 | Db db; 141 | db.newStatement(query); 142 | 143 | 144 | db.bind(1, p.rawData().data(), AN1xPatch::PatchSize); 145 | db.bind(2, p.getType()); 146 | db.bind(3, p.getName()); 147 | db.bind(4, p.getLayer()); 148 | db.bind(5, p.getEffect()); 149 | db.bind(6, p.hasArpSeqEnabled()); 150 | db.bind(7, p.getHash()); 151 | 152 | if (rowid) { 153 | db.bind(8, rowid); 154 | } 155 | 156 | db.execute(); 157 | 158 | db.execute( 159 | "DELETE FROM patch WHERE rowid NOT IN(" 160 | "SELECT MIN(rowid) FROM patch GROUP BY hash)" 161 | ); 162 | 163 | refreshTableView(); 164 | 165 | if (!rowid) GlobalWidgets::browser->scrollToBottom(); 166 | } 167 | 168 | AN1xPatch PatchDatabase::getPatch(long long rowid) 169 | { 170 | Db db; 171 | db.newStatement("SELECT rowid, data FROM patch WHERE rowid=?"); 172 | 173 | db.bind(1, rowid); 174 | 175 | while (db.hasRows()) { 176 | 177 | return AN1xPatch{ db.asBlob(1) }; 178 | } 179 | 180 | return AN1xPatch{}; 181 | } 182 | 183 | void PatchDatabase::updateComment(const std::string& comment, const std::set& rowids) 184 | { 185 | Db db; 186 | 187 | db.execute("BEGIN TRANSACTION"); 188 | 189 | for (auto rowid : rowids) 190 | { 191 | db.newStatement("UPDATE patch SET comment=? WHERE rowid=?"); 192 | db.bind(1, comment); 193 | db.bind(2, rowid); 194 | db.execute(); 195 | } 196 | 197 | db.execute("END TRANSACTION"); 198 | 199 | refreshTableView(); 200 | } 201 | 202 | void PatchDatabase::importExternalDb(const std::string& filepath) 203 | { 204 | if (filepath == Db::getDbPath()) return; 205 | 206 | Db db; 207 | 208 | db.newStatement("ATTACH ? AS external"); 209 | db.bind(1, filepath); 210 | db.execute(); 211 | 212 | db.execute("ALTER TABLE external.patch ADD fav INTEGER DEFAULT 0"); 213 | 214 | db.execute("INSERT INTO " 215 | "patch (hash,type,name,file,layer,effect,arp_seq,comment,data,fav) " 216 | "SELECT hash,type,name,file,layer,effect,arp_seq,comment,data,fav FROM external.patch" 217 | ); 218 | db.execute("DETACH external"); 219 | 220 | db.execute( 221 | "DELETE FROM patch WHERE rowid NOT IN(" 222 | "SELECT MIN(rowid) FROM patch GROUP BY hash)" 223 | ); 224 | 225 | refreshTableView(); 226 | } 227 | 228 | void PatchDatabase::setMidiSettings(const MidiDeviceNames& devices, const AdvancedMidiSettings& s) 229 | { 230 | Db db; 231 | 232 | db.newStatement("DELETE FROM settings"); 233 | 234 | db.execute(); 235 | 236 | db.newStatement("INSERT INTO settings (midi_in, midi_out, midi_send_ch, midi_thru, device_no, buffer_size, buffer_delay) VALUES (?,?,?,?,?,?,?)"); 237 | 238 | db.bind(1, devices.midi_in); 239 | db.bind(2, devices.midi_out); 240 | db.bind(3, s.midi_send_channel); 241 | db.bind(4, s.midi_thru); 242 | db.bind(5, s.device_no); 243 | db.bind(6, s.buffer_size); 244 | db.bind(7, s.msDelay); 245 | 246 | db.execute(); 247 | } 248 | 249 | std::pair PatchDatabase::getMidiSettings() 250 | { 251 | 252 | Db db; 253 | db.newStatement( 254 | "SELECT midi_in, midi_out, midi_send_ch, midi_thru, device_no, buffer_size, buffer_delay FROM settings" 255 | ); 256 | 257 | while (db.hasRows()) { 258 | return { MidiDeviceNames{ 259 | .midi_in = db.asString(0), 260 | .midi_out = db.asString(1) 261 | }, 262 | AdvancedMidiSettings{ 263 | .midi_send_channel = db.asInt(2), 264 | .midi_thru = db.asBool(3), 265 | .device_no = db.asInt(4), 266 | .buffer_size = db.asInt(5), 267 | .msDelay = db.asInt(6) 268 | } 269 | }; 270 | } 271 | 272 | return {}; 273 | } 274 | 275 | 276 | void PatchDatabase::setFavourite(bool fav, long long rowid) 277 | { 278 | Db db; 279 | 280 | db.newStatement("UPDATE patch SET fav=? WHERE rowid=?"); 281 | 282 | db.bind(1, fav); 283 | db.bind(2, rowid); 284 | 285 | db.execute(); 286 | 287 | refreshTableView(); 288 | } 289 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | ## Ignore Visual Studio temporary files, build results, and 2 | ## files generated by popular Visual Studio add-ons. 3 | ## 4 | ## Get latest from https://github.com/github/gitignore/blob/master/VisualStudio.gitignore 5 | 6 | # User-specific files 7 | *.rsuser 8 | *.suo 9 | *.user 10 | *.userosscache 11 | *.sln.docstates 12 | 13 | # User-specific files (MonoDevelop/Xamarin Studio) 14 | *.userprefs 15 | 16 | # Mono auto generated files 17 | mono_crash.* 18 | 19 | # Build results 20 | [Dd]ebug/ 21 | [Dd]ebugPublic/ 22 | [Rr]elease/ 23 | [Rr]eleases/ 24 | x64/ 25 | x86/ 26 | [Ww][Ii][Nn]32/ 27 | [Aa][Rr][Mm]/ 28 | [Aa][Rr][Mm]64/ 29 | bld/ 30 | [Bb]in/ 31 | [Oo]bj/ 32 | [Oo]ut/ 33 | [Ll]og/ 34 | [Ll]ogs/ 35 | 36 | # Visual Studio 2015/2017 cache/options directory 37 | .vs/ 38 | # Uncomment if you have tasks that create the project's static files in wwwroot 39 | #wwwroot/ 40 | 41 | # Visual Studio 2017 auto generated files 42 | Generated\ Files/ 43 | 44 | # MSTest test Results 45 | [Tt]est[Rr]esult*/ 46 | [Bb]uild[Ll]og.* 47 | 48 | # NUnit 49 | *.VisualState.xml 50 | TestResult.xml 51 | nunit-*.xml 52 | 53 | # Build Results of an ATL Project 54 | [Dd]ebugPS/ 55 | [Rr]eleasePS/ 56 | dlldata.c 57 | 58 | # Benchmark Results 59 | BenchmarkDotNet.Artifacts/ 60 | 61 | # .NET Core 62 | project.lock.json 63 | project.fragment.lock.json 64 | artifacts/ 65 | 66 | # ASP.NET Scaffolding 67 | ScaffoldingReadMe.txt 68 | 69 | # StyleCop 70 | StyleCopReport.xml 71 | 72 | # Files built by Visual Studio 73 | *_i.c 74 | *_p.c 75 | *_h.h 76 | *.ilk 77 | *.meta 78 | *.obj 79 | *.iobj 80 | *.pch 81 | *.pdb 82 | *.ipdb 83 | *.pgc 84 | *.pgd 85 | *.rsp 86 | *.sbr 87 | *.tlb 88 | *.tli 89 | *.tlh 90 | *.tmp 91 | *.tmp_proj 92 | *_wpftmp.csproj 93 | *.log 94 | *.vspscc 95 | *.vssscc 96 | .builds 97 | *.pidb 98 | *.svclog 99 | *.scc 100 | 101 | # Chutzpah Test files 102 | _Chutzpah* 103 | 104 | # Visual C++ cache files 105 | ipch/ 106 | *.aps 107 | *.ncb 108 | *.opendb 109 | *.opensdf 110 | *.sdf 111 | *.cachefile 112 | *.VC.db 113 | *.VC.VC.opendb 114 | 115 | # Visual Studio profiler 116 | *.psess 117 | *.vsp 118 | *.vspx 119 | *.sap 120 | 121 | # Visual Studio Trace Files 122 | *.e2e 123 | 124 | # TFS 2012 Local Workspace 125 | $tf/ 126 | 127 | # Guidance Automation Toolkit 128 | *.gpState 129 | 130 | # ReSharper is a .NET coding add-in 131 | _ReSharper*/ 132 | *.[Rr]e[Ss]harper 133 | *.DotSettings.user 134 | 135 | # TeamCity is a build add-in 136 | _TeamCity* 137 | 138 | # DotCover is a Code Coverage Tool 139 | *.dotCover 140 | 141 | # AxoCover is a Code Coverage Tool 142 | .axoCover/* 143 | !.axoCover/settings.json 144 | 145 | # Coverlet is a free, cross platform Code Coverage Tool 146 | coverage*.json 147 | coverage*.xml 148 | coverage*.info 149 | 150 | # Visual Studio code coverage results 151 | *.coverage 152 | *.coveragexml 153 | 154 | # NCrunch 155 | _NCrunch_* 156 | .*crunch*.local.xml 157 | nCrunchTemp_* 158 | 159 | # MightyMoose 160 | *.mm.* 161 | AutoTest.Net/ 162 | 163 | # Web workbench (sass) 164 | .sass-cache/ 165 | 166 | # Installshield output folder 167 | [Ee]xpress/ 168 | 169 | # DocProject is a documentation generator add-in 170 | DocProject/buildhelp/ 171 | DocProject/Help/*.HxT 172 | DocProject/Help/*.HxC 173 | DocProject/Help/*.hhc 174 | DocProject/Help/*.hhk 175 | DocProject/Help/*.hhp 176 | DocProject/Help/Html2 177 | DocProject/Help/html 178 | 179 | # Click-Once directory 180 | publish/ 181 | 182 | # Publish Web Output 183 | *.[Pp]ublish.xml 184 | *.azurePubxml 185 | # Note: Comment the next line if you want to checkin your web deploy settings, 186 | # but database connection strings (with potential passwords) will be unencrypted 187 | *.pubxml 188 | *.publishproj 189 | 190 | # Microsoft Azure Web App publish settings. Comment the next line if you want to 191 | # checkin your Azure Web App publish settings, but sensitive information contained 192 | # in these scripts will be unencrypted 193 | PublishScripts/ 194 | 195 | # NuGet Packages 196 | *.nupkg 197 | # NuGet Symbol Packages 198 | *.snupkg 199 | # The packages folder can be ignored because of Package Restore 200 | **/[Pp]ackages/* 201 | # except build/, which is used as an MSBuild target. 202 | !**/[Pp]ackages/build/ 203 | # Uncomment if necessary however generally it will be regenerated when needed 204 | #!**/[Pp]ackages/repositories.config 205 | # NuGet v3's project.json files produces more ignorable files 206 | *.nuget.props 207 | *.nuget.targets 208 | 209 | # Microsoft Azure Build Output 210 | csx/ 211 | *.build.csdef 212 | 213 | # Microsoft Azure Emulator 214 | ecf/ 215 | rcf/ 216 | 217 | # Windows Store app package directories and files 218 | AppPackages/ 219 | BundleArtifacts/ 220 | Package.StoreAssociation.xml 221 | _pkginfo.txt 222 | *.appx 223 | *.appxbundle 224 | *.appxupload 225 | 226 | # Visual Studio cache files 227 | # files ending in .cache can be ignored 228 | *.[Cc]ache 229 | # but keep track of directories ending in .cache 230 | !?*.[Cc]ache/ 231 | 232 | # Others 233 | ClientBin/ 234 | ~$* 235 | *~ 236 | *.dbmdl 237 | *.dbproj.schemaview 238 | *.jfm 239 | *.pfx 240 | *.publishsettings 241 | orleans.codegen.cs 242 | 243 | # Including strong name files can present a security risk 244 | # (https://github.com/github/gitignore/pull/2483#issue-259490424) 245 | #*.snk 246 | 247 | # Since there are multiple workflows, uncomment next line to ignore bower_components 248 | # (https://github.com/github/gitignore/pull/1529#issuecomment-104372622) 249 | #bower_components/ 250 | 251 | # RIA/Silverlight projects 252 | Generated_Code/ 253 | 254 | # Backup & report files from converting an old project file 255 | # to a newer Visual Studio version. Backup files are not needed, 256 | # because we have git ;-) 257 | _UpgradeReport_Files/ 258 | Backup*/ 259 | UpgradeLog*.XML 260 | UpgradeLog*.htm 261 | ServiceFabricBackup/ 262 | *.rptproj.bak 263 | 264 | # SQL Server files 265 | *.mdf 266 | *.ldf 267 | *.ndf 268 | 269 | # Business Intelligence projects 270 | *.rdl.data 271 | *.bim.layout 272 | *.bim_*.settings 273 | *.rptproj.rsuser 274 | *- [Bb]ackup.rdl 275 | *- [Bb]ackup ([0-9]).rdl 276 | *- [Bb]ackup ([0-9][0-9]).rdl 277 | 278 | # Microsoft Fakes 279 | FakesAssemblies/ 280 | 281 | # GhostDoc plugin setting file 282 | *.GhostDoc.xml 283 | 284 | # Node.js Tools for Visual Studio 285 | .ntvs_analysis.dat 286 | node_modules/ 287 | 288 | # Visual Studio 6 build log 289 | *.plg 290 | 291 | # Visual Studio 6 workspace options file 292 | *.opt 293 | 294 | # Visual Studio 6 auto-generated workspace file (contains which files were open etc.) 295 | *.vbw 296 | 297 | # Visual Studio LightSwitch build output 298 | **/*.HTMLClient/GeneratedArtifacts 299 | **/*.DesktopClient/GeneratedArtifacts 300 | **/*.DesktopClient/ModelManifest.xml 301 | **/*.Server/GeneratedArtifacts 302 | **/*.Server/ModelManifest.xml 303 | _Pvt_Extensions 304 | 305 | # Paket dependency manager 306 | .paket/paket.exe 307 | paket-files/ 308 | 309 | # FAKE - F# Make 310 | .fake/ 311 | 312 | # CodeRush personal settings 313 | .cr/personal 314 | 315 | # Python Tools for Visual Studio (PTVS) 316 | __pycache__/ 317 | *.pyc 318 | 319 | # Cake - Uncomment if you are using it 320 | # tools/** 321 | # !tools/packages.config 322 | 323 | # Tabs Studio 324 | *.tss 325 | 326 | # Telerik's JustMock configuration file 327 | *.jmconfig 328 | 329 | # BizTalk build output 330 | *.btp.cs 331 | *.btm.cs 332 | *.odx.cs 333 | *.xsd.cs 334 | 335 | # OpenCover UI analysis results 336 | OpenCover/ 337 | 338 | # Azure Stream Analytics local run output 339 | ASALocalRun/ 340 | 341 | # MSBuild Binary and Structured Log 342 | *.binlog 343 | 344 | # NVidia Nsight GPU debugger configuration file 345 | *.nvuser 346 | 347 | # MFractors (Xamarin productivity tool) working folder 348 | .mfractor/ 349 | 350 | # Local History for Visual Studio 351 | .localhistory/ 352 | 353 | # BeatPulse healthcheck temp database 354 | healthchecksdb 355 | 356 | # Backup folder for Package Reference Convert tool in Visual Studio 2017 357 | MigrationBackup/ 358 | 359 | # Ionide (cross platform F# VS Code tools) working folder 360 | .ionide/ 361 | 362 | # Fody - auto-generated XML schema 363 | FodyWeavers.xsd 364 | /patches.an2 365 | /include 366 | /installer/files 367 | /installer/compiled 368 | .DS_Store 369 | .DS_Store 370 | resources/.DS_Store 371 | -------------------------------------------------------------------------------- /src/Database/Database.cpp: -------------------------------------------------------------------------------- 1 | #include "Database.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | 9 | Db::Db(Db* existingConnection) 10 | : 11 | m_connectionOwned{ existingConnection == nullptr }, 12 | stmt{nullptr} 13 | { 14 | 15 | if(m_connectionOwned){ 16 | sqlite3_open(location.c_str(), &db_connection); 17 | } 18 | else 19 | { 20 | db_connection = existingConnection->db_connection; 21 | } 22 | 23 | if (db_connection == nullptr) { 24 | throw std::exception(); 25 | } 26 | 27 | execute("PRAGMA foreign_keys = ON"); 28 | 29 | } 30 | 31 | Db::Db(const std::string& path) 32 | { 33 | sqlite3_open(path.c_str(), &db_connection); 34 | 35 | if (db_connection == nullptr) { 36 | throw std::exception(); 37 | } 38 | 39 | execute("PRAGMA foreign_keys = ON"); 40 | } 41 | 42 | bool Db::hasRows(){ 43 | 44 | if (total_bindings != successful_bindings) return false; 45 | 46 | bool result = sqlite3_step(stmt) == SQLITE_ROW;//|| sqlite3_step(stmt) != ; 47 | 48 | if(!result) finalizeStatement(); 49 | 50 | return result; 51 | } 52 | 53 | int Db::asInt(int column){ 54 | if (sqlite3_column_type(stmt, column) == SQLITE_NULL) return 0; 55 | return sqlite3_column_int(stmt, column); 56 | } 57 | 58 | long long Db::asRowId(int column) 59 | { 60 | if (sqlite3_column_type(stmt, column) == SQLITE_NULL) return 0; 61 | return sqlite3_column_int64(stmt, column); 62 | } 63 | 64 | long long Db::asLongLong(int column) 65 | { 66 | if (sqlite3_column_type(stmt, column) == SQLITE_NULL) return 0; 67 | return sqlite3_column_int64(stmt, column); 68 | } 69 | 70 | bool Db::asBool(int column) 71 | { 72 | if (sqlite3_column_type(stmt, column) == SQLITE_NULL) return false; 73 | return static_cast(asInt(column)); 74 | } 75 | 76 | double Db::asDouble(int column) 77 | { 78 | if (sqlite3_column_type(stmt, column) == SQLITE_NULL) return 0; 79 | return sqlite3_column_double(stmt, column); 80 | } 81 | 82 | const void* Db::asBlob(int column) 83 | { 84 | return sqlite3_column_blob(stmt, column); 85 | } 86 | 87 | std::string Db::asString(int column) { 88 | if (sqlite3_column_type(stmt, column) == SQLITE_NULL) return ""; 89 | return reinterpret_cast(sqlite3_column_text(stmt, column)); 90 | } 91 | 92 | int Db::getColumnSize(int column) 93 | { 94 | return sqlite3_column_bytes(stmt, column); 95 | } 96 | 97 | 98 | void Db::newStatement(const std::string& query) 99 | { 100 | finalizeStatement(); 101 | sqlite3_prepare_v2(db_connection, query.c_str(), -1, &stmt, NULL); 102 | } 103 | //#include 104 | bool Db::execute(const std::string& query) 105 | { 106 | char* err; 107 | finalizeStatement(); 108 | int i = sqlite3_exec(db_connection, query.c_str(), NULL, NULL, &err); 109 | // qDebug() << query.c_str(); 110 | if (err && s_showError) { 111 | //SHOW ERROR 112 | } 113 | 114 | finalizeStatement(); 115 | // qDebug() << i; 116 | return i == SQLITE_OK; 117 | 118 | } 119 | 120 | long long Db::lastInsertedRowID() 121 | { 122 | return sqlite3_last_insert_rowid(db_connection); 123 | } 124 | 125 | std::string Db::getPreparedStatement() 126 | { 127 | char* ptr = sqlite3_expanded_sql(stmt); 128 | 129 | int size = 0; 130 | 131 | sqlite3_malloc(size); 132 | 133 | if (!size) return ""; 134 | 135 | std::string result(ptr, size); 136 | 137 | sqlite3_free(ptr); 138 | 139 | return result; 140 | } 141 | 142 | void Db::closeConnection() 143 | { 144 | if (!db_connection) return; 145 | 146 | if (stmt) sqlite3_reset(stmt); 147 | 148 | successful_bindings = 0; 149 | total_bindings = 0; 150 | 151 | sqlite3_close_v2(db_connection); 152 | } 153 | 154 | 155 | void Db::bind(int index, const std::string& value) 156 | { 157 | if(stmt == nullptr) return; 158 | 159 | total_bindings++; 160 | 161 | 162 | successful_bindings += 163 | sqlite3_bind_text(stmt, index, value.c_str(), -1, SQLITE_TRANSIENT) == SQLITE_OK; 164 | } 165 | 166 | void Db::bind(int index, int value) 167 | { 168 | if (stmt == nullptr) return; 169 | 170 | total_bindings++; 171 | 172 | successful_bindings += 173 | sqlite3_bind_int(stmt,index,value) == SQLITE_OK; 174 | } 175 | 176 | void Db::bind(int index, double value) 177 | { 178 | if (stmt == nullptr) return; 179 | 180 | total_bindings++; 181 | 182 | successful_bindings += 183 | sqlite3_bind_double(stmt, index, value) == SQLITE_OK; 184 | } 185 | 186 | void Db::bind(int index, long long value) 187 | { 188 | if (stmt == nullptr) return; 189 | 190 | total_bindings++; 191 | 192 | successful_bindings += 193 | sqlite3_bind_int64(stmt, index, value) == SQLITE_OK; 194 | } 195 | 196 | void Db::bind(int index, const void* ptr, int size) 197 | { 198 | if (stmt == nullptr) return; 199 | 200 | total_bindings++; 201 | 202 | successful_bindings += 203 | sqlite3_bind_blob(stmt, index, ptr, size, SQLITE_STATIC) == SQLITE_OK; 204 | } 205 | 206 | void Db::bindNull(int index) 207 | { 208 | if (stmt == nullptr) return; 209 | 210 | total_bindings++; 211 | 212 | successful_bindings += sqlite3_bind_null(stmt, index) ? 0 : 1; 213 | } 214 | 215 | bool Db::execute() 216 | { 217 | if (stmt == nullptr) return false; 218 | 219 | if (total_bindings != successful_bindings) 220 | { 221 | finalizeStatement(); 222 | return false; 223 | } 224 | 225 | auto result = sqlite3_step(stmt); 226 | finalizeStatement(); 227 | 228 | return result == SQLITE_DONE; 229 | } 230 | 231 | 232 | void Db::finalizeStatement() 233 | { 234 | total_bindings = 0; 235 | successful_bindings = 0; 236 | 237 | if (!stmt) return; 238 | 239 | sqlite3_finalize(stmt); 240 | stmt = nullptr; 241 | 242 | } 243 | 244 | 245 | int Db::version() 246 | { 247 | Db db; 248 | db.newStatement("PRAGMA user_version"); 249 | 250 | while (db.hasRows()) { 251 | return db.asLongLong(0); 252 | } 253 | 254 | return -1; 255 | } 256 | 257 | void Db::setVersion(int version) 258 | { 259 | Db db; 260 | db.newStatement("PRAGMA user_version =" + std::to_string(version)); 261 | db.execute(); 262 | } 263 | 264 | 265 | Db::~Db() 266 | { 267 | if (stmt != nullptr) { 268 | sqlite3_finalize(stmt); 269 | } 270 | 271 | if (m_connectionOwned && db_connection) { 272 | sqlite3_close_v2(db_connection); 273 | } 274 | } 275 | 276 | const char* tableSchema = 277 | "CREATE TABLE IF NOT EXISTS patch(rowid INTEGER PRIMARY KEY, fav INTEGER DEFAULT 0, hash BLOB(32), type INTEGER, name TEXT, file TEXT, layer INTEGER, effect INTEGER, arp_seq INTEGER, comment TEXT, data BLOB);" 278 | "CREATE TABLE IF NOT EXISTS settings (midi_in TEXT, midi_out TEXT, midi_send_ch INTEGER, midi_thru INTEGER DEFAULT 0, device_no INTEGER DEFAULT 1, buffer_size INTEGER DEFAULT 0, buffer_delay INTEGER DEFAULT 100);"; 279 | 280 | bool Db::createIfNotExist() 281 | { 282 | 283 | #ifdef Q_OS_WIN 284 | int index = 1; 285 | #else 286 | int index = 0; 287 | #endif 288 | 289 | auto stdPth = QStandardPaths::standardLocations(QStandardPaths::AppDataLocation); 290 | 291 | auto dataFolder = QDir(stdPth[index] + "/QAN1xEditor"); 292 | 293 | //creating the user data folder 294 | if (!dataFolder.exists()) dataFolder.mkpath("."); 295 | 296 | location = dataFolder.filePath("patches.qan1").toStdString(); 297 | 298 | Db db; 299 | 300 | db.execute(tableSchema); 301 | 302 | //some migrations 303 | if(db.version() == 0){ 304 | db.execute("ALTER TABLE patch ADD fav INTEGER DEFAULT 0"); 305 | db.execute("PRAGMA user_version = 1"); 306 | } 307 | 308 | if (db.version() == 1) { 309 | db.execute("ALTER TABLE settings ADD midi_thru INTEGER DEFAULT 0"); 310 | db.execute("ALTER TABLE settings ADD device_no INTEGER DEFAULT 1"); 311 | db.execute("PRAGMA user_version = 2"); 312 | } 313 | 314 | if (db.version() == 2) { 315 | db.execute("ALTER TABLE settings ADD buffer_size INTEGER DEFAULT 0"); 316 | db.execute("ALTER TABLE settings ADD buffer_delay INTEGER DEFAULT 100"); 317 | db.execute("PRAGMA user_version = 3"); 318 | } 319 | 320 | return true; 321 | 322 | //rc = sqlite3_exec(db,"VACUUM", NULL, NULL, &err); 323 | 324 | } 325 | -------------------------------------------------------------------------------- /src/View/ArpSeq.cpp: -------------------------------------------------------------------------------- 1 | #include "ArpSeq.h" 2 | #include "Model/MidiMaster.h" 3 | 4 | ArpSeq::ArpSeq(QWidget *parent) 5 | : QWidget(parent) 6 | { 7 | ui.setupUi(this); 8 | 9 | ui_controls = { 10 | nullptr, //arp/seq select 11 | nullptr, //arptype - seq number 12 | ui.kbdMode, 13 | ui.stepHoldCombo, 14 | nullptr, //select scene 15 | ui.arpSubdivide, 16 | ui.swing, 17 | ui.velocity, 18 | ui.gate 19 | }; 20 | 21 | ui.swing->setSuffix("%"); 22 | ui.swing->setOffset(18); 23 | 24 | ui.velocity->setSuffix("%"); 25 | ui.gate->setSuffix("%"); 26 | 27 | 28 | setArpLayout(true); 29 | 30 | connect(ui.radioArp, &QRadioButton::clicked, this, [this] { setArpLayout(true); }); 31 | connect(ui.radioSeq, &QRadioButton::clicked, this, [this] { setArpLayout(false); }); 32 | connect(ui.kbdMode, &QComboBox::currentIndexChanged, this, [this](int index) { 33 | ui.seqPatternNo->setDisabled(index < 2); 34 | ui.seqLabel_1->setDisabled(index < 2); 35 | }); 36 | 37 | seq_controls = { 38 | ui.baseUnit, 39 | ui.seqLength, 40 | ui.loopType, 41 | ui.controlChange, 42 | nullptr, 43 | nullptr, 44 | ui.note_1, 45 | ui.note_2, 46 | ui.note_3, 47 | ui.note_4, 48 | ui.note_5, 49 | ui.note_6, 50 | ui.note_7, 51 | ui.note_8, 52 | ui.note_9, 53 | ui.note_10, 54 | ui.note_11, 55 | ui.note_12, 56 | ui.note_13, 57 | ui.note_14, 58 | ui.note_15, 59 | ui.note_16, 60 | ui.velocity_1, 61 | ui.velocity_2, 62 | ui.velocity_3, 63 | ui.velocity_4, 64 | ui.velocity_5, 65 | ui.velocity_6, 66 | ui.velocity_7, 67 | ui.velocity_8, 68 | ui.velocity_9, 69 | ui.velocity_10, 70 | ui.velocity_11, 71 | ui.velocity_12, 72 | ui.velocity_13, 73 | ui.velocity_14, 74 | ui.velocity_15, 75 | ui.velocity_16, 76 | ui.gate_1, 77 | ui.gate_2, 78 | ui.gate_3, 79 | ui.gate_4, 80 | ui.gate_5, 81 | ui.gate_6, 82 | ui.gate_7, 83 | ui.gate_8, 84 | ui.gate_9, 85 | ui.gate_10, 86 | ui.gate_11, 87 | ui.gate_12, 88 | ui.gate_13, 89 | ui.gate_14, 90 | ui.gate_15, 91 | ui.gate_16, 92 | ui.ctrl_1, 93 | ui.ctrl_2, 94 | ui.ctrl_3, 95 | ui.ctrl_4, 96 | ui.ctrl_5, 97 | ui.ctrl_6, 98 | ui.ctrl_7, 99 | ui.ctrl_8, 100 | ui.ctrl_9, 101 | ui.ctrl_10, 102 | ui.ctrl_11, 103 | ui.ctrl_12, 104 | ui.ctrl_13, 105 | ui.ctrl_14, 106 | ui.ctrl_15, 107 | ui.ctrl_16 108 | }; 109 | 110 | for (int i = AN1x::SeqNote1; i < AN1x::SeqVelocity1; i++) { 111 | 112 | auto note = static_cast(seq_controls[i]); 113 | 114 | note->setAsNoteCombo(); 115 | } 116 | 117 | for (int i = AN1x::SeqGateTime1; i < AN1x::SeqCtrlChange1; i++) 118 | { 119 | static_cast(seq_controls[i])->setSuffix("%"); 120 | } 121 | 122 | for (auto ui : ui_controls) if(ui)ui->setCurrentValueAsDefault(); 123 | 124 | p_steps = { 125 | ui.step_1, 126 | ui.step_2, 127 | ui.step_3, 128 | ui.step_4, 129 | ui.step_5, 130 | ui.step_6, 131 | ui.step_7, 132 | ui.step_8, 133 | ui.step_9, 134 | ui.step_10, 135 | ui.step_11, 136 | ui.step_12, 137 | ui.step_13, 138 | ui.step_14, 139 | ui.step_15, 140 | ui.step_16 141 | }; 142 | 143 | 144 | 145 | connect(ui.seqLength, &QSpinBox::valueChanged, this, [this](int value) { 146 | for (int i = 0; i < 16; i++) p_steps[i]->setEnabled(i < value); 147 | } 148 | ); 149 | 150 | ui.seqLength->setValue(8); 151 | 152 | QString SeqCtrlEntities[97]{ 153 | "Off","Mod Depth", "Breath", "Scene select", "Foot", "Portamento Time", "Data MSB", "Volume", "Layer", "Poly/Mono", 154 | "PanPot", "Expression", "Ribbon Z", "Ribbon X", "LFO Reset", "LFO1 Wave", "LFO1 Speed", "LFO2 Speed", "VCO1 LFO Mod Depth", "VCF LFO Mod Depth", 155 | "LFO1 Delay", "VCO1 Tune", "Sync Pitch", "Sync Depth", "Sync Source", "PEG Depth", "PEG Scene", "PEG Decay", "PEG Sustain", "PEG Release", "VCF Key Track", 156 | "AEG Sustain", "--", "VCO Algorithm", "Sync Pitch Mod", "FM Depth", "FM Source 1", "FM Source 2", "Data LSB", "Mix Noise", 157 | "--","--","--","--","--","--","--","--","--","--","VCO1 Wave", 158 | "VCO2 Wave","VCO2 Tune","VCO2 Fine","VCO2 Edge","VCO2 Pulse Width","VCO2 PW Depth","VCO2 LFO Mod Depth","VCF HPF","VCF Type", 159 | "FEG Vel.Sens.","AEG Vel.Sens.","VCA Volume","VCA Feedback","Sustain Switch", "Portamento Switch", "--","--","VCO1 Level","VCO2 Level", 160 | "Ring Mod. Level", "VCF Resonance", "AEG Release", "AEG Attack", "VCF Cutoff","AEG Decay","VCO1 Edge","VCO1 Fine","VCO1 Pusle Width","VCO1 PW Depth", 161 | "VCA LFO Mod Depth","FEG Depth","FEG Attack","FEG Decay","--","Portamento Mode","VCO1PSRC","VCO2PSRC","--","--","Arp/Seq","Reverb Send","--","VAR D:W","Delay Send","--","AT" 162 | }; 163 | 164 | for (auto& s : SeqCtrlEntities) ui.controlChange->addItem(s); 165 | 166 | ui.controlChange->setCurrentIndex(0); 167 | 168 | 169 | connect(ui.radioArp, &QRadioButton::clicked, [=] { MidiMaster::parameterChanged(ParamType::Common, AN1x::ArpSeqSelect, 0); }); 170 | connect(ui.radioSeq, &QRadioButton::clicked, [=] { MidiMaster::parameterChanged(ParamType::Common, AN1x::ArpSeqSelect, 1); }); 171 | connect(ui.radioScene1, &QRadioButton::clicked, [=] { MidiMaster::parameterChanged(ParamType::Common, AN1x::ArpSeqScene, 0); }); 172 | connect(ui.radioScene2, &QRadioButton::clicked, [=] { MidiMaster::parameterChanged(ParamType::Common, AN1x::ArpSeqScene, 1); }); 173 | connect(ui.radioBothScenes, &QRadioButton::clicked, [=] { MidiMaster::parameterChanged(ParamType::Common, AN1x::ArpSeqScene, 2); }); 174 | connect(ui.arpType, &QComboBox::currentIndexChanged, [=](int index) { MidiMaster::parameterChanged(ParamType::Common, AN1x::ArpTypeSeqNo, index); }); 175 | connect(ui.seqPatternNo, &QSpinBox::valueChanged, [=](int value) { MidiMaster::parameterChanged(ParamType::Common, AN1x::ArpTypeSeqNo, value - 1); }); 176 | 177 | for (size_t i = 0; i < ui_controls.size(); i++) 178 | { 179 | if (ui_controls[i] == nullptr) continue; 180 | 181 | auto param = AN1x::ArpSeqSelect + i; 182 | 183 | ui_controls[i]->setCurrentValueAsDefault(); 184 | ui_controls[i]->setParam(ParamType::Common, (AN1x::CommonParam)param); 185 | } 186 | 187 | connect(ui.seqLength, &QSpinBox::valueChanged, this, [=](int value) { MidiMaster::parameterChanged(ParamType::StepSq, AN1x::SeqLength, value); }); 188 | 189 | for (size_t i = 0; i < seq_controls.size(); i++) { 190 | 191 | if (seq_controls[i] == nullptr) continue; 192 | 193 | seq_controls[i]->setCurrentValueAsDefault(); 194 | seq_controls[i]->setParam(ParamType::StepSq, (AN1x::SeqParam)i); 195 | } 196 | 197 | } 198 | 199 | 200 | void ArpSeq::setCommonParameter(AN1x::CommonParam p, int value, bool setAsDefault) 201 | { 202 | if (p < AN1x::CommonParam::ArpSeqSelect || p >= AN1x::CommonParam::NullCommon23) return; 203 | 204 | switch (p) 205 | { 206 | case AN1x::ArpSeqSelect: 207 | value ? ui.radioSeq->click() : ui.radioArp->click(); 208 | break; 209 | 210 | case AN1x::ArpSeqScene: 211 | 212 | switch (value) { 213 | case 0: ui.radioScene1->click(); break; 214 | case 1: ui.radioScene2->click(); break; 215 | case 2: ui.radioBothScenes->click(); break; 216 | } 217 | break; 218 | 219 | case AN1x::ArpTypeSeqNo: 220 | ui.radioArp->isChecked() ? 221 | ui.arpType->setCurrentIndex(value) 222 | : 223 | ui.seqPatternNo->setValue(value + 1); 224 | break; 225 | 226 | default: 227 | auto ctrl = ui_controls[static_cast(p - AN1x::CommonParam::ArpSeqSelect)]; 228 | ctrl->setValue(value); 229 | if(setAsDefault) ctrl->setCurrentValueAsDefault(); 230 | 231 | } 232 | 233 | } 234 | 235 | void ArpSeq::setSequenceParameter(AN1x::SeqParam p, int value, bool setAsDefault) 236 | { 237 | if (seq_controls[p] == nullptr) return; 238 | 239 | if (p == AN1x::SeqLength) { 240 | ui.seqLength->setValue(value); 241 | 242 | if(setAsDefault){ 243 | ui.seqLength->setCurrentValueAsDefault(); 244 | } 245 | 246 | return; 247 | } 248 | 249 | seq_controls[p]->setValue(value); 250 | 251 | if(setAsDefault){ 252 | seq_controls[p]->setCurrentValueAsDefault(); 253 | } 254 | } 255 | 256 | void ArpSeq::setArpLayout(bool arp) 257 | { 258 | ui.arpType->setDisabled(!arp); 259 | ui.arpSubdivide->setDisabled(!arp); 260 | ui.arpLabel_1->setDisabled(!arp); 261 | ui.arpLabel_2->setDisabled(!arp); 262 | ui.stepGroup->setDisabled(arp); 263 | ui.swing->setDisabled(arp); 264 | ui.gate->setDisabled(arp); 265 | 266 | ui.seqLabel_2->setDisabled(arp); 267 | ui.seqLabel_3->setDisabled(arp); 268 | 269 | if (arp) { 270 | 271 | ui.seqPatternNo->setDisabled(true); 272 | ui.seqLabel_1->setDisabled(true); 273 | 274 | ui.kbdMode->setItemText(0, "Chord"); 275 | ui.kbdMode->setItemText(1, "Chord & Normal"); 276 | 277 | if (ui.kbdMode->count() > 2) { 278 | ui.kbdMode->removeItem(2); 279 | ui.kbdMode->removeItem(2); 280 | } 281 | 282 | if (ui.stepHoldCombo->count() > 2) 283 | { 284 | ui.stepHoldCombo->removeItem(2); 285 | ui.stepHoldCombo->setItemText(1, "On"); 286 | } 287 | } 288 | else { 289 | 290 | ui.kbdMode->setItemText(0, "Normal"); 291 | ui.kbdMode->setItemText(1, "Note Shift & Normal"); 292 | 293 | if (ui.kbdMode->count() < 3) 294 | { 295 | ui.kbdMode->addItem("Pt Select & Normal"); 296 | ui.kbdMode->addItem("Pt Select & Note Shift"); 297 | } 298 | 299 | 300 | if (ui.stepHoldCombo->count() < 3) 301 | { 302 | ui.stepHoldCombo->setItemText(1, "Mode 1"); 303 | ui.stepHoldCombo->addItem("Mode 2"); 304 | } 305 | 306 | bool isPattern = ui.kbdMode->currentIndex() > 1; 307 | 308 | ui.seqPatternNo->setDisabled(!isPattern); 309 | ui.seqLabel_1->setDisabled(!isPattern); 310 | 311 | } 312 | 313 | } 314 | 315 | ArpSeq::~ArpSeq() 316 | {} 317 | -------------------------------------------------------------------------------- /src/View/UiWidgets.cpp: -------------------------------------------------------------------------------- 1 | #include "UiWidgets.h" 2 | #include 3 | #include 4 | 5 | #include "Model/MidiMaster.h" 6 | #include 7 | #include 8 | #include 9 | 10 | #include "GlobalWidgets.h" 11 | #include 12 | 13 | bool DialKnob::event(QEvent* e) 14 | { 15 | 16 | switch (e->type()) 17 | { 18 | case QEvent::Enter: 19 | GlobalWidgets::statusBar->showMessage(getValueText()); 20 | return true; 21 | break; 22 | case QEvent::Leave: 23 | GlobalWidgets::statusBar->clearMessage(); 24 | return true; 25 | break; 26 | case QEvent::MouseButtonPress: 27 | if (static_cast(e)->button() == Qt::RightButton) 28 | { 29 | setValue(defaultValue); 30 | 31 | emit valueChanged(value()); 32 | return true; 33 | } 34 | break; 35 | default: 36 | break; 37 | } 38 | return QDial::event(e); 39 | } 40 | 41 | QString DialKnob::getValueText() 42 | { 43 | QString result = "Current value: "; 44 | 45 | switch(m_textType) 46 | { 47 | case Normal: 48 | { 49 | auto val = value() + m_offset; 50 | if (m_showPositive && val > 0) result += "+"; 51 | result += QString::number(val); 52 | 53 | result += m_suffix; 54 | } 55 | break; 56 | 57 | case EqFrequency: 58 | result += AN1x::getFrequencyByValue(value()); 59 | break; 60 | 61 | case Resonance: 62 | { 63 | auto val = value() + m_offset; 64 | QString number = QString::number(val); 65 | number.insert(value() < 100 ? 1:2, '.'); 66 | result += number; 67 | } 68 | break; 69 | 70 | case Milliseconds: 71 | { 72 | auto val = value() + m_offset; 73 | if (val == 0) { 74 | result += "0.0"; 75 | } 76 | else{ 77 | 78 | QString number = QString::number(val); 79 | if (val < 10) number.insert(0, "0."); 80 | else number.insert(number.size() - 1, '.'); 81 | 82 | result += number; 83 | } 84 | 85 | result += " ms"; 86 | } 87 | break; 88 | 89 | case ReverbDamp: 90 | { 91 | //same as Milliseconds, but no suffix 92 | auto val = value() + m_offset; 93 | if (val == 0) { 94 | result += "0.0"; 95 | } 96 | else { 97 | 98 | QString number = QString::number(val); 99 | if (val < 10) number.insert(0, "0."); 100 | else number.insert(number.size() - 1, '.'); 101 | 102 | result += number; 103 | } 104 | } 105 | break; 106 | 107 | 108 | case LFOFrquency: 109 | result += AN1x::getLFOFreqByValue(value()); 110 | break; 111 | case ChorusType: 112 | result += AN1x::getChorusTypeByValue(value()); 113 | break; 114 | case CompressorAttack: 115 | result += QString::number(AN1x::compressorAttack(value())); 116 | break; 117 | case CompressorRelease: 118 | result += QString::number(AN1x::compressorRelease(value())); 119 | break; 120 | case CompressorRatio: 121 | result += AN1x::compressorRatio(value()); 122 | break; 123 | case WahCutoffFreq: 124 | result += AN1x::wahCutoffFreq(value()); 125 | break; 126 | case ReverbTime: 127 | result += AN1x::reverbTime(value()); 128 | result += "s"; 129 | break; 130 | 131 | 132 | case Stage: 133 | { 134 | auto val = value(); 135 | int values[] = { 4,6,8 }; 136 | result += QString::number(values[val]); 137 | } 138 | break; 139 | 140 | case Diffusion: 141 | result += value() ? "Stereo" : "Mono"; 142 | break; 143 | 144 | case PanDirection: 145 | { 146 | const char* values[] = 147 | { 148 | "Left->Right", "Left<-Right","Left<->Right","Left turn", "Right turn" 149 | }; 150 | result += values[value()]; 151 | } 152 | break; 153 | 154 | case AMPType: 155 | { 156 | const char* values[] = 157 | { 158 | "Off", "Stack", "Combo", "Tube" 159 | }; 160 | result += values[value()]; 161 | } 162 | break; 163 | 164 | case DelayInput: 165 | { 166 | const char* values[] = 167 | { 168 | "Left", "Right", "Left & Right" 169 | }; 170 | result += values[value()]; 171 | } 172 | break; 173 | 174 | case TempoDelay: 175 | { 176 | const char* values[] = 177 | { 178 | "1/2","3/8","1/4","3/16","1/6","1/8","3/32","1/12","1/16","1/24","1/32" 179 | }; 180 | result += values[value()]; 181 | } 182 | break; 183 | 184 | case MasterTune: 185 | { 186 | double value = getValue() - 170 - 342; 187 | value = value * (double(100)/double(342)); 188 | return result += QString::number(value, 'F', 1) + " cents"; 189 | 190 | } 191 | break; 192 | 193 | 194 | } 195 | 196 | return result; 197 | } 198 | 199 | DialKnob::DialKnob(QWidget *parent) 200 | : QDial(parent) 201 | { 202 | setMouseTracking(true); 203 | setAttribute(Qt::WA_Hover, true); 204 | 205 | connect(this, &QDial::valueChanged, this, 206 | [&](int value) { 207 | if (type == ParamType::Unknown) return; 208 | GlobalWidgets::statusBar->showMessage(getValueText()); 209 | MidiMaster::parameterChanged(type, parameter, value); 210 | } 211 | ); 212 | 213 | } 214 | 215 | DialKnob::~DialKnob() 216 | {} 217 | 218 | void DialKnob::setCurrentValueAsDefault() 219 | { 220 | defaultValue = value(); 221 | } 222 | 223 | void DialKnob::setValue(int value) 224 | { 225 | 226 | QDial::setValue(value); 227 | 228 | if (underMouse() && GlobalWidgets::statusBar) { 229 | GlobalWidgets::statusBar->showMessage(getValueText()); 230 | } 231 | } 232 | 233 | ComboPicker::ComboPicker(QWidget* parent) : QComboBox(parent) 234 | { 235 | 236 | connect(this, &QComboBox::currentIndexChanged, this, 237 | [&](int value) { 238 | 239 | if(GlobalWidgets::statusBar) { 240 | GlobalWidgets::statusBar->showMessage("Current Value: " + itemText(value)); 241 | } 242 | 243 | if (type == ParamType::Unknown) return; 244 | 245 | if (m_isNoteCombo) value = 127 - value; 246 | 247 | MidiMaster::parameterChanged(type, parameter, value); } 248 | ); 249 | 250 | } 251 | 252 | 253 | 254 | void ComboPicker::setCurrentValueAsDefault() 255 | { 256 | defaultValue = currentIndex(); 257 | } 258 | 259 | void ComboPicker::setValue(int value) 260 | { 261 | if (m_isNoteCombo) value = 127-value; 262 | setCurrentIndex(value); 263 | } 264 | 265 | bool ComboPicker::event(QEvent* e) 266 | { 267 | switch (e->type()) 268 | { 269 | case QEvent::Enter: 270 | GlobalWidgets::statusBar->showMessage("Current Value: " + itemText(currentIndex())); 271 | return true; 272 | break; 273 | case QEvent::Leave: 274 | GlobalWidgets::statusBar->clearMessage(); 275 | return true; 276 | break; 277 | case QEvent::MouseButtonPress: 278 | if (static_cast(e)->button() == Qt::RightButton) 279 | { 280 | int val = defaultValue; 281 | 282 | emit currentIndexChanged(val); 283 | 284 | if(m_isNoteCombo){ 285 | 286 | val = (defaultValue - 127)*-1; 287 | } 288 | 289 | setValue(val); 290 | 291 | return true; 292 | } 293 | 294 | break; 295 | default: 296 | break; 297 | } 298 | 299 | return QComboBox::event(e); 300 | } 301 | 302 | 303 | void ComboPicker::setAsNoteCombo() 304 | { 305 | blockSignals(true); 306 | m_isNoteCombo = true; 307 | 308 | clear(); 309 | 310 | static const char* notelist[12] = { "B","A#","A","G#","G","F#","F","E","D#","D","C#","C" }; 311 | 312 | for (int y = 8; y >= -2; y--) { 313 | for (int j = 0; j < 12; j++) 314 | { 315 | if (y == 8 && j < 4) continue; 316 | addItem(QString::number(y) + notelist[j]); 317 | } 318 | } 319 | 320 | setCurrentIndex(127 - 60); 321 | 322 | blockSignals(false); 323 | } 324 | 325 | 326 | EGSlider::EGSlider(QWidget* parent) : QSlider(parent) 327 | { 328 | setMouseTracking(true); 329 | setAttribute(Qt::WA_Hover, true); 330 | 331 | connect(this, &QSlider::valueChanged, this, [&](int value) { 332 | 333 | if (GlobalWidgets::statusBar){ 334 | GlobalWidgets::statusBar->showMessage("Current value: " + QString::number(value)); 335 | } 336 | 337 | if (type == ParamType::Unknown) return; 338 | 339 | MidiMaster::parameterChanged(type, parameter, value); 340 | } 341 | ); 342 | } 343 | 344 | void EGSlider::setCurrentValueAsDefault() 345 | { 346 | defaultValue = value(); 347 | } 348 | 349 | void EGSlider::setValue(int value) 350 | { 351 | //blockSignals(true); 352 | 353 | QSlider::setValue (value); 354 | 355 | if (underMouse() && GlobalWidgets::statusBar) { 356 | GlobalWidgets::statusBar->showMessage("Current value: " + QString::number(value)); 357 | } 358 | //blockSignals(false); 359 | } 360 | 361 | bool EGSlider::event(QEvent* e) 362 | { 363 | switch (e->type()) 364 | { 365 | case QEvent::Enter: 366 | GlobalWidgets::statusBar->showMessage("Current value: " + QString::number(value())); 367 | return true; 368 | break; 369 | case QEvent::Leave: 370 | GlobalWidgets::statusBar->clearMessage(); 371 | return true; 372 | break; 373 | case QEvent::MouseButtonPress: 374 | if (static_cast(e)->button() == Qt::RightButton) 375 | { 376 | setValue(defaultValue); 377 | 378 | emit valueChanged(value()); 379 | return true; 380 | } 381 | 382 | break; 383 | default: 384 | break; 385 | } 386 | 387 | return QSlider::event(e); 388 | } 389 | 390 | CheckBox::CheckBox(QWidget* parent) : QCheckBox(parent) 391 | { 392 | connect(this, &QCheckBox::stateChanged, [&](bool checked) { 393 | if (type == ParamType::Unknown) return; 394 | MidiMaster::parameterChanged(type, parameter, checked); 395 | } 396 | ); 397 | } 398 | 399 | void CheckBox::setCurrentValueAsDefault() 400 | { 401 | defaultValue = isChecked(); 402 | } 403 | 404 | void CheckBox::setValue(int value) 405 | { 406 | //blockSignals(true); 407 | 408 | QCheckBox::setChecked(value); 409 | 410 | //blockSignals(false); 411 | } 412 | 413 | SpinBox::SpinBox(QWidget* parent) : QSpinBox(parent) 414 | { 415 | connect(this, &QSpinBox::valueChanged, [&](int value) { 416 | if (type == ParamType::Unknown) return; 417 | MidiMaster::parameterChanged(type, parameter, value); 418 | } 419 | ); 420 | } 421 | 422 | void SpinBox::setCurrentValueAsDefault() 423 | { 424 | defaultValue = value(); 425 | } 426 | 427 | void SpinBox::setValue(int value) 428 | { 429 | //blockSignals(true); 430 | QSpinBox::setValue(value); 431 | //blockSignals(false); 432 | } 433 | -------------------------------------------------------------------------------- /QMidiAn1x.vcxproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Debug 6 | x64 7 | 8 | 9 | Release 10 | x64 11 | 12 | 13 | 14 | {74CB02DB-461F-455B-B145-6ECDB581FE9B} 15 | QtVS_v304 16 | 10.0.22000.0 17 | 10.0 18 | $(MSBuildProjectDirectory)\QtMsBuild 19 | QAN1xEditor 20 | 21 | 22 | 23 | Application 24 | v143 25 | 26 | 27 | Application 28 | v143 29 | 30 | 31 | 32 | 33 | 34 | 35 | 6.3.1_msvc2019_64 36 | core;gui;network;widgets 37 | debug 38 | 39 | 40 | 6.3.1_msvc2019_64 41 | core;gui;network;widgets 42 | release 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | $(SolutionDir)include/sqlite3/;$(SolutionDir)include;%(AdditionalIncludeDirectories);$(Qt_INCLUDEPATH_);$(SolutionDir)src 65 | stdcpp20 66 | 67 | 68 | %(AdditionalDependencies);$(Qt_LIBS_);winmm.lib 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | $(SolutionDir)include/sqlite3/;$(SolutionDir)include;%(AdditionalIncludeDirectories);$(Qt_INCLUDEPATH_);$(SolutionDir)src 78 | stdcpp20 79 | 80 | 81 | %(AdditionalDependencies);$(Qt_LIBS_);winmm.lib 82 | 83 | 84 | 85 | 86 | true 87 | true 88 | ProgramDatabase 89 | Disabled 90 | 91 | 92 | Windows 93 | true 94 | 95 | 96 | 97 | 98 | true 99 | true 100 | None 101 | MaxSpeed 102 | 103 | 104 | Windows 105 | false 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | 197 | 198 | 199 | 200 | 201 | 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | 210 | 211 | -------------------------------------------------------------------------------- /src/View/Browser.ui: -------------------------------------------------------------------------------- 1 | 2 | 3 | BrowserClass 4 | 5 | 6 | 7 | 0 8 | 0 9 | 1024 10 | 730 11 | 12 | 13 | 14 | Browser 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | AN1x Memory 23 | 24 | 25 | 26 | 27 | 28 | 0 29 | 30 | 31 | 32 | 33 | Request 34 | 35 | 36 | 37 | 38 | 39 | 40 | Send 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 0 51 | 0 52 | 53 | 54 | 55 | 56 | 0 57 | 0 58 | 59 | 60 | 61 | 62 | 16777215 63 | 16777215 64 | 65 | 66 | 67 | true 68 | 69 | 70 | QAbstractItemView::DragDrop 71 | 72 | 73 | Qt::MoveAction 74 | 75 | 76 | QAbstractItemView::ExtendedSelection 77 | 78 | 79 | 80 | 16 81 | 16 82 | 83 | 84 | 85 | QListView::SinglePass 86 | 87 | 88 | 0 89 | 90 | 91 | 92 | 93 | 94 | 95 | 0 96 | 97 | 98 | 99 | 100 | Open AN1 file 101 | 102 | 103 | 104 | 105 | 106 | 107 | Save AN1 file 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | Local database 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | Search: 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 0 136 | 0 137 | 138 | 139 | 140 | 141 | 142 | 143 | 50 144 | 145 | 146 | 147 | 148 | 149 | 150 | Search column: 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | Name 159 | 160 | 161 | 162 | 163 | Type 164 | 165 | 166 | 167 | 168 | Layer 169 | 170 | 171 | 172 | 173 | Effect 174 | 175 | 176 | 177 | 178 | Arp/Seq 179 | 180 | 181 | 182 | 183 | File 184 | 185 | 186 | 187 | 188 | Comment 189 | 190 | 191 | 192 | 193 | 194 | 195 | 196 | Qt::Horizontal 197 | 198 | 199 | 200 | 40 201 | 20 202 | 203 | 204 | 205 | 206 | 207 | 208 | 209 | Total patches: 0 210 | 211 | 212 | 213 | 214 | 215 | 216 | 217 | 218 | false 219 | 220 | 221 | QAbstractItemView::DragDrop 222 | 223 | 224 | 225 | 226 | 227 | 228 | 3 229 | 230 | 231 | 232 | 233 | Import multiple AN1 files 234 | 235 | 236 | 237 | 238 | 239 | 240 | Edit comment 241 | 242 | 243 | 244 | 245 | 246 | 247 | Delete 248 | 249 | 250 | 251 | 252 | 253 | 254 | Qt::Horizontal 255 | 256 | 257 | QSizePolicy::Expanding 258 | 259 | 260 | 261 | 0 262 | 20 263 | 264 | 265 | 266 | 267 | 268 | 269 | 270 | Import database 271 | 272 | 273 | 274 | 275 | 276 | 277 | Export database 278 | 279 | 280 | 281 | 282 | 283 | 284 | 285 | 286 | 287 | 288 | 289 | 290 | 291 | 9 292 | 293 | 294 | 295 | 296 | true 297 | 298 | 299 | Cancel 300 | 301 | 302 | 303 | 304 | 305 | 306 | 0 307 | 308 | 309 | 1 310 | 311 | 312 | 0 313 | 314 | 315 | Qt::AlignCenter 316 | 317 | 318 | true 319 | 320 | 321 | %v/%m 322 | 323 | 324 | 325 | 326 | 327 | 328 | 329 | 330 | 331 | 332 | DbTableView 333 | QTableView 334 |
View/DbTableView.h
335 |
336 | 337 | MemoryList 338 | QListWidget 339 |
View/MemoryList.h
340 |
341 |
342 | 343 | 344 |
345 | -------------------------------------------------------------------------------- /QMidiAn1x.vcxproj.filters: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | qml;cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;hm;inl;inc;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | qrc;rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | {99349809-55BA-4b9d-BF79-8FDBB0286EB3} 18 | ui 19 | 20 | 21 | {639EADAA-A684-42e4-A9AD-28FC9BCB8F7C} 22 | ts 23 | 24 | 25 | 26 | 27 | Form Files 28 | 29 | 30 | Header Files 31 | 32 | 33 | Source Files 34 | 35 | 36 | Resource Files 37 | 38 | 39 | Form Files 40 | 41 | 42 | 43 | 44 | Source Files 45 | 46 | 47 | Source Files 48 | 49 | 50 | Source Files 51 | 52 | 53 | Source Files 54 | 55 | 56 | Source Files 57 | 58 | 59 | Source Files 60 | 61 | 62 | Source Files 63 | 64 | 65 | Source Files 66 | 67 | 68 | Source Files 69 | 70 | 71 | Source Files 72 | 73 | 74 | Source Files 75 | 76 | 77 | Source Files 78 | 79 | 80 | Source Files 81 | 82 | 83 | Source Files 84 | 85 | 86 | Source Files 87 | 88 | 89 | Source Files 90 | 91 | 92 | Source Files 93 | 94 | 95 | Source Files 96 | 97 | 98 | Source Files 99 | 100 | 101 | Source Files 102 | 103 | 104 | Source Files 105 | 106 | 107 | Source Files 108 | 109 | 110 | Source Files 111 | 112 | 113 | Source Files 114 | 115 | 116 | Source Files 117 | 118 | 119 | Source Files 120 | 121 | 122 | Source Files 123 | 124 | 125 | Source Files 126 | 127 | 128 | Source Files 129 | 130 | 131 | Source Files 132 | 133 | 134 | Source Files 135 | 136 | 137 | Source Files 138 | 139 | 140 | Source Files 141 | 142 | 143 | Source Files 144 | 145 | 146 | Source Files 147 | 148 | 149 | 150 | 151 | Header Files 152 | 153 | 154 | Header Files 155 | 156 | 157 | Header Files 158 | 159 | 160 | Header Files 161 | 162 | 163 | Header Files 164 | 165 | 166 | Header Files 167 | 168 | 169 | Header Files 170 | 171 | 172 | Header Files 173 | 174 | 175 | Header Files 176 | 177 | 178 | Header Files 179 | 180 | 181 | Header Files 182 | 183 | 184 | Header Files 185 | 186 | 187 | Header Files 188 | 189 | 190 | Header Files 191 | 192 | 193 | Header Files 194 | 195 | 196 | Header Files 197 | 198 | 199 | Header Files 200 | 201 | 202 | Header Files 203 | 204 | 205 | Header Files 206 | 207 | 208 | Header Files 209 | 210 | 211 | 212 | 213 | Header Files 214 | 215 | 216 | Header Files 217 | 218 | 219 | Header Files 220 | 221 | 222 | Header Files 223 | 224 | 225 | Header Files 226 | 227 | 228 | Header Files 229 | 230 | 231 | Header Files 232 | 233 | 234 | Header Files 235 | 236 | 237 | Header Files 238 | 239 | 240 | Header Files 241 | 242 | 243 | Header Files 244 | 245 | 246 | Header Files 247 | 248 | 249 | Header Files 250 | 251 | 252 | Header Files 253 | 254 | 255 | Header Files 256 | 257 | 258 | Header Files 259 | 260 | 261 | Header Files 262 | 263 | 264 | Header Files 265 | 266 | 267 | Header Files 268 | 269 | 270 | 271 | 272 | Form Files 273 | 274 | 275 | Form Files 276 | 277 | 278 | Form Files 279 | 280 | 281 | Form Files 282 | 283 | 284 | Form Files 285 | 286 | 287 | Form Files 288 | 289 | 290 | 291 | 292 | Resource Files 293 | 294 | 295 | 296 | 297 | Resource Files 298 | 299 | 300 | Resource Files 301 | 302 | 303 | Resource Files 304 | 305 | 306 | -------------------------------------------------------------------------------- /src/Model/MidiMaster.cpp: -------------------------------------------------------------------------------- 1 | #include "MidiMaster.h" 2 | #include "QMidi/qmidimessage.h" 3 | #include "QMidi/qmidiout.h" 4 | #include "QMidi/qmidiin.h" 5 | #include "View/QAN1xEditor.h" 6 | #include "An1xPatch.h" 7 | #include "View/GlobalWidgets.h" 8 | #include "PatchMemory.h" 9 | #include "PatchDatabase.h" 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | void waitForAWhile(int ms = 500) 16 | { 17 | QEventLoop loop; 18 | QTimer::singleShot(ms, &loop, SLOT(quit())); 19 | loop.exec(); 20 | } 21 | 22 | //static variables 23 | QMidiOut* s_out{ nullptr }; 24 | QMidiIn* s_in{ nullptr }; 25 | AdvancedMidiSettings settings; 26 | 27 | QAN1xEditor* s_view{ nullptr }; 28 | 29 | //patch currently edited 30 | AN1xPatch current_patch; 31 | PatchSource current_patch_src; 32 | bool s_voice_edited{ false }; 33 | 34 | int s_kbdOctave{ 5 }; 35 | 36 | //guards against recursion when setting parameters to view 37 | bool handlingMessage = true; 38 | 39 | void MidiMaster::setView(QAN1xEditor* v) { 40 | 41 | s_view = v; 42 | 43 | handlingMessage = true; 44 | 45 | s_view->setPatch(current_patch); 46 | 47 | handlingMessage = false; 48 | 49 | refreshConnection(); 50 | 51 | auto settings = PatchDatabase::getMidiSettings(); 52 | 53 | setAdvancedSettings(settings.second); 54 | 55 | s_view->setMidiDeviceNames(settings.first); 56 | 57 | 58 | } 59 | 60 | void saveCurrentSettings() { 61 | 62 | PatchDatabase::setMidiSettings(s_view->getCurrentDevices(), settings); 63 | } 64 | 65 | void makeEdited(bool edited) { 66 | s_voice_edited = edited; 67 | s_view->enableSaveButton(edited); 68 | } 69 | 70 | //returns false if dialog is CANCELED 71 | bool permissionToChangePatch() { 72 | 73 | if (!s_voice_edited) return true; 74 | 75 | std::string question = "Do you want to save current patch - "; 76 | question += current_patch.getName(); 77 | 78 | while (question.at(question.size() - 1) == ' ') { 79 | question.pop_back(); 80 | } 81 | question += "?"; 82 | 83 | int answer = GlobalWidgets::YesNoCancelQuestion(question.c_str()); 84 | 85 | switch (answer) { 86 | //no 87 | case 0: return true; 88 | //yes 89 | case 1: 90 | MidiMaster::saveCurrentPatch(); 91 | return true; 92 | //cancel 93 | default: return false; 94 | } 95 | 96 | return true; 97 | 98 | } 99 | 100 | void sendMessage(const Message& msg, bool bulkDump = false) 101 | { 102 | if (s_out == nullptr) return; 103 | 104 | if (handlingMessage) return; 105 | 106 | try { 107 | if (!s_out->isPortOpen()) return; 108 | 109 | auto m = msg; 110 | 111 | //SYSEX 112 | if (m.size() > 4 && 113 | m[0] == 0xF0 && 114 | m[1] == 0x43 && 115 | m[3] == 0x5C 116 | ){ 117 | m[2] += settings.device_no-1; 118 | } 119 | 120 | if(!bulkDump){ 121 | s_out->sendRawMessage(m); 122 | return; 123 | } 124 | 125 | AN1x::addCheckSum(m); 126 | 127 | m.push_back(0xF7); 128 | 129 | //send whole bulk dump at once 130 | if (!settings.buffer_size) { 131 | 132 | s_out->sendRawMessage(m); 133 | 134 | return; 135 | } 136 | 137 | const auto chunkSize = settings.buffer_size; 138 | 139 | auto chunks = m.size() / chunkSize; 140 | auto remainder = m.size() % chunkSize; 141 | 142 | for (size_t i = 0; i < chunks; ++i) { 143 | 144 | auto start = i * chunkSize; 145 | auto end = start + chunkSize; 146 | 147 | std::vector chunk; 148 | 149 | chunk.insert(chunk.end(), m.begin() + start, m.begin() + end); 150 | 151 | s_out->sendRawMessage(chunk); 152 | 153 | waitForAWhile(settings.msDelay); 154 | } 155 | 156 | if (!remainder) return; 157 | 158 | //the last chunk 159 | 160 | auto start = chunks * chunkSize; 161 | 162 | std::vector chunk; 163 | 164 | chunk.insert(chunk.end(), m.begin() + start, m.end()); 165 | 166 | s_out->sendRawMessage(chunk); 167 | 168 | } 169 | catch (std::exception) { 170 | MidiMaster::refreshConnection(); 171 | } 172 | } 173 | 174 | void handleSysMsg(const Message& msg) 175 | { 176 | //header check: 177 | if ( 178 | msg[0] != 0xF0 //exclusive 179 | || msg[1] != 0x43 //YAMAHA ID 180 | || msg[2]%16 != settings.device_no-1 181 | || msg[3] != 0x5C //AN1x MODEL ID 182 | ) { 183 | return; 184 | } 185 | 186 | if (msg.size() == 1953) //voice bulk recieved 187 | { 188 | handlingMessage = false; //otherwise the request for the next patch won't be recieved 189 | 190 | PatchMemory::patchRecieved({ msg }); 191 | 192 | return; 193 | } 194 | 195 | if (msg.size() == 39) //system data recieved 196 | { 197 | current_patch.setSystemData(msg); 198 | 199 | for (int i = 0; i < AN1x::SystemMaxSize; i++) 200 | { 201 | s_view->setParameter(ParamType::System, i, current_patch.getParameter(ParamType::System, i)); 202 | } 203 | 204 | return; 205 | } 206 | 207 | //parameter recieved (AN1x doesnt send AEG ADSR and a few more) 208 | 209 | ParamType type; 210 | 211 | if (msg[4] == 0) //System Param Change 212 | { 213 | type = ParamType::System; 214 | } 215 | else 216 | { 217 | switch (msg[5]) 218 | { 219 | case 0: type = ParamType::Common; break; 220 | case 16: type = ParamType::Scene1; break; 221 | case 17: type = ParamType::Scene2; break; 222 | case 14: type = ParamType::StepSq; break; 223 | default: return; 224 | } 225 | } 226 | 227 | unsigned char param = msg[6]; 228 | 229 | if (AN1x::isNull(type, param)){ 230 | return; 231 | } 232 | 233 | int value = msg[7]; 234 | 235 | if (AN1x::isTwoByteParameter(type, param)) 236 | { 237 | value = msg[7] * 128; 238 | value += msg[8]; 239 | } 240 | 241 | value -= AN1x::getOffset(type, param); 242 | 243 | current_patch.setParameter(type, param, value); 244 | s_view->setParameter(type, param, value); 245 | 246 | if(type != ParamType::System){ 247 | makeEdited(true); 248 | } 249 | } 250 | 251 | void MidiMaster::refreshConnection() 252 | { 253 | if (s_out) { 254 | s_out->closePort(); 255 | delete s_out; 256 | } 257 | 258 | if (s_in) 259 | { 260 | s_in->closePort(); 261 | delete s_in; 262 | } 263 | 264 | s_out = new QMidiOut; 265 | s_in = new QMidiIn; 266 | 267 | s_in->setIgnoreTypes(false, true, true); 268 | 269 | QObject::connect(s_in, &QMidiIn::midiMessageReceived, s_view, [=](QMidiMessage* m) 270 | { 271 | handlingMessage = true; 272 | 273 | auto status = m->getStatus(); 274 | 275 | if (status == QMidiStatus::MIDI_SYSEX) { 276 | handleSysMsg(m->getSysExData()); 277 | } 278 | 279 | if (settings.midi_thru) { 280 | s_out->sendMessage(m); 281 | } 282 | 283 | handlingMessage = false; 284 | 285 | delete m; 286 | }); 287 | 288 | s_view->setMidiDevices(s_in->getPorts(), s_out->getPorts()); 289 | 290 | } 291 | 292 | void MidiMaster::connectMidiIn(int idx) 293 | { 294 | if (!s_in) return; 295 | 296 | s_in->closePort(); 297 | 298 | if (idx != -1) 299 | s_in->openPort(idx); 300 | } 301 | 302 | void MidiMaster::connectMidiOut(int idx) 303 | { 304 | if (!s_out) return; 305 | 306 | s_out->closePort(); 307 | 308 | if (idx != -1) 309 | s_out->openPort(idx); 310 | } 311 | 312 | 313 | void MidiMaster::parameterChanged(ParamType type, unsigned char parameter, int value) 314 | { 315 | if (handlingMessage) return; 316 | 317 | if(type != ParamType::System){ 318 | makeEdited(true); 319 | } 320 | 321 | sendMessage(current_patch.setParameter(type, parameter, value)); 322 | } 323 | 324 | 325 | void MidiMaster::FreeEGChanged(const std::vector& trackData) 326 | { 327 | current_patch.setFreeEGData(trackData); 328 | 329 | makeEdited(true); 330 | 331 | //sending the whole common bulk 332 | sendMessage(current_patch.getDataMessage(ParamType::Common)); 333 | } 334 | 335 | void MidiMaster::setAdvancedSettings(const AdvancedMidiSettings& advSettings) 336 | { 337 | if (settings.midi_send_channel != advSettings.midi_send_channel) { 338 | stopAllSounds(); 339 | } 340 | 341 | settings = advSettings; 342 | 343 | saveCurrentSettings(); 344 | } 345 | 346 | /* 347 | void MidiMaster::goToVoice(int value) 348 | { 349 | if (value < 0 || value > 127) return; 350 | //sendMessage({ 0xC0, (unsigned char)value }); 351 | sendMessage(AN1x::voiceRequest(value)); 352 | 353 | //syncBulk(); 354 | } 355 | */ 356 | 357 | void MidiMaster::requestVoice(int index) 358 | { 359 | if (index < 0 || index > 127) return; 360 | sendMessage(AN1x::voiceRequest(index)); 361 | } 362 | 363 | void MidiMaster::sendBulk(const AN1xPatch& patch, int idx) 364 | { 365 | if (idx < 0 || idx > 127) return; 366 | 367 | Message msg = { 0xF0, 0x43, 0x00, 0x5C, 0x0F, 0x16, 0x11, (unsigned char)idx, 0x00 }; 368 | 369 | msg.reserve(patch.rawData().size() + 11); 370 | 371 | for (auto value : patch.rawData()) msg.push_back(value); 372 | 373 | sendMessage(msg, true); 374 | 375 | waitForAWhile(settings.msDelay); 376 | 377 | handlingMessage = false; 378 | 379 | } 380 | 381 | void MidiMaster::requestSystem() 382 | { 383 | Message msg = { 0xF0, 0x43, 0x20, 0x5C, 0x00, 0x00, 0x00, 0xF7 }; 384 | 385 | sendMessage(msg); 386 | } 387 | 388 | void MidiMaster::sendSystem() 389 | { 390 | sendMessage(AN1xPatch::getSystemDataMsg(), true); 391 | } 392 | 393 | void MidiMaster::restoreSystem() 394 | { 395 | AN1xPatch::restoreSystemData(); 396 | 397 | sendSystem(); 398 | 399 | handlingMessage = true; 400 | 401 | for (int i = 0; i < AN1xPatch::SystemSize; i++) 402 | { 403 | s_view->setParameter(ParamType::System, i, current_patch.getParameter(ParamType::System, i)); 404 | } 405 | 406 | handlingMessage = false; 407 | 408 | } 409 | 410 | void MidiMaster::setCurrentPatch(const AN1xPatch& p, PatchSource src) 411 | { 412 | if (s_voice_edited) { 413 | 414 | if (!current_patch_src.isSameAs(src)) { 415 | 416 | if(!permissionToChangePatch()) return; 417 | } 418 | else if(!GlobalWidgets::askQuestion("Do you want to reload the current patch and lose all changes?")){ 419 | 420 | return; 421 | } 422 | } 423 | 424 | current_patch = p; 425 | 426 | makeEdited(false); 427 | 428 | current_patch_src = src; 429 | 430 | handlingMessage = true; 431 | 432 | s_view->setPatch(p); 433 | 434 | handlingMessage = false; 435 | 436 | sendMessage(p.getDataMessage(ParamType::Common), true); waitForAWhile(settings.msDelay); 437 | sendMessage(p.getDataMessage(ParamType::Scene1), true); waitForAWhile(settings.msDelay); 438 | sendMessage(p.getDataMessage(ParamType::Scene2), true); waitForAWhile(settings.msDelay); 439 | sendMessage(p.getDataMessage(ParamType::StepSq), true); 440 | } 441 | 442 | const AN1xPatch& MidiMaster::currentPatch() 443 | { 444 | return current_patch; 445 | } 446 | 447 | void MidiMaster::notifyRowidDelete(long long rowid) 448 | { 449 | if (current_patch_src.getRowid() != rowid) return; 450 | 451 | current_patch_src.id = 0; 452 | 453 | makeEdited(true); 454 | } 455 | 456 | void MidiMaster::newPatch(AN1x::InitType type) 457 | { 458 | setCurrentPatch(AN1xPatch(type), { PatchSource::Database, 0 }); 459 | } 460 | 461 | 462 | void MidiMaster::setKbdOctave(int octave) { 463 | stopAllSounds(); 464 | s_kbdOctave = octave + 2; 465 | } 466 | 467 | void MidiMaster::pcKeyPress(int kbd_key, bool pressed, int velocity) { 468 | 469 | static int s_buttonsNotes[20]{ 470 | Qt::Key_A, 471 | Qt::Key_W, 472 | Qt::Key_S, 473 | Qt::Key_E, 474 | Qt::Key_D, 475 | Qt::Key_F, 476 | Qt::Key_T, 477 | Qt::Key_G, 478 | Qt::Key_Y, 479 | Qt::Key_H, 480 | Qt::Key_U, 481 | Qt::Key_J, 482 | Qt::Key_K, 483 | Qt::Key_O, 484 | Qt::Key_L, 485 | Qt::Key_P, 486 | Qt::Key_Semicolon, 487 | Qt::Key_Apostrophe, 488 | Qt::Key_BracketRight, 489 | Qt::Key_Backslash, 490 | }; 491 | 492 | int note{ -1 }; 493 | 494 | for (int i = 0; i < 20; i++) { 495 | if (kbd_key == s_buttonsNotes[i]) { 496 | note = (12 * s_kbdOctave) + i; 497 | 498 | if(note > 127) note = -1; 499 | } 500 | } 501 | 502 | setNote(note, pressed, velocity); 503 | }; 504 | 505 | void MidiMaster::setNote(int note, bool on, int velocity) { 506 | 507 | if (note == -1) return; 508 | 509 | QMidiMessage* m = new QMidiMessage(); 510 | 511 | m->setPitch(note); 512 | m->setStatus(on ? QMidiStatus::MIDI_NOTE_ON : QMidiStatus::MIDI_NOTE_OFF); 513 | m->setVelocity(velocity); 514 | m->setChannel(settings.midi_send_channel); 515 | s_out->sendMessage(m); 516 | 517 | s_view->pianoRoll()->setNote(note, on); 518 | 519 | } 520 | 521 | void MidiMaster::modWheelChange(int value) 522 | { 523 | if (value < 0 || value > 127) return; 524 | 525 | unsigned char channel = 0xB0 + settings.midi_send_channel - 1; 526 | sendMessage({ channel, 0x01, (unsigned char)value }); 527 | } 528 | 529 | void MidiMaster::pitchChange(int value) 530 | { 531 | if (value < 0 || value > 127) return; 532 | 533 | unsigned char channel = 0xE0 + settings.midi_send_channel - 1; 534 | sendMessage({ channel, 0x00, (unsigned char)value }); 535 | } 536 | 537 | 538 | void MidiMaster::stopAllSounds() 539 | { 540 | unsigned char channel = 176 + settings.midi_send_channel - 1; 541 | 542 | sendMessage({ channel, 0x78, 0x00 }); 543 | 544 | } 545 | 546 | void MidiMaster::saveCurrentPatch() 547 | { 548 | if (!s_voice_edited) return; 549 | 550 | current_patch_src.location == PatchSource::Database ? 551 | PatchDatabase::saveVoice(current_patch, current_patch_src.getRowid()) 552 | : 553 | PatchMemory::setPatch(current_patch, current_patch_src.getMemoryIndex()); 554 | 555 | makeEdited(false); 556 | 557 | } 558 | 559 | bool MidiMaster::cleanUp() 560 | { 561 | MidiMaster::stopAllSounds(); 562 | 563 | if (current_patch_src.location == PatchSource::Database) { 564 | if (!permissionToChangePatch()) return false; 565 | } 566 | 567 | saveCurrentSettings(); 568 | 569 | if(s_in) { delete s_in; } 570 | if(s_out) { delete s_out; } 571 | 572 | return true; 573 | 574 | } 575 | -------------------------------------------------------------------------------- /src/View/Browser.cpp: -------------------------------------------------------------------------------- 1 | #include "Browser.h" 2 | 3 | //#include 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | 12 | #include "FreeFunctions.h" 13 | #include "GlobalWidgets.h" 14 | 15 | #include "Database/Database.h" 16 | 17 | #include "Model/DragDropManager.h" 18 | #include "Model/PatchMemory.h" 19 | #include "Model/PatchDatabase.h" 20 | #include "Model/ClipoboardManager.h" 21 | #include "Model/An1File.h" 22 | 23 | Browser::Browser(QWidget* parent) 24 | : QWidget(parent) 25 | { 26 | ui.setupUi(this); 27 | 28 | GlobalWidgets::browser = this; 29 | 30 | column_sort.setDynamicSortFilter(true); 31 | 32 | column_sort.setSourceModel(&model); 33 | search.setSourceModel(&column_sort); 34 | search.setFilterKeyColumn(6); 35 | ui.databaseView->setModel(&search); 36 | ui.databaseView->setMouseTracking(true); 37 | disableWidgets(false); 38 | 39 | auto favDelagete = new FavButtonDelegate(); 40 | ui.databaseView->setItemDelegateForColumn(5, favDelagete); 41 | 42 | ui.databaseView->horizontalHeader()->setMinimumSectionSize(30); 43 | 44 | connect(favDelagete, &FavButtonDelegate::updateRequested, this, [&] { 45 | ui.databaseView->viewport()->update(); 46 | }); 47 | 48 | connect(favDelagete, &FavButtonDelegate::favouriteClicked, this, [&](int row) { 49 | 50 | PatchDatabase::setFavourite( 51 | search.index(row, 5).data().toBool(), 52 | search.index(row, 0).data().toLongLong() 53 | ); 54 | 55 | }); 56 | 57 | for (int i = 0; i < 5; i++) { 58 | ui.databaseView->hideColumn(i); 59 | } 60 | 61 | ui.databaseView->setColumnWidth(5, 30); 62 | ui.databaseView->setColumnWidth(10, 55); 63 | 64 | connect(ui.searchTypeCombo, &QComboBox::currentIndexChanged, this, [&](int index) { 65 | 66 | search.setFilterKeyColumn(index + 6); 67 | refreshCountLabel(); 68 | 69 | }); 70 | 71 | connect(ui.An1xList->model(), &QAbstractListModel::rowsMoved, this, [&](const QModelIndex&, int start, int, const QModelIndex& , int row) 72 | { 73 | PatchMemory::rowMoved(start, row); 74 | 75 | recalculateListNames(); 76 | }); 77 | 78 | connect(ui.commentButton, &QPushButton::clicked, this, [&] { editComment(); }); 79 | 80 | connect(ui.lineEdit, &QLineEdit::textChanged, this, [=] 81 | { 82 | QString text = ui.lineEdit->text(); 83 | search.setFilterRegularExpression(QRegularExpression(text, QRegularExpression::PatternOption::CaseInsensitiveOption)); 84 | refreshCountLabel(); 85 | 86 | }); 87 | 88 | connect(ui.databaseView->horizontalHeader(), &QHeaderView::sectionClicked, this, [&](int column) { 89 | 90 | const int hiddenColumnMap[] = { 0,1,2,3,4,5,1,2,3,4,10,11,12 }; 91 | 92 | column_sort.sort(hiddenColumnMap[column]); 93 | 94 | }); 95 | 96 | connect(ui.deleteButton, &QPushButton::clicked, this, [&] { 97 | 98 | auto selectedRowids = getSelectedTableRowids(); 99 | 100 | if (selectedRowids.empty()) return; 101 | 102 | QMessageBox::StandardButton reply; 103 | reply = QMessageBox::question(this, "Warning", "Delete is permanent. Are you sure?", 104 | QMessageBox::Yes | QMessageBox::No); 105 | 106 | if (reply == QMessageBox::No) { 107 | return; 108 | } 109 | 110 | PatchDatabase::deleteSelectedPatches(selectedRowids); 111 | 112 | }); 113 | 114 | connect(ui.databaseView, &DbTableView::deletePressed, this, [&] { ui.deleteButton->click(); }); 115 | 116 | connect(ui.An1xList, &MemoryList::deleteRequested, this, [&] { PatchMemory::initPatches(getSelectedListIndexes()); }); 117 | 118 | connect(ui.loadAN1ToList, &QPushButton::clicked, this, [&] { loadAN1FileToList(); }); 119 | 120 | connect(ui.saveAN1file, &QPushButton::clicked, this, [&] { saveAN1File(); }); 121 | 122 | connect(ui.exportAN2Button, &QPushButton::clicked, this, [&] { exportAN2File(); }); 123 | 124 | connect(ui.cancelButton, &QPushButton::clicked, this, [&] { 125 | 126 | PatchMemory::loadFromAn1x({}); 127 | PatchMemory::sendToAn1x({}); 128 | disableWidgets(false); 129 | 130 | ui.progressBar->setValue(0); 131 | 132 | 133 | }); 134 | 135 | connect(ui.databaseView, &DbTableView::copyRequested, this, [&] { 136 | ClipboardManager::copyRequestFromDatabase(getSelectedTableRowids()); 137 | }); 138 | 139 | connect(ui.An1xList, &MemoryList::copyRequested, this, [&] { 140 | ClipboardManager::copyRequestFromMemoryList(getSelectedListIndexes()); 141 | }); 142 | 143 | connect(ui.An1xList, &MemoryList::pasteRequested, this, [&]{ 144 | 145 | auto indexes = getSelectedListIndexes(); 146 | 147 | if (indexes.empty()) return; 148 | 149 | ClipboardManager::pasteToListRequested(indexes[0]); 150 | 151 | }); 152 | 153 | for (int i = 0; i < 128; i++) 154 | { 155 | ui.An1xList->addItem(""); 156 | setPatchToListView(i, "InitNormal", 0); 157 | } 158 | 159 | connect(ui.loadButton, &QPushButton::clicked, this, [&] {PatchMemory::loadFromAn1x(getSelectedListIndexes());}); 160 | connect(ui.sendButton, &QPushButton::clicked, this, [&] {PatchMemory::sendToAn1x(getSelectedListIndexes()); }); 161 | connect(ui.An1xList, &QListWidget::doubleClicked, this, [&] { 162 | 163 | auto indexes = getSelectedListIndexes(); 164 | 165 | if (indexes.empty()) return; 166 | 167 | PatchMemory::loadAn1xMemPatch(indexes[0]); 168 | 169 | }); 170 | 171 | connect(ui.importAn1, &QPushButton::clicked, this, [&] { 172 | importAN1FileButtonClicked(); 173 | }); 174 | 175 | connect(ui.importAn2, &QPushButton::clicked, this, [&] { 176 | importAN2FileButtonClicked(); 177 | }); 178 | 179 | connect(ui.databaseView, &QTableView::doubleClicked, this, [&](const QModelIndex& index) { 180 | 181 | int idx = search.index(index.row(), 0).data().toLongLong(); 182 | PatchDatabase::setVoiceAsCurrent(idx); 183 | 184 | }); 185 | 186 | connect(ui.databaseView, &DbTableView::dataDroped, this, [&] { 187 | DragDropManager::droppedToDbTable(getSelectedListIndexes()); 188 | 189 | }); 190 | 191 | connect(ui.An1xList, &MemoryList::dataDroped, this, [&](int row) { 192 | DragDropManager::droppedToMemoryList(getSelectedTableRowids(), row); 193 | }); 194 | 195 | PatchDatabase::refreshTableView(); 196 | 197 | } 198 | 199 | void Browser::setPatchToListView(int idx, const std::string& name, int type) 200 | { 201 | ui.An1xList->item(idx)->setText(generatePatchText(idx, name.c_str())); 202 | ui.An1xList->item(idx)->setIcon(FreeFn::getTypeIcon(type)); 203 | } 204 | 205 | Browser::~Browser() 206 | {} 207 | 208 | void Browser::recalculateListNames() 209 | { 210 | for (int i = 0; i < ui.An1xList->count(); i++) 211 | { 212 | auto text = ui.An1xList->item(i)->text(); 213 | 214 | QString number = QString::number(i+1); 215 | 216 | if (i+1 < 10) number += ". "; 217 | else if (i+1 < 100) number += ". "; 218 | else number += ". "; 219 | 220 | for (int y = 0; y < number.size(); y++) 221 | { 222 | text[y] = number[y]; 223 | } 224 | 225 | ui.An1xList->item(i)->setText(text); 226 | 227 | } 228 | } 229 | 230 | std::vector Browser::getSelectedListIndexes() 231 | { 232 | 233 | QModelIndexList indexes = ui.An1xList->selectionModel()->selectedIndexes(); 234 | 235 | std::vector indexList; 236 | 237 | for (QModelIndex& index : indexes){ 238 | indexList.push_back(index.row()); 239 | } 240 | 241 | return indexList; 242 | } 243 | 244 | std::set Browser::getSelectedTableRowids() 245 | { 246 | QModelIndexList indexes = ui.databaseView->selectionModel()->selectedIndexes(); 247 | 248 | std::set rowids; 249 | 250 | for (QModelIndex& index : indexes) { 251 | 252 | rowids.insert(search.index(index.row(), 0).data().toLongLong()); 253 | } 254 | 255 | return rowids; 256 | 257 | } 258 | 259 | void Browser::importAN1FileButtonClicked() 260 | { 261 | 262 | QFileDialog dialog(this); 263 | dialog.setDirectory(QDir::homePath()); 264 | dialog.setFileMode(QFileDialog::ExistingFiles); 265 | dialog.setNameFilter("An1x file (*.an1)"); 266 | QStringList fileNames; 267 | if (dialog.exec()) 268 | fileNames = dialog.selectedFiles(); 269 | 270 | if (fileNames.empty()) return; 271 | 272 | setProgressBarCount(fileNames.size()); 273 | 274 | for (auto& filePath : fileNames) 275 | { 276 | incrementProgressBar(); 277 | 278 | if (filePath.isEmpty()) continue; 279 | 280 | QFile file(filePath); 281 | 282 | if (!file.open(QIODevice::ReadOnly)) continue; 283 | 284 | auto bytes = file.readAll(); 285 | 286 | QFileInfo fileInfo(file.fileName()); 287 | 288 | try { 289 | PatchDatabase::loadAn1FileToBuffer(std::vector{bytes.begin(), bytes.end()}, fileInfo.fileName().toStdString());; 290 | } 291 | catch (std::exception) { 292 | QMessageBox msgBox; 293 | msgBox.setText("The file is corrupted"); 294 | msgBox.exec(); 295 | } 296 | 297 | file.close(); 298 | } 299 | 300 | PatchDatabase::importFileBufferToDb(); 301 | 302 | } 303 | 304 | void Browser::importAN2FileButtonClicked() 305 | { 306 | auto fileName = QFileDialog::getOpenFileName(this, 307 | tr("Open QAN1xEditor"), QDir::homePath(), "QAn1xEditor file(*.qan1)"); 308 | 309 | if (fileName.isEmpty()) return; 310 | 311 | PatchDatabase::importExternalDb(fileName.toStdString()); 312 | } 313 | 314 | void Browser::loadAN1FileToList() 315 | { 316 | auto fileName = QFileDialog::getOpenFileName(this, 317 | tr("Open AN1xEdit file"), QDir::homePath(), "AN1xEdit file(*.an1)"); 318 | 319 | if (fileName.isEmpty()) return; 320 | 321 | QFile file(fileName); 322 | 323 | if (!file.open(QIODevice::ReadOnly)) return; 324 | 325 | auto bytes = file.readAll(); 326 | 327 | try { 328 | PatchMemory::loadAn1File({ std::vector{bytes.begin(), bytes.end()}, "" }); 329 | } 330 | catch (std::exception) { 331 | QMessageBox msgBox; 332 | msgBox.setText("The file is corrupted"); 333 | msgBox.exec(); 334 | } 335 | 336 | file.close(); 337 | } 338 | 339 | 340 | void Browser::exportAN2File() 341 | { 342 | 343 | auto fileName = QFileDialog::getSaveFileName(this, 344 | tr("Export QAN1xEditor"), QDir::homePath() + "/patches.qan1", "QAn1xEditor file(*.qan1)"); 345 | 346 | if (fileName.isEmpty()) return; 347 | 348 | QFile::copy(Db::getDbPath().c_str(), fileName); 349 | } 350 | 351 | void Browser::editComment() 352 | { 353 | std::set rowids = getSelectedTableRowids(); 354 | 355 | if (rowids.empty()) return; 356 | 357 | QString comment; 358 | 359 | if (rowids.size() == 1) { 360 | 361 | comment = search.index(ui.databaseView->selectionModel()->selectedIndexes().first().row(), 12).data().toString(); 362 | } 363 | else { 364 | 365 | QMessageBox::information(this, qAppName(), "Changes will affect multiple patches!"); 366 | } 367 | 368 | QInputDialog d; 369 | d.setInputMode(QInputDialog::InputMode::TextInput); 370 | d.setOption(QInputDialog::InputDialogOption::UsePlainTextEditForTextInput); 371 | 372 | auto textEdit = d.findChild(); 373 | 374 | textEdit->setLineWrapMode(QPlainTextEdit::LineWrapMode::WidgetWidth); 375 | textEdit->setWordWrapMode(QTextOption::WrapMode::WordWrap); 376 | 377 | d.setLabelText("Comment:"); 378 | d.setTextValue(comment); 379 | 380 | if (d.exec() != QDialog::Accepted) return; 381 | 382 | PatchDatabase::updateComment(d.textValue().toStdString(), rowids); 383 | } 384 | 385 | void Browser::disableWidgets(bool disabled) 386 | { 387 | ui.progressSpacer->changeSize(0, 0, disabled ? QSizePolicy::Fixed : QSizePolicy::Expanding); 388 | 389 | for (auto obj : children()) { 390 | if (obj->isWidgetType()) { 391 | static_cast(obj)->setDisabled(disabled); 392 | } 393 | } 394 | ui.progressBar->setTextVisible(disabled); 395 | ui.progressBar->setHidden(!disabled); 396 | ui.cancelButton->setHidden(!disabled); 397 | ui.cancelButton->setDisabled(!disabled); 398 | 399 | } 400 | 401 | void Browser::saveAN1File() 402 | { 403 | auto fileName = QFileDialog::getSaveFileName(this, 404 | tr("Save as An1xEdit file"), QDir::homePath() + "/AN1xEdit1.an1", "An1xEdit file(*.an1)"); 405 | 406 | if (fileName.isEmpty()) return; 407 | 408 | QFile f(fileName); 409 | 410 | f.open(QIODeviceBase::WriteOnly); 411 | 412 | auto source = PatchMemory::getFile(); 413 | 414 | QByteArray byteArray(reinterpret_cast(source.data()), source.size()); 415 | 416 | f.write(byteArray); 417 | 418 | f.close(); 419 | } 420 | 421 | QString Browser::generatePatchText(int index, const char* name) 422 | { 423 | index++; 424 | 425 | QString text = QString::number(index); 426 | 427 | if (index < 10) text += ". "; 428 | else if (index < 100) text += ". "; 429 | else text += ". "; 430 | 431 | text += name; 432 | 433 | return text; 434 | } 435 | 436 | void Browser::setProgressBarCount(int count) 437 | { 438 | disableWidgets(count); 439 | ui.progressBar->setMaximum(count); 440 | ui.progressBar->setValue(0); 441 | } 442 | 443 | void Browser::incrementProgressBar() 444 | { 445 | auto value = ui.progressBar->value() + 1; 446 | ui.progressBar->setValue(value); 447 | 448 | if (value != ui.progressBar->maximum()) return; 449 | 450 | ui.progressBar->setValue(0); 451 | 452 | disableWidgets(false); 453 | 454 | } 455 | 456 | void Browser::setPatchesToTableView(const std::vector& patches) 457 | { 458 | model.setPatchData(patches); 459 | refreshCountLabel(); 460 | } 461 | 462 | void Browser::scrollToBottom() 463 | { 464 | int maximum = ui.databaseView->verticalScrollBar()->maximum(); 465 | 466 | ui.databaseView->verticalScrollBar()->setSliderPosition(maximum); 467 | } 468 | 469 | void Browser::refreshCountLabel() 470 | { 471 | ui.countLabel->setText(QString("Total results: ") + QString::number(search.rowCount())); 472 | } 473 | --------------------------------------------------------------------------------