├── .clang-format ├── .editorconfig ├── .gitignore ├── .gitmodules ├── LICENSE ├── README.md ├── SConstruct ├── addons └── godot-doom-node │ ├── godot-doom-node.gdextension │ ├── linux │ └── .gitkeep │ ├── macos │ └── .gitkeep │ └── windows │ └── .gitkeep ├── demo ├── .gitignore ├── addons ├── default_bus_layout.tres ├── doom │ ├── sf3 │ │ ├── MuseScore_General.sf3 │ │ └── MuseScore_General_License.md │ └── wad │ │ └── DOOM1.WAD ├── main.gd ├── main.tscn └── project.godot ├── flake.lock ├── flake.nix └── src ├── doom.cpp ├── doom.h ├── doomcommon.h ├── doominput.h ├── doommus2mid.cpp ├── doommus2mid.h ├── doommutex.h ├── doomshm.c ├── doomshm.h ├── doomspawn.c ├── doomspawn.h ├── doomspawn_music.c ├── doomspawn_sound.c ├── doomswap.h ├── register_types.cpp └── register_types.h /.clang-format: -------------------------------------------------------------------------------- 1 | # Commented out parameters are those with the same value as base LLVM style. 2 | # We can uncomment them if we want to change their value, or enforce the 3 | # chosen value in case the base style changes (last sync: Clang 14.0). 4 | --- 5 | ### General config, applies to all languages ### 6 | BasedOnStyle: LLVM 7 | AccessModifierOffset: -4 8 | AlignAfterOpenBracket: DontAlign 9 | # AlignArrayOfStructures: None 10 | # AlignConsecutiveMacros: None 11 | # AlignConsecutiveAssignments: None 12 | # AlignConsecutiveBitFields: None 13 | # AlignConsecutiveDeclarations: None 14 | # AlignEscapedNewlines: Right 15 | AlignOperands: DontAlign 16 | AlignTrailingComments: false 17 | # AllowAllArgumentsOnNextLine: true 18 | AllowAllParametersOfDeclarationOnNextLine: false 19 | # AllowShortEnumsOnASingleLine: true 20 | # AllowShortBlocksOnASingleLine: Never 21 | # AllowShortCaseLabelsOnASingleLine: false 22 | # AllowShortFunctionsOnASingleLine: All 23 | # AllowShortLambdasOnASingleLine: All 24 | # AllowShortIfStatementsOnASingleLine: Never 25 | # AllowShortLoopsOnASingleLine: false 26 | # AlwaysBreakAfterDefinitionReturnType: None 27 | # AlwaysBreakAfterReturnType: None 28 | # AlwaysBreakBeforeMultilineStrings: false 29 | # AlwaysBreakTemplateDeclarations: MultiLine 30 | # AttributeMacros: 31 | # - __capability 32 | # BinPackArguments: true 33 | # BinPackParameters: true 34 | # BraceWrapping: 35 | # AfterCaseLabel: false 36 | # AfterClass: false 37 | # AfterControlStatement: Never 38 | # AfterEnum: false 39 | # AfterFunction: false 40 | # AfterNamespace: false 41 | # AfterObjCDeclaration: false 42 | # AfterStruct: false 43 | # AfterUnion: false 44 | # AfterExternBlock: false 45 | # BeforeCatch: false 46 | # BeforeElse: false 47 | # BeforeLambdaBody: false 48 | # BeforeWhile: false 49 | # IndentBraces: false 50 | # SplitEmptyFunction: true 51 | # SplitEmptyRecord: true 52 | # SplitEmptyNamespace: true 53 | # BreakBeforeBinaryOperators: None 54 | # BreakBeforeConceptDeclarations: true 55 | # BreakBeforeBraces: Attach 56 | # BreakBeforeInheritanceComma: false 57 | # BreakInheritanceList: BeforeColon 58 | # BreakBeforeTernaryOperators: true 59 | # BreakConstructorInitializersBeforeComma: false 60 | BreakConstructorInitializers: AfterColon 61 | # BreakStringLiterals: true 62 | ColumnLimit: 0 63 | # CommentPragmas: '^ IWYU pragma:' 64 | # QualifierAlignment: Leave 65 | # CompactNamespaces: false 66 | ConstructorInitializerIndentWidth: 8 67 | ContinuationIndentWidth: 8 68 | Cpp11BracedListStyle: false 69 | # DeriveLineEnding: true 70 | # DerivePointerAlignment: false 71 | # DisableFormat: false 72 | # EmptyLineAfterAccessModifier: Never 73 | # EmptyLineBeforeAccessModifier: LogicalBlock 74 | # ExperimentalAutoDetectBinPacking: false 75 | # PackConstructorInitializers: BinPack 76 | ConstructorInitializerAllOnOneLineOrOnePerLine: true 77 | # AllowAllConstructorInitializersOnNextLine: true 78 | # FixNamespaceComments: true 79 | # ForEachMacros: 80 | # - foreach 81 | # - Q_FOREACH 82 | # - BOOST_FOREACH 83 | # IfMacros: 84 | # - KJ_IF_MAYBE 85 | # IncludeBlocks: Preserve 86 | IncludeCategories: 87 | - Regex: '".*"' 88 | Priority: 1 89 | - Regex: '^<.*\.h>' 90 | Priority: 2 91 | - Regex: '^<.*' 92 | Priority: 3 93 | # IncludeIsMainRegex: '(Test)?$' 94 | # IncludeIsMainSourceRegex: '' 95 | # IndentAccessModifiers: false 96 | IndentCaseLabels: true 97 | # IndentCaseBlocks: false 98 | # IndentGotoLabels: true 99 | # IndentPPDirectives: None 100 | # IndentExternBlock: AfterExternBlock 101 | # IndentRequires: false 102 | IndentWidth: 4 103 | # IndentWrappedFunctionNames: false 104 | # InsertTrailingCommas: None 105 | # JavaScriptQuotes: Leave 106 | # JavaScriptWrapImports: true 107 | KeepEmptyLinesAtTheStartOfBlocks: false 108 | # LambdaBodyIndentation: Signature 109 | # MacroBlockBegin: '' 110 | # MacroBlockEnd: '' 111 | # MaxEmptyLinesToKeep: 1 112 | # NamespaceIndentation: None 113 | # PenaltyBreakAssignment: 2 114 | # PenaltyBreakBeforeFirstCallParameter: 19 115 | # PenaltyBreakComment: 300 116 | # PenaltyBreakFirstLessLess: 120 117 | # PenaltyBreakOpenParenthesis: 0 118 | # PenaltyBreakString: 1000 119 | # PenaltyBreakTemplateDeclaration: 10 120 | # PenaltyExcessCharacter: 1000000 121 | # PenaltyReturnTypeOnItsOwnLine: 60 122 | # PenaltyIndentedWhitespace: 0 123 | # PointerAlignment: Right 124 | # PPIndentWidth: -1 125 | # ReferenceAlignment: Pointer 126 | # ReflowComments: true 127 | # RemoveBracesLLVM: false 128 | # SeparateDefinitionBlocks: Leave 129 | # ShortNamespaceLines: 1 130 | # SortIncludes: CaseSensitive 131 | # SortJavaStaticImport: Before 132 | # SortUsingDeclarations: true 133 | # SpaceAfterCStyleCast: false 134 | # SpaceAfterLogicalNot: false 135 | # SpaceAfterTemplateKeyword: true 136 | # SpaceBeforeAssignmentOperators: true 137 | # SpaceBeforeCaseColon: false 138 | # SpaceBeforeCpp11BracedList: false 139 | # SpaceBeforeCtorInitializerColon: true 140 | # SpaceBeforeInheritanceColon: true 141 | # SpaceBeforeParens: ControlStatements 142 | # SpaceBeforeParensOptions: 143 | # AfterControlStatements: true 144 | # AfterForeachMacros: true 145 | # AfterFunctionDefinitionName: false 146 | # AfterFunctionDeclarationName: false 147 | # AfterIfMacros: true 148 | # AfterOverloadedOperator: false 149 | # BeforeNonEmptyParentheses: false 150 | # SpaceAroundPointerQualifiers: Default 151 | # SpaceBeforeRangeBasedForLoopColon: true 152 | # SpaceInEmptyBlock: false 153 | # SpaceInEmptyParentheses: false 154 | # SpacesBeforeTrailingComments: 1 155 | # SpacesInAngles: Never 156 | # SpacesInConditionalStatement: false 157 | # SpacesInContainerLiterals: true 158 | # SpacesInCStyleCastParentheses: false 159 | ## Godot TODO: We'll want to use a min of 1, but we need to see how to fix 160 | ## our comment capitalization at the same time. 161 | SpacesInLineCommentPrefix: 162 | Minimum: 0 163 | Maximum: -1 164 | # SpacesInParentheses: false 165 | # SpacesInSquareBrackets: false 166 | # SpaceBeforeSquareBrackets: false 167 | # BitFieldColonSpacing: Both 168 | # StatementAttributeLikeMacros: 169 | # - Q_EMIT 170 | # StatementMacros: 171 | # - Q_UNUSED 172 | # - QT_REQUIRE_VERSION 173 | TabWidth: 4 174 | # UseCRLF: false 175 | UseTab: Always 176 | # WhitespaceSensitiveMacros: 177 | # - STRINGIZE 178 | # - PP_STRINGIZE 179 | # - BOOST_PP_STRINGIZE 180 | # - NS_SWIFT_NAME 181 | # - CF_SWIFT_NAME 182 | --- 183 | ### C++ specific config ### 184 | Language: Cpp 185 | Standard: c++17 186 | --- 187 | ### ObjC specific config ### 188 | Language: ObjC 189 | # ObjCBinPackProtocolList: Auto 190 | ObjCBlockIndentWidth: 4 191 | # ObjCBreakBeforeNestedBlockParam: true 192 | # ObjCSpaceAfterProperty: false 193 | # ObjCSpaceBeforeProtocolList: true 194 | --- 195 | ### Java specific config ### 196 | Language: Java 197 | # BreakAfterJavaFieldAnnotations: false 198 | JavaImportGroups: ['org.godotengine', 'android', 'androidx', 'com.android', 'com.google', 'java', 'javax'] 199 | ... 200 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | charset = utf-8 7 | indent_style = space 8 | indent_size = 2 9 | 10 | [*.{h,cpp,c}] 11 | indent_style = tab 12 | indent_size = 4 13 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Godot 4+ specific ignores 2 | .godot/ 3 | 4 | # Godot-specific ignores 5 | .import/ 6 | export.cfg 7 | export_presets.cfg 8 | 9 | # Imported translations (automatically generated from CSV files) 10 | *.translation 11 | 12 | # Mono-specific ignores 13 | .mono/ 14 | data_*/ 15 | mono_crash.*.json 16 | 17 | # godot-cpp 18 | /godot-cpp 19 | 20 | # VSCode 21 | .vscode/* 22 | !.vscode/extensions.json 23 | 24 | # Scons build items 25 | *.os 26 | *.o 27 | *.hpp 28 | .cache/ 29 | .sconsign.dblite 30 | .scons_cache*/ 31 | 32 | # Scons custom.py 33 | /custom.py 34 | 35 | # Tool-versions 36 | .tool-versions 37 | 38 | # Clang 39 | compile_commands.json 40 | 41 | # Addons 42 | demo/bin/* 43 | !demo/bin/godot-doom.gdextension 44 | 45 | # Debug builds 46 | **/addons/godot-doom-node/**/*.template_debug.* 47 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "godot-cpp"] 2 | path = godot-cpp 3 | url = git@github.com:godotengine/godot-cpp.git 4 | branch = master 5 | ignore = dirty 6 | [submodule "thirdparty/doomgeneric"] 7 | path = thirdparty/doomgeneric 8 | url = https://github.com/ozkl/doomgeneric 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Adam Scott 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 | # Godot DOOM Node 2 | Node to run DOOM inside Godot using GDExtension. 3 | -------------------------------------------------------------------------------- /SConstruct: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | import os 3 | import sys 4 | import shutil 5 | from SCons.Errors import UserError 6 | 7 | def normalize_path(val): 8 | return val if os.path.isabs(val) else os.path.join(env.Dir("#").abspath, val) 9 | 10 | 11 | def validate_compiledb_file(key, val, env): 12 | if not os.path.isdir(os.path.dirname(normalize_path(val))): 13 | raise UserError("Directory ('%s') does not exist: %s" % (key, os.path.dirname(val))) 14 | 15 | 16 | def get_compiledb_file(env): 17 | return normalize_path(env.get("compiledb_file", "compile_commands.json")) 18 | 19 | env = Environment() 20 | customs = [os.path.abspath("custom.py")] 21 | opts = Variables(customs, ARGUMENTS) 22 | opts.Add(BoolVariable("compiledb", "Generate compilation DB (`compile_commands.json`) for external tools", False)) 23 | opts.Add( 24 | PathVariable( 25 | "compiledb_file", 26 | help="Path to a custom compile_commands.json", 27 | default=normalize_path("compile_commands.json"), 28 | validator=validate_compiledb_file, 29 | ) 30 | ) 31 | opts.Update(env) 32 | 33 | clonedEnv = env.Clone() 34 | clonedEnv["compiledb"] = False 35 | env = SConscript("godot-cpp/SConstruct", { 36 | "env": clonedEnv, 37 | "customs": customs 38 | }) 39 | 40 | # For reference: 41 | # - CCFLAGS are compilation flags shared between C and C++ 42 | # - CFLAGS are for C-specific compilation flags 43 | # - CXXFLAGS are for C++-specific compilation flags 44 | # - CPPFLAGS are for pre-processor flags 45 | # - CPPDEFINES are for pre-processor defines 46 | # - LINKFLAGS are for linking flags 47 | 48 | # tweak this if you want to use different folders, or more folders, to store your source code in. 49 | spawn_executable_name = f"godot-doom-node-spawn{env['suffix']}{env['PROGSUFFIX']}" 50 | 51 | env.Append(CPPPATH=[os.path.abspath("src/"), os.path.abspath("thirdparty/doomgeneric")]) 52 | env.Append(CPPDEFINES=["FEATURE_SOUND_GODOT", f"SPAWN_EXECUTABLE_NAME=\"\\\"{spawn_executable_name}\\\"\""]) 53 | 54 | sources = Glob("src/*.cpp") 55 | spawn_sources = Glob("src/*.c") 56 | doomgeneric_files = Glob("thirdparty/doomgeneric/doomgeneric/*.c", exclude=[ 57 | "thirdparty/doomgeneric/doomgeneric/doomgeneric_allegro.c", 58 | "thirdparty/doomgeneric/doomgeneric/doomgeneric_emscripten.c", 59 | "thirdparty/doomgeneric/doomgeneric/doomgeneric_sdl.c", 60 | "thirdparty/doomgeneric/doomgeneric/doomgeneric_soso.c", 61 | "thirdparty/doomgeneric/doomgeneric/doomgeneric_sosox.c", 62 | "thirdparty/doomgeneric/doomgeneric/doomgeneric_win.c", 63 | "thirdparty/doomgeneric/doomgeneric/doomgeneric_xlib.c", 64 | "thirdparty/doomgeneric/doomgeneric/i_allegromusic.c", 65 | "thirdparty/doomgeneric/doomgeneric/i_allegrosound.c", 66 | "thirdparty/doomgeneric/doomgeneric/i_sdlmusic.c", 67 | "thirdparty/doomgeneric/doomgeneric/i_sdlsound.c", 68 | ]) 69 | spawn_sources += doomgeneric_files 70 | 71 | # Fluidsynth 72 | env.Append(LIBS=['fluidsynth']) 73 | 74 | root_addons = os.path.join(".", "addons", "godot-doom-node", env["platform"]) 75 | root_demo_addons = os.path.join("demo", root_addons) 76 | 77 | library_name = "" 78 | if env['platform'] == "macos": 79 | library_name = os.path.join(f"libgodot-doom-node.{env['platform']}.{env['target']}.framework", f"godot-doom-node.{env['platform']}.{env['target']}") 80 | else: 81 | library_name = f"libgodot-doom-node{env['suffix']}{env['SHLIBSUFFIX']}" 82 | library_path = os.path.join(root_addons, library_name) 83 | 84 | library = env.SharedLibrary( 85 | library_path, 86 | source=sources, 87 | ) 88 | 89 | program_path = os.path.join(root_addons, spawn_executable_name) 90 | program = env.Program( 91 | program_path, 92 | source=spawn_sources 93 | ) 94 | 95 | Default(library, program) 96 | 97 | if env.get("compiledb", False): 98 | env.Tool("compilation_db") 99 | env.Alias("compiledb", env.CompilationDatabase(env.get("compiledb_file", None))) 100 | -------------------------------------------------------------------------------- /addons/godot-doom-node/godot-doom-node.gdextension: -------------------------------------------------------------------------------- 1 | [configuration] 2 | 3 | entry_symbol = "godot_doom_library_init" 4 | compatibility_minimum = "4.3" 5 | 6 | [libraries] 7 | 8 | linux.debug.x86_64="res://addons/godot-doom-node/linux/libgodot-doom-node.linux.template_debug.x86_64.so" 9 | -------------------------------------------------------------------------------- /addons/godot-doom-node/linux/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamscott/godot-doom-node/713a1451c7600d7c3ef44ed48a28f380086fdaa1/addons/godot-doom-node/linux/.gitkeep -------------------------------------------------------------------------------- /addons/godot-doom-node/macos/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamscott/godot-doom-node/713a1451c7600d7c3ef44ed48a28f380086fdaa1/addons/godot-doom-node/macos/.gitkeep -------------------------------------------------------------------------------- /addons/godot-doom-node/windows/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamscott/godot-doom-node/713a1451c7600d7c3ef44ed48a28f380086fdaa1/addons/godot-doom-node/windows/.gitkeep -------------------------------------------------------------------------------- /demo/.gitignore: -------------------------------------------------------------------------------- 1 | # Godot 4+ specific ignores 2 | .godot/ 3 | 4 | # Godot-specific ignores 5 | .import/ 6 | export.cfg 7 | export_presets.cfg 8 | 9 | # Imported translations (automatically generated from CSV files) 10 | *.translation 11 | 12 | # Mono-specific ignores 13 | .mono/ 14 | data_*/ 15 | mono_crash.*.json 16 | 17 | # godot-cpp 18 | godot-cpp/ 19 | godot-cpp/custom.py 20 | godot-cpp/extension_api.json 21 | 22 | # midi files 23 | midi/* 24 | !midi/.gitkeep 25 | 26 | !bin/godot-doom.gdextension 27 | -------------------------------------------------------------------------------- /demo/addons: -------------------------------------------------------------------------------- 1 | ../addons -------------------------------------------------------------------------------- /demo/default_bus_layout.tres: -------------------------------------------------------------------------------- 1 | [gd_resource type="AudioBusLayout" format=3 uid="uid://dwobahhhqbocg"] 2 | 3 | [resource] 4 | bus/1/name = &"Music" 5 | bus/1/solo = false 6 | bus/1/mute = false 7 | bus/1/bypass_fx = false 8 | bus/1/volume_db = 1.06049 9 | bus/1/send = &"Master" 10 | bus/2/name = &"SFX" 11 | bus/2/solo = false 12 | bus/2/mute = false 13 | bus/2/bypass_fx = false 14 | bus/2/volume_db = 0.862736 15 | bus/2/send = &"Master" 16 | -------------------------------------------------------------------------------- /demo/doom/sf3/MuseScore_General.sf3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamscott/godot-doom-node/713a1451c7600d7c3ef44ed48a28f380086fdaa1/demo/doom/sf3/MuseScore_General.sf3 -------------------------------------------------------------------------------- /demo/doom/sf3/MuseScore_General_License.md: -------------------------------------------------------------------------------- 1 | # MuseScore_General.sf2 2 | --- 3 | 4 | Current version: 0.2 13th May 2020 5 | 6 | This is a scaled-down version of **MuseScore_General-HQ.sf2** that replaces some of the larger instruments to save memory and CPU on older PCs. This SoundFont is currently a work-in-progress. Detailed information on presets and sample sources used can be found in "MuseScore_General_Sample_Sources.csv". All instruments without attribution are still using samples from FluidR3Mono. 7 | 8 | FluidR3 (original version) by Frank Wen Copyright (c) 2000-02 9 | 10 | Mono conversion (FluidR3Mono) by Michael Cowgill Copyright (c) 2014-17 11 | 12 | Adaptation for MuseScore_General.sf2 by S. Christian Collins Copyright (c) 2018-19 13 | 14 | Temple Blocks instrument provided by Ethan Winer Copyright (c) 2002 15 | 16 | Drumline Cymbals provided by Michael Schorsch Copyright (c) 2016 17 | 18 | MuseScore_General.sf2 is shared under the MIT license as described in COPYING, as was FluidR3Mono and FluidR3 before it. 19 | 20 | The COPYING and README files from the original FluidR3GM file are now displayed here for reference. 21 | 22 | The acknowledgements and copyright notices above must be included in any derivative work. 23 | 24 | 25 | README 26 | --- 27 | 28 | Fluid (R3) SoundFont 29 | 30 | Copyright (c) 2000-2002, 2008 Frank Wen 31 | 32 | I hereby release Fluid under the MIT license, as described in COPYING. 33 | 34 | 35 | Thanks to Toby Smithe for helping to get Fluid included in Ubuntu. 36 | 37 | This package, of course, is the original Release 3 of Fluid. 38 | 39 | 40 | Fluid was constructed in part from samples found in the public domain that I 41 | edited/cleaned/remixed/programmed and largely from recordings of my own and 42 | in conjunction with the people below who helped along the way: 43 | 44 | Suren M. Seron 45 | Scott Hanan 46 | Steve Aupperle 47 | Chris Gillman 48 | Alex Taubr 49 | Chris Prola 50 | Andrew Klenk 51 | Winfried Hubbe 52 | Dylan 53 | Tim 54 | Gort 55 | Uros Katic 56 | Ethan Winer (http://www.ethanwiner.com) 57 | 58 | 59 | It's obviously been a few years since the project, but its nice to see that 60 | people are still enjoying my work and getting good use out of it. As always, 61 | I'd like to hear some work done with Fluid so email me, or just email me to 62 | say hello and tell me what is going on in the computer musician world. 63 | Who knows, maybe I'll kick start this project again? ;) 64 | 65 | 66 | COPYING 67 | --- 68 | 69 | Mono version: Copyright (c) 2014-16 Michael Cowgill 70 | Copyright (c) 2000-2002, 2008 Frank Wen 71 | 72 | Permission is hereby granted, free of charge, to any person 73 | obtaining a copy of this software and associated documentation 74 | files (the "Software"), to deal in the Software without 75 | restriction, including without limitation the rights to use, 76 | copy, modify, merge, publish, distribute, sublicense, and/or sell 77 | copies of the Software, and to permit persons to whom the 78 | Software is furnished to do so, subject to the following 79 | conditions: 80 | 81 | The above copyright notice and this permission notice shall be 82 | included in all copies or substantial portions of the Software. 83 | 84 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 85 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 86 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 87 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT 88 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 89 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING 90 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 91 | OTHER DEALINGS IN THE SOFTWARE. 92 | 93 | -------------------------------------------------------------------------------- /demo/doom/wad/DOOM1.WAD: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/adamscott/godot-doom-node/713a1451c7600d7c3ef44ed48a28f380086fdaa1/demo/doom/wad/DOOM1.WAD -------------------------------------------------------------------------------- /demo/main.gd: -------------------------------------------------------------------------------- 1 | extends Node 2 | -------------------------------------------------------------------------------- /demo/main.tscn: -------------------------------------------------------------------------------- 1 | [gd_scene load_steps=2 format=3 uid="uid://b5fakufphm7c5"] 2 | 3 | [ext_resource type="Script" path="res://main.gd" id="1_bak3v"] 4 | 5 | [node name="Main" type="Node"] 6 | script = ExtResource("1_bak3v") 7 | 8 | [node name="DOOM" type="DOOM" parent="."] 9 | assets_wad_path = "res://doom/wad/DOOM1.WAD" 10 | assets_soundfont_path = "res://doom/sf3/MuseScore_General.sf3" 11 | offset_right = 873.0 12 | offset_bottom = 547.0 13 | -------------------------------------------------------------------------------- /demo/project.godot: -------------------------------------------------------------------------------- 1 | ; Engine configuration file. 2 | ; It's best edited using the editor UI and not directly, 3 | ; since the parameters that go here are not all obvious. 4 | ; 5 | ; Format: 6 | ; [section] ; section goes between [] 7 | ; param=value ; assign values to parameters 8 | 9 | config_version=5 10 | 11 | [application] 12 | 13 | config/name="Godot DOOM Node Demo" 14 | config/features=PackedStringArray("4.3") 15 | -------------------------------------------------------------------------------- /flake.lock: -------------------------------------------------------------------------------- 1 | { 2 | "nodes": { 3 | "flake-utils": { 4 | "inputs": { 5 | "systems": "systems" 6 | }, 7 | "locked": { 8 | "lastModified": 1731533236, 9 | "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", 10 | "owner": "numtide", 11 | "repo": "flake-utils", 12 | "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", 13 | "type": "github" 14 | }, 15 | "original": { 16 | "owner": "numtide", 17 | "repo": "flake-utils", 18 | "type": "github" 19 | } 20 | }, 21 | "nixpkgs": { 22 | "locked": { 23 | "lastModified": 1743231893, 24 | "narHash": "sha256-tpJsHMUPEhEnzySoQxx7+kA+KUtgWqvlcUBqROYNNt0=", 25 | "owner": "NixOS", 26 | "repo": "nixpkgs", 27 | "rev": "c570c1f5304493cafe133b8d843c7c1c4a10d3a6", 28 | "type": "github" 29 | }, 30 | "original": { 31 | "owner": "NixOS", 32 | "ref": "nixos-24.11", 33 | "repo": "nixpkgs", 34 | "type": "github" 35 | } 36 | }, 37 | "root": { 38 | "inputs": { 39 | "flake-utils": "flake-utils", 40 | "nixpkgs": "nixpkgs" 41 | } 42 | }, 43 | "systems": { 44 | "locked": { 45 | "lastModified": 1681028828, 46 | "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", 47 | "owner": "nix-systems", 48 | "repo": "default", 49 | "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", 50 | "type": "github" 51 | }, 52 | "original": { 53 | "owner": "nix-systems", 54 | "repo": "default", 55 | "type": "github" 56 | } 57 | } 58 | }, 59 | "root": "root", 60 | "version": 7 61 | } 62 | -------------------------------------------------------------------------------- /flake.nix: -------------------------------------------------------------------------------- 1 | { 2 | description = "Godot DOOM node"; 3 | inputs = { 4 | nixpkgs.url = "github:NixOS/nixpkgs/nixos-24.11"; 5 | flake-utils.url = "github:numtide/flake-utils"; 6 | }; 7 | outputs = { self, nixpkgs, flake-utils }: 8 | flake-utils.lib.eachDefaultSystem (system: 9 | let 10 | pkgs = nixpkgs.legacyPackages.${system}; 11 | in 12 | { 13 | devShell = pkgs.mkShell { 14 | packages = with pkgs; [ 15 | scons 16 | fluidsynth 17 | ]; 18 | nativeBuildInputs = with pkgs; [ 19 | pkg-config 20 | ]; 21 | }; 22 | } 23 | ); 24 | } 25 | -------------------------------------------------------------------------------- /src/doom.cpp: -------------------------------------------------------------------------------- 1 | #include "doom.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | #include "godot_cpp/classes/audio_server.hpp" 12 | #include "godot_cpp/classes/audio_stream_generator.hpp" 13 | #include "godot_cpp/classes/audio_stream_generator_playback.hpp" 14 | #include "godot_cpp/classes/audio_stream_player.hpp" 15 | #include "godot_cpp/classes/audio_stream_player2d.hpp" 16 | #include "godot_cpp/classes/audio_stream_wav.hpp" 17 | #include "godot_cpp/classes/camera2d.hpp" 18 | #include "godot_cpp/classes/control.hpp" 19 | #include "godot_cpp/classes/dir_access.hpp" 20 | #include "godot_cpp/classes/file_access.hpp" 21 | #include "godot_cpp/classes/global_constants.hpp" 22 | #include "godot_cpp/classes/hashing_context.hpp" 23 | #include "godot_cpp/classes/image_texture.hpp" 24 | #include "godot_cpp/classes/input.hpp" 25 | #include "godot_cpp/classes/input_event.hpp" 26 | #include "godot_cpp/classes/input_event_key.hpp" 27 | #include "godot_cpp/classes/input_event_mouse_button.hpp" 28 | #include "godot_cpp/classes/input_event_mouse_motion.hpp" 29 | #include "godot_cpp/classes/node.hpp" 30 | #include "godot_cpp/classes/os.hpp" 31 | #include "godot_cpp/classes/project_settings.hpp" 32 | #include "godot_cpp/classes/scene_tree.hpp" 33 | #include "godot_cpp/classes/sub_viewport.hpp" 34 | #include "godot_cpp/classes/sub_viewport_container.hpp" 35 | #include "godot_cpp/classes/texture_rect.hpp" 36 | #include "godot_cpp/classes/thread.hpp" 37 | #include "godot_cpp/classes/time.hpp" 38 | #include "godot_cpp/core/class_db.hpp" 39 | #include "godot_cpp/core/memory.hpp" 40 | #include "godot_cpp/core/object.hpp" 41 | #include "godot_cpp/core/property_info.hpp" 42 | #include "godot_cpp/variant/callable.hpp" 43 | #include "godot_cpp/variant/char_string.hpp" 44 | #include "godot_cpp/variant/packed_byte_array.hpp" 45 | #include "godot_cpp/variant/packed_int32_array.hpp" 46 | #include "godot_cpp/variant/packed_vector2_array.hpp" 47 | #include "godot_cpp/variant/string.hpp" 48 | #include "godot_cpp/variant/typed_array.hpp" 49 | #include "godot_cpp/variant/utility_functions.hpp" 50 | #include "godot_cpp/variant/variant.hpp" 51 | 52 | #include "doomgeneric/doomgeneric.h" 53 | #include "doomgeneric/doomtype.h" 54 | 55 | #include "doommus2mid.h" 56 | #include "doomswap.h" 57 | 58 | extern "C" { 59 | #include 60 | #include 61 | #include 62 | #include 63 | #include 64 | // Shared memory 65 | #include 66 | #include 67 | #include 68 | 69 | // GodotDoomNode 70 | #include "doomcommon.h" 71 | #include "doommutex.h" 72 | #include "doomshm.h" 73 | 74 | // Fluidsynth 75 | #include "fluidsynth.h" 76 | #include "fluidsynth/audio.h" 77 | #include "fluidsynth/midi.h" 78 | #include "fluidsynth/misc.h" 79 | #include "fluidsynth/settings.h" 80 | #include "fluidsynth/synth.h" 81 | #include "fluidsynth/types.h" 82 | } 83 | 84 | #ifndef SPAWN_EXECUTABLE_NAME 85 | #define SPAWN_EXECUTABLE_NAME "godot-doom-spawn.linux.template_debug.x86_64" 86 | #endif 87 | 88 | #define SOUND_SUBVIEWPORT_SIZE 512 89 | 90 | using namespace godot; 91 | 92 | void DOOM::_bind_methods() { 93 | // Signals. 94 | ADD_SIGNAL(MethodInfo("assets_imported")); 95 | 96 | ClassDB::bind_method(D_METHOD("_doom_thread_func"), &DOOM::_doom_thread_func); 97 | ClassDB::bind_method(D_METHOD("_wad_thread_func"), &DOOM::_wad_thread_func); 98 | ClassDB::bind_method(D_METHOD("_sound_fetching_thread_func"), &DOOM::_sound_fetching_thread_func); 99 | ClassDB::bind_method(D_METHOD("_midi_fetching_thread_func"), &DOOM::_midi_fetching_thread_func); 100 | ClassDB::bind_method(D_METHOD("_midi_thread_func"), &DOOM::_midi_thread_func); 101 | ClassDB::bind_method(D_METHOD("_wad_thread_end"), &DOOM::_wad_thread_end); 102 | ClassDB::bind_method(D_METHOD("_sound_fetching_thread_end"), &DOOM::_sound_fetching_thread_end); 103 | ClassDB::bind_method(D_METHOD("_midi_fetching_thread_end"), &DOOM::_midi_fetching_thread_end); 104 | 105 | ClassDB::bind_method(D_METHOD("import_assets"), &DOOM::import_assets); 106 | ClassDB::bind_method(D_METHOD("pause"), &DOOM::pause); 107 | ClassDB::bind_method(D_METHOD("resume"), &DOOM::resume); 108 | 109 | ADD_GROUP("DOOM", "doom_"); 110 | ADD_GROUP("Assets", "assets_"); 111 | 112 | ClassDB::bind_method(D_METHOD("get_assets_ready"), &DOOM::get_assets_ready); 113 | ClassDB::bind_method(D_METHOD("set_assets_ready"), &DOOM::set_assets_ready); 114 | ADD_PROPERTY(PropertyInfo(Variant::BOOL, "doom_assets_ready"), "set_assets_ready", "get_assets_ready"); 115 | 116 | ClassDB::bind_method(D_METHOD("get_enabled"), &DOOM::get_enabled); 117 | ClassDB::bind_method(D_METHOD("set_enabled", "enabled"), &DOOM::set_enabled); 118 | ADD_PROPERTY(PropertyInfo(Variant::BOOL, "doom_enabled"), "set_enabled", "get_enabled"); 119 | 120 | ClassDB::bind_method(D_METHOD("get_import_assets"), &DOOM::get_import_assets); 121 | ClassDB::bind_method(D_METHOD("set_import_assets", "import"), &DOOM::set_import_assets); 122 | ADD_PROPERTY(PropertyInfo(Variant::BOOL, "assets_import_assets"), "set_import_assets", "get_import_assets"); 123 | 124 | ClassDB::bind_method(D_METHOD("get_wad_path"), &DOOM::get_wad_path); 125 | ClassDB::bind_method(D_METHOD("set_wad_path", "wad_path"), &DOOM::set_wad_path); 126 | ADD_PROPERTY(PropertyInfo(Variant::STRING, "assets_wad_path", PROPERTY_HINT_FILE, "*.wad"), "set_wad_path", "get_wad_path"); 127 | 128 | ClassDB::bind_method(D_METHOD("get_soundfont_path"), &DOOM::get_soundfont_path); 129 | ClassDB::bind_method(D_METHOD("set_soundfont_path", "soundfont_path"), &DOOM::set_soundfont_path); 130 | ADD_PROPERTY(PropertyInfo(Variant::STRING, "assets_soundfont_path", PROPERTY_HINT_FILE, "*.sf2,*.sf3"), "set_soundfont_path", "get_soundfont_path"); 131 | 132 | ClassDB::bind_method(D_METHOD("get_wasd_mode"), &DOOM::get_wasd_mode); 133 | ClassDB::bind_method(D_METHOD("set_wasd_mode", "wasd_mode_enabled"), &DOOM::set_wasd_mode); 134 | ADD_PROPERTY(PropertyInfo(Variant::BOOL, "wasd_mode"), "set_wasd_mode", "get_wasd_mode"); 135 | 136 | ClassDB::bind_method(D_METHOD("get_mouse_acceleration"), &DOOM::get_mouse_acceleration); 137 | ClassDB::bind_method(D_METHOD("set_mouse_acceleration", "mouse_acceleration"), &DOOM::set_mouse_acceleration); 138 | ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "mouse_acceleration"), "set_mouse_acceleration", "get_mouse_acceleration"); 139 | 140 | ClassDB::bind_method(D_METHOD("get_autosave"), &DOOM::get_autosave); 141 | ClassDB::bind_method(D_METHOD("set_autosave", "autosave"), &DOOM::set_autosave); 142 | ADD_PROPERTY(PropertyInfo(Variant::FLOAT, "autosave"), "set_autosave", "get_autosave"); 143 | } 144 | 145 | void DOOM::_input(const Ref &event) { 146 | if (_spawn_pid == 0 || _shm == nullptr || _exiting) { 147 | return; 148 | } 149 | 150 | if (!has_focus()) { 151 | return; 152 | } 153 | 154 | Ref key = event; 155 | if (key.is_valid()) { 156 | if (key->is_echo()) { 157 | get_viewport()->set_input_as_handled(); 158 | return; 159 | } 160 | 161 | Key active_key = key->get_physical_keycode(); 162 | if (_wasd_mode) { 163 | switch (active_key) { 164 | case KEY_W: { 165 | active_key = KEY_UP; 166 | } break; 167 | 168 | case KEY_A: { 169 | active_key = KEY_COMMA; 170 | } break; 171 | 172 | case KEY_S: { 173 | active_key = KEY_DOWN; 174 | } break; 175 | 176 | case KEY_D: { 177 | active_key = KEY_PERIOD; 178 | } break; 179 | 180 | default: { 181 | // do nothing 182 | } 183 | } 184 | } 185 | 186 | if (key->is_pressed() && !_keys_pressed_queue.has(active_key)) { 187 | _keys_pressed_queue.append(active_key); 188 | } else if (key->is_released() && !_keys_released_queue.has(active_key)) { 189 | _keys_released_queue.append(active_key); 190 | } 191 | 192 | if (active_key != KEY_ESCAPE) { 193 | get_viewport()->set_input_as_handled(); 194 | Input::get_singleton()->set_mouse_mode(Input::MouseMode::MOUSE_MODE_CAPTURED); 195 | } 196 | return; 197 | } 198 | 199 | Ref mouse_button = event; 200 | if (mouse_button.is_valid()) { 201 | MouseButton mouse_button_index = mouse_button->get_button_index(); 202 | 203 | if (mouse_button->is_pressed() && !_mouse_buttons_pressed_queue.has(mouse_button_index)) { 204 | _mouse_buttons_pressed_queue.append(mouse_button_index); 205 | } else if (mouse_button->is_released() && !_mouse_buttons_released_queue.has(mouse_button_index)) { 206 | _mouse_buttons_released_queue.append(mouse_button_index); 207 | } 208 | return; 209 | } 210 | 211 | Ref mouse_motion = event; 212 | if (mouse_motion.is_valid()) { 213 | mutex_lock(_shm); 214 | _shm->mouse_x += mouse_motion->get_relative().x * _mouse_acceleration; 215 | mutex_unlock(_shm); 216 | return; 217 | } 218 | } 219 | 220 | bool DOOM::get_import_assets() { 221 | return false; 222 | } 223 | 224 | void DOOM::set_import_assets(bool p_import_assets) { 225 | call_deferred("import_assets"); 226 | } 227 | 228 | bool DOOM::get_assets_ready() { 229 | return _assets_ready; 230 | } 231 | 232 | void DOOM::set_assets_ready(bool p_ready) { 233 | } 234 | 235 | bool DOOM::get_enabled() { 236 | return _enabled; 237 | } 238 | 239 | void DOOM::set_enabled(bool p_enabled) { 240 | if (p_enabled == _enabled) { 241 | return; 242 | } 243 | 244 | UtilityFunctions::print(vformat("set_enabled %s", p_enabled)); 245 | _enabled = p_enabled; 246 | 247 | _update_doom(); 248 | } 249 | 250 | String DOOM::get_wad_path() { 251 | return _wad_path; 252 | } 253 | 254 | void DOOM::set_wad_path(String p_wad_path) { 255 | if (_wad_path == p_wad_path) { 256 | return; 257 | } 258 | bool wad_path_is_empty = _wad_path.is_empty(); 259 | 260 | _wad_path = p_wad_path; 261 | if (!wad_path_is_empty) { 262 | _kill_doom(); 263 | } 264 | 265 | _assets_ready = false; 266 | _enabled = false; 267 | } 268 | 269 | String DOOM::get_soundfont_path() { 270 | return _soundfont_path; 271 | } 272 | 273 | void DOOM::set_soundfont_path(String p_soundfont_path) { 274 | _soundfont_path = p_soundfont_path; 275 | String global_path = ProjectSettings::get_singleton()->globalize_path(p_soundfont_path); 276 | 277 | _mutex->lock(); 278 | if (fluid_is_soundfont(global_path.utf8().ptr())) { 279 | _fluid_synth_id = fluid_synth_sfload(_fluid_synth, global_path.utf8().ptr(), true); 280 | } else if (_fluid_synth_id != -1) { 281 | fluid_synth_sfunload(_fluid_synth, _fluid_synth_id, true); 282 | _fluid_synth_id = -1; 283 | } 284 | _mutex->unlock(); 285 | } 286 | 287 | bool DOOM::get_autosave() { 288 | return _autosave; 289 | } 290 | 291 | void DOOM::set_autosave(bool p_autosave) { 292 | _autosave = p_autosave; 293 | } 294 | 295 | bool DOOM::get_wasd_mode() { 296 | return _wasd_mode; 297 | } 298 | 299 | void DOOM::set_wasd_mode(bool p_wasd_mode) { 300 | _wasd_mode = p_wasd_mode; 301 | } 302 | 303 | float DOOM::get_mouse_acceleration() { 304 | return _mouse_acceleration; 305 | } 306 | 307 | void DOOM::set_mouse_acceleration(float p_mouse_acceleration) { 308 | _mouse_acceleration = p_mouse_acceleration; 309 | } 310 | 311 | void DOOM::import_assets() { 312 | if (_wad_path.is_empty()) { 313 | UtilityFunctions::printerr(vformat("[Godot DOOM node] Wad path must not be empty.")); 314 | return; 315 | } 316 | 317 | if (!FileAccess::file_exists(_wad_path)) { 318 | UtilityFunctions::printerr(vformat("[Godot DOOM node] Wad path (\"%s\") isn't a file.", _wad_path)); 319 | return; 320 | } 321 | 322 | if (_wad_thread.is_valid() && _wad_thread->is_alive()) { 323 | UtilityFunctions::printerr(vformat("[Godot DOOM node] A process is already importing assets.")); 324 | return; 325 | } 326 | 327 | Ref user_dir = DirAccess::open("user://"); 328 | if (!user_dir->dir_exists("godot-doom-node")) { 329 | user_dir->make_dir("godot-doom-node"); 330 | } 331 | 332 | if (_wad_thread.is_null()) { 333 | _wad_thread.instantiate(); 334 | } 335 | Callable wad_func = Callable(this, "_wad_thread_func"); 336 | _wad_thread->start(wad_func); 337 | } 338 | 339 | void DOOM::pause() { 340 | _stop_doom(); 341 | } 342 | 343 | void DOOM::resume() { 344 | if (_spawn_pid == 0) { 345 | return; 346 | } 347 | 348 | #ifdef LINUX_ENABLED 349 | UtilityFunctions::print("SIGCONT"); 350 | kill(_spawn_pid, SIGCONT); 351 | #endif 352 | 353 | if (!_current_midi_file.is_empty()) { 354 | MusicInstruction inst; 355 | inst.type = MUSIC_INSTRUCTION_TYPE_RESUME_SONG; 356 | _music_instructions.append(inst); 357 | } 358 | 359 | _start_threads(); 360 | } 361 | 362 | void DOOM::_stop_music() { 363 | if (_fluid_player != nullptr) { 364 | fluid_player_stop(_fluid_player); 365 | fluid_player_join(_fluid_player); 366 | delete_fluid_player(_fluid_player); 367 | _fluid_player = nullptr; 368 | } 369 | 370 | if (_fluid_synth != nullptr) { 371 | fluid_synth_system_reset(_fluid_synth); 372 | } 373 | } 374 | 375 | void DOOM::_update_doom() { 376 | if (_enabled && _assets_ready) { 377 | _init_doom(); 378 | } else { 379 | _kill_doom(); 380 | } 381 | } 382 | 383 | void DOOM::_init_doom() { 384 | _enabled = true; 385 | 386 | if (_spawn_pid == 0) { 387 | _init_shm(); 388 | } 389 | 390 | mutex_lock(_shm); 391 | _shm->ticks_msec = Time::get_singleton()->get_ticks_msec(); 392 | mutex_unlock(_shm); 393 | 394 | if (_spawn_pid == 0) { 395 | _spawn_pid = _launch_doom_executable(); 396 | } 397 | 398 | _screen_buffer_array.resize(sizeof(_screen_buffer)); 399 | 400 | _start_threads(); 401 | } 402 | 403 | void DOOM::_start_threads() { 404 | _exiting = false; 405 | 406 | if (_doom_thread.is_null()) { 407 | _doom_thread.instantiate(); 408 | } else if (_doom_thread->is_started()) { 409 | _doom_thread->wait_to_finish(); 410 | } 411 | if (_midi_thread.is_null()) { 412 | _midi_thread.instantiate(); 413 | } else if (_midi_thread->is_started()) { 414 | _midi_thread->wait_to_finish(); 415 | } 416 | 417 | Callable doom_func = Callable(this, "_doom_thread_func"); 418 | _doom_thread->start(doom_func); 419 | Callable midi_func = Callable(this, "_midi_thread_func"); 420 | _midi_thread->start(midi_func); 421 | } 422 | 423 | __pid_t DOOM::_launch_doom_executable() { 424 | String operating_system; 425 | String name = OS::get_singleton()->get_name(); 426 | 427 | if (name == "Windows" || name == "UWP") { 428 | operating_system = "windows"; 429 | } else if (name == "macOS") { 430 | operating_system = "macos"; 431 | } else if (name == "Linux" || name == "FreeBSD" || name == "OpenBSD" || name == "BSD") { 432 | operating_system = "linux"; 433 | } 434 | 435 | UtilityFunctions::print(vformat("res://addons/godot-doom-node/%s/%s", operating_system, SPAWN_EXECUTABLE_NAME)); 436 | 437 | CharString path_cs = ProjectSettings::get_singleton()->globalize_path(vformat("res://addons/godot-doom-node/%s/%s", operating_system, SPAWN_EXECUTABLE_NAME)).utf8(); 438 | CharString id_cs = vformat("%s", _shm_id).utf8(); 439 | CharString wad_cs = ProjectSettings::get_singleton()->globalize_path(_wad_path).utf8(); 440 | CharString config_dir_cs = ProjectSettings::get_singleton()->globalize_path(vformat("user://godot-doom/%s-%s/", _wad_path.get_file().get_basename(), _wad_hash)).utf8(); 441 | 442 | char *args[] = { 443 | (char *)path_cs.get_data(), 444 | strdup("-id"), 445 | (char *)id_cs.get_data(), 446 | strdup("-iwad"), 447 | (char *)wad_cs.get_data(), 448 | strdup("-configdir"), 449 | (char *)config_dir_cs.get_data(), 450 | NULL 451 | }; 452 | 453 | char *envp[] = { 454 | NULL 455 | }; 456 | 457 | __pid_t pid = fork(); 458 | if (pid == 0) { 459 | execve(args[0], args, envp); 460 | exit(0); 461 | } 462 | return pid; 463 | } 464 | 465 | void DOOM::_doom_thread_func() { 466 | while (true) { 467 | if (_exiting) { 468 | return; 469 | } 470 | 471 | // Send the tick signal 472 | while (!_shm->init) { 473 | if (_exiting) { 474 | return; 475 | } 476 | OS::get_singleton()->delay_msec(10); 477 | } 478 | 479 | mutex_lock(_shm); 480 | _shm->tick = true; 481 | mutex_unlock(_shm); 482 | 483 | mutex_lock(_shm); 484 | _shm->ticks_msec = Time::get_singleton()->get_ticks_msec(); 485 | mutex_unlock(_shm); 486 | 487 | // // Let's wait for the shared memory to be ready 488 | while (!_shm->ready) { 489 | if (_exiting) { 490 | return; 491 | } 492 | OS::get_singleton()->delay_msec(10); 493 | } 494 | 495 | if (_exiting) { 496 | return; 497 | } 498 | // The shared memory is ready 499 | // Screenbuffer 500 | mutex_lock(_shm); 501 | memcpy(_screen_buffer, _shm->screen_buffer, DOOMGENERIC_RESX * DOOMGENERIC_RESY * 4); 502 | mutex_unlock(_shm); 503 | 504 | // Sounds 505 | _mutex->lock(); 506 | for (int i = 0; i < _shm->sound_instructions_length; i++) { 507 | mutex_lock(_shm); 508 | SoundInstruction instruction; 509 | SoundInstruction_duplicate(&_shm->sound_instructions[i], &instruction); 510 | mutex_unlock(_shm); 511 | _sound_instructions.append(instruction); 512 | } 513 | _mutex->unlock(); 514 | mutex_lock(_shm); 515 | _shm->sound_instructions_length = 0; 516 | mutex_unlock(_shm); 517 | 518 | // Music 519 | _mutex->lock(); 520 | mutex_lock(_shm); 521 | for (int i = 0; i < _shm->music_instructions_length; i++) { 522 | MusicInstruction instruction; 523 | MusicInstruction_duplicate(&_shm->music_instructions[i], &instruction); 524 | _music_instructions.append(instruction); 525 | } 526 | _shm->music_instructions_length = 0; 527 | mutex_unlock(_shm); 528 | _mutex->unlock(); 529 | 530 | // Let's sleep the time Doom asks 531 | OS::get_singleton()->delay_usec(_shm->sleep_ms * 1000); 532 | 533 | _update_input(); 534 | 535 | // Reset the shared memory 536 | mutex_lock(_shm); 537 | _shm->ready = false; 538 | mutex_unlock(_shm); 539 | } 540 | } 541 | 542 | void DOOM::_midi_thread_func() { 543 | while (true) { 544 | _mutex->lock(); 545 | if (_exiting) { 546 | _mutex->unlock(); 547 | return; 548 | } 549 | _mutex->unlock(); 550 | 551 | // Parse music instructions already, sooner the better 552 | _mutex->lock(); 553 | for (MusicInstruction instruction : _music_instructions) { 554 | switch (instruction.type) { 555 | case MUSIC_INSTRUCTION_TYPE_REGISTER_SONG: { 556 | String sha1; 557 | String midi_file; 558 | 559 | for (int i = 0; i < sizeof(instruction.lump_sha1_hex); i += sizeof(instruction.lump_sha1_hex[0])) { 560 | sha1 += vformat("%c", instruction.lump_sha1_hex[i]); 561 | } 562 | 563 | Array keys = _wad_files.keys(); 564 | for (int i = 0; i < keys.size(); i++) { 565 | String key = keys[i]; 566 | if (!key.begins_with("D_")) { 567 | continue; 568 | } 569 | 570 | Dictionary info = _wad_files[key]; 571 | if (sha1.to_lower() == String(info["sha1"]).to_lower()) { 572 | midi_file = key; 573 | break; 574 | } 575 | } 576 | 577 | UtilityFunctions::print(vformat("MUSIC_INSTRUCTION_TYPE_REGISTER_SONG: %s (%s)", midi_file, sha1)); 578 | 579 | if (midi_file.is_empty()) { 580 | break; 581 | } 582 | 583 | if (_stored_midi_files.has(midi_file)) { 584 | if (_fluid_player == nullptr) { 585 | _fluid_player = new_fluid_player(_fluid_synth); 586 | } else if (midi_file != _current_midi_file) { 587 | _stop_music(); 588 | OS::get_singleton()->delay_usec(1000); 589 | 590 | _fluid_player = new_fluid_player(_fluid_synth); 591 | } 592 | 593 | _mutex->lock(); 594 | _current_midi_file = midi_file; 595 | _mutex->unlock(); 596 | 597 | PackedByteArray stored_midi_file = _stored_midi_files[midi_file]; 598 | fluid_player_add_mem(_fluid_player, stored_midi_file.ptrw(), stored_midi_file.size()); 599 | } 600 | } break; 601 | 602 | case MUSIC_INSTRUCTION_TYPE_UNREGISTER_SONG: { 603 | UtilityFunctions::print("MUSIC_INSTRUCTION_TYPE_UNREGISTER_SONG"); 604 | _mutex->lock(); 605 | _current_midi_path = ""; 606 | _mutex->unlock(); 607 | } break; 608 | 609 | case MUSIC_INSTRUCTION_TYPE_PLAY_SONG: { 610 | UtilityFunctions::print(vformat("MUSIC_INSTRUCTION_TYPE_PLAY_SONG: %s", _current_midi_file)); 611 | if (_fluid_player == nullptr) { 612 | _fluid_player = new_fluid_player(_fluid_synth); 613 | PackedByteArray stored_midi_file = _stored_midi_files[_current_midi_file]; 614 | fluid_player_add_mem(_fluid_player, stored_midi_file.ptrw(), stored_midi_file.size()); 615 | } 616 | 617 | _current_midi_looping = instruction.looping; 618 | 619 | fluid_player_seek(_fluid_player, 0); 620 | fluid_player_set_loop(_fluid_player, _current_midi_looping ? -1 : 0); 621 | fluid_player_play(_fluid_player); 622 | } break; 623 | 624 | case MUSIC_INSTRUCTION_TYPE_RESUME_SONG: { 625 | UtilityFunctions::print(vformat("MUSIC_INSTRUCTION_TYPE_RESUME_SONG: %s", _current_midi_file)); 626 | if (_fluid_player == nullptr) { 627 | _fluid_player = new_fluid_player(_fluid_synth); 628 | PackedByteArray stored_midi_file = _stored_midi_files[_current_midi_file]; 629 | fluid_player_add_mem(_fluid_player, stored_midi_file.ptrw(), stored_midi_file.size()); 630 | } 631 | 632 | fluid_player_seek(_fluid_player, _current_midi_tick); 633 | fluid_player_set_loop(_fluid_player, _current_midi_looping ? -1 : 0); 634 | fluid_player_play(_fluid_player); 635 | } break; 636 | 637 | case MUSIC_INSTRUCTION_TYPE_SHUTDOWN_MUSIC: 638 | case MUSIC_INSTRUCTION_TYPE_STOP_SONG: { 639 | UtilityFunctions::print(vformat("MUSIC_INSTRUCTION_TYPE_STOP_SONG: %s", _current_midi_file)); 640 | _stop_music(); 641 | OS::get_singleton()->delay_usec(100); 642 | } break; 643 | 644 | case MUSIC_INSTRUCTION_TYPE_PAUSE_SONG: { 645 | UtilityFunctions::print(vformat("MUSIC_INSTRUCTION_TYPE_PAUSE_SONG: %s", _current_midi_file)); 646 | if (_fluid_player != nullptr) { 647 | _current_midi_tick = fluid_player_get_current_tick(_fluid_player); 648 | } 649 | 650 | _stop_music(); 651 | OS::get_singleton()->delay_usec(100); 652 | } break; 653 | 654 | case MUSIC_INSTRUCTION_TYPE_SET_MUSIC_VOLUME: { 655 | UtilityFunctions::print("MUSIC_INSTRUCTION_TYPE_SET_MUSIC_VOLUME"); 656 | // current_midi_volume = instruction.volume; 657 | } break; 658 | 659 | case MusicInstructionType::MUSIC_INSTRUCTION_TYPE_EMPTY: 660 | default: { 661 | } 662 | } 663 | } 664 | _music_instructions.clear(); 665 | _mutex->unlock(); 666 | 667 | _mutex->lock(); 668 | if (_exiting) { 669 | _mutex->unlock(); 670 | return; 671 | } 672 | _mutex->unlock(); 673 | 674 | if (_fluid_player == nullptr) { 675 | continue; 676 | } 677 | 678 | switch (fluid_player_get_status(_fluid_player)) { 679 | case FLUID_PLAYER_PLAYING: { 680 | if (_current_midi_playback == nullptr) { 681 | continue; 682 | } 683 | 684 | uint64_t ticks = Time::get_singleton()->get_ticks_usec(); 685 | uint64_t diff = ticks - _current_midi_last_tick_usec; 686 | _current_midi_last_tick_usec = ticks; 687 | 688 | uint32_t len_asked = _current_midi_stream->get_mix_rate() / 1000 * diff / 10; 689 | 690 | if (_current_midi_playback->get_frames_available() < len_asked) { 691 | len_asked = _current_midi_playback->get_frames_available(); 692 | } 693 | 694 | if (len_asked <= 0) { 695 | continue; 696 | } 697 | 698 | float bufl[len_asked], bufr[len_asked]; 699 | if (fluid_synth_write_float(_fluid_synth, len_asked, bufl, 0, 1, bufr, 0, 1) == FLUID_FAILED) { 700 | break; 701 | } 702 | 703 | PackedVector2Array frames; 704 | for (int i = 0; i < len_asked; i++) { 705 | frames.append(Vector2(bufl[i], bufr[i])); 706 | } 707 | 708 | _current_midi_playback->push_buffer(frames); 709 | frames.clear(); 710 | 711 | _current_midi_tick = fluid_player_get_current_tick(_fluid_player); 712 | } break; 713 | } 714 | 715 | OS::get_singleton()->delay_usec(10); 716 | } 717 | } 718 | 719 | void DOOM::_wad_thread_func() { 720 | _wad_files.clear(); 721 | 722 | _mutex->lock(); 723 | String local_wad_path = _wad_path; 724 | _mutex->unlock(); 725 | 726 | Ref wad = FileAccess::open(local_wad_path, FileAccess::ModeFlags::READ); 727 | if (wad.is_null()) { 728 | UtilityFunctions::printerr(vformat("Could not open \"%s\"", local_wad_path)); 729 | return; 730 | } 731 | 732 | PackedByteArray sig_array = wad->get_buffer(sizeof(WadOriginalSignature)); 733 | _wad_signature.signature = vformat("%c%c%c%c", sig_array[0], sig_array[1], sig_array[2], sig_array[3]); 734 | _wad_signature.number_of_files = sig_array.decode_s32(sizeof(WadOriginalSignature::sig)); 735 | _wad_signature.fat_offset = sig_array.decode_s32(sizeof(WadOriginalSignature::sig) + sizeof(WadOriginalSignature::numFiles)); 736 | 737 | wad->seek(_wad_signature.fat_offset); 738 | PackedByteArray wad_files = wad->get_buffer(sizeof(WadOriginalFileEntry) * _wad_signature.number_of_files); 739 | for (int i = 0; i < _wad_signature.number_of_files; i++) { 740 | WadFileEntry file_entry; 741 | file_entry.offset_data = wad_files.decode_s32(i * sizeof(WadOriginalFileEntry)); 742 | file_entry.length_data = wad_files.decode_s32(i * sizeof(WadOriginalFileEntry) + sizeof(WadOriginalFileEntry::offData)); 743 | 744 | uint16_t name_offset = i * sizeof(WadOriginalFileEntry) + sizeof(WadOriginalFileEntry::offData) + sizeof(WadOriginalFileEntry::lenData); 745 | file_entry.name = vformat( 746 | "%c%c%c%c%c%c%c%c", 747 | wad_files[name_offset + 0], 748 | wad_files[name_offset + 1], 749 | wad_files[name_offset + 2], 750 | wad_files[name_offset + 3], 751 | wad_files[name_offset + 4], 752 | wad_files[name_offset + 5], 753 | wad_files[name_offset + 6], 754 | wad_files[name_offset + 7]); 755 | wad->seek(file_entry.offset_data); 756 | PackedByteArray file_array = wad->get_buffer(file_entry.length_data); 757 | 758 | Dictionary info; 759 | info["data"] = file_array; 760 | 761 | if (file_array.size() > 0) { 762 | Ref hashing_context; 763 | hashing_context.instantiate(); 764 | hashing_context->start(HashingContext::HASH_SHA1); 765 | hashing_context->update(file_array); 766 | info["sha1"] = hashing_context->finish().hex_encode(); 767 | } else { 768 | info["sha1"] = Variant(); 769 | } 770 | 771 | _wad_files[file_entry.name] = info; 772 | } 773 | 774 | // Calculate hash 775 | Ref hashing_context; 776 | hashing_context.instantiate(); 777 | hashing_context->start(HashingContext::HASH_SHA256); 778 | wad->seek(0); 779 | while (!wad->eof_reached()) { 780 | hashing_context->update(wad->get_buffer(1024)); 781 | } 782 | wad->close(); 783 | PackedByteArray hash = hashing_context->finish(); 784 | 785 | _mutex->lock(); 786 | _wad_hash = hash.hex_encode(); 787 | _mutex->unlock(); 788 | 789 | call_deferred("_wad_thread_end"); 790 | } 791 | 792 | void DOOM::_sound_fetching_thread_func() { 793 | _mutex->lock(); 794 | Array keys = _wad_files.keys(); 795 | _mutex->unlock(); 796 | for (int i = 0; i < keys.size(); i++) { 797 | String key = keys[i]; 798 | if (!key.begins_with("DS")) { 799 | continue; 800 | } 801 | _mutex->lock(); 802 | Dictionary info = _wad_files[key]; 803 | _mutex->unlock(); 804 | PackedByteArray file_array = info["data"]; 805 | 806 | AudioStreamPlayer *player = memnew(AudioStreamPlayer); 807 | player->set_name(key); 808 | 809 | // https://doomwiki.org/wiki/Sound 810 | uint16_t format_number = file_array.decode_u16(0x00); 811 | uint16_t sample_rate = file_array.decode_u16(0x02); 812 | uint32_t number_of_samples = file_array.decode_u32(0x04); 813 | PackedByteArray samples; 814 | for (int j = 0; j < number_of_samples - 0x10; j++) { 815 | // https://docs.godotengine.org/en/stable/classes/class_audiostreamwav.html#property-descriptions 816 | samples.append(file_array.decode_u8(j + 0x18) - 128); 817 | } 818 | 819 | Ref wav; 820 | wav.instantiate(); 821 | wav->set_data(samples); 822 | wav->set_mix_rate(sample_rate); 823 | player->set_stream(wav); 824 | 825 | info["player"] = player; 826 | } 827 | 828 | call_deferred("_sound_fetching_thread_end"); 829 | } 830 | 831 | void DOOM::_midi_fetching_thread_func() { 832 | if (!FileAccess::file_exists(_soundfont_path)) { 833 | return; 834 | } 835 | 836 | _mutex->lock(); 837 | Array keys = _wad_files.keys(); 838 | _mutex->unlock(); 839 | 840 | // Rendering loop 841 | for (int i = 0; i < keys.size(); i++) { 842 | String key = keys[i]; 843 | if (!key.begins_with("D_")) { 844 | continue; 845 | } 846 | 847 | _mutex->lock(); 848 | Dictionary info = _wad_files[key]; 849 | _mutex->unlock(); 850 | 851 | PackedByteArray file_array = info["data"]; 852 | PackedByteArray midi_output; 853 | 854 | // Mus to midi conversion 855 | bool converted = !DOOMMus2Mid::get_singleton()->mus2mid(file_array, midi_output); 856 | if (!converted) { 857 | continue; 858 | } 859 | 860 | _stored_midi_files[key] = midi_output; 861 | } 862 | 863 | call_deferred("_midi_fetching_thread_end"); 864 | } 865 | 866 | void DOOM::_append_sounds() { 867 | SubViewportContainer *sound_subviewportcontainer = (SubViewportContainer *)get_node_or_null("SoundSubViewportContainer"); 868 | if (sound_subviewportcontainer != nullptr) { 869 | sound_subviewportcontainer->set_name("tobedeleted"); 870 | sound_subviewportcontainer->queue_free(); 871 | } 872 | sound_subviewportcontainer = memnew(SubViewportContainer); 873 | sound_subviewportcontainer->set_visible(false); 874 | sound_subviewportcontainer->set_name("SoundSubViewportContainer"); 875 | call_deferred("add_child", sound_subviewportcontainer); 876 | // sound_subviewportcontainer->set_owner(get_tree()->get_edited_scene_root()); 877 | 878 | SubViewport *sound_subviewport = (SubViewport *)get_node_or_null("SoundSubViewport"); 879 | if (sound_subviewport != nullptr) { 880 | sound_subviewport->set_name("tobedeleted"); 881 | sound_subviewport->queue_free(); 882 | } 883 | sound_subviewport = memnew(SubViewport); 884 | sound_subviewport->set_as_audio_listener_2d(true); 885 | sound_subviewport->set_name("SoundSubViewport"); 886 | sound_subviewport->set_size(Vector2(SOUND_SUBVIEWPORT_SIZE, SOUND_SUBVIEWPORT_SIZE)); 887 | sound_subviewportcontainer->add_child(sound_subviewport); 888 | // sound_subviewport->set_owner(get_tree()->get_edited_scene_root()); 889 | 890 | Camera2D *camera = memnew(Camera2D); 891 | camera->set_name("Camera2D"); 892 | sound_subviewport->add_child(camera); 893 | // camera->set_owner(get_tree()->get_edited_scene_root()); 894 | 895 | for (int i = 0; i < 16; i++) { 896 | AudioStreamPlayer2D *player = memnew(AudioStreamPlayer2D); 897 | player->set_position(Vector2(0, 0)); 898 | sound_subviewport->add_child(player); 899 | player->set_name(vformat("Channel%s", i)); 900 | player->set_bus("SFX"); 901 | // player->set_owner(get_tree()->get_edited_scene_root()); 902 | } 903 | 904 | Node *sound_container = get_node_or_null("SoundContainer"); 905 | if (sound_container != nullptr) { 906 | sound_container->set_name("tobedeleted"); 907 | sound_container->queue_free(); 908 | } 909 | sound_container = memnew(Node); 910 | call_deferred("add_child", sound_container); 911 | sound_container->set_name("SoundContainer"); 912 | // sound_container->set_owner(get_tree()->get_edited_scene_root()); 913 | 914 | _mutex->lock(); 915 | Array keys = _wad_files.keys(); 916 | _mutex->unlock(); 917 | 918 | for (int i = 0; i < keys.size(); i++) { 919 | String key = keys[i]; 920 | if (!key.begins_with("DS")) { 921 | continue; 922 | } 923 | 924 | _mutex->lock(); 925 | Dictionary info = _wad_files[key]; 926 | _mutex->unlock(); 927 | 928 | AudioStreamPlayer *player = reinterpret_cast((Object *)info["player"]); 929 | sound_container->add_child(player); 930 | // player->set_owner(get_tree()->get_edited_scene_root()); 931 | } 932 | } 933 | 934 | void DOOM::_append_music() { 935 | Node *music_container = get_node_or_null("MusicContainer"); 936 | if (music_container != nullptr) { 937 | music_container->set_name("tobedeleted"); 938 | music_container->queue_free(); 939 | } 940 | music_container = memnew(Node); 941 | call_deferred("add_child", music_container); 942 | music_container->set_name("MusicContainer"); 943 | // music_container->set_owner(get_tree()->get_edited_scene_root()); 944 | 945 | AudioStreamPlayer *player = memnew(AudioStreamPlayer); 946 | music_container->add_child(player); 947 | player->set_name("Player"); 948 | player->set_bus("Music"); 949 | // player->set_owner(get_tree()->get_edited_scene_root()); 950 | 951 | Ref stream; 952 | stream.instantiate(); 953 | stream->set_buffer_length(0.05); 954 | _current_midi_stream = stream; 955 | player->set_stream(_current_midi_stream); 956 | player->call_deferred("play"); 957 | } 958 | 959 | void DOOM::_wad_thread_end() { 960 | if (_wad_thread.is_valid() && _wad_thread->is_started()) { 961 | _wad_thread->wait_to_finish(); 962 | } 963 | 964 | _sound_fetch_complete = false; 965 | _midi_fetch_complete = false; 966 | 967 | _start_sound_fetching(); 968 | _start_midi_fetching(); 969 | } 970 | 971 | void DOOM::_sound_fetching_thread_end() { 972 | if (_sound_fetching_thread.is_valid() && _sound_fetching_thread->is_started()) { 973 | _sound_fetching_thread->wait_to_finish(); 974 | } 975 | 976 | _append_sounds(); 977 | 978 | _sound_fetch_complete = true; 979 | _update_assets_status(); 980 | } 981 | 982 | void DOOM::_midi_fetching_thread_end() { 983 | if (_midi_fetching_thread.is_valid() && _midi_fetching_thread->is_started()) { 984 | _midi_fetching_thread->wait_to_finish(); 985 | } 986 | 987 | _append_music(); 988 | 989 | _midi_fetch_complete = true; 990 | _update_assets_status(); 991 | } 992 | 993 | void DOOM::_start_sound_fetching() { 994 | if (_sound_fetching_thread.is_null()) { 995 | _sound_fetching_thread.instantiate(); 996 | } else if (_sound_fetching_thread->is_started()) { 997 | _sound_fetching_thread->wait_to_finish(); 998 | } 999 | Callable func = Callable(this, "_sound_fetching_thread_func"); 1000 | _sound_fetching_thread->start(func); 1001 | } 1002 | 1003 | void DOOM::_start_midi_fetching() { 1004 | if (_midi_fetching_thread.is_null()) { 1005 | _midi_fetching_thread.instantiate(); 1006 | } else if (_midi_fetching_thread->is_started()) { 1007 | _midi_fetching_thread->wait_to_finish(); 1008 | } 1009 | Callable func = Callable(this, "_midi_fetching_thread_func"); 1010 | _midi_fetching_thread->start(func); 1011 | } 1012 | 1013 | void DOOM::_update_assets_status() { 1014 | if (_sound_fetch_complete && _midi_fetch_complete) { 1015 | _assets_ready = true; 1016 | emit_signal("assets_imported"); 1017 | } 1018 | } 1019 | 1020 | void DOOM::_wait_for_threads() { 1021 | if (_doom_thread.is_valid() && _doom_thread->is_started()) { 1022 | _doom_thread->wait_to_finish(); 1023 | } 1024 | 1025 | if (_wad_thread.is_valid() && _wad_thread->is_started()) { 1026 | _wad_thread->wait_to_finish(); 1027 | } 1028 | 1029 | if (_midi_fetching_thread.is_valid() && _midi_fetching_thread->is_started()) { 1030 | _midi_fetching_thread->wait_to_finish(); 1031 | } 1032 | 1033 | if (_sound_fetching_thread.is_valid() && _sound_fetching_thread->is_started()) { 1034 | _sound_fetching_thread->wait_to_finish(); 1035 | } 1036 | 1037 | if (_midi_thread.is_valid() && _midi_thread->is_started()) { 1038 | _midi_thread->wait_to_finish(); 1039 | } 1040 | } 1041 | 1042 | void DOOM::_kill_doom() { 1043 | _enabled = false; 1044 | 1045 | if (_shm != nullptr) { 1046 | mutex_lock(_shm); 1047 | _shm->terminate = true; 1048 | mutex_unlock(_shm); 1049 | } 1050 | 1051 | OS::get_singleton()->delay_msec(250); 1052 | 1053 | if (_fluid_player != nullptr) { 1054 | fluid_player_stop(_fluid_player); 1055 | fluid_player_join(_fluid_player); 1056 | delete_fluid_player(_fluid_player); 1057 | _fluid_player = nullptr; 1058 | } 1059 | if (_fluid_synth != nullptr) { 1060 | fluid_synth_system_reset(_fluid_synth); 1061 | } 1062 | 1063 | _mutex->lock(); 1064 | _exiting = true; 1065 | _mutex->unlock(); 1066 | 1067 | String shm_id = _shm_id; 1068 | if (shm_id.length() > 0) { 1069 | // _shm_id is not empty 1070 | UtilityFunctions::print(vformat("Unlinking %s...", shm_id)); 1071 | int result = shm_unlink(_shm_id); 1072 | if (result < 0) { 1073 | fprintf(stderr, "ERROR unlinking shm %s: %s\n", _shm_id, strerror(errno)); 1074 | } 1075 | } 1076 | 1077 | if (_spawn_pid > 0) { 1078 | kill(_spawn_pid, SIGKILL); 1079 | } 1080 | _spawn_pid = 0; 1081 | 1082 | _wait_for_threads(); 1083 | } 1084 | 1085 | void DOOM::_stop_doom() { 1086 | _stop_music(); 1087 | 1088 | _exiting = true; 1089 | 1090 | _wait_for_threads(); 1091 | 1092 | if (_spawn_pid > 0) { 1093 | #ifdef LINUX_ENABLED 1094 | kill(_spawn_pid, SIGSTOP); 1095 | #endif 1096 | } 1097 | } 1098 | 1099 | void DOOM::_update_screen_buffer() { 1100 | memcpy(_screen_buffer_array.ptrw(), _screen_buffer, DOOMGENERIC_RESX * DOOMGENERIC_RESY * RGBA); 1101 | 1102 | Ref image = Image::create_from_data(DOOMGENERIC_RESX, DOOMGENERIC_RESY, false, Image::Format::FORMAT_RGBA8, _screen_buffer_array); 1103 | if (_img_texture->get_image().is_null()) { 1104 | _img_texture->set_image(image); 1105 | } else { 1106 | _img_texture->update(image); 1107 | } 1108 | } 1109 | 1110 | void DOOM::_update_sounds() { 1111 | Node *sound_container = get_node("SoundContainer"); 1112 | if (sound_container == nullptr) { 1113 | return; 1114 | } 1115 | 1116 | for (SoundInstruction instruction : _sound_instructions) { 1117 | switch (instruction.type) { 1118 | case SOUND_INSTRUCTION_TYPE_START_SOUND: { 1119 | String name = vformat("DS%s", String(instruction.name).to_upper()); 1120 | AudioStreamPlayer *source = (AudioStreamPlayer *)sound_container->get_node_or_null(name); 1121 | String channel_name = vformat("SoundSubViewportContainer/SoundSubViewport/Channel%s", instruction.channel); 1122 | AudioStreamPlayer2D *channel = (AudioStreamPlayer2D *)get_node_or_null(channel_name); 1123 | 1124 | if (source == nullptr || channel == nullptr) { 1125 | continue; 1126 | } 1127 | 1128 | channel->stop(); 1129 | channel->set_stream(source->get_stream()); 1130 | // squatting_sound->set_pitch_scale( 1131 | // UtilityFunctions::remap(instruction.pitch, 0, INT8_MAX, 0, 2)); 1132 | channel->set_volume_db( 1133 | UtilityFunctions::linear_to_db( 1134 | UtilityFunctions::remap(instruction.volume, 0.0, INT8_MAX, 0.0, 2.0))); 1135 | 1136 | double pan = UtilityFunctions::remap(instruction.sep, 0, INT8_MAX, -1, 1); 1137 | channel->set_position(Vector2( 1138 | (SOUND_SUBVIEWPORT_SIZE / 2.0) * pan, 1139 | 0)); 1140 | 1141 | channel->play(); 1142 | } break; 1143 | 1144 | case SOUND_INSTRUCTION_TYPE_STOP_SOUND: { 1145 | String channel_name = vformat("SoundSubViewportContainer/SoundSubViewport/Channel%s", instruction.channel); 1146 | AudioStreamPlayer2D *channel = (AudioStreamPlayer2D *)get_node_or_null(channel_name); 1147 | if (channel == nullptr) { 1148 | continue; 1149 | } 1150 | 1151 | channel->stop(); 1152 | } break; 1153 | 1154 | case SOUND_INSTRUCTION_TYPE_UPDATE_SOUND_PARAMS: { 1155 | String channel_name = vformat("SoundSubViewportContainer/SoundSubViewport/Channel%s", instruction.channel); 1156 | AudioStreamPlayer2D *channel = (AudioStreamPlayer2D *)get_node_or_null(channel_name); 1157 | if (channel == nullptr) { 1158 | continue; 1159 | } 1160 | 1161 | channel->set_volume_db( 1162 | UtilityFunctions::linear_to_db( 1163 | UtilityFunctions::remap(instruction.volume, 0.0, INT8_MAX, 0, 2))); 1164 | double pan = UtilityFunctions::remap(instruction.sep, 0, INT8_MAX, -1, 1); 1165 | channel->set_position(Vector2( 1166 | (SOUND_SUBVIEWPORT_SIZE / 2.0) * pan, 1167 | 0)); 1168 | } break; 1169 | 1170 | case SOUND_INSTRUCTION_TYPE_SHUTDOWN_SOUND: { 1171 | SubViewport *sound_subviewport = (SubViewport *)get_node_or_null("SoundSubViewportContainer/SoundSubViewport"); 1172 | if (sound_subviewport == nullptr) { 1173 | continue; 1174 | } 1175 | 1176 | TypedArray children = sound_subviewport->get_children(); 1177 | for (int i = 0; i < children.size(); i++) { 1178 | AudioStreamPlayer2D *channel = (AudioStreamPlayer2D *)(Object *)children[i]; 1179 | if (channel == nullptr) { 1180 | continue; 1181 | } 1182 | 1183 | channel->stop(); 1184 | } 1185 | } break; 1186 | 1187 | case SOUND_INSTRUCTION_TYPE_EMPTY: 1188 | case SOUND_INSTRUCTION_TYPE_PRECACHE_SOUND: 1189 | default: { 1190 | continue; 1191 | } 1192 | } 1193 | } 1194 | _sound_instructions.clear(); 1195 | } 1196 | 1197 | void DOOM::_update_music() { 1198 | AudioStreamPlayer *player = (AudioStreamPlayer *)get_node_or_null("MusicContainer/Player"); 1199 | if (player == nullptr) { 1200 | return; 1201 | } 1202 | 1203 | if (!player->is_playing()) { 1204 | player->play(); 1205 | } 1206 | Ref playback = player->get_stream_playback(); 1207 | if (playback.is_null()) { 1208 | return; 1209 | } 1210 | _current_midi_playback = playback; 1211 | } 1212 | 1213 | void DOOM::_update_input() { 1214 | if (_shm == nullptr) { 1215 | return; 1216 | } 1217 | 1218 | for (Key key_pressed : _keys_pressed_queue) { 1219 | if (!_keys_pressed.has(key_pressed)) { 1220 | _keys_pressed.append(key_pressed); 1221 | } 1222 | 1223 | mutex_lock(_shm); 1224 | _shm->keys_pressed[_shm->keys_pressed_length] = key_pressed | (1 << 31); 1225 | _shm->keys_pressed_length += 1; 1226 | mutex_unlock(_shm); 1227 | } 1228 | _keys_pressed_queue.clear(); 1229 | 1230 | for (Key key_released : _keys_released_queue) { 1231 | if (_keys_pressed.has(key_released)) { 1232 | _keys_pressed.erase(key_released); 1233 | } 1234 | 1235 | mutex_lock(_shm); 1236 | _shm->keys_pressed[_shm->keys_pressed_length] = key_released; 1237 | _shm->keys_pressed_length += 1; 1238 | mutex_unlock(_shm); 1239 | } 1240 | _keys_released_queue.clear(); 1241 | 1242 | for (MouseButton mouse_button_pressed : _mouse_buttons_pressed_queue) { 1243 | if (!_mouse_buttons_pressed.has(mouse_button_pressed)) { 1244 | _mouse_buttons_pressed.append(mouse_button_pressed); 1245 | } 1246 | 1247 | mutex_lock(_shm); 1248 | _shm->mouse_buttons_pressed[_shm->mouse_buttons_pressed_length] = mouse_button_pressed | (1 << 31); 1249 | _shm->mouse_buttons_pressed_length += 1; 1250 | mutex_unlock(_shm); 1251 | } 1252 | _mouse_buttons_pressed_queue.clear(); 1253 | 1254 | for (MouseButton mouse_button_released : _mouse_buttons_released_queue) { 1255 | if (_mouse_buttons_pressed.has(mouse_button_released)) { 1256 | _mouse_buttons_pressed.erase(mouse_button_released); 1257 | } 1258 | 1259 | mutex_lock(_shm); 1260 | _shm->mouse_buttons_pressed[_shm->mouse_buttons_pressed_length] = mouse_button_released; 1261 | _shm->mouse_buttons_pressed_length += 1; 1262 | mutex_unlock(_shm); 1263 | } 1264 | _mouse_buttons_released_queue.clear(); 1265 | } 1266 | 1267 | void DOOM::_init_shm() { 1268 | _doom_instance_id = _last_doom_instance_id; 1269 | _last_doom_instance_id += 1; 1270 | 1271 | const char *spawn_id = vformat("%s-%06d", GODOT_DOOM_SHM_NAME, _doom_instance_id).utf8().get_data(); 1272 | strcpy(_shm_id, spawn_id); 1273 | 1274 | shm_unlink(_shm_id); 1275 | _shm_fd = shm_open(_shm_id, O_RDWR | O_CREAT | O_EXCL, 0666); 1276 | if (_shm_fd < 0) { 1277 | UtilityFunctions::printerr(vformat("ERROR: %s", strerror(errno))); 1278 | return; 1279 | } 1280 | 1281 | ftruncate(_shm_fd, sizeof(SharedMemory)); 1282 | _shm = (SharedMemory *)mmap(0, sizeof(SharedMemory), PROT_WRITE, MAP_SHARED, _shm_fd, 0); 1283 | if (_shm == nullptr) { 1284 | UtilityFunctions::printerr(vformat("ERROR: %s", strerror(errno))); 1285 | return; 1286 | } 1287 | } 1288 | 1289 | void DOOM::doom_ready() { 1290 | _img_texture.instantiate(); 1291 | set_texture(_img_texture); 1292 | 1293 | set_process(true); 1294 | set_process_input(true); 1295 | } 1296 | 1297 | void DOOM::doom_process(double delta) { 1298 | if (_spawn_pid == 0) { 1299 | return; 1300 | } 1301 | 1302 | _update_screen_buffer(); 1303 | _update_sounds(); 1304 | _update_music(); 1305 | 1306 | if (!has_focus()) { 1307 | for (Key key_pressed : _keys_pressed) { 1308 | mutex_lock(_shm); 1309 | _shm->keys_pressed[_shm->keys_pressed_length] = key_pressed; 1310 | _shm->keys_pressed_length += 1; 1311 | mutex_unlock(_shm); 1312 | } 1313 | _keys_pressed.clear(); 1314 | } 1315 | } 1316 | 1317 | void DOOM::_notification(int p_what) { 1318 | switch (p_what) { 1319 | case NOTIFICATION_READY: { 1320 | doom_ready(); 1321 | } break; 1322 | case NOTIFICATION_PROCESS: { 1323 | doom_process(get_process_delta_time()); 1324 | } break; 1325 | case NOTIFICATION_EXIT_TREE: { 1326 | _kill_doom(); 1327 | } break; 1328 | } 1329 | } 1330 | 1331 | DOOM::DOOM() { 1332 | _spawn_pid = 0; 1333 | _mutex.instantiate(); 1334 | 1335 | _fluid_settings = new_fluid_settings(); 1336 | fluid_settings_setstr(_fluid_settings, "player.timing-source", "system"); 1337 | _fluid_synth = new_fluid_synth(_fluid_settings); 1338 | fluid_synth_set_gain(_fluid_synth, 0.8f); 1339 | _fluid_player = new_fluid_player(_fluid_synth); 1340 | } 1341 | 1342 | DOOM::~DOOM() { 1343 | if (_fluid_player != nullptr) { 1344 | delete_fluid_player(_fluid_player); 1345 | } 1346 | if (_fluid_synth != nullptr) { 1347 | delete_fluid_synth(_fluid_synth); 1348 | } 1349 | if (_fluid_settings != nullptr) { 1350 | delete_fluid_settings(_fluid_settings); 1351 | } 1352 | 1353 | _wait_for_threads(); 1354 | } 1355 | 1356 | int DOOM::_last_doom_instance_id = 0; 1357 | -------------------------------------------------------------------------------- /src/doom.h: -------------------------------------------------------------------------------- 1 | #ifndef DOOM_H 2 | #define DOOM_H 3 | 4 | #include 5 | 6 | #include "doomgeneric/doomgeneric.h" 7 | #include "godot_cpp/classes/audio_stream_generator.hpp" 8 | #include "godot_cpp/classes/audio_stream_generator_playback.hpp" 9 | #include "godot_cpp/classes/control.hpp" 10 | #include "godot_cpp/classes/global_constants.hpp" 11 | #include "godot_cpp/classes/image_texture.hpp" 12 | #include "godot_cpp/classes/input_event.hpp" 13 | #include "godot_cpp/classes/mutex.hpp" 14 | #include "godot_cpp/classes/os.hpp" 15 | #include "godot_cpp/classes/texture_rect.hpp" 16 | #include "godot_cpp/classes/thread.hpp" 17 | #include "godot_cpp/classes/wrapped.hpp" 18 | #include "godot_cpp/core/class_db.hpp" 19 | #include "godot_cpp/templates/vector.hpp" 20 | #include "godot_cpp/variant/dictionary.hpp" 21 | #include "godot_cpp/variant/packed_byte_array.hpp" 22 | 23 | extern "C" { 24 | #include "fluidsynth.h" 25 | 26 | #include "doomcommon.h" 27 | #include "doomspawn.h" 28 | } 29 | 30 | namespace godot { 31 | 32 | class DOOM : public TextureRect { 33 | GDCLASS(DOOM, TextureRect); 34 | 35 | private: 36 | struct WadOriginalSignature { 37 | char sig[4]; 38 | int32_t numFiles; 39 | int32_t offFAT; 40 | }; 41 | 42 | struct WadSignature { 43 | String signature; 44 | int32_t number_of_files; 45 | int32_t fat_offset; 46 | }; 47 | 48 | struct WadOriginalFileEntry { 49 | int32_t offData; 50 | int32_t lenData; 51 | char name[8]; 52 | }; 53 | 54 | struct WadFileEntry { 55 | int32_t offset_data; 56 | int32_t length_data; 57 | String name; 58 | }; 59 | 60 | WadSignature _wad_signature; 61 | Dictionary _wad_files; 62 | 63 | static int _last_doom_instance_id; 64 | int _doom_instance_id = 0; 65 | char _shm_id[255] = ""; 66 | 67 | bool _exiting = false; 68 | bool _assets_ready = false; 69 | bool _sound_fetch_complete = false; 70 | bool _midi_fetch_complete = false; 71 | 72 | Ref _mutex = nullptr; 73 | Ref _doom_thread = nullptr; 74 | Ref _midi_thread = nullptr; 75 | Ref _wad_thread = nullptr; 76 | Ref _sound_fetching_thread = nullptr; 77 | Ref _midi_fetching_thread = nullptr; 78 | 79 | SharedMemory *_shm = nullptr; 80 | __pid_t _spawn_pid = 0; 81 | int _shm_fd = 0; 82 | 83 | Vector _uuids; 84 | Vector _sound_instructions; 85 | Vector _music_instructions; 86 | Vector _keys_pressed; 87 | Vector _keys_pressed_queue; 88 | Vector _keys_released_queue; 89 | Vector _mouse_buttons_pressed; 90 | Vector _mouse_buttons_pressed_queue; 91 | Vector _mouse_buttons_released_queue; 92 | 93 | bool _enabled = false; 94 | bool _wasd_mode = false; 95 | float _mouse_acceleration = 1.0f; 96 | bool _autosave = false; 97 | 98 | String _wad_path; 99 | String _soundfont_path; 100 | 101 | String _current_midi_path; 102 | bool current_midi_playing = false; 103 | uint32_t _current_midi_tick = 0; 104 | bool current_midi_pause = false; 105 | bool _current_midi_looping = false; 106 | uint32_t _current_midi_volume = 0; 107 | uint64_t _current_midi_last_tick_usec = 0; 108 | Dictionary _stored_midi_files; 109 | String _current_midi_file; 110 | 111 | fluid_settings_t *_fluid_settings = nullptr; 112 | fluid_synth_t *_fluid_synth = nullptr; 113 | int _fluid_synth_id = -1; 114 | fluid_player_t *_fluid_player = nullptr; 115 | 116 | Ref _current_midi_stream = nullptr; 117 | Ref _current_midi_playback = nullptr; 118 | 119 | String _wad_name; 120 | String _wad_hash; 121 | 122 | // TextureRect *texture_rect; 123 | Ref _img_texture = nullptr; 124 | unsigned char _screen_buffer[DOOMGENERIC_RESX * DOOMGENERIC_RESY * 4]; 125 | PackedByteArray _screen_buffer_array; 126 | 127 | void _init_shm(); 128 | 129 | void _doom_thread_func(); 130 | void _wad_thread_func(); 131 | void _sound_fetching_thread_func(); 132 | void _midi_fetching_thread_func(); 133 | void _midi_thread_func(); 134 | 135 | void _append_sounds(); 136 | void _append_music(); 137 | 138 | void _wad_thread_end(); 139 | void _sound_fetching_thread_end(); 140 | void _midi_fetching_thread_end(); 141 | 142 | void _start_sound_fetching(); 143 | void _start_midi_fetching(); 144 | void _update_assets_status(); 145 | void _wait_for_threads(); 146 | void _start_threads(); 147 | 148 | void _stop_music(); 149 | 150 | void _init_doom(); 151 | void _kill_doom(); 152 | void _stop_doom(); 153 | __pid_t _launch_doom_executable(); 154 | 155 | void _update_screen_buffer(); 156 | void _update_sounds(); 157 | void _update_music(); 158 | void _update_input(); 159 | void _update_doom(); 160 | 161 | protected: 162 | static void _bind_methods(); 163 | 164 | public: 165 | DOOM(); 166 | ~DOOM(); 167 | 168 | bool get_autosave(); 169 | void set_autosave(bool p_autosave); 170 | bool get_wasd_mode(); 171 | void set_wasd_mode(bool p_wasd_mode); 172 | float get_mouse_acceleration(); 173 | void set_mouse_acceleration(float p_mouse_acceleration); 174 | bool get_import_assets(); 175 | void set_import_assets(bool p_import_assets); 176 | bool get_assets_ready(); 177 | void set_assets_ready(bool p_ready); 178 | bool get_enabled(); 179 | void set_enabled(bool p_enabled); 180 | String get_wad_path(); 181 | void set_wad_path(String p_wad_path); 182 | String get_soundfont_path(); 183 | void set_soundfont_path(String p_soundfont_path); 184 | 185 | void import_assets(); 186 | void pause(); 187 | void resume(); 188 | 189 | void _notification(int p_what); 190 | 191 | void doom_ready(); 192 | void doom_process(double p_delta); 193 | 194 | void _input(const Ref &event) override; 195 | }; 196 | 197 | } // namespace godot 198 | 199 | #endif // DOOM_H 200 | -------------------------------------------------------------------------------- /src/doomcommon.h: -------------------------------------------------------------------------------- 1 | #ifndef DOOMCOMMON_H 2 | #define DOOMCOMMON_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #include 9 | #include 10 | 11 | #include "doomgeneric/doomgeneric.h" 12 | #include "doomgeneric/doomtype.h" 13 | #include "doomgeneric/w_wad.h" 14 | 15 | #define GODOT_DOOM_SHM_NAME "/doom" 16 | #define GODOT_DOOM_SPAWN_SHM_ID GODOT_DOOM_SHM_NAME "000000" 17 | #define RGBA 4 18 | 19 | typedef enum SoundInstructionType { 20 | SOUND_INSTRUCTION_TYPE_EMPTY, 21 | SOUND_INSTRUCTION_TYPE_PRECACHE_SOUND, 22 | SOUND_INSTRUCTION_TYPE_START_SOUND, 23 | SOUND_INSTRUCTION_TYPE_STOP_SOUND, 24 | SOUND_INSTRUCTION_TYPE_UPDATE_SOUND_PARAMS, 25 | SOUND_INSTRUCTION_TYPE_SHUTDOWN_SOUND, 26 | SOUND_INSTRUCTION_TYPE_MAX 27 | } SoundInstructionType; 28 | 29 | typedef struct SoundInstruction { 30 | SoundInstructionType type; 31 | char name[9]; 32 | int32_t channel; 33 | int32_t volume; 34 | int32_t sep; 35 | int32_t pitch; 36 | int32_t priority; 37 | int32_t usefulness; 38 | } SoundInstruction; 39 | 40 | void static inline SoundInstruction_duplicate(SoundInstruction *p_from, SoundInstruction *r_to) { 41 | *r_to = *p_from; 42 | strcpy(r_to->name, p_from->name); 43 | } 44 | 45 | typedef enum MusicInstructionType { 46 | MUSIC_INSTRUCTION_TYPE_EMPTY, 47 | MUSIC_INSTRUCTION_TYPE_INIT, 48 | MUSIC_INSTRUCTION_TYPE_SHUTDOWN_MUSIC, 49 | MUSIC_INSTRUCTION_TYPE_SET_MUSIC_VOLUME, 50 | MUSIC_INSTRUCTION_TYPE_PAUSE_SONG, 51 | MUSIC_INSTRUCTION_TYPE_RESUME_SONG, 52 | MUSIC_INSTRUCTION_TYPE_REGISTER_SONG, 53 | MUSIC_INSTRUCTION_TYPE_UNREGISTER_SONG, 54 | MUSIC_INSTRUCTION_TYPE_PLAY_SONG, 55 | MUSIC_INSTRUCTION_TYPE_STOP_SONG, 56 | } MusicInstructionType; 57 | 58 | typedef struct MusicInstruction { 59 | MusicInstructionType type; 60 | char lump_sha1_hex[41]; 61 | int32_t volume; 62 | uint8_t looping : 1; 63 | } MusicInstruction; 64 | 65 | void static inline MusicInstruction_duplicate(MusicInstruction *p_from, MusicInstruction *r_to) { 66 | *r_to = *p_from; 67 | strcpy(r_to->lump_sha1_hex, p_from->lump_sha1_hex); 68 | } 69 | 70 | typedef struct SharedMemory { 71 | uint64_t ticks_msec; 72 | unsigned char *screen_buffer[DOOMGENERIC_RESX * DOOMGENERIC_RESY * 4]; 73 | char window_title[UINT8_MAX]; 74 | uint32_t keys_pressed[UINT8_MAX]; 75 | uint8_t keys_pressed_length; 76 | uint32_t mouse_buttons_pressed[UINT8_MAX]; 77 | uint8_t mouse_buttons_pressed_length; 78 | float mouse_x; 79 | float mouse_y; 80 | uint32_t sleep_ms; 81 | uint8_t terminate : 1; 82 | uint8_t ready : 1; 83 | uint8_t tick : 1; 84 | uint8_t init : 1; 85 | uint8_t lock : 1; 86 | uint8_t sound_instructions_length; 87 | SoundInstruction sound_instructions[UINT8_MAX]; 88 | uint8_t music_instructions_length; 89 | MusicInstruction music_instructions[UINT8_MAX]; 90 | } SharedMemory; 91 | 92 | #ifdef __cplusplus 93 | } 94 | #endif 95 | 96 | #endif /* DOOMCOMMON_H */ 97 | -------------------------------------------------------------------------------- /src/doominput.h: -------------------------------------------------------------------------------- 1 | #ifndef DOOMINPUT_H 2 | #define DOOMINPUT_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | // Took from "core/os/keyboard.h" 9 | typedef enum Key { 10 | NONE = 0, 11 | // Special key: The strategy here is similar to the one used by toolkits, 12 | // which consists in leaving the 21 bits unicode range for printable 13 | // characters, and use the upper 11 bits for special keys and modifiers. 14 | // This way everything (char/keycode) can fit nicely in one 32-bit 15 | // integer (the enum's underlying type is `int` by default). 16 | GDDOOM_KEY_SPECIAL = (1 << 22), 17 | /* CURSOR/FUNCTION/BROWSER/MULTIMEDIA/MISC KEYS */ 18 | GDDOOM_KEY_ESCAPE = GDDOOM_KEY_SPECIAL | 0x01, 19 | GDDOOM_KEY_TAB = GDDOOM_KEY_SPECIAL | 0x02, 20 | GDDOOM_KEY_BACKTAB = GDDOOM_KEY_SPECIAL | 0x03, 21 | GDDOOM_KEY_BACKSPACE = GDDOOM_KEY_SPECIAL | 0x04, 22 | GDDOOM_KEY_ENTER = GDDOOM_KEY_SPECIAL | 0x05, 23 | GDDOOM_KEY_KP_ENTER = GDDOOM_KEY_SPECIAL | 0x06, 24 | GDDOOM_KEY_INSERT = GDDOOM_KEY_SPECIAL | 0x07, 25 | GDDOOM_KEY_KEY_DELETE = GDDOOM_KEY_SPECIAL | 0x08, // "DELETE" is a reserved word on Windows. 26 | GDDOOM_KEY_PAUSE = GDDOOM_KEY_SPECIAL | 0x09, 27 | GDDOOM_KEY_PRINT = GDDOOM_KEY_SPECIAL | 0x0A, 28 | GDDOOM_KEY_SYSREQ = GDDOOM_KEY_SPECIAL | 0x0B, 29 | GDDOOM_KEY_CLEAR = GDDOOM_KEY_SPECIAL | 0x0C, 30 | GDDOOM_KEY_HOME = GDDOOM_KEY_SPECIAL | 0x0D, 31 | GDDOOM_KEY_END = GDDOOM_KEY_SPECIAL | 0x0E, 32 | GDDOOM_KEY_LEFT = GDDOOM_KEY_SPECIAL | 0x0F, 33 | GDDOOM_KEY_UP = GDDOOM_KEY_SPECIAL | 0x10, 34 | GDDOOM_KEY_RIGHT = GDDOOM_KEY_SPECIAL | 0x11, 35 | GDDOOM_KEY_DOWN = GDDOOM_KEY_SPECIAL | 0x12, 36 | GDDOOM_KEY_PAGEUP = GDDOOM_KEY_SPECIAL | 0x13, 37 | GDDOOM_KEY_PAGEDOWN = GDDOOM_KEY_SPECIAL | 0x14, 38 | GDDOOM_KEY_SHIFT = GDDOOM_KEY_SPECIAL | 0x15, 39 | GDDOOM_KEY_CTRL = GDDOOM_KEY_SPECIAL | 0x16, 40 | GDDOOM_KEY_META = GDDOOM_KEY_SPECIAL | 0x17, 41 | #if defined(MACOS_ENABLED) 42 | GDDOOM_KEY_CMD_OR_CTRL = GDDOOM_KEY_META, 43 | #else 44 | GDDOOM_KEY_CMD_OR_CTRL = GDDOOM_KEY_CTRL, 45 | #endif 46 | GDDOOM_KEY_ALT = GDDOOM_KEY_SPECIAL | 0x18, 47 | GDDOOM_KEY_CAPSLOCK = GDDOOM_KEY_SPECIAL | 0x19, 48 | GDDOOM_KEY_NUMLOCK = GDDOOM_KEY_SPECIAL | 0x1A, 49 | GDDOOM_KEY_SCROLLLOCK = GDDOOM_KEY_SPECIAL | 0x1B, 50 | GDDOOM_KEY_F1 = GDDOOM_KEY_SPECIAL | 0x1C, 51 | GDDOOM_KEY_F2 = GDDOOM_KEY_SPECIAL | 0x1D, 52 | GDDOOM_KEY_F3 = GDDOOM_KEY_SPECIAL | 0x1E, 53 | GDDOOM_KEY_F4 = GDDOOM_KEY_SPECIAL | 0x1F, 54 | GDDOOM_KEY_F5 = GDDOOM_KEY_SPECIAL | 0x20, 55 | GDDOOM_KEY_F6 = GDDOOM_KEY_SPECIAL | 0x21, 56 | GDDOOM_KEY_F7 = GDDOOM_KEY_SPECIAL | 0x22, 57 | GDDOOM_KEY_F8 = GDDOOM_KEY_SPECIAL | 0x23, 58 | GDDOOM_KEY_F9 = GDDOOM_KEY_SPECIAL | 0x24, 59 | GDDOOM_KEY_F10 = GDDOOM_KEY_SPECIAL | 0x25, 60 | GDDOOM_KEY_F11 = GDDOOM_KEY_SPECIAL | 0x26, 61 | GDDOOM_KEY_F12 = GDDOOM_KEY_SPECIAL | 0x27, 62 | GDDOOM_KEY_F13 = GDDOOM_KEY_SPECIAL | 0x28, 63 | GDDOOM_KEY_F14 = GDDOOM_KEY_SPECIAL | 0x29, 64 | GDDOOM_KEY_F15 = GDDOOM_KEY_SPECIAL | 0x2A, 65 | GDDOOM_KEY_F16 = GDDOOM_KEY_SPECIAL | 0x2B, 66 | GDDOOM_KEY_F17 = GDDOOM_KEY_SPECIAL | 0x2C, 67 | GDDOOM_KEY_F18 = GDDOOM_KEY_SPECIAL | 0x2D, 68 | GDDOOM_KEY_F19 = GDDOOM_KEY_SPECIAL | 0x2E, 69 | GDDOOM_KEY_F20 = GDDOOM_KEY_SPECIAL | 0x2F, 70 | GDDOOM_KEY_F21 = GDDOOM_KEY_SPECIAL | 0x30, 71 | GDDOOM_KEY_F22 = GDDOOM_KEY_SPECIAL | 0x31, 72 | GDDOOM_KEY_F23 = GDDOOM_KEY_SPECIAL | 0x32, 73 | GDDOOM_KEY_F24 = GDDOOM_KEY_SPECIAL | 0x33, 74 | GDDOOM_KEY_F25 = GDDOOM_KEY_SPECIAL | 0x34, 75 | GDDOOM_KEY_F26 = GDDOOM_KEY_SPECIAL | 0x35, 76 | GDDOOM_KEY_F27 = GDDOOM_KEY_SPECIAL | 0x36, 77 | GDDOOM_KEY_F28 = GDDOOM_KEY_SPECIAL | 0x37, 78 | GDDOOM_KEY_F29 = GDDOOM_KEY_SPECIAL | 0x38, 79 | GDDOOM_KEY_F30 = GDDOOM_KEY_SPECIAL | 0x39, 80 | GDDOOM_KEY_F31 = GDDOOM_KEY_SPECIAL | 0x3A, 81 | GDDOOM_KEY_F32 = GDDOOM_KEY_SPECIAL | 0x3B, 82 | GDDOOM_KEY_F33 = GDDOOM_KEY_SPECIAL | 0x3C, 83 | GDDOOM_KEY_F34 = GDDOOM_KEY_SPECIAL | 0x3D, 84 | GDDOOM_KEY_F35 = GDDOOM_KEY_SPECIAL | 0x3E, 85 | GDDOOM_KEY_KP_MULTIPLY = GDDOOM_KEY_SPECIAL | 0x81, 86 | GDDOOM_KEY_KP_DIVIDE = GDDOOM_KEY_SPECIAL | 0x82, 87 | GDDOOM_KEY_KP_SUBTRACT = GDDOOM_KEY_SPECIAL | 0x83, 88 | GDDOOM_KEY_KP_PERIOD = GDDOOM_KEY_SPECIAL | 0x84, 89 | GDDOOM_KEY_KP_ADD = GDDOOM_KEY_SPECIAL | 0x85, 90 | GDDOOM_KEY_KP_0 = GDDOOM_KEY_SPECIAL | 0x86, 91 | GDDOOM_KEY_KP_1 = GDDOOM_KEY_SPECIAL | 0x87, 92 | GDDOOM_KEY_KP_2 = GDDOOM_KEY_SPECIAL | 0x88, 93 | GDDOOM_KEY_KP_3 = GDDOOM_KEY_SPECIAL | 0x89, 94 | GDDOOM_KEY_KP_4 = GDDOOM_KEY_SPECIAL | 0x8A, 95 | GDDOOM_KEY_KP_5 = GDDOOM_KEY_SPECIAL | 0x8B, 96 | GDDOOM_KEY_KP_6 = GDDOOM_KEY_SPECIAL | 0x8C, 97 | GDDOOM_KEY_KP_7 = GDDOOM_KEY_SPECIAL | 0x8D, 98 | GDDOOM_KEY_KP_8 = GDDOOM_KEY_SPECIAL | 0x8E, 99 | GDDOOM_KEY_KP_9 = GDDOOM_KEY_SPECIAL | 0x8F, 100 | GDDOOM_KEY_MENU = GDDOOM_KEY_SPECIAL | 0x42, 101 | GDDOOM_KEY_HYPER = GDDOOM_KEY_SPECIAL | 0x43, 102 | GDDOOM_KEY_HELP = GDDOOM_KEY_SPECIAL | 0x45, 103 | GDDOOM_KEY_BACK = GDDOOM_KEY_SPECIAL | 0x48, 104 | GDDOOM_KEY_FORWARD = GDDOOM_KEY_SPECIAL | 0x49, 105 | GDDOOM_KEY_STOP = GDDOOM_KEY_SPECIAL | 0x4A, 106 | GDDOOM_KEY_REFRESH = GDDOOM_KEY_SPECIAL | 0x4B, 107 | GDDOOM_KEY_VOLUMEDOWN = GDDOOM_KEY_SPECIAL | 0x4C, 108 | GDDOOM_KEY_VOLUMEMUTE = GDDOOM_KEY_SPECIAL | 0x4D, 109 | GDDOOM_KEY_VOLUMEUP = GDDOOM_KEY_SPECIAL | 0x4E, 110 | GDDOOM_KEY_MEDIAPLAY = GDDOOM_KEY_SPECIAL | 0x54, 111 | GDDOOM_KEY_MEDIASTOP = GDDOOM_KEY_SPECIAL | 0x55, 112 | GDDOOM_KEY_MEDIAPREVIOUS = GDDOOM_KEY_SPECIAL | 0x56, 113 | GDDOOM_KEY_MEDIANEXT = GDDOOM_KEY_SPECIAL | 0x57, 114 | GDDOOM_KEY_MEDIARECORD = GDDOOM_KEY_SPECIAL | 0x58, 115 | GDDOOM_KEY_HOMEPAGE = GDDOOM_KEY_SPECIAL | 0x59, 116 | GDDOOM_KEY_FAVORITES = GDDOOM_KEY_SPECIAL | 0x5A, 117 | GDDOOM_KEY_SEARCH = GDDOOM_KEY_SPECIAL | 0x5B, 118 | GDDOOM_KEY_STANDBY = GDDOOM_KEY_SPECIAL | 0x5C, 119 | GDDOOM_KEY_OPENURL = GDDOOM_KEY_SPECIAL | 0x5D, 120 | GDDOOM_KEY_LAUNCHMAIL = GDDOOM_KEY_SPECIAL | 0x5E, 121 | GDDOOM_KEY_LAUNCHMEDIA = GDDOOM_KEY_SPECIAL | 0x5F, 122 | GDDOOM_KEY_LAUNCH0 = GDDOOM_KEY_SPECIAL | 0x60, 123 | GDDOOM_KEY_LAUNCH1 = GDDOOM_KEY_SPECIAL | 0x61, 124 | GDDOOM_KEY_LAUNCH2 = GDDOOM_KEY_SPECIAL | 0x62, 125 | GDDOOM_KEY_LAUNCH3 = GDDOOM_KEY_SPECIAL | 0x63, 126 | GDDOOM_KEY_LAUNCH4 = GDDOOM_KEY_SPECIAL | 0x64, 127 | GDDOOM_KEY_LAUNCH5 = GDDOOM_KEY_SPECIAL | 0x65, 128 | GDDOOM_KEY_LAUNCH6 = GDDOOM_KEY_SPECIAL | 0x66, 129 | GDDOOM_KEY_LAUNCH7 = GDDOOM_KEY_SPECIAL | 0x67, 130 | GDDOOM_KEY_LAUNCH8 = GDDOOM_KEY_SPECIAL | 0x68, 131 | GDDOOM_KEY_LAUNCH9 = GDDOOM_KEY_SPECIAL | 0x69, 132 | GDDOOM_KEY_LAUNCHA = GDDOOM_KEY_SPECIAL | 0x6A, 133 | GDDOOM_KEY_LAUNCHB = GDDOOM_KEY_SPECIAL | 0x6B, 134 | GDDOOM_KEY_LAUNCHC = GDDOOM_KEY_SPECIAL | 0x6C, 135 | GDDOOM_KEY_LAUNCHD = GDDOOM_KEY_SPECIAL | 0x6D, 136 | GDDOOM_KEY_LAUNCHE = GDDOOM_KEY_SPECIAL | 0x6E, 137 | GDDOOM_KEY_LAUNCHF = GDDOOM_KEY_SPECIAL | 0x6F, 138 | 139 | GDDOOM_KEY_GLOBE = GDDOOM_KEY_SPECIAL | 0x70, 140 | GDDOOM_KEY_KEYBOARD = GDDOOM_KEY_SPECIAL | 0x71, 141 | GDDOOM_KEY_JIS_EISU = GDDOOM_KEY_SPECIAL | 0x72, 142 | GDDOOM_KEY_JIS_KANA = GDDOOM_KEY_SPECIAL | 0x73, 143 | 144 | GDDOOM_KEY_UNKNOWN = GDDOOM_KEY_SPECIAL | 0x7FFFFF, 145 | 146 | /* PRINTABLE LATIN 1 CODES */ 147 | 148 | GDDOOM_KEY_SPACE = 0x0020, 149 | GDDOOM_KEY_EXCLAM = 0x0021, 150 | GDDOOM_KEY_QUOTEDBL = 0x0022, 151 | GDDOOM_KEY_NUMBERSIGN = 0x0023, 152 | GDDOOM_KEY_DOLLAR = 0x0024, 153 | GDDOOM_KEY_PERCENT = 0x0025, 154 | GDDOOM_KEY_AMPERSAND = 0x0026, 155 | GDDOOM_KEY_APOSTROPHE = 0x0027, 156 | GDDOOM_KEY_PARENLEFT = 0x0028, 157 | GDDOOM_KEY_PARENRIGHT = 0x0029, 158 | GDDOOM_KEY_ASTERISK = 0x002A, 159 | GDDOOM_KEY_PLUS = 0x002B, 160 | GDDOOM_KEY_COMMA = 0x002C, 161 | GDDOOM_KEY_MINUS = 0x002D, 162 | GDDOOM_KEY_PERIOD = 0x002E, 163 | GDDOOM_KEY_SLASH = 0x002F, 164 | GDDOOM_KEY_KEY_0 = 0x0030, 165 | GDDOOM_KEY_KEY_1 = 0x0031, 166 | GDDOOM_KEY_KEY_2 = 0x0032, 167 | GDDOOM_KEY_KEY_3 = 0x0033, 168 | GDDOOM_KEY_KEY_4 = 0x0034, 169 | GDDOOM_KEY_KEY_5 = 0x0035, 170 | GDDOOM_KEY_KEY_6 = 0x0036, 171 | GDDOOM_KEY_KEY_7 = 0x0037, 172 | GDDOOM_KEY_KEY_8 = 0x0038, 173 | GDDOOM_KEY_KEY_9 = 0x0039, 174 | GDDOOM_KEY_COLON = 0x003A, 175 | GDDOOM_KEY_SEMICOLON = 0x003B, 176 | GDDOOM_KEY_LESS = 0x003C, 177 | GDDOOM_KEY_EQUAL = 0x003D, 178 | GDDOOM_KEY_GREATER = 0x003E, 179 | GDDOOM_KEY_QUESTION = 0x003F, 180 | GDDOOM_KEY_AT = 0x0040, 181 | GDDOOM_KEY_A = 0x0041, 182 | GDDOOM_KEY_B = 0x0042, 183 | GDDOOM_KEY_C = 0x0043, 184 | GDDOOM_KEY_D = 0x0044, 185 | GDDOOM_KEY_E = 0x0045, 186 | GDDOOM_KEY_F = 0x0046, 187 | GDDOOM_KEY_G = 0x0047, 188 | GDDOOM_KEY_H = 0x0048, 189 | GDDOOM_KEY_I = 0x0049, 190 | GDDOOM_KEY_J = 0x004A, 191 | GDDOOM_KEY_K = 0x004B, 192 | GDDOOM_KEY_L = 0x004C, 193 | GDDOOM_KEY_M = 0x004D, 194 | GDDOOM_KEY_N = 0x004E, 195 | GDDOOM_KEY_O = 0x004F, 196 | GDDOOM_KEY_P = 0x0050, 197 | GDDOOM_KEY_Q = 0x0051, 198 | GDDOOM_KEY_R = 0x0052, 199 | GDDOOM_KEY_S = 0x0053, 200 | GDDOOM_KEY_T = 0x0054, 201 | GDDOOM_KEY_U = 0x0055, 202 | GDDOOM_KEY_V = 0x0056, 203 | GDDOOM_KEY_W = 0x0057, 204 | GDDOOM_KEY_X = 0x0058, 205 | GDDOOM_KEY_Y = 0x0059, 206 | GDDOOM_KEY_Z = 0x005A, 207 | GDDOOM_KEY_BRACKETLEFT = 0x005B, 208 | GDDOOM_KEY_BACKSLASH = 0x005C, 209 | GDDOOM_KEY_BRACKETRIGHT = 0x005D, 210 | GDDOOM_KEY_ASCIICIRCUM = 0x005E, 211 | GDDOOM_KEY_UNDERSCORE = 0x005F, 212 | GDDOOM_KEY_QUOTELEFT = 0x0060, 213 | GDDOOM_KEY_BRACELEFT = 0x007B, 214 | GDDOOM_KEY_BAR = 0x007C, 215 | GDDOOM_KEY_BRACERIGHT = 0x007D, 216 | GDDOOM_KEY_ASCIITILDE = 0x007E, 217 | GDDOOM_KEY_YEN = 0x00A5, 218 | GDDOOM_KEY_SECTION = 0x00A7, 219 | } Key; 220 | 221 | #ifdef __cplusplus 222 | } 223 | #endif 224 | 225 | #endif /* DOOMINPUT_H */ 226 | -------------------------------------------------------------------------------- /src/doommus2mid.cpp: -------------------------------------------------------------------------------- 1 | // 2 | // Copyright(C) 1993-1996 Id Software, Inc. 3 | // Copyright(C) 2005-2014 Simon Howard 4 | // Copyright(C) 2006 Ben Ryves 2006 5 | // 6 | // This program is free software; you can redistribute it and/or 7 | // modify it under the terms of the GNU General Public License 8 | // as published by the Free Software Foundation; either version 2 9 | // of the License, or (at your option) any later version. 10 | // 11 | // This program is distributed in the hope that it will be useful, 12 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 13 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 14 | // GNU General Public License for more details. 15 | // 16 | // mus2mid.c - Ben Ryves 2006 - http://benryves.com - benryves@benryves.com 17 | // Use to convert a MUS file into a single track, type 0 MIDI file. 18 | // mus2mid.cpp - Adam Scott 2023 - https://adamscott.studio 19 | // Use Godot 4 PackedByteArray instead of DOOM memory system. 20 | 21 | #include "doommus2mid.h" 22 | 23 | #include "godot_cpp/classes/file_access.hpp" 24 | #include "godot_cpp/core/class_db.hpp" 25 | #include "godot_cpp/variant/packed_byte_array.hpp" 26 | #include "godot_cpp/variant/utility_functions.hpp" 27 | #include "godot_cpp/variant/variant.hpp" 28 | #include 29 | #include 30 | 31 | #define NUM_CHANNELS 16 32 | 33 | #define MIDI_PERCUSSION_CHAN 9 34 | #define MUS_PERCUSSION_CHAN 15 35 | 36 | using namespace godot; 37 | 38 | void DOOMMus2Mid::_bind_methods() { 39 | } 40 | 41 | bool DOOMMus2Mid::write_time(uint32_t p_time, PackedByteArray &p_midi_output) { 42 | uint32_t buffer = p_time & 0x7F; 43 | uint8_t write_val; 44 | 45 | while ((p_time >>= 7) != 0) { 46 | buffer <<= 8; 47 | buffer |= ((p_time & 0x7F) | 0x80); 48 | } 49 | 50 | while (true) { 51 | write_val = buffer & 0xFF; 52 | 53 | if (p_midi_output.append(write_val)) { 54 | return true; 55 | } 56 | track_size++; 57 | 58 | if ((buffer & 0x80) != 0) { 59 | buffer >>= 8; 60 | } else { 61 | queued_time = 0; 62 | return false; 63 | } 64 | } 65 | } 66 | 67 | bool DOOMMus2Mid::write_end_track(PackedByteArray &p_midi_output) { 68 | uint8_t end_track[] = { 0xFF, 0x2F, 0x00 }; 69 | 70 | if (write_time(queued_time, p_midi_output)) { 71 | return true; 72 | } 73 | 74 | if (p_midi_output.append(end_track[0]) || p_midi_output.append(end_track[1]) || p_midi_output.append(end_track[2])) { 75 | return true; 76 | } 77 | 78 | track_size += 3; 79 | return false; 80 | } 81 | 82 | bool DOOMMus2Mid::write_press_key(uint8_t p_channel, uint8_t p_key, uint8_t p_velocity, PackedByteArray &p_midi_output) { 83 | uint8_t working = MidiEvent::MIDI_PRESSKEY | p_channel; 84 | 85 | if (write_time(queued_time, p_midi_output)) { 86 | return true; 87 | } 88 | 89 | if (p_midi_output.append(working)) { 90 | return true; 91 | } 92 | 93 | working = p_key & 0x7F; 94 | if (p_midi_output.append(working)) { 95 | return true; 96 | } 97 | 98 | working = p_velocity & 0x7F; 99 | if (p_midi_output.append(working)) { 100 | return true; 101 | } 102 | 103 | track_size += 3; 104 | 105 | return false; 106 | } 107 | 108 | bool DOOMMus2Mid::write_release_key(uint8_t p_channel, uint8_t p_key, PackedByteArray &p_midi_output) { 109 | uint8_t working = MidiEvent::MIDI_RELEASEKEY | p_channel; 110 | 111 | if (write_time(queued_time, p_midi_output)) { 112 | return true; 113 | } 114 | 115 | if (p_midi_output.append(working)) { 116 | return true; 117 | } 118 | 119 | working = p_key & 0x7F; 120 | if (p_midi_output.append(working)) { 121 | return true; 122 | } 123 | 124 | working = 0; 125 | if (p_midi_output.append(working)) { 126 | return true; 127 | } 128 | 129 | track_size += 3; 130 | 131 | return false; 132 | } 133 | 134 | bool DOOMMus2Mid::write_pitch_wheel(uint8_t p_channel, uint16_t p_wheel, PackedByteArray &p_midi_output) { 135 | uint8_t working = MidiEvent::MIDI_PITCHWHEEL | p_channel; 136 | 137 | if (write_time(queued_time, p_midi_output)) { 138 | return true; 139 | } 140 | 141 | if (p_midi_output.append(working)) { 142 | return true; 143 | } 144 | 145 | working = p_wheel & 0x7F; 146 | if (p_midi_output.append(working)) { 147 | return true; 148 | } 149 | 150 | working = (p_wheel >> 7) & 0x7F; 151 | if (p_midi_output.append(working)) { 152 | return true; 153 | } 154 | 155 | track_size += 3; 156 | 157 | return false; 158 | } 159 | 160 | bool DOOMMus2Mid::write_change_patch(uint8_t p_channel, uint8_t p_patch, PackedByteArray &p_midi_output) { 161 | uint8_t working = MidiEvent::MIDI_CHANGEPATCH | p_channel; 162 | 163 | if (write_time(queued_time, p_midi_output)) { 164 | return true; 165 | } 166 | 167 | if (p_midi_output.append(working)) { 168 | return true; 169 | } 170 | 171 | working = p_patch & 0x7F; 172 | if (p_midi_output.append(working)) { 173 | return true; 174 | } 175 | 176 | track_size += 2; 177 | 178 | return false; 179 | } 180 | 181 | bool DOOMMus2Mid::write_change_controller_valued(uint8_t p_channel, uint8_t p_control, uint8_t p_value, PackedByteArray &p_midi_output) { 182 | uint8_t working = MidiEvent::MIDI_CHANGECONTROLLER | p_channel; 183 | 184 | if (write_time(queued_time, p_midi_output)) { 185 | return true; 186 | } 187 | 188 | if (p_midi_output.append(working)) { 189 | return true; 190 | } 191 | 192 | working = p_control & 0x7F; 193 | if (p_midi_output.append(working)) { 194 | return true; 195 | } 196 | 197 | // Quirk in vanilla DOOM? MUS controller values should be 198 | // 7-bit, not 8-bit. 199 | 200 | working = p_value; // & 0x7F; 201 | 202 | // Fix on said quirk to stop MIDI players from complaining that 203 | // the value is out of range: 204 | 205 | if (working & 0x80) { 206 | working = 0x7F; 207 | } 208 | 209 | if (p_midi_output.append(working)) { 210 | return true; 211 | } 212 | 213 | track_size += 3; 214 | 215 | return false; 216 | } 217 | 218 | bool DOOMMus2Mid::write_change_controller_valueless(uint8_t p_channel, uint8_t p_control, PackedByteArray &p_midi_output) { 219 | return write_change_controller_valued(p_channel, p_control, 0, p_midi_output); 220 | } 221 | 222 | int32_t DOOMMus2Mid::allocate_midi_channel() { 223 | int32_t result; 224 | int32_t max; 225 | int32_t i; 226 | 227 | max = -1; 228 | 229 | for (i = 0; i < NUM_CHANNELS; i++) { 230 | if (channel_map[i] > max) { 231 | max = channel_map[i]; 232 | } 233 | } 234 | 235 | result = max + 1; 236 | 237 | if (result == MIDI_PERCUSSION_CHAN) { 238 | result++; 239 | } 240 | 241 | return result; 242 | } 243 | 244 | int32_t DOOMMus2Mid::get_midi_channel(int32_t p_mus_channel, PackedByteArray &p_midi_output) { 245 | if (p_mus_channel == MUS_PERCUSSION_CHAN) { 246 | return MIDI_PERCUSSION_CHAN; 247 | } 248 | 249 | if (channel_map[p_mus_channel] == -1) { 250 | channel_map[p_mus_channel] = allocate_midi_channel(); 251 | write_change_controller_valueless(channel_map[p_mus_channel], 0x7b, p_midi_output); 252 | } 253 | 254 | return channel_map[p_mus_channel]; 255 | } 256 | 257 | bool DOOMMus2Mid::read_mus_header(PackedByteArray &p_file, DOOMMus2Mid::MusHeader *header) { 258 | if (p_file.size() < sizeof(DOOMMus2Mid::MusHeader)) { 259 | return false; 260 | } 261 | 262 | String header_id = vformat("%c%c%c%c", p_file[0], p_file[1], p_file[2], p_file[3]); 263 | strcpy((char *)header->id, strdup(header_id.utf8().get_data())); 264 | header->score_length = p_file.decode_u16(sizeof(header->id)); 265 | header->score_start = p_file.decode_u16(sizeof(header->id) + sizeof(header->score_length)); 266 | header->primary_channels = p_file.decode_u16(sizeof(header->id) + sizeof(header->score_length) + sizeof(header->score_start)); 267 | header->secondary_channels = p_file.decode_u16(sizeof(header->id) + sizeof(header->score_length) + sizeof(header->score_start) + sizeof(header->primary_channels)); 268 | header->instrument_count = p_file.decode_u16(sizeof(header->id) + sizeof(header->score_length) + sizeof(header->score_start) + sizeof(header->primary_channels) + sizeof(header->secondary_channels)); 269 | 270 | return true; 271 | } 272 | 273 | bool DOOMMus2Mid::mus2mid(PackedByteArray &p_mus_input, PackedByteArray &p_midi_output) { 274 | uint32_t seek = 0; 275 | 276 | MusHeader mus_file_header; 277 | 278 | uint8_t event_descriptor = 0; 279 | int32_t channel = 0; 280 | MusEvent event; 281 | 282 | uint8_t key = 0; 283 | uint8_t controller_number = 0; 284 | uint8_t controller_value = 0; 285 | 286 | uint8_t track_size_buffer[4]; 287 | 288 | int32_t hit_score_end = 0; 289 | 290 | uint8_t working = 0; 291 | 292 | uint32_t time_delay = 0; 293 | 294 | for (channel = 0; channel < NUM_CHANNELS; channel++) { 295 | channel_map[channel] = -1; 296 | } 297 | 298 | if (!read_mus_header(p_mus_input, &mus_file_header)) { 299 | return true; 300 | } 301 | 302 | if (mus_file_header.id[0] != 'M' || mus_file_header.id[1] != 'U' || mus_file_header.id[2] != 'S' || mus_file_header.id[3] != 0x1A) { 303 | return true; 304 | } 305 | 306 | seek = mus_file_header.score_start; 307 | 308 | if (p_mus_input.size() < mus_file_header.score_start) { 309 | return true; 310 | } 311 | 312 | // Write mus_file_header to the output 313 | uint8_t buffer[sizeof(midi_header)]; 314 | memcpy(&buffer, &midi_header, sizeof(midi_header)); 315 | for (int i = 0; i < sizeof(midi_header); i++) { 316 | p_midi_output.append(buffer[i]); 317 | } 318 | 319 | track_size = 0; 320 | 321 | while (!hit_score_end) { 322 | while (!hit_score_end) { 323 | if (p_mus_input.size() < seek + sizeof(event_descriptor)) { 324 | return true; 325 | } 326 | event_descriptor = p_mus_input.decode_u8(seek); 327 | seek += sizeof(event_descriptor); 328 | 329 | channel = get_midi_channel(event_descriptor & 0x0F, p_midi_output); 330 | event = (MusEvent)(event_descriptor & 0x70); 331 | 332 | switch (event) { 333 | case MusEvent::MUS_RELEASEKEY: { 334 | if (p_mus_input.size() < seek + sizeof(key)) { 335 | return true; 336 | } 337 | key = p_mus_input.decode_u8(seek); 338 | seek += sizeof(key); 339 | 340 | if (write_release_key(channel, key, p_midi_output)) { 341 | return true; 342 | } 343 | } break; 344 | 345 | case MusEvent::MUS_PRESSKEY: { 346 | if (p_mus_input.size() < seek + sizeof(key)) { 347 | return true; 348 | } 349 | key = p_mus_input.decode_u8(seek); 350 | seek += sizeof(key); 351 | 352 | if (key & 0x80) { 353 | if (p_mus_input.size() < seek + sizeof(channel_velocities[channel])) { 354 | return true; 355 | } 356 | channel_velocities[channel] = p_mus_input.decode_u8(seek); 357 | seek += sizeof(channel_velocities[channel]); 358 | 359 | channel_velocities[channel] &= 0x7F; 360 | } 361 | 362 | if (write_press_key(channel, key, channel_velocities[channel], p_midi_output)) { 363 | return true; 364 | } 365 | } break; 366 | 367 | case MusEvent::MUS_PITCHWHEEL: { 368 | if (p_mus_input.size() < seek + sizeof(key)) { 369 | return true; 370 | } 371 | key = p_mus_input.decode_u8(seek); 372 | seek += sizeof(key); 373 | 374 | if (write_pitch_wheel(channel, key * 64, p_midi_output)) { 375 | return true; 376 | } 377 | } break; 378 | 379 | case MusEvent::MUS_SYSTEMEVENT: { 380 | if (p_mus_input.size() < seek + sizeof(controller_number)) { 381 | return true; 382 | } 383 | controller_number = p_mus_input.decode_u8(seek); 384 | seek += sizeof(controller_number); 385 | 386 | if (controller_number < 10 || controller_number > 14) { 387 | return true; 388 | } 389 | 390 | if (write_change_controller_valueless(channel, controller_map[controller_number], p_midi_output)) { 391 | return true; 392 | } 393 | } break; 394 | 395 | case MusEvent::MUS_CHANGECONTROLLER: { 396 | if (p_mus_input.size() < seek + sizeof(controller_number)) { 397 | return true; 398 | } 399 | controller_number = p_mus_input.decode_u8(seek); 400 | seek += sizeof(controller_number); 401 | 402 | if (p_mus_input.size() < seek + sizeof(controller_value)) { 403 | return true; 404 | } 405 | controller_value = p_mus_input.decode_u8(seek); 406 | seek += sizeof(controller_value); 407 | 408 | if (controller_number == 0) { 409 | if (write_change_patch(channel, controller_value, p_midi_output)) { 410 | return true; 411 | } 412 | } else { 413 | if (controller_number < 1 || controller_number > 9) { 414 | return true; 415 | } 416 | 417 | if (write_change_controller_valued(channel, controller_map[controller_number], controller_value, p_midi_output)) { 418 | return true; 419 | } 420 | } 421 | } break; 422 | 423 | case MusEvent::MUS_SCOREEND: { 424 | hit_score_end = true; 425 | } break; 426 | 427 | default: { 428 | return true; 429 | } 430 | } 431 | 432 | if (event_descriptor & 0x80) { 433 | break; 434 | } 435 | } 436 | 437 | if (!hit_score_end) { 438 | time_delay = 0; 439 | while (true) { 440 | if (p_mus_input.size() < seek + sizeof(working)) { 441 | return true; 442 | } 443 | working = p_mus_input.decode_u8(seek); 444 | seek += sizeof(working); 445 | 446 | time_delay = time_delay * 128 + (working & 0x7F); 447 | if ((working & 0x80) == 0) { 448 | break; 449 | } 450 | } 451 | queued_time += time_delay; 452 | } 453 | } 454 | 455 | // End of track 456 | if (write_end_track(p_midi_output)) { 457 | return true; 458 | } 459 | 460 | track_size_buffer[0] = (track_size >> 24) & 0xFF; 461 | track_size_buffer[1] = (track_size >> 16) & 0xFF; 462 | track_size_buffer[2] = (track_size >> 8) & 0xFF; 463 | track_size_buffer[3] = track_size & 0xFF; 464 | 465 | p_midi_output.set(18, track_size_buffer[0]); 466 | p_midi_output.set(19, track_size_buffer[1]); 467 | p_midi_output.set(20, track_size_buffer[2]); 468 | p_midi_output.set(21, track_size_buffer[3]); 469 | 470 | return false; 471 | } 472 | 473 | DOOMMus2Mid *DOOMMus2Mid::get_singleton() { 474 | if (singleton == nullptr) { 475 | memnew(DOOMMus2Mid); 476 | } 477 | 478 | return singleton; 479 | } 480 | 481 | DOOMMus2Mid::DOOMMus2Mid() { 482 | singleton = this; 483 | } 484 | 485 | DOOMMus2Mid::~DOOMMus2Mid() { 486 | singleton = nullptr; 487 | } 488 | 489 | DOOMMus2Mid *DOOMMus2Mid::singleton = nullptr; 490 | -------------------------------------------------------------------------------- /src/doommus2mid.h: -------------------------------------------------------------------------------- 1 | #ifndef DOOMMUS2MID_H 2 | #define DOOMMUS2MID_H 3 | 4 | #include "godot_cpp/classes/object.hpp" 5 | #include "godot_cpp/classes/wrapped.hpp" 6 | #include "godot_cpp/variant/packed_byte_array.hpp" 7 | #include 8 | 9 | #define NUM_CHANNELS 16 10 | 11 | #define MIDI_PERCUSSION_CHAN 9 12 | #define MUS_PERCUSSION_CHAN 15 13 | 14 | namespace godot { 15 | class DOOMMus2Mid : public Object { 16 | GDCLASS(DOOMMus2Mid, Object); 17 | 18 | private: 19 | static DOOMMus2Mid *singleton; 20 | 21 | enum MusEvent { 22 | MUS_RELEASEKEY = 0x00, 23 | MUS_PRESSKEY = 0x10, 24 | MUS_PITCHWHEEL = 0x20, 25 | MUS_SYSTEMEVENT = 0x30, 26 | MUS_CHANGECONTROLLER = 0x40, 27 | MUS_SCOREEND = 0x60 28 | }; 29 | 30 | enum MidiEvent { 31 | MIDI_RELEASEKEY = 0x80, 32 | MIDI_PRESSKEY = 0x90, 33 | MIDI_AFTERTOUCHKEY = 0xA0, 34 | MIDI_CHANGECONTROLLER = 0xB0, 35 | MIDI_CHANGEPATCH = 0xC0, 36 | MIDI_AFTERTOUCHCHANNEL = 0xD0, 37 | MIDI_PITCHWHEEL = 0xE0 38 | }; 39 | 40 | struct MusHeader { 41 | uint8_t id[4]; 42 | uint16_t score_length = 0; 43 | uint16_t score_start = 0; 44 | uint16_t primary_channels = 0; 45 | uint16_t secondary_channels = 0; 46 | uint16_t instrument_count = 0; 47 | }; 48 | 49 | // Standard MIDI type 0 header + track header 50 | static constexpr uint8_t midi_header[] = { 51 | 'M', 'T', 'h', 'd', // Main header 52 | 0x00, 0x00, 0x00, 0x06, // Header size 53 | 0x00, 0x00, // MIDI type (0) 54 | 0x00, 0x01, // Number of tracks 55 | 0x00, 0x46, // Resolution 56 | 'M', 'T', 'r', 'k', // Start of track 57 | 0x00, 0x00, 0x00, 0x00 // Placeholder for track length 58 | }; 59 | 60 | uint8_t channel_velocities[NUM_CHANNELS] = { 61 | 127, 127, 127, 127, 127, 127, 127, 127, 62 | 127, 127, 127, 127, 127, 127, 127, 127 63 | }; 64 | 65 | uint32_t queued_time = 0; 66 | uint32_t track_size = 0; 67 | 68 | uint8_t controller_map[15] = { 69 | 0x00, 0x20, 0x01, 0x07, 0x0A, 0x0B, 0x5B, 0x5D, 70 | 0x40, 0x43, 0x78, 0x7B, 0x7E, 0x7F, 0x79 71 | }; 72 | 73 | int32_t channel_map[NUM_CHANNELS]; 74 | 75 | bool write_time(uint32_t p_time, PackedByteArray &p_midi_output); 76 | bool write_end_track(PackedByteArray &p_midi_output); 77 | bool write_press_key(uint8_t p_channel, uint8_t p_key, uint8_t p_velocity, PackedByteArray &p_midi_output); 78 | bool write_release_key(uint8_t p_channel, uint8_t p_key, PackedByteArray &p_midi_output); 79 | bool write_pitch_wheel(uint8_t p_channel, uint16_t p_wheel, PackedByteArray &p_midi_output); 80 | bool write_change_patch(uint8_t p_channel, uint8_t p_patch, PackedByteArray &p_midi_output); 81 | bool write_change_controller_valued(uint8_t p_channel, uint8_t p_control, uint8_t p_value, PackedByteArray &p_midi_output); 82 | bool write_change_controller_valueless(uint8_t p_channel, uint8_t p_control, PackedByteArray &p_midi_output); 83 | int32_t allocate_midi_channel(); 84 | int32_t get_midi_channel(int32_t p_mus_channel, PackedByteArray &p_midi_output); 85 | bool read_mus_header(PackedByteArray &p_file, MusHeader *header); 86 | 87 | protected: 88 | static void _bind_methods(); 89 | 90 | public: 91 | static DOOMMus2Mid *get_singleton(); 92 | 93 | bool mus2mid(PackedByteArray &p_mus_input, PackedByteArray &midi_output); 94 | 95 | DOOMMus2Mid(); 96 | ~DOOMMus2Mid(); 97 | }; 98 | 99 | } //namespace godot 100 | 101 | #endif /* DOOMMUS2MID_H */ 102 | -------------------------------------------------------------------------------- /src/doommutex.h: -------------------------------------------------------------------------------- 1 | #ifndef DOOMMUTEX_H 2 | #define DOOMMUTEX_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #include 9 | #include 10 | 11 | #include "doomcommon.h" 12 | 13 | static inline void mutex_lock(SharedMemory *shm) { 14 | assert(shm != NULL); 15 | while (shm->lock) { 16 | usleep(1); 17 | } 18 | shm->lock = true; 19 | } 20 | 21 | static inline void mutex_unlock(SharedMemory *shm) { 22 | shm->lock = false; 23 | } 24 | 25 | #ifdef __cplusplus 26 | } 27 | #endif 28 | 29 | #endif /* DOOMMUTEX_H */ 30 | -------------------------------------------------------------------------------- /src/doomshm.c: -------------------------------------------------------------------------------- 1 | #ifndef DOOMSHM_H 2 | #define DOOMSHM_H 3 | 4 | #include "doomshm.h" 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | // Shared memory: 11 | #include 12 | #include 13 | #include 14 | 15 | #include "doomcommon.h" 16 | 17 | char shm_base_id[256]; 18 | int shm_fd = 0; 19 | SharedMemory *shm = NULL; 20 | 21 | int init_shm(char *p_id) { 22 | strcpy(shm_base_id, p_id); 23 | 24 | shm_fd = shm_open(shm_base_id, O_RDWR, 0666); 25 | if (shm_fd == -1) { 26 | printf("shm_open failed. %s\n", strerror(errno)); 27 | return -1; 28 | } 29 | 30 | shm = mmap(0, sizeof(SharedMemory), PROT_WRITE, MAP_SHARED, shm_fd, 0); 31 | if (shm == NULL) { 32 | printf("mmap failed. %s\n", strerror(errno)); 33 | return -1; 34 | } 35 | 36 | return 0; 37 | } 38 | 39 | #endif /* DOOMSHM_H */ 40 | -------------------------------------------------------------------------------- /src/doomshm.h: -------------------------------------------------------------------------------- 1 | #ifndef DOOMSHM_H 2 | #define DOOMSHM_H 3 | 4 | #ifdef __cplusplus 5 | extern "C" { 6 | #endif 7 | 8 | #include "doomcommon.h" 9 | 10 | extern char shm_base_id[256]; 11 | extern int shm_fd; 12 | extern SharedMemory *shm; 13 | 14 | int init_shm(char *p_id); 15 | 16 | #ifdef __cplusplus 17 | } 18 | #endif 19 | 20 | #endif /* DOOMSHM_H */ 21 | -------------------------------------------------------------------------------- /src/doomspawn.c: -------------------------------------------------------------------------------- 1 | #include "doomspawn.h" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | 14 | #include "doomgeneric/d_event.h" 15 | #include "doomgeneric/d_mode.h" 16 | #include "doomgeneric/doomgeneric.h" 17 | #include "doomgeneric/doomkeys.h" 18 | #include "doomgeneric/g_game.h" 19 | 20 | #include "doomcommon.h" 21 | #include "doominput.h" 22 | #include "doommutex.h" 23 | #include "doomshm.h" 24 | 25 | boolean terminate = false; 26 | 27 | boolean left_mouse_button_pressed = false; 28 | boolean middle_mouse_button_pressed = false; 29 | boolean right_mouse_button_pressed = false; 30 | 31 | uint32_t local_keys_pressed[UINT8_MAX]; 32 | uint8_t local_keys_pressed_length; 33 | uint32_t local_mouse_buttons_pressed[UINT8_MAX]; 34 | uint8_t local_mouse_buttons_pressed_length; 35 | 36 | int main(int argc, char **argv) { 37 | if (argc < 3) { 38 | printf("Error, missing arguments.\n"); 39 | return EXIT_FAILURE; 40 | } 41 | 42 | if (strcmp(argv[1], "-id") == -1) { 43 | printf("first argument must be `-id`\n"); 44 | return EXIT_FAILURE; 45 | } 46 | 47 | int new_argc = argc - 2; 48 | char **new_argv = malloc(sizeof(char *) * (argc - 2)); 49 | new_argv[0] = strdup(argv[0]); 50 | for (int i = 3; i < argc; i++) { 51 | new_argv[i - 2] = strdup(argv[i]); 52 | } 53 | 54 | int err = init_shm(argv[2]); 55 | if (err < 0) { 56 | exit(EXIT_FAILURE); 57 | } 58 | 59 | doomgeneric_Create(new_argc, new_argv); 60 | 61 | mutex_lock(shm); 62 | shm->init = true; 63 | mutex_unlock(shm); 64 | 65 | while (true) { 66 | if (terminate) { 67 | exit(EXIT_SUCCESS); 68 | } 69 | 70 | if (shm->tick) { 71 | mutex_lock(shm); 72 | shm->tick = false; 73 | mutex_unlock(shm); 74 | tick(); 75 | } 76 | 77 | usleep(10); 78 | } 79 | 80 | return EXIT_SUCCESS; 81 | } 82 | 83 | void tick() { 84 | mutex_lock(shm); 85 | if (shm->terminate) { 86 | mutex_unlock(shm); 87 | terminate = true; 88 | return; 89 | } 90 | mutex_unlock(shm); 91 | 92 | doomgeneric_Tick(); 93 | 94 | if (shm->terminate) { 95 | while (true) { 96 | usleep(10); 97 | } 98 | } 99 | 100 | mutex_lock(shm); 101 | shm->ready = true; 102 | mutex_unlock(shm); 103 | } 104 | 105 | void DG_Init() {} 106 | 107 | void DG_DrawFrame() { 108 | unsigned char screen_buffer[DOOMGENERIC_RESX * DOOMGENERIC_RESY * RGBA]; 109 | memcpy(screen_buffer, DG_ScreenBuffer, DOOMGENERIC_RESX * DOOMGENERIC_RESY * RGBA); 110 | unsigned char buffer[DOOMGENERIC_RESX * DOOMGENERIC_RESY * RGBA]; 111 | for (int i = 0; i < DOOMGENERIC_RESX * DOOMGENERIC_RESY * RGBA; i += RGBA) { 112 | buffer[i + 0] = screen_buffer[i + 2]; 113 | buffer[i + 1] = screen_buffer[i + 1]; 114 | buffer[i + 2] = screen_buffer[i + 0]; 115 | buffer[i + 3] = 255 - screen_buffer[i + 3]; 116 | } 117 | 118 | mutex_lock(shm); 119 | memcpy(shm->screen_buffer, buffer, DOOMGENERIC_RESX * DOOMGENERIC_RESY * RGBA); 120 | mutex_unlock(shm); 121 | } 122 | 123 | void DG_SleepMs(uint32_t ms) { 124 | mutex_lock(shm); 125 | shm->sleep_ms = ms; 126 | mutex_unlock(shm); 127 | 128 | usleep(ms * 1000); 129 | } 130 | 131 | uint32_t DG_GetTicksMs() { 132 | // printf("DG_GetTicksMs: %ld\n", shm->ticks_msec); 133 | // return shm->ticks_msec; 134 | 135 | struct timeval tp; 136 | struct timezone tzp; 137 | 138 | gettimeofday(&tp, &tzp); 139 | 140 | return (tp.tv_sec * 1000) + (tp.tv_usec / 1000); /* return milliseconds */ 141 | } 142 | 143 | int DG_GetKey(int *pressed, unsigned char *key) { 144 | // If the local buffer is empty, fill it 145 | if (shm->keys_pressed_length > 0 && local_keys_pressed_length == 0) { 146 | mutex_lock(shm); 147 | memcpy(local_keys_pressed, shm->keys_pressed, sizeof(local_keys_pressed)); 148 | local_keys_pressed_length = shm->keys_pressed_length; 149 | shm->keys_pressed_length = 0; 150 | mutex_unlock(shm); 151 | } 152 | 153 | if (local_keys_pressed_length > 0) { 154 | uint32_t key_pressed = local_keys_pressed[local_keys_pressed_length - 1]; 155 | *pressed = key_pressed >> 31; 156 | *key = convert_to_doom_key(key_pressed & ~(1 << 31)); 157 | 158 | printf("key %d, pressed: %d\n", *(uint8_t *)key, *pressed); 159 | 160 | local_keys_pressed_length -= 1; 161 | return true; 162 | } 163 | 164 | local_keys_pressed_length = 0; 165 | return false; 166 | } 167 | 168 | void DG_GetMouseState(int *mouse_x, int *mouse_y, int *mouse_button_bitfield) { 169 | *mouse_x = shm->mouse_x; 170 | *mouse_y = shm->mouse_y; 171 | 172 | mutex_lock(shm); 173 | shm->mouse_x = 0; 174 | shm->mouse_y = 0; 175 | mutex_unlock(shm); 176 | 177 | boolean pressed = false; 178 | int8_t index = 0; 179 | 180 | while (shm->mouse_buttons_pressed_length > 0) { 181 | uint32_t mouse_button_pressed = shm->mouse_buttons_pressed[shm->mouse_buttons_pressed_length - 1]; 182 | pressed = mouse_button_pressed >> 31; 183 | index = ~(1 << 31) & mouse_button_pressed; 184 | 185 | switch (index) { 186 | // left 187 | case 1: { 188 | left_mouse_button_pressed = pressed; 189 | } break; 190 | 191 | // right 192 | case 2: { 193 | right_mouse_button_pressed = pressed; 194 | } break; 195 | 196 | // middle 197 | case 3: { 198 | middle_mouse_button_pressed = pressed; 199 | } break; 200 | 201 | default: { 202 | } 203 | } 204 | 205 | mutex_lock(shm); 206 | shm->mouse_buttons_pressed_length -= 1; 207 | mutex_unlock(shm); 208 | } 209 | mutex_lock(shm); 210 | shm->mouse_buttons_pressed_length = 0; 211 | mutex_unlock(shm); 212 | 213 | *mouse_button_bitfield = left_mouse_button_pressed | right_mouse_button_pressed << 1 | middle_mouse_button_pressed << 2; 214 | } 215 | 216 | void DG_SetWindowTitle(const char *title) { 217 | // printf("DG_SetWindowTitle(%s)\n", title); 218 | if (sizeof(title) > sizeof(shm->window_title)) { 219 | printf("WARN: Could not copy window title \"%s\", as it's longer than 255 characters.", title); 220 | return; 221 | } 222 | mutex_lock(shm); 223 | strcpy(shm->window_title, title); 224 | mutex_unlock(shm); 225 | } 226 | 227 | unsigned char convert_to_doom_key(Key p_doom_key) { 228 | switch (p_doom_key) { 229 | case GDDOOM_KEY_A: 230 | case GDDOOM_KEY_B: 231 | case GDDOOM_KEY_C: 232 | case GDDOOM_KEY_D: 233 | case GDDOOM_KEY_E: 234 | case GDDOOM_KEY_F: 235 | case GDDOOM_KEY_G: 236 | case GDDOOM_KEY_H: 237 | case GDDOOM_KEY_I: 238 | case GDDOOM_KEY_J: 239 | case GDDOOM_KEY_K: 240 | case GDDOOM_KEY_L: 241 | case GDDOOM_KEY_M: 242 | case GDDOOM_KEY_N: 243 | case GDDOOM_KEY_O: 244 | case GDDOOM_KEY_P: 245 | case GDDOOM_KEY_Q: 246 | case GDDOOM_KEY_R: 247 | case GDDOOM_KEY_S: 248 | case GDDOOM_KEY_T: 249 | case GDDOOM_KEY_U: 250 | case GDDOOM_KEY_V: 251 | case GDDOOM_KEY_W: 252 | case GDDOOM_KEY_X: 253 | case GDDOOM_KEY_Y: 254 | case GDDOOM_KEY_Z: { 255 | return toupper(p_doom_key); 256 | } 257 | 258 | case GDDOOM_KEY_F1: { 259 | return KEY_F1; 260 | } 261 | case GDDOOM_KEY_F2: { 262 | return KEY_F2; 263 | } 264 | case GDDOOM_KEY_F3: { 265 | return KEY_F3; 266 | } 267 | case GDDOOM_KEY_F4: { 268 | return KEY_F4; 269 | } 270 | case GDDOOM_KEY_F5: { 271 | return KEY_F5; 272 | } 273 | case GDDOOM_KEY_F6: { 274 | return KEY_F6; 275 | } 276 | case GDDOOM_KEY_F7: { 277 | return KEY_F7; 278 | } 279 | case GDDOOM_KEY_F8: { 280 | return KEY_F8; 281 | } 282 | case GDDOOM_KEY_F9: { 283 | return KEY_F9; 284 | } 285 | case GDDOOM_KEY_F10: { 286 | return KEY_F10; 287 | } 288 | case GDDOOM_KEY_F11: { 289 | return KEY_F11; 290 | } 291 | case GDDOOM_KEY_F12: { 292 | return KEY_F12; 293 | } 294 | 295 | case GDDOOM_KEY_ENTER: { 296 | return KEY_ENTER; 297 | } 298 | case GDDOOM_KEY_ESCAPE: { 299 | return KEY_ESCAPE; 300 | } 301 | case GDDOOM_KEY_TAB: { 302 | return KEY_TAB; 303 | } 304 | case GDDOOM_KEY_LEFT: { 305 | return KEY_LEFTARROW; 306 | } 307 | case GDDOOM_KEY_UP: { 308 | return KEY_UPARROW; 309 | } 310 | case GDDOOM_KEY_DOWN: { 311 | return KEY_DOWNARROW; 312 | } 313 | case GDDOOM_KEY_RIGHT: { 314 | return KEY_RIGHTARROW; 315 | } 316 | case GDDOOM_KEY_BACKSPACE: { 317 | return KEY_BACKSPACE; 318 | } 319 | case GDDOOM_KEY_PAUSE: { 320 | return KEY_PAUSE; 321 | } 322 | 323 | case GDDOOM_KEY_SHIFT: { 324 | return KEY_RSHIFT; 325 | } 326 | case GDDOOM_KEY_ALT: { 327 | return KEY_RALT; 328 | } 329 | case GDDOOM_KEY_CTRL: { 330 | return KEY_FIRE; 331 | } 332 | case GDDOOM_KEY_COMMA: { 333 | return KEY_STRAFE_L; 334 | } 335 | case GDDOOM_KEY_PERIOD: { 336 | return KEY_STRAFE_R; 337 | } 338 | case GDDOOM_KEY_SPACE: { 339 | return KEY_USE; 340 | } 341 | case GDDOOM_KEY_PLUS: 342 | case GDDOOM_KEY_MINUS: { 343 | return KEY_MINUS; 344 | } 345 | case GDDOOM_KEY_EQUAL: { 346 | return KEY_EQUALS; 347 | } 348 | 349 | case GDDOOM_KEY_KEY_0: 350 | case GDDOOM_KEY_KEY_1: 351 | case GDDOOM_KEY_KEY_2: 352 | case GDDOOM_KEY_KEY_3: 353 | case GDDOOM_KEY_KEY_4: 354 | case GDDOOM_KEY_KEY_5: 355 | case GDDOOM_KEY_KEY_6: 356 | case GDDOOM_KEY_KEY_7: 357 | case GDDOOM_KEY_KEY_8: 358 | case GDDOOM_KEY_KEY_9: 359 | default: { 360 | return p_doom_key; 361 | } 362 | } 363 | } 364 | -------------------------------------------------------------------------------- /src/doomspawn.h: -------------------------------------------------------------------------------- 1 | #ifndef DOOMSPAWN_H 2 | #define DOOMSPAWN_H 3 | 4 | #include 5 | 6 | #include "doomgeneric/doomtype.h" 7 | 8 | #include "doomcommon.h" 9 | #include "doominput.h" 10 | 11 | extern boolean terminate; 12 | extern boolean start_loop; 13 | 14 | void signal_handler(int signal); 15 | void tick(); 16 | unsigned char convert_to_doom_key(Key p_doom_key); 17 | 18 | #endif /* DOOMSPAWN_H */ 19 | -------------------------------------------------------------------------------- /src/doomspawn_music.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include "doomgeneric/deh_str.h" 4 | #include "doomgeneric/doomtype.h" 5 | #include "doomgeneric/i_sound.h" 6 | #include "doomgeneric/sha1.h" 7 | 8 | #include "doomcommon.h" 9 | #include "doommutex.h" 10 | #include "doomshm.h" 11 | #include "doomspawn.h" 12 | 13 | #define SHA1_LEN 20 14 | #define SHA1_HEX_LEN 41 15 | 16 | typedef struct LumpSha1 { 17 | unsigned char sha1_hex[SHA1_HEX_LEN]; 18 | uint8_t handle; 19 | } LumpSha1; 20 | 21 | static LumpSha1 lump_sha1_list[UINT8_MAX]; 22 | static uint8_t lump_sha1_list_size = 0; 23 | 24 | static void add_instruction(MusicInstruction inst) { 25 | mutex_lock(shm); 26 | MusicInstruction_duplicate(&inst, &shm->music_instructions[shm->music_instructions_length]); 27 | shm->music_instructions_length++; 28 | mutex_unlock(shm); 29 | } 30 | 31 | static boolean get_sha1_hex_from_handle(void *handle, char *buffer) { 32 | uint64_t index = (uint64_t)handle; 33 | if (index >= lump_sha1_list_size) { 34 | return false; 35 | } 36 | LumpSha1 lump = lump_sha1_list[index]; 37 | 38 | memcpy(buffer, lump.sha1_hex, SHA1_HEX_LEN); 39 | 40 | return true; 41 | } 42 | 43 | static char *bin2hex(const unsigned char *bin, size_t len) { 44 | char *out; 45 | size_t i; 46 | 47 | if (bin == NULL || len == 0) 48 | return NULL; 49 | 50 | out = malloc(len * 2 + 1); 51 | for (i = 0; i < len; i++) { 52 | out[i * 2] = "0123456789ABCDEF"[bin[i] >> 4]; 53 | out[i * 2 + 1] = "0123456789ABCDEF"[bin[i] & 0x0F]; 54 | } 55 | out[len * 2] = '\0'; 56 | 57 | return out; 58 | } 59 | 60 | static boolean Godot_InitMusic(void) { 61 | return true; 62 | } 63 | 64 | static void Godot_ShutdownMusic(void) { 65 | MusicInstruction inst; 66 | inst.type = MUSIC_INSTRUCTION_TYPE_SHUTDOWN_MUSIC; 67 | add_instruction(inst); 68 | printf("shutdown song\n"); 69 | } 70 | 71 | static void Godot_SetMusicVolume(int volume) { 72 | MusicInstruction inst; 73 | inst.type = MUSIC_INSTRUCTION_TYPE_SET_MUSIC_VOLUME; 74 | inst.volume = volume; 75 | add_instruction(inst); 76 | } 77 | 78 | static void Godot_PauseSong(void) { 79 | MusicInstruction inst; 80 | inst.type = MUSIC_INSTRUCTION_TYPE_PAUSE_SONG; 81 | add_instruction(inst); 82 | printf("pause song\n"); 83 | } 84 | 85 | static void Godot_ResumeSong(void) { 86 | MusicInstruction inst; 87 | inst.type = MUSIC_INSTRUCTION_TYPE_RESUME_SONG; 88 | add_instruction(inst); 89 | printf("resume song\n"); 90 | } 91 | 92 | static void *Godot_RegisterSong(void *data, int len) { 93 | sha1_context_t context; 94 | sha1_digest_t hash; 95 | 96 | SHA1_Init(&context); 97 | SHA1_Update(&context, data, len); 98 | SHA1_Final(hash, &context); 99 | 100 | LumpSha1 sha; 101 | memcpy(sha.sha1_hex, bin2hex(hash, SHA1_LEN), SHA1_HEX_LEN); 102 | lump_sha1_list[lump_sha1_list_size] = sha; 103 | void *handle = (void *)(uint64_t)lump_sha1_list_size; 104 | lump_sha1_list_size++; 105 | 106 | MusicInstruction inst; 107 | inst.type = MUSIC_INSTRUCTION_TYPE_REGISTER_SONG; 108 | memcpy(inst.lump_sha1_hex, sha.sha1_hex, SHA1_HEX_LEN); 109 | add_instruction(inst); 110 | printf("register song: %s\n", inst.lump_sha1_hex); 111 | 112 | return handle; 113 | } 114 | 115 | static void Godot_UnRegisterSong(void *handle) { 116 | char sha1[SHA1_HEX_LEN]; 117 | 118 | if (!get_sha1_hex_from_handle(handle, sha1)) { 119 | return; 120 | } 121 | 122 | MusicInstruction inst; 123 | inst.type = MUSIC_INSTRUCTION_TYPE_UNREGISTER_SONG; 124 | memcpy(inst.lump_sha1_hex, sha1, SHA1_HEX_LEN); 125 | add_instruction(inst); 126 | printf("unregister song: %s\n", inst.lump_sha1_hex); 127 | } 128 | 129 | static void Godot_PlaySong(void *handle, boolean looping) { 130 | char sha1[SHA1_HEX_LEN]; 131 | 132 | if (!get_sha1_hex_from_handle(handle, sha1)) { 133 | return; 134 | } 135 | 136 | MusicInstruction inst; 137 | inst.type = MUSIC_INSTRUCTION_TYPE_PLAY_SONG; 138 | memcpy(inst.lump_sha1_hex, sha1, SHA1_HEX_LEN); 139 | inst.looping = true; 140 | add_instruction(inst); 141 | printf("play song: %s\n", inst.lump_sha1_hex); 142 | } 143 | 144 | static void Godot_StopSong(void) { 145 | MusicInstruction inst; 146 | inst.type = MUSIC_INSTRUCTION_TYPE_STOP_SONG; 147 | add_instruction(inst); 148 | printf("stop song: %s\n", inst.lump_sha1_hex); 149 | } 150 | 151 | static boolean Godot_MusicIsPlaying(void) { 152 | return true; 153 | } 154 | 155 | static void Godot_PollMusic(void) {} 156 | 157 | static snddevice_t music_sdl_devices[] = { 158 | SNDDEVICE_PAS, 159 | SNDDEVICE_GUS, 160 | SNDDEVICE_WAVEBLASTER, 161 | SNDDEVICE_SOUNDCANVAS, 162 | SNDDEVICE_GENMIDI, 163 | SNDDEVICE_AWE32, 164 | }; 165 | 166 | music_module_t DG_music_module = { 167 | music_sdl_devices, 168 | arrlen(music_sdl_devices), 169 | Godot_InitMusic, 170 | Godot_ShutdownMusic, 171 | Godot_SetMusicVolume, 172 | Godot_PauseSong, 173 | Godot_ResumeSong, 174 | Godot_RegisterSong, 175 | Godot_UnRegisterSong, 176 | Godot_PlaySong, 177 | Godot_StopSong, 178 | Godot_MusicIsPlaying, 179 | Godot_PollMusic, 180 | }; 181 | -------------------------------------------------------------------------------- /src/doomspawn_sound.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | // Shared memory: 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include "doomcommon.h" 10 | #include "doommutex.h" 11 | #include "doomshm.h" 12 | #include "doomspawn.h" 13 | 14 | #include "doomgeneric/deh_str.h" 15 | #include "doomgeneric/i_sound.h" 16 | #include "doomgeneric/m_misc.h" 17 | #include "doomgeneric/w_wad.h" 18 | 19 | #define NUM_CHANNELS 16 20 | 21 | static boolean use_sfx_prefix; 22 | static sfxinfo_t *channels_playing[NUM_CHANNELS]; 23 | 24 | static snddevice_t sound_godot_devices[] = { 25 | SNDDEVICE_SB, 26 | SNDDEVICE_PAS, 27 | SNDDEVICE_GUS, 28 | SNDDEVICE_WAVEBLASTER, 29 | SNDDEVICE_SOUNDCANVAS, 30 | SNDDEVICE_AWE32, 31 | }; 32 | 33 | static void add_instruction(SoundInstruction inst) { 34 | mutex_lock(shm); 35 | SoundInstruction_duplicate(&inst, &shm->sound_instructions[shm->sound_instructions_length]); 36 | shm->sound_instructions_length++; 37 | mutex_unlock(shm); 38 | } 39 | 40 | static void GetSfxLumpName(sfxinfo_t *sfx, char *buf, size_t buf_len) { 41 | // Linked sfx lumps? Get the lump number for the sound linked to. 42 | 43 | if (sfx->link != NULL) { 44 | sfx = sfx->link; 45 | } 46 | 47 | // Doom adds a DS* prefix to sound lumps; Heretic and Hexen don't 48 | // do this. 49 | 50 | if (use_sfx_prefix) { 51 | M_snprintf(buf, buf_len, "ds%s", DEH_String(sfx->name)); 52 | } else { 53 | M_StringCopy(buf, DEH_String(sfx->name), buf_len); 54 | } 55 | } 56 | 57 | // Preload all the sound effects - stops nasty ingame freezes 58 | 59 | static boolean CacheSFX(sfxinfo_t *sfxinfo) { 60 | return true; 61 | } 62 | 63 | static void Godot_PrecacheSounds(sfxinfo_t *sounds, int num_sounds) { 64 | // char namebuf[9]; 65 | 66 | for (int i = 0; i < num_sounds; i++) { 67 | sfxinfo_t *sound = &sounds[i]; 68 | // GetSfxLumpName(sound, namebuf, sizeof(namebuf)); 69 | 70 | SoundInstruction inst; 71 | inst.type = SOUND_INSTRUCTION_TYPE_PRECACHE_SOUND; 72 | // strcpy(inst.name, namebuf); 73 | strcpy(inst.name, sound->name); 74 | inst.pitch = sound->pitch; 75 | inst.volume = sound->volume; 76 | inst.priority = sound->priority; 77 | inst.usefulness = sound->usefulness; 78 | 79 | add_instruction(inst); 80 | } 81 | } 82 | 83 | static boolean Godot_SoundIsPlaying(int handle) { 84 | return true; 85 | } 86 | 87 | static void Godot_StopSound(int handle) { 88 | SoundInstruction inst; 89 | inst.type = SOUND_INSTRUCTION_TYPE_STOP_SOUND; 90 | inst.channel = handle; 91 | 92 | add_instruction(inst); 93 | } 94 | 95 | static int Godot_StartSound(sfxinfo_t *sfxinfo, int channel, int vol, int sep) { 96 | SoundInstruction inst; 97 | inst.type = SOUND_INSTRUCTION_TYPE_START_SOUND; 98 | strcpy(inst.name, sfxinfo->name); 99 | inst.channel = channel; 100 | inst.volume = vol; 101 | inst.sep = sep; 102 | inst.pitch = sfxinfo->pitch; 103 | 104 | add_instruction(inst); 105 | 106 | channels_playing[channel] = sfxinfo; 107 | 108 | return channel; 109 | } 110 | 111 | static void Godot_UpdateSoundParams(int handle, int vol, int sep) { 112 | SoundInstruction inst; 113 | inst.channel = handle; 114 | inst.volume = vol; 115 | inst.sep = sep; 116 | } 117 | 118 | static void Godot_UpdateSound(void) { 119 | } 120 | 121 | static int Godot_GetSfxLumpNum(sfxinfo_t *sfx) { 122 | char namebuf[9]; 123 | 124 | GetSfxLumpName(sfx, namebuf, sizeof(namebuf)); 125 | 126 | return W_GetNumForName(namebuf); 127 | } 128 | 129 | static void Godot_ShutdownSound(void) { 130 | SoundInstruction inst; 131 | inst.type = SOUND_INSTRUCTION_TYPE_SHUTDOWN_SOUND; 132 | add_instruction(inst); 133 | } 134 | 135 | static boolean Godot_InitSound(boolean _use_sfx_prefix) { 136 | use_sfx_prefix = _use_sfx_prefix; 137 | 138 | int i; 139 | for (i = 0; i < NUM_CHANNELS; ++i) { 140 | channels_playing[i] = NULL; 141 | } 142 | 143 | return true; 144 | } 145 | 146 | sound_module_t DG_sound_module = { 147 | sound_godot_devices, 148 | arrlen(sound_godot_devices), 149 | Godot_InitSound, 150 | Godot_ShutdownSound, 151 | Godot_GetSfxLumpNum, 152 | Godot_UpdateSound, 153 | Godot_UpdateSoundParams, 154 | Godot_StartSound, 155 | Godot_StopSound, 156 | Godot_SoundIsPlaying, 157 | Godot_PrecacheSounds, 158 | }; 159 | -------------------------------------------------------------------------------- /src/doomswap.h: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | inline uint16_t swap_16(uint16_t p_value) { 4 | return (((p_value & 0x00FF) << 8) | 5 | ((p_value & 0xFF00) >> 8)); 6 | } 7 | 8 | inline uint32_t swap_32(uint32_t p_value) { 9 | return (((p_value & 0x000000FF) << 24) | 10 | ((p_value & 0x0000FF00) << 8) | 11 | ((p_value & 0x00FF0000) >> 8) | 12 | ((p_value & 0xFF000000) >> 24)); 13 | } 14 | -------------------------------------------------------------------------------- /src/register_types.cpp: -------------------------------------------------------------------------------- 1 | #include "register_types.h" 2 | 3 | #include 4 | 5 | #include "godot_cpp/core/memory.hpp" 6 | #include 7 | #include 8 | #include 9 | 10 | #include "doom.h" 11 | #include "doommus2mid.h" 12 | 13 | using namespace godot; 14 | 15 | void initialize_godot_doom_module(ModuleInitializationLevel p_level) { 16 | if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) { 17 | return; 18 | } 19 | 20 | ClassDB::register_class(); 21 | ClassDB::register_class(); 22 | } 23 | 24 | void uninitialize_godot_doom_module(ModuleInitializationLevel p_level) { 25 | if (p_level != MODULE_INITIALIZATION_LEVEL_SCENE) { 26 | return; 27 | } 28 | 29 | printf("uninitialize_godot_doom_module\n"); 30 | } 31 | 32 | extern "C" { 33 | // initialization 34 | GDExtensionBool GDE_EXPORT godot_doom_library_init(GDExtensionInterfaceGetProcAddress p_get_proc_address, GDExtensionClassLibraryPtr p_library, GDExtensionInitialization *r_initialization) { 35 | godot::GDExtensionBinding::InitObject init_obj(p_get_proc_address, p_library, r_initialization); 36 | 37 | init_obj.register_initializer(initialize_godot_doom_module); 38 | init_obj.register_terminator(uninitialize_godot_doom_module); 39 | init_obj.set_minimum_library_initialization_level(godot::MODULE_INITIALIZATION_LEVEL_SCENE); 40 | 41 | return init_obj.init(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/register_types.h: -------------------------------------------------------------------------------- 1 | #ifndef DOOM_REGISTER_TYPES 2 | #define DOOM_REGISTER_TYPES 3 | 4 | void initialize_godot_doom_module(); 5 | void uninitialize_godot_doom_module(); 6 | 7 | #endif // DOOM_REGISTER_TYPES 8 | --------------------------------------------------------------------------------