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