From a0720514bb5ca47b983148c83f30fbe8ec166202 Mon Sep 17 00:00:00 2001 From: Tim Osman Date: Mon, 9 Nov 2026 18:09:24 +0000 Subject: [PATCH 1/3] system: fix builds under musl libc --- src/sdl/i_system.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/sdl/i_system.cpp b/src/sdl/i_system.cpp index dc4a53809..66ef3f082 100644 --- a/src/sdl/i_system.cpp +++ b/src/sdl/i_system.cpp @@ -139,10 +139,12 @@ typedef LPVOID (WINAPI *p_MapViewOfFile) (HANDLE, DWORD, DWORD, DWORD, SIZE_T); #endif #if defined (__unix__) || defined(__APPLE__) || defined (UNIXCOMMON) +#if defined (__GLIBC__) // musl doesn't have execinfo #include #include #define UNIXBACKTRACE -#endif +#endif // (__GLIBC__) +#endif // defined (__unix__) || defined(__APPLE__) || defined (UNIXCOMMON) const char *wadDefaultPaths[] = { #if defined (__unix__) || defined(__APPLE__) || defined (UNIXCOMMON) @@ -314,6 +316,8 @@ static void I_ShowErrorMessageBox(const char *messagefordevelopers, dboolean dum "crash-log.txt " #elif defined (_WIN32) ".rpt crash dump " +#else + "" #endif : "", logfilename, @@ -1304,8 +1308,8 @@ void I_SleepDuration(precise_t duration) { duration -= slack; struct timespec ts = { - .tv_sec = static_cast<__time_t>(duration / precision), - .tv_nsec = static_cast<__syscall_slong_t>(duration * 1000000000 / precision % 1000000000), + .tv_sec = duration / precision, + .tv_nsec = duration * 1000000000 / precision % 1000000000, }; int status; #ifdef __OpenBSD__ -- 2.56.0 From d96bd1f23c15a3d33ef29ba8f95bb43c06f754aa Mon Sep 17 00:00:00 2001 From: Tim Osman Date: Mon, 9 Nov 2026 18:09:26 +0000 Subject: [PATCH 2/3] Don't exit game when WAD load errors occur --- src/dehacked.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/dehacked.c b/src/dehacked.c index 766d439a9..a76756fe6 100644 --- a/src/dehacked.c +++ b/src/dehacked.c @@ -936,11 +936,11 @@ static void DEH_LoadDehackedFile(MYFILE *f, dboolean mainfile) if (deh_num_warning) { CONS_Printf(M_GetText("%d warning%s in the SOC lump\n"), deh_num_warning, deh_num_warning == 1 ? "" : "s"); - if (devparm || (numwadfiles > 1 && numwadfiles < NUMMAINWADS)) { - I_Error("%s%s",va(M_GetText("%d warning%s in the SOC lump\n"), deh_num_warning, deh_num_warning == 1 ? "" : "s"), M_GetText("See log.txt for details.\n")); + // if (devparm || (numwadfiles > 1 && numwadfiles < NUMMAINWADS)) { + // I_Error("%s%s",va(M_GetText("%d warning%s in the SOC lump\n"), deh_num_warning, deh_num_warning == 1 ? "" : "s"), M_GetText("See log.txt for details.\n")); //while (!I_GetKey()) //I_OsPolling(); - } + // } } Z_Free(s); -- 2.56.0 From a9a7a145d90ba965e3b218f085102af89da919e9 Mon Sep 17 00:00:00 2001 From: Tim Osman Date: Mon, 9 Nov 2026 18:09:26 +0000 Subject: [PATCH 3/3] Add Zig build system support --- .gitignore | 4 +- build.zig | 776 +++++++++++- build.zig.zon | 69 + deps/gme/build.zig | 119 ++ deps/gme/build.zig.zon | 21 + deps/miniupnpc/build.zig | 58 + deps/miniupnpc/build.zig.zon | 16 + deps/mpg123/build.zig | 553 ++++++++ deps/mpg123/build.zig.zon | 16 + deps/openmpt/build.zig | 312 +++++ deps/openmpt/build.zig.zon | 23 + deps/sndfile/build.zig | 334 +++++ deps/sndfile/build.zig.zon | 44 + deps/sndfile/overlay/src/ogg_opus.c | 1823 +++++++++++++++++++++++++++ deps/xmp/build.zig | 245 ++++ deps/xmp/build.zig.zon | 16 + src/comptime.c | 12 +- src/config.h.in | 5 +- src/core/spmc_queue.hpp | 2 +- src/core/thread_pool.cpp | 2 +- src/d_main.cpp | 2 +- src/dedicated/i_system.cpp | 2 +- src/doomtype.h | 9 +- src/g_game.c | 2 +- src/k_bot.cpp | 2 +- src/k_botitem.cpp | 2 +- src/k_botsearch.cpp | 2 +- src/k_items.c | 2 +- src/k_kart.c | 2 +- src/lua_hooklib.c | 2 +- src/lua_script.c | 2 +- src/p_saveg.c | 2 +- src/p_setup.c | 2 +- src/p_spec.c | 2 +- src/p_tick.c | 2 +- src/p_user.c | 2 +- src/r_bsp.cpp | 2 +- src/r_draw.cpp | 2 +- src/r_draw_column.cpp | 2 +- src/r_draw_span.cpp | 2 +- src/r_plane.cpp | 2 +- src/r_segs.cpp | 2 +- src/sdl/al_sound.c | 2 +- src/sdl/i_system.cpp | 6 +- src/w_wad.c | 3 + src/win32/Srb2win.rc | 6 +- src/z_zone.cpp | 4 +- 47 files changed, 4461 insertions(+), 61 deletions(-) create mode 100644 build.zig.zon create mode 100644 deps/gme/build.zig create mode 100644 deps/gme/build.zig.zon create mode 100644 deps/miniupnpc/build.zig create mode 100644 deps/miniupnpc/build.zig.zon create mode 100644 deps/mpg123/build.zig create mode 100644 deps/mpg123/build.zig.zon create mode 100644 deps/openmpt/build.zig create mode 100644 deps/openmpt/build.zig.zon create mode 100644 deps/sndfile/build.zig create mode 100644 deps/sndfile/build.zig.zon create mode 100644 deps/sndfile/overlay/src/ogg_opus.c create mode 100644 deps/xmp/build.zig create mode 100644 deps/xmp/build.zig.zon diff --git a/.gitignore b/.gitignore index c36abf649..c82865372 100644 --- a/.gitignore +++ b/.gitignore @@ -26,4 +26,6 @@ Win32_LIB_ASM_Release /bin /build /CMakeUserPresets.json -/.zig-cache +.zig-cache +zig-out +zig-pkg diff --git a/build.zig b/build.zig index 062edb673..65b4dc041 100644 --- a/build.zig +++ b/build.zig @@ -1,27 +1,757 @@ +// TODO: maybe generate hashes like `assets/CMakeLists.txt` const std = @import("std"); +const ArrayList = std.ArrayList; + +const rc = @import("rc"); pub fn build(b: *std.Build) void { - const target = b.standardTargetOptions(.{}); - const optimize = b.standardOptimizeOption(.{}); - - // this is just unit tests for now, so the root source file is the tests file - const blankart = b.addModule("blankart", .{ - .root_source_file = b.path("src/tests.zig"), - .target = target, - .optimize = optimize, - }); - - blankart.addIncludePath(b.path("src")); // -Isrc - blankart.linkSystemLibrary("c", .{}); // -lc - blankart.addCSourceFiles(.{ - .root = b.path("src"), - // anything you need to test goes here - .files = &.{ "m_fixed.c", "tables.c" }, - }); - - const blantests = b.addTest(.{ - .root_module = blankart, - }); - const test_step = b.step("test", "Run tests"); - test_step.dependOn(&b.addRunArtifact(blantests).step); + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + // + // build options + // + const opt_disable_video = + b.option(bool, "disable-video", "Disable SDL video implementation") orelse false; + const opt_disable_audio = + b.option(bool, "disable-audio", "Disable audio implementation and use a dummy one") orelse false; + const opt_hw_renderer = + b.option(bool, "enable-hw-renderer", "Enable hardware rendering through OpenGL") orelse !opt_disable_video; + const opt_ogl_fbo = + b.option(bool, "enable-ogl-fbo", "Enable OpenGL FBO Downsampling support") orelse !opt_disable_video; + const opt_debug_mode = + b.option(bool, "debug-mode", "Compile with PARANOIA, ZDEBUG, RANGECHECK and PACKETDROP defined") orelse false; + const opt_mobj_consistency = + b.option(bool, "mobj-consistency", "Compile with MOBJCONSISTANCY defined") orelse false; + const opt_packet_drop = + b.option(bool, "packet-drop", "Compile with PACKETDROP defined") orelse false; + const opt_zdebug = + b.option(bool, "zdebug", "Compile with ZDEBUG defined") orelse false; + + const opt_optimize_for_deps = + b.option(std.builtin.OptimizeMode, "deps-release", "The optimization mode to be used for dependencies") orelse + if (optimize == .debug) .safe else optimize; // if we're building XC as debug, set ReleaseSafe for deps + + if (opt_disable_video and opt_hw_renderer) { + @panic("Cannot disable video and enable hardware renderer, please change one of these options."); + } + + const opt_system_curl = + b.option(bool, "use-system-curl", "Use the system's libcurl") orelse false; + const opt_system_gme = + b.option(bool, "use-system-gme", "Use the system's libgme") orelse false; + const opt_system_openal = + b.option(bool, "use-system-openal", "Use the system's OpenAL") orelse false; + const opt_system_openmpt = + b.option(bool, "use-system-openmpt", "Use the system's libopenmpt") orelse false; + const opt_system_png = + b.option(bool, "use-system-png", "Use the system's libpng") orelse false; + const opt_system_sdl = + b.option(bool, "use-system-sdl", "Use the system's libsdl") orelse false; + const opt_system_sndfile = + b.option(bool, "use-system-sndfile", "Use the system's libsndfile") orelse false; + const opt_system_zlib = + b.option(bool, "use-system-zlib", "Use the system's libzlib") orelse false; + + const opt_commit_hash = + b.option([]const u8, "commit-hash", "The hash of the git commit being built") orelse "unknown-commit"; + const opt_commit_date = + b.option([]const u8, "commit-date", "The git commit's date") orelse "unknown-date"; + const opt_commit_time = + b.option([]const u8, "commit-time", "The git commit's time") orelse "unknown-time"; + const opt_branch_name = + b.option([]const u8, "branch-name", "The hash of the git branch being built") orelse "unknown-branch"; + + // + // generated config headers + // + const gen_config = b.addConfigHeader(.{ + .style = .{ + .cmake = b.path("src/config.h.in"), + }, + }, .{ + .SRB2_COMP_REVISION = opt_commit_hash[0..8], + .SRB2_COMP_BRANCH = opt_branch_name, + .SRB2_COMP_LASTCOMMIT = "", + .SRB2_COMP_DATE = opt_commit_date, + .SRB2_COMP_TIME = opt_commit_time, + }); + + // + // dependencies + // + const dep_curl = b.dependency("curl", .{ + .target = target, + .optimize = opt_optimize_for_deps, + .linkage = .static, + + // use smaller SSL/TLS implementation instead of openssl + .@"use-mbedtls" = true, + // mbedtls has a threading related bug + .@"single-threaded" = true, + + // disable curl dependencies that aren't integrated in the zig fork, + // and that aren't needed by SRB2K anyway + .libpsl = false, + .libssh2 = false, + .libidn2 = false, + .nghttp2 = false, + .@"disable-ldap" = true, + }); + const lib_curl = @import("curl").artifact(dep_curl, .lib); + const dep_gme = b.dependency("gme", .{ + .target = target, + .optimize = opt_optimize_for_deps, + }); + const lib_gme = dep_gme.artifact("gme"); + // TODO: miniupnpc needs to generate a strings header file + // const dep_miniupnpc = b.dependency("miniupnpc", .{ + // .target = target, + // .optimize = opt_optimize_for_deps, + // }); + // const lib_miniupnpc = dep_miniupnpc.artifact("miniupnpc"); + const dep_openal = b.dependency("openal", .{ + .target = target, + .optimize = opt_optimize_for_deps, + .backend = .sdl3, + }); + const lib_openal = dep_openal.artifact("al"); + const dep_openmpt = b.dependency("openmpt", .{ + .target = target, + .optimize = opt_optimize_for_deps, + }); + const lib_openmpt = dep_openmpt.artifact("openmpt"); + const dep_png = b.dependency("png", .{ + .target = target, + .optimize = opt_optimize_for_deps, + }); + const lib_png = dep_png.artifact("png"); + const dep_sdl = b.dependency("sdl", .{ + .target = target, + .optimize = opt_optimize_for_deps, + }); + const lib_sdl = dep_sdl.artifact("SDL3"); + const dep_sndfile = b.dependency("sndfile", .{ + .target = target, + .optimize = opt_optimize_for_deps, + }); + const lib_sndfile = dep_sndfile.artifact("sndfile"); + const dep_zlib = b.dependency("zlib", .{ + .target = target, + .optimize = opt_optimize_for_deps, + }); + const lib_zlib = dep_zlib.artifact("z"); + + const dep_fmt = b.dependency("fmt", .{}); + const dep_imgui = b.dependency("imgui", .{}); + const dep_tcb_span = b.dependency("tcb_span", .{}); + const dep_tracy = b.dependency("tracy", .{}); + const dep_xxhash = b.dependency("xxhash", .{}); + + // + // ACSVM + // + // TODO: build it as a separate Zig package + const lib_acsvm = b.addLibrary(.{ + .name = "ACSVM", + .linkage = .static, + .root_module = b.createModule(.{ + .target = target, + .optimize = optimize, + .link_libc = true, + .link_libcpp = true, + }), + }); + lib_acsvm.root_module.addCSourceFiles(.{ + .root = b.path("src/acs/ACSVM"), + .files = &.{ + // ACSVM + "ACSVM/Action.cpp", + "ACSVM/Array.cpp", + "ACSVM/CallFunc.cpp", + "ACSVM/CodeData.cpp", + "ACSVM/Environment.cpp", + "ACSVM/Error.cpp", + "ACSVM/Function.cpp", + "ACSVM/Init.cpp", + "ACSVM/Jump.cpp", + "ACSVM/Module.cpp", + "ACSVM/ModuleACS0.cpp", + "ACSVM/ModuleACSE.cpp", + "ACSVM/PrintBuf.cpp", + "ACSVM/ProfileData.cpp", + "ACSVM/Scope.cpp", + "ACSVM/Script.cpp", + "ACSVM/Serial.cpp", + "ACSVM/SerialSTD.cpp", + "ACSVM/String.cpp", + "ACSVM/Thread.cpp", + "ACSVM/ThreadExec.cpp", + "ACSVM/Tracer.cpp", + + // Util + "Util/Floats.cpp", + }, + .language = .cpp, + .flags = &.{ + "-DCMAKECONFIG", + "-std=c++17", + }, + }); + lib_acsvm.root_module.addConfigHeader(gen_config); + lib_acsvm.root_module.addIncludePath(b.path("src/acs/ACSVM")); + lib_acsvm.installHeadersDirectory(b.path("src/acs/ACSVM/ACSVM"), "ACSVM", .{ .include_extensions = &.{".hpp"} }); + lib_acsvm.installHeadersDirectory(b.path("src/acs/ACSVM/Util"), "Util", .{ .include_extensions = &.{".hpp"} }); + + // + // fmt + // + // TODO: use a Zig package instead + const lib_fmt = b.addLibrary(.{ + .name = "fmt", + .linkage = .static, + .root_module = b.createModule(.{ + .target = target, + .optimize = optimize, + .link_libc = true, + .link_libcpp = true, + }), + }); + lib_fmt.root_module.addCSourceFiles(.{ + .root = dep_fmt.path("src"), + .files = &.{ + "fmt.cc", + "format.cc", + }, + .language = .cpp, + .flags = &.{"-std=c++20"}, + }); + lib_fmt.root_module.addIncludePath(dep_fmt.path("include")); + lib_fmt.installHeadersDirectory(dep_fmt.path("include"), "", .{}); + + // + // imgui + // + // TODO: use a Zig package instead + const lib_imgui = b.addLibrary(.{ + .name = "imgui", + .linkage = .static, + .root_module = b.createModule(.{ + .target = target, + .optimize = optimize, + .link_libc = true, + .link_libcpp = true, + }), + }); + lib_imgui.root_module.addCSourceFiles(.{ + .root = dep_imgui.path("."), + .files = &.{ + // imgui + "imgui.cpp", + "imgui_demo.cpp", + "imgui_draw.cpp", + "imgui_tables.cpp", + "imgui_widgets.cpp", + + // backend + "backends/imgui_impl_opengl3.cpp", + "backends/imgui_impl_sdl3.cpp", + "backends/imgui_impl_sdlrenderer3.cpp", + }, + .language = .cpp, + .flags = &.{}, + }); + lib_imgui.root_module.addIncludePath(dep_imgui.path(".")); + lib_imgui.installHeadersDirectory(dep_imgui.path("."), "", .{}); + lib_imgui.installHeadersDirectory(dep_imgui.path("backends"), "", .{}); + lib_imgui.root_module.linkLibrary(lib_sdl); + + // + // tracy + // + // TODO: use a Zig package instead + const lib_tracy = b.addLibrary(.{ + .name = "tracy", + .linkage = .static, + .root_module = b.createModule(.{ + .target = target, + .optimize = optimize, + .link_libc = true, + .link_libcpp = true, + }), + }); + lib_tracy.root_module.addCSourceFiles(.{ + .root = dep_tracy.path("public"), + .files = &.{ + "TracyClient.cpp", + }, + .language = .cpp, + .flags = &.{}, + }); + lib_tracy.root_module.addIncludePath(dep_tracy.path("public")); + lib_tracy.installHeadersDirectory(dep_tracy.path("public"), "", .{ .include_extensions = &.{ ".h", ".hpp", ".hmm" } }); + + // + // game executable + // + const exe = b.addExecutable(.{ + .name = "blankart", + + // needed for SDL's loading of OpenGL and X11, + // not needed on headless builds + .linkage = if (opt_disable_video and opt_disable_audio) + .static + else + .dynamic, + + .root_module = b.createModule(.{ + .target = target, + .optimize = optimize, + .link_libc = true, + .link_libcpp = true, + + // standard srb2kart (including neptune) have undefined behavior in code. + // to avoid making many changes in the source code before porting the build system to this one, + // disable all sanitizers. + // FIXME: solve all bugs found with sanitizers + // .sanitize_c = .off, + // .sanitize_thread = false, + }), + }); + + exe.root_module.addConfigHeader(gen_config); + + exe.root_module.addCMacro("CMAKECONFIG", "1"); + exe.root_module.addCMacro("AL_LIBTYPE_STATIC", "1"); + + exe.root_module.addCMacro("_LARGEFILE64_SOURCE", "1"); + exe.root_module.addCMacro("HAVE_BLUA", "1"); + exe.root_module.addCMacro("NOBLUAJIT", "1"); + exe.root_module.addCMacro("CURL_STATICLIB", "1"); + exe.root_module.addCMacro("HAVE_CURL", "1"); + exe.root_module.addCMacro("HAVE_GME", "1"); + // exe.root_module.addCMacro("HAVE_MINIUPNPC", "1"); + exe.root_module.addCMacro("HAVE_OPENMPT", "1"); + exe.root_module.addCMacro("HAVE_PNG", "1"); + exe.root_module.addCMacro("LARGEFILE64_SOURCE", "1"); + exe.root_module.addCMacro("HAVE_OPENAL", "1"); + exe.root_module.addCMacro("HAVE_SDL", "1"); + exe.root_module.addCMacro("HAVE_THREADS", "1"); + exe.root_module.addCMacro("HAVE_ZLIB", "1"); + exe.root_module.addCMacro("USE_STUN", "1"); + + if (target.result.os.tag != .windows) { + exe.root_module.addCMacro("UNIXCOMMON", "1"); + } + + switch (target.result.os.tag) { + else => {}, + .linux => exe.root_module.addCMacro("LINUX", "1"), + .macos => exe.root_module.addCMacro("MACOSX", "1"), + .freebsd => exe.root_module.addCMacro("FREEBSD", "1"), + } + + if (target.result.os.tag == .linux) { + exe.root_module.addCMacro("LINUX", "1"); + } + if (opt_hw_renderer) { + exe.root_module.addCMacro("HWRENDER", "1"); + } + if (opt_ogl_fbo) { + exe.root_module.addCMacro("USE_FBO_OGL", "1"); + } + + if (opt_debug_mode) { + exe.root_module.addCMacro("ZDEBUG", "1"); + exe.root_module.addCMacro("PARANOIA", "1"); + exe.root_module.addCMacro("RANGECHECK", "1"); + exe.root_module.addCMacro("PACKETDROP", "1"); + } + if (opt_mobj_consistency) { + exe.root_module.addCMacro("MOBJCONSISTANCY", "1"); + } + if (opt_packet_drop) { + exe.root_module.addCMacro("PACKETDROP", "1"); + } + if (opt_zdebug) { + exe.root_module.addCMacro("ZDEBUG", "1"); + } + + if (opt_system_curl) { + exe.root_module.linkSystemLibrary("curl", .{}); + } else { + exe.root_module.linkLibrary(lib_curl); + } + if (opt_system_gme) { + exe.root_module.linkSystemLibrary("gme", .{}); + } else { + exe.root_module.linkLibrary(lib_gme); + } + if (opt_system_openmpt) { + exe.root_module.linkSystemLibrary("openmpt", .{}); + } else { + exe.root_module.linkLibrary(lib_openmpt); + } + if (opt_system_png) { + exe.root_module.linkSystemLibrary("png", .{}); + } else { + exe.root_module.linkLibrary(lib_png); + } + if (opt_system_sdl) { + exe.root_module.linkSystemLibrary("SDL3", .{}); + } else { + exe.root_module.linkLibrary(lib_sdl); + if (!opt_disable_audio and !opt_system_openal) { + lib_openal.root_module.linkLibrary(lib_sdl); + } + } + if (opt_system_sndfile) { + exe.root_module.linkSystemLibrary("sndfile", .{}); + } else { + exe.root_module.linkLibrary(lib_sndfile); + } + if (!opt_disable_audio) { + if (opt_system_openal) { + exe.root_module.linkSystemLibrary("openal", .{}); + } else { + exe.root_module.linkLibrary(lib_openal); + } + } + if (opt_system_zlib) { + exe.root_module.linkSystemLibrary("z", .{}); + } else { + exe.root_module.linkLibrary(lib_zlib); + } + // exe.root_module.linkLibrary(lib_miniupnpc); + exe.root_module.linkLibrary(lib_acsvm); + exe.root_module.linkLibrary(lib_fmt); + exe.root_module.linkLibrary(lib_tracy); + exe.root_module.addIncludePath(dep_tcb_span.path("include")); + exe.root_module.addIncludePath(dep_xxhash.path(".")); + if (!opt_disable_video) { + exe.root_module.linkLibrary(lib_imgui); + } + + exe.root_module.addCSourceFiles(.{ + .root = b.path("src"), + .files = &.{ + "comptime.c", + "qs22j.c", + "strbuf.c", + "d_clisrv.c", + "d_net.c", + "d_netfil.c", + "d_netcmd.c", + "dehacked.c", + "deh_soc.c", + "deh_lua.c", + "deh_tables.c", + "f_finale.c", + "f_wipe.c", + "g_demo.c", + "g_game.c", + "g_input.c", + "am_map.c", + "command.c", + "console.c", + "font.c", + "hu_stuff.c", + "i_time.c", + "y_inter.c", + "st_stuff.c", + "m_aatree.c", + "m_anigif.c", + "m_argv.c", + "m_bbox.c", + "m_cheat.c", + "m_cond.c", + "m_easing.c", + "m_fixed.c", + "m_menu.c", + "m_textinput.c", + "m_perfstats.c", + "m_random.c", + "m_queue.c", + "info.c", + "p_ceilng.c", + "p_enemy.c", + "p_floor.c", + "p_inter.c", + "p_lights.c", + "p_map.c", + "p_maputl.c", + "p_mobj.c", + "p_polyobj.c", + "p_saveg.c", + "p_setup.c", + "p_sight.c", + "p_spec.c", + "p_telept.c", + "p_tick.c", + "p_loop.c", + "p_user.c", + "p_slopes.c", + "tables.c", + "r_data.c", + "r_fps.c", + "r_skins.c", + "r_sky.c", + "r_splats.c", + "r_bbox.c", + "r_textures.c", + "r_patch.c", + "r_patchrotation.c", + "r_picformats.c", + "r_portal.c", + "screen.c", + "taglist.c", + "s_sound.c", + "sounds.c", + "w_wad.c", + "filesrch.c", + "mserv.c", + "http-mserv.c", + "i_tcp.c", + "lzf.c", + "lz4.c", + "lua_script.c", + "lua_baselib.c", + "lua_mathlib.c", + "lua_hooklib.c", + "lua_consolelib.c", + "lua_infolib.c", + "lua_mobjlib.c", + "lua_playerlib.c", + "lua_skinlib.c", + "lua_thinkerlib.c", + "lua_maplib.c", + "lua_taglib.c", + "lua_polyobjlib.c", + "lua_blockmaplib.c", + "lua_botvarslib.c", + "lua_hudlib.c", + "lua_hudlib_drawlist.c", + "lua_colorlib.c", + "lua_followerlib.c", + "lua_terrainlib.c", + "lua_waypointslib.c", + "lua_vectorlib.c", + "lua_matrixlib.c", + "lua_quaternionlib.c", + "lua_voicelib.c", + "lua_itemlib.c", + "k_kart.c", + "k_collide.c", + "k_color.c", + "k_battle.c", + "k_pwrlv.c", + "k_pathfind.c", + "k_bheap.c", + "k_items.c", + "k_grandprix.c", + "k_boss.c", + "k_hud.c", + "k_terrain.c", + "k_brightmap.c", + "k_director.c", + "k_follower.c", + "k_mapuser.c", + "k_stats.c", + "k_specialstage.c", + "k_vote.c", + "string.c", + "stun.c", + "lonesha256.c", + + // blan + "blan/internal/b_soc.c", + + // blua + "blua/lapi.c", + "blua/lbaselib.c", + "blua/ldo.c", + "blua/ldblib.c", + "blua/lfunc.c", + "blua/linit.c", + "blua/liolib.c", + "blua/llex.c", + "blua/lmem.c", + "blua/lobject.c", + "blua/lstate.c", + "blua/lstrlib.c", + "blua/ltablib.c", + "blua/lundump.c", + "blua/lzio.c", + "blua/lauxlib.c", + "blua/lcode.c", + "blua/ldebug.c", + "blua/ldump.c", + "blua/lgc.c", + "blua/lopcodes.c", + "blua/lparser.c", + "blua/lstring.c", + "blua/ltable.c", + "blua/ltm.c", + "blua/lvm.c", + "blua/loslib.c", + + // monocypher + "monocypher/monocypher.c", + + // objects + "objects/dash-rings.c", + "objects/sneaker-panel.c", + "objects/neo-spring.c", + + // sdl + "sdl/i_threads.c", + "sdl/i_net.c", + "sdl/i_gamepad.c", + "sdl/i_main.c", + "sdl/dosstr.c", + "sdl/endtxt.c", + "sdl/hwsym_sdl.c", + }, + .flags = &.{"-std=gnu11"}, + .language = .c, + }); + + exe.root_module.addCSourceFiles(.{ + .root = b.path("src"), + .files = &.{ + // main + "d_main.cpp", + "z_zone.cpp", + "f_dscredits.cpp", + "g_party.cpp", + "m_misc.cpp", + "m_emotes.cpp", + "p_mapthing.cpp", + "p_sweep.cpp", + "p_test.cpp", + "p_deepcopy.cpp", + "r_bsp.cpp", + "r_draw.cpp", + "r_main.cpp", + "r_plane.cpp", + "r_segs.cpp", + "r_voicepreference.cpp", + "r_things.cpp", + "r_debug.cpp", + "r_debug_parser.cpp", + "v_video.cpp", + "v_draw.cpp", + "lua_profile.cpp", + "k_waypoint.cpp", + "k_bot.cpp", + "k_botitem.cpp", + "k_botsearch.cpp", + "k_cluster.cpp", + "k_itemlist.cpp", + "k_hud_track.cpp", + "k_hud_message.cpp", + "k_dialogue.cpp", + "h_timers.cpp", + "radio_finish.cpp", + + // acs + "acs/environment.cpp", + "acs/thread.cpp", + "acs/call-funcs.cpp", + "acs/stream.cpp", + "acs/interface.cpp", + + // core + "core/memory.cpp", + "core/thread_pool.cpp", + + // objects + "objects/loops.cpp", + + // sdl + "sdl/i_system.cpp", + "sdl/i_video.cpp", + "sdl/i_imgui.cpp", + }, + .flags = &.{"-std=c++17"}, + .language = .cpp, + }); + + if (!opt_disable_video) { + exe.root_module.addCSourceFiles(.{ + .root = b.path("src/sdl"), + .files = &.{ + "ogl_sdl.cpp", + }, + .flags = &.{"-std=c++17"}, + .language = .cpp, + }); + } + + if (!opt_disable_audio) { + exe.root_module.addCSourceFiles(.{ + .files = &.{ + "src/sdl/al_sound.c", + }, + .flags = &.{"-std=gnu11"}, + .language = .c, + }); + } + + if (opt_hw_renderer) { + exe.root_module.addCSourceFiles(.{ + .root = b.path("src/hardware"), + .files = &.{ + // hardware renderer + "hw_map.c", + "hw_bsp.c", + "hw_draw.c", + "hw_main.c", + "hw_sky.c", + "hw_things.c", + "hw_clip.c", + "hw_md2.c", + "hw_cache.c", + "hw_md2load.c", + "hw_md3load.c", + "hw_model.c", + "u_list.c", + "hw_batching.c", + "hw_drawnodes.c", + "hw_plane.c", + "hw_segs.c", + "hw_shaders.c", + "hw_portal.c", + // opengl + "r_opengl/r_opengl.c", + }, + .flags = &.{"-std=gnu11"}, + .language = .c, + }); + } + + if (target.result.os.tag == .windows) { + const rc_dep = b.dependency("rc", .{}); + var rc_compiler: rc.Compiler = .init(rc_dep, .{ + .rc_files = &.{b.path("src/win32/Srb2win.rc")}, + .target = target, + .optimize = optimize, + }); + exe.root_module.addObjectFile(rc_compiler.getObjectFile()); + + exe.root_module.linkSystemLibrary("secur32", .{}); + } + if (target.result.abi.isMusl()) { + exe.root_module.addCMacro("SRB2_HAVE_STRLCPY", "1"); + } + + b.installArtifact(exe); + + const run_step = b.step("run", "Run SRB2K"); + + const run_cmd = b.addRunArtifact(exe); + run_step.dependOn(&run_cmd.step); + + run_cmd.step.dependOn(b.getInstallStep()); + run_cmd.addPassthruArgs(); } diff --git a/build.zig.zon b/build.zig.zon new file mode 100644 index 000000000..995448c41 --- /dev/null +++ b/build.zig.zon @@ -0,0 +1,69 @@ +.{ + .name = .blankart, + .fingerprint = 0xb10334a9a203adc2, + .version = "1.0.0", + .minimum_zig_version = "0.17.1", + .dependencies = .{ + .curl = .{ + .url = "git+https://github.com/allyourcodebase/curl.git#5184bb9ff29cb0878be7983734c6852602570ea5", + .hash = "curl-8.18.0-mFDAWJ34AADFSd4AEhTLroKwtVlgX9N-T9lU_2qW-VAf", + }, + .fmt = .{ + .url = "https://github.com/fmtlib/fmt/archive/refs/tags/11.2.0.tar.gz", + .hash = "N-V-__8AAKPWLQDA9ng9EpqY8aHMnhNeDQloit7JyPzaGm7z", + }, + .gme = .{ + .path = "deps/gme", + }, + .imgui = .{ + .url = "https://github.com/ocornut/imgui/archive/refs/tags/v1.92.9b.zip", + .hash = "N-V-__8AAPoXegDDrlBN-ExdeEp0KuI2CudplLDeaVyigil4", + }, + .miniupnpc = .{ + .path = "deps/miniupnpc", + }, + .openal = .{ + .url = "git+https://github.com/sorvi-platform/openal-soft.git#d49a5721b50b837e4069b76e7cdbabe378c10174", + .hash = "openal-1.25.0-au8osJggAAAlU6WQAO7AIJqWsIkXHXVamdFz2jvRoudw", + }, + .openmpt = .{ + .path = "deps/openmpt", + }, + .png = .{ + .url = "git+https://github.com/allyourcodebase/libpng#e123eca2140de78f1017a46c24e330fb3024fc58", + .hash = "libpng-1.6.57-oiaFGuYqAAB3CK1mjIzXHn2Z3EVV9okuko-VH23OOSV_", + }, + .sdl = .{ + .url = "git+https://github.com/castholm/SDL.git#b72b3671502a70e109350ecac90afdc480dcfcc6", + .hash = "sdl-0.5.4+3.4.16-SDL--sQ4qAH3kwC19Zs1kxdHplaSDnWl2MUptZD4AItI", + }, + .sndfile = .{ + .path = "deps/sndfile", + }, + .tcb_span = .{ + .url = "git+https://github.com/tcbrindle/span.git#836dc6a0efd9849cb194e88e4aa2387436bb079b", + .hash = "N-V-__8AAFCHCgCaVoODd_KAIhJlZPFyIwnAYiMPu3bK5rW-", + }, + .tracy = .{ + .url = "https://github.com/wolfpld/tracy/archive/refs/tags/v0.15.0.tar.gz", + .hash = "N-V-__8AABliiwINsxd0JlQwa91JejjvuOTL4S_Y6biDBTQG", + }, + .xxhash = .{ + .url = "https://github.com/Cyan4973/xxHash/archive/refs/tags/v0.8.4.tar.gz", + .hash = "N-V-__8AAK4YVADcy-k_G170NQWV8jxfMS6TlhrxRuhfjBHy", + }, + .zlib = .{ + .url = "git+https://github.com/allyourcodebase/zlib.git#d3b5519d3b73616a6bfece0712f080029d2a9244", + .hash = "zlib-1.3.2-ZZQ7lc8NAAAHm9MDfplvwoesXvk4tVm6VCsiI8KnIbT0", + }, + .rc = .{ + .url = "git+https://codeberg.org/ziglang/rc.git#b315438c5a49f7252aa69f4e9e825c859a27252c", + .hash = "rc-0.0.0-rMftL99EAABbc3g55Rier2mxPJfJulJYcYwYnExbIL-m", + }, + }, + .paths = .{ + "build.zig", + "build.zig.zon", + "src", + }, +} diff --git a/deps/gme/build.zig b/deps/gme/build.zig new file mode 100644 index 000000000..e174e44c9 --- /dev/null +++ b/deps/gme/build.zig @@ -0,0 +1,119 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const link_mode = b.option(std.builtin.LinkMode, "link-mode", "Linking mode for the libraries") orelse + .static; + const pic = b.option(bool, "pic", "Produce Position Independent Code"); + const enable_zlib = b.option(bool, "enable-zlib", "Enable zlib file decompression support") orelse true; + + const upstream = b.dependency("gme", .{}); + + const lib = b.addLibrary(.{ + .name = "gme", + .linkage = link_mode, + .root_module = b.createModule(.{ + .target = target, + .optimize = optimize, + .link_libc = true, + .link_libcpp = true, + .pic = pic, + }), + }); + + const base_cflags = [_][]const u8{ + "-DVGM_YM2612_NUKED", + }; + switch (target.result.cpu.arch.endian()) { + .little => lib.root_module.addCMacro("BLARGG_LITTLE_ENDIAN", "1"), + .big => lib.root_module.addCMacro("BLARGG_BIG_ENDIAN", "1"), + } + if (enable_zlib) { + const maybe_dep_zlib = b.lazyDependency("zlib", .{ + .target = target, + .optimize = optimize, + }); + if (maybe_dep_zlib) |dep_zlib| { + const lib_zlib = dep_zlib.artifact("z"); + lib.root_module.linkLibrary(lib_zlib); + lib.root_module.addCMacro("HAVE_ZLIB_H", "1"); + } + } + + lib.root_module.addCSourceFiles(.{ + .root = upstream.path("gme"), + .files = &.{ + "Ay_Apu.cpp", + "Ay_Cpu.cpp", + "Ay_Emu.cpp", + "Blip_Buffer.cpp", + "Classic_Emu.cpp", + "Data_Reader.cpp", + "Dual_Resampler.cpp", + "Effects_Buffer.cpp", + "Fir_Resampler.cpp", + "Gb_Apu.cpp", + "Gb_Cpu.cpp", + "Gb_Oscs.cpp", + "Gbs_Emu.cpp", + "Gme_File.cpp", + "Gym_Emu.cpp", + "Hes_Apu.cpp", + "Hes_Cpu.cpp", + "Hes_Emu.cpp", + "Kss_Cpu.cpp", + "Kss_Emu.cpp", + "Kss_Scc_Apu.cpp", + "M3u_Playlist.cpp", + "Multi_Buffer.cpp", + "Music_Emu.cpp", + "Nes_Apu.cpp", + "Nes_Cpu.cpp", + "Nes_Fds_Apu.cpp", + "Nes_Fme7_Apu.cpp", + "Nes_Namco_Apu.cpp", + "Nes_Oscs.cpp", + "Nes_Vrc6_Apu.cpp", + "Nes_Vrc7_Apu.cpp", + "Nsf_Emu.cpp", + "Nsfe_Emu.cpp", + "Sap_Apu.cpp", + "Sap_Cpu.cpp", + "Sap_Emu.cpp", + "Sms_Apu.cpp", + "Snes_Spc.cpp", + "Spc_Cpu.cpp", + "Spc_Dsp.cpp", + "Spc_Emu.cpp", + "Spc_Filter.cpp", + "Vgm_Emu.cpp", + "Vgm_Emu_Impl.cpp", + "Ym2413_Emu.cpp", + "Ym2612_GENS.cpp", + "Ym2612_MAME.cpp", + "Ym2612_Nuked.cpp", + "gme.cpp", + }, + .flags = &base_cflags, + .language = .cpp, + }); + lib.root_module.addCSourceFiles(.{ + .root = upstream.path("gme/ext"), + .files = &.{ + "emu2413.c", + "panning.c", + }, + .flags = &base_cflags, + }); + + lib.root_module.addIncludePath(upstream.path("gme")); + + lib.installHeadersDirectory( + upstream.path("gme"), + "gme", + .{ .include_extensions = &.{"gme.h"} }, + ); + b.installArtifact(lib); +} diff --git a/deps/gme/build.zig.zon b/deps/gme/build.zig.zon new file mode 100644 index 000000000..5a9579fdf --- /dev/null +++ b/deps/gme/build.zig.zon @@ -0,0 +1,21 @@ +.{ + .name = .gme, + .fingerprint = 0x5f52848a6f2f6c11, + .version = "0.6.4", + .minimum_zig_version = "0.17.0-dev.1387+01b60634c", + .dependencies = .{ + .gme = .{ + .url = "https://github.com/libgme/game-music-emu/archive/refs/tags/0.6.4.tar.gz", + .hash = "N-V-__8AAI7JEQAlAeMvqe7Pe1QybtsPnFgGFSRZzL_9fGZh", + }, + .zlib = .{ + .url = "git+https://github.com/allyourcodebase/zlib.git#d3b5519d3b73616a6bfece0712f080029d2a9244", + .hash = "zlib-1.3.2-ZZQ7lc8NAAAHm9MDfplvwoesXvk4tVm6VCsiI8KnIbT0", + .lazy = true, + }, + }, + .paths = .{ + "build.zig", + "build.zig.zon", + }, +} diff --git a/deps/miniupnpc/build.zig b/deps/miniupnpc/build.zig new file mode 100644 index 000000000..1f5ed89cc --- /dev/null +++ b/deps/miniupnpc/build.zig @@ -0,0 +1,58 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const pic = b.option(bool, "pie", "Produce Position Independent Code"); + + const upstream = b.dependency("miniupnpc", .{}); + + const lib = b.addLibrary(.{ + .name = "miniupnpc", + .linkage = .static, + .root_module = b.createModule(.{ + .target = target, + .optimize = optimize, + .link_libc = true, + .pic = pic, + }), + }); + + const cflags_little = [_][]const u8{ + "-DMINIUPNPC_SET_SOCKET_TIMEOUT", + "-DMINIUPNPC_GET_SRC_ADDR", + }; + const cflags_big = [_][]const u8{ + "-DVGM_YM2612_NUKED", + "-DBLARGG_BIG_ENDIAN=1", + }; + const cflags = if (target.result.cpu.arch.endian() == .little) &cflags_little else &cflags_big; + + lib.root_module.addCSourceFiles(.{ + .root = upstream.path("miniupnpc/src"), + .files = &.{ + "miniwget.c", + "minixml.c", + "igd_desc_parse.c", + "minisoap.c", + "miniupnpc.c", + "upnpreplyparse.c", + "upnpcommands.c", + "upnperrors.c", + "connecthostport.c", + "portlistingparse.c", + "receivedata.c", + "upnpdev.c", + "addr_is_reserved.c", + }, + .flags = cflags, + }); + + lib.root_module.addIncludePath(upstream.path("miniupnpc")); + lib.root_module.addIncludePath(upstream.path("miniupnpc/include")); + lib.root_module.addIncludePath(upstream.path("miniupnpc/src")); + + lib.installHeadersDirectory(upstream.path("miniupnpc/include"), "miniupnpc", .{}); + b.installArtifact(lib); +} diff --git a/deps/miniupnpc/build.zig.zon b/deps/miniupnpc/build.zig.zon new file mode 100644 index 000000000..f7913ca81 --- /dev/null +++ b/deps/miniupnpc/build.zig.zon @@ -0,0 +1,16 @@ +.{ + .name = .miniupnpc, + .fingerprint = 0x9333c0fa68a5a624, + .version = "2.3.3", + .minimum_zig_version = "0.15.2", + .dependencies = .{ + .miniupnpc = .{ + .url = "https://github.com/miniupnp/miniupnp/archive/refs/tags/miniupnpc_2_3_3.tar.gz", + .hash = "N-V-__8AAE-TIgBQMx8H7lhdFVBHDaN4a2GpjuurYVbzXB4e", + }, + }, + .paths = .{ + "build.zig", + "build.zig.zon", + }, +} diff --git a/deps/mpg123/build.zig b/deps/mpg123/build.zig new file mode 100644 index 000000000..3957c15b3 --- /dev/null +++ b/deps/mpg123/build.zig @@ -0,0 +1,553 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) !void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const link_mode = b.option(std.builtin.LinkMode, "link-mode", "Linking mode for the libraries") orelse + .static; + const pic = b.option(bool, "pic", "Produce Position Independent Code"); + const opt_disable_fpu = b.option(bool, "disable-fpu", "Disable CPU floating point unit support") orelse false; + const opt_disable_icy = b.option(bool, "disable-icy", "no ICY metainfo parsing/conversion") orelse false; + const opt_disable_layer1 = b.option(bool, "disable-layer1", "no layer I decoding") orelse false; + const opt_disable_layer2 = b.option(bool, "disable-layer2", "no layer II decoding") orelse false; + const opt_disable_layer3 = b.option(bool, "disable-layer3", "no layer III decoding") orelse false; + const opt_disable_ntom = b.option(bool, "disable-ntom", "no flexible resampling") orelse false; + const opt_disable_16bit = b.option(bool, "disable-16bit", "no 16 bit integer output") orelse false; + const opt_disable_32bit = b.option(bool, "disable-32bit", "no 32 bit integer output (also 24 bit)") orelse false; + const opt_disable_8bit = b.option(bool, "disable-8bit", "no 8 bit integer output") orelse opt_disable_16bit; + const opt_disable_real = b.option(bool, "disable-real", "no real (floating point) output") orelse false; + const opt_disable_string = b.option(bool, "disable-string", "no string API (this will disable ID3v2; main mpg123 won't build anymore)") orelse false; + const opt_disable_feature_report = b.option(bool, "disable-feature-report", "Disable feature report function") orelse false; + const opt_accurate_rounding = b.option(bool, "enable-accurate-rounding", "use rounding instead of fast truncation for integer output, where possible") orelse true; + + const upstream = b.dependency("mpg123", .{}); + + // + // generated config headers + // + const gen_config = b.addConfigHeader(.{ + .style = .{ + .cmake = upstream.path("ports/cmake/src/config.cmake.h.in"), + }, + }, .{ + .PORTABLE_API = 0, + .ACCURATE_ROUNDING = if (opt_accurate_rounding) @as(u32, 1) else null, + .ASMALIGN_BALIGN = 1, + .ASMALIGN_EXP = null, + .ASMALIGN_BYTE = 1, + .CCALIGN = 1, + .DEFAULT_OUTPUT_MODULES = "dummy", + .DEBUG = null, + .DYNAMIC_BUILD = null, + .FIFO = 1, + .FRAME_INDEX = 1, + .GAPLESS = 1, + .HAVE_ATOLL = 1, + .HAVE_DIRENT_H = 1, + .HAVE_DLFCN_H = @intFromBool(target.result.os.tag != .windows), + .HAVE_INTTYPES_H = 1, + .HAVE_LANGINFO_H = 1, + .HAVE_LOCALE_H = 1, + .HAVE_NL_LANGINFO = @intFromBool(target.result.os.tag != .windows), + .HAVE_RANDOM = 1, + .HAVE_SCHED_H = @intFromBool(target.result.os.tag != .windows), + .HAVE_SETLOCALE = @intFromBool(target.result.os.tag != .windows), + .HAVE_USELOCALE = @intFromBool(target.result.os.tag != .windows), + .HAVE_SETPRIORITY = @intFromBool(target.result.os.tag != .windows), + .HAVE_SIGNAL_H = 1, + .HAVE_STRERROR = 1, + .HAVE_STRERROR_L = 1, + .HAVE_STRTOK_R = 1, + .HAVE_STRTOK_S = null, + .HAVE_FORK = 1, + .HAVE_EXECVP = 1, + .HAVE_CTERMID = 1, + .HAVE_CLOCK_GETTIME = 1, + .HAVE_STRINGS_H = 1, + .HAVE_SYS_IOCTL_H = @intFromBool(target.result.os.tag != .windows), + .HAVE_SYS_RESOURCE_H = @intFromBool(target.result.os.tag != .windows), + .HAVE_SYS_SELECT_H = @intFromBool(target.result.os.tag != .windows), + .HAVE_SYS_SIGNAL_H = @intFromBool(target.result.os.tag != .windows), + .HAVE_SYS_STAT_H = 1, + .HAVE_SYS_TIME_H = @intFromBool(target.result.os.tag != .windows), + .HAVE_SYS_TYPES_H = @intFromBool(target.result.os.tag != .windows), + .HAVE_SYS_WAIT_H = @intFromBool(target.result.os.tag != .windows), + + .HAVE_TERMIOS = @intFromBool(target.result.os.tag != .windows), + .HAVE_UNISTD_H = @intFromBool(target.result.os.tag != .windows), + .HAVE_WINDOWS_H = @intFromBool(target.result.os.tag == .windows), + .IEEE_FLOAT = 1, + .WITH_SEEKTABLE = 1, + .IPV6 = 1, + + .CMAKE_SHARED_MODULE_SUFFIX = switch (target.result.os.tag) { + .windows => ".dll", + .macos => ".dylib", + else => ".so", + }, + + .NETWORK = 1, + .NET123 = 1, + .NET123_EXEC = 1, + .NET123_WINHTTP = @intFromBool(target.result.os.tag == .windows), + .NET123_WININET = @intFromBool(target.result.os.tag == .windows), + + .NO_DOWNSAMPLE = null, + .NO_EQUALIZER = null, + .NO_ERETURN = null, + .NO_ERRORMSG = null, + .NO_FEEDER = null, + .NO_ICY = if (opt_disable_icy) @as(u32, 1) else null, + .NO_ID3V2 = null, + .NO_LAYER1 = if (opt_disable_layer1) @as(u32, 1) else null, + .NO_LAYER2 = if (opt_disable_layer2) @as(u32, 1) else null, + .NO_LAYER3 = if (opt_disable_layer3) @as(u32, 1) else null, + .NO_MOREINFO = null, + .NO_NTOM = if (opt_disable_ntom) @as(u32, 1) else null, + .NO_8BIT = if (opt_disable_8bit) @as(u32, 1) else null, + .NO_16BIT = if (opt_disable_16bit) @as(u32, 1) else null, + .NO_32BIT = if (opt_disable_32bit) @as(u32, 1) else null, + .NO_REAL = if (opt_disable_real) @as(u32, 1) else null, + .NO_STRING = if (opt_disable_string) @as(u32, 1) else null, + .NO_SYNTH32 = if (opt_disable_32bit) @as(u32, 1) else null, + .NO_WARNING = null, + + .PROJECT_NAME = "mpg123", + .PROJECT_VERSION = "1.33.7", + .CMAKE_INSTALL_LIBDIR = "lib", + + .MPG123_SIZEOF_OFF_T = target.result.cTypeByteSize(.long).?, + + .STDERR_FILENO = null, + .STDIN_FILENO = null, + .STDOUT_FILENO = null, + + .SYN123_NO_CASES = 1, + .USE_MODULES = 0, + .USE_NEW_HUFFTABLE = 1, + .WANT_WIN32_UNICODE = @intFromBool(target.result.os.tag == .windows), + .WANT_WIN32_FIFO = @intFromBool(target.result.os.tag == .windows), + .WORDS_BIGENDIAN = @intFromBool(target.result.cpu.arch.endian() == .big), + .LFS_LARGEFILE_64 = 0, + .LFS_SENSITIVE = 0, + .HAVE_O_LARGEFILE = 0, + }); + + // library expects these generated headers to be in `src` + const wf_gen = b.addWriteFiles(); + _ = wf_gen.addCopyFile(gen_config.getOutputFile(), "config.h"); + + const lib = b.addLibrary(.{ + .name = "mpg123", + .linkage = link_mode, + .root_module = b.createModule(.{ + .target = target, + .optimize = optimize, + .link_libc = true, + .pic = pic, + }), + }); + + const base_cflags: []const []const u8 = &.{ + "-std=c99", + }; + + lib.root_module.addCSourceFiles(.{ + .root = upstream.path("src/libmpg123"), + .files = &.{ + "parse.c", + "frame.c", + "format.c", + "dct64.c", + "equalizer.c", + "id3.c", + "optimize.c", + "readers.c", + "tabinit.c", + "libmpg123.c", + "index.c", + "lfs_wrap.c", + }, + .flags = base_cflags, + .language = .c, + }); + lib.root_module.addCSourceFiles(.{ + .root = upstream.path("src/compat"), + .files = &.{ + "compat.c", + "compat_str.c", + }, + .flags = base_cflags, + .language = .c, + }); + + if (opt_disable_fpu) { + lib.root_module.addCMacro("REAL_IS_FIXED", "1"); + } else { + lib.root_module.addCMacro("REAL_IS_FLOAT", "1"); + } + + if (target.result.cpu.arch == .x86_64) { + if (opt_disable_fpu) { + lib.root_module.addCMacro("OPT_MULTI", "1"); + lib.root_module.addCMacro("OPT_X86_64", "1"); + lib.root_module.addCMacro("OPT_AVX", "1"); + lib.root_module.addCMacro("OPT_GENERIC", "1"); + lib.root_module.addCMacro("OPT_GENERIC_DITHER", "1"); + lib.root_module.addCSourceFiles(.{ + .root = upstream.path("src/libmpg123"), + .files = &.{ + "dct36_x86_64.S", + "dct64_x86_64_float.S", + "synth_x86_64_float.S", + "synth_x86_64_s32.S", + "synth_stereo_x86_64_float.S", + "synth_stereo_x86_64_s32.S", + "dct36_avx.S", + "dct64_avx_float.S", + "synth_stereo_avx_float.S", + "synth_stereo_avx_s32.S", + "getcpuflags_x86_64.S", + }, + .flags = &.{}, + .language = .assembly_with_preprocessor, + }); + lib.root_module.addCSourceFiles(.{ + .root = upstream.path("src/libmpg123"), + .files = &.{ + "dither.c", + }, + .flags = base_cflags, + .language = .c, + }); + if (opt_accurate_rounding) { + lib.root_module.addCSourceFiles(.{ + .root = upstream.path("src/libmpg123"), + .files = &.{ + "synth_x86_64_accurate.S", + "synth_stereo_x86_64_accurate.S", + "synth_stereo_avx_accurate.S", + }, + .flags = &.{}, + .language = .assembly_with_preprocessor, + }); + } else { + lib.root_module.addCSourceFiles(.{ + .root = upstream.path("src/libmpg123"), + .files = &.{ + "dct64_x86_64.S", + "synth_x86_64.S", + "synth_stereo_x86_64.S", + "synth_stereo_avx.S", + "dct64_avx.S", + }, + .flags = &.{}, + .language = .assembly_with_preprocessor, + }); + } + } else { + lib.root_module.addCMacro("OPT_GENERIC", "1"); + } + } else if (target.result.cpu.arch == .x86) { + lib.root_module.addCMacro("OPT_I386", "1"); + lib.root_module.addCSourceFiles(.{ + .root = upstream.path("src/libmpg123"), + .files = &.{ + "dct64_i386.c", + }, + .flags = base_cflags, + .language = .c, + }); + if (target.result.cpu.has(.x86, .sse)) { + lib.root_module.addCMacro("OPT_SSE", "1"); + + lib.root_module.addCSourceFiles(.{ + .root = upstream.path("src/libmpg123"), + .files = &.{ + "tabinit_mmx.S", + "dct36_sse.S", + "dct64_sse_float.S", + "synth_sse_float.S", + "synth_stereo_sse_float.S", + "synth_sse_s32.S", + "synth_stereo_sse_s32.S", + }, + .flags = &.{}, + .language = .assembly_with_preprocessor, + }); + if (opt_accurate_rounding) { + lib.root_module.addCSourceFiles(.{ + .root = upstream.path("src/libmpg123"), + .files = &.{ + "synth_sse_accurate.S", + "synth_stereo_sse_accurate.S", + }, + .flags = &.{}, + .language = .assembly_with_preprocessor, + }); + } else { + lib.root_module.addCSourceFiles(.{ + .root = upstream.path("src/libmpg123"), + .files = &.{ + "dct64_sse.S", + "synth_sse.S", + }, + .flags = &.{}, + .language = .assembly_with_preprocessor, + }); + } + } + } else if (target.result.cpu.arch.isAarch64()) { + lib.root_module.addCMacro("OPT_MULTI", "1"); + lib.root_module.addCMacro("OPT_GENERIC", "1"); + lib.root_module.addCMacro("OPT_GENERIC_DITHER", "1"); + lib.root_module.addCMacro("OPT_NEON64", "1"); + + lib.root_module.addCSourceFiles(.{ + .root = upstream.path("src/libmpg123"), + .files = &.{ + "dct36_neon64.S", + "dct64_neon64_float.S", + "synth_neon64_float.S", + "synth_neon64_s32.S", + "synth_stereo_neon64_float.S", + "synth_stereo_neon64_s32.S", + "check_neon.S", + }, + .flags = &.{}, + .language = .assembly_with_preprocessor, + }); + if (opt_accurate_rounding) { + lib.root_module.addCSourceFiles(.{ + .root = upstream.path("src/libmpg123"), + .files = &.{ + "synth_neon64_accurate.S", + "synth_stereo_neon64_accurate.S", + }, + .flags = &.{}, + .language = .assembly_with_preprocessor, + }); + } else { + lib.root_module.addCSourceFiles(.{ + .root = upstream.path("src/libmpg123"), + .files = &.{ + "synth_neon64.S", + "synth_stereo_neon64.S", + }, + .flags = &.{}, + .language = .assembly_with_preprocessor, + }); + } + + lib.root_module.addCSourceFiles(.{ + .root = upstream.path("src/libmpg123"), + .files = &.{ + "dither.c", + "getcpuflags_arm.c", + }, + .flags = base_cflags, + .language = .c, + }); + } else if (target.result.cpu.arch.isArm()) { + if (!opt_disable_fpu) { + lib.root_module.addCMacro("OPT_MULTI", "1"); + lib.root_module.addCMacro("OPT_GENERIC", "1"); + lib.root_module.addCMacro("OPT_GENERIC_DITHER", "1"); + lib.root_module.addCMacro("OPT_NEON", "1"); + + lib.root_module.addCSourceFiles(.{ + .root = upstream.path("src/libmpg123"), + .files = &.{ + "dct36_neon.S", + "dct64_neon_float.S", + "synth_neon_float.S", + "synth_neon_s32.S", + "synth_stereo_neon_float.S", + "synth_stereo_neon_s32.S", + "check_neon.S", + }, + .flags = &.{}, + .language = .assembly_with_preprocessor, + }); + if (opt_accurate_rounding) { + lib.root_module.addCSourceFiles(.{ + .root = upstream.path("src/libmpg123"), + .files = &.{ + "synth_neon_accurate.S", + "synth_stereo_neon_accurate.S", + }, + .flags = &.{}, + .language = .assembly_with_preprocessor, + }); + } else { + lib.root_module.addCSourceFiles(.{ + .root = upstream.path("src/libmpg123"), + .files = &.{ + "dct64_neon.S", + "synth_neon.S", + "synth_stereo_neon.S", + }, + .flags = &.{}, + .language = .assembly_with_preprocessor, + }); + } + + lib.root_module.addCSourceFiles(.{ + .root = upstream.path("src/libmpg123"), + .files = &.{ + "dither.c", + "getcpuflags_arm.c", + }, + .flags = base_cflags, + .language = .c, + }); + } else { + lib.root_module.addCMacro("OPT_ARM", "1"); + + if (opt_accurate_rounding) { + lib.root_module.addCSourceFiles(.{ + .root = upstream.path("src/libmpg123"), + .files = &.{ + "synth_arm_accurate.S", + }, + .flags = &.{}, + .language = .assembly_with_preprocessor, + }); + } else { + lib.root_module.addCSourceFiles(.{ + .root = upstream.path("src/libmpg123"), + .files = &.{ + "synth_arm.S", + }, + .flags = &.{}, + .language = .assembly_with_preprocessor, + }); + } + } + } else { + lib.root_module.addCMacro("OPT_GENERIC", "1"); + } + + if (!opt_disable_icy) { + lib.root_module.addCSourceFiles(.{ + .root = upstream.path("src/libmpg123"), + .files = &.{ + "icy.c", + "icy2utf8.c", + }, + .flags = base_cflags, + .language = .c, + }); + } + if (!opt_disable_layer1) { + lib.root_module.addCSourceFiles(.{ + .root = upstream.path("src/libmpg123"), + .files = &.{ + "layer1.c", + }, + .flags = base_cflags, + .language = .c, + }); + } + if (!opt_disable_layer2) { + lib.root_module.addCSourceFiles(.{ + .root = upstream.path("src/libmpg123"), + .files = &.{ + "layer2.c", + }, + .flags = base_cflags, + .language = .c, + }); + } + if (!opt_disable_layer3) { + lib.root_module.addCSourceFiles(.{ + .root = upstream.path("src/libmpg123"), + .files = &.{ + "layer3.c", + }, + .flags = base_cflags, + .language = .c, + }); + } + if (!opt_disable_ntom) { + lib.root_module.addCSourceFiles(.{ + .root = upstream.path("src/libmpg123"), + .files = &.{ + "ntom.c", + }, + .flags = base_cflags, + .language = .c, + }); + } + if (!opt_disable_8bit) { + lib.root_module.addCSourceFiles(.{ + .root = upstream.path("src/libmpg123"), + .files = &.{ + "synth_8bit.c", + }, + .flags = base_cflags, + .language = .c, + }); + } + if (!opt_disable_16bit) { + lib.root_module.addCSourceFiles(.{ + .root = upstream.path("src/libmpg123"), + .files = &.{ + "synth.c", + }, + .flags = base_cflags, + .language = .c, + }); + } + if (!opt_disable_32bit) { + lib.root_module.addCSourceFiles(.{ + .root = upstream.path("src/libmpg123"), + .files = &.{ + "synth_s32.c", + }, + .flags = base_cflags, + .language = .c, + }); + } + if (!opt_disable_real) { + lib.root_module.addCSourceFiles(.{ + .root = upstream.path("src/libmpg123"), + .files = &.{ + "synth_real.c", + }, + .flags = base_cflags, + .language = .c, + }); + } + if (!opt_disable_string) { + lib.root_module.addCSourceFiles(.{ + .root = upstream.path("src/libmpg123"), + .files = &.{ + "stringbuf.c", + }, + .flags = base_cflags, + .language = .c, + }); + } + if (!opt_disable_feature_report) { + lib.root_module.addCSourceFiles(.{ + .root = upstream.path("src/libmpg123"), + .files = &.{ + "feature.c", + }, + .flags = base_cflags, + .language = .c, + }); + } + + lib.root_module.addConfigHeader(gen_config); + + lib.root_module.addIncludePath(upstream.path("src/include")); + lib.root_module.addIncludePath(upstream.path("src/libmpg123")); + lib.root_module.addIncludePath(wf_gen.getDirectory()); + lib.installHeadersDirectory(upstream.path("src/include"), "", .{}); + lib.installHeadersDirectory(wf_gen.getDirectory(), "", .{}); + + b.installArtifact(lib); +} diff --git a/deps/mpg123/build.zig.zon b/deps/mpg123/build.zig.zon new file mode 100644 index 000000000..f2e98f7dc --- /dev/null +++ b/deps/mpg123/build.zig.zon @@ -0,0 +1,16 @@ +.{ + .name = .mpg123, + .fingerprint = 0x2d822892b4249de3, + .version = "1.33.7", + .minimum_zig_version = "0.17.1", + .dependencies = .{ + .mpg123 = .{ + .url = "git+https://github.com/libsdl-org/mpg123#20c59b07437ea6908a5b7bcb9dc43cb2833c0dcd", + .hash = "N-V-__8AAN1JUwAX0Mv9LnmSnjsg67grJ9c3-evyo_jLstWN", + }, + }, + .paths = .{ + "build.zig", + "build.zig.zon", + }, +} diff --git a/deps/openmpt/build.zig b/deps/openmpt/build.zig new file mode 100644 index 000000000..75a216911 --- /dev/null +++ b/deps/openmpt/build.zig @@ -0,0 +1,312 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const pic = b.option(bool, "pie", "Produce Position Independent Code"); + + // + // dependencies + // + const dep_ogg = b.dependency("libogg", .{ + .target = target, + .optimize = optimize, + }); + const lib_ogg = dep_ogg.artifact("ogg"); + const upstream = b.dependency("openmpt", .{}); + + const lib = b.addLibrary(.{ + .name = "openmpt", + .linkage = .static, + .root_module = b.createModule(.{ + .target = target, + .optimize = optimize, + .link_libc = true, + .link_libcpp = true, + .pic = pic, + }), + }); + + const cflags = [_][]const u8{ + "-DLIBOPENMPT_BUILD", + "-DMPT_WITH_MINIZ", + "-DMPT_WITH_MPG123", + "-DMPT_WITH_OGG", + "-DMPT_WITH_VORBIS", + "-DMPT_WITH_ZLIB", + // mpg123 flags + "-DOPT_GENERIC", + // zlib flags + "-DSTDC", + "-DZ_HAVE_UNISTD_H", + }; + + lib.root_module.addCSourceFiles(.{ + .root = upstream.path(""), + .files = &.{ + "common/ComponentManager.cpp", + "common/Logging.cpp", + "common/Profiler.cpp", + "common/mptFileType.cpp", + "common/mptPathString.cpp", + "common/mptRandom.cpp", + "common/mptStringBuffer.cpp", + "common/mptTime.cpp", + "common/serialization_utils.cpp", + "common/version.cpp", + "libopenmpt/libopenmpt_c.cpp", + "libopenmpt/libopenmpt_cxx.cpp", + "libopenmpt/libopenmpt_ext_impl.cpp", + "libopenmpt/libopenmpt_impl.cpp", + "soundlib/AudioCriticalSection.cpp", + "soundlib/ContainerMMCMP.cpp", + "soundlib/ContainerPP20.cpp", + "soundlib/ContainerUMX.cpp", + "soundlib/ContainerXPK.cpp", + "soundlib/Dlsbank.cpp", + "soundlib/Fastmix.cpp", + "soundlib/ITCompression.cpp", + "soundlib/ITTools.cpp", + "soundlib/InstrumentExtensions.cpp", + "soundlib/InstrumentSynth.cpp", + "soundlib/Load_667.cpp", + "soundlib/Load_669.cpp", + "soundlib/Load_amf.cpp", + "soundlib/Load_ams.cpp", + "soundlib/Load_c67.cpp", + "soundlib/Load_cba.cpp", + "soundlib/Load_dbm.cpp", + "soundlib/Load_digi.cpp", + "soundlib/Load_dmf.cpp", + "soundlib/Load_dsm.cpp", + "soundlib/Load_dsym.cpp", + "soundlib/Load_dtm.cpp", + "soundlib/Load_etx.cpp", + "soundlib/Load_far.cpp", + "soundlib/Load_fc.cpp", + "soundlib/Load_fmt.cpp", + "soundlib/Load_ftm.cpp", + "soundlib/Load_gdm.cpp", + "soundlib/Load_gmc.cpp", + "soundlib/Load_gt2.cpp", + "soundlib/Load_ice.cpp", + "soundlib/Load_imf.cpp", + "soundlib/Load_ims.cpp", + "soundlib/Load_it.cpp", + "soundlib/Load_itp.cpp", + "soundlib/Load_kris.cpp", + "soundlib/Load_mdl.cpp", + "soundlib/Load_med.cpp", + "soundlib/Load_mid.cpp", + "soundlib/Load_mo3.cpp", + "soundlib/Load_mod.cpp", + "soundlib/Load_mt2.cpp", + "soundlib/Load_mtm.cpp", + "soundlib/Load_mus_km.cpp", + "soundlib/Load_okt.cpp", + "soundlib/Load_plm.cpp", + "soundlib/Load_psm.cpp", + "soundlib/Load_pt36.cpp", + "soundlib/Load_ptm.cpp", + "soundlib/Load_puma.cpp", + "soundlib/Load_rtm.cpp", + "soundlib/Load_s3m.cpp", + "soundlib/Load_sfx.cpp", + "soundlib/Load_stk.cpp", + "soundlib/Load_stm.cpp", + "soundlib/Load_stp.cpp", + "soundlib/Load_symmod.cpp", + "soundlib/Load_tcb.cpp", + "soundlib/Load_uax.cpp", + "soundlib/Load_ult.cpp", + "soundlib/Load_unic.cpp", + "soundlib/Load_wav.cpp", + "soundlib/Load_xm.cpp", + "soundlib/Load_xmf.cpp", + "soundlib/MIDIEvents.cpp", + "soundlib/MIDIMacroParser.cpp", + "soundlib/MIDIMacros.cpp", + "soundlib/MODTools.cpp", + "soundlib/MPEGFrame.cpp", + "soundlib/Message.cpp", + "soundlib/MixFuncTable.cpp", + "soundlib/MixerLoops.cpp", + "soundlib/MixerSettings.cpp", + "soundlib/ModChannel.cpp", + "soundlib/ModInstrument.cpp", + "soundlib/ModSample.cpp", + "soundlib/ModSequence.cpp", + "soundlib/OPL.cpp", + "soundlib/OggStream.cpp", + "soundlib/Paula.cpp", + "soundlib/PlayState.cpp", + "soundlib/PlaybackTest.cpp", + "soundlib/RowVisitor.cpp", + "soundlib/S3MTools.cpp", + "soundlib/SampleFormatBRR.cpp", + "soundlib/SampleFormatFLAC.cpp", + "soundlib/SampleFormatMP3.cpp", + "soundlib/SampleFormatMediaFoundation.cpp", + "soundlib/SampleFormatOpus.cpp", + "soundlib/SampleFormatSFZ.cpp", + "soundlib/SampleFormatVorbis.cpp", + "soundlib/SampleFormats.cpp", + "soundlib/SampleIO.cpp", + "soundlib/Snd_flt.cpp", + "soundlib/Snd_fx.cpp", + "soundlib/Sndfile.cpp", + "soundlib/Sndmix.cpp", + "soundlib/SoundFilePlayConfig.cpp", + "soundlib/Tables.cpp", + "soundlib/Tagging.cpp", + "soundlib/TinyFFT.cpp", + "soundlib/UMXTools.cpp", + "soundlib/UpgradeModule.cpp", + "soundlib/WAVTools.cpp", + "soundlib/WindowedFIR.cpp", + "soundlib/XMTools.cpp", + "soundlib/load_j2b.cpp", + "soundlib/mod_specifications.cpp", + "soundlib/modcommand.cpp", + "soundlib/modsmp_ctrl.cpp", + "soundlib/pattern.cpp", + "soundlib/patternContainer.cpp", + "soundlib/tuning.cpp", + "soundlib/tuningCollection.cpp", + "soundlib/plugins/DigiBoosterEcho.cpp", + "soundlib/plugins/LFOPlugin.cpp", + "soundlib/plugins/PlugInterface.cpp", + "soundlib/plugins/PluginManager.cpp", + "soundlib/plugins/SymMODEcho.cpp", + "soundlib/plugins/dmo/Chorus.cpp", + "soundlib/plugins/dmo/Compressor.cpp", + "soundlib/plugins/dmo/DMOPlugin.cpp", + "soundlib/plugins/dmo/DMOUtils.cpp", + "soundlib/plugins/dmo/Distortion.cpp", + "soundlib/plugins/dmo/Echo.cpp", + "soundlib/plugins/dmo/Flanger.cpp", + "soundlib/plugins/dmo/Gargle.cpp", + "soundlib/plugins/dmo/I3DL2Reverb.cpp", + "soundlib/plugins/dmo/ParamEq.cpp", + "soundlib/plugins/dmo/WavesReverb.cpp", + "sounddsp/AGC.cpp", + "sounddsp/DSP.cpp", + "sounddsp/EQ.cpp", + "sounddsp/Reverb.cpp", + }, + .flags = &cflags, + .language = .cpp, + }); + lib.root_module.addCSourceFiles(.{ + .root = upstream.path("include/miniz"), + .files = &.{"miniz.c"}, + .flags = &cflags, + }); + lib.root_module.addCSourceFiles(.{ + .root = upstream.path("include/mpg123/src"), + .files = &.{ + "compat/compat.c", + "compat/compat_str.c", + "libmpg123/dct64.c", + "libmpg123/dither.c", + "libmpg123/equalizer.c", + "libmpg123/feature.c", + "libmpg123/format.c", + "libmpg123/frame.c", + "libmpg123/icy.c", + "libmpg123/icy2utf8.c", + "libmpg123/id3.c", + "libmpg123/index.c", + "libmpg123/layer1.c", + "libmpg123/layer2.c", + "libmpg123/layer3.c", + "libmpg123/lfs_wrap.c", + "libmpg123/libmpg123.c", + "libmpg123/ntom.c", + "libmpg123/optimize.c", + "libmpg123/parse.c", + "libmpg123/readers.c", + "libmpg123/stringbuf.c", + "libmpg123/synth.c", + "libmpg123/synth_8bit.c", + "libmpg123/synth_real.c", + "libmpg123/synth_s32.c", + "libmpg123/tabinit.c", + }, + .flags = &cflags, + }); + lib.root_module.addCSourceFiles(.{ + .root = upstream.path("include/vorbis"), + .files = &.{ + "lib/analysis.c", + "lib/bitrate.c", + "lib/block.c", + "lib/codebook.c", + "lib/envelope.c", + "lib/floor0.c", + "lib/floor1.c", + "lib/info.c", + "lib/lookup.c", + "lib/lpc.c", + "lib/lsp.c", + "lib/mapping0.c", + "lib/mdct.c", + "lib/psy.c", + "lib/registry.c", + "lib/res0.c", + "lib/sharedbook.c", + "lib/smallft.c", + "lib/synthesis.c", + "lib/vorbisenc.c", + "lib/vorbisfile.c", + "lib/window.c", + }, + .flags = &cflags, + }); + lib.root_module.addCSourceFiles(.{ + .root = upstream.path("include/zlib"), + .files = &.{ + "adler32.c", + "compress.c", + "crc32.c", + "deflate.c", + "gzclose.c", + "gzlib.c", + "gzread.c", + "gzwrite.c", + "infback.c", + "inffast.c", + "inflate.c", + "inftrees.c", + "trees.c", + "uncompr.c", + "zutil.c", + }, + .flags = &cflags, + }); + + lib.root_module.addIncludePath(upstream.path(".")); + lib.root_module.addIncludePath(upstream.path("common")); + lib.root_module.addIncludePath(upstream.path("src")); + lib.root_module.addIncludePath(upstream.path("include")); + lib.root_module.addIncludePath(upstream.path("include/mpg123/src/include")); + lib.root_module.addIncludePath(upstream.path("include/mpg123/ports/generic")); + lib.root_module.addIncludePath(upstream.path("include/vorbis/include")); + lib.root_module.addIncludePath(upstream.path("include/vorbis/lib")); + lib.root_module.addIncludePath(upstream.path("include/zlib")); + + if (target.result.os.tag != .windows) { + // vorbis flags + lib.root_module.addCMacro("HAVE_ALLOCA_H", "1"); + } + + lib.root_module.linkLibrary(lib_ogg); + + lib.installHeadersDirectory( + upstream.path("libopenmpt"), + "libopenmpt", + .{ .include_extensions = &.{ ".h", ".hpp" } }, + ); + b.installArtifact(lib); +} diff --git a/deps/openmpt/build.zig.zon b/deps/openmpt/build.zig.zon new file mode 100644 index 000000000..15feb1954 --- /dev/null +++ b/deps/openmpt/build.zig.zon @@ -0,0 +1,23 @@ +.{ + .name = .openmpt, + .fingerprint = 0x59c3021e4487e98d, + .version = "0.8.5", + .minimum_zig_version = "0.17.1", + .dependencies = .{ + .mpg123 = .{ + .path = "../mpg123", + }, + .openmpt = .{ + .url = "https://github.com/OpenMPT/openmpt/archive/refs/tags/libopenmpt-0.8.5.tar.gz", + .hash = "N-V-__8AAC4NCAjDOyPIyQuWQaFuHeLffUK_obaJQOm6pHZy", + }, + .libogg = .{ + .url = "git+https://codeberg.org/allyourcodebase/libogg.git#f2c3416feb659251f869439d54fd394f6672d978", + .hash = "libogg-0.2.0+1.3.6-0KP-fj8NAAB9hDsoDSBbZhsDEyJ76xAsn9aY0Hxb-ckR", + }, + }, + .paths = .{ + "build.zig", + "build.zig.zon", + }, +} diff --git a/deps/sndfile/build.zig b/deps/sndfile/build.zig new file mode 100644 index 000000000..875ccd6d1 --- /dev/null +++ b/deps/sndfile/build.zig @@ -0,0 +1,334 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) !void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const link_mode = b.option(std.builtin.LinkMode, "link-mode", "Linking mode for the libraries") orelse + .static; + const pic = b.option(bool, "pic", "Produce Position Independent Code"); + const use_system_flac = b.option(bool, "use-system-flac", "Use the system's libFLAC") orelse false; + const use_system_mp3lame = b.option(bool, "use-system-mp3lame", "Use the system's mp3lame") orelse false; + const use_system_mpg123 = b.option(bool, "use-system-mpg123", "Use the system's mpg123") orelse false; + const use_system_ogg = b.option(bool, "use-system-ogg", "Use the system's libogg") orelse false; + const use_system_opus = b.option(bool, "use-system-opus", "Use the system's Opus") orelse false; + const use_system_vorbis = b.option(bool, "use-system-vorbis", "Use the system's Vorbis") orelse false; + + const upstream = b.dependency("libsndfile", .{}); + + // + // generated config headers + // + const gen_config = b.addConfigHeader(.{ + .style = .{ + .cmake = upstream.path("src/config.h.cmake"), + }, + }, .{ + .COMPILER_IS_GCC = 1, + .CPU_CLIPS_NEGATIVE = 0, + .CPU_CLIPS_POSITIVE = 0, + .CPU_IS_BIG_ENDIAN = @intFromBool(target.result.cpu.arch.endian() == .big), + .CPU_IS_LITTLE_ENDIAN = @intFromBool(target.result.cpu.arch.endian() == .little), + .ENABLE_EXPERIMENTAL_CODE = 0, + .HAVE_ALSA_ASOUNDLIB_H = 0, + .HAVE_BYTESWAP_H = @intFromBool(target.result.os.tag != .windows), + .HAVE_CALLOC = 0, + .HAVE_CEIL = 0, + .HAVE_DECL_S_IRGRP = @intFromBool(target.result.os.tag != .windows), + .HAVE_DLFCN_H = @intFromBool(target.result.os.tag != .windows), + .HAVE_DIRECT_H = @intFromBool(target.result.os.tag == .windows), + .HAVE_ENDIAN_H = 1, + .HAVE_EXTERNAL_XIPH_LIBS = 1, + .HAVE_MPEG = 1, + .HAVE_FLOOR = 0, + .HAVE_FMOD = 0, + .HAVE_FREE = 0, + .HAVE_FSTAT = @intFromBool(target.result.os.tag != .windows), + .HAVE_FSTAT64 = 0, + .HAVE_FSYNC = @intFromBool(target.result.os.tag != .windows), + .HAVE_FTRUNCATE = @intFromBool(target.result.os.tag != .windows), + .HAVE_GETPAGESIZE = @intFromBool(target.result.os.tag != .windows), + .HAVE_GETTIMEOFDAY = @intFromBool(target.result.os.tag != .windows), + .HAVE_GMTIME = @intFromBool(target.result.os.tag != .windows), + .HAVE_GMTIME_R = @intFromBool(target.result.os.tag != .windows), + .HAVE_INTTYPES_H = 1, + .HAVE_IO_H = @intFromBool(target.result.os.tag == .windows), + .HAVE_LIBM = 0, + .HAVE_LOCALE_H = 1, + .HAVE_LOCALTIME = @intFromBool(target.result.os.tag != .windows), + .HAVE_LOCALTIME_R = @intFromBool(target.result.os.tag != .windows), + .HAVE_LRINT = @intFromBool(target.result.os.tag != .windows), + .HAVE_LRINTF = @intFromBool(target.result.os.tag != .windows), + .HAVE_LROUND = 0, + .HAVE_LSEEK = @intFromBool(target.result.os.tag != .windows), + .HAVE_LSEEK64 = @intFromBool(target.result.os.tag != .windows), + .HAVE_MALLOC = 0, + .HAVE_MEMORY_H = @intFromBool(target.result.os.tag == .windows), + .HAVE_MMAP = @intFromBool(target.result.os.tag != .windows), + .HAVE_OPEN = @intFromBool(target.result.os.tag != .windows), + .HAVE_PIPE = @intFromBool(target.result.os.tag != .windows), + .HAVE_READ = @intFromBool(target.result.os.tag != .windows), + .HAVE_REALLOC = 0, + .HAVE_SETLOCALE = @intFromBool(target.result.os.tag != .windows), + .HAVE_SNDIO_H = 0, + .HAVE_SNPRINTF = 0, + .HAVE_SQLITE3 = 0, + .HAVE_SSIZE_T = 1, + .HAVE_STDINT_H = 1, + .HAVE_STDLIB_H = 1, + .HAVE_STRINGS_H = 0, + .HAVE_STRING_H = 1, + .HAVE_SYS_STAT_H = @intFromBool(target.result.os.tag != .windows), + .HAVE_SYS_TIME_H = @intFromBool(target.result.os.tag != .windows), + .HAVE_SYS_TYPES_H = @intFromBool(target.result.os.tag != .windows), + .HAVE_SYS_WAIT_H = @intFromBool(target.result.os.tag != .windows), + .HAVE_UNISTD_H = 1, + .HAVE_IMMINTRIN_H = @intFromBool(target.result.cpu.arch.isX86()), + .HAVE_STDBOOL_H = 1, + .HAVE_VSNPRINTF = 0, + .HAVE_WAITPID = @intFromBool(target.result.os.tag != .windows), + .HAVE_WRITE = @intFromBool(target.result.os.tag != .windows), + + .OS_IS_OPENBSD = target.result.os.tag == .openbsd, + .OS_IS_WIN32 = target.result.os.tag == .windows, + + .USE_SSE2 = @intFromBool(target.result.cpu.has(.x86, .sse2)), + + .PACKAGE_NAME = "libsndfile", + .PACKAGE_BUGREPORT = "", + .PACKAGE_URL = "", + .CPACK_PACKAGE_VERSION_FULL = "1.2.2", + + .SIZEOF_DOUBLE_CODE = b.fmt("#define SIZEOF_DOUBLE_CODE {}", .{target.result.cTypeByteSize(.double).?}), + .SIZEOF_FLOAT_CODE = b.fmt("#define SIZEOF_FLOAT_CODE {}", .{target.result.cTypeByteSize(.float).?}), + .SIZEOF_INT_CODE = b.fmt("#define SIZEOF_INT_CODE {}", .{target.result.cTypeByteSize(.int).?}), + .SIZEOF_INT64_T_CODE = b.fmt("#define SIZEOF_INT64_T_CODE {}", .{target.result.cTypeByteSize(.int).?}), + .SIZEOF_LOFF_T_CODE = b.fmt("#define SIZEOF_LOFF_T_CODE {}", .{target.result.cTypeByteSize(.long).?}), + .SIZEOF_LONG_CODE = b.fmt("#define SIZEOF_LONG_CODE {}", .{target.result.cTypeByteSize(.long).?}), + .SIZEOF_LONG_LONG_CODE = b.fmt("#define SIZEOF_LONG_LONG_CODE {}", .{target.result.cTypeByteSize(.longlong).?}), + .SIZEOF_OFF64_T_CODE = b.fmt("#define SIZEOF_OFF64_T_CODE {}", .{target.result.cTypeByteSize(.longlong).?}), + .SIZEOF_OFF_T_CODE = b.fmt("#define SIZEOF_OFF_T_CODE {}", .{target.result.cTypeByteSize(.long).?}), + .SIZEOF_SHORT_CODE = b.fmt("#define SIZEOF_SHORT_CODE {}", .{target.result.cTypeByteSize(.short).?}), + .SIZEOF_SIZE_T_CODE = b.fmt("#define SIZEOF_SIZE_T_CODE {}", .{target.result.ptrBitWidth() / 8}), + .SIZEOF_SSIZE_T_CODE = b.fmt("#define SIZEOF_SSIZE_T_CODE {}", .{target.result.ptrBitWidth() / 8}), + .SIZEOF_VOIDP_CODE = b.fmt("#define SIZEOF_VOIDP_CODE {}", .{target.result.ptrBitWidth() / 8}), + .SIZEOF_WCHAR_T_CODE = b.fmt("#define SIZEOF_WCHAR_T_CODE {}", .{4}), + + .USE_WINDOWS_API = @intFromBool(target.result.os.tag == .windows), + .PROJECT_VERSION = "1.2.2", + .WIN32_TARGET_DLL = @intFromBool(target.result.os.tag == .windows), + .WORDS_BIGENDIAN = @intFromBool(target.result.cpu.arch.endian() == .big), + ._MINIX = false, + .INLINE_CODE = "", + }); + + const lib = b.addLibrary(.{ + .name = "sndfile", + .linkage = link_mode, + .root_module = b.createModule(.{ + .target = target, + .optimize = optimize, + .link_libc = true, + .pic = pic, + }), + }); + + const base_cflags: [][]const u8 = &.{}; + + lib.root_module.addCSourceFiles(.{ + .root = upstream.path("src"), + .files = &.{ + "common.c", + "file_io.c", + "command.c", + "pcm.c", + "ulaw.c", + "alaw.c", + "float32.c", + "double64.c", + "ima_adpcm.c", + "ms_adpcm.c", + "gsm610.c", + "dwvw.c", + "vox_adpcm.c", + "interleave.c", + "strings.c", + "dither.c", + "cart.c", + "broadcast.c", + "audio_detect.c", + "ima_oki_adpcm.c", + "alac.c", + "chunk.c", + "ogg.c", + "chanmap.c", + "id3.c", + "sndfile.c", + "aiff.c", + "au.c", + "avr.c", + "caf.c", + "dwd.c", + "flac.c", + "g72x.c", + "htk.c", + "ircam.c", + "macos.c", + "mat4.c", + "mat5.c", + "nist.c", + "paf.c", + "pvf.c", + "raw.c", + "rx2.c", + "sd2.c", + "sds.c", + "svx.c", + "txw.c", + "voc.c", + "wve.c", + "w64.c", + "wavlike.c", + "wav.c", + "xi.c", + "mpc2k.c", + "rf64.c", + "ogg_vorbis.c", + "ogg_speex.c", + "ogg_pcm.c", + "ogg_vcomment.c", + "nms_adpcm.c", + "mpeg.c", + "mpeg_decode.c", + "mpeg_l3_encode.c", + "GSM610/add.c", + "GSM610/code.c", + "GSM610/decode.c", + "GSM610/gsm_create.c", + "GSM610/gsm_decode.c", + "GSM610/gsm_destroy.c", + "GSM610/gsm_encode.c", + "GSM610/gsm_option.c", + "GSM610/long_term.c", + "GSM610/lpc.c", + "GSM610/preprocess.c", + "GSM610/rpe.c", + "GSM610/short_term.c", + "GSM610/table.c", + "G72x/g721.c", + "G72x/g723_16.c", + "G72x/g723_24.c", + "G72x/g723_40.c", + "G72x/g72x.c", + "ALAC/ALACBitUtilities.c", + "ALAC/ag_dec.c", + "ALAC/ag_enc.c", + "ALAC/dp_dec.c", + "ALAC/dp_enc.c", + "ALAC/matrix_dec.c", + "ALAC/matrix_enc.c", + "ALAC/alac_decoder.c", + "ALAC/alac_encoder.c", + }, + .flags = base_cflags, + .language = .c, + }); + lib.root_module.addCSourceFiles(.{ + .root = b.path("overlay/src"), + .files = &.{ + "ogg_opus.c", + }, + .flags = base_cflags, + .language = .c, + }); + if (target.result.os.tag == .windows) { + lib.root_module.addCSourceFiles(.{ + .root = upstream.path("src"), + .files = &.{ + "windows.c", + }, + .flags = base_cflags, + .language = .c, + }); + } + + lib.root_module.addConfigHeader(gen_config); + + // + // dependencies + // + if (use_system_flac) { + lib.root_module.linkSystemLibrary("FLAC", .{}); + } else { + const dep_flac = try b.dependencyLazy("libFLAC", .{ + .target = target, + .optimize = optimize, + .pic = pic, + }); + const lib_flac = dep_flac.artifact("FLAC"); + lib.root_module.addCMacro("FLAC__NO_DLL", "1"); + lib.root_module.linkLibrary(lib_flac); + } + + if (use_system_mp3lame) { + lib.root_module.linkSystemLibrary("mp3lame", .{}); + } else { + const dep_mp3lame = try b.dependencyLazy("libmp3lame", .{ + .target = target, + .optimize = optimize, + }); + const lib_mp3lame = dep_mp3lame.artifact("mp3lame"); + lib.root_module.linkLibrary(lib_mp3lame); + } + + if (use_system_mpg123) { + lib.root_module.linkSystemLibrary("mpg123", .{}); + } else { + const dep_mpg123 = b.dependency("mpg123", .{ + .target = target, + .optimize = optimize, + }); + const lib_mpg123 = dep_mpg123.artifact("mpg123"); + lib.root_module.linkLibrary(lib_mpg123); + } + + if (use_system_ogg) { + lib.root_module.linkSystemLibrary("ogg", .{}); + } else { + const dep_ogg = try b.dependencyLazy("libogg", .{ + .target = target, + .optimize = optimize, + }); + const lib_ogg = dep_ogg.artifact("ogg"); + lib.root_module.linkLibrary(lib_ogg); + } + + if (use_system_opus) { + lib.root_module.linkSystemLibrary("opus", .{}); + } else { + const dep_opus = try b.dependencyLazy("opus", .{ + .target = target, + .optimize = optimize, + }); + const lib_opus = dep_opus.artifact("opus"); + lib.root_module.linkLibrary(lib_opus); + } + + if (use_system_vorbis) { + lib.root_module.linkSystemLibrary("vorbis", .{}); + } else { + const dep_vorbis = try b.dependencyLazy("libvorbis", .{ + .target = target, + .optimize = optimize, + .pic = pic, + }); + const lib_vorbis = dep_vorbis.artifact("vorbis"); + lib.root_module.linkLibrary(lib_vorbis); + } + + lib.root_module.addIncludePath(upstream.path("include")); + lib.root_module.addIncludePath(upstream.path("src")); + lib.installHeadersDirectory(upstream.path("include"), "", .{ .include_extensions = &.{ ".h", ".hh" } }); + + b.installArtifact(lib); +} diff --git a/deps/sndfile/build.zig.zon b/deps/sndfile/build.zig.zon new file mode 100644 index 000000000..67f30e487 --- /dev/null +++ b/deps/sndfile/build.zig.zon @@ -0,0 +1,44 @@ +.{ + .name = .sndfile, + .fingerprint = 0xb37b4363c6d83178, + .version = "1.2.2", + .minimum_zig_version = "0.17.1", + .dependencies = .{ + .libsndfile = .{ + .url = "https://github.com/libsndfile/libsndfile/archive/refs/tags/1.2.2.tar.gz", + .hash = "N-V-__8AAHiUMgCAG1bamZYTgFZlJpqi6pyKS857EWZjd2Ov", + }, + .mpg123 = .{ + .path = "../mpg123", + }, + .libFLAC = .{ + .url = "git+https://codeberg.org/allyourcodebase/libFLAC#3ba28bd45a61bc084bb951e441602bea28173854", + .hash = "libFLAC-0.2.1+1.5.0-U66pPIsdAABtVbwDK2o14A8VpFP1R0OtPcfng0Jmy0nv", + .lazy = true, + }, + .libmp3lame = .{ + .url = "git+https://github.com/allyourcodebase/libmp3lame#b2322045e93dc0361006fdc736b9617522b00cd6", + .hash = "libmp3lame-3.100.1-7-67wlF2CxEwDWlVOMMLBNPbnDi5kE2OmH0azUHU2Y4DZY", + .lazy = true, + }, + .libogg = .{ + .url = "git+https://codeberg.org/allyourcodebase/libogg.git#f2c3416feb659251f869439d54fd394f6672d978", + .hash = "libogg-0.2.0+1.3.6-0KP-fj8NAAB9hDsoDSBbZhsDEyJ76xAsn9aY0Hxb-ckR", + .lazy = true, + }, + .libvorbis = .{ + .url = "git+https://codeberg.org/allyourcodebase/libvorbis#410cdd66bd207aef0d248475dbed3cf7ca1eb9d4", + .hash = "libvorbis-0.0.0+1.3.7-1wt9j_QPAADVure94TG4ecBMLD8eIAzWd6ZxVOmBBPCu", + .lazy = true, + }, + .opus = .{ + .url = "git+https://github.com/allyourcodebase/opus.git?ref=0.17-dev#7fc2bef07389ad5731d670099774e8368f83f61b", + .hash = "opus-0.0.2-vuF-cPiQAABH1lEhaTs-C3DF8-YrJ4AAcKInLdVTwKKP", + .lazy = true, + }, + }, + .paths = .{ + "build.zig", + "build.zig.zon", + }, +} diff --git a/deps/sndfile/overlay/src/ogg_opus.c b/deps/sndfile/overlay/src/ogg_opus.c new file mode 100644 index 000000000..f5857df97 --- /dev/null +++ b/deps/sndfile/overlay/src/ogg_opus.c @@ -0,0 +1,1823 @@ +/* +** Copyright (C) 2013-2020 Erik de Castro Lopo +** Copyright (C) 2018 Arthur Taylor +** +** This program is free software ; you can redistribute it and/or modify +** it under the terms of the GNU Lesser General Public License as published by +** the Free Software Foundation ; either version 2.1 of the License, or +** (at your option) any later version. +** +** This program is distributed in the hope that it will be useful, +** but WITHOUT ANY WARRANTY ; without even the implied warranty of +** MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +** GNU Lesser General Public License for more details. +** +** You should have received a copy of the GNU Lesser General Public License +** along with this program ; if not, write to the Free Software +** Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. +*/ + +/* +** This file contains code based on OpusFile and Opus-Tools, both by +** Xiph.Org. COPYING from each is identical and is as follows: +** +** Copyright (c) 1994-2013 Xiph.Org Foundation and contributors +** +** Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions +** are met: +** +** - Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** +** - Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in the +** documentation and/or other materials provided with the distribution. +** +** - Neither the name of the Xiph.Org Foundation nor the names of its +** contributors may be used to endorse or promote products derived from +** this software without specific prior written permission. +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION +** OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +*/ + +/* +** TODO: +** - Channel mapping modification / reporting +** - connect psf->channel_map and Opus channel mapping somehow? +** - Gain parameters and their mappings +*/ + +/* +** Opus Sample, Frame, and Samples/Channel Terminology +** +** libsndfile refers to one PCM value as a 'sample,' and a group of samples of +** the same sample time, one for each channel, as a 'frame.' This differs from +** Opus, which has no corresponding name for sample, and refers to a group of +** PCM values, one per channel (aka libsndfile frames) as 'samples.' +** Further, Opus has an object called a 'frame' that is made up of multiple +** Opus-samples. +** All this means that one has to be careful with what is meant by each term. +** In an attempt to avoid ambiguity, this file adopts the following terms: +** - Samples shall refer to discrete PCM values, regardless of any channel +** considerations. This is the same as what libsndfile calls samples. +** - Samples/channel shall refer to groups of samples, one for each channel. +** This is what Opus calles samples, and what libsndfile calles frames. It +** has the advantage that its name is also the formula to calculate it. +** +** +** Opus vs OggOpus +** +** In this file a distinction is made between Opus and OggOpus. Opus refers to +** the codec alone, support for which is by libopus. OggOpus refers to an Opus +** payload encapsulated in an Ogg stream. This is also know as an "Opus file." +** The OggOpus spec includes information on header and granule position +** interpretation, which is outside of the scope of the Opus spec. As such, an +** attempt here is made to refer to either Opus or OggOpus depending on which +** spec is being referenced. See https://wiki.xiph.org/OggOpus +** +** +** Opus Sample Rates +** +** Opus only supports a fixed number of sample rates: 48kHz, 24kHz, 16kHz, +** 12kHz, 8kHz. Audio may be decoded or encoded at any of these rates, +** independent of the rate it was encoded at or to be decoded at respectively. +** Other sample rates must be converted to one of these rates. +** +** As 44.1kHz (CD sample rate) and 22.5kHz are popular sample rates, and to +** support any other sample rate there may be, the Opus header includes a field +** to save the input (original) sample rate before converting it to a supported +** one. Implementations are recommended by the Opus spec to do a sample rate +** conversion at encode, but decode at 48kHz if outputting to hardware, or do +** the reverse sample rate conversion if outputting to file. +** +** Heretofore libsndfile does not contain a sample rate converter, so doing the +** sample rate conversion is not supported. Instead audio must be provided by +** the user at a supported rate. However, the input sample rate field can be +** set and retrieved by the user using sf_command(). At decode we choose to +** decode at the lowest valid rate that is greater than or equal to the input +** sample rate. +** +** +** OggOpus Granule Positions +** +** Ogg streams include a strictly increasing granule position value. The +** interpretation of this value is dependent on the payload type. For Opus +** streams the granule position is the count of samples in the stream when +** encoding/decoding at 48kHz. Note that the actual position of the output +** sample relative to the granule position is offset by the preskip amount. +** That is, if a packet ends with a granule position of x, the last sample +** output when decoding is actually sample (x - preskip). +** +** Further, to allow for clipping off of the front of a stream without +** rewriting all following granule positions, an Opus stream granule position +** may be offset by a constant amount. This amount is evident by comparing the +** granule position of the first page of an Opus stream on which an audio +** packet completes is greater than the sum of the samples of all audio +** packets completed on the page. Only the first such page is allows to have an +** 'excessive' granule position, and only if it is not also the last page of +** the stream (e_o_s bit is not set.) +** +** The granule position is an unsigned 64-bit integer, with the special value +** of UINT64_MAX/-1 being treated as invalid. However, as not all platforms +** support unsigned 64-bit integers, libOgg uses signed 64-bit integers for the +** granule position. +** +** Remembering that signed integer overflow/underflow is explicitly undefined +** in C, and as we already assume support for unsigned 64-bit integers, the +** easiest way to deal with this problem is to modify granule positions as +** unsigned integers. +*/ + + +#include "sfconfig.h" + +#include +#include +#include +#include +#include +#include + +#if HAVE_UNISTD_H +#include +#else +#include "sf_unistd.h" +#endif + +#include "sndfile.h" +#include "sfendian.h" +#include "common.h" + +#if HAVE_EXTERNAL_XIPH_LIBS + +#include +#include +#include + +#include "ogg.h" +#include "ogg_vcomment.h" + +#define OGG_OPUS_COMMENT_PAD (512) /* Same as oggenc default */ + +/* +** When encoding, we can choose the size of the Opus frames. +** Valid values are 2.5, 5, 10, 20, 40, and 60 milliseconds. +** +** Frames smaller than 10ms can't use CELT (MDCT) mode. +** Frames larger than 20ms "are only interesting at fairly low bitrates." +** +** We choose the suggested default of 20ms for high-fidelity audio, however, +** maybe this could be user-selected, or triggered by bitrate command. +** default for non-realtime of 20ms. While longer packets reduce the overhead +** data somewhat, it also decreases the quality. +*/ +#define OGG_OPUS_ENCODE_PACKET_LEN(samplerate) ((20 * (samplerate)) / 1000) + +/* +** The pre-roll is how long it takes for the decoder to converge. It converges +** pretty quickly, to within -40db within 80ms. However, this also depends on +** the signal. From experimentation, use the conservative pre-roll amount of +** 660ms after which the output is 32-bit-exact with high probability. +*/ +#define OGG_OPUS_PREROLL (660 * 48) /* 660 milliseconds (33 packets of 20ms) */ + +typedef struct +{ uint8_t version ; + + /* Number of channels, 1...255 */ + uint8_t channels ; + + /* Encoder latency, the amount to skip before valid data comes out. */ + uint16_t preskip ; + + /* The sample rate of a the encoded source, as it may have been converted. */ + int32_t input_samplerate ; + + /* 'baked-in' gain to apply, dB S7.8 format. Should be zero when possible. */ + int16_t gain ; + + /* Channel mapping type. See OggOpus spec */ + uint8_t channel_mapping ; + + /* The rest is only used if channel_mapping != 0 */ + /* How many streams are there? */ + uint8_t nb_streams ; + + /* How man of those streams are coupled? (aka stereo) */ + uint8_t nb_coupled ; + + /* Mapping of opus streams to output channels */ + uint8_t stream_map [255] ; +} OpusHeader ; + +typedef struct +{ uint32_t serialno ; + OpusHeader header ; + + /* Encode: Granule position after the previous packet. + * Decode: Granule position after the current packet */ + uint64_t pkt_pos ; + + /* Encode: Granule position at the end of the previous page. + * Decode: Granule position at the end of the current page. */ + uint64_t pg_pos ; + + /* integer coefficient of (current sample rate) / 48000Hz */ + int sr_factor ; + + /* Current position in buffer expressed as samples/channel */ + int loc ; + + /* Current data fill (decode) or target (encode) of buffer expressed in samples/channel */ + int len ; + + /* Size of the buffer storage, in sizeof (float) * channels */ + int buffersize ; + + /* Samples, either decoded from a packet, or assembling for encode. */ + float *buffer ; + + union { + /* decode only members */ + struct { + OpusMSDecoder *state ; + uint64_t gp_start ; + uint64_t gp_end ; + sf_count_t last_offset ; + } decode ; + + /* encode only members */ + struct { + OpusMSEncoder *state ; + + /* How many Ogg page segments are in Ogg page currently being assembled. */ + int last_segments ; + + int bitrate ; + unsigned long latency ; + + /* Least significant bit of the source (aka bitwidth) */ + int lsb ; + int lsb_last ; + } encode ; + } u ; +} OPUS_PRIVATE ; + +/*----------------------------------------------------------------------------------------------- +** Private function prototypes. +*/ + +static int ogg_opus_close (SF_PRIVATE *psf) ; +static void opus_print_header (SF_PRIVATE *psf, OpusHeader *h) ; +static int opus_read_header_packet (SF_PRIVATE *psf, OpusHeader *h, ogg_packet *opacket) ; +static int ogg_opus_read_header (SF_PRIVATE * psf) ; +static int ogg_opus_setup_decoder (SF_PRIVATE *psf, int input_samplerate) ; + +static int ogg_opus_setup_encoder (SF_PRIVATE *psf, OGG_PRIVATE *odata, OPUS_PRIVATE *oopus) ; +static int ogg_opus_write_header (SF_PRIVATE * psf, int calc_length) ; +static void ogg_opus_flush (SF_PRIVATE *psf) ; +static int ogg_opus_unpack_next_page (SF_PRIVATE *psf, OGG_PRIVATE *odata, OPUS_PRIVATE *oopus) ; +static int ogg_opus_calculate_page_duration (OGG_PRIVATE *odata) ; +static int ogg_opus_read_refill (SF_PRIVATE *psf, OGG_PRIVATE *odata, OPUS_PRIVATE *oopus) ; +static int ogg_opus_write_out (SF_PRIVATE *psf, OGG_PRIVATE *odata, OPUS_PRIVATE *oopus) ; + +static sf_count_t ogg_opus_read_s (SF_PRIVATE *psf, short *ptr, sf_count_t len) ; +static sf_count_t ogg_opus_read_i (SF_PRIVATE *psf, int *ptr, sf_count_t len) ; +static sf_count_t ogg_opus_read_f (SF_PRIVATE *psf, float *ptr, sf_count_t len) ; +static sf_count_t ogg_opus_read_d (SF_PRIVATE *psf, double *ptr, sf_count_t len) ; + +static sf_count_t ogg_opus_write_s (SF_PRIVATE *psf, const short *ptr, sf_count_t len) ; +static sf_count_t ogg_opus_write_i (SF_PRIVATE *psf, const int *ptr, sf_count_t len) ; +static sf_count_t ogg_opus_write_f (SF_PRIVATE *psf, const float *ptr, sf_count_t len) ; +static sf_count_t ogg_opus_write_d (SF_PRIVATE *psf, const double *ptr, sf_count_t len) ; + +static sf_count_t ogg_opus_seek (SF_PRIVATE *psf, int mode, sf_count_t offset) ; +static sf_count_t ogg_opus_null_read (SF_PRIVATE *psf, sf_count_t offset) ; +static sf_count_t ogg_opus_page_seek_manual (SF_PRIVATE *psf, uint64_t target_gp) ; +static int ogg_opus_page_seek_search (SF_PRIVATE *psf, uint64_t target_gp) ; + +static int ogg_opus_analyze_file (SF_PRIVATE *psf) ; +static int ogg_opus_command (SF_PRIVATE *psf, int command, void *data, int datasize) ; +static int ogg_opus_byterate (SF_PRIVATE *psf) ; + +/*----------------------------------------------------------------------------------------------- +*/ + +static vorbiscomment_ident opustags_ident = { "OpusTags", 8 } ; + +/*----------------------------------------------------------------------------------------------- +** Exported functions. +*/ + +int +ogg_opus_open (SF_PRIVATE *psf) +{ OGG_PRIVATE* odata = psf->container_data ; + OPUS_PRIVATE* oopus = calloc (1, sizeof (OPUS_PRIVATE)) ; + int error = 0 ; + + if (odata == NULL) + { psf_log_printf (psf, "%s : odata is NULL???\n", __func__) ; + free (oopus) ; + return SFE_INTERNAL ; + } ; + + psf->codec_data = oopus ; + if (oopus == NULL) + return SFE_MALLOC_FAILED ; + + if (psf->file.mode == SFM_RDWR) + return SFE_BAD_MODE_RW ; + + psf_log_printf (psf, "Opus library version: %s\n", opus_get_version_string ()) ; + + psf->codec_close = ogg_opus_close ; + if (psf->file.mode == SFM_READ) + { if ((error = ogg_opus_read_header (psf))) + return error ; + if ((error = ogg_opus_analyze_file (psf))) + return error ; + + psf->read_short = ogg_opus_read_s ; + psf->read_int = ogg_opus_read_i ; + psf->read_float = ogg_opus_read_f ; + psf->read_double = ogg_opus_read_d ; + } ; + + if (psf->file.mode == SFM_WRITE) + { if ((error = ogg_opus_setup_encoder (psf, odata, oopus))) + return error ; + + psf->write_header = ogg_opus_write_header ; + psf->write_short = ogg_opus_write_s ; + psf->write_int = ogg_opus_write_i ; + psf->write_float = ogg_opus_write_f ; + psf->write_double = ogg_opus_write_d ; + + psf->sf.frames = SF_COUNT_MAX ; /* Unknown really */ + psf->strings.flags = SF_STR_ALLOW_START ; + psf->datalength = 0 ; + psf->dataoffset = 0 ; /* will be updated */ + } ; + + psf->seek = ogg_opus_seek ; + psf->command = ogg_opus_command ; + psf->byterate = ogg_opus_byterate ; + psf->sf.format = SF_FORMAT_OGG | SF_FORMAT_OPUS ; + + return error ; +} /* ogg_opus_open */ + +/*============================================================================== +** Private functions. +*/ + +static int +ogg_opus_close (SF_PRIVATE *psf) +{ OGG_PRIVATE *odata = (OGG_PRIVATE *) psf->container_data ; + OPUS_PRIVATE *oopus = (OPUS_PRIVATE *) psf->codec_data ; + + if (!oopus) + return 0 ; + + if (psf->file.mode == SFM_WRITE) + { if (psf->have_written) + ogg_opus_flush (psf) ; + else { + /* Write a header... it is expected. */ + ogg_opus_write_header (psf, 0) ; + } ; + ogg_packet_clear (&odata->opacket) ; + if (oopus->u.encode.state) + { opus_multistream_encoder_destroy (oopus->u.encode.state) ; + oopus->u.encode.state = NULL ; + } ; + } + else if (psf->file.mode == SFM_READ) + { if (oopus->u.decode.state) + { opus_multistream_decoder_destroy (oopus->u.decode.state) ; + oopus->u.decode.state = NULL ; + } ; + } ; + + psf->codec_data = NULL ; + if (oopus->buffer) + free (oopus->buffer) ; + free (oopus) ; + + return 0 ; +} /* ogg_opus_close */ + +static void +opus_print_header (SF_PRIVATE *psf, OpusHeader *h) +{ psf_log_printf (psf, "Opus Header Metadata\n") ; + psf_log_printf (psf, " OggOpus version : %d\n", (int) h->version) ; + psf_log_printf (psf, " Channels : %d\n", (int) h->channels) ; + psf_log_printf (psf, " Preskip : %d samples @48kHz\n", (int) h->preskip) ; + psf_log_printf (psf, " Input Samplerate : %d Hz\n", (int) h->input_samplerate) ; + psf_log_printf (psf, " Gain : %d.%d\n", (int) arith_shift_right (h->gain & 0xF0, 8), h->gain & 0x0F) ; + psf_log_printf (psf, " Channel Mapping : ") ; + switch (h->channel_mapping) + { case 0 : psf_log_printf (psf, "0 (mono or stereo)\n") ; break ; + case 1 : psf_log_printf (psf, "1 (surround, AC3 channel order)\n") ; break ; + case 255 : psf_log_printf (psf, "255 (no channel order)\n") ; break ; + default : psf_log_printf (psf, "%d (unknown or unsupported)\n", (int) h->channel_mapping) ; break ; + } ; + + if (h->channel_mapping > 0) + { int i ; + psf_log_printf (psf, " streams total : %d\n", (int) h->nb_streams) ; + psf_log_printf (psf, " streams coupled : %d\n", (int) h->nb_coupled) ; + psf_log_printf (psf, " stream mapping : [") ; + for (i = 0 ; i < h->channels - 1 ; i++) + psf_log_printf (psf, "%d,", (int) (h->stream_map [i])) ; + psf_log_printf (psf, "%d]\n", (int) (h->stream_map [i])) ; + } ; +} /* opus_print_header */ + +static int +opus_read_header_packet (SF_PRIVATE *psf, OpusHeader *h, ogg_packet *opacket) +{ int count, i ; + + /* + ** Opus headers are 19 bytes, in the case of type 0 channel mapping, + ** or 19 + 2 + (1 * channel count) bytes for other channel mappings, to a + ** maximum of 276 (255 channels). + */ + + if (opacket->bytes < 19 || opacket->bytes > 276) + return SFE_MALFORMED_FILE ; + + if (memcmp (opacket->packet, "OpusHead", 8) != 0) + return SFE_MALFORMED_FILE ; + + /* + ** Copy the header page into the binheader so we can use binheader + ** functions to safely unpack it. + */ + count = psf_binheader_writef (psf, "ob", BHWo (0), BHWv (opacket->packet), BHWz (opacket->bytes)) ; + psf->header.end = count ; + + count = psf_binheader_readf (psf, "ep1", 8, &h->version) ; + if (! (h->version == 1 || h->version == 0)) + { psf_log_printf (psf, "Opus : Unknown / unsupported embedding scheme version: %d.\n", (int) h->version) ; + return SFE_UNIMPLEMENTED ; + } ; + + count += psf_binheader_readf (psf, "e12421", &h->channels, &h->preskip, + &h->input_samplerate, &h->gain, &h->channel_mapping) ; + + if (h->channel_mapping == 0) + { if (h->channels > 2) + return SFE_MALFORMED_FILE ; + + /* + ** Setup the stream mapping, so we can use the multistream decoder, + ** rather than have to deal with two decoder pointer types + */ + h->nb_streams = 1 ; + h->nb_coupled = h->channels - 1 ; + h->stream_map [0] = 0 ; + h->stream_map [1] = 1 ; + } + else + { if (opacket->bytes < 19 + 2 + h->channels) + return SFE_MALFORMED_FILE ; + + if (h->channel_mapping == 1 && h->channels > 8) + return SFE_MALFORMED_FILE ; + + count += psf_binheader_readf (psf, "11", &h->nb_streams, &h->nb_coupled) ; + + if (h->nb_streams < 1 || + h->nb_coupled > h->nb_streams || + h->nb_coupled + h->nb_streams > 255) + return SFE_MALFORMED_FILE ; + + for (i = 0 ; i < h->channels ; i++) + { count += psf_binheader_readf (psf, "1", &(h->stream_map [i])) ; + if (h->stream_map [i] > h->nb_streams + h->nb_coupled && h->stream_map [i] != 255) + return SFE_MALFORMED_FILE ; + } ; + } ; + + if (count != opacket->bytes) + { /* OggOpus spec mandates that this is a hard error. */ + psf_log_printf (psf, "Opus : Error, extra data in Ogg Opus header.\n") ; + return SFE_MALFORMED_FILE ; + } ; + + opus_print_header (psf, h) ; + + return 0 ; +} /* ogg_opus_read_header_packet */ + +static int +ogg_opus_read_header (SF_PRIVATE *psf) +{ OGG_PRIVATE *odata = (OGG_PRIVATE *) psf->container_data ; + OPUS_PRIVATE *oopus = (OPUS_PRIVATE *) psf->codec_data ; + int error ; + + /* + ** First page is already loaded by the ogg container code when it + ** classified the stream, no need to re-load it now. + */ + + if (ogg_page_packets (&odata->opage) != 1 || !ogg_page_bos (&odata->opage)) + return SFE_MALFORMED_FILE ; + + oopus->serialno = ogg_page_serialno (&odata->opage) ; + if ((error = opus_read_header_packet (psf, &oopus->header, &odata->opacket))) + return error ; + + /* + ** The comment header MUST be next. It is one packet, that packet MUST begin + ** on the second page of the stream, but it MAY span multiple pages. + */ + + while (ogg_stream_packetout (&odata->ostream, &odata->opacket) != 1) + { if (ogg_stream_next_page (psf, odata) != 1) + { /* out of data... technically that's malformed. */ + return psf->error ? psf->error : SFE_MALFORMED_FILE ; + } ; + } ; + + if ((error = vorbiscomment_read_tags (psf, &odata->opacket, &opustags_ident))) + return error ; + + return ogg_opus_setup_decoder (psf, oopus->header.input_samplerate) ; +} /* ogg_opus_read_header */ + +static int +ogg_opus_setup_decoder (SF_PRIVATE *psf, int input_samplerate) +{ OPUS_PRIVATE *oopus = (OPUS_PRIVATE *) psf->codec_data ; + OpusMSDecoder *decoder ; + int sr_factor ; + int error ; + + /* + ** Decide what sample rate to decode at. We choose the lowest valid rate + ** that is greater or equal to the original rate. + ** + ** Opus documentation recommends always decoding at 48000Hz if the file is + ** being decoded for playback, since most hardware will resample it back to + ** 48000Hz anyways. We don't know if that's true, maybe the user is + ** decoding for editing or transcoding purposes. + */ + if (input_samplerate > 24000) + sr_factor = 1 ; + else if (input_samplerate > 16000) + sr_factor = 2 ; + else if (input_samplerate > 12000) + sr_factor = 3 ; + else if (input_samplerate > 8000) + sr_factor = 4 ; + else + sr_factor = 6 ; + + decoder = opus_multistream_decoder_create ( + 48000 / sr_factor, + oopus->header.channels, + oopus->header.nb_streams, + oopus->header.nb_coupled, + oopus->header.stream_map, + &error) ; + + if (error != OPUS_OK) + { psf_log_printf (psf, "Opus : Failed to create multistream decoder: %s\n", + opus_strerror (error)) ; + return SFE_INTERNAL ; + } + + /* + ** Replace the decoder, if one was already initialized (see + ** SFC_GET_ORIGINAL_SAMPLERATE) + */ + if (oopus->u.decode.state) + opus_multistream_decoder_destroy (oopus->u.decode.state) ; + oopus->u.decode.state = decoder ; + + oopus->sr_factor = sr_factor ; + psf->sf.samplerate = 48000 / sr_factor ; + psf->sf.channels = oopus->header.channels ; + oopus->loc = oopus->len = 0 ; + + /* + ** The Opus decoder can do our gain for us. The OggOpus header contains a + ** gain field. This field, unlike various gain-related tags, is intended to + ** be a perminent baked-in gain applied before any user-configurable gain + ** (eg replay-gain.) This is so the gain of track can be set without having + ** to re-encode. + ** + ** Both the header.gain field and the parameter are in the Q7.8 format. + ** + ** TODO: Make this configurable? Include other gain sources too? + */ + opus_multistream_decoder_ctl (oopus->u.decode.state, OPUS_SET_GAIN (oopus->header.gain)) ; + + /* + ** Opus packets can vary in length, with the legal values being 2.5, 5, 10, + ** 20, 40 or 60ms. The recommended default for non-realtime is 20ms. As + ** such, allocate a buffer of that size now, we'll realloc later if a + ** larger one is needed. + ** + ** buffersize is expressed in samples/channel, as that is what opus_decode + ** expects. + */ + if (oopus->buffer) + { free (oopus->buffer) ; + oopus->buffer = NULL ; + } ; + oopus->buffersize = 20 * psf->sf.samplerate / 1000 ; + oopus->buffer = malloc (sizeof (float) * psf->sf.channels * oopus->buffersize) ; + if (oopus->buffer == NULL) + return SFE_MALLOC_FAILED ; + + return 0 ; +} /* ogg_opus_setup_decoder */ + +static int +ogg_opus_setup_encoder (SF_PRIVATE *psf, OGG_PRIVATE *odata, OPUS_PRIVATE *oopus) +{ int error ; + int lookahead ; + int nb_streams ; + int nb_coupled ; + + /* default page latency value (1000ms) */ + oopus->u.encode.latency = 1000 * 48 ; + + switch (psf->sf.samplerate) + { case 8000 : + case 12000 : + case 16000 : + case 24000 : + case 48000 : + oopus->sr_factor = 48000 / psf->sf.samplerate ; + break ; + default : + return SFE_OPUS_BAD_SAMPLERATE ; + } ; + + if (psf->sf.channels <= 2) + { oopus->header.channel_mapping = 0 ; + nb_streams = 1 ; + nb_coupled = psf->sf.channels - 1 ; + oopus->header.stream_map [0] = 0 ; + oopus->header.stream_map [1] = 1 ; + + oopus->u.encode.state = opus_multistream_encoder_create ( + psf->sf.samplerate, + psf->sf.channels, + nb_streams, + nb_coupled, + oopus->header.stream_map, + OPUS_APPLICATION_AUDIO, + &error) ; + } + else + { if (psf->sf.channels <= 8) + { /* Use Vorbis/AC3 channel mappings for surround. */ + oopus->header.channel_mapping = 1 ; + } + else + { /* There is no channel mapping, just audio, in parallel, good luck */ + oopus->header.channel_mapping = 255 ; + } + + oopus->u.encode.state = opus_multistream_surround_encoder_create ( + psf->sf.samplerate, + psf->sf.channels, + oopus->header.channel_mapping, + &nb_streams, + &nb_coupled, + oopus->header.stream_map, + OPUS_APPLICATION_AUDIO, + &error) ; + + } + + if (error != OPUS_OK) + { psf_log_printf (psf, "Opus : Error, opus_multistream_encoder_create returned %s\n", opus_strerror (error)) ; + return SFE_BAD_OPEN_FORMAT ; + } ; + oopus->header.nb_streams = nb_streams ; + oopus->header.nb_coupled = nb_coupled ; + + opus_multistream_encoder_ctl (oopus->u.encode.state, OPUS_GET_BITRATE (&oopus->u.encode.bitrate)) ; + psf_log_printf (psf, "Encoding at target bitrate of %dbps\n", oopus->u.encode.bitrate) ; + + /* TODO: Make configurable? */ + error = opus_multistream_encoder_ctl (oopus->u.encode.state, OPUS_SET_COMPLEXITY (10)) ; + if (error != OPUS_OK) + { /* Non-fatal */ + psf_log_printf (psf, "Opus : OPUS_SET_COMPLEXITY returned: %s\n", opus_strerror (error)) ; + } + + /* + ** Get the encoder delay. This can vary depending on implementation and + ** encoder configuration. + ** GOTCHA: This returns the preskip at the encoder samplerate, not the + ** granulepos rate of 48000Hz needed for header.preskip. + */ + error = opus_multistream_encoder_ctl (oopus->u.encode.state, OPUS_GET_LOOKAHEAD (&lookahead)) ; + if (error != OPUS_OK) + { psf_log_printf (psf, "Opus : OPUS_GET_LOOKAHEAD returned: %s\n", opus_strerror (error)) ; + return SFE_BAD_OPEN_FORMAT ; + } ; + oopus->header.preskip = lookahead * oopus->sr_factor ; + + oopus->len = OGG_OPUS_ENCODE_PACKET_LEN (psf->sf.samplerate) ; + oopus->buffer = malloc (sizeof (float) * psf->sf.channels * oopus->len) ; + if (oopus->buffer == NULL) + return SFE_MALLOC_FAILED ; + + /* + ** Set up the resident ogg packet structure, ready for writing into. + ** 1275 * 3 + 7 bytes of packet per stream is from opusenc from opus-tools + */ + ogg_packet_clear (&odata->opacket) ; + oopus->buffersize = (1275 * 3 + 7) * oopus->header.nb_streams ; + odata->opacket.packet = malloc (oopus->buffersize) ; + odata->opacket.packetno = 2 ; + if (odata->opacket.packet == NULL) + return SFE_MALLOC_FAILED ; + + oopus->serialno = psf_rand_int32 () ; + ogg_stream_init (&odata->ostream, oopus->serialno) ; + + return 0 ; +} /* ogg_opus_setup_encoder */ + +static int +ogg_opus_write_header (SF_PRIVATE *psf, int UNUSED (calc_length)) +{ OGG_PRIVATE *odata = (OGG_PRIVATE *) psf->container_data ; + OPUS_PRIVATE *oopus = (OPUS_PRIVATE *) psf->codec_data ; + int nn ; + ogg_packet op ; + + oopus->header.version = 1 ; + oopus->header.channels = psf->sf.channels ; + + /* FIXME: Allow the user to set this ?! */ + oopus->header.gain = 0 ; + + if (psf->dataoffset > 0) + { if (psf->have_written) + { /* + ** Might be possible to deal with this, but it's difficult as we + ** have to take Ogg Page header sizes in to account, not just + ** packet sizes. + */ + return SFE_UNIMPLEMENTED ; + } + if (psf_is_pipe (psf)) + return SFE_NOT_SEEKABLE ; + if (psf_fseek (psf, 0, SEEK_SET) < 0) + return SFE_SEEK_FAILED ; + ogg_stream_reset_serialno (&odata->ostream, oopus->serialno) ; + psf->dataoffset = 0 ; + } + else + opus_print_header (psf, &oopus->header) ; + + psf->header.ptr [0] = 0 ; + psf->header.indx = 0 ; + + /* Opus Header Marker */ + psf_binheader_writef (psf, "eb", BHWv ("OpusHead"), BHWz (8)) ; + + /* Ogg Embedding scheme version, Channel Count, Preskip Samples */ + psf_binheader_writef (psf, "e112", BHW1 (oopus->header.version), BHW1 (psf->sf.channels), BHW2 (oopus->header.preskip)) ; + + /* + ** If an original samplerate has not been set by the user command + ** SFC_SET_ORIGINAL_SAMPLERATE, write the current samplerate. + */ + if (oopus->header.input_samplerate) + psf_binheader_writef (psf, "e4", BHW4 (oopus->header.input_samplerate)) ; + else + psf_binheader_writef (psf, "e4", BHW4 (psf->sf.samplerate)) ; + + /* Input Sample Rate, Gain (S7.8 format), Channel Mapping Type */ + psf_binheader_writef (psf, "e21", BHW2 (oopus->header.gain), BHW1 (oopus->header.channel_mapping)) ; + + /* Channel mappings, required if not using type 0 (mono/stereo) */ + if (oopus->header.channel_mapping > 0) + { psf_binheader_writef (psf, "11", BHW1 (oopus->header.nb_streams), BHW1 (oopus->header.nb_coupled)) ; + for (nn = 0 ; nn < oopus->header.channels ; nn++) + psf_binheader_writef (psf, "1", BHW1 (oopus->header.stream_map [nn])) ; + } ; + + op.packet = psf->header.ptr ; + op.bytes = psf->header.indx ; + op.b_o_s = 1 ; + op.e_o_s = 0 ; + op.granulepos = 0 ; + op.packetno = 1 ; + + /* The first page MUST only contain the header, so flush it out now */ + ogg_stream_packetin (&odata->ostream, &op) ; + for ( ; (nn = ogg_stream_flush (&odata->ostream, &odata->opage)) ; ) + { if (! (nn = ogg_write_page (psf, &odata->opage))) + { psf_log_printf (psf, "Opus : Failed to write header!\n") ; + if (psf->error) + return psf->error ; + return SFE_INTERNAL ; + } ; + psf->dataoffset += nn ; + } + + /* + ** Metadata Tags (manditory) + ** + ** All tags must be in one packet, which may span pages, and these pages + ** must not contain any other packets, so flush. The vendor string should + ** be the libopus library version, as it is doing the actual encoding. We + ** put the libsndfile identifier in the ENCODER tag. + ** + ** See: https://wiki.xiph.org/VorbisComment#ENCODER + */ + vorbiscomment_write_tags (psf, &op, &opustags_ident, opus_get_version_string (), - (OGG_OPUS_COMMENT_PAD)) ; + op.packetno = 2 ; + ogg_stream_packetin (&odata->ostream, &op) ; + for ( ; (nn = ogg_stream_flush (&odata->ostream, &odata->opage)) ; ) + { if (! (nn = ogg_write_page (psf, &odata->opage))) + { psf_log_printf (psf, "Opus : Failed to write comments!\n") ; + if (psf->error) + return psf->error ; + return SFE_INTERNAL ; + } ; + psf->dataoffset += nn ; + } + + return 0 ; +} /* ogg_opus_write_header */ + +static void +ogg_opus_flush (SF_PRIVATE *psf) +{ OGG_PRIVATE *odata = (OGG_PRIVATE *) psf->container_data ; + OPUS_PRIVATE *oopus = (OPUS_PRIVATE *) psf->codec_data ; + uint64_t last_granulepos ; + int nbytes ; + int len ; + int last_packet ; + + /* + ** Need to flush both samples waiting for a complete packet and samples + ** currently 'inside' the encoder because of its latency. In the case of + ** the latter, we need to encode an equivalent amount of silence to push + ** them out. + ** + ** Note that the last packet's granule position might be less than the + ** total number of samples completed in it. This is how Ogg embedded Opus + ** encodes the amount of appended padding to truncate for gapless playback. + */ + + last_granulepos = oopus->pkt_pos + (oopus->sr_factor * oopus->loc) + oopus->header.preskip ; + last_packet = SF_FALSE ; + memset (&(oopus->buffer [oopus->loc * psf->sf.channels]), 0, sizeof (float) * psf->sf.channels * (oopus->len - oopus->loc)) ; + + for (last_packet = SF_FALSE ; last_packet == SF_FALSE ; ) + { oopus->pkt_pos += oopus->len * oopus->sr_factor ; + if (oopus->pkt_pos >= last_granulepos) + { last_packet = SF_TRUE ; + /* + ** Try to shorten the last packet to the smallest valid packet size + ** to minimize padding samples. + */ + len = (oopus->len * oopus->sr_factor) - (oopus->pkt_pos - last_granulepos) ; + if (len <= 120) /* 2.5 ms */ + len = 120 / oopus->sr_factor ; + else if (len <= 240) /* 5 ms */ + len = 240 / oopus->sr_factor ; + else if (len <= 480) /* 10 ms */ + len = 480 / oopus->sr_factor ; + else + len = oopus->len ; + } + else + len = oopus->len ; + + nbytes = opus_multistream_encode_float (oopus->u.encode.state, oopus->buffer, + len, odata->opacket.packet, oopus->buffersize) ; + + if (nbytes < 0) + { psf_log_printf (psf, "Opus : opus_multistream_encode_float returned: %s\n", opus_strerror (nbytes)) ; + break ; + } + + odata->opacket.bytes = nbytes ; + odata->opacket.packetno++ ; + if (last_packet) + { odata->opacket.granulepos = (ogg_int64_t) last_granulepos ; + odata->opacket.e_o_s = 1 ; + } + else + odata->opacket.granulepos = (ogg_int64_t) oopus->pkt_pos ; + + ogg_stream_packetin (&odata->ostream, &odata->opacket) ; + while (ogg_stream_pageout (&odata->ostream, &odata->opage)) + ogg_write_page (psf, &odata->opage) ; + } ; + + while (ogg_stream_flush (&odata->ostream, &odata->opage)) + ogg_write_page (psf, &odata->opage) ; +} /* ogg_opus_flush */ + +static int +ogg_opus_calculate_page_duration (OGG_PRIVATE *odata) +{ int i, samples, duration ; + ogg_packet *ppkt ; + + duration = 0 ; + for (i = 0 , ppkt = odata->pkt ; i < odata->pkt_len ; i++, ppkt++) + { /* Use 48kHz to get the sample count for use with granule positions. */ + samples = opus_packet_get_nb_samples (ppkt->packet, ppkt->bytes, 48000) ; + if (samples > 0) + duration += samples ; + } ; + return duration ; +} /* ogg_opus_calculate_page_duration */ + +static int +ogg_opus_unpack_next_page (SF_PRIVATE *psf, OGG_PRIVATE *odata, OPUS_PRIVATE *oopus) +{ int nn ; + + nn = ogg_stream_unpack_page (psf, odata) ; + + if (nn == 1) + { oopus->pkt_pos = oopus->pg_pos ; + oopus->pg_pos = odata->pkt [odata->pkt_len - 1].granulepos ; + } + else if (nn == 2) + { uint64_t gp, last_page ; + + /* Found a hole. Need to recalculated pkt_pos from pg_pos */ + last_page = oopus->pg_pos ; + oopus->pg_pos = odata->pkt [odata->pkt_len - 1].granulepos ; + gp = ogg_opus_calculate_page_duration (odata) ; + oopus->pkt_pos = oopus->pg_pos - gp ; + psf_log_printf (psf, "Opus : Hole found appears to be of length %D samples.\n", + (oopus->pkt_pos - last_page) / (uint64_t) oopus->sr_factor) ; + /* + ** Could save the hole size here, and have ogg_opus_read_refill() + ** do packet loss concealment until the hole is gone, but libopus does + ** PLC by generating white-noise for the duration of the hole. That is + ** the correct thing for use in telephony, but it isn't generally + ** appropriate here. It actually sounds better with no PLC, as the + ** lapped nature of full-width Opus means the two edges of the hole + ** will be blended together. + */ + return 1 ; + } + + return nn ; +} /* ogg_opus_unpack_next_page */ + +static int +ogg_opus_read_refill (SF_PRIVATE *psf, OGG_PRIVATE *odata, OPUS_PRIVATE *oopus) +{ uint64_t pkt_granulepos ; + int nn, nsamp ; + ogg_packet *ppkt ; + + if (odata->pkt_indx == odata->pkt_len) + { nn = ogg_opus_unpack_next_page (psf, odata, oopus) ; + if (nn <= 0) + return nn ; + } + + if (odata->pkt_indx == odata->pkt_len) + return 0 ; + + ppkt = odata->pkt + odata->pkt_indx ; + nsamp = opus_multistream_decode_float (oopus->u.decode.state, + ppkt->packet, ppkt->bytes, oopus->buffer, oopus->buffersize, 0) ; + + if (nsamp == OPUS_BUFFER_TOO_SMALL) + { nsamp = opus_packet_get_nb_samples (ppkt->packet, ppkt->bytes, psf->sf.samplerate) ; + psf_log_printf (psf, "Growing decode buffer to hold %d samples from %d\n", + nsamp, oopus->buffersize) ; + if (nsamp > 5760) + { psf_log_printf (psf, "Packet is larger than maximum allowable of 120ms!? Skipping.\n") ; + return 0 ; + } ; + oopus->buffersize = nsamp ; + + free (oopus->buffer) ; + oopus->buffer = NULL ; + oopus->buffer = malloc (sizeof (float) * oopus->buffersize * psf->sf.channels) ; + if (oopus->buffer == NULL) + { psf->error = SFE_MALLOC_FAILED ; + oopus->buffersize = 0 ; + return -1 ; + } ; + + nsamp = opus_multistream_decode_float (oopus->u.decode.state, + ppkt->packet, ppkt->bytes, oopus->buffer, oopus->buffersize, 0) ; + } ; + odata->pkt_indx ++ ; + + if (nsamp < 0) + { psf_log_printf (psf, "Opus : opus_multistream_decode returned: %s\n", + opus_strerror (nsamp)) ; + psf->error = SFE_INTERNAL ; + return nsamp ; + } ; + + /* + ** Check for if this decoded packet is the last of the stream, in + ** which case a page granule position which is shorter than the + ** sample count of all packets in the page indicates that the last + ** samples are padding and should be dropped. + */ + pkt_granulepos = oopus->pkt_pos + (nsamp * oopus->sr_factor) ; + if (pkt_granulepos <= oopus->pg_pos) + { oopus->len = nsamp ; + } + else + { if (ogg_page_eos (&odata->opage)) + { /* + ** Possible for pg_pos < pkt_pos if there is a trailing + ** packet. It's not supposed to happen, but could. + */ + oopus->len = SF_MAX ((int) (oopus->pg_pos - oopus->pkt_pos) / oopus->sr_factor, 0) ; + } + else + { /* + ** From https://wiki.xiph.org/OggOpus#Granule_Position + ** A decoder MUST reject as invalid any stream where the granule + ** position is smaller than the number of samples contained in + ** packets that complete on the first page with a completed + ** packet, unless that page has the 'end of stream' flag set. It + ** MAY defer this action until it decodes the last packet + ** completed on that page. + */ + psf_log_printf (psf, "Opus : Mid-stream page's granule position %D is less than total samples of %D\n", oopus->pg_pos, pkt_granulepos) ; + psf->error = SFE_MALFORMED_FILE ; + return -1 ; + } ; + } ; + + if (oopus->len > oopus->buffersize) + { free (oopus->buffer) ; + oopus->buffersize = oopus->len ; + oopus->buffer = malloc (sizeof (float) * oopus->buffersize * psf->sf.channels) ; + if (oopus->buffer == NULL) + { psf->error = SFE_MALLOC_FAILED ; + oopus->buffersize = 0 ; + return -1 ; + } ; + } ; + + /* + ** Check for if this decoded packet contains samples from before the pre- + ** skip point, indicating that these samples are padding to get the decoder + ** to converge and should be dropped. + */ + if (oopus->pkt_pos < (unsigned) oopus->header.preskip) + oopus->loc = SF_MIN ((oopus->header.preskip - (int) oopus->pkt_pos) / oopus->sr_factor, oopus->len) ; + else + oopus->loc = 0 ; + + oopus->pkt_pos = pkt_granulepos ; + + return nsamp ; +} /* ogg_opus_read_refill */ + +static int +ogg_opus_write_out (SF_PRIVATE *psf, OGG_PRIVATE *odata, OPUS_PRIVATE *oopus) +{ int nbytes ; + + if (oopus->u.encode.lsb != oopus->u.encode.lsb_last) + opus_multistream_encoder_ctl (oopus->u.encode.state, OPUS_SET_LSB_DEPTH (oopus->u.encode.lsb)) ; + + nbytes = opus_multistream_encode_float (oopus->u.encode.state, + oopus->buffer, oopus->len, + odata->opacket.packet, oopus->buffersize) ; + + if (nbytes < 0) + { psf_log_printf (psf, "Opus : Error, opus_multistream_encode_float returned: %s\n", opus_strerror (nbytes)) ; + psf->error = SFE_INTERNAL ; + return nbytes ; + } ; + + oopus->u.encode.last_segments += (nbytes + 255) / 255 ; + oopus->pkt_pos += oopus->len * oopus->sr_factor ; + odata->opacket.bytes = nbytes ; + odata->opacket.granulepos = oopus->pkt_pos ; + odata->opacket.packetno++ ; + + /* + ** Decide whether to flush the Ogg page *before* adding the new packet to + ** it. Check both for if there is more than 1 second of audio (our default + ** Ogg page latency, this latency can be modified using sf_command()) + ** or if adding the packet would cause a continued page, + ** in which case we might as well make a new page anyways. + */ + for ( ; ; ) + { if (oopus->pkt_pos - oopus->pg_pos >= oopus->u.encode.latency || oopus->u.encode.last_segments >= 255) + nbytes = ogg_stream_flush_fill (&odata->ostream, &odata->opage, 255 * 255) ; + else + nbytes = ogg_stream_pageout_fill (&odata->ostream, &odata->opage, 255 * 255) ; + if (nbytes > 0) + { oopus->u.encode.last_segments -= ogg_page_segments (&odata->opage) ; + oopus->pg_pos = oopus->pkt_pos ; + ogg_write_page (psf, &odata->opage) ; + } + else + break ; + } ; + + ogg_stream_packetin (&odata->ostream, &odata->opacket) ; + oopus->loc = 0 ; + oopus->u.encode.lsb_last = oopus->u.encode.lsb ; + oopus->u.encode.lsb = 0 ; + + return 1 ; +} /* ogg_opus_write_out */ + +static sf_count_t +ogg_opus_read_s (SF_PRIVATE *psf, short *ptr, sf_count_t len) +{ OGG_PRIVATE *odata = (OGG_PRIVATE *) psf->container_data ; + OPUS_PRIVATE *oopus = (OPUS_PRIVATE *) psf->codec_data ; + sf_count_t total = 0 ; + sf_count_t readlen, i ; + float *iptr ; + + while (total < len) + { if (oopus->loc == oopus->len) + { if (ogg_opus_read_refill (psf, odata, oopus) <= 0) + return total ; + } ; + + readlen = SF_MIN (len - total, (sf_count_t) (oopus->len - oopus->loc) * psf->sf.channels) ; + if (readlen > 0) + { iptr = oopus->buffer + oopus->loc * psf->sf.channels ; + i = total ; + total += readlen ; + + if (psf->float_int_mult) + { float inverse = 1.0 / psf->float_max ; + for ( ; i < total ; i++) + { ptr [i] = psf_lrintf (((*(iptr++)) * inverse) * 32767.0f) ; + } ; + } + else + { for ( ; i < total ; i++) + { ptr [i] = psf_lrintf ((*(iptr++)) * 32767.0f) ; + } ; + } ; + oopus->loc += (readlen / psf->sf.channels) ; + } ; + } ; + return total ; +} /* ogg_opus_read_s */ + +static sf_count_t +ogg_opus_read_i (SF_PRIVATE *psf, int *ptr, sf_count_t len) +{ OGG_PRIVATE *odata = (OGG_PRIVATE *) psf->container_data ; + OPUS_PRIVATE *oopus = (OPUS_PRIVATE *) psf->codec_data ; + sf_count_t total = 0 ; + sf_count_t readlen, i ; + float *iptr ; + + while (total < len) + { if (oopus->loc == oopus->len) + { if (ogg_opus_read_refill (psf, odata, oopus) <= 0) + return total ; + } ; + + readlen = SF_MIN (len - total, (sf_count_t) (oopus->len - oopus->loc) * psf->sf.channels) ; + if (readlen > 0) + { iptr = oopus->buffer + oopus->loc * psf->sf.channels ; + i = total ; + total += readlen ; + + if (psf->float_int_mult) + { float inverse = 1.0 / psf->float_max ; + for ( ; i < total ; i++) + { ptr [i] = psf_lrintf (((*(iptr++)) * inverse) * 2147483647.0f) ; + } + } + else + { for ( ; i < total ; i++) + { ptr [i] = psf_lrintf ((*(iptr++)) * 2147483647.0f) ; + } + } ; + oopus->loc += (readlen / psf->sf.channels) ; + } ; + } ; + return total ; +} /* ogg_opus_read_i */ + +static sf_count_t +ogg_opus_read_f (SF_PRIVATE *psf, float *ptr, sf_count_t len) +{ OGG_PRIVATE *odata = (OGG_PRIVATE *) psf->container_data ; + OPUS_PRIVATE *oopus = (OPUS_PRIVATE *) psf->codec_data ; + sf_count_t total = 0 ; + sf_count_t readlen ; + + while (total < len) + { if (oopus->loc == oopus->len) + { if (ogg_opus_read_refill (psf, odata, oopus) <= 0) + return total ; + } ; + + readlen = SF_MIN (len - total, (sf_count_t) (oopus->len - oopus->loc) * psf->sf.channels) ; + if (readlen > 0) + { memcpy (&(ptr [total]), &(oopus->buffer [oopus->loc * psf->sf.channels]), sizeof (float) * readlen) ; + total += readlen ; + oopus->loc += (readlen / psf->sf.channels) ; + } ; + } ; + return total ; +} /* ogg_opus_read_f */ + +static sf_count_t +ogg_opus_read_d (SF_PRIVATE *psf, double *ptr, sf_count_t len) +{ OGG_PRIVATE *odata = (OGG_PRIVATE *) psf->container_data ; + OPUS_PRIVATE *oopus = (OPUS_PRIVATE *) psf->codec_data ; + sf_count_t total = 0 ; + sf_count_t readlen, i ; + float *fptr ; + + while (total < len) + { if (oopus->loc >= oopus->len) + { if (ogg_opus_read_refill (psf, odata, oopus) <= 0) + return total ; + } ; + + readlen = SF_MIN (len - total, (sf_count_t) (oopus->len - oopus->loc) * psf->sf.channels) ; + + if (readlen > 0) + { fptr = oopus->buffer + oopus->loc * psf->sf.channels ; + i = total ; + total += readlen ; + for ( ; i < total ; i++) + { ptr [i] = *fptr++ ; + } ; + oopus->loc += readlen / psf->sf.channels ; + } ; + } ; + return total ; +} /* ogg_opus_read_d */ + +static sf_count_t +ogg_opus_write_s (SF_PRIVATE *psf, const short *ptr, sf_count_t len) +{ OGG_PRIVATE *odata = (OGG_PRIVATE *) psf->container_data ; + OPUS_PRIVATE *oopus = (OPUS_PRIVATE *) psf->codec_data ; + sf_count_t total, i ; + int writelen ; + float *optr ; + + if (oopus->u.encode.lsb < 16) + oopus->u.encode.lsb = 16 ; + + for (total = 0 ; total < len ; ) + { if (oopus->loc >= oopus->len) + { /* Need to encode the buffer */ + if (ogg_opus_write_out (psf, odata, oopus) <= 0) + return total ; + } ; + + writelen = SF_MIN (len - total, (sf_count_t) (oopus->len - oopus->loc) * psf->sf.channels) ; + if (writelen) + { optr = oopus->buffer + oopus->loc * psf->sf.channels ; + i = total ; + total += writelen ; + for ( ; i < total ; i++) + { *optr++ = (float) (ptr [i]) / 32767.0f ; + } + oopus->loc += (writelen / psf->sf.channels) ; + } ; + } ; + return total ; +} /* ogg_opus_write_s */ + +static sf_count_t +ogg_opus_write_i (SF_PRIVATE *psf, const int *ptr, sf_count_t len) +{ OGG_PRIVATE *odata = (OGG_PRIVATE *) psf->container_data ; + OPUS_PRIVATE *oopus = (OPUS_PRIVATE *) psf->codec_data ; + sf_count_t total, i ; + int writelen ; + float *optr ; + + if (oopus->u.encode.lsb < 24) + oopus->u.encode.lsb = 24 ; + + for (total = 0 ; total < len ; ) + { if (oopus->loc >= oopus->len) + { /* Need to encode the buffer */ + if (ogg_opus_write_out (psf, odata, oopus) <= 0) + return total ; + } ; + + writelen = SF_MIN (len - total, (sf_count_t) (oopus->len - oopus->loc) * psf->sf.channels) ; + if (writelen) + { optr = oopus->buffer + oopus->loc * psf->sf.channels ; + i = total ; + total += writelen ; + for ( ; i < total ; i++) + { *optr++ = (float) (ptr [i]) / 2147483647.0f ; + } ; + oopus->loc += (writelen / psf->sf.channels) ; + } ; + } ; + return total ; +} /* ogg_opus_write_i */ + +static sf_count_t +ogg_opus_write_f (SF_PRIVATE *psf, const float *ptr, sf_count_t len) +{ OGG_PRIVATE *odata = (OGG_PRIVATE *) psf->container_data ; + OPUS_PRIVATE *oopus = (OPUS_PRIVATE *) psf->codec_data ; + sf_count_t total ; + int writelen ; + + if (oopus->u.encode.lsb < 24) + oopus->u.encode.lsb = 24 ; + + for (total = 0 ; total < len ; ) + { if (oopus->loc >= oopus->len) + { /* Need to encode the buffer */ + if (ogg_opus_write_out (psf, odata, oopus) <= 0) + return total ; + } ; + + writelen = SF_MIN (len - total, (sf_count_t) (oopus->len - oopus->loc) * psf->sf.channels) ; + if (writelen) + { memcpy (&(oopus->buffer [oopus->loc * psf->sf.channels]), &(ptr [total]), sizeof (float) * writelen) ; + total += writelen ; + oopus->loc += (writelen / psf->sf.channels) ; + } ; + } ; + return total ; +} /* ogg_opus_write_f */ + +static sf_count_t +ogg_opus_write_d (SF_PRIVATE *psf, const double *ptr, sf_count_t len) +{ OGG_PRIVATE *odata = (OGG_PRIVATE *) psf->container_data ; + OPUS_PRIVATE *oopus = (OPUS_PRIVATE *) psf->codec_data ; + sf_count_t total, i ; + int writelen ; + float *optr ; + + if (oopus->u.encode.lsb < 24) + oopus->u.encode.lsb = 24 ; + + for (total = 0 ; total < len ; ) + { if (oopus->loc >= oopus->len) + { /* Need to encode the buffer */ + if (ogg_opus_write_out (psf, odata, oopus) <= 0) + return total ; + } ; + + writelen = SF_MIN (len - total, (sf_count_t) (oopus->len - oopus->loc) * psf->sf.channels) ; + if (writelen) + { optr = oopus->buffer + oopus->loc * psf->sf.channels ; + i = total ; + total += writelen ; + for ( ; i < total ; i++) + { *optr++ = (float) (ptr [i]) ; + } ; + oopus->loc += (writelen / psf->sf.channels) ; + } ; + } ; + return total ; +} /* ogg_opus_write_d */ + +static int +ogg_opus_analyze_file (SF_PRIVATE *psf) +{ OGG_PRIVATE *odata = (OGG_PRIVATE *) psf->container_data ; + OPUS_PRIVATE *oopus = (OPUS_PRIVATE *) psf->codec_data ; + uint64_t gp ; + sf_count_t saved_offset, last_page ; + int error ; + + psf->sf.sections = 1 ; + psf->sf.frames = SF_COUNT_MAX ; + oopus->u.decode.gp_end = (uint64_t) -1 ; + oopus->u.decode.last_offset = SF_COUNT_MAX ; + + psf->dataoffset = ogg_sync_ftell (psf) ; + if (psf->filelength != SF_COUNT_MAX) + psf->datalength = psf->filelength - psf->dataoffset ; + else + psf->datalength = SF_COUNT_MAX ; + + /* + ** Calculate the start granule position offset + ** + ** OggOpus streams are allowed to start with a granule position other than + ** zero. This allows for cutting the beginning off of streams without + ** having to modify all following granule positions, or for recording/ + ** joining a live stream in the middle. To figure out the offset, we need + ** to sum up how many samples are in all the packets that complete in the + ** page and subtract it from the page granule position. + ** + ** If this is the last page of the steam (EOS set), this is not possible, + ** as the granule position may be /less/ than the number of samples, to + ** indicate how many samples are end-padding. In this case the granule + ** position offset of the file must be 0, as otherwise it is considered + ** malformed. + */ + error = ogg_opus_unpack_next_page (psf, odata, oopus) ; + if (error < 0 && psf->error) + return psf->error ; + + gp = ogg_opus_calculate_page_duration (odata) ; + if (gp <= 0) + { psf_log_printf (psf, "Opus : Page duration of zero!\n") ; + return SFE_MALFORMED_FILE ; + } ; + + if (!ogg_page_eos (&odata->opage)) + { if (gp > oopus->pg_pos) + { psf_log_printf (psf, "Opus : First data page's granule position is less than total number of samples on the page!\n") ; + return SFE_MALFORMED_FILE ; + } + oopus->pkt_pos = oopus->pg_pos - gp ; + } + else if (gp < oopus->pg_pos) + { psf_log_printf (psf, "Opus : First data page is also the last, and granule position has an (ambigious) offset.\n") ; + return SFE_MALFORMED_FILE ; + } ; + oopus->u.decode.gp_start = oopus->pkt_pos ; + + if (!psf->sf.seekable) + return 0 ; + + /* + ** Find the last page and fetch the last granule position. + ** First, save were we are now. + */ + saved_offset = ogg_sync_ftell (psf) ; + + /* This uses the sync page buffer, the stream page buffer is untouched. */ + last_page = ogg_sync_last_page_before (psf, odata, &oopus->u.decode.gp_end, psf->filelength, oopus->serialno) ; + if (last_page > 0) + { if (!ogg_page_eos (&odata->opage)) + psf_log_printf (psf, "Ogg : Last page lacks an end-of-stream bit.\n") ; + if (last_page + odata->opage.header_len + odata->opage.body_len < psf->filelength) + psf_log_printf (psf, "Ogg : Junk after the last page.\n") ; + oopus->u.decode.last_offset = last_page ; + + if (oopus->u.decode.gp_end != (uint64_t) -1) + { psf->sf.frames = (oopus->u.decode.gp_end - oopus->u.decode.gp_start + - oopus->header.preskip) / oopus->sr_factor ; + } ; + } ; + + + psf_log_printf (psf, " Granule pos offset : %D\n", oopus->u.decode.gp_start) ; + if (oopus->u.decode.gp_end != (uint64_t) -1) + psf_log_printf (psf, " Last Granule pos : %D\n", oopus->u.decode.gp_end) ; + + /* Go back to where we left off. */ + ogg_sync_fseek (psf, saved_offset, SEEK_SET) ; + return 0 ; +} /* ogg_opus_analyze_file */ + +/* +** ogg_opus_null_read +** +** Decode samples, doing nothing with them, until the desired granule position +** is reached. +*/ +static sf_count_t +ogg_opus_null_read (SF_PRIVATE *psf, sf_count_t offset) +{ OGG_PRIVATE *odata = (OGG_PRIVATE *) psf->container_data ; + OPUS_PRIVATE *oopus = (OPUS_PRIVATE *) psf->codec_data ; + sf_count_t total ; + + total = (oopus->pkt_pos / oopus->sr_factor) - (oopus->len - oopus->loc) ; + for ( ; total < offset ; ) + { sf_count_t readlen = SF_MIN ((int) (offset - total), (oopus->len - oopus->loc)) ; + if (readlen > 0) + { total += readlen ; + oopus->loc += readlen ; + } ; + if (oopus->loc == oopus->len) + { if (ogg_opus_read_refill (psf, odata, oopus) <= 0) + return total ; + /* + ** Ignore pre-skip skipping. The preskip was accounted for in the + ** arugment to offset, so we need to count it. + */ + oopus->loc = 0 ; + } ; + } ; + return total ; +} /* ogg_opus_null_read */ + +/* +** ogg_opus_page_seek_search +** +** Search within the file for the page with the highest granule position at or +** before our target. +*/ +static int +ogg_opus_page_seek_search (SF_PRIVATE *psf, uint64_t target_gp) +{ OGG_PRIVATE *odata = (OGG_PRIVATE *) psf->container_data ; + OPUS_PRIVATE *oopus = (OPUS_PRIVATE *) psf->codec_data ; + uint64_t pcm_start ; + uint64_t pcm_end ; + uint64_t best_gp ; + sf_count_t begin ; + sf_count_t end ; + sf_count_t old_pos ; + int ret ; + + best_gp = pcm_start = oopus->u.decode.gp_start ; + pcm_end = oopus->u.decode.gp_end ; + begin = psf->dataoffset ; + end = oopus->u.decode.last_offset ; + + /* Search the Ogg stream for such a page */ + old_pos = ogg_sync_ftell (psf) ; + ret = ogg_stream_seek_page_search (psf, odata, target_gp, pcm_start, pcm_end, &best_gp, begin, end, 48000) ; + if (ret != 0) + { ogg_sync_fseek (psf, old_pos, SEEK_SET) ; + return ret ; + } ; + + /* Load the page that contains our pre-roll target */ + oopus->loc = 0 ; + oopus->len = 0 ; + if ((ret = ogg_opus_unpack_next_page (psf, odata, oopus)) != 1) + return ret ; + oopus->pkt_pos = best_gp ; + + /* Reset the decoder (gain settings survive the reset) */ + opus_multistream_decoder_ctl (oopus->u.decode.state, OPUS_RESET_STATE) ; + + return 0 ; +} /* ogg_opus_page_seek_search */ + +/* +** ogg_opus_page_seek_manual +** +** Seek to the beginning of the Ogg stream and read pages until we find one with +** a granule position at or before our target. +*/ +static sf_count_t +ogg_opus_page_seek_manual (SF_PRIVATE *psf, uint64_t target_gp) +{ OGG_PRIVATE *odata = (OGG_PRIVATE *) psf->container_data ; + OPUS_PRIVATE *oopus = (OPUS_PRIVATE *) psf->codec_data ; + sf_count_t pos ; + int nn ; + + if (oopus->pg_pos > target_gp) + { ogg_stream_reset (&odata->ostream) ; + pos = ogg_sync_fseek (psf, psf->dataoffset, SEEK_SET) ; + if (pos < 0) + return pos ; + oopus->pg_pos = oopus->u.decode.gp_start ; + opus_multistream_decoder_ctl (oopus->u.decode.state, OPUS_RESET_STATE) ; + } ; + + while (oopus->pg_pos < target_gp) + { nn = ogg_opus_unpack_next_page (psf, odata, oopus) ; + if (nn <= 0) + return nn ; + } ; + + return 1 ; +} /* ogg_opus_page_seek_manual */ + +static sf_count_t +ogg_opus_seek (SF_PRIVATE *psf, int mode, sf_count_t offset) +{ OGG_PRIVATE *odata = (OGG_PRIVATE *) psf->container_data ; + OPUS_PRIVATE *oopus = (OPUS_PRIVATE *) psf->codec_data ; + uint64_t target_gp, current_gp ; + int ret ; + + /* Only support seeking in read mode. */ + if (mode != SFM_READ || psf->file.mode != SFM_READ) + { psf->error = SFE_BAD_SEEK ; + return PSF_SEEK_ERROR ; + } ; + + /* Figure out the current position granule pos. Use the start of the + * current buffer, to avoid backwards seeking if the target is on the page + * but before the current locaiton. */ + oopus->loc = 0 ; + current_gp = oopus->pkt_pos - (uint64_t) (oopus->len * oopus->sr_factor) ; + + /* Calculate the target granule pos. This includes the decoder delay and + * the file granule position offset. */ + target_gp = offset * oopus->sr_factor ; + target_gp += oopus->u.decode.gp_start ; + target_gp += oopus->header.preskip ; + + /* Check if we need to do a page seek. */ + if (target_gp < current_gp || target_gp - current_gp > OGG_OPUS_PREROLL) + { uint64_t preroll_gp ; + + /* For a page seek, use an earlier target granule pos, giving the + * decoder samples to converge before the actual target. */ + if (target_gp >= OGG_OPUS_PREROLL + oopus->u.decode.gp_start + (uint64_t) oopus->header.preskip) + { preroll_gp = target_gp - OGG_OPUS_PREROLL ; + } + else + { preroll_gp = oopus->u.decode.gp_start + (uint64_t) oopus->header.preskip ; + } ; + + if (oopus->u.decode.gp_end == (uint64_t) -1) + { /* + ** Don't know the end of the file. Could be a chained file we don't yet + ** support. Oh well, just do it manually. + */ + ogg_opus_page_seek_manual (psf, preroll_gp) ; + } + else + { ret = ogg_opus_page_seek_search (psf, preroll_gp) ; + if (ret < 0) + { /* + ** Page seek failed, what to do? Could be bad data. We can + ** either fall-back to manual seeking or bail. Manaul seeking + ** from the beginning has the advantage of finding where the + ** file goes bad. + */ + ret = ogg_opus_page_seek_manual (psf, preroll_gp) ; + if (ret < 0) + { /* + ** If were here, and there is no error, we can be pretty + ** sure that it's the file that is to blame. + */ + if (!psf->error) + psf->error = SFE_MALFORMED_FILE ; + return ret ; + } ; + } ; + } ; + + /* + ** Skip over packets on the found page that are before our pre-roll + ** target to avoid unnecessary decoding, and make decoder convergence + ** independent of page boundaries for more visible errors. + */ + for ( ; odata->pkt_indx != odata->pkt_len ; ) + { ogg_packet *ppkt = &odata->pkt [odata->pkt_indx] ; + int nsamp = opus_packet_get_nb_samples (ppkt->packet, ppkt->bytes, 48000) ; + if (oopus->pkt_pos + nsamp < preroll_gp) + { oopus->pkt_pos += nsamp ; + odata->pkt_indx++ ; + } + else + break ; + } ; + } ; + + /* + ** We've seeked or skipped through pages until just before our target, + ** now decode until we hit it. + */ + offset = ogg_opus_null_read (psf, target_gp / oopus->sr_factor) ; + return offset - ((oopus->header.preskip + oopus->u.decode.gp_start) / oopus->sr_factor) ; + +} /* ogg_opus_seek */ + +static int +ogg_opus_command (SF_PRIVATE *psf, int command, void *data, int datasize) +{ OGG_PRIVATE *odata = (OGG_PRIVATE *) psf->container_data ; + OPUS_PRIVATE *oopus = (OPUS_PRIVATE *) psf->codec_data ; + double quality ; + double latency ; + int error ; + + switch (command) + { case SFC_SET_CHANNEL_MAP_INFO : + /* TODO: figure this out */ + break ; + + case SFC_SET_OGG_PAGE_LATENCY : + /* + ** Argument: double, range 50 to 1600. + ** Average length of OGG page in ms. + ** This length drive the flush of pages. + */ + if (data == NULL || datasize != SIGNED_SIZEOF (double)) + return SFE_BAD_COMMAND_PARAM ; + + latency = *((double *) data) ; + if (latency < 50) + latency = 50 ; + + if (latency > 1600) + latency = 1600 ; + + oopus->u.encode.latency = ((unsigned long) latency) * 48 ; + break ; + + case SFC_SET_COMPRESSION_LEVEL : + /* + ** Argument: double, range 0.0 (lest compressed, best quality) to + ** 1.0 (most compressed, worst quality) + */ + if (data == NULL || datasize != SIGNED_SIZEOF (double)) + return SFE_BAD_COMMAND_PARAM ; + + /* Usable bitrate range is [6, 256] kbps per channel. */ + quality = *((double *) data) ; + oopus->u.encode.bitrate = (int) (((1.0 - quality) * (250000.0)) + 6000.0) * psf->sf.channels ; + if (opus_multistream_encoder_ctl (oopus->u.encode.state, OPUS_SET_BITRATE (oopus->u.encode.bitrate)) == OPUS_OK) + { psf_log_printf (psf, "User changed encoding target bitrate to %dbps\n", oopus->u.encode.bitrate) ; + return SF_TRUE ; + } + psf_log_printf (psf, "Failed to set user encoding target bitrate of %dbps\n", oopus->u.encode.bitrate) ; + return SF_FALSE ; + break ; + + case SFC_SET_ORIGINAL_SAMPLERATE : + if (data == NULL || datasize != SIGNED_SIZEOF (int)) + return SFE_BAD_COMMAND_PARAM ; + /* + ** Only allow changing the input samplerate if at the beginning + ** of the stream, because while it might be possible to change + ** samplerate mid-decode, or to re-write the header for encode, + ** ain't nobody got time to implement and test that. + */ + if (psf->file.mode == SFM_WRITE) + { if (psf->have_written) + return SF_FALSE ; + oopus->header.input_samplerate = *((int *) data) ; + } + else { + if (oopus->pkt_pos > oopus->u.decode.gp_start || oopus->loc > 0) + return SF_FALSE ; + if ((error = ogg_opus_setup_decoder (psf, *((int *) data)))) + return error ; + odata->pkt_indx = 0 ; + /* Adjust file frames count. */ + if (oopus->u.decode.gp_end != (uint64_t) -1) + psf->sf.frames = (oopus->u.decode.gp_end - oopus->u.decode.gp_start + - oopus->header.preskip) / oopus->sr_factor ; + } ; + return SF_TRUE ; + + case SFC_GET_ORIGINAL_SAMPLERATE : + if (data == NULL || datasize != SIGNED_SIZEOF (int)) + return SFE_BAD_COMMAND_PARAM ; + *((int *) data) = oopus->header.input_samplerate ; + return SF_TRUE ; + + case SFC_GET_OGG_STREAM_SERIALNO : + if (data == NULL || datasize != sizeof (int32_t)) + return SF_FALSE ; + + *((int32_t *) data) = odata->ostream.serialno ; + return SF_TRUE ; + + default : + break ; + } + + return SF_FALSE ; +} /* ogg_opus_command */ + +static int +ogg_opus_byterate (SF_PRIVATE *psf) +{ OGG_PRIVATE *odata = (OGG_PRIVATE *) psf->container_data ; + OPUS_PRIVATE *oopus = (OPUS_PRIVATE *) psf->codec_data ; + + if (psf->file.mode == SFM_READ) + { if (odata->pkt_indx == odata->pkt_len) + { if (ogg_opus_unpack_next_page (psf, odata, oopus) < 0) + return -1 ; + } ; + + if (odata->pkt_indx < odata->pkt_len) + { ogg_packet *ppkt = &odata->pkt [odata->pkt_indx] ; + return (ppkt->bytes * 8000) / opus_packet_get_nb_samples (ppkt->packet, ppkt->bytes, 8000) ; + } ; + + if (psf->datalength != SF_COUNT_MAX) + return (psf->datalength * psf->sf.samplerate) / psf->sf.frames ; + } ; + + if (psf->file.mode == SFM_WRITE && oopus->u.encode.state != NULL) + return (oopus->u.encode.bitrate + 7) / 8 ; + + return -1 ; +} /* ogg_opus_byterate */ + +#else /* HAVE_EXTERNAL_XIPH_LIBS */ + +int +ogg_opus_open (SF_PRIVATE *psf) +{ + psf_log_printf (psf, "This version of libsndfile was compiled without Ogg/Opus support.\n") ; + return SFE_UNIMPLEMENTED ; +} /* ogg_opus_open */ + +#endif diff --git a/deps/xmp/build.zig b/deps/xmp/build.zig new file mode 100644 index 000000000..800832122 --- /dev/null +++ b/deps/xmp/build.zig @@ -0,0 +1,245 @@ +const std = @import("std"); + +pub fn build(b: *std.Build) void { + const target = b.standardTargetOptions(.{}); + const optimize = b.standardOptimizeOption(.{}); + + const link_mode = b.option(std.builtin.LinkMode, "link-mode", "Linking mode for the libraries") orelse + .static; + const pic = b.option(bool, "pic", "Produce Position Independent Code"); + const enable_depackers = b.option(bool, "enable-depackers", "Enable archive depackers") orelse true; + const enable_prowizard = b.option(bool, "enable-prowizard", "Enable ProWizard format loaders") orelse true; + + const upstream = b.dependency("xmp", .{}); + + const lib = b.addLibrary(.{ + .name = "xmp", + .linkage = link_mode, + .root_module = b.createModule(.{ + .target = target, + .optimize = optimize, + .link_libc = true, + .pic = pic, + }), + }); + + switch (link_mode) { + .static => lib.root_module.addCMacro("LIBXMP_STATIC", "1"), + .dynamic => lib.root_module.addCMacro("BUILDING_DLL", "1"), + } + if (!enable_depackers) { + lib.root_module.addCMacro("LIBXMP_NO_DEPACKERS", "1"); + } + if (!enable_prowizard) { + lib.root_module.addCMacro("LIBXMP_NO_PROWIZARD", "1"); + } + + lib.root_module.addCSourceFiles(.{ + .root = upstream.path("src"), + .files = &.{ + // core + "virtual.c", + "format.c", + "period.c", + "player.c", + "read_event.c", + "dataio.c", + "misc.c", + "mkstemp.c", + "md5.c", + "lfo.c", + "scan.c", + "control.c", + "far_extras.c", + "med_extras.c", + "filter.c", + "effects.c", + "flow.c", + "mixer.c", + "mix_all.c", + "rng.c", + "load_helpers.c", + "load.c", + "hio.c", + "hmn_extras.c", + "extras.c", + "smix.c", + "path.c", + "filetype.c", + "memio.c", + "tempfile.c", + "mix_paula.c", + "miniz_tinfl.c", + "win32.c", + "loaders/common.c", + "loaders/iff.c", + "loaders/itsex.c", + "loaders/lzw.c", + "loaders/voltable.c", + "loaders/sample.c", + "loaders/vorbis.c", + "loaders/xm_load.c", + "loaders/mod_load.c", + "loaders/s3m_load.c", + "loaders/stm_load.c", + "loaders/669_load.c", + "loaders/far_load.c", + "loaders/mtm_load.c", + "loaders/ptm_load.c", + "loaders/okt_load.c", + "loaders/ult_load.c", + "loaders/mdl_load.c", + "loaders/it_load.c", + "loaders/stx_load.c", + "loaders/pt3_load.c", + "loaders/sfx_load.c", + "loaders/flt_load.c", + "loaders/st_load.c", + "loaders/emod_load.c", + "loaders/imf_load.c", + "loaders/digi_load.c", + "loaders/fnk_load.c", + "loaders/ice_load.c", + "loaders/liq_load.c", + "loaders/ims_load.c", + "loaders/masi_load.c", + "loaders/masi16_load.c", + "loaders/amf_load.c", + "loaders/stim_load.c", + "loaders/mmd_common.c", + "loaders/mmd1_load.c", + "loaders/mmd3_load.c", + "loaders/rtm_load.c", + "loaders/dt_load.c", + "loaders/no_load.c", + "loaders/arch_load.c", + "loaders/sym_load.c", + "loaders/med2_load.c", + "loaders/med3_load.c", + "loaders/med4_load.c", + "loaders/dbm_load.c", + "loaders/umx_load.c", + "loaders/gdm_load.c", + "loaders/pw_load.c", + "loaders/gal5_load.c", + "loaders/gal4_load.c", + "loaders/mfp_load.c", + "loaders/asylum_load.c", + "loaders/muse_load.c", + "loaders/hmn_load.c", + "loaders/mgt_load.c", + "loaders/chip_load.c", + "loaders/abk_load.c", + "loaders/coco_load.c", + "loaders/xmf_load.c", + }, + }); + + if (enable_depackers) { + lib.root_module.addCSourceFiles(.{ + .root = upstream.path("src"), + .files = &.{ + // depacker + "depackers/depacker.c", + "depackers/ppdepack.c", + "depackers/unsqsh.c", + "depackers/mmcmp.c", + "depackers/s404_dec.c", + "depackers/arc.c", + "depackers/arcfs.c", + "depackers/arc_unpack.c", + "depackers/lzx.c", + "depackers/lzx_unpack.c", + "depackers/ice.c", + "depackers/ice_unpack.c", + "depackers/miniz_zip.c", + "depackers/unzip.c", + "depackers/gunzip.c", + "depackers/uncompress.c", + "depackers/bunzip2.c", + "depackers/unlha.c", + "depackers/unxz.c", + "depackers/xz_dec_lzma2.c", + "depackers/xz_dec_stream.c", + "depackers/crc32.c", + "depackers/xfnmatch.c", + "depackers/ptpopen.c", + "depackers/xfd.c", + "depackers/xfd_link.c", + "depackers/lhasa/ext_header.c", + "depackers/lhasa/lha_file_header.c", + "depackers/lhasa/lha_input_stream.c", + "depackers/lhasa/lha_decoder.c", + "depackers/lhasa/lha_reader.c", + "depackers/lhasa/lha_basic_reader.c", + "depackers/lhasa/lh1_decoder.c", + "depackers/lhasa/lh5_decoder.c", + "depackers/lhasa/lh6_decoder.c", + "depackers/lhasa/lh7_decoder.c", + "depackers/lhasa/lhx_decoder.c", + "depackers/lhasa/lk7_decoder.c", + "depackers/lhasa/lz5_decoder.c", + "depackers/lhasa/lzs_decoder.c", + "depackers/lhasa/null_decoder.c", + "depackers/lhasa/pm1_decoder.c", + "depackers/lhasa/pm2_decoder.c", + "depackers/lhasa/macbinary.c", + }, + }); + } + + if (enable_prowizard) { + lib.root_module.addCSourceFiles(.{ + .root = upstream.path("src"), + .files = &.{ + // prowizard + "loaders/prowizard/prowiz.c", + "loaders/prowizard/ptktable.c", + "loaders/prowizard/tuning.c", + "loaders/prowizard/ac1d.c", + "loaders/prowizard/di.c", + "loaders/prowizard/eureka.c", + "loaders/prowizard/fc-m.c", + "loaders/prowizard/fuchs.c", + "loaders/prowizard/fuzzac.c", + "loaders/prowizard/gmc.c", + "loaders/prowizard/heatseek.c", + "loaders/prowizard/ksm.c", + "loaders/prowizard/mp.c", + "loaders/prowizard/np1.c", + "loaders/prowizard/np2.c", + "loaders/prowizard/np3.c", + "loaders/prowizard/p61a.c", + "loaders/prowizard/pm10c.c", + "loaders/prowizard/pm18a.c", + "loaders/prowizard/pha.c", + "loaders/prowizard/prun1.c", + "loaders/prowizard/prun2.c", + "loaders/prowizard/tdd.c", + "loaders/prowizard/unic.c", + "loaders/prowizard/unic2.c", + "loaders/prowizard/wn.c", + "loaders/prowizard/zen.c", + "loaders/prowizard/tp1.c", + "loaders/prowizard/tp3.c", + "loaders/prowizard/p40.c", + "loaders/prowizard/xann.c", + "loaders/prowizard/theplayer.c", + "loaders/prowizard/pp10.c", + "loaders/prowizard/pp21.c", + "loaders/prowizard/starpack.c", + "loaders/prowizard/titanics.c", + "loaders/prowizard/skyt.c", + "loaders/prowizard/novotrade.c", + "loaders/prowizard/hrt.c", + "loaders/prowizard/noiserun.c", + }, + }); + } + + lib.root_module.addIncludePath(upstream.path("include")); + lib.root_module.addIncludePath(upstream.path("src")); + + lib.installHeadersDirectory(upstream.path("include"), "", .{}); + b.installArtifact(lib); +} diff --git a/deps/xmp/build.zig.zon b/deps/xmp/build.zig.zon new file mode 100644 index 000000000..369fe8fc7 --- /dev/null +++ b/deps/xmp/build.zig.zon @@ -0,0 +1,16 @@ +.{ + .name = .xmp, + .fingerprint = 0x25f5842ca8953833, + .version = "4.7.0", + .minimum_zig_version = "0.17.0-dev.1387+01b60634c", + .dependencies = .{ + .xmp = .{ + .url = "https://github.com/libxmp/libxmp/releases/download/libxmp-4.7.0/libxmp-4.7.0.tar.gz", + .hash = "N-V-__8AAKWoMgBbod0U9hJqcgYn3zovkvPTN6uyAPSvEHGH", + }, + }, + .paths = .{ + "build.zig", + "build.zig.zon", + }, +} diff --git a/src/comptime.c b/src/comptime.c index d74d931b6..f3451b892 100644 --- a/src/comptime.c +++ b/src/comptime.c @@ -8,18 +8,27 @@ */ #if (defined(CMAKECONFIG)) + #include "config.h" const char *compbranch = SRB2_COMP_BRANCH; const char *comprevision = SRB2_COMP_REVISION; const char *complast = SRB2_COMP_LASTCOMMIT; +const char *compdate = SRB2_COMP_DATE; +const char *comptime = SRB2_COMP_TIME; #elif (defined(COMPVERSION)) + #include "comptime.h" +const char *compdate = __DATE__; +const char *comptime = __TIME__; #else + const char *compbranch = "Unknown"; const char *comprevision = "illegal"; const char *complast = ""; +const char *compdate = __DATE__; +const char *comptime = __TIME__; #endif @@ -29,6 +38,3 @@ const int compuncommitted = #else 0; #endif - -const char *compdate = __DATE__; -const char *comptime = __TIME__; diff --git a/src/config.h.in b/src/config.h.in index 07d41e4ec..62a96078e 100644 --- a/src/config.h.in +++ b/src/config.h.in @@ -13,7 +13,10 @@ #define SRB2_COMP_REVISION "${SRB2_COMP_REVISION}" #define SRB2_COMP_BRANCH "${SRB2_COMP_BRANCH}" -#define SRB2_COMP_LASTCOMMIT "${SRB2_COMP_LASTCOMMIT}" +#define SRB2_COMP_LASTCOMMIT "${SRB2_COMP_LASTCOMMIT}" +#define SRB2_COMP_DATE "${SRB2_COMP_DATE}" +#define SRB2_COMP_TIME "${SRB2_COMP_TIME}" + // This is done with configure_file instead of defines in order to avoid // recompiling the whole target whenever the working directory state changes #cmakedefine SRB2_COMP_UNCOMMITTED diff --git a/src/core/spmc_queue.hpp b/src/core/spmc_queue.hpp index 9441422d9..41061d175 100644 --- a/src/core/spmc_queue.hpp +++ b/src/core/spmc_queue.hpp @@ -19,7 +19,7 @@ #include #include -#include +#include #include "../cxxutil.hpp" diff --git a/src/core/thread_pool.cpp b/src/core/thread_pool.cpp index c83779a13..7d5bf074e 100644 --- a/src/core/thread_pool.cpp +++ b/src/core/thread_pool.cpp @@ -18,7 +18,7 @@ #include #include -#include +#include #include "../cxxutil.hpp" #include "../m_argv.h" diff --git a/src/d_main.cpp b/src/d_main.cpp index d2dcfee0a..140672110 100644 --- a/src/d_main.cpp +++ b/src/d_main.cpp @@ -81,7 +81,7 @@ #define __STDC_FORMAT_MACROS #include -#include +#include #ifdef HAVE_SDL #include diff --git a/src/dedicated/i_system.cpp b/src/dedicated/i_system.cpp index 44c47bdd6..2c0e164b0 100644 --- a/src/dedicated/i_system.cpp +++ b/src/dedicated/i_system.cpp @@ -26,7 +26,7 @@ #include #ifdef CMAKECONFIG -#include "../config.h" +#include "config.h" #else #include "../config.h.in" #endif diff --git a/src/doomtype.h b/src/doomtype.h index 63b2af54e..76aaa272b 100644 --- a/src/doomtype.h +++ b/src/doomtype.h @@ -82,8 +82,15 @@ extern "C" { /* Boolean type definition */ #ifndef __cplusplus +#ifndef _WIN32 +// windows doesn't have stdbool #include -#endif +#else +#define false 0 +#define true 1 +#define bool int +#endif // _WIN32 +#endif // __cplusplus typedef int32_t dboolean; diff --git a/src/g_game.c b/src/g_game.c index fa600622b..b30fb6b8c 100644 --- a/src/g_game.c +++ b/src/g_game.c @@ -73,7 +73,7 @@ #include "k_vote.h" #include "d_clisrv.h" -#include +#include #ifdef HAVE_DISCORDRPC #include "discord.h" diff --git a/src/k_bot.cpp b/src/k_bot.cpp index 281082b8c..1b68935b8 100644 --- a/src/k_bot.cpp +++ b/src/k_bot.cpp @@ -12,7 +12,7 @@ #include -#include +#include #include "cxxutil.hpp" diff --git a/src/k_botitem.cpp b/src/k_botitem.cpp index b8ab9de82..7ed3757a2 100644 --- a/src/k_botitem.cpp +++ b/src/k_botitem.cpp @@ -12,7 +12,7 @@ #include -#include +#include #include "d_netcmd.h" #include "doomdef.h" diff --git a/src/k_botsearch.cpp b/src/k_botsearch.cpp index 2313b1418..b7614616d 100644 --- a/src/k_botsearch.cpp +++ b/src/k_botsearch.cpp @@ -12,7 +12,7 @@ #include -#include +#include #include "doomdef.h" #include "d_player.h" diff --git a/src/k_items.c b/src/k_items.c index ad051ac0d..28bc4ec70 100644 --- a/src/k_items.c +++ b/src/k_items.c @@ -44,7 +44,7 @@ #include "k_items.h" #include "k_collide.h" -#include +#include kartitem_t kartitems[MAXKARTITEMS] = {0}; kartresult_t kartresults[MAXKARTRESULTS] = {0}; diff --git a/src/k_kart.c b/src/k_kart.c index d1abcbed0..28851da6e 100644 --- a/src/k_kart.c +++ b/src/k_kart.c @@ -72,7 +72,7 @@ #include "h_timers.h" #include "blan/b_soc.h" -#include +#include consvar_t cv_kartstacking_colorflame = CVAR_INIT ("kartstacking_colorflame", "On", CV_SAVE, CV_OnOff, NULL); consvar_t cv_kartstacking_sneakerstacksound = CVAR_INIT ("kartstacking_sneakerstacksound", "On", CV_SAVE, CV_OnOff, NULL); diff --git a/src/lua_hooklib.c b/src/lua_hooklib.c index f5ae03ed6..0c6268a19 100644 --- a/src/lua_hooklib.c +++ b/src/lua_hooklib.c @@ -31,7 +31,7 @@ #include "v_video.h" // V_ClearClipRect -#include +#include /* ========================================================================= ABSTRACTION diff --git a/src/lua_script.c b/src/lua_script.c index 5bff75086..f2322f417 100644 --- a/src/lua_script.c +++ b/src/lua_script.c @@ -42,7 +42,7 @@ #include "g_state.h" #include "m_argv.h" -#include +#include lua_State *gL = NULL; dboolean lua_compatmode = false; diff --git a/src/p_saveg.c b/src/p_saveg.c index 788862efe..372cbe4e6 100644 --- a/src/p_saveg.c +++ b/src/p_saveg.c @@ -52,7 +52,7 @@ #include "cvector.h" -#include +#include savedata_t savedata; diff --git a/src/p_setup.c b/src/p_setup.c index 9e4c06f9d..cfaa82999 100644 --- a/src/p_setup.c +++ b/src/p_setup.c @@ -121,7 +121,7 @@ #include "blan/b_soc.h" -#include +#include // Replay names have time #if !defined (UNDER_CE) diff --git a/src/p_spec.c b/src/p_spec.c index 9e3cf0d92..9038914da 100644 --- a/src/p_spec.c +++ b/src/p_spec.c @@ -55,7 +55,7 @@ // Not sure if this is necessary, but it was in w_wad.c, so I'm putting it here too -Shadow Hog #include -#include +#include mobj_t *skyboxmo[2]; // current skybox mobjs: 0 = viewpoint, 1 = centerpoint mobj_t *skyboxviewpnts[16]; // array of MT_SKYBOX viewpoint mobjs diff --git a/src/p_tick.c b/src/p_tick.c index 4682dee88..d38a4ead1 100644 --- a/src/p_tick.c +++ b/src/p_tick.c @@ -57,7 +57,7 @@ #include "lua_profile.h" -#include +#include #ifdef PARANOIA #include "deh_tables.h" // MOBJTYPE_LIST diff --git a/src/p_user.c b/src/p_user.c index 47216f96a..65960ff87 100644 --- a/src/p_user.c +++ b/src/p_user.c @@ -70,7 +70,7 @@ #include "acs/interface.h" -#include +#include #ifdef HWRENDER #include "hardware/hw_main.h" diff --git a/src/r_bsp.cpp b/src/r_bsp.cpp index 40bdac1f6..06c760570 100644 --- a/src/r_bsp.cpp +++ b/src/r_bsp.cpp @@ -15,7 +15,7 @@ #include #include -#include +#include #include "command.h" #include "doomdef.h" diff --git a/src/r_draw.cpp b/src/r_draw.cpp index f5b697d47..c9424eb3d 100644 --- a/src/r_draw.cpp +++ b/src/r_draw.cpp @@ -36,7 +36,7 @@ #include "hardware/hw_main.h" #endif -#include +#include // -------------------------------------------- // assembly or c drawer routines for 8bpp/16bpp diff --git a/src/r_draw_column.cpp b/src/r_draw_column.cpp index 0866527fc..19a114aaa 100644 --- a/src/r_draw_column.cpp +++ b/src/r_draw_column.cpp @@ -23,7 +23,7 @@ #include "r_main.h" #include "r_draw.h" -#include +#include #include "r_draw_flush.cpp" diff --git a/src/r_draw_span.cpp b/src/r_draw_span.cpp index 70a5dcd87..c29452d93 100644 --- a/src/r_draw_span.cpp +++ b/src/r_draw_span.cpp @@ -14,7 +14,7 @@ /// \note no includes because this is included as part of r_draw.cpp #include "r_draw.h" -#include +#include using namespace libdivide; diff --git a/src/r_plane.cpp b/src/r_plane.cpp index 41f1ea4b7..8f5d6973f 100644 --- a/src/r_plane.cpp +++ b/src/r_plane.cpp @@ -14,7 +14,7 @@ /// while maintaining a per column clipping list only. /// Moreover, the sky areas have to be determined. -#include +#include #include "command.h" #include "doomdef.h" diff --git a/src/r_segs.cpp b/src/r_segs.cpp index 3f1d3d0c4..c0c81faa0 100644 --- a/src/r_segs.cpp +++ b/src/r_segs.cpp @@ -14,7 +14,7 @@ #include -#include +#include #include "command.h" #include "doomdef.h" diff --git a/src/sdl/al_sound.c b/src/sdl/al_sound.c index 4edd3c40a..da4df7d5c 100644 --- a/src/sdl/al_sound.c +++ b/src/sdl/al_sound.c @@ -13,7 +13,7 @@ #ifdef HAVE_OPENAL -#include +#include #ifdef HAVE_GME #ifdef HAVE_ZLIB diff --git a/src/sdl/i_system.cpp b/src/sdl/i_system.cpp index 66ef3f082..d3e1ebee2 100644 --- a/src/sdl/i_system.cpp +++ b/src/sdl/i_system.cpp @@ -26,7 +26,7 @@ #include #ifdef CMAKECONFIG -#include "../config.h" +#include "config.h" #else #include "../config.h.in" #endif @@ -1308,8 +1308,8 @@ void I_SleepDuration(precise_t duration) { duration -= slack; struct timespec ts = { - .tv_sec = duration / precision, - .tv_nsec = duration * 1000000000 / precision % 1000000000, + .tv_sec = (time_t)(duration / precision), + .tv_nsec = (long)(duration * 1000000000 / precision % 1000000000), }; int status; #ifdef __OpenBSD__ diff --git a/src/w_wad.c b/src/w_wad.c index 2857f3c6e..9879b4061 100644 --- a/src/w_wad.c +++ b/src/w_wad.c @@ -24,6 +24,9 @@ #define _FILE_OFFSET_BITS 0 #endif +#define XXH_STATIC_LINKING_ONLY +#define XXH_IMPLEMENTATION + #include #endif diff --git a/src/win32/Srb2win.rc b/src/win32/Srb2win.rc index a83c46092..897c31587 100644 --- a/src/win32/Srb2win.rc +++ b/src/win32/Srb2win.rc @@ -72,7 +72,7 @@ END // // Version // -#include "../doomdef.h" // Needed for version string +//#include "../doomdef.h" // Needed for version string VS_VERSION_INFO VERSIONINFO FILEVERSION 2,0,0,0 @@ -94,14 +94,14 @@ BEGIN VALUE "Comments", "A kart racing mod based on the 3D Sonic the Hedgehog fangame Sonic Robo Blast 2.\0" VALUE "CompanyName", "BlanKart Dev\0" VALUE "FileDescription", "BlanKart\0" - VALUE "FileVersion", VERSIONSTRING_RC + VALUE "FileVersion", "2.0\0" VALUE "InternalName", "blankart\0" VALUE "LegalCopyright", "Copyright 2025 by BlanKart Dev, SRB2Kart Copyright 2018-2020 by Kart Krew\0" VALUE "LegalTrademarks", "Sonic the Hedgehog and related characters are trademarks of Sega, SRB2Kart Copyright 2018-2020 by Kart Krew\0" VALUE "OriginalFilename", "blankart.exe\0" VALUE "PrivateBuild", "\0" VALUE "ProductName", "BlanKart\0" - VALUE "ProductVersion", VERSIONSTRING_RC + VALUE "ProductVersion", "2.0\0" VALUE "SpecialBuild", "\0" END END diff --git a/src/z_zone.cpp b/src/z_zone.cpp index 2fea1af85..1a56a78d4 100644 --- a/src/z_zone.cpp +++ b/src/z_zone.cpp @@ -28,7 +28,7 @@ #include #include -//#include +//#include #include "core/memory.h" #include "doomdef.h" @@ -41,7 +41,7 @@ #include "lua_script.h" #include "i_threads.h" -#include +#include #ifdef HWRENDER #include "hardware/hw_main.h" // For hardware memory info -- 2.56.0