├── example ├── Makefile ├── drumkits.asm ├── wave_samples.asm └── crystaltracked.asm ├── .gitattributes ├── res ├── app.icns ├── app.ico ├── blank.xpm ├── new.xpm ├── one.xpm ├── two.xpm ├── up.xpm ├── down.xpm ├── four.xpm ├── left.xpm ├── loop.xpm ├── minus.xpm ├── pause.xpm ├── play.xpm ├── plus.xpm ├── redo.xpm ├── right.xpm ├── stop.xpm ├── three.xpm ├── undo.xpm ├── up-up.xpm ├── delete.xpm ├── down-down.xpm ├── scroll-dark.xpm ├── scroll-light.xpm ├── keys.xpm ├── open.xpm ├── ruler.xpm ├── save.xpm ├── snip.xpm ├── decrease-spacing.xpm ├── increase-spacing.xpm ├── save-as.xpm ├── split-dark.xpm ├── split-light.xpm ├── notes.xpm ├── zoom-in.xpm ├── zoom-out.xpm ├── glue-dark.xpm ├── glue-light.xpm ├── verify.xpm ├── pencil.xpm ├── pencil-red.xpm ├── pencil-blue.xpm ├── pencil-brown.xpm ├── pencil-green.xpm ├── brush.xpm ├── Info.plist ├── brush-cmy.xpm ├── warning.xpm ├── error.xpm ├── success.xpm └── app-icon.xpm ├── screenshot.png ├── .vscode ├── settings.json ├── launch.json ├── tasks.json └── c_cpp_properties.json ├── ide ├── crystal-tracker.vcxproj.user ├── crystal-tracker.sln ├── crystal-tracker.rc └── crystal-tracker.vcxproj.filters ├── src ├── config.h ├── edit-context-menu.cpp ├── edit-context-menu.h ├── cocoa.h ├── resource.h ├── ruler.h ├── version.h ├── preferences.h ├── directory-chooser.h ├── help-window.h ├── cocoa.mm ├── parse-waves.h ├── config.cpp ├── modal-dialog.h ├── parse-drumkits.h ├── directory-chooser.cpp ├── help-window.cpp ├── hex-spinner.h ├── ruler.cpp ├── note-properties.h ├── preferences.cpp ├── main.cpp ├── parse-waves.cpp ├── themes.h ├── parse-song.h ├── it-module.h ├── utils.h ├── hex-spinner.cpp ├── option-dialogs.h ├── icons.h ├── modal-dialog.cpp ├── utils.cpp ├── widgets.h ├── command.h └── parse-drumkits.cpp ├── .gitignore ├── README.md ├── lib └── patches │ └── portaudio.patch ├── Makefile ├── CHANGELOG.md ├── LICENSE.md └── INSTALL.md /example/Makefile: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | # No newline conversion 2 | * -text 3 | -------------------------------------------------------------------------------- /res/app.icns: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dannye/crystal-tracker/HEAD/res/app.icns -------------------------------------------------------------------------------- /res/app.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dannye/crystal-tracker/HEAD/res/app.ico -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/dannye/crystal-tracker/HEAD/screenshot.png -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "files.associations": { 3 | "*.xpm": "cpp" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /ide/crystal-tracker.vcxproj.user: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/config.h: -------------------------------------------------------------------------------- 1 | #ifndef CONFIG_H 2 | #define CONFIG_H 3 | 4 | #include 5 | 6 | class Config { 7 | private: 8 | public: 9 | static bool project_path_from_asm_path(const char *asm_path, char *project_path); 10 | }; 11 | 12 | #endif 13 | -------------------------------------------------------------------------------- /src/edit-context-menu.cpp: -------------------------------------------------------------------------------- 1 | #include "edit-context-menu.h" 2 | 3 | #include "main-window.h" 4 | 5 | bool Edit_Context_Menu::prepare(int X, int Y) { 6 | Main_Window *mw = (Main_Window *)user_data(); 7 | mw->set_context_menu(X, Y); 8 | return menu() && menu()->text; 9 | } 10 | -------------------------------------------------------------------------------- /example/drumkits.asm: -------------------------------------------------------------------------------- 1 | Drumkits: 2 | dw Drumkit0 3 | 4 | Drumkit0: 5 | dw Drum00 6 | dw Drum00 7 | dw Drum00 8 | dw Drum00 9 | dw Drum00 10 | dw Drum00 11 | dw Drum00 12 | dw Drum00 13 | dw Drum00 14 | dw Drum00 15 | dw Drum00 16 | dw Drum00 17 | dw Drum00 18 | 19 | Drum00: 20 | sound_ret 21 | -------------------------------------------------------------------------------- /src/edit-context-menu.h: -------------------------------------------------------------------------------- 1 | #ifndef EDIT_CONTEXT_MENU_H 2 | #define EDIT_CONTEXT_MENU_H 3 | 4 | #include "widgets.h" 5 | 6 | class Edit_Context_Menu : public Context_Menu { 7 | public: 8 | using Context_Menu::Context_Menu; 9 | 10 | bool prepare(int X, int Y) override; 11 | }; 12 | 13 | #endif 14 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # directories 2 | bin/ 3 | lib/ 4 | share/ 5 | tmp/ 6 | 7 | # no lib includes 8 | include/FL/ 9 | include/libopenmpt/ 10 | include/portaudiocpp/ 11 | 12 | # no IDE files 13 | .vs/ 14 | *.sdf 15 | *.opensdf 16 | *.suo 17 | *.aps 18 | *.vcxproj.user 19 | 20 | # no static analysis files 21 | *.cppcheck 22 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Launch", 6 | "type": "lldb", 7 | "request": "launch", 8 | "program": "${workspaceFolder}/bin/crystaltrackerd", 9 | "args": [], 10 | "cwd": "${workspaceFolder}", 11 | "preLaunchTask": "Build Debug" 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /src/cocoa.h: -------------------------------------------------------------------------------- 1 | #ifndef COCOA_H 2 | #define COCOA_H 3 | 4 | #pragma warning(push, 0) 5 | #include 6 | #pragma warning(pop) 7 | 8 | enum cocoa_appearance { 9 | COCOA_APPEARANCE_AQUA, 10 | COCOA_APPEARANCE_DARK_AQUA 11 | }; 12 | 13 | void cocoa_set_appearance(const Fl_Window *w, enum cocoa_appearance appearance_id); 14 | bool cocoa_is_dark_mode(); 15 | 16 | #endif 17 | -------------------------------------------------------------------------------- /res/blank.xpm: -------------------------------------------------------------------------------- 1 | /* XPM */ 2 | static const char *BLANK_XPM[] = { 3 | "16 16 1 1", 4 | " c None", 5 | " ", 6 | " ", 7 | " ", 8 | " ", 9 | " ", 10 | " ", 11 | " ", 12 | " ", 13 | " ", 14 | " ", 15 | " ", 16 | " ", 17 | " ", 18 | " ", 19 | " ", 20 | " " 21 | }; 22 | -------------------------------------------------------------------------------- /src/resource.h: -------------------------------------------------------------------------------- 1 | //{{NO_DEPENDENCIES}} 2 | // Microsoft Visual C++ generated include file. 3 | // Used by crystal-tracker.rc 4 | // 5 | #define IDI_ICON1 101 6 | 7 | // Next default values for new objects 8 | // 9 | #ifdef APSTUDIO_INVOKED 10 | #ifndef APSTUDIO_READONLY_SYMBOLS 11 | #define _APS_NEXT_RESOURCE_VALUE 101 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 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "Build Debug", 6 | "type": "shell", 7 | "command": "make", 8 | "args": [ 9 | "debug" 10 | ], 11 | "options": { 12 | "cwd": "${workspaceFolder}" 13 | }, 14 | "problemMatcher": [ 15 | "$gcc" 16 | ], 17 | "group": { 18 | "kind": "build", 19 | "isDefault": true 20 | }, 21 | } 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /res/new.xpm: -------------------------------------------------------------------------------- 1 | /* XPM */ 2 | static const char *NEW_XPM[] = { 3 | "18 18 3 1", 4 | " c None", 5 | ". c #000000", 6 | "+ c #FFFFFF", 7 | " ", 8 | " ......... ", 9 | " .+++++++.. ", 10 | " .+++++++.+. ", 11 | " .+++++++.... ", 12 | " .++++++++++. ", 13 | " .++++++++++. ", 14 | " .++++++++++. ", 15 | " .++++++++++. ", 16 | " .++++++++++. ", 17 | " .++++++++++. ", 18 | " .++++++++++. ", 19 | " .++++++++++. ", 20 | " .++++++++++. ", 21 | " .++++++++++. ", 22 | " .++++++++++. ", 23 | " ............ ", 24 | " " 25 | }; 26 | -------------------------------------------------------------------------------- /res/one.xpm: -------------------------------------------------------------------------------- 1 | /* XPM */ 2 | static const char *ONE_XPM[] = { 3 | "18 18 3 1", 4 | " c None", 5 | ". c #000000", 6 | "+ c #FFFFFF", 7 | " ", 8 | " ", 9 | " ", 10 | " .... ", 11 | " ..++. ", 12 | " ..+++. ", 13 | " .++++. ", 14 | " ...++. ", 15 | " .++. ", 16 | " .++. ", 17 | " .++. ", 18 | " ...++... ", 19 | " .++++++. ", 20 | " .++++++. ", 21 | " ........ ", 22 | " ", 23 | " ", 24 | " " 25 | }; 26 | -------------------------------------------------------------------------------- /res/two.xpm: -------------------------------------------------------------------------------- 1 | /* XPM */ 2 | static const char *TWO_XPM[] = { 3 | "18 18 3 1", 4 | " c None", 5 | ". c #000000", 6 | "+ c #FFFFFF", 7 | " ", 8 | " ", 9 | " ", 10 | " ...... ", 11 | " ..++++.. ", 12 | " .++++++. ", 13 | " .++..++. ", 14 | " .++..++. ", 15 | " ....+++. ", 16 | " ..+++.. ", 17 | " ..+++.. ", 18 | " .+++.... ", 19 | " .++++++. ", 20 | " .++++++. ", 21 | " ........ ", 22 | " ", 23 | " ", 24 | " " 25 | }; 26 | -------------------------------------------------------------------------------- /res/up.xpm: -------------------------------------------------------------------------------- 1 | /* XPM */ 2 | static const char *UP_XPM[] = { 3 | "18 18 3 1", 4 | " c None", 5 | ". c #000000", 6 | "+ c #33DDFF", 7 | " ", 8 | " ", 9 | " ", 10 | " ", 11 | " .. ", 12 | " .++. ", 13 | " .++++. ", 14 | " .++++++. ", 15 | " .++++++++. ", 16 | " ....++.... ", 17 | " .++. ", 18 | " .++. ", 19 | " .++. ", 20 | " .... ", 21 | " ", 22 | " ", 23 | " ", 24 | " " 25 | }; 26 | -------------------------------------------------------------------------------- /res/down.xpm: -------------------------------------------------------------------------------- 1 | /* XPM */ 2 | static const char *DOWN_XPM[] = { 3 | "18 18 3 1", 4 | " c None", 5 | ". c #000000", 6 | "+ c #33DDFF", 7 | " ", 8 | " ", 9 | " ", 10 | " ", 11 | " .... ", 12 | " .++. ", 13 | " .++. ", 14 | " .++. ", 15 | " ....++.... ", 16 | " .++++++++. ", 17 | " .++++++. ", 18 | " .++++. ", 19 | " .++. ", 20 | " .. ", 21 | " ", 22 | " ", 23 | " ", 24 | " " 25 | }; 26 | -------------------------------------------------------------------------------- /res/four.xpm: -------------------------------------------------------------------------------- 1 | /* XPM */ 2 | static const char *FOUR_XPM[] = { 3 | "18 18 3 1", 4 | " c None", 5 | ". c #000000", 6 | "+ c #FFFFFF", 7 | " ", 8 | " ", 9 | " ", 10 | " ....... ", 11 | " .++.++. ", 12 | " .++.++. ", 13 | " .++.++. ", 14 | " .++.++.. ", 15 | " .++++++. ", 16 | " .++++++. ", 17 | " ....++.. ", 18 | " .++. ", 19 | " .++. ", 20 | " .++. ", 21 | " .... ", 22 | " ", 23 | " ", 24 | " " 25 | }; 26 | -------------------------------------------------------------------------------- /res/left.xpm: -------------------------------------------------------------------------------- 1 | /* XPM */ 2 | static const char *LEFT_XPM[] = { 3 | "18 18 3 1", 4 | " c None", 5 | ". c #000000", 6 | "+ c #33DDFF", 7 | " ", 8 | " ", 9 | " ", 10 | " ", 11 | " .. ", 12 | " .+. ", 13 | " .++. ", 14 | " .+++..... ", 15 | " .++++++++. ", 16 | " .++++++++. ", 17 | " .+++..... ", 18 | " .++. ", 19 | " .+. ", 20 | " .. ", 21 | " ", 22 | " ", 23 | " ", 24 | " " 25 | }; 26 | -------------------------------------------------------------------------------- /res/loop.xpm: -------------------------------------------------------------------------------- 1 | /* XPM */ 2 | static const char *LOOP_XPM[] = { 3 | "18 18 3 1", 4 | " c None", 5 | ". c #000000", 6 | "+ c #FFFF00", 7 | " ", 8 | " ", 9 | " ............ ", 10 | " .++++++++++++. ", 11 | " .++++++++++++++. ", 12 | " .+++........+++. ", 13 | " .++. .++. ", 14 | " .++. .++. ", 15 | " .++. .. .++. ", 16 | " .++. .+. .++. ", 17 | " .+++...++...+++. ", 18 | " .+++++++++.++++. ", 19 | " .++++++++.+++. ", 20 | " .....++..... ", 21 | " .+. ", 22 | " .. ", 23 | " ", 24 | " " 25 | }; 26 | -------------------------------------------------------------------------------- /res/minus.xpm: -------------------------------------------------------------------------------- 1 | /* XPM */ 2 | static const char *MINUS_XPM[] = { 3 | "18 18 3 1", 4 | " c None", 5 | ". c #000000", 6 | "+ c #33DDFF", 7 | " ", 8 | " ", 9 | " ", 10 | " ", 11 | " ", 12 | " ", 13 | " ", 14 | " ............ ", 15 | " .++++++++++. ", 16 | " .++++++++++. ", 17 | " ............ ", 18 | " ", 19 | " ", 20 | " ", 21 | " ", 22 | " ", 23 | " ", 24 | " " 25 | }; 26 | -------------------------------------------------------------------------------- /res/pause.xpm: -------------------------------------------------------------------------------- 1 | /* XPM */ 2 | static const char *PAUSE_XPM[] = { 3 | "18 18 3 1", 4 | " c None", 5 | ". c #000000", 6 | "+ c #0064C8", 7 | " ", 8 | " ", 9 | " ..... ..... ", 10 | " .+++. .+++. ", 11 | " .+++. .+++. ", 12 | " .+++. .+++. ", 13 | " .+++. .+++. ", 14 | " .+++. .+++. ", 15 | " .+++. .+++. ", 16 | " .+++. .+++. ", 17 | " .+++. .+++. ", 18 | " .+++. .+++. ", 19 | " .+++. .+++. ", 20 | " .+++. .+++. ", 21 | " .+++. .+++. ", 22 | " ..... ..... ", 23 | " ", 24 | " " 25 | }; 26 | -------------------------------------------------------------------------------- /res/play.xpm: -------------------------------------------------------------------------------- 1 | /* XPM */ 2 | static const char *PLAY_XPM[] = { 3 | "18 18 3 1", 4 | " c None", 5 | ". c #000000", 6 | "+ c #00C800", 7 | " ", 8 | " ", 9 | " ... ", 10 | " .++. ", 11 | " .+++. ", 12 | " .++++. ", 13 | " .+++++. ", 14 | " .++++++. ", 15 | " .+++++++. ", 16 | " .+++++++. ", 17 | " .++++++. ", 18 | " .+++++. ", 19 | " .++++. ", 20 | " .+++. ", 21 | " .++. ", 22 | " ... ", 23 | " ", 24 | " " 25 | }; 26 | -------------------------------------------------------------------------------- /res/plus.xpm: -------------------------------------------------------------------------------- 1 | /* XPM */ 2 | static const char *PLUS_XPM[] = { 3 | "18 18 3 1", 4 | " c None", 5 | ". c #000000", 6 | "+ c #33DDFF", 7 | " ", 8 | " ", 9 | " ", 10 | " .... ", 11 | " .++. ", 12 | " .++. ", 13 | " .++. ", 14 | " .....++..... ", 15 | " .++++++++++. ", 16 | " .++++++++++. ", 17 | " .....++..... ", 18 | " .++. ", 19 | " .++. ", 20 | " .++. ", 21 | " .... ", 22 | " ", 23 | " ", 24 | " " 25 | }; 26 | -------------------------------------------------------------------------------- /res/redo.xpm: -------------------------------------------------------------------------------- 1 | /* XPM */ 2 | static const char *REDO_XPM[] = { 3 | "18 18 3 1", 4 | " c None", 5 | ". c #000000", 6 | "+ c #0080FF", 7 | " ", 8 | " ", 9 | " .. ", 10 | " ..+. ", 11 | " .++. ", 12 | " .++. ", 13 | " .+. ", 14 | " .++. ....... ", 15 | " .++. .++++. ", 16 | " .++. .+++. ", 17 | " .+++. ..++++. ", 18 | " .++++..++++.+. ", 19 | " .+++++++++. .. ", 20 | " .+++++++. . ", 21 | " .++++.. ", 22 | " .... ", 23 | " ", 24 | " " 25 | }; 26 | -------------------------------------------------------------------------------- /res/right.xpm: -------------------------------------------------------------------------------- 1 | /* XPM */ 2 | static const char *RIGHT_XPM[] = { 3 | "18 18 3 1", 4 | " c None", 5 | ". c #000000", 6 | "+ c #33DDFF", 7 | " ", 8 | " ", 9 | " ", 10 | " ", 11 | " .. ", 12 | " .+. ", 13 | " .++. ", 14 | " .....+++. ", 15 | " .++++++++. ", 16 | " .++++++++. ", 17 | " .....+++. ", 18 | " .++. ", 19 | " .+. ", 20 | " .. ", 21 | " ", 22 | " ", 23 | " ", 24 | " " 25 | }; 26 | -------------------------------------------------------------------------------- /res/stop.xpm: -------------------------------------------------------------------------------- 1 | /* XPM */ 2 | static const char *STOP_XPM[] = { 3 | "18 18 3 1", 4 | " c None", 5 | ". c #000000", 6 | "+ c #C80000", 7 | " ", 8 | " ", 9 | " ", 10 | " ............ ", 11 | " .++++++++++. ", 12 | " .++++++++++. ", 13 | " .++++++++++. ", 14 | " .++++++++++. ", 15 | " .++++++++++. ", 16 | " .++++++++++. ", 17 | " .++++++++++. ", 18 | " .++++++++++. ", 19 | " .++++++++++. ", 20 | " .++++++++++. ", 21 | " ............ ", 22 | " ", 23 | " ", 24 | " " 25 | }; 26 | -------------------------------------------------------------------------------- /res/three.xpm: -------------------------------------------------------------------------------- 1 | /* XPM */ 2 | static const char *THREE_XPM[] = { 3 | "18 18 3 1", 4 | " c None", 5 | ". c #000000", 6 | "+ c #FFFFFF", 7 | " ", 8 | " ", 9 | " ", 10 | " ....... ", 11 | " .+++++.. ", 12 | " .++++++. ", 13 | " .....++. ", 14 | " ..+++. ", 15 | " .+++.. ", 16 | " .+++.. ", 17 | " ...++. ", 18 | " ....+++. ", 19 | " .++++++. ", 20 | " .+++++.. ", 21 | " ....... ", 22 | " ", 23 | " ", 24 | " " 25 | }; 26 | -------------------------------------------------------------------------------- /res/undo.xpm: -------------------------------------------------------------------------------- 1 | /* XPM */ 2 | static const char *UNDO_XPM[] = { 3 | "18 18 3 1", 4 | " c None", 5 | ". c #000000", 6 | "+ c #0080FF", 7 | " ", 8 | " ", 9 | " .... ", 10 | " ..++++. ", 11 | " . .+++++++. ", 12 | " .. .+++++++++. ", 13 | " .+.++++..++++. ", 14 | " .++++.. .+++. ", 15 | " .+++. .++. ", 16 | " .++++. .++. ", 17 | " ....... .++. ", 18 | " .+. ", 19 | " .++. ", 20 | " .++. ", 21 | " .+.. ", 22 | " .. ", 23 | " ", 24 | " " 25 | }; 26 | -------------------------------------------------------------------------------- /res/up-up.xpm: -------------------------------------------------------------------------------- 1 | /* XPM */ 2 | static const char *UP_UP_XPM[] = { 3 | "18 18 3 1", 4 | " c None", 5 | ". c #000000", 6 | "+ c #33DDFF", 7 | " ", 8 | " ", 9 | " ", 10 | " ", 11 | " ", 12 | " .. .. ", 13 | " .++. .++. ", 14 | " .++++. .++++. ", 15 | " .++++++..++++++. ", 16 | " ...++......++... ", 17 | " .++. .++. ", 18 | " .++. .++. ", 19 | " .... .... ", 20 | " ", 21 | " ", 22 | " ", 23 | " ", 24 | " " 25 | }; 26 | -------------------------------------------------------------------------------- /res/delete.xpm: -------------------------------------------------------------------------------- 1 | /* XPM */ 2 | static const char *DELETE_XPM[] = { 3 | "18 18 3 1", 4 | " c None", 5 | ". c #000000", 6 | "+ c #C80000", 7 | " ", 8 | " ", 9 | " ... ... ", 10 | " ..+.. ..+.. ", 11 | " ..+++....+++.. ", 12 | " .+++++..+++++. ", 13 | " ..++++++++++.. ", 14 | " ..++++++++.. ", 15 | " ..++++++.. ", 16 | " ..++++++.. ", 17 | " ..++++++++.. ", 18 | " ..++++++++++.. ", 19 | " .+++++..+++++. ", 20 | " ..+++....+++.. ", 21 | " ..+.. ..+.. ", 22 | " ... ... ", 23 | " ", 24 | " " 25 | }; 26 | -------------------------------------------------------------------------------- /res/down-down.xpm: -------------------------------------------------------------------------------- 1 | /* XPM */ 2 | static const char *DOWN_DOWN_XPM[] = { 3 | "18 18 3 1", 4 | " c None", 5 | ". c #000000", 6 | "+ c #33DDFF", 7 | " ", 8 | " ", 9 | " ", 10 | " ", 11 | " ", 12 | " .... .... ", 13 | " .++. .++. ", 14 | " .++. .++. ", 15 | " ...++......++... ", 16 | " .++++++..++++++. ", 17 | " .++++. .++++. ", 18 | " .++. .++. ", 19 | " .. .. ", 20 | " ", 21 | " ", 22 | " ", 23 | " ", 24 | " " 25 | }; 26 | -------------------------------------------------------------------------------- /res/scroll-dark.xpm: -------------------------------------------------------------------------------- 1 | /* XPM */ 2 | static const char *SCROLL_DARK_XPM[] = { 3 | "18 18 3 1", 4 | " c None", 5 | ". c #FFFF00", 6 | "+ c #000000", 7 | " ", 8 | " . ", 9 | " . ", 10 | " . + ", 11 | " . ++ ", 12 | " . +.+ ", 13 | " . +++++++++..+ ", 14 | " . .+.+........+ ", 15 | " . +.+..........+ ", 16 | " . .+.+.........+ ", 17 | " . +.+.........+ ", 18 | " . +++++++++..+ ", 19 | " . +.+ ", 20 | " . ++ ", 21 | " . + ", 22 | " . ", 23 | " . ", 24 | " " 25 | }; 26 | -------------------------------------------------------------------------------- /res/scroll-light.xpm: -------------------------------------------------------------------------------- 1 | /* XPM */ 2 | static const char *SCROLL_LIGHT_XPM[] = { 3 | "18 18 3 1", 4 | " c None", 5 | ". c #FF00FF", 6 | "+ c #000000", 7 | " ", 8 | " . ", 9 | " . ", 10 | " . + ", 11 | " . ++ ", 12 | " . +.+ ", 13 | " . +++++++++..+ ", 14 | " . .+.+........+ ", 15 | " . +.+..........+ ", 16 | " . .+.+.........+ ", 17 | " . +.+.........+ ", 18 | " . +++++++++..+ ", 19 | " . +.+ ", 20 | " . ++ ", 21 | " . + ", 22 | " . ", 23 | " . ", 24 | " " 25 | }; 26 | -------------------------------------------------------------------------------- /res/keys.xpm: -------------------------------------------------------------------------------- 1 | /* XPM */ 2 | static const char *KEYS_XPM[] = { 3 | "18 18 4 1", 4 | " c None", 5 | ". c #A0A0A0", 6 | "+ c #000000", 7 | "@ c #FFFFFF", 8 | " ", 9 | " ...............+ ", 10 | " @@@@@@@@@@@+++@+ ", 11 | " @@@@@@@@@@+@@@@+ ", 12 | " @@@@@@@@@@+@++@+ ", 13 | " @@@@@@@@@@+@@+@+ ", 14 | " ++++++++++@++@@+ ", 15 | " +@@@+@+@++@@@@@+ ", 16 | " +@++@@@@@+@@@@@+ ", 17 | " +@@++@+@++++++++ ", 18 | " +@++@@@@@+@@@@@+ ", 19 | " +@+++@+@++@+++@+ ", 20 | " ++++++++++@+@@@+ ", 21 | " @@@@@@@@@@@++@@+ ", 22 | " @@@@@@@@@@@+@@@+ ", 23 | " @@@@@@@@@@@+@@@+ ", 24 | " ...............+ ", 25 | " " 26 | }; 27 | -------------------------------------------------------------------------------- /res/open.xpm: -------------------------------------------------------------------------------- 1 | /* XPM */ 2 | static const char *OPEN_XPM[] = { 3 | "18 18 4 1", 4 | " c None", 5 | ". c #000000", 6 | "+ c #FFC800", 7 | "@ c #FFFFFF", 8 | " ", 9 | " ............ ", 10 | " .+..+++++++. ", 11 | " .+++........ ", 12 | " .++++.@@@@.. ", 13 | " .++++.@@@@.@. ", 14 | " .++++.@@@@.... ", 15 | " .++++.@@@@@@@. ", 16 | " .++++.@@@@@@@. ", 17 | " .++++.@@@@@@@. ", 18 | " .++++.@@@@@@@. ", 19 | " .++++.@@@@@@@. ", 20 | " .++++.@@@@@@@. ", 21 | " .++++.@@@@@@@. ", 22 | " ..+++.@@@@@@@. ", 23 | " ..+......... ", 24 | " .. ", 25 | " " 26 | }; 27 | -------------------------------------------------------------------------------- /src/ruler.h: -------------------------------------------------------------------------------- 1 | #ifndef RULER_H 2 | #define RULER_H 3 | 4 | #pragma warning(push, 0) 5 | #include 6 | #pragma warning(pop) 7 | 8 | #include "option-dialogs.h" 9 | 10 | class Ruler : public Fl_Box { 11 | private: 12 | Ruler_Config_Dialog::Ruler_Options _options; 13 | public: 14 | Ruler(int x, int y, int w, int h, const char *l = NULL); 15 | int handle(int event) override; 16 | void draw(void) override; 17 | 18 | Ruler_Config_Dialog::Ruler_Options get_options() const { return _options; } 19 | void set_options(const Ruler_Config_Dialog::Ruler_Options &o) { _options = o; } 20 | }; 21 | 22 | #endif 23 | -------------------------------------------------------------------------------- /res/ruler.xpm: -------------------------------------------------------------------------------- 1 | /* XPM */ 2 | static const char *RULER_XPM[] = { 3 | "18 18 5 1", 4 | " c None", 5 | ". c #000000", 6 | "+ c #A16C38", 7 | "@ c #FFFFFF", 8 | "# c #E0B380", 9 | " ", 10 | " ", 11 | " ", 12 | " ", 13 | " ", 14 | " ", 15 | " ", 16 | " .+.+.+.+.+.+.+.. ", 17 | " .@#@#@#@#@#@#@#. ", 18 | " .##############. ", 19 | " .##############. ", 20 | " ................ ", 21 | " ", 22 | " ", 23 | " ", 24 | " ", 25 | " ", 26 | " " 27 | }; 28 | -------------------------------------------------------------------------------- /res/save.xpm: -------------------------------------------------------------------------------- 1 | /* XPM */ 2 | static const char *SAVE_XPM[] = { 3 | "18 18 5 1", 4 | " c None", 5 | ". c #000000", 6 | "+ c #646464", 7 | "@ c #FFFFFF", 8 | "# c #C8C8C8", 9 | " ", 10 | " ", 11 | " .............. ", 12 | " .+@@@@@@@@@@+. ", 13 | " .+@@@@@@@@@@+. ", 14 | " .+@@@@@@@@@@+. ", 15 | " .+@@@@@@@@@@+. ", 16 | " .+@@@@@@@@@@+. ", 17 | " .++@@@@@@@@++. ", 18 | " .++++++++++++. ", 19 | " .+..........+. ", 20 | " .+.######.+.+. ", 21 | " .+.#..###.+.+. ", 22 | " .+.#..###.+.+. ", 23 | " ..#..###.+.+. ", 24 | " ............ ", 25 | " ", 26 | " " 27 | }; 28 | -------------------------------------------------------------------------------- /res/snip.xpm: -------------------------------------------------------------------------------- 1 | /* XPM */ 2 | static const char *SNIP_XPM[] = { 3 | "18 18 5 1", 4 | " c None", 5 | ". c #000000", 6 | "+ c #FFFFFF", 7 | "@ c #BBBBBB", 8 | "# c #FF6000", 9 | " ", 10 | " ... ", 11 | " .+@. ", 12 | " .+@. ", 13 | " .+@. ", 14 | " .+@. ", 15 | " .+@. .... ", 16 | " .......+@..####. ", 17 | " .++++++++###..#. ", 18 | " .@@@@@@+@###..#. ", 19 | " ......##..####. ", 20 | " .##. .... ", 21 | " .####. ", 22 | " .#..#. ", 23 | " .#..#. ", 24 | " .####. ", 25 | " .... ", 26 | " " 27 | }; 28 | -------------------------------------------------------------------------------- /res/decrease-spacing.xpm: -------------------------------------------------------------------------------- 1 | /* XPM */ 2 | static const char *DECREASE_SPACING_XPM[] = { 3 | "18 18 4 1", 4 | " c None", 5 | ". c #404040", 6 | "+ c #000000", 7 | "@ c #38C5FF", 8 | " ", 9 | " . . ", 10 | " . . ", 11 | " . . ", 12 | " . ++ ++ . ", 13 | " . +@+ +@+ . ", 14 | " . +@@+ +@@+ . ", 15 | " +++@@@+ +@@@+++ ", 16 | " @@@@@@@++@@@@@@@ ", 17 | " @@@@@@@++@@@@@@@ ", 18 | " +++@@@+ +@@@+++ ", 19 | " . +@@+ +@@+ . ", 20 | " . +@+ +@+ . ", 21 | " . ++ ++ . ", 22 | " . . ", 23 | " . . ", 24 | " . . ", 25 | " " 26 | }; 27 | -------------------------------------------------------------------------------- /res/increase-spacing.xpm: -------------------------------------------------------------------------------- 1 | /* XPM */ 2 | static const char *INCREASE_SPACING_XPM[] = { 3 | "18 18 4 1", 4 | " c None", 5 | ". c #404040", 6 | "+ c #000000", 7 | "@ c #38C5FF", 8 | " ", 9 | " . . ", 10 | " . . ", 11 | " . . ", 12 | " . ++ ++ . ", 13 | " . +@+ +@+ . ", 14 | " . +@@+ +@@+ . ", 15 | " .+@@@++++++@@@+. ", 16 | " +@@@@@@@@@@@@@@+ ", 17 | " +@@@@@@@@@@@@@@+ ", 18 | " .+@@@++++++@@@+. ", 19 | " . +@@+ +@@+ . ", 20 | " . +@+ +@+ . ", 21 | " . ++ ++ . ", 22 | " . . ", 23 | " . . ", 24 | " . . ", 25 | " " 26 | }; 27 | -------------------------------------------------------------------------------- /res/save-as.xpm: -------------------------------------------------------------------------------- 1 | /* XPM */ 2 | static const char *SAVE_AS_XPM[] = { 3 | "18 18 5 1", 4 | " c None", 5 | ". c #000000", 6 | "+ c #646464", 7 | "@ c #FFFFFF", 8 | "# c #C8C8C8", 9 | " ", 10 | " ", 11 | " ............ ", 12 | " .+@@@@@@@@+. ", 13 | " .+............ ", 14 | " .+.+@@@@@@@@+. ", 15 | " .+.+@@@@@@@@+. ", 16 | " .+.+@@@@@@@@+. ", 17 | " .+.+@@@@@@@@+. ", 18 | " .+.++@@@@@@++. ", 19 | " .+.++++++++++. ", 20 | " .+.+........+. ", 21 | " ..+.####.+.+. ", 22 | " .+.#..#.+.+. ", 23 | " ..#..#.+.+. ", 24 | " .......... ", 25 | " ", 26 | " " 27 | }; 28 | -------------------------------------------------------------------------------- /src/version.h: -------------------------------------------------------------------------------- 1 | #ifndef VERSION_H 2 | #define VERSION_H 3 | 4 | #define PROGRAM_NAME "Crystal Tracker" 5 | 6 | #define PROGRAM_AUTHOR "dannye" 7 | 8 | #define CURRENT_YEAR "2025" 9 | 10 | #define PROGRAM_VERSION 0,8,9 11 | #ifdef _DEBUG 12 | #define PROGRAM_VERSION_STRING "0.8.9 [DEBUG]" 13 | #else 14 | #define PROGRAM_VERSION_STRING "0.8.9" 15 | #endif 16 | 17 | #define PROGRAM_EXE_NAME "crystaltracker" 18 | 19 | #ifdef _WIN32 20 | #define PROGRAM_EXE PROGRAM_EXE_NAME ".exe" 21 | #elif defined(__APPLE__) 22 | #define PROGRAM_EXE PROGRAM_NAME ".app" 23 | #else 24 | #define PROGRAM_EXE PROGRAM_EXE_NAME 25 | #endif 26 | 27 | #endif 28 | -------------------------------------------------------------------------------- /res/split-dark.xpm: -------------------------------------------------------------------------------- 1 | /* XPM */ 2 | static const char *SPLIT_DARK_XPM[] = { 3 | "18 18 5 1", 4 | " c None", 5 | ". c #7F7F00", 6 | "+ c #FFFF00", 7 | "@ c #000000", 8 | "# c #D90000", 9 | " ", 10 | " .++++. ", 11 | " .++. ", 12 | " .. ", 13 | " ", 14 | " @@@@@@ @@@@@@@@ ", 15 | " @#####@ @#####@ ", 16 | " @######@ @####@ ", 17 | " @#####@ @#####@ ", 18 | " @####@ @######@ ", 19 | " @#####@ @#####@ ", 20 | " @######@ @####@ ", 21 | " @@@@@@@ @@@@@ ", 22 | " ", 23 | " .. ", 24 | " .++. ", 25 | " .++++. ", 26 | " " 27 | }; 28 | -------------------------------------------------------------------------------- /res/split-light.xpm: -------------------------------------------------------------------------------- 1 | /* XPM */ 2 | static const char *SPLIT_LIGHT_XPM[] = { 3 | "18 18 5 1", 4 | " c None", 5 | ". c #7F007F", 6 | "+ c #FF00FF", 7 | "@ c #000000", 8 | "# c #D90000", 9 | " ", 10 | " .++++. ", 11 | " .++. ", 12 | " .. ", 13 | " ", 14 | " @@@@@@ @@@@@@@@ ", 15 | " @#####@ @#####@ ", 16 | " @######@ @####@ ", 17 | " @#####@ @#####@ ", 18 | " @####@ @######@ ", 19 | " @#####@ @#####@ ", 20 | " @######@ @####@ ", 21 | " @@@@@@@ @@@@@ ", 22 | " ", 23 | " .. ", 24 | " .++. ", 25 | " .++++. ", 26 | " " 27 | }; 28 | -------------------------------------------------------------------------------- /res/notes.xpm: -------------------------------------------------------------------------------- 1 | /* XPM */ 2 | static const char *NOTES_XPM[] = { 3 | "18 18 6 1", 4 | " c None", 5 | ". c #000000", 6 | "+ c #0075FD", 7 | "@ c #00A500", 8 | "# c #F8F7F6", 9 | "$ c #D90000", 10 | " ", 11 | " ............... ", 12 | " .++++++.@@@@@@. ", 13 | " .+##+++.@##@@@. ", 14 | " .+#+#++.@#@#@@. ", 15 | " .+#+#++.@#@#@@. ", 16 | " .+#+#++.@#@#@@. ", 17 | " .+##+++.@##@@@. ", 18 | " .++............. ", 19 | " ....$$$$$$$$$$$. ", 20 | " .$$##$$#$#$$. ", 21 | " .$#$$$#####$. ", 22 | " .$#$$$$#$#$$. ", 23 | " .$#$$$#####$. ", 24 | " .$$##$$#$#$$. ", 25 | " .$$$$$$$$$$$. ", 26 | " ............. ", 27 | " " 28 | }; 29 | -------------------------------------------------------------------------------- /res/zoom-in.xpm: -------------------------------------------------------------------------------- 1 | /* XPM */ 2 | static const char *ZOOM_IN_XPM[] = { 3 | "18 18 6 1", 4 | " c None", 5 | ". c #000000", 6 | "+ c #CBDDED", 7 | "@ c #FFFFFF", 8 | "# c #0000FF", 9 | "$ c #B8B8B8", 10 | " ", 11 | " ..... ", 12 | " .++@@+. ", 13 | " .++@@@@@. ", 14 | " .+++@#@@@+. ", 15 | " .++++#++++. ", 16 | " .++#####++. ", 17 | " .++@@#@@@+. ", 18 | " .++@@#@@@+. ", 19 | " ..+@@@@@@. ", 20 | " .$$.+++++. ", 21 | " .$$$...... ", 22 | " .$$$. ", 23 | " .$$$. ", 24 | " .$$. ", 25 | " .. ", 26 | " ", 27 | " " 28 | }; 29 | -------------------------------------------------------------------------------- /res/zoom-out.xpm: -------------------------------------------------------------------------------- 1 | /* XPM */ 2 | static const char *ZOOM_OUT_XPM[] = { 3 | "18 18 6 1", 4 | " c None", 5 | ". c #000000", 6 | "+ c #CBDDED", 7 | "@ c #FFFFFF", 8 | "# c #0000FF", 9 | "$ c #B8B8B8", 10 | " ", 11 | " ..... ", 12 | " .++@@+. ", 13 | " .++@@@@@. ", 14 | " .+++@@@@@+. ", 15 | " .+++++++++. ", 16 | " .++#####++. ", 17 | " .++@@@@@@+. ", 18 | " .++@@@@@@+. ", 19 | " ..+@@@@@@. ", 20 | " .$$.+++++. ", 21 | " .$$$...... ", 22 | " .$$$. ", 23 | " .$$$. ", 24 | " .$$. ", 25 | " .. ", 26 | " ", 27 | " " 28 | }; 29 | -------------------------------------------------------------------------------- /src/preferences.h: -------------------------------------------------------------------------------- 1 | #ifndef PREFERENCES_H 2 | #define PREFERENCES_H 3 | 4 | #include 5 | 6 | #pragma warning(push, 0) 7 | #include 8 | #pragma warning(pop) 9 | 10 | #define PREFS_EXT ".prefs" 11 | 12 | class Preferences { 13 | private: 14 | static Fl_Preferences *_preferences; 15 | public: 16 | static void initialize(const char *argv0); 17 | static void close(void); 18 | static int get(const char *key, int default_ = 0); 19 | static void set(const char *key, int value); 20 | static std::string get_string(const char *key); 21 | static void set_string(const char *key, const std::string &value); 22 | }; 23 | 24 | #endif 25 | -------------------------------------------------------------------------------- /res/glue-dark.xpm: -------------------------------------------------------------------------------- 1 | /* XPM */ 2 | static const char *GLUE_DARK_XPM[] = { 3 | "18 18 6 1", 4 | " c None", 5 | ". c #7F7F00", 6 | "+ c #FFFF00", 7 | "@ c #000000", 8 | "# c #00A500", 9 | "$ c #00FFFF", 10 | " ", 11 | " .++++. ", 12 | " .++. ", 13 | " .. ", 14 | " ", 15 | " @@@@@@@@@@@@@@@@ ", 16 | " @######$#######@ ", 17 | " @#######$######@ ", 18 | " @######$#######@ ", 19 | " @#######$######@ ", 20 | " @######$#######@ ", 21 | " @#######$######@ ", 22 | " @@@@@@@@@@@@@@@@ ", 23 | " ", 24 | " .. ", 25 | " .++. ", 26 | " .++++. ", 27 | " " 28 | }; 29 | -------------------------------------------------------------------------------- /res/glue-light.xpm: -------------------------------------------------------------------------------- 1 | /* XPM */ 2 | static const char *GLUE_LIGHT_XPM[] = { 3 | "18 18 6 1", 4 | " c None", 5 | ". c #7F007F", 6 | "+ c #FF00FF", 7 | "@ c #000000", 8 | "# c #00A500", 9 | "$ c #00FFFF", 10 | " ", 11 | " .++++. ", 12 | " .++. ", 13 | " .. ", 14 | " ", 15 | " @@@@@@@@@@@@@@@@ ", 16 | " @######$#######@ ", 17 | " @#######$######@ ", 18 | " @######$#######@ ", 19 | " @#######$######@ ", 20 | " @######$#######@ ", 21 | " @#######$######@ ", 22 | " @@@@@@@@@@@@@@@@ ", 23 | " ", 24 | " .. ", 25 | " .++. ", 26 | " .++++. ", 27 | " " 28 | }; 29 | -------------------------------------------------------------------------------- /res/verify.xpm: -------------------------------------------------------------------------------- 1 | /* XPM */ 2 | static const char *VERIFY_XPM[] = { 3 | "18 18 7 1", 4 | " c None", 5 | ". c #808080", 6 | "+ c #000000", 7 | "@ c #FFFFFF", 8 | "# c #00D000", 9 | "$ c #FFFF00", 10 | "% c #00B000", 11 | " ", 12 | " ........... ", 13 | " +++.@@@@@@@##. ", 14 | " +$$$.@@@@@@##%. ", 15 | " +$$$$.@#@@@##%%. ", 16 | " +$$$+.@##@##%%@. ", 17 | " +$$+ .@%###%%@@. ", 18 | " +$$+ .@%%#%%@@@. ", 19 | " +$$+ .@@%%%@@@@. ", 20 | " +$$+ .@@@%@@@@@. ", 21 | " +$$$+.@@@@@@@@@. ", 22 | " +$$$$........... ", 23 | " +$$$$$$$$+$$$+ ", 24 | " +++++$$+++++ ", 25 | " +$+ ", 26 | " ++ ", 27 | " ", 28 | " " 29 | }; 30 | -------------------------------------------------------------------------------- /res/pencil.xpm: -------------------------------------------------------------------------------- 1 | /* XPM */ 2 | static const char *PENCIL_XPM[] = { 3 | "18 18 13 1", 4 | " c None", 5 | ". c #000000", 6 | "+ c #EC8CEA", 7 | "@ c #D67AD6", 8 | "# c #BAC2CA", 9 | "$ c #5C5C5C", 10 | "% c #AC52AC", 11 | "& c #F0BE48", 12 | "* c #A6AEB4", 13 | "= c #D4A430", 14 | "- c #767676", 15 | "; c #BC782A", 16 | "> c #FFFFFF", 17 | " ", 18 | " ", 19 | " .. ", 20 | " .+@. ", 21 | " .+++@. ", 22 | " .#$+@%. ", 23 | " .&$*$%. ", 24 | " .&==$-. ", 25 | " .&===;. ", 26 | " .&===;. ", 27 | " .&===;. ", 28 | " .&===;. ", 29 | " .>===;. ", 30 | " .>>=;. ", 31 | " .$>>. ", 32 | " .... ", 33 | " ", 34 | " " 35 | }; 36 | -------------------------------------------------------------------------------- /res/pencil-red.xpm: -------------------------------------------------------------------------------- 1 | /* XPM */ 2 | static const char *PENCIL_RED_XPM[] = { 3 | "18 18 13 1", 4 | " c None", 5 | ". c #000000", 6 | "+ c #EC8CEA", 7 | "@ c #D67AD6", 8 | "# c #BAC2CA", 9 | "$ c #5C5C5C", 10 | "% c #AC52AC", 11 | "& c #E55454", 12 | "* c #A6AEB4", 13 | "= c #D90000", 14 | "- c #767676", 15 | "; c #910000", 16 | "> c #FFFFFF", 17 | " ", 18 | " ", 19 | " .. ", 20 | " .+@. ", 21 | " .+++@. ", 22 | " .#$+@%. ", 23 | " .&$*$%. ", 24 | " .&==$-. ", 25 | " .&===;. ", 26 | " .&===;. ", 27 | " .&===;. ", 28 | " .&===;. ", 29 | " .>===;. ", 30 | " .>>=;. ", 31 | " .=>>. ", 32 | " .... ", 33 | " ", 34 | " " 35 | }; 36 | -------------------------------------------------------------------------------- /res/pencil-blue.xpm: -------------------------------------------------------------------------------- 1 | /* XPM */ 2 | static const char *PENCIL_BLUE_XPM[] = { 3 | "18 18 13 1", 4 | " c None", 5 | ". c #000000", 6 | "+ c #EC8CEA", 7 | "@ c #D67AD6", 8 | "# c #BAC2CA", 9 | "$ c #5C5C5C", 10 | "% c #AC52AC", 11 | "& c #54A2FD", 12 | "* c #A6AEB4", 13 | "= c #0075FD", 14 | "- c #767676", 15 | "; c #004EA9", 16 | "> c #FFFFFF", 17 | " ", 18 | " ", 19 | " .. ", 20 | " .+@. ", 21 | " .+++@. ", 22 | " .#$+@%. ", 23 | " .&$*$%. ", 24 | " .&==$-. ", 25 | " .&===;. ", 26 | " .&===;. ", 27 | " .&===;. ", 28 | " .&===;. ", 29 | " .>===;. ", 30 | " .>>=;. ", 31 | " .=>>. ", 32 | " .... ", 33 | " ", 34 | " " 35 | }; 36 | -------------------------------------------------------------------------------- /res/pencil-brown.xpm: -------------------------------------------------------------------------------- 1 | /* XPM */ 2 | static const char *PENCIL_BROWN_XPM[] = { 3 | "18 18 13 1", 4 | " c None", 5 | ". c #000000", 6 | "+ c #EC8CEA", 7 | "@ c #D67AD6", 8 | "# c #BAC2CA", 9 | "$ c #5C5C5C", 10 | "% c #AC52AC", 11 | "& c #A77C64", 12 | "* c #A6AEB4", 13 | "= c #7C3C19", 14 | "- c #767676", 15 | "; c #532810", 16 | "> c #FFFFFF", 17 | " ", 18 | " ", 19 | " .. ", 20 | " .+@. ", 21 | " .+++@. ", 22 | " .#$+@%. ", 23 | " .&$*$%. ", 24 | " .&==$-. ", 25 | " .&===;. ", 26 | " .&===;. ", 27 | " .&===;. ", 28 | " .&===;. ", 29 | " .>===;. ", 30 | " .>>=;. ", 31 | " .=>>. ", 32 | " .... ", 33 | " ", 34 | " " 35 | }; 36 | -------------------------------------------------------------------------------- /res/pencil-green.xpm: -------------------------------------------------------------------------------- 1 | /* XPM */ 2 | static const char *PENCIL_GREEN_XPM[] = { 3 | "18 18 13 1", 4 | " c None", 5 | ". c #000000", 6 | "+ c #EC8CEA", 7 | "@ c #D67AD6", 8 | "# c #BAC2CA", 9 | "$ c #5C5C5C", 10 | "% c #AC52AC", 11 | "& c #54C254", 12 | "* c #A6AEB4", 13 | "= c #00A500", 14 | "- c #767676", 15 | "; c #006E00", 16 | "> c #FFFFFF", 17 | " ", 18 | " ", 19 | " .. ", 20 | " .+@. ", 21 | " .+++@. ", 22 | " .#$+@%. ", 23 | " .&$*$%. ", 24 | " .&==$-. ", 25 | " .&===;. ", 26 | " .&===;. ", 27 | " .&===;. ", 28 | " .&===;. ", 29 | " .>===;. ", 30 | " .>>=;. ", 31 | " .=>>. ", 32 | " .... ", 33 | " ", 34 | " " 35 | }; 36 | -------------------------------------------------------------------------------- /res/brush.xpm: -------------------------------------------------------------------------------- 1 | /* XPM */ 2 | static const char *BRUSH_XPM[] = { 3 | "18 18 14 1", 4 | " c None", 5 | ". c #000000", 6 | "+ c #F0BE48", 7 | "@ c #D4A430", 8 | "# c #BC782A", 9 | "$ c #A6AEB4", 10 | "% c #767676", 11 | "& c #44432C", 12 | "* c #BAC2CA", 13 | "= c #D1CF8A", 14 | "- c #E3E096", 15 | "; c #F0ED9F", 16 | "> c #828055", 17 | ", c #565538", 18 | " ", 19 | " .. ", 20 | " .+@. ", 21 | " .. .+@#. ", 22 | " .$%. .+@#. ", 23 | " &%*$%.+@#. ", 24 | " &=-%*$%@#. ", 25 | " &=-;-%*$%. ", 26 | " &=-;-=-%*$%. ", 27 | " &=-;-=-;-%*$%. ", 28 | " &=-=-;-=-%*$. ", 29 | " &>-;-=-;-%. ", 30 | " &=-=-;->& ", 31 | " &>-;->& ", 32 | " &=->& ", 33 | " &,& ", 34 | " ", 35 | " " 36 | }; 37 | -------------------------------------------------------------------------------- /src/directory-chooser.h: -------------------------------------------------------------------------------- 1 | #ifndef DIRECTORY_CHOOSER_H 2 | #define DIRECTORY_CHOOSER_H 3 | 4 | #include "utils.h" 5 | 6 | #ifdef _WIN32 7 | 8 | #include 9 | 10 | class Directory_Chooser { 11 | private: 12 | const char *_title; 13 | const char *_filename; 14 | IFileOpenDialog *_fod_ptr; 15 | public: 16 | Directory_Chooser(int type); 17 | ~Directory_Chooser(); 18 | inline const char *title(void) const { return _title; } 19 | inline void title(const char *t) { _title = t; } 20 | inline const char *filename(void) const { return _filename; } 21 | inline void directory(const char*) { /* TODO */ } 22 | int show(); 23 | }; 24 | 25 | #else 26 | 27 | #pragma warning(push, 0) 28 | #include 29 | #pragma warning(pop) 30 | 31 | typedef Fl_Native_File_Chooser Directory_Chooser; 32 | 33 | #endif 34 | 35 | #endif 36 | -------------------------------------------------------------------------------- /src/help-window.h: -------------------------------------------------------------------------------- 1 | #ifndef HELP_WINDOW_H 2 | #define HELP_WINDOW_H 3 | 4 | #include 5 | 6 | #pragma warning(push, 0) 7 | #include 8 | #include 9 | #pragma warning(pop) 10 | 11 | #include "widgets.h" 12 | 13 | class Help_Window { 14 | private: 15 | int _dx, _dy, _width, _height; 16 | const char *_title, *_content; 17 | Fl_Double_Window *_window; 18 | HTML_View *_body; 19 | Default_Button *_ok_button; 20 | Fl_Box *_spacer; 21 | public: 22 | Help_Window(int x, int y, int w, int h, const char *t = NULL); 23 | ~Help_Window(); 24 | inline void title(const char *t) { _title = t; } 25 | inline void content(const char *c) { _content = c; } 26 | private: 27 | void initialize(void); 28 | void refresh(void); 29 | public: 30 | void show(const Fl_Widget *p); 31 | void redraw(void); 32 | private: 33 | static void close_cb(Fl_Widget *w, Help_Window *hw); 34 | }; 35 | 36 | #endif 37 | -------------------------------------------------------------------------------- /res/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | en 7 | CFBundleExecutable 8 | crystaltracker 9 | CFBundleIconFile 10 | AppIcon 11 | CFBundleIdentifier 12 | dannye.crystaltracker 13 | CFBundleInfoDictionaryVersion 14 | 6.0 15 | CFBundleName 16 | Crystal Tracker 17 | CFBundlePackageType 18 | APPL 19 | CFBundleShortVersionString 20 | 0.0.0 21 | CFBundleVersion 22 | 0 23 | LSMinimumSystemVersion 24 | 10.13 25 | NSHumanReadableCopyright 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /.vscode/c_cpp_properties.json: -------------------------------------------------------------------------------- 1 | { 2 | "configurations": [ 3 | { 4 | "name": "Linux", 5 | "includePath": [ 6 | "${default}", 7 | "${workspaceFolder}/include", 8 | "${workspaceFolder}/res" 9 | ], 10 | "defines": [], 11 | "compilerPath": "/usr/bin/gcc", 12 | "cStandard": "gnu17", 13 | "cppStandard": "gnu++17", 14 | "intelliSenseMode": "linux-gcc-x64" 15 | }, 16 | { 17 | "name": "Mac", 18 | "includePath": [ 19 | "${default}", 20 | "${workspaceFolder}/include", 21 | "${workspaceFolder}/res" 22 | ], 23 | "defines": [], 24 | "compilerPath": "/usr/bin/clang++", 25 | "cStandard": "gnu17", 26 | "cppStandard": "gnu++17", 27 | "intelliSenseMode": "macos-clang-x64" 28 | } 29 | ], 30 | "version": 4 31 | } 32 | -------------------------------------------------------------------------------- /src/cocoa.mm: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | #pragma warning(push, 0) 4 | #include 5 | #pragma warning(pop) 6 | 7 | #include "cocoa.h" 8 | 9 | void cocoa_set_appearance(const Fl_Window *w, enum cocoa_appearance appearance_id) { 10 | NSAppearance *appearance; 11 | switch (appearance_id) { 12 | default: 13 | case COCOA_APPEARANCE_AQUA: 14 | appearance = [NSAppearance appearanceNamed:NSAppearanceNameAqua]; 15 | break; 16 | #ifdef MAC_OS_X_VERSION_10_14 17 | case COCOA_APPEARANCE_DARK_AQUA: 18 | if (@available(macOS 10.14, *)) { 19 | appearance = [NSAppearance appearanceNamed:NSAppearanceNameDarkAqua]; 20 | } else { 21 | appearance = [NSAppearance appearanceNamed:NSAppearanceNameAqua]; 22 | } 23 | break; 24 | #endif 25 | } 26 | [fl_xid(w) setAppearance: appearance]; 27 | } 28 | 29 | bool cocoa_is_dark_mode() { 30 | NSString *dark = @"Dark"; 31 | NSString *osxMode = [[NSUserDefaults standardUserDefaults] stringForKey:@"AppleInterfaceStyle"]; 32 | return [osxMode isEqualToString:dark]; 33 | } 34 | -------------------------------------------------------------------------------- /res/brush-cmy.xpm: -------------------------------------------------------------------------------- 1 | /* XPM */ 2 | static const char *BRUSH_CMY_XPM[] = { 3 | "18 18 27 1", 4 | " c None", 5 | ". c #000000", 6 | "+ c #F0BE48", 7 | "@ c #D4A430", 8 | "# c #BC782A", 9 | "$ c #A6AEB4", 10 | "% c #767676", 11 | "& c #44432C", 12 | "* c #BAC2CA", 13 | "= c #006060", 14 | "- c #D1CF8A", 15 | "; c #E3E096", 16 | "> c #00D0D0", 17 | ", c #00B0B0", 18 | "' c #F0ED9F", 19 | ") c #00E8E8", 20 | "! c #00FFFF", 21 | "~ c #D000D0", 22 | "{ c #B000B0", 23 | "] c #E800E8", 24 | "^ c #FF00FF", 25 | "/ c #B0B000", 26 | "( c #600060", 27 | "_ c #E8E800", 28 | ": c #FFFF00", 29 | "< c #D0D000", 30 | "[ c #606000", 31 | " ", 32 | " .. ", 33 | " .+@. ", 34 | " .. .+@#. ", 35 | " .$%. .+@#. ", 36 | " &%*$%.+@#. ", 37 | " =-;%*$%@#. ", 38 | " =>,';%*$%. ", 39 | " =>)!,-;%*$%. ", 40 | " =>)!)~{';%*$%. ", 41 | " =!)~]^{-;%*$. ", 42 | " =~]^]~/';%. ", 43 | " (^]~_:/-& ", 44 | " (~_:_<[ ", 45 | " [:_<[ ", 46 | " [<[ ", 47 | " [ ", 48 | " " 49 | }; 50 | -------------------------------------------------------------------------------- /src/parse-waves.h: -------------------------------------------------------------------------------- 1 | #ifndef PARSE_WAVES_H 2 | #define PARSE_WAVES_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | constexpr size_t NUM_WAVE_SAMPLES = 32; 9 | typedef std::array Wave; 10 | 11 | class Parsed_Waves { 12 | public: 13 | enum class Result { WAVES_OK, WAVES_BAD_FILE, WAVES_NULL }; 14 | private: 15 | std::string _waves_file; 16 | std::vector _waves; 17 | int32_t _num_parsed_waves = 0; 18 | Result _result = Result::WAVES_NULL; 19 | public: 20 | Parsed_Waves(const char *d); 21 | inline ~Parsed_Waves() {} 22 | inline std::string waves_file(void) const { return _waves_file; } 23 | inline std::vector &&waves(void) { return std::move(_waves); } 24 | inline int32_t num_parsed_waves(void) const { return _num_parsed_waves; } 25 | inline Result result(void) const { return _result; } 26 | 27 | static Result parse_wave(std::istringstream &lss, Wave &wave, bool nybbles); 28 | private: 29 | Result parse_waves(const char *d); 30 | Result try_parse_waves(const char *f); 31 | }; 32 | 33 | #endif 34 | -------------------------------------------------------------------------------- /src/config.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #pragma warning(push, 0) 5 | #include 6 | #pragma warning(pop) 7 | 8 | #include "utils.h" 9 | #include "config.h" 10 | 11 | bool Config::project_path_from_asm_path(const char *asm_path, char *project_path) { 12 | char scratch_path[FL_PATH_MAX] = {}; 13 | fl_filename_absolute(scratch_path, asm_path); 14 | char makefile[FL_PATH_MAX] = {}; 15 | for (;;) { 16 | char *pivot = strrchr(scratch_path, *DIR_SEP); 17 | if (!pivot) { return false; } 18 | *pivot = '\0'; 19 | // Make sure there's enough room for "/Makefile\0" (10 chars) 20 | if (pivot - scratch_path + 10 > FL_PATH_MAX) { return false; } 21 | strcpy(makefile, scratch_path); 22 | strcat(makefile, DIR_SEP "Makefile"); 23 | if (!file_exists(makefile)) { 24 | strcpy(makefile, scratch_path); 25 | strcat(makefile, DIR_SEP "makefile"); 26 | } 27 | if (file_exists(makefile)) { // the project directory contains a Makefile 28 | strcat(scratch_path, DIR_SEP); 29 | strcpy(project_path, scratch_path); 30 | return true; 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /example/wave_samples.asm: -------------------------------------------------------------------------------- 1 | WaveSamples: 2 | dn 0, 2, 4, 6, 8, 10, 12, 14, 15, 15, 15, 14, 14, 13, 13, 12, 12, 11, 10, 9, 8, 7, 6, 5, 4, 4, 3, 3, 2, 2, 1, 1 3 | dn 0, 2, 4, 6, 8, 10, 12, 14, 14, 15, 15, 15, 15, 14, 14, 14, 13, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 2, 1, 1 4 | dn 1, 3, 6, 9, 11, 13, 14, 14, 14, 14, 15, 15, 15, 15, 14, 13, 13, 14, 15, 15, 15, 15, 14, 14, 14, 14, 13, 11, 9, 6, 3, 1 5 | dn 0, 2, 4, 6, 8, 10, 12, 13, 14, 15, 15, 14, 13, 14, 15, 15, 14, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 6 | dn 0, 1, 2, 3, 4, 5, 6, 7, 8, 10, 12, 13, 14, 14, 15, 7, 7, 15, 14, 14, 13, 12, 10, 8, 7, 6, 5, 4, 3, 2, 1, 0 7 | dn 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 3, 3, 2, 2, 1, 1, 15, 15, 14, 14, 12, 12, 10, 10, 8, 8, 10, 10, 12, 12, 14, 14 8 | dn 0, 2, 4, 6, 8, 10, 12, 14, 12, 11, 10, 9, 8, 7, 6, 5, 15, 15, 15, 14, 14, 13, 13, 12, 4, 4, 3, 3, 2, 2, 1, 1 9 | dn 12, 0, 10, 9, 8, 7, 15, 5, 15, 15, 15, 14, 14, 13, 13, 12, 4, 4, 3, 3, 2, 2, 15, 1, 0, 2, 4, 6, 8, 10, 12, 14 10 | dn 4, 4, 3, 3, 2, 2, 1, 15, 0, 0, 4, 6, 8, 10, 12, 14, 15, 8, 15, 14, 14, 13, 13, 12, 12, 11, 10, 9, 8, 7, 6, 5 11 | dn 1, 1, 0, 0, 0, 0, 0, 8, 0, 0, 1, 3, 5, 7, 9, 10, 11, 4, 11, 10, 10, 9, 9, 8, 8, 7, 6, 5, 4, 3, 2, 1 12 | -------------------------------------------------------------------------------- /ide/crystal-tracker.sln: -------------------------------------------------------------------------------- 1 | 2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.2.32516.85 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "crystal-tracker", "crystal-tracker.vcxproj", "{077A0078-AFEE-450B-99E4-B84FE36FE368}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|x64 = Debug|x64 11 | Debug|x86 = Debug|x86 12 | Release|x64 = Release|x64 13 | Release|x86 = Release|x86 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {077A0078-AFEE-450B-99E4-B84FE36FE368}.Debug|x64.ActiveCfg = Debug|x64 17 | {077A0078-AFEE-450B-99E4-B84FE36FE368}.Debug|x64.Build.0 = Debug|x64 18 | {077A0078-AFEE-450B-99E4-B84FE36FE368}.Debug|x86.ActiveCfg = Debug|Win32 19 | {077A0078-AFEE-450B-99E4-B84FE36FE368}.Debug|x86.Build.0 = Debug|Win32 20 | {077A0078-AFEE-450B-99E4-B84FE36FE368}.Release|x64.ActiveCfg = Release|x64 21 | {077A0078-AFEE-450B-99E4-B84FE36FE368}.Release|x64.Build.0 = Release|x64 22 | {077A0078-AFEE-450B-99E4-B84FE36FE368}.Release|x86.ActiveCfg = Release|Win32 23 | {077A0078-AFEE-450B-99E4-B84FE36FE368}.Release|x86.Build.0 = Release|Win32 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {F76E9749-FB82-4B37-9397-C2A06DF3A458} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /src/modal-dialog.h: -------------------------------------------------------------------------------- 1 | #ifndef MODAL_DIALOG_H 2 | #define MODAL_DIALOG_H 3 | 4 | #include 5 | 6 | #pragma warning(push, 0) 7 | #include 8 | #pragma warning(pop) 9 | 10 | #include "widgets.h" 11 | 12 | class Modal_Dialog { 13 | public: 14 | enum class Icon { NO_ICON, SUCCESS_ICON, WARNING_ICON, ERROR_ICON, APP_ICON }; 15 | static Fl_Pixmap SUCCESS_SHIELD_ICON, WARNING_SHIELD_ICON, ERROR_SHIELD_ICON, PROGRAM_ICON; 16 | private: 17 | Icon _icon_type; 18 | std::string _title, _subject, _message; 19 | int _min_w, _max_w; 20 | bool _canceled; 21 | Fl_Window *_top_window; 22 | Fl_Double_Window *_dialog; 23 | Fl_Box *_icon; 24 | Label *_heading; 25 | Label *_body; 26 | Default_Button *_ok_button; 27 | OS_Button *_cancel_button; 28 | public: 29 | Modal_Dialog(Fl_Window *top, const char *t = NULL, Icon c = Icon::NO_ICON, bool cancel = false); 30 | ~Modal_Dialog(); 31 | private: 32 | void initialize(void); 33 | void refresh(void); 34 | public: 35 | inline void icon(Icon c) { _icon_type = c; } 36 | inline void title(const std::string &t) { _title = t; } 37 | inline void subject(const std::string &s) { _subject = s; } 38 | inline void message(const std::string &m) { _message = m; } 39 | inline void width_range(int min_w, int max_w) { _min_w = min_w; _max_w = max_w; } 40 | inline bool canceled(void) const { return _canceled; } 41 | inline void canceled(bool c) { _canceled = c; } 42 | void show(const Fl_Widget *p); 43 | private: 44 | static void close_cb(Fl_Widget *, Modal_Dialog *md); 45 | static void cancel_cb(Fl_Widget *, Modal_Dialog *md); 46 | }; 47 | 48 | #endif 49 | -------------------------------------------------------------------------------- /src/parse-drumkits.h: -------------------------------------------------------------------------------- 1 | #ifndef PARSE_DRUMKITS_H 2 | #define PARSE_DRUMKITS_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | struct Noise_Note { 9 | int32_t length; 10 | 11 | int32_t volume; 12 | int32_t envelope_direction; 13 | int32_t sweep_pace; 14 | 15 | int32_t clock_shift; 16 | int32_t lfsr_width; 17 | int32_t clock_divider; 18 | }; 19 | 20 | struct Drum { 21 | std::string label; 22 | std::vector noise_notes; 23 | }; 24 | 25 | constexpr size_t NUM_DRUMS_PER_DRUMKIT = 13; 26 | 27 | struct Drumkit { 28 | std::string label; 29 | std::array drums; 30 | }; 31 | 32 | class Parsed_Drumkits { 33 | public: 34 | enum class Result { DRUMKITS_OK, DRUMKITS_BAD_FILE, DRUMKITS_NULL }; 35 | private: 36 | std::string _drumkits_file; 37 | std::vector _drumkits; 38 | std::vector _drums; 39 | int32_t _num_parsed_drumkits = 0; 40 | int32_t _num_parsed_drums = 0; 41 | Result _result = Result::DRUMKITS_NULL; 42 | public: 43 | Parsed_Drumkits(const char *d); 44 | inline ~Parsed_Drumkits() {} 45 | inline std::string drumkits_file(void) const { return _drumkits_file; } 46 | inline std::vector &&drumkits(void) { return std::move(_drumkits); } 47 | inline std::vector &&drums(void) { return std::move(_drums); } 48 | inline int32_t num_parsed_drumkits(void) const { return _num_parsed_drumkits; } 49 | inline int32_t num_parsed_drums(void) const { return _num_parsed_drums; } 50 | inline Result result(void) const { return _result; } 51 | private: 52 | Result parse_drumkits(const char *d); 53 | Result try_parse_drumkits(const char *f); 54 | }; 55 | 56 | #endif 57 | -------------------------------------------------------------------------------- /src/directory-chooser.cpp: -------------------------------------------------------------------------------- 1 | #pragma warning(push, 0) 2 | #include 3 | #include 4 | #pragma warning(pop) 5 | 6 | #include "directory-chooser.h" 7 | 8 | #ifdef _WIN32 9 | 10 | Directory_Chooser::Directory_Chooser(int) : _title(NULL), _filename(NULL), _fod_ptr(NULL) {} 11 | 12 | Directory_Chooser::~Directory_Chooser() { 13 | if (_fod_ptr) { _fod_ptr->Release(); } 14 | delete [] _filename; 15 | } 16 | 17 | int Directory_Chooser::show() { 18 | HRESULT hr; 19 | 20 | if (!_fod_ptr) { 21 | hr = CoCreateInstance(CLSID_FileOpenDialog, NULL, CLSCTX_ALL, IID_IFileOpenDialog, reinterpret_cast(&_fod_ptr)); 22 | if (!SUCCEEDED(hr)) { return -1; } 23 | } 24 | delete [] _filename; 25 | _filename = NULL; 26 | 27 | wchar_t wtitle[FL_PATH_MAX] = {}; 28 | fl_utf8towc(_title, (unsigned int) strlen(_title), wtitle, sizeof(wtitle)); 29 | _fod_ptr->SetTitle(wtitle); 30 | 31 | FILEOPENDIALOGOPTIONS fod_opts; 32 | _fod_ptr->GetOptions(&fod_opts); 33 | fod_opts |= FOS_PICKFOLDERS; 34 | _fod_ptr->SetOptions(fod_opts); 35 | 36 | HWND hWnd = GetForegroundWindow(); 37 | hr = _fod_ptr->Show(hWnd); 38 | if (hr == HRESULT_FROM_WIN32(ERROR_CANCELLED)) { return 1; } 39 | if (!SUCCEEDED(hr)) { return -1; } 40 | 41 | IShellItem *pItem; 42 | hr = _fod_ptr->GetResult(&pItem); 43 | if (!SUCCEEDED(hr)) { return -1; } 44 | 45 | PWSTR pszFilePath; 46 | hr = pItem->GetDisplayName(SIGDN_FILESYSPATH, &pszFilePath); 47 | if (!SUCCEEDED(hr)) { pItem->Release(); return -1; } 48 | 49 | size_t len = wcslen(pszFilePath); 50 | char *filename = new char[len + 1]; 51 | fl_utf8fromwc(filename, (unsigned int)len + 1, pszFilePath, (unsigned int)len); 52 | _filename = filename; 53 | 54 | CoTaskMemFree(pszFilePath); 55 | pItem->Release(); 56 | return 0; 57 | } 58 | 59 | #endif 60 | -------------------------------------------------------------------------------- /src/help-window.cpp: -------------------------------------------------------------------------------- 1 | #pragma warning(push, 0) 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #pragma warning(pop) 9 | 10 | #include "themes.h" 11 | #include "widgets.h" 12 | #include "help-window.h" 13 | 14 | Help_Window::Help_Window(int x, int y, int w, int h, const char *t) : _dx(x), _dy(y), _width(w), _height(h), _title(t), 15 | _content(NULL), _window(NULL), _body(NULL), _ok_button(NULL), _spacer(NULL) {} 16 | 17 | Help_Window::~Help_Window() { 18 | delete _window; 19 | delete _body; 20 | delete _ok_button; 21 | delete _spacer; 22 | } 23 | 24 | void Help_Window::initialize() { 25 | if (_window) { return; } 26 | Fl_Group *prev_current = Fl_Group::current(); 27 | Fl_Group::current(NULL); 28 | // Populate window 29 | _window = new Fl_Double_Window(_dx, _dy, _width, _height, _title); 30 | _body = new HTML_View(10, 10, _width-20, _height-52); 31 | _ok_button = new Default_Button(_width-90, _height-32, 80, 22, "OK"); 32 | _spacer = new Fl_Box(10, 10, _width-110, _height-52); 33 | _window->end(); 34 | // Initialize window 35 | _window->box(OS_BG_BOX); 36 | _window->resizable(_spacer); 37 | _window->callback((Fl_Callback *)close_cb, this); 38 | // Initialize window's children 39 | _ok_button->tooltip("OK (Enter)"); 40 | _ok_button->callback((Fl_Callback *)close_cb, this); 41 | Fl_Group::current(prev_current); 42 | } 43 | 44 | void Help_Window::refresh() { 45 | _window->label(_title ? _title : "Help"); 46 | _body->value(_content ? _content : ""); 47 | } 48 | 49 | void Help_Window::show(const Fl_Widget *p) { 50 | initialize(); 51 | refresh(); 52 | Fl_Window *prev_grab = Fl::grab(); 53 | _window->position(p->x() + _dx, p->y() + _dy); 54 | Fl::grab(NULL); 55 | _window->show(); 56 | Fl::grab(prev_grab); 57 | } 58 | 59 | void Help_Window::redraw() { 60 | if (!_window) { return; } 61 | _body->textsize(_body->textsize()); // roundabout way of calling private function format() 62 | _window->redraw(); 63 | } 64 | 65 | void Help_Window::close_cb(Fl_Widget *, Help_Window *hw) { 66 | hw->_window->hide(); 67 | } 68 | -------------------------------------------------------------------------------- /src/hex-spinner.h: -------------------------------------------------------------------------------- 1 | #ifndef HEX_SPINNER_H 2 | #define HEX_SPINNER_H 3 | 4 | #pragma warning(push, 0) 5 | #include 6 | #include 7 | #include 8 | #pragma warning(pop) 9 | 10 | class Hex_Input : public Fl_Int_Input { 11 | public: 12 | Hex_Input(int x, int y, int w, int h, const char *l = NULL); 13 | int handle(int event); 14 | private: 15 | int handle_key(void); 16 | int handle_paste_text(void); 17 | }; 18 | 19 | class Hex_Spinner : public Fl_Group { 20 | // Based on Fl_Spinner 21 | private: 22 | int _value, _minimum, _maximum, _step; 23 | const char *_format; 24 | protected: 25 | Hex_Input _input; 26 | Fl_Repeat_Button _up_button, _down_button; 27 | public: 28 | Hex_Spinner(int x, int y, int w, int h, const char *l = NULL); 29 | inline int value(void) const { return _value; } 30 | inline void value(int v) { _value = v; update(); } 31 | inline int maximum(void) const { return _maximum; } 32 | inline void maximum(int m) { _maximum = m; } 33 | inline int minimum(void) const { return _minimum; } 34 | inline void minimum(int m) { _minimum = m; } 35 | inline void range(int a, int b) { _minimum = a; _maximum = b; } 36 | inline int step(void) const { return _step; } 37 | inline void step(int s) { _step = s; update(); } 38 | inline const char *format(void) const { return _format; } 39 | inline void format(const char *f) { _format = f; update(); } 40 | inline Fl_Color textcolor(void) const { return _input.textcolor(); } 41 | inline void textcolor(Fl_Color c) { _input.textcolor(c); } 42 | inline Fl_Font textfont(void) const { return _input.textfont(); } 43 | inline void textfont(Fl_Font f) { _input.textfont(f); } 44 | inline Fl_Fontsize textsize(void) const { return _input.textsize(); } 45 | inline void textsize(Fl_Fontsize s) { _input.textsize(s); } 46 | inline Fl_Color color(void) const { return _input.color(); } 47 | inline void color(Fl_Color c) { _input.color(c); } 48 | inline Fl_Color selection_color(void) const { return _input.selection_color(); } 49 | inline void selection_color(Fl_Color c) { _input.selection_color(c); } 50 | void resize(int x, int y, int w, int h); 51 | int handle(int event); 52 | private: 53 | void update(void); 54 | static void input_cb(Hex_Input *w, Hex_Spinner *hs); 55 | static void up_button_cb(Fl_Repeat_Button *w, Hex_Spinner *hs); 56 | static void down_button_cb(Fl_Repeat_Button *w, Hex_Spinner *hs); 57 | }; 58 | 59 | #endif 60 | -------------------------------------------------------------------------------- /src/ruler.cpp: -------------------------------------------------------------------------------- 1 | #pragma warning(push, 0) 2 | #include 3 | #pragma warning(pop) 4 | 5 | #include 6 | 7 | #include "ruler.h" 8 | #include "main-window.h" 9 | #include "themes.h" 10 | 11 | Ruler::Ruler(int x, int y, int w, int h, const char *l) : Fl_Box(x, y, w, h, l) {} 12 | 13 | int Ruler::handle(int event) { 14 | Main_Window *mw = (Main_Window *)user_data(); 15 | if (event == FL_PUSH || (event == FL_DRAG && !mw->playing())) { 16 | mw->set_tick_from_x_pos(Fl::event_x()); 17 | return 1; 18 | } 19 | if (event == FL_ENTER && mw->song_loaded()) { 20 | fl_cursor(FL_CURSOR_HAND); 21 | return 1; 22 | } 23 | if (event == FL_LEAVE) { 24 | fl_cursor(FL_CURSOR_DEFAULT); 25 | return 1; 26 | } 27 | return Fl_Box::handle(event); 28 | } 29 | 30 | static inline void print_tick_label(char *s, size_t size, int n) { 31 | snprintf(s, size, "%d", n); 32 | } 33 | 34 | void Ruler::draw() { 35 | int X = x(), Y = y(), W = w(), H = h(); 36 | Main_Window *mw = (Main_Window *)user_data(); 37 | int px = mw->song_ticks_per_step() * TICK_WIDTHS[mw->zoom()+1]; 38 | int s = _options.steps_per_beat * px; 39 | int S = _options.beats_per_measure * s; 40 | int p = _options.pickup_offset * px + WHITE_KEY_WIDTH; 41 | // background 42 | fl_color(FL_DARK2); 43 | fl_rectf(X, Y, W, H); 44 | // edges 45 | fl_color(OS::current_theme() == OS::Theme::HIGH_CONTRAST ? FL_SELECTION_COLOR : fl_color_average(FL_FOREGROUND_COLOR, FL_BACKGROUND_COLOR, 0.4f)); 46 | fl_xyline(X, Y+H-1, X+W-1); 47 | // tick marks and labels 48 | int mx = mw->song_scroll_x(); 49 | // tick marks 50 | int o = (p / S + 1) * S - p; 51 | int r = mx % s; 52 | int n = mx / s + 1; 53 | for (int i = s-r-1; i < W + o; i += s, n++) { 54 | int d = (n % _options.beats_per_measure) ? H / 2 : 0; 55 | fl_yxline(X+i - o, Y+d, Y+H-1); 56 | } 57 | // labels 58 | char t[8] = {}; 59 | fl_font(FL_COURIER, 12); 60 | fl_color(FL_FOREGROUND_COLOR); 61 | fl_push_clip(X, Y, W, H); 62 | int O = (p / S + 1) * S - p; 63 | int R = mx % S; 64 | int N = mx / S - p / S; 65 | for (int i = S-R-1; i < W+S + O; i += S, N++) { 66 | if (N >= 0) { 67 | print_tick_label(t, sizeof(t), N); 68 | fl_draw(t, X+i-S+1 - O, Y, S-2, H, FL_ALIGN_BOTTOM_RIGHT | FL_ALIGN_INSIDE | FL_ALIGN_CLIP); 69 | } 70 | } 71 | fl_pop_clip(); 72 | 73 | fl_color(BOOKMARK_COLOR); 74 | const std::set &bookmarks = mw->bookmarks(); 75 | for (int32_t bookmark : bookmarks) { 76 | int x_pos = X + bookmark * TICK_WIDTHS[mw->zoom()+1] + WHITE_KEY_WIDTH - mx - 1; 77 | fl_polygon(x_pos - 6, Y + H - 8, x_pos + 6, Y + H - 8, x_pos, Y + H - 2); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /src/note-properties.h: -------------------------------------------------------------------------------- 1 | #ifndef NOTE_PROPERTIES_H 2 | #define NOTE_PROPERTIES_H 3 | 4 | #pragma warning(push, 0) 5 | #include 6 | #pragma warning(pop) 7 | 8 | #include "command.h" 9 | #include "widgets.h" 10 | 11 | class Note_Properties : public Fl_Group { 12 | private: 13 | OS_Spinner *_speed_input = nullptr; 14 | OS_Spinner *_volume_input = nullptr; 15 | OS_Spinner *_fade_input = nullptr; 16 | OS_Spinner *_vibrato_delay_input = nullptr; 17 | OS_Spinner *_vibrato_depth_input = nullptr; 18 | OS_Spinner *_vibrato_rate_input = nullptr; 19 | OS_Spinner *_duty_wave_drumkit_input = nullptr; 20 | OS_Button *_advanced_button = nullptr; 21 | 22 | OS_Spinner *_tempo_input = nullptr; 23 | OS_Spinner *_transpose_octaves_input = nullptr; 24 | OS_Spinner *_transpose_pitches_input = nullptr; 25 | OS_Spinner *_slide_duration_input = nullptr; 26 | OS_Spinner *_slide_octave_input = nullptr; 27 | Dropdown *_slide_pitch_input = nullptr; 28 | OS_Check_Button *_panning_left_input = nullptr; 29 | OS_Check_Button *_panning_right_input = nullptr; 30 | OS_Button *_basic_button = nullptr; 31 | 32 | Note_View _note; 33 | int _channel_number = 0; 34 | public: 35 | Note_Properties(int X, int Y, int W, int H, const char *l = nullptr); 36 | ~Note_Properties(); 37 | void set_note_properties(const std::vector ¬es, int channel_number, int num_waves, int num_drumkits, bool first_channel); 38 | int handle(int event); 39 | private: 40 | static void speed_input_cb(OS_Spinner *s, Note_Properties *np); 41 | static void volume_input_cb(OS_Spinner *s, Note_Properties *np); 42 | static void fade_input_cb(OS_Spinner *s, Note_Properties *np); 43 | static void vibrato_delay_input_cb(OS_Spinner *s, Note_Properties *np); 44 | static void vibrato_depth_input_cb(OS_Spinner *s, Note_Properties *np); 45 | static void vibrato_rate_input_cb(OS_Spinner *s, Note_Properties *np); 46 | static void duty_wave_drumkit_input_cb(OS_Spinner *s, Note_Properties *np); 47 | static void advanced_button_cb(OS_Button *b, Note_Properties *np); 48 | 49 | static void tempo_input_cb(OS_Spinner *s, Note_Properties *np); 50 | static void transpose_octaves_input_cb(OS_Spinner *s, Note_Properties *np); 51 | static void transpose_pitches_input_cb(OS_Spinner *s, Note_Properties *np); 52 | static void slide_duration_input_cb(OS_Spinner *s, Note_Properties *np); 53 | static void slide_octave_input_cb(OS_Spinner *s, Note_Properties *np); 54 | static void slide_pitch_input_cb(Dropdown *d, Note_Properties *np); 55 | static void panning_left_input_cb(OS_Check_Button *c, Note_Properties *np); 56 | static void panning_right_input_cb(OS_Check_Button *c, Note_Properties *np); 57 | static void basic_button_cb(OS_Button *b, Note_Properties *np); 58 | }; 59 | 60 | #endif 61 | -------------------------------------------------------------------------------- /res/warning.xpm: -------------------------------------------------------------------------------- 1 | /* XPM */ 2 | static const char *WARNING_XPM[] = { 3 | "48 48 3 1", 4 | " c None", 5 | "# c #000000", 6 | ". c #FFD000", 7 | " ", 8 | " ", 9 | " ", 10 | " ", 11 | " ## ", 12 | " #..# ", 13 | " #....# ", 14 | " #....# ", 15 | " #......# ", 16 | " #......# ", 17 | " #........# ", 18 | " #..........# ", 19 | " #..........# ", 20 | " #............# ", 21 | " #.....##.....# ", 22 | " #.....####.....# ", 23 | " #.....######.....# ", 24 | " #.....######.....# ", 25 | " #......######......# ", 26 | " #......######......# ", 27 | " #.......######.......# ", 28 | " #........######........# ", 29 | " #........######........# ", 30 | " #.........######.........# ", 31 | " #.........######.........# ", 32 | " #...........####...........# ", 33 | " #............####............# ", 34 | " #............####............# ", 35 | " #.............####.............# ", 36 | " #.............####.............# ", 37 | " #..............####..............# ", 38 | " #................##................# ", 39 | " #..................................# ", 40 | " #....................................# ", 41 | " #................####................# ", 42 | " #................######................# ", 43 | " #.................######.................# ", 44 | " #.................######.................# ", 45 | " #..................######..................# ", 46 | " #...................####...................# ", 47 | " #............................................# ", 48 | "#..............................................#", 49 | "#..............................................#", 50 | " ############################################## ", 51 | " ", 52 | " ", 53 | " ", 54 | " " 55 | }; 56 | -------------------------------------------------------------------------------- /res/error.xpm: -------------------------------------------------------------------------------- 1 | /* XPM */ 2 | static const char *ERROR_XPM[] = { 3 | "48 48 4 1", 4 | " c None", 5 | "# c #000000", 6 | ". c #FF0000", 7 | "= c #FFFFFF", 8 | " ########## ", 9 | " ###..........### ", 10 | " ##................## ", 11 | " ##....................## ", 12 | " ##........................## ", 13 | " #............................# ", 14 | " #..............................# ", 15 | " #................................# ", 16 | " #..................................# ", 17 | " #....................................# ", 18 | " #........==..................==........# ", 19 | " #.......====................====.......# ", 20 | " #.......======..............======.......# ", 21 | " #......========............========......# ", 22 | " #.......=========..........=========.......# ", 23 | " #........=========........=========........# ", 24 | " #..........=========......=========..........# ", 25 | " #...........=========....=========...........# ", 26 | " #............=========..=========............# ", 27 | "#..............==================..............#", 28 | "#...............================...............#", 29 | "#................==============................#", 30 | "#.................============.................#", 31 | "#..................==========..................#", 32 | "#..................==========..................#", 33 | "#.................============.................#", 34 | "#................==============................#", 35 | "#...............================...............#", 36 | "#..............==================..............#", 37 | " #............=========..=========............# ", 38 | " #...........=========....=========...........# ", 39 | " #..........=========......=========..........# ", 40 | " #........=========........=========........# ", 41 | " #.......=========..........=========.......# ", 42 | " #......========............========......# ", 43 | " #.......======..............======.......# ", 44 | " #.......====................====.......# ", 45 | " #........==..................==........# ", 46 | " #....................................# ", 47 | " #..................................# ", 48 | " #................................# ", 49 | " #..............................# ", 50 | " #............................# ", 51 | " ##........................## ", 52 | " ##....................## ", 53 | " ##................## ", 54 | " ###..........### ", 55 | " ########## " 56 | }; 57 | -------------------------------------------------------------------------------- /res/success.xpm: -------------------------------------------------------------------------------- 1 | /* XPM */ 2 | static const char *SUCCESS_XPM[] = { 3 | "48 48 4 1", 4 | " c None", 5 | "# c #000000", 6 | ". c #009000", 7 | "= c #FFFFFF", 8 | " ########## ", 9 | " ###..........### ", 10 | " ##................## ", 11 | " ##....................## ", 12 | " ##........................## ", 13 | " #............................# ", 14 | " #..............................# ", 15 | " #................................# ", 16 | " #..................................# ", 17 | " #....................................# ", 18 | " #......................................# ", 19 | " #..............................==......# ", 20 | " #..............................====......# ", 21 | " #.............................======.....# ", 22 | " #.............................========.....# ", 23 | " #............................=========.....# ", 24 | " #............................=========.......# ", 25 | " #...........................=========........# ", 26 | " #..........................=========.........# ", 27 | "#..........................=========...........#", 28 | "#.........................=========............#", 29 | "#........................=========.............#", 30 | "#.......................=========..............#", 31 | "#..........==..........=========...............#", 32 | "#.........====........=========................#", 33 | "#........======......=========.................#", 34 | "#.......========....=========..................#", 35 | "#.......=========..=========...................#", 36 | "#........==================....................#", 37 | " #........================....................# ", 38 | " #.........==============.....................# ", 39 | " #..........============......................# ", 40 | " #..........==========......................# ", 41 | " #...........========.......................# ", 42 | " #...........======.......................# ", 43 | " #............====........................# ", 44 | " #............==........................# ", 45 | " #......................................# ", 46 | " #....................................# ", 47 | " #..................................# ", 48 | " #................................# ", 49 | " #..............................# ", 50 | " #............................# ", 51 | " ##........................## ", 52 | " ##....................## ", 53 | " ##................## ", 54 | " ###..........### ", 55 | " ########## " 56 | }; 57 | -------------------------------------------------------------------------------- /src/preferences.cpp: -------------------------------------------------------------------------------- 1 | #pragma warning(push, 0) 2 | #include 3 | #include 4 | #include 5 | #pragma warning(pop) 6 | 7 | #include "version.h" 8 | #include "preferences.h" 9 | #include "utils.h" 10 | 11 | #ifdef _WIN32 12 | #include 13 | #else 14 | #include 15 | #endif 16 | 17 | Fl_Preferences *Preferences::_preferences = NULL; 18 | 19 | static void get_program_file_dir(char *path, size_t n, const char *argv0) { 20 | char name[FL_PATH_MAX] = {}; 21 | #ifdef _WIN32 22 | wchar_t buffer[MAX_PATH] = {}; 23 | if (GetModuleFileName(NULL, buffer, _countof(buffer))) { 24 | fl_utf8fromwc(name, FL_PATH_MAX, buffer, MAX_PATH); 25 | } 26 | else { 27 | strncpy(name, argv0, FL_PATH_MAX); 28 | } 29 | #else 30 | if (readlink("/proc/self/exe", name, FL_PATH_MAX-1) == -1) { 31 | strncpy(name, argv0, FL_PATH_MAX); 32 | } 33 | #endif 34 | name[FL_PATH_MAX-1] = '\0'; 35 | fl_filename_absolute(path, (int)n, name); 36 | char *pivot = strrchr(path, *DIR_SEP); 37 | if (pivot) { 38 | *pivot = '\0'; 39 | } 40 | else { 41 | strcpy(path, "."); 42 | } 43 | } 44 | 45 | void Preferences::initialize(const char *argv0) { 46 | // Get the directory of crystaltracker.exe 47 | char dirname[FL_PATH_MAX] = {}; 48 | get_program_file_dir(dirname, _countof(dirname), argv0); 49 | 50 | char prefs[FL_PATH_MAX] = {}; 51 | 52 | // Use crystaltracker.prefs if it exists 53 | strcpy(prefs, dirname); 54 | strcat(prefs, DIR_SEP PROGRAM_EXE_NAME PREFS_EXT); 55 | if (file_exists(prefs)) { 56 | _preferences = new Fl_Preferences(dirname, PROGRAM_AUTHOR, PROGRAM_EXE_NAME, Fl_Preferences::C_LOCALE); 57 | return; 58 | } 59 | 60 | // Use Crystal Tracker.prefs if it exists 61 | strcpy(prefs, dirname); 62 | strcat(prefs, DIR_SEP PROGRAM_NAME PREFS_EXT); 63 | if (file_exists(prefs)) { 64 | _preferences = new Fl_Preferences(dirname, PROGRAM_AUTHOR, PROGRAM_NAME, Fl_Preferences::C_LOCALE); 65 | return; 66 | } 67 | 68 | // Use the user's FLTK preferences 69 | _preferences = new Fl_Preferences(Fl_Preferences::USER_L, PROGRAM_AUTHOR, PROGRAM_NAME); 70 | } 71 | 72 | void Preferences::close() { 73 | _preferences->flush(); 74 | delete _preferences; 75 | _preferences = NULL; 76 | } 77 | 78 | int Preferences::get(const char *key, int default_) { 79 | int value; 80 | _preferences->get(key, value, default_); 81 | return value; 82 | } 83 | 84 | void Preferences::set(const char *key, int value) { 85 | _preferences->set(key, value); 86 | } 87 | 88 | std::string Preferences::get_string(const char *key) { 89 | char *value; 90 | _preferences->get(key, value, ""); 91 | std::string s(value ? value : ""); 92 | free(value); 93 | return s; 94 | } 95 | 96 | void Preferences::set_string(const char *key, const std::string &value) { 97 | _preferences->set(key, value.c_str()); 98 | } 99 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #pragma warning(push, 0) 4 | #include 5 | #include 6 | #pragma warning(pop) 7 | 8 | #include 9 | 10 | #include "version.h" 11 | #include "preferences.h" 12 | #include "themes.h" 13 | #include "main-window.h" 14 | 15 | #ifdef _WIN32 16 | 17 | #include 18 | #include 19 | 20 | #define MAKE_WSTR_HELPER(x) L ## x 21 | #define MAKE_WSTR(x) MAKE_WSTR_HELPER(x) 22 | 23 | #elif defined(__APPLE__) 24 | #include "cocoa.h" 25 | #endif 26 | 27 | static Main_Window *window = nullptr; 28 | 29 | int main(int argc, char **argv) { 30 | Preferences::initialize(argv[0]); 31 | std::ios::sync_with_stdio(false); 32 | portaudio::AutoSystem portaudio_initializer; 33 | #ifdef _WIN32 34 | SetCurrentProcessExplicitAppUserModelID(MAKE_WSTR(PROGRAM_AUTHOR) L"." MAKE_WSTR(PROGRAM_NAME)); 35 | #endif 36 | Fl::keyboard_screen_scaling(0); 37 | Fl::visual(FL_DOUBLE | FL_RGB); 38 | fl_contrast_level(50); 39 | 40 | #ifdef _WIN32 41 | OS::Theme default_theme = OS::Theme::METRO; 42 | DWORD reg_value = 1, reg_size = sizeof(reg_value); 43 | if (!RegGetValue(HKEY_CURRENT_USER, _T("SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize"), 44 | _T("AppsUseLightTheme"), RRF_RT_REG_DWORD, NULL, ®_value, ®_size) && reg_value == 0) { 45 | default_theme = OS::Theme::DARK; 46 | } 47 | OS::Theme theme = (OS::Theme)Preferences::get("theme", (int)default_theme); 48 | #elif defined(__APPLE__) 49 | OS::Theme default_theme = OS::Theme::AQUA; 50 | if (cocoa_is_dark_mode()) default_theme = OS::Theme::DARK; 51 | OS::Theme theme = (OS::Theme)Preferences::get("theme", (int)default_theme); 52 | #else 53 | OS::Theme theme = (OS::Theme)Preferences::get("theme", (int)OS::Theme::GREYBIRD); 54 | #endif 55 | OS::use_native_fonts(); 56 | OS::use_theme(theme); 57 | 58 | #ifdef _WIN32 59 | int x = Preferences::get("x", 48), y = Preferences::get("y", 48 + GetSystemMetrics(SM_CYCAPTION)); 60 | #else 61 | int x = Preferences::get("x", 48), y = Preferences::get("y", 48); 62 | #endif 63 | int w = Preferences::get("w", 800), h = Preferences::get("h", 600); 64 | window = new Main_Window(x, y, w, h); 65 | Fl::lock(); 66 | window->show(); 67 | OS::update_macos_appearance(window); 68 | if (window->full_screen()) { 69 | window->fullscreen(); 70 | } 71 | else if (Preferences::get("maximized")) { 72 | window->maximize(); 73 | } 74 | 75 | int argi = 1; 76 | #ifdef __APPLE__ 77 | // Ignore the "-psn_*" parameter passed by some older macOS versions 78 | // See https://stackoverflow.com/questions/10242115/os-x-strange-psn-command-line-parameter-when-launched-from-finder 79 | while (argi < argc) { 80 | if (memcmp(argv[argi], "-psn_", 4) != 0) break; 81 | argi++; 82 | } 83 | #endif 84 | 85 | if (argi < argc) { 86 | window->open_song(argv[argi]); 87 | } 88 | 89 | return Fl::run(); 90 | } 91 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Crystal Tracker 2 | 3 | A song and sound editor for [pokecrystal](https://github.com/pret/pokecrystal), [pokegold](https://github.com/pret/pokegold), [pokeyellow-crysaudio](https://github.com/dannye/pokeyellow-crysaudio), [pokered-crysaudio](https://github.com/dannye/pokered-crysaudio), and [pokepinball](https://github.com/pret/pokepinball). 4 | 5 | Crystal Tracker provides the ability to directly create and modify pokecrystal-based asm song files, with in-app immediate playback by dynamically loading and synthesizing custom channel 3 waveforms and channel 4 drumkits. 6 | 7 | The editor exports fully complete asm song files, so that you can "Save" your changes and then immediately `make` your project and instantly have a new ROM file with your modifications included — no additional conversion tools or steps required. 8 | 9 | ![Screenshot](screenshot.png) 10 | 11 | ## Getting Started 12 | 13 | ### Windows 14 | 15 | Download the latest Windows executable from the [Releases](https://github.com/dannye/crystal-tracker/releases) section. 16 | To build from source, see [INSTALL.md](INSTALL.md). 17 | 18 | --- 19 | 20 | ### Linux 21 | 22 | See [INSTALL.md](INSTALL.md) to build from source or run the Windows executable via Wine. 23 | 24 | --- 25 | 26 | ### Mac 27 | 28 | Download the latest dmg from the [Releases](https://github.com/dannye/crystal-tracker/releases) section or run the Windows executable via Wine. 29 | To build from source, see [INSTALL.md](INSTALL.md). 30 | 31 | After installing from the dmg, launching the app may show an error that says that Crystal Tracker "cannot be opened because the developer cannot be verified" or "is damaged and can't be opened". To fix this, clear the "com.apple.quarantine" attribute from the app by running the following command: 32 | 33 | ```sh 34 | xattr -c "/Applications/Crystal Tracker.app" 35 | ``` 36 | 37 | --- 38 | 39 | The [example/](example/) directory contains crystaltracked.asm, an original composition by Mickey-A 42. 40 | 41 | Be sure to see the Help menu for a full explanation on how to use the editor. 42 | 43 | ## Upgrading a legacy project 44 | 45 | If your project uses the "legacy" macros (pre-2020), you will need to upgrade your project. 46 | 47 | Download [tools/upgrade.py](tools/upgrade.py) and put it at the root of your project. 48 | Run the upgrade script on your music folder, like: 49 | ```sh 50 | python3 upgrade.py -v audio/music/ 51 | ``` 52 | It can also be used to upgrade individual files, like: 53 | ```sh 54 | python3 upgrade.py -v audio/drumkits.asm 55 | ``` 56 | The script also works with python2. Run `./upgrade.py -h` for all options. 57 | 58 | Be sure to make adequate backups of your song files (via git or otherwise) before upgrading, just in case. 59 | 60 | Then you must copy all modern audio macros from the latest [macros/scripts/audio.asm](https://github.com/pret/pokecrystal/blob/master/macros/scripts/audio.asm) and [macros/legacy.asm](https://github.com/pret/pokecrystal/blob/master/macros/legacy.asm) into your project. 61 | 62 | ## Special Thanks 63 | 64 | This project takes a lot of inspiration (and a lot of backbone code structure) from [Polished Map](https://github.com/Rangi42/polished-map). A huge thank you to [Rangi42](https://github.com/Rangi42)! 65 | 66 | Additional thanks to [mid-kid](https://github.com/mid-kid) for the Mac port. 67 | -------------------------------------------------------------------------------- /ide/crystal-tracker.rc: -------------------------------------------------------------------------------- 1 | // Microsoft Visual C++ generated resource script. 2 | // 3 | #include "../src/resource.h" 4 | #include "../src/version.h" 5 | 6 | #define APSTUDIO_READONLY_SYMBOLS 7 | ///////////////////////////////////////////////////////////////////////////// 8 | // 9 | // Generated from the TEXTINCLUDE 2 resource. 10 | // 11 | #include "winres.h" 12 | 13 | ///////////////////////////////////////////////////////////////////////////// 14 | #undef APSTUDIO_READONLY_SYMBOLS 15 | 16 | ///////////////////////////////////////////////////////////////////////////// 17 | // English (United States) resources 18 | 19 | #if !defined(AFX_RESOURCE_DLL) || defined(AFX_TARG_ENU) 20 | LANGUAGE LANG_ENGLISH, SUBLANG_ENGLISH_US 21 | #pragma code_page(1252) 22 | 23 | #ifdef APSTUDIO_INVOKED 24 | ///////////////////////////////////////////////////////////////////////////// 25 | // 26 | // TEXTINCLUDE 27 | // 28 | 29 | 1 TEXTINCLUDE 30 | BEGIN 31 | "resource.h\0" 32 | END 33 | 34 | 2 TEXTINCLUDE 35 | BEGIN 36 | "#include ""winres.h""\r\n" 37 | "\0" 38 | END 39 | 40 | 3 TEXTINCLUDE 41 | BEGIN 42 | "#include ""resource.h""\r\n" 43 | "\0" 44 | END 45 | 46 | #endif // APSTUDIO_INVOKED 47 | 48 | 49 | ///////////////////////////////////////////////////////////////////////////// 50 | // 51 | // Icon 52 | // 53 | 54 | // Icon with lowest ID value placed first to ensure application icon 55 | // remains consistent on all systems. 56 | IDI_ICON1 ICON "..\\res\\app.ico" 57 | 58 | ///////////////////////////////////////////////////////////////////////////// 59 | // 60 | // Version 61 | // 62 | 63 | VS_VERSION_INFO VERSIONINFO 64 | FILEVERSION PROGRAM_VERSION 65 | PRODUCTVERSION PROGRAM_VERSION 66 | FILEFLAGSMASK 0x3fL 67 | #ifdef _DEBUG 68 | FILEFLAGS 0x1L 69 | #else 70 | FILEFLAGS 0x0L 71 | #endif 72 | FILEOS 0x40004L 73 | FILETYPE 0x1L 74 | FILESUBTYPE 0x0L 75 | BEGIN 76 | BLOCK "StringFileInfo" 77 | BEGIN 78 | BLOCK "040904b0" 79 | BEGIN 80 | VALUE "CompanyName", PROGRAM_AUTHOR 81 | VALUE "FileDescription", PROGRAM_NAME 82 | VALUE "FileVersion", PROGRAM_VERSION_STRING 83 | VALUE "InternalName", PROGRAM_EXE 84 | VALUE "LegalCopyright", "Copyright © " CURRENT_YEAR " " PROGRAM_AUTHOR 85 | VALUE "OriginalFilename", PROGRAM_EXE 86 | VALUE "ProductName", PROGRAM_NAME 87 | VALUE "ProductVersion", PROGRAM_VERSION_STRING 88 | END 89 | END 90 | BLOCK "VarFileInfo" 91 | BEGIN 92 | VALUE "Translation", 0x409, 1200 93 | END 94 | END 95 | 96 | #endif // English (United States) resources 97 | ///////////////////////////////////////////////////////////////////////////// 98 | 99 | 100 | 101 | #ifndef APSTUDIO_INVOKED 102 | ///////////////////////////////////////////////////////////////////////////// 103 | // 104 | // Generated from the TEXTINCLUDE 3 resource. 105 | // 106 | #include "../src/resource.h" 107 | 108 | ///////////////////////////////////////////////////////////////////////////// 109 | #endif // not APSTUDIO_INVOKED 110 | 111 | -------------------------------------------------------------------------------- /src/parse-waves.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #pragma warning(push, 0) 4 | #include 5 | #pragma warning(pop) 6 | 7 | #include "parse-waves.h" 8 | 9 | #include "utils.h" 10 | 11 | Parsed_Waves::Parsed_Waves(const char *d) { 12 | parse_waves(d); 13 | } 14 | 15 | Parsed_Waves::Result Parsed_Waves::parse_wave(std::istringstream &lss, Wave &wave, bool nybbles) { 16 | size_t i = 0; 17 | for (std::string token; std::getline(lss, token, ',');) { 18 | if (i >= NUM_WAVE_SAMPLES) { 19 | return Result::WAVES_BAD_FILE; 20 | } 21 | 22 | int32_t v; 23 | if (!parse_value(token, v)) { 24 | return Result::WAVES_BAD_FILE; 25 | } 26 | if (nybbles) { 27 | if (v < 0 || v > 15) { 28 | return Result::WAVES_BAD_FILE; 29 | } 30 | wave[i++] = v; 31 | } 32 | else { 33 | if (v < 0 || v > 255) { 34 | return Result::WAVES_BAD_FILE; 35 | } 36 | int32_t hi = v >> 4; 37 | int32_t lo = v & 0x0F; 38 | wave[i++] = hi; 39 | wave[i++] = lo; 40 | } 41 | } 42 | if (i != NUM_WAVE_SAMPLES) { 43 | return Result::WAVES_BAD_FILE; 44 | } 45 | return Result::WAVES_OK; 46 | } 47 | 48 | Parsed_Waves::Result Parsed_Waves::parse_waves(const char *d) { 49 | char waves_file[FL_PATH_MAX] = {}; 50 | 51 | // first, try crysaudio/wave_samples.asm 52 | strcpy(waves_file, d); 53 | strcat(waves_file, DIR_SEP "crysaudio" DIR_SEP "wave_samples.asm"); 54 | if (try_parse_waves(waves_file) == Parsed_Waves::Result::WAVES_OK) { 55 | return _result; 56 | } 57 | // second, try audio/wave_samples.asm 58 | strcpy(waves_file, d); 59 | strcat(waves_file, DIR_SEP "audio" DIR_SEP "wave_samples.asm"); 60 | if (try_parse_waves(waves_file) == Parsed_Waves::Result::WAVES_OK) { 61 | return _result; 62 | } 63 | // third, try wave_samples.asm 64 | strcpy(waves_file, d); 65 | strcat(waves_file, DIR_SEP "wave_samples.asm"); 66 | if (try_parse_waves(waves_file) == Parsed_Waves::Result::WAVES_OK) { 67 | return _result; 68 | } 69 | 70 | return _result; 71 | } 72 | 73 | Parsed_Waves::Result Parsed_Waves::try_parse_waves(const char *f) { 74 | _waves_file = f; 75 | _waves.clear(); 76 | _num_parsed_waves = 0; 77 | _result = Result::WAVES_NULL; 78 | 79 | std::ifstream ifs; 80 | open_ifstream(ifs, f); 81 | if (!ifs.good()) { 82 | return (_result = Result::WAVES_BAD_FILE); 83 | } 84 | 85 | while (ifs.good()) { 86 | std::string line; 87 | std::getline(ifs, line); 88 | remove_comment(line); 89 | rtrim(line); 90 | if (line.size() == 0) { continue; } 91 | std::istringstream lss(line); 92 | 93 | std::string macro; 94 | if (!leading_macro(lss, macro)) { continue; } 95 | bool nybbles = equals_ignore_case(macro, "dn"); 96 | if (!nybbles && !equals_ignore_case(macro, "db")) { continue; } 97 | 98 | Wave wave; 99 | Result r = parse_wave(lss, wave, nybbles); 100 | if (r != Result::WAVES_OK) { 101 | return (_result = r); 102 | } 103 | _waves.push_back(wave); 104 | } 105 | 106 | _num_parsed_waves = (int32_t)_waves.size(); 107 | _waves.resize(16); 108 | 109 | if (_num_parsed_waves == 0) { 110 | return (_result = Result::WAVES_BAD_FILE); 111 | } 112 | return (_result = Result::WAVES_OK); 113 | } 114 | -------------------------------------------------------------------------------- /src/themes.h: -------------------------------------------------------------------------------- 1 | #ifndef THEMES_H 2 | #define THEMES_H 3 | 4 | #pragma warning(push, 0) 5 | #include 6 | #include 7 | #pragma warning(pop) 8 | 9 | #include "utils.h" 10 | 11 | #ifdef __APPLE__ 12 | #include "cocoa.h" 13 | #endif 14 | 15 | #define OS_FONT FL_FREE_FONT 16 | 17 | #ifdef _WIN32 18 | #define OS_FONT_SIZE 12 19 | #else 20 | #define OS_FONT_SIZE 13 21 | #endif 22 | 23 | #define OS_BUTTON_UP_BOX FL_GTK_UP_BOX 24 | #define OS_CHECK_DOWN_BOX FL_GTK_DOWN_BOX 25 | #define OS_BUTTON_UP_FRAME FL_GTK_UP_FRAME 26 | #define OS_CHECK_DOWN_FRAME FL_GTK_DOWN_FRAME 27 | #define OS_PANEL_THIN_UP_BOX FL_GTK_THIN_UP_BOX 28 | #define OS_SPACER_THIN_DOWN_BOX FL_GTK_THIN_DOWN_BOX 29 | #define OS_PANEL_THIN_UP_FRAME FL_GTK_THIN_UP_FRAME 30 | #define OS_SPACER_THIN_DOWN_FRAME FL_GTK_THIN_DOWN_FRAME 31 | #define OS_RADIO_ROUND_DOWN_BOX FL_GTK_ROUND_DOWN_BOX 32 | #define OS_HOVERED_UP_BOX FL_PLASTIC_UP_BOX 33 | #define OS_DEPRESSED_DOWN_BOX FL_PLASTIC_DOWN_BOX 34 | #define OS_HOVERED_UP_FRAME FL_PLASTIC_UP_FRAME 35 | #define OS_DEPRESSED_DOWN_FRAME FL_PLASTIC_DOWN_FRAME 36 | #define OS_INPUT_THIN_DOWN_BOX FL_PLASTIC_THIN_DOWN_BOX 37 | #define OS_INPUT_THIN_DOWN_FRAME FL_PLASTIC_ROUND_DOWN_BOX 38 | #define OS_MINI_BUTTON_UP_BOX FL_GLEAM_UP_BOX 39 | #define OS_MINI_DEPRESSED_DOWN_BOX FL_GLEAM_DOWN_BOX 40 | #define OS_MINI_BUTTON_UP_FRAME FL_GLEAM_UP_FRAME 41 | #define OS_MINI_DEPRESSED_DOWN_FRAME FL_GLEAM_DOWN_FRAME 42 | #define OS_DEFAULT_BUTTON_UP_BOX FL_DIAMOND_UP_BOX 43 | #define OS_DEFAULT_HOVERED_UP_BOX FL_PLASTIC_THIN_UP_BOX 44 | #define OS_DEFAULT_DEPRESSED_DOWN_BOX FL_DIAMOND_DOWN_BOX 45 | #define OS_TOOLBAR_BUTTON_HOVER_BOX FL_GLEAM_ROUND_UP_BOX 46 | #define OS_SWATCH_BOX FL_ENGRAVED_BOX 47 | #define OS_SWATCH_FRAME FL_ENGRAVED_FRAME 48 | #define OS_BG_BOX FL_FREE_BOXTYPE 49 | #define OS_BG_DOWN_BOX (Fl_Boxtype)(FL_FREE_BOXTYPE+1) 50 | #define OS_TOOLBAR_FRAME (Fl_Boxtype)(FL_FREE_BOXTYPE+2) 51 | 52 | class OS { 53 | public: 54 | enum class Theme { CLASSIC, AERO, METRO, AQUA, GREYBIRD, OCEAN, BLUE, OLIVE, ROSE_GOLD, DARK, BRUSHED_METAL, HIGH_CONTRAST }; 55 | private: 56 | static Theme _current_theme; 57 | static bool _is_consolas; 58 | public: 59 | #ifdef _WIN32 60 | static bool is_classic_windows(void); 61 | static bool is_modern_windows(void); 62 | #endif 63 | inline static void update_macos_appearance(Fl_Window *window) { 64 | #ifdef __APPLE__ 65 | cocoa_set_appearance(window, is_dark_theme(current_theme()) ? COCOA_APPEARANCE_DARK_AQUA : COCOA_APPEARANCE_AQUA); 66 | #endif 67 | } 68 | inline static Theme current_theme(void) { return _current_theme; } 69 | inline static constexpr bool is_dark_theme(Theme t) { return t == Theme::DARK || t == Theme::HIGH_CONTRAST; } 70 | inline static bool is_consolas(void) { return _is_consolas; } 71 | static void use_native_fonts(void); 72 | static void use_native_settings(void); 73 | static void use_theme(Theme theme); 74 | static void use_classic_theme(void); 75 | static void use_aero_theme(void); 76 | static void use_metro_theme(void); 77 | static void use_aqua_theme(void); 78 | static void use_greybird_theme(void); 79 | static void use_ocean_theme(void); 80 | static void use_blue_theme(void); 81 | static void use_olive_theme(void); 82 | static void use_rose_gold_theme(void); 83 | static void use_dark_theme(void); 84 | static void use_brushed_metal_theme(void); 85 | static void use_high_contrast_theme(void); 86 | }; 87 | 88 | #endif 89 | -------------------------------------------------------------------------------- /src/parse-song.h: -------------------------------------------------------------------------------- 1 | #ifndef PARSE_SONG_H 2 | #define PARSE_SONG_H 3 | 4 | #include 5 | 6 | #include "command.h" 7 | #include "parse-waves.h" 8 | 9 | class Parsed_Song { 10 | public: 11 | enum class Result { 12 | SONG_OK, 13 | SONG_BAD_FILE, 14 | SONG_INVALID_HEADER, 15 | SONG_EMPTY_LOOP, 16 | SONG_NESTED_LOOP, 17 | SONG_NESTED_CALL, 18 | SONG_UNFINISHED_LOOP, 19 | SONG_UNFINISHED_CALL, 20 | SONG_NO_DRUMKIT_SELECTED, 21 | SONG_TOGGLE_NOISE_ALREADY_DISABLED, 22 | SONG_TOGGLE_NOISE_ALREADY_ENABLED, 23 | SONG_ENDED_PREMATURELY, 24 | SONG_UNRECOGNIZED_LABEL, 25 | SONG_DUPLICATE_LABEL, 26 | SONG_LOCAL_CHANNEL_LABEL_UNSUPPORTED, 27 | SONG_UNSUPPORTED_KEYWORD, 28 | SONG_ILLEGAL_MACRO, 29 | SONG_UNRECOGNIZED_MACRO, 30 | SONG_INVALID_MACRO_ARGUMENT, 31 | SONG_NULL 32 | }; 33 | private: 34 | std::string _song_name; 35 | int32_t _number_of_channels = 0; 36 | std::string _channel_1_label; 37 | std::string _channel_2_label; 38 | std::string _channel_3_label; 39 | std::string _channel_4_label; 40 | std::vector _channel_1_commands; 41 | std::vector _channel_2_commands; 42 | std::vector _channel_3_commands; 43 | std::vector _channel_4_commands; 44 | int32_t _channel_1_loop_tick = -1; 45 | int32_t _channel_2_loop_tick = -1; 46 | int32_t _channel_3_loop_tick = -1; 47 | int32_t _channel_4_loop_tick = -1; 48 | int32_t _channel_1_end_tick = -1; 49 | int32_t _channel_2_end_tick = -1; 50 | int32_t _channel_3_end_tick = -1; 51 | int32_t _channel_4_end_tick = -1; 52 | 53 | std::vector _waves; 54 | 55 | Result _result = Result::SONG_NULL; 56 | 57 | // for error reporting 58 | int32_t _line_number = 0; 59 | int32_t _channel_number = 0; 60 | std::string _label; 61 | std::vector _mixed_labels; 62 | public: 63 | Parsed_Song(const char *f); 64 | inline ~Parsed_Song() {} 65 | inline std::string song_name(void) const { return _song_name; } 66 | inline int32_t number_of_channels(void) const { return _number_of_channels; } 67 | inline std::string channel_1_label(void) const { return _channel_1_label; } 68 | inline std::string channel_2_label(void) const { return _channel_2_label; } 69 | inline std::string channel_3_label(void) const { return _channel_3_label; } 70 | inline std::string channel_4_label(void) const { return _channel_4_label; } 71 | inline std::vector &&channel_1_commands(void) { return std::move(_channel_1_commands); } 72 | inline std::vector &&channel_2_commands(void) { return std::move(_channel_2_commands); } 73 | inline std::vector &&channel_3_commands(void) { return std::move(_channel_3_commands); } 74 | inline std::vector &&channel_4_commands(void) { return std::move(_channel_4_commands); } 75 | inline int32_t channel_1_loop_tick(void) const { return _channel_1_loop_tick; } 76 | inline int32_t channel_2_loop_tick(void) const { return _channel_2_loop_tick; } 77 | inline int32_t channel_3_loop_tick(void) const { return _channel_3_loop_tick; } 78 | inline int32_t channel_4_loop_tick(void) const { return _channel_4_loop_tick; } 79 | inline int32_t channel_1_end_tick(void) const { return _channel_1_end_tick; } 80 | inline int32_t channel_2_end_tick(void) const { return _channel_2_end_tick; } 81 | inline int32_t channel_3_end_tick(void) const { return _channel_3_end_tick; } 82 | inline int32_t channel_4_end_tick(void) const { return _channel_4_end_tick; } 83 | inline std::vector &&waves(void) { return std::move(_waves); } 84 | inline Result result(void) const { return _result; } 85 | inline int32_t line_number(void) const { return _line_number; } 86 | inline int32_t channel_number(void) const { return _channel_number; } 87 | inline const std::string &label(void) const { return _label; } 88 | inline std::vector &&mixed_labels(void) { return std::move(_mixed_labels); } 89 | private: 90 | Result parse_song(const char *f); 91 | }; 92 | 93 | #endif 94 | -------------------------------------------------------------------------------- /res/app-icon.xpm: -------------------------------------------------------------------------------- 1 | /* XPM */ 2 | static const char *APP_ICON_XPM[] = { 3 | "16 16 168 2", 4 | " c #21000B", 5 | ". c #16000B", 6 | "+ c #000000", 7 | "@ c #FF432C", 8 | "# c #D33743", 9 | "$ c #853737", 10 | "% c #432C4E", 11 | "& c #372C4E", 12 | "* c #002C59", 13 | "= c #003764", 14 | "- c #0B376F", 15 | "; c #21437A", 16 | "> c #0B4E85", 17 | ", c #164E90", 18 | "' c #215990", 19 | ") c #A60B4E", 20 | "! c #FF85D3", 21 | "~ c #B14E64", 22 | "{ c #160000", 23 | "] c #900B00", 24 | "^ c #7A2137", 25 | "/ c #373759", 26 | "( c #00436F", 27 | "_ c #0B437A", 28 | ": c #900000", 29 | "< c #FF7ADE", 30 | "[ c #A67AA6", 31 | "} c #434E59", 32 | "| c #00000B", 33 | "1 c #0B0000", 34 | "2 c #850000", 35 | "3 c #6F2137", 36 | "4 c #4E3759", 37 | "5 c #373764", 38 | "6 c #164E85", 39 | "7 c #FF6FD3", 40 | "8 c #C89BBC", 41 | "9 c #0B0B0B", 42 | "0 c #647A90", 43 | "a c #435964", 44 | "b c #6F0000", 45 | "c c #434364", 46 | "d c #43436F", 47 | "e c #7A0000", 48 | "f c #FF59BC", 49 | "g c #E9A6D3", 50 | "h c #000B0B", 51 | "i c #A6BCBC", 52 | "j c #9BBCD3", 53 | "k c #2C3743", 54 | "l c #640000", 55 | "m c #43214E", 56 | "n c #43376F", 57 | "o c #F44390", 58 | "p c #FFBCE9", 59 | "q c #E97A9B", 60 | "r c #C8C8D3", 61 | "s c #C8E9F4", 62 | "t c #90B1D3", 63 | "u c #37434E", 64 | "v c #2C430B", 65 | "w c #FFB1D3", 66 | "x c #E9D3FF", 67 | "y c #596F85", 68 | "z c #BCDEF4", 69 | "A c #641616", 70 | "B c #D3E9F4", 71 | "C c #BCD3DE", 72 | "D c #7A9BB1", 73 | "E c #59BCC8", 74 | "F c #37E9A6", 75 | "G c #E9DEFF", 76 | "H c #BCD3E9", 77 | "I c #BCC8DE", 78 | "J c #B1C8DE", 79 | "K c #006FC8", 80 | "L c #85FFDE", 81 | "M c #00FF9B", 82 | "N c #BC6F85", 83 | "O c #D390BC", 84 | "P c #C8D3E9", 85 | "Q c #B1D3E9", 86 | "R c #0B7AD3", 87 | "S c #85FFE9", 88 | "T c #21FF9B", 89 | "U c #009B59", 90 | "V c #F40000", 91 | "W c #D34E59", 92 | "X c #B1E9F4", 93 | "Y c #BCD3F4", 94 | "Z c #0064C8", 95 | "` c #6FE9E9", 96 | " . c #43DE9B", 97 | ".. c #00C885", 98 | "+. c #436400", 99 | "@. c #64160B", 100 | "#. c #B11616", 101 | "$. c #BC0000", 102 | "%. c #A64359", 103 | "&. c #BC6485", 104 | "*. c #C8649B", 105 | "=. c #D3C8E9", 106 | "-. c #85E9E9", 107 | ";. c #16DEC8", 108 | ">. c #37BC21", 109 | ",. c #6F8521", 110 | "'. c #370B00", 111 | "). c #9B2121", 112 | "!. c #900B16", 113 | "~. c #6F212C", 114 | "{. c #BC4EB1", 115 | "]. c #C864BC", 116 | "^. c #BCC8E9", 117 | "/. c #9BE9FF", 118 | "(. c #3737DE", 119 | "_. c #00D3F4", 120 | ":. c #16DE90", 121 | "<. c #43A62C", 122 | "[. c #4E000B", 123 | "}. c #9B1616", 124 | "|. c #640016", 125 | "1. c #431616", 126 | "2. c #7A162C", 127 | "3. c #6F0B2C", 128 | "4. c #6F1664", 129 | "5. c #BC21BC", 130 | "6. c #C89BFF", 131 | "7. c #2164DE", 132 | "8. c #00BCFF", 133 | "9. c #0BDEDE", 134 | "0. c #37BC64", 135 | "a. c #004E16", 136 | "b. c #9B1621", 137 | "c. c #850B21", 138 | "d. c #640B21", 139 | "e. c #4E0B21", 140 | "f. c #0B000B", 141 | "g. c #430B16", 142 | "h. c #64214E", 143 | "i. c #430085", 144 | "j. c #4E37C8", 145 | "k. c #5937F4", 146 | "l. c #00DEFF", 147 | "m. c #2CC89B", 148 | "n. c #0B3716", 149 | "o. c #166421", 150 | "p. c #A61621", 151 | "q. c #7A0B21", 152 | "r. c #4E0B2C", 153 | "s. c #2C0B2C", 154 | "t. c #2C0B00", 155 | "u. c #5916A6", 156 | "v. c #432CE9", 157 | "w. c #16D3B1", 158 | "x. c #0B370B", 159 | "y. c #0B6421", 160 | "z. c #168521", 161 | "A. c #851621", 162 | "B. c #6F162C", 163 | "C. c #59162C", 164 | "D. c #430B37", 165 | "E. c #2C1637", 166 | "F. c #161637", 167 | "G. c #7A4390", 168 | "H. c #007A2C", 169 | "I. c #002C00", 170 | "J. c #0B852C", 171 | "K. c #0B9B2C", 172 | " . + + @ # $ % & * = - ; > , ' ", 173 | " + + ) ! ~ { ] ^ % / = ( _ > , ", 174 | "{ + : < [ + } | 1 2 3 4 5 ( _ 6 ", 175 | "+ 2 7 8 + 9 0 + a 9 9 b 3 c d _ ", 176 | "e f g + h i + 9 j + k 9 9 l m n ", 177 | "o p q + r + h s + + t + u h + v ", 178 | "w x y z A + B + + C + 9 D + E F ", 179 | "G y H y I H k + C + + J + K L M ", 180 | "N O y P Q y H y k + H + R S T U ", 181 | "V W N O y X y H y Y 1 Z ` ...+.", 182 | "@.#.$.%.&.*.=.y Q y Q -.Z ;.>.,.", 183 | "1 + '.).!.~.{.].y ^./.(._.:.<.[.", 184 | "}.|.1 + 1.2.3.4.5.6.7.8.9.0.+ a.", 185 | "b.c.d.e.f.+ g.h.i.j.k.l.m.+ n.o.", 186 | "p.b.q.d.r.s.| + t.u.v.w.+ x.y.z.", 187 | "b.b.A.B.C.D.E.F.| + G.H.I.y.J.K." 188 | }; 189 | -------------------------------------------------------------------------------- /src/it-module.h: -------------------------------------------------------------------------------- 1 | #ifndef IT_MODULE_H 2 | #define IT_MODULE_H 3 | 4 | #include 5 | #include 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | #include "command.h" 12 | #include "parse-waves.h" 13 | #include "parse-drumkits.h" 14 | 15 | constexpr std::size_t BUFFER_SIZE = 2048; 16 | constexpr std::int32_t SAMPLE_RATE = 48000; 17 | 18 | constexpr uint32_t ROWS_PER_PATTERN = 192; 19 | 20 | constexpr uint32_t NOISE_SAMPLE_SPEED_FACTOR = 4; 21 | 22 | constexpr float UNITS_PER_MINUTE = 256.0f /* units per frame */ * (262144.0f / 4389.0f) /* frames per second */ * 60.0f /* seconds per minute */; 23 | 24 | class IT_Module { 25 | private: 26 | std::vector _data; 27 | int32_t _tempo_change_wrong_channel = -1; 28 | int32_t _tempo_change_mid_note = -1; 29 | 30 | openmpt::module_ext *_mod = nullptr; 31 | 32 | portaudio::BlockingStream _stream; 33 | std::array _buffer; 34 | bool _is_interleaved = false; 35 | 36 | int32_t _current_pattern = 0; 37 | int32_t _current_row = 0; 38 | 39 | bool _paused = false; 40 | public: 41 | IT_Module(); 42 | IT_Module( 43 | const std::vector &channel_1_notes, 44 | const std::vector &channel_2_notes, 45 | const std::vector &channel_3_notes, 46 | const std::vector &channel_4_notes, 47 | const std::vector &waves, 48 | const std::vector &drumkits, 49 | const std::vector> &drums, 50 | int32_t loop_tick, 51 | bool stereo 52 | ); 53 | ~IT_Module() noexcept; 54 | 55 | IT_Module(const IT_Module&) = delete; 56 | IT_Module& operator=(const IT_Module&) = delete; 57 | 58 | bool export_file(const char *f); 59 | 60 | std::string get_warnings() { return _mod->get_metadata("warnings"); } 61 | int32_t tempo_change_wrong_channel() const { return _tempo_change_wrong_channel; } 62 | int32_t tempo_change_mid_note() const { return _tempo_change_mid_note; } 63 | 64 | bool ready() const { return _stream.isOpen(); } 65 | bool playing() { return Pa_IsStreamActive(_stream.paStream()) == 1; } 66 | bool paused() const { return _paused; } 67 | bool stopped() { return !playing() && !paused(); } 68 | bool looping() { return _mod->get_repeat_count() == -1; } 69 | 70 | bool start() { _paused = false; return Pa_StartStream(_stream.paStream()) == paNoError; } 71 | bool stop() { _paused = false; return Pa_StopStream(_stream.paStream()) == paNoError; } 72 | bool pause() { _paused = true; return Pa_StopStream(_stream.paStream()) == paNoError; } 73 | bool play(); 74 | 75 | void mute_channel(int32_t channel, bool mute); 76 | 77 | int32_t play_note(Pitch pitch, int32_t octave); 78 | void stop_note(int32_t channel); 79 | 80 | int32_t current_tick() const { return _current_pattern * ROWS_PER_PATTERN + _current_row; } 81 | void set_tick(int32_t tick); 82 | 83 | double get_position_seconds(); 84 | double get_duration_seconds(); 85 | private: 86 | bool try_open(); 87 | std::vector> get_instruments(); 88 | std::vector> get_samples(const std::vector &waves, const std::vector> &drums); 89 | std::vector> get_patterns( 90 | const std::vector &channel_1_notes, 91 | const std::vector &channel_2_notes, 92 | const std::vector &channel_3_notes, 93 | const std::vector &channel_4_notes, 94 | const std::vector &drumkits, 95 | int32_t loop_tick, 96 | bool stereo, 97 | int32_t num_inline_waves 98 | ); 99 | void generate_it_module( 100 | const std::vector &channel_1_notes = {}, 101 | const std::vector &channel_2_notes = {}, 102 | const std::vector &channel_3_notes = {}, 103 | const std::vector &channel_4_notes = {}, 104 | const std::vector &waves = {}, 105 | const std::vector &drumkits = {}, 106 | const std::vector> &drums = {}, 107 | int32_t loop_tick = -1, 108 | bool stereo = true 109 | ); 110 | }; 111 | 112 | std::vector> generate_noise_samples(const std::vector &drums); 113 | 114 | #endif 115 | -------------------------------------------------------------------------------- /src/utils.h: -------------------------------------------------------------------------------- 1 | #ifndef UTILS_H 2 | #define UTILS_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #pragma warning(push, 0) 16 | #include 17 | #pragma warning(pop) 18 | 19 | #if defined(__unix__) 20 | #define __X11__ 21 | #endif 22 | 23 | #ifdef _WIN32 24 | #define DIR_SEP "\\" 25 | #else 26 | #define DIR_SEP "/" 27 | #endif 28 | 29 | #ifdef __APPLE__ 30 | #define CONTROL_KEY "\xE2\x8C\x83" // UTF-8 encoding of U+2303 "UP ARROWHEAD" 31 | #define ALT_KEY "\xE2\x8C\xA5" // UTF-8 encoding of U+2325 "OPTION KEY" 32 | #define SHIFT_KEY "\xE2\x87\xA7" // UTF-8 encoding of U+21E7 "UPWARDS WHITE ARROW" 33 | #define COMMAND_KEY "\xE2\x8C\x98" // UTF-8 encoding of U+2318 "PLACE OF INTEREST SIGN" 34 | 35 | #define COMMAND_KEY_PLUS COMMAND_KEY 36 | #define ALT_KEY_PLUS ALT_KEY 37 | #define SHIFT_KEY_PLUS SHIFT_KEY 38 | #define COMMAND_SHIFT_KEYS_PLUS SHIFT_KEY_PLUS COMMAND_KEY_PLUS 39 | #define COMMAND_ALT_KEYS_PLUS ALT_KEY_PLUS COMMAND_KEY_PLUS 40 | 41 | #define TAB_SYMBOL "\xE2\x87\xA5" 42 | #define UP_SYMBOL "\xE2\x86\x91" 43 | #define DOWN_SYMBOL "\xE2\x86\x93" 44 | #define LEFT_SYMBOL "\xE2\x86\x90" 45 | #define RIGHT_SYMBOL "\xE2\x86\x92" 46 | #define BACKSPACE_SYMBOL "\xE2\x8C\xAB" 47 | #define DELETE_SYMBOL COMMAND_KEY_PLUS BACKSPACE_SYMBOL 48 | 49 | #define DELETE_KEY FL_COMMAND + FL_BackSpace 50 | #define INSERT_REST_KEY FL_CONTROL + FL_SHIFT + 'i' 51 | #define GLUE_KEY '?' 52 | #define FULLSCREEN_KEY FL_COMMAND + FL_SHIFT + 'f' 53 | #else 54 | #define CONTROL_KEY "Ctrl" 55 | #define ALT_KEY "Alt" 56 | #define SHIFT_KEY "Shift" 57 | #define COMMAND_KEY CONTROL_KEY 58 | 59 | #define COMMAND_KEY_PLUS CONTROL_KEY "+" 60 | #define ALT_KEY_PLUS ALT_KEY "+" 61 | #define SHIFT_KEY_PLUS SHIFT_KEY "+" 62 | #define COMMAND_SHIFT_KEYS_PLUS COMMAND_KEY_PLUS SHIFT_KEY_PLUS 63 | #define COMMAND_ALT_KEYS_PLUS COMMAND_KEY_PLUS ALT_KEY_PLUS 64 | 65 | #define TAB_SYMBOL "Tab" 66 | #define UP_SYMBOL "Up" 67 | #define DOWN_SYMBOL "Down" 68 | #define LEFT_SYMBOL "Left" 69 | #define RIGHT_SYMBOL "Right" 70 | #define BACKSPACE_SYMBOL "Backspace" 71 | #define DELETE_SYMBOL "Delete" 72 | 73 | #define DELETE_KEY FL_Delete 74 | #define INSERT_REST_KEY FL_SHIFT + FL_Insert 75 | #define GLUE_KEY FL_SHIFT + '/' 76 | #define FULLSCREEN_KEY FL_F + 11 77 | #endif 78 | 79 | #ifndef _countof 80 | #define _countof(a) (sizeof(a) / sizeof(a[0])) 81 | #endif 82 | 83 | #define RANGE(x) std::begin(x), std::end(x) 84 | 85 | typedef uint8_t size8_t; 86 | typedef uint16_t size16_t; 87 | typedef uint32_t size32_t; 88 | typedef uint64_t size64_t; 89 | 90 | extern const std::string whitespace; 91 | 92 | bool equals_ignore_case(std::string_view s, std::string_view p); 93 | bool starts_with(std::string_view s, std::string_view p); 94 | bool ends_with(std::string_view s, std::string_view p); 95 | bool ends_with_ignore_case(std::string_view s, std::string_view p); 96 | bool ends_with_ignore_case(std::wstring_view s, std::wstring_view p); 97 | bool is_indented(std::string_view s); 98 | bool is_hex(std::string_view s); 99 | bool is_decimal(std::string_view s); 100 | bool is_octal(std::string_view s); 101 | bool is_binary(std::string_view s); 102 | void trim(std::string &s, const std::string &t = whitespace); 103 | void rtrim(std::string &s, const std::string &t = whitespace); 104 | void lowercase(std::string &s); 105 | bool leading_macro(std::istringstream &iss, std::string ¯o, const char *v = NULL); 106 | void remove_comment(std::string &s); 107 | void remove_suffix(const char *n, char *s); 108 | void before_suffix(const char *n, char *s); 109 | void after_suffix(const char *n, char *s); 110 | void remove_dot_ext(const char *f, char *s); 111 | void add_dot_ext(const char *f, const char *ext, char *s); 112 | int text_width(const char *l, int pad = 0); 113 | bool file_exists(const char *f); 114 | size_t file_size(const char *f); 115 | size_t file_size(FILE *f); 116 | int64_t file_modified(const char *f); 117 | void open_ifstream(std::ifstream &ifs, const char *f); 118 | void open_ofstream(std::ofstream &ofs, const char *f); 119 | void draw_outlined_text(const char *l, int x, int y, int w, int h, Fl_Align a, Fl_Color c, Fl_Color s); 120 | 121 | bool parse_value(std::string s, int32_t &v); 122 | 123 | #endif 124 | -------------------------------------------------------------------------------- /lib/patches/portaudio.patch: -------------------------------------------------------------------------------- 1 | diff --git a/src/hostapi/wmme/pa_win_wmme.c b/src/hostapi/wmme/pa_win_wmme.c 2 | index f8b1d7e..8a62acf 100644 3 | --- a/src/hostapi/wmme/pa_win_wmme.c 4 | +++ b/src/hostapi/wmme/pa_win_wmme.c 5 | @@ -3717,7 +3717,9 @@ static PaError ReadStream( PaStream* s, 6 | unsigned long framesProcessed; 7 | signed int hostInputBufferIndex; 8 | DWORD waitResult; 9 | - DWORD timeout = (unsigned long)(stream->allBuffersDurationMs * 0.5); 10 | + DWORD pollTimeoutMs = stream->allBuffersDurationMs / 2; 11 | + DWORD failTimeoutMs = stream->allBuffersDurationMs * 3; 12 | + DWORD accumulatedTimoutMs = 0; 13 | unsigned int channel, i; 14 | 15 | if( PA_IS_INPUT_STREAM_(stream) ) 16 | @@ -3742,6 +3744,8 @@ static PaError ReadStream( PaStream* s, 17 | do{ 18 | if( CurrentInputBuffersAreDone( stream ) ) 19 | { 20 | + accumulatedTimoutMs = 0; /* reset failure timer whenever we have a buffer */ 21 | + 22 | if( NoBuffersAreQueued( &stream->input ) ) 23 | { 24 | /** @todo REVIEW: consider what to do if the input overflows. 25 | @@ -3787,7 +3791,7 @@ static PaError ReadStream( PaStream* s, 26 | 27 | }else{ 28 | /* wait for MME to signal that a buffer is available */ 29 | - waitResult = WaitForSingleObject( stream->input.bufferEvent, timeout ); 30 | + waitResult = WaitForSingleObject( stream->input.bufferEvent, pollTimeoutMs ); 31 | if( waitResult == WAIT_FAILED ) 32 | { 33 | result = paUnanticipatedHostError; 34 | @@ -3795,9 +3799,13 @@ static PaError ReadStream( PaStream* s, 35 | } 36 | else if( waitResult == WAIT_TIMEOUT ) 37 | { 38 | - /* if a timeout is encountered, continue, 39 | - perhaps we should give up eventually 40 | - */ 41 | + /* if a timeout is encountered, continue to check for data. but give up eventually. */ 42 | + accumulatedTimoutMs += pollTimeoutMs; 43 | + if( accumulatedTimoutMs >= failTimeoutMs ) 44 | + { 45 | + result = paTimedOut; 46 | + break; 47 | + } 48 | } 49 | } 50 | }while( framesRead < frames ); 51 | @@ -3822,7 +3830,9 @@ static PaError WriteStream( PaStream* s, 52 | unsigned long framesProcessed; 53 | signed int hostOutputBufferIndex; 54 | DWORD waitResult; 55 | - DWORD timeout = (unsigned long)(stream->allBuffersDurationMs * 0.5); 56 | + DWORD pollTimeoutMs = stream->allBuffersDurationMs / 2; 57 | + DWORD failTimeoutMs = stream->allBuffersDurationMs * 3; 58 | + DWORD accumulatedTimoutMs = 0; 59 | unsigned int channel, i; 60 | 61 | 62 | @@ -3848,6 +3858,8 @@ static PaError WriteStream( PaStream* s, 63 | do{ 64 | if( CurrentOutputBuffersAreDone( stream ) ) 65 | { 66 | + accumulatedTimoutMs = 0; /* reset failure timer whenever we have a buffer */ 67 | + 68 | if( NoBuffersAreQueued( &stream->output ) ) 69 | { 70 | /** @todo REVIEW: consider what to do if the output 71 | @@ -3895,7 +3907,7 @@ static PaError WriteStream( PaStream* s, 72 | else 73 | { 74 | /* wait for MME to signal that a buffer is available */ 75 | - waitResult = WaitForSingleObject( stream->output.bufferEvent, timeout ); 76 | + waitResult = WaitForSingleObject( stream->output.bufferEvent, pollTimeoutMs ); 77 | if( waitResult == WAIT_FAILED ) 78 | { 79 | result = paUnanticipatedHostError; 80 | @@ -3903,9 +3915,13 @@ static PaError WriteStream( PaStream* s, 81 | } 82 | else if( waitResult == WAIT_TIMEOUT ) 83 | { 84 | - /* if a timeout is encountered, continue, 85 | - perhaps we should give up eventually 86 | - */ 87 | + /* if a timeout is encountered, continue to try to output. but give up eventually. */ 88 | + accumulatedTimoutMs += pollTimeoutMs; 89 | + if( accumulatedTimoutMs >= failTimeoutMs ) 90 | + { 91 | + result = paTimedOut; 92 | + break; 93 | + } 94 | } 95 | } 96 | }while( framesWritten < frames ); 97 | -------------------------------------------------------------------------------- /src/hex-spinner.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #pragma warning(push, 0) 5 | #include 6 | #include 7 | #pragma warning(pop) 8 | 9 | #include "hex-spinner.h" 10 | #include "utils.h" 11 | 12 | Hex_Input::Hex_Input(int x, int y, int w, int h, const char *l) : Fl_Int_Input(x, y, w, h, l) {} 13 | 14 | int Hex_Input::handle_paste_text() { 15 | // Based on Fl_Input_::handletext() for FL_PASTE case 16 | if (readonly()) { 17 | fl_beep(FL_BEEP_ERROR); 18 | return 1; 19 | } 20 | if (!Fl::event_text() || !Fl::event_length()) { 21 | return 1; 22 | } 23 | const char *t = Fl::event_text(); 24 | const char *e = t + Fl::event_length(); 25 | while (e > t && isspace(*(e - 1) & 0xFF)) { e--; } 26 | if (e <= t) { 27 | return 1; 28 | } 29 | while (isspace(*t & 0xFF) && t < e) { t++; } 30 | const char *p = t; 31 | if (*p == '+' || *p == '-') { p++; } 32 | while (isxdigit((unsigned char)(*p) & 0xFF) && p < e) { p++; } 33 | if (p < e) { 34 | fl_beep(FL_BEEP_ERROR); 35 | return 1; 36 | } 37 | return replace(0, size(), t, (int)(e - t)); 38 | } 39 | 40 | int Hex_Input::handle_key() { 41 | // Based on Fl_Input::handle_key() for FL_INT_INPUT type 42 | int del; 43 | if (!Fl::compose(del)) { 44 | return 0; 45 | } 46 | Fl::compose_reset(); 47 | char a = Fl::event_text()[0]; 48 | int ip = std::min(insert_position(), mark()); 49 | if (isxdigit((unsigned char)a) || (!ip && (a == '+' || a == '-'))) { 50 | if (readonly()) { 51 | fl_beep(); 52 | } 53 | else { 54 | replace(insert_position(), mark(), &a, 1); 55 | } 56 | } 57 | return 1; 58 | } 59 | 60 | int Hex_Input::handle(int event) { 61 | // Based on Fl_Input::handle() 62 | if (event == FL_PASTE) { 63 | return handle_paste_text(); 64 | } 65 | if (event == FL_KEYBOARD) { 66 | if (active_r() && window() && this == Fl::belowmouse()) { 67 | window()->cursor(FL_CURSOR_NONE); 68 | } 69 | if (int v = handle_key()) { 70 | return v; 71 | } 72 | } 73 | return Fl_Input::handle(event); 74 | } 75 | 76 | Hex_Spinner::Hex_Spinner(int x, int y, int w, int h, const char *l) : Fl_Group(x, y, w, h, l), 77 | _value(1), _minimum(0x00), _maximum(0xFF), _step(1), _format("%02X"), _input(x, y, w - h / 2 - 2, h), 78 | _up_button(x + w - h / 2 - 2, y, h / 2 + 2, h / 2, "@-42<"), 79 | _down_button(x + w - h / 2 - 2, y + h - h / 2, h / 2 + 2, h / 2, "@-42>") { 80 | end(); 81 | align(FL_ALIGN_LEFT); 82 | _input.value("01"); 83 | _input.when(FL_WHEN_ENTER_KEY | FL_WHEN_RELEASE); 84 | _input.callback((Fl_Callback *)input_cb, this); 85 | _up_button.callback((Fl_Callback *)up_button_cb, this); 86 | _down_button.callback((Fl_Callback *)down_button_cb, this); 87 | } 88 | 89 | void Hex_Spinner::resize(int x, int y, int w, int h) { 90 | Fl_Group::resize(x, y, w, h); 91 | _input.resize(x, y, w - h / 2 - 2, h); 92 | _up_button.resize(x + w - h / 2 - 2, y, h / 2 + 2, h / 2); 93 | _down_button.resize(x + w - h / 2 - 2, y + h - h / 2, h / 2 + 2, h / 2); 94 | } 95 | 96 | int Hex_Spinner::handle(int event) { 97 | switch (event) { 98 | case FL_KEYDOWN: 99 | case FL_SHORTCUT: 100 | if (Fl::event_key() == FL_Up) { 101 | _up_button.do_callback(); 102 | return 1; 103 | } 104 | else if (Fl::event_key() == FL_Down) { 105 | _down_button.do_callback(); 106 | return 1; 107 | } 108 | return 0; 109 | case FL_FOCUS: 110 | return _input.take_focus() ? 1 : 0; 111 | } 112 | return Fl_Group::handle(event); 113 | } 114 | 115 | void Hex_Spinner::update() { 116 | char s[255] = {}; 117 | snprintf(s, sizeof(s), _format, _value); 118 | _input.value(s); 119 | } 120 | 121 | void Hex_Spinner::input_cb(Hex_Input *, Hex_Spinner *hs) { 122 | int v = strtol(hs->_input.value(), NULL, 16); 123 | if (v < hs->_minimum) { 124 | hs->_value = hs->_minimum; 125 | hs->update(); 126 | } 127 | else if (v > hs->_maximum) { 128 | hs->_value = hs->_maximum; 129 | hs->update(); 130 | } 131 | else { 132 | hs->_value = v; 133 | } 134 | hs->set_changed(); 135 | hs->do_callback(); 136 | } 137 | 138 | void Hex_Spinner::up_button_cb(Fl_Repeat_Button *, Hex_Spinner *hs) { 139 | int v = hs->_value + hs->_step; 140 | if (v > hs->_maximum) { v = hs->_minimum; } 141 | hs->_value = v; 142 | hs->update(); 143 | hs->set_changed(); 144 | hs->do_callback(); 145 | } 146 | 147 | void Hex_Spinner::down_button_cb(Fl_Repeat_Button *, Hex_Spinner *hs) { 148 | int v = hs->_value - hs->_step; 149 | if (v < hs->_minimum) { v = hs->_maximum; } 150 | hs->_value = v; 151 | hs->update(); 152 | hs->set_changed(); 153 | hs->do_callback(); 154 | } 155 | -------------------------------------------------------------------------------- /src/option-dialogs.h: -------------------------------------------------------------------------------- 1 | #ifndef OPTION_DIALOGS_H 2 | #define OPTION_DIALOGS_H 3 | 4 | #include "widgets.h" 5 | 6 | #pragma warning(push, 0) 7 | #include 8 | #pragma warning(pop) 9 | 10 | class Option_Dialog { 11 | protected: 12 | int _width; 13 | const char *_title; 14 | bool _has_reset; 15 | bool _canceled; 16 | void *_user_data; 17 | Fl_Double_Window *_dialog; 18 | Fl_Group *_content; 19 | Default_Button *_ok_button; 20 | OS_Button *_cancel_button; 21 | OS_Button *_reset_button; 22 | public: 23 | Option_Dialog(int w, const char *t = NULL); 24 | virtual ~Option_Dialog(); 25 | inline bool canceled(void) const { return _canceled; } 26 | inline void canceled(bool c) { _canceled = c; } 27 | protected: 28 | void initialize(void); 29 | void refresh(bool reset); 30 | void *user_data() const { return _user_data; } 31 | void user_data(void *v) { _user_data = v; } 32 | virtual void initialize_content(void) = 0; 33 | virtual int refresh_content(int ww, int dy, bool reset) = 0; 34 | virtual void set_reset_cb() {} 35 | virtual void finish(void) {} 36 | public: 37 | inline bool initialized(void) const { return !!_dialog; } 38 | void show(Fl_Widget *p, bool reset = true); 39 | private: 40 | static void close_cb(Fl_Widget *, Option_Dialog *od); 41 | static void cancel_cb(Fl_Widget *, Option_Dialog *od); 42 | }; 43 | 44 | class Song_Options_Dialog : public Option_Dialog { 45 | public: 46 | enum class Result { RESULT_OK, RESULT_BAD_TITLE, RESULT_BAD_END, RESULT_BAD_LOOP }; 47 | struct Song_Options { 48 | std::string song_name; 49 | bool looping; 50 | bool channel_1; 51 | bool channel_2; 52 | bool channel_3; 53 | bool channel_4; 54 | int32_t channel_1_loop_tick; 55 | int32_t channel_2_loop_tick; 56 | int32_t channel_3_loop_tick; 57 | int32_t channel_4_loop_tick; 58 | int32_t channel_1_end_tick; 59 | int32_t channel_2_end_tick; 60 | int32_t channel_3_end_tick; 61 | int32_t channel_4_end_tick; 62 | Result result; 63 | }; 64 | private: 65 | OS_Input *_song_name = nullptr; 66 | OS_Check_Button *_looping_checkbox = nullptr; 67 | OS_Check_Button *_channel_1_checkbox = nullptr; 68 | OS_Check_Button *_channel_2_checkbox = nullptr; 69 | OS_Check_Button *_channel_3_checkbox = nullptr; 70 | OS_Check_Button *_channel_4_checkbox = nullptr; 71 | OS_Int_Input *_channel_1_loop_tick = nullptr; 72 | OS_Int_Input *_channel_2_loop_tick = nullptr; 73 | OS_Int_Input *_channel_3_loop_tick = nullptr; 74 | OS_Int_Input *_channel_4_loop_tick = nullptr; 75 | OS_Int_Input *_channel_1_end_tick = nullptr; 76 | OS_Int_Input *_channel_2_end_tick = nullptr; 77 | OS_Int_Input *_channel_3_end_tick = nullptr; 78 | OS_Int_Input *_channel_4_end_tick = nullptr; 79 | OS_Check_Button *_synchronize_checkbox = nullptr; 80 | OS_Radio_Button *_beats_radio = nullptr; 81 | OS_Radio_Button *_ticks_radio = nullptr; 82 | public: 83 | Song_Options_Dialog(const char *t); 84 | ~Song_Options_Dialog(); 85 | Song_Options get_options(); 86 | void set_options(const Song_Options &options); 87 | const char *get_error_message(Result r); 88 | protected: 89 | void initialize_content(void); 90 | int refresh_content(int ww, int dy, bool reset); 91 | private: 92 | static void looping_checkbox_cb(OS_Check_Button *c, Song_Options_Dialog *sod); 93 | static void channel_checkbox_cb(OS_Check_Button *c, Song_Options_Dialog *sod); 94 | static void channel_loop_tick_cb(OS_Int_Input *i, Song_Options_Dialog *sod); 95 | static void channel_end_tick_cb(OS_Int_Input *i, Song_Options_Dialog *sod); 96 | static void synchronize_checkbox_cb(OS_Check_Button *c, Song_Options_Dialog *sod); 97 | static void beats_ticks_radio_cb(OS_Radio_Button *r, Song_Options_Dialog *sod); 98 | }; 99 | 100 | class Ruler_Config_Dialog : public Option_Dialog { 101 | public: 102 | struct Ruler_Options { 103 | int beats_per_measure = 4; 104 | int steps_per_beat = 4; 105 | int ticks_per_step = 12; 106 | int pickup_offset = 0; 107 | }; 108 | private: 109 | OS_Spinner *_beats_per_measure = nullptr; 110 | OS_Spinner *_steps_per_beat = nullptr; 111 | OS_Spinner *_ticks_per_step = nullptr; 112 | OS_Spinner *_pickup_offset = nullptr; 113 | public: 114 | Ruler_Config_Dialog(const char *t); 115 | ~Ruler_Config_Dialog(); 116 | Ruler_Options get_options(); 117 | void set_options(const Ruler_Options &options); 118 | protected: 119 | void initialize_content(void); 120 | int refresh_content(int ww, int dy, bool reset); 121 | void set_reset_cb(); 122 | private: 123 | static void ruler_config_cb(Fl_Widget *w, Ruler_Config_Dialog *rcd); 124 | static void reset_button_cb(Fl_Widget *w, Ruler_Config_Dialog *rcd); 125 | }; 126 | 127 | #endif 128 | -------------------------------------------------------------------------------- /src/icons.h: -------------------------------------------------------------------------------- 1 | #ifndef ICONS_H 2 | #define ICONS_H 3 | 4 | #pragma warning(push, 0) 5 | #include 6 | #pragma warning(pop) 7 | 8 | #include "blank.xpm" 9 | #include "brush.xpm" 10 | #include "brush-cmy.xpm" 11 | #include "decrease-spacing.xpm" 12 | #include "delete.xpm" 13 | #include "down.xpm" 14 | #include "down-down.xpm" 15 | #include "four.xpm" 16 | #include "glue-dark.xpm" 17 | #include "glue-light.xpm" 18 | #include "increase-spacing.xpm" 19 | #include "keys.xpm" 20 | #include "left.xpm" 21 | #include "loop.xpm" 22 | #include "minus.xpm" 23 | #include "new.xpm" 24 | #include "notes.xpm" 25 | #include "one.xpm" 26 | #include "open.xpm" 27 | #include "pause.xpm" 28 | #include "pencil.xpm" 29 | #include "pencil-blue.xpm" 30 | #include "pencil-brown.xpm" 31 | #include "pencil-green.xpm" 32 | #include "pencil-red.xpm" 33 | #include "play.xpm" 34 | #include "plus.xpm" 35 | #include "redo.xpm" 36 | #include "right.xpm" 37 | #include "ruler.xpm" 38 | #include "save.xpm" 39 | #include "save-as.xpm" 40 | #include "scroll-dark.xpm" 41 | #include "scroll-light.xpm" 42 | #include "snip.xpm" 43 | #include "split-dark.xpm" 44 | #include "split-light.xpm" 45 | #include "stop.xpm" 46 | #include "three.xpm" 47 | #include "two.xpm" 48 | #include "undo.xpm" 49 | #include "up.xpm" 50 | #include "up-up.xpm" 51 | #include "verify.xpm" 52 | #include "zoom-in.xpm" 53 | #include "zoom-out.xpm" 54 | 55 | struct Scalable_Pixmap { 56 | Fl_Pixmap pixmap; 57 | Fl_Image *image = nullptr; 58 | 59 | Scalable_Pixmap(const char * const * data) : pixmap(data) {} 60 | ~Scalable_Pixmap() { if (image) delete image; } 61 | 62 | Fl_Image *get(float scale) { 63 | #if defined(_WIN32) || defined(__APPLE__) 64 | return &pixmap; 65 | #else 66 | if (scale == 1.0f) return &pixmap; 67 | if (!image) { 68 | int W = pixmap.w(), H = pixmap.h(); 69 | Fl_Image *temp = new Fl_RGB_Image(&pixmap); 70 | Fl_Image::RGB_scaling(FL_RGB_SCALING_BILINEAR); 71 | image = temp->copy(2 * W, 2 * H); 72 | Fl_Image::RGB_scaling(FL_RGB_SCALING_NEAREST); 73 | delete temp; 74 | image->scale(W, H); 75 | } 76 | return image; 77 | #endif 78 | } 79 | }; 80 | 81 | static Fl_Pixmap BLANK_ICON(BLANK_XPM); 82 | 83 | static Scalable_Pixmap BRUSH_ICON(BRUSH_XPM); 84 | static Scalable_Pixmap BRUSH_CMY_ICON(BRUSH_CMY_XPM); 85 | static Scalable_Pixmap DECREASE_SPACING_ICON(DECREASE_SPACING_XPM); 86 | static Scalable_Pixmap DELETE_ICON(DELETE_XPM); 87 | static Scalable_Pixmap DOWN_ICON(DOWN_XPM); 88 | static Scalable_Pixmap DOWN_DOWN_ICON(DOWN_DOWN_XPM); 89 | static Scalable_Pixmap FOUR_ICON(FOUR_XPM); 90 | static Scalable_Pixmap GLUE_DARK_ICON(GLUE_DARK_XPM); 91 | static Scalable_Pixmap GLUE_LIGHT_ICON(GLUE_LIGHT_XPM); 92 | static Scalable_Pixmap INCREASE_SPACING_ICON(INCREASE_SPACING_XPM); 93 | static Scalable_Pixmap KEYS_ICON(KEYS_XPM); 94 | static Scalable_Pixmap LEFT_ICON(LEFT_XPM); 95 | static Scalable_Pixmap LOOP_ICON(LOOP_XPM); 96 | static Scalable_Pixmap MINUS_ICON(MINUS_XPM); 97 | static Scalable_Pixmap NEW_ICON(NEW_XPM); 98 | static Scalable_Pixmap NOTES_ICON(NOTES_XPM); 99 | static Scalable_Pixmap ONE_ICON(ONE_XPM); 100 | static Scalable_Pixmap OPEN_ICON(OPEN_XPM); 101 | static Scalable_Pixmap PAUSE_ICON(PAUSE_XPM); 102 | static Scalable_Pixmap PENCIL_ICON(PENCIL_XPM); 103 | static Scalable_Pixmap PENCIL_BLUE_ICON(PENCIL_BLUE_XPM); 104 | static Scalable_Pixmap PENCIL_BROWN_ICON(PENCIL_BROWN_XPM); 105 | static Scalable_Pixmap PENCIL_GREEN_ICON(PENCIL_GREEN_XPM); 106 | static Scalable_Pixmap PENCIL_RED_ICON(PENCIL_RED_XPM); 107 | static Scalable_Pixmap PLAY_ICON(PLAY_XPM); 108 | static Scalable_Pixmap PLUS_ICON(PLUS_XPM); 109 | static Scalable_Pixmap REDO_ICON(REDO_XPM); 110 | static Scalable_Pixmap RIGHT_ICON(RIGHT_XPM); 111 | static Scalable_Pixmap RULER_ICON(RULER_XPM); 112 | static Scalable_Pixmap SAVE_ICON(SAVE_XPM); 113 | static Scalable_Pixmap SAVE_AS_ICON(SAVE_AS_XPM); 114 | static Scalable_Pixmap SCROLL_DARK_ICON(SCROLL_DARK_XPM); 115 | static Scalable_Pixmap SCROLL_LIGHT_ICON(SCROLL_LIGHT_XPM); 116 | static Scalable_Pixmap SNIP_ICON(SNIP_XPM); 117 | static Scalable_Pixmap SPLIT_DARK_ICON(SPLIT_DARK_XPM); 118 | static Scalable_Pixmap SPLIT_LIGHT_ICON(SPLIT_LIGHT_XPM); 119 | static Scalable_Pixmap STOP_ICON(STOP_XPM); 120 | static Scalable_Pixmap THREE_ICON(THREE_XPM); 121 | static Scalable_Pixmap TWO_ICON(TWO_XPM); 122 | static Scalable_Pixmap UNDO_ICON(UNDO_XPM); 123 | static Scalable_Pixmap UP_ICON(UP_XPM); 124 | static Scalable_Pixmap UP_UP_ICON(UP_UP_XPM); 125 | static Scalable_Pixmap VERIFY_ICON(VERIFY_XPM); 126 | static Scalable_Pixmap ZOOM_IN_ICON(ZOOM_IN_XPM); 127 | static Scalable_Pixmap ZOOM_OUT_ICON(ZOOM_OUT_XPM); 128 | 129 | bool make_deimage(Fl_Widget *wgt, Fl_Image *image = nullptr) { 130 | if (!wgt || !wgt->image()) { 131 | return false; 132 | } 133 | if (!image) image = wgt->image(); 134 | Fl_Image *deimg = image->copy(); 135 | if (!deimg) { 136 | return false; 137 | } 138 | deimg->desaturate(); 139 | deimg->color_average(FL_GRAY, 0.5f); 140 | if (wgt->deimage()) { 141 | delete wgt->deimage(); 142 | } 143 | wgt->deimage(deimg); 144 | return true; 145 | } 146 | 147 | #endif 148 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | OS_MAC := 2 | ifeq ($(shell uname -s),Darwin) 3 | OS_MAC := 1 4 | endif 5 | 6 | DESTDIR = 7 | PREFIX = /usr/local 8 | 9 | APPNAME = Crystal Tracker 10 | crystaltracker = crystaltracker 11 | crystaltrackerd = crystaltrackerd 12 | 13 | ifdef OS_MAC 14 | CXX ?= clang++ 15 | else 16 | CXX ?= g++ 17 | endif 18 | LD = $(CXX) 19 | RM = rm -rf 20 | 21 | srcdir = src 22 | resdir = res 23 | tmpdir = tmp 24 | debugdir = tmp/debug 25 | bindir = bin 26 | 27 | pkg-config = PKG_CONFIG_PATH=lib/pkgconfig pkg-config 28 | fltk-config = $(bindir)/fltk-config 29 | 30 | CXXFLAGS := -std=c++17 -I$(srcdir) -I$(resdir) $(shell $(fltk-config) --use-images --cxxflags) $(CXXFLAGS) 31 | 32 | ifdef OS_MAC 33 | PORTAUDIOLDLIBS := $(shell $(pkg-config) --static --libs-only-other portaudiocpp) 34 | FLTKLDLIBS := $(shell $(fltk-config) --use-images --ldstaticflags) 35 | else 36 | PORTAUDIOLDLIBS := $(shell $(pkg-config) --static --libs-only-l portaudiocpp | sed -E "s/-lportaudio(cpp)?//g") 37 | FLTKLDLIBS := $(shell $(fltk-config) --use-images --ldstaticflags) $(shell $(pkg-config) --libs libpng xpm) 38 | endif 39 | 40 | LDFLAGS := lib/libportaudio.a lib/libportaudiocpp.a lib/libopenmpt.a $(PORTAUDIOLDLIBS) $(FLTKLDLIBS) $(LDFLAGS) 41 | 42 | RELEASEFLAGS = -DNDEBUG -O3 -flto 43 | DEBUGFLAGS = -DDEBUG -D_DEBUG -O0 -g -ggdb3 -Wall -Wextra -pedantic -Wno-unknown-pragmas -Wno-sign-compare -Wno-unused-parameter 44 | 45 | COMMON = $(wildcard $(srcdir)/*.h) $(wildcard $(resdir)/*.xpm) $(resdir)/help.html 46 | SOURCES = $(wildcard $(srcdir)/*.cpp) 47 | OBJECTS = $(SOURCES:$(srcdir)/%.cpp=$(tmpdir)/%.o) 48 | DEBUGOBJECTS = $(SOURCES:$(srcdir)/%.cpp=$(debugdir)/%.o) 49 | 50 | ifdef OS_MAC 51 | SOURCES_MAC = $(wildcard $(srcdir)/*.mm) 52 | OBJECTS += $(SOURCES_MAC:$(srcdir)/%.mm=$(tmpdir)/%.o) 53 | DEBUGOBJECTS += $(SOURCES_MAC:$(srcdir)/%.mm=$(debugdir)/%.o) 54 | endif 55 | 56 | TARGET = $(bindir)/$(crystaltracker) 57 | DEBUGTARGET = $(bindir)/$(crystaltrackerd) 58 | 59 | .PHONY: all $(crystaltracker) $(crystaltrackerd) release debug clean appdir appdmg install uninstall 60 | 61 | .SUFFIXES: .o .cpp 62 | 63 | all: $(crystaltracker) 64 | 65 | $(crystaltracker): release 66 | $(crystaltrackerd): debug 67 | 68 | release: CXXFLAGS := $(RELEASEFLAGS) $(CXXFLAGS) 69 | release: $(TARGET) 70 | 71 | debug: CXXFLAGS := $(DEBUGFLAGS) $(CXXFLAGS) 72 | debug: $(DEBUGTARGET) 73 | 74 | $(TARGET): $(OBJECTS) 75 | @mkdir -p $(@D) 76 | $(LD) -o $@ $^ $(CXXFLAGS) $(LDFLAGS) 77 | 78 | $(DEBUGTARGET): $(DEBUGOBJECTS) 79 | @mkdir -p $(@D) 80 | $(LD) -o $@ $^ $(CXXFLAGS) $(LDFLAGS) 81 | 82 | $(tmpdir)/%.o: $(srcdir)/%.cpp $(COMMON) 83 | @mkdir -p $(@D) 84 | $(CXX) -c $(CXXFLAGS) -o $@ $< 85 | 86 | $(debugdir)/%.o: $(srcdir)/%.cpp $(COMMON) 87 | @mkdir -p $(@D) 88 | $(CXX) -c $(CXXFLAGS) -o $@ $< 89 | 90 | ifdef OS_MAC 91 | $(tmpdir)/%.o: $(srcdir)/%.mm $(COMMON) 92 | @mkdir -p $(@D) 93 | $(CXX) -c $(CXXFLAGS) -o $@ $< 94 | 95 | $(debugdir)/%.o: $(srcdir)/%.mm $(COMMON) 96 | @mkdir -p $(@D) 97 | $(CXX) -c $(CXXFLAGS) -o $@ $< 98 | endif 99 | 100 | clean: 101 | $(RM) $(TARGET) $(DEBUGTARGET) $(OBJECTS) $(DEBUGOBJECTS) 102 | 103 | ifdef OS_MAC 104 | APPDIR = "$(bindir)/$(APPNAME).app" 105 | APPDMG = "$(bindir)/$(APPNAME).dmg" 106 | CONTENTS = $(APPDIR)/Contents 107 | 108 | appdir: release 109 | rm -rf $(APPDIR) 110 | install -d $(CONTENTS)/macOS $(CONTENTS)/Resources 111 | install -m755 $(TARGET) $(CONTENTS)/macOS/crystaltracker 112 | install -m644 $(resdir)/app.icns $(CONTENTS)/Resources/AppIcon.icns 113 | install -m644 $(resdir)/Info.plist $(CONTENTS)/Info.plist 114 | printf 'APPL????' > $(CONTENTS)/PkgInfo 115 | 116 | create-dmg = create-dmg 117 | 118 | appdmg: appdir 119 | rm -f $(APPDMG) 120 | rm -rf $(APPDMG).dir/ 121 | mkdir -p $(APPDMG).dir 122 | cp -a $(APPDIR) $(APPDMG).dir/ 123 | $(create-dmg) \ 124 | --volname "$(APPNAME)" \ 125 | --volicon $(resdir)/app.icns \ 126 | --window-pos 200 120 \ 127 | --window-size 800 400 \ 128 | --icon-size 100 \ 129 | --icon "$(APPNAME).app" 200 190 \ 130 | --hide-extension "$(APPNAME).app" \ 131 | --app-drop-link 600 185 \ 132 | $(APPDMG) $(APPDMG).dir/ 133 | rm -rf $(APPDMG).dir/ 134 | 135 | install: appdir 136 | rm -rf "/Applications/$(APPNAME).app" 137 | cp -av $(APPDIR) "/Applications/$(APPNAME).app" 138 | rm -rf $(APPDIR) 139 | 140 | uninstall: 141 | rm -rf "/Applications/$(APPNAME).app" 142 | else 143 | DESKTOP = "$(DESTDIR)$(PREFIX)/share/applications/$(APPNAME).desktop" 144 | 145 | install: release 146 | mkdir -p $(DESTDIR)$(PREFIX)/bin 147 | cp $(TARGET) $(DESTDIR)$(PREFIX)/bin/$(crystaltracker) 148 | mkdir -p $(DESTDIR)$(PREFIX)/share/pixmaps 149 | cp $(resdir)/app.xpm $(DESTDIR)$(PREFIX)/share/pixmaps/crystaltracker48.xpm 150 | cp $(resdir)/app-icon.xpm $(DESTDIR)$(PREFIX)/share/pixmaps/crystaltracker16.xpm 151 | mkdir -p $(DESTDIR)$(PREFIX)/share/applications 152 | echo "[Desktop Entry]" > $(DESKTOP) 153 | echo "Name=$(APPNAME)" >> $(DESKTOP) 154 | echo "Comment=Edit pokecrystal music and sound effects" >> $(DESKTOP) 155 | echo "Icon=$(PREFIX)/share/pixmaps/crystaltracker48.xpm" >> $(DESKTOP) 156 | echo "Exec=$(PREFIX)/bin/$(crystaltracker)" >> $(DESKTOP) 157 | echo "Type=Application" >> $(DESKTOP) 158 | echo "Terminal=false" >> $(DESKTOP) 159 | 160 | uninstall: 161 | rm -f $(DESTDIR)$(PREFIX)/bin/$(crystaltracker) 162 | rm -f $(DESTDIR)$(PREFIX)/share/pixmaps/crystaltracker48.xpm 163 | rm -f $(DESTDIR)$(PREFIX)/share/pixmaps/crystaltracker16.xpm 164 | rm -f $(DESKTOP) 165 | endif 166 | -------------------------------------------------------------------------------- /src/modal-dialog.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #pragma warning(push, 0) 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #pragma warning(pop) 12 | 13 | #include "themes.h" 14 | #include "widgets.h" 15 | #include "modal-dialog.h" 16 | 17 | #include "warning.xpm" 18 | #include "error.xpm" 19 | #include "success.xpm" 20 | #include "app.xpm" 21 | 22 | Fl_Pixmap Modal_Dialog::SUCCESS_SHIELD_ICON(SUCCESS_XPM); 23 | Fl_Pixmap Modal_Dialog::WARNING_SHIELD_ICON(WARNING_XPM); 24 | Fl_Pixmap Modal_Dialog::ERROR_SHIELD_ICON(ERROR_XPM); 25 | Fl_Pixmap Modal_Dialog::PROGRAM_ICON(APP_XPM); 26 | 27 | Modal_Dialog::Modal_Dialog(Fl_Window *top, const char *t, Icon c, bool cancel) : _icon_type(c), 28 | _title(t), _subject(), _message(), _min_w(0), _max_w(1000), _canceled(cancel), 29 | _top_window(top), _dialog(NULL), _icon(NULL), _heading(NULL), _body(NULL), _ok_button(NULL), _cancel_button(NULL) {} 30 | 31 | Modal_Dialog::~Modal_Dialog() { 32 | _top_window = NULL; 33 | delete _dialog; 34 | } 35 | 36 | void Modal_Dialog::initialize() { 37 | if (_dialog) { return; } 38 | Fl_Group *prev_current = Fl_Group::current(); 39 | Fl_Group::current(NULL); 40 | // Populate dialog 41 | _dialog = new Fl_Double_Window(0, 0, 0, 0, _title.c_str()); 42 | _icon = new Fl_Box(0, 0, 0, 0); 43 | _heading = new Label(0, 0, 0, 0, _subject.c_str()); 44 | _body = new Label(0, 0, 0, 0); 45 | _ok_button = new Default_Button(0, 0, 0, 0, "OK"); 46 | _cancel_button = _canceled ? new OS_Button(0, 0, 0, 0, "Cancel") : NULL; 47 | _canceled = false; 48 | _dialog->end(); 49 | // Initialize dialog 50 | _dialog->box(OS_BG_BOX); 51 | _dialog->resizable(NULL); 52 | _dialog->callback((Fl_Callback *)cancel_cb, this); 53 | _dialog->set_modal(); 54 | // Initialize dialog's children 55 | _icon->align(FL_ALIGN_CENTER | FL_ALIGN_INSIDE | FL_ALIGN_CLIP); 56 | _heading->labelsize(OS_FONT_SIZE + 4); 57 | _heading->align(FL_ALIGN_TOP | FL_ALIGN_INSIDE | FL_ALIGN_CLIP); 58 | _body->align(FL_ALIGN_TOP_LEFT | FL_ALIGN_INSIDE | FL_ALIGN_WRAP); 59 | _ok_button->tooltip("OK (Enter)"); 60 | _ok_button->callback((Fl_Callback *)close_cb, this); 61 | if (_cancel_button) { 62 | _cancel_button->shortcut(FL_Escape); 63 | _cancel_button->tooltip("Cancel (Esc)"); 64 | _cancel_button->callback((Fl_Callback *)cancel_cb, this); 65 | } 66 | Fl_Group::current(prev_current); 67 | } 68 | 69 | void Modal_Dialog::refresh() { 70 | _canceled = false; 71 | // Refresh widget labels 72 | _heading->label(_subject.c_str()); 73 | _dialog->label(_title.c_str()); 74 | _body->label(_message.c_str()); 75 | // Refresh icon 76 | switch (_icon_type) { 77 | case Icon::NO_ICON: 78 | _icon->image(NULL); 79 | break; 80 | case Icon::SUCCESS_ICON: 81 | _icon->image(SUCCESS_SHIELD_ICON); 82 | break; 83 | case Icon::WARNING_ICON: 84 | _icon->image(WARNING_SHIELD_ICON); 85 | break; 86 | case Icon::ERROR_ICON: 87 | _icon->image(ERROR_SHIELD_ICON); 88 | break; 89 | case Icon::APP_ICON: 90 | _icon->image(PROGRAM_ICON); 91 | break; 92 | } 93 | // Refresh widget positions and sizes 94 | int bwd = (_icon_type == Icon::NO_ICON ? 0 : 60) + 20; 95 | fl_font(_heading->labelfont(), _heading->labelsize()); 96 | int hw = _max_w - bwd, hh = 0; 97 | fl_measure(_heading->label(), hw, hh); 98 | fl_font(_body->labelfont(), _body->labelsize()); 99 | int bw = _max_w - bwd, bh = 0; 100 | fl_measure(_body->label(), bw, bh); 101 | int w = std::max(std::max(bw, hw) + bwd + OS_FONT_SIZE, _min_w), h = 10; 102 | int ww = w - 20; 103 | int heading_h = 25; 104 | int btn_w = 80, btn_h = 22; 105 | if (_icon_type == Icon::NO_ICON) { 106 | _icon->resize(0, 0, 0, 0); 107 | if (_subject.empty()) { 108 | _heading->resize(0, 0, 0, 0); 109 | } 110 | else { 111 | _heading->resize(10, h, ww, heading_h); 112 | h += _heading->h() + 10; 113 | } 114 | _body->resize(10, h, ww, bh); 115 | h += _body->h() + 10; 116 | } 117 | else { 118 | _icon->resize(10, h, 50, 50); 119 | if (_subject.empty()) { 120 | _heading->resize(0, 0, 0, 0); 121 | } 122 | else { 123 | _heading->resize(70, h, ww-60, heading_h); 124 | h += _heading->h() + 10; 125 | } 126 | _body->resize(70, h, ww-60, bh); 127 | h += _body->h() + 10; 128 | h = std::max(h, 70); 129 | } 130 | #ifdef _WIN32 131 | if (_cancel_button) { 132 | _ok_button->resize(w-btn_w-14-btn_w-10, h, btn_w, btn_h); 133 | _cancel_button->resize(w-btn_w-10, h, btn_w, btn_h); 134 | } 135 | else { 136 | _ok_button->resize(w-btn_w-10, h, btn_w, btn_h); 137 | } 138 | #else 139 | if (_cancel_button) { 140 | _cancel_button->resize(w-btn_w-14-btn_w-10, h, btn_w, btn_h); 141 | } 142 | _ok_button->resize(w-btn_w-10, h, btn_w, btn_h); 143 | #endif 144 | h += _ok_button->h() + 10; 145 | _dialog->size_range(w, h, w, h); 146 | _dialog->size(w, h); 147 | _dialog->redraw(); 148 | } 149 | 150 | void Modal_Dialog::show(const Fl_Widget *p) { 151 | initialize(); 152 | refresh(); 153 | Fl_Window *prev_grab = Fl::grab(); 154 | Fl::grab(NULL); 155 | int x = p->x() + (p->w() - _dialog->w()) / 2; 156 | int y = p->y() + (p->h() - _dialog->h()) / 2; 157 | _dialog->position(x, y); 158 | _dialog->show(); 159 | #ifdef _WIN32 160 | // Flash taskbar button 161 | // 162 | HWND top_hwnd = fl_xid(_top_window); 163 | HWND fgw = GetForegroundWindow(); 164 | if (fgw != top_hwnd && fgw != fl_xid(_dialog)) { 165 | FLASHWINFO fwi; 166 | fwi.cbSize = sizeof(fwi); 167 | fwi.hwnd = top_hwnd; 168 | fwi.dwFlags = FLASHW_ALL; 169 | fwi.dwTimeout = 0; 170 | fwi.uCount = 3; 171 | FlashWindowEx(&fwi); 172 | } 173 | #endif 174 | while (_dialog->shown()) { Fl::wait(); } 175 | Fl::grab(prev_grab); 176 | } 177 | 178 | void Modal_Dialog::close_cb(Fl_Widget *, Modal_Dialog *md) { 179 | md->_dialog->hide(); 180 | } 181 | 182 | void Modal_Dialog::cancel_cb(Fl_Widget *w, Modal_Dialog *md) { 183 | md->_canceled = true; 184 | close_cb(w, md); 185 | } 186 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Changelog 2 | ========= 3 | 4 | ### Crystal Tracker v0.8.9 (2024-12-12) 5 | 6 | * Minor bug fixes. 7 | 8 | ### Crystal Tracker v0.8.8 (2024-11-27) 9 | 10 | * Improve drawing on scaled displays and other minor improvements. 11 | 12 | ### Crystal Tracker v0.8.7 (2024-11-23) 13 | 14 | * Improve drawing on scaled displays. 15 | 16 | ### Crystal Tracker v0.8.6 (2024-11-17) 17 | 18 | * Fix a few more fullscreen bugs and other minor improvements. 19 | 20 | ### Crystal Tracker v0.8.5 (2024-11-08) 21 | 22 | * Fix a few fullscreen bugs and other minor improvements. 23 | 24 | ### Crystal Tracker v0.8.4 (2024-10-29) 25 | 26 | * Minor improvements. 27 | 28 | ### Crystal Tracker v0.8.3 (2024-10-20) 29 | 30 | * Add Move Left/Right for loop and call boxes. 31 | * Add warning for songs that desync badly. 32 | 33 | ### Crystal Tracker v0.8.2 (2024-10-16) 34 | 35 | * Add Bookmarks. 36 | * Home/End now scroll to first/last selected note. 37 | * Improve drawing on scaled displays. 38 | 39 | ### Crystal Tracker v0.8.1 (2024-09-08) 40 | 41 | * Minor bug fixes. 42 | 43 | ### Crystal Tracker v0.8.0 (2024-08-31) 44 | 45 | * Add Format Painter: Copy note properties from one note to another. 46 | * Add full support for stereo panning command. 47 | * Add BPM display to status bar. 48 | * Add Duplicate Note to Edit menu. 49 | * Add Ctrl+F3 for dumping .it file. 50 | * Add Ctrl+\ for centering the playhead in the middle of the screen. 51 | * Add confirmation dialog for clearing recent songs. 52 | * Pencil icon now matches selected channel color. 53 | * Slightly emphasize beat lines when ruler is active. 54 | * Increase max grid width to 48. 55 | * Selecting notes with Enter key: Select the note to the left of the playhead if Alt is also pressed. 56 | * Keep Skip Backward/Forward enabled while the song is playing, which now act like rewind and fast-forward. 57 | * Minor bug fixes. 58 | 59 | ### Crystal Tracker v0.7.0 (2024-06-08) 60 | 61 | * Better Pencil: Click and drag to pick note length. 62 | * Better tempo handling: 63 | * Visualize all tempo changes with dark purple or dark yellow lines. 64 | * Automatically split rests according to first channel tempo changes. 65 | * Disable tempo property input box for non-first channels. 66 | * Add Postprocess Channel to Edit menu to trigger automatic rest splitting on-demand. 67 | * Better Measure Ruler: 68 | * Add ruler config dialog to set the time signature of the ruler to match the song, including pickup notes. 69 | * Remember ruler config for recent files. 70 | * Increase max grid width from 16 to 32. 71 | * Better toolbar: 72 | * Add loop verification toolbar button. 73 | * Add toolbar buttons for moving/resizing notes. 74 | * Add Delete, Snip, Split, and Glue to toolbar. 75 | * Better zoom: Add extra zoom-out zoom level. 76 | * Better playback: Slightly improve vibrato playback for slow tempos. 77 | * Better editing: Add Insert Rest to Edit menu; useful for widening inner loops and calls. 78 | * Update to FLTK 1.4-alpha. 79 | 80 | ### Crystal Tracker v0.6.3 (2024-05-19) 81 | 82 | * Fix a few possible deadlocks on Mac and Windows. 83 | 84 | ### Crystal Tracker v0.6.2 (2024-05-03) 85 | 86 | * Bugfix: Calls can no longer be created across the main loop line. 87 | * Bugfix: When creating a loop, the octave of the notes *after* the loop is now always correctly preserved. 88 | * Minor file parsing fixes/improvements. 89 | 90 | ### Crystal Tracker v0.6.1 (2024-02-12) 91 | 92 | * Bugfix: Calls containing loops cannot be inserted into other loops. 93 | 94 | ### Crystal Tracker v0.6.0 (2024-02-11) 95 | 96 | * Add right-click context menu with loop and call functions. 97 | * Loops: Reduce, Extend, Unroll, Create 98 | * Calls: Delete, Unpack, Create, Insert 99 | * Add note label toggle. 100 | * Add key label toggle, note label toggle, and ruler toggle to toolbar. 101 | * Shorten and Lengthen now drag the cursor if the cursor is aligned to the right edge of a selected note. 102 | * Clicking into the measure ruler now sets the playhead. 103 | * Placing new notes now copies the speed of the previous note when possible. 104 | * Note properties panel now supports alt shortcut keyboard navigation. 105 | * Add Select Invert function. 106 | * Unreferenced labels are now visualized in the timeline with a gray line. 107 | * Add gray flag for misc/other settings changes. 108 | * Add warning when labels are used by multiple channels. 109 | * Add error when loading a song that contains unsupported rgbds keywords (eg, rept). 110 | * Add warning when note properties differ on the second iteration of the main loop. 111 | * Add menu option checkbox to disable main loop verification. 112 | * Increase max undo limit from 100 to 256. 113 | 114 | ### Crystal Tracker v0.5.0 (2024-01-08) 115 | 116 | * Add song resizing. 117 | * Allow specifying song lengths in beats instead of ticks. 118 | * New app icon. 119 | 120 | ### Crystal Tracker v0.4.0 (2023-12-14) 121 | 122 | * Add rectangle select. 123 | * Improve tempo to bpm approximation for playback. 124 | * Improve playback of noise samples that never fade out. (See: Pinball's seelstage.asm) 125 | * More intuitive keyboard controls for the Note Properties panel. 126 | * Escape now aborts note property changes. 127 | * Enter now also deselects the textbox. 128 | * Fix playback of drum samples for songs that also use inline waves. 129 | * Warn for songs that use too many inline waves. 130 | * Using too many drums is now a warning and not an error. 131 | * Allow setting drumkit from Note Properties panel. 132 | 133 | ### Crystal Tracker v0.3.0 (2023-05-28) 134 | 135 | * Improve accuracy of volume fade during playback. 136 | * Add 64-bit build on Windows. 137 | * New app icon. Thanks to nyanpasu64 for the pixelart versions of the icon. 138 | 139 | ### Crystal Tracker v0.2.1 (2023-03-19) 140 | 141 | * Slightly improved quality of channel 4 playback. 142 | * drumkits.asm supports `dr` as well as `dw`. 143 | 144 | ### Crystal Tracker v0.2.0 (2023-03-18) 145 | 146 | * Parse drumkits and synthesize channel 4 playback. 147 | * Validate usage of 0-arg and 1-arg `toggle_noise` commands. 148 | * Pressing Escape to deselect a textbox no longer also resets the playhead. 149 | 150 | ### Crystal Tracker v0.1.0 (2023-03-05) 151 | 152 | * Initial release. 153 | -------------------------------------------------------------------------------- /src/utils.cpp: -------------------------------------------------------------------------------- 1 | #ifdef __APPLE__ 2 | #define _DARWIN_USE_64_BIT_INODE 3 | #endif 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #pragma warning(push, 0) 12 | #include 13 | #include 14 | #pragma warning(pop) 15 | 16 | #include "utils.h" 17 | 18 | const std::string whitespace(" \f\n\r\t\v"); 19 | 20 | const std::string hex("0123456789abcdefABCDEF"); 21 | const std::string decimal("0123456789"); 22 | const std::string octal("01234567"); 23 | const std::string binary("01"); 24 | 25 | static bool cmp_ignore_case(const char &a, const char &b) { 26 | return tolower(a) == tolower(b); 27 | } 28 | 29 | bool equals_ignore_case(std::string_view s, std::string_view p) { 30 | return s.size() == p.size() && std::equal(RANGE(s), RANGE(p), cmp_ignore_case); 31 | } 32 | 33 | bool starts_with(std::string_view s, std::string_view p) { 34 | return !s.compare(0, p.size(), p); 35 | } 36 | 37 | bool ends_with(std::string_view s, std::string_view p) { 38 | return s.size() >= p.size() && !s.compare(s.size() - p.size(), p.size(), p); 39 | } 40 | 41 | bool ends_with_ignore_case(std::string_view s, std::string_view p) { 42 | if (s.size() < p.size()) { return false; } 43 | std::string_view ss = s.substr(s.size() - p.size()); 44 | return std::equal(RANGE(ss), RANGE(p), cmp_ignore_case); 45 | } 46 | 47 | bool ends_with_ignore_case(std::wstring_view s, std::wstring_view p) { 48 | if (s.size() < p.size()) { return false; } 49 | std::wstring_view ss = s.substr(s.size() - p.size()); 50 | return std::equal(RANGE(ss), RANGE(p), [](const wchar_t &a, const wchar_t &b) { 51 | return towlower(a) == towlower(b); 52 | }); 53 | } 54 | 55 | bool is_indented(std::string_view s) { 56 | if (s.size() == 0) { return false; } 57 | int first = s[0]; 58 | return first == ' ' || first == '\t'; 59 | } 60 | 61 | bool is_hex(std::string_view s) { 62 | return s.find_first_not_of(hex) == std::string::npos; 63 | } 64 | 65 | bool is_decimal(std::string_view s) { 66 | return s.find_first_not_of(decimal) == std::string::npos; 67 | } 68 | 69 | bool is_octal(std::string_view s) { 70 | return s.find_first_not_of(octal) == std::string::npos; 71 | } 72 | 73 | bool is_binary(std::string_view s) { 74 | return s.find_first_not_of(binary) == std::string::npos; 75 | } 76 | 77 | void trim(std::string &s, const std::string &t) { 78 | std::string::size_type p = s.find_first_not_of(t); 79 | s.erase(0, p); 80 | p = s.find_last_not_of(t); 81 | s.erase(p + 1); 82 | } 83 | 84 | void rtrim(std::string &s, const std::string &t) { 85 | std::string::size_type p = s.find_last_not_of(t); 86 | s.erase(p + 1); 87 | } 88 | 89 | void lowercase(std::string &s) { 90 | std::transform(RANGE(s), s.begin(), [](char c) { return (char)tolower(c); }); 91 | } 92 | 93 | bool leading_macro(std::istringstream &iss, std::string ¯o, const char *v) { 94 | int first = iss.peek(); 95 | bool indented = first == ' ' || first == '\t'; 96 | if (indented) { iss >> std::ws >> macro >> std::ws; } 97 | return indented && (!v || macro == v); 98 | } 99 | 100 | void remove_comment(std::string &s) { 101 | size_t p = s.find(';'); 102 | if (p != std::string::npos) { 103 | s.erase(p); 104 | } 105 | } 106 | 107 | void remove_suffix(const char *n, char *s) { 108 | strcpy(s, n); 109 | char *dot = strchr(s, '.'); 110 | if (dot) { *dot = '\0'; } 111 | } 112 | 113 | void before_suffix(const char *n, char *s) { 114 | const char *dot = strchr(n, '.'); 115 | strcpy(s, dot ? dot + 1 : ""); 116 | char *comma = strchr(s, ','); 117 | if (comma) { *comma = '\0'; } 118 | } 119 | 120 | void after_suffix(const char *n, char *s) { 121 | const char *dot = strchr(n, '.'); 122 | const char *comma = dot ? strchr(dot, ',') : NULL; 123 | strcpy(s, comma ? comma + 1 : ""); 124 | } 125 | 126 | void remove_dot_ext(const char *f, char *s) { 127 | strcpy(s, fl_filename_name(f)); 128 | char *dot = strchr(s, '.'); 129 | if (dot) { *dot = '\0'; } 130 | } 131 | 132 | void add_dot_ext(const char *f, const char *ext, char *s) { 133 | strcpy(s, f); 134 | const char *e = fl_filename_ext(s); 135 | if (!e || !strlen(e)) { 136 | strcat(s, ext); 137 | } 138 | } 139 | 140 | int text_width(const char *l, int pad) { 141 | int lw = 0, lh = 0; 142 | fl_measure(l, lw, lh, 0); 143 | return lw + 2 * pad; 144 | } 145 | 146 | bool file_exists(const char *f) { 147 | return !fl_access(f, 4); // R_OK 148 | } 149 | 150 | size_t file_size(const char *f) { 151 | struct stat s; 152 | int r = fl_stat(f, &s); 153 | return r ? 0 : (size_t)s.st_size; 154 | } 155 | 156 | size_t file_size(FILE *f) { 157 | #if defined(__CYGWIN__) || defined(__APPLE__) 158 | #define stat64 stat 159 | #define fstat64 fstat 160 | #elif defined(_WIN32) 161 | #define fileno _fileno 162 | #define stat64 _stat32i64 163 | #define fstat64 _fstat32i64 164 | #endif 165 | struct stat64 s; 166 | int r = fstat64(fileno(f), &s); 167 | return r ? 0 : (size_t)s.st_size; 168 | } 169 | 170 | int64_t file_modified(const char *f) { 171 | if (!f) { return 0; } 172 | struct stat s; 173 | int r = fl_stat(f, &s); 174 | return r ? 0 : s.st_mtime; 175 | } 176 | 177 | void open_ifstream(std::ifstream &ifs, const char *f) { 178 | #ifdef _WIN32 179 | wchar_t wf[FL_PATH_MAX] = {}; 180 | fl_utf8towc(f, (unsigned int) strlen(f), wf, sizeof(wf)); 181 | ifs.open(wf); 182 | #else 183 | ifs.open(f); 184 | #endif 185 | } 186 | 187 | void open_ofstream(std::ofstream &ofs, const char *f) { 188 | #ifdef _WIN32 189 | wchar_t wf[FL_PATH_MAX] = {}; 190 | fl_utf8towc(f, (unsigned int) strlen(f), wf, sizeof(wf)); 191 | ofs.open(wf, std::ios::binary); 192 | #else 193 | ofs.open(f, std::ios::binary); 194 | #endif 195 | } 196 | 197 | void draw_outlined_text(const char *l, int x, int y, int w, int h, Fl_Align a, Fl_Color c, Fl_Color s) { 198 | fl_color(s); 199 | fl_draw(l, x-1, y-1, w, h, a); 200 | fl_draw(l, x-1, y+1, w, h, a); 201 | fl_draw(l, x+1, y-1, w, h, a); 202 | fl_draw(l, x+1, y+1, w, h, a); 203 | fl_color(c); 204 | fl_draw(l, x, y, w, h, a); 205 | } 206 | 207 | bool parse_value(std::string s, int32_t &v) { 208 | trim(s); 209 | if (!s.empty()) { 210 | int32_t scale = 1; 211 | if (s[0] == '-') { 212 | s.erase(0, 1); 213 | trim(s); 214 | if (s.empty()) return false; 215 | scale = -1; 216 | } 217 | if (s[0] == '$') { 218 | s.erase(0, 1); 219 | if (s.empty() || !is_hex(s)) return false; 220 | v = (int32_t)strtol(s.c_str(), NULL, 16) * scale; 221 | } 222 | else if (s[0] == '&') { 223 | s.erase(0, 1); 224 | if (s.empty() || !is_octal(s)) return false; 225 | v = (int32_t)strtol(s.c_str(), NULL, 8) * scale; 226 | } 227 | else if (s[0] == '%') { 228 | s.erase(0, 1); 229 | if (s.empty() || !is_binary(s)) return false; 230 | v = (int32_t)strtol(s.c_str(), NULL, 2) * scale; 231 | } 232 | else { 233 | if (!is_decimal(s)) return false; 234 | v = (int32_t)strtol(s.c_str(), NULL, 10) * scale; 235 | } 236 | return true; 237 | } 238 | return false; 239 | } 240 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | ### GNU LESSER GENERAL PUBLIC LICENSE 2 | 3 | Version 3, 29 June 2007 4 | 5 | Copyright (C) 2007 Free Software Foundation, Inc. 6 | 7 | 8 | Everyone is permitted to copy and distribute verbatim copies of this 9 | license document, but changing it is not allowed. 10 | 11 | This version of the GNU Lesser General Public License incorporates the 12 | terms and conditions of version 3 of the GNU General Public License, 13 | supplemented by the additional permissions listed below. 14 | 15 | #### 0. Additional Definitions. 16 | 17 | As used herein, "this License" refers to version 3 of the GNU Lesser 18 | General Public License, and the "GNU GPL" refers to version 3 of the 19 | GNU General Public License. 20 | 21 | "The Library" refers to a covered work governed by this License, other 22 | than an Application or a Combined Work as defined below. 23 | 24 | An "Application" is any work that makes use of an interface provided 25 | by the Library, but which is not otherwise based on the Library. 26 | Defining a subclass of a class defined by the Library is deemed a mode 27 | of using an interface provided by the Library. 28 | 29 | A "Combined Work" is a work produced by combining or linking an 30 | Application with the Library. The particular version of the Library 31 | with which the Combined Work was made is also called the "Linked 32 | Version". 33 | 34 | The "Minimal Corresponding Source" for a Combined Work means the 35 | Corresponding Source for the Combined Work, excluding any source code 36 | for portions of the Combined Work that, considered in isolation, are 37 | based on the Application, and not on the Linked Version. 38 | 39 | The "Corresponding Application Code" for a Combined Work means the 40 | object code and/or source code for the Application, including any data 41 | and utility programs needed for reproducing the Combined Work from the 42 | Application, but excluding the System Libraries of the Combined Work. 43 | 44 | #### 1. Exception to Section 3 of the GNU GPL. 45 | 46 | You may convey a covered work under sections 3 and 4 of this License 47 | without being bound by section 3 of the GNU GPL. 48 | 49 | #### 2. Conveying Modified Versions. 50 | 51 | If you modify a copy of the Library, and, in your modifications, a 52 | facility refers to a function or data to be supplied by an Application 53 | that uses the facility (other than as an argument passed when the 54 | facility is invoked), then you may convey a copy of the modified 55 | version: 56 | 57 | - a) under this License, provided that you make a good faith effort 58 | to ensure that, in the event an Application does not supply the 59 | function or data, the facility still operates, and performs 60 | whatever part of its purpose remains meaningful, or 61 | - b) under the GNU GPL, with none of the additional permissions of 62 | this License applicable to that copy. 63 | 64 | #### 3. Object Code Incorporating Material from Library Header Files. 65 | 66 | The object code form of an Application may incorporate material from a 67 | header file that is part of the Library. You may convey such object 68 | code under terms of your choice, provided that, if the incorporated 69 | material is not limited to numerical parameters, data structure 70 | layouts and accessors, or small macros, inline functions and templates 71 | (ten or fewer lines in length), you do both of the following: 72 | 73 | - a) Give prominent notice with each copy of the object code that 74 | the Library is used in it and that the Library and its use are 75 | covered by this License. 76 | - b) Accompany the object code with a copy of the GNU GPL and this 77 | license document. 78 | 79 | #### 4. Combined Works. 80 | 81 | You may convey a Combined Work under terms of your choice that, taken 82 | together, effectively do not restrict modification of the portions of 83 | the Library contained in the Combined Work and reverse engineering for 84 | debugging such modifications, if you also do each of the following: 85 | 86 | - a) Give prominent notice with each copy of the Combined Work that 87 | the Library is used in it and that the Library and its use are 88 | covered by this License. 89 | - b) Accompany the Combined Work with a copy of the GNU GPL and this 90 | license document. 91 | - c) For a Combined Work that displays copyright notices during 92 | execution, include the copyright notice for the Library among 93 | these notices, as well as a reference directing the user to the 94 | copies of the GNU GPL and this license document. 95 | - d) Do one of the following: 96 | - 0) Convey the Minimal Corresponding Source under the terms of 97 | this License, and the Corresponding Application Code in a form 98 | suitable for, and under terms that permit, the user to 99 | recombine or relink the Application with a modified version of 100 | the Linked Version to produce a modified Combined Work, in the 101 | manner specified by section 6 of the GNU GPL for conveying 102 | Corresponding Source. 103 | - 1) Use a suitable shared library mechanism for linking with 104 | the Library. A suitable mechanism is one that (a) uses at run 105 | time a copy of the Library already present on the user's 106 | computer system, and (b) will operate properly with a modified 107 | version of the Library that is interface-compatible with the 108 | Linked Version. 109 | - e) Provide Installation Information, but only if you would 110 | otherwise be required to provide such information under section 6 111 | of the GNU GPL, and only to the extent that such information is 112 | necessary to install and execute a modified version of the 113 | Combined Work produced by recombining or relinking the Application 114 | with a modified version of the Linked Version. (If you use option 115 | 4d0, the Installation Information must accompany the Minimal 116 | Corresponding Source and Corresponding Application Code. If you 117 | use option 4d1, you must provide the Installation Information in 118 | the manner specified by section 6 of the GNU GPL for conveying 119 | Corresponding Source.) 120 | 121 | #### 5. Combined Libraries. 122 | 123 | You may place library facilities that are a work based on the Library 124 | side by side in a single library together with other library 125 | facilities that are not Applications and are not covered by this 126 | License, and convey such a combined library under terms of your 127 | choice, if you do both of the following: 128 | 129 | - a) Accompany the combined library with a copy of the same work 130 | based on the Library, uncombined with any other library 131 | facilities, conveyed under the terms of this License. 132 | - b) Give prominent notice with the combined library that part of it 133 | is a work based on the Library, and explaining where to find the 134 | accompanying uncombined form of the same work. 135 | 136 | #### 6. Revised Versions of the GNU Lesser General Public License. 137 | 138 | The Free Software Foundation may publish revised and/or new versions 139 | of the GNU Lesser General Public License from time to time. Such new 140 | versions will be similar in spirit to the present version, but may 141 | differ in detail to address new problems or concerns. 142 | 143 | Each version is given a distinguishing version number. If the Library 144 | as you received it specifies that a certain numbered version of the 145 | GNU Lesser General Public License "or any later version" applies to 146 | it, you have the option of following the terms and conditions either 147 | of that published version or of any later version published by the 148 | Free Software Foundation. If the Library as you received it does not 149 | specify a version number of the GNU Lesser General Public License, you 150 | may choose any version of the GNU Lesser General Public License ever 151 | published by the Free Software Foundation. 152 | 153 | If the Library as you received it specifies that a proxy can decide 154 | whether future versions of the GNU Lesser General Public License shall 155 | apply, that proxy's public statement of acceptance of any version is 156 | permanent authorization for you to choose that version for the 157 | Library. 158 | -------------------------------------------------------------------------------- /src/widgets.h: -------------------------------------------------------------------------------- 1 | #ifndef WIDGETS_H 2 | #define WIDGETS_H 3 | 4 | #include 5 | #include 6 | 7 | #pragma warning(push, 0) 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #pragma warning(pop) 20 | 21 | #include "hex-spinner.h" 22 | 23 | #define OS_MENU_ITEM_PREFIX " " 24 | #define OS_MENU_ITEM_SUFFIX " " 25 | 26 | #ifdef __APPLE__ 27 | #define SYS_MENU_ITEM_PREFIX "" 28 | #define SYS_MENU_ITEM_SUFFIX "" 29 | #else 30 | #define SYS_MENU_ITEM_PREFIX OS_MENU_ITEM_PREFIX 31 | #define SYS_MENU_ITEM_SUFFIX OS_MENU_ITEM_SUFFIX 32 | #endif 33 | 34 | #define OS_SUBMENU(l) {l, 0, NULL, NULL, FL_SUBMENU, FL_NORMAL_LABEL, OS_FONT, OS_FONT_SIZE, FL_FOREGROUND_COLOR} 35 | #define OS_NULL_MENU_ITEM(s, c, d, f) {"", s, c, d, f, FL_NORMAL_LABEL, OS_FONT, OS_FONT_SIZE, FL_FOREGROUND_COLOR} 36 | #define OS_MENU_ITEM(l, s, c, d, f) {OS_MENU_ITEM_PREFIX l OS_MENU_ITEM_SUFFIX, s, c, d, f, FL_NORMAL_LABEL, OS_FONT, OS_FONT_SIZE, FL_FOREGROUND_COLOR} 37 | #define SYS_MENU_ITEM(l, s, c, d, f) {SYS_MENU_ITEM_PREFIX l SYS_MENU_ITEM_SUFFIX, s, c, d, f, FL_NORMAL_LABEL, OS_FONT, OS_FONT_SIZE, FL_FOREGROUND_COLOR} 38 | 39 | class DnD_Receiver : public Fl_Box { 40 | public: 41 | static void deferred_callback(DnD_Receiver *dndr); 42 | private: 43 | std::string _text; 44 | public: 45 | DnD_Receiver(int x, int y, int w, int h, const char *l = NULL); 46 | inline const std::string &text(void) const { return _text; } 47 | int handle(int event); 48 | }; 49 | 50 | class Label : public Fl_Box { 51 | public: 52 | Label(int x, int y, int w, int h, const char *l = NULL); 53 | }; 54 | 55 | class Label_Button : public Fl_Button { 56 | private: 57 | bool _enabled = true; 58 | public: 59 | Label_Button(int x, int y, int w, int h, const char *l = NULL); 60 | inline void enable(void) { _enabled = true; } 61 | inline void disable(void) { _enabled = false; } 62 | int handle(int event); 63 | }; 64 | 65 | class Spacer : public Fl_Box { 66 | public: 67 | Spacer(int x, int y, int w, int h, const char *l = NULL); 68 | }; 69 | 70 | class OS_Input : public Fl_Input { 71 | public: 72 | OS_Input(int x, int y, int w, int h, const char *l = NULL); 73 | }; 74 | 75 | class OS_Int_Input : public Fl_Int_Input { 76 | public: 77 | OS_Int_Input(int x, int y, int w, int h, const char *l = NULL); 78 | }; 79 | 80 | class OS_Hex_Input : public Hex_Input { 81 | public: 82 | OS_Hex_Input(int x, int y, int w, int h, const char *l = NULL); 83 | }; 84 | 85 | class OS_Button : public Fl_Button { 86 | public: 87 | OS_Button(int x, int y, int w, int h, const char *l = NULL); 88 | protected: 89 | int handle(int event); 90 | }; 91 | 92 | class Default_Button : public Fl_Button { 93 | public: 94 | Default_Button(int x, int y, int w, int h, const char *l = NULL); 95 | protected: 96 | int handle(int event); 97 | }; 98 | 99 | class OS_Check_Button : public Fl_Check_Button { 100 | public: 101 | OS_Check_Button(int x, int y, int w, int h, const char *l = NULL); 102 | void draw(void); 103 | protected: 104 | int handle(int event); 105 | }; 106 | 107 | class OS_Radio_Button : public Fl_Radio_Round_Button { 108 | public: 109 | OS_Radio_Button(int x, int y, int w, int h, const char *l = NULL); 110 | void draw(void); 111 | protected: 112 | int handle(int event); 113 | }; 114 | 115 | class OS_Spinner : public Fl_Spinner { 116 | public: 117 | OS_Spinner(int x, int y, int w, int h, const char *l = NULL); 118 | void label(const char *text) { input_.label(text); } 119 | void labelfont(Fl_Font f) { input_.labelfont(f); } 120 | void labelsize(Fl_Fontsize pix) { input_.labelsize(pix); } 121 | protected: 122 | int handle(int event); 123 | }; 124 | 125 | class OS_Hex_Spinner : public Hex_Spinner { 126 | public: 127 | OS_Hex_Spinner(int x, int y, int w, int h, const char *l = NULL); 128 | }; 129 | 130 | class Default_Spinner : public OS_Spinner { 131 | private: 132 | double _default_value; 133 | public: 134 | Default_Spinner(int x, int y, int w, int h, const char *l = NULL); 135 | inline double default_value(void) const { return _default_value; } 136 | inline void default_value(double v) { _default_value = v; value(_default_value); } 137 | protected: 138 | int handle(int event); 139 | }; 140 | 141 | class Default_Hex_Spinner : public OS_Hex_Spinner { 142 | private: 143 | int _default_value; 144 | public: 145 | Default_Hex_Spinner(int x, int y, int w, int h, const char *l = NULL); 146 | inline int default_value(void) const { return _default_value; } 147 | inline void default_value(int v) { _default_value = v; value(_default_value); } 148 | protected: 149 | int handle(int event); 150 | }; 151 | 152 | class OS_Slider : public Fl_Hor_Nice_Slider { 153 | public: 154 | OS_Slider(int x, int y, int w, int h, const char *l = NULL); 155 | void draw(void); 156 | void draw(int x, int y, int w, int h); 157 | protected: 158 | int handle(int event); 159 | }; 160 | 161 | class Default_Slider : public OS_Slider { 162 | private: 163 | double _default_value; 164 | public: 165 | Default_Slider(int x, int y, int w, int h, const char *l = NULL); 166 | inline double default_value(void) const { return _default_value; } 167 | inline void default_value(double v) { _default_value = v; value(_default_value); } 168 | protected: 169 | int handle(int event); 170 | int handle(int event, int x, int y, int w, int h); 171 | }; 172 | 173 | class HTML_View : public Fl_Help_View { 174 | public: 175 | HTML_View(int x, int y, int w, int h, const char *l = NULL); 176 | }; 177 | 178 | class Dropdown : public Fl_Choice { 179 | public: 180 | Dropdown(int x, int y, int w, int h, const char *l = NULL); 181 | void draw(void); 182 | protected: 183 | int handle(int event); 184 | }; 185 | 186 | class OS_Scroll : public Fl_Scroll { 187 | public: 188 | OS_Scroll(int x, int y, int w, int h, const char *l = NULL); 189 | }; 190 | 191 | class Workspace : public OS_Scroll { 192 | private: 193 | int _content_w, _content_h; 194 | int _ox, _oy, _cx, _cy; 195 | DnD_Receiver *_dnd_receiver; 196 | std::vector _correlates; 197 | public: 198 | Workspace(int x, int y, int w, int h, const char *l = NULL); 199 | inline void contents(int w, int h) { _content_w = w; _content_h = h; } 200 | inline bool has_x_scroll(void) const { return !!hscrollbar.visible(); } 201 | inline bool has_y_scroll(void) const { return !!scrollbar.visible(); } 202 | inline void dnd_receiver(DnD_Receiver *dndr) { _dnd_receiver = dndr; } 203 | inline void add_correlate(Fl_Widget *wgt) { _correlates.push_back(wgt); } 204 | inline void clear_correlates(void) { _correlates.clear(); } 205 | int handle(int event); 206 | void scroll_to(int x, int y); 207 | private: 208 | static void hscrollbar_cb(Fl_Scrollbar *sb, void *); 209 | static void scrollbar_cb(Fl_Scrollbar *sb, void *); 210 | }; 211 | 212 | class Toolbar : public Fl_Group { 213 | public: 214 | Toolbar(int x, int y, int w, int h, const char *l = NULL); 215 | }; 216 | 217 | class Toolbar_Button : public Fl_Button { 218 | public: 219 | Toolbar_Button(int x, int y, int w, int h, const char *l = NULL); 220 | void simulate_key_action() { Fl_Button::simulate_key_action(); } 221 | protected: 222 | void draw(void); 223 | int handle(int event); 224 | }; 225 | 226 | class Toolbar_Toggle_Button : public Toolbar_Button { 227 | public: 228 | Toolbar_Toggle_Button(int x, int y, int w, int h, const char *l = NULL); 229 | }; 230 | 231 | class Toolbar_Radio_Button : public Toolbar_Button { 232 | public: 233 | Toolbar_Radio_Button(int x, int y, int w, int h, const char *l = NULL); 234 | }; 235 | 236 | class Context_Menu : public Fl_Menu_ { 237 | private: 238 | int _shortcut; 239 | public: 240 | Context_Menu(int x, int y, int w, int h, const char *l = NULL); 241 | int shortcut() const { return _shortcut; } 242 | void shortcut(int s) { _shortcut = s; } 243 | int handle(int event); 244 | virtual bool prepare(int X, int Y); 245 | protected: 246 | void draw(void); 247 | }; 248 | 249 | #endif 250 | -------------------------------------------------------------------------------- /src/command.h: -------------------------------------------------------------------------------- 1 | #ifndef COMMAND_H 2 | #define COMMAND_H 3 | 4 | #include 5 | #include 6 | 7 | enum class Pitch { 8 | REST, 9 | C_NAT, 10 | C_SHARP, 11 | D_NAT, 12 | D_SHARP, 13 | E_NAT, 14 | F_NAT, 15 | F_SHARP, 16 | G_NAT, 17 | G_SHARP, 18 | A_NAT, 19 | A_SHARP, 20 | B_NAT, 21 | }; 22 | 23 | static const char * const PITCH_NAMES[] = { 24 | "--", 25 | "C_", 26 | "C#", 27 | "D_", 28 | "D#", 29 | "E_", 30 | "F_", 31 | "F#", 32 | "G_", 33 | "G#", 34 | "A_", 35 | "A#", 36 | "B_", 37 | }; 38 | 39 | static const size_t NUM_PITCHES = sizeof(PITCH_NAMES) / sizeof(char *) - 1; 40 | 41 | enum class Command_Type { 42 | NOTE, 43 | DRUM_NOTE, 44 | REST, 45 | OCTAVE, 46 | NOTE_TYPE, 47 | DRUM_SPEED, 48 | TRANSPOSE, 49 | TEMPO, 50 | DUTY_CYCLE, 51 | VOLUME_ENVELOPE, 52 | PITCH_SWEEP, 53 | DUTY_CYCLE_PATTERN, 54 | PITCH_SLIDE, 55 | VIBRATO, 56 | TOGGLE_NOISE, 57 | FORCE_STEREO_PANNING, 58 | VOLUME, 59 | PITCH_OFFSET, 60 | STEREO_PANNING, 61 | SOUND_JUMP, 62 | SOUND_LOOP, 63 | SOUND_CALL, 64 | SOUND_RET, 65 | TOGGLE_PERFECT_PITCH, 66 | LOAD_WAVE, 67 | INC_OCTAVE, 68 | DEC_OCTAVE, 69 | SPEED, 70 | CHANNEL_VOLUME, 71 | FADE_WAVE, 72 | }; 73 | 74 | static const char * const COMMAND_NAMES[] = { 75 | "note", 76 | "drum_note", 77 | "rest", 78 | "octave", 79 | "note_type", 80 | "drum_speed", 81 | "transpose", 82 | "tempo", 83 | "duty_cycle", 84 | "volume_envelope", 85 | "pitch_sweep", 86 | "duty_cycle_pattern", 87 | "pitch_slide", 88 | "vibrato", 89 | "toggle_noise", 90 | "force_stereo_panning", 91 | "volume", 92 | "pitch_offset", 93 | "stereo_panning", 94 | "sound_jump", 95 | "sound_loop", 96 | "sound_call", 97 | "sound_ret", 98 | "toggle_perfect_pitch", 99 | "load_wave", 100 | "inc_octave", 101 | "dec_octave", 102 | "speed", 103 | "channel_volume", 104 | "fade_wave", 105 | }; 106 | 107 | static inline bool is_note_command(Command_Type type) { 108 | return ( 109 | type == Command_Type::NOTE || 110 | type == Command_Type::DRUM_NOTE 111 | ); 112 | } 113 | 114 | static inline bool is_note_setting_command(Command_Type type) { 115 | return ( 116 | type == Command_Type::OCTAVE || 117 | type == Command_Type::TRANSPOSE || 118 | type == Command_Type::DUTY_CYCLE || 119 | type == Command_Type::VOLUME_ENVELOPE || 120 | type == Command_Type::PITCH_SWEEP || 121 | type == Command_Type::DUTY_CYCLE_PATTERN || 122 | type == Command_Type::PITCH_SLIDE || 123 | type == Command_Type::VIBRATO || 124 | type == Command_Type::TOGGLE_NOISE || 125 | type == Command_Type::FORCE_STEREO_PANNING || 126 | type == Command_Type::PITCH_OFFSET || 127 | type == Command_Type::STEREO_PANNING || 128 | type == Command_Type::TOGGLE_PERFECT_PITCH || 129 | type == Command_Type::LOAD_WAVE || 130 | type == Command_Type::INC_OCTAVE || 131 | type == Command_Type::DEC_OCTAVE || 132 | type == Command_Type::CHANNEL_VOLUME || 133 | type == Command_Type::FADE_WAVE 134 | ); 135 | } 136 | 137 | static inline bool is_speed_command(Command_Type type) { 138 | return ( 139 | type == Command_Type::NOTE_TYPE || 140 | type == Command_Type::DRUM_SPEED || 141 | type == Command_Type::SPEED 142 | ); 143 | } 144 | 145 | static inline bool is_global_command(Command_Type type) { 146 | return ( 147 | type == Command_Type::TEMPO || 148 | type == Command_Type::VOLUME 149 | ); 150 | } 151 | 152 | static inline bool is_control_command(Command_Type type) { 153 | return ( 154 | type == Command_Type::SOUND_JUMP || 155 | type == Command_Type::SOUND_LOOP || 156 | type == Command_Type::SOUND_CALL || 157 | type == Command_Type::SOUND_RET 158 | ); 159 | } 160 | 161 | static inline int compare_pitch(Pitch p1, int32_t o1, Pitch p2, int32_t o2) { 162 | if (o1 < o2 || (o1 == o2 && p1 < p2)) return -1; 163 | if (o1 > o2 || (o1 == o2 && p1 > p2)) return 1; 164 | return 0; 165 | } 166 | 167 | struct Command { 168 | 169 | struct Note { 170 | int32_t length; 171 | Pitch pitch; 172 | }; 173 | 174 | struct Drum_Note { 175 | int32_t length; 176 | int32_t instrument; 177 | }; 178 | 179 | struct Rest { 180 | int32_t length; 181 | }; 182 | 183 | struct Octave { 184 | int32_t octave; 185 | }; 186 | 187 | struct Note_Type { 188 | int32_t speed; 189 | int32_t volume; 190 | union { 191 | int32_t fade; 192 | int32_t wave; 193 | }; 194 | }; 195 | 196 | struct Drum_Speed { 197 | int32_t speed; 198 | }; 199 | 200 | struct Transpose { 201 | int32_t num_octaves; 202 | int32_t num_pitches; 203 | }; 204 | 205 | struct Tempo { 206 | int32_t tempo; 207 | }; 208 | 209 | struct Duty_Cycle { 210 | int32_t duty; 211 | }; 212 | 213 | struct Volume_Envelope { 214 | int32_t volume; 215 | union { 216 | int32_t fade; 217 | int32_t wave; 218 | }; 219 | }; 220 | 221 | struct Pitch_Sweep { 222 | int32_t duration; 223 | int32_t pitch_change; 224 | }; 225 | 226 | struct Duty_Cycle_Pattern { 227 | int32_t duty1; 228 | int32_t duty2; 229 | int32_t duty3; 230 | int32_t duty4; 231 | }; 232 | 233 | struct Pitch_Slide { 234 | int32_t duration; 235 | int32_t octave; 236 | Pitch pitch; 237 | }; 238 | 239 | struct Vibrato { 240 | int32_t delay; 241 | int32_t extent; 242 | int32_t rate; 243 | }; 244 | 245 | struct Toggle_Noise { 246 | int32_t drumkit; 247 | }; 248 | 249 | struct Force_Stereo_Panning { 250 | int32_t left; 251 | int32_t right; 252 | }; 253 | 254 | struct Volume { 255 | int32_t left; 256 | int32_t right; 257 | }; 258 | 259 | struct Pitch_Offset { 260 | int32_t offset; 261 | }; 262 | 263 | struct Stereo_Panning { 264 | int32_t left; 265 | int32_t right; 266 | }; 267 | 268 | struct Sound_Jump {}; 269 | 270 | struct Sound_Loop { 271 | int32_t loop_count; 272 | }; 273 | 274 | struct Sound_Call {}; 275 | 276 | struct Sound_Ret {}; 277 | 278 | struct Toggle_Perfect_Pitch {}; 279 | 280 | struct Load_Wave { 281 | int32_t wave; 282 | }; 283 | 284 | struct Inc_Octave {}; 285 | 286 | struct Dec_Octave {}; 287 | 288 | struct Speed { 289 | int32_t speed; 290 | }; 291 | 292 | struct Channel_Volume { 293 | int32_t volume; 294 | }; 295 | 296 | struct Fade_Wave { 297 | union { 298 | int32_t fade; 299 | int32_t wave; 300 | }; 301 | }; 302 | 303 | Command_Type type; 304 | std::vector labels; 305 | std::string target; 306 | union { 307 | Note note = {}; 308 | Drum_Note drum_note; 309 | Rest rest; 310 | Octave octave; 311 | Note_Type note_type; 312 | Drum_Speed drum_speed; 313 | Transpose transpose; 314 | Tempo tempo; 315 | Duty_Cycle duty_cycle; 316 | Volume_Envelope volume_envelope; 317 | Pitch_Sweep pitch_sweep; 318 | Duty_Cycle_Pattern duty_cycle_pattern; 319 | Pitch_Slide pitch_slide; 320 | Vibrato vibrato; 321 | Toggle_Noise toggle_noise; 322 | Force_Stereo_Panning force_stereo_panning; 323 | Volume volume; 324 | Pitch_Offset pitch_offset; 325 | Stereo_Panning stereo_panning; 326 | Sound_Jump sound_jump; 327 | Sound_Loop sound_loop; 328 | Sound_Call sound_call; 329 | Sound_Ret sound_ret; 330 | Toggle_Perfect_Pitch toggle_perfect_pitch; 331 | Load_Wave load_wave; 332 | Inc_Octave inc_octave; 333 | Dec_Octave dec_octave; 334 | Speed speed; 335 | Channel_Volume channel_volume; 336 | Fade_Wave fade_wave; 337 | }; 338 | 339 | Command() {} 340 | Command(Command_Type t) { 341 | type = t; 342 | } 343 | Command(Command_Type t, const std::string& label) { 344 | type = t; 345 | labels.push_back(label); 346 | } 347 | }; 348 | 349 | struct Note_View { 350 | int32_t length = 0; 351 | Pitch pitch = Pitch::REST; 352 | int32_t octave = 0; 353 | 354 | int32_t speed = 0; 355 | int32_t volume = 0; 356 | union { 357 | int32_t fade = 0; 358 | int32_t wave; 359 | }; 360 | int32_t drumkit = 0; 361 | 362 | int32_t tempo = 0; 363 | 364 | int32_t duty = 0; 365 | 366 | int32_t vibrato_delay = 0; 367 | int32_t vibrato_extent = 0; 368 | int32_t vibrato_rate = 0; 369 | 370 | int32_t transpose_octaves = 0; 371 | int32_t transpose_pitches = 0; 372 | 373 | int32_t slide_duration = 0; 374 | int32_t slide_octave = 0; 375 | Pitch slide_pitch = Pitch::REST; 376 | 377 | bool panning_left = true; 378 | bool panning_right = true; 379 | 380 | int32_t index = 0; 381 | bool ghost = false; 382 | }; 383 | 384 | #endif 385 | -------------------------------------------------------------------------------- /example/crystaltracked.asm: -------------------------------------------------------------------------------- 1 | Music_CrystalTracked: 2 | channel_count 3 3 | channel 1, Music_CrystalTracked_Ch1 4 | channel 2, Music_CrystalTracked_Ch2 5 | channel 3, Music_CrystalTracked_Ch3 6 | 7 | Music_CrystalTracked_Ch1: 8 | tempo 256 9 | volume 7, 7 10 | note_type 12, 15, 8 11 | .mainLoop: 12 | duty_cycle 0 13 | note_type 12, 10, 8 14 | octave 3 15 | tempo 230 16 | vibrato 0, 0, 0 17 | note G#, 1 18 | rest 1 19 | note G#, 1 20 | rest 2 21 | note A#, 3 22 | octave 4 23 | note C_, 4 24 | octave 3 25 | rest 2 26 | note D#, 2 27 | note G#, 1 28 | rest 1 29 | note A#, 1 30 | rest 1 31 | note G#, 1 32 | rest 2 33 | octave 2 34 | duty_cycle 3 35 | note D#, 1 36 | note G#, 1 37 | note B_, 1 38 | octave 3 39 | note C_, 1 40 | octave 2 41 | note A#, 1 42 | note G#, 1 43 | rest 1 44 | pitch_slide 1, 4, C_ 45 | duty_cycle 0 46 | note G#, 2 47 | octave 3 48 | note G#, 1 49 | rest 1 50 | note G#, 1 51 | rest 2 52 | note A#, 3 53 | octave 4 54 | note C_, 3 55 | rest 2 56 | octave 3 57 | duty_cycle 3 58 | note F_, 1 59 | note G#, 1 60 | note A#, 1 61 | octave 4 62 | note C_, 1 63 | note D#, 1 64 | note C_, 1 65 | octave 3 66 | rest 1 67 | pitch_slide 1, 2, C_ 68 | duty_cycle 0 69 | note G#, 2 70 | octave 2 71 | note G#, 2 72 | note_type 8, 10, 8 73 | note G_, 4 74 | note F_, 4 75 | pitch_slide 2, 4, G_ 76 | note C#, 4 77 | note_type 12, 10, 8 78 | octave 4 79 | sound_call .sub1 80 | rest 3 81 | duty_cycle 2 82 | note E_, 1 83 | note C_, 1 84 | note F_, 1 85 | note E_, 1 86 | note C_, 1 87 | rest 1 88 | octave 3 89 | note F_, 1 90 | note E_, 1 91 | octave 4 92 | duty_cycle 0 93 | note G_, 2 94 | vibrato 0, 1, 5 95 | volume_envelope 6, 8 96 | note G#, 6 97 | note G_, 2 98 | note F_, 4 99 | tempo 243 100 | note E_, 8 101 | octave 3 102 | note G_, 4 103 | tempo 256 104 | note B_, 2 105 | tempo 286 106 | note B_, 2 107 | octave 4 108 | tempo 354 109 | note C_, 2 110 | tempo 440 111 | note D_, 2 112 | volume_envelope 5, 8 113 | tempo 230 114 | note G#, 1 115 | rest 1 116 | note G#, 1 117 | rest 2 118 | note A#, 3 119 | octave 5 120 | note C_, 4 121 | rest 2 122 | octave 4 123 | note D#, 2 124 | note G#, 1 125 | note A#, 1 126 | note G#, 1 127 | rest 1 128 | octave 3 129 | note G#, 1 130 | note B_, 1 131 | octave 4 132 | note C_, 1 133 | note D#, 1 134 | note G#, 1 135 | rest 1 136 | note G#, 1 137 | rest 2 138 | note A#, 3 139 | octave 5 140 | note C_, 2 141 | rest 1 142 | octave 3 143 | duty_cycle 1 144 | note G#, 1 145 | octave 4 146 | note C_, 1 147 | note D#, 1 148 | octave 5 149 | note C_, 1 150 | note D#, 1 151 | note C_, 4 152 | rest 2 153 | duty_cycle 0 154 | note C_, 1 155 | note D#, 1 156 | vibrato 0, 4, 3 157 | note C_, 6 158 | octave 2 159 | duty_cycle 2 160 | note G#, 2 161 | note_type 8, 5, 8 162 | note G_, 4 163 | note F_, 4 164 | vibrato 0, 2, 3 165 | octave 1 166 | pitch_slide 3, 4, B_ 167 | note B_, 4 168 | note_type 12, 5, 8 169 | octave 4 170 | sound_call .sub1 171 | note_type 12, 5, 8 172 | octave 4 173 | rest 5 174 | note D_, 1 175 | note D#, 1 176 | note D_, 1 177 | rest 1 178 | note B_, 1 179 | rest 1 180 | octave 3 181 | note G_, 1 182 | rest 1 183 | vibrato 0, 4, 3 184 | duty_cycle 3 185 | note G#, 6 186 | note G_, 2 187 | note F_, 4 188 | note E_, 6 189 | tempo 243 190 | note E_, 2 191 | octave 2 192 | note G_, 4 193 | tempo 256 194 | note G#, 2 195 | tempo 286 196 | note B_, 2 197 | tempo 354 198 | note A#, 2 199 | octave 3 200 | tempo 500 201 | note C#, 2 202 | pitch_slide 1, 3, G# 203 | octave 8 204 | sound_loop 0, .mainLoop 205 | 206 | .sub1: 207 | note B_, 1 208 | note G_, 1 209 | octave 5 210 | note C_, 1 211 | octave 4 212 | note B_, 1 213 | note G_, 1 214 | note E_, 1 215 | note C_, 1 216 | octave 3 217 | note G_, 1 218 | octave 4 219 | note C_, 1 220 | rest 1 221 | note D_, 1 222 | rest 1 223 | octave 3 224 | note B_, 1 225 | octave 4 226 | note C_, 1 227 | note D_, 1 228 | note E_, 1 229 | note D_, 1 230 | rest 1 231 | note B_, 1 232 | sound_ret 233 | 234 | Music_CrystalTracked_Ch2: 235 | note_type 12, 15, 8 236 | .mainLoop: 237 | volume_envelope 15, 8 238 | octave 2 239 | duty_cycle 1 240 | vibrato 0, 2, 2 241 | sound_call .sub1 242 | note A_, 2 243 | octave 3 244 | note C_, 2 245 | note F_, 1 246 | rest 1 247 | note C_, 1 248 | octave 2 249 | rest 1 250 | note F_, 2 251 | note G_, 2 252 | note A_, 1 253 | rest 1 254 | note G_, 1 255 | rest 1 256 | sound_call .sub2 257 | note C_, 2 258 | note G_, 2 259 | vibrato 0, 5, 1 260 | note G#, 2 261 | octave 3 262 | note C_, 2 263 | octave 1 264 | volume_envelope 10, 8 265 | note G#, 2 266 | octave 2 267 | note D#, 2 268 | volume_envelope 13, 8 269 | note G#, 2 270 | octave 3 271 | note C_, 2 272 | volume_envelope 15, 8 273 | sound_call .sub3 274 | rest 2 275 | octave 2 276 | volume_envelope 12, 8 277 | note D#, 2 278 | note G_, 2 279 | octave 3 280 | volume_envelope 15, 8 281 | note C_, 2 282 | note D#, 2 283 | note D_, 2 284 | note C_, 2 285 | octave 2 286 | note B_, 2 287 | vibrato 0, 1, 1 288 | duty_cycle 2 289 | volume_envelope 10, 8 290 | sound_call .sub1 291 | octave 2 292 | note_type 12, 10, 8 293 | note G#, 2 294 | octave 3 295 | note D#, 2 296 | note F_, 1 297 | rest 1 298 | note C_, 1 299 | octave 2 300 | rest 1 301 | note F_, 2 302 | note G_, 2 303 | note A_, 1 304 | rest 1 305 | note G_, 1 306 | rest 1 307 | sound_call .sub2 308 | octave 2 309 | note_type 12, 10, 8 310 | note C_, 2 311 | note G_, 2 312 | note G#, 1 313 | rest 1 314 | note F#, 1 315 | octave 1 316 | rest 1 317 | duty_cycle 0 318 | note G#, 2 319 | octave 2 320 | note D#, 2 321 | note G#, 2 322 | octave 3 323 | note C_, 2 324 | sound_call .sub3 325 | note_type 12, 10, 8 326 | rest 2 327 | octave 2 328 | note D#, 2 329 | note G_, 2 330 | octave 3 331 | note C_, 2 332 | note E_, 2 333 | note D#, 2 334 | note C#, 2 335 | octave 2 336 | note A#, 2 337 | octave 8 338 | sound_loop 0, .mainLoop 339 | 340 | .sub1: 341 | note G#, 2 342 | octave 3 343 | note C_, 2 344 | note D#, 1 345 | rest 1 346 | note C_, 1 347 | octave 2 348 | rest 1 349 | note G#, 2 350 | octave 3 351 | note D#, 2 352 | note F_, 1 353 | rest 1 354 | note C_, 1 355 | octave 2 356 | rest 1 357 | note G#, 2 358 | octave 3 359 | note C_, 2 360 | note D#, 1 361 | rest 1 362 | note C_, 1 363 | octave 2 364 | rest 1 365 | note G#, 2 366 | octave 3 367 | note D#, 2 368 | note G#, 1 369 | rest 1 370 | note C_, 1 371 | octave 2 372 | rest 1 373 | note G#, 2 374 | octave 3 375 | note C_, 2 376 | note F_, 1 377 | rest 1 378 | note C_, 1 379 | octave 2 380 | rest 1 381 | note F_, 2 382 | octave 3 383 | note C_, 2 384 | note F_, 1 385 | rest 1 386 | octave 2 387 | note B_, 1 388 | rest 1 389 | sound_ret 390 | 391 | .sub2: 392 | note C_, 2 393 | note G_, 2 394 | note B_, 1 395 | rest 1 396 | note G_, 1 397 | rest 1 398 | note C_, 2 399 | note G_, 2 400 | note A#, 1 401 | rest 1 402 | note G_, 1 403 | rest 1 404 | note C_, 2 405 | note G_, 2 406 | note A_, 1 407 | rest 1 408 | note G_, 1 409 | rest 1 410 | sound_ret 411 | 412 | .sub3: 413 | note G#, 2 414 | note G_, 2 415 | note F_, 2 416 | note E_, 2 417 | sound_ret 418 | 419 | Music_CrystalTracked_Ch3: 420 | note_type 12, 1, 0 421 | .mainLoop: 422 | octave 2 423 | note_type 12, 1, 7 424 | sound_call .sub1 425 | note_type 8, 1, 7 426 | note G_, 4 427 | note F_, 4 428 | note C#, 4 429 | note_type 12, 1, 7 430 | .loop1: 431 | note C_, 8 432 | note G_, 2 433 | note F_, 2 434 | note E_, 1 435 | rest 3 436 | sound_loop 2, .loop1 437 | octave 2 438 | volume_envelope 1, 4 439 | note C_, 8 440 | octave 1 441 | note G_, 2 442 | note F_, 2 443 | note C_, 8 444 | note D_, 2 445 | note G#, 2 446 | note G#, 2 447 | note G_, 2 448 | note G_, 2 449 | note G_, 2 450 | octave 2 451 | volume_envelope 1, 6 452 | sound_call .sub1 453 | octave 1 454 | note_type 8, 1, 6 455 | note G_, 4 456 | note F_, 4 457 | note C#, 4 458 | note_type 12, 1, 7 459 | .loop2: 460 | note C_, 8 461 | note G_, 2 462 | note F_, 2 463 | note E_, 1 464 | rest 3 465 | sound_loop 2, .loop2 466 | volume_envelope 1, 8 467 | note C_, 1 468 | rest 1 469 | note G#, 1 470 | rest 1 471 | octave 2 472 | note D#, 1 473 | rest 1 474 | note G#, 1 475 | rest 9 476 | octave 1 477 | note C_, 1 478 | rest 1 479 | note G#, 1 480 | rest 1 481 | octave 2 482 | note D#, 1 483 | rest 1 484 | note G_, 1 485 | rest 1 486 | note E_, 2 487 | note E_, 2 488 | note F#, 2 489 | note F#, 2 490 | octave 8 491 | sound_loop 0, .mainLoop 492 | 493 | .sub1: 494 | note G#, 12 495 | note D#, 4 496 | note G#, 8 497 | note F#, 8 498 | note F_, 4 499 | note C_, 8 500 | octave 1 501 | note G#, 2 502 | note G_, 2 503 | note F_, 6 504 | note G#, 2 505 | sound_ret 506 | -------------------------------------------------------------------------------- /src/parse-drumkits.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #pragma warning(push, 0) 4 | #include 5 | #pragma warning(pop) 6 | 7 | #include "parse-drumkits.h" 8 | 9 | #include "utils.h" 10 | 11 | Parsed_Drumkits::Parsed_Drumkits(const char *d) { 12 | parse_drumkits(d); 13 | } 14 | 15 | Parsed_Drumkits::Result Parsed_Drumkits::parse_drumkits(const char *d) { 16 | char drumkits_file[FL_PATH_MAX] = {}; 17 | 18 | // first, try crysaudio/drumkits.asm 19 | strcpy(drumkits_file, d); 20 | strcat(drumkits_file, DIR_SEP "crysaudio" DIR_SEP "drumkits.asm"); 21 | if (try_parse_drumkits(drumkits_file) == Parsed_Drumkits::Result::DRUMKITS_OK) { 22 | return _result; 23 | } 24 | // second, try audio/drumkits.asm 25 | strcpy(drumkits_file, d); 26 | strcat(drumkits_file, DIR_SEP "audio" DIR_SEP "drumkits.asm"); 27 | if (try_parse_drumkits(drumkits_file) == Parsed_Drumkits::Result::DRUMKITS_OK) { 28 | return _result; 29 | } 30 | // third, try audio/drumkits_0f.asm (for pinball) 31 | strcpy(drumkits_file, d); 32 | strcat(drumkits_file, DIR_SEP "audio" DIR_SEP "drumkits_0f.asm"); 33 | if (try_parse_drumkits(drumkits_file) == Parsed_Drumkits::Result::DRUMKITS_OK) { 34 | return _result; 35 | } 36 | // fourth, try drumkits.asm 37 | strcpy(drumkits_file, d); 38 | strcat(drumkits_file, DIR_SEP "drumkits.asm"); 39 | if (try_parse_drumkits(drumkits_file) == Parsed_Drumkits::Result::DRUMKITS_OK) { 40 | return _result; 41 | } 42 | 43 | return _result; 44 | } 45 | 46 | static bool get_label(std::istringstream &iss, std::string &l, const std::string &scope = "") { 47 | iss >> l; 48 | rtrim(l, ":"); 49 | trim(l); 50 | if (l.size() == 0) { 51 | return false; 52 | } 53 | if (l[0] == '.') { 54 | l = scope + l; 55 | } 56 | return true; 57 | } 58 | 59 | static bool get_number_and_number_and_number_and_number(std::istringstream &iss, int32_t &v1, int32_t &v2, int32_t &v3, int32_t &v4) { 60 | std::string l; 61 | std::getline(iss, l); 62 | size_t p = l.find(','); 63 | if (p == std::string::npos) { 64 | return false; 65 | } 66 | if (!parse_value(l.substr(0, p), v1)) { 67 | return false; 68 | } 69 | l.erase(0, p + 1); 70 | trim(l); 71 | if (l.size() == 0) { 72 | return false; 73 | } 74 | 75 | p = l.find(','); 76 | if (p == std::string::npos) { 77 | return false; 78 | } 79 | if (!parse_value(l.substr(0, p), v2)) { 80 | return false; 81 | } 82 | l.erase(0, p + 1); 83 | trim(l); 84 | if (l.size() == 0) { 85 | return false; 86 | } 87 | 88 | p = l.find(','); 89 | if (p == std::string::npos) { 90 | return false; 91 | } 92 | if (!parse_value(l.substr(0, p), v3)) { 93 | return false; 94 | } 95 | l.erase(0, p + 1); 96 | trim(l); 97 | if (l.size() == 0) { 98 | return false; 99 | } 100 | 101 | if (!parse_value(l, v4)) { 102 | return false; 103 | } 104 | return true; 105 | } 106 | 107 | static int32_t find_drum(const std::vector &drums, const std::string &label) { 108 | for (size_t i = 0; i < drums.size(); ++i) { 109 | if (drums[i].label == label) { 110 | return (int32_t)i; 111 | } 112 | } 113 | return -1; 114 | } 115 | 116 | static bool leading_pointer(std::istringstream &lss) { 117 | std::string macro; 118 | return leading_macro(lss, macro) && (equals_ignore_case(macro, "dw") || equals_ignore_case(macro, "dr")); 119 | } 120 | 121 | Parsed_Drumkits::Result Parsed_Drumkits::try_parse_drumkits(const char *f) { 122 | _drumkits_file = f; 123 | _drumkits.clear(); 124 | _drums.clear(); 125 | _num_parsed_drumkits = 0; 126 | _num_parsed_drums = 0; 127 | _result = Result::DRUMKITS_NULL; 128 | 129 | std::ifstream ifs; 130 | open_ifstream(ifs, f); 131 | if (!ifs.good()) { 132 | return (_result = Result::DRUMKITS_BAD_FILE); 133 | } 134 | 135 | enum class Step { 136 | LOOKING_FOR_DRUMKITS, 137 | READING_DRUMKITS, 138 | LOOKING_FOR_DRUMKIT, 139 | READING_DRUMKIT, 140 | LOOKING_FOR_DRUM, 141 | READING_DRUM, 142 | DONE 143 | }; 144 | 145 | Step step = Step::LOOKING_FOR_DRUMKITS; 146 | auto drumkit_itr = _drumkits.begin(); 147 | size_t drumkit_index = 0; 148 | auto drum_itr = _drums.begin(); 149 | 150 | while (ifs.good()) { 151 | std::string line; 152 | std::getline(ifs, line); 153 | remove_comment(line); 154 | rtrim(line); 155 | if (line.size() == 0) { continue; } 156 | bool indented = is_indented(line); 157 | std::istringstream lss(line); 158 | 159 | if (step == Step::LOOKING_FOR_DRUMKITS) { 160 | if (indented) { 161 | return (_result = Result::DRUMKITS_BAD_FILE); 162 | } 163 | std::string dummy; 164 | if (!get_label(lss, dummy)) { 165 | return (_result = Result::DRUMKITS_BAD_FILE); 166 | } 167 | step = Step::READING_DRUMKITS; 168 | } 169 | 170 | else if (step == Step::READING_DRUMKITS) { 171 | if (!indented) { 172 | if (_drumkits.size() == 0) { 173 | return (_result = Result::DRUMKITS_BAD_FILE); 174 | } 175 | drumkit_itr = _drumkits.begin(); 176 | drumkit_index = 0; 177 | ifs.seekg(0); 178 | step = Step::LOOKING_FOR_DRUMKIT; 179 | continue; 180 | } 181 | if (!leading_pointer(lss)) { 182 | return (_result = Result::DRUMKITS_BAD_FILE); 183 | } 184 | Drumkit drumkit; 185 | if (!get_label(lss, drumkit.label)) { 186 | return (_result = Result::DRUMKITS_BAD_FILE); 187 | } 188 | _drumkits.push_back(drumkit); 189 | } 190 | 191 | else if (step == Step::LOOKING_FOR_DRUMKIT) { 192 | if (indented) { continue; } 193 | std::string label; 194 | if (!get_label(lss, label)) { 195 | continue; 196 | } 197 | if (label == drumkit_itr->label) { 198 | step = Step::READING_DRUMKIT; 199 | } 200 | } 201 | 202 | else if (step == Step::READING_DRUMKIT) { 203 | if (!indented) { continue; } 204 | if (!leading_pointer(lss)) { 205 | return (_result = Result::DRUMKITS_BAD_FILE); 206 | } 207 | std::string label; 208 | if (!get_label(lss, label)) { 209 | return (_result = Result::DRUMKITS_BAD_FILE); 210 | } 211 | int32_t drum_index = find_drum(_drums, label); 212 | if (drum_index == -1) { 213 | drumkit_itr->drums[drumkit_index] = (int32_t)_drums.size(); 214 | Drum drum; 215 | drum.label = label; 216 | _drums.push_back(drum); 217 | } 218 | else { 219 | drumkit_itr->drums[drumkit_index] = drum_index; 220 | } 221 | drumkit_index += 1; 222 | if (drumkit_index == NUM_DRUMS_PER_DRUMKIT) { 223 | drumkit_itr += 1; 224 | if (drumkit_itr == _drumkits.end()) { 225 | drum_itr = _drums.begin(); 226 | ifs.seekg(0); 227 | step = Step::LOOKING_FOR_DRUM; 228 | } 229 | else { 230 | drumkit_index = 0; 231 | ifs.seekg(0); 232 | step = Step::LOOKING_FOR_DRUMKIT; 233 | } 234 | } 235 | } 236 | 237 | else if (step == Step::LOOKING_FOR_DRUM) { 238 | if (indented) { continue; } 239 | std::string label; 240 | if (!get_label(lss, label)) { 241 | continue; 242 | } 243 | if (label == drum_itr->label) { 244 | step = Step::READING_DRUM; 245 | } 246 | } 247 | 248 | else if (step == Step::READING_DRUM) { 249 | if (!indented) { continue; } 250 | std::string macro; 251 | if (!leading_macro(lss, macro)) { 252 | return (_result = Result::DRUMKITS_BAD_FILE); 253 | } 254 | if (macro == "noise_note") { 255 | int32_t length, volume, fade, frequency; 256 | if (!get_number_and_number_and_number_and_number(lss, length, volume, fade, frequency)) { 257 | return (_result = Result::DRUMKITS_BAD_FILE); 258 | } 259 | if (length < 0 || length > 255) { 260 | return (_result = Result::DRUMKITS_BAD_FILE); 261 | } 262 | if (volume < 0 || volume > 15) { 263 | return (_result = Result::DRUMKITS_BAD_FILE); 264 | } 265 | if (fade == 8) fade = 0; // 8 is used in place of 0 266 | if (fade < -7 || fade > 7) { 267 | return (_result = Result::DRUMKITS_BAD_FILE); 268 | } 269 | if (frequency < 0 || frequency > 255) { 270 | return (_result = Result::DRUMKITS_BAD_FILE); 271 | } 272 | Noise_Note noise_note; 273 | noise_note.length = length; 274 | noise_note.volume = volume; 275 | if (fade < 0) { 276 | noise_note.envelope_direction = 1; 277 | noise_note.sweep_pace = fade * -1; 278 | } 279 | else { 280 | noise_note.envelope_direction = 0; 281 | noise_note.sweep_pace = fade; 282 | } 283 | noise_note.clock_shift = frequency >> 4; 284 | noise_note.lfsr_width = (frequency >> 3) & 1; 285 | noise_note.clock_divider = frequency & 0b111; 286 | drum_itr->noise_notes.push_back(noise_note); 287 | } 288 | else if (macro == "sound_ret") { 289 | drum_itr += 1; 290 | if (drum_itr == _drums.end()) { 291 | step = Step::DONE; 292 | break; 293 | } 294 | else { 295 | ifs.seekg(0); 296 | step = Step::LOOKING_FOR_DRUM; 297 | } 298 | } 299 | else { 300 | return (_result = Result::DRUMKITS_BAD_FILE); 301 | } 302 | } 303 | } 304 | 305 | _num_parsed_drumkits = (int32_t)_drumkits.size(); 306 | if (_drumkits.size() > 256) { 307 | _drumkits.resize(256); 308 | } 309 | 310 | _num_parsed_drums = (int32_t)_drums.size(); 311 | if (_drums.size() > 64) { 312 | _drums.resize(64); 313 | } 314 | 315 | if (step != Step::DONE) { 316 | return (_result = Result::DRUMKITS_BAD_FILE); 317 | } 318 | return (_result = Result::DRUMKITS_OK); 319 | } 320 | -------------------------------------------------------------------------------- /INSTALL.md: -------------------------------------------------------------------------------- 1 | # Install Guide 2 | 3 | ## Windows 4 | 5 | ### Build Crystal Tracker from source 6 | 7 | You will need [Microsoft Visual Studio](https://visualstudio.microsoft.com/vs/); the Community edition is free. 8 | 9 | #### Clone this repository 10 | 11 | 0. Clone [crystal-tracker](https://github.com/dannye/crystal-tracker). This will create the **crystal-tracker** folder. 12 | 13 | #### Setting up FLTK 14 | 15 | 1. Clone [fltk release-1.4.1](https://github.com/fltk/fltk/tree/release-1.4.1) into lib\\**fltk**. (ie, `git clone -b release-1.4.1 https://github.com/fltk/fltk.git lib/fltk`) 16 | 2. Open Visual Studio, select **Open a local folder**, and open the lib\fltk folder. This will automatically generate the CMake project with a configuration named **x64-Debug**. 17 | 3. Create the following additional configurations with the appropriate **Configuration Type** of either Debug or Release: x64-Release, x86-Debug, and x86-Release. For x64 configurations, make sure the **Toolset** is set to **msvc_x64_x64**. For x86 configurations, make sure the Toolset is set to **msvc_x86_x64**. For all 4 configurations, uncheck the **FLTK_GRAPHICS_GDIPLUS** option. 18 | 4. Set the configuration to **x86-Release**. 19 | 5. In the **Solution Explorer**, switch to the **CMake Targets View**, right-click on **fltk_images**, and select **Build fltk_images**. This will also build the other required libraries: fltk, fltk_png, and fltk_z. 20 | 6. Move all the .lib files from lib\fltk\out\build\x86-Release\lib\\\*.lib up to lib\\\*.lib. (You may also choose the x86-Debug config in the previous step and move the .lib files from lib\fltk\out\build\x86-Debug\lib\\\*.lib to lib\Debug\\\*.lib instead.) 21 | 7. Copy the lib\fltk\\**FL** folder to a new include\\**FL** folder. Also copy lib\fltk\out\build\x86-Release\FL\fl_config.h into include\FL. All configurations will generate their own fl_config.h, but they should all be identical. 22 | 23 | #### Setting up PortAudio 24 | 25 | 8. Clone [portaudio v19.7.0](https://github.com/PortAudio/portaudio/tree/v19.7.0) into lib\\**portaudio**. (ie, `git clone -b v19.7.0 https://github.com/PortAudio/portaudio.git lib/portaudio`) 26 | 9. Apply the PortAudio patch that is provided with Crystal Tracker by running the following command from the root of the PortAudio directory (ie, lib\\**portaudio**): `git apply ../patches/portaudio.patch` 27 | This fixes a Windows-only glitch that will be fixed in the next major release of PortAudio but is being fixed manually here for now. See https://github.com/PortAudio/portaudio/commit/ba486a3a8c9e7b2a7b217ab6e31a4c46c6ca38db for more details. 28 | 10. Open lib\portaudio\build\msvc\\**portaudio.sln** in Visual Studio 2022. 29 | 11. A "One-way upgrade" dialog will open, since portaudio.sln was made for Visual Studio 2005. Click OK to upgrade. 30 | 12. In the **Solution Explorer**, remove the 4 ASIO-related cpp files under **Source Files → hostapi → ASIO** from the project. (Right-click each of the 4 cpp files and click "Remove".) 31 | 13. In the **Project Properties** window, go to **Configuration Properties → General** and change the value of **Configuration Type** from "Dynamic Library (.dll)" to "Static Library (.lib)". Do this for the Release configuration, and also the Debug configuration if desired. 32 | 14. In the **Project Properties** window, go to **Configuration Properties → C/C++ → Preprocessor** and add `PA_USE_ASIO=0;` to the beginning of the **Processor Definitions** property. Do this for the Release configuration, and also the Debug configuration if desired. 33 | 15. Open lib\portaudio\build\msvc\\**portaudio.def** in a text editor and comment out the 4 lines beginning with "PaAsio_" by putting a semicolon (`;`) at the start of the line. 34 | 16. Set the Solution Configuration to Release and set the Solution Platform to Win32 then press **Build → Build Solution**. You may also build Debug|Win32 if desired. 35 | 17. Move lib\portaudio\build\msvc\Win32\Release\portaudio.lib to lib\portaudio.lib (or move lib\portaudio\build\msvc\Win32\Debug\portaudio.lib to lib\Debug\portaudio.lib if Debug build). 36 | 18. Open lib\portaudio\bindings\cpp\build\vc7_1\\**static_library.sln** in Visual Studio 2022. 37 | 19. A "One-way upgrade" dialog will open. Click OK to upgrade. 38 | 20. In the **Project Properties** window, go to **Configuration Properties → C/C++ → Code Generation** and change **Runtime Library** from "Multi-threaded DLL (/MD)" to "Multi-threaded (/MT)". For the Debug configuration, change it from "Multi-threaded Debug DLL (/MDd)" to "Multi-threaded Debug (/MTd)". 39 | 21. Set the Solution Configuration to Release then press **Build → Build Solution**. You may also build Debug|x86 if desired. 40 | 22. Move lib\portaudio\bindings\cpp\lib\portaudiocpp-vc7_1-r.lib to lib\portaudiocpp-vc7_1-r.lib (or move lib\portaudio\bindings\cpp\lib\portaudiocpp-vc7_1-d.lib to lib\Debug\portaudiocpp-vc7_1-d.lib if Debug build). 41 | 23. Copy the lib\portaudio\bindings\cpp\include\\**portaudiocpp** folder to a new include\\**portaudiocpp** folder. 42 | 24. Copy the header files from lib\portaudio\include into the include\\**portaudiocpp** folder as well. 43 | 44 | #### Setting up libopenmpt 45 | 46 | 25. Clone [libopenmpt-0.6.3](https://github.com/OpenMPT/openmpt/tree/libopenmpt-0.6.3) into lib\\**openmpt**. (ie, `git clone -b libopenmpt-0.6.3 https://github.com/OpenMPT/openmpt.git lib/openmpt`) 47 | 26. Open lib\openmpt\build\vs2022win10\\**libopenmpt-small.sln** in Visual Studio 2022. 48 | 27. Retarget the 4 projects to your installed version of the Windows 10 SDK if necessary. 49 | 28. For each of the 4 projects, go to **Configuration Properties → C/C++ → Code Generation** and change **Spectre Mitigation** to "Disabled". Do this for the Release configuration, and also the Debug configuration if desired. 50 | 29. Set the Solution Configuration to Release and set the Solution Platform to Win32 then press **Build → Build Solution**. You may also build Debug|Win32 if desired. 51 | 30. Move the 4 .lib files from lib\openmpt\build\lib\vs2022win10\x86\Release to lib (or from lib\openmpt\build\lib\vs2022win10\x86\Debug to lib\Debug if Debug build). 52 | 31. Copy the public interface header files from lib\openmpt\libopenmpt into a new include\\**libopenmpt** folder. (Only libopenmpt.hpp, libopenmpt_ext.hpp, libopenmpt_config.h, and libopenmpt_version.h are required.) 53 | 54 | #### Building Crystal Tracker 55 | 56 | 32. Open ide\\**crystal-tracker.sln** in Visual Studio 2022. 57 | 33. If the Solution Configuration dropdown on the toolbar says Debug, set it to **Release**. 58 | 34. Go to **Build → Build Solution** to build the project. This will create bin\Release\\**crystaltracker.exe**. (A Debug build will create bin\Debug\\**crystaltrackerd.exe**.) 59 | 60 | **Note:** To build a 64-bit executable, you will need to create the x64 Solution Platform for portaudiocpp (static_library.sln). To do this, go to **Build → Configuration Manager…** and in the **Active solution platform:** dropdown select ****. Set the new platform to "x64" and copy settings from the 32-bit platform (either Win32 or x86). Make sure the "Create new project platforms" checkbox is checked and then click OK. Make sure the project configuration changes described above are also applied to this platform. portaudio.sln and libopenmpt-small.sln already have an x64 target. 61 | After building the x64 libs for fltk, portaudio, portaudiocpp, and openmpt, copy the .lib files to lib\\**x64** and lib\Debug\\**x64**. 62 | 63 | 64 | ## Linux 65 | 66 | ### Install dependencies 67 | 68 | You need at least g++ 7 for C++17 support. 69 | g++ 8 is needed if building libopenmpt from source. 70 | 71 | CMake (version 3.15 or later) is required for building FLTK 1.4. 72 | 73 | #### Ubuntu/Debian 74 | 75 | Run the following commands: 76 | 77 | ```bash 78 | sudo apt install make g++ git autoconf 79 | sudo apt install zlib1g-dev libpng-dev libxpm-dev libx11-dev libxft-dev libxinerama-dev libfontconfig1-dev x11proto-xext-dev libxrender-dev libxfixes-dev 80 | ``` 81 | 82 | ### Install and build Crystal Tracker 83 | 84 | Run the following commands: 85 | 86 | ```bash 87 | # Clone Crystal Tracker 88 | git clone https://github.com/dannye/crystal-tracker.git 89 | cd crystal-tracker 90 | 91 | # Build FLTK 1.4.1 92 | git clone -b release-1.4.1 https://github.com/fltk/fltk.git lib/fltk 93 | pushd lib/fltk 94 | cmake -D CMAKE_INSTALL_PREFIX="$(realpath "$PWD/../..")" -D CMAKE_BUILD_TYPE=Release -D FLTK_GRAPHICS_CAIRO=1 -D FLTK_BACKEND_WAYLAND=0 95 | make 96 | make install 97 | popd 98 | 99 | # Build PortAudio v19.7.0 100 | git clone -b v19.7.0 https://github.com/PortAudio/portaudio.git lib/portaudio 101 | pushd lib/portaudio 102 | ./configure --prefix="$(realpath "$PWD/../..")" CXXFLAGS="-O2" CFLAGS="-O2" 103 | make 104 | make install 105 | cd bindings/cpp 106 | ./configure --prefix="$(realpath "$PWD/../../../..")" CXXFLAGS="-O2" CFLAGS="-O2" 107 | make 108 | make install 109 | popd 110 | 111 | # Build libopenmpt-0.6.3 112 | pushd lib 113 | wget https://lib.openmpt.org/files/libopenmpt/src/libopenmpt-0.6.3+release.autotools.tar.gz 114 | mkdir libopenmpt && tar xf libopenmpt-0.6.3+release.autotools.tar.gz -C libopenmpt --strip-components=1 115 | cd libopenmpt 116 | ./configure --prefix="$(realpath "$PWD/../..")" \ 117 | --without-mpg123 \ 118 | --without-ogg \ 119 | --without-vorbis \ 120 | --without-vorbisfile \ 121 | --without-sndfile \ 122 | --without-flac \ 123 | CXX="g++-8" CXXFLAGS="-O2" CFLAGS="-O2" \ 124 | PKG_CONFIG_PATH="$(realpath "$PWD/../../lib/pkgconfig")" 125 | make 126 | make install 127 | popd 128 | 129 | mv include/pa_linux_alsa.h include/portaudio.h include/portaudiocpp/ 130 | 131 | # Build Crystal Tracker 132 | make 133 | 134 | # Install Crystal Tracker 135 | # (tested on Ubuntu and Ubuntu derivatives only; it just copies bin/crystaltracker 136 | # and res/app.xpm to system directories and creates the .desktop entry) 137 | sudo make install 138 | ``` 139 | 140 | 141 | ## Mac 142 | 143 | Follow the ["Install and build"](#install-and-build-crystal-tracker) instructions for Linux, but with the following changes: 144 | 145 | ### FLTK 146 | 147 | When building FLTK with CMake, use the following `cmake` command instead of the one shown above: 148 | 149 | ```bash 150 | cmake \ 151 | -D CMAKE_INSTALL_PREFIX="$(realpath "$PWD/../..")" \ 152 | -D CMAKE_BUILD_TYPE=Release \ 153 | -D CMAKE_OSX_DEPLOYMENT_TARGET="$(sw_vers -productVersion | cut -d '.' -f 1).0" \ 154 | -D FLTK_USE_SYSTEM_LIBPNG=ON \ 155 | -D PNG_PNG_INCLUDE_DIR="$(pkg-config --cflags-only-I libpng | cut -c 3-)" \ 156 | -D PNG_LIBRARY_RELEASE="$(pkg-config --static --libs-only-L libpng | cut -c 3-)/libpng.a" \ 157 | -D LIB_png="$(pkg-config --static --libs-only-L libpng | cut -c 3-)/libpng.a" \ 158 | -D FLTK_USE_SYSTEM_ZLIB=ON \ 159 | -D ZLIB_INCLUDE_DIR="$(PKG_CONFIG_PATH=/opt/homebrew/opt/zlib/lib/pkgconfig pkg-config --cflags-only-I zlib | cut -c 3-)" \ 160 | -D ZLIB_LIBRARY_RELEASE="$(PKG_CONFIG_PATH=/opt/homebrew/opt/zlib/lib/pkgconfig pkg-config --static --libs-only-L zlib | cut -c 3-)/libz.a" \ 161 | -D LIB_zlib="$(PKG_CONFIG_PATH=/opt/homebrew/opt/zlib/lib/pkgconfig pkg-config --static --libs-only-L zlib | cut -c 3-)/libz.a" 162 | ``` 163 | 164 | zlib may be installed in a different directory, such as `/usr/local/opt/zlib` instead of `/opt/homebrew/opt/zlib`. 165 | 166 | ### PortAudio 167 | 168 | If errors about unused variables are encountered when building PortAudio, apply [this fix](https://github.com/PortAudio/portaudio/commit/bc3ad0214a358be3cc01f6b2cc2eaaf284c6de34) and try again. 169 | 170 | When relocating the PortAudio headers, there will be no pa_linux_alsa.h, so that step will just be: `mv include/portaudio.h include/portaudiocpp/` 171 | 172 | ### libopenmpt 173 | 174 | When building libopenmpt from source use `CXX="clang++"` instead of `CXX="g++-8"`. 175 | -------------------------------------------------------------------------------- /ide/crystal-tracker.vcxproj.filters: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {4FC737F1-C7A5-4376-A066-2A32D752A2FF} 6 | cpp;c;cc;cxx;c++;cppm;ixx;def;odl;idl;hpj;bat;asm;asmx 7 | 8 | 9 | {93995380-89BD-4b04-88EB-625FBE52EBFB} 10 | h;hh;hpp;hxx;h++;hm;inl;inc;ipp;xsd 11 | 12 | 13 | {67DA6AB6-F800-4c08-8B7A-83BB121AAD01} 14 | rc;ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe;resx;tiff;tif;png;wav;mfcribbon-ms 15 | 16 | 17 | 18 | 19 | Source Files 20 | 21 | 22 | Source Files 23 | 24 | 25 | Source Files 26 | 27 | 28 | Source Files 29 | 30 | 31 | Source Files 32 | 33 | 34 | Source Files 35 | 36 | 37 | Source Files 38 | 39 | 40 | Source Files 41 | 42 | 43 | Source Files 44 | 45 | 46 | Source Files 47 | 48 | 49 | Source Files 50 | 51 | 52 | Source Files 53 | 54 | 55 | Source Files 56 | 57 | 58 | Source Files 59 | 60 | 61 | Source Files 62 | 63 | 64 | Source Files 65 | 66 | 67 | Source Files 68 | 69 | 70 | Source Files 71 | 72 | 73 | Source Files 74 | 75 | 76 | Source Files 77 | 78 | 79 | Source Files 80 | 81 | 82 | 83 | 84 | Header Files 85 | 86 | 87 | Header Files 88 | 89 | 90 | Header Files 91 | 92 | 93 | Header Files 94 | 95 | 96 | Header Files 97 | 98 | 99 | Header Files 100 | 101 | 102 | Header Files 103 | 104 | 105 | Header Files 106 | 107 | 108 | Header Files 109 | 110 | 111 | Header Files 112 | 113 | 114 | Header Files 115 | 116 | 117 | Header Files 118 | 119 | 120 | Header Files 121 | 122 | 123 | Header Files 124 | 125 | 126 | Header Files 127 | 128 | 129 | Header Files 130 | 131 | 132 | Header Files 133 | 134 | 135 | Header Files 136 | 137 | 138 | Header Files 139 | 140 | 141 | Header Files 142 | 143 | 144 | Header Files 145 | 146 | 147 | Header Files 148 | 149 | 150 | Header Files 151 | 152 | 153 | Header Files 154 | 155 | 156 | 157 | 158 | Resource Files 159 | 160 | 161 | 162 | 163 | Resource Files 164 | 165 | 166 | 167 | 168 | Resource Files 169 | 170 | 171 | Resource Files 172 | 173 | 174 | Resource Files 175 | 176 | 177 | Resource Files 178 | 179 | 180 | Resource Files 181 | 182 | 183 | Resource Files 184 | 185 | 186 | Resource Files 187 | 188 | 189 | Resource Files 190 | 191 | 192 | Resource Files 193 | 194 | 195 | Resource Files 196 | 197 | 198 | Resource Files 199 | 200 | 201 | Resource Files 202 | 203 | 204 | Resource Files 205 | 206 | 207 | Resource Files 208 | 209 | 210 | Resource Files 211 | 212 | 213 | Resource Files 214 | 215 | 216 | Resource Files 217 | 218 | 219 | Resource Files 220 | 221 | 222 | Resource Files 223 | 224 | 225 | Resource Files 226 | 227 | 228 | Resource Files 229 | 230 | 231 | Resource Files 232 | 233 | 234 | Resource Files 235 | 236 | 237 | Resource Files 238 | 239 | 240 | Resource Files 241 | 242 | 243 | Resource Files 244 | 245 | 246 | Resource Files 247 | 248 | 249 | Resource Files 250 | 251 | 252 | Resource Files 253 | 254 | 255 | Resource Files 256 | 257 | 258 | Resource Files 259 | 260 | 261 | Resource Files 262 | 263 | 264 | Resource Files 265 | 266 | 267 | Resource Files 268 | 269 | 270 | Resource Files 271 | 272 | 273 | Resource Files 274 | 275 | 276 | Resource Files 277 | 278 | 279 | Resource Files 280 | 281 | 282 | Resource Files 283 | 284 | 285 | Resource Files 286 | 287 | 288 | Resource Files 289 | 290 | 291 | Resource Files 292 | 293 | 294 | Resource Files 295 | 296 | 297 | Resource Files 298 | 299 | 300 | Resource Files 301 | 302 | 303 | Resource Files 304 | 305 | 306 | Resource Files 307 | 308 | 309 | Resource Files 310 | 311 | 312 | Resource Files 313 | 314 | 315 | Resource Files 316 | 317 | 318 | Resource Files 319 | 320 | 321 | Resource Files 322 | 323 | 324 | Resource Files 325 | 326 | 327 | --------------------------------------------------------------------------------