├── .github
└── workflows
│ └── c-cpp.yml
├── .gitignore
├── .vscode
├── launch.json
└── tasks.json
├── LICENSE
├── README.md
├── ide
├── codeblocks
│ └── ebmused.cbp
├── vs2015
│ ├── ebmused.sln
│ └── ebmused.vcxproj
└── vs2019
│ ├── ebmused.sln
│ └── ebmused.vcxproj
└── src
├── Makefile
├── bgmlist.c
├── blank.spc
├── brr.c
├── ctrltbl.c
├── ebmused.exe.manifest
├── ebmusv2.h
├── help.c
├── id.h
├── inst.c
├── ldscript
├── loadrom.c
├── main.c
├── metadata.c
├── midi.c
├── misc.c
├── misc.h
├── packlist.c
├── packs.c
├── parser.c
├── play.c
├── ranges.c
├── record.ico
├── resource.rc
├── song.c
├── songed.c
├── sound.c
├── status.c
├── structs.h
├── text.c
└── tracker.c
/.github/workflows/c-cpp.yml:
--------------------------------------------------------------------------------
1 | name: C/C++ CI
2 |
3 | on:
4 | push:
5 | branches: [ "master" ]
6 | pull_request:
7 | branches: [ "master" ]
8 |
9 | jobs:
10 | build:
11 |
12 | runs-on: windows-latest
13 |
14 | steps:
15 | - uses: actions/checkout@v3
16 | - name: make
17 | run: make -C src
18 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # Object files
2 | *.o
3 | *.ko
4 | *.obj
5 | *.elf
6 |
7 | # Precompiled Headers
8 | *.gch
9 | *.pch
10 |
11 | # Libraries
12 | *.lib
13 | *.a
14 | *.la
15 | *.lo
16 |
17 | # Shared objects (inc. Windows DLLs)
18 | *.dll
19 | *.so
20 | *.so.*
21 | *.dylib
22 |
23 | # Executables
24 | *.exe
25 | *.out
26 | *.app
27 | *.i*86
28 | *.x86_64
29 | *.hex
30 |
31 | # Debug files
32 | *.dSYM/
33 | *.su
34 |
35 | # CodeBlocks files
36 | *.layout
37 | obj/
38 | *.depend
39 |
40 | # Visual Studio 2015 files
41 | Debug/
42 | Release/
43 | x64/
44 | .vs/
45 | *.db
46 | *.opendb
47 | *.vcxproj.*
--------------------------------------------------------------------------------
/.vscode/launch.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "0.2.0",
3 | "configurations": [
4 | {
5 | "name": "Release (mingw32)",
6 | "type": "cppvsdbg",
7 | "request": "launch",
8 | "program": "${workspaceFolder}/build/release/ebmused.exe",
9 | "args": [],
10 | "stopAtEntry": false,
11 | "cwd": "${workspaceFolder}/build/release",
12 | "environment": [{ "name": "config", "value": "Release" }],
13 | "preLaunchTask": "Build ebmused Release (mingw32-make)"
14 | },
15 | {
16 | "name": "Debug (mingw32)",
17 | "type": "cppdbg",
18 | "request": "launch",
19 | "program": "${workspaceFolder}/build/debug/ebmused.exe",
20 | "args": [],
21 | "stopAtEntry": false,
22 | "cwd": "${workspaceFolder}/build/debug",
23 | "environment": [{ "name": "config", "value": "Debug" }],
24 | "externalConsole": false,
25 | "MIMode": "gdb",
26 | "miDebuggerPath": "gdb",
27 | "setupCommands": [
28 | {
29 | "description": "Enable pretty-printing for gdb",
30 | "text": "-enable-pretty-printing",
31 | "ignoreFailures": true
32 | },
33 | {
34 | "description": "Set Disassembly Flavor to Intel",
35 | "text": "-gdb-set disassembly-flavor intel",
36 | "ignoreFailures": true
37 | }
38 | ],
39 | "preLaunchTask": "Build ebmused Debug (mingw32-make)"
40 | },
41 | ]
42 | }
43 |
--------------------------------------------------------------------------------
/.vscode/tasks.json:
--------------------------------------------------------------------------------
1 | {
2 | "version": "2.0.0",
3 | "tasks": [
4 | {
5 | "type": "shell",
6 | "label": "Build ebmused Release (mingw32-make)",
7 | "command": "mingw32-make.exe",
8 | "options": {
9 | "cwd": "${workspaceFolder}\\src"
10 | },
11 | "problemMatcher": "$gcc",
12 | "group": {
13 | "kind": "build",
14 | "isDefault": true
15 | },
16 | "detail": "Compiles ebmused via running make in /src"
17 | },
18 | {
19 | "type": "shell",
20 | "label": "Build ebmused Debug (mingw32-make)",
21 | "command": "mingw32-make.exe debug",
22 | "options": {
23 | "cwd": "${workspaceFolder}\\src"
24 | },
25 | "problemMatcher": "$gcc",
26 | "group": {
27 | "kind": "build",
28 | "isDefault": false
29 | },
30 | "detail": "Compiles ebmused via running make in /src"
31 | }
32 | ]
33 | }
34 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2016 Chris
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## EarthBound Music Editor
2 | EBMusEd is a ROM hacking tool for editing and playing back EarthBound's N-SPC sequence data.
3 |
4 | For a tutorial on how to use it, check out the collection of EarthBound hacking-related tutorials on the [CoilSnake Wiki](https://github.com/pk-hack/CoilSnake/wiki/EBMusEd), as well as the `Code list` reference in the Help menu.
5 |
6 | ## Background
7 | EBMusEd was originally written by Goplat, who publicly posted the source code on [starmen.net](https://forum.starmen.net/forum)'s PK Hack subforum.
8 |
9 | Goplat has kindly provided the source for the community to continue working on it together.
10 |
11 | ## Building with Visual Studio Code in Windows
12 | - Have MinGW installed.
13 | - Open the `/ebmused` directory in VS Code and press CTRL+Shift+B.
14 | - The executable will be build to `/build/release/ebmused.exe`
15 |
--------------------------------------------------------------------------------
/ide/codeblocks/ebmused.cbp:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
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 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 |
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
97 |
98 |
99 |
100 |
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 |
121 |
--------------------------------------------------------------------------------
/ide/vs2015/ebmused.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 14
4 | VisualStudioVersion = 14.0.25420.1
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ebmused", "ebmused.vcxproj", "{490C16A6-0881-4332-A0E9-9E7CAF569779}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|x86 = Debug|x86
11 | Release|x86 = Release|x86
12 | EndGlobalSection
13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
14 | {490C16A6-0881-4332-A0E9-9E7CAF569779}.Debug|x86.ActiveCfg = Debug|Win32
15 | {490C16A6-0881-4332-A0E9-9E7CAF569779}.Debug|x86.Build.0 = Debug|Win32
16 | {490C16A6-0881-4332-A0E9-9E7CAF569779}.Release|x86.ActiveCfg = Release|Win32
17 | {490C16A6-0881-4332-A0E9-9E7CAF569779}.Release|x86.Build.0 = Release|Win32
18 | EndGlobalSection
19 | GlobalSection(SolutionProperties) = preSolution
20 | HideSolutionNode = FALSE
21 | EndGlobalSection
22 | EndGlobal
23 |
--------------------------------------------------------------------------------
/ide/vs2015/ebmused.vcxproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | Win32
7 |
8 |
9 | Release
10 | Win32
11 |
12 |
13 | Debug
14 | x64
15 |
16 |
17 | Release
18 | x64
19 |
20 |
21 |
22 | {490C16A6-0881-4332-A0E9-9E7CAF569779}
23 | ebmused
24 | 10.0.17134.0
25 |
26 |
27 |
28 | Application
29 | true
30 | v141
31 | MultiByte
32 |
33 |
34 | Application
35 | false
36 | v141
37 | true
38 | MultiByte
39 |
40 |
41 | Application
42 | true
43 | v141
44 | MultiByte
45 |
46 |
47 | Application
48 | false
49 | v141
50 | true
51 | MultiByte
52 |
53 |
54 | false
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 |
75 |
76 |
77 | Level3
78 | Disabled
79 | true
80 | _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)
81 | EnableFastChecks
82 | 8Bytes
83 |
84 |
85 | comctl32.lib;winmm.lib;%(AdditionalDependencies)
86 |
87 |
88 |
89 |
90 | Level3
91 | Disabled
92 | true
93 | _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)
94 | EnableFastChecks
95 |
96 |
97 | comctl32.lib;winmm.lib;%(AdditionalDependencies)
98 |
99 |
100 |
101 |
102 | Level3
103 | MaxSpeed
104 | true
105 | true
106 | true
107 | _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)
108 |
109 |
110 | true
111 | true
112 | gdi32.lib;user32.lib;kernel32.lib;comdlg32.lib;comctl32.lib;winmm.lib
113 |
114 |
115 |
116 |
117 | Level3
118 | MaxSpeed
119 | true
120 | true
121 | true
122 | _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)
123 |
124 |
125 | true
126 | true
127 | gdi32.lib;user32.lib;kernel32.lib;comdlg32.lib;comctl32.lib;winmm.lib
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
--------------------------------------------------------------------------------
/ide/vs2019/ebmused.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 14
4 | VisualStudioVersion = 14.0.25420.1
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "ebmused", "ebmused.vcxproj", "{490C16A6-0881-4332-A0E9-9E7CAF569779}"
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 | {490C16A6-0881-4332-A0E9-9E7CAF569779}.Debug|x64.ActiveCfg = Debug|x64
17 | {490C16A6-0881-4332-A0E9-9E7CAF569779}.Debug|x64.Build.0 = Debug|x64
18 | {490C16A6-0881-4332-A0E9-9E7CAF569779}.Debug|x86.ActiveCfg = Debug|Win32
19 | {490C16A6-0881-4332-A0E9-9E7CAF569779}.Debug|x86.Build.0 = Debug|Win32
20 | {490C16A6-0881-4332-A0E9-9E7CAF569779}.Release|x64.ActiveCfg = Release|x64
21 | {490C16A6-0881-4332-A0E9-9E7CAF569779}.Release|x64.Build.0 = Release|x64
22 | {490C16A6-0881-4332-A0E9-9E7CAF569779}.Release|x86.ActiveCfg = Release|Win32
23 | {490C16A6-0881-4332-A0E9-9E7CAF569779}.Release|x86.Build.0 = Release|Win32
24 | EndGlobalSection
25 | GlobalSection(SolutionProperties) = preSolution
26 | HideSolutionNode = FALSE
27 | EndGlobalSection
28 | EndGlobal
29 |
--------------------------------------------------------------------------------
/ide/vs2019/ebmused.vcxproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | Win32
7 |
8 |
9 | Release
10 | Win32
11 |
12 |
13 | Debug
14 | x64
15 |
16 |
17 | Release
18 | x64
19 |
20 |
21 |
22 | {490C16A6-0881-4332-A0E9-9E7CAF569779}
23 | ebmused
24 | 10.0
25 |
26 |
27 |
28 | Application
29 | true
30 | v142
31 | MultiByte
32 |
33 |
34 | Application
35 | false
36 | v142
37 | true
38 | MultiByte
39 |
40 |
41 | Application
42 | true
43 | v142
44 | MultiByte
45 |
46 |
47 | Application
48 | false
49 | v142
50 | true
51 | MultiByte
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 | false
74 |
75 |
76 |
77 | Level3
78 | Disabled
79 | true
80 | _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)
81 | EnableFastChecks
82 | 8Bytes
83 |
84 |
85 | comctl32.lib;winmm.lib;%(AdditionalDependencies)
86 |
87 |
88 |
89 |
90 | Level3
91 | Disabled
92 | true
93 | _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)
94 | EnableFastChecks
95 |
96 |
97 | comctl32.lib;winmm.lib;%(AdditionalDependencies)
98 |
99 |
100 |
101 |
102 | Level3
103 | MaxSpeed
104 | true
105 | true
106 | true
107 | _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)
108 |
109 |
110 | true
111 | true
112 | gdi32.lib;user32.lib;kernel32.lib;comdlg32.lib;comctl32.lib;winmm.lib;shell32.lib
113 |
114 |
115 |
116 |
117 | Level3
118 | MaxSpeed
119 | true
120 | true
121 | true
122 | _CRT_SECURE_NO_WARNINGS;%(PreprocessorDefinitions)
123 |
124 |
125 | true
126 | true
127 | gdi32.lib;user32.lib;kernel32.lib;comdlg32.lib;comctl32.lib;winmm.lib;shell32.lib
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
138 |
139 |
140 |
141 |
142 |
143 |
144 |
145 |
146 |
147 |
148 |
149 |
150 |
151 |
152 |
153 |
154 |
155 |
156 |
157 |
158 |
159 |
160 |
161 |
162 |
163 |
164 |
--------------------------------------------------------------------------------
/src/Makefile:
--------------------------------------------------------------------------------
1 | SRCS = bgmlist.c brr.c ctrltbl.c help.c inst.c loadrom.c main.c metadata.c midi.c misc.c packlist.c packs.c parser.c play.c ranges.c resource.c song.c songed.c sound.c text.c tracker.c status.c
2 |
3 | BUILD_DIR = ../build
4 | RELEASE_DIR = $(BUILD_DIR)/release
5 | DEBUG_DIR = $(BUILD_DIR)/debug
6 | OBJ_DIR = obj
7 | DEBUG_O_DIR = $(DEBUG_DIR)/$(OBJ_DIR)
8 | RELEASE_O_DIR = $(RELEASE_DIR)/$(OBJ_DIR)
9 |
10 | O = $(SRCS:%.c=%.o)
11 | RELEASE_O = $(addprefix $(RELEASE_O_DIR)/, $O)
12 | DEBUG_O = $(addprefix $(DEBUG_O_DIR)/, $O)
13 |
14 | CC = gcc -W -Wall -Werror=vla -std=gnu99 -fno-exceptions
15 |
16 | RELEASE_FLAGS = -DNDEBUG -Os
17 | DEBUG_FLAGS = -Og -g
18 | LINK_FLAGS = -lcomctl32 -lcomdlg32 -lgdi32 -lwinmm
19 |
20 | release: $(RELEASE_DIR)/ebmused.exe
21 | debug: $(DEBUG_DIR)/ebmused.exe
22 |
23 | $(RELEASE_DIR)/ebmused.exe: $(RELEASE_O)
24 | $(CC) $(RELEASE_O) -static-libgcc -mwindows -s $(LINK_FLAGS) -o $@
25 |
26 | $(RELEASE_O_DIR)/%.o: %.c | $(RELEASE_O_DIR)
27 | $(CC) $(RELEASE_FLAGS) -c $< -o $@
28 |
29 | $(DEBUG_DIR)/ebmused.exe: $(DEBUG_O)
30 | $(CC) $(DEBUG_O) $(LINK_FLAGS) -o $@
31 |
32 | $(DEBUG_O_DIR)/%.o: %.c | $(DEBUG_O_DIR)
33 | $(CC) $(DEBUG_FLAGS) -c $< -o $@
34 |
35 | %/$(OBJ_DIR)/resource.o: resource.rc
36 | windres $< -o $@
37 |
38 | %/$(OBJ_DIR):
39 | mkdir $(subst /,\,$(BUILD_DIR))
40 | mkdir $(subst /,\,$(DEBUG_DIR))
41 | mkdir $(subst /,\,$(DEBUG_O_DIR))
42 | mkdir $(subst /,\,$(RELEASE_DIR))
43 | mkdir $(subst /,\,$(RELEASE_O_DIR))
44 |
--------------------------------------------------------------------------------
/src/bgmlist.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #define WIN32_LEAN_AND_MEAN
4 | #include
5 | #include "ebmusv2.h"
6 | #include "misc.h"
7 | #include "id.h"
8 |
9 | #define IDC_ROM_FILE 17
10 | #define IDC_ORIG_ROM_FILE 18
11 | #define IDC_ROM_SIZE 19
12 |
13 | #define IDC_LIST 20
14 | #define IDC_SEARCH_TEXT 21
15 | #define IDC_SEARCH 22
16 | #define IDC_TITLE 23
17 | #define IDC_TITLE_CHANGE 24
18 |
19 | #define IDC_BGM_NUMBER 25
20 | #define IDC_BGM_IPACK_1 30
21 | #define IDC_BGM_IPACK_2 31
22 | #define IDC_BGM_SPACK 32
23 | #define IDC_BGM_SPCADDR 33
24 | #define IDC_SAVE_INFO 34
25 |
26 | #define IDC_CUR_IPACK_1 35
27 | #define IDC_CUR_IPACK_2 36
28 | #define IDC_CUR_SPACK 37
29 | #define IDC_CUR_SPCADDR 38
30 |
31 | #define IDC_LOAD_BGM 40
32 | #define IDC_CHANGE_BGM 41
33 |
34 | int selected_bgm = 0;
35 | static char bgm_num_text[32] = "BGM --:";
36 |
37 | static const struct control_desc bgm_list_controls[] = {
38 | { "ListBox", 10, 10,300,-20, NULL, IDC_LIST, WS_BORDER | LBS_NOTIFY | WS_VSCROLL },
39 |
40 | { "Static", 310, 10, 90, 20, "Current ROM:", 0, SS_RIGHT },
41 | { "Static", 410, 10,1000,20, NULL, IDC_ROM_FILE, 0 },
42 | { "Static", 310, 30, 90, 20, "Original ROM:", 0, SS_RIGHT },
43 | { "Static", 410, 30,1000,20, NULL, IDC_ORIG_ROM_FILE, SS_NOTIFY },
44 | { "Static", 310, 50, 90, 20, "Size:", 0, SS_RIGHT },
45 | { "Static", 410, 50,100, 20, NULL, IDC_ROM_SIZE, 0 },
46 |
47 | { "Static", 410,110,100, 20, bgm_num_text, IDC_BGM_NUMBER, 0 },
48 | { "Static", 530,110,100, 20, "Currently loaded:", IDC_BGM_NUMBER+1, 0 },
49 | { "Static", 315,133, 90, 20, "Inst. packs:", 0, SS_RIGHT },
50 | { "Edit", 410,130, 25, 20, NULL, IDC_BGM_IPACK_1, WS_BORDER }, //(ROM) Main Pack textbox
51 | { "Edit", 440,130, 25, 20, NULL, IDC_BGM_IPACK_2, WS_BORDER }, //(ROM) Secondary Pack textbox
52 | { "Edit", 530,130, 25, 20, NULL, IDC_CUR_IPACK_1, WS_BORDER }, //(Current) Main Pack textbox
53 | { "Edit", 560,130, 25, 20, NULL, IDC_CUR_IPACK_2, WS_BORDER }, //(Current) Secondary Pack textbox
54 | { "Static", 325,157, 80, 20, "Music pack:", 0, SS_RIGHT },
55 | { "Edit", 410,155, 25, 20, NULL, IDC_BGM_SPACK, WS_BORDER }, //(ROM) Music Pack textbox
56 | { "Edit", 530,155, 25, 20, NULL, IDC_CUR_SPACK, WS_BORDER }, //(Current) Music Pack textbox
57 | { "Static", 325,182, 80, 20, "Start address:", 0, SS_RIGHT },
58 | { "Edit", 410,180, 55, 20, NULL, IDC_BGM_SPCADDR, WS_BORDER }, //(ROM) Music ARAM textbox
59 | {"ComboBox",530,180, 55, 200, NULL, IDC_CUR_SPCADDR, CBS_DROPDOWNLIST | WS_VSCROLL }, //(Current) Music ARAM ComboBox
60 | { "Button", 485,130, 25, 30, "-->", IDC_LOAD_BGM, 0 },
61 | { "Button", 485,170, 25, 30, "<--", IDC_CHANGE_BGM, 0 },
62 | { "Button", 353,205,112, 20, "Update BGM Table", IDC_SAVE_INFO, 0 },
63 | { "Edit", 320,250,230, 20, NULL, IDC_SEARCH_TEXT, WS_BORDER },
64 | { "Button", 560,250, 60, 20, "Search", IDC_SEARCH, 0 },
65 | { "Edit", 320,275,230, 20, NULL, IDC_TITLE, WS_BORDER | ES_AUTOHSCROLL },
66 | { "Button", 560,275, 60, 20, "Rename", IDC_TITLE_CHANGE, 0 },
67 | };
68 | static struct window_template bgm_list_template = {
69 | sizeof(bgm_list_controls) / sizeof(*bgm_list_controls),
70 | sizeof(bgm_list_controls) / sizeof(*bgm_list_controls),
71 | 0, 0, bgm_list_controls
72 | };
73 |
74 | static void set_bgm_info(BYTE *packs_used, int spc_addr) {
75 | for (int i = 0; i < 3; i++)
76 | SetDlgItemHex(hwndBGMList, IDC_BGM_IPACK_1+i, packs_used[i], 2);
77 | SetDlgItemHex(hwndBGMList, IDC_BGM_SPCADDR, spc_addr, 4);
78 | }
79 |
80 | static void show_bgm_info() {
81 | sprintf(bgm_num_text + 4, "%d (0x%02X):", selected_bgm+1, selected_bgm+1);
82 | SetDlgItemText(hwndBGMList, IDC_BGM_NUMBER, bgm_num_text);
83 | SetDlgItemText(hwndBGMList, IDC_TITLE, bgm_title[selected_bgm]);
84 | set_bgm_info(pack_used[selected_bgm], song_address[selected_bgm]);
85 | }
86 |
87 | static void show_cur_info() {
88 | for (int i = 0; i < 3; i++)
89 | SetDlgItemHex(hwndBGMList, IDC_CUR_IPACK_1+i, packs_loaded[i], 2);
90 |
91 | HWND cb = GetDlgItem(hwndBGMList, IDC_CUR_SPCADDR);
92 | SendMessage(cb, CB_RESETCONTENT, 0, 0);
93 | SendMessage(cb, CB_ADDSTRING, 0, (LPARAM)"----");
94 | int song_pack = packs_loaded[2];
95 | if (song_pack < NUM_PACKS) {
96 | struct pack *p = &inmem_packs[song_pack];
97 | for (int i = 0; i < p->block_count; i++) {
98 | char buf[5];
99 | sprintf(buf, "%04X", p->blocks[i].spc_address);
100 | SendMessage(cb, CB_ADDSTRING, 0, (LPARAM)buf);
101 | }
102 | }
103 | SendMessage(cb, CB_SETCURSEL, current_block + 1, 0);
104 | }
105 |
106 | void load_instruments() {
107 | free_samples();
108 | memset(spc, 0, 0x10000);
109 | for (int i = 0; i < 2; i++) {
110 | int p = packs_loaded[i];
111 | if (p >= NUM_PACKS) continue;
112 | int addr, size;
113 | fseek(rom, rom_packs[p].start_address - 0xC00000 + rom_offset, 0);
114 | while ((size = fgetw(rom))) {
115 | addr = fgetw(rom);
116 | if (size + addr >= 0x10000) {
117 | MessageBox2("Invalid SPC block", "Error loading instruments", MB_ICONERROR);
118 | return;
119 | }
120 | fread(&spc[addr], size, 1, rom);
121 | }
122 | }
123 | sample_ptr_base = 0x6C00;
124 | decode_samples(&spc[sample_ptr_base]);
125 | inst_base = 0x6E00;
126 | if (samp[0].data == NULL) {
127 | stop_playing();
128 | EnableMenuItem(hmenu, ID_PLAY, MF_ENABLED);
129 | }
130 | initialize_state();
131 | }
132 |
133 | static void load_music(BYTE *packs_used, int spc_addr) {
134 | packs_loaded[0] = packs_used[0];
135 | packs_loaded[1] = packs_used[1];
136 | load_songpack(packs_used[2]);
137 | select_block_by_address(spc_addr);
138 | show_cur_info();
139 | load_instruments();
140 | }
141 |
142 | static void song_selected(int index) {
143 | selected_bgm = index;
144 | show_bgm_info();
145 | load_music(pack_used[index], song_address[index]);
146 | }
147 |
148 | static void song_search() {
149 | char str[MAX_TITLE_LEN+1];
150 | char *endhex;
151 | GetDlgItemText(hwndBGMList, IDC_SEARCH_TEXT, str, MAX_TITLE_LEN+1);
152 | int num = strtol(str, &endhex, 16) - 1;
153 | if (*endhex != '\0' || num < 0 || num >= NUM_SONGS) {
154 | num = selected_bgm;
155 | _strlwr(str);
156 | do {
157 | char title[MAX_TITLE_LEN+1];
158 | if (++num == NUM_SONGS) num = 0;
159 | if (strstr(_strlwr(strcpy(title, bgm_title[num])), str))
160 | break;
161 | } while (num != selected_bgm);
162 | }
163 | SendDlgItemMessage(hwndBGMList, IDC_LIST, LB_SETCURSEL, num, 0);
164 | song_selected(num);
165 | }
166 |
167 | LRESULT CALLBACK BGMListWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
168 | char buf[MAX_TITLE_LEN+5];
169 | switch (uMsg) {
170 | case WM_CREATE:
171 | create_controls(hWnd, &bgm_list_template, lParam);
172 | break;
173 | case WM_SIZE:
174 | move_controls(hWnd, &bgm_list_template, lParam);
175 | break;
176 | case WM_ROM_OPENED:
177 | SetDlgItemText(hWnd, IDC_ROM_FILE, rom_filename);
178 | SetDlgItemText(hWnd, IDC_ORIG_ROM_FILE, orig_rom_filename
179 | ? orig_rom_filename : "None specified (click to set)");
180 | sprintf(buf, "%.2f MB", rom_size / 1048576.0);
181 | SetDlgItemText(hWnd, IDC_ROM_SIZE, buf);
182 | HWND list = GetDlgItem(hWnd, IDC_LIST);
183 | SendMessage(list, WM_SETREDRAW, FALSE, 0);
184 | for (int i = 0; i < NUM_SONGS; i++) {
185 | sprintf(buf, "%02X: %s", i+1, bgm_title[i]);
186 | SendMessage(list, LB_ADDSTRING, 0, (LPARAM)buf);
187 | }
188 | SendMessage(list, WM_SETREDRAW, TRUE, 0);
189 | SendMessage(list, LB_SETCURSEL, selected_bgm, 0);
190 | SetFocus(list);
191 | show_bgm_info();
192 | for (int i = 20; i <= 41; i++)
193 | EnableWindow(GetDlgItem(hWnd, i), TRUE);
194 | // fallthrough
195 | case WM_SONG_IMPORTED:
196 | show_cur_info();
197 | break;
198 | case WM_ROM_CLOSED:
199 | SetDlgItemText(hWnd, IDC_ROM_FILE, NULL);
200 | SetDlgItemText(hWnd, IDC_ORIG_ROM_FILE, NULL);
201 | SetDlgItemText(hWnd, IDC_ROM_SIZE, NULL);
202 | SendDlgItemMessage(hWnd, IDC_LIST, LB_RESETCONTENT, 0, 0);
203 | for (int i = 20; i <= 41; i++)
204 | EnableWindow(GetDlgItem(hWnd, i), FALSE);
205 | break;
206 | case WM_COMMAND: {
207 | WORD id = LOWORD(wParam), action = HIWORD(wParam);
208 | switch (id) {
209 | case IDC_ORIG_ROM_FILE:
210 | if (!rom) break;
211 | if (get_original_rom())
212 | SetWindowText((HWND)lParam, orig_rom_filename);
213 | break;
214 | case IDC_SEARCH:
215 | song_search();
216 | break;
217 | case IDC_LIST:
218 | if (action == LBN_SELCHANGE)
219 | song_selected(SendMessage((HWND)lParam, LB_GETCURSEL, 0, 0));
220 | break;
221 | case IDC_TITLE_CHANGE: {
222 | if (bgm_title[selected_bgm] != bgm_orig_title[selected_bgm])
223 | free(bgm_title[selected_bgm]);
224 | GetDlgItemText(hWnd, IDC_TITLE, buf+4, MAX_TITLE_LEN+1);
225 | bgm_title[selected_bgm] = _strdup(buf+4);
226 | sprintf(buf, "%02X:", selected_bgm + 1);
227 | buf[3] = ' ';
228 | SendDlgItemMessage(hWnd, IDC_LIST, LB_DELETESTRING, selected_bgm, 0);
229 | SendDlgItemMessage(hWnd, IDC_LIST, LB_INSERTSTRING, selected_bgm, (LPARAM)buf);
230 | SendDlgItemMessage(hWnd, IDC_LIST, LB_SETCURSEL, selected_bgm, 0);
231 |
232 | metadata_changed = TRUE;
233 | break;
234 | }
235 | case IDC_SAVE_INFO: {
236 | BYTE new_pack_used[3];
237 | for (int i = 0; i < 3; i++) {
238 | int pack = GetDlgItemHex(hWnd, IDC_BGM_IPACK_1 + i);
239 | if (pack < 0) break;
240 | new_pack_used[i] = pack;
241 | }
242 | int new_spc_address = GetDlgItemHex(hWnd, IDC_BGM_SPCADDR);
243 | if (new_spc_address < 0 || new_spc_address > 0xFFFF) break;
244 |
245 | fseek(rom, BGM_PACK_TABLE + rom_offset + 3 * selected_bgm, SEEK_SET);
246 | if (!fwrite(new_pack_used, 3, 1, rom)) {
247 | write_error: MessageBox2(strerror(errno), "Save", MB_ICONERROR);
248 | break;
249 | }
250 | memcpy(&pack_used[selected_bgm], new_pack_used, 3);
251 | fseek(rom, song_pointer_table_offset + 2 * selected_bgm, SEEK_SET);
252 | if (!fwrite(&new_spc_address, 2, 1, rom))
253 | goto write_error;
254 | song_address[selected_bgm] = new_spc_address;
255 | fflush(rom);
256 | sprintf(buf, "Info for BGM %02X saved!", selected_bgm + 1);
257 | MessageBox2(buf, "BGM Table Updated", MB_OK);
258 | break;
259 | }
260 | case IDC_CUR_IPACK_1:
261 | case IDC_CUR_IPACK_2:
262 | case IDC_CUR_SPACK:
263 | if (action == EN_KILLFOCUS) {
264 | int num = GetDlgItemHex(hWnd, id);
265 | if (num < 0 || packs_loaded[id - IDC_CUR_IPACK_1] == num)
266 | break;
267 | if (id == IDC_CUR_SPACK) {
268 | load_songpack(num);
269 | select_block(-1);
270 | show_cur_info();
271 | } else {
272 | packs_loaded[id - IDC_CUR_IPACK_1] = num;
273 | load_instruments();
274 | }
275 | }
276 | break;
277 | case IDC_CUR_SPCADDR:
278 | if (action == CBN_SELCHANGE) {
279 | select_block(SendMessage((HWND)lParam, CB_GETCURSEL, 0, 0) - 1);
280 | }
281 | break;
282 | case IDC_LOAD_BGM: {
283 | BYTE pack_used[3];
284 | int spc_address;
285 | for (int i = 0; i < 3; i++) {
286 | pack_used[i] = GetDlgItemHex(hWnd, IDC_BGM_IPACK_1 + i);
287 | }
288 | spc_address = GetDlgItemHex(hWnd, IDC_BGM_SPCADDR);
289 | load_music(pack_used, spc_address);
290 | break;
291 | }
292 | case IDC_CHANGE_BGM:
293 | { struct block *b = get_cur_block();
294 | if (b) set_bgm_info(packs_loaded, b->spc_address);
295 | }
296 | break;
297 | }
298 | break;
299 | }
300 | default:
301 | return DefWindowProc(hWnd, uMsg, wParam, lParam);
302 | }
303 | return 0;
304 | }
305 |
--------------------------------------------------------------------------------
/src/blank.spc:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PKHackers/ebmused/272b0123859a46034937346e5e52ab8f3d4d9d3b/src/blank.spc
--------------------------------------------------------------------------------
/src/brr.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include "ebmusv2.h"
6 |
7 | enum {
8 | BRR_FLAG_END = 1,
9 | BRR_FLAG_LOOP = 2,
10 | BRR_BLOCK_SIZE = 9
11 | };
12 |
13 | struct sample samp[128];
14 | WORD sample_ptr_base = 0x6C00;
15 |
16 | // Counts and returns the number of BRR blocks from a specific location in memory.
17 | // This makes no attempt to simulate the behavior of the SPC on key on. It ignores the header of a
18 | // block on key on. That would complicate decoding, because you could have a loop that results in a
19 | // sample that ends, or that has a second loop point, and... no one does that. Right?
20 | // The count should never be greater than 7281 since that would take more memory than the SPC has.
21 | unsigned int count_brr_blocks(const uint8_t *spc_memory, uint16_t start) {
22 | unsigned int count = 0;
23 | uint8_t b = 0;
24 | // Count blocks until one has the end flag or there's not enough space for another to be in RAM.
25 | while (!(b & BRR_FLAG_END) && start + (count + 1) * BRR_BLOCK_SIZE <= 0xFFFF) {
26 | b = spc_memory[start + count*BRR_BLOCK_SIZE];
27 | count++;
28 | }
29 |
30 | // Return 0 if we reached the end of RAM and the last block doesn't have the end flag
31 | return !(b & BRR_FLAG_END) ? 0 : count;
32 | }
33 |
34 | static void decode_brr_block(int16_t *buffer, const uint8_t *block, BOOL first_block) {
35 | int range = block[0] >> 4;
36 | int filter = (block[0] >> 2) & 3;
37 |
38 | if (first_block) {
39 | // Assume filter 0 here no matter what.
40 | // Not enforcing this could result in a read out of bounds.
41 | filter = 0;
42 | }
43 |
44 | for (int i = 2; i < 18; i++) {
45 | int32_t s = block[i / 2];
46 |
47 | if (i % 2 == 0) {
48 | s >>= 4;
49 | } else {
50 | s &= 0x0F;
51 | }
52 |
53 | if (s >= 8) {
54 | s -= 16;
55 | }
56 |
57 | s *= 1 << range;
58 | s >>= 1;
59 | if (range > 12) {
60 | s = (s < 0) ? -(1 << 11) : 0;
61 | }
62 |
63 | switch (filter) {
64 | // The exact formulas used here affect how stable loop points are,
65 | // which can wildly change the way certain glitch samples sound,
66 | // like KungFuFurby's 3-block "SuperMiniGlitchNoise.brr."
67 | case 1:
68 | s += (1 * (buffer[-1] >> 1)) + ((1 * -(buffer[-1] >> 1)) >> 4);
69 | break;
70 | case 2:
71 | s += (2 * (buffer[-1] >> 1)) + ((3 * -(buffer[-1] >> 1)) >> 5);
72 | s += (-(buffer[-2] >> 1)) + ((1 * (buffer[-2] >> 1)) >> 4);
73 | break;
74 | case 3:
75 | s += (2 * (buffer[-1] >> 1)) + ((13 * -(buffer[-1] >> 1)) >> 6);
76 | s += (-(buffer[-2] >> 1)) + ((3 * (buffer[-2] >> 1)) >> 4);
77 | break;
78 | }
79 |
80 | s *= 2;
81 |
82 | // Clamp to [-65536, 65534] and then have it wrap around at
83 | // [-32768, 32767]
84 | if (s < -0x10000) s = (-0x10000 + 0x10000);
85 | else if (s > 0xFFFE) s = (0xFFFE - 0x10000);
86 | else if (s < -0x8000) s += 0x10000;
87 | else if (s > 0x7FFF) s -= 0x10000;
88 |
89 | *buffer++ = s;
90 | }
91 | }
92 |
93 | static int get_full_loop_len(const struct sample *sa, const int16_t *next_block, int first_loop_start) {
94 | int loop_start = sa->length - sa->loop_len;
95 | int no_match_found = TRUE;
96 | while (loop_start >= first_loop_start && no_match_found) {
97 | // If the first two samples in a loop are the same, the rest all will be too.
98 | // BRR filters can rely on, at most, two previous samples.
99 | if (sa->data[loop_start] == next_block[0] &&
100 | sa->data[loop_start + 1] == next_block[1]) {
101 | no_match_found = FALSE;
102 | } else {
103 | loop_start -= sa->loop_len;
104 | }
105 | }
106 |
107 | if (loop_start >= first_loop_start)
108 | return sa->length - loop_start;
109 | else
110 | return -1;
111 | }
112 |
113 | void decode_samples(const unsigned char *ptrtable) {
114 | for (unsigned sn = 0; sn < 128; sn++) {
115 | struct sample *sa = &samp[sn];
116 | uint16_t start = ptrtable[0] | (ptrtable[1] << 8);
117 | uint16_t loop = ptrtable[2] | (ptrtable[3] << 8);
118 | ptrtable += 4;
119 |
120 | sa->data = NULL;
121 | if (start == 0 || start == 0xffff)
122 | continue;
123 |
124 | unsigned int num_blocks = count_brr_blocks(spc, start);
125 | if (num_blocks == 0)
126 | continue;
127 |
128 | uint16_t end = start + num_blocks * BRR_BLOCK_SIZE;
129 | sa->length = num_blocks * 16;
130 | // The LOOP bit only matters for the last brr block
131 | if (spc[start + (num_blocks - 1) * BRR_BLOCK_SIZE] & BRR_FLAG_LOOP) {
132 | if (loop < start || loop >= end || (loop - start) % BRR_BLOCK_SIZE)
133 | continue;
134 | sa->loop_len = ((end - loop) / BRR_BLOCK_SIZE) * 16;
135 | } else
136 | sa->loop_len = 0;
137 |
138 | size_t allocation_size = sizeof(int16_t) * (sa->length + 3);
139 |
140 | int16_t *p = malloc(allocation_size);
141 | if (!p) {
142 | printf("malloc failed in BRR decoding (sn: %02X)\n", sn);
143 | continue;
144 | }
145 |
146 | sa->data = p;
147 |
148 | int needs_another_loop;
149 | int first_block = TRUE;
150 | int decoding_start = 0; // Index of BRR where decoding begins.
151 | int times = 0;
152 |
153 | do {
154 | needs_another_loop = FALSE;
155 |
156 | for (int i = decoding_start; i < num_blocks; i++) {
157 | decode_brr_block(p, &spc[start + i*BRR_BLOCK_SIZE], first_block);
158 | p += 16;
159 | first_block = FALSE;
160 | }
161 |
162 | if (sa->loop_len != 0) {
163 | decoding_start = (loop - start) / BRR_BLOCK_SIZE; // Start decoding from "loop" BRR block.
164 |
165 | int16_t after_loop[18];
166 | after_loop[0] = p[-2];
167 | after_loop[1] = p[-1];
168 |
169 | decode_brr_block(&after_loop[2], &spc[loop], FALSE);
170 | int full_loop_len = get_full_loop_len(sa, &after_loop[2], (loop - start) / BRR_BLOCK_SIZE * 16);
171 |
172 | if (full_loop_len == -1) {
173 | needs_another_loop = TRUE;
174 | ptrdiff_t diff = p - sa->data;
175 | int16_t *new_stuff = realloc(sa->data, (sa->length + sa->loop_len + 3) * sizeof(int16_t));
176 | if (new_stuff == NULL) {
177 | printf("realloc failed in BRR decoding (sn: %02X)\n", sn);
178 | // TODO What do we do now? Replace this with something better
179 | needs_another_loop = FALSE;
180 | break;
181 | }
182 | p = new_stuff + diff;
183 | sa->length += sa->loop_len;
184 | sa->data = new_stuff;
185 | } else {
186 | sa->loop_len = full_loop_len;
187 | // needs_another_loop is already false
188 | }
189 | }
190 |
191 | // In the vanilla game, the most iterations needed is 79 (for sample 0x19 in pack 5).
192 | // Most samples need less than 10 or 15.
193 | // Other examples of very unstable samples:
194 | // - A Link to the Past glitchy sample 00: 124 iterations
195 | // - KungFuFurby 3-block glitch noise sample: 231 iterations
196 | // - Other grievous offenders exist, but I don't remember much about them
197 | ++times;
198 | } while (needs_another_loop && times < 512);
199 |
200 | if (needs_another_loop) {
201 | printf("Sample %02X took too many iterations to get into a cycle\n", sn);
202 | }
203 |
204 | // Put 3 extra samples at the end for easier interpolation
205 | if (sa->loop_len != 0) {
206 | p[0] = sa->data[sa->length - sa->loop_len + 0];
207 | p[1] = sa->data[sa->length - sa->loop_len + 1];
208 | p[2] = sa->data[sa->length - sa->loop_len + 2];
209 | } else {
210 | p[0] = 0;
211 | p[1] = 0;
212 | p[2] = 0;
213 | }
214 | }
215 | }
216 |
217 | void free_samples(void) {
218 | for (int sn = 0; sn < 128; sn++) {
219 | free(samp[sn].data);
220 | samp[sn].data = NULL;
221 | }
222 | }
223 |
--------------------------------------------------------------------------------
/src/ctrltbl.c:
--------------------------------------------------------------------------------
1 | #define WIN32_LEAN_AND_MEAN
2 | #include
3 | #include
4 | #include
5 | #include "ebmusv2.h"
6 | #include "misc.h"
7 |
8 | void create_controls(HWND hWnd, struct window_template *t, LPARAM cs) {
9 | int width = ((CREATESTRUCT *)cs)->cx;
10 | int winheight = ((CREATESTRUCT *)cs)->cy;
11 |
12 | assert(width <= 0xFFFF);
13 | assert(winheight <= 0xFFFF);
14 | t->winsize = MAKELONG(width, winheight);
15 |
16 | // Start with the upper half of the window
17 | int top = 0;
18 | int height = t->divy;
19 |
20 | const struct control_desc *c = t->controls;
21 |
22 | for (int num = t->num; num; num--, c++) {
23 | int x = scale_x(c->x);
24 | int y = scale_y(c->y);
25 | int xsize = scale_x(c->xsize);
26 | int ysize = scale_y(c->ysize);
27 | if (num == t->lower) {
28 | // Switch to the lower half of the window
29 | top = height;
30 | height = winheight - top;
31 | }
32 | if (x < 0) x += width;
33 | if (y < 0) y += height;
34 | if (xsize <= 0) xsize += width;
35 | if (ysize <= 0) ysize += height;
36 |
37 | HWND w = CreateWindow(c->class, c->title,
38 | WS_CHILD | WS_VISIBLE | c->style,
39 | x, top + y, xsize, ysize,
40 | hWnd, (HMENU)c->id, hinstance, NULL);
41 |
42 | // Override the font, if it's not a "Sys" class that handles that normally
43 | if (c->class[1] != 'y')
44 | SendMessage(w, WM_SETFONT, (WPARAM)default_font(), 0);
45 | }
46 | }
47 |
48 | void move_controls(HWND hWnd, struct window_template *t, LPARAM lParam) {
49 | int width = LOWORD(lParam);
50 | int top, height;
51 | int i = 0;
52 | int dir = 1;
53 | int end = t->num;
54 | // move controls in reverse order when making the window larger,
55 | // so that they don't get drawn on top of each other
56 | if (lParam > t->winsize) {
57 | i = t->num - 1;
58 | dir = -1;
59 | end = -1;
60 | }
61 | for (; i != end; i += dir) {
62 | const struct control_desc *c = &t->controls[i];
63 | int x = scale_x(c->x);
64 | int y = scale_y(c->y);
65 | int xsize = scale_x(c->xsize);
66 | int ysize = scale_y(c->ysize);
67 | if (i < (t->num - t->lower)) {
68 | top = 0;
69 | height = t->divy;
70 | } else {
71 | top = t->divy;
72 | height = HIWORD(lParam) - t->divy;
73 | }
74 |
75 | // Don't resize controls in the upper half of the window, unless they are positioned/sized
76 | // relative to the bottom or right side of the window
77 | if (top == 0 && x >= 0 && y >= 0 && xsize > 0 && ysize > 0)
78 | continue;
79 | if (x < 0) x += width;
80 | if (y < 0) y += height;
81 | if (xsize <= 0) xsize += width;
82 | if (ysize <= 0) ysize += height;
83 | MoveWindow(GetDlgItem(hWnd, c->id), x, top + y, xsize, ysize, TRUE);
84 | }
85 | t->winsize = lParam;
86 | }
87 |
--------------------------------------------------------------------------------
/src/ebmused.exe.manifest:
--------------------------------------------------------------------------------
1 |
2 |
3 |
9 | EarthBound Music Editor
10 |
11 |
12 | true
13 |
14 |
15 |
16 |
17 |
18 |
26 |
27 |
28 |
29 |
--------------------------------------------------------------------------------
/src/ebmusv2.h:
--------------------------------------------------------------------------------
1 | #ifndef EBMUSV2_H
2 | #define EBMUSV2_H
3 |
4 | #define WIN32_LEAN_AND_MEAN
5 | #include
6 | #include "structs.h"
7 |
8 | #ifdef NDEBUG
9 | #define printf(x,...)
10 | #endif
11 |
12 | // EarthBound related constants
13 | #define NUM_SONGS 0xBF
14 | #define NUM_PACKS 0xA9
15 | #define BGM_PACK_TABLE 0x4F70A
16 | #define PACK_POINTER_TABLE 0x4F947
17 | // This value is now determined dynamically based on the location of pack 1.
18 | // See song_pointer_table_offset.
19 | // #define SONG_POINTER_TABLE 0x26298C
20 |
21 | // other constants and stuff
22 | #define MAX_TITLE_LEN 60
23 | #define MAX_TITLE_LEN_STR "60"
24 | #define WM_ROM_OPENED WM_USER
25 | #define WM_ROM_CLOSED WM_USER+1
26 | #define WM_SONG_IMPORTED WM_USER+2
27 | #define WM_SONG_LOADED WM_USER+3
28 | #define WM_SONG_NOT_LOADED WM_USER+4
29 | #define WM_PACKS_SAVED WM_USER+5
30 | #define MAX_INSTRUMENTS 128
31 |
32 | // main.c
33 | extern BYTE packs_loaded[3];
34 | extern int current_block;
35 | extern int octave;
36 | extern int midiDevice;
37 | extern int selected_bgm;
38 | extern struct song cur_song;
39 | extern struct song_state pattop_state, state;
40 | extern HINSTANCE hinstance;
41 | extern HWND hwndMain;
42 | extern HWND hwndStatus;
43 | #ifdef CreateWindow
44 | extern HMENU hmenu, hcontextmenu;
45 | #endif
46 | #define NUM_TABS 4
47 | extern HWND tab_hwnd[NUM_TABS];
48 | #define hwndBGMList tab_hwnd[0]
49 | #define hwndInstruments tab_hwnd[1]
50 | #define hwndEditor tab_hwnd[2]
51 | #define hwndPackList tab_hwnd[3]
52 | BOOL get_original_rom(void);
53 | BOOL save_all_packs(void);
54 |
55 | // bgmlist.c
56 |
57 | // brr.c
58 | extern struct sample samp[128];
59 | extern WORD sample_ptr_base;
60 | extern unsigned int count_brr_blocks(const BYTE *spc, WORD start);
61 | void decode_samples(const unsigned char *ptrtable);
62 | void free_samples(void);
63 |
64 | // ctrltbl.c
65 | struct control_desc {
66 | // Window class (a class atom or class name; the first argument to CreateWindow)
67 | const char *class;
68 | // Position and dimensions in parent window (in pixels, before DPI scaling)
69 | // Negative x and y are positioned relative to the right edge of the parent window
70 | short x;
71 | short y;
72 | // Non-positive xsize and ysize are modulo the width/height of the parent window.
73 | // This does NOT mean they specify the amount of padding from the right/bottom edge of the
74 | // window!
75 | short xsize;
76 | short ysize;
77 | // Window title (text associated with control; the second argument to CreateWindow)
78 | const char *title;
79 | // Child window identifier (IDC_ constant used in window procedure)
80 | DWORD id;
81 | // Window style (the third argument to CreateWindow)
82 | DWORD style;
83 | };
84 | struct window_template {
85 | // Number of elements in `controls`
86 | int num;
87 | // How many elements of `controls` take up the area below divy (usually equal to `num`)
88 | int lower;
89 | // Packed dimensions of window (CREATESTRUCT.cx concatenated to CREATESTRUCT.cy)
90 | DWORD winsize;
91 | // y coordinate of top of lower half of window (usually 0, so that the entire window is the
92 | // lower half).
93 | // THIS FIELD IS ALREADY DPI-SCALED! It is calculated dynamically for tabs that actually use it.
94 | int divy;
95 | // Pointer to array of child window descriptions
96 | const struct control_desc *controls;
97 | };
98 | #ifdef CreateWindow
99 | void create_controls(HWND hWnd, struct window_template *t, LPARAM cs);
100 | void move_controls(HWND hWnd, struct window_template *t, LPARAM lParam);
101 | #endif
102 |
103 | // inst.c
104 | int note_from_key(int key, BOOL shift);
105 |
106 | // midi.c
107 | void closeMidiInDevice();
108 | void openMidiInDevice(int deviceId, void* callback);
109 |
110 | // parser.c
111 | extern const BYTE code_length[];
112 | void parser_init(struct parser *p, const struct channel_state *c);
113 | BYTE *next_code(BYTE *p);
114 | BOOL parser_advance(struct parser *p);
115 |
116 | // play.c
117 | extern BYTE spc[65536];
118 | extern int inst_base;
119 | void set_inst(struct song_state *st, struct channel_state *c, int inst);
120 | void calc_freq(struct channel_state *c, int note16);
121 | void initialize_envelope(struct channel_state *c);
122 | void load_pattern(void);
123 | BOOL do_cycle_no_sound(struct song_state *st);
124 | BOOL do_timer(void);
125 | void initialize_state(void);
126 |
127 | // loadrom.c
128 | #ifdef EOF
129 | extern FILE *rom;
130 | #endif
131 | extern int rom_size;
132 | extern int rom_offset;
133 | extern int song_pointer_table_offset;
134 | extern char *rom_filename;
135 | extern unsigned char pack_used[NUM_SONGS][3];
136 | extern unsigned short song_address[NUM_SONGS];
137 | extern struct pack rom_packs[NUM_PACKS];
138 | extern struct pack inmem_packs[NUM_PACKS];
139 | BOOL close_rom(void);
140 | BOOL open_rom(char *filename, BOOL readonly);
141 | BOOL open_orig_rom(char *filename);
142 |
143 | // metadata.c
144 | extern char *bgm_title[NUM_SONGS];
145 | extern BOOL metadata_changed;
146 | #ifdef EOF
147 | extern FILE *orig_rom;
148 | extern int orig_rom_offset;
149 | #endif
150 | extern char *orig_rom_filename;
151 | extern const char *const bgm_orig_title[NUM_SONGS];
152 | void load_metadata(void);
153 | void save_metadata(void);
154 | void free_metadata(void);
155 |
156 | // packlist.c
157 |
158 | // packs.c
159 | extern const DWORD pack_orig_crc[];
160 | void free_pack(struct pack *p);
161 | struct pack *load_pack(int pack);
162 | void load_songpack(int new_pack);
163 | struct block *get_cur_block(void);
164 | void select_block(int block);
165 | void select_block_by_address(int spc_addr);
166 | struct block *save_cur_song_to_pack(void);
167 | int calc_pack_size(struct pack *p);
168 | void new_block(struct block *b);
169 | void delete_block(int block);
170 | void move_block(int to);
171 | BOOL save_pack(int pack);
172 |
173 | // ranges.c
174 | #define AREA_END -4
175 | #define AREA_NOT_IN_FILE -3
176 | #define AREA_NON_SPC -2
177 | #define AREA_FREE -1
178 | extern int area_count;
179 | extern struct area { int address, pack; } *areas;
180 | extern void init_areas(void);
181 | extern void change_range(int start, int end, int from, int to);
182 | extern int check_range(int start, int end, int pack);
183 |
184 | // song.c
185 | extern char *decomp_error;
186 | BOOL validate_track(BYTE *data, int size, BOOL is_sub);
187 | int compile_song(struct song *s);
188 | void decompile_song(struct song *s, int start_addr, int end_addr);
189 | void free_song(struct song *s);
190 |
191 | // songed.c
192 | void order_insert(int pos, int pat);
193 | struct track *pattern_insert(int pat);
194 | void pattern_delete(int pat);
195 | BOOL split_pattern(int pos);
196 | BOOL join_patterns(void);
197 | int create_sub(BYTE *start, BYTE *end, int *count);
198 | void order_delete(int pos);
199 |
200 | // sound.c
201 | extern int mixrate;
202 | extern int chmask;
203 | BOOL is_playing(void);
204 | BOOL start_playing(void);
205 | void stop_playing(void);
206 | BOOL is_capturing_audio(void);
207 | BOOL start_capturing_audio(void);
208 | void stop_capturing_audio(void);
209 | extern int timer_speed;
210 | int sound_init(void);
211 | void winmm_message(unsigned int uMsg);
212 |
213 | // text.c
214 | int calc_track_size_from_text(char *p);
215 | BOOL text_to_track(char *str, struct track *t, BOOL is_sub);
216 | int text_length(BYTE *start, BYTE *end);
217 | void track_to_text(char *out, BYTE *track, int size);
218 |
219 | // tracker.c
220 | extern HWND hwndTracker;
221 | void tracker_scrolled(void);
222 | void load_pattern_into_tracker(void);
223 | void editor_command(int id);
224 |
225 | // status.c
226 | #ifdef __GNUC__
227 | __attribute__ ((format (gnu_printf, 2, 3)))
228 | #endif
229 | void format_status(int part, const char* format, ...);
230 | void set_tracker_status(int part, BYTE *code);
231 |
232 | #endif // EBMUSV2_H
233 |
--------------------------------------------------------------------------------
/src/help.c:
--------------------------------------------------------------------------------
1 | #define WIN32_LEAN_AND_MEAN
2 | #include
3 | #include
4 | #include "ebmusv2.h"
5 | #include "id.h"
6 | #include "misc.h"
7 |
8 | #define IDC_HELPTEXT 1
9 |
10 | const char help_text[] = {
11 | "00: End of pattern or subroutine\r\n"
12 | " (Don't use this in the editor; it automatically\r\n"
13 | " inserts this where necessary when compiling)\r\n"
14 | "\r\n"
15 | "01-7F: Set note length\r\n"
16 | "\r\n"
17 | " normal triplet\r\n"
18 | " whole 60 40\r\n"
19 | " half 30 20\r\n"
20 | " quarter 18 10\r\n"
21 | " eighth 0C 08\r\n"
22 | " 16th 06 04\r\n"
23 | " 32nd 03 02\r\n"
24 | "\r\n"
25 | " May be optionally followed by another byte to set note style:\r\n"
26 | " First nybble: 0-7, release time\r\n"
27 | " Second nybble: 0-F, volume\r\n"
28 | "\r\n"
29 | "-----------------------\r\n"
30 | "\r\n"
31 | "80-C7: Notes\r\n"
32 | "\r\n"
33 | " C C# D D# E F F# G G# A A# B\r\n"
34 | " 1 80 81 82 83 84 85 86 87 88 89 8A 8B\r\n"
35 | " 2 8C 8D 8E 8F 90 91 92 93 94 95 96 97\r\n"
36 | " 3 98 99 9A 9B 9C 9D 9E 9F A0 A1 A2 A3\r\n"
37 | " 4 A4 A5 A6 A7 A8 A9 AA AB AC AD AE AF\r\n"
38 | " 5 B0 B1 B2 B3 B4 B5 B6 B7 B8 B9 BA BB\r\n"
39 | " 6 BC BD BE BF C0 C1 C2 C3 C4 C5 C6 C7\r\n"
40 | "(note C-1 is too low to play without a finetune of at least 26)"
41 | "\r\n"
42 | "C8: Continue previous note\r\n"
43 | "C9: Rest\r\n"
44 | "CA-DF: Set instrument and play note C-4. Usually used for drums.\r\n"
45 | " The instrument used is equal to the code minus CA plus\r\n"
46 | " the base instrument number set by [FA].\r\n"
47 | "\r\n"
48 | "-----------------------\r\n"
49 | "\r\n"
50 | "[E0 instrument]\r\n"
51 | " Set instrument. The parameter can either specify an instrument\r\n"
52 | " number directly, or it can be a value from CA onward, in which case\r\n"
53 | " it is relative to the CA base instrument set by [FA].\r\n"
54 | "[E1 panning]\r\n"
55 | " Set channel panning. 00 = right, 0A = middle, 14 = left\r\n"
56 | " The top two bits set if the left and/or right stereo channels\r\n"
57 | " should be inverted.\r\n"
58 | "[E2 time panning]\r\n"
59 | " Slide channel panning\r\n"
60 | "[E3 start speed range]\r\n"
61 | " Vibrato on. If range is <= F0, it is in 1/256s of a semitone;\r\n"
62 | " if range is F1-FF then it is in semitones.\r\n"
63 | "[E4]\r\n"
64 | " Vibrato off\r\n"
65 | "[E5 volume]\r\n"
66 | " Set global volume\r\n"
67 | "[E6 time volume]\r\n"
68 | " Slide global volume\r\n"
69 | "[E7 tempo]\r\n"
70 | " Set tempo\r\n"
71 | "[E8 time tempo]\r\n"
72 | " Slide tempo\r\n"
73 | "[E9 transpose]\r\n"
74 | " Set global transpose\r\n"
75 | "[EA transpose]\r\n"
76 | " Set channel transpose\r\n"
77 | "[EB start speed range]\r\n"
78 | " Tremolo on\r\n"
79 | "[EC]\r\n"
80 | " Tremolo off\r\n"
81 | "[ED volume]\r\n"
82 | " Set channel volume\r\n"
83 | "[EE time volume]\r\n"
84 | " Slide channel volume\r\n"
85 | "[EF addr-lo addr-hi count]\r\n"
86 | " Call a subroutine the given number of times.\r\n"
87 | " In the editor, use the *s,n syntax instead.\r\n"
88 | "[F0 time]\r\n"
89 | " Set vibrato fadein time\r\n"
90 | "[F1 start length range]\r\n"
91 | " Portamento on. Goes from note to note+range\r\n"
92 | "[F2 start length range]\r\n"
93 | " Portamento on. Goes from note-range to note\r\n"
94 | "[F3]\r\n"
95 | " Portamento off\r\n"
96 | "[F4 finetune]\r\n"
97 | " Sets channel finetune (in 1/256 of a semitone)\r\n"
98 | "[F5 channels lvol rvol]\r\n"
99 | " Echo on (not implemented)\r\n"
100 | "[F6]\r\n"
101 | " Echo off (not implemented)\r\n"
102 | "[F7 delay feedback filter]\r\n"
103 | " Set echo settings (not implemented)\r\n"
104 | "[F8 time lvol rvol]\r\n"
105 | " Slide echo volumes (not implemented)\r\n"
106 | "[F9 start length note]\r\n"
107 | " Pitch bend\r\n"
108 | "[FA instrument]\r\n"
109 | " Set the first instrument to be used by CA-DF codes\r\n"
110 | " In EarthBound, this is always set to the first instrument of the\r\n"
111 | " second pack, but this is not required.\r\n"
112 | "[FB ?? ??]\r\n"
113 | " Does nothing\r\n"
114 | "[FC]\r\n"
115 | " Mute channel (debug code, not implemented)\r\n"
116 | "[FD]\r\n"
117 | " Fast-forward on (debug code, not implemented)\r\n"
118 | "[FE]\r\n"
119 | " Fast-forward off (debug code, not implemented)\r\n"
120 | "[FF]\r\n"
121 | " Invalid"
122 | };
123 |
124 | LRESULT CALLBACK CodeListWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
125 | switch (uMsg) {
126 | case WM_CTLCOLORSTATIC:
127 | return (LRESULT)GetSysColorBrush(COLOR_WINDOW);
128 | case WM_CREATE: {
129 | HWND ed = CreateWindow("Edit", help_text,
130 | WS_CHILD | WS_VISIBLE | WS_VSCROLL | ES_MULTILINE | ES_READONLY,
131 | 0, 0, 0, 0,
132 | hWnd, (HMENU)IDC_HELPTEXT, hinstance, NULL);
133 | HFONT font = fixed_font();;
134 | SendMessage(ed, WM_SETFONT, (WPARAM)font, 0);
135 | break;
136 | }
137 | case WM_SIZE:
138 | MoveWindow(GetDlgItem(hWnd, IDC_HELPTEXT),
139 | 0, 0, LOWORD(lParam), HIWORD(lParam), TRUE);
140 | break;
141 | default:
142 | return DefWindowProc(hWnd, uMsg, wParam, lParam);
143 | }
144 | return 0;
145 | }
146 |
147 | static WNDPROC HomepageLinkWndProc;
148 | static LRESULT CALLBACK HomepageLinkProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
149 | switch (uMsg) {
150 | case WM_SETCURSOR: {
151 | HCURSOR hCursor = LoadCursor(NULL, IDC_HAND);
152 | if (NULL == hCursor) hCursor = LoadCursor(NULL, IDC_ARROW);
153 | SetCursor(hCursor);
154 | return TRUE;
155 | }
156 | }
157 |
158 | return CallWindowProc(HomepageLinkWndProc, hWnd, uMsg, wParam, lParam);
159 | }
160 |
161 | BOOL CALLBACK AboutDlgProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
162 | switch(uMsg) {
163 | case WM_INITDIALOG: {
164 | HWND hwndLink = GetDlgItem(hWnd, IDC_HOMEPAGELINK);
165 | HomepageLinkWndProc = (WNDPROC)SetWindowLongPtr(hwndLink, GWLP_WNDPROC, (LONG_PTR)HomepageLinkProc);
166 |
167 | // Set font to underlined
168 | HFONT hFont = (HFONT)SendMessage(hwndLink, WM_GETFONT, 0, 0);
169 | LOGFONT lf;
170 | GetObject(hFont, sizeof(lf), &lf);
171 | lf.lfUnderline = TRUE;
172 | HFONT hUnderlinedFont = CreateFontIndirect(&lf);
173 | SendMessage(hwndLink, WM_SETFONT, (WPARAM)hUnderlinedFont, FALSE);
174 | SetTextColor(hwndLink, RGB(0, 0, 192));
175 |
176 | break;
177 | }
178 | case WM_COMMAND:
179 | switch(LOWORD(wParam)) {
180 | case IDC_HOMEPAGELINK:
181 | if (HIWORD(wParam) == BN_CLICKED) {
182 | ShellExecute(hWnd, "open", "https://github.com/PKHackers/ebmused/", NULL, NULL, SW_SHOWNORMAL);
183 | }
184 | break;
185 | case IDOK:
186 | EndDialog(hWnd, IDOK);
187 | break;
188 | }
189 | break;
190 | case WM_CTLCOLORSTATIC:
191 | if ((HWND)lParam == GetDlgItem(hWnd, IDC_HOMEPAGELINK))
192 | {
193 | SetBkMode((HDC)wParam, TRANSPARENT);
194 | SetTextColor((HDC)wParam, RGB(0, 0, 192));
195 | return (BOOL)GetSysColorBrush(COLOR_3DFACE);
196 | }
197 | return FALSE;
198 | break;
199 | default:
200 | return FALSE;
201 | }
202 |
203 | return TRUE;
204 | }
205 |
--------------------------------------------------------------------------------
/src/id.h:
--------------------------------------------------------------------------------
1 | #define IDM_MENU 1
2 | #define IDM_CONTEXTMENU 2
3 | #define IDS_STATUS 3
4 | #define ID_OPEN 100
5 | #define ID_SAVE_ALL 101
6 | #define ID_CLOSE 102
7 | #define ID_IMPORT 103
8 | #define ID_IMPORT_SPC 104
9 | #define ID_EXPORT 105
10 | #define ID_EXPORT_SPC 106
11 | #define ID_EXIT 107
12 | #define ID_UNDO 110
13 | #define ID_CUT 111
14 | #define ID_COPY 112
15 | #define ID_PASTE 113
16 | #define ID_DELETE 114
17 | #define ID_SPLIT_PATTERN 115
18 | #define ID_JOIN_PATTERNS 116
19 | #define ID_TRANSPOSE 117
20 | #define ID_MAKE_SUBROUTINE 118
21 | #define ID_UNMAKE_SUBROUTINE 119
22 | #define ID_INCREMENT_DURATION 120
23 | #define ID_DECREMENT_DURATION 121
24 | #define ID_SET_DURATION_1 123
25 | #define ID_SET_DURATION_2 124
26 | #define ID_SET_DURATION_3 125
27 | #define ID_SET_DURATION_4 126
28 | #define ID_SET_DURATION_5 127
29 | #define ID_SET_DURATION_6 128
30 | #define ID_OPTIONS 130
31 | #define ID_PLAY 131
32 | #define ID_STOP 132
33 | #define ID_CAPTURE_AUDIO 133
34 | #define ID_CLEAR_SONG 134
35 | #define ID_ZOOM_IN 140
36 | #define ID_ZOOM_OUT 141
37 | #define ID_STATUS_BAR 142
38 | #define ID_OCTAVE_1 150
39 | #define ID_HELP 160
40 | #define ID_ABOUT 161
41 |
42 | #define IDA_ACCEL 1
43 |
44 | #define IDD_OPTIONS 1
45 | #define IDC_RATE 3
46 | #define IDC_BUFSIZE 4
47 |
48 | #define IDD_ABOUT 2
49 | #define IDC_HOMEPAGELINK 3
50 |
51 | #define IDD_TRANSPOSE 3
52 | #define IDC_TRANSPOSE_OFF 3
53 |
54 | #define IDRC_SPC 1
55 |
--------------------------------------------------------------------------------
/src/inst.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #define _WIN32_WINNT 0x0500 // for VK_OEM_PERIOD ?????
4 | #define WIN32_LEAN_AND_MEAN
5 | #include
6 | #include
7 | #include
8 | #include "id.h"
9 | #include "ebmusv2.h"
10 | #include "misc.h"
11 |
12 | #define IDC_SAMPLIST_CAPTION 1
13 | #define IDC_SAMPLIST 2
14 | #define IDC_INSTLIST_CAPTION 3
15 | #define IDC_INSTLIST 4
16 | #define IDC_MIDIINCOMBO 5
17 |
18 | #define inst_list_template_num 10
19 | #define inst_list_template_lower 10
20 |
21 | static HWND samplist, instlist, insttest;
22 | static int prev_chmask;
23 | static int selectedInstrument = 0;
24 |
25 | static const struct control_desc inst_list_controls[] = {
26 | { "Static", 10, 10,100, 20, "Sample Directory:", 0, 0 },
27 | { "Static", 15, 30,180, 20, " Strt Loop Size", IDC_SAMPLIST_CAPTION, 0 },
28 | { "ListBox", 10, 50,180,-60, NULL, IDC_SAMPLIST, WS_BORDER | WS_VSCROLL }, // Sample Directory ListBox
29 |
30 | { "Static", 200, 10,100, 20, "Instrument Config:", 0, 0 },
31 | { "Static", 205, 30,160, 20, "S# ADSR/Gain Tuning", IDC_INSTLIST_CAPTION, 0 },
32 | { "ListBox",200, 50,180,-60, NULL, IDC_INSTLIST, WS_BORDER | LBS_NOTIFY | WS_VSCROLL }, // Instrument Config ListBox
33 |
34 | { "Static", 400, 10,100, 20, "Instrument test:", 0, 0},
35 | { "ebmused_insttest",400, 30,260,140, NULL, IDC_INSTLIST_CAPTION, 0 },
36 | { "Static", 400, 180,100, 20, "MIDI In Device:", 0, 0},
37 | { "ComboBox", 400, 200, 140, 200, NULL, IDC_MIDIINCOMBO, CBS_DROPDOWNLIST | WS_VSCROLL },
38 | };
39 | static struct window_template inst_list_template = {
40 | inst_list_template_num, inst_list_template_lower, 0, 0, inst_list_controls
41 | };
42 |
43 | static unsigned char valid_insts[MAX_INSTRUMENTS];
44 | static int cnote[INST_MAX_POLYPHONY];
45 | static char sustained[INST_MAX_POLYPHONY] = { 0 };
46 | static char sustain = FALSE;
47 | static struct history {
48 | struct history *prev;
49 | struct history *next;
50 | } channel_order[INST_MAX_POLYPHONY] = { 0 };
51 | static struct history* oldest_chan = channel_order;
52 |
53 | int note_from_key(int key, BOOL shift) {
54 | if (key == VK_OEM_PERIOD) return 0x48; // continue
55 | if (key == VK_SPACE) return 0x49; // rest
56 | if (shift) {
57 | static const char drums[] = "1234567890\xBD\xBBQWERTYUIOP";
58 | char *p = strchr(drums, key);
59 | if (p) return 0x4A + (p-drums);
60 | } else {
61 | static const char low[] = "ZSXDCVGBHNJM\xBCL";
62 | static const char high[] = "Q2W3ER5T6Y7UI9O0P";
63 | char *p = strchr(low, key);
64 | if (p) return octave*12 + (p-low);
65 | p = strchr(high, key);
66 | if (p) return (octave+1)*12 + (p-high);
67 | }
68 | return -1;
69 | }
70 |
71 | static void draw_square(int note, HBRUSH brush) {
72 | HDC hdc = GetDC(insttest);
73 | int x = (note % 12 + 1) * 20;
74 | int y = (6 - note / 12) * 20;
75 | RECT rc = { scale_x(x), scale_y(y), scale_x(x + 20) - 1, scale_y(y + 20) - 1 };
76 | FillRect(hdc, &rc, brush);
77 | ReleaseDC(insttest, hdc);
78 | }
79 |
80 | static int note_colors[12] = {
81 | WHITE_BRUSH,
82 | DKGRAY_BRUSH,
83 | WHITE_BRUSH,
84 | DKGRAY_BRUSH,
85 | WHITE_BRUSH,
86 | WHITE_BRUSH,
87 | DKGRAY_BRUSH,
88 | WHITE_BRUSH,
89 | DKGRAY_BRUSH,
90 | WHITE_BRUSH,
91 | DKGRAY_BRUSH,
92 | WHITE_BRUSH
93 | };
94 |
95 | // Sets the channel as being the latest one that's been played.
96 | static void set_latest_channel(int ch) {
97 | if (&channel_order[ch] == oldest_chan) {
98 | oldest_chan = oldest_chan->next;
99 | } else {
100 | // Verify channel_order items are defined. (They should also form a complete loop.)
101 | assert(channel_order[ch].prev && channel_order[ch].next);
102 |
103 | // Remove this item from the linked list.
104 | channel_order[ch].prev->next = channel_order[ch].next;
105 | channel_order[ch].next->prev = channel_order[ch].prev;
106 |
107 | // Move it to the end of the linked list
108 | channel_order[ch].next = oldest_chan ? oldest_chan : (oldest_chan = &channel_order[ch]);
109 | channel_order[ch].prev = oldest_chan->prev ? oldest_chan->prev : (oldest_chan->prev = &channel_order[ch]);
110 | oldest_chan->prev->next = &channel_order[ch];
111 | oldest_chan->prev = &channel_order[ch];
112 | }
113 | }
114 |
115 | // Sets channel as the oldest one to have been played.
116 | static void set_oldest_channel(int ch) {
117 | set_latest_channel(ch);
118 | oldest_chan = &channel_order[ch];
119 | }
120 |
121 | static void channel_off(int ch) {
122 | if (state.chan[ch].samp_pos >= 0) {
123 | state.chan[ch].note_release = 0;
124 | state.chan[ch].next_env_state = ENV_STATE_KEY_OFF;
125 | set_oldest_channel(ch);
126 | sustained[ch] = FALSE;
127 | }
128 | }
129 |
130 | static void sustain_on() {
131 | sustain = TRUE;
132 | }
133 |
134 | static void sustain_off() {
135 | sustain = FALSE;
136 | for (int ch = 0; ch < INST_MAX_POLYPHONY; ch++) {
137 | if (sustained[ch]) {
138 | channel_off(ch);
139 | }
140 | }
141 | }
142 |
143 | static void note_off(int note) {
144 | for (int ch = 0; ch < INST_MAX_POLYPHONY; ch++) {
145 | if (cnote[ch] == note) {
146 | if (sustain)
147 | sustained[ch] = TRUE;
148 | else
149 | channel_off(ch);
150 | }
151 | }
152 |
153 | draw_square(note, GetStockObject(note_colors[note % 12]));
154 | }
155 |
156 | static void note_on(int note, int velocity) {
157 | int sel = SendMessage(instlist, LB_GETCURSEL, 0, 0);
158 | if (sel < 0) return;
159 | int inst = valid_insts[sel];
160 |
161 | int ch = oldest_chan - channel_order;
162 | set_latest_channel(ch);
163 | sustained[ch] = FALSE;
164 |
165 | cnote[ch] = note;
166 | struct channel_state *c = &state.chan[ch];
167 | set_inst(&state, c, inst);
168 | c->samp_pos = 0;
169 | c->samp = &samp[spc[inst_base + 6*c->inst]];
170 |
171 | c->note_release = 1;
172 | initialize_envelope(c);
173 | calc_freq(c, note << 8);
174 | c->left_vol = c->right_vol = min(max(velocity, 0), 127);
175 | draw_square(note, (HBRUSH)(COLOR_HIGHLIGHT + 1));
176 | }
177 |
178 | static void CALLBACK MidiInProc(HMIDIIN handle, UINT wMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2) {
179 | if (wMsg == MIM_DATA)
180 | {
181 | unsigned char
182 | eventType = (dwParam1 & 0xFF),
183 | param1 = (dwParam1 >> 8) & 0xFF,
184 | param2 = (dwParam1 >> 16) & 0xFF;
185 |
186 | if ((eventType & 0x80) && eventType < 0xF0) { // If not a system exclusive MIDI message
187 | switch (eventType & 0xF0) {
188 | case 0xC0: // Instrument change event
189 | SendMessage(instlist, LB_SETCURSEL, param1, 0);
190 | break;
191 | case 0x90: // Note On event
192 | if (param2 > 0)
193 | note_on(param1 + (octave - 4)*12, param2/2);
194 | else
195 | note_off(param1 + (octave - 4)*12);
196 | break;
197 | case 0x80: // Note Off event
198 | note_off(param1 + (octave - 4)*12);
199 | break;
200 | case 0xB0: // Control change
201 | if (param1 == 64) { // Sustain pedal
202 | if (param2 >= 64)
203 | sustain_on();
204 | else
205 | sustain_off();
206 | }
207 | break;
208 | }
209 | }
210 | }
211 | }
212 |
213 | static WNDPROC ListBoxWndProc;
214 | // Custom window procedure for the instrument ListBox
215 | static LRESULT CALLBACK InstListWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
216 | if (uMsg == WM_KEYDOWN && !(lParam & (1 << 30))) {
217 | int note = note_from_key(wParam, FALSE);
218 | if (note >= 0 && note < 0x48)
219 | note_on(note, 24);
220 | } else if (uMsg == WM_KEYUP) {
221 | int note = note_from_key(wParam, FALSE);
222 | if (note >= 0 && note < 0x48)
223 | note_off(note);
224 | }
225 | // so pressing 0 or 2 doesn't move the selection around
226 | else if (uMsg == WM_CHAR)
227 | return 0;
228 |
229 | return CallWindowProc(ListBoxWndProc, hWnd, uMsg, wParam, lParam);
230 | }
231 |
232 | static HFONT hScaledFont;
233 |
234 | LRESULT CALLBACK InstTestWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
235 | switch (uMsg) {
236 | case WM_CREATE:
237 | insttest = hWnd;
238 | LOGFONT lf;
239 | GetObject(default_font(), sizeof(LOGFONT), &lf);
240 | lf.lfHeight = (int)(lf.lfHeight * 0.8);
241 | hScaledFont = CreateFontIndirect(&lf);
242 | break;
243 | case WM_DESTROY:
244 | DeleteObject(hScaledFont);
245 | break;
246 | case WM_ERASEBKGND: {
247 | DefWindowProc(hWnd, uMsg, wParam, lParam);
248 | HDC hdc = (HDC)wParam;
249 | set_up_hdc(hdc);
250 | // Most of these magic offsets were eyeballed 👀
251 | for (char o = '1'; o <= '6'; o++) {
252 | const int y = 2 + 20 * ('7' - o);
253 | TextOut(hdc, 0, scale_y(y),
254 | &o, 1);
255 | }
256 |
257 | for (int i = 0; i < 12; i++) {
258 | const int x =
259 | (int[12]){ 6, 5, 6, 5, 6, 6, 5, 6, 5, 6, 5, 6 }[i]
260 | + 20 * (i + 1);
261 | TextOut(hdc, scale_x(x), 0,
262 | "CCDDEFFGGAAB" + i, 1);
263 | }
264 |
265 | SelectObject(hdc, hScaledFont);
266 | SetBkMode(hdc, TRANSPARENT);
267 | for (int i = 0; i < 12; i++) {
268 | const int x = 13 + 20 * (i + 1);
269 | TextOut(hdc, scale_x(x), 0,
270 | " # # # # # " + i, 1);
271 | }
272 |
273 | Rectangle(hdc, scale_x(19), scale_y(19), scale_x(260), scale_y(140));
274 | reset_hdc(hdc);
275 | for (int i=0; i<72; i++) draw_square(i, GetStockObject(note_colors[i % 12]));
276 | return 1;
277 | }
278 | case WM_LBUTTONDOWN:
279 | case WM_LBUTTONUP: {
280 | int note = LOWORD(lParam) / scale_x(20) - 1;
281 | if (note < 0 || note > 11) break;
282 | int octave = 6 - HIWORD(lParam) / scale_y(20);
283 | if (octave < 0 || octave > 5) break;
284 | note += 12 * octave;
285 | if (uMsg == WM_LBUTTONDOWN)
286 | note_on(note, 24);
287 | else
288 | note_off(note);
289 | break;
290 | }
291 | default:
292 | return DefWindowProc(hWnd, uMsg, wParam, lParam);
293 | }
294 | return 0;
295 | }
296 |
297 | LRESULT CALLBACK InstrumentsWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
298 | switch (uMsg) {
299 | case WM_CREATE: {
300 | prev_chmask = chmask;
301 |
302 | #if INST_MAX_POLYPHONY > 31
303 | #error INST_MAX_POLYPHONY must be less than 32 to prevent left-shift overflowing.
304 | #else
305 | chmask = (1u << INST_MAX_POLYPHONY) - 1;
306 | #endif
307 |
308 | WPARAM fixed = (WPARAM)fixed_font();
309 | char buf[40];
310 |
311 | // HACK: For some reason when the compiler has optimization turned on, it doesn't initialize the values of inst_list_template correctly. So we'll reset them here. . .
312 | // NOTE: This may be due to a sprintf overflowing, as was the case with bgm_list_template when compiling in Visual Studio 2015
313 | inst_list_template.num = inst_list_template_num;
314 | inst_list_template.lower = inst_list_template_lower;
315 |
316 | create_controls(hWnd, &inst_list_template, lParam);
317 |
318 | SendDlgItemMessage(hWnd, IDC_SAMPLIST_CAPTION, WM_SETFONT, fixed, 0);
319 | samplist = GetDlgItem(hWnd, IDC_SAMPLIST);
320 | SendMessage(samplist, WM_SETFONT, fixed, 0);
321 | SendDlgItemMessage(hWnd, IDC_INSTLIST_CAPTION, WM_SETFONT, fixed, 0);
322 | instlist = GetDlgItem(hWnd, IDC_INSTLIST);
323 | SendMessage(instlist, WM_SETFONT, fixed, 0);
324 |
325 | // Insert a custom window procedure on the instrument list, so we
326 | // can see WM_KEYDOWN and WM_KEYUP messages for instrument testing.
327 | // (LBS_WANTKEYBOARDINPUT doesn't notify on WM_KEYUP)
328 | ListBoxWndProc = (WNDPROC)SetWindowLongPtr(instlist, GWLP_WNDPROC,
329 | (LONG_PTR)InstListWndProc);
330 |
331 | for (int i = 0; i < 128; i++) { //filling out the Sample Directory ListBox
332 | if (samp[i].data == NULL) continue;
333 | WORD *ptr = (WORD *)&spc[sample_ptr_base + 4*i];
334 | sprintf(buf, "%02X: %04X %04X %4d", i,
335 | ptr[0], ptr[1], samp[i].length >> 4);
336 | SendMessage(samplist, LB_ADDSTRING, 0, (LPARAM)buf);
337 | }
338 |
339 | unsigned char *p = valid_insts;
340 | for (int i = 0; i < MAX_INSTRUMENTS; i++) { //filling out the Instrument Config ListBox
341 | unsigned char *inst = &spc[inst_base + i*6];
342 | if (inst[0] >= 128
343 | || !samp[inst[0]].data
344 | || (inst[4] == 0 && inst[5] == 0)) continue;
345 | // Index ADSR Tuning
346 | sprintf(buf, "%02X: %02X %02X %02X %02X%02X",
347 | inst[0], inst[1], inst[2], inst[3], inst[4], inst[5]);
348 | SendMessage(instlist, LB_ADDSTRING, 0, (LPARAM)buf);
349 | *p++ = i;
350 | }
351 | start_playing();
352 | timer_speed = 0;
353 | memset(&state.chan, 0, sizeof state.chan);
354 | for (int ch = 0; ch < INST_MAX_POLYPHONY; ch++) {
355 | state.chan[ch].samp_pos = -1;
356 | channel_order[ch].next = &channel_order[(ch + 1) % INST_MAX_POLYPHONY];
357 | channel_order[ch].prev = &channel_order[(ch - 1 + INST_MAX_POLYPHONY) % INST_MAX_POLYPHONY];
358 | }
359 |
360 | // Restore the previous instrument selection
361 | if (SendMessage(instlist, LB_GETCOUNT, 0, 0) < selectedInstrument)
362 | selectedInstrument = 0;
363 | SendMessage(instlist, LB_SETCURSEL, selectedInstrument, 0);
364 | SetFocus(instlist);
365 |
366 | // Populate the MIDI In Devices combo box
367 | HWND cb = GetDlgItem(hWnd, IDC_MIDIINCOMBO);
368 | SendMessage(cb, CB_RESETCONTENT, 0, 0);
369 | SendMessage(cb, CB_ADDSTRING, 0, (LPARAM)"None");
370 |
371 | MIDIINCAPS inCaps;
372 | unsigned int numDevices = midiInGetNumDevs();
373 | for (unsigned int i=0; iinst_adsr1 & 0x80) {
403 | format_status(0, "ADSR: %02d/15 %d/7 %d/7 %02d/31", c->inst_adsr1 & 0xF, (c->inst_adsr1 >> 4) & 0x7, (c->inst_adsr2 >> 5) & 7, c->inst_adsr2 & 0x1F);
404 | } else if (c->inst_gain & 0x80) {
405 | format_status(0, "Direct Gain: %d/127", c->inst_gain & 0x7F);
406 | } else {
407 | static const char *gain_modes[] = { "Linear Decrease", "Exponential Decrease", "Linear Increase", "Bent Increase" };
408 | format_status(0, "%s Gain: %d/31", gain_modes[(c->inst_gain >> 5) & 3], c->inst_gain & 0x1F);
409 | }
410 | }
411 | break;
412 | }
413 | break;
414 | }
415 | case WM_ROM_CLOSED:
416 | SendMessage(samplist, LB_RESETCONTENT, 0, 0);
417 | SendMessage(instlist, LB_RESETCONTENT, 0, 0);
418 | break;
419 | case WM_SIZE:
420 | move_controls(hWnd, &inst_list_template, lParam);
421 | break;
422 | case WM_DESTROY:
423 | stop_playing();
424 | state = pattop_state;
425 | timer_speed = 500;
426 | chmask = prev_chmask;
427 | closeMidiInDevice();
428 |
429 | // Store the current selected instrument.
430 | selectedInstrument = SendMessage(instlist, LB_GETCURSEL, 0, 0);
431 |
432 | EnableMenuItem(hmenu, ID_PLAY, MF_ENABLED);
433 | break;
434 | default:
435 | return DefWindowProc(hWnd, uMsg, wParam, lParam);
436 | }
437 | return 0;
438 | }
439 |
--------------------------------------------------------------------------------
/src/ldscript:
--------------------------------------------------------------------------------
1 | SECTIONS {
2 | /* 8-byte import stubs + 16-byte alignment = lots of blank space */
3 | .text : SUBALIGN(8) { }
4 |
5 | /* Trim some bloat from the import table too
6 | * See: http://sourceforge.net/tracker/index.php?func=detail&aid=1292097&group_id=2435&atid=102435
7 | * for why the idata$7 stuff is there and why it's not really necessary.
8 | * Apparently I'm the only one who cares, though */
9 | .idata : SUBALIGN(1) { }
10 | /DISCARD/ : { d*s?????.o(.idata$7) }
11 | }
12 |
--------------------------------------------------------------------------------
/src/loadrom.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #define WIN32_LEAN_AND_MEAN
5 | #include
6 | #include "ebmusv2.h"
7 | #include "misc.h"
8 | #include "id.h"
9 |
10 | FILE *rom;
11 | int rom_size;
12 | int rom_offset;
13 | int song_pointer_table_offset;
14 | char *rom_filename;
15 |
16 | unsigned char pack_used[NUM_SONGS][3];
17 | unsigned short song_address[NUM_SONGS];
18 | struct pack rom_packs[NUM_PACKS];
19 | struct pack inmem_packs[NUM_PACKS];
20 |
21 | static char *skip_dirname(char *filename) {
22 | for (char *p = filename; *p; p++)
23 | if (*p == '/' || *p == '\\') filename = p + 1;
24 | return filename;
25 | }
26 |
27 | static DWORD crc_table[256];
28 |
29 | static void init_crc() {
30 | for (int i = 0; i < 256; i++) {
31 | DWORD crc = i;
32 | for (int j = 8; j; j--)
33 | if (crc & 1)
34 | crc = (crc >> 1) ^ 0xEDB88320;
35 | else
36 | crc = (crc >> 1);
37 | crc_table[i] = crc;
38 | }
39 | }
40 |
41 | static DWORD update_crc(DWORD crc, BYTE *block, int size) {
42 | do {
43 | crc = (crc >> 8) ^ crc_table[(crc ^ *block++) & 0xFF];
44 | } while (--size);
45 | return crc;
46 | }
47 |
48 | static const BYTE rom_menu_cmds[] = {
49 | ID_SAVE_ALL, ID_CLOSE, 0
50 | };
51 |
52 | BOOL close_rom() {
53 | if (rom) {
54 | save_cur_song_to_pack();
55 | int unsaved_packs = 0;
56 | for (int i = 0; i < NUM_PACKS; i++)
57 | if (inmem_packs[i].status & IPACK_CHANGED)
58 | unsaved_packs++;
59 | if (unsaved_packs) {
60 |
61 | char buf[70];
62 | if (unsaved_packs == 1)
63 | sprintf(buf, "A pack has unsaved changes.\nDo you want to save?");
64 | else
65 | sprintf(buf, "%d packs have unsaved changes.\nDo you want to save?", unsaved_packs);
66 |
67 | int action = MessageBox2(buf, "Close", MB_ICONEXCLAMATION | MB_YESNOCANCEL);
68 | if (action == IDCANCEL || (action == IDYES && !save_all_packs()))
69 | return FALSE;
70 | }
71 | save_metadata();
72 |
73 | fclose(rom);
74 | rom = NULL;
75 | free(rom_filename);
76 | rom_filename = NULL;
77 | enable_menu_items(rom_menu_cmds, MF_GRAYED);
78 | free(areas);
79 | free_metadata();
80 | for (int i = 0; i < NUM_PACKS; i++) {
81 | free(rom_packs[i].blocks);
82 | if (inmem_packs[i].status & IPACK_INMEM)
83 | free_pack(&inmem_packs[i]);
84 | }
85 | }
86 |
87 | // Closing an SPC should be correlated with closing a ROM.
88 | // So whether a ROM was loaded or not, we need to reset the playback state.
89 | // This protects from crashes if an SPC was playing.
90 | free_samples();
91 | free_song(&cur_song);
92 | stop_playing();
93 | initialize_state();
94 |
95 | memset(packs_loaded, 0xFF, 3);
96 | current_block = -1;
97 |
98 | return TRUE;
99 | }
100 |
101 | BOOL open_rom(char *filename, BOOL readonly) {
102 | FILE *f = fopen(filename, readonly ? "rb" : "r+b");
103 | if (!f) {
104 | MessageBox2(strerror(errno), "Can't open file", MB_ICONEXCLAMATION);
105 | return FALSE;
106 | }
107 |
108 | if (!close_rom())
109 | return FALSE;
110 |
111 | rom_size = _filelength(_fileno(f));
112 | rom_offset = rom_size & 0x200;
113 | if (rom_size < 0x300000) {
114 | MessageBox2("An EarthBound ROM must be at least 3 MB", "Can't open file", MB_ICONEXCLAMATION);
115 | fclose(f);
116 | return FALSE;
117 | }
118 | rom = f;
119 | rom_filename = _strdup(filename);
120 | enable_menu_items(rom_menu_cmds, MF_ENABLED);
121 |
122 | init_areas();
123 | change_range(0xBFFE00 + rom_offset, 0xBFFC00 + rom_offset + rom_size, AREA_NOT_IN_FILE, AREA_NON_SPC);
124 |
125 | char *bfile = skip_dirname(filename);
126 | char *title = malloc(sizeof("EarthBound Music Editor") + 3 + strlen(bfile));
127 | sprintf(title, "%s - %s", bfile, "EarthBound Music Editor");
128 | SetWindowText(hwndMain, title);
129 | free(title);
130 |
131 | fseek(f, BGM_PACK_TABLE + rom_offset, SEEK_SET);
132 | fread(pack_used, NUM_SONGS, 3, f);
133 | // pack pointer table follows immediately after
134 | for (int i = 0; i < NUM_PACKS; i++) {
135 | int addr = fgetc(f) << 16;
136 | addr |= fgetw(f);
137 | rom_packs[i].start_address = addr;
138 | }
139 |
140 | song_pointer_table_offset = 0;
141 | init_crc();
142 | for (int i = 0; i < NUM_PACKS; i++) {
143 | int size;
144 | int count = 0;
145 | struct block *blocks = NULL;
146 | BOOL valid = TRUE;
147 | struct pack *rp = &rom_packs[i];
148 |
149 | int offset = rp->start_address - 0xC00000 + rom_offset;
150 | if (offset < rom_offset || offset >= rom_size) {
151 | valid = FALSE;
152 | goto bad_pointer;
153 | }
154 |
155 | fseek(f, offset, SEEK_SET);
156 | DWORD crc = ~0;
157 | while ((size = fgetw(f)) > 0) {
158 | int spc_addr = fgetw(f);
159 | if (spc_addr + size > 0x10000) { valid = FALSE; break; }
160 | offset += 4 + size;
161 | if (offset > rom_size) { valid = FALSE; break; }
162 |
163 | count++;
164 | blocks = realloc(blocks, sizeof(struct block) * count);
165 | blocks[count-1].size = size;
166 | blocks[count-1].spc_address = spc_addr;
167 |
168 | if (spc_addr == 0x0500) {
169 | int back = ftell(f);
170 | song_pointer_table_offset = back + 0x2E4A - 0x500;
171 | }
172 |
173 | fread(&spc[spc_addr], size, 1, f);
174 | crc = update_crc(crc, (BYTE *)&size, 2);
175 | crc = update_crc(crc, (BYTE *)&spc_addr, 2);
176 | crc = update_crc(crc, &spc[spc_addr], size);
177 | }
178 | crc = ~update_crc(crc, (BYTE *)&size, 2);
179 | bad_pointer:
180 | change_range(rp->start_address, offset + 2 + 0xC00000 - rom_offset,
181 | AREA_NON_SPC, i);
182 | rp->status = valid ? crc != pack_orig_crc[i] : 2;
183 | rp->block_count = count;
184 | rp->blocks = blocks;
185 | inmem_packs[i].status = 0;
186 | }
187 | load_metadata();
188 | if (song_pointer_table_offset) {
189 | fseek(f, song_pointer_table_offset, SEEK_SET);
190 | fread(song_address, NUM_SONGS, 2, f);
191 | } else {
192 | close_rom();
193 | MessageBox2("Unable to determine location of song pointer table.", "Can't open file", MB_ICONEXCLAMATION);
194 | return FALSE;
195 | }
196 | return TRUE;
197 | }
198 |
--------------------------------------------------------------------------------
/src/metadata.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #define WIN32_LEAN_AND_MEAN
5 | #include
6 | #include "ebmusv2.h"
7 | #include "misc.h"
8 |
9 | char *bgm_title[NUM_SONGS];
10 | BOOL metadata_changed;
11 | static char md_filename[MAX_PATH+8];
12 | FILE *orig_rom;
13 | char *orig_rom_filename;
14 | int orig_rom_offset;
15 |
16 | const char *const bgm_orig_title[NUM_SONGS] = {
17 | "Gas Station",
18 | "Your Name, Please",
19 | "Choose a File",
20 | "None",
21 | "Fanfare - You Won!",
22 | "Level Up",
23 | "A Bad Dream",
24 | "Battle Swirl (Boss)",
25 | "Battle Swirl (Ambushed)",
26 | "(Unused)",
27 | "Fanfare - You've Got A New Friend!",
28 | "Fanfare - Instant Revitalization",
29 | "Teleportation - Departure",
30 | "Teleportation - Failure",
31 | "Falling Underground",
32 | "Doctor Andonuts' Lab",
33 | "Suspicious House",
34 | "Sloppy House",
35 | "Friendly Neighbors",
36 | "Arcade",
37 | "Pokey's House",
38 | "Hospital",
39 | "Home Sweet Home",
40 | "Paula's Theme",
41 | "Chaos Theater",
42 | "Enjoy Your Stay",
43 | "Good Morning, Eagleland",
44 | "Department Store",
45 | "Onett at Night (Version 1)",
46 | "Welcome to Your Sanctuary",
47 | "A Flash of Memory",
48 | "Melody - Giant Step", //These are the melodies as Ness hears them
49 | "Melody - Lilliput Steps",
50 | "Melody - Milky Well",
51 | "Melody - Rainy Circle",
52 | "Melody - Magnet Hill",
53 | "Melody - Pink Cloud",
54 | "Melody - Lumine Hall",
55 | "Melody - Fire Spring",
56 | "Third Strongest", //aka "Approaching Mt. Itoi" in MOTHER 1
57 | "Alien Investigation (Stonehenge Base)",
58 | "Fire Spring",
59 | "Belch's Factory",
60 | "Threed, Zombie Central",
61 | "Spooky Cave",
62 | "Onett (first pattern is skipped in-game)",
63 | "The Metropolis of Fourside",
64 | "Saturn Valley",
65 | "Monkey Caves",
66 | "Moonside Swing",
67 | "Dusty Dunes Desert",
68 | "Peaceful Rest Valley",
69 | "Happy Happy Village",
70 | "Winters White",
71 | "Caverns of Winters",
72 | "Summers, Eternal Tourist Trap",
73 | "Jackie's Cafe",
74 | "Sailing to Scaraba - Departure",
75 | "The Floating Kingdom of Dalaam",
76 | "Mu Training",
77 | "Bazaar",
78 | "Scaraba Desert",
79 | "In the Pyramid",
80 | "Deep Darkness",
81 | "Tenda Village",
82 | "Magicant - Welcome Home",
83 | "Magicant - Dark Thoughts",
84 | "Lost Underworld",
85 | "The Cliff That Time Forgot", //Cave of the Beatles
86 | "The Past", //Cave of the Beach Boys
87 | "Giygas' Lair", //Intestines
88 | "Giygas Awakens",
89 | "Giygas - Struggling (Phase 2)",
90 | "Giygas - Weakening",
91 | "Giygas - Breaking Down",
92 | "Runaway Five, Live at the Chaos Theater",
93 | "Runaway Five, On Tour",
94 | "Runaway Five, Live at the Topolla Theater",
95 | "Magicant - The Power",
96 | "Venus' Performance",
97 | "Yellow Submarine",
98 | "Bicycle",
99 | "Sky Runner - In Flight",
100 | "Sky Runner - Going Down",
101 | "Bulldozer",
102 | "Tessie",
103 | "Greyhand Bus",
104 | "What a Great Photograph!",
105 | "Escargo Express at your Service!",
106 | "The Heroes Return (Part 1)",
107 | "Phase Distorter - Time Vortex",
108 | "Coffee Break", //aka You've Come Far, Ness
109 | "Because I Love You",
110 | "Good Friends, Bad Friends",
111 | "Smiles and Tears",
112 | "Battle Against a Weird Opponent",
113 | "Battle Against a Machine",
114 | "Battle Against a Mobile Opponent",
115 | "Battle Against Belch",
116 | "Battle Against a New Age Retro Hippie",
117 | "Battle Against a Weak Opponent",
118 | "Battle Against an Unsettling Opponent",
119 | "Sanctuary Guardian",
120 | "Kraken of the Sea",
121 | "Giygas - Cease to Exist!", //aka Pokey Means Business
122 | "Inside the Dungeon",
123 | "Megaton Walk",
124 | "Magicant - The Sea of Eden",
125 | "Sky Runner - Explosion (Unused)",
126 | "Sky Runner - Explosion",
127 | "Magic Cake",
128 | "Pokey's House (with Buzz Buzz)",
129 | "Buzz Buzz Swatted",
130 | "Onett at Night (Version 2, with Buzz Buzz)",
131 | "Phone Call",
132 | "Annoying Knock (Right)",
133 | "Pink Cloud Shrine",
134 | "Buzz Buzz Emerges",
135 | "Buzz Buzz's Prophecy",
136 | "Heartless Hotel",
137 | "Onett Flyover",
138 | "Onett (with sunrise)",
139 | "Fanfare - A Good Buddy",
140 | "Starman Junior Appears",
141 | "Snow Wood Boarding School", //aka Snowman
142 | "Phase Distorter - Failure",
143 | "Phase Distorter - Teleport to Lost Underworld",
144 | "Boy Meets Girl (Twoson)",
145 | "Threed, Free At Last",
146 | "The Runaway Five, Free To Go!",
147 | "Flying Man",
148 | "Cave Ambiance (\"Onett at Night Version 2\")",
149 | "Deep Underground (Unused)", //Extra-spooky MOTHER 1 track
150 | "Greeting the Sanctuary Boss",
151 | "Teleportation - Arrival",
152 | "Saturn Valley Caverns",
153 | "Elevator (Going Down)",
154 | "Elevator (Going Up)",
155 | "Elevator (Stopping)",
156 | "Topolla Theater",
157 | "Battle Aganst Belch (Duplicate Entry)",
158 | "Magicant - Realization",
159 | "Magicant - Departure",
160 | "Sailing to Scaraba - Onwards!",
161 | "Stonehenge Base Shuts Down",
162 | "Tessie Watchers",
163 | "Meteor Fall",
164 | "Battle Against an Otherworldly Foe",
165 | "The Runaway Five To The Rescue!",
166 | "Annoying Knock (Left)",
167 | "Alien Investigation (Onett)",
168 | "Past Your Bedtime",
169 | "Pokey's Theme",
170 | "Onett at Night (Version 4, with Buzz Buzz)",
171 | "Greeting the Sanctuary Boss (Duplicate Entry)",
172 | "Meteor Strike (fades into 0x98)",
173 | "Opening Credits",
174 | "Are You Sure? Yep!",
175 | "Peaceful Rest Valley Ambiance",
176 | "Sound Stone - Giant Step",
177 | "Sound Stone - Lilliput Steps",
178 | "Sound Stone - Milky Well",
179 | "Sound Stone - Rainy Circle",
180 | "Sound Stone - Magnet Hill",
181 | "Sound Stone - Pink Cloud",
182 | "Sound Stone - Lumine Hall",
183 | "Sound Stone - Fire Spring",
184 | "Sound Stone - Empty",
185 | "Eight Melodies",
186 | "Dalaam Flyover",
187 | "Winters Flyover",
188 | "Pokey's Theme (Helicopter)",
189 | "Good Morning, Moonside",
190 | "Gas Station (Part 2)",
191 | "Title Screen",
192 | "Battle Swirl (Normal)",
193 | "Pokey Springs Into Action",
194 | "Good Morning, Scaraba",
195 | "Robotomy",
196 | "Pokey's Helicopter (Unused)",
197 | "The Heroes Return (Part 2)",
198 | "Static",
199 | "Fanfare - Instant Victory",
200 | "You Win! (Version 3, versus Boss)",
201 | "Giygas - Lashing Out (Phase 3)",
202 | "Giygas - Mindless (Phase 1)",
203 | "Giygas - Give Us Strength!",
204 | "Good Morning, Winters",
205 | "Sound Stone - Empty (Duplicate Entry)",
206 | "Giygas - Breaking Down (Quiet)",
207 | "Giygas - Weakening (Quiet)",
208 | };
209 |
210 | BOOL open_orig_rom(char *filename) {
211 | FILE *f = fopen(filename, "rb");
212 | if (!f) {
213 | MessageBox2(strerror(errno), filename, MB_ICONEXCLAMATION);
214 | return FALSE;
215 | }
216 | long size = _filelength(_fileno(f));
217 | if (size != rom_size) {
218 | MessageBox2("File is not same size as current ROM", filename, MB_ICONEXCLAMATION);
219 | fclose(f);
220 | return FALSE;
221 | }
222 | if (orig_rom) fclose(orig_rom);
223 | orig_rom = f;
224 | orig_rom_offset = size & 0x200;
225 | free(orig_rom_filename);
226 | orig_rom_filename = _strdup(filename);
227 | return TRUE;
228 | }
229 |
230 | void load_metadata() {
231 | for (int i = 0; i < NUM_SONGS; i++)
232 | bgm_title[i] = (char *)bgm_orig_title[i];
233 | metadata_changed = FALSE;
234 |
235 | // We want an absolute path here, so we don't get screwed by
236 | // GetOpenFileName's current-directory shenanigans when we update.
237 | char *lastpart;
238 | GetFullPathName(rom_filename, MAX_PATH, md_filename, &lastpart);
239 | char *ext = strrchr(lastpart, '.');
240 | if (!ext) ext = lastpart + strlen(lastpart);
241 | strcpy(ext, ".ebmused");
242 |
243 | FILE *mf = fopen(md_filename, "r");
244 | if (!mf) return;
245 |
246 | int c;
247 | while ((c = fgetc(mf)) >= 0) {
248 | char buf[MAX_PATH];
249 | #if MAX_TITLE_LEN >= MAX_PATH
250 | #error
251 | #endif
252 | if (c == 'O') {
253 | fgetc(mf);
254 | fgets(buf, MAX_PATH, mf);
255 | { char *p = strchr(buf, '\n'); if (p) *p = '\0'; }
256 | open_orig_rom(buf);
257 | } else if (c == 'R') {
258 | int start, end;
259 | fscanf(mf, "%X %X", &start, &end);
260 | change_range(start, end, AREA_NON_SPC, AREA_FREE);
261 | while ((c = fgetc(mf)) >= 0 && c != '\n');
262 | } else if (c == 'T') {
263 | unsigned int bgm;
264 | fscanf(mf, "%X %" MAX_TITLE_LEN_STR "[^\n]", &bgm, buf);
265 | if (--bgm < NUM_SONGS)
266 | bgm_title[bgm] = _strdup(buf);
267 | while ((c = fgetc(mf)) >= 0 && c != '\n');
268 | } else {
269 | printf("unrecognized metadata line %c\n", c);
270 | }
271 | }
272 | fclose(mf);
273 | }
274 |
275 | void save_metadata() {
276 | if (!metadata_changed) return;
277 | FILE *mf = fopen(md_filename, "w");
278 | if (!mf) {
279 | MessageBox2(strerror(errno), md_filename, MB_ICONEXCLAMATION);
280 | return;
281 | }
282 |
283 | if (orig_rom_filename)
284 | fprintf(mf, "O %s\n", orig_rom_filename);
285 |
286 | // SPC ranges containing at least one free area
287 | for (int i = 0; i < area_count; i++) {
288 | int start = areas[i].address;
289 | int has_free = 0;
290 | for (; areas[i].pack >= AREA_FREE; i++)
291 | has_free |= areas[i].pack == AREA_FREE;
292 | if (has_free)
293 | fprintf(mf, "R %06X %06X\n", start, areas[i].address);
294 | }
295 |
296 | for (int i = 0; i < NUM_SONGS; i++)
297 | if (strcmp(bgm_title[i], bgm_orig_title[i]) != 0)
298 | fprintf(mf, "T %02X %s\n", i+1, bgm_title[i]);
299 |
300 | int size = ftell(mf);
301 | fclose(mf);
302 | if (size == 0) remove(md_filename);
303 | metadata_changed = FALSE;
304 | }
305 |
306 | void free_metadata() {
307 | if (orig_rom) { fclose(orig_rom); orig_rom = NULL; }
308 | free(orig_rom_filename);
309 | orig_rom_filename = NULL;
310 | for (int i = 0; i < NUM_SONGS; i++)
311 | if (bgm_title[i] != bgm_orig_title[i])
312 | free(bgm_title[i]);
313 | }
314 |
--------------------------------------------------------------------------------
/src/midi.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include "ebmusv2.h"
4 | #include "misc.h"
5 |
6 | static HMIDIIN hMidiIn = NULL;
7 |
8 | static void outputMidiError(unsigned int err) {
9 | char errmsg[256];
10 | midiInGetErrorText(err, &errmsg[0], 255);
11 | MessageBox2(errmsg, "MIDI Error", MB_ICONEXCLAMATION);
12 | }
13 |
14 | void closeMidiInDevice() {
15 | if (hMidiIn != NULL) {
16 | midiInStop(hMidiIn);
17 | midiInClose(hMidiIn);
18 | hMidiIn = NULL;
19 | }
20 | }
21 |
22 | void openMidiInDevice(int deviceId, void* callback) {
23 | if (deviceId > -1) {
24 | unsigned int err;
25 | if ((err = midiInOpen(&hMidiIn, deviceId, (DWORD_PTR)(void*)callback, 0, CALLBACK_FUNCTION))) {
26 | outputMidiError(err);
27 | return;
28 | }
29 |
30 | if ((err = midiInStart(hMidiIn))) {
31 | midiInClose(hMidiIn);
32 | outputMidiError(err);
33 | return;
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/misc.c:
--------------------------------------------------------------------------------
1 | #define _WIN32_WINNT 0x600
2 | #include
3 | #include
4 | #include "ebmusv2.h"
5 | #include "misc.h"
6 |
7 | void enable_menu_items(const BYTE *list, int flags) {
8 | while (*list) EnableMenuItem(hmenu, *list++, flags);
9 | }
10 |
11 | void update_menu_item(UINT item, LPTSTR label) {
12 | MENUITEMINFO menuiteminfo = { sizeof(MENUITEMINFO) };
13 | GetMenuItemInfo(hmenu, item, FALSE, &menuiteminfo);
14 | menuiteminfo.fMask = MIIM_STRING;
15 | menuiteminfo.dwTypeData = label;
16 | SetMenuItemInfo(hmenu, item, FALSE, &menuiteminfo);
17 | }
18 |
19 | HFONT oldfont;
20 | COLORREF oldtxt, oldbk;
21 |
22 | static int dpi_x;
23 | static int dpi_y;
24 |
25 | static HFONT hFixedFont;
26 | static HFONT hDefaultGUIFont;
27 | static HFONT hTabsFont;
28 | static HFONT hOrderFont;
29 |
30 | void set_up_hdc(HDC hdc) {
31 | oldfont = SelectObject(hdc, default_font());
32 | oldtxt = SetTextColor(hdc, GetSysColor(COLOR_WINDOWTEXT));
33 | oldbk = SetBkColor(hdc, GetSysColor(COLOR_3DFACE));
34 | }
35 |
36 | void reset_hdc(HDC hdc) {
37 | SelectObject(hdc, oldfont);
38 | SetTextColor(hdc, oldtxt);
39 | SetBkColor(hdc, oldbk);
40 | }
41 |
42 | int fgetw(FILE *f) {
43 | int lo, hi;
44 | lo = fgetc(f); if (lo < 0) return -1;
45 | hi = fgetc(f); if (hi < 0) return -1;
46 | return lo | hi<<8;
47 | }
48 |
49 | // Like Set/GetDlgItemInt but for hex.
50 | // (Why isn't this in the Win32 API? Darned decimal fascists)
51 | BOOL SetDlgItemHex(HWND hwndDlg, int idControl, UINT uValue, int size) {
52 | char buf[9];
53 | sprintf(buf, "%0*X", size, uValue);
54 | return SetDlgItemText(hwndDlg, idControl, buf);
55 | }
56 |
57 | int GetDlgItemHex(HWND hwndDlg, int idControl) {
58 | char buf[9];
59 | int n = -1;
60 | if (GetDlgItemText(hwndDlg, idControl, buf, 9)) {
61 | char *endp;
62 | n = strtol(buf, &endp, 16);
63 | if (*endp != '\0') n = -1;
64 | }
65 | return n;
66 | }
67 |
68 | // MessageBox takes the focus away and doesn't restore it - annoying,
69 | // since the user will probably want to correct the error.
70 | int MessageBox2(char *error, char *title, int flags) {
71 | HWND focus = GetFocus();
72 | int ret = MessageBox(hwndMain, error, title, flags);
73 | SetFocus(focus);
74 | return ret;
75 | }
76 |
77 | void setup_dpi_scale_values(void) {
78 | // Use the old DPI system, which works as far back as Windows 2000 Professional
79 | HDC screen;
80 | if (0) {
81 | // Per-monitor DPI awareness checking would go here
82 | } else if ((screen = GetDC(0)) != NULL) {
83 | // https://docs.microsoft.com/en-us/previous-versions/ms969894(v=msdn.10)
84 | dpi_x = GetDeviceCaps(screen, LOGPIXELSX);
85 | dpi_y = GetDeviceCaps(screen, LOGPIXELSY);
86 |
87 | ReleaseDC(0, screen);
88 | } else {
89 | printf("GetDC failed; filling in default values for DPI.\n");
90 | dpi_x = 96;
91 | dpi_y = 96;
92 | }
93 |
94 | printf("DPI values initialized: %d %d\n", dpi_x, dpi_y);
95 | }
96 |
97 | int scale_x(int n) {
98 | return MulDiv(n, dpi_x, 96);
99 | }
100 |
101 | int scale_y(int n) {
102 | return MulDiv(n, dpi_y, 96);
103 | }
104 |
105 | void set_up_fonts(void) {
106 | LOGFONT lf = {0};
107 | LOGFONT lf2 = {0};
108 | NONCLIENTMETRICS ncm = {0};
109 | // This size is different in 2000 and XP. That could be causing different values to be returned
110 | // between the Windows SDK and MinGW builds for the new iPaddedBorderWidth field?
111 | // So don't use that field for now.
112 | // https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-nonclientmetricsa#remarks
113 | ncm.cbSize = sizeof(NONCLIENTMETRICS);
114 | BOOL ncmInitialized = SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(NONCLIENTMETRICS), &ncm, 0);
115 |
116 | HFONT h = GetStockObject(ANSI_FIXED_FONT);
117 | int err = GetObject(h, sizeof(LOGFONT), &lf);
118 | if (err != sizeof(LOGFONT)) {
119 | printf("ANSI_FIXED_FONT: only %d bytes written to lf!\n", err);
120 | hFixedFont = h;
121 | } else {
122 | strcpy(lf.lfFaceName, "Consolas");
123 | if (!ncmInitialized) {
124 | lf.lfHeight = scale_y(lf.lfHeight + 3);
125 | lf.lfWidth = 0;
126 | } else {
127 | // Make the font wide enough to nearly fill the instrument view
128 | // (Courier New/Consolas are roughly twice as tall as they are wide, and the header has
129 | // 20 characters)
130 | lf.lfWidth = (scale_x(180) - ncm.iScrollWidth) / 20;
131 | lf.lfHeight = lf.lfWidth * 2;
132 | }
133 | hFixedFont = CreateFontIndirect(&lf);
134 | }
135 |
136 | hDefaultGUIFont = GetStockObject(DEFAULT_GUI_FONT);
137 |
138 | err = GetObject(hDefaultGUIFont, sizeof(LOGFONT), &lf);
139 | if (err != sizeof(LOGFONT)) {
140 | printf("DEFAULT_GUI_FONT: only %d bytes written to lf!\n", err);
141 | hOrderFont = GetStockObject(SYSTEM_FONT);
142 | } else {
143 | lf.lfWeight = FW_BOLD;
144 | lf.lfHeight = scale_y(16);
145 | hOrderFont = CreateFontIndirect(&lf);
146 | }
147 |
148 | if (!ncmInitialized) {
149 | err = GetObject(GetStockObject(SYSTEM_FONT), sizeof(LOGFONT), &lf2);
150 | if (err != sizeof(LOGFONT)) {
151 | printf("SYSTEM_FONT: only %d bytes written to lf2!\n", err);
152 | hTabsFont = hDefaultGUIFont;
153 | }
154 | lf.lfHeight = scale_y(lf2.lfHeight - 1);
155 | hTabsFont = CreateFontIndirect(&lf);
156 | } else {
157 | lf = ncm.lfMessageFont;
158 | lf.lfHeight = scale_y(16);
159 | hTabsFont = CreateFontIndirect(&lf);
160 | }
161 | }
162 |
163 | void destroy_fonts(void) {
164 | DeleteObject(hFixedFont);
165 | DeleteObject(hDefaultGUIFont);
166 | DeleteObject(hTabsFont);
167 | DeleteObject(hOrderFont);
168 | }
169 |
170 | HFONT fixed_font(void) {
171 | return hFixedFont;
172 | }
173 |
174 | HFONT default_font(void) {
175 | return hDefaultGUIFont;
176 | }
177 |
178 | HFONT tabs_font(void) {
179 | return hTabsFont;
180 | }
181 |
182 | HFONT order_font(void) {
183 | return hOrderFont;
184 | }
185 |
186 | void *array_insert(void **array, int *size, int elemsize, int index) {
187 | int new_size = elemsize * ++*size;
188 | char *a = realloc(*array, new_size);
189 | index *= elemsize;
190 | *array = a;
191 | a += index;
192 | memmove(a + elemsize, a, new_size - (index + elemsize));
193 | return a;
194 | }
195 |
196 | /*void array_delete(void *array, int *size, int elemsize, int index) {
197 | int new_size = elemsize * --*size;
198 | char *a = array;
199 | index *= elemsize;
200 | a += index;
201 | memmove(a, a + elemsize, new_size - index);
202 | }*/
203 |
--------------------------------------------------------------------------------
/src/misc.h:
--------------------------------------------------------------------------------
1 | #ifndef MISC_H
2 | #define MISC_H
3 |
4 | #define WIN32_LEAN_AND_MEAN
5 | #include
6 | #include
7 |
8 | void enable_menu_items(const BYTE *list, int flags);
9 | void update_menu_item(UINT item, LPTSTR label);
10 | #ifdef CreateWindow
11 | void set_up_hdc(HDC hdc);
12 | void reset_hdc(HDC hdc);
13 | #endif
14 | #ifdef EOF
15 | int fgetw(FILE *f);
16 | #endif
17 | BOOL SetDlgItemHex(HWND hwndDlg, int idControl, unsigned int uValue, int size);
18 | int GetDlgItemHex(HWND hwndDlg, int idControl);
19 | int MessageBox2(char *error, char *title, int flags);
20 | char *open_dialog(BOOL (WINAPI *func)(LPOPENFILENAME ofn), char *filter, char *extension, DWORD flags);
21 | void setup_dpi_scale_values(void);
22 | int scale_x(int n);
23 | int scale_y(int n);
24 | void set_up_fonts(void);
25 | void destroy_fonts(void);
26 | HFONT fixed_font(void);
27 | HFONT default_font(void);
28 | HFONT tabs_font(void);
29 | HFONT order_font(void);
30 | // Don't declare parameters, to avoid pointer conversion warnings
31 | // (you can't implicitly convert between foo** and void** because of
32 | // word-addressed architectures. This is x86 so it's ok)
33 | void *array_insert(/*void **array, int *size, int elemsize, int index*/);
34 | void array_delete(void *array, int *size, int elemsize, int index);
35 |
36 | #endif // MISC_H
37 |
--------------------------------------------------------------------------------
/src/packlist.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #define WIN32_LEAN_AND_MEAN
4 | #include
5 | #define _WIN32_IE 0x0300
6 | #include
7 | #include "ebmusv2.h"
8 | #include "misc.h"
9 |
10 | #define IDC_RLIST_CAPTION 10
11 | #define IDC_ROM_LIST 11
12 | #define IDC_MLIST_CAPTION 12
13 | #define IDC_INMEM_LIST 13
14 |
15 | #define IDC_RANGE_OPTS_HDR 20
16 | #define IDC_RANGE_START 21
17 | #define IDC_RANGE_TO 22
18 | #define IDC_RANGE_END 23
19 | #define IDC_RANGE_ADD 24
20 | #define IDC_RANGE_REMOVE 25
21 |
22 | #define IDC_PACK_OPTS_HDR 30
23 | #define IDC_PACK_SAVE 31
24 | #define IDC_PACK_ADDRESS 32
25 | #define IDC_PACK_MOVE 33
26 | #define IDC_PACK_RESET 34
27 |
28 | #define IDC_SONG_OPTS_HDR 40
29 | #define IDC_SONG_ADDRESS 41
30 | #define IDC_SONG_NEW 42
31 | #define IDC_SONG_MOVE 43
32 | #define IDC_SONG_UP 44
33 | #define IDC_SONG_DOWN 45
34 | #define IDC_SONG_DEL 46
35 |
36 | static HWND rom_list, inmem_list;
37 | static int sort_by;
38 | static int inmem_sel;
39 |
40 | static const struct control_desc pack_list_controls[] = {
41 | //The format here is X position, Y position, height, and width. The negative numbers mean it starts from the bottom
42 | //The upper half of the SPC Packs tab - list of all packs in the game
43 | { "Static", 10, 10,100, 18, "All Packs:", IDC_RLIST_CAPTION, 0 },
44 | { WC_LISTVIEW,10, 30,-20,-60, NULL, IDC_ROM_LIST, WS_BORDER | LVS_REPORT | LVS_SINGLESEL },
45 |
46 | { "Static", 10,-22, 35, 20, "Range:", IDC_RANGE_OPTS_HDR, 0 },
47 | { "Edit", 50,-25, 60, 20, NULL, IDC_RANGE_START, WS_BORDER }, //Range Start textbox
48 | { "Static", 110,-22, 19, 18, "to", IDC_RANGE_TO, SS_CENTER },
49 | { "Edit", 129,-25, 60, 20, NULL, IDC_RANGE_END, WS_BORDER }, //Range End textbox
50 | { "Button", 195,-25, 52, 20, "Add", IDC_RANGE_ADD, 0 }, //Add button
51 | { "Button", 249,-25, 52, 20, "Remove", IDC_RANGE_REMOVE, 0 }, //Remove button
52 |
53 | //The lower half of the SPC Packs tab - info about anything that's been modified
54 | { "Static", 10, 10,100, 18, "Modified packs:", IDC_MLIST_CAPTION, 0 },
55 | { WC_LISTVIEW,10, 30,-20,-90, NULL, IDC_INMEM_LIST, WS_BORDER | LVS_REPORT | LVS_SINGLESEL | LVS_SHOWSELALWAYS },
56 |
57 | { "Static", 10,-52,130, 20, "ROM address for the pack:", IDC_PACK_OPTS_HDR, WS_DISABLED },
58 | { "Edit", 145,-55, 60, 20, NULL, IDC_PACK_ADDRESS, WS_BORDER | WS_DISABLED }, //ROM Address textbox
59 | { "Button", 210,-55, 40, 20, "Apply", IDC_PACK_MOVE, WS_DISABLED }, //ROM Address Apply button
60 |
61 | { "Static", 10,-27,130, 20, "ARAM offset for the song:", IDC_SONG_OPTS_HDR, WS_DISABLED },
62 | { "Edit", 145,-30, 60, 20, NULL, IDC_SONG_ADDRESS, WS_BORDER | WS_DISABLED }, //ARAM Address textbox
63 | { "Button", 210,-30, 40, 20, "Apply", IDC_SONG_MOVE, WS_DISABLED }, //ARAM Address Apply button
64 |
65 | //Buttons on the right for modifying the pack
66 | { "Button", 290,-55, 118, 20, "Save Changes", IDC_PACK_SAVE, WS_DISABLED }, //Same as Ctrl-S I think
67 | { "Button", 410,-55, 118, 20, "Revert", IDC_PACK_RESET, WS_DISABLED }, //Wipes all changes
68 | { "Button", 290,-30, 78, 20, "New BGM", IDC_SONG_NEW, WS_DISABLED }, //adds a new song entry in the current modified pack
69 | { "Button", 370,-30, 78, 20, "Delete BGM", IDC_SONG_DEL, WS_DISABLED }, //Deleltes the currently-selected song
70 | { "Button", 450,-30, 38, 20, "Up", IDC_SONG_UP, WS_DISABLED }, //Moves the currently-selected song
71 | { "Button", 490,-30, 38, 20, "Down", IDC_SONG_DOWN, WS_DISABLED },
72 |
73 | };
74 |
75 | static struct window_template pack_list_template = {
76 | 22, 14, 0, 0, pack_list_controls
77 | };
78 |
79 | static void show_blocks(HWND packlist, struct pack *p, LV_ITEM *lvi) {
80 | char buf[MAX_TITLE_LEN+5];
81 | struct block *b = p->blocks;
82 | int packno = lvi->lParam >> 16;
83 | for (int i = 1; i <= p->block_count; i++, b++) {
84 | lvi->mask = LVIF_PARAM;
85 | lvi->iSubItem = 0;
86 | lvi->lParam = (lvi->lParam & 0xFFFF0000) | i;
87 | (void)ListView_InsertItem(packlist, lvi);
88 | lvi->mask = LVIF_TEXT;
89 | lvi->iSubItem = 1;
90 | lvi->pszText = buf;
91 | sprintf(buf, "%04X-%04X", b->spc_address, b->spc_address + b->size - 1);
92 | (void)ListView_SetItem(packlist, lvi);
93 | lvi->iSubItem = 2;
94 | sprintf(buf, "%d", b->size);
95 | (void)ListView_SetItem(packlist, lvi);
96 |
97 | lvi->iSubItem = 4;
98 | if (b->spc_address == 0x0500) {
99 | lvi->pszText = "Program";
100 | } else if (b->spc_address >= 0x6C00 && b->spc_address < 0x6E00) {
101 | lvi->pszText = "Sample pointers";
102 | } else if (b->spc_address >= 0x6E00 && b->spc_address < 0x6F80) {
103 | lvi->pszText = "Instruments";
104 | } else if (b->spc_address == 0x6F80) {
105 | lvi->pszText = "Note style tables";
106 | } else if (b->spc_address >= 0x7000 && b->spc_address <= 0xE800) {
107 | lvi->pszText = "Samples";
108 | } else if (b->spc_address >= 0x4800 && b->spc_address < 0x6C00) {
109 | strcpy(buf, "Unused song");
110 | for (int song = 0; song < NUM_SONGS; song++) {
111 | if (pack_used[song][2] == packno && song_address[song] == b->spc_address) {
112 | sprintf(buf, "%02X: %s", song+1, bgm_title[song]);
113 | break;
114 | }
115 | }
116 | } else {
117 | lvi->pszText = "Unknown";
118 | }
119 | (void)ListView_SetItem(packlist, lvi);
120 | lvi->iItem++;
121 | }
122 | }
123 |
124 | static void show_or_hide_blocks(HWND packlist, LV_ITEM *lvi) {
125 | int packno = HIWORD(lvi->lParam);
126 | struct pack *pack = (packlist == rom_list ? rom_packs : inmem_packs) + packno;
127 |
128 | LV_FINDINFO lvfi;
129 | lvfi.flags = LVFI_PARAM;
130 | lvfi.lParam = packno << 16 | 1;
131 | int block = ListView_FindItem(packlist, -1, &lvfi);
132 | if (block >= 0) {
133 | for (int i = 0; i < pack->block_count; i++)
134 | (void)ListView_DeleteItem(packlist, block);
135 | } else {
136 | show_blocks(packlist, pack, lvi);
137 | }
138 | }
139 |
140 | static int CALLBACK comparator(LPARAM first, LPARAM second, LPARAM column) {
141 | int p[2] = { HIWORD(first), HIWORD(second) };
142 | int val[2];
143 | for (int i = 0; i < 2; i++) {
144 | int v = 0;
145 | if (p[i] == 0xFF) { // Free area
146 | struct area *a = &areas[LOWORD(i ? second : first)];
147 | if (column == 1)
148 | v = a->address;
149 | else if (column == 2)
150 | v = (a+1)->address - a->address;
151 | else if (column == 3)
152 | v = -4;
153 | } else { // Pack
154 | struct pack *rp = &rom_packs[p[i]];
155 | if (column == 1)
156 | v = rp->start_address;
157 | else if (column == 2)
158 | v = calc_pack_size(rp);
159 | else if (column == 3)
160 | v = -rp->status;
161 | }
162 | val[i] = v;
163 | }
164 | if (val[0] < val[1]) return -1;
165 | if (val[0] > val[1]) return 1;
166 | if (first < second) return -1;
167 | if (first > second) return 1;
168 | return 0;
169 | }
170 |
171 | static void center_on_item(HWND packlist, LPARAM lParam) {
172 | LV_FINDINFO lvfi;
173 | lvfi.flags = LVFI_PARAM;
174 | lvfi.lParam = lParam;
175 | int index = ListView_FindItem(packlist, -1, &lvfi);
176 | if (index < 0) return;
177 | RECT rc;
178 | GetClientRect(packlist, &rc);
179 | SetFocus(packlist);
180 | ListView_SetItemState(packlist, index,
181 | LVIS_FOCUSED | LVIS_SELECTED,
182 | LVIS_FOCUSED | LVIS_SELECTED);
183 | // Center the view around the selection
184 | POINT pt;
185 | (void)ListView_GetItemPosition(packlist, index, &pt);
186 | (void)ListView_Scroll(packlist, 0, pt.y - (rc.bottom >> 1));
187 | }
188 |
189 | static void hide_free_ranges() {
190 | LV_FINDINFO lvfi;
191 | lvfi.flags = LVFI_STRING;
192 | lvfi.psz = "--";
193 | int index;
194 | while ((index = ListView_FindItem(rom_list, -1, &lvfi)) >= 0)
195 | (void)ListView_DeleteItem(rom_list, index);
196 | }
197 |
198 | static void show_free_ranges() {
199 | LV_ITEM lvi;
200 | char buf[14];
201 | lvi.iItem = ListView_GetItemCount(rom_list);
202 | for (int i = 0; i < area_count; i++) {
203 | if (areas[i].pack != AREA_FREE) continue;
204 | lvi.mask = LVIF_TEXT | LVIF_PARAM;
205 | lvi.pszText = "--";
206 | lvi.iSubItem = 0;
207 | lvi.lParam = 0xFF0000 | i;
208 | (void)ListView_InsertItem(rom_list, &lvi);
209 | lvi.mask = LVIF_TEXT;
210 | lvi.iSubItem = 1;
211 | lvi.pszText = buf;
212 | sprintf(buf, "%06X-%06X", areas[i].address, areas[i+1].address - 1);
213 | (void)ListView_SetItem(rom_list, &lvi);
214 | lvi.iSubItem = 2;
215 | sprintf(buf, "%d", areas[i+1].address - areas[i].address);
216 | (void)ListView_SetItem(rom_list, &lvi);
217 | lvi.iSubItem = 3;
218 | lvi.pszText = "Free";
219 | (void)ListView_SetItem(rom_list, &lvi);
220 | lvi.iItem++;
221 | }
222 | }
223 |
224 | static void show_rom_packs() {
225 | HWND packlist = rom_list;
226 |
227 | LVITEM lvi;
228 | lvi.iItem = 0;
229 | char buf[14];
230 | SendMessage(packlist, WM_SETREDRAW, FALSE, 0);
231 | for (int i = 0; i < NUM_PACKS; i++) {
232 | struct pack *pack = &rom_packs[i];
233 |
234 | lvi.mask = LVIF_TEXT | LVIF_PARAM;
235 | lvi.lParam = i << 16;
236 | lvi.pszText = buf;
237 | lvi.iSubItem = 0;
238 | sprintf(buf, "%02X", i);
239 | (void)ListView_InsertItem(packlist, &lvi);
240 |
241 | int size = calc_pack_size(pack);
242 | lvi.mask = LVIF_TEXT;
243 | lvi.iSubItem = 1;
244 | sprintf(buf, "%06X-%06X",
245 | pack->start_address, pack->start_address + size - 1);
246 | (void)ListView_SetItem(packlist, &lvi);
247 |
248 | lvi.iSubItem = 2;
249 | sprintf(buf, "%d", size);
250 | (void)ListView_SetItem(packlist, &lvi);
251 |
252 | lvi.iSubItem = 3;
253 | static char *const status_text[] = {
254 | "Original", "Modified", "Invalid", "Saved"
255 | };
256 | lvi.pszText = status_text[pack->status];
257 | (void)ListView_SetItem(packlist, &lvi);
258 |
259 | lvi.iItem++;
260 | if (i == packs_loaded[2] && !(inmem_packs[i].status & IPACK_CHANGED))
261 | show_blocks(packlist, pack, &lvi);
262 | }
263 | show_free_ranges(packlist);
264 | if (sort_by != 0)
265 | (void)ListView_SortItems(packlist, comparator, sort_by);
266 |
267 | SendMessage(packlist, WM_SETREDRAW, TRUE, 0);
268 | center_on_item(packlist, packs_loaded[2] << 16 | (1 + current_block));
269 |
270 | }
271 |
272 | static void show_inmem_packs() {
273 | HWND packlist = inmem_list;
274 | LVITEM lvi;
275 |
276 | lvi.iItem = 0;
277 | SendMessage(packlist, WM_SETREDRAW, FALSE, 0);
278 | for (int i = 0; i < NUM_PACKS; i++) {
279 | struct pack *pack = &inmem_packs[i];
280 | if (!(pack->status & IPACK_CHANGED)) continue;
281 | char buf[25];
282 |
283 | lvi.mask = LVIF_TEXT | LVIF_PARAM;
284 | lvi.lParam = i << 16;
285 | lvi.pszText = buf;
286 | lvi.iSubItem = 0;
287 | sprintf(buf, "%02X", i);
288 | (void)ListView_InsertItem(packlist, &lvi);
289 |
290 | int size = calc_pack_size(pack);
291 | lvi.mask = LVIF_TEXT;
292 | lvi.iSubItem = 1;
293 | sprintf(buf, "%06X-%06X", pack->start_address, pack->start_address + size - 1);
294 | (void)ListView_SetItem(packlist, &lvi);
295 |
296 | lvi.iSubItem = 2;
297 | sprintf(buf, "%d", size);
298 | (void)ListView_SetItem(packlist, &lvi);
299 |
300 | lvi.iSubItem = 3;
301 | int conflict = check_range(pack->start_address, pack->start_address + size, i);
302 | switch (conflict) {
303 | case AREA_NOT_IN_FILE: lvi.pszText = "Invalid address"; break;
304 | case AREA_NON_SPC: lvi.pszText = "Out of range"; break;
305 | case AREA_FREE: lvi.pszText = "Ready to save"; break;
306 | default: sprintf(buf, "Overlap with %02X", conflict); break;
307 | }
308 | (void)ListView_SetItem(packlist, &lvi);
309 |
310 | lvi.iItem++;
311 | if (i == packs_loaded[2])
312 | show_blocks(packlist, pack, &lvi);
313 |
314 | }
315 | SendMessage(packlist, WM_SETREDRAW, TRUE, 0);
316 | center_on_item(packlist, packs_loaded[2] << 16 | (1 + current_block));
317 | }
318 |
319 | static void packs_saved() {
320 | inmem_sel = -1;
321 | (void)ListView_DeleteAllItems(inmem_list);
322 | show_inmem_packs();
323 | }
324 |
325 | LRESULT CALLBACK PackListWndProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
326 | switch (uMsg) {
327 | case WM_CREATE: {
328 | inmem_sel = -1;
329 | create_controls(hWnd, &pack_list_template, lParam);
330 |
331 | rom_list = GetDlgItem(hWnd, IDC_ROM_LIST);
332 | inmem_list = GetDlgItem(hWnd, IDC_INMEM_LIST);
333 | (void)ListView_SetExtendedListViewStyle(rom_list, LVS_EX_FULLROWSELECT);
334 | (void)ListView_SetExtendedListViewStyle(inmem_list, LVS_EX_FULLROWSELECT);
335 | LVCOLUMN lvc;
336 | lvc.mask = LVCF_TEXT | LVCF_WIDTH;
337 | for (int i = 0; i < 5; i++) {
338 | static char *const colname[] = { "#", "Address", "Size", "Status", "Description" };
339 | static const WORD cx[] = { 30, 120, 60, 100, 270 };
340 | lvc.pszText = colname[i];
341 | lvc.cx = scale_x(cx[i]);
342 | (void)ListView_InsertColumn(rom_list, i, &lvc);
343 | (void)ListView_InsertColumn(inmem_list, i, &lvc);
344 | }
345 | break;
346 | }
347 | case WM_ROM_OPENED:
348 | show_rom_packs();
349 | show_inmem_packs();
350 | for (int i = 20; i <= 25; i++)
351 | EnableWindow(GetDlgItem(hWnd, i), TRUE);
352 | break;
353 | case WM_ROM_CLOSED:
354 | inmem_sel = -1;
355 | (void)ListView_DeleteAllItems(rom_list);
356 | (void)ListView_DeleteAllItems(inmem_list);
357 | for (int i = 20; i <= 46; i++)
358 | EnableWindow(GetDlgItem(hWnd, i), FALSE);
359 | break;
360 | case WM_PACKS_SAVED:
361 | update_all:
362 | packs_saved();
363 | (void)ListView_DeleteAllItems(rom_list);
364 | show_rom_packs();
365 | // fallthrough
366 | case WM_SONG_IMPORTED:
367 | update_inmem:
368 | inmem_sel = -1;
369 | (void)ListView_DeleteAllItems(inmem_list);
370 | show_inmem_packs();
371 | break;
372 | case WM_SIZE:
373 | // make the top listview twice as big as the bottom
374 | pack_list_template.divy = (HIWORD(lParam) - 120) * 2 / 3 + 30;
375 | move_controls(hWnd, &pack_list_template, lParam);
376 | break;
377 | case WM_NOTIFY: {
378 | NM_LISTVIEW *nm = (LPNM_LISTVIEW)lParam;
379 | HWND hwnd = nm->hdr.hwndFrom;
380 | UINT code = nm->hdr.code;
381 | if (code == LVN_ITEMACTIVATE) {
382 | int index = ListView_GetNextItem(hwnd, -1, LVNI_SELECTED);
383 | LV_ITEM lvi;
384 | lvi.mask = LVIF_PARAM;
385 | lvi.iItem = index;
386 | lvi.iSubItem = 0;
387 | if (!ListView_GetItem(hwnd, &lvi)) break;
388 | if (LOWORD(lvi.lParam) == 0) {
389 | // Double-clicked on a pack - show/hide its blocks
390 | lvi.iItem++;
391 | show_or_hide_blocks(hwnd, &lvi);
392 | } else {
393 | // Double-clicked on a block - load it
394 | int pack = HIWORD(lvi.lParam);
395 | if (pack >= NUM_PACKS) break;
396 | int block = LOWORD(lvi.lParam) - 1;
397 | load_songpack(pack);
398 | if (hwnd == rom_list)
399 | select_block_by_address(rom_packs[pack].blocks[LOWORD(block)].spc_address);
400 | else
401 | select_block(block);
402 | }
403 | } else if (code == LVN_COLUMNCLICK) {
404 | sort_by = nm->iSubItem;
405 | (void)ListView_SortItems(hwnd, comparator, sort_by);
406 | int index = ListView_GetNextItem(hwnd, -1, LVNI_SELECTED);
407 | (void)ListView_EnsureVisible(hwnd, index, TRUE);
408 | } else if (code == LVN_ITEMCHANGED) {
409 | if (hwnd == inmem_list && nm->uNewState & LVIS_SELECTED) {
410 | inmem_sel = nm->lParam;
411 | printf("Selected %x\n", inmem_sel);
412 | for (int i = 30; i <= 46; i++) {
413 | EnableWindow(GetDlgItem(hWnd, i), TRUE);
414 | }
415 | }
416 | } else {
417 | // printf("Notify %d\n", code);
418 | }
419 | break;
420 | }
421 | case WM_COMMAND: {
422 | WORD id = LOWORD(wParam);
423 | int pack = HIWORD(inmem_sel);
424 | struct pack *p = &inmem_packs[pack];
425 | switch (id) {
426 | case IDC_RANGE_ADD:
427 | case IDC_RANGE_REMOVE: {
428 | int start = GetDlgItemHex(hWnd, IDC_RANGE_START);
429 | if (start < 0) break;
430 | int end = GetDlgItemHex(hWnd, IDC_RANGE_END);
431 | if (end < 0) break;
432 |
433 | int from = (id == IDC_RANGE_ADD) ? AREA_NON_SPC : AREA_FREE;
434 | int to = (id == IDC_RANGE_ADD) ? AREA_FREE : AREA_NON_SPC;
435 | printf("changing range\n");
436 | change_range(start, end + 1, from, to);
437 | metadata_changed = TRUE;
438 | hide_free_ranges();
439 | show_free_ranges();
440 | goto update_inmem;
441 | }
442 | case IDC_PACK_SAVE:
443 | if (save_pack(pack)) {
444 | save_metadata();
445 | goto update_all;
446 | }
447 | break;
448 | case IDC_PACK_MOVE: {
449 | int addr = GetDlgItemHex(hWnd, IDC_PACK_ADDRESS);
450 | if (addr < 0) break;
451 | printf("moving %d to %x\n", pack, addr);
452 | p->start_address = addr;
453 | goto update_inmem;
454 | }
455 | case IDC_PACK_RESET:
456 | if (pack == packs_loaded[2]) {
457 | load_songpack(0xFF);
458 | select_block(-1);
459 | }
460 | free_pack(&inmem_packs[pack]);
461 | goto update_inmem;
462 | case IDC_SONG_NEW: {
463 | int addr = GetDlgItemHex(hWnd, IDC_SONG_ADDRESS);
464 | if (addr < 0) break;
465 | load_songpack(pack);
466 | struct block b = { 21, addr, calloc(21, 1) };
467 | *(WORD *)b.data = addr + 4;
468 | new_block(&b);
469 | goto update_inmem;
470 | }
471 | case IDC_SONG_MOVE: {
472 | int addr = GetDlgItemHex(hWnd, IDC_SONG_ADDRESS);
473 | if (addr < 0) break;
474 | int block = LOWORD(inmem_sel) - 1;
475 | if (block < 0 || block >= p->block_count) break;
476 | load_songpack(pack);
477 | select_block(block);
478 | if (cur_song.order_length) {
479 | cur_song.address = addr;
480 | cur_song.changed = TRUE;
481 | save_cur_song_to_pack();
482 | goto update_inmem;
483 | }
484 | break;
485 | }
486 | case IDC_SONG_UP:
487 | case IDC_SONG_DOWN: {
488 | int from = LOWORD(inmem_sel) - 1;
489 | if (from < 0 || from >= p->block_count) break;
490 | int to = from + (id == IDC_SONG_UP ? -1 : 1);
491 | if (to < 0 || to >= p->block_count) break;
492 | load_songpack(pack);
493 | select_block(from);
494 | move_block(to);
495 | goto update_inmem;
496 | }
497 | case IDC_SONG_DEL: {
498 | int block = LOWORD(inmem_sel) - 1;
499 | if (block < 0 || block >= p->block_count) break;
500 | load_songpack(pack);
501 | delete_block(block);
502 | goto update_inmem;
503 | }
504 | }
505 | }
506 | default:
507 | return DefWindowProc(hWnd, uMsg, wParam, lParam);
508 | }
509 | return 0;
510 | }
511 |
--------------------------------------------------------------------------------
/src/packs.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include "ebmusv2.h"
5 | #include "misc.h"
6 |
7 | const DWORD pack_orig_crc[] = {
8 | 0x35994B97, 0xDB04D065, 0xC13D8165, 0xEEFF028E, 0x5330392D, 0x705AEBBC,
9 | 0x4ED3BBAB, 0xFF11F6A1, 0x9E69B6C1, 0xBF0F580B, 0x0460DAD8, 0xD3EEC6FB,
10 | 0x082C8FC1, 0x5B81C947, 0xE157E6C2, 0x641EB570, 0x79C6A5D2, 0xFE892ACA,
11 | 0xEE4C1723, 0x947F5985, 0x20822EF9, 0xCF193A5F, 0x311520DA, 0x10765295,
12 | 0xAA43B31F, 0xE72085EC, 0x324821DE, 0x73054B6A, 0x0AE457C7, 0xB9D0E9D4,
13 | 0xC93C3678, 0x3DFED2B6, 0xDEB68F1C, 0x8C62B8B6, 0x7F35744F, 0x8D3E7AF9,
14 | 0xFCAF31CD, 0x827F8B23, 0x347E2419, 0x9945AE89, 0x83245B62, 0x6432A069,
15 | 0x58290E16, 0xCED6200A, 0x1424E797, 0x802E483A, 0x53583F0B, 0x1FB73242,
16 | 0x9BA15381, 0x83BFCDBD, 0x07E9A480, 0x105074C2, 0xD90BBBC1, 0xE9E68007,
17 | 0x9E7DAAC1, 0xEEECB692, 0x3B4F935F, 0x6B9CB808, 0x2625C94A, 0xA9210DC4,
18 | 0x8EF74F19, 0x3EF02201, 0x7B8B59E9, 0xAA163725, 0x849B7F34, 0xE15EF409,
19 | 0xC2774561, 0x8898F96B, 0x87344C8F, 0xCFF94FCF, 0x58907350, 0x7269B3F8,
20 | 0x66C0991C, 0x4992871D, 0xB72E486D, 0xBF0BE12F, 0xA3509B6F, 0xBEF628D0,
21 | 0x9452EC07, 0x032D37F3, 0x559DDB52, 0x7429ACCE, 0xF3FF8749, 0x6DDC7BB8,
22 | 0x5BDA587E, 0x95B863A7, 0x35BD7758, 0x733A7B93, 0xFD61E984, 0x93B4834F,
23 | 0xF446B13F, 0x1D3426C1, 0x9BA7D579, 0x2DB7314D, 0x2630298F, 0xB432655A,
24 | 0xE2E071F4, 0x8B393217, 0x51033BB3, 0x1619C100, 0x8EB3F2FC, 0x82207885,
25 | 0xEB4767C6, 0x6CDE8654, 0x61DB258E, 0x2DBA2FEF, 0x19F45E6D, 0xF90F25A4,
26 | 0xDDE08443, 0xD187DFCB, 0x0630027E, 0xCEFFC22B, 0xF5F39A6D, 0x88C82FBA,
27 | 0xF3B86811, 0x005EEE83, 0xBADE3AC4, 0xBE11ECEA, 0x2EA452C2, 0x7C09903E,
28 | 0xD3055E99, 0x184714DA, 0x8C3E615A, 0x4FB3F125, 0x6BC1A993, 0x58BDDFE4,
29 | 0x8E8ED38B, 0x10637EEB, 0xC654BDFA, 0x6AB0C2F7, 0xDFFF0971, 0x9855C03D,
30 | 0xA36E5FD6, 0xF6A72D30, 0x80AAE5AD, 0x1195ED2F, 0x87A0336E, 0x824F38DB,
31 | 0x2458ADC6, 0xCCD4AC63, 0xAB6C84DB, 0x90DFCA16, 0x55F1C184, 0x2BFFE745,
32 | 0xE5F96BF9, 0x9BE7C8D6, 0x0F5DADC7, 0x02BEA184, 0x66CC6C71, 0x8100B1C5,
33 | 0x2E894645, 0xF487A0B5, 0x60EDA440, 0x4CBA4829, 0xFD5F55ED, 0x37C5DEA7,
34 | 0x664D83E7, 0x135D3B35, 0x7ED32ACE, 0x2D23FA7E, 0x5B969EA6, 0xDC7A49AD,
35 | 0xEE1071E0, 0x28E8DB77, 0x02E1409C, 0x0665F2E2, 0xE01946DF, 0xB6E7A174,
36 | 0xD07DBF27,
37 | };
38 |
39 | void free_pack(struct pack *p) {
40 | for (int i = 0; i < p->block_count; i++)
41 | free(p->blocks[i].data);
42 | free(p->blocks);
43 | p->status = 0;
44 | }
45 |
46 | struct pack *load_pack(int pack) {
47 | struct pack *mp = &inmem_packs[pack];
48 | if (!(mp->status & IPACK_INMEM)) {
49 | struct pack *rp = &rom_packs[pack];
50 | mp->start_address = rp->start_address;
51 | mp->block_count = rp->block_count;
52 | mp->blocks = memcpy(malloc(mp->block_count * sizeof(struct block)),
53 | rp->blocks, mp->block_count * sizeof(struct block));
54 | struct block *b = mp->blocks;
55 | fseek(rom, mp->start_address - 0xC00000 + rom_offset, SEEK_SET);
56 | for (int i = 0; i < mp->block_count; i++) {
57 | fseek(rom, 4, SEEK_CUR);
58 | b->data = malloc(b->size);
59 | fread(b->data, b->size, 1, rom);
60 | b++;
61 | }
62 | mp->status |= IPACK_INMEM;
63 | }
64 |
65 | return mp;
66 | }
67 |
68 | // Changes the current song pack.
69 | // This should always be followed by a call to select_block()
70 | void load_songpack(int new_pack) {
71 | if (packs_loaded[2] == new_pack)
72 | return;
73 |
74 | // Unload the current songpack unless it has been changed
75 | if (packs_loaded[2] < NUM_PACKS) {
76 | struct pack *old = &inmem_packs[packs_loaded[2]];
77 | if (!(old->status & IPACK_CHANGED))
78 | free_pack(old);
79 | }
80 |
81 | packs_loaded[2] = new_pack;
82 | if (new_pack >= NUM_PACKS)
83 | return;
84 |
85 | load_pack(new_pack);
86 | }
87 |
88 | struct block *get_cur_block() {
89 | if (packs_loaded[2] < NUM_PACKS) {
90 | struct pack *p = &inmem_packs[packs_loaded[2]];
91 | if (current_block >= 0 && current_block < p->block_count)
92 | return &p->blocks[current_block];
93 | }
94 | return NULL;
95 | }
96 |
97 | void select_block(int block) {
98 | current_block = block;
99 |
100 | free_song(&cur_song);
101 |
102 | struct block *b = get_cur_block();
103 | if (b != NULL) {
104 | memcpy(&spc[b->spc_address], b->data, b->size);
105 | decompile_song(&cur_song, b->spc_address, b->spc_address + b->size);
106 | }
107 | initialize_state();
108 | }
109 |
110 | void select_block_by_address(int spc_addr) {
111 | int bnum = -1;
112 | if (packs_loaded[2] < NUM_PACKS) {
113 | struct pack *p = &inmem_packs[packs_loaded[2]];
114 | for (bnum = p->block_count - 1; bnum >= 0; bnum--) {
115 | struct block *b = &p->blocks[bnum];
116 | if ((unsigned)(spc_addr - b->spc_address) < b->size) break;
117 | }
118 | }
119 | select_block(bnum);
120 | }
121 |
122 | struct block *save_cur_song_to_pack() {
123 | struct block *b = get_cur_block();
124 | if (b && cur_song.changed) {
125 | int size = compile_song(&cur_song);
126 | b->size = size;
127 | b->spc_address = cur_song.address;
128 | free(b->data);
129 | b->data = memcpy(malloc(size), &spc[cur_song.address], size);
130 | inmem_packs[packs_loaded[2]].status |= IPACK_CHANGED;
131 | cur_song.changed = FALSE;
132 | }
133 | return b;
134 | }
135 |
136 | int calc_pack_size(struct pack *p) {
137 | int size = 2;
138 | for (int i = 0; i < p->block_count; i++)
139 | size += 4 + p->blocks[i].size;
140 | return size;
141 | }
142 |
143 | // Adds a new block to the current pack
144 | void new_block(struct block *b) {
145 | struct pack *p = &inmem_packs[packs_loaded[2]];
146 | int pos = 0;
147 | while (pos < p->block_count && p->blocks[pos].spc_address <= b->spc_address)
148 | pos++;
149 |
150 | struct block *newb = array_insert(&p->blocks, &p->block_count, sizeof(struct block), pos);
151 | *newb = *b;
152 | p->status |= IPACK_CHANGED;
153 | select_block(pos);
154 | }
155 |
156 | void delete_block(int block) {
157 | struct pack *p = &inmem_packs[packs_loaded[2]];
158 | select_block(-1);
159 | memmove(&p->blocks[block], &p->blocks[block+1], (--p->block_count - block) * sizeof(struct block));
160 | p->status |= IPACK_CHANGED;
161 | }
162 |
163 | // Moves the current block to a different location within the pack
164 | void move_block(int to) {
165 | struct pack *p = &inmem_packs[packs_loaded[2]];
166 | int from = current_block;
167 | struct block b = p->blocks[from];
168 | if (to > from) {
169 | memmove(&p->blocks[from], &p->blocks[from + 1], (to - from) * sizeof(struct block));
170 | } else {
171 | memmove(&p->blocks[to + 1], &p->blocks[to], (from - to) * sizeof(struct block));
172 | }
173 | p->blocks[to] = b;
174 | current_block = to;
175 | p->status |= IPACK_CHANGED;
176 | }
177 |
178 | BOOL save_pack(int pack) {
179 | static char error[512];
180 | struct pack *p = &inmem_packs[pack];
181 | struct pack *rp = &rom_packs[pack];
182 | if (!(p->status & IPACK_CHANGED))
183 | return FALSE;
184 |
185 | if (!orig_rom) {
186 | MessageBox2("Before saving a pack, the original ROM file needs to be specified so that it can be used to ensure that no unused remnants of previous versions of the pack are left in the file in such a way that they would increase the patch size.", "Save", 48);
187 | return FALSE;
188 | }
189 | int size = calc_pack_size(p);
190 | int conflict = check_range(p->start_address, p->start_address + size, pack);
191 | if (conflict != AREA_FREE) {
192 | char *p = error;
193 | p += sprintf(p, "Pack %02X could not be saved:\n", pack);
194 | if (conflict == AREA_NOT_IN_FILE)
195 | strcpy(p, "The ROM address is invalid");
196 | else if (conflict == AREA_NON_SPC)
197 | strcpy(p, "The ROM address is not in a range designated for SPC data");
198 | else
199 | sprintf(p, "Would overlap with pack %02X", conflict);
200 | MessageBox2(error, "Save", 48);
201 | return FALSE;
202 | }
203 |
204 | int old_start = rp->start_address;
205 | int old_size = calc_pack_size(rp);
206 | BYTE *filler = malloc(old_size);
207 |
208 | fseek(orig_rom, old_start - 0xC00000 + orig_rom_offset, SEEK_SET);
209 | if (!fread(filler, old_size, 1, orig_rom)) {
210 | error:
211 | MessageBox2(strerror(errno), "Save", 16);
212 | return FALSE;
213 | }
214 | fseek(rom, old_start - 0xC00000 + rom_offset, SEEK_SET);
215 | if (!fwrite(filler, old_size, 1, rom))
216 | goto error;
217 | free(filler);
218 |
219 | fseek(rom, PACK_POINTER_TABLE + rom_offset + 3*pack, SEEK_SET);
220 | fputc(p->start_address >> 16, rom);
221 | fputc(p->start_address, rom);
222 | fputc(p->start_address >> 8, rom);
223 |
224 | fseek(rom, p->start_address - 0xC00000 + rom_offset, SEEK_SET);
225 | for (int i = 0; i < p->block_count; i++) {
226 | struct block *b = &p->blocks[i];
227 | if (!fwrite(b, 4, 1, rom))
228 | goto error;
229 | if (!fwrite(b->data, b->size, 1, rom))
230 | goto error;
231 | }
232 | if (!fwrite("\0", 2, 1, rom))
233 | goto error;
234 | fflush(rom);
235 |
236 | p->status &= ~IPACK_CHANGED;
237 |
238 | rp->start_address = p->start_address;
239 | rp->status = RPACK_SAVED;
240 | rp->block_count = p->block_count;
241 | free(rp->blocks);
242 | rp->blocks = memcpy(malloc(sizeof(struct block) * p->block_count),
243 | p->blocks, sizeof(struct block) * p->block_count);
244 |
245 | change_range(old_start, old_start + old_size, pack, AREA_FREE);
246 | change_range(p->start_address, p->start_address + size, AREA_FREE, pack);
247 |
248 | if (pack != packs_loaded[2])
249 | free_pack(p);
250 |
251 | // an SPC range that previously had no free blocks might have some now
252 | metadata_changed = TRUE;
253 |
254 | return TRUE;
255 | }
256 |
--------------------------------------------------------------------------------
/src/parser.c:
--------------------------------------------------------------------------------
1 | #include "ebmusv2.h"
2 |
3 | // number of bytes following a Ex/Fx code
4 | const BYTE code_length[] = {
5 | 1, 1, 2, 3, 0, 1, 2, 1, 2, 1, 1, 3, 0, 1, 2, 3,
6 | 1, 3, 3, 0, 1, 3, 0, 3, 3, 3, 1, 2, 0, 0, 0, 0
7 | };
8 |
9 | void parser_init(struct parser *p, const struct channel_state *c) {
10 | p->ptr = c->ptr;
11 | p->sub_start = c->sub_start;
12 | p->sub_ret = c->sub_ret;
13 | p->sub_count = c->sub_count;
14 | p->note_len = c->note_len;
15 | }
16 |
17 | BYTE *next_code(BYTE *p) {
18 | BYTE chr = *p++;
19 | if (chr < 0x80)
20 | p += *p < 0x80;
21 | else if (chr >= 0xE0)
22 | p += code_length[chr - 0xE0];
23 | return p;
24 | }
25 |
26 | BOOL parser_advance(struct parser *p) {
27 | int chr = *p->ptr;
28 | if (chr == 0) {
29 | if (p->sub_count == 0) return FALSE;
30 | p->ptr = --p->sub_count ? cur_song.sub[p->sub_start].track : p->sub_ret;
31 | } else if (chr == 0xEF) {
32 | p->sub_ret = p->ptr + 4;
33 | p->sub_start = *(WORD *)&p->ptr[1];
34 | p->sub_count = p->ptr[3];
35 | p->ptr = cur_song.sub[p->sub_start].track;
36 | } else {
37 | if (chr < 0x80)
38 | p->note_len = chr;
39 | p->ptr = next_code(p->ptr);
40 | }
41 | return TRUE;
42 | }
43 |
--------------------------------------------------------------------------------
/src/play.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include "ebmusv2.h"
5 | #include "id.h"
6 |
7 | BYTE spc[65536];
8 | int inst_base = 0x6E00;
9 |
10 | // note style tables, from 6F80
11 | static const unsigned char release_table[] = {
12 | 0x33, 0x66, 0x7f, 0x99, 0xb2, 0xcc, 0xe5, 0xfc
13 | };
14 | static const unsigned char volume_table[] = {
15 | 0x19, 0x33, 0x4c, 0x66, 0x72, 0x7f, 0x8c, 0x99,
16 | 0xa5, 0xb2, 0xbf, 0xcc, 0xd8, 0xe5, 0xf2, 0xfc
17 | };
18 |
19 | static void calc_total_vol(struct song_state *st, struct channel_state *c,
20 | signed char trem_phase)
21 | {
22 | BYTE v = (trem_phase << 1 ^ trem_phase >> 7) & 0xFF;
23 | v = ~(v * c->tremolo_range >> 8) & 0xFF;
24 |
25 | v = v * (st->volume.cur >> 8) >> 8;
26 | v = v * volume_table[c->note_style & 15] >> 8;
27 | v = v * (c->volume.cur >> 8) >> 8;
28 | c->total_vol = v * v >> 8;
29 | }
30 |
31 | static int calc_vol_3(struct channel_state *c, int pan, int flag) {
32 | static const BYTE pan_table[] = {
33 | 0x00, 0x01, 0x03, 0x07, 0x0D, 0x15, 0x1E, 0x29,
34 | 0x34, 0x42, 0x51, 0x5E, 0x67, 0x6E, 0x73, 0x77,
35 | 0x7A, 0x7C, 0x7D, 0x7E, 0x7F, 0xAA
36 | };
37 | const BYTE *ph = &pan_table[pan >> 8];
38 | int v = ph[0] + ((ph[1] - ph[0]) * (pan & 255) >> 8);
39 | v = v * c->total_vol >> 8;
40 | if (c->pan_flags & flag) v = -v;
41 | return v;
42 | }
43 |
44 | static void calc_vol_2(struct channel_state *c, int pan) {
45 | c->left_vol = calc_vol_3(c, pan, 0x80);
46 | c->right_vol = calc_vol_3(c, 0x1400 - pan, 0x40);
47 | }
48 |
49 | static void make_slider(struct slider *s, int cycles, int target) {
50 | s->delta = cycles == 0 ? 0xFF : ((target << 8) - (s->cur & 0xFF00)) / cycles;
51 | s->cycles = cycles;
52 | s->target = target;
53 | }
54 |
55 | static void slide(struct slider *s) {
56 | if (s->cycles) {
57 | if (--s->cycles == 0)
58 | s->cur = s->target << 8;
59 | else
60 | s->cur += s->delta;
61 | }
62 | }
63 |
64 | void set_inst(struct song_state *st, struct channel_state *c, int inst) {
65 | static const short rates[32] = {
66 | 0, 2048, 1536, 1280, 1024, 768, 640, 512,
67 | 384, 320, 256, 192, 160, 128, 96, 80,
68 | 64, 48, 40, 32, 24, 20, 16, 12,
69 | 10, 8, 6, 5, 4, 3, 2, 1
70 | };
71 | // CA and up is for instruments in the second pack (set with FA xx)
72 | if (inst >= 0x80)
73 | inst += st->first_CA_inst - 0xCA;
74 |
75 | BYTE *idata = &spc[inst_base + 6*inst];
76 | if (inst < 0 || inst >= MAX_INSTRUMENTS || !samp[idata[0]].data ||
77 | (idata[4] == 0 && idata[5] == 0))
78 | {
79 | printf("ch %d: bad inst %X\n", (int)(c - st->chan), inst);
80 | return;
81 | }
82 |
83 | c->inst = inst;
84 | c->inst_adsr1 = idata[1];
85 | c->inst_adsr2 = idata[2];
86 | c->inst_gain = idata[3];
87 | c->attack_rate = rates[(c->inst_adsr1 & 0xF) * 2 + 1];
88 | c->decay_rate = rates[((c->inst_adsr1 >> 4) & 7) * 2 + 16];
89 | c->sustain_level = ((c->inst_adsr2 >> 5) & 7) * 0x100 + 0x100;
90 | c->sustain_rate = rates[c->inst_adsr2 & 0x1F];
91 | c->gain_rate = rates[c->inst_gain & 0x1F];
92 | }
93 |
94 | // calculate how far to advance the sample pointer on each output sample
95 | void calc_freq(struct channel_state *c, int note16) {
96 | static const WORD note_freq_table[] = {
97 | 0x085F, 0x08DE, 0x0965, 0x09F4, 0x0A8C, 0x0B2C, 0x0BD6, 0x0C8B,
98 | 0x0D4A, 0x0E14, 0x0EEA, 0x0FCD, 0x10BE
99 | };
100 |
101 | // What is this for???
102 | if (note16 >= 0x3400) note16 += (note16 >> 8) - 0x34;
103 | else if (note16 < 0x1300) note16 += ((note16 >> 8) - 0x13) << 1;
104 |
105 | if ((WORD)note16 >= 0x5400) {
106 | c->note_freq = 0;
107 | return;
108 | }
109 |
110 | int octave = (note16 >> 8) / 12;
111 | int tone = (note16 >> 8) % 12;
112 | int freq = note_freq_table[tone];
113 | freq += (note_freq_table[tone+1] - freq) * (note16 & 0xFF) >> 8;
114 | freq <<= 1;
115 | freq >>= 6 - octave;
116 |
117 | BYTE *inst_freq = &spc[inst_base + 6*c->inst + 4];
118 | freq *= (inst_freq[0] << 8 | inst_freq[1]);
119 | freq >>= 8;
120 | freq &= 0x3fff;
121 |
122 | c->note_freq = (freq * (32000U << (15 - 12))) / mixrate;
123 | }
124 |
125 | static int calc_vib_disp(struct channel_state *c, int phase) {
126 | int range = c->cur_vib_range;
127 | if (range > 0xF0)
128 | range = (range - 0xF0) * 256;
129 |
130 | int disp = (phase << 2) & 255; /* //// */
131 | if (phase & 0x40) disp ^= 0xFF; /* /\/\ */
132 | disp = (disp * range) >> 8;
133 |
134 | if (phase & 0x80) disp = -disp; /* /\ */
135 | return disp; /* \/ */
136 | }
137 |
138 | // do a Ex/Fx code
139 | static void do_command(struct song_state *st, struct channel_state *c) {
140 | unsigned char *p = c->ptr;
141 | c->ptr += 1 + code_length[*p - 0xE0];
142 | switch (*p) {
143 | case 0xE0:
144 | set_inst(st, c, p[1]);
145 | break;
146 | case 0xE1:
147 | c->pan_flags = p[1];
148 | c->panning.cur = (p[1] & 0x1F) << 8;
149 | break;
150 | case 0xE2:
151 | make_slider(&c->panning, p[1], p[2]);
152 | break;
153 | case 0xE3:
154 | c->vibrato_start = p[1];
155 | c->vibrato_speed = p[2];
156 | c->cur_vib_range = c->vibrato_max_range = p[3];
157 | c->vibrato_fadein = 0;
158 | break;
159 | case 0xE4:
160 | c->cur_vib_range = c->vibrato_max_range = 0;
161 | c->vibrato_fadein = 0;
162 | break;
163 | case 0xE5:
164 | st->volume.cur = p[1] << 8;
165 | break;
166 | case 0xE6:
167 | make_slider(&st->volume, p[1], p[2]);
168 | break;
169 | case 0xE7:
170 | st->tempo.cur = p[1] << 8;
171 | break;
172 | case 0xE8:
173 | make_slider(&st->tempo, p[1], p[2]);
174 | break;
175 | case 0xE9:
176 | st->transpose = p[1];
177 | break;
178 | case 0xEA:
179 | c->transpose = p[1];
180 | break;
181 | case 0xEB:
182 | c->tremolo_start = p[1];
183 | c->tremolo_speed = p[2];
184 | c->tremolo_range = p[3];
185 | break;
186 | case 0xEC:
187 | c->tremolo_range = 0;
188 | break;
189 | case 0xED:
190 | c->volume.cur = p[1] << 8;
191 | break;
192 | case 0xEE:
193 | make_slider(&c->volume, p[1], p[2]);
194 | break;
195 | case 0xEF:
196 | c->sub_start = p[1] | (p[2] << 8);
197 | c->sub_ret = c->ptr;
198 | c->sub_count = p[3];
199 | c->ptr = cur_song.sub[c->sub_start].track;
200 | break;
201 | case 0xF0:
202 | c->vibrato_fadein = p[1];
203 | c->vibrato_range_delta = p[1] == 0 ? 0xFF : c->cur_vib_range / p[1];
204 | break;
205 | case 0xF1: case 0xF2:
206 | c->port_type = *p & 1;
207 | c->port_start = p[1];
208 | c->port_length = p[2];
209 | c->port_range = p[3];
210 | break;
211 | case 0xF3:
212 | c->port_length = 0;
213 | break;
214 | case 0xF4:
215 | c->finetune = p[1];
216 | break;
217 | case 0xF9: {
218 | c->cur_port_start_ctr = p[1];
219 | int target = p[3] + st->transpose;
220 | if (target >= 0x100) target -= 0xFF;
221 | target += c->transpose;
222 | make_slider(&c->note, p[2], target & 0x7F);
223 | break;
224 | }
225 | case 0xFA:
226 | st->first_CA_inst = p[1];
227 | break;
228 | }
229 | }
230 |
231 | static short initial_env_height(BOOL adsr_on, unsigned char gain) {
232 | if (adsr_on || (gain & 0x80))
233 | return 0;
234 | else
235 | return (gain & 0x7F) * 16;
236 | }
237 |
238 | void initialize_envelope(struct channel_state *c) {
239 | c->env_height = initial_env_height(c->inst_adsr1 & 0x80, c->inst_gain);
240 | c->next_env_height = c->env_height;
241 | c->env_state = (c->inst_adsr1 & 0x80) ? ENV_STATE_ATTACK : ENV_STATE_GAIN;
242 | c->next_env_state = c->env_state;
243 | c->env_counter = 0;
244 | c->env_fractional_counter = 0;
245 | }
246 |
247 | // $0654 + $08D4-$8EF
248 | static void do_note(struct song_state *st, struct channel_state *c, int note) {
249 | // using >=CA as a note switches to that instrument and plays note A4
250 | if (note >= 0xCA) {
251 | set_inst(st, c, note);
252 | note = 0xA4;
253 | }
254 |
255 | if (note < 0xC8) {
256 | c->vibrato_phase = c->vibrato_fadein & 1 ? 0x80 : 0;
257 | c->vibrato_start_ctr = 0;
258 | c->vibrato_fadein_ctr = 0;
259 | c->tremolo_phase = 0;
260 | c->tremolo_start_ctr = 0;
261 |
262 | c->samp_pos = 0;
263 | c->samp = &samp[spc[inst_base + 6*c->inst]];
264 | initialize_envelope(c);
265 |
266 | note &= 0x7F;
267 | note += st->transpose + c->transpose;
268 | c->note.cur = note << 8 | c->finetune;
269 |
270 | c->note.cycles = c->port_length;
271 | if (c->note.cycles) {
272 | int target = note;
273 | c->cur_port_start_ctr = c->port_start;
274 | if (c->port_type == 0)
275 | c->note.cur -= c->port_range << 8;
276 | else
277 | target += c->port_range;
278 | make_slider(&c->note, c->port_length, target & 0x7F);
279 | }
280 |
281 | calc_freq(c, c->note.cur);
282 | }
283 |
284 | // Search forward for the next note (to see if it's C8). This is annoying
285 | // but necessary - C8 can continue the last note of a subroutine as well
286 | // as a normal note.
287 | int next_note;
288 | { struct parser p;
289 | parser_init(&p, c);
290 | do {
291 | if (*p.ptr >= 0x80 && *p.ptr < 0xE0)
292 | break;
293 | } while (parser_advance(&p));
294 | next_note = *p.ptr;
295 | }
296 |
297 | int rel;
298 | if (next_note == 0xC8) {
299 | // if the note will be continued, don't release yet
300 | rel = c->note_len;
301 | } else {
302 | rel = (c->note_len * release_table[c->note_style >> 4]) >> 8;
303 | if (rel > c->note_len - 2)
304 | rel = c->note_len - 2;
305 | if (rel < 1)
306 | rel = 1;
307 | }
308 | c->note_release = rel;
309 | }
310 |
311 |
312 | void load_pattern() {
313 | state.ordnum++;
314 | if (state.ordnum >= cur_song.order_length) {
315 | if (--state.repeat_count >= 0x80)
316 | state.repeat_count = cur_song.repeat;
317 | if (state.repeat_count == 0) {
318 | state.ordnum--;
319 | stop_playing();
320 | EnableMenuItem(hmenu, ID_PLAY, MF_ENABLED);
321 | return;
322 | }
323 | state.ordnum = cur_song.repeat_pos;
324 | }
325 |
326 | int pat = cur_song.order[state.ordnum];
327 | printf("Order %d: pattern %d\n", state.ordnum, pat);
328 |
329 | int ch;
330 | for (ch = 0; ch < 8; ch++) {
331 | state.chan[ch].ptr = cur_song.pattern[pat][ch].track;
332 | state.chan[ch].sub_count = 0;
333 | state.chan[ch].volume.cycles = 0;
334 | state.chan[ch].panning.cycles = 0;
335 | state.chan[ch].next = 0;
336 | }
337 | state.patpos = 0;
338 |
339 | pattop_state = state;
340 | }
341 |
342 | static void CF7(struct channel_state *c) {
343 | if (c->note_release) {
344 | c->note_release--;
345 | }
346 | if (c->note_release == 0) {
347 | c->next_env_state = ENV_STATE_KEY_OFF;
348 | }
349 |
350 | // 0D60
351 | if (c->note.cycles) {
352 | if (c->cur_port_start_ctr == 0) {
353 | slide(&c->note);
354 | calc_freq(c, c->note.cur);
355 | } else {
356 | c->cur_port_start_ctr--;
357 | }
358 | }
359 |
360 | // 0D79
361 | if (c->cur_vib_range) {
362 | if (c->vibrato_start_ctr == c->vibrato_start) {
363 | int range;
364 | if (c->vibrato_fadein_ctr == c->vibrato_fadein) {
365 | range = c->vibrato_max_range;
366 | } else {
367 | range = c->cur_vib_range;
368 | if (c->vibrato_fadein_ctr == 0)
369 | range = 0;
370 | range += c->vibrato_range_delta;
371 | c->vibrato_fadein_ctr++;
372 | } // DA0
373 | c->cur_vib_range = range;
374 | c->vibrato_phase += c->vibrato_speed;
375 | calc_freq(c, c->note.cur + calc_vib_disp(c, c->vibrato_phase));
376 | } else {
377 | c->vibrato_start_ctr++;
378 | }
379 | }
380 | }
381 |
382 | // $07F9 + $0625
383 | static BOOL do_cycle(struct song_state *st) {
384 | int ch;
385 | struct channel_state *c;
386 | for (ch = 0; ch < 8; ch++) {
387 | c = &st->chan[ch];
388 | if (c->ptr == NULL) continue; //8F0
389 |
390 | if (--c->next >= 0) {
391 | CF7(c);
392 | } else while (1) {
393 | unsigned char *p = c->ptr;
394 |
395 | if (*p == 0) { // end of sub or pattern
396 | if (c->sub_count) // end of sub
397 | c->ptr = --c->sub_count
398 | ? cur_song.sub[c->sub_start].track
399 | : c->sub_ret;
400 | else
401 | return FALSE;
402 | } else if (*p < 0x80) {
403 | c->note_len = *p;
404 | if (p[1] < 0x80) {
405 | c->note_style = p[1];
406 | c->ptr += 2;
407 | } else {
408 | c->ptr++;
409 | }
410 | } else if (*p < 0xE0) {
411 | c->ptr++;
412 | c->next = c->note_len - 1;
413 | do_note(st, c, *p);
414 | break;
415 | } else { // E0-FF
416 | do_command(st, c);
417 | }
418 | }
419 | // $0B84
420 | if (c->note.cycles == 0 && *c->ptr == 0xF9)
421 | do_command(st, c);
422 | }
423 |
424 | st->patpos++;
425 |
426 | slide(&st->tempo);
427 | slide(&st->volume);
428 |
429 | for (c = &st->chan[0]; c != &st->chan[8]; c++) {
430 | if (c->ptr == NULL) continue;
431 |
432 | // @ 0C40
433 | slide(&c->volume);
434 |
435 | // @ 0C4D
436 | int tphase = 0;
437 | if (c->tremolo_range) {
438 | if (c->tremolo_start_ctr == c->tremolo_start) {
439 | if (c->tremolo_phase >= 0x80 && c->tremolo_range == 0xFF)
440 | c->tremolo_phase = 0x80;
441 | else
442 | c->tremolo_phase += c->tremolo_speed;
443 | tphase = c->tremolo_phase;
444 | } else {
445 | c->tremolo_start_ctr++;
446 | }
447 | }
448 | calc_total_vol(st, c, tphase);
449 |
450 | // 0C79
451 | slide(&c->panning);
452 |
453 | // 0C86: volume stuff
454 | calc_vol_2(c, c->panning.cur);
455 | }
456 | return TRUE;
457 | }
458 |
459 | BOOL do_cycle_no_sound(struct song_state *st) {
460 | BOOL ret = do_cycle(st);
461 | if (ret) {
462 | int ch;
463 | for (ch = 0; ch < 8; ch++)
464 | if (st->chan[ch].note_release == 0)
465 | st->chan[ch].samp_pos = -1;
466 | }
467 | return ret;
468 | }
469 |
470 | static int sub_cycle_calc(struct song_state *st, int delta) {
471 | if (delta < 0x8000)
472 | return st->cycle_timer * delta >> 8;
473 | else
474 | return -(st->cycle_timer * (0x10000 - delta) >> 8);
475 | }
476 |
477 | static void do_sub_cycle(struct song_state *st) {
478 | struct channel_state *c;
479 | for (c = &st->chan[0]; c != &st->chan[8]; c++) {
480 | if (c->ptr == NULL) continue;
481 | // $0DD0
482 |
483 | BOOL changed = FALSE;
484 | if (c->tremolo_range && c->tremolo_start_ctr == c->tremolo_start) {
485 | int p = c->tremolo_phase + sub_cycle_calc(st, c->tremolo_speed);
486 | changed = TRUE;
487 | calc_total_vol(st, c, p);
488 | }
489 | int pan = c->panning.cur;
490 | if (c->panning.cycles) {
491 | pan += sub_cycle_calc(st, c->panning.delta);
492 | changed = TRUE;
493 | }
494 | if (changed) calc_vol_2(c, pan);
495 |
496 | changed = FALSE;
497 | int note = c->note.cur; // $0BBC
498 | if (c->note.cycles && c->cur_port_start_ctr == 0) {
499 | note += sub_cycle_calc(st, c->note.delta);
500 | changed = TRUE;
501 | }
502 | if (c->cur_vib_range && c->vibrato_start_ctr == c->vibrato_start) {
503 | int p = c->vibrato_phase + sub_cycle_calc(st, c->vibrato_speed);
504 | note += calc_vib_disp(c, p);
505 | changed = TRUE;
506 | }
507 | if (changed) calc_freq(c, note);
508 | }
509 | }
510 |
511 | BOOL do_timer() {
512 | state.cycle_timer += state.tempo.cur >> 8;
513 | if (state.cycle_timer >= 256) {
514 | state.cycle_timer -= 256;
515 | while (!do_cycle(&state)) {
516 | load_pattern();
517 | if (!is_playing()) return FALSE;
518 | load_pattern_into_tracker();
519 | }
520 | } else {
521 | do_sub_cycle(&state);
522 | }
523 | return TRUE;
524 | }
525 |
526 | void initialize_state() {
527 | memset(&state, 0, sizeof(state));
528 | int i;
529 | for (i = 0; i < 8; i++) {
530 | state.chan[i].volume.cur = 0xFF00;
531 | state.chan[i].panning.cur = 0x0A00;
532 | state.chan[i].samp_pos = -1;
533 | set_inst(&state, &state.chan[i], 0);
534 | }
535 | state.volume.cur = 0xC000;
536 | state.tempo.cur = 0x2000;
537 | state.cycle_timer = 255;
538 |
539 | state.ordnum = -1;
540 | if (cur_song.order_length) {
541 | load_pattern();
542 | } else {
543 | pattop_state = state;
544 | stop_playing();
545 | EnableMenuItem(hmenu, ID_PLAY, MF_ENABLED);
546 | }
547 | }
548 |
--------------------------------------------------------------------------------
/src/ranges.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include "ebmusv2.h"
4 | #include "misc.h"
5 |
6 | int area_count;
7 | struct area *areas;
8 |
9 | void init_areas() {
10 | area_count = 2;
11 | areas = malloc(sizeof(struct area) * 2);
12 | areas[0].address = -2147483647 - 1; // -1 so compilers like Visual Studio won't interpret the - as a unary operator
13 | areas[0].pack = AREA_NOT_IN_FILE;
14 | areas[1].address = 2147483647;
15 | areas[1].pack = AREA_END;
16 | }
17 |
18 | static void split_area(int i, int address) {
19 | struct area *a = array_insert(&areas, &area_count, sizeof(struct area), i);
20 | a->address = address;
21 | a->pack = (a-1)->pack;
22 | }
23 |
24 | void change_range(int start, int end, int from, int to) {
25 | int i = 0;
26 |
27 | while (areas[i].address < start) i++;
28 | if (areas[i].address != start && areas[i-1].pack == from)
29 | split_area(i, start);
30 |
31 | while (areas[i].address < end) {
32 | if (areas[i+1].address > end)
33 | split_area(i+1, end);
34 |
35 | if (areas[i].pack == from) {
36 | areas[i].pack = to;
37 | if (areas[i-1].pack == to) {
38 | memmove(&areas[i], &areas[i+1], (--area_count - i) * sizeof(struct area));
39 | i--;
40 | }
41 | if (areas[i+1].pack == to) {
42 | memmove(&areas[i+1], &areas[i+2], (--area_count - (i + 1)) * sizeof(struct area));
43 | }
44 | }
45 | i++;
46 | }
47 | }
48 |
49 | // Determine if a range is OK to save a pack at. (i.e. it contains
50 | // only free areas and the current pack itself
51 | int check_range(int start, int end, int pack) {
52 | int i = 0;
53 | while (areas[i+1].address <= start) i++;
54 | while (areas[i].address < end) {
55 | if (areas[i].pack != AREA_FREE && areas[i].pack != pack)
56 | return areas[i].pack;
57 | i++;
58 | }
59 | return AREA_FREE;
60 | }
61 |
--------------------------------------------------------------------------------
/src/record.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/PKHackers/ebmused/272b0123859a46034937346e5e52ab8f3d4d9d3b/src/record.ico
--------------------------------------------------------------------------------
/src/resource.rc:
--------------------------------------------------------------------------------
1 | #include "id.h"
2 | #include
3 |
4 | 1 ICON record.ico
5 |
6 | IDM_MENU MENU
7 | BEGIN
8 | POPUP "&File"
9 | BEGIN
10 | MENUITEM "&Open ROM...\tCtrl+O", ID_OPEN
11 | MENUITEM "&Save All\tCtrl+S", ID_SAVE_ALL, GRAYED
12 | MENUITEM "&Close", ID_CLOSE, GRAYED
13 | MENUITEM SEPARATOR
14 | MENUITEM "&Import EBM...", ID_IMPORT//, GRAYED
15 | MENUITEM "&Export EBM...", ID_EXPORT//, GRAYED
16 | MENUITEM "Import S&PC...", ID_IMPORT_SPC
17 | MENUITEM "Expo&rt SPC...", ID_EXPORT_SPC//, GRAYED
18 | MENUITEM SEPARATOR
19 | MENUITEM "E&xit", ID_EXIT
20 | END
21 | POPUP "&Edit"
22 | BEGIN
23 | // MENUITEM "&Undo\tCtrl+Z", ID_UNDO, GRAYED
24 | // MENUITEM SEPARATOR
25 | MENUITEM "Cu&t\tCtrl+X", ID_CUT, GRAYED
26 | MENUITEM "&Copy\tCtrl+C", ID_COPY, GRAYED
27 | MENUITEM "&Paste\tCtrl+V", ID_PASTE, GRAYED
28 | MENUITEM "&Delete\tDel", ID_DELETE, GRAYED
29 | MENUITEM SEPARATOR
30 | MENUITEM "&Split Pattern", ID_SPLIT_PATTERN, GRAYED
31 | MENUITEM "&Join Patterns", ID_JOIN_PATTERNS, GRAYED
32 | MENUITEM "&Make Subroutine", ID_MAKE_SUBROUTINE, GRAYED
33 | MENUITEM "U&nmake Subroutine", ID_UNMAKE_SUBROUTINE, GRAYED
34 | MENUITEM "T&ranspose...", ID_TRANSPOSE, GRAYED
35 | POPUP "Set D&uration"
36 | {
37 | MENUITEM "&Increment\tCtrl+.", ID_INCREMENT_DURATION, GRAYED
38 | MENUITEM "&Decrement\tCtrl+,", ID_DECREMENT_DURATION, GRAYED
39 | MENUITEM "&Whole Note\tCtrl+1", ID_SET_DURATION_1, GRAYED
40 | MENUITEM "&Half Note\tCtrl+2", ID_SET_DURATION_2, GRAYED
41 | MENUITEM "&Quarter Note\tCtrl+3", ID_SET_DURATION_3, GRAYED
42 | MENUITEM "&Eighth Note\tCtrl+4", ID_SET_DURATION_4, GRAYED
43 | MENUITEM "&Sixteenth Note\tCtrl+5", ID_SET_DURATION_5, GRAYED
44 | MENUITEM "&Thirty-second Note\tCtrl+6", ID_SET_DURATION_6, GRAYED
45 | }
46 | END
47 | POPUP "&Options"
48 | BEGIN
49 | MENUITEM "&Sound Options...", ID_OPTIONS
50 | END
51 | POPUP "&Playback"
52 | BEGIN
53 | MENUITEM "&Play\tCtrl+P", ID_PLAY
54 | MENUITEM "&Stop\tEsc", ID_STOP, GRAYED
55 | MENUITEM "Capture &Audio...", ID_CAPTURE_AUDIO
56 | MENUITEM SEPARATOR
57 | MENUITEM "&Clear", ID_CLEAR_SONG, GRAYED
58 | END
59 | POPUP "&View"
60 | BEGIN
61 | MENUITEM "Zoom &In\tCtrl+=", ID_ZOOM_IN, GRAYED
62 | MENUITEM "Zoom &Out\tCtrl+-", ID_ZOOM_OUT, GRAYED
63 | MENUITEM "&Status Bar", ID_STATUS_BAR, CHECKED
64 | END
65 | POPUP "O&ctave"
66 | BEGIN
67 | MENUITEM "1-2\tF1", ID_OCTAVE_1
68 | MENUITEM "2-3\tF2", ID_OCTAVE_1+1
69 | MENUITEM "3-4\tF3", ID_OCTAVE_1+2
70 | MENUITEM "4-5\tF4", ID_OCTAVE_1+3
71 | MENUITEM "5-6\tF5", ID_OCTAVE_1+4
72 | END
73 | POPUP "&Help"
74 | BEGIN
75 | MENUITEM "&Code List", ID_HELP
76 | MENUITEM SEPARATOR
77 | MENUITEM "&About", ID_ABOUT
78 | END
79 | END
80 |
81 | IDM_CONTEXTMENU MENU
82 | BEGIN
83 | POPUP ""
84 | BEGIN
85 | MENUITEM "Cu&t", ID_CUT
86 | MENUITEM "&Copy", ID_COPY
87 | MENUITEM "&Paste", ID_PASTE
88 | MENUITEM "&Delete", ID_DELETE
89 | MENUITEM SEPARATOR
90 | MENUITEM "&Make Subroutine", ID_MAKE_SUBROUTINE
91 | MENUITEM "U&nmake Subroutine", ID_UNMAKE_SUBROUTINE
92 | MENUITEM "T&ranspose...", ID_TRANSPOSE
93 | END
94 | END
95 |
96 |
97 | IDA_ACCEL ACCELERATORS
98 | BEGIN
99 | VK_ESCAPE, ID_STOP, VIRTKEY
100 | VK_F1, ID_OCTAVE_1, VIRTKEY
101 | VK_F2, ID_OCTAVE_1+1, VIRTKEY
102 | VK_F3, ID_OCTAVE_1+2, VIRTKEY
103 | VK_F4, ID_OCTAVE_1+3, VIRTKEY
104 | VK_F5, ID_OCTAVE_1+4, VIRTKEY
105 | "O", ID_OPEN, CONTROL, VIRTKEY
106 | "P", ID_PLAY, CONTROL, VIRTKEY
107 | "S", ID_SAVE_ALL, CONTROL, VIRTKEY
108 | VK_OEM_MINUS, ID_ZOOM_OUT, CONTROL, VIRTKEY
109 | VK_OEM_PLUS, ID_ZOOM_IN, CONTROL, VIRTKEY
110 | // "Z", ID_UNDO, CONTROL, VIRTKEY
111 | END
112 |
113 | IDD_OPTIONS DIALOG 0, 0, 101, 62
114 | STYLE 0
115 | CAPTION "Sound Options"
116 | FONT 8, "MS Shell Dlg"
117 | BEGIN
118 | LTEXT "Sample rate (Hz):", 0, 7, 7, 55, 14
119 | EDITTEXT IDC_RATE, 65, 5, 30, 14, ES_RIGHT | ES_NUMBER
120 | LTEXT "Buffer size:", 0, 7, 26, 50, 14
121 | EDITTEXT IDC_BUFSIZE, 65, 24, 30, 14, ES_RIGHT | ES_NUMBER
122 | DEFPUSHBUTTON "&Apply", IDOK, 5, 43, 44, 14
123 | PUSHBUTTON "&Cancel", IDCANCEL, 51, 43, 44, 14
124 | END
125 |
126 | IDD_ABOUT DIALOG 0, 0, 180, 70
127 | STYLE 0
128 | CAPTION "About"
129 | FONT 8, "MS Shell Dlg"
130 | BEGIN
131 | LTEXT "EarthBound Music Editor\r\nv2.5.0\r\n", 0,
132 | 5, 5, 170, 55
133 | CONTROL "https://github.com/PKHackers/ebmused/", IDC_HOMEPAGELINK, "Static", SS_NOTIFY | WS_GROUP | WS_TABSTOP,
134 | 5, 30, 170, 8
135 | DEFPUSHBUTTON "&OK", IDOK, 125, 51, 50, 14
136 | END
137 |
138 | IDD_TRANSPOSE DIALOG 0, 0, 98, 43
139 | STYLE 0
140 | CAPTION "Transpose"
141 | FONT 8, "MS Shell Dlg"
142 | BEGIN
143 | LTEXT "Semitones:", 0, 7, 7, 50, 14
144 | EDITTEXT IDC_TRANSPOSE_OFF, 50, 5, 42, 14
145 | DEFPUSHBUTTON "&Apply", IDOK, 5, 24, 42, 14
146 | PUSHBUTTON "&Cancel", IDCANCEL, 50, 24, 42, 14
147 | END
148 |
149 | IDRC_SPC RCDATA "blank.spc"
150 |
151 | CREATEPROCESS_MANIFEST_RESOURCE_ID RT_MANIFEST "ebmused.exe.manifest"
152 |
--------------------------------------------------------------------------------
/src/song.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include "ebmusv2.h"
5 | #include "misc.h"
6 |
7 | static char errbuf[60];
8 | char *decomp_error;
9 |
10 | static char *internal_validate_track(BYTE *data, int size, BOOL is_sub) {
11 | for (int pos = 0; pos < size; ) {
12 | int byte = data[pos];
13 | int next = pos + 1;
14 |
15 | if (byte < 0x80) {
16 | if (byte == 0) return "Track can not contain [00]";
17 | if (next != size && data[next] < 0x80) next++;
18 | if (next == size) return "Track can not end with note-length code";
19 | } else if (byte >= 0xE0) {
20 | if (byte == 0xFF) return "Invalid code [FF]";
21 | next += code_length[byte - 0xE0];
22 | if (next > size) {
23 | char *p = strcpy(errbuf, "Incomplete code: [") + 18;
24 | for (; pos < size; pos++)
25 | p += sprintf(p, "%02X ", data[pos]);
26 | for (; pos < next; pos++)
27 | p += sprintf(p, "?? ");
28 | p[-1] = ']';
29 | return errbuf;
30 | }
31 |
32 | if (byte == 0xEF) {
33 | if (is_sub) return "Can't call sub from within a sub";
34 | int sub = *(WORD *)&data[pos+1];
35 | if (sub >= cur_song.subs) {
36 | sprintf(errbuf, "Subroutine %d not present", sub);
37 | return errbuf;
38 | }
39 | if (data[pos+3] == 0) return "Subroutine loop count can not be 0";
40 | }
41 | }
42 |
43 | pos = next;
44 | }
45 | return NULL;
46 | }
47 |
48 | BOOL validate_track(BYTE *data, int size, BOOL is_sub) {
49 | char *err = internal_validate_track(data, size, is_sub);
50 | if (err) {
51 | MessageBox2(err, NULL, 48/*MB_ICONEXCLAMATION*/);
52 | return FALSE;
53 | }
54 | return TRUE;
55 | }
56 |
57 | int compile_song(struct song *s) {
58 | int i;
59 |
60 | // Put order
61 | WORD *wout = (WORD *)&spc[s->address];
62 | int first_pat = s->address + s->order_length*2 + (s->repeat ? 6 : 2);
63 | for (i = 0; i < s->order_length; i++)
64 | *wout++ = first_pat + (s->order[i] << 4);
65 | if (s->repeat) {
66 | *wout++ = s->repeat;
67 | *wout++ = s->address + s->repeat_pos*2;
68 | }
69 | *wout++ = 0;
70 |
71 | // Put patterns and tracks
72 | BYTE *tracks_start = &spc[first_pat + (s->patterns << 4)];
73 | BYTE *tout = tracks_start;
74 | for (i = 0; i < s->patterns; i++) {
75 | int first = 1;
76 | for (int ch = 0; ch < 8; ch++) {
77 | struct track *t = &s->pattern[i][ch];
78 | if (t->track == NULL) {
79 | *wout++ = 0;
80 | } else {
81 | *wout++ = tout - spc;
82 | // Only the first track in a pattern is 0 terminated
83 | int size = t->size + first;
84 | memcpy(tout, t->track, size);
85 | tout += size;
86 | first = 0;
87 | }
88 | }
89 | }
90 | // There's another 0 before subs start
91 | *tout++ = 0;
92 | BYTE *tracks_end = tout;
93 |
94 | // Convert subroutine numbers into addresses, and append the subs as
95 | // they are used. This is consistent with the way the original songs are:
96 | // subs are always in order of first use, and there are no unused subs.
97 | WORD *sub_table = calloc(sizeof(WORD), s->subs);
98 | for (BYTE *pos = tracks_start; pos < tracks_end; pos = next_code(pos)) {
99 | if (*pos == 0xEF) {
100 | int sub = *(WORD *)(pos + 1);
101 | if (sub >= s->subs) abort(); // can't happen
102 |
103 | if (sub_table[sub] == 0) {
104 | struct track *t = &s->sub[sub];
105 | sub_table[sub] = tout - spc;
106 | memcpy(tout, t->track, t->size + 1);
107 | tout += t->size + 1;
108 | }
109 | *(WORD *)(pos + 1) = sub_table[sub];
110 | }
111 | }
112 | free(sub_table);
113 |
114 | return (tout - spc) - s->address;
115 | }
116 |
117 | void decompile_song(struct song *s, int start_addr, int end_addr) {
118 | char *error = errbuf;
119 | s->address = start_addr;
120 | s->changed = FALSE;
121 |
122 | // Get order length and repeat info (at this point, we don't know how
123 | // many patterns there are, so the pattern pointers aren't validated yet)
124 | WORD *wp = (WORD *)&spc[start_addr];
125 | while (*wp >= 0x100) wp++;
126 | s->order_length = wp - (WORD *)&spc[start_addr];
127 | if (s->order_length == 0) {
128 | error = "Order length is 0";
129 | goto error1;
130 | }
131 | s->repeat = *wp++;
132 | if (s->repeat == 0) {
133 | s->repeat_pos = 0;
134 | } else {
135 | int repeat_off = *wp++ - start_addr;
136 | if (repeat_off & 1 || repeat_off < 0 || repeat_off >= s->order_length*2) {
137 | sprintf(errbuf, "Bad repeat pointer: %x", repeat_off + start_addr);
138 | goto error1;
139 | }
140 | if (*wp++ != 0) {
141 | error = "Repeat not followed by end of song";
142 | goto error1;
143 | }
144 | s->repeat_pos = repeat_off >> 1;
145 | }
146 |
147 | int first_pattern = (BYTE *)wp - spc;
148 |
149 | int tracks_start;
150 | // locate first track, determine number of patterns
151 | while (((BYTE *)wp)+1 < &spc[end_addr] && *wp == 0) wp++;
152 | if (((BYTE *)wp)+1 >= &spc[end_addr]) {
153 | // no tracks in the song
154 | tracks_start = end_addr - 1;
155 | } else {
156 | tracks_start = *wp;
157 | }
158 |
159 | int pat_bytes = tracks_start - first_pattern;
160 | if (pat_bytes <= 0 || pat_bytes & 15) {
161 | sprintf(errbuf, "Bad first track pointer: %x", tracks_start);
162 | goto error1;
163 | }
164 |
165 | int tracks_end;
166 | if (((BYTE *)wp)+1 >= &spc[end_addr]) {
167 | // no tracks in the song
168 | tracks_end = end_addr - 1;
169 | } else {
170 | // find the last track
171 | int tp, tpp = tracks_start;
172 | while ((tp = *(WORD *)&spc[tpp -= 2]) == 0);
173 |
174 | if (tp < tracks_start || tp >= end_addr) {
175 | sprintf(errbuf, "Bad last track pointer: %x", tp);
176 | goto error1;
177 | }
178 |
179 |
180 | // is the last track the first one in its pattern?
181 | BOOL first = TRUE;
182 | int chan = (tpp - first_pattern) >> 1 & 7;
183 | for (; chan; chan--)
184 | first &= *(WORD *)&spc[tpp -= 2] == 0;
185 |
186 | BYTE *end = &spc[tp];
187 | while (*end) end = next_code(end);
188 | end += first;
189 | tracks_end = end - spc;
190 | }
191 |
192 | // Now the number of patterns is known, so go back and get the order
193 | s->order = malloc(sizeof(int) * s->order_length);
194 | wp = (WORD *)&spc[start_addr];
195 | for (int i = 0; i < s->order_length; i++) {
196 | int pat = *wp++ - first_pattern;
197 | if (pat < 0 || pat >= pat_bytes || pat & 15) {
198 | sprintf(errbuf, "Bad pattern pointer: %x", pat + first_pattern);
199 | goto error2;
200 | }
201 | s->order[i] = pat >> 4;
202 | }
203 |
204 | WORD *sub_table = NULL;
205 | s->patterns = pat_bytes >> 4;
206 | s->pattern = calloc(sizeof(*s->pattern), s->patterns);
207 | s->subs = 0;
208 | s->sub = NULL;
209 |
210 | wp = (WORD *)&spc[first_pattern];
211 | for (int trk = 0; trk < s->patterns * 8; trk++) {
212 | struct track *t = &s->pattern[0][0] + trk;
213 | int start = *wp++;
214 | if (start == 0) continue;
215 | if (start < tracks_start || start >= tracks_end) {
216 | sprintf(errbuf, "Bad track pointer: %x", start);
217 | goto error3;
218 | }
219 |
220 | // Go through track list (patterns) and find first track that has an address higher than us.
221 | // If we find a track after us, we'll assume that this track doesn't overlap with that one.
222 | // If we don't find one, then next will remain at 0x10000 and we will search until the
223 | // end of memory to find a 00 byte to terminate the track.
224 | int next = 0x10000; // offset of following track
225 | for (int track_ind = 0; track_ind < (s->patterns * 8); track_ind += 1) {
226 | int track_addr = ((WORD *)(spc + first_pattern))[track_ind];
227 | if (track_addr < next && track_addr > start) {
228 | next = track_addr;
229 | }
230 | }
231 | // Determine the end of the track.
232 | BYTE *track_end;
233 | for (track_end = spc + start; track_end < spc + next && *track_end != 0; track_end = next_code(track_end)) {}
234 |
235 | t->size = (track_end - spc) - start;
236 | t->track = memcpy(malloc(t->size + 1), &spc[start], t->size);
237 | t->track[t->size] = 0;
238 |
239 | for (BYTE *p = t->track; p < t->track + t->size; p = next_code(p)) {
240 | if (*p != 0xEF) continue;
241 | int sub_ptr = *(WORD *)(p + 1);
242 | int sub_entry;
243 |
244 | // find existing entry in sub_table
245 | for (sub_entry = 0; sub_entry < s->subs && sub_table[sub_entry] != sub_ptr; sub_entry++);
246 | if (sub_entry == s->subs) {
247 | // sub_entry doesn't already exist in sub_table; create it
248 | sub_entry = s->subs++;
249 |
250 | sub_table = realloc(sub_table, sizeof(WORD) * s->subs);
251 | sub_table[sub_entry] = sub_ptr;
252 |
253 | s->sub = realloc(s->sub, sizeof(struct track) * s->subs);
254 | struct track *st = &s->sub[sub_entry];
255 |
256 | BYTE *substart = &spc[sub_ptr];
257 | BYTE *subend = substart;
258 | while (*subend != 0) subend = next_code(subend);
259 | st->size = subend - substart;
260 | st->track = memcpy(malloc(st->size + 1), substart, st->size + 1);
261 | char *e = internal_validate_track(st->track, st->size, TRUE);
262 | if (e) {
263 | error = e;
264 | goto error3;
265 | }
266 | }
267 | *(WORD *)(p + 1) = sub_entry;
268 | }
269 | char *e = internal_validate_track(t->track, t->size, FALSE);
270 | if (e) {
271 | error = e;
272 | goto error3;
273 | }
274 | }
275 | free(sub_table);
276 |
277 | return;
278 |
279 | error3:
280 | free(sub_table);
281 | for (int trk = 0; trk < s->patterns * 8; trk++)
282 | free(s->pattern[0][trk].track);
283 | for (int trk = 0; trk < s->subs; trk++)
284 | free(s->sub[trk].track);
285 | free(s->sub);
286 | free(s->pattern);
287 | error2:
288 | free(s->order);
289 | error1:
290 | s->order_length = 0;
291 | decomp_error = error;
292 | printf("Can't decompile: %s\n", error);
293 | return;
294 | }
295 |
296 | void free_song(struct song *s) {
297 | int pat, ch, sub;
298 | if (!s->order_length) return;
299 | s->changed = FALSE;
300 | free(s->order);
301 | for (pat = 0; pat < s->patterns; pat++)
302 | for (ch = 0; ch < 8; ch++)
303 | free(s->pattern[pat][ch].track);
304 | free(s->pattern);
305 | for (sub = 0; sub < s->subs; sub++)
306 | free(s->sub[sub].track);
307 | free(s->sub);
308 | s->order_length = 0;
309 | }
310 |
--------------------------------------------------------------------------------
/src/songed.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include "ebmusv2.h"
5 | #include "misc.h"
6 |
7 | void order_insert(int pos, int pat) {
8 | int *p = array_insert(&cur_song.order, &cur_song.order_length,
9 | sizeof(int), pos);
10 | *p = pat;
11 |
12 | if (cur_song.repeat_pos >= pos)
13 | cur_song.repeat_pos++;
14 |
15 | if (state.ordnum >= pos) {
16 | state.ordnum++;
17 | pattop_state.ordnum++;
18 | }
19 | }
20 |
21 | void order_delete(int pos) {
22 | memmove(&cur_song.order[pos], &cur_song.order[pos+1],
23 | (--cur_song.order_length - pos) * sizeof(int));
24 | if (cur_song.repeat_pos > pos)
25 | cur_song.repeat_pos--;
26 | if (state.ordnum > pos) {
27 | state.ordnum--;
28 | pattop_state.ordnum--;
29 | }
30 | }
31 |
32 | struct track *pattern_insert(int pat) {
33 | cur_song.pattern = realloc(cur_song.pattern, ++cur_song.patterns * sizeof(struct track) * 8);
34 | memmove(&cur_song.pattern[pat+1], &cur_song.pattern[pat], (cur_song.patterns - (pat+1)) * sizeof(struct track) * 8);
35 |
36 | // Update the order to reflect the renumbered patterns
37 | for (int i = 0; i < cur_song.order_length; i++)
38 | if (cur_song.order[i] >= pat)
39 | cur_song.order[i]++;
40 |
41 | return &cur_song.pattern[pat][0];
42 | }
43 |
44 | void pattern_delete(int pat) {
45 | for (int i = 0; i < 8; i++)
46 | free(cur_song.pattern[pat][i].track);
47 | memmove(&cur_song.pattern[pat], &cur_song.pattern[pat+1], (--cur_song.patterns - pat) * sizeof(struct track) * 8);
48 |
49 | for (int i = 0; i < cur_song.order_length; ) {
50 | if (cur_song.order[i] == pat) {
51 | order_delete(i);
52 | continue;
53 | }
54 | if (cur_song.order[i] > pat) cur_song.order[i]--;
55 | i++;
56 | }
57 | }
58 |
59 | BOOL split_pattern(int pos) {
60 | struct song_state split_state;
61 | char buf[32];
62 | int ch;
63 | if (pos == 0) return FALSE;
64 | split_state = pattop_state;
65 | while (split_state.patpos < pos) {
66 | if (!do_cycle_no_sound(&split_state)) return FALSE;
67 | }
68 | for (ch = 0; ch < 8; ch++) {
69 | struct channel_state *c = &split_state.chan[ch];
70 | if (c->sub_count && *c->ptr != '\0') {
71 | sprintf(buf, "Track %d is inside a subroutine", ch);
72 | MessageBox2(buf, "Cannot split", 48/*MB_ICONEXCLAMATION*/);
73 | return FALSE;
74 | }
75 | if (c->next != 0) {
76 | sprintf(buf, "Track %d is inside a note", ch);
77 | MessageBox2(buf, "Cannot split", 48/*MB_ICONEXCLAMATION*/);
78 | return FALSE;
79 | }
80 | }
81 | int this_pat = cur_song.order[split_state.ordnum];
82 | struct track *ap = pattern_insert(this_pat + 1);
83 | struct track *bp = ap - 8;
84 | for (ch = 0; ch < 8; ch++) {
85 | struct channel_state *c = &split_state.chan[ch];
86 | BYTE *splitptr = c->sub_count ? c->sub_ret : c->ptr;
87 | if (splitptr == NULL) {
88 | ap[ch].size = 0;
89 | ap[ch].track = NULL;
90 | continue;
91 | }
92 | int before_size = splitptr - bp[ch].track;
93 | int after_size = bp[ch].size - before_size;
94 |
95 | int after_subcount = c->sub_count ? c->sub_count - 1 : 0;
96 | if (after_subcount) {
97 | after_size += 4;
98 | splitptr -= 4;
99 | splitptr[3] -= after_subcount;
100 | }
101 |
102 | ap[ch].size = after_size;
103 | ap[ch].track = memcpy(malloc(after_size + 1), splitptr, after_size + 1);
104 | if (after_subcount)
105 | ap[ch].track[3] = after_subcount;
106 |
107 | bp[ch].size = before_size;
108 | bp[ch].track[before_size] = 0;
109 | }
110 | for (int i = 0; i < cur_song.order_length; i++)
111 | if (cur_song.order[i] == this_pat)
112 | order_insert(i + 1, this_pat + 1);
113 | return TRUE;
114 | }
115 |
116 | BOOL join_patterns() {
117 | char buf[60];
118 | if (state.ordnum+1 == cur_song.order_length)
119 | return FALSE;
120 | int this_pat = cur_song.order[state.ordnum];
121 | int next_pat = cur_song.order[state.ordnum+1];
122 | int i;
123 | if (this_pat == next_pat) {
124 | MessageBox2("Next pattern is same as current", "Cannot join", 48);
125 | return FALSE;
126 | }
127 | for (i = 0; i < cur_song.order_length; i++) {
128 | if (cur_song.order[i] == this_pat) {
129 | i++;
130 | if (i == cur_song.order_length
131 | || i == cur_song.repeat_pos
132 | || cur_song.order[i] != next_pat) goto nonconsec;
133 | } else if (cur_song.order[i] == next_pat) {
134 | nonconsec:
135 | sprintf(buf, "Patterns %d and %d are not always consecutive",
136 | this_pat, next_pat);
137 | error:
138 | MessageBox2(buf, "Cannot join", 48/*MB_ICONEXCLAMATION*/);
139 | return FALSE;
140 | }
141 | }
142 | struct track *tp = cur_song.pattern[this_pat];
143 | struct track *np = cur_song.pattern[next_pat];
144 | for (i = 0; i < 8; i++) {
145 | if (tp[i].track == NULL && np[i].track != NULL) {
146 | sprintf(buf, "Track %d active in pattern %d but not in %d",
147 | i, next_pat, this_pat);
148 | goto error;
149 | } else if (tp[i].track != NULL && np[i].track == NULL) {
150 | sprintf(buf, "Track %d active in pattern %d but not in %d",
151 | i, this_pat, next_pat);
152 | goto error;
153 | }
154 | }
155 | for (i = 0; i < 8; i++) {
156 | if (tp[i].track == NULL) continue;
157 | int oldsize = tp[i].size;
158 | tp[i].size += np[i].size;
159 | tp[i].track = realloc(tp[i].track, tp[i].size + 1);
160 | memcpy(tp[i].track + oldsize, np[i].track, np[i].size + 1);
161 | }
162 | pattern_delete(next_pat);
163 | return TRUE;
164 | }
165 |
166 | // Check to see if a part of a track can be made into a subroutine
167 | static int check_repeat(BYTE *sub, int subsize, BYTE *p, int size) {
168 | if (size % subsize != 0) return 0;
169 | int cnt = size / subsize;
170 | if (cnt > 255) return 0;
171 | for (int i = 0; i < cnt; i++) {
172 | if (memcmp(p, sub, subsize) != 0) return 0;
173 | p += subsize;
174 | }
175 | return cnt;
176 | }
177 |
178 | int create_sub(BYTE *start, BYTE *end, int *count) {
179 | int size = end - start;
180 | int sub;
181 | int subsize;
182 | int cnt;
183 |
184 | for (sub = 0; sub < cur_song.subs; sub++) {
185 | struct track *t = &cur_song.sub[sub];
186 | subsize = t->size;
187 | if (subsize == 0) continue;
188 | cnt = check_repeat(t->track, subsize, start, size);
189 | if (cnt) {
190 | *count = cnt;
191 | return sub;
192 | }
193 | }
194 |
195 | if (!validate_track(start, size, TRUE))
196 | return -1;
197 |
198 | BYTE *p = start;
199 | while (p < end) {
200 | p = next_code(p);
201 | subsize = p - start;
202 | cnt = check_repeat(start, subsize, start, size);
203 | // eventually p will reach end, and this must succeed
204 | if (cnt) {
205 | struct track *t = array_insert(&cur_song.sub, &cur_song.subs,
206 | sizeof(struct track), sub);
207 | t->size = subsize;
208 | t->track = memcpy(malloc(subsize + 1), start, subsize);
209 | t->track[subsize] = '\0';
210 | *count = cnt;
211 | return sub;
212 | }
213 | }
214 | // should never get here
215 | return -1;
216 | }
217 |
--------------------------------------------------------------------------------
/src/sound.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #define WIN32_LEAN_AND_MEAN
6 | #include
7 | #include
8 | #include
9 | #include "id.h"
10 | #include "ebmusv2.h"
11 | #include "misc.h"
12 |
13 | // TODO: make non-const, add UI to allow changing this at runtime
14 | static const enum InterpolationMethod {
15 | // The method used by the actual SNES DSP, a weighted average that filters
16 | // out some high frequency content
17 | INTERPOLATION_SNES,
18 | // A polynomial spline approximation that preserves endpoints and estimated
19 | // slopes at those endpoints, based on 4 samples' worth of data like the
20 | // SNES method.
21 | INTERPOLATION_CUBIC,
22 | // A straight line is drawn between every pair of points. Similar to the
23 | // original interpolation code used by ebmused.
24 | INTERPOLATION_LINEAR
25 | } interpolation_method = INTERPOLATION_SNES;
26 | int mixrate = 44100;
27 | int bufsize = 2205;
28 | int chmask = 0xFF;
29 | int timer_speed = 500;
30 | HWAVEOUT hwo;
31 | static BOOL song_playing = FALSE;
32 | FILE* wav_file = NULL;
33 |
34 | BOOL is_playing(void) { return song_playing; }
35 | BOOL start_playing(void) {
36 | if (sound_init()) {
37 | song_playing = TRUE;
38 | EnableMenuItem(hmenu, ID_PLAY, MF_GRAYED);
39 | }
40 |
41 | return song_playing;
42 | }
43 | void stop_playing(void) {
44 | stop_capturing_audio();
45 | song_playing = FALSE;
46 | EnableMenuItem(hmenu, ID_STOP, MF_GRAYED);
47 | }
48 |
49 | static WAVEHDR wh[2], *curbuf = &wh[0];
50 | static int bufs_used;
51 |
52 | int sound_init() {
53 | WAVEFORMATEX wfx;
54 |
55 | if (hwo) {
56 | printf("Already playing!\n");
57 | return 0;
58 | }
59 |
60 | wfx.wFormatTag = WAVE_FORMAT_PCM;
61 | wfx.nChannels = 2;
62 | wfx.nSamplesPerSec = mixrate;
63 | wfx.nAvgBytesPerSec = mixrate*4;
64 | wfx.nBlockAlign = 4;
65 | wfx.wBitsPerSample = 16;
66 | wfx.cbSize = sizeof wfx;
67 |
68 | int error = waveOutOpen(&hwo, WAVE_MAPPER, &wfx, (DWORD_PTR)hwndMain, 0, CALLBACK_WINDOW);
69 | if (error) {
70 | char buf[60];
71 | sprintf(buf, "waveOut device could not be opened (%d)", error);
72 | MessageBox2(buf, NULL, MB_ICONERROR);
73 | return 0;
74 | }
75 |
76 | wh[0].lpData = malloc(bufsize*4 * 2);
77 | wh[0].dwBufferLength = bufsize*4;
78 | wh[1].lpData = wh[0].lpData + bufsize*4;
79 | wh[1].dwBufferLength = bufsize*4;
80 | waveOutPrepareHeader(hwo, &wh[0], sizeof *wh);
81 | waveOutPrepareHeader(hwo, &wh[1], sizeof *wh);
82 |
83 | return 1;
84 | }
85 |
86 | BOOL is_capturing_audio(void) {
87 | return wav_file ? TRUE : FALSE;
88 | }
89 |
90 | BOOL start_capturing_audio(void) {
91 | if (song_playing || sound_init()) {
92 | char *file = open_dialog(GetSaveFileName, "WAV files (*.wav)\0*.wav\0", "wav", OFN_OVERWRITEPROMPT);
93 | if (file) {
94 | stop_capturing_audio();
95 | wav_file = fopen(file, "wb");
96 |
97 | if (wav_file) {
98 | update_menu_item(ID_CAPTURE_AUDIO, "Stop C&apturing");
99 | EnableMenuItem(hmenu, ID_STOP, MF_ENABLED);
100 |
101 | DWORD size_placeholder = 0;
102 | DWORD format_header_size = 16;
103 | WORD formatTag = WAVE_FORMAT_PCM;
104 | WORD num_channels = 2;
105 | DWORD sample_rate = mixrate;
106 | DWORD avg_byte_rate = mixrate*4;
107 | WORD block_alignment = 4;
108 | WORD bit_depth = 16;
109 |
110 | fputs("RIFF", wav_file);
111 | fwrite(&size_placeholder, sizeof size_placeholder, 1, wav_file);
112 | fputs("WAVE", wav_file);
113 | fputs("fmt ", wav_file);
114 | fwrite(&format_header_size, sizeof format_header_size, 1, wav_file);
115 | fwrite(&formatTag, sizeof formatTag, 1, wav_file);
116 | fwrite(&num_channels, sizeof num_channels, 1, wav_file);
117 | fwrite(&sample_rate, sizeof sample_rate, 1, wav_file);
118 | fwrite(&avg_byte_rate, sizeof avg_byte_rate, 1, wav_file);
119 | fwrite(&block_alignment, sizeof block_alignment, 1, wav_file);
120 | fwrite(&bit_depth, sizeof bit_depth, 1, wav_file);
121 | fputs("data", wav_file);
122 | fwrite(&size_placeholder, sizeof size_placeholder, 1, wav_file);
123 | }
124 | }
125 | }
126 |
127 | return wav_file ? TRUE : FALSE;
128 | }
129 |
130 | void stop_capturing_audio(void) {
131 | if (wav_file) {
132 | update_menu_item(ID_CAPTURE_AUDIO, "Capture &Audio...");
133 |
134 | int size = ftell(wav_file) - 8;
135 | fseek(wav_file, 4, SEEK_SET);
136 | fwrite(&size, sizeof size, 1, wav_file);
137 |
138 | fseek(wav_file, 40, SEEK_SET);
139 | size -= 36;
140 | fwrite(&size, sizeof size, 1, wav_file);
141 | fclose(wav_file);
142 | wav_file = NULL;
143 | }
144 | }
145 |
146 | static void sound_uninit() {
147 | waveOutUnprepareHeader(hwo, &wh[0], sizeof *wh);
148 | waveOutUnprepareHeader(hwo, &wh[1], sizeof *wh);
149 | waveOutClose(hwo);
150 | free(wh[0].lpData);
151 | hwo = NULL;
152 | }
153 |
154 | // Move the envelope forward enough SNES ticks to match one tick of ebmused.
155 | // Returns 1 if the note was keyed off as a result of calculating the envelope, or 0 otherwise.
156 | // Note that mixing_rate values near INT_MAX may result in undefined behavior. But no one should
157 | // be trying to play back 2 GHz audio anyway.
158 | static BOOL do_envelope(struct channel_state *c, int mixing_rate) {
159 | BOOL hit_zero = FALSE;
160 |
161 | // c->env_fractional_counter keeps track of accumulated error from running or
162 | // not running the below loop. When it gets to be high enough, we run the
163 | // loop again.
164 | // Examples:
165 | // If mixing_rate is 32000, this loop will run 1 time every function call.
166 | // If mixing_rate is 16000, this loop will run 2 times every call.
167 | // If mixing_rate is 64000, this loop will run 0/1 times every two calls.
168 | // If mixing_rate is 48000, this loop will run 0/1/1 times every three calls.
169 | c->env_fractional_counter += 32000;
170 | while (c->env_fractional_counter >= mixing_rate && !hit_zero) {
171 | ++c->env_counter;
172 | c->env_fractional_counter -= mixing_rate;
173 | c->env_height = c->next_env_height;
174 | c->env_state = c->next_env_state;
175 |
176 | switch (c->env_state) {
177 | case ENV_STATE_ATTACK:
178 | if (c->env_counter >= c->attack_rate) {
179 | c->env_counter = 0;
180 | c->next_env_height = c->env_height + (c->attack_rate == 1 ? 1024 : 32);
181 | if (c->next_env_height > 0x7FF) {
182 | c->next_env_height = 0x7FF;
183 | }
184 | if (c->next_env_height >= 0x7E0) {
185 | c->next_env_state = ENV_STATE_DECAY;
186 | }
187 | }
188 | break;
189 | case ENV_STATE_DECAY:
190 | if (c->env_counter >= c->decay_rate) {
191 | c->env_counter = 0;
192 | c->next_env_height = c->env_height - (((c->env_height - 1) >> 8) + 1);
193 | }
194 | if (c->next_env_height <= c->sustain_level) {
195 | c->next_env_state = ENV_STATE_SUSTAIN;
196 | }
197 | break;
198 | case ENV_STATE_SUSTAIN:
199 | if (c->sustain_rate != 0 && c->env_counter >= c->sustain_rate) {
200 | c->env_counter = 0;
201 | c->next_env_height = c->env_height - (((c->env_height - 1) >> 8) + 1);
202 | }
203 | break;
204 | case ENV_STATE_KEY_OFF:
205 | default:
206 | // "if (env_counter >= 1)" should always be true, because we just incremented it
207 | // (1 being rates[31], the fixed rate used in the release phase)
208 | c->env_counter = 0;
209 | c->next_env_height = c->env_height - 8;
210 | // We want to check if the sample has *already* hit zero, not if it will next tick.
211 | if (c->env_height < 0) {
212 | c->samp_pos = -1;
213 | hit_zero = TRUE;
214 | }
215 | break;
216 | case ENV_STATE_GAIN:
217 | if (c->gain_rate != 0 && c->env_counter >= c->gain_rate) {
218 | c->env_counter = 0;
219 | // There is no work to do for direct gain -- the current level
220 | // is sustained, and c->next_env_height should already equal it
221 | if (c->inst_gain & 0x80) {
222 | switch ((c->inst_gain >> 5) & 3) {
223 | case 0: // Linear decrease
224 | case 1: // Exponential decrease
225 | // Unimplemented
226 | c->samp_pos = -1;
227 | hit_zero = TRUE;
228 | break;
229 | case 2: // Linear increase
230 | c->next_env_height = c->env_height + 32;
231 | break;
232 | case 3: // Bent increase
233 | c->next_env_height = (c->env_height < 0x600) ? c->env_height + 32 : c->env_height + 8;
234 | break;
235 | }
236 |
237 | if (c->next_env_height > 0x7FF) {
238 | c->next_env_height = 0x7FF;
239 | } else if (c->next_env_height < 0) {
240 | c->next_env_height = 0;
241 | }
242 | }
243 | }
244 | }
245 | }
246 | return hit_zero;
247 | }
248 |
249 | static short get_interpolated_sample(const short *sample_data, int inter_sample_pos) {
250 | // For the "accurate" "Gaussian" SNES DSP interpolation method
251 | static const short interpolation_table[512] = {
252 | 0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,0x000,
253 | 0x001,0x001,0x001,0x001,0x001,0x001,0x001,0x001,0x001,0x001,0x001,0x002,0x002,0x002,0x002,0x002,
254 | 0x002,0x002,0x003,0x003,0x003,0x003,0x003,0x004,0x004,0x004,0x004,0x004,0x005,0x005,0x005,0x005,
255 | 0x006,0x006,0x006,0x006,0x007,0x007,0x007,0x008,0x008,0x008,0x009,0x009,0x009,0x00A,0x00A,0x00A,
256 | 0x00B,0x00B,0x00B,0x00C,0x00C,0x00D,0x00D,0x00E,0x00E,0x00F,0x00F,0x00F,0x010,0x010,0x011,0x011,
257 | 0x012,0x013,0x013,0x014,0x014,0x015,0x015,0x016,0x017,0x017,0x018,0x018,0x019,0x01A,0x01B,0x01B,
258 | 0x01C,0x01D,0x01D,0x01E,0x01F,0x020,0x020,0x021,0x022,0x023,0x024,0x024,0x025,0x026,0x027,0x028,
259 | 0x029,0x02A,0x02B,0x02C,0x02D,0x02E,0x02F,0x030,0x031,0x032,0x033,0x034,0x035,0x036,0x037,0x038,
260 | 0x03A,0x03B,0x03C,0x03D,0x03E,0x040,0x041,0x042,0x043,0x045,0x046,0x047,0x049,0x04A,0x04C,0x04D,
261 | 0x04E,0x050,0x051,0x053,0x054,0x056,0x057,0x059,0x05A,0x05C,0x05E,0x05F,0x061,0x063,0x064,0x066,
262 | 0x068,0x06A,0x06B,0x06D,0x06F,0x071,0x073,0x075,0x076,0x078,0x07A,0x07C,0x07E,0x080,0x082,0x084,
263 | 0x086,0x089,0x08B,0x08D,0x08F,0x091,0x093,0x096,0x098,0x09A,0x09C,0x09F,0x0A1,0x0A3,0x0A6,0x0A8,
264 | 0x0AB,0x0AD,0x0AF,0x0B2,0x0B4,0x0B7,0x0BA,0x0BC,0x0BF,0x0C1,0x0C4,0x0C7,0x0C9,0x0CC,0x0CF,0x0D2,
265 | 0x0D4,0x0D7,0x0DA,0x0DD,0x0E0,0x0E3,0x0E6,0x0E9,0x0EC,0x0EF,0x0F2,0x0F5,0x0F8,0x0FB,0x0FE,0x101,
266 | 0x104,0x107,0x10B,0x10E,0x111,0x114,0x118,0x11B,0x11E,0x122,0x125,0x129,0x12C,0x130,0x133,0x137,
267 | 0x13A,0x13E,0x141,0x145,0x148,0x14C,0x150,0x153,0x157,0x15B,0x15F,0x162,0x166,0x16A,0x16E,0x172,
268 | 0x176,0x17A,0x17D,0x181,0x185,0x189,0x18D,0x191,0x195,0x19A,0x19E,0x1A2,0x1A6,0x1AA,0x1AE,0x1B2,
269 | 0x1B7,0x1BB,0x1BF,0x1C3,0x1C8,0x1CC,0x1D0,0x1D5,0x1D9,0x1DD,0x1E2,0x1E6,0x1EB,0x1EF,0x1F3,0x1F8,
270 | 0x1FC,0x201,0x205,0x20A,0x20F,0x213,0x218,0x21C,0x221,0x226,0x22A,0x22F,0x233,0x238,0x23D,0x241,
271 | 0x246,0x24B,0x250,0x254,0x259,0x25E,0x263,0x267,0x26C,0x271,0x276,0x27B,0x280,0x284,0x289,0x28E,
272 | 0x293,0x298,0x29D,0x2A2,0x2A6,0x2AB,0x2B0,0x2B5,0x2BA,0x2BF,0x2C4,0x2C9,0x2CE,0x2D3,0x2D8,0x2DC,
273 | 0x2E1,0x2E6,0x2EB,0x2F0,0x2F5,0x2FA,0x2FF,0x304,0x309,0x30E,0x313,0x318,0x31D,0x322,0x326,0x32B,
274 | 0x330,0x335,0x33A,0x33F,0x344,0x349,0x34E,0x353,0x357,0x35C,0x361,0x366,0x36B,0x370,0x374,0x379,
275 | 0x37E,0x383,0x388,0x38C,0x391,0x396,0x39B,0x39F,0x3A4,0x3A9,0x3AD,0x3B2,0x3B7,0x3BB,0x3C0,0x3C5,
276 | 0x3C9,0x3CE,0x3D2,0x3D7,0x3DC,0x3E0,0x3E5,0x3E9,0x3ED,0x3F2,0x3F6,0x3FB,0x3FF,0x403,0x408,0x40C,
277 | 0x410,0x415,0x419,0x41D,0x421,0x425,0x42A,0x42E,0x432,0x436,0x43A,0x43E,0x442,0x446,0x44A,0x44E,
278 | 0x452,0x455,0x459,0x45D,0x461,0x465,0x468,0x46C,0x470,0x473,0x477,0x47A,0x47E,0x481,0x485,0x488,
279 | 0x48C,0x48F,0x492,0x496,0x499,0x49C,0x49F,0x4A2,0x4A6,0x4A9,0x4AC,0x4AF,0x4B2,0x4B5,0x4B7,0x4BA,
280 | 0x4BD,0x4C0,0x4C3,0x4C5,0x4C8,0x4CB,0x4CD,0x4D0,0x4D2,0x4D5,0x4D7,0x4D9,0x4DC,0x4DE,0x4E0,0x4E3,
281 | 0x4E5,0x4E7,0x4E9,0x4EB,0x4ED,0x4EF,0x4F1,0x4F3,0x4F5,0x4F6,0x4F8,0x4FA,0x4FB,0x4FD,0x4FF,0x500,
282 | 0x502,0x503,0x504,0x506,0x507,0x508,0x50A,0x50B,0x50C,0x50D,0x50E,0x50F,0x510,0x511,0x511,0x512,
283 | 0x513,0x514,0x514,0x515,0x516,0x516,0x517,0x517,0x517,0x518,0x518,0x518,0x518,0x518,0x519,0x519
284 | };
285 |
286 | int s = 0;
287 | switch(interpolation_method) {
288 | default:
289 | // If this happens in a debug build, catch it, because it's a mistake
290 | assert(0 && "unimplemented interpolation method");
291 | // Otherwise... might as well just use a good default
292 | // fallthrough
293 | case INTERPOLATION_SNES: {
294 | unsigned char i = inter_sample_pos >> (15 - 8);
295 | short weights[4] = {
296 | interpolation_table[255 - i],
297 | interpolation_table[511 - i],
298 | interpolation_table[256 + i],
299 | interpolation_table[ 0 + i]
300 | };
301 | // Use an unsigned short to ensure wrapping behavior
302 | unsigned short r = 0;
303 |
304 | // fullsnes uses >> 10, because it treats the BRR output as 15-bit, but we decode to
305 | // 16-bit PCM with the bottom bit clear
306 | r += sample_data[0] * weights[0] >> 11;
307 | r += sample_data[1] * weights[1] >> 11;
308 | r += sample_data[2] * weights[2] >> 11;
309 | s = (short)r + (sample_data[3] * weights[3] >> 11);
310 | s = s > 32767 ? 32767 :
311 | (s < -32768 ? -32768 : s);
312 | }
313 | break;
314 | case INTERPOLATION_CUBIC: {
315 | // Create a cubic curve starting at sample_data[1] (t = 0) and ending at sample_data[2] (t = 1),
316 | // also specifying the slopes at both of those points using a two-sided average.
317 | // Then use that cubic curve to estimate the point between sample_data[1] and sample_data[2].
318 | double t_power[4] = {
319 | [0] = 1.,
320 | [1] = inter_sample_pos / (double)(1 << 15),
321 | [2] = inter_sample_pos * inter_sample_pos / (double)(1 << 30),
322 | [3] = (long long)inter_sample_pos * inter_sample_pos * inter_sample_pos / (double)(1LL << 45)
323 | };
324 |
325 | double start = sample_data[1];
326 | double end = sample_data[2];
327 | double start_slope = (sample_data[2] - sample_data[0]) / 2.;
328 | double end_slope = (sample_data[3] - sample_data[1]) / 2.;
329 |
330 | // We add together four cubic equations that only affect the position or slope of either
331 | // the starting point or the ending point to find the right cubic equation for the sample.
332 | // coefficients[0] is the sum of all the t^0 (constant) terms,
333 | // coefficients[1] is the sum of all the t^1 terms, etc.
334 | // The equations are:
335 | // 2t^3 - 3t^2 + 0t + 1, scaled by the desired starting position
336 | // at t = 0, this equals 1 and has a slope of 0
337 | // at t = 1, this equals 0 and has a slope of 0
338 | // 1t^3 - 2t^2 + 1t + 0, scaled by the desired starting slope
339 | // at t = 0, this equals 0 and has a slope of 1
340 | // at t = 1, this equals 0 and has a slope of 0
341 | // -2t^3 + 3t^2 + 0t + 0, scaled by the desired ending position
342 | // at t = 0, this equals 0 and has a slope of 0
343 | // at t = 1, this equals 1 and has a slope of 0
344 | // 1t^3 - 1t^2 + 0t + 0, scaled by the desired ending slope
345 | // at t = 0, this equals 0 and has a slope of 0
346 | // at t = 1, this equals 0 and has a slope of 1
347 | double coefficients[4] = {
348 | [0] = 1. * start,
349 | [1] = 1. * start_slope,
350 | [2] = -3. * start - 2. * start_slope + 3. * end - 1. * end_slope,
351 | [3] = 2. * start + 1. * start_slope - 2. * end + 1. * end_slope
352 | };
353 |
354 | double r = t_power[0] * coefficients[0] + t_power[1] * coefficients[1]
355 | + t_power[2] * coefficients[2] + t_power[3] * coefficients[3];
356 | assert(isfinite(r));
357 | s = r > 32767. ? 32767 :
358 | (r < -32768. ? -32768 : (int)r);
359 | }
360 | break;
361 | case INTERPOLATION_LINEAR:
362 | // Slope, or rise/run, is (sample_data[2] - sample_data[1]) / (1 - 0)
363 | // y-intercept is sample_data[1]
364 | s = sample_data[1] + (((long long)sample_data[2] - sample_data[1]) * inter_sample_pos >> 15);
365 | s = s > 32767 ? 32767 :
366 | (s < -32768 ? -32768 : s);
367 | break;
368 | }
369 |
370 | // The interpolation result is also 15-bit, treated by us as 16-bit.
371 | s >>= 1;
372 | s *= 2;
373 | return (short)s;
374 | }
375 |
376 | //DWORD cnt;
377 |
378 | static void fill_buffer() {
379 | short (*bufp)[2] = (short (*)[2])curbuf->lpData;
380 |
381 | if (hwndTracker != NULL)
382 | tracker_scrolled();
383 |
384 | int bytes_left = curbuf->dwBufferLength;
385 | while (bytes_left > 0) {
386 | if ((state.next_timer_tick -= timer_speed) < 0) {
387 | state.next_timer_tick += mixrate;
388 | if (!do_timer()) {
389 | curbuf->dwBufferLength -= bytes_left;
390 | break;
391 | }
392 | }
393 |
394 | // for (int blah = 0; blah < 50; blah++) {
395 | int left = 0, right = 0;
396 | struct channel_state *c = state.chan;
397 | for (int cm = chmask; cm; c++, cm >>= 1) {
398 | if (!(cm & 1)) continue;
399 |
400 | if (c->samp_pos < 0) continue;
401 |
402 | int ipos = c->samp_pos >> 15;
403 |
404 | struct sample *s = c->samp;
405 | if (!s) continue;
406 | if (ipos > s->length) {
407 | printf("This can't happen. %d > %d\n", ipos, s->length);
408 | c->samp_pos = -1;
409 | continue;
410 | }
411 |
412 | // Tick the envelope forward once and check if the note becomes
413 | // completely silent
414 | if (do_envelope(c, mixrate)) {
415 | continue;
416 | }
417 |
418 | int s1 = get_interpolated_sample(&s->data[ipos], c->samp_pos & 0x7FFF);
419 |
420 | // Linear interpolation between envelope ticks
421 | int env_height = c->env_height +
422 | (long long)(c->next_env_height - c->env_height) * c->env_fractional_counter / mixrate;
423 | left += s1 * env_height / 0x800 * c->left_vol / 128;
424 | right += s1 * env_height / 0x800 * c->right_vol / 128;
425 |
426 | // int sp = c->samp_pos;
427 |
428 | c->samp_pos += c->note_freq;
429 | if ((c->samp_pos >> 15) >= s->length) {
430 | if (s->loop_len)
431 | c->samp_pos -= s->loop_len << 15;
432 | else
433 | c->samp_pos = -1;
434 | }
435 | // if (blah != 1) c->samp_pos = sp;
436 | }
437 | if (left < -32768) left = -32768;
438 | else if (left > 32767) left = 32767;
439 | if (right < -32768) right = -32768;
440 | else if (right > 32767) right = 32767;
441 | (*bufp)[0] = left;
442 | (*bufp)[1] = right;
443 | // }
444 | bufp++;
445 | bytes_left -= 4;
446 | }
447 | /* { MMTIME mmt;
448 | mmt.wType = TIME_BYTES;
449 | waveOutGetPosition(hwo, &mmt, sizeof(mmt));
450 | printf("%lu / %lu", mmt.u.cb + cnt, curbuf->dwBufferLength);
451 | for (int i = mmt.u.cb + cnt; i >= 0; i -= 500)
452 | putchar(219);
453 | putchar('\n');
454 | }*/
455 | if (wav_file) {
456 | fwrite(curbuf->lpData, curbuf->dwBufferLength, 1, wav_file);
457 | }
458 |
459 | waveOutWrite(hwo, curbuf, sizeof *wh);
460 | bufs_used++;
461 | curbuf = &wh[(curbuf - wh) ^ 1];
462 | }
463 |
464 | void winmm_message(UINT uMsg) {
465 | if (uMsg == MM_WOM_CLOSE)
466 | return;
467 |
468 | if (uMsg == MM_WOM_DONE) {
469 | bufs_used--;
470 | // cnt -= bufsize*4;
471 | }/* else
472 | cnt = 0;*/
473 |
474 | if (song_playing) {
475 | while (bufs_used < 2)
476 | fill_buffer();
477 | } else {
478 | if (bufs_used == 0)
479 | sound_uninit();
480 | }
481 | }
482 |
483 | BOOL CALLBACK OptionsDlgProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
484 | switch (uMsg) {
485 | case WM_INITDIALOG:
486 | SetDlgItemInt(hWnd, IDC_RATE, mixrate, FALSE);
487 | SetDlgItemInt(hWnd, IDC_BUFSIZE, bufsize, FALSE);
488 | stop_playing();
489 | EnableMenuItem(hmenu, ID_PLAY, MF_ENABLED);
490 | break;
491 | case WM_COMMAND:
492 | switch (LOWORD(wParam)) {
493 | int new_rate, new_bufsize;
494 | case IDOK:
495 | new_rate = GetDlgItemInt(hWnd, IDC_RATE, NULL, FALSE);
496 | new_bufsize = GetDlgItemInt(hWnd, IDC_BUFSIZE, NULL, FALSE);
497 | if (new_rate < 8000) new_rate = 8000;
498 | if (new_rate >= 128000) new_rate = 128000;
499 | if (new_bufsize < new_rate/100) new_bufsize = new_rate/100;
500 | if (new_bufsize > new_rate) new_bufsize = new_rate;
501 |
502 | mixrate = new_rate;
503 | bufsize = new_bufsize;
504 | // fallthrough
505 | case IDCANCEL:
506 | EndDialog(hWnd, LOWORD(wParam));
507 | break;
508 | }
509 | default: return FALSE;
510 | }
511 | return TRUE;
512 | }
513 |
--------------------------------------------------------------------------------
/src/status.c:
--------------------------------------------------------------------------------
1 | #include // Must be included before ebmusv2.h
2 |
3 | #include "ebmusv2.h"
4 | #include
5 | #include
6 | #include
7 | #include
8 |
9 | void format_status(int part, const char* format, ...) {
10 | if (hwndStatus) {
11 | va_list args;
12 | va_start(args, format);
13 | int size = vsnprintf(0, 0, format, args);
14 | va_end(args);
15 |
16 | char *buf = malloc(size + 1);
17 | va_start(args, format);
18 | vsprintf(buf, format, args);
19 | va_end(args);
20 |
21 | SendMessage(hwndStatus, SB_SETTEXT, part, (LPARAM)buf);
22 |
23 | free(buf);
24 | }
25 | }
26 |
27 | static const char* notes[] = { "C", "C#", "D", "D#", "E", "F", "F#", "G", "G#", "A", "A#", "B" };
28 |
29 | void set_tracker_status(int p, BYTE *code) {
30 | if (code[0] == 0x00) {
31 | format_status(p, "End of pattern/subroutine");
32 | } else if (code[0] < 0x80) {
33 | if (code[1] < 0x80) {
34 | format_status(p, "Note length: %d, release: %d/7, velocity: %d/15", code[0], (code[1] >> 4) & 0x7, code[1] & 0xF);
35 | } else {
36 | format_status(p, "Note length: %d", code[0]);
37 | }
38 | } else if (code[0] < 0xC8) {
39 | BYTE note = (code[0] & 0x7F);
40 | format_status(p, "Note %s%c", notes[note % 12], '1' + note / 12);
41 | } else if (code[0] == 0xC8) {
42 | format_status(p, "Hold");
43 | } else if (code[0] == 0xC9) {
44 | format_status(p, "Rest");
45 | } else if (code[0] < 0xE0) {
46 | format_status(p, "Percussion %d", code[0] - 0xC9);
47 | } else {
48 | switch (code[0]) {
49 | case 0xE0:
50 | format_status(p,
51 | code[1] < 0xCA ? "Instrument %d" : "Instrument %d (Percussion instrument %d)",
52 | code[1],
53 | code[1] - 0xCA);
54 | break;
55 | case 0xE1:
56 | format_status(p, "Pan %d%s%s",
57 | code[1] & 0x3F,
58 | code[1] & 0x80 ? " (left inverted)" : "",
59 | code[1] & 0x40 ? " (right inverted)" : "");
60 | break;
61 | case 0xE2: format_status(p, "Pan (duration: %d, pan: %d)", code[1], code[2]); break;
62 | case 0xE3:
63 | format_status(p, "Enable vibrato (delay: %d, freq: %d, range: \xB1%d%s)",
64 | code[1],
65 | code[2],
66 | code[3] <= 0xF0 ? code[3] : code[3] - 0xF0,
67 | code[3] <= 0xF0 ? "/256 semitones" : " semitones");
68 | break;
69 | case 0xE4: format_status(p, "Disable vibrato"); break;
70 | case 0xE5: format_status(p, "Master volume %d/255", code[1]); break;
71 | case 0xE6: format_status(p, "Master volume (duration: %d, volume: %d/255)", code[1], code[2]); break;
72 | case 0xE7: format_status(p, "Tempo %d", code[1]); break;
73 | case 0xE8: format_status(p, "Tempo (duration: %d, tempo: %d)", code[1], code[2]); break;
74 | case 0xE9: format_status(p, "Global transpose %+d semitones", (signed char)code[1]); break;
75 | case 0xEA: format_status(p, "Channel transpose %+d semitones", (signed char)code[1]); break;
76 | case 0xEB: format_status(p, "Enable tremolo (delay: %d, freq: %d, amp: %d)", code[1], code[2], code[3]); break;
77 | case 0xEC: format_status(p, "Disable tremolo"); break;
78 | case 0xED: format_status(p, "Channel volume %d/255", code[1]); break;
79 | case 0xEE: format_status(p, "Channel volume (duration: %d, volume: %d/255)", code[1], code[2]); break;
80 | case 0xEF: format_status(p, "Call subroutine %d, %d %s", code[1] | code[2] << 8, code[3], code[3] == 1 ? "time" : "times"); break;
81 | case 0xF0: format_status(p, "Set vibrato attack %d", code[1]); break;
82 | case 0xF1: format_status(p, "Enable portamento (delay: %d, duration: %d, end note: %+d)", code[1], code[2], (signed char)code[3]); break;
83 | case 0xF2: format_status(p, "Enable portamento (delay: %d, duration: %d, start note: %+d)", code[1], code[2], -(signed char)code[3]); break;
84 | case 0xF3: format_status(p, "Disable portamento"); break;
85 | case 0xF4: format_status(p, "Finetune %d/256 semitones", code[1]); break;
86 | case 0xF5:
87 | format_status(p, "Enable echo (channels: %c%c%c%c%c%c%c%c, left volume: %d/%s, right volume: %d/%s)",
88 | '0' + (code[1] & 1),
89 | '0' + ((code[1] >> 1) & 1),
90 | '0' + ((code[1] >> 2) & 1),
91 | '0' + ((code[1] >> 3) & 1),
92 | '0' + ((code[1] >> 4) & 1),
93 | '0' + ((code[1] >> 5) & 1),
94 | '0' + ((code[1] >> 6) & 1),
95 | '0' + ((code[1] >> 7) & 1),
96 | (signed char)code[2], code[2] > 0x7F ? "128" : "127",
97 | (signed char)code[3], code[3] > 0x7F ? "128" : "127");
98 | break;
99 | case 0xF6: format_status(p, "Disable echo"); break;
100 | case 0xF7: format_status(p, "Echo settings (delay: %d, feedback: %d, filter: %d)", code[1], (signed char)code[2], code[3]); break;
101 | case 0xF8: format_status(p, "Echo volume (delay: %d, left volume: %d, right volume: %d)", code[1], (signed char)code[2], (signed char)code[3]); break;
102 | case 0xF9: {
103 | BYTE note = (code[3] & 0x7F);
104 | format_status(p, "Pitch bend (delay: %d, duration: %d, note: %s%c)", code[1], code[2], notes[note%12], '1' + note / 12);
105 | break;
106 | }
107 | case 0xFA: format_status(p, "Base percussion instrument %d", code[1]); break;
108 | case 0xFB: format_status(p, "No op [FB %02X %02X]", code[1], code[2]); break;
109 | case 0xFC: format_status(p, "Mute channel (DEBUG)"); break;
110 | case 0xFD: format_status(p, "Enable fast-foward (DEBUG)"); break;
111 | case 0xFE: format_status(p, "Disable fast-foward (DEBUG)"); break;
112 | case 0xFF: format_status(p, "INVALID"); break;
113 | }
114 | }
115 | }
116 |
--------------------------------------------------------------------------------
/src/structs.h:
--------------------------------------------------------------------------------
1 | #ifndef CreateWindow
2 | typedef unsigned char BYTE;
3 | typedef unsigned short WORD;
4 | typedef unsigned long DWORD;
5 | typedef int BOOL;
6 | #define FALSE 0
7 | #define TRUE 1
8 | typedef void *HWND;
9 | #endif
10 | #define INST_MAX_POLYPHONY 16
11 |
12 | // structure used for track or subroutine
13 | // "size" does not include the ending [00] byte
14 | struct track {
15 | int size;
16 | BYTE *track; // NULL for inactive track
17 | };
18 |
19 | struct song {
20 | WORD address;
21 | BYTE changed;
22 | int order_length;
23 | int *order;
24 | int repeat, repeat_pos;
25 | int patterns;
26 | struct track (*pattern)[8];
27 | int subs;
28 | struct track *sub;
29 | };
30 |
31 | struct parser {
32 | BYTE *ptr;
33 | BYTE *sub_ret;
34 | int sub_start;
35 | BYTE sub_count;
36 | BYTE note_len;
37 | };
38 |
39 | struct slider {
40 | WORD cur, delta;
41 | BYTE cycles, target;
42 | };
43 |
44 | struct song_state {
45 | struct channel_state {
46 | BYTE *ptr;
47 |
48 | int next; // time left in note
49 |
50 | struct slider note; BYTE cur_port_start_ctr;
51 | BYTE note_len, note_style;
52 |
53 | BYTE note_release; // time to release note, in cycles
54 |
55 | int sub_start; // current subroutine number
56 | BYTE *sub_ret; // where to return to after sub
57 | BYTE sub_count; // number of loops
58 |
59 | BYTE inst; // instrument
60 | BYTE inst_adsr1;
61 | BYTE inst_adsr2;
62 | BYTE inst_gain;
63 | BYTE finetune;
64 | signed char transpose;
65 | struct slider panning; BYTE pan_flags;
66 | struct slider volume;
67 | BYTE total_vol;
68 | signed char left_vol, right_vol;
69 |
70 | BYTE port_type, port_start, port_length, port_range;
71 | BYTE vibrato_start, vibrato_speed, vibrato_max_range, vibrato_fadein;
72 | BYTE tremolo_start, tremolo_speed, tremolo_range;
73 |
74 | BYTE vibrato_phase, vibrato_start_ctr, cur_vib_range;
75 | BYTE vibrato_fadein_ctr, vibrato_range_delta;
76 | BYTE tremolo_phase, tremolo_start_ctr;
77 |
78 | struct sample *samp;
79 | int samp_pos, note_freq;
80 |
81 | // Envelope state for the current/previous 32 KHz tick...
82 | enum envelope_state {
83 | ENV_STATE_ATTACK,
84 | ENV_STATE_DECAY,
85 | ENV_STATE_SUSTAIN,
86 | ENV_STATE_KEY_OFF,
87 | ENV_STATE_GAIN
88 | } env_state;
89 | // ...and for the next 32 KHz tick
90 | enum envelope_state next_env_state;
91 | // Envelope height for the current/previous 32 KHz tick...
92 | short env_height;
93 | // ...and for the next 32 KHz tick, for interpolation purposes
94 | short next_env_height;
95 | unsigned short env_counter;
96 | unsigned env_fractional_counter;
97 | short attack_rate;
98 | short decay_rate;
99 | short sustain_level;
100 | short sustain_rate;
101 | short gain_rate;
102 | } chan[INST_MAX_POLYPHONY];
103 | signed char transpose;
104 | struct slider volume;
105 | struct slider tempo;
106 | int next_timer_tick, cycle_timer;
107 | BYTE first_CA_inst; // set with FA
108 | BYTE repeat_count;
109 | int ordnum;
110 | int patpos; // Number of cycles since top of pattern
111 | };
112 |
113 | struct sample {
114 | short *data;
115 | int length;
116 | int loop_len;
117 | };
118 |
119 | struct block {
120 | WORD size, spc_address;
121 | BYTE *data; // only used for inmem packs
122 | };
123 |
124 | // rom_packs contain info about the pack as it stands in the ROM file
125 | // .status is one of these constants:
126 | #define RPACK_ORIGINAL 0
127 | #define RPACK_MODIFIED 1
128 | #define RPACK_INVALID 2
129 | #define RPACK_SAVED 3
130 |
131 | // inmem_packs contain info about the pack as it currently is in the editor
132 | // .status is a bitmask of these constants:
133 | #define IPACK_INMEM 1 // blocks[i].data valid if set
134 | #define IPACK_CHANGED 2
135 | struct pack {
136 | int start_address;
137 | int status; // See constants above
138 | int block_count;
139 | struct block *blocks;
140 | };
141 |
--------------------------------------------------------------------------------
/src/text.c:
--------------------------------------------------------------------------------
1 | #include
2 | #include
3 | #include
4 | #include
5 | #include "ebmusv2.h"
6 | #include "misc.h"
7 |
8 | static int unhex(int chr) {
9 | if (chr >= '0' && chr <= '9')
10 | return chr - '0';
11 | chr |= 0x20; // fold to lower case
12 | if (chr >= 'a' && chr <= 'f')
13 | return chr - 'a' + 10;
14 | return -1;
15 | }
16 |
17 | int calc_track_size_from_text(char *p) {
18 | char buf[60];
19 | int size = 0;
20 | while (*p) {
21 | int c = *p++;
22 | if (unhex(c) >= 0) {
23 | if (unhex(*p) >= 0) p++;
24 | size++;
25 | } else if (c == '[' || c == ']' || isspace(c)) {
26 | // nothing
27 | } else if (c == '*') {
28 | strtol(p, &p, 10);
29 | if (*p == ',') strtol(p + 1, &p, 10);
30 | size += 4;
31 | } else {
32 | sprintf(buf, "Bad character: '%c'", c);
33 | MessageBox2(buf, NULL, 48);
34 | return -1;
35 | }
36 | }
37 | return size;
38 | }
39 |
40 | // returns 1 if successful
41 | BOOL text_to_track(char *str, struct track *t, BOOL is_sub) {
42 | BYTE *data;
43 | int size = calc_track_size_from_text(str);
44 | if (size < 0)
45 | return FALSE;
46 |
47 | int pos;
48 | if (size == 0 && !is_sub) {
49 | data = NULL;
50 | } else {
51 | data = malloc(size + 1);
52 | char *p = str;
53 | pos = 0;
54 | while (*p) {
55 | int c = *p++;
56 | int h = unhex(c);
57 | if (h >= 0) {
58 | int h2 = unhex(*p);
59 | if (h2 >= 0) { h = h << 4 | h2; p++; }
60 | data[pos++] = h;
61 | } else if (c == '*') {
62 | int sub = strtol(p, &p, 10);
63 | int count = *p == ',' ? strtol(p + 1, &p, 10) : 1;
64 | data[pos++] = 0xEF;
65 | data[pos++] = sub & 0xFF;
66 | data[pos++] = sub >> 8;
67 | data[pos++] = count;
68 | }
69 | }
70 | data[pos] = '\0';
71 | }
72 |
73 | if (!validate_track(data, size, is_sub)) {
74 | free(data);
75 | return FALSE;
76 | }
77 |
78 | if (size != t->size || memcmp(data, t->track, size)) {
79 | t->size = size;
80 | free(t->track);
81 | t->track = data;
82 | } else {
83 | free(data);
84 | }
85 | return TRUE;
86 | }
87 |
88 | // includes ending '\0'
89 | int text_length(BYTE *start, BYTE *end) {
90 | int textlength = 0;
91 | for (BYTE *p = start; p < end; ) {
92 | int byte = *p;
93 | int len;
94 | if (byte < 0x80) {
95 | len = p[1] < 0x80 ? 2 : 1;
96 | textlength += 3*len + 2;
97 | } else if (byte < 0xE0) {
98 | len = 1;
99 | textlength += 3;
100 | } else {
101 | len = 1 + code_length[byte - 0xE0];
102 | if (byte == 0xEF) {
103 | char buf[12];
104 | textlength += sprintf(buf, "*%d,%d ", p[1] | p[2] << 8, p[3]);
105 | } else {
106 | textlength += 3*len + 2;
107 | }
108 | }
109 | p += len;
110 | }
111 | return textlength;
112 | }
113 |
114 | // convert a track to text. size must not be 0
115 | void track_to_text(char *out, BYTE *track, int size) {
116 | for (int len, pos = 0; pos < size; pos += len) {
117 | int byte = track[pos];
118 |
119 | len = next_code(&track[pos]) - &track[pos];
120 |
121 | if (byte == 0xEF) {
122 | int sub = track[pos+1] | track[pos+2] << 8;
123 | out += sprintf(out, "*%d,%d", sub, track[pos + 3]);
124 | } else {
125 | int i;
126 | if (byte < 0x80 || byte >= 0xE0) *out++ = '[';
127 | for (i = 0; i < len; i++) {
128 | int byte = track[pos + i];
129 | if (i != 0) *out++ = ' ';
130 | *out++ = "0123456789ABCDEF"[byte >> 4];
131 | *out++ = "0123456789ABCDEF"[byte & 15];
132 | }
133 | if (byte < 0x80 || byte >= 0xE0) *out++ = ']';
134 | }
135 |
136 | *out++ = ' ';
137 | }
138 | out[-1] = '\0';
139 | }
140 |
--------------------------------------------------------------------------------