├── 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 | 
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 |
--------------------------------------------------------------------------------