diff --git a/Frameworks/OpenMPT/OpenMPT/Makefile b/Frameworks/OpenMPT/OpenMPT/Makefile index e2e82dad8..3cc4a0cfc 100644 --- a/Frameworks/OpenMPT/OpenMPT/Makefile +++ b/Frameworks/OpenMPT/OpenMPT/Makefile @@ -51,9 +51,12 @@ # XMP_OPENMPT=0 Build xmp-openmpt (XMPlay plugin) # SHARED_SONAME=1 Set SONAME of shared library # DEBUG=0 Build debug binaries without optimization and with symbols -# OPTIMIZE=vectorize -O3 +# OPTIMIZE=default vectorize +# vectorize -O3 # speed -O2 -# size -Os/-Oz +# size -Os +# extrasize -Oz +# some -O1 # test -Og # debug -O0 # none @@ -144,18 +147,41 @@ all: INFO = @echo SILENT = @ VERYSILENT = @ - +define PRINT_TRACE +endef +define PRINT_DEBUG +endef +define PRINT_INFO +$(info $1) +endef ifeq ($(VERBOSE),2) INFO = @true SILENT = VERYSILENT = +define PRINT_TRACE +$(info $1) +endef +define PRINT_DEBUG +$(info $1) +endef +define PRINT_INFO +$(info $1) +endef endif ifeq ($(VERBOSE),1) INFO = @true SILENT = VERYSILENT = @ +define PRINT_TRACE +endef +define PRINT_DEBUG +$(info $1) +endef +define PRINT_INFO +$(info $1) +endef endif @@ -163,9 +189,14 @@ ifeq ($(QUIET),1) INFO = @true SILENT = @ VERYSILENT = @ +define PRINT_TRACE +endef +define PRINT_DEBUG +endef +define PRINT_INFO +endef endif - # general settings DYNLINK=1 @@ -175,7 +206,7 @@ EXAMPLES=1 FUZZ=0 SHARED_SONAME=1 DEBUG=0 -OPTIMIZE=vectorize +OPTIMIZE=default OPTIMIZE_LTO=0 OPTIMIZE_FASTMATH=0 TEST=1 @@ -342,6 +373,50 @@ endif endif +# tar + +ifeq ($(findstring Darwin,$(UNAME_S)),Darwin) +TAR_C=tar -c --format pax -f +else ifeq ($(findstring OpenBSD,$(UNAME_S)),OpenBSD) +UNAME_R:=$(shell uname -r) +ifeq ($(findstring 1.,$(UNAME_R)),1.) +TAR_C=tar -c -N +else ifeq ($(findstring 1.,$(UNAME_R)),1.) +TAR_C=tar -c -N +else ifeq ($(findstring 2.,$(UNAME_R)),2.) +TAR_C=tar -c -N +else ifeq ($(findstring 3.,$(UNAME_R)),3.) +TAR_C=tar -c -N +else ifeq ($(findstring 4.,$(UNAME_R)),4.) +TAR_C=tar -c -N +else ifeq ($(findstring 5.,$(UNAME_R)),5.) +TAR_C=tar -c -N +else ifeq ($(findstring 6.,$(UNAME_R)),6.) +TAR_C=tar -c -N +else ifeq ($(findstring 7.0,$(UNAME_R)),7.0) +TAR_C=tar -c -N +else ifeq ($(findstring 7.1,$(UNAME_R)),7.1) +TAR_C=tar -c -N +else ifeq ($(findstring 7.2,$(UNAME_R)),7.2) +TAR_C=tar -c -N +else ifeq ($(findstring 7.3,$(UNAME_R)),7.3) +TAR_C=tar -c -N +else ifeq ($(findstring 7.4,$(UNAME_R)),7.4) +TAR_C=tar -c -N +else ifeq ($(findstring 7.5,$(UNAME_R)),7.5) +TAR_C=tar -c -N +else +TAR_C=tar -c -F pax -N +endif +else ifeq ($(findstring BSD,$(UNAME_S)),BSD) +TAR_C=tar -c --format pax --numeric-owner --uname "" --gname "" --uid 0 --gid 0 +else +# GNU +TAR_C=tar -c --format=pax --numeric-owner --owner=0 --group=0 +#TAR_C=tar -c +endif + + # early build setup BINDIR_MADE:=$(shell $(MKDIR_P) bin) @@ -462,15 +537,56 @@ CXXFLAGS += -g CFLAGS += -g else ifeq ($(OPTIMIZE),debug) CPPFLAGS += -CXXFLAGS += -O0 -fno-omit-frame-pointer -CFLAGS += -O0 -fno-omit-frame-pointer +ifneq ($(MPT_COMPILER_NO_O),1) +CXXFLAGS += -O0 +CFLAGS += -O0 +endif +CXXFLAGS += -fno-omit-frame-pointer +CFLAGS += -fno-omit-frame-pointer else ifeq ($(OPTIMIZE),test) CPPFLAGS += -CXXFLAGS += -Og -fno-omit-frame-pointer -CFLAGS += -Og -fno-omit-frame-pointer +ifneq ($(MPT_COMPILER_NO_O),1) +CXXFLAGS += -Og +CFLAGS += -Og +endif +CXXFLAGS += -fno-omit-frame-pointer +CFLAGS += -fno-omit-frame-pointer +else ifeq ($(OPTIMIZE),some) +ifneq ($(MPT_COMPILER_NO_O),1) +CXXFLAGS += -O1 +CFLAGS += -O1 +endif +CXXFLAGS += +CFLAGS += -fno-strict-aliasing +ifneq ($(MPT_COMPILER_NOSECTIONS),1) +CXXFLAGS += -ffunction-sections -fdata-sections +CFLAGS += -ffunction-sections -fdata-sections +endif +ifneq ($(MPT_COMPILER_NOGCSECTIONS),1) +LDFLAGS += -Wl,--gc-sections +endif +else ifeq ($(OPTIMIZE),extrasize) +ifneq ($(MPT_COMPILER_NO_O),1) +CXXFLAGS += -Oz +CFLAGS += -Oz +endif +CXXFLAGS += +CFLAGS += -fno-strict-aliasing +LDFLAGS += +ifneq ($(MPT_COMPILER_NOSECTIONS),1) +CXXFLAGS += -ffunction-sections -fdata-sections +CFLAGS += -ffunction-sections -fdata-sections +endif +ifneq ($(MPT_COMPILER_NOGCSECTIONS),1) +LDFLAGS += -Wl,--gc-sections +endif else ifeq ($(OPTIMIZE),size) +ifneq ($(MPT_COMPILER_NO_O),1) CXXFLAGS += -Os -CFLAGS += -Os -fno-strict-aliasing +CFLAGS += -Os +endif +CXXFLAGS += +CFLAGS += -fno-strict-aliasing LDFLAGS += ifneq ($(MPT_COMPILER_NOSECTIONS),1) CXXFLAGS += -ffunction-sections -fdata-sections @@ -480,8 +596,12 @@ ifneq ($(MPT_COMPILER_NOGCSECTIONS),1) LDFLAGS += -Wl,--gc-sections endif else ifeq ($(OPTIMIZE),speed) +ifneq ($(MPT_COMPILER_NO_O),1) CXXFLAGS += -O2 -CFLAGS += -O2 -fno-strict-aliasing +CFLAGS += -O2 +endif +CXXFLAGS += +CFLAGS += -fno-strict-aliasing ifneq ($(MPT_COMPILER_NOSECTIONS),1) CXXFLAGS += -ffunction-sections -fdata-sections CFLAGS += -ffunction-sections -fdata-sections @@ -490,8 +610,26 @@ ifneq ($(MPT_COMPILER_NOGCSECTIONS),1) LDFLAGS += -Wl,--gc-sections endif else ifeq ($(OPTIMIZE),vectorize) +ifneq ($(MPT_COMPILER_NO_O),1) CXXFLAGS += -O3 -CFLAGS += -O3 -fno-strict-aliasing +CFLAGS += -O3 +endif +CXXFLAGS += +CFLAGS += -fno-strict-aliasing +ifneq ($(MPT_COMPILER_NOSECTIONS),1) +CXXFLAGS += -ffunction-sections -fdata-sections +CFLAGS += -ffunction-sections -fdata-sections +endif +ifneq ($(MPT_COMPILER_NOGCSECTIONS),1) +LDFLAGS += -Wl,--gc-sections +endif +else ifeq ($(OPTIMIZE),default) +ifneq ($(MPT_COMPILER_NO_O),1) +CXXFLAGS += -O3 +CFLAGS += -O3 +endif +CXXFLAGS += +CFLAGS += -fno-strict-aliasing ifneq ($(MPT_COMPILER_NOSECTIONS),1) CXXFLAGS += -ffunction-sections -fdata-sections CFLAGS += -ffunction-sections -fdata-sections @@ -631,6 +769,9 @@ CXXFLAGS += -Wall -Wextra -Wpedantic $(CXXFLAGS_WARNINGS) CFLAGS += -Wall -Wextra -Wpedantic $(CFLAGS_WARNINGS) LDFLAGS += $(LDFLAGS_WARNINGS) +CXXFLAGS += $(OVERWRITE_CXXFLAGS) +CFLAGS += $(OVERWRITE_CFLAGS) + endif ifeq ($(STRICT),1) @@ -665,39 +806,49 @@ ifeq ($(HACK_ARCHIVE_SUPPORT),1) NO_ZLIB:=1 endif +$(call PRINT_TRACE,[DEP] zlib) ifeq ($(LOCAL_ZLIB),1) +$(call PRINT_INFO,[DEP] zlib: local) CPPFLAGS_ZLIB := -DMPT_WITH_ZLIB LDFLAGS_ZLIB := LDLIBS_ZLIB := CPPFLAGS_ZLIB += -Iinclude/zlib/ -LOCAL_ZLIB_SOURCES := -LOCAL_ZLIB_SOURCES += include/zlib/adler32.c -LOCAL_ZLIB_SOURCES += include/zlib/compress.c -LOCAL_ZLIB_SOURCES += include/zlib/crc32.c -LOCAL_ZLIB_SOURCES += include/zlib/deflate.c -LOCAL_ZLIB_SOURCES += include/zlib/gzclose.c -LOCAL_ZLIB_SOURCES += include/zlib/gzlib.c -LOCAL_ZLIB_SOURCES += include/zlib/gzread.c -LOCAL_ZLIB_SOURCES += include/zlib/gzwrite.c -LOCAL_ZLIB_SOURCES += include/zlib/infback.c -LOCAL_ZLIB_SOURCES += include/zlib/inffast.c -LOCAL_ZLIB_SOURCES += include/zlib/inflate.c -LOCAL_ZLIB_SOURCES += include/zlib/inftrees.c -LOCAL_ZLIB_SOURCES += include/zlib/trees.c -LOCAL_ZLIB_SOURCES += include/zlib/uncompr.c -LOCAL_ZLIB_SOURCES += include/zlib/zutil.c -include/zlib/%$(FLAVOUR_O).o : CFLAGS+=$(CFLAGS_SILENT) -DSTDC -DZ_HAVE_UNISTD_H -include/zlib/%.test$(FLAVOUR_O).o : CFLAGS+=$(CFLAGS_SILENT) -DSTDC -DZ_HAVE_UNISTD_H +ZLIB_SOURCES := +ZLIB_SOURCES += include/zlib/adler32.c +ZLIB_SOURCES += include/zlib/compress.c +ZLIB_SOURCES += include/zlib/crc32.c +ZLIB_SOURCES += include/zlib/deflate.c +ZLIB_SOURCES += include/zlib/gzclose.c +ZLIB_SOURCES += include/zlib/gzlib.c +ZLIB_SOURCES += include/zlib/gzread.c +ZLIB_SOURCES += include/zlib/gzwrite.c +ZLIB_SOURCES += include/zlib/infback.c +ZLIB_SOURCES += include/zlib/inffast.c +ZLIB_SOURCES += include/zlib/inflate.c +ZLIB_SOURCES += include/zlib/inftrees.c +ZLIB_SOURCES += include/zlib/trees.c +ZLIB_SOURCES += include/zlib/uncompr.c +ZLIB_SOURCES += include/zlib/zutil.c +include/zlib/%.zlib$(FLAVOUR_O).o : CFLAGS+=$(CFLAGS_SILENT) -DSTDC -DZ_HAVE_UNISTD_H +ZLIB_OBJECTS = $(ZLIB_SOURCES:.c=.zlib$(FLAVOUR_O).o) +ZLIB_DEPENDS = $(ZLIB_OBJECTS:$(FLAVOUR_O).o=$(FLAVOUR_O).d) +ALL_OBJECTS += $(ZLIB_OBJECTS) +ALL_DEPENDS += $(ZLIB_DEPENDS) +OBJECTS_ZLIB = $(ZLIB_OBJECTS) else ifeq ($(NO_ZLIB),1) +$(call PRINT_INFO,[DEP] zlib: disabled) else #LDLIBS += -lz +$(call PRINT_DEBUG,[DEP] zlib: checking pkg-config ...) ifeq ($(shell $(PKG_CONFIG)$(TOOLCHAIN_SUFFIX) --exists zlib && echo yes),yes) +$(call PRINT_INFO,[DEP] zlib: pkg-config/zlib) CPPFLAGS_ZLIB := $(shell $(PKG_CONFIG)$(TOOLCHAIN_SUFFIX) --cflags-only-I zlib ) -DMPT_WITH_ZLIB LDFLAGS_ZLIB := $(shell $(PKG_CONFIG)$(TOOLCHAIN_SUFFIX) --libs-only-L zlib ) $(shell $(PKG_CONFIG)$(TOOLCHAIN_SUFFIX) --libs-only-other zlib ) LDLIBS_ZLIB := $(shell $(PKG_CONFIG)$(TOOLCHAIN_SUFFIX) --libs-only-l zlib ) PC_REQUIRES_ZLIB := zlib else +$(call PRINT_INFO,[DEP] zlib: no) ifeq ($(FORCE_DEPS),1) $(error zlib not found) else @@ -708,56 +859,179 @@ endif endif endif +$(call PRINT_TRACE,[DEP] mpg123) ifeq ($(LOCAL_MPG123),1) +$(call PRINT_INFO,[DEP] mpg123: local) + +ifeq ($(ENABLE_DXE),1) + CPPFLAGS_MPG123 := -DMPT_WITH_MPG123 -DMPG123_NO_LARGENAME LDFLAGS_MPG123 := +LDLIBS_MPG123 := +CPPFLAGS_MPG123 += -Iinclude/mpg123/src/include/ -Iinclude/mpg123/ports/generic/ +MPG123_SOURCES := +MPG123_SOURCES += include/mpg123/src/compat/compat.c +MPG123_SOURCES += include/mpg123/src/compat/compat_str.c +MPG123_SOURCES += include/mpg123/src/libmpg123/dct64.c +MPG123_SOURCES += include/mpg123/src/libmpg123/equalizer.c +MPG123_SOURCES += include/mpg123/src/libmpg123/feature.c +MPG123_SOURCES += include/mpg123/src/libmpg123/format.c +MPG123_SOURCES += include/mpg123/src/libmpg123/frame.c +MPG123_SOURCES += include/mpg123/src/libmpg123/icy.c +MPG123_SOURCES += include/mpg123/src/libmpg123/icy2utf8.c +MPG123_SOURCES += include/mpg123/src/libmpg123/id3.c +MPG123_SOURCES += include/mpg123/src/libmpg123/index.c +MPG123_SOURCES += include/mpg123/src/libmpg123/layer1.c +MPG123_SOURCES += include/mpg123/src/libmpg123/layer2.c +MPG123_SOURCES += include/mpg123/src/libmpg123/layer3.c +MPG123_SOURCES += include/mpg123/src/libmpg123/lfs_wrap.c +MPG123_SOURCES += include/mpg123/src/libmpg123/libmpg123.c +MPG123_SOURCES += include/mpg123/src/libmpg123/ntom.c +MPG123_SOURCES += include/mpg123/src/libmpg123/optimize.c +MPG123_SOURCES += include/mpg123/src/libmpg123/parse.c +MPG123_SOURCES += include/mpg123/src/libmpg123/readers.c +MPG123_SOURCES += include/mpg123/src/libmpg123/stringbuf.c +MPG123_SOURCES += include/mpg123/src/libmpg123/synth.c +MPG123_SOURCES += include/mpg123/src/libmpg123/synth_8bit.c +MPG123_SOURCES += include/mpg123/src/libmpg123/synth_real.c +MPG123_SOURCES += include/mpg123/src/libmpg123/synth_s32.c +MPG123_SOURCES += include/mpg123/src/libmpg123/tabinit.c +MPG123_OBJECTS += $(MPG123_SOURCES:.c=.mpg123$(FLAVOUR_O).o) +MPG123_DEPENDS = $(MPG123_OBJECTS:$(FLAVOUR_O).o=$(FLAVOUR_O).d) +ALL_OBJECTS += $(MPG123_OBJECTS) +ALL_DEPENDS += $(MPG123_DEPENDS) +include/mpg123/src/compat/%.mpg123$(FLAVOUR_O).o : CFLAGS+=$(CFLAGS_SILENT) -DOPT_GENERIC +include/mpg123/src/libmpg123/%.mpg123$(FLAVOUR_O).o : CFLAGS+=$(CFLAGS_SILENT) -DOPT_GENERIC +include/mpg123/src/compat/%.mpg123$(FLAVOUR_O).o : CPPFLAGS:= -Iinclude/mpg123/src/include/ -Iinclude/mpg123/ports/generic/ $(CPPFLAGS) +include/mpg123/src/libmpg123/%.mpg123$(FLAVOUR_O).o : CPPFLAGS:= -Iinclude/mpg123/src/include/ -Iinclude/mpg123/ports/generic/ $(CPPFLAGS) +LIBS_MPG123 = bin/$(FLAVOUR_DIR)mpg123.a + +bin/$(FLAVOUR_DIR)mpg123.a: $(MPG123_OBJECTS) + $(INFO) [DXE] $@ +ifeq ($(NO_SHARED_LINKER_FLAG),1) + $(SILENT)PATH="./build/djgpp/bin:${PATH}" $(DXE3GEN) -o bin/$(FLAVOUR_DIR)mpg123$(SOSUFFIX) -Y $@ -U $^ $(MPG123_LDFLAGS) $(SO_LDFLAGS) +else + $(SILENT)PATH="./build/djgpp/bin:${PATH}" $(DXE3GEN) -o bin/$(FLAVOUR_DIR)mpg123$(SOSUFFIX) -Y $@ -U $^ -shared $(MPG123_LDFLAGS) $(SO_LDFLAGS) +endif +ifeq ($(SHARED_SONAME),1) + $(SILENT)mv bin/$(FLAVOUR_DIR)mpg123$(SOSUFFIX) bin/$(FLAVOUR_DIR)$(MPG123_SONAME) + $(SILENT)ln -sf $(MPG123_SONAME) bin/$(FLAVOUR_DIR)mpg123$(SOSUFFIX) +endif + +else ifeq ($(ENABLE_DLL),1) + +CPPFLAGS_MPG123 := -DMPT_WITH_MPG123 -DMPG123_NO_LARGENAME +LDFLAGS_MPG123 := +LDLIBS_MPG123 := +CPPFLAGS_MPG123 += -Iinclude/mpg123/src/include/ -Iinclude/mpg123/ports/generic/ +MPG123_SOURCES := +MPG123_SOURCES += include/mpg123/src/compat/compat.c +MPG123_SOURCES += include/mpg123/src/compat/compat_str.c +MPG123_SOURCES += include/mpg123/src/libmpg123/dct64.c +MPG123_SOURCES += include/mpg123/src/libmpg123/equalizer.c +MPG123_SOURCES += include/mpg123/src/libmpg123/feature.c +MPG123_SOURCES += include/mpg123/src/libmpg123/format.c +MPG123_SOURCES += include/mpg123/src/libmpg123/frame.c +MPG123_SOURCES += include/mpg123/src/libmpg123/icy.c +MPG123_SOURCES += include/mpg123/src/libmpg123/icy2utf8.c +MPG123_SOURCES += include/mpg123/src/libmpg123/id3.c +MPG123_SOURCES += include/mpg123/src/libmpg123/index.c +MPG123_SOURCES += include/mpg123/src/libmpg123/layer1.c +MPG123_SOURCES += include/mpg123/src/libmpg123/layer2.c +MPG123_SOURCES += include/mpg123/src/libmpg123/layer3.c +MPG123_SOURCES += include/mpg123/src/libmpg123/lfs_wrap.c +MPG123_SOURCES += include/mpg123/src/libmpg123/libmpg123.c +MPG123_SOURCES += include/mpg123/src/libmpg123/ntom.c +MPG123_SOURCES += include/mpg123/src/libmpg123/optimize.c +MPG123_SOURCES += include/mpg123/src/libmpg123/parse.c +MPG123_SOURCES += include/mpg123/src/libmpg123/readers.c +MPG123_SOURCES += include/mpg123/src/libmpg123/stringbuf.c +MPG123_SOURCES += include/mpg123/src/libmpg123/synth.c +MPG123_SOURCES += include/mpg123/src/libmpg123/synth_8bit.c +MPG123_SOURCES += include/mpg123/src/libmpg123/synth_real.c +MPG123_SOURCES += include/mpg123/src/libmpg123/synth_s32.c +MPG123_SOURCES += include/mpg123/src/libmpg123/tabinit.c +MPG123_OBJECTS += $(MPG123_SOURCES:.c=.mpg123$(FLAVOUR_O).o) +MPG123_DEPENDS = $(MPG123_OBJECTS:$(FLAVOUR_O).o=$(FLAVOUR_O).d) +ALL_OBJECTS += $(MPG123_OBJECTS) +ALL_DEPENDS += $(MPG123_DEPENDS) +include/mpg123/src/compat/%.mpg123$(FLAVOUR_O).o : CFLAGS+=$(CFLAGS_SILENT) -DOPT_GENERIC +include/mpg123/src/libmpg123/%.mpg123$(FLAVOUR_O).o : CFLAGS+=$(CFLAGS_SILENT) -DOPT_GENERIC +include/mpg123/src/compat/%.mpg123$(FLAVOUR_O).o : CPPFLAGS:= -Iinclude/mpg123/src/include/ -Iinclude/mpg123/ports/generic/ $(CPPFLAGS) +include/mpg123/src/libmpg123/%.mpg123$(FLAVOUR_O).o : CPPFLAGS:= -Iinclude/mpg123/src/include/ -Iinclude/mpg123/ports/generic/ $(CPPFLAGS) +LIBS_MPG123 = bin/$(FLAVOUR_DIR)mpg123$(SOSUFFIX) + +bin/$(FLAVOUR_DIR)mpg123$(SOSUFFIX): $(MPG123_OBJECTS) + $(INFO) [LD] $@ +ifeq ($(NO_SHARED_LINKER_FLAG),1) + $(SILENT)$(LINK.cc) -shared $(MPG123_LDFLAGS) $(SO_LDFLAGS) $^ -o $@ +else + $(SILENT)$(LINK.cc) -shared $(MPG123_LDFLAGS) $(SO_LDFLAGS) $^ -o $@ +endif +ifeq ($(SHARED_SONAME),1) + $(SILENT)mv bin/$(FLAVOUR_DIR)mpg123$(SOSUFFIX) bin/$(FLAVOUR_DIR)$(MPG123_SONAME) + $(SILENT)ln -sf $(MPG123_SONAME) bin/$(FLAVOUR_DIR)mpg123$(SOSUFFIX) +endif + +else + +CPPFLAGS_MPG123 := -DMPT_WITH_MPG123 -DMPG123_NO_LARGENAME +LDFLAGS_MPG123 := LDLIBS_MPG123 := CPPFLAGS_MPG123 += -Iinclude/mpg123/src/include/ -Iinclude/mpg123/ports/generic/ -LOCAL_MPG123_SOURCES := -LOCAL_MPG123_SOURCES += include/mpg123/src/compat/compat.c -LOCAL_MPG123_SOURCES += include/mpg123/src/compat/compat_str.c -LOCAL_MPG123_SOURCES += include/mpg123/src/libmpg123/dct64.c -LOCAL_MPG123_SOURCES += include/mpg123/src/libmpg123/equalizer.c -LOCAL_MPG123_SOURCES += include/mpg123/src/libmpg123/feature.c -LOCAL_MPG123_SOURCES += include/mpg123/src/libmpg123/format.c -LOCAL_MPG123_SOURCES += include/mpg123/src/libmpg123/frame.c -LOCAL_MPG123_SOURCES += include/mpg123/src/libmpg123/icy.c -LOCAL_MPG123_SOURCES += include/mpg123/src/libmpg123/icy2utf8.c -LOCAL_MPG123_SOURCES += include/mpg123/src/libmpg123/id3.c -LOCAL_MPG123_SOURCES += include/mpg123/src/libmpg123/index.c -LOCAL_MPG123_SOURCES += include/mpg123/src/libmpg123/layer1.c -LOCAL_MPG123_SOURCES += include/mpg123/src/libmpg123/layer2.c -LOCAL_MPG123_SOURCES += include/mpg123/src/libmpg123/layer3.c -LOCAL_MPG123_SOURCES += include/mpg123/src/libmpg123/lfs_wrap.c -LOCAL_MPG123_SOURCES += include/mpg123/src/libmpg123/libmpg123.c -LOCAL_MPG123_SOURCES += include/mpg123/src/libmpg123/ntom.c -LOCAL_MPG123_SOURCES += include/mpg123/src/libmpg123/optimize.c -LOCAL_MPG123_SOURCES += include/mpg123/src/libmpg123/parse.c -LOCAL_MPG123_SOURCES += include/mpg123/src/libmpg123/readers.c -LOCAL_MPG123_SOURCES += include/mpg123/src/libmpg123/stringbuf.c -LOCAL_MPG123_SOURCES += include/mpg123/src/libmpg123/synth.c -LOCAL_MPG123_SOURCES += include/mpg123/src/libmpg123/synth_8bit.c -LOCAL_MPG123_SOURCES += include/mpg123/src/libmpg123/synth_real.c -LOCAL_MPG123_SOURCES += include/mpg123/src/libmpg123/synth_s32.c -LOCAL_MPG123_SOURCES += include/mpg123/src/libmpg123/tabinit.c -include/mpg123/src/compat/%$(FLAVOUR_O).o : CFLAGS+=$(CFLAGS_SILENT) -DOPT_GENERIC -include/mpg123/src/compat/%.test$(FLAVOUR_O).o : CFLAGS+=$(CFLAGS_SILENT) -DOPT_GENERIC -include/mpg123/src/libmpg123/%$(FLAVOUR_O).o : CFLAGS+=$(CFLAGS_SILENT) -DOPT_GENERIC -include/mpg123/src/libmpg123/%.test$(FLAVOUR_O).o : CFLAGS+=$(CFLAGS_SILENT) -DOPT_GENERIC -include/mpg123/src/compat/%$(FLAVOUR_O).o : CPPFLAGS:= -Iinclude/mpg123/src/include/ -Iinclude/mpg123/ports/generic/ $(CPPFLAGS) -include/mpg123/src/compat/%.test$(FLAVOUR_O).o : CPPFLAGS:= -Iinclude/mpg123/src/include/ -Iinclude/mpg123/ports/generic/ $(CPPFLAGS) -include/mpg123/src/libmpg123/%$(FLAVOUR_O).o : CPPFLAGS:= -Iinclude/mpg123/src/include/ -Iinclude/mpg123/ports/generic/ $(CPPFLAGS) -include/mpg123/src/libmpg123/%.test$(FLAVOUR_O).o : CPPFLAGS:= -Iinclude/mpg123/src/include/ -Iinclude/mpg123/ports/generic/ $(CPPFLAGS) +MPG123_SOURCES := +MPG123_SOURCES += include/mpg123/src/compat/compat.c +MPG123_SOURCES += include/mpg123/src/compat/compat_str.c +MPG123_SOURCES += include/mpg123/src/libmpg123/dct64.c +MPG123_SOURCES += include/mpg123/src/libmpg123/equalizer.c +MPG123_SOURCES += include/mpg123/src/libmpg123/feature.c +MPG123_SOURCES += include/mpg123/src/libmpg123/format.c +MPG123_SOURCES += include/mpg123/src/libmpg123/frame.c +MPG123_SOURCES += include/mpg123/src/libmpg123/icy.c +MPG123_SOURCES += include/mpg123/src/libmpg123/icy2utf8.c +MPG123_SOURCES += include/mpg123/src/libmpg123/id3.c +MPG123_SOURCES += include/mpg123/src/libmpg123/index.c +MPG123_SOURCES += include/mpg123/src/libmpg123/layer1.c +MPG123_SOURCES += include/mpg123/src/libmpg123/layer2.c +MPG123_SOURCES += include/mpg123/src/libmpg123/layer3.c +MPG123_SOURCES += include/mpg123/src/libmpg123/lfs_wrap.c +MPG123_SOURCES += include/mpg123/src/libmpg123/libmpg123.c +MPG123_SOURCES += include/mpg123/src/libmpg123/ntom.c +MPG123_SOURCES += include/mpg123/src/libmpg123/optimize.c +MPG123_SOURCES += include/mpg123/src/libmpg123/parse.c +MPG123_SOURCES += include/mpg123/src/libmpg123/readers.c +MPG123_SOURCES += include/mpg123/src/libmpg123/stringbuf.c +MPG123_SOURCES += include/mpg123/src/libmpg123/synth.c +MPG123_SOURCES += include/mpg123/src/libmpg123/synth_8bit.c +MPG123_SOURCES += include/mpg123/src/libmpg123/synth_real.c +MPG123_SOURCES += include/mpg123/src/libmpg123/synth_s32.c +MPG123_SOURCES += include/mpg123/src/libmpg123/tabinit.c +include/mpg123/src/compat/%.mpg123$(FLAVOUR_O).o : CFLAGS+=$(CFLAGS_SILENT) -DOPT_GENERIC +include/mpg123/src/libmpg123/%.mpg123$(FLAVOUR_O).o : CFLAGS+=$(CFLAGS_SILENT) -DOPT_GENERIC +include/mpg123/src/compat/%.mpg123$(FLAVOUR_O).o : CPPFLAGS:= -Iinclude/mpg123/src/include/ -Iinclude/mpg123/ports/generic/ $(CPPFLAGS) +include/mpg123/src/libmpg123/%.mpg123$(FLAVOUR_O).o : CPPFLAGS:= -Iinclude/mpg123/src/include/ -Iinclude/mpg123/ports/generic/ $(CPPFLAGS) +MPG123_OBJECTS = $(MPG123_SOURCES:.c=.mpg123$(FLAVOUR_O).o) +MPG123_DEPENDS = $(MPG123_OBJECTS:$(FLAVOUR_O).o=$(FLAVOUR_O).d) +ALL_OBJECTS += $(MPG123_OBJECTS) +ALL_DEPENDS += $(MPG123_DEPENDS) +OBJECTS_MPG123 = $(MPG123_OBJECTS) + +endif + else ifeq ($(NO_MPG123),1) +$(call PRINT_INFO,[DEP] mpg123: disabled) else #LDLIBS += -lmpg123 +$(call PRINT_DEBUG,[DEP] mpg123: checking pkg-config ...) ifeq ($(shell $(PKG_CONFIG)$(TOOLCHAIN_SUFFIX) --exists 'libmpg123 >= 1.14.0' && echo yes),yes) +$(call PRINT_INFO,[DEP] mpg123: pkg-config/mpg123) CPPFLAGS_MPG123 := $(shell $(PKG_CONFIG)$(TOOLCHAIN_SUFFIX) --cflags-only-I 'libmpg123 >= 1.14.0' ) -DMPT_WITH_MPG123 LDFLAGS_MPG123 := $(shell $(PKG_CONFIG)$(TOOLCHAIN_SUFFIX) --libs-only-L 'libmpg123 >= 1.14.0' ) $(shell $(PKG_CONFIG)$(TOOLCHAIN_SUFFIX) --libs-only-other 'libmpg123 >= 1.14.0' ) LDLIBS_MPG123 := $(shell $(PKG_CONFIG)$(TOOLCHAIN_SUFFIX) --libs-only-l 'libmpg123 >= 1.14.0' ) PC_REQUIRES_MPG123 := libmpg123 else +$(call PRINT_INFO,[DEP] mpg123: no) ifeq ($(FORCE_DEPS),1) $(error mpg123 not found) else @@ -768,26 +1042,36 @@ endif endif endif +$(call PRINT_TRACE,[DEP] ogg) ifeq ($(LOCAL_OGG),1) +$(call PRINT_INFO,[DEP] ogg: local) CPPFLAGS_OGG := -DMPT_WITH_OGG LDFLAGS_OGG := LDLIBS_OGG := CPPFLAGS_OGG += -Iinclude/ogg/include/ -Iinclude/ogg/ports/makefile/ -LOCAL_OGG_SOURCES := -LOCAL_OGG_SOURCES += include/ogg/src/bitwise.c -LOCAL_OGG_SOURCES += include/ogg/src/framing.c -include/ogg/src/%$(FLAVOUR_O).o : CFLAGS+=$(CFLAGS_SILENT) -include/ogg/src/%.test$(FLAVOUR_O).o : CFLAGS+=$(CFLAGS_SILENT) +OGG_SOURCES := +OGG_SOURCES += include/ogg/src/bitwise.c +OGG_SOURCES += include/ogg/src/framing.c +include/ogg/src/%.ogg$(FLAVOUR_O).o : CFLAGS+=$(CFLAGS_SILENT) +OGG_OBJECTS += $(OGG_SOURCES:.c=.ogg$(FLAVOUR_O).o) +OGG_DEPENDS = $(OGG_OBJECTS:$(FLAVOUR_O).o=$(FLAVOUR_O).d) +ALL_OBJECTS += $(OGG_OBJECTS) +ALL_DEPENDS += $(OGG_DEPENDS) +OBJECTS_OGG = $(OGG_OBJECTS) else ifeq ($(NO_OGG),1) +$(call PRINT_INFO,[DEP] ogg: disabled) else #LDLIBS += -logg +$(call PRINT_DEBUG,[DEP] ogg: checking pkg-config ...) ifeq ($(shell $(PKG_CONFIG)$(TOOLCHAIN_SUFFIX) --exists ogg && echo yes),yes) +$(call PRINT_INFO,[DEP] ogg: pkg-config/ogg) CPPFLAGS_OGG := $(shell $(PKG_CONFIG)$(TOOLCHAIN_SUFFIX) --cflags-only-I ogg ) -DMPT_WITH_OGG LDFLAGS_OGG := $(shell $(PKG_CONFIG)$(TOOLCHAIN_SUFFIX) --libs-only-L ogg ) $(shell $(PKG_CONFIG)$(TOOLCHAIN_SUFFIX) --libs-only-other ogg ) LDLIBS_OGG := $(shell $(PKG_CONFIG)$(TOOLCHAIN_SUFFIX) --libs-only-l ogg ) PC_REQUIRES_OGG := ogg else +$(call PRINT_INFO,[DEP] ogg: no) ifeq ($(FORCE_DEPS),1) $(error ogg not found) else @@ -798,7 +1082,9 @@ endif endif endif +$(call PRINT_TRACE,[DEP] vorbis) ifeq ($(LOCAL_VORBIS),1) +$(call PRINT_INFO,[DEP] vorbis: local) CPPFLAGS_VORBIS := -DMPT_WITH_VORBIS LDFLAGS_VORBIS := LDLIBS_VORBIS := @@ -806,41 +1092,49 @@ CPPFLAGS_VORBIS += -Iinclude/vorbis/include/ -Iinclude/vorbis/lib/ ifneq ($(MPT_COMPILER_NOALLOCAH),1) CPPFLAGS_VORBIS += -DHAVE_ALLOCA_H endif -LOCAL_VORBIS_SOURCES := -LOCAL_VORBIS_SOURCES += include/vorbis/lib/analysis.c -LOCAL_VORBIS_SOURCES += include/vorbis/lib/bitrate.c -LOCAL_VORBIS_SOURCES += include/vorbis/lib/block.c -LOCAL_VORBIS_SOURCES += include/vorbis/lib/codebook.c -LOCAL_VORBIS_SOURCES += include/vorbis/lib/envelope.c -LOCAL_VORBIS_SOURCES += include/vorbis/lib/floor0.c -LOCAL_VORBIS_SOURCES += include/vorbis/lib/floor1.c -LOCAL_VORBIS_SOURCES += include/vorbis/lib/info.c -LOCAL_VORBIS_SOURCES += include/vorbis/lib/lookup.c -LOCAL_VORBIS_SOURCES += include/vorbis/lib/lpc.c -LOCAL_VORBIS_SOURCES += include/vorbis/lib/lsp.c -LOCAL_VORBIS_SOURCES += include/vorbis/lib/mapping0.c -LOCAL_VORBIS_SOURCES += include/vorbis/lib/mdct.c -LOCAL_VORBIS_SOURCES += include/vorbis/lib/psy.c -LOCAL_VORBIS_SOURCES += include/vorbis/lib/registry.c -LOCAL_VORBIS_SOURCES += include/vorbis/lib/res0.c -LOCAL_VORBIS_SOURCES += include/vorbis/lib/sharedbook.c -LOCAL_VORBIS_SOURCES += include/vorbis/lib/smallft.c -LOCAL_VORBIS_SOURCES += include/vorbis/lib/synthesis.c -LOCAL_VORBIS_SOURCES += include/vorbis/lib/vorbisenc.c -LOCAL_VORBIS_SOURCES += include/vorbis/lib/vorbisfile.c -LOCAL_VORBIS_SOURCES += include/vorbis/lib/window.c -include/vorbis/lib/%$(FLAVOUR_O).o : CFLAGS+=$(CFLAGS_SILENT) -include/vorbis/lib/%.test$(FLAVOUR_O).o : CFLAGS+=$(CFLAGS_SILENT) +VORBIS_SOURCES := +VORBIS_SOURCES += include/vorbis/lib/analysis.c +VORBIS_SOURCES += include/vorbis/lib/bitrate.c +VORBIS_SOURCES += include/vorbis/lib/block.c +VORBIS_SOURCES += include/vorbis/lib/codebook.c +VORBIS_SOURCES += include/vorbis/lib/envelope.c +VORBIS_SOURCES += include/vorbis/lib/floor0.c +VORBIS_SOURCES += include/vorbis/lib/floor1.c +VORBIS_SOURCES += include/vorbis/lib/info.c +VORBIS_SOURCES += include/vorbis/lib/lookup.c +VORBIS_SOURCES += include/vorbis/lib/lpc.c +VORBIS_SOURCES += include/vorbis/lib/lsp.c +VORBIS_SOURCES += include/vorbis/lib/mapping0.c +VORBIS_SOURCES += include/vorbis/lib/mdct.c +VORBIS_SOURCES += include/vorbis/lib/psy.c +VORBIS_SOURCES += include/vorbis/lib/registry.c +VORBIS_SOURCES += include/vorbis/lib/res0.c +VORBIS_SOURCES += include/vorbis/lib/sharedbook.c +VORBIS_SOURCES += include/vorbis/lib/smallft.c +VORBIS_SOURCES += include/vorbis/lib/synthesis.c +VORBIS_SOURCES += include/vorbis/lib/vorbisenc.c +VORBIS_SOURCES += include/vorbis/lib/vorbisfile.c +VORBIS_SOURCES += include/vorbis/lib/window.c +include/vorbis/lib/%.vorbis$(FLAVOUR_O).o : CFLAGS+=$(CFLAGS_SILENT) +VORBIS_OBJECTS += $(VORBIS_SOURCES:.c=.vorbis$(FLAVOUR_O).o) +VORBIS_DEPENDS = $(VORBIS_OBJECTS:$(FLAVOUR_O).o=$(FLAVOUR_O).d) +ALL_OBJECTS += $(VORBIS_OBJECTS) +ALL_DEPENDS += $(VORBIS_DEPENDS) +OBJECTS_VORBIS = $(VORBIS_OBJECTS) else ifeq ($(NO_VORBIS),1) +$(call PRINT_INFO,[DEP] vorbis: disabled) else #LDLIBS += -lvorbis +$(call PRINT_DEBUG,[DEP] vorbis: checking pkg-config ...) ifeq ($(shell $(PKG_CONFIG)$(TOOLCHAIN_SUFFIX) --exists vorbis && echo yes),yes) +$(call PRINT_INFO,[DEP] vorbis: pkg-config/vorbis) CPPFLAGS_VORBIS := $(shell $(PKG_CONFIG)$(TOOLCHAIN_SUFFIX) --cflags-only-I vorbis ) -DMPT_WITH_VORBIS LDFLAGS_VORBIS := $(shell $(PKG_CONFIG)$(TOOLCHAIN_SUFFIX) --libs-only-L vorbis ) $(shell $(PKG_CONFIG)$(TOOLCHAIN_SUFFIX) --libs-only-other vorbis ) LDLIBS_VORBIS := $(shell $(PKG_CONFIG)$(TOOLCHAIN_SUFFIX) --libs-only-l vorbis ) PC_REQUIRES_VORBIS := vorbis else +$(call PRINT_INFO,[DEP] vorbis: no) ifeq ($(FORCE_DEPS),1) $(error vorbis not found) else @@ -851,20 +1145,26 @@ endif endif endif +$(call PRINT_TRACE,[DEP] vorbisfile) ifeq ($(LOCAL_VORBIS),1) +$(call PRINT_INFO,[DEP] vorbisfile: local) CPPFLAGS_VORBISFILE := -DMPT_WITH_VORBISFILE LDFLAGS_VORBISFILE := LDLIBS_VORBISFILE := else ifeq ($(NO_VORBISFILE),1) +$(call PRINT_INFO,[DEP] vorbisfile: disabled) else #LDLIBS += -lvorbisfile +$(call PRINT_DEBUG,[DEP] vorbisfile: checking pkg-config ...) ifeq ($(shell $(PKG_CONFIG)$(TOOLCHAIN_SUFFIX) --exists vorbisfile && echo yes),yes) +$(call PRINT_INFO,[DEP] vorbisfile: pkg-config/vorbisfile) CPPFLAGS_VORBISFILE := $(shell $(PKG_CONFIG)$(TOOLCHAIN_SUFFIX) --cflags-only-I vorbisfile ) -DMPT_WITH_VORBISFILE LDFLAGS_VORBISFILE := $(shell $(PKG_CONFIG)$(TOOLCHAIN_SUFFIX) --libs-only-L vorbisfile ) $(shell $(PKG_CONFIG)$(TOOLCHAIN_SUFFIX) --libs-only-other vorbisfile ) LDLIBS_VORBISFILE := $(shell $(PKG_CONFIG)$(TOOLCHAIN_SUFFIX) --libs-only-l vorbisfile ) PC_REQUIRES_VORBISFILE := vorbisfile else +$(call PRINT_INFO,[DEP] vorbisfile: no) ifeq ($(FORCE_DEPS),1) $(error vorbisfile not found) else @@ -875,14 +1175,19 @@ endif endif endif +$(call PRINT_TRACE,[DEP] SDL2) ifeq ($(NO_SDL2),1) +$(call PRINT_INFO,[DEP] SDL2: disabled) else #LDLIBS += -lsdl2 +$(call PRINT_DEBUG,[DEP] SDL2: checking pkg-config ...) ifeq ($(shell $(PKG_CONFIG)$(TOOLCHAIN_SUFFIX) --exists 'sdl2 >= 2.0.4' && echo yes),yes) +$(call PRINT_INFO,[DEP] SDL2: pkg-config/sdl2) CPPFLAGS_SDL2 := $(shell $(PKG_CONFIG)$(TOOLCHAIN_SUFFIX) --cflags-only-I 'sdl2 >= 2.0.4' ) -DMPT_WITH_SDL2 LDFLAGS_SDL2 := $(shell $(PKG_CONFIG)$(TOOLCHAIN_SUFFIX) --libs-only-L 'sdl2 >= 2.0.4' ) $(shell $(PKG_CONFIG)$(TOOLCHAIN_SUFFIX) --libs-only-other 'sdl2 >= 2.0.4' ) LDLIBS_SDL2 := $(shell $(PKG_CONFIG)$(TOOLCHAIN_SUFFIX) --libs-only-l 'sdl2 >= 2.0.4' ) else +$(call PRINT_INFO,[DEP] SDL2: no) ifeq ($(FORCE_DEPS),1) $(error sdl2 not found) else @@ -892,14 +1197,19 @@ NO_SDL2:=1 endif endif +$(call PRINT_TRACE,[DEP] PortAudio) ifeq ($(NO_PORTAUDIO),1) +$(call PRINT_INFO,[DEP] PortAudio: disabled) else #LDLIBS += -lportaudio +$(call PRINT_DEBUG,[DEP] PortAudio: checking pkg-config ...) ifeq ($(shell $(PKG_CONFIG)$(TOOLCHAIN_SUFFIX) --exists portaudio-2.0 && echo yes),yes) +$(call PRINT_INFO,[DEP] PortAudio: pkg-config/portaudio-2.0) CPPFLAGS_PORTAUDIO := $(shell $(PKG_CONFIG)$(TOOLCHAIN_SUFFIX) --cflags-only-I portaudio-2.0 ) -DMPT_WITH_PORTAUDIO LDFLAGS_PORTAUDIO := $(shell $(PKG_CONFIG)$(TOOLCHAIN_SUFFIX) --libs-only-L portaudio-2.0 ) $(shell $(PKG_CONFIG)$(TOOLCHAIN_SUFFIX) --libs-only-other portaudio-2.0 ) LDLIBS_PORTAUDIO := $(shell $(PKG_CONFIG)$(TOOLCHAIN_SUFFIX) --libs-only-l portaudio-2.0 ) else +$(call PRINT_INFO,[DEP] PortAudio: no) ifeq ($(FORCE_DEPS),1) $(error portaudio not found) else @@ -909,14 +1219,19 @@ NO_PORTAUDIO:=1 endif endif +$(call PRINT_TRACE,[DEP] PortAudio-C++) ifeq ($(NO_PORTAUDIOCPP),1) +$(call PRINT_INFO,[DEP] PortAudio-C++: disabled) else #LDLIBS += -lportaudiocpp +$(call PRINT_DEBUG,[DEP] PortAudio-C++: checking pkg-config ...) ifeq ($(shell $(PKG_CONFIG)$(TOOLCHAIN_SUFFIX) --exists portaudiocpp && echo yes),yes) +$(call PRINT_INFO,[DEP] PortAudio-C++: pkg-config/portaudiocpp) CPPFLAGS_PORTAUDIOCPP := $(shell $(PKG_CONFIG)$(TOOLCHAIN_SUFFIX) --cflags-only-I portaudiocpp ) -DMPT_WITH_PORTAUDIOCPP LDFLAGS_PORTAUDIOCPP := $(shell $(PKG_CONFIG)$(TOOLCHAIN_SUFFIX) --libs-only-L portaudiocpp ) $(shell $(PKG_CONFIG)$(TOOLCHAIN_SUFFIX) --libs-only-other portaudiocpp ) LDLIBS_PORTAUDIOCPP := $(shell $(PKG_CONFIG)$(TOOLCHAIN_SUFFIX) --libs-only-l portaudiocpp ) else +$(call PRINT_INFO,[DEP] PortAudio-C++: no) ifeq ($(FORCE_DEPS),1) $(error portaudiocpp not found) else @@ -926,14 +1241,20 @@ NO_PORTAUDIOCPP:=1 endif endif +$(call PRINT_TRACE,[DEP] PulseAudio) ifeq ($(NO_PULSEAUDIO),1) +$(call PRINT_INFO,[DEP] PulseAudio: disabled) else #LDLIBS += -lpulse-simple +$(call PRINT_DEBUG,[DEP] PulseAudio: checking pkg-config ...) ifeq ($(shell $(PKG_CONFIG)$(TOOLCHAIN_SUFFIX) --exists libpulse libpulse-simple && echo yes),yes) +$(call PRINT_INFO,[DEP] PulseAudio: pkg-config/libpulse) +$(call PRINT_INFO,[DEP] PulseAudio: pkg-config/libpulse-simple) CPPFLAGS_PULSEAUDIO := $(shell $(PKG_CONFIG)$(TOOLCHAIN_SUFFIX) --cflags-only-I libpulse libpulse-simple ) -DMPT_WITH_PULSEAUDIO LDFLAGS_PULSEAUDIO := $(shell $(PKG_CONFIG)$(TOOLCHAIN_SUFFIX) --libs-only-L libpulse libpulse-simple ) $(shell $(PKG_CONFIG)$(TOOLCHAIN_SUFFIX) --libs-only-other libpulse libpulse-simple ) LDLIBS_PULSEAUDIO := $(shell $(PKG_CONFIG)$(TOOLCHAIN_SUFFIX) --libs-only-l libpulse libpulse-simple ) else +$(call PRINT_INFO,[DEP] PulseAudio: no) ifeq ($(FORCE_DEPS),1) $(error pulseaudio not found) else @@ -943,14 +1264,19 @@ NO_PULSEAUDIO:=1 endif endif +$(call PRINT_TRACE,[DEP] FLAC) ifeq ($(NO_FLAC),1) +$(call PRINT_INFO,[DEP] FLAC: disabled) else #LDLIBS += -lFLAC +$(call PRINT_DEBUG,[DEP] FLAC: checking pkg-config ...) ifeq ($(shell $(PKG_CONFIG)$(TOOLCHAIN_SUFFIX) --exists 'flac >= 1.3.0' && echo yes),yes) +$(call PRINT_INFO,[DEP] FLAC: pkg-config/flac) CPPFLAGS_FLAC := $(shell $(PKG_CONFIG)$(TOOLCHAIN_SUFFIX) --cflags-only-I 'flac >= 1.3.0' ) -DMPT_WITH_FLAC LDFLAGS_FLAC := $(shell $(PKG_CONFIG)$(TOOLCHAIN_SUFFIX) --libs-only-L 'flac >= 1.3.0' ) $(shell $(PKG_CONFIG)$(TOOLCHAIN_SUFFIX) --libs-only-other 'flac >= 1.3.0' ) LDLIBS_FLAC := $(shell $(PKG_CONFIG)$(TOOLCHAIN_SUFFIX) --libs-only-l 'flac >= 1.3.0' ) else +$(call PRINT_INFO,[DEP] FLAC: no) ifeq ($(FORCE_DEPS),1) $(error flac not found) else @@ -960,14 +1286,19 @@ NO_FLAC:=1 endif endif +$(call PRINT_TRACE,[DEP] sndfile) ifeq ($(NO_SNDFILE),1) +$(call PRINT_INFO,[DEP] sndfile: disabled) else #LDLIBS += -lsndfile +$(call PRINT_DEBUG,[DEP] sndfile: checking pkg-config ...) ifeq ($(shell $(PKG_CONFIG)$(TOOLCHAIN_SUFFIX) --exists sndfile && echo yes),yes) +$(call PRINT_INFO,[DEP] sndfile: pkg-config/sndfile) CPPFLAGS_SNDFILE := $(shell $(PKG_CONFIG)$(TOOLCHAIN_SUFFIX) --cflags-only-I sndfile ) -DMPT_WITH_SNDFILE LDFLAGS_SNDFILE := $(shell $(PKG_CONFIG)$(TOOLCHAIN_SUFFIX) --libs-only-L sndfile ) $(shell $(PKG_CONFIG)$(TOOLCHAIN_SUFFIX) --libs-only-other sndfile ) LDLIBS_SNDFILE := $(shell $(PKG_CONFIG)$(TOOLCHAIN_SUFFIX) --libs-only-l sndfile ) else +$(call PRINT_INFO,[DEP] sndfile: no) ifeq ($(FORCE_DEPS),1) $(error sndfile not found) else @@ -977,7 +1308,9 @@ NO_SNDFILE:=1 endif endif +$(call PRINT_TRACE,[DEP] Allegro-4.2) ifeq ($(USE_ALLEGRO42),1) +$(call PRINT_INFO,[DEP] Allegro-4.2: local) CPPFLAGS_ALLEGRO42 := -Iinclude/allegro42/include -DALLEGRO_HAVE_STDINT_H -DLONG_LONG="long long" -DMPT_WITH_ALLEGRO42 LDFLAGS_ALLEGRO42 := @@ -990,6 +1323,8 @@ include/allegro42/lib/djgpp/liballeg.a: MISC_OUTPUTS += include/allegro42/lib/djgpp/liballeg.a +else +$(call PRINT_INFO,[DEP] Allegro-4.2: disabled) endif @@ -998,7 +1333,21 @@ CPPFLAGS += -DMPT_BUILD_HACK_ARCHIVE_SUPPORT endif CPPCHECK_FLAGS += -j $(NUMTHREADS) -CPPCHECK_FLAGS += --std=c11 --std=c++17 +ifneq ($(STDC),) +CPPCHECK_FLAGS += --std=$(STDC) +else +#CPPCHECK_FLAGS += --std=c23 +#CPPCHECK_FLAGS += --std=c18 +#CPPCHECK_FLAGS += --std=c17 +CPPCHECK_FLAGS += --std=c11 +endif +ifneq ($(STDCXX),) +CPPCHECK_FLAGS += --std=$(STDCXX) +else +#CPPCHECK_FLAGS += --std=c++23 +#CPPCHECK_FLAGS += --std=c++20 +CPPCHECK_FLAGS += --std=c++17 +endif CPPCHECK_FLAGS += --library=build/cppcheck/glibc-workarounds.cfg CPPCHECK_FLAGS += --quiet CPPCHECK_FLAGS += --enable=warning --inline-suppr --template='{file}:{line}: warning: {severity}: {message} [{id}]' @@ -1009,11 +1358,11 @@ CPPCHECK_FLAGS += --suppress=uninitMemberVar CPPCHECK_FLAGS += $(CPPFLAGS) CPPFLAGS += $(CPPFLAGS_ZLIB) $(CPPFLAGS_MPG123) $(CPPFLAGS_OGG) $(CPPFLAGS_VORBIS) $(CPPFLAGS_VORBISFILE) LDFLAGS += $(LDFLAGS_ZLIB) $(LDFLAGS_MPG123) $(LDFLAGS_OGG) $(LDFLAGS_VORBIS) $(LDFLAGS_VORBISFILE) -LDLIBS += $(LDLIBS_ZLIB) $(LDLIBS_MPG123) $(LDLIBS_OGG) $(LDLIBS_VORBIS) $(LDLIBS_VORBISFILE) +LDLIBS += $(LDLIBS_ZLIB) $(LDLIBS_MPG123) $(LDLIBS_OGG) $(LDLIBS_VORBIS) $(LDLIBS_VORBISFILE) $(LDLIBS_PLATFORM) CPPFLAGS_OPENMPT123 += $(CPPFLAGS_SDL2) $(CPPFLAGS_PORTAUDIO) $(CPPFLAGS_PULSEAUDIO) $(CPPFLAGS_FLAC) $(CPPFLAGS_SNDFILE) $(CPPFLAGS_ALLEGRO42) LDFLAGS_OPENMPT123 += $(LDFLAGS_SDL2) $(LDFLAGS_PORTAUDIO) $(LDFLAGS_PULSEAUDIO) $(LDFLAGS_FLAC) $(LDFLAGS_SNDFILE) $(LDFLAGS_ALLEGRO42) -LDLIBS_OPENMPT123 += $(LDLIBS_SDL2) $(LDLIBS_PORTAUDIO) $(LDLIBS_PULSEAUDIO) $(LDLIBS_FLAC) $(LDLIBS_SNDFILE) $(LDLIBS_ALLEGRO42) +LDLIBS_OPENMPT123 += $(LDLIBS_SDL2) $(LDLIBS_PORTAUDIO) $(LDLIBS_PULSEAUDIO) $(LDLIBS_FLAC) $(LDLIBS_SNDFILE) $(LDLIBS_ALLEGRO42) $(LDLIBS_PLATFORM) %: %$(FLAVOUR_O).o @@ -1031,15 +1380,67 @@ LDLIBS_OPENMPT123 += $(LDLIBS_SDL2) $(LDLIBS_PORTAUDIO) $(LDLIBS_PULSEAUDIO) $ $(SILENT)$(COMPILE.c) $(OUTPUT_OPTION) $< %.test$(FLAVOUR_O).o: %.cpp - $(INFO) [CXX-TEST] $< + $(INFO) [CXX] libopenmpt-test: $< $(VERYSILENT)$(CXX) -DLIBOPENMPT_BUILD_TEST $(CXXFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -M -MT$@ $< > $*.test$(FLAVOUR_O).d $(SILENT)$(COMPILE.cc) -DLIBOPENMPT_BUILD_TEST $(OUTPUT_OPTION) $< %.test$(FLAVOUR_O).o: %.c - $(INFO) [CC-TEST] $< + $(INFO) [CC] libopenmpt-test: $< $(VERYSILENT)$(CC) -DLIBOPENMPT_BUILD_TEST $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -M -MT$@ $< > $*.test$(FLAVOUR_O).d $(SILENT)$(COMPILE.c) -DLIBOPENMPT_BUILD_TEST $(OUTPUT_OPTION) $< +%.openmpt123$(FLAVOUR_O).o: %.cpp + $(INFO) [CXX] openmpt123: $< + $(VERYSILENT)$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(CPPFLAGS_OPENMPT123) $(TARGET_ARCH) -M -MT$@ $< > $*.openmpt123$(FLAVOUR_O).d + $(SILENT)$(COMPILE.cc) $(CPPFLAGS_OPENMPT123) $(OUTPUT_OPTION) $< + +%.openmpt123$(FLAVOUR_O).o: %.c + $(INFO) [CC] openmpt123: $< + $(VERYSILENT)$(CC) $(CFLAGS) $(CPPFLAGS) $(CPPFLAGS_OPENMPT123) $(TARGET_ARCH) -M -MT$@ $< > $*.openmpt123$(FLAVOUR_O).d + $(SILENT)$(COMPILE.c) $(CPPFLAGS_OPENMPT123) $(OUTPUT_OPTION) $< + + +%.zlib$(FLAVOUR_O).o: %.cpp + $(INFO) [CXX] zlib: $< + $(VERYSILENT)$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -M -MT$@ $< > $*.zlib$(FLAVOUR_O).d + $(SILENT)$(COMPILE.cc) $(OUTPUT_OPTION) $< + +%.zlib$(FLAVOUR_O).o: %.c + $(INFO) [CC] zlib: $< + $(VERYSILENT)$(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -M -MT$@ $< > $*.zlib$(FLAVOUR_O).d + $(SILENT)$(COMPILE.c) $(OUTPUT_OPTION) $< + +%.mpg123$(FLAVOUR_O).o: %.cpp + $(INFO) [CXX] mpg123: $< + $(VERYSILENT)$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -M -MT$@ $< > $*.mpg123$(FLAVOUR_O).d + $(SILENT)$(COMPILE.cc) $(OUTPUT_OPTION) $< + +%.mpg123$(FLAVOUR_O).o: %.c + $(INFO) [CC] mpg123: $< + $(VERYSILENT)$(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -M -MT$@ $< > $*.mpg123$(FLAVOUR_O).d + $(SILENT)$(COMPILE.c) $(OUTPUT_OPTION) $< + +%.ogg$(FLAVOUR_O).o: %.cpp + $(INFO) [CXX] ogg: $< + $(VERYSILENT)$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -M -MT$@ $< > $*.ogg$(FLAVOUR_O).d + $(SILENT)$(COMPILE.cc) $(OUTPUT_OPTION) $< + +%.ogg$(FLAVOUR_O).o: %.c + $(INFO) [CC] ogg: $< + $(VERYSILENT)$(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -M -MT$@ $< > $*.ogg$(FLAVOUR_O).d + $(SILENT)$(COMPILE.c) $(OUTPUT_OPTION) $< + +%.vorbis$(FLAVOUR_O).o: %.cpp + $(INFO) [CXX] vorbis: $< + $(VERYSILENT)$(CXX) $(CXXFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -M -MT$@ $< > $*.vorbis$(FLAVOUR_O).d + $(SILENT)$(COMPILE.cc) $(OUTPUT_OPTION) $< + +%.vorbis$(FLAVOUR_O).o: %.c + $(INFO) [CC] vorbis: $< + $(VERYSILENT)$(CC) $(CFLAGS) $(CPPFLAGS) $(TARGET_ARCH) -M -MT$@ $< > $*.vorbis$(FLAVOUR_O).d + $(SILENT)$(COMPILE.c) $(OUTPUT_OPTION) $< + + %.tar.gz: %.tar $(INFO) [GZIP] $< $(SILENT)gzip --rsyncable --no-name --best > $@ < $< @@ -1116,12 +1517,17 @@ COMMON_CXX_SOURCES += \ SOUNDLIB_CXX_SOURCES += \ $(COMMON_CXX_SOURCES) \ + $(sort $(wildcard src/openmpt/fileformat_base/*.cpp)) \ $(sort $(wildcard src/openmpt/soundbase/*.cpp)) \ + $(sort $(wildcard src/openmpt/soundfile_data/*.cpp)) \ $(sort $(wildcard soundlib/*.cpp)) \ $(sort $(wildcard soundlib/plugins/*.cpp)) \ $(sort $(wildcard soundlib/plugins/dmo/*.cpp)) \ $(sort $(wildcard sounddsp/*.cpp)) \ +SOUNDLIB_TEST_CXX_SOURCES += \ + $(sort $(wildcard src/openmpt/soundfile_write/*.cpp)) \ + ifeq ($(HACK_ARCHIVE_SUPPORT),1) SOUNDLIB_CXX_SOURCES += $(sort $(wildcard unarchiver/*.cpp)) @@ -1131,11 +1537,12 @@ LIBOPENMPT_CXX_SOURCES += \ $(SOUNDLIB_CXX_SOURCES) \ $(sort $(wildcard libopenmpt/*.cpp)) \ + include/miniz/miniz$(FLAVOUR_O).o : CFLAGS+=$(CFLAGS_SILENT) include/miniz/miniz.test$(FLAVOUR_O).o : CFLAGS+=$(CFLAGS_SILENT) ifeq ($(LOCAL_ZLIB),1) -LIBOPENMPT_C_SOURCES += $(LOCAL_ZLIB_SOURCES) -LIBOPENMPTTEST_C_SOURCES += $(LOCAL_ZLIB_SOURCES) +LIBOPENMPT_OBJECTS += $(OBJECTS_ZLIB) +LIBOPENMPTTEST_OBJECTS += $(OBJECTS_ZLIB) else ifeq ($(NO_ZLIB),1) ifeq ($(NO_MINIZ),1) @@ -1151,8 +1558,10 @@ endif include/minimp3/minimp3$(FLAVOUR_O).o : CFLAGS+=$(CFLAGS_SILENT) include/minimp3/minimp3.test$(FLAVOUR_O).o : CFLAGS+=$(CFLAGS_SILENT) ifeq ($(LOCAL_MPG123),1) -LIBOPENMPT_C_SOURCES += $(LOCAL_MPG123_SOURCES) -LIBOPENMPTTEST_C_SOURCES += $(LOCAL_MPG123_SOURCES) +LIBOPENMPT_OBJECTS += $(OBJECTS_MPG123) +LIBOPENMPTTEST_OBJECTS += $(OBJECTS_MPG123) +LIBOPENMPT_LIBS += $(LIBS_MPG123) +LIBOPENMPTTEST_LIBS += $(LIBS_MPG123) else ifeq ($(NO_MPG123),1) ifeq ($(NO_MINIMP3),1) @@ -1169,11 +1578,15 @@ include/stb_vorbis/stb_vorbis$(FLAVOUR_O).o : CFLAGS+=$(CFLAGS_SILENT) include/stb_vorbis/stb_vorbis.test$(FLAVOUR_O).o : CFLAGS+=$(CFLAGS_SILENT) ifeq ($(LOCAL_VORBIS),1) ifeq ($(LOCAL_OGG),1) -LIBOPENMPT_C_SOURCES += $(LOCAL_OGG_SOURCES) -LIBOPENMPTTEST_C_SOURCES += $(LOCAL_OGG_SOURCES) +LIBOPENMPT_OBJECTS += $(OBJECTS_OGG) +LIBOPENMPTTEST_OBJECTS += $(OBJECTS_OGG) +LIBOPENMPT_LIBS += $(LIBS_OGG) +LIBOPENMPTTEST_LIBS += $(LIBS_OGG) endif -LIBOPENMPT_C_SOURCES += $(LOCAL_VORBIS_SOURCES) -LIBOPENMPTTEST_C_SOURCES += $(LOCAL_VORBIS_SOURCES) +LIBOPENMPT_OBJECTS += $(OBJECTS_VORBIS) +LIBOPENMPTTEST_OBJECTS += $(OBJECTS_VORBIS) +LIBOPENMPT_LIBS += $(LIBS_VORBIS) +LIBOPENMPTTEST_LIBS += $(LIBS_VORBIS) else ifeq ($(NO_OGG),1) ifeq ($(NO_STBVORBIS),1) @@ -1215,7 +1628,7 @@ ALL_DEPENDS += $(LIBOPENMPT_DEPENDS) ifeq ($(DYNLINK),1) OUTPUT_LIBOPENMPT += bin/$(FLAVOUR_DIR)libopenmpt$(SOSUFFIX) else -OBJECTS_LIBOPENMPT += $(LIBOPENMPT_OBJECTS) +OBJECTS_LIBOPENMPT += $(LIBOPENMPT_OBJECTS) $(LIBOPENMPT_LIBS) endif @@ -1248,15 +1661,17 @@ OPENMPT123_CXX_SOURCES += \ OPENMPT123_C_SOURCES += \ $(sort $(wildcard openmpt123/*.c)) \ -OPENMPT123_OBJECTS += $(OPENMPT123_CXX_SOURCES:.cpp=$(FLAVOUR_O).o) $(OPENMPT123_C_SOURCES:.c=$(FLAVOUR_O).o) +OPENMPT123_OBJECTS += $(OPENMPT123_CXX_SOURCES:.cpp=.openmpt123$(FLAVOUR_O).o) $(OPENMPT123_C_SOURCES:.c=.openmpt123$(FLAVOUR_O).o) OPENMPT123_DEPENDS = $(OPENMPT123_OBJECTS:$(FLAVOUR_O).o=$(FLAVOUR_O).d) ALL_OBJECTS += $(OPENMPT123_OBJECTS) ALL_DEPENDS += $(OPENMPT123_DEPENDS) LIBOPENMPTTEST_CXX_SOURCES += \ - test/libopenmpt_test.cpp \ + libopenmpt/libopenmpt_test/libopenmpt_test.cpp \ + $(sort $(wildcard libopenmpt/*.cpp)) \ $(SOUNDLIB_CXX_SOURCES) \ + $(SOUNDLIB_TEST_CXX_SOURCES) \ test/mpt_tests_base.cpp \ test/mpt_tests_binary.cpp \ test/mpt_tests_crc.cpp \ @@ -1276,8 +1691,8 @@ LIBOPENMPTTEST_CXX_SOURCES += \ LIBOPENMPTTEST_C_SOURCES += \ -LIBOPENMPTTEST_OBJECTS = $(LIBOPENMPTTEST_CXX_SOURCES:.cpp=.test$(FLAVOUR_O).o) $(LIBOPENMPTTEST_C_SOURCES:.c=.test$(FLAVOUR_O).o) -LIBOPENMPTTEST_DEPENDS = $(LIBOPENMPTTEST_CXX_SOURCES:.cpp=.test$(FLAVOUR_O).d) $(LIBOPENMPTTEST_C_SOURCES:.c=.test$(FLAVOUR_O).d) +LIBOPENMPTTEST_OBJECTS += $(LIBOPENMPTTEST_CXX_SOURCES:.cpp=.test$(FLAVOUR_O).o) $(LIBOPENMPTTEST_C_SOURCES:.c=.test$(FLAVOUR_O).o) +LIBOPENMPTTEST_DEPENDS = $(LIBOPENMPTTEST_OBJECTS:$(FLAVOUR_O).o=$(FLAVOUR_O).d) ALL_OBJECTS += $(LIBOPENMPTTEST_OBJECTS) ALL_DEPENDS += $(LIBOPENMPTTEST_DEPENDS) @@ -1358,6 +1773,9 @@ ifeq ($(SHARED_SONAME),1) LIBOPENMPT_LDFLAGS += -Wl,-soname,$(LIBOPENMPT_SONAME) endif +MISC_OUTPUTS += bin/distversion +MISC_OUTPUTS += bin/distversion-pure +MISC_OUTPUTS += bin/distversion-tarball MISC_OUTPUTS += bin/$(FLAVOUR_DIR)empty.cpp MISC_OUTPUTS += bin/$(FLAVOUR_DIR)empty.out MISC_OUTPUTS += bin/$(FLAVOUR_DIR)openmpt123$(EXESUFFIX).norpath @@ -1460,9 +1878,9 @@ else bin/$(FLAVOUR_DIR)libopenmpt_test$(EXESUFFIX) endif -bin/$(FLAVOUR_DIR)libopenmpt_test$(EXESUFFIX): $(LIBOPENMPTTEST_OBJECTS) +bin/$(FLAVOUR_DIR)libopenmpt_test$(EXESUFFIX): $(LIBOPENMPTTEST_OBJECTS) $(LIBOPENMPT_LIBS) $(INFO) [LD-TEST] $@ - $(SILENT)$(LINK.cc) $(LDFLAGS_RPATH) $(TEST_LDFLAGS) $(LIBOPENMPTTEST_OBJECTS) $(LOADLIBES) $(LDLIBS) $(LDLIBS_LIBOPENMPTTEST) -o $@ + $(SILENT)$(LINK.cc) $(LDFLAGS_RPATH) $(TEST_LDFLAGS) $(LIBOPENMPTTEST_OBJECTS) $(LIBOPENMPT_LIBS) $(LOADLIBES) $(LDLIBS) $(LDLIBS_LIBOPENMPTTEST) -o $@ bin/$(FLAVOUR_DIR)libopenmpt.pc: $(INFO) [GEN] $@ @@ -1557,7 +1975,7 @@ bin/$(FLAVOUR_DIR)dist-tar.tar: bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIB cd bin/$(FLAVOUR_DIR)dist-tar/ && rm -rf libopenmpt cd bin/$(FLAVOUR_DIR)dist-tar/ && mkdir -p libopenmpt/src.makefile/$(DIST_LIBOPENMPT_TARBALL_VERSION)/ cd bin/$(FLAVOUR_DIR)dist-tar/ && cp libopenmpt-$(DIST_LIBOPENMPT_VERSION).makefile.tar.gz libopenmpt/src.makefile/$(DIST_LIBOPENMPT_TARBALL_VERSION)/ - cd bin/$(FLAVOUR_DIR)dist-tar/ && tar cv --numeric-owner --owner=0 --group=0 -f ../dist-tar.tar libopenmpt + cd bin/$(FLAVOUR_DIR)dist-tar/ && $(TAR_C) -v -f ../dist-tar.tar libopenmpt .PHONY: dist-zip dist-zip: bin/$(FLAVOUR_DIR)dist-zip.tar @@ -1567,7 +1985,7 @@ bin/$(FLAVOUR_DIR)dist-zip.tar: bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIB cd bin/$(FLAVOUR_DIR)dist-zip/ && rm -rf libopenmpt cd bin/$(FLAVOUR_DIR)dist-zip/ && mkdir -p libopenmpt/src.msvc/$(DIST_LIBOPENMPT_TARBALL_VERSION)/ cd bin/$(FLAVOUR_DIR)dist-zip/ && cp libopenmpt-$(DIST_LIBOPENMPT_VERSION).msvc.zip libopenmpt/src.msvc/$(DIST_LIBOPENMPT_TARBALL_VERSION)/ - cd bin/$(FLAVOUR_DIR)dist-zip/ && tar cv --numeric-owner --owner=0 --group=0 -f ../dist-zip.tar libopenmpt + cd bin/$(FLAVOUR_DIR)dist-zip/ && $(TAR_C) -v -f ../dist-zip.tar libopenmpt .PHONY: dist-doc dist-doc: bin/$(FLAVOUR_DIR)dist-doc.tar @@ -1577,7 +1995,7 @@ bin/$(FLAVOUR_DIR)dist-doc.tar: bin/$(FLAVOUR_DIR)dist-doc/libopenmpt-$(DIST_LIB cd bin/$(FLAVOUR_DIR)dist-doc/ && rm -rf libopenmpt cd bin/$(FLAVOUR_DIR)dist-doc/ && mkdir -p libopenmpt/doc/$(DIST_LIBOPENMPT_TARBALL_VERSION)/ cd bin/$(FLAVOUR_DIR)dist-doc/ && cp libopenmpt-$(DIST_LIBOPENMPT_VERSION).doc.tar.gz libopenmpt/doc/$(DIST_LIBOPENMPT_TARBALL_VERSION)/ - cd bin/$(FLAVOUR_DIR)dist-doc/ && tar cv --numeric-owner --owner=0 --group=0 -f ../dist-doc.tar libopenmpt + cd bin/$(FLAVOUR_DIR)dist-doc/ && $(TAR_C) -v -f ../dist-doc.tar libopenmpt .PHONY: dist-js dist-js: bin/$(FLAVOUR_DIR)dist-js.tar @@ -1587,7 +2005,7 @@ bin/$(FLAVOUR_DIR)dist-js.tar: bin/$(FLAVOUR_DIR)dist-js/libopenmpt-$(DIST_LIBOP cd bin/$(FLAVOUR_DIR)dist-js/ && rm -rf libopenmpt cd bin/$(FLAVOUR_DIR)dist-js/ && mkdir -p libopenmpt/dev.js/$(DIST_LIBOPENMPT_TARBALL_VERSION)/ cd bin/$(FLAVOUR_DIR)dist-js/ && cp libopenmpt-$(DIST_LIBOPENMPT_VERSION).dev.js.tar.gz libopenmpt/dev.js/$(DIST_LIBOPENMPT_TARBALL_VERSION)/ - cd bin/$(FLAVOUR_DIR)dist-js/ && tar cv --numeric-owner --owner=0 --group=0 -f ../dist-js.tar libopenmpt + cd bin/$(FLAVOUR_DIR)dist-js/ && $(TAR_C) -v -f ../dist-js.tar libopenmpt .PHONY: dist-dos dist-dos: bin/$(FLAVOUR_DIR)dist-dos.tar @@ -1597,7 +2015,7 @@ bin/$(FLAVOUR_DIR)dist-dos.tar: bin/$(FLAVOUR_DIR)dist-dos/libopenmpt-$(DIST_LIB cd bin/$(FLAVOUR_DIR)dist-dos/ && rm -rf libopenmpt cd bin/$(FLAVOUR_DIR)dist-dos/ && mkdir -p libopenmpt/bin.dos/$(DIST_LIBOPENMPT_TARBALL_VERSION)/ cd bin/$(FLAVOUR_DIR)dist-dos/ && cp libopenmpt-$(DIST_LIBOPENMPT_VERSION).bin.dos.zip libopenmpt/bin.dos/$(DIST_LIBOPENMPT_TARBALL_VERSION)/ - cd bin/$(FLAVOUR_DIR)dist-dos/ && tar cv --numeric-owner --owner=0 --group=0 -f ../dist-dos.tar libopenmpt + cd bin/$(FLAVOUR_DIR)dist-dos/ && $(TAR_C) -v -f ../dist-dos.tar libopenmpt .PHONY: dist-retro-win98 dist-retro-win98: bin/$(FLAVOUR_DIR)dist-retro-win98.tar @@ -1607,7 +2025,7 @@ bin/$(FLAVOUR_DIR)dist-retro-win98.tar: bin/$(FLAVOUR_DIR)dist-retro-win98/libop cd bin/$(FLAVOUR_DIR)dist-retro-win98/ && rm -rf libopenmpt cd bin/$(FLAVOUR_DIR)dist-retro-win98/ && mkdir -p libopenmpt/bin.retro.win98/$(DIST_LIBOPENMPT_TARBALL_VERSION)/ cd bin/$(FLAVOUR_DIR)dist-retro-win98/ && cp libopenmpt-$(DIST_LIBOPENMPT_VERSION).bin.retro.win98.zip libopenmpt/bin.retro.win98/$(DIST_LIBOPENMPT_TARBALL_VERSION)/ - cd bin/$(FLAVOUR_DIR)dist-retro-win98/ && tar cv --numeric-owner --owner=0 --group=0 -f ../dist-retro-win98.tar libopenmpt + cd bin/$(FLAVOUR_DIR)dist-retro-win98/ && $(TAR_C) -v -f ../dist-retro-win98.tar libopenmpt .PHONY: dist-retro-win95 dist-retro-win95: bin/$(FLAVOUR_DIR)dist-retro-win95.tar @@ -1617,7 +2035,7 @@ bin/$(FLAVOUR_DIR)dist-retro-win95.tar: bin/$(FLAVOUR_DIR)dist-retro-win95/libop cd bin/$(FLAVOUR_DIR)dist-retro-win95/ && rm -rf libopenmpt cd bin/$(FLAVOUR_DIR)dist-retro-win95/ && mkdir -p libopenmpt/bin.retro.win95/$(DIST_LIBOPENMPT_TARBALL_VERSION)/ cd bin/$(FLAVOUR_DIR)dist-retro-win95/ && cp libopenmpt-$(DIST_LIBOPENMPT_VERSION).bin.retro.win95.zip libopenmpt/bin.retro.win95/$(DIST_LIBOPENMPT_TARBALL_VERSION)/ - cd bin/$(FLAVOUR_DIR)dist-retro-win95/ && tar cv --numeric-owner --owner=0 --group=0 -f ../dist-retro-win95.tar libopenmpt + cd bin/$(FLAVOUR_DIR)dist-retro-win95/ && $(TAR_C) -v -f ../dist-retro-win95.tar libopenmpt .PHONY: bin/$(FLAVOUR_DIR)dist.mk bin/$(FLAVOUR_DIR)dist.mk: @@ -1649,198 +2067,205 @@ bin/$(FLAVOUR_DIR)dist-doc/libopenmpt-$(DIST_LIBOPENMPT_VERSION).doc.tar: docs rm -rf bin/$(FLAVOUR_DIR)dist-doc/libopenmpt-$(DIST_LIBOPENMPT_VERSION).doc mkdir -p bin/$(FLAVOUR_DIR)dist-doc/libopenmpt-$(DIST_LIBOPENMPT_VERSION).doc cp -Rv bin/$(FLAVOUR_DIR)docs/html bin/$(FLAVOUR_DIR)dist-doc/libopenmpt-$(DIST_LIBOPENMPT_VERSION).doc/docs - cd bin/$(FLAVOUR_DIR)dist-doc/ && tar cv --numeric-owner --owner=0 --group=0 libopenmpt-$(DIST_LIBOPENMPT_VERSION).doc > libopenmpt-$(DIST_LIBOPENMPT_VERSION).doc.tar + cd bin/$(FLAVOUR_DIR)dist-doc/ && $(TAR_C) -v libopenmpt-$(DIST_LIBOPENMPT_VERSION).doc > libopenmpt-$(DIST_LIBOPENMPT_VERSION).doc.tar .PHONY: bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION).makefile.tar bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION).makefile.tar: bin/$(FLAVOUR_DIR)dist.mk bin/$(FLAVOUR_DIR)svn_version_dist.h - mkdir -p bin/$(FLAVOUR_DIR)dist-tar - rm -rf bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION) - mkdir -p bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION) - mkdir -p bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/build - mkdir -p bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/doc - mkdir -p bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/include - mkdir -p bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src - mkdir -p bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt - mkdir -p bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/openmpt - svn export ./LICENSE bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/LICENSE - svn export ./README.md bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/README.md - svn export ./Makefile bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/Makefile - svn export ./.clang-format bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/.clang-format - svn export ./bin bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/bin - svn export ./build/download_externals.sh bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/build/download_externals.sh - svn export ./build/android_ndk bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/build/android_ndk - svn export ./build/make bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/build/make - svn export ./build/svn_version bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/build/svn_version - svn export ./build/xcode-ios bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/build/xcode-ios - svn export ./build/xcode-macosx bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/build/xcode-macosx - svn export ./common bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/common - svn export ./doc/contributing.md bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/doc/contributing.md - svn export ./doc/libopenmpt_styleguide.md bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/doc/libopenmpt_styleguide.md - svn export ./doc/module_formats.md bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/doc/module_formats.md - svn export ./soundlib bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/soundlib - svn export ./sounddsp bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/sounddsp - svn export ./src/mpt/.clang-format bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/.clang-format + mkdir -p bin/$(FLAVOUR_DIR)dist-tar + rm -rf bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION) + mkdir -p bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION) + mkdir -p bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/build + mkdir -p bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/doc + mkdir -p bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/include + mkdir -p bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src + mkdir -p bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt + mkdir -p bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/openmpt + svn export ./LICENSE bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/LICENSE + svn export ./README.md bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/README.md + svn export ./Makefile bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/Makefile + svn export ./.clang-format bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/.clang-format + svn export ./bin bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/bin + svn export ./build/download_externals.sh bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/build/download_externals.sh + svn export ./build/android_ndk bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/build/android_ndk + svn export ./build/djgpp bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/build/djgpp + svn export ./build/make bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/build/make + svn export ./build/svn_version bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/build/svn_version + svn export ./build/xcode-ios bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/build/xcode-ios + svn export ./build/xcode-macosx bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/build/xcode-macosx + svn export ./common bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/common + svn export ./doc/contributing.md bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/doc/contributing.md + svn export ./doc/libopenmpt_styleguide.md bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/doc/libopenmpt_styleguide.md + svn export ./doc/module_formats.md bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/doc/module_formats.md + svn export ./soundlib bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/soundlib + svn export ./sounddsp bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/sounddsp + svn export ./src/mpt/.clang-format bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/.clang-format svn export ./src/mpt/LICENSE.BSD-3-Clause.txt bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/LICENSE.BSD-3-Clause.txt - svn export ./src/mpt/LICENSE.BSL-1.0.txt bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/LICENSE.BSL-1.0.txt - svn export ./src/mpt/arch bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/arch - svn export ./src/mpt/audio bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/audio - svn export ./src/mpt/base bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/base - svn export ./src/mpt/binary bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/binary - svn export ./src/mpt/check bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/check - svn export ./src/mpt/crc bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/crc - #svn export ./src/mpt/crypto bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/crypto - svn export ./src/mpt/detect bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/detect - svn export ./src/mpt/endian bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/endian - svn export ./src/mpt/environment bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/environment - svn export ./src/mpt/exception bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/exception - svn export ./src/mpt/format bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/format - #svn export ./src/mpt/fs bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/fs - svn export ./src/mpt/io bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/io - svn export ./src/mpt/io_file bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/io_file - svn export ./src/mpt/io_file_adapter bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/io_file_adapter - svn export ./src/mpt/io_file_read bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/io_file_read - svn export ./src/mpt/io_file_unique bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/io_file_unique - svn export ./src/mpt/io_read bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/io_read - svn export ./src/mpt/io_write bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/io_write - #svn export ./src/mpt/json bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/json - #svn export ./src/mpt/library bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/library - svn export ./src/mpt/mutex bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/mutex - svn export ./src/mpt/out_of_memory bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/out_of_memory - svn export ./src/mpt/osinfo bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/osinfo - svn export ./src/mpt/parse bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/parse - svn export ./src/mpt/path bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/path - svn export ./src/mpt/random bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/random - svn export ./src/mpt/string bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/string - svn export ./src/mpt/string_transcode bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/string_transcode - svn export ./src/mpt/system_error bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/system_error - svn export ./src/mpt/test bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/test - svn export ./src/mpt/uuid bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/uuid - #svn export ./src/mpt/uuid_namespace bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/uuid_namespace - svn export ./src/openmpt/all bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/openmpt/all - svn export ./src/openmpt/base bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/openmpt/base - svn export ./src/openmpt/logging bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/openmpt/logging - svn export ./src/openmpt/random bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/openmpt/random - svn export ./src/openmpt/soundbase bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/openmpt/soundbase - svn export ./test bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/test - rm bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/test/mpt_tests_crypto.cpp - rm bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/test/mpt_tests_uuid_namespace.cpp - svn export ./libopenmpt bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/libopenmpt - svn export ./examples bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/examples - svn export ./openmpt123 bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/openmpt123 - svn export ./contrib bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/contrib - svn export ./include/allegro42 bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/include/allegro42 - svn export ./include/cwsdpmi bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/include/cwsdpmi - svn export ./include/minimp3 bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/include/minimp3 - svn export ./include/miniz bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/include/miniz - svn export ./include/stb_vorbis bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/include/stb_vorbis - cp bin/$(FLAVOUR_DIR)dist.mk bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/build/dist.mk - cp bin/$(FLAVOUR_DIR)svn_version_dist.h bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/build/svn_version/svn_version.h - cd bin/$(FLAVOUR_DIR)dist-tar/ && tar cv --numeric-owner --owner=0 --group=0 libopenmpt-$(DIST_LIBOPENMPT_VERSION) > libopenmpt-$(DIST_LIBOPENMPT_VERSION).makefile.tar + svn export ./src/mpt/LICENSE.BSL-1.0.txt bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/LICENSE.BSL-1.0.txt + svn export ./src/mpt/arch bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/arch + svn export ./src/mpt/audio bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/audio + svn export ./src/mpt/base bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/base + svn export ./src/mpt/binary bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/binary + svn export ./src/mpt/check bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/check + svn export ./src/mpt/crc bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/crc + #svn export ./src/mpt/crypto bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/crypto + svn export ./src/mpt/detect bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/detect + svn export ./src/mpt/endian bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/endian + svn export ./src/mpt/environment bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/environment + svn export ./src/mpt/exception bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/exception + svn export ./src/mpt/format bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/format + #svn export ./src/mpt/fs bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/fs + svn export ./src/mpt/io bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/io + svn export ./src/mpt/io_file bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/io_file + svn export ./src/mpt/io_file_adapter bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/io_file_adapter + svn export ./src/mpt/io_file_read bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/io_file_read + svn export ./src/mpt/io_file_unique bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/io_file_unique + svn export ./src/mpt/io_read bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/io_read + svn export ./src/mpt/io_write bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/io_write + #svn export ./src/mpt/json bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/json + #svn export ./src/mpt/library bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/library + svn export ./src/mpt/main bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/main + svn export ./src/mpt/mutex bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/mutex + svn export ./src/mpt/out_of_memory bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/out_of_memory + svn export ./src/mpt/osinfo bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/osinfo + svn export ./src/mpt/parse bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/parse + svn export ./src/mpt/path bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/path + svn export ./src/mpt/random bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/random + svn export ./src/mpt/string bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/string + svn export ./src/mpt/string_transcode bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/string_transcode + svn export ./src/mpt/system_error bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/system_error + svn export ./src/mpt/test bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/test + svn export ./src/mpt/uuid bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/uuid + #svn export ./src/mpt/uuid_namespace bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/uuid_namespace + svn export ./src/openmpt/all bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/openmpt/all + svn export ./src/openmpt/base bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/openmpt/base + svn export ./src/openmpt/fileformat_base bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/openmpt/fileformat_base + svn export ./src/openmpt/logging bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/openmpt/logging + svn export ./src/openmpt/random bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/openmpt/random + svn export ./src/openmpt/soundbase bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/openmpt/soundbase + svn export ./src/openmpt/soundfile_data bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/openmpt/soundfile_data + svn export ./src/openmpt/soundfile_write bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/openmpt/soundfile_write + svn export ./test bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/test + rm bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/test/mpt_tests_crypto.cpp + rm bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/test/mpt_tests_uuid_namespace.cpp + svn export ./libopenmpt bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/libopenmpt + svn export ./examples bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/examples + svn export ./openmpt123 bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/openmpt123 + svn export ./contrib bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/contrib + svn export ./include/allegro42 bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/include/allegro42 + svn export ./include/cwsdpmi bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/include/cwsdpmi + svn export ./include/minimp3 bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/include/minimp3 + svn export ./include/miniz bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/include/miniz + svn export ./include/stb_vorbis bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/include/stb_vorbis + cp bin/$(FLAVOUR_DIR)dist.mk bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/build/dist.mk + cp bin/$(FLAVOUR_DIR)svn_version_dist.h bin/$(FLAVOUR_DIR)dist-tar/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/build/svn_version/svn_version.h + cd bin/$(FLAVOUR_DIR)dist-tar/ && $(TAR_C) -v libopenmpt-$(DIST_LIBOPENMPT_VERSION) > libopenmpt-$(DIST_LIBOPENMPT_VERSION).makefile.tar .PHONY: bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION).msvc.zip bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION).msvc.zip: bin/$(FLAVOUR_DIR)dist.mk bin/$(FLAVOUR_DIR)svn_version_dist.h - mkdir -p bin/$(FLAVOUR_DIR)dist-zip - rm -rf bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION) - mkdir -p bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION) - mkdir -p bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/build - mkdir -p bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/build/premake - mkdir -p bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/doc - mkdir -p bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/include - mkdir -p bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src - mkdir -p bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt - mkdir -p bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/openmpt - svn export ./LICENSE bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/LICENSE --native-eol CRLF - svn export ./README.md bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/README.md --native-eol CRLF - svn export ./Makefile bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/Makefile --native-eol CRLF - svn export ./.clang-format bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/.clang-format --native-eol CRLF - svn export ./bin bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/bin --native-eol CRLF - svn export ./build/premake/def bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/build/premake/def --native-eol CRLF - svn export ./build/premake/inc bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/build/premake/inc --native-eol CRLF - svn export ./build/premake/lnk bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/build/premake/lnk --native-eol CRLF - svn export ./build/scriptlib bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/build/scriptlib --native-eol CRLF - svn export ./build/svn_version bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/build/svn_version --native-eol CRLF - svn export ./build/vs bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/build/vs --native-eol CRLF - svn export ./build/vs2017winxpansi bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/build/vs2017winxpansi --native-eol CRLF - svn export ./build/vs2017winxp bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/build/vs2017winxp --native-eol CRLF - svn export ./build/vs2019win7 bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/build/vs2019win7 --native-eol CRLF - svn export ./build/vs2019win81 bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/build/vs2019win81 --native-eol CRLF - svn export ./build/vs2019win10 bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/build/vs2019win10 --native-eol CRLF - svn export ./build/vs2019win10uwp bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/build/vs2019win10uwp --native-eol CRLF - svn export ./build/vs2022win7 bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/build/vs2022win7 --native-eol CRLF - svn export ./build/vs2022win81 bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/build/vs2022win81 --native-eol CRLF - svn export ./build/vs2022win10 bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/build/vs2022win10 --native-eol CRLF - svn export ./build/vs2022win10uwp bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/build/vs2022win10uwp --native-eol CRLF - svn export ./build/vs2022win10clang bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/build/vs2022win10clang --native-eol CRLF - svn export ./build/download_externals.cmd bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/build/download_externals.cmd --native-eol CRLF - svn export ./common bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/common --native-eol CRLF - svn export ./doc/contributing.md bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/doc/contributing.md --native-eol CRLF - svn export ./doc/libopenmpt_styleguide.md bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/doc/libopenmpt_styleguide.md --native-eol CRLF - svn export ./doc/module_formats.md bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/doc/module_formats.md --native-eol CRLF - svn export ./soundlib bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/soundlib --native-eol CRLF - svn export ./sounddsp bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/sounddsp --native-eol CRLF - svn export ./src/mpt/.clang-format bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/.clang-format --native-eol CRLF + mkdir -p bin/$(FLAVOUR_DIR)dist-zip + rm -rf bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION) + mkdir -p bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION) + mkdir -p bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/build + mkdir -p bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/build/premake + mkdir -p bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/doc + mkdir -p bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/include + mkdir -p bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src + mkdir -p bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt + mkdir -p bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/openmpt + svn export ./LICENSE bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/LICENSE --native-eol CRLF + svn export ./README.md bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/README.md --native-eol CRLF + svn export ./Makefile bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/Makefile --native-eol CRLF + svn export ./.clang-format bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/.clang-format --native-eol CRLF + svn export ./bin bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/bin --native-eol CRLF + svn export ./build/premake/def bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/build/premake/def --native-eol CRLF + svn export ./build/premake/inc bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/build/premake/inc --native-eol CRLF + svn export ./build/premake/lnk bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/build/premake/lnk --native-eol CRLF + svn export ./build/scriptlib bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/build/scriptlib --native-eol CRLF + svn export ./build/svn_version bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/build/svn_version --native-eol CRLF + svn export ./build/vs bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/build/vs --native-eol CRLF + svn export ./build/vs2017winxpansi bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/build/vs2017winxpansi --native-eol CRLF + svn export ./build/vs2017winxp bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/build/vs2017winxp --native-eol CRLF + svn export ./build/vs2019win7 bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/build/vs2019win7 --native-eol CRLF + svn export ./build/vs2022win7 bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/build/vs2022win7 --native-eol CRLF + svn export ./build/vs2022win8 bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/build/vs2022win8 --native-eol CRLF + svn export ./build/vs2022win81 bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/build/vs2022win81 --native-eol CRLF + svn export ./build/vs2022win10 bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/build/vs2022win10 --native-eol CRLF + svn export ./build/vs2022win10uwp bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/build/vs2022win10uwp --native-eol CRLF + svn export ./build/vs2022win10clang bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/build/vs2022win10clang --native-eol CRLF + svn export ./build/download_externals.cmd bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/build/download_externals.cmd --native-eol CRLF + svn export ./common bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/common --native-eol CRLF + svn export ./doc/contributing.md bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/doc/contributing.md --native-eol CRLF + svn export ./doc/libopenmpt_styleguide.md bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/doc/libopenmpt_styleguide.md --native-eol CRLF + svn export ./doc/module_formats.md bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/doc/module_formats.md --native-eol CRLF + svn export ./soundlib bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/soundlib --native-eol CRLF + svn export ./sounddsp bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/sounddsp --native-eol CRLF + svn export ./src/mpt/.clang-format bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/.clang-format --native-eol CRLF svn export ./src/mpt/LICENSE.BSD-3-Clause.txt bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/LICENSE.BSD-3-Clause.txt --native-eol CRLF - svn export ./src/mpt/LICENSE.BSL-1.0.txt bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/LICENSE.BSL-1.0.txt --native-eol CRLF - svn export ./src/mpt/arch bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/arch --native-eol CRLF - svn export ./src/mpt/audio bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/audio --native-eol CRLF - svn export ./src/mpt/base bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/base --native-eol CRLF - svn export ./src/mpt/binary bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/binary --native-eol CRLF - svn export ./src/mpt/check bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/check --native-eol CRLF - svn export ./src/mpt/crc bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/crc --native-eol CRLF - #svn export ./src/mpt/crypto bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/crypto --native-eol CRLF - svn export ./src/mpt/detect bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/detect --native-eol CRLF - svn export ./src/mpt/endian bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/endian --native-eol CRLF - svn export ./src/mpt/environment bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/environment --native-eol CRLF - svn export ./src/mpt/exception bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/exception --native-eol CRLF - svn export ./src/mpt/format bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/format --native-eol CRLF - #svn export ./src/mpt/fs bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/fs --native-eol CRLF - svn export ./src/mpt/io bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/io --native-eol CRLF - svn export ./src/mpt/io_file bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/io_file --native-eol CRLF - svn export ./src/mpt/io_file_adapter bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/io_file_adapter --native-eol CRLF - svn export ./src/mpt/io_file_read bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/io_file_read --native-eol CRLF - svn export ./src/mpt/io_file_unique bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/io_file_unique --native-eol CRLF - svn export ./src/mpt/io_read bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/io_read --native-eol CRLF - svn export ./src/mpt/io_write bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/io_write --native-eol CRLF - #svn export ./src/mpt/json bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/json --native-eol CRLF - #svn export ./src/mpt/library bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/library --native-eol CRLF - svn export ./src/mpt/mutex bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/mutex --native-eol CRLF - svn export ./src/mpt/out_of_memory bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/out_of_memory --native-eol CRLF - svn export ./src/mpt/osinfo bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/osinfo --native-eol CRLF - svn export ./src/mpt/parse bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/parse --native-eol CRLF - svn export ./src/mpt/path bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/path --native-eol CRLF - svn export ./src/mpt/random bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/random --native-eol CRLF - svn export ./src/mpt/string bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/string --native-eol CRLF - svn export ./src/mpt/string_transcode bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/string_transcode --native-eol CRLF - svn export ./src/mpt/system_error bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/system_error --native-eol CRLF - svn export ./src/mpt/test bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/test --native-eol CRLF - svn export ./src/mpt/uuid bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/uuid --native-eol CRLF - #svn export ./src/mpt/uuid_namespace bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/uuid_namespace --native-eol CRLF - svn export ./src/openmpt/all bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/openmpt/all --native-eol CRLF - svn export ./src/openmpt/base bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/openmpt/base --native-eol CRLF - svn export ./src/openmpt/logging bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/openmpt/logging --native-eol CRLF - svn export ./src/openmpt/random bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/openmpt/random --native-eol CRLF - svn export ./src/openmpt/soundbase bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/openmpt/soundbase --native-eol CRLF - svn export ./test bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/test --native-eol CRLF - rm bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/test/mpt_tests_crypto.cpp - rm bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/test/mpt_tests_uuid_namespace.cpp - svn export ./libopenmpt bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/libopenmpt --native-eol CRLF - svn export ./examples bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/examples --native-eol CRLF - svn export ./openmpt123 bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/openmpt123 --native-eol CRLF - svn export ./contrib bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/contrib --native-eol CRLF - svn export ./include/minimp3 bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/include/minimp3 --native-eol CRLF - svn export ./include/miniz bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/include/miniz --native-eol CRLF - svn export ./include/mpg123 bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/include/mpg123 --native-eol CRLF - svn export ./include/flac bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/include/flac --native-eol CRLF - svn export ./include/portaudio bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/include/portaudio --native-eol CRLF - svn export ./include/ogg bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/include/ogg --native-eol CRLF - svn export ./include/pugixml bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/include/pugixml --native-eol CRLF - svn export ./include/stb_vorbis bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/include/stb_vorbis --native-eol CRLF - svn export ./include/vorbis bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/include/vorbis --native-eol CRLF - svn export ./include/winamp bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/include/winamp --native-eol CRLF - svn export ./include/xmplay bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/include/xmplay --native-eol CRLF - svn export ./include/zlib bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/include/zlib --native-eol CRLF - cp bin/$(FLAVOUR_DIR)dist.mk bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/build/dist.mk - cp bin/$(FLAVOUR_DIR)svn_version_dist.h bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/build/svn_version/svn_version.h + svn export ./src/mpt/LICENSE.BSL-1.0.txt bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/LICENSE.BSL-1.0.txt --native-eol CRLF + svn export ./src/mpt/arch bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/arch --native-eol CRLF + svn export ./src/mpt/audio bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/audio --native-eol CRLF + svn export ./src/mpt/base bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/base --native-eol CRLF + svn export ./src/mpt/binary bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/binary --native-eol CRLF + svn export ./src/mpt/check bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/check --native-eol CRLF + svn export ./src/mpt/crc bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/crc --native-eol CRLF + #svn export ./src/mpt/crypto bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/crypto --native-eol CRLF + svn export ./src/mpt/detect bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/detect --native-eol CRLF + svn export ./src/mpt/endian bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/endian --native-eol CRLF + svn export ./src/mpt/environment bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/environment --native-eol CRLF + svn export ./src/mpt/exception bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/exception --native-eol CRLF + svn export ./src/mpt/format bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/format --native-eol CRLF + #svn export ./src/mpt/fs bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/fs --native-eol CRLF + svn export ./src/mpt/io bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/io --native-eol CRLF + svn export ./src/mpt/io_file bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/io_file --native-eol CRLF + svn export ./src/mpt/io_file_adapter bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/io_file_adapter --native-eol CRLF + svn export ./src/mpt/io_file_read bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/io_file_read --native-eol CRLF + svn export ./src/mpt/io_file_unique bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/io_file_unique --native-eol CRLF + svn export ./src/mpt/io_read bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/io_read --native-eol CRLF + svn export ./src/mpt/io_write bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/io_write --native-eol CRLF + #svn export ./src/mpt/json bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/json --native-eol CRLF + #svn export ./src/mpt/library bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/library --native-eol CRLF + svn export ./src/mpt/main bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/main --native-eol CRLF + svn export ./src/mpt/mutex bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/mutex --native-eol CRLF + svn export ./src/mpt/out_of_memory bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/out_of_memory --native-eol CRLF + svn export ./src/mpt/osinfo bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/osinfo --native-eol CRLF + svn export ./src/mpt/parse bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/parse --native-eol CRLF + svn export ./src/mpt/path bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/path --native-eol CRLF + svn export ./src/mpt/random bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/random --native-eol CRLF + svn export ./src/mpt/string bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/string --native-eol CRLF + svn export ./src/mpt/string_transcode bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/string_transcode --native-eol CRLF + svn export ./src/mpt/system_error bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/system_error --native-eol CRLF + svn export ./src/mpt/test bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/test --native-eol CRLF + svn export ./src/mpt/uuid bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/uuid --native-eol CRLF + #svn export ./src/mpt/uuid_namespace bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/mpt/uuid_namespace --native-eol CRLF + svn export ./src/openmpt/all bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/openmpt/all --native-eol CRLF + svn export ./src/openmpt/base bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/openmpt/base --native-eol CRLF + svn export ./src/openmpt/fileformat_base bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/openmpt/fileformat_base --native-eol CRLF + svn export ./src/openmpt/logging bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/openmpt/logging --native-eol CRLF + svn export ./src/openmpt/random bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/openmpt/random --native-eol CRLF + svn export ./src/openmpt/soundbase bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/openmpt/soundbase --native-eol CRLF + svn export ./src/openmpt/soundfile_data bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/openmpt/soundfile_data --native-eol CRLF + svn export ./src/openmpt/soundfile_write bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/src/openmpt/soundfile_write --native-eol CRLF + svn export ./test bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/test --native-eol CRLF + rm bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/test/mpt_tests_crypto.cpp + rm bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/test/mpt_tests_uuid_namespace.cpp + svn export ./libopenmpt bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/libopenmpt --native-eol CRLF + svn export ./examples bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/examples --native-eol CRLF + svn export ./openmpt123 bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/openmpt123 --native-eol CRLF + svn export ./contrib bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/contrib --native-eol CRLF + svn export ./include/minimp3 bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/include/minimp3 --native-eol CRLF + svn export ./include/miniz bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/include/miniz --native-eol CRLF + svn export ./include/mpg123 bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/include/mpg123 --native-eol CRLF + svn export ./include/flac bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/include/flac --native-eol CRLF + svn export ./include/portaudio bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/include/portaudio --native-eol CRLF + svn export ./include/ogg bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/include/ogg --native-eol CRLF + svn export ./include/pugixml bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/include/pugixml --native-eol CRLF + svn export ./include/stb_vorbis bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/include/stb_vorbis --native-eol CRLF + svn export ./include/vorbis bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/include/vorbis --native-eol CRLF + svn export ./include/winamp bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/include/winamp --native-eol CRLF + svn export ./include/xmplay bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/include/xmplay --native-eol CRLF + svn export ./include/zlib bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/include/zlib --native-eol CRLF + cp bin/$(FLAVOUR_DIR)dist.mk bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/build/dist.mk + cp bin/$(FLAVOUR_DIR)svn_version_dist.h bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/build/svn_version/svn_version.h cd bin/$(FLAVOUR_DIR)dist-zip/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/ && zip -r ../libopenmpt-$(DIST_LIBOPENMPT_VERSION).msvc.zip --compression-method deflate -9 * .PHONY: bin/$(FLAVOUR_DIR)dist-zip/OpenMPT-src-$(DIST_OPENMPT_VERSION).zip @@ -1853,132 +2278,137 @@ bin/$(FLAVOUR_DIR)dist-zip/OpenMPT-src-$(DIST_OPENMPT_VERSION).zip: bin/$(FLAVOU .PHONY: bin/$(FLAVOUR_DIR)dist-js/libopenmpt-$(DIST_LIBOPENMPT_VERSION).dev.js.tar bin/$(FLAVOUR_DIR)dist-js/libopenmpt-$(DIST_LIBOPENMPT_VERSION).dev.js.tar: - mkdir -p bin/$(FLAVOUR_DIR)dist-js - rm -rf bin/$(FLAVOUR_DIR)dist-js/libopenmpt-$(DIST_LIBOPENMPT_VERSION) - mkdir -p bin/$(FLAVOUR_DIR)dist-js/libopenmpt-$(DIST_LIBOPENMPT_VERSION) - mkdir -p bin/$(FLAVOUR_DIR)dist-js/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/licenses - svn export ./LICENSE bin/$(FLAVOUR_DIR)dist-js/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/license.txt - svn export ./src/mpt/LICENSE.BSD-3-Clause.txt bin/$(FLAVOUR_DIR)dist-js/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/licenses/license.mpt.BSD-3-Clause.txt - svn export ./src/mpt/LICENSE.BSL-1.0.txt bin/$(FLAVOUR_DIR)dist-js/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/licenses/license.mpt.BSL-1.0.txt - svn export ./include/minimp3/LICENSE bin/$(FLAVOUR_DIR)dist-js/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/licenses/license.minimp3.txt - svn export ./include/miniz/miniz.c bin/$(FLAVOUR_DIR)dist-js/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/licenses/license.miniz.txt - svn export ./include/stb_vorbis/stb_vorbis.c bin/$(FLAVOUR_DIR)dist-js/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/licenses/license.stb_vorbis.txt - mkdir -p bin/$(FLAVOUR_DIR)dist-js/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/bin - mkdir -p bin/$(FLAVOUR_DIR)dist-js/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/bin/$(FLAVOUR_DIR)all - cp bin/$(FLAVOUR_DIR)stage/all/libopenmpt.js bin/$(FLAVOUR_DIR)dist-js/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/bin/$(FLAVOUR_DIR)all/libopenmpt.js - cp bin/$(FLAVOUR_DIR)stage/all/libopenmpt.wasm bin/$(FLAVOUR_DIR)dist-js/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/bin/$(FLAVOUR_DIR)all/libopenmpt.wasm - cp bin/$(FLAVOUR_DIR)stage/all/libopenmpt.wasm.js bin/$(FLAVOUR_DIR)dist-js/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/bin/$(FLAVOUR_DIR)all/libopenmpt.wasm.js - mkdir -p bin/$(FLAVOUR_DIR)dist-js/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/bin/$(FLAVOUR_DIR)wasm - cp bin/$(FLAVOUR_DIR)stage/wasm/libopenmpt.js bin/$(FLAVOUR_DIR)dist-js/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/bin/$(FLAVOUR_DIR)wasm/libopenmpt.js - cp bin/$(FLAVOUR_DIR)stage/wasm/libopenmpt.wasm bin/$(FLAVOUR_DIR)dist-js/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/bin/$(FLAVOUR_DIR)wasm/libopenmpt.wasm - mkdir -p bin/$(FLAVOUR_DIR)dist-js/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/bin/$(FLAVOUR_DIR)js - cp bin/$(FLAVOUR_DIR)stage/js/libopenmpt.js bin/$(FLAVOUR_DIR)dist-js/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/bin/$(FLAVOUR_DIR)js/libopenmpt.js - cd bin/$(FLAVOUR_DIR)dist-js/ && tar cv --numeric-owner --owner=0 --group=0 libopenmpt-$(DIST_LIBOPENMPT_VERSION) > libopenmpt-$(DIST_LIBOPENMPT_VERSION).dev.js.tar + mkdir -p bin/$(FLAVOUR_DIR)dist-js + rm -rf bin/$(FLAVOUR_DIR)dist-js/libopenmpt-$(DIST_LIBOPENMPT_VERSION) + mkdir -p bin/$(FLAVOUR_DIR)dist-js/libopenmpt-$(DIST_LIBOPENMPT_VERSION) + mkdir -p bin/$(FLAVOUR_DIR)dist-js/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/licenses + svn export ./LICENSE bin/$(FLAVOUR_DIR)dist-js/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/license.txt + svn export ./src/mpt/LICENSE.BSD-3-Clause.txt bin/$(FLAVOUR_DIR)dist-js/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/licenses/license.mpt.BSD-3-Clause.txt + svn export ./src/mpt/LICENSE.BSL-1.0.txt bin/$(FLAVOUR_DIR)dist-js/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/licenses/license.mpt.BSL-1.0.txt + svn export ./include/minimp3/LICENSE bin/$(FLAVOUR_DIR)dist-js/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/licenses/license.minimp3.txt + svn export ./include/miniz/miniz.c bin/$(FLAVOUR_DIR)dist-js/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/licenses/license.miniz.txt + svn export ./include/stb_vorbis/stb_vorbis.c bin/$(FLAVOUR_DIR)dist-js/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/licenses/license.stb_vorbis.txt + mkdir -p bin/$(FLAVOUR_DIR)dist-js/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/bin + mkdir -p bin/$(FLAVOUR_DIR)dist-js/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/bin/$(FLAVOUR_DIR)all + cp bin/$(FLAVOUR_DIR)stage/all/libopenmpt.js bin/$(FLAVOUR_DIR)dist-js/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/bin/$(FLAVOUR_DIR)all/libopenmpt.js + cp bin/$(FLAVOUR_DIR)stage/all/libopenmpt.wasm bin/$(FLAVOUR_DIR)dist-js/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/bin/$(FLAVOUR_DIR)all/libopenmpt.wasm + cp bin/$(FLAVOUR_DIR)stage/all/libopenmpt.wasm.js bin/$(FLAVOUR_DIR)dist-js/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/bin/$(FLAVOUR_DIR)all/libopenmpt.wasm.js + mkdir -p bin/$(FLAVOUR_DIR)dist-js/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/bin/$(FLAVOUR_DIR)wasm + cp bin/$(FLAVOUR_DIR)stage/wasm/libopenmpt.js bin/$(FLAVOUR_DIR)dist-js/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/bin/$(FLAVOUR_DIR)wasm/libopenmpt.js + cp bin/$(FLAVOUR_DIR)stage/wasm/libopenmpt.wasm bin/$(FLAVOUR_DIR)dist-js/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/bin/$(FLAVOUR_DIR)wasm/libopenmpt.wasm + mkdir -p bin/$(FLAVOUR_DIR)dist-js/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/bin/$(FLAVOUR_DIR)js + cp bin/$(FLAVOUR_DIR)stage/js/libopenmpt.js bin/$(FLAVOUR_DIR)dist-js/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/bin/$(FLAVOUR_DIR)js/libopenmpt.js + cd bin/$(FLAVOUR_DIR)dist-js/ && $(TAR_C) -v libopenmpt-$(DIST_LIBOPENMPT_VERSION) > libopenmpt-$(DIST_LIBOPENMPT_VERSION).dev.js.tar .PHONY: bin/$(FLAVOUR_DIR)dist-dos/libopenmpt-$(DIST_LIBOPENMPT_VERSION).bin.dos.zip bin/$(FLAVOUR_DIR)dist-dos/libopenmpt-$(DIST_LIBOPENMPT_VERSION).bin.dos.zip: mkdir -p bin/$(FLAVOUR_DIR)dist-dos - rm -rf bin/$(FLAVOUR_DIR)dist-dos/libopenmpt-$(DIST_LIBOPENMPT_VERSION) - mkdir -p bin/$(FLAVOUR_DIR)dist-dos/libopenmpt-$(DIST_LIBOPENMPT_VERSION) - mkdir -p bin/$(FLAVOUR_DIR)dist-dos/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/LICENSES - svn export ./LICENSE bin/$(FLAVOUR_DIR)dist-dos/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/LICENSE.TXT --native-eol CRLF - svn export ./src/mpt/LICENSE.BSD-3-Clause.txt bin/$(FLAVOUR_DIR)dist-dos/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/LICENSES/MPT_BSD3.TXT --native-eol CRLF - svn export ./src/mpt/LICENSE.BSL-1.0.txt bin/$(FLAVOUR_DIR)dist-dos/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/LICENSES/MPT_BSL1.TXT --native-eol CRLF - cp include/allegro42/readme.txt bin/$(FLAVOUR_DIR)dist-dos/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/LICENSES/ALLEGRO.TXT - cp include/cwsdpmi/bin/$(FLAVOUR_DIR)cwsdpmi.doc bin/$(FLAVOUR_DIR)dist-dos/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/LICENSES/CWSDPMI.TXT + rm -rf bin/$(FLAVOUR_DIR)dist-dos/libopenmpt-$(DIST_LIBOPENMPT_VERSION) + mkdir -p bin/$(FLAVOUR_DIR)dist-dos/libopenmpt-$(DIST_LIBOPENMPT_VERSION) + mkdir -p bin/$(FLAVOUR_DIR)dist-dos/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/LICENSES + svn export ./LICENSE bin/$(FLAVOUR_DIR)dist-dos/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/LICENSE.TXT --native-eol CRLF + svn export ./src/mpt/LICENSE.BSD-3-Clause.txt bin/$(FLAVOUR_DIR)dist-dos/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/LICENSES/MPT_BSD3.TXT --native-eol CRLF + svn export ./src/mpt/LICENSE.BSL-1.0.txt bin/$(FLAVOUR_DIR)dist-dos/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/LICENSES/MPT_BSL1.TXT --native-eol CRLF + cp include/allegro42/readme.txt bin/$(FLAVOUR_DIR)dist-dos/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/LICENSES/ALLEGRO.TXT + cp include/cwsdpmi/bin/$(FLAVOUR_DIR)cwsdpmi.doc bin/$(FLAVOUR_DIR)dist-dos/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/LICENSES/CWSDPMI.TXT ifeq ($(ALLOW_LGPL),1) - svn export ./include/mpg123/COPYING bin/$(FLAVOUR_DIR)dist-dos/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/LICENSES/MPG123.TXT --native-eol CRLF - svn export ./include/mpg123/AUTHORS bin/$(FLAVOUR_DIR)dist-dos/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/LICENSES/MPG123_A.TXT --native-eol CRLF - svn export ./include/vorbis/COPYING bin/$(FLAVOUR_DIR)dist-dos/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/LICENSES/VORBIS.TXT --native-eol CRLF - svn export ./include/zlib/README bin/$(FLAVOUR_DIR)dist-dos/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/LICENSES/ZLIB.TXT --native-eol CRLF + svn export ./include/mpg123/COPYING bin/$(FLAVOUR_DIR)dist-dos/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/LICENSES/MPG123.TXT --native-eol CRLF + svn export ./include/mpg123/AUTHORS bin/$(FLAVOUR_DIR)dist-dos/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/LICENSES/MPG123_A.TXT --native-eol CRLF + svn export ./include/vorbis/COPYING bin/$(FLAVOUR_DIR)dist-dos/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/LICENSES/VORBIS.TXT --native-eol CRLF + svn export ./include/zlib/README bin/$(FLAVOUR_DIR)dist-dos/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/LICENSES/ZLIB.TXT --native-eol CRLF else - svn export ./include/minimp3/LICENSE bin/$(FLAVOUR_DIR)dist-dos/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/LICENSES/MINIMP3.TXT --native-eol CRLF - svn export ./include/miniz/miniz.c bin/$(FLAVOUR_DIR)dist-dos/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/LICENSES/MINIZ.TXT --native-eol CRLF - svn export ./include/stb_vorbis/stb_vorbis.c bin/$(FLAVOUR_DIR)dist-dos/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/LICENSES/STBVORB.TXT --native-eol CRLF + svn export ./include/minimp3/LICENSE bin/$(FLAVOUR_DIR)dist-dos/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/LICENSES/MINIMP3.TXT --native-eol CRLF + svn export ./include/miniz/miniz.c bin/$(FLAVOUR_DIR)dist-dos/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/LICENSES/MINIZ.TXT --native-eol CRLF + svn export ./include/stb_vorbis/stb_vorbis.c bin/$(FLAVOUR_DIR)dist-dos/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/LICENSES/STBVORB.TXT --native-eol CRLF endif - mkdir -p bin/$(FLAVOUR_DIR)dist-dos/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/LICENSES/DJGPP + mkdir -p bin/$(FLAVOUR_DIR)dist-dos/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/LICENSES/DJGPP cp $(shell dirname $(shell which i386-pc-msdosdjgpp-gcc))/../license/copying bin/$(FLAVOUR_DIR)dist-dos/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/LICENSES/DJGPP/COPYING cp $(shell dirname $(shell which i386-pc-msdosdjgpp-gcc))/../license/copying.dj bin/$(FLAVOUR_DIR)dist-dos/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/LICENSES/DJGPP/COPYING.DJ cp $(shell dirname $(shell which i386-pc-msdosdjgpp-gcc))/../license/copying.lib bin/$(FLAVOUR_DIR)dist-dos/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/LICENSES/DJGPP/COPYING.LIB cp $(shell dirname $(shell which i386-pc-msdosdjgpp-gcc))/../license/source.txt bin/$(FLAVOUR_DIR)dist-dos/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/LICENSES/DJGPP/SOURCE.TXT - mkdir -p bin/$(FLAVOUR_DIR)dist-dos/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/SRC - cp build/externals/csdpmi7s.zip bin/$(FLAVOUR_DIR)dist-dos/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/SRC/CSDPMI7S.ZIP - mkdir -p bin/$(FLAVOUR_DIR)dist-dos/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/BIN - cp bin/$(FLAVOUR_DIR)openmpt123.exe bin/$(FLAVOUR_DIR)dist-dos/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/BIN/OMPT123.EXE - cp include/cwsdpmi/bin/cwsdpmi.doc bin/$(FLAVOUR_DIR)dist-dos/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/BIN/CWSDPMI.DOC - cp include/cwsdpmi/bin/CWSDPMI.EXE bin/$(FLAVOUR_DIR)dist-dos/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/BIN/CWSDPMI.EXE - cp include/cwsdpmi/bin/CWSDPR0.EXE bin/$(FLAVOUR_DIR)dist-dos/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/BIN/CWSDPR0.EXE - cp include/cwsdpmi/bin/cwsparam.doc bin/$(FLAVOUR_DIR)dist-dos/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/BIN/CWSPARAM.DOC - cp include/cwsdpmi/bin/CWSPARAM.EXE bin/$(FLAVOUR_DIR)dist-dos/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/BIN/CWSPARAM.EXE - cp include/cwsdpmi/bin/CWSDSTUB.EXE bin/$(FLAVOUR_DIR)dist-dos/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/BIN/CWSDSTUB.EXE - cp include/cwsdpmi/bin/CWSDSTR0.EXE bin/$(FLAVOUR_DIR)dist-dos/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/BIN/CWSDSTR0.EXE + mkdir -p bin/$(FLAVOUR_DIR)dist-dos/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/SRC + cp build/externals/csdpmi7s.zip bin/$(FLAVOUR_DIR)dist-dos/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/SRC/CSDPMI7S.ZIP + mkdir -p bin/$(FLAVOUR_DIR)dist-dos/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/BIN +ifeq ($(ALLOW_LGPL),1) +ifeq ($(ENABLE_DXE),1) + cp bin/$(FLAVOUR_DIR)mpg123.dxe bin/$(FLAVOUR_DIR)dist-dos/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/BIN/MPG123.DXE +endif +endif + cp bin/$(FLAVOUR_DIR)openmpt123.exe bin/$(FLAVOUR_DIR)dist-dos/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/BIN/OMPT123.EXE + cp include/cwsdpmi/bin/cwsdpmi.doc bin/$(FLAVOUR_DIR)dist-dos/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/BIN/CWSDPMI.DOC + cp include/cwsdpmi/bin/CWSDPMI.EXE bin/$(FLAVOUR_DIR)dist-dos/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/BIN/CWSDPMI.EXE + cp include/cwsdpmi/bin/CWSDPR0.EXE bin/$(FLAVOUR_DIR)dist-dos/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/BIN/CWSDPR0.EXE + cp include/cwsdpmi/bin/cwsparam.doc bin/$(FLAVOUR_DIR)dist-dos/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/BIN/CWSPARAM.DOC + cp include/cwsdpmi/bin/CWSPARAM.EXE bin/$(FLAVOUR_DIR)dist-dos/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/BIN/CWSPARAM.EXE + cp include/cwsdpmi/bin/CWSDSTUB.EXE bin/$(FLAVOUR_DIR)dist-dos/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/BIN/CWSDSTUB.EXE + cp include/cwsdpmi/bin/CWSDSTR0.EXE bin/$(FLAVOUR_DIR)dist-dos/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/BIN/CWSDSTR0.EXE cd bin/$(FLAVOUR_DIR)dist-dos/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/ && zip -r ../libopenmpt-$(DIST_LIBOPENMPT_VERSION).bin.dos.zip --compression-method deflate -9 * .PHONY: bin/$(FLAVOUR_DIR)dist-retro-win98/libopenmpt-$(DIST_LIBOPENMPT_VERSION).bin.retro.win98.zip bin/$(FLAVOUR_DIR)dist-retro-win98/libopenmpt-$(DIST_LIBOPENMPT_VERSION).bin.retro.win98.zip: - mkdir -p bin/$(FLAVOUR_DIR)dist-retro-win98 - rm -rf bin/$(FLAVOUR_DIR)dist-retro-win98/libopenmpt-$(DIST_LIBOPENMPT_VERSION) - mkdir -p bin/$(FLAVOUR_DIR)dist-retro-win98/libopenmpt-$(DIST_LIBOPENMPT_VERSION) - mkdir -p bin/$(FLAVOUR_DIR)dist-retro-win98/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/Licenses - svn export ./LICENSE bin/$(FLAVOUR_DIR)dist-retro-win98/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/LICENSE.TXT --native-eol CRLF - svn export ./doc/libopenmpt/changelog.md bin/$(FLAVOUR_DIR)dist-retro-win98/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/Changelog.txt --native-eol CRLF - svn export ./src/mpt/LICENSE.BSD-3-Clause.txt bin/$(FLAVOUR_DIR)dist-retro-win98/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/Licenses/license.mpt.BSD-3-Clause.txt --native-eol CRLF - svn export ./src/mpt/LICENSE.BSL-1.0.txt bin/$(FLAVOUR_DIR)dist-retro-win98/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/Licenses/license.mpt.BSL-1.0.txt --native-eol CRLF + mkdir -p bin/$(FLAVOUR_DIR)dist-retro-win98 + rm -rf bin/$(FLAVOUR_DIR)dist-retro-win98/libopenmpt-$(DIST_LIBOPENMPT_VERSION) + mkdir -p bin/$(FLAVOUR_DIR)dist-retro-win98/libopenmpt-$(DIST_LIBOPENMPT_VERSION) + mkdir -p bin/$(FLAVOUR_DIR)dist-retro-win98/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/Licenses + svn export ./LICENSE bin/$(FLAVOUR_DIR)dist-retro-win98/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/LICENSE.TXT --native-eol CRLF + svn export ./doc/libopenmpt/changelog.md bin/$(FLAVOUR_DIR)dist-retro-win98/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/Changelog.txt --native-eol CRLF + svn export ./src/mpt/LICENSE.BSD-3-Clause.txt bin/$(FLAVOUR_DIR)dist-retro-win98/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/Licenses/license.mpt.BSD-3-Clause.txt --native-eol CRLF + svn export ./src/mpt/LICENSE.BSL-1.0.txt bin/$(FLAVOUR_DIR)dist-retro-win98/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/Licenses/license.mpt.BSL-1.0.txt --native-eol CRLF ifeq ($(ALLOW_LGPL),1) - svn export ./include/mpg123/COPYING bin/$(FLAVOUR_DIR)dist-retro-win98/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/Licenses/License.mpg123.txt --native-eol CRLF - svn export ./include/mpg123/AUTHORS bin/$(FLAVOUR_DIR)dist-retro-win98/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/Licenses/License.Authors.txt --native-eol CRLF - svn export ./include/vorbis/COPYING bin/$(FLAVOUR_DIR)dist-retro-win98/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/Licenses/License.Vorbis.txt --native-eol CRLF - svn export ./include/zlib/README bin/$(FLAVOUR_DIR)dist-retro-win98/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/Licenses/License.zlib.txt --native-eol CRLF + svn export ./include/mpg123/COPYING bin/$(FLAVOUR_DIR)dist-retro-win98/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/Licenses/License.mpg123.txt --native-eol CRLF + svn export ./include/mpg123/AUTHORS bin/$(FLAVOUR_DIR)dist-retro-win98/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/Licenses/License.Authors.txt --native-eol CRLF + svn export ./include/vorbis/COPYING bin/$(FLAVOUR_DIR)dist-retro-win98/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/Licenses/License.Vorbis.txt --native-eol CRLF + svn export ./include/zlib/README bin/$(FLAVOUR_DIR)dist-retro-win98/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/Licenses/License.zlib.txt --native-eol CRLF else - svn export ./include/minimp3/LICENSE bin/$(FLAVOUR_DIR)dist-retro-win98/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/Licenses/License.minimp3.txt --native-eol CRLF - svn export ./include/miniz/miniz.c bin/$(FLAVOUR_DIR)dist-retro-win98/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/Licenses/License.miniz.txt --native-eol CRLF - svn export ./include/stb_vorbis/stb_vorbis.c bin/$(FLAVOUR_DIR)dist-retro-win98/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/Licenses/License.stb_vorbis.txt --native-eol CRLF + svn export ./include/minimp3/LICENSE bin/$(FLAVOUR_DIR)dist-retro-win98/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/Licenses/License.minimp3.txt --native-eol CRLF + svn export ./include/miniz/miniz.c bin/$(FLAVOUR_DIR)dist-retro-win98/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/Licenses/License.miniz.txt --native-eol CRLF + svn export ./include/stb_vorbis/stb_vorbis.c bin/$(FLAVOUR_DIR)dist-retro-win98/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/Licenses/License.stb_vorbis.txt --native-eol CRLF endif - mkdir -p bin/$(FLAVOUR_DIR)dist-retro-win98/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/openmpt123 - cp bin/$(FLAVOUR_DIR)openmpt123.exe bin/$(FLAVOUR_DIR)dist-retro-win98/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/openmpt123/openmpt123.exe - mkdir -p bin/$(FLAVOUR_DIR)dist-retro-win98/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/XMPlay - svn export ./libopenmpt/xmp-openmpt/xmp-openmpt.txt bin/$(FLAVOUR_DIR)dist-retro-win98/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/XMPlay/xmp-openmpt.txt --native-eol CRLF - cp bin/$(FLAVOUR_DIR)xmp-openmpt.dll bin/$(FLAVOUR_DIR)dist-retro-win98/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/XMPlay/xmp-openmpt.dll - mkdir -p bin/$(FLAVOUR_DIR)dist-retro-win98/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/Winamp - svn export ./libopenmpt/in_openmpt/in_openmpt.txt bin/$(FLAVOUR_DIR)dist-retro-win98/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/Winamp/in_openmpt.txt --native-eol CRLF - cp bin/$(FLAVOUR_DIR)in_openmpt.dll bin/$(FLAVOUR_DIR)dist-retro-win98/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/Winamp/in_openmpt.dll + mkdir -p bin/$(FLAVOUR_DIR)dist-retro-win98/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/openmpt123 + cp bin/$(FLAVOUR_DIR)openmpt123.exe bin/$(FLAVOUR_DIR)dist-retro-win98/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/openmpt123/openmpt123.exe + mkdir -p bin/$(FLAVOUR_DIR)dist-retro-win98/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/XMPlay + svn export ./libopenmpt/xmp-openmpt/xmp-openmpt.txt bin/$(FLAVOUR_DIR)dist-retro-win98/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/XMPlay/xmp-openmpt.txt --native-eol CRLF + cp bin/$(FLAVOUR_DIR)xmp-openmpt.dll bin/$(FLAVOUR_DIR)dist-retro-win98/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/XMPlay/xmp-openmpt.dll + mkdir -p bin/$(FLAVOUR_DIR)dist-retro-win98/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/Winamp + svn export ./libopenmpt/in_openmpt/in_openmpt.txt bin/$(FLAVOUR_DIR)dist-retro-win98/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/Winamp/in_openmpt.txt --native-eol CRLF + cp bin/$(FLAVOUR_DIR)in_openmpt.dll bin/$(FLAVOUR_DIR)dist-retro-win98/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/Winamp/in_openmpt.dll cd bin/$(FLAVOUR_DIR)dist-retro-win98/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/ && ../../../build/tools/7zip/7z a -tzip -mx=9 ../libopenmpt-$(DIST_LIBOPENMPT_VERSION).bin.retro.win98.zip * .PHONY: bin/$(FLAVOUR_DIR)dist-retro-win95/libopenmpt-$(DIST_LIBOPENMPT_VERSION).bin.retro.win95.zip bin/$(FLAVOUR_DIR)dist-retro-win95/libopenmpt-$(DIST_LIBOPENMPT_VERSION).bin.retro.win95.zip: - mkdir -p bin/$(FLAVOUR_DIR)dist-retro-win95 - rm -rf bin/$(FLAVOUR_DIR)dist-retro-win95/libopenmpt-$(DIST_LIBOPENMPT_VERSION) - mkdir -p bin/$(FLAVOUR_DIR)dist-retro-win95/libopenmpt-$(DIST_LIBOPENMPT_VERSION) - mkdir -p bin/$(FLAVOUR_DIR)dist-retro-win95/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/Licenses - svn export ./LICENSE bin/$(FLAVOUR_DIR)dist-retro-win95/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/LICENSE.TXT --native-eol CRLF - svn export ./doc/libopenmpt/changelog.md bin/$(FLAVOUR_DIR)dist-retro-win95/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/Changelog.txt --native-eol CRLF - svn export ./src/mpt/LICENSE.BSD-3-Clause.txt bin/$(FLAVOUR_DIR)dist-retro-win95/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/Licenses/license.mpt.BSD-3-Clause.txt --native-eol CRLF - svn export ./src/mpt/LICENSE.BSL-1.0.txt bin/$(FLAVOUR_DIR)dist-retro-win95/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/Licenses/license.mpt.BSL-1.0.txt --native-eol CRLF + mkdir -p bin/$(FLAVOUR_DIR)dist-retro-win95 + rm -rf bin/$(FLAVOUR_DIR)dist-retro-win95/libopenmpt-$(DIST_LIBOPENMPT_VERSION) + mkdir -p bin/$(FLAVOUR_DIR)dist-retro-win95/libopenmpt-$(DIST_LIBOPENMPT_VERSION) + mkdir -p bin/$(FLAVOUR_DIR)dist-retro-win95/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/Licenses + svn export ./LICENSE bin/$(FLAVOUR_DIR)dist-retro-win95/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/LICENSE.TXT --native-eol CRLF + svn export ./doc/libopenmpt/changelog.md bin/$(FLAVOUR_DIR)dist-retro-win95/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/Changelog.txt --native-eol CRLF + svn export ./src/mpt/LICENSE.BSD-3-Clause.txt bin/$(FLAVOUR_DIR)dist-retro-win95/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/Licenses/license.mpt.BSD-3-Clause.txt --native-eol CRLF + svn export ./src/mpt/LICENSE.BSL-1.0.txt bin/$(FLAVOUR_DIR)dist-retro-win95/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/Licenses/license.mpt.BSL-1.0.txt --native-eol CRLF ifeq ($(ALLOW_LGPL),1) - svn export ./include/mpg123/COPYING bin/$(FLAVOUR_DIR)dist-retro-win95/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/Licenses/License.mpg123.txt --native-eol CRLF - svn export ./include/mpg123/AUTHORS bin/$(FLAVOUR_DIR)dist-retro-win95/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/Licenses/License.Authors.txt --native-eol CRLF - svn export ./include/vorbis/COPYING bin/$(FLAVOUR_DIR)dist-retro-win95/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/Licenses/License.Vorbis.txt --native-eol CRLF - svn export ./include/zlib/README bin/$(FLAVOUR_DIR)dist-retro-win95/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/Licenses/License.zlib.txt --native-eol CRLF + svn export ./include/mpg123/COPYING bin/$(FLAVOUR_DIR)dist-retro-win95/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/Licenses/License.mpg123.txt --native-eol CRLF + svn export ./include/mpg123/AUTHORS bin/$(FLAVOUR_DIR)dist-retro-win95/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/Licenses/License.Authors.txt --native-eol CRLF + svn export ./include/vorbis/COPYING bin/$(FLAVOUR_DIR)dist-retro-win95/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/Licenses/License.Vorbis.txt --native-eol CRLF + svn export ./include/zlib/README bin/$(FLAVOUR_DIR)dist-retro-win95/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/Licenses/License.zlib.txt --native-eol CRLF else - svn export ./include/minimp3/LICENSE bin/$(FLAVOUR_DIR)dist-retro-win95/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/Licenses/License.minimp3.txt --native-eol CRLF - svn export ./include/miniz/miniz.c bin/$(FLAVOUR_DIR)dist-retro-win95/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/Licenses/License.miniz.txt --native-eol CRLF - svn export ./include/stb_vorbis/stb_vorbis.c bin/$(FLAVOUR_DIR)dist-retro-win95/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/Licenses/License.stb_vorbis.txt --native-eol CRLF + svn export ./include/minimp3/LICENSE bin/$(FLAVOUR_DIR)dist-retro-win95/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/Licenses/License.minimp3.txt --native-eol CRLF + svn export ./include/miniz/miniz.c bin/$(FLAVOUR_DIR)dist-retro-win95/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/Licenses/License.miniz.txt --native-eol CRLF + svn export ./include/stb_vorbis/stb_vorbis.c bin/$(FLAVOUR_DIR)dist-retro-win95/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/Licenses/License.stb_vorbis.txt --native-eol CRLF endif - mkdir -p bin/$(FLAVOUR_DIR)dist-retro-win95/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/openmpt123 - cp bin/$(FLAVOUR_DIR)openmpt123.exe bin/$(FLAVOUR_DIR)dist-retro-win95/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/openmpt123/openmpt123.exe - #mkdir -p bin/$(FLAVOUR_DIR)dist-retro-win95/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/XMPlay - #svn export ./libopenmpt/xmp-openmpt/xmp-openmpt.txt bin/$(FLAVOUR_DIR)dist-retro-win95/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/XMPlay/xmp-openmpt.txt --native-eol CRLF - #cp bin/$(FLAVOUR_DIR)xmp-openmpt.dll bin/$(FLAVOUR_DIR)dist-retro-win95/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/XMPlay/xmp-openmpt.dll - #mkdir -p bin/$(FLAVOUR_DIR)dist-retro-win95/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/Winamp - #svn export ./libopenmpt/in_openmpt/in_openmpt.txt bin/$(FLAVOUR_DIR)dist-retro-win95/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/Winamp/in_openmpt.txt --native-eol CRLF - #cp bin/$(FLAVOUR_DIR)in_openmpt.dll bin/$(FLAVOUR_DIR)dist-retro-win95/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/Winamp/in_openmpt.dll + mkdir -p bin/$(FLAVOUR_DIR)dist-retro-win95/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/openmpt123 + cp bin/$(FLAVOUR_DIR)openmpt123.exe bin/$(FLAVOUR_DIR)dist-retro-win95/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/openmpt123/openmpt123.exe + #mkdir -p bin/$(FLAVOUR_DIR)dist-retro-win95/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/XMPlay + #svn export ./libopenmpt/xmp-openmpt/xmp-openmpt.txt bin/$(FLAVOUR_DIR)dist-retro-win95/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/XMPlay/xmp-openmpt.txt --native-eol CRLF + #cp bin/$(FLAVOUR_DIR)xmp-openmpt.dll bin/$(FLAVOUR_DIR)dist-retro-win95/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/XMPlay/xmp-openmpt.dll + #mkdir -p bin/$(FLAVOUR_DIR)dist-retro-win95/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/Winamp + #svn export ./libopenmpt/in_openmpt/in_openmpt.txt bin/$(FLAVOUR_DIR)dist-retro-win95/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/Winamp/in_openmpt.txt --native-eol CRLF + #cp bin/$(FLAVOUR_DIR)in_openmpt.dll bin/$(FLAVOUR_DIR)dist-retro-win95/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/Winamp/in_openmpt.dll cd bin/$(FLAVOUR_DIR)dist-retro-win95/libopenmpt-$(DIST_LIBOPENMPT_VERSION)/ && 7z a -tzip -mx=9 ../libopenmpt-$(DIST_LIBOPENMPT_VERSION).bin.retro.win95.zip * -bin/$(FLAVOUR_DIR)libopenmpt.a: $(LIBOPENMPT_OBJECTS) +bin/$(FLAVOUR_DIR)libopenmpt.a: $(LIBOPENMPT_OBJECTS) $(LIBOPENMPT_LIBS) $(INFO) [AR] $@ $(SILENT)$(AR) $(ARFLAGS) $@ $^ -bin/$(FLAVOUR_DIR)libopenmpt$(SOSUFFIX): $(LIBOPENMPT_OBJECTS) +bin/$(FLAVOUR_DIR)libopenmpt$(SOSUFFIX): $(LIBOPENMPT_OBJECTS) $(LIBOPENMPT_LIBS) $(INFO) [LD] $@ ifeq ($(NO_SHARED_LINKER_FLAG),1) $(SILENT)$(LINK.cc) $(LIBOPENMPT_LDFLAGS) $(SO_LDFLAGS) $^ $(LOADLIBES) $(LDLIBS) -o $@ @@ -1994,7 +2424,7 @@ bin/$(FLAVOUR_DIR)openmpt123.1: bin/$(FLAVOUR_DIR)openmpt123$(EXESUFFIX) openmpt $(INFO) [HELP2MAN] $@ $(SILENT)help2man --no-discard-stderr --no-info --version-option=--man-version --help-option=--man-help --include=openmpt123/openmpt123.h2m $< > $@ -bin/$(FLAVOUR_DIR)in_openmpt$(SOSUFFIX): $(INOPENMPT_OBJECTS) $(LIBOPENMPT_OBJECTS) +bin/$(FLAVOUR_DIR)in_openmpt$(SOSUFFIX): $(INOPENMPT_OBJECTS) $(LIBOPENMPT_OBJECTS) $(LIBOPENMPT_LIBS) $(INFO) [LD] $@ ifeq ($(NO_SHARED_LINKER_FLAG),1) $(SILENT)$(LINK.cc) $(LIBOPENMPT_LDFLAGS) $(SO_LDFLAGS) $^ $(LOADLIBES) $(LDLIBS) -o $@ @@ -2002,7 +2432,7 @@ else $(SILENT)$(LINK.cc) -shared $(LIBOPENMPT_LDFLAGS) $(SO_LDFLAGS) $^ $(LOADLIBES) $(LDLIBS) -o $@ endif -bin/$(FLAVOUR_DIR)xmp-openmpt$(SOSUFFIX): $(XMPOPENMPT_OBJECTS) $(LIBOPENMPT_OBJECTS) +bin/$(FLAVOUR_DIR)xmp-openmpt$(SOSUFFIX): $(XMPOPENMPT_OBJECTS) $(LIBOPENMPT_OBJECTS) $(LIBOPENMPT_LIBS) $(INFO) [LD] $@ ifeq ($(NO_SHARED_LINKER_FLAG),1) $(SILENT)$(LINK.cc) $(LIBOPENMPT_LDFLAGS) $(SO_LDFLAGS) $^ $(LOADLIBES) $(LDLIBS) -lgdi32 -o $@ @@ -2174,12 +2604,12 @@ clean-dist: .PHONY: distversion distversion: - $(SILENT)echo "$(DIST_LIBOPENMPT_VERSION)" + $(SILENT)echo "$(DIST_LIBOPENMPT_VERSION)" > bin/distversion .PHONY: distversion-pure distversion-pure: - $(SILENT)echo "$(DIST_LIBOPENMPT_VERSION_PURE)" + $(SILENT)echo "$(DIST_LIBOPENMPT_VERSION_PURE)" > bin/distversion-pure .PHONY: distversion-tarball distversion-tarball: - $(SILENT)echo "$(DIST_LIBOPENMPT_TARBALL_VERSION)" + $(SILENT)echo "$(DIST_LIBOPENMPT_TARBALL_VERSION)" > bin/distversion-tarball diff --git a/Frameworks/OpenMPT/OpenMPT/build/android_ndk/Android.mk b/Frameworks/OpenMPT/OpenMPT/build/android_ndk/Android.mk index bdbd8a535..2c13c554a 100644 --- a/Frameworks/OpenMPT/OpenMPT/build/android_ndk/Android.mk +++ b/Frameworks/OpenMPT/OpenMPT/build/android_ndk/Android.mk @@ -7,40 +7,40 @@ include $(CLEAR_VARS) LOCAL_MODULE := openmpt ifeq ($(NDK_MAJOR),) -LOCAL_CFLAGS += -std=c17 +LOCAL_CFLAGS += -std=c18 LOCAL_CPPFLAGS += -std=c++17 else ifeq ($(NDK_MAJOR),21) # clang 9 -LOCAL_CFLAGS += -std=c17 +LOCAL_CFLAGS += -std=c18 LOCAL_CPPFLAGS += -std=c++17 else ifeq ($(NDK_MAJOR),22) # clang 11 -LOCAL_CFLAGS += -std=c17 +LOCAL_CFLAGS += -std=c18 LOCAL_CPPFLAGS += -std=c++20 else ifeq ($(NDK_MAJOR),23) # clang 12 -LOCAL_CFLAGS += -std=c17 +LOCAL_CFLAGS += -std=c18 LOCAL_CPPFLAGS += -std=c++20 else ifeq ($(NDK_MAJOR),24) # clang 14 -LOCAL_CFLAGS += -std=c17 +LOCAL_CFLAGS += -std=c18 LOCAL_CPPFLAGS += -std=c++20 else ifeq ($(NDK_MAJOR),25) # clang 14 -LOCAL_CFLAGS += -std=c17 +LOCAL_CFLAGS += -std=c18 LOCAL_CPPFLAGS += -std=c++20 else ifeq ($(NDK_MAJOR),26) # clang 17 -LOCAL_CFLAGS += -std=c17 +LOCAL_CFLAGS += -std=c18 LOCAL_CPPFLAGS += -std=c++20 else ifeq ($(NDK_MAJOR),27) # clang 18 -LOCAL_CFLAGS += -std=c17 -LOCAL_CPPFLAGS += -std=c++20 +LOCAL_CFLAGS += -std=c23 +LOCAL_CPPFLAGS += -std=c++23 else -LOCAL_CFLAGS += -std=c17 -LOCAL_CPPFLAGS += -std=c++20 +LOCAL_CFLAGS += -std=c23 +LOCAL_CPPFLAGS += -std=c++23 endif endif @@ -124,8 +124,6 @@ endif LOCAL_SRC_FILES += \ common/ComponentManager.cpp \ common/Logging.cpp \ - common/mptFileIO.cpp \ - common/mptFileTemporary.cpp \ common/mptFileType.cpp \ common/mptPathString.cpp \ common/mptRandom.cpp \ @@ -146,6 +144,7 @@ LOCAL_SRC_FILES += \ soundlib/Dlsbank.cpp \ soundlib/Fastmix.cpp \ soundlib/InstrumentExtensions.cpp \ + soundlib/InstrumentSynth.cpp \ soundlib/ITCompression.cpp \ soundlib/ITTools.cpp \ soundlib/Load_667.cpp \ @@ -153,20 +152,28 @@ LOCAL_SRC_FILES += \ 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_j2b.cpp \ + soundlib/Load_kris.cpp \ soundlib/Load_mdl.cpp \ soundlib/Load_med.cpp \ soundlib/Load_mid.cpp \ @@ -178,19 +185,26 @@ LOCAL_SRC_FILES += \ 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_ult.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/Message.cpp \ soundlib/MIDIEvents.cpp \ + soundlib/MIDIMacroParser.cpp \ soundlib/MIDIMacros.cpp \ soundlib/MixerLoops.cpp \ soundlib/MixerSettings.cpp \ @@ -202,12 +216,15 @@ LOCAL_SRC_FILES += \ soundlib/ModSequence.cpp \ soundlib/modsmp_ctrl.cpp \ soundlib/mod_specifications.cpp \ + soundlib/MODTools.cpp \ soundlib/MPEGFrame.cpp \ soundlib/OggStream.cpp \ soundlib/OPL.cpp \ soundlib/Paula.cpp \ soundlib/patternContainer.cpp \ soundlib/pattern.cpp \ + soundlib/PlaybackTest.cpp \ + soundlib/PlayState.cpp \ soundlib/RowVisitor.cpp \ soundlib/S3MTools.cpp \ soundlib/SampleFormats.cpp \ diff --git a/Frameworks/OpenMPT/OpenMPT/build/android_ndk/Application.mk b/Frameworks/OpenMPT/OpenMPT/build/android_ndk/Application.mk index d0f0e2418..b885a8a47 100644 --- a/Frameworks/OpenMPT/OpenMPT/build/android_ndk/Application.mk +++ b/Frameworks/OpenMPT/OpenMPT/build/android_ndk/Application.mk @@ -1,39 +1,39 @@ ifeq ($(NDK_MAJOR),) -APP_CFLAGS := -std=c17 +APP_CFLAGS := -std=c18 APP_CPPFLAGS := -std=c++17 -fexceptions -frtti else ifeq ($(NDK_MAJOR),21) # clang 9 -APP_CFLAGS := -std=c17 +APP_CFLAGS := -std=c18 APP_CPPFLAGS := -std=c++17 -fexceptions -frtti else ifeq ($(NDK_MAJOR),22) # clang 11 -APP_CFLAGS := -std=c17 +APP_CFLAGS := -std=c18 APP_CPPFLAGS := -std=c++20 -fexceptions -frtti else ifeq ($(NDK_MAJOR),23) # clang 12 -APP_CFLAGS := -std=c17 +APP_CFLAGS := -std=c18 APP_CPPFLAGS := -std=c++20 -fexceptions -frtti else ifeq ($(NDK_MAJOR),24) # clang 14 -APP_CFLAGS := -std=c17 +APP_CFLAGS := -std=c18 APP_CPPFLAGS := -std=c++20 -fexceptions -frtti else ifeq ($(NDK_MAJOR),25) # clang 14 -APP_CFLAGS := -std=c17 +APP_CFLAGS := -std=c18 APP_CPPFLAGS := -std=c++20 -fexceptions -frtti else ifeq ($(NDK_MAJOR),26) # clang 17 -APP_CFLAGS := -std=c17 +APP_CFLAGS := -std=c18 APP_CPPFLAGS := -std=c++20 -fexceptions -frtti else ifeq ($(NDK_MAJOR),27) # clang 18 -APP_CFLAGS := -std=c17 -APP_CPPFLAGS := -std=c++20 -fexceptions -frtti +APP_CFLAGS := -std=c23 +APP_CPPFLAGS := -std=c++23 -fexceptions -frtti else -APP_CFLAGS := -std=c17 -APP_CPPFLAGS := -std=c++20 -fexceptions -frtti +APP_CFLAGS := -std=c23 +APP_CPPFLAGS := -std=c++23 -fexceptions -frtti endif endif diff --git a/Frameworks/OpenMPT/OpenMPT/build/dist.mk b/Frameworks/OpenMPT/OpenMPT/build/dist.mk index c558fe7fc..0f7bc7629 100644 --- a/Frameworks/OpenMPT/OpenMPT/build/dist.mk +++ b/Frameworks/OpenMPT/OpenMPT/build/dist.mk @@ -1,4 +1,4 @@ -MPT_SVNVERSION=22826 -MPT_SVNURL=https://source.openmpt.org/svn/openmpt/tags/libopenmpt-0.7.13 -MPT_SVNDATE=2025-01-06T13:49:43.586768Z +MPT_SVNVERSION=23245 +MPT_SVNURL=https://source.openmpt.org/svn/openmpt/tags/libopenmpt-0.8.0 +MPT_SVNDATE=2025-05-31T13:56:14.548439Z diff --git a/Frameworks/OpenMPT/OpenMPT/build/djgpp/bin/dxe3gen b/Frameworks/OpenMPT/OpenMPT/build/djgpp/bin/dxe3gen new file mode 100644 index 000000000..e6500518f --- /dev/null +++ b/Frameworks/OpenMPT/OpenMPT/build/djgpp/bin/dxe3gen @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +exec i386-pc-msdosdjgpp-dxe3gen "$@" diff --git a/Frameworks/OpenMPT/OpenMPT/build/djgpp/bin/dxe3gen.sh b/Frameworks/OpenMPT/OpenMPT/build/djgpp/bin/dxe3gen.sh new file mode 100644 index 000000000..1d8981101 --- /dev/null +++ b/Frameworks/OpenMPT/OpenMPT/build/djgpp/bin/dxe3gen.sh @@ -0,0 +1,3 @@ +#!/usr/bin/env bash +echo "$@" +exec i386-pc-msdosdjgpp-dxe3gen "$@" diff --git a/Frameworks/OpenMPT/OpenMPT/build/djgpp/bin/dxe3res b/Frameworks/OpenMPT/OpenMPT/build/djgpp/bin/dxe3res new file mode 100644 index 000000000..2a4654ffa --- /dev/null +++ b/Frameworks/OpenMPT/OpenMPT/build/djgpp/bin/dxe3res @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +exec i386-pc-msdosdjgpp-dxe3res "$@" diff --git a/Frameworks/OpenMPT/OpenMPT/build/djgpp/bin/dxe3res.sh b/Frameworks/OpenMPT/OpenMPT/build/djgpp/bin/dxe3res.sh new file mode 100644 index 000000000..2a4654ffa --- /dev/null +++ b/Frameworks/OpenMPT/OpenMPT/build/djgpp/bin/dxe3res.sh @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +exec i386-pc-msdosdjgpp-dxe3res "$@" diff --git a/Frameworks/OpenMPT/OpenMPT/build/download_externals.sh b/Frameworks/OpenMPT/OpenMPT/build/download_externals.sh index 1c8ac95cf..82932fb01 100644 --- a/Frameworks/OpenMPT/OpenMPT/build/download_externals.sh +++ b/Frameworks/OpenMPT/OpenMPT/build/download_externals.sh @@ -12,7 +12,9 @@ function download () { MPT_GET_FILE_NAME="$1" MPT_GET_FILE_SIZE="$2" MPT_GET_FILE_CHECKSUM="$3" - MPT_GET_URLS="$4" + shift + shift + shift echo "Checking '$MPT_GET_FILE_NAME' ..." if [ -f "$MPT_GET_FILE_NAME" ]; then FILE_SIZE=$(find "$MPT_GET_FILE_NAME" -printf '%s') @@ -28,10 +30,15 @@ function download () { rm -f "$MPT_GET_FILE_NAME" fi fi - for URL in $MPT_GET_URLS; do + while (( "$#" )); do + URL="$(echo ""$1"" | sed 's/ /%20/g')" if [ ! -f "$MPT_GET_FILE_NAME" ]; then echo "Downloading '$MPT_GET_FILE_NAME' from '$URL' ..." - curl -o "$MPT_GET_FILE_NAME" "$URL" + if command -v curl &> /dev/null ; then + curl --location -o "$MPT_GET_FILE_NAME" "$URL" || true + elif command -v wget &> /dev/null ; then + wget -O "$MPT_GET_FILE_NAME" "$URL" || true + fi echo "Verifying '$URL' ..." if [ -f "$MPT_GET_FILE_NAME" ]; then FILE_SIZE=$(find "$MPT_GET_FILE_NAME" -printf '%s') @@ -48,6 +55,7 @@ function download () { fi fi fi + shift done if [ ! -f "$MPT_GET_FILE_NAME" ]; then echo "Failed to download '$MPT_GET_FILE_NAME'." @@ -95,11 +103,12 @@ if [ ! -d "build/tools" ]; then mkdir build/tools fi -download "build/externals/allegro-4.2.3.1-hg.8+r8500.zip" 3872466 46cd8d4d7138b795dbc66994e953d0abc578c6d3c00615e3580237458529d33d7ad9d269a9778918d4b3719d75750d5cca74ff6bf38ad357a766472799ee9e7b "https://lib.openmpt.org/files/libopenmpt/contrib/allegro/allegro-4.2.3.1-hg.8+r8500.zip" -download "build/externals/csdpmi7b.zip" 71339 58c24691d27cead1cec92d334af551f37a3ba31de25a687d99399c28d822ec9f6ffccc9332bfce35e65dae4dd1210b54e54b223a4de17f5adcb11e2da004b834 "https://lib.openmpt.org/files/libopenmpt/contrib/djgpp/cwsdpmi/csdpmi7b.zip https://djgpp.mirror.garr.it/current/v2misc/csdpmi7b.zip" -download "build/externals/csdpmi7s.zip" 89872 ea5652d31850d8eb0d15a919de0b51849f58efea0d16ad2aa4687fac4abd223d0ca34a2d1b616b02fafe84651dbef3e506df9262cfb399eb6d9909bffc89bfd3 "https://lib.openmpt.org/files/libopenmpt/contrib/djgpp/cwsdpmi/csdpmi7s.zip https://djgpp.mirror.garr.it/current/v2misc/csdpmi7s.zip" -download "build/externals/WA5.55_SDK.exe" 336166 394375db8a16bf155b5de9376f6290488ab339e503dbdfdc4e2f5bede967799e625c559cca363bc988324f1a8e86e5fd28a9f697422abd7bb3dcde4a766607b5 "http://download.nullsoft.com/winamp/plugin-dev/WA5.55_SDK.exe https://web.archive.org/web/20131217072017id_/http://download.nullsoft.com/winamp/plugin-dev/WA5.55_SDK.exe" -download "build/externals/xmp-sdk.zip" 322903 67b96c6e6aa794e9de4f446d23f969e3591457196fd100c5475f5df52308de861a0c411db54fcb2bf46a12e9136ddda9d2974a5167432a979a701ef2c4679ef9 "https://www.un4seen.com/files/xmp-sdk.zip" +# download +cat build/download_externals.txt | ( + while IFS=$'\n' read -r URL; do + eval download $URL + done +) unpack "include/allegro42" "build/externals/allegro-4.2.3.1-hg.8+r8500.zip" "." unpack "include/cwsdpmi" "build/externals/csdpmi7b.zip" "." @@ -108,3 +117,6 @@ unpack "include/xmplay" "build/externals/xmp-sdk.zip" "." ln -s OUT.H include/winamp/Winamp/out.h +mkdir -p build/tools/svn_apply_autoprops +cp "build/externals/svn_apply_autoprops.py" "build/tools/svn_apply_autoprops/" +chmod u+x "build/tools/svn_apply_autoprops/svn_apply_autoprops.py" diff --git a/Frameworks/OpenMPT/OpenMPT/build/make/config-afl.mk b/Frameworks/OpenMPT/OpenMPT/build/make/config-afl.mk index 69938ec5c..30997c8ba 100644 --- a/Frameworks/OpenMPT/OpenMPT/build/make/config-afl.mk +++ b/Frameworks/OpenMPT/OpenMPT/build/make/config-afl.mk @@ -14,9 +14,12 @@ endif ifneq ($(STDCXX),) CXXFLAGS_STDCXX = -std=$(STDCXX) -fexceptions -frtti -pthread -# We do not enable C++20 for fuzzer builds, because it prevents detecting -# shifting of signed values which changed from undefined to defined behaviour -# in C++20. As we still support C+ü+17, we need to catch these problem cases. +# We do not enable C++20 or C++23 for fuzzer builds, because it prevents +# detecting shifting of signed values which changed from undefined to defined +# behaviour in C++20 and C++23. As we still support C++17, we need to catch +# these problem cases. +#else ifeq ($(shell printf '\n' > bin/empty.cpp ; if $(CXX) -std=c++23 -c bin/empty.cpp -o bin/empty.out > /dev/null 2>&1 ; then echo 'c++23' ; fi ), c++23) +#CXXFLAGS_STDCXX = -std=c++23 -fexceptions -frtti -pthread #else ifeq ($(shell printf '\n' > bin/empty.cpp ; if $(CXX) -std=c++20 -c bin/empty.cpp -o bin/empty.out > /dev/null 2>&1 ; then echo 'c++20' ; fi ), c++20) #CXXFLAGS_STDCXX = -std=c++20 -fexceptions -frtti -pthread else @@ -24,6 +27,10 @@ CXXFLAGS_STDCXX = -std=c++17 -fexceptions -frtti -pthread endif ifneq ($(STDC),) CFLAGS_STDC = -std=$(STDC) -pthread +else ifeq ($(shell printf '\n' > bin/empty.c ; if $(CC) -std=c23 -c bin/empty.c -o bin/empty.out > /dev/null 2>&1 ; then echo 'c23' ; fi ), c23) +CFLAGS_STDC = -std=c23 -pthread +else ifeq ($(shell printf '\n' > bin/empty.c ; if $(CC) -std=c18 -c bin/empty.c -o bin/empty.out > /dev/null 2>&1 ; then echo 'c18' ; fi ), c18) +CFLAGS_STDC = -std=c18 -pthread else ifeq ($(shell printf '\n' > bin/empty.c ; if $(CC) -std=c17 -c bin/empty.c -o bin/empty.out > /dev/null 2>&1 ; then echo 'c17' ; fi ), c17) CFLAGS_STDC = -std=c17 -pthread else diff --git a/Frameworks/OpenMPT/OpenMPT/build/make/config-aocc.mk b/Frameworks/OpenMPT/OpenMPT/build/make/config-aocc.mk index ac5697b84..7c9b3efc3 100644 --- a/Frameworks/OpenMPT/OpenMPT/build/make/config-aocc.mk +++ b/Frameworks/OpenMPT/OpenMPT/build/make/config-aocc.mk @@ -12,8 +12,12 @@ ifeq ($(origin AR),default) AR = $(TOOLCHAIN_PREFIX)ar$(TOOLCHAIN_SUFFIX) endif +STDCXX?=c++20 + ifneq ($(STDCXX),) CXXFLAGS_STDCXX = -std=$(STDCXX) -fexceptions -frtti -pthread +else ifeq ($(shell printf '\n' > bin/empty.cpp ; if $(CXX) -std=c++23 -c bin/empty.cpp -o bin/empty.out > /dev/null 2>&1 ; then echo 'c++23' ; fi ), c++23) +CXXFLAGS_STDCXX = -std=c++23 -fexceptions -frtti -pthread else ifeq ($(shell printf '\n' > bin/empty.cpp ; if $(CXX) -std=c++20 -c bin/empty.cpp -o bin/empty.out > /dev/null 2>&1 ; then echo 'c++20' ; fi ), c++20) CXXFLAGS_STDCXX = -std=c++20 -fexceptions -frtti -pthread else @@ -21,6 +25,10 @@ CXXFLAGS_STDCXX = -std=c++17 -fexceptions -frtti -pthread endif ifneq ($(STDC),) CFLAGS_STDC = -std=$(STDC) -pthread +else ifeq ($(shell printf '\n' > bin/empty.c ; if $(CC) -std=c23 -c bin/empty.c -o bin/empty.out > /dev/null 2>&1 ; then echo 'c23' ; fi ), c23) +CFLAGS_STDC = -std=c23 -pthread +else ifeq ($(shell printf '\n' > bin/empty.c ; if $(CC) -std=c18 -c bin/empty.c -o bin/empty.out > /dev/null 2>&1 ; then echo 'c18' ; fi ), c18) +CFLAGS_STDC = -std=c18 -pthread else ifeq ($(shell printf '\n' > bin/empty.c ; if $(CC) -std=c17 -c bin/empty.c -o bin/empty.out > /dev/null 2>&1 ; then echo 'c17' ; fi ), c17) CFLAGS_STDC = -std=c17 -pthread else diff --git a/Frameworks/OpenMPT/OpenMPT/build/make/config-clang.mk b/Frameworks/OpenMPT/OpenMPT/build/make/config-clang.mk index cb5d8b314..64335fc29 100644 --- a/Frameworks/OpenMPT/OpenMPT/build/make/config-clang.mk +++ b/Frameworks/OpenMPT/OpenMPT/build/make/config-clang.mk @@ -14,6 +14,8 @@ endif ifneq ($(STDCXX),) CXXFLAGS_STDCXX = -std=$(STDCXX) -fexceptions -frtti -pthread +else ifeq ($(shell printf '\n' > bin/empty.cpp ; if $(CXX) -std=c++23 -c bin/empty.cpp -o bin/empty.out > /dev/null 2>&1 ; then echo 'c++23' ; fi ), c++23) +CXXFLAGS_STDCXX = -std=c++23 -fexceptions -frtti -pthread else ifeq ($(shell printf '\n' > bin/empty.cpp ; if $(CXX) -std=c++20 -c bin/empty.cpp -o bin/empty.out > /dev/null 2>&1 ; then echo 'c++20' ; fi ), c++20) CXXFLAGS_STDCXX = -std=c++20 -fexceptions -frtti -pthread else @@ -21,6 +23,10 @@ CXXFLAGS_STDCXX = -std=c++17 -fexceptions -frtti -pthread endif ifneq ($(STDC),) CFLAGS_STDC = -std=$(STDC) -pthread +else ifeq ($(shell printf '\n' > bin/empty.c ; if $(CC) -std=c23 -c bin/empty.c -o bin/empty.out > /dev/null 2>&1 ; then echo 'c23' ; fi ), c23) +CFLAGS_STDC = -std=c23 -pthread +else ifeq ($(shell printf '\n' > bin/empty.c ; if $(CC) -std=c18 -c bin/empty.c -o bin/empty.out > /dev/null 2>&1 ; then echo 'c18' ; fi ), c18) +CFLAGS_STDC = -std=c18 -pthread else ifeq ($(shell printf '\n' > bin/empty.c ; if $(CC) -std=c17 -c bin/empty.c -o bin/empty.out > /dev/null 2>&1 ; then echo 'c17' ; fi ), c17) CFLAGS_STDC = -std=c17 -pthread else @@ -55,11 +61,13 @@ endif ifeq ($(CHECKED_ADDRESS),1) CXXFLAGS += -fsanitize=address CFLAGS += -fsanitize=address +NO_NO_UNDEFINED_LINKER_FLAG=1 endif ifeq ($(CHECKED_UNDEFINED),1) CXXFLAGS += -fsanitize=undefined CFLAGS += -fsanitize=undefined +NO_NO_UNDEFINED_LINKER_FLAG=1 endif include build/make/warnings-clang.mk diff --git a/Frameworks/OpenMPT/OpenMPT/build/make/config-cygwin.mk b/Frameworks/OpenMPT/OpenMPT/build/make/config-cygwin.mk index 9894887a5..a7b109a94 100644 --- a/Frameworks/OpenMPT/OpenMPT/build/make/config-cygwin.mk +++ b/Frameworks/OpenMPT/OpenMPT/build/make/config-cygwin.mk @@ -14,6 +14,8 @@ endif ifneq ($(STDCXX),) CXXFLAGS_STDCXX = -std=$(STDCXX) -fexceptions -frtti -pthread +else ifeq ($(shell printf '\n' > bin/empty.cpp ; if $(CXX) -std=c++23 -c bin/empty.cpp -o bin/empty.out > /dev/null 2>&1 ; then echo 'c++23' ; fi ), c++23) +CXXFLAGS_STDCXX = -std=c++23 -fexceptions -frtti -pthread else ifeq ($(shell printf '\n' > bin/empty.cpp ; if $(CXX) -std=c++20 -c bin/empty.cpp -o bin/empty.out > /dev/null 2>&1 ; then echo 'c++20' ; fi ), c++20) CXXFLAGS_STDCXX = -std=c++20 -fexceptions -frtti -pthread else @@ -21,6 +23,8 @@ CXXFLAGS_STDCXX = -std=c++17 -fexceptions -frtti -pthread endif ifneq ($(STDC),) CFLAGS_STDC = -std=$(STDC) -pthread +else ifeq ($(shell printf '\n' > bin/empty.c ; if $(CC) -std=c23 -c bin/empty.c -o bin/empty.out > /dev/null 2>&1 ; then echo 'c23' ; fi ), c23) +CFLAGS_STDC = -std=c23 -pthread else ifeq ($(shell printf '\n' > bin/empty.c ; if $(CC) -std=c18 -c bin/empty.c -o bin/empty.out > /dev/null 2>&1 ; then echo 'c18' ; fi ), c18) CFLAGS_STDC = -std=c18 -pthread else ifeq ($(shell printf '\n' > bin/empty.c ; if $(CC) -std=c17 -c bin/empty.c -o bin/empty.out > /dev/null 2>&1 ; then echo 'c17' ; fi ), c17) diff --git a/Frameworks/OpenMPT/OpenMPT/build/make/config-defaults.mk b/Frameworks/OpenMPT/OpenMPT/build/make/config-defaults.mk index cc0a94132..07aed1355 100644 --- a/Frameworks/OpenMPT/OpenMPT/build/make/config-defaults.mk +++ b/Frameworks/OpenMPT/OpenMPT/build/make/config-defaults.mk @@ -49,6 +49,7 @@ else ifeq ($(HOST_FLAVOUR),OPENBSD) NO_PORTAUDIOCPP?=1 NO_PULSEAUDIO?=1 +LDLIBS_PLATFORM=-lc++ -lc include build/make/config-clang.mk MPT_COMPILER_NOALLOCAH=1 diff --git a/Frameworks/OpenMPT/OpenMPT/build/make/config-djgpp.mk b/Frameworks/OpenMPT/OpenMPT/build/make/config-djgpp.mk index 26683ff25..ae27d01b3 100644 --- a/Frameworks/OpenMPT/OpenMPT/build/make/config-djgpp.mk +++ b/Frameworks/OpenMPT/OpenMPT/build/make/config-djgpp.mk @@ -12,10 +12,15 @@ ifeq ($(origin AR),default) AR = i386-pc-msdosdjgpp-ar endif +DXE3GEN = i386-pc-msdosdjgpp-dxe3gen +DXE3RES = i386-pc-msdosdjgpp-dxe3res + # Note that we are using GNU extensions instead of 100% standards-compliant # mode, because otherwise DJGPP-specific headers/functions are unavailable. ifneq ($(STDCXX),) CXXFLAGS_STDCXX = -std=$(STDCXX) -fexceptions -frtti -fpermissive +else ifeq ($(shell printf '\n' > bin/empty.cpp ; if $(CXX) -std=gnu++23 -c bin/empty.cpp -o bin/empty.out > /dev/null 2>&1 ; then echo 'c++23' ; fi ), c++23) +CXXFLAGS_STDCXX = -std=gnu++23 -fexceptions -frtti -fpermissive else ifeq ($(shell printf '\n' > bin/empty.cpp ; if $(CXX) -std=gnu++20 -c bin/empty.cpp -o bin/empty.out > /dev/null 2>&1 ; then echo 'c++20' ; fi ), c++20) CXXFLAGS_STDCXX = -std=gnu++20 -fexceptions -frtti -fpermissive else @@ -23,15 +28,21 @@ CXXFLAGS_STDCXX = -std=gnu++17 -fexceptions -frtti -fpermissive endif ifneq ($(STDC),) CFLAGS_STDC = -std=$(STDC) +else ifeq ($(shell printf '\n' > bin/empty.c ; if $(CC) -std=gnu23 -c bin/empty.c -o bin/empty.out > /dev/null 2>&1 ; then echo 'c23' ; fi ), c23) +CFLAGS_STDC = -std=gnu23 +else ifeq ($(shell printf '\n' > bin/empty.c ; if $(CC) -std=gnu20 -c bin/empty.c -o bin/empty.out > /dev/null 2>&1 ; then echo 'c20' ; fi ), c20) +CFLAGS_STDC = -std=gnu20 else ifeq ($(shell printf '\n' > bin/empty.c ; if $(CC) -std=gnu17 -c bin/empty.c -o bin/empty.out > /dev/null 2>&1 ; then echo 'c17' ; fi ), c17) CFLAGS_STDC = -std=gnu17 else CFLAGS_STDC = -std=gnu11 endif -CXXFLAGS += $(CXXFLAGS_STDCXX) -fallow-store-data-races -fno-threadsafe-statics -CFLAGS += $(CFLAGS_STDC) -fallow-store-data-races +CXXFLAGS += $(CXXFLAGS_STDCXX) +CFLAGS += $(CFLAGS_STDC) +OVERWRITE_CXXFLAGS += -fallow-store-data-races -fno-threadsafe-statics +OVERWRITE_CFLAGS += -fallow-store-data-races -CPU?=generic/common +CPU?=generic/compatible # Enable 128bit SSE registers. # This requires pure DOS with only CWSDPMI as DOS extender. @@ -46,7 +57,10 @@ ifneq ($(SSE),0) FPU_MMX := -m80387 -mmmx -mfpmath=387 FPU_3DNOW := -m80387 -mmmx -m3dnow -mfpmath=387 FPU_3DNOWA := -m80387 -mmmx -m3dnow -m3dnowa -mfpmath=387 - FPU_3DASSE := -m80387 -mmmx -m3dnow -m3dnowa -mfxsr -msse -mfpmath=sse,387 + FPU_3DSSE := -m80387 -mmmx -m3dnow -m3dnowa -mfxsr -msse -mfpmath=sse,387 + FPU_3DSSE2 := -m80387 -mmmx -m3dnow -m3dnowa -mfxsr -msse -msse2 -mfpmath=sse + FPU_3DSSE3 := -m80387 -mmmx -m3dnow -m3dnowa -mfxsr -msse -msse2 -msse3 -mfpmath=sse + FPU_3DSSE4 := -m80387 -mmmx -m3dnow -m3dnowa -mfxsr -msse -msse2 -msse3 -msse4a -mfpmath=sse FPU_SSE := -m80387 -mmmx -mfxsr -msse -mfpmath=sse,387 FPU_SSE2 := -m80387 -mmmx -mfxsr -msse -msse2 -mfpmath=sse FPU_SSE3 := -m80387 -mmmx -mfxsr -msse -msse2 -msse3 -mfpmath=sse @@ -62,7 +76,10 @@ else FPU_MMX := -m80387 -mmmx -mfpmath=387 FPU_3DNOW := -m80387 -mmmx -m3dnow -mfpmath=387 FPU_3DNOWA := -m80387 -mmmx -m3dnow -m3dnowa -mfpmath=387 - FPU_3DASSE := -mno-sse -mno-fxsr -m80387 -mmmx -m3dnow -m3dnowa -mfpmath=387 + FPU_3DSSE := -mno-sse -mno-fxsr -m80387 -mmmx -m3dnow -m3dnowa -mfpmath=387 + FPU_3DSSE2 := -mno-sse2 -mno-sse -mno-fxsr -m80387 -mmmx -m3dnow -m3dnowa -mfpmath=387 + FPU_3DSSE3 := -mno-sse3 -mno-sse2 -mno-sse -mno-fxsr -m80387 -mmmx -m3dnow -m3dnowa -mfpmath=387 + FPU_3DSSE4 := -mno-sse4a -mno-sse3 -mno-sse2 -mno-sse -mno-fxsr -m80387 -mmmx -m3dnow -m3dnowa -mfpmath=387 FPU_SSE := -mno-sse -mno-fxsr -m80387 -mmmx -mfpmath=387 FPU_SSE2 := -mno-sse2 -mno-sse -mno-fxsr -m80387 -mmmx -mfpmath=387 FPU_SSE3 := -mno-sse3 -mno-sse2 -mno-sse -mno-fxsr -m80387 -mmmx -mfpmath=387 @@ -73,12 +90,56 @@ else FPU_SSSE4A := -mno-sse4a -mno-ssse3 -mno-sse3 -mno-sse2 -mno-sse -mno-fxsr -m80387 -mmmx -mfpmath=387 endif -OPT_DEF := -Os -OPT_SIMD := -O3 + + +ifeq ($(OPTIMIZE),default) + +OPT_UARCH_EMUL := -Os # interpreter +OPT_UARCH_CISC := -Os # non-pipelined, scalar, in-order, optimize for size i386 am386 +OPT_UARCH_PIPE := -Os # pipelined, scalar, in-order, optimize for size i486 am486 cx486slc +OPT_UARCH_SCAL := -O2 # pipelined, super-scalar, in-order, optimize for speed pentium cx5x86 +OPT_UARCH_OOOE := -O2 # pipelined, super-scalar, out-of-order, optimize for speed pentiumpro k5 cx6x86 +OPT_UARCH_COMP := -O2 # recompiler + +# vectorize for MMX/3DNOW (64bit wide) (unsupported by GCC) +OPT_UARCH_EMUL_64 := -Os # interpreter +OPT_UARCH_CISC_64 := -Os # non-pipelined, scalar, in-order, optimize for size +OPT_UARCH_PIPE_64 := -Os # pipelined, scalar, in-order, optimize for size +OPT_UARCH_SCAL_64 := -O2 # pipelined, super-scalar, in-order, optimize for speed pentium-mmx +OPT_UARCH_OOOE_64 := -O2 # pipelined, super-scalar, out-of-order, optimize for speed pentium2 k6 m2 +OPT_UARCH_COMP_64 := -O2 # recompiler + +# vectorize for SSE (128bit wide) +ifeq ($(SSE),0) +OPT_UARCH_EMUL_128 := -Os # interpreter +OPT_UARCH_CISC_128 := -Os # non-pipelined, scalar, in-order, optimize for size +OPT_UARCH_PIPE_128 := -Os # pipelined, scalar, in-order, optimize for size +OPT_UARCH_SCAL_128 := -O2 # pipelined, super-scalar, in-order, optimize for speed +OPT_UARCH_OOOE_128 := -O2 # pipelined, super-scalar, out-of-order, optimize for speed +OPT_UARCH_COMP_128 := -O2 # recompiler +else +OPT_UARCH_EMUL_128 := -O3 # interpreter +OPT_UARCH_CISC_128 := -O3 # non-pipelined, scalar, in-order, optimize for size +OPT_UARCH_PIPE_128 := -O3 # pipelined, scalar, in-order, optimize for size +OPT_UARCH_SCAL_128 := -O3 # pipelined, super-scalar, in-order, optimize for speed +OPT_UARCH_OOOE_128 := -O3 # pipelined, super-scalar, out-of-order, optimize for speed +OPT_UARCH_COMP_128 := -O3 # recompiler +endif + +else + +OPT_UARCH_EMUL := +OPT_UARCH_CISC := +OPT_UARCH_PIPE := +OPT_UARCH_SCAL := +OPT_UARCH_OOOE := +OPT_UARCH_COMP := + +endif -CACHE_386 :=64 # 0/64/128 +CACHE_386 :=64 # 0/32/64/128 CACHE_486 :=128 # 0/64/128/256 CACHE_S7 :=256 # 128/256/512 CACHE_SS7 :=512 # 256/512/1024 @@ -97,7 +158,6 @@ CACHE_PENTIUMM :=1024 # 1024/2048 CACHE_ATOM :=512 # 512 -CACHE_K63 :=256 # 128/256 CACHE_ATHLON :=512 # 512 CACHE_ATHLONXP :=256 # 256/512 CACHE_ATHLON64 :=512 # 256/512/1024 @@ -110,187 +170,234 @@ CACHE_SEMPRON64 :=128 # 128/256/512 TUNE_586 :=-mtune=pentium TUNE_586MMX :=-mtune=pentium-mmx +TUNE_5863DN :=-mtune=k6-2 TUNE_686 :=-mtune=pentiumpro TUNE_686MMX :=-mtune=pentium2 +TUNE_6863DN :=-mtune=athlon TUNE_686SSE :=-mtune=pentium3 TUNE_686SSE2:=-mtune=pentium-m TUNE_686SSE3:=-mtune=pentium-m -generic/early := $(XXX) -march=i386 $(FPU_NONE) -mtune=i386 $(OPT_DEF) +generic/early := $(XXX) -march=i386 $(FPU_NONE) -mtune=i386 $(OPT_UARCH_CISC) -generic/common := $(XXX) -march=i386 $(FPU_387) -mtune=pentium $(OPT_DEF) -generic/late := $(XXX) -march=i686 $(FPU_SSSE3) -mtune=generic $(OPT_SIMD) +generic/compatible := $(XXX) -march=i386 $(FPU_387) -mtune=pentium $(OPT_UARCH_CISC) +generic/common := $(XXX) -march=i486 $(FPU_387) -mtune=pentium $(OPT_UARCH_CISC) + +generic/late := $(XXX) -march=i686 $(FPU_SSE2) -mtune=generic $(OPT_UARCH_OOOE_128) -generic/nofpu := $(X__) -march=i386 $(FPU_NONE) -mtune=i386 $(OPT_DEF) # 386SX, 386DX, 486SX, Cyrix Cx486SLC..Cx486S, NexGen Nx586 +virtual/ibmulator := $(___) -march=i386 $(FPU_NONE) -mtune=i386 $(OPT_UARCH_EMUL) -generic/386 := $(X__) -march=i386 $(FPU_387) -mtune=i386 $(OPT_DEF) # 386+387 +virtual/ao486 := $(___) -march=i486 $(FPU_NONE) -mtune=i486 $(OPT_UARCH_PIPE) -generic/486 := $(XX_) -march=i486 $(FPU_387) -mtune=i486 $(OPT_DEF) # 486DX, AMD Am5x86, Cyrix Cx4x86DX..6x86L, NexGen Nx586-PF -generic/486-mmx := $(___) -march=i486 $(FPU_MMX) -mtune=winchip-c6 $(OPT_SIMD) # IDT WinChip-C6, Rise mP6 -generic/486-3dnow := $(___) -march=i486 $(FPU_3DNOW) -mtune=winchip2 $(OPT_SIMD) # IDT WinChip-2 +virtual/bochs := $(___) -march=i686 $(FPU_387) -mtune=generic $(OPT_UARCH_EMUL) -generic/586 := $(XX_) -march=i586 $(FPU_387) -mtune=pentium $(OPT_DEF) # Intel Pentium, AMD K5 -generic/586-mmx := $(XX_) -march=pentium-mmx $(FPU_MMX) -mtune=pentium-mmx $(OPT_SIMD) # Intel Pentium-MMX, AMD K6 -generic/586-3dnow := $(XX_) -march=k6-2 $(FPU_3DNOW) -mtune=k6-2 $(OPT_SIMD) # AMD K6-2..K6-3 +virtual/qemu := $(___) -march=i686 $(FPU_SSE2) -mtune=generic $(OPT_UARCH_COMP_128) -generic/686 := $(___) -march=pentiumpro $(FPU_387) -mtune=pentiumpro $(OPT_DEF) # Intel Pentium-Pro -generic/686-mmx := $(XXX) -march=i686 $(FPU_MMX) -mtune=pentium2 $(OPT_SIMD) # Intel Pentium-2.., AMD Bulldozer.., VIA C3-Nehemiah.., Cyrix 6x86MX.., Transmeta Crusoe.., NSC Geode-GX1.. -generic/686-3dnow := $(___) -march=i686 $(FPU_3DNOW) -mtune=c3 $(OPT_SIMD) # VIA Cyrix-3..C3-Ezra -generic/686-3dnowa:= $(XX_) -march=athlon $(FPU_3DNOWA) -mtune=athlon $(OPT_SIMD) # AMD Athlon..K10 +virtual/varcem := $(___) -march=i686 $(FPU_387) -mtune=generic $(OPT_UARCH_COMP) +virtual/pcem := $(___) -march=i686 $(FPU_3DNOW) -mtune=generic $(OPT_UARCH_COMP_64) +virtual/86box := $(___) -march=i686 $(FPU_3DNOW) -mtune=generic $(OPT_UARCH_COMP_64) +virtual/pcbox := $(___) -march=i686 $(FPU_SSE2) -mtune=generic $(OPT_UARCH_COMP_128) +virtual/unipcemu := $(___) -march=i386 $(FPU_NONE) -mtune=i386 $(OPT_UARCH_EMUL) -generic/sse := $(X__) -march=i686 $(FPU_SSE) -mtune=pentium3 $(OPT_SIMD) # Intel Pentium-3.., AMD Athlon-XP.., VIA C3-Nehemiah.., Transmeta Efficeon.., DM&P Vortex86DX3.. -generic/sse2 := $(XX_) -march=i686 $(FPU_SSE2) -mtune=generic $(OPT_SIMD) # Intel Pentium-4.., AMD Athlon-64.., VIA C7-Esther.., Transmeta Efficeon.. -generic/sse3 := $(___) -march=i686 $(FPU_SSE3) -mtune=generic $(OPT_SIMD) # Intel Core.., AMD Athlon-64-X2.., VIA C7-Esther.., Transmeta Efficeon-88xx.. -generic/ssse3 := $(___) -march=i686 $(FPU_SSSE3) -mtune=generic $(OPT_SIMD) # Intel Core-2.., AMD Bobcat.., Via Nano-1000.. -generic/sse4_1 := $(___) -march=i686 $(FPU_SSE4_1) -mtune=generic $(OPT_SIMD) # Intel Core-1st, AMD Bulldozer.., Via Nano-3000.. -generic/sse4_2 := $(___) -march=i686 $(FPU_SSE4_2) -mtune=generic $(OPT_SIMD) # Intel Core-1st, AMD Bulldozer.., Via Nano-C.. +virtual/dosbox := $(___) -march=i486 $(FPU_387) -mtune=i386 $(OPT_UARCH_EMUL) +virtual/dosbox-svn := $(___) -march=i486 $(FPU_387) -mtune=i386 $(OPT_UARCH_EMUL) +virtual/dosbox-ece := $(___) -march=i486 $(FPU_387) -mtune=i386 $(OPT_UARCH_EMUL) +virtual/dosbox-sta := $(___) -march=i486 $(FPU_387) -mtune=i386 $(OPT_UARCH_COMP) +virtual/dosbox-x := $(___) -march=i686 $(FPU_SSE) -mtune=generic $(OPT_UARCH_EMUL_128) -intel/i386 := $(XX_) -march=i386 $(FPU_NONE) -mtune=i386 $(OPT_DEF) --param l1-cache-size=0 --param l2-cache-size=$(CACHE_386) -intel/i486sx := $(___) -march=i486 $(FPU_NONE) -mtune=i486 $(OPT_DEF) --param l1-cache-size=6 --param l2-cache-size=$(CACHE_486) -intel/i386+80287 := $(___) -march=i386 $(FPU_287) -mtune=i386 $(OPT_DEF) --param l1-cache-size=0 --param l2-cache-size=$(CACHE_386) +generic/nofpu := $(X__) -march=i386 $(FPU_NONE) -mtune=i386 $(OPT_UARCH_CISC) # 386SX, 386DX, 486SX, Cyrix Cx486SLC..Cx486S, NexGen Nx586 -intel/i386+80387 := $(XX_) -march=i386 $(FPU_387) -mtune=i386 $(OPT_DEF) --param l1-cache-size=0 --param l2-cache-size=$(CACHE_386) -intel/i486dx := $(XXX) -march=i486 $(FPU_387) -mtune=i486 $(OPT_DEF) --param l1-cache-size=6 --param l2-cache-size=$(CACHE_486) -intel/pentium := $(XXX) -march=pentium $(FPU_387) -mtune=pentium $(OPT_DEF) --param l1-cache-size=8 --param l2-cache-size=$(CACHE_S7) -intel/pentium-mmx := $(XXX) -march=pentium-mmx $(FPU_MMX) -mtune=pentium-mmx $(OPT_SIMD) --param l1-cache-size=16 --param l2-cache-size=$(CACHE_S7) -intel/pentium-pro := $(___) -march=pentiumpro $(FPU_387) -mtune=pentiumpro $(OPT_DEF) --param l1-cache-size=8 --param l2-cache-size=$(CACHE_PENTIUMPRO) -intel/pentium2 := $(___) -march=pentium2 $(FPU_MMX) -mtune=pentium2 $(OPT_SIMD) --param l1-cache-size=16 --param l2-cache-size=$(CACHE_PENTIUM2) -intel/pentium3 := $(___) -march=pentium3 $(FPU_SSE) -mtune=pentium3 $(OPT_SIMD) --param l1-cache-size=16 --param l2-cache-size=$(CACHE_PENTIUM3) -intel/pentium4 := $(___) -march=pentium4 $(FPU_SSE2) -mtune=pentium4 $(OPT_SIMD) --param l1-cache-size=8 --param l2-cache-size=$(CACHE_PENTIUM4) -intel/pentium4.1 := $(___) -march=prescott $(FPU_SSE3) -mtune=prescott $(OPT_SIMD) --param l1-cache-size=8 --param l2-cache-size=$(CACHE_PENTIUM41) -intel/core2 := $(___) -march=core2 $(FPU_SSSE3) -mtune=core2 $(OPT_SIMD) --param l1-cache-size=32 --param l2-cache-size=$(CACHE_CORE2) +generic/386 := $(X__) -march=i386 $(FPU_387) -mtune=i386 $(OPT_UARCH_CISC) # 386+387 -intel/celeron := $(___) -march=pentium2 $(FPU_MMX) -mtune=pentium2 $(OPT_SIMD) --param l1-cache-size=16 --param l2-cache-size=$(CACHE_CELERON) -intel/pentium-m := $(___) -march=pentium-m $(FPU_SSE2) -mtune=pentium-m $(OPT_SIMD) --param l1-cache-size=16 --param l2-cache-size=$(CACHE_PENTIUMM) -intel/core := $(___) -march=pentium-m $(FPU_SSE3) -mtune=core2 $(OPT_SIMD) --param l1-cache-size=32 --param l2-cache-size=$(CACHE_CORE) -intel/atom := $(___) -march=bonnell $(FPU_SSSE3) -mtune=bonnell $(OPT_SIMD) --param l1-cache-size=24 --param l2-cache-size=$(CACHE_ATOM) +generic/486 := $(XX_) -march=i486 $(FPU_387) -mtune=i486 $(OPT_UARCH_PIPE) # 486DX, AMD Am5x86, Cyrix Cx4x86DX..6x86L, NexGen Nx586-PF -intel/late := $(XX_) -march=i686 $(FPU_SSSE3) -mtune=intel $(OPT_SIMD) +generic/586 := $(XXX) -march=i586 $(FPU_387) -mtune=pentium $(OPT_UARCH_SCAL) # Intel Pentium, AMD K5 +generic/586-mmx := $(XX_) -march=pentium-mmx $(FPU_MMX) -mtune=pentium-mmx $(OPT_UARCH_SCAL_64) # Intel Pentium-MMX, AMD K6, IDT WinChip-C6, Rise mP6 +generic/586-3dnow := $(XX_) -march=k6-2 $(FPU_3DNOW) -mtune=k6-2 $(OPT_UARCH_SCAL_64) # AMD K6-2..K6-3+, IDT WinChip-2, VIA-C3-Samuel..VIA C3-Ezra + +generic/686 := $(___) -march=pentiumpro $(FPU_387) -mtune=pentiumpro $(OPT_UARCH_OOOE) # Intel Pentium-Pro +generic/686-mmx := $(XX_) -march=i686 $(FPU_MMX) -mtune=pentium2 $(OPT_UARCH_OOOE_64) # Intel Pentium-2.., AMD Bulldozer.., VIA C3-Nehemiah.., Cyrix 6x86MX.., Transmeta Crusoe.., NSC Geode-GX1.. +generic/686-3dnow := $(___) -march=i686 $(FPU_3DNOW) -mtune=athlon $(OPT_UARCH_OOOE_64) # VIA Cyrix-3-Joshua +generic/686-3dnowa := $(XX_) -march=athlon $(FPU_3DNOWA) -mtune=athlon $(OPT_UARCH_OOOE_64) # AMD Athlon..K10 + +generic/sse := $(___) -march=i686 $(FPU_SSE) -mtune=pentium3 $(OPT_UARCH_OOOE_128) # Intel Pentium-3, AMD Athlon-XP, VIA C3-Nehemiah, DM&P Vortex86DX3.. + +generic/sse2 := $(X__) -march=i686 $(FPU_SSE2) -mtune=generic $(OPT_UARCH_OOOE_128) # Intel Pentium-4.., AMD Athlon-64.., VIA C7-Esther.., Transmeta Efficeon.. +generic/sse3 := $(___) -march=i686 $(FPU_SSE3) -mtune=generic $(OPT_UARCH_OOOE_128) # Intel Core.., AMD Athlon-64-X2.., VIA C7-Esther.., Transmeta Efficeon-88xx.. +generic/ssse3 := $(___) -march=i686 $(FPU_SSSE3) -mtune=generic $(OPT_UARCH_OOOE_128) # Intel Core-2.., AMD Bobcat.., Via Nano-1000.. +generic/sse4_1 := $(___) -march=i686 $(FPU_SSE4_1) -mtune=generic $(OPT_UARCH_OOOE_128) # Intel Core-1st, AMD Bulldozer.., Via Nano-3000.. +generic/sse4_2 := $(___) -march=i686 $(FPU_SSE4_2) -mtune=generic $(OPT_UARCH_OOOE_128) # Intel Core-1st, AMD Bulldozer.., Via Nano-C.. -amd/am386 := $(___) -march=i386 $(FPU_NONE) -mtune=i386 $(OPT_DEF) --param l1-cache-size=0 --param l2-cache-size=$(CACHE_386) -amd/am486sx := $(___) -march=i486 $(FPU_NONE) -mtune=i486 $(OPT_DEF) --param l1-cache-size=6 --param l2-cache-size=$(CACHE_486) +intel/i386 := $(X__) -march=i386 $(FPU_NONE) -mtune=i386 $(OPT_UARCH_CISC) --param l1-cache-size=0 --param l2-cache-size=$(CACHE_386) +intel/i486sx := $(___) -march=i486 $(FPU_NONE) -mtune=i486 $(OPT_UARCH_PIPE) --param l1-cache-size=6 --param l2-cache-size=$(CACHE_486) +intel/i386+80287 := $(___) -march=i386 $(FPU_287) -mtune=i386 $(OPT_UARCH_CISC) --param l1-cache-size=0 --param l2-cache-size=$(CACHE_386) -amd/am386+80387 := $(___) -march=i386 $(FPU_387) -mtune=i386 $(OPT_DEF) --param l1-cache-size=0 --param l2-cache-size=$(CACHE_386) -amd/am486dx := $(___) -march=i486 $(FPU_387) -mtune=i486 $(OPT_DEF) --param l1-cache-size=6 --param l2-cache-size=$(CACHE_486) -amd/am486dxe := $(___) -march=i486 $(FPU_387) -mtune=i486 $(OPT_DEF) --param l1-cache-size=12 --param l2-cache-size=$(CACHE_486) -amd/am5x86 := $(___) -march=i486 $(FPU_387) -mtune=i486 $(OPT_DEF) --param l1-cache-size=12 --param l2-cache-size=$(CACHE_486) -amd/k5 := $(XXX) -march=i586 $(FPU_387) -mtune=i586 $(OPT_DEF) --param l1-cache-size=8 --param l2-cache-size=$(CACHE_S7) -amd/k5-pentium := $(XXX) -march=i586 $(FPU_387) -mtune=pentium $(OPT_DEF) --param l1-cache-size=8 --param l2-cache-size=$(CACHE_S7) -amd/k5-pentiumpro := $(XXX) -march=i586 $(FPU_387) -mtune=pentiumpro $(OPT_DEF) --param l1-cache-size=8 --param l2-cache-size=$(CACHE_S7) -amd/k5-pentium2 := $(XXX) -march=i586 $(FPU_387) -mtune=pentium2 $(OPT_DEF) --param l1-cache-size=8 --param l2-cache-size=$(CACHE_S7) -amd/k5-k6 := $(XXX) -march=i586 $(FPU_387) -mtune=k6 $(OPT_DEF) --param l1-cache-size=8 --param l2-cache-size=$(CACHE_S7) -amd/k6 := $(XXX) -march=k6 $(FPU_MMX) -mtune=k6 $(OPT_SIMD) --param l1-cache-size=32 --param l2-cache-size=$(CACHE_S7) -amd/k6-2 := $(XXX) -march=k6-2 $(FPU_3DNOW) -mtune=k6-2 $(OPT_SIMD) --param l1-cache-size=32 --param l2-cache-size=$(CACHE_SS7) -amd/k6-3 := $(___) -march=k6-3 $(FPU_3DNOW) -mtune=k6-3 $(OPT_SIMD) --param l1-cache-size=32 --param l2-cache-size=$(CACHE_K63) -amd/athlon := $(XX_) -march=athlon $(FPU_3DNOWA) -mtune=athlon $(OPT_SIMD) --param l1-cache-size=64 --param l2-cache-size=$(CACHE_ATHLON) -amd/athlon-xp := $(XX_) -march=athlon-xp $(FPU_3DASSE) -mtune=athlon-xp $(OPT_SIMD) --param l1-cache-size=64 --param l2-cache-size=$(CACHE_ATHLONXP) -amd/athlon64 := $(X__) -march=k8 $(FPU_SSE2) -mtune=k8 $(OPT_SIMD) --param l1-cache-size=64 --param l2-cache-size=$(CACHE_ATHLON64) -amd/athlon64-sse3 := $(___) -march=k8-sse3 $(FPU_SSE3) -mtune=k8-sse3 $(OPT_SIMD) --param l1-cache-size=64 --param l2-cache-size=$(CACHE_ATHLON64) -amd/k10 := $(___) -march=amdfam10 $(FPU_SSE4A) -mtune=amdfam10 $(OPT_SIMD) --param l1-cache-size=64 --param l2-cache-size=512 +intel/i386+80387 := $(X__) -march=i386 $(FPU_387) -mtune=i386 $(OPT_UARCH_CISC) --param l1-cache-size=0 --param l2-cache-size=$(CACHE_386) +intel/i486sx+i487sx := $(___) -march=i486 $(FPU_387) -mtune=i486 $(OPT_UARCH_PIPE) --param l1-cache-size=6 --param l2-cache-size=$(CACHE_486) +intel/i486dx := $(XXX) -march=i486 $(FPU_387) -mtune=i486 $(OPT_UARCH_PIPE) --param l1-cache-size=6 --param l2-cache-size=$(CACHE_486) +intel/pentium := $(XXX) -march=pentium $(FPU_387) -mtune=pentium $(OPT_UARCH_SCAL) --param l1-cache-size=8 --param l2-cache-size=$(CACHE_S7) +intel/pentium-mmx := $(XXX) -march=pentium-mmx $(FPU_MMX) -mtune=pentium-mmx $(OPT_UARCH_SCAL_64) --param l1-cache-size=16 --param l2-cache-size=$(CACHE_S7) +intel/pentium-pro := $(___) -march=pentiumpro $(FPU_387) -mtune=pentiumpro $(OPT_UARCH_OOOE) --param l1-cache-size=8 --param l2-cache-size=$(CACHE_PENTIUMPRO) +intel/pentium2 := $(XX_) -march=pentium2 $(FPU_MMX) -mtune=pentium2 $(OPT_UARCH_OOOE_64) --param l1-cache-size=16 --param l2-cache-size=$(CACHE_PENTIUM2) +intel/pentium3 := $(X__) -march=pentium3 $(FPU_SSE) -mtune=pentium3 $(OPT_UARCH_OOOE_128) --param l1-cache-size=16 --param l2-cache-size=$(CACHE_PENTIUM3) +intel/pentium4 := $(XX_) -march=pentium4 $(FPU_SSE2) -mtune=pentium4 $(OPT_UARCH_OOOE_128) --param l1-cache-size=8 --param l2-cache-size=$(CACHE_PENTIUM4) +intel/pentium4.1 := $(___) -march=prescott $(FPU_SSE3) -mtune=prescott $(OPT_UARCH_OOOE_128) --param l1-cache-size=8 --param l2-cache-size=$(CACHE_PENTIUM41) +intel/core2 := $(___) -march=core2 $(FPU_SSSE3) -mtune=core2 $(OPT_UARCH_OOOE_128) --param l1-cache-size=32 --param l2-cache-size=$(CACHE_CORE2) -amd/duron := $(XX_) -march=athlon $(FPU_3DNOWA) -mtune=athlon $(OPT_SIMD) --param l1-cache-size=64 --param l2-cache-size=$(CACHE_DURON) -amd/duron-xp := $(___) -march=athlon-xp $(FPU_3DASSE) -mtune=athlon-xp $(OPT_SIMD) --param l1-cache-size=64 --param l2-cache-size=$(CACHE_DURONXP) -amd/sempron64 := $(___) -march=k8 $(FPU_SSE2) -mtune=k8 $(OPT_SIMD) --param l1-cache-size=64 --param l2-cache-size=$(CACHE_SEMPRON64) +intel/celeron := $(___) -march=pentium2 $(FPU_MMX) -mtune=pentium2 $(OPT_UARCH_OOOE_64) --param l1-cache-size=16 --param l2-cache-size=$(CACHE_CELERON) +intel/pentium-m := $(___) -march=pentium-m $(FPU_SSE2) -mtune=pentium-m $(OPT_UARCH_OOOE_128) --param l1-cache-size=16 --param l2-cache-size=$(CACHE_PENTIUMM) +intel/core := $(___) -march=pentium-m $(FPU_SSE3) -mtune=core2 $(OPT_UARCH_OOOE_128) --param l1-cache-size=32 --param l2-cache-size=$(CACHE_CORE) +intel/atom := $(___) -march=bonnell $(FPU_SSSE3) -mtune=bonnell $(OPT_UARCH_SCAL_128) --param l1-cache-size=24 --param l2-cache-size=$(CACHE_ATOM) -amd/geode-gx := $(___) -march=geode $(FPU_3DNOW) -mtune=geode $(OPT_SIMD) --param l1-cache-size=16 --param l2-cache-size=0 -amd/geode-lx := $(___) -march=geode $(FPU_3DNOW) -mtune=geode $(OPT_SIMD) --param l1-cache-size=64 --param l2-cache-size=128 -amd/geode-nx := $(___) -march=athlon-xp $(FPU_3DASSE) -mtune=athlon-xp $(OPT_SIMD) --param l1-cache-size=64 --param l2-cache-size=256 -amd/bobcat := $(___) -march=btver1 $(FPU_SSSE4A) -mtune=btver1 $(OPT_SIMD) --param l1-cache-size=32 --param l2-cache-size=512 -amd/jaguar := $(___) -march=btver2 $(FPU_SSSE4A) -mtune=btver2 $(OPT_SIMD) --param l1-cache-size=32 --param l2-cache-size=1024 - -amd/late-3dnow := $(XX_) -march=athlon-xp $(FPU_3DASSE) -mtune=athlon-xp $(OPT_SIMD) --param l1-cache-size=64 --param l2-cache-size=512 -amd/late := $(XX_) -march=i686 $(FPU_SSSE4A) -mtune=generic $(OPT_SIMD) +intel/late := $(XX_) -march=i686 $(FPU_SSSE3) -mtune=intel $(OPT_UARCH_OOOE_128) -nexgen/nx586 := $(___) -march=i486 $(FPU_NONE) $(TUNE_586) $(OPT_DEF) --param l1-cache-size=16 --param l2-cache-size=$(CACHE_486) +amd/am386 := $(___) -march=i386 $(FPU_NONE) -mtune=i386 $(OPT_UARCH_CISC) --param l1-cache-size=0 --param l2-cache-size=$(CACHE_386) +amd/am486sx := $(___) -march=i486 $(FPU_NONE) -mtune=i486 $(OPT_UARCH_PIPE) --param l1-cache-size=6 --param l2-cache-size=$(CACHE_486) -nexgen/nx586pf := $(___) -march=i486 $(FPU_387) $(TUNE_586) $(OPT_DEF) --param l1-cache-size=16 --param l2-cache-size=$(CACHE_486) +amd/am386+80387 := $(___) -march=i386 $(FPU_387) -mtune=i386 $(OPT_UARCH_CISC) --param l1-cache-size=0 --param l2-cache-size=$(CACHE_386) +amd/am486sx+am487sx := $(___) -march=i486 $(FPU_387) -mtune=i486 $(OPT_UARCH_PIPE) --param l1-cache-size=6 --param l2-cache-size=$(CACHE_486) +amd/am486dx := $(XX_) -march=i486 $(FPU_387) -mtune=i486 $(OPT_UARCH_PIPE) --param l1-cache-size=6 --param l2-cache-size=$(CACHE_486) +amd/am486dxe := $(XX_) -march=i486 $(FPU_387) -mtune=i486 $(OPT_UARCH_PIPE) --param l1-cache-size=12 --param l2-cache-size=$(CACHE_486) +amd/am5x86 := $(___) -march=i486 $(FPU_387) -mtune=i486 $(OPT_UARCH_PIPE) --param l1-cache-size=12 --param l2-cache-size=$(CACHE_486) +amd/k5 := $(X__) -march=i586 $(FPU_387) -mtune=i586 $(OPT_UARCH_OOOE) --param l1-cache-size=8 --param l2-cache-size=$(CACHE_S7) +amd/k6 := $(XX_) -march=k6 $(FPU_MMX) -mtune=k6 $(OPT_UARCH_OOOE_64) --param l1-cache-size=32 --param l2-cache-size=$(CACHE_S7) +amd/k6-2 := $(XXX) -march=k6-2 $(FPU_3DNOW) -mtune=k6-2 $(OPT_UARCH_OOOE_64) --param l1-cache-size=32 --param l2-cache-size=$(CACHE_SS7) +amd/k6-3 := $(___) -march=k6-3 $(FPU_3DNOW) -mtune=k6-3 $(OPT_UARCH_OOOE_64) --param l1-cache-size=32 --param l2-cache-size=256 +amd/k6-2+ := $(___) -march=k6-3 $(FPU_3DNOW) -mtune=k6-3 $(OPT_UARCH_OOOE_64) --param l1-cache-size=32 --param l2-cache-size=128 +amd/k6-3+ := $(___) -march=k6-3 $(FPU_3DNOW) -mtune=k6-3 $(OPT_UARCH_OOOE_64) --param l1-cache-size=32 --param l2-cache-size=256 +amd/athlon := $(XX_) -march=athlon $(FPU_3DNOWA) -mtune=athlon $(OPT_UARCH_OOOE_64) --param l1-cache-size=64 --param l2-cache-size=$(CACHE_ATHLON) +amd/athlon-xp := $(XXX) -march=athlon-xp $(FPU_3DSSE) -mtune=athlon-xp $(OPT_UARCH_OOOE_128) --param l1-cache-size=64 --param l2-cache-size=$(CACHE_ATHLONXP) +amd/athlon64 := $(X__) -march=k8 $(FPU_3DSSE2) -mtune=k8 $(OPT_UARCH_OOOE_128) --param l1-cache-size=64 --param l2-cache-size=$(CACHE_ATHLON64) +amd/athlon64-sse3 := $(___) -march=k8-sse3 $(FPU_3DSSE3) -mtune=k8-sse3 $(OPT_UARCH_OOOE_128) --param l1-cache-size=64 --param l2-cache-size=$(CACHE_ATHLON64) +amd/k10 := $(___) -march=amdfam10 $(FPU_3DSSE4) -mtune=amdfam10 $(OPT_UARCH_OOOE_128) --param l1-cache-size=64 --param l2-cache-size=512 + +amd/duron := $(X__) -march=athlon $(FPU_3DNOWA) -mtune=athlon $(OPT_UARCH_OOOE_64) --param l1-cache-size=64 --param l2-cache-size=$(CACHE_DURON) +amd/duron-xp := $(___) -march=athlon-xp $(FPU_3DSSE) -mtune=athlon-xp $(OPT_UARCH_OOOE_128) --param l1-cache-size=64 --param l2-cache-size=$(CACHE_DURONXP) +amd/sempron64 := $(___) -march=k8 $(FPU_3DSSE2) -mtune=k8 $(OPT_UARCH_OOOE_128) --param l1-cache-size=64 --param l2-cache-size=$(CACHE_SEMPRON64) + +amd/geode-gx := $(___) -march=geode $(FPU_3DNOWA) -mtune=geode $(OPT_UARCH_OOOE_64) --param l1-cache-size=16 --param l2-cache-size=0 +amd/geode-lx := $(___) -march=geode $(FPU_3DNOWA) -mtune=geode $(OPT_UARCH_OOOE_64) --param l1-cache-size=64 --param l2-cache-size=128 +amd/geode-nx := $(___) -march=athlon-xp $(FPU_3DSSE) -mtune=athlon-xp $(OPT_UARCH_OOOE_128) --param l1-cache-size=64 --param l2-cache-size=256 +amd/bobcat := $(X__) -march=btver1 $(FPU_SSSE4A) -mtune=btver1 $(OPT_UARCH_OOOE_128) --param l1-cache-size=32 --param l2-cache-size=512 +amd/jaguar := $(___) -march=btver2 $(FPU_SSSE4A) -mtune=btver2 $(OPT_UARCH_OOOE_128) --param l1-cache-size=32 --param l2-cache-size=1024 + +amd/late := $(XX_) -march=i686 $(FPU_SSSE4A) -mtune=generic $(OPT_UARCH_OOOE_128) -ibm/386slc := $(___) -march=i386 $(FPU_NONE) -mtune=i386 $(OPT_DEF) --param l1-cache-size=6 --param l2-cache-size=$(CACHE_386) -ibm/486slc := $(___) -march=i486 $(FPU_NONE) -mtune=i386 $(OPT_DEF) --param l1-cache-size=12 --param l2-cache-size=$(CACHE_386) -ibm/486bl := $(___) -march=i486 $(FPU_NONE) -mtune=i386 $(OPT_DEF) --param l1-cache-size=12 --param l2-cache-size=$(CACHE_486) +ct/38600 := $(___) -march=i386 $(FPU_NONE) -mtune=i486 $(OPT_UARCH_PIPE) --param l1-cache-size=0 --param l2-cache-size=$(CACHE_386) +ct/38605 := $(___) -march=i386 $(FPU_NONE) -mtune=i486 $(OPT_UARCH_PIPE) --param l1-cache-size=1 --param l2-cache-size=$(CACHE_386) -cyrix/cx486slc := $(___) -march=i486 $(FPU_NONE) -mtune=i486 $(OPT_DEF) --param l1-cache-size=1 --param l2-cache-size=$(CACHE_386) -cyrix/cx486dlc := $(___) -march=i486 $(FPU_NONE) -mtune=i486 $(OPT_DEF) --param l1-cache-size=1 --param l2-cache-size=$(CACHE_386) -cyrix/cx4x86s := $(___) -march=i486 $(FPU_NONE) -mtune=i486 $(OPT_DEF) --param l1-cache-size=2 --param l2-cache-size=$(CACHE_486) +nexgen/nx586 := $(___) -march=i486 $(FPU_NONE) $(TUNE_586) $(OPT_UARCH_OOOE) --param l1-cache-size=16 --param l2-cache-size=$(CACHE_486) -cyrix/cx4x86dx := $(___) -march=i486 $(FPU_387) -mtune=i486 $(OPT_DEF) --param l1-cache-size=6 --param l2-cache-size=$(CACHE_486) -cyrix/cx5x86 := $(___) -march=i486 $(FPU_387) -mtune=i486 $(OPT_DEF) --param l1-cache-size=12 --param l2-cache-size=$(CACHE_486) -cyrix/6x86 := $(XXX) -march=i486 $(FPU_387) $(TUNE_586) $(OPT_DEF) --param l1-cache-size=12 --param l2-cache-size=$(CACHE_S7) -cyrix/6x86l := $(___) -march=i486 $(FPU_387) $(TUNE_586) $(OPT_DEF) --param l1-cache-size=12 --param l2-cache-size=$(CACHE_S7) -cyrix/6x86mx := $(___) -march=i686 $(FPU_MMX) $(TUNE_686MMX) $(OPT_SIMD) --param l1-cache-size=48 --param l2-cache-size=$(CACHE_SS7) - -cyrix/mediagx-gx := $(___) -march=i486 $(FPU_387) -mtune=i486 $(OPT_DEF) --param l1-cache-size=9 --param l2-cache-size=0 -cyrix/mediagx-gxm := $(___) -march=i686 $(FPU_MMX) $(TUNE_686MMX) $(OPT_SIMD) --param l1-cache-size=9 --param l2-cache-size=0 +nexgen/nx586pf := $(___) -march=i486 $(FPU_387) $(TUNE_586) $(OPT_UARCH_OOOE) --param l1-cache-size=16 --param l2-cache-size=$(CACHE_486) -nsc/geode-gx1 := $(___) -march=i686 $(FPU_MMX) $(TUNE_686MMX) $(OPT_SIMD) --param l1-cache-size=9 --param l2-cache-size=0 -nsc/geode-gx2 := $(___) -march=geode $(FPU_3DNOW) -mtune=geode $(OPT_SIMD) --param l1-cache-size=16 --param l2-cache-size=0 +ibm/386slc := $(___) -march=i386 $(FPU_NONE) -mtune=i386 $(OPT_UARCH_CISC) --param l1-cache-size=6 --param l2-cache-size=$(CACHE_386) +ibm/486slc := $(___) -march=i486 $(FPU_NONE) -mtune=i386 $(OPT_UARCH_CISC) --param l1-cache-size=12 --param l2-cache-size=$(CACHE_386) +ibm/486bl := $(___) -march=i486 $(FPU_NONE) -mtune=i386 $(OPT_UARCH_CISC) --param l1-cache-size=12 --param l2-cache-size=$(CACHE_486) + +ibm/386slc+fasmath := $(___) -march=i386 $(FPU_387) -mtune=i386 $(OPT_UARCH_CISC) --param l1-cache-size=6 --param l2-cache-size=$(CACHE_386) +ibm/486slc+fasmath := $(___) -march=i486 $(FPU_387) -mtune=i386 $(OPT_UARCH_CISC) --param l1-cache-size=12 --param l2-cache-size=$(CACHE_386) +ibm/486bl+fasmath := $(___) -march=i486 $(FPU_387) -mtune=i386 $(OPT_UARCH_CISC) --param l1-cache-size=12 --param l2-cache-size=$(CACHE_486) -idt/winchip-c6 := $(XX_) -march=winchip-c6 $(FPU_MMX) -mtune=winchip-c6 $(OPT_SIMD) --param l1-cache-size=32 --param l2-cache-size=$(CACHE_S7) -idt/winchip2 := $(XX_) -march=winchip2 $(FPU_3DNOW) -mtune=winchip2 $(OPT_SIMD) --param l1-cache-size=32 --param l2-cache-size=$(CACHE_SS7) +cyrix/cx486slc := $(___) -march=i486 $(FPU_NONE) -mtune=i486 $(OPT_UARCH_PIPE) --param l1-cache-size=1 --param l2-cache-size=$(CACHE_386) +cyrix/cx486dlc := $(___) -march=i486 $(FPU_NONE) -mtune=i486 $(OPT_UARCH_PIPE) --param l1-cache-size=1 --param l2-cache-size=$(CACHE_386) +cyrix/cx4x86s := $(___) -march=i486 $(FPU_NONE) -mtune=i486 $(OPT_UARCH_PIPE) --param l1-cache-size=2 --param l2-cache-size=$(CACHE_486) + +cyrix/cx486slc+80387 := $(___) -march=i486 $(FPU_387) -mtune=i486 $(OPT_UARCH_PIPE) --param l1-cache-size=1 --param l2-cache-size=$(CACHE_386) +cyrix/cx486dlc+80387 := $(___) -march=i486 $(FPU_387) -mtune=i486 $(OPT_UARCH_PIPE) --param l1-cache-size=1 --param l2-cache-size=$(CACHE_386) +cyrix/cx4x86s+cx487s := $(___) -march=i486 $(FPU_387) -mtune=i486 $(OPT_UARCH_PIPE) --param l1-cache-size=2 --param l2-cache-size=$(CACHE_486) +cyrix/cx4x86dx := $(___) -march=i486 $(FPU_387) -mtune=i486 $(OPT_UARCH_PIPE) --param l1-cache-size=6 --param l2-cache-size=$(CACHE_486) +cyrix/cx5x86 := $(___) -march=i486 $(FPU_387) -mtune=i486 $(OPT_UARCH_SCAL) --param l1-cache-size=12 --param l2-cache-size=$(CACHE_486) +cyrix/6x86 := $(XXX) -march=i486 $(FPU_387) $(TUNE_586) $(OPT_UARCH_OOOE) --param l1-cache-size=12 --param l2-cache-size=$(CACHE_S7) +cyrix/6x86l := $(___) -march=i486 $(FPU_387) $(TUNE_586) $(OPT_UARCH_OOOE) --param l1-cache-size=12 --param l2-cache-size=$(CACHE_S7) +cyrix/6x86mx := $(XX_) -march=i686 $(FPU_MMX) $(TUNE_686MMX) $(OPT_UARCH_OOOE_64) --param l1-cache-size=48 --param l2-cache-size=$(CACHE_SS7) + +cyrix/mediagx-gx := $(___) -march=i486 $(FPU_387) -mtune=i486 $(OPT_UARCH_SCAL) --param l1-cache-size=9 --param l2-cache-size=0 +cyrix/mediagx-gxm := $(___) -march=i686 $(FPU_MMX) $(TUNE_686MMX) $(OPT_UARCH_SCAL_64) --param l1-cache-size=9 --param l2-cache-size=0 -via/cyrix3-joshua := $(XX_) -march=i686 $(FPU_3DNOW) $(TUNE_686MMX) $(OPT_SIMD) --param l1-cache-size=48 --param l2-cache-size=256 -via/cyrix3-samuel := $(___) -march=c3 $(FPU_3DNOW) -mtune=c3 $(OPT_SIMD) --param l1-cache-size=64 --param l2-cache-size=0 -via/c3-samuel2 := $(___) -march=samuel-2 $(FPU_3DNOW) -mtune=samuel-2 $(OPT_SIMD) --param l1-cache-size=64 --param l2-cache-size=64 -via/c3-ezra := $(___) -march=samuel-2 $(FPU_3DNOW) -mtune=samuel-2 $(OPT_SIMD) --param l1-cache-size=64 --param l2-cache-size=64 -via/c3-nehemiah := $(XX_) -march=nehemiah $(FPU_SSE) -mtune=nehemiah $(OPT_SIMD) --param l1-cache-size=64 --param l2-cache-size=64 -via/c7-esther := $(XX_) -march=esther $(FPU_SSE3) -mtune=esther $(OPT_SIMD) --param l1-cache-size=64 --param l2-cache-size=128 - -via/late := $(XX_) -march=i686 $(FPU_SSE3) -mtune=esther $(OPT_SIMD) +nsc/geode-gx1 := $(___) -march=i686 $(FPU_MMX) $(TUNE_686MMX) $(OPT_UARCH_SCAL_64) --param l1-cache-size=9 --param l2-cache-size=0 +nsc/geode-gx2 := $(___) -march=geode $(FPU_3DNOWA) -mtune=geode $(OPT_UARCH_OOOE_64) --param l1-cache-size=16 --param l2-cache-size=0 -umc/u5s := $(___) -march=i486 $(FPU_NONE) -mtune=i486 $(OPT_DEF) --param l1-cache-size=6 --param l2-cache-size=$(CACHE_486) -umc/u5d := $(___) -march=i486 $(FPU_387) -mtune=i486 $(OPT_DEF) --param l1-cache-size=6 --param l2-cache-size=$(CACHE_486) +idt/winchip-c6 := $(X__) -march=i586 $(FPU_MMX) -mtune=winchip-c6 $(OPT_UARCH_PIPE_64) --param l1-cache-size=32 --param l2-cache-size=$(CACHE_S7) +idt/winchip2 := $(X__) -march=i586 $(FPU_3DNOW) -mtune=winchip2 $(OPT_UARCH_SCAL_64) --param l1-cache-size=32 --param l2-cache-size=$(CACHE_SS7) -transmeta/crusoe := $(___) -march=i686 $(FPU_MMX) $(TUNE_686MMX) $(OPT_SIMD) --param l1-cache-size=64 --param l2-cache-size=256 -transmeta/efficeon:= $(___) -march=i686 $(FPU_SSE2) $(TUNE_686SSE2) $(OPT_SIMD) --param l1-cache-size=64 --param l2-cache-size=1024 -transmeta/tm8800 := $(___) -march=i686 $(FPU_SSE3) $(TUNE_686SSE3) $(OPT_SIMD) --param l1-cache-size=64 --param l2-cache-size=1024 +via/cyrix3-joshua := $(___) -march=i686 $(FPU_3DNOW) $(TUNE_6863DN) $(OPT_UARCH_OOOE_64) --param l1-cache-size=48 --param l2-cache-size=256 +via/c3-samuel := $(___) -march=c3 $(FPU_3DNOW) -mtune=c3 $(OPT_UARCH_SCAL_64) --param l1-cache-size=64 --param l2-cache-size=0 +via/c3-samuel2 := $(___) -march=samuel-2 $(FPU_3DNOW) -mtune=samuel-2 $(OPT_UARCH_SCAL_64) --param l1-cache-size=64 --param l2-cache-size=64 +via/c3-ezra := $(___) -march=samuel-2 $(FPU_3DNOW) -mtune=samuel-2 $(OPT_UARCH_SCAL_64) --param l1-cache-size=64 --param l2-cache-size=64 +via/c3-nehemiah := $(___) -march=nehemiah $(FPU_SSE) -mtune=nehemiah $(OPT_UARCH_SCAL_128) --param l1-cache-size=64 --param l2-cache-size=64 +via/c7-esther := $(XX_) -march=esther $(FPU_SSE3) -mtune=esther $(OPT_UARCH_SCAL_128) --param l1-cache-size=64 --param l2-cache-size=128 +via/eden-x2 := $(___) -march=eden-x2 $(FPU_SSE3) -mtune=eden-x2 $(OPT_UARCH_SCAL_128) --param l1-cache-size=64 --param l2-cache-size=64 +via/nano := $(___) -march=nano $(FPU_SSSE3) -mtune=nano $(OPT_UARCH_SCAL_128) --param l1-cache-size=64 --param l2-cache-size=1024 +via/nano-1000 := $(___) -march=nano-1000 $(FPU_SSSE3) -mtune=nano-1000 $(OPT_UARCH_SCAL_128) --param l1-cache-size=64 --param l2-cache-size=1024 +via/nano-2000 := $(___) -march=nano-2000 $(FPU_SSSE3) -mtune=nano-2000 $(OPT_UARCH_SCAL_128) --param l1-cache-size=64 --param l2-cache-size=1024 +via/nano-3000 := $(___) -march=nano-3000 $(FPU_SSE4_1) -mtune=nano-3000 $(OPT_UARCH_SCAL_128) --param l1-cache-size=64 --param l2-cache-size=1024 +via/nano-4000 := $(___) -march=nano-4000 $(FPU_SSE4_1) -mtune=nano-4000 $(OPT_UARCH_SCAL_128) --param l1-cache-size=64 --param l2-cache-size=1024 +via/nano-x2 := $(___) -march=nano-x2 $(FPU_SSE4_1) -mtune=nano-x2 $(OPT_UARCH_SCAL_128) --param l1-cache-size=64 --param l2-cache-size=1024 +via/nano-x4 := $(___) -march=nano-x4 $(FPU_SSE4_1) -mtune=nano-x4 $(OPT_UARCH_SCAL_128) --param l1-cache-size=64 --param l2-cache-size=1024 +via/eden-x4 := $(___) -march=eden-x4 $(FPU_SSE4_2) -mtune=eden-x4 $(OPT_UARCH_SCAL_128) --param l1-cache-size=64 --param l2-cache-size=2048 + +via/late := $(XX_) -march=eden-x4 $(FPU_SSE4_2) -mtune=eden-x4 $(OPT_UARCH_SCAL_128) -uli/m6117c := $(___) -march=i386 $(FPU_NONE) -mtune=i386 $(OPT_DEF) --param l1-cache-size=0 --param l2-cache-size=$(CACHE_386) +umc/u5s := $(___) -march=i486 $(FPU_NONE) -mtune=i486 $(OPT_UARCH_PIPE) --param l1-cache-size=6 --param l2-cache-size=$(CACHE_486) +umc/u5d := $(___) -march=i486 $(FPU_387) -mtune=i486 $(OPT_UARCH_PIPE) --param l1-cache-size=6 --param l2-cache-size=$(CACHE_486) -rise/mp6 := $(XX_) -march=i586 $(FPU_MMX) $(TUNE_586MMX) $(OPT_SIMD) --param l1-cache-size=8 --param l2-cache-size=$(CACHE_SS7) +transmeta/crusoe := $(X__) -march=i686 $(FPU_MMX) $(TUNE_686MMX) $(OPT_UARCH_COMP) --param l1-cache-size=64 --param l2-cache-size=256 +transmeta/efficeon := $(___) -march=i686 $(FPU_SSE2) $(TUNE_686SSE2) $(OPT_UARCH_COMP) --param l1-cache-size=64 --param l2-cache-size=1024 +transmeta/tm8800 := $(___) -march=i686 $(FPU_SSE3) $(TUNE_686SSE3) $(OPT_UARCH_COMP) --param l1-cache-size=64 --param l2-cache-size=1024 -sis/55x := $(___) -march=i586 $(FPU_MMX) $(TUNE_586MMX) $(OPT_SIMD) --param l1-cache-size=8 --param l2-cache-size=0 +uli/m6117c := $(___) -march=i386 $(FPU_NONE) -mtune=i386 $(OPT_UARCH_CISC) --param l1-cache-size=0 --param l2-cache-size=$(CACHE_386) -dmnp/m6117d := $(___) -march=i386 $(FPU_NONE) -mtune=i386 $(OPT_DEF) --param l1-cache-size=0 --param l2-cache-size=$(CACHE_386) -dmnp/vortex86sx := $(___) -march=i386 $(FPU_NONE) -mtune=i386 $(OPT_DEF) --param l1-cache-size=16 --param l2-cache-size=0 +rise/mp6 := $(X__) -march=i586 $(FPU_MMX) $(TUNE_586MMX) $(OPT_UARCH_SCAL_64) --param l1-cache-size=8 --param l2-cache-size=$(CACHE_SS7) -dmnp/vortex86dx := $(___) -march=i486 $(FPU_387) -mtune=i486 $(OPT_DEF) --param l1-cache-size=16 --param l2-cache-size=256 -dmnp/vortex86mx := $(___) -march=i486 $(FPU_387) -mtune=i486 $(OPT_DEF) --param l1-cache-size=16 --param l2-cache-size=256 -dmnp/vortex86 := $(___) -march=i586 $(FPU_MMX) $(TUNE_586MMX) $(OPT_SIMD) --param l1-cache-size=8 --param l2-cache-size=0 -dmnp/vortex86dx2 := $(___) -march=i586 $(FPU_MMX) $(TUNE_586MMX) $(OPT_SIMD) --param l1-cache-size=16 --param l2-cache-size=256 -dmnp/vortex86dx3 := $(___) -march=i686 $(FPU_SSE) $(TUNE_686SSE) $(OPT_SIMD) --param l1-cache-size=32 --param l2-cache-size=512 + + +sis/55x := $(___) -march=i586 $(FPU_MMX) $(TUNE_586MMX) $(OPT_UARCH_SCAL_64) --param l1-cache-size=8 --param l2-cache-size=0 + + + +dmnp/m6117d := $(___) -march=i386 $(FPU_NONE) -mtune=i386 $(OPT_UARCH_CISC) --param l1-cache-size=0 --param l2-cache-size=$(CACHE_386) +dmnp/vortex86 := $(___) -march=i586 $(FPU_MMX) $(TUNE_586MMX) $(OPT_UARCH_SCAL_64) --param l1-cache-size=8 --param l2-cache-size=0 +dmnp/vortex86sx := $(___) -march=i586 $(FPU_NONE) $(TUNE_586) $(OPT_UARCH_SCAL) --param l1-cache-size=16 --param l2-cache-size=0 +dmnp/vortex86dx := $(___) -march=i586 $(FPU_387) $(TUNE_586) $(OPT_UARCH_SCAL) --param l1-cache-size=16 --param l2-cache-size=256 +dmnp/vortex86mx := $(___) -march=i586 $(FPU_387) $(TUNE_586) $(OPT_UARCH_SCAL) --param l1-cache-size=16 --param l2-cache-size=256 +dmnp/vortex86mxp := $(___) -march=i586 $(FPU_387) $(TUNE_586) $(OPT_UARCH_SCAL) --param l1-cache-size=16 --param l2-cache-size=256 +dmnp/vortex86dx2 := $(___) -march=i586 $(FPU_387) $(TUNE_586) $(OPT_UARCH_SCAL) --param l1-cache-size=16 --param l2-cache-size=256 +dmnp/vortex86ex := $(___) -march=i586 $(FPU_387) $(TUNE_586) $(OPT_UARCH_SCAL) --param l1-cache-size=16 --param l2-cache-size=128 +dmnp/vortex86dx3 := $(___) -march=i686 $(FPU_SSE) $(TUNE_686SSE) $(OPT_UARCH_SCAL_128) --param l1-cache-size=32 --param l2-cache-size=256 +dmnp/vortex86ex2 := $(___) -march=i686 $(FPU_SSE) $(TUNE_686SSE) $(OPT_UARCH_SCAL_128) --param l1-cache-size=32 --param l2-cache-size=128 @@ -301,12 +408,25 @@ CPUFLAGS := $($(CPU)) # parse CPU optimization options ifeq ($(findstring -O3,$(CPUFLAGS)),-O3) -OPTIMIZE=vectorize -CPUFLAGS := $(filter-out -O3,$(CPUFLAGS)) +MPT_COMPILER_NO_O=1 +endif +ifeq ($(findstring -O2,$(CPUFLAGS)),-O2) +MPT_COMPILER_NO_O=1 endif ifeq ($(findstring -Os,$(CPUFLAGS)),-Os) -OPTIMIZE=size -CPUFLAGS := $(filter-out -Os,$(CPUFLAGS)) +MPT_COMPILER_NO_O=1 +endif +ifeq ($(findstring -Oz,$(CPUFLAGS)),-Oz) +MPT_COMPILER_NO_O=1 +endif +ifeq ($(findstring -O1,$(CPUFLAGS)),-O1) +MPT_COMPILER_NO_O=1 +endif +ifeq ($(findstring -O0,$(CPUFLAGS)),-O0) +MPT_COMPILER_NO_O=1 +endif +ifeq ($(findstring -Og,$(CPUFLAGS)),-Og) +MPT_COMPILER_NO_O=1 endif # Handle the no-FPU case by linking DJGPP's own emulator. @@ -328,6 +448,7 @@ endif ifeq ($(FLAVOURED_DIR),1) EXESUFFIX=.exe +SOSUFFIX=.dxe ifeq ($(findstring -msse,$(CPUFLAGS)),-msse) FLAVOUR_DIR=$(CPU)-sse/ FLAVOUR_O=.$(subst /,-,$(CPU)-sse) @@ -341,19 +462,32 @@ else ifeq ($(FLAVOURED_EXE),1) ifeq ($(CPU),generic/common) EXESUFFIX=.exe +SOSUFFIX=.dxe else EXESUFFIX:=.exe +SOSUFFIX=.dxe ifeq ($(findstring -msse,$(CPUFLAGS)),-msse) EXESUFFIX:=-SSE$(EXESUFFIX) +SOSUFFIX:=-SSE$(SOSUFFIX) endif -ifeq ($(OPTIMIZE),size) +ifeq ($(OPTIMIZE),some) +EXESUFFIX:=-O1$(EXESUFFIX) +SOSUFFIX:=-O1$(SOSUFFIX) +else ifeq ($(OPTIMIZE),extrasize) +EXESUFFIX:=-Oz$(EXESUFFIX) +SOSUFFIX:=-Oz$(SOSUFFIX) +else ifeq ($(OPTIMIZE),size) EXESUFFIX:=-Os$(EXESUFFIX) +SOSUFFIX:=-Os$(SOSUFFIX) else ifeq ($(OPTIMIZE),speed) EXESUFFIX:=-O2$(EXESUFFIX) +SOSUFFIX:=-O2$(SOSUFFIX) else ifeq ($(OPTIMIZE),vectorize) EXESUFFIX:=-O3$(EXESUFFIX) +SOSUFFIX:=-O3$(SOSUFFIX) endif EXESUFFIX:=-$(subst /,-,$(CPU))$(EXESUFFIX) +SOSUFFIX:=-$(subst /,-,$(CPU))$(SOSUFFIX) endif ifeq ($(findstring -msse,$(CPUFLAGS)),-msse) FLAVOUR_O=.$(subst /,-,$(CPU)-sse) @@ -364,12 +498,13 @@ endif else EXESUFFIX=.exe +SOSUFFIX=.dxe FLAVOUR_DIR= FLAVOUR_O= endif -CPPFLAGS += +CPPFLAGS += CXXFLAGS += $(CPU_CFLAGS) CFLAGS += $(CPU_CFLAGS) LDFLAGS += $(CPU_LDFLAGS) @@ -383,6 +518,7 @@ MPT_COMPILER_NOIPARA=1 include build/make/warnings-gcc.mk +ALLOW_LGPL=1 DYNLINK=0 SHARED_LIB=0 STATIC_LIB=1 @@ -396,10 +532,15 @@ IS_CROSS=1 MPT_COMPILER_NOVISIBILITY=1 # causes crashes on process shutdown with liballegro +MPT_COMPILER_NOSECTIONS=1 MPT_COMPILER_NOGCSECTIONS=1 MPT_COMPILER_NOALLOCAH=1 +NO_SHARED_LINKER_FLAG=1 + +ENABLE_DXE=1 + ifeq ($(OPTIMIZE_LTO),1) CXXFLAGS += -flto=auto -Wno-attributes CFLAGS += -flto=auto -Wno-attributes diff --git a/Frameworks/OpenMPT/OpenMPT/build/make/config-emscripten.mk b/Frameworks/OpenMPT/OpenMPT/build/make/config-emscripten.mk index 6f04c7522..775ae3ef4 100644 --- a/Frameworks/OpenMPT/OpenMPT/build/make/config-emscripten.mk +++ b/Frameworks/OpenMPT/OpenMPT/build/make/config-emscripten.mk @@ -19,6 +19,8 @@ EMSCRIPTEN_PORTS?=0 ifneq ($(STDCXX),) CXXFLAGS_STDCXX = -std=$(STDCXX) +else ifeq ($(shell printf '\n' > bin/empty.cpp ; if $(CXX) -std=c++23 -c bin/empty.cpp -o bin/empty.out > /dev/null 2>&1 ; then echo 'c++23' ; fi ), c++23) +CXXFLAGS_STDCXX = -std=c++23 else ifeq ($(shell printf '\n' > bin/empty.cpp ; if $(CXX) -std=c++20 -c bin/empty.cpp -o bin/empty.out > /dev/null 2>&1 ; then echo 'c++20' ; fi ), c++20) CXXFLAGS_STDCXX = -std=c++20 else @@ -26,6 +28,10 @@ CXXFLAGS_STDCXX = -std=c++17 endif ifneq ($(STDC),) CFLAGS_STDC = -std=$(STDC) +else ifeq ($(shell printf '\n' > bin/empty.c ; if $(CC) -std=c23 -c bin/empty.c -o bin/empty.out > /dev/null 2>&1 ; then echo 'c23' ; fi ), c23) +CFLAGS_STDC = -std=c23 +else ifeq ($(shell printf '\n' > bin/empty.c ; if $(CC) -std=c18 -c bin/empty.c -o bin/empty.out > /dev/null 2>&1 ; then echo 'c18' ; fi ), c18) +CFLAGS_STDC = -std=c18 else ifeq ($(shell printf '\n' > bin/empty.c ; if $(CC) -std=c17 -c bin/empty.c -o bin/empty.out > /dev/null 2>&1 ; then echo 'c17' ; fi ), c17) CFLAGS_STDC = -std=c17 else @@ -48,9 +54,15 @@ LDFLAGS += -pthread endif ifeq ($(EMSCRIPTEN_PORTS),1) +ifeq ($(ANCIENT),1) CXXFLAGS += -s USE_ZLIB=1 -sUSE_MPG123=1 -sUSE_OGG=1 -sUSE_VORBIS=1 -DMPT_WITH_ZLIB -DMPT_WITH_MPG123 -DMPT_WITH_VORBIS -DMPT_WITH_VORBISFILE -DMPT_WITH_OGG CFLAGS += -s USE_ZLIB=1 -sUSE_MPG123=1 -sUSE_OGG=1 -sUSE_VORBIS=1 -DMPT_WITH_ZLIB -DMPT_WITH_MPG123 -DMPT_WITH_VORBIS -DMPT_WITH_VORBISFILE -DMPT_WITH_OGG LDFLAGS += -s USE_ZLIB=1 -sUSE_MPG123=1 -sUSE_OGG=1 -sUSE_VORBIS=1 +else +CXXFLAGS += --use-port=zlib --use-port=mpg123 --use-port=vorbis --use-port=ogg -DMPT_WITH_ZLIB -DMPT_WITH_MPG123 -DMPT_WITH_VORBIS -DMPT_WITH_VORBISFILE -DMPT_WITH_OGG +CFLAGS += --use-port=zlib --use-port=mpg123 --use-port=vorbis --use-port=ogg -DMPT_WITH_ZLIB -DMPT_WITH_MPG123 -DMPT_WITH_VORBIS -DMPT_WITH_VORBISFILE -DMPT_WITH_OGG +LDFLAGS += --use-port=zlib --use-port=mpg123 --use-port=vorbis --use-port=ogg +endif NO_MINIZ=1 NO_MINIMP3=1 NO_STBVORBIS=1 @@ -70,15 +82,9 @@ CXXFLAGS += -flto CFLAGS += -flto LDFLAGS += -flto -# Work-around . -# The warning with emscripten 3.1.50 sounds very dangerous, -# and since it is apparently caused by removing whitespace, -# additional whitespace is a small price to pay for correctness. -LDFLAGS += -g1 - ifeq ($(EMSCRIPTEN_TARGET),default) -# emits whatever is emscripten's default, currently (1.38.8) this is the same as "wasm" below. -CPPFLAGS += -DMPT_BUILD_WASM +# emits whatever is emscripten's default, currently (13.1.51) this is the same as "wasm" below. +CPPFLAGS += CXXFLAGS += CFLAGS += LDFLAGS += @@ -87,10 +93,10 @@ LDFLAGS += -s ALLOW_MEMORY_GROWTH=1 else ifeq ($(EMSCRIPTEN_TARGET),all) # emits native wasm AND javascript with full wasm optimizations. -CPPFLAGS += -DMPT_BUILD_WASM +CPPFLAGS += CXXFLAGS += CFLAGS += -LDFLAGS += -s WASM=2 -s LEGACY_VM_SUPPORT=1 -Wno-transpile +LDFLAGS += -s WASM=2 -s LEGACY_VM_SUPPORT=1 # work-around . CXXFLAGS += -fno-inline-functions @@ -101,7 +107,7 @@ LDFLAGS += -s ALLOW_MEMORY_GROWTH=1 else ifeq ($(EMSCRIPTEN_TARGET),audioworkletprocessor) # emits an es6 module in a single file suitable for use in an AudioWorkletProcessor -CPPFLAGS += -DMPT_BUILD_WASM -DMPT_BUILD_AUDIOWORKLETPROCESSOR +CPPFLAGS += -DMPT_BUILD_AUDIOWORKLETPROCESSOR CXXFLAGS += CFLAGS += LDFLAGS += -s WASM=1 -s WASM_ASYNC_COMPILATION=0 -s MODULARIZE=1 -s EXPORT_ES6=1 -s SINGLE_FILE=1 @@ -110,7 +116,7 @@ LDFLAGS += -s ALLOW_MEMORY_GROWTH=1 else ifeq ($(EMSCRIPTEN_TARGET),wasm) # emits native wasm. -CPPFLAGS += -DMPT_BUILD_WASM +CPPFLAGS += CXXFLAGS += CFLAGS += LDFLAGS += -s WASM=1 @@ -119,10 +125,10 @@ LDFLAGS += -s ALLOW_MEMORY_GROWTH=1 else ifeq ($(EMSCRIPTEN_TARGET),js) # emits only plain javascript with plain javascript focused optimizations. -CPPFLAGS += -DMPT_BUILD_ASMJS +CPPFLAGS += CXXFLAGS += CFLAGS += -LDFLAGS += -s WASM=0 -s LEGACY_VM_SUPPORT=1 -Wno-transpile +LDFLAGS += -s WASM=0 -s LEGACY_VM_SUPPORT=1 # work-around . CXXFLAGS += -fno-inline-functions @@ -138,6 +144,8 @@ CFLAGS += -s DISABLE_EXCEPTION_CATCHING=0 -fno-strict-aliasing LDFLAGS += -s DISABLE_EXCEPTION_CATCHING=0 -s ERROR_ON_UNDEFINED_SYMBOLS=1 -s ERROR_ON_MISSING_LIBRARIES=1 -s EXPORT_NAME="'libopenmpt'" SO_LDFLAGS += -s EXPORTED_FUNCTIONS="['_malloc','_free']" +NO_NO_UNDEFINED_LINKER_FLAG=1 + include build/make/warnings-clang.mk REQUIRES_RUNPREFIX=1 @@ -145,7 +153,7 @@ REQUIRES_RUNPREFIX=1 EXESUFFIX=.js SOSUFFIX=.js RUNPREFIX=node -TEST_LDFLAGS= --pre-js build/make/test-pre.js -lnodefs.js +TEST_LDFLAGS= -lnodefs.js ifeq ($(EMSCRIPTEN_THREADS),1) RUNPREFIX+=--experimental-wasm-threads --experimental-wasm-bulk-memory diff --git a/Frameworks/OpenMPT/OpenMPT/build/make/config-gcc.mk b/Frameworks/OpenMPT/OpenMPT/build/make/config-gcc.mk index 8130a6fdd..34be9ffa2 100644 --- a/Frameworks/OpenMPT/OpenMPT/build/make/config-gcc.mk +++ b/Frameworks/OpenMPT/OpenMPT/build/make/config-gcc.mk @@ -14,6 +14,8 @@ endif ifneq ($(STDCXX),) CXXFLAGS_STDCXX = -std=$(STDCXX) -fexceptions -frtti -pthread +else ifeq ($(shell printf '\n' > bin/empty.cpp ; if $(CXX) -std=c++23 -c bin/empty.cpp -o bin/empty.out > /dev/null 2>&1 ; then echo 'c++23' ; fi ), c++23) +CXXFLAGS_STDCXX = -std=c++23 -fexceptions -frtti -pthread else ifeq ($(shell printf '\n' > bin/empty.cpp ; if $(CXX) -std=c++20 -c bin/empty.cpp -o bin/empty.out > /dev/null 2>&1 ; then echo 'c++20' ; fi ), c++20) CXXFLAGS_STDCXX = -std=c++20 -fexceptions -frtti -pthread else @@ -21,6 +23,10 @@ CXXFLAGS_STDCXX = -std=c++17 -fexceptions -frtti -pthread endif ifneq ($(STDC),) CFLAGS_STDC = -std=$(STDC) -pthread +else ifeq ($(shell printf '\n' > bin/empty.c ; if $(CC) -std=c23 -c bin/empty.c -o bin/empty.out > /dev/null 2>&1 ; then echo 'c23' ; fi ), c23) +CFLAGS_STDC = -std=c23 -pthread +else ifeq ($(shell printf '\n' > bin/empty.c ; if $(CC) -std=c18 -c bin/empty.c -o bin/empty.out > /dev/null 2>&1 ; then echo 'c18' ; fi ), c18) +CFLAGS_STDC = -std=c18 -pthread else ifeq ($(shell printf '\n' > bin/empty.c ; if $(CC) -std=c17 -c bin/empty.c -o bin/empty.out > /dev/null 2>&1 ; then echo 'c17' ; fi ), c17) CFLAGS_STDC = -std=c17 -pthread else diff --git a/Frameworks/OpenMPT/OpenMPT/build/make/config-generic.mk b/Frameworks/OpenMPT/OpenMPT/build/make/config-generic.mk deleted file mode 100644 index e69de29bb..000000000 diff --git a/Frameworks/OpenMPT/OpenMPT/build/make/config-haiku.mk b/Frameworks/OpenMPT/OpenMPT/build/make/config-haiku.mk deleted file mode 100644 index e69de29bb..000000000 diff --git a/Frameworks/OpenMPT/OpenMPT/build/make/config-icx.mk b/Frameworks/OpenMPT/OpenMPT/build/make/config-icx.mk index cd7cd3967..029e6d9e8 100644 --- a/Frameworks/OpenMPT/OpenMPT/build/make/config-icx.mk +++ b/Frameworks/OpenMPT/OpenMPT/build/make/config-icx.mk @@ -14,6 +14,8 @@ endif ifneq ($(STDCXX),) CXXFLAGS_STDCXX = -std=$(STDCXX) -fexceptions -frtti -pthread +else ifeq ($(shell printf '\n' > bin/empty.cpp ; if $(CXX) -std=c++23 -c bin/empty.cpp -o bin/empty.out > /dev/null 2>&1 ; then echo 'c++23' ; fi ), c++23) +CXXFLAGS_STDCXX = -std=c++23 -fexceptions -frtti -pthread else ifeq ($(shell printf '\n' > bin/empty.cpp ; if $(CXX) -std=c++20 -c bin/empty.cpp -o bin/empty.out > /dev/null 2>&1 ; then echo 'c++20' ; fi ), c++20) CXXFLAGS_STDCXX = -std=c++20 -fexceptions -frtti -pthread else @@ -21,6 +23,10 @@ CXXFLAGS_STDCXX = -std=c++17 -fexceptions -frtti -pthread endif ifneq ($(STDC),) CFLAGS_STDC = -std=$(STDC) -pthread +else ifeq ($(shell printf '\n' > bin/empty.c ; if $(CC) -std=c23 -c bin/empty.c -o bin/empty.out > /dev/null 2>&1 ; then echo 'c23' ; fi ), c23) +CFLAGS_STDC = -std=c23 -pthread +else ifeq ($(shell printf '\n' > bin/empty.c ; if $(CC) -std=c18 -c bin/empty.c -o bin/empty.out > /dev/null 2>&1 ; then echo 'c18' ; fi ), c18) +CFLAGS_STDC = -std=c18 -pthread else ifeq ($(shell printf '\n' > bin/empty.c ; if $(CC) -std=c17 -c bin/empty.c -o bin/empty.out > /dev/null 2>&1 ; then echo 'c17' ; fi ), c17) CFLAGS_STDC = -std=c17 -pthread else diff --git a/Frameworks/OpenMPT/OpenMPT/build/make/config-macos.mk b/Frameworks/OpenMPT/OpenMPT/build/make/config-macos.mk index 2b29c6b3e..cb9e3f0c6 100644 --- a/Frameworks/OpenMPT/OpenMPT/build/make/config-macos.mk +++ b/Frameworks/OpenMPT/OpenMPT/build/make/config-macos.mk @@ -1,4 +1,5 @@ +NO_NO_UNDEFINED_LINKER_FLAG=1 NO_PULSEAUDIO?=1 include build/make/config-clang.mk # Mac OS X overrides diff --git a/Frameworks/OpenMPT/OpenMPT/build/make/config-macosx.mk b/Frameworks/OpenMPT/OpenMPT/build/make/config-macosx.mk deleted file mode 100644 index e69de29bb..000000000 diff --git a/Frameworks/OpenMPT/OpenMPT/build/make/config-mingw-w64.mk b/Frameworks/OpenMPT/OpenMPT/build/make/config-mingw-w64.mk index 46536c8e1..bd130e65f 100644 --- a/Frameworks/OpenMPT/OpenMPT/build/make/config-mingw-w64.mk +++ b/Frameworks/OpenMPT/OpenMPT/build/make/config-mingw-w64.mk @@ -38,6 +38,8 @@ endif ifneq ($(STDCXX),) CXXFLAGS_STDCXX = -std=$(STDCXX) -fexceptions -frtti +else ifeq ($(shell printf '\n' > bin/empty.cpp ; if $(CXX) -std=c++23 -c bin/empty.cpp -o bin/empty.out > /dev/null 2>&1 ; then echo 'c++23' ; fi ), c++23) +CXXFLAGS_STDCXX = -std=c++23 -fexceptions -frtti else ifeq ($(shell printf '\n' > bin/empty.cpp ; if $(CXX) -std=c++20 -c bin/empty.cpp -o bin/empty.out > /dev/null 2>&1 ; then echo 'c++20' ; fi ), c++20) CXXFLAGS_STDCXX = -std=c++20 -fexceptions -frtti else @@ -45,6 +47,10 @@ CXXFLAGS_STDCXX = -std=c++17 -fexceptions -frtti endif ifneq ($(STDC),) CFLAGS_STDC = -std=$(STDC) +else ifeq ($(shell printf '\n' > bin/empty.c ; if $(CC) -std=c23 -c bin/empty.c -o bin/empty.out > /dev/null 2>&1 ; then echo 'c23' ; fi ), c23) +CFLAGS_STDC = -std=c23 +else ifeq ($(shell printf '\n' > bin/empty.c ; if $(CC) -std=c18 -c bin/empty.c -o bin/empty.out > /dev/null 2>&1 ; then echo 'c18' ; fi ), c18) +CFLAGS_STDC = -std=c18 else ifeq ($(shell printf '\n' > bin/empty.c ; if $(CC) -std=c17 -c bin/empty.c -o bin/empty.out > /dev/null 2>&1 ; then echo 'c17' ; fi ), c17) CFLAGS_STDC = -std=c17 else @@ -119,6 +125,8 @@ else $(error unknown WINDOWS_VERSION) endif +MPT_COMPILER_NOALLOCAH=1 + ifneq ($(MINGW_COMPILER),clang) # See . MPT_COMPILER_NOIPARA=1 @@ -134,22 +142,34 @@ EXESUFFIX=.exe SOSUFFIX=.dll SOSUFFIXWINDOWS=1 +ALLOW_LGPL=0 + DYNLINK=0 SHARED_LIB=1 STATIC_LIB=0 SHARED_SONAME=0 +ENABLE_DLL=1 + ifeq ($(HOST_FLAVOUR),MSYS2) else IS_CROSS=1 +ifeq ($(ALLOW_LGPL),1) +LOCAL_ZLIB=1 +LOCAL_MPG123=1 +LOCAL_OGG=1 +LOCAL_VORBIS=1 +else NO_ZLIB=1 NO_MPG123=1 NO_OGG=1 NO_VORBIS=1 NO_VORBISFILE=1 +endif + NO_PORTAUDIO=1 NO_PORTAUDIOCPP=1 NO_PULSEAUDIO=1 diff --git a/Frameworks/OpenMPT/OpenMPT/build/make/config-mingw-win9x.mk b/Frameworks/OpenMPT/OpenMPT/build/make/config-mingw-win9x.mk deleted file mode 100644 index e69de29bb..000000000 diff --git a/Frameworks/OpenMPT/OpenMPT/build/make/config-mingw.mk b/Frameworks/OpenMPT/OpenMPT/build/make/config-mingw.mk index 55ee9d9fc..c60abafcd 100644 --- a/Frameworks/OpenMPT/OpenMPT/build/make/config-mingw.mk +++ b/Frameworks/OpenMPT/OpenMPT/build/make/config-mingw.mk @@ -14,6 +14,8 @@ endif ifneq ($(STDCXX),) CXXFLAGS_STDCXX = -std=$(STDCXX) -fexceptions -frtti +else ifeq ($(shell printf '\n' > bin/empty.cpp ; if $(CXX) -std=gnu++23 -c bin/empty.cpp -o bin/empty.out > /dev/null 2>&1 ; then echo 'c++23' ; fi ), c++23) +CXXFLAGS_STDCXX = -std=gnu++23 -fexceptions -frtti else ifeq ($(shell printf '\n' > bin/empty.cpp ; if $(CXX) -std=gnu++20 -c bin/empty.cpp -o bin/empty.out > /dev/null 2>&1 ; then echo 'c++20' ; fi ), c++20) CXXFLAGS_STDCXX = -std=gnu++20 -fexceptions -frtti else @@ -21,6 +23,10 @@ CXXFLAGS_STDCXX = -std=gnu++17 -fexceptions -frtti endif ifneq ($(STDC),) CFLAGS_STDC = -std=$(STDC) +else ifeq ($(shell printf '\n' > bin/empty.c ; if $(CC) -std=gnu23 -c bin/empty.c -o bin/empty.out > /dev/null 2>&1 ; then echo 'c23' ; fi ), c23) +CFLAGS_STDC = -std=gnu23 +else ifeq ($(shell printf '\n' > bin/empty.c ; if $(CC) -std=gnu18 -c bin/empty.c -o bin/empty.out > /dev/null 2>&1 ; then echo 'c18' ; fi ), c18) +CFLAGS_STDC = -std=gnu18 else ifeq ($(shell printf '\n' > bin/empty.c ; if $(CC) -std=gnu17 -c bin/empty.c -o bin/empty.out > /dev/null 2>&1 ; then echo 'c17' ; fi ), c17) CFLAGS_STDC = -std=gnu17 else @@ -68,6 +74,8 @@ CXXFLAGS += -ffunction-sections -fdata-sections CFLAGS += -ffunction-sections -fdata-sections LDFLAGS += -Wl,--gc-sections +MPT_COMPILER_NOALLOCAH=1 + CXXFLAGS += -march=i586 -m80387 -mtune=pentium CFLAGS += -march=i586 -m80387 -mtune=pentium @@ -96,11 +104,19 @@ XMP_OPENMPT=1 IS_CROSS=1 +ifeq ($(ALLOW_LGPL),1) +LOCAL_ZLIB=1 +LOCAL_MPG123=1 +LOCAL_OGG=1 +LOCAL_VORBIS=1 +else NO_ZLIB=1 NO_MPG123=1 NO_OGG=1 NO_VORBIS=1 NO_VORBISFILE=1 +endif + NO_PORTAUDIO=1 NO_PORTAUDIOCPP=1 NO_PULSEAUDIO=1 diff --git a/Frameworks/OpenMPT/OpenMPT/build/make/config-mingw32crt.mk b/Frameworks/OpenMPT/OpenMPT/build/make/config-mingw32crt.mk index 9a91f3e12..caca83c7f 100644 --- a/Frameworks/OpenMPT/OpenMPT/build/make/config-mingw32crt.mk +++ b/Frameworks/OpenMPT/OpenMPT/build/make/config-mingw32crt.mk @@ -14,6 +14,8 @@ endif ifneq ($(STDCXX),) CXXFLAGS_STDCXX = -std=$(STDCXX) -fexceptions -frtti +else ifeq ($(shell printf '\n' > bin/empty.cpp ; if $(CXX) -std=gnu++23 -c bin/empty.cpp -o bin/empty.out > /dev/null 2>&1 ; then echo 'c++23' ; fi ), c++23) +CXXFLAGS_STDCXX = -std=gnu++23 -fexceptions -frtti else ifeq ($(shell printf '\n' > bin/empty.cpp ; if $(CXX) -std=gnu++20 -c bin/empty.cpp -o bin/empty.out > /dev/null 2>&1 ; then echo 'c++20' ; fi ), c++20) CXXFLAGS_STDCXX = -std=gnu++20 -fexceptions -frtti else @@ -21,6 +23,10 @@ CXXFLAGS_STDCXX = -std=gnu++17 -fexceptions -frtti endif ifneq ($(STDC),) CFLAGS_STDC = -std=$(STDC) +else ifeq ($(shell printf '\n' > bin/empty.c ; if $(CC) -std=gnu23 -c bin/empty.c -o bin/empty.out > /dev/null 2>&1 ; then echo 'c23' ; fi ), c23) +CFLAGS_STDC = -std=gnu23 +else ifeq ($(shell printf '\n' > bin/empty.c ; if $(CC) -std=gnu18 -c bin/empty.c -o bin/empty.out > /dev/null 2>&1 ; then echo 'c18' ; fi ), c18) +CFLAGS_STDC = -std=gnu18 else ifeq ($(shell printf '\n' > bin/empty.c ; if $(CC) -std=gnu17 -c bin/empty.c -o bin/empty.out > /dev/null 2>&1 ; then echo 'c17' ; fi ), c17) CFLAGS_STDC = -std=gnu17 else @@ -68,6 +74,8 @@ CXXFLAGS += -ffunction-sections -fdata-sections CFLAGS += -ffunction-sections -fdata-sections LDFLAGS += -Wl,--gc-sections +MPT_COMPILER_NOALLOCAH=1 + CXXFLAGS += -march=i386 -m80387 -mtune=i486 CFLAGS += -march=i386 -m80387 -mtune=i486 @@ -97,11 +105,19 @@ XMP_OPENMPT=0 IS_CROSS=1 +ifeq ($(ALLOW_LGPL),1) +LOCAL_ZLIB=1 +LOCAL_MPG123=1 +LOCAL_OGG=1 +LOCAL_VORBIS=1 +else NO_ZLIB=1 NO_MPG123=1 NO_OGG=1 NO_VORBIS=1 NO_VORBISFILE=1 +endif + NO_PORTAUDIO=1 NO_PORTAUDIOCPP=1 NO_PULSEAUDIO=1 diff --git a/Frameworks/OpenMPT/OpenMPT/build/make/config-mingw64-win32.mk b/Frameworks/OpenMPT/OpenMPT/build/make/config-mingw64-win32.mk deleted file mode 100644 index e69de29bb..000000000 diff --git a/Frameworks/OpenMPT/OpenMPT/build/make/config-mingw64-win64.mk b/Frameworks/OpenMPT/OpenMPT/build/make/config-mingw64-win64.mk deleted file mode 100644 index e69de29bb..000000000 diff --git a/Frameworks/OpenMPT/OpenMPT/build/make/config-mingw64-winrt-amd64.mk b/Frameworks/OpenMPT/OpenMPT/build/make/config-mingw64-winrt-amd64.mk deleted file mode 100644 index e69de29bb..000000000 diff --git a/Frameworks/OpenMPT/OpenMPT/build/make/config-mingw64-winrt-x86.mk b/Frameworks/OpenMPT/OpenMPT/build/make/config-mingw64-winrt-x86.mk deleted file mode 100644 index e69de29bb..000000000 diff --git a/Frameworks/OpenMPT/OpenMPT/build/make/config-standard.mk b/Frameworks/OpenMPT/OpenMPT/build/make/config-standard.mk index 05f4f2949..64757e74b 100644 --- a/Frameworks/OpenMPT/OpenMPT/build/make/config-standard.mk +++ b/Frameworks/OpenMPT/OpenMPT/build/make/config-standard.mk @@ -12,7 +12,11 @@ ifeq ($(origin AR),default) AR = ar endif +#CXXFLAGS_STDCXX = -std=c++23 +#CXXFLAGS_STDCXX = -std=c++20 CXXFLAGS_STDCXX = -std=c++17 +#CFLAGS_STDC = -std=c23 +#CFLAGS_STDC = -std=c18 CFLAGS_STDC = -std=c17 CXXFLAGS += $(CXXFLAGS_STDCXX) CFLAGS += $(CFLAGS_STDC) diff --git a/Frameworks/OpenMPT/OpenMPT/build/make/config-unknown.mk b/Frameworks/OpenMPT/OpenMPT/build/make/config-unknown.mk index f8e61e149..2f6e12953 100644 --- a/Frameworks/OpenMPT/OpenMPT/build/make/config-unknown.mk +++ b/Frameworks/OpenMPT/OpenMPT/build/make/config-unknown.mk @@ -12,7 +12,12 @@ ifeq ($(origin AR),default) AR = ar endif +#CXXFLAGS_STDCXX = -std=c++23 +#CXXFLAGS_STDCXX = -std=c++20 CXXFLAGS_STDCXX = -std=c++17 +#CFLAGS_STDC = -std=c23 +#CFLAGS_STDC = -std=c18 +#CFLAGS_STDC = -std=c17 CFLAGS_STDC = -std=c11 CXXFLAGS += $(CXXFLAGS_STDCXX) CFLAGS += $(CFLAGS_STDC) diff --git a/Frameworks/OpenMPT/OpenMPT/build/make/test-pre.js b/Frameworks/OpenMPT/OpenMPT/build/make/test-pre.js deleted file mode 100644 index 3acc7d25b..000000000 --- a/Frameworks/OpenMPT/OpenMPT/build/make/test-pre.js +++ /dev/null @@ -1,9 +0,0 @@ - -var Module = { - 'preInit': function(text) { - FS.mkdir('/test'); - FS.mount(NODEFS, {'root': '../test/'}, '/test'); - FS.mkdir('/libopenmpt'); - FS.mount(NODEFS, {'root': '../libopenmpt/'}, '/libopenmpt'); - } -}; diff --git a/Frameworks/OpenMPT/OpenMPT/build/make/warnings-clang.mk b/Frameworks/OpenMPT/OpenMPT/build/make/warnings-clang.mk index 7954bdec8..f847b9b77 100644 --- a/Frameworks/OpenMPT/OpenMPT/build/make/warnings-clang.mk +++ b/Frameworks/OpenMPT/OpenMPT/build/make/warnings-clang.mk @@ -2,7 +2,7 @@ CXXFLAGS_WARNINGS += -Wcast-align -Wcast-qual -Wdouble-promotion -Wfloat-conversion -Wmissing-prototypes -Wshift-count-negative -Wshift-count-overflow -Wshift-op-parentheses -Wshift-overflow -Wshift-sign-overflow -Wundef CFLAGS_WARNINGS += -Wcast-align -Wcast-qual -Wdouble-promotion -Wfloat-conversion -Wmissing-prototypes -Wshift-count-negative -Wshift-count-overflow -Wshift-op-parentheses -Wshift-overflow -Wshift-sign-overflow -Wundef -CXXFLAGS_WARNINGS += -Wdeprecated -Wextra-semi -Wglobal-constructors -Wimplicit-fallthrough -Wmissing-declarations -Wnon-virtual-dtor -Wreserved-id-macro +CXXFLAGS_WARNINGS += -Wdeprecated -Wexit-time-destructors -Wextra-semi -Wglobal-constructors -Wimplicit-fallthrough -Wmissing-declarations -Wnon-virtual-dtor -Wreserved-id-macro CFLAGS_WARNINGS += ifneq ($(ANCIENT),1) @@ -15,10 +15,14 @@ endif #CXXFLAGS_WARNINGS += -Wconversion #CXXFLAGS_WARNINGS += -Weverything -Wno-c++98-compat -Wno-c++98-compat-pedantic -Wno-c++98-c++11-c++14-compat -Wno-padded -Wno-weak-vtables -Wno-sign-conversion -Wno-shadow-field-in-constructor -Wno-conversion -Wno-switch-enum -Wno-old-style-cast +ifneq ($(NO_NO_UNDEFINED_LINKER_FLAG),1) +LDFLAGS_WARNINGS += -Wl,--no-undefined +endif + ifeq ($(MODERN),1) CXXFLAGS_WARNINGS += CFLAGS_WARNINGS += -LDFLAGS_WARNINGS += -Wl,-no-undefined +LDFLAGS_WARNINGS += endif CFLAGS_SILENT += -Wno-\#warnings diff --git a/Frameworks/OpenMPT/OpenMPT/build/make/warnings-gcc.mk b/Frameworks/OpenMPT/OpenMPT/build/make/warnings-gcc.mk index d7470e6dd..8fccddbf9 100644 --- a/Frameworks/OpenMPT/OpenMPT/build/make/warnings-gcc.mk +++ b/Frameworks/OpenMPT/OpenMPT/build/make/warnings-gcc.mk @@ -4,11 +4,18 @@ CFLAGS_WARNINGS += -Wcast-align -Wcast-qual -Wdouble-promotion -Wfloat-convers CXXFLAGS_WARNINGS += -Wno-psabi +ifneq ($(NO_NO_UNDEFINED_LINKER_FLAG),1) +LDFLAGS_WARNINGS += -Wl,--no-undefined +endif + ifeq ($(MODERN),1) +# GCC >= 12 +# -Wconversion is way too noisy for earlier GCC versions CFLAGS_WARNINGS += -Wframe-larger-than=4000 #CXXFLAGS_WARNINGS += -Wshadow -Wswitch-enum +CXXFLAGS_WARNINGS += -Wconversion # gold -LDFLAGS_WARNINGS += -Wl,-no-undefined -Wl,--detect-odr-violations +LDFLAGS_WARNINGS += -Wl,--detect-odr-violations # GCC 8 CXXFLAGS_WARNINGS += -Wcast-align=strict CFLAGS_WARNINGS += -Wcast-align=strict diff --git a/Frameworks/OpenMPT/OpenMPT/build/svn_version/svn_version.h b/Frameworks/OpenMPT/OpenMPT/build/svn_version/svn_version.h index fc930eb18..c9cfab489 100644 --- a/Frameworks/OpenMPT/OpenMPT/build/svn_version/svn_version.h +++ b/Frameworks/OpenMPT/OpenMPT/build/svn_version/svn_version.h @@ -1,10 +1,10 @@ #pragma once -#define OPENMPT_VERSION_SVNVERSION "22826" -#define OPENMPT_VERSION_REVISION 22826 +#define OPENMPT_VERSION_SVNVERSION "23245" +#define OPENMPT_VERSION_REVISION 23245 #define OPENMPT_VERSION_DIRTY 0 #define OPENMPT_VERSION_MIXEDREVISIONS 0 -#define OPENMPT_VERSION_URL "https://source.openmpt.org/svn/openmpt/tags/libopenmpt-0.7.13" -#define OPENMPT_VERSION_DATE "2025-01-06T13:49:43.586768Z" +#define OPENMPT_VERSION_URL "https://source.openmpt.org/svn/openmpt/tags/libopenmpt-0.8.0" +#define OPENMPT_VERSION_DATE "2025-05-31T13:56:14.548439Z" #define OPENMPT_VERSION_IS_PACKAGE 1 diff --git a/Frameworks/OpenMPT/OpenMPT/build/xcode-ios/libopenmpt.xcodeproj/project.pbxproj b/Frameworks/OpenMPT/OpenMPT/build/xcode-ios/libopenmpt.xcodeproj/project.pbxproj index b24fdd2c6..020820abd 100644 --- a/Frameworks/OpenMPT/OpenMPT/build/xcode-ios/libopenmpt.xcodeproj/project.pbxproj +++ b/Frameworks/OpenMPT/OpenMPT/build/xcode-ios/libopenmpt.xcodeproj/project.pbxproj @@ -20,12 +20,14 @@ 1553F71F02BB89D19FB57D5F /* WavesReverb.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7E0A11A78A94C41905798FE7 /* WavesReverb.cpp */; }; 168C3C51AED92203DD202291 /* Message.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 681E06B9AC6CAC2BEE93E4F9 /* Message.cpp */; }; 18C62C9BF7E346CDD3ECA2DB /* SampleIO.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 78CAB4630B587E55743122A3 /* SampleIO.cpp */; }; - 1CD4D902FBF1F334D7FB4F42 /* mptFileIO.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 13F63AEAA68404DC0F5CA92A /* mptFileIO.cpp */; }; + 19C908D7F8E62309D4EF7F17 /* Load_tcb.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 73A166DF062F30D16F07D51F /* Load_tcb.cpp */; }; 1CE1B0B6B52E9668E37596F6 /* Profiler.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8F52A05ED3A145D015C87E9E /* Profiler.cpp */; }; 1E48E6EB2E2E131D1AA19D2B /* Gargle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 78BD15B31AA6B1A511CDC3F3 /* Gargle.cpp */; }; 1F7082161F13654859681856 /* mptFileType.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 074C76BE5EB1E9B010C004FE /* mptFileType.cpp */; }; + 212E136BD4241D1D254679AB /* MIDIMacroParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C61BC533BA1B0EA53F062373 /* MIDIMacroParser.cpp */; }; 247D3937039A5369DFA3AF77 /* Load_plm.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4E4D213FE0DAEB3149B38F7F /* Load_plm.cpp */; }; 281FA911C06C8EC3EEB38F51 /* Sndfile.cpp in Sources */ = {isa = PBXBuildFile; fileRef = BAF08779FF3F2CEB416665B9 /* Sndfile.cpp */; }; + 2AE422F10A013D23E60A9931 /* Load_cba.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EDF8E6598086B04BE95F5499 /* Load_cba.cpp */; }; 2B1F4A030A3C6435E645C043 /* Load_mo3.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0F49974BA1D7613D0AB0058B /* Load_mo3.cpp */; }; 30BE965074D2678268A3EC90 /* mptTime.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B176D8582299794AD8602698 /* mptTime.cpp */; }; 30C4FED93067E20B6ABC9519 /* ModChannel.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5FDB15C1B74088B3694EA401 /* ModChannel.cpp */; }; @@ -38,6 +40,7 @@ 393D72CF5409E18157DA790F /* MPEGFrame.cpp in Sources */ = {isa = PBXBuildFile; fileRef = BF406A572C6878C92DFB6897 /* MPEGFrame.cpp */; }; 39B7C99F18D4E3D1F4DE3FDF /* Load_mdl.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2052C727B2E091191BB93567 /* Load_mdl.cpp */; }; 3C92F25FD4DFD8110326D89F /* Fastmix.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1054EEE754A3945996CACD27 /* Fastmix.cpp */; }; + 3E00C2FF58CD31B15C9DC93F /* Load_pt36.cpp in Sources */ = {isa = PBXBuildFile; fileRef = BE474F872B6F5DF92D024DC7 /* Load_pt36.cpp */; }; 3EF604A50D1958579427AAE5 /* DSP.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E2066CCD3D47C03F10A20B0D /* DSP.cpp */; }; 3F8606FDF27C10AF439E6D3D /* SampleFormatMP3.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4737ECA53B373617C0224AE5 /* SampleFormatMP3.cpp */; }; 40F236CF2E59C981CB53BD0F /* I3DL2Reverb.cpp in Sources */ = {isa = PBXBuildFile; fileRef = AA3E0E57B6C8C0C931AD8C97 /* I3DL2Reverb.cpp */; }; @@ -54,8 +57,10 @@ 56DD54C135FA6EF31203CB01 /* Load_mtm.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0990B3299C1E7D1B04F72169 /* Load_mtm.cpp */; }; 574F1261366C2C93127588A1 /* load_j2b.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A33106C935BED0BB9E977509 /* load_j2b.cpp */; }; 5EE7936B3E04AD9D1A0E09AB /* Load_667.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CB46E6335DD4B025C6AD5473 /* Load_667.cpp */; }; + 609638333FB352651BBCAE73 /* Load_ftm.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0C4FEA7B9EDDB46D07B658BB /* Load_ftm.cpp */; }; 63B0011DD79878CFF9E6275D /* Load_mus_km.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 02F4B8C5C982B0373784D705 /* Load_mus_km.cpp */; }; 6600F8E10024EE13B04ACF21 /* ContainerMMCMP.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 124C374986A67C3B048A0589 /* ContainerMMCMP.cpp */; }; + 66139DF74530B829213A1437 /* Load_ims.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 26B859FFB94623F1221EC83F /* Load_ims.cpp */; }; 689E270C1013C2BEB8D8CD4C /* DigiBoosterEcho.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D078EDD4AF1B894663FD8C14 /* DigiBoosterEcho.cpp */; }; 69AC6475078D6527DF0BAAB5 /* ParamEq.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8C3D549D4EFDB50FAC3292DD /* ParamEq.cpp */; }; 6A44FDD1AE58CF03A22A5411 /* tuning.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 48199D39B93C3E2B6F02EB79 /* tuning.cpp */; }; @@ -68,36 +73,43 @@ 7F5CA93E3358C270150AFF7E /* openmpt-vorbis.lib in Frameworks */ = {isa = PBXBuildFile; fileRef = AB169C264570851881A8EA66 /* openmpt-vorbis.lib */; }; 7FDA539F18273951466E39DF /* Load_it.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 62235C27A6720199E8993A67 /* Load_it.cpp */; }; 80EEA677809189A9BAE63CB7 /* MIDIMacros.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 50FB747FA860E7715A6F02BF /* MIDIMacros.cpp */; }; + 81C26ED360DF89053CE8E513 /* Load_gmc.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D668A71B68F6710DD1CF155B /* Load_gmc.cpp */; }; 8479AC1F9F461AD1A316B25F /* Load_digi.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D14936A73E714519400434E7 /* Load_digi.cpp */; }; 84A9E60B844CC93DBEA17C4B /* MixerLoops.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B810F0D30F7663C5C1847F13 /* MixerLoops.cpp */; }; + 84F9B8439FC626F5A396BE83 /* Load_unic.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9EEAF28B0C1300FD0DA5F0CB /* Load_unic.cpp */; }; 8676CA3F6593E471419D407F /* Load_gt2.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 10E0ADC7A36E77B90C471C07 /* Load_gt2.cpp */; }; 8751165421750B86D19AEC94 /* mptStringBuffer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C510221C396A670EB74DF05C /* mptStringBuffer.cpp */; }; + 877C818BA248F03DA61987CB /* Load_puma.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6DB68B53DADE99C5DC718993 /* Load_puma.cpp */; }; 8B4D81ED6A6A9C1F4673F82D /* Load_dsm.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F5D60F958863D987F13C7DD5 /* Load_dsm.cpp */; }; 8D04F5E56C221017482B6C25 /* WAVTools.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1F5AB80DB1E881FF1AC1264D /* WAVTools.cpp */; }; 8F84E60E36FA81C0DFBF8C4E /* openmpt-ogg.lib in Frameworks */ = {isa = PBXBuildFile; fileRef = 5B7F44F63A21E068EF03E336 /* openmpt-ogg.lib */; }; 90C3E5D96FE1000B4BEA5C19 /* Load_mid.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EC1456C17EA220B3E77AC501 /* Load_mid.cpp */; }; 926864F471857F264D8EDB34 /* mptRandom.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5D6A74BCEFF83EAE58D0E2FC /* mptRandom.cpp */; }; + 9580A935A565D56791D95F75 /* PlaybackTest.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EDB72A5D8FA0C64F86C7D89D /* PlaybackTest.cpp */; }; 9715CA0B7632E43D523C404B /* Load_s3m.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1F41A2D3B1CF6CC51AA81113 /* Load_s3m.cpp */; }; 976568F7F2B0B929E0E29F37 /* libopenmpt_ext_impl.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7355A89F850CE891FA7AD6DF /* libopenmpt_ext_impl.cpp */; }; + 978580C976A29AFB52ABF709 /* MODTools.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 99E1AAB12C6F74A3954818F1 /* MODTools.cpp */; }; 97AC91CD76C9ABFF52D3080D /* Load_stm.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0735F17599C3BB67029C5FB5 /* Load_stm.cpp */; }; 97B1A0C5975483F7D1A93705 /* Echo.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9C9696EDF3FC09DFA60A252D /* Echo.cpp */; }; + 98C2B4F5B38F23A7B75FBB35 /* PlayState.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FB77AD1D689FBB8F6A32AB5D /* PlayState.cpp */; }; 9F1029ABB9DC985DBDAD2FEB /* ModSample.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A32EC1731056CFE511E9BFB3 /* ModSample.cpp */; }; 9F2D020A8C9494BC298E884A /* PlugInterface.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D89E49F2E528FC64600DC832 /* PlugInterface.cpp */; }; 9FC1BA253DA2BAD715210065 /* Flanger.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3286C74DF54727BF527C058D /* Flanger.cpp */; }; A0E13DD3392E238567752413 /* XMTools.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7C6EC51BC0BD6A8D02E4A35B /* XMTools.cpp */; }; A5DAFA6B19C3721D3C1120AB /* WindowedFIR.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 14715833DAFF4FA549017673 /* WindowedFIR.cpp */; }; A7B576CD86D290FF62DBED0D /* Load_amf.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 37F80675CA85D067335E74B5 /* Load_amf.cpp */; }; + A88063C9879D7DFB63A6DA09 /* Load_stk.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1D12DDB1AFA0A7A318794BF1 /* Load_stk.cpp */; }; A896F1F84677F2AA1DF63838 /* LFOPlugin.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D6BBDE00997C3E72F6B11C40 /* LFOPlugin.cpp */; }; A9C7C28B051312BDF344F8CB /* AudioCriticalSection.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3947FF534AFF3F45C06D2D93 /* AudioCriticalSection.cpp */; }; A9D47C71514A1823FA0F22B1 /* SoundFilePlayConfig.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F19DB8D9D040544B85225719 /* SoundFilePlayConfig.cpp */; }; AB7FFC4D9117037F545C128D /* mod_specifications.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 274EB9F5814FD0E74896C835 /* mod_specifications.cpp */; }; ABC5779D79E8CB4F00F71DDD /* OPL.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B3A14F450EE2A2B7E23CED85 /* OPL.cpp */; }; B17E84D14F5F858326DDCB11 /* ContainerPP20.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 38019939FAC1F9AB57F6D779 /* ContainerPP20.cpp */; }; + B464B3479381CD796F8B2987 /* Load_ice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D727124F69B4DC41D28D808F /* Load_ice.cpp */; }; B5220D6D8345611F0A53B3AD /* AGC.cpp in Sources */ = {isa = PBXBuildFile; fileRef = BFC43D151B059087EE5FDB55 /* AGC.cpp */; }; B591ED4C6887F6FEB9AA538C /* ComponentManager.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 13F9789407F8C2068CE3D6D4 /* ComponentManager.cpp */; }; B5D9190DF9ECEA3FEDBE6F4D /* Sndmix.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1A0FD6B58B3277A740F924F5 /* Sndmix.cpp */; }; BA54A1E79971BC19757B1827 /* Load_ams.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 29DC06EFBC69D0E12542752F /* Load_ams.cpp */; }; - BB65D2D86E5BDC8ABF7E3918 /* mptFileTemporary.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FF7F8F60F37ED8D27869EDA0 /* mptFileTemporary.cpp */; }; BBC2281DFFD5F94FF3A77E5D /* Tables.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 488AACC5B9AD4DB76F73FB05 /* Tables.cpp */; }; BC15882904B4C65B27E07E69 /* patternContainer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8BE9A4117CE11203E8E49251 /* patternContainer.cpp */; }; BCC110A956E505DB070AE6E9 /* DMOUtils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 664DAA91DAA7EF83588B78D1 /* DMOUtils.cpp */; }; @@ -105,14 +117,18 @@ BD4216739C5F30A578688CB3 /* Load_fmt.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9CF744BB2F850EAD985DB2FB /* Load_fmt.cpp */; }; BE9B2A6B02AEFB9DF68080AB /* Snd_fx.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 74D7DB33E5FA7C259BC12973 /* Snd_fx.cpp */; }; C0082C03041BFD35F7ED8243 /* Reverb.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B50C144B262EB53DDBF5628B /* Reverb.cpp */; }; + C3D725375C240AE98A6B0B77 /* Load_fc.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B6BBD03FFB0A75B13D31AE7F /* Load_fc.cpp */; }; C4FBD3C878F7ECFA5AAA2A08 /* openmpt-mpg123.lib in Frameworks */ = {isa = PBXBuildFile; fileRef = 738151100DDB3A024A139F50 /* openmpt-mpg123.lib */; }; C55C8AFDA479A52F8083013D /* UMXTools.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C7851FA55A12E997C2EB8DE5 /* UMXTools.cpp */; }; + C6B55BA579AB6557CACDC1E5 /* InstrumentSynth.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 89879ACD7D86E43F0271F90D /* InstrumentSynth.cpp */; }; C746074BA663217D826C7D8B /* Load_mt2.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0E85EC13A113B60509EC5A53 /* Load_mt2.cpp */; }; C79768571036A68933625E97 /* libopenmpt_impl.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7F22B5FF701A23F1DC1DA43F /* libopenmpt_impl.cpp */; }; + C806B217E2D320C9E6A3B857 /* Load_kris.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 19B6191F86DE27918871175F /* Load_kris.cpp */; }; C81D1553A73A2F8583438B93 /* Load_dmf.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 36CAA59BC9586F8D323113DB /* Load_dmf.cpp */; }; CB31B3AF3F1A2B616167D9EF /* modsmp_ctrl.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 09E33737D0712EA93E735577 /* modsmp_ctrl.cpp */; }; CC12FBA114B239D337DDF1E1 /* Compressor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9334A009842C0DFBF02F8E49 /* Compressor.cpp */; }; CCEFFE57CC92E18906E79497 /* MIDIEvents.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A78B5E5FFEF0D151B0FEEC9F /* MIDIEvents.cpp */; }; + CE9155C7ADAE6FF989B7CC07 /* Load_etx.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 14754CCFA70316C10FDBBB0F /* Load_etx.cpp */; }; CEDA1F2FDEBF4B61CB32D56F /* Chorus.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 84A787B7269123A91DB835F7 /* Chorus.cpp */; }; CF000469179F429B3ACAFAA9 /* tuningCollection.cpp in Sources */ = {isa = PBXBuildFile; fileRef = DFE0BC51D0D82A433CDBAA91 /* tuningCollection.cpp */; }; CF116C1742F9E3C965479257 /* Load_symmod.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7D070D1F43950491B1972B5F /* Load_symmod.cpp */; }; @@ -126,6 +142,7 @@ DEB582A5DE5865D718AD18E5 /* modcommand.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 746376CDCBC8E9BF7DD7050D /* modcommand.cpp */; }; DEBC9FDC22D0710E16A1F61C /* version.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3E2FDF24AF52801665192D64 /* version.cpp */; }; DF08AE5AEEEDDA8CDB61649A /* mptPathString.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B406AC2ED2A06B4E4511902 /* mptPathString.cpp */; }; + E234B24BC151CC7D9D5B288B /* Load_rtm.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 079A67139A2831050300D553 /* Load_rtm.cpp */; }; E6583D037EA522B5ACEC2343 /* ITTools.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 778D494BBBDBEEBDFE03278B /* ITTools.cpp */; }; E76008D1C67D2303A2867F11 /* Load_med.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8F093E392197082B8A6FAC79 /* Load_med.cpp */; }; E79434E3857535955CF37B23 /* SampleFormats.cpp in Sources */ = {isa = PBXBuildFile; fileRef = DD65792BA025D99DFD5AB76B /* SampleFormats.cpp */; }; @@ -198,6 +215,7 @@ 02F4B8C5C982B0373784D705 /* Load_mus_km.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Load_mus_km.cpp; path = ../../soundlib/Load_mus_km.cpp; sourceTree = ""; }; 03BF85B7480E2B298A3563F7 /* TinyFFT.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = TinyFFT.cpp; path = ../../soundlib/TinyFFT.cpp; sourceTree = ""; }; 04155894998C17C608286ED4 /* openmpt-mpg123.lib */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = "mpg123.xcodeproj"; path = ext/mpg123.xcodeproj; sourceTree = SOURCE_ROOT; }; + 042BC5075B9137F90D9F5347 /* PlaybackTest.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = PlaybackTest.h; path = ../../soundlib/PlaybackTest.h; sourceTree = ""; }; 046F30B171973F23732A2EF1 /* numeric.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = numeric.hpp; path = ../../src/mpt/base/numeric.hpp; sourceTree = ""; }; 04D4FEEB3466CDDD0F740D2B /* EQ.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = EQ.cpp; path = ../../sounddsp/EQ.cpp; sourceTree = ""; }; 04EAEC77C7AB4CE924E02AB7 /* DMOPlugin.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = DMOPlugin.h; path = ../../soundlib/plugins/dmo/DMOPlugin.h; sourceTree = ""; }; @@ -206,6 +224,7 @@ 073F0DABFB3E571D80296BEB /* SampleFormatSFZ.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = SampleFormatSFZ.cpp; path = ../../soundlib/SampleFormatSFZ.cpp; sourceTree = ""; }; 074C76BE5EB1E9B010C004FE /* mptFileType.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = mptFileType.cpp; path = ../../common/mptFileType.cpp; sourceTree = ""; }; 07580841CEBCC33359749681 /* tests_string_utility.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = tests_string_utility.hpp; path = ../../src/mpt/string/tests/tests_string_utility.hpp; sourceTree = ""; }; + 079A67139A2831050300D553 /* Load_rtm.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Load_rtm.cpp; path = ../../soundlib/Load_rtm.cpp; sourceTree = ""; }; 079DA5A7A1C19AD951E77BE7 /* filedata_memory.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = filedata_memory.hpp; path = ../../src/mpt/io_read/filedata_memory.hpp; sourceTree = ""; }; 0863524F9AF11C4103C9C08F /* Load_ptm.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Load_ptm.cpp; path = ../../soundlib/Load_ptm.cpp; sourceTree = ""; }; 09387CD815C32F4A90A7FB18 /* PluginMixBuffer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = PluginMixBuffer.h; path = ../../soundlib/plugins/PluginMixBuffer.h; sourceTree = ""; }; @@ -214,6 +233,7 @@ 09E33737D0712EA93E735577 /* modsmp_ctrl.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = modsmp_ctrl.cpp; path = ../../soundlib/modsmp_ctrl.cpp; sourceTree = ""; }; 0A20B0FECCE111702A15EF3E /* ComponentManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ComponentManager.h; path = ../../common/ComponentManager.h; sourceTree = ""; }; 0BC4F1DBADAE8DCDA4D5A01B /* ContainerXPK.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ContainerXPK.cpp; path = ../../soundlib/ContainerXPK.cpp; sourceTree = ""; }; + 0C4FEA7B9EDDB46D07B658BB /* Load_ftm.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Load_ftm.cpp; path = ../../soundlib/Load_ftm.cpp; sourceTree = ""; }; 0D18D5B79FA69FA9087F43F7 /* Load_dtm.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Load_dtm.cpp; path = ../../soundlib/Load_dtm.cpp; sourceTree = ""; }; 0E85EC13A113B60509EC5A53 /* Load_mt2.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Load_mt2.cpp; path = ../../soundlib/Load_mt2.cpp; sourceTree = ""; }; 0F49974BA1D7613D0AB0058B /* Load_mo3.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Load_mo3.cpp; path = ../../soundlib/Load_mo3.cpp; sourceTree = ""; }; @@ -225,9 +245,9 @@ 1197459E05968F108A81A3DE /* feature_flags.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = feature_flags.hpp; path = ../../src/mpt/arch/feature_flags.hpp; sourceTree = ""; }; 124C374986A67C3B048A0589 /* ContainerMMCMP.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ContainerMMCMP.cpp; path = ../../soundlib/ContainerMMCMP.cpp; sourceTree = ""; }; 12DCF57C800503EE8197F3BC /* mptBaseUtils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = mptBaseUtils.h; path = ../../common/mptBaseUtils.h; sourceTree = ""; }; - 13F63AEAA68404DC0F5CA92A /* mptFileIO.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = mptFileIO.cpp; path = ../../common/mptFileIO.cpp; sourceTree = ""; }; 13F9789407F8C2068CE3D6D4 /* ComponentManager.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ComponentManager.cpp; path = ../../common/ComponentManager.cpp; sourceTree = ""; }; 14715833DAFF4FA549017673 /* WindowedFIR.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = WindowedFIR.cpp; path = ../../soundlib/WindowedFIR.cpp; sourceTree = ""; }; + 14754CCFA70316C10FDBBB0F /* Load_etx.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Load_etx.cpp; path = ../../soundlib/Load_etx.cpp; sourceTree = ""; }; 159B6F7E099AB8F08E85CDBE /* PluginManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = PluginManager.h; path = ../../soundlib/plugins/PluginManager.h; sourceTree = ""; }; 15A918AF09A862218E9376EF /* aligned_array.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = aligned_array.hpp; path = ../../src/mpt/base/aligned_array.hpp; sourceTree = ""; }; 1679D9615AC87ED39CEFB7A1 /* alloc.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = alloc.hpp; path = ../../src/mpt/base/alloc.hpp; sourceTree = ""; }; @@ -235,15 +255,18 @@ 178BC3E3DA4C245537810223 /* native_path.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = native_path.hpp; path = ../../src/mpt/path/native_path.hpp; sourceTree = ""; }; 17F58A2FDAB5EAA137EAC86F /* detect_libc.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = detect_libc.hpp; path = ../../src/mpt/base/detect_libc.hpp; sourceTree = ""; }; 18A72A335CF5CFA59F1D0873 /* seed.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = seed.hpp; path = ../../src/mpt/random/seed.hpp; sourceTree = ""; }; + 19B6191F86DE27918871175F /* Load_kris.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Load_kris.cpp; path = ../../soundlib/Load_kris.cpp; sourceTree = ""; }; 1A0FD6B58B3277A740F924F5 /* Sndmix.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Sndmix.cpp; path = ../../soundlib/Sndmix.cpp; sourceTree = ""; }; 1A88E03F71EE533123FC6E7F /* RowVisitor.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = RowVisitor.cpp; path = ../../soundlib/RowVisitor.cpp; sourceTree = ""; }; 1A94BBC4BC7E57B6B3A56A04 /* mptStringFormat.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = mptStringFormat.h; path = ../../common/mptStringFormat.h; sourceTree = ""; }; 1AA25C6F0B99CA61779D4AAF /* check_platform.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = check_platform.hpp; path = ../../src/mpt/base/check_platform.hpp; sourceTree = ""; }; 1AD54F9F7616A3114970EDDF /* Paula.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = Paula.h; path = ../../soundlib/Paula.h; sourceTree = ""; }; 1B5F95F15FAE3B63A1D57431 /* Tagging.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Tagging.cpp; path = ../../soundlib/Tagging.cpp; sourceTree = ""; }; + 1BD71CF93D735A2B35E9F339 /* filedata_base_unseekable_buffer.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = filedata_base_unseekable_buffer.hpp; path = ../../src/mpt/io_read/filedata_base_unseekable_buffer.hpp; sourceTree = ""; }; 1C2F81B7DEEFE2293C24BFF7 /* detect_arch.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = detect_arch.hpp; path = ../../src/mpt/base/detect_arch.hpp; sourceTree = ""; }; 1C6D52530D64C04579684093 /* SampleFormatOpus.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = SampleFormatOpus.cpp; path = ../../soundlib/SampleFormatOpus.cpp; sourceTree = ""; }; 1CE2C4318A0AD2A38B9DC271 /* device.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = device.hpp; path = ../../src/mpt/random/device.hpp; sourceTree = ""; }; + 1D12DDB1AFA0A7A318794BF1 /* Load_stk.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Load_stk.cpp; path = ../../soundlib/Load_stk.cpp; sourceTree = ""; }; 1E7AA06D8BA2AEDF8D359EAD /* simple.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = simple.hpp; path = ../../src/mpt/format/simple.hpp; sourceTree = ""; }; 1F41A2D3B1CF6CC51AA81113 /* Load_s3m.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Load_s3m.cpp; path = ../../soundlib/Load_s3m.cpp; sourceTree = ""; }; 1F5AB80DB1E881FF1AC1264D /* WAVTools.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = WAVTools.cpp; path = ../../soundlib/WAVTools.cpp; sourceTree = ""; }; @@ -252,12 +275,14 @@ 20B693A5E7448B175546B1E5 /* MixerSettings.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = MixerSettings.h; path = ../../soundlib/MixerSettings.h; sourceTree = ""; }; 212A36FD7B2B4DEF4272453D /* semantic_version.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = semantic_version.hpp; path = ../../src/mpt/base/semantic_version.hpp; sourceTree = ""; }; 2151B0037C9303754FED4E43 /* Mixer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = Mixer.h; path = ../../soundlib/Mixer.h; sourceTree = ""; }; + 219A364192BCD73348838481 /* size.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = size.hpp; path = ../../src/mpt/base/size.hpp; sourceTree = ""; }; 22594181666D12B35A3E97C1 /* fileref.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = fileref.hpp; path = ../../src/mpt/io_file/fileref.hpp; sourceTree = ""; }; 2323E4A5944685974A0D32E5 /* Snd_defs.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = Snd_defs.h; path = ../../soundlib/Snd_defs.h; sourceTree = ""; }; 236E8DFB1D304A6D572F4C3B /* Tagging.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = Tagging.h; path = ../../soundlib/Tagging.h; sourceTree = ""; }; 23EF16CF7F4F3B81C192DD0F /* filedata_base_buffered.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = filedata_base_buffered.hpp; path = ../../src/mpt/io_read/filedata_base_buffered.hpp; sourceTree = ""; }; 25972C49C780C83BBEA7DA89 /* ContainerUMX.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ContainerUMX.cpp; path = ../../soundlib/ContainerUMX.cpp; sourceTree = ""; }; 25EAC3A1C044AC93FC7D11E1 /* unique_basename.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = unique_basename.hpp; path = ../../src/mpt/io_file_unique/unique_basename.hpp; sourceTree = ""; }; + 26B859FFB94623F1221EC83F /* Load_ims.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Load_ims.cpp; path = ../../soundlib/Load_ims.cpp; sourceTree = ""; }; 274EB9F5814FD0E74896C835 /* mod_specifications.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = mod_specifications.cpp; path = ../../soundlib/mod_specifications.cpp; sourceTree = ""; }; 27B40A4D98D6AB3F4E9D588D /* SampleIO.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SampleIO.h; path = ../../soundlib/SampleIO.h; sourceTree = ""; }; 2810796521D235D75BD137A5 /* XMTools.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = XMTools.h; path = ../../soundlib/XMTools.h; sourceTree = ""; }; @@ -279,6 +304,7 @@ 338C775D77DB1CCFBA02559D /* BitReader.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = BitReader.h; path = ../../soundlib/BitReader.h; sourceTree = ""; }; 34D45985C7622377303AC7C5 /* Load_imf.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Load_imf.cpp; path = ../../soundlib/Load_imf.cpp; sourceTree = ""; }; 352908E1F7E96953551E4721 /* libopenmpt_c.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = libopenmpt_c.cpp; path = ../../libopenmpt/libopenmpt_c.cpp; sourceTree = ""; }; + 352971E219707A149D5DA822 /* libcxx.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = libcxx.hpp; path = ../../src/mpt/check/libcxx.hpp; sourceTree = ""; }; 36619CBCFDC657AE887E2AFC /* inputfile_filecursor.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = inputfile_filecursor.hpp; path = ../../src/mpt/io_file_read/inputfile_filecursor.hpp; sourceTree = ""; }; 36CAA59BC9586F8D323113DB /* Load_dmf.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Load_dmf.cpp; path = ../../soundlib/Load_dmf.cpp; sourceTree = ""; }; 37064A799107616B584E58B9 /* tests_random.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = tests_random.hpp; path = ../../src/mpt/random/tests/tests_random.hpp; sourceTree = ""; }; @@ -308,6 +334,7 @@ 43B7419134AEAF83A0B22FD1 /* libopenmpt_config.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = libopenmpt_config.h; path = ../../libopenmpt/libopenmpt_config.h; sourceTree = ""; }; 43CBFB4A068C5BBC63C1398A /* mptFileTemporary.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = mptFileTemporary.h; path = ../../common/mptFileTemporary.h; sourceTree = ""; }; 44E6E1C323897D35D86B8003 /* tests_base_bit.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = tests_base_bit.hpp; path = ../../src/mpt/base/tests/tests_base_bit.hpp; sourceTree = ""; }; + 45A87D570868DDC9659DBB97 /* InstrumentSynth.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = InstrumentSynth.h; path = ../../soundlib/InstrumentSynth.h; sourceTree = ""; }; 45D0E0DE525B9350CD405F1E /* DigiBoosterEcho.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = DigiBoosterEcho.h; path = ../../soundlib/plugins/DigiBoosterEcho.h; sourceTree = ""; }; 45DBF5F73F9DB269799CB437 /* Dlsbank.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = Dlsbank.h; path = ../../soundlib/Dlsbank.h; sourceTree = ""; }; 4614D43FB73775316CFE227F /* math.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = math.hpp; path = ../../src/mpt/base/math.hpp; sourceTree = ""; }; @@ -327,7 +354,6 @@ 4DFC0969924AAEDBD471E7A9 /* ltdl.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = ltdl.hpp; path = ../../src/mpt/detect/ltdl.hpp; sourceTree = ""; }; 4E4D213FE0DAEB3149B38F7F /* Load_plm.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Load_plm.cpp; path = ../../soundlib/Load_plm.cpp; sourceTree = ""; }; 4E96BB31E124852349FD2971 /* types.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = types.hpp; path = ../../src/mpt/string/types.hpp; sourceTree = ""; }; - 4FB7A50743B6EE79C8A20347 /* floatingpoint.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = floatingpoint.hpp; path = ../../src/mpt/base/floatingpoint.hpp; sourceTree = ""; }; 50318A59BD5998CBBEEC8899 /* os_path.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = os_path.hpp; path = ../../src/mpt/path/os_path.hpp; sourceTree = ""; }; 50CF7EB4C1F21FA677B8CCF4 /* mptFileIO.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = mptFileIO.h; path = ../../common/mptFileIO.h; sourceTree = ""; }; 50FB747FA860E7715A6F02BF /* MIDIMacros.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = MIDIMacros.cpp; path = ../../soundlib/MIDIMacros.cpp; sourceTree = ""; }; @@ -373,6 +399,7 @@ 6B9C8BDDC301FECF75101A1D /* Gargle.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = Gargle.h; path = ../../soundlib/plugins/dmo/Gargle.h; sourceTree = ""; }; 6C67B7D97E1EF7CBF38CE619 /* tests_base_math.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = tests_base_math.hpp; path = ../../src/mpt/base/tests/tests_base_math.hpp; sourceTree = ""; }; 6D2350A8B171F61AF3992EE8 /* FileReader.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = FileReader.h; path = ../../common/FileReader.h; sourceTree = ""; }; + 6DB68B53DADE99C5DC718993 /* Load_puma.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Load_puma.cpp; path = ../../soundlib/Load_puma.cpp; sourceTree = ""; }; 6E84090F7E6935416ADCBF4F /* environment.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = environment.hpp; path = ../../src/mpt/environment/environment.hpp; sourceTree = ""; }; 6F3A912F8923F36144AA076F /* filecursor_callbackstream.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = filecursor_callbackstream.hpp; path = ../../src/mpt/io_read/filecursor_callbackstream.hpp; sourceTree = ""; }; 70644035FF1571275A9F8E75 /* libopenmpt_stream_callbacks_file_msvcrt.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = libopenmpt_stream_callbacks_file_msvcrt.h; path = ../../libopenmpt/libopenmpt_stream_callbacks_file_msvcrt.h; sourceTree = ""; }; @@ -384,6 +411,7 @@ 73262ABB150FC6AD0C36D8FB /* DMOUtils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = DMOUtils.h; path = ../../soundlib/plugins/dmo/DMOUtils.h; sourceTree = ""; }; 7355A89F850CE891FA7AD6DF /* libopenmpt_ext_impl.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = libopenmpt_ext_impl.cpp; path = ../../libopenmpt/libopenmpt_ext_impl.cpp; sourceTree = ""; }; 73781183B7C6B6F5F9EDEFC3 /* Snd_flt.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Snd_flt.cpp; path = ../../soundlib/Snd_flt.cpp; sourceTree = ""; }; + 73A166DF062F30D16F07D51F /* Load_tcb.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Load_tcb.cpp; path = ../../soundlib/Load_tcb.cpp; sourceTree = ""; }; 73A52C155E915207A6771A55 /* EQ.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = EQ.h; path = ../../sounddsp/EQ.h; sourceTree = ""; }; 746376CDCBC8E9BF7DD7050D /* modcommand.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = modcommand.cpp; path = ../../soundlib/modcommand.cpp; sourceTree = ""; }; 74D7DB33E5FA7C259BC12973 /* Snd_fx.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Snd_fx.cpp; path = ../../soundlib/Snd_fx.cpp; sourceTree = ""; }; @@ -410,6 +438,7 @@ 7FD19A57125F64497B380897 /* Load_ult.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Load_ult.cpp; path = ../../soundlib/Load_ult.cpp; sourceTree = ""; }; 80C993CB8D54463D0839120B /* message_macros.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = message_macros.hpp; path = ../../src/mpt/format/message_macros.hpp; sourceTree = ""; }; 80F9D5FD1722E7EFD889443D /* default_floatingpoint.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = default_floatingpoint.hpp; path = ../../src/mpt/format/default_floatingpoint.hpp; sourceTree = ""; }; + 8209AB5BF32C4C4DA8F2F99B /* MODTools.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = MODTools.h; path = ../../soundlib/MODTools.h; sourceTree = ""; }; 82EA4C5D44A2DDCFEBBDAA9D /* tests_base_wrapping_divide.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = tests_base_wrapping_divide.hpp; path = ../../src/mpt/base/tests/tests_base_wrapping_divide.hpp; sourceTree = ""; }; 830F3566F431D658A9F883A6 /* Logging.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Logging.cpp; path = ../../common/Logging.cpp; sourceTree = ""; }; 84A787B7269123A91DB835F7 /* Chorus.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Chorus.cpp; path = ../../soundlib/plugins/dmo/Chorus.cpp; sourceTree = ""; }; @@ -419,6 +448,7 @@ 87A62ABDE1A741AFA8EE38FD /* AudioCriticalSection.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = AudioCriticalSection.h; path = ../../soundlib/AudioCriticalSection.h; sourceTree = ""; }; 8856685FF57E76D1F711669F /* base64.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = base64.hpp; path = ../../src/mpt/binary/base64.hpp; sourceTree = ""; }; 8876678BE2777E7DA9BE75CB /* default_engines.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = default_engines.hpp; path = ../../src/mpt/random/default_engines.hpp; sourceTree = ""; }; + 89879ACD7D86E43F0271F90D /* InstrumentSynth.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = InstrumentSynth.cpp; path = ../../soundlib/InstrumentSynth.cpp; sourceTree = ""; }; 8A87EA73FEE22F657CC5B8B3 /* macros.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = macros.hpp; path = ../../src/mpt/string_transcode/macros.hpp; sourceTree = ""; }; 8ABDA3472E24D0F9455A8987 /* filedata_base_unseekable.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = filedata_base_unseekable.hpp; path = ../../src/mpt/io_read/filedata_base_unseekable.hpp; sourceTree = ""; }; 8ABF23E958E2779BDFF0CA29 /* mutex.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = mutex.hpp; path = ../../src/mpt/mutex/mutex.hpp; sourceTree = ""; }; @@ -435,7 +465,6 @@ 8F52A05ED3A145D015C87E9E /* Profiler.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Profiler.cpp; path = ../../common/Profiler.cpp; sourceTree = ""; }; 9061AC518460F5C3094C0A91 /* SampleFormatBRR.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = SampleFormatBRR.cpp; path = ../../soundlib/SampleFormatBRR.cpp; sourceTree = ""; }; 90E45EE632CDFAD829F50D26 /* mptStringBuffer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = mptStringBuffer.h; path = ../../common/mptStringBuffer.h; sourceTree = ""; }; - 9133E604FE5BF476FFEEE444 /* OpCodes.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = OpCodes.h; path = ../../soundlib/plugins/OpCodes.h; sourceTree = ""; }; 915D6003E8C2D2F59AD0EE43 /* message.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = message.hpp; path = ../../src/mpt/format/message.hpp; sourceTree = ""; }; 91AA522185A99B930A94B061 /* I3DL2Reverb.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = I3DL2Reverb.h; path = ../../soundlib/plugins/dmo/I3DL2Reverb.h; sourceTree = ""; }; 91EC613953A4F2ABFABFBF79 /* libopenmpt_stream_callbacks_file.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = libopenmpt_stream_callbacks_file.h; path = ../../libopenmpt/libopenmpt_stream_callbacks_file.h; sourceTree = ""; }; @@ -449,6 +478,7 @@ 97B8EC8104E0FAF30673EAC1 /* OggStream.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = OggStream.cpp; path = ../../soundlib/OggStream.cpp; sourceTree = ""; }; 97BF12BD2A4CDCAF932580FD /* Load_gdm.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Load_gdm.cpp; path = ../../soundlib/Load_gdm.cpp; sourceTree = ""; }; 995821ED5E0D785F127C202D /* tests_string_buffer.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = tests_string_buffer.hpp; path = ../../src/mpt/string/tests/tests_string_buffer.hpp; sourceTree = ""; }; + 99E1AAB12C6F74A3954818F1 /* MODTools.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = MODTools.cpp; path = ../../soundlib/MODTools.cpp; sourceTree = ""; }; 9B1917A761A70F19CFA935E7 /* ParamEq.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ParamEq.h; path = ../../soundlib/plugins/dmo/ParamEq.h; sourceTree = ""; }; 9B95674D94FB68BF8896C58D /* libopenmpt.dll */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; name = libopenmpt.dll; path = libopenmpt.dll; sourceTree = BUILT_PRODUCTS_DIR; }; 9BB2E75F2E40B1519719559F /* Load_far.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Load_far.cpp; path = ../../soundlib/Load_far.cpp; sourceTree = ""; }; @@ -461,6 +491,7 @@ 9E737DF712CDC2E990B14C37 /* Distortion.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = Distortion.h; path = ../../soundlib/plugins/dmo/Distortion.h; sourceTree = ""; }; 9E8AC865F88BDF57BFD2D6A5 /* tests_binary.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = tests_binary.hpp; path = ../../src/mpt/binary/tests/tests_binary.hpp; sourceTree = ""; }; 9EB0D073130B156590EE9EB3 /* Compressor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = Compressor.h; path = ../../soundlib/plugins/dmo/Compressor.h; sourceTree = ""; }; + 9EEAF28B0C1300FD0DA5F0CB /* Load_unic.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Load_unic.cpp; path = ../../soundlib/Load_unic.cpp; sourceTree = ""; }; 9F187E69B9E4ED1BBDB584A9 /* outputfile.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = outputfile.hpp; path = ../../src/mpt/io_file/outputfile.hpp; sourceTree = ""; }; 9F2E1C1AF92F330CC0762A5A /* BuildSettingsCompiler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = BuildSettingsCompiler.h; path = ../../common/BuildSettingsCompiler.h; sourceTree = ""; }; 9FCB791A628BD98CBFC0B75A /* dos_memory.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = dos_memory.hpp; path = ../../src/mpt/osinfo/dos_memory.hpp; sourceTree = ""; }; @@ -471,6 +502,7 @@ A32EC1731056CFE511E9BFB3 /* ModSample.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ModSample.cpp; path = ../../soundlib/ModSample.cpp; sourceTree = ""; }; A33106C935BED0BB9E977509 /* load_j2b.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = load_j2b.cpp; path = ../../soundlib/load_j2b.cpp; sourceTree = ""; }; A34959973B963F4969DD3FD7 /* filedata.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = filedata.hpp; path = ../../src/mpt/io_read/filedata.hpp; sourceTree = ""; }; + A35EA1E7E7AD475929D48027 /* PlayState.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = PlayState.h; path = ../../soundlib/PlayState.h; sourceTree = ""; }; A527626137B52C53A08DD0A1 /* int24.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = int24.hpp; path = ../../src/mpt/endian/int24.hpp; sourceTree = ""; }; A78B5E5FFEF0D151B0FEEC9F /* MIDIEvents.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = MIDIEvents.cpp; path = ../../soundlib/MIDIEvents.cpp; sourceTree = ""; }; A7A09A55A16256C7DB615895 /* Paula.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Paula.cpp; path = ../../soundlib/Paula.cpp; sourceTree = ""; }; @@ -493,7 +525,9 @@ B517EB3DCFE459EFD3B4F17D /* exception.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = exception.hpp; path = ../../src/mpt/exception/exception.hpp; sourceTree = ""; }; B5449C6B226CAADD23FF9AAB /* ModSequence.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ModSequence.h; path = ../../soundlib/ModSequence.h; sourceTree = ""; }; B547A5AF29A1EAA1A78573EF /* type_traits.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = type_traits.hpp; path = ../../src/mpt/endian/type_traits.hpp; sourceTree = ""; }; + B55D7A70F9AC1FE23BD358B0 /* GzipWriter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = GzipWriter.h; path = ../../common/GzipWriter.h; sourceTree = ""; }; B569F9F747F7C3E9B0D06837 /* Load_669.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Load_669.cpp; path = ../../soundlib/Load_669.cpp; sourceTree = ""; }; + B6BBD03FFB0A75B13D31AE7F /* Load_fc.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Load_fc.cpp; path = ../../soundlib/Load_fc.cpp; sourceTree = ""; }; B810F0D30F7663C5C1847F13 /* MixerLoops.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = MixerLoops.cpp; path = ../../soundlib/MixerLoops.cpp; sourceTree = ""; }; B99E396D4C2C035FB504A7AD /* Load_c67.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Load_c67.cpp; path = ../../soundlib/Load_c67.cpp; sourceTree = ""; }; B9B7FE84CBE1DB36FFD2C4C4 /* windows.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = windows.hpp; path = ../../src/mpt/check/windows.hpp; sourceTree = ""; }; @@ -504,6 +538,7 @@ BAF08779FF3F2CEB416665B9 /* Sndfile.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Sndfile.cpp; path = ../../soundlib/Sndfile.cpp; sourceTree = ""; }; BBEF0B95B5B0C807EFAFC9D5 /* ITTools.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ITTools.h; path = ../../soundlib/ITTools.h; sourceTree = ""; }; BD023BA1315C8093AF4009E1 /* simple_spec.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = simple_spec.hpp; path = ../../src/mpt/format/simple_spec.hpp; sourceTree = ""; }; + BE474F872B6F5DF92D024DC7 /* Load_pt36.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Load_pt36.cpp; path = ../../soundlib/Load_pt36.cpp; sourceTree = ""; }; BF406A572C6878C92DFB6897 /* MPEGFrame.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = MPEGFrame.cpp; path = ../../soundlib/MPEGFrame.cpp; sourceTree = ""; }; BFC43D151B059087EE5FDB55 /* AGC.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = AGC.cpp; path = ../../sounddsp/AGC.cpp; sourceTree = ""; }; C0B331FD5340FBEFBC19A03D /* MixerLoops.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = MixerLoops.h; path = ../../soundlib/MixerLoops.h; sourceTree = ""; }; @@ -513,6 +548,7 @@ C3E634155673FE07BF4CA255 /* macros.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = macros.hpp; path = ../../src/mpt/base/macros.hpp; sourceTree = ""; }; C3FAC67D5688906FBF6134BD /* detect.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = detect.hpp; path = ../../src/mpt/base/detect.hpp; sourceTree = ""; }; C510221C396A670EB74DF05C /* mptStringBuffer.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = mptStringBuffer.cpp; path = ../../common/mptStringBuffer.cpp; sourceTree = ""; }; + C61BC533BA1B0EA53F062373 /* MIDIMacroParser.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = MIDIMacroParser.cpp; path = ../../soundlib/MIDIMacroParser.cpp; sourceTree = ""; }; C6F2110A1E5783FCD0659F4A /* FileReaderFwd.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = FileReaderFwd.h; path = ../../common/FileReaderFwd.h; sourceTree = ""; }; C7851FA55A12E997C2EB8DE5 /* UMXTools.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = UMXTools.cpp; path = ../../soundlib/UMXTools.cpp; sourceTree = ""; }; C875E7BBB96D55AD2570D5FB /* floatingpoint.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = floatingpoint.hpp; path = ../../src/mpt/endian/floatingpoint.hpp; sourceTree = ""; }; @@ -520,6 +556,7 @@ C950084F5BDDD241C4B6768F /* Load_sfx.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Load_sfx.cpp; path = ../../soundlib/Load_sfx.cpp; sourceTree = ""; }; C9545B192355720BEA9C6959 /* arithmetic_shift.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = arithmetic_shift.hpp; path = ../../src/mpt/base/arithmetic_shift.hpp; sourceTree = ""; }; C9DD180C21428AFED350A64C /* mptPathString.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = mptPathString.h; path = ../../common/mptPathString.h; sourceTree = ""; }; + C9E029A3906E2115FE7047E3 /* debugging.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = debugging.hpp; path = ../../src/mpt/base/debugging.hpp; sourceTree = ""; }; CA2995890E783AFB509F73C9 /* array.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = array.hpp; path = ../../src/mpt/base/array.hpp; sourceTree = ""; }; CA67A8613B8A4953F150F6A1 /* S3MTools.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = S3MTools.h; path = ../../soundlib/S3MTools.h; sourceTree = ""; }; CB4660EB0F95065D51BC3F2B /* pattern.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = pattern.cpp; path = ../../soundlib/pattern.cpp; sourceTree = ""; }; @@ -541,7 +578,9 @@ D35A7771C759C0E34C44D5B1 /* WavesReverb.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = WavesReverb.h; path = ../../soundlib/plugins/dmo/WavesReverb.h; sourceTree = ""; }; D37D7529660B3F1BCEE3E369 /* MIDIMacros.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = MIDIMacros.h; path = ../../soundlib/MIDIMacros.h; sourceTree = ""; }; D432AEE7E417DB19D08B6527 /* tests_parse.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = tests_parse.hpp; path = ../../src/mpt/parse/tests/tests_parse.hpp; sourceTree = ""; }; + D668A71B68F6710DD1CF155B /* Load_gmc.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Load_gmc.cpp; path = ../../soundlib/Load_gmc.cpp; sourceTree = ""; }; D6BBDE00997C3E72F6B11C40 /* LFOPlugin.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = LFOPlugin.cpp; path = ../../soundlib/plugins/LFOPlugin.cpp; sourceTree = ""; }; + D727124F69B4DC41D28D808F /* Load_ice.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Load_ice.cpp; path = ../../soundlib/Load_ice.cpp; sourceTree = ""; }; D89E49F2E528FC64600DC832 /* PlugInterface.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = PlugInterface.cpp; path = ../../soundlib/plugins/PlugInterface.cpp; sourceTree = ""; }; D941724BCA38E03D363C608B /* saturate_round.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = saturate_round.hpp; path = ../../src/mpt/base/saturate_round.hpp; sourceTree = ""; }; DA41BBEFE6CC6E6161B13A2F /* transcode.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = transcode.hpp; path = ../../src/mpt/string_transcode/transcode.hpp; sourceTree = ""; }; @@ -560,18 +599,21 @@ E2066CCD3D47C03F10A20B0D /* DSP.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = DSP.cpp; path = ../../sounddsp/DSP.cpp; sourceTree = ""; }; E276AB813DD6D033801A71C1 /* filedata_base_seekable.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = filedata_base_seekable.hpp; path = ../../src/mpt/io_read/filedata_base_seekable.hpp; sourceTree = ""; }; E2B3BCB34FDBCB25516EBAF3 /* random.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = random.hpp; path = ../../src/mpt/random/random.hpp; sourceTree = ""; }; + E38574ABA645D51D037AB2EB /* any_engine.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = any_engine.hpp; path = ../../src/mpt/random/any_engine.hpp; sourceTree = ""; }; E438935FD53001514133819F /* mod_specifications.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = mod_specifications.h; path = ../../soundlib/mod_specifications.h; sourceTree = ""; }; E4C0ABA9F677EB9B6BE5D9E9 /* libopenmpt_ext_impl.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = libopenmpt_ext_impl.hpp; path = ../../libopenmpt/libopenmpt_ext_impl.hpp; sourceTree = ""; }; E59C6F4DDF5E2BBF195D2D8D /* Loaders.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = Loaders.h; path = ../../soundlib/Loaders.h; sourceTree = ""; }; E64FF33AA9BBB4ECE85F597A /* openmpt-ogg.lib */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = "ogg.xcodeproj"; path = ext/ogg.xcodeproj; sourceTree = SOURCE_ROOT; }; E6905433791E1E25E1F6C273 /* SampleCopy.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SampleCopy.h; path = ../../soundlib/SampleCopy.h; sourceTree = ""; }; E7811C8A7A0EE67CE2E78ACA /* class.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = class.hpp; path = ../../src/mpt/osinfo/class.hpp; sourceTree = ""; }; + E7ACD57DAA6D35EF07A213BD /* MIDIMacroParser.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = MIDIMacroParser.h; path = ../../soundlib/MIDIMacroParser.h; sourceTree = ""; }; E898D5ABAF26CD1D1D28F3EB /* ModInstrument.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ModInstrument.h; path = ../../soundlib/ModInstrument.h; sourceTree = ""; }; EABA2F1F4147999119C0AD5F /* AGC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = AGC.h; path = ../../sounddsp/AGC.h; sourceTree = ""; }; EC1456C17EA220B3E77AC501 /* Load_mid.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Load_mid.cpp; path = ../../soundlib/Load_mid.cpp; sourceTree = ""; }; ED3DBD7D7FCB876FE8A42BBD /* memory.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = memory.hpp; path = ../../src/mpt/base/memory.hpp; sourceTree = ""; }; + EDB72A5D8FA0C64F86C7D89D /* PlaybackTest.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = PlaybackTest.cpp; path = ../../soundlib/PlaybackTest.cpp; sourceTree = ""; }; + EDF8E6598086B04BE95F5499 /* Load_cba.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Load_cba.cpp; path = ../../soundlib/Load_cba.cpp; sourceTree = ""; }; EEC8D9B98156A3ABEA2F47F9 /* tuningbase.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = tuningbase.h; path = ../../soundlib/tuningbase.h; sourceTree = ""; }; - EF78F5224ABA48941E149362 /* Dither.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = Dither.h; path = ../../common/Dither.h; sourceTree = ""; }; F06C78E982FA42DBEBD2E729 /* RowVisitor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = RowVisitor.h; path = ../../soundlib/RowVisitor.h; sourceTree = ""; }; F1208C2D83AE561FEC86FA6D /* Load_psm.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Load_psm.cpp; path = ../../soundlib/Load_psm.cpp; sourceTree = ""; }; F18CD1F1E58C1B636A773031 /* saturate_cast.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = saturate_cast.hpp; path = ../../src/mpt/base/saturate_cast.hpp; sourceTree = ""; }; @@ -585,6 +627,7 @@ F48DAABB97F4D86DAF2A90FB /* filecursor_traits_memory.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = filecursor_traits_memory.hpp; path = ../../src/mpt/io_read/filecursor_traits_memory.hpp; sourceTree = ""; }; F5D2F247BC60E9B92A631087 /* libopenmpt.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = libopenmpt.hpp; path = ../../libopenmpt/libopenmpt.hpp; sourceTree = ""; }; F5D60F958863D987F13C7DD5 /* Load_dsm.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Load_dsm.cpp; path = ../../soundlib/Load_dsm.cpp; sourceTree = ""; }; + F5DB217BB89B81ED15D05FBB /* type_traits.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = type_traits.hpp; path = ../../src/mpt/base/type_traits.hpp; sourceTree = ""; }; F61229996734CA8B1CFB77D9 /* guid.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = guid.hpp; path = ../../src/mpt/uuid/guid.hpp; sourceTree = ""; }; F6F37E8F68161F811DDCCCCF /* span.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = span.hpp; path = ../../src/mpt/base/span.hpp; sourceTree = ""; }; F78A378464B245F6664535C4 /* mptBaseTypes.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = mptBaseTypes.h; path = ../../common/mptBaseTypes.h; sourceTree = ""; }; @@ -592,12 +635,13 @@ F9D67691EACDE48356D164D1 /* SampleFormatFLAC.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = SampleFormatFLAC.cpp; path = ../../soundlib/SampleFormatFLAC.cpp; sourceTree = ""; }; FB5FB0E7EF5EFA59744A0F27 /* libopenmpt_ext.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = libopenmpt_ext.hpp; path = ../../libopenmpt/libopenmpt_ext.hpp; sourceTree = ""; }; FB635E3D352E14EFEA89647D /* filecursor_traits_filedata.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = filecursor_traits_filedata.hpp; path = ../../src/mpt/io_read/filecursor_traits_filedata.hpp; sourceTree = ""; }; + FB77AD1D689FBB8F6A32AB5D /* PlayState.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = PlayState.cpp; path = ../../soundlib/PlayState.cpp; sourceTree = ""; }; FBE197BEECD905B058DC85FE /* serialization_utils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = serialization_utils.h; path = ../../common/serialization_utils.h; sourceTree = ""; }; FBF22E79CF82B5EBC2544CB9 /* libopenmpt_stream_callbacks_file_posix_lfs64.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = libopenmpt_stream_callbacks_file_posix_lfs64.h; path = ../../libopenmpt/libopenmpt_stream_callbacks_file_posix_lfs64.h; sourceTree = ""; }; + FC83067740D1ABE982F8E4B7 /* float.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = float.hpp; path = ../../src/mpt/base/float.hpp; sourceTree = ""; }; FC85D407DBA2EE39B7AC4A47 /* inputfile.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = inputfile.hpp; path = ../../src/mpt/io_file/inputfile.hpp; sourceTree = ""; }; FED01DA32FB4159542CC4BE3 /* tuning.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = tuning.h; path = ../../soundlib/tuning.h; sourceTree = ""; }; FEDB3A1791690409FA41A857 /* Echo.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = Echo.h; path = ../../soundlib/plugins/dmo/Echo.h; sourceTree = ""; }; - FF7F8F60F37ED8D27869EDA0 /* mptFileTemporary.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = mptFileTemporary.cpp; path = ../../common/mptFileTemporary.cpp; sourceTree = ""; }; FFB3088F4401AE018628E6CF /* Resampler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = Resampler.h; path = ../../soundlib/Resampler.h; sourceTree = ""; }; /* End PBXFileReference section */ @@ -713,7 +757,6 @@ 45D0E0DE525B9350CD405F1E /* DigiBoosterEcho.h */, D6BBDE00997C3E72F6B11C40 /* LFOPlugin.cpp */, 6858F8CA2EE6F03C9CE9170A /* LFOPlugin.h */, - 9133E604FE5BF476FFEEE444 /* OpCodes.h */, D89E49F2E528FC64600DC832 /* PlugInterface.cpp */, 6058487C545791EED942A6BC /* PlugInterface.h */, CE32D5B4DABD882655A253F4 /* PluginManager.cpp */, @@ -898,6 +941,7 @@ children = ( 64377F84A84B50B69C1CD5C4 /* compiler.hpp */, 417FE082D6F69FB44592F6C2 /* libc.hpp */, + 352971E219707A149D5DA822 /* libcxx.hpp */, 788395FAAA7CE0AC245C1C3A /* mfc.hpp */, B9B7FE84CBE1DB36FFD2C4C4 /* windows.hpp */, ); @@ -1030,9 +1074,9 @@ 9F2E1C1AF92F330CC0762A5A /* BuildSettingsCompiler.h */, 13F9789407F8C2068CE3D6D4 /* ComponentManager.cpp */, 0A20B0FECCE111702A15EF3E /* ComponentManager.h */, - EF78F5224ABA48941E149362 /* Dither.h */, 6D2350A8B171F61AF3992EE8 /* FileReader.h */, C6F2110A1E5783FCD0659F4A /* FileReaderFwd.h */, + B55D7A70F9AC1FE23BD358B0 /* GzipWriter.h */, 830F3566F431D658A9F883A6 /* Logging.cpp */, 6303157093E70D62A6FF43B0 /* Logging.h */, 8F52A05ED3A145D015C87E9E /* Profiler.cpp */, @@ -1043,9 +1087,7 @@ F78A378464B245F6664535C4 /* mptBaseTypes.h */, 12DCF57C800503EE8197F3BC /* mptBaseUtils.h */, BA99A2F415DAF666E9354134 /* mptCPU.h */, - 13F63AEAA68404DC0F5CA92A /* mptFileIO.cpp */, 50CF7EB4C1F21FA677B8CCF4 /* mptFileIO.h */, - FF7F8F60F37ED8D27869EDA0 /* mptFileTemporary.cpp */, 43CBFB4A068C5BBC63C1398A /* mptFileTemporary.h */, 074C76BE5EB1E9B010C004FE /* mptFileType.cpp */, AFFB10484288DA3AAB617E88 /* mptFileType.h */, @@ -1116,6 +1158,7 @@ 1AA25C6F0B99CA61779D4AAF /* check_platform.hpp */, 86260AE5E2AD0F576B4FC925 /* compiletime_warning.hpp */, 021DA33D0EA855AF898D217D /* constexpr_throw.hpp */, + C9E029A3906E2115FE7047E3 /* debugging.hpp */, C3FAC67D5688906FBF6134BD /* detect.hpp */, 1C2F81B7DEEFE2293C24BFF7 /* detect_arch.hpp */, 321030713E9AE2E3B97FAEB1 /* detect_compiler.hpp */, @@ -1123,7 +1166,7 @@ 755174CF6950BE41EE3BD30F /* detect_libcxx.hpp */, 175234BFDDE02C314BE252FF /* detect_os.hpp */, 107683B90475CD2B8960E1F9 /* detect_quirks.hpp */, - 4FB7A50743B6EE79C8A20347 /* floatingpoint.hpp */, + FC83067740D1ABE982F8E4B7 /* float.hpp */, 787945A7E5A15419E73443E7 /* integer.hpp */, C3E634155673FE07BF4CA255 /* macros.hpp */, 4614D43FB73775316CFE227F /* math.hpp */, @@ -1137,9 +1180,11 @@ D941724BCA38E03D363C608B /* saturate_round.hpp */, 85812219180EEC0B80E79059 /* secure.hpp */, 212A36FD7B2B4DEF4272453D /* semantic_version.hpp */, + 219A364192BCD73348838481 /* size.hpp */, A105879DAD903A0F287505DD /* source_location.hpp */, F6F37E8F68161F811DDCCCCF /* span.hpp */, 8BDA4A4F76C67041BEAC388F /* tests */, + F5DB217BB89B81ED15D05FBB /* type_traits.hpp */, 5609DBF3C331EA65C4C4DA33 /* utility.hpp */, AB10601718386E8919CB5E57 /* version.hpp */, 2C28EA4338B39CB5B3986883 /* wrapping_divide.hpp */, @@ -1258,25 +1303,35 @@ 778D494BBBDBEEBDFE03278B /* ITTools.cpp */, BBEF0B95B5B0C807EFAFC9D5 /* ITTools.h */, 567D9E616834DE53DDA2CCA1 /* InstrumentExtensions.cpp */, + 89879ACD7D86E43F0271F90D /* InstrumentSynth.cpp */, + 45A87D570868DDC9659DBB97 /* InstrumentSynth.h */, 9E091D790F2BBE6BC4F26BB9 /* IntMixer.h */, CB46E6335DD4B025C6AD5473 /* Load_667.cpp */, B569F9F747F7C3E9B0D06837 /* Load_669.cpp */, 37F80675CA85D067335E74B5 /* Load_amf.cpp */, 29DC06EFBC69D0E12542752F /* Load_ams.cpp */, B99E396D4C2C035FB504A7AD /* Load_c67.cpp */, + EDF8E6598086B04BE95F5499 /* Load_cba.cpp */, 6A66E753FCF4B14565CD5593 /* Load_dbm.cpp */, D14936A73E714519400434E7 /* Load_digi.cpp */, 36CAA59BC9586F8D323113DB /* Load_dmf.cpp */, F5D60F958863D987F13C7DD5 /* Load_dsm.cpp */, 9BE2D067090ADED90A9DCEA7 /* Load_dsym.cpp */, 0D18D5B79FA69FA9087F43F7 /* Load_dtm.cpp */, + 14754CCFA70316C10FDBBB0F /* Load_etx.cpp */, 9BB2E75F2E40B1519719559F /* Load_far.cpp */, + B6BBD03FFB0A75B13D31AE7F /* Load_fc.cpp */, 9CF744BB2F850EAD985DB2FB /* Load_fmt.cpp */, + 0C4FEA7B9EDDB46D07B658BB /* Load_ftm.cpp */, 97BF12BD2A4CDCAF932580FD /* Load_gdm.cpp */, + D668A71B68F6710DD1CF155B /* Load_gmc.cpp */, 10E0ADC7A36E77B90C471C07 /* Load_gt2.cpp */, + D727124F69B4DC41D28D808F /* Load_ice.cpp */, 34D45985C7622377303AC7C5 /* Load_imf.cpp */, + 26B859FFB94623F1221EC83F /* Load_ims.cpp */, 62235C27A6720199E8993A67 /* Load_it.cpp */, 6A572747FCE4F13965BD9587 /* Load_itp.cpp */, + 19B6191F86DE27918871175F /* Load_kris.cpp */, 2052C727B2E091191BB93567 /* Load_mdl.cpp */, 8F093E392197082B8A6FAC79 /* Load_med.cpp */, EC1456C17EA220B3E77AC501 /* Load_mid.cpp */, @@ -1288,22 +1343,32 @@ 6AE995E9FD775FDB66500429 /* Load_okt.cpp */, 4E4D213FE0DAEB3149B38F7F /* Load_plm.cpp */, F1208C2D83AE561FEC86FA6D /* Load_psm.cpp */, + BE474F872B6F5DF92D024DC7 /* Load_pt36.cpp */, 0863524F9AF11C4103C9C08F /* Load_ptm.cpp */, + 6DB68B53DADE99C5DC718993 /* Load_puma.cpp */, + 079A67139A2831050300D553 /* Load_rtm.cpp */, 1F41A2D3B1CF6CC51AA81113 /* Load_s3m.cpp */, C950084F5BDDD241C4B6768F /* Load_sfx.cpp */, + 1D12DDB1AFA0A7A318794BF1 /* Load_stk.cpp */, 0735F17599C3BB67029C5FB5 /* Load_stm.cpp */, 666A8F1BF8F8590D61D0FD5B /* Load_stp.cpp */, 7D070D1F43950491B1972B5F /* Load_symmod.cpp */, + 73A166DF062F30D16F07D51F /* Load_tcb.cpp */, 54393E69E6C7085B4F9FACA9 /* Load_uax.cpp */, 7FD19A57125F64497B380897 /* Load_ult.cpp */, + 9EEAF28B0C1300FD0DA5F0CB /* Load_unic.cpp */, 694D3F69FBDB095B64B3ADA9 /* Load_wav.cpp */, 2A6435F76EB2DB69B0DA1437 /* Load_xm.cpp */, 2EF17543C17F3F352A57E383 /* Load_xmf.cpp */, E59C6F4DDF5E2BBF195D2D8D /* Loaders.h */, A78B5E5FFEF0D151B0FEEC9F /* MIDIEvents.cpp */, F42AAB0986B874FBEF911949 /* MIDIEvents.h */, + C61BC533BA1B0EA53F062373 /* MIDIMacroParser.cpp */, + E7ACD57DAA6D35EF07A213BD /* MIDIMacroParser.h */, 50FB747FA860E7715A6F02BF /* MIDIMacros.cpp */, D37D7529660B3F1BCEE3E369 /* MIDIMacros.h */, + 99E1AAB12C6F74A3954818F1 /* MODTools.cpp */, + 8209AB5BF32C4C4DA8F2F99B /* MODTools.h */, BF406A572C6878C92DFB6897 /* MPEGFrame.cpp */, A0ADCD61E4FC72D32723ABA1 /* MPEGFrame.h */, 681E06B9AC6CAC2BEE93E4F9 /* Message.cpp */, @@ -1331,6 +1396,10 @@ F1E743CB3635E93D785D220B /* OggStream.h */, A7A09A55A16256C7DB615895 /* Paula.cpp */, 1AD54F9F7616A3114970EDDF /* Paula.h */, + FB77AD1D689FBB8F6A32AB5D /* PlayState.cpp */, + A35EA1E7E7AD475929D48027 /* PlayState.h */, + EDB72A5D8FA0C64F86C7D89D /* PlaybackTest.cpp */, + 042BC5075B9137F90D9F5347 /* PlaybackTest.h */, FFB3088F4401AE018628E6CF /* Resampler.h */, 1A88E03F71EE533123FC6E7F /* RowVisitor.cpp */, F06C78E982FA42DBEBD2E729 /* RowVisitor.h */, @@ -1406,6 +1475,7 @@ F76908210E7BA353FE979E61 /* random */ = { isa = PBXGroup; children = ( + E38574ABA645D51D037AB2EB /* any_engine.hpp */, 8DE932C12076FCB3894FA101 /* crand.hpp */, 8876678BE2777E7DA9BE75CB /* default_engines.hpp */, 1CE2C4318A0AD2A38B9DC271 /* device.hpp */, @@ -1434,6 +1504,7 @@ 23EF16CF7F4F3B81C192DD0F /* filedata_base_buffered.hpp */, E276AB813DD6D033801A71C1 /* filedata_base_seekable.hpp */, 8ABDA3472E24D0F9455A8987 /* filedata_base_unseekable.hpp */, + 1BD71CF93D735A2B35E9F339 /* filedata_base_unseekable_buffer.hpp */, E0CF51E794CB6B19767DA827 /* filedata_callbackstream.hpp */, 079DA5A7A1C19AD951E77BE7 /* filedata_memory.hpp */, CE271C83BB8EAF355888A2C3 /* filedata_stdstream.hpp */, @@ -1557,8 +1628,6 @@ B591ED4C6887F6FEB9AA538C /* ComponentManager.cpp in Sources */, 527E833E969254708A63D97E /* Logging.cpp in Sources */, 1CE1B0B6B52E9668E37596F6 /* Profiler.cpp in Sources */, - 1CD4D902FBF1F334D7FB4F42 /* mptFileIO.cpp in Sources */, - BB65D2D86E5BDC8ABF7E3918 /* mptFileTemporary.cpp in Sources */, 1F7082161F13654859681856 /* mptFileType.cpp in Sources */, DF08AE5AEEEDDA8CDB61649A /* mptPathString.cpp in Sources */, 926864F471857F264D8EDB34 /* mptRandom.cpp in Sources */, @@ -1584,24 +1653,33 @@ 32D83065D0B93117A83776A5 /* ITCompression.cpp in Sources */, E6583D037EA522B5ACEC2343 /* ITTools.cpp in Sources */, 4FD75379AB22A3AB995489B9 /* InstrumentExtensions.cpp in Sources */, + C6B55BA579AB6557CACDC1E5 /* InstrumentSynth.cpp in Sources */, 5EE7936B3E04AD9D1A0E09AB /* Load_667.cpp in Sources */, 4E13C16F2D30DBA1093A37AF /* Load_669.cpp in Sources */, A7B576CD86D290FF62DBED0D /* Load_amf.cpp in Sources */, BA54A1E79971BC19757B1827 /* Load_ams.cpp in Sources */, 44F9DD452416F77700205385 /* Load_c67.cpp in Sources */, + 2AE422F10A013D23E60A9931 /* Load_cba.cpp in Sources */, 7B65168B5A8230BD368B8CCB /* Load_dbm.cpp in Sources */, 8479AC1F9F461AD1A316B25F /* Load_digi.cpp in Sources */, C81D1553A73A2F8583438B93 /* Load_dmf.cpp in Sources */, 8B4D81ED6A6A9C1F4673F82D /* Load_dsm.cpp in Sources */, D2EFC1DFEDBC3091F18CC81F /* Load_dsym.cpp in Sources */, F5A6792FD4C39361B0CCEF6F /* Load_dtm.cpp in Sources */, + CE9155C7ADAE6FF989B7CC07 /* Load_etx.cpp in Sources */, D1EA5157B1076B898D10C797 /* Load_far.cpp in Sources */, + C3D725375C240AE98A6B0B77 /* Load_fc.cpp in Sources */, BD4216739C5F30A578688CB3 /* Load_fmt.cpp in Sources */, + 609638333FB352651BBCAE73 /* Load_ftm.cpp in Sources */, 707EA3954F9BBDC72BA519D5 /* Load_gdm.cpp in Sources */, + 81C26ED360DF89053CE8E513 /* Load_gmc.cpp in Sources */, 8676CA3F6593E471419D407F /* Load_gt2.cpp in Sources */, + B464B3479381CD796F8B2987 /* Load_ice.cpp in Sources */, 537472DD32918D0F0E9AE91D /* Load_imf.cpp in Sources */, + 66139DF74530B829213A1437 /* Load_ims.cpp in Sources */, 7FDA539F18273951466E39DF /* Load_it.cpp in Sources */, E7C01BBFC6DD35F1A2E691FF /* Load_itp.cpp in Sources */, + C806B217E2D320C9E6A3B857 /* Load_kris.cpp in Sources */, 39B7C99F18D4E3D1F4DE3FDF /* Load_mdl.cpp in Sources */, E76008D1C67D2303A2867F11 /* Load_med.cpp in Sources */, 90C3E5D96FE1000B4BEA5C19 /* Load_mid.cpp in Sources */, @@ -1613,19 +1691,27 @@ 49C7038128E41DB304ED79C1 /* Load_okt.cpp in Sources */, 247D3937039A5369DFA3AF77 /* Load_plm.cpp in Sources */, 0CEBFC05EC091637C8127245 /* Load_psm.cpp in Sources */, + 3E00C2FF58CD31B15C9DC93F /* Load_pt36.cpp in Sources */, 7744F34756620D79326B6987 /* Load_ptm.cpp in Sources */, + 877C818BA248F03DA61987CB /* Load_puma.cpp in Sources */, + E234B24BC151CC7D9D5B288B /* Load_rtm.cpp in Sources */, 9715CA0B7632E43D523C404B /* Load_s3m.cpp in Sources */, EA420947C95F2379A5687F87 /* Load_sfx.cpp in Sources */, + A88063C9879D7DFB63A6DA09 /* Load_stk.cpp in Sources */, 97AC91CD76C9ABFF52D3080D /* Load_stm.cpp in Sources */, FE6ED6D3DD8BF105B9954D13 /* Load_stp.cpp in Sources */, CF116C1742F9E3C965479257 /* Load_symmod.cpp in Sources */, + 19C908D7F8E62309D4EF7F17 /* Load_tcb.cpp in Sources */, 4174F40120920E33FC9B6A41 /* Load_uax.cpp in Sources */, F4EF37CFD40C5201B015AE0F /* Load_ult.cpp in Sources */, + 84F9B8439FC626F5A396BE83 /* Load_unic.cpp in Sources */, BD3885019C559F33785EFB41 /* Load_wav.cpp in Sources */, 00BB926F99087821C74F78AF /* Load_xm.cpp in Sources */, F57A8B7BD497A5ADB0A101BB /* Load_xmf.cpp in Sources */, CCEFFE57CC92E18906E79497 /* MIDIEvents.cpp in Sources */, + 212E136BD4241D1D254679AB /* MIDIMacroParser.cpp in Sources */, 80EEA677809189A9BAE63CB7 /* MIDIMacros.cpp in Sources */, + 978580C976A29AFB52ABF709 /* MODTools.cpp in Sources */, 393D72CF5409E18157DA790F /* MPEGFrame.cpp in Sources */, 168C3C51AED92203DD202291 /* Message.cpp in Sources */, 7A5E5DAB8A4389DD76B713EB /* MixFuncTable.cpp in Sources */, @@ -1638,6 +1724,8 @@ ABC5779D79E8CB4F00F71DDD /* OPL.cpp in Sources */, FD083C9917D4AB4B1BA542D9 /* OggStream.cpp in Sources */, 33DB2DAD46050A5F79F5F3ED /* Paula.cpp in Sources */, + 98C2B4F5B38F23A7B75FBB35 /* PlayState.cpp in Sources */, + 9580A935A565D56791D95F75 /* PlaybackTest.cpp in Sources */, 119C6E37113F51694B940477 /* RowVisitor.cpp in Sources */, D443016FB3601BA18F6977AF /* S3MTools.cpp in Sources */, 77A73F692A9D491B7BBFA5A9 /* SampleFormatBRR.cpp in Sources */, diff --git a/Frameworks/OpenMPT/OpenMPT/build/xcode-macosx/libopenmpt.xcodeproj/project.pbxproj b/Frameworks/OpenMPT/OpenMPT/build/xcode-macosx/libopenmpt.xcodeproj/project.pbxproj index f0c136f76..9e8a80f78 100644 --- a/Frameworks/OpenMPT/OpenMPT/build/xcode-macosx/libopenmpt.xcodeproj/project.pbxproj +++ b/Frameworks/OpenMPT/OpenMPT/build/xcode-macosx/libopenmpt.xcodeproj/project.pbxproj @@ -20,13 +20,15 @@ 1553F71F02BB89D19FB57D5F /* WavesReverb.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7E0A11A78A94C41905798FE7 /* WavesReverb.cpp */; }; 168C3C51AED92203DD202291 /* Message.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 681E06B9AC6CAC2BEE93E4F9 /* Message.cpp */; }; 18C62C9BF7E346CDD3ECA2DB /* SampleIO.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 78CAB4630B587E55743122A3 /* SampleIO.cpp */; }; - 1CD4D902FBF1F334D7FB4F42 /* mptFileIO.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 13F63AEAA68404DC0F5CA92A /* mptFileIO.cpp */; }; + 19C908D7F8E62309D4EF7F17 /* Load_tcb.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 73A166DF062F30D16F07D51F /* Load_tcb.cpp */; }; 1CE1B0B6B52E9668E37596F6 /* Profiler.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8F52A05ED3A145D015C87E9E /* Profiler.cpp */; }; 1E48E6EB2E2E131D1AA19D2B /* Gargle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 78BD15B31AA6B1A511CDC3F3 /* Gargle.cpp */; }; 1F7082161F13654859681856 /* mptFileType.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 074C76BE5EB1E9B010C004FE /* mptFileType.cpp */; }; + 212E136BD4241D1D254679AB /* MIDIMacroParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C61BC533BA1B0EA53F062373 /* MIDIMacroParser.cpp */; }; 247D3937039A5369DFA3AF77 /* Load_plm.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4E4D213FE0DAEB3149B38F7F /* Load_plm.cpp */; }; 281FA911C06C8EC3EEB38F51 /* Sndfile.cpp in Sources */ = {isa = PBXBuildFile; fileRef = BAF08779FF3F2CEB416665B9 /* Sndfile.cpp */; }; 2837D1CEECED2840A15BD00E /* openmpt-ogg.lib in Frameworks */ = {isa = PBXBuildFile; fileRef = 5DFCA7B6A69BE5E8C9C79DF6 /* openmpt-ogg.lib */; }; + 2AE422F10A013D23E60A9931 /* Load_cba.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EDF8E6598086B04BE95F5499 /* Load_cba.cpp */; }; 2B1F4A030A3C6435E645C043 /* Load_mo3.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0F49974BA1D7613D0AB0058B /* Load_mo3.cpp */; }; 30BE965074D2678268A3EC90 /* mptTime.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B176D8582299794AD8602698 /* mptTime.cpp */; }; 30C4FED93067E20B6ABC9519 /* ModChannel.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5FDB15C1B74088B3694EA401 /* ModChannel.cpp */; }; @@ -39,6 +41,7 @@ 393D72CF5409E18157DA790F /* MPEGFrame.cpp in Sources */ = {isa = PBXBuildFile; fileRef = BF406A572C6878C92DFB6897 /* MPEGFrame.cpp */; }; 39B7C99F18D4E3D1F4DE3FDF /* Load_mdl.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 2052C727B2E091191BB93567 /* Load_mdl.cpp */; }; 3C92F25FD4DFD8110326D89F /* Fastmix.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1054EEE754A3945996CACD27 /* Fastmix.cpp */; }; + 3E00C2FF58CD31B15C9DC93F /* Load_pt36.cpp in Sources */ = {isa = PBXBuildFile; fileRef = BE474F872B6F5DF92D024DC7 /* Load_pt36.cpp */; }; 3EF604A50D1958579427AAE5 /* DSP.cpp in Sources */ = {isa = PBXBuildFile; fileRef = E2066CCD3D47C03F10A20B0D /* DSP.cpp */; }; 3F8606FDF27C10AF439E6D3D /* SampleFormatMP3.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4737ECA53B373617C0224AE5 /* SampleFormatMP3.cpp */; }; 40F236CF2E59C981CB53BD0F /* I3DL2Reverb.cpp in Sources */ = {isa = PBXBuildFile; fileRef = AA3E0E57B6C8C0C931AD8C97 /* I3DL2Reverb.cpp */; }; @@ -55,8 +58,10 @@ 56DD54C135FA6EF31203CB01 /* Load_mtm.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0990B3299C1E7D1B04F72169 /* Load_mtm.cpp */; }; 574F1261366C2C93127588A1 /* load_j2b.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A33106C935BED0BB9E977509 /* load_j2b.cpp */; }; 5EE7936B3E04AD9D1A0E09AB /* Load_667.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CB46E6335DD4B025C6AD5473 /* Load_667.cpp */; }; + 609638333FB352651BBCAE73 /* Load_ftm.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0C4FEA7B9EDDB46D07B658BB /* Load_ftm.cpp */; }; 63B0011DD79878CFF9E6275D /* Load_mus_km.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 02F4B8C5C982B0373784D705 /* Load_mus_km.cpp */; }; 6600F8E10024EE13B04ACF21 /* ContainerMMCMP.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 124C374986A67C3B048A0589 /* ContainerMMCMP.cpp */; }; + 66139DF74530B829213A1437 /* Load_ims.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 26B859FFB94623F1221EC83F /* Load_ims.cpp */; }; 689E270C1013C2BEB8D8CD4C /* DigiBoosterEcho.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D078EDD4AF1B894663FD8C14 /* DigiBoosterEcho.cpp */; }; 69AC6475078D6527DF0BAAB5 /* ParamEq.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8C3D549D4EFDB50FAC3292DD /* ParamEq.cpp */; }; 6A44FDD1AE58CF03A22A5411 /* tuning.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 48199D39B93C3E2B6F02EB79 /* tuning.cpp */; }; @@ -69,18 +74,24 @@ 7DDE7A6B76A93A1D011DA0AB /* SampleFormatMediaFoundation.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D2D268330651A7A51E4B8673 /* SampleFormatMediaFoundation.cpp */; }; 7FDA539F18273951466E39DF /* Load_it.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 62235C27A6720199E8993A67 /* Load_it.cpp */; }; 80EEA677809189A9BAE63CB7 /* MIDIMacros.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 50FB747FA860E7715A6F02BF /* MIDIMacros.cpp */; }; + 81C26ED360DF89053CE8E513 /* Load_gmc.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D668A71B68F6710DD1CF155B /* Load_gmc.cpp */; }; 8479AC1F9F461AD1A316B25F /* Load_digi.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D14936A73E714519400434E7 /* Load_digi.cpp */; }; 84A9E60B844CC93DBEA17C4B /* MixerLoops.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B810F0D30F7663C5C1847F13 /* MixerLoops.cpp */; }; + 84F9B8439FC626F5A396BE83 /* Load_unic.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9EEAF28B0C1300FD0DA5F0CB /* Load_unic.cpp */; }; 8676CA3F6593E471419D407F /* Load_gt2.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 10E0ADC7A36E77B90C471C07 /* Load_gt2.cpp */; }; 8751165421750B86D19AEC94 /* mptStringBuffer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C510221C396A670EB74DF05C /* mptStringBuffer.cpp */; }; + 877C818BA248F03DA61987CB /* Load_puma.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 6DB68B53DADE99C5DC718993 /* Load_puma.cpp */; }; 8B4D81ED6A6A9C1F4673F82D /* Load_dsm.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F5D60F958863D987F13C7DD5 /* Load_dsm.cpp */; }; 8D04F5E56C221017482B6C25 /* WAVTools.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1F5AB80DB1E881FF1AC1264D /* WAVTools.cpp */; }; 90C3E5D96FE1000B4BEA5C19 /* Load_mid.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EC1456C17EA220B3E77AC501 /* Load_mid.cpp */; }; 926864F471857F264D8EDB34 /* mptRandom.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5D6A74BCEFF83EAE58D0E2FC /* mptRandom.cpp */; }; + 9580A935A565D56791D95F75 /* PlaybackTest.cpp in Sources */ = {isa = PBXBuildFile; fileRef = EDB72A5D8FA0C64F86C7D89D /* PlaybackTest.cpp */; }; 9715CA0B7632E43D523C404B /* Load_s3m.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1F41A2D3B1CF6CC51AA81113 /* Load_s3m.cpp */; }; 976568F7F2B0B929E0E29F37 /* libopenmpt_ext_impl.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7355A89F850CE891FA7AD6DF /* libopenmpt_ext_impl.cpp */; }; + 978580C976A29AFB52ABF709 /* MODTools.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 99E1AAB12C6F74A3954818F1 /* MODTools.cpp */; }; 97AC91CD76C9ABFF52D3080D /* Load_stm.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0735F17599C3BB67029C5FB5 /* Load_stm.cpp */; }; 97B1A0C5975483F7D1A93705 /* Echo.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9C9696EDF3FC09DFA60A252D /* Echo.cpp */; }; + 98C2B4F5B38F23A7B75FBB35 /* PlayState.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FB77AD1D689FBB8F6A32AB5D /* PlayState.cpp */; }; 9F1029ABB9DC985DBDAD2FEB /* ModSample.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A32EC1731056CFE511E9BFB3 /* ModSample.cpp */; }; 9F2D020A8C9494BC298E884A /* PlugInterface.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D89E49F2E528FC64600DC832 /* PlugInterface.cpp */; }; 9FC1BA253DA2BAD715210065 /* Flanger.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3286C74DF54727BF527C058D /* Flanger.cpp */; }; @@ -88,17 +99,18 @@ A5815DFEB5CE41F0B3BB0C3E /* openmpt-vorbis.lib in Frameworks */ = {isa = PBXBuildFile; fileRef = E7CD47E68F42E3983807EE26 /* openmpt-vorbis.lib */; }; A5DAFA6B19C3721D3C1120AB /* WindowedFIR.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 14715833DAFF4FA549017673 /* WindowedFIR.cpp */; }; A7B576CD86D290FF62DBED0D /* Load_amf.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 37F80675CA85D067335E74B5 /* Load_amf.cpp */; }; + A88063C9879D7DFB63A6DA09 /* Load_stk.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1D12DDB1AFA0A7A318794BF1 /* Load_stk.cpp */; }; A896F1F84677F2AA1DF63838 /* LFOPlugin.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D6BBDE00997C3E72F6B11C40 /* LFOPlugin.cpp */; }; A9C7C28B051312BDF344F8CB /* AudioCriticalSection.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3947FF534AFF3F45C06D2D93 /* AudioCriticalSection.cpp */; }; A9D47C71514A1823FA0F22B1 /* SoundFilePlayConfig.cpp in Sources */ = {isa = PBXBuildFile; fileRef = F19DB8D9D040544B85225719 /* SoundFilePlayConfig.cpp */; }; AB7FFC4D9117037F545C128D /* mod_specifications.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 274EB9F5814FD0E74896C835 /* mod_specifications.cpp */; }; ABC5779D79E8CB4F00F71DDD /* OPL.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B3A14F450EE2A2B7E23CED85 /* OPL.cpp */; }; B17E84D14F5F858326DDCB11 /* ContainerPP20.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 38019939FAC1F9AB57F6D779 /* ContainerPP20.cpp */; }; + B464B3479381CD796F8B2987 /* Load_ice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = D727124F69B4DC41D28D808F /* Load_ice.cpp */; }; B5220D6D8345611F0A53B3AD /* AGC.cpp in Sources */ = {isa = PBXBuildFile; fileRef = BFC43D151B059087EE5FDB55 /* AGC.cpp */; }; B591ED4C6887F6FEB9AA538C /* ComponentManager.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 13F9789407F8C2068CE3D6D4 /* ComponentManager.cpp */; }; B5D9190DF9ECEA3FEDBE6F4D /* Sndmix.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 1A0FD6B58B3277A740F924F5 /* Sndmix.cpp */; }; BA54A1E79971BC19757B1827 /* Load_ams.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 29DC06EFBC69D0E12542752F /* Load_ams.cpp */; }; - BB65D2D86E5BDC8ABF7E3918 /* mptFileTemporary.cpp in Sources */ = {isa = PBXBuildFile; fileRef = FF7F8F60F37ED8D27869EDA0 /* mptFileTemporary.cpp */; }; BBC2281DFFD5F94FF3A77E5D /* Tables.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 488AACC5B9AD4DB76F73FB05 /* Tables.cpp */; }; BC15882904B4C65B27E07E69 /* patternContainer.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8BE9A4117CE11203E8E49251 /* patternContainer.cpp */; }; BCC110A956E505DB070AE6E9 /* DMOUtils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 664DAA91DAA7EF83588B78D1 /* DMOUtils.cpp */; }; @@ -106,13 +118,17 @@ BD4216739C5F30A578688CB3 /* Load_fmt.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9CF744BB2F850EAD985DB2FB /* Load_fmt.cpp */; }; BE9B2A6B02AEFB9DF68080AB /* Snd_fx.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 74D7DB33E5FA7C259BC12973 /* Snd_fx.cpp */; }; C0082C03041BFD35F7ED8243 /* Reverb.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B50C144B262EB53DDBF5628B /* Reverb.cpp */; }; + C3D725375C240AE98A6B0B77 /* Load_fc.cpp in Sources */ = {isa = PBXBuildFile; fileRef = B6BBD03FFB0A75B13D31AE7F /* Load_fc.cpp */; }; C55C8AFDA479A52F8083013D /* UMXTools.cpp in Sources */ = {isa = PBXBuildFile; fileRef = C7851FA55A12E997C2EB8DE5 /* UMXTools.cpp */; }; + C6B55BA579AB6557CACDC1E5 /* InstrumentSynth.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 89879ACD7D86E43F0271F90D /* InstrumentSynth.cpp */; }; C746074BA663217D826C7D8B /* Load_mt2.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 0E85EC13A113B60509EC5A53 /* Load_mt2.cpp */; }; C79768571036A68933625E97 /* libopenmpt_impl.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7F22B5FF701A23F1DC1DA43F /* libopenmpt_impl.cpp */; }; + C806B217E2D320C9E6A3B857 /* Load_kris.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 19B6191F86DE27918871175F /* Load_kris.cpp */; }; C81D1553A73A2F8583438B93 /* Load_dmf.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 36CAA59BC9586F8D323113DB /* Load_dmf.cpp */; }; CB31B3AF3F1A2B616167D9EF /* modsmp_ctrl.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 09E33737D0712EA93E735577 /* modsmp_ctrl.cpp */; }; CC12FBA114B239D337DDF1E1 /* Compressor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 9334A009842C0DFBF02F8E49 /* Compressor.cpp */; }; CCEFFE57CC92E18906E79497 /* MIDIEvents.cpp in Sources */ = {isa = PBXBuildFile; fileRef = A78B5E5FFEF0D151B0FEEC9F /* MIDIEvents.cpp */; }; + CE9155C7ADAE6FF989B7CC07 /* Load_etx.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 14754CCFA70316C10FDBBB0F /* Load_etx.cpp */; }; CEDA1F2FDEBF4B61CB32D56F /* Chorus.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 84A787B7269123A91DB835F7 /* Chorus.cpp */; }; CF000469179F429B3ACAFAA9 /* tuningCollection.cpp in Sources */ = {isa = PBXBuildFile; fileRef = DFE0BC51D0D82A433CDBAA91 /* tuningCollection.cpp */; }; CF116C1742F9E3C965479257 /* Load_symmod.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 7D070D1F43950491B1972B5F /* Load_symmod.cpp */; }; @@ -126,6 +142,7 @@ DEB582A5DE5865D718AD18E5 /* modcommand.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 746376CDCBC8E9BF7DD7050D /* modcommand.cpp */; }; DEBC9FDC22D0710E16A1F61C /* version.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 3E2FDF24AF52801665192D64 /* version.cpp */; }; DF08AE5AEEEDDA8CDB61649A /* mptPathString.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 4B406AC2ED2A06B4E4511902 /* mptPathString.cpp */; }; + E234B24BC151CC7D9D5B288B /* Load_rtm.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 079A67139A2831050300D553 /* Load_rtm.cpp */; }; E6583D037EA522B5ACEC2343 /* ITTools.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 778D494BBBDBEEBDFE03278B /* ITTools.cpp */; }; E76008D1C67D2303A2867F11 /* Load_med.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8F093E392197082B8A6FAC79 /* Load_med.cpp */; }; E79434E3857535955CF37B23 /* SampleFormats.cpp in Sources */ = {isa = PBXBuildFile; fileRef = DD65792BA025D99DFD5AB76B /* SampleFormats.cpp */; }; @@ -198,6 +215,7 @@ 02F4B8C5C982B0373784D705 /* Load_mus_km.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Load_mus_km.cpp; path = ../../soundlib/Load_mus_km.cpp; sourceTree = ""; }; 03BF85B7480E2B298A3563F7 /* TinyFFT.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = TinyFFT.cpp; path = ../../soundlib/TinyFFT.cpp; sourceTree = ""; }; 04155894998C17C608286ED4 /* openmpt-mpg123.lib */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = "mpg123.xcodeproj"; path = ext/mpg123.xcodeproj; sourceTree = SOURCE_ROOT; }; + 042BC5075B9137F90D9F5347 /* PlaybackTest.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = PlaybackTest.h; path = ../../soundlib/PlaybackTest.h; sourceTree = ""; }; 046F30B171973F23732A2EF1 /* numeric.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = numeric.hpp; path = ../../src/mpt/base/numeric.hpp; sourceTree = ""; }; 04D4FEEB3466CDDD0F740D2B /* EQ.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = EQ.cpp; path = ../../sounddsp/EQ.cpp; sourceTree = ""; }; 04EAEC77C7AB4CE924E02AB7 /* DMOPlugin.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = DMOPlugin.h; path = ../../soundlib/plugins/dmo/DMOPlugin.h; sourceTree = ""; }; @@ -206,6 +224,7 @@ 073F0DABFB3E571D80296BEB /* SampleFormatSFZ.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = SampleFormatSFZ.cpp; path = ../../soundlib/SampleFormatSFZ.cpp; sourceTree = ""; }; 074C76BE5EB1E9B010C004FE /* mptFileType.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = mptFileType.cpp; path = ../../common/mptFileType.cpp; sourceTree = ""; }; 07580841CEBCC33359749681 /* tests_string_utility.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = tests_string_utility.hpp; path = ../../src/mpt/string/tests/tests_string_utility.hpp; sourceTree = ""; }; + 079A67139A2831050300D553 /* Load_rtm.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Load_rtm.cpp; path = ../../soundlib/Load_rtm.cpp; sourceTree = ""; }; 079DA5A7A1C19AD951E77BE7 /* filedata_memory.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = filedata_memory.hpp; path = ../../src/mpt/io_read/filedata_memory.hpp; sourceTree = ""; }; 0863524F9AF11C4103C9C08F /* Load_ptm.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Load_ptm.cpp; path = ../../soundlib/Load_ptm.cpp; sourceTree = ""; }; 09387CD815C32F4A90A7FB18 /* PluginMixBuffer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = PluginMixBuffer.h; path = ../../soundlib/plugins/PluginMixBuffer.h; sourceTree = ""; }; @@ -214,6 +233,7 @@ 09E33737D0712EA93E735577 /* modsmp_ctrl.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = modsmp_ctrl.cpp; path = ../../soundlib/modsmp_ctrl.cpp; sourceTree = ""; }; 0A20B0FECCE111702A15EF3E /* ComponentManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ComponentManager.h; path = ../../common/ComponentManager.h; sourceTree = ""; }; 0BC4F1DBADAE8DCDA4D5A01B /* ContainerXPK.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ContainerXPK.cpp; path = ../../soundlib/ContainerXPK.cpp; sourceTree = ""; }; + 0C4FEA7B9EDDB46D07B658BB /* Load_ftm.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Load_ftm.cpp; path = ../../soundlib/Load_ftm.cpp; sourceTree = ""; }; 0D18D5B79FA69FA9087F43F7 /* Load_dtm.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Load_dtm.cpp; path = ../../soundlib/Load_dtm.cpp; sourceTree = ""; }; 0E85EC13A113B60509EC5A53 /* Load_mt2.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Load_mt2.cpp; path = ../../soundlib/Load_mt2.cpp; sourceTree = ""; }; 0F49974BA1D7613D0AB0058B /* Load_mo3.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Load_mo3.cpp; path = ../../soundlib/Load_mo3.cpp; sourceTree = ""; }; @@ -225,9 +245,9 @@ 1197459E05968F108A81A3DE /* feature_flags.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = feature_flags.hpp; path = ../../src/mpt/arch/feature_flags.hpp; sourceTree = ""; }; 124C374986A67C3B048A0589 /* ContainerMMCMP.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ContainerMMCMP.cpp; path = ../../soundlib/ContainerMMCMP.cpp; sourceTree = ""; }; 12DCF57C800503EE8197F3BC /* mptBaseUtils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = mptBaseUtils.h; path = ../../common/mptBaseUtils.h; sourceTree = ""; }; - 13F63AEAA68404DC0F5CA92A /* mptFileIO.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = mptFileIO.cpp; path = ../../common/mptFileIO.cpp; sourceTree = ""; }; 13F9789407F8C2068CE3D6D4 /* ComponentManager.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ComponentManager.cpp; path = ../../common/ComponentManager.cpp; sourceTree = ""; }; 14715833DAFF4FA549017673 /* WindowedFIR.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = WindowedFIR.cpp; path = ../../soundlib/WindowedFIR.cpp; sourceTree = ""; }; + 14754CCFA70316C10FDBBB0F /* Load_etx.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Load_etx.cpp; path = ../../soundlib/Load_etx.cpp; sourceTree = ""; }; 159B6F7E099AB8F08E85CDBE /* PluginManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = PluginManager.h; path = ../../soundlib/plugins/PluginManager.h; sourceTree = ""; }; 15A918AF09A862218E9376EF /* aligned_array.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = aligned_array.hpp; path = ../../src/mpt/base/aligned_array.hpp; sourceTree = ""; }; 1679D9615AC87ED39CEFB7A1 /* alloc.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = alloc.hpp; path = ../../src/mpt/base/alloc.hpp; sourceTree = ""; }; @@ -235,15 +255,18 @@ 178BC3E3DA4C245537810223 /* native_path.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = native_path.hpp; path = ../../src/mpt/path/native_path.hpp; sourceTree = ""; }; 17F58A2FDAB5EAA137EAC86F /* detect_libc.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = detect_libc.hpp; path = ../../src/mpt/base/detect_libc.hpp; sourceTree = ""; }; 18A72A335CF5CFA59F1D0873 /* seed.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = seed.hpp; path = ../../src/mpt/random/seed.hpp; sourceTree = ""; }; + 19B6191F86DE27918871175F /* Load_kris.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Load_kris.cpp; path = ../../soundlib/Load_kris.cpp; sourceTree = ""; }; 1A0FD6B58B3277A740F924F5 /* Sndmix.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Sndmix.cpp; path = ../../soundlib/Sndmix.cpp; sourceTree = ""; }; 1A88E03F71EE533123FC6E7F /* RowVisitor.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = RowVisitor.cpp; path = ../../soundlib/RowVisitor.cpp; sourceTree = ""; }; 1A94BBC4BC7E57B6B3A56A04 /* mptStringFormat.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = mptStringFormat.h; path = ../../common/mptStringFormat.h; sourceTree = ""; }; 1AA25C6F0B99CA61779D4AAF /* check_platform.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = check_platform.hpp; path = ../../src/mpt/base/check_platform.hpp; sourceTree = ""; }; 1AD54F9F7616A3114970EDDF /* Paula.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = Paula.h; path = ../../soundlib/Paula.h; sourceTree = ""; }; 1B5F95F15FAE3B63A1D57431 /* Tagging.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Tagging.cpp; path = ../../soundlib/Tagging.cpp; sourceTree = ""; }; + 1BD71CF93D735A2B35E9F339 /* filedata_base_unseekable_buffer.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = filedata_base_unseekable_buffer.hpp; path = ../../src/mpt/io_read/filedata_base_unseekable_buffer.hpp; sourceTree = ""; }; 1C2F81B7DEEFE2293C24BFF7 /* detect_arch.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = detect_arch.hpp; path = ../../src/mpt/base/detect_arch.hpp; sourceTree = ""; }; 1C6D52530D64C04579684093 /* SampleFormatOpus.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = SampleFormatOpus.cpp; path = ../../soundlib/SampleFormatOpus.cpp; sourceTree = ""; }; 1CE2C4318A0AD2A38B9DC271 /* device.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = device.hpp; path = ../../src/mpt/random/device.hpp; sourceTree = ""; }; + 1D12DDB1AFA0A7A318794BF1 /* Load_stk.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Load_stk.cpp; path = ../../soundlib/Load_stk.cpp; sourceTree = ""; }; 1E7AA06D8BA2AEDF8D359EAD /* simple.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = simple.hpp; path = ../../src/mpt/format/simple.hpp; sourceTree = ""; }; 1F41A2D3B1CF6CC51AA81113 /* Load_s3m.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Load_s3m.cpp; path = ../../soundlib/Load_s3m.cpp; sourceTree = ""; }; 1F5AB80DB1E881FF1AC1264D /* WAVTools.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = WAVTools.cpp; path = ../../soundlib/WAVTools.cpp; sourceTree = ""; }; @@ -252,12 +275,14 @@ 20B693A5E7448B175546B1E5 /* MixerSettings.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = MixerSettings.h; path = ../../soundlib/MixerSettings.h; sourceTree = ""; }; 212A36FD7B2B4DEF4272453D /* semantic_version.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = semantic_version.hpp; path = ../../src/mpt/base/semantic_version.hpp; sourceTree = ""; }; 2151B0037C9303754FED4E43 /* Mixer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = Mixer.h; path = ../../soundlib/Mixer.h; sourceTree = ""; }; + 219A364192BCD73348838481 /* size.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = size.hpp; path = ../../src/mpt/base/size.hpp; sourceTree = ""; }; 22594181666D12B35A3E97C1 /* fileref.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = fileref.hpp; path = ../../src/mpt/io_file/fileref.hpp; sourceTree = ""; }; 2323E4A5944685974A0D32E5 /* Snd_defs.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = Snd_defs.h; path = ../../soundlib/Snd_defs.h; sourceTree = ""; }; 236E8DFB1D304A6D572F4C3B /* Tagging.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = Tagging.h; path = ../../soundlib/Tagging.h; sourceTree = ""; }; 23EF16CF7F4F3B81C192DD0F /* filedata_base_buffered.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = filedata_base_buffered.hpp; path = ../../src/mpt/io_read/filedata_base_buffered.hpp; sourceTree = ""; }; 25972C49C780C83BBEA7DA89 /* ContainerUMX.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ContainerUMX.cpp; path = ../../soundlib/ContainerUMX.cpp; sourceTree = ""; }; 25EAC3A1C044AC93FC7D11E1 /* unique_basename.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = unique_basename.hpp; path = ../../src/mpt/io_file_unique/unique_basename.hpp; sourceTree = ""; }; + 26B859FFB94623F1221EC83F /* Load_ims.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Load_ims.cpp; path = ../../soundlib/Load_ims.cpp; sourceTree = ""; }; 274EB9F5814FD0E74896C835 /* mod_specifications.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = mod_specifications.cpp; path = ../../soundlib/mod_specifications.cpp; sourceTree = ""; }; 27B40A4D98D6AB3F4E9D588D /* SampleIO.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SampleIO.h; path = ../../soundlib/SampleIO.h; sourceTree = ""; }; 2810796521D235D75BD137A5 /* XMTools.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = XMTools.h; path = ../../soundlib/XMTools.h; sourceTree = ""; }; @@ -279,6 +304,7 @@ 338C775D77DB1CCFBA02559D /* BitReader.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = BitReader.h; path = ../../soundlib/BitReader.h; sourceTree = ""; }; 34D45985C7622377303AC7C5 /* Load_imf.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Load_imf.cpp; path = ../../soundlib/Load_imf.cpp; sourceTree = ""; }; 352908E1F7E96953551E4721 /* libopenmpt_c.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = libopenmpt_c.cpp; path = ../../libopenmpt/libopenmpt_c.cpp; sourceTree = ""; }; + 352971E219707A149D5DA822 /* libcxx.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = libcxx.hpp; path = ../../src/mpt/check/libcxx.hpp; sourceTree = ""; }; 36619CBCFDC657AE887E2AFC /* inputfile_filecursor.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = inputfile_filecursor.hpp; path = ../../src/mpt/io_file_read/inputfile_filecursor.hpp; sourceTree = ""; }; 36CAA59BC9586F8D323113DB /* Load_dmf.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Load_dmf.cpp; path = ../../soundlib/Load_dmf.cpp; sourceTree = ""; }; 37064A799107616B584E58B9 /* tests_random.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = tests_random.hpp; path = ../../src/mpt/random/tests/tests_random.hpp; sourceTree = ""; }; @@ -308,6 +334,7 @@ 43B7419134AEAF83A0B22FD1 /* libopenmpt_config.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = libopenmpt_config.h; path = ../../libopenmpt/libopenmpt_config.h; sourceTree = ""; }; 43CBFB4A068C5BBC63C1398A /* mptFileTemporary.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = mptFileTemporary.h; path = ../../common/mptFileTemporary.h; sourceTree = ""; }; 44E6E1C323897D35D86B8003 /* tests_base_bit.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = tests_base_bit.hpp; path = ../../src/mpt/base/tests/tests_base_bit.hpp; sourceTree = ""; }; + 45A87D570868DDC9659DBB97 /* InstrumentSynth.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = InstrumentSynth.h; path = ../../soundlib/InstrumentSynth.h; sourceTree = ""; }; 45D0E0DE525B9350CD405F1E /* DigiBoosterEcho.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = DigiBoosterEcho.h; path = ../../soundlib/plugins/DigiBoosterEcho.h; sourceTree = ""; }; 45DBF5F73F9DB269799CB437 /* Dlsbank.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = Dlsbank.h; path = ../../soundlib/Dlsbank.h; sourceTree = ""; }; 4614D43FB73775316CFE227F /* math.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = math.hpp; path = ../../src/mpt/base/math.hpp; sourceTree = ""; }; @@ -327,7 +354,6 @@ 4DFC0969924AAEDBD471E7A9 /* ltdl.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = ltdl.hpp; path = ../../src/mpt/detect/ltdl.hpp; sourceTree = ""; }; 4E4D213FE0DAEB3149B38F7F /* Load_plm.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Load_plm.cpp; path = ../../soundlib/Load_plm.cpp; sourceTree = ""; }; 4E96BB31E124852349FD2971 /* types.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = types.hpp; path = ../../src/mpt/string/types.hpp; sourceTree = ""; }; - 4FB7A50743B6EE79C8A20347 /* floatingpoint.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = floatingpoint.hpp; path = ../../src/mpt/base/floatingpoint.hpp; sourceTree = ""; }; 50318A59BD5998CBBEEC8899 /* os_path.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = os_path.hpp; path = ../../src/mpt/path/os_path.hpp; sourceTree = ""; }; 50CF7EB4C1F21FA677B8CCF4 /* mptFileIO.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = mptFileIO.h; path = ../../common/mptFileIO.h; sourceTree = ""; }; 50FB747FA860E7715A6F02BF /* MIDIMacros.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = MIDIMacros.cpp; path = ../../soundlib/MIDIMacros.cpp; sourceTree = ""; }; @@ -373,6 +399,7 @@ 6B9C8BDDC301FECF75101A1D /* Gargle.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = Gargle.h; path = ../../soundlib/plugins/dmo/Gargle.h; sourceTree = ""; }; 6C67B7D97E1EF7CBF38CE619 /* tests_base_math.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = tests_base_math.hpp; path = ../../src/mpt/base/tests/tests_base_math.hpp; sourceTree = ""; }; 6D2350A8B171F61AF3992EE8 /* FileReader.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = FileReader.h; path = ../../common/FileReader.h; sourceTree = ""; }; + 6DB68B53DADE99C5DC718993 /* Load_puma.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Load_puma.cpp; path = ../../soundlib/Load_puma.cpp; sourceTree = ""; }; 6E84090F7E6935416ADCBF4F /* environment.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = environment.hpp; path = ../../src/mpt/environment/environment.hpp; sourceTree = ""; }; 6F3A912F8923F36144AA076F /* filecursor_callbackstream.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = filecursor_callbackstream.hpp; path = ../../src/mpt/io_read/filecursor_callbackstream.hpp; sourceTree = ""; }; 70644035FF1571275A9F8E75 /* libopenmpt_stream_callbacks_file_msvcrt.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = libopenmpt_stream_callbacks_file_msvcrt.h; path = ../../libopenmpt/libopenmpt_stream_callbacks_file_msvcrt.h; sourceTree = ""; }; @@ -384,6 +411,7 @@ 73262ABB150FC6AD0C36D8FB /* DMOUtils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = DMOUtils.h; path = ../../soundlib/plugins/dmo/DMOUtils.h; sourceTree = ""; }; 7355A89F850CE891FA7AD6DF /* libopenmpt_ext_impl.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = libopenmpt_ext_impl.cpp; path = ../../libopenmpt/libopenmpt_ext_impl.cpp; sourceTree = ""; }; 73781183B7C6B6F5F9EDEFC3 /* Snd_flt.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Snd_flt.cpp; path = ../../soundlib/Snd_flt.cpp; sourceTree = ""; }; + 73A166DF062F30D16F07D51F /* Load_tcb.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Load_tcb.cpp; path = ../../soundlib/Load_tcb.cpp; sourceTree = ""; }; 73A52C155E915207A6771A55 /* EQ.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = EQ.h; path = ../../sounddsp/EQ.h; sourceTree = ""; }; 746376CDCBC8E9BF7DD7050D /* modcommand.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = modcommand.cpp; path = ../../soundlib/modcommand.cpp; sourceTree = ""; }; 74D7DB33E5FA7C259BC12973 /* Snd_fx.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Snd_fx.cpp; path = ../../soundlib/Snd_fx.cpp; sourceTree = ""; }; @@ -410,6 +438,7 @@ 7FD19A57125F64497B380897 /* Load_ult.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Load_ult.cpp; path = ../../soundlib/Load_ult.cpp; sourceTree = ""; }; 80C993CB8D54463D0839120B /* message_macros.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = message_macros.hpp; path = ../../src/mpt/format/message_macros.hpp; sourceTree = ""; }; 80F9D5FD1722E7EFD889443D /* default_floatingpoint.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = default_floatingpoint.hpp; path = ../../src/mpt/format/default_floatingpoint.hpp; sourceTree = ""; }; + 8209AB5BF32C4C4DA8F2F99B /* MODTools.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = MODTools.h; path = ../../soundlib/MODTools.h; sourceTree = ""; }; 82EA4C5D44A2DDCFEBBDAA9D /* tests_base_wrapping_divide.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = tests_base_wrapping_divide.hpp; path = ../../src/mpt/base/tests/tests_base_wrapping_divide.hpp; sourceTree = ""; }; 830F3566F431D658A9F883A6 /* Logging.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Logging.cpp; path = ../../common/Logging.cpp; sourceTree = ""; }; 84A787B7269123A91DB835F7 /* Chorus.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Chorus.cpp; path = ../../soundlib/plugins/dmo/Chorus.cpp; sourceTree = ""; }; @@ -419,6 +448,7 @@ 87A62ABDE1A741AFA8EE38FD /* AudioCriticalSection.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = AudioCriticalSection.h; path = ../../soundlib/AudioCriticalSection.h; sourceTree = ""; }; 8856685FF57E76D1F711669F /* base64.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = base64.hpp; path = ../../src/mpt/binary/base64.hpp; sourceTree = ""; }; 8876678BE2777E7DA9BE75CB /* default_engines.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = default_engines.hpp; path = ../../src/mpt/random/default_engines.hpp; sourceTree = ""; }; + 89879ACD7D86E43F0271F90D /* InstrumentSynth.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = InstrumentSynth.cpp; path = ../../soundlib/InstrumentSynth.cpp; sourceTree = ""; }; 8A87EA73FEE22F657CC5B8B3 /* macros.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = macros.hpp; path = ../../src/mpt/string_transcode/macros.hpp; sourceTree = ""; }; 8ABDA3472E24D0F9455A8987 /* filedata_base_unseekable.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = filedata_base_unseekable.hpp; path = ../../src/mpt/io_read/filedata_base_unseekable.hpp; sourceTree = ""; }; 8ABF23E958E2779BDFF0CA29 /* mutex.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = mutex.hpp; path = ../../src/mpt/mutex/mutex.hpp; sourceTree = ""; }; @@ -435,7 +465,6 @@ 8F52A05ED3A145D015C87E9E /* Profiler.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Profiler.cpp; path = ../../common/Profiler.cpp; sourceTree = ""; }; 9061AC518460F5C3094C0A91 /* SampleFormatBRR.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = SampleFormatBRR.cpp; path = ../../soundlib/SampleFormatBRR.cpp; sourceTree = ""; }; 90E45EE632CDFAD829F50D26 /* mptStringBuffer.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = mptStringBuffer.h; path = ../../common/mptStringBuffer.h; sourceTree = ""; }; - 9133E604FE5BF476FFEEE444 /* OpCodes.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = OpCodes.h; path = ../../soundlib/plugins/OpCodes.h; sourceTree = ""; }; 915D6003E8C2D2F59AD0EE43 /* message.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = message.hpp; path = ../../src/mpt/format/message.hpp; sourceTree = ""; }; 91AA522185A99B930A94B061 /* I3DL2Reverb.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = I3DL2Reverb.h; path = ../../soundlib/plugins/dmo/I3DL2Reverb.h; sourceTree = ""; }; 91EC613953A4F2ABFABFBF79 /* libopenmpt_stream_callbacks_file.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = libopenmpt_stream_callbacks_file.h; path = ../../libopenmpt/libopenmpt_stream_callbacks_file.h; sourceTree = ""; }; @@ -449,6 +478,7 @@ 97B8EC8104E0FAF30673EAC1 /* OggStream.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = OggStream.cpp; path = ../../soundlib/OggStream.cpp; sourceTree = ""; }; 97BF12BD2A4CDCAF932580FD /* Load_gdm.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Load_gdm.cpp; path = ../../soundlib/Load_gdm.cpp; sourceTree = ""; }; 995821ED5E0D785F127C202D /* tests_string_buffer.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = tests_string_buffer.hpp; path = ../../src/mpt/string/tests/tests_string_buffer.hpp; sourceTree = ""; }; + 99E1AAB12C6F74A3954818F1 /* MODTools.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = MODTools.cpp; path = ../../soundlib/MODTools.cpp; sourceTree = ""; }; 9B1917A761A70F19CFA935E7 /* ParamEq.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ParamEq.h; path = ../../soundlib/plugins/dmo/ParamEq.h; sourceTree = ""; }; 9B95674D94FB68BF8896C58D /* libopenmpt.dll */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.dylib"; includeInIndex = 0; name = libopenmpt.dll; path = libopenmpt.dll; sourceTree = BUILT_PRODUCTS_DIR; }; 9BB2E75F2E40B1519719559F /* Load_far.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Load_far.cpp; path = ../../soundlib/Load_far.cpp; sourceTree = ""; }; @@ -461,6 +491,7 @@ 9E737DF712CDC2E990B14C37 /* Distortion.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = Distortion.h; path = ../../soundlib/plugins/dmo/Distortion.h; sourceTree = ""; }; 9E8AC865F88BDF57BFD2D6A5 /* tests_binary.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = tests_binary.hpp; path = ../../src/mpt/binary/tests/tests_binary.hpp; sourceTree = ""; }; 9EB0D073130B156590EE9EB3 /* Compressor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = Compressor.h; path = ../../soundlib/plugins/dmo/Compressor.h; sourceTree = ""; }; + 9EEAF28B0C1300FD0DA5F0CB /* Load_unic.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Load_unic.cpp; path = ../../soundlib/Load_unic.cpp; sourceTree = ""; }; 9F187E69B9E4ED1BBDB584A9 /* outputfile.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = outputfile.hpp; path = ../../src/mpt/io_file/outputfile.hpp; sourceTree = ""; }; 9F2E1C1AF92F330CC0762A5A /* BuildSettingsCompiler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = BuildSettingsCompiler.h; path = ../../common/BuildSettingsCompiler.h; sourceTree = ""; }; 9FCB791A628BD98CBFC0B75A /* dos_memory.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = dos_memory.hpp; path = ../../src/mpt/osinfo/dos_memory.hpp; sourceTree = ""; }; @@ -471,6 +502,7 @@ A32EC1731056CFE511E9BFB3 /* ModSample.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = ModSample.cpp; path = ../../soundlib/ModSample.cpp; sourceTree = ""; }; A33106C935BED0BB9E977509 /* load_j2b.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = load_j2b.cpp; path = ../../soundlib/load_j2b.cpp; sourceTree = ""; }; A34959973B963F4969DD3FD7 /* filedata.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = filedata.hpp; path = ../../src/mpt/io_read/filedata.hpp; sourceTree = ""; }; + A35EA1E7E7AD475929D48027 /* PlayState.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = PlayState.h; path = ../../soundlib/PlayState.h; sourceTree = ""; }; A527626137B52C53A08DD0A1 /* int24.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = int24.hpp; path = ../../src/mpt/endian/int24.hpp; sourceTree = ""; }; A78B5E5FFEF0D151B0FEEC9F /* MIDIEvents.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = MIDIEvents.cpp; path = ../../soundlib/MIDIEvents.cpp; sourceTree = ""; }; A7A09A55A16256C7DB615895 /* Paula.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Paula.cpp; path = ../../soundlib/Paula.cpp; sourceTree = ""; }; @@ -493,7 +525,9 @@ B517EB3DCFE459EFD3B4F17D /* exception.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = exception.hpp; path = ../../src/mpt/exception/exception.hpp; sourceTree = ""; }; B5449C6B226CAADD23FF9AAB /* ModSequence.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ModSequence.h; path = ../../soundlib/ModSequence.h; sourceTree = ""; }; B547A5AF29A1EAA1A78573EF /* type_traits.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = type_traits.hpp; path = ../../src/mpt/endian/type_traits.hpp; sourceTree = ""; }; + B55D7A70F9AC1FE23BD358B0 /* GzipWriter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = GzipWriter.h; path = ../../common/GzipWriter.h; sourceTree = ""; }; B569F9F747F7C3E9B0D06837 /* Load_669.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Load_669.cpp; path = ../../soundlib/Load_669.cpp; sourceTree = ""; }; + B6BBD03FFB0A75B13D31AE7F /* Load_fc.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Load_fc.cpp; path = ../../soundlib/Load_fc.cpp; sourceTree = ""; }; B810F0D30F7663C5C1847F13 /* MixerLoops.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = MixerLoops.cpp; path = ../../soundlib/MixerLoops.cpp; sourceTree = ""; }; B99E396D4C2C035FB504A7AD /* Load_c67.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Load_c67.cpp; path = ../../soundlib/Load_c67.cpp; sourceTree = ""; }; B9B7FE84CBE1DB36FFD2C4C4 /* windows.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = windows.hpp; path = ../../src/mpt/check/windows.hpp; sourceTree = ""; }; @@ -504,6 +538,7 @@ BAF08779FF3F2CEB416665B9 /* Sndfile.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Sndfile.cpp; path = ../../soundlib/Sndfile.cpp; sourceTree = ""; }; BBEF0B95B5B0C807EFAFC9D5 /* ITTools.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ITTools.h; path = ../../soundlib/ITTools.h; sourceTree = ""; }; BD023BA1315C8093AF4009E1 /* simple_spec.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = simple_spec.hpp; path = ../../src/mpt/format/simple_spec.hpp; sourceTree = ""; }; + BE474F872B6F5DF92D024DC7 /* Load_pt36.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Load_pt36.cpp; path = ../../soundlib/Load_pt36.cpp; sourceTree = ""; }; BF406A572C6878C92DFB6897 /* MPEGFrame.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = MPEGFrame.cpp; path = ../../soundlib/MPEGFrame.cpp; sourceTree = ""; }; BFC43D151B059087EE5FDB55 /* AGC.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = AGC.cpp; path = ../../sounddsp/AGC.cpp; sourceTree = ""; }; C0B331FD5340FBEFBC19A03D /* MixerLoops.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = MixerLoops.h; path = ../../soundlib/MixerLoops.h; sourceTree = ""; }; @@ -513,6 +548,7 @@ C3E634155673FE07BF4CA255 /* macros.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = macros.hpp; path = ../../src/mpt/base/macros.hpp; sourceTree = ""; }; C3FAC67D5688906FBF6134BD /* detect.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = detect.hpp; path = ../../src/mpt/base/detect.hpp; sourceTree = ""; }; C510221C396A670EB74DF05C /* mptStringBuffer.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = mptStringBuffer.cpp; path = ../../common/mptStringBuffer.cpp; sourceTree = ""; }; + C61BC533BA1B0EA53F062373 /* MIDIMacroParser.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = MIDIMacroParser.cpp; path = ../../soundlib/MIDIMacroParser.cpp; sourceTree = ""; }; C6F2110A1E5783FCD0659F4A /* FileReaderFwd.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = FileReaderFwd.h; path = ../../common/FileReaderFwd.h; sourceTree = ""; }; C7851FA55A12E997C2EB8DE5 /* UMXTools.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = UMXTools.cpp; path = ../../soundlib/UMXTools.cpp; sourceTree = ""; }; C875E7BBB96D55AD2570D5FB /* floatingpoint.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = floatingpoint.hpp; path = ../../src/mpt/endian/floatingpoint.hpp; sourceTree = ""; }; @@ -520,6 +556,7 @@ C950084F5BDDD241C4B6768F /* Load_sfx.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Load_sfx.cpp; path = ../../soundlib/Load_sfx.cpp; sourceTree = ""; }; C9545B192355720BEA9C6959 /* arithmetic_shift.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = arithmetic_shift.hpp; path = ../../src/mpt/base/arithmetic_shift.hpp; sourceTree = ""; }; C9DD180C21428AFED350A64C /* mptPathString.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = mptPathString.h; path = ../../common/mptPathString.h; sourceTree = ""; }; + C9E029A3906E2115FE7047E3 /* debugging.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = debugging.hpp; path = ../../src/mpt/base/debugging.hpp; sourceTree = ""; }; CA2995890E783AFB509F73C9 /* array.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = array.hpp; path = ../../src/mpt/base/array.hpp; sourceTree = ""; }; CA67A8613B8A4953F150F6A1 /* S3MTools.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = S3MTools.h; path = ../../soundlib/S3MTools.h; sourceTree = ""; }; CB4660EB0F95065D51BC3F2B /* pattern.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = pattern.cpp; path = ../../soundlib/pattern.cpp; sourceTree = ""; }; @@ -541,7 +578,9 @@ D35A7771C759C0E34C44D5B1 /* WavesReverb.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = WavesReverb.h; path = ../../soundlib/plugins/dmo/WavesReverb.h; sourceTree = ""; }; D37D7529660B3F1BCEE3E369 /* MIDIMacros.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = MIDIMacros.h; path = ../../soundlib/MIDIMacros.h; sourceTree = ""; }; D432AEE7E417DB19D08B6527 /* tests_parse.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = tests_parse.hpp; path = ../../src/mpt/parse/tests/tests_parse.hpp; sourceTree = ""; }; + D668A71B68F6710DD1CF155B /* Load_gmc.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Load_gmc.cpp; path = ../../soundlib/Load_gmc.cpp; sourceTree = ""; }; D6BBDE00997C3E72F6B11C40 /* LFOPlugin.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = LFOPlugin.cpp; path = ../../soundlib/plugins/LFOPlugin.cpp; sourceTree = ""; }; + D727124F69B4DC41D28D808F /* Load_ice.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Load_ice.cpp; path = ../../soundlib/Load_ice.cpp; sourceTree = ""; }; D89E49F2E528FC64600DC832 /* PlugInterface.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = PlugInterface.cpp; path = ../../soundlib/plugins/PlugInterface.cpp; sourceTree = ""; }; D941724BCA38E03D363C608B /* saturate_round.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = saturate_round.hpp; path = ../../src/mpt/base/saturate_round.hpp; sourceTree = ""; }; DA41BBEFE6CC6E6161B13A2F /* transcode.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = transcode.hpp; path = ../../src/mpt/string_transcode/transcode.hpp; sourceTree = ""; }; @@ -560,18 +599,21 @@ E2066CCD3D47C03F10A20B0D /* DSP.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = DSP.cpp; path = ../../sounddsp/DSP.cpp; sourceTree = ""; }; E276AB813DD6D033801A71C1 /* filedata_base_seekable.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = filedata_base_seekable.hpp; path = ../../src/mpt/io_read/filedata_base_seekable.hpp; sourceTree = ""; }; E2B3BCB34FDBCB25516EBAF3 /* random.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = random.hpp; path = ../../src/mpt/random/random.hpp; sourceTree = ""; }; + E38574ABA645D51D037AB2EB /* any_engine.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = any_engine.hpp; path = ../../src/mpt/random/any_engine.hpp; sourceTree = ""; }; E438935FD53001514133819F /* mod_specifications.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = mod_specifications.h; path = ../../soundlib/mod_specifications.h; sourceTree = ""; }; E4C0ABA9F677EB9B6BE5D9E9 /* libopenmpt_ext_impl.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = libopenmpt_ext_impl.hpp; path = ../../libopenmpt/libopenmpt_ext_impl.hpp; sourceTree = ""; }; E59C6F4DDF5E2BBF195D2D8D /* Loaders.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = Loaders.h; path = ../../soundlib/Loaders.h; sourceTree = ""; }; E64FF33AA9BBB4ECE85F597A /* openmpt-ogg.lib */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = "ogg.xcodeproj"; path = ext/ogg.xcodeproj; sourceTree = SOURCE_ROOT; }; E6905433791E1E25E1F6C273 /* SampleCopy.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SampleCopy.h; path = ../../soundlib/SampleCopy.h; sourceTree = ""; }; E7811C8A7A0EE67CE2E78ACA /* class.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = class.hpp; path = ../../src/mpt/osinfo/class.hpp; sourceTree = ""; }; + E7ACD57DAA6D35EF07A213BD /* MIDIMacroParser.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = MIDIMacroParser.h; path = ../../soundlib/MIDIMacroParser.h; sourceTree = ""; }; E898D5ABAF26CD1D1D28F3EB /* ModInstrument.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = ModInstrument.h; path = ../../soundlib/ModInstrument.h; sourceTree = ""; }; EABA2F1F4147999119C0AD5F /* AGC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = AGC.h; path = ../../sounddsp/AGC.h; sourceTree = ""; }; EC1456C17EA220B3E77AC501 /* Load_mid.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Load_mid.cpp; path = ../../soundlib/Load_mid.cpp; sourceTree = ""; }; ED3DBD7D7FCB876FE8A42BBD /* memory.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = memory.hpp; path = ../../src/mpt/base/memory.hpp; sourceTree = ""; }; + EDB72A5D8FA0C64F86C7D89D /* PlaybackTest.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = PlaybackTest.cpp; path = ../../soundlib/PlaybackTest.cpp; sourceTree = ""; }; + EDF8E6598086B04BE95F5499 /* Load_cba.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Load_cba.cpp; path = ../../soundlib/Load_cba.cpp; sourceTree = ""; }; EEC8D9B98156A3ABEA2F47F9 /* tuningbase.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = tuningbase.h; path = ../../soundlib/tuningbase.h; sourceTree = ""; }; - EF78F5224ABA48941E149362 /* Dither.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = Dither.h; path = ../../common/Dither.h; sourceTree = ""; }; F06C78E982FA42DBEBD2E729 /* RowVisitor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = RowVisitor.h; path = ../../soundlib/RowVisitor.h; sourceTree = ""; }; F1208C2D83AE561FEC86FA6D /* Load_psm.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Load_psm.cpp; path = ../../soundlib/Load_psm.cpp; sourceTree = ""; }; F18CD1F1E58C1B636A773031 /* saturate_cast.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = saturate_cast.hpp; path = ../../src/mpt/base/saturate_cast.hpp; sourceTree = ""; }; @@ -585,6 +627,7 @@ F48DAABB97F4D86DAF2A90FB /* filecursor_traits_memory.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = filecursor_traits_memory.hpp; path = ../../src/mpt/io_read/filecursor_traits_memory.hpp; sourceTree = ""; }; F5D2F247BC60E9B92A631087 /* libopenmpt.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = libopenmpt.hpp; path = ../../libopenmpt/libopenmpt.hpp; sourceTree = ""; }; F5D60F958863D987F13C7DD5 /* Load_dsm.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = Load_dsm.cpp; path = ../../soundlib/Load_dsm.cpp; sourceTree = ""; }; + F5DB217BB89B81ED15D05FBB /* type_traits.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = type_traits.hpp; path = ../../src/mpt/base/type_traits.hpp; sourceTree = ""; }; F61229996734CA8B1CFB77D9 /* guid.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = guid.hpp; path = ../../src/mpt/uuid/guid.hpp; sourceTree = ""; }; F6F37E8F68161F811DDCCCCF /* span.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = span.hpp; path = ../../src/mpt/base/span.hpp; sourceTree = ""; }; F78A378464B245F6664535C4 /* mptBaseTypes.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = mptBaseTypes.h; path = ../../common/mptBaseTypes.h; sourceTree = ""; }; @@ -592,12 +635,13 @@ F9D67691EACDE48356D164D1 /* SampleFormatFLAC.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = SampleFormatFLAC.cpp; path = ../../soundlib/SampleFormatFLAC.cpp; sourceTree = ""; }; FB5FB0E7EF5EFA59744A0F27 /* libopenmpt_ext.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = libopenmpt_ext.hpp; path = ../../libopenmpt/libopenmpt_ext.hpp; sourceTree = ""; }; FB635E3D352E14EFEA89647D /* filecursor_traits_filedata.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = filecursor_traits_filedata.hpp; path = ../../src/mpt/io_read/filecursor_traits_filedata.hpp; sourceTree = ""; }; + FB77AD1D689FBB8F6A32AB5D /* PlayState.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = PlayState.cpp; path = ../../soundlib/PlayState.cpp; sourceTree = ""; }; FBE197BEECD905B058DC85FE /* serialization_utils.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = serialization_utils.h; path = ../../common/serialization_utils.h; sourceTree = ""; }; FBF22E79CF82B5EBC2544CB9 /* libopenmpt_stream_callbacks_file_posix_lfs64.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = libopenmpt_stream_callbacks_file_posix_lfs64.h; path = ../../libopenmpt/libopenmpt_stream_callbacks_file_posix_lfs64.h; sourceTree = ""; }; + FC83067740D1ABE982F8E4B7 /* float.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = float.hpp; path = ../../src/mpt/base/float.hpp; sourceTree = ""; }; FC85D407DBA2EE39B7AC4A47 /* inputfile.hpp */ = {isa = PBXFileReference; lastKnownFileType = text; name = inputfile.hpp; path = ../../src/mpt/io_file/inputfile.hpp; sourceTree = ""; }; FED01DA32FB4159542CC4BE3 /* tuning.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = tuning.h; path = ../../soundlib/tuning.h; sourceTree = ""; }; FEDB3A1791690409FA41A857 /* Echo.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = Echo.h; path = ../../soundlib/plugins/dmo/Echo.h; sourceTree = ""; }; - FF7F8F60F37ED8D27869EDA0 /* mptFileTemporary.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; name = mptFileTemporary.cpp; path = ../../common/mptFileTemporary.cpp; sourceTree = ""; }; FFB3088F4401AE018628E6CF /* Resampler.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = Resampler.h; path = ../../soundlib/Resampler.h; sourceTree = ""; }; /* End PBXFileReference section */ @@ -713,7 +757,6 @@ 45D0E0DE525B9350CD405F1E /* DigiBoosterEcho.h */, D6BBDE00997C3E72F6B11C40 /* LFOPlugin.cpp */, 6858F8CA2EE6F03C9CE9170A /* LFOPlugin.h */, - 9133E604FE5BF476FFEEE444 /* OpCodes.h */, D89E49F2E528FC64600DC832 /* PlugInterface.cpp */, 6058487C545791EED942A6BC /* PlugInterface.h */, CE32D5B4DABD882655A253F4 /* PluginManager.cpp */, @@ -898,6 +941,7 @@ children = ( 64377F84A84B50B69C1CD5C4 /* compiler.hpp */, 417FE082D6F69FB44592F6C2 /* libc.hpp */, + 352971E219707A149D5DA822 /* libcxx.hpp */, 788395FAAA7CE0AC245C1C3A /* mfc.hpp */, B9B7FE84CBE1DB36FFD2C4C4 /* windows.hpp */, ); @@ -1030,9 +1074,9 @@ 9F2E1C1AF92F330CC0762A5A /* BuildSettingsCompiler.h */, 13F9789407F8C2068CE3D6D4 /* ComponentManager.cpp */, 0A20B0FECCE111702A15EF3E /* ComponentManager.h */, - EF78F5224ABA48941E149362 /* Dither.h */, 6D2350A8B171F61AF3992EE8 /* FileReader.h */, C6F2110A1E5783FCD0659F4A /* FileReaderFwd.h */, + B55D7A70F9AC1FE23BD358B0 /* GzipWriter.h */, 830F3566F431D658A9F883A6 /* Logging.cpp */, 6303157093E70D62A6FF43B0 /* Logging.h */, 8F52A05ED3A145D015C87E9E /* Profiler.cpp */, @@ -1043,9 +1087,7 @@ F78A378464B245F6664535C4 /* mptBaseTypes.h */, 12DCF57C800503EE8197F3BC /* mptBaseUtils.h */, BA99A2F415DAF666E9354134 /* mptCPU.h */, - 13F63AEAA68404DC0F5CA92A /* mptFileIO.cpp */, 50CF7EB4C1F21FA677B8CCF4 /* mptFileIO.h */, - FF7F8F60F37ED8D27869EDA0 /* mptFileTemporary.cpp */, 43CBFB4A068C5BBC63C1398A /* mptFileTemporary.h */, 074C76BE5EB1E9B010C004FE /* mptFileType.cpp */, AFFB10484288DA3AAB617E88 /* mptFileType.h */, @@ -1116,6 +1158,7 @@ 1AA25C6F0B99CA61779D4AAF /* check_platform.hpp */, 86260AE5E2AD0F576B4FC925 /* compiletime_warning.hpp */, 021DA33D0EA855AF898D217D /* constexpr_throw.hpp */, + C9E029A3906E2115FE7047E3 /* debugging.hpp */, C3FAC67D5688906FBF6134BD /* detect.hpp */, 1C2F81B7DEEFE2293C24BFF7 /* detect_arch.hpp */, 321030713E9AE2E3B97FAEB1 /* detect_compiler.hpp */, @@ -1123,7 +1166,7 @@ 755174CF6950BE41EE3BD30F /* detect_libcxx.hpp */, 175234BFDDE02C314BE252FF /* detect_os.hpp */, 107683B90475CD2B8960E1F9 /* detect_quirks.hpp */, - 4FB7A50743B6EE79C8A20347 /* floatingpoint.hpp */, + FC83067740D1ABE982F8E4B7 /* float.hpp */, 787945A7E5A15419E73443E7 /* integer.hpp */, C3E634155673FE07BF4CA255 /* macros.hpp */, 4614D43FB73775316CFE227F /* math.hpp */, @@ -1137,9 +1180,11 @@ D941724BCA38E03D363C608B /* saturate_round.hpp */, 85812219180EEC0B80E79059 /* secure.hpp */, 212A36FD7B2B4DEF4272453D /* semantic_version.hpp */, + 219A364192BCD73348838481 /* size.hpp */, A105879DAD903A0F287505DD /* source_location.hpp */, F6F37E8F68161F811DDCCCCF /* span.hpp */, 8BDA4A4F76C67041BEAC388F /* tests */, + F5DB217BB89B81ED15D05FBB /* type_traits.hpp */, 5609DBF3C331EA65C4C4DA33 /* utility.hpp */, AB10601718386E8919CB5E57 /* version.hpp */, 2C28EA4338B39CB5B3986883 /* wrapping_divide.hpp */, @@ -1258,25 +1303,35 @@ 778D494BBBDBEEBDFE03278B /* ITTools.cpp */, BBEF0B95B5B0C807EFAFC9D5 /* ITTools.h */, 567D9E616834DE53DDA2CCA1 /* InstrumentExtensions.cpp */, + 89879ACD7D86E43F0271F90D /* InstrumentSynth.cpp */, + 45A87D570868DDC9659DBB97 /* InstrumentSynth.h */, 9E091D790F2BBE6BC4F26BB9 /* IntMixer.h */, CB46E6335DD4B025C6AD5473 /* Load_667.cpp */, B569F9F747F7C3E9B0D06837 /* Load_669.cpp */, 37F80675CA85D067335E74B5 /* Load_amf.cpp */, 29DC06EFBC69D0E12542752F /* Load_ams.cpp */, B99E396D4C2C035FB504A7AD /* Load_c67.cpp */, + EDF8E6598086B04BE95F5499 /* Load_cba.cpp */, 6A66E753FCF4B14565CD5593 /* Load_dbm.cpp */, D14936A73E714519400434E7 /* Load_digi.cpp */, 36CAA59BC9586F8D323113DB /* Load_dmf.cpp */, F5D60F958863D987F13C7DD5 /* Load_dsm.cpp */, 9BE2D067090ADED90A9DCEA7 /* Load_dsym.cpp */, 0D18D5B79FA69FA9087F43F7 /* Load_dtm.cpp */, + 14754CCFA70316C10FDBBB0F /* Load_etx.cpp */, 9BB2E75F2E40B1519719559F /* Load_far.cpp */, + B6BBD03FFB0A75B13D31AE7F /* Load_fc.cpp */, 9CF744BB2F850EAD985DB2FB /* Load_fmt.cpp */, + 0C4FEA7B9EDDB46D07B658BB /* Load_ftm.cpp */, 97BF12BD2A4CDCAF932580FD /* Load_gdm.cpp */, + D668A71B68F6710DD1CF155B /* Load_gmc.cpp */, 10E0ADC7A36E77B90C471C07 /* Load_gt2.cpp */, + D727124F69B4DC41D28D808F /* Load_ice.cpp */, 34D45985C7622377303AC7C5 /* Load_imf.cpp */, + 26B859FFB94623F1221EC83F /* Load_ims.cpp */, 62235C27A6720199E8993A67 /* Load_it.cpp */, 6A572747FCE4F13965BD9587 /* Load_itp.cpp */, + 19B6191F86DE27918871175F /* Load_kris.cpp */, 2052C727B2E091191BB93567 /* Load_mdl.cpp */, 8F093E392197082B8A6FAC79 /* Load_med.cpp */, EC1456C17EA220B3E77AC501 /* Load_mid.cpp */, @@ -1288,22 +1343,32 @@ 6AE995E9FD775FDB66500429 /* Load_okt.cpp */, 4E4D213FE0DAEB3149B38F7F /* Load_plm.cpp */, F1208C2D83AE561FEC86FA6D /* Load_psm.cpp */, + BE474F872B6F5DF92D024DC7 /* Load_pt36.cpp */, 0863524F9AF11C4103C9C08F /* Load_ptm.cpp */, + 6DB68B53DADE99C5DC718993 /* Load_puma.cpp */, + 079A67139A2831050300D553 /* Load_rtm.cpp */, 1F41A2D3B1CF6CC51AA81113 /* Load_s3m.cpp */, C950084F5BDDD241C4B6768F /* Load_sfx.cpp */, + 1D12DDB1AFA0A7A318794BF1 /* Load_stk.cpp */, 0735F17599C3BB67029C5FB5 /* Load_stm.cpp */, 666A8F1BF8F8590D61D0FD5B /* Load_stp.cpp */, 7D070D1F43950491B1972B5F /* Load_symmod.cpp */, + 73A166DF062F30D16F07D51F /* Load_tcb.cpp */, 54393E69E6C7085B4F9FACA9 /* Load_uax.cpp */, 7FD19A57125F64497B380897 /* Load_ult.cpp */, + 9EEAF28B0C1300FD0DA5F0CB /* Load_unic.cpp */, 694D3F69FBDB095B64B3ADA9 /* Load_wav.cpp */, 2A6435F76EB2DB69B0DA1437 /* Load_xm.cpp */, 2EF17543C17F3F352A57E383 /* Load_xmf.cpp */, E59C6F4DDF5E2BBF195D2D8D /* Loaders.h */, A78B5E5FFEF0D151B0FEEC9F /* MIDIEvents.cpp */, F42AAB0986B874FBEF911949 /* MIDIEvents.h */, + C61BC533BA1B0EA53F062373 /* MIDIMacroParser.cpp */, + E7ACD57DAA6D35EF07A213BD /* MIDIMacroParser.h */, 50FB747FA860E7715A6F02BF /* MIDIMacros.cpp */, D37D7529660B3F1BCEE3E369 /* MIDIMacros.h */, + 99E1AAB12C6F74A3954818F1 /* MODTools.cpp */, + 8209AB5BF32C4C4DA8F2F99B /* MODTools.h */, BF406A572C6878C92DFB6897 /* MPEGFrame.cpp */, A0ADCD61E4FC72D32723ABA1 /* MPEGFrame.h */, 681E06B9AC6CAC2BEE93E4F9 /* Message.cpp */, @@ -1331,6 +1396,10 @@ F1E743CB3635E93D785D220B /* OggStream.h */, A7A09A55A16256C7DB615895 /* Paula.cpp */, 1AD54F9F7616A3114970EDDF /* Paula.h */, + FB77AD1D689FBB8F6A32AB5D /* PlayState.cpp */, + A35EA1E7E7AD475929D48027 /* PlayState.h */, + EDB72A5D8FA0C64F86C7D89D /* PlaybackTest.cpp */, + 042BC5075B9137F90D9F5347 /* PlaybackTest.h */, FFB3088F4401AE018628E6CF /* Resampler.h */, 1A88E03F71EE533123FC6E7F /* RowVisitor.cpp */, F06C78E982FA42DBEBD2E729 /* RowVisitor.h */, @@ -1406,6 +1475,7 @@ F76908210E7BA353FE979E61 /* random */ = { isa = PBXGroup; children = ( + E38574ABA645D51D037AB2EB /* any_engine.hpp */, 8DE932C12076FCB3894FA101 /* crand.hpp */, 8876678BE2777E7DA9BE75CB /* default_engines.hpp */, 1CE2C4318A0AD2A38B9DC271 /* device.hpp */, @@ -1434,6 +1504,7 @@ 23EF16CF7F4F3B81C192DD0F /* filedata_base_buffered.hpp */, E276AB813DD6D033801A71C1 /* filedata_base_seekable.hpp */, 8ABDA3472E24D0F9455A8987 /* filedata_base_unseekable.hpp */, + 1BD71CF93D735A2B35E9F339 /* filedata_base_unseekable_buffer.hpp */, E0CF51E794CB6B19767DA827 /* filedata_callbackstream.hpp */, 079DA5A7A1C19AD951E77BE7 /* filedata_memory.hpp */, CE271C83BB8EAF355888A2C3 /* filedata_stdstream.hpp */, @@ -1557,8 +1628,6 @@ B591ED4C6887F6FEB9AA538C /* ComponentManager.cpp in Sources */, 527E833E969254708A63D97E /* Logging.cpp in Sources */, 1CE1B0B6B52E9668E37596F6 /* Profiler.cpp in Sources */, - 1CD4D902FBF1F334D7FB4F42 /* mptFileIO.cpp in Sources */, - BB65D2D86E5BDC8ABF7E3918 /* mptFileTemporary.cpp in Sources */, 1F7082161F13654859681856 /* mptFileType.cpp in Sources */, DF08AE5AEEEDDA8CDB61649A /* mptPathString.cpp in Sources */, 926864F471857F264D8EDB34 /* mptRandom.cpp in Sources */, @@ -1584,24 +1653,33 @@ 32D83065D0B93117A83776A5 /* ITCompression.cpp in Sources */, E6583D037EA522B5ACEC2343 /* ITTools.cpp in Sources */, 4FD75379AB22A3AB995489B9 /* InstrumentExtensions.cpp in Sources */, + C6B55BA579AB6557CACDC1E5 /* InstrumentSynth.cpp in Sources */, 5EE7936B3E04AD9D1A0E09AB /* Load_667.cpp in Sources */, 4E13C16F2D30DBA1093A37AF /* Load_669.cpp in Sources */, A7B576CD86D290FF62DBED0D /* Load_amf.cpp in Sources */, BA54A1E79971BC19757B1827 /* Load_ams.cpp in Sources */, 44F9DD452416F77700205385 /* Load_c67.cpp in Sources */, + 2AE422F10A013D23E60A9931 /* Load_cba.cpp in Sources */, 7B65168B5A8230BD368B8CCB /* Load_dbm.cpp in Sources */, 8479AC1F9F461AD1A316B25F /* Load_digi.cpp in Sources */, C81D1553A73A2F8583438B93 /* Load_dmf.cpp in Sources */, 8B4D81ED6A6A9C1F4673F82D /* Load_dsm.cpp in Sources */, D2EFC1DFEDBC3091F18CC81F /* Load_dsym.cpp in Sources */, F5A6792FD4C39361B0CCEF6F /* Load_dtm.cpp in Sources */, + CE9155C7ADAE6FF989B7CC07 /* Load_etx.cpp in Sources */, D1EA5157B1076B898D10C797 /* Load_far.cpp in Sources */, + C3D725375C240AE98A6B0B77 /* Load_fc.cpp in Sources */, BD4216739C5F30A578688CB3 /* Load_fmt.cpp in Sources */, + 609638333FB352651BBCAE73 /* Load_ftm.cpp in Sources */, 707EA3954F9BBDC72BA519D5 /* Load_gdm.cpp in Sources */, + 81C26ED360DF89053CE8E513 /* Load_gmc.cpp in Sources */, 8676CA3F6593E471419D407F /* Load_gt2.cpp in Sources */, + B464B3479381CD796F8B2987 /* Load_ice.cpp in Sources */, 537472DD32918D0F0E9AE91D /* Load_imf.cpp in Sources */, + 66139DF74530B829213A1437 /* Load_ims.cpp in Sources */, 7FDA539F18273951466E39DF /* Load_it.cpp in Sources */, E7C01BBFC6DD35F1A2E691FF /* Load_itp.cpp in Sources */, + C806B217E2D320C9E6A3B857 /* Load_kris.cpp in Sources */, 39B7C99F18D4E3D1F4DE3FDF /* Load_mdl.cpp in Sources */, E76008D1C67D2303A2867F11 /* Load_med.cpp in Sources */, 90C3E5D96FE1000B4BEA5C19 /* Load_mid.cpp in Sources */, @@ -1613,19 +1691,27 @@ 49C7038128E41DB304ED79C1 /* Load_okt.cpp in Sources */, 247D3937039A5369DFA3AF77 /* Load_plm.cpp in Sources */, 0CEBFC05EC091637C8127245 /* Load_psm.cpp in Sources */, + 3E00C2FF58CD31B15C9DC93F /* Load_pt36.cpp in Sources */, 7744F34756620D79326B6987 /* Load_ptm.cpp in Sources */, + 877C818BA248F03DA61987CB /* Load_puma.cpp in Sources */, + E234B24BC151CC7D9D5B288B /* Load_rtm.cpp in Sources */, 9715CA0B7632E43D523C404B /* Load_s3m.cpp in Sources */, EA420947C95F2379A5687F87 /* Load_sfx.cpp in Sources */, + A88063C9879D7DFB63A6DA09 /* Load_stk.cpp in Sources */, 97AC91CD76C9ABFF52D3080D /* Load_stm.cpp in Sources */, FE6ED6D3DD8BF105B9954D13 /* Load_stp.cpp in Sources */, CF116C1742F9E3C965479257 /* Load_symmod.cpp in Sources */, + 19C908D7F8E62309D4EF7F17 /* Load_tcb.cpp in Sources */, 4174F40120920E33FC9B6A41 /* Load_uax.cpp in Sources */, F4EF37CFD40C5201B015AE0F /* Load_ult.cpp in Sources */, + 84F9B8439FC626F5A396BE83 /* Load_unic.cpp in Sources */, BD3885019C559F33785EFB41 /* Load_wav.cpp in Sources */, 00BB926F99087821C74F78AF /* Load_xm.cpp in Sources */, F57A8B7BD497A5ADB0A101BB /* Load_xmf.cpp in Sources */, CCEFFE57CC92E18906E79497 /* MIDIEvents.cpp in Sources */, + 212E136BD4241D1D254679AB /* MIDIMacroParser.cpp in Sources */, 80EEA677809189A9BAE63CB7 /* MIDIMacros.cpp in Sources */, + 978580C976A29AFB52ABF709 /* MODTools.cpp in Sources */, 393D72CF5409E18157DA790F /* MPEGFrame.cpp in Sources */, 168C3C51AED92203DD202291 /* Message.cpp in Sources */, 7A5E5DAB8A4389DD76B713EB /* MixFuncTable.cpp in Sources */, @@ -1638,6 +1724,8 @@ ABC5779D79E8CB4F00F71DDD /* OPL.cpp in Sources */, FD083C9917D4AB4B1BA542D9 /* OggStream.cpp in Sources */, 33DB2DAD46050A5F79F5F3ED /* Paula.cpp in Sources */, + 98C2B4F5B38F23A7B75FBB35 /* PlayState.cpp in Sources */, + 9580A935A565D56791D95F75 /* PlaybackTest.cpp in Sources */, 119C6E37113F51694B940477 /* RowVisitor.cpp in Sources */, D443016FB3601BA18F6977AF /* S3MTools.cpp in Sources */, 77A73F692A9D491B7BBFA5A9 /* SampleFormatBRR.cpp in Sources */, diff --git a/Frameworks/OpenMPT/OpenMPT/common/BuildSettings.h b/Frameworks/OpenMPT/OpenMPT/common/BuildSettings.h index 0e38ab298..5d7b0ea20 100644 --- a/Frameworks/OpenMPT/OpenMPT/common/BuildSettings.h +++ b/Frameworks/OpenMPT/OpenMPT/common/BuildSettings.h @@ -88,6 +88,9 @@ #define ENABLE_TESTS #endif +// Enable generation and verification of playback traces +#define MPT_ENABLE_PLAYBACK_TRACE + // Disable any file saving functionality (not really useful except for the player library) //#define MODPLUG_NO_FILESAVE @@ -119,6 +122,10 @@ #define MPT_ENABLE_UPDATE +#if defined(MPT_BUILD_DEBUG) +#define MPT_ENABLE_PLAYBACK_TEST_MENU +#endif + // Disable unarchiving support //#define NO_ARCHIVE_SUPPORT @@ -157,6 +164,7 @@ #if defined(LIBOPENMPT_BUILD_TEST) #define ENABLE_TESTS +#define MPT_ENABLE_PLAYBACK_TRACE #else #define MODPLUG_NO_FILESAVE #endif @@ -230,19 +238,19 @@ #endif #if defined(MPT_ENABLE_ARCH_INTRINSICS) -#if MPT_COMPILER_MSVC && MPT_ARCH_X86 +#if MPT_ARCH_X86 #define MPT_ENABLE_ARCH_X86 -#define MPT_ENABLE_ARCH_INTRINSICS_SSE -#define MPT_ENABLE_ARCH_INTRINSICS_SSE2 +#define MPT_WANT_ARCH_INTRINSICS_X86_SSE +#define MPT_WANT_ARCH_INTRINSICS_X86_SSE2 -#elif MPT_COMPILER_MSVC && MPT_ARCH_AMD64 +#elif MPT_ARCH_AMD64 #define MPT_ENABLE_ARCH_AMD64 -#define MPT_ENABLE_ARCH_INTRINSICS_SSE -#define MPT_ENABLE_ARCH_INTRINSICS_SSE2 +#define MPT_WANT_ARCH_INTRINSICS_X86_SSE +#define MPT_WANT_ARCH_INTRINSICS_X86_SSE2 #endif // arch #endif // MPT_ENABLE_ARCH_INTRINSICS @@ -327,6 +335,8 @@ #endif +#define MPT_CONFIGURATION_IO_READ_FILEDATA_NO_64BIT + // platform configuration diff --git a/Frameworks/OpenMPT/OpenMPT/common/ComponentManager.cpp b/Frameworks/OpenMPT/OpenMPT/common/ComponentManager.cpp index 4c558ddcf..b25c0c485 100644 --- a/Frameworks/OpenMPT/OpenMPT/common/ComponentManager.cpp +++ b/Frameworks/OpenMPT/OpenMPT/common/ComponentManager.cpp @@ -463,12 +463,6 @@ ComponentInfo ComponentManager::GetComponentInfo(std::string name) const } -mpt::PathString ComponentManager::GetComponentPath() const -{ - return m_Settings.Path(); -} - - #endif // MPT_COMPONENT_MANAGER diff --git a/Frameworks/OpenMPT/OpenMPT/common/ComponentManager.h b/Frameworks/OpenMPT/OpenMPT/common/ComponentManager.h index 201945d79..170a832c2 100644 --- a/Frameworks/OpenMPT/OpenMPT/common/ComponentManager.h +++ b/Frameworks/OpenMPT/OpenMPT/common/ComponentManager.h @@ -308,7 +308,6 @@ public: virtual bool LoadOnStartup() const = 0; virtual bool KeepLoaded() const = 0; virtual bool IsBlocked(const std::string &key) const = 0; - virtual mpt::PathString Path() const = 0; protected: virtual ~IComponentManagerSettings() = default; }; @@ -321,7 +320,6 @@ public: bool LoadOnStartup() const override { return false; } bool KeepLoaded() const override { return true; } bool IsBlocked(const std::string & /*key*/ ) const override { return false; } - mpt::PathString Path() const override { return mpt::PathString(); } }; @@ -374,7 +372,6 @@ public: std::shared_ptr ReloadComponent(const IComponentFactory &componentFactory); std::vector GetRegisteredComponents() const; ComponentInfo GetComponentInfo(std::string name) const; - mpt::PathString GetComponentPath() const; }; @@ -423,12 +420,6 @@ std::shared_ptr ReloadComponent() } -inline mpt::PathString GetComponentPath() -{ - return ComponentManager::Instance()->GetComponentPath(); -} - - #else // !MPT_COMPONENT_MANAGER @@ -438,8 +429,15 @@ inline mpt::PathString GetComponentPath() template std::shared_ptr GetComponent() { +#if MPT_COMPILER_CLANG +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wexit-time-destructors" +#endif // MPT_COMPILER_CLANG static std::weak_ptr cache; static mpt::mutex m; +#if MPT_COMPILER_CLANG +#pragma clang diagnostic pop +#endif // MPT_COMPILER_CLANG mpt::lock_guard l(m); mpt::lock_guard l(m); std::shared_ptr component = cache.lock(); if(!component) diff --git a/Frameworks/OpenMPT/OpenMPT/common/Dither.h b/Frameworks/OpenMPT/OpenMPT/common/Dither.h deleted file mode 100644 index 9790aeca5..000000000 --- a/Frameworks/OpenMPT/OpenMPT/common/Dither.h +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Dither.h - * -------- - * Purpose: Dithering when converting to lower resolution sample formats. - * Notes : (currently none) - * Authors: Olivier Lapicque - * OpenMPT Devs - * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. - */ - - -#pragma once - -#include "openmpt/all/BuildSettings.hpp" - -#include "mpt/base/macros.hpp" -#include "mpt/string/types.hpp" -#include "openmpt/soundbase/Dither.hpp" -#include "openmpt/soundbase/DitherModPlug.hpp" -#include "openmpt/soundbase/DitherNone.hpp" -#include "openmpt/soundbase/DitherSimple.hpp" - -#include "mptRandom.h" - -#include -#include - -#include - - -OPENMPT_NAMESPACE_BEGIN - - -using Dither_Default = Dither_Simple; - - -class DitherNamesOpenMPT -{ -public: - static mpt::ustring GetModeName(std::size_t mode) - { - mpt::ustring result; - switch(mode) - { - case 0: - // no dither - result = MPT_USTRING("no"); - break; - case 1: - // chosen by OpenMPT code, might change - result = MPT_USTRING("default"); - break; - case 2: - // rectangular, 0.5 bit depth, no noise shaping (original ModPlug Tracker) - result = MPT_USTRING("0.5 bit"); - break; - case 3: - // rectangular, 1 bit depth, simple 1st order noise shaping - result = MPT_USTRING("1 bit"); - break; - default: - result = MPT_USTRING(""); - break; - } - return result; - } -}; - - -using DithersOpenMPT = - Dithers, MultiChannelDither, MultiChannelDither, MultiChannelDither>, DitherNamesOpenMPT, 4, 1, 0, mpt::good_prng>; - - -struct DithersWrapperOpenMPT - : DithersOpenMPT -{ - template - DithersWrapperOpenMPT(Trd &rd, std::size_t mode = DithersOpenMPT::DefaultDither, std::size_t channels = DithersOpenMPT::DefaultChannels) - : DithersOpenMPT(rd, mode, channels) - { - return; - } -}; - - -OPENMPT_NAMESPACE_END diff --git a/Frameworks/OpenMPT/OpenMPT/common/FileReader.h b/Frameworks/OpenMPT/OpenMPT/common/FileReader.h index 646c2282f..0e095bb43 100644 --- a/Frameworks/OpenMPT/OpenMPT/common/FileReader.h +++ b/Frameworks/OpenMPT/OpenMPT/common/FileReader.h @@ -45,11 +45,11 @@ namespace FileReaderExt // Read a string of length srcSize into fixed-length char array destBuffer using a given read mode. // The file cursor is advanced by "srcSize" bytes. // Returns true if at least one byte could be read or 0 bytes were requested. - template - bool ReadString(TFileCursor &f, char (&destBuffer)[destSize], const typename TFileCursor::pos_type srcSize) + template + bool ReadString(TFileCursor &f, char (&destBuffer)[destSize], const std::size_t srcSize) { typename TFileCursor::PinnedView source = f.ReadPinnedView(srcSize); // Make sure the string is cached properly. - typename TFileCursor::pos_type realSrcSize = source.size(); // In case fewer bytes are available + std::size_t realSrcSize = source.size(); // In case fewer bytes are available mpt::String::WriteAutoBuf(destBuffer) = mpt::String::ReadBuf(mode, mpt::byte_cast(source.data()), realSrcSize); return (realSrcSize > 0 || srcSize == 0); } @@ -58,11 +58,11 @@ namespace FileReaderExt // The file cursor is advanced by "srcSize" bytes. // Returns true if at least one character could be read or 0 characters were requested. template - bool ReadString(TFileCursor &f, std::string &dest, const typename TFileCursor::pos_type srcSize) + bool ReadString(TFileCursor &f, std::string &dest, const std::size_t srcSize) { dest.clear(); typename TFileCursor::PinnedView source = f.ReadPinnedView(srcSize); // Make sure the string is cached properly. - typename TFileCursor::pos_type realSrcSize = source.size(); // In case fewer bytes are available + std::size_t realSrcSize = source.size(); // In case fewer bytes are available dest = mpt::String::ReadBuf(mode, mpt::byte_cast(source.data()), realSrcSize); return (realSrcSize > 0 || srcSize == 0); } @@ -71,10 +71,10 @@ namespace FileReaderExt // The file cursor is advanced by "srcSize" bytes. // Returns true if at least one character could be read or 0 characters were requested. template - bool ReadString(TFileCursor &f, mpt::charbuf &dest, const typename TFileCursor::pos_type srcSize) + bool ReadString(TFileCursor &f, mpt::charbuf &dest, const std::size_t srcSize) { typename TFileCursor::PinnedView source = f.ReadPinnedView(srcSize); // Make sure the string is cached properly. - typename TFileCursor::pos_type realSrcSize = source.size(); // In case fewer bytes are available + std::size_t realSrcSize = source.size(); // In case fewer bytes are available dest = mpt::String::ReadBuf(mode, mpt::byte_cast(source.data()), realSrcSize); return (realSrcSize > 0 || srcSize == 0); } @@ -83,11 +83,11 @@ namespace FileReaderExt // The file cursor is advanced by "srcSize" bytes. // Returns true if at least one character could be read or 0 characters were requested. template - bool ReadString(TFileCursor &f, mpt::ustring &dest, mpt::Charset charset, const typename TFileCursor::pos_type srcSize) + bool ReadString(TFileCursor &f, mpt::ustring &dest, mpt::Charset charset, const std::size_t srcSize) { dest.clear(); typename TFileCursor::PinnedView source = f.ReadPinnedView(srcSize); // Make sure the string is cached properly. - typename TFileCursor::pos_type realSrcSize = source.size(); // In case fewer bytes are available + std::size_t realSrcSize = source.size(); // In case fewer bytes are available dest = mpt::ToUnicode(charset, mpt::String::ReadBuf(mode, mpt::byte_cast(source.data()), realSrcSize)); return (realSrcSize > 0 || srcSize == 0); } @@ -95,8 +95,8 @@ namespace FileReaderExt // Read a string with a preprended length field of type Tsize (must be a packed<*,*> type) into a std::string dest using a given read mode. // The file cursor is advanced by the string length. // Returns true if the size field could be read and at least one character could be read or 0 characters were requested. - template - bool ReadSizedString(TFileCursor &f, char (&destBuffer)[destSize], const typename TFileCursor::pos_type maxLength = std::numeric_limits::max()) + template + bool ReadSizedString(TFileCursor &f, char (&destBuffer)[destSize], const std::size_t maxLength = std::numeric_limits::max()) { static_assert(mpt::is_binary_safe::value); Tsize srcSize; @@ -104,14 +104,14 @@ namespace FileReaderExt { return false; } - return FileReaderExt::ReadString(f, destBuffer, std::min(static_cast(srcSize), maxLength)); + return FileReaderExt::ReadString(f, destBuffer, std::min(static_cast(srcSize), maxLength)); } // Read a string with a preprended length field of type Tsize (must be a packed<*,*> type) into a std::string dest using a given read mode. // The file cursor is advanced by the string length. // Returns true if the size field could be read and at least one character could be read or 0 characters were requested. template - bool ReadSizedString(TFileCursor &f, std::string &dest, const typename TFileCursor::pos_type maxLength = std::numeric_limits::max()) + bool ReadSizedString(TFileCursor &f, std::string &dest, const std::size_t maxLength = std::numeric_limits::max()) { static_assert(mpt::is_binary_safe::value); Tsize srcSize; @@ -119,14 +119,14 @@ namespace FileReaderExt { return false; } - return FileReaderExt::ReadString(f, dest, std::min(static_cast(srcSize), maxLength)); + return FileReaderExt::ReadString(f, dest, std::min(static_cast(srcSize), maxLength)); } // Read a string with a preprended length field of type Tsize (must be a packed<*,*> type) into a mpt::charbuf dest using a given read mode. // The file cursor is advanced by the string length. // Returns true if the size field could be read and at least one character could be read or 0 characters were requested. template - bool ReadSizedString(TFileCursor &f, mpt::charbuf &dest, const typename TFileCursor::pos_type maxLength = std::numeric_limits::max()) + bool ReadSizedString(TFileCursor &f, mpt::charbuf &dest, const std::size_t maxLength = std::numeric_limits::max()) { static_assert(mpt::is_binary_safe::value); Tsize srcSize; @@ -134,7 +134,7 @@ namespace FileReaderExt { return false; } - return FileReaderExt::ReadString(f, dest, std::min(static_cast(srcSize), maxLength)); + return FileReaderExt::ReadString(f, dest, std::min(static_cast(srcSize), maxLength)); } } // namespace FileReaderExt @@ -157,7 +157,6 @@ private: public: using pos_type = typename traits_type::pos_type; - using off_t = pos_type; using data_type = typename traits_type::data_type; using ref_data_type = typename traits_type::ref_data_type; @@ -221,13 +220,13 @@ public: } template - T ReadTruncatedIntLE(pos_type size) + T ReadTruncatedIntLE(std::size_t size) { return mpt::IO::FileReader::ReadTruncatedIntLE(*this, size); } template - T ReadSizedIntLE(pos_type size) + T ReadSizedIntLE(std::size_t size) { return mpt::IO::FileReader::ReadSizedIntLE(*this, size); } @@ -324,17 +323,17 @@ public: } template - size_t ReadStructPartial(T &target, size_t partialSize = sizeof(T)) + std::size_t ReadStructPartial(T &target, std::size_t partialSize = sizeof(T)) { return mpt::IO::FileReader::ReadStructPartial(*this, target, partialSize); } - bool ReadNullString(std::string &dest, const pos_type maxLength = std::numeric_limits::max()) + bool ReadNullString(std::string &dest, const std::size_t maxLength = std::numeric_limits::max()) { return mpt::IO::FileReader::ReadNullString(*this, dest, maxLength); } - bool ReadLine(std::string &dest, const pos_type maxLength = std::numeric_limits::max()) + bool ReadLine(std::string &dest, const std::size_t maxLength = std::numeric_limits::max()) { return mpt::IO::FileReader::ReadLine(*this, dest, maxLength); } @@ -358,12 +357,12 @@ public: } template - bool ReadVector(std::vector &destVector, size_t destSize) + bool ReadVector(std::vector &destVector, std::size_t destSize) { return mpt::IO::FileReader::ReadVector(*this, destVector, destSize); } - template + template bool ReadMagic(const char (&magic)[N]) { return mpt::IO::FileReader::ReadMagic(*this, magic); @@ -382,61 +381,61 @@ public: using ChunkList = mpt::IO::FileReader::ChunkList; template - Item ReadNextChunk(off_t alignment) + Item ReadNextChunk(pos_type alignment) { return mpt::IO::FileReader::ReadNextChunk(*this, alignment); } template - ChunkList ReadChunks(off_t alignment) + ChunkList ReadChunks(pos_type alignment) { return mpt::IO::FileReader::ReadChunks(*this, alignment); } template - ChunkList ReadChunksUntil(off_t alignment, decltype(T().GetID()) stopAtID) + ChunkList ReadChunksUntil(pos_type alignment, decltype(T().GetID()) stopAtID) { return mpt::IO::FileReader::ReadChunksUntil(*this, alignment, stopAtID); } - template - bool ReadString(char (&destBuffer)[destSize], const pos_type srcSize) + template + bool ReadString(char (&destBuffer)[destSize], const std::size_t srcSize) { return FileReaderExt::ReadString(*this, destBuffer, srcSize); } template - bool ReadString(std::string &dest, const pos_type srcSize) + bool ReadString(std::string &dest, const std::size_t srcSize) { return FileReaderExt::ReadString(*this, dest, srcSize); } template - bool ReadString(mpt::charbuf &dest, const pos_type srcSize) + bool ReadString(mpt::charbuf &dest, const std::size_t srcSize) { return FileReaderExt::ReadString(*this, dest, srcSize); } template - bool ReadString(mpt::ustring &dest, mpt::Charset charset, const pos_type srcSize) + bool ReadString(mpt::ustring &dest, mpt::Charset charset, const std::size_t srcSize) { return FileReaderExt::ReadString(*this, dest, charset, srcSize); } - template - bool ReadSizedString(char (&destBuffer)[destSize], const pos_type maxLength = std::numeric_limits::max()) + template + bool ReadSizedString(char (&destBuffer)[destSize], const std::size_t maxLength = std::numeric_limits::max()) { return FileReaderExt::ReadSizedString(*this, destBuffer, maxLength); } template - bool ReadSizedString(std::string &dest, const pos_type maxLength = std::numeric_limits::max()) + bool ReadSizedString(std::string &dest, const std::size_t maxLength = std::numeric_limits::max()) { return FileReaderExt::ReadSizedString(*this, dest, maxLength); } template - bool ReadSizedString(mpt::charbuf &dest, const pos_type maxLength = std::numeric_limits::max()) + bool ReadSizedString(mpt::charbuf &dest, const std::size_t maxLength = std::numeric_limits::max()) { return FileReaderExt::ReadSizedString(*this, dest, maxLength); } diff --git a/Frameworks/OpenMPT/OpenMPT/common/FileReaderFwd.h b/Frameworks/OpenMPT/OpenMPT/common/FileReaderFwd.h index fc2a1221e..b3f989db0 100644 --- a/Frameworks/OpenMPT/OpenMPT/common/FileReaderFwd.h +++ b/Frameworks/OpenMPT/OpenMPT/common/FileReaderFwd.h @@ -36,10 +36,10 @@ class FileCursorFilenameTraits; template class FileCursor; -} +} // namespace IO -} -} +} // inline namespace MPT_INLINE_NS +} // namespace mpt OPENMPT_NAMESPACE_BEGIN diff --git a/Frameworks/OpenMPT/OpenMPT/common/GzipWriter.h b/Frameworks/OpenMPT/OpenMPT/common/GzipWriter.h new file mode 100644 index 000000000..ca3ade7de --- /dev/null +++ b/Frameworks/OpenMPT/OpenMPT/common/GzipWriter.h @@ -0,0 +1,57 @@ +/* + * GzipWriter.h + * ------------ + * Purpose: Simple wrapper around zlib's Gzip writer + * Notes : miniz doesn't implement Gzip writing, so this is only compatible with zlib. + * Authors: OpenMPT Devs + * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. + */ + + +#pragma once + +#include "openmpt/all/BuildSettings.hpp" + +#include "mptString.h" +#include "mpt/io/io.hpp" +#include "mpt/io/io_stdstream.hpp" + +#ifdef MPT_WITH_ZLIB + +#include + +OPENMPT_NAMESPACE_BEGIN + +inline void WriteGzip(std::ostream &output, std::string &outData, const mpt::ustring &fileName) +{ + z_stream strm{}; + strm.avail_in = static_cast(outData.size()); + strm.next_in = reinterpret_cast(outData.data()); + if(deflateInit2(&strm, Z_BEST_COMPRESSION, Z_DEFLATED, 15 | 16, 9, Z_DEFAULT_STRATEGY) != Z_OK) + throw std::runtime_error{"zlib init failed"}; + gz_header gzHeader{}; + gzHeader.time = static_cast(time(nullptr)); + std::string filenameISO = mpt::ToCharset(mpt::Charset::ISO8859_1, fileName); + gzHeader.name = reinterpret_cast(filenameISO.data()); + deflateSetHeader(&strm, &gzHeader); + try + { + do + { + std::array buffer; + strm.avail_out = static_cast(buffer.size()); + strm.next_out = buffer.data(); + deflate(&strm, Z_FINISH); + mpt::IO::WritePartial(output, buffer, buffer.size() - strm.avail_out); + } while(strm.avail_out == 0); + deflateEnd(&strm); + } catch(const std::exception &) + { + deflateEnd(&strm); + throw; + } +} + +OPENMPT_NAMESPACE_END + +#endif diff --git a/Frameworks/OpenMPT/OpenMPT/common/Logging.cpp b/Frameworks/OpenMPT/OpenMPT/common/Logging.cpp index 2ef1b7bcb..1c31463b3 100644 --- a/Frameworks/OpenMPT/OpenMPT/common/Logging.cpp +++ b/Frameworks/OpenMPT/OpenMPT/common/Logging.cpp @@ -16,8 +16,14 @@ #include "mpt/io/base.hpp" #include "mpt/io/io.hpp" #include "mpt/io/io_stdstream.hpp" +#if defined(MODPLUG_TRACKER) +#include "mpt/io_file/fstream.hpp" +#endif +#if defined(MODPLUG_TRACKER) #include "mptFileIO.h" +#endif + #if defined(MODPLUG_TRACKER) #include #endif @@ -128,7 +134,7 @@ void GlobalLogger::SendLogMessage(const mpt::source_location &loc, LogLevel leve #endif if(mpt::log::FileEnabled) { - static std::optional s_logfile; + static std::optional s_logfile; if(!s_logfile) { s_logfile.emplace(P_("mptrack.log"), std::ios::app); @@ -318,7 +324,7 @@ bool Dump(const mpt::PathString &filename) // sort according to index in case of overflows std::stable_sort(Entries.begin(), Entries.end()); - mpt::ofstream f(filename); + mpt::IO::ofstream f(filename); f << "Build: OpenMPT " << mpt::transcode(mpt::logfile_encoding, Build::GetVersionStringExtended()) << std::endl; diff --git a/Frameworks/OpenMPT/OpenMPT/common/mptBaseTypes.h b/Frameworks/OpenMPT/OpenMPT/common/mptBaseTypes.h index 85fc5528e..1bbdd9532 100644 --- a/Frameworks/OpenMPT/OpenMPT/common/mptBaseTypes.h +++ b/Frameworks/OpenMPT/OpenMPT/common/mptBaseTypes.h @@ -12,10 +12,11 @@ #include "openmpt/all/BuildSettings.hpp" -#include "mpt/base/integer.hpp" -#include "mpt/base/floatingpoint.hpp" -#include "mpt/base/pointer.hpp" #include "mpt/base/check_platform.hpp" +#include "mpt/base/float.hpp" +#include "mpt/base/integer.hpp" +#include "mpt/base/pointer.hpp" +#include "mpt/base/size.hpp" #include "mpt/base/source_location.hpp" #include "openmpt/base/Types.hpp" diff --git a/Frameworks/OpenMPT/OpenMPT/common/mptCPU.h b/Frameworks/OpenMPT/OpenMPT/common/mptCPU.h index 5ea23d409..c04ff3497 100644 --- a/Frameworks/OpenMPT/OpenMPT/common/mptCPU.h +++ b/Frameworks/OpenMPT/OpenMPT/common/mptCPU.h @@ -28,9 +28,6 @@ namespace CPU #ifdef MPT_ENABLE_ARCH_INTRINSICS - - - #if defined(MODPLUG_TRACKER) && !defined(MPT_BUILD_WINESUPPORT) @@ -93,11 +90,6 @@ public: #endif // MODPLUG_TRACKER - - - -// legacy interface - namespace feature = mpt::arch::current::feature; namespace mode = mpt::arch::current::mode; @@ -112,8 +104,6 @@ namespace mode = mpt::arch::current::mode; } - - #endif // MPT_ENABLE_ARCH_INTRINSICS diff --git a/Frameworks/OpenMPT/OpenMPT/common/mptFileIO.cpp b/Frameworks/OpenMPT/OpenMPT/common/mptFileIO.cpp deleted file mode 100644 index 9cb265eff..000000000 --- a/Frameworks/OpenMPT/OpenMPT/common/mptFileIO.cpp +++ /dev/null @@ -1,100 +0,0 @@ -/* - * mptFileIO.cpp - * ------------- - * Purpose: File I/O wrappers - * Notes : (currently none) - * Authors: OpenMPT Devs - * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. - */ - - -#include "stdafx.h" -#include "mptFileIO.h" - -#ifdef MODPLUG_TRACKER -#if MPT_OS_WINDOWS -#include -#include -#include -#endif // MPT_OS_WINDOWS -#endif // MODPLUG_TRACKER - - -OPENMPT_NAMESPACE_BEGIN - - -#if defined(MPT_ENABLE_FILEIO) - - -#if !defined(MPT_BUILD_SILENCE_LIBOPENMPT_CONFIGURATION_WARNINGS) - -#if defined(MPT_COMPILER_QUIRK_WINDOWS_FSTREAM_NO_WCHAR) -#if MPT_GCC_BEFORE(9,1,0) -MPT_WARNING("Warning: MinGW with GCC earlier than 9.1 detected. Standard library does neither provide std::fstream wchar_t overloads nor std::filesystem with wchar_t support. Unicode filename support is thus unavailable.") -#endif // MPT_GCC_AT_LEAST(9,1,0) -#endif // MPT_COMPILER_QUIRK_WINDOWS_FSTREAM_NO_WCHAR - -#endif // !MPT_BUILD_SILENCE_LIBOPENMPT_CONFIGURATION_WARNINGS - - -#ifdef MODPLUG_TRACKER - -#if MPT_OS_WINDOWS - -bool SetFilesystemCompression(HANDLE hFile) -{ - if(hFile == INVALID_HANDLE_VALUE) - { - return false; - } - USHORT format = COMPRESSION_FORMAT_DEFAULT; - DWORD dummy = 0; - BOOL result = DeviceIoControl(hFile, FSCTL_SET_COMPRESSION, (LPVOID)&format, sizeof(format), NULL, 0, &dummy /*required*/ , NULL); - return result != FALSE; -} - -bool SetFilesystemCompression(int fd) -{ - if(fd < 0) - { - return false; - } - uintptr_t fhandle = _get_osfhandle(fd); - HANDLE hFile = (HANDLE)fhandle; - if(hFile == INVALID_HANDLE_VALUE) - { - return false; - } - return SetFilesystemCompression(hFile); -} - -bool SetFilesystemCompression(const mpt::PathString &filename) -{ - DWORD attributes = GetFileAttributes(mpt::support_long_path(filename.AsNative()).c_str()); - if(attributes == INVALID_FILE_ATTRIBUTES) - { - return false; - } - if(attributes & FILE_ATTRIBUTE_COMPRESSED) - { - return true; - } - HANDLE hFile = CreateFile(mpt::support_long_path(filename.AsNative()).c_str(), GENERIC_ALL, FILE_SHARE_READ | FILE_SHARE_WRITE | FILE_SHARE_DELETE, NULL, OPEN_EXISTING, FILE_FLAG_BACKUP_SEMANTICS, NULL); - if(hFile == INVALID_HANDLE_VALUE) - { - return false; - } - bool result = SetFilesystemCompression(hFile); - CloseHandle(hFile); - return result; -} - -#endif // MPT_OS_WINDOWS - -#endif // MODPLUG_TRACKER - - -#endif // MPT_ENABLE_FILEIO - - -OPENMPT_NAMESPACE_END diff --git a/Frameworks/OpenMPT/OpenMPT/common/mptFileIO.h b/Frameworks/OpenMPT/OpenMPT/common/mptFileIO.h index 88c658875..19bdfb6b5 100644 --- a/Frameworks/OpenMPT/OpenMPT/common/mptFileIO.h +++ b/Frameworks/OpenMPT/OpenMPT/common/mptFileIO.h @@ -1,7 +1,7 @@ /* * mptFileIO.h * ----------- - * Purpose: A wrapper around std::fstream, enforcing usage of mpt::PathString. + * Purpose: * Notes : You should only ever use these wrappers instead of plain std::fstream classes. * Authors: OpenMPT Devs * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. @@ -13,23 +13,13 @@ #if defined(MPT_ENABLE_FILEIO) -#include "mpt/base/detect_libcxx.hpp" -#include "mpt/base/namespace.hpp" -#include "mpt/io_file/fstream.hpp" #include "mpt/io_file_read/inputfile_filecursor.hpp" -#include "../common/mptString.h" #include "../common/mptPathString.h" #include "../common/FileReaderFwd.h" #include -#ifdef MODPLUG_TRACKER -#if MPT_OS_WINDOWS -#include -#endif // MPT_OS_WINDOWS -#endif // MODPLUG_TRACKER - #endif // MPT_ENABLE_FILEIO @@ -39,29 +29,6 @@ OPENMPT_NAMESPACE_BEGIN #if defined(MPT_ENABLE_FILEIO) -// Sets the NTFS compression attribute on the file or directory. -// Requires read and write permissions for already opened files. -// Returns true if the attribute has been set. -// In almost all cases, the return value should be ignored because most filesystems other than NTFS do not support compression. -#ifdef MODPLUG_TRACKER -#if MPT_OS_WINDOWS -bool SetFilesystemCompression(HANDLE hFile); -bool SetFilesystemCompression(int fd); -bool SetFilesystemCompression(const mpt::PathString &filename); -#endif // MPT_OS_WINDOWS -#endif // MODPLUG_TRACKER - - -namespace mpt -{ - -using fstream = mpt::IO::fstream; -using ifstream = mpt::IO::ifstream; -using ofstream = mpt::IO::ofstream; - -} // namespace mpt - - template inline FileCursor GetFileReader(Targ1 &&arg1) { diff --git a/Frameworks/OpenMPT/OpenMPT/common/mptFileTemporary.cpp b/Frameworks/OpenMPT/OpenMPT/common/mptFileTemporary.cpp deleted file mode 100644 index 8b3330197..000000000 --- a/Frameworks/OpenMPT/OpenMPT/common/mptFileTemporary.cpp +++ /dev/null @@ -1,114 +0,0 @@ -/* - * mptFileTemporary.cpp - * -------------------- - * Purpose: - * Notes : Currently none. - * Authors: OpenMPT Devs - * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. - */ - -#include "stdafx.h" -#include "mptFileTemporary.h" - -#if defined(MODPLUG_TRACKER) && MPT_OS_WINDOWS -#include "mpt/fs/common_directories.hpp" -#include "mpt/fs/fs.hpp" -#include "mpt/io_file_unique/unique_basename.hpp" -#include "mpt/io_file_unique/unique_tempfilename.hpp" -#endif // MODPLUG_TRACKER && MPT_OS_WINDOWS -#include "mpt/string_transcode/transcode.hpp" -#include "mpt/uuid/uuid.hpp" - -#include "mptRandom.h" - -#if MPT_OS_WINDOWS -#include -#endif - - - -OPENMPT_NAMESPACE_BEGIN - - - -#if defined(MODPLUG_TRACKER) && MPT_OS_WINDOWS - - - -namespace mpt -{ - - - -TemporaryPathname::TemporaryPathname(const mpt::PathString &fileNameExtension) -{ - mpt::PathString prefix; -#if defined(LIBOPENMPT_BUILD) - prefix = P_("libopenmpt"); -#else - prefix = P_("OpenMPT"); -#endif - m_Path = mpt::PathString::FromNative(mpt::IO::unique_tempfilename{mpt::IO::unique_basename{prefix, mpt::UUID::GenerateLocalUseOnly(mpt::global_prng())}, fileNameExtension}); -} - - - -TempFileGuard::TempFileGuard(const mpt::TemporaryPathname &pathname) - : filename(pathname.GetPathname()) -{ - return; -} - -mpt::PathString TempFileGuard::GetFilename() const -{ - return filename; -} - -TempFileGuard::~TempFileGuard() -{ - if(!filename.empty()) - { - DeleteFile(mpt::support_long_path(filename.AsNative()).c_str()); - } -} - - -TempDirGuard::TempDirGuard(const mpt::TemporaryPathname &pathname) - : dirname(pathname.GetPathname().WithTrailingSlash()) -{ - if(dirname.empty()) - { - return; - } - if(::CreateDirectory(mpt::support_long_path(dirname.AsNative()).c_str(), NULL) == 0) - { // fail - dirname = mpt::PathString(); - } -} - -mpt::PathString TempDirGuard::GetDirname() const -{ - return dirname; -} - -TempDirGuard::~TempDirGuard() -{ - if(!dirname.empty()) - { - mpt::native_fs{}.delete_tree(dirname); - } -} - - - -} // namespace mpt - - - -#else -MPT_MSVC_WORKAROUND_LNK4221(mptFileTemporary) -#endif // MODPLUG_TRACKER && MPT_OS_WINDOWS - - - -OPENMPT_NAMESPACE_END diff --git a/Frameworks/OpenMPT/OpenMPT/common/mptFileTemporary.h b/Frameworks/OpenMPT/OpenMPT/common/mptFileTemporary.h index 9cc9a0c9f..6d938e3dd 100644 --- a/Frameworks/OpenMPT/OpenMPT/common/mptFileTemporary.h +++ b/Frameworks/OpenMPT/OpenMPT/common/mptFileTemporary.h @@ -14,7 +14,23 @@ #include "mpt/base/namespace.hpp" +#if defined(MODPLUG_TRACKER) && MPT_OS_WINDOWS + +#include "mpt/fs/common_directories.hpp" +#include "mpt/fs/fs.hpp" +#include "mpt/io_file_unique/unique_basename.hpp" +#include "mpt/io_file_unique/unique_tempfilename.hpp" +#include "mpt/uuid/uuid.hpp" + #include "mptPathString.h" +#include "mptRandom.h" + +#if MPT_OS_WINDOWS +#include +#endif + +#endif // MODPLUG_TRACKER && MPT_OS_WINDOWS + OPENMPT_NAMESPACE_BEGIN @@ -36,7 +52,16 @@ class TemporaryPathname private: mpt::PathString m_Path; public: - TemporaryPathname(const mpt::PathString &fileNameExtension = P_("tmp")); + TemporaryPathname(const mpt::PathString &fileNameExtension = P_("tmp")) + { + mpt::PathString prefix; +#if defined(LIBOPENMPT_BUILD) + prefix = P_("libopenmpt"); +#else + prefix = P_("OpenMPT"); +#endif + m_Path = mpt::PathString::FromNative(mpt::IO::unique_tempfilename{mpt::IO::unique_basename{prefix, mpt::UUID::GenerateLocalUseOnly(mpt::global_prng())}, fileNameExtension}); + } public: mpt::PathString GetPathname() const { @@ -53,12 +78,26 @@ class TempFileGuard private: const mpt::PathString filename; public: - TempFileGuard(const mpt::TemporaryPathname &pathname = mpt::TemporaryPathname{}); - mpt::PathString GetFilename() const; - ~TempFileGuard(); + TempFileGuard(const mpt::TemporaryPathname &pathname = mpt::TemporaryPathname{}) + : filename(pathname.GetPathname()) + { + return; + } + mpt::PathString GetFilename() const + { + return filename; + } + ~TempFileGuard() + { + if(!filename.empty()) + { + DeleteFile(mpt::support_long_path(filename.AsNative()).c_str()); + } + } }; + // Scoped temporary directory guard. Deletes the directory when going out of scope. // The directory itself is created automatically. class TempDirGuard @@ -66,9 +105,29 @@ class TempDirGuard private: mpt::PathString dirname; public: - TempDirGuard(const mpt::TemporaryPathname &pathname = mpt::TemporaryPathname{}); - mpt::PathString GetDirname() const; - ~TempDirGuard(); + TempDirGuard(const mpt::TemporaryPathname &pathname = mpt::TemporaryPathname{}) + : dirname(pathname.GetPathname().WithTrailingSlash()) + { + if(dirname.empty()) + { + return; + } + if(::CreateDirectory(mpt::support_long_path(dirname.AsNative()).c_str(), NULL) == 0) + { // fail + dirname = mpt::PathString(); + } + } + mpt::PathString GetDirname() const + { + return dirname; + } + ~TempDirGuard() + { + if(!dirname.empty()) + { + mpt::native_fs{}.delete_tree(dirname); + } + } }; diff --git a/Frameworks/OpenMPT/OpenMPT/common/mptPathString.cpp b/Frameworks/OpenMPT/OpenMPT/common/mptPathString.cpp index 2ab02faca..ca0aaeb51 100644 --- a/Frameworks/OpenMPT/OpenMPT/common/mptPathString.cpp +++ b/Frameworks/OpenMPT/OpenMPT/common/mptPathString.cpp @@ -10,18 +10,12 @@ #include "stdafx.h" #include "mptPathString.h" -#include "mpt/path/os_path_long.hpp" - #include -#if MPT_OS_WINDOWS +#if defined(MODPLUG_TRACKER) && MPT_OS_WINDOWS #include #endif -#if MPT_OS_WINDOWS -#include -#endif - OPENMPT_NAMESPACE_BEGIN @@ -31,24 +25,10 @@ namespace mpt -#if MPT_OS_WINDOWS - -#if !MPT_OS_WINDOWS_WINRT - -int PathCompareNoCase(const PathString & a, const PathString & b) -{ - return lstrcmpi(a.AsNative().c_str(), b.AsNative().c_str()); -} - -#endif // !MPT_OS_WINDOWS_WINRT - -#endif // MPT_OS_WINDOWS - - - #if defined(MODPLUG_TRACKER) && MPT_OS_WINDOWS + // Convert an absolute path to a path that's relative to "&relativeTo". mpt::PathString AbsolutePathToRelative(const mpt::PathString &path, const mpt::PathString &relativeTo) { @@ -107,59 +87,13 @@ mpt::PathString RelativePathToAbsolute(const mpt::PathString &path, const mpt::P } + #endif // MODPLUG_TRACKER && MPT_OS_WINDOWS -#if MPT_OS_WINDOWS - -#if !(MPT_WINRT_BEFORE(MPT_WIN_10)) - -mpt::PathString GetAbsolutePath(const mpt::PathString &path) -{ - DWORD size = GetFullPathName(path.AsNative().c_str(), 0, nullptr, nullptr); - if(size == 0) - { - return path; - } - std::vector fullPathName(size, TEXT('\0')); - if(GetFullPathName(path.AsNative().c_str(), size, fullPathName.data(), nullptr) == 0) - { - return path; - } - return mpt::PathString::FromNative(fullPathName.data()); -} - -#endif - -#endif // MPT_OS_WINDOWS - - - } // namespace mpt -#if defined(MODPLUG_TRACKER) - - - -mpt::ustring SanitizePathComponent(mpt::ustring str) -{ - return mpt::PathString::FromUnicode(str).AsSanitizedComponent().ToUnicode(); -} - -#if defined(MPT_WITH_MFC) -CString SanitizePathComponent(CString str) -{ - return mpt::PathString::FromCString(str).AsSanitizedComponent().ToCString(); -} -#endif // MPT_WITH_MFC - - - -#endif // MODPLUG_TRACKER - - - OPENMPT_NAMESPACE_END diff --git a/Frameworks/OpenMPT/OpenMPT/common/mptPathString.h b/Frameworks/OpenMPT/OpenMPT/common/mptPathString.h index f41175a9d..0867020fe 100644 --- a/Frameworks/OpenMPT/OpenMPT/common/mptPathString.h +++ b/Frameworks/OpenMPT/OpenMPT/common/mptPathString.h @@ -19,8 +19,16 @@ #include "mpt/path/os_path.hpp" #include "mpt/string/types.hpp" +#if defined(MODPLUG_TRACKER) +#include "mpt/string_transcode/transcode.hpp" +#endif + #include "mptString.h" +#if defined(MODPLUG_TRACKER) && MPT_OS_WINDOWS +#include +#endif + OPENMPT_NAMESPACE_BEGIN @@ -64,14 +72,13 @@ inline mpt::ustring ToUString(const T &x) -#if MPT_OS_WINDOWS -#if !(MPT_WINRT_BEFORE(MPT_WIN_10)) -// Returns the absolute path for a potentially relative path and removes ".." or "." components. (same as GetFullPathNameW) -mpt::PathString GetAbsolutePath(const mpt::PathString &path); -#endif -#endif // MPT_OS_WINDOWS +#if defined(MODPLUG_TRACKER) + + + +#if MPT_OS_WINDOWS + -#if defined(MODPLUG_TRACKER) && MPT_OS_WINDOWS // Relative / absolute paths conversion @@ -79,15 +86,30 @@ mpt::PathString AbsolutePathToRelative(const mpt::PathString &p, const mpt::Path mpt::PathString RelativePathToAbsolute(const mpt::PathString &p, const mpt::PathString &relativeTo); -#endif // MODPLUG_TRACKER && MPT_OS_WINDOWS - -#if MPT_OS_WINDOWS #if !MPT_OS_WINDOWS_WINRT -int PathCompareNoCase(const PathString &a, const PathString &b); +inline int PathCompareNoCase(const PathString &a, const PathString &b) +{ + return lstrcmpi(a.AsNative().c_str(), b.AsNative().c_str()); +} #endif // !MPT_OS_WINDOWS_WINRT -#endif + + + +#endif // MPT_OS_WINDOWS + + + +template +inline Tstring SanitizePathComponent(const Tstring &str) +{ + return mpt::transcode(mpt::native_path::FromNative(mpt::transcode(str)).AsSanitizedComponent().AsNative()); +} + + + +#endif // MODPLUG_TRACKER @@ -95,18 +117,4 @@ int PathCompareNoCase(const PathString &a, const PathString &b); -#if defined(MODPLUG_TRACKER) - -// Sanitize a filename (remove special chars) - -mpt::ustring SanitizePathComponent(mpt::ustring str); - -#if defined(MPT_WITH_MFC) -CString SanitizePathComponent(CString str); -#endif // MPT_WITH_MFC - -#endif // MODPLUG_TRACKER - - - OPENMPT_NAMESPACE_END diff --git a/Frameworks/OpenMPT/OpenMPT/common/mptRandom.cpp b/Frameworks/OpenMPT/OpenMPT/common/mptRandom.cpp index 4735a0951..a6035ab54 100644 --- a/Frameworks/OpenMPT/OpenMPT/common/mptRandom.cpp +++ b/Frameworks/OpenMPT/OpenMPT/common/mptRandom.cpp @@ -45,13 +45,27 @@ mpt::thread_safe_prng & global_prng() mpt::random_device & global_random_device() { +#if MPT_COMPILER_CLANG +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wexit-time-destructors" +#endif // MPT_COMPILER_CLANG static mpt::random_device g_rd; +#if MPT_COMPILER_CLANG +#pragma clang diagnostic pop +#endif // MPT_COMPILER_CLANG return g_rd; } mpt::thread_safe_prng & global_prng() { +#if MPT_COMPILER_CLANG +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wexit-time-destructors" +#endif // MPT_COMPILER_CLANG static mpt::thread_safe_prng g_global_prng(mpt::make_prng(global_random_device())); +#if MPT_COMPILER_CLANG +#pragma clang diagnostic pop +#endif // MPT_COMPILER_CLANG return g_global_prng; } diff --git a/Frameworks/OpenMPT/OpenMPT/common/mptRandom.h b/Frameworks/OpenMPT/OpenMPT/common/mptRandom.h index 05b01e656..3b29c23ce 100644 --- a/Frameworks/OpenMPT/OpenMPT/common/mptRandom.h +++ b/Frameworks/OpenMPT/OpenMPT/common/mptRandom.h @@ -95,7 +95,7 @@ public: #ifdef MPT_BUILD_FUZZER // Use deterministic seeding -using random_device = deterministc_random_device; +using random_device = deterministic_random_device; #else // !MPT_BUILD_FUZZER diff --git a/Frameworks/OpenMPT/OpenMPT/common/mptString.cpp b/Frameworks/OpenMPT/OpenMPT/common/mptString.cpp deleted file mode 100644 index e69de29bb..000000000 diff --git a/Frameworks/OpenMPT/OpenMPT/common/mptString.h b/Frameworks/OpenMPT/OpenMPT/common/mptString.h index 99b5f7e6c..af1161b7a 100644 --- a/Frameworks/OpenMPT/OpenMPT/common/mptString.h +++ b/Frameworks/OpenMPT/OpenMPT/common/mptString.h @@ -408,6 +408,7 @@ inline CString ToCString(Tencoding &&from, Tsrc &&str) #define UC_(x) MPT_UCHAR(x) #define UL_(x) MPT_ULITERAL(x) +#define UV_(x) MPT_USTRINGVIEW(x) #define U_(x) MPT_USTRING(x) diff --git a/Frameworks/OpenMPT/OpenMPT/common/mptStringFormat.cpp b/Frameworks/OpenMPT/OpenMPT/common/mptStringFormat.cpp deleted file mode 100644 index e69de29bb..000000000 diff --git a/Frameworks/OpenMPT/OpenMPT/common/mptStringParse.cpp b/Frameworks/OpenMPT/OpenMPT/common/mptStringParse.cpp deleted file mode 100644 index e69de29bb..000000000 diff --git a/Frameworks/OpenMPT/OpenMPT/common/mptStringParse.h b/Frameworks/OpenMPT/OpenMPT/common/mptStringParse.h deleted file mode 100644 index e69de29bb..000000000 diff --git a/Frameworks/OpenMPT/OpenMPT/common/mptTime.cpp b/Frameworks/OpenMPT/OpenMPT/common/mptTime.cpp index 6a7af06c2..53c3c7ff3 100644 --- a/Frameworks/OpenMPT/OpenMPT/common/mptTime.cpp +++ b/Frameworks/OpenMPT/OpenMPT/common/mptTime.cpp @@ -25,11 +25,12 @@ #include #endif // MODPLUG_TRACKER && MPT_OS_WINDOWS +#if defined(MPT_FALLBACK_TIMEZONE_C) +#include +#endif // MPT_FALLBACK_TIMEZONE_C + #if MPT_OS_WINDOWS #include -#if defined(MODPLUG_TRACKER) -#include -#endif #endif @@ -41,6 +42,8 @@ namespace mpt namespace Date { + + #if defined(MODPLUG_TRACKER) #if MPT_OS_WINDOWS @@ -51,7 +54,11 @@ namespace ANSI uint64 Now() { FILETIME filetime; +#if MPT_WIN_AT_LEAST(MPT_WIN_8) + GetSystemTimePreciseAsFileTime(&filetime); +#else GetSystemTimeAsFileTime(&filetime); +#endif return ((uint64)filetime.dwHighDateTime << 32 | filetime.dwLowDateTime); } @@ -91,6 +98,8 @@ mpt::ustring ToUString(uint64 time100ns) #endif // MODPLUG_TRACKER + + namespace nochrono { @@ -393,100 +402,4 @@ mpt::ustring ToShortenedISO8601(Local date) -#ifdef MODPLUG_TRACKER - -namespace Util -{ - -#if MPT_OS_WINDOWS - -void MultimediaClock::Init() -{ - m_CurrentPeriod = 0; -} - -void MultimediaClock::SetPeriod(uint32 ms) -{ - TIMECAPS caps = {}; - if(timeGetDevCaps(&caps, sizeof(caps)) != MMSYSERR_NOERROR) - { - return; - } - if((caps.wPeriodMax == 0) || (caps.wPeriodMin > caps.wPeriodMax)) - { - return; - } - ms = std::clamp(mpt::saturate_cast(ms), caps.wPeriodMin, caps.wPeriodMax); - if(timeBeginPeriod(ms) != MMSYSERR_NOERROR) - { - return; - } - m_CurrentPeriod = ms; -} - -void MultimediaClock::Cleanup() -{ - if(m_CurrentPeriod > 0) - { - if(timeEndPeriod(m_CurrentPeriod) != MMSYSERR_NOERROR) - { - // should not happen - MPT_ASSERT_NOTREACHED(); - } - m_CurrentPeriod = 0; - } -} - -MultimediaClock::MultimediaClock() -{ - Init(); -} - -MultimediaClock::MultimediaClock(uint32 ms) -{ - Init(); - SetResolution(ms); -} - -MultimediaClock::~MultimediaClock() -{ - Cleanup(); -} - -uint32 MultimediaClock::SetResolution(uint32 ms) -{ - if(m_CurrentPeriod == ms) - { - return m_CurrentPeriod; - } - Cleanup(); - if(ms != 0) - { - SetPeriod(ms); - } - return GetResolution(); -} - -uint32 MultimediaClock::GetResolution() const -{ - return m_CurrentPeriod; -} - -uint32 MultimediaClock::Now() const -{ - return timeGetTime(); -} - -uint64 MultimediaClock::NowNanoseconds() const -{ - return (uint64)timeGetTime() * (uint64)1000000; -} - -#endif // MPT_OS_WINDOWS - -} // namespace Util - -#endif // MODPLUG_TRACKER - - OPENMPT_NAMESPACE_END diff --git a/Frameworks/OpenMPT/OpenMPT/common/mptTime.h b/Frameworks/OpenMPT/OpenMPT/common/mptTime.h index bb599a1a6..04d0a35c3 100644 --- a/Frameworks/OpenMPT/OpenMPT/common/mptTime.h +++ b/Frameworks/OpenMPT/OpenMPT/common/mptTime.h @@ -15,10 +15,13 @@ #if MPT_CXX_AT_LEAST(20) && !defined(MPT_LIBCXX_QUIRK_NO_CHRONO) && !defined(MPT_LIBCXX_QUIRK_NO_CHRONO_DATE) #include #include +#elif MPT_CXX_AT_LEAST(17) && !defined(MPT_LIBCXX_QUIRK_NO_CHRONO) && defined(MODPLUG_TRACKER) +#include #endif -#include +#if MPT_CXX_BEFORE(20) || defined(MPT_LIBCXX_QUIRK_NO_CHRONO) || defined(MPT_LIBCXX_QUIRK_NO_CHRONO_DATE) #include +#endif #if MPT_WINNT_AT_LEAST(MPT_WIN_8) #define MPT_FALLBACK_TIMEZONE_WINDOWS_HISTORIC @@ -37,11 +40,35 @@ OPENMPT_NAMESPACE_BEGIN +#if defined(MODPLUG_TRACKER) && !defined(MPT_LIBCXX_QUIRK_NO_CHRONO) + +namespace mpt { +namespace chrono { +#if MPT_CXX_AT_LEAST(20) +using days = std::chrono::days; +using weeks = std::chrono::weeks; +using years = std::chrono::years; +using months = std::chrono::months; +#else +using days = std::chrono::duration, std::chrono::hours::period>>; +using weeks = std::chrono::duration, mpt::chrono::days::period>>; +using years = std::chrono::duration, mpt::chrono::days::period>>; +using months = std::chrono::duration>>; +#endif +} +} + +#endif // !MPT_LIBCXX_QUIRK_NO_CHRONO + + + namespace mpt { namespace Date { + + #if defined(MODPLUG_TRACKER) #if MPT_OS_WINDOWS @@ -60,6 +87,8 @@ mpt::ustring ToUString(uint64 time100ns); // i.e. 2015-01-15 18:32:01.718 #endif // MODPLUG_TRACKER + + enum class LogicalTimezone { Unspecified, @@ -146,11 +175,15 @@ struct Unix } }; +#if MPT_CXX_BEFORE(20) || defined(MPT_LIBCXX_QUIRK_NO_CHRONO) || defined(MPT_LIBCXX_QUIRK_NO_CHRONO_DATE) + inline Unix UnixNow() { return Unix{static_cast(std::time(nullptr))}; } +#endif + inline int64 UnixAsSeconds(Unix tp) { return tp.value; @@ -238,6 +271,7 @@ inline mpt::Date::UTC UnixAsUTC(Unix tp) inline mpt::Date::Unix UnixFromLocal(Local local) { +#if !defined(MPT_LIBCXX_QUIRK_CHRONO_DATE_NO_ZONED_TIME) try { std::chrono::time_point local_tp = @@ -255,6 +289,7 @@ inline mpt::Date::Unix UnixFromLocal(Local local) return std::chrono::zoned_time{std::chrono::current_zone(), local_tp}.get_sys_time(); #endif } catch(const std::exception &) +#endif { return mpt::Date::UnixFromSeconds(mpt::Date::nochrono::UnixAsSeconds(mpt::Date::nochrono::UnixFromLocal(local))); } @@ -262,6 +297,7 @@ inline mpt::Date::Unix UnixFromLocal(Local local) inline mpt::Date::Local UnixAsLocal(Unix tp) { +#if !defined(MPT_LIBCXX_QUIRK_CHRONO_DATE_NO_ZONED_TIME) try { std::chrono::zoned_time local_tp{ std::chrono::current_zone(), tp }; @@ -277,6 +313,7 @@ inline mpt::Date::Local UnixAsLocal(Unix tp) result.seconds = static_cast(hms.seconds().count()); return result; } catch(const std::exception &) +#endif { return mpt::Date::nochrono::UnixAsLocal(mpt::Date::nochrono::UnixFromSeconds(mpt::Date::UnixAsSeconds(tp))); } @@ -317,52 +354,4 @@ mpt::ustring ToShortenedISO8601(Local date); // i.e. 2015-01-15T18:32:01 -#ifdef MODPLUG_TRACKER - -namespace Util -{ - -#if MPT_OS_WINDOWS - -// RAII wrapper around timeBeginPeriod/timeEndPeriod/timeGetTime (on Windows). -// This clock is monotonic, even across changing its resolution. -// This is needed to synchronize time in Steinberg APIs (ASIO and VST). -class MultimediaClock -{ -private: - uint32 m_CurrentPeriod; -private: - void Init(); - void SetPeriod(uint32 ms); - void Cleanup(); -public: - MultimediaClock(); - MultimediaClock(uint32 ms); - ~MultimediaClock(); -public: - // Sets the desired resolution in milliseconds, returns the obtained resolution in milliseconds. - // A parameter of 0 causes the resolution to be reset to system defaults. - // A return value of 0 means the resolution is unknown, but timestamps will still be valid. - uint32 SetResolution(uint32 ms); - // Returns obtained resolution in milliseconds. - // A return value of 0 means the resolution is unknown, but timestamps will still be valid. - uint32 GetResolution() const; - // Returns current instantaneous timestamp in milliseconds. - // The epoch (offset) of the timestamps is undefined but constant until the next system reboot. - // The resolution is the value returned from GetResolution(). - uint32 Now() const; - // Returns current instantaneous timestamp in nanoseconds. - // The epoch (offset) of the timestamps is undefined but constant until the next system reboot. - // The resolution is the value returned from GetResolution() in milliseconds. - uint64 NowNanoseconds() const; -}; - -#endif // MPT_OS_WINDOWS - -} // namespace Util - -#endif // MODPLUG_TRACKER - - - OPENMPT_NAMESPACE_END diff --git a/Frameworks/OpenMPT/OpenMPT/common/serialization_utils.h b/Frameworks/OpenMPT/OpenMPT/common/serialization_utils.h index f290c6dd9..16b1d8216 100644 --- a/Frameworks/OpenMPT/OpenMPT/common/serialization_utils.h +++ b/Frameworks/OpenMPT/OpenMPT/common/serialization_utils.h @@ -60,7 +60,6 @@ enum class StatusLevel : uint8 Failure = 0x2, Note = 0x1, None = 0x0, - Max = 0xff, }; enum class StatusMessages : uint32 @@ -89,8 +88,6 @@ enum class StatusMessages : uint32 SNW_INSUFFICIENT_DATASIZETYPE = 0x00'10'00'00, SNRW_BADGIVEN_STREAM = 0x01'00'00'00, - - Max = 0xffffffff, }; struct Status @@ -149,6 +146,7 @@ enum Rwf RwfRTwoBytesDescChar, // Read. True if map description characters are two bytes. RwfRHeaderIsRead, // Read. True when header is read. RwfRwHasMap, // Read/write. True if map exists. + RwfNumFlags }; diff --git a/Frameworks/OpenMPT/OpenMPT/common/stdafx.h b/Frameworks/OpenMPT/OpenMPT/common/stdafx.h index f8e740ddf..f87efa3f9 100644 --- a/Frameworks/OpenMPT/OpenMPT/common/stdafx.h +++ b/Frameworks/OpenMPT/OpenMPT/common/stdafx.h @@ -15,11 +15,7 @@ // has to be first #include "openmpt/all/BuildSettings.hpp" - - -#if defined(__MINGW32__) && !defined(__MINGW64__) -#include -#endif +#include "openmpt/all/PlatformFixes.hpp" #if defined(MODPLUG_TRACKER) @@ -66,6 +62,7 @@ #include "mpt/check/compiler.hpp" #include "mpt/check/libc.hpp" +#include "mpt/check/libcxx.hpp" #if defined(MPT_WITH_MFC) #include "mpt/check/mfc.hpp" #endif diff --git a/Frameworks/OpenMPT/OpenMPT/common/version.cpp b/Frameworks/OpenMPT/OpenMPT/common/version.cpp index 8ecd9d42a..ca13046d9 100644 --- a/Frameworks/OpenMPT/OpenMPT/common/version.cpp +++ b/Frameworks/OpenMPT/OpenMPT/common/version.cpp @@ -629,15 +629,12 @@ mpt::ustring GetFullCreditsString() "http://modplug-xmms.sourceforge.net/\n" "\n" #ifdef MODPLUG_TRACKER - "Stephan M. Bernsee for pitch shifting source code\n" - "http://www.dspdimension.com/\n" + "Geraint Luff for Signalsmith Stretch\n" + "https://signalsmith-audio.co.uk/code/stretch/\n" "\n" "Aleksey Vaneev of Voxengo for r8brain sample rate converter\n" "https://github.com/avaneev/r8brain-free-src\n" "\n" - "Olli Parviainen for SoundTouch Library (time stretching)\n" - "https://www.surina.net/soundtouch/\n" - "\n" #endif #ifdef MPT_WITH_VST "Hermann Seib for his example VST Host implementation\n" diff --git a/Frameworks/OpenMPT/OpenMPT/common/versionNumber.h b/Frameworks/OpenMPT/OpenMPT/common/versionNumber.h index d0683a02d..6eebf87fd 100644 --- a/Frameworks/OpenMPT/OpenMPT/common/versionNumber.h +++ b/Frameworks/OpenMPT/OpenMPT/common/versionNumber.h @@ -10,14 +10,10 @@ #pragma once -#include "openmpt/all/BuildSettings.hpp" -OPENMPT_NAMESPACE_BEGIN // Version definitions. The only thing that needs to be changed when changing version number. #define VER_MAJORMAJOR 1 -#define VER_MAJOR 31 -#define VER_MINOR 14 +#define VER_MAJOR 32 +#define VER_MINOR 01 #define VER_MINORMINOR 00 - -OPENMPT_NAMESPACE_END diff --git a/Frameworks/OpenMPT/OpenMPT/contrib/fuzzing/all_formats.dict b/Frameworks/OpenMPT/OpenMPT/contrib/fuzzing/all_formats.dict index d78bed301..7199a12fc 100644 --- a/Frameworks/OpenMPT/OpenMPT/contrib/fuzzing/all_formats.dict +++ b/Frameworks/OpenMPT/OpenMPT/contrib/fuzzing/all_formats.dict @@ -10,6 +10,8 @@ amf="DMF\x0E" ams="Extreme" ams="AMShdr\x1A\x00\x02\x02" +cba="CBA\xF9" + #dbm="DBM0" dbm="NAME" dbm="INFO" @@ -48,11 +50,18 @@ dtm="S.Q." dtm="DAPT" dtm="DAIT" +etx="EASYTRAX 1.0\x01\x00z\x01\x20\x00\x00\x00\x20\x04\x00\x00" + far="FAR\xFE" far="\x0D\x0A\x1A" +fc="SMOD" +fc="FC14" + fmt="FMTracker\x01\x01" +ftm="FTMN\x03\x03\x00\x08\x20\x20\x00\xFF\x3F\x03\x06\x10" + gdm="GDM\xFE" gdm="\x0D\x0A\x1AGMFS\x01\x00" @@ -126,6 +135,8 @@ j2b="ORDR" j2b="AI " j2b="AS " +kris="KRIS" + MDL="DMDL" # Most chunk IDs are commented out as they are substrings of other dictionary entries #mdl="IN" @@ -146,9 +157,7 @@ mo3="MO3\x05" # A couple of magic bytes are commented out because they do not modify the loader's behaviour, apart from setting a "made with" string. mod="M.K." #mod="M!K!" -mod="M&K!" mod="N.T." -#mod="FEST" #mod="NSMS" #mod="LARD" mod="OKTA" @@ -170,6 +179,10 @@ sfx="SO31" stam="ST1.3 ModuleINFO" stam="AudioSculpture10" +hmn="M&K!" +#hmn="FEST" +hmn="Mupp" + mptm="->MPT_ORIGINAL_IT<-" mptm=".tpm" mptm="mptm" @@ -239,8 +252,14 @@ stp="STP3\x02" symmod="SymM\x00\x00\x00\x01\xFF\xFF\xFF\xFF\x00\x00\x00" +tcb="AN COOL.\x01\x00\x00\x00\x0D\x00" +#tcb="AN COOL!" + ult="MAS_UTrack_V004" +#unic="UNIC" +#unic="\x00\x00\x00\x00" + umx="\xC1\x83\x2A\x9E" umx="music" umx="sound" @@ -348,3 +367,9 @@ iff="CHAN" iff="MHDR" iff="MDAT" iff="NAME" + +puma="patt" +puma="inst" +puma="insf" + +rtm="RTMM " diff --git a/Frameworks/OpenMPT/OpenMPT/contrib/fuzzing/build.sh b/Frameworks/OpenMPT/OpenMPT/contrib/fuzzing/build.sh index 833397efe..12ebe3f77 100755 --- a/Frameworks/OpenMPT/OpenMPT/contrib/fuzzing/build.sh +++ b/Frameworks/OpenMPT/OpenMPT/contrib/fuzzing/build.sh @@ -1,4 +1,4 @@ #!/usr/bin/env bash cd "${0%/*}" cd ../.. -AFL_HARDEN=1 CONFIG=afl make clean all EXAMPLES=0 TEST=0 OPENMPT123=0 NO_VORBIS=1 NO_VORBISFILE=1 NO_MPG123=1 CHECKED_ADDRESS=1 +AFL_LLVM_CMPLOG=1 AFL_USE_ASAN=1 CONFIG=afl make clean all EXAMPLES=0 TEST=0 OPENMPT123=0 NO_VORBIS=1 NO_VORBISFILE=1 NO_MPG123=1 CHECKED_ADDRESS=1 diff --git a/Frameworks/OpenMPT/OpenMPT/contrib/fuzzing/fuzz-main.sh b/Frameworks/OpenMPT/OpenMPT/contrib/fuzzing/fuzz-main.sh index a7429831d..6e5fa0f96 100644 --- a/Frameworks/OpenMPT/OpenMPT/contrib/fuzzing/fuzz-main.sh +++ b/Frameworks/OpenMPT/OpenMPT/contrib/fuzzing/fuzz-main.sh @@ -9,5 +9,4 @@ rm -rf $FUZZING_TEMPDIR/bin mkdir $FUZZING_TEMPDIR/bin cp -d ../../bin/* $FUZZING_TEMPDIR/bin/ -#export AFL_PRELOAD=$AFL_DIR/libdislocator.so -LD_LIBRARY_PATH=$FUZZING_TEMPDIR/bin $AFL_DIR/afl-fuzz -p exploit -x all_formats.dict -t $FUZZING_TIMEOUT $FUZZING_INPUT -o $FUZZING_FINDINGS_DIR -D -M fuzzer01 $FUZZING_TEMPDIR/bin/fuzz +LD_LIBRARY_PATH=$FUZZING_TEMPDIR/bin $FUZZING_AFL_DIR/afl-fuzz -x all_formats.dict -t $FUZZING_TIMEOUT $FUZZING_INPUT -o $FUZZING_FINDINGS_DIR -M fuzzer01 $FUZZING_TEMPDIR/bin/fuzz diff --git a/Frameworks/OpenMPT/OpenMPT/contrib/fuzzing/fuzz-secondary1.sh b/Frameworks/OpenMPT/OpenMPT/contrib/fuzzing/fuzz-secondary1.sh index 2d9a5f08f..10bc257bc 100644 --- a/Frameworks/OpenMPT/OpenMPT/contrib/fuzzing/fuzz-secondary1.sh +++ b/Frameworks/OpenMPT/OpenMPT/contrib/fuzzing/fuzz-secondary1.sh @@ -2,5 +2,4 @@ cd "${0%/*}" . ./fuzz-settings.sh -#export AFL_PRELOAD=$AFL_DIR/libdislocator.so -LD_LIBRARY_PATH=$FUZZING_TEMPDIR/bin $AFL_DIR/afl-fuzz -p coe -x all_formats.dict -t $FUZZING_TIMEOUT $FUZZING_INPUT -o $FUZZING_FINDINGS_DIR -S fuzzer02 $FUZZING_TEMPDIR/bin/fuzz +LD_LIBRARY_PATH=$FUZZING_TEMPDIR/bin $FUZZING_AFL_DIR/afl-fuzz -c0 -l2 -x all_formats.dict -t $FUZZING_TIMEOUT $FUZZING_INPUT -o $FUZZING_FINDINGS_DIR -S fuzzer02 $FUZZING_TEMPDIR/bin/fuzz diff --git a/Frameworks/OpenMPT/OpenMPT/contrib/fuzzing/fuzz-secondary2.sh b/Frameworks/OpenMPT/OpenMPT/contrib/fuzzing/fuzz-secondary2.sh index 503be1304..30979f5c6 100644 --- a/Frameworks/OpenMPT/OpenMPT/contrib/fuzzing/fuzz-secondary2.sh +++ b/Frameworks/OpenMPT/OpenMPT/contrib/fuzzing/fuzz-secondary2.sh @@ -2,5 +2,4 @@ cd "${0%/*}" . ./fuzz-settings.sh -#export AFL_PRELOAD=$AFL_DIR/libdislocator.so -LD_LIBRARY_PATH=$FUZZING_TEMPDIR/bin $AFL_DIR/afl-fuzz -p explore -x all_formats.dict -t $FUZZING_TIMEOUT $FUZZING_INPUT -o $FUZZING_FINDINGS_DIR -S fuzzer03 $FUZZING_TEMPDIR/bin/fuzz +LD_LIBRARY_PATH=$FUZZING_TEMPDIR/bin $FUZZING_AFL_DIR/afl-fuzz -p fast -x all_formats.dict -t $FUZZING_TIMEOUT $FUZZING_INPUT -o $FUZZING_FINDINGS_DIR -S fuzzer03 $FUZZING_TEMPDIR/bin/fuzz diff --git a/Frameworks/OpenMPT/OpenMPT/contrib/fuzzing/fuzz-secondary3.sh b/Frameworks/OpenMPT/OpenMPT/contrib/fuzzing/fuzz-secondary3.sh new file mode 100644 index 000000000..56d194bfb --- /dev/null +++ b/Frameworks/OpenMPT/OpenMPT/contrib/fuzzing/fuzz-secondary3.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +cd "${0%/*}" +. ./fuzz-settings.sh + +unset AFL_DISABLE_TRIM +LD_LIBRARY_PATH=$FUZZING_TEMPDIR/bin $FUZZING_AFL_DIR/afl-fuzz -p exploit -P 300 -a binary -x all_formats.dict -t $FUZZING_TIMEOUT $FUZZING_INPUT -o $FUZZING_FINDINGS_DIR -S fuzzer04 $FUZZING_TEMPDIR/bin/fuzz diff --git a/Frameworks/OpenMPT/OpenMPT/contrib/fuzzing/fuzz-settings.sh b/Frameworks/OpenMPT/OpenMPT/contrib/fuzzing/fuzz-settings.sh index 1b280673e..439dadb2e 100755 --- a/Frameworks/OpenMPT/OpenMPT/contrib/fuzzing/fuzz-settings.sh +++ b/Frameworks/OpenMPT/OpenMPT/contrib/fuzzing/fuzz-settings.sh @@ -15,4 +15,13 @@ FUZZING_FINDINGS_DIR=~/libopenmpt-fuzzing # Fuzzer timeout in ms, + = don't abort on timeout FUZZING_TIMEOUT=5000+ # Path to afl-fuzz binary -AFL_DIR=afl +FUZZING_AFL_DIR=afl + +# AFL specific envs +AFL_TRY_AFFINITY=1 +AFL_CMPLOG_ONLY_NEW=1 +AFL_NO_WARN_INSTABILITY=1 +AFL_FAST_CAL=1 +AFL_IMPORT_FIRST=1 +AFL_DISABLE_TRIM=1 +AFL_IGNORE_SEED_PROBLEMS=1 diff --git a/Frameworks/OpenMPT/OpenMPT/contrib/fuzzing/fuzz.c b/Frameworks/OpenMPT/OpenMPT/contrib/fuzzing/fuzz.c deleted file mode 100644 index e69de29bb..000000000 diff --git a/Frameworks/OpenMPT/OpenMPT/contrib/fuzzing/readme.md b/Frameworks/OpenMPT/OpenMPT/contrib/fuzzing/readme.md index e67be227b..b0342d75f 100644 --- a/Frameworks/OpenMPT/OpenMPT/contrib/fuzzing/readme.md +++ b/Frameworks/OpenMPT/OpenMPT/contrib/fuzzing/readme.md @@ -10,13 +10,13 @@ Contents: module formats to make the life of the fuzzer a bit easier. * `fuzz-main.sh`: Script to launch the main fuzzing process. If you want to use just one fuzzer instance, run this one. -* `fuzz-secondary[1|2].sh`: Scripts to launch the secondary fuzzing process. It - is recommended to run at least two fuzzer instances, as the deterministic and +* `fuzz-secondary[1|2|3].sh`: Scripts to launch the secondary fuzzing processes. + It is recommended to run at least 2 fuzzer instances, as the deterministic and random fuzz mode have been found to complement each other really well. The two scripts are set up to use different exploration strategies. * `fuzz-settings.sh`: Set up your preferences and afl settings here before the first run. -* `fuzz.c`: A tiny C program that is used by the fuzzer to test libopenmpt. +* `fuzz.cpp`: A tiny C++ program that is used by the fuzzer to test libopenmpt. * `get-afl.sh`: A simple script to obtain the latest version of afl++. You can also make it download from a specific branch or tag, e.g. `GET_AFL_VERSION=stable ./get-afl.sh` to download the latest stable but @@ -43,9 +43,8 @@ How to use The default setup mounts a tmpfs folder for all temporary files. You may change this behaviour if you do not have root privileges. * Run `fuzz-main.sh` for the first (deterministic) instance of afl-fuzz. -* For a "secondary" instance to run on another core, run `fuzz-secondary1.sh` - and/or `fuzz-secondary2.sh`. +* For a "secondary" instance to run on another core, run `fuzz-secondary1.sh`, + `fuzz-secondary2.sh` and `fuzz-secondary3.sh`. * If you want to make use of even more cores, create more copies of - `fuzz-secondary2.sh` and adjust "infile03" / "fuzzer03" to - "infile04" / "fuzzer04" and so on (they need to be unique). Try varying the - fuzzing strategey (the -p parameter) to get results more quickly. + `fuzz-secondary2.sh` and adjust "fuzzer03" to "fuzzer05" and so on (they need to be unique). + Try varying the fuzzing strategy (the -p parameter) to get more varied results quickly. diff --git a/Frameworks/OpenMPT/OpenMPT/doc/module_formats.md b/Frameworks/OpenMPT/OpenMPT/doc/module_formats.md index 8cbd5773d..8abefd4c0 100644 --- a/Frameworks/OpenMPT/OpenMPT/doc/module_formats.md +++ b/Frameworks/OpenMPT/OpenMPT/doc/module_formats.md @@ -17,13 +17,13 @@ General hints a subset of ProTracker MOD). * When reading binary structs from the file, use our data types with defined endianness, which can be found in `common/Endianness.h`: - * Big-Endian: (u)int8/16/32/64be, float32be, float64be - * Little-Endian: (u)int8/16/32/64le, float32le, float64le + * Big-Endian: (u)int8/16/24/32/64be, float32be, float64be + * Little-Endian: (u)int8/16/24/32/64le, float32le, float64le Entire structs containing integers with defined endianness can be read in one go if they are tagged with `MPT_BINARY_STRUCT` (see existing loaders for an example). -* `CSoundFile::m_nChannels` **MUST NOT** be changed after a pattern has been +* `CSoundFile::ChnSettings` **MUST NOT** be resized after a pattern has been created, as existing patterns will be interpreted incorrectly. For module formats that support per-pattern channel amounts, the maximum number of channels must be determined beforehand. @@ -38,8 +38,8 @@ General hints thread-safe for libopenmpt. * `FileReader` instances may be used to treat a portion of a file as its own independent file (through `FileReader::ReadChunk`). This can be useful with - "embedded files" such as WAV or Ogg samples. Container formats such as UMX - are another good example for this usage. + "embedded files" such as WAV or Ogg samples (see MO3 or SymMOD for examples). + Container formats such as UMX are another good example for this usage. * Samples *either* use middle-C frequency *or* finetune + transpose. For the few weird formats that use both, it may make sense to translate everything into middle-C frequency. @@ -66,7 +66,7 @@ General hints (which may e.g. cause a song title starting with if ..." in various other formats to be interpreted as a 669 module), and of course Ultimate SoundTracker modules, which have no magic bytes at all. -* Avoid use of functions tagged with [[deprecated]]. +* Avoid use of functions tagged with `[[deprecated]]`. Probing ------- @@ -85,7 +85,7 @@ the first `CSoundFile::ProbeRecommendedSize` bytes of the file. any file that would normally be accepted by the loader. In particular, this means that any header checks must not be any more aggressive than they would be in the real loader (hence it is a good idea to not copy-paste this code but - rather put it in a separate function), and the minimum additional size passed + rather put it in a reusable function), and the minimum additional size passed to `CSoundFile::ProbeAdditionalSize` must not be higher than the biggest size that would cause a hard failure (i.e. returning `false`) in the module loader. * Probing functions **may** return ProbeSuccess for files that would be rejected @@ -103,5 +103,8 @@ that need to be updated: * Run `build/regenerate_vs_projects.sh` / `build/regenerate_vs_projects.cmd` (depending on your platform) * Add file extension to `installer/filetypes-*.iss`. -* Add file extension to `CTrackApp::OpenModulesDialog` in `mptrack/Mptrack.cpp`. +* Add file extension to `CTrackApp::OpenModulesDialog` in `mptrack/Mptrack.cpp` + as required (e.g. if it's a compressed container format or if it's relevant + enough to be mentioned). * Add format information to `soundlib/Tables.cpp`. +* Add magic bytes to `contrib/fuzzing/all_formats.dict`. diff --git a/Frameworks/OpenMPT/OpenMPT/examples/libopenmpt_example_c.c b/Frameworks/OpenMPT/OpenMPT/examples/libopenmpt_example_c.c index 368787f34..73de969b9 100644 --- a/Frameworks/OpenMPT/OpenMPT/examples/libopenmpt_example_c.c +++ b/Frameworks/OpenMPT/OpenMPT/examples/libopenmpt_example_c.c @@ -72,10 +72,10 @@ #define BUFFERSIZE 480 #define SAMPLERATE 48000 -static int16_t left[BUFFERSIZE]; -static int16_t right[BUFFERSIZE]; -static int16_t * const buffers[2] = { left, right }; -static int16_t interleaved_buffer[BUFFERSIZE * 2]; +static float left[BUFFERSIZE]; +static float right[BUFFERSIZE]; +static float * const buffers[2] = { left, right }; +static float interleaved_buffer[BUFFERSIZE * 2]; static int is_interleaved = 0; static void libopenmpt_example_logfunc( const char * message, void * userdata ) { @@ -208,10 +208,10 @@ int main( int argc, char * argv[] ) { } pa_initialized = 1; - pa_error = Pa_OpenDefaultStream( &stream, 0, 2, paInt16 | paNonInterleaved, SAMPLERATE, paFramesPerBufferUnspecified, NULL, NULL ); + pa_error = Pa_OpenDefaultStream( &stream, 0, 2, paFloat32 | paNonInterleaved, SAMPLERATE, paFramesPerBufferUnspecified, NULL, NULL ); if ( pa_error == paSampleFormatNotSupported ) { is_interleaved = 1; - pa_error = Pa_OpenDefaultStream( &stream, 0, 2, paInt16, SAMPLERATE, paFramesPerBufferUnspecified, NULL, NULL ); + pa_error = Pa_OpenDefaultStream( &stream, 0, 2, paFloat32, SAMPLERATE, paFramesPerBufferUnspecified, NULL, NULL ); } if ( pa_error != paNoError ) { fprintf( stderr, "Error: %s\n", "Pa_OpenStream() failed." ); @@ -231,7 +231,7 @@ int main( int argc, char * argv[] ) { while ( 1 ) { openmpt_module_error_clear( mod ); - count = is_interleaved ? openmpt_module_read_interleaved_stereo( mod, SAMPLERATE, BUFFERSIZE, interleaved_buffer ) : openmpt_module_read_stereo( mod, SAMPLERATE, BUFFERSIZE, left, right ); + count = is_interleaved ? openmpt_module_read_interleaved_float_stereo( mod, SAMPLERATE, BUFFERSIZE, interleaved_buffer ) : openmpt_module_read_float_stereo( mod, SAMPLERATE, BUFFERSIZE, left, right ); mod_err = openmpt_module_error_get_last( mod ); mod_err_str = openmpt_module_error_get_last_message( mod ); if ( mod_err != OPENMPT_ERROR_OK ) { diff --git a/Frameworks/OpenMPT/OpenMPT/examples/libopenmpt_example_c_mem.c b/Frameworks/OpenMPT/OpenMPT/examples/libopenmpt_example_c_mem.c index 1e7184b54..309eae27e 100644 --- a/Frameworks/OpenMPT/OpenMPT/examples/libopenmpt_example_c_mem.c +++ b/Frameworks/OpenMPT/OpenMPT/examples/libopenmpt_example_c_mem.c @@ -44,10 +44,10 @@ #define BUFFERSIZE 480 #define SAMPLERATE 48000 -static int16_t left[BUFFERSIZE]; -static int16_t right[BUFFERSIZE]; -static int16_t * const buffers[2] = { left, right }; -static int16_t interleaved_buffer[BUFFERSIZE * 2]; +static float left[BUFFERSIZE]; +static float right[BUFFERSIZE]; +static float * const buffers[2] = { left, right }; +static float interleaved_buffer[BUFFERSIZE * 2]; static int is_interleaved = 0; static void libopenmpt_example_logfunc( const char * message, void * userdata ) { @@ -254,10 +254,10 @@ int main( int argc, char * argv[] ) { pa_initialized = 1; is_interleaved = 0; - pa_error = Pa_OpenDefaultStream( &stream, 0, 2, paInt16 | paNonInterleaved, SAMPLERATE, paFramesPerBufferUnspecified, NULL, NULL ); + pa_error = Pa_OpenDefaultStream( &stream, 0, 2, paFloat32 | paNonInterleaved, SAMPLERATE, paFramesPerBufferUnspecified, NULL, NULL ); if ( pa_error == paSampleFormatNotSupported ) { is_interleaved = 1; - pa_error = Pa_OpenDefaultStream( &stream, 0, 2, paInt16, SAMPLERATE, paFramesPerBufferUnspecified, NULL, NULL ); + pa_error = Pa_OpenDefaultStream( &stream, 0, 2, paFloat32, SAMPLERATE, paFramesPerBufferUnspecified, NULL, NULL ); } if ( pa_error != paNoError ) { fprintf( stderr, "Error: %s\n", "Pa_OpenStream() failed." ); @@ -277,7 +277,7 @@ int main( int argc, char * argv[] ) { while ( 1 ) { openmpt_module_error_clear( mod ); - count = is_interleaved ? openmpt_module_read_interleaved_stereo( mod, SAMPLERATE, BUFFERSIZE, interleaved_buffer ) : openmpt_module_read_stereo( mod, SAMPLERATE, BUFFERSIZE, left, right ); + count = is_interleaved ? openmpt_module_read_interleaved_float_stereo( mod, SAMPLERATE, BUFFERSIZE, interleaved_buffer ) : openmpt_module_read_float_stereo( mod, SAMPLERATE, BUFFERSIZE, left, right ); mod_err = openmpt_module_error_get_last( mod ); mod_err_str = openmpt_module_error_get_last_message( mod ); if ( mod_err != OPENMPT_ERROR_OK ) { diff --git a/Frameworks/OpenMPT/OpenMPT/examples/libopenmpt_example_c_unsafe.c b/Frameworks/OpenMPT/OpenMPT/examples/libopenmpt_example_c_unsafe.c index 78b460eda..7eccf8fe5 100644 --- a/Frameworks/OpenMPT/OpenMPT/examples/libopenmpt_example_c_unsafe.c +++ b/Frameworks/OpenMPT/OpenMPT/examples/libopenmpt_example_c_unsafe.c @@ -46,9 +46,9 @@ #define BUFFERSIZE 480 #define SAMPLERATE 48000 -static int16_t left[BUFFERSIZE]; -static int16_t right[BUFFERSIZE]; -static int16_t * const buffers[2] = { left, right }; +static float left[BUFFERSIZE]; +static float right[BUFFERSIZE]; +static float * const buffers[2] = { left, right }; #if defined( __DJGPP__ ) /* clang-format off */ @@ -88,10 +88,10 @@ int main( int argc, char * argv[] ) { #endif fclose( file ); Pa_Initialize(); - Pa_OpenDefaultStream( &stream, 0, 2, paInt16 | paNonInterleaved, SAMPLERATE, paFramesPerBufferUnspecified, NULL, NULL ); + Pa_OpenDefaultStream( &stream, 0, 2, paFloat32 | paNonInterleaved, SAMPLERATE, paFramesPerBufferUnspecified, NULL, NULL ); Pa_StartStream( stream ); while ( 1 ) { - count = openmpt_module_read_stereo( mod, SAMPLERATE, BUFFERSIZE, left, right ); + count = openmpt_module_read_float_stereo( mod, SAMPLERATE, BUFFERSIZE, left, right ); if ( count == 0 ) { break; } diff --git a/Frameworks/OpenMPT/OpenMPT/include/stb_vorbis/OpenMPT.txt b/Frameworks/OpenMPT/OpenMPT/include/stb_vorbis/OpenMPT.txt index e723225a3..ea576e7c5 100644 --- a/Frameworks/OpenMPT/OpenMPT/include/stb_vorbis/OpenMPT.txt +++ b/Frameworks/OpenMPT/OpenMPT/include/stb_vorbis/OpenMPT.txt @@ -3,6 +3,7 @@ https://github.com/nothings/stb/blob/master/stb_vorbis.c v1.22 commit 5a0bb8b1c1b1ca3f4e2485f4114c1c8ea021b781 (2021-07-12) Modifications: + * has been applied. * Use of alloca has been replaced with malloc, as alloca is not in C99 and fails to compile. * Macro redefinition of alloca with mingw-w64 has been fixed. @@ -10,4 +11,3 @@ Modifications: For building, premake is used to generate Visual Studio project files. See ../build/premake/ for details. - diff --git a/Frameworks/OpenMPT/OpenMPT/include/stb_vorbis/stb_vorbis.c b/Frameworks/OpenMPT/OpenMPT/include/stb_vorbis/stb_vorbis.c index 1d4deecb8..20c8154b6 100644 --- a/Frameworks/OpenMPT/OpenMPT/include/stb_vorbis/stb_vorbis.c +++ b/Frameworks/OpenMPT/OpenMPT/include/stb_vorbis/stb_vorbis.c @@ -1410,7 +1410,7 @@ static int set_file_offset(stb_vorbis *f, unsigned int loc) #endif f->eof = 0; if (USE_MEMORY(f)) { - if (f->stream_start + loc >= f->stream_end || f->stream_start + loc < f->stream_start) { + if (loc >= f->stream_len) { f->stream = f->stream_end; f->eof = 1; return 0; diff --git a/Frameworks/OpenMPT/OpenMPT/libopenmpt/bindings/freebasic/libopenmpt.bi b/Frameworks/OpenMPT/OpenMPT/libopenmpt/bindings/freebasic/libopenmpt.bi index f5acb7871..13c42d2b8 100644 --- a/Frameworks/OpenMPT/OpenMPT/libopenmpt/bindings/freebasic/libopenmpt.bi +++ b/Frameworks/OpenMPT/OpenMPT/libopenmpt/bindings/freebasic/libopenmpt.bi @@ -775,6 +775,25 @@ Declare Function openmpt_module_select_subsong(ByVal module As openmpt_module Pt '/ Declare Function openmpt_module_get_selected_subsong(ByVal module As openmpt_module Ptr) As Long +/'* \brief Get the restart order of the specified sub-song + + \param module The module handle to work on. + \param subsong Index of the sub-song to retrieve the restart position from. + \return The restart order of the specified sub-song. This is the order to which playback returns after the last pattern row of the song has been played. -1 is returned if if sub-song is not in range [0,openmpt_module_get_num_subsongs()[ + \sa openmpt_module_get_restart_row + \since 0.8.0 +'/ +Declare Function openmpt_module_get_restart_order(ByVal module As openmpt_module Ptr, ByVal subsong As Long) As Long +/'* \brief Get the restart row of the specified sub-song + + \param module The module handle to work on. + \param subsong Index of the sub-song to retrieve the restart position from. + \return The restart row of the specified sub-song. This is the first played row of the order to which playback returns after the last pattern row of the song has been played. -1 is returned if if sub-song is not in range [0,openmpt_module_get_num_subsongs()[ + \sa openmpt_module_get_restart_order + \since 0.8.0 +'/ +Declare Function openmpt_module_get_restart_row(ByVal module As openmpt_module Ptr, ByVal subsong As Long) As Long + /'* \brief Set Repeat Count \param module The module handle to work on. @@ -806,6 +825,18 @@ Declare Function openmpt_module_get_repeat_count(ByVal module As openmpt_module '/ Declare Function openmpt_module_get_duration_seconds(ByVal module As openmpt_module Ptr) As Double +/'* \brief Get approximate playback time in seconds at given position + + \param module The module handle to work on. + \param order The order position at which the time should be retrieved. + \param row The pattern row number at which the time should be retrieved. + \return Approximate playback time in seconds of current sub-song at the start of the given order and row combination. Negative if the position does not exist, or the pattern data is too complex to evaluate. + \remarks If an order / row combination is played multiple times (e.g. due the pattern loops), the first occurence of this position is returned. + \since 0.8.0 +'/ +Declare Function openmpt_module_get_time_at_position(ByVal module As openmpt_module Ptr, ByVal order As Long, ByVal row As Long) As Double + + /'* \brief Set approximate current song position \param module The module handle to work on. @@ -1273,6 +1304,39 @@ Declare Function openmpt_module_get_sample_name_ Alias "openmpt_module_get_sampl '/ Declare Function openmpt_module_get_order_pattern(ByVal module As openmpt_module Ptr, ByVal order As Long) As Long +/'* \brief Check if specified order is a skip ("+++") item + + \param order The order index to check. + \return Returns non-zero value if the pattern index at the given order position represents a skip item. During playback, this item is ignored and playback resumes at the next order list item. + \sa openmpt_module_is_order_stop_entry, openmpt_module_is_pattern_skip_item + \since 0.8.0 +'/ +Declare Function openmpt_module_is_order_skip_entry(ByVal module As openmpt_module Ptr, ByVal order As Long) As Long +/'* \brief Check if specified pattern index is a skip ("+++") item + + \param pattern The pattern index to check. + \return Returns non-zero value if the pattern index represents a skip item. During playback, this item is ignored and playback resumes at the next order list item. + \sa openmpt_module_is_pattern_stop_item, openmpt_module_is_order_skip_entry, openmpt_module_get_order_pattern + \since 0.8.0 +'/ +Declare Function openmpt_module_is_pattern_skip_item(ByVal module As openmpt_module Ptr, ByVal pattern As Long) As Long +/'* \brief Check if specified order is a stop ("---") item + + \param order The order index to check. + \return Returns non-zero value if the pattern index at the given order position represents a stop item. When this item is reached, playback continues at the restart position of the current sub-song. + \sa openmpt_module_is_order_skip_entry, openmpt_module_is_pattern_stop_item + \since 0.8.0 +'/ +Declare Function openmpt_module_is_order_stop_entry(ByVal module As openmpt_module Ptr, ByVal order As Long) As Long +/'* \brief Check if specified pattern index is a stop ("---") item + + \param pattern The pattern index to check. + \return Returns non-zero value if the pattern index represents a stop item. When this item is reached, playback continues at the restart position of the current sub-song. + \sa openmpt_module_is_pattern_skip_item, openmpt_module_is_order_stop_entry, openmpt_module_get_order_pattern + \since 0.8.0 +'/ +Declare Function openmpt_module_is_pattern_stop_item(ByVal module As openmpt_module Ptr, ByVal pattern As Long) As Long + /'* \brief Get the number of rows in a pattern \param module The module handle to work on. @@ -1281,6 +1345,26 @@ Declare Function openmpt_module_get_order_pattern(ByVal module As openmpt_module '/ Declare Function openmpt_module_get_pattern_num_rows(ByVal module As openmpt_module Ptr, ByVal pattern As Long) As Long +/'* \brief Get the rows per beat of a pattern + + \param mod The module handle to work on. + \param pattern The pattern whose time signature should be retrieved. + \return The rows per beat of the given pattern. If the pattern does not exist or the time signature is not defined, 0 is returned. + \remarks Many module formats lack time signature metadata. In this case, the returned value may be an incorrect estimation. + \since 0.8.0 +'/ +Declare Function openmpt_module_get_pattern_rows_per_beat(ByVal module As openmpt_module Ptr, ByVal pattern As Long) As Long + +/'* \brief Get the rows per measure of a pattern + + \param mod The module handle to work on. + \param pattern The pattern whose time signature should be retrieved. + \return The rows per measure of the given pattern. If the pattern does not exist or the time signature is not defined, 0 is returned. + \remarks Many module formats lack time signature metadata. In this case, the returned value may be an incorrect estimation. + \since 0.8.0 +'/ +Declare Function openmpt_module_get_pattern_rows_per_measure(ByVal module As openmpt_module Ptr, ByVal pattern As Long) As Long + /'* \brief Get raw pattern content \param module The module handle to work on. diff --git a/Frameworks/OpenMPT/OpenMPT/libopenmpt/doc/in_openmpt.txt b/Frameworks/OpenMPT/OpenMPT/libopenmpt/doc/in_openmpt.txt deleted file mode 100644 index e69de29bb..000000000 diff --git a/Frameworks/OpenMPT/OpenMPT/libopenmpt/doc/xmp-openmpt.txt b/Frameworks/OpenMPT/OpenMPT/libopenmpt/doc/xmp-openmpt.txt deleted file mode 100644 index e69de29bb..000000000 diff --git a/Frameworks/OpenMPT/OpenMPT/libopenmpt/dox/changelog.md b/Frameworks/OpenMPT/OpenMPT/libopenmpt/dox/changelog.md deleted file mode 100644 index e69de29bb..000000000 diff --git a/Frameworks/OpenMPT/OpenMPT/libopenmpt/dox/dependencies.md b/Frameworks/OpenMPT/OpenMPT/libopenmpt/dox/dependencies.md deleted file mode 100644 index e69de29bb..000000000 diff --git a/Frameworks/OpenMPT/OpenMPT/libopenmpt/dox/gettingstarted.md b/Frameworks/OpenMPT/OpenMPT/libopenmpt/dox/gettingstarted.md deleted file mode 100644 index e69de29bb..000000000 diff --git a/Frameworks/OpenMPT/OpenMPT/libopenmpt/dox/index.dox b/Frameworks/OpenMPT/OpenMPT/libopenmpt/dox/index.dox deleted file mode 100644 index e69de29bb..000000000 diff --git a/Frameworks/OpenMPT/OpenMPT/libopenmpt/dox/packaging.md b/Frameworks/OpenMPT/OpenMPT/libopenmpt/dox/packaging.md deleted file mode 100644 index e69de29bb..000000000 diff --git a/Frameworks/OpenMPT/OpenMPT/libopenmpt/dox/tests.md b/Frameworks/OpenMPT/OpenMPT/libopenmpt/dox/tests.md deleted file mode 100644 index e69de29bb..000000000 diff --git a/Frameworks/OpenMPT/OpenMPT/libopenmpt/in_openmpt.cpp b/Frameworks/OpenMPT/OpenMPT/libopenmpt/in_openmpt.cpp deleted file mode 100644 index e69de29bb..000000000 diff --git a/Frameworks/OpenMPT/OpenMPT/libopenmpt/in_openmpt/in_openmpt.cpp b/Frameworks/OpenMPT/OpenMPT/libopenmpt/in_openmpt/in_openmpt.cpp index d697b9fd4..7075e1b57 100644 --- a/Frameworks/OpenMPT/OpenMPT/libopenmpt/in_openmpt/in_openmpt.cpp +++ b/Frameworks/OpenMPT/OpenMPT/libopenmpt/in_openmpt/in_openmpt.cpp @@ -212,11 +212,7 @@ static std::basic_string generate_infotext( const std::basic_stringsettings, hwndParent, TEXT(SHORT_TITLE) ); -#else - static_cast(hwndParent); -#endif apply_options(); } @@ -227,18 +223,9 @@ static void about( HWND hwndParent ) { about << " OpenMPT version " << openmpt::string::get( "core_version" ) << std::endl; about << std::endl; about << openmpt::string::get( "contact" ) << std::endl; - about << std::endl; - about << "Show full credits?" << std::endl; - if ( MessageBox( hwndParent, StringToWINAPI( StringDecode( about.str(), CP_UTF8 ) ).c_str(), TEXT(SHORT_TITLE), MB_ICONINFORMATION | MB_YESNOCANCEL | MB_DEFBUTTON1 ) != IDYES ) { - return; - } std::ostringstream credits; - credits << openmpt::string::get( "credits" ); -#if 1 - libopenmpt::plugin::gui_show_file_info( hwndParent, TEXT(SHORT_TITLE), StringToWINAPI( StringReplace( StringDecode( credits.str(), CP_UTF8 ), L"\n", L"\r\n" ) ) ); -#else - MessageBox( hwndParent, StringToWINAPI( StringReplace(StringDecode(credits.str(), CP_UTF8 ), L"\n", L"\r\n" ) ).c_str(), TEXT(SHORT_TITLE), MB_OK ); -#endif + credits << openmpt::string::get( "credits" ) << std::endl; + libopenmpt::plugin::gui_show_about( hwndParent, TEXT(SHORT_TITLE), StringReplace( StringToWINAPI( StringDecode( about.str(), CP_UTF8 ) ), TEXT("\n"), TEXT("\r\n") ), StringReplace( StringToWINAPI( StringDecode( credits.str(), CP_UTF8 ) ), TEXT("\n"), TEXT("\r\n") ) ); } static void init() { diff --git a/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt.h b/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt.h index 88425b5bb..f06e8acd5 100644 --- a/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt.h +++ b/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt.h @@ -875,6 +875,26 @@ LIBOPENMPT_API int openmpt_module_select_subsong( openmpt_module * mod, int32_t * \since 0.3.0 */ LIBOPENMPT_API int32_t openmpt_module_get_selected_subsong( openmpt_module * mod ); + +/*! \brief Get the restart order of the specified sub-song + * + * \param mod The module handle to work on. + * \param subsong Index of the sub-song to retrieve the restart position from. + * \return The restart order of the specified sub-song. This is the order to which playback returns after the last pattern row of the song has been played. -1 is returned if if sub-song is not in range [0,openmpt_module_get_num_subsongs()[ + * \sa openmpt_module_get_restart_row + * \since 0.8.0 + */ +LIBOPENMPT_API int32_t openmpt_module_get_restart_order( openmpt_module * mod, int32_t subsong ); +/*! \brief Get the restart row of the specified sub-song + * + * \param mod The module handle to work on. + * \param subsong Index of the sub-song to retrieve the restart position from. + * \return The restart row of the specified sub-song. This is the first played row of the order to which playback returns after the last pattern row of the song has been played. -1 is returned if if sub-song is not in range [0,openmpt_module_get_num_subsongs()[ + * \sa openmpt_module_get_restart_order + * \since 0.8.0 + */ +LIBOPENMPT_API int32_t openmpt_module_get_restart_row( openmpt_module * mod, int32_t subsong ); + /*! \brief Set Repeat Count * * \param mod The module handle to work on. @@ -905,6 +925,17 @@ LIBOPENMPT_API int32_t openmpt_module_get_repeat_count( openmpt_module * mod ); */ LIBOPENMPT_API double openmpt_module_get_duration_seconds( openmpt_module * mod ); +/*! \brief Get approximate playback time in seconds at given position + * + * \param mod The module handle to work on. + * \param order The order position at which the time should be retrieved. + * \param row The pattern row number at which the time should be retrieved. + * \return Approximate playback time in seconds of current sub-song at the start of the given order and row combination. Negative if the position does not exist, or the pattern data is too complex to evaluate. + * \remarks If an order / row combination is played multiple times (e.g. due the pattern loops), the first occurence of this position is returned. + * \since 0.8.0 + */ +LIBOPENMPT_API double openmpt_module_get_time_at_position( openmpt_module * mod, int32_t order, int32_t row ); + /*! \brief Set approximate current song position * * \param mod The module handle to work on. @@ -1330,6 +1361,44 @@ LIBOPENMPT_API const char * openmpt_module_get_sample_name( openmpt_module * mod * \return The pattern index found at the given order position of the current sequence. */ LIBOPENMPT_API int32_t openmpt_module_get_order_pattern( openmpt_module * mod, int32_t order ); + +/*! \brief Check if specified order is a skip ("+++") item + * + * \param mod The module handle to work on. + * \param order The order index to check. + * \return Returns non-zero value if the pattern index at the given order position represents a skip item. During playback, this item is ignored and playback resumes at the next order list item. + * \sa openmpt_module_is_order_stop_entry, openmpt_module_is_pattern_skip_item + * \since 0.8.0 + */ +LIBOPENMPT_API int openmpt_module_is_order_skip_entry( openmpt_module * mod, int32_t order ); +/*! \brief Check if specified pattern index is a skip ("+++") item + * + * \param mod The module handle to work on. + * \param pattern The pattern index to check. + * \return Returns non-zero value if the pattern index represents a skip item. During playback, this item is ignored and playback resumes at the next order list item. + * \sa openmpt_module_is_pattern_stop_item, openmpt_module_is_order_skip_entry, openmpt_module_get_order_pattern + * \since 0.8.0 + */ +LIBOPENMPT_API int openmpt_module_is_pattern_skip_item( openmpt_module * mod, int32_t pattern ); +/*! \brief Check if specified order is a stop ("---") item + * + * \param mod The module handle to work on. + * \param order The order index to check. + * \return Returns non-zero value if the pattern index at the given order position represents a stop item. When this item is reached, playback continues at the restart position of the current sub-song. + * \sa openmpt_module_is_order_skip_entry, openmpt_module_is_pattern_stop_item + * \since 0.8.0 + */ +LIBOPENMPT_API int openmpt_module_is_order_stop_entry( openmpt_module * mod, int32_t order ); +/*! \brief Check if specified pattern index is a stop ("---") item + * + * \param mod The module handle to work on. + * \param pattern The pattern index to check. + * \return Returns non-zero value if the pattern index represents a stop item. When this item is reached, playback continues at the restart position of the current sub-song. + * \sa openmpt_module_is_pattern_skip_item, openmpt_module_is_order_stop_entry, openmpt_module_get_order_pattern + * \since 0.8.0 + */ +LIBOPENMPT_API int openmpt_module_is_pattern_stop_item( openmpt_module * mod, int32_t pattern ); + /*! \brief Get the number of rows in a pattern * * \param mod The module handle to work on. @@ -1338,6 +1407,25 @@ LIBOPENMPT_API int32_t openmpt_module_get_order_pattern( openmpt_module * mod, i */ LIBOPENMPT_API int32_t openmpt_module_get_pattern_num_rows( openmpt_module * mod, int32_t pattern ); +/*! \brief Get the rows per beat of a pattern + * + * \param mod The module handle to work on. + * \param pattern The pattern whose time signature should be retrieved. + * \return The rows per beat of the given pattern. If the pattern does not exist or the time signature is not defined, 0 is returned. + * \remarks Many module formats lack time signature metadata. In this case, the returned value may be an incorrect estimation. + * \since 0.8.0 + */ +LIBOPENMPT_API int32_t openmpt_module_get_pattern_rows_per_beat( openmpt_module * mod, int32_t pattern ); + +/*! \brief Get the rows per measure of a pattern + * + * \param mod The module handle to work on. + * \param pattern The pattern whose time signature should be retrieved. + * \return The rows per measure of the given pattern. If the pattern does not exist or the time signature is not defined, 0 is returned. + * \remarks Many module formats lack time signature metadata. In this case, the returned value may be an incorrect estimation. + */ +LIBOPENMPT_API int32_t openmpt_module_get_pattern_rows_per_measure( openmpt_module * mod, int32_t pattern ); + /*! \brief Get raw pattern content * * \param mod The module handle to work on. diff --git a/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt.hpp b/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt.hpp index 8bcd47a6c..8acc35a0a 100644 --- a/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt.hpp +++ b/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt.hpp @@ -592,6 +592,26 @@ public: \since 0.3.0 */ LIBOPENMPT_CXX_API_MEMBER std::int32_t get_selected_subsong() const; + + //! Get the restart order of the specified sub-song + /*! + \param subsong Index of the sub-song to retrieve the restart position from. + \return The restart order of the specified sub-song. This is the order to which playback returns after the last pattern row of the song has been played. + \throws openmpt::exception Throws an exception derived from openmpt::exception if sub-song is not in range [0,openmpt::module::get_num_subsongs()[ + \sa openmpt::module::get_restart_row + \since 0.8.0 + */ + LIBOPENMPT_CXX_API_MEMBER std::int32_t get_restart_order( std::int32_t subsong ) const; + //! Get the restart row of the specified sub-song + /*! + \param subsong Index of the sub-song to retrieve the restart position from. + \return The restart row of the specified sub-song. This is the first played row of the order to which playback returns after the last pattern row of the song has been played. + \throws openmpt::exception Throws an exception derived from openmpt::exception if sub-song is not in range [0,openmpt::module::get_num_subsongs()[ + \sa openmpt::module::get_restart_order + \since 0.8.0 + */ + LIBOPENMPT_CXX_API_MEMBER std::int32_t get_restart_row( std::int32_t subsong ) const; + //! Set Repeat Count /*! \param repeat_count Repeat Count @@ -618,6 +638,16 @@ public: */ LIBOPENMPT_CXX_API_MEMBER double get_duration_seconds() const; + //! Get approximate playback time in seconds at given position + /*! + \param order The order position at which the time should be retrieved. + \param row The pattern row number at which the time should be retrieved. + \return Approximate playback time in seconds of current sub-song at the start of the given order and row combination. Negative if the position does not exist, or the pattern data is too complex to evaluate. + \remarks If an order / row combination is played multiple times (e.g. due the pattern loops), the first occurence of this position is returned. + \since 0.8.0 + */ + LIBOPENMPT_CXX_API_MEMBER double get_time_at_position( std::int32_t order, std::int32_t row ) const; + //! Set approximate current song position /*! \param seconds Seconds to seek to. If seconds is out of range, the position gets set to song start or end respectively. @@ -989,6 +1019,39 @@ public: */ LIBOPENMPT_CXX_API_MEMBER std::int32_t get_order_pattern( std::int32_t order ) const; + //! Check if specified order is a skip ("+++") item + /*! + \param order The order index to check. + \return Returns true if the pattern index at the given order position represents a skip item. During playback, this item is ignored and playback resumes at the next order list item. + \sa openmpt::module::is_order_stop_entry, openmpt::module::is_pattern_skip_item + \since 0.8.0 + */ + LIBOPENMPT_CXX_API_MEMBER bool is_order_skip_entry( std::int32_t order ) const ; + //! Check if specified pattern index is a skip ("+++") item + /*! + \param pattern The pattern index to check. + \return Returns true if the pattern index represents a skip item. During playback, this item is ignored and playback resumes at the next order list item. + \sa openmpt::module::is_pattern_stop_item, openmpt::module::is_order_skip_entry, openmpt::module::get_order_pattern + \since 0.8.0 + */ + LIBOPENMPT_CXX_API_MEMBER bool is_pattern_skip_item( std::int32_t pattern ) const; + //! Check if specified order is a stop ("---") item + /*! + \param order The order index to check. + \return Returns true if the pattern index at the given order position represents a stop item. When this item is reached, playback continues at the restart position of the current sub-song. + \sa openmpt::module::is_order_skip_entry, openmpt::module::is_pattern_stop_item + \since 0.8.0 + */ + LIBOPENMPT_CXX_API_MEMBER bool is_order_stop_entry( std::int32_t order ) const; + //! Check if specified pattern index is a stop ("---") item + /*! + \param pattern The pattern index to check. + \return Returns true if the pattern index represents a stop item. When this item is reached, playback continues at the restart position of the current sub-song. + \sa openmpt::module::is_pattern_skip_item, openmpt::module::is_order_stop_entry, openmpt::module::get_order_pattern + \since 0.8.0 + */ + LIBOPENMPT_CXX_API_MEMBER bool is_pattern_stop_item( std::int32_t pattern ) const; + //! Get the number of rows in a pattern /*! \param pattern The pattern whose row count should be retrieved. @@ -996,6 +1059,24 @@ public: */ LIBOPENMPT_CXX_API_MEMBER std::int32_t get_pattern_num_rows( std::int32_t pattern ) const; + //! Get the rows per beat of a pattern + /*! + \param pattern The pattern whose time signature should be retrieved. + \return The rows per beat of the given pattern. If the pattern does not exist or the time signature is not defined, 0 is returned. + \remarks Many module formats lack time signature metadata. In this case, the returned value may be an incorrect estimation. + \since 0.8.0 + */ + LIBOPENMPT_CXX_API_MEMBER std::int32_t get_pattern_rows_per_beat( std::int32_t pattern ) const; + + //! Get the rows per measure of a pattern + /*! + \param pattern The pattern whose time signature should be retrieved. + \return The rows per measure of the given pattern. If the pattern does not exist or the time signature is not defined, 0 is returned. + \remarks Many module formats lack time signature metadata. In this case, the returned value may be an incorrect estimation. + \since 0.8.0 + */ + LIBOPENMPT_CXX_API_MEMBER std::int32_t get_pattern_rows_per_measure( std::int32_t pattern ) const; + //! Get raw pattern content /*! \param pattern The pattern whose data should be retrieved. diff --git a/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt_c.cpp b/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt_c.cpp index 9faab6a9a..519b2eb6b 100644 --- a/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt_c.cpp +++ b/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt_c.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -741,6 +742,26 @@ int32_t openmpt_module_get_selected_subsong( openmpt_module * mod ) { return -1; } +int32_t openmpt_module_get_restart_order( openmpt_module * mod, int32_t subsong ) { + try { + openmpt::interface::check_soundfile( mod ); + return mod->impl->get_restart_order( subsong ); + } catch ( ... ) { + openmpt::report_exception( __func__, mod ); + } + return -1; +} + +int32_t openmpt_module_get_restart_row( openmpt_module * mod, int32_t subsong ) { + try { + openmpt::interface::check_soundfile( mod ); + return mod->impl->get_restart_row( subsong ); + } catch ( ... ) { + openmpt::report_exception( __func__, mod ); + } + return -1; +} + int openmpt_module_set_repeat_count( openmpt_module * mod, int32_t repeat_count ) { try { openmpt::interface::check_soundfile( mod ); @@ -771,6 +792,16 @@ double openmpt_module_get_duration_seconds( openmpt_module * mod ) { return 0.0; } +double openmpt_module_get_time_at_position( openmpt_module * mod, int32_t order, int32_t row ) { + try { + openmpt::interface::check_soundfile( mod ); + return mod->impl->get_time_at_position( order, row ); + } catch ( ... ) { + openmpt::report_exception( __func__, mod ); + } + return -1.0; +} + double openmpt_module_set_position_seconds( openmpt_module * mod, double seconds ) { try { openmpt::interface::check_soundfile( mod ); @@ -1225,6 +1256,44 @@ int32_t openmpt_module_get_order_pattern( openmpt_module * mod, int32_t order ) return 0; } +int openmpt_module_is_order_skip_entry( openmpt_module * mod, int32_t order ) { + try { + openmpt::interface::check_soundfile( mod ); + return mod->impl->is_order_skip_entry( order ) ? 1 : 0; + } catch ( ... ) { + openmpt::report_exception( __func__, mod ); + } + return 0; +} +int openmpt_module_is_pattern_skip_item( openmpt_module * mod, int32_t pattern ) { + try { + openmpt::interface::check_soundfile( mod ); + return mod->impl->is_pattern_skip_item( pattern ) ? 1 : 0; + } catch ( ... ) { + openmpt::report_exception( __func__, mod ); + } + return 0; +} +int openmpt_module_is_order_stop_entry( openmpt_module * mod, int32_t order ) { + try { + openmpt::interface::check_soundfile( mod ); + return mod->impl->is_order_stop_entry( order ) ? 1 : 0; + } catch ( ... ) { + openmpt::report_exception( __func__, mod ); + } + return 0; +} +int openmpt_module_is_pattern_stop_item( openmpt_module * mod, int32_t pattern ) { + try { + openmpt::interface::check_soundfile( mod ); + return mod->impl->is_pattern_stop_item( pattern ) ? 1 : 0; + } catch ( ... ) { + openmpt::report_exception( __func__, mod ); + } + return 0; +} + + int32_t openmpt_module_get_pattern_num_rows( openmpt_module * mod, int32_t pattern ) { try { openmpt::interface::check_soundfile( mod ); @@ -1235,6 +1304,25 @@ int32_t openmpt_module_get_pattern_num_rows( openmpt_module * mod, int32_t patte return 0; } +int32_t openmpt_module_get_pattern_rows_per_beat( openmpt_module * mod, int32_t pattern ) { + try { + openmpt::interface::check_soundfile( mod ); + return mod->impl->get_pattern_rows_per_beat( pattern ); + } catch ( ... ) { + openmpt::report_exception( __func__, mod ); + } + return 0; +} +int32_t openmpt_module_get_pattern_rows_per_measure( openmpt_module * mod, int32_t pattern ) { + try { + openmpt::interface::check_soundfile( mod ); + return mod->impl->get_pattern_rows_per_measure( pattern ); + } catch ( ... ) { + openmpt::report_exception( __func__, mod ); + } + return 0; +} + uint8_t openmpt_module_get_pattern_row_channel_command( openmpt_module * mod, int32_t pattern, int32_t row, int32_t channel, int command ) { try { openmpt::interface::check_soundfile( mod ); @@ -1823,12 +1911,13 @@ int openmpt_module_ext_get_interface( openmpt_module_ext * mod_ext, const char * openmpt::interface::check_pointer( interface ); std::memset( interface, 0, interface_size ); int result = 0; - if ( !std::strcmp( interface_id, "" ) ) { + std::string_view interface_id_sv = interface_id; + if ( interface_id_sv == "" ) { result = 0; - } else if ( !std::strcmp( interface_id, LIBOPENMPT_EXT_C_INTERFACE_PATTERN_VIS ) && ( interface_size == sizeof( openmpt_module_ext_interface_pattern_vis ) ) ) { + } else if ( ( interface_id_sv == LIBOPENMPT_EXT_C_INTERFACE_PATTERN_VIS ) && ( interface_size == sizeof( openmpt_module_ext_interface_pattern_vis ) ) ) { openmpt_module_ext_interface_pattern_vis * i = static_cast< openmpt_module_ext_interface_pattern_vis * >( interface ); i->get_pattern_row_channel_volume_effect_type = &get_pattern_row_channel_volume_effect_type; i->get_pattern_row_channel_effect_type = &get_pattern_row_channel_effect_type; @@ -1836,7 +1925,7 @@ int openmpt_module_ext_get_interface( openmpt_module_ext * mod_ext, const char * - } else if ( !std::strcmp( interface_id, LIBOPENMPT_EXT_C_INTERFACE_INTERACTIVE ) && ( interface_size == sizeof( openmpt_module_ext_interface_interactive ) ) ) { + } else if ( ( interface_id_sv == LIBOPENMPT_EXT_C_INTERFACE_INTERACTIVE ) && ( interface_size == sizeof( openmpt_module_ext_interface_interactive ) ) ) { openmpt_module_ext_interface_interactive * i = static_cast< openmpt_module_ext_interface_interactive * >( interface ); i->set_current_speed = &set_current_speed; i->set_current_tempo = &set_current_tempo; @@ -1858,7 +1947,7 @@ int openmpt_module_ext_get_interface( openmpt_module_ext * mod_ext, const char * - } else if ( !std::strcmp( interface_id, LIBOPENMPT_EXT_C_INTERFACE_INTERACTIVE2 ) && ( interface_size == sizeof( openmpt_module_ext_interface_interactive2 ) ) ) { + } else if ( ( interface_id_sv == LIBOPENMPT_EXT_C_INTERFACE_INTERACTIVE2 ) && ( interface_size == sizeof( openmpt_module_ext_interface_interactive2 ) ) ) { openmpt_module_ext_interface_interactive2 * i = static_cast< openmpt_module_ext_interface_interactive2 * >( interface ); i->note_off = ¬e_off; i->note_fade = ¬e_fade; @@ -1870,7 +1959,7 @@ int openmpt_module_ext_get_interface( openmpt_module_ext * mod_ext, const char * - } else if ( !std::strcmp( interface_id, LIBOPENMPT_EXT_C_INTERFACE_INTERACTIVE3 ) && ( interface_size == sizeof( openmpt_module_ext_interface_interactive3 ) ) ) { + } else if ( ( interface_id_sv == LIBOPENMPT_EXT_C_INTERFACE_INTERACTIVE3 ) && ( interface_size == sizeof( openmpt_module_ext_interface_interactive3 ) ) ) { openmpt_module_ext_interface_interactive3 * i = static_cast< openmpt_module_ext_interface_interactive3 * >( interface ); i->set_current_tempo2 = &set_current_tempo2; result = 1; diff --git a/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt_cxx.cpp b/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt_cxx.cpp index 4290b7bed..1f6750219 100644 --- a/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt_cxx.cpp +++ b/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt_cxx.cpp @@ -240,6 +240,13 @@ std::int32_t module::get_selected_subsong() const { return impl->get_selected_subsong(); } +std::int32_t module::get_restart_order( std::int32_t subsong ) const { + return impl->get_restart_order( subsong ); +} +std::int32_t module::get_restart_row( std::int32_t subsong ) const { + return impl->get_restart_row( subsong ); +} + void module::set_repeat_count( std::int32_t repeat_count ) { impl->set_repeat_count( repeat_count ); } @@ -251,6 +258,10 @@ double module::get_duration_seconds() const { return impl->get_duration_seconds(); } +double module::get_time_at_position( std::int32_t order, std::int32_t row ) const { + return impl->get_time_at_position( order, row ); +} + double module::set_position_seconds( double seconds ) { return impl->set_position_seconds( seconds ); } @@ -389,10 +400,32 @@ std::vector module::get_sample_names() const { std::int32_t module::get_order_pattern( std::int32_t order ) const { return impl->get_order_pattern( order ); } + +bool module::is_order_skip_entry(std::int32_t order) const { + return impl->is_order_skip_entry( order ); +} +bool module::is_pattern_skip_item( std::int32_t pattern ) const { + return module_impl::is_pattern_skip_item( pattern ); +} +bool module::is_order_stop_entry( std::int32_t order ) const { + return impl->is_order_stop_entry( order ); +} +bool module::is_pattern_stop_item( std::int32_t pattern ) const { + return module_impl::is_pattern_stop_item( pattern ); +} + std::int32_t module::get_pattern_num_rows( std::int32_t pattern ) const { return impl->get_pattern_num_rows( pattern ); } +std::int32_t module::get_pattern_rows_per_beat( std::int32_t pattern ) const { + return impl->get_pattern_rows_per_beat( pattern ); +} + +std::int32_t module::get_pattern_rows_per_measure( std::int32_t pattern ) const { + return impl->get_pattern_rows_per_measure( pattern ); +} + std::uint8_t module::get_pattern_row_channel_command( std::int32_t pattern, std::int32_t row, std::int32_t channel, int command ) const { return impl->get_pattern_row_channel_command( pattern, row, channel, command ); } diff --git a/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt_ext_impl.cpp b/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt_ext_impl.cpp index 274ccf4e6..b6feca030 100644 --- a/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt_ext_impl.cpp +++ b/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt_ext_impl.cpp @@ -183,7 +183,7 @@ namespace openmpt { if ( volume < 0.0 || volume > 1.0 ) { throw openmpt::exception("invalid global volume"); } - m_sndFile->m_PlayState.Chn[channel].nGlobalVol = mpt::saturate_round(volume * 64.0); + m_sndFile->m_PlayState.Chn[channel].nGlobalVol = mpt::saturate_round(volume * 64.0); } double module_ext_impl::get_channel_volume( std::int32_t channel ) const { diff --git a/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt_impl.cpp b/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt_impl.cpp index ca5c7ad99..a0fa3bda0 100644 --- a/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt_impl.cpp +++ b/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt_impl.cpp @@ -46,7 +46,6 @@ #include "common/version.h" #include "common/misc_util.h" -#include "common/Dither.h" #include "common/FileReader.h" #include "common/Logging.h" #include "soundlib/Sndfile.h" @@ -77,6 +76,14 @@ MPT_WARNING("Warning: Platform (Windows) supports multi-threading, however the t MPT_WARNING("Warning: libopenmpt is known to trigger bad code generation with Clang 5..10 on powerpc (32bit) when using -O3. See .") #endif +#if defined(ENABLE_TESTS) +#if defined(MPT_COMPILER_QUIRK_WINDOWS_FSTREAM_NO_WCHAR) +#if MPT_GCC_BEFORE(9,1,0) +MPT_WARNING("Warning: MinGW with GCC earlier than 9.1 detected. Standard library does neither provide std::fstream wchar_t overloads nor std::filesystem with wchar_t support. Unicode filename support is thus unavailable.") +#endif // MPT_GCC_AT_LEAST(9,1,0) +#endif // MPT_COMPILER_QUIRK_WINDOWS_FSTREAM_NO_WCHAR +#endif // ENABLE_TESTS + #endif // !MPT_BUILD_SILENCE_LIBOPENMPT_CONFIGURATION_WARNINGS #if defined(MPT_ASSERT_HANDLER_NEEDED) && !defined(ENABLE_TESTS) @@ -224,7 +231,7 @@ std::string get_string( const std::string & key ) { return get_source_revision_string(); } else if ( key == "source_is_modified" ) { return OpenMPT::SourceInfo::Current().IsDirty() ? "1" : "0"; - } else if ( key == "source_has_mixed_revision" ) { + } else if ( key == "source_has_mixed_revisions" ) { return OpenMPT::SourceInfo::Current().HasMixedRevisions() ? "1" : "0"; } else if ( key == "source_is_package" ) { return OpenMPT::SourceInfo::Current().IsPackage() ? "1" : "0"; @@ -306,11 +313,13 @@ void module_impl::PushToCSoundFileLog( int loglevel, const std::string & text ) m_sndFile->AddToLog( static_cast( loglevel ), mpt::transcode( mpt::common_encoding::utf8, text ) ); } -module_impl::subsong_data::subsong_data( double duration, std::int32_t start_row, std::int32_t start_order, std::int32_t sequence ) +module_impl::subsong_data::subsong_data( double duration, std::int32_t start_row, std::int32_t start_order, std::int32_t sequence, std::int32_t restart_row, std::int32_t restart_order ) : duration(duration) , start_row(start_row) , start_order(start_order) , sequence(sequence) + , restart_row(restart_row) + , restart_order(restart_order) { return; } @@ -436,7 +445,7 @@ module_impl::subsongs_type module_impl::get_subsongs() const { for ( OpenMPT::SEQUENCEINDEX seq = 0; seq < m_sndFile->Order.GetNumSequences(); ++seq ) { const std::vector lengths = m_sndFile->GetLength( OpenMPT::eNoAdjust, OpenMPT::GetLengthTarget( true ).StartPos( seq, 0, 0 ) ); for ( const auto & l : lengths ) { - subsongs.push_back( subsong_data( l.duration, l.startRow, l.startOrder, seq ) ); + subsongs.push_back( subsong_data( l.duration, l.startRow, l.startOrder, seq, l.restartRow, l.restartOrder ) ); } } return subsongs; @@ -512,7 +521,7 @@ std::size_t module_impl::read_wrapper( std::size_t count, std::int16_t * left, s OpenMPT::AudioTargetBufferWithGain> target( mpt::audio_span_planar( buffers, valid_channels( buffers, std::size( buffers ) ), count ), *m_Dithers, m_Gain ); while ( count > 0 ) { std::size_t count_chunk = m_sndFile->Read( - static_cast( std::min( static_cast( count ), static_cast( std::numeric_limits::max() / 2 / 4 / 4 ) ) ), // safety margin / samplesize / channels + static_cast( std::min( static_cast( count ), static_cast( std::numeric_limits::max() / 2 / 4 / 4 ) ) ), // safety margin / samplesize / channels target ); if ( count_chunk == 0 ) { @@ -523,7 +532,7 @@ std::size_t module_impl::read_wrapper( std::size_t count, std::int16_t * left, s } if ( count_read == 0 && m_ctl_play_at_end == song_end_action::continue_song ) { // This is the song end, but allow the song or loop to restart on the next call - m_sndFile->m_SongFlags.reset(OpenMPT::SONG_ENDREACHED); + m_sndFile->m_PlayState.m_flags.reset(OpenMPT::SONG_ENDREACHED); } return count_read; } @@ -535,7 +544,7 @@ std::size_t module_impl::read_wrapper( std::size_t count, float * left, float * OpenMPT::AudioTargetBufferWithGain> target( mpt::audio_span_planar( buffers, valid_channels( buffers, std::size( buffers ) ), count ), *m_Dithers, m_Gain ); while ( count > 0 ) { std::size_t count_chunk = m_sndFile->Read( - static_cast( std::min( static_cast( count ), static_cast( std::numeric_limits::max() / 2 / 4 / 4 ) ) ), // safety margin / samplesize / channels + static_cast( std::min( static_cast( count ), static_cast( std::numeric_limits::max() / 2 / 4 / 4 ) ) ), // safety margin / samplesize / channels target ); if ( count_chunk == 0 ) { @@ -546,7 +555,7 @@ std::size_t module_impl::read_wrapper( std::size_t count, float * left, float * } if ( count_read == 0 && m_ctl_play_at_end == song_end_action::continue_song ) { // This is the song end, but allow the song or loop to restart on the next call - m_sndFile->m_SongFlags.reset(OpenMPT::SONG_ENDREACHED); + m_sndFile->m_PlayState.m_flags.reset(OpenMPT::SONG_ENDREACHED); } return count_read; } @@ -557,7 +566,7 @@ std::size_t module_impl::read_interleaved_wrapper( std::size_t count, std::size_ OpenMPT::AudioTargetBufferWithGain> target( mpt::audio_span_interleaved( interleaved, channels, count ), *m_Dithers, m_Gain ); while ( count > 0 ) { std::size_t count_chunk = m_sndFile->Read( - static_cast( std::min( static_cast( count ), static_cast( std::numeric_limits::max() / 2 / 4 / 4 ) ) ), // safety margin / samplesize / channels + static_cast( std::min( static_cast( count ), static_cast( std::numeric_limits::max() / 2 / 4 / 4 ) ) ), // safety margin / samplesize / channels target ); if ( count_chunk == 0 ) { @@ -568,7 +577,7 @@ std::size_t module_impl::read_interleaved_wrapper( std::size_t count, std::size_ } if ( count_read == 0 && m_ctl_play_at_end == song_end_action::continue_song ) { // This is the song end, but allow the song or loop to restart on the next call - m_sndFile->m_SongFlags.reset(OpenMPT::SONG_ENDREACHED); + m_sndFile->m_PlayState.m_flags.reset(OpenMPT::SONG_ENDREACHED); } return count_read; } @@ -579,7 +588,7 @@ std::size_t module_impl::read_interleaved_wrapper( std::size_t count, std::size_ OpenMPT::AudioTargetBufferWithGain> target( mpt::audio_span_interleaved( interleaved, channels, count ), *m_Dithers, m_Gain ); while ( count > 0 ) { std::size_t count_chunk = m_sndFile->Read( - static_cast( std::min( static_cast( count ), static_cast( std::numeric_limits::max() / 2 / 4 / 4 ) ) ), // safety margin / samplesize / channels + static_cast( std::min( static_cast( count ), static_cast( std::numeric_limits::max() / 2 / 4 / 4 ) ) ), // safety margin / samplesize / channels target ); if ( count_chunk == 0 ) { @@ -590,7 +599,7 @@ std::size_t module_impl::read_interleaved_wrapper( std::size_t count, std::size_ } if ( count_read == 0 && m_ctl_play_at_end == song_end_action::continue_song ) { // This is the song end, but allow the song or loop to restart on the next call - m_sndFile->m_SongFlags.reset(OpenMPT::SONG_ENDREACHED); + m_sndFile->m_PlayState.m_flags.reset(OpenMPT::SONG_ENDREACHED); } return count_read; } @@ -1075,6 +1084,15 @@ double module_impl::get_duration_seconds() const { } return subsongs[m_current_subsong].duration; } + +double module_impl::get_time_at_position( std::int32_t order, std::int32_t row ) const { + const auto t = m_sndFile->GetLength( OpenMPT::eNoAdjust, OpenMPT::GetLengthTarget( static_cast( order ), static_cast( row ) ) ).back(); + if ( t.targetReached ) + return t.duration; + else + return -1.0; +} + void module_impl::select_subsong( std::int32_t subsong ) { std::unique_ptr subsongs_temp = has_subsongs_inited() ? std::unique_ptr() : std::make_unique( get_subsongs() ); const subsongs_type & subsongs = has_subsongs_inited() ? m_subsongs : *subsongs_temp; @@ -1093,6 +1111,24 @@ void module_impl::select_subsong( std::int32_t subsong ) { std::int32_t module_impl::get_selected_subsong() const { return m_current_subsong; } + +std::int32_t module_impl::get_restart_order( std::int32_t subsong ) const { + std::unique_ptr subsongs_temp = has_subsongs_inited() ? std::unique_ptr() : std::make_unique( get_subsongs() ); + const subsongs_type & subsongs = has_subsongs_inited() ? m_subsongs : *subsongs_temp; + if ( subsong < 0 || subsong >= static_cast( subsongs.size() ) ) { + throw openmpt::exception( "invalid subsong" ); + } + return subsongs[subsong].restart_order; +} +std::int32_t module_impl::get_restart_row( std::int32_t subsong ) const { + std::unique_ptr subsongs_temp = has_subsongs_inited() ? std::unique_ptr() : std::make_unique( get_subsongs() ); + const subsongs_type & subsongs = has_subsongs_inited() ? m_subsongs : *subsongs_temp; + if ( subsong < 0 || subsong >= static_cast( subsongs.size() ) ) { + throw openmpt::exception( "invalid subsong" ); + } + return subsongs[subsong].restart_row; +} + void module_impl::set_repeat_count( std::int32_t repeat_count ) { m_sndFile->SetRepeatCount( repeat_count ); } @@ -1123,8 +1159,8 @@ double module_impl::set_position_seconds( double seconds ) { } m_sndFile->SetCurrentOrder( static_cast( subsong->start_order ) ); OpenMPT::GetLengthType t = m_sndFile->GetLength( m_ctl_seek_sync_samples ? OpenMPT::eAdjustSamplePositions : OpenMPT::eAdjust, OpenMPT::GetLengthTarget( seconds ).StartPos( static_cast( subsong->sequence ), static_cast( subsong->start_order ), static_cast( subsong->start_row ) ) ).back(); - m_sndFile->m_PlayState.m_nNextOrder = m_sndFile->m_PlayState.m_nCurrentOrder = t.targetReached ? t.lastOrder : t.endOrder; - m_sndFile->m_PlayState.m_nNextRow = t.targetReached ? t.lastRow : t.endRow; + m_sndFile->m_PlayState.m_nNextOrder = m_sndFile->m_PlayState.m_nCurrentOrder = t.targetReached ? t.restartOrder : t.endOrder; + m_sndFile->m_PlayState.m_nNextRow = t.targetReached ? t.restartRow : t.endRow; m_sndFile->m_PlayState.m_nTickCount = OpenMPT::CSoundFile::TICKS_ROW_FINISHED; m_currentPositionSeconds = base_seconds + t.duration; return m_currentPositionSeconds; @@ -1415,9 +1451,9 @@ std::vector module_impl::get_order_names() const { if ( m_sndFile->Patterns.IsValidIndex( pat ) ) { retval.push_back( mod_string_to_utf8( m_sndFile->Patterns[ m_sndFile->Order()[i] ].GetName() ) ); } else { - if ( pat == m_sndFile->Order.GetIgnoreIndex() ) { + if ( pat == OpenMPT::PATTERNINDEX_SKIP ) { retval.push_back( "+++ skip" ); - } else if ( pat == m_sndFile->Order.GetInvalidPatIndex() ) { + } else if ( pat == OpenMPT::PATTERNINDEX_INVALID ) { retval.push_back( "--- stop" ); } else { retval.push_back( "???" ); @@ -1457,6 +1493,26 @@ std::int32_t module_impl::get_order_pattern( std::int32_t o ) const { } return m_sndFile->Order()[o]; } + +bool module_impl::is_order_skip_entry( std::int32_t order ) const { + if ( order < 0 || order >= m_sndFile->Order().GetLengthTailTrimmed() ) { + return false; + } + return is_pattern_skip_item( m_sndFile->Order()[order] ); +} +bool module_impl::is_pattern_skip_item( std::int32_t pattern ) { + return pattern == OpenMPT::PATTERNINDEX_SKIP; +} +bool module_impl::is_order_stop_entry( std::int32_t order ) const { + if ( order < 0 || order >= m_sndFile->Order().GetLengthTailTrimmed() ) { + return false; + } + return is_pattern_stop_item( m_sndFile->Order()[order] ); +} +bool module_impl::is_pattern_stop_item( std::int32_t pattern ) { + return pattern == OpenMPT::PATTERNINDEX_INVALID; +} + std::int32_t module_impl::get_pattern_num_rows( std::int32_t p ) const { if ( !mpt::is_in_range( p, std::numeric_limits::min(), std::numeric_limits::max() ) || !m_sndFile->Patterns.IsValidPat( static_cast( p ) ) ) { return 0; @@ -1464,6 +1520,26 @@ std::int32_t module_impl::get_pattern_num_rows( std::int32_t p ) const { return m_sndFile->Patterns[p].GetNumRows(); } +std::int32_t module_impl::get_pattern_rows_per_beat( std::int32_t p ) const { + if ( !mpt::is_in_range( p, std::numeric_limits::min(), std::numeric_limits::max() ) || !m_sndFile->Patterns.IsValidPat( static_cast( p ) ) ) { + return 0; + } + if ( m_sndFile->Patterns[p].GetOverrideSignature() ) { + return m_sndFile->Patterns[p].GetRowsPerBeat(); + } + return m_sndFile->m_nDefaultRowsPerBeat; +} + +std::int32_t module_impl::get_pattern_rows_per_measure( std::int32_t p ) const { + if ( !mpt::is_in_range( p, std::numeric_limits::min(), std::numeric_limits::max() ) || !m_sndFile->Patterns.IsValidPat( static_cast( p ) ) ) { + return 0; + } + if ( m_sndFile->Patterns[p].GetOverrideSignature() ) { + return m_sndFile->Patterns[p].GetRowsPerMeasure(); + } + return m_sndFile->m_nDefaultRowsPerMeasure; +} + std::uint8_t module_impl::get_pattern_row_channel_command( std::int32_t p, std::int32_t r, std::int32_t c, int cmd ) const { if ( !mpt::is_in_range( p, std::numeric_limits::min(), std::numeric_limits::max() ) || !m_sndFile->Patterns.IsValidPat( static_cast( p ) ) ) { return 0; @@ -1539,7 +1615,7 @@ std::pair< std::string, std::string > module_impl::format_and_highlight_pattern_ break; case module::command_volumeffect: return std::make_pair( - cell.IsPcNote() ? std::string(" ") : cell.volcmd != OpenMPT::VOLCMD_NONE ? std::string( 1, m_sndFile->GetModSpecifications().GetVolEffectLetter( cell.volcmd ) ) : std::string(" ") + cell.IsPcNote() ? std::string(" ") : std::string( 1, OpenMPT::CModSpecifications::GetGenericVolEffectLetter( cell.volcmd ) ) , cell.IsPcNote() ? std::string(" ") : cell.volcmd != OpenMPT::VOLCMD_NONE ? std::string("u") : std::string(" ") ); @@ -1605,7 +1681,7 @@ std::pair< std::string, std::string > module_impl::format_and_highlight_pattern_ high += cell.instr ? std::string("ii") : std::string(".."); } if ( ( width == 0 ) || ( width >= 9 ) ) { - text += cell.IsPcNote() ? std::string(" ") + OpenMPT::mpt::afmt::HEX0<2>( cell.GetValueVolCol() & 0xff ) : cell.volcmd != OpenMPT::VOLCMD_NONE ? std::string( 1, m_sndFile->GetModSpecifications().GetVolEffectLetter( cell.volcmd ) ) + OpenMPT::mpt::afmt::HEX0<2>( cell.vol ) : std::string(" .."); + text += cell.IsPcNote() ? std::string(" ") + OpenMPT::mpt::afmt::HEX0<2>( cell.GetValueVolCol() & 0xff ) : cell.volcmd != OpenMPT::VOLCMD_NONE ? std::string( 1, OpenMPT::CModSpecifications::GetGenericVolEffectLetter( cell.volcmd ) ) + OpenMPT::mpt::afmt::HEX0<2>( cell.vol ) : std::string(" .."); high += cell.IsPcNote() ? std::string(" vv") : cell.volcmd != OpenMPT::VOLCMD_NONE ? std::string("uvv") : std::string(" .."); } if ( ( width == 0 ) || ( width >= 13 ) ) { diff --git a/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt_impl.hpp b/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt_impl.hpp index 58018baa8..dab69810c 100644 --- a/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt_impl.hpp +++ b/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt_impl.hpp @@ -107,7 +107,9 @@ protected: std::int32_t start_row; std::int32_t start_order; std::int32_t sequence; - subsong_data( double duration, std::int32_t start_row, std::int32_t start_order, std::int32_t sequence ); + std::int32_t restart_row; + std::int32_t restart_order; + subsong_data( double duration, std::int32_t start_row, std::int32_t start_order, std::int32_t sequence, std::int32_t restart_row, std::int32_t restart_order ); }; // struct subsong_data typedef std::vector subsongs_type; @@ -198,9 +200,14 @@ public: public: void select_subsong( std::int32_t subsong ); std::int32_t get_selected_subsong() const; + + std::int32_t get_restart_order( std::int32_t subsong ) const; + std::int32_t get_restart_row( std::int32_t subsong ) const; + void set_repeat_count( std::int32_t repeat_count ); std::int32_t get_repeat_count() const; double get_duration_seconds() const; + double get_time_at_position( std::int32_t order, std::int32_t row ) const; double set_position_seconds( double seconds ); double get_position_seconds() const; double set_position_order_row( std::int32_t order, std::int32_t row ); @@ -244,7 +251,13 @@ public: std::vector get_instrument_names() const; std::vector get_sample_names() const; std::int32_t get_order_pattern( std::int32_t o ) const; + bool is_order_skip_entry( std::int32_t order ) const; + static bool is_pattern_skip_item( std::int32_t pattern ); + bool is_order_stop_entry( std::int32_t order ) const; + static bool is_pattern_stop_item( std::int32_t pattern ); std::int32_t get_pattern_num_rows( std::int32_t p ) const; + std::int32_t get_pattern_rows_per_beat( std::int32_t pattern ) const; + std::int32_t get_pattern_rows_per_measure( std::int32_t pattern ) const; std::uint8_t get_pattern_row_channel_command( std::int32_t p, std::int32_t r, std::int32_t c, int cmd ) const; std::string format_pattern_row_channel_command( std::int32_t p, std::int32_t r, std::int32_t c, int cmd ) const; std::string highlight_pattern_row_channel_command( std::int32_t p, std::int32_t r, std::int32_t c, int cmd ) const; diff --git a/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt_plugin_gui.cpp b/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt_plugin_gui.cpp deleted file mode 100644 index e69de29bb..000000000 diff --git a/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt_plugin_gui.hpp b/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt_plugin_gui.hpp deleted file mode 100644 index e69de29bb..000000000 diff --git a/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt_plugin_gui.rc b/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt_plugin_gui.rc deleted file mode 100644 index e69de29bb..000000000 diff --git a/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt_plugin_settings.hpp b/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt_plugin_settings.hpp deleted file mode 100644 index e69de29bb..000000000 diff --git a/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt_test.cpp b/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt_test.cpp deleted file mode 100644 index e69de29bb..000000000 diff --git a/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt_test/libopenmpt_test.cpp b/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt_test/libopenmpt_test.cpp new file mode 100644 index 000000000..37822b2d5 --- /dev/null +++ b/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt_test/libopenmpt_test.cpp @@ -0,0 +1,84 @@ +/* + * libopenmpt_test.cpp + * ------------------- + * Purpose: libopenmpt test suite driver + * Notes : (currently none) + * Authors: OpenMPT Devs + * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. + */ + +#include "openmpt/all/BuildSettings.hpp" +#include "openmpt/all/PlatformFixes.hpp" + +#include "mpt/base/integer.hpp" +#include "mpt/main/main.hpp" + +#include "../../libopenmpt/libopenmpt_internal.h" + +#include "../../test/test.h" + +#include +#include + +#include +#include + +#if defined(__EMSCRIPTEN__) +#include +#endif /* __EMSCRIPTEN__ */ + +namespace libopenmpt_test { + +static mpt::uint8 main() { + +#if defined(__EMSCRIPTEN__) + EM_ASM( + FS.mkdir('/test'); + FS.mount(NODEFS, {'root': '../test/'}, '/test'); + FS.mkdir('/libopenmpt'); + FS.mount(NODEFS, {'root': '../libopenmpt/'}, '/libopenmpt'); + ); +#endif /* __EMSCRIPTEN__ */ + + try { + + using namespace OpenMPT; + + Test::PrintHeader(); + + // run test with "C" / classic() locale + Test::DoTests(); + + // try setting the C locale to the user locale + setlocale( LC_ALL, "" ); + + // run all tests again with a set C locale + Test::DoTests(); + + // try to set the C and C++ locales to the user locale + try { + std::locale old = std::locale::global( std::locale( "" ) ); + static_cast( old ); + } catch ( ... ) { + // Setting c++ global locale does not work. + // This is no problem for libopenmpt, just continue. + } + + // and now, run all tests once again + Test::DoTests(); + + Test::PrintFooter(); + + } catch ( const std::exception & e ) { + std::cerr << "TEST ERROR: exception: " << ( e.what() ? e.what() : "" ) << std::endl; + return 255; + } catch ( ... ) { + std::cerr << "TEST ERROR: unknown exception" << std::endl; + return 255; + } + return 0; +} + +} // namespace libopenmpt_test + +MPT_MAIN_IMPLEMENT_MAIN_NO_ARGS(libopenmpt_test) diff --git a/Frameworks/OpenMPT/OpenMPT/test/libopenmpt_test.manifest b/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt_test/libopenmpt_test.manifest similarity index 100% rename from Frameworks/OpenMPT/OpenMPT/test/libopenmpt_test.manifest rename to Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt_test/libopenmpt_test.manifest diff --git a/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt_version.h b/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt_version.h index 8f03baee2..b3d37f305 100644 --- a/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt_version.h +++ b/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt_version.h @@ -19,9 +19,9 @@ /*! \brief libopenmpt major version number */ #define OPENMPT_API_VERSION_MAJOR 0 /*! \brief libopenmpt minor version number */ -#define OPENMPT_API_VERSION_MINOR 7 +#define OPENMPT_API_VERSION_MINOR 8 /*! \brief libopenmpt patch version number */ -#define OPENMPT_API_VERSION_PATCH 13 +#define OPENMPT_API_VERSION_PATCH 0 /*! \brief libopenmpt pre-release tag */ #define OPENMPT_API_VERSION_PREREL "" /*! \brief libopenmpt pre-release flag */ diff --git a/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt_version.mk b/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt_version.mk index 89eb7aaba..5fa8419c8 100644 --- a/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt_version.mk +++ b/Frameworks/OpenMPT/OpenMPT/libopenmpt/libopenmpt_version.mk @@ -1,8 +1,8 @@ LIBOPENMPT_VERSION_MAJOR=0 -LIBOPENMPT_VERSION_MINOR=7 -LIBOPENMPT_VERSION_PATCH=13 +LIBOPENMPT_VERSION_MINOR=8 +LIBOPENMPT_VERSION_PATCH=0 LIBOPENMPT_VERSION_PREREL= -LIBOPENMPT_LTVER_CURRENT=4 -LIBOPENMPT_LTVER_REVISION=13 -LIBOPENMPT_LTVER_AGE=4 +LIBOPENMPT_LTVER_CURRENT=5 +LIBOPENMPT_LTVER_REVISION=0 +LIBOPENMPT_LTVER_AGE=5 diff --git a/Frameworks/OpenMPT/OpenMPT/libopenmpt/plugin-common/libopenmpt_plugin_gui.cpp b/Frameworks/OpenMPT/OpenMPT/libopenmpt/plugin-common/libopenmpt_plugin_gui.cpp index f8d3cae1a..7ecd09364 100644 --- a/Frameworks/OpenMPT/OpenMPT/libopenmpt/plugin-common/libopenmpt_plugin_gui.cpp +++ b/Frameworks/OpenMPT/OpenMPT/libopenmpt/plugin-common/libopenmpt_plugin_gui.cpp @@ -19,6 +19,10 @@ #include #endif +#if defined(MPT_WITH_MFC) +#include +#endif + #if !defined(MPT_WITH_MFC) #include #endif @@ -123,20 +127,20 @@ protected: selected = false; if ( !s->no_default_format ) { - m_ComboBoxSamplerate.SetItemData( m_ComboBoxSamplerate.AddString( L"Default" ), 0 ); + m_ComboBoxSamplerate.SetItemData( m_ComboBoxSamplerate.AddString( s->player_setting_name ? s->player_setting_name.value().c_str() : TEXT("Default") ), 0 ); } - m_ComboBoxSamplerate.SetItemData( m_ComboBoxSamplerate.AddString( L"6000" ), 6000 ); - m_ComboBoxSamplerate.SetItemData( m_ComboBoxSamplerate.AddString( L"8000" ), 8000 ); - m_ComboBoxSamplerate.SetItemData( m_ComboBoxSamplerate.AddString( L"11025" ), 11025 ); - m_ComboBoxSamplerate.SetItemData( m_ComboBoxSamplerate.AddString( L"16000" ), 16000 ); - m_ComboBoxSamplerate.SetItemData( m_ComboBoxSamplerate.AddString( L"22050" ), 22050 ); - m_ComboBoxSamplerate.SetItemData( m_ComboBoxSamplerate.AddString( L"32000" ), 32000 ); - m_ComboBoxSamplerate.SetItemData( m_ComboBoxSamplerate.AddString( L"44100" ), 44100 ); - m_ComboBoxSamplerate.SetItemData( m_ComboBoxSamplerate.AddString( L"48000" ), 48000 ); - m_ComboBoxSamplerate.SetItemData( m_ComboBoxSamplerate.AddString( L"88200" ), 88200 ); - m_ComboBoxSamplerate.SetItemData( m_ComboBoxSamplerate.AddString( L"96000" ), 96000 ); + m_ComboBoxSamplerate.SetItemData( m_ComboBoxSamplerate.AddString( TEXT("6000") ), 6000 ); + m_ComboBoxSamplerate.SetItemData( m_ComboBoxSamplerate.AddString( TEXT("8000") ), 8000 ); + m_ComboBoxSamplerate.SetItemData( m_ComboBoxSamplerate.AddString( TEXT("11025") ), 11025 ); + m_ComboBoxSamplerate.SetItemData( m_ComboBoxSamplerate.AddString( TEXT("16000") ), 16000 ); + m_ComboBoxSamplerate.SetItemData( m_ComboBoxSamplerate.AddString( TEXT("22050") ), 22050 ); + m_ComboBoxSamplerate.SetItemData( m_ComboBoxSamplerate.AddString( TEXT("32000") ), 32000 ); + m_ComboBoxSamplerate.SetItemData( m_ComboBoxSamplerate.AddString( TEXT("44100") ), 44100 ); + m_ComboBoxSamplerate.SetItemData( m_ComboBoxSamplerate.AddString( TEXT("48000") ), 48000 ); + m_ComboBoxSamplerate.SetItemData( m_ComboBoxSamplerate.AddString( TEXT("88200") ), 88200 ); + m_ComboBoxSamplerate.SetItemData( m_ComboBoxSamplerate.AddString( TEXT("96000") ), 96000 ); if ( !s->no_default_format && s->samplerate == 0 ) { - m_ComboBoxSamplerate.SelectString( 0, L"Default" ); + m_ComboBoxSamplerate.SelectString( 0, TEXT("Default") ); } for ( int index = 0; index < m_ComboBoxSamplerate.GetCount(); ++index ) { if ( static_cast( m_ComboBoxSamplerate.GetItemData( index ) ) == s->samplerate ) { @@ -145,18 +149,18 @@ protected: } } if ( !selected ) { - m_ComboBoxSamplerate.SelectString( 0, L"48000" ); + m_ComboBoxSamplerate.SelectString( 0, TEXT("48000") ); } selected = false; if ( !s->no_default_format ) { - m_ComboBoxChannels.SetItemData( m_ComboBoxChannels.AddString( L"Default" ), 0 ); + m_ComboBoxChannels.SetItemData( m_ComboBoxChannels.AddString( s->player_setting_name ? s->player_setting_name.value().c_str() : TEXT("Default") ), 0 ); } - m_ComboBoxChannels.SetItemData( m_ComboBoxChannels.AddString( L"Mono" ), 1 ); - m_ComboBoxChannels.SetItemData( m_ComboBoxChannels.AddString( L"Stereo" ), 2 ); - m_ComboBoxChannels.SetItemData( m_ComboBoxChannels.AddString( L"Quad" ), 4 ); + m_ComboBoxChannels.SetItemData( m_ComboBoxChannels.AddString( TEXT("Mono") ), 1 ); + m_ComboBoxChannels.SetItemData( m_ComboBoxChannels.AddString( TEXT("Stereo") ), 2 ); + m_ComboBoxChannels.SetItemData( m_ComboBoxChannels.AddString( TEXT("Quad") ), 4 ); if ( !s->no_default_format && s->channels == 0 ) { - m_ComboBoxChannels.SelectString( 0, L"Default" ); + m_ComboBoxChannels.SelectString( 0, TEXT("Default") ); } for ( int index = 0; index < m_ComboBoxChannels.GetCount(); ++index ) { if ( static_cast( m_ComboBoxChannels.GetItemData( index ) ) == s->channels ) { @@ -165,7 +169,7 @@ protected: } } if ( !selected ) { - m_ComboBoxChannels.SelectString( 0, L"Stereo" ); + m_ComboBoxChannels.SelectString( 0, TEXT("Stereo") ); } m_SliderCtrlGain.SetRange( -1200, 1200 ); @@ -175,10 +179,10 @@ protected: m_SliderCtrlGain.SetPos( s->mastergain_millibel ); selected = false; - m_ComboBoxInterpolation.SetItemData( m_ComboBoxInterpolation.AddString( L"Off / 1 Tap (Nearest)" ), 1 ); - m_ComboBoxInterpolation.SetItemData( m_ComboBoxInterpolation.AddString( L"2 Tap (Linear)" ), 2 ); - m_ComboBoxInterpolation.SetItemData( m_ComboBoxInterpolation.AddString( L"4 Tap (Cubic)" ), 4 ); - m_ComboBoxInterpolation.SetItemData( m_ComboBoxInterpolation.AddString( L"8 Tap (Polyphase FIR)" ), 8 ); + m_ComboBoxInterpolation.SetItemData( m_ComboBoxInterpolation.AddString( TEXT("Off / 1 Tap (Nearest)") ), 1 ); + m_ComboBoxInterpolation.SetItemData( m_ComboBoxInterpolation.AddString( TEXT("2 Tap (Linear)") ), 2 ); + m_ComboBoxInterpolation.SetItemData( m_ComboBoxInterpolation.AddString( TEXT("4 Tap (Cubic)") ), 4 ); + m_ComboBoxInterpolation.SetItemData( m_ComboBoxInterpolation.AddString( TEXT("8 Tap (Polyphase FIR)") ), 8 ); for ( int index = 0; index < m_ComboBoxInterpolation.GetCount(); ++index ) { if ( static_cast( m_ComboBoxInterpolation.GetItemData( index ) ) == s->interpolationfilterlength ) { m_ComboBoxInterpolation.SetCurSel( index ); @@ -186,16 +190,16 @@ protected: } } if ( !selected ) { - m_ComboBoxInterpolation.SelectString( 0, L"8 Tap (Polyphase FIR)" ); + m_ComboBoxInterpolation.SelectString( 0, TEXT("8 Tap (Polyphase FIR)") ); } m_CheckBoxAmigaResampler.SetCheck( s->use_amiga_resampler ? BST_CHECKED : BST_UNCHECKED ); selected = false; m_ComboBoxAmigaFilter.EnableWindow( s->use_amiga_resampler ? TRUE : FALSE ); - m_ComboBoxAmigaFilter.SetItemData( m_ComboBoxAmigaFilter.AddString( L"Default" ), 0 ); - m_ComboBoxAmigaFilter.SetItemData( m_ComboBoxAmigaFilter.AddString( L"A500 Filter" ), 0xA500 ); - m_ComboBoxAmigaFilter.SetItemData( m_ComboBoxAmigaFilter.AddString( L"A1200 Filter" ), 0xA1200 ); - m_ComboBoxAmigaFilter.SetItemData( m_ComboBoxAmigaFilter.AddString( L"Unfiltered" ), 1 ); + m_ComboBoxAmigaFilter.SetItemData( m_ComboBoxAmigaFilter.AddString( TEXT("Default") ), 0 ); + m_ComboBoxAmigaFilter.SetItemData( m_ComboBoxAmigaFilter.AddString( TEXT("A500 Filter") ), 0xA500 ); + m_ComboBoxAmigaFilter.SetItemData( m_ComboBoxAmigaFilter.AddString( TEXT("A1200 Filter") ), 0xA1200 ); + m_ComboBoxAmigaFilter.SetItemData( m_ComboBoxAmigaFilter.AddString( TEXT("Unfiltered") ), 1 ); for ( int index = 0; index < m_ComboBoxAmigaFilter.GetCount(); ++index ) { if ( static_cast( m_ComboBoxAmigaFilter.GetItemData( index ) ) == s->amiga_filter_type ) { m_ComboBoxAmigaFilter.SetCurSel( index ); @@ -203,13 +207,13 @@ protected: } } if ( !selected ) { - m_ComboBoxAmigaFilter.SelectString( 0, L"Default" ); + m_ComboBoxAmigaFilter.SelectString( 0, TEXT("Default") ); } selected = false; - m_ComboBoxRepeat.SetItemData( m_ComboBoxRepeat.AddString( L"Forever" ), static_cast( -1 ) ); - m_ComboBoxRepeat.SetItemData( m_ComboBoxRepeat.AddString( L"Never" ), 0 ); - m_ComboBoxRepeat.SetItemData( m_ComboBoxRepeat.AddString( L"Once" ), 1 ); + m_ComboBoxRepeat.SetItemData( m_ComboBoxRepeat.AddString( TEXT("Forever") ), static_cast( -1 ) ); + m_ComboBoxRepeat.SetItemData( m_ComboBoxRepeat.AddString( TEXT("Never") ), 0 ); + m_ComboBoxRepeat.SetItemData( m_ComboBoxRepeat.AddString( TEXT("Once") ), 1 ); for ( int index = 0; index < m_ComboBoxRepeat.GetCount(); ++index ) { if ( static_cast( m_ComboBoxRepeat.GetItemData( index ) ) == s->repeatcount ) { m_ComboBoxRepeat.SetCurSel( index ); @@ -217,7 +221,7 @@ protected: } } if ( !selected ) { - m_ComboBoxRepeat.SelectString( 0, L"Never" ); + m_ComboBoxRepeat.SelectString( 0, TEXT("Never") ); } m_SliderCtrlStereoSeparation.SetRange( 0, 200 ); @@ -227,13 +231,13 @@ protected: m_SliderCtrlStereoSeparation.SetPos( s->stereoseparation ); selected = false; - m_ComboBoxRamping.SetItemData( m_ComboBoxRamping.AddString( L"Default" ), static_cast( -1 ) ); - m_ComboBoxRamping.SetItemData( m_ComboBoxRamping.AddString( L"Off" ), 0 ); - m_ComboBoxRamping.SetItemData( m_ComboBoxRamping.AddString( L"1 ms" ), 1 ); - m_ComboBoxRamping.SetItemData( m_ComboBoxRamping.AddString( L"2 ms" ), 2 ); - m_ComboBoxRamping.SetItemData( m_ComboBoxRamping.AddString( L"3 ms" ), 3 ); - m_ComboBoxRamping.SetItemData( m_ComboBoxRamping.AddString( L"5 ms" ), 5 ); - m_ComboBoxRamping.SetItemData( m_ComboBoxRamping.AddString( L"10 ms" ), 10 ); + m_ComboBoxRamping.SetItemData( m_ComboBoxRamping.AddString( TEXT("Default") ), static_cast( -1 ) ); + m_ComboBoxRamping.SetItemData( m_ComboBoxRamping.AddString( TEXT("Off") ), 0 ); + m_ComboBoxRamping.SetItemData( m_ComboBoxRamping.AddString( TEXT("1 ms") ), 1 ); + m_ComboBoxRamping.SetItemData( m_ComboBoxRamping.AddString( TEXT("2 ms") ), 2 ); + m_ComboBoxRamping.SetItemData( m_ComboBoxRamping.AddString( TEXT("3 ms") ), 3 ); + m_ComboBoxRamping.SetItemData( m_ComboBoxRamping.AddString( TEXT("5 ms") ), 5 ); + m_ComboBoxRamping.SetItemData( m_ComboBoxRamping.AddString( TEXT("10 ms") ), 10 ); for ( int index = 0; index < m_ComboBoxRamping.GetCount(); ++index ) { if ( static_cast( m_ComboBoxRamping.GetItemData( index ) ) == s->ramping ) { m_ComboBoxRamping.SetCurSel( index ); @@ -241,7 +245,7 @@ protected: } } if ( !selected ) { - m_ComboBoxRamping.SelectString( 0, L"Default" ); + m_ComboBoxRamping.SelectString( 0, TEXT("Default") ); } return TRUE; @@ -285,11 +289,11 @@ protected: switch ( nID ) { case IDC_SLIDER_GAIN: - swprintf( pTTT->szText, _countof(pTTT->szText), L"%.02f dB", m_SliderCtrlGain.GetPos() * 0.01f ); + _sntprintf( pTTT->szText, _countof(pTTT->szText), TEXT("%.02f dB"), m_SliderCtrlGain.GetPos() * 0.01f ); break; case IDC_SLIDER_STEREOSEPARATION: - swprintf( pTTT->szText, _countof(pTTT->szText), L"%d %%", m_SliderCtrlStereoSeparation.GetPos()); + _sntprintf( pTTT->szText, _countof(pTTT->szText), TEXT("%d %%"), m_SliderCtrlStereoSeparation.GetPos() ); break; default: @@ -361,6 +365,7 @@ protected: #if defined(MPT_WITH_MFC) + void gui_edit_settings( libopenmpt_settings * s, HWND parent, std::wstring title ) { AFX_MANAGE_STATE( AfxGetStaticModuleState() ); CSettingsDialog dlg( s, title.c_str(), parent ? CWnd::FromHandle( parent ) : nullptr ); @@ -370,7 +375,25 @@ void gui_edit_settings( libopenmpt_settings * s, HWND parent, std::wstring title void gui_show_file_info( HWND parent, std::wstring title, std::wstring info ) { AFX_MANAGE_STATE( AfxGetStaticModuleState() ); - CInfoDialog dlg( title.c_str(), info.c_str(), parent ? CWnd::FromHandle( parent ) : nullptr); + CInfoDialog dlg( title.c_str(), info.c_str(), parent ? CWnd::FromHandle( parent ) : nullptr ); + dlg.DoModal(); +} + + +void gui_show_about( HWND parent, std::basic_string title, std::basic_string about, std::basic_string credits ) { + AFX_MANAGE_STATE( AfxGetStaticModuleState() ); + about += TEXT("\r\n"); + about += TEXT("Show full credits?\r\n"); + if ( parent ) { + if ( CWnd::FromHandle( parent )->MessageBox( about.c_str(), title.c_str(), MB_ICONINFORMATION | MB_YESNOCANCEL | MB_DEFBUTTON1 ) != IDYES ) { + return; + } + } else { + if ( MessageBox( parent, about.c_str(), title.c_str(), MB_ICONINFORMATION | MB_YESNOCANCEL | MB_DEFBUTTON1 ) != IDYES ) { + return; + } + } + CInfoDialog dlg( title.c_str(), credits.c_str(), parent ? CWnd::FromHandle( parent ) : nullptr ); dlg.DoModal(); } @@ -465,6 +488,14 @@ void gui_show_file_info( HWND /* parent */ , std::basic_string title, std } +void gui_show_about( HWND parent, std::basic_string title, std::basic_string about, std::basic_string credits ) { + if ( MessageBox( parent, about.c_str(), title.c_str(), MB_ICONINFORMATION | MB_YESNOCANCEL | MB_DEFBUTTON1 ) != IDYES ) { + return; + } + gui_show_file_info( parent, title, credits ); +} + + #endif // MPT_WITH_MFC diff --git a/Frameworks/OpenMPT/OpenMPT/libopenmpt/plugin-common/libopenmpt_plugin_gui.hpp b/Frameworks/OpenMPT/OpenMPT/libopenmpt/plugin-common/libopenmpt_plugin_gui.hpp index 1cc387865..4134fe840 100644 --- a/Frameworks/OpenMPT/OpenMPT/libopenmpt/plugin-common/libopenmpt_plugin_gui.hpp +++ b/Frameworks/OpenMPT/OpenMPT/libopenmpt/plugin-common/libopenmpt_plugin_gui.hpp @@ -30,6 +30,8 @@ void gui_edit_settings( libopenmpt_settings * s, HWND parent, std::basic_string< void gui_show_file_info( HWND parent, std::basic_string title, std::basic_string info ); +void gui_show_about( HWND parent, std::basic_string title, std::basic_string about, std::basic_string credits ); + } // namespace plugin } // namespace libopenmpt diff --git a/Frameworks/OpenMPT/OpenMPT/libopenmpt/plugin-common/libopenmpt_plugin_settings.hpp b/Frameworks/OpenMPT/OpenMPT/libopenmpt/plugin-common/libopenmpt_plugin_settings.hpp index b77ad1a1c..b6dbbd2ab 100644 --- a/Frameworks/OpenMPT/OpenMPT/libopenmpt/plugin-common/libopenmpt_plugin_settings.hpp +++ b/Frameworks/OpenMPT/OpenMPT/libopenmpt/plugin-common/libopenmpt_plugin_settings.hpp @@ -12,6 +12,7 @@ #include +#include #include @@ -33,6 +34,7 @@ struct libopenmpt_settings { int interpolationfilterlength = 8; int ramping = -1; int vis_allow_scroll = 1; + std::optional> player_setting_name = std::nullopt; changed_func changed = nullptr; }; @@ -70,18 +72,15 @@ protected: } } public: - settings( const std::basic_string & subkey, bool no_default_format_ ) - : subkey(subkey) + settings( const std::basic_string & subkey_, bool no_default_format_, const std::optional> & player_setting_name_ = std::nullopt) + : subkey(subkey_) { no_default_format = no_default_format_; + player_setting_name = player_setting_name_; } void load() { - #ifdef UNICODE - #define read_setting(a,b,c) read_setting( b , L ## b , c) - #else - #define read_setting(a,b,c) read_setting( b , b , c) - #endif + #define read_setting(a,b,c) read_setting( b , TEXT(b) , c) read_setting( subkey, "Samplerate_Hz", samplerate ); read_setting( subkey, "Channels", channels ); read_setting( subkey, "MasterGain_milliBel", mastergain_millibel ); @@ -96,11 +95,7 @@ public: } void save() { - #ifdef UNICODE - #define write_setting(a,b,c) write_setting( b , L ## b , c) - #else - #define write_setting(a,b,c) write_setting( b , b , c) - #endif + #define write_setting(a,b,c) write_setting( b , TEXT(b) , c) write_setting( subkey, "Samplerate_Hz", samplerate ); write_setting( subkey, "Channels", channels ); write_setting( subkey, "MasterGain_milliBel", mastergain_millibel ); diff --git a/Frameworks/OpenMPT/OpenMPT/libopenmpt/resource.h b/Frameworks/OpenMPT/OpenMPT/libopenmpt/resource.h deleted file mode 100644 index e69de29bb..000000000 diff --git a/Frameworks/OpenMPT/OpenMPT/libopenmpt/xmp-openmpt.cpp b/Frameworks/OpenMPT/OpenMPT/libopenmpt/xmp-openmpt.cpp deleted file mode 100644 index e69de29bb..000000000 diff --git a/Frameworks/OpenMPT/OpenMPT/libopenmpt/xmp-openmpt/xmp-openmpt.cpp b/Frameworks/OpenMPT/OpenMPT/libopenmpt/xmp-openmpt/xmp-openmpt.cpp index b2609a7f8..4569a01e9 100644 --- a/Frameworks/OpenMPT/OpenMPT/libopenmpt/xmp-openmpt/xmp-openmpt.cpp +++ b/Frameworks/OpenMPT/OpenMPT/libopenmpt/xmp-openmpt/xmp-openmpt.cpp @@ -42,10 +42,6 @@ static const char * xmp_openmpt_string = "OpenMPT (" OPENMPT_API_VERSION_STRING static const char * xmp_openmpt_string = "OpenMPT (" OPENMPT_API_VERSION_STRING ")"; #endif -#define USE_XMPLAY_FILE_IO - -#define USE_XMPLAY_ISTREAM - // XMPLAY expects a WINAPI (which is __stdcall) function using an undecorated symbol name which conflicts with the provided declaration. #if defined(__GNUC__) #define XMPIN_GetInterface XMPIN_GetInterface_Dummy @@ -145,14 +141,11 @@ protected: } public: xmp_openmpt_settings() - : libopenmpt::plugin::settings(TEXT(SHORT_TITLE), false) - { - return; - } - virtual ~xmp_openmpt_settings() + : libopenmpt::plugin::settings(TEXT(SHORT_TITLE), false, TEXT("XMPlay output format")) { return; } + virtual ~xmp_openmpt_settings() = default; }; struct self_xmplay_t { @@ -181,9 +174,6 @@ struct self_xmplay_t { mod = 0; } } - ~self_xmplay_t() { - return; - } }; static std::string convert_to_native( const std::string & str ) { @@ -504,45 +494,29 @@ static void WINAPI openmpt_About( HWND win ) { about << " OpenMPT version " << openmpt::string::get( "core_version" ) << std::endl; about << std::endl; about << openmpt::string::get( "contact" ) << std::endl; - about << std::endl; - about << "Show full credits?" << std::endl; - if ( MessageBox( win, StringToWINAPI( StringDecode( about.str(), CP_UTF8 ) ).c_str(), TEXT(SHORT_TITLE), MB_ICONINFORMATION | MB_YESNOCANCEL | MB_DEFBUTTON1 ) != IDYES ) { - return; - } std::ostringstream credits; credits << openmpt::string::get( "credits" ); credits << "Additional thanks to:" << std::endl; credits << std::endl; credits << "Arseny Kapoulkine for pugixml" << std::endl; credits << "https://pugixml.org/" << std::endl; -#if 1 - libopenmpt::plugin::gui_show_file_info( win, TEXT(SHORT_TITLE), StringToWINAPI( StringReplace( StringDecode( credits.str(), CP_UTF8 ), L"\n", L"\r\n" ) ) ); -#else - MessageBox( win, StringToWINAPI( StringReplace( StringDecode( credits.str(), CP_UTF8 ), L"\n", L"\r\n" ) ).c_str(), TEXT(SHORT_TITLE), MB_OK ); -#endif + libopenmpt::plugin::gui_show_about( win, TEXT(SHORT_TITLE), StringReplace( StringToWINAPI( StringDecode( about.str(), CP_UTF8 ) ), TEXT("\n"), TEXT("\r\n") ), StringReplace( StringToWINAPI( StringDecode( credits.str(), CP_UTF8 ) ), TEXT("\n"), TEXT("\r\n") ) ); } static void WINAPI openmpt_Config( HWND win ) { -#if 1 libopenmpt::plugin::gui_edit_settings( &self->settings, win, TEXT(SHORT_TITLE) ); -#else - static_cast(win); -#endif apply_and_save_options(); } -#ifdef USE_XMPLAY_FILE_IO - -#ifdef USE_XMPLAY_ISTREAM - class xmplay_streambuf : public std::streambuf { public: explicit xmplay_streambuf( XMPFILE & file ); private: + xmplay_streambuf( const xmplay_streambuf & ) = delete; + xmplay_streambuf & operator = ( const xmplay_streambuf & ) = delete; +protected: int_type underflow() override; - xmplay_streambuf( const xmplay_streambuf & ); - xmplay_streambuf & operator = ( const xmplay_streambuf & ); -private: +protected: XMPFILE & file; static inline constexpr std::size_t put_back = 4096; static inline constexpr std::size_t buf_size = 65536; @@ -550,43 +524,116 @@ private: }; // class xmplay_streambuf xmplay_streambuf::xmplay_streambuf( XMPFILE & file_ ) : file(file_), buffer(buf_size) { - char * end = &buffer.front() + buffer.size(); - setg( end, end, end ); + setg( buffer.data(), buffer.data() + buffer.size(), buffer.data() + buffer.size() ); } std::streambuf::int_type xmplay_streambuf::underflow() { if ( gptr() < egptr() ) { return traits_type::to_int_type( *gptr() ); } - char * base = &buffer.front(); - char * start = base; - if ( eback() == base ) { - std::size_t put_back_count = std::min( put_back, static_cast( egptr() - base ) ); - std::memmove( base, egptr() - put_back_count, put_back_count ); - start += put_back_count; - } - std::size_t n = xmpffile->Read( file, start, buffer.size() - ( start - base ) ); - setg( base, start, start + n ); - if ( n == 0 ) { + std::size_t put_back_count = std::min( put_back, static_cast( egptr() - buffer.data() ) ); + std::memmove( buffer.data(), egptr() - put_back_count, put_back_count ); + std::size_t readcount = xmpffile->Read( file, buffer.data() + put_back_count, buffer.size() - put_back_count ); + setg( buffer.data(), buffer.data() + put_back_count, buffer.data() + put_back_count + readcount ); + if ( readcount == 0 ) { return traits_type::eof(); } return traits_type::to_int_type( *gptr() ); } +#include +class xmplay_streambuf_seekable : public xmplay_streambuf { +public: + explicit xmplay_streambuf_seekable( XMPFILE & file ); +private: + xmplay_streambuf_seekable( const xmplay_streambuf_seekable & ) = delete; + xmplay_streambuf_seekable & operator = ( const xmplay_streambuf_seekable & ) = delete; +private: + pos_type seekoff( off_type off, std::ios::seekdir dir, std::ios::openmode which ) override; + pos_type seekpos( pos_type pos, std::ios::openmode which ) override; +}; // class xmplay_streambuf_seekable + +xmplay_streambuf_seekable::xmplay_streambuf_seekable( XMPFILE & file ) + : xmplay_streambuf(file) +{ + return; +} + +std::streambuf::pos_type xmplay_streambuf_seekable::seekoff( off_type off, std::ios::seekdir dir, std::ios::openmode which ) { + if ( !(which & std::ios::in) ) { + return pos_type(off_type(-1)); + } + if ( (dir == std::ios::cur) && (off == 0) ) { + // shortcut without invalidating buffer + return xmpffile->Tell64( file ) - (egptr() - gptr()); + } + if ( (dir == std::ios::beg) && (off == static_cast( xmpffile->Tell64( file ) ) - (egptr() - gptr())) ) { + // shortcut without invalidating buffer + return off; + } + switch ( dir ) { + case std::ios::beg: + if ( !xmpffile->Seek64( file, off ) ) { + return pos_type(off_type(-1)); + } + break; + case std::ios::cur: + if ( !xmpffile->Seek64( file, xmpffile->Tell64( file ) - (egptr() - gptr()) + off ) ) { + return pos_type(off_type(-1)); + } + break; + case std::ios::end: + if ( !xmpffile->Seek64( file, xmpffile->GetSize64( file ) + off ) ) { + return pos_type(off_type(-1)); + } + break; + default: + return pos_type(off_type(-1)); + break; + } + setg( buffer.data(), buffer.data() + buffer.size(), buffer.data() + buffer.size() ); + return xmpffile->Tell64( file ); +} + +std::streambuf::pos_type xmplay_streambuf_seekable::seekpos( pos_type pos, std::ios::openmode which ) { + if ( !(which & std::ios::in) ) { + return pos_type(off_type(-1)); + } + if ( pos == static_cast( xmpffile->Tell64( file ) - (egptr() - gptr()) ) ) { + // shortcut without invalidating buffer + return pos; + } + if ( !xmpffile->Seek64( file, pos ) ) { + return pos_type(off_type(-1)); + } + setg( buffer.data(), buffer.data() + buffer.size(), buffer.data() + buffer.size() ); + return xmpffile->Tell64( file ); +} + class xmplay_istream : public std::istream { private: xmplay_streambuf buf; private: - xmplay_istream( const xmplay_istream & ); - xmplay_istream & operator = ( const xmplay_istream & ); + xmplay_istream( const xmplay_istream & ) = delete; + xmplay_istream & operator = ( const xmplay_istream & ) = delete; public: xmplay_istream( XMPFILE & file ) : std::istream(&buf), buf(file) { return; } - ~xmplay_istream() { +}; // class xmplay_istream + +class xmplay_istream_seekable : public std::istream { +private: + xmplay_streambuf_seekable buf; +private: + xmplay_istream_seekable( const xmplay_istream_seekable & ) = delete; + xmplay_istream_seekable & operator = ( const xmplay_istream_seekable & ) = delete; +public: + xmplay_istream_seekable( XMPFILE & file ) : std::istream(&buf), buf(file) { return; } -}; // class xmplay_istream +}; // class xmplay_istream_seekable + // Stream for memory-based files (required for could_open_probability) struct xmplay_membuf : std::streambuf { @@ -604,25 +651,6 @@ struct xmplay_imemstream : virtual xmplay_membuf, std::istream { } }; -#else // !USE_XMPLAY_ISTREAM - -static std::vector read_XMPFILE_vector( XMPFILE & file ) { - std::vector data( xmpffile->GetSize( file ) ); - if ( data.size() != xmpffile->Read( file, data.data(), data.size() ) ) { - return std::vector(); - } - return data; -} - -static std::string read_XMPFILE_string( XMPFILE & file ) { - std::vector data = read_XMPFILE_vector( file ); - return std::string( data.begin(), data.end() ); -} - -#endif // USE_XMPLAY_ISTREAM - -#endif // USE_XMPLAY_FILE_IO - static std::string string_replace( std::string str, const std::string & oldStr, const std::string & newStr ) { std::size_t pos = 0; while((pos = str.find(oldStr, pos)) != std::string::npos) @@ -842,40 +870,33 @@ static std::string sanitize_xmplay_multiline_string( const std::string & str ) { static BOOL WINAPI openmpt_CheckFile( const char * filename, XMPFILE file ) { static_cast( filename ); try { - #ifdef USE_XMPLAY_FILE_IO - #ifdef USE_XMPLAY_ISTREAM - switch ( xmpffile->GetType( file ) ) { - case XMPFILE_TYPE_MEMORY: - { - xmplay_imemstream s( reinterpret_cast( xmpffile->GetMemory( file ) ), xmpffile->GetSize( file ) ); - return ( openmpt::probe_file_header( openmpt::probe_file_header_flags_default2, s ) == openmpt::probe_file_header_result_success ) ? TRUE : FALSE; - } - break; - case XMPFILE_TYPE_FILE: - case XMPFILE_TYPE_NETFILE: - case XMPFILE_TYPE_NETSTREAM: - default: - { - xmplay_istream s( file ); - return ( openmpt::probe_file_header( openmpt::probe_file_header_flags_default2, s ) == openmpt::probe_file_header_result_success ) ? TRUE : FALSE; - } - break; - } - #else - if ( xmpffile->GetType( file ) == XMPFILE_TYPE_MEMORY ) { - std::string data( reinterpret_cast( xmpffile->GetMemory( file ) ), xmpffile->GetSize( file ) ); - std::istringstream s( data ); - return ( openmpt::probe_file_header( openmpt::probe_file_header_flags_default2, s ) == openmpt::probe_file_header_result_success ) ? TRUE : FALSE; - } else { - std::string data = read_XMPFILE_string( file ); - std::istringstream s(data); + switch ( xmpffile->GetType( file ) ) { + case XMPFILE_TYPE_MEMORY: + { + xmplay_imemstream s( reinterpret_cast( xmpffile->GetMemory( file ) ), xmpffile->GetSize( file ) ); return ( openmpt::probe_file_header( openmpt::probe_file_header_flags_default2, s ) == openmpt::probe_file_header_result_success ) ? TRUE : FALSE; } - #endif - #else - std::ifstream s( filename, std::ios_base::binary ); - return ( openmpt::probe_file_header( openmpt::probe_file_header_flags_default2, s ) == openmpt::probe_file_header_result_success ) ? TRUE : FALSE; - #endif + break; + case XMPFILE_TYPE_FILE: + case XMPFILE_TYPE_NETFILE: + { + if ( xmpfmisc->GetVersion() >= 0x03080200 ) { + xmplay_istream_seekable s( file ); + return ( openmpt::probe_file_header( openmpt::probe_file_header_flags_default2, s ) == openmpt::probe_file_header_result_success ) ? TRUE : FALSE; + } else { + xmplay_istream s( file ); + return ( openmpt::probe_file_header( openmpt::probe_file_header_flags_default2, s ) == openmpt::probe_file_header_result_success ) ? TRUE : FALSE; + } + } + break; + case XMPFILE_TYPE_NETSTREAM: + default: + { + xmplay_istream s( file ); + return ( openmpt::probe_file_header( openmpt::probe_file_header_flags_default2, s ) == openmpt::probe_file_header_result_success ) ? TRUE : FALSE; + } + break; + } } catch ( ... ) { return FALSE; } @@ -891,40 +912,36 @@ static DWORD WINAPI openmpt_GetFileInfo( const char * filename, XMPFILE file, fl { "load.skip_plugins", "1" }, { "load.skip_samples", "1" }, }; - #ifdef USE_XMPLAY_FILE_IO - #ifdef USE_XMPLAY_ISTREAM - switch ( xmpffile->GetType( file ) ) { - case XMPFILE_TYPE_MEMORY: - { - openmpt::module mod( xmpffile->GetMemory( file ), xmpffile->GetSize( file ), std::clog, ctls ); - subsongs = build_xmplay_file_info( mod, length, tags ); - } - break; - case XMPFILE_TYPE_FILE: - case XMPFILE_TYPE_NETFILE: - case XMPFILE_TYPE_NETSTREAM: - default: - { - xmplay_istream s( file ); - openmpt::module mod( s, std::clog, ctls ); - subsongs = build_xmplay_file_info( mod, length, tags ); - } - break; - } - #else - if ( xmpffile->GetType( file ) == XMPFILE_TYPE_MEMORY ) { + switch ( xmpffile->GetType( file ) ) { + case XMPFILE_TYPE_MEMORY: + { openmpt::module mod( xmpffile->GetMemory( file ), xmpffile->GetSize( file ), std::clog, ctls ); subsongs = build_xmplay_file_info( mod, length, tags ); - } else { - openmpt::module mod( read_XMPFILE_vector( file ), std::clog, ctls ); + } + break; + case XMPFILE_TYPE_FILE: + case XMPFILE_TYPE_NETFILE: + { + if ( xmpfmisc->GetVersion() >= 0x03080200 ) { + xmplay_istream_seekable s( file ); + openmpt::module mod( s, std::clog, ctls ); + subsongs = build_xmplay_file_info( mod, length, tags ); + } else { + xmplay_istream s( file ); + openmpt::module mod( s, std::clog, ctls ); + subsongs = build_xmplay_file_info( mod, length, tags ); + } + } + break; + case XMPFILE_TYPE_NETSTREAM: + default: + { + xmplay_istream s( file ); + openmpt::module mod( s, std::clog, ctls ); subsongs = build_xmplay_file_info( mod, length, tags ); } - #endif - #else - std::ifstream s( filename, std::ios_base::binary ); - openmpt::module mod( s, std::clog, ctls ); - subsongs = build_xmplay_file_info( mod, length, tags ); -#endif + break; + } } catch ( ... ) { if ( length ) *length = nullptr; if ( tags ) *tags = nullptr; @@ -945,32 +962,30 @@ static DWORD WINAPI openmpt_Open( const char * filename, XMPFILE file ) { { "play.at_end", "continue" }, }; self->delete_mod(); - #ifdef USE_XMPLAY_FILE_IO - #ifdef USE_XMPLAY_ISTREAM - switch ( xmpffile->GetType( file ) ) { - case XMPFILE_TYPE_MEMORY: - self->mod = new openmpt::module_ext( xmpffile->GetMemory( file ), xmpffile->GetSize( file ), std::clog, ctls ); - break; - case XMPFILE_TYPE_FILE: - case XMPFILE_TYPE_NETFILE: - case XMPFILE_TYPE_NETSTREAM: - default: - { - xmplay_istream s( file ); - self->mod = new openmpt::module_ext( s, std::clog, ctls ); - } - break; + switch ( xmpffile->GetType( file ) ) { + case XMPFILE_TYPE_MEMORY: + self->mod = new openmpt::module_ext( xmpffile->GetMemory( file ), xmpffile->GetSize( file ), std::clog, ctls ); + break; + case XMPFILE_TYPE_FILE: + case XMPFILE_TYPE_NETFILE: + { + if ( xmpfmisc->GetVersion() >= 0x03080200 ) { + xmplay_istream_seekable s( file ); + self->mod = new openmpt::module_ext( s, std::clog, ctls ); + } else { + xmplay_istream s( file ); + self->mod = new openmpt::module_ext( s, std::clog, ctls ); + } } - #else - if ( xmpffile->GetType( file ) == XMPFILE_TYPE_MEMORY ) { - self->mod = new openmpt::module_ext( xmpffile->GetMemory( file ), xmpffile->GetSize( file ), std::clog, ctls ); - } else { - self->mod = new openmpt::module_ext( read_XMPFILE_vector( file ), std::clog, ctls ); + break; + case XMPFILE_TYPE_NETSTREAM: + default: + { + xmplay_istream s( file ); + self->mod = new openmpt::module_ext( s, std::clog, ctls ); } - #endif - #else - self->mod = new openmpt::module_ext( std::ifstream( filename, std::ios_base::binary ), std::clog, ctls ); - #endif + break; + } self->on_new_mod(); clear_current_timeinfo(); reset_timeinfos(); @@ -1677,11 +1692,6 @@ static void WINAPI VisButton( DWORD /* x */ , DWORD /* y */ ) { #endif static XMPIN xmpin = { -#ifdef USE_XMPLAY_FILE_IO - 0 | -#else - XMPIN_FLAG_NOXMPFILE | -#endif XMPIN_FLAG_CONFIG | XMPIN_FLAG_LOOP, xmp_openmpt_string, nullptr, // "libopenmpt\0mptm/mptmz", diff --git a/Frameworks/OpenMPT/OpenMPT/openmpt123/openmpt123.cpp b/Frameworks/OpenMPT/OpenMPT/openmpt123/openmpt123.cpp index 6981dd7d3..eacc8be72 100644 --- a/Frameworks/OpenMPT/OpenMPT/openmpt123/openmpt123.cpp +++ b/Frameworks/OpenMPT/OpenMPT/openmpt123/openmpt123.cpp @@ -37,12 +37,23 @@ static const char * const license = #include "openmpt123_config.hpp" -#if defined(__MINGW32__) && !defined(__MINGW64__) +#include "mpt/base/detect_compiler.hpp" +#include "mpt/base/detect_os.hpp" +#include "mpt/base/detect_quirks.hpp" + +#if defined(MPT_LIBC_QUIRK_REQUIRES_SYS_TYPES_H) #include #endif -#include "mpt/base/check_platform.hpp" +#include "mpt/base/algorithm.hpp" #include "mpt/base/detect.hpp" +#include "mpt/main/main.hpp" + +#include "mpt/random/crand.hpp" +#include "mpt/random/default_engines.hpp" +#include "mpt/random/device.hpp" +#include "mpt/random/engine.hpp" +#include "mpt/random/seed.hpp" #include #include @@ -54,7 +65,6 @@ static const char * const license = #include #include #include -#include #include #include #include @@ -67,43 +77,18 @@ static const char * const license = #include #include #include -#include -#if MPT_OS_DJGPP -#include -#include -#include -#include -#include -#include -#include -#include -#include -#elif MPT_OS_WINDOWS -#include -#include -#include -#include -#if defined(__MINGW32__) && !defined(__MINGW64__) -#include -#endif -#include -#include -#include +#if MPT_OS_WINDOWS #include #include -#else -#include -#include -#include -#include -#include -#include #endif #include #include "openmpt123.hpp" +#include "openmpt123_exception.hpp" +#include "openmpt123_stdio.hpp" +#include "openmpt123_terminal.hpp" #include "openmpt123_flac.hpp" #include "openmpt123_mmio.hpp" @@ -144,67 +129,25 @@ struct show_long_version_number_exception : public std::exception { constexpr auto libopenmpt_encoding = mpt::common_encoding::utf8; -#if MPT_OS_WINDOWS && !MPT_WINRT_BEFORE(MPT_WIN_10) -bool IsConsole( DWORD stdHandle ) { - HANDLE hStd = GetStdHandle( stdHandle ); - if ( ( hStd != NULL ) && ( hStd != INVALID_HANDLE_VALUE ) ) { - DWORD mode = 0; - if ( GetConsoleMode( hStd, &mode ) != FALSE ) { - return true; - } - } - return false; -} -#endif // MPT_OS_WINDOWS && !MPT_WINRT_BEFORE(MPT_WIN_10) -bool IsTerminal( int fd ) { -#if MPT_OS_WINDOWS && !MPT_WINRT_BEFORE(MPT_WIN_10) - if ( !_isatty( fd ) ) { - return false; - } - DWORD stdHandle = 0; - if ( fd == 0 ) { - stdHandle = STD_INPUT_HANDLE; - } else if ( fd == 1 ) { - stdHandle = STD_OUTPUT_HANDLE; - } else if ( fd == 2 ) { - stdHandle = STD_ERROR_HANDLE; - } - return IsConsole( stdHandle ); -#else - return isatty( fd ) ? true : false; -#endif -} - -#if !MPT_OS_WINDOWS - -static termios saved_attributes; - -static void reset_input_mode() { - tcsetattr( STDIN_FILENO, TCSANOW, &saved_attributes ); -} - -static void set_input_mode() { - termios tattr; - if ( !isatty( STDIN_FILENO ) ) { - return; - } - tcgetattr( STDIN_FILENO, &saved_attributes ); - atexit( reset_input_mode ); - tcgetattr( STDIN_FILENO, &tattr ); - tattr.c_lflag &= ~( ICANON | ECHO ); - tattr.c_cc[VMIN] = 1; - tattr.c_cc[VTIME] = 0; - tcsetattr( STDIN_FILENO, TCSAFLUSH, &tattr ); -} - -#endif - -class file_audio_stream_raii : public file_audio_stream_base { +class file_audio_stream : public file_audio_stream_base { private: std::unique_ptr impl; public: - file_audio_stream_raii( const commandlineflags & flags, const mpt::native_path & filename, concat_stream & log ) + static void show_versions([[maybe_unused]] concat_stream & log ) { +#ifdef MPT_WITH_FLAC + log << MPT_USTRING(" FLAC ") << mpt::transcode( mpt::source_encoding, FLAC__VERSION_STRING ) << MPT_USTRING(", ") << mpt::transcode( mpt::source_encoding, FLAC__VENDOR_STRING ) << MPT_USTRING(", API ") << FLAC_API_VERSION_CURRENT << MPT_USTRING(".") << FLAC_API_VERSION_REVISION << MPT_USTRING(".") << FLAC_API_VERSION_AGE << MPT_USTRING(" ") << lf; +#endif +#ifdef MPT_WITH_SNDFILE + char sndfile_info[128]; + std::memset( sndfile_info, 0, sizeof( sndfile_info ) ); + sf_command( 0, SFC_GET_LIB_VERSION, sndfile_info, sizeof( sndfile_info ) ); + sndfile_info[127] = '\0'; + log << MPT_USTRING(" libsndfile ") << mpt::transcode( sndfile_encoding, sndfile_info ) << MPT_USTRING(" ") << lf; +#endif + } +public: + file_audio_stream( const commandlineflags & flags, const mpt::native_path & filename, [[maybe_unused]] concat_stream & log ) : impl(nullptr) { if ( !flags.force_overwrite ) { @@ -220,11 +163,11 @@ public: #if MPT_OS_WINDOWS && !MPT_OS_WINDOWS_WINRT } else if ( flags.output_extension == MPT_NATIVE_PATH("wav") ) { impl = std::make_unique( filename, flags, log ); -#endif +#endif #ifdef MPT_WITH_FLAC } else if ( flags.output_extension == MPT_NATIVE_PATH("flac") ) { impl = std::make_unique( filename, flags, log ); -#endif +#endif #ifdef MPT_WITH_SNDFILE } else { impl = std::make_unique( filename, flags, log ); @@ -234,7 +177,7 @@ public: throw exception( MPT_USTRING("file format handler '") + mpt::transcode( flags.output_extension ) + MPT_USTRING("' not found") ); } } - virtual ~file_audio_stream_raii() { + virtual ~file_audio_stream() { return; } void write_metadata( std::map metadata ) override { @@ -249,7 +192,148 @@ public: void write( const std::vector buffers, std::size_t frames ) override { impl->write( buffers, frames ); } -}; +}; + +class realtime_audio_stream : public write_buffers_interface { +private: + std::unique_ptr impl; +public: + static void show_versions( [[maybe_unused]] concat_stream & log ) { +#ifdef MPT_WITH_SDL2 + log << MPT_USTRING(" ") << show_sdl2_version() << lf; +#endif +#ifdef MPT_WITH_PULSEAUDIO + log << MPT_USTRING(" ") << show_pulseaudio_version() << lf; +#endif +#ifdef MPT_WITH_PORTAUDIO + log << MPT_USTRING(" ") << show_portaudio_version() << lf; +#endif + } + static void show_drivers( concat_stream & drivers ) { + drivers << MPT_USTRING(" Available drivers:") << lf; + drivers << MPT_USTRING(" default") << lf; +#if defined( MPT_WITH_PULSEAUDIO ) + drivers << MPT_USTRING(" pulseaudio") << lf; +#endif +#if defined( MPT_WITH_SDL2 ) + drivers << MPT_USTRING(" sdl2") << lf; +#endif +#if defined( MPT_WITH_PORTAUDIO ) + drivers << MPT_USTRING(" portaudio") << lf; +#endif +#if MPT_OS_WINDOWS + drivers << MPT_USTRING(" waveout") << lf; +#endif +#if defined( MPT_WITH_ALLEGRO42 ) + drivers << MPT_USTRING(" allegro42") << lf; +#endif + } + static void show_devices( concat_stream & devices, [[maybe_unused]] concat_stream & log ) { + devices << MPT_USTRING(" Available devices:") << lf; + devices << MPT_USTRING(" default: default") << lf; +#if defined( MPT_WITH_PULSEAUDIO ) + devices << MPT_USTRING(" pulseaudio:") << lf; + { + auto devs = show_pulseaudio_devices( log ); + for ( const auto & dev : devs ) { + devices << MPT_USTRING(" ") << dev << lf; + } + } +#endif +#if defined( MPT_WITH_SDL2 ) + devices << MPT_USTRING(" SDL2:") << lf; + { + auto devs = show_sdl2_devices( log ); + for ( const auto & dev : devs ) { + devices << MPT_USTRING(" ") << dev << lf; + } + } +#endif +#if defined( MPT_WITH_PORTAUDIO ) + devices << MPT_USTRING(" portaudio:") << lf; + { + auto devs = show_portaudio_devices( log ); + for ( const auto & dev : devs ) { + devices << MPT_USTRING(" ") << dev << lf; + } + } +#endif +#if MPT_OS_WINDOWS && !MPT_OS_WINDOWS_WINRT + devices << MPT_USTRING(" waveout:") << lf; + { + auto devs = show_waveout_devices( log ); + for ( const auto & dev : devs ) { + devices << MPT_USTRING(" ") << dev << lf; + } + } +#endif +#if defined( MPT_WITH_ALLEGRO42 ) + devices << MPT_USTRING(" allegro42:") << lf; + { + auto devs = show_allegro42_devices( log ); + for ( const auto & dev : devs ) { + devices << MPT_USTRING(" ") << dev << lf; + } + } +#endif + } +public: + realtime_audio_stream( commandlineflags & flags, [[maybe_unused]] concat_stream & log ) + : impl(nullptr) + { + if constexpr ( false ) { + // nothing +#if defined( MPT_WITH_PULSEAUDIO ) + } else if ( flags.driver == MPT_USTRING("pulseaudio") || flags.driver.empty() ) { + impl = std::make_unique( flags, log ); +#endif +#if defined( MPT_WITH_SDL2 ) + } else if ( flags.driver == MPT_USTRING("sdl2") || flags.driver.empty() ) { + impl = std::make_unique( flags, log ); +#endif +#if defined( MPT_WITH_PORTAUDIO ) + } else if ( flags.driver == MPT_USTRING("portaudio") || flags.driver.empty() ) { + impl = std::make_unique( flags, log ); +#endif +#if MPT_OS_WINDOWS && !MPT_OS_WINDOWS_WINRT + } else if ( flags.driver == MPT_USTRING("waveout") || flags.driver.empty() ) { + impl = std::make_unique( flags, log ); +#endif +#if defined( MPT_WITH_ALLEGRO42 ) + } else if ( flags.driver == MPT_USTRING("allegro42") || flags.driver.empty() ) { + impl = std::make_unique( flags, log ); +#endif + } else if ( flags.driver.empty() ) { + throw exception(MPT_USTRING("openmpt123 is compiled without any audio driver")); + } else { + throw exception( MPT_USTRING("audio driver '") + flags.driver + MPT_USTRING("' not found") ); + } + } + virtual ~realtime_audio_stream() { + return; + } + void write_metadata( std::map metadata ) override { + impl->write_metadata( metadata ); + } + void write_updated_metadata( std::map metadata ) override { + impl->write_updated_metadata( metadata ); + } + void write( const std::vector buffers, std::size_t frames ) override { + impl->write( buffers, frames ); + } + void write( const std::vector buffers, std::size_t frames ) override { + impl->write( buffers, frames ); + } + bool unpause() override { + return impl->unpause(); + } + bool sleep( int ms ) override { + return impl->sleep( ms ); + } + bool is_dummy() const override { + return impl->is_dummy(); + } +}; static mpt::ustring ctls_to_string( const std::map & ctls ) { mpt::ustring result; @@ -424,38 +508,8 @@ static void show_banner( concat_stream & log, verbosity banner ) { log << lf; log << MPT_USTRING(" libopenmpt compiler: ") << mpt::transcode( libopenmpt_encoding, openmpt::string::get( "build_compiler" ) ) << lf; log << MPT_USTRING(" libopenmpt features: ") << mpt::transcode( libopenmpt_encoding, openmpt::string::get( "library_features" ) ) << lf; -#ifdef MPT_WITH_SDL2 - log << MPT_USTRING(" libSDL2 "); - SDL_version sdlver; - std::memset( &sdlver, 0, sizeof( SDL_version ) ); - SDL_GetVersion( &sdlver ); - log << static_cast( sdlver.major ) << MPT_USTRING(".") << static_cast( sdlver.minor ) << MPT_USTRING(".") << static_cast( sdlver.patch ); - const char * revision = SDL_GetRevision(); - if ( revision ) { - log << MPT_USTRING(" (") << mpt::transcode( sdl2_encoding, revision ) << MPT_USTRING(")"); - } - log << MPT_USTRING(", "); - std::memset( &sdlver, 0, sizeof( SDL_version ) ); - SDL_VERSION( &sdlver ); - log << MPT_USTRING("API: ") << static_cast( sdlver.major ) << MPT_USTRING(".") << static_cast( sdlver.minor ) << MPT_USTRING(".") << static_cast( sdlver.patch ); - log << MPT_USTRING(" ") << lf; -#endif -#ifdef MPT_WITH_PULSEAUDIO - log << MPT_USTRING(" ") << MPT_USTRING("libpulse, libpulse-simple") << MPT_USTRING(" (headers ") << mpt::transcode( pulseaudio_encoding, pa_get_headers_version() ) << MPT_USTRING(", API ") << PA_API_VERSION << MPT_USTRING(", PROTOCOL ") << PA_PROTOCOL_VERSION << MPT_USTRING(", library ") << mpt::transcode( pulseaudio_encoding, ( pa_get_library_version() ? pa_get_library_version() : "unknown" ) ) << MPT_USTRING(") ") << lf; -#endif -#ifdef MPT_WITH_PORTAUDIO - log << MPT_USTRING(" ") << mpt::transcode( portaudio_encoding, Pa_GetVersionText() ) << MPT_USTRING(" (") << Pa_GetVersion() << MPT_USTRING(") ") << lf; -#endif -#ifdef MPT_WITH_FLAC - log << MPT_USTRING(" FLAC ") << mpt::transcode( mpt::source_encoding, FLAC__VERSION_STRING ) << MPT_USTRING(", ") << mpt::transcode( mpt::source_encoding, FLAC__VENDOR_STRING ) << MPT_USTRING(", API ") << FLAC_API_VERSION_CURRENT << MPT_USTRING(".") << FLAC_API_VERSION_REVISION << MPT_USTRING(".") << FLAC_API_VERSION_AGE << MPT_USTRING(" ") << lf; -#endif -#ifdef MPT_WITH_SNDFILE - char sndfile_info[128]; - std::memset( sndfile_info, 0, sizeof( sndfile_info ) ); - sf_command( 0, SFC_GET_LIB_VERSION, sndfile_info, sizeof( sndfile_info ) ); - sndfile_info[127] = '\0'; - log << MPT_USTRING(" libsndfile ") << mpt::transcode( sndfile_encoding, sndfile_info ) << MPT_USTRING(" ") << lf; -#endif + realtime_audio_stream::show_versions( log ); + file_audio_stream::show_versions( log ); log << lf; } @@ -1030,54 +1084,16 @@ void render_loop( commandlineflags & flags, Tmod & mod, double & duration, texto if ( flags.mode == Mode::UI ) { -#if MPT_OS_DJGPP - - while ( kbhit() ) { - int c = getch(); - if ( !handle_keypress( c, flags, mod, audio_stream ) ) { - return; - } - } - -#elif MPT_OS_WINDOWS && defined( UNICODE ) && !MPT_OS_WINDOWS_WINRT - - while ( _kbhit() ) { - wint_t c = _getwch(); - if ( !handle_keypress( c, flags, mod, audio_stream ) ) { - return; - } - } - -#elif MPT_OS_WINDOWS - - while ( _kbhit() ) { - int c = _getch(); - if ( !handle_keypress( c, flags, mod, audio_stream ) ) { - return; - } - } - -#else - - while ( true ) { - pollfd pollfds; - pollfds.fd = STDIN_FILENO; - pollfds.events = POLLIN; - poll(&pollfds, 1, 0); - if ( !( pollfds.revents & POLLIN ) ) { + while ( terminal_input::is_input_available() ) { + auto c = terminal_input::read_input_char(); + if ( !c ) { break; } - char c = 0; - if ( read( STDIN_FILENO, &c, 1 ) != 1 ) { - break; - } - if ( !handle_keypress( c, flags, mod, audio_stream ) ) { + if ( !handle_keypress( *c, flags, mod, audio_stream ) ) { return; } } -#endif - if ( flags.paused ) { audio_stream.sleep( flags.ui_redraw_interval ); continue; @@ -1635,15 +1651,15 @@ static void render_file( commandlineflags & flags, const mpt::native_path & file } -static mpt::native_path get_random_filename( std::set & filenames, std::default_random_engine & prng ) { - std::size_t index = std::uniform_int_distribution( 0, filenames.size() - 1 )( prng ); +static mpt::native_path get_random_filename( std::set & filenames, mpt::good_engine & prng ) { + std::size_t index = mpt::random( prng, 0, filenames.size() - 1 ); std::set::iterator it = filenames.begin(); std::advance( it, index ); return *it; } -static void render_files( commandlineflags & flags, textout & log, write_buffers_interface & audio_stream, std::default_random_engine & prng ) { +static void render_files( commandlineflags & flags, textout & log, write_buffers_interface & audio_stream, mpt::good_engine & prng ) { if ( flags.randomize ) { std::shuffle( flags.filenames.begin(), flags.filenames.end(), prng ); } @@ -1941,23 +1957,7 @@ static void parse_openmpt123( commandlineflags & flags, const std::vector drivers; - drivers << MPT_USTRING(" Available drivers:") << lf; - drivers << MPT_USTRING(" default") << lf; -#if defined( MPT_WITH_PULSEAUDIO ) - drivers << MPT_USTRING(" pulseaudio") << lf; -#endif -#if defined( MPT_WITH_SDL2 ) - drivers << MPT_USTRING(" sdl2") << lf; -#endif -#if defined( MPT_WITH_PORTAUDIO ) - drivers << MPT_USTRING(" portaudio") << lf; -#endif -#if MPT_OS_WINDOWS - drivers << MPT_USTRING(" waveout") << lf; -#endif -#if defined( MPT_WITH_ALLEGRO42 ) - drivers << MPT_USTRING(" allegro42") << lf; -#endif + realtime_audio_stream::show_drivers( drivers ); throw show_help_exception( drivers.str() ); } else if ( nextarg == MPT_USTRING("default") ) { flags.driver = MPT_USTRING(""); @@ -1970,23 +1970,7 @@ static void parse_openmpt123( commandlineflags & flags, const std::vector devices; - devices << MPT_USTRING(" Available devices:") << lf; - devices << MPT_USTRING(" default: default") << lf; -#if defined( MPT_WITH_PULSEAUDIO ) - devices << show_pulseaudio_devices(log); -#endif -#if defined( MPT_WITH_SDL2 ) - devices << show_sdl2_devices( log ); -#endif -#if defined( MPT_WITH_PORTAUDIO ) - devices << show_portaudio_devices( log ); -#endif -#if MPT_OS_WINDOWS && !MPT_OS_WINDOWS_WINRT - devices << show_waveout_devices( log ); -#endif -#if defined( MPT_WITH_ALLEGRO42 ) - devices << show_allegro42_devices( log ); -#endif + realtime_audio_stream::show_devices( devices, log ); throw show_help_exception( devices.str() ); } else if ( nextarg == MPT_USTRING("default") ) { flags.device = MPT_USTRING(""); @@ -2125,124 +2109,13 @@ static void parse_openmpt123( commandlineflags & flags, const std::vector args ) { -class FD_utf8_raii { -private: - FILE * file; - int old_mode; -public: - FD_utf8_raii( FILE * file, bool set_utf8 ) - : file(file) - , old_mode(-1) - { - if ( set_utf8 ) { - fflush( file ); - #if defined(UNICODE) - old_mode = _setmode( _fileno( file ), _O_U8TEXT ); - #else - old_mode = _setmode( _fileno( file ), _O_TEXT ); - #endif - if ( old_mode == -1 ) { - throw exception( MPT_USTRING("failed to set TEXT mode on file descriptor") ); - } - } - } - ~FD_utf8_raii() - { - if ( old_mode != -1 ) { - fflush( file ); - old_mode = _setmode( _fileno( file ), old_mode ); - } - } -}; + FILE_mode_guard stdout_text_guard( stdout, FILE_mode::text ); + FILE_mode_guard stderr_text_guard( stderr, FILE_mode::text ); -class FD_binary_raii { -private: - FILE * file; - int old_mode; -public: - FD_binary_raii( FILE * file, bool set_binary ) - : file(file) - , old_mode(-1) - { - if ( set_binary ) { - fflush( file ); - old_mode = _setmode( _fileno( file ), _O_BINARY ); - if ( old_mode == -1 ) { - throw exception( MPT_USTRING("failed to set binary mode on file descriptor") ); - } - } - } - ~FD_binary_raii() - { - if ( old_mode != -1 ) { - fflush( file ); - old_mode = _setmode( _fileno( file ), old_mode ); - } - } -}; - -#endif - -#if MPT_OS_DJGPP -/* Work-around */ -/* clang-format off */ -extern "C" { - int _crt0_startup_flags = 0 - | _CRT0_FLAG_NONMOVE_SBRK /* force interrupt compatible allocation */ - | _CRT0_DISABLE_SBRK_ADDRESS_WRAP /* force NT compatible allocation */ - | _CRT0_FLAG_LOCK_MEMORY /* lock all code and data at program startup */ - | 0; -} -/* clang-format on */ -#endif /* MPT_OS_DJGPP */ -#if MPT_OS_WINDOWS && defined(UNICODE) -static int wmain( int wargc, wchar_t * wargv [] ) { -#else -static int main( int argc, char * argv [] ) { -#endif - #if MPT_OS_DJGPP - assert(mpt::platform::libc().is_ok()); - _crt0_startup_flags &= ~_CRT0_FLAG_LOCK_MEMORY; /* disable automatic locking for all further memory allocations */ - #endif /* MPT_OS_DJGPP */ - std::vector args; - #if MPT_OS_WINDOWS && defined(UNICODE) - for ( int arg = 0; arg < wargc; ++arg ) { - args.push_back( mpt::transcode( wargv[arg] ) ); - } - #else - for ( int arg = 0; arg < argc; ++arg ) { - args.push_back( mpt::transcode( mpt::logical_encoding::locale, argv[arg] ) ); - } - #endif - -#if MPT_OS_WINDOWS - FD_utf8_raii stdin_utf8_guard( stdin, true ); - FD_utf8_raii stdout_utf8_guard( stdout, true ); - FD_utf8_raii stderr_utf8_guard( stderr, true ); -#endif - textout_dummy dummy_log; -#if MPT_OS_WINDOWS && !MPT_WINRT_BEFORE(MPT_WIN_10) -#if defined(UNICODE) - textout_ostream_console std_out( std::wcout, STD_OUTPUT_HANDLE ); - textout_ostream_console std_err( std::wclog, STD_ERROR_HANDLE ); -#else - textout_ostream_console std_out( std::cout, STD_OUTPUT_HANDLE ); - textout_ostream_console std_err( std::clog, STD_ERROR_HANDLE ); -#endif -#elif MPT_OS_WINDOWS -#if defined(UNICODE) - textout_wostream std_out( std::wcout ); - textout_wostream std_err( std::wclog ); -#else - textout_ostream std_out( std::cout ); - textout_ostream std_err( std::clog ); -#endif -#else - textout_ostream std_out( std::cout ); - textout_ostream std_err( std::clog ); -#endif + textout_wrapper std_out; + textout_wrapper std_err; commandlineflags flags; @@ -2317,39 +2190,25 @@ static int main( int argc, char * argv [] ) { try { - bool stdin_can_ui = true; - for ( const auto & filename : flags.filenames ) { - if ( filename == MPT_NATIVE_PATH("-") ) { - stdin_can_ui = false; - break; - } - } + const FILE_mode stdin_mode = mpt::contains( flags.filenames, MPT_NATIVE_PATH("-") ) ? FILE_mode::binary : FILE_mode::text; + const FILE_mode stdout_mode = flags.use_stdout ? FILE_mode::binary : FILE_mode::text; - bool stdout_can_ui = true; - if ( flags.use_stdout ) { - stdout_can_ui = false; - } + [[maybe_unused]] const bool stdin_text = ( stdin_mode == FILE_mode::text ); + [[maybe_unused]] const bool stdin_data = ( stdin_mode == FILE_mode::binary ); + [[maybe_unused]] const bool stdout_text = ( stdout_mode == FILE_mode::text ); + [[maybe_unused]] const bool stdout_data = ( stdout_mode == FILE_mode::binary ); - // set stdin binary -#if MPT_OS_WINDOWS - FD_binary_raii stdin_guard( stdin, !stdin_can_ui ); -#endif + // set stdin/stdout to binary for data input/output + [[maybe_unused]] std::optional stdin_guard{ stdin_data ? std::make_optional( stdin, FILE_mode::binary ) : std::nullopt }; + [[maybe_unused]] std::optional stdout_guard{ stdout_data ? std::make_optional( stdout, FILE_mode::binary ) : std::nullopt }; - // set stdout binary -#if MPT_OS_WINDOWS - FD_binary_raii stdout_guard( stdout, !stdout_can_ui ); -#endif + // setup terminal input + [[maybe_unused]] std::optional stdin_text_guard{ stdin_text ? std::make_optional( stdin, FILE_mode::text ) : std::nullopt }; + [[maybe_unused]] std::optional input_guard{ stdin_text && ( flags.mode == Mode::UI ) ? std::make_optional() : std::nullopt }; - // setup terminal - #if !MPT_OS_WINDOWS - if ( stdin_can_ui ) { - if ( flags.mode == Mode::UI ) { - set_input_mode(); - } - } - #endif - - textout & log = flags.quiet ? static_cast( dummy_log ) : static_cast( stdout_can_ui ? std_out : std_err ); + // choose text output between quiet/stdout/stderr + textout_dummy dummy_log; + textout & log = flags.quiet ? static_cast( dummy_log ) : stdout_text ? static_cast( std_out ) : static_cast( std_err ); show_banner( log, flags.banner ); @@ -2363,16 +2222,9 @@ static int main( int argc, char * argv [] ) { log.writeout(); - std::default_random_engine prng; - try { - std::random_device rd; - std::seed_seq seq{ rd(), static_cast( std::time( NULL ) ) }; - prng = std::default_random_engine{ seq }; - } catch ( const std::exception & ) { - std::seed_seq seq{ static_cast( std::time( NULL ) ) }; - prng = std::default_random_engine{ seq }; - } - std::srand( std::uniform_int_distribution()( prng ) ); + mpt::sane_random_device rd; + mpt::good_engine prng = mpt::make_prng( rd ); + mpt::crand::reseed( prng ); switch ( flags.mode ) { case Mode::Probe: { @@ -2393,45 +2245,17 @@ static int main( int argc, char * argv [] ) { render_files( flags, log, stdout_audio_stream, prng ); } else if ( !flags.output_filename.empty() ) { flags.apply_default_buffer_sizes(); - file_audio_stream_raii file_audio_stream( flags, flags.output_filename, log ); + file_audio_stream file_audio_stream( flags, flags.output_filename, log ); render_files( flags, log, file_audio_stream, prng ); -#if defined( MPT_WITH_PULSEAUDIO ) - } else if ( flags.driver == MPT_USTRING("pulseaudio") || flags.driver.empty() ) { - pulseaudio_stream_raii pulseaudio_stream( flags, log ); - render_files( flags, log, pulseaudio_stream, prng ); -#endif -#if defined( MPT_WITH_SDL2 ) - } else if ( flags.driver == MPT_USTRING("sdl2") || flags.driver.empty() ) { - sdl2_stream_raii sdl2_stream( flags, log ); - render_files( flags, log, sdl2_stream, prng ); -#endif -#if defined( MPT_WITH_PORTAUDIO ) - } else if ( flags.driver == MPT_USTRING("portaudio") || flags.driver.empty() ) { - portaudio_stream_raii portaudio_stream( flags, log ); - render_files( flags, log, portaudio_stream, prng ); -#endif -#if MPT_OS_WINDOWS && !MPT_OS_WINDOWS_WINRT - } else if ( flags.driver == MPT_USTRING("waveout") || flags.driver.empty() ) { - waveout_stream_raii waveout_stream( flags ); - render_files( flags, log, waveout_stream, prng ); -#endif -#if defined( MPT_WITH_ALLEGRO42 ) - } else if ( flags.driver == MPT_USTRING("allegro42") || flags.driver.empty() ) { - allegro42_stream_raii allegro42_stream( flags, log ); - render_files( flags, log, allegro42_stream, prng ); -#endif } else { - if ( flags.driver.empty() ) { - throw exception( MPT_USTRING("openmpt123 is compiled without any audio driver") ); - } else { - throw exception( MPT_USTRING("audio driver '") + flags.driver + MPT_USTRING("' not found") ); - } + realtime_audio_stream audio_stream( flags, log ); + render_files( flags, log, audio_stream, prng ); } } break; case Mode::Render: { for ( const auto & filename : flags.filenames ) { flags.apply_default_buffer_sizes(); - file_audio_stream_raii file_audio_stream( flags, filename + MPT_NATIVE_PATH(".") + flags.output_extension, log ); + file_audio_stream file_audio_stream( flags, filename + MPT_NATIVE_PATH(".") + flags.output_extension, log ); render_file( flags, filename, log, file_audio_stream ); flags.playlist_index++; } @@ -2446,30 +2270,6 @@ static int main( int argc, char * argv [] ) { std_err << MPT_USTRING("Error parsing command line.") << lf; std_err.writeout(); return 1; -#ifdef MPT_WITH_ALLEGRO42 - } catch ( allegro42_exception & e ) { - std_err << MPT_USTRING("Allegro-4.2 error: ") << mpt::get_exception_text( e ) << lf; - std_err.writeout(); - return 1; -#endif -#ifdef MPT_WITH_PULSEAUDIO - } catch ( pulseaudio_exception & e ) { - std_err << MPT_USTRING("PulseAudio error: ") << mpt::get_exception_text( e ) << lf; - std_err.writeout(); - return 1; -#endif -#ifdef MPT_WITH_PORTAUDIO - } catch ( portaudio_exception & e ) { - std_err << MPT_USTRING("PortAudio error: ") << mpt::get_exception_text( e ) << lf; - std_err.writeout(); - return 1; -#endif -#ifdef MPT_WITH_SDL2 - } catch ( sdl2_exception & e ) { - std_err << MPT_USTRING("SDL2 error: ") << mpt::get_exception_text( e ) << lf; - std_err.writeout(); - return 1; -#endif } catch ( silent_exit_exception & ) { return 0; } catch ( exception & e ) { @@ -2491,17 +2291,5 @@ static int main( int argc, char * argv [] ) { } // namespace openmpt123 -#if MPT_OS_WINDOWS && defined(UNICODE) -#if defined(__GNUC__) || (defined(__clang__) && !defined(_MSC_VER)) -// mingw64 does only default to special C linkage for "main", but not for "wmain". -extern "C" int wmain( int wargc, wchar_t * wargv [] ); -extern "C" -#endif -int wmain( int wargc, wchar_t * wargv [] ) { - return openmpt123::wmain( wargc, wargv ); -} -#else -int main( int argc, char * argv [] ) { - return openmpt123::main( argc, argv ); -} -#endif + +MPT_MAIN_IMPLEMENT_MAIN(openmpt123) diff --git a/Frameworks/OpenMPT/OpenMPT/openmpt123/openmpt123.hpp b/Frameworks/OpenMPT/OpenMPT/openmpt123/openmpt123.hpp index 92542821e..636dfdb52 100644 --- a/Frameworks/OpenMPT/OpenMPT/openmpt123/openmpt123.hpp +++ b/Frameworks/OpenMPT/OpenMPT/openmpt123/openmpt123.hpp @@ -12,9 +12,12 @@ #include "openmpt123_config.hpp" +#include "openmpt123_exception.hpp" +#include "openmpt123_terminal.hpp" + #include "mpt/base/compiletime_warning.hpp" #include "mpt/base/detect.hpp" -#include "mpt/base/floatingpoint.hpp" +#include "mpt/base/float.hpp" #include "mpt/base/math.hpp" #include "mpt/base/namespace.hpp" #include "mpt/base/preprocessor.hpp" @@ -63,10 +66,6 @@ struct string_transcoder { namespace openmpt123 { -struct exception : public openmpt::exception { - exception( const mpt::ustring & text ) : openmpt::exception(mpt::transcode( mpt::common_encoding::utf8, text )) { } -}; - struct show_help_exception { mpt::ustring message; bool longhelp; @@ -94,45 +93,6 @@ inline Tstring align_right( const Tchar pad, std::size_t width, const T val ) { return str; } -template -struct concat_stream { - virtual concat_stream & append( Tstring str ) = 0; - virtual ~concat_stream() = default; - inline concat_stream & operator<<( concat_stream & (*func)( concat_stream & s ) ) { - return func( *this ); - } -}; - -template -inline concat_stream & lf( concat_stream & s ) { - return s.append( Tstring(1, mpt::char_constants::lf) ); -} - -template -inline concat_stream & operator<<( concat_stream & s, const T & val ) { - return s.append( mpt::default_formatter::template format( val ) ); -} - -template -struct string_concat_stream - : public concat_stream -{ -private: - Tstring m_str; -public: - inline void str( Tstring s ) { - m_str = std::move( s ); - } - inline concat_stream & append( Tstring s ) override { - m_str += std::move( s ); - return *this; - } - inline Tstring str() const { - return m_str; - } - ~string_concat_stream() override = default; -}; - struct field { mpt::ustring key; @@ -140,201 +100,6 @@ struct field { }; -#if MPT_OS_WINDOWS -bool IsConsole( DWORD stdHandle ); -#endif -bool IsTerminal( int fd ); - - -class textout : public string_concat_stream { -public: - textout() { - return; - } - virtual ~textout() { - return; - } -protected: - mpt::ustring pop() { - mpt::ustring text = str(); - str( mpt::ustring() ); - return text; - } -public: - virtual void writeout() = 0; - virtual void cursor_up( std::size_t lines ) { - static_cast( lines ); - } -}; - -class textout_dummy : public textout { -public: - textout_dummy() { - return; - } - virtual ~textout_dummy() { - return; - } -public: - void writeout() override { - static_cast( pop() ); - } -}; - -class textout_ostream : public textout { -private: - std::ostream & s; -#if MPT_OS_DJGPP - mpt::common_encoding codepage; -#endif -public: - textout_ostream( std::ostream & s_ ) - : s(s_) -#if MPT_OS_DJGPP - , codepage(mpt::common_encoding::cp437) -#endif - { - #if MPT_OS_DJGPP - codepage = mpt::djgpp_get_locale_encoding(); - #endif - return; - } - virtual ~textout_ostream() { - writeout_impl(); - } -private: - void writeout_impl() { - mpt::ustring text = pop(); - if ( text.length() > 0 ) { - #if MPT_OS_DJGPP - s << mpt::transcode( codepage, text ); - #elif MPT_OS_EMSCRIPTEN - s << mpt::transcode( mpt::common_encoding::utf8, text ) ; - #else - s << mpt::transcode( mpt::logical_encoding::locale, text ); - #endif - s.flush(); - } - } -public: - void writeout() override { - writeout_impl(); - } - void cursor_up( std::size_t lines ) override { - s.flush(); - for ( std::size_t line = 0; line < lines; ++line ) { - *this << MPT_USTRING("\x1b[1A"); - } - } -}; - -#if MPT_OS_WINDOWS && defined(UNICODE) - -class textout_wostream : public textout { -private: - std::wostream & s; -public: - textout_wostream( std::wostream & s_ ) - : s(s_) - { - return; - } - virtual ~textout_wostream() { - writeout_impl(); - } -private: - void writeout_impl() { - mpt::ustring text = pop(); - if ( text.length() > 0 ) { - s << mpt::transcode( text ); - s.flush(); - } - } -public: - void writeout() override { - writeout_impl(); - } - void cursor_up( std::size_t lines ) override { - s.flush(); - for ( std::size_t line = 0; line < lines; ++line ) { - *this << MPT_USTRING("\x1b[1A"); - } - } -}; - -#endif // MPT_OS_WINDOWS && UNICODE - -#if MPT_OS_WINDOWS && !MPT_WINRT_BEFORE(MPT_WIN_10) - -class textout_ostream_console : public textout { -private: -#if defined(UNICODE) - std::wostream & s; -#else - std::ostream & s; -#endif - HANDLE handle; - bool console; -public: -#if defined(UNICODE) - textout_ostream_console( std::wostream & s_, DWORD stdHandle_ ) -#else - textout_ostream_console( std::ostream & s_, DWORD stdHandle_ ) -#endif - : s(s_) - , handle(GetStdHandle( stdHandle_ )) - , console(IsConsole( stdHandle_ )) - { - return; - } - virtual ~textout_ostream_console() { - writeout_impl(); - } -private: - void writeout_impl() { - mpt::ustring text = pop(); - if ( text.length() > 0 ) { - if ( console ) { - DWORD chars_written = 0; - #if defined(UNICODE) - std::wstring wtext = mpt::transcode( text ); - WriteConsole( handle, wtext.data(), static_cast( wtext.size() ), &chars_written, NULL ); - #else - std::string ltext = mpt::transcode( mpt::logical_encoding::locale, text ); - WriteConsole( handle, ltext.data(), static_cast( ltext.size() ), &chars_written, NULL ); - #endif - } else { - #if defined(UNICODE) - s << mpt::transcode( text ); - #else - s << mpt::transcode( mpt::logical_encoding::locale, text ); - #endif - s.flush(); - } - } - } -public: - void writeout() override { - writeout_impl(); - } - void cursor_up( std::size_t lines ) override { - if ( console ) { - s.flush(); - CONSOLE_SCREEN_BUFFER_INFO csbi; - ZeroMemory( &csbi, sizeof( CONSOLE_SCREEN_BUFFER_INFO ) ); - COORD coord_cursor = COORD(); - if ( GetConsoleScreenBufferInfo( handle, &csbi ) != FALSE ) { - coord_cursor = csbi.dwCursorPosition; - coord_cursor.X = 1; - coord_cursor.Y -= static_cast( lines ); - SetConsoleCursorPosition( handle, coord_cursor ); - } - } - } -}; - -#endif // MPT_OS_WINDOWS && !MPT_WINRT_BEFORE(MPT_WIN_10) - inline mpt::ustring append_software_tag( mpt::ustring software ) { mpt::ustring openmpt123 = mpt::ustring() + MPT_USTRING("openmpt123 ") @@ -404,51 +169,53 @@ enum verbosity : std::int8_t { }; struct commandlineflags { - Mode mode; - std::int32_t ui_redraw_interval; - mpt::ustring driver; - mpt::ustring device; - std::int32_t buffer; - std::int32_t period; - std::int32_t samplerate; - std::int32_t channels; - std::int32_t gain; - std::int32_t separation; - std::int32_t filtertaps; - std::int32_t ramping; // ramping strength : -1:default 0:off 1 2 3 4 5 // roughly milliseconds - std::int32_t tempo; - std::int32_t pitch; - std::int32_t dither; - std::int32_t repeatcount; - std::int32_t subsong; - std::map ctls; - double seek_target; - double end_time; - bool quiet; - verbosity banner; - bool verbose; - bool assume_terminal; - int terminal_width; - int terminal_height; - bool show_details; - bool show_message; - bool show_ui; - bool show_progress; - bool show_meters; - bool show_channel_meters; - bool show_pattern; - bool use_float; - bool use_stdout; - bool randomize; - bool shuffle; - bool restart; - std::size_t playlist_index; - std::vector filenames; - mpt::native_path output_filename; - mpt::native_path output_extension; - bool force_overwrite; - bool paused; - mpt::ustring warnings; + + Mode mode = Mode::UI; + std::int32_t ui_redraw_interval = default_high; + mpt::ustring driver = MPT_USTRING(""); + mpt::ustring device = MPT_USTRING(""); + std::int32_t buffer = default_high; + std::int32_t period = default_high; + std::int32_t samplerate = MPT_OS_DJGPP ? 44100 : 48000; + std::int32_t channels = 2; + std::int32_t gain = 0; + std::int32_t separation = 100; + std::int32_t filtertaps = 8; + std::int32_t ramping = -1; // ramping strength : -1:default 0:off 1 2 3 4 5 // roughly milliseconds + std::int32_t tempo = 0; + std::int32_t pitch = 0; + std::int32_t dither = 1; + std::int32_t repeatcount = 0; + std::int32_t subsong = -1; + std::map ctls = {}; + double seek_target = 0.0; + double end_time = 0.0; + bool quiet = false; + verbosity banner = verbosity_normal; + bool verbose = false; + bool assume_terminal = false; + int terminal_width = -1; + int terminal_height = -1; + bool show_details = true; + bool show_message = false; + bool show_ui = true; + bool show_progress = true; + bool show_meters = true; + bool show_channel_meters = false; + bool show_pattern = false; + bool use_float = MPT_OS_DJGPP ? false : mpt::float_traits::is_hard && mpt::float_traits::is_ieee754_binary; + bool use_stdout = false; + bool randomize = false; + bool shuffle = false; + bool restart = false; + std::size_t playlist_index = 0; + std::vector filenames = {}; + mpt::native_path output_filename = MPT_NATIVE_PATH(""); + mpt::native_path output_extension = MPT_NATIVE_PATH("auto"); + bool force_overwrite = false; + bool paused = false; + mpt::ustring warnings = MPT_USTRING(""); + void apply_default_buffer_sizes() { if ( ui_redraw_interval == default_high ) { ui_redraw_interval = 50; @@ -466,103 +233,7 @@ struct commandlineflags { period = 10; } } - commandlineflags() { - mode = Mode::UI; - ui_redraw_interval = default_high; - driver = MPT_USTRING(""); - device = MPT_USTRING(""); - buffer = default_high; - period = default_high; -#if MPT_OS_DJGPP - samplerate = 44100; - channels = 2; - use_float = false; -#else - samplerate = 48000; - channels = 2; - use_float = mpt::float_traits::is_hard && mpt::float_traits::is_ieee754_binary; -#endif - gain = 0; - separation = 100; - filtertaps = 8; - ramping = -1; - tempo = 0; - pitch = 0; - dither = 1; - repeatcount = 0; - subsong = -1; - seek_target = 0.0; - end_time = 0.0; - quiet = false; - banner = verbosity_normal; - verbose = false; - assume_terminal = false; -#if MPT_OS_DJGPP - terminal_width = 80; - terminal_height = 25; -#else - terminal_width = 72; - terminal_height = 23; -#endif -#if MPT_OS_WINDOWS && !MPT_WINRT_BEFORE(MPT_WIN_10) - terminal_width = 72; - terminal_height = 23; - HANDLE hStdOutput = GetStdHandle( STD_OUTPUT_HANDLE ); - if ( ( hStdOutput != NULL ) && ( hStdOutput != INVALID_HANDLE_VALUE ) ) { - CONSOLE_SCREEN_BUFFER_INFO csbi; - ZeroMemory( &csbi, sizeof( CONSOLE_SCREEN_BUFFER_INFO ) ); - if ( GetConsoleScreenBufferInfo( hStdOutput, &csbi ) != FALSE ) { - terminal_width = std::min( static_cast( 1 + csbi.srWindow.Right - csbi.srWindow.Left ), static_cast( csbi.dwSize.X ) ); - terminal_height = std::min( static_cast( 1 + csbi.srWindow.Bottom - csbi.srWindow.Top ), static_cast( csbi.dwSize.Y ) ); - } - } -#else // !(MPT_OS_WINDOWS && !MPT_WINRT_BEFORE(MPT_WIN_10)) - if ( isatty( STDERR_FILENO ) ) { - const char * env_columns = std::getenv( "COLUMNS" ); - if ( env_columns ) { - int tmp = mpt::parse_or( env_columns, 0 ); - if ( tmp > 0 ) { - terminal_width = tmp; - } - } - const char * env_rows = std::getenv( "ROWS" ); - if ( env_rows ) { - int tmp = mpt::parse_or( env_rows, 0 ); - if ( tmp > 0 ) { - terminal_height = tmp; - } - } - #if defined(TIOCGWINSZ) - struct winsize ts; - if ( ioctl( STDERR_FILENO, TIOCGWINSZ, &ts ) >= 0 ) { - terminal_width = ts.ws_col; - terminal_height = ts.ws_row; - } - #elif defined(TIOCGSIZE) - struct ttysize ts; - if ( ioctl( STDERR_FILENO, TIOCGSIZE, &ts ) >= 0 ) { - terminal_width = ts.ts_cols; - terminal_height = ts.ts_rows; - } - #endif - } -#endif // MPT_OS_WINDOWS && !MPT_WINRT_BEFORE(MPT_WIN_10) - show_details = true; - show_message = false; - show_ui = true; - show_progress = true; - show_meters = true; - show_channel_meters = false; - show_pattern = false; - use_stdout = false; - randomize = false; - shuffle = false; - restart = false; - playlist_index = 0; - output_extension = MPT_NATIVE_PATH("auto"); - force_overwrite = false; - paused = false; - } + void check_and_sanitize() { bool canUI = true; bool canProgress = true; @@ -575,6 +246,7 @@ struct commandlineflags { canProgress = isatty( STDERR_FILENO ) ? true : false; #endif // MPT_OS_WINDOWS } + query_terminal_size( terminal_width, terminal_height ); if ( filenames.size() == 0 ) { throw args_nofiles_exception(); } @@ -673,6 +345,7 @@ struct commandlineflags { output_extension = MPT_NATIVE_PATH("wav"); } } + }; template < typename Tsample > Tsample convert_sample_to( float val ); @@ -687,11 +360,10 @@ template < > std::int16_t convert_sample_to( float val ) { } class write_buffers_interface { -protected: +public: virtual ~write_buffers_interface() { return; } -public: virtual void write_metadata( std::map metadata ) { (void)metadata; return; diff --git a/Frameworks/OpenMPT/OpenMPT/openmpt123/openmpt123_allegro42.hpp b/Frameworks/OpenMPT/OpenMPT/openmpt123/openmpt123_allegro42.hpp index 0e3d8dc45..88674317d 100644 --- a/Frameworks/OpenMPT/OpenMPT/openmpt123/openmpt123_allegro42.hpp +++ b/Frameworks/OpenMPT/OpenMPT/openmpt123/openmpt123_allegro42.hpp @@ -39,7 +39,7 @@ struct allegro42_exception : public exception { } } allegro42_exception() - : exception( error_to_string() ) + : exception( MPT_USTRING("Allegro-4.2: ") + error_to_string() ) { } }; @@ -174,11 +174,10 @@ public: } }; -static mpt::ustring show_allegro42_devices( concat_stream & /* log */ ) { - string_concat_stream devices; - devices << MPT_USTRING(" allegro42:") << lf; - devices << MPT_USTRING(" ") << MPT_USTRING("0") << MPT_USTRING(": Default Device") << lf; - return devices.str(); +inline std::vector show_allegro42_devices( concat_stream & /* log */ ) { + string_concat_stream device; + device << MPT_USTRING("0") << MPT_USTRING(": Default Device"); + return { device.str() }; } } // namespace openmpt123 diff --git a/Frameworks/OpenMPT/OpenMPT/openmpt123/openmpt123_exception.hpp b/Frameworks/OpenMPT/OpenMPT/openmpt123/openmpt123_exception.hpp new file mode 100644 index 000000000..79e23c54e --- /dev/null +++ b/Frameworks/OpenMPT/OpenMPT/openmpt123/openmpt123_exception.hpp @@ -0,0 +1,30 @@ +/* + * openmpt123_exception.hpp + * ------------------------ + * Purpose: libopenmpt command line player + * Notes : (currently none) + * Authors: OpenMPT Devs + * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. + */ + +#ifndef OPENMPT123_EXCEPTION_HPP +#define OPENMPT123_EXCEPTION_HPP + +#include "openmpt123_config.hpp" + +#include "mpt/string/types.hpp" +#include "mpt/string_transcode/transcode.hpp" + +#include + +#include + +namespace openmpt123 { + +struct exception : public openmpt::exception { + exception( const mpt::ustring & text ) : openmpt::exception(mpt::transcode( mpt::common_encoding::utf8, text )) { } +}; + +} // namespace openmpt123 + +#endif // OPENMPT123_EXCEPTION_HPP diff --git a/Frameworks/OpenMPT/OpenMPT/openmpt123/openmpt123_flac.hpp b/Frameworks/OpenMPT/OpenMPT/openmpt123/openmpt123_flac.hpp index 597913fd3..727be20a8 100644 --- a/Frameworks/OpenMPT/OpenMPT/openmpt123/openmpt123_flac.hpp +++ b/Frameworks/OpenMPT/OpenMPT/openmpt123/openmpt123_flac.hpp @@ -18,6 +18,11 @@ #include "mpt/base/detect.hpp" #include "mpt/base/saturate_round.hpp" +#include +#if MPT_PLATFORM_MULTITHREADED && !defined(MPT_COMPILER_QUIRK_NO_STDCPP_THREADS) +#include +#endif + #if defined(_MSC_VER) && defined(__clang__) && defined(__c2__) #include #if __STDC__ @@ -66,6 +71,14 @@ public: FLAC__stream_encoder_set_bits_per_sample( encoder, flags.use_float ? 24 : 16 ); FLAC__stream_encoder_set_sample_rate( encoder, flags.samplerate ); FLAC__stream_encoder_set_compression_level( encoder, 8 ); +#if (FLAC_API_VERSION_CURRENT >= 14) && MPT_PLATFORM_MULTITHREADED && !defined(MPT_COMPILER_QUIRK_NO_STDCPP_THREADS) + std::uint32_t threads = static_cast(std::max(std::thread::hardware_concurrency(), static_cast(1))); + // Work-around . + //FLAC__stream_encoder_set_num_threads( encoder, threads ); + while ( ( FLAC__stream_encoder_set_num_threads( encoder, threads ) == FLAC__STREAM_ENCODER_SET_NUM_THREADS_TOO_MANY_THREADS ) && ( threads > 1 ) ) { + threads = ( ( threads > 256 ) ? 256 : ( threads - 1 ) ); + } +#endif } ~flac_stream_raii() { if ( encoder ) { diff --git a/Frameworks/OpenMPT/OpenMPT/openmpt123/openmpt123_portaudio.hpp b/Frameworks/OpenMPT/OpenMPT/openmpt123/openmpt123_portaudio.hpp index c6f3ab534..68b29feb0 100644 --- a/Frameworks/OpenMPT/OpenMPT/openmpt123/openmpt123_portaudio.hpp +++ b/Frameworks/OpenMPT/OpenMPT/openmpt123/openmpt123_portaudio.hpp @@ -30,7 +30,7 @@ namespace openmpt123 { inline constexpr auto portaudio_encoding = mpt::common_encoding::utf8; struct portaudio_exception : public exception { - portaudio_exception( PaError code ) : exception( mpt::transcode( portaudio_encoding, Pa_GetErrorText( code ) ) ) { } + portaudio_exception( PaError code ) : exception( MPT_USTRING("PortAudio: ") + mpt::transcode( portaudio_encoding, Pa_GetErrorText( code ) ) ) { } }; typedef void (*PaUtilLogCallback ) (const char *log); @@ -258,38 +258,44 @@ public: #define portaudio_stream_raii portaudio_stream_blocking_raii -static mpt::ustring show_portaudio_devices( concat_stream & log ) { - string_concat_stream devices; - devices << MPT_USTRING(" portaudio:") << lf; +inline std::vector show_portaudio_devices( concat_stream & log ) { + std::vector devices; portaudio_raii portaudio( false, log ); for ( PaDeviceIndex i = 0; i < Pa_GetDeviceCount(); ++i ) { if ( Pa_GetDeviceInfo( i ) && Pa_GetDeviceInfo( i )->maxOutputChannels > 0 ) { - devices << MPT_USTRING(" ") << i << MPT_USTRING(": "); + string_concat_stream device; + device << i << MPT_USTRING(": "); if ( Pa_GetHostApiInfo( Pa_GetDeviceInfo( i )->hostApi ) && Pa_GetHostApiInfo( Pa_GetDeviceInfo( i )->hostApi )->name ) { - devices << mpt::transcode( portaudio_encoding, Pa_GetHostApiInfo( Pa_GetDeviceInfo( i )->hostApi )->name ); + device << mpt::transcode( portaudio_encoding, Pa_GetHostApiInfo( Pa_GetDeviceInfo( i )->hostApi )->name ); } else { - devices << MPT_USTRING("Host API ") << Pa_GetDeviceInfo( i )->hostApi; + device << MPT_USTRING("Host API ") << Pa_GetDeviceInfo( i )->hostApi; } if ( Pa_GetHostApiInfo( Pa_GetDeviceInfo( i )->hostApi ) ) { if ( i == Pa_GetHostApiInfo( Pa_GetDeviceInfo( i )->hostApi )->defaultOutputDevice ) { - devices << MPT_USTRING(" (default)"); + device << MPT_USTRING(" (default)"); } } - devices << MPT_USTRING(" - "); + device << MPT_USTRING(" - "); if ( Pa_GetDeviceInfo( i )->name ) { - devices << mpt::transcode( portaudio_encoding, Pa_GetDeviceInfo( i )->name ); + device << mpt::transcode( portaudio_encoding, Pa_GetDeviceInfo( i )->name ); } else { - devices << MPT_USTRING("Device ") << i; + device << MPT_USTRING("Device ") << i; } - devices << MPT_USTRING(" ("); - devices << MPT_USTRING("high latency: ") << Pa_GetDeviceInfo( i )->defaultHighOutputLatency; - devices << MPT_USTRING(", "); - devices << MPT_USTRING("low latency: ") << Pa_GetDeviceInfo( i )->defaultLowOutputLatency; - devices << MPT_USTRING(")"); - devices << lf; + device << MPT_USTRING(" ("); + device << MPT_USTRING("high latency: ") << Pa_GetDeviceInfo( i )->defaultHighOutputLatency; + device << MPT_USTRING(", "); + device << MPT_USTRING("low latency: ") << Pa_GetDeviceInfo( i )->defaultLowOutputLatency; + device << MPT_USTRING(")"); + devices.push_back( device.str() ); } } - return devices.str(); + return devices; +} + +inline mpt::ustring show_portaudio_version() { + string_concat_stream log; + log << mpt::transcode( portaudio_encoding, Pa_GetVersionText() ) << MPT_USTRING(" (") << Pa_GetVersion() << MPT_USTRING(") "); + return log.str(); } } // namespace openmpt123 diff --git a/Frameworks/OpenMPT/OpenMPT/openmpt123/openmpt123_pulseaudio.hpp b/Frameworks/OpenMPT/OpenMPT/openmpt123/openmpt123_pulseaudio.hpp index 22d0862b8..b4d1ab992 100644 --- a/Frameworks/OpenMPT/OpenMPT/openmpt123/openmpt123_pulseaudio.hpp +++ b/Frameworks/OpenMPT/OpenMPT/openmpt123/openmpt123_pulseaudio.hpp @@ -44,7 +44,7 @@ struct pulseaudio_exception : public exception { return mpt::ustring(); } } - pulseaudio_exception( int error ) : exception( error_to_string( error ) ) { } + pulseaudio_exception( int error ) : exception( MPT_USTRING("PulseAudio: ") + error_to_string( error ) ) { } }; class pulseaudio_stream_raii : public write_buffers_interface { @@ -154,11 +154,16 @@ public: } }; -static mpt::ustring show_pulseaudio_devices( concat_stream & /* log */ ) { - string_concat_stream devices; - devices << MPT_USTRING(" pulseaudio:") << lf; - devices << MPT_USTRING(" ") << MPT_USTRING("0") << MPT_USTRING(": Default Device") << lf; - return devices.str(); +inline std::vector show_pulseaudio_devices( concat_stream & /* log */ ) { + string_concat_stream device; + device << MPT_USTRING("0") << MPT_USTRING(": Default Device"); + return { device.str() }; +} + +inline mpt::ustring show_pulseaudio_version() { + string_concat_stream log; + log << MPT_USTRING("libpulse, libpulse-simple") << MPT_USTRING(" (headers ") << mpt::transcode( pulseaudio_encoding, pa_get_headers_version() ) << MPT_USTRING(", API ") << PA_API_VERSION << MPT_USTRING(", PROTOCOL ") << PA_PROTOCOL_VERSION << MPT_USTRING(", library ") << mpt::transcode( pulseaudio_encoding, ( pa_get_library_version() ? pa_get_library_version() : "unknown" ) ) << MPT_USTRING(") "); + return log.str(); } } // namespace openmpt123 diff --git a/Frameworks/OpenMPT/OpenMPT/openmpt123/openmpt123_sdl2.hpp b/Frameworks/OpenMPT/OpenMPT/openmpt123/openmpt123_sdl2.hpp index 9ac0fd3b7..35318d3d2 100644 --- a/Frameworks/OpenMPT/OpenMPT/openmpt123/openmpt123_sdl2.hpp +++ b/Frameworks/OpenMPT/OpenMPT/openmpt123/openmpt123_sdl2.hpp @@ -46,7 +46,7 @@ private: return s.str(); } public: - sdl2_exception( int code, const char * error ) : exception( text_from_code( code ) + MPT_USTRING(" (") + mpt::transcode( sdl2_encoding, error ? std::string(error) : std::string("NULL") ) + MPT_USTRING(")") ) { } + sdl2_exception( int code, const char * error ) : exception( MPT_USTRING("SDL2: ") + text_from_code( code ) + MPT_USTRING(" (") + mpt::transcode( sdl2_encoding, error ? std::string(error) : std::string("NULL") ) + MPT_USTRING(")") ) { } }; static void check_sdl2_error( int e ) { @@ -189,10 +189,9 @@ public: } }; -static mpt::ustring show_sdl2_devices( concat_stream & /* log */ ) { - string_concat_stream devices; +inline std::vector show_sdl2_devices( concat_stream & /* log */ ) { + std::vector devices; std::size_t device_index = 0; - devices << MPT_USTRING(" SDL2:") << lf; sdl2_raii sdl2( SDL_INIT_NOPARACHUTE | SDL_INIT_AUDIO ); for ( int driver = 0; driver < SDL_GetNumAudioDrivers(); ++driver ) { const char * driver_name = SDL_GetAudioDriver( driver ); @@ -206,6 +205,7 @@ static mpt::ustring show_sdl2_devices( concat_stream & /* log */ ) continue; } for ( int device = 0; device < SDL_GetNumAudioDevices( 0 ); ++device ) { + string_concat_stream dev; const char * device_name = SDL_GetAudioDeviceName( device, 0 ); if ( !device_name ) { continue; @@ -213,12 +213,32 @@ static mpt::ustring show_sdl2_devices( concat_stream & /* log */ ) if ( std::string( device_name ).empty() ) { continue; } - devices << MPT_USTRING(" ") << device_index << MPT_USTRING(": ") << mpt::transcode( sdl2_encoding, driver_name ) << MPT_USTRING(" - ") << mpt::transcode( sdl2_encoding, device_name ) << lf; + dev << device_index << MPT_USTRING(": ") << mpt::transcode( sdl2_encoding, driver_name ) << MPT_USTRING(" - ") << mpt::transcode( sdl2_encoding, device_name ); device_index++; + devices.push_back( dev.str() ); } SDL_AudioQuit(); } - return devices.str(); + return devices; +} + +inline mpt::ustring show_sdl2_version() { + string_concat_stream log; + log << MPT_USTRING("libSDL2 "); + SDL_version sdlver; + std::memset(&sdlver, 0, sizeof(SDL_version)); + SDL_GetVersion(&sdlver); + log << static_cast(sdlver.major) << MPT_USTRING(".") << static_cast(sdlver.minor) << MPT_USTRING(".") << static_cast(sdlver.patch); + const char* revision = SDL_GetRevision(); + if (revision) { + log << MPT_USTRING(" (") << mpt::transcode(sdl2_encoding, revision) << MPT_USTRING(")"); + } + log << MPT_USTRING(", "); + std::memset(&sdlver, 0, sizeof(SDL_version)); + SDL_VERSION(&sdlver); + log << MPT_USTRING("API: ") << static_cast(sdlver.major) << MPT_USTRING(".") << static_cast(sdlver.minor) << MPT_USTRING(".") << static_cast(sdlver.patch); + log << MPT_USTRING(" "); + return log.str(); } } // namespace openmpt123 diff --git a/Frameworks/OpenMPT/OpenMPT/openmpt123/openmpt123_stdio.hpp b/Frameworks/OpenMPT/OpenMPT/openmpt123/openmpt123_stdio.hpp new file mode 100644 index 000000000..64b46fc7b --- /dev/null +++ b/Frameworks/OpenMPT/OpenMPT/openmpt123/openmpt123_stdio.hpp @@ -0,0 +1,141 @@ +/* + * openmpt123_stdio.hpp + * -------------------- + * Purpose: libopenmpt command line player + * Notes : (currently none) + * Authors: OpenMPT Devs + * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. + */ + +#ifndef OPENMPT123_STDIO_HPP +#define OPENMPT123_STDIO_HPP + +#include "openmpt123_config.hpp" + +#include "openmpt123_exception.hpp" + +#include "mpt/base/detect.hpp" +#include "mpt/base/namespace.hpp" +#include "mpt/string/types.hpp" + +#include + +#if MPT_OS_DJGPP +#include +#include +#elif MPT_OS_WINDOWS +#include +#include +#endif +#include + +namespace openmpt123 { + +enum class FILE_mode { + text, + binary, +}; + +#if MPT_OS_DJGPP + +class FILE_mode_guard { +private: + FILE * file; + int old_mode; +public: + FILE_mode_guard( FILE * file, FILE_mode new_mode ) + : file(file) + , old_mode(-1) + { + switch (new_mode) { + case FILE_mode::text: + fflush( file ); + old_mode = setmode( fileno( file ), O_TEXT ); + if ( old_mode == -1 ) { + throw exception( MPT_USTRING("failed to set TEXT mode on file descriptor") ); + } + break; + case FILE_mode::binary: + fflush( file ); + old_mode = setmode( fileno( file ), O_BINARY ); + if ( old_mode == -1 ) { + throw exception( MPT_USTRING("failed to set binary mode on file descriptor") ); + } + break; + } + } + FILE_mode_guard( const FILE_mode_guard & ) = delete; + FILE_mode_guard( FILE_mode_guard && ) = default; + FILE_mode_guard & operator=( const FILE_mode_guard & ) = delete; + FILE_mode_guard & operator=( FILE_mode_guard && ) = default; + ~FILE_mode_guard() { + if ( old_mode != -1 ) { + fflush( file ); + old_mode = setmode( fileno( file ), old_mode ); + } + } +}; + +#elif MPT_OS_WINDOWS + +class FILE_mode_guard { +private: + FILE * file; + int old_mode; +public: + FILE_mode_guard( FILE * file, FILE_mode new_mode ) + : file(file) + , old_mode(-1) + { + switch (new_mode) { + case FILE_mode::text: + fflush( file ); + #if defined(UNICODE) + old_mode = _setmode( _fileno( file ), _O_U8TEXT ); + #else + old_mode = _setmode( _fileno( file ), _O_TEXT ); + #endif + if ( old_mode == -1 ) { + throw exception( MPT_USTRING("failed to set TEXT mode on file descriptor") ); + } + break; + case FILE_mode::binary: + fflush( file ); + old_mode = _setmode( _fileno( file ), _O_BINARY ); + if ( old_mode == -1 ) { + throw exception( MPT_USTRING("failed to set binary mode on file descriptor") ); + } + break; + } + } + FILE_mode_guard( const FILE_mode_guard & ) = delete; + FILE_mode_guard( FILE_mode_guard && ) = default; + FILE_mode_guard & operator=( const FILE_mode_guard & ) = delete; + FILE_mode_guard & operator=( FILE_mode_guard && ) = default; + ~FILE_mode_guard() { + if ( old_mode != -1 ) { + fflush( file ); + old_mode = _setmode( _fileno( file ), old_mode ); + } + } +}; + +#else + +class FILE_mode_guard { +public: + FILE_mode_guard( FILE * /* file */, FILE_mode /* new_mode */ ) { + return; + } + FILE_mode_guard( const FILE_mode_guard & ) = delete; + FILE_mode_guard( FILE_mode_guard && ) = default; + FILE_mode_guard & operator=( const FILE_mode_guard & ) = delete; + FILE_mode_guard & operator=( FILE_mode_guard && ) = default; + ~FILE_mode_guard() = default; +}; + +#endif + +} // namespace openmpt123 + +#endif // OPENMPT123_STDIO_HPP diff --git a/Frameworks/OpenMPT/OpenMPT/openmpt123/openmpt123_terminal.hpp b/Frameworks/OpenMPT/OpenMPT/openmpt123/openmpt123_terminal.hpp new file mode 100644 index 000000000..0a9021c91 --- /dev/null +++ b/Frameworks/OpenMPT/OpenMPT/openmpt123/openmpt123_terminal.hpp @@ -0,0 +1,525 @@ +/* + * openmpt123_terminal.hpp + * ----------------------- + * Purpose: libopenmpt command line player + * Notes : (currently none) + * Authors: OpenMPT Devs + * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. + */ + +#ifndef OPENMPT123_TERMINAL_HPP +#define OPENMPT123_TERMINAL_HPP + +#include "openmpt123_config.hpp" + +#include "mpt/base/detect.hpp" +#include "mpt/base/namespace.hpp" +#include "mpt/format/simple.hpp" +#include "mpt/parse/parse.hpp" +#include "mpt/string/types.hpp" +#include "mpt/string_transcode/transcode.hpp" + +#include +#include +#include +#include + +#include + +#if MPT_OS_DJGPP +#include +#include +#include +#include +#include +#include +#include +#include +#elif MPT_OS_WINDOWS +#include +#include +#include +#include +#if MPT_LIBC_MINGW +#include +#endif +#include +#include +#include +#else +#include +#include +#include +#include +#include +#include +#endif + +namespace openmpt123 { + +template +struct concat_stream { + virtual concat_stream & append( Tstring str ) = 0; + virtual ~concat_stream() = default; + inline concat_stream & operator<<( concat_stream & (*func)( concat_stream & s ) ) { + return func( *this ); + } +}; + +template +inline concat_stream & lf( concat_stream & s ) { + return s.append( Tstring(1, mpt::char_constants::lf) ); +} + +template +inline concat_stream & operator<<( concat_stream & s, const T & val ) { + return s.append( mpt::default_formatter::template format( val ) ); +} + +template +struct string_concat_stream + : public concat_stream +{ +private: + Tstring m_str; +public: + inline void str( Tstring s ) { + m_str = std::move( s ); + } + inline concat_stream & append( Tstring s ) override { + m_str += std::move( s ); + return *this; + } + inline Tstring str() const { + return m_str; + } + ~string_concat_stream() override = default; +}; + + +#if MPT_OS_WINDOWS +inline std::optional StdHandleFromFd( int fd ) { + std::optional stdHandle; + if ( fd == _fileno( stdin ) ) { + stdHandle = STD_INPUT_HANDLE; + } else if ( fd == _fileno( stdout ) ) { + stdHandle = STD_OUTPUT_HANDLE; + } else if ( fd == _fileno( stderr ) ) { + stdHandle = STD_ERROR_HANDLE; + } + return stdHandle; +} +#endif + +#if MPT_OS_WINDOWS && !MPT_WINRT_BEFORE(MPT_WIN_10) +inline bool IsConsole( DWORD stdHandle ) { + HANDLE hStd = GetStdHandle( stdHandle ); + if ( ( hStd == NULL ) || ( hStd == INVALID_HANDLE_VALUE ) ) { + return false; + } + DWORD mode = 0; + return GetConsoleMode( hStd, &mode ) != FALSE; +} +#endif // MPT_OS_WINDOWS && !MPT_WINRT_BEFORE(MPT_WIN_10) + +inline bool IsTerminal( int fd ) { +#if MPT_OS_WINDOWS && !MPT_WINRT_BEFORE(MPT_WIN_10) + if ( !_isatty( fd ) ) { + return false; + } + std::optional stdHandle = StdHandleFromFd( fd ); + if ( !stdHandle ) { + return false; + } + return IsConsole( *stdHandle ); +#else + return isatty( fd ) ? true : false; +#endif +} + + +class textout : public string_concat_stream { +protected: + textout() = default; +public: + virtual ~textout() = default; +protected: + mpt::ustring pop() { + mpt::ustring text = str(); + str( mpt::ustring() ); + return text; + } +public: + virtual void writeout() = 0; + virtual void cursor_up( std::size_t lines ) = 0; +}; + + + +class textout_dummy : public textout { +public: + textout_dummy() = default; + ~textout_dummy() override { + static_cast( pop() ); + } +public: + void writeout() override { + static_cast( pop() ); + } + void cursor_up( std::size_t lines ) override { + static_cast( lines ); + } +}; + + + +enum class textout_destination { + destination_stdout, + destination_stderr, +}; + +class textout_backend { +protected: + textout_backend() = default; +public: + virtual ~textout_backend() = default; +public: + virtual void write( const mpt::ustring & text ) = 0; + virtual void cursor_up(std::size_t lines) = 0; +}; + + + +class textout_ostream : public textout_backend { +private: + std::ostream & s; +#if MPT_OS_DJGPP + mpt::common_encoding codepage; +#endif +public: + textout_ostream( std::ostream & s_ ) + : s(s_) +#if MPT_OS_DJGPP + , codepage(mpt::common_encoding::cp437) +#endif + { + #if MPT_OS_DJGPP + codepage = mpt::djgpp_get_locale_encoding(); + #endif + return; + } + ~textout_ostream() override = default; +public: + void write( const mpt::ustring & text ) override { + if ( text.length() > 0 ) { + #if MPT_OS_DJGPP + s << mpt::transcode( codepage, text ); + #elif MPT_OS_EMSCRIPTEN + s << mpt::transcode( mpt::common_encoding::utf8, text ) ; + #else + s << mpt::transcode( mpt::logical_encoding::locale, text ); + #endif + s.flush(); + } + } + void cursor_up( std::size_t lines ) override { + s.flush(); + for ( std::size_t line = 0; line < lines; ++line ) { + s << std::string("\x1b[1A"); + } + } +}; + +#if MPT_OS_WINDOWS && defined(UNICODE) + +class textout_wostream : public textout_backend { +private: + std::wostream & s; +public: + textout_wostream( std::wostream & s_ ) + : s(s_) + { + return; + } + ~textout_wostream() override = default; +public: + void write( const mpt::ustring & text ) override { + if ( text.length() > 0 ) { + s << mpt::transcode( text ); + s.flush(); + } + } + void cursor_up( std::size_t lines ) override { + s.flush(); + for ( std::size_t line = 0; line < lines; ++line ) { + s << std::wstring(L"\x1b[1A"); + } + } +}; + +#endif // MPT_OS_WINDOWS && UNICODE + +#if MPT_OS_WINDOWS && !MPT_WINRT_BEFORE(MPT_WIN_10) + +class textout_ostream_console : public textout_backend { +private: +#if defined(UNICODE) + std::wostream & s; +#else + std::ostream & s; +#endif + HANDLE handle; + bool console; +public: +#if defined(UNICODE) + textout_ostream_console( std::wostream & s_, DWORD stdHandle_ ) +#else + textout_ostream_console( std::ostream & s_, DWORD stdHandle_ ) +#endif + : s(s_) + , handle(GetStdHandle( stdHandle_ )) + , console(IsConsole( stdHandle_ )) + { + return; + } + ~textout_ostream_console() override = default; +public: + void write( const mpt::ustring & text ) override { + if ( text.length() > 0 ) { + if ( console ) { + DWORD chars_written = 0; + #if defined(UNICODE) + std::wstring wtext = mpt::transcode( text ); + WriteConsole( handle, wtext.data(), static_cast( wtext.size() ), &chars_written, NULL ); + #else + std::string ltext = mpt::transcode( mpt::logical_encoding::locale, text ); + WriteConsole( handle, ltext.data(), static_cast( ltext.size() ), &chars_written, NULL ); + #endif + } else { + #if defined(UNICODE) + s << mpt::transcode( text ); + #else + s << mpt::transcode( mpt::logical_encoding::locale, text ); + #endif + s.flush(); + } + } + } + void cursor_up( std::size_t lines ) override { + if ( console ) { + s.flush(); + CONSOLE_SCREEN_BUFFER_INFO csbi; + ZeroMemory( &csbi, sizeof( CONSOLE_SCREEN_BUFFER_INFO ) ); + COORD coord_cursor = COORD(); + if ( GetConsoleScreenBufferInfo( handle, &csbi ) != FALSE ) { + coord_cursor = csbi.dwCursorPosition; + coord_cursor.X = 1; + coord_cursor.Y -= static_cast( lines ); + SetConsoleCursorPosition( handle, coord_cursor ); + } + } + } +}; + +#endif // MPT_OS_WINDOWS && !MPT_WINRT_BEFORE(MPT_WIN_10) + + + +template +class textout_wrapper : public textout { +private: +#if MPT_OS_WINDOWS && !MPT_WINRT_BEFORE(MPT_WIN_10) +#if defined(UNICODE) + textout_ostream_console out{ dest == textout_destination::destination_stdout ? std::wcout : std::wclog, dest == textout_destination::destination_stdout ? STD_OUTPUT_HANDLE : STD_ERROR_HANDLE }; +#else + textout_ostream_console out{ dest == textout_destination::destination_stdout ? std::cout : std::clog, dest == textout_destination::destination_stdout ? STD_OUTPUT_HANDLE : STD_ERROR_HANDLE }; +#endif +#elif MPT_OS_WINDOWS +#if defined(UNICODE) + textout_wostream out{ dest == textout_destination::destination_stdout ? std::wcout : std::wclog }; +#else + textout_ostream out{ dest == textout_destination::destination_stdout ? std::cout : std::clog }; +#endif +#else + textout_ostream out{ dest == textout_destination::destination_stdout ? std::cout : std::clog }; +#endif +public: + textout_wrapper() = default; + ~textout_wrapper() override { + out.write( pop() ); + } +public: + void writeout() override { + out.write( pop() ); + } + void cursor_up(std::size_t lines) override { + out.cursor_up( lines ); + } +}; + + +inline void query_terminal_size( int & terminal_width, int & terminal_height ) { +#if MPT_OS_WINDOWS && !MPT_WINRT_BEFORE(MPT_WIN_10) + HANDLE hStdOutput = GetStdHandle( STD_OUTPUT_HANDLE ); + if ( ( hStdOutput != NULL ) && ( hStdOutput != INVALID_HANDLE_VALUE ) ) { + CONSOLE_SCREEN_BUFFER_INFO csbi; + ZeroMemory( &csbi, sizeof( CONSOLE_SCREEN_BUFFER_INFO ) ); + if ( GetConsoleScreenBufferInfo( hStdOutput, &csbi ) != FALSE ) { + if ( terminal_width <= 0 ) { + terminal_width = std::min( static_cast( 1 + csbi.srWindow.Right - csbi.srWindow.Left ), static_cast( csbi.dwSize.X ) ); + } + if ( terminal_height <= 0 ) { + terminal_height = std::min( static_cast( 1 + csbi.srWindow.Bottom - csbi.srWindow.Top ), static_cast( csbi.dwSize.Y ) ); + } + } + } +#else // !(MPT_OS_WINDOWS && !MPT_WINRT_BEFORE(MPT_WIN_10)) + if ( isatty( STDERR_FILENO ) ) { + if ( terminal_width <= 0 ) { + const char * env_columns = std::getenv( "COLUMNS" ); + if ( env_columns ) { + int tmp = mpt::parse_or( env_columns, 0 ); + if ( tmp > 0 ) { + terminal_width = tmp; + } + } + } + if ( terminal_height <= 0 ) { + const char * env_rows = std::getenv( "ROWS" ); + if ( env_rows ) { + int tmp = mpt::parse_or( env_rows, 0 ); + if ( tmp > 0 ) { + terminal_height = tmp; + } + } + } + #if defined(TIOCGWINSZ) + struct winsize ts; + if ( ioctl( STDERR_FILENO, TIOCGWINSZ, &ts ) >= 0 ) { + if ( terminal_width <= 0 ) { + terminal_width = ts.ws_col; + } + if ( terminal_height <= 0 ) { + terminal_height = ts.ws_row; + } + } + #elif defined(TIOCGSIZE) + struct ttysize ts; + if ( ioctl( STDERR_FILENO, TIOCGSIZE, &ts ) >= 0 ) { + if ( terminal_width <= 0 ) { + terminal_width = ts.ts_cols; + } + if ( terminal_height <= 0 ) { + terminal_height = ts.ts_rows; + } + } + #endif + } +#endif // MPT_OS_WINDOWS && !MPT_WINRT_BEFORE(MPT_WIN_10) +#if MPT_OS_DJGPP + if ( terminal_width <= 0 ) { + terminal_width = 80; + } + if ( terminal_height <= 0 ) { + terminal_height = 25; + } +#else + if ( terminal_width <= 0 ) { + terminal_width = 72; + } + if ( terminal_height <= 0 ) { + terminal_height = 23; + } +#endif +} + + +#if MPT_OS_WINDOWS + +class terminal_ui_guard { +public: + terminal_ui_guard() = default; + terminal_ui_guard( const terminal_ui_guard & ) = delete; + terminal_ui_guard( terminal_ui_guard && ) = default; + terminal_ui_guard & operator=( const terminal_ui_guard & ) = delete; + terminal_ui_guard & operator=( terminal_ui_guard && ) = default; + ~terminal_ui_guard() = default; +}; + +#else + +class terminal_ui_guard { +private: + bool changed = false; + termios saved_attributes; +public: + terminal_ui_guard() { + if ( !isatty( STDIN_FILENO ) ) { + return; + } + tcgetattr( STDIN_FILENO, &saved_attributes ); + termios tattr = saved_attributes; + tattr.c_lflag &= ~( ICANON | ECHO ); + tattr.c_cc[VMIN] = 1; + tattr.c_cc[VTIME] = 0; + tcsetattr( STDIN_FILENO, TCSAFLUSH, &tattr ); + changed = true; + } + terminal_ui_guard( const terminal_ui_guard & ) = delete; + terminal_ui_guard( terminal_ui_guard && ) = default; + terminal_ui_guard & operator=( const terminal_ui_guard & ) = delete; + terminal_ui_guard & operator=( terminal_ui_guard && ) = default; + ~terminal_ui_guard() { + if ( changed ) { + tcsetattr(STDIN_FILENO, TCSANOW, &saved_attributes); + } + } +}; + +#endif + + +class terminal_input { +public: + static inline bool is_input_available() { +#if MPT_OS_DJGPP + return kbhit() ? true : false; +#elif MPT_OS_WINDOWS && defined( UNICODE ) && !MPT_OS_WINDOWS_WINRT + return _kbhit() ? true : false; +#elif MPT_OS_WINDOWS + return _kbhit() ? true : false; +#else + pollfd pollfds; + pollfds.fd = STDIN_FILENO; + pollfds.events = POLLIN; + poll(&pollfds, 1, 0); + if ( !( pollfds.revents & POLLIN ) ) { + return false; + } + return true; +#endif + } + static inline std::optional read_input_char() { +#if MPT_OS_DJGPP + int c = getch(); + return static_cast( c ); +#elif MPT_OS_WINDOWS && defined( UNICODE ) && !MPT_OS_WINDOWS_WINRT + wint_t c = _getwch(); + return static_cast( c ); +#elif MPT_OS_WINDOWS + int c = _getch(); + return static_cast( c ); +#else + char c = 0; + if ( read( STDIN_FILENO, &c, 1 ) != 1 ) { + return std::nullopt; + } + return static_cast( c ); +#endif + } +}; + + +} // namespace openmpt123 + +#endif // OPENMPT123_TERMINAL_HPP diff --git a/Frameworks/OpenMPT/OpenMPT/openmpt123/openmpt123_waveout.hpp b/Frameworks/OpenMPT/OpenMPT/openmpt123/openmpt123_waveout.hpp index 07b34dbe1..b8589448e 100644 --- a/Frameworks/OpenMPT/OpenMPT/openmpt123/openmpt123_waveout.hpp +++ b/Frameworks/OpenMPT/OpenMPT/openmpt123/openmpt123_waveout.hpp @@ -20,7 +20,7 @@ namespace openmpt123 { struct waveout_exception : public exception { - waveout_exception() : exception( MPT_USTRING("waveout") ) { } + waveout_exception() : exception( MPT_USTRING("WaveOut: ") + MPT_USTRING("waveout") ) { } }; class waveout_stream_raii : public write_buffers_interface { @@ -34,7 +34,7 @@ private: std::vector > wavebuffers; std::deque byte_queue; public: - waveout_stream_raii( commandlineflags & flags ) + waveout_stream_raii( commandlineflags & flags, concat_stream & /* log */ ) : waveout(NULL) , num_channels(0) , num_chunks(0) @@ -177,22 +177,22 @@ public: } }; -static mpt::ustring show_waveout_devices( concat_stream & /*log*/ ) { - string_concat_stream devices; - devices << MPT_USTRING(" waveout:") << lf; +inline std::vector show_waveout_devices( concat_stream & /*log*/ ) { + std::vector devices; for ( UINT i = 0; i < waveOutGetNumDevs(); ++i ) { - devices << MPT_USTRING(" ") << i << MPT_USTRING(": "); + string_concat_stream device; + device << i << MPT_USTRING(": "); WAVEOUTCAPS caps; ZeroMemory( &caps, sizeof( caps ) ); waveOutGetDevCaps( i, &caps, sizeof( caps ) ); #if defined(UNICODE) - devices << mpt::transcode( caps.szPname ); + device << mpt::transcode( caps.szPname ); #else - devices << mpt::transcode( mpt::logical_encoding::locale, caps.szPname ); + device << mpt::transcode( mpt::logical_encoding::locale, caps.szPname ); #endif - devices << lf; + devices.push_back( device.str() ); } - return devices.str(); + return devices; } } // namespace openmpt123 diff --git a/Frameworks/OpenMPT/OpenMPT/sounddsp/AGC.cpp b/Frameworks/OpenMPT/OpenMPT/sounddsp/AGC.cpp index fac3408a4..a91e8e13b 100644 --- a/Frameworks/OpenMPT/OpenMPT/sounddsp/AGC.cpp +++ b/Frameworks/OpenMPT/OpenMPT/sounddsp/AGC.cpp @@ -85,7 +85,7 @@ static UINT ProcessAGC(int *pBuffer, int *pRearBuffer, std::size_t nSamples, std CAGC::CAGC() { - Initialize(true, 44100); + Initialize(true, 48000); } diff --git a/Frameworks/OpenMPT/OpenMPT/sounddsp/EQ.cpp b/Frameworks/OpenMPT/OpenMPT/sounddsp/EQ.cpp index abf308c38..b58e23bc5 100644 --- a/Frameworks/OpenMPT/OpenMPT/sounddsp/EQ.cpp +++ b/Frameworks/OpenMPT/OpenMPT/sounddsp/EQ.cpp @@ -19,18 +19,16 @@ #include "openmpt/soundbase/MixSample.hpp" #include "openmpt/soundbase/MixSampleConvert.hpp" -#ifndef NO_EQ -#if defined(MPT_ENABLE_ARCH_INTRINSICS_SSE) +#if defined(MPT_WANT_ARCH_INTRINSICS_X86_SSE) #include "../common/mptCPU.h" #endif -#endif #include #include #include -#if defined(MPT_ENABLE_ARCH_INTRINSICS_SSE) +#if defined(MPT_WANT_ARCH_INTRINSICS_X86_SSE) && defined(MPT_ARCH_INTRINSICS_X86_SSE) #if MPT_COMPILER_MSVC #include #endif @@ -100,7 +98,7 @@ static void EQFilter(Tbuf & buf, const std::array template void CEQ::ProcessTemplate(TMixSample *frontBuffer, TMixSample *rearBuffer, std::size_t countFrames, std::size_t numChannels) { -#if defined(MPT_ENABLE_ARCH_INTRINSICS_SSE) +#if defined(MPT_WANT_ARCH_INTRINSICS_X86_SSE) && defined(MPT_ARCH_INTRINSICS_X86_SSE) unsigned int old_csr = 0; if(CPU::HasFeatureSet(CPU::feature::sse) && CPU::HasModesEnabled(CPU::mode::xmm128sse)) { @@ -122,7 +120,7 @@ void CEQ::ProcessTemplate(TMixSample *frontBuffer, TMixSample *rearBuffer, std:: mpt::audio_span_planar_strided buf{ buffers.data(), 4, countFrames, 2 }; EQFilter<4>(buf, m_Bands, m_ChannelState); } -#if defined(MPT_ENABLE_ARCH_INTRINSICS_SSE) +#if defined(MPT_WANT_ARCH_INTRINSICS_X86_SSE) && defined(MPT_ARCH_INTRINSICS_X86_SSE) if(CPU::HasFeatureSet(CPU::feature::sse) && CPU::HasModesEnabled(CPU::mode::xmm128sse)) { _mm_setcsr(old_csr); diff --git a/Frameworks/OpenMPT/OpenMPT/sounddsp/Reverb.cpp b/Frameworks/OpenMPT/OpenMPT/sounddsp/Reverb.cpp index bf2cae4ff..e4ce2648b 100644 --- a/Frameworks/OpenMPT/OpenMPT/sounddsp/Reverb.cpp +++ b/Frameworks/OpenMPT/OpenMPT/sounddsp/Reverb.cpp @@ -13,13 +13,13 @@ #ifndef NO_REVERB #include "Reverb.h" -#if defined(MPT_ENABLE_ARCH_INTRINSICS_SSE2) +#if defined(MPT_WANT_ARCH_INTRINSICS_X86_SSE2) #include "../common/mptCPU.h" #endif #include "../soundlib/MixerLoops.h" #include "mpt/base/numbers.hpp" -#if defined(MPT_ENABLE_ARCH_INTRINSICS_SSE2) +#if defined(MPT_WANT_ARCH_INTRINSICS_X86_SSE2) && defined(MPT_ARCH_INTRINSICS_X86_SSE2) #if MPT_COMPILER_MSVC #include #endif @@ -35,7 +35,7 @@ OPENMPT_NAMESPACE_BEGIN #ifndef NO_REVERB -#if defined(MPT_ENABLE_ARCH_INTRINSICS_SSE2) +#if defined(MPT_WANT_ARCH_INTRINSICS_X86_SSE2) && defined(MPT_ARCH_INTRINSICS_X86_SSE2) // Load two 32-bit values static MPT_FORCEINLINE __m128i Load64SSE(const int32 *x) { return _mm_loadl_epi64(reinterpret_cast(x)); } // Load four 16-bit values @@ -594,7 +594,7 @@ void CReverb::ReverbProcessPostFiltering2x(const int32 * MPT_RESTRICT pRvb, int3 // Stereo Add + DC removal void CReverb::ReverbProcessPostFiltering1x(const int32 * MPT_RESTRICT pRvb, int32 * MPT_RESTRICT pDry, uint32 nSamples) { -#if defined(MPT_ENABLE_ARCH_INTRINSICS_SSE2) +#if defined(MPT_WANT_ARCH_INTRINSICS_X86_SSE2) && defined(MPT_ARCH_INTRINSICS_X86_SSE2) if(CPU::HasFeatureSet(CPU::feature::sse2) && CPU::HasModesEnabled(CPU::mode::xmm128sse)) { __m128i nDCRRvb_Y1 = Load64SSE(gnDCRRvb_Y1); @@ -656,7 +656,7 @@ void CReverb::ReverbProcessPostFiltering1x(const int32 * MPT_RESTRICT pRvb, int3 void CReverb::ReverbDCRemoval(int32 * MPT_RESTRICT pBuffer, uint32 nSamples) { -#if defined(MPT_ENABLE_ARCH_INTRINSICS_SSE2) +#if defined(MPT_WANT_ARCH_INTRINSICS_X86_SSE2) && defined(MPT_ARCH_INTRINSICS_X86_SSE2) if(CPU::HasFeatureSet(CPU::feature::sse2) && CPU::HasModesEnabled(CPU::mode::xmm128sse)) { __m128i nDCRRvb_Y1 = Load64SSE(gnDCRRvb_Y1); @@ -721,7 +721,7 @@ void CReverb::ProcessPreDelay(SWRvbRefDelay * MPT_RESTRICT pPreDelay, const int3 { uint32 preDifPos = pPreDelay->nPreDifPos; uint32 delayPos = pPreDelay->nDelayPos - 1; -#if defined(MPT_ENABLE_ARCH_INTRINSICS_SSE2) +#if defined(MPT_WANT_ARCH_INTRINSICS_X86_SSE2) && defined(MPT_ARCH_INTRINSICS_X86_SSE2) if(CPU::HasFeatureSet(CPU::feature::sse2) && CPU::HasModesEnabled(CPU::mode::xmm128sse)) { __m128i coeffs = _mm_cvtsi32_si128(pPreDelay->nCoeffs.lr); @@ -793,7 +793,7 @@ void CReverb::ProcessPreDelay(SWRvbRefDelay * MPT_RESTRICT pPreDelay, const int3 void CReverb::ProcessReflections(SWRvbRefDelay * MPT_RESTRICT pPreDelay, LR16 * MPT_RESTRICT pRefOut, int32 * MPT_RESTRICT pOut, uint32 nSamples) { -#if defined(MPT_ENABLE_ARCH_INTRINSICS_SSE2) +#if defined(MPT_WANT_ARCH_INTRINSICS_X86_SSE2) && defined(MPT_ARCH_INTRINSICS_X86_SSE2) if(CPU::HasFeatureSet(CPU::feature::sse2) && CPU::HasModesEnabled(CPU::mode::xmm128sse)) { union @@ -888,7 +888,7 @@ void CReverb::ProcessLateReverb(SWLateReverb * MPT_RESTRICT pReverb, LR16 * MPT_ // Calculate delay line offset from current delay position #define DELAY_OFFSET(x) ((delayPos - (x)) & RVBDLY_MASK) -#if defined(MPT_ENABLE_ARCH_INTRINSICS_SSE2) +#if defined(MPT_WANT_ARCH_INTRINSICS_X86_SSE2) && defined(MPT_ARCH_INTRINSICS_X86_SSE2) if(CPU::HasFeatureSet(CPU::feature::sse2) && CPU::HasModesEnabled(CPU::mode::xmm128sse)) { int delayPos = pReverb->nDelayPos & RVBDLY_MASK; @@ -920,7 +920,7 @@ void CReverb::ProcessLateReverb(SWLateReverb * MPT_RESTRICT pReverb, LR16 * MPT_ // Apply decay gain __m128i histDecay = _mm_srai_epi32(_mm_madd_epi16(Load64SSE(pReverb->nDecayDC), lpHistory), 15); __m128i histDecayPacked = _mm_shuffle_epi32(_mm_packs_epi32(histDecay, histDecay), _MM_SHUFFLE(2, 0, 2, 0)); - __m128i histDecayIn = _mm_adds_epi16(_mm_shuffle_epi32(_mm_packs_epi32(histDecay, histDecay), _MM_SHUFFLE(2, 0, 2, 0)), _mm_srai_epi16(_mm_unpacklo_epi32(refIn, refIn), 2)); + __m128i histDecayIn = _mm_adds_epi16(histDecayPacked, _mm_srai_epi16(_mm_unpacklo_epi32(refIn, refIn), 2)); __m128i histDecayInDiff = _mm_subs_epi16(histDecayIn, _mm_mulhi_epi16(_mm_cvtsi32_si128(diffusion1), difCoeffs)); pReverb->Diffusion1[delayPos].lr = _mm_cvtsi128_si32(histDecayInDiff); diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/AudioReadTarget.h b/Frameworks/OpenMPT/OpenMPT/soundlib/AudioReadTarget.h index 236520161..e4531955d 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/AudioReadTarget.h +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/AudioReadTarget.h @@ -11,21 +11,81 @@ #include "openmpt/all/BuildSettings.hpp" -#include "Sndfile.h" #include "mpt/audio/span.hpp" +#include "mpt/base/macros.hpp" +#include "mpt/string/types.hpp" #include "openmpt/soundbase/SampleFormat.hpp" #include "openmpt/soundbase/CopyMix.hpp" #include "openmpt/soundbase/Dither.hpp" +#include "openmpt/soundbase/DitherModPlug.hpp" +#include "openmpt/soundbase/DitherNone.hpp" +#include "openmpt/soundbase/DitherSimple.hpp" + #include "MixerLoops.h" #include "Mixer.h" -#include "../common/Dither.h" +#include "Sndfile.h" #include +#include + +#include OPENMPT_NAMESPACE_BEGIN +using Dither_Default = Dither_Simple; + + +class DitherNamesOpenMPT +{ +public: + static mpt::ustring GetModeName(std::size_t mode) + { + mpt::ustring result; + switch(mode) + { + case 0: + // no dither + result = MPT_USTRING("no"); + break; + case 1: + // chosen by OpenMPT code, might change + result = MPT_USTRING("default"); + break; + case 2: + // rectangular, 0.5 bit depth, no noise shaping (original ModPlug Tracker) + result = MPT_USTRING("0.5 bit"); + break; + case 3: + // rectangular, 1 bit depth, simple 1st order noise shaping + result = MPT_USTRING("1 bit"); + break; + default: + result = MPT_USTRING(""); + break; + } + return result; + } +}; + + +using DithersOpenMPT = + Dithers, MultiChannelDither, MultiChannelDither, MultiChannelDither>, DitherNamesOpenMPT, 4, 1, 0, mpt::good_prng>; + + +struct DithersWrapperOpenMPT + : DithersOpenMPT +{ + template + DithersWrapperOpenMPT(Trd &rd, std::size_t mode = DithersOpenMPT::DefaultDither, std::size_t channels = DithersOpenMPT::DefaultChannels) + : DithersOpenMPT(rd, mode, channels) + { + return; + } +}; + + template class AudioTargetBuffer : public IAudioTarget diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/BitReader.h b/Frameworks/OpenMPT/OpenMPT/soundlib/BitReader.h index 30feb8dab..8e1b3fdf7 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/BitReader.h +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/BitReader.h @@ -25,7 +25,8 @@ OPENMPT_NAMESPACE_BEGIN class BitReader : private FileReader { protected: - off_t m_bufPos = 0, m_bufSize = 0; + pos_type m_bufPos = 0; + pos_type m_bufSize = 0; uint32 bitBuf = 0; // Current bit buffer int m_bitNum = 0; // Currently available number of bits std::byte buffer[mpt::IO::BUFFERSIZE_TINY]{}; @@ -43,12 +44,12 @@ public: BitReader(const FileCursor &other) : FileReader(other) { } BitReader(FileCursor &&other) : FileReader(std::move(other)) { } - off_t GetLength() const + pos_type GetLength() const { return FileReader::GetLength(); } - off_t GetPosition() const + pos_type GetPosition() const { return FileReader::GetPosition() - m_bufSize + m_bufPos; } diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/ContainerPP20.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/ContainerPP20.cpp index 5dc59066f..35a110537 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/ContainerPP20.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/ContainerPP20.cpp @@ -181,7 +181,7 @@ bool UnpackPP20(std::vector &containerItems, FileReader &file, Co containerItems.back().data_cache = std::make_unique >(); std::vector & unpackedData = *(containerItems.back().data_cache); - FileReader::off_t length = file.GetLength(); + FileReader::pos_type length = file.GetLength(); if(!mpt::in_range(length)) return false; // Length word must be aligned if((length % 2u) != 0) diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Dlsbank.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/Dlsbank.cpp index f865cfdda..0a7098153 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/Dlsbank.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Dlsbank.cpp @@ -10,23 +10,23 @@ #include "stdafx.h" +#include "Dlsbank.h" #include "Sndfile.h" #ifdef MODPLUG_TRACKER +#include "../common/mptFileIO.h" #include "../mptrack/Mptrack.h" #include "mpt/io_file/inputfile.hpp" #include "mpt/io_file_read/inputfile_filecursor.hpp" -#include "../common/mptFileIO.h" #endif -#include "Dlsbank.h" #include "Loaders.h" #include "SampleCopy.h" -#include "../common/mptStringBuffer.h" -#include "../common/FileReader.h" -#include "openmpt/base/Endian.hpp" #include "SampleIO.h" +#include "../common/FileReader.h" +#include "../common/mptStringBuffer.h" #include "mpt/io/base.hpp" #include "mpt/io/io.hpp" #include "mpt/io/io_stdstream.hpp" +#include "openmpt/base/Endian.hpp" OPENMPT_NAMESPACE_BEGIN @@ -762,7 +762,15 @@ bool CDLSBank::FindAndExtract(CSoundFile &sndFile, const INSTRUMENTINDEX ins, co pIns = sndFile.Instruments[ins]; // Reset pointer because ExtractInstrument may delete the previous value. if((key >= 24) && (key < 24 + std::size(szMidiPercussionNames))) { +#if MPT_COMPILER_MSVC +#pragma warning(push) +// false-positive +#pragma warning(disable:6385) // Reading invalid data from 'szMidiPercussionNames'. +#endif pIns->name = szMidiPercussionNames[key - 24]; +#if MPT_COMPILER_MSVC +#pragma warning(pop) +#endif } return true; } @@ -2263,7 +2271,7 @@ bool CDLSBank::ExtractInstrument(CSoundFile &sndFile, INSTRUMENTINDEX nInstr, ui float tempoScale = 1.0f; if(sndFile.m_nTempoMode == TempoMode::Modern) { - uint32 ticksPerBeat = sndFile.m_nDefaultRowsPerBeat * sndFile.m_nDefaultSpeed; + uint32 ticksPerBeat = sndFile.m_nDefaultRowsPerBeat * sndFile.Order().GetDefaultSpeed(); if(ticksPerBeat != 0) tempoScale = ticksPerBeat / 24.0f; } diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Dlsbank.h b/Frameworks/OpenMPT/OpenMPT/soundlib/Dlsbank.h index 9dbaca583..f0798f805 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/Dlsbank.h +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Dlsbank.h @@ -12,16 +12,14 @@ #pragma once #include "openmpt/all/BuildSettings.hpp" - -OPENMPT_NAMESPACE_BEGIN -class CSoundFile; -OPENMPT_NAMESPACE_END #include "Snd_defs.h" OPENMPT_NAMESPACE_BEGIN #ifdef MODPLUG_TRACKER +class CSoundFile; +struct InstrumentEnvelope; struct DLSREGION { diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Fastmix.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/Fastmix.cpp index 09de6eadf..bfce6adc7 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/Fastmix.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Fastmix.cpp @@ -305,8 +305,6 @@ struct MixLoopState // Render count * number of channels samples void CSoundFile::CreateStereoMix(int count) { - mixsample_t *pOfsL, *pOfsR; - if(!count) return; @@ -315,17 +313,24 @@ void CSoundFile::CreateStereoMix(int count) if(m_MixerSettings.gnChannels > 2) StereoFill(MixRearBuffer, count, m_surroundROfsVol, m_surroundLOfsVol); - CHANNELINDEX nchmixed = 0; + // Channels that are actually mixed and not skipped (because they are paused or muted) + CHANNELINDEX numChannelsMixed = 0; for(uint32 nChn = 0; nChn < m_nMixChannels; nChn++) { - ModChannel &chn = m_PlayState.Chn[m_PlayState.ChnMix[nChn]]; + if(MixChannel(count, m_PlayState.Chn[m_PlayState.ChnMix[nChn]], m_PlayState.ChnMix[nChn], numChannelsMixed < m_MixerSettings.m_nMaxMixChannels)) + numChannelsMixed++; + } + m_nMixStat = std::max(m_nMixStat, numChannelsMixed); +} - if(!chn.pCurrentSample && !chn.nLOfs && !chn.nROfs) - continue; - pOfsR = &m_dryROfsVol; - pOfsL = &m_dryLOfsVol; +bool CSoundFile::MixChannel(int count, ModChannel &chn, CHANNELINDEX channel, bool doMix) +{ + if(chn.pCurrentSample || chn.nLOfs || chn.nROfs) + { + mixsample_t *pOfsR = &m_dryROfsVol; + mixsample_t *pOfsL = &m_dryLOfsVol; uint32 functionNdx = MixFuncTable::ResamplingModeToMixFlags(static_cast(chn.resamplingMode)); if(chn.dwFlags[CHN_16BIT]) functionNdx |= MixFuncTable::ndx16Bit; @@ -351,14 +356,13 @@ void CSoundFile::CreateStereoMix(int count) pOfsL = &m_surroundLOfsVol; } - //Look for plugins associated with this implicit tracker channel. + // Look for plugins associated with this implicit tracker channel. #ifndef NO_PLUGINS - PLUGINDEX nMixPlugin = GetBestPlugin(m_PlayState, m_PlayState.ChnMix[nChn], PrioritiseInstrument, RespectMutes); - - if ((nMixPlugin > 0) && (nMixPlugin <= MAX_MIXPLUGINS) && m_MixPlugins[nMixPlugin - 1].pMixPlugin != nullptr) + const PLUGINDEX mixPlugin = GetBestPlugin(chn, channel, PrioritiseInstrument, RespectMutes); + if((mixPlugin > 0) && (mixPlugin <= MAX_MIXPLUGINS) && m_MixPlugins[mixPlugin - 1].pMixPlugin != nullptr) { // Render into plugin buffer instead of global buffer - SNDMIXPLUGINSTATE &mixState = m_MixPlugins[nMixPlugin - 1].pMixPlugin->m_MixState; + SNDMIXPLUGINSTATE &mixState = m_MixPlugins[mixPlugin - 1].pMixPlugin->m_MixState; if (mixState.pMixBuffer) { pbuffer = mixState.pMixBuffer; @@ -379,13 +383,13 @@ void CSoundFile::CreateStereoMix(int count) *pOfsR += chn.nROfs; *pOfsL += chn.nLOfs; chn.nROfs = chn.nLOfs = 0; - continue; + return false; } MixLoopState mixLoopState(*this, chn); //////////////////////////////////////////////////// - CHANNELINDEX naddmix = 0; + bool addToMix = false; int nsamples = count; // Keep mixing this sample until the buffer is filled. do @@ -412,14 +416,14 @@ void CSoundFile::CreateStereoMix(int count) break; } - // Should we mix this channel ? - if((nchmixed >= m_MixerSettings.m_nMaxMixChannels) // Too many channels - || (!chn.nRampLength && !(chn.leftVol | chn.rightVol))) // Channel is completely silent + // Should we mix this channel? + if(!doMix // Too many channels + || (!chn.nRampLength && !(chn.leftVol | chn.rightVol))) // Channel is completely silent { chn.position += chn.increment * nSmpCount; chn.nROfs = chn.nLOfs = 0; pbuffer += nSmpCount * 2; - naddmix = 0; + addToMix = false; } #ifdef MODPLUG_TRACKER else if(m_SamplePlayLengths != nullptr) @@ -456,7 +460,7 @@ void CSoundFile::CreateStereoMix(int count) chn.nROfs += *(pbufmax - 2); chn.nLOfs += *(pbufmax - 1); pbuffer = pbufmax; - naddmix = 1; + addToMix = true; } nsamples -= nSmpCount; @@ -482,7 +486,7 @@ void CSoundFile::CreateStereoMix(int count) const bool pastLoopEnd = chn.position.GetUInt() >= chn.nLoopEnd && chn.dwFlags[CHN_LOOP]; const bool pastSampleEnd = chn.position.GetUInt() >= chn.nLength && !chn.dwFlags[CHN_LOOP] && chn.nLength && !chn.nMasterChn; - const bool doSampleSwap = m_playBehaviour[kMODSampleSwap] && chn.nNewIns && chn.nNewIns <= GetNumSamples() && chn.pModSample != &Samples[chn.nNewIns]; + const bool doSampleSwap = m_playBehaviour[kMODSampleSwap] && chn.swapSampleIndex && chn.swapSampleIndex <= GetNumSamples() && chn.pModSample != &Samples[chn.swapSampleIndex]; if((pastLoopEnd || pastSampleEnd) && doSampleSwap) { // ProTracker compatibility: Instrument changes without a note do not happen instantly, but rather when the sample loop has finished playing. @@ -498,19 +502,23 @@ void CSoundFile::CreateStereoMix(int count) } } #endif - const ModSample &smp = Samples[chn.nNewIns]; + const ModSample &smp = Samples[chn.swapSampleIndex]; chn.pModSample = &smp; chn.pCurrentSample = smp.samplev(); chn.dwFlags = (chn.dwFlags & CHN_CHANNELFLAGS) | smp.uFlags; - chn.nLength = smp.uFlags[CHN_LOOP] ? smp.nLoopEnd : 0; // non-looping sample continue in oneshot mode (i.e. they will most probably just play silence) + if(smp.uFlags[CHN_LOOP]) + chn.nLength = smp.nLoopEnd; + else if(!m_playBehaviour[kMODOneShotLoops]) + chn.nLength = smp.nLength; + else + chn.nLength = 0; // non-looping sample continue in oneshot mode (i.e. they will most probably just play silence) chn.nLoopStart = smp.nLoopStart; chn.nLoopEnd = smp.nLoopEnd; chn.position.SetInt(chn.nLoopStart); + chn.swapSampleIndex = 0; mixLoopState.UpdateLookaheadPointers(chn); if(!chn.pCurrentSample) - { break; - } } else if(pastLoopEnd && !doSampleSwap && m_playBehaviour[kMODOneShotLoops] && chn.nLoopStart == 0) { // ProTracker "oneshot" loops (if loop start is 0, play the whole sample once and then repeat until loop end) @@ -521,16 +529,16 @@ void CSoundFile::CreateStereoMix(int count) // Restore sample pointer in case it got changed through loop wrap-around chn.pCurrentSample = mixLoopState.samplePointer; - nchmixed += naddmix; #ifndef NO_PLUGINS - if(naddmix && nMixPlugin > 0 && nMixPlugin <= MAX_MIXPLUGINS && m_MixPlugins[nMixPlugin - 1].pMixPlugin) + if(addToMix && mixPlugin > 0 && mixPlugin <= MAX_MIXPLUGINS && m_MixPlugins[mixPlugin - 1].pMixPlugin) { - m_MixPlugins[nMixPlugin - 1].pMixPlugin->ResetSilence(); + m_MixPlugins[mixPlugin - 1].pMixPlugin->ResetSilence(); } #endif // NO_PLUGINS + return addToMix; } - m_nMixStat = std::max(m_nMixStat, nchmixed); + return false; } diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/ITCompression.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/ITCompression.cpp index 28ce870d9..6a1dd17dc 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/ITCompression.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/ITCompression.cpp @@ -10,14 +10,15 @@ #include "stdafx.h" -#include #include "ITCompression.h" +#include "ModSample.h" +#include "SampleCopy.h" +#include "../common/misc_util.h" #include "mpt/io/base.hpp" #include "mpt/io/io.hpp" #include "mpt/io/io_stdstream.hpp" -#include "../common/misc_util.h" -#include "ModSample.h" -#include "SampleCopy.h" + +#include OPENMPT_NAMESPACE_BEGIN diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/ITCompression.h b/Frameworks/OpenMPT/OpenMPT/soundlib/ITCompression.h index d0670b335..11357731e 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/ITCompression.h +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/ITCompression.h @@ -35,8 +35,6 @@ protected: std::vector bwt; // Bit width table for each sampling point std::vector packedData; // Compressed data for current sample block std::ostream *file = nullptr; // File to which compressed data will be written (can be nullptr if you only want to find out the sample size) - std::vector sampleData8; // Pre-processed sample data for currently compressed sample block - std::vector sampleData16; // Pre-processed sample data for currently compressed sample block const ModSample &mptSample; // Sample that is being processed size_t packedLength = 0; // Size of currently compressed sample block size_t packedTotalLength = 0; // Size of all compressed data so far diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/ITTools.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/ITTools.cpp index 527d53c19..0200cbf08 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/ITTools.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/ITTools.cpp @@ -9,8 +9,8 @@ #include "stdafx.h" -#include "Loaders.h" #include "ITTools.h" +#include "Loaders.h" #include "Tables.h" #include "../common/mptStringBuffer.h" #include "../common/version.h" @@ -42,14 +42,14 @@ void ITEnvelope::ConvertToIT(const InstrumentEnvelope &mptEnv, uint8 envOffset, // Attention: Full MPTM envelope is stored in extended instrument properties for(uint32 ev = 0; ev < num; ev++) { - data[ev].value = static_cast(mptEnv[ev].value) - envOffset; + data[ev].value = static_cast(static_cast(mptEnv[ev].value) - envOffset); data[ev].tick = mptEnv[ev].tick; } } else { // Fix non-existing envelopes so that they can still be edited in Impulse Tracker. num = 2; - data[0].value = data[1].value = envDefault - envOffset; + data[0].value = data[1].value = static_cast(envDefault - envOffset); data[1].tick = 10; } } @@ -75,7 +75,7 @@ void ITEnvelope::ConvertToMPT(InstrumentEnvelope &mptEnv, uint8 envOffset, uint8 // Attention: Full MPTM envelope is stored in extended instrument properties for(uint32 ev = 0; ev < std::min(uint8(25), num); ev++) { - mptEnv[ev].value = Clamp(data[ev].value + envOffset, 0, 64); + mptEnv[ev].value = Clamp(static_cast(data[ev].value + envOffset), 0, 64); mptEnv[ev].tick = data[ev].tick; if(ev > 0 && mptEnv[ev].tick < mptEnv[ev - 1].tick && !(mptEnv[ev].tick & 0xFF00)) { @@ -83,7 +83,7 @@ void ITEnvelope::ConvertToMPT(InstrumentEnvelope &mptEnv, uint8 envOffset, uint8 // NoGap.it was saved with MPT 1.07 - 1.09, which *normally* doesn't do this in IT files. // However... It turns out that MPT 1.07 omitted the high byte of envelope nodes when saving an XI instrument file, and it looks like // Instrument 2 and 3 in NoGap.it were loaded from XI files. - mptEnv[ev].tick |= mptEnv[ev - 1].tick & 0xFF00; + mptEnv[ev].tick |= static_cast(mptEnv[ev - 1].tick & 0xFF00u); if(mptEnv[ev].tick < mptEnv[ev - 1].tick) mptEnv[ev].tick += 0x100; } @@ -195,7 +195,7 @@ uint32 ITInstrument::ConvertToIT(const ModInstrument &mptIns, bool compatExport, // MIDI Setup if(mptIns.nMidiProgram > 0) - mpr = mptIns.nMidiProgram - 1u; + mpr = static_cast(mptIns.nMidiProgram - 1u); else mpr = 0xFF; if(mptIns.wMidiBank > 0) @@ -316,7 +316,7 @@ uint32 ITInstrument::ConvertToMPT(ModInstrument &mptIns, MODTYPE modFormat) cons if(mbank[0] < 128) bank = mbank[0] + 1; if(mbank[1] < 128) - bank += (mbank[1] << 7); + bank += static_cast(mbank[1] << 7); mptIns.wMidiBank = bank; } mptIns.nMidiChannel = mch; diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/InstrumentExtensions.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/InstrumentExtensions.cpp index b9c83f36f..b562e46c1 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/InstrumentExtensions.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/InstrumentExtensions.cpp @@ -4,7 +4,7 @@ * Purpose: Instrument properties I/O * Notes : Welcome to the absolutely horrible abominations that are the "extended instrument properties" * which are some of the earliest additions OpenMPT did to the IT / XM format. They are ugly, - * and the way they work even differs between IT/XM and ITI/XI/ITP. + * and the way they work even differs between IT/XM/ITI/XI and ITI/XI/ITP. * Yes, the world would be a better place without this stuff. * Authors: OpenMPT Devs * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. @@ -18,6 +18,8 @@ #include "mpt/io/base.hpp" #include "mpt/io/io.hpp" #include "mpt/io/io_stdstream.hpp" + +#include #endif OPENMPT_NAMESPACE_BEGIN @@ -30,10 +32,9 @@ MODULAR (in/out) ModInstrument : * to update: ------------ -- both following functions need to be updated when adding a new member in ModInstrument : - -void WriteInstrumentHeaderStructOrField(ModInstrument * input, std::ostream &file, uint32 only_this_code, int16 fixedsize); -bool ReadInstrumentHeaderField(ModInstrument * input, uint32 fcode, int16 fsize, FileReader &file); +- both following functions need to be updated when adding a new member in ModInstrument: + - SaveExtendedInstrumentProperties + - ReadInstrumentHeaderField - see below for body declaration. @@ -51,13 +52,13 @@ bool ReadInstrumentHeaderField(ModInstrument * input, uint32 fcode, int16 fsize, - have a look below in current tag dictionnary - take the initial ones of the field name -- 4 caracters code (not more, not less) -- must be filled with '.' caracters if code has less than 4 caracters -- for arrays, must include a '[' caracter following significant caracters ('.' not significant!!!) -- use only caracters used in full member name, ordered as they appear in it -- match caracter attribute (small,capital) +- 4 characters code (not more, not less) +- must be filled with '.' characters if code has less than 4 characters +- for arrays, must include a '[' character following significant characters ('.' not significant!!!) +- use only characters used in full member name, ordered as they appear in it +- match character attribute (small, capital) -Example with "PanEnv.nLoopEnd" , "PitchEnv.nLoopEnd" & "VolEnv.Values[MAX_ENVPOINTS]" members : +Example with "PanEnv.nLoopEnd" , "PitchEnv.nLoopEnd" & "VolEnv.Values[MAX_ENVPOINTS]" members: - use 'PLE.' for PanEnv.nLoopEnd - use 'PiLE' for PitchEnv.nLoopEnd - use 'VE[.' for VolEnv.Values[MAX_ENVPOINTS] @@ -66,418 +67,324 @@ Example with "PanEnv.nLoopEnd" , "PitchEnv.nLoopEnd" & "VolEnv.Values[MAX_ENVPOI * In use CODE tag dictionary (alphabetical order): -------------------------------------------------- - !!!!!!!!!!!!!!!!!!!!!!!!!!!!! - !!! SECTION TO BE UPDATED !!! - !!!!!!!!!!!!!!!!!!!!!!!!!!!!! +AERN RW PanEnv.nReleaseNode +AFLG R PanEnv.dwFlags +CS.. RW nCutSwing +DCT. R nDCT +dF.. R dwFlags +DNA. R nDNA +FM.. RW filterMode +fn[. R filename[12] +FO.. RW nFadeOut +GV.. R nGlobalVol +IFC. R nIFC +IFR. R nIFR +K[.. Keyboard[128] +MB.. RW wMidiBank +MC.. RW nMidiChannel +MiP. RW nMixPlug +MP.. RW nMidiProgram +MPWD RW MIDI Pitch Wheel Depth +n[.. R name[32] +NM[. R NoteMap[128] +NNA. R nNNA +P... RW nPan +PE.. RW PanEnv.nNodes +PE[. RW PanEnv.Values[MAX_ENVPOINTS] +PERN RW PitchEnv.nReleaseNode +PFLG R PitchEnv.dwFlags +PiE. RW PitchEnv.nNodes +PiE[ RW PitchEnv.Values[MAX_ENVPOINTS] +PiLE R PitchEnv.nLoopEnd +PiLS R PitchEnv.nLoopStart +PiP[ RW PitchEnv.Ticks[MAX_ENVPOINTS] +PiSB R PitchEnv.nSustainStart +PiSE R PitchEnv.nSustainEnd +PLE. R PanEnv.nLoopEnd +PLS. R PanEnv.nLoopStart +PP[. RW PanEnv.Ticks[MAX_ENVPOINTS] +PPC. R nPPC +PPS. R nPPS +PS.. R nPanSwing +PSB. R PanEnv.nSustainStart +PSE. R PanEnv.nSustainEnd +PTTF RW pitchToTempoLock (fractional part) +PTTL RW pitchToTempoLock (integer part) +PVEH RW pluginVelocityHandling +PVOH RW pluginVolumeHandling +R... RW Resampling +RS.. RW nResSwing +VE.. RW VolEnv.nNodes +VE[. RW VolEnv.Values[MAX_ENVPOINTS] +VERN RW VolEnv.nReleaseNode +VFLG R VolEnv.dwFlags +VLE. R VolEnv.nLoopEnd +VLS. R VolEnv.nLoopStart +VP[. RW VolEnv.Ticks[MAX_ENVPOINTS] +VR.. RW nVolRampUp +VS.. R nVolSwing +VSB. R VolEnv.nSustainStart +VSE. R VolEnv.nSustainEnd + +Note that many of these extensions were only relevant for ITP files, and thus there is no code for writing them, only reading. +Some of them used to be written but were never read ("K[.." sample map - it was only relevant for ITP files, but even there +it was always ignored, because sample indices may change when loading external instruments). - [EXT] means external (not related) to ModInstrument content -AUTH [EXT] Song artist -C... [EXT] nChannels -ChnS [EXT] IT/MPTM: Channel settings for channels 65-127 if needed (doesn't fit to IT header). -CS.. nCutSwing -CUES [EXT] Sample cue points -CWV. [EXT] dwCreatedWithVersion -DCT. nDCT; -dF.. dwFlags; -DGV. [EXT] nDefaultGlobalVolume -DT.. [EXT] nDefaultTempo; -DTFR [EXT] Fractional part of default tempo -DNA. nDNA; -EBIH [EXT] embeded instrument header tag (ITP file format) -FM.. filterMode; -fn[. filename[12]; -FO.. nFadeOut; -GV.. nGlobalVol; -IFC. nIFC; -IFR. nIFR; -K[. Keyboard[128]; -LSWV [EXT] Last Saved With Version -MB.. wMidiBank; -MC.. nMidiChannel; -MDK. nMidiDrumKey; -MIMA [EXT] MIdi MApping directives -MiP. nMixPlug; -MP.. nMidiProgram; -MPTS [EXT] Extra song info tag -MPTX [EXT] EXTRA INFO tag -MSF. [EXT] Mod(Specific)Flags -n[.. name[32]; -NNA. nNNA; -NM[. NoteMap[128]; -P... nPan; -PE.. PanEnv.nNodes; -PE[. PanEnv.Values[MAX_ENVPOINTS]; -PiE. PitchEnv.nNodes; -PiE[ PitchEnv.Values[MAX_ENVPOINTS]; -PiLE PitchEnv.nLoopEnd; -PiLS PitchEnv.nLoopStart; -PiP[ PitchEnv.Ticks[MAX_ENVPOINTS]; -PiSB PitchEnv.nSustainStart; -PiSE PitchEnv.nSustainEnd; -PLE. PanEnv.nLoopEnd; -PLS. PanEnv.nLoopStart; -PMM. [EXT] nPlugMixMode; -PP[. PanEnv.Ticks[MAX_ENVPOINTS]; -PPC. nPPC; -PPS. nPPS; -PS.. nPanSwing; -PSB. PanEnv.nSustainStart; -PSE. PanEnv.nSustainEnd; -PTTL pitchToTempoLock; -PTTF pitchToTempoLock (fractional part); -PVEH pluginVelocityHandling; -PVOH pluginVolumeHandling; -R... resampling; -RP.. [EXT] nRestartPos; -RPB. [EXT] nRowsPerBeat; -RPM. [EXT] nRowsPerMeasure; -RS.. nResSwing; -RSMP [EXT] Global resampling -SEP@ [EXT] chunk SEPARATOR tag -SPA. [EXT] m_nSamplePreAmp; -TM.. [EXT] nTempoMode; -VE.. VolEnv.nNodes; -VE[. VolEnv.Values[MAX_ENVPOINTS]; -VLE. VolEnv.nLoopEnd; -VLS. VolEnv.nLoopStart; -VP[. VolEnv.Ticks[MAX_ENVPOINTS]; -VR.. nVolRampUp; -VS.. nVolSwing; -VSB. VolEnv.nSustainStart; -VSE. VolEnv.nSustainEnd; -VSTV [EXT] nVSTiVolume; -PERN PitchEnv.nReleaseNode -AERN PanEnv.nReleaseNode -VERN VolEnv.nReleaseNode -PFLG PitchEnv.dwFlag -AFLG PanEnv.dwFlags -VFLG VolEnv.dwFlags -MPWD MIDI Pitch Wheel Depth ----------------------------------------------------------------------------------------------- ---------------------------------------------------------------------------------------------*/ #ifndef MODPLUG_NO_FILESAVE -template struct IsNegativeFunctor { bool operator()(T val) const { return val < 0; } }; -template struct IsNegativeFunctor { bool operator()(T val) const { return val < 0; } }; -template struct IsNegativeFunctor { bool operator()(T /*val*/) const { return false; } }; -template -bool IsNegative(const T &val) +// We want constexpr ModInstrument{} due to bad code generation with temporary objects mostly in MSVC, +// however most stdlib implementations of C++20 fail to provide constexpr std::vector in C++20 mode, +// which is required for the envelopes. Thus we only activate that for C++23. +// For libopenmpt, this code path is only required for test suite, +// and inefficient code generation does not really matter. +#if MPT_CXX_AT_LEAST(23) || (MPT_CXX_AT_LEAST(20) && !defined(MPT_LIBCXX_QUIRK_NO_CXX20_CONSTEXPR_CONTAINER)) +#define MODINSTRUMENT_DEFAULT MPT_FORCE_CONSTEXPR_VALUE(ModInstrument{}) +#elif defined(LIBOPENMPT_BUILD) +#define MODINSTRUMENT_DEFAULT ModInstrument{} +#else +#if MPT_COMPILER_CLANG +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wglobal-constructors" +#pragma clang diagnostic ignored "-Wexit-time-destructors" +#endif // MPT_COMPILER_CLANG +static MPT_CONSTEXPR20_CONTAINER_VAR ModInstrument ModInstrumentDefault; +#if MPT_COMPILER_CLANG +#pragma clang diagnostic pop +#endif // MPT_COMPILER_CLANG +#define MODINSTRUMENT_DEFAULT ModInstrumentDefault +#endif + + +template +inline bool IsPropertyNonDefault(const ModInstrument &ins) { return MODINSTRUMENT_DEFAULT.*Member != ins.*Member; } + +template +constexpr uint16 PropertySize() noexcept { return sizeof(ModInstrument{}.*Member); } + +template +struct PropertyWriterBase { - return IsNegativeFunctor::is_signed>()(val); -} + PropertyNeededFunc IsPropertyNeeded; + static constexpr auto Size = PropertySizeFunc; -// ------------------------------------------------------------------------------------------ -// Convenient macro to help WRITE_HEADER declaration for single type members ONLY (non-array) -// ------------------------------------------------------------------------------------------ -#define WRITE_MPTHEADER_sized_member(name,type,code) \ - static_assert(sizeof(input->name) == sizeof(type), "Instrument property does match specified type!");\ - fcode = code;\ - fsize = sizeof( type );\ - if(writeAll) \ - { \ - mpt::IO::WriteIntLE(file, fcode); \ - mpt::IO::WriteIntLE(file, fsize); \ - } else if(only_this_code == fcode)\ - { \ - MPT_ASSERT(fixedsize == fsize); \ - } \ - if(only_this_code == fcode || only_this_code == Util::MaxValueOfType(only_this_code)) \ - { \ - type tmp = (type)(input-> name ); \ - mpt::IO::WriteIntLE(file, tmp); \ - } \ -/**/ + PropertyWriterBase(PropertyNeededFunc propertyNeededFunc = IsPropertyNonDefault) + : IsPropertyNeeded{std::move(propertyNeededFunc)} + { } +}; -// ----------------------------------------------------------------------------------------------------- -// Convenient macro to help WRITE_HEADER declaration for single type members which are written truncated -// ----------------------------------------------------------------------------------------------------- -#define WRITE_MPTHEADER_trunc_member(name,type,code) \ - static_assert(sizeof(input->name) > sizeof(type), "Instrument property would not be truncated, use WRITE_MPTHEADER_sized_member instead!");\ - fcode = code;\ - fsize = sizeof( type );\ - if(writeAll) \ - { \ - mpt::IO::WriteIntLE(file, fcode); \ - mpt::IO::WriteIntLE(file, fsize); \ - type tmp = (type)(input-> name ); \ - mpt::IO::WriteIntLE(file, tmp); \ - } else if(only_this_code == fcode)\ - { \ - /* hackish workaround to resolve mismatched size values: */ \ - /* nResampling was a long time declared as uint32 but these macro tables used uint16 and UINT. */ \ - /* This worked fine on little-endian, on big-endian not so much. Thus support writing size-mismatched fields. */ \ - MPT_ASSERT(fixedsize >= fsize); \ - type tmp = (type)(input-> name ); \ - mpt::IO::WriteIntLE(file, tmp); \ - if(fixedsize > fsize) \ - { \ - for(int16 i = 0; i < fixedsize - fsize; ++i) \ - { \ - uint8 fillbyte = !IsNegative(tmp) ? 0 : 0xff; /* sign extend */ \ - mpt::IO::WriteIntLE(file, fillbyte); \ - } \ - } \ - } \ -/**/ - -// ------------------------------------------------------------------------ -// Convenient macro to help WRITE_HEADER declaration for array members ONLY -// ------------------------------------------------------------------------ -#define WRITE_MPTHEADER_array_member(name,type,code,arraysize) \ - static_assert(sizeof(type) == sizeof(input-> name [0])); \ - MPT_ASSERT(sizeof(input->name) >= sizeof(type) * arraysize);\ - fcode = code;\ - fsize = sizeof( type ) * arraysize;\ - if(writeAll) \ - { \ - mpt::IO::WriteIntLE(file, fcode); \ - mpt::IO::WriteIntLE(file, fsize); \ - } else if(only_this_code == fcode)\ - { \ - /* MPT_ASSERT(fixedsize <= fsize); */ \ - fsize = fixedsize; /* just trust the size we got passed */ \ - } \ - if(only_this_code == fcode || only_this_code == Util::MaxValueOfType(only_this_code)) \ - { \ - for(std::size_t i = 0; i < fsize/sizeof(type); ++i) \ - { \ - type tmp; \ - tmp = input-> name [i]; \ - mpt::IO::WriteIntLE(file, tmp); \ - } \ - } \ -/**/ - -// ------------------------------------------------------------------------ -// Convenient macro to help WRITE_HEADER declaration for envelope members ONLY -// ------------------------------------------------------------------------ -#define WRITE_MPTHEADER_envelope_member(envType,envField,type,code) \ - {\ - const InstrumentEnvelope &env = input->GetEnvelope(envType); \ - static_assert(sizeof(type) == sizeof(env[0]. envField)); \ - fcode = code;\ - fsize = mpt::saturate_cast(sizeof( type ) * env.size());\ - MPT_ASSERT(size_t(fsize) == sizeof( type ) * env.size()); \ - \ - if(writeAll) \ - { \ - mpt::IO::WriteIntLE(file, fcode); \ - mpt::IO::WriteIntLE(file, fsize); \ - } else if(only_this_code == fcode)\ - { \ - fsize = fixedsize; /* just trust the size we got passed */ \ - } \ - if(only_this_code == fcode || only_this_code == Util::MaxValueOfType(only_this_code)) \ - { \ - uint32 maxNodes = std::min(static_cast(fsize/sizeof(type)), static_cast(env.size())); \ - for(uint32 i = 0; i < maxNodes; ++i) \ - { \ - type tmp; \ - tmp = env[i]. envField ; \ - mpt::IO::WriteIntLE(file, tmp); \ - } \ - /* Not every instrument's envelope will be the same length. fill up with zeros. */ \ - for(uint32 i = maxNodes; i < fsize/sizeof(type); ++i) \ - { \ - type tmp = 0; \ - mpt::IO::WriteIntLE(file, tmp); \ - } \ - } \ - }\ -/**/ - - -// Write (in 'file') 'input' ModInstrument with 'code' & 'size' extra field infos for each member -void WriteInstrumentHeaderStructOrField(ModInstrument * input, std::ostream &file, uint32 only_this_code, uint16 fixedsize) +template ), auto PropertySizeFunc = PropertySize> +struct PropertyWriterInt : PropertyWriterBase { - uint32 fcode; - uint16 fsize; - // If true, all extension are written to the file; otherwise only the specified extension is written. - // writeAll is true iff we are saving an instrument (or, hypothetically, the legacy ITP format) - const bool writeAll = only_this_code == Util::MaxValueOfType(only_this_code); + using PropertyWriterBase::PropertyWriterBase; + static void Write(std::ostream &file, const ModInstrument &ins) { mpt::IO::WriteIntLE(file, ins.*Member); } +}; - if(!writeAll) +template ), auto PropertySizeFunc = PropertySize> +struct PropertyWriterEnum : PropertyWriterBase +{ + using PropertyWriterBase::PropertyWriterBase; + static void Write(std::ostream &file, const ModInstrument &ins) { - MPT_ASSERT(fixedsize > 0); + const auto value = ins.*Member; + static_assert(std::is_enum_v); + mpt::IO::WriteIntLE(file, mpt::to_underlying(value)); + } +}; + +struct PropertyWriterReleaseNode +{ + bool IsPropertyNeeded(const ModInstrument &ins) const noexcept { return MODINSTRUMENT_DEFAULT.GetEnvelope(type).nReleaseNode != ins.GetEnvelope(type).nReleaseNode; } + static constexpr uint16 Size() noexcept { return sizeof(InstrumentEnvelope{}.nReleaseNode); } + void Write(std::ostream &file, const ModInstrument &ins) const { mpt::IO::WriteIntLE(file, ins.GetEnvelope(type).nReleaseNode); } + const EnvelopeType type; +}; + +struct PropertyWriterEnvelopeBase +{ + PropertyWriterEnvelopeBase(uint32 nodes, EnvelopeType type) : nodes{nodes}, type{type} {} + static bool IsPropertyNeeded(const ModInstrument &) noexcept + { + return true; + } + const uint32 nodes; + const EnvelopeType type; +}; + +struct PropertyWriterEnvelopeSize : PropertyWriterEnvelopeBase +{ + using PropertyWriterEnvelopeBase::PropertyWriterEnvelopeBase; + static constexpr uint16 Size() noexcept { return sizeof(uint32le); } + void Write(std::ostream &file, const ModInstrument &ins) const { mpt::IO::WriteIntLE(file, ins.GetEnvelope(type).size()); } +}; + +struct PropertyWriterEnvelopeTicks : PropertyWriterEnvelopeBase +{ + using PropertyWriterEnvelopeBase::PropertyWriterEnvelopeBase; + uint16 Size() const noexcept { return static_cast(sizeof(uint16le) * nodes); } + void Write(std::ostream &file, const ModInstrument &ins) const + { + const auto &env = ins.GetEnvelope(type); + const uint32 maxNodes = std::min(nodes, static_cast(env.size())); + for(uint32 i = 0; i < maxNodes; ++i) + { + mpt::IO::WriteIntLE(file, static_cast(env[i].tick)); + } + // Not every instrument's envelope will be the same length. fill up with zeros. + uint16le padding{}; + for(uint32 i = maxNodes; i < nodes; ++i) + { + mpt::IO::Write(file, padding); + } + } +}; + +struct PropertyWriterEnvelopeValues : PropertyWriterEnvelopeBase +{ + using PropertyWriterEnvelopeBase::PropertyWriterEnvelopeBase; + uint16 Size() const noexcept { return static_cast(sizeof(uint8) * nodes); } + void Write(std::ostream &file, const ModInstrument &ins) const + { + const auto &env = ins.GetEnvelope(type); + const uint32 maxNodes = std::min(nodes, static_cast(env.size())); + for(uint32 i = 0; i < maxNodes; ++i) + { + mpt::IO::WriteIntLE(file, static_cast(env[i].value)); + } + // Not every instrument's envelope will be the same length. fill up with zeros. + uint8 padding{}; + for(uint32 i = maxNodes; i < nodes; ++i) + { + mpt::IO::Write(file, padding); + } + } +}; + +struct PropertyWriterPitchTempoLock +{ + static constexpr auto IsPropertyNeeded = IsPropertyNonDefault<&ModInstrument::pitchToTempoLock>; + static constexpr uint16 Size() noexcept { return sizeof(uint16le); } + PropertyWriterPitchTempoLock(bool intPart) : m_intPart{intPart} {} + void Write(std::ostream &file, const ModInstrument &ins) + { + mpt::IO::WriteIntLE(file, static_cast(m_intPart ? ins.pitchToTempoLock.GetInt() : ins.pitchToTempoLock.GetFract())); } - // clang-format off - WRITE_MPTHEADER_sized_member( nFadeOut , uint32 , MagicBE("FO..") ) - WRITE_MPTHEADER_sized_member( nPan , uint32 , MagicBE("P...") ) - WRITE_MPTHEADER_sized_member( VolEnv.size() , uint32 , MagicBE("VE..") ) - WRITE_MPTHEADER_sized_member( PanEnv.size() , uint32 , MagicBE("PE..") ) - WRITE_MPTHEADER_sized_member( PitchEnv.size() , uint32 , MagicBE("PiE.") ) - WRITE_MPTHEADER_sized_member( wMidiBank , uint16 , MagicBE("MB..") ) - WRITE_MPTHEADER_sized_member( nMidiProgram , uint8 , MagicBE("MP..") ) - WRITE_MPTHEADER_sized_member( nMidiChannel , uint8 , MagicBE("MC..") ) - WRITE_MPTHEADER_envelope_member( ENV_VOLUME , tick , uint16 , MagicBE("VP[.") ) - WRITE_MPTHEADER_envelope_member( ENV_PANNING , tick , uint16 , MagicBE("PP[.") ) - WRITE_MPTHEADER_envelope_member( ENV_PITCH , tick , uint16 , MagicBE("PiP[") ) - WRITE_MPTHEADER_envelope_member( ENV_VOLUME , value , uint8 , MagicBE("VE[.") ) - WRITE_MPTHEADER_envelope_member( ENV_PANNING , value , uint8 , MagicBE("PE[.") ) - WRITE_MPTHEADER_envelope_member( ENV_PITCH , value , uint8 , MagicBE("PiE[") ) - WRITE_MPTHEADER_sized_member( nMixPlug , uint8 , MagicBE("MiP.") ) - WRITE_MPTHEADER_sized_member( nVolRampUp , uint16 , MagicBE("VR..") ) - WRITE_MPTHEADER_sized_member( resampling , uint8 , MagicBE("R...") ) - WRITE_MPTHEADER_sized_member( nCutSwing , uint8 , MagicBE("CS..") ) - WRITE_MPTHEADER_sized_member( nResSwing , uint8 , MagicBE("RS..") ) - WRITE_MPTHEADER_sized_member( filterMode , uint8 , MagicBE("FM..") ) - WRITE_MPTHEADER_sized_member( pluginVelocityHandling , uint8 , MagicBE("PVEH") ) - WRITE_MPTHEADER_sized_member( pluginVolumeHandling , uint8 , MagicBE("PVOH") ) - WRITE_MPTHEADER_trunc_member( pitchToTempoLock.GetInt() , uint16 , MagicBE("PTTL") ) - WRITE_MPTHEADER_trunc_member( pitchToTempoLock.GetFract() , uint16 , MagicLE("PTTF") ) - WRITE_MPTHEADER_sized_member( PitchEnv.nReleaseNode , uint8 , MagicBE("PERN") ) - WRITE_MPTHEADER_sized_member( PanEnv.nReleaseNode , uint8 , MagicBE("AERN") ) - WRITE_MPTHEADER_sized_member( VolEnv.nReleaseNode , uint8 , MagicBE("VERN") ) - WRITE_MPTHEADER_sized_member( PitchEnv.dwFlags , uint8 , MagicBE("PFLG") ) - WRITE_MPTHEADER_sized_member( PanEnv.dwFlags , uint8 , MagicBE("AFLG") ) - WRITE_MPTHEADER_sized_member( VolEnv.dwFlags , uint8 , MagicBE("VFLG") ) - WRITE_MPTHEADER_sized_member( midiPWD , int8 , MagicBE("MPWD") ) - // clang-format on + const bool m_intPart; +}; -} - - -template -static bool IsPropertyNeeded(const TIns &Instruments, PropType ModInstrument::*Prop) +template +static void WriteProperty(std::ostream &f, uint32 code, mpt::span instruments, PropertyWriter property) { - const ModInstrument defaultIns; - for(const auto ins : Instruments) + bool writeProperty = false; + for(const ModInstrument *ins : instruments) { - if(ins != nullptr && defaultIns.*Prop != ins->*Prop) - return true; + if(ins != nullptr && property.IsPropertyNeeded(*ins)) + { + writeProperty = true; + break; + } } - return false; -} - - -template -static void WritePropertyIfNeeded(const CSoundFile &sndFile, PropType ModInstrument::*Prop, uint32 code, uint16 size, std::ostream &f, INSTRUMENTINDEX numInstruments) -{ - if(IsPropertyNeeded(sndFile.Instruments, Prop)) + if(!writeProperty) + return; + mpt::IO::WriteIntLE(f, code); + mpt::IO::WriteIntLE(f, property.Size()); + for(const ModInstrument *ins : instruments) { - sndFile.WriteInstrumentPropertyForAllInstruments(code, size, f, numInstruments); + property.Write(f, ins ? *ins : MODINSTRUMENT_DEFAULT); } } -// Used only when saving IT, XM and MPTM. -// ITI, ITP saves using Ericus' macros etc... -// The reason is that ITs and XMs save [code][size][ins1.Value][ins2.Value]... -// whereas ITP saves [code][size][ins1.Value][code][size][ins2.Value]... -// too late to turn back.... -void CSoundFile::SaveExtendedInstrumentProperties(INSTRUMENTINDEX numInstruments, std::ostream &f) const +void CSoundFile::SaveExtendedInstrumentProperties(INSTRUMENTINDEX instr, MODTYPE forceType, std::ostream &f) const { - uint32 code = MagicBE("MPTX"); // write extension header code + const bool allInstruments = (instr < 1 || instr > GetNumInstruments()); + const auto instruments = mpt::as_span(Instruments).subspan(allInstruments ? 1 : instr, allInstruments ? GetNumInstruments() : 1); + SaveExtendedInstrumentProperties(instruments, forceType, f, allInstruments); +} + +void CSoundFile::SaveExtendedInstrumentProperties(mpt::span instruments, MODTYPE forceType, std::ostream &f, bool allInstruments) +{ + uint32 code = MagicBE("MPTX"); // write extension header code mpt::IO::WriteIntLE(f, code); - if (numInstruments == 0) - return; + WriteProperty(f, MagicBE("VR.."), instruments, PropertyWriterInt<&ModInstrument::nVolRampUp>{}); + WriteProperty(f, MagicBE("MiP."), instruments, PropertyWriterInt<&ModInstrument::nMixPlug>{}); + WriteProperty(f, MagicBE("R..."), instruments, PropertyWriterEnum<&ModInstrument::resampling>{}); + WriteProperty(f, MagicBE("PVEH"), instruments, PropertyWriterEnum<&ModInstrument::pluginVelocityHandling>{}); + WriteProperty(f, MagicBE("PVOH"), instruments, PropertyWriterEnum<&ModInstrument::pluginVolumeHandling>{}); - WritePropertyIfNeeded(*this, &ModInstrument::nVolRampUp, MagicBE("VR.."), sizeof(ModInstrument::nVolRampUp), f, numInstruments); - WritePropertyIfNeeded(*this, &ModInstrument::nMixPlug, MagicBE("MiP."), sizeof(ModInstrument::nMixPlug), f, numInstruments); - WritePropertyIfNeeded(*this, &ModInstrument::nMidiChannel, MagicBE("MC.."), sizeof(ModInstrument::nMidiChannel), f, numInstruments); - WritePropertyIfNeeded(*this, &ModInstrument::nMidiProgram, MagicBE("MP.."), sizeof(ModInstrument::nMidiProgram), f, numInstruments); - WritePropertyIfNeeded(*this, &ModInstrument::wMidiBank, MagicBE("MB.."), sizeof(ModInstrument::wMidiBank), f, numInstruments); - WritePropertyIfNeeded(*this, &ModInstrument::resampling, MagicBE("R..."), sizeof(ModInstrument::resampling), f, numInstruments); - WritePropertyIfNeeded(*this, &ModInstrument::pluginVelocityHandling, MagicBE("PVEH"), sizeof(ModInstrument::pluginVelocityHandling), f, numInstruments); - WritePropertyIfNeeded(*this, &ModInstrument::pluginVolumeHandling, MagicBE("PVOH"), sizeof(ModInstrument::pluginVolumeHandling), f, numInstruments); - - if(!(GetType() & MOD_TYPE_XM)) + if(!(forceType & MOD_TYPE_XM)) { // XM instrument headers already stores full-precision fade-out - bool writeFadeOut = false, writePan = false, writePWD = false; - int32 prevPWD = int32_min; - for(const auto ins : Instruments) + WriteProperty(f, MagicBE("FO.."), instruments, PropertyWriterInt<&ModInstrument::nFadeOut>{[](const ModInstrument &ins) { return (ins.nFadeOut % 32u) || ins.nFadeOut > 8192; }}); + // XM instrument headers already have support for this + // Note: For ITI we always want to write this property, hence the allInstruments check + int32 prevPWD = allInstruments ? int32_min : int32_max; + WriteProperty(f, MagicBE("MPWD"), instruments, PropertyWriterInt<&ModInstrument::midiPWD, std::function>{[&prevPWD](const ModInstrument& ins) + { + if((prevPWD != int32_min && ins.midiPWD != prevPWD) || (ins.midiPWD < 0)) + return true; + prevPWD = ins.midiPWD; + return false; + }}); + // We never supported these as hacks in XM (luckily!) + WriteProperty(f, MagicBE("P..."), instruments, PropertyWriterInt<&ModInstrument::nPan>{[](const ModInstrument &ins) { return ins.dwFlags[INS_SETPANNING] && (ins.nPan % 4u); }}); + WriteProperty(f, MagicBE("CS.."), instruments, PropertyWriterInt<&ModInstrument::nCutSwing>{}); + WriteProperty(f, MagicBE("RS.."), instruments, PropertyWriterInt<&ModInstrument::nResSwing>{}); + WriteProperty(f, MagicBE("FM.."), instruments, PropertyWriterEnum<&ModInstrument::filterMode>{}); + WriteProperty(f, MagicBE("PTTL"), instruments, PropertyWriterPitchTempoLock{true}); + WriteProperty(f, MagicLE("PTTF"), instruments, PropertyWriterPitchTempoLock{false}); + } else + { + WriteProperty(f, MagicBE("MC.."), instruments, PropertyWriterInt<&ModInstrument::nMidiChannel>{[](const ModInstrument &ins) { return ins.nMidiChannel == MidiMappedChannel; }}); + // Can be saved in XM, but it's not possible to NOT save a MIDI program if a MIDI channel is set + WriteProperty(f, MagicBE("MP.."), instruments, PropertyWriterInt<&ModInstrument::nMidiProgram>{[](const ModInstrument &ins) { return ins.HasValidMIDIChannel() == (ins.nMidiProgram == 0); }}); + WriteProperty(f, MagicBE("MB.."), instruments, PropertyWriterInt<&ModInstrument::wMidiBank>{}); + } + + if(forceType & MOD_TYPE_MPT) + { + uint32 maxNodes[3] = { 0, 0, 0 }; + for(const ModInstrument *ins : instruments) { if(ins == nullptr) continue; - if((ins->nFadeOut % 32u) || ins->nFadeOut > 8192) - writeFadeOut = true; - if(ins->dwFlags[INS_SETPANNING] && (ins->nPan % 4u)) - writePan = true; - if((prevPWD != int32_min && ins->midiPWD != prevPWD) || (ins->midiPWD < 0)) - writePWD = true; - prevPWD = ins->midiPWD; - } - if(writeFadeOut) - WriteInstrumentPropertyForAllInstruments(MagicBE("FO.."), sizeof(ModInstrument::nFadeOut), f, numInstruments); - // XM instrument headers already have support for this - if(writePWD) - WriteInstrumentPropertyForAllInstruments(MagicBE("MPWD"), sizeof(ModInstrument::midiPWD), f, numInstruments); - // We never supported these as hacks in XM (luckily!) - if(writePan) - WriteInstrumentPropertyForAllInstruments(MagicBE("P..."), sizeof(ModInstrument::nPan), f, numInstruments); - WritePropertyIfNeeded(*this, &ModInstrument::nCutSwing, MagicBE("CS.."), sizeof(ModInstrument::nCutSwing), f, numInstruments); - WritePropertyIfNeeded(*this, &ModInstrument::nResSwing, MagicBE("RS.."), sizeof(ModInstrument::nResSwing), f, numInstruments); - WritePropertyIfNeeded(*this, &ModInstrument::filterMode, MagicBE("FM.."), sizeof(ModInstrument::filterMode), f, numInstruments); - if(IsPropertyNeeded(Instruments, &ModInstrument::pitchToTempoLock)) - { - WriteInstrumentPropertyForAllInstruments(MagicBE("PTTL"), sizeof(uint16), f, numInstruments); - WriteInstrumentPropertyForAllInstruments(MagicLE("PTTF"), sizeof(uint16), f, numInstruments); - } - } - - if(GetType() & MOD_TYPE_MPT) - { - uint32 maxNodes[3] = { 0, 0, 0 }; - bool hasReleaseNode[3] = { false, false, false }; - for(INSTRUMENTINDEX i = 1; i <= numInstruments; i++) if(Instruments[i] != nullptr) - { - maxNodes[0] = std::max(maxNodes[0], Instruments[i]->VolEnv.size()); - maxNodes[1] = std::max(maxNodes[1], Instruments[i]->PanEnv.size()); - maxNodes[2] = std::max(maxNodes[2], Instruments[i]->PitchEnv.size()); - hasReleaseNode[0] |= (Instruments[i]->VolEnv.nReleaseNode != ENV_RELEASE_NODE_UNSET); - hasReleaseNode[1] |= (Instruments[i]->PanEnv.nReleaseNode != ENV_RELEASE_NODE_UNSET); - hasReleaseNode[2] |= (Instruments[i]->PitchEnv.nReleaseNode != ENV_RELEASE_NODE_UNSET); + maxNodes[0] = std::max(maxNodes[0], ins->VolEnv.size()); + maxNodes[1] = std::max(maxNodes[1], ins->PanEnv.size()); + maxNodes[2] = std::max(maxNodes[2], ins->PitchEnv.size()); } // write full envelope information for MPTM files (more env points) if(maxNodes[0] > 25) { - WriteInstrumentPropertyForAllInstruments(MagicBE("VE.."), sizeof(ModInstrument::VolEnv.size()), f, numInstruments); - WriteInstrumentPropertyForAllInstruments(MagicBE("VP[."), static_cast(maxNodes[0] * sizeof(EnvelopeNode::tick)), f, numInstruments); - WriteInstrumentPropertyForAllInstruments(MagicBE("VE[."), static_cast(maxNodes[0] * sizeof(EnvelopeNode::value)), f, numInstruments); + WriteProperty(f, MagicBE("VE.."), instruments, PropertyWriterEnvelopeSize{maxNodes[0], ENV_VOLUME}); + WriteProperty(f, MagicBE("VP[."), instruments, PropertyWriterEnvelopeTicks{maxNodes[0], ENV_VOLUME}); + WriteProperty(f, MagicBE("VE[."), instruments, PropertyWriterEnvelopeValues{maxNodes[0], ENV_VOLUME}); } if(maxNodes[1] > 25) { - WriteInstrumentPropertyForAllInstruments(MagicBE("PE.."), sizeof(ModInstrument::PanEnv.size()), f, numInstruments); - WriteInstrumentPropertyForAllInstruments(MagicBE("PP[."), static_cast(maxNodes[1] * sizeof(EnvelopeNode::tick)), f, numInstruments); - WriteInstrumentPropertyForAllInstruments(MagicBE("PE[."), static_cast(maxNodes[1] * sizeof(EnvelopeNode::value)), f, numInstruments); + WriteProperty(f, MagicBE("PE.."), instruments, PropertyWriterEnvelopeSize{maxNodes[1], ENV_PANNING}); + WriteProperty(f, MagicBE("PP[."), instruments, PropertyWriterEnvelopeTicks{maxNodes[1], ENV_PANNING}); + WriteProperty(f, MagicBE("PE[."), instruments, PropertyWriterEnvelopeValues{maxNodes[1], ENV_PANNING}); } if(maxNodes[2] > 25) { - WriteInstrumentPropertyForAllInstruments(MagicBE("PiE."), sizeof(ModInstrument::PitchEnv.size()), f, numInstruments); - WriteInstrumentPropertyForAllInstruments(MagicBE("PiP["), static_cast(maxNodes[2] * sizeof(EnvelopeNode::tick)), f, numInstruments); - WriteInstrumentPropertyForAllInstruments(MagicBE("PiE["), static_cast(maxNodes[2] * sizeof(EnvelopeNode::value)), f, numInstruments); + WriteProperty(f, MagicBE("PiE."), instruments, PropertyWriterEnvelopeSize{maxNodes[2], ENV_PITCH}); + WriteProperty(f, MagicBE("PiP["), instruments, PropertyWriterEnvelopeTicks{maxNodes[2], ENV_PITCH}); + WriteProperty(f, MagicBE("PiE["), instruments, PropertyWriterEnvelopeValues{maxNodes[2], ENV_PITCH}); } - if(hasReleaseNode[0]) - WriteInstrumentPropertyForAllInstruments(MagicBE("VERN"), sizeof(ModInstrument::VolEnv.nReleaseNode), f, numInstruments); - if(hasReleaseNode[1]) - WriteInstrumentPropertyForAllInstruments(MagicBE("AERN"), sizeof(ModInstrument::PanEnv.nReleaseNode), f, numInstruments); - if(hasReleaseNode[2]) - WriteInstrumentPropertyForAllInstruments(MagicBE("PERN"), sizeof(ModInstrument::PitchEnv.nReleaseNode), f, numInstruments); + WriteProperty(f, MagicBE("VERN"), instruments, PropertyWriterReleaseNode{ENV_VOLUME}); + WriteProperty(f, MagicBE("AERN"), instruments, PropertyWriterReleaseNode{ENV_PANNING}); + WriteProperty(f, MagicBE("PERN"), instruments, PropertyWriterReleaseNode{ENV_PITCH}); } } -void CSoundFile::WriteInstrumentPropertyForAllInstruments(uint32 code, uint16 size, std::ostream &f, INSTRUMENTINDEX nInstruments) const -{ - mpt::IO::WriteIntLE(f, code); //write code - mpt::IO::WriteIntLE(f, size); //write size - for(INSTRUMENTINDEX i = 1; i <= nInstruments; i++) //for all instruments... - { - if (Instruments[i]) - { - WriteInstrumentHeaderStructOrField(Instruments[i], f, code, size); - } else - { - ModInstrument emptyInstrument; - WriteInstrumentHeaderStructOrField(&emptyInstrument, f, code, size); - } - } -} + +#undef MODINSTRUMENT_DEFAULT #endif // !MODPLUG_NO_FILESAVE @@ -519,264 +426,177 @@ static void ConvertEnvelopeFlags(ModInstrument &instr, uint32 flags, EnvelopeTyp } -// -------------------------------------------------------------------------------------------- -// Convenient macro to help GET_HEADER declaration for single type members ONLY (non-array) -// -------------------------------------------------------------------------------------------- -#define GET_MPTHEADER_sized_member(name,type,code) \ - case code: \ - {\ - if( fsize <= sizeof( type ) ) \ - { \ - /* hackish workaround to resolve mismatched size values: */ \ - /* nResampling was a long time declared as uint32 but these macro tables used uint16 and UINT. */ \ - /* This worked fine on little-endian, on big-endian not so much. Thus support reading size-mismatched fields. */ \ - if(file.CanRead(fsize)) \ - { \ - type tmp; \ - tmp = file.ReadTruncatedIntLE(fsize); \ - static_assert(sizeof(tmp) == sizeof(input-> name )); \ - input-> name = decltype(input-> name )(tmp); \ - result = true; \ - } \ - } \ - } break; - -// -------------------------------------------------------------------------------------------- -// Convenient macro to help GET_HEADER declaration for array members ONLY -// -------------------------------------------------------------------------------------------- -#define GET_MPTHEADER_array_member(name,type,code) \ - case code: \ - {\ - if( fsize <= sizeof( type ) * std::size(input-> name) ) \ - { \ - FileReader arrayChunk = file.ReadChunk(fsize); \ - for(std::size_t i = 0; i < std::size(input-> name); ++i) \ - { \ - input-> name [i] = arrayChunk.ReadIntLE(); \ - } \ - result = true; \ - } \ - } break; - -// -------------------------------------------------------------------------------------------- -// Convenient macro to help GET_HEADER declaration for character buffer members ONLY -// -------------------------------------------------------------------------------------------- -#define GET_MPTHEADER_charbuf_member(name,type,code) \ - case code: \ - {\ - if( fsize <= sizeof( type ) * input-> name .static_length() ) \ - { \ - FileReader arrayChunk = file.ReadChunk(fsize); \ - std::string tmp; \ - for(std::size_t i = 0; i < fsize; ++i) \ - { \ - tmp += arrayChunk.ReadChar(); \ - } \ - input-> name = tmp; \ - result = true; \ - } \ - } break; - -// -------------------------------------------------------------------------------------------- -// Convenient macro to help GET_HEADER declaration for envelope tick/value members -// -------------------------------------------------------------------------------------------- -#define GET_MPTHEADER_envelope_member(envType,envField,type,code) \ - case code: \ - {\ - FileReader arrayChunk = file.ReadChunk(fsize); \ - InstrumentEnvelope &env = input->GetEnvelope(envType); \ - for(uint32 i = 0; i < env.size(); i++) \ - { \ - env[i]. envField = arrayChunk.ReadIntLE(); \ - } \ - result = true; \ - } break; - - -// Return a pointer on the wanted field in 'input' ModInstrument given field code & size -bool ReadInstrumentHeaderField(ModInstrument *input, uint32 fcode, uint16 fsize, FileReader &file) +static void ReadInstrumentHeaderField(ModInstrument &ins, uint32 fcode, FileReader &file) { - if(input == nullptr) return false; + const size_t size = static_cast(file.GetLength()); - bool result = false; + // Note: Various int / enum members have changed their size over the past. + // Hence we use ReadSizedIntLE everywhere to allow reading both truncated and oversized values. + constexpr auto ReadInt = [](FileReader &file, auto size, auto &member) + { + using T = std::remove_reference_t; + member = file.ReadSizedIntLE(size); + }; + constexpr auto ReadEnum = [](FileReader &file, auto size, auto &member) + { + using T = std::remove_reference_t; + static_assert(std::is_enum_v); + member = static_cast(file.ReadSizedIntLE>(size)); + }; + constexpr auto ReadEnvelopeTicks = [](FileReader &file, auto size, InstrumentEnvelope &env) + { + const uint32 points = std::min(env.size(), static_cast(size / 2)); + for(uint32 i = 0; i < points; i++) + { + env[i].tick = file.ReadUint16LE(); + } + }; + constexpr auto ReadEnvelopeValues = [](FileReader &file, auto size, InstrumentEnvelope &env) + { + const uint32 points = std::min(env.size(), static_cast(size)); + for(uint32 i = 0; i < points; i++) + { + env[i].value = file.ReadUint8(); + } + }; // Members which can be found in this table but not in the write table are only required in the legacy ITP format. switch(fcode) { - // clang-format off - GET_MPTHEADER_sized_member( nFadeOut , uint32 , MagicBE("FO..") ) - GET_MPTHEADER_sized_member( nGlobalVol , uint32 , MagicBE("GV..") ) - GET_MPTHEADER_sized_member( nPan , uint32 , MagicBE("P...") ) - GET_MPTHEADER_sized_member( VolEnv.nLoopStart , uint8 , MagicBE("VLS.") ) - GET_MPTHEADER_sized_member( VolEnv.nLoopEnd , uint8 , MagicBE("VLE.") ) - GET_MPTHEADER_sized_member( VolEnv.nSustainStart , uint8 , MagicBE("VSB.") ) - GET_MPTHEADER_sized_member( VolEnv.nSustainEnd , uint8 , MagicBE("VSE.") ) - GET_MPTHEADER_sized_member( PanEnv.nLoopStart , uint8 , MagicBE("PLS.") ) - GET_MPTHEADER_sized_member( PanEnv.nLoopEnd , uint8 , MagicBE("PLE.") ) - GET_MPTHEADER_sized_member( PanEnv.nSustainStart , uint8 , MagicBE("PSB.") ) - GET_MPTHEADER_sized_member( PanEnv.nSustainEnd , uint8 , MagicBE("PSE.") ) - GET_MPTHEADER_sized_member( PitchEnv.nLoopStart , uint8 , MagicBE("PiLS") ) - GET_MPTHEADER_sized_member( PitchEnv.nLoopEnd , uint8 , MagicBE("PiLE") ) - GET_MPTHEADER_sized_member( PitchEnv.nSustainStart , uint8 , MagicBE("PiSB") ) - GET_MPTHEADER_sized_member( PitchEnv.nSustainEnd , uint8 , MagicBE("PiSE") ) - GET_MPTHEADER_sized_member( nNNA , uint8 , MagicBE("NNA.") ) - GET_MPTHEADER_sized_member( nDCT , uint8 , MagicBE("DCT.") ) - GET_MPTHEADER_sized_member( nDNA , uint8 , MagicBE("DNA.") ) - GET_MPTHEADER_sized_member( nPanSwing , uint8 , MagicBE("PS..") ) - GET_MPTHEADER_sized_member( nVolSwing , uint8 , MagicBE("VS..") ) - GET_MPTHEADER_sized_member( nIFC , uint8 , MagicBE("IFC.") ) - GET_MPTHEADER_sized_member( nIFR , uint8 , MagicBE("IFR.") ) - GET_MPTHEADER_sized_member( wMidiBank , uint16 , MagicBE("MB..") ) - GET_MPTHEADER_sized_member( nMidiProgram , uint8 , MagicBE("MP..") ) - GET_MPTHEADER_sized_member( nMidiChannel , uint8 , MagicBE("MC..") ) - GET_MPTHEADER_sized_member( nPPS , int8 , MagicBE("PPS.") ) - GET_MPTHEADER_sized_member( nPPC , uint8 , MagicBE("PPC.") ) - GET_MPTHEADER_envelope_member(ENV_VOLUME , tick , uint16 , MagicBE("VP[.") ) - GET_MPTHEADER_envelope_member(ENV_PANNING , tick , uint16 , MagicBE("PP[.") ) - GET_MPTHEADER_envelope_member(ENV_PITCH , tick , uint16 , MagicBE("PiP[") ) - GET_MPTHEADER_envelope_member(ENV_VOLUME , value , uint8 , MagicBE("VE[.") ) - GET_MPTHEADER_envelope_member(ENV_PANNING , value , uint8 , MagicBE("PE[.") ) - GET_MPTHEADER_envelope_member(ENV_PITCH , value , uint8 , MagicBE("PiE[") ) - GET_MPTHEADER_array_member( NoteMap , uint8 , MagicBE("NM[.") ) - GET_MPTHEADER_array_member( Keyboard , uint16 , MagicBE("K[..") ) - GET_MPTHEADER_charbuf_member( name , char , MagicBE("n[..") ) - GET_MPTHEADER_charbuf_member( filename , char , MagicBE("fn[.") ) - GET_MPTHEADER_sized_member( nMixPlug , uint8 , MagicBE("MiP.") ) - GET_MPTHEADER_sized_member( nVolRampUp , uint16 , MagicBE("VR..") ) - GET_MPTHEADER_sized_member( nCutSwing , uint8 , MagicBE("CS..") ) - GET_MPTHEADER_sized_member( nResSwing , uint8 , MagicBE("RS..") ) - GET_MPTHEADER_sized_member( filterMode , uint8 , MagicBE("FM..") ) - GET_MPTHEADER_sized_member( pluginVelocityHandling , uint8 , MagicBE("PVEH") ) - GET_MPTHEADER_sized_member( pluginVolumeHandling , uint8 , MagicBE("PVOH") ) - GET_MPTHEADER_sized_member( PitchEnv.nReleaseNode , uint8 , MagicBE("PERN") ) - GET_MPTHEADER_sized_member( PanEnv.nReleaseNode , uint8 , MagicBE("AERN") ) - GET_MPTHEADER_sized_member( VolEnv.nReleaseNode , uint8 , MagicBE("VERN") ) - GET_MPTHEADER_sized_member( midiPWD , int8 , MagicBE("MPWD") ) - // clang-format on + case MagicBE("FO.."): ReadInt(file, size, ins.nFadeOut); break; + case MagicBE("GV.."): ReadInt(file, size, ins.nGlobalVol); break; + case MagicBE("P..."): ReadInt(file, size, ins.nPan); break; + case MagicBE("VLS."): ReadInt(file, size, ins.VolEnv.nLoopStart); break; + case MagicBE("VLE."): ReadInt(file, size, ins.VolEnv.nLoopEnd); break; + case MagicBE("VSB."): ReadInt(file, size, ins.VolEnv.nSustainStart); break; + case MagicBE("VSE."): ReadInt(file, size, ins.VolEnv.nSustainEnd); break; + case MagicBE("PLS."): ReadInt(file, size, ins.PanEnv.nLoopStart); break; + case MagicBE("PLE."): ReadInt(file, size, ins.PanEnv.nLoopEnd); break; + case MagicBE("PSB."): ReadInt(file, size, ins.PanEnv.nSustainStart); break; + case MagicBE("PSE."): ReadInt(file, size, ins.PanEnv.nSustainEnd); break; + case MagicBE("PiLS"): ReadInt(file, size, ins.PitchEnv.nLoopStart); break; + case MagicBE("PiLE"): ReadInt(file, size, ins.PitchEnv.nLoopEnd); break; + case MagicBE("PiSB"): ReadInt(file, size, ins.PitchEnv.nSustainStart); break; + case MagicBE("PiSE"): ReadInt(file, size, ins.PitchEnv.nSustainEnd); break; + case MagicBE("NNA."): ReadEnum(file, size, ins.nNNA); break; + case MagicBE("DCT."): ReadEnum(file, size, ins.nDCT); break; + case MagicBE("DNA."): ReadEnum(file, size, ins.nDNA); break; + case MagicBE("PS.."): ReadInt(file, size, ins.nPanSwing); break; + case MagicBE("VS.."): ReadInt(file, size, ins.nVolSwing); break; + case MagicBE("IFC."): ReadInt(file, size, ins.nIFC); break; + case MagicBE("IFR."): ReadInt(file, size, ins.nIFR); break; + case MagicBE("MB.."): ReadInt(file, size, ins.wMidiBank); break; + case MagicBE("MP.."): ReadInt(file, size, ins.nMidiProgram); break; + case MagicBE("MC.."): ReadInt(file, size, ins.nMidiChannel); break; + case MagicBE("PPS."): ReadInt(file, size, ins.nPPS); break; + case MagicBE("PPC."): ReadInt(file, size, ins.nPPC); break; + case MagicBE("VP[."): ReadEnvelopeTicks(file, size, ins.VolEnv); break; + case MagicBE("PP[."): ReadEnvelopeTicks(file, size, ins.PanEnv); break; + case MagicBE("PiP["): ReadEnvelopeTicks(file, size, ins.PitchEnv); break; + case MagicBE("VE[."): ReadEnvelopeValues(file, size, ins.VolEnv); break; + case MagicBE("PE[."): ReadEnvelopeValues(file, size, ins.PanEnv); break; + case MagicBE("PiE["): ReadEnvelopeValues(file, size, ins.PitchEnv); break; + case MagicBE("MiP."): ReadInt(file, size, ins.nMixPlug); break; + case MagicBE("VR.."): ReadInt(file, size, ins.nVolRampUp); break; + case MagicBE("CS.."): ReadInt(file, size, ins.nCutSwing); break; + case MagicBE("RS.."): ReadInt(file, size, ins.nResSwing); break; + case MagicBE("FM.."): ReadEnum(file, size, ins.filterMode); break; + case MagicBE("PVEH"): ReadEnum(file, size, ins.pluginVelocityHandling); break; + case MagicBE("PVOH"): ReadEnum(file, size, ins.pluginVolumeHandling); break; + case MagicBE("PERN"): ReadInt(file, size, ins.PitchEnv.nReleaseNode); break; + case MagicBE("AERN"): ReadInt(file, size, ins.PanEnv.nReleaseNode); break; + case MagicBE("VERN"): ReadInt(file, size, ins.VolEnv.nReleaseNode); break; + case MagicBE("MPWD"): ReadInt(file, size, ins.midiPWD); break; case MagicBE("dF.."): - ConvertInstrumentFlags(*input, file.ReadSizedIntLE(fsize)); - return true; + ConvertInstrumentFlags(ins, file.ReadSizedIntLE(size)); + break; case MagicBE("VFLG"): - ConvertEnvelopeFlags(*input, file.ReadSizedIntLE(fsize), ENV_VOLUME); - return true; + ConvertEnvelopeFlags(ins, file.ReadSizedIntLE(size), ENV_VOLUME); + break; case MagicBE("AFLG"): - ConvertEnvelopeFlags(*input, file.ReadSizedIntLE(fsize), ENV_PANNING); - return true; + ConvertEnvelopeFlags(ins, file.ReadSizedIntLE(size), ENV_PANNING); + break; case MagicBE("PFLG"): - ConvertEnvelopeFlags(*input, file.ReadSizedIntLE(fsize), ENV_PITCH); - return true; + ConvertEnvelopeFlags(ins, file.ReadSizedIntLE(size), ENV_PITCH); + break; + case MagicBE("NM[."): + for(std::size_t i = 0; i < std::min(size, ins.NoteMap.size()); i++) + { + ins.NoteMap[i] = file.ReadUint8(); + } + break; + case MagicBE("n[.."): + { + char name[32] = ""; + file.ReadString(name, size); + ins.name = name; + } + break; + case MagicBE("fn[."): + { + char filename[32] = ""; + file.ReadString(filename, size); + ins.filename = filename; + } + break; case MagicBE("R..."): - { // Resampling has been written as various sizes including uint16 and uint32 in the past - uint32 tmp = file.ReadSizedIntLE(fsize); - if(Resampling::IsKnownMode(tmp)) - input->resampling = static_cast(tmp); - result = true; - } break; + if(uint32 resampling = file.ReadSizedIntLE(size); Resampling::IsKnownMode(resampling)) + ins.resampling = static_cast(resampling); + break; case MagicBE("PTTL"): - { // Integer part of pitch/tempo lock - uint16 tmp = file.ReadSizedIntLE(fsize); - input->pitchToTempoLock.Set(tmp, input->pitchToTempoLock.GetFract()); - result = true; - } break; + ins.pitchToTempoLock.Set(file.ReadSizedIntLE(size), ins.pitchToTempoLock.GetFract()); + break; case MagicLE("PTTF"): - { // Fractional part of pitch/tempo lock - uint16 tmp = file.ReadSizedIntLE(fsize); - input->pitchToTempoLock.Set(input->pitchToTempoLock.GetInt(), tmp); - result = true; - } break; + ins.pitchToTempoLock.Set(ins.pitchToTempoLock.GetInt(), file.ReadSizedIntLE(size)); + break; case MagicBE("VE.."): - input->VolEnv.resize(std::min(uint32(MAX_ENVPOINTS), file.ReadSizedIntLE(fsize))); - result = true; + ins.VolEnv.resize(std::min(uint32(MAX_ENVPOINTS), file.ReadSizedIntLE(size))); break; case MagicBE("PE.."): - input->PanEnv.resize(std::min(uint32(MAX_ENVPOINTS), file.ReadSizedIntLE(fsize))); - result = true; + ins.PanEnv.resize(std::min(uint32(MAX_ENVPOINTS), file.ReadSizedIntLE(size))); break; case MagicBE("PiE."): - input->PitchEnv.resize(std::min(uint32(MAX_ENVPOINTS), file.ReadSizedIntLE(fsize))); - result = true; + ins.PitchEnv.resize(std::min(uint32(MAX_ENVPOINTS), file.ReadSizedIntLE(size))); break; } - - return result; -} - -void ReadInstrumentExtensionField(ModInstrument* pIns, const uint32 code, const uint16 size, FileReader &file) -{ - if(code == MagicBE("K[..")) - { - // skip keyboard mapping - file.Skip(size); - return; - } - - bool success = ReadInstrumentHeaderField(pIns, code, size, file); - - if(!success) - { - file.Skip(size); - return; - } } -void ReadExtendedInstrumentProperty(ModInstrument* pIns, const uint32 code, FileReader &file) +// For ITP and internal usage +void CSoundFile::ReadExtendedInstrumentProperty(mpt::span instruments, const uint32 code, FileReader &file) { uint16 size = file.ReadUint16LE(); - if(!file.CanRead(size)) + for(ModInstrument *ins : instruments) { - return; - } - ReadInstrumentExtensionField(pIns, code, size, file); -} - - -void ReadExtendedInstrumentProperties(ModInstrument* pIns, FileReader &file) -{ - if(!file.ReadMagic("XTPM")) // 'MPTX' - { - return; - } - - while(file.CanRead(7)) - { - ReadExtendedInstrumentProperty(pIns, file.ReadUint32LE(), file); + FileReader chunk = file.ReadChunk(size); + if(ins && chunk.GetLength() == size) + ReadInstrumentHeaderField(*ins, code, chunk); } } -bool CSoundFile::LoadExtendedInstrumentProperties(FileReader &file) +// For IT / XM / MO3 / ITI / XI +bool CSoundFile::LoadExtendedInstrumentProperties(mpt::span instruments, FileReader &file) { - if(!file.ReadMagic("XTPM")) // 'MPTX' - { + if(!file.ReadMagic("XTPM")) // 'MPTX' return false; - } while(file.CanRead(6)) { uint32 code = file.ReadUint32LE(); - if(code == MagicBE("MPTS") // Reached song extensions, break out of this loop - || code == MagicLE("228\x04") // Reached MPTM extensions (in case there are no song extensions) - || (code & 0x80808080) || !(code & 0x60606060)) // Non-ASCII chunk ID + if(code == MagicBE("MPTS") // Reached song extensions, break out of this loop + || code == MagicLE("228\x04") // Reached MPTM extensions (in case there are no song extensions) + || (code & 0x80808080) || !(code & 0x60606060)) // Non-ASCII chunk ID { file.SkipBack(4); break; } - // Read size of this property for *one* instrument - const uint16 size = file.ReadUint16LE(); - - for(INSTRUMENTINDEX i = 1; i <= GetNumInstruments(); i++) - { - if(Instruments[i]) - { - ReadInstrumentExtensionField(Instruments[i], code, size, file); - } - } + ReadExtendedInstrumentProperty(instruments, code, file); } return true; } diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/InstrumentSynth.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/InstrumentSynth.cpp new file mode 100644 index 000000000..1aa60f30f --- /dev/null +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/InstrumentSynth.cpp @@ -0,0 +1,1059 @@ +/* + * InstrumentSynth.cpp + * ------------------- + * Purpose: "Script" / "Synth" processor for various file formats (MED, GT2, Puma, His Master's Noise, Face The Music, Future Composer) + * Notes : (currently none) + * Authors: OpenMPT Devs + * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. + */ + + +#include "stdafx.h" +#include "InstrumentSynth.h" +#include "ModChannel.h" +#include "Sndfile.h" +#include "Tables.h" + +OPENMPT_NAMESPACE_BEGIN + +struct InstrumentSynth::States::State +{ + static constexpr uint16 STOP_ROW = uint16_max; + + enum Flags + { + kJumpConditionSet, + kGTKTremorEnabled, + kGTKTremorMute, + kGTKTremoloEnabled, + kGTKVibratoEnabled, + kFCVibratoDelaySet, + kFCVibratoStep, + kFCPitchBendStep, + kFCVolumeBendStep, + kNumFlags, + }; + + std::bitset m_flags; + + uint16 m_currentRow = STOP_ROW; + uint16 m_nextRow = 0; + uint16 m_ticksRemain = 0; + uint8 m_stepSpeed = 1; + uint8 m_stepsRemain = 0; + + uint16 m_volumeFactor = 16384; + int16 m_volumeAdd = int16_min; + uint16 m_panning = 2048; + int16 m_linearPitchFactor = 0; + int16 m_periodFreqSlide = 0; + int16 m_periodAdd = 0; + uint16 m_loopCount = 0; + + uint16 m_gtkKeyOffOffset = STOP_ROW; + int16 m_gtkVolumeStep = 0, m_gtkPitchStep = 0, m_gtkPanningStep = 0; + uint16 m_gtkPitch = 4096; + uint8 m_gtkSpeed = 1, m_gtkSpeedRemain = 1; + uint8 m_gtkTremorOnTime = 3, m_gtkTremorOffTime = 3, m_gtkTremorPos = 0; + uint8 m_gtkVibratoWidth = 0, m_gtkVibratoSpeed = 0, m_gtkVibratoPos = 0; + + uint8 m_pumaStartWaveform = 0, m_pumaEndWaveform = 0, m_pumaWaveform = 0; + int8 m_pumaWaveformStep = 0; + + uint8 m_medVibratoEnvelope = uint8_max, m_medVibratoSpeed = 0, m_medVibratoDepth = 0; + int8 m_medVibratoValue = 0; + uint16 m_medVibratoPos = 0; + int16 m_medVolumeStep = 0; + int16 m_medPeriodStep = 0; + uint16 m_medArpOffset = STOP_ROW; + uint8 m_medArpPos = 0; + uint8 m_medHold = uint8_max; + uint16 m_medDecay = STOP_ROW; + uint8 m_medVolumeEnv = uint8_max, m_medVolumeEnvPos = 0; + + uint32 m_ftmSampleStart = 0; + int16 m_ftmDetune = 1; + uint16 m_ftmVolumeChangeJump = STOP_ROW; + uint16 m_ftmPitchChangeJump = STOP_ROW; + uint16 m_ftmSampleChangeJump = STOP_ROW; + uint16 m_ftmReleaseJump = STOP_ROW; + uint16 m_ftmVolumeDownJump = STOP_ROW; + uint16 m_ftmPortamentoJump = STOP_ROW; + struct LFO + { + uint8 targetWaveform = 0, speed = 0, depth = 0, position = 0; + }; + std::array m_ftmLFO; + uint8 m_ftmWorkTrack = 0; + + int8 m_fcPitch = 0; + int16 m_fcVibratoValue = 0; + uint8 m_fcVibratoDelay = 0, m_fcVibratoSpeed = 0, m_fcVibratoDepth = 0; + int8 m_fcVolumeBendSpeed = 0, m_fcPitchBendSpeed = 0; + uint8 m_fcVolumeBendRemain = 0, m_fcPitchBendRemain = 0; + + void JumpToPosition(const Events &events, uint16 position); + void NextTick(const Events &events, PlayState &playState, CHANNELINDEX channel, const CSoundFile &sndFile, States &states); + void ApplyChannelState(ModChannel &chn, int32 &period, const CSoundFile &sndFile); + bool EvaluateEvent(const Event &event, PlayState &playState, CHANNELINDEX channel, const CSoundFile &sndFile, States &states); + void EvaluateRunningEvent(const Event &event); + void HandleFTMInterrupt(uint16 &target, const bool condition); + bool HandleFCVolumeBend(bool forceRun = false); + + CHANNELINDEX FTMRealChannel(CHANNELINDEX channel, const CSoundFile &sndFile) const noexcept + { + if(m_ftmWorkTrack) + return static_cast(m_ftmWorkTrack - 1) % sndFile.GetNumChannels(); + else + return channel; + } +}; + + +static int32 ApplyLinearPitchSlide(int32 target, const int32 totalAmount, const bool periodsAreFrequencies) +{ + const auto &table = (periodsAreFrequencies ^ (totalAmount < 0)) ? LinearSlideUpTable : LinearSlideDownTable; + size_t value = std::abs(totalAmount); + while(value > 0) + { + const size_t amount = std::min(value, std::size(table) - size_t(1)); + target = Util::muldivr(target, table[amount], 65536); + value -= amount; + } + return target; +} + + +static int16 TranslateGT2Pitch(uint16 pitch) +{ + // 4096 = normal, 8192 = one octave up + return mpt::saturate_round((mpt::log2(8192.0 / std::max(pitch, uint16(1))) - 1.0) * (16 * 12)); +} + + +static int32 TranslateFTMPitch(uint16 pitch, ModChannel &chn, const CSoundFile &sndFile) +{ + int32 period = sndFile.GetPeriodFromNote(NOTE_MIDDLEC - 12 + pitch / 16, chn.nFineTune, chn.nC5Speed); + sndFile.DoFreqSlide(chn, period, (pitch % 16) * 4); + return period; +} + + +static int8 MEDEnvelopeFromSample(const ModInstrument &instr, const CSoundFile &sndFile, uint8 envelope, uint16 envelopePos) +{ + SAMPLEINDEX smp = instr.Keyboard[NOTE_MIDDLEC - NOTE_MIN] + envelope; + if(smp < 1 || smp > sndFile.GetNumSamples()) + return 0; + + const auto &mptSmp = sndFile.GetSample(smp); + if(envelopePos >= mptSmp.nLength || mptSmp.uFlags[CHN_16BIT] || !mptSmp.sample8()) + return 0; + + return mptSmp.sample8()[envelopePos]; +} + + +static void ChannelSetSample(ModChannel &chn, const CSoundFile &sndFile, SAMPLEINDEX smp, bool swapAtEnd = true) +{ + if(smp < 1 || smp > sndFile.GetNumSamples()) + return; + const bool channelIsActive = chn.pCurrentSample && chn.nLength; + if(sndFile.m_playBehaviour[kMODSampleSwap] && smp <= uint8_max && swapAtEnd && channelIsActive) + { + chn.swapSampleIndex = smp; + return; + } + const ModSample &sample = sndFile.GetSample(smp); + if(chn.pModSample == &sample && channelIsActive) + return; + if(chn.increment.IsZero() && chn.nLength == 0 && chn.nVolume == 0) + chn.nVolume = 256; + chn.pModSample = &sample; + chn.pCurrentSample = sample.samplev(); + chn.dwFlags = (chn.dwFlags & CHN_CHANNELFLAGS) | sample.uFlags; + chn.nLength = sample.uFlags[CHN_LOOP] ? sample.nLoopEnd : sample.nLength; + chn.nLoopStart = sample.nLoopStart; + chn.nLoopEnd = sample.nLoopEnd; + if(chn.position.GetUInt() >= chn.nLength) + chn.position.Set(0); +} + + +// To allow State to be forward-declared +InstrumentSynth::States::States() = default; +InstrumentSynth::States::States(const States &other) = default; +InstrumentSynth::States::States(States &&other) noexcept = default; +InstrumentSynth::States::~States() = default; +InstrumentSynth::States &InstrumentSynth::States::operator=(const States &other) = default; +InstrumentSynth::States &InstrumentSynth::States::operator=(States &&other) noexcept = default; + + +void InstrumentSynth::States::Stop() +{ + for(auto &state : states) + state.m_currentRow = state.m_nextRow = State::STOP_ROW; +} + + +void InstrumentSynth::States::NextTick(PlayState &playState, CHANNELINDEX channel, const CSoundFile &sndFile) +{ + ModChannel &chn = playState.Chn[channel]; + if(!chn.pModInstrument || !chn.pModInstrument->synth.HasScripts()) + return; + + const auto &scripts = chn.pModInstrument->synth.m_scripts; + states.resize(scripts.size()); + for(size_t i = 0; i < scripts.size(); i++) + { + auto &state = states[i]; + if(chn.triggerNote) + mpt::reconstruct(state); + if(i == 1 && chn.rowCommand.command == CMD_MED_SYNTH_JUMP && chn.isFirstTick) + state.JumpToPosition(scripts[i], chn.rowCommand.param); + + state.NextTick(scripts[i], playState, channel, sndFile, *this); + } +} + + +void InstrumentSynth::States::ApplyChannelState(ModChannel &chn, int32 &period, const CSoundFile &sndFile) +{ + if(!chn.pModInstrument || !chn.pModInstrument->synth.HasScripts()) + return; + + for(auto &state : states) + { + state.ApplyChannelState(chn, period, sndFile); + } +} + + +void InstrumentSynth::States::State::JumpToPosition(const Events &events, uint16 position) +{ + for(size_t pos = 0; pos < events.size(); pos++) + { + if(events[pos].type == Event::Type::JumpMarker && events[pos].u16 >= position) + { + m_nextRow = static_cast(pos); + m_ticksRemain = 0; + return; + } + } +} + + +void InstrumentSynth::States::State::NextTick(const Events &events, PlayState &playState, CHANNELINDEX channel, const CSoundFile &sndFile, States &states) +{ + if(events.empty()) + return; + + const CHANNELINDEX origChannel = channel; + channel = FTMRealChannel(channel, sndFile); + ModChannel *chn = &playState.Chn[channel]; + + if(m_gtkKeyOffOffset != STOP_ROW && chn->dwFlags[CHN_KEYOFF]) + { + m_nextRow = m_gtkKeyOffOffset; + m_ticksRemain = 0; + m_gtkKeyOffOffset = STOP_ROW; + } + if(m_pumaWaveformStep) + { + m_pumaWaveform = static_cast(Clamp(m_pumaWaveform + m_pumaWaveformStep, m_pumaStartWaveform, m_pumaEndWaveform)); + if(m_pumaWaveform <= m_pumaStartWaveform || m_pumaWaveform >= m_pumaEndWaveform) + m_pumaWaveformStep = -m_pumaWaveformStep; + ChannelSetSample(*chn, sndFile, m_pumaWaveform); + } + + if(m_medHold != uint8_max) + { + if(!m_medHold--) + m_nextRow = m_medDecay; + } + + const ModCommand &m = chn->rowCommand; + HandleFTMInterrupt(m_ftmPitchChangeJump, m.IsNote()); + HandleFTMInterrupt(m_ftmVolumeChangeJump, m.command == CMD_CHANNELVOLUME); + HandleFTMInterrupt(m_ftmSampleChangeJump, m.instr != 0); + HandleFTMInterrupt(m_ftmReleaseJump, m.note == NOTE_KEYOFF); + HandleFTMInterrupt(m_ftmVolumeDownJump, m.command == CMD_VOLUMEDOWN_DURATION); + HandleFTMInterrupt(m_ftmPortamentoJump, m.command == CMD_TONEPORTA_DURATION); + + if(!HandleFCVolumeBend() && m_stepSpeed && !m_stepsRemain--) + { + // Yep, MED executes this before a potential SPD command may change the step speed on this very row... + m_stepsRemain = m_stepSpeed - 1; + + if(m_medVolumeStep) + m_volumeFactor = static_cast(std::clamp(m_volumeFactor + m_medVolumeStep, 0, 16384)); + if(m_medPeriodStep) + m_periodAdd = mpt::saturate_cast(m_periodAdd - m_medPeriodStep); + if(m_medVolumeEnv != uint8_max && chn->pModInstrument) + { + m_volumeFactor = static_cast(std::clamp((MEDEnvelopeFromSample(*chn->pModInstrument, sndFile, m_medVolumeEnv & 0x7F, m_medVolumeEnvPos) + 128) * 64, 0, 16384)); + if(m_medVolumeEnvPos < 127) + m_medVolumeEnvPos++; + else if(m_medVolumeEnv & 0x80) + m_medVolumeEnvPos = 0; + } + + if(m_ticksRemain) + { + if(m_currentRow < events.size()) + EvaluateRunningEvent(events[m_currentRow]); + m_ticksRemain--; + } else + { + uint8 jumpCount = 0; + while(!m_ticksRemain) + { + m_currentRow = m_nextRow; + if(m_currentRow >= std::min(events.size(), static_cast(STOP_ROW))) + break; + m_nextRow++; + if(EvaluateEvent(events[m_currentRow], playState, channel, sndFile, states)) + break; + + MPT_ASSERT(m_nextRow == STOP_ROW || m_nextRow == m_currentRow + 1 || events[m_currentRow].IsJumpEvent()); + if(events[m_currentRow].IsJumpEvent()) + { + // This smells like an infinite loop + if(jumpCount++ > 10) + break; + } + + channel = FTMRealChannel(origChannel, sndFile); + chn = &playState.Chn[channel]; + } + } + } + + // MED stuff + if(m_medArpOffset < events.size() && events[m_medArpOffset].u16) + { + m_linearPitchFactor = 16 * events[m_medArpOffset + m_medArpPos].u8; + m_medArpPos = static_cast((m_medArpPos + 1) % events[m_medArpOffset].u16); + } + if(m_medVibratoDepth) + { + static_assert(std::size(ModSinusTable) == 64); + uint16 offset = m_medVibratoPos / 16u; + if(m_medVibratoEnvelope == uint8_max) + m_medVibratoValue = ModSinusTable[(offset * 2) % std::size(ModSinusTable)]; + else if(chn->pModInstrument) + m_medVibratoValue = MEDEnvelopeFromSample(*chn->pModInstrument, sndFile, m_medVibratoEnvelope, offset); + m_medVibratoPos = (m_medVibratoPos + m_medVibratoSpeed) % (32u * 16u); + } + + // GTK stuff + if(m_currentRow < events.size() && m_gtkSpeed && !--m_gtkSpeedRemain) + { + m_gtkSpeedRemain = m_gtkSpeed; + if(m_gtkVolumeStep) + m_volumeFactor = static_cast(std::clamp(m_volumeFactor + m_gtkVolumeStep, 0, 16384)); + if(m_gtkPanningStep) + m_panning = static_cast(std::clamp(m_panning + m_gtkPanningStep, 0, 4096)); + if(m_gtkPitchStep) + { + m_gtkPitch = static_cast(std::clamp(m_gtkPitch + m_gtkPitchStep, 0, 32768)); + m_linearPitchFactor = TranslateGT2Pitch(m_gtkPitch); + } + } + if(m_flags[kGTKTremorEnabled]) + { + if(m_gtkTremorPos >= m_gtkTremorOnTime + m_gtkTremorOffTime) + m_gtkTremorPos = 0; + m_flags.set(kGTKTremorMute, m_gtkTremorPos >= m_gtkTremorOnTime); + m_gtkTremorPos++; + } + if(m_flags[kGTKTremoloEnabled]) + { + m_volumeAdd = static_cast(ModSinusTable[(m_gtkVibratoPos / 4u) % std::size(ModSinusTable)] * m_gtkVibratoWidth / 2); + m_gtkVibratoPos += m_gtkVibratoSpeed; + } + if(m_flags[kGTKVibratoEnabled]) + { + m_periodFreqSlide = static_cast(-ModSinusTable[(m_gtkVibratoPos / 4u) % std::size(ModSinusTable)] * m_gtkVibratoWidth / 96); + m_gtkVibratoPos += m_gtkVibratoSpeed; + } + + // FTM LFOs + for(auto &lfo : m_ftmLFO) + { + if(!lfo.speed && !lfo.depth) + continue; + + const uint8 lutPos = static_cast(Util::muldivr_unsigned(lfo.position, 256, 192)); + int32 value = 0; + switch(lfo.targetWaveform & 0x07) + { + case 0: value = ITSinusTable[lutPos]; break; + case 1: value = lutPos < 128 ? 64 : -64; break; + case 2: value = 64 - std::abs(((lutPos + 64) % 256) - 128); break; + case 3: value = 64 - lutPos / 2; break; + case 4: value = lutPos / 2 - 64; break; + } + if((lfo.targetWaveform & 0xF0) < 0xA0) + value += 64; + value *= lfo.depth; // -8192...+8192 or 0...16384 for LFO targets + + switch(lfo.targetWaveform & 0xF0) + { + case 0x10: m_ftmLFO[0].speed = static_cast(value / 64); break; + case 0x20: m_ftmLFO[1].speed = static_cast(value / 64); break; + case 0x30: m_ftmLFO[2].speed = static_cast(value / 64); break; + case 0x40: m_ftmLFO[3].speed = static_cast(value / 64); break; + case 0x50: m_ftmLFO[0].depth = static_cast(value / 64); break; + case 0x60: m_ftmLFO[1].depth = static_cast(value / 64); break; + case 0x70: m_ftmLFO[2].depth = static_cast(value / 64); break; + case 0x80: m_ftmLFO[3].depth = static_cast(value / 64); break; + case 0xA0: m_volumeAdd = mpt::saturate_cast(value * 4); break; + case 0xF0: m_periodFreqSlide = static_cast(value / 8); break; + } + + uint16 newPos = lfo.position + lfo.speed; + if(newPos >= 192) + { + newPos -= 192; + if(lfo.targetWaveform & 0x08) + { + lfo.speed = 0; + newPos = 191; + } + } + lfo.position = static_cast(newPos); + } + + // Future Composer stuff + if(m_flags[kFCVibratoDelaySet] && m_fcVibratoDelay > 0) + { + m_fcVibratoDelay--; + } else if(m_fcVibratoDepth) + { + if(m_flags[kFCVibratoStep]) + { + int16 delta = m_fcVibratoDepth * 2; + m_fcVibratoValue += m_fcVibratoSpeed; + if(m_fcVibratoValue > delta) + { + m_fcVibratoValue = delta; + m_flags.flip(kFCVibratoStep); + } + } else + { + m_fcVibratoValue -= m_fcVibratoSpeed; + if(m_fcVibratoValue < 0) + { + m_fcVibratoValue = 0; + m_flags.flip(kFCVibratoStep); + } + } + } + if(m_fcPitchBendRemain) + { + m_flags.flip(kFCPitchBendStep); + if(m_flags[kFCPitchBendStep]) + { + m_fcPitchBendRemain--; + m_periodAdd -= static_cast(m_fcPitchBendSpeed * 4); + } + } +} + + +void InstrumentSynth::States::State::ApplyChannelState(ModChannel &chn, int32 &period, const CSoundFile &sndFile) +{ + if(m_volumeFactor != 16384) + chn.nRealVolume = Util::muldivr(chn.nRealVolume, m_volumeFactor, 16384); + if(m_volumeAdd != int16_min) + chn.nRealVolume = std::clamp(chn.nRealVolume + m_volumeAdd, int32(0), int32(16384)); + if(m_flags[kGTKTremorEnabled] && m_flags[kGTKTremorMute]) + chn.nRealVolume = 0; + + if(m_panning != 2048) + { + if(chn.nRealPan >= 128) + chn.nRealPan += ((m_panning - 2048) * (256 - chn.nRealPan)) / 2048; + else + chn.nRealPan += ((m_panning - 2048) * (chn.nRealPan)) / 2048; + } + + const bool periodsAreFrequencies = sndFile.PeriodsAreFrequencies(); + if(m_linearPitchFactor != 0) + period = ApplyLinearPitchSlide(period, m_linearPitchFactor, periodsAreFrequencies); + if(m_periodFreqSlide != 0) + sndFile.DoFreqSlide(chn, period, m_periodFreqSlide); + if(periodsAreFrequencies) + period -= m_periodAdd; + else + period += m_periodAdd; + if(m_medVibratoDepth) + period += m_medVibratoValue * m_medVibratoDepth / 64; + + int16 vibratoFC = m_fcVibratoValue - m_fcVibratoDepth; + const bool doVibratoFC = vibratoFC != 0 && m_fcVibratoDelay < 1; + if(m_fcPitch || doVibratoFC) + { + uint8 fcNote = static_cast(m_fcPitch >= 0 ? m_fcPitch + chn.nLastNote - NOTE_MIN : m_fcPitch) & 0x7F; + static_assert(mpt::array_sizeNoteMap)>::size > 0x7F); + if(m_fcPitch && ModCommand::IsNote(chn.nLastNote)) + period += (sndFile.GetPeriodFromNote(chn.pModInstrument->NoteMap[fcNote], chn.nFineTune, chn.nC5Speed) - sndFile.GetPeriodFromNote(chn.pModInstrument->NoteMap[chn.nLastNote - NOTE_MIN], chn.nFineTune, chn.nC5Speed)); + + if(doVibratoFC) + { + int note = (fcNote * 2) + 160; + while(note < 256) + { + vibratoFC *= 2; + note += 24; + } + period += vibratoFC * 4; + } + } + + if((m_linearPitchFactor || m_periodFreqSlide || m_periodAdd) && !sndFile.PeriodsAreFrequencies()) + { + if(period < sndFile.m_nMinPeriod) + period = sndFile.m_nMinPeriod; + else if(period > sndFile.m_nMaxPeriod && sndFile.m_playBehaviour[kApplyUpperPeriodLimit]) + period = sndFile.m_nMaxPeriod; + } + if(period < 1) + period = 1; + + if(m_ftmDetune != 1) + chn.microTuning = m_ftmDetune; +} + + +bool InstrumentSynth::States::State::EvaluateEvent(const Event &event, PlayState &playState, CHANNELINDEX channel, const CSoundFile &sndFile, States &states) +{ + // Return true to indicate end of processing for this tick + ModChannel &chn = playState.Chn[channel]; + switch(event.type) + { + case Event::Type::StopScript: + m_nextRow = STOP_ROW; + return true; + case Event::Type::Jump: + m_nextRow = event.u16; + return false; + case Event::Type::JumpIfTrue: + if(m_flags[kJumpConditionSet]) + m_nextRow = event.u16; + return false; + case Event::Type::Delay: + m_ticksRemain = event.u16; + return true; + case Event::Type::SetStepSpeed: + m_stepSpeed = event.u8; + if(event.Byte1()) + m_stepsRemain = m_stepSpeed - 1; + return false; + case Event::Type::JumpMarker: + return false; + case Event::Type::SampleOffset: + case Event::Type::SampleOffsetAdd: + case Event::Type::SampleOffsetSub: + { + int64 pos = event.Value24Bit(); + if(event.type == Event::Type::SampleOffsetAdd) + pos += chn.position.GetInt(); + else if(event.type == Event::Type::SampleOffsetSub) + pos = chn.position.GetInt() - pos; + else + pos += m_ftmSampleStart; + chn.position.Set(std::min(chn.nLength, mpt::saturate_cast(pos)), 0); + } + return false; + case Event::Type::SetLoopCounter: + if(!m_loopCount || event.u8) + m_loopCount = 1 + std::min(event.u16, uint16(0xFFFE)); + return false; + case Event::Type::EvaluateLoopCounter: + if(m_loopCount > 1) + m_nextRow = event.u16; + if(m_loopCount) + m_loopCount--; + return false; + case Event::Type::NoteCut: + chn.nFadeOutVol = 0; + chn.dwFlags.set(CHN_NOTEFADE); + return false; + + case Event::Type::GTK_KeyOff: + m_gtkKeyOffOffset = event.u16; + return false; + case Event::Type::GTK_SetVolume: + m_volumeFactor = event.u16; + chn.dwFlags.set(CHN_FASTVOLRAMP); + return false; + case Event::Type::GTK_SetPitch: + m_gtkPitch = event.u16; + m_linearPitchFactor = TranslateGT2Pitch(event.u16); + m_periodAdd = 0; + return false; + case Event::Type::GTK_SetPanning: + m_panning = event.u16; + return false; + case Event::Type::GTK_SetVolumeStep: + m_gtkVolumeStep = event.i16; + return false; + case Event::Type::GTK_SetPitchStep: + m_gtkPitchStep = event.i16; + return false; + case Event::Type::GTK_SetPanningStep: + m_gtkPanningStep = event.i16; + return false; + case Event::Type::GTK_SetSpeed: + m_gtkSpeed = m_gtkSpeedRemain = event.u8; + return false; + case Event::Type::GTK_EnableTremor: + m_flags.set(kGTKTremorEnabled, event.u8 != 0); + return false; + case Event::Type::GTK_SetTremorTime: + if(event.Byte0()) + m_gtkTremorOnTime = event.Byte0(); + if(event.Byte1()) + m_gtkTremorOffTime = event.Byte1(); + m_gtkTremorPos = 0; + return false; + case Event::Type::GTK_EnableTremolo: + m_flags.set(kGTKTremoloEnabled, event.u8 != 0); + m_gtkVibratoPos = 0; + if(!m_gtkVibratoWidth) + m_gtkVibratoWidth = 8; + if(!m_gtkVibratoSpeed) + m_gtkVibratoSpeed = 16; + return false; + case Event::Type::GTK_EnableVibrato: + m_flags.set(kGTKVibratoEnabled, event.u8 != 0); + m_periodFreqSlide = 0; + m_gtkVibratoPos = 0; + if(!m_gtkVibratoWidth) + m_gtkVibratoWidth = 3; + if(!m_gtkVibratoSpeed) + m_gtkVibratoSpeed = 8; + return false; + case Event::Type::GTK_SetVibratoParams: + if(event.Byte0()) + m_gtkVibratoWidth = event.Byte0(); + if(event.Byte1()) + m_gtkVibratoSpeed = event.Byte1(); + return false; + + case Event::Type::Puma_SetWaveform: + m_pumaWaveform = m_pumaStartWaveform = event.Byte0() + 1; + if(event.Byte0() < 10) + { + m_pumaWaveformStep = 0; + } else + { + m_pumaWaveformStep = static_cast(event.Byte1()); + m_pumaEndWaveform = event.Byte2() + m_pumaStartWaveform; + } + ChannelSetSample(chn, sndFile, m_pumaWaveform); + return false; + case Event::Type::Puma_VolumeRamp: + m_ticksRemain = event.Byte2(); + m_volumeAdd = static_cast(event.Byte0() * 256 - 16384); + chn.dwFlags.set(CHN_FASTVOLRAMP); + return true; + case Event::Type::Puma_StopVoice: + chn.Stop(); + m_nextRow = STOP_ROW; + return true; + case Event::Type::Puma_SetPitch: + m_linearPitchFactor = event.i8 * 8; + m_periodAdd = 0; + m_ticksRemain = std::max(event.Byte2(), uint8(1)) - 1; + return true; + case Event::Type::Puma_PitchRamp: + m_linearPitchFactor = 0; + m_periodAdd = event.i8 * 4; + m_ticksRemain = std::max(event.Byte2(), uint8(1)) - 1; + return true; + + case Event::Type::Mupp_SetWaveform: + ChannelSetSample(chn, sndFile, static_cast(32 + event.Byte0() * 28 + event.Byte1())); + m_volumeFactor = static_cast(std::min(event.Byte2() & 0x7F, 64) * 256u); + chn.dwFlags.set(CHN_FASTVOLRAMP); + return true; + + case Event::Type::MED_DefineArpeggio: + if(!event.u16) + return false; + m_nextRow = m_currentRow + event.u16; + m_medArpOffset = m_currentRow; + m_medArpPos = 0; + return true; + case Event::Type::MED_JumpScript: + if(event.u8 < chn.synthState.states.size() && chn.pModInstrument && event.u8 < chn.pModInstrument->synth.m_scripts.size()) + { + chn.synthState.states[event.u8].JumpToPosition(chn.pModInstrument->synth.m_scripts[event.u8], event.u16); + chn.synthState.states[event.u8].m_stepsRemain = 0; + } + return false; + case Event::Type::MED_SetEnvelope: + if(event.Byte2()) + m_medVolumeEnv = (event.Byte0() & 0x3F) | (event.Byte1() ? 0x80 : 0x00); + else + m_medVibratoEnvelope = event.Byte0(); + m_medVolumeEnvPos = 0; + return false; + case Event::Type::MED_SetVolume: + m_volumeFactor = event.u8 * 256u; + chn.dwFlags.set(CHN_FASTVOLRAMP); + return true; + case Event::Type::MED_SetWaveform: + if(chn.pModInstrument) + ChannelSetSample(chn, sndFile, chn.pModInstrument->Keyboard[NOTE_MIDDLEC - NOTE_MIN] + event.u8); + return true; + case Event::Type::MED_SetVibratoSpeed: + m_medVibratoSpeed = event.u8; + return false; + case Event::Type::MED_SetVibratoDepth: + m_medVibratoDepth = event.u8; + return false; + case Event::Type::MED_SetVolumeStep: + m_medVolumeStep = static_cast(event.i16 * 256); + return false; + case Event::Type::MED_SetPeriodStep: + m_medPeriodStep = static_cast(event.i16 * 4); + return false; + case Event::Type::MED_HoldDecay: + m_medHold = event.u8; + m_medDecay = event.u16; + return false; + + case Event::Type::FTM_SetCondition: + { + MPT_ASSERT(!sndFile.PeriodsAreFrequencies()); + const int32 threshold = (event.u8 < 3) ? int32_max - TranslateFTMPitch(event.u16, chn, sndFile) : event.u16; + const int32 compare = (event.u8 < 3) ? int32_max - chn.nPeriod : chn.nGlobalVol; + switch(event.u8 % 3u) + { + case 0: m_flags.set(kJumpConditionSet, compare == threshold); break; + case 1: m_flags.set(kJumpConditionSet, compare < threshold); break; + case 2: m_flags.set(kJumpConditionSet, compare > threshold); break; + } + } + return false; + case Event::Type::FTM_SetInterrupt: + if(event.u8 & 0x01) m_ftmPitchChangeJump = event.u16; + if(event.u8 & 0x02) m_ftmVolumeChangeJump = event.u16; + if(event.u8 & 0x04) m_ftmSampleChangeJump = event.u16; + if(event.u8 & 0x08) m_ftmReleaseJump = event.u16; + if(event.u8 & 0x10) m_ftmPortamentoJump = event.u16; + if(event.u8 & 0x20) m_ftmVolumeDownJump = event.u16; + return false; + case Event::Type::FTM_PlaySample: + if(chn.nNewIns > 0 && chn.nNewIns <= sndFile.GetNumSamples()) + chn.pModSample = &sndFile.GetSample(chn.nNewIns); + if(chn.pModSample) + { + const ModSample &sample = *chn.pModSample; + chn.nVolume = sample.nVolume; + chn.UpdateInstrumentVolume(&sample, nullptr); + chn.nC5Speed = sample.nC5Speed; + chn.dwFlags = (chn.dwFlags & (CHN_CHANNELFLAGS ^ CHN_NOTEFADE)) | sample.uFlags; + chn.nLength = chn.pModSample->uFlags[CHN_LOOP] ? sample.nLoopEnd : sample.nLength; + chn.nLoopStart = sample.nLoopStart; + chn.nLoopEnd = sample.nLoopEnd; + } + chn.position.Set(0); + return false; + case Event::Type::FTM_SetPitch: + chn.nPeriod = TranslateFTMPitch(event.u16 * 2, chn, sndFile); + return false; + case Event::Type::FTM_SetDetune: + // Detune always applies to the first channel of a channel pair (and only if the other channel is playing a sample) + states.states[channel & ~1].m_ftmDetune = static_cast(event.u16 * -8); + return false; + case Event::Type::FTM_AddDetune: + states.states[channel & ~1].m_ftmDetune -= static_cast(event.i16 * 8); + return false; + case Event::Type::FTM_AddPitch: + if(event.i16) + { + sndFile.DoFreqSlide(chn, chn.nPeriod, event.i16 * 8); + const int32 limit = TranslateFTMPitch((event.i16 < 0) ? 0 : 0x21E, chn, sndFile); + if((event.i16 > 0) == sndFile.PeriodsAreFrequencies()) + chn.nPeriod = std::min(chn.nPeriod, limit); + else + chn.nPeriod = std::max(chn.nPeriod, limit); + } + return false; + case Event::Type::FTM_SetVolume: + chn.nGlobalVol = std::min(event.u8, uint8(64)); + chn.dwFlags.set(CHN_FASTVOLRAMP); + return false; + case Event::Type::FTM_AddVolume: + chn.nGlobalVol = static_cast(std::clamp(chn.nGlobalVol + event.i16, 0, 64)); + return false; + case Event::Type::FTM_SetSample: + chn.swapSampleIndex = event.u8 + 1; + return false; + case Event::Type::FTM_SetSampleStart: + // Documentation says this should be in words, but it really appears to work with bytes. + // The relative variants appear to be completely broken. + if(event.u8 == 1) + m_ftmSampleStart += std::min(static_cast(event.u16), Util::MaxValueOfType(m_ftmSampleStart) - m_ftmSampleStart); + else if(event.u8 == 2) + m_ftmSampleStart -= std::min(static_cast(event.u16), m_ftmSampleStart); + else + m_ftmSampleStart = event.u16 * 2u; + return false; + case Event::Type::FTM_SetOneshotLength: + if(chn.pModSample) + { + const SmpLength loopLength = chn.nLoopEnd - chn.nLoopStart; + int64 loopStart = event.u16 * 2; + if(event.u8 == 1) + loopStart += chn.nLoopStart; + else if(event.u8 == 2) + loopStart = chn.nLoopStart - loopStart; + loopStart = std::clamp(loopStart, int64(0), static_cast(chn.pModSample->nLength)); + chn.nLoopStart = static_cast(loopStart); + chn.nLoopEnd = chn.nLoopStart + loopLength; + LimitMax(chn.nLoopEnd, chn.pModSample->nLength); + chn.nLength = chn.nLoopEnd; + chn.dwFlags.set(CHN_LOOP, chn.nLoopEnd > chn.nLoopStart); + if(chn.position.GetUInt() >= chn.nLength && chn.dwFlags[CHN_LOOP]) + chn.position.SetInt(chn.nLoopStart); + } + return false; + case Event::Type::FTM_SetRepeatLength: + if(chn.pModSample) + { + int64 loopEnd = chn.nLoopStart + event.u16 * 2; + if(event.u8 == 1) + loopEnd = chn.nLoopEnd + event.u16 * 2; + else if(event.u8 == 2) + loopEnd = chn.nLoopEnd - event.u16 * 2; + loopEnd = std::clamp(loopEnd, static_cast(chn.nLoopStart), static_cast(chn.pModSample->nLength)); + chn.nLoopEnd = static_cast(loopEnd); + chn.nLength = chn.nLoopEnd; + chn.dwFlags.set(CHN_LOOP, chn.nLoopEnd > chn.nLoopStart); + if(chn.position.GetUInt() >= chn.nLength && chn.dwFlags[CHN_LOOP]) + chn.position.SetInt(chn.nLoopStart); + } + return false; + case Event::Type::FTM_CloneTrack: + if(event.Byte0() < sndFile.GetNumChannels()) + { + const ModChannel &srcChn = playState.Chn[event.Byte0()]; + if(event.Byte1() & (0x01 | 0x08)) + chn.nPeriod = srcChn.nPeriod; + if(event.Byte1() & (0x02 | 0x08)) + chn.nGlobalVol = srcChn.nGlobalVol; + if(event.Byte1() & (0x04 | 0x08)) + { + chn.nNewIns = srcChn.nNewIns; + chn.swapSampleIndex = srcChn.swapSampleIndex; + chn.pModSample = srcChn.pModSample; + chn.position = srcChn.position; + chn.dwFlags = (chn.dwFlags & CHN_CHANNELFLAGS) | (srcChn.dwFlags & CHN_SAMPLEFLAGS); + chn.nLength = srcChn.nLength; + chn.nLoopStart = srcChn.nLoopStart; + chn.nLoopEnd = srcChn.nLoopEnd; + } + if(event.Byte1() & 0x08) + { + // Note: This does not appear to behave entirely as documented. + // When the command is triggered, it copies frequency, volume, sample and the state of running slide commands. + // Running LFOs are not copied. But any notes and effects (including newly triggered LFOs) on the source track on following rows are copied. + // There appears to be no way to stop this cloning once it has started. + // As no FTM in the wild makes use of this command, we will glance over this ugly detail. + chn.position = srcChn.position; + chn.portamentoSlide = srcChn.portamentoSlide; + chn.nPortamentoDest = srcChn.nPortamentoDest; + chn.volSlideDownStart = srcChn.volSlideDownStart; + chn.volSlideDownTotal = srcChn.volSlideDownTotal; + chn.volSlideDownRemain = srcChn.volSlideDownRemain; + chn.autoSlide.SetActive(AutoSlideCommand::TonePortamentoWithDuration, srcChn.autoSlide.IsActive(AutoSlideCommand::TonePortamentoWithDuration)); + chn.autoSlide.SetActive(AutoSlideCommand::VolumeDownWithDuration, srcChn.autoSlide.IsActive(AutoSlideCommand::VolumeDownWithDuration)); + } + } + return false; + case Event::Type::FTM_StartLFO: + { + auto &lfo = m_ftmLFO[event.Byte0() & 3]; + lfo.targetWaveform = event.Byte1(); + lfo.speed = lfo.depth = lfo.position = 0; + } + return false; + case Event::Type::FTM_LFOAddSub: + { + auto &lfo = m_ftmLFO[event.Byte0() & 3]; + int factor = (event.Byte0() & 4) ? -1 : 1; + lfo.speed = std::min(mpt::saturate_cast(lfo.speed + event.Byte1() * factor), uint8(0xBF)); + lfo.depth = std::min(mpt::saturate_cast(lfo.depth + event.Byte2() * factor), uint8(0x7F)); + } + return false; + case Event::Type::FTM_SetWorkTrack: + if(event.Byte0() == uint8_max) + { + m_ftmWorkTrack = 0; + } else if(const bool isRelative = event.Byte1() != 0; isRelative && event.Byte0()) + { + if(!m_ftmWorkTrack) + m_ftmWorkTrack = static_cast(channel + 1); + m_ftmWorkTrack = static_cast((m_ftmWorkTrack - 1u + event.Byte0()) % sndFile.GetNumChannels() + 1); + } else if(!isRelative) + { + m_ftmWorkTrack = event.Byte0() + 1; + } + return false; + case Event::Type::FTM_SetGlobalVolume: + playState.m_nGlobalVolume = event.u16; + return false; + case Event::Type::FTM_SetTempo: + playState.m_nMusicTempo = TEMPO(1777517.482 / std::clamp(event.u16, uint16(0x1000), uint16(0x4FFF))); + return false; + case Event::Type::FTM_SetSpeed: + if(event.u16) + playState.m_nMusicSpeed = event.u16; + else + playState.m_nMusicSpeed = uint16_max; + return false; + case Event::Type::FTM_SetPlayPosition: + if(ORDERINDEX playPos = sndFile.Order().FindOrder(event.u16, event.u16); playPos != ORDERINDEX_INVALID) + { + playState.m_nNextOrder = playPos; + playState.m_nNextRow = event.u8; + } + return false; + + case Event::Type::FC_SetWaveform: + { + uint8 waveform = event.Byte1() + 1; + if(event.Byte0() == 0xE9) + waveform += static_cast(event.Byte2() * 10 + 90); + ChannelSetSample(chn, sndFile, waveform, event.Byte0() == 0xE4); + } + return false; + case Event::Type::FC_SetPitch: + m_fcPitch = event.i8; + return true; + case Event::Type::FC_SetVibrato: + m_fcVibratoSpeed = event.Byte0(); + m_fcVibratoDepth = event.Byte1(); + if(!m_flags[kFCVibratoDelaySet]) + { + m_flags.set(kFCVibratoDelaySet); + m_fcVibratoDelay = event.Byte2(); + m_fcVibratoValue = m_fcVibratoDepth; + } + return false; + case Event::Type::FC_PitchSlide: + m_fcPitchBendSpeed = event.Byte0(); + m_fcPitchBendRemain = event.Byte1(); + return false; + case Event::Type::FC_VolumeSlide: + m_fcVolumeBendSpeed = event.Byte0(); + m_fcVolumeBendRemain = event.Byte1(); + HandleFCVolumeBend(true); + return true; + } + + MPT_ASSERT_NOTREACHED(); + return false; +} + + +void InstrumentSynth::States::State::EvaluateRunningEvent(const Event &event) +{ + switch(event.type) + { + case Event::Type::Puma_VolumeRamp: + if(event.Byte2() > 0) + m_volumeAdd = static_cast((event.Byte1() + Util::muldivr(event.Byte0() - event.Byte1(), m_ticksRemain, event.Byte2())) * 256 - 16384); + break; + case Event::Type::Puma_PitchRamp: + if(event.Byte2() > 0) + m_periodAdd = static_cast((static_cast(event.Byte1()) + Util::muldivr(static_cast(event.Byte0()) - static_cast(event.Byte1()), m_ticksRemain, event.Byte2())) * 4); + break; + default: + break; + } +} + + +void InstrumentSynth::States::State::HandleFTMInterrupt(uint16 &target, const bool condition) +{ + if(target == STOP_ROW || !condition) + return; + m_nextRow = target; + m_ticksRemain = 0; + m_stepsRemain = 0; + target = STOP_ROW; +} + + +bool InstrumentSynth::States::State::HandleFCVolumeBend(bool forceRun) +{ + if(!m_fcVolumeBendRemain && !forceRun) + return false; + + m_flags.flip(kFCVolumeBendStep); + if(m_flags[kFCVolumeBendStep]) + { + m_fcVolumeBendRemain--; + int32 target = m_volumeFactor + m_fcVolumeBendSpeed * 256; + if(target < 0 || target >= 32768) + m_fcVolumeBendRemain = 0; + m_volumeFactor = static_cast(std::clamp(target, int32(0), int32(16384))); + } + return true; +} + + +void GlobalScriptState::Initialize(const CSoundFile &sndFile) +{ + if(!sndFile.m_globalScript.empty()) + states.assign(sndFile.GetNumChannels(), {}); +} + + +void GlobalScriptState::NextTick(PlayState &playState, const CSoundFile &sndFile) +{ + if(sndFile.m_globalScript.empty()) + return; + states.resize(sndFile.GetNumChannels()); + for(CHANNELINDEX chn = 0; chn < sndFile.GetNumChannels(); chn++) + { + auto &state = states[chn]; + auto &modChn = playState.Chn[chn]; + if(modChn.rowCommand.command == CMD_MED_SYNTH_JUMP && !playState.m_nTickCount) + states[chn].JumpToPosition(sndFile.m_globalScript, modChn.rowCommand.param); + state.NextTick(sndFile.m_globalScript, playState, chn, sndFile, *this); + } +} + + +void GlobalScriptState::ApplyChannelState(PlayState &playState, CHANNELINDEX chn, int32 &period, const CSoundFile &sndFile) +{ + if(sndFile.m_globalScript.empty()) + return; + for(CHANNELINDEX s = 0; s < states.size(); s++) + { + if(states[s].FTMRealChannel(s, sndFile) == chn) + { + states[s].ApplyChannelState(playState.Chn[chn], period, sndFile); + } + } +} + + +void InstrumentSynth::Sanitize() +{ + for(auto &script : m_scripts) + { + if(script.size() >= States::State::STOP_ROW) + script.resize(States::State::STOP_ROW - 1); + } +} + + +OPENMPT_NAMESPACE_END diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/InstrumentSynth.h b/Frameworks/OpenMPT/OpenMPT/soundlib/InstrumentSynth.h new file mode 100644 index 000000000..657674a73 --- /dev/null +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/InstrumentSynth.h @@ -0,0 +1,280 @@ +/* + * InstrumentSynth.h + * ----------------- + * Purpose: "Script" / "Synth" processor for various file formats (MED, GT2, Puma, His Master's Noise, Face The Music, Future Composer) + * Notes : (currently none) + * Authors: OpenMPT Devs + * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. + */ + + +#pragma once + +#include "openmpt/all/BuildSettings.hpp" +#include "Snd_defs.h" + +#include + +OPENMPT_NAMESPACE_BEGIN + +class CSoundFile; +struct ModChannel; +struct PlayState; + +struct InstrumentSynth +{ + struct Event + { + enum class Type : uint8 + { + StopScript, // No parameter + Jump, // Parameter: Event index (uint16) + JumpIfTrue, // Parameter: Event index (uint16) + Delay, // Parameter: Number of ticks (uint16) + SetStepSpeed, // Parameter: Speed (uint8), update speed now? (bool) + JumpMarker, // Parameter: Marker ID (uint16) + SampleOffset, // Parameter: Offset (uint32) + SampleOffsetAdd, // Parameter: Offset (uint32) + SampleOffsetSub, // Parameter: Offset (uint32) + SetLoopCounter, // Parameter: Count (uint16), force? (bool) + EvaluateLoopCounter, // Parameter: Event index (uint16) + NoteCut, // No parameter + + GTK_KeyOff, // Parameter: Jump target once key is released (uint16) + GTK_SetVolume, // Parameter: Volume (uint16) + GTK_SetPitch, // Parameter: Pitch (uint16) + GTK_SetPanning, // Parameter: Panning (uint16) + GTK_SetVolumeStep, // Parameter: Step size (int16) + GTK_SetPitchStep, // Parameter: Step size (int16) + GTK_SetPanningStep, // Parameter: Step size (int16) + GTK_SetSpeed, // Parameter: Speed (uint8) + GTK_EnableTremor, // Parameter: Enable (uint8) + GTK_SetTremorTime, // Parameter: On time (uint8), off time (uint8) + GTK_EnableTremolo, // Parameter: Enable (uint8) + GTK_EnableVibrato, // Parameter: Enable (uint8) + GTK_SetVibratoParams, // Parameter: Width (uint8), speed (uint8) + + Puma_SetWaveform, // Parameter: Waveform (uint8), wavestorm step (uint8), number of waveforms to cycle (uint8) + Puma_VolumeRamp, // Parameter: Start volume (uint8), end volume (uint8), number of ticks (uint8) + Puma_StopVoice, // No parameter + Puma_SetPitch, // Parameter: Pitch offset (int8), (uint8), number of ticks (uint8) + Puma_PitchRamp, // Parameter: Start pitch offset (int8), end pitch offset (int8), number of ticks (uint8) + + Mupp_SetWaveform, // Parameter: Source instrument (uint8), waveform (uint8), volume (uint8) + + MED_DefineArpeggio, // Parameter: Arpeggio note (uint8), arp length or 0 if it's not the first note (uint16) + MED_JumpScript, // Parameter: Script index (uint8), jump target (uint16 - JumpMarker ID, not event index!) + MED_SetEnvelope, // Parameter: Envelope index (uint8), loop on/off (uint8), is volume envelope (uint8) + MED_SetVolume, // Parameter: Volume (uint8) + MED_SetWaveform, // Parameter: Waveform (uint8) + MED_SetVibratoSpeed, // Parameter: Speed (uint8) + MED_SetVibratoDepth, // Parameter: Depth (uint8) + MED_SetVolumeStep, // Parameter: Volume step (int16) + MED_SetPeriodStep, // Parameter: Period step (int16) + MED_HoldDecay, // Parameter: Hold time (uint8), decay point (uint16) + + FTM_PlaySample, // No parameter + FTM_SetPitch, // Parameter: New pitch (uint16) + FTM_AddPitch, // Parameter: Pitch amount (int16) + FTM_SetDetune, // Parameter: Detune amount (uint16) + FTM_AddDetune, // Parameter: Detune amount (int16) + FTM_SetVolume, // Parameter: Channel volume (uint8) + FTM_AddVolume, // Parameter: Volume amount (int16) + FTM_SetSample, // Parameter: New sample (uint8) + FTM_SetCondition, // Parameter: Pitch/volume threshold (uint16), condition type (uint8) + FTM_SetInterrupt, // Parameter: Jump target (uint16), interrupt type (uint8) + FTM_SetSampleStart, // Parameter: Offset (uint16), modification type (uint8) + FTM_SetOneshotLength, // Parameter: Length (uint16), modification type (uint8) + FTM_SetRepeatLength, // Parameter: Length (uint16), modification type (uint8) + FTM_CloneTrack, // Parameter: Track (uint8), properties (uint8) + FTM_StartLFO, // Parameter: LFO index (uint8), target/waveform (uint8) + FTM_LFOAddSub, // Parameter: LFO/addSub (uint8), speed (uint8), depth (uint8) + FTM_SetWorkTrack, // Parameter: Channel index (uint8), is relative? (bool) + FTM_SetGlobalVolume, // Parameter: Global volume (uint16) + FTM_SetTempo, // Parameter: Tempo (uint16) + FTM_SetSpeed, // Parameter: Speed (uint16) + FTM_SetPlayPosition, // Parameter: Pattern to play (uint16), row in pattern (uint8) + + FC_SetWaveform, // Parameter: Command type (uint8), waveform (uint8), sample pack (uint8) + FC_SetPitch, // Parameter: Pitch (int8) + FC_SetVibrato, // Parameter: Speed (uint8), depth (uint8), delay (uint8) + FC_PitchSlide, // Parameter: Speed (uint8), time (uint8) + FC_VolumeSlide, // Parameter: Speed (uint8), time (uint8) + }; + + static constexpr Type JumpEvents[] = + { + Type::Jump, Type::JumpIfTrue, Type::EvaluateLoopCounter, + Type::GTK_KeyOff, + Type::MED_HoldDecay, + Type::FTM_SetInterrupt, + }; + + Type type = Type::StopScript; + union + { + uint8 u8 = 0; + int8 i8; + }; + union + { + uint16 u16; + int16 i16; + std::array bytes = {{}}; + }; + + static constexpr Event StopScript() noexcept { return Event{Type::StopScript}; } + static constexpr Event Jump(uint16 target) noexcept { return Event{Type::Jump, target}; } + static constexpr Event JumpIfTrue(uint16 target) noexcept { return Event{Type::JumpIfTrue, target}; } + static constexpr Event Delay(uint16 ticks) noexcept { return Event{Type::Delay, ticks}; } + static constexpr Event SetStepSpeed(uint8 speed, bool updateNow) noexcept { return Event{Type::SetStepSpeed, speed, uint8(updateNow ? 1 : 0)}; } + static constexpr Event JumpMarker(uint16 data) noexcept { return Event{Type::JumpMarker, data}; } + static constexpr Event SampleOffset(uint32 offset) noexcept { return Event24Bit(Type::SampleOffset, offset); } + static constexpr Event SampleOffsetAdd(uint32 offset) noexcept { return Event24Bit(Type::SampleOffsetAdd, offset); } + static constexpr Event SampleOffsetSub(uint32 offset) noexcept { return Event24Bit(Type::SampleOffsetSub, offset); } + static constexpr Event SetLoopCounter(uint16 count, bool force) noexcept { return Event{Type::SetLoopCounter, count, uint8(force ? 1 : 0)}; } + static constexpr Event EvaluateLoopCounter(uint16 target) noexcept { return Event{Type::EvaluateLoopCounter, target}; } + static constexpr Event NoteCut() noexcept { return Event{Type::NoteCut}; } + + static constexpr Event GTK_KeyOff(uint16 target) noexcept { return Event{Type::GTK_KeyOff, target}; } + static constexpr Event GTK_SetVolume(uint16 volume) noexcept { return Event{Type::GTK_SetVolume, volume}; } + static constexpr Event GTK_SetPitch(uint16 pitch) noexcept { return Event{Type::GTK_SetPitch, pitch}; } + static constexpr Event GTK_SetPanning(uint16 panning) noexcept { return Event{Type::GTK_SetPanning, panning}; } + static constexpr Event GTK_SetVolumeStep(int16 stepSize) noexcept { return Event{Type::GTK_SetVolumeStep, stepSize}; } + static constexpr Event GTK_SetPitchStep(int16 stepSize) noexcept { return Event{Type::GTK_SetPitchStep, stepSize}; } + static constexpr Event GTK_SetPanningStep(int16 stepSize) noexcept { return Event{Type::GTK_SetPanningStep, stepSize}; } + static constexpr Event GTK_SetSpeed(uint8 speed) noexcept { return Event{Type::GTK_SetSpeed, speed}; } + static constexpr Event GTK_EnableTremor(uint8 enable) noexcept { return Event{Type::GTK_EnableTremor, enable}; } + static constexpr Event GTK_SetTremorTime(uint8 onTime, uint8 offTime) noexcept { return Event{Type::GTK_SetTremorTime, onTime, offTime}; } + static constexpr Event GTK_EnableTremolo(uint8 enable) noexcept { return Event{Type::GTK_EnableTremolo, enable}; } + static constexpr Event GTK_EnableVibrato(uint8 enable) noexcept { return Event{Type::GTK_EnableVibrato, enable}; } + static constexpr Event GTK_SetVibratoParams(uint8 width, uint8 speed) noexcept { return Event{Type::GTK_SetVibratoParams, width, speed}; } + + static constexpr Event Puma_SetWaveform(uint8 waveform, uint8 step, uint8 count) noexcept { return Event{Type::Puma_SetWaveform, waveform, step, count}; } + static constexpr Event Puma_VolumeRamp(uint8 startVol, uint8 endVol, uint8 ticks) noexcept { return Event{Type::Puma_VolumeRamp, startVol, endVol, ticks}; } + static constexpr Event Puma_StopVoice() noexcept { return Event{Type::Puma_StopVoice}; } + static constexpr Event Puma_SetPitch(int8 pitchOffset, uint8 ticks) noexcept { return Event{Type::Puma_SetPitch, pitchOffset, uint8(0), ticks}; } + static constexpr Event Puma_PitchRamp(int8 startPitch, int8 endPitch, uint8 ticks) noexcept { return Event{Type::Puma_PitchRamp, startPitch, endPitch, ticks}; } + + static constexpr Event Mupp_SetWaveform(uint8 instr, uint8 waveform, uint8 volume) noexcept { return Event{Type::Mupp_SetWaveform, instr, waveform, volume}; } + + static constexpr Event MED_DefineArpeggio(uint8 note, uint16 noteCount) noexcept { return Event{Type::MED_DefineArpeggio, noteCount, note}; } + static constexpr Event MED_JumpScript(uint8 scriptIndex, uint16 target) noexcept { return Event{Type::MED_JumpScript, target, scriptIndex}; } + static constexpr Event MED_SetEnvelope(uint8 envelope, bool loop, bool volumeEnv) noexcept { return Event{Type::MED_SetEnvelope, envelope, uint8(loop ? 1 : 0), uint8(volumeEnv ? 1 : 0)}; } + static constexpr Event MED_SetVolume(uint8 volume) noexcept { return Event{Type::MED_SetVolume, volume}; } + static constexpr Event MED_SetWaveform(uint8 waveform) noexcept { return Event{Type::MED_SetWaveform, waveform}; } + static constexpr Event MED_SetVibratoSpeed(uint8 depth) noexcept { return Event{Type::MED_SetVibratoSpeed, depth}; } + static constexpr Event MED_SetVibratoDepth(uint8 depth) noexcept { return Event{Type::MED_SetVibratoDepth, depth}; } + static constexpr Event MED_SetVolumeStep(int16 volumeStep) noexcept { return Event{Type::MED_SetVolumeStep, volumeStep}; } + static constexpr Event MED_SetPeriodStep(int16 periodStep) noexcept { return Event{Type::MED_SetPeriodStep, periodStep}; } + static constexpr Event MED_HoldDecay(uint8 hold, uint16 decay) noexcept { return Event{Type::MED_HoldDecay, decay, hold}; } + + static constexpr Event FTM_SetCondition(uint16 threshold, uint8 condition) noexcept { return Event{Type::FTM_SetCondition, threshold, condition}; } + static constexpr Event FTM_SetInterrupt(uint16 target, uint8 type) noexcept { return Event{Type::FTM_SetInterrupt, target, type}; } + static constexpr Event FTM_PlaySample() noexcept { return Event{Type::FTM_PlaySample}; } + static constexpr Event FTM_SetPitch(uint16 pitch) noexcept { return Event{Type::FTM_SetPitch, pitch}; } + static constexpr Event FTM_AddPitch(int16 pitch) noexcept { return Event{Type::FTM_AddPitch, pitch}; } + static constexpr Event FTM_SetDetune(uint16 detune) noexcept { return Event{Type::FTM_SetDetune, detune}; } + static constexpr Event FTM_AddDetune(int16 detune) noexcept { return Event{Type::FTM_AddDetune, detune}; } + static constexpr Event FTM_SetVolume(uint8 volume) noexcept { return Event{Type::FTM_SetVolume, volume}; } + static constexpr Event FTM_AddVolume(int16 volume) noexcept { return Event{Type::FTM_AddVolume, volume}; } + static constexpr Event FTM_SetSample(uint8 sample) noexcept { return Event{Type::FTM_SetSample, sample}; } + static constexpr Event FTM_SetSampleStart(uint16 offset, uint8 type) noexcept { return Event{Type::FTM_SetSampleStart, offset, type}; } + static constexpr Event FTM_SetOneshotLength(uint16 length, uint8 type) noexcept { return Event{Type::FTM_SetOneshotLength, length, type}; } + static constexpr Event FTM_SetRepeatLength(uint16 length, uint8 type) noexcept { return Event{ Type::FTM_SetRepeatLength, length, type }; } + static constexpr Event FTM_CloneTrack(uint8 track, uint8 properties) noexcept { return Event{Type::FTM_CloneTrack, track, properties}; } + static constexpr Event FTM_StartLFO(uint8 lfo, uint8 targetWaveform) noexcept { return Event{Type::FTM_StartLFO, lfo, targetWaveform, 0}; } + static constexpr Event FTM_LFOAddSub(uint8 lfoAddSub, uint8 speed, uint8 depth) noexcept { return Event{Type::FTM_LFOAddSub, lfoAddSub, speed, depth}; } + static constexpr Event FTM_SetWorkTrack(uint8 track, bool relative) noexcept { return Event{ Type::FTM_SetWorkTrack, track, uint8(relative ? 1 : 0), 0}; } + static constexpr Event FTM_SetGlobalVolume(uint16 globalVolume) noexcept { return Event{Type::FTM_SetGlobalVolume, globalVolume}; } + static constexpr Event FTM_SetTempo(uint16 tempo) noexcept { return Event{Type::FTM_SetTempo, tempo}; } + static constexpr Event FTM_SetSpeed(uint16 speed) noexcept { return Event{Type::FTM_SetSpeed, speed}; } + static constexpr Event FTM_SetPlayPosition(uint16 pattern, uint8 row) noexcept { return Event{Type::FTM_SetPlayPosition, pattern, row}; } + + static constexpr Event FC_SetWaveform(uint8 command, uint8 waveform, uint8 samplePack) noexcept { return Event{Type::FC_SetWaveform, command, waveform, samplePack}; } + static constexpr Event FC_SetPitch(int8 pitch) noexcept { return Event{Type::FC_SetPitch, pitch}; } + static constexpr Event FC_SetVibrato(uint8 speed, uint8 depth, uint8 delay) noexcept { return Event{Type::FC_SetVibrato, speed, depth, delay}; } + static constexpr Event FC_PitchSlide(uint8 speed, uint8 time) noexcept { return Event{Type::FC_PitchSlide, speed, time}; } + static constexpr Event FC_VolumeSlide(uint8 speed, uint8 time) noexcept { return Event{Type::FC_VolumeSlide, speed, time}; } + + constexpr Event() noexcept : u8{}, u16{} {} + constexpr Event(const Event &other) noexcept = default; + constexpr Event(Event &&other) noexcept = default; + constexpr Event &operator=(const Event &other) noexcept = default; + constexpr Event &operator=(Event &&other) noexcept = default; + + MPT_CONSTEXPR20_FUN bool IsJumpEvent() const noexcept + { + return mpt::contains(JumpEvents, type); + } + + template + void FixupJumpTarget(TMap &offsetToIndexMap) + { + if(!IsJumpEvent()) + return; + if(auto it = offsetToIndexMap.lower_bound(u16); it != offsetToIndexMap.end()) + u16 = it->second; + else + u16 = uint16_max; + } + + constexpr uint8 Byte0() const noexcept { return u8; } + constexpr uint8 Byte1() const noexcept { return bytes[0]; } + constexpr uint8 Byte2() const noexcept { return bytes[1]; } + constexpr uint32 Value24Bit() const noexcept { return Byte0() | (Byte1() << 8) | (Byte2() << 16); } + + protected: + constexpr Event(Type type, uint8 b1, uint8 b2, uint8 b3) noexcept : type{type}, u8{b1}, bytes{b2, b3} {} + constexpr Event(Type type, int8 b1, uint8 b2, uint8 b3) noexcept : type{type}, i8{b1}, bytes{b2, b3} {} + constexpr Event(Type type, int8 b1, int8 b2, uint8 b3) noexcept : type{type}, i8{b1}, bytes{static_cast(b2), b3} {} + constexpr Event(Type type, uint8 b1, uint8 b2) noexcept : type{type}, u8{b1}, bytes{b2, 0} {} + constexpr Event(Type type, uint16 u16, uint8 u8) noexcept : type{type}, u8{u8}, u16{u16} {} + constexpr Event(Type type, uint16 u16) noexcept : type{type}, u8{0}, u16{u16} {} + constexpr Event(Type type, int16 i16) noexcept : type{type}, u8{}, i16{i16} {} + constexpr Event(Type type, uint8 u8) noexcept : type{type}, u8{u8}, u16{} {} + constexpr Event(Type type, int8 i8) noexcept : type{type}, i8{i8}, u16{} {} + explicit constexpr Event(Type type) noexcept : type{type}, u8{}, u16{} {} + + static constexpr Event Event24Bit(Type type, uint32 value) { value = std::min(value, uint32(0xFFFFFF)); return Event{type, static_cast(value & 0xFF), static_cast(value >> 8), static_cast(value >> 16)}; } + }; + + using Events = std::vector; + + class States + { + public: + struct State; + friend struct State; + + States(); + States(const States &other); + States(States &&other) noexcept; + virtual ~States(); + States& operator=(const States &other); + States& operator=(States &&other) noexcept; + + void Stop(); + void NextTick(PlayState &playState, CHANNELINDEX channel, const CSoundFile &sndFile); + void ApplyChannelState(ModChannel &chn, int32 &period, const CSoundFile& sndFile); + + protected: + std::vector states; + }; + + std::vector m_scripts; + + bool HasScripts() const noexcept { return !m_scripts.empty(); } + void Clear() { m_scripts.clear(); } + void Sanitize(); +}; + + +struct GlobalScriptState final : private InstrumentSynth::States +{ + void Initialize(const CSoundFile &sndFile); + void NextTick(PlayState &playState, const CSoundFile &sndFile); + void ApplyChannelState(PlayState &playState, CHANNELINDEX chn, int32 &period, const CSoundFile &sndFile); +}; + +OPENMPT_NAMESPACE_END diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_667.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_667.cpp index f7f78226f..675bdb995 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_667.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_667.cpp @@ -77,11 +77,10 @@ bool CSoundFile::Read667(FileReader &file, ModLoadingFlags loadFlags) if(loadFlags == onlyVerifyHeader) return true; - InitializeGlobals(MOD_TYPE_S3M); + InitializeGlobals(MOD_TYPE_S3M, 18); m_SongFlags.set(SONG_IMPORTED); - m_nDefaultTempo.Set(150); - m_nDefaultSpeed = fileHeader.speed; - m_nChannels = 18; + Order().SetDefaultTempoInt(150); + Order().SetDefaultSpeed(fileHeader.speed); m_nSamples = 64; ReadOrderFromFile(Order(), file, fileHeader.numOrders); @@ -91,8 +90,6 @@ bool CSoundFile::Read667(FileReader &file, ModLoadingFlags loadFlags) return false; } - InitializeChannels(); - for(SAMPLEINDEX smp = 1; smp <= m_nSamples; smp++) { // Reorder OPL patch bytes (interleave modulator and carrier) @@ -140,14 +137,14 @@ bool CSoundFile::Read667(FileReader &file, ModLoadingFlags loadFlags) { // Instrument auto instr = patData.ReadArray(); - if(instr[0] >= m_nChannels || instr[1] > 63) + if(instr[0] >= GetNumChannels() || instr[1] > 63) return false; rowData[instr[0]].instr = instr[1] + 1; } else if(b == 0xFD) { // Volume auto vol = patData.ReadArray(); - if(vol[0] >= m_nChannels || vol[1] > 63) + if(vol[0] >= GetNumChannels() || vol[1] > 63) return false; rowData[vol[0]].SetVolumeCommand(VOLCMD_VOLUME, 63u - vol[1]); } else if(b == 0xFC) @@ -159,13 +156,13 @@ bool CSoundFile::Read667(FileReader &file, ModLoadingFlags loadFlags) { // Pattern break rowData[0].SetEffectCommand(CMD_PATTERNBREAK, 0); - } else if(b < m_nChannels) + } else if(b < GetNumChannels()) { // Note data uint8 note = patData.ReadUint8(); if(note >= 0x7C) return false; - rowData[b].note = NOTE_MIN + 12 + (note & 0x0F) + (note >> 4) * 12; + rowData[b].note = static_cast(NOTE_MIN + 12 + (note & 0x0F) + (note >> 4) * 12); if(b % 2u) rightChn = true; else @@ -178,7 +175,7 @@ bool CSoundFile::Read667(FileReader &file, ModLoadingFlags loadFlags) } if(leftChn && rightChn) { - for(CHANNELINDEX chn = 0; chn < m_nChannels; chn++) + for(CHANNELINDEX chn = 0; chn < GetNumChannels(); chn++) { ChnSettings[chn].nPan = (chn % 2u) ? 256 : 0; } diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_669.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_669.cpp index 5f88b23d6..ce291df38 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_669.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_669.cpp @@ -130,26 +130,27 @@ bool CSoundFile::Read669(FileReader &file, ModLoadingFlags loadFlags) return true; } - if(!file.CanRead(mpt::saturate_cast(GetHeaderMinimumAdditionalSize(fileHeader)))) + if(!file.CanRead(mpt::saturate_cast(GetHeaderMinimumAdditionalSize(fileHeader)))) { return false; } - InitializeGlobals(MOD_TYPE_669); + InitializeGlobals(MOD_TYPE_669, 8); m_nMinPeriod = 28 << 2; m_nMaxPeriod = 1712 << 3; - m_nDefaultTempo.Set(78); - m_nDefaultSpeed = 4; - m_nChannels = 8; + Order().SetDefaultTempoInt(78); + Order().SetDefaultSpeed(4); m_playBehaviour.set(kPeriodsAreHertz); + m_SongFlags.set(SONG_FASTPORTAS | SONG_AUTO_TONEPORTA); #ifdef MODPLUG_TRACKER // 669 uses frequencies rather than periods, so linear slides mode will sound better in the higher octaves. //m_SongFlags.set(SONG_LINEARSLIDES); #endif // MODPLUG_TRACKER - m_modFormat.formatName = U_("Composer 669"); - m_modFormat.type = U_("669"); - m_modFormat.madeWithTracker = !memcmp(fileHeader.magic, "if", 2) ? UL_("Composer 669") : UL_("UNIS 669"); + const bool isExtended = !memcmp(fileHeader.magic, "JN", 2); + m_modFormat.formatName = UL_("Composer 669"); + m_modFormat.type = UL_("669"); + m_modFormat.madeWithTracker = isExtended ? UL_("UNIS 669") : UL_("Composer 669"); m_modFormat.charset = mpt::Charset::CP437; m_nSamples = fileHeader.samples; @@ -178,7 +179,6 @@ bool CSoundFile::Read669(FileReader &file, ModLoadingFlags loadFlags) // Set up panning for(CHANNELINDEX chn = 0; chn < 8; chn++) { - ChnSettings[chn].Reset(); ChnSettings[chn].nPan = (chn & 1) ? 0xD0 : 0x30; } @@ -194,8 +194,8 @@ bool CSoundFile::Read669(FileReader &file, ModLoadingFlags loadFlags) static constexpr ModCommand::COMMAND effTrans[] = { - CMD_PORTAMENTOUP, // Slide up (param * 80) Hz on every tick - CMD_PORTAMENTODOWN, // Slide down (param * 80) Hz on every tick + CMD_AUTO_PORTAUP, // Slide up (param * 80) Hz on every tick + CMD_AUTO_PORTADOWN, // Slide down (param * 80) Hz on every tick CMD_TONEPORTAMENTO, // Slide to note by (param * 40) Hz on every tick CMD_S3MCMDEX, // Add (param * 80) Hz to sample frequency CMD_VIBRATO, // Add (param * 669) Hz on every other tick @@ -225,44 +225,30 @@ bool CSoundFile::Read669(FileReader &file, ModLoadingFlags loadFlags) if(noteInstr <= 0xFE) { m->volcmd = VOLCMD_VOLUME; - m->vol = ((vol * 64 + 8) / 15); + m->vol = static_cast((vol * 64 + 8) / 15); } if(effParam != 0xFF) - { effect[chn] = effParam; - } - if((effParam & 0x0F) == 0 && effParam != 0x30) - { - // A param value of 0 resets the effect. - effect[chn] = 0xFF; - } if(effect[chn] == 0xFF) - { continue; - } - m->param = effect[chn] & 0x0F; + uint8 command = effect[chn] >> 4; // Weird stuff happening in corehop.669 with effects > 8... they seem to do the same thing as if the high bit wasn't set, but the sample also behaves strangely. - uint8 command = effect[chn] >> 4; - if(command < static_cast(std::size(effTrans))) + if(command < mpt::array_size::size) { -#if MPT_COMPILER_MSVC -#pragma warning(push) -// false-positive -#pragma warning(disable:6385) // Reading invalid data from 'effTrans'. -#endif - m->command = effTrans[command]; -#if MPT_COMPILER_MSVC -#pragma warning(pop) -#endif + m->SetEffectCommand(effTrans[command], effect[chn] & 0x0F); } else { m->command = CMD_NONE; continue; } + // Currently not implemented as auto-slides + if(m->command != CMD_PANNINGSLIDE) + effect[chn] = 0xFF; + // Fix some commands switch(command) { @@ -275,7 +261,6 @@ bool CSoundFile::Read669(FileReader &file, ModLoadingFlags loadFlags) #else m->param |= 0x20; #endif - effect[chn] = 0xFF; break; case 4: @@ -289,7 +274,6 @@ bool CSoundFile::Read669(FileReader &file, ModLoadingFlags loadFlags) case 5: // F - set tempo // TODO: param 0 is a "super fast tempo" in Unis 669 mode (?) - effect[chn] = 0xFF; break; case 6: @@ -308,6 +292,11 @@ bool CSoundFile::Read669(FileReader &file, ModLoadingFlags loadFlags) m->command = CMD_NONE; } break; + + case 7: + // H- slot retrig ("This command rapidly fires 4 slots. The command parameter specifies the speed at which to do it. The speed difference across the values is exponential.") + if(!m->IsNote() || !isExtended) + m->command = CMD_NONE; } } } diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_amf.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_amf.cpp index dd0ac6fd5..3b2fce499 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_amf.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_amf.cpp @@ -116,7 +116,7 @@ bool CSoundFile::ReadAMF_Asylum(FileReader &file, ModLoadingFlags loadFlags) { return false; } - if(!file.CanRead(mpt::saturate_cast(GetHeaderMinimumAdditionalSize(fileHeader)))) + if(!file.CanRead(mpt::saturate_cast(GetHeaderMinimumAdditionalSize(fileHeader)))) { return false; } @@ -125,20 +125,18 @@ bool CSoundFile::ReadAMF_Asylum(FileReader &file, ModLoadingFlags loadFlags) return true; } - InitializeGlobals(MOD_TYPE_AMF0); - InitializeChannels(); + InitializeGlobals(MOD_TYPE_AMF0, 8); SetupMODPanning(true); - m_nChannels = 8; - m_nDefaultSpeed = fileHeader.defaultSpeed; - m_nDefaultTempo.Set(fileHeader.defaultTempo); + Order().SetDefaultSpeed(fileHeader.defaultSpeed); + Order().SetDefaultTempoInt(fileHeader.defaultTempo); m_nSamples = fileHeader.numSamples; if(fileHeader.restartPos < fileHeader.numOrders) { Order().SetRestartPos(fileHeader.restartPos); } - m_modFormat.formatName = U_("ASYLUM Music Format"); - m_modFormat.type = U_("amf"); + m_modFormat.formatName = UL_("ASYLUM Music Format"); + m_modFormat.type = UL_("amf"); m_modFormat.charset = mpt::Charset::CP437; uint8 orders[256]; @@ -240,6 +238,8 @@ struct AMFFileHeader return false; if(version < 9) return true; + if(version < 12) + return (numChannels >= 1 && numChannels <= 16); return (numChannels >= 1 && numChannels <= 32); } @@ -446,7 +446,7 @@ static void AMFReadPattern(CPattern &pattern, CHANNELINDEX chn, FileReader &file if(param) { if(param & 0x80) - param = 0xF0 | ((-static_cast(param)) & 0x0F); + param = static_cast(0xF0 | ((-static_cast(param)) & 0x0F)); else param = 0x0F | ((param & 0x0F) << 4); } else @@ -573,28 +573,25 @@ bool CSoundFile::ReadAMF_DSMI(FileReader &file, ModLoadingFlags loadFlags) if(loadFlags == onlyVerifyHeader) return true; - InitializeGlobals(MOD_TYPE_AMF); - InitializeChannels(); + InitializeGlobals(MOD_TYPE_AMF, (fileSignature.version < 9) ? 4 : fileHeader.numChannels); if(isDMF) { - m_modFormat.formatName = MPT_UFORMAT("DSMI Compact v{}")(fileSignature.version); - m_modFormat.type = U_("dmf"); + m_modFormat.formatName = MPT_UFORMAT("DSMI Advanced Music Format (Compact) v{}")(fileSignature.version); + m_modFormat.type = UL_("dmf"); } else { m_songName = mpt::String::ReadBuf(mpt::String::maybeNullTerminated, title); - m_modFormat.formatName = MPT_UFORMAT("DSMI v{}")(fileSignature.version); - m_modFormat.type = U_("amf"); + m_modFormat.formatName = MPT_UFORMAT("DSMI Advanced Music Format v{}")(fileSignature.version); + m_modFormat.type = UL_("amf"); } m_modFormat.charset = mpt::Charset::CP437; - m_nChannels = fileHeader.numChannels; m_nSamples = fileHeader.numSamples; if(fileSignature.version < 9) { // Old format revisions are fixed to 4 channels - m_nChannels = 4; for(CHANNELINDEX chn = 0; chn < 4; chn++) { ChnSettings[chn].nPan = (chn & 1) ? 0xC0 : 0x40; @@ -605,14 +602,15 @@ bool CSoundFile::ReadAMF_DSMI(FileReader &file, ModLoadingFlags loadFlags) if(fileSignature.version >= 11) { const CHANNELINDEX readChannels = fileSignature.version >= 12 ? 32 : 16; - for(CHANNELINDEX chn = 0; chn < readChannels; chn++) + for(auto &chn : ChnSettings) { int8 pan = file.ReadInt8(); if(pan == 100) - ChnSettings[chn].dwFlags = CHN_SURROUND; + chn.dwFlags = CHN_SURROUND; else - ChnSettings[chn].nPan = static_cast(std::clamp((pan + 64) * 2, 0, 256)); + chn.nPan = static_cast(std::clamp((pan + 64) * 2, 0, 256)); } + file.Skip(readChannels - GetNumChannels()); } else if(fileSignature.version >= 9) { // Internally, DSMI assigns an Amiga-like LRRL panning scheme to the channels in pre-v11 files, @@ -621,7 +619,7 @@ bool CSoundFile::ReadAMF_DSMI(FileReader &file, ModLoadingFlags loadFlags) // This can be observed by looking at a 4-channel MOD and the converted AMF file: The last two channels are swapped. // We ignore all this mess and simply assume that all AMF files use the standard remap table. file.Skip(16); - for(CHANNELINDEX chn = 0; chn < 16; chn++) + for(CHANNELINDEX chn = 0; chn < GetNumChannels(); chn++) { ChnSettings[chn].nPan = (chn & 1) ? 0xC0 : 0x40; } @@ -633,18 +631,18 @@ bool CSoundFile::ReadAMF_DSMI(FileReader &file, ModLoadingFlags loadFlags) auto [tempo, speed] = file.ReadArray(); if(tempo < 32) tempo = 125; - m_nDefaultTempo.Set(tempo); - m_nDefaultSpeed = speed; + Order().SetDefaultTempoInt(tempo); + Order().SetDefaultSpeed(speed); } else { - m_nDefaultTempo.Set(125); - m_nDefaultSpeed = 6; + Order().SetDefaultTempoInt(125); + Order().SetDefaultSpeed(6); } // Setup Order List Order().resize(fileHeader.numOrders); std::vector patternLength; - const FileReader::off_t trackStartPos = file.GetPosition() + (fileSignature.version >= 14 ? 2 : 0); + const FileReader::pos_type trackStartPos = file.GetPosition() + (fileSignature.version >= 14 ? 2 : 0); if(fileSignature.version >= 14) { patternLength.resize(fileHeader.numOrders); @@ -658,7 +656,7 @@ bool CSoundFile::ReadAMF_DSMI(FileReader &file, ModLoadingFlags loadFlags) patternLength[ord] = file.ReadUint16LE(); } // Track positions will be read as needed. - file.Skip(m_nChannels * 2); + file.Skip(GetNumChannels() * 2); } // Read Sample Headers @@ -754,7 +752,7 @@ bool CSoundFile::ReadAMF_DSMI(FileReader &file, ModLoadingFlags loadFlags) // Unsigned delta samples, how novel! for(auto &v : mpt::as_span(sample.sample8(), sample.nLength)) { - v ^= 0x80; + v = static_cast(static_cast(v) ^ 0x80u); } } } diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_ams.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_ams.cpp index fea47f9ff..c89403239 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_ams.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_ams.cpp @@ -207,12 +207,12 @@ static void ReadAMSPattern(CPattern &pattern, bool newVersion, FileReader &patte case 0xA: // Extra fine volume slide up m.command = CMD_VOLUMESLIDE; - m.param = ((((m.param & 0x0F) + 1) / 2) << 4) | 0x0F; + m.param = static_cast(((((m.param & 0x0F) + 1) / 2) << 4) | 0x0F); break; case 0xB: // Extra fine volume slide down m.command = CMD_VOLUMESLIDE; - m.param = (((m.param & 0x0F) + 1) / 2) | 0xF0; + m.param = static_cast((((m.param & 0x0F) + 1) / 2) | 0xF0); break; default: m.command = CMD_NONE; @@ -300,7 +300,7 @@ struct AMSSampleHeader mptSmp.nLoopStart = std::min(loopStart, length); mptSmp.nLoopEnd = std::min(loopEnd, length); - mptSmp.nVolume = (std::min(uint8(127), volume.get()) * 256 + 64) / 127; + mptSmp.nVolume = static_cast((std::min(uint8(127), volume.get()) * 256 + 64) / 127); if(panFinetune & 0xF0) { mptSmp.nPan = (panFinetune & 0xF0); @@ -387,7 +387,7 @@ bool CSoundFile::ReadAMS(FileReader &file, ModLoadingFlags loadFlags) { return false; } - if(!file.CanRead(mpt::saturate_cast(GetHeaderMinimumAdditionalSize(fileHeader)))) + if(!file.CanRead(mpt::saturate_cast(GetHeaderMinimumAdditionalSize(fileHeader)))) { return false; } @@ -400,16 +400,14 @@ bool CSoundFile::ReadAMS(FileReader &file, ModLoadingFlags loadFlags) return true; } - InitializeGlobals(MOD_TYPE_AMS); - InitializeChannels(); + InitializeGlobals(MOD_TYPE_AMS, (fileHeader.channelConfig & 0x1F) + 1); m_SongFlags = SONG_ITCOMPATGXX | SONG_ITOLDEFFECTS; - m_nChannels = (fileHeader.channelConfig & 0x1F) + 1; m_nSamples = fileHeader.numSamps; SetupMODPanning(true); - m_modFormat.formatName = U_("Extreme's Tracker"); - m_modFormat.type = U_("ams"); + m_modFormat.formatName = UL_("Extreme's Tracker"); + m_modFormat.type = UL_("ams"); m_modFormat.madeWithTracker = MPT_UFORMAT("Extreme's Tracker {}.{}")(fileHeader.versionHigh, fileHeader.versionLow); m_modFormat.charset = mpt::Charset::CP437; @@ -662,7 +660,7 @@ struct AMS2SampleHeader uint32 newC4speed = ModSample::TransposeToFrequency(relativeTone, MOD2XMFineTune(panFinetune & 0x0F)); mptSmp.nC5Speed = (mptSmp.nC5Speed * newC4speed) / 8363; - mptSmp.nVolume = (std::min(volume.get(), uint8(127)) * 256 + 64) / 127; + mptSmp.nVolume = static_cast((std::min(volume.get(), uint8(127)) * 256 + 64) / 127); if(panFinetune & 0xF0) { mptSmp.nPan = (panFinetune & 0xF0); @@ -765,7 +763,7 @@ bool CSoundFile::ReadAMS2(FileReader &file, ModLoadingFlags loadFlags) { return false; } - if(!file.CanRead(mpt::saturate_cast(GetHeaderMinimumAdditionalSize(fileHeader)))) + if(!file.CanRead(mpt::saturate_cast(GetHeaderMinimumAdditionalSize(fileHeader)))) { return false; } @@ -773,46 +771,45 @@ bool CSoundFile::ReadAMS2(FileReader &file, ModLoadingFlags loadFlags) { return true; } - - InitializeGlobals(MOD_TYPE_AMS); - - m_songName = songName; + + InitializeGlobals(MOD_TYPE_AMS, 32); + + m_songName = std::move(songName); m_nInstruments = fileHeader.numIns; - m_nChannels = 32; SetupMODPanning(true); - m_modFormat.formatName = U_("Velvet Studio"); - m_modFormat.type = U_("ams"); + m_modFormat.formatName = UL_("Velvet Studio"); + m_modFormat.type = UL_("ams"); m_modFormat.madeWithTracker = MPT_UFORMAT("Velvet Studio {}.{}")(fileHeader.versionHigh.get(), mpt::ufmt::dec0<2>(fileHeader.versionLow.get())); m_modFormat.charset = mpt::Charset::CP437; uint16 headerFlags; if(fileHeader.versionLow >= 2) { - uint16 tempo = std::max(uint16(32 << 8), file.ReadUint16LE()); // 8.8 tempo - m_nDefaultTempo.SetRaw((tempo * TEMPO::fractFact) >> 8); - m_nDefaultSpeed = std::max(uint8(1), file.ReadUint8()); - file.Skip(3); // Default values for pattern editor + uint16 tempo = std::max(uint16(32 << 8), file.ReadUint16LE()); // 8.8 tempo + Order().SetDefaultTempo(TEMPO{}.SetRaw((tempo * TEMPO::fractFact) >> 8)); + Order().SetDefaultSpeed(std::max(uint8(1), file.ReadUint8())); + file.Skip(3); // Default values for pattern editor headerFlags = file.ReadUint16LE(); } else { - m_nDefaultTempo.Set(std::max(uint8(32), file.ReadUint8())); - m_nDefaultSpeed = std::max(uint8(1), file.ReadUint8()); + Order().SetDefaultTempoInt(std::max(uint8(32), file.ReadUint8())); + Order().SetDefaultSpeed(std::max(uint8(1), file.ReadUint8())); headerFlags = file.ReadUint8(); } m_SongFlags = SONG_ITCOMPATGXX | SONG_ITOLDEFFECTS | ((headerFlags & AMS2FileHeader::linearSlides) ? SONG_LINEARSLIDES : SongFlags(0)); // Instruments - std::vector firstSample; // First sample of instrument - std::vector sampleSettings; // Shadow sample map... Lo byte = Instrument, Hi byte, lo nibble = Sample index in instrument, Hi byte, hi nibble = Sample pack status + std::vector firstSample; // First sample of instrument + std::vector sampleSettings; // Shadow sample map... Lo byte = Instrument, Hi byte, lo nibble = Sample index in instrument, Hi byte, hi nibble = Sample pack status enum { - instrIndexMask = 0xFF, // Shadow instrument - sampleIndexMask = 0x7F00, // Sample index in instrument + instrIndexMask = 0xFF, // Shadow instrument + sampleIndexMask = 0x7F00, // Sample index in instrument sampleIndexShift = 8, - packStatusMask = 0x8000, // If bit is set, sample is packed + packStatusMask = 0x8000, // If bit is set, sample is packed }; static_assert(MAX_INSTRUMENTS > 255); @@ -839,7 +836,7 @@ bool CSoundFile::ReadAMS2(FileReader &file, ModLoadingFlags loadFlags) static_assert(mpt::array_sizeKeyboard)>::size >= std::size(sampleAssignment)); for(size_t i = 0; i < 120; i++) { - instrument->Keyboard[i] = sampleAssignment[i] + GetNumSamples() + 1; + instrument->Keyboard[i] = static_cast(sampleAssignment[i] + GetNumSamples() + 1); } AMS2Envelope volEnv, panEnv, vibratoEnv; @@ -911,7 +908,6 @@ bool CSoundFile::ReadAMS2(FileReader &file, ModLoadingFlags loadFlags) // Channel names for(CHANNELINDEX chn = 0; chn < 32; chn++) { - ChnSettings[chn].Reset(); file.ReadSizedString(ChnSettings[chn].szName); } @@ -1089,9 +1085,9 @@ void AMSUnpack(mpt::const_byte_span source, mpt::byte_span dest, int8 packCharac for(uint16 count = 0; count < 8; count++) { uint16 bl = al & bitcount; - bl = ((bl | (bl << 8)) >> ((dh + 8 - count) & 7)) & 0xFF; + bl = static_cast((bl | (bl << 8)) >> ((dh + 8 - count) & 7)); bitcount = ((bitcount | (bitcount << 8)) >> 1) & 0xFF; - dst[k++] |= bl; + dst[k++] |= (bl & 0xFFu); if(k >= dest.size()) { k = 0; diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_c67.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_c67.cpp index 4d73e17dd..04b4d204b 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_c67.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_c67.cpp @@ -115,7 +115,7 @@ static void TranslateVolume(ModCommand &m, uint8 volume, bool isFM) volume &= 0x0F; m.volcmd = VOLCMD_VOLUME; - m.vol = isFM ? fmVolume[volume] : (4u + volume * 4u); + m.vol = isFM ? fmVolume[volume] : static_cast((4u + volume * 4u)); } @@ -137,7 +137,7 @@ bool CSoundFile::ReadC67(FileReader &file, ModLoadingFlags loadFlags) return true; } - if(!file.CanRead(mpt::saturate_cast(GetHeaderMinimumAdditionalSize(fileHeader)))) + if(!file.CanRead(mpt::saturate_cast(GetHeaderMinimumAdditionalSize(fileHeader)))) { return false; } @@ -157,19 +157,17 @@ bool CSoundFile::ReadC67(FileReader &file, ModLoadingFlags loadFlags) } } - InitializeGlobals(MOD_TYPE_S3M); - InitializeChannels(); + InitializeGlobals(MOD_TYPE_S3M, 4 + 9); - m_modFormat.formatName = U_("CDFM"); - m_modFormat.type = U_("c67"); - m_modFormat.madeWithTracker = U_("Composer 670"); + m_modFormat.formatName = UL_("CDFM"); + m_modFormat.type = UL_("c67"); + m_modFormat.madeWithTracker = UL_("Composer 670"); m_modFormat.charset = mpt::Charset::CP437; - m_nDefaultSpeed = fileHeader.speed; - m_nDefaultTempo.Set(143); + Order().SetDefaultSpeed(fileHeader.speed); + Order().SetDefaultTempoInt(143); Order().SetRestartPos(fileHeader.restartPos); m_nSamples = 64; - m_nChannels = 4 + 9; m_playBehaviour.set(kOPLBeatingOscillators); m_SongFlags.set(SONG_IMPORTED); @@ -233,8 +231,8 @@ bool CSoundFile::ReadC67(FileReader &file, ModLoadingFlags loadFlags) ModCommand &m = *pattern.GetpModCommand(row, cmd); const auto [note, instrVol] = patChunk.ReadArray(); bool fmChn = (cmd >= 4); - m.note = NOTE_MIN + (fmChn ? 12 : 36) + (note & 0x0F) + ((note >> 4) & 0x07) * 12; - m.instr = (fmChn ? 33 : 1) + (instrVol >> 4) + ((note & 0x80) >> 3); + m.note = static_cast(NOTE_MIN + (fmChn ? 12 : 36) + (note & 0x0F) + ((note >> 4) & 0x07) * 12); + m.instr = static_cast((fmChn ? 33 : 1) + (instrVol >> 4) + ((note & 0x80) >> 3)); TranslateVolume(m, instrVol, fmChn); } else if(cmd >= 0x20 && cmd <= 0x2C) { diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_cba.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_cba.cpp new file mode 100644 index 000000000..fc8509756 --- /dev/null +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_cba.cpp @@ -0,0 +1,179 @@ +/* + * Load_cba.cpp + * ------------ + * Purpose: Chuck Biscuits / Black Artist (CBA) module loader + * Notes : This format appears to have been used only for the Expoze musicdisk by Heretics. + * Authors: OpenMPT Devs + * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. + */ + + +#include "stdafx.h" +#include "Loaders.h" + +OPENMPT_NAMESPACE_BEGIN + +struct CBAFileHeader +{ + char magic[4]; // 'CBA\xF9' + char title[32]; + uint8 eof; + uint16le messageLength; + uint8 numChannels; + uint8 lastPattern; + uint8 numOrders; + uint8 numSamples; + uint8 speed; + uint8 tempo; + uint8 panPos[32]; + uint8 orders[255]; + + bool IsValid() const + { + return !memcmp(magic, "CBA\xF9", 4) + && eof == 0x1A + && numChannels > 0 && numChannels <= 32 + && speed > 0 + && tempo >= 32; + } + + uint32 GetHeaderMinimumAdditionalSize() const + { + return numSamples * 48 + messageLength; + } +}; + +MPT_BINARY_STRUCT(CBAFileHeader, 332) + + +struct CBASampleHeader +{ + char name[32]; + uint8 flags; + uint8 volume; + uint16le sampleRate; + uint32le length; + uint32le loopStart; + uint32le loopEnd; + + void ConvertToMPT(ModSample &mptSmp) const + { + mptSmp.Initialize(); + mptSmp.nVolume = volume * 4; + mptSmp.nC5Speed = sampleRate; + mptSmp.nLength = length; + mptSmp.nLoopStart = loopStart; + mptSmp.nLoopEnd = loopEnd; + mptSmp.uFlags.set(CHN_LOOP, flags & 0x08); + } +}; + +MPT_BINARY_STRUCT(CBASampleHeader, 48) + + +CSoundFile::ProbeResult CSoundFile::ProbeFileHeaderCBA(MemoryFileReader file, const uint64 *pfilesize) +{ + CBAFileHeader fileHeader; + if(!file.ReadStruct(fileHeader)) + return ProbeWantMoreData; + if(!fileHeader.IsValid()) + return ProbeFailure; + return ProbeAdditionalSize(file, pfilesize, fileHeader.GetHeaderMinimumAdditionalSize()); +} + + +bool CSoundFile::ReadCBA(FileReader &file, ModLoadingFlags loadFlags) +{ + CBAFileHeader fileHeader; + + file.Rewind(); + if(!file.ReadStruct(fileHeader) || !fileHeader.IsValid()) + return false; + if(!file.CanRead(fileHeader.GetHeaderMinimumAdditionalSize())) + return false; + if(loadFlags == onlyVerifyHeader) + return true; + + InitializeGlobals(MOD_TYPE_S3M, fileHeader.numChannels); + m_SongFlags.set(SONG_IMPORTED); + m_playBehaviour.set(kST3SampleSwap); // AFTERMIX.CBA, pattern 53 + m_nMixLevels = MixLevels::CompatibleFT2; + m_nSamples = fileHeader.numSamples; + m_songName = mpt::String::ReadBuf(mpt::String::maybeNullTerminated, fileHeader.title); + Order().SetDefaultTempoInt(fileHeader.tempo); + Order().SetDefaultSpeed(fileHeader.speed); + ReadOrderFromArray(Order(), fileHeader.orders, fileHeader.numOrders, 0xFF, 0xFE); + + for(CHANNELINDEX chn = 0; chn < GetNumChannels(); chn++) + { + ChnSettings[chn].nPan = fileHeader.panPos[chn] * 2; + } + + for(SAMPLEINDEX smp = 1; smp <= m_nSamples; smp++) + { + CBASampleHeader sampleHeader; + file.ReadStruct(sampleHeader); + sampleHeader.ConvertToMPT(Samples[smp]); + m_szNames[smp] = mpt::String::ReadBuf(mpt::String::maybeNullTerminated, sampleHeader.name); + } + + Patterns.ResizeArray(fileHeader.lastPattern + 1); + for(PATTERNINDEX pat = 0; pat < Patterns.Size(); pat++) + { + if(!(loadFlags & loadPatternData) || !file.CanRead(64 * 5 * fileHeader.numChannels) || !Patterns.Insert(pat, 64)) + { + file.Skip(64 * 5 * fileHeader.numChannels); + continue; + } + + for(ModCommand &m : Patterns[pat]) + { + const auto [instr, note, vol, command, param] = file.ReadArray(); + m.instr = instr; + if(note == 255) + m.note = NOTE_NOTECUT; + else if(note > 0 && note <= 96) + m.note = NOTE_MIDDLEC - 49 + note; + + if(vol) + m.SetVolumeCommand(VOLCMD_VOLUME, std::min(vol, uint8(65)) - 1); + + if(command > 0 && command < 0x0F) + ConvertModCommand(m, command - 1, param); + else if(command == 0x0F) // "Funky sync" + m.SetEffectCommand(CMD_DUMMY, param); + else if(command == 0x18) + m.SetEffectCommand(CMD_RETRIG, param); + else if(command >= 0x10 && command <= 0x1E) + m.SetEffectCommand(CMD_MODCMDEX, static_cast(((command << 4) + 0x10) | std::min(param, uint8(0x0F)))); + else if(command == 0x1F) + m.SetEffectCommand(CMD_SPEED, param); + else if(command == 0x20) + m.SetEffectCommand(CMD_TEMPO, param); + m.ExtendedMODtoS3MEffect(); + } + } + + for(SAMPLEINDEX smp = 1; smp <= m_nSamples; smp++) + { + if(loadFlags & loadSampleData) + { + SampleIO(SampleIO::_8bit, SampleIO::mono, SampleIO::littleEndian, SampleIO::deltaPCM) + .ReadSample(Samples[smp], file); + } else + { + file.Skip(Samples[smp].nLength); + } + } + + m_songMessage.Read(file, fileHeader.messageLength, SongMessage::leCRLF); + + m_modFormat.formatName = UL_("Chuck Biscuits / Black Artist"); + m_modFormat.type = UL_("cba"); + m_modFormat.charset = mpt::Charset::CP437; + + return true; +} + + +OPENMPT_NAMESPACE_END diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_dbm.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_dbm.cpp index 8fa92c422..67313ecc0 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_dbm.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_dbm.cpp @@ -221,7 +221,7 @@ static std::pair ConvertDBMEffect(const uint8 cmd, uint8 p break; case CMD_PATTERNBREAK: - param = ((param >> 4) * 10) + (param & 0x0F); + param = static_cast(((param >> 4) * 10) + (param & 0x0F)); break; #ifdef MODPLUG_TRACKER @@ -297,7 +297,7 @@ static std::pair ConvertDBMEffect(const uint8 cmd, uint8 p case CMD_MIDI: // Encode echo parameters into fixed MIDI macros - param = 128 + (cmd - 32) * 32 + param / 8; + param = static_cast(128 + (cmd - 32) * 32 + param / 8); break; default: @@ -380,10 +380,8 @@ bool CSoundFile::ReadDBM(FileReader &file, ModLoadingFlags loadFlags) return false; } - InitializeGlobals(MOD_TYPE_DBM); - InitializeChannels(); + InitializeGlobals(MOD_TYPE_DBM, Clamp(infoData.channels, 1, MAX_BASECHANNELS)); // Note: MAX_BASECHANNELS is currently 192, but DBPro 3 apparently supports up to 254 channels. m_SongFlags = SONG_ITCOMPATGXX | SONG_ITOLDEFFECTS; - m_nChannels = Clamp(infoData.channels, 1, MAX_BASECHANNELS); // note: MAX_BASECHANNELS is currently 127, but DBPro 2 supports up to 128 channels, DBPro 3 apparently up to 254. m_nInstruments = std::min(static_cast(infoData.instruments), static_cast(MAX_INSTRUMENTS - 1)); m_nSamples = std::min(static_cast(infoData.samples), static_cast(MAX_SAMPLES - 1)); m_playBehaviour.set(kSlidesAtSpeed1); @@ -392,8 +390,8 @@ bool CSoundFile::ReadDBM(FileReader &file, ModLoadingFlags loadFlags) m_playBehaviour.reset(kITInstrWithNoteOff); m_playBehaviour.reset(kITInstrWithNoteOffOldEffects); - m_modFormat.formatName = U_("DigiBooster Pro"); - m_modFormat.type = U_("dbm"); + m_modFormat.formatName = UL_("DigiBooster Pro"); + m_modFormat.type = UL_("dbm"); m_modFormat.madeWithTracker = MPT_UFORMAT("DigiBooster Pro {}.{}")(mpt::ufmt::hex(fileHeader.trkVerHi), mpt::ufmt::hex(fileHeader.trkVerLo)); m_modFormat.charset = mpt::Charset::Amiga_no_C1; @@ -553,7 +551,7 @@ bool CSoundFile::ReadDBM(FileReader &file, ModLoadingFlags loadFlags) if(note == 0x1F) m.note = NOTE_KEYOFF; else if(note > 0 && note < 0xFE) - m.note = ((note >> 4) * 12) + (note & 0x0F) + 13; + m.note = static_cast(((note >> 4) * 12) + (note & 0x0F) + 13); } if(b & 0x02) { @@ -575,7 +573,7 @@ bool CSoundFile::ReadDBM(FileReader &file, ModLoadingFlags loadFlags) std::swap(param1, param2); } else if(cmd1 == CMD_TONEPORTAMENTO && cmd2 == CMD_OFFSET && param2 == 0) { - // Offset + Portmaneto: Ignore portamento. If the offset command has a non-zero parameter, keep it for effect memory. + // Offset + Portamento: Ignore portamento. If the offset command has a non-zero parameter, keep it for effect memory. cmd2 = CMD_NONE; } else if(cmd2 == CMD_TONEPORTAMENTO && cmd1 == CMD_OFFSET && param1 == 0) { @@ -608,7 +606,7 @@ bool CSoundFile::ReadDBM(FileReader &file, ModLoadingFlags loadFlags) if(hasEchoEnable) { // If there are any Vxx effects to dynamically enable / disable echo, use the CHN_NOFX flag. - for(CHANNELINDEX i = 0; i < m_nChannels; i++) + for(CHANNELINDEX i = 0; i < GetNumChannels(); i++) { ChnSettings[i].nMixPlugin = 1; ChnSettings[i].dwFlags.set(CHN_NOFX); @@ -625,7 +623,7 @@ bool CSoundFile::ReadDBM(FileReader &file, ModLoadingFlags loadFlags) for(uint16 i = 0; i < maskLen; i++) { bool enabled = (dspChunk.ReadUint8() == 0); - if(i < m_nChannels) + if(i < GetNumChannels()) { if(hasEchoEnable) { diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_digi.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_digi.cpp index fbcebc503..0d4172de6 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_digi.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_digi.cpp @@ -119,15 +119,12 @@ bool CSoundFile::ReadDIGI(FileReader &file, ModLoadingFlags loadFlags) } // Globals - InitializeGlobals(MOD_TYPE_DIGI); - InitializeChannels(); - - m_nChannels = fileHeader.numChannels; + InitializeGlobals(MOD_TYPE_DIGI, fileHeader.numChannels); m_nSamples = 31; - m_nSamplePreAmp = 256 / m_nChannels; + m_nSamplePreAmp = 256 / GetNumChannels(); - m_modFormat.formatName = U_("DigiBooster"); - m_modFormat.type = U_("digi"); + m_modFormat.formatName = UL_("DigiBooster"); + m_modFormat.type = UL_("digi"); m_modFormat.madeWithTracker = MPT_UFORMAT("Digi Booster {}.{}")(fileHeader.versionInt >> 4, fileHeader.versionInt & 0x0F); m_modFormat.charset = mpt::Charset::Amiga_no_C1; diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_dmf.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_dmf.cpp index bb537ba9a..f2375ec34 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_dmf.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_dmf.cpp @@ -381,7 +381,7 @@ static PATTERNINDEX ConvertDMFPattern(FileReader &file, const uint8 fileVersion, settings.tempoBPM = globalData; // Tempo in real BPM (depends on rows per beat) if(settings.beat != 0) { - settings.tempoTicks = (globalData * settings.beat * 15); // Automatically updated by X-Tracker + settings.tempoTicks = static_cast(globalData * settings.beat * 15); // Automatically updated by X-Tracker } tempoChange = true; } @@ -505,12 +505,12 @@ static PATTERNINDEX ConvertDMFPattern(FileReader &file, const uint8 fileVersion, m->note = file.ReadUint8(); if(m->note >= 1 && m->note <= 108) { - m->note = static_cast(Clamp(m->note + 24, NOTE_MIN, NOTE_MAX)); + m->note = Clamp(static_cast(m->note + 24), NOTE_MIN, NOTE_MAX); settings.channels[chn].lastNote = m->note; } else if(m->note >= 129 && m->note <= 236) { // "Buffer notes" for portamento (and other effects?) that are actually not played, but just "queued"... - m->note = static_cast(Clamp((m->note & 0x7F) + 24, NOTE_MIN, NOTE_MAX)); + m->note = Clamp(static_cast((m->note & 0x7F) + 24), NOTE_MIN, NOTE_MAX); settings.channels[chn].noteBuffer = m->note; m->note = NOTE_NONE; } else if(m->note == 255) @@ -540,7 +540,7 @@ static PATTERNINDEX ConvertDMFPattern(FileReader &file, const uint8 fileVersion, if((channelInfo & patVolume) != 0) { m->volcmd = VOLCMD_VOLUME; - m->vol = (file.ReadUint8() + 2) / 4; // Should be + 3 instead of + 2, but volume 1 is silent in X-Tracker. + m->vol = static_cast((file.ReadUint8() + 2) / 4); // Should be + 3 instead of + 2, but volume 1 is silent in X-Tracker. } //////////////////////////////////////////////////////////////// @@ -846,7 +846,7 @@ static PATTERNINDEX ConvertDMFPattern(FileReader &file, const uint8 fileVersion, } if(writeDelay & 0x0F) { - const uint8 param = (writeDelay & 0x0F) * settings.internalTicks / 15; + const uint8 param = static_cast((writeDelay & 0x0F) * settings.internalTicks / 15); sndFile.Patterns[pat].WriteEffect(EffectWriter(CMD_S3MCMDEX, 0x60u | Clamp(param, uint8(1), uint8(15))).Row(row).AllowMultiple()); } writeDelay = 0; @@ -901,22 +901,6 @@ bool CSoundFile::ReadDMF(FileReader &file, ModLoadingFlags loadFlags) return true; } - InitializeGlobals(MOD_TYPE_DMF); - - m_modFormat.formatName = MPT_UFORMAT("X-Tracker v{}")(fileHeader.version); - m_modFormat.type = U_("dmf"); - m_modFormat.charset = mpt::Charset::CP437; - - m_songName = mpt::String::ReadBuf(mpt::String::spacePadded, fileHeader.songname); - m_songArtist = mpt::ToUnicode(mpt::Charset::CP437, mpt::String::ReadBuf(mpt::String::spacePadded, fileHeader.composer)); - - FileHistory mptHistory; - mptHistory.loadDate.day = Clamp(fileHeader.creationDay, uint8(1), uint8(31)); - mptHistory.loadDate.month = Clamp(fileHeader.creationMonth, uint8(1), uint8(12)); - mptHistory.loadDate.year = 1900 + fileHeader.creationYear; - m_FileHistory.clear(); - m_FileHistory.push_back(mptHistory); - // Go through all chunks now... cannot use our standard IFF chunk reader here because early X-Tracker versions write some malformed chunk headers... fun code ahead! ChunkReader::ChunkList chunks; while(file.CanRead(sizeof(DMFChunk))) @@ -940,6 +924,47 @@ bool CSoundFile::ReadDMF(FileReader &file, ModLoadingFlags loadFlags) } FileReader chunk; + // Read pattern chunk first so that we know how many channels there are + chunk = chunks.GetChunk(DMFChunk::idPATT); + if(!chunk.IsValid()) + return false; + + DMFPatterns patHeader; + chunk.ReadStruct(patHeader); + // First, find out where all of our patterns are... + std::vector patternChunks; + if(loadFlags & loadPatternData) + { + patternChunks.resize(patHeader.numPatterns); + const uint8 headerSize = fileHeader.version < 3 ? 9 : 8; + for(auto &patternChunk : patternChunks) + { + chunk.Skip(headerSize - sizeof(uint32le)); + const uint32 patLength = chunk.ReadUint32LE(); + if(!chunk.CanRead(patLength)) + return false; + chunk.SkipBack(headerSize); + patternChunk = chunk.ReadChunk(headerSize + patLength); + } + } + + InitializeGlobals(MOD_TYPE_DMF, Clamp(patHeader.numTracks, 1, 32) + 1); // + 1 for global track (used for tempo stuff) + + m_modFormat.formatName = MPT_UFORMAT("Delusion Digital Music Format v{}")(fileHeader.version); + m_modFormat.madeWithTracker = fileHeader.version == 10 ? UL_("X-Tracker 32") : UL_("X-Tracker"); + m_modFormat.type = UL_("dmf"); + m_modFormat.charset = mpt::Charset::CP437; + + m_songName = mpt::String::ReadBuf(mpt::String::spacePadded, fileHeader.songname); + m_songArtist = mpt::ToUnicode(mpt::Charset::CP437, mpt::String::ReadBuf(mpt::String::spacePadded, fileHeader.composer)); + + FileHistory mptHistory; + mptHistory.loadDate.day = Clamp(fileHeader.creationDay, uint8(1), uint8(31)); + mptHistory.loadDate.month = Clamp(fileHeader.creationMonth, uint8(1), uint8(12)); + mptHistory.loadDate.year = 1900 + fileHeader.creationYear; + m_FileHistory.clear(); + m_FileHistory.push_back(mptHistory); + // Read order list chunk = chunks.GetChunk(DMFChunk::idSEQU); ORDERINDEX seqLoopStart = 0, seqLoopEnd = ORDERINDEX_MAX; @@ -955,27 +980,8 @@ bool CSoundFile::ReadDMF(FileReader &file, ModLoadingFlags loadFlags) LimitMax(seqLoopStart, Order().GetLastIndex()); LimitMax(seqLoopEnd, Order().GetLastIndex()); - // Read patterns - chunk = chunks.GetChunk(DMFChunk::idPATT); - if(chunk.IsValid() && (loadFlags & loadPatternData)) + if(loadFlags & loadPatternData) { - DMFPatterns patHeader; - chunk.ReadStruct(patHeader); - m_nChannels = Clamp(patHeader.numTracks, 1, 32) + 1; // + 1 for global track (used for tempo stuff) - - // First, find out where all of our patterns are... - std::vector patternChunks(patHeader.numPatterns); - for(auto &patternChunk : patternChunks) - { - const uint8 headerSize = fileHeader.version < 3 ? 9 : 8; - chunk.Skip(headerSize - sizeof(uint32le)); - const uint32 patLength = chunk.ReadUint32LE(); - if(!chunk.CanRead(patLength)) - return false; - chunk.SkipBack(headerSize); - patternChunk = chunk.ReadChunk(headerSize + patLength); - } - // Now go through the order list and load them. DMFPatternSettings settings(GetNumChannels()); @@ -1041,10 +1047,9 @@ bool CSoundFile::ReadDMF(FileReader &file, ModLoadingFlags loadFlags) } } - InitializeChannels(); m_SongFlags = SONG_LINEARSLIDES | SONG_ITCOMPATGXX; // this will be converted to IT format by MPT. SONG_ITOLDEFFECTS is not set because of tremor and vibrato. - m_nDefaultSpeed = 6; - m_nDefaultTempo.Set(120); + Order().SetDefaultSpeed(6); + Order().SetDefaultTempoInt(120); m_nDefaultGlobalVolume = 256; m_nSamplePreAmp = m_nVSTiVolume = 48; m_playBehaviour.set(kApplyOffsetWithoutNote); diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_dsm.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_dsm.cpp index 8bd20f74c..1891f4dfa 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_dsm.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_dsm.cpp @@ -206,27 +206,25 @@ bool CSoundFile::ReadDSM(FileReader &file, ModLoadingFlags loadFlags) return false; } - InitializeGlobals(MOD_TYPE_DSM); + InitializeGlobals(MOD_TYPE_DSM, std::max(songHeader.numChannels.get(), uint16(1))); - m_modFormat.formatName = U_("DSIK Format"); - m_modFormat.type = U_("dsm"); + m_modFormat.formatName = UL_("DSIK Format"); + m_modFormat.type = UL_("dsm"); m_modFormat.charset = mpt::Charset::CP437; m_songName = mpt::String::ReadBuf(mpt::String::maybeNullTerminated, songHeader.songName); - m_nChannels = std::max(songHeader.numChannels.get(), uint16(1)); - m_nDefaultSpeed = songHeader.speed; - m_nDefaultTempo.Set(songHeader.bpm); + Order().SetDefaultSpeed(songHeader.speed); + Order().SetDefaultTempoInt(songHeader.bpm); m_nDefaultGlobalVolume = std::min(songHeader.globalVol.get(), uint8(64)) * 4u; if(!m_nDefaultGlobalVolume) m_nDefaultGlobalVolume = MAX_GLOBAL_VOLUME; if(songHeader.mastervol == 0x80) - m_nSamplePreAmp = std::min(256u / m_nChannels, 128u); + m_nSamplePreAmp = std::min(256u / GetNumChannels(), 128u); else m_nSamplePreAmp = songHeader.mastervol & 0x7F; // Read channel panning - for(CHANNELINDEX chn = 0; chn < 16; chn++) + for(CHANNELINDEX chn = 0; chn < GetNumChannels(); chn++) { - ChnSettings[chn].Reset(); if(songHeader.panPos[chn] <= 0x80) { ChnSettings[chn].nPan = songHeader.panPos[chn] * 2; @@ -369,7 +367,7 @@ struct DSmFileHeader uint32 GetHeaderMinimumAdditionalSize() const noexcept { - return numChannels + numOrders + numSamples * sizeof(DSmSampleHeader); + return static_cast(numChannels + numOrders + numSamples * sizeof(DSmSampleHeader)); } }; @@ -400,9 +398,8 @@ bool CSoundFile::ReadDSm(FileReader &file, ModLoadingFlags loadFlags) if(loadFlags == onlyVerifyHeader) return true; - InitializeGlobals(MOD_TYPE_MOD); + InitializeGlobals(MOD_TYPE_MOD, fileHeader.numChannels); m_SongFlags = SONG_IMPORTED; - m_nChannels = fileHeader.numChannels; static_assert(MAX_BASECHANNELS >= 32 && MAX_SAMPLES > 255); m_nSamples = fileHeader.numSamples; m_nDefaultGlobalVolume = Util::muldivr_unsigned(fileHeader.globalVol, MAX_GLOBAL_VOLUME, 100); @@ -410,9 +407,8 @@ bool CSoundFile::ReadDSm(FileReader &file, ModLoadingFlags loadFlags) m_songName = mpt::String::ReadBuf(mpt::String::spacePadded, fileHeader.title); m_songArtist = mpt::ToUnicode(mpt::Charset::CP437, mpt::String::ReadBuf(mpt::String::spacePadded, fileHeader.artist)); - for(CHANNELINDEX chn = 0; chn < m_nChannels; chn++) + for(CHANNELINDEX chn = 0; chn < GetNumChannels(); chn++) { - ChnSettings[chn].Reset(); ChnSettings[chn].nPan = (file.ReadUint8() & 0x0F) * 0x11; } @@ -424,15 +420,15 @@ bool CSoundFile::ReadDSm(FileReader &file, ModLoadingFlags loadFlags) } numPatterns++; - if(!file.CanRead((numPatterns * m_nChannels * 8) + (m_nSamples * sizeof(DSmSampleHeader)) + (numPatterns * m_nChannels * 64 * 4))) + if(!file.CanRead((numPatterns * GetNumChannels() * 8) + (m_nSamples * sizeof(DSmSampleHeader)) + (numPatterns * GetNumChannels() * 64 * 4))) return false; // Track names for each pattern - we only read the track names of the first pattern - for(CHANNELINDEX chn = 0; chn < m_nChannels; chn++) + for(CHANNELINDEX chn = 0; chn < GetNumChannels(); chn++) { ChnSettings[chn].szName = mpt::String::ReadBuf(mpt::String::spacePadded, file.ReadArray()); } - file.Skip((numPatterns - 1) * m_nChannels * 8); + file.Skip((numPatterns - 1) * GetNumChannels() * 8); for(SAMPLEINDEX smp = 1; smp <= m_nSamples; smp++) { @@ -447,7 +443,7 @@ bool CSoundFile::ReadDSm(FileReader &file, ModLoadingFlags loadFlags) { if(!(loadFlags & loadPatternData) || !Patterns.Insert(pat, 64)) { - file.Skip(m_nChannels * 64 * 4); + file.Skip(GetNumChannels() * 64 * 4); continue; } for(ModCommand &m : Patterns[pat]) @@ -499,8 +495,7 @@ bool CSoundFile::ReadDSm(FileReader &file, ModLoadingFlags loadFlags) { // Offset + volume m.command = CMD_OFFSET; - m.volcmd = VOLCMD_VOLUME; - m.vol = (data[2] & 0x0F) * 4 + 4; + m.SetVolumeCommand(VOLCMD_VOLUME, static_cast((data[2] & 0x0F) * 4 + 4)); } else if(data[2] <= 0x0F || data[2] == 0x11 || data[2] == 0x12) { // 0x11 and 0x12 support the full 5-octave range, 0x01 and 0x02 presumably only the ProTracker 3-octave range @@ -520,8 +515,8 @@ bool CSoundFile::ReadDSm(FileReader &file, ModLoadingFlags loadFlags) } } - m_modFormat.formatName = U_("Dynamic Studio"); - m_modFormat.type = U_("dsm"); + m_modFormat.formatName = UL_("Dynamic Studio"); + m_modFormat.type = UL_("dsm"); m_modFormat.charset = mpt::Charset::CP437; return true; diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_dsym.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_dsym.cpp index 7bd3c422b..9e0236d2b 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_dsym.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_dsym.cpp @@ -125,7 +125,7 @@ static std::vector DecompressDSymLZW(FileReader &file, uint32 size) MPT_ASSERT(output.size() == size); // Align length to 4 bytes - file.Seek(startPos + ((bitFile.GetPosition() - startPos + 3u) & ~FileReader::off_t(3))); + file.Seek(startPos + ((bitFile.GetPosition() - startPos + 3u) & ~FileReader::pos_type(3))); // cppcheck false-positive // cppcheck-suppress returnDanglingLifetime return output; @@ -185,7 +185,7 @@ static std::vector DecompressDSymSigmaDelta(FileReader &file, uint32 } // Align length to 4 bytes - file.Seek(startPos + ((bitFile.GetPosition() - startPos + 3u) & ~FileReader::off_t(3))); + file.Seek(startPos + ((bitFile.GetPosition() - startPos + 3u) & ~FileReader::pos_type(3))); return output; } @@ -232,20 +232,18 @@ bool CSoundFile::ReadDSym(FileReader &file, ModLoadingFlags loadFlags) file.Rewind(); if(!file.ReadStruct(fileHeader) || !fileHeader.Validate()) return false; - if(!file.CanRead(mpt::saturate_cast(fileHeader.GetHeaderMinimumAdditionalSize()))) + if(!file.CanRead(mpt::saturate_cast(fileHeader.GetHeaderMinimumAdditionalSize()))) return false; if(loadFlags == onlyVerifyHeader) return true; - InitializeGlobals(MOD_TYPE_MOD); + InitializeGlobals(MOD_TYPE_MOD, fileHeader.numChannels); m_SongFlags.set(SONG_IMPORTED | SONG_AMIGALIMITS); m_SongFlags.reset(SONG_ISAMIGA); - m_nChannels = fileHeader.numChannels; m_nSamples = 63; - for(CHANNELINDEX chn = 0; chn < m_nChannels; chn++) + for(CHANNELINDEX chn = 0; chn < GetNumChannels(); chn++) { - InitChannel(chn); ChnSettings[chn].nPan = (((chn & 3) == 1) || ((chn & 3) == 2)) ? 64 : 192; } @@ -293,14 +291,14 @@ bool CSoundFile::ReadDSym(FileReader &file, ModLoadingFlags loadFlags) if(!(loadFlags & loadPatternData) || !Patterns.Insert(pat, 64)) continue; - for(CHANNELINDEX chn = 0; chn < m_nChannels; chn++) + for(CHANNELINDEX chn = 0; chn < GetNumChannels(); chn++) { - const uint16 track = sequence[pat * m_nChannels + chn]; + const uint16 track = sequence[pat * GetNumChannels() + chn]; if(track >= fileHeader.numTracks) continue; ModCommand *m = Patterns[pat].GetpModCommand(0, chn); - for(ROWINDEX row = 0; row < 64; row++, m += m_nChannels) + for(ROWINDEX row = 0; row < 64; row++, m += GetNumChannels()) { const auto data = tracks.subspan(track * 256 + row * 4, 4); m->note = data[0] & 0x3F; @@ -427,7 +425,7 @@ bool CSoundFile::ReadDSym(FileReader &file, ModLoadingFlags loadFlags) break; case 0x2B: // 2B xyy Line Jump m->command = CMD_PATTERNBREAK; - for(CHANNELINDEX brkChn = 0; brkChn < m_nChannels; brkChn++) + for(CHANNELINDEX brkChn = 0; brkChn < GetNumChannels(); brkChn++) { ModCommand &cmd = *(m - chn + brkChn); if(cmd.command != CMD_NONE) @@ -605,8 +603,8 @@ bool CSoundFile::ReadDSym(FileReader &file, ModLoadingFlags loadFlags) } m_modFormat.formatName = MPT_UFORMAT("Digital Symphony v{}")(fileHeader.version); - m_modFormat.type = U_("dsym"); // RISC OS doesn't use file extensions but this is a common abbreviation used for this tracker - m_modFormat.madeWithTracker = U_("Digital Symphony"); + m_modFormat.type = UL_("dsym"); // RISC OS doesn't use file extensions but this is a common abbreviation used for this tracker + m_modFormat.madeWithTracker = UL_("Digital Symphony"); m_modFormat.charset = mpt::Charset::RISC_OS; return true; diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_dtm.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_dtm.cpp index 14560e316..8e41796aa 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_dtm.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_dtm.cpp @@ -233,22 +233,41 @@ bool CSoundFile::ReadDTM(FileReader &file, ModLoadingFlags loadFlags) return true; } - InitializeGlobals(MOD_TYPE_DTM); - InitializeChannels(); - m_SongFlags.set(SONG_ITCOMPATGXX | SONG_ITOLDEFFECTS); + std::string songName; + file.ReadString(songName, fileHeader.headerSize - (sizeof(fileHeader) - 8u)); + + auto chunks = ChunkReader(file).ReadChunks(1); + + // Read pattern properties + uint32 patternFormat; + if(FileReader chunk = chunks.GetChunk(DTMChunk::idPATT)) + { + const uint16 numChannels = chunk.ReadUint16BE(); + if(numChannels < 1 || numChannels > 32) + return false; + + InitializeGlobals(MOD_TYPE_DTM, numChannels); + + Patterns.ResizeArray(chunk.ReadUint16BE()); // Number of stored patterns, may be lower than highest pattern number + patternFormat = chunk.ReadUint32BE(); + if(patternFormat != DTM_PT_PATTERN_FORMAT && patternFormat != DTM_204_PATTERN_FORMAT && patternFormat != DTM_206_PATTERN_FORMAT) + return false; + } else + { + return false; + } + + m_SongFlags.set(SONG_ITCOMPATGXX | SONG_ITOLDEFFECTS | SONG_FASTPORTAS); m_playBehaviour.reset(kPeriodsAreHertz); m_playBehaviour.reset(kITVibratoTremoloPanbrello); // Various files have a default speed or tempo of 0 if(fileHeader.tempo) - m_nDefaultTempo.Set(fileHeader.tempo); + Order().SetDefaultTempoInt(fileHeader.tempo); if(fileHeader.speed) - m_nDefaultSpeed = fileHeader.speed; + Order().SetDefaultSpeed(fileHeader.speed); if(fileHeader.stereoMode == 0) SetupMODPanning(true); - - file.ReadString(m_songName, fileHeader.headerSize - (sizeof(fileHeader) - 8u)); - - auto chunks = ChunkReader(file).ReadChunks(1); + m_songName = std::move(songName); // Read order list if(FileReader chunk = chunks.GetChunk(DTMChunk::idS_Q_)) @@ -263,32 +282,12 @@ bool CSoundFile::ReadDTM(FileReader &file, ModLoadingFlags loadFlags) return false; } - // Read pattern properties - uint32 patternFormat; - if(FileReader chunk = chunks.GetChunk(DTMChunk::idPATT)) - { - m_nChannels = chunk.ReadUint16BE(); - if(m_nChannels < 1 || m_nChannels > 32) - { - return false; - } - Patterns.ResizeArray(chunk.ReadUint16BE()); // Number of stored patterns, may be lower than highest pattern number - patternFormat = chunk.ReadUint32BE(); - if(patternFormat != DTM_PT_PATTERN_FORMAT && patternFormat != DTM_204_PATTERN_FORMAT && patternFormat != DTM_206_PATTERN_FORMAT) - { - return false; - } - } else - { - return false; - } - // Read global info if(FileReader chunk = chunks.GetChunk(DTMChunk::idSV19)) { chunk.Skip(2); // Ticks per quarter note, typically 24 uint32 fractionalTempo = chunk.ReadUint32BE(); - m_nDefaultTempo = TEMPO(m_nDefaultTempo.GetInt() + fractionalTempo / 4294967296.0); + Order().SetDefaultTempo(TEMPO(Order().GetDefaultTempo().GetInt() + fractionalTempo / 4294967296.0)); uint16be panning[32]; chunk.ReadArray(panning); @@ -416,7 +415,7 @@ bool CSoundFile::ReadDTM(FileReader &file, ModLoadingFlags loadFlags) if(patternFormat == DTM_206_PATTERN_FORMAT) { // The stored data is actually not row-based, but tick-based. - numRows /= m_nDefaultSpeed; + numRows /= Order().GetDefaultSpeed(); } if(!(loadFlags & loadPatternData) || patNum > 255 || !Patterns.Insert(patNum, numRows)) { @@ -472,7 +471,7 @@ bool CSoundFile::ReadDTM(FileReader &file, ModLoadingFlags loadFlags) tick += (delay & 0x7F) * 0x100 + rowChunk.ReadUint8(); else tick += delay; - position = std::div(tick, m_nDefaultSpeed); + position = std::div(tick, Order().GetDefaultSpeed()); } } } else @@ -486,13 +485,12 @@ bool CSoundFile::ReadDTM(FileReader &file, ModLoadingFlags loadFlags) const auto [note, instrVol, instrCmd, param] = data; if(note > 0 && note < 0x80) { - m.note = (note >> 4) * 12 + (note & 0x0F) + NOTE_MIN + 11; + m.note = static_cast((note >> 4) * 12 + (note & 0x0F) + NOTE_MIN + 11); } uint8 vol = instrVol >> 2; if(vol) { - m.volcmd = VOLCMD_VOLUME; - m.vol = vol - 1u; + m.SetVolumeCommand(VOLCMD_VOLUME, static_cast(vol - 1u)); } m.instr = ((instrVol & 0x03) << 4) | (instrCmd >> 4); command = instrCmd & 0x0F; @@ -500,7 +498,7 @@ bool CSoundFile::ReadDTM(FileReader &file, ModLoadingFlags loadFlags) } else { std::tie(command, m.param) = ReadMODPatternEntry(data, m); - m.instr |= data[0] & 0x30; // Allow more than 31 instruments + m.instr |= static_cast(data[0] & 0x30u); // Allow more than 31 instruments } ConvertModCommand(m, command, m.param); // Fix commands without memory and slide nibble precedence @@ -575,20 +573,20 @@ bool CSoundFile::ReadDTM(FileReader &file, ModLoadingFlags loadFlags) mpt::ustring tracker; if(patternFormat == DTM_206_PATTERN_FORMAT) { - tracker = U_("Digital Home Studio"); + tracker = UL_("Digital Home Studio"); } else if(patternFormat == DTM_PT_PATTERN_FORMAT) { - tracker = U_("Digital Tracker 2.3"); + tracker = UL_("Digital Tracker 2.3"); } else if(FileReader chunk = chunks.GetChunk(DTMChunk::idVERS)) { uint32 version = chunk.ReadUint32BE(); tracker = MPT_UFORMAT("Digital Tracker {}.{}")(version >> 4, version & 0x0F); } else { - tracker = U_("Digital Tracker"); + tracker = UL_("Digital Tracker"); } - m_modFormat.formatName = U_("Digital Tracker"); - m_modFormat.type = U_("dtm"); + m_modFormat.formatName = UL_("Digital Tracker"); + m_modFormat.type = UL_("dtm"); m_modFormat.madeWithTracker = std::move(tracker); m_modFormat.charset = mpt::Charset::Amiga_no_C1; diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_etx.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_etx.cpp new file mode 100644 index 000000000..4f3f13a8b --- /dev/null +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_etx.cpp @@ -0,0 +1,177 @@ +/* + * Load_etx.cpp + * ------------ + * Purpose: EasyTrax (ETX) module loader + * Notes : (currently none) + * Authors: OpenMPT Devs + * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. + */ + + +#include "stdafx.h" +#include "Loaders.h" + +OPENMPT_NAMESPACE_BEGIN + +struct ETXFileHeader +{ + char magic[14]; // 'EASYTRAX 1.0\x01\x00' + uint8 tempo; + uint8 lastPattern; + uint32le orderlistOffset; + uint32le patternsOffset; + uint32le sampleHeadersOffset; + uint32le sampleDataOffset; + + bool IsValid() const + { + return !memcmp(magic, "EASYTRAX 1.0\x01\x00", 14) + && tempo > 0 + && lastPattern <= 127 + && orderlistOffset >= 32 && orderlistOffset < 0x80'0000 + && patternsOffset >= 32 && patternsOffset < 0x80'0000 + && sampleHeadersOffset >= 32 && sampleHeadersOffset < 0x80'0000 + && sampleDataOffset >= 32 && sampleDataOffset < 0x80'0000; + } + + uint32 GetHeaderMinimumAdditionalSize() const + { + return 1024; // Order list + } +}; + +MPT_BINARY_STRUCT(ETXFileHeader, 32) + + +struct ETXSampleHeader +{ + char name[13]; + uint32le offset; // Relative to fileHeader.sampleDataOffset + uint32le length; + uint32le loopStart; // 0xFFFFFFFF if no loop + uint32le sampleRate; + int8 transpose; + int8 finetune; + uint8 zero; + + void ConvertToMPT(ModSample &mptSmp) const + { + mptSmp.Initialize(); + mptSmp.nC5Speed = sampleRate; + mptSmp.nLength = length; + if(loopStart != uint32_max) + { + mptSmp.nLoopStart = loopStart; + mptSmp.nLoopEnd = mptSmp.nLength; + mptSmp.uFlags.set(CHN_LOOP); + } + mptSmp.Transpose((transpose * 100 + finetune) / 1200.0); + } +}; + +MPT_BINARY_STRUCT(ETXSampleHeader, 32) + + +CSoundFile::ProbeResult CSoundFile::ProbeFileHeaderETX(MemoryFileReader file, const uint64 *pfilesize) +{ + ETXFileHeader fileHeader; + if(!file.ReadStruct(fileHeader)) + return ProbeWantMoreData; + if(!fileHeader.IsValid()) + return ProbeFailure; + return ProbeAdditionalSize(file, pfilesize, fileHeader.GetHeaderMinimumAdditionalSize()); +} + + +bool CSoundFile::ReadETX(FileReader &file, ModLoadingFlags loadFlags) +{ + ETXFileHeader fileHeader; + file.Rewind(); + if(!file.ReadStruct(fileHeader) || !fileHeader.IsValid()) + return false; + if(!file.CanRead(fileHeader.GetHeaderMinimumAdditionalSize())) + return false; + if(loadFlags == onlyVerifyHeader) + return true; + + InitializeGlobals(MOD_TYPE_S3M, 4); + m_SongFlags.set(SONG_IMPORTED); + m_playBehaviour.reset(kST3EffectMemory); + m_nMinPeriod = 218; // Highest possible sample playback rate appears to be 65535 Hz + m_nSamples = 128; + Order().SetDefaultTempoInt(fileHeader.tempo); + Order().SetDefaultSpeed(6); + + if(!file.Seek(fileHeader.orderlistOffset)) + return false; + ReadOrderFromFile(Order(), file, 1024, 0xFF); + for(ORDERINDEX ord = 0; ord < Order().size(); ord++) + { + if(Order()[ord] == PATTERNINDEX_INVALID) + Order().resize(ord); + else if(Order()[ord] > 127) + return false; + } + + if(!file.Seek(fileHeader.patternsOffset)) + return false; + Patterns.ResizeArray(fileHeader.lastPattern + 1); + for(PATTERNINDEX pat = 0; pat < Patterns.Size(); pat++) + { + if(!(loadFlags & loadPatternData) || !file.CanRead(1024) || !Patterns.Insert(pat, 64)) + break; + + auto m = Patterns[pat].begin(); + for(ROWINDEX row = 0; row < Patterns[pat].GetNumRows(); row++) + { + for(CHANNELINDEX chn = 0; chn < 4; chn++, m++) + { + const auto [note, vol, instr, unused] = file.ReadArray(); + MPT_UNUSED_VARIABLE(unused); + if(note == 0xFF && !chn) + { + if(!Patterns[pat].WriteEffect(EffectWriter(CMD_PATTERNBREAK, 0).Row(std::max(row, ROWINDEX(1)) - 1))) + { + Patterns[pat].Resize(row, false); + break; + } + } else if(note == 0xFE) + { + m->SetEffectCommand(CMD_VOLUMEDOWN_ETX, vol); + } else if(note > 0 && note <= 96) + { + m->note = NOTE_MIDDLEC - 24 + note; + m->instr = instr + 1; + m->SetVolumeCommand(VOLCMD_VOLUME, static_cast((std::min(vol, uint8(127)) + 1u) / 2u)); + } + } + } + } + + if(!file.Seek(fileHeader.sampleHeadersOffset)) + return false; + FileReader sampleHeaderChunk = file.ReadChunk(128 * sizeof(ETXSampleHeader)); + for(SAMPLEINDEX smp = 1; smp <= m_nSamples; smp++) + { + ETXSampleHeader sampleHeader; + sampleHeaderChunk.ReadStruct(sampleHeader); + sampleHeader.ConvertToMPT(Samples[smp]); + m_szNames[smp] = mpt::String::ReadBuf(mpt::String::maybeNullTerminated, sampleHeader.name); + if(loadFlags & loadSampleData) + { + if(!file.Seek(fileHeader.sampleDataOffset + sampleHeader.offset)) + return false; + SampleIO(SampleIO::_8bit, SampleIO::mono, SampleIO::littleEndian, SampleIO::unsignedPCM) + .ReadSample(Samples[smp], file); + } + } + + m_modFormat.formatName = UL_("EasyTrax"); + m_modFormat.type = UL_("etx"); + m_modFormat.charset = mpt::Charset::CP437; + + return true; +} + + +OPENMPT_NAMESPACE_END diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_far.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_far.cpp index 6992ffb6a..ef35846b9 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_far.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_far.cpp @@ -152,7 +152,7 @@ bool CSoundFile::ReadFAR(FileReader &file, ModLoadingFlags loadFlags) { return false; } - if(!file.CanRead(mpt::saturate_cast(GetHeaderMinimumAdditionalSize(fileHeader)))) + if(!file.CanRead(mpt::saturate_cast(GetHeaderMinimumAdditionalSize(fileHeader)))) { return false; } @@ -162,17 +162,16 @@ bool CSoundFile::ReadFAR(FileReader &file, ModLoadingFlags loadFlags) } // Globals - InitializeGlobals(MOD_TYPE_FAR); - m_nChannels = 16; + InitializeGlobals(MOD_TYPE_FAR, 16); m_nSamplePreAmp = 32; - m_nDefaultSpeed = fileHeader.defaultSpeed; - m_nDefaultTempo.Set(80); + Order().SetDefaultSpeed(fileHeader.defaultSpeed); + Order().SetDefaultTempoInt(80); m_nDefaultGlobalVolume = MAX_GLOBAL_VOLUME; - m_SongFlags = SONG_LINEARSLIDES; + m_SongFlags = SONG_LINEARSLIDES | SONG_AUTO_TONEPORTA | SONG_AUTO_TONEPORTA_CONT; m_playBehaviour.set(kPeriodsAreHertz); - m_modFormat.formatName = U_("Farandole Composer"); - m_modFormat.type = U_("far"); + m_modFormat.formatName = UL_("Farandole Composer"); + m_modFormat.type = UL_("far"); m_modFormat.charset = mpt::Charset::CP437; m_songName = mpt::String::ReadBuf(mpt::String::maybeNullTerminated, fileHeader.songName); @@ -180,7 +179,6 @@ bool CSoundFile::ReadFAR(FileReader &file, ModLoadingFlags loadFlags) // Read channel settings for(CHANNELINDEX chn = 0; chn < 16; chn++) { - ChnSettings[chn].Reset(); ChnSettings[chn].dwFlags = fileHeader.onOff[chn] ? ChannelFlags(0) : CHN_MUTE; ChnSettings[chn].nPan = ((fileHeader.chnPanning[chn] & 0x0F) << 4) + 8; } @@ -244,12 +242,9 @@ bool CSoundFile::ReadFAR(FileReader &file, ModLoadingFlags loadFlags) ROWINDEX breakRow = patternChunk.ReadUint8(); patternChunk.Skip(1); if(breakRow > 0 && breakRow < numRows - 2) - { breakRow++; - } else - { + else breakRow = ROWINDEX_INVALID; - } // Read pattern data for(ROWINDEX row = 0; row < numRows; row++) @@ -266,8 +261,7 @@ bool CSoundFile::ReadFAR(FileReader &file, ModLoadingFlags loadFlags) if(volume > 0 && volume <= 16) { - m.volcmd = VOLCMD_VOLUME; - m.vol = (volume - 1u) * 64u / 15u; + m.SetVolumeCommand(VOLCMD_VOLUME, static_cast((volume - 1u) * 64u / 15u)); } m.param = effect & 0x0F; @@ -278,25 +272,26 @@ bool CSoundFile::ReadFAR(FileReader &file, ModLoadingFlags loadFlags) case 0x02: m.param |= 0xF0; break; - case 0x03: // Porta to note (TODO: Parameter is number of rows the portamento should take) - m.param <<= 2; + case 0x03: // Porta to note (TODO: Parameter is number of rows the portamento should take) + if(m.param != 0) + m.param = 60 / m.param; break; - case 0x04: // Retrig - m.param = 6 / (1 + (m.param & 0xf)) + 1; // ugh? + case 0x04: // Retrig + m.param = static_cast(6 / (1 + (m.param & 0xf)) + 1); break; - case 0x06: // Vibrato speed - case 0x07: // Volume slide up + case 0x06: // Vibrato speed + case 0x07: // Volume slide up m.param *= 8; break; - case 0x0A: // Volume-portamento (what!) + case 0x0A: // Volume-portamento (what!) m.volcmd = VOLCMD_VOLUME; - m.vol = (m.param << 2) + 4; + m.vol = static_cast((m.param << 2) + 4); break; - case 0x0B: // Panning + case 0x0B: // Panning m.param |= 0x80; break; - case 0x0C: // Note offset - m.param = 6 / (1 + m.param) + 1; + case 0x0C: // Note offset + m.param = static_cast(6 / (1 + m.param) + 1); m.param |= 0x0D; } m.command = farEffects[effect >> 4]; diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_fc.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_fc.cpp new file mode 100644 index 000000000..6203f96f5 --- /dev/null +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_fc.cpp @@ -0,0 +1,553 @@ +/* + * Load_fc.cpp + * ------------- + * Purpose: Future Composer 1.0 - 1.4 module loader + * Notes : (currently none) + * Authors: OpenMPT Devs + * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. + */ + + +#include "stdafx.h" +#include "Loaders.h" +#include "InstrumentSynth.h" + +OPENMPT_NAMESPACE_BEGIN + +struct FCPlaylistEntry +{ + struct Entry + { + uint8 pattern; + int8 noteTranspose; + int8 instrTranspose; + }; + + std::array channels; + uint8 speed; +}; + +MPT_BINARY_STRUCT(FCPlaylistEntry, 13) + + +struct FCSampleInfo +{ + uint16be length; // In words + uint16be loopStart; // In bytes + uint16be loopLength; // In words + + void ConvertToMPT(ModSample &mptSmp, FileReader &file, bool isFC14, bool loadSampleData) const + { + mptSmp.Initialize(MOD_TYPE_MOD); + mptSmp.nLength = length * 2; + if(isFC14 && mptSmp.nLength) + mptSmp.nLength += 2; + mptSmp.nLoopStart = loopStart; + mptSmp.nLoopEnd = mptSmp.nLoopStart + loopLength * 2; + mptSmp.uFlags.set(CHN_LOOP, loopLength > 1); + + // Fix for axel foley remix.smod (loop extends into next sample) + auto nextSampleStart = file.GetPosition() + mptSmp.nLength; + if(mptSmp.uFlags[CHN_LOOP] && mptSmp.nLoopEnd > mptSmp.nLength) + mptSmp.nLength = mptSmp.nLoopEnd; + if(loadSampleData) + { + SampleIO{SampleIO::_8bit, SampleIO::mono, SampleIO::bigEndian, SampleIO::signedPCM} + .ReadSample(mptSmp, file); + } + file.Seek(nextSampleStart); + } +}; + +MPT_BINARY_STRUCT(FCSampleInfo, 6) + + +struct FCFileHeader +{ + char magic[4]; // "SMOD" (Future Composer 1.0 - 1.3) or "FC14" (Future Composer 1.4) + uint32be sequenceSize; // All sizes in bytes + uint32be patternsOffset; + uint32be patternsSize; + uint32be freqSequenceOffset; + uint32be freqSequenceSize; + uint32be volSequenceOffset; + uint32be volSequenceSize; + uint32be sampleDataOffset; + uint32be waveTableOffset; // or sample data size if version < 1.4 + + std::array sampleInfo; + + bool IsValid() const + { + if(memcmp(magic, "SMOD", 4) && memcmp(magic, "FC14", 4)) + return false; + + static constexpr uint32 MAX_FC_SIZE = 0x8'0000; // According to manual: Sample memory allocated = 100,000 bytes + if(sequenceSize % sizeof(FCPlaylistEntry) > 1 // Some files have a mysterious extra byte + || sequenceSize < sizeof(FCPlaylistEntry) || sequenceSize > 256 * 13 + || patternsSize % 64u || !patternsSize || patternsSize > 64 * 256 || patternsOffset > MAX_FC_SIZE + || freqSequenceSize % 64u || !freqSequenceSize || freqSequenceSize > 64 * 256 || freqSequenceOffset > MAX_FC_SIZE + || volSequenceSize % 64u || !volSequenceSize || volSequenceSize > 64 * 256 || volSequenceOffset > MAX_FC_SIZE + || sampleDataOffset > MAX_FC_SIZE + || waveTableOffset > MAX_FC_SIZE) + return false; + + return true; + } + + bool IsFC14() const + { + return !memcmp(magic, "FC14", 4); + } + + ORDERINDEX NumOrders() const + { + return static_cast(sequenceSize / sizeof(FCPlaylistEntry)); + } + + uint32 GetHeaderMinimumAdditionalSize() const + { + return sequenceSize + (IsFC14() ? 80 : 0); + } +}; + +MPT_BINARY_STRUCT(FCFileHeader, 100) + + +static void TranslateFCScript(InstrumentSynth::Events &events, const mpt::span script, const bool isFC14, const uint16 startSequence = uint16_max) +{ + FileReader file{script}; + const bool isVolume = startSequence > 255; + const uint8 volScriptSpeed = (script[0] > 0) ? script[0] : uint8_max; + if(isVolume) + events.push_back(InstrumentSynth::Event::SetStepSpeed(volScriptSpeed, true)); + + std::vector sequencesToParse(1, static_cast(isVolume ? 0 : startSequence)); + std::bitset<256> parsedSequences; + parsedSequences.set(sequencesToParse.back()); + + std::map entryFromByte; + events.push_back(InstrumentSynth::Event::JumpMarker(0)); + while(!sequencesToParse.empty()) + { + const uint16 currentSequenceOffset = sequencesToParse.back() * 64u; + sequencesToParse.pop_back(); + file.Seek(currentSequenceOffset); + if(isVolume) + file.Skip(5); + + // Due to an underflow bug in the volume command E0, it can jump up to 260 bytes from the start of the chosen volume sequence. + // Luckily this is not possible with frequency sequences - as multiple frequency sequences can be chained using command E7, + // not having to care for that bug there makes the book-keeping a bit easier for us, as jumps will always be "local" within the currently selected sequence, + // and a jump target address cannot belong to two parsed sequences. + const uint16 maxScriptJumpPos = static_cast(currentSequenceOffset + (isVolume ? 260 : 63)); + uint16 maxJump = 0; + bool nextByteIsPitch = false; + while(file.CanRead(1)) + { + uint16 scriptPos = static_cast(file.GetPosition()); + if(scriptPos <= maxScriptJumpPos) + entryFromByte[scriptPos] = static_cast(events.size()); + const uint8 b = file.ReadUint8(); + + // After several pitch Ex commands, the next byte is always interpreted as Set Pitch + if(nextByteIsPitch) + { + events.push_back(InstrumentSynth::Event::FC_SetPitch(b)); + nextByteIsPitch = false; + continue; + } + + const auto volumeCommand = InstrumentSynth::Event::MED_SetVolume(static_cast(std::min(b & 0x7F, 64))); + switch(b) + { + case 0xE0: // Loop (position) + { + uint16 target = file.ReadUint8() & 0x3F; + if(isVolume && target < 5) // Volume sequence offset is relative to first volume command at offset 5, so FC subtracts 5 from the jump target and happily underflows the byte value + target += 256; + target += currentSequenceOffset; + if(!isVolume && target < script.size() && (script[target] == 0xE0 || script[target] == 0xE1)) + { + // The first byte that is executed after an E0 jump is never interpreted as command E0 or E1. + events.push_back(InstrumentSynth::Event::FC_SetPitch(script[target])); + target++; + if(target < script.size() && script[target - 1] == 0xE0) + { + events.push_back(InstrumentSynth::Event::FC_SetPitch(script[target])); + target++; + } + } + maxJump = std::max(maxJump, target); + events.push_back(InstrumentSynth::Event::Jump(target)); + } + break; + case 0xE1: // End (none) + events.push_back(InstrumentSynth::Event::StopScript()); + break; + case 0xE2: // Set waveform (waveform) + if(!isVolume) + events.push_back(InstrumentSynth::Event::MED_JumpScript(1, 0)); + [[fallthrough]]; + case 0xE4: // Change waveform (waveform) + if(isVolume) + events.push_back(volumeCommand); + else + events.push_back(InstrumentSynth::Event::FC_SetWaveform(b, file.ReadUint8(), 0)); + break; + case 0xE3: // Set new vibrato (speed, amplitude) + if(isVolume) + { + events.push_back(volumeCommand); + } else + { + const auto [speed, depth] = file.ReadArray(); + events.push_back(InstrumentSynth::Event::FC_SetVibrato(speed, depth, 0)); + } + break; + case 0xE7: // Jump to freq. sequence (sequence) + if(isVolume) + { + events.push_back(volumeCommand); + } else + { + uint8 sequence = file.ReadUint8(); + events.push_back(InstrumentSynth::Event::Jump(sequence * 64u)); + if(!parsedSequences[sequence]) + { + parsedSequences.set(sequence); + sequencesToParse.push_back(sequence); + } + } + break; + case 0xE8: // Sustain (time) + { + const uint8 delay = file.ReadUint8(); + if(isVolume && volScriptSpeed > 1) + { + events.push_back(InstrumentSynth::Event::SetStepSpeed(1, true)); + events.push_back(InstrumentSynth::Event::Delay(static_cast(delay + volScriptSpeed - 2))); + events.push_back(InstrumentSynth::Event::SetStepSpeed(volScriptSpeed, true)); + } else if(delay) + { + events.push_back(InstrumentSynth::Event::Delay(delay - 1)); + } + } + break; + case 0xE9: // Set waveform (waveform, number) + if(isVolume) + { + events.push_back(volumeCommand); + } else if(isFC14) + { + const auto [waveform, subSample] = file.ReadArray(); + events.push_back(InstrumentSynth::Event::MED_JumpScript(1, 0)); + events.push_back(InstrumentSynth::Event::FC_SetWaveform(b, subSample, waveform)); + } else + { + events.push_back(InstrumentSynth::Event::FC_SetPitch(b)); + } + break; + case 0xEA: // Volume slide / Pitch bend (step, time) + if(isFC14) + { + const auto [speed, time] = file.ReadArray(); + if(isVolume) + events.push_back(InstrumentSynth::Event::FC_VolumeSlide(speed, time)); + else + events.push_back(InstrumentSynth::Event::FC_PitchSlide(speed, time)); + break; + } + [[fallthrough]]; + default: + if(isVolume) + events.push_back(volumeCommand); + else + events.push_back(InstrumentSynth::Event::FC_SetPitch(b)); + break; + } + if(!isVolume) + nextByteIsPitch = (b >= 0xE2 && b <= 0xE4) || (isFC14 && (b == 0xE9 || b == 0xEA)); + + // If a sequence doesn't end with an E0/E1/E7 command, execution will continue in the next sequence. + // In the general case, we cannot stop translating the script at an E0/E1 command because we might jump beyond it with an E0 command. + // However, E0 commands cannot jump beyond the end of the initial sequence (modulo underflow, see above), so we can stop translating additonal sequences when we hit command E0/E1 there. + if((b == 0xE0 || b == 0xE1 || (b == 0xE7 && !isVolume)) && (maxJump <= scriptPos || scriptPos >= maxScriptJumpPos)) + break; + } + } + + for(auto &event : events) + { + event.FixupJumpTarget(entryFromByte); + } +} + + +CSoundFile::ProbeResult CSoundFile::ProbeFileHeaderFC(MemoryFileReader file, const uint64 *pfilesize) +{ + FCFileHeader fileHeader; + if(!file.ReadStruct(fileHeader)) + return ProbeWantMoreData; + if(!fileHeader.IsValid()) + return ProbeFailure; + return ProbeAdditionalSize(file, pfilesize, fileHeader.GetHeaderMinimumAdditionalSize()); +} + + +bool CSoundFile::ReadFC(FileReader &file, ModLoadingFlags loadFlags) +{ + FCFileHeader fileHeader; + + file.Rewind(); + if(!file.ReadStruct(fileHeader) || !fileHeader.IsValid()) + return false; + if(!file.CanRead(fileHeader.GetHeaderMinimumAdditionalSize())) + return false; + if(loadFlags == onlyVerifyHeader) + return true; + + const bool isFC14 = fileHeader.IsFC14(); + InitializeGlobals(MOD_TYPE_MOD, 4); + SetupMODPanning(true); + m_SongFlags.set(SONG_IMPORTED | SONG_ISAMIGA); + m_nSamples = isFC14 ? 90 : 57; + m_nInstruments = std::min(static_cast(fileHeader.volSequenceSize / 64u + 1), static_cast(MAX_INSTRUMENTS - 1)); + m_playBehaviour.set(kMODSampleSwap); + m_playBehaviour.set(kApplyUpperPeriodLimit); + m_nMinPeriod = 113 * 4; + m_nMaxPeriod = 3424 * 4; + + m_modFormat.formatName = isFC14 ? UL_("Future Composer 1.4") : UL_("Future Composer 1.0 - 1.3"); + m_modFormat.type = isFC14 ? UL_("fc") : UL_("smod"); + m_modFormat.madeWithTracker = m_modFormat.formatName; + m_modFormat.charset = mpt::Charset::Amiga_no_C1; // No strings in this format... + + std::array waveTableLengths; + if(isFC14) + file.ReadArray(waveTableLengths); + + const ORDERINDEX numOrders = fileHeader.NumOrders(); + std::vector orderData; + file.ReadVector(orderData, numOrders); + + std::vector> patternData(fileHeader.patternsSize / 2u); + file.Seek(fileHeader.patternsOffset); + if(!file.ReadVector(patternData, fileHeader.patternsSize / 2u)) + return false; + + Order().SetDefaultSpeed(3); + Order().resize(numOrders); + if(loadFlags & loadPatternData) + Patterns.ResizeArray(numOrders); + std::array prevNote = {{}}; + for(ORDERINDEX ord = 0; ord < numOrders; ord++) + { + Order()[ord] = ord; + static constexpr uint16 numRows = 32; + if(!(loadFlags & loadPatternData) || !Patterns.Insert(ord, numRows)) + continue; + ROWINDEX lastRow = numRows; + for(CHANNELINDEX chn = 0; chn < 4; chn++) + { + const auto &chnInfo = orderData[ord].channels[chn]; + const uint16 patternOffset = chnInfo.pattern * numRows; + if(patternOffset >= patternData.size()) + continue; + ModCommand *m = Patterns[ord].GetpModCommand(0, chn); + const auto &pattern = mpt::as_span(patternData).subspan(patternOffset); + for(ROWINDEX row = 0; row < numRows; row++, m += GetNumChannels()) + { + const auto p = pattern[row]; + // Channels should be broken independently, but no tune in the wild relies on it, I think. + // (Vim's blue-funk has inconsistent pattern lengths in the very last order item but that's all I could find) + if(p[0] == 0x49) + lastRow = std::min(lastRow, row); + + if(p[0] > 0 && p[0] != 0x49) + { + prevNote[chn] = p[0]; + m->note = NOTE_MIN + ((chnInfo.noteTranspose + p[0]) & 0x7F); + if(int instr = (p[1] & 0x3F) + chnInfo.instrTranspose + 1; instr >= 1 && instr <= m_nInstruments) + m->instr = static_cast(instr); + else + m->instr = static_cast(m_nInstruments); + } else if(row == 0 && ord > 0 && orderData[ord - 1].channels[chn].noteTranspose != chnInfo.noteTranspose && prevNote[chn] > 0) + { + m->note = NOTE_MIN + ((chnInfo.noteTranspose + prevNote[chn]) & 0x7F); + if(p[1] & 0xC0) + m->SetVolumeCommand(VOLCMD_TONEPORTAMENTO, 9); + else + m->SetEffectCommand(CMD_TONEPORTA_DURATION, 0); + } + if(p[1] & 0xC0) + m->SetEffectCommand(CMD_AUTO_PORTAMENTO_FC, 0); + if(p[1] & 0x80) + { + const uint8 data = ((row + 1) < pattern.size()) ? pattern[row + 1][1] : uint8(0); + int8 param = data & 0x1F; + if(!isFC14) + param *= 2; + if(data > 0x1F) + param = -param; + m->param = static_cast(param); + } + } + } + if(orderData[ord].speed) + Patterns[ord].WriteEffect(EffectWriter(CMD_SPEED, orderData[ord].speed).RetryNextRow()); + if(lastRow < numRows) + Patterns[ord].WriteEffect(EffectWriter(CMD_PATTERNBREAK, 0).Row(std::max(lastRow, ROWINDEX(1)) - 1).RetryNextRow()); + } + + std::vector freqSequences, volSequences; + freqSequences.reserve(64 * 256); + if(!file.Seek(fileHeader.freqSequenceOffset) || !file.ReadVector(freqSequences, fileHeader.freqSequenceSize)) + return false; + freqSequences.resize(64 * 256); + volSequences.reserve(fileHeader.volSequenceSize + 8); + if(!file.Seek(fileHeader.volSequenceOffset) || !file.ReadVector(volSequences, fileHeader.volSequenceSize)) + return false; + + // Add an empty instrument for out-of-range instrument numbers + static constexpr std::array EmptyInstr = {0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xE1}; + volSequences.insert(volSequences.end(), EmptyInstr.begin(), EmptyInstr.end()); + + for(INSTRUMENTINDEX ins = 1; ins <= m_nInstruments; ins++) + { + const auto volSeq = mpt::as_span(volSequences).subspan((ins - 1) * 64u); + const auto freqSeq = mpt::as_span(freqSequences).subspan(volSeq[1] * 64u); + uint8 defaultSample = freqSeq[1]; + if(freqSeq[0] == 0xE9) + defaultSample = static_cast(90 + defaultSample * 10 + freqSeq[2]); + + ModInstrument *instr = AllocateInstrument(ins, defaultSample + 1); + if(!instr) + return false; + + static_assert(NOTE_MAX - NOTE_MIN + 1 >= 128, "Need at least a note range of 128 for correct emulation of note wrap-around logic in Future Composer"); + for(uint8 note = 0; note < static_cast(instr->NoteMap.size()); note++) + { + if(note < 48) + instr->NoteMap[note] = note + NOTE_MIDDLEC - 24; + else if(note < 60 || note >= 120) + instr->NoteMap[note] = NOTE_MIDDLEC + 23; + else + instr->NoteMap[note] = note + NOTE_MIDDLEC - 36 - 60; + } + + instr->synth.m_scripts.resize(2); + instr->synth.m_scripts[0].push_back(InstrumentSynth::Event::FC_SetVibrato(volSeq[2], volSeq[3], volSeq[4])); + TranslateFCScript(instr->synth.m_scripts[0], mpt::as_span(freqSequences), isFC14, volSeq[1]); + TranslateFCScript(instr->synth.m_scripts[1], volSeq, isFC14); + } + + if(!file.Seek(fileHeader.sampleDataOffset)) + return false; + for(SAMPLEINDEX smp = 0; smp < 10; smp++) + { + if(!fileHeader.sampleInfo[smp].length) + continue; + + if(isFC14 && file.ReadMagic("SSMP")) + { + m_nSamples = 190; + FileReader sampleHeaders = file.ReadChunk(160); + for(SAMPLEINDEX subSmp = 0; subSmp < 10; subSmp++) + { + FCSampleInfo sampleInfo; + sampleHeaders.Skip(4); + sampleHeaders.Read(sampleInfo); + sampleHeaders.Skip(6); + + sampleInfo.ConvertToMPT(Samples[91 + (smp * 10) + subSmp], file, true, (loadFlags & loadSampleData) != 0); + } + } else + { + fileHeader.sampleInfo[smp].ConvertToMPT(Samples[smp + 1], file, isFC14, (loadFlags& loadSampleData) != 0); + } + } + + if(!(loadFlags & loadSampleData)) + return true; + + static constexpr uint8 SampleLengths[] = + { + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, 16, + 8, 8, 8, 8, 8, 8, 8, 8, 16, 8, 16, 16, 8, 8, 24, // Future Composer 1.4 imports the last sample with a length of 32 instead of 48, causing some older FC files to be detuned. + }; + + static constexpr uint8 SampleData[] = + { + 0xC0, 0xC0, 0xD0, 0xD8, 0xE0, 0xE8, 0xF0, 0xF8, 0x00, 0xF8, 0xF0, 0xE8, 0xE0, 0xD8, 0xD0, 0xC8, 0x3F, 0x37, 0x2F, 0x27, 0x1F, 0x17, 0x0F, 0x07, 0xFF, 0x07, 0x0F, 0x17, 0x1F, 0x27, 0x2F, 0x37, + 0xC0, 0xC0, 0xD0, 0xD8, 0xE0, 0xE8, 0xF0, 0xF8, 0x00, 0xF8, 0xF0, 0xE8, 0xE0, 0xD8, 0xD0, 0xC8, 0xC0, 0x37, 0x2F, 0x27, 0x1F, 0x17, 0x0F, 0x07, 0xFF, 0x07, 0x0F, 0x17, 0x1F, 0x27, 0x2F, 0x37, + 0xC0, 0xC0, 0xD0, 0xD8, 0xE0, 0xE8, 0xF0, 0xF8, 0x00, 0xF8, 0xF0, 0xE8, 0xE0, 0xD8, 0xD0, 0xC8, 0xC0, 0xB8, 0x2F, 0x27, 0x1F, 0x17, 0x0F, 0x07, 0xFF, 0x07, 0x0F, 0x17, 0x1F, 0x27, 0x2F, 0x37, + 0xC0, 0xC0, 0xD0, 0xD8, 0xE0, 0xE8, 0xF0, 0xF8, 0x00, 0xF8, 0xF0, 0xE8, 0xE0, 0xD8, 0xD0, 0xC8, 0xC0, 0xB8, 0xB0, 0x27, 0x1F, 0x17, 0x0F, 0x07, 0xFF, 0x07, 0x0F, 0x17, 0x1F, 0x27, 0x2F, 0x37, + 0xC0, 0xC0, 0xD0, 0xD8, 0xE0, 0xE8, 0xF0, 0xF8, 0x00, 0xF8, 0xF0, 0xE8, 0xE0, 0xD8, 0xD0, 0xC8, 0xC0, 0xB8, 0xB0, 0xA8, 0x1F, 0x17, 0x0F, 0x07, 0xFF, 0x07, 0x0F, 0x17, 0x1F, 0x27, 0x2F, 0x37, + 0xC0, 0xC0, 0xD0, 0xD8, 0xE0, 0xE8, 0xF0, 0xF8, 0x00, 0xF8, 0xF0, 0xE8, 0xE0, 0xD8, 0xD0, 0xC8, 0xC0, 0xB8, 0xB0, 0xA8, 0xA0, 0x17, 0x0F, 0x07, 0xFF, 0x07, 0x0F, 0x17, 0x1F, 0x27, 0x2F, 0x37, + 0xC0, 0xC0, 0xD0, 0xD8, 0xE0, 0xE8, 0xF0, 0xF8, 0x00, 0xF8, 0xF0, 0xE8, 0xE0, 0xD8, 0xD0, 0xC8, 0xC0, 0xB8, 0xB0, 0xA8, 0xA0, 0x98, 0x0F, 0x07, 0xFF, 0x07, 0x0F, 0x17, 0x1F, 0x27, 0x2F, 0x37, + 0xC0, 0xC0, 0xD0, 0xD8, 0xE0, 0xE8, 0xF0, 0xF8, 0x00, 0xF8, 0xF0, 0xE8, 0xE0, 0xD8, 0xD0, 0xC8, 0xC0, 0xB8, 0xB0, 0xA8, 0xA0, 0x98, 0x90, 0x07, 0xFF, 0x07, 0x0F, 0x17, 0x1F, 0x27, 0x2F, 0x37, + 0xC0, 0xC0, 0xD0, 0xD8, 0xE0, 0xE8, 0xF0, 0xF8, 0x00, 0xF8, 0xF0, 0xE8, 0xE0, 0xD8, 0xD0, 0xC8, 0xC0, 0xB8, 0xB0, 0xA8, 0xA0, 0x98, 0x90, 0x88, 0xFF, 0x07, 0x0F, 0x17, 0x1F, 0x27, 0x2F, 0x37, + 0xC0, 0xC0, 0xD0, 0xD8, 0xE0, 0xE8, 0xF0, 0xF8, 0x00, 0xF8, 0xF0, 0xE8, 0xE0, 0xD8, 0xD0, 0xC8, 0xC0, 0xB8, 0xB0, 0xA8, 0xA0, 0x98, 0x90, 0x88, 0x80, 0x07, 0x0F, 0x17, 0x1F, 0x27, 0x2F, 0x37, + 0xC0, 0xC0, 0xD0, 0xD8, 0xE0, 0xE8, 0xF0, 0xF8, 0x00, 0xF8, 0xF0, 0xE8, 0xE0, 0xD8, 0xD0, 0xC8, 0xC0, 0xB8, 0xB0, 0xA8, 0xA0, 0x98, 0x90, 0x88, 0x80, 0x88, 0x0F, 0x17, 0x1F, 0x27, 0x2F, 0x37, + 0xC0, 0xC0, 0xD0, 0xD8, 0xE0, 0xE8, 0xF0, 0xF8, 0x00, 0xF8, 0xF0, 0xE8, 0xE0, 0xD8, 0xD0, 0xC8, 0xC0, 0xB8, 0xB0, 0xA8, 0xA0, 0x98, 0x90, 0x88, 0x80, 0x88, 0x90, 0x17, 0x1F, 0x27, 0x2F, 0x37, + 0xC0, 0xC0, 0xD0, 0xD8, 0xE0, 0xE8, 0xF0, 0xF8, 0x00, 0xF8, 0xF0, 0xE8, 0xE0, 0xD8, 0xD0, 0xC8, 0xC0, 0xB8, 0xB0, 0xA8, 0xA0, 0x98, 0x90, 0x88, 0x80, 0x88, 0x90, 0x98, 0x1F, 0x27, 0x2F, 0x37, + 0xC0, 0xC0, 0xD0, 0xD8, 0xE0, 0xE8, 0xF0, 0xF8, 0x00, 0xF8, 0xF0, 0xE8, 0xE0, 0xD8, 0xD0, 0xC8, 0xC0, 0xB8, 0xB0, 0xA8, 0xA0, 0x98, 0x90, 0x88, 0x80, 0x88, 0x90, 0x98, 0xA0, 0x27, 0x2F, 0x37, + 0xC0, 0xC0, 0xD0, 0xD8, 0xE0, 0xE8, 0xF0, 0xF8, 0x00, 0xF8, 0xF0, 0xE8, 0xE0, 0xD8, 0xD0, 0xC8, 0xC0, 0xB8, 0xB0, 0xA8, 0xA0, 0x98, 0x90, 0x88, 0x80, 0x88, 0x90, 0x98, 0xA0, 0xA8, 0x2F, 0x37, + 0xC0, 0xC0, 0xD0, 0xD8, 0xE0, 0xE8, 0xF0, 0xF8, 0x00, 0xF8, 0xF0, 0xE8, 0xE0, 0xD8, 0xD0, 0xC8, 0xC0, 0xB8, 0xB0, 0xA8, 0xA0, 0x98, 0x90, 0x88, 0x80, 0x88, 0x90, 0x98, 0xA0, 0xA8, 0xB0, 0x37, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x7F, 0x7F, 0x7F, 0x7F, + 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x7F, 0x7F, 0x7F, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x7F, 0x7F, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x7F, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, + 0x80, 0x80, 0x80, 0x80, 0x80, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, + 0x80, 0x80, 0x80, 0x80, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, + 0x80, 0x80, 0x80, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, + 0x80, 0x80, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, + 0x80, 0x80, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, + 0x80, 0x80, 0x90, 0x98, 0xA0, 0xA8, 0xB0, 0xB8, 0xC0, 0xC8, 0xD0, 0xD8, 0xE0, 0xE8, 0xF0, 0xF8, 0x00, 0x08, 0x10, 0x18, 0x20, 0x28, 0x30, 0x38, 0x40, 0x48, 0x50, 0x58, 0x60, 0x68, 0x70, 0x7F, + 0x80, 0x80, 0xA0, 0xB0, 0xC0, 0xD0, 0xE0, 0xF0, 0x00, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, + 0x45, 0x45, 0x79, 0x7D, 0x7A, 0x77, 0x70, 0x66, 0x61, 0x58, 0x53, 0x4D, 0x2C, 0x20, 0x18, 0x12, 0x04, 0xDB, 0xD3, 0xCD, 0xC6, 0xBC, 0xB5, 0xAE, 0xA8, 0xA3, 0x9D, 0x99, 0x93, 0x8E, 0x8B, 0x8A, + 0x45, 0x45, 0x79, 0x7D, 0x7A, 0x77, 0x70, 0x66, 0x5B, 0x4B, 0x43, 0x37, 0x2C, 0x20, 0x18, 0x12, 0x04, 0xF8, 0xE8, 0xDB, 0xCF, 0xC6, 0xBE, 0xB0, 0xA8, 0xA4, 0x9E, 0x9A, 0x95, 0x94, 0x8D, 0x83, + 0x00, 0x00, 0x40, 0x60, 0x7F, 0x60, 0x40, 0x20, 0x00, 0xE0, 0xC0, 0xA0, 0x80, 0xA0, 0xC0, 0xE0, + 0x00, 0x00, 0x40, 0x60, 0x7F, 0x60, 0x40, 0x20, 0x00, 0xE0, 0xC0, 0xA0, 0x80, 0xA0, 0xC0, 0xE0, + 0x80, 0x80, 0x90, 0x98, 0xA0, 0xA8, 0xB0, 0xB8, 0xC0, 0xC8, 0xD0, 0xD8, 0xE0, 0xE8, 0xF0, 0xF8, 0x00, 0x08, 0x10, 0x18, 0x20, 0x28, 0x30, 0x38, 0x40, 0x48, 0x50, 0x58, 0x60, 0x68, 0x70, 0x7F, 0x80, 0x80, 0xA0, 0xB0, 0xC0, 0xD0, 0xE0, 0xF0, 0x00, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, + }; + + if(isFC14 && !file.Seek(fileHeader.waveTableOffset)) + return false; + + FileReader smpFile{mpt::as_span(SampleData)}; + const auto sampleLengths = isFC14 ? mpt::span(waveTableLengths) : mpt::as_span(SampleLengths); + SampleIO sampleIO{SampleIO::_8bit, SampleIO::mono, SampleIO::bigEndian, SampleIO::signedPCM}; + for(SAMPLEINDEX smp = 0; smp < sampleLengths.size(); smp++) + { + ModSample &mptSmp = Samples[smp + 11]; + mptSmp.Initialize(MOD_TYPE_MOD); + mptSmp.nLength = sampleLengths[smp] * 2u; + mptSmp.nLoopStart = 0; + mptSmp.nLoopEnd = mptSmp.nLength; + mptSmp.uFlags.set(CHN_LOOP); + sampleIO.ReadSample(mptSmp, isFC14 ? file : smpFile); + } + + return true; +} + + +OPENMPT_NAMESPACE_END diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_fmt.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_fmt.cpp index 2b8a83a1f..09e4a9f89 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_fmt.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_fmt.cpp @@ -80,18 +80,16 @@ bool CSoundFile::ReadFMT(FileReader &file, ModLoadingFlags loadFlags) return false; if(!ValidateHeader(fileHeader)) return false; - if(!file.CanRead(mpt::saturate_cast(GetHeaderMinimumAdditionalSize(fileHeader)))) + if(!file.CanRead(mpt::saturate_cast(GetHeaderMinimumAdditionalSize(fileHeader)))) return false; if(loadFlags == onlyVerifyHeader) return true; - InitializeGlobals(MOD_TYPE_S3M); - InitializeChannels(); - m_nChannels = 8; + InitializeGlobals(MOD_TYPE_S3M, 8); m_nSamples = 8; - m_nDefaultTempo = TEMPO(45.5); // 18.2 Hz timer + Order().SetDefaultTempo(TEMPO(45.5)); // 18.2 Hz timer m_playBehaviour.set(kOPLNoteStopWith0Hz); - m_SongFlags.set(SONG_IMPORTED); + m_SongFlags.set(SONG_IMPORTED | SONG_FORMAT_NO_VOLCOL); m_songName = mpt::String::ReadBuf(mpt::String::maybeNullTerminated, fileHeader.songName); for(CHANNELINDEX chn = 0; chn < 8; chn++) @@ -118,7 +116,7 @@ bool CSoundFile::ReadFMT(FileReader &file, ModLoadingFlags loadFlags) if(delay < 1 || delay > 8) return false; } - m_nDefaultSpeed = delays[0]; + Order().SetDefaultSpeed(delays[0]); const PATTERNINDEX numPatterns = fileHeader.lastPattern + 1u; const ROWINDEX numRows = fileHeader.lastRow + 1u; @@ -184,8 +182,8 @@ bool CSoundFile::ReadFMT(FileReader &file, ModLoadingFlags loadFlags) m->param = delay; } - m_modFormat.formatName = U_("FM Tracker"); - m_modFormat.type = U_("fmt"); + m_modFormat.formatName = UL_("FM Tracker"); + m_modFormat.type = UL_("fmt"); m_modFormat.madeWithTracker = mpt::ToUnicode(mpt::Charset::CP437, mpt::String::ReadBuf(mpt::String::maybeNullTerminated, fileHeader.trackerName)); m_modFormat.charset = mpt::Charset::CP437; diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_ftm.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_ftm.cpp new file mode 100644 index 000000000..6484a32c6 --- /dev/null +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_ftm.cpp @@ -0,0 +1,490 @@ +/* + * Load_ftm.cpp + * ------------ + * Purpose: FTM (Face The Music) loader + * Notes : I actually bought a copy of Face The Music with its manual in order to write this loader. + * Authors: OpenMPT Devs + * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. + */ + + +#include "stdafx.h" +#include "Loaders.h" + +OPENMPT_NAMESPACE_BEGIN + +struct FTMSampleHeader +{ + char name[30]; // FTM cannot load samples with a filename longer than 29 characters + uint8 unknown; // Probably padding. Usually contains null or space (probably overflowing from name) + uint8 iffOctave; // Only relevant in song mode +}; + +MPT_BINARY_STRUCT(FTMSampleHeader, 32) + + +struct FTMFileHeader +{ + char magic[4]; // "FTMN" + uint8 version; // ...I guess? + uint8 numSamples; // 0...63 + uint16be numMeasures; + uint16be tempo; // Smaller values = faster, the default of 14209 is ~125.098 BPM (though it seems to fluctuate and *sometimes* it drops to 124.307 BPM?) + uint8 tonality; // Not relevant for playback (0 = C/a, 1 = Db/bb, etc.) + uint8 muteStatus; + uint8 globalVolume; // 0...63 + uint8 flags; // 1 = module (embedded samples), 2 = enable LED filter + uint8 ticksPerRow; + uint8 rowsPerMeasure; + char title[32]; + char artist[32]; + uint8 numEffects; + uint8 padding; + + bool IsValid() const + { + return !memcmp(magic, "FTMN", 4) + && version == 3 + && numSamples < 64 + && tempo >= 0x1000 && tempo <= 0x4FFF + && tonality < 12 + && globalVolume < 64 +#if !defined(MPT_EXTERNAL_SAMPLES) && !defined(MPT_BUILD_FUZZER) + && (flags & 0x01) +#endif + && !(flags & 0xFC) + && ticksPerRow >= 1 && ticksPerRow <= 24 + && rowsPerMeasure >= 4 && rowsPerMeasure <= 96 + && rowsPerMeasure == 96 / ticksPerRow + && numEffects <= 64 + && padding == 0; + } + + uint32 GetHeaderMinimumAdditionalSize() const + { + return static_cast(numSamples * sizeof(FTMSampleHeader) + numEffects * 4); + } +}; + +MPT_BINARY_STRUCT(FTMFileHeader, 82) + + +struct FTMScriptItem +{ + uint8 type; + std::array data; +}; + +MPT_BINARY_STRUCT(FTMScriptItem, 4) + + +CSoundFile::ProbeResult CSoundFile::ProbeFileHeaderFTM(MemoryFileReader file, const uint64 *pfilesize) +{ + FTMFileHeader fileHeader; + if(!file.ReadStruct(fileHeader)) + return ProbeWantMoreData; + if(!fileHeader.IsValid()) + return ProbeFailure; + return ProbeAdditionalSize(file, pfilesize, fileHeader.GetHeaderMinimumAdditionalSize()); +} + + +bool CSoundFile::ReadFTM(FileReader &file, ModLoadingFlags loadFlags) +{ + file.Rewind(); + FTMFileHeader fileHeader; + if(!file.ReadStruct(fileHeader)) + return false; + if(!fileHeader.IsValid()) + return false; + if(!file.CanRead(fileHeader.GetHeaderMinimumAdditionalSize())) + return false; + else if(loadFlags == onlyVerifyHeader) + return true; + + InitializeGlobals(MOD_TYPE_MOD, 8); + for(CHANNELINDEX chn = 0; chn < 8; chn++) + { + ChnSettings[chn].nPan = (chn < 2 || chn > 5) ? 64 : 192; + ChnSettings[chn].dwFlags.set(CHN_MUTE, !(fileHeader.muteStatus & (1 << chn))); + } + m_SongFlags.set(SONG_LINEARSLIDES | SONG_ISAMIGA | SONG_IMPORTED | SONG_FORMAT_NO_VOLCOL); + m_playBehaviour.set(kContinueSampleWithoutInstr); + m_playBehaviour.set(kST3NoMutedChannels); + m_playBehaviour.set(kApplyUpperPeriodLimit); + Order().SetDefaultSpeed(fileHeader.ticksPerRow); + Order().SetDefaultTempo(TEMPO(1777517.482 / fileHeader.tempo)); + m_nDefaultRowsPerMeasure = fileHeader.rowsPerMeasure; + if(fileHeader.ticksPerRow == 2 || fileHeader.ticksPerRow == 4 || fileHeader.ticksPerRow == 8 || fileHeader.ticksPerRow == 16) + m_nDefaultRowsPerBeat = m_nDefaultRowsPerMeasure / 3; + else + m_nDefaultRowsPerBeat = m_nDefaultRowsPerMeasure / 4; + m_nDefaultGlobalVolume = Util::muldivr_unsigned(fileHeader.globalVolume, MAX_GLOBAL_VOLUME, 63); + m_nMinPeriod = 3208; + m_nMaxPeriod = 5376; + const bool moduleWithSamples = (fileHeader.flags & 0x01); + + m_modFormat.formatName = UL_("Face The Music"); + m_modFormat.type = UL_("ftm"); + m_modFormat.madeWithTracker = UL_("Face The Music"); + m_modFormat.charset = mpt::Charset::Amiga_no_C1; + + m_songName = mpt::String::ReadBuf(mpt::String::nullTerminated, fileHeader.title); + m_songArtist = mpt::ToUnicode(mpt::Charset::Amiga_no_C1, mpt::String::ReadBuf(mpt::String::nullTerminated, fileHeader.artist)); + + m_nSamples = fileHeader.numSamples; + for(SAMPLEINDEX smp = 1; smp <= m_nSamples; smp++) + { + Samples[smp].Initialize(MOD_TYPE_MOD); + FTMSampleHeader sampleHeader; + file.ReadStruct(sampleHeader); + const auto name = mpt::String::ReadBuf(mpt::String::nullTerminated, sampleHeader.name); +#if defined(MPT_EXTERNAL_SAMPLES) + if(!moduleWithSamples && !name.empty() && (loadFlags & loadSampleData)) + { + const auto filename = mpt::PathString::FromUnicode(mpt::ToUnicode(mpt::Charset::Amiga_no_C1, name)); + bool ok = false; + if(const auto moduleFilename = file.GetOptionalFileName()) + { + // Try to load the sample directly, so that we can retain IFF loop points, octave selection and raw samples + mpt::IO::InputFile f(moduleFilename->GetDirectoryWithDrive() + filename, SettingCacheCompleteFileBeforeLoading()); + if(f.IsValid()) + { + FileReader sampleFile = GetFileReader(f); + if(!ReadIFFSample(smp, sampleFile, false, sampleHeader.iffOctave)) + { + SampleIO( + SampleIO::_8bit, + SampleIO::mono, + SampleIO::littleEndian, + SampleIO::signedPCM) + .ReadSample(Samples[smp], sampleFile); + } + ok = true; + } + } + if(!ok) + { + // Fall back to external sample mechanism. TODO: external sample loading ignores loop data on purpose, and does not support raw samples or IFF octave selection. How do we get it back? + Samples[smp].uFlags.set(SMP_KEEPONDISK); + SetSamplePath(smp, filename); + } + } +#endif // MPT_EXTERNAL_SAMPLES + m_szNames[smp] = name; + Samples[smp].nC5Speed = 8287; + Samples[smp].RelativeTone = Samples[smp].nFineTune = 0; + } + + auto &events = m_globalScript; + constexpr uint16 STOP_INDEX = uint16_max - 1; + std::map offsetToIndex; + std::bitset<64> effectDefined; + for(uint8 effect = 0; effect < fileHeader.numEffects; effect++) + { + const auto [numLines, index] = file.ReadArray(); + if(numLines > 0x200 || index > 63 || offsetToIndex.count(index << 10)) + return false; + effectDefined.set(index); + events.reserve(events.size() + 4 + numLines); + events.push_back(InstrumentSynth::Event::Jump(STOP_INDEX)); + events.push_back(InstrumentSynth::Event::JumpMarker(index)); + events.push_back(InstrumentSynth::Event::FTM_SetWorkTrack(uint8_max, false)); + events.push_back(InstrumentSynth::Event::FTM_SetInterrupt(uint16_max, uint8_max)); + + std::vector items; + file.ReadVector(items, numLines); + for(uint16 i = 0; i < numLines; i++) + { + const auto &item = items[i]; + const uint32 u24 = (item.data[0] << 16) | (item.data[1] << 8) | item.data[2]; + const uint16 u16 = static_cast(u24 & 0xFFFF); + const uint16 u12lo = u16 & 0xFFF, u12hi = static_cast(u24 >> 12); + const uint8 u8 = item.data[2]; + const uint16 jumpTarget = static_cast((index << 10) | std::min(u12lo, uint16(0x200))); + offsetToIndex[(index << 10u) | i] = static_cast(events.size()); + switch(item.type) + { + case 0: // NOP [......] + break; + case 1: // WAIT t TICKS [..tttt] + if(u16) + events.push_back(InstrumentSynth::Event::Delay(u16 - 1)); + else + events.push_back(InstrumentSynth::Event::StopScript()); + break; + case 2: // GOTO LINE l [...lll] + events.push_back(InstrumentSynth::Event::Jump(jumpTarget)); + break; + case 3: // LOOP x TIMES TO l [xxxlll] + events.push_back(InstrumentSynth::Event::SetLoopCounter(u12hi, false)); + events.push_back(InstrumentSynth::Event::EvaluateLoopCounter(jumpTarget)); + break; + case 4: // GOTO EFF e / LINE l [eeelll] + events.push_back(InstrumentSynth::Event::Jump(static_cast((u12hi << 10) | (jumpTarget & 0x3FF)))); + break; + case 5: // END OF EFFECT [......] + events.push_back(InstrumentSynth::Event::Jump(STOP_INDEX)); + break; + case 6: // IF PITCH=v GOTO l [ppplll] + case 7: // IF PITCHv GOTO l [ppplll] + case 9: // IF VOLUM=v GOTO l [vvvlll] + case 10: // IF VOLUMv GOTO l [vvvlll] + events.push_back(InstrumentSynth::Event::FTM_SetCondition(u12hi, item.type - 6)); + events.push_back(InstrumentSynth::Event::JumpIfTrue(jumpTarget)); + break; + case 12: // ON NEWPIT. GOTO l [...lll] + case 13: // ON NEWVOL. GOTO l [...lll] + case 14: // ON NEWSAM. GOTO l [...lll] + case 15: // ON RELEASE GOTO l [...lll] + case 16: // ON PB GOTO l [...lll] + case 17: // ON VD GOTO l [...lll] + events.push_back(InstrumentSynth::Event::FTM_SetInterrupt(jumpTarget, 1 << (item.type - 12))); + break; + case 18: // PLAY CURR. SAMPLE [......] + events.push_back(InstrumentSynth::Event::FTM_PlaySample()); + break; + case 19: // PLAY QUIET SAMPLE [......] + events.push_back(InstrumentSynth::Event::NoteCut()); + break; + case 20: // PLAYPOSITION = [.ppppp] + events.push_back(InstrumentSynth::Event::SampleOffset((u24 & 0xFFFFF) << 1)); + break; + case 21: // PLAYPOSITION ADD [.ppppp] + events.push_back(InstrumentSynth::Event::SampleOffsetAdd((u24 & 0xFFFFF) << 1)); + break; + case 22: // PLAYPOSITION SUB [.ppppp] + events.push_back(InstrumentSynth::Event::SampleOffsetSub((u24 & 0xFFFFF) << 1)); + break; + case 23: // PITCH = [...ppp] + events.push_back(InstrumentSynth::Event::FTM_SetPitch(std::min(u12lo, uint16(0x10F)))); + break; + case 24: // DETUNE = [...ddd] + events.push_back(InstrumentSynth::Event::FTM_SetDetune(u12lo)); + break; + case 25: // DETUNE/PITCH ADD [dddppp] + case 26: // DETUNE/PITCH SUB [dddppp] + events.push_back(InstrumentSynth::Event::FTM_AddDetune((item.type == 26) ? -static_cast(u12hi) : u12hi)); + events.push_back(InstrumentSynth::Event::FTM_AddPitch((item.type == 26) ? -static_cast(u12lo) : u12lo)); + break; + case 27: // VOLUME = [....vv] + events.push_back(InstrumentSynth::Event::FTM_SetVolume(u8)); + break; + case 28: // VOLUME ADD [....vv] + case 29: // VOLUME SUB [....vv] + events.push_back(InstrumentSynth::Event::FTM_AddVolume((item.type == 29) ? -static_cast(u8) : u8)); + break; + case 30: // CURRENT SAMPLE = [....ss] + events.push_back(InstrumentSynth::Event::FTM_SetSample(u8)); + break; + case 31: // SAMPLESTART = [.sssss] + case 32: // SAMPLESTART ADD [.sssss] + case 33: // SAMPLESTART SUB [.sssss] + events.push_back(InstrumentSynth::Event::FTM_SetSampleStart(mpt::saturate_cast((u24 & 0xFFFFF) >> 1), item.type - 31)); + break; + case 34: // ONESHOTLENGTH = [..oooo] + case 35: // ONESHOTLENGTH ADD [..oooo] + case 36: // ONESHOTLENGTH SUB [..oooo] + events.push_back(InstrumentSynth::Event::FTM_SetOneshotLength(u16, item.type - 34)); + break; + case 37: // REPEATLENGTH = [..rrrr] + case 38: // REPEATLENGTH ADD [..rrrr] + case 39: // REPEATLENGTH SUB [..rrrr] + events.push_back(InstrumentSynth::Event::FTM_SetRepeatLength(u16, item.type - 37)); + break; + case 40: // GET PIT.OF TRACK [.....t] + case 41: // GET VOL.OF TRACK [.....t] + case 42: // GET SAM.OF TRACK [.....t] + case 43: // CLONE TRACK [.....t] + events.push_back(InstrumentSynth::Event::FTM_CloneTrack(u8 & 0x07, 1 << (item.type - 40))); + break; + case 44: // 1ST LFO START [mfssdd] + case 47: // 2ND LFO START [mfssdd] + case 50: // 3RD LFO START [mfssdd] + case 53: // 4TH LFO START [mfssdd] + events.push_back(InstrumentSynth::Event::FTM_StartLFO(static_cast((item.type - 44u) / 3u), item.data[0])); + [[fallthrough]]; + case 45: // 1ST LFO SP/DE ADD [..ssdd] + case 46: // 1ST LFO SP/DE SUB [..ssdd] + case 48: // 2ND LFO SP/DE ADD [..ssdd] + case 49: // 2ND LFO SP/DE SUB [..ssdd] + case 51: // 3RD LFO SP/DE ADD [..ssdd] + case 52: // 3RD LFO SP/DE SUB [..ssdd] + case 54: // 4TH LFO SP/DE ADD [..ssdd] + case 55: // 4TH LFO SP/DE SUB [..ssdd] + events.push_back(InstrumentSynth::Event::FTM_LFOAddSub(static_cast(((item.type - 44u) / 3u) | (((item.type - 44u) % 3u == 2) ? 4 : 0)), item.data[1], item.data[2])); + break; + case 56: // WORK ON TRACK t [.....t] + case 57: // WORKTRACK ADD [.....t] + events.push_back(InstrumentSynth::Event::FTM_SetWorkTrack(u8 & 0x07, item.type == 57)); + break; + case 58: // GLOBAL VOLUME = [....vv] + events.push_back(InstrumentSynth::Event::FTM_SetGlobalVolume(mpt::saturate_cast(Util::muldivr_unsigned(u8, MAX_GLOBAL_VOLUME, 64)))); + break; + case 59: // GLOBAL SPEED = [..ssss] + events.push_back(InstrumentSynth::Event::FTM_SetTempo(u16)); + break; + case 60: // TICKS PER LINE = [....tt] + events.push_back(InstrumentSynth::Event::FTM_SetSpeed(u8)); + break; + case 61: // JUMP TO SONGLINE [..llll] + events.push_back(InstrumentSynth::Event::FTM_SetPlayPosition(u16 / fileHeader.rowsPerMeasure, static_cast(u16 % fileHeader.rowsPerMeasure))); + break; + } + } + } + if(fileHeader.numEffects) + { + events.push_back(InstrumentSynth::Event::JumpMarker(64)); + offsetToIndex[STOP_INDEX] = static_cast(events.size()); + events.push_back(InstrumentSynth::Event::FTM_SetInterrupt(uint16_max, uint8_max)); + } + for(auto &event : events) + { + event.FixupJumpTarget(offsetToIndex); + } + + if(fileHeader.numMeasures) + { + Patterns.ResizeArray(fileHeader.numMeasures); + Order().resize(fileHeader.numMeasures); + for(uint16 measure = 0; measure < fileHeader.numMeasures; measure++) + { + Order()[measure] = measure; + if(!Patterns.Insert(measure, fileHeader.rowsPerMeasure)) + return false; + } + + struct PatternLoopPoint + { + ORDERINDEX order; + uint16 param : 6, channel : 3, row : 7; + bool operator<(const PatternLoopPoint other) const noexcept { return std::tie(order, row, channel) < std::tie(other.order, other.row, other.channel); } + }; + std::vector loopPoints; + for(CHANNELINDEX chn = 0; chn < GetNumChannels(); chn++) + { + const uint16 defaultSpacing = file.ReadUint16BE(); + FileReader channelChunk = file.ReadChunk(file.ReadUint32BE()); + if(!(loadFlags & loadPatternData)) + continue; + + uint32 globalRow = 0; + uint16 spacing = defaultSpacing; + while(channelChunk.CanRead(2)) + { + const auto data = channelChunk.ReadArray(); + if((data[0] & 0xF0) == 0xF0) + { + spacing = data[1] | ((data[0] & 0x0F) << 8); + continue; + } + + const auto position = std::div(globalRow + spacing, fileHeader.rowsPerMeasure); + if(position.quot >= fileHeader.numMeasures) + break; + const PATTERNINDEX pat = static_cast(position.quot); + + ModCommand &m = *Patterns[pat].GetpModCommand(position.rem, chn); + const uint8 param = ((data[0] & 0x0F) << 2) | (data[1] >> 6); // 0...63 + switch(data[0] & 0xF0) + { + case 0x00: // Set instrument, no effect + m.instr = param; + break; + case 0xB0: // SEL effect + m.SetEffectCommand(CMD_MED_SYNTH_JUMP, (param < effectDefined.size() && effectDefined[param]) ? param : 64); + break; + case 0xC0: // Pitch bend + m.SetEffectCommand(CMD_TONEPORTA_DURATION, param); + break; + case 0xD0: // Volume down + m.SetEffectCommand(CMD_VOLUMEDOWN_DURATION, param); + break; + case 0xE0: // Loop + loopPoints.push_back({pat, static_cast(param & 0x3F), static_cast(chn & 0x07), static_cast(position.rem & 0x7F)}); + break; + case 0xF0: // Already handled + break; + default: + m.SetEffectCommand(CMD_CHANNELVOLUME, static_cast(Util::muldivr_unsigned((data[0] >> 4) - 1, 64, 9))); + m.instr = param; + break; + } + if(uint8 note = data[1] & 0x3F; note >= 35) + m.note = NOTE_KEYOFF; + else if(note != 0) + m.note = NOTE_MIDDLEC - 13 + note; + globalRow += 1 + spacing; + spacing = defaultSpacing; + } + } + + if(Patterns.IsValidPat(0) && (fileHeader.flags & 0x02)) + { + Patterns[0].WriteEffect(EffectWriter(CMD_MODCMDEX, 0).Row(0).RetryNextRow()); + } + + // Evaluate pattern loops (there can be 16 nested loops at most) + std::sort(loopPoints.begin(), loopPoints.end()); + std::array, 16> loopStart; + ORDERINDEX ordersInserted = 0; + uint8 activeLoops = 0; + bool canAddMore = true; + for(const auto &loop : loopPoints) + { + ORDERINDEX ord = loop.order + ordersInserted; + if(loop.param && activeLoops < loopStart.size()) + { + loopStart[activeLoops++] = std::make_pair(ord, static_cast(loop.param)); + } else if(!loop.param && activeLoops > 0) + { + activeLoops--; + const auto start = loopStart[activeLoops].first; + const std::vector ordersToCopy(Order().begin() + start, Order().begin() + ord + 1); + for(uint8 rep = 1; rep < loopStart[activeLoops].second && canAddMore; rep++) + { + if(ORDERINDEX inserted = Order().insert(ord + 1, mpt::as_span(ordersToCopy), false); inserted == ordersToCopy.size()) + ordersInserted += inserted; + else + canAddMore = false; + } + if(!canAddMore) + break; + } + } + } + + if((loadFlags & loadSampleData) && moduleWithSamples) + { + for(SAMPLEINDEX smp = 1; smp <= m_nSamples; smp++) + { + if(!m_szNames[smp][0]) + continue; + ModSample &mptSmp = Samples[smp]; + SmpLength loopStart = file.ReadUint16BE() * 2u; + SmpLength loopLength = file.ReadUint16BE() * 2u; + mptSmp.nLength = loopStart + loopLength; + if(!mptSmp.nLength) + continue; + if(loopLength) + { + mptSmp.nLoopStart = loopStart; + mptSmp.nLoopEnd = loopStart + loopLength; + mptSmp.uFlags.set(CHN_LOOP); + } + SampleIO( + SampleIO::_8bit, + SampleIO::mono, + SampleIO::littleEndian, + SampleIO::signedPCM) + .ReadSample(mptSmp, file); + } + } + + return true; +} + +OPENMPT_NAMESPACE_END diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_gdm.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_gdm.cpp index 5c6a4b123..0a8a907bf 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_gdm.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_gdm.cpp @@ -34,7 +34,7 @@ struct GDMFileHeader uint16le trackerID; // Composing Tracker ID code (00 = 2GDM) uint8le trackerMajorVer; // Tracker's major version uint8le trackerMinorVer; // Tracker's minor version - uint8le panMap[32]; // 0-Left to 15-Right, 255-N/U + uint8le panMap[32]; // 0-Left to 15-Right, 16=Surround, 255-N/U uint8le masterVol; // Range: 0...64 uint8le tempo; // Initial music tempo (6) uint8le bpm; // Initial music BPM (125) @@ -57,6 +57,11 @@ struct GDMFileHeader uint16le scrollyScriptLength; uint32le textGraphicOffset; // Offset of text graphic (huh?) uint16le textGraphicLength; + + uint8 GetNumChannels() const + { + return static_cast(std::distance(std::begin(panMap), std::find(std::begin(panMap), std::end(panMap), uint8_max))); + } }; MPT_BINARY_STRUCT(GDMFileHeader, 157) @@ -120,7 +125,8 @@ static bool ValidateHeader(const GDMFileHeader &fileHeader) || std::memcmp(fileHeader.magic2, "GMFS", 4) || fileHeader.formatMajorVer != 1 || fileHeader.formatMinorVer != 0 || fileHeader.originalFormat >= std::size(gdmFormatOrigin) - || fileHeader.originalFormat == 0) + || fileHeader.originalFormat == 0 + || !fileHeader.GetNumChannels()) { return false; } @@ -162,11 +168,11 @@ bool CSoundFile::ReadGDM(FileReader &file, ModLoadingFlags loadFlags) return true; } - InitializeGlobals(gdmFormatOrigin[fileHeader.originalFormat]); + InitializeGlobals(gdmFormatOrigin[fileHeader.originalFormat], fileHeader.GetNumChannels()); m_SongFlags.set(SONG_IMPORTED); - m_modFormat.formatName = U_("General Digital Music"); - m_modFormat.type = U_("gdm"); + m_modFormat.formatName = UL_("General Digital Music"); + m_modFormat.type = UL_("gdm"); m_modFormat.madeWithTracker = MPT_UFORMAT("BWSB 2GDM {}.{}")(fileHeader.trackerMajorVer, fileHeader.formatMinorVer); m_modFormat.originalType = gdmFormatOriginType[fileHeader.originalFormat]; m_modFormat.originalFormatName = gdmFormatOriginFormat[fileHeader.originalFormat]; @@ -185,10 +191,8 @@ bool CSoundFile::ReadGDM(FileReader &file, ModLoadingFlags loadFlags) } // Read channel pan map... 0...15 = channel panning, 16 = surround channel, 255 = channel does not exist - m_nChannels = 32; - for(CHANNELINDEX i = 0; i < 32; i++) + for(CHANNELINDEX i = 0; i < GetNumChannels(); i++) { - ChnSettings[i].Reset(); if(fileHeader.panMap[i] < 16) { ChnSettings[i].nPan = static_cast(std::min((fileHeader.panMap[i] * 16) + 8, 256)); @@ -196,20 +200,12 @@ bool CSoundFile::ReadGDM(FileReader &file, ModLoadingFlags loadFlags) { ChnSettings[i].nPan = 128; ChnSettings[i].dwFlags = CHN_SURROUND; - } else if(fileHeader.panMap[i] == 0xFF) - { - m_nChannels = i; - break; } } - if(m_nChannels < 1) - { - return false; - } m_nDefaultGlobalVolume = std::min(fileHeader.masterVol * 4u, 256u); - m_nDefaultSpeed = fileHeader.tempo; - m_nDefaultTempo.Set(fileHeader.bpm); + Order().SetDefaultSpeed(fileHeader.tempo); + Order().SetDefaultTempoInt(fileHeader.bpm); // Read orders if(file.Seek(fileHeader.orderOffset)) @@ -348,7 +344,7 @@ bool CSoundFile::ReadGDM(FileReader &file, ModLoadingFlags loadFlags) while((channelByte = chunk.ReadUint8()) != rowDone) { CHANNELINDEX channel = channelByte & channelMask; - if(channel >= m_nChannels) break; // Better safe than sorry! + if(channel >= GetNumChannels()) break; // Better safe than sorry! ModCommand &m = rowBase[channel]; @@ -360,7 +356,7 @@ bool CSoundFile::ReadGDM(FileReader &file, ModLoadingFlags loadFlags) if(note) { note = (note & 0x7F) - 1; // High bit = no-retrig flag (notes with portamento have this set) - m.note = (note & 0x0F) + 12 * (note >> 4) + 12 + NOTE_MIN; + m.note = static_cast((note & 0x0F) + 12 * (note >> 4) + 12 + NOTE_MIN); if(!m.IsAmigaNote()) { onlyAmigaNotes = false; @@ -482,10 +478,8 @@ bool CSoundFile::ReadGDM(FileReader &file, ModLoadingFlags loadFlags) // Move pannings to volume column - should never happen if(m.command == CMD_S3MCMDEX && ((m.param >> 4) == 0x8) && m.volcmd == VOLCMD_NONE) { - m.volcmd = VOLCMD_PANNING; - m.vol = ((m.param & 0x0F) * 64 + 8) / 15; - m.command = oldCmd.command; - m.param = oldCmd.param; + m.SetVolumeCommand(VOLCMD_PANNING, static_cast(((m.param & 0x0F) * 64 + 8) / 15)); + m.SetEffectCommand(oldCmd); } if(!(effByte & effectMore)) diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_gmc.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_gmc.cpp new file mode 100644 index 000000000..8677989a4 --- /dev/null +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_gmc.cpp @@ -0,0 +1,240 @@ +/* + * Load_gmc.cpp + * ------------ + * Purpose: GMC (Game Music Creator) module loader + * Notes : (currently none) + * Authors: OpenMPT Devs + * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. + */ + +#include "stdafx.h" +#include "Loaders.h" + +OPENMPT_NAMESPACE_BEGIN + +// Sample Header +struct GMCSampleHeader +{ + uint32be offset; + uint16be length; + uint8 zero; + uint8 volume; + uint32be address; + uint16be loopLength; // Loop start is implicit + uint16be dataStart; // Non-zero if sample was shortened in editor (amount of bytes trimmed from sample start) + + // Convert a GMC sample header to OpenMPT's internal sample header. + void ConvertToMPT(ModSample &mptSmp) const + { + mptSmp.Initialize(MOD_TYPE_MOD); + mptSmp.nLength = length * 2u; + mptSmp.nVolume = 4u * std::min(volume, uint8(64)); + + if(loopLength > 2) + { + mptSmp.nLoopStart = mptSmp.nLength - loopLength * 2u; + mptSmp.nLoopEnd = mptSmp.nLength; + mptSmp.uFlags.set(CHN_LOOP); + } + } + + bool IsValid() const + { + if(offset > 0x7F'FFFF || (offset & 1) || address > 0x7F'FFFF || (address & 1)) + return false; + if(length > 0x7FFF || dataStart > 0x7FFF || (dataStart & 1)) + return false; + if(loopLength > 2 && loopLength > length) + return false; + if(volume > 64) + return false; + if(zero != 0) + return false; + return true; + } +}; + +MPT_BINARY_STRUCT(GMCSampleHeader, 16) + + +// File Header +struct GMCFileHeader +{ + GMCSampleHeader samples[15]; + uint8 zero[3]; + uint8 numOrders; + uint16be orders[100]; + + bool IsValid() const noexcept + { + for(const auto &sample : samples) + { + if(!sample.IsValid()) + return false; + } + if(zero[0] != 0 || zero[1] != 0 || zero[2] != 0) + return false; + if(!numOrders || numOrders > std::size(orders)) + return false; + for(uint16 ord : orders) + { + if(ord % 1024u) + return false; + } + return true; + } +}; + +MPT_BINARY_STRUCT(GMCFileHeader, 444) + + +CSoundFile::ProbeResult CSoundFile::ProbeFileHeaderGMC(MemoryFileReader file, const uint64 *pfilesize) +{ + GMCFileHeader fileHeader; + if(!file.ReadStruct(fileHeader)) + return ProbeWantMoreData; + if(!fileHeader.IsValid()) + return ProbeFailure; + + MPT_UNREFERENCED_PARAMETER(pfilesize); + return ProbeSuccess; +} + + +bool CSoundFile::ReadGMC(FileReader &file, ModLoadingFlags loadFlags) +{ + file.Rewind(); + GMCFileHeader fileHeader; + if(!file.ReadStruct(fileHeader) || !fileHeader.IsValid()) + return false; + else if(loadFlags == onlyVerifyHeader) + return true; + + InitializeGlobals(MOD_TYPE_MOD, 4); + m_nSamples = 15; + m_nMinPeriod = 113 * 4; + m_nMaxPeriod = 856 * 4; + m_nSamplePreAmp = 64; + m_SongFlags.set(SONG_FASTPORTAS | SONG_IMPORTED | SONG_FORMAT_NO_VOLCOL); + Order().SetDefaultTempoInt(125); + Order().SetDefaultSpeed(6); + + // Setup channel pan positions and volume + SetupMODPanning(true); + + // Note: The Brides of Dracula modules contain a "hidden" pattern past fileHeader.numOrders. + // This pattern would need to be read in order to align the samples correctly, however this would mean that the file size no longer matches + // the expected size (the last sample would be missing 1024 samples at the end). + // Looking at some gameplay footage, the playback is apparently broken ingame as well, and fixing it would break other modules (e.g. Jet Set Willy 2 Title). + Order().resize(fileHeader.numOrders); + PATTERNINDEX numPatterns = 0; + for(ORDERINDEX ord = 0; ord < fileHeader.numOrders; ord++) + { + PATTERNINDEX pat = Order()[ord] = fileHeader.orders[ord] / 1024u; + // Fix for Covert Action theme music (there appears to be a general bug in GMC export when pattern 63 is used) + if(pat != 63) + numPatterns = std::max(numPatterns, static_cast(pat + 1)); + } + + for(SAMPLEINDEX smp = 1; smp <= 15; smp++) + { + fileHeader.samples[smp - 1].ConvertToMPT(Samples[smp]); + } + + if(loadFlags & loadPatternData) + Patterns.ResizeArray(numPatterns); + for(PATTERNINDEX pat = 0; pat < numPatterns; pat++) + { + if(!(loadFlags & loadPatternData) || !Patterns.Insert(pat, 64)) + { + file.Skip(64 * 4 * 4); + continue; + } + + for(ROWINDEX row = 0; row < 64; row++) + { + auto rowBase = Patterns[pat].GetRow(row); + for(CHANNELINDEX chn = 0; chn < 4; chn++) + { + ModCommand &m = rowBase[chn]; + auto data = file.ReadArray(); + // Probably bad conversion from SoundFX Import? (Stryx title music) + const bool noteCut = (data[0] == 0xFF && data[1] == 0xFE); + if(noteCut) + data[0] = 0; + else if(data[0] & 0xF0) + return false; + + uint8 command = data[2] & 0x0F, param = data[3]; + switch(command) + { + case 0x00: // Nothing + param = 0; + break; + case 0x01: // Portamento up + case 0x02: // Portamento down + break; + case 0x03: // Volume + command = 0x0C; + param &= 0x7F; + break; + case 0x04: // Pattern break + if(param > 0x63) + return false; + command = 0x0D; + break; + case 0x05: // Position jump + if(param > fileHeader.numOrders + 1) + return false; + command = 0x0B; + break; + case 0x06: // LED filter on + case 0x07: // LED filter off + param = command - 0x06; + command = 0x0E; + break; + case 0x08: // Set speed + command = 0x0F; + break; + default: + command = param = 0; + break; + } + ReadMODPatternEntry(data, m); + ConvertModCommand(m, command, param); + if(noteCut) + m.note = NOTE_NOTECUT; + if(m.command == CMD_PORTAMENTOUP) + m.command = CMD_AUTO_PORTAUP; + else if(m.command == CMD_PORTAMENTODOWN) + m.command = CMD_AUTO_PORTADOWN; + else if(m.command == CMD_TEMPO) + m.command = CMD_SPEED; + } + } + } + + if(loadFlags & loadSampleData) + { + for(SAMPLEINDEX smp = 1; smp <= m_nSamples; smp++) + { + if(!Samples[smp].nLength) + continue; + SampleIO( + SampleIO::_8bit, + SampleIO::mono, + SampleIO::littleEndian, + SampleIO::signedPCM) + .ReadSample(Samples[smp], file); + } + } + + m_modFormat.madeWithTracker = UL_("Game Music Creator"); + m_modFormat.formatName = UL_("Game Music Creator"); + m_modFormat.type = UL_("GMC"); + m_modFormat.charset = mpt::Charset::Amiga_no_C1; // No strings in this format... + + return true; +} + +OPENMPT_NAMESPACE_END diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_gt2.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_gt2.cpp index 39fea249c..6175a40ba 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_gt2.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_gt2.cpp @@ -2,7 +2,8 @@ * Load_gt2.cpp * ------------ * Purpose: Graoumf Tracker 1/2 module loader (GTK and GT2) - * Notes : DSPs (delay, filter) are currently not supported. They are kinda tricky because DSP to track assignments are established using pattern commands and can change at any time. + * Notes : DSPs (delay, filter) are currently not supported. + * They are kinda tricky because DSP to track assignments are established using pattern commands and can change at any time. * Authors: OpenMPT Devs * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. */ @@ -204,7 +205,7 @@ static void TranslateGraoumfEffect(CSoundFile &sndFile, ModCommand &m, const uin if(!m.IsNote() || !param) break; m.command = CMD_FINETUNE; - m.param = 0x80 + ((param & 0xF0) >> 1) - ((param & 0x0F) << 3); + m.param = static_cast(0x80 + ((param & 0xF0) >> 1) - ((param & 0x0F) << 3)); if(!sndFile.GetNumInstruments()) { // Need instruments for correct pitch wheel depth @@ -393,7 +394,7 @@ static void TranslateGraoumfEffect(CSoundFile &sndFile, ModCommand &m, const uin break; case 0xBA: // BAxx: Fine Offset m.command = CMD_OFFSET; - m.param = (m.param + 15u) / 16u; + m.param = static_cast((m.param + 15u) / 16u); break; case 0xBB: // BBxx: Very Fine Offset m.command = CMD_OFFSET; @@ -459,8 +460,8 @@ static void TranslateGraoumfEffect(CSoundFile &sndFile, ModCommand &m, const uin m.vol = 9; } else { - m.command = CMD_TONEPORTAMENTO; - m.param = 0xFF; + m.command = CMD_TONEPORTA_DURATION; + m.param = 0; } } } @@ -525,23 +526,21 @@ bool CSoundFile::ReadGTK(FileReader &file, ModLoadingFlags loadFlags) return false; if(!fileHeader.Validate()) return false; - if(!file.CanRead(mpt::saturate_cast(fileHeader.GetHeaderMinimumAdditionalSize()))) + if(!file.CanRead(mpt::saturate_cast(fileHeader.GetHeaderMinimumAdditionalSize()))) return false; if(loadFlags == onlyVerifyHeader) return true; // Globals - InitializeGlobals(MOD_TYPE_MPT); - InitializeChannels(); + InitializeGlobals(MOD_TYPE_MPT, fileHeader.numChannels); m_SongFlags.set(SONG_IMPORTED); m_playBehaviour = GetDefaultPlaybackBehaviour(MOD_TYPE_IT); m_playBehaviour.set(kApplyOffsetWithoutNote); - m_nChannels = fileHeader.numChannels; SetupMODPanning(true); - m_modFormat.madeWithTracker = U_("Graoumf Tracker"); + m_modFormat.madeWithTracker = UL_("Graoumf Tracker"); m_modFormat.formatName = MPT_UFORMAT("Graoumf Tracker v{}")(fileHeader.fileVersion); - m_modFormat.type = U_("gtk"); + m_modFormat.type = UL_("gtk"); m_modFormat.charset = mpt::Charset::ISO8859_1_no_C1; m_songName = mpt::String::ReadBuf(mpt::String::spacePadded, fileHeader.songName); @@ -599,8 +598,8 @@ bool CSoundFile::ReadGTK(FileReader &file, ModLoadingFlags loadFlags) Patterns.ResizeArray(numPatterns); const uint8 eventSize = fileHeader.fileVersion < 4 ? 4 : 5; - TEMPO currentTempo = m_nDefaultTempo; - uint32 currentSpeed = m_nDefaultSpeed; + TEMPO currentTempo = Order().GetDefaultTempo(); + uint32 currentSpeed = Order().GetDefaultSpeed(); for(PATTERNINDEX pat = 0; pat < numPatterns; pat++) { if(!(loadFlags & loadPatternData) || !file.CanRead(fileHeader.numRows * GetNumChannels() * eventSize) || !Patterns.Insert(pat, fileHeader.numRows)) @@ -967,7 +966,7 @@ struct GT2SampleV1 mptSmp.Initialize(MOD_TYPE_IT); mptSmp.Set16BitCuePoints(); - mptSmp.nGlobalVol = volume / 4u; + mptSmp.nGlobalVol = static_cast(volume / 4u); if(defaultPan > 0) { mptSmp.uFlags.set(CHN_PANNING); @@ -1034,149 +1033,123 @@ struct GT2MixPresetTrack MPT_BINARY_STRUCT(GT2MixPresetTrack, 8) -static EnvelopeNode::value_t ConvertGT2EnvelopeValue(const EnvelopeType envType, int32 value) -{ - if(envType == ENV_VOLUME) - value = Util::muldivr(value, ENVELOPE_MAX, 16384); - else if(envType == ENV_PANNING) - value = Util::muldivr(value, ENVELOPE_MAX, 4096); - else - value = mpt::saturate_round(mpt::log2(8192.0 / std::max(value, int32(1))) * 24.0 + (ENVELOPE_MID - 24)); - return static_cast(Clamp(value, ENVELOPE_MIN, ENVELOPE_MAX)); -} - -static void ConvertGT2Envelope(ModInstrument &mptIns, const EnvelopeType envType, const uint16 envNum, std::vector &envChunks) +static void ConvertGT2Envelope(InstrumentSynth &synth, const uint16 envNum, std::vector &envChunks) { if(!envNum) return; - auto &mptEnv = mptIns.GetEnvelope(envType); for(auto &chunk : envChunks) { chunk.Rewind(); if(chunk.ReadUint16BE() != envNum) continue; chunk.Skip(20); // Name - uint32 chunkOffset = 24; - const uint32 keyoffOffset = chunk.ReadUint16BE() + chunkOffset; - bool keyOffRequested = false, addSustainAtEnd = true; + const uint16 keyoffOffset = chunk.ReadUint16BE(); + uint16 jumpOffset = 0; - int16 step = 0; - uint8 speed = 1; - uint8 counter = 0; - uint32 counterOffset = 0; // Position of last encountered counter (to avoid infinite parsing loops) - int32 initValue = 16384; - if(envType == ENV_PITCH) - initValue = 4096; - else if(envType == ENV_PANNING) - initValue = 2048; - int32 value = initValue; - - std::map offsetToPoint; - mptEnv.assign(1, {0u, (envType == ENV_VOLUME) ? ENVELOPE_MAX : ENVELOPE_MID}); + std::map offsetToIndex; + auto &events = synth.m_scripts.emplace_back(); + events.push_back(InstrumentSynth::Event::GTK_KeyOff(keyoffOffset)); while(chunk.CanRead(1)) { - offsetToPoint[static_cast(chunk.GetPosition())] = mptEnv.size(); - const uint8 command = chunk.ReadUint8(); - if(command == 0) + const uint16 chunkPos = static_cast(chunk.GetPosition() - 24); + if(!jumpOffset && chunkPos >= keyoffOffset) { - if(keyoffOffset <= chunkOffset) - break; - keyOffRequested = true; + events.push_back(InstrumentSynth::Event::StopScript()); + jumpOffset = keyoffOffset; } + + offsetToIndex[chunkPos] = static_cast(events.size()); + const uint8 command = chunk.ReadUint8(); switch(command) { + case 0x00: // 00: End + events.push_back(InstrumentSynth::Event::StopScript()); + break; case 0x01: // 01: Unconditional Jump - if(!mptEnv.dwFlags[ENV_SUSTAIN]) - { - mptEnv.nSustainStart = static_cast(offsetToPoint[chunk.ReadUint16BE()]); - mptEnv.nSustainEnd = static_cast(mptEnv.size() - 1); - mptEnv.dwFlags.set(ENV_SUSTAIN); - } + events.push_back(InstrumentSynth::Event::Jump(jumpOffset + chunk.ReadUint16BE())); break; case 0x02: // 02: Wait - { - uint16 delay = std::max(chunk.ReadUint16BE(), uint16(1)); - value += step * speed * delay; - if(delay > 1) - mptEnv.emplace_back(mpt::saturate_cast(mptEnv.back().tick + delay - 1u), ConvertGT2EnvelopeValue(envType, value)); - mptEnv.emplace_back(mpt::saturate_cast(mptEnv.back().tick + 1u), ConvertGT2EnvelopeValue(envType, value)); - } + events.push_back(InstrumentSynth::Event::Delay(std::max(chunk.ReadUint16BE(), uint16(1)) - 1)); break; case 0x03: // 03: Set Counter - counter = chunk.ReadUint8(); - counterOffset = static_cast(chunk.GetPosition()); + events.push_back(InstrumentSynth::Event::SetLoopCounter(std::max(chunk.ReadUint8(), uint8(1)) - 1, true)); break; case 0x04: // 04: Loop - if(const uint32 loopStart = chunk.ReadUint16BE() + chunkOffset; loopStart >= counterOffset && counter) - { - if(--counter) - chunk.Seek(loopStart); - } else if(counter) - { - // This is an infinite loop. - mptEnv.nLoopStart = static_cast(offsetToPoint[loopStart]); - mptEnv.nLoopEnd = static_cast(mptEnv.size() - 1); - mptEnv.dwFlags.set(ENV_LOOP); - } + events.push_back(InstrumentSynth::Event::EvaluateLoopCounter(jumpOffset + chunk.ReadUint16BE())); break; case 0x05: // 05: Key Off - keyOffRequested = true; - addSustainAtEnd = false; + events.push_back(InstrumentSynth::Event::Jump(keyoffOffset)); break; case 0x80: // 80: Set Volume (16384 = 100%, max = 32767) + events.push_back(InstrumentSynth::Event::GTK_SetVolume(chunk.ReadUint16BE())); + break; case 0xA0: // A0: Set Tone (4096 = normal period) + events.push_back(InstrumentSynth::Event::GTK_SetPitch(chunk.ReadUint16BE())); + break; case 0xC0: // C0: Set Panning (2048 = normal position) - value = chunk.ReadUint16BE(); - mptEnv.back().value = ConvertGT2EnvelopeValue(envType, value); + events.push_back(InstrumentSynth::Event::GTK_SetPanning(chunk.ReadUint16BE())); break; case 0x81: // 81: Set Volume step + events.push_back(InstrumentSynth::Event::GTK_SetVolumeStep(chunk.ReadInt16BE())); + break; case 0xA1: // A1: Set Tone step + events.push_back(InstrumentSynth::Event::GTK_SetPitchStep(chunk.ReadInt16BE())); + break; case 0xC1: // C1: Set Pan step - step = chunk.ReadInt16BE(); + events.push_back(InstrumentSynth::Event::GTK_SetPanningStep(chunk.ReadInt16BE())); break; case 0x82: // 82: Set Volume speed case 0xA2: // A2: Set Tone speed case 0xC2: // C2: Set Pan speed - speed = std::max(chunk.ReadUint8(), uint8(1)); + events.push_back(InstrumentSynth::Event::GTK_SetSpeed(std::max(chunk.ReadUint8(), uint8(1)))); break; - // Various more complex features are not implemented as I couldn't find any actual GT2 modules using the envelope feature at all. case 0x87: // 87: Tremor On + events.push_back(InstrumentSynth::Event::GTK_EnableTremor(true)); + break; case 0x88: // 88: Tremor Off + events.push_back(InstrumentSynth::Event::GTK_EnableTremor(false)); + break; + case 0x89: // 89: Set Tremor Time 1 + events.push_back(InstrumentSynth::Event::GTK_SetTremorTime(std::max(chunk.ReadUint8(), uint8(1)), 0)); + break; + case 0x8A: // 8A: Set Tremor Time 2 + events.push_back(InstrumentSynth::Event::GTK_SetTremorTime(0, std::max(chunk.ReadUint8(), uint8(1)))); + break; + case 0x83: // 83: Tremolo On + events.push_back(InstrumentSynth::Event::GTK_EnableTremolo(true)); + break; case 0x84: // 84: Tremolo Off - case 0xA3: // A3: Vibrato On - case 0xA4: // A4: Vibrato Off + events.push_back(InstrumentSynth::Event::GTK_EnableTremolo(false)); break; case 0x85: // 85: Set Tremolo Width + events.push_back(InstrumentSynth::Event::GTK_SetVibratoParams(std::max(chunk.ReadUint8(), uint8(1)), 0)); + break; case 0x86: // 86: Set Tremolo Speed - case 0x89: // 89: Set Tremor Time 1 - case 0x8A: // 8A: Set Tremor Time 2 + events.push_back(InstrumentSynth::Event::GTK_SetVibratoParams(0, std::max(chunk.ReadUint8(), uint8(1)))); + break; + + case 0xA3: // A3: Vibrato On + events.push_back(InstrumentSynth::Event::GTK_EnableVibrato(true)); + break; + case 0xA4: // A4: Vibrato Off + events.push_back(InstrumentSynth::Event::GTK_EnableVibrato(false)); + break; case 0xA5: // A5: Set Vibrato Width + events.push_back(InstrumentSynth::Event::GTK_SetVibratoParams(std::max(chunk.ReadUint8(), uint8(1)), 0)); + break; case 0xA6: // A6: Set Vibrato Speed - chunk.Skip(1); + events.push_back(InstrumentSynth::Event::GTK_SetVibratoParams(0, std::max(chunk.ReadUint8(), uint8(1)))); break; } - - if(keyOffRequested && keyoffOffset > chunkOffset) - { - // Enter key-off section, but only if we can read more than just the expected end marker byte - chunkOffset = keyoffOffset; - if(!chunk.Seek(chunkOffset) || !chunk.CanRead(2)) - break; - if(!mptEnv.dwFlags[ENV_SUSTAIN] && addSustainAtEnd) - { - mptEnv.nSustainStart = mptEnv.nSustainEnd = static_cast(mptEnv.size() - 1); - mptEnv.dwFlags.set(ENV_SUSTAIN); - } - value = initValue; - mptEnv.emplace_back(mpt::saturate_cast(mptEnv.back().tick + 1u), ConvertGT2EnvelopeValue(envType, value)); - mptEnv.nReleaseNode = mptEnv.nSustainEnd + 1; - } } - mptEnv.dwFlags.set(ENV_ENABLED); + for(auto &event : events) + { + event.FixupJumpTarget(offsetToIndex); + } break; } } @@ -1202,14 +1175,29 @@ bool CSoundFile::ReadGT2(FileReader &file, ModLoadingFlags loadFlags) return false; if(!fileHeader.Validate()) return false; - if(!file.CanRead(mpt::saturate_cast(fileHeader.GetHeaderMinimumAdditionalSize()))) + if(!file.CanRead(mpt::saturate_cast(fileHeader.GetHeaderMinimumAdditionalSize()))) return false; if(loadFlags == onlyVerifyHeader) return true; + std::vector pannedTracks; + file.ReadVector(pannedTracks, fileHeader.numPannedTracks); + + ChunkReader chunkFile(file); + auto chunks = chunkFile.ReadChunksUntil(1, GT2Chunk::idENDC); + + if(auto chunk = chunks.GetChunk(GT2Chunk::idPATS); chunk.CanRead(2)) + { + if(uint16 channels = chunk.ReadUint16BE(); channels >= 1 && channels <= MAX_BASECHANNELS) + InitializeGlobals(MOD_TYPE_MPT, channels); + else + return false; + } else + { + return false; + } + // Globals - InitializeGlobals(MOD_TYPE_MPT); - InitializeChannels(); m_SongFlags.set(SONG_IMPORTED | SONG_EXFILTERRANGE); m_playBehaviour = GetDefaultPlaybackBehaviour(MOD_TYPE_IT); m_playBehaviour.set(kFT2ST3OffsetOutOfRange, fileHeader.fileVersion >= 6); @@ -1221,45 +1209,29 @@ bool CSoundFile::ReadGT2(FileReader &file, ModLoadingFlags loadFlags) mptHistory.loadDate.year = fileHeader.year; m_FileHistory.push_back(mptHistory); - m_nChannels = 32; m_modFormat.madeWithTracker = mpt::ToUnicode(mpt::Charset::ASCII, mpt::String::ReadBuf(mpt::String::spacePadded, fileHeader.trackerName)); m_modFormat.formatName = (fileHeader.fileVersion <= 5 ? MPT_UFORMAT("Graoumf Tracker v{}") : MPT_UFORMAT("Graoumf Tracker 2 v{}"))(fileHeader.fileVersion); - m_modFormat.type = U_("gt2"); + m_modFormat.type = UL_("gt2"); m_modFormat.charset = mpt::Charset::ISO8859_1_no_C1; m_songName = mpt::String::ReadBuf(mpt::String::spacePadded, fileHeader.songName); m_nSamplePreAmp = 256; - m_nDefaultGlobalVolume = 384 / (3 + m_nChannels); // See documentation on command 5xxx + m_nDefaultGlobalVolume = 384 / (3 + GetNumChannels()); // See documentation on command 5xxx: Default linear master volume is 12288 / (3 + number of channels) if(fileHeader.fileVersion <= 5) { - m_nDefaultSpeed = std::max(fileHeader.speed.get(), uint16(1)); - m_nDefaultTempo.Set(std::max(fileHeader.tempo.get(), uint16(1))); + Order().SetDefaultSpeed(std::max(fileHeader.speed.get(), uint16(1))); + Order().SetDefaultTempoInt(std::max(fileHeader.tempo.get(), uint16(1))); m_nDefaultGlobalVolume = std::min(Util::muldivr_unsigned(fileHeader.masterVol, MAX_GLOBAL_VOLUME, 4095), uint32(MAX_GLOBAL_VOLUME)); - uint16 tracks = fileHeader.numPannedTracks; - LimitMax(tracks, MAX_BASECHANNELS); + const CHANNELINDEX tracks = std::min(static_cast(pannedTracks.size()), GetNumChannels()); for(CHANNELINDEX chn = 0; chn < tracks; chn++) { - ChnSettings[chn].nPan = std::min(static_cast(Util::muldivr_unsigned(file.ReadUint16BE(), 256, 4095)), uint16(256)); + ChnSettings[chn].nPan = std::min(static_cast(Util::muldivr_unsigned(pannedTracks[chn], 256, 4095)), uint16(256)); } } file.Seek(fileHeader.headerSize); - ChunkReader chunkFile(file); - auto chunks = chunkFile.ReadChunksUntil(1, GT2Chunk::idENDC); - - if(auto chunk = chunks.GetChunk(GT2Chunk::idPATS); chunk.CanRead(2)) - { - if(uint16 channels = chunk.ReadUint16BE(); channels >= 1 && channels <= MAX_BASECHANNELS) - m_nChannels = channels; - else - return false; - } else - { - return false; - } - if(auto chunk = chunks.GetChunk(GT2Chunk::idSONG); chunk.CanRead(2)) { ORDERINDEX numOrders = chunk.ReadUint16BE(); @@ -1287,7 +1259,8 @@ bool CSoundFile::ReadGT2(FileReader &file, ModLoadingFlags loadFlags) { // Only channel mute status is of interest here uint32 muteStatus = chunk.ReadUint32BE(); - for(CHANNELINDEX i = 0; i < 32; i++) + const CHANNELINDEX numChannels = std::min(GetNumChannels(), CHANNELINDEX(32)); + for(CHANNELINDEX i = 0; i < numChannels; i++) { ChnSettings[i].dwFlags.set(CHN_MUTE, !(muteStatus & (1u << i))); } @@ -1296,8 +1269,8 @@ bool CSoundFile::ReadGT2(FileReader &file, ModLoadingFlags loadFlags) if(auto chunk = chunks.GetChunk(GT2Chunk::idTCN2); chunk.CanRead(12)) { const auto [chunkVersion, bpmInt, bpmFract, speed, timeSigNum, timeSigDenum] = chunk.ReadArray(); - m_nDefaultTempo = TEMPO(Clamp(bpmInt, 32, 999), Util::muldivr_unsigned(bpmFract, TEMPO::fractFact, 65536)); - m_nDefaultSpeed = Clamp(speed, 1, 255); + Order().SetDefaultTempo(TEMPO(Clamp(bpmInt, 32, 999), Util::muldivr_unsigned(bpmFract, TEMPO::fractFact, 65536))); + Order().SetDefaultSpeed(Clamp(speed, 1, 255)); m_nDefaultRowsPerBeat = 16 / Clamp(timeSigDenum, 1, 16); m_nDefaultRowsPerMeasure = m_nDefaultRowsPerBeat * Clamp(timeSigNum, 1, 16); if(chunkVersion >= 1) @@ -1454,10 +1427,10 @@ bool CSoundFile::ReadGT2(FileReader &file, ModLoadingFlags loadFlags) if(instr.version == 0) { // Old "envelopes" - mptIns->nFadeOut = 0; - ConvertGT2Envelope(*mptIns, ENV_VOLUME, instr.volEnv, volEnvChunks); - ConvertGT2Envelope(*mptIns, ENV_PANNING, instr.panEnv, panEnvChunks); - ConvertGT2Envelope(*mptIns, ENV_PITCH, instr.toneEnv, pitchEnvChunks); + mptIns->nFadeOut = instr.volEnv ? 0 : 32768; + ConvertGT2Envelope(mptIns->synth, instr.volEnv, volEnvChunks); + ConvertGT2Envelope(mptIns->synth, instr.panEnv, panEnvChunks); + ConvertGT2Envelope(mptIns->synth, instr.toneEnv, pitchEnvChunks); } else { GT2InstrumentExt instrExt; @@ -1520,8 +1493,8 @@ bool CSoundFile::ReadGT2(FileReader &file, ModLoadingFlags loadFlags) auto patterns = chunks.GetAllChunks(GT2Chunk::idPATD); Patterns.ResizeArray(static_cast(patterns.size())); - TEMPO currentTempo = m_nDefaultTempo; - uint32 currentSpeed = m_nDefaultSpeed; + TEMPO currentTempo = Order().GetDefaultTempo(); + uint32 currentSpeed = Order().GetDefaultSpeed(); for(auto &patChunk : patterns) { if(!(loadFlags & loadPatternData) || !patChunk.CanRead(24)) @@ -1578,7 +1551,7 @@ bool CSoundFile::ReadGT2(FileReader &file, ModLoadingFlags loadFlags) { GT2TrackName trackName; chunk.ReadStruct(trackName); - if(trackName.type == 0 && trackName.trackNumber < m_nChannels) + if(trackName.type == 0 && trackName.trackNumber < GetNumChannels()) { ChnSettings[trackName.trackNumber].szName = mpt::String::ReadBuf(mpt::String::spacePadded, trackName.name); } diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_ice.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_ice.cpp new file mode 100644 index 000000000..d04facc0c --- /dev/null +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_ice.cpp @@ -0,0 +1,204 @@ +/* + * Load_ice.cpp + * ------------ + * Purpose: ST26 (SoundTracker 2.6 / Ice Tracker) loader + * Notes : The only real difference to other SoundTracker formats is the way patterns are stored: + * Every pattern consists of four independent, re-usable tracks. + * Authors: OpenMPT Devs + * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. + */ + + +#include "stdafx.h" +#include "Loaders.h" +#include "MODTools.h" + +OPENMPT_NAMESPACE_BEGIN + +CSoundFile::ProbeResult CSoundFile::ProbeFileHeaderICE(MemoryFileReader file, const uint64 *pfilesize) +{ + if(!file.CanRead(1464 + 4)) + return ProbeWantMoreData; + + file.Seek(1464); + char magic[4]; + file.ReadArray(magic); + if(!IsMagic(magic, "MTN\0") && !IsMagic(magic, "IT10")) + return ProbeFailure; + + file.Seek(20); + uint32 invalidBytes = 0; + for(SAMPLEINDEX smp = 1; smp <= 31; smp++) + { + MODSampleHeader sampleHeader; + file.ReadStruct(sampleHeader); + invalidBytes += sampleHeader.GetInvalidByteScore(); + if(invalidBytes > MODSampleHeader::INVALID_BYTE_THRESHOLD) + return ProbeFailure; + } + const auto [numOrders, numTracks] = file.ReadArray(); + if(numOrders > 128) + { + return ProbeFailure; + } + std::array tracks; + file.ReadArray(tracks); + for(auto track : tracks) + { + if(track > numTracks) + return ProbeFailure; + } + MPT_UNREFERENCED_PARAMETER(pfilesize); + return ProbeSuccess; +} + + +bool CSoundFile::ReadICE(FileReader &file, ModLoadingFlags loadFlags) +{ + char magic[4]; + if(!file.Seek(1464) || !file.ReadArray(magic)) + { + return false; + } + + if(IsMagic(magic, "MTN\0")) + { + InitializeGlobals(MOD_TYPE_MOD, 4); + m_modFormat.formatName = UL_("MnemoTroN SoundTracker"); + m_modFormat.type = UL_("st26"); + m_modFormat.madeWithTracker = UL_("SoundTracker 2.6"); + m_modFormat.charset = mpt::Charset::Amiga_no_C1; + } else if(IsMagic(magic, "IT10")) + { + InitializeGlobals(MOD_TYPE_MOD, 4); + m_modFormat.formatName = UL_("Ice Tracker"); + m_modFormat.type = UL_("ice"); + m_modFormat.madeWithTracker = UL_("Ice Tracker 1.0 / 1.1"); + m_modFormat.charset = mpt::Charset::Amiga_no_C1; + } else + { + return false; + } + + // Reading song title + file.Seek(0); + file.ReadString(m_songName, 20); + + // Load Samples + m_nSamples = 31; + uint32 invalidBytes = 0; + for(SAMPLEINDEX smp = 1; smp <= 31; smp++) + { + MODSampleHeader sampleHeader; + file.ReadStruct(sampleHeader); + invalidBytes += ReadMODSample(sampleHeader, Samples[smp], m_szNames[smp], true); + if(invalidBytes > MODSampleHeader::INVALID_BYTE_THRESHOLD) + return false; + } + + const auto [numOrders, numTracks] = file.ReadArray(); + if(numOrders > 128) + return false; + + std::array tracks; + file.ReadArray(tracks); + for(auto track : tracks) + { + if(track > numTracks) + return false; + } + + if(loadFlags == onlyVerifyHeader) + return true; + + // Now we can be pretty sure that this is a valid ICE file. Set up default song settings. + SetupMODPanning(true); + Order().SetDefaultSpeed(6); + Order().SetDefaultTempoInt(125); + m_nMinPeriod = 14 * 4; + m_nMaxPeriod = 3424 * 4; + m_nSamplePreAmp = 64; + m_SongFlags.set(SONG_PT_MODE | SONG_IMPORTED | SONG_FORMAT_NO_VOLCOL); + m_playBehaviour.reset(kMODOneShotLoops); + m_playBehaviour.set(kMODIgnorePanning); + m_playBehaviour.set(kMODSampleSwap); // untested + + // Reading patterns + Order().resize(numOrders); + uint8 speed = 0; + if(loadFlags & loadPatternData) + Patterns.ResizeArray(numOrders); + for(PATTERNINDEX pat = 0; pat < numOrders; pat++) + { + Order()[pat] = pat; + if(!(loadFlags & loadPatternData) || !Patterns.Insert(pat, 64)) + break; + + for(CHANNELINDEX chn = 0; chn < 4; chn++) + { + file.Seek(1468 + tracks[pat * 4 + chn] * 64u * 4u); + ModCommand *m = Patterns[pat].GetpModCommand(0, chn); + + for(ROWINDEX row = 0; row < 64; row++, m += 4) + { + const auto [command, param] = ReadMODPatternEntry(file, *m); + + if((command || param) + && !(command == 0x0E && param >= 0x10) // Exx only sets filter + && !(command >= 0x05 && command <= 0x09)) // These don't exist in ST2.6 + { + ConvertModCommand(*m, command, param); + } else + { + m->command = CMD_NONE; + } + } + } + + // Handle speed command with both nibbles set - this enables auto-swing (alternates between the two nibbles) + auto m = Patterns[pat].begin(); + for(ROWINDEX row = 0; row < 64; row++) + { + for(CHANNELINDEX chn = 0; chn < 4; chn++, m++) + { + if(m->command == CMD_SPEED || m->command == CMD_TEMPO) + { + m->command = CMD_SPEED; + if(m->param & 0xF0) + { + if((m->param >> 4) != (m->param & 0x0F) && (m->param & 0x0F) != 0) + { + // Both nibbles set + speed = m->param; + } + m->param >>= 4; + } + } + } + if(speed) + { + speed = mpt::rotr(speed, 4); + Patterns[pat].WriteEffect(EffectWriter(CMD_SPEED, speed & 0x0F).Row(row)); + } + } + } + + // Reading samples + if(loadFlags & loadSampleData) + { + file.Seek(1468 + numTracks * 64u * 4u); + for(SAMPLEINDEX smp = 1; smp <= 31; smp++) if(Samples[smp].nLength) + { + SampleIO( + SampleIO::_8bit, + SampleIO::mono, + SampleIO::littleEndian, + SampleIO::signedPCM) + .ReadSample(Samples[smp], file); + } + } + + return true; +} + +OPENMPT_NAMESPACE_END diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_imf.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_imf.cpp index af779fcd7..2ef0c9fe8 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_imf.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_imf.cpp @@ -45,6 +45,19 @@ struct IMFFileHeader uint8le unused2[8]; char im10[4]; // 'IM10' IMFChannel channels[32]; // Channel settings + + CHANNELINDEX GetNumChannels() const + { + uint8 detectedChannels = 0; + for(uint8 chn = 0; chn < 32; chn++) + { + if(channels[chn].status < 2) + detectedChannels = chn + 1; + else if(channels[chn].status > 2) + return 0; + } + return detectedChannels; + } }; MPT_BINARY_STRUCT(IMFFileHeader, 576) @@ -286,7 +299,7 @@ static std::pair TranslateIMFEffect(uint8 command, uint8 p param |= 0xE0; break; case 0x16: // cutoff - param = (0xFF - param) / 2u; + param = static_cast((0xFF - param) / 2u); break; case 0x17: // cutoff slide + resonance (TODO: cutoff slide is currently not handled) param = 0x80 | (param & 0x0F); @@ -365,29 +378,8 @@ static bool ValidateHeader(const IMFFileHeader &fileHeader) || fileHeader.bpm < 32 || fileHeader.master > 64 || fileHeader.amp < 4 - || fileHeader.amp > 127) - { - return false; - } - bool channelFound = false; - for(const auto &chn : fileHeader.channels) - { - switch(chn.status) - { - case 0: // enabled; don't worry about it - channelFound = true; - break; - case 1: // mute - channelFound = true; - break; - case 2: // disabled - // nothing - break; - default: // uhhhh.... freak out - return false; - } - } - if(!channelFound) + || fileHeader.amp > 127 + || !fileHeader.GetNumChannels()) { return false; } @@ -428,7 +420,7 @@ bool CSoundFile::ReadIMF(FileReader &file, ModLoadingFlags loadFlags) { return false; } - if(!file.CanRead(mpt::saturate_cast(GetHeaderMinimumAdditionalSize(fileHeader)))) + if(!file.CanRead(mpt::saturate_cast(GetHeaderMinimumAdditionalSize(fileHeader)))) { return false; } @@ -437,60 +429,48 @@ bool CSoundFile::ReadIMF(FileReader &file, ModLoadingFlags loadFlags) return true; } + InitializeGlobals(MOD_TYPE_IMF, fileHeader.GetNumChannels()); + + m_modFormat.formatName = UL_("Imago Orpheus"); + m_modFormat.type = UL_("imf"); + m_modFormat.charset = mpt::Charset::CP437; + // Read channel configuration - std::bitset<32> ignoreChannels; // bit set for each channel that's completely disabled - uint8 detectedChannels = 0; - for(uint8 chn = 0; chn < 32; chn++) + std::bitset<32> ignoreChannels; // bit set for each channel that's completely disabled + uint64 channelMuteStatus = 0; + for(CHANNELINDEX chn = 0; chn < GetNumChannels(); chn++) { - ChnSettings[chn].Reset(); - ChnSettings[chn].nPan = fileHeader.channels[chn].panning * 256 / 255; - + ChnSettings[chn].nPan = static_cast(fileHeader.channels[chn].panning * 256 / 255); ChnSettings[chn].szName = mpt::String::ReadBuf(mpt::String::nullTerminated, fileHeader.channels[chn].name); - + channelMuteStatus |= static_cast(fileHeader.channels[chn].status) << (chn * 2); // TODO: reverb/chorus? switch(fileHeader.channels[chn].status) { - case 0: // enabled; don't worry about it - detectedChannels = chn + 1; + case 0: // enabled; don't worry about it break; - case 1: // mute + case 1: // mute ChnSettings[chn].dwFlags = CHN_MUTE; - detectedChannels = chn + 1; break; - case 2: // disabled + case 2: // disabled ChnSettings[chn].dwFlags = CHN_MUTE; ignoreChannels[chn] = true; break; - default: // uhhhh.... freak out - return false; } } - - InitializeGlobals(MOD_TYPE_IMF); - m_nChannels = detectedChannels; - - m_modFormat.formatName = U_("Imago Orpheus"); - m_modFormat.type = U_("imf"); - m_modFormat.charset = mpt::Charset::CP437; - - //From mikmod: work around an Orpheus bug - if(fileHeader.channels[0].status == 0) + // BEHIND.IMF: All channels but the first are muted + // mikmod refers to this as an Orpheus bug, but I haven't seen any other files like this, so maybe it's just an incorrectly saved file? + if(GetNumChannels() == 16 && channelMuteStatus == 0x5555'5554) { - CHANNELINDEX chn; - for(chn = 1; chn < 16; chn++) - if(fileHeader.channels[chn].status != 1) - break; - if(chn == 16) - for(chn = 1; chn < 16; chn++) - ChnSettings[chn].dwFlags.reset(CHN_MUTE); + for(CHANNELINDEX chn = 1; chn < GetNumChannels(); chn++) + ChnSettings[chn].dwFlags.reset(CHN_MUTE); } // Song Name m_songName = mpt::String::ReadBuf(mpt::String::nullTerminated, fileHeader.title); m_SongFlags.set(SONG_LINEARSLIDES, fileHeader.flags & IMFFileHeader::linearSlides); - m_nDefaultSpeed = fileHeader.tempo; - m_nDefaultTempo.Set(fileHeader.bpm); + Order().SetDefaultSpeed(fileHeader.tempo); + Order().SetDefaultTempoInt(fileHeader.bpm); m_nDefaultGlobalVolume = fileHeader.master * 4u; m_nSamplePreAmp = fileHeader.amp; @@ -543,7 +523,7 @@ bool CSoundFile::ReadIMF(FileReader &file, ModLoadingFlags loadFlags) m.note = NOTE_NONE; } else { - m.note = (m.note >> 4) * 12 + (m.note & 0x0F) + 12 + 1; + m.note = static_cast((m.note >> 4) * 12 + (m.note & 0x0F) + 12 + 1); if(!m.IsNoteOrEmpty()) { m.note = NOTE_NONE; diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_ims.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_ims.cpp new file mode 100644 index 000000000..b7930bf36 --- /dev/null +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_ims.cpp @@ -0,0 +1,158 @@ +/* + * Load_ims.cpp + * ------------ + * Purpose: Images Music System (IMS) module loader + * Notes : (currently none) + * Authors: OpenMPT Devs + * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. + */ + + +#include "stdafx.h" +#include "Loaders.h" +#include "MODTools.h" + +OPENMPT_NAMESPACE_BEGIN + +struct IMSFileHeader +{ + std::array songName; + std::array sampleHeaders; + MODFileHeader order; + uint32be sampleDataOffset; + + bool IsValid() const + { + if(CountInvalidChars(songName)) + return false; + + uint16 hasSampleLength = 0; + for(const auto &sampleHeader : sampleHeaders) + { + if(sampleHeader.length > 0x8000 || sampleHeader.finetune || sampleHeader.GetInvalidByteScore()) + return false; + hasSampleLength |= sampleHeader.length; + } + if(!hasSampleLength) + return false; + + if(!order.numOrders || order.numOrders > 128 || order.restartPos > order.numOrders) + return false; + + if(sampleDataOffset <= 1084) + return false; + const auto patternDiv = std::div(sampleDataOffset - 1084, 768); + if(patternDiv.rem) + return false; + const uint8 numPatterns = mpt::saturate_cast(patternDiv.quot); + if(numPatterns > 128) + return false; + + for(const auto pat : order.orderList) + { + if(pat >= numPatterns) + return false; + } + + return true; + } +}; + +MPT_BINARY_STRUCT(IMSFileHeader, 1084) + + +CSoundFile::ProbeResult CSoundFile::ProbeFileHeaderIMS(MemoryFileReader file, const uint64 *pfilesize) +{ + IMSFileHeader fileHeader; + if(!file.ReadStruct(fileHeader)) + return ProbeWantMoreData; + if(pfilesize && *pfilesize < fileHeader.sampleDataOffset) + return ProbeFailure; + if(!fileHeader.IsValid()) + return ProbeFailure; + + return ProbeSuccess; +} + + +bool CSoundFile::ReadIMS(FileReader &file, ModLoadingFlags loadFlags) +{ + file.Rewind(); + IMSFileHeader fileHeader; + if(!file.ReadStruct(fileHeader)) + return false; + if(!fileHeader.IsValid()) + return false; + if(!file.LengthIsAtLeast(fileHeader.sampleDataOffset)) + return false; + + if(loadFlags == onlyVerifyHeader) + return true; + + InitializeGlobals(MOD_TYPE_MOD, 4); + m_SongFlags.set(SONG_IMPORTED | SONG_FORMAT_NO_VOLCOL); + Order().SetDefaultTempoInt(125); + Order().SetDefaultSpeed(6); + Order().SetRestartPos(fileHeader.order.restartPos); + m_nMinPeriod = 113 * 4; + m_nMaxPeriod = 1712 * 4; + m_nSamples = 31; + m_songName = mpt::String::ReadBuf(mpt::String::maybeNullTerminated, fileHeader.songName); + + ReadOrderFromArray(Order(), fileHeader.order.orderList, fileHeader.order.numOrders); + + file.Seek(20); + for(SAMPLEINDEX smp = 1; smp <= m_nSamples; smp++) + { + const auto &sampleHeader = fileHeader.sampleHeaders[smp - 1]; + sampleHeader.ConvertToMPT(Samples[smp], true); + m_szNames[smp] = mpt::String::ReadBuf(mpt::String::maybeNullTerminated, sampleHeader.name); + } + + if(loadFlags & loadPatternData) + { + file.Seek(1084); + const PATTERNINDEX numPatterns = static_cast((fileHeader.sampleDataOffset - 1084u) / 768u); + for(PATTERNINDEX pat = 0; pat < numPatterns; pat++) + { + if(!Patterns.Insert(pat, 64)) + return false; + for(ModCommand &m : Patterns[pat]) + { + const auto data = file.ReadArray(); + if(const uint8 note = (data[0] & 0x3F); note < 48) + m.note = NOTE_MIDDLEC - 24 + note; + else if(note != 63) + return false; + m.instr = ((data[0] & 0xC0) >> 2) | (data[1] >> 4); + if(m.instr > 31) + return false; + ConvertModCommand(m, data[1] & 0x0F, data[2]); + } + } + } + + if(loadFlags & loadSampleData) + { + file.Seek(fileHeader.sampleDataOffset); + for(SAMPLEINDEX smp = 1; smp <= m_nSamples; smp++) + { + if(!Samples[smp].nLength) + continue; + SampleIO( + SampleIO::_8bit, + SampleIO::mono, + SampleIO::littleEndian, + SampleIO::signedPCM) + .ReadSample(Samples[smp], file); + } + } + + m_modFormat.formatName = UL_("Images Music System"); + m_modFormat.type = UL_("ims"); + m_modFormat.charset = mpt::Charset::Amiga_no_C1; + + return true; +} + +OPENMPT_NAMESPACE_END diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_it.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_it.cpp index 5edd4885f..cd2314083 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_it.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_it.cpp @@ -262,13 +262,13 @@ static void ReadTuningMapImpl(std::istream& iStrm, CSoundFile& csf, mpt::Charset #endif // MODPLUG_TRACKER } - csf.Instruments[i]->pTuning = csf.GetDefaultTuning(); + csf.Instruments[i]->pTuning = nullptr; } else { //This 'else' happens probably only in case of corrupted file. if(csf.Instruments[i]) - csf.Instruments[i]->pTuning = csf.GetDefaultTuning(); + csf.Instruments[i]->pTuning = nullptr; } } @@ -302,7 +302,7 @@ size_t CSoundFile::ITInstrToMPT(FileReader &file, ModInstrument &ins, uint16 trk } } else { - const FileReader::off_t offset = file.GetPosition(); + const FileReader::pos_type offset = file.GetPosition(); // Try loading extended instrument... instSize will differ between normal and extended instruments. ITInstrumentEx instrumentHeader; @@ -441,7 +441,7 @@ bool CSoundFile::ReadIT(FileReader &file, ModLoadingFlags loadFlags) { return false; } - if(!file.CanRead(mpt::saturate_cast(GetHeaderMinimumAdditionalSize(fileHeader)))) + if(!file.CanRead(mpt::saturate_cast(GetHeaderMinimumAdditionalSize(fileHeader)))) { return false; } @@ -450,7 +450,7 @@ bool CSoundFile::ReadIT(FileReader &file, ModLoadingFlags loadFlags) return true; } - InitializeGlobals(MOD_TYPE_IT); + InitializeGlobals(MOD_TYPE_IT, 0); bool interpretModPlugMade = false; mpt::ustring madeWithTracker; @@ -510,7 +510,7 @@ bool CSoundFile::ReadIT(FileReader &file, ModLoadingFlags loadFlags) { // ModPlug Tracker b3.2 - 1.09, instruments 557 bytes apart m_dwLastSavedWithVersion = MPT_V("1.09.00.00"); - madeWithTracker = U_("ModPlug Tracker b3.2 - 1.09"); + madeWithTracker = UL_("ModPlug Tracker b3.2 - 1.09"); interpretModPlugMade = true; } else if(fileHeader.cwtv == 0x0300 && fileHeader.cmwt == 0x0300 && fileHeader.reserved == 0 && fileHeader.ordnum == 256 && fileHeader.sep == 128 && fileHeader.pwd == 0) { @@ -553,21 +553,10 @@ bool CSoundFile::ReadIT(FileReader &file, ModLoadingFlags loadFlags) if(m_nDefaultGlobalVolume > MAX_GLOBAL_VOLUME) m_nDefaultGlobalVolume = MAX_GLOBAL_VOLUME; if(fileHeader.speed) - m_nDefaultSpeed = fileHeader.speed; - m_nDefaultTempo.Set(std::max(uint8(31), static_cast(fileHeader.tempo))); + Order().SetDefaultSpeed(fileHeader.speed); + Order().SetDefaultTempoInt(std::max(uint8(31), static_cast(fileHeader.tempo))); m_nSamplePreAmp = std::min(static_cast(fileHeader.mv), uint8(128)); - // Reading Channels Pan Positions - for(CHANNELINDEX i = 0; i < 64; i++) if(fileHeader.chnpan[i] != 0xFF) - { - ChnSettings[i].Reset(); - ChnSettings[i].nVolume = Clamp(fileHeader.chnvol[i], 0, 64); - if(fileHeader.chnpan[i] & 0x80) ChnSettings[i].dwFlags.set(CHN_MUTE); - uint8 n = fileHeader.chnpan[i] & 0x7F; - if(n <= 64) ChnSettings[i].nPan = n * 4; - if(n == 100) ChnSettings[i].dwFlags.set(CHN_SURROUND); - } - // Reading orders file.Seek(sizeof(ITFileHeader)); if(GetType() == MOD_TYPE_MPT && fileHeader.cwtv > 0x88A && fileHeader.cwtv <= 0x88D) @@ -641,13 +630,13 @@ bool CSoundFile::ReadIT(FileReader &file, ModLoadingFlags loadFlags) } if(oldUNMO3) { - madeWithTracker = U_("UNMO3 <= 2.4"); + madeWithTracker = UL_("UNMO3 <= 2.4"); } } if(possiblyUNMO3 && fileHeader.cwtv == 0) { - madeWithTracker = U_("UNMO3 v0/1"); + madeWithTracker = UL_("UNMO3 v0/1"); } // Reading IT Edit History Info @@ -673,9 +662,9 @@ bool CSoundFile::ReadIT(FileReader &file, ModLoadingFlags loadFlags) if(possiblyUNMO3 && nflt == 0) { if(fileHeader.special & ITFileHeader::embedPatternHighlights) - madeWithTracker = U_("UNMO3 <= 2.4.0.1"); // Set together with MIDI macro embed flag + madeWithTracker = UL_("UNMO3 <= 2.4.0.1"); // Set together with MIDI macro embed flag else - madeWithTracker = U_("UNMO3"); // Either 2.4.0.2+ or no MIDI macros embedded + madeWithTracker = UL_("UNMO3"); // Either 2.4.0.2+ or no MIDI macros embedded } } else { @@ -685,12 +674,12 @@ bool CSoundFile::ReadIT(FileReader &file, ModLoadingFlags loadFlags) } else if(possiblyUNMO3 && fileHeader.special <= 1) { // UNMO3 < v2.4.0.1 will set the edit history special bit iff the MIDI macro embed bit is also set, - // but it always writes the two extra bytes for the edit history length (zeroes). + // but it always writes the two extra bytes for the edit history length (zeros). // If MIDI macros are embedded, we are fine and end up in the first case of the if statement (read edit history). // Otherwise we end up here and might have to read the edit history length. if(file.ReadUint16LE() == 0) { - madeWithTracker = U_("UNMO3 <= 2.4"); + madeWithTracker = UL_("UNMO3 <= 2.4"); } else { // These were not zero bytes, but potentially belong to the upcoming MIDI config - need to skip back. @@ -717,35 +706,32 @@ bool CSoundFile::ReadIT(FileReader &file, ModLoadingFlags loadFlags) hasModPlugExtensions = true; } - m_nChannels = 1; // Read channel names: "CNAM" if(file.ReadMagic("CNAM")) { FileReader chnNames = file.ReadChunk(file.ReadUint32LE()); - const CHANNELINDEX readChns = std::min(MAX_BASECHANNELS, static_cast(chnNames.GetLength() / MAX_CHANNELNAME)); - m_nChannels = readChns; + ChnSettings.resize(std::min(MAX_BASECHANNELS, static_cast(chnNames.GetLength() / MAX_CHANNELNAME))); hasModPlugExtensions = true; - - for(CHANNELINDEX i = 0; i < readChns; i++) + for(auto &chn : ChnSettings) { - chnNames.ReadString(ChnSettings[i].szName, MAX_CHANNELNAME); + chnNames.ReadString(chn.szName, MAX_CHANNELNAME); } } // Read mix plugins information FileReader pluginChunk = file.ReadChunk((minPtr >= file.GetPosition()) ? minPtr - file.GetPosition() : file.BytesLeft()); - const auto [hasPluginChunks, isBeRoTracker] = LoadMixPlugins(pluginChunk); + const auto [hasPluginChunks, isBeRoTracker] = LoadMixPlugins(pluginChunk, false); if(hasPluginChunks) hasModPlugExtensions = true; if(fileHeader.cwtv == 0x0217 && fileHeader.cmwt == 0x0200 && fileHeader.reserved == 0 && !isBeRoTracker) { if(hasModPlugExtensions - || (!Order().empty() && Order().back() == Order.GetInvalidPatIndex()) + || (!Order().empty() && Order().back() == PATTERNINDEX_INVALID) || memchr(fileHeader.chnpan, 0xFF, sizeof(fileHeader.chnpan)) != nullptr) { m_dwLastSavedWithVersion = MPT_V("1.16"); - madeWithTracker = U_("ModPlug Tracker 1.09 - 1.16"); + madeWithTracker = UL_("ModPlug Tracker 1.09 - 1.16"); } else { // OpenMPT 1.17 disguised as this in compatible mode, @@ -753,7 +739,7 @@ bool CSoundFile::ReadIT(FileReader &file, ModLoadingFlags loadFlags) // It also doesn't write a final "---" pattern in the order list. // Could also be original ModPlug Tracker though if all 64 channels and no ModPlug extensions are used. m_dwLastSavedWithVersion = MPT_V("1.17"); - madeWithTracker = U_("OpenMPT 1.17 (compatibility export)"); + madeWithTracker = UL_("OpenMPT 1.17 (compatibility export)"); } interpretModPlugMade = true; } @@ -790,7 +776,7 @@ bool CSoundFile::ReadIT(FileReader &file, ModLoadingFlags loadFlags) // In order to properly compute the position, in file, of eventual extended settings // such as "attack" we need to keep the "real" size of the last sample as those extra // setting will follow this sample in the file - FileReader::off_t lastSampleOffset = 0; + FileReader::pos_type lastSampleOffset = 0; if(fileHeader.smpnum > 0) { lastSampleOffset = smpPos[fileHeader.smpnum - 1] + sizeof(ITSample); @@ -904,7 +890,7 @@ bool CSoundFile::ReadIT(FileReader &file, ModLoadingFlags loadFlags) possibleXMconversion = false; } if(possibleXMconversion) - madeWithTracker = U_("XM Conversion"); + madeWithTracker = UL_("XM Conversion"); } m_nMinPeriod = 0; @@ -923,7 +909,8 @@ bool CSoundFile::ReadIT(FileReader &file, ModLoadingFlags loadFlags) numPats = 0; } - // Checking for number of used channels, which is not explicitely specified in the file. + // Checking for number of used channels, which is not explicitly specified in the file. + CHANNELINDEX numChannels = std::max(GetNumChannels(), CHANNELINDEX(1)); for(PATTERNINDEX pat = 0; pat < numPats; pat++) { if(patPos[pat] == 0 || !file.Seek(patPos[pat])) @@ -939,7 +926,7 @@ bool CSoundFile::ReadIT(FileReader &file, ModLoadingFlags loadFlags) FileReader patternData = file.ReadChunk(len); ROWINDEX row = 0; - std::vector chnMask(GetNumChannels()); + std::vector chnMask(numChannels); while(row < numRows && patternData.CanRead(1)) { @@ -961,34 +948,26 @@ bool CSoundFile::ReadIT(FileReader &file, ModLoadingFlags loadFlags) chnMask.resize(ch + 1, 0); } - if(b & IT_bitmask_patternChanEnabled_c) // 0x80 check if the upper bit is enabled. + if(b & IT_bitmask_patternChanEnabled_c) // 0x80 check if the upper bit is enabled. { - chnMask[ch] = patternData.ReadUint8(); // set the channel mask for this channel. + chnMask[ch] = patternData.ReadUint8(); // set the channel mask for this channel. } // Channel used - if(chnMask[ch] & 0x0F) // if this channel is used set m_nChannels + if(chnMask[ch] & 0x0F) // if this channel is used set m_nChannels { - if(ch >= GetNumChannels() && ch < MAX_BASECHANNELS) + if(ch >= numChannels && ch < MAX_BASECHANNELS) { - m_nChannels = ch + 1; + numChannels = ch + 1; } + + // Skip a number of bytes depending on note, instrument, volume, effect being present. + static constexpr uint8 maskToSkips[] = {0, 1, 1, 2, 1, 2, 2, 3, 2, 3, 3, 4, 3, 4, 4, 5}; + patternData.Skip(maskToSkips[chnMask[ch] & 0x0F]); } - // Now we actually update the pattern-row entry the note,instrument etc. - // Note - if(chnMask[ch] & 1) - patternData.Skip(1); - // Instrument - if(chnMask[ch] & 2) - patternData.Skip(1); - // Volume - if(chnMask[ch] & 4) - patternData.Skip(1); - // Effect - if(chnMask[ch] & 8) - patternData.Skip(2); } lastSampleOffset = std::max(lastSampleOffset, file.GetPosition()); } + ChnSettings.resize(numChannels); // Compute extra instruments settings position if(lastSampleOffset > 0) @@ -1027,8 +1006,25 @@ bool CSoundFile::ReadIT(FileReader &file, ModLoadingFlags loadFlags) // Need to do this before reading the patterns because m_nChannels might be modified by LoadExtendedSongProperties. *sigh* const bool hasExtendedSongProperties = LoadExtendedSongProperties(file, false, &interpretModPlugMade); + // Reading Channels Pan Positions + const CHANNELINDEX headerChannels = std::min(GetNumChannels(), CHANNELINDEX(64)); + for(CHANNELINDEX i = 0; i < headerChannels; i++) + { + if(fileHeader.chnpan[i] == 0xFF) + continue; + ChnSettings[i].nVolume = Clamp(fileHeader.chnvol[i], 0, 64); + if(fileHeader.chnpan[i] & 0x80) + ChnSettings[i].dwFlags.set(CHN_MUTE); + uint8 n = fileHeader.chnpan[i] & 0x7F; + if(n <= 64) + ChnSettings[i].nPan = n * 4; + if(n == 100) + ChnSettings[i].dwFlags.set(CHN_SURROUND); + } + // Reading Patterns Patterns.ResizeArray(numPats); + bool hasVolColOffset = false; for(PATTERNINDEX pat = 0; pat < numPats; pat++) { if(patPos[pat] == 0 || !file.Seek(patPos[pat])) @@ -1092,7 +1088,7 @@ bool CSoundFile::ReadIT(FileReader &file, ModLoadingFlags loadFlags) } // Now we grab the data for this particular row/channel. - ModCommand &m = ch < m_nChannels ? patData[ch] : dummy; + ModCommand &m = ch < GetNumChannels() ? patData[ch] : dummy; if(chnMask[ch] & 0x10) { @@ -1117,19 +1113,19 @@ bool CSoundFile::ReadIT(FileReader &file, ModLoadingFlags loadFlags) uint8 note = patternData.ReadUint8(); if(note < 0x80) note += NOTE_MIN; - if(!(GetType() & MOD_TYPE_MPT)) - { - if(note > NOTE_MAX && note < 0xFD) note = NOTE_FADE; - else if(note == 0xFD) note = NOTE_NONE; - } - m.note = note; - lastValue[ch].note = note; + else if(note == 0xFF) + note = NOTE_KEYOFF; + else if(note == 0xFE) + note = NOTE_NOTECUT; + else if(note == 0xFD && GetType() != MOD_TYPE_MPT) + note = NOTE_NONE; // Note: in MPTM format, NOTE_FADE is written as 0xFD to preserve compatibility with older OpenMPT versions. + else + note = NOTE_FADE; + m.note = lastValue[ch].note = note; } if(chnMask[ch] & 2) { - uint8 instr = patternData.ReadUint8(); - m.instr = instr; - lastValue[ch].instr = instr; + m.instr = lastValue[ch].instr = patternData.ReadUint8(); } if(chnMask[ch] & 4) { @@ -1163,7 +1159,7 @@ bool CSoundFile::ReadIT(FileReader &file, ModLoadingFlags loadFlags) } else // 213-222: Unused (was velocity) // 223-232: Offset - if(vol >= 223 && vol <= 232) { m.volcmd = VOLCMD_OFFSET; m.vol = vol - 223; } + if(vol >= 223 && vol <= 232) { m.volcmd = VOLCMD_OFFSET; m.vol = vol - 223; hasVolColOffset = true; } lastValue[ch].volcmd = m.volcmd; lastValue[ch].vol = m.vol; } @@ -1190,6 +1186,14 @@ bool CSoundFile::ReadIT(FileReader &file, ModLoadingFlags loadFlags) } } } + // Remove (default) cue points if no volume column offset is found (unless it's a new enough MPTM file, which could contain intentionally-placed custom cue points that we don't want to lose) + if(!hasVolColOffset && (GetType() != MOD_TYPE_MPT || m_dwLastSavedWithVersion < MPT_V("1.24.02.06"))) + { + for(SAMPLEINDEX smp = 1; smp <= GetNumSamples(); smp++) + { + Samples[smp].RemoveAllCuePoints(); + } + } if(!m_dwLastSavedWithVersion && fileHeader.cwtv == 0x0888) { @@ -1200,7 +1204,7 @@ bool CSoundFile::ReadIT(FileReader &file, ModLoadingFlags loadFlags) if(m_dwLastSavedWithVersion && madeWithTracker.empty()) { - madeWithTracker = U_("OpenMPT ") + mpt::ufmt::val(m_dwLastSavedWithVersion); + madeWithTracker = UL_("OpenMPT ") + mpt::ufmt::val(m_dwLastSavedWithVersion); bool isCompatExport = memcmp(&fileHeader.reserved, "OMPT", 4) && (fileHeader.cwtv & 0xF000) == 0x5000; if(m_dwLastSavedWithVersion == MPT_V("1.17.00.00")) @@ -1208,10 +1212,10 @@ bool CSoundFile::ReadIT(FileReader &file, ModLoadingFlags loadFlags) if(isCompatExport) { - madeWithTracker += U_(" (compatibility export)"); + madeWithTracker += UL_(" (compatibility export)"); } else if(m_dwLastSavedWithVersion.IsTestVersion()) { - madeWithTracker += U_(" (test build)"); + madeWithTracker += UL_(" (test build)"); } } else { @@ -1222,19 +1226,19 @@ bool CSoundFile::ReadIT(FileReader &file, ModLoadingFlags loadFlags) if(isBeRoTracker) { // Old versions - madeWithTracker = U_("BeRoTracker"); + madeWithTracker = UL_("BeRoTracker"); } else if(fileHeader.cwtv == 0x0214 && fileHeader.cmwt == 0x0200 && fileHeader.flags == 9 && fileHeader.special == 0 && fileHeader.highlight_major == 0 && fileHeader.highlight_minor == 0 && fileHeader.insnum == 0 && fileHeader.patnum + 1 == fileHeader.ordnum && fileHeader.globalvol == 128 && fileHeader.mv == 100 && fileHeader.speed == 1 && fileHeader.sep == 128 && fileHeader.pwd == 0 && fileHeader.msglength == 0 && fileHeader.msgoffset == 0 && fileHeader.reserved == 0) { - madeWithTracker = U_("OpenSPC conversion"); + madeWithTracker = UL_("OpenSPC conversion"); } else if(fileHeader.cwtv == 0x0202 && fileHeader.cmwt == 0x0200 && fileHeader.highlight_major == 0 && fileHeader.highlight_minor == 0 && fileHeader.reserved == 0 && !patPos.empty() && !smpPos.empty() && patPos[0] != 0 && patPos[0] < smpPos[0]) { // ModPlug Tracker 1.0 pre-alpha up to alpha 4, patterns located before instruments / samples m_dwLastSavedWithVersion = MPT_V("1.00.00.A0"); - madeWithTracker = U_("ModPlug Tracker 1.0 pre-alpha / alpha"); + madeWithTracker = UL_("ModPlug Tracker 1.0 pre-alpha / alpha"); interpretModPlugMade = true; } else if(fileHeader.cwtv == 0x0214 && fileHeader.cmwt == 0x0200 && fileHeader.highlight_major == 0 && fileHeader.highlight_minor == 0 && fileHeader.reserved == 0) { @@ -1245,32 +1249,32 @@ bool CSoundFile::ReadIT(FileReader &file, ModLoadingFlags loadFlags) if(insPos.size() >= 2 && insPos[1] - insPos[0] == 557) { m_dwLastSavedWithVersion = MPT_V("1.00.00.B2"); - madeWithTracker = U_("ModPlug Tracker 1.0b2"); + madeWithTracker = UL_("ModPlug Tracker 1.0b2"); } else { m_dwLastSavedWithVersion = MPT_V("1.00.00.B1"); - madeWithTracker = U_("ModPlug Tracker 1.0 alpha / beta"); + madeWithTracker = UL_("ModPlug Tracker 1.0 alpha / beta"); } } else { // ModPlug Tracker 1.0a5, instruments 560 bytes apart m_dwLastSavedWithVersion = MPT_V("1.00.00.A5"); - madeWithTracker = U_("ModPlug Tracker 1.0a5"); + madeWithTracker = UL_("ModPlug Tracker 1.0a5"); } interpretModPlugMade = true; } else if(fileHeader.cwtv == 0x0214 && fileHeader.cmwt == 0x0214 && !memcmp(&fileHeader.reserved, "CHBI", 4)) { - madeWithTracker = U_("ChibiTracker"); + madeWithTracker = UL_("ChibiTracker"); m_playBehaviour.reset(kITShortSampleRetrig); m_nSamplePreAmp /= 2; } else if(fileHeader.cwtv == 0x0214 && fileHeader.cmwt == 0x0214 && fileHeader.special <= 1 && fileHeader.pwd == 0 && fileHeader.reserved == 0 && (fileHeader.flags & (ITFileHeader::vol0Optimisations | ITFileHeader::instrumentMode | ITFileHeader::useMIDIPitchController | ITFileHeader::reqEmbeddedMIDIConfig | ITFileHeader::extendedFilterRange)) == ITFileHeader::instrumentMode && m_nSamples > 1 && (Samples[1].filename == "XXXXXXXX.YYY")) { - madeWithTracker = U_("CheeseTracker"); + madeWithTracker = UL_("CheeseTracker"); } else if(fileHeader.cwtv == 0 && madeWithTracker.empty()) { - madeWithTracker = U_("Unknown"); + madeWithTracker = UL_("Unknown"); } else if(fileHeader.cwtv >= 0x0208 && fileHeader.cwtv <= 0x0214 && !fileHeader.reserved && m_FileHistory.empty() && madeWithTracker.empty()) { // Any file made with IT starting from v2.07 onwards should have an edit history @@ -1291,83 +1295,68 @@ bool CSoundFile::ReadIT(FileReader &file, ModLoadingFlags loadFlags) break; case 1: madeWithTracker = GetSchismTrackerVersion(fileHeader.cwtv, fileHeader.reserved); - // Hertz in linear mode: Added 2015-01-29, https://github.com/schismtracker/schismtracker/commit/671b30311082a0e7df041fca25f989b5d2478f69 - if(schismDateVersion < SchismVersionFromDate<2015, 01, 29>::date && m_SongFlags[SONG_LINEARSLIDES]) - m_playBehaviour.reset(kPeriodsAreHertz); + { + static constexpr std::pair SchismQuirks[] = + { + {SchismVersionFromDate<2015, 1, 29>::date, kPeriodsAreHertz }, // https://github.com/schismtracker/schismtracker/commit/671b30311082a0e7df041fca25f989b5d2478f69 + {SchismVersionFromDate<2016, 5, 13>::date, kITShortSampleRetrig }, // https://github.com/schismtracker/schismtracker/commit/e7b1461fe751554309fd403713c2a1ef322105ca + {SchismVersionFromDate<2021, 5, 2>::date, kITDoNotOverrideChannelPan }, // https://github.com/schismtracker/schismtracker/commit/a34ec86dc819915debc9e06f4727b77bf2dd29ee + {SchismVersionFromDate<2021, 5, 2>::date, kITPanningReset }, // https://github.com/schismtracker/schismtracker/commit/648f5116f984815c69e11d018b32dfec53c6b97a + {SchismVersionFromDate<2021, 11, 1>::date, kITPitchPanSeparation }, // https://github.com/schismtracker/schismtracker/commit/6e9f1207015cae0fe1b829fff7bb867e02ec6dea + {SchismVersionFromDate<2022, 4, 30>::date, kITEmptyNoteMapSlot }, // https://github.com/schismtracker/schismtracker/commit/1b2f7d5522fcb971f134a6664182ca569f7c8008 + {SchismVersionFromDate<2022, 4, 30>::date, kITPortamentoSwapResetsPos }, // https://github.com/schismtracker/schismtracker/commit/1b2f7d5522fcb971f134a6664182ca569f7c8008 + {SchismVersionFromDate<2022, 4, 30>::date, kITMultiSampleInstrumentNumber}, // https://github.com/schismtracker/schismtracker/commit/1b2f7d5522fcb971f134a6664182ca569f7c8008 + {SchismVersionFromDate<2023, 3, 9>::date, kITInitialNoteMemory }, // https://github.com/schismtracker/schismtracker/commit/73e9d60676c2b48c8e94e582373e29517105b2b1 + {SchismVersionFromDate<2023, 10, 17>::date, kITDCTBehaviour }, // https://github.com/schismtracker/schismtracker/commit/31d36dc00013fc5ab0efa20c782af18e8b006e07 + {SchismVersionFromDate<2023, 10, 19>::date, kITSampleAndHoldPanbrello }, // https://github.com/schismtracker/schismtracker/commit/411ec16b190ba1a486d8b0907ad8d74f8fdc2840 + {SchismVersionFromDate<2023, 10, 19>::date, kITPortaNoNote }, // https://github.com/schismtracker/schismtracker/commit/8ff0a86a715efb50c89770fb9095d4c4089ff187 + {SchismVersionFromDate<2023, 10, 22>::date, kITFirstTickHandling }, // https://github.com/schismtracker/schismtracker/commit/b9609e4f827e1b6ce9ebe6573b85e69388ca0ea0 + {SchismVersionFromDate<2023, 10, 22>::date, kITMultiSampleInstrumentNumber}, // https://github.com/schismtracker/schismtracker/commit/a9e5df533ab52c35190fcc1cbfed4f0347b660bb + {SchismVersionFromDate<2024, 3, 9>::date, kITPanbrelloHold }, // https://github.com/schismtracker/schismtracker/commit/ebdebaa8c8a735a7bf49df55debded1b7aac3605 + {SchismVersionFromDate<2024, 5, 12>::date, kITNoSustainOnPortamento }, // https://github.com/schismtracker/schismtracker/commit/6f68f2855a7e5e4ffe825869244e631e15741037 + {SchismVersionFromDate<2024, 5, 12>::date, kITEmptyNoteMapSlotIgnoreCell }, // https://github.com/schismtracker/schismtracker/commit/aa84148e019a65f3d52ecd33fd84bfecfdb87bf4 + {SchismVersionFromDate<2024, 5, 27>::date, kITOffsetWithInstrNumber }, // https://github.com/schismtracker/schismtracker/commit/9237960d45079a54ad73f87bacfe5dd8ae82e273 + {SchismVersionFromDate<2024, 10, 13>::date, kITDoublePortamentoSlides }, // https://github.com/schismtracker/schismtracker/commit/223e327d9448561931b8cac8a55180286b17276c + {SchismVersionFromDate<2025, 1, 8>::date, kITCarryAfterNoteOff }, // https://github.com/schismtracker/schismtracker/commit/ff7a817df327c8f13d97b8c6546a9329f59edff8 + }; + for(const auto &quirk : SchismQuirks) + { + if(schismDateVersion < quirk.first) + m_playBehaviour.reset(quirk.second); + } + } // Hertz in Amiga mode: Added 2021-05-02, https://github.com/schismtracker/schismtracker/commit/c656a6cbd5aaf81198a7580faf81cb7960cb6afa - else if(schismDateVersion < SchismVersionFromDate<2021, 05, 02>::date && !m_SongFlags[SONG_LINEARSLIDES]) + if(schismDateVersion < SchismVersionFromDate<2021, 05, 02>::date && !m_SongFlags[SONG_LINEARSLIDES]) m_playBehaviour.reset(kPeriodsAreHertz); - // Qxx with short samples: Added 2016-05-13, https://github.com/schismtracker/schismtracker/commit/e7b1461fe751554309fd403713c2a1ef322105ca - if(schismDateVersion < SchismVersionFromDate<2016, 05, 13>::date) - m_playBehaviour.reset(kITShortSampleRetrig); - // Instrument pan doesn't override channel pan: Added 2021-05-02, https://github.com/schismtracker/schismtracker/commit/a34ec86dc819915debc9e06f4727b77bf2dd29ee - if(schismDateVersion < SchismVersionFromDate<2021, 05, 02>::date) - m_playBehaviour.reset(kITDoNotOverrideChannelPan); - // Notes set instrument panning, not instrument numbers: Added 2021-05-02, https://github.com/schismtracker/schismtracker/commit/648f5116f984815c69e11d018b32dfec53c6b97a - if(schismDateVersion < SchismVersionFromDate<2021, 05, 02>::date) - m_playBehaviour.reset(kITPanningReset); // Imprecise calculation of ping-pong loop wraparound: Added 2021-11-01, https://github.com/schismtracker/schismtracker/commit/22cbb9b676e9c2c9feb7a6a17deca7a17ac138cc if(schismDateVersion < SchismVersionFromDate<2021, 11, 01>::date) m_playBehaviour.set(kImprecisePingPongLoops); - // Pitch/Pan Separation can be overridden by panning commands: Added 2021-11-01, https://github.com/schismtracker/schismtracker/commit/6e9f1207015cae0fe1b829fff7bb867e02ec6dea - if(schismDateVersion < SchismVersionFromDate<2021, 11, 01>::date) - m_playBehaviour.reset(kITPitchPanSeparation); - // Various fixes to multisampled instruments: Added 2022-04-30, https://github.com/schismtracker/schismtracker/commit/1b2f7d5522fcb971f134a6664182ca569f7c8008 - if(schismDateVersion < SchismVersionFromDate<2022, 04, 30>::date) - { - m_playBehaviour.reset(kITEmptyNoteMapSlot); - m_playBehaviour.reset(kITPortamentoSwapResetsPos); - m_playBehaviour.reset(kITMultiSampleInstrumentNumber); - } - // Initial note memory for channel is C-0: Added 2023-03-09, https://github.com/schismtracker/schismtracker/commit/73e9d60676c2b48c8e94e582373e29517105b2b1 - if(schismDateVersion < SchismVersionFromDate<2023, 03, 9>::date) - m_playBehaviour.reset(kITInitialNoteMemory); - // DCT note comparison: Added 2023-10-17, https://github.com/schismtracker/schismtracker/commit/31d36dc00013fc5ab0efa20c782af18e8b006e07 - if(schismDateVersion < SchismVersionFromDate<2023, 10, 17>::date) - m_playBehaviour.reset(kITDCTBehaviour); - if(schismDateVersion < SchismVersionFromDate<2023, 10, 19>::date) - { - // Panbrello sample & hold random waveform: Added 2023-10-19, https://github.com/schismtracker/schismtracker/commit/411ec16b190ba1a486d8b0907ad8d74f8fdc2840 - m_playBehaviour.reset(kITSampleAndHoldPanbrello); - // Don't apply any portamento if no previous note is playing: Added 2023-10-19, https://github.com/schismtracker/schismtracker/commit/8ff0a86a715efb50c89770fb9095d4c4089ff187 - m_playBehaviour.reset(kITPortaNoNote); - } - if(schismDateVersion < SchismVersionFromDate<2023, 10, 22>::date) - { - // Note delay delays first-tick behaviour for slides: Added 2023-10-22, https://github.com/schismtracker/schismtracker/commit/b9609e4f827e1b6ce9ebe6573b85e69388ca0ea0 - m_playBehaviour.reset(kITFirstTickHandling); - // https://github.com/schismtracker/schismtracker/commit/a9e5df533ab52c35190fcc1cbfed4f0347b660bb - m_playBehaviour.reset(kITMultiSampleInstrumentNumber); - } - // Panbrello hold: Added 2024-03-09, https://github.com/schismtracker/schismtracker/commit/ebdebaa8c8a735a7bf49df55debded1b7aac3605 - if(schismDateVersion < SchismVersionFromDate<2024, 03, 9>::date) - m_playBehaviour.reset(kITPanbrelloHold); break; case 4: madeWithTracker = MPT_UFORMAT("pyIT {}.{}")((fileHeader.cwtv & 0x0F00) >> 8, mpt::ufmt::hex0<2>(fileHeader.cwtv & 0xFF)); break; case 6: - madeWithTracker = U_("BeRoTracker"); + madeWithTracker = UL_("BeRoTracker"); break; case 7: if(fileHeader.cwtv == 0x7FFF && fileHeader.cmwt == 0x0215) - madeWithTracker = U_("munch.py"); + madeWithTracker = UL_("munch.py"); else madeWithTracker = MPT_UFORMAT("ITMCK {}.{}.{}")((fileHeader.cwtv >> 8) & 0x0F, (fileHeader.cwtv >> 4) & 0x0F, fileHeader.cwtv & 0x0F); break; case 0xD: if(fileHeader.cwtv == 0xDAEB) - madeWithTracker = U_("spc2it"); + madeWithTracker = UL_("spc2it"); else if(fileHeader.cwtv == 0xD1CE) - madeWithTracker = U_("itwriter"); + madeWithTracker = UL_("itwriter"); else - madeWithTracker = U_("Unknown"); + madeWithTracker = UL_("Unknown"); break; } } if(anyADPCM) - madeWithTracker += U_(" (ADPCM packed)"); + madeWithTracker += UL_(" (ADPCM packed)"); // Ignore MIDI data. Fixes some files like denonde.it that were made with old versions of Impulse Tracker (which didn't support Zxx filters) and have Zxx effects in the patterns. // Example: denonde.it by Mystical @@ -1595,8 +1584,8 @@ bool CSoundFile::SaveIT(std::ostream &f, const mpt::PathString &filename, bool c itHeader.globalvol = static_cast(m_nDefaultGlobalVolume / 2u); itHeader.mv = static_cast(std::min(m_nSamplePreAmp, uint32(128))); - itHeader.speed = mpt::saturate_cast(m_nDefaultSpeed); - itHeader.tempo = mpt::saturate_cast(m_nDefaultTempo.GetInt()); // We save the real tempo in an extension below if it exceeds 255. + itHeader.speed = mpt::saturate_cast(Order().GetDefaultSpeed()); + itHeader.tempo = mpt::saturate_cast(Order().GetDefaultTempo().GetInt()); // We save the real tempo in an extension below if it exceeds 255. itHeader.sep = 128; // pan separation // IT doesn't have a per-instrument Pitch Wheel Depth setting, so we just store the first non-zero PWD setting in the header. for(INSTRUMENTINDEX ins = 1; ins <= GetNumInstruments(); ins++) @@ -1613,7 +1602,7 @@ bool CSoundFile::SaveIT(std::ostream &f, const mpt::PathString &filename, bool c memset(itHeader.chnpan, 0xA0, 64); memset(itHeader.chnvol, 64, 64); - for(CHANNELINDEX ich = 0; ich < std::min(m_nChannels, CHANNELINDEX(64)); ich++) // Header only has room for settings for 64 chans... + for(CHANNELINDEX ich = 0; ich < std::min(GetNumChannels(), CHANNELINDEX(64)); ich++) // Header only has room for settings for 64 chans... { itHeader.chnpan[ich] = (uint8)(ChnSettings[ich].nPan >> 2); if (ChnSettings[ich].dwFlags[CHN_SURROUND]) itHeader.chnpan[ich] = 100; @@ -1627,7 +1616,7 @@ bool CSoundFile::SaveIT(std::ostream &f, const mpt::PathString &filename, bool c // Channel names if(!compatibilityExport) { - for(CHANNELINDEX i = 0; i < m_nChannels; i++) + for(CHANNELINDEX i = 0; i < GetNumChannels(); i++) { if(ChnSettings[i].szName[0]) { @@ -1811,14 +1800,23 @@ bool CSoundFile::SaveIT(std::ostream &f, const mpt::PathString &filename, bool c } auto &chnState = chnStates[ch]; - uint8 b = 0; + uint8 b = 1; uint8 vol = 0xFF; uint8 note = m->note; - if (note != NOTE_NONE) b |= 1; - if (m->IsNote()) note -= NOTE_MIN; - if (note == NOTE_FADE && GetType() != MOD_TYPE_MPT) note = 0xF6; - if (m->instr) b |= 2; - if (m->volcmd != VOLCMD_NONE) + if(note >= NOTE_MIN && note <= NOTE_MIN + 119) + note = m->note - NOTE_MIN; + else if(note == NOTE_FADE) + note = (GetType() == MOD_TYPE_MPT) ? 0xFD : 0xF6; + else if(note == NOTE_NOTECUT) + note = 0xFE; + else if(note == NOTE_KEYOFF) + note = 0xFF; + else + b = 0; + + if(m->instr) + b |= 2; + if(m->volcmd != VOLCMD_NONE) { vol = std::min(m->vol, uint8(9)); switch(m->volcmd) @@ -1843,7 +1841,8 @@ bool CSoundFile::SaveIT(std::ostream &f, const mpt::PathString &filename, bool c default: vol = 0xFF; } } - if (vol != 0xFF) b |= 4; + if(vol != 0xFF) + b |= 4; uint8 command = 0, param = 0; if(m->command == CMD_VOLUME && vol == 0xFF) { @@ -2032,7 +2031,7 @@ bool CSoundFile::SaveIT(std::ostream &f, const mpt::PathString &filename, bool c { if(GetNumInstruments()) { - SaveExtendedInstrumentProperties(itHeader.insnum, f); + SaveExtendedInstrumentProperties(0, GetType(), f); } SaveExtendedSongProperties(f); } @@ -2119,8 +2118,8 @@ uint32 CSoundFile::SaveMixPlugins(std::ostream *file, bool updatePlugData) } const uint32 extraDataSize = - 4 + sizeof(float32) + // 4 for ID and size of dryRatio - 4 + sizeof(int32); // Default Program + 4 + sizeof(IEEE754binary32LE) + // 4 for ID and size of dryRatio + 4 + sizeof(int32); // Default Program // For each extra entity, add 4 for ID, plus 4 for size of entity, plus size of entity chunkSize += extraDataSize + 4; // +4 is for size field itself @@ -2133,9 +2132,9 @@ uint32 CSoundFile::SaveMixPlugins(std::ostream *file, bool updatePlugData) std::ostream &f = *file; // Chunk ID (= plugin ID) char id[4] = { 'F', 'X', '0', '0' }; - if(i >= 100) id[1] = '0' + (i / 100u); - id[2] += (i / 10u) % 10u; - id[3] += (i % 10u); + if(i >= 100) id[1] = static_cast(static_cast('0') + (i / 100u)); + id[2] = static_cast(static_cast('0') + ((i / 10u) % 10u)); + id[3] = static_cast(static_cast('0') + (i % 10u)); mpt::IO::WriteRaw(f, id, 4); // Write chunk size, plugin info and plugin data chunk @@ -2198,7 +2197,7 @@ uint32 CSoundFile::SaveMixPlugins(std::ostream *file, bool updatePlugData) #endif // MODPLUG_NO_FILESAVE -std::pair CSoundFile::LoadMixPlugins(FileReader &file) +std::pair CSoundFile::LoadMixPlugins(FileReader &file, bool ignoreChannelCount) { bool hasPluginChunks = false, isBeRoTracker = false; while(file.CanRead(9)) @@ -2220,6 +2219,10 @@ std::pair CSoundFile::LoadMixPlugins(FileReader &file) // Channel FX if(!memcmp(code, "CHFX", 4)) { + if(!ignoreChannelCount) + { + ChnSettings.resize(std::clamp(static_cast(chunkSize / 4), GetNumChannels(), MAX_BASECHANNELS)); + } for(auto &chn : ChnSettings) { chn.nMixPlugin = static_cast(chunk.ReadUint32LE()); @@ -2232,8 +2235,8 @@ std::pair CSoundFile::LoadMixPlugins(FileReader &file) else if(code[0] == 'F' && (code[1] == 'X' || MPT_ISDIGIT(1)) && MPT_ISDIGIT(2) && MPT_ISDIGIT(3)) #undef MPT_ISDIGIT { - uint16 fxplug = (code[2] - '0') * 10 + (code[3] - '0'); //calculate plug-in number. - if(code[1] != 'X') fxplug += (code[1] - '0') * 100; + uint16 fxplug = static_cast((code[2] - '0') * 10 + (code[3] - '0')); //calculate plug-in number. + if(code[1] != 'X') fxplug += static_cast((code[1] - '0') * 100); if(fxplug < MAX_MIXPLUGINS) { PLUGINDEX plug = static_cast(fxplug); @@ -2308,45 +2311,44 @@ void CSoundFile::SaveExtendedSongProperties(std::ostream &f) const // Extra song data - Yet Another Hack. mpt::IO::WriteIntLE(f, MagicBE("MPTS")); -#define WRITEMODULARHEADER(code, fsize) \ - { \ - mpt::IO::WriteIntLE(f, code); \ - MPT_ASSERT(mpt::in_range(fsize)); \ - const uint16 _size = fsize; \ - mpt::IO::WriteIntLE(f, _size); \ - } -#define WRITEMODULAR(code, field) \ - { \ - WRITEMODULARHEADER(code, sizeof(field)) \ - mpt::IO::WriteIntLE(f, field); \ - } + const auto WriteModularHeader = [](std::ostream &f, uint32 code, size_t fsize) + { + mpt::IO::WriteIntLE(f, code); + MPT_ASSERT(mpt::in_range(fsize)); + mpt::IO::WriteIntLE(f, static_cast(fsize)); + }; + const auto WriteModular = [&WriteModularHeader](std::ostream &f, uint32 code, auto field) + { + WriteModularHeader(f, code, sizeof(field)); + mpt::IO::WriteIntLE(f, field); + }; - if(m_nDefaultTempo.GetInt() > 255) + if(Order().GetDefaultTempo().GetInt() > 255) { - uint32 tempo = m_nDefaultTempo.GetInt(); - WRITEMODULAR(MagicBE("DT.."), tempo); + uint32 tempo = Order().GetDefaultTempo().GetInt(); + WriteModular(f, MagicBE("DT.."), tempo); } - if(m_nDefaultTempo.GetFract() != 0 && specs.hasFractionalTempo) + if(Order().GetDefaultTempo().GetFract() != 0 && specs.hasFractionalTempo) { - uint32 tempo = m_nDefaultTempo.GetFract(); - WRITEMODULAR(MagicLE("DTFR"), tempo); + uint32 tempo = Order().GetDefaultTempo().GetFract(); + WriteModular(f, MagicLE("DTFR"), tempo); } if(m_nDefaultRowsPerBeat > 255 || m_nDefaultRowsPerMeasure > 255 || GetType() == MOD_TYPE_XM) { - WRITEMODULAR(MagicBE("RPB."), m_nDefaultRowsPerBeat); - WRITEMODULAR(MagicBE("RPM."), m_nDefaultRowsPerMeasure); + WriteModular(f, MagicBE("RPB."), m_nDefaultRowsPerBeat); + WriteModular(f, MagicBE("RPM."), m_nDefaultRowsPerMeasure); } if(GetType() != MOD_TYPE_XM) { - WRITEMODULAR(MagicBE("C..."), m_nChannels); + WriteModular(f, MagicBE("C..."), GetNumChannels()); } if((GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT)) && GetNumChannels() > 64) { // IT header has only room for 64 channels. Save the settings that do not fit to the header here as an extension. - WRITEMODULARHEADER(MagicBE("ChnS"), (GetNumChannels() - 64) * 2); + WriteModularHeader(f, MagicBE("ChnS"), (GetNumChannels() - 64) * 2); for(CHANNELINDEX chn = 64; chn < GetNumChannels(); chn++) { uint8 panvol[2]; @@ -2358,37 +2360,41 @@ void CSoundFile::SaveExtendedSongProperties(std::ostream &f) const } } + if(m_nTempoMode != TempoMode::Classic) { - WRITEMODULARHEADER(MagicBE("TM.."), 1); + WriteModularHeader(f, MagicBE("TM.."), 1); uint8 mode = static_cast(m_nTempoMode); mpt::IO::WriteIntLE(f, mode); } const int32 tmpMixLevels = static_cast(m_nMixLevels); - WRITEMODULAR(MagicBE("PMM."), tmpMixLevels); + WriteModular(f, MagicBE("PMM."), tmpMixLevels); if(m_dwCreatedWithVersion) { - WRITEMODULAR(MagicBE("CWV."), m_dwCreatedWithVersion.GetRawVersion()); + WriteModular(f, MagicBE("CWV."), m_dwCreatedWithVersion.GetRawVersion()); } - WRITEMODULAR(MagicBE("LSWV"), Version::Current().GetRawVersion()); - WRITEMODULAR(MagicBE("SPA."), m_nSamplePreAmp); - WRITEMODULAR(MagicBE("VSTV"), m_nVSTiVolume); + WriteModular(f, MagicBE("LSWV"), Version::Current().GetRawVersion()); + if(GetType() == MOD_TYPE_XM || m_nSamplePreAmp > 128) + { + WriteModular(f, MagicBE("SPA."), m_nSamplePreAmp); + } + WriteModular(f, MagicBE("VSTV"), m_nVSTiVolume); if(GetType() == MOD_TYPE_XM && m_nDefaultGlobalVolume != MAX_GLOBAL_VOLUME) { - WRITEMODULAR(MagicBE("DGV."), m_nDefaultGlobalVolume); + WriteModular(f, MagicBE("DGV."), m_nDefaultGlobalVolume); } if(GetType() != MOD_TYPE_XM && Order().GetRestartPos() != 0) { - WRITEMODULAR(MagicBE("RP.."), Order().GetRestartPos()); + WriteModular(f, MagicBE("RP.."), Order().GetRestartPos()); } if(m_nResampling != SRCMODE_DEFAULT && specs.hasDefaultResampling) { - WRITEMODULAR(MagicLE("RSMP"), static_cast(m_nResampling)); + WriteModular(f, MagicLE("RSMP"), static_cast(m_nResampling)); } // Sample cues @@ -2402,7 +2408,7 @@ void CSoundFile::SaveExtendedSongProperties(std::ostream &f) const // Write one chunk for every sample. // Rationale: chunks are limited to 65536 bytes, which can easily be reached // with the amount of samples that OpenMPT supports. - WRITEMODULARHEADER(MagicLE("CUES"), static_cast(2 + std::size(sample.cues) * 4)); + WriteModularHeader(f, MagicLE("CUES"), 2 + std::size(sample.cues) * 4); mpt::IO::WriteIntLE(f, smp); for(auto cue : sample.cues) { @@ -2419,33 +2425,34 @@ void CSoundFile::SaveExtendedSongProperties(std::ostream &f) const TempoSwing::Serialize(oStrm, m_tempoSwing); std::string data = oStrm.str(); uint16 length = mpt::saturate_cast(data.size()); - WRITEMODULARHEADER(MagicLE("SWNG"), length); + WriteModularHeader(f, MagicLE("SWNG"), length); mpt::IO::WriteRaw(f, data.data(), length); } // Playback compatibility flags { - uint8 bits[(kMaxPlayBehaviours + 7) / 8u]; - MemsetZero(bits); + const auto supportedBehaviours = GetSupportedPlaybackBehaviour(GetBestSaveFormat()); + std::array bits; + bits.fill(0); size_t maxBit = 0; for(size_t i = 0; i < kMaxPlayBehaviours; i++) { - if(m_playBehaviour[i]) + if(m_playBehaviour[i] && supportedBehaviours[i]) { bits[i >> 3] |= 1 << (i & 0x07); maxBit = i + 8; } } uint16 numBytes = static_cast(maxBit / 8u); - WRITEMODULARHEADER(MagicBE("MSF."), numBytes); - mpt::IO::WriteRaw(f, bits, numBytes); + WriteModularHeader(f, MagicBE("MSF."), numBytes); + mpt::IO::WriteRaw(f, bits.data(), numBytes); } if(!m_songArtist.empty() && specs.hasArtistName) { std::string songArtistU8 = mpt::ToCharset(mpt::Charset::UTF8, m_songArtist); uint16 length = mpt::saturate_cast(songArtistU8.length()); - WRITEMODULARHEADER(MagicLE("AUTH"), length); + WriteModularHeader(f, MagicLE("AUTH"), length); mpt::IO::WriteRaw(f, songArtistU8.c_str(), length); } @@ -2459,7 +2466,7 @@ void CSoundFile::SaveExtendedSongProperties(std::ostream &f) const AddToLog(LogWarning, U_("Too many MIDI Mapping directives to save; data won't be written.")); } else { - WRITEMODULARHEADER(MagicBE("MIMA"), static_cast(objectsize)); + WriteModularHeader(f, MagicBE("MIMA"), objectsize); GetMIDIMapper().Serialize(&f); } } @@ -2467,7 +2474,7 @@ void CSoundFile::SaveExtendedSongProperties(std::ostream &f) const // Channel colors { CHANNELINDEX numChannels = 0; - for(CHANNELINDEX i = 0; i < m_nChannels; i++) + for(CHANNELINDEX i = 0; i < GetNumChannels(); i++) { if(ChnSettings[i].color != ModChannelSettings::INVALID_COLOR) { @@ -2476,7 +2483,7 @@ void CSoundFile::SaveExtendedSongProperties(std::ostream &f) const } if(numChannels > 0) { - WRITEMODULARHEADER(MagicLE("CCOL"), numChannels * 4); + WriteModularHeader(f, MagicLE("CCOL"), numChannels * 4); for(CHANNELINDEX i = 0; i < numChannels; i++) { uint32 color = ChnSettings[i].color; @@ -2488,14 +2495,36 @@ void CSoundFile::SaveExtendedSongProperties(std::ostream &f) const } } #endif - -#undef WRITEMODULAR -#undef WRITEMODULARHEADER } #endif // MODPLUG_NO_FILESAVE +/* +The following song properties can be read and written: +AUTH [all] Song artist +C... [IT / MPTM] Number of channels (for IT / MPTM where there is no explicit channel count and we want to keep the properties of channels beyond the last channel that contains any pattern data) +CCOL [all] Channel colors +ChnS [IT / MPTM] Channel settings for channels 65-127 if needed (doesn't fit to IT header). +CUES [MPTM] Sample cue points +CWV. [all] Created With Version +DGV. [XM] Default Global Volume +DT.. [all] Default Tempo, if it doesn't fit in the header value +DTFR [MPTM] Fractional part of default tempo +LSWV [all] Last Saved With Version +MIMA [all] MIdi MApping directives +MSF. [all] Mod(Specific)Flags +PMM. [all] Mix Mode +RP.. [IT / MPTM] Legacy Restart Position +RPB. [all] Rows Per Beat (if not supported / value doesn't fit in header) +RPM. [all] Per Measure (if not supported / value doesn't fit in header) +RSMP [MPTM] Default resampling +SPA. [all] Sample Pre-Amp (if not supported / value doesn't fit in header) +SWNG [MPTM] Tempo swing factors +TM.. [all] Tempo Mode +VSTV [all] Synth volume +*/ + template void ReadField(FileReader &chunk, std::size_t size, T &field) { @@ -2544,12 +2573,12 @@ bool CSoundFile::LoadExtendedSongProperties(FileReader &file, bool ignoreChannel switch (code) // interpret field code { - case MagicBE("DT.."): { uint32 tempo; ReadField(chunk, size, tempo); m_nDefaultTempo.Set(tempo, m_nDefaultTempo.GetFract()); break; } - case MagicLE("DTFR"): { uint32 tempoFract; ReadField(chunk, size, tempoFract); m_nDefaultTempo.Set(m_nDefaultTempo.GetInt(), tempoFract); break; } + case MagicBE("DT.."): { uint32 tempo; ReadField(chunk, size, tempo); Order().SetDefaultTempo(TEMPO(tempo, Order().GetDefaultTempo().GetFract())); break; } + case MagicLE("DTFR"): { uint32 tempoFract; ReadField(chunk, size, tempoFract); Order().SetDefaultTempo(TEMPO(Order().GetDefaultTempo().GetInt(), tempoFract)); break; } case MagicBE("RPB."): ReadField(chunk, size, m_nDefaultRowsPerBeat); break; case MagicBE("RPM."): ReadField(chunk, size, m_nDefaultRowsPerMeasure); break; // FIXME: If there are only PC events on the last few channels in an MPTM MO3, they won't be imported! - case MagicBE("C..."): if(!ignoreChannelCount) { CHANNELINDEX chn = 0; ReadField(chunk, size, chn); m_nChannels = Clamp(chn, m_nChannels, MAX_BASECHANNELS); } break; + case MagicBE("C..."): if(!ignoreChannelCount) { CHANNELINDEX chn = 0; ReadField(chunk, size, chn); ChnSettings.resize(Clamp(chn, GetNumChannels(), MAX_BASECHANNELS)); } break; case MagicBE("TM.."): ReadFieldCast(chunk, size, m_nTempoMode); break; case MagicBE("PMM."): ReadFieldCast(chunk, size, m_nMixLevels); break; case MagicBE("CWV."): { uint32 ver = 0; ReadField(chunk, size, ver); m_dwCreatedWithVersion = Version(ver); break; } @@ -2568,7 +2597,10 @@ bool CSoundFile::LoadExtendedSongProperties(FileReader &file, bool ignoreChannel case MagicLE("CCOL"): // Channel colors { - const CHANNELINDEX numChannels = std::min(MAX_BASECHANNELS, static_cast(size / 4u)); + const CHANNELINDEX channelsInFile = static_cast(size / 4u); + if(!ignoreChannelCount) + ChnSettings.resize(std::clamp(GetNumChannels(), channelsInFile, MAX_BASECHANNELS)); + const CHANNELINDEX numChannels = std::min(channelsInFile, GetNumChannels()); for(CHANNELINDEX i = 0; i < numChannels; i++) { auto rgb = chunk.ReadArray(); @@ -2589,12 +2621,14 @@ bool CSoundFile::LoadExtendedSongProperties(FileReader &file, bool ignoreChannel break; case MagicBE("ChnS"): // Channel settings for channels 65+ - if(size <= (MAX_BASECHANNELS - 64) * 2 && (size % 2u) == 0) + static_assert(MAX_BASECHANNELS >= 64); + if(size <= (MAX_BASECHANNELS - 64) * 2 && (size % 2u) == 0 && (GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT))) { - static_assert(mpt::array_size::size >= 64); - const CHANNELINDEX loopLimit = std::min(uint16(64 + size / 2), uint16(std::size(ChnSettings))); - - for(CHANNELINDEX chn = 64; chn < loopLimit; chn++) + const CHANNELINDEX channelsInFile = mpt::saturate_cast(64 + size / 2); + if(!ignoreChannelCount) + ChnSettings.resize(std::clamp(GetNumChannels(), channelsInFile, MAX_BASECHANNELS)); + const CHANNELINDEX numChannels = std::min(channelsInFile, GetNumChannels()); + for(CHANNELINDEX chn = 64; chn < numChannels; chn++) { auto [pan, vol] = chunk.ReadArray(); if(pan != 0xFF) @@ -2661,7 +2695,7 @@ bool CSoundFile::LoadExtendedSongProperties(FileReader &file, bool ignoreChannel } // Validate read values. - Limit(m_nDefaultTempo, GetModSpecifications().GetTempoMin(), GetModSpecifications().GetTempoMax()); + Order().SetDefaultTempo(Clamp(Order().GetDefaultTempo(), GetModSpecifications().GetTempoMin(), GetModSpecifications().GetTempoMax())); if(m_nTempoMode >= TempoMode::NumModes) m_nTempoMode = TempoMode::Classic; if(m_nMixLevels >= MixLevels::NumMixLevels) diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_itp.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_itp.cpp index ddea9c299..ce9ecf351 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_itp.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_itp.cpp @@ -86,43 +86,63 @@ struct ITPHeader { uint32le magic; uint32le version; + + bool IsValid() const + { + return magic == MagicBE(".itp") + && version >= 0x00000100 && version <= 0x00000103; + } + + uint32 GetHeaderMinimumAdditionalSize() const + { + return 76 + (version <= 0x102 ? 4 : 0); + } }; MPT_BINARY_STRUCT(ITPHeader, 8) -static bool ValidateHeader(const ITPHeader &hdr) +struct ITPSongHeader { - if(hdr.magic != MagicBE(".itp")) + enum SongFlags { - return false; - } - if(hdr.version < 0x00000100 || hdr.version > 0x00000103) + ITP_EMBEDMIDICFG = 0x00001, // Embed macros in file + ITP_ITOLDEFFECTS = 0x00004, // Old Impulse Tracker effect implementations + ITP_ITCOMPATGXX = 0x00008, // IT "Compatible Gxx" (IT's flag to behave more like other trackers w/r/t portamento effects) + ITP_LINEARSLIDES = 0x00010, // Linear slides vs. Amiga slides + ITP_EXFILTERRANGE = 0x08000, // Cutoff Filter has double frequency range (up to ~10Khz) + ITP_ITPROJECT = 0x20000, // Is a project file + ITP_ITPEMBEDIH = 0x40000, // Embed instrument headers in project file + }; + + uint32le flags; // See SongFlangs + uint32le globalVolume; + uint32le samplePreAmp; + uint32le speed; + uint32le tempo; + uint32le numChannels; + uint32le channelNameLength; + + bool IsValid() const { - return false; + return (flags & ITP_ITPROJECT) != 0 + && speed >= 1 + && tempo >= 32 + && numChannels >= 1 && numChannels <= MAX_BASECHANNELS; } - return true; -} +}; - -static uint64 GetHeaderMinimumAdditionalSize(const ITPHeader &hdr) -{ - return 76 + (hdr.version <= 0x102 ? 4 : 0); -} +MPT_BINARY_STRUCT(ITPSongHeader, 28) CSoundFile::ProbeResult CSoundFile::ProbeFileHeaderITP(MemoryFileReader file, const uint64 *pfilesize) { ITPHeader hdr; if(!file.ReadStruct(hdr)) - { return ProbeWantMoreData; - } - if(!ValidateHeader(hdr)) - { + if(!hdr.IsValid()) return ProbeFailure; - } - return ProbeAdditionalSize(file, pfilesize, GetHeaderMinimumAdditionalSize(hdr)); + return ProbeAdditionalSize(file, pfilesize, hdr.GetHeaderMinimumAdditionalSize()); } @@ -135,85 +155,60 @@ bool CSoundFile::ReadITP(FileReader &file, ModLoadingFlags loadFlags) return false; #else // !MPT_EXTERNAL_SAMPLES && !MPT_FUZZ_TRACKER - enum ITPSongFlags - { - ITP_EMBEDMIDICFG = 0x00001, // Embed macros in file - ITP_ITOLDEFFECTS = 0x00004, // Old Impulse Tracker effect implementations - ITP_ITCOMPATGXX = 0x00008, // IT "Compatible Gxx" (IT's flag to behave more like other trackers w/r/t portamento effects) - ITP_LINEARSLIDES = 0x00010, // Linear slides vs. Amiga slides - ITP_EXFILTERRANGE = 0x08000, // Cutoff Filter has double frequency range (up to ~10Khz) - ITP_ITPROJECT = 0x20000, // Is a project file - ITP_ITPEMBEDIH = 0x40000, // Embed instrument headers in project file - }; - file.Rewind(); ITPHeader hdr; if(!file.ReadStruct(hdr)) - { return false; - } - if(!ValidateHeader(hdr)) - { + if(!hdr.IsValid()) return false; - } - if(!file.CanRead(mpt::saturate_cast(GetHeaderMinimumAdditionalSize(hdr)))) - { + if(!file.CanRead(hdr.GetHeaderMinimumAdditionalSize())) return false; - } if(loadFlags == onlyVerifyHeader) - { return true; - } const uint32 version = hdr.version; - InitializeGlobals(MOD_TYPE_IT); - m_playBehaviour.reset(); - file.ReadSizedString(m_songName); + std::string songName, songMessage; + file.ReadSizedString(songName); + file.ReadSizedString(songMessage); - // Song comments - m_songMessage.Read(file, file.ReadUint32LE(), SongMessage::leCR); - - // Song global config - const uint32 songFlags = file.ReadUint32LE(); - if(!(songFlags & ITP_ITPROJECT)) - { + ITPSongHeader songHeader; + if(!file.ReadStruct(songHeader) || !songHeader.IsValid()) return false; - } + + InitializeGlobals(MOD_TYPE_IT, static_cast(songHeader.numChannels)); + m_playBehaviour.reset(); + m_SongFlags.set(SONG_IMPORTED); - if(songFlags & ITP_ITOLDEFFECTS) + if(songHeader.flags & ITPSongHeader::ITP_ITOLDEFFECTS) m_SongFlags.set(SONG_ITOLDEFFECTS); - if(songFlags & ITP_ITCOMPATGXX) + if(songHeader.flags & ITPSongHeader::ITP_ITCOMPATGXX) m_SongFlags.set(SONG_ITCOMPATGXX); - if(songFlags & ITP_LINEARSLIDES) + if(songHeader.flags & ITPSongHeader::ITP_LINEARSLIDES) m_SongFlags.set(SONG_LINEARSLIDES); - if(songFlags & ITP_EXFILTERRANGE) + if(songHeader.flags & ITPSongHeader::ITP_EXFILTERRANGE) m_SongFlags.set(SONG_EXFILTERRANGE); - m_nDefaultGlobalVolume = file.ReadUint32LE(); - m_nSamplePreAmp = file.ReadUint32LE(); - m_nDefaultSpeed = std::max(uint32(1), file.ReadUint32LE()); - m_nDefaultTempo.Set(std::max(uint32(32), file.ReadUint32LE())); - m_nChannels = static_cast(file.ReadUint32LE()); - if(m_nChannels == 0 || m_nChannels > MAX_BASECHANNELS) - { - return false; - } + m_nDefaultGlobalVolume = songHeader.globalVolume; + m_nSamplePreAmp = songHeader.samplePreAmp; + Order().SetDefaultSpeed(songHeader.speed); + Order().SetDefaultTempoInt(songHeader.tempo); - // channel name string length (=MAX_CHANNELNAME) - uint32 size = file.ReadUint32LE(); + m_songName = std::move(songName); + m_songMessage.SetRaw(std::move(songMessage)); // Channels' data - for(CHANNELINDEX chn = 0; chn < m_nChannels; chn++) + uint32 size = songHeader.channelNameLength; + for(auto &chn : ChnSettings) { - ChnSettings[chn].nPan = std::min(static_cast(file.ReadUint32LE()), uint16(256)); - ChnSettings[chn].dwFlags.reset(); + chn.nPan = std::min(static_cast(file.ReadUint32LE()), uint16(256)); + chn.dwFlags.reset(); uint32 flags = file.ReadUint32LE(); - if(flags & 0x100) ChnSettings[chn].dwFlags.set(CHN_MUTE); - if(flags & 0x800) ChnSettings[chn].dwFlags.set(CHN_SURROUND); - ChnSettings[chn].nVolume = std::min(static_cast(file.ReadUint32LE()), uint16(64)); - file.ReadString(ChnSettings[chn].szName, size); + if(flags & 0x100) chn.dwFlags.set(CHN_MUTE); + if(flags & 0x800) chn.dwFlags.set(CHN_SURROUND); + chn.nVolume = std::min(static_cast(file.ReadUint32LE()), uint8(64)); + file.ReadString(chn.szName, size); } // Song mix plugins @@ -227,12 +222,10 @@ bool CSoundFile::ReadITP(FileReader &file, ModLoadingFlags loadFlags) m_MidiCfg.Sanitize(); // Song Instruments - m_nInstruments = static_cast(file.ReadUint32LE()); - if(m_nInstruments >= MAX_INSTRUMENTS) - { - m_nInstruments = 0; + if(uint32 numIns = file.ReadUint32LE(); numIns < MAX_INSTRUMENTS) + m_nInstruments = static_cast(numIns); + else return false; - } // Instruments' paths if(version <= 0x102) @@ -376,7 +369,7 @@ bool CSoundFile::ReadITP(FileReader &file, ModLoadingFlags loadFlags) uint32 code = file.ReadUint32LE(); // Embed instruments' header [v1.01] - if(version >= 0x101 && (songFlags & ITP_ITPEMBEDIH) && code == MagicBE("EBIH")) + if(version >= 0x101 && (songHeader.flags & ITPSongHeader::ITP_ITPEMBEDIH) && code == MagicBE("EBIH")) { code = file.ReadUint32LE(); @@ -392,7 +385,7 @@ bool CSoundFile::ReadITP(FileReader &file, ModLoadingFlags loadFlags) ins++; } else { - ReadExtendedInstrumentProperty(Instruments[ins], code, file); + ReadExtendedInstrumentProperty(mpt::as_span(&Instruments[ins], 1), code, file); } code = file.ReadUint32LE(); @@ -415,14 +408,14 @@ bool CSoundFile::ReadITP(FileReader &file, ModLoadingFlags loadFlags) m_nMinPeriod = 8; // Before OpenMPT 1.20.01.09, the MIDI macros were always read from the file, even if the "embed" flag was not set. - if(m_dwLastSavedWithVersion >= MPT_V("1.20.01.09") && !(songFlags & ITP_EMBEDMIDICFG)) + if(m_dwLastSavedWithVersion >= MPT_V("1.20.01.09") && !(songHeader.flags & ITPSongHeader::ITP_EMBEDMIDICFG)) { m_MidiCfg.Reset(); } - m_modFormat.formatName = U_("Impulse Tracker Project"); - m_modFormat.type = U_("itp"); - m_modFormat.madeWithTracker = U_("OpenMPT ") + mpt::ufmt::val(m_dwLastSavedWithVersion); + m_modFormat.formatName = UL_("Impulse Tracker Project"); + m_modFormat.type = UL_("itp"); + m_modFormat.madeWithTracker = UL_("OpenMPT ") + mpt::ufmt::val(m_dwLastSavedWithVersion); m_modFormat.charset = mpt::Charset::Windows1252; return true; diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_kris.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_kris.cpp new file mode 100644 index 000000000..850d716d0 --- /dev/null +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_kris.cpp @@ -0,0 +1,165 @@ +/* + * Load_kris.cpp + * ------------- + * Purpose: ChipTracker loader + * Notes : Another NoiseTracker variant, storing tracks instead of patterns (like ICE / ST26) + * Authors: OpenMPT Devs + * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. + */ + + +#include "stdafx.h" +#include "Loaders.h" +#include "MODTools.h" + +OPENMPT_NAMESPACE_BEGIN + +CSoundFile::ProbeResult CSoundFile::ProbeFileHeaderKRIS(MemoryFileReader file, const uint64 *pfilesize) +{ + if(!file.CanRead(952 + 4)) + return ProbeWantMoreData; + + file.Seek(952); + if(!file.ReadMagic("KRIS")) + return ProbeFailure; + + const auto [numOrders, restartPos] = file.ReadArray(); + if(numOrders > 128 || restartPos > 127) + return ProbeFailure; + + file.Seek(22); + uint32 invalidBytes = 0; + for(SAMPLEINDEX smp = 1; smp <= 31; smp++) + { + MODSampleHeader sampleHeader; + file.ReadStruct(sampleHeader); + if(sampleHeader.name[0] != 0) + invalidBytes += sampleHeader.GetInvalidByteScore(); + if(invalidBytes > MODSampleHeader::INVALID_BYTE_THRESHOLD) + return ProbeFailure; + } + + MPT_UNREFERENCED_PARAMETER(pfilesize); + return ProbeSuccess; +} + + +bool CSoundFile::ReadKRIS(FileReader &file, ModLoadingFlags loadFlags) +{ + if(!file.Seek(952) || !file.ReadMagic("KRIS")) + return false; + + const auto [numOrders, restartPos] = file.ReadArray(); + if(numOrders > 128 || restartPos > 127) + return false; + + std::array, 128 * 4> tracks; + if(!file.ReadArray(tracks)) + return false; + uint32 tracksOffset = 1984; + + InitializeGlobals(MOD_TYPE_MOD, 4); + + file.Seek(0); + file.ReadString(m_songName, 22); + + m_nSamples = 31; + uint32 invalidBytes = 0; + uint8 numSynthWaveforms = 0; + for(SAMPLEINDEX smp = 1; smp <= m_nSamples; smp++) + { + MODSampleHeader sampleHeader; + file.ReadStruct(sampleHeader); + if(sampleHeader.name[0] != 0) + { + invalidBytes += ReadMODSample(sampleHeader, Samples[smp], m_szNames[smp], true); + } else + { + // Unfinished feature. Synth parameters are stored in the module, but loading and saving of synth waveforms + // (which I'd assume the extra space before the track data is reserved for) is completely absent, making the feature useless. + Samples[smp].Initialize(MOD_TYPE_MOD); + m_szNames[smp] = "Synthetic"; + const uint8 maxWaveform = std::max({ sampleHeader.name[1], sampleHeader.name[5], sampleHeader.name[10], sampleHeader.name[19] }); + if(maxWaveform) + numSynthWaveforms = std::max(numSynthWaveforms, static_cast(maxWaveform + 1)); + } + if(invalidBytes > MODSampleHeader::INVALID_BYTE_THRESHOLD) + return false; + } + tracksOffset += numSynthWaveforms * 64u; + + if(loadFlags == onlyVerifyHeader) + return true; + + SetupMODPanning(true); + Order().SetDefaultSpeed(6); + Order().SetDefaultTempoInt(125); + Order().SetRestartPos(restartPos); + m_nMinPeriod = 113 * 4; + m_nMaxPeriod = 856 * 4; + m_nSamplePreAmp = 64; + m_SongFlags.set(SONG_PT_MODE | SONG_IMPORTED | SONG_FORMAT_NO_VOLCOL); + m_playBehaviour.set(kMODIgnorePanning); + m_playBehaviour.set(kMODSampleSwap); + + Order().resize(numOrders); + if(loadFlags & loadPatternData) + Patterns.ResizeArray(numOrders); + for(PATTERNINDEX pat = 0; pat < numOrders; pat++) + { + if(!(loadFlags & loadPatternData) || !Patterns.Insert(pat, 64)) + break; + Order()[pat] = pat; + for(CHANNELINDEX chn = 0; chn < 4; chn++) + { + const uint8 track = tracks[pat * 4u + chn][0]; + const int8 transpose = tracks[pat * 4u + chn][1]; + if(!file.Seek(tracksOffset + track * 256u)) + return false; + ModCommand *m = Patterns[pat].GetpModCommand(0, chn); + for(ROWINDEX row = 0; row < 64; row++, m += 4) + { + const auto data = file.ReadArray(); + if(data[0] & 1) + return false; + if(data[0] >= 0x18 && data[0] <= 0x9E) + m->note = static_cast(Clamp(NOTE_MIDDLEC - 36 + (data[0] - 0x18) / 2 + transpose, NOTE_MIDDLEC - 12, NOTE_MIDDLEC + 23)); + else if(data[0] != 0xA8) + return false; + if(data[2] >> 4) + return false; + m->instr = data[1]; + ConvertModCommand(*m, data[2] & 0x0F, data[3]); + } + } + } + + m_modFormat.formatName = UL_("ChipTracker"); + m_modFormat.type = UL_("mod"); + m_modFormat.madeWithTracker = UL_("ChipTracker"); + m_modFormat.charset = mpt::Charset::Amiga_no_C1; + + if(loadFlags & loadSampleData) + { + uint8 maxTrack = 0; + for(uint32 ord = 0; ord < numOrders * 4u; ord++) + { + maxTrack = std::max(tracks[ord][0], maxTrack); + } + file.Seek(tracksOffset + (maxTrack + 1u) * 256u); + for(SAMPLEINDEX smp = 1; smp <= m_nSamples; smp++) + if(Samples[smp].nLength) + { + SampleIO( + SampleIO::_8bit, + SampleIO::mono, + SampleIO::littleEndian, + SampleIO::signedPCM) + .ReadSample(Samples[smp], file); + } + } + + return true; +} + +OPENMPT_NAMESPACE_END diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_mdl.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_mdl.cpp index b4d19c678..d54e277eb 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_mdl.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_mdl.cpp @@ -65,10 +65,21 @@ struct MDLInfoBlock char composer[20]; uint16le numOrders; uint16le restartPos; - uint8le globalVol; // 1...255 - uint8le speed; // 1...255 - uint8le tempo; // 4...255 + uint8le globalVol; // 1...255 + uint8le speed; // 1...255 + uint8le tempo; // 4...255 uint8le chnSetup[32]; + + uint8 GetNumChannels() const + { + uint8 numChannels = 0; + for(uint8 c = 0; c < 32; c++) + { + if(!(chnSetup[c] & 0x80)) + numChannels = c + 1; + } + return numChannels; + } }; MPT_BINARY_STRUCT(MDLInfoBlock, 91) @@ -214,11 +225,11 @@ static std::pair ConvertMDLCommand(const uint8 command, ui cmd = CMD_NONE; break; case 0x0C: // Global volume - param = (param + 1) / 2u; + param = static_cast((param + 1) / 2u); break; case 0x0D: // Pattern Break // Convert from BCD - param = 10 * (param >> 4) + (param & 0x0F); + param = static_cast(10 * (param >> 4) + (param & 0x0F)); break; case 0x0E: // Special switch(param >> 4) @@ -255,11 +266,11 @@ static std::pair ConvertMDLCommand(const uint8 command, ui break; case 0xA: // Global vol slide up cmd = CMD_GLOBALVOLSLIDE; - param = 0xF0 & (((param & 0x0F) + 1) << 3); + param = static_cast(0xF0 & (((param & 0x0F) + 1) << 3)); break; case 0xB: // Global vol slide down cmd = CMD_GLOBALVOLSLIDE; - param = ((param & 0x0F) + 1) >> 1; + param = static_cast(((param & 0x0F) + 1) >> 1); break; case 0xC: // Note cut case 0xD: // Note delay @@ -361,8 +372,7 @@ static bool ImportMDLCommands(ModCommand &m, uint8 vol, uint8 cmd1, uint8 cmd2, if(vol) { - m.volcmd = VOLCMD_VOLUME; - m.vol = (vol + 2) / 4u; + m.SetVolumeCommand(VOLCMD_VOLUME, static_cast((vol + 2) / 4u)); } // If we have Dxx + G00, or Dxx + H00, combine them into Lxx/Kxx. @@ -419,6 +429,28 @@ static void CopyEnvelope(InstrumentEnvelope &mptEnv, uint8 flags, std::vector= 0x10) + { + MDLPatternHeader patHead; + chunk.ReadStruct(patHead); + readChans = patHead.channels; + } + for(uint8 chn = 0; chn < readChans; chn++) + { + if(chunk.ReadUint16LE() > 0 && chn >= numChannels && chn < 32) + numChannels = chn + 1; + } + } + return numChannels; +} + + static bool ValidateHeader(const MDLFileHeader &fileHeader) { if(std::memcmp(fileHeader.id, "DMDL", 4) @@ -474,7 +506,9 @@ bool CSoundFile::ReadMDL(FileReader &file, ModLoadingFlags loadFlags) return false; } - InitializeGlobals(MOD_TYPE_MDL); + // In case any muted channels contain data, be sure that we import them as well. + const uint8 numChannels = std::max(GetMDLPatternChannelCount(chunks.GetChunk(MDLChunk::idPats), info.GetNumChannels(), fileHeader.version), uint8(1)); + InitializeGlobals(MOD_TYPE_MDL, numChannels); m_SongFlags = SONG_ITCOMPATGXX; m_playBehaviour.set(kPerChannelGlobalVolSlide); m_playBehaviour.set(kApplyOffsetWithoutNote); @@ -482,36 +516,32 @@ bool CSoundFile::ReadMDL(FileReader &file, ModLoadingFlags loadFlags) m_playBehaviour.reset(kITVibratoTremoloPanbrello); m_playBehaviour.reset(kITSCxStopsSample); // Gate effect in underbeat.mdl - m_modFormat.formatName = U_("Digitrakker"); - m_modFormat.type = U_("mdl"); + m_modFormat.formatName = UL_("Digitrakker"); + m_modFormat.type = UL_("mdl"); m_modFormat.madeWithTracker = U_("Digitrakker ") + ( - (fileHeader.version == 0x11) ? U_("3") // really could be 2.99b - close enough - : (fileHeader.version == 0x10) ? U_("2.3") - : (fileHeader.version == 0x00) ? U_("2.0 - 2.2b") // there was no 1.x release - : U_("")); + (fileHeader.version == 0x11) ? UL_("3") // really could be 2.99b - close enough + : (fileHeader.version == 0x10) ? UL_("2.3") + : (fileHeader.version == 0x00) ? UL_("2.0 - 2.2b") // there was no 1.x release + : UL_("")); m_modFormat.charset = mpt::Charset::CP437; m_songName = mpt::String::ReadBuf(mpt::String::spacePadded, info.title); m_songArtist = mpt::ToUnicode(mpt::Charset::CP437, mpt::String::ReadBuf(mpt::String::spacePadded, info.composer)); m_nDefaultGlobalVolume = info.globalVol + 1; - m_nDefaultSpeed = Clamp(info.speed, 1, 255); - m_nDefaultTempo.Set(Clamp(info.tempo, 4, 255)); + Order().SetDefaultSpeed(Clamp(info.speed, 1, 255)); + Order().SetDefaultTempoInt(Clamp(info.tempo, 4, 255)); ReadOrderFromFile(Order(), chunk, info.numOrders); Order().SetRestartPos(info.restartPos); - m_nChannels = 0; - for(CHANNELINDEX c = 0; c < 32; c++) + for(CHANNELINDEX c = 0; c < GetNumChannels(); c++) { - ChnSettings[c].Reset(); ChnSettings[c].nPan = (info.chnSetup[c] & 0x7F) * 2u; if(ChnSettings[c].nPan == 254) ChnSettings[c].nPan = 256; if(info.chnSetup[c] & 0x80) ChnSettings[c].dwFlags.set(CHN_MUTE); - else - m_nChannels = c + 1; chunk.ReadString(ChnSettings[c].szName, 8); } @@ -652,7 +682,7 @@ bool CSoundFile::ReadMDL(FileReader &file, ModLoadingFlags loadFlags) mptSmp.nPan = std::min(static_cast(sampleHeader.panning * 2), uint16(254)); mptSmp.nVibType = MDLVibratoType[sampleHeader.vibType & 3]; mptSmp.nVibSweep = sampleHeader.vibSweep; - mptSmp.nVibDepth = (sampleHeader.vibDepth + 3u) / 4u; + mptSmp.nVibDepth = static_cast((sampleHeader.vibDepth + 3u) / 4u); mptSmp.nVibRate = sampleHeader.vibSpeed; // Convert to IT-like vibrato sweep if(mptSmp.nVibSweep != 0) @@ -681,27 +711,6 @@ bool CSoundFile::ReadMDL(FileReader &file, ModLoadingFlags loadFlags) if((loadFlags & loadPatternData) && (chunk = chunks.GetChunk(MDLChunk::idPats)).IsValid()) { PATTERNINDEX numPats = chunk.ReadUint8(); - - // In case any muted channels contain data, be sure that we import them as well. - for(PATTERNINDEX pat = 0; pat < numPats; pat++) - { - CHANNELINDEX numChans = 32; - if(fileHeader.version >= 0x10) - { - MDLPatternHeader patHead; - chunk.ReadStruct(patHead); - if(patHead.channels > m_nChannels && patHead.channels <= 32) - m_nChannels = patHead.channels; - numChans = patHead.channels; - } - for(CHANNELINDEX chn = 0; chn < numChans; chn++) - { - if(chunk.ReadUint16LE() > 0 && chn >= m_nChannels && chn < 32) - m_nChannels = chn + 1; - } - } - chunk.Seek(1); - Patterns.ResizeArray(numPats); for(PATTERNINDEX pat = 0; pat < numPats; pat++) { @@ -727,7 +736,7 @@ bool CSoundFile::ReadMDL(FileReader &file, ModLoadingFlags loadFlags) for(CHANNELINDEX chn = 0; chn < numChans; chn++) { uint16 trkNum = chunk.ReadUint16LE(); - if(!trkNum || trkNum >= tracks.size() || chn >= m_nChannels) + if(!trkNum || trkNum >= tracks.size() || chn >= GetNumChannels()) continue; FileReader &track = tracks[trkNum]; @@ -752,7 +761,7 @@ bool CSoundFile::ReadMDL(FileReader &file, ModLoadingFlags loadFlags) do { *m = orig; - m += m_nChannels; + m += GetNumChannels(); row++; } while (row < numRows && x--); } diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_med.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_med.cpp index 42361afe8..cd159e47a 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_med.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_med.cpp @@ -2,7 +2,7 @@ * Load_med.cpp * ------------ * Purpose: OctaMED / MED Soundstudio module loader - * Notes : Support for synthesized instruments is still missing. + * Notes : (currently none) * Authors: OpenMPT Devs * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. */ @@ -253,6 +253,29 @@ struct MMDPackedSampleHeader MPT_BINARY_STRUCT(MMDPackedSampleHeader, 14) +struct MMDSynthInstr +{ + uint8 defaultDecay; // Not used in modules + char reserved[3]; + uint16be loopStart; // Only for hybrid + uint16be loopLength; + uint16be volTableLen; + uint16be waveTableLen; + uint8 volSpeed; + uint8 waveSpeed; + uint16be numWaveforms; + std::array volTable; + std::array waveTable; + + bool IsValid() const + { + return volTableLen <= 128 && waveTableLen <= 128 && (numWaveforms <= 64 || numWaveforms == 0xFFFF); + } +}; + +MPT_BINARY_STRUCT(MMDSynthInstr, 272) + + struct MMDInstrExt { enum @@ -372,6 +395,10 @@ static TEMPO MMDTempoToBPM(uint32 tempo, bool is8Ch, bool softwareMixing, bool b { if(bpmMode && !is8Ch) { + // Observed in OctaMED 5 and MED SoundStudio 1.03 (bug?) + if(tempo < 7) + return TEMPO(111.5); + // You would have thought that we could use modern tempo mode here. // Alas, the number of ticks per row still influences the tempo. :( return TEMPO((tempo * rowsPerBeat) / 4.0); @@ -456,8 +483,8 @@ static std::pair ConvertMEDEffect(ModCommand & if(param) m.SetEffectCommand(CMD_VOLUMESLIDE, param); break; - case 0x0E: // Synth jump - m.command = CMD_NONE; + case 0x0E: // Synth jump / MIDI panning + m.SetEffectCommand(CMD_MED_SYNTH_JUMP, param); break; case 0x0F: // Misc if(param == 0) @@ -497,13 +524,16 @@ static std::pair ConvertMEDEffect(ModCommand & case 0xF9: // Turn filter on m.SetEffectCommand(CMD_MODCMDEX, 0xF9 - param); break; + case 0xFD: // Set pitch + m.SetEffectCommand(CMD_TONEPORTA_DURATION, 0); + break; case 0xFA: // MIDI pedal on case 0xFB: // MIDI pedal off - case 0xFD: // Set pitch case 0xFE: // End of song break; case 0xFF: // Turn note off - m.note = NOTE_NOTECUT; + if(!m.IsNote()) + m.note = NOTE_NOTECUT; break; } break; @@ -631,7 +661,12 @@ static bool TranslateMEDPattern(FileReader &file, FileReader &cmdExt, CPattern & if(note >= NOTE_MIDDLEC + 2 * 12) needInstruments = true; - if(note >= NOTE_MIN && note <= NOTE_MAX) + // This doesn't happen in MED SoundStudio for Windows... closest we have to be able to identify it is the usage of 7-bit volume + if(note > NOTE_MIN + 131 && !ctx.vol7bit) + note -= 108; + else if(note > NOTE_MAX) + note -= mpt::align_down(note - (NOTE_MAX - 11), 12); + if(note >= NOTE_MIN) m->note = static_cast(note); if(!cmd && !param1) @@ -659,6 +694,104 @@ static bool TranslateMEDPattern(FileReader &file, FileReader &cmdExt, CPattern & } +static void TranslateMEDSynthScript(std::array &arr, size_t numEntries, uint8 speed, uint8 hold, uint8 decay, InstrumentSynth::Events &events, bool isVolume) +{ + events.push_back(InstrumentSynth::Event::SetStepSpeed(speed, true)); + if(hold && isVolume) + events.push_back(InstrumentSynth::Event::MED_HoldDecay(hold, decay)); + + std::map entryFromByte; + + FileReader chunk{mpt::as_span(arr).subspan(0, std::min(arr.size(), numEntries))}; + while(chunk.CanRead(1)) + { + const uint16 scriptPos = static_cast(chunk.GetPosition()); + entryFromByte[scriptPos] = static_cast(events.size()); + events.push_back(InstrumentSynth::Event::JumpMarker(scriptPos)); + uint8 b = chunk.ReadUint8(); + switch(b) + { + case 0xFF: // END - End sequence + case 0xFB: // HLT - Halt + events.push_back(InstrumentSynth::Event::StopScript()); + break; + case 0xFE: // JMP - Jump + events.push_back(InstrumentSynth::Event::Jump(chunk.ReadUint8())); + break; + case 0xFD: // ARE - End arpeggio definition + break; + case 0xFC: // ARP - Begin arpeggio definition + { + size_t firstEvent = events.size(); + uint8 arpSize = 0; + while(chunk.CanRead(1)) + { + b = chunk.ReadUint8(); + if(b >= 0x80) + break; + events.push_back(InstrumentSynth::Event::MED_DefineArpeggio(b, 0)); + arpSize++; + } + if(arpSize) + events[firstEvent].u16 = arpSize; + } + break; + case 0xFA: // JWV / JWS - Jump waveform / volume sequence + events.push_back(InstrumentSynth::Event::MED_JumpScript(isVolume ? 1 : 0, chunk.ReadUint8())); + break; + case 0xF7: // - / VWF - Set vibrato waveform + if(!isVolume) + events.push_back(InstrumentSynth::Event::MED_SetEnvelope(chunk.ReadUint8(), true, false)); + break; + case 0xF6: // EST / RES - ? / reset pitch + if(!isVolume) + events.push_back(InstrumentSynth::Event::Puma_PitchRamp(0, 0, 0)); + break; + case 0xF5: // EN2 / VBS - Looping envelope / set vibrato speed + if(isVolume) + events.push_back(InstrumentSynth::Event::MED_SetEnvelope(chunk.ReadUint8(), true, true)); + else + events.push_back(InstrumentSynth::Event::MED_SetVibratoSpeed(chunk.ReadUint8())); + break; + case 0xF4: // EN1 - VBD - One shot envelope / set vibrato depth + if(isVolume) + events.push_back(InstrumentSynth::Event::MED_SetEnvelope(chunk.ReadUint8(), false, true)); + else + events.push_back(InstrumentSynth::Event::MED_SetVibratoDepth(chunk.ReadUint8())); + break; + case 0xF3: // CHU - Change volume / pitch up speed + if(isVolume) + events.push_back(InstrumentSynth::Event::MED_SetVolumeStep(chunk.ReadUint8())); + else + events.push_back(InstrumentSynth::Event::MED_SetPeriodStep(chunk.ReadUint8())); + break; + case 0xF2: // CHD - Change volume / pitch down speed + if(isVolume) + events.push_back(InstrumentSynth::Event::MED_SetVolumeStep(-static_cast(chunk.ReadUint8()))); + else + events.push_back(InstrumentSynth::Event::MED_SetPeriodStep(-static_cast(chunk.ReadUint8()))); + break; + case 0xF1: // WAI - Wait + events.push_back(InstrumentSynth::Event::Delay(std::max(chunk.ReadUint8(), uint8(1)) - 1)); + break; + case 0xF0: // SPD - Set Speed + events.push_back(InstrumentSynth::Event::SetStepSpeed(chunk.ReadUint8(), false)); + break; + default: + if(isVolume && b <= 64) + events.push_back(InstrumentSynth::Event::MED_SetVolume(b)); + else if(!isVolume) + events.push_back(InstrumentSynth::Event::MED_SetWaveform(b)); + break; + } + } + for(auto &event : events) + { + event.FixupJumpTarget(entryFromByte); + } +} + + #ifdef MPT_WITH_VST static std::wstring ReadMEDStringUTF16BE(FileReader &file) { @@ -771,15 +904,11 @@ bool CSoundFile::ReadMED(FileReader &file, ModLoadingFlags loadFlags) return false; if(!ValidateHeader(fileHeader)) return false; - if(!file.CanRead(mpt::saturate_cast(GetHeaderMinimumAdditionalSize(fileHeader)))) + if(!file.CanRead(mpt::saturate_cast(GetHeaderMinimumAdditionalSize(fileHeader)))) return false; if(loadFlags == onlyVerifyHeader) return true; - InitializeGlobals(MOD_TYPE_MED); - InitializeChannels(); - const uint8 version = fileHeader.version - '0'; - file.Seek(fileHeader.songOffset); FileReader sampleHeaderChunk = file.ReadChunk(63 * sizeof(MMD0Sample)); @@ -789,16 +918,12 @@ bool CSoundFile::ReadMED(FileReader &file, ModLoadingFlags loadFlags) if(songHeader.numSamples > 63 || songHeader.numBlocks > 0x7FFF) return false; - MMD0Exp expData{}; - if(fileHeader.expDataOffset && file.Seek(fileHeader.expDataOffset)) - { - file.ReadStruct(expData); - } - + const uint8 version = fileHeader.version - '0'; const auto [numChannels, numSongs] = MEDScanNumChannels(file, version); if(numChannels < 1 || numChannels > MAX_BASECHANNELS) return false; - m_nChannels = numChannels; + + InitializeGlobals(MOD_TYPE_MED, numChannels); // Start with the instruments, as those are shared between songs @@ -813,6 +938,25 @@ bool CSoundFile::ReadMED(FileReader &file, ModLoadingFlags loadFlags) } m_nInstruments = m_nSamples = songHeader.numSamples; + MMD0Exp expData{}; + if(fileHeader.expDataOffset && file.Seek(fileHeader.expDataOffset)) + { + file.ReadStruct(expData); + } + + FileReader expDataChunk; + if(expData.instrExtOffset != 0 && file.Seek(expData.instrExtOffset)) + { + const uint16 entries = std::min(expData.instrExtEntries, songHeader.numSamples); + expDataChunk = file.ReadChunk(expData.instrExtEntrySize * entries); + } + FileReader instrInfoChunk; + if(expData.instrInfoOffset != 0 && file.Seek(expData.instrInfoOffset)) + { + const uint16 entries = std::min(expData.instrInfoEntries, songHeader.numSamples); + instrInfoChunk = file.ReadChunk(expData.instrInfoEntrySize * entries); + } + // In MMD0 / MMD1, octave wrapping is not done for synth instruments // - It's required e.g. for automatic terminated to.mmd0 and you got to let the music.mmd1 // - starkelsesirap.mmd0 (synth instruments) on the other hand don't need it @@ -821,7 +965,6 @@ bool CSoundFile::ReadMED(FileReader &file, ModLoadingFlags loadFlags) m_nMinPeriod = hardwareMixSamples ? (113 * 4) : (55 * 4); bool needInstruments = false; - bool anySynthInstrs = false; #ifndef NO_PLUGINS PLUGINDEX numPlugins = 0; #endif // !NO_PLUGINS @@ -831,6 +974,9 @@ bool CSoundFile::ReadMED(FileReader &file, ModLoadingFlags loadFlags) return false; ModInstrument &instr = *Instruments[ins]; + MMDInstrExt instrExt{}; + expDataChunk.ReadStructPartial(instrExt, expData.instrExtEntrySize); + MMDInstrHeader instrHeader{}; FileReader sampleChunk; if(instrOffsets[ins - 1] != 0 && file.Seek(instrOffsets[ins - 1])) @@ -841,6 +987,7 @@ bool CSoundFile::ReadMED(FileReader &file, ModLoadingFlags loadFlags) chunkLength *= 2u; sampleChunk = file.ReadChunk(chunkLength); } + std::vector waveformOffsets; // For synth instruments const bool isSynth = instrHeader.type < 0; const size_t maskedType = static_cast(instrHeader.type & MMDInstrHeader::TYPEMASK); @@ -869,24 +1016,47 @@ bool CSoundFile::ReadMED(FileReader &file, ModLoadingFlags loadFlags) } } else #endif // MPT_WITH_VST - if(isSynth) + if(instrHeader.type == MMDInstrHeader::SYNTHETIC || instrHeader.type == MMDInstrHeader::HYBRID) + { + needInstruments = true; + MMDSynthInstr synthInstr; + sampleChunk.ReadStruct(synthInstr); + if(!synthInstr.IsValid()) + return false; + + if(instrHeader.type == MMDInstrHeader::SYNTHETIC) + instr.filename = "Synth"; + else + instr.filename = "Hybrid"; + + instr.AssignSample(smp); + if(instrHeader.type == MMDInstrHeader::SYNTHETIC && version <= 2) + instr.Transpose(-24); + + instr.synth.m_scripts.resize(2); + TranslateMEDSynthScript(synthInstr.volTable, synthInstr.volTableLen, synthInstr.volSpeed, instrExt.hold, instrExt.decay, instr.synth.m_scripts[0], true); + TranslateMEDSynthScript(synthInstr.waveTable, synthInstr.waveTableLen, synthInstr.waveSpeed, instrExt.hold, instrExt.decay, instr.synth.m_scripts[1], false); + + if(synthInstr.numWaveforms <= 64) + file.ReadVector(waveformOffsets, synthInstr.numWaveforms); + } else if(isSynth) { - // TODO: Figure out synth instruments - anySynthInstrs = true; instr.AssignSample(0); } MMD0Sample sampleHeader; sampleHeaderChunk.ReadStruct(sampleHeader); + int8 sampleTranspose = sampleHeader.sampleTranspose; - uint8 numSamples = 1; + uint8 numSamples = std::max(uint8(1), static_cast(waveformOffsets.size())); static constexpr uint8 SamplesPerType[] = {1, 5, 3, 2, 4, 6, 7}; if(!isSynth && maskedType < std::size(SamplesPerType)) numSamples = SamplesPerType[maskedType]; if(numSamples > 1) { - static_assert(MAX_SAMPLES > 63 * 9, "Check IFFOCT multisample code"); - m_nSamples += numSamples - 1; + if(!CanAddMoreSamples(numSamples - 1)) + continue; + m_nSamples += static_cast(numSamples - 1); needInstruments = true; static constexpr uint8 OctSampleMap[][8] = { @@ -909,27 +1079,23 @@ bool CSoundFile::ReadMED(FileReader &file, ModLoadingFlags loadFlags) }; // TODO: Move octaves so that they align better (C-4 = lowest, we don't have access to the highest four octaves) - for(int octave = 4; octave < 10; octave++) + if(!isSynth) { - for(int note = 0, i = 12 * octave; note < 12; note++, i++) + for(int i = 0; i < static_cast(instr.Keyboard.size()); i++) { - instr.Keyboard[i] = smp + OctSampleMap[numSamples - 2][octave - 4]; - instr.NoteMap[i] = static_cast(instr.NoteMap[i] + OctTransposeMap[numSamples - 2][octave - 4]); + int note = i + sampleTranspose; + if(note < 0) + note = -note % 12; + int octave = std::clamp(note / 12 - 4, 0, static_cast(std::size(OctTransposeMap[0]) - 1)); + instr.Keyboard[i] = smp + OctSampleMap[numSamples - 2][octave]; + instr.NoteMap[i] = static_cast(NOTE_MIN + note + OctTransposeMap[numSamples - 2][octave]); } + sampleTranspose = 0; } } else if(maskedType == MMDInstrHeader::EXTSAMPLE) { needInstruments = true; instr.Transpose(-24); - } else if(!isSynth && (hardwareMixSamples || sampleHeader.sampleTranspose)) - { - int offset = NOTE_MIDDLEC + (hardwareMixSamples ? 24 : 36); - for(auto ¬e : instr.NoteMap) - { - int realNote = note + sampleHeader.sampleTranspose; - if(realNote >= offset) - note -= static_cast(mpt::align_down(realNote - offset + 12, 12)); - } } // midiChannel = 0xFF == midi instrument but with invalid channel, midiChannel = 0x00 == sample-based instrument? @@ -961,18 +1127,23 @@ bool CSoundFile::ReadMED(FileReader &file, ModLoadingFlags loadFlags) instr.nMidiProgram = sampleHeader.midiPreset; } + if(instr.nMidiChannel == MidiNoChannel) + { + int offset = NOTE_MIDDLEC + (hardwareMixSamples ? 24 : 36); + for(auto ¬e : instr.NoteMap) + { + int realNote = note + sampleTranspose; + if(realNote >= offset) + note -= static_cast(mpt::align_down(realNote - offset + 12, 12)); + } + } + for(SAMPLEINDEX i = 0; i < numSamples; i++) { ModSample &mptSmp = Samples[smp + i]; mptSmp.Initialize(MOD_TYPE_MED); mptSmp.nVolume = 4u * std::min(sampleHeader.sampleVolume, 64u); - mptSmp.RelativeTone = sampleHeader.sampleTranspose; - } - - if(isSynth || !(loadFlags & loadSampleData)) - { - smp += numSamples; - continue; + mptSmp.RelativeTone = sampleTranspose; } SampleIO sampleIO( @@ -984,85 +1155,115 @@ bool CSoundFile::ReadMED(FileReader &file, ModLoadingFlags loadFlags) const bool hasLoop = sampleHeader.loopLength > 1; SmpLength loopStart = sampleHeader.loopStart * 2; SmpLength loopEnd = loopStart + sampleHeader.loopLength * 2; - - SmpLength length = mpt::saturate_cast(sampleChunk.GetLength()); - if(instrHeader.type & MMDInstrHeader::S_16) + if(isSynth) { - sampleIO |= SampleIO::_16bit; - length /= 2; - } - if(instrHeader.type & MMDInstrHeader::STEREO) - { - sampleIO |= SampleIO::stereoSplit; - length /= 2; - m_SongFlags.reset(SONG_ISAMIGA); // Amiga resampler does not handle stereo samples - } - if(instrHeader.type & MMDInstrHeader::DELTA) - { - sampleIO |= SampleIO::deltaPCM; - } - - if(numSamples > 1) - length = length / ((1u << numSamples) - 1); - - for(SAMPLEINDEX i = 0; i < numSamples; i++) - { - ModSample &mptSmp = Samples[smp + i]; - - mptSmp.nLength = length; - sampleIO.ReadSample(mptSmp, sampleChunk); - - if(hasLoop) + for(size_t i = 0; i < waveformOffsets.size(); i++) { - mptSmp.nLoopStart = loopStart; - mptSmp.nLoopEnd = loopEnd; - mptSmp.uFlags.set(CHN_LOOP); + const uint32 offset = waveformOffsets[i]; + if(offset <= sizeof(MMDInstrHeader) + sizeof(MMDSynthInstr) || !file.Seek(instrOffsets[ins - 1] + offset)) + continue; + + ModSample &mptSmp = Samples[smp + i]; + if(instrHeader.type == MMDInstrHeader::SYNTHETIC || i > 0) + { + mptSmp.nLength = file.ReadUint16BE() * 2; + mptSmp.nLoopStart = 0; + mptSmp.nLoopEnd = mptSmp.nLength; + mptSmp.uFlags.set(CHN_LOOP); + m_szNames[smp + i] = "Synth"; + } else + { + MMDInstrHeader hybridHeader; + file.ReadStruct(hybridHeader); + if(hybridHeader.type == MMDInstrHeader::SAMPLE) + { + mptSmp.nLength = hybridHeader.length; + if(hasLoop) + { + mptSmp.nLoopStart = loopStart; + mptSmp.nLoopEnd = loopEnd; + mptSmp.uFlags.set(CHN_LOOP); + } + } + m_szNames[smp + i] = "Hybrid"; + } + if(loadFlags & loadSampleData) + sampleIO.ReadSample(mptSmp, file); + } + } else + { + + SmpLength length = mpt::saturate_cast(sampleChunk.GetLength()); + if(instrHeader.type & MMDInstrHeader::S_16) + { + sampleIO |= SampleIO::_16bit; + length /= 2; + } + if(instrHeader.type & MMDInstrHeader::STEREO) + { + sampleIO |= SampleIO::stereoSplit; + length /= 2; + m_SongFlags.reset(SONG_ISAMIGA); // Amiga resampler does not handle stereo samples + } + if(instrHeader.type & MMDInstrHeader::DELTA) + { + sampleIO |= SampleIO::deltaPCM; } - length *= 2; - loopStart *= 2; - loopEnd *= 2; + if(numSamples > 1) + length = length / ((1u << numSamples) - 1); + + for(SAMPLEINDEX i = 0; i < numSamples; i++) + { + ModSample &mptSmp = Samples[smp + i]; + + mptSmp.nLength = length; + if(loadFlags & loadSampleData) + sampleIO.ReadSample(mptSmp, sampleChunk); + + if(hasLoop) + { + mptSmp.nLoopStart = loopStart; + mptSmp.nLoopEnd = loopEnd; + mptSmp.uFlags.set(CHN_LOOP); + } + + length *= 2; + loopStart *= 2; + loopEnd *= 2; + } } - smp += numSamples; - } - - if(expData.instrExtOffset != 0 && expData.instrExtEntries != 0 && file.Seek(expData.instrExtOffset)) - { - const uint16 entries = std::min(expData.instrExtEntries, songHeader.numSamples); - const uint16 size = expData.instrExtEntrySize; - for(uint16 i = 0; i < entries; i++) + // On to the extended instrument info... + if(expDataChunk.IsValid()) { - MMDInstrExt instrExt; - file.ReadStructPartial(instrExt, size); - - ModInstrument &ins = *Instruments[i + 1]; - if(instrExt.hold) + const uint16 size = expData.instrExtEntrySize; + if(instrExt.hold && !isSynth) { - ins.VolEnv.assign({ + instr.VolEnv.assign({ EnvelopeNode{0u, ENVELOPE_MAX}, EnvelopeNode{static_cast(instrExt.hold - 1), ENVELOPE_MAX}, EnvelopeNode{static_cast(instrExt.hold + (instrExt.decay ? 64u / instrExt.decay : 0u)), ENVELOPE_MIN}, }); if(instrExt.hold == 1) - ins.VolEnv.erase(ins.VolEnv.begin()); - ins.nFadeOut = instrExt.decay ? (instrExt.decay * 512) : 32767; - ins.VolEnv.dwFlags.set(ENV_ENABLED); + instr.VolEnv.erase(instr.VolEnv.begin()); + instr.nFadeOut = instrExt.decay ? (instrExt.decay * 512) : 32767; + instr.VolEnv.dwFlags.set(ENV_ENABLED); needInstruments = true; } if(size > offsetof(MMDInstrExt, defaultPitch) && instrExt.defaultPitch != 0) { - ins.NoteMap[24] = instrExt.defaultPitch + NOTE_MIN + 23; + instr.NoteMap[24] = instrExt.defaultPitch + NOTE_MIN + 23; needInstruments = true; } if(size > offsetof(MMDInstrExt, volume)) - ins.nGlobalVol = (instrExt.volume + 1u) / 2u; + instr.nGlobalVol = (instrExt.volume + 1u) / 2u; if(size > offsetof(MMDInstrExt, midiBank)) - ins.wMidiBank = instrExt.midiBank; + instr.wMidiBank = instrExt.midiBank; #ifdef MPT_WITH_VST - if(ins.nMixPlug > 0) + if(instr.nMixPlug > 0) { - PLUGINDEX plug = ins.nMixPlug - 1; + PLUGINDEX plug = instr.nMixPlug - 1; auto &mixPlug = m_MixPlugins[plug]; if(mixPlug.Info.dwPluginId2 == PLUGMAGIC('M', 'M', 'I', 'D')) { @@ -1081,7 +1282,7 @@ bool CSoundFile::ReadMED(FileReader &file, ModLoadingFlags loadFlags) && otherPlug.Info.dwPluginId2 == mixPlug.Info.dwPluginId2 && otherPlug.pluginData == mixPlug.pluginData) { - ins.nMixPlug = p + 1; + instr.nMixPlug = p + 1; mixPlug = {}; break; } @@ -1090,37 +1291,46 @@ bool CSoundFile::ReadMED(FileReader &file, ModLoadingFlags loadFlags) } #endif // MPT_WITH_VST - ModSample &sample = Samples[ins.Keyboard[NOTE_MIDDLEC]]; - sample.nFineTune = MOD2XMFineTune(instrExt.finetune); + loopStart = instrExt.loopStart; + loopEnd = instrExt.loopStart + instrExt.loopLength; + for(SAMPLEINDEX i = 0; i < numSamples; i++) + { + ModSample &sample = Samples[smp + i]; + sample.nFineTune = MOD2XMFineTune(instrExt.finetune); - if(size > offsetof(MMDInstrExt, loopLength)) - { - sample.nLoopStart = instrExt.loopStart; - sample.nLoopEnd = instrExt.loopStart + instrExt.loopLength; - } - if(size > offsetof(MMDInstrExt, instrFlags)) - { - sample.uFlags.set(CHN_LOOP, (instrExt.instrFlags & MMDInstrExt::SSFLG_LOOP) != 0); - sample.uFlags.set(CHN_PINGPONGLOOP, (instrExt.instrFlags & MMDInstrExt::SSFLG_PINGPONG) != 0); - if(instrExt.instrFlags & MMDInstrExt::SSFLG_DISABLED) - sample.nGlobalVol = 0; + if(!isSynth && size > offsetof(MMDInstrExt, loopLength)) + { + sample.nLoopStart = loopStart; + sample.nLoopEnd = loopEnd; + loopStart *= 2; + loopEnd *= 2; + } + if(size > offsetof(MMDInstrExt, instrFlags)) + { + if(!isSynth) + { + sample.uFlags.set(CHN_LOOP, (instrExt.instrFlags & MMDInstrExt::SSFLG_LOOP) != 0); + sample.uFlags.set(CHN_PINGPONGLOOP, (instrExt.instrFlags & MMDInstrExt::SSFLG_PINGPONG) != 0); + } + if(instrExt.instrFlags & MMDInstrExt::SSFLG_DISABLED) + sample.nGlobalVol = 0; + } } } - } - if(expData.instrInfoOffset != 0 && expData.instrInfoEntries != 0 && file.Seek(expData.instrInfoOffset)) - { - const uint16 entries = std::min(expData.instrInfoEntries, songHeader.numSamples); - const uint16 size = expData.instrInfoEntrySize; - for(uint16 i = 0; i < entries; i++) + + // And even more optional data! + if(instrInfoChunk.IsValid()) { MMDInstrInfo instrInfo; - file.ReadStructPartial(instrInfo, size); - Instruments[i + 1]->name = mpt::String::ReadBuf(mpt::String::maybeNullTerminated, instrInfo.name); - for(auto smp : Instruments[i + 1]->GetSamples()) + instrInfoChunk.ReadStructPartial(instrInfo, expData.instrInfoEntrySize); + instr.name = mpt::String::ReadBuf(mpt::String::maybeNullTerminated, instrInfo.name); + for(SAMPLEINDEX i = 0; i < numSamples; i++) { - m_szNames[smp] = Instruments[i + 1]->name; + m_szNames[smp + i] = instr.name; } } + + smp += numSamples; } // Setup a program change macro for command 1C (even if MIDI plugin is disabled, as otherwise these commands may act as filter commands) @@ -1156,7 +1366,7 @@ bool CSoundFile::ReadMED(FileReader &file, ModLoadingFlags loadFlags) SetupMODPanning(true); // With MED SoundStudio 1.03 it's possible to create MMD1 files with more than 16 channels. - const CHANNELINDEX numChannelVols = std::min(m_nChannels, CHANNELINDEX(16)); + const CHANNELINDEX numChannelVols = std::min(GetNumChannels(), CHANNELINDEX(16)); for(CHANNELINDEX chn = 0; chn < numChannelVols; chn++) { ChnSettings[chn].nVolume = std::min(songHeader.trackVol[chn], 64); @@ -1164,7 +1374,7 @@ bool CSoundFile::ReadMED(FileReader &file, ModLoadingFlags loadFlags) } else { const MMD2Song header = songHeader.GetMMD2Song(); - if(header.numTracks < 1 || header.numTracks > 64 || m_nChannels > 64) + if(header.numTracks < 1 || header.numTracks > 64 || GetNumChannels() > 64) return false; const bool freePan = !hardwareMixSamples && (header.flags3 & MMD2Song::FLAG3_FREEPAN); @@ -1175,16 +1385,16 @@ bool CSoundFile::ReadMED(FileReader &file, ModLoadingFlags loadFlags) if(file.Seek(header.trackVolsOffset)) { - for(CHANNELINDEX chn = 0; chn < m_nChannels; chn++) + for(CHANNELINDEX chn = 0; chn < GetNumChannels(); chn++) { ChnSettings[chn].nVolume = std::min(file.ReadUint8(), 64); } } if((freePan || version > 2) && header.trackPanOffset && file.Seek(header.trackPanOffset)) { - for(CHANNELINDEX chn = 0; chn < m_nChannels; chn++) + for(CHANNELINDEX chn = 0; chn < GetNumChannels(); chn++) { - ChnSettings[chn].nPan = (Clamp(file.ReadInt8(), -16, 16) + 16) * 8; + ChnSettings[chn].nPan = static_cast((Clamp(file.ReadInt8(), -16, 16) + 16) * 8); } } else { @@ -1239,7 +1449,7 @@ bool CSoundFile::ReadMED(FileReader &file, ModLoadingFlags loadFlags) file.ReadStruct(playSeq); if(!order.empty()) - order.push_back(order.GetIgnoreIndex()); + order.push_back(PATTERNINDEX_SKIP); const size_t orderStart = order.size(); size_t readOrders = playSeq.length; @@ -1271,11 +1481,11 @@ bool CSoundFile::ReadMED(FileReader &file, ModLoadingFlags loadFlags) break; if(command.command == MMDPlaySeqCommand::kStop) { - order[ord] = order.GetInvalidPatIndex(); + order[ord] = PATTERNINDEX_INVALID; } else if(command.command == MMDPlaySeqCommand::kJump) { jumpTargets[ord] = chunk.ReadUint16BE(); - order[ord] = order.GetIgnoreIndex(); + order[ord] = PATTERNINDEX_SKIP; } } } @@ -1287,23 +1497,20 @@ bool CSoundFile::ReadMED(FileReader &file, ModLoadingFlags loadFlags) const bool bpmMode = (songHeader.flags2 & MMDSong::FLAG2_BPM) != 0; const bool softwareMixing = (songHeader.flags2 & MMDSong::FLAG2_MIX) != 0; const uint8 rowsPerBeat = 1 + (songHeader.flags2 & MMDSong::FLAG2_BMASK); - if(song == 0) + order.SetDefaultTempo(MMDTempoToBPM(songHeader.defaultTempo, is8Ch, softwareMixing, bpmMode, rowsPerBeat)); + order.SetDefaultSpeed(Clamp(songHeader.tempo2, 1, 32)); + if(bpmMode) { - m_nDefaultTempo = MMDTempoToBPM(songHeader.defaultTempo, is8Ch, softwareMixing, bpmMode, rowsPerBeat); - m_nDefaultSpeed = Clamp(songHeader.tempo2, 1, 32); - if(bpmMode) - { - m_nDefaultRowsPerBeat = rowsPerBeat; - m_nDefaultRowsPerMeasure = m_nDefaultRowsPerBeat * 4u; - } + m_nDefaultRowsPerBeat = rowsPerBeat; + m_nDefaultRowsPerMeasure = m_nDefaultRowsPerBeat * 4u; } if(songHeader.masterVol) m_nDefaultGlobalVolume = std::min(songHeader.masterVol, 64) * 4; m_nSamplePreAmp = m_nVSTiVolume = preamp; - // For MED, this affects both volume and pitch slides m_SongFlags.set(SONG_FASTVOLSLIDES, !(songHeader.flags & MMDSong::FLAG_STSLIDE)); + m_SongFlags.set(SONG_FASTPORTAS, !(songHeader.flags& MMDSong::FLAG_STSLIDE)); m_playBehaviour.set(kST3OffsetWithoutInstrument); m_playBehaviour.set(kST3PortaSampleChange); m_playBehaviour.set(kFT2PortaNoNote); @@ -1370,7 +1577,7 @@ bool CSoundFile::ReadMED(FileReader &file, ModLoadingFlags loadFlags) // Track Names if(version >= 2 && expData.trackInfoOffset) { - for(CHANNELINDEX chn = 0; chn < m_nChannels; chn++) + for(CHANNELINDEX chn = 0; chn < GetNumChannels(); chn++) { if(file.Seek(expData.trackInfoOffset + chn * 4) && file.Seek(file.ReadUint32BE())) @@ -1469,7 +1676,7 @@ bool CSoundFile::ReadMED(FileReader &file, ModLoadingFlags loadFlags) CPattern &pattern = Patterns[basePattern + pat]; pattern.SetName(patName); - LimitMax(numTracks, m_nChannels); + LimitMax(numTracks, GetNumChannels()); TranslateMEDPatternContext context{transpose, numTracks, version, rowsPerBeat, is8Ch, softwareMixing, bpmMode, volHex, vol7bit}; needInstruments |= TranslateMEDPattern(file, cmdExt, pattern, context, false); @@ -1509,7 +1716,7 @@ bool CSoundFile::ReadMED(FileReader &file, ModLoadingFlags loadFlags) PATTERNINDEX firstPat = order.EnsureUnique(order.GetFirstValidIndex()); if(firstPat != PATTERNINDEX_INVALID) { - for(CHANNELINDEX chn = 0; chn < m_nChannels; chn++) + for(CHANNELINDEX chn = 0; chn < GetNumChannels(); chn++) { Patterns[firstPat].WriteEffect(EffectWriter(CMD_CHANNELVOLUME, static_cast(ChnSettings[chn].nVolume)).Channel(chn).RetryNextRow()); Patterns[firstPat].WriteEffect(EffectWriter(CMD_PANNING8, mpt::saturate_cast(ChnSettings[chn].nPan)).Channel(chn).RetryNextRow()); @@ -1536,13 +1743,10 @@ bool CSoundFile::ReadMED(FileReader &file, ModLoadingFlags loadFlags) m_nInstruments = 0; } - if(anySynthInstrs) - AddToLog(LogWarning, U_("Synthesized MED instruments are not supported.")); - const mpt::uchar *madeWithTracker = MPT_ULITERAL(""); switch(version) { - case 0: madeWithTracker = m_nChannels > 4 ? MPT_ULITERAL("OctaMED v2.10 (MMD0)") : MPT_ULITERAL("MED v2 (MMD0)"); break; + case 0: madeWithTracker = GetNumChannels() > 4 ? MPT_ULITERAL("OctaMED v2.10 (MMD0)") : MPT_ULITERAL("MED v2 (MMD0)"); break; case 1: madeWithTracker = MPT_ULITERAL("OctaMED v4 (MMD1)"); break; case 2: madeWithTracker = MPT_ULITERAL("OctaMED v5 (MMD2)"); break; case 3: madeWithTracker = MPT_ULITERAL("OctaMED Soundstudio (MMD3)"); break; diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_mid.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_mid.cpp index 696ae785c..a51ffda6e 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_mid.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_mid.cpp @@ -12,6 +12,7 @@ #include "Loaders.h" #include "Dlsbank.h" #include "MIDIEvents.h" +#include "mod_specifications.h" #ifdef MODPLUG_TRACKER #include "../mptrack/TrackerSettings.h" #include "../mptrack/Moddoc.h" @@ -624,12 +625,11 @@ bool CSoundFile::ReadMID(FileReader &file, ModLoadingFlags loadFlags) return true; } - InitializeGlobals(MOD_TYPE_MID); - InitializeChannels(); + InitializeGlobals(MOD_TYPE_MID, GetModSpecifications(MOD_TYPE_MPT).channelsMax); #ifdef MODPLUG_TRACKER const uint32 quantize = Clamp(TrackerSettings::Instance().midiImportQuantize.Get(), 4u, 256u); - const ROWINDEX patternLen = Clamp(TrackerSettings::Instance().midiImportPatternLen.Get(), ROWINDEX(1), MAX_PATTERN_ROWS); + const ROWINDEX patternLen = Clamp(TrackerSettings::Instance().midiImportPatternLen.Get(), GetModSpecifications().patternRowsMin, GetModSpecifications().patternRowsMax); const uint8 ticksPerRow = Clamp(TrackerSettings::Instance().midiImportTicks.Get(), uint8(2), uint8(16)); #else const uint32 quantize = 32; // Must be 4 or higher @@ -643,22 +643,21 @@ bool CSoundFile::ReadMID(FileReader &file, ModLoadingFlags loadFlags) const ORDERINDEX MPT_MIDI_IMPORT_MAX_ORDERS = MAX_ORDERS; #endif - m_songArtist = U_("MIDI Conversion"); - m_modFormat.formatName = U_("Standard MIDI File"); + m_songArtist = UL_("MIDI Conversion"); + m_modFormat.formatName = UL_("Standard MIDI File"); m_modFormat.type = isRIFF ? UL_("rmi") : UL_("mid"); - m_modFormat.madeWithTracker = U_("Standard MIDI File"); + m_modFormat.madeWithTracker = UL_("Standard MIDI File"); m_modFormat.charset = mpt::Charset::ISO8859_1; SetMixLevels(MixLevels::v1_17RC3); m_nTempoMode = TempoMode::Modern; m_SongFlags = SONG_LINEARSLIDES; - m_nDefaultTempo.Set(120); - m_nDefaultSpeed = ticksPerRow; - m_nChannels = MAX_BASECHANNELS; + TEMPO tempo{120, 0}; + Order().SetDefaultTempo(tempo); + Order().SetDefaultSpeed(ticksPerRow); m_nDefaultRowsPerBeat = quantize / 4; m_nDefaultRowsPerMeasure = 4 * m_nDefaultRowsPerBeat; m_nSamplePreAmp = m_nVSTiVolume = 32; - TEMPO tempo = m_nDefaultTempo; uint16 ppqn = fileHeader.division; if(ppqn & 0x8000) { @@ -671,10 +670,10 @@ bool CSoundFile::ReadMID(FileReader &file, ModLoadingFlags loadFlags) Order().clear(); std::array midiChnStatus; - const CHANNELINDEX tempoChannel = m_nChannels - 2, globalVolChannel = m_nChannels - 1; + const CHANNELINDEX tempoChannel = GetNumChannels() - 2, globalVolChannel = GetNumChannels() - 1; const uint16 numTracks = fileHeader.numTracks; std::vector tracks(numTracks); - std::vector modChnStatus(m_nChannels); + std::vector modChnStatus(GetNumChannels()); std::bitset drumChns; drumChns.set(MIDI_DRUMCHANNEL - 1); drumChns.set(MIDI_DRUMCHANNEL + 15); @@ -839,7 +838,7 @@ bool CSoundFile::ReadMID(FileReader &file, ModLoadingFlags loadFlags) TEMPO newTempo(60000000.0 / tempoInt); if(!tick) { - m_nDefaultTempo = newTempo; + Order().SetDefaultTempo(newTempo); } else if(newTempo != tempo) { patRow[tempoChannel].command = CMD_TEMPO; @@ -1231,7 +1230,7 @@ bool CSoundFile::ReadMID(FileReader &file, ModLoadingFlags loadFlags) // Need a normal slide. absDiff /= 4 * (ticksPerRow - 1); LimitMax(absDiff, 0xDF); - m.param = static_cast(absDiff); + m.param = static_cast(absDiff); realDiff = absDiff * 4 * (ticksPerRow - 1); } chnState.porta += realDiff * mpt::signum(diff); @@ -1284,8 +1283,8 @@ bool CSoundFile::ReadMID(FileReader &file, ModLoadingFlags loadFlags) Order.SetSequence(0); std::vector channels; - channels.reserve(m_nChannels); - for(CHANNELINDEX i = 0; i < m_nChannels; i++) + channels.reserve(GetNumChannels()); + for(CHANNELINDEX i = 0; i < GetNumChannels(); i++) { if(modChnStatus[i].midiCh != ModChannelState::NOMIDI #ifdef MODPLUG_TRACKER @@ -1360,7 +1359,7 @@ bool CSoundFile::ReadMID(FileReader &file, ModLoadingFlags loadFlags) continue; } - const mpt::PathString &midiMapName = midiLib[midiCode]; + const mpt::PathString &midiMapName = midiLib[midiCode].value_or(P_("")); if(!midiMapName.empty()) { // Load from DLS/SF2 Bank diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_mo3.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_mo3.cpp index 6e5104872..dbea105f4 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_mo3.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_mo3.cpp @@ -19,6 +19,7 @@ #include "mpt/io/base.hpp" #include "mpt/io/io.hpp" #include "mpt/io/io_stdstream.hpp" +#include "mpt/io_read/filedata_base_unseekable_buffer.hpp" #include "Tables.h" #include "../common/version.h" @@ -67,26 +68,26 @@ struct MO3FileHeader { enum MO3HeaderFlags { - linearSlides = 0x0001, - isS3M = 0x0002, - s3mFastSlides = 0x0004, - isMTM = 0x0008, // Actually this is simply "not XM". But if none of the S3M, MOD and IT flags are set, it's an MTM. - s3mAmigaLimits = 0x0010, + linearSlides = 0x0001, + isS3M = 0x0002, + s3mFastSlides = 0x0004, + isMTM = 0x0008, // Actually this is simply "not XM". But if none of the S3M, MOD and IT flags are set, it's an MTM. + s3mAmigaLimits = 0x0010, // 0x20 and 0x40 have been used in old versions for things that can be inferred from the file format anyway. // The official UNMO3 ignores them. - isMOD = 0x0080, - isIT = 0x0100, - instrumentMode = 0x0200, - itCompatGxx = 0x0400, - itOldFX = 0x0800, - modplugMode = 0x10000, - unknown = 0x20000, // Always set (internal BASS flag to designate modules) - modVBlank = 0x80000, - hasPlugins = 0x100000, - extFilterRange = 0x200000, + isMOD = 0x0080, + isIT = 0x0100, + instrumentMode = 0x0200, + itCompatGxx = 0x0400, + itOldFX = 0x0800, + modplugMode = 0x10000, + unknown = 0x20000, // Always set (internal BASS flag to designate modules) + modVBlank = 0x80000, + hasPlugins = 0x100000, + extFilterRange = 0x200000, }; - uint8le numChannels; // 1...64 (limited by channel panning and volume) + uint8le numChannels; // 1...64 (limited by channel panning and volume) uint16le numOrders; uint16le restartPos; uint16le numPatterns; @@ -95,12 +96,12 @@ struct MO3FileHeader uint16le numSamples; uint8le defaultSpeed; uint8le defaultTempo; - uint32le flags; // See MO3HeaderFlags - uint8le globalVol; // 0...128 in IT, 0...64 in S3M - uint8le panSeparation; // 0...128 in IT - int8le sampleVolume; // Only used in IT - uint8le chnVolume[64]; // 0...64 - uint8le chnPan[64]; // 0...256, 127 = surround + uint32le flags; // See MO3HeaderFlags + uint8le globalVol; // 0...128 in IT, 0...64 in S3M + uint8le panSeparation; // 0...128 in IT + int8le sampleVolume; // Only used in IT + uint8le chnVolume[64]; // 0...64 + uint8le chnPan[64]; // 0...256, 127 = surround uint8le sfxMacros[16]; uint8le fixedMacros[128][2]; }; @@ -112,14 +113,14 @@ struct MO3Envelope { enum MO3EnvelopeFlags { - envEnabled = 0x01, - envSustain = 0x02, - envLoop = 0x04, - envFilter = 0x10, - envCarry = 0x20, + envEnabled = 0x01, + envSustain = 0x02, + envLoop = 0x04, + envFilter = 0x10, + envCarry = 0x20, }; - uint8le flags; // See MO3EnvelopeFlags + uint8le flags; // See MO3EnvelopeFlags uint8le numNodes; uint8le sustainStart; uint8le sustainEnd; @@ -157,11 +158,11 @@ struct MO3Instrument { enum MO3InstrumentFlags { - playOnMIDI = 0x01, - mute = 0x02, + playOnMIDI = 0x01, + mute = 0x02, }; - uint32le flags; // See MO3InstrumentFlags + uint32le flags; // See MO3InstrumentFlags uint16le sampleMap[120][2]; MO3Envelope volEnv; MO3Envelope panEnv; @@ -172,23 +173,23 @@ struct MO3Instrument uint8le sweep; uint8le depth; uint8le rate; - } vibrato; // Applies to all samples of this instrument (XM) + } vibrato; // Applies to all samples of this instrument (XM) uint16le fadeOut; uint8le midiChannel; uint8le midiBank; uint8le midiPatch; uint8le midiBend; - uint8le globalVol; // 0...128 - uint16le panning; // 0...256 if enabled, 0xFFFF otherwise + uint8le globalVol; // 0...128 + uint16le panning; // 0...256 if enabled, 0xFFFF otherwise uint8le nna; uint8le pps; uint8le ppc; uint8le dct; uint8le dca; - uint16le volSwing; // 0...100 - uint16le panSwing; // 0...256 - uint8le cutoff; // 0...127, + 128 if enabled - uint8le resonance; // 0...127, + 128 if enabled + uint16le volSwing; // 0...100 + uint16le panSwing; // 0...256 + uint8le cutoff; // 0...127, + 128 if enabled + uint8le resonance; // 0...127, + 128 if enabled // Convert MO3 instrument data into OpenMPT's internal instrument format void ConvertToMPT(ModInstrument &mptIns, MODTYPE type) const @@ -281,23 +282,23 @@ struct MO3Sample smpCompressionMask = 0x1000 | 0x2000 | 0x4000 | 0x8000 }; - uint32le freqFinetune; // Frequency in S3M and IT, finetune (0...255) in MOD, MTM, XM + uint32le freqFinetune; // Frequency in S3M and IT, finetune (0...255) in MOD, MTM, XM int8le transpose; - uint8le defaultVolume; // 0...64 - uint16le panning; // 0...256 if enabled, 0xFFFF otherwise + uint8le defaultVolume; // 0...64 + uint16le panning; // 0...256 if enabled, 0xFFFF otherwise uint32le length; uint32le loopStart; uint32le loopEnd; - uint16le flags; // See MO3SampleFlags + uint16le flags; // See MO3SampleFlags uint8le vibType; uint8le vibSweep; uint8le vibDepth; uint8le vibRate; - uint8le globalVol; // 0...64 in IT, in XM it represents the instrument number + uint8le globalVol; // 0...64 in IT, in XM it represents the instrument number uint32le sustainStart; uint32le sustainEnd; int32le compressedSize; - uint16le encoderDelay; // MP3: Ignore first n bytes of decoded output. Ogg: Shared Ogg header size + uint16le encoderDelay; // MP3: Ignore first n bytes of decoded output. Ogg: Shared Ogg header size // Convert MO3 sample data into OpenMPT's internal instrument format void ConvertToMPT(ModSample &mptSmp, MODTYPE type, bool frequencyIsHertz) const @@ -353,13 +354,13 @@ MPT_BINARY_STRUCT(MO3Sample, 41) // We need all this information for Ogg-compressed samples with shared headers: // A shared header can be taken from a sample that has not been read yet, so // we first need to read all headers, and then load the Ogg samples afterwards. -struct MO3SampleChunk +struct MO3SampleInfo { FileReader chunk; - uint16 headerSize; - int16 sharedHeader; - MO3SampleChunk(const FileReader &chunk_ = FileReader(), uint16 headerSize_ = 0, int16 sharedHeader_ = 0) - : chunk(chunk_), headerSize(headerSize_), sharedHeader(sharedHeader_) {} + const MO3Sample smpHeader; + const int16 sharedHeader; + MO3SampleInfo(MO3Sample smpHeader, int16 sharedHeader) + : smpHeader{smpHeader}, sharedHeader{sharedHeader} {} }; @@ -381,7 +382,8 @@ struct MO3SampleChunk if(!file.Read(nextByte)) \ break; \ data = nextByte; \ - data = (data << 1) + 1; \ + data <<= 1; \ + data += 1; \ carry = (data > 0xFF); \ data &= 0xFF; \ } @@ -403,99 +405,162 @@ struct MO3SampleChunk } -static bool UnpackMO3Data(FileReader &file, std::vector &uncompressed, const uint32 size) +class MO3FileReaderBuffer final : public mpt::IO::FileDataUnseekableBuffer { - if(!size) - return false; - - uint16 data = 0; - int8 carry = 0; // x86 carry (used to propagate the most significant bit from one byte to another) - int32 strLen = 0; // length of previous string - int32 strOffset; // string offset - uint32 previousPtr = 0; - - // Read first uncompressed byte - uncompressed.push_back(file.ReadUint8()); - uint32 remain = size - 1; - - while(remain > 0) +public: + MO3FileReaderBuffer(const FileReader &file, uint32 targetSize, uint32 suggestedReserveSize) + : file{file} + , m_suggestedReserveSize{suggestedReserveSize} + , m_targetSize{targetSize} + , m_totalRemain{targetSize} { - READ_CTRL_BIT; - if(!carry) + } + + bool UnpackedSuccessfully() const + { + return !m_totalRemain && !m_broken; + } + + auto SourcePosition() const + { + return file.GetPosition(); + } + +protected: + bool InternalEof() const override + { + return !m_totalRemain || m_broken; + } + + void InternalReadContinue(std::vector &streamCache, std::size_t suggestedCount) const override + { + if(!suggestedCount|| !m_targetSize || m_broken) + return; + + uint32 remain = std::min(m_totalRemain, mpt::saturate_cast(suggestedCount)); + + if(streamCache.empty()) { - // a 0 ctrl bit means 'copy', not compressed byte - if(uint8 b; file.Read(b)) - uncompressed.push_back(b); - else - break; + // Fist byte is always read verbatim + streamCache.reserve(std::min(m_targetSize, m_suggestedReserveSize)); + streamCache.push_back(mpt::byte_cast(file.ReadUint8())); + m_totalRemain--; remain--; - } else + } + + int32 strLen = m_strLen; + if(strLen) { - // a 1 ctrl bit means compressed bytes are following - uint8 lengthAdjust = 0; // length adjustment - DECODE_CTRL_BITS; // read length, and if strLen > 3 (coded using more than 1 bits pair) also part of the offset value - strLen -= 3; - if(strLen < 0) - { - // means LZ ptr with same previous relative LZ ptr (saved one) - strOffset = previousPtr; // restore previous Ptr - strLen++; - } else - { - // LZ ptr in ctrl stream - if(uint8 b; file.Read(b)) - strOffset = mpt::lshift_signed(strLen, 8) | b; // read less significant offset byte from stream - else - break; - strLen = 0; - strOffset = ~strOffset; - if(strOffset < -1280) - lengthAdjust++; - lengthAdjust++; // length is always at least 1 - if(strOffset < -32000) - lengthAdjust++; - previousPtr = strOffset; // save current Ptr - } - - // read the next 2 bits as part of strLen - READ_CTRL_BIT; - strLen = mpt::lshift_signed(strLen, 1) + carry; - READ_CTRL_BIT; - strLen = mpt::lshift_signed(strLen, 1) + carry; - if(strLen == 0) - { - // length does not fit in 2 bits - DECODE_CTRL_BITS; // decode length: 1 is the most significant bit, - strLen += 2; // then first bit of each bits pairs (noted n1), until n0. - } - strLen += lengthAdjust; // length adjustment - - if(remain < static_cast(strLen) || strLen <= 0) - break; - if(strOffset >= 0 || -static_cast(uncompressed.size()) > strOffset) - break; - - // Copy previous string - // Need to do this in two steps as source and destination may overlap (e.g. strOffset = -1, strLen = 2 repeats last character twice) - uncompressed.insert(uncompressed.end(), strLen, 0); - remain -= strLen; - auto src = uncompressed.cend() - strLen + strOffset; - auto dst = uncompressed.end() - strLen; + // Previous string copy is still in progress + uint32 copyLen = std::min(static_cast(strLen), remain); + m_totalRemain -= copyLen; + remain -= copyLen; + strLen -= copyLen; + streamCache.insert(streamCache.end(), copyLen, std::byte{}); + auto src = streamCache.cend() - copyLen + m_strOffset; + auto dst = streamCache.end() - copyLen; do { - strLen--; + copyLen--; *dst++ = *src++; - } while(strLen > 0); + } while(copyLen > 0); } + + uint16 data = m_data; + int8 carry = 0; // x86 carry (used to propagate the most significant bit from one byte to another) + while(remain) + { + READ_CTRL_BIT; + if(!carry) + { + // a 0 ctrl bit means 'copy', not compressed byte + if(std::byte b; file.Read(b)) + streamCache.push_back(b); + else + break; + m_totalRemain--; + remain--; + } else + { + // a 1 ctrl bit means compressed bytes are following + uint8 lengthAdjust = 0; // length adjustment + DECODE_CTRL_BITS; // read length, and if strLen > 3 (coded using more than 1 bits pair) also part of the offset value + strLen -= 3; + if(strLen < 0) + { + // reuse same previous relative LZ ptr (m_strOffset is not re-computed) + strLen++; + } else + { + // LZ ptr in ctrl stream + if(uint8 b; file.Read(b)) + m_strOffset = mpt::lshift_signed(strLen, 8) | b; // read less significant offset byte from stream + else + break; + strLen = 0; + m_strOffset = ~m_strOffset; + if(m_strOffset < -1280) + lengthAdjust++; + lengthAdjust++; // length is always at least 1 + if(m_strOffset < -32000) + lengthAdjust++; + } + if(m_strOffset >= 0 || -static_cast(streamCache.size()) > m_strOffset) + break; + + // read the next 2 bits as part of strLen + READ_CTRL_BIT; + strLen = mpt::lshift_signed(strLen, 1) + carry; + READ_CTRL_BIT; + strLen = mpt::lshift_signed(strLen, 1) + carry; + if(strLen == 0) + { + // length does not fit in 2 bits + DECODE_CTRL_BITS; // decode length: 1 is the most significant bit, + strLen += 2; // then first bit of each bits pairs (noted n1), until n0. + } + strLen += lengthAdjust; // length adjustment + + if(strLen <= 0 || m_totalRemain < static_cast(strLen)) + break; + + // Copy previous string + // Need to do this in two steps (allocate, then copy) as source and destination may overlap (e.g. strOffset = -1, strLen = 2 repeats last character twice) + uint32 copyLen = std::min(static_cast(strLen), remain); + m_totalRemain -= copyLen; + remain -= copyLen; + strLen -= copyLen; + streamCache.insert(streamCache.end(), copyLen, std::byte{}); + auto src = streamCache.cend() - copyLen + m_strOffset; + auto dst = streamCache.end() - copyLen; + do + { + copyLen--; + *dst++ = *src++; + } while(copyLen > 0); + } + } + m_data = data; + m_strLen = strLen; + // Premature EOF or corrupted stream? + if(remain) + m_broken = true; } -#ifdef MPT_BUILD_FUZZER - // When using a fuzzer, we should not care if the decompressed buffer has the correct size. - // This makes finding new interesting test cases much easier. - return true; -#else - return remain == 0; -#endif // MPT_BUILD_FUZZER -} + + bool HasPinnedView() const override + { + return false; + } + + mutable FileReader file; + const uint32 m_suggestedReserveSize; + const uint32 m_targetSize; + mutable bool m_broken = false; + mutable uint16 m_data = 0; + mutable int32 m_strLen = 0; // Length of repeated string + mutable int32 m_strOffset = 0; // Offset of repeated string + mutable uint32 m_totalRemain = 0; +}; struct MO3Delta8BitParams @@ -510,7 +575,7 @@ struct MO3Delta8BitParams do { READ_CTRL_BIT; - val = (val << 1) + carry; + val = static_cast((val << 1) + carry); READ_CTRL_BIT; } while(carry); } @@ -530,9 +595,9 @@ struct MO3Delta16BitParams do { READ_CTRL_BIT; - val = (val << 1) + carry; + val = static_cast((val << 1) + carry); READ_CTRL_BIT; - val = (val << 1) + carry; + val = static_cast((val << 1) + carry); READ_CTRL_BIT; } while(carry); } else @@ -540,7 +605,7 @@ struct MO3Delta16BitParams do { READ_CTRL_BIT; - val = (val << 1) + carry; + val = static_cast((val << 1) + carry); READ_CTRL_BIT; } while(carry); } @@ -569,7 +634,7 @@ static void UnpackMO3DeltaSample(FileReader &file, typename Properties::sample_t while(cl > 0) { READ_CTRL_BIT; - val = (val << 1) + carry; + val = static_cast((val << 1) + carry); cl--; } cl = 1; @@ -585,7 +650,7 @@ static void UnpackMO3DeltaSample(FileReader &file, typename Properties::sample_t val >>= 1; if(carry == 0) val = ~val; // negative delta - val += previous; // previous value + delta + val = static_cast(val + previous); // previous value + delta *p = val; p += numChannels; previous = val; @@ -616,7 +681,7 @@ static void UnpackMO3DeltaPredictionSample(FileReader &file, typename Properties while(cl > 0) { READ_CTRL_BIT; - val = (val << 1) + carry; + val = static_cast((val << 1) + carry); cl--; } cl = 1; @@ -666,11 +731,11 @@ static int VorbisfileFilereaderSeek(void *datasource, ogg_int64_t offset, int wh switch(whence) { case SEEK_SET: - if(!mpt::in_range(offset)) + if(!mpt::in_range(offset)) { return -1; } - return file.Seek(mpt::saturate_cast(offset)) ? 0 : -1; + return file.Seek(mpt::saturate_cast(offset)) ? 0 : -1; case SEEK_CUR: if(offset < 0) @@ -679,31 +744,31 @@ static int VorbisfileFilereaderSeek(void *datasource, ogg_int64_t offset, int wh { return -1; } - if(!mpt::in_range(0 - offset)) + if(!mpt::in_range(0 - offset)) { return -1; } - return file.SkipBack(mpt::saturate_cast(0 - offset)) ? 0 : -1; + return file.SkipBack(mpt::saturate_cast(0 - offset)) ? 0 : -1; } else { - if(!mpt::in_range(offset)) + if(!mpt::in_range(offset)) { return -1; } - return file.Skip(mpt::saturate_cast(offset)) ? 0 : -1; + return file.Skip(mpt::saturate_cast(offset)) ? 0 : -1; } break; case SEEK_END: - if(!mpt::in_range(offset)) + if(!mpt::in_range(offset)) { return -1; } - if(!mpt::in_range(file.GetLength() + offset)) + if(!mpt::in_range(file.GetLength() + offset)) { return -1; } - return file.Seek(mpt::saturate_cast(file.GetLength() + offset)) ? 0 : -1; + return file.Seek(mpt::saturate_cast(file.GetLength() + offset)) ? 0 : -1; default: return -1; @@ -713,7 +778,7 @@ static int VorbisfileFilereaderSeek(void *datasource, ogg_int64_t offset, int wh static long VorbisfileFilereaderTell(void *datasource) { FileReader &file = *mpt::void_ptr(datasource); - FileReader::off_t result = file.GetPosition(); + FileReader::pos_type result = file.GetPosition(); if(!mpt::in_range(result)) { return -1; @@ -726,7 +791,7 @@ static long VorbisfileFilereaderTell(void *datasource) struct MO3ContainerHeader { - char magic[3]; // MO3 + char magic[3]; // MO3 uint8le version; uint32le musicSize; }; @@ -801,54 +866,45 @@ bool CSoundFile::ReadMO3(FileReader &file, ModLoadingFlags loadFlags) reserveSize = std::min(Util::MaxValueOfType(reserveSize) / 32u, compressedSize) * 32u; } - std::vector musicData; - // We don't always reserve the whole uncompressed size as claimed by the module to guard against broken files - // that e.g. claim that the uncompressed size is 1GB while the MO3 file itself is only 100 bytes. - // As the LZ compression used in MO3 doesn't allow for establishing a clear upper bound for the maximum size, - // this is probably the only sensible way we can prevent DoS due to huge allocations. - musicData.reserve(std::min(reserveSize, containerHeader.musicSize.get())); - if(!UnpackMO3Data(file, musicData, containerHeader.musicSize)) - { - return false; - } - if(version >= 5) - { - file.Seek(12 + compressedSize); - } + std::shared_ptr filenamePtr; + if(auto filename = file.GetOptionalFileName(); filename) + filenamePtr = std::make_shared(std::move(*filename)); + auto musicChunkData = std::make_shared(file, containerHeader.musicSize, reserveSize); + mpt::IO::FileCursor> fileCursor{musicChunkData, std::move(filenamePtr)}; + FileReader musicChunk{fileCursor}; - InitializeGlobals(); - InitializeChannels(); - - FileReader musicChunk(mpt::as_span(musicData)); - musicChunk.ReadNullString(m_songName); - musicChunk.ReadNullString(m_songMessage); + std::string songName, songMessage; + musicChunk.ReadNullString(songName); + musicChunk.ReadNullString(songMessage); MO3FileHeader fileHeader; if(!musicChunk.ReadStruct(fileHeader) || fileHeader.numChannels == 0 || fileHeader.numChannels > MAX_BASECHANNELS + || fileHeader.restartPos > fileHeader.numOrders || fileHeader.numInstruments >= MAX_INSTRUMENTS || fileHeader.numSamples >= MAX_SAMPLES) { return false; } - m_nChannels = fileHeader.numChannels; + MODTYPE modType = MOD_TYPE_XM; + if(fileHeader.flags & MO3FileHeader::isIT) + modType = MOD_TYPE_IT; + else if(fileHeader.flags & MO3FileHeader::isS3M) + modType = MOD_TYPE_S3M; + else if(fileHeader.flags & MO3FileHeader::isMOD) + modType = MOD_TYPE_MOD; + else if(fileHeader.flags & MO3FileHeader::isMTM) + modType = MOD_TYPE_MTM; + + InitializeGlobals(modType, fileHeader.numChannels); Order().SetRestartPos(fileHeader.restartPos); m_nInstruments = fileHeader.numInstruments; m_nSamples = fileHeader.numSamples; - m_nDefaultSpeed = fileHeader.defaultSpeed ? fileHeader.defaultSpeed : 6; - m_nDefaultTempo.Set(fileHeader.defaultTempo ? fileHeader.defaultTempo : 125, 0); - - if(fileHeader.flags & MO3FileHeader::isIT) - SetType(MOD_TYPE_IT); - else if(fileHeader.flags & MO3FileHeader::isS3M) - SetType(MOD_TYPE_S3M); - else if(fileHeader.flags & MO3FileHeader::isMOD) - SetType(MOD_TYPE_MOD); - else if(fileHeader.flags & MO3FileHeader::isMTM) - SetType(MOD_TYPE_MTM); - else - SetType(MOD_TYPE_XM); + Order().SetDefaultSpeed(fileHeader.defaultSpeed ? fileHeader.defaultSpeed : 6); + Order().SetDefaultTempoInt(fileHeader.defaultTempo ? fileHeader.defaultTempo : 125); + m_songName = std::move(songName); + m_songMessage.SetRaw(std::move(songMessage)); m_SongFlags.set(SONG_IMPORTED); if(fileHeader.flags & MO3FileHeader::linearSlides) @@ -870,6 +926,8 @@ bool CSoundFile::ReadMO3(FileReader &file, ModLoadingFlags loadFlags) m_nDefaultGlobalVolume = std::min(fileHeader.globalVol.get(), uint8(128)) * 2; else if(m_nType == MOD_TYPE_S3M) m_nDefaultGlobalVolume = std::min(fileHeader.globalVol.get(), uint8(64)) * 4; + else if(m_nType == MOD_TYPE_MOD) + m_SongFlags.set(SONG_FORMAT_NO_VOLCOL); if(fileHeader.sampleVolume < 0) m_nSamplePreAmp = fileHeader.sampleVolume + 52; @@ -877,7 +935,7 @@ bool CSoundFile::ReadMO3(FileReader &file, ModLoadingFlags loadFlags) m_nSamplePreAmp = static_cast(std::exp(fileHeader.sampleVolume * 3.1 / 20.0)) + 51; // Header only has room for 64 channels, like in IT - const CHANNELINDEX headerChannels = std::min(m_nChannels, CHANNELINDEX(64)); + const CHANNELINDEX headerChannels = std::min(GetNumChannels(), CHANNELINDEX(64)); for(CHANNELINDEX i = 0; i < headerChannels; i++) { if(m_nType == MOD_TYPE_IT) @@ -934,6 +992,10 @@ bool CSoundFile::ReadMO3(FileReader &file, ModLoadingFlags loadFlags) for(auto &track : tracks) { uint32 len = musicChunk.ReadUint32LE(); + // A pattern can be at most 65535 rows long, one row can contain at most 15 events (with the status bytes, that 31 bytes per row). + // Leaving some margin for error, that gives us an upper limit of 2MB per track. + if(len >= 0x20'0000) + return false; track = musicChunk.ReadChunk(len); } @@ -1138,7 +1200,7 @@ bool CSoundFile::ReadMO3(FileReader &file, ModLoadingFlags loadFlags) // Pattern break m.SetEffectCommand(CMD_PATTERNBREAK, cmd[1]); if(m_nType != MOD_TYPE_IT) - m.param = ((m.param >> 4) * 10) + (m.param & 0x0F); + m.param = static_cast(((m.param >> 4) * 10) + (m.param & 0x0F)); break; case 0x12: // Combined Tempo / Speed command @@ -1248,7 +1310,8 @@ bool CSoundFile::ReadMO3(FileReader &file, ModLoadingFlags loadFlags) while(musicChunk.ReadUint8() != 0) ; } - musicChunk.Skip(sizeof(MO3Instrument)); + if(!musicChunk.Skip(sizeof(MO3Instrument))) + return false; continue; } @@ -1263,7 +1326,7 @@ bool CSoundFile::ReadMO3(FileReader &file, ModLoadingFlags loadFlags) MO3Instrument insHeader; if(!musicChunk.ReadStruct(insHeader)) - break; + return false; insHeader.ConvertToMPT(*pIns, m_nType); if(m_nType == MOD_TYPE_XM) @@ -1272,10 +1335,8 @@ bool CSoundFile::ReadMO3(FileReader &file, ModLoadingFlags loadFlags) if(isSampleMode) m_nInstruments = 0; - std::vector sampleChunks(m_nSamples); - + std::vector sampleInfos; const bool frequencyIsHertz = (version >= 5 || !(fileHeader.flags & MO3FileHeader::linearSlides)); - bool unsupportedSamples = false; for(SAMPLEINDEX smp = 1; smp <= m_nSamples; smp++) { ModSample &sample = Samples[smp]; @@ -1289,7 +1350,7 @@ bool CSoundFile::ReadMO3(FileReader &file, ModLoadingFlags loadFlags) MO3Sample smpHeader; if(!musicChunk.ReadStruct(smpHeader)) - break; + return false; smpHeader.ConvertToMPT(sample, m_nType, frequencyIsHertz); sample.filename = name; @@ -1299,132 +1360,330 @@ bool CSoundFile::ReadMO3(FileReader &file, ModLoadingFlags loadFlags) sharedOggHeader = musicChunk.ReadInt16LE(); } - if(!(loadFlags & loadSampleData)) - continue; + if(loadFlags & loadSampleData) + { + sampleInfos.reserve(m_nSamples); + sampleInfos.emplace_back(smpHeader, sharedOggHeader); + } + } + if(m_nType == MOD_TYPE_XM) + { + // Transfer XM instrument vibrato to samples + for(INSTRUMENTINDEX ins = 0; ins < m_nInstruments; ins++) + { + PropagateXMAutoVibrato(ins + 1, static_cast(instrVibrato[ins].type.get()), instrVibrato[ins].sweep, instrVibrato[ins].depth, instrVibrato[ins].rate); + } + } + + if((fileHeader.flags & MO3FileHeader::hasPlugins) && musicChunk.CanRead(1)) + { + // Plugin data + uint8 pluginFlags = musicChunk.ReadUint8(); + if(pluginFlags & 1) + { + // Channel plugins + for(auto &chn : ChnSettings) + { + chn.nMixPlugin = static_cast(musicChunk.ReadUint32LE()); + } + } + while(musicChunk.CanRead(1)) + { + PLUGINDEX plug = musicChunk.ReadUint8(); + if(!plug) + break; + uint32 len = musicChunk.ReadUint32LE(); + if(len >= containerHeader.musicSize || containerHeader.musicSize - len < musicChunk.GetPosition()) + return false; + FileReader pluginChunk = musicChunk.ReadChunk(len); +#ifndef NO_PLUGINS + if(plug <= MAX_MIXPLUGINS) + { + ReadMixPluginChunk(pluginChunk, m_MixPlugins[plug - 1]); + } +#endif // NO_PLUGINS + } + } + + mpt::ustring madeWithTracker; + uint16 cwtv = 0; + uint16 cmwt = 0; + while(musicChunk.CanRead(8)) + { + uint32 id = musicChunk.ReadUint32LE(); + uint32 len = musicChunk.ReadUint32LE(); + if(len >= containerHeader.musicSize || containerHeader.musicSize - len < musicChunk.GetPosition()) + return false; + FileReader chunk = musicChunk.ReadChunk(len); + switch(id) + { + case MagicLE("VERS"): + // Tracker magic bytes (depending on format) + switch(m_nType) + { + case MOD_TYPE_IT: + cwtv = chunk.ReadUint16LE(); + cmwt = chunk.ReadUint16LE(); + /*switch(cwtv >> 12) + { + + }*/ + break; + case MOD_TYPE_S3M: + cwtv = chunk.ReadUint16LE(); + break; + case MOD_TYPE_XM: + chunk.ReadString(madeWithTracker, mpt::Charset::CP437, std::min(FileReader::pos_type(32), chunk.GetLength())); + break; + case MOD_TYPE_MTM: + { + uint8 mtmVersion = chunk.ReadUint8(); + madeWithTracker = MPT_UFORMAT("MultiTracker {}.{}")(mtmVersion >> 4, mtmVersion & 0x0F); + } + break; + default: + break; + } + break; + case MagicLE("PRHI"): + m_nDefaultRowsPerBeat = chunk.ReadUint8(); + m_nDefaultRowsPerMeasure = chunk.ReadUint8(); + break; + case MagicLE("MIDI"): + // Full MIDI config + chunk.ReadStruct(m_MidiCfg); + m_MidiCfg.Sanitize(); + break; + case MagicLE("OMPT"): + // Read pattern names: "PNAM" + if(chunk.ReadMagic("PNAM")) + { + FileReader patterns = chunk.ReadChunk(chunk.ReadUint32LE()); + const PATTERNINDEX namedPats = std::min(static_cast(patterns.GetLength() / MAX_PATTERNNAME), Patterns.Size()); + + for(PATTERNINDEX pat = 0; pat < namedPats; pat++) + { + char patName[MAX_PATTERNNAME]; + patterns.ReadString(patName, MAX_PATTERNNAME); + Patterns[pat].SetName(patName); + } + } + + // Read channel names: "CNAM" + if(chunk.ReadMagic("CNAM")) + { + FileReader channels = chunk.ReadChunk(chunk.ReadUint32LE()); + const CHANNELINDEX namedChans = std::min(static_cast(channels.GetLength() / MAX_CHANNELNAME), GetNumChannels()); + for(CHANNELINDEX chn = 0; chn < namedChans; chn++) + { + channels.ReadString(ChnSettings[chn].szName, MAX_CHANNELNAME); + } + } + + LoadExtendedInstrumentProperties(chunk); + LoadExtendedSongProperties(chunk, true); + if(cwtv > 0x0889 && cwtv <= 0x8FF) + { + m_nType = MOD_TYPE_MPT; + LoadMPTMProperties(chunk, cwtv); + } + + if(m_dwLastSavedWithVersion) + { + madeWithTracker = UL_("OpenMPT ") + mpt::ufmt::val(m_dwLastSavedWithVersion); + } + break; + } + } + + if((GetType() == MOD_TYPE_IT && cwtv >= 0x0100 && cwtv < 0x0214) + || (GetType() == MOD_TYPE_S3M && cwtv >= 0x3100 && cwtv < 0x3214) + || (GetType() == MOD_TYPE_S3M && cwtv >= 0x1300 && cwtv < 0x1320)) + { + // Ignore MIDI data in files made with IT older than version 2.14 and old ST3 versions. + m_MidiCfg.ClearZxxMacros(); + } + + if(fileHeader.flags & MO3FileHeader::modplugMode) + { + // Apply some old ModPlug (mis-)behaviour + if(!m_dwLastSavedWithVersion) + { + // These fixes are only applied when the OpenMPT version number is not known, as otherwise the song upgrade feature will take care of it. + for(INSTRUMENTINDEX i = 1; i <= GetNumInstruments(); i++) + { + if(ModInstrument *ins = Instruments[i]) + { + // Fix pitch / filter envelope being shortened by one tick (for files before v1.20) + ins->GetEnvelope(ENV_PITCH).Convert(MOD_TYPE_XM, GetType()); + // Fix excessive pan swing range (for files before v1.26) + ins->nPanSwing = static_cast((ins->nPanSwing + 3) / 4u); + } + } + } + if(m_dwLastSavedWithVersion < MPT_V("1.18.00.00")) + { + m_playBehaviour.reset(kITOffset); + m_playBehaviour.reset(kFT2ST3OffsetOutOfRange); + } + if(m_dwLastSavedWithVersion < MPT_V("1.23.00.00")) + m_playBehaviour.reset(kFT2Periods); + if(m_dwLastSavedWithVersion < MPT_V("1.26.00.00")) + m_playBehaviour.reset(kITInstrWithNoteOff); + } + + if(madeWithTracker.empty()) + madeWithTracker = MPT_UFORMAT("MO3 v{}")(version); + else + madeWithTracker = MPT_UFORMAT("MO3 v{} ({})")(version, madeWithTracker); + + m_modFormat.formatName = MPT_UFORMAT("Un4seen MO3 v{}")(version); + m_modFormat.type = UL_("mo3"); + + switch(GetType()) + { + case MOD_TYPE_MTM: + m_modFormat.originalType = UL_("mtm"); + m_modFormat.originalFormatName = UL_("MultiTracker"); + break; + case MOD_TYPE_MOD: + m_modFormat.originalType = UL_("mod"); + m_modFormat.originalFormatName = UL_("Generic MOD"); + break; + case MOD_TYPE_XM: + m_modFormat.originalType = UL_("xm"); + m_modFormat.originalFormatName = UL_("FastTracker 2"); + break; + case MOD_TYPE_S3M: + m_modFormat.originalType = UL_("s3m"); + m_modFormat.originalFormatName = UL_("Scream Tracker 3"); + break; + case MOD_TYPE_IT: + m_modFormat.originalType = UL_("it"); + if(cmwt) + m_modFormat.originalFormatName = MPT_UFORMAT("Impulse Tracker {}.{}")(cmwt >> 8, mpt::ufmt::hex0<2>(cmwt & 0xFF)); + else + m_modFormat.originalFormatName = UL_("Impulse Tracker"); + break; + case MOD_TYPE_MPT: + m_modFormat.originalType = UL_("mptm"); + m_modFormat.originalFormatName = UL_("OpenMPT MPTM"); + break; + default: + MPT_ASSERT_NOTREACHED(); + } + m_modFormat.madeWithTracker = std::move(madeWithTracker); + if(m_dwLastSavedWithVersion) + m_modFormat.charset = mpt::Charset::Windows1252; + else if(GetType() == MOD_TYPE_MOD) + m_modFormat.charset = mpt::Charset::Amiga_no_C1; + else + m_modFormat.charset = mpt::Charset::CP437; + + if(!(loadFlags & loadSampleData)) + return true; + + if(containerHeader.version < 5) + { + // As we don't know where the compressed data ends, we don't know where the sample data starts, either. + if(!musicChunkData->UnpackedSuccessfully()) + return false; + file.Seek(musicChunkData->SourcePosition()); + } else + { + file.Seek(12 + compressedSize); + } + + bool unsupportedSamples = false; + for(SAMPLEINDEX smp = 1; smp <= m_nSamples; smp++) + { + MO3SampleInfo &smpInfo = sampleInfos[smp - 1]; + const MO3Sample &smpHeader = smpInfo.smpHeader; const uint32 compression = (smpHeader.flags & MO3Sample::smpCompressionMask); + if(!compression && smpHeader.compressedSize == 0) { // Uncompressed sample SampleIO( - (smpHeader.flags & MO3Sample::smp16Bit) ? SampleIO::_16bit : SampleIO::_8bit, - (smpHeader.flags & MO3Sample::smpStereo) ? SampleIO::stereoSplit : SampleIO::mono, - SampleIO::littleEndian, - SampleIO::signedPCM) - .ReadSample(Samples[smp], file); - } else if(smpHeader.compressedSize < 0 && (smp + smpHeader.compressedSize) > 0) - { - // Duplicate sample - sample.CopyWaveform(Samples[smp + smpHeader.compressedSize]); + (smpHeader.flags & MO3Sample::smp16Bit) ? SampleIO::_16bit : SampleIO::_8bit, + (smpHeader.flags & MO3Sample::smpStereo) ? SampleIO::stereoSplit : SampleIO::mono, + SampleIO::littleEndian, + SampleIO::signedPCM) + .ReadSample(Samples[smp], file); } else if(smpHeader.compressedSize > 0) { - if(smpHeader.flags & MO3Sample::smp16Bit) - sample.uFlags.set(CHN_16BIT); - if(smpHeader.flags & MO3Sample::smpStereo) - sample.uFlags.set(CHN_STEREO); - - FileReader sampleData = file.ReadChunk(smpHeader.compressedSize); - if(!smpHeader.length) - continue; - const uint8 numChannels = sample.GetNumChannels(); - - if(compression == MO3Sample::smpDeltaCompression || compression == MO3Sample::smpDeltaPrediction) - { - // In the best case, MO3 compression represents each sample point as two bits. - // As a result, if we have a file length of n, we know that the sample can be at most n*4 sample points long. - auto maxLength = sampleData.GetLength(); - uint8 maxSamplesPerByte = 4 / numChannels; - if(Util::MaxValueOfType(maxLength) / maxSamplesPerByte >= maxLength) - maxLength *= maxSamplesPerByte; - else - maxLength = Util::MaxValueOfType(maxLength); - LimitMax(sample.nLength, mpt::saturate_cast(maxLength)); - } - - if(compression == MO3Sample::smpDeltaCompression) - { - if(sample.AllocateSample()) - { - if(smpHeader.flags & MO3Sample::smp16Bit) - UnpackMO3DeltaSample(sampleData, sample.sample16(), sample.nLength, numChannels); - else - UnpackMO3DeltaSample(sampleData, sample.sample8(), sample.nLength, numChannels); - } - } else if(compression == MO3Sample::smpDeltaPrediction) - { - if(sample.AllocateSample()) - { - if(smpHeader.flags & MO3Sample::smp16Bit) - UnpackMO3DeltaPredictionSample(sampleData, sample.sample16(), sample.nLength, numChannels); - else - UnpackMO3DeltaPredictionSample(sampleData, sample.sample8(), sample.nLength, numChannels); - } - } else if(compression == MO3Sample::smpCompressionOgg || compression == MO3Sample::smpSharedOgg) - { - // Since shared Ogg headers can stem from a sample that has not been read yet, postpone Ogg import. - sampleChunks[smp - 1] = MO3SampleChunk(sampleData, smpHeader.encoderDelay, sharedOggHeader); - } else if(compression == MO3Sample::smpCompressionMPEG) - { - // Old MO3 encoders didn't remove LAME info frames. This is unfortunate since the encoder delay - // specified in the sample header does not take the gapless information from the LAME info frame - // into account. We should not depend on the MP3 decoder's capabilities to read or ignore such frames: - // - libmpg123 has MPG123_IGNORE_INFOFRAME but that requires API version 31 (mpg123 v1.14) or higher - // - Media Foundation does (currently) not read LAME gapless information at all - // So we just play safe and remove such frames. - FileReader mpegData(sampleData); - MPEGFrame frame(sampleData); - uint16 frameDelay = frame.numSamples * 2; - if(frame.isLAME && smpHeader.encoderDelay >= frameDelay) - { - // The info frame does not produce any output, but still counts towards the encoder delay. - smpHeader.encoderDelay -= frameDelay; - sampleData.Seek(frame.frameSize); - mpegData = sampleData.ReadChunk(sampleData.BytesLeft()); - } - - if(ReadMP3Sample(smp, mpegData, true, true) || ReadMediaFoundationSample(smp, mpegData, true)) - { - if(smpHeader.encoderDelay > 0 && smpHeader.encoderDelay < sample.GetSampleSizeInBytes()) - { - SmpLength delay = smpHeader.encoderDelay / sample.GetBytesPerSample(); - memmove(sample.sampleb(), sample.sampleb() + smpHeader.encoderDelay, sample.GetSampleSizeInBytes() - smpHeader.encoderDelay); - sample.nLength -= delay; - } - LimitMax(sample.nLength, smpHeader.length); - } else - { - unsupportedSamples = true; - } - } else if(compression == MO3Sample::smpOPLInstrument) - { - OPLPatch patch; - if(sampleData.ReadArray(patch)) - { - sample.SetAdlib(true, patch); - } - } else - { - unsupportedSamples = true; - } + // Compressed sample; we read those in a second pass because Ogg samples with shared headers may reference a later sample's header + smpInfo.chunk = file.ReadChunk(smpHeader.compressedSize); } } - // Now we can load Ogg samples with shared headers. - if(loadFlags & loadSampleData) + for(SAMPLEINDEX smp = 1; smp <= m_nSamples; smp++) { - for(SAMPLEINDEX smp = 1; smp <= m_nSamples; smp++) - { - MO3SampleChunk &sampleChunk = sampleChunks[smp - 1]; - // Is this an Ogg sample? - if(!sampleChunk.chunk.IsValid()) - continue; + ModSample &sample = Samples[smp]; + MO3SampleInfo &smpInfo = sampleInfos[smp - 1]; + const MO3Sample &smpHeader = smpInfo.smpHeader; - SAMPLEINDEX sharedOggHeader = (smp + sampleChunk.sharedHeader > 0) ? static_cast(smp + sampleChunk.sharedHeader) : smp; + if(smpHeader.compressedSize < 0 && (smp + smpHeader.compressedSize) > 0) + { + // Duplicate sample + sample.CopyWaveform(Samples[smp + smpHeader.compressedSize]); + continue; + } + + // Not a compressed sample? + if(!smpHeader.length || !smpInfo.chunk.IsValid()) + continue; + + if(smpHeader.flags & MO3Sample::smp16Bit) + sample.uFlags.set(CHN_16BIT); + if(smpHeader.flags & MO3Sample::smpStereo) + sample.uFlags.set(CHN_STEREO); + + FileReader &sampleData = smpInfo.chunk; + const uint8 numChannels = sample.GetNumChannels(); + const uint32 compression = (smpHeader.flags & MO3Sample::smpCompressionMask); + + if(compression == MO3Sample::smpDeltaCompression || compression == MO3Sample::smpDeltaPrediction) + { + // In the best case, MO3 compression represents each sample point as two bits. + // As a result, if we have a file length of n, we know that the sample can be at most n*4 sample points long. + auto maxLength = sampleData.GetLength(); + uint8 maxSamplesPerByte = 4 / numChannels; + if(Util::MaxValueOfType(maxLength) / maxSamplesPerByte >= maxLength) + maxLength *= maxSamplesPerByte; + else + maxLength = Util::MaxValueOfType(maxLength); + LimitMax(sample.nLength, mpt::saturate_cast(maxLength)); + } + + if(compression == MO3Sample::smpDeltaCompression) + { + if(sample.AllocateSample()) + { + if(smpHeader.flags & MO3Sample::smp16Bit) + UnpackMO3DeltaSample(sampleData, sample.sample16(), sample.nLength, numChannels); + else + UnpackMO3DeltaSample(sampleData, sample.sample8(), sample.nLength, numChannels); + } + } else if(compression == MO3Sample::smpDeltaPrediction) + { + if(sample.AllocateSample()) + { + if(smpHeader.flags & MO3Sample::smp16Bit) + UnpackMO3DeltaPredictionSample(sampleData, sample.sample16(), sample.nLength, numChannels); + else + UnpackMO3DeltaPredictionSample(sampleData, sample.sample8(), sample.nLength, numChannels); + } + } else if(compression == MO3Sample::smpCompressionOgg || compression == MO3Sample::smpSharedOgg) + { + const uint16 sharedHeaderSize = smpHeader.encoderDelay; + SAMPLEINDEX sharedOggHeader = (smp + smpInfo.sharedHeader > 0) ? static_cast(smp + smpInfo.sharedHeader) : smp; // Which chunk are we going to read the header from? // Note: Every Ogg stream has a unique serial number. // stb_vorbis (currently) ignores this serial number so we can just stitch // together our sample without adjusting the shared header's serial number. - const bool sharedHeader = sharedOggHeader != smp && sharedOggHeader > 0 && sharedOggHeader <= m_nSamples && sampleChunk.headerSize > 0; + const bool sharedHeader = sharedOggHeader != smp && sharedOggHeader > 0 && sharedOggHeader <= m_nSamples && sharedHeaderSize > 0; #if defined(MPT_WITH_VORBIS) && defined(MPT_WITH_VORBISFILE) @@ -1451,8 +1710,8 @@ bool CSoundFile::ReadMO3(FileReader &file, ModLoadingFlags loadFlags) std::ostringstream mergedStream(std::ios::binary); mergedStream.imbue(std::locale::classic()); - sampleChunks[sharedOggHeader - 1].chunk.Rewind(); - FileReader sharedChunk = sampleChunks[sharedOggHeader - 1].chunk.ReadChunk(sampleChunk.headerSize); + sampleInfos[sharedOggHeader - 1].chunk.Rewind(); + FileReader sharedChunk = sampleInfos[sharedOggHeader - 1].chunk.ReadChunk(sharedHeaderSize); sharedChunk.Rewind(); std::vector streamSerials; @@ -1475,7 +1734,7 @@ bool CSoundFile::ReadMO3(FileReader &file, ModLoadingFlags loadFlags) } streamSerials.clear(); - while(Ogg::ReadPageAndSkipJunk(sampleChunk.chunk, oggPageInfo, oggPageData)) + while(Ogg::ReadPageAndSkipJunk(smpInfo.chunk, oggPageInfo, oggPageData)) { auto it = std::find(streamSerials.begin(), streamSerials.end(), oggPageInfo.header.bitstream_serial_number); if(it == streamSerials.end()) @@ -1500,8 +1759,8 @@ bool CSoundFile::ReadMO3(FileReader &file, ModLoadingFlags loadFlags) std::ostringstream mergedStream(std::ios::binary); mergedStream.imbue(std::locale::classic()); - sampleChunks[sharedOggHeader - 1].chunk.Rewind(); - FileReader sharedChunk = sampleChunks[sharedOggHeader - 1].chunk.ReadChunk(sampleChunk.headerSize); + sampleInfos[sharedOggHeader - 1].chunk.Rewind(); + FileReader sharedChunk = sampleInfos[sharedOggHeader - 1].chunk.ReadChunk(sharedHeaderSize); sharedChunk.Rewind(); std::vector dataStreamSerials; @@ -1511,7 +1770,7 @@ bool CSoundFile::ReadMO3(FileReader &file, ModLoadingFlags loadFlags) // Gather bitstream serial numbers form sample data chunk dataStreamSerials.clear(); - while(Ogg::ReadPageAndSkipJunk(sampleChunk.chunk, oggPageInfo, oggPageData)) + while(Ogg::ReadPageAndSkipJunk(smpInfo.chunk, oggPageInfo, oggPageData)) { if(!mpt::contains(dataStreamSerials, oggPageInfo.header.bitstream_serial_number)) { @@ -1569,8 +1828,8 @@ bool CSoundFile::ReadMO3(FileReader &file, ModLoadingFlags loadFlags) std::string mergedStreamData = mergedStream.str(); mergedData.insert(mergedData.end(), mergedStreamData.begin(), mergedStreamData.end()); - sampleChunk.chunk.Rewind(); - FileReader::PinnedView sampleChunkView = sampleChunk.chunk.GetPinnedView(); + smpInfo.chunk.Rewind(); + FileReader::PinnedView sampleChunkView = smpInfo.chunk.GetPinnedView(); mpt::span sampleChunkViewSpan = mpt::byte_cast>(sampleChunkView.span()); mergedData.insert(mergedData.end(), sampleChunkViewSpan.begin(), sampleChunkViewSpan.end()); @@ -1578,21 +1837,20 @@ bool CSoundFile::ReadMO3(FileReader &file, ModLoadingFlags loadFlags) } FileReader mergedDataChunk(mpt::byte_cast(mpt::as_span(mergedData))); - FileReader &sampleData = sharedHeader ? mergedDataChunk : sampleChunk.chunk; - FileReader &headerChunk = sampleData; + FileReader &sampleChunk = sharedHeader ? mergedDataChunk : smpInfo.chunk; + FileReader &headerChunk = sampleChunk; #else // !(MPT_WITH_VORBIS && MPT_WITH_VORBISFILE) - FileReader &sampleData = sampleChunk.chunk; - FileReader &headerChunk = sharedHeader ? sampleChunks[sharedOggHeader - 1].chunk : sampleData; + FileReader &headerChunk = sharedHeader ? sampleInfos[sharedOggHeader - 1].chunk : sampleData; #if defined(MPT_WITH_STBVORBIS) - std::size_t initialRead = sharedHeader ? sampleChunk.headerSize : headerChunk.GetLength(); + std::size_t initialRead = sharedHeader ? sharedHeaderSize : headerChunk.GetLength(); #endif // MPT_WITH_STBVORBIS #endif // MPT_WITH_VORBIS && MPT_WITH_VORBISFILE headerChunk.Rewind(); - if(sharedHeader && !headerChunk.CanRead(sampleChunk.headerSize)) + if(sharedHeader && !headerChunk.CanRead(sharedHeaderSize)) continue; #if defined(MPT_WITH_VORBIS) && defined(MPT_WITH_VORBISFILE) @@ -1604,14 +1862,13 @@ bool CSoundFile::ReadMO3(FileReader &file, ModLoadingFlags loadFlags) &VorbisfileFilereaderTell}; OggVorbis_File vf; MemsetZero(vf); - if(ov_open_callbacks(mpt::void_ptr(&sampleData), &vf, nullptr, 0, callbacks) == 0) + if(ov_open_callbacks(mpt::void_ptr(&sampleChunk), &vf, nullptr, 0, callbacks) == 0) { if(ov_streams(&vf) == 1) { // we do not support chained vorbis samples vorbis_info *vi = ov_info(&vf, -1); if(vi && vi->rate > 0 && vi->channels > 0) { - ModSample &sample = Samples[smp]; sample.AllocateSample(); SmpLength offset = 0; int channels = vi->channels; @@ -1642,7 +1899,7 @@ bool CSoundFile::ReadMO3(FileReader &file, ModLoadingFlags loadFlags) CopyAudio(mpt::audio_span_interleaved(sample.sample8() + (offset * sample.GetNumChannels()), sample.GetNumChannels(), decodedSamples), mpt::audio_span_planar(output, channels, decodedSamples)); } } - offset += decodedSamples; + offset += static_cast(decodedSamples); } } } else @@ -1691,7 +1948,6 @@ bool CSoundFile::ReadMO3(FileReader &file, ModLoadingFlags loadFlags) if(vorb) { // Header has been read, proceed to reading the sample data - ModSample &sample = Samples[smp]; sample.AllocateSample(); SmpLength offset = 0; while((error == VORBIS__no_error || (error == VORBIS_need_more_data && dataLeft > 0)) @@ -1728,218 +1984,52 @@ bool CSoundFile::ReadMO3(FileReader &file, ModLoadingFlags loadFlags) unsupportedSamples = true; #endif // VORBIS - } - } - - if(m_nType == MOD_TYPE_XM) - { - // Transfer XM instrument vibrato to samples - for(INSTRUMENTINDEX ins = 0; ins < m_nInstruments; ins++) + } else if(compression == MO3Sample::smpCompressionMPEG) { - PropagateXMAutoVibrato(ins + 1, static_cast(instrVibrato[ins].type.get()), instrVibrato[ins].sweep, instrVibrato[ins].depth, instrVibrato[ins].rate); - } - } - - if((fileHeader.flags & MO3FileHeader::hasPlugins) && musicChunk.CanRead(1)) - { - // Plugin data - uint8 pluginFlags = musicChunk.ReadUint8(); - if(pluginFlags & 1) - { - // Channel plugins - for(CHANNELINDEX chn = 0; chn < m_nChannels; chn++) + // Old MO3 encoders didn't remove LAME info frames. This is unfortunate since the encoder delay + // specified in the sample header does not take the gapless information from the LAME info frame + // into account. We should not depend on the MP3 decoder's capabilities to read or ignore such frames: + // - libmpg123 has MPG123_IGNORE_INFOFRAME but that requires API version 31 (mpg123 v1.14) or higher + // - Media Foundation does (currently) not read LAME gapless information at all + // So we just play safe and remove such frames. + FileReader mpegData(sampleData); + MPEGFrame frame(sampleData); + uint16 encoderDelay = smpHeader.encoderDelay; + uint16 frameDelay = frame.numSamples * 2; + if(frame.isLAME && encoderDelay >= frameDelay) { - ChnSettings[chn].nMixPlugin = static_cast(musicChunk.ReadUint32LE()); + // The info frame does not produce any output, but still counts towards the encoder delay. + encoderDelay -= frameDelay; + sampleData.Seek(frame.frameSize); + mpegData = sampleData.ReadChunk(sampleData.BytesLeft()); } - } - while(musicChunk.CanRead(1)) - { - PLUGINDEX plug = musicChunk.ReadUint8(); - if(!plug) - break; - FileReader pluginChunk = musicChunk.ReadChunk(musicChunk.ReadUint32LE()); -#ifndef NO_PLUGINS - if(plug <= MAX_MIXPLUGINS) - { - ReadMixPluginChunk(pluginChunk, m_MixPlugins[plug - 1]); - } -#endif // NO_PLUGINS - } - } - mpt::ustring madeWithTracker; - uint16 cwtv = 0; - uint16 cmwt = 0; - while(musicChunk.CanRead(8)) - { - uint32 id = musicChunk.ReadUint32LE(); - uint32 len = musicChunk.ReadUint32LE(); - FileReader chunk = musicChunk.ReadChunk(len); - switch(id) - { - case MagicLE("VERS"): - // Tracker magic bytes (depending on format) - switch(m_nType) + if(ReadMP3Sample(smp, mpegData, true, true) || ReadMediaFoundationSample(smp, mpegData, true)) { - case MOD_TYPE_IT: - cwtv = chunk.ReadUint16LE(); - cmwt = chunk.ReadUint16LE(); - /*switch(cwtv >> 12) + if(encoderDelay > 0 && encoderDelay < sample.GetSampleSizeInBytes()) { - - }*/ - break; - case MOD_TYPE_S3M: - cwtv = chunk.ReadUint16LE(); - break; - case MOD_TYPE_XM: - chunk.ReadString(madeWithTracker, mpt::Charset::CP437, std::min(FileReader::off_t(32), chunk.GetLength())); - break; - case MOD_TYPE_MTM: - { - uint8 mtmVersion = chunk.ReadUint8(); - madeWithTracker = MPT_UFORMAT("MultiTracker {}.{}")(mtmVersion >> 4, mtmVersion & 0x0F); - } - break; - default: - break; - } - break; - case MagicLE("PRHI"): - m_nDefaultRowsPerBeat = chunk.ReadUint8(); - m_nDefaultRowsPerMeasure = chunk.ReadUint8(); - break; - case MagicLE("MIDI"): - // Full MIDI config - chunk.ReadStruct(m_MidiCfg); - m_MidiCfg.Sanitize(); - break; - case MagicLE("OMPT"): - // Read pattern names: "PNAM" - if(chunk.ReadMagic("PNAM")) - { - FileReader patterns = chunk.ReadChunk(chunk.ReadUint32LE()); - const PATTERNINDEX namedPats = std::min(static_cast(patterns.GetLength() / MAX_PATTERNNAME), Patterns.Size()); - - for(PATTERNINDEX pat = 0; pat < namedPats; pat++) - { - char patName[MAX_PATTERNNAME]; - patterns.ReadString(patName, MAX_PATTERNNAME); - Patterns[pat].SetName(patName); + SmpLength delay = encoderDelay / sample.GetBytesPerSample(); + memmove(sample.sampleb(), sample.sampleb() + encoderDelay, sample.GetSampleSizeInBytes() - encoderDelay); + sample.nLength -= delay; } - } - - // Read channel names: "CNAM" - if(chunk.ReadMagic("CNAM")) + LimitMax(sample.nLength, smpHeader.length); + } else { - FileReader channels = chunk.ReadChunk(chunk.ReadUint32LE()); - const CHANNELINDEX namedChans = std::min(static_cast(channels.GetLength() / MAX_CHANNELNAME), GetNumChannels()); - for(CHANNELINDEX chn = 0; chn < namedChans; chn++) - { - channels.ReadString(ChnSettings[chn].szName, MAX_CHANNELNAME); - } + unsupportedSamples = true; } - - LoadExtendedInstrumentProperties(chunk); - LoadExtendedSongProperties(chunk, true); - if(cwtv > 0x0889 && cwtv <= 0x8FF) - { - m_nType = MOD_TYPE_MPT; - LoadMPTMProperties(chunk, cwtv); - } - - if(m_dwLastSavedWithVersion) - { - madeWithTracker = U_("OpenMPT ") + mpt::ufmt::val(m_dwLastSavedWithVersion); - } - break; - } - } - - if((GetType() == MOD_TYPE_IT && cwtv >= 0x0100 && cwtv < 0x0214) - || (GetType() == MOD_TYPE_S3M && cwtv >= 0x3100 && cwtv < 0x3214) - || (GetType() == MOD_TYPE_S3M && cwtv >= 0x1300 && cwtv < 0x1320)) - { - // Ignore MIDI data in files made with IT older than version 2.14 and old ST3 versions. - m_MidiCfg.ClearZxxMacros(); - } - - if(fileHeader.flags & MO3FileHeader::modplugMode) - { - // Apply some old ModPlug (mis-)behaviour - if(!m_dwLastSavedWithVersion) + } else if(compression == MO3Sample::smpOPLInstrument) { - // These fixes are only applied when the OpenMPT version number is not known, as otherwise the song upgrade feature will take care of it. - for(INSTRUMENTINDEX i = 1; i <= GetNumInstruments(); i++) + OPLPatch patch; + if(sampleData.ReadArray(patch)) { - if(ModInstrument *ins = Instruments[i]) - { - // Fix pitch / filter envelope being shortened by one tick (for files before v1.20) - ins->GetEnvelope(ENV_PITCH).Convert(MOD_TYPE_XM, GetType()); - // Fix excessive pan swing range (for files before v1.26) - ins->nPanSwing = (ins->nPanSwing + 3) / 4u; - } + sample.SetAdlib(true, patch); } - } - if(m_dwLastSavedWithVersion < MPT_V("1.18.00.00")) + } else { - m_playBehaviour.reset(kITOffset); - m_playBehaviour.reset(kFT2ST3OffsetOutOfRange); + unsupportedSamples = true; } - if(m_dwLastSavedWithVersion < MPT_V("1.23.00.00")) - m_playBehaviour.reset(kFT2Periods); - if(m_dwLastSavedWithVersion < MPT_V("1.26.00.00")) - m_playBehaviour.reset(kITInstrWithNoteOff); } - if(madeWithTracker.empty()) - madeWithTracker = MPT_UFORMAT("MO3 v{}")(version); - else - madeWithTracker = MPT_UFORMAT("MO3 v{} ({})")(version, madeWithTracker); - - m_modFormat.formatName = MPT_UFORMAT("Un4seen MO3 v{}")(version); - m_modFormat.type = U_("mo3"); - - switch(GetType()) - { - case MOD_TYPE_MTM: - m_modFormat.originalType = U_("mtm"); - m_modFormat.originalFormatName = U_("MultiTracker"); - break; - case MOD_TYPE_MOD: - m_modFormat.originalType = U_("mod"); - m_modFormat.originalFormatName = U_("Generic MOD"); - break; - case MOD_TYPE_XM: - m_modFormat.originalType = U_("xm"); - m_modFormat.originalFormatName = U_("FastTracker 2"); - break; - case MOD_TYPE_S3M: - m_modFormat.originalType = U_("s3m"); - m_modFormat.originalFormatName = U_("Scream Tracker 3"); - break; - case MOD_TYPE_IT: - m_modFormat.originalType = U_("it"); - if(cmwt) - m_modFormat.originalFormatName = MPT_UFORMAT("Impulse Tracker {}.{}")(cmwt >> 8, mpt::ufmt::hex0<2>(cmwt & 0xFF)); - else - m_modFormat.originalFormatName = U_("Impulse Tracker"); - break; - case MOD_TYPE_MPT: - m_modFormat.originalType = U_("mptm"); - m_modFormat.originalFormatName = U_("OpenMPT MPTM"); - break; - default: - MPT_ASSERT_NOTREACHED(); - } - m_modFormat.madeWithTracker = std::move(madeWithTracker); - if(m_dwLastSavedWithVersion) - m_modFormat.charset = mpt::Charset::Windows1252; - else if(GetType() == MOD_TYPE_MOD) - m_modFormat.charset = mpt::Charset::Amiga_no_C1; - else - m_modFormat.charset = mpt::Charset::CP437; - if(unsupportedSamples) { AddToLog(LogWarning, U_("Some compressed samples could not be loaded because they use an unsupported codec.")); diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_mod.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_mod.cpp index e5c562f7b..59f1888be 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_mod.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_mod.cpp @@ -1,9 +1,10 @@ /* * Load_mod.cpp * ------------ - * Purpose: MOD / NST (ProTracker / NoiseTracker), M15 / STK (Ultimate Soundtracker / Soundtracker) and ST26 (SoundTracker 2.6 / Ice Tracker) module loader / saver - * Notes : "2000 LOC for processing MOD files?!" you say? Well, this file also contains loaders for some formats that are almost identical to MOD, and extensive - * heuristics for more or less broken MOD files and files saved with tons of different trackers, to allow for the most optimal playback. + * Purpose: MOD / NST (ProTracker / NoiseTracker / Startrekker) module loader / saver + * Notes : "1100 LOC for processing MOD files?!" you say? + * Well, extensive heuristics for more or less broken MOD files and files saved with tons of different trackers, to allow for the most optimal playback, + * do take up some space... and then there's also Startrekker synthesized instruments support, of course. It all adds up. * Authors: Olivier Lapicque * OpenMPT Devs * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. @@ -12,6 +13,7 @@ #include "stdafx.h" #include "Loaders.h" +#include "MODTools.h" #include "Tables.h" #ifndef MODPLUG_NO_FILESAVE #include "mpt/io/base.hpp" @@ -29,330 +31,6 @@ OPENMPT_NAMESPACE_BEGIN -void CSoundFile::ConvertModCommand(ModCommand &m, const uint8 command, const uint8 param) -{ - m.param = param; - switch(command) - { - case 0x00: m.command = m.param ? CMD_ARPEGGIO : CMD_NONE; break; - case 0x01: m.command = CMD_PORTAMENTOUP; break; - case 0x02: m.command = CMD_PORTAMENTODOWN; break; - case 0x03: m.command = CMD_TONEPORTAMENTO; break; - case 0x04: m.command = CMD_VIBRATO; break; - case 0x05: m.command = CMD_TONEPORTAVOL; break; - case 0x06: m.command = CMD_VIBRATOVOL; break; - case 0x07: m.command = CMD_TREMOLO; break; - case 0x08: m.command = CMD_PANNING8; break; - case 0x09: m.command = CMD_OFFSET; break; - case 0x0A: m.command = CMD_VOLUMESLIDE; break; - case 0x0B: m.command = CMD_POSITIONJUMP; break; - case 0x0C: m.command = CMD_VOLUME; break; - case 0x0D: m.command = CMD_PATTERNBREAK; m.param = ((m.param >> 4) * 10) + (m.param & 0x0F); break; - case 0x0E: m.command = CMD_MODCMDEX; break; - case 0x0F: - // For a very long time, this code imported 0x20 as CMD_SPEED for MOD files, but this seems to contradict - // pretty much the majority of other MOD player out there. - // 0x20 is Speed: Impulse Tracker, Scream Tracker, old ModPlug - // 0x20 is Tempo: ProTracker, XMPlay, Imago Orpheus, Cubic Player, ChibiTracker, BeRoTracker, DigiTrakker, DigiTrekker, Disorder Tracker 2, DMP, Extreme's Tracker, ... - if(m.param < 0x20) - m.command = CMD_SPEED; - else - m.command = CMD_TEMPO; - break; - - // Extension for XM extended effects - case 'G' - 55: m.command = CMD_GLOBALVOLUME; break; //16 - case 'H' - 55: m.command = CMD_GLOBALVOLSLIDE; break; - case 'K' - 55: m.command = CMD_KEYOFF; break; - case 'L' - 55: m.command = CMD_SETENVPOSITION; break; - case 'P' - 55: m.command = CMD_PANNINGSLIDE; break; - case 'R' - 55: m.command = CMD_RETRIG; break; - case 'T' - 55: m.command = CMD_TREMOR; break; - case 'W' - 55: m.command = CMD_DUMMY; break; - case 'X' - 55: m.command = CMD_XFINEPORTAUPDOWN; break; - case 'Y' - 55: m.command = CMD_PANBRELLO; break; // 34 - case 'Z' - 55: m.command = CMD_MIDI; break; // 35 - case '\\' - 56: m.command = CMD_SMOOTHMIDI; break; // 36 - note: this is actually displayed as "-" in FT2, but seems to be doing nothing. - case 37: m.command = CMD_SMOOTHMIDI; break; // BeRoTracker uses this for smooth MIDI macros for some reason; in old OpenMPT versions this was reserved for the unimplemented "velocity" command - case '#' + 3: m.command = CMD_XPARAM; break; // 38 - default: m.command = CMD_NONE; - } -} - -#ifndef MODPLUG_NO_FILESAVE - -void CSoundFile::ModSaveCommand(const ModCommand &source, uint8 &command, uint8 ¶m, const bool toXM, const bool compatibilityExport) const -{ - command = 0; - param = source.param; - switch(source.command) - { - case CMD_NONE: command = param = 0; break; - case CMD_ARPEGGIO: command = 0; break; - case CMD_PORTAMENTOUP: - if (GetType() & (MOD_TYPE_S3M|MOD_TYPE_IT|MOD_TYPE_STM|MOD_TYPE_MPT)) - { - if ((param & 0xF0) == 0xE0) { command = 0x0E; param = ((param & 0x0F) >> 2) | 0x10; break; } - else if ((param & 0xF0) == 0xF0) { command = 0x0E; param &= 0x0F; param |= 0x10; break; } - } - command = 0x01; - break; - case CMD_PORTAMENTODOWN: - if(GetType() & (MOD_TYPE_S3M|MOD_TYPE_IT|MOD_TYPE_STM|MOD_TYPE_MPT)) - { - if ((param & 0xF0) == 0xE0) { command = 0x0E; param= ((param & 0x0F) >> 2) | 0x20; break; } - else if ((param & 0xF0) == 0xF0) { command = 0x0E; param &= 0x0F; param |= 0x20; break; } - } - command = 0x02; - break; - case CMD_TONEPORTAMENTO: command = 0x03; break; - case CMD_VIBRATO: command = 0x04; break; - case CMD_TONEPORTAVOL: command = 0x05; break; - case CMD_VIBRATOVOL: command = 0x06; break; - case CMD_TREMOLO: command = 0x07; break; - case CMD_PANNING8: - command = 0x08; - if(GetType() & MOD_TYPE_S3M) - { - if(param <= 0x80) - { - param = mpt::saturate_cast(param * 2); - } - else if(param == 0xA4) // surround - { - if(compatibilityExport || !toXM) - { - command = param = 0; - } - else - { - command = 'X' - 55; - param = 91; - } - } - } - break; - case CMD_OFFSET: command = 0x09; break; - case CMD_VOLUMESLIDE: command = 0x0A; break; - case CMD_POSITIONJUMP: command = 0x0B; break; - case CMD_VOLUME: command = 0x0C; break; - case CMD_PATTERNBREAK: command = 0x0D; param = ((param / 10) << 4) | (param % 10); break; - case CMD_MODCMDEX: command = 0x0E; break; - case CMD_SPEED: command = 0x0F; param = std::min(param, uint8(0x1F)); break; - case CMD_TEMPO: command = 0x0F; param = std::max(param, uint8(0x20)); break; - case CMD_GLOBALVOLUME: command = 'G' - 55; break; - case CMD_GLOBALVOLSLIDE: command = 'H' - 55; break; - case CMD_KEYOFF: command = 'K' - 55; break; - case CMD_SETENVPOSITION: command = 'L' - 55; break; - case CMD_PANNINGSLIDE: command = 'P' - 55; break; - case CMD_RETRIG: command = 'R' - 55; break; - case CMD_TREMOR: command = 'T' - 55; break; - case CMD_DUMMY: command = 'W' - 55; break; - case CMD_XFINEPORTAUPDOWN: command = 'X' - 55; - if(compatibilityExport && param >= 0x30) // X1x and X2x are legit, everything above are MPT extensions, which don't belong here. - param = 0; // Don't set command to 0 to indicate that there *was* some X command here... - break; - case CMD_PANBRELLO: - if(compatibilityExport) - command = param = 0; - else - command = 'Y' - 55; - break; - case CMD_MIDI: - if(compatibilityExport) - command = param = 0; - else - command = 'Z' - 55; - break; - case CMD_SMOOTHMIDI: //rewbs.smoothVST: 36 - if(compatibilityExport) - command = param = 0; - else - command = '\\' - 56; - break; - case CMD_XPARAM: //rewbs.XMfixes - XParam is 38 - if(compatibilityExport) - command = param = 0; - else - command = '#' + 3; - break; - case CMD_S3MCMDEX: - { - ModCommand mConv; - mConv.command = CMD_S3MCMDEX; - mConv.param = param; - mConv.ExtendedS3MtoMODEffect(); - ModSaveCommand(mConv, command, param, toXM, compatibilityExport); - } - return; - case CMD_VOLUME8: - command = 0x0C; - param = static_cast((param + 3u) / 4u); - break; - default: - command = param = 0; - } - - // Don't even think about saving XM effects in MODs... - if(command > 0x0F && !toXM) - { - command = param = 0; - } -} - -#endif // MODPLUG_NO_FILESAVE - - -// File Header -struct MODFileHeader -{ - uint8be numOrders; - uint8be restartPos; // Tempo (early SoundTracker) or restart position (only PC trackers?) - uint8be orderList[128]; -}; - -MPT_BINARY_STRUCT(MODFileHeader, 130) - - -// Sample Header -struct MODSampleHeader -{ - char name[22]; - uint16be length; - uint8be finetune; - uint8be volume; - uint16be loopStart; - uint16be loopLength; - - // Convert an MOD sample header to OpenMPT's internal sample header. - void ConvertToMPT(ModSample &mptSmp, bool is4Chn) const - { - mptSmp.Initialize(MOD_TYPE_MOD); - mptSmp.nLength = length * 2; - mptSmp.nFineTune = MOD2XMFineTune(finetune & 0x0F); - mptSmp.nVolume = 4u * std::min(volume.get(), uint8(64)); - - SmpLength lStart = loopStart * 2; - SmpLength lLength = loopLength * 2; - // See if loop start is incorrect as words, but correct as bytes (like in Soundtracker modules) - if(lLength > 2 && (lStart + lLength > mptSmp.nLength) - && (lStart / 2 + lLength <= mptSmp.nLength)) - { - lStart /= 2; - } - - if(mptSmp.nLength == 2) - { - mptSmp.nLength = 0; - } - - if(mptSmp.nLength) - { - mptSmp.nLoopStart = lStart; - mptSmp.nLoopEnd = lStart + lLength; - - if(mptSmp.nLoopStart >= mptSmp.nLength) - { - mptSmp.nLoopStart = mptSmp.nLength - 1; - } - if(mptSmp.nLoopStart > mptSmp.nLoopEnd || mptSmp.nLoopEnd < 4 || mptSmp.nLoopEnd - mptSmp.nLoopStart < 4) - { - mptSmp.nLoopStart = 0; - mptSmp.nLoopEnd = 0; - } - - // Fix for most likely broken sample loops. This fixes super_sufm_-_new_life.mod (M.K.) which has a long sample which is looped from 0 to 4. - // This module also has notes outside of the Amiga frequency range, so we cannot say that it should be played using ProTracker one-shot loops. - // On the other hand, "Crew Generation" by Necros (6CHN) has a sample with a similar loop, which is supposed to be played. - // To be able to correctly play both modules, we will draw a somewhat arbitrary line here and trust the loop points in MODs with more than - // 4 channels, even if they are tiny and at the very beginning of the sample. - if(mptSmp.nLoopEnd <= 8 && mptSmp.nLoopStart == 0 && mptSmp.nLength > mptSmp.nLoopEnd && is4Chn) - { - mptSmp.nLoopEnd = 0; - } - if(mptSmp.nLoopEnd > mptSmp.nLoopStart) - { - mptSmp.uFlags.set(CHN_LOOP); - } - } - } - - // Convert OpenMPT's internal sample header to a MOD sample header. - SmpLength ConvertToMOD(const ModSample &mptSmp) - { - SmpLength writeLength = mptSmp.HasSampleData() ? mptSmp.nLength : 0; - // If the sample size is odd, we have to add a padding byte, as all sample sizes in MODs are even. - if((writeLength % 2u) != 0) - { - writeLength++; - } - LimitMax(writeLength, SmpLength(0x1FFFE)); - - length = static_cast(writeLength / 2u); - - if(mptSmp.RelativeTone < 0) - { - finetune = 0x08; - } else if(mptSmp.RelativeTone > 0) - { - finetune = 0x07; - } else - { - finetune = XM2MODFineTune(mptSmp.nFineTune); - } - volume = static_cast(mptSmp.nVolume / 4u); - - loopStart = 0; - loopLength = 1; - if(mptSmp.uFlags[CHN_LOOP] && (mptSmp.nLoopStart + 2u) < writeLength) - { - const SmpLength loopEnd = Clamp(mptSmp.nLoopEnd, (mptSmp.nLoopStart & ~1) + 2u, writeLength) & ~1; - loopStart = static_cast(mptSmp.nLoopStart / 2u); - loopLength = static_cast((loopEnd - (mptSmp.nLoopStart & ~1)) / 2u); - } - - return writeLength; - } - - // Compute a "rating" of this sample header by counting invalid header data to ultimately reject garbage files. - uint32 GetInvalidByteScore() const - { - return ((volume > 64) ? 1 : 0) - + ((finetune > 15) ? 1 : 0) - + ((loopStart > length * 2) ? 1 : 0); - } - - bool HasDiskName() const - { - return (!memcmp(name, "st-", 3) || !memcmp(name, "ST-", 3)) && name[5] == ':'; - } - - // Suggested threshold for rejecting invalid files based on cumulated score returned by GetInvalidByteScore - static constexpr uint32 INVALID_BYTE_THRESHOLD = 40; - - // This threshold is used for files where the file magic only gives a - // fragile result which alone would lead to too many false positives. - // In particular, the files from Inconexia demo by Iguana - // (https://www.pouet.net/prod.php?which=830) which have 3 \0 bytes in - // the file magic tend to cause misdetection of random files. - static constexpr uint32 INVALID_BYTE_FRAGILE_THRESHOLD = 1; - - // Retrieve the internal sample format flags for this sample. - static SampleIO GetSampleFormat() - { - return SampleIO( - SampleIO::_8bit, - SampleIO::mono, - SampleIO::bigEndian, - SampleIO::signedPCM); - } -}; - -MPT_BINARY_STRUCT(MODSampleHeader, 30) - -// Pattern data of a 4-channel MOD file -using MODPatternData = std::array, 4>, 64>; - // Synthesized StarTrekker instruments struct AMInstrument { @@ -464,261 +142,6 @@ struct AMInstrument MPT_BINARY_STRUCT(AMInstrument, 36) -struct PT36IffChunk -{ - // IFF chunk names - enum ChunkIdentifiers - { - idVERS = MagicBE("VERS"), - idINFO = MagicBE("INFO"), - idCMNT = MagicBE("CMNT"), - idPTDT = MagicBE("PTDT"), - }; - - uint32be signature; // IFF chunk name - uint32be chunksize; // chunk size without header -}; - -MPT_BINARY_STRUCT(PT36IffChunk, 8) - -struct PT36InfoChunk -{ - char name[32]; - uint16be numSamples; - uint16be numOrders; - uint16be numPatterns; - uint16be volume; - uint16be tempo; - uint16be flags; - uint16be dateDay; - uint16be dateMonth; - uint16be dateYear; - uint16be dateHour; - uint16be dateMinute; - uint16be dateSecond; - uint16be playtimeHour; - uint16be playtimeMinute; - uint16be playtimeSecond; - uint16be playtimeMsecond; -}; - -MPT_BINARY_STRUCT(PT36InfoChunk, 64) - - -// Check if header magic equals a given string. -static bool IsMagic(const char *magic1, const char (&magic2)[5]) noexcept -{ - return std::memcmp(magic1, magic2, 4) == 0; -} - - -// For .DTM files from Apocalypse Abyss, where the first 2108 bytes are swapped -template -static T ReadAndSwap(TFileReader &file, const bool swapBytes) -{ - T value; - if(file.Read(value) && swapBytes) - { - static_assert(sizeof(value) % 2u == 0); - auto byteView = mpt::as_raw_memory(value); - for(size_t i = 0; i < sizeof(T); i += 2) - { - std::swap(byteView[i], byteView[i + 1]); - } - } - return value; -} - - -static uint32 ReadSample(const MODSampleHeader &sampleHeader, ModSample &sample, mpt::charbuf &sampleName, bool is4Chn) -{ - sampleHeader.ConvertToMPT(sample, is4Chn); - sampleName = mpt::String::ReadBuf(mpt::String::spacePadded, sampleHeader.name); - // Get rid of weird characters in sample names. - for(auto &c : sampleName.buf) - { - if(c > 0 && c < ' ') - { - c = ' '; - } - } - // Check for invalid values - return sampleHeader.GetInvalidByteScore(); -} - - -// Count malformed bytes in MOD pattern data -static uint32 CountMalformedMODPatternData(const MODPatternData &patternData, const bool extendedFormat) -{ - const uint8 mask = extendedFormat ? 0xE0 : 0xF0; - uint32 malformedBytes = 0; - for(const auto &row : patternData) - { - for(const auto &data : row) - { - if(data[0] & mask) - malformedBytes++; - if(!extendedFormat) - { - const uint16 period = (((static_cast(data[0]) & 0x0F) << 8) | data[1]); - if(period && period != 0xFFF) - { - // Allow periods to deviate by +/-1 as found in some files - const auto CompareFunc = [](uint16 l, uint16 r) { return l > (r + 1); }; - const auto PeriodTable = mpt::as_span(ProTrackerPeriodTable).subspan(24, 36); - if(!std::binary_search(PeriodTable.begin(), PeriodTable.end(), period, CompareFunc)) - malformedBytes += 2; - } - } - } - } - return malformedBytes; -} - - -// Check if number of malformed bytes in MOD pattern data exceeds some threshold -template -static bool ValidateMODPatternData(TFileReader &file, const uint32 threshold, const bool extendedFormat) -{ - MODPatternData patternData; - if(!file.Read(patternData)) - return false; - return CountMalformedMODPatternData(patternData, extendedFormat) <= threshold; -} - - -// Parse the order list to determine how many patterns are used in the file. -static PATTERNINDEX GetNumPatterns(FileReader &file, ModSequence &Order, ORDERINDEX numOrders, SmpLength totalSampleLen, CHANNELINDEX &numChannels, SmpLength wowSampleLen, bool validateHiddenPatterns) -{ - PATTERNINDEX numPatterns = 0; // Total number of patterns in file (determined by going through the whole order list) with pattern number < 128 - PATTERNINDEX officialPatterns = 0; // Number of patterns only found in the "official" part of the order list (i.e. order positions < claimed order length) - PATTERNINDEX numPatternsIllegal = 0; // Total number of patterns in file, also counting in "invalid" pattern indexes >= 128 - - for(ORDERINDEX ord = 0; ord < 128; ord++) - { - PATTERNINDEX pat = Order[ord]; - if(pat < 128 && numPatterns <= pat) - { - numPatterns = pat + 1; - if(ord < numOrders) - { - officialPatterns = numPatterns; - } - } - if(pat >= numPatternsIllegal) - { - numPatternsIllegal = pat + 1; - } - } - - // Remove the garbage patterns past the official order end now that we don't need them anymore. - Order.resize(numOrders); - - const size_t patternStartOffset = file.GetPosition(); - const size_t sizeWithoutPatterns = totalSampleLen + patternStartOffset; - const size_t sizeWithOfficialPatterns = sizeWithoutPatterns + officialPatterns * numChannels * 256; - // There are some WOW files with an extra byte at the end, and also a MOD file (idntmind.mod, MD5 a3af5c3e1af269e32dfb6677c41c8453, SHA1 4884717c298575f9884b2211c762bb1725f73743) - // where only the "official" patterns should be counted but the file also has an extra byte at the end. - // Since MOD files can technically not have an odd file size, we just always round the actual file size down. - const auto fileSize = mpt::align_down(file.GetLength(), FileReader::pos_type{2}); - - if(wowSampleLen && (wowSampleLen + patternStartOffset) + numPatterns * 8 * 256 == fileSize) - { - // Check if this is a Mod's Grave WOW file... WOW files use the M.K. magic but are actually 8CHN files. - // We do a simple pattern validation as well for regular MOD files that have non-module data attached at the end - // (e.g. ponylips.mod, MD5 c039af363b1d99a492dafc5b5f9dd949, SHA1 1bee1941c47bc6f913735ce0cf1880b248b8fc93) - file.Seek(patternStartOffset + numPatterns * 4 * 256); - if(ValidateMODPatternData(file, 16, true)) - numChannels = 8; - file.Seek(patternStartOffset); - } else if(numPatterns != officialPatterns && (validateHiddenPatterns || sizeWithOfficialPatterns == fileSize)) - { - // 15-sample SoundTracker specifics: - // Fix SoundTracker modules where "hidden" patterns should be ignored. - // razor-1911.mod (MD5 b75f0f471b0ae400185585ca05bf7fe8, SHA1 4de31af234229faec00f1e85e1e8f78f405d454b) - // and captain_fizz.mod (MD5 55bd89fe5a8e345df65438dbfc2df94e, SHA1 9e0e8b7dc67939885435ea8d3ff4be7704207a43) - // seem to have the "correct" file size when only taking the "official" patterns into account, - // but they only play correctly when also loading the inofficial patterns. - // On the other hand, the SoundTracker module - // wolf1.mod (MD5 a4983d7a432d324ce8261b019257f4ed, SHA1 aa6b399d02546bcb6baf9ec56a8081730dea3f44), - // wolf3.mod (MD5 af60840815aa9eef43820a7a04417fa6, SHA1 24d6c2e38894f78f6c5c6a4b693a016af8fa037b) - // and jean_baudlot_-_bad_dudes_vs_dragonninja-dragonf.mod (MD5 fa48e0f805b36bdc1833f6b82d22d936, SHA1 39f2f8319f4847fe928b9d88eee19d79310b9f91) - // only play correctly if we ignore the hidden patterns. - // Hence, we have a peek at the first hidden pattern and check if it contains a lot of illegal data. - // If that is the case, we assume it's part of the sample data and only consider the "official" patterns. - - // 31-sample NoiseTracker / ProTracker specifics: - // Interestingly, (broken) variants of the ProTracker modules - // "killing butterfly" (MD5 bd676358b1dbb40d40f25435e845cf6b, SHA1 9df4ae21214ff753802756b616a0cafaeced8021), - // "quartex" by Reflex (MD5 35526bef0fb21cb96394838d94c14bab, SHA1 116756c68c7b6598dcfbad75a043477fcc54c96c), - // seem to have the "correct" file size when only taking the "official" patterns into account, but they only play - // correctly when also loading the inofficial patterns. - // On the other hand, "Shofixti Ditty.mod" from Star Control 2 (MD5 62b7b0819123400e4d5a7813eef7fc7d, SHA1 8330cd595c61f51c37a3b6f2a8559cf3fcaaa6e8) - // doesn't sound correct when taking the second "inofficial" pattern into account. - file.Seek(patternStartOffset + officialPatterns * numChannels * 256); - if(!ValidateMODPatternData(file, 64, true)) - numPatterns = officialPatterns; - file.Seek(patternStartOffset); - } - - if(numPatternsIllegal > numPatterns && sizeWithoutPatterns + numPatternsIllegal * numChannels * 256 == fileSize) - { - // Even those illegal pattern indexes (> 128) appear to be valid... What a weird file! - // e.g. NIETNU.MOD, where the end of the order list is filled with FF rather than 00, and the file actually contains 256 patterns. - numPatterns = numPatternsIllegal; - } else if(numPatternsIllegal >= 0xFF) - { - // Patterns FE and FF are used with S3M semantics (e.g. some MODs written with old OpenMPT versions) - Order.Replace(0xFE, Order.GetIgnoreIndex()); - Order.Replace(0xFF, Order.GetInvalidPatIndex()); - } - - return numPatterns; -} - - -std::pair CSoundFile::ReadMODPatternEntry(FileReader &file, ModCommand &m) -{ - return ReadMODPatternEntry(file.ReadArray(), m); -} - - -std::pair CSoundFile::ReadMODPatternEntry(const std::array data, ModCommand &m) -{ - // Read Period - uint16 period = (((static_cast(data[0]) & 0x0F) << 8) | data[1]); - size_t note = NOTE_NONE; - if(period > 0 && period != 0xFFF) - { - note = std::size(ProTrackerPeriodTable) + 23 + NOTE_MIN; - for(size_t i = 0; i < std::size(ProTrackerPeriodTable); i++) - { - if(period >= ProTrackerPeriodTable[i]) - { - if(period != ProTrackerPeriodTable[i] && i != 0) - { - uint16 p1 = ProTrackerPeriodTable[i - 1]; - uint16 p2 = ProTrackerPeriodTable[i]; - if(p1 - period < (period - p2)) - { - note = i + 23 + NOTE_MIN; - break; - } - } - note = i + 24 + NOTE_MIN; - break; - } - } - } - m.note = static_cast(note); - // Read Instrument - m.instr = (data[2] >> 4) | (data[0] & 0x10); - // Read Effect - m.command = CMD_NONE; - uint8 command = data[2] & 0x0F, param = data[3]; - return {command, param}; -} - struct MODMagicResult { @@ -748,7 +171,7 @@ static bool CheckMODMagic(const char magic[4], MODMagicResult &result) || IsMagic(magic, "FEST") // "His Master's Noise" musicdisk || IsMagic(magic, "N.T.")) { - result.madeWithTracker = UL_("NoiseTracker"); + result.madeWithTracker = IsMagic(magic, "N.T.") ? UL_("NoiseTracker") : UL_("His Master's NoiseTracker"); result.isNoiseTracker = true; result.setMODVBlankTiming = true; result.numChannels = 4; @@ -763,7 +186,7 @@ static bool CheckMODMagic(const char magic[4], MODMagicResult &result) { // Octalyser on Atari STe/Falcon result.madeWithTracker = UL_("Octalyser (Atari)"); - result.numChannels = magic[2] - '0'; + result.numChannels = static_cast(magic[2] - '0'); } else if(IsMagic(magic, "M\0\0\0") || IsMagic(magic, "8\0\0\0")) { // Inconexia demo by Iguana, delta samples (https://www.pouet.net/prod.php?which=830) @@ -774,34 +197,34 @@ static bool CheckMODMagic(const char magic[4], MODMagicResult &result) { // Digital Tracker on Atari Falcon result.madeWithTracker = UL_("Digital Tracker"); - result.numChannels = magic[3] - '0'; + result.numChannels = static_cast(magic[3] - '0'); // Digital Tracker MODs contain four bytes (00 40 00 00) right after the magic bytes which don't seem to do anything special. result.patternDataOffset = 1088; - } else if((!memcmp(magic, "FLT", 3) || !memcmp(magic, "EXO", 3)) && magic[3] >= '4' && magic[3] <= '9') + } else if((!memcmp(magic, "FLT", 3) || !memcmp(magic, "EXO", 3)) && (magic[3] == '4' || magic[3] == '8')) { // FLTx / EXOx - Startrekker by Exolon / Fairlight result.madeWithTracker = UL_("Startrekker"); result.isStartrekker = true; result.setMODVBlankTiming = true; - result.numChannels = magic[3] - '0'; + result.numChannels = static_cast(magic[3] - '0'); } else if(magic[0] >= '1' && magic[0] <= '9' && !memcmp(magic + 1, "CHN", 3)) { // xCHN - Many trackers result.madeWithTracker = UL_("Generic MOD-compatible Tracker"); result.isGenericMultiChannel = true; - result.numChannels = magic[0] - '0'; + result.numChannels = static_cast(magic[0] - '0'); } else if(magic[0] >= '1' && magic[0] <= '9' && magic[1] >= '0' && magic[1] <= '9' && (!memcmp(magic + 2, "CH", 2) || !memcmp(magic + 2, "CN", 2))) { // xxCN / xxCH - Many trackers result.madeWithTracker = UL_("Generic MOD-compatible Tracker"); result.isGenericMultiChannel = true; - result.numChannels = (magic[0] - '0') * 10 + magic[1] - '0'; + result.numChannels = static_cast((magic[0] - '0') * 10 + magic[1] - '0'); } else if(!memcmp(magic, "TDZ", 3) && magic[3] >= '1' && magic[3] <= '9') { // TDZx - TakeTracker (only TDZ1-TDZ3 should exist, but historically this code only supported 4-9 channels, so we keep those for the unlikely case that they were actually used for something) result.madeWithTracker = UL_("TakeTracker"); - result.numChannels = magic[3] - '0'; + result.numChannels = static_cast(magic[3] - '0'); } else if(IsMagic(magic, ".M.K")) { // Hacked .DMF files from the game "Apocalypse Abyss" @@ -874,8 +297,7 @@ bool CSoundFile::ReadMOD(FileReader &file, ModLoadingFlags loadFlags) return true; } - InitializeGlobals(MOD_TYPE_MOD); - m_nChannels = modMagicResult.numChannels; + InitializeGlobals(MOD_TYPE_MOD, modMagicResult.numChannels); bool isNoiseTracker = modMagicResult.isNoiseTracker; bool isStartrekker = modMagicResult.isStartrekker; @@ -891,7 +313,7 @@ bool CSoundFile::ReadMOD(FileReader &file, ModLoadingFlags loadFlags) } // Startrekker 8 channel mod (needs special treatment, see below) - const bool isFLT8 = isStartrekker && m_nChannels == 8; + const bool isFLT8 = isStartrekker && GetNumChannels() == 8; const bool isMdKd = IsMagic(magic, "M.K."); // Adjust finetune values for modules saved with "His Master's Noisetracker" const bool isHMNT = IsMagic(magic, "M&K!") || IsMagic(magic, "FEST"); @@ -910,7 +332,7 @@ bool CSoundFile::ReadMOD(FileReader &file, ModLoadingFlags loadFlags) for(SAMPLEINDEX smp = 1; smp <= 31; smp++) { MODSampleHeader sampleHeader = ReadAndSwap(file, modMagicResult.swapBytes); - invalidBytes += ReadSample(sampleHeader, Samples[smp], m_szNames[smp], m_nChannels == 4); + invalidBytes += ReadMODSample(sampleHeader, Samples[smp], m_szNames[smp], GetNumChannels() == 4); totalSampleLen += Samples[smp].nLength; if(isHMNT) @@ -968,7 +390,7 @@ bool CSoundFile::ReadMOD(FileReader &file, ModLoadingFlags loadFlags) } // Get number of patterns (including some order list sanity checks) - PATTERNINDEX numPatterns = GetNumPatterns(file, Order(), realOrders, totalSampleLen, m_nChannels, wowSampleLen, false); + PATTERNINDEX numPatterns = GetNumPatterns(file, *this, realOrders, totalSampleLen, wowSampleLen, false); if(maybeWOW && GetNumChannels() == 8) { // M.K. with 8 channels = Mod's Grave @@ -996,20 +418,20 @@ bool CSoundFile::ReadMOD(FileReader &file, ModLoadingFlags loadFlags) // Files that have an order list longer than 0x78 with restart pos = 0x78: my_shoe_is_barking.mod, papermix.mod // - in both cases it does not appear like the restart position should be used. MPT_ASSERT(fileHeader.restartPos != 0x78 || fileHeader.restartPos + 1u >= realOrders); - if(fileHeader.restartPos > realOrders || (fileHeader.restartPos == 0x78 && m_nChannels == 4)) + if(fileHeader.restartPos > realOrders || (fileHeader.restartPos == 0x78 && GetNumChannels() == 4)) { Order().SetRestartPos(0); } - m_nDefaultSpeed = 6; - m_nDefaultTempo.Set(125); + Order().SetDefaultSpeed(6); + Order().SetDefaultTempoInt(125); m_nMinPeriod = 14 * 4; m_nMaxPeriod = 3424 * 4; // Prevent clipping based on number of channels... If all channels are playing at full volume, "256 / #channels" // is the maximum possible sample pre-amp without getting distortion (Compatible mix levels given). // The more channels we have, the less likely it is that all of them are used at the same time, though, so cap at 32... - m_nSamplePreAmp = Clamp(256 / m_nChannels, 32, 128); - m_SongFlags.reset(); // SONG_ISAMIGA will be set conditionally + m_nSamplePreAmp = Clamp(256 / GetNumChannels(), 32, 128); + m_SongFlags = SONG_FORMAT_NO_VOLCOL; // SONG_ISAMIGA will be set conditionally // Setup channel pan positions and volume SetupMODPanning(); @@ -1025,7 +447,7 @@ bool CSoundFile::ReadMOD(FileReader &file, ModLoadingFlags loadFlags) const uint8 ENABLE_MOD_PANNING_THRESHOLD = 0x30; if(!isNoiseTracker) { - const uint32 patternLength = m_nChannels * 64; + const uint32 patternLength = GetNumChannels() * 64; bool leftPanning = false, extendedPanning = false; // For detecting 800-880 panning isNoiseTracker = isMdKd && !hasEmptySampleWithVolume && !hasLongSamples; for(PATTERNINDEX pat = 0; pat < numPatterns; pat++) @@ -1066,7 +488,7 @@ bool CSoundFile::ReadMOD(FileReader &file, ModLoadingFlags loadFlags) } file.Seek(modMagicResult.patternDataOffset); - const CHANNELINDEX readChannels = (isFLT8 ? 4 : m_nChannels); // 4 channels per pattern in FLT8 format. + const CHANNELINDEX readChannels = (isFLT8 ? 4 : GetNumChannels()); // 4 channels per pattern in FLT8 format. if(isFLT8) numPatterns++; // as one logical pattern consists of two real patterns in FLT8 format, the highest pattern number has to be increased by one. bool hasTempoCommands = false, definitelyCIA = hasLongSamples; // for detecting VBlank MODs @@ -1108,7 +530,7 @@ bool CSoundFile::ReadMOD(FileReader &file, ModLoadingFlags loadFlags) std::vector lastInstrument(GetNumChannels(), 0); std::vector instrWithoutNoteCount(GetNumChannels(), 0); - for(ROWINDEX row = 0; row < 64; row++, rowBase += m_nChannels) + for(ROWINDEX row = 0; row < 64; row++, rowBase += GetNumChannels()) { // If we have more than one Fxx command on this row and one can be interpreted as speed // and the other as tempo, we can be rather sure that it is not a VBlank mod. @@ -1146,6 +568,9 @@ bool CSoundFile::ReadMOD(FileReader &file, ModLoadingFlags loadFlags) } else if(m.command == CMD_PATTERNBREAK && isNoiseTracker) { m.param = 0; + } else if(m.command == CMD_TREMOLO && isHMNT) + { + m.command = CMD_HMN_MEGA_ARP; } else if(m.command == CMD_PANNING8 && fix7BitPanning) { // Fix MODs with 7-bit + surround panning @@ -1263,7 +688,7 @@ bool CSoundFile::ReadMOD(FileReader &file, ModLoadingFlags loadFlags) // On the other hand, the loop points in Purple Motions's SOUL-O-M.MOD are completely broken and shouldn't be treated like this. // As it was most likely written in Scream Tracker, it has empty sample slots with a default volume of 64, which we use for // rejecting this quirk for that file. - FileReader::off_t nextSample = file.GetPosition() + sampleIO.CalculateEncodedSize(sample.nLength); + FileReader::pos_type nextSample = file.GetPosition() + sampleIO.CalculateEncodedSize(sample.nLength); if(isMdKd && onlyAmigaNotes && !hasEmptySampleWithVolume) sample.nLength = std::max(sample.nLength, sample.nLoopEnd); @@ -1271,7 +696,10 @@ bool CSoundFile::ReadMOD(FileReader &file, ModLoadingFlags loadFlags) file.Seek(nextSample); } } - if(isMdKd && file.ReadArray() == std::array{0x00, 0x11, 0x55, 0x33, 0x22, 0x11, 0x04, 0x01, 0x01}) + // XOR with 0xDF gives the message "TakeTrackered with version 0.9E!!!!!" + if(GetNumChannels() <= 16 && file.ReadMagic("\x8B\xBE\xB4\xBA\x8B\xAD\xBE\xBC\xB4\xBA\xAD\xBA\xBB\xFF\xA8\xB6\xAB\xB7\xFF\xA9\xBA\xAD\xAC\xB6\xB0\xB1\xFF\xEF\xF1\xE6\xBA\xFE\xFE\xFE\xFE\xFE")) + modMagicResult.madeWithTracker = UL_("TakeTracker"); + else if(isMdKd && file.ReadArray() == std::array{0x00, 0x11, 0x55, 0x33, 0x22, 0x11} && file.CanRead(3)) // 3 more bytes that differ between modules and Tetramed version, purpose unknown modMagicResult.madeWithTracker = UL_("Tetramed"); } @@ -1327,6 +755,8 @@ bool CSoundFile::ReadMOD(FileReader &file, ModLoadingFlags loadFlags) m_nInstruments = 31; #endif + mpt::deterministic_random_device rd; + auto prng = mpt::make_prng(rd); for(SAMPLEINDEX smp = 1; smp <= GetNumInstruments(); smp++) { // For Startrekker AM synthesis, we need instrument envelopes. @@ -1341,7 +771,7 @@ bool CSoundFile::ReadMOD(FileReader &file, ModLoadingFlags loadFlags) // Allow partial reads for fa.worse face.mod if(amData.ReadStructPartial(am) && !memcmp(am.am, "AM", 2) && am.waveform < 4) { - am.ConvertToMPT(Samples[smp], *ins, AccessPRNG()); + am.ConvertToMPT(Samples[smp], *ins, prng); } // This extra padding is probably present to have identical block sizes for AM and FM instruments. @@ -1376,6 +806,79 @@ bool CSoundFile::ReadMOD(FileReader &file, ModLoadingFlags loadFlags) } } + // His Master's Noise "Mupp" instrument extensions + if((loadFlags & loadSampleData) && isHMNT) + { + uint8 muppCount = 0; + for(SAMPLEINDEX smp = 1; smp <= 31; smp++) + { + file.Seek(20 + (smp - 1) * sizeof(MODSampleHeader)); + if(!file.ReadMagic("Mupp") || !CanAddMoreSamples(28)) + continue; + + if(!m_nInstruments) + { + m_playBehaviour.set(kMODSampleSwap); + m_nInstruments = 31; + for(INSTRUMENTINDEX ins = 1; ins <= 31; ins++) + { + if(ModInstrument *instr = AllocateInstrument(ins, ins); instr != nullptr) + instr->name = m_szNames[ins]; + } + } + ModInstrument *instr = Instruments[smp]; + if(!instr) + continue; + + const auto [muppPattern, loopStart, loopEnd] = file.ReadArray(); + file.Seek(1084 + 1024 * muppPattern); + SAMPLEINDEX startSmp = m_nSamples + 1; + m_nSamples += 28; + instr->AssignSample(startSmp); + + SampleIO sampleIO(SampleIO::_8bit, SampleIO::mono, SampleIO::littleEndian, SampleIO::signedPCM); + for(SAMPLEINDEX muppSmp = startSmp; muppSmp <= m_nSamples; muppSmp++) + { + ModSample &mptSmp = Samples[muppSmp]; + mptSmp.Initialize(MOD_TYPE_MOD); + mptSmp.nLength = 32; + mptSmp.nLoopStart = 0; + mptSmp.nLoopEnd = 32; + mptSmp.nFineTune = Samples[smp].nFineTune; + mptSmp.nVolume = Samples[smp].nVolume; + mptSmp.uFlags.set(CHN_LOOP); + sampleIO.ReadSample(mptSmp, file); + } + + auto &events = instr->synth.m_scripts.emplace_back(); + events.reserve(std::min(loopEnd + 2, 65)); + const auto waveforms = file.ReadArray(); + const auto volumes = file.ReadArray(); + for(uint8 i = 0; i < 64; i++) + { + events.push_back(InstrumentSynth::Event::Mupp_SetWaveform(muppCount, waveforms[i], volumes[i])); + if(i == loopEnd && loopStart <= loopEnd) + { + events.push_back(InstrumentSynth::Event::Jump(loopStart)); + break; + } + } + muppCount++; + } + } + + // For "the ultimate beeper.mod" + { + ModSample &sample = Samples[0]; + sample.Initialize(MOD_TYPE_MOD); + sample.nLength = 2; + sample.nLoopStart = 0; + sample.nLoopEnd = 2; + sample.nVolume = 0; + sample.uFlags.set(CHN_LOOP); + sample.AllocateSample(); + } + // Fix VBlank MODs. Arbitrary threshold: 8 minutes (enough for "frame of mind" by Dascon...). // Basically, this just converts all tempo commands into speed commands // for MODs which are supposed to have VBlank timing (instead of CIA timing). @@ -1384,7 +887,7 @@ bool CSoundFile::ReadMOD(FileReader &file, ModLoadingFlags loadFlags) // (as this would indicate that e.g. a F30 command was really meant to set // the ticks per row to 48, and not the tempo to 48 BPM). // In the pattern loader above, a second condition is used: Only tempo commands - // below 100 BPM are taken into account. Furthermore, only M.K. (ProTracker) + // below 100 BPM are taken into account. Furthermore, only ProTracker (M.K. / M!K!) // modules are checked. if((isMdKd || IsMagic(magic, "M!K!")) && hasTempoCommands && !definitelyCIA) { @@ -1406,972 +909,23 @@ bool CSoundFile::ReadMOD(FileReader &file, ModLoadingFlags loadFlags) std::transform(std::begin(magic), std::end(magic), std::begin(magic), [](unsigned char c) -> unsigned char { return (c < ' ') ? ' ' : c; }); m_modFormat.formatName = MPT_UFORMAT("ProTracker MOD ({})")(mpt::ToUnicode(mpt::Charset::ASCII, std::string(std::begin(magic), std::end(magic)))); - m_modFormat.type = U_("mod"); + m_modFormat.type = UL_("mod"); if(modMagicResult.madeWithTracker) m_modFormat.madeWithTracker = modMagicResult.madeWithTracker; m_modFormat.charset = mpt::Charset::Amiga_no_C1; if(anyADPCM) - m_modFormat.madeWithTracker += U_(" (ADPCM packed)"); + m_modFormat.madeWithTracker += UL_(" (ADPCM packed)"); return true; } -// Check if a name string is valid (i.e. doesn't contain binary garbage data) -static uint32 CountInvalidChars(const mpt::span name) noexcept -{ - uint32 invalidChars = 0; - for(int8 c : name) // char can be signed or unsigned - { - // Check for any Extended ASCII and control characters - if(c != 0 && c < ' ') - invalidChars++; - } - return invalidChars; -} - - -enum class NameClassification -{ - Empty, - ValidASCII, - Invalid, -}; - -// Check if a name is a valid null-terminated ASCII string with no garbage after the null terminator, or if it's empty -static NameClassification ClassifyName(const mpt::span name) noexcept -{ - bool foundNull = false, foundNormal = false; - for(auto c : name) - { - if(c > 0 && c < ' ') - return NameClassification::Invalid; - if(c == 0) - foundNull = true; - else if(foundNull) - return NameClassification::Invalid; - else - foundNormal = true; - } - if(!foundNull) - return NameClassification::Invalid; - return foundNormal ? NameClassification::ValidASCII : NameClassification::Empty; -} - - -// We'll have to do some heuristic checks to find out whether this is an old Ultimate Soundtracker module -// or if it was made with the newer Soundtracker versions. -// Thanks for Fraggie for this information! (https://www.un4seen.com/forum/?topic=14471.msg100829#msg100829) -enum STVersions -{ - UST1_00, // Ultimate Soundtracker 1.0-1.21 (K. Obarski) - UST1_80, // Ultimate Soundtracker 1.8-2.0 (K. Obarski) - ST2_00_Exterminator, // SoundTracker 2.0 (The Exterminator), D.O.C. Sountracker II (Unknown/D.O.C.) - ST_III, // Defjam Soundtracker III (Il Scuro/Defjam), Alpha Flight SoundTracker IV (Alpha Flight), D.O.C. SoundTracker IV (Unknown/D.O.C.), D.O.C. SoundTracker VI (Unknown/D.O.C.) - ST_IX, // D.O.C. SoundTracker IX (Unknown/D.O.C.) - MST1_00, // Master Soundtracker 1.0 (Tip/The New Masters) - ST2_00, // SoundTracker 2.0, 2.1, 2.2 (Unknown/D.O.C.) -}; - - - -struct M15FileHeaders -{ - char songname[20]; - MODSampleHeader sampleHeaders[15]; - MODFileHeader fileHeader; -}; - -MPT_BINARY_STRUCT(M15FileHeaders, 20 + 15 * 30 + 130) - - -static bool ValidateHeader(const M15FileHeaders &fileHeaders) -{ - // In theory, sample and song names should only ever contain printable ASCII chars and null. - // However, there are quite a few SoundTracker modules in the wild with random - // characters. To still be able to distguish them from other formats, we just reject - // files with *too* many bogus characters. Arbitrary threshold: 48 bogus characters in total - // or more than 5 invalid characters just in the title alone - uint32 invalidCharsInTitle = CountInvalidChars(fileHeaders.songname); - uint32 invalidChars = invalidCharsInTitle; - - SmpLength totalSampleLen = 0; - uint8 allVolumes = 0; - uint8 validNameCount = 0; - bool invalidNames = false; - - for(SAMPLEINDEX smp = 0; smp < 15; smp++) - { - const MODSampleHeader &sampleHeader = fileHeaders.sampleHeaders[smp]; - - invalidChars += CountInvalidChars(sampleHeader.name); - - // schmokk.mod has a non-zero value here but it should not be treated as finetune - if(sampleHeader.finetune != 0) - invalidChars += 16; - if(const auto nameType = ClassifyName(sampleHeader.name); nameType == NameClassification::ValidASCII) - validNameCount++; - else if(nameType == NameClassification::Invalid) - invalidNames = true; - - // Sanity checks - invalid character count adjusted for ata.mod (MD5 937b79b54026fa73a1a4d3597c26eace, SHA1 3322ca62258adb9e0ae8e9afe6e0c29d39add874) - // Sample length adjusted for romantic.stk which has a (valid) sample of length 72222 - if(invalidChars > 48 - || sampleHeader.volume > 64 - || sampleHeader.length > 37000) - { - return false; - } - - totalSampleLen += sampleHeader.length; - allVolumes |= sampleHeader.volume; - } - - // scramble_2.mod has a lot of garbage in the song title, but it has lots of properly-formatted sample names, so we consider those to be more important than the garbage bytes. - if(invalidCharsInTitle > 5 && (validNameCount < 4 || invalidNames)) - { - return false; - } - - // Reject any files with no (or only silent) samples at all, as this might just be a random binary file (e.g. ID3 tags with tons of padding) - if(totalSampleLen == 0 || allVolumes == 0) - { - return false; - } - - // Sanity check: No more than 128 positions. ST's GUI limits tempo to [1, 220]. - // There are some mods with a tempo of 0 (explora3-death.mod) though, so ignore the lower limit. - if(fileHeaders.fileHeader.numOrders > 128 || fileHeaders.fileHeader.restartPos > 220) - { - return false; - } - - uint8 maxPattern = *std::max_element(std::begin(fileHeaders.fileHeader.orderList), std::end(fileHeaders.fileHeader.orderList)); - // Sanity check: 64 patterns max. - if(maxPattern > 63) - { - return false; - } - - // No playable song, and lots of null values => most likely a sparse binary file but not a module - if(fileHeaders.fileHeader.restartPos == 0 && fileHeaders.fileHeader.numOrders == 0 && maxPattern == 0) - { - return false; - } - - return true; -} - - -template -static bool ValidateFirstM15Pattern(TFileReader &file) -{ - // threshold is chosen as: [threshold for all patterns combined] / [max patterns] * [margin, do not reject too much] - return ValidateMODPatternData(file, 512 / 64 * 2, false); -} - - -CSoundFile::ProbeResult CSoundFile::ProbeFileHeaderM15(MemoryFileReader file, const uint64 *pfilesize) -{ - M15FileHeaders fileHeaders; - if(!file.ReadStruct(fileHeaders)) - { - return ProbeWantMoreData; - } - if(!ValidateHeader(fileHeaders)) - { - return ProbeFailure; - } - if(!file.CanRead(sizeof(MODPatternData))) - { - return ProbeWantMoreData; - } - if(!ValidateFirstM15Pattern(file)) - { - return ProbeFailure; - } - MPT_UNREFERENCED_PARAMETER(pfilesize); - return ProbeSuccess; -} - - -bool CSoundFile::ReadM15(FileReader &file, ModLoadingFlags loadFlags) -{ - file.Rewind(); - - M15FileHeaders fileHeaders; - if(!file.ReadStruct(fileHeaders)) - { - return false; - } - if(!ValidateHeader(fileHeaders)) - { - return false; - } - if(!ValidateFirstM15Pattern(file)) - { - return false; - } - - char songname[20]; - std::memcpy(songname, fileHeaders.songname, 20); - - InitializeGlobals(MOD_TYPE_MOD); - m_playBehaviour.reset(kMODOneShotLoops); - m_playBehaviour.set(kMODIgnorePanning); - m_playBehaviour.set(kMODSampleSwap); // untested - m_nChannels = 4; - - STVersions minVersion = UST1_00; - - bool hasDiskNames = true; - SmpLength totalSampleLen = 0; - m_nSamples = 15; - - file.Seek(20); - for(SAMPLEINDEX smp = 1; smp <= 15; smp++) - { - ModSample &mptSmp = Samples[smp]; - MODSampleHeader sampleHeader; - file.ReadStruct(sampleHeader); - ReadSample(sampleHeader, Samples[smp], m_szNames[smp], true); - mptSmp.nFineTune = 0; - - totalSampleLen += mptSmp.nLength; - - if(m_szNames[smp][0] && sampleHeader.HasDiskName()) - { - // Ultimate Soundtracker 1.8 and D.O.C. SoundTracker IX always have sample names containing disk names. - hasDiskNames = false; - } - - // Loop start is always in bytes, not words, so don't trust the auto-fix magic in the sample header conversion (fixes loop of "st-01:asia" in mod.drag 10) - if(sampleHeader.loopLength > 1) - { - mptSmp.nLoopStart = sampleHeader.loopStart; - mptSmp.nLoopEnd = sampleHeader.loopStart + sampleHeader.loopLength * 2; - mptSmp.SanitizeLoops(); - } - - // UST only handles samples up to 9999 bytes. Master Soundtracker 1.0 and SoundTracker 2.0 introduce 32KB samples. - if(sampleHeader.length > 4999 || sampleHeader.loopStart > 9999) - minVersion = std::max(minVersion, MST1_00); - } - - MODFileHeader fileHeader; - file.ReadStruct(fileHeader); - - ReadOrderFromArray(Order(), fileHeader.orderList); - PATTERNINDEX numPatterns = GetNumPatterns(file, Order(), fileHeader.numOrders, totalSampleLen, m_nChannels, 0, true); - - // Most likely just a file with lots of NULs at the start - if(fileHeader.restartPos == 0 && fileHeader.numOrders == 0 && numPatterns <= 1) - { - return false; - } - - // Let's see if the file is too small (including some overhead for broken files like sll7.mod or ghostbus.mod) - std::size_t requiredRemainingDataSize = numPatterns * 64u * 4u * 4u + totalSampleLen; - if(!file.CanRead(requiredRemainingDataSize - std::min(requiredRemainingDataSize, 65536u))) - return false; - - if(loadFlags == onlyVerifyHeader) - return true; - - // Now we can be pretty sure that this is a valid Soundtracker file. Set up default song settings. - // explora3-death.mod has a tempo of 0 - if(!fileHeader.restartPos) - fileHeader.restartPos = 0x78; - // jjk55 by Jesper Kyd has a weird tempo set, but it needs to be ignored. - if(!memcmp(songname, "jjk55", 6)) - fileHeader.restartPos = 0x78; - // Sample 7 in echoing.mod won't "loop" correctly if we don't convert the VBlank tempo. - m_nDefaultTempo.Set(125); - if(fileHeader.restartPos != 0x78) - { - // Convert to CIA timing - m_nDefaultTempo = TEMPO((709379.0 * 125.0 / 50.0) / ((240 - fileHeader.restartPos) * 122.0)); - if(minVersion > UST1_80) - { - // D.O.C. SoundTracker IX re-introduced the variable tempo after some other versions dropped it. - minVersion = std::max(minVersion, hasDiskNames ? ST_IX : MST1_00); - } else - { - // Ultimate Soundtracker 1.8 adds variable tempo - minVersion = std::max(minVersion, hasDiskNames ? UST1_80 : ST2_00_Exterminator); - } - } - m_nMinPeriod = 113 * 4; - m_nMaxPeriod = 856 * 4; - m_nSamplePreAmp = 64; - m_SongFlags.set(SONG_PT_MODE); - m_songName = mpt::String::ReadBuf(mpt::String::spacePadded, songname); - - // Setup channel pan positions and volume - SetupMODPanning(); - - FileReader::off_t patOffset = file.GetPosition(); - - // Scan patterns to identify Ultimate Soundtracker modules. - uint32 illegalBytes = 0, totalNumDxx = 0; - for(PATTERNINDEX pat = 0; pat < numPatterns; pat++) - { - const bool patternInUse = mpt::contains(Order(), pat); - uint8 numDxx = 0; - uint8 emptyCmds = 0; - MODPatternData patternData; - file.ReadArray(patternData); - if(patternInUse) - { - illegalBytes += CountMalformedMODPatternData(patternData, false); - // Reject files that contain a lot of illegal pattern data. - // STK.the final remix (MD5 5ff13cdbd77211d1103be7051a7d89c9, SHA1 e94dba82a5da00a4758ba0c207eb17e3a89c3aa3) - // has one illegal byte, so we only reject after an arbitrary threshold has been passed. - // This also allows to play some rather damaged files like - // crockets.mod (MD5 995ed9f44cab995a0eeb19deb52e2a8b, SHA1 6c79983c3b7d55c9bc110b625eaa07ce9d75f369) - // but naturally we cannot recover the broken data. - - // We only check patterns that are actually being used in the order list, because some bad rips of the - // "operation wolf" soundtrack have 15 patterns for several songs, but the last few patterns are just garbage. - // Apart from those hidden patterns, the files play fine. - // Example: operation wolf - wolf1.mod (MD5 739acdbdacd247fbefcac7bc2d8abe6b, SHA1 e6b4813daacbf95f41ce9ec3b22520a2ae07eed8) - if(illegalBytes > std::max(512u, numPatterns * 128u)) - return false; - } - for(ROWINDEX row = 0; row < 64; row++) - { - for(CHANNELINDEX chn = 0; chn < 4; chn++) - { - const auto &data = patternData[row][chn]; - const uint8 eff = data[2] & 0x0F, param = data[3]; - // Check for empty space between the last Dxx command and the beginning of another pattern - if(emptyCmds != 0 && !memcmp(data.data(), "\0\0\0\0", 4)) - { - emptyCmds++; - if(emptyCmds > 32) - { - // Since there is a lot of empty space after the last Dxx command, - // we assume it's supposed to be a pattern break effect. - minVersion = ST2_00; - } - } else - { - emptyCmds = 0; - } - - switch(eff) - { - case 1: - case 2: - if(param > 0x1F && minVersion == UST1_80) - { - // If a 1xx / 2xx effect has a parameter greater than 0x20, it is assumed to be UST. - minVersion = hasDiskNames ? UST1_80 : UST1_00; - } else if(eff == 1 && param > 0 && param < 0x03) - { - // This doesn't look like an arpeggio. - minVersion = std::max(minVersion, ST2_00_Exterminator); - } else if(eff == 1 && (param == 0x37 || param == 0x47) && minVersion <= ST2_00_Exterminator) - { - // This suspiciously looks like an arpeggio. - // Catch sleepwalk.mod by Karsten Obarski, which has a default tempo of 125 rather than 120 in the header, so gets mis-identified as a later tracker version. - minVersion = hasDiskNames ? UST1_80 : UST1_00; - } - break; - case 0x0B: - minVersion = ST2_00; - break; - case 0x0C: - case 0x0D: - case 0x0E: - minVersion = std::max(minVersion, ST2_00_Exterminator); - if(eff == 0x0D) - { - emptyCmds = 1; - if(param == 0 && row == 0) - { - // Fix a possible tracking mistake in Blood Money title - who wants to do a pattern break on the first row anyway? - break; - } - numDxx++; - } - break; - case 0x0F: - minVersion = std::max(minVersion, ST_III); - break; - } - } - } - - if(numDxx > 0 && numDxx < 3) - { - // Not many Dxx commands in one pattern means they were probably pattern breaks - minVersion = ST2_00; - } - totalNumDxx += numDxx; - } - - // If there is a huge number of Dxx commands, this is extremely unlikely to be a SoundTracker 2.0 module - if(totalNumDxx > numPatterns + 32u && minVersion == ST2_00) - minVersion = MST1_00; - - file.Seek(patOffset); - - // Reading patterns - if(loadFlags & loadPatternData) - Patterns.ResizeArray(numPatterns); - for(PATTERNINDEX pat = 0; pat < numPatterns; pat++) - { - MODPatternData patternData; - file.ReadArray(patternData); - - if(!(loadFlags & loadPatternData) || !Patterns.Insert(pat, 64)) - { - continue; - } - - uint8 autoSlide[4] = {0, 0, 0, 0}; - for(ROWINDEX row = 0; row < 64; row++) - { - auto rowBase = Patterns[pat].GetRow(row); - for(CHANNELINDEX chn = 0; chn < 4; chn++) - { - ModCommand &m = rowBase[chn]; - auto [command, param] = ReadMODPatternEntry(patternData[row][chn], m); - - if(!param || command == 0x0E) - { - autoSlide[chn] = 0; - } - if(command || param) - { - if(autoSlide[chn] != 0) - { - if(autoSlide[chn] & 0xF0) - { - m.volcmd = VOLCMD_VOLSLIDEUP; - m.vol = autoSlide[chn] >> 4; - } else - { - m.volcmd = VOLCMD_VOLSLIDEDOWN; - m.vol = autoSlide[chn] & 0x0F; - } - } - if(command == 0x0D) - { - if(minVersion != ST2_00) - { - // Dxy is volume slide in some Soundtracker versions, D00 is a pattern break in the latest versions. - command = 0x0A; - } else - { - param = 0; - } - } else if(command == 0x0C) - { - // Volume is sent as-is to the chip, which ignores the highest bit. - param &= 0x7F; - } else if(command == 0x0E && (param > 0x01 || minVersion < ST_IX)) - { - // Import auto-slides as normal slides and fake them using volume column slides. - command = 0x0A; - autoSlide[chn] = param; - } else if(command == 0x0F) - { - // Only the low nibble is evaluated in Soundtracker. - param &= 0x0F; - } - - if(minVersion <= UST1_80) - { - // UST effects - m.param = param; - switch(command) - { - case 0: - // jackdance.mod by Karsten Obarski has 0xy arpeggios... - if(param < 0x03) - { - m.command = CMD_NONE; - } else - { - m.command = CMD_ARPEGGIO; - } - break; - case 1: - m.command = CMD_ARPEGGIO; - break; - case 2: - if(m.param & 0x0F) - { - m.command = CMD_PORTAMENTOUP; - m.param &= 0x0F; - } else if(m.param >> 4) - { - m.command = CMD_PORTAMENTODOWN; - m.param >>= 4; - } - break; - default: - m.command = CMD_NONE; - break; - } - } else - { - ConvertModCommand(m, command, param); - } - } else - { - autoSlide[chn] = 0; - } - } - } - } - - [[maybe_unused]] /* silence clang-tidy deadcode.DeadStores */ const mpt::uchar *madeWithTracker = UL_(""); - switch(minVersion) - { - case UST1_00: - madeWithTracker = UL_("Ultimate Soundtracker 1.0-1.21"); - break; - case UST1_80: - madeWithTracker = UL_("Ultimate Soundtracker 1.8-2.0"); - break; - case ST2_00_Exterminator: - madeWithTracker = UL_("SoundTracker 2.0 / D.O.C. SoundTracker II"); - break; - case ST_III: - madeWithTracker = UL_("Defjam Soundtracker III / Alpha Flight SoundTracker IV / D.O.C. SoundTracker IV / VI"); - break; - case ST_IX: - madeWithTracker = UL_("D.O.C. SoundTracker IX"); - break; - case MST1_00: - madeWithTracker = UL_("Master Soundtracker 1.0"); - break; - case ST2_00: - madeWithTracker = UL_("SoundTracker 2.0 / 2.1 / 2.2"); - break; - } - - m_modFormat.formatName = U_("Soundtracker"); - m_modFormat.type = U_("stk"); - m_modFormat.madeWithTracker = madeWithTracker; - m_modFormat.charset = mpt::Charset::Amiga_no_C1; - - // Reading samples - if(loadFlags & loadSampleData) - { - for(SAMPLEINDEX smp = 1; smp <= 15; smp++) - { - // Looped samples in (Ultimate) Soundtracker seem to ignore all sample data before the actual loop start. - // This avoids the clicks in the first sample of pretend.mod by Karsten Obarski. - file.Skip(Samples[smp].nLoopStart); - Samples[smp].nLength -= Samples[smp].nLoopStart; - Samples[smp].nLoopEnd -= Samples[smp].nLoopStart; - Samples[smp].nLoopStart = 0; - MODSampleHeader::GetSampleFormat().ReadSample(Samples[smp], file); - } - } - - return true; -} - - -CSoundFile::ProbeResult CSoundFile::ProbeFileHeaderICE(MemoryFileReader file, const uint64 *pfilesize) -{ - if(!file.CanRead(1464 + 4)) - { - return ProbeWantMoreData; - } - file.Seek(1464); - char magic[4]; - file.ReadArray(magic); - if(!IsMagic(magic, "MTN\0") && !IsMagic(magic, "IT10")) - { - return ProbeFailure; - } - file.Seek(20); - uint32 invalidBytes = 0; - for(SAMPLEINDEX smp = 1; smp <= 31; smp++) - { - MODSampleHeader sampleHeader; - if(!file.ReadStruct(sampleHeader)) - { - return ProbeWantMoreData; - } - invalidBytes += sampleHeader.GetInvalidByteScore(); - } - if(invalidBytes > MODSampleHeader::INVALID_BYTE_THRESHOLD) - { - return ProbeFailure; - } - const auto [numOrders, numTracks] = file.ReadArray(); - if(numOrders > 128) - { - return ProbeFailure; - } - uint8 tracks[128 * 4]; - file.ReadArray(tracks); - for(auto track : tracks) - { - if(track > numTracks) - { - return ProbeFailure; - } - } - MPT_UNREFERENCED_PARAMETER(pfilesize); - return ProbeSuccess; -} - - -// SoundTracker 2.6 / Ice Tracker variation of the MOD format -// The only real difference to other SoundTracker formats is the way patterns are stored: -// Every pattern consists of four independent, re-usable tracks. -bool CSoundFile::ReadICE(FileReader &file, ModLoadingFlags loadFlags) -{ - char magic[4]; - if(!file.Seek(1464) || !file.ReadArray(magic)) - { - return false; - } - - InitializeGlobals(MOD_TYPE_MOD); - m_playBehaviour.reset(kMODOneShotLoops); - m_playBehaviour.set(kMODIgnorePanning); - m_playBehaviour.set(kMODSampleSwap); // untested - - if(IsMagic(magic, "MTN\0")) - { - m_modFormat.formatName = U_("MnemoTroN SoundTracker"); - m_modFormat.type = U_("st26"); - m_modFormat.madeWithTracker = U_("SoundTracker 2.6"); - m_modFormat.charset = mpt::Charset::Amiga_no_C1; - } else if(IsMagic(magic, "IT10")) - { - m_modFormat.formatName = U_("Ice Tracker"); - m_modFormat.type = U_("ice"); - m_modFormat.madeWithTracker = U_("Ice Tracker 1.0 / 1.1"); - m_modFormat.charset = mpt::Charset::Amiga_no_C1; - } else - { - return false; - } - - // Reading song title - file.Seek(0); - file.ReadString(m_songName, 20); - - // Load Samples - m_nSamples = 31; - uint32 invalidBytes = 0; - for(SAMPLEINDEX smp = 1; smp <= 31; smp++) - { - MODSampleHeader sampleHeader; - file.ReadStruct(sampleHeader); - invalidBytes += ReadSample(sampleHeader, Samples[smp], m_szNames[smp], true); - } - if(invalidBytes > MODSampleHeader::INVALID_BYTE_THRESHOLD) - { - return false; - } - - const auto [numOrders, numTracks] = file.ReadArray(); - if(numOrders > 128) - { - return false; - } - - uint8 tracks[128 * 4]; - file.ReadArray(tracks); - for(auto track : tracks) - { - if(track > numTracks) - { - return false; - } - } - - if(loadFlags == onlyVerifyHeader) - { - return true; - } - - // Now we can be pretty sure that this is a valid MOD file. Set up default song settings. - m_nChannels = 4; - m_nInstruments = 0; - m_nDefaultSpeed = 6; - m_nDefaultTempo.Set(125); - m_nMinPeriod = 14 * 4; - m_nMaxPeriod = 3424 * 4; - m_nSamplePreAmp = 64; - m_SongFlags.set(SONG_PT_MODE | SONG_IMPORTED); - - // Setup channel pan positions and volume - SetupMODPanning(); - - // Reading patterns - Order().resize(numOrders); - uint8 speed[2] = {0, 0}, speedPos = 0; - Patterns.ResizeArray(numOrders); - for(PATTERNINDEX pat = 0; pat < numOrders; pat++) - { - Order()[pat] = pat; - if(!Patterns.Insert(pat, 64)) - continue; - - for(CHANNELINDEX chn = 0; chn < 4; chn++) - { - file.Seek(1468 + tracks[pat * 4 + chn] * 64u * 4u); - ModCommand *m = Patterns[pat].GetpModCommand(0, chn); - - for(ROWINDEX row = 0; row < 64; row++, m += 4) - { - const auto [command, param] = ReadMODPatternEntry(file, *m); - - if((command || param) - && !(command == 0x0E && param >= 0x10) // Exx only sets filter - && !(command >= 0x05 && command <= 0x09)) // These don't exist in ST2.6 - { - ConvertModCommand(*m, command, param); - } else - { - m->command = CMD_NONE; - } - } - } - - // Handle speed command with both nibbles set - this enables auto-swing (alternates between the two nibbles) - auto m = Patterns[pat].begin(); - for(ROWINDEX row = 0; row < 64; row++) - { - for(CHANNELINDEX chn = 0; chn < 4; chn++, m++) - { - if(m->command == CMD_SPEED || m->command == CMD_TEMPO) - { - m->command = CMD_SPEED; - speedPos = 0; - if(m->param & 0xF0) - { - if((m->param >> 4) != (m->param & 0x0F) && (m->param & 0x0F) != 0) - { - // Both nibbles set - speed[0] = m->param >> 4; - speed[1] = m->param & 0x0F; - speedPos = 1; - } - m->param >>= 4; - } - } - } - if(speedPos) - { - Patterns[pat].WriteEffect(EffectWriter(CMD_SPEED, speed[speedPos - 1]).Row(row)); - speedPos++; - if(speedPos == 3) - speedPos = 1; - } - } - } - - // Reading samples - if(loadFlags & loadSampleData) - { - file.Seek(1468 + numTracks * 64u * 4u); - for(SAMPLEINDEX smp = 1; smp <= 31; smp++) if(Samples[smp].nLength) - { - SampleIO( - SampleIO::_8bit, - SampleIO::mono, - SampleIO::littleEndian, - SampleIO::signedPCM) - .ReadSample(Samples[smp], file); - } - } - - return true; -} - - - -struct PT36Header -{ - char magicFORM[4]; // "FORM" - uint32be size; - char magicMODL[4]; // "MODL" -}; - -MPT_BINARY_STRUCT(PT36Header, 12) - - -static bool ValidateHeader(const PT36Header &fileHeader) -{ - if(std::memcmp(fileHeader.magicFORM, "FORM", 4)) - { - return false; - } - if(std::memcmp(fileHeader.magicMODL, "MODL", 4)) - { - return false; - } - return true; -} - - -CSoundFile::ProbeResult CSoundFile::ProbeFileHeaderPT36(MemoryFileReader file, const uint64 *pfilesize) -{ - PT36Header fileHeader; - if(!file.ReadStruct(fileHeader)) - { - return ProbeWantMoreData; - } - if(!ValidateHeader(fileHeader)) - { - return ProbeFailure; - } - MPT_UNREFERENCED_PARAMETER(pfilesize); - return ProbeSuccess; -} - - -// ProTracker 3.6 version of the MOD format -// Basically just a normal ProTracker mod with different magic, wrapped in an IFF file. -// The "PTDT" chunk is passed to the normal MOD loader. -bool CSoundFile::ReadPT36(FileReader &file, ModLoadingFlags loadFlags) -{ - file.Rewind(); - - PT36Header fileHeader; - if(!file.ReadStruct(fileHeader)) - { - return false; - } - if(!ValidateHeader(fileHeader)) - { - return false; - } - - bool ok = false, infoOk = false; - FileReader commentChunk; - mpt::ustring version; - PT36InfoChunk info; - MemsetZero(info); - - // Go through IFF chunks... - PT36IffChunk iffHead; - if(!file.ReadStruct(iffHead)) - { - return false; - } - // First chunk includes "MODL" magic in size - iffHead.chunksize -= 4; - - do - { - // All chunk sizes include chunk header - iffHead.chunksize -= 8; - if(loadFlags == onlyVerifyHeader && iffHead.signature == PT36IffChunk::idPTDT) - { - return true; - } - - FileReader chunk = file.ReadChunk(iffHead.chunksize); - if(!chunk.IsValid()) - { - break; - } - - switch(iffHead.signature) - { - case PT36IffChunk::idVERS: - chunk.Skip(4); - if(chunk.ReadMagic("PT") && iffHead.chunksize > 6) - { - chunk.ReadString(version, mpt::Charset::Amiga_no_C1, iffHead.chunksize - 6); - } - break; - - case PT36IffChunk::idINFO: - infoOk = chunk.ReadStruct(info); - break; - - case PT36IffChunk::idCMNT: - commentChunk = chunk; - break; - - case PT36IffChunk::idPTDT: - ok = ReadMOD(chunk, loadFlags); - break; - } - } while(file.ReadStruct(iffHead)); - - if(version.empty()) - { - version = U_("3.6"); - } - - // both an info chunk and a module are required - if(ok && infoOk) - { - bool vblank = (info.flags & 0x100) == 0; - m_playBehaviour.set(kMODVBlankTiming, vblank); - if(info.volume != 0) - m_nSamplePreAmp = std::min(uint16(64), static_cast(info.volume)); - if(info.tempo != 0 && !vblank) - m_nDefaultTempo.Set(info.tempo); - - if(info.name[0]) - m_songName = mpt::String::ReadBuf(mpt::String::maybeNullTerminated, info.name); - - if(mpt::is_in_range(info.dateMonth, 1, 12) && mpt::is_in_range(info.dateDay, 1, 31) && mpt::is_in_range(info.dateHour, 0, 23) - && mpt::is_in_range(info.dateMinute, 0, 59) && mpt::is_in_range(info.dateSecond, 0, 59)) - { -#ifdef MODPLUG_TRACKER - m_modFormat.timezone = mpt::Date::LogicalTimezone::Local; -#else - m_modFormat.timezone = mpt::Date::LogicalTimezone::Unspecified; -#endif - FileHistory mptHistory; - mptHistory.loadDate.year = info.dateYear + 1900; - mptHistory.loadDate.month = info.dateMonth; - mptHistory.loadDate.day = info.dateDay; - mptHistory.loadDate.hours = info.dateHour; - mptHistory.loadDate.minutes = info.dateMinute; - mptHistory.loadDate.seconds = info.dateSecond; - m_FileHistory.push_back(mptHistory); - } - } - if(ok) - { - if(commentChunk.IsValid()) - { - std::string author; - commentChunk.ReadString(author, 32); - if(author != "UNNAMED AUTHOR") - m_songArtist = mpt::ToUnicode(mpt::Charset::Amiga_no_C1, author); - if(!commentChunk.NoBytesLeft()) - { - m_songMessage.ReadFixedLineLength(commentChunk, commentChunk.BytesLeft(), 40, 0); - } - } - - m_modFormat.madeWithTracker = U_("ProTracker ") + version; - } - m_SongFlags.set(SONG_PT_MODE); - m_playBehaviour.set(kMODIgnorePanning); - m_playBehaviour.set(kMODOneShotLoops); - m_playBehaviour.reset(kMODSampleSwap); - - return ok; -} - - #ifndef MODPLUG_NO_FILESAVE bool CSoundFile::SaveMod(std::ostream &f) const { - if(m_nChannels == 0) + if(GetNumChannels() == 0) { return false; } @@ -2522,7 +1076,7 @@ bool CSoundFile::SaveMod(std::ostream &f) const invalidInstruments = true; events[eventByte + 0] = ((period >> 8) & 0x0F) | (instr & 0x10); - events[eventByte + 1] = period & 0xFF; + events[eventByte + 1] = period & 0xFFu; events[eventByte + 2] = ((instr & 0x0F) << 4) | (command & 0x0F); events[eventByte + 3] = param; } diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_mt2.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_mt2.cpp index f4580713b..dd2e005d1 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_mt2.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_mt2.cpp @@ -214,8 +214,7 @@ static bool ConvertMT2Command(CSoundFile *that, ModCommand &m, MT2Command &p) // Volume Column if(p.vol >= 0x10 && p.vol <= 0x90) { - m.volcmd = VOLCMD_VOLUME; - m.vol = (p.vol - 0x10) / 2; + m.SetVolumeCommand(VOLCMD_VOLUME, static_cast((p.vol - 0x10) / 2)); } else if(p.vol >= 0xA0 && p.vol <= 0xAF) { m.volcmd = VOLCMD_VOLSLIDEDOWN; @@ -439,7 +438,7 @@ bool CSoundFile::ReadMT2(FileReader &file, ModLoadingFlags loadFlags) { return false; } - if(!file.CanRead(mpt::saturate_cast(GetHeaderMinimumAdditionalSize(fileHeader)))) + if(!file.CanRead(mpt::saturate_cast(GetHeaderMinimumAdditionalSize(fileHeader)))) { return false; } @@ -448,18 +447,16 @@ bool CSoundFile::ReadMT2(FileReader &file, ModLoadingFlags loadFlags) return true; } - InitializeGlobals(MOD_TYPE_MT2); - InitializeChannels(); + InitializeGlobals(MOD_TYPE_MT2, fileHeader.numChannels); m_modFormat.formatName = MPT_UFORMAT("MadTracker {}.{}")(fileHeader.version >> 8, mpt::ufmt::hex0<2>(fileHeader.version & 0xFF)); - m_modFormat.type = U_("mt2"); + m_modFormat.type = UL_("mt2"); m_modFormat.madeWithTracker = mpt::ToUnicode(mpt::Charset::Windows1252, mpt::String::ReadBuf(mpt::String::maybeNullTerminated, fileHeader.trackerName)); m_modFormat.charset = mpt::Charset::Windows1252; m_songName = mpt::String::ReadBuf(mpt::String::maybeNullTerminated, fileHeader.songName); - m_nChannels = fileHeader.numChannels; - m_nDefaultSpeed = Clamp(fileHeader.ticksPerLine, 1, 31); - m_nDefaultTempo.Set(125); + Order().SetDefaultSpeed(Clamp(fileHeader.ticksPerLine, 1, 31)); + Order().SetDefaultTempoInt(125); m_SongFlags = SONG_LINEARSLIDES | SONG_ITCOMPATGXX | SONG_EXFILTERRANGE; m_nInstruments = fileHeader.numInstruments; m_nSamples = fileHeader.numSamples; @@ -479,11 +476,11 @@ bool CSoundFile::ReadMT2(FileReader &file, ModLoadingFlags loadFlags) FileReader drumData = file.ReadChunk(hasDrumChannels ? sizeof(MT2DrumsData) : 0); FileReader extraData = file.ReadChunk(file.ReadUint32LE()); - const CHANNELINDEX channelsWithoutDrums = m_nChannels; + const CHANNELINDEX channelsWithoutDrums = GetNumChannels(); static_assert(MAX_BASECHANNELS >= 64 + 8); if(hasDrumChannels) { - m_nChannels += 8; + ChnSettings.resize(GetNumChannels() + 8); } bool hasLegacyTempo = false; @@ -564,11 +561,11 @@ bool CSoundFile::ReadMT2(FileReader &file, ModLoadingFlags loadFlags) { if(hasLegacyTempo) { - m_nDefaultTempo.SetRaw(Util::muldivr(110250, TEMPO::fractFact, fileHeader.samplesPerTick)); + Order().SetDefaultTempo(TEMPO{}.SetRaw(Util::muldivr(110250, TEMPO::fractFact, fileHeader.samplesPerTick))); m_nTempoMode = TempoMode::Classic; } else { - m_nDefaultTempo = TEMPO(44100.0 * 60.0 / (m_nDefaultSpeed * m_nDefaultRowsPerBeat * fileHeader.samplesPerTick)); + Order().SetDefaultTempo(TEMPO(44100.0 * 60.0 / (Order().GetDefaultSpeed() * m_nDefaultRowsPerBeat * fileHeader.samplesPerTick))); m_nTempoMode = TempoMode::Modern; } } @@ -590,7 +587,7 @@ bool CSoundFile::ReadMT2(FileReader &file, ModLoadingFlags loadFlags) double d = chunk.ReadDoubleLE(); if(d > 0.00000001) { - m_nDefaultTempo = TEMPO(44100.0 * 60.0 / (m_nDefaultSpeed * m_nDefaultRowsPerBeat * d)); + Order().SetDefaultTempo(TEMPO(44100.0 * 60.0 / (Order().GetDefaultSpeed() * m_nDefaultRowsPerBeat * d))); } } break; @@ -619,7 +616,7 @@ bool CSoundFile::ReadMT2(FileReader &file, ModLoadingFlags loadFlags) break; case MagicLE("TRKL"): - for(CHANNELINDEX i = 0; i < m_nChannels && chunk.CanRead(1); i++) + for(CHANNELINDEX i = 0; i < GetNumChannels() && chunk.CanRead(1); i++) { std::string name; chunk.ReadNullString(name); @@ -686,12 +683,12 @@ bool CSoundFile::ReadMT2(FileReader &file, ModLoadingFlags loadFlags) if(libraryName.length() > 4 && libraryName[libraryName.length() - 4] == '.') { // Remove ".dll" from library name - libraryName.resize(libraryName.length() - 4 ); + libraryName.resize(libraryName.length() - 4); } mixPlug.Info.szLibraryName = libraryName; mixPlug.Info.dwPluginId1 = Vst::kEffectMagic; mixPlug.Info.dwPluginId2 = vstHeader.fxID; - if(vstHeader.track >= m_nChannels) + if(vstHeader.track >= GetNumChannels()) { mixPlug.SetMasterEffect(true); } else @@ -844,7 +841,7 @@ bool CSoundFile::ReadMT2(FileReader &file, ModLoadingFlags loadFlags) const ROWINDEX numRows = static_cast(chunk.GetLength() / 32u); for(ROWINDEX row = 0; row < Patterns[writePat].GetNumRows(); row++) { - ModCommand *m = Patterns[writePat].GetpModCommand(row, m_nChannels - 8); + ModCommand *m = Patterns[writePat].GetpModCommand(row, GetNumChannels() - 8); for(CHANNELINDEX chn = 0; chn < 8; chn++, m++) { *m = ModCommand{}; @@ -876,7 +873,7 @@ bool CSoundFile::ReadMT2(FileReader &file, ModLoadingFlags loadFlags) // Read automation envelopes if(fileHeader.flags & MT2FileHeader::automation) { - const uint32 numEnvelopes = ((fileHeader.flags & MT2FileHeader::drumsAutomation) ? m_nChannels : channelsWithoutDrums) + const uint32 numEnvelopes = ((fileHeader.flags & MT2FileHeader::drumsAutomation) ? GetNumChannels() : channelsWithoutDrums) + ((fileHeader.version >= 0x0250) ? numVST : 0) + ((fileHeader.flags & MT2FileHeader::masterAutomation) ? 1 : 0); @@ -1039,7 +1036,7 @@ bool CSoundFile::ReadMT2(FileReader &file, ModLoadingFlags loadFlags) if(sampleHeader.panning == -128) mptSmp.uFlags.set(CHN_SURROUND); else - mptSmp.nPan = sampleHeader.panning + 128; + mptSmp.nPan = static_cast(sampleHeader.panning + 128); mptSmp.uFlags.set(CHN_PANNING); mptSmp.RelativeTone = sampleHeader.note; diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_mtm.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_mtm.cpp index 55d408d17..16621c5bc 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_mtm.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_mtm.cpp @@ -126,7 +126,7 @@ bool CSoundFile::ReadMTM(FileReader &file, ModLoadingFlags loadFlags) { return false; } - if(!file.CanRead(mpt::saturate_cast(GetHeaderMinimumAdditionalSize(fileHeader)))) + if(!file.CanRead(mpt::saturate_cast(GetHeaderMinimumAdditionalSize(fileHeader)))) { return false; } @@ -135,13 +135,12 @@ bool CSoundFile::ReadMTM(FileReader &file, ModLoadingFlags loadFlags) return true; } - InitializeGlobals(MOD_TYPE_MTM); + InitializeGlobals(MOD_TYPE_MTM, fileHeader.numChannels); m_songName = mpt::String::ReadBuf(mpt::String::maybeNullTerminated, fileHeader.songName); m_nSamples = fileHeader.numSamples; - m_nChannels = fileHeader.numChannels; - m_modFormat.formatName = U_("MultiTracker"); - m_modFormat.type = U_("mtm"); + m_modFormat.formatName = UL_("MultiTracker"); + m_modFormat.type = UL_("mtm"); m_modFormat.madeWithTracker = MPT_UFORMAT("MultiTracker {}.{}")(fileHeader.version >> 4, fileHeader.version & 0x0F); m_modFormat.charset = mpt::Charset::CP437; @@ -157,7 +156,6 @@ bool CSoundFile::ReadMTM(FileReader &file, ModLoadingFlags loadFlags) // Setting Channel Pan Position for(CHANNELINDEX chn = 0; chn < GetNumChannels(); chn++) { - ChnSettings[chn].Reset(); ChnSettings[chn].nPan = ((fileHeader.panPos[chn] & 0x0F) << 4) + 8; } diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_mus_km.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_mus_km.cpp index da67bb869..9f42afba3 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_mus_km.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_mus_km.cpp @@ -154,7 +154,7 @@ bool CSoundFile::ReadMUS_KM(FileReader &file, ModLoadingFlags loadFlags) return false; if(!ValidateHeader(fileHeader)) return false; - if(!file.CanRead(mpt::saturate_cast(GetHeaderMinimumAdditionalSize(fileHeader)))) + if(!file.CanRead(mpt::saturate_cast(GetHeaderMinimumAdditionalSize(fileHeader)))) return false; if(loadFlags == onlyVerifyHeader) return true; @@ -169,10 +169,8 @@ bool CSoundFile::ReadMUS_KM(FileReader &file, ModLoadingFlags loadFlags) if(songChunks.empty() || sampleChunks.empty()) return false; - InitializeGlobals(MOD_TYPE_MOD); - InitializeChannels(); - m_SongFlags = SONG_AMIGALIMITS | SONG_IMPORTED | SONG_ISAMIGA; // Yes, those were not Amiga games but the format fully conforms to Amiga limits, so allow the Amiga Resampler to be used. - m_nChannels = 4; + InitializeGlobals(MOD_TYPE_MOD, 4); + m_SongFlags = SONG_AMIGALIMITS | SONG_IMPORTED | SONG_FORMAT_NO_VOLCOL | SONG_ISAMIGA; // Yes, those were not Amiga games but the format fully conforms to Amiga limits, so allow the Amiga Resampler to be used. m_nSamples = 0; static constexpr uint16 MUS_SAMPLE_UNUSED = 255; // Sentinel value to check if a sample needs to be duplicated @@ -375,8 +373,8 @@ bool CSoundFile::ReadMUS_KM(FileReader &file, ModLoadingFlags loadFlags) Order.SetSequence(0); - m_modFormat.formatName = U_("Karl Morton Music Format"); - m_modFormat.type = U_("mus"); + m_modFormat.formatName = UL_("Karl Morton Music Format"); + m_modFormat.type = UL_("mus"); m_modFormat.charset = mpt::Charset::CP437; return true; diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_okt.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_okt.cpp index bd08449ce..27f39a25f 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_okt.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_okt.cpp @@ -136,7 +136,7 @@ static void ReadOKTPattern(FileReader &chunk, PATTERNINDEX pat, CSoundFile &sndF // Default volume only works on raw Paula channels if(pairedChn[chn] && sample.nVolume < 256) m.SetVolumeCommand(VOLCMD_VOLUME, 64); - + // If channel and sample type don't match, stop this channel (add 100 to the instrument number to make it understandable what happened during import) if((sample.cues[0] == 1 && pairedChn[chn] != 0) || (sample.cues[0] == 0 && pairedChn[chn] == 0)) { @@ -326,10 +326,10 @@ bool CSoundFile::ReadOKT(FileReader &file, ModLoadingFlags loadFlags) std::array pairedChn{{}}; ORDERINDEX numOrders = 0; - InitializeGlobals(MOD_TYPE_OKT); + InitializeGlobals(MOD_TYPE_OKT, 0); - m_modFormat.formatName = U_("Oktalyzer"); - m_modFormat.type = U_("okt"); + m_modFormat.formatName = UL_("Oktalyzer"); + m_modFormat.type = UL_("okt"); m_modFormat.charset = mpt::Charset::Amiga_no_C1; // Go through IFF chunks... @@ -347,20 +347,21 @@ bool CSoundFile::ReadOKT(FileReader &file, ModLoadingFlags loadFlags) { case OktIffChunk::idCMOD: // Channel setup table - if(m_nChannels == 0 && chunk.GetLength() >= 8) + if(GetNumChannels() == 0 && chunk.CanRead(8)) { const auto chnTable = chunk.ReadArray(); + ChnSettings.reserve(8); + CHANNELINDEX realChn = 0; for(CHANNELINDEX chn = 0; chn < 4; chn++) { if(chnTable[chn]) { - pairedChn[m_nChannels] = 1; - pairedChn[m_nChannels + 1] = -1; - ChnSettings[m_nChannels].Reset(); - ChnSettings[m_nChannels++].nPan = (((chn & 3) == 1) || ((chn & 3) == 2)) ? 0xC0 : 0x40; + pairedChn[realChn++] = 1; + pairedChn[realChn] = -1; + ChnSettings.emplace_back().nPan = (((chn & 3) == 1) || ((chn & 3) == 2)) ? 0xC0 : 0x40; } - ChnSettings[m_nChannels].Reset(); - ChnSettings[m_nChannels++].nPan = (((chn & 3) == 1) || ((chn & 3) == 2)) ? 0xC0 : 0x40; + realChn++; + ChnSettings.emplace_back().nPan = (((chn & 3) == 1) || ((chn & 3) == 2)) ? 0xC0 : 0x40; } if(loadFlags == onlyVerifyHeader) @@ -383,7 +384,7 @@ bool CSoundFile::ReadOKT(FileReader &file, ModLoadingFlags loadFlags) // Read default speed if(chunk.GetLength() >= 2) { - m_nDefaultSpeed = Clamp(chunk.ReadUint16BE(), uint16(1), uint16(255)); + Order().SetDefaultSpeed(Clamp(chunk.ReadUint16BE(), uint16(1), uint16(255))); } break; @@ -423,14 +424,15 @@ bool CSoundFile::ReadOKT(FileReader &file, ModLoadingFlags loadFlags) } // If there wasn't even a CMOD chunk, we can't really load this. - if(m_nChannels == 0) + if(GetNumChannels() == 0) return false; - m_nDefaultTempo.Set(125); + Order().SetDefaultTempoInt(125); m_nDefaultGlobalVolume = MAX_GLOBAL_VOLUME; m_nSamplePreAmp = m_nVSTiVolume = 48; m_nMinPeriod = 113 * 4; m_nMaxPeriod = 856 * 4; + m_SongFlags.set(SONG_FASTPORTAS); // Fix orderlist Order().resize(numOrders); @@ -455,7 +457,6 @@ bool CSoundFile::ReadOKT(FileReader &file, ModLoadingFlags loadFlags) ModSample &mptSample = Samples[smp]; const bool needCopy = mptSample.cues[1] != 0; - mptSample.SetDefaultCuePoints(); if(mptSample.nLength == 0) continue; diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_plm.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_plm.cpp index 402022abf..d135eddf4 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_plm.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_plm.cpp @@ -131,7 +131,7 @@ bool CSoundFile::ReadPLM(FileReader &file, ModLoadingFlags loadFlags) { return false; } - if(!file.CanRead(mpt::saturate_cast(GetHeaderMinimumAdditionalSize(fileHeader)))) + if(!file.CanRead(mpt::saturate_cast(GetHeaderMinimumAdditionalSize(fileHeader)))) { return false; } @@ -145,21 +145,19 @@ bool CSoundFile::ReadPLM(FileReader &file, ModLoadingFlags loadFlags) return false; } - InitializeGlobals(MOD_TYPE_PLM); - InitializeChannels(); + InitializeGlobals(MOD_TYPE_PLM, fileHeader.numChannels + 1); // Additional channel for writing pattern breaks m_SongFlags = SONG_ITOLDEFFECTS; m_playBehaviour.set(kApplyOffsetWithoutNote); - m_modFormat.formatName = U_("Disorder Tracker 2"); - m_modFormat.type = U_("plm"); + m_modFormat.formatName = UL_("Disorder Tracker 2"); + m_modFormat.type = UL_("plm"); m_modFormat.charset = mpt::Charset::CP437; // Some PLMs use ASCIIZ, some space-padding strings...weird. Oh, and the file browser stops at 0 bytes in the name, the main GUI doesn't. m_songName = mpt::String::ReadBuf(mpt::String::spacePadded, fileHeader.songName); - m_nChannels = fileHeader.numChannels + 1; // Additional channel for writing pattern breaks m_nSamplePreAmp = fileHeader.amplify; - m_nDefaultTempo.Set(fileHeader.tempo); - m_nDefaultSpeed = fileHeader.speed; + Order().SetDefaultTempoInt(fileHeader.tempo); + Order().SetDefaultSpeed(fileHeader.speed); for(CHANNELINDEX chn = 0; chn < fileHeader.numChannels; chn++) { ChnSettings[chn].nPan = fileHeader.panPos[chn] * 0x11; @@ -275,7 +273,8 @@ bool CSoundFile::ReadPLM(FileReader &file, ModLoadingFlags loadFlags) const uint32 patternEnd = ord.x + patHeader.numRows; maxPos = std::max(maxPos, patternEnd); - ModCommand::NOTE lastNote[32] = { 0 }; + std::array lastNote; + lastNote.fill(NOTE_NONE); for(ROWINDEX r = 0; r < patHeader.numRows; r++, curRow++) { if(curRow >= rowsPerPat) @@ -296,7 +295,7 @@ bool CSoundFile::ReadPLM(FileReader &file, ModLoadingFlags loadFlags) { const auto [note, instr, volume, command, param] = file.ReadArray(); if(note > 0 && note < 0x90) - lastNote[c] = m->note = (note >> 4) * 12 + (note & 0x0F) + 12 + NOTE_MIN; + lastNote[c] = m->note = static_cast((note >> 4) * 12 + (note & 0x0F) + 12 + NOTE_MIN); else m->note = NOTE_NONE; m->instr = instr; @@ -324,7 +323,7 @@ bool CSoundFile::ReadPLM(FileReader &file, ModLoadingFlags loadFlags) { uint16 target = order[m->param].x; m->param = static_cast(target / rowsPerPat); - ModCommand *mBreak = Patterns[pat].GetpModCommand(curRow, m_nChannels - 1); + ModCommand *mBreak = Patterns[pat].GetpModCommand(curRow, GetNumChannels() - 1); mBreak->command = CMD_PATTERNBREAK; mBreak->param = static_cast(target % rowsPerPat); } @@ -332,7 +331,7 @@ bool CSoundFile::ReadPLM(FileReader &file, ModLoadingFlags loadFlags) case 0x0C: // Jump to end of order { m->param = static_cast(patternEnd / rowsPerPat); - ModCommand *mBreak = Patterns[pat].GetpModCommand(curRow, m_nChannels - 1); + ModCommand *mBreak = Patterns[pat].GetpModCommand(curRow, GetNumChannels() - 1); mBreak->command = CMD_PATTERNBREAK; mBreak->param = static_cast(patternEnd % rowsPerPat); } @@ -386,7 +385,7 @@ bool CSoundFile::ReadPLM(FileReader &file, ModLoadingFlags loadFlags) PATTERNINDEX blankPat = PATTERNINDEX_INVALID; for(auto &pat : Order()) { - if(pat == Order.GetInvalidPatIndex()) + if(pat == PATTERNINDEX_INVALID) { if(blankPat == PATTERNINDEX_INVALID) { diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_psm.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_psm.cpp index 9cc1f09fe..2732e284e 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_psm.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_psm.cpp @@ -14,10 +14,6 @@ #include "mpt/parse/parse.hpp" -#ifdef LIBOPENMPT_BUILD -#define MPT_PSM_USE_REAL_SUBSONGS -#endif - OPENMPT_NAMESPACE_BEGIN //////////////////////////////////////////////////////////// @@ -114,7 +110,7 @@ struct PSMSampleHeader // On the other hand, sample 8 of MUSIC_A.PSM from Extreme Pinball will sound detuned if we don't adjust the loop end here. if(loopEnd) mptSmp.nLoopEnd = loopEnd + 1; - mptSmp.nVolume = (defaultVolume + 1) * 2; + mptSmp.nVolume = static_cast((defaultVolume + 1) * 2); mptSmp.uFlags.set(CHN_LOOP, (flags & 0x80) != 0); LimitMax(mptSmp.nLoopEnd, mptSmp.nLength); LimitMax(mptSmp.nLoopStart, mptSmp.nLoopEnd); @@ -152,7 +148,7 @@ struct PSMSinariaSampleHeader mptSmp.nLength = sampleLength; mptSmp.nLoopStart = loopStart; mptSmp.nLoopEnd = loopEnd; - mptSmp.nVolume = (defaultVolume + 1) * 2; + mptSmp.nVolume = static_cast((defaultVolume + 1) * 2); mptSmp.uFlags.set(CHN_LOOP, (flags & 0x80) != 0); LimitMax(mptSmp.nLoopEnd, mptSmp.nLength); LimitMax(mptSmp.nLoopStart, mptSmp.nLoopEnd); @@ -166,14 +162,12 @@ struct PSMSubSong // For internal use (pattern conversion) { std::vector channelPanning, channelVolume; std::vector channelSurround; - ORDERINDEX startOrder = ORDERINDEX_INVALID, endOrder = ORDERINDEX_INVALID, restartPos = 0; - uint8 defaultTempo = 125, defaultSpeed = 6; char songName[10] = {}; - PSMSubSong() - : channelPanning(MAX_BASECHANNELS, 128) - , channelVolume(MAX_BASECHANNELS, 64) - , channelSurround(MAX_BASECHANNELS, false) + PSMSubSong(CHANNELINDEX numChannels) + : channelPanning(numChannels, 128) + , channelVolume(numChannels, 64) + , channelSurround(numChannels, false) { } void SetPanning(CHANNELINDEX chn, uint8 type, int16 pan, bool &subsongPanningDiffers, std::vector &subsongs) @@ -223,7 +217,8 @@ static PATTERNINDEX ReadPSMPatternIndex(FileReader &file, bool &sinariaFormat) { char patternID[5]; uint8 offset = 1; - file.ReadString(patternID, 4); + if(!file.ReadString(patternID, 4)) + return 0; if(!memcmp(patternID, "PATT", 4)) { file.ReadString(patternID, 4); @@ -317,8 +312,21 @@ bool CSoundFile::ReadPSM(FileReader &file, ModLoadingFlags loadFlags) else if(loadFlags == onlyVerifyHeader) return true; + auto songChunks = chunks.GetAllChunks(PSMChunk::idSONG); + CHANNELINDEX numChannels = 0; + for(FileReader chunk : songChunks) + { + PSMSongHeader songHeader; + if(!chunk.ReadStruct(songHeader) || songHeader.compression != 0x01) // No compression for PSM files + return false; + // Subsongs *might* have different channel count + numChannels = Clamp(static_cast(songHeader.numChannels), numChannels, MAX_BASECHANNELS); + } + if(!numChannels) + return false; + // Yep, this seems to be a valid file. - InitializeGlobals(MOD_TYPE_PSM); + InitializeGlobals(MOD_TYPE_PSM, numChannels); m_SongFlags = SONG_ITOLDEFFECTS | SONG_ITCOMPATGXX; // "TITL" - Song Title @@ -332,22 +340,14 @@ bool CSoundFile::ReadPSM(FileReader &file, ModLoadingFlags loadFlags) bool sinariaFormat = false; // The game "Sinaria" uses a slightly modified PSM structure - in some ways it's more like PSM16 (e.g. effects). // "SONG" - Subsong information (channel count etc) - auto songChunks = chunks.GetAllChunks(PSMChunk::idSONG); for(ChunkReader chunk : songChunks) { PSMSongHeader songHeader; - if(!chunk.ReadStruct(songHeader) - || songHeader.compression != 0x01) // No compression for PSM files - { - return false; - } - // Subsongs *might* have different channel count - m_nChannels = Clamp(static_cast(songHeader.numChannels), m_nChannels, MAX_BASECHANNELS); + chunk.ReadStruct(songHeader); - PSMSubSong subsong; + PSMSubSong subsong{GetNumChannels()}; mpt::String::WriteAutoBuf(subsong.songName) = mpt::String::ReadBuf(mpt::String::nullTerminated, songHeader.songType); -#ifdef MPT_PSM_USE_REAL_SUBSONGS if(!Order().empty()) { // Add a new sequence for this subsong @@ -355,7 +355,6 @@ bool CSoundFile::ReadPSM(FileReader &file, ModLoadingFlags loadFlags) break; } Order().SetName(mpt::ToUnicode(mpt::Charset::CP437, subsong.songName)); -#endif // MPT_PSM_USE_REAL_SUBSONGS // Read "Sub chunks" auto subChunks = chunk.ReadChunks(1); @@ -409,14 +408,11 @@ bool CSoundFile::ReadPSM(FileReader &file, ModLoadingFlags loadFlags) { case 0x01: // Play order list item { - if(subsong.startOrder == ORDERINDEX_INVALID) - subsong.startOrder = Order().GetLength(); - subsong.endOrder = Order().GetLength(); PATTERNINDEX pat = ReadPSMPatternIndex(subChunk, sinariaFormat); if(pat == 0xFF) - pat = Order.GetInvalidPatIndex(); + pat = PATTERNINDEX_INVALID; else if(pat == 0xFE) - pat = Order.GetIgnoreIndex(); + pat = PATTERNINDEX_SKIP; Order().push_back(pat); // Decide whether this is the first order chunk or not (for finding out the correct restart position) if(firstOrderChunk == uint16_max) @@ -433,8 +429,7 @@ bool CSoundFile::ReadPSM(FileReader &file, ModLoadingFlags loadFlags) { uint16 restartChunk = subChunk.ReadUint16LE(); if(restartChunk >= firstOrderChunk) - subsong.restartPos = static_cast(restartChunk - firstOrderChunk); // Close enough - we assume that order list is continuous (like in any real-world PSM) - Order().SetRestartPos(subsong.restartPos); + Order().SetRestartPos(static_cast(restartChunk - firstOrderChunk)); // Close enough - we assume that order list is continuous (like in any real-world PSM) if(opcode == 0x03) subChunk.Skip(1); } @@ -452,11 +447,11 @@ bool CSoundFile::ReadPSM(FileReader &file, ModLoadingFlags loadFlags) break; case 0x07: // Default Speed - subsong.defaultSpeed = subChunk.ReadUint8(); + Order().SetDefaultSpeed(subChunk.ReadUint8()); break; case 0x08: // Default Tempo - subsong.defaultTempo = subChunk.ReadUint8(); + Order().SetDefaultTempoInt(subChunk.ReadUint8()); break; case 0x0C: // Sample map table @@ -506,8 +501,8 @@ bool CSoundFile::ReadPSM(FileReader &file, ModLoadingFlags loadFlags) case PSMChunk::idPPAN: // PPAN - Channel panning table (used in Sinaria) // In some Sinaria tunes, this is actually longer than 2 * channels... - MPT_ASSERT(subChunkHead.GetLength() >= m_nChannels * 2u); - for(CHANNELINDEX chn = 0; chn < m_nChannels; chn++) + MPT_ASSERT(subChunkHead.GetLength() >= GetNumChannels() * 2u); + for(CHANNELINDEX chn = 0; chn < GetNumChannels(); chn++) { if(!subChunk.CanRead(2)) break; @@ -532,17 +527,11 @@ bool CSoundFile::ReadPSM(FileReader &file, ModLoadingFlags loadFlags) } // Attach this subsong to the subsong list - finally, all "sub sub sub ..." chunks are parsed. - if(subsong.startOrder != ORDERINDEX_INVALID && subsong.endOrder != ORDERINDEX_INVALID) - { - // Separate subsongs by "---" patterns - Order().push_back(); + if(!Order().empty()) subsongs.push_back(subsong); - } } -#ifdef MPT_PSM_USE_REAL_SUBSONGS Order.SetSequence(0); -#endif // MPT_PSM_USE_REAL_SUBSONGS if(subsongs.empty()) return false; @@ -595,22 +584,18 @@ bool CSoundFile::ReadPSM(FileReader &file, ModLoadingFlags loadFlags) } // Make the default variables of the first subsong global - m_nDefaultSpeed = subsongs[0].defaultSpeed; - m_nDefaultTempo.Set(subsongs[0].defaultTempo); - Order().SetRestartPos(subsongs[0].restartPos); - for(CHANNELINDEX chn = 0; chn < m_nChannels; chn++) + for(CHANNELINDEX chn = 0; chn < GetNumChannels(); chn++) { - ChnSettings[chn].Reset(); ChnSettings[chn].nVolume = subsongs[0].channelVolume[chn]; ChnSettings[chn].nPan = subsongs[0].channelPanning[chn]; ChnSettings[chn].dwFlags.set(CHN_SURROUND, subsongs[0].channelSurround[chn]); } - m_modFormat.formatName = sinariaFormat ? U_("Epic MegaGames MASI (New Version / Sinaria)") : U_("Epic MegaGames MASI (New Version)"); - m_modFormat.type = U_("psm"); + m_modFormat.formatName = sinariaFormat ? UL_("Epic MegaGames MASI (New Version / Sinaria)") : UL_("Epic MegaGames MASI (New Version)"); + m_modFormat.type = UL_("psm"); m_modFormat.charset = mpt::Charset::CP437; - if(!(loadFlags & loadPatternData) || m_nChannels == 0) + if(!(loadFlags & loadPatternData)) { return true; } @@ -659,7 +644,7 @@ bool CSoundFile::ReadPSM(FileReader &file, ModLoadingFlags loadFlags) { const auto [flags, channel] = rowChunk.ReadArray(); // Point to the correct channel - ModCommand &m = rowBase[std::min(static_cast(m_nChannels - 1), static_cast(channel))]; + ModCommand &m = rowBase[std::min(static_cast(GetNumChannels() - 1), static_cast(channel))]; if(flags & noteFlag) { @@ -670,7 +655,7 @@ bool CSoundFile::ReadPSM(FileReader &file, ModLoadingFlags loadFlags) if(note == 0xFF) // Can be found in a few files but is apparently not supported by MASI note = NOTE_NOTECUT; else - if(note < 129) note = (note & 0x0F) + 12 * (note >> 4) + 13; + if(note < 129) note = static_cast((note & 0x0F) + 12 * (note >> 4) + 13); } else { if(note < 85) note += 36; @@ -688,8 +673,7 @@ bool CSoundFile::ReadPSM(FileReader &file, ModLoadingFlags loadFlags) { // Volume present uint8 vol = rowChunk.ReadUint8(); - m.volcmd = VOLCMD_VOLUME; - m.vol = (std::min(vol, uint8(127)) + 1) / 2; + m.SetVolumeCommand(VOLCMD_VOLUME, static_cast((std::min(vol, uint8(127)) + 1) / 2)); } if(flags & effectFlag) @@ -725,36 +709,29 @@ bool CSoundFile::ReadPSM(FileReader &file, ModLoadingFlags loadFlags) // Portamento case 0x0B: // fine portamento up - m.command = CMD_PORTAMENTOUP; - m.param = 0xF0 | ConvertPSMPorta(m.param, sinariaFormat); + m.SetEffectCommand(CMD_PORTAMENTOUP, 0xF0 | ConvertPSMPorta(m.param, sinariaFormat)); break; case 0x0C: // portamento up - m.command = CMD_PORTAMENTOUP; - m.param = ConvertPSMPorta(m.param, sinariaFormat); + m.SetEffectCommand(CMD_PORTAMENTOUP, ConvertPSMPorta(m.param, sinariaFormat)); break; case 0x0D: // fine portamento down - m.command = CMD_PORTAMENTODOWN; - m.param = 0xF0 | ConvertPSMPorta(m.param, sinariaFormat); + m.SetEffectCommand(CMD_PORTAMENTODOWN, 0xF0 | ConvertPSMPorta(m.param, sinariaFormat)); break; case 0x0E: // portamento down - m.command = CMD_PORTAMENTODOWN; - m.param = ConvertPSMPorta(m.param, sinariaFormat); + m.SetEffectCommand(CMD_PORTAMENTODOWN, ConvertPSMPorta(m.param, sinariaFormat)); break; case 0x0F: // tone portamento m.command = CMD_TONEPORTAMENTO; if(!sinariaFormat) m.param >>= 2; break; case 0x11: // glissando control - m.command = CMD_S3MCMDEX; - m.param = 0x10 | (m.param & 0x01); + m.SetEffectCommand(CMD_S3MCMDEX, 0x10 | (m.param & 0x01)); break; case 0x10: // tone portamento + volslide up - m.command = CMD_TONEPORTAVOL; - m.param = m.param & 0xF0; + m.SetEffectCommand(CMD_TONEPORTAVOL, m.param & 0xF0); break; case 0x12: // tone portamento + volslide down - m.command = CMD_TONEPORTAVOL; - m.param = (m.param >> 4) & 0x0F; + m.SetEffectCommand(CMD_TONEPORTAVOL, (m.param >> 4) & 0x0F); break; case 0x13: // ScreamTracker command S - actually hangs / crashes MASI @@ -766,12 +743,10 @@ bool CSoundFile::ReadPSM(FileReader &file, ModLoadingFlags loadFlags) m.command = CMD_VIBRATO; break; case 0x16: // vibrato waveform - m.command = CMD_S3MCMDEX; - m.param = 0x30 | (m.param & 0x0F); + m.SetEffectCommand(CMD_S3MCMDEX, 0x30 | (m.param & 0x0F)); break; case 0x17: // vibrato + volslide up - m.command = CMD_VIBRATOVOL; - m.param = 0xF0 | m.param; + m.SetEffectCommand(CMD_VIBRATOVOL, 0xF0 | m.param); break; case 0x18: // vibrato + volslide down m.command = CMD_VIBRATOVOL; @@ -782,48 +757,40 @@ bool CSoundFile::ReadPSM(FileReader &file, ModLoadingFlags loadFlags) m.command = CMD_TREMOLO; break; case 0x20: // tremolo waveform - m.command = CMD_S3MCMDEX; - m.param = 0x40 | (m.param & 0x0F); + m.SetEffectCommand(CMD_S3MCMDEX, 0x40 | (m.param & 0x0F)); break; // Sample commands case 0x29: // 3-byte offset - we only support the middle byte. - m.command = CMD_OFFSET; - m.param = rowChunk.ReadUint8(); - rowChunk.Skip(1); + m.SetEffectCommand(CMD_OFFSET, rowChunk.ReadArray()[0]); break; case 0x2A: // retrigger m.command = CMD_RETRIG; break; case 0x2B: // note cut - m.command = CMD_S3MCMDEX; - m.param = 0xC0 | (m.param & 0x0F); + m.SetEffectCommand(CMD_S3MCMDEX, 0xC0 | (m.param & 0x0F)); break; case 0x2C: // note delay - m.command = CMD_S3MCMDEX; - m.param = 0xD0 | (m.param & 0x0F); + m.SetEffectCommand(CMD_S3MCMDEX, 0xD0 | (m.param & 0x0F)); break; // Position change case 0x33: // position jump - MASI seems to ignore this command, and CONVERT.EXE never writes it - m.command = CMD_POSITIONJUMP; - m.param /= 2u; // actually it is probably just an index into the order table + // Parameter actually is probably just an index into the order table + m.SetEffectCommand(CMD_POSITIONJUMP, m.param / 2u); rowChunk.Skip(1); break; case 0x34: // pattern break - m.command = CMD_PATTERNBREAK; // When converting from S3M, the parameter is double-BDC-encoded (wtf!) // When converting from MOD, it's in binary. // MASI ignores the parameter entirely, and so do we. - m.param = 0; + m.SetEffectCommand(CMD_PATTERNBREAK, 0); break; case 0x35: // loop pattern - m.command = CMD_S3MCMDEX; - m.param = 0xB0 | (m.param & 0x0F); + m.SetEffectCommand(CMD_S3MCMDEX, 0xB0 | (m.param & 0x0F)); break; case 0x36: // pattern delay - m.command = CMD_S3MCMDEX; - m.param = 0xE0 | (m.param & 0x0F); + m.SetEffectCommand(CMD_S3MCMDEX, 0xE0 | (m.param & 0x0F)); break; // speed change @@ -839,18 +806,15 @@ bool CSoundFile::ReadPSM(FileReader &file, ModLoadingFlags loadFlags) m.command = CMD_ARPEGGIO; break; case 0x48: // set finetune - m.command = CMD_S3MCMDEX; - m.param = 0x20 | (m.param & 0x0F); + m.SetEffectCommand(CMD_S3MCMDEX, 0x20 | (m.param & 0x0F)); break; case 0x49: // set balance - m.command = CMD_S3MCMDEX; - m.param = 0x80 | (m.param & 0x0F); + m.SetEffectCommand(CMD_S3MCMDEX, 0x80 | (m.param & 0x0F)); break; default: m.command = CMD_NONE; break; - } } } @@ -862,59 +826,24 @@ bool CSoundFile::ReadPSM(FileReader &file, ModLoadingFlags loadFlags) // Write subsong "configuration" to patterns (only if there are multiple subsongs) for(size_t i = 0; i < subsongs.size(); i++) { -#ifdef MPT_PSM_USE_REAL_SUBSONGS - ModSequence &order = Order(static_cast(i)); -#else - ModSequence &order = Order(); -#endif // MPT_PSM_USE_REAL_SUBSONGS const PSMSubSong &subsong = subsongs[i]; - PATTERNINDEX startPattern = order[subsong.startOrder]; - if(Patterns.IsValidPat(startPattern)) - { - startPattern = order.EnsureUnique(subsong.startOrder); - // Subsongs with different panning setup -> write to pattern (MUSIC_C.PSM) - // Don't write channel volume for now, as there is no real-world module which needs it. - if(subsongPanningDiffers) - { - for(CHANNELINDEX chn = 0; chn < m_nChannels; chn++) - { - if(subsong.channelSurround[chn]) - Patterns[startPattern].WriteEffect(EffectWriter(CMD_S3MCMDEX, 0x91).Row(0).Channel(chn).RetryNextRow()); - else - Patterns[startPattern].WriteEffect(EffectWriter(CMD_PANNING8, subsong.channelPanning[chn]).Row(0).Channel(chn).RetryNextRow()); - } - } - // Write default tempo/speed to pattern - Patterns[startPattern].WriteEffect(EffectWriter(CMD_SPEED, subsong.defaultSpeed).Row(0).RetryNextRow()); - Patterns[startPattern].WriteEffect(EffectWriter(CMD_TEMPO, subsong.defaultTempo).Row(0).RetryNextRow()); - } + ModSequence &order = Order(static_cast(i)); + const PATTERNINDEX startPattern = order.EnsureUnique(order.GetFirstValidIndex()); + if(startPattern == PATTERNINDEX_INVALID) + continue; -#ifndef MPT_PSM_USE_REAL_SUBSONGS - // Add restart position to the last pattern - PATTERNINDEX endPattern = order[subsong.endOrder]; - if(Patterns.IsValidPat(endPattern)) + // Subsongs with different panning setup -> write to pattern (MUSIC_C.PSM) + // Don't write channel volume for now, as there is no real-world module which needs it. + if(subsongPanningDiffers) { - endPattern = order.EnsureUnique(subsong.endOrder); - ROWINDEX lastRow = Patterns[endPattern].GetNumRows() - 1; - auto m = Patterns[endPattern].cbegin(); - for(uint32 cell = 0; cell < m_nChannels * Patterns[endPattern].GetNumRows(); cell++, m++) + for(CHANNELINDEX chn = 0; chn < GetNumChannels(); chn++) { - if(m->command == CMD_PATTERNBREAK || m->command == CMD_POSITIONJUMP) - { - lastRow = cell / m_nChannels; - break; - } + if(subsong.channelSurround[chn]) + Patterns[startPattern].WriteEffect(EffectWriter(CMD_S3MCMDEX, 0x91).Row(0).Channel(chn).RetryNextRow()); + else + Patterns[startPattern].WriteEffect(EffectWriter(CMD_PANNING8, subsong.channelPanning[chn]).Row(0).Channel(chn).RetryNextRow()); } - Patterns[endPattern].WriteEffect(EffectWriter(CMD_POSITIONJUMP, mpt::saturate_cast(subsong.startOrder + subsong.restartPos)).Row(lastRow).RetryPreviousRow()); } - - // Set the subsong name to all pattern names - for(ORDERINDEX ord = subsong.startOrder; ord <= subsong.endOrder; ord++) - { - if(Patterns.IsValidIndex(order[ord])) - Patterns[order[ord]].SetName(subsong.songName); - } -#endif // MPT_PSM_USE_REAL_SUBSONGS } } @@ -1049,10 +978,10 @@ static bool ValidateHeader(const PSM16FileHeader &fileHeader) if(std::memcmp(fileHeader.formatID, "PSM\xFE", 4) || fileHeader.lineEnd != 0x1A || (fileHeader.formatVersion != 0x10 && fileHeader.formatVersion != 0x01) // why is this sometimes 0x01? - || fileHeader.patternVersion != 0 // 255ch pattern version not supported (did anyone use this?) + || fileHeader.patternVersion != 0 // 255ch pattern version not supported (does it actually exist? The pattern format is not documented) || (fileHeader.songType & 3) != 0 - || fileHeader.numChannelsPlay > MAX_BASECHANNELS - || fileHeader.numChannelsReal > MAX_BASECHANNELS + || fileHeader.numChannelsPlay > 32 + || fileHeader.numChannelsReal > 32 || std::max(fileHeader.numChannelsPlay, fileHeader.numChannelsReal) == 0) { return false; @@ -1097,21 +1026,20 @@ bool CSoundFile::ReadPSM16(FileReader &file, ModLoadingFlags loadFlags) } // Seems to be valid! - InitializeGlobals(MOD_TYPE_PSM); + InitializeGlobals(MOD_TYPE_PSM, Clamp(CHANNELINDEX(fileHeader.numChannelsPlay), CHANNELINDEX(fileHeader.numChannelsReal), MAX_BASECHANNELS)); - m_modFormat.formatName = U_("Epic MegaGames MASI (Old Version)"); - m_modFormat.type = U_("psm"); + m_modFormat.formatName = UL_("Epic MegaGames MASI (Old Version)"); + m_modFormat.type = UL_("psm"); m_modFormat.charset = mpt::Charset::CP437; - m_nChannels = Clamp(CHANNELINDEX(fileHeader.numChannelsPlay), CHANNELINDEX(fileHeader.numChannelsReal), MAX_BASECHANNELS); m_nSamplePreAmp = fileHeader.masterVolume; if(m_nSamplePreAmp == 255) { // Most of the time, the master volume value makes sense... Just not when it's 255. m_nSamplePreAmp = 48; } - m_nDefaultSpeed = fileHeader.songSpeed; - m_nDefaultTempo.Set(fileHeader.songTempo); + Order().SetDefaultSpeed(fileHeader.songSpeed); + Order().SetDefaultTempoInt(fileHeader.songTempo); m_songName = mpt::String::ReadBuf(mpt::String::spacePadded, fileHeader.songName); @@ -1124,10 +1052,9 @@ bool CSoundFile::ReadPSM16(FileReader &file, ModLoadingFlags loadFlags) // Read pan positions if(fileHeader.panOffset > 4 && file.Seek(fileHeader.panOffset - 4) && file.ReadMagic("PPAN")) { - for(CHANNELINDEX i = 0; i < 32; i++) + for(CHANNELINDEX i = 0; i < GetNumChannels(); i++) { - ChnSettings[i].Reset(); - ChnSettings[i].nPan = ((15 - (file.ReadUint8() & 0x0F)) * 256 + 8) / 15; // 15 seems to be left and 0 seems to be right... + ChnSettings[i].nPan = static_cast(((15 - (file.ReadUint8() & 0x0F)) * 256 + 8) / 15); // 15 seems to be left and 0 seems to be right... // ChnSettings[i].dwFlags = (i >= fileHeader.numChannelsPlay) ? CHN_MUTE : 0; // don't mute channels, as muted channels are completely ignored in S3M } } @@ -1210,7 +1137,7 @@ bool CSoundFile::ReadPSM16(FileReader &file, ModLoadingFlags loadFlags) continue; } - ModCommand &m = *Patterns[pat].GetpModCommand(curRow, std::min(static_cast(chnFlag & channelMask), static_cast(m_nChannels - 1))); + ModCommand &m = *Patterns[pat].GetpModCommand(curRow, std::min(static_cast(chnFlag & channelMask), static_cast(GetNumChannels() - 1))); if(chnFlag & noteFlag) { @@ -1235,33 +1162,27 @@ bool CSoundFile::ReadPSM16(FileReader &file, ModLoadingFlags loadFlags) { // Volslides case 0x01: // fine volslide up - m.command = CMD_VOLUMESLIDE; - m.param = (m.param << 4) | 0x0F; + m.SetEffectCommand(CMD_VOLUMESLIDE, (m.param << 4) | 0x0F); break; case 0x02: // volslide up - m.command = CMD_VOLUMESLIDE; - m.param = (m.param << 4) & 0xF0; + m.SetEffectCommand(CMD_VOLUMESLIDE, (m.param << 4) & 0xF0); break; case 0x03: // fine voslide down - m.command = CMD_VOLUMESLIDE; - m.param = 0xF0 | m.param; + m.SetEffectCommand(CMD_VOLUMESLIDE, 0xF0 | m.param); break; case 0x04: // volslide down - m.command = CMD_VOLUMESLIDE; - m.param = m.param & 0x0F; + m.SetEffectCommand(CMD_VOLUMESLIDE, m.param & 0x0F); break; // Portamento case 0x0A: // fine portamento up - m.command = CMD_PORTAMENTOUP; - m.param |= 0xF0; + m.SetEffectCommand(CMD_PORTAMENTOUP, 0xF0 | m.param); break; case 0x0B: // portamento down m.command = CMD_PORTAMENTOUP; break; case 0x0C: // fine portamento down - m.command = CMD_PORTAMENTODOWN; - m.param |= 0xF0; + m.SetEffectCommand(CMD_PORTAMENTODOWN, m.param | 0xF0); break; case 0x0D: // portamento down m.command = CMD_PORTAMENTODOWN; @@ -1269,17 +1190,14 @@ bool CSoundFile::ReadPSM16(FileReader &file, ModLoadingFlags loadFlags) case 0x0E: // tone portamento m.command = CMD_TONEPORTAMENTO; break; - case 0x0F: // glissando control - m.command = CMD_S3MCMDEX; - m.param |= 0x10; + case 0x0F: // glissando control (note: this can be found in the Odyssey music from Silverball but it seems like it was actually a literal translation from MOD effect F) + m.SetEffectCommand(CMD_S3MCMDEX, 0x10 | (m.param & 0x0F)); break; case 0x10: // tone portamento + volslide up - m.command = CMD_TONEPORTAVOL; - m.param <<= 4; + m.SetEffectCommand(CMD_TONEPORTAVOL, m.param << 4); break; case 0x11: // tone portamento + volslide down - m.command = CMD_TONEPORTAVOL; - m.param &= 0x0F; + m.SetEffectCommand(CMD_TONEPORTAVOL, m.param & 0x0F); break; // Vibrato @@ -1287,16 +1205,13 @@ bool CSoundFile::ReadPSM16(FileReader &file, ModLoadingFlags loadFlags) m.command = CMD_VIBRATO; break; case 0x15: // vibrato waveform - m.command = CMD_S3MCMDEX; - m.param |= 0x30; + m.SetEffectCommand(CMD_S3MCMDEX, 0x30 | (m.param & 0x0F)); break; case 0x16: // vibrato + volslide up - m.command = CMD_VIBRATOVOL; - m.param <<= 4; + m.SetEffectCommand(CMD_VIBRATOVOL, m.param << 4); break; case 0x17: // vibrato + volslide down - m.command = CMD_VIBRATOVOL; - m.param &= 0x0F; + m.SetEffectCommand(CMD_VIBRATOVOL, m.param & 0x0F); break; // Tremolo @@ -1304,19 +1219,15 @@ bool CSoundFile::ReadPSM16(FileReader &file, ModLoadingFlags loadFlags) m.command = CMD_TREMOLO; break; case 0x1F: // tremolo waveform - m.command = CMD_S3MCMDEX; - m.param |= 0x40; + m.SetEffectCommand(CMD_S3MCMDEX, 0x40 | (m.param & 0x0F)); break; // Sample commands case 0x28: // 3-byte offset - we only support the middle byte. - m.command = CMD_OFFSET; - m.param = patternChunk.ReadUint8(); - patternChunk.Skip(1); + m.SetEffectCommand(CMD_OFFSET, patternChunk.ReadArray()[0]); break; case 0x29: // retrigger - m.command = CMD_RETRIG; - m.param &= 0x0F; + m.SetEffectCommand(CMD_RETRIG, m.param & 0x0F); break; case 0x2A: // note cut m.command = CMD_S3MCMDEX; @@ -1336,8 +1247,7 @@ bool CSoundFile::ReadPSM16(FileReader &file, ModLoadingFlags loadFlags) m.param |= 0xC0; break; case 0x2B: // note delay - m.command = CMD_S3MCMDEX; - m.param |= 0xD0; + m.SetEffectCommand(CMD_S3MCMDEX, 0xD0 | (m.param & 0x0F)); break; // Position change @@ -1348,12 +1258,10 @@ bool CSoundFile::ReadPSM16(FileReader &file, ModLoadingFlags loadFlags) m.command = CMD_PATTERNBREAK; break; case 0x34: // loop pattern - m.command = CMD_S3MCMDEX; - m.param |= 0xB0; + m.SetEffectCommand(CMD_S3MCMDEX, 0xB0 | (m.param & 0x0F)); break; case 0x35: // pattern delay - m.command = CMD_S3MCMDEX; - m.param |= 0xE0; + m.SetEffectCommand(CMD_S3MCMDEX, 0xE0 | (m.param & 0x0F)); break; // speed change @@ -1369,12 +1277,10 @@ bool CSoundFile::ReadPSM16(FileReader &file, ModLoadingFlags loadFlags) m.command = CMD_ARPEGGIO; break; case 0x47: // set finetune - m.command = CMD_S3MCMDEX; - m.param = 0x20 | (m.param & 0x0F); + m.SetEffectCommand(CMD_S3MCMDEX, 0x20 | (m.param & 0x0F)); break; case 0x48: // set balance (panning?) - m.command = CMD_S3MCMDEX; - m.param = 0x80 | (m.param & 0x0F); + m.SetEffectCommand(CMD_S3MCMDEX, 0x80 | (m.param & 0x0F)); break; default: @@ -1383,11 +1289,6 @@ bool CSoundFile::ReadPSM16(FileReader &file, ModLoadingFlags loadFlags) } } } - // Pattern break for short patterns (so saving the modules as S3M won't break it) - if(patternHeader.numRows != 64) - { - Patterns[pat].WriteEffect(EffectWriter(CMD_PATTERNBREAK, 0).Row(patternHeader.numRows - 1).RetryNextRow()); - } } } diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_pt36.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_pt36.cpp new file mode 100644 index 000000000..97e296a53 --- /dev/null +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_pt36.cpp @@ -0,0 +1,215 @@ +/* + * Load_pt36.cpp + * ------------- + * Purpose: ProTracker 3.6 wrapper format loader + * Notes : (currently none) + * Authors: OpenMPT Devs + * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. + */ + + +#include "stdafx.h" +#include "Loaders.h" + +OPENMPT_NAMESPACE_BEGIN + +struct PT36IffChunk +{ + // IFF chunk names + enum ChunkIdentifiers + { + idVERS = MagicBE("VERS"), + idINFO = MagicBE("INFO"), + idCMNT = MagicBE("CMNT"), + idPTDT = MagicBE("PTDT"), + }; + + uint32be signature; // IFF chunk name + uint32be chunksize; // chunk size without header +}; + +MPT_BINARY_STRUCT(PT36IffChunk, 8) + +struct PT36InfoChunk +{ + char name[32]; + uint16be numSamples; + uint16be numOrders; + uint16be numPatterns; + uint16be volume; + uint16be tempo; + uint16be flags; + uint16be dateDay; + uint16be dateMonth; + uint16be dateYear; + uint16be dateHour; + uint16be dateMinute; + uint16be dateSecond; + uint16be playtimeHour; + uint16be playtimeMinute; + uint16be playtimeSecond; + uint16be playtimeMsecond; +}; + +MPT_BINARY_STRUCT(PT36InfoChunk, 64) + + +struct PT36Header +{ + char magicFORM[4]; // "FORM" + uint32be size; + char magicMODL[4]; // "MODL" + + bool IsValid() const + { + if(std::memcmp(magicFORM, "FORM", 4)) + return false; + if(std::memcmp(magicMODL, "MODL", 4)) + return false; + return true; + } +}; + +MPT_BINARY_STRUCT(PT36Header, 12) + + +CSoundFile::ProbeResult CSoundFile::ProbeFileHeaderPT36(MemoryFileReader file, const uint64 *pfilesize) +{ + PT36Header fileHeader; + if(!file.ReadStruct(fileHeader)) + return ProbeWantMoreData; + if(!fileHeader.IsValid()) + return ProbeFailure; + MPT_UNREFERENCED_PARAMETER(pfilesize); + return ProbeSuccess; +} + + +// ProTracker 3.6 version of the MOD format +// Basically just a normal ProTracker mod with different magic, wrapped in an IFF file. +// The "PTDT" chunk is passed to the normal MOD loader. +bool CSoundFile::ReadPT36(FileReader &file, ModLoadingFlags loadFlags) +{ + file.Rewind(); + + PT36Header fileHeader; + if(!file.ReadStruct(fileHeader)) + return false; + if(!fileHeader.IsValid()) + return false; + + bool ok = false, infoOk = false; + FileReader commentChunk; + mpt::ustring version; + PT36InfoChunk info; + MemsetZero(info); + + // Go through IFF chunks... + PT36IffChunk iffHead; + if(!file.ReadStruct(iffHead)) + { + return false; + } + // First chunk includes "MODL" magic in size + iffHead.chunksize -= 4; + + do + { + // All chunk sizes include chunk header + iffHead.chunksize -= 8; + if(loadFlags == onlyVerifyHeader && iffHead.signature == PT36IffChunk::idPTDT) + { + return true; + } + + FileReader chunk = file.ReadChunk(iffHead.chunksize); + if(!chunk.IsValid()) + { + break; + } + + switch(iffHead.signature) + { + case PT36IffChunk::idVERS: + chunk.Skip(4); + if(chunk.ReadMagic("PT") && iffHead.chunksize > 6) + { + chunk.ReadString(version, mpt::Charset::Amiga_no_C1, iffHead.chunksize - 6); + } + break; + + case PT36IffChunk::idINFO: + infoOk = chunk.ReadStruct(info); + break; + + case PT36IffChunk::idCMNT: + commentChunk = chunk; + break; + + case PT36IffChunk::idPTDT: + ok = ReadMOD(chunk, loadFlags); + break; + } + } while(file.ReadStruct(iffHead)); + + if(version.empty()) + { + version = UL_("3.6"); + } + + // both an info chunk and a module are required + if(ok && infoOk) + { + bool vblank = (info.flags & 0x100) == 0; + m_playBehaviour.set(kMODVBlankTiming, vblank); + if(info.volume != 0) + m_nSamplePreAmp = std::min(uint16(64), static_cast(info.volume)); + if(info.tempo != 0 && !vblank) + Order().SetDefaultTempoInt(info.tempo); + + if(info.name[0]) + m_songName = mpt::String::ReadBuf(mpt::String::maybeNullTerminated, info.name); + + if(mpt::is_in_range(info.dateMonth, 1, 12) && mpt::is_in_range(info.dateDay, 1, 31) && mpt::is_in_range(info.dateHour, 0, 23) + && mpt::is_in_range(info.dateMinute, 0, 59) && mpt::is_in_range(info.dateSecond, 0, 59)) + { +#ifdef MODPLUG_TRACKER + m_modFormat.timezone = mpt::Date::LogicalTimezone::Local; +#else + m_modFormat.timezone = mpt::Date::LogicalTimezone::Unspecified; +#endif + FileHistory mptHistory; + mptHistory.loadDate.year = info.dateYear + 1900; + mptHistory.loadDate.month = info.dateMonth; + mptHistory.loadDate.day = info.dateDay; + mptHistory.loadDate.hours = info.dateHour; + mptHistory.loadDate.minutes = info.dateMinute; + mptHistory.loadDate.seconds = info.dateSecond; + m_FileHistory.push_back(mptHistory); + } + } + if(ok) + { + if(commentChunk.IsValid()) + { + std::string author; + commentChunk.ReadString(author, 32); + if(author != "UNNAMED AUTHOR") + m_songArtist = mpt::ToUnicode(mpt::Charset::Amiga_no_C1, author); + if(!commentChunk.NoBytesLeft()) + { + m_songMessage.ReadFixedLineLength(commentChunk, commentChunk.BytesLeft(), 40, 0); + } + } + + m_modFormat.madeWithTracker = UL_("ProTracker ") + version; + } + m_SongFlags.set(SONG_PT_MODE); + m_playBehaviour.set(kMODIgnorePanning); + m_playBehaviour.set(kMODOneShotLoops); + m_playBehaviour.reset(kMODSampleSwap); + + return ok; +} + +OPENMPT_NAMESPACE_END diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_ptm.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_ptm.cpp index cbb92daa9..539de7495 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_ptm.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_ptm.cpp @@ -157,7 +157,7 @@ bool CSoundFile::ReadPTM(FileReader &file, ModLoadingFlags loadFlags) { return false; } - if(!file.CanRead(mpt::saturate_cast(GetHeaderMinimumAdditionalSize(fileHeader)))) + if(!file.CanRead(mpt::saturate_cast(GetHeaderMinimumAdditionalSize(fileHeader)))) { return false; } @@ -166,25 +166,23 @@ bool CSoundFile::ReadPTM(FileReader &file, ModLoadingFlags loadFlags) return true; } - InitializeGlobals(MOD_TYPE_PTM); + InitializeGlobals(MOD_TYPE_PTM, fileHeader.numChannels); m_songName = mpt::String::ReadBuf(mpt::String::maybeNullTerminated, fileHeader.songname); - m_modFormat.formatName = U_("PolyTracker"); - m_modFormat.type = U_("ptm"); + m_modFormat.formatName = UL_("PolyTracker"); + m_modFormat.type = UL_("ptm"); m_modFormat.madeWithTracker = MPT_UFORMAT("PolyTracker {}.{}")(fileHeader.versionHi.get(), mpt::ufmt::hex0<2>(fileHeader.versionLo.get())); m_modFormat.charset = mpt::Charset::CP437; SetMixLevels(MixLevels::CompatibleFT2); m_SongFlags = SONG_ITCOMPATGXX | SONG_ITOLDEFFECTS; - m_nChannels = fileHeader.numChannels; m_nSamples = std::min(static_cast(fileHeader.numSamples), static_cast(MAX_SAMPLES - 1)); ReadOrderFromArray(Order(), fileHeader.orders, fileHeader.numOrders, 0xFF, 0xFE); // Reading channel panning - for(CHANNELINDEX chn = 0; chn < m_nChannels; chn++) + for(CHANNELINDEX chn = 0; chn < GetNumChannels(); chn++) { - ChnSettings[chn].Reset(); ChnSettings[chn].nPan = ((fileHeader.chnPan[chn] & 0x0F) << 4) + 4; } @@ -230,7 +228,7 @@ bool CSoundFile::ReadPTM(FileReader &file, ModLoadingFlags loadFlags) if(b == 0) { row++; - rowBase += m_nChannels; + rowBase += GetNumChannels(); continue; } CHANNELINDEX chn = (b & 0x1F); diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_puma.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_puma.cpp new file mode 100644 index 000000000..675bb85b4 --- /dev/null +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_puma.cpp @@ -0,0 +1,374 @@ +/* + * Load_puma.cpp + * ------------- + * Purpose: PumaTracker module loader + * Notes : (currently none) + * Authors: OpenMPT Devs + * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. + */ + + +#include "stdafx.h" +#include "Loaders.h" +#include "InstrumentSynth.h" + +OPENMPT_NAMESPACE_BEGIN + +struct PumaPlaylistEntry +{ + struct Entry + { + uint8 pattern; + int8 instrTranspose; + int8 noteTranspose; + }; + + std::array channels; + uint8 speed; + uint8 zero; + + bool IsValid() const + { + for(const auto &chn : channels) + { + if(chn.pattern >= 128) + return false; + if((chn.noteTranspose & 1) || (chn.noteTranspose < -48 || chn.noteTranspose > 48)) + return false; + } + return speed <= 15 && !zero; + } +}; + +MPT_BINARY_STRUCT(PumaPlaylistEntry, 14) + + +struct PumaFileHeader +{ + char songName[12]; + uint16be lastOrder; // "LoopTrack" in GUI + uint16be numPatterns; + uint16be numInstruments; + uint16be unknown; + uint32be sampleOffset[10]; + uint16be sampleLength[10]; // in words + + bool IsValid() const + { + for(int8 c : songName) + { + if(c != 0 && c < ' ') + return false; + } + + if(lastOrder > 255 || !numPatterns || numPatterns > 128 || !numInstruments || numInstruments > 32 || unknown) + return false; + + const auto minSampleOffset = sizeof(PumaFileHeader) + GetHeaderMinimumAdditionalSize(); + for(uint32 i = 0; i < 10; i++) + { + if(sampleLength[i] > 0 && !sampleOffset[i]) + return false; + if(sampleOffset[i] > 0x10'0000) + return false; + if(sampleOffset[i] > 0 && sampleOffset[i] < minSampleOffset) + return false; + } + return true; + } + + ORDERINDEX NumOrders() const + { + return lastOrder + 1; + } + + uint32 GetHeaderMinimumAdditionalSize() const + { + return NumOrders() * static_cast(sizeof(PumaPlaylistEntry)) + numPatterns * 8 + 4 + numInstruments * 16 + 4; + } +}; + +MPT_BINARY_STRUCT(PumaFileHeader, 80) + + +static bool TranslatePumaScript(InstrumentSynth::Events &events, ModInstrument &instr, FileReader &file, bool isVolume) +{ + bool isFirst = true; + while(file.CanRead(4)) + { + const auto data = file.ReadArray(); + if(isFirst && isVolume && data[0] != 0xC0) + return false; + switch(data[0]) + { + case 0xA0: // Volume ramp / pitch ramp + if(isVolume) + events.push_back(InstrumentSynth::Event::Puma_VolumeRamp(std::min(data[1], uint8(64)), std::min(data[2], uint8(64)), data[3])); + else + events.push_back(InstrumentSynth::Event::Puma_PitchRamp(static_cast(data[1]), static_cast(data[2]), data[3])); + break; + case 0xB0: // Jump + if(data[1] & 3) + return false; + events.push_back(InstrumentSynth::Event::Jump(data[1] / 4u)); + return true; + case 0xC0: // Set waveform / - + if(isVolume) + events.push_back(InstrumentSynth::Event::Puma_SetWaveform(data[1], data[2], data[3])); + else + return false; + if(isFirst) + instr.AssignSample(data[1] + 1); + break; + case 0xD0: // - / Set pitch + // Odd values can be entered in the editor but playback will freeze + if((data[1] & 1) || isVolume) + return false; + events.push_back(InstrumentSynth::Event::Puma_SetPitch(static_cast(data[1]), data[3])); + break; + case 0xE0: // Stop sound / End of script + if(isVolume) + events.push_back(InstrumentSynth::Event::Puma_StopVoice()); + else + events.push_back(InstrumentSynth::Event::StopScript()); + return true; + default: + if(!isVolume && !memcmp(data.data(), "inst", 4)) + { + // vimto-02.puma has insf chunks that only consist of a single D0 00 00 01 + file.SkipBack(4); + return !events.empty(); + } + return false; + } + isFirst = false; + } + return !events.empty(); +} + + +CSoundFile::ProbeResult CSoundFile::ProbeFileHeaderPuma(MemoryFileReader file, const uint64 *pfilesize) +{ + PumaFileHeader fileHeader; + if(!file.ReadStruct(fileHeader)) + return ProbeWantMoreData; + if(!fileHeader.IsValid()) + return ProbeFailure; + + const size_t probeOrders = std::min(static_cast(fileHeader.NumOrders()), (ProbeRecommendedSize - sizeof(fileHeader)) / sizeof(PumaPlaylistEntry)); + for(size_t ord = 0; ord < probeOrders; ord++) + { + PumaPlaylistEntry entry; + if(!file.ReadStruct(entry)) + return ProbeWantMoreData; + if(!entry.IsValid()) + return ProbeFailure; + } + return ProbeAdditionalSize(file, pfilesize, fileHeader.GetHeaderMinimumAdditionalSize() - probeOrders * sizeof(PumaPlaylistEntry)); +} + + +bool CSoundFile::ReadPuma(FileReader &file, ModLoadingFlags loadFlags) +{ + PumaFileHeader fileHeader; + + file.Rewind(); + if(!file.ReadStruct(fileHeader) || !fileHeader.IsValid()) + return false; + if(!file.CanRead(fileHeader.GetHeaderMinimumAdditionalSize())) + return false; + if(loadFlags == onlyVerifyHeader) + return true; + + InitializeGlobals(MOD_TYPE_MOD, 4); + SetupMODPanning(true); + m_SongFlags.set(SONG_IMPORTED | SONG_ISAMIGA | SONG_FASTPORTAS); + m_nSamples = 52; + m_nInstruments = fileHeader.numInstruments; + m_playBehaviour.set(kMODSampleSwap); + + m_songName = mpt::String::ReadBuf(mpt::String::maybeNullTerminated, fileHeader.songName); + + const ORDERINDEX numOrders = fileHeader.NumOrders(); + std::vector orderData; + file.ReadVector(orderData, numOrders); + + static constexpr ROWINDEX numRows = 32; + std::vector, numRows>> patternData(fileHeader.numPatterns); + for(auto &pattern : patternData) + { + if(!file.ReadMagic("patt")) + return false; + + size_t row = 0; + while(row < pattern.size() && file.CanRead(4)) + { + const auto data = file.ReadArray(); + if(data[0] & 1) + return false; + if(!data[3] || data[3] > pattern.size() - row) + return false; + pattern[row] = {data[0], data[1], data[2]}; + row += data[3]; + } + } + if(!file.ReadMagic("patt")) + return false; + + Order().resize(numOrders); + if(loadFlags & loadPatternData) + Patterns.ResizeArray(numOrders); + for(ORDERINDEX ord = 0; ord < numOrders; ord++) + { + if(!orderData[ord].IsValid()) + return false; + if(!(loadFlags & loadPatternData) || !Patterns.Insert(ord, numRows)) + continue; + Order()[ord] = ord; + for(CHANNELINDEX chn = 0; chn < 4; chn++) + { + const auto &chnInfo = orderData[ord].channels[chn]; + if(chnInfo.pattern >= patternData.size()) + continue; + ModCommand *m = Patterns[ord].GetpModCommand(0, chn); + // Auto-portmentos appear to stop on pattern transitions and revert to the note's original pitch. + VolumeCommand autoPorta = VOLCMD_NONE; + for(const auto &p : patternData[chnInfo.pattern]) + { + if(p[0]) + m->note = static_cast(NOTE_MIDDLEC - 49 + (p[0] + chnInfo.noteTranspose) / 2); + if(uint8 instr = (p[1] & 0x1F); instr != 0) + m->instr = (instr + chnInfo.instrTranspose) & 0x1F; + if(!m->instr && m->note != NOTE_NONE) + m->instr = 99; + m->param = p[2]; + switch(p[1] >> 5) + { + case 1: + m->command = CMD_VOLUME; + break; + case 2: + autoPorta = m->param ? VOLCMD_PORTADOWN : VOLCMD_NONE; + if(autoPorta != VOLCMD_NONE) + m->command = CMD_PORTAMENTODOWN; + break; + case 3: + autoPorta = m->param ? VOLCMD_PORTAUP : VOLCMD_NONE; + if(autoPorta != VOLCMD_NONE) + m->command = CMD_PORTAMENTOUP; + break; + } + if(m->command != CMD_PORTAMENTOUP && m->command != CMD_PORTAMENTODOWN) + { + if(m->note != NOTE_NONE) + autoPorta = VOLCMD_NONE; + else if(autoPorta != VOLCMD_NONE) + m->volcmd = autoPorta; + } + m += GetNumChannels(); + } + } + if(orderData[ord].speed) + Patterns[ord].WriteEffect(EffectWriter(CMD_SPEED, orderData[ord].speed).RetryNextRow()); + } + + for(INSTRUMENTINDEX ins = 1; ins <= m_nInstruments; ins++) + { + if(!file.ReadMagic("inst")) + return false; + + ModInstrument *instr = AllocateInstrument(ins); + if(!instr) + return false; + + instr->synth.m_scripts.resize(2); + if(!TranslatePumaScript(instr->synth.m_scripts[0], *instr, file, true)) + return false; + if(!file.ReadMagic("insf")) + return false; + if(!TranslatePumaScript(instr->synth.m_scripts[1], *instr, file, false)) + return false; + } + if(!file.ReadMagic("inst")) + return false; + + SampleIO sampleIO(SampleIO::_8bit, SampleIO::mono, SampleIO::bigEndian, SampleIO::signedPCM); + for(SAMPLEINDEX smp = 0; smp < 10; smp++) + { + if(!fileHeader.sampleLength[smp]) + continue; + if(!file.Seek(fileHeader.sampleOffset[smp])) + return false; + ModSample &mptSmp = Samples[smp + 1]; + mptSmp.Initialize(MOD_TYPE_MOD); + mptSmp.nLength = fileHeader.sampleLength[smp] * 2; + sampleIO.ReadSample(mptSmp, file); + } + + static constexpr std::array SampleData[] = + { + {0xC0, 0xC0, 0xD0, 0xD8, 0xE0, 0xE8, 0xF0, 0xF8, 0x00, 0xF8, 0xF0, 0xE8, 0xE0, 0xD8, 0xD0, 0xC8, 0x3F, 0x37, 0x2F, 0x27, 0x1F, 0x17, 0x0F, 0x07, 0xFF, 0x07, 0x0F, 0x17, 0x1F, 0x27, 0x2F, 0x37}, + {0xC0, 0xC0, 0xD0, 0xD8, 0xE0, 0xE8, 0xF0, 0xF8, 0x00, 0xF8, 0xF0, 0xE8, 0xE0, 0xD8, 0xD0, 0xC8, 0xC0, 0x37, 0x2F, 0x27, 0x1F, 0x17, 0x0F, 0x07, 0xFF, 0x07, 0x0F, 0x17, 0x1F, 0x27, 0x2F, 0x37}, + {0xC0, 0xC0, 0xD0, 0xD8, 0xE0, 0xE8, 0xF0, 0xF8, 0x00, 0xF8, 0xF0, 0xE8, 0xE0, 0xD8, 0xD0, 0xC8, 0xC0, 0xB8, 0x2F, 0x27, 0x1F, 0x17, 0x0F, 0x07, 0xFF, 0x07, 0x0F, 0x17, 0x1F, 0x27, 0x2F, 0x37}, + {0xC0, 0xC0, 0xD0, 0xD8, 0xE0, 0xE8, 0xF0, 0xF8, 0x00, 0xF8, 0xF0, 0xE8, 0xE0, 0xD8, 0xD0, 0xC8, 0xC0, 0xB8, 0xB0, 0x27, 0x1F, 0x17, 0x0F, 0x07, 0xFF, 0x07, 0x0F, 0x17, 0x1F, 0x27, 0x2F, 0x37}, + {0xC0, 0xC0, 0xD0, 0xD8, 0xE0, 0xE8, 0xF0, 0xF8, 0x00, 0xF8, 0xF0, 0xE8, 0xE0, 0xD8, 0xD0, 0xC8, 0xC0, 0xB8, 0xB0, 0xA8, 0x1F, 0x17, 0x0F, 0x07, 0xFF, 0x07, 0x0F, 0x17, 0x1F, 0x27, 0x2F, 0x37}, + {0xC0, 0xC0, 0xD0, 0xD8, 0xE0, 0xE8, 0xF0, 0xF8, 0x00, 0xF8, 0xF0, 0xE8, 0xE0, 0xD8, 0xD0, 0xC8, 0xC0, 0xB8, 0xB0, 0xA8, 0xA0, 0x17, 0x0F, 0x07, 0xFF, 0x07, 0x0F, 0x17, 0x1F, 0x27, 0x2F, 0x37}, + {0xC0, 0xC0, 0xD0, 0xD8, 0xE0, 0xE8, 0xF0, 0xF8, 0x00, 0xF8, 0xF0, 0xE8, 0xE0, 0xD8, 0xD0, 0xC8, 0xC0, 0xB8, 0xB0, 0xA8, 0xA0, 0x98, 0x0F, 0x07, 0xFF, 0x07, 0x0F, 0x17, 0x1F, 0x27, 0x2F, 0x37}, + {0xC0, 0xC0, 0xD0, 0xD8, 0xE0, 0xE8, 0xF0, 0xF8, 0x00, 0xF8, 0xF0, 0xE8, 0xE0, 0xD8, 0xD0, 0xC8, 0xC0, 0xB8, 0xB0, 0xA8, 0xA0, 0x98, 0x90, 0x07, 0xFF, 0x07, 0x0F, 0x17, 0x1F, 0x27, 0x2F, 0x37}, + {0xC0, 0xC0, 0xD0, 0xD8, 0xE0, 0xE8, 0xF0, 0xF8, 0x00, 0xF8, 0xF0, 0xE8, 0xE0, 0xD8, 0xD0, 0xC8, 0xC0, 0xB8, 0xB0, 0xA8, 0xA0, 0x98, 0x90, 0x88, 0xFF, 0x07, 0x0F, 0x17, 0x1F, 0x27, 0x2F, 0x37}, + {0xC0, 0xC0, 0xD0, 0xD8, 0xE0, 0xE8, 0xF0, 0xF8, 0x00, 0xF8, 0xF0, 0xE8, 0xE0, 0xD8, 0xD0, 0xC8, 0xC0, 0xB8, 0xB0, 0xA8, 0xA0, 0x98, 0x90, 0x88, 0x80, 0x07, 0x0F, 0x17, 0x1F, 0x27, 0x2F, 0x37}, + {0xC0, 0xC0, 0xD0, 0xD8, 0xE0, 0xE8, 0xF0, 0xF8, 0x00, 0xF8, 0xF0, 0xE8, 0xE0, 0xD8, 0xD0, 0xC8, 0xC0, 0xB8, 0xB0, 0xA8, 0xA0, 0x98, 0x90, 0x88, 0x80, 0x88, 0x0F, 0x17, 0x1F, 0x27, 0x2F, 0x37}, + {0xC0, 0xC0, 0xD0, 0xD8, 0xE0, 0xE8, 0xF0, 0xF8, 0x00, 0xF8, 0xF0, 0xE8, 0xE0, 0xD8, 0xD0, 0xC8, 0xC0, 0xB8, 0xB0, 0xA8, 0xA0, 0x98, 0x90, 0x88, 0x80, 0x88, 0x90, 0x17, 0x1F, 0x27, 0x2F, 0x37}, + {0xC0, 0xC0, 0xD0, 0xD8, 0xE0, 0xE8, 0xF0, 0xF8, 0x00, 0xF8, 0xF0, 0xE8, 0xE0, 0xD8, 0xD0, 0xC8, 0xC0, 0xB8, 0xB0, 0xA8, 0xA0, 0x98, 0x90, 0x88, 0x80, 0x88, 0x90, 0x98, 0x1F, 0x27, 0x2F, 0x37}, + {0xC0, 0xC0, 0xD0, 0xD8, 0xE0, 0xE8, 0xF0, 0xF8, 0x00, 0xF8, 0xF0, 0xE8, 0xE0, 0xD8, 0xD0, 0xC8, 0xC0, 0xB8, 0xB0, 0xA8, 0xA0, 0x98, 0x90, 0x88, 0x80, 0x88, 0x90, 0x98, 0xA0, 0x27, 0x2F, 0x37}, + {0xC0, 0xC0, 0xD0, 0xD8, 0xE0, 0xE8, 0xF0, 0xF8, 0x00, 0xF8, 0xF0, 0xE8, 0xE0, 0xD8, 0xD0, 0xC8, 0xC0, 0xB8, 0xB0, 0xA8, 0xA0, 0x98, 0x90, 0x88, 0x80, 0x88, 0x90, 0x98, 0xA0, 0xA8, 0x2F, 0x37}, + {0xC0, 0xC0, 0xD0, 0xD8, 0xE0, 0xE8, 0xF0, 0xF8, 0x00, 0xF8, 0xF0, 0xE8, 0xE0, 0xD8, 0xD0, 0xC8, 0xC0, 0xB8, 0xB0, 0xA8, 0xA0, 0x98, 0x90, 0x88, 0x80, 0x88, 0x90, 0x98, 0xA0, 0xA8, 0xB0, 0x37}, + {0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F}, + {0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F}, + {0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F}, + {0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F}, + {0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F}, + {0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F}, + {0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F}, + {0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F}, + {0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F}, + {0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F}, + {0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F}, + {0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F}, + {0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x7F, 0x7F, 0x7F, 0x7F}, + {0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x81, 0x7F, 0x7F, 0x7F}, + {0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x7F, 0x7F}, + {0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x7F}, + {0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F}, + {0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x80, 0x80, 0x80, 0x80, 0x80, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F}, + {0x80, 0x80, 0x80, 0x80, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x80, 0x80, 0x80, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F}, + {0x80, 0x80, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x80, 0x80, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F}, + {0x80, 0x80, 0x90, 0x98, 0xA0, 0xA8, 0xB0, 0xB8, 0xC0, 0xC8, 0xD0, 0xD8, 0xE0, 0xE8, 0xF0, 0xF8, 0x00, 0x08, 0x10, 0x18, 0x20, 0x28, 0x30, 0x38, 0x40, 0x48, 0x50, 0x58, 0x60, 0x68, 0x70, 0x7F}, + {0x80, 0x80, 0xA0, 0xB0, 0xC0, 0xD0, 0xE0, 0xF0, 0x00, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70, 0x45, 0x45, 0x79, 0x7D, 0x7A, 0x77, 0x70, 0x66, 0x61, 0x58, 0x53, 0x4D, 0x2C, 0x20, 0x18, 0x12}, + {0x04, 0xDB, 0xD3, 0xCD, 0xC6, 0xBC, 0xB5, 0xAE, 0xA8, 0xA3, 0x9D, 0x99, 0x93, 0x8E, 0x8B, 0x8A, 0x45, 0x45, 0x79, 0x7D, 0x7A, 0x77, 0x70, 0x66, 0x5B, 0x4B, 0x43, 0x37, 0x2C, 0x20, 0x18, 0x12}, + {0x04, 0xF8, 0xE8, 0xDB, 0xCF, 0xC6, 0xBE, 0xB0, 0xA8, 0xA4, 0x9E, 0x9A, 0x95, 0x94, 0x8D, 0x83, 0x00, 0x00, 0x40, 0x60, 0x7F, 0x60, 0x40, 0x20, 0x00, 0xE0, 0xC0, 0xA0, 0x80, 0xA0, 0xC0, 0xE0}, + {0x00, 0x00, 0x40, 0x60, 0x7F, 0x60, 0x40, 0x20, 0x00, 0xE0, 0xC0, 0xA0, 0x80, 0xA0, 0xC0, 0xE0, 0x80, 0x80, 0x90, 0x98, 0xA0, 0xA8, 0xB0, 0xB8, 0xC0, 0xC8, 0xD0, 0xD8, 0xE0, 0xE8, 0xF0, 0xF8}, + {0x00, 0x08, 0x10, 0x18, 0x20, 0x28, 0x30, 0x38, 0x40, 0x48, 0x50, 0x58, 0x60, 0x68, 0x70, 0x7F, 0x80, 0x80, 0xA0, 0xB0, 0xC0, 0xD0, 0xE0, 0xF0, 0x00, 0x10, 0x20, 0x30, 0x40, 0x50, 0x60, 0x70}, + }; + for(SAMPLEINDEX smp = 0; smp < 42; smp++) + { + ModSample &mptSmp = Samples[smp + 11]; + mptSmp.Initialize(MOD_TYPE_MOD); + mptSmp.nLength = 32; + mptSmp.nLoopStart = 0; + mptSmp.nLoopEnd = 32; + mptSmp.uFlags.set(CHN_LOOP); + FileReader smpFile{mpt::as_span(SampleData[smp])}; + sampleIO.ReadSample(mptSmp, smpFile); + } + + m_modFormat.formatName = UL_("Puma Tracker"); + m_modFormat.type = UL_("puma"); + m_modFormat.madeWithTracker = UL_("Puma Tracker"); + m_modFormat.charset = mpt::Charset::Amiga_no_C1; + + return true; +} + + +OPENMPT_NAMESPACE_END diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_rtm.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_rtm.cpp new file mode 100644 index 000000000..bb05b9951 --- /dev/null +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_rtm.cpp @@ -0,0 +1,443 @@ +/* + * Load_rtm.cpp + * ------------ + * Purpose: Real Tracker 2 (RTM) module Loader + * Notes : (currently none) + * Authors: OpenMPT Devs + * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. + */ + + +#include "stdafx.h" +#include "Loaders.h" + +OPENMPT_NAMESPACE_BEGIN + +struct RTMObjectHeader +{ + char id[4]; // "RTMM" for song header, "RTND" for patterns, "RTIN" for instruments, "RTSM" for samples + uint8 space; + char name[32]; + uint8 eof; + uint16le version; + uint16le objectSize; + + bool IsValid() const + { + return !memcmp(id, "RTMM", 4) + && space == 0x20 + && eof == 0x1A + && version >= 0x100 && version <= 0x112 + && objectSize >= 98; + } +}; + +MPT_BINARY_STRUCT(RTMObjectHeader, 42) + + +struct RTMMHeader +{ + enum SongFlags + { + songLinearSlides = 0x01, + songTrackNames = 0x02, + }; + + char software[20]; + char composer[32]; + uint16le flags; + uint8 numChannels; + uint8 numInstruments; + uint16le numOrders; + uint16le numPatterns; + uint8 speed; + uint8 tempo; + int8 panning[32]; + uint32le extraDataSize; // Order list / track names + char originalName[32]; + + bool IsValid() const + { + return numChannels > 0 && numChannels <= 32 + && numOrders <= 999 + && numPatterns <= 999 + && speed != 0 + && extraDataSize < 0x10000; + } + + static constexpr size_t HeaderProbingSize() + { + return offsetof(RTMMHeader, originalName); + } + + uint32 GetHeaderMinimumAdditionalSize() const + { + return extraDataSize; + } +}; + +MPT_BINARY_STRUCT(RTMMHeader, 130) + + +// Pattern header (RTND magic bytes) +struct RTMPatternHeader +{ + uint16le flags; // Always 1 + uint8 numTracks; + uint16le numRows; + uint32le packedSize; +}; + +MPT_BINARY_STRUCT(RTMPatternHeader, 9) + + +struct RTMEnvelope +{ + enum EnvelopeFlags + { + envEnabled = 0x01, + envSustain = 0x02, + envLoop = 0x04, + }; + + struct EnvPoint + { + uint32le x; + int32le y; + }; + + uint8 numPoints; + EnvPoint points[12]; + uint8 sustainPoint; + uint8 loopStart; + uint8 loopEnd; + uint16le flags; + + void ConvertToMPT(InstrumentEnvelope &mptEnv, int8 offset) const + { + mptEnv.resize(std::min(numPoints, uint8(12))); + mptEnv.nSustainStart = mptEnv.nSustainEnd = sustainPoint; + mptEnv.nLoopStart = loopStart; + mptEnv.nLoopEnd = loopEnd; + mptEnv.dwFlags.set(ENV_ENABLED, flags & envEnabled); + mptEnv.dwFlags.set(ENV_SUSTAIN, flags & envSustain); + mptEnv.dwFlags.set(ENV_LOOP, flags & envLoop); + + for(size_t i = 0; i < mptEnv.size(); i++) + { + mptEnv[i].tick = mpt::saturate_cast(points[i].x.get()); + mptEnv[i].value = mpt::saturate_cast(points[i].y + offset); + } + } +}; + +MPT_BINARY_STRUCT(RTMEnvelope, 102) + + +// Instrument header (RTIN magic bytes) +struct RTINHeader +{ + enum InstrumentFlags + { + insDefaultPanning = 0x01, + insMuteSamples = 0x02, + }; + + uint8 numSamples; + uint16le flags; + uint8 samples[120]; + + RTMEnvelope volumeEnv; + RTMEnvelope panningEnv; + + uint8 vibratoType; + uint8 vibratoSweep; + uint8 vibratoDepth; + uint8 vibratoRate; + uint16le fadeOut; + uint8 midiPort; + uint8 midiChannel; + uint8 midiProgram; + uint8 midiEnable; + int8 midiTranspose; + uint8 midiBenderRange; + uint8 midiBaseVolume; + uint8 midiUseVelocity; + + void ConvertToMPT(ModInstrument &mptIns, SAMPLEINDEX baseSample) const + { + mptIns.nFadeOut = fadeOut / 2; + volumeEnv.ConvertToMPT(mptIns.VolEnv, 0); + panningEnv.ConvertToMPT(mptIns.PanEnv, ENVELOPE_MID); + if(flags & insMuteSamples) + mptIns.nGlobalVol = 0; + static_assert(mpt::array_size::size <= mpt::array_size::size); + for(size_t i = 0; i < std::size(samples); i++) + { + mptIns.Keyboard[i] = baseSample + samples[i]; + } + if(midiEnable) + { + mptIns.nMidiChannel = MidiFirstChannel + midiChannel; + mptIns.nMidiProgram = midiProgram + 1; + mptIns.midiPWD = midiBenderRange; + } + } +}; + +MPT_BINARY_STRUCT(RTINHeader, 341) + + +// Sample header (RTSM magic bytes) +struct RTSMHeader +{ + enum SampleFlags + { + smp16Bit = 0x02, + smpDelta = 0x04, + }; + + uint16le flags; + uint8 baseVolume; // 0...64 + uint8 defaultVolume; // 0...64 + uint32le length; + uint8 loopType; // 0 = no loop, 1 = forward loop, 2 = ping-pong loop + char reserved[3]; + uint32le loopStart; + uint32le loopEnd; + uint32le sampleRate; + uint8 baseNote; + int8 panning; // -64...64 + + void ConvertToMPT(ModSample &mptSmp, const RTINHeader &insHeader) const + { + mptSmp.Initialize(MOD_TYPE_IT); + mptSmp.nVolume = defaultVolume * 4; + mptSmp.nGlobalVol = baseVolume; + mptSmp.nLength = length; + mptSmp.nLoopStart = loopStart; + mptSmp.nLoopEnd = loopEnd; + if(flags & smp16Bit) + { + mptSmp.nLength /= 2; + mptSmp.nLoopStart /= 2; + mptSmp.nLoopEnd /= 2; + } + mptSmp.uFlags.set(CHN_PANNING, insHeader.flags & RTINHeader::insDefaultPanning); + mptSmp.uFlags.set(CHN_LOOP, loopType != 0); + mptSmp.uFlags.set(CHN_SUSTAINLOOP, loopType == 2); + mptSmp.nC5Speed = sampleRate; + mptSmp.Transpose((48 - baseNote) / 12.0); + mptSmp.nPan = static_cast((panning + 64) * 2); + + mptSmp.nVibType = static_cast(insHeader.vibratoType); + mptSmp.nVibDepth = insHeader.vibratoDepth * 2; + mptSmp.nVibRate = insHeader.vibratoRate / 2; + mptSmp.nVibSweep = insHeader.vibratoSweep; + if(mptSmp.nVibSweep != 0) + mptSmp.nVibSweep = mpt::saturate_cast(Util::muldivr_unsigned(mptSmp.nVibDepth, 256, mptSmp.nVibSweep)); + else + mptSmp.nVibSweep = 255; + } + + SampleIO GetSampleFormat() const + { + return SampleIO{(flags & smp16Bit) ? SampleIO::_16bit : SampleIO::_8bit, + SampleIO::mono, + SampleIO::littleEndian, + (flags & smpDelta) ? SampleIO::deltaPCM: SampleIO::signedPCM}; + } +}; + +MPT_BINARY_STRUCT(RTSMHeader, 26) + + +static void ConvertRTMEffect(ModCommand &m, const uint8 command, const uint8 param, const CSoundFile &sndFile) +{ + // Commands not handled: M (Select MIDI controller), V (Select MIDI controller value) + if(command == 8) + m.SetEffectCommand(CMD_PANNING8, mpt::saturate_cast(param * 2)); + else if(command == 'S' - 55 && (param & 0xF0) == 0xA0) + m.SetEffectCommand(CMD_S3MCMDEX, param); + else if(command <= 'X' - 55) + { + CSoundFile::ConvertModCommand(m, command, param); + m.Convert(MOD_TYPE_XM, MOD_TYPE_IT, sndFile); + } else if(command == 36) + m.SetEffectCommand(CMD_VOLUMESLIDE, param); + else if(command == 37) + m.SetEffectCommand(CMD_PORTAMENTOUP, param); + else if(command == 38) + m.SetEffectCommand(CMD_PORTAMENTODOWN, param); + else if(command == 39) + m.SetEffectCommand(CMD_VIBRATOVOL, param); + else if(command == 40) + m.SetEffectCommand(CMD_SPEED, param); +} + + +CSoundFile::ProbeResult CSoundFile::ProbeFileHeaderRTM(MemoryFileReader file, const uint64* pfilesize) +{ + RTMObjectHeader fileHeader; + if(!file.ReadStruct(fileHeader)) + return ProbeWantMoreData; + if(!fileHeader.IsValid()) + return ProbeFailure; + RTMMHeader songHeader; + if(file.ReadStructPartial(songHeader, RTMMHeader::HeaderProbingSize()) < RTMMHeader::HeaderProbingSize()) + return ProbeWantMoreData; + if(!fileHeader.IsValid()) + return ProbeFailure; + return ProbeAdditionalSize(file, pfilesize, fileHeader.objectSize - RTMMHeader::HeaderProbingSize() + songHeader.GetHeaderMinimumAdditionalSize()); +} + + +bool CSoundFile::ReadRTM(FileReader& file, ModLoadingFlags loadFlags) +{ + file.Rewind(); + RTMObjectHeader fileHeader; + if(!file.ReadStruct(fileHeader)) + return false; + if(!fileHeader.IsValid()) + return false; + RTMMHeader songHeader; + if(file.ReadStructPartial(songHeader, fileHeader.objectSize) < fileHeader.objectSize) + return false; + if(!songHeader.IsValid()) + return false; + if(!file.CanRead(songHeader.GetHeaderMinimumAdditionalSize())) + return false; + if(loadFlags == onlyVerifyHeader) + return true; + + InitializeGlobals(MOD_TYPE_IT, songHeader.numChannels); + m_nInstruments = std::min(static_cast(songHeader.numInstruments), static_cast(MAX_INSTRUMENTS - 1)); + m_SongFlags = SONG_ITCOMPATGXX | SONG_ITOLDEFFECTS; + m_SongFlags.set(SONG_LINEARSLIDES, songHeader.flags & RTMMHeader::songLinearSlides); + Order().SetDefaultTempoInt(songHeader.tempo); + Order().SetDefaultSpeed(songHeader.speed); + + m_songArtist = mpt::ToUnicode(mpt::Charset::CP437, mpt::String::ReadBuf(mpt::String::maybeNullTerminated, songHeader.composer)); + m_songName = mpt::String::ReadBuf(mpt::String::maybeNullTerminated, fileHeader.name); + if(fileHeader.version >= 0x112) + { + if(m_songName.empty()) + m_songName = mpt::String::ReadBuf(mpt::String::maybeNullTerminated, songHeader.originalName); + else + m_songMessage.SetRaw(mpt::String::ReadBuf(mpt::String::maybeNullTerminated, songHeader.originalName)); + } + + FileReader extraData = file.ReadChunk(songHeader.extraDataSize); + ReadOrderFromFile(Order(), extraData, songHeader.numOrders); + for(CHANNELINDEX chn = 0; chn < GetNumChannels(); chn++) + { + ChnSettings[chn].nPan = static_cast((songHeader.panning[chn] + 64) * 2); + if(songHeader.flags & RTMMHeader::songTrackNames) + extraData.ReadString(ChnSettings[chn].szName, 16); + } + + m_modFormat.formatName = MPT_UFORMAT("Real Tracker {}.{}")(fileHeader.version >> 8, mpt::ufmt::hex0<2>(fileHeader.version & 0xFF)); + m_modFormat.type = UL_("rtm"); + m_modFormat.madeWithTracker = mpt::ToUnicode(mpt::Charset::CP437, mpt::String::ReadBuf(mpt::String::maybeNullTerminated, songHeader.software)); + m_modFormat.charset = mpt::Charset::CP437; + + Patterns.ResizeArray(songHeader.numPatterns); + for(PATTERNINDEX pat = 0; pat < songHeader.numPatterns; pat++) + { + RTMObjectHeader objectHeader; + if(!file.ReadStruct(objectHeader)) + return false; + RTMPatternHeader patHeader; + file.ReadStructPartial(patHeader, objectHeader.objectSize); + FileReader patternData = file.ReadChunk(patHeader.packedSize); + if(!(loadFlags & loadPatternData) || !Patterns.Insert(pat, patHeader.numRows)) + continue; + + Patterns[pat].SetName(mpt::String::ReadBuf(mpt::String::maybeNullTerminated, objectHeader.name)); + ROWINDEX row = 0; + CHANNELINDEX chn = 0; + auto rowData = Patterns[pat].GetRow(0); + while(row < patHeader.numRows && patternData.CanRead(1)) + { + const uint8 b = patternData.ReadUint8(); + if(b == 0) + { + row++; + chn = 0; + if(row < patHeader.numRows) + rowData = Patterns[pat].GetRow(row); + continue; + } + + if(b & 0x01) + chn = patternData.ReadUint8(); + if(chn >= GetNumChannels()) + return false; + + ModCommand &m = rowData[chn]; + if(b & 0x02) + { + uint8 note = patternData.ReadUint8(); + if(note == 0xFE) + m.note = NOTE_KEYOFF; + else if(note < 120) + m.note = note + NOTE_MIDDLEC - 48; + } + if(b & 0x04) + m.instr = patternData.ReadUint8(); + + uint8 cmd1 = 0, param1 = 0, cmd2 = 0, param2 = 0; + if(b & 0x08) + cmd1 = patternData.ReadUint8(); + if(b & 0x10) + param1 = patternData.ReadUint8(); + if(b & 0x20) + cmd2 = patternData.ReadUint8(); + if(b & 0x40) + param2 = patternData.ReadUint8(); + + if(cmd1 || param1) + ConvertRTMEffect(m, cmd1, param1, *this); + if(cmd2 || param2) + { + ModCommand dummy; + ConvertRTMEffect(dummy, cmd2, param2, *this); + m.FillInTwoCommands(m.command, m.param, dummy.command, dummy.param); + } + chn++; + } + } + + for(INSTRUMENTINDEX instr = 1; instr <= m_nInstruments; instr++) + { + RTMObjectHeader objectHeader; + if(!file.ReadStruct(objectHeader)) + return false; + RTINHeader insHeader; + file.ReadStructPartial(insHeader, objectHeader.objectSize); + if(!AllocateInstrument(instr)) + return false; + insHeader.ConvertToMPT(*Instruments[instr], m_nSamples + 1); + Instruments[instr]->name = mpt::String::ReadBuf(mpt::String::maybeNullTerminated, objectHeader.name); + for(SAMPLEINDEX smp = 0; smp < insHeader.numSamples; smp++) + { + RTMObjectHeader smpObjectHeader; + if(!file.ReadStruct(smpObjectHeader)) + return false; + RTSMHeader smpHeader; + file.ReadStructPartial(smpHeader, smpObjectHeader.objectSize); + FileReader sampleData = file.ReadChunk(smpHeader.length); + if(!CanAddMoreSamples()) + continue; + ModSample &mptSmp = Samples[++m_nSamples]; + smpHeader.ConvertToMPT(mptSmp, insHeader); + m_szNames[m_nSamples] = mpt::String::ReadBuf(mpt::String::maybeNullTerminated, smpObjectHeader.name); + if(loadFlags & loadSampleData) + smpHeader.GetSampleFormat().ReadSample(mptSmp, sampleData); + } + } + + return true; +} + +OPENMPT_NAMESPACE_END diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_s3m.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_s3m.cpp index c4fce463c..a6465c545 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_s3m.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_s3m.cpp @@ -18,6 +18,7 @@ #include "mpt/io/io_stdstream.hpp" #include "../common/mptFileIO.h" #ifdef MODPLUG_TRACKER +#include "../mptrack/Moddoc.h" #include "../mptrack/TrackerSettings.h" #endif // MODPLUG_TRACKER #endif // MODPLUG_NO_FILESAVE @@ -84,7 +85,7 @@ void CSoundFile::S3MSaveConvert(const ModCommand &source, uint8 &command, uint8 case CMD_DUMMY: command = (param ? '@' : 0); break; case CMD_SPEED: command = 'A'; break; case CMD_POSITIONJUMP: command = 'B'; break; - case CMD_PATTERNBREAK: command = 'C'; if(!toIT) param = ((param / 10) << 4) + (param % 10); break; + case CMD_PATTERNBREAK: command = 'C'; if(!toIT) param = static_cast(((param / 10) << 4) + (param % 10)); break; case CMD_VOLUMESLIDE: command = 'D'; break; case CMD_PORTAMENTODOWN: command = 'E'; if(param >= 0xE0 && (GetType() & (MOD_TYPE_MOD | MOD_TYPE_XM))) param = 0xDF; break; case CMD_PORTAMENTOUP: command = 'F'; if(param >= 0xE0 && (GetType() & (MOD_TYPE_MOD | MOD_TYPE_XM))) param = 0xDF; break; @@ -219,7 +220,7 @@ bool CSoundFile::ReadS3M(FileReader &file, ModLoadingFlags loadFlags) { return false; } - if(!file.CanRead(mpt::saturate_cast(GetHeaderMinimumAdditionalSize(fileHeader)))) + if(!file.CanRead(mpt::saturate_cast(GetHeaderMinimumAdditionalSize(fileHeader)))) { return false; } @@ -228,7 +229,7 @@ bool CSoundFile::ReadS3M(FileReader &file, ModLoadingFlags loadFlags) return true; } - InitializeGlobals(MOD_TYPE_S3M); + InitializeGlobals(MOD_TYPE_S3M, fileHeader.GetNumChannels()); m_nMinPeriod = 64; m_nMaxPeriod = 32767; @@ -252,6 +253,7 @@ bool CSoundFile::ReadS3M(FileReader &file, ModLoadingFlags loadFlags) const bool usePanningTable = fileHeader.usePanningTable == S3MFileHeader::idPanning; const bool offsetsAreCanonical = !patternOffsets.empty() && !sampleOffsets.empty() && patternOffsets[0] > sampleOffsets[0]; const int32 schismDateVersion = SchismTrackerEpoch + ((fileHeader.cwtv == 0x4FFF) ? fileHeader.reserved2 : (fileHeader.cwtv - 0x4050)); + uint32 editTimer = 0; switch(fileHeader.cwtv & S3MFileHeader::trackerMask) { case S3MFileHeader::trkAkord & S3MFileHeader::trackerMask: @@ -329,17 +331,15 @@ bool CSoundFile::ReadS3M(FileReader &file, ModLoadingFlags loadFlags) if(fileHeader.cwtv >= S3MFileHeader::trkIT2_07 && fileHeader.reserved3 != 0) { // Starting from version 2.07, IT stores the total edit time of a module in the "reserved" field - uint32 editTime = DecodeITEditTimer(fileHeader.cwtv, fileHeader.reserved3); - - FileHistory hist; - hist.openTime = static_cast(editTime * (HISTORY_TIMER_PRECISION / 18.2)); - m_FileHistory.push_back(hist); + editTimer = DecodeITEditTimer(fileHeader.cwtv, fileHeader.reserved3); } nonCompatTracker = true; m_playBehaviour.set(kPeriodsAreHertz); m_playBehaviour.set(kITRetrigger); m_playBehaviour.set(kITShortSampleRetrig); m_playBehaviour.set(kST3SampleSwap); // Not exactly like ST3, but close enough + m_playBehaviour.set(kITPortaNoNote); + m_playBehaviour.set(kITPortamentoSwapResetsPos); m_nMinPeriod = 1; break; case S3MFileHeader::trkSchismTracker: @@ -357,6 +357,12 @@ bool CSoundFile::ReadS3M(FileReader &file, ModLoadingFlags loadFlags) if(schismDateVersion >= SchismVersionFromDate<2016, 05, 13>::date) m_playBehaviour.set(kITShortSampleRetrig); m_playBehaviour.reset(kST3TonePortaWithAdlibNote); + + if(fileHeader.cwtv == (S3MFileHeader::trkSchismTracker | 0xFFF) && fileHeader.reserved3 != 0) + { + // Added in commit 6c4b71f10d4e0bf202dddfa8bd781de510b8bc0b + editTimer = fileHeader.reserved3; + } } nonCompatTracker = true; break; @@ -377,7 +383,12 @@ bool CSoundFile::ReadS3M(FileReader &file, ModLoadingFlags loadFlags) { uint32 mptVersion = (fileHeader.cwtv & S3MFileHeader::versionMask) << 16; if(mptVersion >= 0x01'29'00'00) + { mptVersion |= fileHeader.reserved2; + // Added in OpenMPT 1.32.00.31 + if(fileHeader.reserved3 != 0) + editTimer = fileHeader.reserved3; + } m_dwLastSavedWithVersion = Version(mptVersion); madeWithTracker = UL_("OpenMPT ") + mpt::ufmt::val(m_dwLastSavedWithVersion); } else @@ -402,6 +413,14 @@ bool CSoundFile::ReadS3M(FileReader &file, ModLoadingFlags loadFlags) madeWithTracker = MPT_UFORMAT("{} {}.{}")(madeWithTracker, (fileHeader.cwtv & 0xF00) >> 8, mpt::ufmt::hex0<2>(fileHeader.cwtv & 0xFF)); } + // IT edit timer + if(editTimer != 0) + { + FileHistory hist; + hist.openTime = static_cast(editTimer * (HISTORY_TIMER_PRECISION / 18.2)); + m_FileHistory.push_back(hist); + } + m_modFormat.formatName = UL_("Scream Tracker 3"); m_modFormat.type = UL_("s3m"); m_modFormat.madeWithTracker = std::move(madeWithTracker); @@ -449,26 +468,24 @@ bool CSoundFile::ReadS3M(FileReader &file, ModLoadingFlags loadFlags) m_SongFlags.set(SONG_FASTVOLSLIDES); } - // Speed - m_nDefaultSpeed = fileHeader.speed; - if(m_nDefaultSpeed == 0 || (m_nDefaultSpeed == 255 && isST3)) - { - // Even though ST3 accepts the command AFF as expected, it mysteriously fails to load a default speed of 255... - m_nDefaultSpeed = 6; - } + // Even though ST3 accepts the command AFF as expected, it mysteriously fails to load a default speed of 255... + if(fileHeader.speed == 0 || (fileHeader.speed == 255 && isST3)) + Order().SetDefaultSpeed(6); + else + Order().SetDefaultSpeed(fileHeader.speed); - // Tempo - m_nDefaultTempo.Set(fileHeader.tempo); + // ST3 also fails to load an otherwise valid default tempo of 32... if(fileHeader.tempo < 33) - { - // ST3 also fails to load an otherwise valid default tempo of 32... - m_nDefaultTempo.Set(isST3 ? 125 : 32); - } + Order().SetDefaultTempoInt(isST3 ? 125 : 32); + else + Order().SetDefaultTempoInt(fileHeader.tempo); // Global Volume m_nDefaultGlobalVolume = std::min(fileHeader.globalVol.get(), uint8(64)) * 4u; // The following check is probably not very reliable, but it fixes a few tunes, e.g. - // DARKNESS.S3M by Purple Motion (ST 3.00) and "Image of Variance" by C.C.Catch (ST 3.01): + // DARKNESS.S3M by Purple Motion (ST 3.00) and "Image of Variance" by C.C.Catch (ST 3.01). + // Note that even ST 3.01b imports these files with a global volume of 0, + // so it's not clear if these files ever played "as intended" in any ST3 versions (I don't have any older ST3 versions). if(m_nDefaultGlobalVolume == 0 && fileHeader.cwtv < S3MFileHeader::trkST3_20) { m_nDefaultGlobalVolume = MAX_GLOBAL_VOLUME; @@ -498,23 +515,14 @@ bool CSoundFile::ReadS3M(FileReader &file, ModLoadingFlags loadFlags) } // Channel setup - m_nChannels = 4; std::bitset<32> isAdlibChannel; - for(CHANNELINDEX i = 0; i < 32; i++) + for(CHANNELINDEX i = 0; i < GetNumChannels(); i++) { - ChnSettings[i].Reset(); - uint8 ctype = fileHeader.channels[i] & ~0x80; - if(fileHeader.channels[i] != 0xFF) - { - m_nChannels = i + 1; - if(isStereo) - ChnSettings[i].nPan = (ctype & 8) ? 0xCC : 0x33; // 200 : 56 - } + if(fileHeader.channels[i] != 0xFF && isStereo) + ChnSettings[i].nPan = (ctype & 8) ? 0xCC : 0x33; // 200 : 56 if(fileHeader.channels[i] & 0x80) - { ChnSettings[i].dwFlags = CHN_MUTE; - } if(ctype >= 16 && ctype <= 29) { // Adlib channel - except for OpenMPT 1.19 and older, which would write wrong channel types for PCM channels 16-32. @@ -523,29 +531,22 @@ bool CSoundFile::ReadS3M(FileReader &file, ModLoadingFlags loadFlags) isAdlibChannel[i] = true; } } - if(m_nChannels < 1) - { - m_nChannels = 1; - } // Read extended channel panning if(usePanningTable) { bool hasChannelsWithoutPanning = false; const auto pan = file.ReadArray(); - for(CHANNELINDEX i = 0; i < 32; i++) + for(CHANNELINDEX i = 0; i < GetNumChannels(); i++) { if((pan[i] & 0x20) != 0 && (!isST3 || !isAdlibChannel[i])) - { - ChnSettings[i].nPan = (static_cast(pan[i] & 0x0F) * 256 + 8) / 15; - } else if(pan[i] < 0x10) - { + ChnSettings[i].nPan = static_cast((static_cast(pan[i] & 0x0F) * 256 + 8) / 15u); + else if(pan[i] < 0x10) hasChannelsWithoutPanning = true; - } } - if(m_nChannels < 32 && m_dwLastSavedWithVersion == MPT_V("1.16")) + if(GetNumChannels() < 32 && m_dwLastSavedWithVersion == MPT_V("1.16")) { - // MPT 1.0 alpha 6 up to 1.16.203 set ths panning bit for all channels, regardless of whether they are used or not. + // MPT 1.0 alpha 6 up to 1.16.203 set the panning bit for all channels, regardless of whether they are used or not. // Note: Schism Tracker fixed the same bug in git commit f21fe8bcae8b6dde2df27ede4ac9fe563f91baff if(hasChannelsWithoutPanning) m_modFormat.madeWithTracker = UL_("ModPlug Tracker 1.16 / OpenMPT 1.17"); @@ -591,7 +592,6 @@ bool CSoundFile::ReadS3M(FileReader &file, ModLoadingFlags loadFlags) // All Scream Tracker versions except for some probably early revisions of Scream Tracker 3.00 write GUS addresses. GUS support might not have existed at that point (1992). // Hence if a file claims to be written with ST3 (but not ST3.00), but has no GUS addresses, we deduce that it must be written by some other software (e.g. some PSM -> S3M conversions) isST3 = false; - MPT_UNUSED(isST3); m_modFormat.madeWithTracker = UL_("Unknown"); // Check these only after we are certain that it can't be ST3.01 because that version doesn't sanitize the ultraClicks value yet if(fileHeader.cwtv == S3MFileHeader::trkST3_01 && fileHeader.ultraClicks == 0) @@ -617,6 +617,8 @@ bool CSoundFile::ReadS3M(FileReader &file, ModLoadingFlags loadFlags) if(useGUS) m_nSamplePreAmp = 48; } + if(isST3) + m_playBehaviour.set(kS3MIgnoreCombinedFineSlides); if(anyADPCM) m_modFormat.madeWithTracker += UL_(" (ADPCM packed)"); @@ -657,6 +659,7 @@ bool CSoundFile::ReadS3M(FileReader &file, ModLoadingFlags loadFlags) ROWINDEX row = 0; auto rowBase = Patterns[pat].GetRow(0); + ModCommand dummy; while(row < 64) { uint8 info = file.ReadUint8(); @@ -672,7 +675,6 @@ bool CSoundFile::ReadS3M(FileReader &file, ModLoadingFlags loadFlags) } CHANNELINDEX channel = (info & s3mChannelMask); - ModCommand dummy; ModCommand &m = (channel < GetNumChannels()) ? rowBase[channel] : dummy; if(info & s3mNotePresent) @@ -743,7 +745,7 @@ bool CSoundFile::ReadS3M(FileReader &file, ModLoadingFlags loadFlags) } } - if(pixPlayPanning && zxxCountLeft + zxxCountRight >= m_nChannels && (-zxxCountLeft + zxxCountRight) < static_cast(m_nChannels)) + if(pixPlayPanning && zxxCountLeft + zxxCountRight >= GetNumChannels() && (-zxxCountLeft + zxxCountRight) < static_cast(GetNumChannels())) { // There are enough Zxx commands, so let's assume this was made to be played with PixPlay Patterns.ForEachModCommand([](ModCommand &m) @@ -770,7 +772,7 @@ bool CSoundFile::SaveS3M(std::ostream &f) const 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, 0x80, }; - if(m_nChannels == 0) + if(GetNumChannels() == 0) { return false; } @@ -840,12 +842,27 @@ bool CSoundFile::SaveS3M(std::ostream &f) const // Song Variables fileHeader.globalVol = static_cast(std::min(m_nDefaultGlobalVolume / 4u, uint32(64))); - fileHeader.speed = static_cast(Clamp(m_nDefaultSpeed, 1u, 254u)); - fileHeader.tempo = static_cast(Clamp(m_nDefaultTempo.GetInt(), 33u, 255u)); + fileHeader.speed = static_cast(Clamp(Order().GetDefaultSpeed(), 1u, 254u)); + fileHeader.tempo = static_cast(Clamp(Order().GetDefaultTempo().GetInt(), 33u, 255u)); fileHeader.masterVolume = static_cast(Clamp(m_nSamplePreAmp, 16u, 127u) | 0x80); fileHeader.ultraClicks = 16; fileHeader.usePanningTable = S3MFileHeader::idPanning; + // IT edit timer + uint64 editTimer = 0; + for(const auto &mptHistory : GetFileHistory()) + { + editTimer += static_cast(mptHistory.openTime * HISTORY_TIMER_PRECISION / 18.2); + } +#ifdef MODPLUG_TRACKER + if(const auto modDoc = GetpModDoc(); modDoc != nullptr) + { + auto creationTime = modDoc->GetCreationTime(); + editTimer += mpt::saturate_round((mpt::Date::UnixAsSeconds(mpt::Date::UnixNow()) - mpt::Date::UnixAsSeconds(creationTime)) * HISTORY_TIMER_PRECISION); + } +#endif // MODPLUG_TRACKER + fileHeader.reserved3 = mpt::saturate_cast(editTimer); + mpt::IO::Write(f, fileHeader); Order().WriteAsByte(f, writeOrders); @@ -952,21 +969,17 @@ bool CSoundFile::SaveS3M(std::ostream &f) const { info |= s3mNotePresent; - if(note == NOTE_NONE) - { - note = s3mNoteNone; - } else if(ModCommand::IsSpecialNote(note)) + if(ModCommand::IsSpecialNote(note)) { // Note Cut note = s3mNoteOff; - } else if(note < 12 + NOTE_MIN) + } else if(note >= NOTE_MIN + 12 && note <= NOTE_MIN + 107) { - // Too low - note = 0; - } else if(note <= NOTE_MAX) + note -= NOTE_MIN + 12; + note = static_cast((note % 12) + ((note / 12) << 4)); + } else { - note -= (12 + NOTE_MIN); - note = (note % 12) + ((note / 12) << 4); + note = s3mNoteNone; } if(m.instr > 0 && m.instr <= GetNumSamples()) diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_sfx.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_sfx.cpp index 2a20edd68..c9f322153 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_sfx.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_sfx.cpp @@ -60,7 +60,7 @@ struct SFXSampleHeader uint16be loopStart; uint16be loopLength; - // Convert an MOD sample header to OpenMPT's internal sample header. + // Convert an SFX sample header to OpenMPT's internal sample header. void ConvertToMPT(ModSample &mptSmp, uint32 length) const { mptSmp.Initialize(MOD_TYPE_MOD); @@ -168,11 +168,11 @@ bool CSoundFile::ReadSFX(FileReader &file, ModLoadingFlags loadFlags) SFXFileHeader fileHeader; if(file.Seek(0x3C) && file.ReadStruct(fileHeader) && fileHeader.IsValid(15)) { - InitializeGlobals(MOD_TYPE_SFX); + InitializeGlobals(MOD_TYPE_SFX, 4); m_nSamples = 15; } else if(file.Seek(0x7C) && file.ReadStruct(fileHeader) && fileHeader.IsValid(31)) { - InitializeGlobals(MOD_TYPE_SFX); + InitializeGlobals(MOD_TYPE_SFX, 4); m_nSamples = 31; } else { @@ -190,10 +190,9 @@ bool CSoundFile::ReadSFX(FileReader &file, ModLoadingFlags loadFlags) } file.Skip(sizeof(SFXFileHeader)); - m_nChannels = 4; m_nInstruments = 0; - m_nDefaultTempo = TEMPO((14565.0 * 122.0) / fileHeader.speed); - m_nDefaultSpeed = 6; + Order().SetDefaultTempo(TEMPO((14565.0 * 122.0) / fileHeader.speed)); + Order().SetDefaultSpeed(6); m_nMinPeriod = 14 * 4; m_nMaxPeriod = 3424 * 4; m_nSamplePreAmp = 64; @@ -449,7 +448,7 @@ bool CSoundFile::ReadSFX(FileReader &file, ModLoadingFlags loadFlags) } } - m_modFormat.formatName = m_nSamples == 15 ? MPT_UFORMAT("SoundFX 1.{}")(version) : U_("SoundFX 2.0 / MultiMedia Sound"); + m_modFormat.formatName = m_nSamples == 15 ? MPT_UFORMAT("SoundFX 1.{}")(version) : UL_("SoundFX 2.0 / MultiMedia Sound"); m_modFormat.type = m_nSamples == 15 ? UL_("sfx") : UL_("sfx2"); m_modFormat.charset = mpt::Charset::Amiga_no_C1; diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_stk.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_stk.cpp new file mode 100644 index 000000000..934f25329 --- /dev/null +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_stk.cpp @@ -0,0 +1,488 @@ +/* + * Load_stk.cpp + * ------------ + * Purpose: M15 / STK (Ultimate Soundtracker / Soundtracker) loader + * Notes : (currently none) + * Authors: OpenMPT Devs + * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. + */ + + +#include "stdafx.h" +#include "Loaders.h" +#include "MODTools.h" + +OPENMPT_NAMESPACE_BEGIN + +// We'll have to do some heuristic checks to find out whether this is an old Ultimate Soundtracker module +// or if it was made with the newer Soundtracker versions. +// Thanks for Fraggie for this information! (https://www.un4seen.com/forum/?topic=14471.msg100829#msg100829) +enum STVersions +{ + UST1_00, // Ultimate Soundtracker 1.0-1.21 (K. Obarski) + UST1_80, // Ultimate Soundtracker 1.8-2.0 (K. Obarski) + ST2_00_Exterminator, // SoundTracker 2.0 (The Exterminator), D.O.C. Sountracker II (Unknown/D.O.C.) + ST_III, // Defjam Soundtracker III (Il Scuro/Defjam), Alpha Flight SoundTracker IV (Alpha Flight), D.O.C. SoundTracker IV (Unknown/D.O.C.), D.O.C. SoundTracker VI (Unknown/D.O.C.) + ST_IX, // D.O.C. SoundTracker IX (Unknown/D.O.C.) + MST1_00, // Master Soundtracker 1.0 (Tip/The New Masters) + ST2_00, // SoundTracker 2.0, 2.1, 2.2 (Unknown/D.O.C.) +}; + + +struct STKFileHeaders +{ + char songname[20]; + MODSampleHeader sampleHeaders[15]; + MODFileHeader fileHeader; +}; + +MPT_BINARY_STRUCT(STKFileHeaders, 20 + 15 * 30 + 130) + + +static bool ValidateHeader(const STKFileHeaders &fileHeaders) +{ + // In theory, sample and song names should only ever contain printable ASCII chars and null. + // However, there are quite a few SoundTracker modules in the wild with random + // characters. To still be able to distguish them from other formats, we just reject + // files with *too* many bogus characters. Arbitrary threshold: 48 bogus characters in total + // or more than 5 invalid characters just in the title alone + uint32 invalidCharsInTitle = CountInvalidChars(fileHeaders.songname); + uint32 invalidChars = invalidCharsInTitle; + + SmpLength totalSampleLen = 0; + uint8 allVolumes = 0; + uint8 validNameCount = 0; + bool invalidNames = false; + + for(SAMPLEINDEX smp = 0; smp < 15; smp++) + { + const MODSampleHeader &sampleHeader = fileHeaders.sampleHeaders[smp]; + + invalidChars += CountInvalidChars(sampleHeader.name); + + // schmokk.mod has a non-zero value here but it should not be treated as finetune + if(sampleHeader.finetune != 0) + invalidChars += 16; + if(const auto nameType = ClassifyName(sampleHeader.name); nameType == NameClassification::ValidASCII) + validNameCount++; + else if(nameType == NameClassification::Invalid) + invalidNames = true; + + // Sanity checks - invalid character count adjusted for ata.mod (MD5 937b79b54026fa73a1a4d3597c26eace, SHA1 3322ca62258adb9e0ae8e9afe6e0c29d39add874) + // Sample length adjusted for romantic.stk which has a (valid) sample of length 72222 + if(invalidChars > 48 + || sampleHeader.volume > 64 + || sampleHeader.length > 37000) + { + return false; + } + + totalSampleLen += sampleHeader.length; + allVolumes |= sampleHeader.volume; + } + + // scramble_2.mod has a lot of garbage in the song title, but it has lots of properly-formatted sample names, so we consider those to be more important than the garbage bytes. + if(invalidCharsInTitle > 5 && (validNameCount < 4 || invalidNames)) + return false; + + // Reject any files with no (or only silent) samples at all, as this might just be a random binary file (e.g. ID3 tags with tons of padding) + if(totalSampleLen == 0 || allVolumes == 0) + return false; + + // Sanity check: No more than 128 positions. ST's GUI limits tempo to [1, 220]. + // There are some mods with a tempo of 0 (explora3-death.mod) though, so ignore the lower limit. + if(fileHeaders.fileHeader.numOrders > 128 || fileHeaders.fileHeader.restartPos > 220) + return false; + + uint8 maxPattern = *std::max_element(std::begin(fileHeaders.fileHeader.orderList), std::end(fileHeaders.fileHeader.orderList)); + // Sanity check: 64 patterns max. + if(maxPattern > 63) + return false; + + // No playable song, and lots of null values => most likely a sparse binary file but not a module + if(fileHeaders.fileHeader.restartPos == 0 && fileHeaders.fileHeader.numOrders == 0 && maxPattern == 0) + return false; + + return true; +} + + +template +static bool ValidateFirstSTKPattern(TFileReader &file) +{ + // threshold is chosen as: [threshold for all patterns combined] / [max patterns] * [margin, do not reject too much] + return ValidateMODPatternData(file, 512 / 64 * 2, false); +} + + +CSoundFile::ProbeResult CSoundFile::ProbeFileHeaderSTK(MemoryFileReader file, const uint64 *pfilesize) +{ + STKFileHeaders fileHeaders; + if(!file.ReadStruct(fileHeaders)) + return ProbeWantMoreData; + if(!ValidateHeader(fileHeaders)) + return ProbeFailure; + if(!file.CanRead(sizeof(MODPatternData))) + return ProbeWantMoreData; + if(!ValidateFirstSTKPattern(file)) + return ProbeFailure; + MPT_UNREFERENCED_PARAMETER(pfilesize); + return ProbeSuccess; +} + + +bool CSoundFile::ReadSTK(FileReader &file, ModLoadingFlags loadFlags) +{ + file.Rewind(); + + STKFileHeaders fileHeaders; + if(!file.ReadStruct(fileHeaders)) + return false; + if(!ValidateHeader(fileHeaders)) + return false; + if(!ValidateFirstSTKPattern(file)) + return false; + file.Seek(sizeof(STKFileHeaders)); + + InitializeGlobals(MOD_TYPE_MOD, 4); + m_playBehaviour.reset(kMODOneShotLoops); + m_playBehaviour.set(kMODIgnorePanning); + m_playBehaviour.set(kMODSampleSwap); // untested + + STVersions minVersion = UST1_00; + + bool hasDiskNames = true; + SmpLength totalSampleLen = 0; + m_nSamples = 15; + + for(SAMPLEINDEX smp = 1; smp <= 15; smp++) + { + ModSample &mptSmp = Samples[smp]; + const MODSampleHeader &sampleHeader = fileHeaders.sampleHeaders[smp - 1]; + ReadMODSample(sampleHeader, Samples[smp], m_szNames[smp], true); + mptSmp.nFineTune = 0; + + totalSampleLen += mptSmp.nLength; + + if(sampleHeader.HasDiskName()) + { + // Ultimate Soundtracker 1.8 and D.O.C. SoundTracker IX always have sample names containing disk names. + hasDiskNames = false; + } + + // Loop start is always in bytes, not words, so don't trust the auto-fix magic in the sample header conversion (fixes loop of "st-01:asia" in mod.drag 10) + if(sampleHeader.loopLength > 1) + { + mptSmp.nLoopStart = sampleHeader.loopStart; + mptSmp.nLoopEnd = sampleHeader.loopStart + sampleHeader.loopLength * 2; + mptSmp.SanitizeLoops(); + } + + // UST only handles samples up to 9999 bytes. Master Soundtracker 1.0 and SoundTracker 2.0 introduce 32KB samples. + if(sampleHeader.length > 4999 || sampleHeader.loopStart > 9999) + minVersion = std::max(minVersion, MST1_00); + } + + MODFileHeader &fileHeader = fileHeaders.fileHeader; + ReadOrderFromArray(Order(), fileHeader.orderList); + PATTERNINDEX numPatterns = GetNumPatterns(file, *this, fileHeader.numOrders, totalSampleLen, 0, true); + + // Most likely just a file with lots of NULs at the start + if(fileHeader.restartPos == 0 && fileHeader.numOrders == 0 && numPatterns <= 1) + { + return false; + } + + // Let's see if the file is too small (including some overhead for broken files like sll7.mod or ghostbus.mod) + std::size_t requiredRemainingDataSize = numPatterns * 64u * 4u * 4u + totalSampleLen; + if(!file.CanRead(requiredRemainingDataSize - std::min(requiredRemainingDataSize, 65536u))) + return false; + + if(loadFlags == onlyVerifyHeader) + return true; + + // Now we can be pretty sure that this is a valid Soundtracker file. Set up default song settings. + // explora3-death.mod has a tempo of 0 + if(!fileHeader.restartPos) + fileHeader.restartPos = 0x78; + // jjk55 by Jesper Kyd has a weird tempo set, but it needs to be ignored. + if(!memcmp(fileHeaders.songname, "jjk55", 6)) + fileHeader.restartPos = 0x78; + // Sample 7 in echoing.mod won't "loop" correctly if we don't convert the VBlank tempo. + Order().SetDefaultTempoInt(125); + if(fileHeader.restartPos != 0x78) + { + // Convert to CIA timing + Order().SetDefaultTempo(TEMPO((709379.0 * 125.0 / 50.0) / ((240 - fileHeader.restartPos) * 122.0))); + if(minVersion > UST1_80) + { + // D.O.C. SoundTracker IX re-introduced the variable tempo after some other versions dropped it. + minVersion = std::max(minVersion, hasDiskNames ? ST_IX : MST1_00); + } else + { + // Ultimate Soundtracker 1.8 adds variable tempo + minVersion = std::max(minVersion, hasDiskNames ? UST1_80 : ST2_00_Exterminator); + } + } + m_nMinPeriod = 113 * 4; + m_nMaxPeriod = 856 * 4; + m_nSamplePreAmp = 64; + m_SongFlags.set(SONG_PT_MODE | SONG_FORMAT_NO_VOLCOL | SONG_AUTO_VOLSLIDE_STK); + m_songName = mpt::String::ReadBuf(mpt::String::spacePadded, fileHeaders.songname); + + // Setup channel pan positions and volume + SetupMODPanning(); + + FileReader::pos_type patOffset = file.GetPosition(); + + // Scan patterns to identify Soundtracker versions and reject garbage. + uint32 illegalBytes = 0, totalNumDxx = 0; + bool useAutoSlides = false; + for(PATTERNINDEX pat = 0; pat < numPatterns; pat++) + { + const bool patternInUse = mpt::contains(Order(), pat); + uint8 numDxx = 0, autoSlides = 0; + uint8 emptyCmds = 0; + MODPatternData patternData; + file.ReadArray(patternData); + if(patternInUse) + { + illegalBytes += CountMalformedMODPatternData(patternData, false); + // Reject files that contain a lot of illegal pattern data. + // STK.the final remix (MD5 5ff13cdbd77211d1103be7051a7d89c9, SHA1 e94dba82a5da00a4758ba0c207eb17e3a89c3aa3) + // has one illegal byte, so we only reject after an arbitrary threshold has been passed. + // This also allows to play some rather damaged files like + // crockets.mod (MD5 995ed9f44cab995a0eeb19deb52e2a8b, SHA1 6c79983c3b7d55c9bc110b625eaa07ce9d75f369) + // but naturally we cannot recover the broken data. + + // We only check patterns that are actually being used in the order list, because some bad rips of the + // "operation wolf" soundtrack have 15 patterns for several songs, but the last few patterns are just garbage. + // Apart from those hidden patterns, the files play fine. + // Example: operation wolf - wolf1.mod (MD5 739acdbdacd247fbefcac7bc2d8abe6b, SHA1 e6b4813daacbf95f41ce9ec3b22520a2ae07eed8) + if(illegalBytes > std::max(512u, numPatterns * 128u)) + return false; + } + for(ROWINDEX row = 0; row < 64; row++) + { + for(CHANNELINDEX chn = 0; chn < 4; chn++) + { + const auto &data = patternData[row][chn]; + const uint8 eff = data[2] & 0x0F, param = data[3]; + // Check for empty space between the last Dxx command and the beginning of another pattern + if(emptyCmds != 0 && !memcmp(data.data(), "\0\0\0\0", 4)) + { + emptyCmds++; + if(emptyCmds > 32) + { + // Since there is a lot of empty space after the last Dxx command, + // we assume it's supposed to be a pattern break effect. + minVersion = ST2_00; + } + } else + { + emptyCmds = 0; + } + + switch(eff) + { + case 1: + case 2: + if(param > 0x1F && minVersion == UST1_80) + { + // If a 1xx / 2xx effect has a parameter greater than 0x20, it is assumed to be UST. + minVersion = hasDiskNames ? UST1_80 : UST1_00; + } else if(eff == 1 && param > 0 && param < 0x03) + { + // This doesn't look like an arpeggio. + minVersion = std::max(minVersion, ST2_00_Exterminator); + } else if(eff == 1 && (param == 0x37 || param == 0x47) && minVersion <= ST2_00_Exterminator) + { + // This suspiciously looks like an arpeggio. + // Catch sleepwalk.mod by Karsten Obarski, which has a default tempo of 125 rather than 120 in the header, so gets mis-identified as a later tracker version. + minVersion = hasDiskNames ? UST1_80 : UST1_00; + } + break; + case 0x0B: + minVersion = ST2_00; + break; + case 0x0C: + case 0x0D: + case 0x0E: + minVersion = std::max(minVersion, ST2_00_Exterminator); + if(eff == 0x0D) + { + emptyCmds = 1; + if(param == 0 && row == 0) + { + // Fix a possible tracking mistake in Blood Money title - who wants to do a pattern break on the first row anyway? + break; + } + numDxx++; + } else if(eff == 0x0E) + { + if(param > 1 || ++autoSlides > 1) + useAutoSlides = true; + } + break; + case 0x0F: + minVersion = std::max(minVersion, ST_III); + break; + } + } + } + + if(numDxx > 0 && numDxx < 3) + { + // Not many Dxx commands in one pattern means they were probably pattern breaks + minVersion = ST2_00; + } + totalNumDxx += numDxx; + } + + // If there is a huge number of Dxx commands, this is extremely unlikely to be a SoundTracker 2.0 module + if(totalNumDxx > numPatterns + 32u && minVersion == ST2_00) + minVersion = MST1_00; + + file.Seek(patOffset); + + // Reading patterns + if(loadFlags & loadPatternData) + Patterns.ResizeArray(numPatterns); + for(PATTERNINDEX pat = 0; pat < numPatterns; pat++) + { + MODPatternData patternData; + file.ReadArray(patternData); + + if(!(loadFlags & loadPatternData) || !Patterns.Insert(pat, 64)) + { + continue; + } + + for(ROWINDEX row = 0; row < 64; row++) + { + auto rowBase = Patterns[pat].GetRow(row); + for(CHANNELINDEX chn = 0; chn < 4; chn++) + { + ModCommand &m = rowBase[chn]; + auto [command, param] = ReadMODPatternEntry(patternData[row][chn], m); + + if(command || param) + { + if(command == 0x0D) + { + if(minVersion != ST2_00) + { + // Dxy is volume slide in some Soundtracker versions, D00 is a pattern break in the latest versions. + command = 0x0A; + } else + { + param = 0; + } + } else if(command == 0x0C) + { + // Volume is sent as-is to the chip, which ignores the highest bit. + param &= 0x7F; + } else if(command == 0x0E && (param > 0x01 || minVersion < ST_IX) && useAutoSlides) + { + m.command = CMD_AUTO_VOLUMESLIDE; + m.param = param; + continue; + } else if(command == 0x0F) + { + // Only the low nibble is evaluated in Soundtracker. + param &= 0x0F; + } + + if(minVersion <= UST1_80) + { + // UST effects + m.param = param; + switch(command) + { + case 0: + // jackdance.mod by Karsten Obarski has 0xy arpeggios... + if(param < 0x03) + { + m.command = CMD_NONE; + } else + { + m.command = CMD_ARPEGGIO; + } + break; + case 1: + m.command = CMD_ARPEGGIO; + break; + case 2: + if(m.param & 0x0F) + { + m.command = CMD_PORTAMENTOUP; + m.param &= 0x0F; + } else if(m.param >> 4) + { + m.command = CMD_PORTAMENTODOWN; + m.param >>= 4; + } + break; + default: + m.command = CMD_NONE; + break; + } + } else + { + ConvertModCommand(m, command, param); + } + } + } + } + } + + [[maybe_unused]] /* silence clang-tidy deadcode.DeadStores */ const mpt::uchar *madeWithTracker = UL_(""); + switch(minVersion) + { + case UST1_00: + madeWithTracker = UL_("Ultimate Soundtracker 1.0-1.21"); + break; + case UST1_80: + madeWithTracker = UL_("Ultimate Soundtracker 1.8-2.0"); + break; + case ST2_00_Exterminator: + madeWithTracker = UL_("SoundTracker 2.0 / D.O.C. SoundTracker II"); + break; + case ST_III: + madeWithTracker = UL_("Defjam Soundtracker III / Alpha Flight SoundTracker IV / D.O.C. SoundTracker IV / VI"); + break; + case ST_IX: + madeWithTracker = UL_("D.O.C. SoundTracker IX"); + break; + case MST1_00: + madeWithTracker = UL_("Master Soundtracker 1.0"); + break; + case ST2_00: + madeWithTracker = UL_("SoundTracker 2.0 / 2.1 / 2.2"); + break; + } + + m_modFormat.formatName = UL_("Soundtracker"); + m_modFormat.type = UL_("stk"); + m_modFormat.madeWithTracker = madeWithTracker; + m_modFormat.charset = mpt::Charset::Amiga_no_C1; + + // Reading samples + if(loadFlags & loadSampleData) + { + for(SAMPLEINDEX smp = 1; smp <= 15; smp++) + { + // Looped samples in (Ultimate) Soundtracker seem to ignore all sample data before the actual loop start. + // This avoids the clicks in the first sample of pretend.mod by Karsten Obarski. + file.Skip(Samples[smp].nLoopStart); + Samples[smp].nLength -= Samples[smp].nLoopStart; + Samples[smp].nLoopEnd -= Samples[smp].nLoopStart; + Samples[smp].nLoopStart = 0; + MODSampleHeader::GetSampleFormat().ReadSample(Samples[smp], file); + } + } + + return true; +} + +OPENMPT_NAMESPACE_END diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_stm.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_stm.cpp index b158e91f4..ef95a86ef 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_stm.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_stm.cpp @@ -49,7 +49,7 @@ struct STMSampleHeader && mptSmp.nLoopEnd != 0xFFFF) { mptSmp.uFlags = CHN_LOOP; - mptSmp.nLength = std::max(mptSmp.nLoopEnd, mptSmp.nLength); + mptSmp.nLength = std::max(mptSmp.nLoopEnd, mptSmp.nLength); // ST2 does not sanitize loop end, allow it to overflow into the next sample's data } } }; @@ -113,7 +113,7 @@ static bool ValidateSTMOrderList(ModSequence &order) for(auto &pat : order) { if(pat == 99 || pat == 255) // 99 is regular, sometimes a single 255 entry can be found too - pat = order.GetInvalidPatIndex(); + pat = PATTERNINDEX_INVALID; else if(pat > 63) return false; } @@ -145,7 +145,7 @@ static void ConvertSTMCommand(ModCommand &m, const uint8 command, const ROWINDEX break; case CMD_PATTERNBREAK: - m.param = (m.param & 0xF0) * 10 + (m.param & 0x0F); + m.param = static_cast((m.param & 0xF0) * 10 + (m.param & 0x0F)); if(breakPos != ORDERINDEX_INVALID && m.param == 0) { // Merge Bxx + C00 into just Bxx @@ -177,7 +177,7 @@ static void ConvertSTMCommand(ModCommand &m, const uint8 command, const ROWINDEX case CMD_SPEED: if(fileVerMinor < 21) - m.param = ((m.param / 10u) << 4u) + m.param % 10u; + m.param = static_cast(((m.param / 10u) << 4u) + m.param % 10u); if(!m.param) { @@ -224,18 +224,17 @@ bool CSoundFile::ReadSTM(FileReader &file, ModLoadingFlags loadFlags) return false; if(!fileHeader.Validate()) return false; - if(!file.CanRead(mpt::saturate_cast(fileHeader.GetHeaderMinimumAdditionalSize()))) + if(!file.CanRead(mpt::saturate_cast(fileHeader.GetHeaderMinimumAdditionalSize()))) return false; if(loadFlags == onlyVerifyHeader) return true; - InitializeGlobals(MOD_TYPE_STM); - InitializeChannels(); + InitializeGlobals(MOD_TYPE_STM, 4); m_songName = mpt::String::ReadBuf(mpt::String::maybeNullTerminated, fileHeader.songname); - m_modFormat.formatName = U_("Scream Tracker 2"); - m_modFormat.type = U_("stm"); + m_modFormat.formatName = UL_("Scream Tracker 2"); + m_modFormat.type = UL_("stm"); m_modFormat.charset = mpt::Charset::CP437; if(!std::memcmp(fileHeader.trackerName, "!Scream!", 8)) @@ -257,7 +256,6 @@ bool CSoundFile::ReadSTM(FileReader &file, ModLoadingFlags loadFlags) m_playBehaviour.set(kST3SampleSwap); m_nSamples = 31; - m_nChannels = 4; m_nMinPeriod = 64; m_nMaxPeriod = 0x7FFF; @@ -265,12 +263,12 @@ bool CSoundFile::ReadSTM(FileReader &file, ModLoadingFlags loadFlags) uint8 initTempo = fileHeader.initTempo; if(fileHeader.verMinor < 21) - initTempo = ((initTempo / 10u) << 4u) + initTempo % 10u; + initTempo = static_cast(((initTempo / 10u) << 4u) + initTempo % 10u); if(initTempo == 0) initTempo = 0x60; - m_nDefaultTempo = ConvertST2Tempo(initTempo); - m_nDefaultSpeed = initTempo >> 4; + Order().SetDefaultTempo(ConvertST2Tempo(initTempo)); + Order().SetDefaultSpeed(initTempo >> 4); if(fileHeader.verMinor > 10) m_nDefaultGlobalVolume = std::min(fileHeader.globalVolume, uint8(64)) * 4u; @@ -340,7 +338,7 @@ bool CSoundFile::ReadSTM(FileReader &file, ModLoadingFlags loadFlags) if(note == 0xFE) m->note = NOTE_NOTECUT; else if(note < 0x60) - m->note = (note >> 4) * 12 + (note & 0x0F) + 36 + NOTE_MIN; + m->note = static_cast((note >> 4) * 12 + (note & 0x0F) + 36 + NOTE_MIN); m->instr = insVol >> 3; if(m->instr > 31) @@ -385,7 +383,7 @@ bool CSoundFile::ReadSTM(FileReader &file, ModLoadingFlags loadFlags) // ST2 just plays random noise for samples with a default volume of 0 if(sample.nLength && sample.nVolume > 0) { - FileReader::off_t sampleOffset = sampleOffsets[smp - 1] << 4; + FileReader::pos_type sampleOffset = sampleOffsets[smp - 1] << 4; // acidlamb.stm has some bogus samples with sample offsets past EOF if(sampleOffset > sizeof(STMFileHeader) && file.Seek(sampleOffset)) { @@ -464,18 +462,16 @@ bool CSoundFile::ReadSTX(FileReader &file, ModLoadingFlags loadFlags) return false; if(!fileHeader.Validate()) return false; - if (!file.CanRead(mpt::saturate_cast(fileHeader.GetHeaderMinimumAdditionalSize()))) + if (!file.CanRead(mpt::saturate_cast(fileHeader.GetHeaderMinimumAdditionalSize()))) return false; if(loadFlags == onlyVerifyHeader) return true; - InitializeGlobals(MOD_TYPE_STM); - InitializeChannels(); + InitializeGlobals(MOD_TYPE_STM, 4); m_songName = mpt::String::ReadBuf(mpt::String::maybeNullTerminated, fileHeader.songName); m_nSamples = fileHeader.numSamples; - m_nChannels = 4; m_nMinPeriod = 64; m_nMaxPeriod = 0x7FFF; @@ -485,8 +481,8 @@ bool CSoundFile::ReadSTX(FileReader &file, ModLoadingFlags loadFlags) if(initTempo == 0) initTempo = 0x60; - m_nDefaultTempo = ConvertST2Tempo(initTempo); - m_nDefaultSpeed = initTempo >> 4; + Order().SetDefaultTempo(ConvertST2Tempo(initTempo)); + Order().SetDefaultSpeed(initTempo >> 4); m_nDefaultGlobalVolume = std::min(fileHeader.globalVolume, uint8(64)) * 4u; std::vector patternOffsets, sampleOffsets; @@ -607,8 +603,8 @@ bool CSoundFile::ReadSTX(FileReader &file, ModLoadingFlags loadFlags) } } - m_modFormat.formatName = U_("Scream Tracker Music Interface Kit"); - m_modFormat.type = U_("stx"); + m_modFormat.formatName = UL_("Scream Tracker Music Interface Kit"); + m_modFormat.type = UL_("stx"); m_modFormat.charset = mpt::Charset::CP437; m_modFormat.madeWithTracker = MPT_UFORMAT("STM2STX 1.{}")(formatVersion); diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_stp.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_stp.cpp index ea66d4ab8..6f8513b86 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_stp.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_stp.cpp @@ -254,17 +254,17 @@ bool CSoundFile::ReadSTP(FileReader &file, ModLoadingFlags loadFlags) return true; } - InitializeGlobals(MOD_TYPE_STP); + InitializeGlobals(MOD_TYPE_STP, 4); m_modFormat.formatName = MPT_UFORMAT("Soundtracker Pro II v{}")(fileHeader.version); - m_modFormat.type = U_("stp"); + m_modFormat.type = UL_("stp"); m_modFormat.charset = mpt::Charset::Amiga_no_C1; - m_nChannels = 4; m_nSamples = 0; + m_SongFlags.set(SONG_AUTO_TONEPORTA | SONG_AUTO_GLOBALVOL | SONG_AUTO_VIBRATO | SONG_AUTO_TREMOLO); - m_nDefaultSpeed = fileHeader.speed; - m_nDefaultTempo = ConvertTempo(fileHeader.timerCount); + Order().SetDefaultSpeed(fileHeader.speed); + Order().SetDefaultTempo(ConvertTempo(fileHeader.timerCount)); m_nMinPeriod = 14 * 4; m_nMaxPeriod = 3424 * 4; @@ -365,7 +365,7 @@ bool CSoundFile::ReadSTP(FileReader &file, ModLoadingFlags loadFlags) if(fileHeader.version > 0) { // Scan for total number of channels - FileReader::off_t patOffset = file.GetPosition(); + FileReader::pos_type patOffset = file.GetPosition(); for(uint16 pat = 0; pat < numPatterns; pat++) { PATTERNINDEX actualPat = file.ReadUint16BE(); @@ -376,22 +376,14 @@ bool CSoundFile::ReadSTP(FileReader &file, ModLoadingFlags loadFlags) channels = file.ReadUint16BE(); if(channels > MAX_BASECHANNELS) return false; - m_nChannels = std::max(m_nChannels, channels); + ChnSettings.resize(std::max(GetNumChannels(), channels)); file.Skip(channels * patternLength * 4u); } file.Seek(patOffset); } - struct ChannelMemory - { - uint8 autoFinePorta, autoPortaUp, autoPortaDown, autoVolSlide, autoVibrato; - uint8 vibratoMem, autoTremolo, autoTonePorta, tonePortaMem; - }; - std::vector channelMemory(m_nChannels); - uint8 globalVolSlide = 0; uint8 speedFrac = static_cast(fileHeader.speedFrac); - for(uint16 pat = 0; pat < numPatterns; pat++) { PATTERNINDEX actualPat = pat; @@ -419,8 +411,6 @@ bool CSoundFile::ReadSTP(FileReader &file, ModLoadingFlags loadFlags) { auto rowBase = Patterns[actualPat].GetRow(row); - bool didGlobalVolSlide = false; - // if a fractional speed value is in use then determine if we should stick a fine pattern delay somewhere bool shouldDelay; switch(speedFrac & 3) @@ -436,17 +426,13 @@ bool CSoundFile::ReadSTP(FileReader &file, ModLoadingFlags loadFlags) for(CHANNELINDEX chn = 0; chn < channels; chn++) { - ChannelMemory &chnMem = channelMemory[chn]; ModCommand &m = rowBase[chn]; const auto [instr, note, command, param] = file.ReadArray(); m.instr = instr; m.param = param; if(note) - { m.note = NOTE_MIDDLEC - 36 + note; - chnMem = ChannelMemory(); - } // Volume slides not only have their nibbles swapped, but the up and down parameters also add up const int totalSlide = -static_cast(m.param >> 4) + (m.param & 0x0F); @@ -458,8 +444,7 @@ bool CSoundFile::ReadSTP(FileReader &file, ModLoadingFlags loadFlags) uint16 ciaTempo = (static_cast(command & 0x0F) << 8) | m.param; if(ciaTempo) { - m.param = mpt::saturate_round(ConvertTempo(ciaTempo).ToDouble()); - m.command = CMD_TEMPO; + m.SetEffectCommand(CMD_TEMPO, mpt::saturate_round(ConvertTempo(ciaTempo).ToDouble())); } } else switch(command) { @@ -468,64 +453,51 @@ bool CSoundFile::ReadSTP(FileReader &file, ModLoadingFlags loadFlags) m.command = CMD_ARPEGGIO; break; case 0x01: // portamento up - m.command = CMD_PORTAMENTOUP; + if(m.param) + m.command = CMD_PORTAMENTOUP; break; case 0x02: // portamento down - m.command = CMD_PORTAMENTODOWN; + if(m.param) + m.command = CMD_PORTAMENTODOWN; break; case 0x03: // auto fine portamento up - chnMem.autoFinePorta = 0x10 | std::min(m.param, ModCommand::PARAM(15)); - chnMem.autoPortaUp = 0; - chnMem.autoPortaDown = 0; - chnMem.autoTonePorta = 0; + m.command = CMD_AUTO_PORTAUP_FINE; break; case 0x04: // auto fine portamento down - chnMem.autoFinePorta = 0x20 | std::min(m.param, ModCommand::PARAM(15)); - chnMem.autoPortaUp = 0; - chnMem.autoPortaDown = 0; - chnMem.autoTonePorta = 0; + m.command = CMD_AUTO_PORTADOWN_FINE; break; case 0x05: // auto portamento up - chnMem.autoFinePorta = 0; - chnMem.autoPortaUp = m.param; - chnMem.autoPortaDown = 0; - chnMem.autoTonePorta = 0; + m.command = CMD_AUTO_PORTAUP; break; case 0x06: // auto portamento down - chnMem.autoFinePorta = 0; - chnMem.autoPortaUp = 0; - chnMem.autoPortaDown = m.param; - chnMem.autoTonePorta = 0; + m.command = CMD_AUTO_PORTADOWN; break; case 0x07: // set global volume m.command = CMD_GLOBALVOLUME; - globalVolSlide = 0; break; case 0x08: // auto global fine volume slide - globalVolSlide = slideParam; + if(totalSlide < 0) + m.SetEffectCommand(CMD_GLOBALVOLSLIDE, 0xF0 | slideParam); + else if(totalSlide > 0) + m.SetEffectCommand(CMD_GLOBALVOLSLIDE, slideParam | 0x0F); break; case 0x09: // fine portamento up - m.command = CMD_MODCMDEX; - m.param = 0x10 | std::min(m.param, ModCommand::PARAM(15)); + m.SetEffectCommand(CMD_MODCMDEX, 0x10 | std::min(m.param, ModCommand::PARAM(15))); break; case 0x0A: // fine portamento down - m.command = CMD_MODCMDEX; - m.param = 0x20 | std::min(m.param, ModCommand::PARAM(15)); + m.SetEffectCommand(CMD_MODCMDEX, 0x20 | std::min(m.param, ModCommand::PARAM(15))); break; case 0x0B: // auto fine volume slide - chnMem.autoVolSlide = slideParam; + m.SetEffectCommand(CMD_AUTO_VOLUMESLIDE, slideParam); break; case 0x0C: // set volume - m.volcmd = VOLCMD_VOLUME; - m.vol = m.param; - chnMem.autoVolSlide = 0; + m.SetVolumeCommand(VOLCMD_VOLUME, std::min(m.param, ModCommand::PARAM(64))); break; case 0x0D: // volume slide (param is swapped compared to .mod) if(totalSlide < 0) m.SetVolumeCommand(VOLCMD_VOLSLIDEDOWN, slideParam & 0x0F); else if(totalSlide > 0) m.SetVolumeCommand(VOLCMD_VOLSLIDEUP, slideParam >> 4); - chnMem.autoVolSlide = 0; break; case 0x0E: // set filter (also uses opposite value compared to .mod) m.SetEffectCommand(CMD_MODCMDEX, 1 ^ (m.param ? 1 : 0)); @@ -535,25 +507,16 @@ bool CSoundFile::ReadSTP(FileReader &file, ModLoadingFlags loadFlags) m.SetEffectCommand(CMD_SPEED, m.param >> 4); break; case 0x10: // auto vibrato - chnMem.autoVibrato = m.param; - chnMem.vibratoMem = 0; + m.command = CMD_VIBRATO; break; case 0x11: // auto tremolo - if(m.param & 0xF) - chnMem.autoTremolo = m.param; - else - chnMem.autoTremolo = 0; + m.command = CMD_TREMOLO; break; case 0x12: // pattern break m.command = CMD_PATTERNBREAK; break; case 0x13: // auto tone portamento - chnMem.autoFinePorta = 0; - chnMem.autoPortaUp = 0; - chnMem.autoPortaDown = 0; - chnMem.autoTonePorta = m.param; - - chnMem.tonePortaMem = 0; + m.command = CMD_TONEPORTAMENTO; break; case 0x14: // position jump m.command = CMD_POSITIONJUMP; @@ -561,13 +524,11 @@ bool CSoundFile::ReadSTP(FileReader &file, ModLoadingFlags loadFlags) case 0x16: // start loop sequence if(m.instr && m.instr <= loopInfo.size()) { - STPLoopList &loopList = loopInfo[m.instr - 1]; - + const STPLoopList &loopList = loopInfo[m.instr - 1]; m.param--; if(m.param < std::min(std::size(ModSample().cues), loopList.size())) { - m.volcmd = VOLCMD_OFFSET; - m.vol = m.param; + m.SetVolumeCommand(VOLCMD_OFFSET, m.param); } } break; @@ -575,7 +536,6 @@ bool CSoundFile::ReadSTP(FileReader &file, ModLoadingFlags loadFlags) if(m.instr && m.instr <= loopInfo.size()) { STPLoopList &loopList = loopInfo[m.instr - 1]; - m.param--; if(m.param < loopList.size()) { @@ -588,13 +548,11 @@ bool CSoundFile::ReadSTP(FileReader &file, ModLoadingFlags loadFlags) case 0x18: // play sequence without loop if(m.instr && m.instr <= loopInfo.size()) { - STPLoopList &loopList = loopInfo[m.instr - 1]; - + const STPLoopList &loopList = loopInfo[m.instr - 1]; m.param--; if(m.param < std::min(std::size(ModSample().cues), loopList.size())) { - m.volcmd = VOLCMD_OFFSET; - m.vol = m.param; + m.SetVolumeCommand(VOLCMD_OFFSET, m.param); } // switch to non-looped version of sample and create it if needed if(!nonLooped[m.instr - 1] && CanAddMoreSamples()) @@ -606,7 +564,6 @@ bool CSoundFile::ReadSTP(FileReader &file, ModLoadingFlags loadFlags) if(m.instr && m.instr <= loopInfo.size()) { STPLoopList &loopList = loopInfo[m.instr - 1]; - m.param--; if(m.param < loopList.size()) { @@ -617,7 +574,7 @@ bool CSoundFile::ReadSTP(FileReader &file, ModLoadingFlags loadFlags) } break; case 0x1D: // fine volume slide (nibble order also swapped) - if(totalSlide < 0) // slide down + if(totalSlide < 0) // slide down m.SetEffectCommand(CMD_MODCMDEX, 0xB0 | (slideParam & 0x0F)); else if(totalSlide > 0) m.SetEffectCommand(CMD_MODCMDEX, 0xA0 | (slideParam >> 4)); @@ -626,12 +583,9 @@ bool CSoundFile::ReadSTP(FileReader &file, ModLoadingFlags loadFlags) // just behave like either a normal fade or a notecut // depending on the speed if(m.param & 0xF0) - { - chnMem.autoVolSlide = m.param >> 4; - } else - { - m.SetEffectCommand(CMD_MODCMDEX, 0xC0 | (m.param & 0xF)); - } + m.SetEffectCommand(CMD_AUTO_VOLUMESLIDE, m.param >> 4); + else + m.SetEffectCommand(CMD_MODCMDEX, 0xC0 | (m.param & 0x0F)); break; case 0x21: // note delay m.SetEffectCommand(CMD_MODCMDEX, 0xD0 | std::min(m.param, ModCommand::PARAM(15))); @@ -660,95 +614,21 @@ bool CSoundFile::ReadSTP(FileReader &file, ModLoadingFlags loadFlags) break; } - bool didVolSlide = false; - - // try to put volume slide in volume command - if(chnMem.autoVolSlide && m.volcmd == VOLCMD_NONE) - { - if(chnMem.autoVolSlide & 0xF0) - { - m.volcmd = VOLCMD_FINEVOLUP; - m.vol = chnMem.autoVolSlide >> 4; - } else - { - m.volcmd = VOLCMD_FINEVOLDOWN; - m.vol = chnMem.autoVolSlide & 0xF; - } - didVolSlide = true; - } - // try to place/combine all remaining running effects. - if(m.command == CMD_NONE) + if(shouldDelay && m.command == CMD_NONE) { - if(chnMem.autoPortaUp) - { - m.command = CMD_PORTAMENTOUP; - m.param = chnMem.autoPortaUp; - - } else if(chnMem.autoPortaDown) - { - m.command = CMD_PORTAMENTODOWN; - m.param = chnMem.autoPortaDown; - } else if(chnMem.autoFinePorta) - { - m.command = CMD_MODCMDEX; - m.param = chnMem.autoFinePorta; - - } else if(chnMem.autoTonePorta) - { - m.command = CMD_TONEPORTAMENTO; - m.param = chnMem.tonePortaMem = chnMem.autoTonePorta; - - } else if(chnMem.autoVibrato) - { - m.command = CMD_VIBRATO; - m.param = chnMem.vibratoMem = chnMem.autoVibrato; - - } else if(!didVolSlide && chnMem.autoVolSlide) - { - m.command = CMD_VOLUMESLIDE; - m.param = chnMem.autoVolSlide; - // convert to a "fine" value by setting the other nibble to 0xF - if(m.param & 0x0F) - m.param |= 0xF0; - else if(m.param & 0xF0) - m.param |= 0x0F; - didVolSlide = true; - MPT_UNUSED(didVolSlide); - - } else if(chnMem.autoTremolo) - { - m.command = CMD_TREMOLO; - m.param = chnMem.autoTremolo; - - } else if(shouldDelay) - { - // insert a fine pattern delay here - m.command = CMD_S3MCMDEX; - m.param = 0x61; - shouldDelay = false; - - } else if(!didGlobalVolSlide && globalVolSlide) - { - m.command = CMD_GLOBALVOLSLIDE; - m.param = globalVolSlide; - // convert to a "fine" value by setting the other nibble to 0xF - if(m.param & 0x0F) - m.param |= 0xF0; - else if(m.param & 0xF0) - m.param |= 0x0F; - - didGlobalVolSlide = true; - } + // insert a fine pattern delay here + m.SetEffectCommand(CMD_S3MCMDEX, 0x61); + shouldDelay = false; } } - // TODO: create/use extra channels for global volslide/delay if needed + // TODO: create/use extra channels for delay if needed? } } // after we know how many channels there really are... - m_nSamplePreAmp = 256 / m_nChannels; + m_nSamplePreAmp = 256 / GetNumChannels(); // Setup channel pan positions and volume SetupMODPanning(true); @@ -797,13 +677,9 @@ bool CSoundFile::ReadSTP(FileReader &file, ModLoadingFlags loadFlags) { // make duplicate samples for this individual section if needed if(info.looped) - { ConvertLoopSlice(Samples[smp], Samples[info.looped], info.loopStart, info.loopLength, true); - } if(info.nonLooped) - { ConvertLoopSlice(Samples[smp], Samples[info.nonLooped], info.loopStart, info.loopLength, false); - } } } } diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_symmod.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_symmod.cpp index bd0d4621b..99752b6ef 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_symmod.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_symmod.cpp @@ -35,14 +35,20 @@ struct SymFileHeader { char magic[4]; // "SymM" uint32be version; + // Technically this is already the first chunk; for simplicity we always assume that the channel count comes first (which in practice it does) + int32be firstChunkID; + uint32be numChannels; bool Validate() const { - return !std::memcmp(magic, "SymM", 4) && version == 1; + return !std::memcmp(magic, "SymM", 4) + && version == 1 + && firstChunkID == -1 + && numChannels > 0 && numChannels <= 256; } }; -MPT_BINARY_STRUCT(SymFileHeader, 8) +MPT_BINARY_STRUCT(SymFileHeader, 16) struct SymEvent @@ -276,8 +282,8 @@ struct SymTranswaveInst std::pair ConvertLoop(const ModSample &mptSmp) const { const double loopScale = static_cast(mptSmp.nLength) / (100 << 16); - const SmpLength start = mpt::saturate_cast(loopScale * std::min(uint32(100 << 16), loopStart.get())); - const SmpLength length = mpt::saturate_cast(loopScale * std::min(uint32(100 << 16), loopLen.get())); + const SmpLength start = mpt::saturate_trunc(loopScale * std::min(uint32(100 << 16), loopStart.get())); + const SmpLength length = mpt::saturate_trunc(loopScale * std::min(uint32(100 << 16), loopLen.get())); return {start, std::min(mptSmp.nLength - start, length)}; } }; @@ -503,7 +509,7 @@ struct SymInstrument return; CopySample, SC::DecodeIdentity>>(newSample, mptSmp.nLength * mptSmp.GetNumChannels(), 1, mptSmp.sample8(), mptSmp.GetSampleSizeInBytes(), 1); mptSmp.uFlags.set(CHN_16BIT); - ctrlSmp::ReplaceSample(mptSmp, newSample, mptSmp.nLength, sndFile); + mptSmp.ReplaceWaveform(newSample, mptSmp.nLength, sndFile); } // Highpass @@ -640,7 +646,6 @@ struct SymInstrument if(!newSample) return; - mptSmp.nLength = newLength; std::memcpy(newSample, mptSmp.sampleb(), (loopStart + loopLen) * bps); for(uint8 i = 0; i < numRepetitions; i++) { @@ -648,7 +653,7 @@ struct SymInstrument } std::memcpy(newSample + loopEnd * bps, mptSmp.sampleb() + (loopStart + loopLen) * bps, (newLength - loopEnd) * bps); - ctrlSmp::ReplaceSample(mptSmp, newSample, mptSmp.nLength, sndFile); + mptSmp.ReplaceWaveform(newSample, newLength, sndFile); } } @@ -657,16 +662,16 @@ struct SymInstrument if(type != Loop && type != Sustain) return {0, 0}; - SmpLength loopStart = static_cast(std::min(loopStartHigh.get(), uint8(100))); - SmpLength loopLen = static_cast(std::min(loopLenHigh.get(), uint8(100))); + SmpLength loopStart = std::min(loopStartHigh.get(), uint8(100)); + SmpLength loopLen = std::min(loopLenHigh.get(), uint8(100)); if(sampleFlags & NewLoopSystem) { loopStart = (loopStart << 16) + loopStartFine; loopLen = (loopLen << 16) + loopLenFine; const double loopScale = static_cast(mptSmp.nLength) / (100 << 16); - loopStart = std::min(mptSmp.nLength, mpt::saturate_cast(loopStart * loopScale)); - loopLen = std::min(mptSmp.nLength - loopStart, mpt::saturate_cast(loopLen * loopScale)); + loopStart = std::min(mptSmp.nLength, mpt::saturate_trunc(loopStart * loopScale)); + loopLen = std::min(mptSmp.nLength - loopStart, mpt::saturate_trunc(loopLen * loopScale)); } else if(mptSmp.HasSampleData()) { // The order of operations here may seem weird as it reduces precision, but it's taken directly from the original assembly source (UpdateRecalcLoop) @@ -952,7 +957,7 @@ static bool ConvertDSP(const SymEvent event, MIDIMacroConfigData::Macro ¯o, { // Symphonie practically uses the same filter for this as for the sample processing. // The cutoff and resonance are an approximation. - const uint8 type = event.note % 5u; + const uint8 type = static_cast(event.note % 5u); const uint8 cutoff = sndFile.FrequencyToCutOff(event.param * 10000.0 / 240.0); const uint8 reso = static_cast(std::min(127, event.inst * 127 / 185)); @@ -1012,10 +1017,6 @@ CSoundFile::ProbeResult CSoundFile::ProbeFileHeaderSymMOD(MemoryFileReader file, return ProbeWantMoreData; if(!fileHeader.Validate()) return ProbeFailure; - if(!file.CanRead(sizeof(uint32be))) - return ProbeWantMoreData; - if(file.ReadInt32BE() >= 0) - return ProbeFailure; return ProbeSuccess; } @@ -1026,16 +1027,15 @@ bool CSoundFile::ReadSymMOD(FileReader &file, ModLoadingFlags loadFlags) SymFileHeader fileHeader; if(!file.ReadStruct(fileHeader) || !fileHeader.Validate()) return false; - if(file.ReadInt32BE() >= 0) - return false; else if(loadFlags == onlyVerifyHeader) return true; - InitializeGlobals(MOD_TYPE_MPT); + InitializeGlobals(MOD_TYPE_MPT, std::min(MAX_BASECHANNELS, static_cast(fileHeader.numChannels))); - m_SongFlags.set(SONG_LINEARSLIDES | SONG_EXFILTERRANGE | SONG_IMPORTED); + m_SongFlags.set(SONG_LINEARSLIDES | SONG_EXFILTERRANGE | SONG_AUTO_VIBRATO | SONG_AUTO_TREMOLO | SONG_IMPORTED); m_playBehaviour = GetDefaultPlaybackBehaviour(MOD_TYPE_IT); m_playBehaviour.reset(kITShortSampleRetrig); + m_nSamplePreAmp = Clamp(512 / GetNumChannels(), 16, 128); enum class ChunkType : int32 { @@ -1074,7 +1074,6 @@ bool CSoundFile::ReadSymMOD(FileReader &file, ModLoadingFlags loadFlags) std::vector patternData; std::vector instruments; - file.SkipBack(sizeof(int32)); while(file.CanRead(sizeof(int32))) { const ChunkType chunkType = static_cast(file.ReadInt32BE()); @@ -1082,11 +1081,7 @@ bool CSoundFile::ReadSymMOD(FileReader &file, ModLoadingFlags loadFlags) { // Simple values case ChunkType::NumChannels: - if(auto numChannels = static_cast(file.ReadUint32BE()); !m_nChannels && numChannels > 0 && numChannels <= MAX_BASECHANNELS) - { - m_nChannels = numChannels; - m_nSamplePreAmp = Clamp(512 / m_nChannels, 16, 128); - } + file.Skip(sizeof(uint32be)); // Already handled break; case ChunkType::TrackLength: @@ -1101,7 +1096,7 @@ bool CSoundFile::ReadSymMOD(FileReader &file, ModLoadingFlags loadFlags) break; case ChunkType::Tempo: - m_nDefaultTempo = TEMPO(1.24 * std::min(file.ReadUint32BE(), uint32(800))); + Order().SetDefaultTempo(TEMPO(1.24 * std::min(file.ReadUint32BE(), uint32(800)))); break; // Unused values @@ -1234,7 +1229,7 @@ bool CSoundFile::ReadSymMOD(FileReader &file, ModLoadingFlags loadFlags) } } - if(!m_nChannels || !trackLen || instruments.empty()) + if(!trackLen || instruments.empty()) return false; if((loadFlags & loadPatternData) && (positions.empty() || patternData.empty() || sequences.empty())) return false; @@ -1340,7 +1335,7 @@ bool CSoundFile::ReadSymMOD(FileReader &file, ModLoadingFlags loadFlags) std::map macroMap; bool useDSP = false; - const uint32 patternSize = m_nChannels * trackLen; + const uint32 patternSize = fileHeader.numChannels * trackLen; const PATTERNINDEX numPatterns = mpt::saturate_cast(patternData.size() / patternSize); Patterns.ResizeArray(numPatterns); @@ -1359,7 +1354,9 @@ bool CSoundFile::ReadSymMOD(FileReader &file, ModLoadingFlags loadFlags) uint8 channelVol = 100; // Volume multiplier, 0...100 uint8 calculatedVol = 64; // Final channel volume uint8 fromAdd = 0; // Base sample offset for FROM and FR&P effects + bool retrigVibrato = false; uint8 curVibrato = 0; + bool retrigTremolo = false; uint8 curTremolo = 0; uint8 sampleVibSpeed = 0; uint8 sampleVibDepth = 0; @@ -1368,7 +1365,7 @@ bool CSoundFile::ReadSymMOD(FileReader &file, ModLoadingFlags loadFlags) uint16 retriggerRemain = 0; uint16 tonePortaRemain = 0; }; - std::vector chnStates(m_nChannels); + std::vector chnStates(GetNumChannels()); // In Symphonie, sequences represent the structure of a song, and not separate songs like in OpenMPT. Hence they will all be loaded into the same ModSequence. for(SymSequence &seq : sequences) @@ -1388,7 +1385,7 @@ bool CSoundFile::ReadSymMOD(FileReader &file, ModLoadingFlags loadFlags) // Sequences are all part of the same song, just add a skip index as a divider ModSequence &order = Order(); if(!order.empty()) - order.push_back(ModSequence::GetIgnoreIndex()); + order.push_back(PATTERNINDEX_SKIP); for(auto &pos : seqPositions) { @@ -1415,19 +1412,21 @@ bool CSoundFile::ReadSymMOD(FileReader &file, ModLoadingFlags loadFlags) uint8 patternSpeed = static_cast(pos.speed); // This may intentionally read into the next pattern - auto srcEvent = patternData.cbegin() + (pos.pattern * patternSize) + (pos.start * m_nChannels); + auto srcEvent = patternData.cbegin() + (pos.pattern * patternSize) + (pos.start * fileHeader.numChannels); const SymEvent emptyEvent{}; ModCommand syncPlayCommand; for(ROWINDEX row = 0; row < pos.length; row++) { ModCommand *rowBase = Patterns[patternIndex].GetpModCommand(row, 0); bool applySyncPlay = false; - for(CHANNELINDEX chn = 0; chn < m_nChannels; chn++) + for(CHANNELINDEX chn = 0; chn < fileHeader.numChannels; chn++) { - ModCommand &m = rowBase[chn]; const SymEvent &event = (srcEvent != patternData.cend()) ? *srcEvent : emptyEvent; if(srcEvent != patternData.cend()) srcEvent++; + if(chn >= GetNumChannels()) + continue; + ModCommand &m = rowBase[chn]; int8 note = (event.note >= 0 && event.note <= 84) ? event.note + 25 : -1; uint8 origInst = event.inst; @@ -1454,8 +1453,8 @@ bool CSoundFile::ReadSymMOD(FileReader &file, ModLoadingFlags loadFlags) m = syncPlayCommand; if(m.command == CMD_NONE && chnState.calculatedVol != chnStates[chn - 1].calculatedVol) { - m.command = CMD_CHANNELVOLUME; - m.param = chnState.calculatedVol = chnStates[chn - 1].calculatedVol; + chnState.calculatedVol = chnStates[chn - 1].calculatedVol; + m.SetEffectCommand(CMD_CHANNELVOLUME, chnState.calculatedVol); } if(!event.IsGlobal()) continue; @@ -1470,37 +1469,32 @@ bool CSoundFile::ReadSymMOD(FileReader &file, ModLoadingFlags loadFlags) switch(event.param) { case SymEvent::StopSample: - m.volcmd = VOLCMD_PLAYCONTROL; - m.vol = 0; + m.SetVolumeCommand(VOLCMD_PLAYCONTROL, 0); chnState.stopped = true; break; case SymEvent::ContSample: - m.volcmd = VOLCMD_PLAYCONTROL; - m.vol = 1; + m.SetVolumeCommand(VOLCMD_PLAYCONTROL, 1); chnState.stopped = false; break; case SymEvent::KeyOff: if(m.note == NOTE_NONE) m.note = chnState.lastNote; - m.volcmd = VOLCMD_OFFSET; - m.vol = 1; + m.SetVolumeCommand(VOLCMD_OFFSET, 1); break; case SymEvent::SpeedDown: if(patternSpeed > 1) { - m.command = CMD_SPEED; - m.param = --patternSpeed; + m.SetEffectCommand(CMD_SPEED, --patternSpeed); } break; case SymEvent::SpeedUp: if(patternSpeed < 0xFF) { - m.command = CMD_SPEED; - m.param = ++patternSpeed; + m.SetEffectCommand(CMD_SPEED, ++patternSpeed); } break; @@ -1509,36 +1503,30 @@ bool CSoundFile::ReadSymMOD(FileReader &file, ModLoadingFlags loadFlags) if(mappedInst != chnState.lastInst) break; m.note = note; - m.command = CMD_TONEPORTAMENTO; - m.param = 0xFF; + m.SetEffectCommand(CMD_TONEPORTA_DURATION, 0); chnState.curPitchSlide = 0; chnState.tonePortaRemain = 0; + chnState.retrigVibrato = chnState.retrigTremolo = true; break; // fine portamentos with range up to half a semitone case SymEvent::PitchUp: - m.command = CMD_PORTAMENTOUP; - m.param = 0xF2; + m.SetEffectCommand(CMD_PORTAMENTOUP, 0xF2); break; case SymEvent::PitchDown: - m.command = CMD_PORTAMENTODOWN; - m.param = 0xF2; + m.SetEffectCommand(CMD_PORTAMENTODOWN, 0xF2); break; case SymEvent::PitchUp2: - m.command = CMD_PORTAMENTOUP; - m.param = 0xF4; + m.SetEffectCommand(CMD_PORTAMENTOUP, 0xF4); break; case SymEvent::PitchDown2: - m.command = CMD_PORTAMENTODOWN; - m.param = 0xF4; + m.SetEffectCommand(CMD_PORTAMENTODOWN, 0xF4); break; case SymEvent::PitchUp3: - m.command = CMD_PORTAMENTOUP; - m.param = 0xF8; + m.SetEffectCommand(CMD_PORTAMENTOUP, 0xF8); break; case SymEvent::PitchDown3: - m.command = CMD_PORTAMENTODOWN; - m.param = 0xF8; + m.SetEffectCommand(CMD_PORTAMENTODOWN, 0xF8); break; } } else @@ -1551,6 +1539,7 @@ bool CSoundFile::ReadSymMOD(FileReader &file, ModLoadingFlags loadFlags) m.instr = chnState.lastInst = mappedInst; chnState.curPitchSlide = 0; chnState.tonePortaRemain = 0; + chnState.retrigVibrato = chnState.retrigTremolo = true; } if(event.param > 0) @@ -1567,12 +1556,11 @@ bool CSoundFile::ReadSymMOD(FileReader &file, ModLoadingFlags loadFlags) applyVolume || chnState.calculatedVol != newVol) { chnState.calculatedVol = newVol; - m.command = CMD_CHANNELVOLUME; - m.param = newVol; + m.SetEffectCommand(CMD_CHANNELVOLUME, newVol); } // Key-On commands with stereo instruments are played on both channels - unless there's already some sort of event - if(event.note > 0 && (chn < m_nChannels - 1) && !(chn % 2u) + if(event.note > 0 && (chn < GetNumChannels() - 1) && !(chn % 2u) && origInst < instruments.size() && instruments[origInst].channel == SymInstrument::StereoL) { ModCommand &next = rowBase[chn + 1]; @@ -1607,14 +1595,18 @@ bool CSoundFile::ReadSymMOD(FileReader &file, ModLoadingFlags loadFlags) m.command = CMD_NONE; break; case SymEvent::Tremolo: - { - // both tremolo speed and depth can go much higher than OpenMPT supports, - // but modules will probably use pretty sane, supportable values anyway - // TODO: handle very small nonzero params - uint8 speed = std::min(15, event.inst >> 3); - uint8 depth = std::min(15, event.param >> 3); - chnState.curTremolo = (speed << 4) | depth; - } + { + // both tremolo speed and depth can go much higher than OpenMPT supports, + // but modules will probably use pretty sane, supportable values anyway + // TODO: handle very small nonzero params + uint8 speed = std::min(15, event.inst >> 3); + uint8 depth = std::min(15, event.param >> 3); + chnState.curTremolo = (speed << 4) | depth; + if(chnState.curTremolo) + chnState.retrigTremolo = true; + else + m.SetEffectCommand(CMD_TREMOLO, 0); + } break; // pitch effects @@ -1637,8 +1629,8 @@ bool CSoundFile::ReadSymMOD(FileReader &file, ModLoadingFlags loadFlags) const int distance = std::abs((note - chnState.lastNote) * 32); chnState.curPitchSlide = 0; m.note = chnState.lastNote = note; - m.command = CMD_TONEPORTAMENTO; - chnState.tonePortaAmt = m.param = mpt::saturate_cast(distance / (2 * event.param)); + chnState.tonePortaAmt = mpt::saturate_cast(distance / (2 * event.param)); + m.SetEffectCommand(CMD_TONEPORTAMENTO, chnState.tonePortaAmt); chnState.tonePortaRemain = static_cast(distance - std::min(distance, chnState.tonePortaAmt * (patternSpeed - 1))); } break; @@ -1647,19 +1639,22 @@ bool CSoundFile::ReadSymMOD(FileReader &file, ModLoadingFlags loadFlags) m.command = CMD_NONE; break; case SymEvent::Vibrato: - { - // both vibrato speed and depth can go much higher than OpenMPT supports, - // but modules will probably use pretty sane, supportable values anyway - // TODO: handle very small nonzero params - uint8 speed = std::min(15, event.inst >> 3); - uint8 depth = std::min(15, event.param); - chnState.curVibrato = (speed << 4) | depth; - } + { + // both vibrato speed and depth can go much higher than OpenMPT supports, + // but modules will probably use pretty sane, supportable values anyway + // TODO: handle very small nonzero params + uint8 speed = std::min(15, event.inst >> 3); + uint8 depth = std::min(15, event.param); + chnState.curVibrato = (speed << 4) | depth; + if(chnState.curVibrato) + chnState.retrigVibrato = true; + else + m.SetEffectCommand(CMD_VIBRATO, 0); + } break; case SymEvent::AddHalfTone: m.note = chnState.lastNote = Clamp(static_cast(chnState.lastNote + event.param), NOTE_MIN, NOTE_MAX); - m.command = CMD_TONEPORTAMENTO; - m.param = 0xFF; + m.SetEffectCommand(CMD_TONEPORTA_DURATION, 0); chnState.tonePortaRemain = 0; break; @@ -1677,8 +1672,7 @@ bool CSoundFile::ReadSymMOD(FileReader &file, ModLoadingFlags loadFlags) uint8 param = static_cast(macroMap.size()); if(ConvertDSP(event, m_MidiCfg.Zxx[param], *this)) { - m.command = CMD_MIDI; - m.param = macroMap[event] = 0x80 | param; + m.SetEffectCommand(CMD_MIDI, macroMap[event] = 0x80 | param); if(event.command == SymEvent::DSPEcho || event.command == SymEvent::DSPDelay) useDSP = true; @@ -1695,14 +1689,13 @@ bool CSoundFile::ReadSymMOD(FileReader &file, ModLoadingFlags loadFlags) // The effect continues on the following rows until the correct amount is reached. if(event.param < 1) break; - m.command = CMD_RETRIG; - m.param = static_cast(std::min(15, event.inst + 1)); - chnState.retriggerRemain = event.param * (event.inst + 1u); + m.SetEffectCommand(CMD_RETRIG, static_cast(std::min(15, event.inst + 1))); + chnState.retriggerRemain = static_cast(event.param * (event.inst + 1u)); break; case SymEvent::SetSpeed: - m.command = CMD_SPEED; - m.param = patternSpeed = event.param ? event.param : 4u; + patternSpeed = event.param ? event.param : 4u; + m.SetEffectCommand(CMD_SPEED, patternSpeed); break; // TODO this applies a fade on the sample level @@ -1720,17 +1713,14 @@ bool CSoundFile::ReadSymMOD(FileReader &file, ModLoadingFlags loadFlags) if(volL != chnState.channelVol) { chnState.channelVol = volL; - - m.command = CMD_CHANNELVOLUME; - m.param = chnState.calculatedVol = static_cast(Util::muldivr_unsigned(chnState.lastVol, chnState.channelVol, 100)); + chnState.calculatedVol = static_cast(Util::muldivr_unsigned(chnState.lastVol, chnState.channelVol, 100)); + m.SetEffectCommand(CMD_CHANNELVOLUME, chnState.calculatedVol); } - if(event.note == 4 && chn < (m_nChannels - 1) && chnStates[chn + 1].channelVol != volR) + if(event.note == 4 && chn < (GetNumChannels() - 1) && chnStates[chn + 1].channelVol != volR) { chnStates[chn + 1].channelVol = volR; - - ModCommand &next = rowBase[chn + 1]; - next.command = CMD_CHANNELVOLUME; - next.param = chnState.calculatedVol = static_cast(Util::muldivr_unsigned(chnState.lastVol, chnState.channelVol, 100)); + chnState.calculatedVol = static_cast(Util::muldivr_unsigned(chnState.lastVol, chnState.channelVol, 100)); + rowBase[chn + 1].SetEffectCommand(CMD_CHANNELVOLUME, chnState.calculatedVol); } } break; @@ -1767,8 +1757,7 @@ bool CSoundFile::ReadSymMOD(FileReader &file, ModLoadingFlags loadFlags) m.instr = chnState.lastInst = mappedInst; if(event.command == SymEvent::ReplayFrom) { - m.volcmd = VOLCMD_TONEPORTAMENTO; - m.vol = 1; + m.SetVolumeCommand(VOLCMD_TONEPORTAMENTO, 1); } // don't always add the command, because often FromAndPitch is used with offset 0 // to act as a key-on which doesn't cancel volume slides, etc @@ -1777,8 +1766,7 @@ bool CSoundFile::ReadSymMOD(FileReader &file, ModLoadingFlags loadFlags) double sampleVib = 0.0; if(chnState.sampleVibDepth) sampleVib = chnState.sampleVibDepth * (std::sin(chnState.sampleVibPhase * (mpt::numbers::pi * 2.0 / 1024.0) + 1.5 * mpt::numbers::pi) - 1.0) / 4.0; - m.command = CMD_OFFSETPERCENTAGE; - m.param = mpt::saturate_round(event.param + chnState.fromAdd + sampleVib); + m.SetEffectCommand(CMD_OFFSETPERCENTAGE, mpt::saturate_round(event.param + chnState.fromAdd + sampleVib)); } chnState.tonePortaRemain = 0; break; @@ -1794,10 +1782,7 @@ bool CSoundFile::ReadSymMOD(FileReader &file, ModLoadingFlags loadFlags) { chnState.retriggerRemain = std::max(chnState.retriggerRemain, static_cast(patternSpeed)) - patternSpeed; if(m.command == CMD_NONE) - { - m.command = CMD_RETRIG; - m.param = 0; - } + m.SetEffectCommand(CMD_RETRIG, 0); } // Handle fractional volume slides @@ -1809,31 +1794,27 @@ bool CSoundFile::ReadSymMOD(FileReader &file, ModLoadingFlags loadFlags) if(patternSpeed > 1 && chnState.curVolSlideAmt >= (patternSpeed - 1)) { uint8 slideAmt = std::min(15, mpt::saturate_round(chnState.curVolSlideAmt / (patternSpeed - 1))); - chnState.curVolSlideAmt -= slideAmt * (patternSpeed - 1); + chnState.curVolSlideAmt -= static_cast(slideAmt * (patternSpeed - 1)); // normal slide up - m.command = CMD_CHANNELVOLSLIDE; - m.param = slideAmt << 4; + m.SetEffectCommand(CMD_CHANNELVOLSLIDE, slideAmt << 4); } else if(chnState.curVolSlideAmt >= 1.0f) { uint8 slideAmt = std::min(15, mpt::saturate_round(chnState.curVolSlideAmt)); - chnState.curVolSlideAmt -= slideAmt; + chnState.curVolSlideAmt -= static_cast(slideAmt); // fine slide up - m.command = CMD_CHANNELVOLSLIDE; - m.param = (slideAmt << 4) | 0x0F; + m.SetEffectCommand(CMD_CHANNELVOLSLIDE, (slideAmt << 4) | 0x0F); } else if(patternSpeed > 1 && chnState.curVolSlideAmt <= -(patternSpeed - 1)) { uint8 slideAmt = std::min(15, mpt::saturate_round(-chnState.curVolSlideAmt / (patternSpeed - 1))); - chnState.curVolSlideAmt += slideAmt * (patternSpeed - 1); + chnState.curVolSlideAmt += static_cast(slideAmt * (patternSpeed - 1)); // normal slide down - m.command = CMD_CHANNELVOLSLIDE; - m.param = slideAmt; + m.SetEffectCommand(CMD_CHANNELVOLSLIDE, slideAmt); } else if(chnState.curVolSlideAmt <= -1.0f) { uint8 slideAmt = std::min(14, mpt::saturate_round(-chnState.curVolSlideAmt)); - chnState.curVolSlideAmt += slideAmt; + chnState.curVolSlideAmt += static_cast(slideAmt); // fine slide down - m.command = CMD_CHANNELVOLSLIDE; - m.param = slideAmt | 0xF0; + m.SetEffectCommand(CMD_CHANNELVOLSLIDE, slideAmt | 0xF0); } } } @@ -1846,31 +1827,27 @@ bool CSoundFile::ReadSymMOD(FileReader &file, ModLoadingFlags loadFlags) if(patternSpeed > 1 && chnState.curPitchSlideAmt >= (patternSpeed - 1)) { uint8 slideAmt = std::min(0xDF, mpt::saturate_round(chnState.curPitchSlideAmt / (patternSpeed - 1))); - chnState.curPitchSlideAmt -= slideAmt * (patternSpeed - 1); + chnState.curPitchSlideAmt -= static_cast(slideAmt * (patternSpeed - 1)); // normal slide up - m.command = CMD_PORTAMENTOUP; - m.param = slideAmt; + m.SetEffectCommand(CMD_PORTAMENTOUP, slideAmt); } else if(chnState.curPitchSlideAmt >= 1.0f) { uint8 slideAmt = std::min(15, mpt::saturate_round(chnState.curPitchSlideAmt)); - chnState.curPitchSlideAmt -= slideAmt; + chnState.curPitchSlideAmt -= static_cast(slideAmt); // fine slide up - m.command = CMD_PORTAMENTOUP; - m.param = slideAmt | 0xF0; + m.SetEffectCommand(CMD_PORTAMENTOUP, slideAmt | 0xF0); } else if(patternSpeed > 1 && chnState.curPitchSlideAmt <= -(patternSpeed - 1)) { uint8 slideAmt = std::min(0xDF, mpt::saturate_round(-chnState.curPitchSlideAmt / (patternSpeed - 1))); - chnState.curPitchSlideAmt += slideAmt * (patternSpeed - 1); + chnState.curPitchSlideAmt += static_cast(slideAmt * (patternSpeed - 1)); // normal slide down - m.command = CMD_PORTAMENTODOWN; - m.param = slideAmt; + m.SetEffectCommand(CMD_PORTAMENTODOWN, slideAmt); } else if(chnState.curPitchSlideAmt <= -1.0f) { uint8 slideAmt = std::min(14, mpt::saturate_round(-chnState.curPitchSlideAmt)); - chnState.curPitchSlideAmt += slideAmt; + chnState.curPitchSlideAmt += static_cast(slideAmt); // fine slide down - m.command = CMD_PORTAMENTODOWN; - m.param = slideAmt | 0xF0; + m.SetEffectCommand(CMD_PORTAMENTODOWN, slideAmt | 0xF0); } } // TODO: use volume column if effect column is occupied @@ -1879,28 +1856,26 @@ bool CSoundFile::ReadSymMOD(FileReader &file, ModLoadingFlags loadFlags) if(patternSpeed > 1 && chnState.curPitchSlideAmt / 4 >= (patternSpeed - 1)) { uint8 slideAmt = std::min(9, mpt::saturate_round(chnState.curPitchSlideAmt / (patternSpeed - 1)) / 4); - chnState.curPitchSlideAmt -= slideAmt * (patternSpeed - 1) * 4; - m.volcmd = VOLCMD_PORTAUP; - m.vol = slideAmt; + chnState.curPitchSlideAmt -= static_cast(slideAmt * (patternSpeed - 1) * 4); + m.SetVolumeCommand(VOLCMD_PORTAUP, slideAmt); } else if(patternSpeed > 1 && chnState.curPitchSlideAmt / 4 <= -(patternSpeed - 1)) { uint8 slideAmt = std::min(9, mpt::saturate_round(-chnState.curPitchSlideAmt / (patternSpeed - 1)) / 4); - chnState.curPitchSlideAmt += slideAmt * (patternSpeed - 1) * 4; - m.volcmd = VOLCMD_PORTADOWN; - m.vol = slideAmt; + chnState.curPitchSlideAmt += static_cast(slideAmt * (patternSpeed - 1) * 4); + m.SetVolumeCommand(VOLCMD_PORTADOWN, slideAmt); } } } // Vibrato and Tremolo - if(m.command == CMD_NONE && chnState.curVibrato != 0) + if(m.command == CMD_NONE && chnState.curVibrato && chnState.retrigVibrato) { - m.command = CMD_VIBRATO; - m.param = chnState.curVibrato; + m.SetEffectCommand(CMD_VIBRATO, chnState.curVibrato); + chnState.retrigVibrato = false; } - if(m.command == CMD_NONE && chnState.curTremolo != 0) + if(m.command == CMD_NONE && chnState.curTremolo && chnState.retrigTremolo) { - m.command = CMD_TREMOLO; - m.param = chnState.curTremolo; + m.SetEffectCommand(CMD_TREMOLO, chnState.curTremolo); + chnState.retrigTremolo = false; } // Tone Portamento if(m.command != CMD_TONEPORTAMENTO && chnState.tonePortaRemain) @@ -1937,7 +1912,7 @@ bool CSoundFile::ReadSymMOD(FileReader &file, ModLoadingFlags loadFlags) if(useDSP) { SNDMIXPLUGIN &plugin = m_MixPlugins[0]; - plugin.Destroy(); + mpt::reconstruct(plugin); memcpy(&plugin.Info.dwPluginId1, "SymM", 4); memcpy(&plugin.Info.dwPluginId2, "Echo", 4); plugin.Info.routingFlags = SNDMIXPLUGININFO::irAutoSuspend; @@ -1954,21 +1929,20 @@ bool CSoundFile::ReadSymMOD(FileReader &file, ModLoadingFlags loadFlags) #endif // NO_PLUGINS // Channel panning - for(CHANNELINDEX chn = 0; chn < m_nChannels; chn++) + for(CHANNELINDEX chn = 0; chn < GetNumChannels(); chn++) { - InitChannel(chn); ChnSettings[chn].nPan = (chn & 1) ? 256 : 0; ChnSettings[chn].nMixPlugin = useDSP ? 1 : 0; // For MIDI macros controlling the echo DSP } - m_modFormat.formatName = U_("Symphonie"); - m_modFormat.type = U_("symmod"); + m_modFormat.formatName = UL_("Symphonie"); + m_modFormat.type = UL_("symmod"); if(!isSymphoniePro) - m_modFormat.madeWithTracker = U_("Symphonie"); // or Symphonie Jr + m_modFormat.madeWithTracker = UL_("Symphonie"); // or Symphonie Jr else if(instruments.size() <= 128) - m_modFormat.madeWithTracker = U_("Symphonie Pro"); + m_modFormat.madeWithTracker = UL_("Symphonie Pro"); else - m_modFormat.madeWithTracker = U_("Symphonie Pro 256"); + m_modFormat.madeWithTracker = UL_("Symphonie Pro 256"); m_modFormat.charset = mpt::Charset::Amiga_no_C1; return true; diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_tcb.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_tcb.cpp new file mode 100644 index 000000000..13bb1f4b6 --- /dev/null +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_tcb.cpp @@ -0,0 +1,193 @@ +/* + * Load_tcb.cpp + * ------------ + * Purpose: TCB Tracker module loader + * Notes : Based on the manual scan available at https://files.scene.org/view/resources/gotpapers/manuals/tcb_tracker_1.0_manual_1990.pdf + * and a bit of messing about in TCB Tracker 1.0 and 1.1. + * Authors: OpenMPT Devs + * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. + */ + + +#include "stdafx.h" +#include "Loaders.h" +#include "MODTools.h" + +OPENMPT_NAMESPACE_BEGIN + +struct TCBFileHeader +{ + char magic[8]; // "AN COOL." (new) or "AN COOL!" (early TCB Tracker beta versions; not even TCB Tracker 1.0 can read these files) + uint32be numPatterns; + uint8 tempo; + uint8 unused1; + uint8 order[128]; + uint8 numOrders; + uint8 unused2; // Supposed to be part of lastOrder but then it would have to be a little-endian word + + bool IsNewFormat() const + { + return magic[7] == '.'; + } + + bool IsValid() const + { + if(memcmp(magic, "AN COOL.", 8) && memcmp(magic, "AN COOL!", 8)) + return false; + if(tempo > 15 || unused1 || numOrders > 127 || unused2 || numPatterns > 128) + return false; + for(uint8 ord : order) + { + if(ord >= 128) + return false; + } + return true; + } + + uint32 GetHeaderMinimumAdditionalSize() const + { + uint32 size = 16 * 8; // Instrument names + if(IsNewFormat()) + size += 2 + 32; // Amiga flag + special values + size += numPatterns * 512; + return size; + } +}; + +MPT_BINARY_STRUCT(TCBFileHeader, 144) + + +CSoundFile::ProbeResult CSoundFile::ProbeFileHeaderTCB(MemoryFileReader file, const uint64 *pfilesize) +{ + TCBFileHeader fileHeader; + file.Rewind(); + if(!file.ReadStruct(fileHeader)) + return ProbeWantMoreData; + if(!fileHeader.IsValid()) + return ProbeFailure; + return ProbeAdditionalSize(file, pfilesize, fileHeader.GetHeaderMinimumAdditionalSize()); +} + + +bool CSoundFile::ReadTCB(FileReader &file, ModLoadingFlags loadFlags) +{ + file.Rewind(); + TCBFileHeader fileHeader; + if(!file.ReadStruct(fileHeader) || !fileHeader.IsValid()) + return false; + if(!file.CanRead(fileHeader.GetHeaderMinimumAdditionalSize())) + return false; + if(loadFlags == onlyVerifyHeader) + return true; + + InitializeGlobals(MOD_TYPE_MOD, 4); + + SetupMODPanning(true); + Order().SetDefaultSpeed(16 - fileHeader.tempo); + Order().SetDefaultTempoInt(125); + ReadOrderFromArray(Order(), fileHeader.order, std::max(uint8(1), fileHeader.numOrders)); + m_nSamplePreAmp = 64; + m_SongFlags.set(SONG_IMPORTED); + m_playBehaviour.set(kApplyUpperPeriodLimit); + + const bool newFormat = fileHeader.IsNewFormat(); + bool useAmigaFreqs = false; + if(newFormat) + { + uint16 amigaFreqs = file.ReadUint16BE(); + if(amigaFreqs > 1) + return false; + useAmigaFreqs = amigaFreqs != 0; + } + const auto instrNames = file.ReadArray(); + std::array specialValues{}; + if(newFormat) + file.ReadStruct(specialValues); + + m_nMinPeriod = useAmigaFreqs ? 113 * 4 : 92 * 4; + m_nMaxPeriod = useAmigaFreqs ? 856 * 4 : 694 * 4; + + const PATTERNINDEX numPatterns = static_cast(fileHeader.numPatterns); + if(loadFlags & loadPatternData) + Patterns.ResizeArray(numPatterns); + const uint8 noteOffset = useAmigaFreqs ? 0 : 3; + for(PATTERNINDEX pat = 0; pat < numPatterns; pat++) + { + if(!(loadFlags & loadPatternData) || !Patterns.Insert(pat, 64)) + { + file.Skip(512); + continue; + } + for(ModCommand &m : Patterns[pat]) + { + const auto [note, instrEffect] = file.ReadArray(); + if(note >= 0x10 && note <= 0x3B) + { + m.note = static_cast(NOTE_MIDDLEC - 24 + (note >> 4) * 12 + (note & 0x0F) + noteOffset); + m.instr = static_cast((instrEffect >> 4) + 1); + } else if(note) + { + return false; + } + switch(instrEffect & 0x0F) + { + case 0x00: // Nothing + case 0x0E: // Reserved + case 0x0F: // Reserved + break; + case 0x0B: // Interrupt sample + case 0x0C: // Continue sample after interrupt + m.SetVolumeCommand(VOLCMD_PLAYCONTROL, static_cast(((specialValues[0x0B] == 2) ? 5 : 0) + ((instrEffect & 0x0F) - 0x0B))); + break; + case 0x0D: // End Pattern + m.SetEffectCommand(CMD_PATTERNBREAK, 0); + break; + default: // Pitch Bend + if(int value = specialValues[(instrEffect & 0x0F)]; value > 0) + m.SetEffectCommand(CMD_PORTAMENTODOWN, mpt::saturate_cast((value + 16) / 32)); + else if(value < 0) + m.SetEffectCommand(CMD_PORTAMENTOUP, mpt::saturate_cast((- value + 16) / 32)); + } + } + } + + const auto sampleStart = file.GetPosition(); + file.Skip(4); // Size of remaining data + + FileReader sampleHeaders1 = file.ReadChunk(16 * 4); + FileReader sampleHeaders2 = file.ReadChunk(16 * 8); + SampleIO sampleIO(SampleIO::_8bit, SampleIO::mono, SampleIO::bigEndian, SampleIO::unsignedPCM); + m_nSamples = 16; + for(SAMPLEINDEX smp = 1; smp <= 16; smp++) + { + ModSample &mptSmp = Samples[smp]; + mptSmp.Initialize(MOD_TYPE_MOD); + mptSmp.nVolume = std::min(sampleHeaders1.ReadUint8(), uint8(127)) * 2; + sampleHeaders1.Skip(1); // Empty value according to docs + mptSmp.nLoopStart = sampleHeaders1.ReadUint16BE(); + uint32 offset = sampleHeaders2.ReadUint32BE(); + mptSmp.nLength = sampleHeaders2.ReadUint32BE(); + if(mptSmp.nLoopStart && mptSmp.nLoopStart < mptSmp.nLength) + { + mptSmp.nLoopEnd = mptSmp.nLength; + mptSmp.nLoopStart = mptSmp.nLength - mptSmp.nLoopStart; + mptSmp.uFlags.set(CHN_LOOP); + } + if(!useAmigaFreqs) + mptSmp.nFineTune = 5 * 16; + + if((loadFlags & loadSampleData) && mptSmp.nLength > 1 && file.Seek(sampleStart + offset)) + sampleIO.ReadSample(mptSmp, file); + + m_szNames[smp] = mpt::String::ReadBuf(mpt::String::spacePadded, instrNames[smp - 1]); + } + + m_modFormat.formatName = newFormat ? UL_("TCB Tracker") : UL_("TCB Tracker (Beta Format)"); + m_modFormat.type = UL_("mod"); + m_modFormat.madeWithTracker = newFormat ? UL_("TCB Tracker 1.0 - 2.0") : UL_("TCB Tracker Beta"); + m_modFormat.charset = mpt::Charset::AtariST; + + return true; +} + +OPENMPT_NAMESPACE_END diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_uax.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_uax.cpp index 43f6d2060..cfdbc88d2 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_uax.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_uax.cpp @@ -42,9 +42,9 @@ bool CSoundFile::ReadUAX(FileReader &file, ModLoadingFlags loadFlags) const std::vector names = UMX::ReadNameTable(file, fileHeader); const std::vector classes = UMX::ReadImportTable(file, fileHeader, names); - InitializeGlobals(); + InitializeGlobals(MOD_TYPE_MPT, 4); m_modFormat.formatName = MPT_UFORMAT("Unreal Package v{}")(fileHeader.packageVersion); - m_modFormat.type = U_("uax"); + m_modFormat.type = UL_("uax"); m_modFormat.charset = mpt::Charset::Windows1252; // Read export table @@ -70,10 +70,7 @@ bool CSoundFile::ReadUAX(FileReader &file, ModLoadingFlags loadFlags) if(m_nSamples != 0) { - InitializeChannels(); - SetType(MOD_TYPE_MPT); m_ContainerType = ModContainerType::UAX; - m_nChannels = 4; Patterns.Insert(0, 64); Order().assign(1, 0); return true; diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_ult.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_ult.cpp index 34f8f09af..b4dbe0f92 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_ult.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_ult.cpp @@ -151,7 +151,7 @@ static std::pair TranslateULTCommands(const uint8 e, uint8 param = (param & 0x0F) * 0x11; break; case 0x0D: // pattern break - param = 10 * (param >> 4) + (param & 0x0F); + param = static_cast(10 * (param >> 4) + (param & 0x0F)); break; case 0x0E: // special switch(param >> 4) @@ -264,75 +264,6 @@ static ULTEventResult ReadULTEvent(ModCommand &m, FileReader &file, uint8 versio } -// Functor for postfixing ULT patterns (this is easier than just remembering everything WHILE we're reading the pattern events) -struct PostFixUltCommands -{ - PostFixUltCommands(CHANNELINDEX channels) : numChannels{channels} - { - isPortaActive.resize(channels, false); - } - - void operator()(ModCommand &m) - { - // Attempt to fix portamentos. - // UltraTracker will slide until the destination note is reached or 300 is encountered. - - // Stop porta? - if(m.command == CMD_TONEPORTAMENTO && m.param == 0) - { - isPortaActive[curChannel] = false; - m.command = CMD_NONE; - } - if(m.volcmd == VOLCMD_TONEPORTAMENTO && m.vol == 0) - { - isPortaActive[curChannel] = false; - m.volcmd = VOLCMD_NONE; - } - - // Apply porta? - if(m.note == NOTE_NONE && isPortaActive[curChannel]) - { - if(m.command == CMD_NONE && m.volcmd != VOLCMD_TONEPORTAMENTO) - { - m.command = CMD_TONEPORTAMENTO; - m.param = 0; - } else if(m.volcmd == VOLCMD_NONE && m.command != CMD_TONEPORTAMENTO) - { - m.volcmd = VOLCMD_TONEPORTAMENTO; - m.vol = 0; - } - } else // new note -> stop porta (or initialize again) - { - isPortaActive[curChannel] = (m.command == CMD_TONEPORTAMENTO || m.volcmd == VOLCMD_TONEPORTAMENTO); - } - - // attempt to fix F00 (reset to tempo 125, speed 6) - if(writeT125 && m.command == CMD_NONE) - { - m.command = CMD_TEMPO; - m.param = 125; - } - if(m.command == CMD_SPEED && m.param == 0) - { - m.param = 6; - writeT125 = true; - } - if(m.command == CMD_TEMPO) // don't try to fix this anymore if the tempo has already changed. - { - writeT125 = false; - } - curChannel++; - if(curChannel >= numChannels) - curChannel = 0; - } - - std::vector isPortaActive; - const CHANNELINDEX numChannels; - CHANNELINDEX curChannel = 0; - bool writeT125 = false; -}; - - static bool ValidateHeader(const UltFileHeader &fileHeader) { if(fileHeader.version < '1' || fileHeader.version > '4' @@ -352,13 +283,9 @@ CSoundFile::ProbeResult CSoundFile::ProbeFileHeaderULT(MemoryFileReader file, co { UltFileHeader fileHeader; if(!file.ReadStruct(fileHeader)) - { return ProbeWantMoreData; - } if(!ValidateHeader(fileHeader)) - { return ProbeFailure; - } return ProbeAdditionalSize(file, pfilesize, GetHeaderMinimumAdditionalSize(fileHeader)); } @@ -369,23 +296,15 @@ bool CSoundFile::ReadULT(FileReader &file, ModLoadingFlags loadFlags) UltFileHeader fileHeader; if(!file.ReadStruct(fileHeader)) - { return false; - } if(!ValidateHeader(fileHeader)) - { return false; - } if(loadFlags == onlyVerifyHeader) - { return true; - } - if(!file.CanRead(mpt::saturate_cast(GetHeaderMinimumAdditionalSize(fileHeader)))) - { + if(!file.CanRead(mpt::saturate_cast(GetHeaderMinimumAdditionalSize(fileHeader)))) return false; - } - InitializeGlobals(MOD_TYPE_ULT); + InitializeGlobals(MOD_TYPE_ULT, 0); m_songName = mpt::String::ReadBuf(mpt::String::spacePadded, fileHeader.songName); const mpt::uchar *versions[] = {UL_("<1.4"), UL_("1.4"), UL_("1.5"), UL_("1.6")}; @@ -394,7 +313,10 @@ bool CSoundFile::ReadULT(FileReader &file, ModLoadingFlags loadFlags) m_modFormat.madeWithTracker = U_("UltraTracker ") + versions[fileHeader.version - '1']; m_modFormat.charset = mpt::Charset::CP437; - m_SongFlags = SONG_ITCOMPATGXX | SONG_ITOLDEFFECTS; // this will be converted to IT format by MPT. + m_SongFlags = SONG_AUTO_TONEPORTA | SONG_AUTO_TONEPORTA_CONT | SONG_ITCOMPATGXX | SONG_ITOLDEFFECTS; // this will be converted to IT format by MPT. + m_playBehaviour.reset(kITClearPortaTarget); + m_playBehaviour.reset(kITPortaTargetReached); + m_playBehaviour.set(kFT2PortaTargetNoReset); // Read "messageLength" lines, each containing 32 characters. m_songMessage.ReadFixedLineLength(file, fileHeader.messageLength * 32, 32, 0); @@ -426,7 +348,7 @@ bool CSoundFile::ReadULT(FileReader &file, ModLoadingFlags loadFlags) ReadOrderFromFile(Order(), file, 256, 0xFF, 0xFE); if(CHANNELINDEX numChannels = file.ReadUint8() + 1u; numChannels <= MAX_BASECHANNELS) - m_nChannels = numChannels; + ChnSettings.resize(numChannels); else return false; @@ -434,7 +356,6 @@ bool CSoundFile::ReadULT(FileReader &file, ModLoadingFlags loadFlags) for(CHANNELINDEX chn = 0; chn < GetNumChannels(); chn++) { - ChnSettings[chn].Reset(); if(fileHeader.version >= '3') ChnSettings[chn].nPan = ((file.ReadUint8() & 0x0F) << 4) + 8; else @@ -448,7 +369,8 @@ bool CSoundFile::ReadULT(FileReader &file, ModLoadingFlags loadFlags) return false; } - for(CHANNELINDEX chn = 0; chn < m_nChannels; chn++) + bool postFixSpeedCommands = false; + for(CHANNELINDEX chn = 0; chn < GetNumChannels(); chn++) { ModCommand evnote; for(PATTERNINDEX pat = 0; pat < numPats && file.CanRead(5); pat++) @@ -465,6 +387,8 @@ bool CSoundFile::ReadULT(FileReader &file, ModLoadingFlags loadFlags) repeat = 64 - row; if(repeat == 0) break; + if(evnote.command == CMD_SPEED && evnote.param == 0) + postFixSpeedCommands = true; while(repeat--) { *note = evnote; @@ -474,10 +398,24 @@ bool CSoundFile::ReadULT(FileReader &file, ModLoadingFlags loadFlags) } } } - - // Post-fix some effects. - Patterns.ForEachModCommand(PostFixUltCommands(GetNumChannels())); - + if(postFixSpeedCommands) + { + for(CPattern &pat : Patterns) + { + for(ROWINDEX row = 0; row < pat.GetNumRows(); row++) + { + for(auto &m : pat.GetRow(row)) + { + if(m.command == CMD_SPEED && m.param == 0) + { + m.param = 6; + pat.WriteEffect(EffectWriter(CMD_TEMPO, 125).Row(row).RetryNextRow()); + } + } + } + } + } + if(loadFlags & loadSampleData) { for(SAMPLEINDEX smp = 1; smp <= GetNumSamples(); smp++) diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_unic.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_unic.cpp new file mode 100644 index 000000000..cd515473c --- /dev/null +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_unic.cpp @@ -0,0 +1,254 @@ +/* + * Load_unic.cpp + * ------------- + * Purpose: UNIC Tracker v1 loader + * Notes : UNIC Tracker is actually a module packer, not a stand-alone tracker software. + * Support is mostly included to avoid such modules being recognized as regular M.K. MODs. + * UNIC files without file signature are not supported. + * Authors: OpenMPT Devs + * Based on ProWizard by Asle + * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. + */ + + +#include "stdafx.h" +#include "Loaders.h" +#include "MODTools.h" + +OPENMPT_NAMESPACE_BEGIN + + +static bool ValidateUNICSampleHeader(const MODSampleHeader &sampleHeader) +{ + if(CountInvalidChars(mpt::as_span(sampleHeader.name).subspan(0, 20))) + return false; + int16be finetune; + memcpy(&finetune, &sampleHeader.name[20], sizeof(int16be)); + if(finetune < -42 || finetune > 8) // African Dreams.unic has finetune = -42 + return false; + if(sampleHeader.finetune != 0 || sampleHeader.volume > 64) + return false; + if(sampleHeader.length >= 0x8000 || sampleHeader.loopStart >= 0x8000 || sampleHeader.loopLength >= 0x8000) + return false; + if(!sampleHeader.length && (sampleHeader.loopStart > 0 || sampleHeader.loopLength > 1 || finetune != 0)) + return false; + if(sampleHeader.length && sampleHeader.length < sampleHeader.loopStart + sampleHeader.loopLength) + return false; + return true; +} + + +static bool ValidateUNICPatternEntry(const std::array data, SAMPLEINDEX lastSample) +{ + if(data[0] > 0x74) + return false; + if((data[0] & 0x3F) > 0x24) + return false; + const uint8 command = (data[1] & 0x0F), param = data[2]; + if(command == 0x0C && param > 80) // Mastercoma.unic has values > 64 + return false; + if(command == 0x0B && param > 0x7F) + return false; + if(command == 0x0D && param > 0x40) + return false; + if(uint8 instr = ((data[0] >> 2) & 0x30) | ((data[1] >> 4) & 0x0F); instr > lastSample) + return false; + return true; +} + + +struct UNICFileHeader +{ + using PatternData = std::array, 64 * 4>; + + std::array title; + MODSampleHeader sampleHeaders[31]; + MODFileHeader fileHeader; + std::array magic; + PatternData firstPattern; + + struct ValidationResult + { + uint32 totalSampleSize = 0; + SAMPLEINDEX lastSample = 0; + uint8 numPatterns = 0; + }; + + ValidationResult IsValid() const noexcept + { + if(!IsMagic(magic.data(), "M.K.") && !IsMagic(magic.data(), "UNIC") && !IsMagic(magic.data(), "\0\0\0\0")) + return {}; + + if(CountInvalidChars(title)) + return {}; + + uint32 totalSampleSize = 0; + SAMPLEINDEX lastSample = 0; + for(SAMPLEINDEX smp = 1; smp <= 31; smp++) + { + const MODSampleHeader &sampleHeader = sampleHeaders[smp - 1]; + if(!ValidateUNICSampleHeader(sampleHeader)) + return {}; + totalSampleSize += sampleHeader.length * 2; + if(sampleHeader.length) + lastSample = smp; + } + if(totalSampleSize < 256) + return {}; + + if(!fileHeader.numOrders || fileHeader.numOrders >= 128) + return {}; + + uint8 numPatterns = 0; + for(uint8 pat = 0; pat < 128; pat++) + { + if(fileHeader.orderList[pat] >= 128 || (pat > fileHeader.numOrders + 1 && fileHeader.orderList[pat] != 0)) + return {}; + numPatterns = std::max(numPatterns, fileHeader.orderList[pat].get()); + } + numPatterns++; + + for(const auto data : firstPattern) + { + if(!ValidateUNICPatternEntry(data, lastSample)) + return {}; + } + + return {totalSampleSize, lastSample, numPatterns}; + } + +}; + +MPT_BINARY_STRUCT(UNICFileHeader, 1084 + 768) + + +CSoundFile::ProbeResult CSoundFile::ProbeFileHeaderUNIC(MemoryFileReader file, const uint64 *pfilesize) +{ + UNICFileHeader fileHeader; + if(!file.ReadStruct(fileHeader)) + return ProbeWantMoreData; + + const auto headerValidationResult = fileHeader.IsValid(); + if(!headerValidationResult.totalSampleSize) + return ProbeFailure; + + if(pfilesize && *pfilesize < 1084 + headerValidationResult.numPatterns * 64u * 4u * 3u + headerValidationResult.totalSampleSize) + return ProbeFailure; + + return ProbeSuccess; +} + + +bool CSoundFile::ReadUNIC(FileReader &file, ModLoadingFlags loadFlags) +{ + UNICFileHeader fileHeader; + file.Rewind(); + if(!file.ReadStruct(fileHeader)) + return false; + + const auto headerValidationResult = fileHeader.IsValid(); + if(!headerValidationResult.totalSampleSize) + return false; + if(loadFlags == onlyVerifyHeader) + return true; + + InitializeGlobals(MOD_TYPE_MOD, 4); + + // Reading patterns (done first to avoid doing unnecessary work if this is a real ProTracker M.K. file) + file.Seek(1084); + if(!file.CanRead(headerValidationResult.numPatterns * 64u * 4u * 3u)) + return false; + if(loadFlags & loadPatternData) + Patterns.ResizeArray(headerValidationResult.numPatterns); + uint16 numNotes = 0; + ModCommand::INSTR allInstrs = 0; + for(PATTERNINDEX pat = 0; pat < headerValidationResult.numPatterns; pat++) + { + if(!(loadFlags & loadPatternData) || !Patterns.Insert(pat, 64)) + { + UNICFileHeader::PatternData pattern; + if(!file.ReadArray(pattern)) + return false; + + for(const auto data : pattern) + { + if(!ValidateUNICPatternEntry(data, headerValidationResult.lastSample)) + return false; + } + continue; + } + + for(ModCommand &m : Patterns[pat]) + { + const auto data = file.ReadArray(); + if(!ValidateUNICPatternEntry(data, headerValidationResult.lastSample)) + return false; + + if(data[0] & 0x3F) + { + m.note = NOTE_MIDDLEC - 13 + (data[0] & 0x3F); + numNotes++; + } + m.instr = ((data[0] >> 2) & 0x30) | ((data[1] >> 4) & 0x0F); + allInstrs |= m.instr; + ConvertModCommand(m, data[1] & 0x0F, data[2]); + } + } + if(numNotes < 16 || !allInstrs) + return false; + + // Reading samples + if(!file.CanRead(headerValidationResult.totalSampleSize)) + return false; + m_nSamples = 31; + for(SAMPLEINDEX smp = 1; smp <= 31; smp++) + { + const MODSampleHeader &sampleHeader = fileHeader.sampleHeaders[smp - 1]; + ModSample &mptSmp = Samples[smp]; + sampleHeader.ConvertToMPT(mptSmp, true); + int16be finetune; + memcpy(&finetune, &sampleHeader.name[20], sizeof(int16be)); + mptSmp.nFineTune = MOD2XMFineTune(-finetune); + // Metal Jumpover.unic (and various other files) has incorrect loop starts expressed as DWORDs + // But for the flute sample African Dreams.unic this fix doesn't seem to be quite right + if(mptSmp.uFlags[CHN_LOOP] && mptSmp.nLoopStart > 0 + && mptSmp.nLoopStart + mptSmp.nLoopEnd >= mptSmp.nLength - 2 + && mptSmp.nLoopStart + mptSmp.nLoopEnd <= mptSmp.nLength) + { + mptSmp.nLoopEnd += mptSmp.nLoopStart; + mptSmp.nLoopStart += mptSmp.nLoopStart; + } + + m_szNames[smp] = mpt::String::ReadBuf(mpt::String::spacePadded, sampleHeader.name, 20); + + if(!(loadFlags & loadSampleData)) + continue; + SampleIO( + SampleIO::_8bit, + SampleIO::mono, + SampleIO::littleEndian, + SampleIO::signedPCM).ReadSample(Samples[smp], file); + } + + SetupMODPanning(true); + Order().SetDefaultSpeed(6); + Order().SetDefaultTempoInt(125); + ReadOrderFromArray(Order(), fileHeader.fileHeader.orderList, headerValidationResult.numPatterns); + m_nMinPeriod = 113 * 4; + m_nMaxPeriod = 856 * 4; + m_nSamplePreAmp = 64; + m_SongFlags.set(SONG_PT_MODE | SONG_IMPORTED | SONG_FORMAT_NO_VOLCOL); + m_playBehaviour.reset(kMODOneShotLoops); + m_playBehaviour.set(kMODIgnorePanning); + m_playBehaviour.set(kMODSampleSwap); // untested + + m_songName = mpt::String::ReadBuf(mpt::String::spacePadded, fileHeader.title); + + m_modFormat.formatName = UL_("UNIC Tracker"); + m_modFormat.type = UL_("unic"); + m_modFormat.charset = mpt::Charset::Amiga_no_C1; + + return true; +} + +OPENMPT_NAMESPACE_END diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_wav.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_wav.cpp index f84c44d93..3fe9ea8fb 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_wav.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_wav.cpp @@ -79,17 +79,16 @@ bool CSoundFile::ReadWAV(FileReader &file, ModLoadingFlags loadFlags) return true; } - InitializeGlobals(MOD_TYPE_MPT); + InitializeGlobals(MOD_TYPE_MPT, std::max(wavFile.GetNumChannels(), uint16(2))); m_ContainerType = ModContainerType::WAV; - m_nChannels = std::max(wavFile.GetNumChannels(), uint16(2)); Patterns.ResizeArray(2); if(!Patterns.Insert(0, 64) || !Patterns.Insert(1, 64)) { return false; } - m_modFormat.formatName = U_("RIFF WAVE"); - m_modFormat.type = U_("wav"); + m_modFormat.formatName = UL_("RIFF WAVE"); + m_modFormat.type = UL_("wav"); m_modFormat.charset = mpt::Charset::Windows1252; const SmpLength sampleLength = wavFile.GetSampleLength(); @@ -111,13 +110,12 @@ bool CSoundFile::ReadWAV(FileReader &file, ModLoadingFlags loadFlags) m_nSamples = wavFile.GetNumChannels(); m_nInstruments = 0; - m_nDefaultSpeed = ticksPerRow; - m_nDefaultTempo.Set(125); + Order().SetDefaultSpeed(ticksPerRow); + Order().SetDefaultTempoInt(125); m_SongFlags = SONG_LINEARSLIDES; - for(CHANNELINDEX channel = 0; channel < m_nChannels; channel++) + for(CHANNELINDEX channel = 0; channel < GetNumChannels(); channel++) { - ChnSettings[channel].Reset(); ChnSettings[channel].nPan = (channel % 2u) ? 256 : 0; } @@ -177,9 +175,9 @@ bool CSoundFile::ReadWAV(FileReader &file, ModLoadingFlags loadFlags) if(wavFile.GetSampleFormat() == WAVFormatChunk::fmtFloat) { if(wavFile.GetBitsPerSample() <= 32) - CopyWavChannel, SC::DecodeFloat32>>(sample, sampleChunk, channel, wavFile.GetNumChannels()); + CopyWavChannel, SC::DecodeFloat32>>(sample, sampleChunk, channel, wavFile.GetNumChannels()); else - CopyWavChannel, SC::DecodeFloat64>>(sample, sampleChunk, channel, wavFile.GetNumChannels()); + CopyWavChannel, SC::DecodeFloat64>>(sample, sampleChunk, channel, wavFile.GetNumChannels()); } else { if(wavFile.GetBitsPerSample() <= 8) diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_xm.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_xm.cpp index 761dd8e12..7e6a196f3 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_xm.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_xm.cpp @@ -79,11 +79,11 @@ static int VorbisfileFilereaderSeek(void *datasource, ogg_int64_t offset, int wh { case SEEK_SET: { - if(!mpt::in_range(offset)) + if(!mpt::in_range(offset)) { return -1; } - return file.Seek(mpt::saturate_cast(offset)) ? 0 : -1; + return file.Seek(mpt::saturate_cast(offset)) ? 0 : -1; } break; case SEEK_CUR: @@ -94,32 +94,32 @@ static int VorbisfileFilereaderSeek(void *datasource, ogg_int64_t offset, int wh { return -1; } - if(!mpt::in_range(0-offset)) + if(!mpt::in_range(0-offset)) { return -1; } - return file.SkipBack(mpt::saturate_cast(0 - offset)) ? 0 : -1; + return file.SkipBack(mpt::saturate_cast(0 - offset)) ? 0 : -1; } else { - if(!mpt::in_range(offset)) + if(!mpt::in_range(offset)) { return -1; } - return file.Skip(mpt::saturate_cast(offset)) ? 0 : -1; + return file.Skip(mpt::saturate_cast(offset)) ? 0 : -1; } } break; case SEEK_END: { - if(!mpt::in_range(offset)) + if(!mpt::in_range(offset)) { return -1; } - if(!mpt::in_range(file.GetLength() + offset)) + if(!mpt::in_range(file.GetLength() + offset)) { return -1; } - return file.Seek(mpt::saturate_cast(file.GetLength() + offset)) ? 0 : -1; + return file.Seek(mpt::saturate_cast(file.GetLength() + offset)) ? 0 : -1; } break; default: @@ -130,7 +130,7 @@ static int VorbisfileFilereaderSeek(void *datasource, ogg_int64_t offset, int wh static long VorbisfileFilereaderTell(void *datasource) { FileReader &file = *mpt::void_ptr(datasource); - FileReader::off_t result = file.GetPosition(); + FileReader::pos_type result = file.GetPosition(); if(!mpt::in_range(result)) { return -1; @@ -217,7 +217,7 @@ static std::vector AllocateXMSamples(CSoundFile &sndFile, SAMPLEIND candidateSlot = static_cast(std::find(usedSamples.begin() + 1, usedSamples.end(), false) - usedSamples.begin()); } else { - // No unused sampel slots: Give up :( + // No unused sample slots: Give up :( break; } } @@ -243,7 +243,7 @@ static void ReadXMPatterns(FileReader &file, const XMFileHeader &fileHeader, CSo sndFile.Patterns.ResizeArray(fileHeader.patterns); for(PATTERNINDEX pat = 0; pat < fileHeader.patterns; pat++) { - FileReader::off_t curPos = file.GetPosition(); + FileReader::pos_type curPos = file.GetPosition(); const uint32 headerSize = file.ReadUint32LE(); if(headerSize < 8 || !file.CanRead(headerSize - 4)) break; @@ -497,7 +497,7 @@ static bool ReadSampleData(ModSample &sample, SampleIO sampleFlags, FileReader & CopyAudio(mpt::audio_span_interleaved(sample.sample8() + (offset * sample.GetNumChannels()), sample.GetNumChannels(), decodedSamples), mpt::audio_span_planar(output, channels, decodedSamples)); } } - offset += decodedSamples; + offset += static_cast(decodedSamples); } } } else @@ -595,7 +595,7 @@ bool CSoundFile::ReadXM(FileReader &file, ModLoadingFlags loadFlags) { return false; } - if(!file.CanRead(mpt::saturate_cast(GetHeaderMinimumAdditionalSize(fileHeader)))) + if(!file.CanRead(mpt::saturate_cast(GetHeaderMinimumAdditionalSize(fileHeader)))) { return false; } else if(loadFlags == onlyVerifyHeader) @@ -603,8 +603,7 @@ bool CSoundFile::ReadXM(FileReader &file, ModLoadingFlags loadFlags) return true; } - InitializeGlobals(MOD_TYPE_XM); - InitializeChannels(); + InitializeGlobals(MOD_TYPE_XM, fileHeader.channels); m_nMixLevels = MixLevels::Compatible; FlagSet madeWith(verUnknown); @@ -692,12 +691,11 @@ bool CSoundFile::ReadXM(FileReader &file, ModLoadingFlags loadFlags) m_nMaxPeriod = 31999; Order().SetRestartPos(fileHeader.restartPos); - m_nChannels = fileHeader.channels; m_nInstruments = std::min(static_cast(fileHeader.instruments), static_cast(MAX_INSTRUMENTS - 1)); if(fileHeader.speed) - m_nDefaultSpeed = fileHeader.speed; + Order().SetDefaultSpeed(fileHeader.speed); if(fileHeader.tempo) - m_nDefaultTempo = Clamp(TEMPO(fileHeader.tempo, 0), ModSpecs::xmEx.GetTempoMin(), ModSpecs::xmEx.GetTempoMax()); + Order().SetDefaultTempo(Clamp(TEMPO(fileHeader.tempo, 0), ModSpecs::xmEx.GetTempoMin(), ModSpecs::xmEx.GetTempoMax())); m_SongFlags.reset(); m_SongFlags.set(SONG_LINEARSLIDES, (fileHeader.flags & XMFileHeader::linearSlides) != 0); @@ -757,12 +755,12 @@ bool CSoundFile::ReadXM(FileReader &file, ModLoadingFlags loadFlags) { // ModPlug Tracker Alpha m_dwLastSavedWithVersion = MPT_V("1.00.00.A5"); - madeWithTracker = U_("ModPlug Tracker 1.0 alpha"); + madeWithTracker = UL_("ModPlug Tracker 1.0 alpha"); } else if(instrHeader.size == 263) { // ModPlug Tracker Beta (Beta 1 still behaves like Alpha, but Beta 3.3 does it this way) m_dwLastSavedWithVersion = MPT_V("1.00.00.B3"); - madeWithTracker = U_("ModPlug Tracker 1.0 beta"); + madeWithTracker = UL_("ModPlug Tracker 1.0 beta"); } else { // WTF? @@ -999,7 +997,7 @@ bool CSoundFile::ReadXM(FileReader &file, ModLoadingFlags loadFlags) // Read mix plugins information if(file.CanRead(8)) { - FileReader::off_t oldPos = file.GetPosition(); + FileReader::pos_type oldPos = file.GetPosition(); LoadMixPlugins(file); if(file.GetPosition() != oldPos) { @@ -1013,18 +1011,18 @@ bool CSoundFile::ReadXM(FileReader &file, ModLoadingFlags loadFlags) if(madeWith[verModPlugBidiFlag]) { m_dwLastSavedWithVersion = MPT_V("1.11"); - madeWithTracker = U_("ModPlug Tracker 1.0 - 1.11"); + madeWithTracker = UL_("ModPlug Tracker 1.0 - 1.11"); } else if(madeWith[verNewModPlug] && !madeWith[verPlayerPRO]) { - m_dwLastSavedWithVersion = MPT_V("1.16.00.00"); - madeWithTracker = U_("ModPlug Tracker 1.0 - 1.16"); + m_dwLastSavedWithVersion = MPT_V("1.16"); + madeWithTracker = UL_("ModPlug Tracker 1.0 - 1.16"); } else if(madeWith[verNewModPlug] && madeWith[verPlayerPRO]) { m_dwLastSavedWithVersion = MPT_V("1.16"); - madeWithTracker = U_("ModPlug Tracker 1.0 - 1.16 / PlayerPRO"); + madeWithTracker = UL_("ModPlug Tracker 1.0 - 1.16 / PlayerPRO"); } else if(!madeWith[verNewModPlug] && madeWith[verPlayerPRO]) { - madeWithTracker = U_("PlayerPRO"); + madeWithTracker = UL_("PlayerPRO"); } } @@ -1073,13 +1071,13 @@ bool CSoundFile::ReadXM(FileReader &file, ModLoadingFlags loadFlags) { if(madeWith[verDigiTrakker] && sampleReserved == 0 && (lastInstrType ? lastInstrType : -1) == -1) { - madeWithTracker = U_("DigiTrakker"); + madeWithTracker = UL_("DigiTrakker"); } else if(madeWith[verFT2Generic]) { - madeWithTracker = U_("FastTracker 2 or compatible"); + madeWithTracker = UL_("FastTracker 2 or compatible"); } else { - madeWithTracker = U_("Unknown"); + madeWithTracker = UL_("Unknown"); } } @@ -1091,16 +1089,16 @@ bool CSoundFile::ReadXM(FileReader &file, ModLoadingFlags loadFlags) LoadExtendedSongProperties(file, true, &isOpenMPTMade); - if(isOpenMPTMade && m_dwLastSavedWithVersion < MPT_V("1.17.00.00")) + if(isOpenMPTMade && m_dwLastSavedWithVersion < MPT_V("1.17")) { // Up to OpenMPT 1.17.02.45 (r165), it was possible that the "last saved with" field was 0 // when saving a file in OpenMPT for the first time. - m_dwLastSavedWithVersion = MPT_V("1.17.00.00"); + m_dwLastSavedWithVersion = MPT_V("1.17"); } - if(m_dwLastSavedWithVersion >= MPT_V("1.17.00.00")) + if(m_dwLastSavedWithVersion >= MPT_V("1.17")) { - madeWithTracker = U_("OpenMPT ") + m_dwLastSavedWithVersion.ToUString(); + madeWithTracker = UL_("OpenMPT ") + m_dwLastSavedWithVersion.ToUString(); } // We no longer allow any --- or +++ items in the order list now. @@ -1109,7 +1107,7 @@ bool CSoundFile::ReadXM(FileReader &file, ModLoadingFlags loadFlags) if(!Patterns.IsValidPat(0xFE)) Order().RemovePattern(0xFE); if(!Patterns.IsValidPat(0xFF)) - Order().Replace(0xFF, Order.GetInvalidPatIndex()); + Order().Replace(0xFF, PATTERNINDEX_INVALID); } m_modFormat.formatName = MPT_UFORMAT("FastTracker 2 v{}.{}")(fileHeader.version >> 8, mpt::ufmt::hex0<2>(fileHeader.version & 0xFF)); @@ -1118,16 +1116,16 @@ bool CSoundFile::ReadXM(FileReader &file, ModLoadingFlags loadFlags) if(isOXM) { m_modFormat.originalFormatName = std::move(m_modFormat.formatName); - m_modFormat.formatName = U_("OggMod FastTracker 2"); - m_modFormat.type = U_("oxm"); - m_modFormat.originalType = U_("xm"); + m_modFormat.formatName = UL_("OggMod FastTracker 2"); + m_modFormat.type = UL_("oxm"); + m_modFormat.originalType = UL_("xm"); } else { - m_modFormat.type = U_("xm"); + m_modFormat.type = UL_("xm"); } if(anyADPCM) - m_modFormat.madeWithTracker += U_(" (ADPCM packed)"); + m_modFormat.madeWithTracker += UL_(" (ADPCM packed)"); return true; } @@ -1166,8 +1164,8 @@ bool CSoundFile::SaveXM(std::ostream &f, bool compatibilityExport) fileHeader.size = sizeof(XMFileHeader) - 60; // minus everything before this field fileHeader.restartPos = Order().GetRestartPos(); - fileHeader.channels = m_nChannels; - if((m_nChannels % 2u) && m_nChannels < 32) + fileHeader.channels = GetNumChannels(); + if((GetNumChannels() % 2u) && GetNumChannels() < 32) { // Avoid odd channel count for FT2 compatibility fileHeader.channels++; @@ -1189,7 +1187,7 @@ bool CSoundFile::SaveXM(std::ostream &f, bool compatibilityExport) for(ORDERINDEX ord = 0; ord < trimmedLength; ord++) { PATTERNINDEX pat = Order()[ord]; - if(pat == Order.GetIgnoreIndex() || pat == Order.GetInvalidPatIndex() || pat > uint8_max) + if(pat == PATTERNINDEX_SKIP || pat == PATTERNINDEX_INVALID || pat > uint8_max) { changeOrderList = true; } else if(numOrders < orderLimit) @@ -1220,8 +1218,8 @@ bool CSoundFile::SaveXM(std::ostream &f, bool compatibilityExport) fileHeader.flags = fileHeader.flags; // Fasttracker 2 will happily accept any tempo faster than 255 BPM. XMPlay does also support this, great! - fileHeader.tempo = mpt::saturate_cast(m_nDefaultTempo.GetInt()); - fileHeader.speed = static_cast(Clamp(m_nDefaultSpeed, 1u, 31u)); + fileHeader.tempo = mpt::saturate_cast(Order().GetDefaultTempo().GetInt()); + fileHeader.speed = static_cast(Clamp(Order().GetDefaultSpeed(), 1u, 31u)); mpt::IO::Write(f, fileHeader); @@ -1257,17 +1255,20 @@ bool CSoundFile::SaveXM(std::ostream &f, bool compatibilityExport) // Empty patterns are always loaded as 64-row patterns in FT2, regardless of their real size... bool emptyPattern = true; - for(size_t j = m_nChannels * numRows; j > 0; j--, p++) + for(size_t j = GetNumChannels() * numRows; j > 0; j--, p++) { // Don't write more than 32 channels - if(compatibilityExport && m_nChannels - ((j - 1) % m_nChannels) > 32) continue; + if(compatibilityExport && GetNumChannels() - ((j - 1) % GetNumChannels()) > 32) continue; uint8 note = p->note, command = 0, param = 0; ModSaveCommand(*p, command, param, true, compatibilityExport); - if (note >= NOTE_MIN_SPECIAL) note = 97; else - if ((note <= 12) || (note > 96+12)) note = 0; else - note -= 12; + if(note >= NOTE_MIN_SPECIAL) + note = 97; + else if(note < NOTE_MIN + 12 || note >= NOTE_MIN + 12 + 96) + note = 0; + else + note -= 12; uint8 vol = 0; if (p->volcmd != VOLCMD_NONE) { @@ -1339,7 +1340,7 @@ bool CSoundFile::SaveXM(std::ostream &f, bool compatibilityExport) if (b & 16) s[len++] = param; } - if(addChannel && (j % m_nChannels == 1 || m_nChannels == 1)) + if(addChannel && (j % GetNumChannels() == 1 || GetNumChannels() == 1)) { ASSERT_CAN_WRITE(1); s[len++] = 0x80; @@ -1505,7 +1506,7 @@ bool CSoundFile::SaveXM(std::ostream &f, bool compatibilityExport) // Writing Channel Names { CHANNELINDEX numNamedChannels = 0; - for(CHANNELINDEX chn = 0; chn < m_nChannels; chn++) + for(CHANNELINDEX chn = 0; chn < GetNumChannels(); chn++) { if (ChnSettings[chn].szName[0]) numNamedChannels = chn + 1; } @@ -1527,7 +1528,7 @@ bool CSoundFile::SaveXM(std::ostream &f, bool compatibilityExport) SaveMixPlugins(&f); if(GetNumInstruments()) { - SaveExtendedInstrumentProperties(writeInstruments, f); + SaveExtendedInstrumentProperties(0, MOD_TYPE_XM, f); } SaveExtendedSongProperties(f); } diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_xmf.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_xmf.cpp index 7cd3773ed..6c3b9e5a6 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/Load_xmf.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Load_xmf.cpp @@ -160,12 +160,18 @@ bool CSoundFile::ReadXMF(FileReader &file, ModLoadingFlags loadFlags) } if(!numSamples) return false; + + file.Skip(256); + const uint8 lastChannel = file.ReadUint8(); + if(lastChannel > 31) + return false; if(loadFlags == onlyVerifyHeader) return true; - InitializeGlobals(MOD_TYPE_MOD); + InitializeGlobals(MOD_TYPE_MOD, lastChannel + 1); m_SongFlags.set(SONG_IMPORTED); m_SongFlags.reset(SONG_ISAMIGA); + m_SongFlags.set(SONG_AUTO_TONEPORTA | SONG_AUTO_TONEPORTA_CONT, type < 4); m_nSamples = numSamples; m_nSamplePreAmp = (type == 3) ? 192 : 48; // Imperium Galactica files are really quiet, no other XMFs appear to use type 3 @@ -181,18 +187,13 @@ bool CSoundFile::ReadXMF(FileReader &file, ModLoadingFlags loadFlags) file.Seek(1 + 256 * sizeof(XMFSampleHeader)); ReadOrderFromFile(Order(), file, 256, 0xFF); - const uint8 lastChannel = file.ReadUint8(); - if(lastChannel > 31) - return false; - m_nChannels = lastChannel + 1u; + file.Skip(1); // Channel count already read const PATTERNINDEX numPatterns = file.ReadUint8() + 1u; - - if(!file.CanRead(m_nChannels + numPatterns * m_nChannels * 64 * 6)) + if(!file.CanRead(GetNumChannels() + numPatterns * GetNumChannels() * 64 * 6)) return false; - for(CHANNELINDEX chn = 0; chn < m_nChannels; chn++) + for(CHANNELINDEX chn = 0; chn < GetNumChannels(); chn++) { - ChnSettings[chn].Reset(); ChnSettings[chn].nPan = file.ReadUint8() * 0x11; } @@ -201,7 +202,7 @@ bool CSoundFile::ReadXMF(FileReader &file, ModLoadingFlags loadFlags) { if(!(loadFlags & loadPatternData) || !Patterns.Insert(pat, 64)) { - file.Skip(m_nChannels * 64 * 6); + file.Skip(GetNumChannels() * 64 * 6); continue; } ModCommand dummy; diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Loaders.h b/Frameworks/OpenMPT/OpenMPT/soundlib/Loaders.h index 4b9b6504a..1f992f658 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/Loaders.h +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Loaders.h @@ -15,32 +15,10 @@ #include "../common/FileReader.h" #include "Sndfile.h" #include "SampleIO.h" +#include "openmpt/fileformat_base/magic.hpp" OPENMPT_NAMESPACE_BEGIN -// Functions to create 4-byte and 2-byte magic byte identifiers in little-endian format -// Use this together with uint32le/uint16le file members. -constexpr uint32 MagicLE(const char(&id)[5]) -{ - return static_cast((static_cast(id[3]) << 24) | (static_cast(id[2]) << 16) | (static_cast(id[1]) << 8) | static_cast(id[0])); -} -constexpr uint16 MagicLE(const char(&id)[3]) -{ - return static_cast((static_cast(id[1]) << 8) | static_cast(id[0])); -} -// Functions to create 4-byte and 2-byte magic byte identifiers in big-endian format -// Use this together with uint32be/uint16be file members. -// Note: Historically, some magic bytes in MPT-specific fields are reversed (due to the use of multi-char literals). -// Such fields turned up reversed in files, so MagicBE is used to keep them readable in the code. -constexpr uint32 MagicBE(const char(&id)[5]) -{ - return static_cast((static_cast(id[0]) << 24) | (static_cast(id[1]) << 16) | (static_cast(id[2]) << 8) | static_cast(id[3])); -} -constexpr uint16 MagicBE(const char(&id)[3]) -{ - return static_cast((static_cast(id[0]) << 8) | static_cast(id[1])); -} - // Read 'howMany' order items from an array. // 'stopIndex' is treated as '---', 'ignoreIndex' is treated as '+++'. If the format doesn't support such indices, just pass uint16_max. @@ -56,8 +34,8 @@ bool ReadOrderFromArray(ModSequence &order, const T(&orders)[arraySize], size_t for(int i = 0; i < readEntries; i++) { PATTERNINDEX pat = static_cast(orders[i]); - if(pat == stopIndex) pat = order.GetInvalidPatIndex(); - else if(pat == ignoreIndex) pat = order.GetIgnoreIndex(); + if(pat == stopIndex) pat = PATTERNINDEX_INVALID; + else if(pat == ignoreIndex) pat = PATTERNINDEX_SKIP; order.at(i) = pat; } return true; @@ -81,8 +59,8 @@ bool ReadOrderFromFile(ModSequence &order, FileReader &file, size_t howMany, uin { file.ReadStruct(patF); pat = static_cast(patF); - if(pat == stopIndex) pat = order.GetInvalidPatIndex(); - else if(pat == ignoreIndex) pat = order.GetIgnoreIndex(); + if(pat == stopIndex) pat = PATTERNINDEX_INVALID; + else if(pat == ignoreIndex) pat = PATTERNINDEX_SKIP; } return true; } diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/MIDIEvents.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/MIDIEvents.cpp index 6ef8b819a..a485857c3 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/MIDIEvents.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/MIDIEvents.cpp @@ -65,6 +65,13 @@ uint8 System(SystemEvent eventType) } +// Build a MIDI Song Position Event +uint32 SongPosition(uint16 quarterNotes) +{ + return Event(evSystem, sysPositionPointer, static_cast(quarterNotes & 0x7F), static_cast((quarterNotes >> 7) & 0x7F)); +} + + // Get MIDI channel from a MIDI event uint8 GetChannelFromEvent(uint32 midiMsg) { diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/MIDIEvents.h b/Frameworks/OpenMPT/OpenMPT/soundlib/MIDIEvents.h index 1b0dc8eaa..cd548d1de 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/MIDIEvents.h +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/MIDIEvents.h @@ -151,6 +151,8 @@ namespace MIDIEvents uint32 NoteOn(uint8 midiChannel, uint8 note, uint8 velocity); // Build a MIDI System Event uint8 System(SystemEvent eventType); + // Build a MIDI Song Position Event + uint32 SongPosition(uint16 quarterNotes); // Get MIDI channel from a MIDI event uint8 GetChannelFromEvent(uint32 midiMsg); diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/MIDIMacroParser.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/MIDIMacroParser.cpp new file mode 100644 index 000000000..a2c4fdbed --- /dev/null +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/MIDIMacroParser.cpp @@ -0,0 +1,324 @@ +/* + * MIDIMacroParser.cpp + * ------------------- + * Purpose: Class for parsing IT MIDI macro strings and splitting them into individual raw MIDI messages. + * Notes : (currently none) + * Authors: OpenMPT Devs + * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. + */ + + +#include "stdafx.h" +#include "MIDIMacroParser.h" +#include "MIDIEvents.h" +#include "Sndfile.h" +#include "plugins/PlugInterface.h" + +OPENMPT_NAMESPACE_BEGIN + + +bool MIDIMacroParser::NextMessage(mpt::span &message, bool outputRunningStatus) +{ + if(m_runningStatusPos < m_data.size()) + { + m_data[m_runningStatusPos] = m_runningstatusOldData; + m_runningStatusPos = uint32_max; + } + const uint32 outSize = static_cast(m_data.size()); + while(m_sendPos < outSize) + { + uint32 sendLen = 0; + if(m_data[m_sendPos] == 0xF0) + { + // SysEx start + if((outSize - m_sendPos >= 4) && (m_data[m_sendPos + 1] == 0xF0 || m_data[m_sendPos + 1] == 0xF1)) + { + // Internal macro (normal (F0F0) or extended (F0F1)), 4 bytes long + sendLen = 4; + } else + { + // SysEx message, find end of message + sendLen = outSize - m_sendPos; + for(uint32 i = m_sendPos + 1; i < outSize; i++) + { + if(m_data[i] == 0xF7) + { + // Found end of SysEx message + sendLen = i - m_sendPos + 1; + break; + } + } + } + } else if(!(m_data[m_sendPos] & 0x80)) + { + // Missing status byte? Try inserting running status + if(m_runningStatus != 0) + { + if(outputRunningStatus) + { + m_sendPos--; + m_runningstatusOldData = m_data[m_sendPos]; + m_data[m_sendPos] = m_runningStatus; + m_runningStatusPos = m_sendPos; + continue; + } else + { + sendLen = std::min(static_cast(MIDIEvents::GetEventLength(m_runningStatus) - 1), outSize - m_sendPos); + } + } else + { + // No running status to re-use; skip this byte + m_sendPos++; + continue; + } + } else + { + // Other MIDI messages + sendLen = std::min(static_cast(MIDIEvents::GetEventLength(m_data[m_sendPos])), outSize - m_sendPos); + } + + if(sendLen == 0) + break; + + if(m_data[m_sendPos] < 0xF0) + m_runningStatus = m_data[m_sendPos]; + + message = m_data.subspan(m_sendPos, sendLen); + m_sendPos += sendLen; + return true; + } + message = {}; + return false; +} + + +MIDIMacroParser::MIDIMacroParser(const CSoundFile &sndFile, PlayState *playState, CHANNELINDEX nChn, bool isSmooth, const mpt::span macro, mpt::span out, uint8 param, PLUGINDEX plugin) + : m_data{out} +{ + // Need to be able to add potentially missing F7 (End Of SysEx) + MPT_ASSERT(out.size() > macro.size()); + ModChannel *chn = (playState && nChn < playState->Chn.size()) ? &playState->Chn[nChn] : nullptr; + const ModInstrument *pIns = chn ? chn->pModInstrument : nullptr; + MPT_ASSERT(!isSmooth || chn); // If we want to interpolate, we need access to the channel. + + const uint8 lastZxxParam = chn ? chn->lastZxxParam : 0xFF; // always interpolate based on original value in case z appears multiple times in macro string + uint8 updateZxxParam = 0xFF; // avoid updating lastZxxParam immediately if macro contains both internal and external MIDI message + + bool firstNibble = true; + size_t outPos = 0; // output buffer position, which also equals the number of complete bytes + for(size_t pos = 0; pos < macro.size() && outPos < out.size(); pos++) + { + bool isNibble = false; // did we parse a nibble or a byte value? + uint8 data = 0; // data that has just been parsed + + // Parse next macro byte... See Impulse Tracker's MIDI.TXT for detailed information on each possible character. + if(macro[pos] >= '0' && macro[pos] <= '9') + { + isNibble = true; + data = static_cast(macro[pos] - '0'); + } else if(macro[pos] >= 'A' && macro[pos] <= 'F') + { + isNibble = true; + data = static_cast(macro[pos] - 'A' + 0x0A); + } else if(macro[pos] == 'c') + { + // MIDI channel + isNibble = true; + data = 0xFF; +#ifndef NO_PLUGINS + const PLUGINDEX plug = (plugin != 0 || !chn) ? plugin : sndFile.GetBestPlugin(*chn, nChn, PrioritiseChannel, EvenIfMuted); + if(plug > 0 && plug <= MAX_MIXPLUGINS) + { + auto midiPlug = dynamic_cast(sndFile.m_MixPlugins[plug - 1u].pMixPlugin); + if(midiPlug && chn) + data = midiPlug->GetMidiChannel(*chn, nChn); + } +#endif // NO_PLUGINS + if(data == 0xFF) + { + // Fallback if no plugin was found + if(pIns && chn) + data = pIns->GetMIDIChannel(*chn, nChn); + else + data = 0; + } + } else if(macro[pos] == 'n') + { + // Last triggered note + if(chn && ModCommand::IsNote(chn->nLastNote)) + data = chn->nLastNote - NOTE_MIN; + } else if(macro[pos] == 'v') + { + // Velocity + // This is "almost" how IT does it - apparently, IT seems to lag one row behind on global volume or channel volume changes. + if(chn && playState) + { + const int swing = (sndFile.m_playBehaviour[kITSwingBehaviour] || sndFile.m_playBehaviour[kMPTOldSwingBehaviour]) ? chn->nVolSwing : 0; + const int vol = Util::muldiv((chn->nVolume + swing) * playState->m_nGlobalVolume, chn->nGlobalVol * chn->nInsVol, 1 << 20); + data = static_cast(Clamp(vol / 2, 1, 127)); + //data = static_cast(std::min((chn->nVolume * chn->nGlobalVol * playState->m_nGlobalVolume) >> (1 + 6 + 8), 127)); + } + } else if(macro[pos] == 'u') + { + // Calculated volume + // Same note as with velocity applies here, but apparently also for instrument / sample volumes? + if(chn && playState) + { + const int vol = Util::muldiv(chn->nCalcVolume * playState->m_nGlobalVolume, chn->nGlobalVol * chn->nInsVol, 1 << 26); + data = static_cast(Clamp(vol / 2, 1, 127)); + //data = static_cast(std::min((chn->nCalcVolume * chn->nGlobalVol * playState->m_nGlobalVolume) >> (7 + 6 + 8), 127)); + } + } else if(macro[pos] == 'x') + { + // Pan set + if(chn) + data = static_cast(std::min(static_cast(chn->nPan / 2), 127)); + } else if(macro[pos] == 'y') + { + // Calculated pan + if(chn) + data = static_cast(std::min(static_cast(chn->nRealPan / 2), 127)); + } else if(macro[pos] == 'a') + { + // High byte of bank select + if(pIns && pIns->wMidiBank) + { + data = static_cast(((pIns->wMidiBank - 1) >> 7) & 0x7F); + } + } else if(macro[pos] == 'b') + { + // Low byte of bank select + if(pIns && pIns->wMidiBank) + { + data = static_cast((pIns->wMidiBank - 1) & 0x7F); + } + } else if(macro[pos] == 'o') + { + // Offset (ignoring high offset) + if(chn) + data = static_cast((chn->oldOffset >> 8) & 0xFF); + } else if(macro[pos] == 'h') + { + // Host channel number + if(chn) + data = static_cast((nChn >= sndFile.GetNumChannels() ? (chn->nMasterChn - 1) : nChn) & 0x7F); + } else if(macro[pos] == 'm') + { + // Loop direction (on sample channels - MIDI note on MIDI channels) + if(chn) + data = chn->dwFlags[CHN_PINGPONGFLAG] ? 1 : 0; + } else if(macro[pos] == 'p') + { + // Program select + if(pIns && pIns->nMidiProgram) + { + data = static_cast((pIns->nMidiProgram - 1) & 0x7F); + } + } else if(macro[pos] == 'z') + { + // Zxx parameter + data = param; + if(isSmooth && playState && chn && chn->lastZxxParam < 0x80 + && (outPos < 3 || out[outPos - 3] != 0xF0 || out[outPos - 2] < 0xF0)) + { + // Interpolation for external MIDI messages - interpolation for internal messages + // is handled separately to allow for more than 7-bit granularity where it's possible + data = static_cast(CSoundFile::CalculateSmoothParamChange(*playState, lastZxxParam, data)); + chn->lastZxxParam = data; + updateZxxParam = 0x80; + } else if(updateZxxParam == 0xFF) + { + updateZxxParam = data; + } + } else if(macro[pos] == 's') + { + // SysEx Checksum (not an original Impulse Tracker macro variable, but added for convenience) + if(!firstNibble) // From MIDI.TXT: '9n' is exactly the same as '09 n' or '9 n' -- so finish current byte first + { + outPos++; + firstNibble = true; + } + + auto startPos = outPos; + while(startPos > 0 && out[--startPos] != 0xF0) + ; + + if(outPos - startPos < 3 || out[startPos] != 0xF0) + continue; + + // If first byte of model number is 0, read one more + uint8 checksumStart = out[startPos + 3] ? 5 : 6; + if(outPos - startPos < checksumStart) + continue; + + for(auto p = startPos + checksumStart; p != outPos; p++) + { + data += out[p]; + } + data = (~data + 1) & 0x7F; + } else + { + // Unrecognized byte (e.g. space char) + continue; + } + + // Append parsed data + if(isNibble) // parsed a nibble (constant or 'c' variable) + { + if(firstNibble) + { + out[outPos] = data; + } else + { + out[outPos] = (out[outPos] << 4) | data; + outPos++; + } + firstNibble = !firstNibble; + } else // parsed a byte (variable) + { + if(!firstNibble) // From MIDI.TXT: '9n' is exactly the same as '09 n' or '9 n' -- so finish current byte first + { + outPos++; + } + out[outPos++] = data; + firstNibble = true; + } + } + // Finish current byte + if(!firstNibble) + outPos++; + if(chn && updateZxxParam < 0x80) + chn->lastZxxParam = updateZxxParam; + + // Add end of SysEx byte if necessary + for(size_t i = 0; i < outPos; i++) + { + if(out[i] != 0xF0) + continue; + if(outPos - i >= 4 && (out[i + 1] == 0xF0 || out[i + 1] == 0xF1)) + { + // Internal message + i += 3; + } else + { + // Real SysEx + while(i < outPos && out[i] != 0xF7) + i++; + if(i == outPos && outPos < out.size()) + out[outPos++] = 0xF7; + } + + } + + m_data = out.first(outPos); +} + + +MIDIMacroParser::~MIDIMacroParser() +{ + if(m_runningStatusPos < m_data.size()) + m_data[m_runningStatusPos] = m_runningstatusOldData; +} + + +OPENMPT_NAMESPACE_END diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/MIDIMacroParser.h b/Frameworks/OpenMPT/OpenMPT/soundlib/MIDIMacroParser.h new file mode 100644 index 000000000..275784f6c --- /dev/null +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/MIDIMacroParser.h @@ -0,0 +1,42 @@ +/* + * MIDIMacroParser.h + * ----------------- + * Purpose: Class for parsing IT MIDI macro strings and splitting them into individual raw MIDI messages. + * Notes : (currently none) + * Authors: OpenMPT Devs + * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. + */ + + +#pragma once + +#include "openmpt/all/BuildSettings.hpp" + +#include "Snd_defs.h" + +OPENMPT_NAMESPACE_BEGIN + +class CSoundFile; +struct PlayState; + +class MIDIMacroParser +{ +public: + // Parse the given MIDI macro into the out span. out needs to be at least one byte longer than the input string to support the longest possible macro translation. + MIDIMacroParser(const CSoundFile &sndFile, PlayState *playState, CHANNELINDEX nChn, bool isSmooth, const mpt::span macro, mpt::span out, uint8 param = 0, PLUGINDEX plugin = 0); + // Split a raw MIDI dump into multiple messages. Note that in order to support running status, NextMessage() may temporarily alter the provided data. + // When the MIDIMacroParser destructor has run, the data will be back in its original state. + MIDIMacroParser(mpt::span data) : m_data{data} {} + ~MIDIMacroParser(); + + bool NextMessage(mpt::span &message, bool outputRunningStatus = true); + +private: + mpt::span m_data; + uint32 m_sendPos = 0; + uint32 m_runningStatusPos = uint32_max; + uint8 m_runningStatus = 0; + uint8 m_runningstatusOldData = 0; +}; + +OPENMPT_NAMESPACE_END diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/MIDIMacros.h b/Frameworks/OpenMPT/OpenMPT/soundlib/MIDIMacros.h index ec9030b91..150aec82b 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/MIDIMacros.h +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/MIDIMacros.h @@ -12,10 +12,13 @@ #include "openmpt/all/BuildSettings.hpp" +#include "Snd_defs.h" #include "openmpt/base/Endian.hpp" OPENMPT_NAMESPACE_BEGIN +class IMixPlugin; + enum { kGlobalMacros = 9, // Number of global macros @@ -24,14 +27,6 @@ enum kMacroLength = 32, // Max number of chars per macro }; -OPENMPT_NAMESPACE_END - -#ifdef MODPLUG_TRACKER -#include "plugins/PluginStructs.h" -#endif // MODPLUG_TRACKER - -OPENMPT_NAMESPACE_BEGIN - // Parametered macro presets enum ParameteredMacro { diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/MODTools.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/MODTools.cpp new file mode 100644 index 000000000..0ee8c30e7 --- /dev/null +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/MODTools.cpp @@ -0,0 +1,516 @@ +/* + * MODTools.cpp + * ------------ + * Purpose: Definition of MOD file structures (shared between several SoundTracker-/ProTracker-like formats) and helper functions + * Notes : (currently none) + * Authors: OpenMPT Devs + * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. + */ + + +#include "stdafx.h" +#include "Loaders.h" +#include "MODTools.h" +#include "Tables.h" + +OPENMPT_NAMESPACE_BEGIN + +void CSoundFile::ConvertModCommand(ModCommand &m, const uint8 command, const uint8 param) +{ + static constexpr EffectCommand effTrans[] = + { + // MOD effects + CMD_ARPEGGIO, CMD_PORTAMENTOUP, CMD_PORTAMENTODOWN, CMD_TONEPORTAMENTO, // 0123 + CMD_VIBRATO, CMD_TONEPORTAVOL, CMD_VIBRATOVOL, CMD_TREMOLO, // 4567 + CMD_PANNING8, CMD_OFFSET, CMD_VOLUMESLIDE, CMD_POSITIONJUMP, // 89AB + CMD_VOLUME, CMD_PATTERNBREAK, CMD_MODCMDEX, CMD_TEMPO, // CDEF + // XM extended effects + CMD_GLOBALVOLUME, CMD_GLOBALVOLSLIDE, CMD_NONE, CMD_NONE, // GHIJ + CMD_KEYOFF, CMD_SETENVPOSITION, CMD_NONE, CMD_NONE, // KLMN + CMD_NONE, CMD_PANNINGSLIDE, CMD_NONE, CMD_RETRIG, // OPQR + CMD_NONE, CMD_TREMOR, CMD_NONE, CMD_NONE, // STUV + CMD_DUMMY, CMD_XFINEPORTAUPDOWN, CMD_PANBRELLO, CMD_MIDI, // WXYZ + CMD_SMOOTHMIDI, CMD_SMOOTHMIDI, CMD_XPARAM, // \\# (BeRoTracker uses command 37 instead of 36 for smooth MIDI macros; in old OpenMPT versions this was reserved for the unimplemented "velocity" command) + }; + + m.command = CMD_NONE; + m.param = param; + if(command == 0x00 && param == 0x00) + { + m.command = CMD_NONE; + } else if(command == 0x0F && param < 0x20) + { + // For a very long time (until OpenMPT 1.25.02.02), this code also imported 0x20 as CMD_SPEED for MOD files, + // but this seems to contradict pretty much the majority of other MOD player out there. + // 0x20 is Speed: Impulse Tracker, Scream Tracker, old ModPlug + // 0x20 is Tempo: ProTracker, XMPlay, Imago Orpheus, Cubic Player, ChibiTracker, BeRoTracker, DigiTrakker, DigiTrekker, Disorder Tracker 2, DMP, Extreme's Tracker, ... + m.command = CMD_SPEED; + } else if(command < std::size(effTrans)) + { + m.command = effTrans[command]; + if(m.command == CMD_PATTERNBREAK) + m.param = static_cast(((m.param >> 4) * 10) + (m.param & 0x0F)); + } +} + +#ifndef MODPLUG_NO_FILESAVE + +void CSoundFile::ModSaveCommand(const ModCommand &source, uint8 &command, uint8 ¶m, const bool toXM, const bool compatibilityExport) const +{ + command = 0; + param = source.param; + switch(source.command) + { + case CMD_NONE: command = param = 0; break; + case CMD_ARPEGGIO: command = 0; break; + case CMD_PORTAMENTOUP: + command = 0x01; + if(UseCombinedPortamentoCommands() && param >= 0xE0) + { + command = 0x0E; + if(param < 0xF0) + param = ((param & 0x0F) >> 2) | 0x10; + else + param = (param & 0x0F) | 0x10; + } + break; + case CMD_PORTAMENTODOWN: + command = 0x02; + if(UseCombinedPortamentoCommands() && param >= 0xE0) + { + command = 0x0E; + if(param < 0xF0) + param = ((param & 0x0F) >> 2) | 0x20; + else + param = (param & 0x0F) | 0x20; + } + break; + case CMD_TONEPORTAMENTO: command = 0x03; break; + case CMD_VIBRATO: command = 0x04; break; + case CMD_TONEPORTAVOL: command = 0x05; break; + case CMD_VIBRATOVOL: command = 0x06; break; + case CMD_TREMOLO: command = 0x07; break; + case CMD_PANNING8: + command = 0x08; + if(GetType() & MOD_TYPE_S3M) + { + if(param <= 0x80) + { + param = mpt::saturate_cast(param * 2); + } else if(param == 0xA4) // Surround + { + if(compatibilityExport || !toXM) + { + command = param = 0; + } else + { + command = 'X' - 55; + param = 91; + } + } + } + break; + case CMD_OFFSETPERCENTAGE: + case CMD_OFFSET: command = 0x09; break; + case CMD_VOLUMESLIDE: command = 0x0A; break; + case CMD_POSITIONJUMP: command = 0x0B; break; + case CMD_VOLUME: command = 0x0C; break; + case CMD_PATTERNBREAK: + command = 0x0D; + param = ((param / 10) << 4) | (param % 10); + break; + case CMD_MODCMDEX: command = 0x0E; break; + case CMD_SPEED: + command = 0x0F; + param = std::min(param, uint8(0x1F)); + break; + case CMD_TEMPO: + command = 0x0F; + param = std::max(param, uint8(0x20)); + break; + case CMD_GLOBALVOLUME: command = 'G' - 55; break; + case CMD_GLOBALVOLSLIDE: command = 'H' - 55; break; + case CMD_KEYOFF: command = 'K' - 55; break; + case CMD_SETENVPOSITION: command = 'L' - 55; break; + case CMD_PANNINGSLIDE: command = 'P' - 55; break; + case CMD_RETRIG: command = 'R' - 55; break; + case CMD_TREMOR: command = 'T' - 55; break; + case CMD_DUMMY: command = 'W' - 55; break; + case CMD_XFINEPORTAUPDOWN: + command = 'X' - 55; + if(compatibilityExport && param >= 0x30) // X1x and X2x are legit, everything above are MPT extensions, which don't belong here. + param = 0; // Don't set command to 0 to indicate that there *was* some X command here... + break; + case CMD_PANBRELLO: + if(compatibilityExport) + command = param = 0; + else + command = 'Y' - 55; + break; + case CMD_MIDI: + if(compatibilityExport) + command = param = 0; + else + command = 'Z' - 55; + break; + case CMD_SMOOTHMIDI: + if(compatibilityExport) + command = param = 0; + else + command = '\\' - 56; + break; + case CMD_XPARAM: + if(compatibilityExport) + command = param = 0; + else + command = '#' + 3; + break; + case CMD_S3MCMDEX: + { + ModCommand mConv; + mConv.command = CMD_S3MCMDEX; + mConv.param = param; + mConv.ExtendedS3MtoMODEffect(); + ModSaveCommand(mConv, command, param, toXM, compatibilityExport); + } + return; + case CMD_VOLUME8: + command = 0x0C; + param = static_cast((param + 3u) / 4u); + break; + default: + command = param = 0; + } + + // Don't even think about saving XM effects in MODs... + if(command > 0x0F && !toXM) + { + command = param = 0; + } +} + +#endif // MODPLUG_NO_FILESAVE + + +// Convert an MOD sample header to OpenMPT's internal sample header. +void MODSampleHeader::ConvertToMPT(ModSample &mptSmp, bool is4Chn) const +{ + mptSmp.Initialize(MOD_TYPE_MOD); + mptSmp.nLength = length * 2; + mptSmp.nFineTune = MOD2XMFineTune(finetune & 0x0F); + mptSmp.nVolume = 4u * std::min(volume.get(), uint8(64)); + + SmpLength lStart = loopStart * 2; + SmpLength lLength = loopLength * 2; + // See if loop start is incorrect as words, but correct as bytes (like in Soundtracker modules) + if(lLength > 2 && (lStart + lLength > mptSmp.nLength) + && (lStart / 2 + lLength <= mptSmp.nLength)) + { + lStart /= 2; + } + + if(mptSmp.nLength == 2) + { + mptSmp.nLength = 0; + } + + if(mptSmp.nLength) + { + mptSmp.nLoopStart = lStart; + mptSmp.nLoopEnd = lStart + lLength; + + if(mptSmp.nLoopStart >= mptSmp.nLength) + { + mptSmp.nLoopStart = mptSmp.nLength - 1; + } + if(mptSmp.nLoopStart > mptSmp.nLoopEnd || mptSmp.nLoopEnd < 4 || mptSmp.nLoopEnd - mptSmp.nLoopStart < 4) + { + mptSmp.nLoopStart = 0; + mptSmp.nLoopEnd = 0; + } + + // Fix for most likely broken sample loops. This fixes super_sufm_-_new_life.mod (M.K.) which has a long sample which is looped from 0 to 4. + // This module also has notes outside of the Amiga frequency range, so we cannot say that it should be played using ProTracker one-shot loops. + // On the other hand, "Crew Generation" by Necros (6CHN) has a sample with a similar loop, which is supposed to be played. + // To be able to correctly play both modules, we will draw a somewhat arbitrary line here and trust the loop points in MODs with more than + // 4 channels, even if they are tiny and at the very beginning of the sample. + if(mptSmp.nLoopEnd <= 8 && mptSmp.nLoopStart == 0 && mptSmp.nLength > mptSmp.nLoopEnd && is4Chn) + { + mptSmp.nLoopEnd = 0; + } + if(mptSmp.nLoopEnd > mptSmp.nLoopStart) + { + mptSmp.uFlags.set(CHN_LOOP); + } + } +} + + +// Convert OpenMPT's internal sample header to a MOD sample header. +SmpLength MODSampleHeader::ConvertToMOD(const ModSample &mptSmp) +{ + SmpLength writeLength = mptSmp.HasSampleData() ? mptSmp.nLength : 0; + // If the sample size is odd, we have to add a padding byte, as all sample sizes in MODs are even. + if((writeLength % 2u) != 0) + { + writeLength++; + } + LimitMax(writeLength, SmpLength(0x1FFFE)); + + length = static_cast(writeLength / 2u); + + if(mptSmp.RelativeTone < 0) + { + finetune = 0x08; + } else if(mptSmp.RelativeTone > 0) + { + finetune = 0x07; + } else + { + finetune = XM2MODFineTune(mptSmp.nFineTune); + } + volume = static_cast(mptSmp.nVolume / 4u); + + loopStart = 0; + loopLength = 1; + if(mptSmp.uFlags[CHN_LOOP] && (mptSmp.nLoopStart + 2u) < writeLength) + { + const SmpLength loopEnd = Clamp(mptSmp.nLoopEnd, (mptSmp.nLoopStart & ~1) + 2u, writeLength) & ~1; + loopStart = static_cast(mptSmp.nLoopStart / 2u); + loopLength = static_cast((loopEnd - (mptSmp.nLoopStart & ~1)) / 2u); + } + + return writeLength; +} + + +// Compute a "rating" of this sample header by counting invalid header data to ultimately reject garbage files. +uint32 MODSampleHeader::GetInvalidByteScore() const +{ + return ((volume > 64) ? 1 : 0) + + ((finetune > 15) ? 1 : 0) + + ((loopStart > length * 2) ? 1 : 0); +} + + +bool MODSampleHeader::HasDiskName() const +{ + return (!memcmp(name, "st-", 3) || !memcmp(name, "ST-", 3)) && name[5] == ':'; +} + + +uint32 ReadMODSample(const MODSampleHeader &sampleHeader, ModSample &sample, mpt::charbuf &sampleName, bool is4Chn) +{ + sampleHeader.ConvertToMPT(sample, is4Chn); + sampleName = mpt::String::ReadBuf(mpt::String::spacePadded, sampleHeader.name); + // Get rid of weird characters in sample names. + for(auto &c : sampleName.buf) + { + if(c > 0 && c < ' ') + { + c = ' '; + } + } + // Check for invalid values + return sampleHeader.GetInvalidByteScore(); +} + + +// Check if a name string is valid (i.e. doesn't contain binary garbage data) +uint32 CountInvalidChars(const mpt::span name) noexcept +{ + uint32 invalidChars = 0; + for(int8 c : name) // char can be signed or unsigned + { + // Check for any Extended ASCII and control characters + if(c != 0 && c < ' ') + invalidChars++; + } + return invalidChars; +} + + +// Check if a name is a valid null-terminated ASCII string with no garbage after the null terminator, or if it's empty +NameClassification ClassifyName(const mpt::span name) noexcept +{ + bool foundNull = false, foundNormal = false; + for(int8 c : name) + { + if(c != 0 && c < ' ') + return NameClassification::Invalid; + if(c == 0) + foundNull = true; + else if(foundNull) + return NameClassification::Invalid; + else + foundNormal = true; + } + if(!foundNull) + return NameClassification::Invalid; + return foundNormal ? NameClassification::ValidASCII : NameClassification::Empty; +} + + +// Count malformed bytes in MOD pattern data +uint32 CountMalformedMODPatternData(const MODPatternData &patternData, const bool extendedFormat) +{ + const uint8 mask = extendedFormat ? 0xE0 : 0xF0; + uint32 malformedBytes = 0; + for(const auto &row : patternData) + { + for(const auto &data : row) + { + if(data[0] & mask) + malformedBytes++; + if(!extendedFormat) + { + const uint16 period = (((static_cast(data[0]) & 0x0F) << 8) | data[1]); + if(period && period != 0xFFF) + { + // Allow periods to deviate by +/-1 as found in some files + const auto CompareFunc = [](uint16 l, uint16 r) { return l > (r + 1); }; + const auto PeriodTable = mpt::as_span(ProTrackerPeriodTable).subspan(24, 36); + if(!std::binary_search(PeriodTable.begin(), PeriodTable.end(), period, CompareFunc)) + malformedBytes += 2; + } + } + } + } + return malformedBytes; +} + + +// Parse the order list to determine how many patterns are used in the file. +PATTERNINDEX GetNumPatterns(FileReader &file, CSoundFile &sndFile, ORDERINDEX numOrders, SmpLength totalSampleLen, SmpLength wowSampleLen, bool validateHiddenPatterns) +{ + ModSequence &Order = sndFile.Order(); + PATTERNINDEX numPatterns = 0; // Total number of patterns in file (determined by going through the whole order list) with pattern number < 128 + PATTERNINDEX officialPatterns = 0; // Number of patterns only found in the "official" part of the order list (i.e. order positions < claimed order length) + PATTERNINDEX numPatternsIllegal = 0; // Total number of patterns in file, also counting in "invalid" pattern indexes >= 128 + + for(ORDERINDEX ord = 0; ord < 128; ord++) + { + PATTERNINDEX pat = Order[ord]; + if(pat < 128 && numPatterns <= pat) + { + numPatterns = pat + 1; + if(ord < numOrders) + { + officialPatterns = numPatterns; + } + } + if(pat >= numPatternsIllegal) + { + numPatternsIllegal = pat + 1; + } + } + + // Remove the garbage patterns past the official order end now that we don't need them anymore. + Order.resize(numOrders); + + const size_t patternStartOffset = file.GetPosition(); + const size_t sizeWithoutPatterns = totalSampleLen + patternStartOffset; + const size_t sizeWithOfficialPatterns = sizeWithoutPatterns + officialPatterns * sndFile.GetNumChannels() * 256; + // There are some WOW files with an extra byte at the end, and also a MOD file (idntmind.mod, MD5 a3af5c3e1af269e32dfb6677c41c8453, SHA1 4884717c298575f9884b2211c762bb1725f73743) + // where only the "official" patterns should be counted but the file also has an extra byte at the end. + // Since MOD files can technically not have an odd file size, we just always round the actual file size down. + const auto fileSize = mpt::align_down(file.GetLength(), FileReader::pos_type{2}); + + if(wowSampleLen && (wowSampleLen + patternStartOffset) + numPatterns * 8 * 256 == fileSize) + { + // Check if this is a Mod's Grave WOW file... WOW files use the M.K. magic but are actually 8CHN files. + // We do a simple pattern validation as well for regular MOD files that have non-module data attached at the end + // (e.g. ponylips.mod, MD5 c039af363b1d99a492dafc5b5f9dd949, SHA1 1bee1941c47bc6f913735ce0cf1880b248b8fc93) + file.Seek(patternStartOffset + numPatterns * 4 * 256); + if(ValidateMODPatternData(file, 16, true)) + sndFile.ChnSettings.resize(8); + file.Seek(patternStartOffset); + } else if(numPatterns != officialPatterns && (validateHiddenPatterns || sizeWithOfficialPatterns == fileSize)) + { + // 15-sample SoundTracker specifics: + // Fix SoundTracker modules where "hidden" patterns should be ignored. + // razor-1911.mod (MD5 b75f0f471b0ae400185585ca05bf7fe8, SHA1 4de31af234229faec00f1e85e1e8f78f405d454b) + // and captain_fizz.mod (MD5 55bd89fe5a8e345df65438dbfc2df94e, SHA1 9e0e8b7dc67939885435ea8d3ff4be7704207a43) + // seem to have the "correct" file size when only taking the "official" patterns into account, + // but they only play correctly when also loading the inofficial patterns. + // On the other hand, the SoundTracker module + // wolf1.mod (MD5 a4983d7a432d324ce8261b019257f4ed, SHA1 aa6b399d02546bcb6baf9ec56a8081730dea3f44), + // wolf3.mod (MD5 af60840815aa9eef43820a7a04417fa6, SHA1 24d6c2e38894f78f6c5c6a4b693a016af8fa037b) + // and jean_baudlot_-_bad_dudes_vs_dragonninja-dragonf.mod (MD5 fa48e0f805b36bdc1833f6b82d22d936, SHA1 39f2f8319f4847fe928b9d88eee19d79310b9f91) + // only play correctly if we ignore the hidden patterns. + // Hence, we have a peek at the first hidden pattern and check if it contains a lot of illegal data. + // If that is the case, we assume it's part of the sample data and only consider the "official" patterns. + + // 31-sample NoiseTracker / ProTracker specifics: + // Interestingly, (broken) variants of the ProTracker modules + // "killing butterfly" (MD5 bd676358b1dbb40d40f25435e845cf6b, SHA1 9df4ae21214ff753802756b616a0cafaeced8021), + // "quartex" by Reflex (MD5 35526bef0fb21cb96394838d94c14bab, SHA1 116756c68c7b6598dcfbad75a043477fcc54c96c), + // seem to have the "correct" file size when only taking the "official" patterns into account, but they only play + // correctly when also loading the inofficial patterns. + // On the other hand, "Shofixti Ditty.mod" from Star Control 2 (MD5 62b7b0819123400e4d5a7813eef7fc7d, SHA1 8330cd595c61f51c37a3b6f2a8559cf3fcaaa6e8) + // doesn't sound correct when taking the second "inofficial" pattern into account. + file.Seek(patternStartOffset + officialPatterns * sndFile.GetNumChannels() * 256); + if(!ValidateMODPatternData(file, 64, true)) + numPatterns = officialPatterns; + file.Seek(patternStartOffset); + } + + if(numPatternsIllegal > numPatterns && sizeWithoutPatterns + numPatternsIllegal * sndFile.GetNumChannels() * 256 == fileSize) + { + // Even those illegal pattern indexes (> 128) appear to be valid... What a weird file! + // e.g. NIETNU.MOD, where the end of the order list is filled with FF rather than 00, and the file actually contains 256 patterns. + numPatterns = numPatternsIllegal; + } else if(numPatternsIllegal >= 0xFF) + { + // Patterns FE and FF are used with S3M semantics (e.g. some MODs written with old OpenMPT versions) + Order.Replace(0xFE, PATTERNINDEX_SKIP); + Order.Replace(0xFF, PATTERNINDEX_INVALID); + } + + return numPatterns; +} + + +std::pair CSoundFile::ReadMODPatternEntry(FileReader &file, ModCommand &m) +{ + return ReadMODPatternEntry(file.ReadArray(), m); +} + + +std::pair CSoundFile::ReadMODPatternEntry(const std::array data, ModCommand &m) +{ + // Read Period + uint16 period = (((static_cast(data[0]) & 0x0F) << 8) | data[1]); + size_t note = NOTE_NONE; + if(period > 0 && period != 0xFFF) + { + note = std::size(ProTrackerPeriodTable) + 23 + NOTE_MIN; + for(size_t i = 0; i < std::size(ProTrackerPeriodTable); i++) + { + if(period >= ProTrackerPeriodTable[i]) + { + if(period != ProTrackerPeriodTable[i] && i != 0) + { + uint16 p1 = ProTrackerPeriodTable[i - 1]; + uint16 p2 = ProTrackerPeriodTable[i]; + if(p1 - period < (period - p2)) + { + note = i + 23 + NOTE_MIN; + break; + } + } + note = i + 24 + NOTE_MIN; + break; + } + } + } + m.note = static_cast(note); + // Read Instrument + m.instr = (data[2] >> 4) | (data[0] & 0x10); + // Read Effect + m.command = CMD_NONE; + uint8 command = data[2] & 0x0F, param = data[3]; + return {command, param}; +} + +OPENMPT_NAMESPACE_END diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/MODTools.h b/Frameworks/OpenMPT/OpenMPT/soundlib/MODTools.h new file mode 100644 index 000000000..6651a70a8 --- /dev/null +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/MODTools.h @@ -0,0 +1,144 @@ +/* + * MODTools.h + * ---------- + * Purpose: Definition of MOD file structures (shared between several SoundTracker-/ProTracker-like formats) and helper functions + * Notes : (currently none) + * Authors: OpenMPT Devs + * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. + */ + + +#pragma once + +#include "openmpt/all/BuildSettings.hpp" +#include "openmpt/base/Endian.hpp" +#include "SampleIO.h" +#include "Snd_defs.h" + +OPENMPT_NAMESPACE_BEGIN + +class CSoundFile; +struct ModSample; + +// File header following the sample headers +struct MODFileHeader +{ + uint8be numOrders; + uint8be restartPos; // Tempo (early SoundTracker) or restart position (only PC trackers?) + uint8be orderList[128]; +}; + +MPT_BINARY_STRUCT(MODFileHeader, 130) + + +// Sample Header +struct MODSampleHeader +{ + char name[22]; + uint16be length; + uint8be finetune; + uint8be volume; + uint16be loopStart; + uint16be loopLength; + + // Convert an MOD sample header to OpenMPT's internal sample header. + void ConvertToMPT(ModSample &mptSmp, bool is4Chn) const; + + // Convert OpenMPT's internal sample header to a MOD sample header. + SmpLength ConvertToMOD(const ModSample &mptSmp); + + // Compute a "rating" of this sample header by counting invalid header data to ultimately reject garbage files. + uint32 GetInvalidByteScore() const; + + bool HasDiskName() const; + + // Suggested threshold for rejecting invalid files based on cumulated score returned by GetInvalidByteScore + static constexpr uint32 INVALID_BYTE_THRESHOLD = 40; + + // This threshold is used for files where the file magic only gives a + // fragile result which alone would lead to too many false positives. + // In particular, the files from Inconexia demo by Iguana + // (https://www.pouet.net/prod.php?which=830) which have 3 \0 bytes in + // the file magic tend to cause misdetection of random files. + static constexpr uint32 INVALID_BYTE_FRAGILE_THRESHOLD = 1; + + // Retrieve the internal sample format flags for this sample. + static SampleIO GetSampleFormat() + { + return SampleIO( + SampleIO::_8bit, + SampleIO::mono, + SampleIO::bigEndian, + SampleIO::signedPCM); + } +}; + +MPT_BINARY_STRUCT(MODSampleHeader, 30) + +// Pattern data of a 4-channel MOD file +using MODPatternData = std::array, 4>, 64>; + +// Check if header magic equals a given string. +inline bool IsMagic(const char *magic1, const char (&magic2)[5]) noexcept +{ + return std::memcmp(magic1, magic2, 4) == 0; +} + + +// For .DTM files from Apocalypse Abyss, where the first 2108 bytes are swapped +template +inline T ReadAndSwap(TFileReader &file, const bool swapBytes) +{ + T value{}; + if(file.Read(value) && swapBytes) + { + static_assert(sizeof(value) % 2u == 0); + auto byteView = mpt::as_raw_memory(value); + for(size_t i = 0; i < sizeof(T); i += 2) + { + std::swap(byteView[i], byteView[i + 1]); + } + } + return value; +} + + +// Convert MOD sample header and validate +uint32 ReadMODSample(const MODSampleHeader &sampleHeader, ModSample &sample, mpt::charbuf &sampleName, bool is4Chn); + + +// Check if a name string is valid (i.e. doesn't contain binary garbage data) +uint32 CountInvalidChars(const mpt::span name) noexcept; + + +enum class NameClassification +{ + Empty, + ValidASCII, + Invalid, +}; + +// Check if a name is a valid null-terminated ASCII string with no garbage after the null terminator, or if it's empty +NameClassification ClassifyName(const mpt::span name) noexcept; + + +// Count malformed bytes in MOD pattern data +uint32 CountMalformedMODPatternData(const MODPatternData &patternData, const bool extendedFormat); + + +// Check if number of malformed bytes in MOD pattern data exceeds some threshold +template +inline bool ValidateMODPatternData(TFileReader &file, const uint32 threshold, const bool extendedFormat) +{ + MODPatternData patternData; + if(!file.Read(patternData)) + return false; + return CountMalformedMODPatternData(patternData, extendedFormat) <= threshold; +} + + +// Parse the order list to determine how many patterns are used in the file. +PATTERNINDEX GetNumPatterns(FileReader &file, CSoundFile &sndFile, ORDERINDEX numOrders, SmpLength totalSampleLen, SmpLength wowSampleLen, bool validateHiddenPatterns); + + +OPENMPT_NAMESPACE_END diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Message.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/Message.cpp index a0f8e8456..7b0535682 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/Message.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Message.cpp @@ -109,7 +109,7 @@ bool SongMessage::Read(const std::byte *data, size_t length, LineEnding lineEndi bool SongMessage::Read(FileReader &file, const size_t length, LineEnding lineEnding) { - FileReader::off_t readLength = std::min(static_cast(length), file.BytesLeft()); + FileReader::pos_type readLength = std::min(static_cast(length), file.BytesLeft()); FileReader::PinnedView fileView = file.ReadPinnedView(readLength); bool success = Read(fileView.data(), fileView.size(), lineEnding); return success; @@ -159,7 +159,7 @@ bool SongMessage::ReadFixedLineLength(const std::byte *data, const size_t length bool SongMessage::ReadFixedLineLength(FileReader &file, const size_t length, const size_t lineLength, const size_t lineEndingLength) { - FileReader::off_t readLength = std::min(static_cast(length), file.BytesLeft()); + FileReader::pos_type readLength = std::min(static_cast(length), file.BytesLeft()); FileReader::PinnedView fileView = file.ReadPinnedView(readLength); bool success = ReadFixedLineLength(fileView.data(), fileView.size(), lineLength, lineEndingLength); return success; diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/MixFuncTable.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/MixFuncTable.cpp index 585f57c79..788bc2c87 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/MixFuncTable.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/MixFuncTable.cpp @@ -12,11 +12,10 @@ #include "stdafx.h" - -#include "Mixer.h" -#include "Snd_defs.h" -#include "ModChannel.h" #include "MixFuncTable.h" +#include "Mixer.h" +#include "ModChannel.h" +#include "Snd_defs.h" #ifdef MPT_INTMIXER #include "IntMixer.h" diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/ModChannel.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/ModChannel.cpp index 888de321a..8e6819cc1 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/ModChannel.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/ModChannel.cpp @@ -1,7 +1,8 @@ /* * ModChannel.cpp * -------------- - * Purpose: Module Channel header class and helpers + * Purpose: The ModChannel struct represents the state of one mixer channel. + * ModChannelSettings represents the default settings of one pattern channel. * Notes : (currently none) * Authors: OpenMPT Devs * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. @@ -9,14 +10,16 @@ #include "stdafx.h" -#include "Sndfile.h" #include "ModChannel.h" +#include "Sndfile.h" #include "tuning.h" OPENMPT_NAMESPACE_BEGIN void ModChannel::Reset(ResetFlags resetMask, const CSoundFile &sndFile, CHANNELINDEX sourceChannel, ChannelFlags muteFlag) { + // For "the ultimate beeper.mod" + const ModSample *defaultSample = (sndFile.GetType() == MOD_TYPE_MOD && sndFile.GetSample(0).HasSampleData()) ? &sndFile.GetSample(0) : nullptr; if(resetMask & resetSetPosBasic) { // IT compatibility: Initial "last note memory" of channel is C-0 (so a lonely instrument number without note will play that note). @@ -24,7 +27,8 @@ void ModChannel::Reset(ResetFlags resetMask, const CSoundFile &sndFile, CHANNELI nNote = nNewNote = (sndFile.m_playBehaviour[kITInitialNoteMemory] ? NOTE_MIN : NOTE_NONE); nArpeggioLastNote = lastMidiNoteWithoutArp = NOTE_NONE; nNewIns = nOldIns = 0; - pModSample = nullptr; + swapSampleIndex = 0; + pModSample = defaultSample; pModInstrument = nullptr; nPortamentoDest = 0; nCommand = CMD_NONE; @@ -33,6 +37,9 @@ void ModChannel::Reset(ResetFlags resetMask, const CSoundFile &sndFile, CHANNELI nFadeOutVol = 0; dwFlags.set(CHN_KEYOFF | CHN_NOTEFADE); dwOldFlags.reset(); + autoSlide.Reset(); + nInsVol = 64; + nnaGeneration = 0; //IT compatibility 15. Retrigger if(sndFile.m_playBehaviour[kITRetrigger]) { @@ -50,6 +57,7 @@ void ModChannel::Reset(ResetFlags resetMask, const CSoundFile &sndFile, CHANNELI isPaused = false; portaTargetReached = false; rowCommand.Clear(); + mpt::reconstruct(synthState); } if(resetMask & resetSetPosAdvanced) @@ -61,7 +69,7 @@ void ModChannel::Reset(ResetFlags resetMask, const CSoundFile &sndFile, CHANNELI nLoopStart = 0; nLoopEnd = 0; nROfs = nLOfs = 0; - pModSample = nullptr; + pModSample = defaultSample; pModInstrument = nullptr; nCutOff = 0x7F; nResonance = 0; @@ -73,6 +81,9 @@ void ModChannel::Reset(ResetFlags resetMask, const CSoundFile &sndFile, CHANNELI nVibratoPos = nTremoloPos = nPanbrelloPos = 0; nOldHiOffset = 0; nLeftVU = nRightVU = 0; + nOldExtraFinePortaUpDown = nOldFinePortaUpDown = nOldPortaDown = nOldPortaUp = 0; + portamentoSlide = 0; + nMasterChn = 0; // Custom tuning related m_ReCalculateFreqOnFirstTick = false; @@ -83,7 +94,7 @@ void ModChannel::Reset(ResetFlags resetMask, const CSoundFile &sndFile, CHANNELI if(resetMask & resetChannelSettings) { - if(sourceChannel < MAX_BASECHANNELS) + if(sourceChannel < sndFile.ChnSettings.size()) { dwFlags = sndFile.ChnSettings[sourceChannel].dwFlags; nPan = sndFile.ChnSettings[sourceChannel].nPan; @@ -121,13 +132,19 @@ void ModChannel::UpdateInstrumentVolume(const ModSample *smp, const ModInstrumen { nInsVol = 64; if(smp != nullptr) - nInsVol = smp->nGlobalVol; + nInsVol = static_cast(smp->nGlobalVol); if(ins != nullptr) - nInsVol = (nInsVol * ins->nGlobalVol) / 64; + nInsVol = static_cast((nInsVol * ins->nGlobalVol) / 64); } -ModCommand::NOTE ModChannel::GetPluginNote(bool ignoreArpeggio) const +uint32 ModChannel::GetVSTVolume() const noexcept +{ + return pModInstrument ? pModInstrument->nGlobalVol * 4 : nVolume; +} + + +ModCommand::NOTE ModChannel::GetPluginNote(bool ignoreArpeggio) const noexcept { if(nArpeggioLastNote != NOTE_NONE && !ignoreArpeggio) { @@ -143,6 +160,24 @@ ModCommand::NOTE ModChannel::GetPluginNote(bool ignoreArpeggio) const } +bool ModChannel::HasMIDIOutput() const noexcept +{ + return pModInstrument != nullptr && pModInstrument->HasValidMIDIChannel(); +} + + +bool ModChannel::HasCustomTuning() const noexcept +{ + return pModInstrument != nullptr && pModInstrument->pTuning != nullptr; +} + + +bool ModChannel::InSustainLoop() const noexcept +{ + return (dwFlags & (CHN_LOOP | CHN_KEYOFF)) == CHN_LOOP && pModSample->uFlags[CHN_SUSTAINLOOP]; +} + + void ModChannel::SetInstrumentPan(int32 pan, const CSoundFile &sndFile) { // IT compatibility: Instrument and sample panning does not override channel panning @@ -221,4 +256,20 @@ void ModChannel::InstrumentControl(uint8 param, const CSoundFile &sndFile) } +// Volume command :xx +void ModChannel::PlayControl(uint8 param) +{ + switch(param) + { + case 0: isPaused = true; break; + case 1: isPaused = false; break; + case 2: dwFlags.set(CHN_PINGPONGFLAG, false); break; + case 3: dwFlags.set(CHN_PINGPONGFLAG, true); break; + case 4: dwFlags.flip(CHN_PINGPONGFLAG); break; + case 5: oldOffset = position.GetUInt(); break; + case 6: position.Set(oldOffset); break; + } +} + + OPENMPT_NAMESPACE_END diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/ModChannel.h b/Frameworks/OpenMPT/OpenMPT/soundlib/ModChannel.h index b31b5c10e..94a4658cd 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/ModChannel.h +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/ModChannel.h @@ -1,7 +1,8 @@ /* * ModChannel.h * ------------ - * Purpose: Module Channel header class and helpers + * Purpose: The ModChannel struct represents the state of one mixer channel. + * ModChannelSettings represents the default settings of one pattern channel. * Notes : (currently none) * Authors: OpenMPT Devs * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. @@ -12,15 +13,18 @@ #include "openmpt/all/BuildSettings.hpp" -#include "ModSample.h" -#include "ModInstrument.h" +#include "InstrumentSynth.h" #include "modcommand.h" #include "Paula.h" #include "tuningbase.h" +#include + OPENMPT_NAMESPACE_BEGIN class CSoundFile; +struct ModSample; +struct ModInstrument; // Mix Channel Struct struct ModChannel @@ -39,6 +43,24 @@ struct ModChannel } }; + struct AutoSlideStatus + { + bool AnyActive() const noexcept { return m_set.any(); } + bool IsActive(AutoSlideCommand cmd) const noexcept { return m_set[static_cast(cmd)]; } + void SetActive(AutoSlideCommand cmd, bool active = true) noexcept { m_set[static_cast(cmd)] = active; } + void Reset() noexcept { m_set.reset(); } + + bool AnyPitchSlideActive() const noexcept + { + return IsActive(AutoSlideCommand::TonePortamento) + || IsActive(AutoSlideCommand::PortamentoUp) || IsActive(AutoSlideCommand::PortamentoDown) + || IsActive(AutoSlideCommand::FinePortamentoUp) || IsActive(AutoSlideCommand::FinePortamentoDown) + || IsActive(AutoSlideCommand::PortamentoFC); + } + private: + std::bitset(AutoSlideCommand::NumCommands)> m_set; + }; + // Information used in the mixer (should be kept tight for better caching) SamplePosition position; // Current play position (fixed point) SamplePosition increment; // Sample speed relative to mixing frequency (fixed point) @@ -62,6 +84,7 @@ struct ModChannel const ModSample *pModSample; // Currently assigned sample slot (may already be stopped) Paula::State paulaState; + InstrumentSynth::States synthState; // Information not used in the mixer const ModInstrument *pModInstrument; // Currently assigned instrument slot @@ -76,21 +99,29 @@ struct ModChannel int32 cachedPeriod, glissandoPeriod; int32 nCalcVolume; // Calculated channel volume, 14-Bit (without global volume, pre-amp etc applied) - for MIDI macros EnvInfo VolEnv, PanEnv, PitchEnv; // Envelope playback info - int32 nGlobalVol; // Channel volume (CV in ITTECH.TXT) 0...64 - int32 nInsVol; // Sample / Instrument volume (SV * IV in ITTECH.TXT) 0...64 int32 nAutoVibDepth; uint32 nEFxOffset; // Offset memory for Invert Loop (EFx, .MOD only) ROWINDEX nPatternLoop; + AutoSlideStatus autoSlide; uint16 portamentoSlide; - int16 nTranspose; int16 nFineTune; int16 microTuning; // Micro-tuning / MIDI pitch wheel command int16 nVolSwing, nPanSwing; int16 nCutSwing, nResSwing; - uint16 nRestorePanOnNewNote; // If > 0, nPan should be set to nRestorePanOnNewNote - 1 on new note. Used to recover from pan swing and IT sample / instrument panning. High bit set = surround + uint16 volSlideDownRemain, volSlideDownTotal; + union + { + uint16 nRestorePanOnNewNote; // If > 0, nPan should be set to nRestorePanOnNewNote - 1 on new note. Used to recover from pan swing and IT sample / instrument panning. High bit set = surround + uint16 nnaChannelAge; // If channel is moved to background (NNA), this counts up how old it is + }; + uint16 nnaGeneration; // For PlaybackTest implementation CHANNELINDEX nMasterChn; + SAMPLEINDEX swapSampleIndex; // Sample to swap to when current sample (loop) has finished playing ModCommand rowCommand; // 8-bit members + uint8 nGlobalVol; // Channel volume (CV in ITTECH.TXT) 0...64 + uint8 nInsVol; // Sample / Instrument volume (SV * IV in ITTECH.TXT) 0...64 + int8 nTranspose; ResamplingMode resamplingMode; uint8 nRestoreResonanceOnNewNote; // See nRestorePanOnNewNote uint8 nRestoreCutoffOnNewNote; // ditto @@ -116,6 +147,7 @@ struct ModChannel uint8 nPatternLoopCount; uint8 nLeftVU, nRightVU; uint8 nActiveMacro; + uint8 volSlideDownStart; FilterMode nFilterMode; uint8 nEFxSpeed, nEFxDelay; // memory for Invert Loop (EFx, .MOD only) uint8 noteSlideParam, noteSlideCounter; // IMF / PTM Note Slide @@ -125,6 +157,7 @@ struct ModChannel bool isPreviewNote : 1; // Notes preview in editor bool isPaused : 1; // Don't mix or increment channel position, but keep the note alive bool portaTargetReached : 1; // Tone portamento is finished + bool fcPortaTick : 1; // Future Composer portamento state //-->Variables used to make user-definable tuning modes work with pattern effects. //If true, freq should be recalculated in ReadNote() on first tick. @@ -142,8 +175,6 @@ struct ModChannel uint16 m_RowPlugParam; PLUGINDEX m_RowPlug; - void ClearRowCmd() { rowCommand = ModCommand(); } - // Get a reference to a specific envelope of this channel const EnvInfo &GetEnvelope(EnvelopeType envType) const { @@ -185,17 +216,17 @@ struct ModChannel bool IsSamplePlaying() const noexcept { return !increment.IsZero(); } - uint32 GetVSTVolume() const noexcept { return (pModInstrument) ? pModInstrument->nGlobalVol * 4 : nVolume; } + uint32 GetVSTVolume() const noexcept; - ModCommand::NOTE GetPluginNote(bool ignoreArpeggio = false) const; + ModCommand::NOTE GetPluginNote(bool ignoreArpeggio = false) const noexcept; // Check if the channel has a valid MIDI output. A return value of true implies that pModInstrument != nullptr. - bool HasMIDIOutput() const noexcept { return pModInstrument != nullptr && pModInstrument->HasValidMIDIChannel(); } + bool HasMIDIOutput() const noexcept; // Check if the channel uses custom tuning. A return value of true implies that pModInstrument != nullptr. - bool HasCustomTuning() const noexcept { return pModInstrument != nullptr && pModInstrument->pTuning != nullptr; } + bool HasCustomTuning() const noexcept; // Check if currently processed loop is a sustain loop. pModSample is not checked for validity! - bool InSustainLoop() const noexcept { return (dwFlags & (CHN_LOOP | CHN_KEYOFF)) == CHN_LOOP && pModSample->uFlags[CHN_SUSTAINLOOP]; } + bool InSustainLoop() const noexcept; void UpdateInstrumentVolume(const ModSample *smp, const ModInstrument *ins); @@ -206,6 +237,8 @@ struct ModChannel // IT command S73-S7E void InstrumentControl(uint8 param, const CSoundFile &sndFile); + // Volume command :xx + void PlayControl(uint8 param); int32 GetMIDIPitchBend() const noexcept { return (static_cast(microTuning) + 0x8000) / 4; } void SetMIDIPitchBend(const uint8 high, const uint8 low) noexcept @@ -224,15 +257,10 @@ struct ModChannelSettings #endif // MODPLUG_TRACKER FlagSet dwFlags; // Channel flags uint16 nPan = 128; // Initial pan (0...256) - uint16 nVolume = 64; // Initial channel volume (0...64) + uint8 nVolume = 64; // Initial channel volume (0...64) PLUGINDEX nMixPlugin = 0; // Assigned plugin mpt::charbuf szName; // Channel name - - void Reset() - { - *this = {}; - } }; OPENMPT_NAMESPACE_END diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/ModInstrument.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/ModInstrument.cpp index d6d405848..738e3ee50 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/ModInstrument.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/ModInstrument.cpp @@ -9,8 +9,8 @@ #include "stdafx.h" -#include "Sndfile.h" #include "ModInstrument.h" +#include "Sndfile.h" OPENMPT_NAMESPACE_BEGIN @@ -152,24 +152,11 @@ void InstrumentEnvelope::Sanitize(uint8 maxValue) } -ModInstrument::ModInstrument(SAMPLEINDEX sample) -{ - SetCutoff(0, false); - SetResonance(0, false); - - pitchToTempoLock.Set(0); - - pTuning = CSoundFile::GetDefaultTuning(); - - AssignSample(sample); - ResetNoteMap(); -} - - // Translate instrument properties between two given formats. void ModInstrument::Convert(MODTYPE fromType, MODTYPE toType) { MPT_UNREFERENCED_PARAMETER(fromType); + synth.Clear(); if(toType & MOD_TYPE_XM) { @@ -307,6 +294,7 @@ void ModInstrument::Sanitize(MODTYPE modType) VolEnv.Sanitize(); PanEnv.Sanitize(); PitchEnv.Sanitize(range); + synth.Sanitize(); for(size_t i = 0; i < std::size(NoteMap); i++) { diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/ModInstrument.h b/Frameworks/OpenMPT/OpenMPT/soundlib/ModInstrument.h index f28e84121..4a136b66d 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/ModInstrument.h +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/ModInstrument.h @@ -12,11 +12,13 @@ #include "openmpt/all/BuildSettings.hpp" +#include "InstrumentSynth.h" #include "modcommand.h" #include "tuningbase.h" #include "Snd_defs.h" #include "openmpt/base/FlagSet.hpp" #include "../common/misc_util.h" + #include #include @@ -33,8 +35,8 @@ struct EnvelopeNode tick_t tick = 0; // Envelope node position (x axis) value_t value = 0; // Envelope node value (y axis) - EnvelopeNode() { } - EnvelopeNode(tick_t tick, value_t value) : tick(tick), value(value) { } + constexpr EnvelopeNode() = default; + constexpr EnvelopeNode(tick_t tick, value_t value) : tick{tick}, value{value} { } bool operator== (const EnvelopeNode &other) const { return tick == other.tick && value == other.value; } }; @@ -107,6 +109,7 @@ struct ModInstrument TEMPO pitchToTempoLock; // BPM at which the samples assigned to this instrument loop correctly (0 = unset) CTuning *pTuning = nullptr; // sample tuning assigned to this instrument + InstrumentSynth synth; // Synth scripts for this instrument InstrumentEnvelope VolEnv; // Volume envelope data InstrumentEnvelope PanEnv; // Panning envelope data @@ -125,16 +128,21 @@ struct ModInstrument // WHEN adding new members here, ALSO update InstrumentExtensions.cpp // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! - ModInstrument(SAMPLEINDEX sample = 0); + MPT_CONSTEXPR20_CONTAINER_FUN explicit ModInstrument(SAMPLEINDEX sample = 0) + : NoteMap{mpt::generate_array([](std::size_t i){ return static_cast(NOTE_MIN + i); })} + , Keyboard{mpt::init_array(sample)} + { + return; + } // Assign all notes to a given sample. - void AssignSample(SAMPLEINDEX sample) + MPT_CONSTEXPR20_ALGORITHM_FUN void AssignSample(SAMPLEINDEX sample) { Keyboard.fill(sample); } // Reset note mapping (i.e. every note is mapped to itself) - void ResetNoteMap() + MPT_CONSTEXPR20_ALGORITHM_FUN void ResetNoteMap() { std::iota(NoteMap.begin(), NoteMap.end(), static_cast(NOTE_MIN)); } @@ -146,23 +154,23 @@ struct ModInstrument // Transpose entire note mapping by given number of semitones void Transpose(int8 amount); - bool IsCutoffEnabled() const { return (nIFC & 0x80) != 0; } - bool IsResonanceEnabled() const { return (nIFR & 0x80) != 0; } - uint8 GetCutoff() const { return (nIFC & 0x7F); } - uint8 GetResonance() const { return (nIFR & 0x7F); } - void SetCutoff(uint8 cutoff, bool enable) { nIFC = std::min(cutoff, uint8(0x7F)) | (enable ? 0x80 : 0x00); } - void SetResonance(uint8 resonance, bool enable) { nIFR = std::min(resonance, uint8(0x7F)) | (enable ? 0x80 : 0x00); } + MPT_CONSTEXPRINLINE bool IsCutoffEnabled() const { return (nIFC & 0x80) != 0; } + MPT_CONSTEXPRINLINE bool IsResonanceEnabled() const { return (nIFR & 0x80) != 0; } + MPT_CONSTEXPRINLINE uint8 GetCutoff() const { return (nIFC & 0x7F); } + MPT_CONSTEXPRINLINE uint8 GetResonance() const { return (nIFR & 0x7F); } + MPT_CONSTEXPRINLINE void SetCutoff(uint8 cutoff, bool enable) { nIFC = std::min(cutoff, uint8(0x7F)) | (enable ? 0x80 : 0x00); } + MPT_CONSTEXPRINLINE void SetResonance(uint8 resonance, bool enable) { nIFR = std::min(resonance, uint8(0x7F)) | (enable ? 0x80 : 0x00); } - bool HasValidMIDIChannel() const { return (nMidiChannel >= 1 && nMidiChannel <= 17); } + MPT_CONSTEXPRINLINE bool HasValidMIDIChannel() const { return (nMidiChannel >= 1 && nMidiChannel <= 17); } uint8 GetMIDIChannel(const ModChannel &channel, CHANNELINDEX chn) const; - void SetTuning(CTuning *pT) + MPT_CONSTEXPRINLINE void SetTuning(CTuning *pT) { pTuning = pT; } // Get a reference to a specific envelope of this instrument - const InstrumentEnvelope &GetEnvelope(EnvelopeType envType) const + MPT_CONSTEXPRINLINE const InstrumentEnvelope &GetEnvelope(EnvelopeType envType) const { switch(envType) { diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/ModSample.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/ModSample.cpp index e59757ac7..19cc5fde6 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/ModSample.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/ModSample.cpp @@ -9,9 +9,10 @@ #include "stdafx.h" -#include "Sndfile.h" #include "ModSample.h" +#include "AudioCriticalSection.h" #include "modsmp_ctrl.h" +#include "Sndfile.h" #include "mpt/base/numbers.hpp" #include @@ -199,7 +200,27 @@ bool ModSample::CopyWaveform(const ModSample &smpFrom) return true; } return false; +} + +// Replace waveform with given data, keeping the currently chosen format of the sample slot. +void ModSample::ReplaceWaveform(void *newWaveform, const SmpLength newLength, CSoundFile &sndFile) +{ + auto oldWaveform = samplev(); + FlagSet setFlags, resetFlags; + + setFlags.set(CHN_16BIT, uFlags[CHN_16BIT]); + resetFlags.set(CHN_16BIT, !uFlags[CHN_16BIT]); + + setFlags.set(CHN_STEREO, uFlags[CHN_STEREO]); + resetFlags.set(CHN_STEREO, !uFlags[CHN_STEREO]); + + CriticalSection cs; + + ctrlChn::ReplaceSample(sndFile, *this, newWaveform, newLength, setFlags, resetFlags); + pData.pSample = newWaveform; + nLength = newLength; + FreeSample(oldWaveform); } @@ -451,7 +472,7 @@ void ModSample::PrecomputeLoops(CSoundFile &sndFile, bool updateChannels) // Update channels with possibly changed loop values if(updateChannels) { - ctrlSmp::UpdateLoopPoints(*this, sndFile); + UpdateLoopPointsInActiveChannels(sndFile); } if(GetElementarySampleSize() == 2) @@ -461,6 +482,60 @@ void ModSample::PrecomputeLoops(CSoundFile &sndFile, bool updateChannels) } +// Propagate loop point changes to player +bool ModSample::UpdateLoopPointsInActiveChannels(CSoundFile &sndFile) +{ + if(!HasSampleData()) + return false; + + CriticalSection cs; + + // Update channels with new loop values + for(auto &chn : sndFile.m_PlayState.Chn) + { + if(chn.pModSample != this || chn.nLength == 0) + continue; + + bool looped = false, bidi = false; + if(nSustainStart < nSustainEnd && nSustainEnd <= nLength && uFlags[CHN_SUSTAINLOOP] && !chn.dwFlags[CHN_KEYOFF]) + { + // Sustain loop is active + chn.nLoopStart = nSustainStart; + chn.nLoopEnd = nSustainEnd; + chn.nLength = nSustainEnd; + looped = true; + bidi = uFlags[CHN_PINGPONGSUSTAIN]; + } else if(nLoopStart < nLoopEnd && nLoopEnd <= nLength && uFlags[CHN_LOOP]) + { + // Normal loop is active + chn.nLoopStart = nLoopStart; + chn.nLoopEnd = nLoopEnd; + chn.nLength = nLoopEnd; + looped = true; + bidi = uFlags[CHN_PINGPONGLOOP]; + } + chn.dwFlags.set(CHN_LOOP, looped); + chn.dwFlags.set(CHN_PINGPONGLOOP, looped && bidi); + + if(chn.position.GetUInt() > chn.nLength) + { + chn.position.Set(chn.nLoopStart); + chn.dwFlags.reset(CHN_PINGPONGFLAG); + } + if(!bidi) + { + chn.dwFlags.reset(CHN_PINGPONGFLAG); + } + if(!looped) + { + chn.nLength = nLength; + } + } + + return true; +} + + // Remove loop points if they're invalid. void ModSample::SanitizeLoops() { @@ -592,4 +667,5 @@ void ModSample::SetAdlib(bool enable, OPLPatch patch) } } + OPENMPT_NAMESPACE_END diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/ModSample.h b/Frameworks/OpenMPT/OpenMPT/soundlib/ModSample.h index d13563e43..d456944f6 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/ModSample.h +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/ModSample.h @@ -11,6 +11,7 @@ #pragma once #include "openmpt/all/BuildSettings.hpp" +#include "Snd_defs.h" OPENMPT_NAMESPACE_BEGIN @@ -129,6 +130,9 @@ struct ModSample // Copies sample data from another sample slot and ensures that the 16-bit/stereo flags are set accordingly. bool CopyWaveform(const ModSample &smpFrom); + // Replace waveform with given data, keeping the currently chosen format of the sample slot. + void ReplaceWaveform(void *newWaveform, const SmpLength newLength, CSoundFile &sndFile); + // Allocate sample based on a ModSample's properties. // Returns number of bytes allocated, 0 on failure. size_t AllocateSample(); @@ -151,6 +155,9 @@ struct ModSample // Update loop wrap-around buffer void PrecomputeLoops(CSoundFile &sndFile, bool updateChannels = true); + // Propagate loop point changes to player + bool UpdateLoopPointsInActiveChannels(CSoundFile &sndFile); + constexpr bool HasLoop() const noexcept { return uFlags[CHN_LOOP] && nLoopEnd > nLoopStart; } constexpr bool HasSustainLoop() const noexcept { return uFlags[CHN_SUSTAINLOOP] && nSustainEnd > nSustainStart; } constexpr bool HasPingPongLoop() const noexcept { return uFlags.test_all(CHN_LOOP | CHN_PINGPONGLOOP) && nLoopEnd > nLoopStart; } diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/ModSequence.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/ModSequence.cpp index ce5e3e7fd..8bf26e837 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/ModSequence.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/ModSequence.cpp @@ -10,8 +10,8 @@ #include "stdafx.h" #include "ModSequence.h" -#include "Sndfile.h" #include "mod_specifications.h" +#include "Sndfile.h" #include "../common/version.h" #include "../common/serialization_utils.h" #include "mpt/io/io.hpp" @@ -45,6 +45,17 @@ bool ModSequence::operator== (const ModSequence &other) const noexcept } +void ModSequence::SetDefaultTempo(TEMPO tempo) noexcept +{ + if(!tempo.GetInt()) + tempo.Set(125); + else + LimitMax(tempo, TEMPO{uint16_max, 0}); + + m_defaultTempo = tempo; +} + + bool ModSequence::NeedsExtraDatafield() const noexcept { return (m_sndFile.GetType() == MOD_TYPE_MPT && m_sndFile.Patterns.GetNumPatterns() > 0xFD); @@ -60,12 +71,12 @@ void ModSequence::AdjustToNewModType(const MODTYPE oldtype) // If not supported, remove "+++" separator order items. if(!specs.hasIgnoreIndex) { - RemovePattern(GetIgnoreIndex()); + RemovePattern(PATTERNINDEX_SKIP); } // If not supported, remove "---" items between patterns. if(!specs.hasStopIndex) { - RemovePattern(GetInvalidPatIndex()); + RemovePattern(PATTERNINDEX_INVALID); } } @@ -90,14 +101,27 @@ ORDERINDEX ModSequence::GetLengthTailTrimmed() const noexcept { if(empty()) return 0; - auto last = std::find_if(rbegin(), rend(), [] (PATTERNINDEX pat) { return pat != GetInvalidPatIndex(); }); + auto last = std::find_if(rbegin(), rend(), [] (PATTERNINDEX pat) { return pat != PATTERNINDEX_INVALID; }); return static_cast(std::distance(begin(), last.base())); } ORDERINDEX ModSequence::GetLengthFirstEmpty() const noexcept { - return static_cast(std::distance(begin(), std::find(begin(), end(), GetInvalidPatIndex()))); + return static_cast(std::distance(begin(), std::find(begin(), end(), PATTERNINDEX_INVALID))); +} + + +ORDERINDEX ModSequence::GetRemainingCapacity(ORDERINDEX startingFrom, bool enforceFormatLimits) const noexcept +{ + const ORDERINDEX ordersMax = enforceFormatLimits ? m_sndFile.GetModSpecifications().ordersMax : MAX_ORDERS; + ORDERINDEX length = GetLengthTailTrimmed(); + if(startingFrom != ORDERINDEX_INVALID && startingFrom > length) + length = startingFrom; + if(length >= ordersMax) + return 0; + else + return ordersMax - length; } @@ -107,7 +131,7 @@ ORDERINDEX ModSequence::GetNextOrderIgnoringSkips(const ORDERINDEX start) const return 0; auto length = GetLength(); ORDERINDEX next = std::min(ORDERINDEX(length - 1), ORDERINDEX(start + 1)); - while(next + 1 < length && (*this)[next] == GetIgnoreIndex()) + while(next + 1 < length && (*this)[next] == PATTERNINDEX_SKIP) next++; return next; } @@ -119,7 +143,7 @@ ORDERINDEX ModSequence::GetPreviousOrderIgnoringSkips(const ORDERINDEX start) co if(start == 0 || last == 0) return 0; ORDERINDEX prev = std::min(ORDERINDEX(start - 1), last); - while(prev > 0 && (*this)[prev] == GetIgnoreIndex()) + while(prev > 0 && (*this)[prev] == PATTERNINDEX_SKIP) prev--; return prev; } @@ -195,13 +219,13 @@ void ModSequence::assign(ORDERINDEX newSize, PATTERNINDEX pat) } -ORDERINDEX ModSequence::insert(ORDERINDEX pos, ORDERINDEX count, PATTERNINDEX fill) +ORDERINDEX ModSequence::insert(ORDERINDEX pos, ORDERINDEX count, PATTERNINDEX fill, bool enforceFormatLimits) { - const auto ordersMax = m_sndFile.GetModSpecifications().ordersMax; + const ORDERINDEX ordersMax = enforceFormatLimits ? m_sndFile.GetModSpecifications().ordersMax : MAX_ORDERS; + // Limit number of orders to be inserted so that we don't exceed the format limit or drop items at the end of the order list. + LimitMax(count, GetRemainingCapacity(pos, enforceFormatLimits)); if(pos >= ordersMax || GetLengthTailTrimmed() >= ordersMax || count == 0) return 0; - // Limit number of orders to be inserted so that we don't exceed the format limit. - LimitMax(count, static_cast(ordersMax - pos)); reserve(std::max(pos, GetLength()) + count); // Inserting past the end of the container? if(pos > size()) @@ -214,6 +238,15 @@ ORDERINDEX ModSequence::insert(ORDERINDEX pos, ORDERINDEX count, PATTERNINDEX fi } +ORDERINDEX ModSequence::insert(ORDERINDEX pos, const mpt::span orders, bool enforceFormatLimits) +{ + MPT_ASSERT(reinterpret_cast(orders.data()) < reinterpret_cast(data()) || reinterpret_cast(orders.data()) > reinterpret_cast(data() + size())); + ORDERINDEX count = insert(pos, mpt::saturate_cast(orders.size()), 0, enforceFormatLimits); + std::copy(orders.begin(), orders.begin() + count, begin() + pos); + return count; +} + + bool ModSequence::IsValidPat(ORDERINDEX ord) const noexcept { if(ord < size()) @@ -394,12 +427,12 @@ bool ModSequenceSet::SplitSubsongsToMultipleSequences() for(ORDERINDEX ord = 0; ord < length; ord++) { // End of subsong? - if(!m_Sequences[0].IsValidPat(ord) && m_Sequences[0][ord] != GetIgnoreIndex()) + if(!m_Sequences[0].IsValidPat(ord) && m_Sequences[0][ord] != PATTERNINDEX_SKIP) { // Remove all separator patterns between current and next subsong first while(ord < length && !m_sndFile.Patterns.IsValidPat(m_Sequences[0][ord])) { - m_Sequences[0][ord] = GetInvalidPatIndex(); + m_Sequences[0][ord] = PATTERNINDEX_INVALID; ord++; modified = true; } @@ -415,11 +448,11 @@ bool ModSequenceSet::SplitSubsongsToMultipleSequences() modified = true; // Now, move all following orders to the new sequence - while(ord < length && m_Sequences[0][ord] != GetInvalidPatIndex()) + while(ord < length && m_Sequences[0][ord] != PATTERNINDEX_INVALID) { PATTERNINDEX copyPat = m_Sequences[0][ord]; m_Sequences[newSeq].push_back(copyPat); - m_Sequences[0][ord] = GetInvalidPatIndex(); + m_Sequences[0][ord] = PATTERNINDEX_INVALID; ord++; // Is this a valid pattern? adjust pattern jump commands, if necessary. @@ -442,26 +475,34 @@ bool ModSequenceSet::SplitSubsongsToMultipleSequences() } -// Convert the sequence's restart position information to a pattern command. -bool ModSequenceSet::RestartPosToPattern(SEQUENCEINDEX seq) +// Convert the sequence's restart position and tempo information to a pattern command. +bool ModSequenceSet::WriteGlobalsToPattern(SEQUENCEINDEX seq, bool writeRestartPos, bool writeTempo) { - bool result = false; + bool result = true; auto length = m_sndFile.GetLength(eNoAdjust, GetLengthTarget(true).StartPos(seq, 0, 0)); ModSequence &order = m_Sequences[seq]; for(const auto &subSong : length) { - if(subSong.endOrder != ORDERINDEX_INVALID && subSong.endRow != ROWINDEX_INVALID) + if(writeRestartPos && subSong.endOrder != ORDERINDEX_INVALID && subSong.endRow != ROWINDEX_INVALID) { if(mpt::in_range(order.GetRestartPos())) { PATTERNINDEX writePat = order.EnsureUnique(subSong.endOrder); - result = m_sndFile.Patterns[writePat].WriteEffect( + result &= m_sndFile.Patterns[writePat].WriteEffect( EffectWriter(CMD_POSITIONJUMP, static_cast(order.GetRestartPos())).Row(subSong.endRow).RetryNextRow()); } else { result = false; } } + if(writeTempo && subSong.startOrder != ORDERINDEX_INVALID && subSong.startRow != ORDERINDEX_INVALID) + { + PATTERNINDEX writePat = order.EnsureUnique(subSong.startOrder); + result &= m_sndFile.Patterns[writePat].WriteEffect( + EffectWriter(CMD_TEMPO, mpt::saturate_round(order.GetDefaultTempo().ToDouble())).Row(subSong.startRow).RetryNextRow()); + result &= m_sndFile.Patterns[writePat].WriteEffect( + EffectWriter(CMD_SPEED, mpt::saturate_cast(order.GetDefaultSpeed())).Row(subSong.startRow).RetryNextRow()); + } } order.SetRestartPos(0); return result; @@ -485,9 +526,9 @@ bool ModSequenceSet::MergeSequences() for(SEQUENCEINDEX seqNum = 1; seqNum < GetNumSequences(); seqNum++) { - ModSequence &seq = m_Sequences[seqNum]; + ModSequence &sourceSeq = m_Sequences[seqNum]; const ORDERINDEX firstOrder = firstSeq.GetLength() + 1; // +1 for separator item - const ORDERINDEX lengthTrimmed = seq.GetLengthTailTrimmed(); + const ORDERINDEX lengthTrimmed = sourceSeq.GetLengthTailTrimmed(); if(firstOrder + lengthTrimmed > m_sndFile.GetModSpecifications().ordersMax) { m_sndFile.AddToLog(LogWarning, MPT_UFORMAT("WARNING: Cannot merge Sequence {} (too long!)")(seqNum + 1)); @@ -495,18 +536,18 @@ bool ModSequenceSet::MergeSequences() } firstSeq.reserve(firstOrder + lengthTrimmed); firstSeq.push_back(); // Separator item - RestartPosToPattern(seqNum); + WriteGlobalsToPattern(seqNum, true, sourceSeq.GetDefaultTempo() != firstSeq.GetDefaultTempo() || sourceSeq.GetDefaultSpeed() != firstSeq.GetDefaultSpeed()); patternsFixed.resize(m_sndFile.Patterns.Size(), SEQUENCEINDEX_INVALID); // Previous line might have increased pattern count for(ORDERINDEX ord = 0; ord < lengthTrimmed; ord++) { - PATTERNINDEX pat = seq[ord]; + PATTERNINDEX pat = sourceSeq[ord]; firstSeq.push_back(pat); // Try to fix pattern jump commands if(!m_sndFile.Patterns.IsValidPat(pat)) continue; auto m = m_sndFile.Patterns[pat].begin(); - for(size_t len = 0; len < m_sndFile.Patterns[pat].GetNumRows() * m_sndFile.m_nChannels; m++, len++) + for(size_t len = 0; len < m_sndFile.Patterns[pat].GetNumRows() * m_sndFile.GetNumChannels(); m++, len++) { if(m->command == CMD_POSITIONJUMP) { @@ -536,6 +577,7 @@ bool ModSequenceSet::MergeSequences() } m_Sequences.erase(m_Sequences.begin() + 1, m_Sequences.end()); m_currentSeq = 0; + firstSeq.SetName({}); return true; } @@ -552,7 +594,7 @@ bool ModSequence::HasSubsongs() const noexcept { const auto endPat = begin() + GetLengthTailTrimmed(); return std::find_if(begin(), endPat, - [&](PATTERNINDEX pat) { return pat != GetIgnoreIndex() && !m_sndFile.Patterns.IsValidPat(pat); }) != endPat; + [&](PATTERNINDEX pat) { return pat != PATTERNINDEX_SKIP && !m_sndFile.Patterns.IsValidPat(pat); }) != endPat; } #endif // MODPLUG_TRACKER @@ -572,8 +614,10 @@ size_t ModSequence::WriteAsByte(std::ostream &f, const ORDERINDEX count, uint8 s const PATTERNINDEX pat = (*this)[i]; uint8 temp = static_cast(pat); - if(pat == GetInvalidPatIndex()) temp = stopIndex; - else if(pat == GetIgnoreIndex() || pat > 0xFF) temp = ignoreIndex; + if(pat == PATTERNINDEX_INVALID) + temp = stopIndex; + else if(pat == PATTERNINDEX_SKIP || pat > 0xFF) + temp = ignoreIndex; mpt::IO::WriteIntLE(f, temp); } // Fill non-existing order items with stop indices @@ -631,6 +675,8 @@ void WriteModSequence(std::ostream& oStrm, const ModSequence& seq) ssb.WriteItem(seq, "a", srlztn::VectorWriter(length)); if(seq.GetRestartPos() > 0) ssb.WriteItem(seq.GetRestartPos(), "r"); + ssb.WriteItem(seq.GetDefaultTempo().GetRaw(), "t"); + ssb.WriteItem(seq.GetDefaultSpeed(), "s"); ssb.FinishWrite(); } #endif // MODPLUG_NO_FILESAVE @@ -659,6 +705,18 @@ void ReadModSequence(std::istream& iStrm, ModSequence& seq, const size_t, mpt::C { seq.SetRestartPos(restartPos); } + + TEMPO::store_t defaultTempo = 0; + if(ssb.ReadItem(defaultTempo, "t") && defaultTempo > 0) + { + seq.SetDefaultTempo(TEMPO{}.SetRaw(defaultTempo)); + } + + uint32 defaultSpeed = 0; + if(ssb.ReadItem(defaultSpeed, "s") && defaultSpeed > 0) + { + seq.SetDefaultSpeed(defaultSpeed); + } } @@ -698,12 +756,16 @@ void ReadModSequences(std::istream& iStrm, ModSequenceSet& seq, const size_t, mp if (seq.GetNumSequences() < seqs) seq.m_Sequences.resize(seqs, ModSequence(seq.m_sndFile)); - // There used to be only one restart position for all sequences - ORDERINDEX legacyRestartPos = seq(0).GetRestartPos(); + // There used to be only one restart position / tempo / speed for all sequences + const auto legacyRestartPos = seq(0).GetRestartPos(); + const auto legacyTempo = seq(0).GetDefaultTempo(); + const auto legacySpeed = seq(0).GetDefaultSpeed(); for(SEQUENCEINDEX i = 0; i < seqs; i++) { seq(i).SetRestartPos(legacyRestartPos); + seq(i).SetDefaultTempo(legacyTempo); + seq(i).SetDefaultSpeed(legacySpeed); ssb.ReadItem(seq(i), srlztn::ID::FromInt(i), [defaultCharset](std::istream &iStrm, ModSequence &seq, std::size_t dummy) { return ReadModSequence(iStrm, seq, dummy, defaultCharset); }); } seq.m_currentSeq = (currentSeq < seq.GetNumSequences()) ? currentSeq : 0; diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/ModSequence.h b/Frameworks/OpenMPT/OpenMPT/soundlib/ModSequence.h index c7ea1e4c3..ec3a61e62 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/ModSequence.h +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/ModSequence.h @@ -28,9 +28,11 @@ class ModSequence: public std::vector friend class ModSequenceSet; protected: - mpt::ustring m_name; // Sequence name - CSoundFile &m_sndFile; // Associated CSoundFile - ORDERINDEX m_restartPos = 0; // Restart position when playback of this order ended + mpt::ustring m_name; // Sequence name + CSoundFile &m_sndFile; // Associated CSoundFile + ORDERINDEX m_restartPos = 0; // Restart position when playback of this order ended + TEMPO m_defaultTempo{125, 0}; // Default tempo at start of sequence + uint32 m_defaultSpeed = 6; // Default ticks per row at start of sequence public: ModSequence(CSoundFile &sndFile); @@ -48,6 +50,8 @@ public: ORDERINDEX GetLengthTailTrimmed() const noexcept; // Returns length of sequence stopping counting on first '---' (or at the end of sequence). ORDERINDEX GetLengthFirstEmpty() const noexcept; + // Returns amount of patterns that can be added at the end of the order list before reaching the current format's limits. + ORDERINDEX GetRemainingCapacity(ORDERINDEX startingFrom = ORDERINDEX_INVALID, bool enforceFormatLimits = true) const noexcept; // Replaces order list with 'newSize' copies of 'pat'. void assign(ORDERINDEX newSize, PATTERNINDEX pat); @@ -55,13 +59,14 @@ public: // Inserts 'count' orders starting from 'pos' using 'fill' as the pattern index for all inserted orders. // Sequence will automatically grow if needed and if it can't grow enough, some tail orders will be discarded. // Return: Number of orders inserted (up to 'count' many). - ORDERINDEX insert(ORDERINDEX pos, ORDERINDEX count) { return insert(pos, count, GetInvalidPatIndex()); } - ORDERINDEX insert(ORDERINDEX pos, ORDERINDEX count, PATTERNINDEX fill); + ORDERINDEX insert(ORDERINDEX pos, ORDERINDEX count) { return insert(pos, count, PATTERNINDEX_INVALID, true); } + ORDERINDEX insert(ORDERINDEX pos, ORDERINDEX count, PATTERNINDEX fill, bool enforceFormatLimits = true); + ORDERINDEX insert(ORDERINDEX pos, const mpt::span orders, bool enforceFormatLimits = true); - void push_back() { push_back(GetInvalidPatIndex()); } + void push_back() { push_back(PATTERNINDEX_INVALID); } void push_back(PATTERNINDEX pat) { if(GetLength() < MAX_ORDERS) std::vector::push_back(pat); } - void resize(ORDERINDEX newSize) { resize(newSize, GetInvalidPatIndex()); } + void resize(ORDERINDEX newSize) { resize(newSize, PATTERNINDEX_INVALID); } void resize(ORDERINDEX newSize, PATTERNINDEX pat) { std::vector::resize(std::min(MAX_ORDERS, newSize), pat); } // Removes orders from range [posBegin, posEnd]. @@ -83,11 +88,6 @@ public: void AdjustToNewModType(const MODTYPE oldtype); - // Returns the internal representation of a stop '---' index - static constexpr PATTERNINDEX GetInvalidPatIndex() noexcept { return uint16_max; } - // Returns the internal representation of an ignore '+++' index - static constexpr PATTERNINDEX GetIgnoreIndex() noexcept { return uint16_max - 1; } - // Returns the previous/next order ignoring skip indices (+++). // If no previous/next order exists, return first/last order, and zero // when orderlist is empty. @@ -119,13 +119,18 @@ public: bool HasSubsongs() const noexcept; #endif // MODPLUG_TRACKER - // Sequence name setter / getter inline void SetName(mpt::ustring newName) noexcept { m_name = std::move(newName); } inline mpt::ustring GetName() const { return m_name; } - // Restart position setter / getter inline void SetRestartPos(ORDERINDEX restartPos) noexcept { m_restartPos = restartPos; } inline ORDERINDEX GetRestartPos() const noexcept { return m_restartPos; } + + void SetDefaultTempo(TEMPO tempo) noexcept; + inline void SetDefaultTempoInt(uint32 tempo) noexcept { SetDefaultTempo(TEMPO{mpt::saturate_cast(tempo), 0}); } + inline TEMPO GetDefaultTempo() const noexcept { return m_defaultTempo; } + + inline void SetDefaultSpeed(uint32 speed) noexcept { m_defaultSpeed = speed ? speed : 6; } + inline uint32 GetDefaultSpeed() const noexcept { return m_defaultSpeed; } }; @@ -165,11 +170,6 @@ public: // Removes given sequence. void RemoveSequence(SEQUENCEINDEX); - // Returns the internal representation of a stop '---' index - static constexpr PATTERNINDEX GetInvalidPatIndex() noexcept { return ModSequence::GetInvalidPatIndex(); } - // Returns the internal representation of an ignore '+++' index - static constexpr PATTERNINDEX GetIgnoreIndex() noexcept { return ModSequence::GetIgnoreIndex(); } - #ifdef MODPLUG_TRACKER // Assigns a new set of sequences. The vector contents indicate which existing sequences to keep / duplicate or if a new sequences should be inserted (SEQUENCEINDEX_INVALID) // The function fails if the vector is empty or contains too many sequences. @@ -185,8 +185,8 @@ public: // Returns true if sequences were modified, false otherwise. bool SplitSubsongsToMultipleSequences(); - // Convert the sequence's restart position information to a pattern command. - bool RestartPosToPattern(SEQUENCEINDEX seq); + // Convert the sequence's restart position and tempo information to a pattern command. + bool WriteGlobalsToPattern(SEQUENCEINDEX seq, bool writeRestartPos, bool writeTempo); // Merges multiple sequences into one and destroys all other sequences. // Returns false if there were no sequences to merge, true otherwise. bool MergeSequences(); diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/OPL.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/OPL.cpp index faa205ebf..afc305a65 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/OPL.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/OPL.cpp @@ -9,15 +9,24 @@ */ #include "stdafx.h" -#include "../common/misc_util.h" #include "OPL.h" +#include "../common/misc_util.h" + +#include +#if MPT_COMPILER_GCC +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wconversion" +#endif #include "opal.h" +#if MPT_COMPILER_GCC +#pragma GCC diagnostic pop +#endif OPENMPT_NAMESPACE_BEGIN -OPL::OPL(uint32 samplerate) +OPL::OPL(uint32 sampleRate) { - Initialize(samplerate); + Initialize(sampleRate); } @@ -34,12 +43,12 @@ OPL::~OPL() } -void OPL::Initialize(uint32 samplerate) +void OPL::Initialize(uint32 sampleRate) { if(m_opl == nullptr) - m_opl = std::make_unique(samplerate); + m_opl = std::make_unique(sampleRate); else - m_opl->SetSampleRate(samplerate); + m_opl->SetSampleRate(sampleRate); Reset(); } @@ -62,7 +71,7 @@ void OPL::Mix(int32 *target, size_t count, uint32 volumeFactorQ16) } -uint16 OPL::ChannelToRegister(uint8 oplCh) +OPL::Register OPL::ChannelToRegister(uint8 oplCh) { if(oplCh < 9) return oplCh; @@ -72,7 +81,7 @@ uint16 OPL::ChannelToRegister(uint8 oplCh) // Translate a channel's first operator address into a register -uint16 OPL::OperatorToRegister(uint8 oplCh) +OPL::Register OPL::OperatorToRegister(uint8 oplCh) { static constexpr uint8 OPLChannelToOperator[] = { 0, 1, 2, 8, 9, 10, 16, 17, 18 }; if(oplCh < 9) @@ -143,6 +152,8 @@ void OPL::MoveChannel(CHANNELINDEX from, CHANNELINDEX to) m_OPLtoChan[oplCh] = to; m_ChanToOPL[from] = OPL_CHANNEL_INVALID; m_ChanToOPL[to] = oplCh; + if(m_logger) + m_logger->MoveChannel(from, to); } @@ -151,6 +162,8 @@ void OPL::NoteOff(CHANNELINDEX c) uint8 oplCh = GetVoice(c); if(oplCh == OPL_CHANNEL_INVALID || m_opl == nullptr) return; + if(!(m_KeyOnBlock[oplCh] & KEYON_BIT)) + return; m_KeyOnBlock[oplCh] &= ~KEYON_BIT; Port(c, KEYON_BLOCK | ChannelToRegister(oplCh), m_KeyOnBlock[oplCh]); } @@ -202,9 +215,9 @@ void OPL::Frequency(CHANNELINDEX c, uint32 milliHertz, bool keyOff, bool beating fnum |= (block << 10); - uint16 channel = ChannelToRegister(oplCh); - m_KeyOnBlock[oplCh] = (keyOff ? 0 : KEYON_BIT) | (fnum >> 8); // Key on bit + Octave (block) + F-number high 2 bits - Port(c, FNUM_LOW | channel, fnum & 0xFF); // F-Number low 8 bits + OPL::Register channel = ChannelToRegister(oplCh); + m_KeyOnBlock[oplCh] = static_cast((keyOff ? 0 : KEYON_BIT) | (fnum >> 8)); // Key on bit + Octave (block) + F-number high 2 bits + Port(c, FNUM_LOW | channel, fnum & 0xFFu); // F-Number low 8 bits Port(c, KEYON_BLOCK | channel, m_KeyOnBlock[oplCh]); m_isActive = true; @@ -217,7 +230,7 @@ uint8 OPL::CalcVolume(uint8 trackerVol, uint8 kslVolume) return kslVolume; if(trackerVol > 0) trackerVol++; - return (kslVolume & KSL_MASK) | (63u - ((63u - (kslVolume & TOTAL_LEVEL_MASK)) * trackerVol) / 64u); + return static_cast((kslVolume & KSL_MASK) | (63u - ((63u - (kslVolume & TOTAL_LEVEL_MASK)) * trackerVol) / 64u)); } @@ -228,7 +241,7 @@ void OPL::Volume(CHANNELINDEX c, uint8 vol, bool applyToModulator) return; const auto &patch = m_Patches[oplCh]; - const uint16 modulator = OperatorToRegister(oplCh), carrier = modulator + 3; + const OPL::Register modulator = OperatorToRegister(oplCh), carrier = modulator + 3; if((patch[10] & CONNECTION_BIT) || applyToModulator) { // Set volume of both operators in additive mode @@ -258,7 +271,7 @@ int8 OPL::Pan(CHANNELINDEX c, int32 pan) fbConn |= VOICE_TO_RIGHT; Port(c, FEEDBACK_CONNECTION | ChannelToRegister(oplCh), fbConn); - return ((fbConn & VOICE_TO_LEFT) ? -1 : 0) + ((fbConn & VOICE_TO_RIGHT) ? 1 : 0); + return static_cast(((fbConn & VOICE_TO_LEFT) ? -1 : 0) + ((fbConn & VOICE_TO_RIGHT) ? 1 : 0)); } @@ -270,7 +283,7 @@ void OPL::Patch(CHANNELINDEX c, const OPLPatch &patch) m_Patches[oplCh] = patch; - const uint16 modulator = OperatorToRegister(oplCh), carrier = modulator + 3; + const OPL::Register modulator = OperatorToRegister(oplCh), carrier = modulator + 3; for(uint8 op = 0; op < 2; op++) { const auto opReg = op ? carrier : modulator; @@ -305,7 +318,7 @@ void OPL::Reset() } -void OPL::Port(CHANNELINDEX c, uint16 reg, uint8 value) +void OPL::Port(CHANNELINDEX c, OPL::Register reg, OPL::Value value) { if(!m_logger) m_opl->Port(reg, value); @@ -314,32 +327,65 @@ void OPL::Port(CHANNELINDEX c, uint16 reg, uint8 value) } -std::vector OPL::AllVoiceRegisters() +std::vector OPL::AllVoiceRegisters(uint8 oplCh) { - static constexpr uint8 opRegisters[] = {OPL::AM_VIB, OPL::KSL_LEVEL, OPL::ATTACK_DECAY, OPL::SUSTAIN_RELEASE, OPL::WAVE_SELECT}; - static constexpr uint8 chnRegisters[] = {OPL::FNUM_LOW, OPL::KEYON_BLOCK, OPL::FEEDBACK_CONNECTION}; - std::vector result; - result.reserve(234); - for(uint16 chip = 0; chip < 2; chip++) + static constexpr uint8 opRegisters[] = {AM_VIB, KSL_LEVEL, ATTACK_DECAY, SUSTAIN_RELEASE, WAVE_SELECT}; + static constexpr uint8 chnRegisters[] = {FNUM_LOW, KEYON_BLOCK, FEEDBACK_CONNECTION}; + std::vector result; + uint8 minVoice = 0, maxVoice = OPL_CHANNELS; + if(oplCh < OPL_CHANNELS) { + minVoice = oplCh; + maxVoice = oplCh + 1; + } + result.reserve(13 * (maxVoice - minVoice)); + for(uint8 voice = minVoice; voice < maxVoice; voice++) + { + const Register opBaseReg = OperatorToRegister(voice); for(uint8 opReg : opRegisters) { - for(uint8 op = 0; op < 22; op++) + for(uint8 op = 0; op <= 3; op += 3) { - if((op & 7) < 6) - result.push_back((chip << 8) | opReg | op); + result.push_back(opReg | (opBaseReg + op)); + MPT_ASSERT(RegisterToVoice(result.back()) == voice); } } + const Register chnBaseReg = ChannelToRegister(voice); for(uint8 chnReg : chnRegisters) { - for(uint8 chn = 0; chn < 9; chn++) - { - result.push_back((chip << 8) | chnReg | chn); - } + result.push_back(chnReg | chnBaseReg); + MPT_ASSERT(RegisterToVoice(result.back()) == voice); } } return result; } +uint8 OPL::RegisterToVoice(OPL::Register reg) +{ + const OPL::Register regLo = reg & 0xE0; + const uint8 baseCh = (reg > 0xFF) ? 9 : 0; + if(reg == TREMOLO_VIBRATO_DEPTH) + return 0xFF; + if(regLo >= FNUM_LOW && regLo <= FEEDBACK_CONNECTION) + return baseCh + static_cast(reg & 0x0F); + if(regLo >= AM_VIB && regLo <= WAVE_SELECT) + return static_cast(baseCh + (reg & 0x07) % 3u + ((reg & 0x1F) >> 3) * 3); + return 0xFF; +} + + +OPL::Register OPL::StripVoiceFromRegister(OPL::Register reg) +{ + const OPL::Register regLo = reg & 0xE0; + if(reg == TREMOLO_VIBRATO_DEPTH) + return reg; + if(regLo >= FNUM_LOW && regLo <= FEEDBACK_CONNECTION) + return (reg & 0xF0); + if(regLo >= AM_VIB && regLo <= WAVE_SELECT) + return static_cast(regLo + ((reg & 0x07) >= 3 ? 3 : 0)); + return reg; +} + + OPENMPT_NAMESPACE_END diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/OPL.h b/Frameworks/OpenMPT/OpenMPT/soundlib/OPL.h index bf1fee002..214a00602 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/OPL.h +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/OPL.h @@ -23,16 +23,18 @@ public: enum OPLRegisters : uint8 { // Operators (combine with result of OperatorToRegister) - AM_VIB = 0x20, // AM / VIB / EG / KSR / Multiple (0x20 to 0x35) - KSL_LEVEL = 0x40, // KSL / Total level (0x40 to 0x55) - ATTACK_DECAY = 0x60, // Attack rate / Decay rate (0x60 to 0x75) - SUSTAIN_RELEASE = 0x80, // Sustain level / Release rate (0x80 to 0x95) - WAVE_SELECT = 0xE0, // Wave select (0xE0 to 0xF5) + AM_VIB = 0x20, // AM / VIB / EG / KSR / Multiple (0x20 to 0x35) + KSL_LEVEL = 0x40, // KSL / Total level (0x40 to 0x55) + ATTACK_DECAY = 0x60, // Attack rate / Decay rate (0x60 to 0x75) + SUSTAIN_RELEASE = 0x80, // Sustain level / Release rate (0x80 to 0x95) + WAVE_SELECT = 0xE0, // Wave select (0xE0 to 0xF5) // Channels (combine with result of ChannelToRegister) - FNUM_LOW = 0xA0, // F-number low bits (0xA0 to 0xA8) - KEYON_BLOCK = 0xB0, // F-number high bits / Key on / Block (octave) (0xB0 to 0xB8) - FEEDBACK_CONNECTION = 0xC0, // Feedback / Connection (0xC0 to 0xC8) + FNUM_LOW = 0xA0, // F-number low bits (0xA0 to 0xA8) + KEYON_BLOCK = 0xB0, // F-number high bits / Key on / Block (octave) (0xB0 to 0xB8) + FEEDBACK_CONNECTION = 0xC0, // Feedback / Connection (0xC0 to 0xC8) + + TREMOLO_VIBRATO_DEPTH = 0xBD, // Tremolo Depth / Vibrato Depth / Percussion Mode / BD/SD/TT/CY/HH Key-On }; enum OPLValues : uint8 @@ -41,12 +43,12 @@ public: TREMOLO_ON = 0x80, VIBRATO_ON = 0x40, SUSTAIN_ON = 0x20, - KSR = 0x10, // Key scaling rate - MULTIPLE_MASK = 0x0F, // Frequency multiplier + KSR = 0x10, // Key scaling rate + MULTIPLE_MASK = 0x0F, // Frequency multiplier // KSL_LEVEL - KSL_MASK = 0xC0, // Envelope scaling bits - TOTAL_LEVEL_MASK = 0x3F, // Strength (volume) of OP + KSL_MASK = 0xC0, // Envelope scaling bits + TOTAL_LEVEL_MASK = 0x3F, // Strength (volume) of OP // ATTACK_DECAY ATTACK_MASK = 0xF0, @@ -60,25 +62,29 @@ public: KEYON_BIT = 0x20, // FEEDBACK_CONNECTION - FEEDBACK_MASK = 0x0E, // Valid just for first OP of a voice + FEEDBACK_MASK = 0x0E, // Valid just for first OP of a voice CONNECTION_BIT = 0x01, VOICE_TO_LEFT = 0x10, VOICE_TO_RIGHT = 0x20, STEREO_BITS = VOICE_TO_LEFT | VOICE_TO_RIGHT, }; + using Register = uint16; + using Value = uint8; + class IRegisterLogger { public: - virtual void Port(CHANNELINDEX c, uint16 reg, uint8 value) = 0; + virtual void Port(CHANNELINDEX c, Register reg, Value value) = 0; + virtual void MoveChannel(CHANNELINDEX from, CHANNELINDEX to) = 0; virtual ~IRegisterLogger() {} }; - OPL(uint32 samplerate); - OPL(IRegisterLogger &logger); + explicit OPL(uint32 sampleRate); + explicit OPL(IRegisterLogger &logger); ~OPL(); - void Initialize(uint32 samplerate); + void Initialize(uint32 sampleRate); void Mix(int32 *buffer, size_t count, uint32 volumeFactorQ16); void NoteOff(CHANNELINDEX c); @@ -91,16 +97,20 @@ public: void MoveChannel(CHANNELINDEX from, CHANNELINDEX to); void Reset(); - // A list of all registers for channels and operators - static std::vector AllVoiceRegisters(); + // A list of all registers for channels and operators if oplCh == 0xFF, otherwise all registers for the given channel and its operators + static std::vector AllVoiceRegisters(uint8 oplCh = 0xFF); + // Returns voice for given register, or 0xFF if it's not a voice-specific register + static uint8 RegisterToVoice(Register reg); + // Returns register without any voice offset, so as if it was triggering the first voice + static Register StripVoiceFromRegister(Register reg); protected: - static uint16 ChannelToRegister(uint8 oplCh); - static uint16 OperatorToRegister(uint8 oplCh); + static Register ChannelToRegister(uint8 oplCh); + static Register OperatorToRegister(uint8 oplCh); static uint8 CalcVolume(uint8 trackerVol, uint8 kslVolume); uint8 GetVoice(CHANNELINDEX c) const; uint8 AllocateVoice(CHANNELINDEX c); - void Port(CHANNELINDEX c, uint16 reg, uint8 value); + void Port(CHANNELINDEX c, Register reg, Value value); enum { diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/PlayState.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/PlayState.cpp new file mode 100644 index 000000000..477fa5008 --- /dev/null +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/PlayState.cpp @@ -0,0 +1,76 @@ +/* + * PlayState.cpp + * ------------- + * Purpose: This class represents all of the playback state of a module. + * Notes : (currently none) + * Authors: OpenMPT Devs + * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. + */ + + +#include "stdafx.h" +#include "PlayState.h" +#include "MIDIMacros.h" +#include "Mixer.h" +#include "Sndfile.h" + + +OPENMPT_NAMESPACE_BEGIN + + +PlayState::PlayState() +{ + Chn.fill({}); + m_midiMacroScratchSpace.reserve(kMacroLength); // Note: If macros ever become variable-length, the scratch space needs to be at least one byte longer than the longest macro in the file for end-of-SysEx insertion to stay allocation-free in the mixer! +} + + +void PlayState::ResetGlobalVolumeRamping() noexcept +{ + m_lHighResRampingGlobalVolume = m_nGlobalVolume << VOLUMERAMPPRECISION; + m_nGlobalVolumeDestination = m_nGlobalVolume; + m_nSamplesToGlobalVolRampDest = 0; + m_nGlobalVolumeRampAmount = 0; +} + + +void PlayState::UpdateTimeSignature(const CSoundFile &sndFile) noexcept +{ + if(!sndFile.Patterns.IsValidIndex(m_nPattern) || !sndFile.Patterns[m_nPattern].GetOverrideSignature()) + { + m_nCurrentRowsPerBeat = sndFile.m_nDefaultRowsPerBeat; + m_nCurrentRowsPerMeasure = sndFile.m_nDefaultRowsPerMeasure; + } else + { + m_nCurrentRowsPerBeat = sndFile.Patterns[m_nPattern].GetRowsPerBeat(); + m_nCurrentRowsPerMeasure = sndFile.Patterns[m_nPattern].GetRowsPerMeasure(); + } +} + + +void PlayState::UpdatePPQ(bool patternTransition) noexcept +{ + ROWINDEX rpm = m_nCurrentRowsPerMeasure ? m_nCurrentRowsPerMeasure : DEFAULT_ROWS_PER_MEASURE; + ROWINDEX rpb = m_nCurrentRowsPerBeat ? m_nCurrentRowsPerBeat : DEFAULT_ROWS_PER_BEAT; + if(m_lTotalSampleCount > 0 && (patternTransition || !(m_nRow % rpm))) + { + // Pattern end = end of measure, so round up PPQ to the next full measure + m_ppqPosBeat += (rpm + (rpb - 1)) / rpb; + m_ppqPosFract = 0; + } +} + + +mpt::span PlayState::PatternChannels(const CSoundFile &sndFile) noexcept +{ + return mpt::as_span(Chn).subspan(0, std::min(Chn.size(), static_cast(sndFile.GetNumChannels()))); +} + + +mpt::span PlayState::BackgroundChannels(const CSoundFile &sndFile) noexcept +{ + return mpt::as_span(Chn).subspan(std::min(Chn.size(), static_cast(sndFile.GetNumChannels()))); +} + + +OPENMPT_NAMESPACE_END diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/PlayState.h b/Frameworks/OpenMPT/OpenMPT/soundlib/PlayState.h new file mode 100644 index 000000000..c7cffa2fb --- /dev/null +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/PlayState.h @@ -0,0 +1,114 @@ +/* + * PlayState.h + * ----------- + * Purpose: This class represents all of the playback state of a module. + * Notes : (currently none) + * Authors: OpenMPT Devs + * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. + */ + + +#pragma once + +#include "openmpt/all/BuildSettings.hpp" +#include "ModChannel.h" +#include "Snd_defs.h" + +#include +#include +#include + +OPENMPT_NAMESPACE_BEGIN + +struct PlayState +{ + friend class CSoundFile; + +public: + samplecount_t m_lTotalSampleCount = 0; // Total number of rendered samples +protected: + samplecount_t m_nBufferCount = 0; // Remaining number samples to render for this tick + double m_dBufferDiff = 0.0; // Modern tempo rounding error compensation + +public: + double m_ppqPosFract = 0.0; // Fractional PPQ position within current measure + uint32 m_ppqPosBeat = 0; // PPQ position of the last start of measure + uint32 m_nTickCount = 0; // Current tick being processed +protected: + uint32 m_nPatternDelay = 0; // Pattern delay (rows) + uint32 m_nFrameDelay = 0; // Fine pattern delay (ticks) +public: + uint32 m_nSamplesPerTick = 0; + ROWINDEX m_nCurrentRowsPerBeat = 0; // Current time signature + ROWINDEX m_nCurrentRowsPerMeasure = 0; // Current time signature + uint32 m_nMusicSpeed = 0; // Current speed + TEMPO m_nMusicTempo; // Current tempo + + // Playback position + ROWINDEX m_nRow = 0; // Current row being processed + ROWINDEX m_nNextRow = 0; // Next row to process +protected: + ROWINDEX m_nextPatStartRow = 0; // For FT2's E60 bug + ROWINDEX m_breakRow = 0; // Candidate target row for pattern break + ROWINDEX m_patLoopRow = 0; // Candidate target row for pattern loop + ORDERINDEX m_posJump = 0; // Candidate target order for position jump + +public: + PATTERNINDEX m_nPattern = 0; // Current pattern being processed + ORDERINDEX m_nCurrentOrder = 0; // Current order being processed + ORDERINDEX m_nNextOrder = 0; // Next order to process + ORDERINDEX m_nSeqOverride = ORDERINDEX_INVALID; // Queued order to be processed next, regardless of what order would normally follow + OrderTransitionMode m_seqOverrideMode = OrderTransitionMode::AtPatternEnd; + + // Global volume +public: + int32 m_nGlobalVolume = MAX_GLOBAL_VOLUME; // Current global volume (0...MAX_GLOBAL_VOLUME) +protected: + // Global volume ramping + int32 m_nSamplesToGlobalVolRampDest = 0, m_nGlobalVolumeRampAmount = 0; + int32 m_nGlobalVolumeDestination = 0, m_lHighResRampingGlobalVolume = 0; + +public: + FlagSet m_flags = SONG_POSITIONCHANGED; + + std::array ChnMix; // Index of channels in Chn to be actually mixed + std::array Chn; // Mixing channels... First m_nChannels channels are directly mapped to pattern channels (i.e. they are never NNA channels)! + GlobalScriptState m_globalScriptState; + + struct MIDIMacroEvaluationResults + { + std::map pluginDryWetRatio; + std::map, PlugParamValue> pluginParameter; + }; + + std::vector m_midiMacroScratchSpace; + std::optional m_midiMacroEvaluationResults; + +public: + PlayState(); + + void ResetGlobalVolumeRamping() noexcept; + + void UpdateTimeSignature(const CSoundFile &sndFile) noexcept; + void UpdatePPQ(bool patternTransition) noexcept; + + constexpr uint32 TicksOnRow() const noexcept + { + return (m_nMusicSpeed + m_nFrameDelay) * std::max(m_nPatternDelay, uint32(1)); + } + + mpt::span PatternChannels(const CSoundFile &sndFile) noexcept; + mpt::span PatternChannels(const CSoundFile &sndFile) const noexcept + { + return const_cast(this)->PatternChannels(sndFile); + } + + mpt::span BackgroundChannels(const CSoundFile &sndFile) noexcept; + mpt::span BackgroundChannels(const CSoundFile &sndFile) const noexcept + { + return const_cast(this)->PatternChannels(sndFile); + } +}; + + +OPENMPT_NAMESPACE_END diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/PlaybackTest.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/PlaybackTest.cpp new file mode 100644 index 000000000..3ba616e06 --- /dev/null +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/PlaybackTest.cpp @@ -0,0 +1,778 @@ +/* + * PlaybackTest.cpp + * ---------------- + * Purpose: Tools for verifying correct playback of modules + * Notes : (currently none) + * Authors: OpenMPT Devs + * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. + */ + + +#include "stdafx.h" + +#include "PlaybackTest.h" + +#include "../common/mptBaseMacros.h" + +#if defined(MPT_ENABLE_PLAYBACK_TRACE) + +#include "../common/FileReader.h" +#include "OPL.h" +#include "SampleIO.h" +#include "Sndfile.h" + +#include "mpt/base/bit.hpp" +#include "mpt/binary/hex.hpp" +#include "mpt/crc/crc.hpp" +#include "mpt/io/io.hpp" +#include "mpt/io/io_stdstream.hpp" +#include "mpt/random/seed.hpp" + +#include "openmpt/base/Endian.hpp" + +#include +#include + + +#endif // MPT_ENABLE_PLAYBACK_TRACE + + +OPENMPT_NAMESPACE_BEGIN + + +#if defined(MPT_ENABLE_PLAYBACK_TRACE) + + +struct TestDataHeader +{ + static constexpr char TestDataHeaderMagic[] = "OpenMPT Test Data\r\n\x1A"; + + char magic[std::size(TestDataHeaderMagic) - 1]; + uint8 fileVersion; + uint8 isAmiga; + uint8 positionPrecisionBits; + uint8 filterPrecisionBits; + uint8 srcMode; + uint8 outputChannels; + uint16le mixerChannels; + uint16le numSamples; + uint16le synthVolume; + int32le globalVolumeUnity; + int32le channelVolumeUnity; + uint32le mixingFreq; + uint32le mptVersion; +}; + +MPT_BINARY_STRUCT(TestDataHeader, 48) + + +struct TestDataRow +{ + uint32le order; + uint32le row; + int32le globalVolume; + uint32le tickLength; + uint8 isFirstTick; + uint16le activeChannels; +}; + +MPT_BINARY_STRUCT(TestDataRow, 19) + + +struct TestDataChannel +{ + enum Flags : uint8 + { + kFilterNone = 0x00, + kSurround = 0x01, + kOPL = 0x02, + kAmigaFilter = 0x04, + kFilterLowPass = 0x08, + kFilterHighPass = 0x10, + kFilterMask = 0x18, + }; + + int16le channel; // Negative = NNA channel (-1 = NNA of first channel, -2 = NNA of second channel, etc.) + uint16le nnaAge; // Generation of NNA channels + uint8 flags; + uint8 srcMode; + uint16le sample; + int32le leftVol; + int32le rightVol; + int64le increment; + int64le position; + int32le filterA0; + int32le filterB0; + int32le filterB1; + + bool operator<(const TestDataChannel &other) const noexcept + { + if(channel == -other.channel) // One is an NNA channel of the other + return channel > 0; + else if(channel != other.channel) // Completely unrelated channels + return std::abs(channel) < std::abs(other.channel); + else // Both are NNA channels of the same source channel + return nnaAge < other.nnaAge; + } +}; + +MPT_BINARY_STRUCT(TestDataChannel, 44) + + +using SampleDataHashAlgorithm = mpt::crc64_jones; +using SampleDataHash = mpt::packed; + + +struct PlaybackTestData +{ + struct Row + { + TestDataRow header; + std::vector channels; + std::vector oplRegisters; + }; + + TestDataHeader header; + std::vector sampleDataHashes; + std::vector rows; + + PlaybackTestSettings GetSettings() const + { + PlaybackTestSettings result; + result.mixingFreq = header.mixingFreq; + result.outputChannels = header.outputChannels; + result.mixerChannels = header.mixerChannels; + result.srcMode = Resampling::ToKnownMode(header.srcMode); + return result; + } +}; + + +class OPLPlaybackLog final : public OPL::IRegisterLogger +{ +public: + using OPLData = std::vector; + + OPLPlaybackLog(const PlayState &playState) : m_playState{playState} {} + + void Reset() + { + m_registers.clear(); + m_prevRegisters.clear(); + m_keyOnToggle.clear(); + m_globalRegisters.clear(); + m_chnRegisters.clear(); + m_prevChnRegisters.clear(); + } + + OPLData DumpRegisters() + { + OPLData dump; + + // Dump registers for all channels + for(const auto &[chn, registerDump] : m_chnRegisters) + { + bool first = true; + const auto prevRegisters = m_prevChnRegisters.find(chn); + for(const auto &[reg, value] : registerDump) + { + if(prevRegisters != m_prevChnRegisters.end()) + { + const auto prevRegister = prevRegisters->second.find(reg); + if(prevRegister != prevRegisters->second.end() && prevRegister->second == value) + continue; + } + m_prevChnRegisters[chn][reg] = value; + if(first) + { + const auto [sourceChn, nnaGeneration] = chn; + MPT_ASSERT(sourceChn < uint8_max); // nnaGeneration may be truncated but it doesn't matter, as it is purely for informational purposes + dump.insert(dump.end(), {static_cast(sourceChn), static_cast(nnaGeneration)}); + + // Was key-on toggled on this channel? + dump.push_back(static_cast(m_keyOnToggle.count(chn.first))); + first = false; + } + dump.insert(dump.end(), {reg, value}); + } + if(!first) + dump.push_back(uint8_max); + } + // Dump global register updates + bool first = true; + for(const auto reg : m_globalRegisters) + { + if(!m_prevRegisters.count(reg) || m_registers[reg] != m_prevRegisters[reg]) + { + if(first) + { + dump.insert(dump.end(), {uint8_max, uint8_max}); + first = false; + } + dump.insert(dump.end(), {static_cast(reg & 0xFF), static_cast(reg >> 8), m_registers[reg]}); + } + } + + for(const auto &[reg, value] : m_registers) + { + m_prevRegisters[reg] = value; + } + + m_chnRegisters.clear(); + m_globalRegisters.clear(); + m_registers.clear(); + m_keyOnToggle.clear(); + + return dump; + } + +#if MPT_GCC_AT_LEAST(12, 0, 0) && MPT_GCC_BEFORE(13, 1, 0) +// Work-around / +// . +#pragma GCC push_options +#if defined(__OPTIMIZE__) +#pragma GCC optimize("O1") +#endif +#endif + static std::string Format(const OPLData &data) + { + FileReader file(mpt::as_span(data)); + std::string result; + while(file.CanRead(3)) + { + const auto [chn, generation] = file.ReadArray(); + if(chn == uint8_max && generation == uint8_max) + break; + + result += "[Ch " + mpt::afmt::val(chn); + if(generation) + result += ":" + mpt::afmt::val(generation); + result += "] "; + + if(file.ReadUint8()) + result += "[Toggle] "; + + while(file.CanRead(2)) + { + const uint8 reg = file.ReadUint8(); + if(reg == uint8_max) + break; + const uint8 value = file.ReadUint8(); + result += mpt::afmt::HEX0<2>(reg) + "=" + mpt::afmt::HEX0<2>(value) + " "; + } + } + if(!file.CanRead(3)) + return result; + result += "[Global] "; + while(file.CanRead(3)) + { + const uint16 reg = file.ReadUint16LE(); + const uint8 value = file.ReadUint8(); + result += mpt::afmt::HEX0<2>(reg) + "=" + mpt::afmt::HEX0<2>(value) + " "; + } + return result; + } +#if MPT_GCC_AT_LEAST(12, 0, 0) && MPT_GCC_BEFORE(13, 1, 0) +#pragma GCC diagnostic pop +#pragma GCC pop_options +#endif + +protected: + void Port(CHANNELINDEX c, OPL::Register reg, OPL::Value value) override + { + MPT_ASSERT((OPL::RegisterToVoice(reg) == 0xFF) == (c == CHANNELINDEX_INVALID)); + if(c != CHANNELINDEX_INVALID) + { + if((reg & 0xF0) == OPL::KEYON_BLOCK) + { + const auto prev = m_prevRegisters.find(reg); + const auto current = m_registers.find(reg); + if(prev != m_prevRegisters.end() && current != m_registers.end() + && (prev->second & OPL::KEYON_BIT) == (value & OPL::KEYON_BIT) + && (prev->second & OPL::KEYON_BIT) != (current->second & OPL::KEYON_BIT)) + { + // Key-On was toggled off and on again within a single frame, which retriggers the note. This needs to be recorded. + m_keyOnToggle.insert(c); + m_prevRegisters.erase(reg); + } + } + + const auto voiceReg = OPL::StripVoiceFromRegister(reg); + MPT_ASSERT(voiceReg <= uint8_max); + + std::pair key{c, uint16(0)}; + if(m_playState.Chn[c].nMasterChn) + key = {static_cast(m_playState.Chn[c].nMasterChn - 1), m_playState.Chn[c].nnaGeneration}; + m_chnRegisters[key][static_cast(voiceReg)] = value; + } else + { + m_globalRegisters.insert(reg); + } + m_registers[reg] = value; + } + + void MoveChannel(CHANNELINDEX from, CHANNELINDEX to) override + { + m_prevChnRegisters[{from, m_playState.Chn[to].nnaGeneration}] = std::move(m_prevChnRegisters[{from, uint16(0)}]); + } + + std::map m_registers; // All registers that have been updated in the current tick + std::map m_prevRegisters; // Previous state of all registers that have been set so far + std::set m_keyOnToggle; // Set of channels on which a key-on -> key-off -> key-on transition was made on this tick + std::set m_globalRegisters; // Set of all global registers that have been modified in the current tick + + std::map, std::map> m_chnRegisters; // Maps [source channel, age] to [voice register, voice value] + std::map, std::map> m_prevChnRegisters; // Previous state of registers for this channel + + const PlayState &m_playState; +}; + + +PlaybackTest::PlaybackTest(FileReader file) noexcept(false) +{ + Deserialize(file); +} + +PlaybackTest::PlaybackTest(PlaybackTestData &&testData) + : m_testData{std::make_unique(std::move(testData))} +{ +} + +PlaybackTest::PlaybackTest(PlaybackTest &&other) noexcept + : m_testData{std::move(other.m_testData)} +{ +} + +PlaybackTest::~PlaybackTest() +{ + // This destructor is put here so that we can forward-declare the PlaybackTestData class. +} + +PlaybackTest& PlaybackTest::operator=(PlaybackTest &&other) noexcept +{ + m_testData = std::move(other.m_testData); + return *this; +} + + +void PlaybackTest::Deserialize(FileReader file) noexcept(false) +{ + + file.Rewind(); + + m_testData = std::make_unique(); + + auto &header = m_testData->header; + file.Read(header); + if(memcmp(header.magic, TestDataHeader::TestDataHeaderMagic, sizeof(header.magic))) + throw std::runtime_error{"Invalid test data file"}; + if(header.fileVersion != 0) + throw std::runtime_error{"Invalid test data file version"}; + if(!Resampling::IsKnownMode(header.srcMode)) + throw std::runtime_error{"Invalid test data: SRC mode"}; + if(header.outputChannels <= 0) + throw std::runtime_error{"Invalid test data: number of output channels"}; + if(header.mixerChannels <= 0) + throw std::runtime_error{"Invalid test data: number of mixer channels"}; + if(header.mixingFreq <= 0) + throw std::runtime_error{"Invalid test data: mixing frequency"}; + if(header.globalVolumeUnity <= 0) + throw std::runtime_error{"Invalid test data: global volume unity"}; + if(header.channelVolumeUnity <= 0) + throw std::runtime_error{"Invalid test data: channel volume unity"}; + + file.ReadVector(m_testData->sampleDataHashes, header.numSamples); + + while(!file.EndOfFile()) + { + auto &row = m_testData->rows.emplace_back(); + + file.Read(row.header); + file.ReadVector(row.channels, row.header.activeChannels); + uint32 oplDataSize = 0; + file.ReadVarInt(oplDataSize); + file.ReadVector(row.oplRegisters, oplDataSize); + } +} + + +void PlaybackTest::Serialize(std::ostream &output) const noexcept(false) +{ + mpt::IO::Write(output, m_testData->header); + mpt::IO::Write(output, m_testData->sampleDataHashes); + for(const auto &row : m_testData->rows) + { + mpt::IO::Write(output, row.header); + mpt::IO::Write(output, row.channels); + mpt::IO::WriteVarInt(output, row.oplRegisters.size()); + mpt::IO::Write(output, row.oplRegisters); + } +} + + +void PlaybackTest::ToTSV(std::ostream &output) const noexcept(false) +{ + const auto &header = m_testData->header; + const auto positionPrecision = 1.0 / (int64(1) << header.positionPrecisionBits); + const auto filterPrecision = 1.0 / (int64(1) << header.filterPrecisionBits); + + static constexpr int floatPrecision = 10; + const auto floatFormat = mpt::format_simple_spec().SetPrecision(floatPrecision); + output << std::setprecision(floatPrecision); + + output << "OpenMPT Test Data version " << mpt::afmt::val(header.fileVersion) << "\n" + "Created by OpenMPT " << mpt::ToCharset(mpt::Charset::UTF8, Version::Current().ToUString()) << "\n" + "isAmiga\t" << mpt::afmt::val(header.isAmiga) << "\n" + "srcMode\t" << mpt::afmt::val(header.srcMode) << "\n" + "outputChannels\t" << mpt::afmt::val(header.outputChannels) << "\n" + "mixerChannels\t" << header.mixerChannels << "\n" + "synthVolume\t" << header.synthVolume << "\n" + "mixingFreq\t" << header.mixingFreq << "\n" + "\nSample data hashes:\n"; + + for(SAMPLEINDEX smp = 1; smp <= header.numSamples; smp++) + output << mpt::ToCharset(mpt::Charset::UTF8, mpt::encode_hex(mpt::as_raw_memory(m_testData->sampleDataHashes[smp - 1]))) << "\t" << smp << "\n"; + + output << "\nChannel data:\n" + "index\torder\trow\ttick\tglobalVolume\ttickLength\tchannel\tsample\tleftVol\trightVol\tsurround\tspeed\tposition\tfilterType\tfilterA0\tfilterB0\tfilterB1\tsrcMode\toplRegisters\n"; + + uint32 tick = 0, rowIndex = 0; + for(const auto &row : m_testData->rows) + { + if(row.header.isFirstTick) + tick = 0; + + const auto headerFormat = MPT_AFORMAT("{}\t{}\t{}\t{}\t{}\t{}\t"); + const auto channelHeaderFirst = headerFormat(rowIndex, row.header.order, row.header.row, tick, row.header.globalVolume, Util::muldivr_unsigned(row.header.tickLength, 100000, header.mixingFreq)); + const auto channelHeaderFollow = headerFormat("", "", "", "", "", ""); + tick++; + rowIndex++; + + bool first = true; + if(!row.oplRegisters.empty()) + { + output << channelHeaderFirst << "\t\t\t\t\t\t\t\t\t\t\t\t" << OPLPlaybackLog::Format(row.oplRegisters); + output << "\n"; + first = false; + } else if(row.channels.empty()) + { + output << channelHeaderFirst << "--\n"; + } + for(const auto &channel : row.channels) + { + output << (first ? channelHeaderFirst : channelHeaderFollow); + first = false; + + const char *filterType; + switch(channel.flags & TestDataChannel::kFilterMask) + { + case TestDataChannel::kFilterNone: filterType = "--"; break; + case TestDataChannel::kFilterLowPass: filterType = "LP"; break; + case TestDataChannel::kFilterHighPass: filterType = "HP"; break; + default: throw std::runtime_error{"Unknown filter type in test data"}; + } + + output << std::abs(channel.channel) << (channel.channel < 0 ? "[NNA]" : "") << "\t" + << channel.sample << "\t" + << channel.leftVol << "\t" + << channel.rightVol << "\t" + << ((channel.flags & TestDataChannel::kSurround) ? "yes" : "no") << "\t" + << (static_cast(channel.increment) * positionPrecision * header.mixingFreq) << "\t" + << ((channel.flags & TestDataChannel::kOPL) ? "OPL" : mpt::afmt::fmt(static_cast(channel.position) * positionPrecision, floatFormat)) << "\t" + << filterType << "\t" + << channel.filterA0 * filterPrecision << "\t" + << channel.filterB0 * filterPrecision << "\t" + << channel.filterB1 * filterPrecision << "\t" + << mpt::afmt::val(channel.srcMode) << "\n"; + } + } +} + + +PlaybackTestSettings PlaybackTest::GetSettings() const noexcept +{ + return m_testData->GetSettings(); +} + + + +static bool FuzzyEquals(const double left, const double right, const double epsilon) noexcept +{ + return std::abs(left - right) <= std::min(std::abs(left), std::abs(right)) * epsilon; +} + +#define MPT_LOG_TEST(propName, left, right) \ + errors.push_back(mpt::ToUnicode(mpt::Charset::UTF8, MPT_AFORMAT("{} differs: {} vs {}") \ + (propName, left, right))); +#define MPT_LOG_TEST_WITH_ROW(propName, left, right) \ + errors.push_back(mpt::ToUnicode(mpt::Charset::UTF8, MPT_AFORMAT("{} differs in test row {} (order {}, row {}, tick {}): {} vs {}") \ + (propName, row, lRow.header.order, lRow.header.row, lTick, left, right))); +#define MPT_LOG_TEST_WITH_ROW_CHN(propName, left, right) \ + errors.push_back(mpt::ToUnicode(mpt::Charset::UTF8, MPT_AFORMAT("{} differs in test row {} (order {}, row {}, tick {}), channel {}: {} vs {}") \ + (propName, row, lRow.header.order, lRow.header.row, lTick, chn, left, right))); + +std::vector PlaybackTest::Compare(const PlaybackTest &lhs, const PlaybackTest &rhs) +{ + return lhs.Compare(rhs); +} + +std::vector PlaybackTest::Compare(const PlaybackTest &otherTest) const +{ + const auto &other = *otherTest.m_testData; + const auto &header = m_testData->header; + + std::vector errors; + + if(header.mixingFreq != other.header.mixingFreq) + return {MPT_UFORMAT("Mixing frequency differs ({} vs {}), not even going to try to compare them")(header.mixingFreq, other.header.mixingFreq)}; + if(header.outputChannels != other.header.outputChannels) + return {MPT_UFORMAT("Output channel layout differs ({} vs {} channels), not even going to try to compare them")(header.outputChannels, other.header.outputChannels)}; + if(header.mixerChannels != other.header.mixerChannels) + errors.push_back(MPT_UFORMAT("Mixer channel limit differs ({} vs {} channels), results may differ in case of channel starvation")(header.mixerChannels, other.header.mixerChannels)); + if(header.srcMode != other.header.srcMode) + errors.push_back(MPT_UFORMAT("Default SRC mode differs ({} vs {} channels), results may differ")(header.srcMode, other.header.srcMode)); + + const auto lPositionPrecision = 1.0 / (int64(1) << header.positionPrecisionBits); + const auto rPositionPrecision = 1.0 / (int64(1) << other.header.positionPrecisionBits); + const auto lFilterPrecision = 1.0 / (int64(1) << header.filterPrecisionBits); + const auto rFilterPrecision = 1.0 / (int64(1) << other.header.filterPrecisionBits); + const auto lGlobalVolumeScale = 1.0 / header.globalVolumeUnity; + const auto rGlobalVolumeScale = 1.0 / other.header.globalVolumeUnity; + const auto lChannelVolumeScale = 1.0 / header.channelVolumeUnity; + const auto rChannelVolumeScale = 1.0 / other.header.channelVolumeUnity; + const auto epsilon = 0.000001; + + if(header.isAmiga != other.header.isAmiga) + MPT_LOG_TEST("Amiga mode", header.isAmiga, other.header.isAmiga); + if(header.synthVolume != other.header.synthVolume) + MPT_LOG_TEST("Synth volume", header.synthVolume, other.header.synthVolume); + + if(m_testData->sampleDataHashes.size() != other.sampleDataHashes.size()) + MPT_LOG_TEST("Number of sample slots", m_testData->sampleDataHashes.size(), other.sampleDataHashes.size()); + for(size_t smp = 0; smp < std::min(m_testData->sampleDataHashes.size(), other.sampleDataHashes.size()); smp++) + { + if(m_testData->sampleDataHashes[smp] != other.sampleDataHashes[smp]) + errors.push_back(MPT_UFORMAT("Sample hash in slot {} differs: {} vs {}")(smp + 1, mpt::encode_hex(mpt::as_raw_memory(m_testData->sampleDataHashes[smp])), mpt::encode_hex(mpt::as_raw_memory(other.sampleDataHashes[smp])))); + } + + uint64 lDuration = 0, rDuration = 0; + uint32 lTick = 0, rTick = 0; + + if(m_testData->rows.size() != other.rows.size()) + MPT_LOG_TEST("Number of test rows", m_testData->rows.size(), other.rows.size()); + for(size_t row = 0; row < std::min(m_testData->rows.size(), other.rows.size()); row++) + { + const auto &lRow = m_testData->rows[row], &rRow = other.rows[row]; + + if(lRow.header.isFirstTick) + lTick = 0; + if(rRow.header.isFirstTick) + rTick = 0; + + if(lRow.header.order != rRow.header.order + || lRow.header.row != rRow.header.row + || lRow.header.isFirstTick != rRow.header.isFirstTick) + errors.push_back(MPT_UFORMAT("Play position differs in test row {} (order {}, row {}, tick {} vs order {}, row {}, tick {})")(row, lRow.header.order, lRow.header.row, lTick, rRow.header.order, rRow.header.row, rTick)); + + if(const auto l = lRow.header.globalVolume * lGlobalVolumeScale, r = rRow.header.globalVolume * rGlobalVolumeScale; !FuzzyEquals(l, r, epsilon)) + MPT_LOG_TEST_WITH_ROW("Global volume", l, r); + + if(std::abs(static_cast(lRow.header.tickLength) - static_cast(rRow.header.tickLength)) > 1) + { + const auto lTickLength = Util::muldivr_unsigned(lRow.header.tickLength, 1'000'000, header.mixingFreq); + const auto rTickLength = Util::muldivr_unsigned(rRow.header.tickLength, 1'000'000, other.header.mixingFreq); + MPT_LOG_TEST_WITH_ROW("Tick length", mpt::afmt::val(lTickLength) + "us", mpt::afmt::val(rTickLength) + "us"); + } + + if(lRow.oplRegisters != rRow.oplRegisters) + { + MPT_LOG_TEST_WITH_ROW("OPL register log", OPLPlaybackLog::Format(lRow.oplRegisters), OPLPlaybackLog::Format(rRow.oplRegisters)) + } + + if(lRow.channels.size() != rRow.channels.size()) + MPT_LOG_TEST_WITH_ROW("Number of active voices", lRow.channels.size(), rRow.channels.size()); + for(size_t chn = 0; chn < std::min(lRow.channels.size(), rRow.channels.size()); chn++) + { + const auto &lChn = lRow.channels[chn], &rChn = rRow.channels[chn]; + + if(lChn.channel != rChn.channel) + MPT_LOG_TEST_WITH_ROW_CHN("Source channel", lChn.channel, rChn.channel); + if(lChn.flags != rChn.flags) + MPT_LOG_TEST_WITH_ROW_CHN("Flags", lChn.flags, rChn.flags); + if(lChn.srcMode != rChn.srcMode) + MPT_LOG_TEST_WITH_ROW_CHN("SRC mode", lChn.srcMode, rChn.srcMode); + if(lChn.sample != rChn.sample) + MPT_LOG_TEST_WITH_ROW_CHN("Sample", lChn.sample, rChn.sample); + if(const auto l = lChn.leftVol * lChannelVolumeScale, r = rChn.leftVol * rChannelVolumeScale; !FuzzyEquals(l, r, epsilon)) + MPT_LOG_TEST_WITH_ROW_CHN("Left volume", l, r); + if(const auto l = lChn.rightVol * lChannelVolumeScale, r = rChn.rightVol * rChannelVolumeScale; !FuzzyEquals(l, r, epsilon)) + MPT_LOG_TEST_WITH_ROW_CHN("Right volume", l, r); + if(const auto l = static_cast(lChn.increment) * lPositionPrecision * header.mixingFreq, r = static_cast(rChn.increment) * rPositionPrecision * other.header.mixingFreq; !FuzzyEquals(l, r, epsilon)) + MPT_LOG_TEST_WITH_ROW_CHN("Speed", l, r); + if(const auto l = static_cast(lChn.position) * lPositionPrecision, r = static_cast(rChn.position) * rPositionPrecision; !FuzzyEquals(l, r, epsilon)) + MPT_LOG_TEST_WITH_ROW_CHN("Position", l, r); + if(const auto l = lChn.filterA0 * lFilterPrecision, r = rChn.filterA0 * rFilterPrecision; !FuzzyEquals(l, r, epsilon)) + MPT_LOG_TEST_WITH_ROW_CHN("Filter A0", l, r); + if(const auto l = lChn.filterB0 * lFilterPrecision, r = rChn.filterB0 * rFilterPrecision; !FuzzyEquals(l, r, epsilon)) + MPT_LOG_TEST_WITH_ROW_CHN("Filter B0", l, r); + if(const auto l = lChn.filterB1 * lFilterPrecision, r = rChn.filterB1 * rFilterPrecision; !FuzzyEquals(l, r, epsilon)) + MPT_LOG_TEST_WITH_ROW_CHN("Filter B1", l, r); + } + + lDuration += lRow.header.tickLength; + rDuration += rRow.header.tickLength; + lTick++; + rTick++; + } + + if(const auto l = static_cast(lDuration) / header.mixingFreq, r = static_cast(rDuration) / other.header.mixingFreq; !FuzzyEquals(l, r, epsilon)) + MPT_LOG_TEST("Total duration", mpt::afmt::val(l) + "s", mpt::afmt::val(r) + "s"); + + return errors; +} + + +PlaybackTest CSoundFile::CreatePlaybackTest(PlaybackTestSettings settings) +{ + settings.Sanitize(); + + PlaybackTestData testData{}; + + m_bIsRendering = true; + const auto origResamplerSettings = m_Resampler.m_Settings; + m_Resampler.m_Settings.SrcMode = settings.srcMode; + m_Resampler.m_Settings.emulateAmiga = m_SongFlags[SONG_ISAMIGA] ? Resampling::AmigaFilter::A1200 : Resampling::AmigaFilter::Off; + const auto origMixerSettings = m_MixerSettings; + MixerSettings testSettings; + testSettings.gdwMixingFreq = settings.mixingFreq; + testSettings.gnChannels = settings.outputChannels; + testSettings.m_nMaxMixChannels = settings.mixerChannels; + testSettings.VolumeRampUpMicroseconds = 0; + testSettings.VolumeRampDownMicroseconds = 0; + SetMixerSettings(testSettings); + + const auto origRepeatCount = GetRepeatCount(); + SetRepeatCount(0); + + auto origPRNG = std::move(m_PRNG); + mpt::deterministic_random_device rd; + m_PRNG = mpt::make_prng(rd); + + auto origPlayState = std::make_unique(std::move(m_PlayState)); + mpt::reconstruct(m_PlayState); + ResetPlayPos(); + + auto origOPL = std::move(m_opl); + OPLPlaybackLog oplLogger{m_PlayState}; + + auto &header = testData.header; + memcpy(header.magic, TestDataHeader::TestDataHeaderMagic, sizeof(header.magic)); + header.fileVersion = 0; + header.isAmiga = m_SongFlags[SONG_ISAMIGA] ? 1 : 0; + header.positionPrecisionBits = static_cast(mpt::bit_width(static_cast::type>(SamplePosition{1, 0}.GetRaw())) - 1); + header.filterPrecisionBits = MIXING_FILTER_PRECISION; + header.srcMode = m_Resampler.m_Settings.SrcMode; + header.outputChannels = static_cast(m_MixerSettings.gnChannels); + header.mixerChannels = static_cast(m_MixerSettings.m_nMaxMixChannels); + header.synthVolume = static_cast(m_nVSTiVolume); + header.globalVolumeUnity = MAX_GLOBAL_VOLUME; + header.channelVolumeUnity = 16384; + header.mixingFreq = m_MixerSettings.gdwMixingFreq; + header.mptVersion = Version::Current().GetRawVersion(); + header.numSamples = GetNumSamples(); + + testData.sampleDataHashes.reserve(GetNumSamples()); + for(SAMPLEINDEX smp = 1; smp <= GetNumSamples(); smp++) + { + SampleDataHashAlgorithm hasher; + const ModSample &sample = Samples[smp]; + if(sample.uFlags[CHN_ADLIB]) + { + hasher.process(mpt::as_raw_memory(sample.adlib)); + } else + { + std::ostringstream ss{std::ios::out | std::ios::binary}; + SampleIO{sample.uFlags[CHN_16BIT] ? SampleIO::_16bit : SampleIO::_8bit, sample.uFlags[CHN_STEREO] ? SampleIO::stereoInterleaved : SampleIO::mono, SampleIO::littleEndian, SampleIO::signedPCM} + .WriteSample(ss, sample); + const auto s = std::move(ss).str(); + hasher.process(mpt::byte_cast(mpt::as_span(s))); + } + SampleDataHash result; + result.set(hasher.result()); + testData.sampleDataHashes.push_back(result); + } + + for(const auto &song : GetAllSubSongs()) + { + oplLogger.Reset(); + m_opl = std::make_unique(oplLogger); + ResetPlayPos(); + GetLength(eAdjust, GetLengthTarget(song.startOrder, song.startRow).StartPos(song.sequence, 0, 0)); + m_PlayState.m_flags.reset(); + + while(ReadOneTick()) + { + auto &row = testData.rows.emplace_back(); + row.header.order = m_PlayState.m_nCurrentOrder; + row.header.row = m_PlayState.m_nRow; + row.header.globalVolume = m_PlayState.m_nGlobalVolume; + row.header.tickLength = m_PlayState.m_nSamplesPerTick; + row.header.isFirstTick = m_PlayState.m_nTickCount ? 0 : 1; + row.header.activeChannels = static_cast(std::count_if(std::begin(m_PlayState.Chn), std::end(m_PlayState.Chn), [](const auto &chn) { return chn.nLength != 0; })); + + row.channels.reserve(row.header.activeChannels); + for(CHANNELINDEX chn = 0; chn < MAX_CHANNELS; chn++) + { + if(!m_PlayState.Chn[chn].nLength) + continue; + + auto &channel = m_PlayState.Chn[chn]; + auto &channelData = row.channels.emplace_back(); + channelData.channel = static_cast(channel.nMasterChn ? -static_cast(channel.nMasterChn) : (chn + 1)); + channelData.nnaAge = channel.nnaGeneration; + if(channel.dwFlags[CHN_SURROUND]) + channelData.flags |= TestDataChannel::kSurround; + if(channel.dwFlags[CHN_ADLIB]) + channelData.flags |= TestDataChannel::kOPL; + if(channel.dwFlags[CHN_AMIGAFILTER]) + channelData.flags |= TestDataChannel::kAmigaFilter; + channelData.srcMode = channel.resamplingMode; + channelData.sample = static_cast(std::distance(&static_cast(Samples[0]), channel.pModSample)); + channelData.leftVol = channel.newLeftVol; + channelData.rightVol = channel.newRightVol; + channelData.increment = channel.increment.GetRaw(); + channelData.position = channel.position.GetRaw(); + if(channel.dwFlags[CHN_FILTER]) + channelData.flags |= (channel.nFilter_HP ? TestDataChannel::kFilterHighPass : TestDataChannel::kFilterLowPass); + channelData.filterA0 = channel.nFilter_A0; + channelData.filterB0 = channel.nFilter_B0; + channelData.filterB1 = channel.nFilter_B1; + } + std::sort(row.channels.begin(), row.channels.end()); + + row.oplRegisters = oplLogger.DumpRegisters(); + } + } + + m_opl = std::move(origOPL); + m_PlayState = std::move(*origPlayState); + m_PRNG = std::move(origPRNG); + SetRepeatCount(origRepeatCount); + SetMixerSettings(origMixerSettings); + m_Resampler.m_Settings = origResamplerSettings; + m_bIsRendering = false; + + return PlaybackTest{std::move(testData)}; +} + + +#else // !MPT_ENABLE_PLAYBACK_TRACE + + +MPT_MSVC_WORKAROUND_LNK4221(PlaybackTest) + + +#endif // MPT_ENABLE_PLAYBACK_TRACE + + +OPENMPT_NAMESPACE_END diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/PlaybackTest.h b/Frameworks/OpenMPT/OpenMPT/soundlib/PlaybackTest.h new file mode 100644 index 000000000..a72900e81 --- /dev/null +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/PlaybackTest.h @@ -0,0 +1,60 @@ +/* + * PlaybackTest.h + * -------------- + * Purpose: Tools for verifying correct playback of modules + * Notes : (currently none) + * Authors: OpenMPT Devs + * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. + */ + + +#pragma once + +#include "openmpt/all/BuildSettings.hpp" + +#if defined(MPT_ENABLE_PLAYBACK_TRACE) + +#include "../common/FileReaderFwd.h" + +#include + +#endif // MPT_ENABLE_PLAYBACK_TRACE + +OPENMPT_NAMESPACE_BEGIN + +#if defined(MPT_ENABLE_PLAYBACK_TRACE) + +struct PlaybackTestData; +struct PlaybackTestSettings; +class CSoundFile; + +class PlaybackTest +{ +public: + explicit PlaybackTest(FileReader file) noexcept(false); + explicit PlaybackTest(PlaybackTestData &&testData); + PlaybackTest(PlaybackTest &&other) noexcept; + PlaybackTest(const PlaybackTest &) = delete; + ~PlaybackTest(); + + PlaybackTest& operator=(PlaybackTest &&other) noexcept; + PlaybackTest& operator=(const PlaybackTest &) = delete; + + void Deserialize(FileReader file) noexcept(false); + void Serialize(std::ostream &output) const noexcept(false); + void ToTSV(std::ostream &output) const noexcept(false); + + PlaybackTestSettings GetSettings() const noexcept; + + static std::vector Compare(const PlaybackTest &lhs, const PlaybackTest &rhs); + +private: + std::vector Compare(const PlaybackTest &otherTest) const; + +private: + std::unique_ptr m_testData; +}; + +#endif // MPT_ENABLE_PLAYBACK_TRACE + +OPENMPT_NAMESPACE_END diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/S3MTools.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/S3MTools.cpp index 27be50867..79001ca98 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/S3MTools.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/S3MTools.cpp @@ -9,8 +9,8 @@ #include "stdafx.h" -#include "Loaders.h" #include "S3MTools.h" +#include "Loaders.h" #include "../common/mptStringBuffer.h" diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/S3MTools.h b/Frameworks/OpenMPT/OpenMPT/soundlib/S3MTools.h index 1d9da988b..c0ee1c9e4 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/S3MTools.h +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/S3MTools.h @@ -96,6 +96,17 @@ struct S3MFileHeader uint16le reserved4; uint16le special; // Pointer to special custom data (unused) uint8le channels[32]; // Channel setup + + uint8 GetNumChannels() const + { + uint8 numChannels = 4; + for(uint8 i = 0; i < 32; i++) + { + if(channels[i] != 0xFF) + numChannels = i + 1; + } + return numChannels; + } }; MPT_BINARY_STRUCT(S3MFileHeader, 96) diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/SampleFormatBRR.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/SampleFormatBRR.cpp index 9f448736e..d76945111 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/SampleFormatBRR.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/SampleFormatBRR.cpp @@ -87,7 +87,14 @@ bool CSoundFile::ReadBRRSample(SAMPLEINDEX sample, FileReader &file) if(isLast != file.EndOfFile()) return false; if(!first && enableLoop != isLoop) - return false; + { + if(!hasLoopInfo) + return false; + // In some files, the loop flag is only set for the blocks within the loop (except for the first block?) + const bool inLoop = file.GetPosition() > loopStart + 11u; + if(enableLoop != inLoop) + return false; + } // While a range of 13 is technically invalid as well, it can be found in the wild. if(range > 13) return false; diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/SampleFormatFLAC.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/SampleFormatFLAC.cpp index b296d35bb..afb526c16 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/SampleFormatFLAC.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/SampleFormatFLAC.cpp @@ -14,6 +14,7 @@ #include "../mptrack/TrackerSettings.h" #endif //MODPLUG_TRACKER #ifndef MODPLUG_NO_FILESAVE +#include "mpt/io_file/fstream.hpp" #include "../common/mptFileIO.h" #endif #include "../common/misc_util.h" @@ -33,6 +34,10 @@ #include "mpt/parse/parse.hpp" //#include "mpt/crc/crc.hpp" #include "OggStream.h" +#include +#if MPT_PLATFORM_MULTITHREADED && !defined(MPT_COMPILER_QUIRK_NO_STDCPP_THREADS) +#include +#endif #ifdef MPT_WITH_OGG #if MPT_COMPILER_CLANG #pragma clang diagnostic push @@ -79,7 +84,7 @@ struct FLACDecoder FileReader &file = static_cast(client_data)->m_file; if(*bytes > 0) { - FileReader::off_t readBytes = *bytes; + FileReader::pos_type readBytes = *bytes; LimitMax(readBytes, file.BytesLeft()); file.ReadRaw(mpt::byte_cast(mpt::span(buffer, readBytes))); *bytes = readBytes; @@ -96,7 +101,7 @@ struct FLACDecoder static FLAC__StreamDecoderSeekStatus seek_cb(const FLAC__StreamDecoder *, FLAC__uint64 absolute_byte_offset, void *client_data) { FileReader &file = static_cast(client_data)->m_file; - if(!file.Seek(static_cast(absolute_byte_offset))) + if(!file.Seek(static_cast(absolute_byte_offset))) return FLAC__STREAM_DECODER_SEEK_STATUS_ERROR; else return FLAC__STREAM_DECODER_SEEK_STATUS_OK; @@ -491,7 +496,7 @@ struct FLAC__StreamEncoder_RAII static FLAC__StreamEncoderWriteStatus StreamEncoderWriteCallback(const FLAC__StreamEncoder *encoder, const FLAC__byte buffer[], size_t bytes, unsigned samples, unsigned current_frame, void *client_data) { - mpt::ofstream & file = *mpt::void_ptr(client_data); + mpt::IO::ofstream & file = *mpt::void_ptr(client_data); MPT_UNUSED_VARIABLE(encoder); MPT_UNUSED_VARIABLE(samples); MPT_UNUSED_VARIABLE(current_frame); @@ -503,7 +508,7 @@ struct FLAC__StreamEncoder_RAII } static FLAC__StreamEncoderSeekStatus StreamEncoderSeekCallback(const FLAC__StreamEncoder *encoder, FLAC__uint64 absolute_byte_offset, void *client_data) { - mpt::ofstream & file = *mpt::void_ptr(client_data); + mpt::IO::ofstream & file = *mpt::void_ptr(client_data); MPT_UNUSED_VARIABLE(encoder); if(!mpt::in_range(absolute_byte_offset)) { @@ -517,7 +522,7 @@ struct FLAC__StreamEncoder_RAII } static FLAC__StreamEncoderTellStatus StreamEncoderTellCallback(const FLAC__StreamEncoder *encoder, FLAC__uint64 *absolute_byte_offset, void *client_data) { - mpt::ofstream & file = *mpt::void_ptr(client_data); + mpt::IO::ofstream & file = *mpt::void_ptr(client_data); MPT_UNUSED_VARIABLE(encoder); mpt::IO::Offset pos = mpt::IO::TellWrite(file); if(pos < 0) @@ -639,6 +644,11 @@ bool CSoundFile::SaveFLACSample(SAMPLEINDEX nSample, std::ostream &f) const { chunk.loops[chunk.info.numLoops++].ConvertToWAV(sample.nLoopStart, sample.nLoopEnd, sample.uFlags[CHN_PINGPONGLOOP]); chunk.header.length += sizeof(WAVSampleLoop); + } else if(sample.uFlags[CHN_SUSTAINLOOP]) + { + // Invent zero-length loop to distinguish sustain loop from normal loop + chunk.loops[chunk.info.numLoops++].ConvertToWAV(0, 0, false); + chunk.header.length += sizeof(WAVSampleLoop); } const uint32 length = sizeof(RIFFChunk) + chunk.header.length; @@ -664,7 +674,7 @@ bool CSoundFile::SaveFLACSample(SAMPLEINDEX nSample, std::ostream &f) const for(uint32 i = 0; i < std::size(sample.cues); i++) { - chunk.cues[i].ConvertToWAV(i, sample.cues[i]); + chunk.cues[i] = ConvertToWAVCuePoint(i, sample.cues[i]); } const uint32 length = sizeof(RIFFChunk) + chunk.header.length; @@ -688,6 +698,15 @@ bool CSoundFile::SaveFLACSample(SAMPLEINDEX nSample, std::ostream &f) const FLAC__stream_encoder_set_metadata(encoder, metadata.data(), numBlocks); #ifdef MODPLUG_TRACKER FLAC__stream_encoder_set_compression_level(encoder, TrackerSettings::Instance().m_FLACCompressionLevel); +#if (FLAC_API_VERSION_CURRENT >= 14) && MPT_PLATFORM_MULTITHREADED && !defined(MPT_COMPILER_QUIRK_NO_STDCPP_THREADS) + uint32 threads = TrackerSettings::Instance().m_FLACMultithreading ? static_cast(std::max(std::thread::hardware_concurrency(), static_cast(1))) : static_cast(1); + // Work-around . + //FLAC__stream_encoder_set_num_threads(encoder, threads); + while((FLAC__stream_encoder_set_num_threads(encoder, threads) == FLAC__STREAM_ENCODER_SET_NUM_THREADS_TOO_MANY_THREADS) && (threads > 1)) + { + threads = ((threads > 256) ? 256 : (threads - 1)); + } +#endif #endif // MODPLUG_TRACKER bool success = FLAC__stream_encoder_init_stream(encoder, &FLAC__StreamEncoder_RAII::StreamEncoderWriteCallback, &FLAC__StreamEncoder_RAII::StreamEncoderSeekCallback, &FLAC__StreamEncoder_RAII::StreamEncoderTellCallback, nullptr, &encoder.f) == FLAC__STREAM_ENCODER_INIT_STATUS_OK; diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/SampleFormatMP3.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/SampleFormatMP3.cpp index aed530749..a77330a7f 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/SampleFormatMP3.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/SampleFormatMP3.cpp @@ -54,6 +54,7 @@ #endif #endif #include +#define MPT_USE_MPG123_PORTABLE_API 1 #endif @@ -66,6 +67,8 @@ OPENMPT_NAMESPACE_BEGIN #if defined(MPT_WITH_MPG123) +#if (MPG123_API_VERSION < 48) || !MPT_USE_MPG123_PORTABLE_API + using mpg123_off_t = off_t; using mpg123_size_t = size_t; @@ -79,6 +82,8 @@ using mpg123_ssize_t = ptrdiff_t; using mpg123_ssize_t = ssize_t; #endif +#endif + class ComponentMPG123 : public ComponentBuiltin { @@ -86,6 +91,47 @@ class ComponentMPG123 public: +#if (MPG123_API_VERSION >= 48) && MPT_USE_MPG123_PORTABLE_API + static int FileReaderRead(void *fp, void *buf, size_t count, size_t *returned) + { + FileReader &file = *static_cast(fp); + std::size_t readBytes = std::min(count, mpt::saturate_cast(file.BytesLeft())); + file.ReadRaw(mpt::span(mpt::void_cast(buf), readBytes)); + if(!returned) + { + return -1; + } + *returned = readBytes; + return 0; + } + static int64_t FileReaderSeek(void *fp, int64_t offset, int whence) + { + FileReader &file = *static_cast(fp); + if(whence == SEEK_CUR) + { + if(!mpt::in_range(file.GetPosition() + offset)) + { + return -1; + } + file.Seek(static_cast(file.GetPosition() + offset)); + } else if(whence == SEEK_END) + { + if(!mpt::in_range(file.GetLength() + offset)) + { + return -1; + } + file.Seek(static_cast(file.GetLength() + offset)); + } else + { + if(!mpt::in_range(offset)) + { + return -1; + } + file.Seek(static_cast(offset)); + } + return static_cast(file.GetPosition()); + } +#else static mpg123_ssize_t FileReaderRead(void *fp, void *buf, mpg123_size_t count) { FileReader &file = *static_cast(fp); @@ -96,7 +142,7 @@ public: static mpg123_off_t FileReaderLSeek(void *fp, mpg123_off_t offset, int whence) { FileReader &file = *static_cast(fp); - FileReader::off_t oldpos = file.GetPosition(); + FileReader::pos_type oldpos = file.GetPosition(); if(whence == SEEK_CUR) file.Seek(file.GetPosition() + offset); else if(whence == SEEK_END) file.Seek(file.GetLength() + offset); else file.Seek(offset); @@ -107,6 +153,7 @@ public: } return static_cast(file.GetPosition()); } +#endif public: ComponentMPG123() @@ -260,8 +307,13 @@ bool CSoundFile::ReadMP3Sample(SAMPLEINDEX sample, FileReader &file, bool raw, b if(!raw) { +#if (MPG123_API_VERSION >= 48) && MPT_USE_MPG123_PORTABLE_API + int64_t length_raw = 0; + int64_t length_hdr = 0; +#else mpg123_off_t length_raw = 0; mpg123_off_t length_hdr = 0; +#endif // libmpg123 provides no way to determine whether it parsed ID3V2 or VBR tags. // Thus, we use a pre-scan with those disabled and compare the resulting length. @@ -303,10 +355,17 @@ bool CSoundFile::ReadMP3Sample(SAMPLEINDEX sample, FileReader &file, bool raw, b { return false; } +#if (MPG123_API_VERSION >= 48) && MPT_USE_MPG123_PORTABLE_API + if(mpg123_reader64(mh, ComponentMPG123::FileReaderRead, ComponentMPG123::FileReaderSeek, 0)) + { + return false; + } +#else if(mpg123_replace_reader_handle(mh, ComponentMPG123::FileReaderRead, ComponentMPG123::FileReaderLSeek, 0)) { return false; } +#endif if(mpg123_open_handle(mh, &file)) { return false; @@ -344,7 +403,11 @@ bool CSoundFile::ReadMP3Sample(SAMPLEINDEX sample, FileReader &file, bool raw, b { return false; } +#if (MPG123_API_VERSION >= 48) && MPT_USE_MPG123_PORTABLE_API + length_raw = mpg123_length64(mh); +#else length_raw = mpg123_length(mh); +#endif } { @@ -382,10 +445,17 @@ bool CSoundFile::ReadMP3Sample(SAMPLEINDEX sample, FileReader &file, bool raw, b { return false; } +#if (MPG123_API_VERSION >= 48) && MPT_USE_MPG123_PORTABLE_API + if(mpg123_reader64(mh, ComponentMPG123::FileReaderRead, ComponentMPG123::FileReaderSeek, 0)) + { + return false; + } +#else if(mpg123_replace_reader_handle(mh, ComponentMPG123::FileReaderRead, ComponentMPG123::FileReaderLSeek, 0)) { return false; } +#endif if(mpg123_open_handle(mh, &file)) { return false; @@ -423,7 +493,11 @@ bool CSoundFile::ReadMP3Sample(SAMPLEINDEX sample, FileReader &file, bool raw, b { return false; } +#if (MPG123_API_VERSION >= 48) && MPT_USE_MPG123_PORTABLE_API + length_hdr = mpg123_length64(mh); +#else length_hdr = mpg123_length(mh); +#endif } hasLameXingVbriHeader = (length_raw != length_hdr); @@ -465,10 +539,17 @@ bool CSoundFile::ReadMP3Sample(SAMPLEINDEX sample, FileReader &file, bool raw, b { return false; } +#if (MPG123_API_VERSION >= 48) && MPT_USE_MPG123_PORTABLE_API + if(mpg123_reader64(mh, ComponentMPG123::FileReaderRead, ComponentMPG123::FileReaderSeek, 0)) + { + return false; + } +#else if(mpg123_replace_reader_handle(mh, ComponentMPG123::FileReaderRead, ComponentMPG123::FileReaderLSeek, 0)) { return false; } +#endif if(mpg123_open_handle(mh, &file)) { return false; @@ -540,7 +621,11 @@ bool CSoundFile::ReadMP3Sample(SAMPLEINDEX sample, FileReader &file, bool raw, b { buf_bytes.resize(mpg123_outblock(mh)); buf_samples.resize(buf_bytes.size() / sizeof(int16)); +#if (MPG123_API_VERSION >= 48) && MPT_USE_MPG123_PORTABLE_API + size_t buf_bytes_decoded = 0; +#else mpg123_size_t buf_bytes_decoded = 0; +#endif int mpg123_read_result = mpg123_read(mh, mpt::byte_cast(buf_bytes.data()), buf_bytes.size(), &buf_bytes_decoded); std::memcpy(buf_samples.data(), buf_bytes.data(), buf_bytes_decoded); mpt::append(data, buf_samples.data(), buf_samples.data() + buf_bytes_decoded / sizeof(int16)); diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/SampleFormatOpus.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/SampleFormatOpus.cpp index 0d8341072..45225de1e 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/SampleFormatOpus.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/SampleFormatOpus.cpp @@ -58,11 +58,11 @@ static int OpusfileFilereaderSeek(void *stream, opus_int64 offset, int whence) { case SEEK_SET: { - if(!mpt::in_range(offset)) + if(!mpt::in_range(offset)) { return -1; } - return file.Seek(mpt::saturate_cast(offset)) ? 0 : -1; + return file.Seek(mpt::saturate_cast(offset)) ? 0 : -1; } break; case SEEK_CUR: @@ -73,32 +73,32 @@ static int OpusfileFilereaderSeek(void *stream, opus_int64 offset, int whence) { return -1; } - if(!mpt::in_range(0-offset)) + if(!mpt::in_range(0-offset)) { return -1; } - return file.SkipBack(mpt::saturate_cast(0 - offset)) ? 0 : -1; + return file.SkipBack(mpt::saturate_cast(0 - offset)) ? 0 : -1; } else { - if(!mpt::in_range(offset)) + if(!mpt::in_range(offset)) { return -1; } - return file.Skip(mpt::saturate_cast(offset)) ? 0 : -1; + return file.Skip(mpt::saturate_cast(offset)) ? 0 : -1; } } break; case SEEK_END: { - if(!mpt::in_range(offset)) + if(!mpt::in_range(offset)) { return -1; } - if(!mpt::in_range(file.GetLength() + offset)) + if(!mpt::in_range(file.GetLength() + offset)) { return -1; } - return file.Seek(mpt::saturate_cast(file.GetLength() + offset)) ? 0 : -1; + return file.Seek(mpt::saturate_cast(file.GetLength() + offset)) ? 0 : -1; } break; default: diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/SampleFormatSFZ.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/SampleFormatSFZ.cpp index 1e9f96fe8..6a0753135 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/SampleFormatSFZ.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/SampleFormatSFZ.cpp @@ -739,6 +739,11 @@ bool CSoundFile::ReadSFZInstrument(INSTRUMENTINDEX nInstr, FileReader &file) case kControl: control.Parse(key, value); break; + case kNone: + case kCurve: + case kEffect: + case kUnknown: + break; } } else { @@ -896,7 +901,7 @@ bool CSoundFile::ReadSFZInstrument(INSTRUMENTINDEX nInstr, FileReader &file) if(region.ampEnv.release > 0) { const double tickDuration = m_PlayState.m_nSamplesPerTick / static_cast(GetSampleRate()); - pIns->nFadeOut = std::min(mpt::saturate_cast(32768.0 * tickDuration / region.ampEnv.release), uint32(32767)); + pIns->nFadeOut = std::min(mpt::saturate_trunc(32768.0 * tickDuration / region.ampEnv.release), uint32(32767)); if(GetType() == MOD_TYPE_IT) pIns->nFadeOut = std::min((pIns->nFadeOut + 16u) & ~31u, uint32(8192)); } @@ -932,6 +937,10 @@ bool CSoundFile::ReadSFZInstrument(INSTRUMENTINDEX nInstr, FileReader &file) case SFZRegion::LoopMode::kNoLoop: case SFZRegion::LoopMode::kOneShot: sample.uFlags.reset(CHN_LOOP | CHN_SUSTAINLOOP); + break; + case SFZRegion::LoopMode::kUnspecified: + MPT_ASSERT_NOTREACHED(); + break; } } if(region.loopEnd > region.loopStart) @@ -1136,7 +1145,7 @@ bool CSoundFile::SaveSFZInstrument(INSTRUMENTINDEX nInstr, std::ostream &f, cons case TempoMode::Modern: f << ", " << m_PlayState.m_nMusicSpeed << " ticks per row, " << m_PlayState.m_nCurrentRowsPerBeat << " rows per beat (modern tempo mode)"; break; - default: + case TempoMode::NumModes: MPT_ASSERT_NOTREACHED(); break; } diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/SampleFormatVorbis.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/SampleFormatVorbis.cpp index 794715c6f..b4ec7a978 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/SampleFormatVorbis.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/SampleFormatVorbis.cpp @@ -79,11 +79,11 @@ static int VorbisfileFilereaderSeek(void *datasource, ogg_int64_t offset, int wh { case SEEK_SET: { - if(!mpt::in_range(offset)) + if(!mpt::in_range(offset)) { return -1; } - return file.Seek(mpt::saturate_cast(offset)) ? 0 : -1; + return file.Seek(mpt::saturate_cast(offset)) ? 0 : -1; } break; case SEEK_CUR: @@ -94,32 +94,32 @@ static int VorbisfileFilereaderSeek(void *datasource, ogg_int64_t offset, int wh { return -1; } - if(!mpt::in_range(0-offset)) + if(!mpt::in_range(0-offset)) { return -1; } - return file.SkipBack(mpt::saturate_cast(0 - offset)) ? 0 : -1; + return file.SkipBack(mpt::saturate_cast(0 - offset)) ? 0 : -1; } else { - if(!mpt::in_range(offset)) + if(!mpt::in_range(offset)) { return -1; } - return file.Skip(mpt::saturate_cast(offset)) ? 0 : -1; + return file.Skip(mpt::saturate_cast(offset)) ? 0 : -1; } } break; case SEEK_END: { - if(!mpt::in_range(offset)) + if(!mpt::in_range(offset)) { return -1; } - if(!mpt::in_range(file.GetLength() + offset)) + if(!mpt::in_range(file.GetLength() + offset)) { return -1; } - return file.Seek(mpt::saturate_cast(file.GetLength() + offset)) ? 0 : -1; + return file.Seek(mpt::saturate_cast(file.GetLength() + offset)) ? 0 : -1; } break; default: diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/SampleFormats.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/SampleFormats.cpp index aefb74939..ac010b019 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/SampleFormats.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/SampleFormats.cpp @@ -141,7 +141,7 @@ bool CSoundFile::ReadSampleAsInstrument(INSTRUMENTINDEX nInstr, FileReader &file DestroyInstrument(nInstr, doNoDeleteAssociatedSamples); Instruments[nInstr] = pIns; -#if defined(MPT_ENABLE_FILEIO) && defined(MPT_EXTERNAL_SAMPLES) +#if defined(MPT_EXTERNAL_SAMPLES) SetSamplePath(nSample, file.GetOptionalFileName().value_or(P_(""))); #endif @@ -623,7 +623,7 @@ struct Wave64Chunk { Wave64ChunkHeader header; - FileReader::off_t GetLength() const + FileReader::pos_type GetLength() const { uint64 length = header.Size; if(length < sizeof(Wave64ChunkHeader)) @@ -633,7 +633,7 @@ struct Wave64Chunk { length -= sizeof(Wave64ChunkHeader); } - return mpt::saturate_cast(length); + return mpt::saturate_cast(length); } mpt::UUID GetID() const @@ -657,7 +657,7 @@ static void Wave64TagFromLISTINFO(mpt::ustring & dst, uint16 codePage, const Fil return; } std::string str; - textChunk.ReadString(str, textChunk.GetLength()); + textChunk.ReadString(str, mpt::saturate_cast(textChunk.GetLength())); str = mpt::replace(str, std::string("\r\n"), std::string("\n")); str = mpt::replace(str, std::string("\r"), std::string("\n")); dst = mpt::ToUnicode(codePage, mpt::Charset::Windows1252, str); @@ -1341,7 +1341,7 @@ bool CSoundFile::ReadXIInstrument(INSTRUMENTINDEX nInstr, FileReader &file) } // Read MPT crap - ReadExtendedInstrumentProperties(pIns, file); + LoadExtendedInstrumentProperties(mpt::as_span(&Instruments[nInstr], 1), file); pIns->Convert(MOD_TYPE_XM, GetType()); pIns->Sanitize(GetType()); return true; @@ -1402,9 +1402,7 @@ bool CSoundFile::SaveXIInstrument(INSTRUMENTINDEX nInstr, std::ostream &f) const } } - // Write 'MPTX' extension tag - mpt::IO::WriteText(f, "XTPM"); - WriteInstrumentHeaderStructOrField(pIns, f); // Write full extended header. + SaveExtendedInstrumentProperties(nInstr, MOD_TYPE_XM, f); return true; } @@ -1434,7 +1432,7 @@ bool CSoundFile::ReadXISample(SAMPLEINDEX nSample, FileReader &file) } uint16 numSamples = fileHeader.numSamples; - FileReader::off_t samplePos = sizeof(XIInstrumentHeader) + numSamples * sizeof(XMSample); + FileReader::pos_type samplePos = sizeof(XIInstrumentHeader) + numSamples * sizeof(XMSample); // Preferably read the middle-C sample auto sample = fileHeader.instrument.sampleMap[48]; if(sample >= fileHeader.numSamples) @@ -1507,7 +1505,7 @@ struct CAFChunk CAFChunkHeader header; - FileReader::off_t GetLength() const + FileReader::pos_type GetLength() const { int64 length = header.mChunkSize; if(length == -1) @@ -1518,7 +1516,7 @@ struct CAFChunk { length = std::numeric_limits::max(); // heuristic } - return mpt::saturate_cast(length); + return mpt::saturate_cast(length); } ChunkIdentifiers GetID() const @@ -1618,11 +1616,11 @@ bool CSoundFile::ReadCAFSample(SAMPLEINDEX nSample, FileReader &file, bool mayNo return false; } - if(!mpt::in_range(mpt::saturate_round(audioFormat.mSampleRate))) + if(!mpt::in_range(mpt::saturate_round(audioFormat.mSampleRate.get()))) { return false; } - uint32 sampleRate = static_cast(mpt::saturate_round(audioFormat.mSampleRate)); + uint32 sampleRate = static_cast(mpt::saturate_round(audioFormat.mSampleRate.get())); if(sampleRate <= 0) { return false; @@ -1696,9 +1694,9 @@ bool CSoundFile::ReadCAFSample(SAMPLEINDEX nSample, FileReader &file, bool mayNo { uint32 stringID = stringsChunk.ReadUint32BE(); int64 offset = stringsChunk.ReadIntBE(); - if(offset >= 0 && mpt::in_range(offset)) + if(offset >= 0 && mpt::in_range(offset)) { - stringData.Seek(mpt::saturate_cast(offset)); + stringData.Seek(mpt::saturate_cast(offset)); std::string str; if(stringData.ReadNullString(str)) { @@ -2064,7 +2062,7 @@ bool CSoundFile::ReadAIFFSample(SAMPLEINDEX nSample, FileReader &file, bool mayN FileReader nameChunk(chunks.GetChunk(AIFFChunk::idNAME)); if(nameChunk.IsValid()) { - nameChunk.ReadString(m_szNames[nSample], nameChunk.GetLength()); + nameChunk.ReadString(m_szNames[nSample], mpt::saturate_cast(nameChunk.GetLength())); } else { m_szNames[nSample] = ""; @@ -2376,7 +2374,7 @@ bool CSoundFile::ReadITIInstrument(INSTRUMENTINDEX nInstr, FileReader &file) // In order to properly compute the position, in file, of eventual extended settings // such as "attack" we need to keep the "real" size of the last sample as those extra // setting will follow this sample in the file - FileReader::off_t extraOffset = file.GetPosition(); + FileReader::pos_type extraOffset = file.GetPosition(); // Reading Samples std::vector samplemap(nsamples, 0); @@ -2385,7 +2383,7 @@ bool CSoundFile::ReadITIInstrument(INSTRUMENTINDEX nInstr, FileReader &file) smp = GetNextFreeSample(nInstr, smp + 1); if(smp == SAMPLEINDEX_INVALID) break; samplemap[i] = smp; - const FileReader::off_t offset = file.GetPosition(); + const FileReader::pos_type offset = file.GetPosition(); if(!ReadITSSample(smp, file, false)) smp--; extraOffset = std::max(extraOffset, file.GetPosition()); @@ -2405,7 +2403,7 @@ bool CSoundFile::ReadITIInstrument(INSTRUMENTINDEX nInstr, FileReader &file) if(file.Seek(extraOffset)) { // Read MPT crap - ReadExtendedInstrumentProperties(pIns, file); + LoadExtendedInstrumentProperties(mpt::as_span(&Instruments[nInstr], 1), file); } pIns->Convert(MOD_TYPE_IT, GetType()); @@ -2490,9 +2488,7 @@ bool CSoundFile::SaveITIInstrument(INSTRUMENTINDEX nInstr, std::ostream &f, cons } mpt::IO::SeekEnd(f); - // Write 'MPTX' extension tag - mpt::IO::WriteRaw(f, "XTPM", 4); - WriteInstrumentHeaderStructOrField(pIns, f); // Write full extended header. + SaveExtendedInstrumentProperties(nInstr, MOD_TYPE_MPT, f); return true; } @@ -2566,7 +2562,7 @@ struct IFFSampleHeader MPT_BINARY_STRUCT(IFFSampleHeader, 20) -bool CSoundFile::ReadIFFSample(SAMPLEINDEX nSample, FileReader &file, bool allowLittleEndian) +bool CSoundFile::ReadIFFSample(SAMPLEINDEX nSample, FileReader &file, bool allowLittleEndian, uint8 octave) { file.Rewind(); @@ -2657,6 +2653,18 @@ bool CSoundFile::ReadIFFSample(SAMPLEINDEX nSample, FileReader &file, bool allow sampleRate = sampleHeader.samplesPerSec; volume = sampleHeader.volume; numSamples = mpt::saturate_cast(sampleData.GetLength() / bytesPerFrame); + + if(octave < sampleHeader.octave) + { + numSamples = sampleHeader.oneShotHiSamples + sampleHeader.repeatHiSamples; + for(uint8 o = 0; o < octave; o++) + { + sampleData.Skip(numSamples * bytesPerSample * numChannels); + numSamples *= 2; + loopStart *= 2; + loopLength *= 2; + } + } } DestroySampleThreadsafe(nSample); @@ -2680,7 +2688,7 @@ bool CSoundFile::ReadIFFSample(SAMPLEINDEX nSample, FileReader &file, bool allow FileReader nameChunk = chunks.GetChunk(IFFChunk::idNAME); if(nameChunk.IsValid()) - nameChunk.ReadString(m_szNames[nSample], nameChunk.GetLength()); + nameChunk.ReadString(m_szNames[nSample], mpt::saturate_cast(nameChunk.GetLength())); else m_szNames[nSample] = ""; @@ -2815,7 +2823,7 @@ bool CSoundFile::SaveIFFSample(SAMPLEINDEX smp, std::ostream &f) const chunk.length = 4; mpt::IO::Write(f, chunk); mpt::IO::WriteIntBE(f, 6); - totalSize += sizeof(chunk) + chunk.length; + totalSize += mpt::saturate_cast(sizeof(chunk) + chunk.length); } totalSize += WriteIFFStringChunk(f, IFFChunk::idNAME, mpt::ToCharset(mpt::Charset::Amiga, GetCharsetInternal(), m_szNames[smp])); @@ -2831,7 +2839,7 @@ bool CSoundFile::SaveIFFSample(SAMPLEINDEX smp, std::ostream &f) const chunk.length = mpt::saturate_cast(sampleIO.CalculateEncodedSize(sample.nLength)); mpt::IO::Write(f, chunk); sampleIO.WriteSample(f, sample); - totalSize += sizeof(chunk) + chunk.length; + totalSize += mpt::saturate_cast(sizeof(chunk) + chunk.length); if(totalSize % 2u) { mpt::IO::WriteIntBE(f, 0); diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/SampleIO.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/SampleIO.cpp index ae2c83416..4097b102d 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/SampleIO.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/SampleIO.cpp @@ -11,13 +11,13 @@ #include "stdafx.h" -#include "Loaders.h" #include "SampleIO.h" -#include "openmpt/soundbase/SampleDecode.hpp" +#include "BitReader.h" +#include "ITCompression.h" +#include "Loaders.h" +#include "ModSampleCopy.h" #include "SampleCopy.h" #include "SampleNormalize.h" -#include "ModSampleCopy.h" -#include "ITCompression.h" #ifndef MODPLUG_NO_FILESAVE #include "../common/mptFileIO.h" #include "mpt/io/base.hpp" @@ -25,7 +25,7 @@ #include "mpt/io/io_stdstream.hpp" #include "mpt/io_write/buffer.hpp" #endif -#include "BitReader.h" +#include "openmpt/soundbase/SampleDecode.hpp" OPENMPT_NAMESPACE_BEGIN @@ -41,12 +41,12 @@ size_t SampleIO::ReadSample(ModSample &sample, FileReader &file) const LimitMax(sample.nLength, MAX_SAMPLE_LENGTH); - FileReader::off_t bytesRead = 0; // Amount of memory that has been read from file + FileReader::pos_type bytesRead = 0; // Amount of memory that has been read from file - FileReader::off_t filePosition = file.GetPosition(); + FileReader::pos_type filePosition = file.GetPosition(); const std::byte * sourceBuf = nullptr; FileReader::PinnedView restrictedSampleDataView; - FileReader::off_t fileSize = 0; + FileReader::pos_type fileSize = 0; if(UsesFileReaderForDecoding()) { sourceBuf = nullptr; @@ -69,7 +69,7 @@ size_t SampleIO::ReadSample(ModSample &sample, FileReader &file) const // However, for ProTracker MODs we need to support samples exceeding the end of file // (see the comment about MOD.shorttune2 in Load_mod.cpp), so as a semi-arbitrary threshold, // we do not apply this limit to samples shorter than 256K. - size_t maxLength = fileSize - std::min(GetEncodedHeaderSize(), fileSize); + std::size_t maxLength = static_cast(fileSize) - std::min(GetEncodedHeaderSize(), static_cast(fileSize)); uint8 bps = GetEncodedBitsPerSample(); if(bps % 8u != 0) { @@ -146,7 +146,7 @@ size_t SampleIO::ReadSample(ModSample &sample, FileReader &file) const if(file.ReadArray(compressionTable)) { size_t readLength = (sample.nLength + 1) / 2; - LimitMax(readLength, file.BytesLeft()); + LimitMax(readLength, mpt::saturate_cast(file.BytesLeft())); const uint8 *inBuf = mpt::byte_cast(sourceBuf) + sizeof(compressionTable); int8 *outBuf = sample.sample8(); @@ -572,10 +572,10 @@ size_t SampleIO::ReadSample(ModSample &sample, FileReader &file) const { if(GetEndianness() == littleEndian) { - bytesRead = CopyMonoSample, SC::DecodeFloat32 > >(sample, sourceBuf, fileSize); + bytesRead = CopyMonoSample, SC::DecodeFloat32 > >(sample, sourceBuf, fileSize); } else { - bytesRead = CopyMonoSample, SC::DecodeFloat32 > >(sample, sourceBuf, fileSize); + bytesRead = CopyMonoSample, SC::DecodeFloat32 > >(sample, sourceBuf, fileSize); } } @@ -585,10 +585,10 @@ size_t SampleIO::ReadSample(ModSample &sample, FileReader &file) const { if(GetEndianness() == littleEndian) { - bytesRead = CopyStereoInterleavedSample, SC::DecodeFloat32 > >(sample, sourceBuf, fileSize); + bytesRead = CopyStereoInterleavedSample, SC::DecodeFloat32 > >(sample, sourceBuf, fileSize); } else { - bytesRead = CopyStereoInterleavedSample, SC::DecodeFloat32 > >(sample, sourceBuf, fileSize); + bytesRead = CopyStereoInterleavedSample, SC::DecodeFloat32 > >(sample, sourceBuf, fileSize); } } @@ -598,10 +598,10 @@ size_t SampleIO::ReadSample(ModSample &sample, FileReader &file) const { if(GetEndianness() == littleEndian) { - bytesRead = CopyMonoSample, SC::DecodeFloat64 > >(sample, sourceBuf, fileSize); + bytesRead = CopyMonoSample, SC::DecodeFloat64 > >(sample, sourceBuf, fileSize); } else { - bytesRead = CopyMonoSample, SC::DecodeFloat64 > >(sample, sourceBuf, fileSize); + bytesRead = CopyMonoSample, SC::DecodeFloat64 > >(sample, sourceBuf, fileSize); } } @@ -611,10 +611,10 @@ size_t SampleIO::ReadSample(ModSample &sample, FileReader &file) const { if(GetEndianness() == littleEndian) { - bytesRead = CopyStereoInterleavedSample, SC::DecodeFloat64 > >(sample, sourceBuf, fileSize); + bytesRead = CopyStereoInterleavedSample, SC::DecodeFloat64 > >(sample, sourceBuf, fileSize); } else { - bytesRead = CopyStereoInterleavedSample, SC::DecodeFloat64 > >(sample, sourceBuf, fileSize); + bytesRead = CopyStereoInterleavedSample, SC::DecodeFloat64 > >(sample, sourceBuf, fileSize); } } @@ -665,13 +665,13 @@ size_t SampleIO::ReadSample(ModSample &sample, FileReader &file) const else if(GetBitDepth() == 32 && (GetChannelFormat() == mono || GetChannelFormat() == stereoInterleaved) && GetEncoding() == floatPCMnormalize) { // Normalize to 16-Bit - float32 srcPeak = 1.0f; + somefloat32 srcPeak = 1.0f; if(GetEndianness() == littleEndian) { - bytesRead = CopyAndNormalizeSample, SC::DecodeFloat32 > >(sample, sourceBuf, fileSize, &srcPeak); + bytesRead = CopyAndNormalizeSample, SC::DecodeFloat32 > >(sample, sourceBuf, fileSize, &srcPeak); } else { - bytesRead = CopyAndNormalizeSample, SC::DecodeFloat32 > >(sample, sourceBuf, fileSize, &srcPeak); + bytesRead = CopyAndNormalizeSample, SC::DecodeFloat32 > >(sample, sourceBuf, fileSize, &srcPeak); } if(bytesRead && srcPeak != 1.0f) { @@ -686,13 +686,13 @@ size_t SampleIO::ReadSample(ModSample &sample, FileReader &file) const else if(GetBitDepth() == 64 && (GetChannelFormat() == mono || GetChannelFormat() == stereoInterleaved) && GetEncoding() == floatPCMnormalize) { // Normalize to 16-Bit - float64 srcPeak = 1.0; + somefloat64 srcPeak = 1.0; if(GetEndianness() == littleEndian) { - bytesRead = CopyAndNormalizeSample, SC::DecodeFloat64 > >(sample, sourceBuf, fileSize, &srcPeak); + bytesRead = CopyAndNormalizeSample, SC::DecodeFloat64 > >(sample, sourceBuf, fileSize, &srcPeak); } else { - bytesRead = CopyAndNormalizeSample, SC::DecodeFloat64 > >(sample, sourceBuf, fileSize, &srcPeak); + bytesRead = CopyAndNormalizeSample, SC::DecodeFloat64 > >(sample, sourceBuf, fileSize, &srcPeak); } if(bytesRead && srcPeak != 1.0) { @@ -710,15 +710,15 @@ size_t SampleIO::ReadSample(ModSample &sample, FileReader &file) const { bytesRead = CopyMonoSample (sample, sourceBuf, fileSize, - SC::ConversionChain, SC::DecodeScaledFloat32 > - (SC::Convert(), SC::DecodeScaledFloat32(1.0f / static_cast(1<<15))) + SC::ConversionChain, SC::DecodeScaledFloat32 > + (SC::Convert(), SC::DecodeScaledFloat32(1.0f / static_cast(1<<15))) ); } else { bytesRead = CopyMonoSample (sample, sourceBuf, fileSize, - SC::ConversionChain, SC::DecodeScaledFloat32 > - (SC::Convert(), SC::DecodeScaledFloat32(1.0f / static_cast(1<<15))) + SC::ConversionChain, SC::DecodeScaledFloat32 > + (SC::Convert(), SC::DecodeScaledFloat32(1.0f / static_cast(1<<15))) ); } } @@ -731,15 +731,15 @@ size_t SampleIO::ReadSample(ModSample &sample, FileReader &file) const { bytesRead = CopyStereoInterleavedSample (sample, sourceBuf, fileSize, - SC::ConversionChain, SC::DecodeScaledFloat32 > - (SC::Convert(), SC::DecodeScaledFloat32(1.0f / static_cast(1<<15))) + SC::ConversionChain, SC::DecodeScaledFloat32 > + (SC::Convert(), SC::DecodeScaledFloat32(1.0f / static_cast(1<<15))) ); } else { bytesRead = CopyStereoInterleavedSample (sample, sourceBuf, fileSize, - SC::ConversionChain, SC::DecodeScaledFloat32 > - (SC::Convert(), SC::DecodeScaledFloat32(1.0f / static_cast(1<<15))) + SC::ConversionChain, SC::DecodeScaledFloat32 > + (SC::Convert(), SC::DecodeScaledFloat32(1.0f / static_cast(1<<15))) ); } } @@ -752,15 +752,15 @@ size_t SampleIO::ReadSample(ModSample &sample, FileReader &file) const { bytesRead = CopyMonoSample (sample, sourceBuf, fileSize, - SC::ConversionChain, SC::DecodeScaledFloat32 > - (SC::Convert(), SC::DecodeScaledFloat32(1.0f / static_cast(1<<23))) + SC::ConversionChain, SC::DecodeScaledFloat32 > + (SC::Convert(), SC::DecodeScaledFloat32(1.0f / static_cast(1<<23))) ); } else { bytesRead = CopyMonoSample (sample, sourceBuf, fileSize, - SC::ConversionChain, SC::DecodeScaledFloat32 > - (SC::Convert(), SC::DecodeScaledFloat32(1.0f / static_cast(1<<23))) + SC::ConversionChain, SC::DecodeScaledFloat32 > + (SC::Convert(), SC::DecodeScaledFloat32(1.0f / static_cast(1<<23))) ); } } @@ -773,15 +773,15 @@ size_t SampleIO::ReadSample(ModSample &sample, FileReader &file) const { bytesRead = CopyStereoInterleavedSample (sample, sourceBuf, fileSize, - SC::ConversionChain, SC::DecodeScaledFloat32 > - (SC::Convert(), SC::DecodeScaledFloat32(1.0f / static_cast(1<<23))) + SC::ConversionChain, SC::DecodeScaledFloat32 > + (SC::Convert(), SC::DecodeScaledFloat32(1.0f / static_cast(1<<23))) ); } else { bytesRead = CopyStereoInterleavedSample (sample, sourceBuf, fileSize, - SC::ConversionChain, SC::DecodeScaledFloat32 > - (SC::Convert(), SC::DecodeScaledFloat32(1.0f / static_cast(1<<23))) + SC::ConversionChain, SC::DecodeScaledFloat32 > + (SC::Convert(), SC::DecodeScaledFloat32(1.0f / static_cast(1<<23))) ); } } diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/SampleNormalize.h b/Frameworks/OpenMPT/OpenMPT/soundlib/SampleNormalize.h index bbe4b9723..89aad4713 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/SampleNormalize.h +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/SampleNormalize.h @@ -64,11 +64,11 @@ struct Normalize }; template <> -struct Normalize +struct Normalize { - using input_t = float32; - using output_t = float32; - using peak_t = float32; + using input_t = somefloat32; + using output_t = somefloat32; + using peak_t = somefloat32; float maxVal; float maxValInv; MPT_FORCEINLINE Normalize() @@ -104,11 +104,11 @@ struct Normalize }; template <> -struct Normalize +struct Normalize { - using input_t = float64; - using output_t = float64; - using peak_t = float64; + using input_t = somefloat64; + using output_t = somefloat64; + using peak_t = somefloat64; double maxVal; double maxValInv; MPT_FORCEINLINE Normalize() diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Snd_defs.h b/Frameworks/OpenMPT/OpenMPT/soundlib/Snd_defs.h index 73d90ddd1..19926c597 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/Snd_defs.h +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Snd_defs.h @@ -1,5 +1,5 @@ /* - * Snd_Defs.h + * Snd_defs.h * ---------- * Purpose: Basic definitions of data types, enums, etc. for the playback engine core. * Notes : (currently none) @@ -27,7 +27,8 @@ using ORDERINDEX = uint16; inline constexpr ORDERINDEX ORDERINDEX_INVALID = uint16_max; inline constexpr ORDERINDEX ORDERINDEX_MAX = uint16_max - 1; using PATTERNINDEX = uint16; -inline constexpr PATTERNINDEX PATTERNINDEX_INVALID = uint16_max; +inline constexpr PATTERNINDEX PATTERNINDEX_INVALID = uint16_max; // "---" in order list +inline constexpr PATTERNINDEX PATTERNINDEX_SKIP = uint16_max - 1; // "+++" in order list using PLUGINDEX = uint8; inline constexpr PLUGINDEX PLUGINDEX_INVALID = uint8_max; using SAMPLEINDEX = uint16; @@ -47,7 +48,7 @@ inline constexpr ROWINDEX MAX_ROWS_PER_BEAT = 65536; inline constexpr ROWINDEX DEFAULT_ROWS_PER_BEAT = 4; inline constexpr ROWINDEX DEFAULT_ROWS_PER_MEASURE = 16; -inline constexpr ROWINDEX MAX_PATTERN_ROWS = 1024; +inline constexpr ROWINDEX MAX_PATTERN_ROWS = 4096; inline constexpr ORDERINDEX MAX_ORDERS = ORDERINDEX_MAX + 1; inline constexpr PATTERNINDEX MAX_PATTERNS = 4000; inline constexpr SAMPLEINDEX MAX_SAMPLES = 4000; @@ -56,11 +57,16 @@ inline constexpr PLUGINDEX MAX_MIXPLUGINS = 250; inline constexpr SEQUENCEINDEX MAX_SEQUENCES = 50; -inline constexpr CHANNELINDEX MAX_BASECHANNELS = 127; // Maximum pattern channels. +inline constexpr CHANNELINDEX MAX_BASECHANNELS = 192; // Maximum pattern channels. inline constexpr CHANNELINDEX MAX_CHANNELS = 256; // Maximum number of mixing channels. enum { FREQ_FRACBITS = 4 }; // Number of fractional bits in return value of CSoundFile::GetFreqFromPeriod() +using samplecount_t = uint32; // Number of rendered samples + +using PlugParamIndex = uint32; +using PlugParamValue = float; + // String lengths (including trailing null char) enum { @@ -105,6 +111,8 @@ enum MODTYPE MOD_TYPE_STP = 0x8000000, MOD_TYPE_PLM = 0x10000000, MOD_TYPE_SFX = 0x20000000, + + MOD_TYPE_MOD_PC = MOD_TYPE_MOD | MOD_TYPE_XM, }; DECLARE_FLAGSET(MODTYPE) @@ -122,8 +130,29 @@ enum class ModContainerType }; +enum class AutoSlideCommand +{ + TonePortamento, + TonePortamentoWithDuration, + PortamentoUp, + PortamentoDown, + FinePortamentoUp, + FinePortamentoDown, + PortamentoFC, + FineVolumeSlideUp, + FineVolumeSlideDown, + VolumeDownETX, + VolumeSlideSTK, + VolumeDownWithDuration, + GlobalVolumeSlide, + Vibrato, + Tremolo, + NumCommands +}; + + // Module channel / sample flags -enum ChannelFlags +enum ChannelFlags : uint32 { // Sample Flags CHN_16BIT = 0x01, // 16-bit sample @@ -154,9 +183,8 @@ enum ChannelFlags CHN_EXTRALOUD = 0x400000, // Force sample to play at 0dB CHN_REVERB = 0x800000, // Apply reverb on this channel CHN_NOREVERB = 0x1000000, // Disable reverb on this channel - CHN_SOLO = 0x2000000, // Solo channel - CHN_NOFX = 0x4000000, // Dry channel (no plugins) - CHN_SYNCMUTE = 0x8000000, // Keep sample sync on mute + CHN_NOFX = 0x2000000, // Dry channel (no plugins) + CHN_SYNCMUTE = 0x4000000, // Keep sample sync on mute // Sample flags (only present in ModSample::uFlags, may overlap with CHN_CHANNELFLAGS) SMP_MODIFIED = 0x2000, // Sample data has been edited in the tracker @@ -165,8 +193,8 @@ enum ChannelFlags }; DECLARE_FLAGSET(ChannelFlags) -#define CHN_SAMPLEFLAGS (CHN_16BIT | CHN_LOOP | CHN_PINGPONGLOOP | CHN_SUSTAINLOOP | CHN_PINGPONGSUSTAIN | CHN_PANNING | CHN_STEREO | CHN_PINGPONGFLAG | CHN_REVERSE | CHN_SURROUND | CHN_ADLIB) -#define CHN_CHANNELFLAGS (~CHN_SAMPLEFLAGS | CHN_SURROUND) +inline constexpr ChannelFlags CHN_SAMPLEFLAGS = (CHN_16BIT | CHN_LOOP | CHN_PINGPONGLOOP | CHN_SUSTAINLOOP | CHN_PINGPONGSUSTAIN | CHN_PANNING | CHN_STEREO | CHN_PINGPONGFLAG | CHN_REVERSE | CHN_SURROUND | CHN_ADLIB).as_enum(); +inline constexpr ChannelFlags CHN_CHANNELFLAGS = (~CHN_SAMPLEFLAGS | CHN_SURROUND).as_enum(); // Sample flags fit into the first 16 bits, and with the current memory layout, storing them as a 16-bit integer packs struct ModSample nicely. using SampleFlags = FlagSet; @@ -185,13 +213,10 @@ DECLARE_FLAGSET(EnvelopeFlags) // Envelope value boundaries -enum : uint8 -{ - ENVELOPE_MIN = 0, // Vertical min value of a point - ENVELOPE_MID = 32, // Vertical middle line - ENVELOPE_MAX = 64, // Vertical max value of a point -}; -#define MAX_ENVPOINTS 240 // Maximum length of each instrument envelope +inline constexpr uint8 ENVELOPE_MIN = 0; // Vertical min value of a point +inline constexpr uint8 ENVELOPE_MID = 32; // Vertical middle line +inline constexpr uint8 ENVELOPE_MAX = 64; // Vertical max value of a point +inline constexpr uint8 MAX_ENVPOINTS = 240; // Maximum length of each instrument envelope // Instrument-specific flags @@ -250,35 +275,47 @@ enum class DuplicateNoteAction : uint8 }; -// Module flags - contains both song configuration and playback state... Use SONG_FILE_FLAGS and SONG_PLAY_FLAGS distinguish between the two. +enum PlayFlags : uint16 +{ + SONG_PATTERNLOOP = 0x01, // Loop current pattern (pattern editor) + SONG_STEP = 0x02, // Song is in "step" mode (pattern editor) + SONG_PAUSED = 0x04, // Song is paused (no tick processing, just rendering audio) + SONG_FADINGSONG = 0x08, // Song is fading out + SONG_ENDREACHED = 0x10, // Song is finished + SONG_FIRSTTICK = 0x20, // Is set when the current tick is the first tick of the row + SONG_MPTFILTERMODE = 0x40, // Local filter mode (reset filter on each note) + SONG_SURROUNDPAN = 0x80, // Pan in the rear channels + SONG_POSJUMP = 0x100, // Position jump encountered + SONG_BREAKTOROW = 0x200, // Break to row command encountered + SONG_POSITIONCHANGED = 0x400, // Report to plugins that we jumped around in the module +}; +DECLARE_FLAGSET(PlayFlags) + + enum SongFlags { - SONG_FASTVOLSLIDES = 0x02, // Old Scream Tracker 3.0 volume slides - SONG_ITOLDEFFECTS = 0x04, // Old Impulse Tracker effect implementations - SONG_ITCOMPATGXX = 0x08, // IT "Compatible Gxx" (IT's flag to behave more like other trackers w/r/t portamento effects) - SONG_LINEARSLIDES = 0x10, // Linear slides vs. Amiga slides - SONG_PATTERNLOOP = 0x20, // Loop current pattern (pattern editor) - SONG_STEP = 0x40, // Song is in "step" mode (pattern editor) - SONG_PAUSED = 0x80, // Song is paused (no tick processing, just rendering audio) - SONG_FADINGSONG = 0x0100, // Song is fading out - SONG_ENDREACHED = 0x0200, // Song is finished - SONG_FIRSTTICK = 0x1000, // Is set when the current tick is the first tick of the row - SONG_MPTFILTERMODE = 0x2000, // Local filter mode (reset filter on each note) - SONG_SURROUNDPAN = 0x4000, // Pan in the rear channels - SONG_EXFILTERRANGE = 0x8000, // Cutoff Filter has double frequency range (up to ~10Khz) - SONG_AMIGALIMITS = 0x1'0000, // Enforce amiga frequency limits - SONG_S3MOLDVIBRATO = 0x2'0000, // ScreamTracker 2 vibrato in S3M files - SONG_BREAKTOROW = 0x8'0000, // Break to row command encountered (internal flag, do not touch) - SONG_POSJUMP = 0x10'0000, // Position jump encountered (internal flag, do not touch) - SONG_PT_MODE = 0x20'0000, // ProTracker 1/2 playback mode - SONG_PLAYALLSONGS = 0x40'0000, // Play all subsongs consecutively (libopenmpt) - SONG_ISAMIGA = 0x80'0000, // Is an Amiga module and thus qualifies to be played using the Paula BLEP resampler - SONG_IMPORTED = 0x100'0000, // Song type does not represent actual module format / was imported from a different format (OpenMPT) + SONG_FASTPORTAS = 0x01, // Portamentos are executed on every tick + SONG_FASTVOLSLIDES = 0x02, // Old Scream Tracker 3.0 volume slides (executed on every tick) + SONG_ITOLDEFFECTS = 0x04, // Old Impulse Tracker effect implementations + SONG_ITCOMPATGXX = 0x08, // IT "Compatible Gxx" (IT's flag to behave more like other trackers w/r/t portamento effects) + SONG_LINEARSLIDES = 0x10, // Linear slides vs. Amiga slides + SONG_EXFILTERRANGE = 0x20, // Cutoff Filter has double frequency range (up to ~10Khz) + SONG_AMIGALIMITS = 0x40, // Enforce amiga frequency limits + SONG_S3MOLDVIBRATO = 0x80, // ScreamTracker 2 vibrato in S3M files + SONG_PT_MODE = 0x100, // ProTracker 1/2 playback mode + SONG_ISAMIGA = 0x200, // Is an Amiga module and thus qualifies to be played using the Paula BLEP resampler + SONG_IMPORTED = 0x400, // Song type does not represent actual module format / was imported from a different format (OpenMPT) + SONG_PLAYALLSONGS = 0x800, // Play all subsongs consecutively (libopenmpt) + SONG_AUTO_TONEPORTA = 0x1000, // Tone portamento command is continued automatically + SONG_AUTO_TONEPORTA_CONT = 0x2000, // Auto tone portamento is not interruped by a tone portamento with parameter 0 + SONG_AUTO_GLOBALVOL = 0x4000, // Global volume slide command is continued automatically + SONG_AUTO_VIBRATO = 0x8000, // Vibrato command is continued automatically + SONG_AUTO_TREMOLO = 0x1'8000, // Tremolo command is continued automatically + SONG_AUTO_VOLSLIDE_STK = 0x2'0000, // Automatic volume slide command is interpreted like in STK files (rather than like in STP files) + SONG_FORMAT_NO_VOLCOL = 0x4'0000, // The original (imported) format has no volume column, so it can be hidden in the pattern editor. }; DECLARE_FLAGSET(SongFlags) -#define SONG_FILE_FLAGS (SONG_FASTVOLSLIDES|SONG_ITOLDEFFECTS|SONG_ITCOMPATGXX|SONG_LINEARSLIDES|SONG_EXFILTERRANGE|SONG_AMIGALIMITS|SONG_S3MOLDVIBRATO|SONG_PT_MODE|SONG_ISAMIGA|SONG_IMPORTED) -#define SONG_PLAY_FLAGS (~SONG_FILE_FLAGS) // Global Options (Renderer) #ifndef NO_AGC @@ -306,6 +343,16 @@ DECLARE_FLAGSET(SongFlags) inline constexpr uint32 MAX_GLOBAL_VOLUME = 256; inline constexpr uint32 MAX_PREAMP = 2000; +// When to execute a position override event +enum class OrderTransitionMode : uint8 +{ + AtPatternEnd, + AtMeasureEnd, + AtBeatEnd, + AtRowEnd, +}; + + // Resampling modes enum ResamplingMode : uint8 { @@ -558,6 +605,18 @@ enum PlayBehaviour kITResetFilterOnPortaSmpChange, // Filter is reset on portamento if sample is swapped kITInitialNoteMemory, // Initial "last note memory" for each channel is C-0 and not "no note" kPluginDefaultProgramAndBank1, // Default program and bank is set to 1 for plugins, so if an instrument is set to either of those, the program / bank change event is not sent to the plugin + kITNoSustainOnPortamento, // Do not re-enable sustain loop on portamento, even when switching between samples + kITEmptyNoteMapSlotIgnoreCell, // IT ignores the entire pattern cell when trying to play an unmapped note of an instrument + kITOffsetWithInstrNumber, // IT applies offset commands even if just an instrument number without note is present + kContinueSampleWithoutInstr, // FTM: A note without instrument number continues looped samples with the new pitch instead of retriggering them + kMIDINotesFromChannelPlugin, // Behaviour before OpenMPT 1.26: Channel plugin can be used to send MIDI notes + kITDoublePortamentoSlides, // IT only reads parameters once per row, so if two commands sharing effect parameters are found in the two effect columns, they influence each other + kS3MIgnoreCombinedFineSlides, // S3M commands Kxy and Lxy ignore fine slides + kFT2AutoVibratoAbortSweep, // Key-off before auto-vibrato sweep-in is complete resets auto-vibrato depth + kLegacyPPQpos, // Report fake PPQ position to VST plugins + kLegacyPluginNNABehaviour, // Plugin notes with NNA=continue are affected by note-offs etc. + kITCarryAfterNoteOff, // Envelope Carry continues to function as normal even after note-off + kFT2OffsetMemoryRequiresNote, // Offset memory is only updated when offset command is next to a note // Add new play behaviours here. diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Snd_fx.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/Snd_fx.cpp index 4937c6ca8..859b32bc6 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/Snd_fx.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Snd_fx.cpp @@ -5,7 +5,7 @@ * Notes : This needs some heavy refactoring. * I thought of actually adding an effect interface class. Every pattern effect * could then be moved into its own class that inherits from the effect interface. - * If effect handling differs severly between module formats, every format would have + * If effect handling differs severely between module formats, every format would have * its own class for that effect. Then, a call chain of effect classes could be set up * for each format, since effects cannot be processed in the same order in all formats. * Authors: Olivier Lapicque @@ -16,13 +16,14 @@ #include "stdafx.h" #include "Sndfile.h" +#include "MIDIMacroParser.h" #include "mod_specifications.h" #ifdef MODPLUG_TRACKER #include "../mptrack/Moddoc.h" #endif // MODPLUG_TRACKER #include "tuning.h" #include "Tables.h" -#include "modsmp_ctrl.h" // For updating the loop wraparound data with the invert loop effect +#include "modsmp_ctrl.h" // For updating the loop wraparound data with the invert loop effect #include "plugins/PlugInterface.h" #include "OPL.h" #include "MIDIEvents.h" @@ -62,7 +63,7 @@ protected: const CSoundFile &sndFile; public: - std::unique_ptr state; + std::unique_ptr state; struct ChnSettings { uint32 ticksToRender = 0; // When using sample sync, we still need to render this many ticks @@ -72,11 +73,13 @@ public: std::vector chnSettings; double elapsedTime; + const SEQUENCEINDEX m_sequence; static constexpr uint32 IGNORE_CHANNEL = uint32_max; - GetLengthMemory(const CSoundFile &sf) - : sndFile(sf) - , state(std::make_unique(sf.m_PlayState)) + GetLengthMemory(const CSoundFile &sf, SEQUENCEINDEX sequence) + : sndFile{sf} + , state{std::make_unique(sf.m_PlayState)} + , m_sequence{sequence} { Reset(); } @@ -87,10 +90,13 @@ public: state->m_midiMacroEvaluationResults.emplace(); elapsedTime = 0.0; state->m_lTotalSampleCount = 0; - state->m_nMusicSpeed = sndFile.m_nDefaultSpeed; - state->m_nMusicTempo = sndFile.m_nDefaultTempo; + state->m_nMusicSpeed = sndFile.Order(m_sequence).GetDefaultSpeed(); + state->m_nMusicTempo = sndFile.Order(m_sequence).GetDefaultTempo(); + state->m_ppqPosFract = 0.0; + state->m_ppqPosBeat = 0; state->m_nGlobalVolume = sndFile.m_nDefaultGlobalVolume; - chnSettings.assign(sndFile.GetNumChannels(), ChnSettings()); + state->m_globalScriptState.Initialize(sndFile); + chnSettings.assign(sndFile.GetNumChannels(), {}); const auto muteFlag = CSoundFile::GetChannelMuteFlag(); for(CHANNELINDEX chn = 0; chn < sndFile.GetNumChannels(); chn++) { @@ -189,6 +195,21 @@ public: break; } + if(chn.autoSlide.IsActive(AutoSlideCommand::TonePortamento) && !chn.rowCommand.IsTonePortamento()) + sndFile.TonePortamento(*state, channel, chn.portamentoSlide); + else if(chn.autoSlide.IsActive(AutoSlideCommand::TonePortamentoWithDuration)) + sndFile.TonePortamentoWithDuration(chn, 0); + if(chn.autoSlide.IsActive(AutoSlideCommand::PortamentoUp)) + sndFile.PortamentoUp(*state, channel, chn.nOldPortaUp, true); + else if(chn.autoSlide.IsActive(AutoSlideCommand::PortamentoDown)) + sndFile.PortamentoDown(*state, channel, chn.nOldPortaDown, true); + else if(chn.autoSlide.IsActive(AutoSlideCommand::FinePortamentoUp)) + sndFile.FinePortamentoUp(chn, chn.nOldFinePortaUpDown); + else if(chn.autoSlide.IsActive(AutoSlideCommand::FinePortamentoDown)) + sndFile.FinePortamentoDown(chn, chn.nOldFinePortaUpDown); + if(chn.autoSlide.IsActive(AutoSlideCommand::PortamentoFC)) + sndFile.PortamentoFC(chn); + updateInc = true; } @@ -280,6 +301,43 @@ public: } chnSettings[channel].ticksToRender = 0; } + + void GlobalVolSlide(ModChannel &chn, ModCommand::PARAM param, uint32 nonRowTicks) + { + if(sndFile.m_SongFlags[SONG_AUTO_GLOBALVOL]) + chn.autoSlide.SetActive(AutoSlideCommand::GlobalVolumeSlide, param != 0); + if(param) + chn.nOldGlobalVolSlide = param; + else + param = chn.nOldGlobalVolSlide; + + if((param & 0x0F) == 0x0F && (param & 0xF0)) + { + param >>= 4; + if(!(sndFile.GetType() & GLOBALVOL_7BIT_FORMATS)) + param <<= 1; + state->m_nGlobalVolume += param << 1; + } else if((param & 0xF0) == 0xF0 && (param & 0x0F)) + { + param = (param & 0x0F) << 1; + if(!(sndFile.GetType() & GLOBALVOL_7BIT_FORMATS)) + param <<= 1; + state->m_nGlobalVolume -= param; + } else if(param & 0xF0) + { + param >>= 4; + param <<= 1; + if(!(sndFile.GetType() & GLOBALVOL_7BIT_FORMATS)) + param <<= 1; + state->m_nGlobalVolume += param * nonRowTicks; + } else + { + param = (param & 0x0F) << 1; + if(!(sndFile.GetType() & GLOBALVOL_7BIT_FORMATS)) param <<= 1; + state->m_nGlobalVolume -= param * nonRowTicks; + } + Limit(state->m_nGlobalVolume, 0, 256); + } }; @@ -300,8 +358,8 @@ std::vector CSoundFile::GetLength(enmGetLengthResetMode adjustMod if(sequence >= Order.GetNumSequences()) sequence = Order.GetCurrentSequenceIndex(); const ModSequence &orderList = Order(sequence); - GetLengthMemory memory(*this); - CSoundFile::PlayState &playState = *memory.state; + GetLengthMemory memory(*this, sequence); + PlayState &playState = *memory.state; // Temporary visited rows vector (so that GetLength() won't interfere with the player code if the module is playing at the same time) RowVisitor visitedRows(*this, sequence); ROWINDEX allowedPatternLoopComplexity = 32768; @@ -332,7 +390,7 @@ std::vector CSoundFile::GetLength(enmGetLengthResetMode adjustMod for(CHANNELINDEX i = 0; i < GetNumChannels(); i++, m++) { if(m->note == NOTE_NOTECUT || m->note == NOTE_KEYOFF || (m->note == NOTE_FADE && GetNumInstruments()) - || (m->IsNote() && m->instr && !m->IsPortamento())) + || (m->IsNote() && m->instr && !m->IsTonePortamento())) { memory.chnSettings[i].ticksToRender = GetLengthMemory::IGNORE_CHANNEL; } @@ -360,9 +418,10 @@ std::vector CSoundFile::GetLength(enmGetLengthResetMode adjustMod } // Check if pattern is valid - playState.m_nPattern = playState.m_nCurrentOrder < orderList.size() ? orderList[playState.m_nCurrentOrder] : orderList.GetInvalidPatIndex(); + playState.m_nPattern = playState.m_nCurrentOrder < orderList.size() ? orderList[playState.m_nCurrentOrder] : PATTERNINDEX_INVALID; + playState.m_nTickCount = 0; - if(!Patterns.IsValidPat(playState.m_nPattern) && playState.m_nPattern != orderList.GetInvalidPatIndex() && target.mode == GetLengthTarget::SeekPosition && playState.m_nCurrentOrder == target.pos.order) + if(!Patterns.IsValidPat(playState.m_nPattern) && playState.m_nPattern != PATTERNINDEX_INVALID && target.mode == GetLengthTarget::SeekPosition && playState.m_nCurrentOrder == target.pos.order) { // Early test: Target is inside +++ or non-existing pattern retval.targetReached = true; @@ -372,7 +431,7 @@ std::vector CSoundFile::GetLength(enmGetLengthResetMode adjustMod while(playState.m_nPattern >= Patterns.Size()) { // End of song? - if((playState.m_nPattern == orderList.GetInvalidPatIndex()) || (playState.m_nCurrentOrder >= orderList.size())) + if((playState.m_nPattern == PATTERNINDEX_INVALID) || (playState.m_nCurrentOrder >= orderList.size())) { if(playState.m_nCurrentOrder == orderList.GetRestartPos()) break; @@ -382,14 +441,14 @@ std::vector CSoundFile::GetLength(enmGetLengthResetMode adjustMod { playState.m_nCurrentOrder++; } - playState.m_nPattern = (playState.m_nCurrentOrder < orderList.size()) ? orderList[playState.m_nCurrentOrder] : orderList.GetInvalidPatIndex(); + playState.m_nPattern = (playState.m_nCurrentOrder < orderList.size()) ? orderList[playState.m_nCurrentOrder] : PATTERNINDEX_INVALID; playState.m_nNextOrder = playState.m_nCurrentOrder; if((!Patterns.IsValidPat(playState.m_nPattern)) && visitedRows.Visit(playState.m_nCurrentOrder, 0, playState.Chn, ignoreRow)) { if(!hasSearchTarget) { - retval.lastOrder = playState.m_nCurrentOrder; - retval.lastRow = 0; + retval.restartOrder = playState.m_nCurrentOrder; + retval.restartRow = 0; } if(target.mode == GetLengthTarget::NoTarget || !visitedRows.GetFirstUnvisitedRow(playState.m_nNextOrder, playState.m_nRow, true)) { @@ -469,8 +528,8 @@ std::vector CSoundFile::GetLength(enmGetLengthResetMode adjustMod { if(!hasSearchTarget) { - retval.lastOrder = playState.m_nCurrentOrder; - retval.lastRow = playState.m_nRow; + retval.restartOrder = playState.m_nCurrentOrder; + retval.restartRow = playState.m_nRow; } if(target.mode == GetLengthTarget::NoTarget || !visitedRows.GetFirstUnvisitedRow(playState.m_nNextOrder, playState.m_nRow, true)) { @@ -501,6 +560,9 @@ std::vector CSoundFile::GetLength(enmGetLengthResetMode adjustMod playState.m_nRow = 0; } + playState.UpdatePPQ(breakToRow); + playState.UpdateTimeSignature(*this); + if(ignoreRow) continue; @@ -510,6 +572,7 @@ std::vector CSoundFile::GetLength(enmGetLengthResetMode adjustMod for(CHANNELINDEX nChn = 0; nChn < GetNumChannels(); nChn++, p++) { ModChannel &chn = playState.Chn[nChn]; + chn.isFirstTick = true; if(p->IsEmpty() || (ignoreMutedChn && ChnSettings[nChn].dwFlags[CHN_MUTE])) // not even effects are processed on muted S3M channels { chn.rowCommand.Clear(); @@ -526,6 +589,25 @@ std::vector CSoundFile::GetLength(enmGetLengthResetMode adjustMod chn.rowCommand.Clear(); continue; } + + if(p->IsNote()) + chn.nNewNote = chn.nLastNote = p->note; + else if(p->note > NOTE_MAX && m_playBehaviour[kITClearOldNoteAfterCut]) + chn.nNewNote = NOTE_NONE; + + if(m_playBehaviour[kITEmptyNoteMapSlotIgnoreCell] && p->instr > 0 && p->instr <= GetNumInstruments() + && Instruments[p->instr] != nullptr && !Instruments[p->instr]->HasValidMIDIChannel()) + { + auto note = (chn.rowCommand.note != NOTE_NONE) ? p->note : chn.nNewNote; + if (ModCommand::IsNote(note) && Instruments[p->instr]->Keyboard[note - NOTE_MIN] == 0) + { + chn.nNewNote = chn.nLastNote = note; + chn.nNewIns = p->instr; + chn.rowCommand.Clear(); + continue; + } + } + chn.rowCommand = *p; switch(p->command) { @@ -539,6 +621,7 @@ std::vector CSoundFile::GetLength(enmGetLengthResetMode adjustMod // ProTracker MODs with VBlank timing: All Fxx parameters set the tick count. if(p->param != 0) SetSpeed(playState, p->param); } + // Regular tempo handled below break; case CMD_S3MCMDEX: @@ -574,6 +657,9 @@ std::vector CSoundFile::GetLength(enmGetLengthResetMode adjustMod break; } } + // This may change speed/tempo/global volume/next row + playState.m_globalScriptState.NextTick(playState, *this); + const uint32 numTicks = playState.TicksOnRow(); const uint32 nonRowTicks = numTicks - std::max(playState.m_nPatternDelay, uint32(1)); @@ -584,23 +670,21 @@ std::vector CSoundFile::GetLength(enmGetLengthResetMode adjustMod for(CHANNELINDEX nChn = 0; nChn < GetNumChannels(); nChn++) { ModChannel &chn = playState.Chn[nChn]; - if(chn.rowCommand.IsEmpty()) + if(chn.rowCommand.IsEmpty() && !chn.autoSlide.AnyActive()) continue; ModCommand::COMMAND command = chn.rowCommand.command; ModCommand::PARAM param = chn.rowCommand.param; ModCommand::NOTE note = chn.rowCommand.note; - if(adjustMode & eAdjust) + if((adjustMode & eAdjust) && !chn.rowCommand.IsEmpty()) { if(chn.rowCommand.instr) { - chn.nNewIns = chn.rowCommand.instr; - chn.nLastNote = NOTE_NONE; + chn.swapSampleIndex = chn.nNewIns = chn.rowCommand.instr; memory.chnSettings[nChn].vol = 0xFF; } if(chn.rowCommand.IsNote()) { - chn.nLastNote = note; chn.RestorePanAndFilter(); if(!adjustSamplePos || memory.chnSettings[nChn].ticksToRender == GetLengthMemory::IGNORE_CHANNEL) @@ -616,6 +700,9 @@ std::vector CSoundFile::GetLength(enmGetLengthResetMode adjustMod if(instr->IsResonanceEnabled()) chn.nResonance = instr->GetResonance(); } + const bool wasGlobalSlideRunning = chn.autoSlide.IsActive(AutoSlideCommand::GlobalVolumeSlide); + chn.autoSlide.Reset(); + chn.autoSlide.SetActive(AutoSlideCommand::GlobalVolumeSlide, wasGlobalSlideRunning); } } @@ -678,42 +765,23 @@ std::vector CSoundFile::GetLength(enmGetLengthResetMode adjustMod if(!m_playBehaviour[kMODVBlankTiming]) { TEMPO tempo(CalculateXParam(playState.m_nPattern, playState.m_nRow, nChn), 0); - if ((adjustMode & eAdjust) && (GetType() & (MOD_TYPE_S3M | MOD_TYPE_IT | MOD_TYPE_MPT))) + if(GetType() & (MOD_TYPE_S3M | MOD_TYPE_IT | MOD_TYPE_MPT)) { if (tempo.GetInt()) chn.nOldTempo = static_cast(tempo.GetInt()); else tempo.Set(chn.nOldTempo); } - const auto &specs = GetModSpecifications(); if(tempo >= GetMinimumTempoParam(GetType())) { -#if MPT_MSVC_BEFORE(2019, 0) - // Work-around for VS2017 /std:c++17 /permissive- - // which fails to find operator < for templated user types inside std::min. - playState.m_nMusicTempo.SetRaw(std::min(tempo.GetRaw(), specs.GetTempoMax().GetRaw())); -#else - playState.m_nMusicTempo = std::min(tempo, specs.GetTempoMax()); -#endif + playState.m_flags.set(SONG_FIRSTTICK, !m_playBehaviour[kMODTempoOnSecondTick]); + SetTempo(playState, tempo, false); } else { // Tempo Slide - TEMPO tempoDiff((tempo.GetInt() & 0x0F) * nonRowTicks, 0); - if ((tempo.GetInt() & 0xF0) == 0x10) + playState.m_flags.reset(SONG_FIRSTTICK); + for(uint32 i = 0; i < nonRowTicks; i++) { - playState.m_nMusicTempo += tempoDiff; - } else - { - if(tempoDiff < playState.m_nMusicTempo) - playState.m_nMusicTempo -= tempoDiff; - else - playState.m_nMusicTempo.Set(0); + SetTempo(playState, tempo, false); } - - TEMPO tempoMin = specs.GetTempoMin(), tempoMax = specs.GetTempoMax(); - if(m_playBehaviour[kTempoClamp]) // clamp tempo correctly in compatible mode - { - tempoMax.Set(255); - } - Limit(playState.m_nMusicTempo, tempoMin, tempoMax); } } break; @@ -721,22 +789,9 @@ std::vector CSoundFile::GetLength(enmGetLengthResetMode adjustMod case CMD_S3MCMDEX: switch(param & 0xF0) { - case 0x90: - if(param <= 0x91) - chn.dwFlags.set(CHN_SURROUND, param == 0x91); - break; - - case 0xA0: // High sample offset - chn.nOldHiOffset = param & 0x0F; - break; - case 0xB0: // Pattern Loop PatternLoop(playState, nChn, param & 0x0F); break; - - case 0xF0: // Active macro - chn.nActiveMacro = param & 0x0F; - break; } break; @@ -746,26 +801,20 @@ std::vector CSoundFile::GetLength(enmGetLengthResetMode adjustMod case 0x60: // Pattern Loop PatternLoop(playState, nChn, param & 0x0F); break; - - case 0xF0: // Active macro - chn.nActiveMacro = param & 0x0F; - break; } break; - case CMD_XFINEPORTAUPDOWN: - // ignore high offset in compatible mode - if(((param & 0xF0) == 0xA0) && !m_playBehaviour[kFT2RestrictXCommand]) - chn.nOldHiOffset = param & 0x0F; - break; - default: break; } - // The following calculations are not interesting if we just want to get the song length. - if(!(adjustMode & eAdjust)) + // The following calculations are not interesting if we just want to get the song length... + // ...unless we're playing a Face The Music module with scripts that may modify the speed or tempo based on some volume or pitch variable (see schlendering.ftm) + if(!(adjustMode & eAdjust) && m_globalScript.empty()) continue; + + ResetAutoSlides(chn); + switch(command) { // Portamento Up/Down @@ -803,6 +852,12 @@ std::vector CSoundFile::GetLength(enmGetLengthResetMode adjustMod case CMD_TONEPORTAVOL: if (param) chn.nOldVolumeSlide = param; break; + case CMD_AUTO_VOLUMESLIDE: + AutoVolumeSlide(chn, param); + break; + case CMD_VOLUMEDOWN_ETX: + VolumeDownETX(playState, chn, param); + break; // Set Volume case CMD_VOLUME: memory.chnSettings[nChn].vol = param; @@ -821,40 +876,11 @@ std::vector CSoundFile::GetLength(enmGetLengthResetMode adjustMod { playState.m_nGlobalVolume = 256; } + playState.Chn[m_playBehaviour[kPerChannelGlobalVolSlide] ? nChn : 0].autoSlide.SetActive(AutoSlideCommand::GlobalVolumeSlide, false); break; // Global Volume Slide case CMD_GLOBALVOLSLIDE: - if(m_playBehaviour[kPerChannelGlobalVolSlide]) - { - // IT compatibility 16. Global volume slide params are stored per channel (FT2/IT) - if (param) chn.nOldGlobalVolSlide = param; else param = chn.nOldGlobalVolSlide; - } else - { - if (param) playState.Chn[0].nOldGlobalVolSlide = param; else param = playState.Chn[0].nOldGlobalVolSlide; - } - if (((param & 0x0F) == 0x0F) && (param & 0xF0)) - { - param >>= 4; - if (!(GetType() & GLOBALVOL_7BIT_FORMATS)) param <<= 1; - playState.m_nGlobalVolume += param << 1; - } else if (((param & 0xF0) == 0xF0) && (param & 0x0F)) - { - param = (param & 0x0F) << 1; - if (!(GetType() & GLOBALVOL_7BIT_FORMATS)) param <<= 1; - playState.m_nGlobalVolume -= param; - } else if (param & 0xF0) - { - param >>= 4; - param <<= 1; - if (!(GetType() & GLOBALVOL_7BIT_FORMATS)) param <<= 1; - playState.m_nGlobalVolume += param * nonRowTicks; - } else - { - param = (param & 0x0F) << 1; - if (!(GetType() & GLOBALVOL_7BIT_FORMATS)) param <<= 1; - playState.m_nGlobalVolume -= param * nonRowTicks; - } - Limit(playState.m_nGlobalVolume, 0, 256); + memory.GlobalVolSlide(playState.Chn[m_playBehaviour[kPerChannelGlobalVolSlide] ? nChn : 0], param, nonRowTicks); break; case CMD_CHANNELVOLUME: if (param <= 64) chn.nGlobalVol = param; @@ -872,29 +898,64 @@ std::vector CSoundFile::GetLength(enmGetLengthResetMode adjustMod else // Up volume += ((param & 0xF0) >> 4) * nonRowTicks; Limit(volume, 0, 64); - chn.nGlobalVol = volume; + chn.nGlobalVol = static_cast(volume); } break; + case CMD_VOLUMEDOWN_DURATION: + ChannelVolumeDownWithDuration(chn, param); + break; case CMD_PANNING8: Panning(chn, param, Pan8bit); break; case CMD_MODCMDEX: - if(param < 0x10) + switch(param & 0xF0) { - // LED filter + case 0x00: // LED filter for(CHANNELINDEX channel = 0; channel < GetNumChannels(); channel++) { playState.Chn[channel].dwFlags.set(CHN_AMIGAFILTER, !(param & 1)); } - } - [[fallthrough]]; - case CMD_S3MCMDEX: - if((param & 0xF0) == 0x80) - { + break; + + case 0x80: // Panning Panning(chn, (param & 0x0F), Pan4bit); + break; + + case 0xF0: // Active macro + chn.nActiveMacro = param & 0x0F; + break; } break; + case CMD_S3MCMDEX: + switch(param & 0xF0) + { + case 0x80: // Panning + Panning(chn, (param & 0x0F), Pan4bit); + break; + + case 0x90: // Extended channel effects + // Change play direction is handled in adjustSamplePos case + if (param < 0x9E) + ExtendedChannelEffect(chn, param, playState); + break; + + case 0xA0: // High sample offset + chn.nOldHiOffset = param & 0x0F; + break; + + case 0xF0: // Active macro + chn.nActiveMacro = param & 0x0F; + break; + } + break; + + case CMD_XFINEPORTAUPDOWN: + // ignore high offset in compatible mode + if (((param & 0xF0) == 0xA0) && !m_playBehaviour[kFT2RestrictXCommand]) + chn.nOldHiOffset = param & 0x0F; + break; + case CMD_VIBRATOVOL: if (param) chn.nOldVolumeSlide = param; param = 0; @@ -910,6 +971,9 @@ std::vector CSoundFile::GetLength(enmGetLengthResetMode adjustMod break; case CMD_PANBRELLO: Panbrello(chn, param); + // Panbrello effect is permanent in compatible mode, so actually apply panbrello for the last tick of this row + chn.nPanbrelloPos += static_cast(chn.nPanbrelloSpeed * nonRowTicks); + ProcessPanbrello(chn); break; case CMD_MIDI: @@ -945,44 +1009,41 @@ std::vector CSoundFile::GetLength(enmGetLengthResetMode adjustMod break; } - // Process vibrato / tremolo / panbrello - switch(chn.rowCommand.command) + chn.isFirstTick = true; + if(chn.autoSlide.IsActive(AutoSlideCommand::FineVolumeSlideUp) && command != CMD_AUTO_VOLUMESLIDE) + FineVolumeUp(chn, 0, false); + if(chn.autoSlide.IsActive(AutoSlideCommand::FineVolumeSlideDown) && command != CMD_AUTO_VOLUMESLIDE) + FineVolumeDown(chn, 0, false); + if(chn.autoSlide.IsActive(AutoSlideCommand::VolumeSlideSTK)) { - case CMD_VIBRATO: - case CMD_FINEVIBRATO: - case CMD_VIBRATOVOL: - if(adjustMode & eAdjust) + for(uint32 i = 0; i < numTicks; i++) { - uint32 vibTicks = ((GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT)) && !m_SongFlags[SONG_ITOLDEFFECTS]) ? numTicks : nonRowTicks; - uint32 inc = chn.nVibratoSpeed * vibTicks; - if(m_playBehaviour[kITVibratoTremoloPanbrello]) - inc *= 4; - chn.nVibratoPos += static_cast(inc); + chn.isFirstTick = (i == 0); + VolumeSlide(chn, 0); } - break; - - case CMD_TREMOLO: - if(adjustMode & eAdjust) - { - uint32 tremTicks = ((GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT)) && !m_SongFlags[SONG_ITOLDEFFECTS]) ? numTicks : nonRowTicks; - uint32 inc = chn.nTremoloSpeed * tremTicks; - if(m_playBehaviour[kITVibratoTremoloPanbrello]) - inc *= 4; - chn.nTremoloPos += static_cast(inc); - } - break; - - case CMD_PANBRELLO: - if(adjustMode & eAdjust) - { - // Panbrello effect is permanent in compatible mode, so actually apply panbrello for the last tick of this row - chn.nPanbrelloPos += static_cast(chn.nPanbrelloSpeed * (numTicks - 1)); - ProcessPanbrello(chn); - } - break; - - default: - break; + } + if(chn.autoSlide.IsActive(AutoSlideCommand::VolumeDownWithDuration)) + { + chn.volSlideDownRemain -= std::min(chn.volSlideDownRemain, mpt::saturate_cast(numTicks - 1)); + ChannelVolumeDownWithDuration(chn); + } + if(chn.autoSlide.IsActive(AutoSlideCommand::GlobalVolumeSlide) && command != CMD_GLOBALVOLSLIDE) + memory.GlobalVolSlide(chn, chn.nOldGlobalVolSlide, nonRowTicks); + if(command == CMD_VIBRATO || command == CMD_FINEVIBRATO || command == CMD_VIBRATOVOL || chn.autoSlide.IsActive(AutoSlideCommand::Vibrato)) + { + uint32 vibTicks = ((GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT)) && !m_SongFlags[SONG_ITOLDEFFECTS]) ? numTicks : nonRowTicks; + uint32 inc = chn.nVibratoSpeed * vibTicks; + if(m_playBehaviour[kITVibratoTremoloPanbrello]) + inc *= 4; + chn.nVibratoPos += static_cast(inc); + } + if(command == CMD_TREMOLO || chn.autoSlide.IsActive(AutoSlideCommand::Tremolo)) + { + uint32 tremTicks = ((GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT)) && !m_SongFlags[SONG_ITOLDEFFECTS]) ? numTicks : nonRowTicks; + uint32 inc = chn.nTremoloSpeed * tremTicks; + if(m_playBehaviour[kITVibratoTremoloPanbrello]) + inc *= 4; + chn.nTremoloPos += static_cast(inc); } if(m_playBehaviour[kST3EffectMemory] && command != CMD_NONE && param != 0) @@ -991,6 +1052,14 @@ std::vector CSoundFile::GetLength(enmGetLengthResetMode adjustMod } } + if(!m_globalScript.empty()) + { + for(playState.m_nTickCount = 1; playState.m_nTickCount < numTicks; playState.m_nTickCount++) + { + playState.m_globalScriptState.NextTick(playState, *this); + } + } + // Interpret F00 effect in XM files as "stop song" if(GetType() == MOD_TYPE_XM && playState.m_nMusicSpeed == uint16_max) { @@ -999,16 +1068,12 @@ std::vector CSoundFile::GetLength(enmGetLengthResetMode adjustMod continue; } - playState.m_nCurrentRowsPerBeat = m_nDefaultRowsPerBeat; - if(Patterns[playState.m_nPattern].GetOverrideSignature()) - { - playState.m_nCurrentRowsPerBeat = Patterns[playState.m_nPattern].GetRowsPerBeat(); - } - const uint32 tickDuration = GetTickDuration(playState); const uint32 rowDuration = tickDuration * numTicks; memory.elapsedTime += static_cast(rowDuration) / static_cast(m_MixerSettings.gdwMixingFreq); playState.m_lTotalSampleCount += rowDuration; + const ROWINDEX rowsPerBeat = playState.m_nCurrentRowsPerBeat ? playState.m_nCurrentRowsPerBeat : DEFAULT_ROWS_PER_BEAT; + playState.m_ppqPosFract += 1.0 / rowsPerBeat; if(adjustSamplePos) { @@ -1025,7 +1090,7 @@ std::vector CSoundFile::GetLength(enmGetLengthResetMode adjustMod uint32 paramHi = m.param >> 4, paramLo = m.param & 0x0F; uint32 startTick = 0; - const bool porta = m.IsPortamento(); + const bool porta = m.IsTonePortamento(); bool stopNote = false; if(m.instr) chn.prevNoteOffset = 0; @@ -1037,7 +1102,6 @@ std::vector CSoundFile::GetLength(enmGetLengthResetMode adjustMod chn.increment = GetChannelIncrement(chn, chn.nPeriod, 0).first; } int32 setPan = chn.nPan; - chn.nNewNote = chn.nLastNote; if(chn.nNewIns != 0) InstrumentChange(chn, chn.nNewIns, porta); NoteChange(chn, m.note, porta); HandleNoteChangeFilter(chn); @@ -1082,7 +1146,7 @@ std::vector CSoundFile::GetLength(enmGetLengthResetMode adjustMod startTick = playState.m_nMusicSpeed - 1; } else if(m.volcmd == VOLCMD_OFFSET) { - if(chn.pModSample != nullptr && m.vol <= std::size(chn.pModSample->cues)) + if(chn.pModSample != nullptr && !chn.pModSample->uFlags[CHN_ADLIB] && m.vol <= std::size(chn.pModSample->cues)) { SmpLength offset; if(m.vol == 0) @@ -1148,7 +1212,9 @@ std::vector CSoundFile::GetLength(enmGetLengthResetMode adjustMod case CMD_VIBRATOVOL: if(m.param || (GetType() != MOD_TYPE_MOD)) { - for(uint32 i = 0; i < numTicks; i++) + // ST3 compatibility: Do not run combined slides (Kxy / Lxy) on first tick + // Test cases: NoCombinedSlidesOnFirstTick-Normal.s3m, NoCombinedSlidesOnFirstTick-Fast.s3m + for(uint32 i = (m_playBehaviour[kS3MIgnoreCombinedFineSlides] ? 1 : 0); i < numTicks; i++) { chn.isFirstTick = (i == 0); VolumeSlide(chn, m.param); @@ -1169,19 +1235,13 @@ std::vector CSoundFile::GetLength(enmGetLengthResetMode adjustMod break; case CMD_S3MCMDEX: - if(m.param == 0x9E) + if((m.param & 0xF0) == 0x90) { - // Play forward - memory.RenderChannel(nChn, oldTickDuration); // Re-sync what we've got so far - chn.dwFlags.reset(CHN_PINGPONGFLAG); - } else if(m.param == 0x9F) - { - // Reverse - memory.RenderChannel(nChn, oldTickDuration); // Re-sync what we've got so far - chn.dwFlags.set(CHN_PINGPONGFLAG); - if(!chn.position.GetInt() && chn.nLength && (m.IsNote() || !chn.dwFlags[CHN_LOOP])) + // Change play direction - other cases already handled above + if(m.param == 0x9E || m.param == 0x9F) { - chn.position.Set(chn.nLength - 1, SamplePosition::fractMax); + memory.RenderChannel(nChn, oldTickDuration); // Re-sync what we've got so far + ExtendedChannelEffect(chn, m.param, playState); } } else if((m.param & 0xF0) == 0x70) { @@ -1199,6 +1259,34 @@ std::vector CSoundFile::GetLength(enmGetLengthResetMode adjustMod memory.RenderChannel(nChn, oldTickDuration); // Re-sync what we've got so far chn.microTuning = CalculateFinetuneTarget(playState.m_nPattern, playState.m_nRow, nChn); // TODO should render each tick individually for CMD_FINETUNE_SMOOTH for higher sync accuracy break; + + // Auto portamentos + case CMD_AUTO_PORTAUP: + chn.autoSlide.SetActive(AutoSlideCommand::PortamentoUp, m.param != 0); + chn.nOldPortaUp = m.param; + break; + case CMD_AUTO_PORTADOWN: + chn.autoSlide.SetActive(AutoSlideCommand::PortamentoDown, m.param != 0); + chn.nOldPortaDown = m.param; + break; + case CMD_AUTO_PORTAUP_FINE: + chn.autoSlide.SetActive(AutoSlideCommand::FinePortamentoUp, m.param != 0); + chn.nOldFinePortaUpDown = m.param; + break; + case CMD_AUTO_PORTADOWN_FINE: + chn.autoSlide.SetActive(AutoSlideCommand::FinePortamentoDown, m.param != 0); + chn.nOldFinePortaUpDown = m.param; + break; + case CMD_AUTO_PORTAMENTO_FC: + chn.autoSlide.SetActive(AutoSlideCommand::PortamentoFC, m.param != 0); + chn.nOldPortaUp = chn.nOldPortaDown = m.param; + break; + + case CMD_TONEPORTA_DURATION: + if(chn.rowCommand.IsNote()) + TonePortamentoWithDuration(chn, m.param); + break; + default: break; } @@ -1229,8 +1317,9 @@ std::vector CSoundFile::GetLength(enmGetLengthResetMode adjustMod } break; case VOLCMD_PLAYCONTROL: - if(m.vol <= 1) - chn.isPaused = (m.vol == 0); + if(m.vol >= 2 && m.vol <= 4) + memory.RenderChannel(nChn, oldTickDuration); // Re-sync what we've got so far + chn.PlayControl(m.vol); break; default: break; @@ -1238,7 +1327,8 @@ std::vector CSoundFile::GetLength(enmGetLengthResetMode adjustMod if(chn.isPaused) continue; - if(m.IsAnyPitchSlide()) + + if(m.IsAnyPitchSlide() || chn.autoSlide.AnyPitchSlideActive()) { // Portamento needs immediate syncing, as the pitch changes on each tick uint32 portaTick = memory.chnSettings[nChn].ticksToRender + startTick; @@ -1270,8 +1360,8 @@ std::vector CSoundFile::GetLength(enmGetLengthResetMode adjustMod if(retval.targetReached) { - retval.lastOrder = playState.m_nCurrentOrder; - retval.lastRow = playState.m_nRow; + retval.restartOrder = playState.m_nCurrentOrder; + retval.restartRow = playState.m_nRow; } retval.duration = memory.elapsedTime; results.push_back(retval); @@ -1289,21 +1379,17 @@ std::vector CSoundFile::GetLength(enmGetLengthResetMode adjustMod m_PlayState.m_nNextRow = m_PlayState.m_nRow; m_PlayState.m_nFrameDelay = m_PlayState.m_nPatternDelay = 0; m_PlayState.m_nTickCount = TICKS_ROW_FINISHED; - m_PlayState.m_bPositionChanged = true; + m_PlayState.m_flags.set(SONG_POSITIONCHANGED); if(m_opl != nullptr) m_opl->Reset(); for(CHANNELINDEX n = 0; n < GetNumChannels(); n++) { auto &chn = m_PlayState.Chn[n]; - if(chn.nLastNote != NOTE_NONE) - { - chn.nNewNote = chn.nLastNote; - } if(memory.chnSettings[n].vol != 0xFF && !adjustSamplePos) { chn.nVolume = std::min(memory.chnSettings[n].vol, uint8(64)) * 4; } - if(chn.pModSample != nullptr && chn.pModSample->uFlags[CHN_ADLIB] && m_opl) + if(!chn.dwFlags[CHN_MUTE | CHN_SYNCMUTE] && chn.pModSample != nullptr && chn.pModSample->uFlags[CHN_ADLIB] && m_opl) { m_opl->Patch(n, chn.pModSample->adlib); m_opl->NoteCut(n); @@ -1344,12 +1430,14 @@ std::vector CSoundFile::GetLength(enmGetLengthResetMode adjustMod { m_MixPlugins[plug].fDryRatio = dryWetRatio; } + + UpdatePluginPositions(); #endif // NO_PLUGINS } else if(adjustMode != eAdjustOnSuccess) { // Target not found (e.g. when jumping to a hidden sub song), reset global variables... - m_PlayState.m_nMusicSpeed = m_nDefaultSpeed; - m_PlayState.m_nMusicTempo = m_nDefaultTempo; + m_PlayState.m_nMusicSpeed = Order(sequence).GetDefaultSpeed(); + m_PlayState.m_nMusicTempo = Order(sequence).GetDefaultTempo(); m_PlayState.m_nGlobalVolume = m_nDefaultGlobalVolume; } // When adjusting the playback status, we will also want to update the visited rows vector according to the current position. @@ -1405,7 +1493,7 @@ void CSoundFile::InstrumentChange(ModChannel &chn, uint32 instr, bool bPorta, bo // We won't ignore them if a plugin is assigned to this slot, so that VSTis still work as intended. // Test case: emptyslot.it, PortaInsNum.it, gxsmp.it, gxsmp2.it chn.pModInstrument = nullptr; - chn.nNewIns = 0; + chn.swapSampleIndex = chn.nNewIns = 0; return; } pSmp = nullptr; @@ -1446,7 +1534,7 @@ void CSoundFile::InstrumentChange(ModChannel &chn, uint32 instr, bool bPorta, bo // but still uses the sample info from the old one (bug?) returnAfterVolumeAdjust = true; } - // IT compatbility: Reset filter if portamento results in sample change + // IT compatibility: Reset filter if portamento results in sample change // Test case: FilterPortaSmpChange.it, FilterPortaSmpChange-InsMode.it if(m_playBehaviour[kITResetFilterOnPortaSmpChange] && !m_nInstruments) chn.triggerNote = true; @@ -1514,7 +1602,7 @@ void CSoundFile::InstrumentChange(ModChannel &chn, uint32 instr, bool bPorta, bo if(returnAfterVolumeAdjust) return; // Instrument adjust - chn.nNewIns = 0; + chn.swapSampleIndex = chn.nNewIns = 0; // IT Compatiblity: NNA is reset on every note change, not every instrument change (fixes s7xinsnum.it). if (pIns && ((!m_playBehaviour[kITNNAReset] && pSmp) || pIns->nMixPlug || instrumentChanged)) @@ -1555,10 +1643,12 @@ void CSoundFile::InstrumentChange(ModChannel &chn, uint32 instr, bool bPorta, bo reset = (!chn.nLength || (insNumber && bPorta && m_SongFlags[SONG_ITCOMPATGXX]) || (insNumber && !bPorta && chn.dwFlags[CHN_NOTEFADE | CHN_KEYOFF] && m_SongFlags[SONG_ITOLDEFFECTS])); - // NOTE: IT2.14 with SB/GUS/etc. output is different. We are going after IT's WAV writer here. - // For SB/GUS/etc. emulation, envelope carry should only apply when the NNA isn't set to "Note Cut". + // NOTE: Carry behaviour is not consistent between IT drivers. + // If NNA is set to "Note Cut", carry only works if the driver uses volume ramping on cut notes. + // This means that the normal SB and GUS drivers behave differently than what is implemented here. + // We emulate IT's WAV writer and SB16 MMX driver instead. // Test case: CarryNNA.it - resetAlways = (!chn.nFadeOutVol || instrumentChanged || chn.dwFlags[CHN_KEYOFF]); + resetAlways = !chn.nFadeOutVol || instrumentChanged || (m_playBehaviour[kITCarryAfterNoteOff] ? !chn.rowCommand.IsNote() : chn.dwFlags[CHN_KEYOFF]); } else { reset = (!bPorta || !(GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT | MOD_TYPE_DBM)) || m_SongFlags[SONG_ITCOMPATGXX] @@ -1607,9 +1697,15 @@ void CSoundFile::InstrumentChange(ModChannel &chn, uint32 instr, bool bPorta, bo return; } + const bool wasKeyOff = chn.dwFlags[CHN_KEYOFF]; + // Tone-Portamento doesn't reset the pingpong direction flag if(bPorta && pSmp == chn.pModSample && pSmp != nullptr) { + // IT compatibility: Instrument change but sample stays the same: still reset the key-off flags + // Test case: SampleSustainAfterPortaInstrMode.it + if(instrumentChanged && pIns && m_playBehaviour[kITNoSustainOnPortamento]) + chn.dwFlags.reset(CHN_KEYOFF | CHN_NOTEFADE); // If channel length is 0, we cut a previous sample using SCx. In that case, we have to update sample length, loop points, etc... if(GetType() & (MOD_TYPE_S3M|MOD_TYPE_IT|MOD_TYPE_MPT) && chn.nLength != 0) return; @@ -1718,7 +1814,9 @@ void CSoundFile::InstrumentChange(ModChannel &chn, uint32 instr, bool bPorta, bo } chn.m_PortamentoFineSteps = 0; - if(chn.dwFlags[CHN_SUSTAINLOOP]) + // IT compatibility: Do not reset sustain loop status when using portamento after key-off + // Test case: SampleSustainAfterPorta.it, SampleSustainAfterPortaCompatGxx.it, SampleSustainAfterPortaInstrMode.it + if(chn.dwFlags[CHN_SUSTAINLOOP] && (!m_playBehaviour[kITNoSustainOnPortamento] || !bPorta || (pIns && !wasKeyOff))) { chn.nLoopStart = pSmp->nSustainStart; chn.nLoopEnd = pSmp->nSustainEnd; @@ -1872,7 +1970,7 @@ void CSoundFile::NoteChange(ModChannel &chn, int note, bool bPorta, bool bResetE chn.isPaused = false; if ((!bPorta) || (GetType() & (MOD_TYPE_S3M|MOD_TYPE_IT|MOD_TYPE_MPT))) - chn.nNewIns = 0; + chn.swapSampleIndex = chn.nNewIns = 0; uint32 period = GetPeriodFromNote(note, chn.nFineTune, chn.nC5Speed); chn.nPanbrelloOffset = 0; @@ -2099,6 +2197,11 @@ void CSoundFile::NoteChange(ModChannel &chn, int note, bool bPorta, bool bResetE { chn.paulaState.Reset(); } + const bool wasGlobalSlideRunning = chn.autoSlide.IsActive(AutoSlideCommand::GlobalVolumeSlide); + const bool wasChannelVolSlideRunning = chn.autoSlide.IsActive(AutoSlideCommand::VolumeDownWithDuration); + chn.autoSlide.Reset(); + chn.autoSlide.SetActive(AutoSlideCommand::GlobalVolumeSlide, wasGlobalSlideRunning); + chn.autoSlide.SetActive(AutoSlideCommand::VolumeDownWithDuration, wasChannelVolSlideRunning); } @@ -2118,7 +2221,7 @@ void CSoundFile::ApplyInstrumentPanning(ModChannel &chn, const ModInstrument *in chn.SetInstrumentPan(newPan, *this); // IT compatibility: Sample and instrument panning overrides channel surround status. // Test case: SmpInsPanSurround.it - if(m_playBehaviour[kPanOverride] && !m_SongFlags[SONG_SURROUNDPAN]) + if(m_playBehaviour[kPanOverride] && !m_PlayState.m_flags[SONG_SURROUNDPAN]) { chn.dwFlags.reset(CHN_SURROUND); } @@ -2129,7 +2232,7 @@ void CSoundFile::ApplyInstrumentPanning(ModChannel &chn, const ModInstrument *in CHANNELINDEX CSoundFile::GetNNAChannel(CHANNELINDEX nChn) const { // Check for empty channel - for(CHANNELINDEX i = m_nChannels; i < MAX_CHANNELS; i++) + for(CHANNELINDEX i = GetNumChannels(); i < m_PlayState.Chn.size(); i++) { const ModChannel &c = m_PlayState.Chn[i]; // Sample playing? @@ -2141,9 +2244,6 @@ CHANNELINDEX CSoundFile::GetNNAChannel(CHANNELINDEX nChn) const // Has the plugin note already been released? (note: lastMidiNoteWithoutArp is set from within IMixPlugin, so this implies that there is a valid plugin assignment) if(c.dwFlags[CHN_KEYOFF | CHN_NOTEFADE] || c.lastMidiNoteWithoutArp == NOTE_NONE) return i; - // Stopped OPL channel - if(c.dwFlags[CHN_ADLIB] && (!m_opl || !m_opl->IsActive(i))) - return i; } int32 vol = 0x800100; @@ -2158,9 +2258,12 @@ CHANNELINDEX CSoundFile::GetNNAChannel(CHANNELINDEX nChn) const // All channels are used: check for lowest volume CHANNELINDEX result = CHANNELINDEX_INVALID; uint32 envpos = 0; - for(CHANNELINDEX i = m_nChannels; i < MAX_CHANNELS; i++) + for(CHANNELINDEX i = GetNumChannels(); i < m_PlayState.Chn.size(); i++) { const ModChannel &c = m_PlayState.Chn[i]; + // Stopped OPL channel + if(c.dwFlags[CHN_ADLIB] && (!m_opl || !m_opl->IsActive(i))) + return i; if(c.nLength && !c.nFadeOutVol) return i; // Use a combination of real volume [14 bit] (which includes volume envelopes, but also potentially global volume) and note volume [9 bit]. @@ -2170,7 +2273,10 @@ CHANNELINDEX CSoundFile::GetNNAChannel(CHANNELINDEX nChn) const // Less priority to looped samples if(c.dwFlags[CHN_LOOP]) v /= 2; - if((v < vol) || ((v == vol) && (c.VolEnv.nEnvPosition > envpos))) + // Less priority for channels potentially held for plugin notes with NNA=continue the older they get + if(!c.nLength && c.nMasterChn) + v -= std::min(static_cast(c.nnaChannelAge) * c.nnaChannelAge, static_cast(int32_max / 16)) * 16; + if((v < vol) || ((v == vol) && (c.VolEnv.nEnvPosition > envpos || !c.VolEnv.flags[ENV_ENABLED]))) { envpos = c.VolEnv.nEnvPosition; vol = v; @@ -2188,12 +2294,33 @@ CHANNELINDEX CSoundFile::CheckNNA(CHANNELINDEX nChn, uint32 instr, int note, boo if(!ModCommand::IsNote(static_cast(note))) return CHANNELINDEX_INVALID; - // Always NNA cut - using - if((!(GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT | MOD_TYPE_MT2)) || !m_nInstruments || forceCut) && !srcChn.HasMIDIOutput()) + // Do we need to apply New/Duplicate Note Action to an instrument plugin? +#ifndef NO_PLUGINS + IMixPlugin *pPlugin = nullptr; + if(srcChn.HasMIDIOutput() && ModCommand::IsNote(srcChn.nNote)) // Instrument has MIDI channel assigned (but not necessarily a plugin) + { + const PLUGINDEX plugin = GetBestPlugin(m_PlayState.Chn[nChn], nChn, PrioritiseInstrument, RespectMutes); + if(plugin > 0 && plugin <= MAX_MIXPLUGINS) + pPlugin = m_MixPlugins[plugin - 1].pMixPlugin; + } + // apply NNA to this plugin iff it is currently playing a note on this tracker channel + // (and if it is playing a note, we know that would be the last note played on this chan). + const bool applyNNAtoPlug = pPlugin && (srcChn.lastMidiNoteWithoutArp != NOTE_NONE) && pPlugin->IsNotePlaying(srcChn.lastMidiNoteWithoutArp, nChn); +#else + const bool applyNNAtoPlug = false; +#endif // NO_PLUGINS + + // Always NNA cut + if(!(GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT | MOD_TYPE_MT2)) || !m_nInstruments || forceCut) { if(!srcChn.nLength || srcChn.dwFlags[CHN_MUTE] || !(srcChn.rightVol | srcChn.leftVol)) return CHANNELINDEX_INVALID; +#ifndef NO_PLUGINS + if(applyNNAtoPlug) + SendMIDINote(nChn, NOTE_KEYOFF, 0, m_playBehaviour[kMIDINotesFromChannelPlugin] ? pPlugin : nullptr); +#endif // NO_PLUGINS + if(srcChn.dwFlags[CHN_ADLIB] && m_opl) { m_opl->NoteCut(nChn, false); @@ -2204,6 +2331,7 @@ CHANNELINDEX CSoundFile::CheckNNA(CHANNELINDEX nChn, uint32 instr, int note, boo if(nnaChn == CHANNELINDEX_INVALID) return CHANNELINDEX_INVALID; ModChannel &chn = m_PlayState.Chn[nnaChn]; + StopOldNNA(chn, nnaChn); // Copy Channel chn = srcChn; chn.dwFlags.reset(CHN_VIBRATO | CHN_TREMOLO | CHN_MUTE | CHN_PORTAMENTO); @@ -2214,6 +2342,8 @@ CHANNELINDEX CSoundFile::CheckNNA(CHANNELINDEX nChn, uint32 instr, int note, boo // Cut the note chn.nFadeOutVol = 0; chn.dwFlags.set(CHN_NOTEFADE | CHN_FASTVOLRAMP); + chn.nnaChannelAge = 0; + chn.nnaGeneration = ++srcChn.nnaGeneration; // Stop this channel srcChn.nLength = 0; srcChn.position.Set(0); @@ -2250,10 +2380,10 @@ CHANNELINDEX CSoundFile::CheckNNA(CHANNELINDEX nChn, uint32 instr, int note, boo if(srcChn.dwFlags[CHN_MUTE]) return CHANNELINDEX_INVALID; - for(CHANNELINDEX i = nChn; i < MAX_CHANNELS; i++) + for(CHANNELINDEX i = nChn; i < m_PlayState.Chn.size(); i++) { // Only apply to background channels, or the same pattern channel - if(i < m_nChannels && i != nChn) + if(i < GetNumChannels() && i != nChn) continue; ModChannel &chn = m_PlayState.Chn[i]; @@ -2350,33 +2480,14 @@ CHANNELINDEX CSoundFile::CheckNNA(CHANNELINDEX nChn, uint32 instr, int note, boo } } - // Do we need to apply New/Duplicate Note Action to a VSTi? - bool applyNNAtoPlug = false; -#ifndef NO_PLUGINS - IMixPlugin *pPlugin = nullptr; - if(srcChn.HasMIDIOutput() && ModCommand::IsNote(srcChn.nNote)) // instro sends to a midi chan - { - PLUGINDEX plugin = GetBestPlugin(m_PlayState, nChn, PrioritiseInstrument, RespectMutes); - - if(plugin > 0 && plugin <= MAX_MIXPLUGINS) - { - pPlugin = m_MixPlugins[plugin - 1].pMixPlugin; - if(pPlugin) - { - // apply NNA to this plugin iff it is currently playing a note on this tracker channel - // (and if it is playing a note, we know that would be the last note played on this chan). - applyNNAtoPlug = (srcChn.lastMidiNoteWithoutArp != NOTE_NONE) && pPlugin->IsNotePlaying(srcChn.lastMidiNoteWithoutArp, nChn); - } - } - } -#endif // NO_PLUGINS - // New Note Action if(!srcChn.IsSamplePlaying() && !applyNNAtoPlug) return CHANNELINDEX_INVALID; + const CHANNELINDEX nnaChn = GetNNAChannel(nChn); + #ifndef NO_PLUGINS - if(applyNNAtoPlug && pPlugin) + if(applyNNAtoPlug) { switch(srcChn.nNNA) { @@ -2384,23 +2495,26 @@ CHANNELINDEX CSoundFile::CheckNNA(CHANNELINDEX nChn, uint32 instr, int note, boo case NewNoteAction::NoteCut: case NewNoteAction::NoteFade: // Switch off note played on this plugin, on this tracker channel and midi channel - SendMIDINote(nChn, NOTE_KEYOFF, 0); + SendMIDINote(nChn, NOTE_KEYOFF, 0, m_playBehaviour[kMIDINotesFromChannelPlugin] ? pPlugin : nullptr); srcChn.nArpeggioLastNote = NOTE_NONE; srcChn.lastMidiNoteWithoutArp = NOTE_NONE; break; case NewNoteAction::Continue: + // If there's no NNA channels available, avoid the note lingering on forever + if(nnaChn == CHANNELINDEX_INVALID) + SendMIDINote(nChn, NOTE_KEYOFF, 0, m_playBehaviour[kMIDINotesFromChannelPlugin] ? pPlugin : nullptr); + else if(!m_playBehaviour[kLegacyPluginNNABehaviour]) + pPlugin->MoveChannel(nChn, nnaChn); break; } } #endif // NO_PLUGINS - CHANNELINDEX nnaChn = GetNNAChannel(nChn); if(nnaChn == CHANNELINDEX_INVALID) return CHANNELINDEX_INVALID; ModChannel &chn = m_PlayState.Chn[nnaChn]; - if(chn.dwFlags[CHN_ADLIB] && m_opl) - m_opl->NoteCut(nnaChn); + StopOldNNA(chn, nnaChn); // Copy Channel chn = srcChn; chn.dwFlags.reset(CHN_VIBRATO | CHN_TREMOLO | CHN_PORTAMENTO); @@ -2408,6 +2522,8 @@ CHANNELINDEX CSoundFile::CheckNNA(CHANNELINDEX nChn, uint32 instr, int note, boo chn.nMasterChn = nChn < GetNumChannels() ? nChn + 1 : 0; chn.nCommand = CMD_NONE; + chn.nnaChannelAge = 0; + chn.nnaGeneration = ++srcChn.nnaGeneration; // Key Off the note switch(srcChn.nNNA) @@ -2416,9 +2532,14 @@ CHANNELINDEX CSoundFile::CheckNNA(CHANNELINDEX nChn, uint32 instr, int note, boo KeyOff(chn); if(chn.dwFlags[CHN_ADLIB] && m_opl) { - m_opl->NoteOff(nChn); if(m_playBehaviour[kOPLwithNNA]) + { m_opl->MoveChannel(nChn, nnaChn); + m_opl->NoteOff(nnaChn); // This needs to be done on the NNA channel so that our PlaybackTest implementation knows that it belongs to the "old" note, not to the "new" note + } else + { + m_opl->NoteOff(nChn); + } } break; case NewNoteAction::NoteCut: @@ -2456,6 +2577,31 @@ CHANNELINDEX CSoundFile::CheckNNA(CHANNELINDEX nChn, uint32 instr, int note, boo } +void CSoundFile::StopOldNNA(ModChannel &chn, CHANNELINDEX channel) +{ + if(chn.dwFlags[CHN_ADLIB] && m_opl) + m_opl->NoteCut(channel); + +#ifndef NO_PLUGINS + // Is a plugin note still associated with this old NNA channel? Stop it first. + if(chn.HasMIDIOutput() && ModCommand::IsNote(chn.nNote) && !chn.dwFlags[CHN_KEYOFF] && chn.lastMidiNoteWithoutArp != NOTE_NONE) + { + const PLUGINDEX plugin = GetBestPlugin(m_PlayState.Chn[channel], channel, PrioritiseInstrument, RespectMutes); + if(plugin > 0 && plugin <= MAX_MIXPLUGINS) + { + IMixPlugin *nnaPlugin = m_MixPlugins[plugin - 1].pMixPlugin; + // apply NNA to this plugin iff it is currently playing a note on this tracker channel + // (and if it is playing a note, we know that would be the last note played on this chan). + if(nnaPlugin && (chn.lastMidiNoteWithoutArp != NOTE_NONE) && nnaPlugin->IsNotePlaying(chn.lastMidiNoteWithoutArp, channel)) + { + SendMIDINote(channel, chn.lastMidiNoteWithoutArp | IMixPlugin::MIDI_NOTE_OFF, 0, m_playBehaviour[kMIDINotesFromChannelPlugin] ? nnaPlugin : nullptr); + } + } + } +#endif // NO_PLUGINS +} + + bool CSoundFile::ProcessEffects() { m_PlayState.m_breakRow = ROWINDEX_INVALID; // Is changed if a break to row command is encountered @@ -2468,13 +2614,13 @@ bool CSoundFile::ProcessEffects() const uint32 tickCount = m_PlayState.m_nTickCount % (m_PlayState.m_nMusicSpeed + m_PlayState.m_nFrameDelay); uint32 instr = chn.rowCommand.instr; ModCommand::VOLCMD volcmd = chn.rowCommand.volcmd; - uint32 vol = chn.rowCommand.vol; + ModCommand::VOL vol = chn.rowCommand.vol; ModCommand::COMMAND cmd = chn.rowCommand.command; uint32 param = chn.rowCommand.param; - bool bPorta = chn.rowCommand.IsPortamento(); + bool bPorta = chn.rowCommand.IsTonePortamento(); uint32 nStartTick = 0; - chn.isFirstTick = m_SongFlags[SONG_FIRSTTICK]; + chn.isFirstTick = m_PlayState.m_flags[SONG_FIRSTTICK]; // Process parameter control note. if(chn.rowCommand.note == NOTE_PC) @@ -2485,7 +2631,7 @@ bool CSoundFile::ProcessEffects() const PlugParamValue value = chn.rowCommand.GetValueEffectCol() / PlugParamValue(ModCommand::maxColumnValue); if(plug > 0 && plug <= MAX_MIXPLUGINS && m_MixPlugins[plug - 1].pMixPlugin) - m_MixPlugins[plug-1].pMixPlugin->SetParameter(plugparam, value); + m_MixPlugins[plug-1].pMixPlugin->SetParameter(plugparam, value, &m_PlayState, nChn); #endif // NO_PLUGINS } @@ -2498,7 +2644,7 @@ bool CSoundFile::ProcessEffects() if(chn.rowCommand.note == NOTE_PCS || (cmd == CMD_NONE && chn.m_plugParamValueStep != 0)) { #ifndef NO_PLUGINS - const bool isFirstTick = m_SongFlags[SONG_FIRSTTICK]; + const bool isFirstTick = m_PlayState.m_flags[SONG_FIRSTTICK]; if(isFirstTick) chn.m_RowPlug = chn.rowCommand.instr; const PLUGINDEX plugin = chn.m_RowPlug; @@ -2516,10 +2662,10 @@ bool CSoundFile::ProcessEffects() } if(m_PlayState.m_nTickCount + 1 == m_PlayState.TicksOnRow()) { // On last tick, set parameter exactly to target value. - m_MixPlugins[plugin - 1].pMixPlugin->SetParameter(plugparam, chn.m_plugParamTargetValue); + m_MixPlugins[plugin - 1].pMixPlugin->SetParameter(plugparam, chn.m_plugParamTargetValue, &m_PlayState, nChn); } else - m_MixPlugins[plugin - 1].pMixPlugin->ModifyParameter(plugparam, chn.m_plugParamValueStep); + m_MixPlugins[plugin - 1].pMixPlugin->ModifyParameter(plugparam, chn.m_plugParamValueStep, m_PlayState, nChn); } #endif // NO_PLUGINS } @@ -2528,7 +2674,7 @@ bool CSoundFile::ProcessEffects() // To achieve this, clearing the note data so that rest of the process sees the row as empty row. if(ModCommand::IsPcNote(chn.rowCommand.note)) { - chn.ClearRowCmd(); + chn.rowCommand.Clear(); instr = 0; volcmd = VOLCMD_NONE; vol = 0; @@ -2537,8 +2683,30 @@ bool CSoundFile::ProcessEffects() bPorta = false; } + // IT compatibility: Empty sample mapping + // This is probably the single biggest WTF replayer bug in Impulse Tracker. + // In instrument mode, when an note + instrument is triggered that does not map to any sample, the entire cell (including potentially present global effects!) + // is ignored. Even better, if on a following row another instrument number (this time without a note) is encountered, we end up in the same situation! + // Test cases: NoMap.it, NoMapEffects.it + if(m_playBehaviour[kITEmptyNoteMapSlotIgnoreCell] && instr > 0 && instr <= GetNumInstruments() + && Instruments[instr] != nullptr && !Instruments[instr]->HasValidMIDIChannel()) + { + auto note = (chn.rowCommand.note != NOTE_NONE) ? chn.rowCommand.note : chn.nNewNote; + if(ModCommand::IsNote(note) && Instruments[instr]->Keyboard[note - NOTE_MIN] == 0) + { + chn.nNewNote = chn.nLastNote = note; + chn.nNewIns = static_cast(instr); + chn.rowCommand.Clear(); + continue; + } + } + + const bool continueNote = !bPorta && m_playBehaviour[kContinueSampleWithoutInstr] && !chn.rowCommand.instr && chn.dwFlags[CHN_LOOP] && chn.pCurrentSample; + if(continueNote) + bPorta = true; + // Process Invert Loop (MOD Effect, called every row if it's active) - if(!m_SongFlags[SONG_FIRSTTICK]) + if(!m_PlayState.m_flags[SONG_FIRSTTICK]) { InvertLoop(m_PlayState.Chn[nChn]); } else @@ -2582,7 +2750,7 @@ bool CSoundFile::ProcessEffects() } continue; } - } else if(m_SongFlags[SONG_FIRSTTICK]) + } else if(m_PlayState.m_flags[SONG_FIRSTTICK]) { // Pattern Loop ? if((param & 0xF0) == 0xE0) @@ -2660,6 +2828,7 @@ bool CSoundFile::ProcessEffects() if(!triggerNote && chn.IsSamplePlaying()) { chn.nNewIns = static_cast(instr); + chn.swapSampleIndex = GetSampleIndex(chn.nLastNote, instr); if(instr <= GetNumSamples()) { chn.nVolume = Samples[instr].nVolume; @@ -2672,7 +2841,11 @@ bool CSoundFile::ProcessEffects() if(triggerNote) { ModCommand::NOTE note = chn.rowCommand.note; - if(instr) chn.nNewIns = static_cast(instr); + if(instr) + { + chn.nNewIns = static_cast(instr); + chn.swapSampleIndex = GetSampleIndex(ModCommand::IsNote(note) ? note : chn.nLastNote, instr); + } if(ModCommand::IsNote(note) && m_playBehaviour[kFT2Transpose]) { @@ -2740,11 +2913,11 @@ bool CSoundFile::ProcessEffects() note = NOTE_NONE; instr = 0; retrigEnv = false; - // FT2 Compatbility: Start fading the note for notes with no delay. Only relevant when a volume command is encountered after the note-off. + // FT2 Compatibility: Start fading the note for notes with no delay. Only relevant when a volume command is encountered after the note-off. // Test case: NoteOffFadeNoEnv.xm - if(m_SongFlags[SONG_FIRSTTICK] && m_playBehaviour[kFT2NoteOffFlags]) + if(m_PlayState.m_flags[SONG_FIRSTTICK] && m_playBehaviour[kFT2NoteOffFlags]) chn.dwFlags.set(CHN_NOTEFADE); - } else if(m_playBehaviour[kFT2RetrigWithNoteDelay] && !m_SongFlags[SONG_FIRSTTICK]) + } else if(m_playBehaviour[kFT2RetrigWithNoteDelay] && !m_PlayState.m_flags[SONG_FIRSTTICK]) { // FT2 Compatibility: Some special hacks for rogue note delays... (EDx with x > 0) // Apparently anything that is next to a note delay behaves totally unpredictable in FT2. Swedish tracker logic. :) @@ -2862,7 +3035,7 @@ bool CSoundFile::ProcessEffects() chn.nAutoVibDepth = 0; chn.nAutoVibPos = 0; chn.nFadeOutVol = 65536; - // FT2 Compatbility: Reset key-off status with instrument number + // FT2 Compatibility: Reset key-off status with instrument number // Test case: NoteOffInstrChange.xm if(m_playBehaviour[kFT2NoteOffFlags]) chn.dwFlags.reset(CHN_KEYOFF); @@ -2887,6 +3060,7 @@ bool CSoundFile::ProcessEffects() instr = 0; } + const auto previousNewNote = chn.nNewNote; if(ModCommand::IsNote(note)) { chn.nNewNote = chn.nLastNote = note; @@ -2908,7 +3082,7 @@ bool CSoundFile::ProcessEffects() InstrumentChange(chn, instr, bPorta, true); - if(chn.pModSample != nullptr && chn.pModSample->uFlags[CHN_ADLIB] && m_opl) + if(!chn.dwFlags[CHN_MUTE | CHN_SYNCMUTE] && chn.pModSample != nullptr && chn.pModSample->uFlags[CHN_ADLIB] && m_opl) { m_opl->Patch(nChn, chn.pModSample->adlib); } @@ -2924,6 +3098,15 @@ bool CSoundFile::ProcessEffects() if(!m_playBehaviour[kITInstrWithNoteOff] || ModCommand::IsNote(note)) chn.nNewIns = 0; } + // When swapping samples without explicit note change (e.g. during portamento), avoid clicks at end of sample (as there won't be an NNA channel to fade the sample out) + if(oldSample != nullptr && oldSample != chn.pModSample) + { + m_dryLOfsVol += chn.nLOfs; + m_dryROfsVol += chn.nROfs; + chn.nLOfs = 0; + chn.nROfs = 0; + } + if(m_playBehaviour[kITPortamentoSwapResetsPos]) { // Test cases: PortaInsNum.it, PortaSample.it @@ -2956,15 +3139,24 @@ bool CSoundFile::ProcessEffects() const bool instrChange = (!instr) && (chn.nNewIns) && ModCommand::IsNote(note); if(instrChange) { + // If we change to a new instrument, we need to do so based on whatever previous note would have played + // - so that we trigger the correct sample in a multisampled instrument (based on the previous note, not the new note). + // Test case: InitialNoteMemoryInstrMode.it + if(m_playBehaviour[kITEmptyNoteMapSlotIgnoreCell] && ModCommand::IsNote(previousNewNote)) + chn.nNewNote = previousNewNote; + InstrumentChange(chn, chn.nNewIns, bPorta, chn.pModSample == nullptr && chn.pModInstrument == nullptr, !(GetType() & (MOD_TYPE_XM|MOD_TYPE_MT2))); - chn.nNewIns = 0; + chn.nNewNote = note; + chn.swapSampleIndex = chn.nNewIns = 0; } - if(chn.pModSample != nullptr && chn.pModSample->uFlags[CHN_ADLIB] && m_opl && (instrChange || !m_opl->IsActive(nChn))) + if(!chn.dwFlags[CHN_MUTE | CHN_SYNCMUTE] && chn.pModSample != nullptr && chn.pModSample->uFlags[CHN_ADLIB] && m_opl && (instrChange || !m_opl->IsActive(nChn))) { m_opl->Patch(nChn, chn.pModSample->adlib); } NoteChange(chn, note, bPorta, !(GetType() & (MOD_TYPE_XM | MOD_TYPE_MT2)), false, nChn); + if(continueNote) + chn.nPeriod = chn.nPortamentoDest; if(ModCommand::IsNote(note)) HandleDigiSamplePlayDirection(m_PlayState, nChn); if ((bPorta) && (GetType() & (MOD_TYPE_XM|MOD_TYPE_MT2)) && (instr)) @@ -3002,6 +3194,9 @@ bool CSoundFile::ProcessEffects() if(m_playBehaviour[kST3NoMutedChannels] && ChnSettings[nChn].dwFlags[CHN_MUTE]) // not even effects are processed on muted S3M channels continue; + if(!m_PlayState.m_nTickCount) + ResetAutoSlides(chn); + // Volume Column Effect (except volume & panning) /* A few notes, paraphrased from ITTECH.TXT by Storlek (creator of schismtracker): Ex/Fx/Gx are shared with Exx/Fxx/Gxx; Ex/Fx are 4x the 'normal' slide value @@ -3021,6 +3216,32 @@ bool CSoundFile::ProcessEffects() { doVolumeColumn = m_PlayState.m_nTickCount != 0 && (m_PlayState.m_nTickCount != nStartTick || (chn.rowCommand.instr == 0 && volcmd != VOLCMD_TONEPORTAMENTO)); } + + // IT compatibility: Various mind-boggling behaviours when combining volume colum and effect column portamentos + // The most crucial thing here is to initialize effect memory in the exact right order. + // Test cases: DoubleSlide.it, DoubleSlideCompatGxx.it + if(m_playBehaviour[kITDoublePortamentoSlides] && chn.isFirstTick) + { + const bool effectColumnTonePorta = (cmd == CMD_TONEPORTAMENTO || cmd == CMD_TONEPORTAVOL); + if(effectColumnTonePorta) + InitTonePortamento(chn, static_cast(cmd == CMD_TONEPORTAVOL ? 0 : param)); + if(volcmd == VOLCMD_TONEPORTAMENTO) + InitTonePortamento(chn, GetVolCmdTonePorta(chn.rowCommand, nStartTick).first); + + if(vol && (volcmd == VOLCMD_PORTAUP || volcmd == VOLCMD_PORTADOWN)) + { + chn.nOldPortaUp = chn.nOldPortaDown = vol << 2; + if(!effectColumnTonePorta && TonePortamentoSharesEffectMemory()) + chn.portamentoSlide = vol << 2; + } + if(param && (cmd == CMD_PORTAMENTOUP || cmd == CMD_PORTAMENTODOWN)) + { + chn.nOldPortaUp = chn.nOldPortaDown = static_cast(param); + if(TonePortamentoSharesEffectMemory()) + chn.portamentoSlide = static_cast(param); + } + } + if(volcmd > VOLCMD_PANNING && doVolumeColumn) { if(volcmd == VOLCMD_TONEPORTAMENTO) @@ -3044,7 +3265,7 @@ bool CSoundFile::ProcessEffects() case VOLCMD_PANSLIDELEFT: // FT2 Compatibility: Pan slide left with zero parameter causes panning to be set to full left on every non-row tick. // Test case: PanSlideZero.xm - if(!m_SongFlags[SONG_FIRSTTICK]) + if(!m_PlayState.m_flags[SONG_FIRSTTICK]) { chn.nPan = 0; } @@ -3053,12 +3274,11 @@ bool CSoundFile::ProcessEffects() // no memory here. volcmd = VOLCMD_NONE; } - - } else if(!m_playBehaviour[kITVolColMemory]) + } else if(!m_playBehaviour[kITVolColMemory] && volcmd != VOLCMD_PLAYCONTROL) { // IT Compatibility: Effects in the volume column don't have an unified memory. // Test case: VolColMemory.it - if(vol) chn.nOldVolParam = static_cast(vol); else vol = chn.nOldVolParam; + if(vol) chn.nOldVolParam = vol; else vol = chn.nOldVolParam; } switch(volcmd) @@ -3074,7 +3294,7 @@ bool CSoundFile::ProcessEffects() break; } else { - chn.nOldVolParam = static_cast(vol); + chn.nOldVolParam = vol; } VolumeSlide(chn, static_cast(volcmd == VOLCMD_VOLSLIDEUP ? (vol << 4) : vol)); break; @@ -3086,7 +3306,7 @@ bool CSoundFile::ProcessEffects() { // IT Compatibility: Volume column volume slides have their own memory // Test case: VolColMemory.it - FineVolumeUp(chn, static_cast(vol), m_playBehaviour[kITVolColMemory]); + FineVolumeUp(chn, vol, m_playBehaviour[kITVolColMemory]); } break; @@ -3097,7 +3317,7 @@ bool CSoundFile::ProcessEffects() { // IT Compatibility: Volume column volume slides have their own memory // Test case: VolColMemory.it - FineVolumeDown(chn, static_cast(vol), m_playBehaviour[kITVolColMemory]); + FineVolumeDown(chn, vol, m_playBehaviour[kITVolColMemory]); } break; @@ -3114,7 +3334,7 @@ bool CSoundFile::ProcessEffects() break; case VOLCMD_PANSLIDELEFT: - PanningSlide(chn, static_cast(vol), !m_playBehaviour[kFT2VolColMemory]); + PanningSlide(chn, vol, !m_playBehaviour[kFT2VolColMemory]); break; case VOLCMD_PANSLIDERIGHT: @@ -3132,7 +3352,7 @@ bool CSoundFile::ProcessEffects() break; case VOLCMD_OFFSET: - if(triggerNote && chn.pModSample && vol <= std::size(chn.pModSample->cues)) + if(triggerNote && chn.pModSample && !chn.pModSample->uFlags[CHN_ADLIB] && vol <= std::size(chn.pModSample->cues)) { SmpLength offset; if(vol == 0) @@ -3144,8 +3364,8 @@ bool CSoundFile::ProcessEffects() break; case VOLCMD_PLAYCONTROL: - if(vol <= 1) - chn.isPaused = (vol == 0); + if(chn.isFirstTick) + chn.PlayControl(vol); break; default: @@ -3159,14 +3379,14 @@ bool CSoundFile::ProcessEffects() { // Set Volume case CMD_VOLUME: - if(m_SongFlags[SONG_FIRSTTICK]) + if(m_PlayState.m_flags[SONG_FIRSTTICK]) { chn.nVolume = (param < 64) ? param * 4 : 256; chn.dwFlags.set(CHN_FASTVOLRAMP); } break; case CMD_VOLUME8: - if(m_SongFlags[SONG_FIRSTTICK]) + if(m_PlayState.m_flags[SONG_FIRSTTICK]) { chn.nVolume = param; chn.dwFlags.set(CHN_FASTVOLRAMP); @@ -3185,6 +3405,28 @@ bool CSoundFile::ProcessEffects() PortamentoDown(nChn, static_cast(param), false); break; + // Auto portamentos + case CMD_AUTO_PORTAUP: + chn.autoSlide.SetActive(AutoSlideCommand::PortamentoUp, param != 0); + chn.nOldPortaUp = static_cast(param); + break; + case CMD_AUTO_PORTADOWN: + chn.autoSlide.SetActive(AutoSlideCommand::PortamentoDown, param != 0); + chn.nOldPortaDown = static_cast(param); + break; + case CMD_AUTO_PORTAUP_FINE: + chn.autoSlide.SetActive(AutoSlideCommand::FinePortamentoUp, param != 0); + chn.nOldFinePortaUpDown = static_cast(param); + break; + case CMD_AUTO_PORTADOWN_FINE: + chn.autoSlide.SetActive(AutoSlideCommand::FinePortamentoDown, param != 0); + chn.nOldFinePortaUpDown = static_cast(param); + break; + case CMD_AUTO_PORTAMENTO_FC: + chn.autoSlide.SetActive(AutoSlideCommand::PortamentoFC, param != 0); + chn.nOldPortaUp = chn.nOldPortaDown = static_cast(param); + break; + // Volume Slide case CMD_VOLUMESLIDE: if (param || (GetType() != MOD_TYPE_MOD)) VolumeSlide(chn, static_cast(param)); @@ -3197,7 +3439,14 @@ bool CSoundFile::ProcessEffects() // Tone-Portamento + Volume Slide case CMD_TONEPORTAVOL: - if ((param) || (GetType() != MOD_TYPE_MOD)) VolumeSlide(chn, static_cast(param)); + if(param || GetType() != MOD_TYPE_MOD) + { + // ST3 compatibility: Do not run combined slides (Kxy / Lxy) on first tick + // Test cases: NoCombinedSlidesOnFirstTick-Normal.s3m, NoCombinedSlidesOnFirstTick-Fast.s3m + + if(!chn.isFirstTick || !m_playBehaviour[kS3MIgnoreCombinedFineSlides]) + VolumeSlide(chn, static_cast(param)); + } TonePortamento(nChn, 0); break; @@ -3208,13 +3457,19 @@ bool CSoundFile::ProcessEffects() // Vibrato + Volume Slide case CMD_VIBRATOVOL: - if ((param) || (GetType() != MOD_TYPE_MOD)) VolumeSlide(chn, static_cast(param)); + if(param || GetType() != MOD_TYPE_MOD) + { + // ST3 compatibility: Do not run combined slides (Kxy / Lxy) on first tick + // Test cases: NoCombinedSlidesOnFirstTick-Normal.s3m, NoCombinedSlidesOnFirstTick-Fast.s3m + if(!chn.isFirstTick || !m_playBehaviour[kS3MIgnoreCombinedFineSlides]) + VolumeSlide(chn, static_cast(param)); + } Vibrato(chn, 0); break; // Set Speed case CMD_SPEED: - if(m_SongFlags[SONG_FIRSTTICK]) + if(m_PlayState.m_flags[SONG_FIRSTTICK]) SetSpeed(m_PlayState, param); break; @@ -3223,18 +3478,16 @@ bool CSoundFile::ProcessEffects() if(m_playBehaviour[kMODVBlankTiming]) { // ProTracker MODs with VBlank timing: All Fxx parameters set the tick count. - if(m_SongFlags[SONG_FIRSTTICK] && param != 0) SetSpeed(m_PlayState, param); - break; - } + if(m_PlayState.m_flags[SONG_FIRSTTICK] && param != 0) + SetSpeed(m_PlayState, param); + } else { param = CalculateXParam(m_PlayState.m_nPattern, m_PlayState.m_nRow, nChn); - if (GetType() & (MOD_TYPE_S3M|MOD_TYPE_IT|MOD_TYPE_MPT)) + if (GetType() & (MOD_TYPE_S3M | MOD_TYPE_IT | MOD_TYPE_MPT)) { if (param) chn.nOldTempo = static_cast(param); else param = chn.nOldTempo; } - TEMPO t(param, 0); - LimitMax(t, GetModSpecifications().GetTempoMax()); - SetTempo(t); + SetTempo(m_PlayState, TEMPO(param, 0)); } break; @@ -3294,7 +3547,7 @@ bool CSoundFile::ProcessEffects() // Tremor case CMD_TREMOR: - if(!m_SongFlags[SONG_FIRSTTICK]) + if(!m_PlayState.m_flags[SONG_FIRSTTICK]) { break; } @@ -3328,11 +3581,11 @@ bool CSoundFile::ProcessEffects() case CMD_GLOBALVOLUME: // IT compatibility: Only apply global volume on first tick (and multiples) // Test case: GlobalVolFirstTick.it - if(!m_SongFlags[SONG_FIRSTTICK]) + if(!m_PlayState.m_flags[SONG_FIRSTTICK]) break; // ST3 applies global volume on tick 1 and does other weird things, but we won't emulate this for now. // if(((GetType() & MOD_TYPE_S3M) && m_nTickCount != 1) -// || (!(GetType() & MOD_TYPE_S3M) && !m_SongFlags[SONG_FIRSTTICK])) +// || (!(GetType() & MOD_TYPE_S3M) && !m_PlayState.m_flags[SONG_FIRSTTICK])) // { // break; // } @@ -3341,7 +3594,7 @@ bool CSoundFile::ProcessEffects() // until the second tick of the row. Since we apply global volume on the mix buffer rather than note volumes, this // cannot be fixed for now. // Test case: GlobalVolume.xm -// if(IsCompatibleMode(TRK_FASTTRACKER2) && m_SongFlags[SONG_FIRSTTICK] && m_nMusicSpeed > 1) +// if(IsCompatibleMode(TRK_FASTTRACKER2) && m_PlayState.m_flags[SONG_FIRSTTICK] && m_nMusicSpeed > 1) // { // break; // } @@ -3357,20 +3610,18 @@ bool CSoundFile::ProcessEffects() { m_PlayState.m_nGlobalVolume = 256; } + m_PlayState.Chn[m_playBehaviour[kPerChannelGlobalVolSlide] ? nChn : 0].autoSlide.SetActive(AutoSlideCommand::GlobalVolumeSlide, false); break; // Global Volume Slide case CMD_GLOBALVOLSLIDE: //IT compatibility 16. Saving last global volume slide param per channel (FT2/IT) - if(m_playBehaviour[kPerChannelGlobalVolSlide]) - GlobalVolSlide(static_cast(param), chn.nOldGlobalVolSlide); - else - GlobalVolSlide(static_cast(param), m_PlayState.Chn[0].nOldGlobalVolSlide); + GlobalVolSlide(m_PlayState, static_cast(param), m_playBehaviour[kPerChannelGlobalVolSlide] ? nChn : 0); break; // Set 8-bit Panning case CMD_PANNING8: - if(m_SongFlags[SONG_FIRSTTICK]) + if(m_PlayState.m_flags[SONG_FIRSTTICK]) { Panning(chn, param, Pan8bit); } @@ -3427,7 +3678,7 @@ bool CSoundFile::ProcessEffects() // This is how it's NOT supposed to sound... else { - if(m_SongFlags[SONG_FIRSTTICK]) + if(m_PlayState.m_flags[SONG_FIRSTTICK]) KeyOff(chn); } break; @@ -3459,16 +3710,16 @@ bool CSoundFile::ProcessEffects() case CMD_FINETUNE: case CMD_FINETUNE_SMOOTH: - if(m_SongFlags[SONG_FIRSTTICK] || cmd == CMD_FINETUNE_SMOOTH) + if(m_PlayState.m_flags[SONG_FIRSTTICK] || cmd == CMD_FINETUNE_SMOOTH) SetFinetune(m_PlayState.m_nPattern, m_PlayState.m_nRow, nChn, m_PlayState, cmd == CMD_FINETUNE_SMOOTH); break; // Set Channel Global Volume case CMD_CHANNELVOLUME: - if(!m_SongFlags[SONG_FIRSTTICK]) break; + if(!m_PlayState.m_flags[SONG_FIRSTTICK]) break; if (param <= 64) { - chn.nGlobalVol = param; + chn.nGlobalVol = static_cast(param); chn.dwFlags.set(CHN_FASTVOLRAMP); } break; @@ -3485,7 +3736,7 @@ bool CSoundFile::ProcessEffects() // Set Envelope Position case CMD_SETENVPOSITION: - if(m_SongFlags[SONG_FIRSTTICK]) + if(m_PlayState.m_flags[SONG_FIRSTTICK]) { chn.VolEnv.nEnvPosition = param; @@ -3496,10 +3747,20 @@ bool CSoundFile::ProcessEffects() chn.PanEnv.nEnvPosition = param; chn.PitchEnv.nEnvPosition = param; } - } break; + // MED Synth Jump (handled in InstrumentSynth) / MIDI Panning + case CMD_MED_SYNTH_JUMP: +#ifndef NO_PLUGINS + if(chn.isFirstTick) + { + if(IMixPlugin *plugin = GetChannelInstrumentPlugin(chn); plugin != nullptr) + plugin->MidiCC(MIDIEvents::MIDICC_Panposition_Coarse, static_cast(param & 0x7F), nChn); + } +#endif // NO_PLUGINS + break; + // Position Jump case CMD_POSITIONJUMP: PositionJump(m_PlayState, nChn); @@ -3510,7 +3771,7 @@ bool CSoundFile::ProcessEffects() if(ROWINDEX row = PatternBreak(m_PlayState, nChn, static_cast(param)); row != ROWINDEX_INVALID) { m_PlayState.m_breakRow = row; - if(m_SongFlags[SONG_PATTERNLOOP]) + if(m_PlayState.m_flags[SONG_PATTERNLOOP]) { //If song is set to loop and a pattern break occurs we should stay on the same pattern. //Use nPosJump to force playback to "jump to this pattern" rather than move to next, as by default. @@ -3548,7 +3809,7 @@ bool CSoundFile::ProcessEffects() if(echoType == 1) { firstChn = 0; - lastChn = m_nChannels - 1; + lastChn = GetNumChannels() - 1; } for(CHANNELINDEX c = firstChn; c <= lastChn; c++) { @@ -3564,6 +3825,24 @@ bool CSoundFile::ProcessEffects() DigiBoosterSampleReverse(chn, static_cast(param)); break; + case CMD_AUTO_VOLUMESLIDE: + AutoVolumeSlide(chn, static_cast(param)); + break; + case CMD_VOLUMEDOWN_ETX: + if(chn.isFirstTick) + VolumeDownETX(m_PlayState, chn, static_cast(param)); + break; + + case CMD_TONEPORTA_DURATION: + if(chn.rowCommand.IsNote() && triggerNote) + TonePortamentoWithDuration(chn, static_cast(param)); + break; + + case CMD_VOLUMEDOWN_DURATION: + if(m_PlayState.m_nTickCount == 0) + ChannelVolumeDownWithDuration(chn, static_cast(param)); + break; + default: break; } @@ -3579,13 +3858,14 @@ bool CSoundFile::ProcessEffects() chn.nOldIns = chn.rowCommand.instr; } + ProcessAutoSlides(m_PlayState, nChn); } // for(...) end // Navigation Effects - if(m_SongFlags[SONG_FIRSTTICK]) + if(m_PlayState.m_flags[SONG_FIRSTTICK]) { if(HandleNextRow(m_PlayState, Order(), true)) - m_SongFlags.set(SONG_BREAKTOROW); + m_PlayState.m_flags.set(SONG_BREAKTOROW); } return true; } @@ -3629,7 +3909,7 @@ bool CSoundFile::HandleNextRow(PlayState &state, const ModSequence &order, bool } state.m_nNextRow = state.m_breakRow; - if(!honorPatternLoop || !m_SongFlags[SONG_PATTERNLOOP]) + if(!honorPatternLoop || !m_PlayState.m_flags[SONG_PATTERNLOOP]) state.m_nNextOrder = state.m_posJump; } else if(doPatternLoop) { @@ -3664,6 +3944,81 @@ bool CSoundFile::HandleNextRow(PlayState &state, const ModSequence &order, bool // Channels effects +void CSoundFile::ResetAutoSlides(ModChannel &chn) const +{ + const auto cmd = chn.rowCommand.command; + const auto volcmd = chn.rowCommand.volcmd; + if(cmd != CMD_NONE && GetType() == MOD_TYPE_669) + { + chn.autoSlide.Reset(); + return; + } + + if((cmd == CMD_NONE || !chn.rowCommand.param) && chn.autoSlide.IsActive(AutoSlideCommand::VolumeSlideSTK)) + chn.autoSlide.SetActive(AutoSlideCommand::VolumeSlideSTK, false); + if((cmd == CMD_CHANNELVOLUME || cmd == CMD_CHANNELVOLSLIDE) && chn.autoSlide.IsActive(AutoSlideCommand::VolumeDownWithDuration)) + chn.autoSlide.SetActive(AutoSlideCommand::VolumeDownWithDuration, false); + + if(chn.autoSlide.IsActive(AutoSlideCommand::FinePortamentoDown) || chn.autoSlide.IsActive(AutoSlideCommand::PortamentoDown) + || chn.autoSlide.IsActive(AutoSlideCommand::FinePortamentoUp) || chn.autoSlide.IsActive(AutoSlideCommand::PortamentoUp)) + { + if(!chn.rowCommand.IsTonePortamento() && chn.rowCommand.IsAnyPitchSlide()) + { + chn.autoSlide.SetActive(AutoSlideCommand::FinePortamentoDown, false); + chn.autoSlide.SetActive(AutoSlideCommand::PortamentoDown, false); + chn.autoSlide.SetActive(AutoSlideCommand::FinePortamentoUp, false); + chn.autoSlide.SetActive(AutoSlideCommand::PortamentoUp, false); + } + } + if(chn.autoSlide.IsActive(AutoSlideCommand::FineVolumeSlideUp) || chn.autoSlide.IsActive(AutoSlideCommand::FineVolumeSlideDown) || chn.autoSlide.IsActive(AutoSlideCommand::VolumeDownETX)) + { + if(cmd == CMD_VOLUME || cmd == CMD_AUTO_VOLUMESLIDE || cmd == CMD_VOLUMEDOWN_ETX || chn.rowCommand.IsNormalVolumeSlide() + || volcmd == VOLCMD_VOLUME || volcmd == VOLCMD_VOLSLIDEUP || volcmd == VOLCMD_VOLSLIDEDOWN || volcmd == VOLCMD_FINEVOLUP || volcmd == VOLCMD_FINEVOLDOWN) + { + chn.autoSlide.SetActive(AutoSlideCommand::FineVolumeSlideUp, false); + chn.autoSlide.SetActive(AutoSlideCommand::FineVolumeSlideDown, false); + chn.autoSlide.SetActive(AutoSlideCommand::VolumeDownETX, false); + } + } +} + + +void CSoundFile::ProcessAutoSlides(PlayState &playState, CHANNELINDEX channel) +{ + ModChannel &chn = playState.Chn[channel]; + if(chn.autoSlide.IsActive(AutoSlideCommand::TonePortamento) && !chn.rowCommand.IsTonePortamento()) + TonePortamento(channel, chn.portamentoSlide); + else if(chn.autoSlide.IsActive(AutoSlideCommand::TonePortamentoWithDuration)) + TonePortamentoWithDuration(chn); + if(chn.autoSlide.IsActive(AutoSlideCommand::PortamentoUp)) + PortamentoUp(channel, chn.nOldPortaUp, true); + else if(chn.autoSlide.IsActive(AutoSlideCommand::PortamentoDown)) + PortamentoDown(channel, chn.nOldPortaDown, true); + else if(chn.autoSlide.IsActive(AutoSlideCommand::FinePortamentoUp)) + FinePortamentoUp(chn, chn.nOldFinePortaUpDown); + else if(chn.autoSlide.IsActive(AutoSlideCommand::FinePortamentoDown)) + FinePortamentoDown(chn, chn.nOldFinePortaUpDown); + if(chn.autoSlide.IsActive(AutoSlideCommand::PortamentoFC)) + PortamentoFC(chn); + if(chn.autoSlide.IsActive(AutoSlideCommand::FineVolumeSlideUp) && chn.rowCommand.command != CMD_AUTO_VOLUMESLIDE) + FineVolumeUp(chn, 0, false); + if(chn.autoSlide.IsActive(AutoSlideCommand::FineVolumeSlideDown) && chn.rowCommand.command != CMD_AUTO_VOLUMESLIDE) + FineVolumeDown(chn, 0, false); + if(chn.autoSlide.IsActive(AutoSlideCommand::VolumeDownETX)) + chn.nVolume = std::max(int32(0), chn.nVolume - chn.nOldVolumeSlide); + if(chn.autoSlide.IsActive(AutoSlideCommand::VolumeSlideSTK)) + VolumeSlide(chn, 0); + if(chn.autoSlide.IsActive(AutoSlideCommand::GlobalVolumeSlide) && chn.rowCommand.command != CMD_GLOBALVOLSLIDE) + GlobalVolSlide(playState, chn.nOldGlobalVolSlide, channel); + if(chn.autoSlide.IsActive(AutoSlideCommand::VolumeDownWithDuration)) + ChannelVolumeDownWithDuration(chn); + if(chn.autoSlide.IsActive(AutoSlideCommand::Vibrato)) + chn.dwFlags.set(CHN_VIBRATO); + if(chn.autoSlide.IsActive(AutoSlideCommand::Tremolo)) + chn.dwFlags.set(CHN_TREMOLO); +} + + // Update the effect memory of all S3M effects that use the last non-zero effect parameter as memory (Dxy, Exx, Fxx, Ixy, Jxy, Kxy, Lxy, Qxy, Rxy, Sxy) // Test case: ParamMemory.s3m void CSoundFile::UpdateS3MEffectMemory(ModChannel &chn, ModCommand::PARAM param) const @@ -3777,6 +4132,15 @@ ROWINDEX CSoundFile::PatternBreak(PlayState &state, CHANNELINDEX chn, uint8 para } +void CSoundFile::PortamentoFC(ModChannel &chn) const +{ + chn.fcPortaTick = !chn.fcPortaTick; + if(!chn.fcPortaTick) + return; + chn.nPeriod -= static_cast(chn.nOldPortaUp) * 4; +} + + void CSoundFile::PortamentoUp(CHANNELINDEX nChn, ModCommand::PARAM param, const bool doFinePortamentoAsRegular) { PortamentoUp(m_PlayState, nChn, param, doFinePortamentoAsRegular); @@ -3788,7 +4152,9 @@ void CSoundFile::PortamentoUp(PlayState &playState, CHANNELINDEX nChn, ModComman { ModChannel &chn = playState.Chn[nChn]; - if(param) + // IT compatibility: Initialize effect memory in the right order in case there are portamentos in both effect columns. + // Test cases: DoubleSlide.it, DoubleSlideCompatGxx.it + if(param && !m_playBehaviour[kITDoublePortamentoSlides]) { // FT2 compatibility: Separate effect memory for all portamento commands // Test case: Porta-LinkMem.xm @@ -3841,8 +4207,7 @@ void CSoundFile::PortamentoUp(PlayState &playState, CHANNELINDEX nChn, ModComman // Regular Slide if(!chn.isFirstTick || (m_PlayState.m_nMusicSpeed == 1 && m_playBehaviour[kSlidesAtSpeed1]) - || (GetType() & (MOD_TYPE_669 | MOD_TYPE_OKT)) - || (GetType() == MOD_TYPE_MED && m_SongFlags[SONG_FASTVOLSLIDES])) + || m_SongFlags[SONG_FASTPORTAS]) { DoFreqSlide(chn, chn.nPeriod, param * 4); } @@ -3860,7 +4225,9 @@ void CSoundFile::PortamentoDown(PlayState &playState, CHANNELINDEX nChn, ModComm { ModChannel &chn = playState.Chn[nChn]; - if(param) + // IT compatibility: Initialize effect memory in the right order in case there are portamentos in both effect columns. + // Test cases: DoubleSlide.it, DoubleSlideCompatGxx.it + if(param && !m_playBehaviour[kITDoublePortamentoSlides]) { // FT2 compatibility: Separate effect memory for all portamento commands // Test case: Porta-LinkMem.xm @@ -3913,8 +4280,7 @@ void CSoundFile::PortamentoDown(PlayState &playState, CHANNELINDEX nChn, ModComm if(!chn.isFirstTick || (m_PlayState.m_nMusicSpeed == 1 && m_playBehaviour[kSlidesAtSpeed1]) - || (GetType() & (MOD_TYPE_669 | MOD_TYPE_OKT)) - || (GetType() == MOD_TYPE_MED && m_SongFlags[SONG_FASTVOLSLIDES])) + || m_SongFlags[SONG_FASTPORTAS]) { DoFreqSlide(chn, chn.nPeriod, param * -4); } @@ -4054,11 +4420,10 @@ void CSoundFile::ProcessFinetune(PATTERNINDEX pattern, ROWINDEX row, CHANNELINDE { SetFinetune(pattern, row, channel, m_PlayState, isSmooth); // Also apply to notes played via CModDoc::PlayNote - for(CHANNELINDEX chn = GetNumChannels(); chn < MAX_CHANNELS; chn++) + for(ModChannel &chn : m_PlayState.BackgroundChannels(*this)) { - auto &modChn = m_PlayState.Chn[chn]; - if(modChn.nMasterChn == channel + 1 && modChn.isPreviewNote && !modChn.dwFlags[CHN_KEYOFF]) - modChn.microTuning = m_PlayState.Chn[channel].microTuning; + if(chn.nMasterChn == channel + 1 && chn.isPreviewNote && !chn.dwFlags[CHN_KEYOFF]) + chn.microTuning = m_PlayState.Chn[channel].microTuning; } } @@ -4108,7 +4473,7 @@ void CSoundFile::NoteSlide(ModChannel &chn, uint32 param, bool slideUp, bool ret bool doTrigger = false; if(GetType() == MOD_TYPE_OKT) - doTrigger = ((chn.noteSlideParam & 0xF0) == 0x10) || m_SongFlags[SONG_FIRSTTICK]; + doTrigger = ((chn.noteSlideParam & 0xF0) == 0x10) || m_PlayState.m_flags[SONG_FIRSTTICK]; else doTrigger = !chn.isFirstTick && (--chn.noteSlideCounter == 0); @@ -4158,6 +4523,27 @@ std::pair CSoundFile::GetVolCmdTonePorta(const ModCommand &m, uint } +bool CSoundFile::TonePortamentoSharesEffectMemory() const +{ + return (!m_SongFlags[SONG_ITCOMPATGXX] && m_playBehaviour[kITPortaMemoryShare]) || GetType() == MOD_TYPE_PLM; +} + + +void CSoundFile::InitTonePortamento(ModChannel &chn, uint16 param) const +{ + // IT compatibility 03: Share effect memory with portamento up/down + if(TonePortamentoSharesEffectMemory()) + { + if(param == 0) + param = chn.nOldPortaUp; + chn.nOldPortaUp = chn.nOldPortaDown = static_cast(param); + } + + if(param) + chn.portamentoSlide = param; +} + + void CSoundFile::TonePortamento(CHANNELINDEX nChn, uint16 param) { auto delta = TonePortamento(m_PlayState, nChn, param); @@ -4183,27 +4569,24 @@ int32 CSoundFile::TonePortamento(PlayState &playState, CHANNELINDEX nChn, uint16 { ModChannel &chn = playState.Chn[nChn]; chn.dwFlags.set(CHN_PORTAMENTO); + if(m_SongFlags[SONG_AUTO_TONEPORTA]) + chn.autoSlide.SetActive(AutoSlideCommand::TonePortamento, param != 0 || m_SongFlags[SONG_AUTO_TONEPORTA_CONT]); - //IT compatibility 03: Share effect memory with portamento up/down - if((!m_SongFlags[SONG_ITCOMPATGXX] && m_playBehaviour[kITPortaMemoryShare]) || GetType() == MOD_TYPE_PLM) - { - if(param == 0) param = chn.nOldPortaUp; - chn.nOldPortaUp = chn.nOldPortaDown = static_cast(param); - } - - if(param) - chn.portamentoSlide = param; + // IT compatibility: Initialize effect memory in the right order in case there are portamentos in both effect columns. + // Test cases: DoubleSlide.it, DoubleSlideCompatGxx.it + if(!m_playBehaviour[kITDoublePortamentoSlides]) + InitTonePortamento(chn, param); + int32 delta = chn.portamentoSlide; if(chn.HasCustomTuning()) { //Behavior: Param tells number of finesteps(or 'fullsteps'(notes) with glissando) //to slide per row(not per tick). - if(chn.portamentoSlide == 0) + if(delta == 0) return 0; const int32 oldPortamentoTickSlide = (playState.m_nTickCount != 0) ? chn.m_PortamentoTickSlide : 0; - int32 delta = chn.portamentoSlide; if(chn.nPortamentoDest < 0) delta = -delta; @@ -4241,11 +4624,10 @@ int32 CSoundFile::TonePortamento(PlayState &playState, CHANNELINDEX nChn, uint16 return 0; bool doPorta = !chn.isFirstTick - || (GetType() & (MOD_TYPE_DBM | MOD_TYPE_669)) + || GetType() == MOD_TYPE_DBM || (playState.m_nMusicSpeed == 1 && m_playBehaviour[kSlidesAtSpeed1]) - || (GetType() == MOD_TYPE_MED && m_SongFlags[SONG_FASTVOLSLIDES]); + || m_SongFlags[SONG_FASTPORTAS]; - int32 delta = chn.portamentoSlide; if(GetType() == MOD_TYPE_PLM && delta >= 0xF0) { delta -= 0xF0; @@ -4256,7 +4638,15 @@ int32 CSoundFile::TonePortamento(PlayState &playState, CHANNELINDEX nChn, uint16 if(chn.nPeriod && chn.nPortamentoDest && doPorta) { const int32 actualDelta = PeriodsAreFrequencies() ? delta : -delta; - if(chn.nPeriod < chn.nPortamentoDest || chn.portaTargetReached) + // IT compatibility: Command Lxx, with no tone portamento set up before, will always execute the "portamento down" branch. + // Test cases: LxxWith0Portamento-Linear.it, LxxWith0Portamento-Amiga.it + if(m_playBehaviour[kITDoublePortamentoSlides] && !delta && chn.rowCommand.command == CMD_TONEPORTAVOL) + { + if(chn.nPeriod > 1 && m_SongFlags[SONG_LINEARSLIDES]) + chn.nPeriod--; + if(chn.nPeriod < chn.nPortamentoDest) + chn.nPeriod = chn.nPortamentoDest; + } else if(chn.nPeriod < chn.nPortamentoDest || chn.portaTargetReached) { DoFreqSlide(chn, chn.nPeriod, actualDelta, true); if(chn.nPeriod > chn.nPortamentoDest) @@ -4283,11 +4673,55 @@ int32 CSoundFile::TonePortamento(PlayState &playState, CHANNELINDEX nChn, uint16 } +void CSoundFile::TonePortamentoWithDuration(ModChannel &chn, uint16 param) const +{ + if(param != uint16_max) + { + // Prepare portamento + if(!chn.rowCommand.IsNote()) + return; + chn.autoSlide.SetActive(AutoSlideCommand::TonePortamentoWithDuration, param != 0); + if(param == 0) + { + chn.nPeriod = chn.nPortamentoDest; + return; + } + uint32 sourceNote = GetNoteFromPeriod(chn.nPeriod, chn.nFineTune, chn.nC5Speed); + chn.portamentoSlide = static_cast(Util::muldivr_unsigned(std::abs(static_cast(chn.rowCommand.note - sourceNote)), 64, m_PlayState.m_nMusicSpeed * param)); + } else if(chn.nPeriod && chn.nPortamentoDest) + { + // Run portamento + chn.dwFlags.set(CHN_PORTAMENTO); + const int32 actualDelta = PeriodsAreFrequencies() ? chn.portamentoSlide : -chn.portamentoSlide; + if(chn.nPeriod < chn.nPortamentoDest) + { + DoFreqSlide(chn, chn.nPeriod, actualDelta, true); + if(chn.nPeriod >= chn.nPortamentoDest) + { + chn.nPeriod = chn.nPortamentoDest; + chn.nPortamentoDest = 0; + } + } else if(chn.nPeriod > chn.nPortamentoDest) + { + DoFreqSlide(chn, chn.nPeriod, -actualDelta, true); + if(chn.nPeriod <= chn.nPortamentoDest) + { + chn.nPeriod = chn.nPortamentoDest; + chn.nPortamentoDest = 0; + } + } + } +} + + void CSoundFile::Vibrato(ModChannel &chn, uint32 param) const { if (param & 0x0F) chn.nVibratoDepth = (param & 0x0F) * 4; if (param & 0xF0) chn.nVibratoSpeed = (param >> 4) & 0x0F; - chn.dwFlags.set(CHN_VIBRATO); + if(m_SongFlags[SONG_AUTO_VIBRATO]) + chn.autoSlide.SetActive(AutoSlideCommand::Vibrato, param != 0); + else + chn.dwFlags.set(CHN_VIBRATO); } @@ -4295,7 +4729,10 @@ void CSoundFile::FineVibrato(ModChannel &chn, uint32 param) const { if (param & 0x0F) chn.nVibratoDepth = param & 0x0F; if (param & 0xF0) chn.nVibratoSpeed = (param >> 4) & 0x0F; - chn.dwFlags.set(CHN_VIBRATO); + if(m_SongFlags[SONG_AUTO_VIBRATO]) + chn.autoSlide.SetActive(AutoSlideCommand::Vibrato, param != 0); + else + chn.dwFlags.set(CHN_VIBRATO); // ST3 compatibility: Do not distinguish between vibrato types in effect memory // Test case: VibratoTypeChange.s3m if(m_playBehaviour[kST3VibratoMemory] && (param & 0x0F)) @@ -4320,7 +4757,7 @@ void CSoundFile::Panning(ModChannel &chn, uint32 param, PanningType panBits) con return; } // IT Compatibility (and other trackers as well): panning disables surround (unless panning in rear channels is enabled, which is not supported by the original trackers anyway) - if (!m_SongFlags[SONG_SURROUNDPAN] && (panBits == Pan8bit || m_playBehaviour[kPanOverride])) + if (!m_PlayState.m_flags[SONG_SURROUNDPAN] && (panBits == Pan8bit || m_playBehaviour[kPanOverride])) { chn.dwFlags.reset(CHN_SURROUND); } @@ -4364,6 +4801,38 @@ void CSoundFile::Panning(ModChannel &chn, uint32 param, PanningType panBits) con } +void CSoundFile::AutoVolumeSlide(ModChannel &chn, ModCommand::PARAM param) const +{ + if(m_SongFlags[SONG_AUTO_VOLSLIDE_STK]) + { + chn.nOldVolumeSlide = param; + chn.autoSlide.SetActive(AutoSlideCommand::VolumeSlideSTK); + } else + { + if(param & 0x0F) + { + FineVolumeDown(chn, param, false); + chn.autoSlide.SetActive(AutoSlideCommand::FineVolumeSlideDown); + } else + { + FineVolumeUp(chn, param, false); + chn.autoSlide.SetActive(AutoSlideCommand::FineVolumeSlideUp); + } + } +} + + +void CSoundFile::VolumeDownETX(const PlayState &playState, ModChannel &chn, ModCommand::PARAM param) const +{ + chn.autoSlide.SetActive(AutoSlideCommand::VolumeDownETX, param != 0); + if(!param || !playState.m_nSamplesPerTick) + return; + const uint32 slideDuration = Util::muldivr_unsigned(m_MixerSettings.gdwMixingFreq, 600, 1000) / param; // 600ms at maximum volume + const uint32 neededTicks = std::max(uint32(1), (slideDuration + playState.m_nSamplesPerTick / 2u) / playState.m_nSamplesPerTick); + chn.nOldVolumeSlide = mpt::saturate_cast(256 / neededTicks); +} + + void CSoundFile::VolumeSlide(ModChannel &chn, ModCommand::PARAM param) const { if (param) @@ -4465,18 +4934,18 @@ void CSoundFile::PanningSlide(ModChannel &chn, ModCommand::PARAM param, bool mem { if (((param & 0x0F) == 0x0F) && (param & 0xF0)) { - if(m_SongFlags[SONG_FIRSTTICK]) + if(m_PlayState.m_flags[SONG_FIRSTTICK]) { param = (param & 0xF0) / 4u; nPanSlide = - (int)param; } } else if (((param & 0xF0) == 0xF0) && (param & 0x0F)) { - if(m_SongFlags[SONG_FIRSTTICK]) + if(m_PlayState.m_flags[SONG_FIRSTTICK]) { nPanSlide = (param & 0x0F) * 4u; } - } else if(!m_SongFlags[SONG_FIRSTTICK]) + } else if(!m_PlayState.m_flags[SONG_FIRSTTICK]) { if (param & 0x0F) { @@ -4490,7 +4959,7 @@ void CSoundFile::PanningSlide(ModChannel &chn, ModCommand::PARAM param, bool mem } } else { - if(!m_SongFlags[SONG_FIRSTTICK]) + if(!m_PlayState.m_flags[SONG_FIRSTTICK]) { if (param & 0xF0) { @@ -4566,7 +5035,10 @@ void CSoundFile::Tremolo(ModChannel &chn, uint32 param) const { if (param & 0x0F) chn.nTremoloDepth = (param & 0x0F) << 2; if (param & 0xF0) chn.nTremoloSpeed = (param >> 4) & 0x0F; - chn.dwFlags.set(CHN_TREMOLO); + if(m_SongFlags[SONG_AUTO_TREMOLO]) + chn.autoSlide.SetActive(AutoSlideCommand::Tremolo, (param & 0x0F) != 0); + else + chn.dwFlags.set(CHN_TREMOLO); } @@ -4577,13 +5049,13 @@ void CSoundFile::ChannelVolSlide(ModChannel &chn, ModCommand::PARAM param) const if (((param & 0x0F) == 0x0F) && (param & 0xF0)) { - if(m_SongFlags[SONG_FIRSTTICK]) nChnSlide = param >> 4; + if(m_PlayState.m_flags[SONG_FIRSTTICK]) nChnSlide = param >> 4; } else if (((param & 0xF0) == 0xF0) && (param & 0x0F)) { - if(m_SongFlags[SONG_FIRSTTICK]) nChnSlide = - (int)(param & 0x0F); + if(m_PlayState.m_flags[SONG_FIRSTTICK]) nChnSlide = - (int)(param & 0x0F); } else { - if(!m_SongFlags[SONG_FIRSTTICK]) + if(!m_PlayState.m_flags[SONG_FIRSTTICK]) { if (param & 0x0F) { @@ -4598,8 +5070,32 @@ void CSoundFile::ChannelVolSlide(ModChannel &chn, ModCommand::PARAM param) const if (nChnSlide) { nChnSlide += chn.nGlobalVol; - nChnSlide = Clamp(nChnSlide, 0, 64); - chn.nGlobalVol = nChnSlide; + Limit(nChnSlide, 0, 64); + chn.nGlobalVol = static_cast(nChnSlide); + } +} + + +void CSoundFile::ChannelVolumeDownWithDuration(ModChannel &chn, uint16 param) const +{ + if(param != uint16_max) + { + // Prepare slide + chn.autoSlide.SetActive(AutoSlideCommand::VolumeDownWithDuration, param != 0); + if(param == 0) + { + chn.nGlobalVol = 0; + return; + } + chn.volSlideDownStart = chn.nGlobalVol; + chn.volSlideDownTotal = chn.volSlideDownRemain = mpt::saturate_cast(param * m_PlayState.m_nMusicSpeed); + } else if(chn.volSlideDownTotal) + { + // Run slide + if(chn.volSlideDownRemain) + chn.nGlobalVol = static_cast(Util::muldivr(chn.volSlideDownStart, --chn.volSlideDownRemain, chn.volSlideDownTotal)); + else + chn.nGlobalVol = 0; } } @@ -4641,7 +5137,7 @@ void CSoundFile::ExtendedMODCommands(CHANNELINDEX nChn, ModCommand::PARAM param) // E4x: Set Vibrato WaveForm case 0x40: chn.nVibratoType = param & 0x07; break; // E5x: Set FineTune - case 0x50: if(!m_SongFlags[SONG_FIRSTTICK]) + case 0x50: if(!m_PlayState.m_flags[SONG_FIRSTTICK]) break; if(GetType() & (MOD_TYPE_MOD | MOD_TYPE_DIGI | MOD_TYPE_AMF0 | MOD_TYPE_MED)) { @@ -4664,14 +5160,14 @@ void CSoundFile::ExtendedMODCommands(CHANNELINDEX nChn, ModCommand::PARAM param) break; // E6x: Pattern Loop case 0x60: - if(m_SongFlags[SONG_FIRSTTICK]) + if(m_PlayState.m_flags[SONG_FIRSTTICK]) PatternLoop(m_PlayState, nChn, param & 0x0F); break; // E7x: Set Tremolo WaveForm case 0x70: chn.nTremoloType = param & 0x07; break; // E8x: Set 4-bit Panning case 0x80: - if(m_SongFlags[SONG_FIRSTTICK]) + if(m_PlayState.m_flags[SONG_FIRSTTICK]) { Panning(chn, param, Pan4bit); } @@ -4690,7 +5186,7 @@ void CSoundFile::ExtendedMODCommands(CHANNELINDEX nChn, ModCommand::PARAM param) if(GetType() == MOD_TYPE_MOD) // MOD: Invert Loop { chn.nEFxSpeed = param; - if(m_SongFlags[SONG_FIRSTTICK]) InvertLoop(chn); + if(m_PlayState.m_flags[SONG_FIRSTTICK]) InvertLoop(chn); } else // XM: Set Active Midi Macro { chn.nActiveMacro = param; @@ -4711,7 +5207,7 @@ void CSoundFile::ExtendedS3MCommands(CHANNELINDEX nChn, ModCommand::PARAM param) // S1x: Set Glissando Control case 0x10: chn.dwFlags.set(CHN_GLISSANDO, param != 0); break; // S2x: Set FineTune - case 0x20: if(!m_SongFlags[SONG_FIRSTTICK]) + case 0x20: if(!m_PlayState.m_flags[SONG_FIRSTTICK]) break; if(chn.HasCustomTuning()) { @@ -4768,7 +5264,7 @@ void CSoundFile::ExtendedS3MCommands(CHANNELINDEX nChn, ModCommand::PARAM param) break; // S6x: Pattern Delay for x frames case 0x60: - if(m_SongFlags[SONG_FIRSTTICK] && m_PlayState.m_nTickCount == 0) + if(m_PlayState.m_flags[SONG_FIRSTTICK] && m_PlayState.m_nTickCount == 0) { // Tick delays are added up. // Scream Tracker 3 does actually not support this command. @@ -4781,14 +5277,14 @@ void CSoundFile::ExtendedS3MCommands(CHANNELINDEX nChn, ModCommand::PARAM param) } break; // S7x: Envelope Control / Instrument Control - case 0x70: if(!m_SongFlags[SONG_FIRSTTICK]) break; + case 0x70: if(!m_PlayState.m_flags[SONG_FIRSTTICK]) break; switch(param) { case 0: case 1: case 2: { - for (CHANNELINDEX i = m_nChannels; i < MAX_CHANNELS; i++) + for(CHANNELINDEX i = GetNumChannels(); i < m_PlayState.Chn.size(); i++) { ModChannel &bkChn = m_PlayState.Chn[i]; if (bkChn.nMasterChn == nChn + 1) @@ -4815,7 +5311,7 @@ void CSoundFile::ExtendedS3MCommands(CHANNELINDEX nChn, ModCommand::PARAM param) IMixPlugin *pPlugin; if(pIns != nullptr && pIns->nMixPlug && (pPlugin = m_MixPlugins[pIns->nMixPlug - 1].pMixPlugin) != nullptr) { - pPlugin->MidiCommand(*pIns, bkChn.nNote | IMixPlugin::MIDI_NOTE_OFF, 0, nChn); + pPlugin->MidiCommand(*pIns, bkChn.nNote | IMixPlugin::MIDI_NOTE_OFF, 0, m_playBehaviour[kLegacyPluginNNABehaviour] ? nChn : i); } #endif // NO_PLUGINS } @@ -4829,15 +5325,20 @@ void CSoundFile::ExtendedS3MCommands(CHANNELINDEX nChn, ModCommand::PARAM param) break; // S8x: Set 4-bit Panning case 0x80: - if(m_SongFlags[SONG_FIRSTTICK]) + if(m_PlayState.m_flags[SONG_FIRSTTICK]) { Panning(chn, param, Pan4bit); } break; // S9x: Sound Control - case 0x90: ExtendedChannelEffect(chn, param); break; + case 0x90: + if(m_PlayState.m_flags[SONG_FIRSTTICK]) + { + ExtendedChannelEffect(chn, param, m_PlayState); break; + } + break; // SAx: Set 64k Offset - case 0xA0: if(m_SongFlags[SONG_FIRSTTICK]) + case 0xA0: if(m_PlayState.m_flags[SONG_FIRSTTICK]) { chn.nOldHiOffset = static_cast(param); if (!m_playBehaviour[kITHighOffsetNoRetrig] && chn.rowCommand.IsNote()) @@ -4849,7 +5350,7 @@ void CSoundFile::ExtendedS3MCommands(CHANNELINDEX nChn, ModCommand::PARAM param) break; // SBx: Pattern Loop case 0xB0: - if(m_SongFlags[SONG_FIRSTTICK]) + if(m_PlayState.m_flags[SONG_FIRSTTICK]) PatternLoop(m_PlayState, nChn, param & 0x0F); break; // SCx: Note Cut @@ -4880,16 +5381,15 @@ void CSoundFile::ExtendedS3MCommands(CHANNELINDEX nChn, ModCommand::PARAM param) } -void CSoundFile::ExtendedChannelEffect(ModChannel &chn, uint32 param) +void CSoundFile::ExtendedChannelEffect(ModChannel &chn, uint32 param, PlayState &playState) const { // S9x and X9x commands (S3M/XM/IT only) - if(!m_SongFlags[SONG_FIRSTTICK]) return; switch(param & 0x0F) { // S90: Surround Off - case 0x00: chn.dwFlags.reset(CHN_SURROUND); break; + case 0x00: chn.dwFlags.reset(CHN_SURROUND); break; // S91: Surround On - case 0x01: chn.dwFlags.set(CHN_SURROUND); chn.nPan = 128; break; + case 0x01: chn.dwFlags.set(CHN_SURROUND); chn.nPan = 128; break; //////////////////////////////////////////////////////////// // ModPlug Extensions @@ -4905,19 +5405,19 @@ void CSoundFile::ExtendedChannelEffect(ModChannel &chn, uint32 param) break; // S9A: 2-Channels surround mode case 0x0A: - m_SongFlags.reset(SONG_SURROUNDPAN); + playState.m_flags.reset(SONG_SURROUNDPAN); break; // S9B: 4-Channels surround mode case 0x0B: - m_SongFlags.set(SONG_SURROUNDPAN); + playState.m_flags.set(SONG_SURROUNDPAN); break; // S9C: IT Filter Mode case 0x0C: - m_SongFlags.reset(SONG_MPTFILTERMODE); + playState.m_flags.reset(SONG_MPTFILTERMODE); break; // S9D: MPT Filter Mode case 0x0D: - m_SongFlags.set(SONG_MPTFILTERMODE); + playState.m_flags.set(SONG_MPTFILTERMODE); break; // S9E: Go forward case 0x0E: @@ -4979,279 +5479,15 @@ void CSoundFile::InvertLoop(ModChannel &chn) void CSoundFile::ProcessMIDIMacro(PlayState &playState, CHANNELINDEX nChn, bool isSmooth, const MIDIMacroConfigData::Macro ¯o, uint8 param, PLUGINDEX plugin) { playState.m_midiMacroScratchSpace.resize(macro.Length() + 1); - auto out = mpt::as_span(playState.m_midiMacroScratchSpace); - - ParseMIDIMacro(playState, nChn, isSmooth, macro, out, param, plugin); - - // Macro string has been parsed and translated, now send the message(s)... - uint32 outSize = static_cast(out.size()); - uint32 sendPos = 0; - uint8 runningStatus = 0; - while(sendPos < out.size()) + MIDIMacroParser parser{*this, &playState, nChn, isSmooth, macro, mpt::as_span(playState.m_midiMacroScratchSpace), param, plugin}; + mpt::span midiMsg; + while(parser.NextMessage(midiMsg)) { - uint32 sendLen = 0; - if(out[sendPos] == 0xF0) - { - // SysEx start - if((outSize - sendPos >= 4) && (out[sendPos + 1] == 0xF0 || out[sendPos + 1] == 0xF1)) - { - // Internal macro (normal (F0F0) or extended (F0F1)), 4 bytes long - sendLen = 4; - } else - { - // SysEx message, find end of message - sendLen = outSize - sendPos; - for(uint32 i = sendPos + 1; i < outSize; i++) - { - if(out[i] == 0xF7) - { - // Found end of SysEx message - sendLen = i - sendPos + 1; - break; - } - } - } - } else if(!(out[sendPos] & 0x80)) - { - // Missing status byte? Try inserting running status - if(runningStatus != 0) - { - sendPos--; - out[sendPos] = runningStatus; - } else - { - // No running status to re-use; skip this byte - sendPos++; - } - continue; - } else - { - // Other MIDI messages - sendLen = std::min(static_cast(MIDIEvents::GetEventLength(out[sendPos])), outSize - sendPos); - } - - if(sendLen == 0) - break; - - if(out[sendPos] < 0xF0) - { - runningStatus = out[sendPos]; - } - const auto midiMsg = out.subspan(sendPos, sendLen); SendMIDIData(playState, nChn, isSmooth, midiMsg, plugin); - sendPos += sendLen; } } -void CSoundFile::ParseMIDIMacro(PlayState &playState, CHANNELINDEX nChn, bool isSmooth, const mpt::span macro, mpt::span &out, uint8 param, PLUGINDEX plugin) const -{ - ModChannel &chn = playState.Chn[nChn]; - const ModInstrument *pIns = chn.pModInstrument; - - const uint8 lastZxxParam = chn.lastZxxParam; // always interpolate based on original value in case z appears multiple times in macro string - uint8 updateZxxParam = 0xFF; // avoid updating lastZxxParam immediately if macro contains both internal and external MIDI message - - bool firstNibble = true; - size_t outPos = 0; // output buffer position, which also equals the number of complete bytes - for(size_t pos = 0; pos < macro.size() && outPos < out.size(); pos++) - { - bool isNibble = false; // did we parse a nibble or a byte value? - uint8 data = 0; // data that has just been parsed - - // Parse next macro byte... See Impulse Tracker's MIDI.TXT for detailed information on each possible character. - if(macro[pos] >= '0' && macro[pos] <= '9') - { - isNibble = true; - data = static_cast(macro[pos] - '0'); - } else if(macro[pos] >= 'A' && macro[pos] <= 'F') - { - isNibble = true; - data = static_cast(macro[pos] - 'A' + 0x0A); - } else if(macro[pos] == 'c') - { - // MIDI channel - isNibble = true; - data = 0xFF; -#ifndef NO_PLUGINS - const PLUGINDEX plug = (plugin != 0) ? plugin : GetBestPlugin(playState, nChn, PrioritiseChannel, EvenIfMuted); - if(plug > 0 && plug <= MAX_MIXPLUGINS) - { - auto midiPlug = dynamic_cast(m_MixPlugins[plug - 1u].pMixPlugin); - if(midiPlug) - data = midiPlug->GetMidiChannel(playState.Chn[nChn], nChn); - } -#endif // NO_PLUGINS - if(data == 0xFF) - { - // Fallback if no plugin was found - if(pIns) - data = pIns->GetMIDIChannel(playState.Chn[nChn], nChn); - else - data = 0; - } - } else if(macro[pos] == 'n') - { - // Last triggered note - if(ModCommand::IsNote(chn.nLastNote)) - { - data = chn.nLastNote - NOTE_MIN; - } - } else if(macro[pos] == 'v') - { - // Velocity - // This is "almost" how IT does it - apparently, IT seems to lag one row behind on global volume or channel volume changes. - const int swing = (m_playBehaviour[kITSwingBehaviour] || m_playBehaviour[kMPTOldSwingBehaviour]) ? chn.nVolSwing : 0; - const int vol = Util::muldiv((chn.nVolume + swing) * playState.m_nGlobalVolume, chn.nGlobalVol * chn.nInsVol, 1 << 20); - data = static_cast(Clamp(vol / 2, 1, 127)); - //data = (unsigned char)std::min((chn.nVolume * chn.nGlobalVol * playState.m_nGlobalVolume) >> (1 + 6 + 8), 127); - } else if(macro[pos] == 'u') - { - // Calculated volume - // Same note as with velocity applies here, but apparently also for instrument / sample volumes? - const int vol = Util::muldiv(chn.nCalcVolume * playState.m_nGlobalVolume, chn.nGlobalVol * chn.nInsVol, 1 << 26); - data = static_cast(Clamp(vol / 2, 1, 127)); - //data = (unsigned char)std::min((chn.nCalcVolume * chn.nGlobalVol * playState.m_nGlobalVolume) >> (7 + 6 + 8), 127); - } else if(macro[pos] == 'x') - { - // Pan set - data = static_cast(std::min(static_cast(chn.nPan / 2), 127)); - } else if(macro[pos] == 'y') - { - // Calculated pan - data = static_cast(std::min(static_cast(chn.nRealPan / 2), 127)); - } else if(macro[pos] == 'a') - { - // High byte of bank select - if(pIns && pIns->wMidiBank) - { - data = static_cast(((pIns->wMidiBank - 1) >> 7) & 0x7F); - } - } else if(macro[pos] == 'b') - { - // Low byte of bank select - if(pIns && pIns->wMidiBank) - { - data = static_cast((pIns->wMidiBank - 1) & 0x7F); - } - } else if(macro[pos] == 'o') - { - // Offset (ignoring high offset) - data = static_cast((chn.oldOffset >> 8) & 0xFF); - } else if(macro[pos] == 'h') - { - // Host channel number - data = static_cast((nChn >= GetNumChannels() ? (chn.nMasterChn - 1) : nChn) & 0x7F); - } else if(macro[pos] == 'm') - { - // Loop direction (judging from the character, it was supposed to be loop type, though) - data = chn.dwFlags[CHN_PINGPONGFLAG] ? 1 : 0; - } else if(macro[pos] == 'p') - { - // Program select - if(pIns && pIns->nMidiProgram) - { - data = static_cast((pIns->nMidiProgram - 1) & 0x7F); - } - } else if(macro[pos] == 'z') - { - // Zxx parameter - data = param; - if(isSmooth && chn.lastZxxParam < 0x80 - && (outPos < 3 || out[outPos - 3] != 0xF0 || out[outPos - 2] < 0xF0)) - { - // Interpolation for external MIDI messages - interpolation for internal messages - // is handled separately to allow for more than 7-bit granularity where it's possible - data = static_cast(CalculateSmoothParamChange(playState, lastZxxParam, data)); - chn.lastZxxParam = data; - updateZxxParam = 0x80; - } else if(updateZxxParam == 0xFF) - { - updateZxxParam = data; - } - } else if(macro[pos] == 's') - { - // SysEx Checksum (not an original Impulse Tracker macro variable, but added for convenience) - if(!firstNibble) // From MIDI.TXT: '9n' is exactly the same as '09 n' or '9 n' -- so finish current byte first - { - outPos++; - firstNibble = true; - } - - auto startPos = outPos; - while(startPos > 0 && out[--startPos] != 0xF0) - ; - - if(outPos - startPos < 3 || out[startPos] != 0xF0) - continue; - - uint8 checksumStart = out[startPos + 3] ? 5 : 6; - if(outPos - startPos < checksumStart) - continue; - - for(auto p = startPos + checksumStart; p != outPos; p++) - { - data += out[p]; - } - data = (~data + 1) & 0x7F; - } else - { - // Unrecognized byte (e.g. space char) - continue; - } - - // Append parsed data - if(isNibble) // parsed a nibble (constant or 'c' variable) - { - if(firstNibble) - { - out[outPos] = data; - } else - { - out[outPos] = (out[outPos] << 4) | data; - outPos++; - } - firstNibble = !firstNibble; - } else // parsed a byte (variable) - { - if(!firstNibble) // From MIDI.TXT: '9n' is exactly the same as '09 n' or '9 n' -- so finish current byte first - { - outPos++; - } - out[outPos++] = data; - firstNibble = true; - } - } - // Finish current byte - if(!firstNibble) - outPos++; - if(updateZxxParam < 0x80) - chn.lastZxxParam = updateZxxParam; - - // Add end of SysEx byte if necessary - for(size_t i = 0; i < outPos; i++) - { - if(out[i] != 0xF0) - continue; - if(outPos - i >= 4 && (out[i + 1] == 0xF0 || out[i + 1] == 0xF1)) - { - // Internal message - i += 3; - } else - { - // Real SysEx - while(i < outPos && out[i] != 0xF7) - i++; - if(i == outPos && outPos < out.size()) - out[outPos++] = 0xF7; - } - - } - - out = out.first(outPos); -} - - // Calculate smooth MIDI macro slide parameter for current tick. float CSoundFile::CalculateSmoothParamChange(const PlayState &playState, float currentValue, float param) { @@ -5333,7 +5569,7 @@ void CSoundFile::SendMIDIData(PlayState &playState, CHANNELINDEX nChn, bool isSm } else if(macroCode == 0x03 && !isExtended) { // F0.F0.03.xx: Set plug dry/wet - PLUGINDEX plug = (plugin != 0) ? plugin : GetBestPlugin(playState, nChn, PrioritiseChannel, EvenIfMuted); + PLUGINDEX plug = (plugin != 0) ? plugin : GetBestPlugin(chn, nChn, PrioritiseChannel, EvenIfMuted); if(plug > 0 && plug <= MAX_MIXPLUGINS && param < 0x80) { plug--; @@ -5351,7 +5587,7 @@ void CSoundFile::SendMIDIData(PlayState &playState, CHANNELINDEX nChn, bool isSm } else if((macroCode & 0x80) || isExtended) { // F0.F0.{80|n}.xx / F0.F1.n.xx: Set VST effect parameter n to xx - PLUGINDEX plug = (plugin != 0) ? plugin : GetBestPlugin(playState, nChn, PrioritiseChannel, EvenIfMuted); + PLUGINDEX plug = (plugin != 0) ? plugin : GetBestPlugin(chn, nChn, PrioritiseChannel, EvenIfMuted); if(plug > 0 && plug <= MAX_MIXPLUGINS && param < 0x80) { plug--; @@ -5362,9 +5598,9 @@ void CSoundFile::SendMIDIData(PlayState &playState, CHANNELINDEX nChn, bool isSm if(localOnly) playState.m_midiMacroEvaluationResults->pluginParameter[{plug, plugParam}] = value; else if(!isSmooth) - pPlugin->SetParameter(plugParam, value); + pPlugin->SetParameter(plugParam, value, &playState, nChn); else - pPlugin->SetParameter(plugParam, CalculateSmoothParamChange(playState, pPlugin->GetParameter(plugParam), value)); + pPlugin->SetParameter(plugParam, CalculateSmoothParamChange(playState, pPlugin->GetParameter(plugParam), value), &playState, nChn); } } #endif // NO_PLUGINS @@ -5379,23 +5615,14 @@ void CSoundFile::SendMIDIData(PlayState &playState, CHANNELINDEX nChn, bool isSm PLUGINDEX plug = 0; if(!chn.dwFlags[CHN_NOFX]) { - plug = (plugin != 0) ? plugin : GetBestPlugin(playState, nChn, PrioritiseChannel, EvenIfMuted); + plug = (plugin != 0) ? plugin : GetBestPlugin(chn, nChn, PrioritiseChannel, EvenIfMuted); } if(plug > 0 && plug <= MAX_MIXPLUGINS) { if(IMixPlugin *pPlugin = m_MixPlugins[plug - 1].pMixPlugin; pPlugin != nullptr) { - if(macro[0] == 0xF0) - { - pPlugin->MidiSysexSend(mpt::byte_cast(macro)); - } else - { - size_t len = std::min(static_cast(MIDIEvents::GetEventLength(macro[0])), macro.size()); - uint32 curData = 0; - memcpy(&curData, macro.data(), len); - pPlugin->MidiSend(curData); - } + pPlugin->MidiSend(mpt::byte_cast(macro)); } } } @@ -5406,24 +5633,22 @@ void CSoundFile::SendMIDIData(PlayState &playState, CHANNELINDEX nChn, bool isSm } -void CSoundFile::SendMIDINote(CHANNELINDEX chn, uint16 note, uint16 volume) +void CSoundFile::SendMIDINote(CHANNELINDEX chn, uint16 note, uint16 volume, IMixPlugin *plugin) { #ifndef NO_PLUGINS auto &channel = m_PlayState.Chn[chn]; const ModInstrument *pIns = channel.pModInstrument; // instro sends to a midi chan - if (pIns && pIns->HasValidMIDIChannel()) + if(pIns && pIns->HasValidMIDIChannel()) { - PLUGINDEX plug = pIns->nMixPlug; - if(plug > 0 && plug <= MAX_MIXPLUGINS) + if(plugin == nullptr && pIns->nMixPlug > 0 && pIns->nMixPlug <= MAX_MIXPLUGINS) + plugin = m_MixPlugins[pIns->nMixPlug - 1].pMixPlugin; + + if(plugin != nullptr) { - IMixPlugin *pPlug = m_MixPlugins[plug - 1].pMixPlugin; - if (pPlug != nullptr) - { - pPlug->MidiCommand(*pIns, note, volume, chn); - if(note < NOTE_MIN_SPECIAL) - channel.nLeftVU = channel.nRightVU = 0xFF; - } + plugin->MidiCommand(*pIns, note, volume, chn); + if(note < NOTE_MIN_SPECIAL) + channel.nLeftVU = channel.nRightVU = 0xFF; } } #endif // NO_PLUGINS @@ -5440,7 +5665,9 @@ void CSoundFile::ProcessSampleOffset(ModChannel &chn, CHANNELINDEX nChn, const P // No X-param (normal behaviour) const bool isPercentageOffset = (m.volcmd == VOLCMD_OFFSET && m.vol == 0); offset <<= 8; - if(offset) + // FT2 compatibility: 9xx command without a note next to it does not update effect memory. + // Test case: OffsetWithoutNote.xm + if(offset && (!m_playBehaviour[kFT2OffsetMemoryRequiresNote] || m.IsNote())) chn.oldOffset = offset; else if(m.volcmd != VOLCMD_OFFSET) offset = chn.oldOffset; @@ -5452,7 +5679,7 @@ void CSoundFile::ProcessSampleOffset(ModChannel &chn, CHANNELINDEX nChn, const P { if(m.vol == 0) offset = Util::muldivr_unsigned(chn.nLength, offset, 256u << (8u * std::max(uint32(1), extendedRows))); // o00 + Oxx = Percentage Offset - else if(m.vol <= std::size(ModSample().cues) && chn.pModSample != nullptr) + else if(m.vol <= std::size(ModSample().cues) && chn.pModSample != nullptr && !chn.pModSample->uFlags[CHN_ADLIB]) offset += chn.pModSample->cues[m.vol - 1]; // Offset relative to cue point chn.oldOffset = offset; } @@ -5483,20 +5710,23 @@ void CSoundFile::SampleOffset(ModChannel &chn, SmpLength param) const param /= 2u; } - if(chn.rowCommand.IsNote() || m_playBehaviour[kApplyOffsetWithoutNote]) + // IT compatibility: Offset with instrument number but note note recalls previous note and executes offset. + // Test case: OffsetWithInstr.it + const auto note = (m_playBehaviour[kITOffsetWithInstrNumber] && chn.rowCommand.instr) ? chn.nNewNote : chn.rowCommand.note; + if(ModCommand::IsNote(note) || m_playBehaviour[kApplyOffsetWithoutNote]) { // IT compatibility: If this note is not mapped to a sample, ignore it. // Test case: empty_sample_offset.it - if(chn.pModInstrument != nullptr && chn.rowCommand.IsNote()) + if(chn.pModInstrument != nullptr && ModCommand::IsNote(note)) { - SAMPLEINDEX smp = chn.pModInstrument->Keyboard[chn.rowCommand.note - NOTE_MIN]; + SAMPLEINDEX smp = chn.pModInstrument->Keyboard[note - NOTE_MIN]; if(smp == 0 || smp > GetNumSamples()) return; } if(m_SongFlags[SONG_PT_MODE]) { - // ProTracker compatbility: PT1/2-style funky 9xx offset command + // ProTracker compatibility: PT1/2-style funky 9xx offset command // Test case: ptoffset.mod chn.position.Set(chn.prevNoteOffset); chn.prevNoteOffset += param; @@ -5614,7 +5844,7 @@ void CSoundFile::RetrigNote(CHANNELINDEX nChn, int param, int offset) { // Buggy-like-hell FT2 Rxy retrig! // Test case: retrig.xm - if(m_SongFlags[SONG_FIRSTTICK]) + if(m_PlayState.m_flags[SONG_FIRSTTICK]) { // Here are some really stupid things FT2 does on the first tick. // Test case: RetrigTick0.xm @@ -5629,7 +5859,7 @@ void CSoundFile::RetrigNote(CHANNELINDEX nChn, int param, int offset) } if(retrigCount >= retrigSpeed) { - if(!m_SongFlags[SONG_FIRSTTICK] || !chn.rowCommand.IsNote()) + if(!m_PlayState.m_flags[SONG_FIRSTTICK] || !chn.rowCommand.IsNote()) { doRetrig = true; retrigCount = 0; @@ -5664,7 +5894,7 @@ void CSoundFile::RetrigNote(CHANNELINDEX nChn, int param, int offset) // FT2 bug: if a retrig (Rxy) occurs together with a volume command, the first retrig interval is increased by one tick if((param & 0x100) && (chn.rowCommand.volcmd == VOLCMD_VOLUME) && (chn.rowCommand.param & 0xF0)) realspeed++; - if(!m_SongFlags[SONG_FIRSTTICK] || (param & 0x100)) + if(!m_PlayState.m_flags[SONG_FIRSTTICK] || (param & 0x100)) { if(!realspeed) realspeed = 1; @@ -5774,7 +6004,7 @@ void CSoundFile::RetrigNote(CHANNELINDEX nChn, int param, int offset) chn.position.Set(0); offset--; - if(chn.pModSample != nullptr && offset >= 0 && offset <= static_cast(std::size(chn.pModSample->cues))) + if(chn.pModSample != nullptr && !chn.pModSample->uFlags[CHN_ADLIB] && offset >= 0 && offset <= static_cast(std::size(chn.pModSample->cues))) { if(offset == 0) offset = chn.oldOffset; @@ -5812,28 +6042,33 @@ void CSoundFile::DoFreqSlide(ModChannel &chn, int32 &period, int32 amount, bool } else if(GetType() == MOD_TYPE_FAR) { period += (amount * 36318 / 1024); - } else if(m_SongFlags[SONG_LINEARSLIDES] && GetType() != MOD_TYPE_XM) + } else if(m_SongFlags[SONG_LINEARSLIDES] && !(GetType() & (MOD_TYPE_XM | MOD_TYPE_MOD))) { // IT Linear slides const auto oldPeriod = period; - uint32 n = std::abs(amount); - LimitMax(n, 255u * 4u); + uint32 absAmount = std::abs(amount); // Note: IT ignores the lower 2 bits when abs(mount) > 16 (it either uses the fine *or* the regular table, not both) // This means that vibratos are slightly less accurate in this range than they could be. // Other code paths will *either* have an amount that's a multiple of 4 *or* it's less than 16. - if(amount > 0) + if(absAmount < 16) { - if(n < 16) - period = Util::muldivr(period, GetFineLinearSlideUpTable(this, n), 65536); + if(amount > 0) + period = Util::muldivr(period, GetFineLinearSlideUpTable(this, absAmount), 65536); else - period = Util::muldivr(period, GetLinearSlideUpTable(this, n / 4u), 65536); + period = Util::muldivr(period, GetFineLinearSlideDownTable(this, absAmount), 65536); } else { - if(n < 16) - period = Util::muldivr(period, GetFineLinearSlideDownTable(this, n), 65536); - else - period = Util::muldivr(period, GetLinearSlideDownTable(this, n / 4u), 65536); + absAmount /= 4u; + while(absAmount > 0) + { + const uint32 n = std::min(absAmount, static_cast(std::size(LinearSlideUpTable) - 1)); + if(amount > 0) + period = Util::muldivr(period, GetLinearSlideUpTable(this, n), 65536); + else + period = Util::muldivr(period, GetLinearSlideDownTable(this, n), 65536); + absAmount -= n; + } } if(period == oldPeriod) @@ -6004,38 +6239,41 @@ TEMPO CSoundFile::ConvertST2Tempo(uint8 tempo) } -void CSoundFile::SetTempo(TEMPO param, bool setFromUI) +void CSoundFile::SetTempo(PlayState &playState, TEMPO param, bool setFromUI) const { const CModSpecifications &specs = GetModSpecifications(); // Anything lower than the minimum tempo is considered to be a tempo slide const TEMPO minTempo = GetMinimumTempoParam(GetType()); + TEMPO maxTempo = specs.GetTempoMax(); + // MED files may be imported with #xx parameter extension for tempos above 255, but they may be imported as either MOD or XM. + // As regular MOD files cannot contain effect #xx, the tempo parameter cannot exceed 255 anyway, so we simply ignore their max tempo in CModSpecifications here. + if(!(GetType() & (MOD_TYPE_XM | MOD_TYPE_IT | MOD_TYPE_MPT))) + maxTempo = GetModSpecifications(MOD_TYPE_MPT).GetTempoMax(); + if(m_playBehaviour[kTempoClamp]) + maxTempo.Set(255); if(setFromUI) { // Set tempo from UI - ignore slide commands and such. - m_PlayState.m_nMusicTempo = Clamp(param, specs.GetTempoMin(), specs.GetTempoMax()); - } else if(param >= minTempo && m_SongFlags[SONG_FIRSTTICK] == !m_playBehaviour[kMODTempoOnSecondTick]) + playState.m_nMusicTempo = Clamp(param, specs.GetTempoMin(), maxTempo); + } else if(param >= minTempo && playState.m_flags[SONG_FIRSTTICK] == !m_playBehaviour[kMODTempoOnSecondTick]) { // ProTracker sets the tempo after the first tick. // Note: The case of one tick per row is handled in ProcessRow() instead. // Test case: TempoChange.mod - m_PlayState.m_nMusicTempo = std::min(param, specs.GetTempoMax()); - } else if(param < minTempo && !m_SongFlags[SONG_FIRSTTICK]) + playState.m_nMusicTempo = std::min(param, maxTempo); + } else if(param < minTempo && !playState.m_flags[SONG_FIRSTTICK]) { // Tempo Slide TEMPO tempDiff(param.GetInt() & 0x0F, 0); if((param.GetInt() & 0xF0) == 0x10) - m_PlayState.m_nMusicTempo += tempDiff; + playState.m_nMusicTempo += tempDiff; else - m_PlayState.m_nMusicTempo -= tempDiff; + playState.m_nMusicTempo -= tempDiff; - TEMPO tempoMin = specs.GetTempoMin(), tempoMax = specs.GetTempoMax(); - if(m_playBehaviour[kTempoClamp]) // clamp tempo correctly in compatible mode - { - tempoMax.Set(255); - } - Limit(m_PlayState.m_nMusicTempo, tempoMin, tempoMax); + TEMPO tempoMin = specs.GetTempoMin(); + Limit(playState.m_nMusicTempo, tempoMin, maxTempo); } } @@ -6076,11 +6314,10 @@ void CSoundFile::PatternLoop(PlayState &state, CHANNELINDEX nChn, ModCommand::PA // IT compatibility 10. Pattern loops (+ same fix for XM / MOD / S3M files) if(!m_playBehaviour[kITFT2PatternLoop] && !(GetType() & (MOD_TYPE_MOD | MOD_TYPE_S3M))) { - auto p = state.Chn.data(); - for(CHANNELINDEX i = 0; i < GetNumChannels(); i++, p++) + for(const ModChannel &otherChn : state.PatternChannels(*this)) { // Loop on other channel - if(p != &chn && p->nPatternLoopCount) + if(&otherChn != &chn && otherChn.nPatternLoopCount) return; } } @@ -6105,10 +6342,15 @@ void CSoundFile::PatternLoop(PlayState &state, CHANNELINDEX nChn, ModCommand::PA } -void CSoundFile::GlobalVolSlide(ModCommand::PARAM param, uint8 &nOldGlobalVolSlide) +void CSoundFile::GlobalVolSlide(PlayState &playState, ModCommand::PARAM param, CHANNELINDEX chn) const { - int32 nGlbSlide = 0; - if (param) nOldGlobalVolSlide = param; else param = nOldGlobalVolSlide; + if(m_SongFlags[SONG_AUTO_GLOBALVOL]) + playState.Chn[chn].autoSlide.SetActive(AutoSlideCommand::GlobalVolumeSlide, param != 0); + + if(param) + playState.Chn[chn].nOldGlobalVolSlide = param; + else + param = playState.Chn[chn].nOldGlobalVolSlide; if((GetType() & (MOD_TYPE_XM | MOD_TYPE_MT2))) { @@ -6122,16 +6364,17 @@ void CSoundFile::GlobalVolSlide(ModCommand::PARAM param, uint8 &nOldGlobalVolSli } } + int32 nGlbSlide = 0; if (((param & 0x0F) == 0x0F) && (param & 0xF0)) { - if(m_SongFlags[SONG_FIRSTTICK]) nGlbSlide = (param >> 4) * 2; + if(playState.m_flags[SONG_FIRSTTICK]) nGlbSlide = (param >> 4) * 2; } else if (((param & 0xF0) == 0xF0) && (param & 0x0F)) { - if(m_SongFlags[SONG_FIRSTTICK]) nGlbSlide = - (int)((param & 0x0F) * 2); + if(playState.m_flags[SONG_FIRSTTICK]) nGlbSlide = - (int)((param & 0x0F) * 2); } else { - if(!m_SongFlags[SONG_FIRSTTICK]) + if(!playState.m_flags[SONG_FIRSTTICK]) { if (param & 0xF0) { @@ -6147,9 +6390,9 @@ void CSoundFile::GlobalVolSlide(ModCommand::PARAM param, uint8 &nOldGlobalVolSli if (nGlbSlide) { if(!(GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT | MOD_TYPE_IMF | MOD_TYPE_J2B | MOD_TYPE_MID | MOD_TYPE_AMS | MOD_TYPE_DBM))) nGlbSlide *= 2; - nGlbSlide += m_PlayState.m_nGlobalVolume; + nGlbSlide += playState.m_nGlobalVolume; Limit(nGlbSlide, 0, 256); - m_PlayState.m_nGlobalVolume = nGlbSlide; + playState.m_nGlobalVolume = nGlbSlide; } } @@ -6221,7 +6464,7 @@ uint32 CSoundFile::GetPeriodFromNote(uint32 note, int32 nFineTune, uint32 nC5Spe return Util::muldiv_unsigned(8363, (FreqS3MTable[note % 12u] << 5), nC5Speed << (note / 12u)); //8363 * freq[note%12] / nC5Speed * 2^(5-note/12) } - } else if(GetType() & (MOD_TYPE_XM | MOD_TYPE_MTM)) + } else if((GetType() & (MOD_TYPE_XM | MOD_TYPE_MTM)) || m_SongFlags[SONG_LINEARSLIDES]) { if (note < 12) note = 12; note -= 12; @@ -6238,7 +6481,7 @@ uint32 CSoundFile::GetPeriodFromNote(uint32 note, int32 nFineTune, uint32 nC5Spe if(m_SongFlags[SONG_LINEARSLIDES]) { - int l = ((NOTE_MAX - note) << 6) - (nFineTune / 2); + int l = ((120 - note) << 6) - (nFineTune / 2); if (l < 1) l = 1; return static_cast(l); } else @@ -6279,7 +6522,7 @@ uint32 CSoundFile::GetPeriodFromNote(uint32 note, int32 nFineTune, uint32 nC5Spe uint32 CSoundFile::GetFreqFromPeriod(uint32 period, uint32 c5speed, int32 nPeriodFrac) const { if (!period) return 0; - if (GetType() & (MOD_TYPE_XM | MOD_TYPE_MTM)) + if ((GetType() & (MOD_TYPE_XM | MOD_TYPE_MTM)) || (m_SongFlags[SONG_LINEARSLIDES] && UseFinetuneAndTranspose())) { if(m_playBehaviour[kFT2Periods]) { @@ -6346,35 +6589,30 @@ uint32 CSoundFile::GetFreqFromPeriod(uint32 period, uint32 c5speed, int32 nPerio } -PLUGINDEX CSoundFile::GetBestPlugin(const PlayState &playState, CHANNELINDEX nChn, PluginPriority priority, PluginMutePriority respectMutes) const +PLUGINDEX CSoundFile::GetBestPlugin(const ModChannel &channel, CHANNELINDEX nChn, PluginPriority priority, PluginMutePriority respectMutes) const { - if (nChn >= MAX_CHANNELS) //Check valid channel number - { - return 0; - } - //Define search source order PLUGINDEX plugin = 0; - switch (priority) + switch(priority) { case ChannelOnly: - plugin = GetChannelPlugin(playState, nChn, respectMutes); + plugin = GetChannelPlugin(channel, nChn, respectMutes); break; case InstrumentOnly: - plugin = GetActiveInstrumentPlugin(playState.Chn[nChn], respectMutes); + plugin = GetActiveInstrumentPlugin(channel, respectMutes); break; case PrioritiseInstrument: - plugin = GetActiveInstrumentPlugin(playState.Chn[nChn], respectMutes); + plugin = GetActiveInstrumentPlugin(channel, respectMutes); if(!plugin || plugin > MAX_MIXPLUGINS) { - plugin = GetChannelPlugin(playState, nChn, respectMutes); + plugin = GetChannelPlugin(channel, nChn, respectMutes); } break; case PrioritiseChannel: - plugin = GetChannelPlugin(playState, nChn, respectMutes); + plugin = GetChannelPlugin(channel, nChn, respectMutes); if(!plugin || plugin > MAX_MIXPLUGINS) { - plugin = GetActiveInstrumentPlugin(playState.Chn[nChn], respectMutes); + plugin = GetActiveInstrumentPlugin(channel, respectMutes); } break; } @@ -6383,10 +6621,8 @@ PLUGINDEX CSoundFile::GetBestPlugin(const PlayState &playState, CHANNELINDEX nCh } -PLUGINDEX CSoundFile::GetChannelPlugin(const PlayState &playState, CHANNELINDEX nChn, PluginMutePriority respectMutes) const +PLUGINDEX CSoundFile::GetChannelPlugin(const ModChannel &channel, CHANNELINDEX nChn, PluginMutePriority respectMutes) const { - const ModChannel &channel = playState.Chn[nChn]; - PLUGINDEX plugin; if((respectMutes == RespectMutes && channel.dwFlags[CHN_MUTE | CHN_SYNCMUTE]) || channel.dwFlags[CHN_NOFX]) { @@ -6396,17 +6632,12 @@ PLUGINDEX CSoundFile::GetChannelPlugin(const PlayState &playState, CHANNELINDEX // If it looks like this is an NNA channel, we need to find the master channel. // This ensures we pick up the right ChnSettings. if(channel.nMasterChn > 0) - { nChn = channel.nMasterChn - 1; - } - if(nChn < MAX_BASECHANNELS) - { + if(nChn < ChnSettings.size()) plugin = ChnSettings[nChn].nMixPlugin; - } else - { + else plugin = 0; - } } return plugin; } @@ -6420,14 +6651,10 @@ PLUGINDEX CSoundFile::GetActiveInstrumentPlugin(const ModChannel &chn, PluginMut PLUGINDEX plug = 0; if(chn.pModInstrument != nullptr) { - // TODO this looks fishy. Shouldn't it check the mute status of the instrument itself?! - if(respectMutes == RespectMutes && chn.pModSample && chn.pModSample->uFlags[CHN_MUTE]) - { + if(respectMutes == RespectMutes && chn.pModInstrument->dwFlags[INS_MUTE]) plug = 0; - } else - { + else plug = chn.pModInstrument->nMixPlug; - } } return plug; } @@ -6464,50 +6691,82 @@ IMixPlugin *CSoundFile::GetChannelInstrumentPlugin(const ModChannel &chn) const #ifdef MODPLUG_TRACKER -void CSoundFile::HandlePatternTransitionEvents() +void CSoundFile::HandleRowTransitionEvents(bool nextPattern) { - // MPT sequence override + bool doTransition = nextPattern; + + // Jump to another pattern? if(m_PlayState.m_nSeqOverride != ORDERINDEX_INVALID && m_PlayState.m_nSeqOverride < Order().size()) { - if(m_SongFlags[SONG_PATTERNLOOP]) + switch(m_PlayState.m_seqOverrideMode) { - m_PlayState.m_nPattern = Order()[m_PlayState.m_nSeqOverride]; + case OrderTransitionMode::AtPatternEnd: + doTransition = nextPattern; + break; + case OrderTransitionMode::AtMeasureEnd: + if(m_PlayState.m_nCurrentRowsPerMeasure > 0) + doTransition = (m_PlayState.m_nRow % m_PlayState.m_nCurrentRowsPerMeasure) == 0; + break; + case OrderTransitionMode::AtBeatEnd: + if(m_PlayState.m_nCurrentRowsPerBeat > 0) + doTransition = (m_PlayState.m_nRow % m_PlayState.m_nCurrentRowsPerBeat) == 0; + break; + case OrderTransitionMode::AtRowEnd: + doTransition = true; + break; + } + if(doTransition) + { + if(m_PlayState.m_flags[SONG_PATTERNLOOP]) + m_PlayState.m_nPattern = Order()[m_PlayState.m_nSeqOverride]; + m_PlayState.m_nCurrentOrder = m_PlayState.m_nSeqOverride; + m_PlayState.m_nSeqOverride = ORDERINDEX_INVALID; } - m_PlayState.m_nCurrentOrder = m_PlayState.m_nSeqOverride; - m_PlayState.m_nSeqOverride = ORDERINDEX_INVALID; } - // Channel mutes - for (CHANNELINDEX chan = 0; chan < GetNumChannels(); chan++) + if(doTransition && GetpModDoc()) { - if (m_bChannelMuteTogglePending[chan]) + // Update channel mutes + for(CHANNELINDEX chan = 0; chan < GetNumChannels(); chan++) { - if(GetpModDoc()) + if(m_bChannelMuteTogglePending[chan]) { GetpModDoc()->MuteChannel(chan, !GetpModDoc()->IsChannelMuted(chan)); + m_bChannelMuteTogglePending[chan] = false; } - m_bChannelMuteTogglePending[chan] = false; } } + + // Metronome + if(IsMetronomeEnabled() && !IsRenderingToDisc() && !m_PlayState.m_flags[SONG_PAUSED | SONG_STEP]) + { + const ROWINDEX rpm = m_PlayState.m_nCurrentRowsPerMeasure ? m_PlayState.m_nCurrentRowsPerMeasure : DEFAULT_ROWS_PER_MEASURE; + const ROWINDEX rpb = m_PlayState.m_nCurrentRowsPerBeat ? m_PlayState.m_nCurrentRowsPerBeat : DEFAULT_ROWS_PER_BEAT; + const ModSample *sample = nullptr; + if(!m_PlayState.m_lTotalSampleCount || !(m_PlayState.m_nRow % rpm)) + sample = m_metronomeMeasure; + else if(!(m_PlayState.m_nRow % rpm % rpb)) + sample = m_metronomeBeat; + if(sample) + { + m_metronomeChn.pModSample = sample; + m_metronomeChn.pCurrentSample = sample->samplev(); + m_metronomeChn.dwFlags = (sample->uFlags & CHN_SAMPLEFLAGS) | CHN_NOREVERB; + m_metronomeChn.position.Set(0); + m_metronomeChn.increment = SamplePosition::Ratio(sample->nC5Speed, m_MixerSettings.gdwMixingFreq); + m_metronomeChn.rampLeftVol = m_metronomeChn.rampRightVol = m_metronomeChn.leftVol = m_metronomeChn.rightVol = sample->nVolume * 16; + m_metronomeChn.leftRamp = m_metronomeChn.rightRamp = 0; + m_metronomeChn.nLength = m_metronomeChn.pModSample->nLength; + m_metronomeChn.resamplingMode = m_Resampler.m_Settings.SrcMode; + } + } else + { + m_metronomeChn.pCurrentSample = nullptr; + } } #endif // MODPLUG_TRACKER -// Update time signatures (global or pattern-specific). Don't forget to call this when changing the RPB/RPM settings anywhere! -void CSoundFile::UpdateTimeSignature() -{ - if(!Patterns.IsValidIndex(m_PlayState.m_nPattern) || !Patterns[m_PlayState.m_nPattern].GetOverrideSignature()) - { - m_PlayState.m_nCurrentRowsPerBeat = m_nDefaultRowsPerBeat; - m_PlayState.m_nCurrentRowsPerMeasure = m_nDefaultRowsPerMeasure; - } else - { - m_PlayState.m_nCurrentRowsPerBeat = Patterns[m_PlayState.m_nPattern].GetRowsPerBeat(); - m_PlayState.m_nCurrentRowsPerMeasure = Patterns[m_PlayState.m_nPattern].GetRowsPerMeasure(); - } -} - - void CSoundFile::PortamentoMPT(ModChannel &chn, int param) const { //Behavior: Modifies portamento by param-steps on every tick. diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Sndfile.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/Sndfile.cpp index 0a294479f..bc200f835 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/Sndfile.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Sndfile.cpp @@ -10,34 +10,36 @@ #include "stdafx.h" -#ifdef MODPLUG_TRACKER -#include "../mptrack/Mptrack.h" // For CTrackApp::OpenURL -#include "../mptrack/TrackerSettings.h" -#include "../mptrack/Moddoc.h" -#include "../mptrack/Reporting.h" -#include "../mptrack/Mainfrm.h" -#endif // MODPLUG_TRACKER -#ifdef MPT_EXTERNAL_SAMPLES -#include "mpt/io_file/inputfile.hpp" -#include "mpt/io_file_read/inputfile_filecursor.hpp" -#include "../common/mptFileIO.h" -#include "../common/mptFileIO.h" -#endif // MPT_EXTERNAL_SAMPLES -#include "../common/version.h" -#include "../soundlib/AudioCriticalSection.h" -#include "../common/serialization_utils.h" #include "Sndfile.h" -#include "Tables.h" +#include "Container.h" #include "mod_specifications.h" +#include "OPL.h" +#include "Tables.h" #include "tuningcollection.h" #include "plugins/PluginManager.h" #include "plugins/PlugInterface.h" -#include "../common/mptStringBuffer.h" #include "../common/FileReader.h" -#include "Container.h" -#include "OPL.h" +#include "../common/mptStringBuffer.h" +#include "../common/serialization_utils.h" +#include "../common/version.h" +#include "../soundlib/AudioCriticalSection.h" #include "mpt/io/io.hpp" #include "mpt/io/io_stdstream.hpp" +#include "mpt/random/seed.hpp" + +#ifdef MODPLUG_TRACKER +#include "../mptrack/Mainfrm.h" +#include "../mptrack/Moddoc.h" +#include "../mptrack/Mptrack.h" // For CTrackApp::OpenURL +#include "../mptrack/Reporting.h" +#include "../mptrack/TrackerSettings.h" +#endif // MODPLUG_TRACKER + +#ifdef MPT_EXTERNAL_SAMPLES +#include "../common/mptFileIO.h" +#include "mpt/io_file/inputfile.hpp" +#include "mpt/io_file_read/inputfile_filecursor.hpp" +#endif // MPT_EXTERNAL_SAMPLES #ifndef NO_ARCHIVE_SUPPORT #include "../unarchiver/unarchiver.h" @@ -101,13 +103,6 @@ mpt::ustring FileHistory::AsISO8601(mpt::Date::LogicalTimezone internalTimezone) } -CSoundFile::PlayState::PlayState() -{ - std::fill(std::begin(Chn), std::end(Chn), ModChannel{}); - m_midiMacroScratchSpace.reserve(kMacroLength); // Note: If macros ever become variable-length, the scratch space needs to be at least one byte longer than the longest macro in the file for end-of-SysEx insertion to stay allocation-free in the mixer! -} - - ////////////////////////////////////////////////////////// // CSoundFile @@ -175,10 +170,12 @@ void CSoundFile::AddToLog(LogLevel level, const mpt::ustring &text) const // Global variable initializer for loader functions -void CSoundFile::InitializeGlobals(MODTYPE type) +void CSoundFile::InitializeGlobals(MODTYPE type, CHANNELINDEX numChannels) { // Do not add or change any of these values! And if you do, review each and every loader to check if they require these defaults! m_nType = type; + MPT_ASSERT(numChannels <= MAX_BASECHANNELS); + LimitMax(numChannels, MAX_BASECHANNELS); MODTYPE bestType = GetBestSaveFormat(); m_playBehaviour = GetDefaultPlaybackBehaviour(bestType); @@ -187,7 +184,7 @@ void CSoundFile::InitializeGlobals(MODTYPE type) // This is such an odd behaviour that it's unlikely that any of the other formats will need it by default. Re-enable as needed. m_playBehaviour.reset(kITInitialNoteMemory); } - SetModSpecsPointer(m_pModSpecs, bestType); + m_pModSpecs = &GetModSpecifications(bestType); // Delete instruments in case some previously called loader already created them. for(INSTRUMENTINDEX i = 1; i <= m_nInstruments; i++) @@ -197,14 +194,11 @@ void CSoundFile::InitializeGlobals(MODTYPE type) } m_ContainerType = ModContainerType::None; - m_nChannels = 0; m_nInstruments = 0; m_nSamples = 0; m_nSamplePreAmp = 48; m_nVSTiVolume = 48; m_OPLVolumeFactor = m_OPLVolumeFactorScale; - m_nDefaultSpeed = 6; - m_nDefaultTempo.Set(125); m_nDefaultGlobalVolume = MAX_GLOBAL_VOLUME; m_SongFlags.reset(); m_nMinPeriod = 16; @@ -216,9 +210,10 @@ void CSoundFile::InitializeGlobals(MODTYPE type) SetMixLevels(MixLevels::Compatible); - Patterns.ClearPatterns(); + Patterns.DestroyPatterns(); Order.Initialize(); + m_globalScript.clear(); m_songName.clear(); m_songArtist.clear(); m_songMessage.clear(); @@ -231,18 +226,11 @@ void CSoundFile::InitializeGlobals(MODTYPE type) // Note: we do not use the Amiga resampler for DBM as it's a multichannel format and can make use of higher-quality Amiga soundcards instead of Paula. if(GetType() & (/*MOD_TYPE_DBM | */MOD_TYPE_DIGI | MOD_TYPE_MED | MOD_TYPE_MOD | MOD_TYPE_OKT | MOD_TYPE_SFX | MOD_TYPE_STP)) - { m_SongFlags.set(SONG_ISAMIGA); - } -} + if(GetType() & (MOD_TYPE_AMF0 | MOD_TYPE_DIGI | MOD_TYPE_MTM)) + m_SongFlags.set(SONG_FORMAT_NO_VOLCOL); - -void CSoundFile::InitializeChannels() -{ - for(CHANNELINDEX nChn = 0; nChn < MAX_BASECHANNELS; nChn++) - { - InitChannel(nChn); - } + ChnSettings.assign(numChannels, {}); } @@ -252,7 +240,7 @@ struct FileFormatLoader decltype(&CSoundFile::ReadXM) loader; }; -#ifdef MODPLUG_TRACKER +#if defined(MODPLUG_TRACKER) && !defined(MPT_BUILD_DEBUG) #define MPT_DECLARE_FORMAT(format) { nullptr, &CSoundFile::Read ## format } #else #define MPT_DECLARE_FORMAT(format) { CSoundFile::ProbeFileHeader ## format, &CSoundFile::Read ## format } @@ -310,15 +298,26 @@ static constexpr FileFormatLoader ModuleFormatLoaders[] = MPT_DECLARE_FORMAT(STP), MPT_DECLARE_FORMAT(DSym), MPT_DECLARE_FORMAT(STX), + MPT_DECLARE_FORMAT(UNIC), // Magic bytes clash with MOD, must be tried first MPT_DECLARE_FORMAT(MOD), MPT_DECLARE_FORMAT(ICE), + MPT_DECLARE_FORMAT(KRIS), MPT_DECLARE_FORMAT(669), MPT_DECLARE_FORMAT(667), MPT_DECLARE_FORMAT(C67), MPT_DECLARE_FORMAT(MO3), + MPT_DECLARE_FORMAT(FC), + MPT_DECLARE_FORMAT(FTM), + MPT_DECLARE_FORMAT(RTM), + MPT_DECLARE_FORMAT(TCB), + MPT_DECLARE_FORMAT(CBA), + MPT_DECLARE_FORMAT(ETX), MPT_DECLARE_FORMAT(DSm), - MPT_DECLARE_FORMAT(M15), + MPT_DECLARE_FORMAT(STK), MPT_DECLARE_FORMAT(XMF), + MPT_DECLARE_FORMAT(Puma), + MPT_DECLARE_FORMAT(GMC), + MPT_DECLARE_FORMAT(IMS), }; #undef MPT_DECLARE_FORMAT @@ -416,6 +415,14 @@ CSoundFile::ProbeResult CSoundFile::Probe(ProbeFlags flags, mpt::span*(format.loader))(file, loadFlags); if(loaderSuccess) + { +#if defined(MPT_BUILD_DEBUG) + // Verify that the probing function is consistent with our API contract + file.Rewind(); + const auto data = file.GetRawDataAsByteVector(ProbeRecommendedSize); + MemoryFileReader mf{mpt::as_span(data)}; + const uint64 size = file.GetLength(); + MPT_ASSERT(format.prober(mf, &size) != ProbeFailure); +#endif break; + } } if(!loaderSuccess) @@ -532,7 +549,7 @@ bool CSoundFile::CreateInternal(FileReader file, ModLoadingFlags loadFlags) } else { // New song - InitializeGlobals(); + InitializeGlobals(MOD_TYPE_NONE, 0); m_visitedRows.Initialize(true); m_dwCreatedWithVersion = Version::Current(); #if MPT_TIME_UTC_ON_DISK @@ -581,9 +598,9 @@ bool CSoundFile::CreateInternal(FileReader file, ModLoadingFlags loadFlags) // Adjust channels const auto muteFlag = GetChannelMuteFlag(); - for(CHANNELINDEX chn = 0; chn < MAX_BASECHANNELS; chn++) + for(CHANNELINDEX chn = 0; chn < ChnSettings.size(); chn++) { - LimitMax(ChnSettings[chn].nVolume, uint16(64)); + LimitMax(ChnSettings[chn].nVolume, uint8(64)); if(ChnSettings[chn].nPan > 256) ChnSettings[chn].nPan = 128; if(ChnSettings[chn].nMixPlugin > MAX_MIXPLUGINS) @@ -599,7 +616,7 @@ bool CSoundFile::CreateInternal(FileReader file, ModLoadingFlags loadFlags) sample.SanitizeLoops(); #ifdef MPT_EXTERNAL_SAMPLES - if(SampleHasPath(nSmp)) + if(SampleHasPath(nSmp) && (loadFlags & loadSampleData)) { mpt::PathString filename = GetSamplePath(nSmp); if(file.GetOptionalFileName()) @@ -651,13 +668,6 @@ bool CSoundFile::CreateInternal(FileReader file, ModLoadingFlags loadFlags) m_nInstruments = maxInstr; // Set default play state values - if(!m_nDefaultTempo.GetInt()) - m_nDefaultTempo.Set(125); - else - LimitMax(m_nDefaultTempo, TEMPO(uint16_max, 0)); - if(!m_nDefaultSpeed) - m_nDefaultSpeed = 6; - if(!m_nDefaultRowsPerBeat && m_nTempoMode == TempoMode::Modern) m_nDefaultRowsPerBeat = 1; if(m_nDefaultRowsPerMeasure < m_nDefaultRowsPerBeat) @@ -670,8 +680,8 @@ bool CSoundFile::CreateInternal(FileReader file, ModLoadingFlags loadFlags) if(!m_tempoSwing.empty()) m_tempoSwing.resize(m_nDefaultRowsPerBeat); - m_PlayState.m_nMusicSpeed = m_nDefaultSpeed; - m_PlayState.m_nMusicTempo = m_nDefaultTempo; + m_PlayState.m_nMusicSpeed = Order().GetDefaultSpeed(); + m_PlayState.m_nMusicTempo = Order().GetDefaultTempo(); m_PlayState.m_nCurrentRowsPerBeat = m_nDefaultRowsPerBeat; m_PlayState.m_nCurrentRowsPerMeasure = m_nDefaultRowsPerMeasure; m_PlayState.m_nGlobalVolume = static_cast(m_nDefaultGlobalVolume); @@ -692,7 +702,7 @@ bool CSoundFile::CreateInternal(FileReader file, ModLoadingFlags loadFlags) if(UseFinetuneAndTranspose()) m_playBehaviour.reset(kPeriodsAreHertz); - m_nMaxOrderPosition = 0; + m_restartOverridePos = m_maxOrderPosition = 0; RecalculateSamplesPerTick(); @@ -700,9 +710,7 @@ bool CSoundFile::CreateInternal(FileReader file, ModLoadingFlags loadFlags) { order.Shrink(); if(order.GetRestartPos() >= order.size()) - { order.SetRestartPos(0); - } } if(GetType() == MOD_TYPE_NONE) @@ -710,7 +718,7 @@ bool CSoundFile::CreateInternal(FileReader file, ModLoadingFlags loadFlags) return false; } - SetModSpecsPointer(m_pModSpecs, GetBestSaveFormat()); + m_pModSpecs = &GetModSpecifications(GetBestSaveFormat()); // When reading a file made with an older version of MPT, it might be necessary to upgrade some settings automatically. if(m_dwLastSavedWithVersion) @@ -747,6 +755,15 @@ bool CSoundFile::CreateInternal(FileReader file, ModLoadingFlags loadFlags) { // Plugin was found plugin.pMixPlugin->RestoreAllParameters(plugin.defaultProgram); + + // Special handling for instrument plugins in ProcessMixOps was removed + if(m_dwLastSavedWithVersion < MPT_V("1.32.00.11")) + { + if(plugin.pMixPlugin->IsInstrument()) + plugin.SetMixMode(PluginMixMode::Instrument); + if(!plugin.pMixPlugin->GetNumInputChannels()) + plugin.SetExpandedMix(false); + } } else { // Plugin not found - add to list @@ -818,6 +835,7 @@ bool CSoundFile::Destroy() m_songArtist.clear(); m_songMessage.clear(); m_FileHistory.clear(); + ChnSettings.clear(); #ifdef MPT_EXTERNAL_SAMPLES m_samplePaths.clear(); #endif // MPT_EXTERNAL_SAMPLES @@ -840,7 +858,7 @@ bool CSoundFile::Destroy() m_nType = MOD_TYPE_NONE; m_ContainerType = ModContainerType::None; - m_nChannels = m_nSamples = m_nInstruments = 0; + m_nSamples = m_nInstruments = 0; return true; } @@ -895,15 +913,15 @@ double CSoundFile::GetCurrentBPM() const void CSoundFile::ResetPlayPos() { const auto muteFlag = GetChannelMuteFlag(); - for(CHANNELINDEX i = 0; i < MAX_CHANNELS; i++) + for(CHANNELINDEX i = 0; i < m_PlayState.Chn.size(); i++) m_PlayState.Chn[i].Reset(ModChannel::resetSetPosFull, *this, i, muteFlag); m_visitedRows.Initialize(true); - m_SongFlags.reset(SONG_FADINGSONG | SONG_ENDREACHED); + m_PlayState.m_flags.reset(SONG_FADINGSONG | SONG_ENDREACHED); m_PlayState.m_nGlobalVolume = m_nDefaultGlobalVolume; - m_PlayState.m_nMusicSpeed = m_nDefaultSpeed; - m_PlayState.m_nMusicTempo = m_nDefaultTempo; + m_PlayState.m_nMusicSpeed = Order().GetDefaultSpeed(); + m_PlayState.m_nMusicTempo = Order().GetDefaultTempo(); // Do not ramp global volume when starting playback m_PlayState.ResetGlobalVolumeRamping(); @@ -916,6 +934,9 @@ void CSoundFile::ResetPlayPos() m_PlayState.m_nFrameDelay = 0; m_PlayState.m_nextPatStartRow = 0; m_PlayState.m_lTotalSampleCount = 0; + m_PlayState.m_ppqPosFract = 0.0; + m_PlayState.m_ppqPosBeat = 0; + m_PlayState.m_globalScriptState.Initialize(*this); } @@ -963,9 +984,10 @@ void CSoundFile::SetCurrentOrder(ORDERINDEX nOrder) m_PlayState.m_nPatternDelay = 0; m_PlayState.m_nFrameDelay = 0; m_PlayState.m_nextPatStartRow = 0; + m_PlayState.m_globalScriptState.Initialize(*this); } - m_SongFlags.reset(SONG_FADINGSONG | SONG_ENDREACHED); + m_PlayState.m_flags.reset(SONG_FADINGSONG | SONG_ENDREACHED); } void CSoundFile::SuspendPlugins() @@ -1000,6 +1022,21 @@ void CSoundFile::ResumePlugins() } +void CSoundFile::UpdatePluginPositions() +{ +#ifndef NO_PLUGINS + for(auto &plugin : m_MixPlugins) + { + IMixPlugin *pPlugin = plugin.pMixPlugin; + if(pPlugin != nullptr && !pPlugin->IsResumed()) + { + pPlugin->PositionChanged(); + } + } +#endif // NO_PLUGINS +} + + void CSoundFile::StopAllVsti() { #ifndef NO_PLUGINS @@ -1037,39 +1074,39 @@ void CSoundFile::RecalculateGainForAllPlugs() void CSoundFile::ResetChannels() { - m_SongFlags.reset(SONG_FADINGSONG | SONG_ENDREACHED); + m_PlayState.m_flags.reset(SONG_FADINGSONG | SONG_ENDREACHED); m_PlayState.m_nBufferCount = 0; - for(auto &chn : m_PlayState.Chn) + for(CHANNELINDEX channel = 0; channel < m_PlayState.Chn.size(); channel++) { + ModChannel &chn = m_PlayState.Chn[channel]; chn.nROfs = chn.nLOfs = 0; chn.nLength = 0; if(chn.dwFlags[CHN_ADLIB] && m_opl) - { - CHANNELINDEX c = static_cast(std::distance(m_PlayState.Chn.data(), &chn)); - m_opl->NoteCut(c); - } + m_opl->NoteCut(channel); } } #ifdef MODPLUG_TRACKER -void CSoundFile::PatternTranstionChnSolo(const CHANNELINDEX chnIndex) +void CSoundFile::PatternTranstionChnSolo(const CHANNELINDEX first, const CHANNELINDEX last) { - if(chnIndex >= m_nChannels) + if(first >= GetNumChannels() || last < first) return; - for(CHANNELINDEX i = 0; i < m_nChannels; i++) + for(CHANNELINDEX i = 0; i < GetNumChannels(); i++) { - m_bChannelMuteTogglePending[i] = !ChnSettings[i].dwFlags[CHN_MUTE]; + if(i >= first && i <= last) + m_bChannelMuteTogglePending[i] = ChnSettings[i].dwFlags[CHN_MUTE]; + else + m_bChannelMuteTogglePending[i] = !ChnSettings[i].dwFlags[CHN_MUTE]; } - m_bChannelMuteTogglePending[chnIndex] = ChnSettings[chnIndex].dwFlags[CHN_MUTE]; } void CSoundFile::PatternTransitionChnUnmuteAll() { - for(CHANNELINDEX i = 0; i < m_nChannels; i++) + for(CHANNELINDEX i = 0; i < GetNumChannels(); i++) { m_bChannelMuteTogglePending[i] = ChnSettings[i].dwFlags[CHN_MUTE]; } @@ -1082,7 +1119,7 @@ void CSoundFile::LoopPattern(PATTERNINDEX nPat, ROWINDEX nRow) { if(!Patterns.IsValidPat(nPat)) { - m_SongFlags.reset(SONG_PATTERNLOOP); + m_PlayState.m_flags.reset(SONG_PATTERNLOOP); } else { if(nRow >= Patterns[nPat].GetNumRows()) nRow = 0; @@ -1092,7 +1129,7 @@ void CSoundFile::LoopPattern(PATTERNINDEX nPat, ROWINDEX nRow) m_PlayState.m_nPatternDelay = 0; m_PlayState.m_nFrameDelay = 0; m_PlayState.m_nextPatStartRow = 0; - m_SongFlags.set(SONG_PATTERNLOOP); + m_PlayState.m_flags.set(SONG_PATTERNLOOP); } m_PlayState.m_nBufferCount = 0; } @@ -1109,7 +1146,7 @@ void CSoundFile::DontLoopPattern(PATTERNINDEX nPat, ROWINDEX nRow) m_PlayState.m_nFrameDelay = 0; m_PlayState.m_nBufferCount = 0; m_PlayState.m_nextPatStartRow = 0; - m_SongFlags.reset(SONG_PATTERNLOOP); + m_PlayState.m_flags.reset(SONG_PATTERNLOOP); } @@ -1125,6 +1162,10 @@ PlayBehaviourSet CSoundFile::GetSupportedPlaybackBehaviour(MODTYPE type) switch(type) { case MOD_TYPE_MPT: + playBehaviour.set(kOPLFlexibleNoteOff); + playBehaviour.set(kOPLwithNNA); + playBehaviour.set(kOPLNoteOffOnNoteChange); + [[fallthrough]]; case MOD_TYPE_IT: playBehaviour.set(MSF_COMPATIBLE_PLAY); playBehaviour.set(kPeriodsAreHertz); @@ -1179,12 +1220,11 @@ PlayBehaviourSet CSoundFile::GetSupportedPlaybackBehaviour(MODTYPE type) playBehaviour.set(kITPitchPanSeparation); playBehaviour.set(kITResetFilterOnPortaSmpChange); playBehaviour.set(kITInitialNoteMemory); - if(type == MOD_TYPE_MPT) - { - playBehaviour.set(kOPLFlexibleNoteOff); - playBehaviour.set(kOPLwithNNA); - playBehaviour.set(kOPLNoteOffOnNoteChange); - } + playBehaviour.set(kITNoSustainOnPortamento); + playBehaviour.set(kITEmptyNoteMapSlotIgnoreCell); + playBehaviour.set(kITOffsetWithInstrNumber); + playBehaviour.set(kITDoublePortamentoSlides); + playBehaviour.set(kITCarryAfterNoteOff); break; case MOD_TYPE_XM: @@ -1228,6 +1268,8 @@ PlayBehaviourSet CSoundFile::GetSupportedPlaybackBehaviour(MODTYPE type) playBehaviour.set(kFT2PanSustainRelease); playBehaviour.set(kFT2NoteDelayWithoutInstr); playBehaviour.set(kFT2PortaResetDirection); + playBehaviour.set(kFT2AutoVibratoAbortSweep); + playBehaviour.set(kFT2OffsetMemoryRequiresNote); break; case MOD_TYPE_S3M: @@ -1248,6 +1290,7 @@ PlayBehaviourSet CSoundFile::GetSupportedPlaybackBehaviour(MODTYPE type) playBehaviour.set(kOPLNoteOffOnNoteChange); playBehaviour.set(kApplyUpperPeriodLimit); playBehaviour.set(kST3TonePortaWithAdlibNote); + playBehaviour.set(kS3MIgnoreCombinedFineSlides); break; case MOD_TYPE_MOD: @@ -1316,6 +1359,8 @@ PlayBehaviourSet CSoundFile::GetDefaultPlaybackBehaviour(MODTYPE type) // Default behaviour was chosen to follow GUS, so kST3PortaSampleChange is enabled and kST3SampleSwap is disabled. // For SoundBlaster behaviour, those two flags would need to be swapped. playBehaviour.reset(kST3SampleSwap); + // Most trackers supporting the S3M format, including all OpenMPT versions up to now, support fine slides with Kxy / Lxy, so only enable this quirk for files made with ST3. + playBehaviour.reset(kS3MIgnoreCombinedFineSlides); break; case MOD_TYPE_XM: @@ -1363,6 +1408,8 @@ MODTYPE CSoundFile::GetBestSaveFormat() const } return MOD_TYPE_XM; case MOD_TYPE_PSM: + if(Order.GetNumSequences() > 1) + return MOD_TYPE_MPT; if(GetNumChannels() > 16) return MOD_TYPE_IT; for(CHANNELINDEX i = 0; i < GetNumChannels(); i++) @@ -1423,29 +1470,6 @@ const char *CSoundFile::GetInstrumentName(INSTRUMENTINDEX nInstr) const } -bool CSoundFile::InitChannel(CHANNELINDEX nChn) -{ - if(nChn >= MAX_BASECHANNELS) - return true; - - ChnSettings[nChn].Reset(); - m_PlayState.Chn[nChn].Reset(ModChannel::resetTotal, *this, nChn, GetChannelMuteFlag()); - -#ifdef MODPLUG_TRACKER - if(GetpModDoc() != nullptr) - { - GetpModDoc()->SetChannelRecordGroup(nChn, RecordGroup::NoGroup); - } -#endif // MODPLUG_TRACKER - -#ifdef MODPLUG_TRACKER - m_bChannelMuteTogglePending[nChn] = false; -#endif // MODPLUG_TRACKER - - return false; -} - - void CSoundFile::InitAmigaResampler() { if(m_SongFlags[SONG_ISAMIGA] && m_Resampler.m_Settings.emulateAmiga != Resampling::AmigaFilter::Off) @@ -1643,9 +1667,10 @@ mpt::ustring CSoundFile::GetNoteName(const ModCommand::NOTE note, const NoteName return specialNoteNames[note - NOTE_MIN_SPECIAL]; } else if(ModCommand::IsNote(note)) { + const int octave = (note - NOTE_MIN) / 12; return mpt::ustring() .append(noteNames[(note - NOTE_MIN) % 12]) - .append(1, static_cast(UC_('0') + ((note - NOTE_MIN) / 12))) + .append(1, static_cast((octave <= 9 ? UC_('0') : UC_('A') - 10) + octave)) ; // e.g. "C#" + "5" } else if(note == NOTE_NONE) { @@ -1670,29 +1695,29 @@ const NoteName *CSoundFile::GetDefaultNoteNames() #endif // MODPLUG_TRACKER -void CSoundFile::SetModSpecsPointer(const CModSpecifications*& pModSpecs, const MODTYPE type) +const CModSpecifications &CSoundFile::GetModSpecifications(const MODTYPE type) { switch(type) { case MOD_TYPE_MPT: - pModSpecs = &ModSpecs::mptm; + return ModSpecs::mptm; break; case MOD_TYPE_IT: - pModSpecs = &ModSpecs::itEx; + return ModSpecs::itEx; break; case MOD_TYPE_XM: - pModSpecs = &ModSpecs::xmEx; + return ModSpecs::xmEx; break; case MOD_TYPE_S3M: - pModSpecs = &ModSpecs::s3mEx; + return ModSpecs::s3mEx; break; case MOD_TYPE_MOD: default: - pModSpecs = &ModSpecs::mod; + return ModSpecs::mod; break; } } @@ -1702,7 +1727,7 @@ void CSoundFile::SetType(MODTYPE type) { m_nType = type; m_playBehaviour = GetDefaultPlaybackBehaviour(GetBestSaveFormat()); - SetModSpecsPointer(m_pModSpecs, GetBestSaveFormat()); + m_pModSpecs = &GetModSpecifications(GetBestSaveFormat()); } @@ -1712,7 +1737,7 @@ void CSoundFile::ChangeModTypeTo(const MODTYPE newType, bool adjust) { const MODTYPE oldType = GetType(); m_nType = newType; - SetModSpecsPointer(m_pModSpecs, m_nType); + m_pModSpecs = &GetModSpecifications(m_nType); if(oldType == newType || !adjust) return; @@ -1799,7 +1824,7 @@ std::vector CSoundFile::GetAllSubSongs() subSongs.reserve(subSongs.size() + subSongsSeq.size()); for(const auto &song : subSongsSeq) { - subSongs.push_back({song.duration, song.startRow, song.endRow, song.lastRow, song.startOrder, song.endOrder, song.lastOrder, seq}); + subSongs.push_back({song.duration, song.startRow, song.endRow, song.restartRow, song.startOrder, song.endOrder, song.restartOrder, seq}); } } return subSongs; @@ -1902,10 +1927,8 @@ double CSoundFile::GetRowDuration(TEMPO tempo, uint32 speed) const return static_cast(2500 * speed) / tempo.ToDouble(); case TempoMode::Modern: - { - // If there are any row delay effects, the row length factor compensates for those. - return 60000.0 / tempo.ToDouble() / static_cast(m_PlayState.m_nCurrentRowsPerBeat); - } + // If there are any row delay effects, the row length factor compensates for those. + return 60000.0 / tempo.ToDouble() / static_cast(m_PlayState.m_nCurrentRowsPerBeat); case TempoMode::Alternative: return static_cast(1000 * speed) / tempo.ToDouble(); @@ -1913,18 +1936,10 @@ double CSoundFile::GetRowDuration(TEMPO tempo, uint32 speed) const } -const CModSpecifications& CSoundFile::GetModSpecifications(const MODTYPE type) -{ - const CModSpecifications* p = nullptr; - SetModSpecsPointer(p, type); - return *p; -} - - ChannelFlags CSoundFile::GetChannelMuteFlag() { #ifdef MODPLUG_TRACKER - return (TrackerSettings::Instance().m_dwPatternSetup & PATTERN_SYNCMUTE) ? CHN_SYNCMUTE : CHN_MUTE; + return (TrackerSettings::Instance().patternSetup & PatternSetup::SyncMute) ? CHN_SYNCMUTE : CHN_MUTE; #else return CHN_SYNCMUTE; #endif @@ -2024,7 +2039,7 @@ bool CSoundFile::IsSampleReferencedByInstrument(SAMPLEINDEX sample, INSTRUMENTIN if(targetIns == nullptr) return false; - return mpt::contains(mpt::as_span(targetIns->Keyboard).first(NOTE_MAX), sample); + return mpt::contains(mpt::as_span(targetIns->Keyboard).first(NOTE_MAX - NOTE_MIN + 1), sample); } @@ -2102,20 +2117,20 @@ bool CSoundFile::LoadExternalSample(SAMPLEINDEX smp, const mpt::PathString &file #endif // MPT_EXTERNAL_SAMPLES -// Set up channel panning and volume suitable for MOD + similar files. If the current mod type is not MOD, bForceSetup has to be set to true. -void CSoundFile::SetupMODPanning(bool bForceSetup) +// Set up channel panning suitable for MOD + similar files. If the current mod type is not MOD, forceSetup has to be set to true for this function to take effect. +void CSoundFile::SetupMODPanning(bool forceSetup) { // Setup LRRL panning, max channel volume - if(!(GetType() & MOD_TYPE_MOD) && bForceSetup == false) return; + if(!(GetType() & MOD_TYPE_MOD) && !forceSetup) + return; - for(CHANNELINDEX nChn = 0; nChn < MAX_BASECHANNELS; nChn++) + for(CHANNELINDEX chn = 0; chn < GetNumChannels(); chn++) { - ChnSettings[nChn].nVolume = 64; - ChnSettings[nChn].dwFlags.reset(CHN_SURROUND); + ChnSettings[chn].dwFlags.reset(CHN_SURROUND); if(m_MixerSettings.MixerFlags & SNDMIX_MAXDEFAULTPAN) - ChnSettings[nChn].nPan = (((nChn & 3) == 1) || ((nChn & 3) == 2)) ? 256 : 0; + ChnSettings[chn].nPan = (((chn & 3) == 1) || ((chn & 3) == 2)) ? 256 : 0; else - ChnSettings[nChn].nPan = (((nChn & 3) == 1) || ((nChn & 3) == 2)) ? 0xC0 : 0x40; + ChnSettings[chn].nPan = (((chn & 3) == 1) || ((chn & 3) == 2)) ? 0xC0 : 0x40; } } diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Sndfile.h b/Frameworks/OpenMPT/OpenMPT/soundlib/Sndfile.h index 3cc25cefe..c1cc28ab6 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/Sndfile.h +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Sndfile.h @@ -19,9 +19,6 @@ #include "../common/mptFileType.h" #include "../common/mptRandom.h" #include "../common/version.h" -#include -#include -#include #include "Snd_defs.h" #include "tuningbase.h" #include "MIDIMacros.h" @@ -44,21 +41,26 @@ #include "../sounddsp/EQ.h" #endif -#include "modcommand.h" -#include "ModSample.h" -#include "ModInstrument.h" -#include "ModChannel.h" -#include "plugins/PluginStructs.h" -#include "RowVisitor.h" #include "Message.h" +#include "ModChannel.h" +#include "modcommand.h" +#include "ModInstrument.h" +#include "ModSample.h" +#include "ModSequence.h" #include "pattern.h" #include "patternContainer.h" -#include "ModSequence.h" +#include "PlayState.h" +#include "plugins/PluginStructs.h" +#include "RowVisitor.h" #include "mpt/audio/span.hpp" #include "../common/FileReaderFwd.h" +#include +#include +#include + OPENMPT_NAMESPACE_BEGIN @@ -66,17 +68,6 @@ OPENMPT_NAMESPACE_BEGIN bool SettingCacheCompleteFileBeforeLoading(); -// ----------------------------------------------------------------------------- -// MODULAR ModInstrument FIELD ACCESS : body content in InstrumentExtensions.cpp -// ----------------------------------------------------------------------------- -#ifndef MODPLUG_NO_FILESAVE -void WriteInstrumentHeaderStructOrField(ModInstrument * input, std::ostream &file, uint32 only_this_code = -1 /* -1 for all */, uint16 fixedsize = 0); -#endif // !MODPLUG_NO_FILESAVE -bool ReadInstrumentHeaderField(ModInstrument * input, uint32 fcode, uint16 fsize, FileReader &file); -// -------------------------------------------------------------------------------------------- -// -------------------------------------------------------------------------------------------- - - // Sample decompression routines in format-specific source files void AMSUnpack(mpt::const_byte_span source, mpt::byte_span dest, int8 packCharacter); uintptr_t DMFUnpack(FileReader &file, uint8 *psample, uint32 maxlen); @@ -107,14 +98,14 @@ struct PatternCuePoint // Return values for GetLength() struct GetLengthType { - double duration = 0.0; // Total time in seconds - ROWINDEX lastRow = ROWINDEX_INVALID; // Last parsed row (if no target is specified, this is the first row that is parsed twice, i.e. not the *last* played order) - ROWINDEX endRow = ROWINDEX_INVALID; // Last row before module loops (UNDEFINED if a target is specified) - ROWINDEX startRow = 0; // First row of parsed subsong - ORDERINDEX lastOrder = ORDERINDEX_INVALID; // Last parsed order (see lastRow remark) - ORDERINDEX endOrder = ORDERINDEX_INVALID; // Last order before module loops (UNDEFINED if a target is specified) - ORDERINDEX startOrder = 0; // First order of parsed subsong - bool targetReached = false; // True if the specified order/row combination or duration has been reached while going through the module + double duration = 0.0; // Total time in seconds + ROWINDEX restartRow = ROWINDEX_INVALID; // First row to play after module loops (or last parsed row if target is specified; equal to target if it was found) + ROWINDEX endRow = ROWINDEX_INVALID; // Last row played before module loops (UNDEFINED if a target is specified) + ROWINDEX startRow = 0; // First row of parsed subsong + ORDERINDEX restartOrder = ORDERINDEX_INVALID; // First row to play after module loops (see restartRow remark) + ORDERINDEX endOrder = ORDERINDEX_INVALID; // Last order played before module loops (UNDEFINED if a target is specified) + ORDERINDEX startOrder = 0; // First order of parsed subsong + bool targetReached = false; // True if the specified order/row combination or duration has been reached while going through the module }; @@ -233,6 +224,7 @@ class CTuningCollection; using CTuningCollection = Tuning::CTuningCollection; struct CModSpecifications; class OPL; +class PlaybackTest; class CModDoc; @@ -373,9 +365,34 @@ public: using NoteName = mpt::uchar[4]; +struct PlaybackTestSettings +{ + uint32 mixingFreq = 48000; + uint32 outputChannels = 2; + uint32 mixerChannels = MAX_CHANNELS; + ResamplingMode srcMode = SRCMODE_CUBIC; + void Sanitize() + { + if(mixingFreq < 1000) + { + mixingFreq = 48000; + } + if(outputChannels != 1 && outputChannels != 2 && outputChannels != 4) + { + outputChannels = 2; + } + if(mixerChannels < 1) + { + mixerChannels = MAX_CHANNELS; + } + } +}; + + class CSoundFile { friend class GetLengthMemory; + friend class MIDIMacroParser; public: #ifdef MODPLUG_TRACKER @@ -392,7 +409,6 @@ public: //Tuning--> public: static std::unique_ptr CreateTuning12TET(const mpt::ustring &name); - static CTuning *GetDefaultTuning() {return nullptr;} CTuningCollection& GetTuneSpecificTunings() {return *m_pTuningsTuneSpecific;} mpt::ustring GetNoteName(const ModCommand::NOTE note, const INSTRUMENTINDEX inst, const NoteName *noteNames = nullptr) const; @@ -416,9 +432,6 @@ private: private: CTuningCollection* m_pTuningsTuneSpecific = nullptr; -private: //Misc private methods. - static void SetModSpecsPointer(const CModSpecifications* &pModSpecs, const MODTYPE type); - private: //Misc data const CModSpecifications *m_pModSpecs; @@ -456,29 +469,24 @@ public: BitCrush m_BitCrush; #endif - using samplecount_t = uint32; // Number of rendered samples - static constexpr uint32 TICKS_ROW_FINISHED = uint32_max - 1u; -public: // for Editing +private: #ifdef MODPLUG_TRACKER - CModDoc *m_pModDoc = nullptr; // Can be a null pointer for example when previewing samples from the treeview. + CModDoc *m_pModDoc = nullptr; // Can be a null pointer for example when previewing samples from the treeview. #endif // MODPLUG_TRACKER Enum m_nType; -private: ModContainerType m_ContainerType = ModContainerType::None; public: - CHANNELINDEX m_nChannels = 0; SAMPLEINDEX m_nSamples = 0; INSTRUMENTINDEX m_nInstruments = 0; - uint32 m_nDefaultSpeed, m_nDefaultGlobalVolume; - TEMPO m_nDefaultTempo; + uint32 m_nDefaultGlobalVolume; FlagSet m_SongFlags; CHANNELINDEX m_nMixChannels = 0; private: CHANNELINDEX m_nMixStat; public: - ROWINDEX m_nDefaultRowsPerBeat, m_nDefaultRowsPerMeasure; // default rows per beat and measure for this module + ROWINDEX m_nDefaultRowsPerBeat, m_nDefaultRowsPerMeasure; // default rows per beat and measure for this module TempoMode m_nTempoMode = TempoMode::Classic; #ifdef MODPLUG_TRACKER @@ -508,14 +516,15 @@ public: ResamplingMode m_nResampling; // Resampling mode (if overriding the globally set resampling) int32 m_nRepeatCount = 0; // -1 means repeat infinitely. - ORDERINDEX m_nMaxOrderPosition; - std::array ChnSettings; // Initial channels settings + ORDERINDEX m_restartOverridePos = 0, m_maxOrderPosition = 0; + std::vector ChnSettings; // Initial channels settings CPatternContainer Patterns; ModSequenceSet Order; // Pattern sequences (order lists) protected: ModSample Samples[MAX_SAMPLES]; public: ModInstrument *Instruments[MAX_INSTRUMENTS]; // Instrument Headers + InstrumentSynth::Events m_globalScript; MIDIMacroConfig m_MidiCfg; // MIDI Macro config table #ifndef NO_PLUGINS std::array m_MixPlugins; // Mix plugins @@ -540,84 +549,6 @@ protected: MixLevels m_nMixLevels; public: - struct PlayState - { - friend class CSoundFile; - - public: - samplecount_t m_lTotalSampleCount = 0; // Total number of rendered samples - protected: - samplecount_t m_nBufferCount = 0; // Remaining number samples to render for this tick - double m_dBufferDiff = 0.0; // Modern tempo rounding error compensation - - public: - uint32 m_nTickCount = 0; // Current tick being processed - protected: - uint32 m_nPatternDelay = 0; // Pattern delay (rows) - uint32 m_nFrameDelay = 0; // Fine pattern delay (ticks) - public: - uint32 m_nSamplesPerTick = 0; - ROWINDEX m_nCurrentRowsPerBeat = 0; // Current time signature - ROWINDEX m_nCurrentRowsPerMeasure = 0; // Current time signature - uint32 m_nMusicSpeed = 0; // Current speed - TEMPO m_nMusicTempo; // Current tempo - - // Playback position - ROWINDEX m_nRow = 0; // Current row being processed - ROWINDEX m_nNextRow = 0; // Next row to process - protected: - ROWINDEX m_nextPatStartRow = 0; // For FT2's E60 bug - ROWINDEX m_breakRow = 0; // Candidate target row for pattern break - ROWINDEX m_patLoopRow = 0; // Candidate target row for pattern loop - ORDERINDEX m_posJump = 0; // Candidate target order for position jump - - public: - PATTERNINDEX m_nPattern = 0; // Current pattern being processed - ORDERINDEX m_nCurrentOrder = 0; // Current order being processed - ORDERINDEX m_nNextOrder = 0; // Next order to process - ORDERINDEX m_nSeqOverride = ORDERINDEX_INVALID; // Queued order to be processed next, regardless of what order would normally follow - - // Global volume - public: - int32 m_nGlobalVolume = MAX_GLOBAL_VOLUME; // Current global volume (0...MAX_GLOBAL_VOLUME) - protected: - int32 m_nSamplesToGlobalVolRampDest = 0, m_nGlobalVolumeRampAmount = 0, - m_nGlobalVolumeDestination = 0; // Global volume ramping - int32 m_lHighResRampingGlobalVolume = 0; // Global volume ramping - - public: - bool m_bPositionChanged = true; // Report to plugins that we jumped around in the module - - public: - std::array ChnMix; // Index of channels in Chn to be actually mixed - std::array Chn; // Mixing channels... First m_nChannels channels are master channels (i.e. they are never NNA channels)! - - struct MIDIMacroEvaluationResults - { - std::map pluginDryWetRatio; - std::map, PlugParamValue> pluginParameter; - }; - - std::vector m_midiMacroScratchSpace; - std::optional m_midiMacroEvaluationResults; - - public: - PlayState(); - - void ResetGlobalVolumeRamping() - { - m_lHighResRampingGlobalVolume = m_nGlobalVolume << VOLUMERAMPPRECISION; - m_nGlobalVolumeDestination = m_nGlobalVolume; - m_nSamplesToGlobalVolRampDest = 0; - m_nGlobalVolumeRampAmount = 0; - } - - constexpr uint32 TicksOnRow() const noexcept - { - return (m_nMusicSpeed + m_nFrameDelay) * std::max(m_nPatternDelay, uint32(1)); - } - }; - PlayState m_PlayState; protected: @@ -644,11 +575,9 @@ private: #endif // MODPLUG_TRACKER public: -#ifdef LIBOPENMPT_BUILD -#ifndef NO_PLUGINS +#if defined(LIBOPENMPT_BUILD) && !defined(NO_PLUGINS) std::unique_ptr m_PluginManager; #endif -#endif public: std::string m_songName; @@ -744,6 +673,7 @@ public: CModDoc *GetpModDoc() const noexcept { return m_pModDoc; } #endif // MODPLUG_TRACKER + void Create(MODTYPE type, CHANNELINDEX numChannels, CModDoc *pModDoc = nullptr); bool Create(FileReader file, ModLoadingFlags loadFlags = loadCompleteModule, CModDoc *pModDoc = nullptr); private: bool CreateInternal(FileReader file, ModLoadingFlags loadFlags); @@ -785,7 +715,7 @@ public: constexpr SAMPLEINDEX GetNumSamples() const noexcept { return m_nSamples; } constexpr PATTERNINDEX GetCurrentPattern() const noexcept { return m_PlayState.m_nPattern; } constexpr ORDERINDEX GetCurrentOrder() const noexcept { return m_PlayState.m_nCurrentOrder; } - constexpr CHANNELINDEX GetNumChannels() const noexcept { return m_nChannels; } + MPT_FORCEINLINE CHANNELINDEX GetNumChannels() const noexcept { return static_cast(ChnSettings.size()); } constexpr bool CanAddMoreSamples(SAMPLEINDEX amount = 1) const noexcept { return (amount < MAX_SAMPLES) && m_nSamples < (MAX_SAMPLES - amount); } constexpr bool CanAddMoreInstruments(INSTRUMENTINDEX amount = 1) const noexcept { return (amount < MAX_INSTRUMENTS) && m_nInstruments < (MAX_INSTRUMENTS - amount); } @@ -799,11 +729,25 @@ public: static ChannelFlags GetChannelMuteFlag(); #ifdef MODPLUG_TRACKER - void PatternTranstionChnSolo(const CHANNELINDEX chnIndex); + void PatternTranstionChnSolo(const CHANNELINDEX first, const CHANNELINDEX last); void PatternTransitionChnUnmuteAll(); protected: - void HandlePatternTransitionEvents(); + void HandleRowTransitionEvents(bool nextPattern); + + const ModSample *m_metronomeMeasure = nullptr; + const ModSample *m_metronomeBeat = nullptr; + ModChannel m_metronomeChn{}; + +public: + void SetMetronomeSamples(const ModSample *measure, const ModSample *beat) + { + m_metronomeMeasure = measure; + m_metronomeBeat = beat; + m_metronomeChn.pModSample = nullptr; + m_metronomeChn.pCurrentSample = nullptr; + } + constexpr bool IsMetronomeEnabled() const noexcept { return m_metronomeMeasure || m_metronomeBeat; } #endif // MODPLUG_TRACKER public: @@ -832,10 +776,9 @@ public: // A repeat count value of -1 means infinite loop void SetRepeatCount(int n) { m_nRepeatCount = n; } int GetRepeatCount() const { return m_nRepeatCount; } - bool IsPaused() const { return m_SongFlags[SONG_PAUSED | SONG_STEP]; } // Added SONG_STEP as it seems to be desirable in most cases to check for this as well. + bool IsPaused() const { return m_PlayState.m_flags[SONG_PAUSED | SONG_STEP]; } // Added SONG_STEP as it seems to be desirable in most cases to check for this as well. void LoopPattern(PATTERNINDEX nPat, ROWINDEX nRow = 0); - bool InitChannel(CHANNELINDEX nChn); void InitAmigaResampler(); void InitOPL(); @@ -857,6 +800,7 @@ public: static ProbeResult ProbeFileHeaderAMS(MemoryFileReader file, const uint64 *pfilesize); static ProbeResult ProbeFileHeaderAMS2(MemoryFileReader file, const uint64 *pfilesize); static ProbeResult ProbeFileHeaderC67(MemoryFileReader file, const uint64 *pfilesize); + static ProbeResult ProbeFileHeaderCBA(MemoryFileReader file, const uint64 *pfilesize); static ProbeResult ProbeFileHeaderDBM(MemoryFileReader file, const uint64 *pfilesize); static ProbeResult ProbeFileHeaderDTM(MemoryFileReader file, const uint64 *pfilesize); static ProbeResult ProbeFileHeaderDIGI(MemoryFileReader file, const uint64 *pfilesize); @@ -864,18 +808,24 @@ public: static ProbeResult ProbeFileHeaderDSM(MemoryFileReader file, const uint64 *pfilesize); static ProbeResult ProbeFileHeaderDSm(MemoryFileReader file, const uint64 *pfilesize); static ProbeResult ProbeFileHeaderDSym(MemoryFileReader file, const uint64 *pfilesize); + static ProbeResult ProbeFileHeaderETX(MemoryFileReader file, const uint64 *pfilesize); static ProbeResult ProbeFileHeaderFAR(MemoryFileReader file, const uint64 *pfilesize); - static ProbeResult ProbeFileHeaderFMT(MemoryFileReader file, const uint64* pfilesize); + static ProbeResult ProbeFileHeaderFC(MemoryFileReader file, const uint64 *pfilesize); + static ProbeResult ProbeFileHeaderFMT(MemoryFileReader file, const uint64 *pfilesize); + static ProbeResult ProbeFileHeaderFTM(MemoryFileReader file, const uint64 *pfilesize); static ProbeResult ProbeFileHeaderGDM(MemoryFileReader file, const uint64 *pfilesize); + static ProbeResult ProbeFileHeaderGMC(MemoryFileReader file, const uint64 *pfilesize); static ProbeResult ProbeFileHeaderGT2(MemoryFileReader file, const uint64 *pfilesize); static ProbeResult ProbeFileHeaderGTK(MemoryFileReader file, const uint64 *pfilesize); static ProbeResult ProbeFileHeaderICE(MemoryFileReader file, const uint64 *pfilesize); static ProbeResult ProbeFileHeaderIMF(MemoryFileReader file, const uint64 *pfilesize); + static ProbeResult ProbeFileHeaderIMS(MemoryFileReader file, const uint64 *pfilesize); static ProbeResult ProbeFileHeaderIT(MemoryFileReader file, const uint64 *pfilesize); static ProbeResult ProbeFileHeaderITP(MemoryFileReader file, const uint64 *pfilesize); static ProbeResult ProbeFileHeaderJ2B(MemoryFileReader file, const uint64 *pfilesize); + static ProbeResult ProbeFileHeaderKRIS(MemoryFileReader file, const uint64 *pfilesize); static ProbeResult ProbeFileHeaderMUS_KM(MemoryFileReader file, const uint64 *pfilesize); - static ProbeResult ProbeFileHeaderM15(MemoryFileReader file, const uint64 *pfilesize); + static ProbeResult ProbeFileHeaderSTK(MemoryFileReader file, const uint64 *pfilesize); static ProbeResult ProbeFileHeaderMDL(MemoryFileReader file, const uint64 *pfilesize); static ProbeResult ProbeFileHeaderMED(MemoryFileReader file, const uint64 *pfilesize); static ProbeResult ProbeFileHeaderMO3(MemoryFileReader file, const uint64 *pfilesize); @@ -888,12 +838,16 @@ public: static ProbeResult ProbeFileHeaderPSM16(MemoryFileReader file, const uint64 *pfilesize); static ProbeResult ProbeFileHeaderPT36(MemoryFileReader file, const uint64 *pfilesize); static ProbeResult ProbeFileHeaderPTM(MemoryFileReader file, const uint64 *pfilesize); + static ProbeResult ProbeFileHeaderPuma(MemoryFileReader file, const uint64 *pfilesize); + static ProbeResult ProbeFileHeaderRTM(MemoryFileReader file, const uint64 *pfilesize); static ProbeResult ProbeFileHeaderS3M(MemoryFileReader file, const uint64 *pfilesize); static ProbeResult ProbeFileHeaderSFX(MemoryFileReader file, const uint64 *pfilesize); static ProbeResult ProbeFileHeaderSTM(MemoryFileReader file, const uint64 *pfilesize); static ProbeResult ProbeFileHeaderSTP(MemoryFileReader file, const uint64 *pfilesize); static ProbeResult ProbeFileHeaderSTX(MemoryFileReader file, const uint64 *pfilesize); static ProbeResult ProbeFileHeaderSymMOD(MemoryFileReader file, const uint64 *pfilesize); + static ProbeResult ProbeFileHeaderTCB(MemoryFileReader file, const uint64 *pfilesize); + static ProbeResult ProbeFileHeaderUNIC(MemoryFileReader file, const uint64 *pfilesize); static ProbeResult ProbeFileHeaderULT(MemoryFileReader file, const uint64 *pfilesize); static ProbeResult ProbeFileHeaderXM(MemoryFileReader file, const uint64 *pfilesize); static ProbeResult ProbeFileHeaderXMF(MemoryFileReader file, const uint64 *pfilesize); @@ -911,6 +865,7 @@ public: bool ReadAMS(FileReader &file, ModLoadingFlags loadFlags = loadCompleteModule); bool ReadAMS2(FileReader &file, ModLoadingFlags loadFlags = loadCompleteModule); bool ReadC67(FileReader &file, ModLoadingFlags loadFlags = loadCompleteModule); + bool ReadCBA(FileReader &file, ModLoadingFlags loadFlags = loadCompleteModule); bool ReadDBM(FileReader &file, ModLoadingFlags loadFlags = loadCompleteModule); bool ReadDTM(FileReader &file, ModLoadingFlags loadFlags = loadCompleteModule); bool ReadDIGI(FileReader &file, ModLoadingFlags loadFlags = loadCompleteModule); @@ -918,18 +873,24 @@ public: bool ReadDSM(FileReader &file, ModLoadingFlags loadFlags = loadCompleteModule); bool ReadDSm(FileReader &file, ModLoadingFlags loadFlags = loadCompleteModule); bool ReadDSym(FileReader &file, ModLoadingFlags loadFlags = loadCompleteModule); + bool ReadETX(FileReader &file, ModLoadingFlags loadFlags = loadCompleteModule); bool ReadFAR(FileReader &file, ModLoadingFlags loadFlags = loadCompleteModule); + bool ReadFC(FileReader &file, ModLoadingFlags loadFlags = loadCompleteModule); bool ReadFMT(FileReader &file, ModLoadingFlags loadFlags = loadCompleteModule); + bool ReadFTM(FileReader &file, ModLoadingFlags loadFlags = loadCompleteModule); bool ReadGDM(FileReader &file, ModLoadingFlags loadFlags = loadCompleteModule); + bool ReadGMC(FileReader &file, ModLoadingFlags loadFlags = loadCompleteModule); bool ReadGT2(FileReader &file, ModLoadingFlags loadFlags = loadCompleteModule); bool ReadGTK(FileReader &file, ModLoadingFlags loadFlags = loadCompleteModule); bool ReadICE(FileReader &file, ModLoadingFlags loadFlags = loadCompleteModule); bool ReadIMF(FileReader &file, ModLoadingFlags loadFlags = loadCompleteModule); + bool ReadIMS(FileReader &file, ModLoadingFlags loadFlags = loadCompleteModule); bool ReadIT(FileReader &file, ModLoadingFlags loadFlags = loadCompleteModule); bool ReadITP(FileReader &file, ModLoadingFlags loadFlags = loadCompleteModule); bool ReadJ2B(FileReader &file, ModLoadingFlags loadFlags = loadCompleteModule); + bool ReadKRIS(FileReader &file, ModLoadingFlags loadFlags = loadCompleteModule); bool ReadMUS_KM(FileReader &file, ModLoadingFlags loadFlags = loadCompleteModule); - bool ReadM15(FileReader &file, ModLoadingFlags loadFlags = loadCompleteModule); + bool ReadSTK(FileReader &file, ModLoadingFlags loadFlags = loadCompleteModule); bool ReadMDL(FileReader &file, ModLoadingFlags loadFlags = loadCompleteModule); bool ReadMED(FileReader &file, ModLoadingFlags loadFlags = loadCompleteModule); bool ReadMO3(FileReader &file, ModLoadingFlags loadFlags = loadCompleteModule); @@ -942,12 +903,16 @@ public: bool ReadPSM16(FileReader &file, ModLoadingFlags loadFlags = loadCompleteModule); bool ReadPT36(FileReader &file, ModLoadingFlags loadFlags = loadCompleteModule); bool ReadPTM(FileReader &file, ModLoadingFlags loadFlags = loadCompleteModule); + bool ReadPuma(FileReader &file, ModLoadingFlags loadFlags = loadCompleteModule); + bool ReadRTM(FileReader &file, ModLoadingFlags loadFlags = loadCompleteModule); bool ReadS3M(FileReader &file, ModLoadingFlags loadFlags = loadCompleteModule); bool ReadSFX(FileReader &file, ModLoadingFlags loadFlags = loadCompleteModule); bool ReadSTM(FileReader &file, ModLoadingFlags loadFlags = loadCompleteModule); bool ReadSTP(FileReader &file, ModLoadingFlags loadFlags = loadCompleteModule); bool ReadSTX(FileReader &file, ModLoadingFlags loadFlags = loadCompleteModule); bool ReadSymMOD(FileReader &file, ModLoadingFlags loadFlags = loadCompleteModule); + bool ReadTCB(FileReader &file, ModLoadingFlags loadFlags = loadCompleteModule); + bool ReadUNIC(FileReader &file, ModLoadingFlags loadFlags = loadCompleteModule); bool ReadULT(FileReader &file, ModLoadingFlags loadFlags = loadCompleteModule); bool ReadXM(FileReader &file, ModLoadingFlags loadFlags = loadCompleteModule); bool ReadXMF(FileReader &file, ModLoadingFlags loadFlags = loadCompleteModule); @@ -971,19 +936,21 @@ public: bool SaveMod(std::ostream &f) const; bool SaveIT(std::ostream &f, const mpt::PathString &filename, bool compatibilityExport = false); uint32 SaveMixPlugins(std::ostream *file=nullptr, bool bUpdate=true); - void WriteInstrumentPropertyForAllInstruments(uint32 code, uint16 size, std::ostream &f, INSTRUMENTINDEX nInstruments) const; - void SaveExtendedInstrumentProperties(INSTRUMENTINDEX nInstruments, std::ostream &f) const; + void SaveExtendedInstrumentProperties(INSTRUMENTINDEX instr, MODTYPE forceType, std::ostream &f) const; + static void SaveExtendedInstrumentProperties(mpt::span instruments, MODTYPE forceType, std::ostream &f, bool allInstruments); void SaveExtendedSongProperties(std::ostream &f) const; #endif // MODPLUG_NO_FILESAVE + static void ReadExtendedInstrumentProperty(mpt::span instruments, const uint32 code, FileReader &file); bool LoadExtendedSongProperties(FileReader &file, bool ignoreChannelCount, bool* pInterpretMptMade = nullptr); void LoadMPTMProperties(FileReader &file, uint16 cwtv); static mpt::ustring GetImpulseTrackerVersion(uint16 cwtv, uint16 cmwt); static mpt::ustring GetSchismTrackerVersion(uint16 cwtv, uint32 reserved); - // Reads extended instrument properties(XM/IT/MPTM). + // Reads extended instrument properties(XM/IT/MPTM/ITI/XI). // Returns true if extended instrument properties were found. - bool LoadExtendedInstrumentProperties(FileReader &file); + bool LoadExtendedInstrumentProperties(FileReader &file) { return LoadExtendedInstrumentProperties(mpt::as_span(Instruments).subspan(1, GetNumInstruments()), file); } + static bool LoadExtendedInstrumentProperties(mpt::span instruments, FileReader &file); void SetDefaultPlaybackBehaviour(MODTYPE type); static PlayBehaviourSet GetSupportedPlaybackBehaviour(MODTYPE type); @@ -1000,12 +967,13 @@ public: // Converts 4 bytes formatted like SoundTracker/NoiseTracker/ProTracker pattern data by converting the period to a note and filling the instrument number, and returns the effect command and parameter bytes. static std::pair ReadMODPatternEntry(const std::array data, ModCommand &m); - void SetupMODPanning(bool bForceSetup = false); // Setup LRRL panning, max channel volume + void SetupMODPanning(bool forceSetup = false); // Setup LRRL panning public: // Real-time sound functions void SuspendPlugins(); void ResumePlugins(); + void UpdatePluginPositions(); void StopAllVsti(); void RecalculateGainForAllPlugs(); void ResetChannels(); @@ -1020,6 +988,7 @@ public: samplecount_t ReadOneTick(); private: void CreateStereoMix(int count); + bool MixChannel(int count, ModChannel &chn, CHANNELINDEX channel, bool doMix); public: bool FadeSong(uint32 msec); private: @@ -1028,7 +997,7 @@ private: void ProcessInputChannels(IAudioSource &source, std::size_t countChunk); public: samplecount_t GetTotalSampleCount() const { return m_PlayState.m_lTotalSampleCount; } - bool HasPositionChanged() { bool b = m_PlayState.m_bPositionChanged; m_PlayState.m_bPositionChanged = false; return b; } + bool HasPositionChanged() { bool b = m_PlayState.m_flags[SONG_POSITIONCHANGED]; m_PlayState.m_flags.reset(SONG_POSITIONCHANGED); return b; } bool IsRenderingToDisc() const { return m_bIsRendering; } void PrecomputeSampleLoops(bool updateChannels = false); @@ -1052,6 +1021,7 @@ public: void SetupNextRow(PlayState &playState, const bool patternLoop) const; CHANNELINDEX GetNNAChannel(CHANNELINDEX nChn) const; CHANNELINDEX CheckNNA(CHANNELINDEX nChn, uint32 instr, int note, bool forceCut); + void StopOldNNA(ModChannel &chn, CHANNELINDEX channel); void NoteChange(ModChannel &chn, int note, bool bPorta = false, bool bResetEnv = true, bool bManual = false, CHANNELINDEX channelHint = CHANNELINDEX_INVALID) const; void InstrumentChange(ModChannel &chn, uint32 instr, bool bPorta = false, bool bUpdVol = true, bool bResetEnv = true) const; void ApplyInstrumentPanning(ModChannel &chn, const ModInstrument *instr, const ModSample *smp) const; @@ -1060,7 +1030,8 @@ public: // Channel Effects void KeyOff(ModChannel &chn) const; // Global Effects - void SetTempo(TEMPO param, bool setAsNonModcommand = false); + void SetTempo(TEMPO param, bool setAsNonModcommand = false) { SetTempo(m_PlayState, param, setAsNonModcommand); } + void SetTempo(PlayState &playState, TEMPO param, bool setAsNonModcommand = false) const; void SetSpeed(PlayState &playState, uint32 param) const; static TEMPO ConvertST2Tempo(uint8 tempo); @@ -1071,8 +1042,7 @@ public: protected: // Global variable initializer for loader functions void SetType(MODTYPE type); - void InitializeGlobals(MODTYPE type = MOD_TYPE_NONE); - void InitializeChannels(); + void InitializeGlobals(MODTYPE type, CHANNELINDEX numChannels); // Channel effect processing int GetVibratoDelta(int type, int position) const; @@ -1110,7 +1080,10 @@ protected: Pan8bit = 8, }; // Channel Effects + void ResetAutoSlides(ModChannel &chn) const; + void ProcessAutoSlides(PlayState &playState, CHANNELINDEX channel); void UpdateS3MEffectMemory(ModChannel &chn, ModCommand::PARAM param) const; + void PortamentoFC(ModChannel &chn) const; void PortamentoUp(CHANNELINDEX nChn, ModCommand::PARAM param, const bool doFinePortamentoAsRegular); void PortamentoUp(PlayState &playState, CHANNELINDEX nChn, ModCommand::PARAM param, const bool doFinePortamentoAsRegular) const; void PortamentoDown(CHANNELINDEX nChn, ModCommand::PARAM param, const bool doFinePortamentoAsRegular); @@ -1127,13 +1100,19 @@ protected: int16 CalculateFinetuneTarget(PATTERNINDEX pattern, ROWINDEX row, CHANNELINDEX channel) const; void NoteSlide(ModChannel &chn, uint32 param, bool slideUp, bool retrig) const; std::pair GetVolCmdTonePorta(const ModCommand &m, uint32 startTick) const; + bool TonePortamentoSharesEffectMemory() const; + void InitTonePortamento(ModChannel &chn, uint16 param) const; void TonePortamento(CHANNELINDEX chn, uint16 param); int32 TonePortamento(PlayState &playState, CHANNELINDEX nChn, uint16 param) const; + void TonePortamentoWithDuration(ModChannel &chn, uint16 param = uint16_max) const; void Vibrato(ModChannel &chn, uint32 param) const; void FineVibrato(ModChannel &chn, uint32 param) const; + void AutoVolumeSlide(ModChannel &chn, ModCommand::PARAM param) const; + void VolumeDownETX(const PlayState &playState, ModChannel &chn, ModCommand::PARAM param) const; void VolumeSlide(ModChannel &chn, ModCommand::PARAM param) const; void PanningSlide(ModChannel &chn, ModCommand::PARAM param, bool memory = true) const; void ChannelVolSlide(ModChannel &chn, ModCommand::PARAM param) const; + void ChannelVolumeDownWithDuration(ModChannel &chn, uint16 param = uint16_max) const; void FineVolumeUp(ModChannel &chn, ModCommand::PARAM param, bool volCol) const; void FineVolumeDown(ModChannel &chn, ModCommand::PARAM param, bool volCol) const; void Tremolo(ModChannel &chn, uint32 param) const; @@ -1150,27 +1129,25 @@ protected: bool HandleNextRow(PlayState &state, const ModSequence &order, bool honorPatternLoop) const; void ExtendedMODCommands(CHANNELINDEX nChn, ModCommand::PARAM param); void ExtendedS3MCommands(CHANNELINDEX nChn, ModCommand::PARAM param); - void ExtendedChannelEffect(ModChannel &chn, uint32 param); + void ExtendedChannelEffect(ModChannel &chn, uint32 param, PlayState &playState) const; void InvertLoop(ModChannel &chn); void PositionJump(PlayState &state, CHANNELINDEX chn) const; ROWINDEX PatternBreak(PlayState &state, CHANNELINDEX chn, uint8 param) const; - void GlobalVolSlide(ModCommand::PARAM param, uint8 &nOldGlobalVolSlide); + void GlobalVolSlide(PlayState &playState, ModCommand::PARAM param, CHANNELINDEX chn) const; void ProcessMacroOnChannel(CHANNELINDEX nChn); void ProcessMIDIMacro(PlayState &playState, CHANNELINDEX nChn, bool isSmooth, const MIDIMacroConfigData::Macro ¯o, uint8 param = 0, PLUGINDEX plugin = 0); - void ParseMIDIMacro(PlayState &playState, CHANNELINDEX nChn, bool isSmooth, const mpt::span macro, mpt::span &out, uint8 param = 0, PLUGINDEX plugin = 0) const; - static float CalculateSmoothParamChange(const PlayState &playState, float currentValue, float param); void SendMIDIData(PlayState &playState, CHANNELINDEX nChn, bool isSmooth, const mpt::span macro, PLUGINDEX plugin); - void SendMIDINote(CHANNELINDEX chn, uint16 note, uint16 volume); + void SendMIDINote(CHANNELINDEX chn, uint16 note, uint16 volume, IMixPlugin *plugin = nullptr); int SetupChannelFilter(ModChannel &chn, bool bReset, int envModifier = 256) const; int HandleNoteChangeFilter(ModChannel &chn) const; - // Low-Level effect processing - void DoFreqSlide(ModChannel &chn, int32 &period, int32 amount, bool isTonePorta = false) const; - void UpdateTimeSignature(); - public: + static float CalculateSmoothParamChange(const PlayState &playState, float currentValue, float param); + + void DoFreqSlide(ModChannel &chn, int32 &period, int32 amount, bool isTonePorta = false) const; + // Convert frequency to IT cutoff (0...127) uint8 FrequencyToCutOff(double frequency) const; // Convert IT cutoff (0...127 + modifier) to frequency @@ -1202,6 +1179,11 @@ public: return UseCombinedPortamentoCommands(GetType()); } + uint32 GlobalVolumeRange() const noexcept + { + return !(GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT | MOD_TYPE_IMF | MOD_TYPE_J2B | MOD_TYPE_MID | MOD_TYPE_AMS | MOD_TYPE_DBM | MOD_TYPE_PTM | MOD_TYPE_MDL | MOD_TYPE_DTM)) ? 64 : 128; + } + bool DestroySample(SAMPLEINDEX nSample); bool DestroySampleThreadsafe(SAMPLEINDEX nSample); @@ -1237,7 +1219,7 @@ protected: bool ReadXISample(SAMPLEINDEX nSample, FileReader &file); bool ReadITSSample(SAMPLEINDEX nSample, FileReader &file, bool rewind = true); bool ReadITISample(SAMPLEINDEX nSample, FileReader &file); - bool ReadIFFSample(SAMPLEINDEX sample, FileReader &file, bool allowLittleEndian = true); + bool ReadIFFSample(SAMPLEINDEX sample, FileReader &file, bool allowLittleEndian = true, uint8 octave = uint8_max); bool ReadBRRSample(SAMPLEINDEX sample, FileReader &file); bool ReadFLACSample(SAMPLEINDEX sample, FileReader &file); bool ReadOpusSample(SAMPLEINDEX sample, FileReader &file); @@ -1288,7 +1270,7 @@ public: uint32 MapMidiInstrument(uint8 program, uint16 bank, uint8 midiChannel, uint8 note, bool isXG, std::bitset<32> drumChns); size_t ITInstrToMPT(FileReader &file, ModInstrument &ins, uint16 trkvers); - std::pair LoadMixPlugins(FileReader &file); + std::pair LoadMixPlugins(FileReader &file, bool ignoreChannelCount = true); #ifndef NO_PLUGINS static void ReadMixPluginChunk(FileReader &file, SNDMIXPLUGIN &plugin); void ProcessMidiOut(CHANNELINDEX nChn); @@ -1298,12 +1280,16 @@ public: void ProcessStereoSeparation(samplecount_t countChunk); private: - PLUGINDEX GetChannelPlugin(const PlayState &playState, CHANNELINDEX nChn, PluginMutePriority respectMutes) const; + PLUGINDEX GetChannelPlugin(const ModChannel &channel, CHANNELINDEX nChn, PluginMutePriority respectMutes) const; static PLUGINDEX GetActiveInstrumentPlugin(const ModChannel &chn, PluginMutePriority respectMutes); IMixPlugin *GetChannelInstrumentPlugin(const ModChannel &chn) const; public: - PLUGINDEX GetBestPlugin(const PlayState &playState, CHANNELINDEX nChn, PluginPriority priority, PluginMutePriority respectMutes) const; + PLUGINDEX GetBestPlugin(const ModChannel &channel, CHANNELINDEX nChn, PluginPriority priority, PluginMutePriority respectMutes) const; + +#if defined(MPT_ENABLE_PLAYBACK_TRACE) + PlaybackTest CreatePlaybackTest(PlaybackTestSettings settings); +#endif // MPT_ENABLE_PLAYBACK_TRACE }; @@ -1319,22 +1305,10 @@ inline IMixPlugin* CSoundFile::GetInstrumentPlugin(INSTRUMENTINDEX instr) const #endif // NO_PLUGINS -/////////////////////////////////////////////////////////// -// Low-level Mixing functions - #define FADESONGDELAY 100 MPT_CONSTEXPRINLINE int8 MOD2XMFineTune(int v) { return static_cast(static_cast(v) << 4); } MPT_CONSTEXPRINLINE int8 XM2MODFineTune(int v) { return static_cast(static_cast(v) >> 4); } -// Read instrument property with 'code' and 'size' from 'file' to instrument 'pIns'. -void ReadInstrumentExtensionField(ModInstrument* pIns, const uint32 code, const uint16 size, FileReader &file); - -// Read instrument property with 'code' from 'file' to instrument 'pIns'. -void ReadExtendedInstrumentProperty(ModInstrument* pIns, const uint32 code, FileReader &file); - -// Read extended instrument properties from 'file' to instrument 'pIns'. -void ReadExtendedInstrumentProperties(ModInstrument* pIns, FileReader &file); - OPENMPT_NAMESPACE_END diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Sndmix.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/Sndmix.cpp index 28549f81c..3f2e03795 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/Sndmix.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Sndmix.cpp @@ -98,6 +98,9 @@ void CSoundFile::InitPlayer(bool bReset) #endif #ifndef NO_DSP m_BitCrush.Initialize(bReset, m_MixerSettings.gdwMixingFreq); +#endif +#ifdef MODPLUG_TRACKER + m_metronomeChn.pCurrentSample = nullptr; #endif if(m_opl) { @@ -196,7 +199,7 @@ void CSoundFile::ProcessInputChannels(IAudioSource &source, std::size_t countChu // Read one tick but skip all expensive rendering options -CSoundFile::samplecount_t CSoundFile::ReadOneTick() +samplecount_t CSoundFile::ReadOneTick() { const auto origMaxMixChannels = m_MixerSettings.m_nMaxMixChannels; m_MixerSettings.m_nMaxMixChannels = 0; @@ -216,25 +219,24 @@ CSoundFile::samplecount_t CSoundFile::ReadOneTick() } -CSoundFile::samplecount_t CSoundFile::Read(samplecount_t count, IAudioTarget &target, IAudioSource &source, std::optional> outputMonitor, std::optional> inputMonitor) +samplecount_t CSoundFile::Read(samplecount_t count, IAudioTarget &target, IAudioSource &source, std::optional> outputMonitor, std::optional> inputMonitor) { MPT_ASSERT_ALWAYS(m_MixerSettings.IsValid()); samplecount_t countRendered = 0; samplecount_t countToRender = count; - while(!m_SongFlags[SONG_ENDREACHED] && countToRender > 0) + while(!m_PlayState.m_flags[SONG_ENDREACHED] && countToRender > 0) { - // Update Channel Data if(!m_PlayState.m_nBufferCount) { // Last tick or fade completely processed, find out what to do next - if(m_SongFlags[SONG_FADINGSONG]) + if(m_PlayState.m_flags[SONG_FADINGSONG]) { // Song was faded out - m_SongFlags.set(SONG_ENDREACHED); + m_PlayState.m_flags.set(SONG_ENDREACHED); } else if(ReadNote()) { // Render next tick (normal progress) @@ -253,32 +255,26 @@ CSoundFile::samplecount_t CSoundFile::Read(samplecount_t count, IAudioTarget &ta } else { // No new pattern data - #ifdef MODPLUG_TRACKER - if((m_nMaxOrderPosition) && (m_PlayState.m_nCurrentOrder >= m_nMaxOrderPosition)) - { - m_SongFlags.set(SONG_ENDREACHED); - } - #endif // MODPLUG_TRACKER if(IsRenderingToDisc()) { // Disable song fade when rendering or when requested in libopenmpt. - m_SongFlags.set(SONG_ENDREACHED); + m_PlayState.m_flags.set(SONG_ENDREACHED); } else { // end of song reached, fade it out if(FadeSong(FADESONGDELAY)) // sets m_nBufferCount xor returns false { // FadeSong sets m_nBufferCount here MPT_ASSERT(m_PlayState.m_nBufferCount > 0); - m_SongFlags.set(SONG_FADINGSONG); + m_PlayState.m_flags.set(SONG_FADINGSONG); } else { - m_SongFlags.set(SONG_ENDREACHED); + m_PlayState.m_flags.set(SONG_ENDREACHED); } } } } - if(m_SongFlags[SONG_ENDREACHED]) + if(m_PlayState.m_flags[SONG_ENDREACHED]) { // Mix done. @@ -346,6 +342,15 @@ CSoundFile::samplecount_t CSoundFile::Read(samplecount_t count, IAudioTarget &ta ProcessDSP(countChunk); } +#ifdef MODPLUG_TRACKER + // Metronome needs to be mixed last, so that it is not affected by global volume, plugins, DSP effects, etc... + // It will still be visible on VU Meters though, which is not optimal. + if(IsMetronomeEnabled()) + { + MixChannel(countChunk, m_metronomeChn, CHANNELINDEX_INVALID, true); + } +#endif // MODPLUG_TRACKER + if(m_MixerSettings.gnChannels == 4) { InterleaveFrontRear(MixSoundBuffer, MixRearBuffer, countChunk); @@ -363,6 +368,9 @@ CSoundFile::samplecount_t CSoundFile::Read(samplecount_t count, IAudioTarget &ta countToRender -= countChunk; m_PlayState.m_nBufferCount -= countChunk; m_PlayState.m_lTotalSampleCount += countChunk; + const ROWINDEX rowsPerBeat = m_PlayState.m_nCurrentRowsPerBeat ? m_PlayState.m_nCurrentRowsPerBeat : DEFAULT_ROWS_PER_BEAT; + if(!m_PlayState.m_nBufferCount && !m_PlayState.m_flags[SONG_PAUSED]) + m_PlayState.m_ppqPosFract += 1.0 / (rowsPerBeat * m_PlayState.TicksOnRow()); #ifdef MODPLUG_TRACKER if(IsRenderingToDisc()) @@ -375,7 +383,7 @@ CSoundFile::samplecount_t CSoundFile::Read(samplecount_t count, IAudioTarget &ta // the end of the next tick. if(m_PlayState.m_nMusicSpeed == uint16_max && (m_nMixStat == 0 || m_PlayState.m_nGlobalVolume == 0) && GetType() == MOD_TYPE_XM && !m_PlayState.m_nBufferCount) { - m_SongFlags.set(SONG_ENDREACHED); + m_PlayState.m_flags.set(SONG_ENDREACHED); } } #endif // MODPLUG_TRACKER @@ -438,13 +446,10 @@ bool CSoundFile::ProcessRow() { while(++m_PlayState.m_nTickCount >= m_PlayState.TicksOnRow()) { - const auto [ignoreRow, patternTransition] = NextRow(m_PlayState, m_SongFlags[SONG_BREAKTOROW]); + const auto [ignoreRow, patternTransition] = NextRow(m_PlayState, m_PlayState.m_flags[SONG_BREAKTOROW]); #ifdef MODPLUG_TRACKER - if(patternTransition) - { - HandlePatternTransitionEvents(); - } + HandleRowTransitionEvents(patternTransition); // "Lock row" editing feature if(m_lockRowStart != ROWINDEX_INVALID && (m_PlayState.m_nRow < m_lockRowStart || m_PlayState.m_nRow > m_lockRowEnd) && !IsRenderingToDisc()) { @@ -455,29 +460,32 @@ bool CSoundFile::ProcessRow() { m_PlayState.m_nCurrentOrder = m_lockOrderStart; } -#else - MPT_UNUSED_VARIABLE(patternTransition); #endif // MODPLUG_TRACKER + m_PlayState.UpdatePPQ(patternTransition); + // Check if pattern is valid - if(!m_SongFlags[SONG_PATTERNLOOP]) + if(!m_PlayState.m_flags[SONG_PATTERNLOOP]) { - m_PlayState.m_nPattern = (m_PlayState.m_nCurrentOrder < Order().size()) ? Order()[m_PlayState.m_nCurrentOrder] : Order.GetInvalidPatIndex(); - if (m_PlayState.m_nPattern < Patterns.Size() && !Patterns[m_PlayState.m_nPattern].IsValid()) m_PlayState.m_nPattern = Order.GetIgnoreIndex(); - while (m_PlayState.m_nPattern >= Patterns.Size()) + const size_t songEnd = m_maxOrderPosition ? m_maxOrderPosition : Order().size(); + m_PlayState.m_nPattern = (m_PlayState.m_nCurrentOrder < songEnd) ? Order()[m_PlayState.m_nCurrentOrder] : PATTERNINDEX_INVALID; + if(m_PlayState.m_nPattern < Patterns.Size() && !Patterns[m_PlayState.m_nPattern].IsValid()) + m_PlayState.m_nPattern = PATTERNINDEX_SKIP; + + while(m_PlayState.m_nPattern >= Patterns.Size()) { // End of song? - if ((m_PlayState.m_nPattern == Order.GetInvalidPatIndex()) || (m_PlayState.m_nCurrentOrder >= Order().size())) + if ((m_PlayState.m_nPattern == PATTERNINDEX_INVALID) || (m_PlayState.m_nCurrentOrder >= songEnd)) { - ORDERINDEX restartPosOverride = Order().GetRestartPos(); - if(restartPosOverride == 0 && m_PlayState.m_nCurrentOrder <= Order().size() && m_PlayState.m_nCurrentOrder > 0) + ORDERINDEX restartPosOverride = m_maxOrderPosition ? m_restartOverridePos : Order().GetRestartPos(); + if(restartPosOverride == 0 && m_PlayState.m_nCurrentOrder <= songEnd && m_PlayState.m_nCurrentOrder > 0) { // Subtune detection. Subtunes are separated by "---" order items, so if we're in a // subtune and there's no restart position, we go to the first order of the subtune // (i.e. the first order after the previous "---" item) for(ORDERINDEX ord = m_PlayState.m_nCurrentOrder - 1; ord > 0; ord--) { - if(Order()[ord] == Order.GetInvalidPatIndex()) + if(Order()[ord] == PATTERNINDEX_INVALID) { // Jump back to first order of this subtune restartPosOverride = ord + 1; @@ -488,20 +496,20 @@ bool CSoundFile::ProcessRow() // If channel resetting is disabled in MPT, we will emulate a pattern break (and we always do it if we're not in MPT) #ifdef MODPLUG_TRACKER - if(!(TrackerSettings::Instance().m_dwPatternSetup & PATTERN_RESETCHANNELS)) + if(!(TrackerSettings::Instance().patternSetup & PatternSetup::ResetChannelsOnLoop)) #endif // MODPLUG_TRACKER { - m_SongFlags.set(SONG_BREAKTOROW); + m_PlayState.m_flags.set(SONG_BREAKTOROW); } - if (restartPosOverride == 0 && !m_SongFlags[SONG_BREAKTOROW]) + if (restartPosOverride == 0 && !m_PlayState.m_flags[SONG_BREAKTOROW]) { //rewbs.instroVSTi: stop all VSTi at end of song, if looping. StopAllVsti(); - m_PlayState.m_nMusicSpeed = m_nDefaultSpeed; - m_PlayState.m_nMusicTempo = m_nDefaultTempo; + m_PlayState.m_nMusicSpeed = Order().GetDefaultSpeed(); + m_PlayState.m_nMusicTempo = Order().GetDefaultTempo(); m_PlayState.m_nGlobalVolume = m_nDefaultGlobalVolume; - for(CHANNELINDEX i = 0; i < MAX_CHANNELS; i++) + for(CHANNELINDEX i = 0; i < m_PlayState.Chn.size(); i++) { auto &chn = m_PlayState.Chn[i]; if(chn.dwFlags[CHN_ADLIB] && m_opl) @@ -511,7 +519,7 @@ bool CSoundFile::ProcessRow() chn.dwFlags.set(CHN_NOTEFADE | CHN_KEYOFF); chn.nFadeOutVol = 0; - if(i < m_nChannels) + if(i < GetNumChannels()) { chn.nGlobalVol = ChnSettings[i].nVolume; chn.nVolume = ChnSettings[i].nVolume; @@ -537,9 +545,9 @@ bool CSoundFile::ProcessRow() //Handle Repeat position m_PlayState.m_nCurrentOrder = restartPosOverride; - m_SongFlags.reset(SONG_BREAKTOROW); + m_PlayState.m_flags.reset(SONG_BREAKTOROW); //If restart pos points to +++, move along - while(m_PlayState.m_nCurrentOrder < Order().size() && Order()[m_PlayState.m_nCurrentOrder] == Order.GetIgnoreIndex()) + while(m_PlayState.m_nCurrentOrder < Order().size() && Order()[m_PlayState.m_nCurrentOrder] == PATTERNINDEX_SKIP) { m_PlayState.m_nCurrentOrder++; } @@ -558,16 +566,12 @@ bool CSoundFile::ProcessRow() if (m_PlayState.m_nCurrentOrder < Order().size()) m_PlayState.m_nPattern = Order()[m_PlayState.m_nCurrentOrder]; else - m_PlayState.m_nPattern = Order.GetInvalidPatIndex(); + m_PlayState.m_nPattern = PATTERNINDEX_INVALID; if (m_PlayState.m_nPattern < Patterns.Size() && !Patterns[m_PlayState.m_nPattern].IsValid()) - m_PlayState.m_nPattern = Order.GetIgnoreIndex(); + m_PlayState.m_nPattern = PATTERNINDEX_SKIP; } m_PlayState.m_nNextOrder = m_PlayState.m_nCurrentOrder; - -#ifdef MODPLUG_TRACKER - if ((m_nMaxOrderPosition) && (m_PlayState.m_nCurrentOrder >= m_nMaxOrderPosition)) return false; -#endif // MODPLUG_TRACKER } // Weird stuff? @@ -580,7 +584,7 @@ bool CSoundFile::ProcessRow() // But: We will not mark the row as modified if the song is not in loop mode but // the pattern loop (editor flag, not to be confused with the pattern loop effect) // flag is set - because in that case, the module would stop after the first pattern loop... - const bool overrideLoopCheck = (m_nRepeatCount != -1) && m_SongFlags[SONG_PATTERNLOOP]; + const bool overrideLoopCheck = (m_nRepeatCount != -1) && m_PlayState.m_flags[SONG_PATTERNLOOP]; if(!overrideLoopCheck && m_visitedRows.Visit(m_PlayState.m_nCurrentOrder, m_PlayState.m_nRow, m_PlayState.Chn, ignoreRow)) { if(m_nRepeatCount) @@ -604,7 +608,7 @@ bool CSoundFile::ProcessRow() { for(const auto &t : GetLength(eNoAdjust, GetLengthTarget(true))) { - if(t.lastOrder == m_PlayState.m_nCurrentOrder && t.lastRow == m_PlayState.m_nRow) + if(t.restartOrder == m_PlayState.m_nCurrentOrder && t.restartRow == m_PlayState.m_nRow) { isReallyAtEnd = true; break; @@ -642,12 +646,12 @@ bool CSoundFile::ProcessRow() } // When jumping to the next subsong, stop all playing notes from the previous song... const auto muteFlag = CSoundFile::GetChannelMuteFlag(); - for(CHANNELINDEX i = 0; i < MAX_CHANNELS; i++) + for(CHANNELINDEX i = 0; i < m_PlayState.Chn.size(); i++) m_PlayState.Chn[i].Reset(ModChannel::resetSetPosFull, *this, i, muteFlag); StopAllVsti(); // ...and the global playback information. - m_PlayState.m_nMusicSpeed = m_nDefaultSpeed; - m_PlayState.m_nMusicTempo = m_nDefaultTempo; + m_PlayState.m_nMusicSpeed = Order().GetDefaultSpeed(); + m_PlayState.m_nMusicTempo = Order().GetDefaultTempo(); m_PlayState.m_nGlobalVolume = m_nDefaultGlobalVolume; m_PlayState.m_nNextOrder = m_PlayState.m_nCurrentOrder; @@ -666,63 +670,63 @@ bool CSoundFile::ProcessRow() } } - SetupNextRow(m_PlayState, m_SongFlags[SONG_PATTERNLOOP]); + SetupNextRow(m_PlayState, m_PlayState.m_flags[SONG_PATTERNLOOP]); // Reset channel values ModCommand *m = Patterns[m_PlayState.m_nPattern].GetpModCommand(m_PlayState.m_nRow, 0); - for (ModChannel *pChn = m_PlayState.Chn.data(), *pEnd = pChn + m_nChannels; pChn != pEnd; pChn++, m++) + for(ModChannel &chn : m_PlayState.PatternChannels(*this)) { // First, handle some quirks that happen after the last tick of the previous row... if(m_playBehaviour[KST3PortaAfterArpeggio] - && pChn->nCommand == CMD_ARPEGGIO // Previous row state! + && chn.nCommand == CMD_ARPEGGIO // Previous row state! && (m->command == CMD_PORTAMENTOUP || m->command == CMD_PORTAMENTODOWN)) { // In ST3, a portamento immediately following an arpeggio continues where the arpeggio left off. // Test case: PortaAfterArp.s3m - pChn->nPeriod = GetPeriodFromNote(pChn->nArpeggioLastNote, pChn->nFineTune, pChn->nC5Speed); + chn.nPeriod = GetPeriodFromNote(chn.nArpeggioLastNote, chn.nFineTune, chn.nC5Speed); } if(m_playBehaviour[kMODOutOfRangeNoteDelay] && !m->IsNote() - && pChn->rowCommand.IsNote() - && pChn->rowCommand.command == CMD_MODCMDEX && (pChn->rowCommand.param & 0xF0) == 0xD0 - && (pChn->rowCommand.param & 0x0Fu) >= m_PlayState.m_nMusicSpeed) + && chn.rowCommand.IsNote() + && chn.rowCommand.command == CMD_MODCMDEX && (chn.rowCommand.param & 0xF0) == 0xD0 + && (chn.rowCommand.param & 0x0Fu) >= m_PlayState.m_nMusicSpeed) { // In ProTracker, a note triggered by an out-of-range note delay can be heard on the next row // if there is no new note on that row. // Test case: NoteDelay-NextRow.mod - pChn->nPeriod = GetPeriodFromNote(pChn->rowCommand.note, pChn->nFineTune, 0); + chn.nPeriod = GetPeriodFromNote(chn.rowCommand.note, chn.nFineTune, 0); } if(m_playBehaviour[kST3TonePortaWithAdlibNote] && !m->IsNote() - && pChn->dwFlags[CHN_ADLIB] - && pChn->nPortamentoDest - && pChn->rowCommand.IsNote() - && pChn->rowCommand.IsPortamento()) + && chn.dwFlags[CHN_ADLIB] + && chn.nPortamentoDest + && chn.rowCommand.IsNote() + && chn.rowCommand.IsTonePortamento()) { // ST3: Adlib Note + Tone Portamento does not execute the slide, but changes to the target note instantly on the next row (unless there is another note with tone portamento) // Test case: TonePortamentoWithAdlibNote.s3m - pChn->nPeriod = pChn->nPortamentoDest; + chn.nPeriod = chn.nPortamentoDest; } - if(m_playBehaviour[kMODTempoOnSecondTick] && !m_playBehaviour[kMODVBlankTiming] && m_PlayState.m_nMusicSpeed == 1 && pChn->rowCommand.command == CMD_TEMPO) + if(m_playBehaviour[kMODTempoOnSecondTick] && !m_playBehaviour[kMODVBlankTiming] && m_PlayState.m_nMusicSpeed == 1 && chn.rowCommand.command == CMD_TEMPO) { // ProTracker sets the tempo after the first tick. This block handles the case of one tick per row. // Test case: TempoChange.mod - m_PlayState.m_nMusicTempo = TEMPO(std::max(ModCommand::PARAM(1), pChn->rowCommand.param), 0); + m_PlayState.m_nMusicTempo = TEMPO(std::max(ModCommand::PARAM(1), chn.rowCommand.param), 0); } - pChn->rowCommand = *m; - - pChn->rightVol = pChn->newRightVol; - pChn->leftVol = pChn->newLeftVol; - pChn->dwFlags.reset(CHN_VIBRATO | CHN_TREMOLO); - if(!m_playBehaviour[kITVibratoTremoloPanbrello]) pChn->nPanbrelloOffset = 0; - pChn->nCommand = CMD_NONE; - pChn->m_plugParamValueStep = 0; + chn.rightVol = chn.newRightVol; + chn.leftVol = chn.newLeftVol; + chn.dwFlags.reset(CHN_VIBRATO | CHN_TREMOLO); + if(!m_playBehaviour[kITVibratoTremoloPanbrello]) + chn.nPanbrelloOffset = 0; + chn.nCommand = CMD_NONE; + chn.m_plugParamValueStep = 0; + chn.rowCommand = *m++; } // Now that we know which pattern we're on, we can update time signatures (global or pattern-specific) - UpdateTimeSignature(); + m_PlayState.UpdateTimeSignature(*this); if(ignoreRow) { @@ -738,17 +742,17 @@ bool CSoundFile::ProcessRow() #ifdef MODPLUG_TRACKER if (m_PlayState.m_nTickCount >= m_PlayState.TicksOnRow() - 1) { - if(m_SongFlags[SONG_STEP]) + if(m_PlayState.m_flags[SONG_STEP]) { - m_SongFlags.reset(SONG_STEP); - m_SongFlags.set(SONG_PAUSED); + m_PlayState.m_flags.reset(SONG_STEP); + m_PlayState.m_flags.set(SONG_PAUSED); } } #endif // MODPLUG_TRACKER if (m_PlayState.m_nTickCount) { - m_SongFlags.reset(SONG_FIRSTTICK); + m_PlayState.m_flags.reset(SONG_FIRSTTICK); if(!(GetType() & (MOD_TYPE_XM | MOD_TYPE_MT2)) && (GetType() != MOD_TYPE_MOD || m_SongFlags[SONG_PT_MODE]) // Fix infinite loop in "GamerMan " by MrGamer, which was made with FT2 && m_PlayState.m_nTickCount < m_PlayState.TicksOnRow()) @@ -757,13 +761,13 @@ bool CSoundFile::ProcessRow() // Test cases: PatternDelaysRetrig.it, PatternDelaysRetrig.s3m, PatternDelaysRetrig.xm, PatternDelaysRetrig.mod if(!(m_PlayState.m_nTickCount % (m_PlayState.m_nMusicSpeed + m_PlayState.m_nFrameDelay))) { - m_SongFlags.set(SONG_FIRSTTICK); + m_PlayState.m_flags.set(SONG_FIRSTTICK); } } } else { - m_SongFlags.set(SONG_FIRSTTICK); - m_SongFlags.reset(SONG_BREAKTOROW); + m_PlayState.m_flags.set(SONG_FIRSTTICK); + m_PlayState.m_flags.reset(SONG_BREAKTOROW); } // Update Effects @@ -910,7 +914,7 @@ void CSoundFile::ProcessTremolo(ModChannel &chn, int &vol) const { if (chn.dwFlags[CHN_TREMOLO]) { - if(m_SongFlags.test_all(SONG_FIRSTTICK | SONG_PT_MODE)) + if(m_SongFlags[SONG_PT_MODE] && m_PlayState.m_flags[SONG_FIRSTTICK]) { // ProTracker doesn't apply tremolo nor advance on the first tick. // Test case: VibratoReset.mod @@ -926,12 +930,12 @@ void CSoundFile::ProcessTremolo(ModChannel &chn, int &vol) const int delta = GetVibratoDelta(chn.nTremoloType, chn.nTremoloPos); if((chn.nTremoloType & 0x03) == 1 && m_playBehaviour[kFT2MODTremoloRampWaveform]) { - // FT2 compatibility: Tremolo ramp down / triangle implementation is weird and affected by vibrato position (copypaste bug) + // FT2 compatibility: Tremolo ramp down / triangle implementation is weird and affected by vibrato position (copy-paste bug) // Test case: TremoloWaveforms.xm, TremoloVibrato.xm uint8 ramp = (chn.nTremoloPos * 4u) & 0x7F; - // Volume-colum vibrato gets executed first in FT2, so we may need to advance the vibrato position first + // Volume-column vibrato gets executed first in FT2, so we may need to advance the vibrato position first uint32 vibPos = chn.nVibratoPos; - if(!m_SongFlags[SONG_FIRSTTICK] && chn.dwFlags[CHN_VIBRATO]) + if(!m_PlayState.m_flags[SONG_FIRSTTICK] && chn.dwFlags[CHN_VIBRATO]) vibPos += chn.nVibratoSpeed; if((vibPos & 0x3F) >= 32) ramp ^= 0x7F; @@ -949,11 +953,11 @@ void CSoundFile::ProcessTremolo(ModChannel &chn, int &vol) const vol -= (vol * chn.nTremoloDepth * (64 - delta)) / (128 * 64); } } - if(!m_SongFlags[SONG_FIRSTTICK] || ((GetType() & (MOD_TYPE_IT|MOD_TYPE_MPT)) && !m_SongFlags[SONG_ITOLDEFFECTS])) + if(!m_PlayState.m_flags[SONG_FIRSTTICK] || ((GetType() & (MOD_TYPE_IT|MOD_TYPE_MPT)) && !m_SongFlags[SONG_ITOLDEFFECTS])) { // IT compatibility: IT has its own, more precise tables if(m_playBehaviour[kITVibratoTremoloPanbrello]) - chn.nTremoloPos += 4 * chn.nTremoloSpeed; + chn.nTremoloPos += static_cast(4u * chn.nTremoloSpeed); else chn.nTremoloPos += chn.nTremoloSpeed; } @@ -971,7 +975,7 @@ void CSoundFile::ProcessTremor(CHANNELINDEX nChn, int &vol) // Test case: Tremor.xm if(chn.nTremorCount & 0x80) { - if(!m_SongFlags[SONG_FIRSTTICK] && chn.nCommand == CMD_TREMOR) + if(!m_PlayState.m_flags[SONG_FIRSTTICK] && chn.nCommand == CMD_TREMOR) { chn.nTremorCount &= ~0x20; if(chn.nTremorCount == 0x80) @@ -1029,7 +1033,7 @@ void CSoundFile::ProcessTremor(CHANNELINDEX nChn, int &vol) chn.nTremorCount = tremcount + 1; } else { - if(m_SongFlags[SONG_FIRSTTICK]) + if(m_PlayState.m_flags[SONG_FIRSTTICK]) { // tremcount is only 0 on the first tremor tick after triggering a note. if(tremcount > 0) @@ -1159,7 +1163,6 @@ void CSoundFile::ProcessPanningEnvelope(ModChannel &chn) const pan += (envval * (pan)) / 32; } chn.nRealPan = Clamp(pan, 0, 256); - } } @@ -1377,7 +1380,8 @@ void CSoundFile::ProcessInstrumentFade(ModChannel &chn, int &vol) const if (fadeout) { chn.nFadeOutVol -= fadeout * 2; - if (chn.nFadeOutVol <= 0) chn.nFadeOutVol = 0; + if (chn.nFadeOutVol <= 0) + chn.nFadeOutVol = 0; vol = (vol * chn.nFadeOutVol) / 65536; } else if (!chn.nFadeOutVol) { @@ -1476,8 +1480,8 @@ void CSoundFile::ProcessArpeggio(CHANNELINDEX nChn, int32 &period, Tuning::NOTEI // - If there's no arpeggio // - but an arpeggio note is still active and // - there's no note stop or new note that would stop it anyway - if((arpOnRow && chn.nArpeggioLastNote != arpNote && (!chn.isFirstTick || !chn.rowCommand.IsNote() || chn.rowCommand.IsPortamento())) - || (!arpOnRow && (chn.rowCommand.note == NOTE_NONE || chn.rowCommand.IsPortamento()) && chn.nArpeggioLastNote != NOTE_NONE)) + if((arpOnRow && chn.nArpeggioLastNote != arpNote && (!chn.isFirstTick || !chn.rowCommand.IsNote() || chn.rowCommand.IsTonePortamento())) + || (!arpOnRow && (chn.rowCommand.note == NOTE_NONE || chn.rowCommand.IsTonePortamento()) && chn.nArpeggioLastNote != NOTE_NONE)) SendMIDINote(nChn, arpNote | IMixPlugin::MIDI_NOTE_ARPEGGIO, static_cast(chn.nVolume)); // Stop note: // - If some arpeggio note is still registered or @@ -1513,7 +1517,7 @@ void CSoundFile::ProcessArpeggio(CHANNELINDEX nChn, int32 &period, Tuning::NOTEI chn.m_ReCalculateFreqOnFirstTick = true; } else { - if(GetType() == MOD_TYPE_MT2 && m_SongFlags[SONG_FIRSTTICK]) + if(GetType() == MOD_TYPE_MT2 && m_PlayState.m_flags[SONG_FIRSTTICK]) { // MT2 resets any previous portamento when an arpeggio occurs. chn.nPeriod = period = GetPeriodFromNote(chn.nNote, chn.nFineTune, chn.nC5Speed); @@ -1542,7 +1546,7 @@ void CSoundFile::ProcessArpeggio(CHANNELINDEX nChn, int32 &period, Tuning::NOTEI } else if(m_playBehaviour[kFT2Arpeggio]) { // FastTracker 2: Swedish tracker logic (TM) arpeggio - if(!m_SongFlags[SONG_FIRSTTICK]) + if(!m_PlayState.m_flags[SONG_FIRSTTICK]) { // Arpeggio is added on top of current note, but cannot do it the IT way because of // the behaviour in ArpeggioClamp.xm. @@ -1633,6 +1637,12 @@ void CSoundFile::ProcessArpeggio(CHANNELINDEX nChn, int32 &period, Tuning::NOTEI } } } + } else if(chn.rowCommand.command == CMD_HMN_MEGA_ARP) + { + uint8 note = static_cast(GetNoteFromPeriod(period, chn.nFineTune, chn.nC5Speed)); + note += HisMastersNoiseMegaArp[chn.rowCommand.param & 0x0F][chn.nArpeggio & 0x0F]; + chn.nArpeggio++; + period = GetPeriodFromNote(note, chn.nFineTune, chn.nC5Speed); } } @@ -1643,7 +1653,7 @@ void CSoundFile::ProcessVibrato(CHANNELINDEX nChn, int32 &period, Tuning::RATIOT if(chn.dwFlags[CHN_VIBRATO]) { - const bool advancePosition = !m_SongFlags[SONG_FIRSTTICK] || ((GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT | MOD_TYPE_MED)) && !(m_SongFlags[SONG_ITOLDEFFECTS])); + const bool advancePosition = !m_PlayState.m_flags[SONG_FIRSTTICK] || ((GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT | MOD_TYPE_MED)) && !(m_SongFlags[SONG_ITOLDEFFECTS])); if(GetType() == MOD_TYPE_669) { @@ -1657,7 +1667,7 @@ void CSoundFile::ProcessVibrato(CHANNELINDEX nChn, int32 &period, Tuning::RATIOT // IT compatibility: IT has its own, more precise tables and pre-increments the vibrato position if(advancePosition && m_playBehaviour[kITVibratoTremoloPanbrello]) - chn.nVibratoPos += 4 * chn.nVibratoSpeed; + chn.nVibratoPos += static_cast(4u * chn.nVibratoSpeed); int vdelta = GetVibratoDelta(chn.nVibratoType, chn.nVibratoPos); @@ -1665,7 +1675,7 @@ void CSoundFile::ProcessVibrato(CHANNELINDEX nChn, int32 &period, Tuning::RATIOT { //Hack implementation: Scaling vibratofactor to [0.95; 1.05] //using figure from above tables and vibratodepth parameter - vibratoFactor += 0.05f * (vdelta * chn.nVibratoDepth) / (128.0f * 60.0f); + vibratoFactor += 0.05f * static_cast(vdelta * static_cast(chn.nVibratoDepth)) / (128.0f * 60.0f); chn.m_CalculateFreq = true; chn.m_ReCalculateFreqOnFirstTick = false; @@ -1674,7 +1684,7 @@ void CSoundFile::ProcessVibrato(CHANNELINDEX nChn, int32 &period, Tuning::RATIOT } else { // Original behaviour - if(m_SongFlags.test_all(SONG_FIRSTTICK | SONG_PT_MODE) || ((GetType() & (MOD_TYPE_DIGI | MOD_TYPE_DBM)) && m_SongFlags[SONG_FIRSTTICK])) + if((m_SongFlags[SONG_PT_MODE] || (GetType() & (MOD_TYPE_DIGI | MOD_TYPE_DBM))) && m_PlayState.m_flags[SONG_FIRSTTICK]) { // ProTracker doesn't apply vibrato nor advance on the first tick. // Test case: VibratoReset.mod @@ -1852,23 +1862,33 @@ void CSoundFile::ProcessSampleAutoVibrato(ModChannel &chn, int32 &period, Tuning } else { // MPT's autovibrato code + int32 autoVibDepth = chn.nAutoVibDepth; + const int32 fullDepth = pSmp->nVibDepth * 256u; if (pSmp->nVibSweep == 0 && !(GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT))) { - chn.nAutoVibDepth = pSmp->nVibDepth * 256; + autoVibDepth = fullDepth; } else { // Calculate current autovibrato depth using vibsweep - if (GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT)) + if(GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT)) { - chn.nAutoVibDepth += pSmp->nVibSweep * 2u; + autoVibDepth += pSmp->nVibSweep * 2u; + LimitMax(autoVibDepth, fullDepth); + chn.nAutoVibDepth = autoVibDepth; } else { - if(!chn.dwFlags[CHN_KEYOFF]) + if(!chn.dwFlags[CHN_KEYOFF] && autoVibDepth <= fullDepth) { - chn.nAutoVibDepth += (pSmp->nVibDepth * 256u) / pSmp->nVibSweep; + autoVibDepth += fullDepth / pSmp->nVibSweep; + chn.nAutoVibDepth = autoVibDepth; } + // FT2 compatibility: Key-off before auto-vibrato sweep-in is complete resets auto-vibrato depth + // Test case: AutoVibratoSweepKeyOff.xm + if(autoVibDepth > fullDepth) + autoVibDepth = fullDepth; + else if(chn.dwFlags[CHN_KEYOFF] && m_playBehaviour[kFT2AutoVibratoAbortSweep]) + autoVibDepth = fullDepth / pSmp->nVibSweep; } - LimitMax(chn.nAutoVibDepth, static_cast(pSmp->nVibDepth * 256u)); } chn.nAutoVibPos += pSmp->nVibRate; int vdelta; @@ -1900,12 +1920,12 @@ void CSoundFile::ProcessSampleAutoVibrato(ModChannel &chn, int32 &period, Tuning vdelta = (-ITSinusTable[(chn.nAutoVibPos + 192) & 0xFF] + 64) / 2; } } - int n = (vdelta * chn.nAutoVibDepth) / 256; + int n = (vdelta * autoVibDepth) / 256; if(hasTuning) { //Vib sweep is not taken into account here. - vibratoFactor += 0.05F * pSmp->nVibDepth * vdelta / 4096.0f; //4096 == 64^2 + vibratoFactor += 0.05f * static_cast(static_cast(pSmp->nVibDepth) * vdelta) / 4096.0f; //4096 == 64^2 //See vibrato for explanation. chn.m_CalculateFreq = true; /* @@ -2020,7 +2040,7 @@ int CSoundFile::HandleNoteChangeFilter(ModChannel &chn) const if(!chn.triggerNote) return cutoff; - bool useFilter = !m_SongFlags[SONG_MPTFILTERMODE]; + bool useFilter = !m_PlayState.m_flags[SONG_MPTFILTERMODE]; if(const ModInstrument *pIns = chn.pModInstrument; pIns != nullptr) { if(pIns->IsResonanceEnabled()) @@ -2090,7 +2110,7 @@ bool CSoundFile::ReadNote() { #ifdef MODPLUG_TRACKER // Checking end of row ? - if(m_SongFlags[SONG_PAUSED]) + if(m_PlayState.m_flags[SONG_PAUSED]) { m_PlayState.m_nTickCount = 0; if (!m_PlayState.m_nMusicSpeed) m_PlayState.m_nMusicSpeed = 6; @@ -2104,13 +2124,14 @@ bool CSoundFile::ReadNote() //////////////////////////////////////////////////////////////////////////////////// if (m_PlayState.m_nMusicTempo.GetRaw() == 0) return false; + m_PlayState.m_globalScriptState.NextTick(m_PlayState, *this); m_PlayState.m_nSamplesPerTick = GetTickDuration(m_PlayState); m_PlayState.m_nBufferCount = m_PlayState.m_nSamplesPerTick; // Master Volume + Pre-Amplification / Attenuation setup uint32 nMasterVol; { - CHANNELINDEX nchn32 = Clamp(m_nChannels, CHANNELINDEX(1), CHANNELINDEX(31)); + CHANNELINDEX nchn32 = Clamp(GetNumChannels(), CHANNELINDEX(1), CHANNELINDEX(31)); uint32 mastervol; @@ -2147,7 +2168,7 @@ bool CSoundFile::ReadNote() //////////////////////////////////////////////////////////////////////////////////// // Update channels data m_nMixChannels = 0; - for (CHANNELINDEX nChn = 0; nChn < MAX_CHANNELS; nChn++) + for(CHANNELINDEX nChn = 0; nChn < m_PlayState.Chn.size(); nChn++) { ModChannel &chn = m_PlayState.Chn[nChn]; // FT2 Compatibility: Prevent notes to be stopped after a fadeout. This way, a portamento effect can pick up a faded instrument which is long enough. @@ -2158,10 +2179,13 @@ bool CSoundFile::ReadNote() chn.nLength = 0; chn.nROfs = chn.nLOfs = 0; } + // Increment age of NNA channels + if(chn.nMasterChn && nChn < GetNumChannels() && chn.nnaChannelAge < Util::MaxValueOfType(chn.nnaChannelAge)) + chn.nnaChannelAge++; // Check for unused channel - if(chn.dwFlags[CHN_MUTE] || (nChn >= m_nChannels && !chn.nLength)) + if(chn.dwFlags[CHN_MUTE] || (nChn >= GetNumChannels() && !chn.nLength)) { - if(nChn < m_nChannels) + if(nChn < GetNumChannels()) { // Process MIDI macros on channels that are currently muted. ProcessMacroOnChannel(nChn); @@ -2185,10 +2209,12 @@ bool CSoundFile::ReadNote() // Calc Frequency int32 period = 0; + chn.synthState.NextTick(m_PlayState, nChn, *this); + // Also process envelopes etc. when there's a plugin on this channel, for possible fake automation using volume and pan data. // We only care about master channels, though, since automation only "happens" on them. const bool samplePlaying = (chn.nPeriod && chn.nLength); - const bool plugAssigned = (nChn < m_nChannels) && (ChnSettings[nChn].nMixPlugin || (chn.pModInstrument != nullptr && chn.pModInstrument->nMixPlug)); + const bool plugAssigned = (nChn < GetNumChannels()) && (ChnSettings[nChn].nMixPlugin || (chn.pModInstrument != nullptr && chn.pModInstrument->nMixPlug)); if (samplePlaying || plugAssigned) { int vol = chn.nVolume; @@ -2274,7 +2300,7 @@ bool CSoundFile::ReadNote() // When glissando mode is set to semitones, clamp to the next halftone. if((chn.dwFlags & (CHN_GLISSANDO | CHN_PORTAMENTO)) == (CHN_GLISSANDO | CHN_PORTAMENTO) - && (!m_SongFlags[SONG_PT_MODE] || (chn.rowCommand.IsPortamento() && !m_SongFlags[SONG_FIRSTTICK]))) + && (!m_SongFlags[SONG_PT_MODE] || (chn.rowCommand.IsTonePortamento() && !m_PlayState.m_flags[SONG_FIRSTTICK]))) { if(period != chn.cachedPeriod) { @@ -2312,7 +2338,7 @@ bool CSoundFile::ReadNote() // IT Compatibility: Ensure that there is no pan swing, panbrello, panning envelopes, etc. applied on surround channels. // Test case: surround-pan.it - if(chn.dwFlags[CHN_SURROUND] && !m_SongFlags[SONG_SURROUNDPAN] && m_playBehaviour[kITNoSurroundPan]) + if(chn.dwFlags[CHN_SURROUND] && !m_PlayState.m_flags[SONG_SURROUNDPAN] && m_playBehaviour[kITNoSurroundPan]) { chn.nRealPan = 128; } @@ -2341,7 +2367,7 @@ bool CSoundFile::ReadNote() // XM Compatibility: Vibrato should be advanced twice (but not added up) if both volume-column and effect column vibrato is present. // Effect column vibrato parameter has precedence if non-zero. // Test case: VibratoDouble.xm - if(!m_SongFlags[SONG_FIRSTTICK]) + if(!m_PlayState.m_flags[SONG_FIRSTTICK]) chn.nVibratoPos += chn.nVibratoSpeed; } else if(GetType() & (MOD_TYPE_IT | MOD_TYPE_MPT)) { @@ -2357,6 +2383,9 @@ bool CSoundFile::ReadNote() if(samplePlaying) { + chn.synthState.ApplyChannelState(chn, period, *this); + m_PlayState.m_globalScriptState.ApplyChannelState(m_PlayState, nChn, period, *this); + int nPeriodFrac = 0; ProcessSampleAutoVibrato(chn, period, vibratoFactor, nPeriodFrac); @@ -2599,7 +2628,7 @@ void CSoundFile::ProcessMacroOnChannel(CHANNELINDEX nChn) //ProcessMIDIMacro(m_PlayState, nChn, false, m_MidiCfg.Global[MIDIOUT_PAN]); //ProcessMIDIMacro(m_PlayState, nChn, false, m_MidiCfg.Global[MIDIOUT_VOLUME]); - if((chn.rowCommand.command == CMD_MIDI && m_SongFlags[SONG_FIRSTTICK]) || chn.rowCommand.command == CMD_SMOOTHMIDI) + if((chn.rowCommand.command == CMD_MIDI && m_PlayState.m_flags[SONG_FIRSTTICK]) || chn.rowCommand.command == CMD_SMOOTHMIDI) { if(chn.rowCommand.param < 0x80) ProcessMIDIMacro(m_PlayState, nChn, (chn.rowCommand.command == CMD_SMOOTHMIDI), m_MidiCfg.SFx[chn.nActiveMacro], chn.rowCommand.param); @@ -2630,7 +2659,7 @@ void CSoundFile::ProcessMidiOut(CHANNELINDEX nChn) } // Check instrument plugins - const PLUGINDEX nPlugin = GetBestPlugin(m_PlayState, nChn, PrioritiseInstrument, RespectMutes); + const PLUGINDEX nPlugin = GetBestPlugin(chn, nChn, PrioritiseInstrument, RespectMutes); IMixPlugin *pPlugin = nullptr; if(nPlugin > 0 && nPlugin <= MAX_MIXPLUGINS) { @@ -2659,7 +2688,7 @@ void CSoundFile::ProcessMidiOut(CHANNELINDEX nChn) ModCommand::NOTE realNote = note; if(ModCommand::IsNote(note)) realNote = pIns->NoteMap[note - NOTE_MIN]; - SendMIDINote(nChn, realNote, static_cast(chn.nVolume)); + SendMIDINote(nChn, realNote, static_cast(chn.nVolume), m_playBehaviour[kMIDINotesFromChannelPlugin] ? pPlugin : nullptr); } else if(hasVolCommand) { pPlugin->MidiCC(MIDIEvents::MIDICC_Volume_Fine, vol / 2u, nChn); @@ -2692,8 +2721,8 @@ void CSoundFile::ProcessMidiOut(CHANNELINDEX nChn) realNote = pIns->NoteMap[note - NOTE_MIN]; // Experimental VST panning //ProcessMIDIMacro(nChn, false, m_MidiCfg.Global[MIDIOUT_PAN], 0, nPlugin); - if(m_playBehaviour[kPluginIgnoreTonePortamento] || !chn.rowCommand.IsPortamento()) - SendMIDINote(nChn, realNote, static_cast(velocity)); + if(m_playBehaviour[kPluginIgnoreTonePortamento] || !chn.rowCommand.IsTonePortamento()) + SendMIDINote(nChn, realNote, static_cast(velocity), m_playBehaviour[kMIDINotesFromChannelPlugin] ? pPlugin : nullptr); } const bool processVolumeAlsoOnNote = (pIns->pluginVelocityHandling == PLUGIN_VELOCITYHANDLING_VOLUME); @@ -2705,7 +2734,7 @@ void CSoundFile::ProcessMidiOut(CHANNELINDEX nChn) { case PLUGIN_VOLUMEHANDLING_DRYWET: if(hasVolCommand) pPlugin->SetDryRatio(1.0f - vol / 127.0f); - else pPlugin->SetDryRatio(1.0f - (2 * defaultVolume) / 127.0f); + else pPlugin->SetDryRatio(1.0f - static_cast(2 * defaultVolume) / 127.0f); break; case PLUGIN_VOLUMEHANDLING_MIDI: if(hasVolCommand) pPlugin->MidiCC(MIDIEvents::MIDICC_Volume_Coarse, std::min(uint8(127), vol), nChn); @@ -2713,7 +2742,7 @@ void CSoundFile::ProcessMidiOut(CHANNELINDEX nChn) break; default: break; - } + } } } @@ -2731,14 +2760,14 @@ MPT_FORCEINLINE void ApplyGlobalVolumeWithRamping(int32 *SoundBuffer, int32 *Rea { // Ramping required m_lHighResRampingGlobalVolume += step; - SoundBuffer[0] = Util::muldiv(SoundBuffer[0], m_lHighResRampingGlobalVolume, MAX_GLOBAL_VOLUME << VOLUMERAMPPRECISION); + SoundBuffer[0] = Util::muldiv(SoundBuffer[0], m_lHighResRampingGlobalVolume, MAX_GLOBAL_VOLUME << VOLUMERAMPPRECISION); if constexpr(isStereo) SoundBuffer[1] = Util::muldiv(SoundBuffer[1], m_lHighResRampingGlobalVolume, MAX_GLOBAL_VOLUME << VOLUMERAMPPRECISION); if constexpr(hasRear) RearBuffer[0] = Util::muldiv(RearBuffer[0] , m_lHighResRampingGlobalVolume, MAX_GLOBAL_VOLUME << VOLUMERAMPPRECISION); else MPT_UNUSED_VARIABLE(RearBuffer); if constexpr(hasRear) RearBuffer[1] = Util::muldiv(RearBuffer[1] , m_lHighResRampingGlobalVolume, MAX_GLOBAL_VOLUME << VOLUMERAMPPRECISION); else MPT_UNUSED_VARIABLE(RearBuffer); m_nSamplesToGlobalVolRampDest--; } else { - SoundBuffer[0] = Util::muldiv(SoundBuffer[0], m_nGlobalVolume, MAX_GLOBAL_VOLUME); + SoundBuffer[0] = Util::muldiv(SoundBuffer[0], m_nGlobalVolume, MAX_GLOBAL_VOLUME); if constexpr(isStereo) SoundBuffer[1] = Util::muldiv(SoundBuffer[1], m_nGlobalVolume, MAX_GLOBAL_VOLUME); if constexpr(hasRear) RearBuffer[0] = Util::muldiv(RearBuffer[0] , m_nGlobalVolume, MAX_GLOBAL_VOLUME); else MPT_UNUSED_VARIABLE(RearBuffer); if constexpr(hasRear) RearBuffer[1] = Util::muldiv(RearBuffer[1] , m_nGlobalVolume, MAX_GLOBAL_VOLUME); else MPT_UNUSED_VARIABLE(RearBuffer); @@ -2780,8 +2809,8 @@ void CSoundFile::ProcessGlobalVolume(samplecount_t lCount) // Still some ramping left to do. int32 highResGlobalVolumeDestination = static_cast(m_PlayState.m_nGlobalVolumeDestination) << VOLUMERAMPPRECISION; - const long delta = highResGlobalVolumeDestination - m_PlayState.m_lHighResRampingGlobalVolume; - step = delta / static_cast(m_PlayState.m_nSamplesToGlobalVolRampDest); + const int32 delta = highResGlobalVolumeDestination - m_PlayState.m_lHighResRampingGlobalVolume; + step = delta / m_PlayState.m_nSamplesToGlobalVolRampDest; if(m_nMixLevels == MixLevels::v1_17RC2) { diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/SoundFilePlayConfig.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/SoundFilePlayConfig.cpp index 76317542e..5f8786282 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/SoundFilePlayConfig.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/SoundFilePlayConfig.cpp @@ -10,8 +10,8 @@ #include "stdafx.h" -#include "Mixer.h" #include "SoundFilePlayConfig.h" +#include "Mixer.h" OPENMPT_NAMESPACE_BEGIN diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Tables.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/Tables.cpp index d8110f4e6..be27206b5 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/Tables.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Tables.cpp @@ -51,6 +51,8 @@ static constexpr ModFormatInfo modFormatInfo[] = { { UL_("OpenMPT"), "mptm" }, { UL_("ProTracker"), "mod" }, + { UL_("ChipTracker"), "mod" }, + { UL_("TCB Tracker"), "mod" }, { UL_("Scream Tracker 3"), "s3m" }, { UL_("FastTracker 2"), "xm" }, { UL_("Impulse Tracker"), "it" }, @@ -62,18 +64,25 @@ static constexpr ModFormatInfo modFormatInfo[] = { UL_("Extreme's Tracker"), "ams" }, { UL_("Velvet Studio"), "ams" }, { UL_("CDFM / Composer 670"), "c67" }, + { UL_("Chuck Biscuits / Black Artist"), "cba" }, { UL_("DigiBooster Pro"), "dbm" }, { UL_("DigiBooster"), "digi" }, { UL_("X-Tracker"), "dmf" }, - { UL_("DSMI Compact Advanced Music Format"), "dmf" }, + { UL_("DSMI Advanced Music Format (Compact)"), "dmf" }, { UL_("DSIK Format"), "dsm" }, { UL_("Dynamic Studio"), "dsm" }, { UL_("Digital Symphony"), "dsym" }, { UL_("Digital Tracker"), "dtm" }, + { UL_("EasyTrax"), "etx" }, { UL_("Farandole Composer"), "far" }, + { UL_("Future Composer"), "fc" }, + { UL_("Future Composer"), "fc13" }, + { UL_("Future Composer"), "fc14" }, { UL_("FM Tracker"), "fmt" }, { UL_("ProTracker"), "fst" }, + { UL_("Face The Music"), "ftm" }, { UL_("Imago Orpheus"), "imf" }, + { UL_("Images Music System"), "ims" }, { UL_("Ice Tracker"), "ice" }, #ifdef MPT_EXTERNAL_SAMPLES { UL_("Impulse Tracker Project"), "itp" }, @@ -92,17 +101,22 @@ static constexpr ModFormatInfo modFormatInfo[] = { UL_("Epic Megagames MASI"), "psm" }, { UL_("ProTracker"), "pt36" }, { UL_("PolyTracker"), "ptm" }, + { UL_("Puma Tracker"), "puma" }, + { UL_("Real Tracker 2"), "rtm" }, { UL_("SoundFX"), "sfx" }, { UL_("SoundFX"), "sfx2" }, + { UL_("Future Composer"), "smod" }, { UL_("SoundTracker 2.6"), "st26" }, { UL_("Soundtracker"), "stk" }, { UL_("Scream Tracker 2"), "stm" }, { UL_("Scream Tracker Music Interface Kit"), "stx" }, { UL_("Soundtracker Pro II"), "stp" }, { UL_("Symphonie"), "symmod"}, - { UL_("Graoumf Tracker"), "gtk"}, - { UL_("Graoumf Tracker 1 / 2"), "gt2"}, + { UL_("Game Music Creator"), "gmc" }, + { UL_("Graoumf Tracker"), "gtk" }, + { UL_("Graoumf Tracker 1 / 2"), "gt2" }, { UL_("UltraTracker"), "ult" }, + { UL_("UNIC Tracker"), "unic" }, { UL_("Mod's Grave"), "wow" }, { UL_("Astroidea XMF"), "xmf" }, // converted formats (no MODTYPE) @@ -690,6 +704,28 @@ const int16 CResampler::FastSincTable[256*4] = }; +// Note LUT for His Master's Noise command 7 (Mega-Arp) +const std::array HisMastersNoiseMegaArp[16] = +{ + {0, 3, 7, 12, 15, 12, 7, 3, 0, 3, 7, 12, 15, 12, 7, 3 }, + {0, 4, 7, 12, 16, 12, 7, 4, 0, 4, 7, 12, 16, 12, 7, 4 }, + {0, 3, 8, 12, 15, 12, 8, 3, 0, 3, 8, 12, 15, 12, 8, 3 }, + {0, 4, 8, 12, 16, 12, 8, 4, 0, 4, 8, 12, 16, 12, 8, 4 }, + {0, 5, 8, 12, 17, 12, 8, 5, 0, 5, 8, 12, 17, 12, 8, 5 }, + {0, 5, 9, 12, 17, 12, 9, 5, 0, 5, 9, 12, 17, 12, 9, 5 }, + {12, 0, 7, 0, 3, 0, 7, 0, 12, 0, 7, 0, 3, 0, 7, 0 }, + {12, 0, 7, 0, 4, 0, 7, 0, 12, 0, 7, 0, 4, 0, 7, 0 }, + {0, 3, 7, 3, 7, 12, 7, 12, 15, 12, 7, 12, 7, 3, 7, 3 }, + {0, 4, 7, 4, 7, 12, 7, 12, 16, 12, 7, 12, 7, 4, 7, 4 }, + {31, 27, 24, 19, 15, 12, 7, 3, 0, 3, 7, 12, 15, 19, 24, 27}, + {31, 28, 24, 19, 16, 12, 7, 4, 0, 4, 7, 12, 16, 19, 24, 28}, + {0, 12, 0, 12, 0, 12, 0, 12, 0, 12, 0, 12, 0, 12, 0, 12}, + {0, 12, 24, 12, 0, 12, 24, 12, 0, 12, 24, 12, 0, 12, 24, 12}, + {0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3, 0, 3 }, + {0, 4, 0, 4, 0, 4, 0, 4, 0, 4, 0, 4, 0, 4, 0, 4 } +}; + + ///////////////////////////////////////////////////////////////////////////////////////////// diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Tables.h b/Frameworks/OpenMPT/OpenMPT/soundlib/Tables.h index d9a8fccde..0b8c05934 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/Tables.h +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Tables.h @@ -39,6 +39,7 @@ extern const uint32 FineLinearSlideDownTable[16]; extern const uint32 LinearSlideUpTable[256]; extern const uint32 LinearSlideDownTable[256]; extern const uint16 XMPanningTable[256]; +extern const std::array HisMastersNoiseMegaArp[16]; extern const uint8 AutoVibratoIT2XM[8]; extern const uint8 AutoVibratoXM2IT[8]; diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/Tagging.h b/Frameworks/OpenMPT/OpenMPT/soundlib/Tagging.h index 81cb4f0f3..4eb2ca15a 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/Tagging.h +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/Tagging.h @@ -12,30 +12,12 @@ #include "openmpt/all/BuildSettings.hpp" -#include +#include "openmpt/soundfile_data/tags.hpp" + OPENMPT_NAMESPACE_BEGIN -struct FileTags -{ - - mpt::ustring encoder; - - mpt::ustring title; - mpt::ustring comments; - - mpt::ustring bpm; - - mpt::ustring artist; - mpt::ustring album; - mpt::ustring trackno; - mpt::ustring year; - mpt::ustring url; - - mpt::ustring genre; - -}; mpt::ustring GetSampleNameFromTags(const FileTags &tags); diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/UMXTools.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/UMXTools.cpp index 1f57de88e..df1c4dd35 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/UMXTools.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/UMXTools.cpp @@ -9,8 +9,8 @@ #include "stdafx.h" -#include "Loaders.h" #include "UMXTools.h" +#include "Loaders.h" OPENMPT_NAMESPACE_BEGIN @@ -118,7 +118,7 @@ static bool FindNameTableEntryImpl(TFile &file, const FileHeader &fileHeader, co return false; } bool result = false; - const FileReader::off_t oldpos = file.GetPosition(); + const FileReader::pos_type oldpos = file.GetPosition(); if(file.Seek(fileHeader.nameOffset)) { for(uint32 i = 0; i < fileHeader.nameCount && file.CanRead(5); i++) diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/UMXTools.h b/Frameworks/OpenMPT/OpenMPT/soundlib/UMXTools.h index 65e247840..ae92eebe9 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/UMXTools.h +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/UMXTools.h @@ -11,6 +11,7 @@ #pragma once #include "openmpt/all/BuildSettings.hpp" +#include "Sndfile.h" OPENMPT_NAMESPACE_BEGIN diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/UpgradeModule.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/UpgradeModule.cpp index af0000f8c..9c9ef4036 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/UpgradeModule.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/UpgradeModule.cpp @@ -431,9 +431,11 @@ void CSoundFile::UpgradeModule() // OpenMPT 1.18 fixed the depth of random pan in compatible mode. // OpenMPT 1.26 fixes it in normal mode too. if(!compatModeIT || m_dwLastSavedWithVersion < MPT_V("1.18.00.00")) - { ins->nPanSwing = static_cast((ins->nPanSwing + 3) / 4u); - } + + // Before OpenMPT 1.26 (r6129), it was possible to trigger MIDI notes using channel plugins if the instrument had a valid MIDI channel. + if(!ins->nMixPlug && ins->HasValidMIDIChannel() && m_dwLastSavedWithVersion >= MPT_V("1.17.00.00")) + m_playBehaviour.set(kMIDINotesFromChannelPlugin); } } @@ -596,6 +598,11 @@ void CSoundFile::UpgradeModule() { kITPitchPanSeparation, MPT_V("1.30.00.53") }, { kITResetFilterOnPortaSmpChange, MPT_V("1.30.08.02") }, { kITInitialNoteMemory, MPT_V("1.31.00.25") }, + { kITNoSustainOnPortamento, MPT_V("1.32.00.13") }, + { kITEmptyNoteMapSlotIgnoreCell, MPT_V("1.32.00.13") }, + { kITOffsetWithInstrNumber, MPT_V("1.32.00.15") }, + { kITDoublePortamentoSlides, MPT_V("1.32.00.27") }, + { kITCarryAfterNoteOff, MPT_V("1.32.00.40") }, }; for(const auto &b : behaviours) @@ -619,6 +626,8 @@ void CSoundFile::UpgradeModule() { kFT2NoteDelayWithoutInstr, MPT_V("1.28.00.44") }, { kITFT2DontResetNoteOffOnPorta, MPT_V("1.29.00.34") }, { kFT2PortaResetDirection, MPT_V("1.30.00.40") }, + { kFT2AutoVibratoAbortSweep, MPT_V("1.32.00.29") }, + { kFT2OffsetMemoryRequiresNote, MPT_V("1.32.00.43") }, }; for(const auto &b : behaviours) @@ -737,6 +746,7 @@ void CSoundFile::UpgradeModule() } } +#ifndef NO_PLUGINS if(m_dwLastSavedWithVersion >= MPT_V("1.27.00.42") && m_dwLastSavedWithVersion < MPT_V("1.30.00.46") && hasAnyPlugins) { // The Flanger DMO plugin is almost identical to the Chorus... but only almost. @@ -785,6 +795,31 @@ void CSoundFile::UpgradeModule() } } } + + if(m_dwLastSavedWithVersion >= MPT_V("1.17") && m_dwLastSavedWithVersion < MPT_V("1.32.00.30") && hasAnyPlugins) + { + for(const auto &plugin : m_MixPlugins) + { + if(plugin.Info.dwPluginId1 == PLUGMAGIC('V', 's', 't', 'P')) + { + m_playBehaviour.set(kLegacyPPQpos); + break; + } + } + } + + if(m_dwLastSavedWithVersion < MPT_V("1.32.00.38") && hasAnyPlugins) + { + for(const auto &plugin : m_MixPlugins) + { + if(plugin.Info.dwPluginId1 == PLUGMAGIC('V', 's', 't', 'P')) + { + m_playBehaviour.set(kLegacyPluginNNABehaviour); + break; + } + } + } +#endif } OPENMPT_NAMESPACE_END diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/WAVTools.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/WAVTools.cpp index 2147820c3..0631f4c72 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/WAVTools.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/WAVTools.cpp @@ -9,8 +9,8 @@ #include "stdafx.h" -#include "Loaders.h" #include "WAVTools.h" +#include "Loaders.h" #include "Tagging.h" #include "../common/version.h" #ifndef MODPLUG_NO_FILESAVE @@ -163,7 +163,7 @@ uint16 WAVReader::GetFileCodePage(FileReader::ChunkList &chunks) if(iSFT.ReadMagic("OpenMPT")) { std::string versionString; - iSFT.ReadString(versionString, iSFT.BytesLeft()); + iSFT.ReadString(versionString, mpt::saturate_cast(iSFT.BytesLeft())); versionString = mpt::trim(versionString); Version version = Version::Parse(mpt::ToUnicode(mpt::Charset::ISO8859_1, versionString)); if(version && version < MPT_V("1.28.00.02")) @@ -195,7 +195,7 @@ void WAVReader::ApplySampleSettings(ModSample &sample, mpt::Charset sampleCharse if(textChunk.IsValid()) { std::string sampleNameEncoded; - textChunk.ReadString(sampleNameEncoded, textChunk.GetLength()); + textChunk.ReadString(sampleNameEncoded, mpt::saturate_cast(textChunk.GetLength())); sampleName = mpt::ToCharset(sampleCharset, mpt::ToUnicode(codePage, mpt::Charset::Windows1252, sampleNameEncoded)); } if(isDLS) @@ -282,7 +282,7 @@ void WAVReader::ApplySampleSettings(ModSample &sample, mpt::Charset sampleCharse // internal metadata gets converted to Unicode, we must adjust this to // also specify encoding. xtraChunk.ReadString(sampleName, MAX_SAMPLENAME); - xtraChunk.ReadString(sample.filename, xtraChunk.BytesLeft()); + xtraChunk.ReadString(sample.filename, mpt::saturate_cast(xtraChunk.BytesLeft())); } } } @@ -333,183 +333,6 @@ void WAVSampleLoop::ConvertToWAV(SmpLength start, SmpLength end, bool bidi) #ifndef MODPLUG_NO_FILESAVE -/////////////////////////////////////////////////////////// -// WAV Writing - - -// Output to stream: Initialize with std::ostream*. -WAVWriter::WAVWriter(mpt::IO::OFileBase &stream) - : s(stream) -{ - // Skip file header for now - mpt::IO::SeekRelative(s, sizeof(RIFFHeader)); -} - - -WAVWriter::~WAVWriter() -{ - MPT_ASSERT(finalized); -} - - -// Finalize the file by closing the last open chunk and updating the file header. Returns total size of file. -mpt::IO::Offset WAVWriter::Finalize() -{ - FinalizeChunk(); - - mpt::IO::Offset totalSize = mpt::IO::TellWrite(s); - - RIFFHeader fileHeader; - Clear(fileHeader); - fileHeader.magic = RIFFHeader::idRIFF; - fileHeader.length = mpt::saturate_cast(totalSize - 8); - fileHeader.type = RIFFHeader::idWAVE; - - mpt::IO::SeekBegin(s); - mpt::IO::Write(s, fileHeader); - mpt::IO::SeekAbsolute(s, totalSize); - finalized = true; - - return totalSize; -} - - -// Write a new chunk header to the file. -void WAVWriter::StartChunk(RIFFChunk::ChunkIdentifiers id) -{ - FinalizeChunk(); - - chunkHeaderPos = mpt::IO::TellWrite(s); - chunkHeader.id = id; - mpt::IO::SeekRelative(s, sizeof(chunkHeader)); -} - - -// End current chunk by updating the chunk header and writing a padding byte if necessary. -void WAVWriter::FinalizeChunk() -{ - if(chunkHeaderPos != 0) - { - const mpt::IO::Offset position = mpt::IO::TellWrite(s); - - const mpt::IO::Offset chunkSize = position - (chunkHeaderPos + sizeof(RIFFChunk)); - chunkHeader.length = mpt::saturate_cast(chunkSize); - - mpt::IO::SeekAbsolute(s, chunkHeaderPos); - mpt::IO::Write(s, chunkHeader); - - mpt::IO::SeekAbsolute(s, position); - - if((chunkSize % 2u) != 0) - { - // Write padding - uint8 padding = 0; - mpt::IO::Write(s, padding); - } - - chunkHeaderPos = 0; - } -} - - -// Write the WAV format to the file. -void WAVWriter::WriteFormat(uint32 sampleRate, uint16 bitDepth, uint16 numChannels, WAVFormatChunk::SampleFormats encoding) -{ - StartChunk(RIFFChunk::idfmt_); - WAVFormatChunk wavFormat; - Clear(wavFormat); - - bool extensible = (numChannels > 2); - - wavFormat.format = static_cast(extensible ? WAVFormatChunk::fmtExtensible : encoding); - wavFormat.numChannels = numChannels; - wavFormat.sampleRate = sampleRate; - wavFormat.blockAlign = static_cast((bitDepth * numChannels + 7u) / 8u); - wavFormat.byteRate = wavFormat.sampleRate * wavFormat.blockAlign; - wavFormat.bitsPerSample = bitDepth; - - mpt::IO::Write(s, wavFormat); - - if(extensible) - { - WAVFormatChunkExtension extFormat; - Clear(extFormat); - extFormat.size = sizeof(WAVFormatChunkExtension) - sizeof(uint16); - extFormat.validBitsPerSample = bitDepth; - switch(numChannels) - { - case 1: - extFormat.channelMask = 0x0004; // FRONT_CENTER - break; - case 2: - extFormat.channelMask = 0x0003; // FRONT_LEFT | FRONT_RIGHT - break; - case 3: - extFormat.channelMask = 0x0103; // FRONT_LEFT | FRONT_RIGHT | BACK_CENTER - break; - case 4: - extFormat.channelMask = 0x0033; // FRONT_LEFT | FRONT_RIGHT | BACK_LEFT | BACK_RIGHT - break; - default: - extFormat.channelMask = 0; - break; - } - extFormat.subFormat = mpt::UUID(static_cast(encoding), 0x0000, 0x0010, 0x800000AA00389B71ull); - mpt::IO::Write(s, extFormat); - } -} - - -// Write text tags to the file. -void WAVWriter::WriteMetatags(const FileTags &tags) -{ - StartChunk(RIFFChunk::idCSET); - mpt::IO::Write(s, mpt::as_le(uint16(65001))); // code page (UTF-8) - mpt::IO::Write(s, mpt::as_le(uint16(0))); // country code (unset) - mpt::IO::Write(s, mpt::as_le(uint16(0))); // language (unset) - mpt::IO::Write(s, mpt::as_le(uint16(0))); // dialect (unset) - - StartChunk(RIFFChunk::idLIST); - const char info[] = { 'I', 'N', 'F', 'O' }; - mpt::IO::Write(s, info); - - WriteTag(RIFFChunk::idINAM, tags.title); - WriteTag(RIFFChunk::idIART, tags.artist); - WriteTag(RIFFChunk::idIPRD, tags.album); - WriteTag(RIFFChunk::idICRD, tags.year); - WriteTag(RIFFChunk::idICMT, tags.comments); - WriteTag(RIFFChunk::idIGNR, tags.genre); - WriteTag(RIFFChunk::idTURL, tags.url); - WriteTag(RIFFChunk::idISFT, tags.encoder); - //WriteTag(RIFFChunk:: , tags.bpm); - WriteTag(RIFFChunk::idTRCK, tags.trackno); -} - - -// Write a single tag into a open idLIST chunk -void WAVWriter::WriteTag(RIFFChunk::ChunkIdentifiers id, const mpt::ustring &utext) -{ - std::string text = mpt::ToCharset(mpt::Charset::UTF8, utext); - text = text.substr(0, uint32_max - 1u); - if(!text.empty()) - { - const uint32 length = mpt::saturate_cast(text.length() + 1); - - RIFFChunk chunk; - Clear(chunk); - chunk.id = static_cast(id); - chunk.length = length; - mpt::IO::Write(s, chunk); - mpt::IO::Write(s, mpt::byte_cast(mpt::span(text.c_str(), length))); - - if((length % 2u) != 0) - { - uint8 padding = 0; - mpt::IO::Write(s, padding); - } - } -} - WAVSampleWriter::WAVSampleWriter(mpt::IO::OFileBase &stream) : WAVWriter(stream) @@ -525,12 +348,17 @@ WAVSampleWriter::~WAVSampleWriter() // Write a sample loop information chunk to the file. -void WAVSampleWriter::WriteLoopInformation(const ModSample &sample) +void WAVSampleWriter::WriteLoopInformation(const ModSample &sample, SmpLength rangeStart, SmpLength rangeEnd) { if(!sample.uFlags[CHN_LOOP | CHN_SUSTAINLOOP] && !ModCommand::IsNote(sample.rootNote)) { return; } + if(rangeEnd <= rangeStart) + { + rangeStart = 0; + rangeEnd = sample.nLength; + } StartChunk(RIFFChunk::idsmpl); WAVSampleInfoChunk info; @@ -544,16 +372,17 @@ void WAVSampleWriter::WriteLoopInformation(const ModSample &sample) info.ConvertToWAV(sampleRate, sample.rootNote); // Set up loops - WAVSampleLoop loops[2]; - Clear(loops); - if(sample.uFlags[CHN_SUSTAINLOOP]) + std::array loops{{}}; + const bool writeSustainLoop = sample.uFlags[CHN_SUSTAINLOOP] && sample.nSustainStart < rangeEnd && sample.nSustainEnd >= rangeStart; + const bool writeNormalLoop = sample.uFlags[CHN_LOOP] && sample.nLoopStart < rangeEnd && sample.nLoopEnd >= rangeStart; + if(writeSustainLoop) { - loops[info.numLoops++].ConvertToWAV(sample.nSustainStart, sample.nSustainEnd, sample.uFlags[CHN_PINGPONGSUSTAIN]); + loops[info.numLoops++].ConvertToWAV(std::max(sample.nSustainStart, rangeStart) - rangeStart, std::clamp(sample.nSustainEnd, rangeStart, rangeEnd) - rangeStart, sample.uFlags[CHN_PINGPONGSUSTAIN]); } - if(sample.uFlags[CHN_LOOP]) + if(writeNormalLoop) { - loops[info.numLoops++].ConvertToWAV(sample.nLoopStart, sample.nLoopEnd, sample.uFlags[CHN_PINGPONGLOOP]); - } else if(sample.uFlags[CHN_SUSTAINLOOP]) + loops[info.numLoops++].ConvertToWAV(std::max(sample.nLoopStart, rangeStart) - rangeStart, std::clamp(sample.nLoopEnd, rangeStart, rangeEnd) - rangeStart, sample.uFlags[CHN_PINGPONGLOOP]); + } else if(writeSustainLoop) { // Since there are no "loop types" to distinguish between sustain and normal loops, OpenMPT assumes // that the first loop is a sustain loop if there are two loops. If we only want a sustain loop, @@ -570,12 +399,18 @@ void WAVSampleWriter::WriteLoopInformation(const ModSample &sample) // Write a sample's cue points to the file. -void WAVSampleWriter::WriteCueInformation(const ModSample &sample) +void WAVSampleWriter::WriteCueInformation(const ModSample &sample, SmpLength rangeStart, SmpLength rangeEnd) { + if(rangeEnd <= rangeStart) + { + rangeStart = 0; + rangeEnd = sample.nLength; + } + uint32 numMarkers = 0; for(const auto cue : sample.cues) { - if(cue < sample.nLength) + if(mpt::is_in_range(cue, rangeStart, rangeEnd)) numMarkers++; } @@ -584,10 +419,9 @@ void WAVSampleWriter::WriteCueInformation(const ModSample &sample) uint32 i = 0; for(const auto cue : sample.cues) { - if(cue < sample.nLength) + if(mpt::is_in_range(cue, rangeStart, rangeEnd)) { - WAVCuePoint cuePoint; - cuePoint.ConvertToWAV(i++, cue); + WAVCuePoint cuePoint = ConvertToWAVCuePoint(i++, cue - rangeStart); mpt::IO::Write(s, cuePoint); } } @@ -621,6 +455,7 @@ void WAVSampleWriter::WriteExtraInformation(const ModSample &sample, MODTYPE mod } } + #endif // MODPLUG_NO_FILESAVE diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/WAVTools.h b/Frameworks/OpenMPT/OpenMPT/soundlib/WAVTools.h index a0744647e..bb795d297 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/WAVTools.h +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/WAVTools.h @@ -12,131 +12,24 @@ #include "openmpt/all/BuildSettings.hpp" -#include "mpt/uuid/uuid.hpp" - -#include "../common/FileReader.h" -#include "Loaders.h" - #ifndef MODPLUG_NO_FILESAVE -#include "mpt/io/io.hpp" #include "mpt/io/io_virtual_wrapper.hpp" #endif +#include "openmpt/soundfile_data/wav.hpp" +#ifndef MODPLUG_NO_FILESAVE +#include "openmpt/soundfile_write/wav_write.hpp" +#endif + +#include "../common/FileReader.h" + +#include "Loaders.h" + + OPENMPT_NAMESPACE_BEGIN struct FileTags; -// RIFF header -struct RIFFHeader -{ - // 32-Bit chunk identifiers - enum RIFFMagic - { - idRIFF = MagicLE("RIFF"), // magic for WAV files - idLIST = MagicLE("LIST"), // magic for samples in DLS banks - idWAVE = MagicLE("WAVE"), // type for WAV files - idwave = MagicLE("wave"), // type for samples in DLS banks - }; - - uint32le magic; // RIFF (in WAV files) or LIST (in DLS banks) - uint32le length; // Size of the file, not including magic and length - uint32le type; // WAVE (in WAV files) or wave (in DLS banks) -}; - -MPT_BINARY_STRUCT(RIFFHeader, 12) - - -// General RIFF Chunk header -struct RIFFChunk -{ - // 32-Bit chunk identifiers - enum ChunkIdentifiers - { - idfmt_ = MagicLE("fmt "), // Sample format information - iddata = MagicLE("data"), // Sample data - idpcm_ = MagicLE("pcm "), // IMA ADPCM samples - idfact = MagicLE("fact"), // Compressed samples - idsmpl = MagicLE("smpl"), // Sampler and loop information - idinst = MagicLE("inst"), // Instrument information - idLIST = MagicLE("LIST"), // List of chunks - idxtra = MagicLE("xtra"), // OpenMPT extra infomration - idcue_ = MagicLE("cue "), // Cue points - idwsmp = MagicLE("wsmp"), // DLS bank samples - idCSET = MagicLE("CSET"), // Character Set - id____ = 0x00000000, // Found when loading buggy MPT samples - - // Identifiers in "LIST" chunk - idINAM = MagicLE("INAM"), // title - idISFT = MagicLE("ISFT"), // software - idICOP = MagicLE("ICOP"), // copyright - idIART = MagicLE("IART"), // artist - idIPRD = MagicLE("IPRD"), // product (album) - idICMT = MagicLE("ICMT"), // comment - idIENG = MagicLE("IENG"), // engineer - idISBJ = MagicLE("ISBJ"), // subject - idIGNR = MagicLE("IGNR"), // genre - idICRD = MagicLE("ICRD"), // date created - - idYEAR = MagicLE("YEAR"), // year - idTRCK = MagicLE("TRCK"), // track number - idTURL = MagicLE("TURL"), // url - }; - - uint32le id; // See ChunkIdentifiers - uint32le length; // Chunk size without header - - size_t GetLength() const - { - return length; - } - - ChunkIdentifiers GetID() const - { - return static_cast(id.get()); - } -}; - -MPT_BINARY_STRUCT(RIFFChunk, 8) - - -// Format Chunk -struct WAVFormatChunk -{ - // Sample formats - enum SampleFormats - { - fmtPCM = 1, - fmtFloat = 3, - fmtALaw = 6, - fmtULaw = 7, - fmtIMA_ADPCM = 17, - fmtMP3 = 85, - fmtExtensible = 0xFFFE, - }; - - uint16le format; // Sample format, see SampleFormats - uint16le numChannels; // Number of audio channels - uint32le sampleRate; // Sample rate in Hz - uint32le byteRate; // Bytes per second (should be freqHz * blockAlign) - uint16le blockAlign; // Size of a sample, in bytes (do not trust this value, it's incorrect in some files) - uint16le bitsPerSample; // Bits per sample -}; - -MPT_BINARY_STRUCT(WAVFormatChunk, 16) - - -// Extension of the WAVFormatChunk structure, used if format == formatExtensible -struct WAVFormatChunkExtension -{ - uint16le size; - uint16le validBitsPerSample; - uint32le channelMask; - mpt::GUIDms subFormat; -}; - -MPT_BINARY_STRUCT(WAVFormatChunkExtension, 24) - - // Sample information chunk struct WAVSampleInfoChunk { @@ -262,29 +155,18 @@ struct WAVExtraChunk MPT_BINARY_STRUCT(WAVExtraChunk, 16) -// Sample cue point structure for the "cue " chunk -struct WAVCuePoint +// Set up sample information +inline WAVCuePoint ConvertToWAVCuePoint(uint32 id_, SmpLength offset_) { - uint32le id; // Unique identification value - uint32le position; // Play order position - uint32le riffChunkID; // RIFF ID of corresponding data chunk - uint32le chunkStart; // Byte Offset of Data Chunk - uint32le blockStart; // Byte Offset to sample of First Channel - uint32le offset; // Byte Offset to sample byte of First Channel - - // Set up sample information - void ConvertToWAV(uint32 id_, SmpLength offset_) - { - id = id_; - position = offset_; - riffChunkID = static_cast(RIFFChunk::iddata); - chunkStart = 0; // we use no Wave List Chunk (wavl) as we have only one data block, so this should be 0. - blockStart = 0; // ditto - offset = offset_; - } -}; - -MPT_BINARY_STRUCT(WAVCuePoint, 24) + WAVCuePoint result{}; + result.id = id_; + result.position = offset_; + result.riffChunkID = static_cast(RIFFChunk::iddata); + result.chunkStart = 0; // we use no Wave List Chunk (wavl) as we have only one data block, so this should be 0. + result.blockStart = 0; // ditto + result.offset = offset_; + return result; +} class WAVReader @@ -294,7 +176,7 @@ protected: FileReader sampleData, smplChunk, instChunk, xtraChunk, wsmpChunk, cueChunk; FileReader::ChunkList infoChunk; - FileReader::off_t sampleLength; + FileReader::pos_type sampleLength; WAVFormatChunk formatInfo; uint16 subFormat; uint16 codePage; @@ -331,41 +213,9 @@ public: void ApplySampleSettings(ModSample &sample, mpt::Charset sampleCharset, mpt::charbuf &sampleName); }; + #ifndef MODPLUG_NO_FILESAVE -class WAVWriter -{ -protected: - // Output stream - mpt::IO::OFileBase &s; - - // Currently written chunk - mpt::IO::Offset chunkHeaderPos = 0; - RIFFChunk chunkHeader; - bool finalized = false; - -public: - // Output to stream - WAVWriter(mpt::IO::OFileBase &stream); - ~WAVWriter(); - - // Finalize the file by closing the last open chunk and updating the file header. Returns total size of file. - mpt::IO::Offset Finalize(); - // Begin writing a new chunk to the file. - void StartChunk(RIFFChunk::ChunkIdentifiers id); - - // Write the WAV format to the file. - void WriteFormat(uint32 sampleRate, uint16 bitDepth, uint16 numChannels, WAVFormatChunk::SampleFormats encoding); - // Write text tags to the file. - void WriteMetatags(const FileTags &tags); - -protected: - // End current chunk by updating the chunk header and writing a padding byte if necessary. - void FinalizeChunk(); - - // Write a single tag into a open idLIST chunk - void WriteTag(RIFFChunk::ChunkIdentifiers id, const mpt::ustring &utext); -}; class WAVSampleWriter : public WAVWriter @@ -377,9 +227,9 @@ public: public: // Write a sample loop information chunk to the file. - void WriteLoopInformation(const ModSample &sample); + void WriteLoopInformation(const ModSample &sample, SmpLength rangeStart = 0, SmpLength rangeEnd = 0); // Write a sample's cue points to the file. - void WriteCueInformation(const ModSample &sample); + void WriteCueInformation(const ModSample &sample, SmpLength rangeStart = 0, SmpLength rangeEnd = 0); // Write MPT's sample information chunk to the file. void WriteExtraInformation(const ModSample &sample, MODTYPE modType, const char *sampleName = nullptr); }; diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/XMTools.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/XMTools.cpp index 66395a83b..67b52cf5f 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/XMTools.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/XMTools.cpp @@ -9,10 +9,11 @@ #include "stdafx.h" -#include "Loaders.h" #include "XMTools.h" +#include "Loaders.h" #include "Sndfile.h" #include "../common/version.h" + #include @@ -147,7 +148,7 @@ void XMInstrument::ConvertEnvelopeToMPT(InstrumentEnvelope &mptEnv, uint8 numPoi // value. Try to compensate by adding the missing high byte." // Note: MPT 1.07's XI instrument saver omitted the high byte of envelope nodes. // This might be the source for some broken envelopes in IT and XM files. - mptEnv[i].tick |= mptEnv[i - 1].tick & 0xFF00; + mptEnv[i].tick |= static_cast(mptEnv[i - 1].tick & 0xFF00u); if(mptEnv[i].tick < mptEnv[i - 1].tick) mptEnv[i].tick += 0x100; } diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/XMTools.h b/Frameworks/OpenMPT/OpenMPT/soundlib/XMTools.h index 50c42add2..c1bb12900 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/XMTools.h +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/XMTools.h @@ -17,6 +17,10 @@ OPENMPT_NAMESPACE_BEGIN +struct InstrumentEnvelope; +struct ModInstrument; +struct ModSample; +class SampleIO; // XM File Header struct XMFileHeader diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/load_j2b.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/load_j2b.cpp index 7e43e52f1..22a2222b2 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/load_j2b.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/load_j2b.cpp @@ -6,7 +6,7 @@ * It seems like no other game used the AM(FF) format. * RIFF AM is the newer version of the format, generally following the RIFF "standard" closely. * Authors: Johannes Schultz (OpenMPT port, reverse engineering + loader implementation of the instrument format) - * kode54 (foo_dumb - this is almost a complete port of his code, thanks) + * Largely based on the J2B loader of kode54's DUMB fork * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. */ @@ -43,17 +43,18 @@ struct J2BFileHeader { // Magic Bytes // 32-Bit J2B header identifiers - enum : uint32 { + enum : uint32 + { magicDEADBEAF = 0xAFBEADDEu, magicDEADBABE = 0xBEBAADDEu }; - char signature[4]; // MUSE - uint32le deadbeaf; // 0xDEADBEAF (AM) or 0xDEADBABE (AMFF) - uint32le fileLength; // complete filesize - uint32le crc32; // checksum of the compressed data block - uint32le packedLength; // length of the compressed data block - uint32le unpackedLength; // length of the decompressed module + char signature[4]; // MUSE + uint32le deadbeaf; // 0xDEADBEAF (AM) or 0xDEADBABE (AMFF) + uint32le fileLength; // complete filesize + uint32le crc32; // checksum of the compressed data block + uint32le packedLength; // length of the compressed data block + uint32le unpackedLength; // length of the decompressed module }; MPT_BINARY_STRUCT(J2BFileHeader, 24) @@ -66,21 +67,21 @@ struct AMFFRiffChunk // 32-Bit chunk identifiers enum ChunkIdentifiers { - idRIFF = MagicLE("RIFF"), - idAMFF = MagicLE("AMFF"), - idAM__ = MagicLE("AM "), - idMAIN = MagicLE("MAIN"), - idINIT = MagicLE("INIT"), - idORDR = MagicLE("ORDR"), - idPATT = MagicLE("PATT"), - idINST = MagicLE("INST"), - idSAMP = MagicLE("SAMP"), - idAI__ = MagicLE("AI "), - idAS__ = MagicLE("AS "), + idRIFF = MagicLE("RIFF"), + idAMFF = MagicLE("AMFF"), + idAM__ = MagicLE("AM "), + idMAIN = MagicLE("MAIN"), + idINIT = MagicLE("INIT"), + idORDR = MagicLE("ORDR"), + idPATT = MagicLE("PATT"), + idINST = MagicLE("INST"), + idSAMP = MagicLE("SAMP"), + idAI__ = MagicLE("AI "), + idAS__ = MagicLE("AS "), }; - uint32le id; // See ChunkIdentifiers - uint32le length; // Chunk size without header + uint32le id; // See ChunkIdentifiers + uint32le length; // Chunk size without header size_t GetLength() const { @@ -110,8 +111,8 @@ struct AMFFMainChunk uint8le channels; uint8le speed; uint8le tempo; - uint16le minPeriod; // 16x Amiga periods, but we should ignore them - otherwise some high notes in Medivo.j2b won't sound correct. - uint16le maxPeriod; // Ditto + uint16le minPeriod; // 16x Amiga periods, but we should ignore them - otherwise some high notes in Medivo.j2b won't sound correct. + uint16le maxPeriod; // Ditto uint8le globalvolume; }; @@ -132,14 +133,14 @@ struct AMFFEnvelope struct EnvPoint { uint16le tick; - uint8le value; // 0...64 + uint8le value; // 0...64 }; - uint8le envFlags; // high nibble = pan env flags, low nibble = vol env flags (both nibbles work the same way) - uint8le envNumPoints; // high nibble = pan env length, low nibble = vol env length - uint8le envSustainPoints; // you guessed it... high nibble = pan env sustain point, low nibble = vol env sustain point - uint8le envLoopStarts; // I guess you know the pattern now. - uint8le envLoopEnds; // same here. + uint8le envFlags; // high nibble = pan env flags, low nibble = vol env flags (both nibbles work the same way) + uint8le envNumPoints; // high nibble = pan env length, low nibble = vol env length + uint8le envSustainPoints; // you guessed it... high nibble = pan env sustain point, low nibble = vol env sustain point + uint8le envLoopStarts; // I guess you know the pattern now. + uint8le envLoopEnds; // same here. EnvPoint volEnv[10]; EnvPoint panEnv[10]; @@ -191,8 +192,8 @@ MPT_BINARY_STRUCT(AMFFEnvelope, 65) // AMFF instrument header (old format) struct AMFFInstrumentHeader { - uint8le unknown; // 0x00 - uint8le index; // actual instrument number + uint8le unknown; // 0x00 + uint8le index; // actual instrument number char name[28]; uint8le numSamples; uint8le sampleMap[120]; @@ -211,7 +212,7 @@ struct AMFFInstrumentHeader static_assert(mpt::array_size::size <= mpt::array_size::size); for(size_t i = 0; i < std::size(sampleMap); i++) { - mptIns.Keyboard[i] = sampleMap[i] + baseSample + 1; + mptIns.Keyboard[i] = static_cast(sampleMap[i] + baseSample + 1); } mptIns.nFadeOut = fadeout << 5; @@ -229,16 +230,16 @@ struct AMFFSampleHeader // Sample flags (also used for RIFF AM) enum SampleFlags { - smp16Bit = 0x04, - smpLoop = 0x08, - smpPingPong = 0x10, - smpPanning = 0x20, - smpExists = 0x80, + smp16Bit = 0x04, + smpLoop = 0x08, + smpPingPong = 0x10, + smpPanning = 0x20, + smpExists = 0x80, // some flags are still missing... what is e.g. 0x8000? }; - uint32le id; // "SAMP" - uint32le chunkSize; // header + sample size + uint32le id; // "SAMP" + uint32le chunkSize; // header + sample size char name[28]; uint8le pan; uint8le volume; @@ -363,16 +364,15 @@ struct AMEnvelope } }; -MPT_BINARY_STRUCT(AMEnvelope::EnvPoint, 4) MPT_BINARY_STRUCT(AMEnvelope, 48) // AM instrument header (new format) struct AMInstrumentHeader { - uint32le headSize; // Header size (i.e. the size of this struct) - uint8le unknown1; // 0x00 - uint8le index; // Actual instrument number + uint32le headSize; // Header size (i.e. the size of this struct) + uint8le unknown1; // 0x00 + uint8le index; // Actual instrument number char name[32]; uint8le sampleMap[128]; uint8le vibratoType; @@ -393,7 +393,7 @@ struct AMInstrumentHeader static_assert(mpt::array_size::size <= mpt::array_size::size); for(uint8 i = 0; i < std::size(sampleMap); i++) { - mptIns.Keyboard[i] = sampleMap[i] + baseSample + 1; + mptIns.Keyboard[i] = static_cast(sampleMap[i] + baseSample + 1); } mptIns.nFadeOut = volEnv.fadeout << 5; @@ -415,12 +415,12 @@ MPT_BINARY_STRUCT(AMInstrumentHeader, 326) // AM sample header (new format) struct AMSampleHeader { - uint32le headSize; // Header size (i.e. the size of this struct), apparently not including headSize. + uint32le headSize; // Header size (i.e. the size of this struct), apparently not including headSize. char name[32]; uint16le pan; uint16le volume; uint16le flags; - uint16le unknown; // 0x0000 / 0x0080? + uint16le unknown; // 0x0000 / 0x0080? uint32le length; uint32le loopStart; uint32le loopEnd; @@ -430,8 +430,8 @@ struct AMSampleHeader void ConvertToMPT(AMInstrumentHeader &instrHeader, ModSample &mptSmp) const { mptSmp.Initialize(); - mptSmp.nPan = std::min(pan.get(), uint16(32767)) * 256 / 32767; - mptSmp.nVolume = std::min(volume.get(), uint16(32767)) * 256 / 32767; + mptSmp.nPan = static_cast(std::min(pan.get(), uint16(32767)) * 256 / 32767); + mptSmp.nVolume = static_cast(std::min(volume.get(), uint16(32767)) * 256 / 32767); mptSmp.nGlobalVol = 64; mptSmp.nLength = length; mptSmp.nLoopStart = loopStart; @@ -490,30 +490,24 @@ static bool ConvertAMPattern(FileReader chunk, PATTERNINDEX pat, bool isAM, CSou enum { - rowDone = 0, // Advance to next row - channelMask = 0x1F, // Mask for retrieving channel information - volFlag = 0x20, // Volume effect present - noteFlag = 0x40, // Note + instr present - effectFlag = 0x80, // Effect information present - dataFlag = 0xE0, // Channel data present + rowDone = 0x00, // Advance to next row + channelMask = 0x1F, // Mask for retrieving channel information + volFlag = 0x20, // Volume effect present + noteFlag = 0x40, // Note + instr present + effectFlag = 0x80, // Effect information present + dataFlag = 0xE0, // Channel data present }; if(chunk.NoBytesLeft()) - { return false; - } - ROWINDEX numRows = Clamp(static_cast(chunk.ReadUint8()) + 1, ROWINDEX(1), MAX_PATTERN_ROWS); + const ROWINDEX numRows = std::min(static_cast(chunk.ReadUint8() + 1), MAX_PATTERN_ROWS); if(!sndFile.Patterns.Insert(pat, numRows)) return false; - const CHANNELINDEX channels = sndFile.GetNumChannels(); - if(channels == 0) - return false; - + const CHANNELINDEX lastChannel = sndFile.GetNumChannels() - 1; ROWINDEX row = 0; - while(row < numRows && chunk.CanRead(1)) { const uint8 flags = chunk.ReadUint8(); @@ -523,98 +517,95 @@ static bool ConvertAMPattern(FileReader chunk, PATTERNINDEX pat, bool isAM, CSou row++; continue; } + if(!(flags & dataFlag)) + continue; - ModCommand &m = *sndFile.Patterns[pat].GetpModCommand(row, std::min(static_cast(flags & channelMask), static_cast(channels - 1))); + ModCommand &m = *sndFile.Patterns[pat].GetpModCommand(row, std::min(static_cast(flags & channelMask), lastChannel)); - if(flags & dataFlag) + if(flags & effectFlag) // effect { - if(flags & effectFlag) // effect + const auto [param, command] = chunk.ReadArray(); + if(command < std::size(amEffTrans)) + { + m.SetEffectCommand(amEffTrans[command], param); + } else { - m.param = chunk.ReadUint8(); - uint8 command = chunk.ReadUint8(); - - if(command < std::size(amEffTrans)) - { - // command translation - m.command = amEffTrans[command]; - } else - { #ifdef J2B_LOG - MPT_LOG_GLOBAL(LogDebug, "J2B", MPT_UFORMAT("J2B: Unknown command: 0x{}, param 0x{}")(mpt::ufmt::HEX0<2>(command), mpt::ufmt::HEX0<2>(m.param))); + MPT_LOG_GLOBAL(LogDebug, "J2B", MPT_UFORMAT("J2B: Unknown command: 0x{}, param 0x{}")(mpt::ufmt::HEX0<2>(command), mpt::ufmt::HEX0<2>(m.param))); #endif + m.command = CMD_NONE; + } + + // Handling special commands + switch(m.command) + { + case CMD_ARPEGGIO: + if(m.param == 0) m.command = CMD_NONE; + break; + case CMD_VOLUME: + if(m.volcmd == VOLCMD_NONE && !(flags & volFlag)) + { + m.SetVolumeCommand(VOLCMD_VOLUME, Clamp(m.param, uint8(0), uint8(64))); m.command = CMD_NONE; } - - // Handling special commands - switch(m.command) + break; + case CMD_TONEPORTAVOL: + case CMD_VIBRATOVOL: + case CMD_VOLUMESLIDE: + case CMD_GLOBALVOLSLIDE: + case CMD_PANNINGSLIDE: + if(m.param & 0xF0) + m.param &= 0xF0; + break; + case CMD_PANNING8: + if(m.param <= 0x80) + m.param = mpt::saturate_cast(m.param * 2); + else if(m.param == 0xA4) + m.SetEffectCommand(CMD_S3MCMDEX, 0x91u); + break; + case CMD_PATTERNBREAK: + m.param = static_cast(((m.param >> 4) * 10u) + (m.param & 0x0Fu)); + break; + case CMD_MODCMDEX: + m.ExtendedMODtoS3MEffect(); + break; + case CMD_TEMPO: + if(m.param <= 0x1F) + m.command = CMD_SPEED; + break; + case CMD_XFINEPORTAUPDOWN: + switch(m.param & 0xF0) { - case CMD_ARPEGGIO: - if(m.param == 0) m.command = CMD_NONE; + case 0x10: + m.command = CMD_PORTAMENTOUP; break; - case CMD_VOLUME: - if(m.volcmd == VOLCMD_NONE) - { - m.volcmd = VOLCMD_VOLUME; - m.vol = Clamp(m.param, uint8(0), uint8(64)); - m.command = CMD_NONE; - m.param = 0; - } - break; - case CMD_TONEPORTAVOL: - case CMD_VIBRATOVOL: - case CMD_VOLUMESLIDE: - case CMD_GLOBALVOLSLIDE: - case CMD_PANNINGSLIDE: - if (m.param & 0xF0) m.param &= 0xF0; - break; - case CMD_PANNING8: - if(m.param <= 0x80) m.param = mpt::saturate_cast(m.param * 2); - else if(m.param == 0xA4) {m.command = CMD_S3MCMDEX; m.param = 0x91;} - break; - case CMD_PATTERNBREAK: - m.param = ((m.param >> 4) * 10) + (m.param & 0x0F); - break; - case CMD_MODCMDEX: - m.ExtendedMODtoS3MEffect(); - break; - case CMD_TEMPO: - if(m.param <= 0x1F) m.command = CMD_SPEED; - break; - case CMD_XFINEPORTAUPDOWN: - switch(m.param & 0xF0) - { - case 0x10: - m.command = CMD_PORTAMENTOUP; - break; - case 0x20: - m.command = CMD_PORTAMENTODOWN; - break; - } - m.param = (m.param & 0x0F) | 0xE0; - break; - default: + case 0x20: + m.command = CMD_PORTAMENTODOWN; break; } + m.param = (m.param & 0x0F) | 0xE0; + break; + default: + break; } + } - if (flags & noteFlag) // note + ins - { - const auto [instr, note] = chunk.ReadArray(); - m.instr = instr; - m.note = note; - if(m.note == 0x80) m.note = NOTE_KEYOFF; - else if(m.note > 0x80) m.note = NOTE_FADE; // I guess the support for IT "note fade" notes was not intended in mod2j2b, but hey, it works! :-D - } + if(flags & noteFlag) // note + ins + { + const auto [instr, note] = chunk.ReadArray(); + m.instr = instr; + m.note = note; + if(m.note == 0x80) + m.note = NOTE_KEYOFF; + else if(m.note > 0x80) + m.note = NOTE_FADE; // I guess the support for IT "note fade" notes was not intended in mod2j2b, but hey, it works! :-D + } - if (flags & volFlag) // volume - { - m.volcmd = VOLCMD_VOLUME; - m.vol = chunk.ReadUint8(); - if(isAM) - { - m.vol = m.vol * 64 / 127; - } - } + if(flags & volFlag) // volume + { + m.SetVolumeCommand(VOLCMD_VOLUME, chunk.ReadUint8()); + if(isAM) + m.vol = static_cast(m.vol * 64u / 127u); } } @@ -727,7 +718,7 @@ bool CSoundFile::ReadAM(FileReader &file, ModLoadingFlags loadFlags) FileReader chunkMain(chunks.GetChunk(mainChunkID)); AMFFMainChunk mainChunk; - if(!chunkMain.IsValid() + if(!chunkMain.IsValid() || !chunkMain.ReadStruct(mainChunk) || mainChunk.channels < 1 || !chunkMain.CanRead(mainChunk.channels)) @@ -738,17 +729,16 @@ bool CSoundFile::ReadAM(FileReader &file, ModLoadingFlags loadFlags) return true; } - InitializeGlobals(MOD_TYPE_J2B); + InitializeGlobals(MOD_TYPE_J2B, std::min(static_cast(mainChunk.channels), static_cast(MAX_BASECHANNELS))); m_SongFlags = SONG_ITOLDEFFECTS | SONG_ITCOMPATGXX; m_SongFlags.set(SONG_LINEARSLIDES, !(mainChunk.flags & AMFFMainChunk::amigaSlides)); - m_nChannels = std::min(static_cast(mainChunk.channels), static_cast(MAX_BASECHANNELS)); - m_nDefaultSpeed = mainChunk.speed; - m_nDefaultTempo.Set(mainChunk.tempo); + Order().SetDefaultSpeed(mainChunk.speed); + Order().SetDefaultTempoInt(mainChunk.tempo); m_nDefaultGlobalVolume = mainChunk.globalvolume * 2; m_modFormat.formatName = isAM ? UL_("Galaxy Sound System (new version)") : UL_("Galaxy Sound System (old version)"); - m_modFormat.type = U_("j2b"); + m_modFormat.type = UL_("j2b"); m_modFormat.charset = mpt::Charset::CP437; m_songName = mpt::String::ReadBuf(mpt::String::maybeNullTerminated, mainChunk.songname); @@ -756,23 +746,21 @@ bool CSoundFile::ReadAM(FileReader &file, ModLoadingFlags loadFlags) // It seems like there's no way to differentiate between // Muted and Surround channels (they're all 0xA0) - might // be a limitation in mod2j2b. - for(CHANNELINDEX nChn = 0; nChn < m_nChannels; nChn++) + for(auto &chn : ChnSettings) { - ChnSettings[nChn].Reset(); - uint8 pan = chunkMain.ReadUint8(); if(isAM) { if(pan > 128) - ChnSettings[nChn].dwFlags = CHN_MUTE; + chn.dwFlags = CHN_MUTE; else - ChnSettings[nChn].nPan = pan * 2; + chn.nPan = pan * 2; } else { if(pan >= 128) - ChnSettings[nChn].dwFlags = CHN_MUTE; + chn.dwFlags = CHN_MUTE; else - ChnSettings[nChn].nPan = static_cast(std::min(pan * 4, 256)); + chn.nPan = static_cast(std::min(pan * 4, 256)); } } @@ -1041,7 +1029,7 @@ bool CSoundFile::ReadJ2B(FileReader &file, ModLoadingFlags loadFlags) Bytef buffer[mpt::IO::BUFFERSIZE_TINY]; uint32 readSize = std::min(static_cast(sizeof(buffer)), remainRead); file.ReadRaw(mpt::span(buffer, readSize)); - crc = crc32(crc, buffer, readSize); + crc = static_cast(crc32(crc, buffer, readSize)); strm.avail_in = readSize; strm.next_in = buffer; diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/mod_specifications.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/mod_specifications.cpp index bfff37238..3db71d96f 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/mod_specifications.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/mod_specifications.cpp @@ -29,7 +29,7 @@ constexpr CModSpecifications mptm_ = MOD_TYPE_MPT, // Internal MODTYPE value "mptm", // File extension NOTE_MIN, // Minimum note index - NOTE_MAX, // Maximum note index + NOTE_MIN + 119, // Maximum note index 4000, // Pattern max. 4000, // Order max. MAX_SEQUENCES, // Sequences max @@ -67,13 +67,10 @@ constexpr CModSpecifications mptm_ = true, // Has artist name true, // Has default resampling true, // Fixed point tempo - " JFEGHLKRXODB?CQATI?SMNVW?UY?P?Z\\:#+*??????????", // Supported Effects + " JFEGHLKRXODB?CQATI?SMNVW?UY?P?Z\\:#+*?????????????????????", // Supported Effects " vpcdabuh??gfe?o", // Supported Volume Column commands }; - - - constexpr CModSpecifications mod_ = { MOD_TYPE_MOD, // Internal MODTYPE value @@ -100,7 +97,7 @@ constexpr CModSpecifications mod_ = 31, // SamplesMax 0, // instrumentMax MixLevels::Compatible, // defaultMixLevels - SONG_PT_MODE | SONG_AMIGALIMITS | SONG_ISAMIGA, // Supported song flags + SONG_PT_MODE | SONG_AMIGALIMITS | SONG_ISAMIGA | SONG_FORMAT_NO_VOLCOL, // Supported song flags 0, // Max MIDI mapping directives 0, // No instrument envelopes false, // No notecut. @@ -117,11 +114,10 @@ constexpr CModSpecifications mod_ = false, // Doesn't have artist name false, // Doesn't have default resampling false, // Integer tempo - " 0123456789ABCD?FF?E???????????????????????????", // Supported Effects + " 0123456789ABCD?FF?E??????????????????????????????????????", // Supported Effects " ???????????????", // Supported Volume Column commands }; - constexpr CModSpecifications xm_ = { MOD_TYPE_XM, // Internal MODTYPE value @@ -165,7 +161,7 @@ constexpr CModSpecifications xm_ = false, // Doesn't have artist name false, // Doesn't have default resampling false, // Integer tempo - " 0123456789ABCDRFFTE???GHK??XPL??????W?????????", // Supported Effects + " 0123456789ABCDRFFTE???GHK??XPL??????W????????????????????", // Supported Effects " vpcdabuhlrg????", // Supported Volume Column commands }; @@ -180,7 +176,7 @@ constexpr CModSpecifications xmEx_ = 255, // Order max. 1, // Only one order list 1, // Channel min - 127, // Channel max + 128, // Channel max 32, // Min tempo 1000, // Max tempo 1, // Min Speed @@ -213,7 +209,7 @@ constexpr CModSpecifications xmEx_ = true, // Has artist name false, // Doesn't have default resampling false, // Integer tempo - " 0123456789ABCDRFFTE???GHK?YXPLZ\\?#??W?????????", // Supported Effects + " 0123456789ABCDRFFTE???GHK?YXPLZ\\?#??W????????????????????", // Supported Effects " vpcdabuhlrg????", // Supported Volume Column commands }; @@ -260,7 +256,7 @@ constexpr CModSpecifications s3m_ = false, // Doesn't have artist name false, // Doesn't have default resampling false, // Integer tempo - " JFEGHLKRXODB?CQATI?SMNVW?U?????????? ?????????", // Supported Effects + " JFEGHLKRXODB?CQATI?SMNVW?U?????????? ????????????????????", // Supported Effects " vp?????????????", // Supported Volume Column commands }; @@ -308,7 +304,7 @@ constexpr CModSpecifications s3mEx_ = false, // Doesn't have artist name false, // Doesn't have default resampling false, // Integer tempo - " JFEGHLKRXODB?CQATI?SMNVW?UY?P?Z????? ?????????", // Supported Effects + " JFEGHLKRXODB?CQATI?SMNVW?UY?P?Z????? ????????????????????", // Supported Effects " vp?????????????", // Supported Volume Column commands }; @@ -355,7 +351,7 @@ constexpr CModSpecifications it_ = false, // Doesn't have artist name false, // Doesn't have default resampling false, // Integer tempo - " JFEGHLKRXODB?CQATI?SMNVW?UY?P?Z????? ?????????", // Supported Effects + " JFEGHLKRXODB?CQATI?SMNVW?UY?P?Z????? ????????????????????", // Supported Effects " vpcdab?h??gfe??", // Supported Volume Column commands }; @@ -402,7 +398,7 @@ constexpr CModSpecifications itEx_ = true, // Has artist name false, // Doesn't have default resampling false, // Integer tempo - " JFEGHLKRXODB?CQATI?SMNVW?UY?P?Z\\?#?? ?????????", // Supported Effects + " JFEGHLKRXODB?CQATI?SMNVW?UY?P?Z\\?#?? ????????????????????", // Supported Effects " vpcdab?h??gfe??", // Supported Volume Column commands }; @@ -470,29 +466,40 @@ bool CModSpecifications::HasNote(ModCommand::NOTE note) const bool CModSpecifications::HasVolCommand(ModCommand::VOLCMD volcmd) const { if(volcmd >= MAX_VOLCMDS) return false; - if(volcommands[volcmd] == '?') return false; - return true; + return volcommands[volcmd] != '?'; } bool CModSpecifications::HasCommand(ModCommand::COMMAND cmd) const { if(cmd >= MAX_EFFECTS) return false; - if(commands[cmd] == '?') return false; - return true; + return commands[cmd] != '?'; } char CModSpecifications::GetVolEffectLetter(ModCommand::VOLCMD volcmd) const { - if(volcmd >= MAX_VOLCMDS) return '?'; + if(volcmd >= MAX_VOLCMDS) + return '?'; return volcommands[volcmd]; } +char CModSpecifications::GetGenericVolEffectLetter(ModCommand::VOLCMD volcmd) +{ + // Note: Remove this function if volume effect letter display is ever going to differ between formats, and update users to GetVolEffectLetter instead. + static constexpr char VolCommands[] = " vpcdabuhlrgfe:o"; + static_assert(std::size(VolCommands) == MAX_VOLCMDS + 1); + if(volcmd >= MAX_VOLCMDS) + return '?'; + return VolCommands[volcmd]; +} + + char CModSpecifications::GetEffectLetter(ModCommand::COMMAND cmd) const { - if(cmd >= MAX_EFFECTS) return '?'; + if(cmd >= MAX_EFFECTS) + return '?'; return commands[cmd]; } diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/mod_specifications.h b/Frameworks/OpenMPT/OpenMPT/soundlib/mod_specifications.h index 692236047..7cd65b42a 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/mod_specifications.h +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/mod_specifications.h @@ -33,7 +33,8 @@ struct CModSpecifications bool HasCommand(ModCommand::COMMAND cmd) const; // Return corresponding effect letter for this format char GetEffectLetter(ModCommand::COMMAND cmd) const; - char GetVolEffectLetter(ModCommand::VOLCMD cmd) const; + char GetVolEffectLetter(ModCommand::VOLCMD volcmd) const; + static char GetGenericVolEffectLetter(ModCommand::VOLCMD volcmd); // NOTE: If changing order, update all initializations in .cpp file. MODTYPE internalType; // Internal MODTYPE value diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/modcommand.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/modcommand.cpp index 002d0ed10..9f9b47dd2 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/modcommand.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/modcommand.cpp @@ -9,8 +9,9 @@ #include "stdafx.h" -#include "Sndfile.h" +#include "modcommand.h" #include "mod_specifications.h" +#include "Sndfile.h" #include "Tables.h" @@ -30,7 +31,10 @@ static constexpr EffectType effectTypes[] = EffectType::Normal, EffectType::Normal, EffectType::Normal, EffectType::Pitch, EffectType::Pitch, EffectType::Normal, EffectType::Pitch, EffectType::Pitch, EffectType::Pitch, EffectType::Pitch, EffectType::Normal, EffectType::Normal, - EffectType::Normal, EffectType::Normal, EffectType::Volume + EffectType::Normal, EffectType::Normal, EffectType::Volume, EffectType::Normal, + EffectType::Normal, EffectType::Volume, EffectType::Pitch, EffectType::Pitch, + EffectType::Pitch, EffectType::Pitch, EffectType::Pitch, EffectType::Pitch, + EffectType::Volume, EffectType::Volume, }; static_assert(std::size(effectTypes) == MAX_EFFECTS); @@ -247,7 +251,7 @@ void ModCommand::Convert(MODTYPE fromType, MODTYPE toType, const CSoundFile &snd ///////////////////////////////////////// // Convert MOD / XM to S3M / IT / MPTM - if(oldTypeIsMOD_XM && newTypeIsS3M_IT_MPT) + if(!oldTypeIsS3M_IT_MPT && newTypeIsS3M_IT_MPT) { switch(command) { @@ -314,7 +318,7 @@ void ModCommand::Convert(MODTYPE fromType, MODTYPE toType, const CSoundFile &snd command = CMD_S3MCMDEX; if(param == 0) instr = 0; - param = 0xD0 | (param & 0x0F); + param = 0xD0 | std::min(param, PARAM(0x0F)); } break; @@ -338,12 +342,12 @@ void ModCommand::Convert(MODTYPE fromType, MODTYPE toType, const CSoundFile &snd default: break; } - } // End if(oldTypeIsMOD_XM && newTypeIsS3M_IT_MPT) + } // End if(!oldTypeIsS3M_IT_MPT && newTypeIsS3M_IT_MPT) ///////////////////////////////////////// // Convert S3M / IT / MPTM to MOD / XM - else if(oldTypeIsS3M_IT_MPT && newTypeIsMOD_XM) + else if(!oldTypeIsMOD_XM && newTypeIsMOD_XM) { if(note == NOTE_NOTECUT) { @@ -474,7 +478,7 @@ void ModCommand::Convert(MODTYPE fromType, MODTYPE toType, const CSoundFile &snd default: break; } - } // End if(oldTypeIsS3M_IT_MPT && newTypeIsMOD_XM) + } // End if(!oldTypeIsMOD_XM && newTypeIsMOD_XM) /////////////////////// @@ -910,11 +914,29 @@ void ModCommand::Convert(MODTYPE fromType, MODTYPE toType, const CSoundFile &snd { param = vol << 3; } + } else if(volcmd == VOLCMD_PLAYCONTROL && (vol == 2 || vol == 3) && command == CMD_NONE + && !newSpecs.HasVolCommand(VOLCMD_PLAYCONTROL) + && (newSpecs.HasCommand(CMD_S3MCMDEX) || newSpecs.HasCommand(CMD_XFINEPORTAUPDOWN))) + { + volcmd = VOLCMD_NONE; + param = vol - 2 + 0x9E; + if(newSpecs.HasCommand(CMD_S3MCMDEX)) + command = CMD_S3MCMDEX; + else + command = CMD_XFINEPORTAUPDOWN; } + // Offset effect memory is only updated when the command is placed next to a note. + if(oldTypeIsXM && command == CMD_OFFSET && !IsNote()) + command = CMD_NONE; + if((command == CMD_REVERSEOFFSET || command == CMD_OFFSETPERCENTAGE) && !newSpecs.HasCommand(command)) { command = CMD_OFFSET; + } else if(command == CMD_HMN_MEGA_ARP && !newSpecs.HasCommand(CMD_HMN_MEGA_ARP)) + { + command = CMD_ARPEGGIO; + param = (HisMastersNoiseMegaArp[param & 0x0F][1] << 4) | HisMastersNoiseMegaArp[param & 0x0F][2]; } if(!newSpecs.HasNote(note)) @@ -941,6 +963,12 @@ bool ModCommand::IsAnyPitchSlide() const case CMD_NOTESLIDEDOWN: case CMD_NOTESLIDEUPRETRIG: case CMD_NOTESLIDEDOWNRETRIG: + case CMD_AUTO_PORTAUP: + case CMD_AUTO_PORTADOWN: + case CMD_AUTO_PORTAUP_FINE: + case CMD_AUTO_PORTADOWN_FINE: + case CMD_AUTO_PORTAMENTO_FC: + case CMD_TONEPORTA_DURATION: return true; case CMD_MODCMDEX: case CMD_XFINEPORTAUPDOWN: @@ -980,6 +1008,13 @@ bool ModCommand::IsContinousCommand(const CSoundFile &sndFile) const case CMD_NOTESLIDEDOWN: case CMD_NOTESLIDEUPRETRIG: case CMD_NOTESLIDEDOWNRETRIG: + case CMD_HMN_MEGA_ARP: + case CMD_AUTO_VOLUMESLIDE: + case CMD_AUTO_PORTAUP: + case CMD_AUTO_PORTADOWN: + case CMD_AUTO_PORTAUP_FINE: + case CMD_AUTO_PORTADOWN_FINE: + case CMD_AUTO_PORTAMENTO_FC: return true; case CMD_PORTAMENTOUP: case CMD_PORTAMENTODOWN: @@ -1045,6 +1080,7 @@ bool ModCommand::IsSlideUpDownCommand() const case CMD_GLOBALVOLSLIDE: case CMD_CHANNELVOLSLIDE: case CMD_PANNINGSLIDE: + case CMD_AUTO_VOLUMESLIDE: return true; default: return false; @@ -1120,6 +1156,7 @@ bool ModCommand::CommandHasTwoNibbles(COMMAND command) case CMD_NOTESLIDEDOWN: case CMD_NOTESLIDEUPRETRIG: case CMD_NOTESLIDEDOWNRETRIG: + case CMD_AUTO_VOLUMESLIDE: return true; default: return false; @@ -1138,6 +1175,7 @@ size_t ModCommand::GetEffectWeight(COMMAND cmd) CMD_DUMMY, CMD_XPARAM, CMD_SETENVPOSITION, + CMD_MED_SYNTH_JUMP, CMD_KEYOFF, CMD_TREMOLO, CMD_FINEVIBRATO, @@ -1158,8 +1196,14 @@ size_t ModCommand::GetEffectWeight(COMMAND cmd) CMD_NOTESLIDEDOWNRETRIG, CMD_NOTESLIDEDOWN, CMD_PORTAMENTOUP, + CMD_AUTO_PORTAMENTO_FC, + CMD_AUTO_PORTAUP_FINE, + CMD_AUTO_PORTAUP, CMD_PORTAMENTODOWN, + CMD_AUTO_PORTADOWN_FINE, + CMD_AUTO_PORTADOWN, CMD_VOLUMESLIDE, + CMD_AUTO_VOLUMESLIDE, CMD_VIBRATOVOL, CMD_VOLUME, CMD_VOLUME8, @@ -1169,10 +1213,14 @@ size_t ModCommand::GetEffectWeight(COMMAND cmd) CMD_OFFSET, CMD_TREMOR, CMD_RETRIG, + CMD_HMN_MEGA_ARP, CMD_ARPEGGIO, + CMD_TONEPORTA_DURATION, CMD_TONEPORTAMENTO, CMD_TONEPORTAVOL, CMD_DBMECHO, + CMD_VOLUMEDOWN_DURATION, + CMD_VOLUMEDOWN_ETX, CMD_CHANNELVOLSLIDE, CMD_CHANNELVOLUME, CMD_GLOBALVOLSLIDE, @@ -1271,22 +1319,26 @@ std::pair ModCommand::ConvertToVolCommand(const return {VOLCMD_FINEVOLDOWN, static_cast(param & 0x0F)}; break; case CMD_S3MCMDEX: - switch(param >> 4) + switch(param & 0xF0) { - case 0x08: + case 0x80: return {VOLCMD_PANNING, static_cast(((param & 0x0F) << 2) + 2)}; + case 0x90: + if(param >= 0x9E && force) + return {VOLCMD_PLAYCONTROL, static_cast(param - 0x9E + 2)}; + break; default: break; } break; case CMD_MODCMDEX: - switch(param >> 4) + switch(param & 0xF0) { - case 0x08: + case 0x80: return {VOLCMD_PANNING, static_cast(((param & 0x0F) << 2) + 2)}; - case 0x0A: + case 0xA0: return {VOLCMD_FINEVOLUP, static_cast(param & 0x0F)}; - case 0x0B: + case 0xB0: return {VOLCMD_FINEVOLDOWN, static_cast(param & 0x0F)}; default: break; @@ -1376,6 +1428,8 @@ std::pair ModCommand::FillInTwoCommands(Effect case CMD_OFFSETPERCENTAGE: case CMD_DIGIREVERSESAMPLE: case CMD_VOLUME8: + case CMD_HMN_MEGA_ARP: + case CMD_MED_SYNTH_JUMP: effect2 = CMD_NONE; break; default: diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/modcommand.h b/Frameworks/OpenMPT/OpenMPT/soundlib/modcommand.h index ca360ee35..3c0edfe22 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/modcommand.h +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/modcommand.h @@ -23,7 +23,7 @@ enum : uint8 // ModCommand::NOTE { NOTE_NONE = 0, // Empty note cell NOTE_MIN = 1, // Minimum note value - NOTE_MAX = 120, // Maximum note value + NOTE_MAX = 128, // Maximum note value NOTE_MIDDLEC = (5 * 12 + NOTE_MIN), NOTE_KEYOFF = 0xFF, // === (Note Off, releases envelope / fades samples, stops plugin note) NOTE_NOTECUT = 0xFE, // ^^^ (Cuts sample / stops all plugin notes) @@ -108,6 +108,17 @@ enum EffectCommand : uint8 CMD_OFFSETPERCENTAGE = 44, // PLM Percentage Offset CMD_DIGIREVERSESAMPLE = 45, // DIGI reverse sample CMD_VOLUME8 = 46, // 8-bit volume + CMD_HMN_MEGA_ARP = 47, // His Master's Noise "mega-arp" + CMD_MED_SYNTH_JUMP = 48, // MED synth jump / MIDI panning + CMD_AUTO_VOLUMESLIDE = 49, + CMD_AUTO_PORTAUP = 50, + CMD_AUTO_PORTADOWN = 51, + CMD_AUTO_PORTAUP_FINE = 52, + CMD_AUTO_PORTADOWN_FINE = 53, + CMD_AUTO_PORTAMENTO_FC = 54, // Future Composer + CMD_TONEPORTA_DURATION = 55, // Parameter = how many rows the slide should last + CMD_VOLUMEDOWN_DURATION = 56, // Parameter = how many rows the slide should last + CMD_VOLUMEDOWN_ETX = 57, // EasyTrax fade-out (parameter = speed, independent of song tempo) MAX_EFFECTS }; @@ -191,7 +202,7 @@ public: bool IsNoteOrEmpty() const { return note == NOTE_NONE || IsNote(); } static bool IsNoteOrEmpty(NOTE note) { return note == NOTE_NONE || IsNote(note); } // Returns true if any of the commands in this cell trigger a tone portamento. - bool IsPortamento() const { return command == CMD_TONEPORTAMENTO || command == CMD_TONEPORTAVOL || volcmd == VOLCMD_TONEPORTAMENTO; } + bool IsTonePortamento() const { return command == CMD_TONEPORTAMENTO || command == CMD_TONEPORTAVOL || command == CMD_TONEPORTA_DURATION || volcmd == VOLCMD_TONEPORTAMENTO; } // Returns true if any commands in this cell trigger any sort of pitch slide / portamento. bool IsAnyPitchSlide() const; // Returns true if the cell contains a sliding or otherwise continuous effect command. @@ -205,7 +216,7 @@ public: // Returns true if the cell contains an effect command whose parameter is divided into two nibbles bool CommandHasTwoNibbles() const { return CommandHasTwoNibbles(command); } static bool CommandHasTwoNibbles(COMMAND command); - // Returns true if the two commands' parameters have the same + // Returns true if the command is a regular volume slide bool IsNormalVolumeSlide() const { return command == CMD_VOLUMESLIDE || command == CMD_VIBRATOVOL || command == CMD_TONEPORTAVOL; } // Returns true if the note is inside the Amiga frequency range diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/modsmp_ctrl.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/modsmp_ctrl.cpp index 31eea76ed..54eac0c91 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/modsmp_ctrl.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/modsmp_ctrl.cpp @@ -18,78 +18,6 @@ OPENMPT_NAMESPACE_BEGIN namespace ctrlSmp { -void ReplaceSample(ModSample &smp, void *pNewSample, const SmpLength newLength, CSoundFile &sndFile) -{ - void * const pOldSmp = smp.samplev(); - FlagSet setFlags, resetFlags; - - setFlags.set(CHN_16BIT, smp.uFlags[CHN_16BIT]); - resetFlags.set(CHN_16BIT, !smp.uFlags[CHN_16BIT]); - - setFlags.set(CHN_STEREO, smp.uFlags[CHN_STEREO]); - resetFlags.set(CHN_STEREO, !smp.uFlags[CHN_STEREO]); - - CriticalSection cs; - - ctrlChn::ReplaceSample(sndFile, smp, pNewSample, newLength, setFlags, resetFlags); - smp.pData.pSample = pNewSample; - smp.nLength = newLength; - ModSample::FreeSample(pOldSmp); -} - - -// Propagate loop point changes to player -bool UpdateLoopPoints(const ModSample &smp, CSoundFile &sndFile) -{ - if(!smp.HasSampleData()) - return false; - - CriticalSection cs; - - // Update channels with new loop values - for(auto &chn : sndFile.m_PlayState.Chn) if((chn.pModSample == &smp) && chn.nLength != 0) - { - bool looped = false, bidi = false; - - if(smp.nSustainStart < smp.nSustainEnd && smp.nSustainEnd <= smp.nLength && smp.uFlags[CHN_SUSTAINLOOP] && !chn.dwFlags[CHN_KEYOFF]) - { - // Sustain loop is active - chn.nLoopStart = smp.nSustainStart; - chn.nLoopEnd = smp.nSustainEnd; - chn.nLength = smp.nSustainEnd; - looped = true; - bidi = smp.uFlags[CHN_PINGPONGSUSTAIN]; - } else if(smp.nLoopStart < smp.nLoopEnd && smp.nLoopEnd <= smp.nLength && smp.uFlags[CHN_LOOP]) - { - // Normal loop is active - chn.nLoopStart = smp.nLoopStart; - chn.nLoopEnd = smp.nLoopEnd; - chn.nLength = smp.nLoopEnd; - looped = true; - bidi = smp.uFlags[CHN_PINGPONGLOOP]; - } - chn.dwFlags.set(CHN_LOOP, looped); - chn.dwFlags.set(CHN_PINGPONGLOOP, looped && bidi); - - if(chn.position.GetUInt() > chn.nLength) - { - chn.position.Set(chn.nLoopStart); - chn.dwFlags.reset(CHN_PINGPONGFLAG); - } - if(!bidi) - { - chn.dwFlags.reset(CHN_PINGPONGFLAG); - } - if(!looped) - { - chn.nLength = smp.nLength; - } - } - - return true; -} - - template static void ReverseSampleImpl(T *pStart, const SmpLength length) { @@ -368,7 +296,7 @@ bool ConvertToStereo(ModSample &smp, CSoundFile &sndFile) CriticalSection cs; smp.uFlags.set(CHN_STEREO); - ReplaceSample(smp, newSample, smp.nLength, sndFile); + smp.ReplaceWaveform(newSample, smp.nLength, sndFile); smp.PrecomputeLoops(sndFile, false); return true; diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/modsmp_ctrl.h b/Frameworks/OpenMPT/OpenMPT/soundlib/modsmp_ctrl.h index 0111b564a..120afc267 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/modsmp_ctrl.h +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/modsmp_ctrl.h @@ -23,12 +23,6 @@ struct ModChannel; namespace ctrlSmp { -// Replaces sample in 'smp' with given sample and frees the old sample. -void ReplaceSample(ModSample &smp, void *pNewSample, const SmpLength newLength, CSoundFile &sndFile); - -// Propagate loop point changes to player -bool UpdateLoopPoints(const ModSample &smp, CSoundFile &sndFile); - // Reverse sample data bool ReverseSample(ModSample &smp, SmpLength start, SmpLength end, CSoundFile &sndFile); diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/pattern.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/pattern.cpp index 96dd326ec..6204ea32d 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/pattern.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/pattern.cpp @@ -65,19 +65,21 @@ bool CPattern::RowHasJump(ROWINDEX row) const noexcept bool CPattern::SetSignature(const ROWINDEX rowsPerBeat, const ROWINDEX rowsPerMeasure) noexcept { - if(rowsPerBeat < 1 - || rowsPerBeat > GetSoundFile().GetModSpecifications().patternRowsMax - || rowsPerMeasure < rowsPerBeat - || rowsPerMeasure > GetSoundFile().GetModSpecifications().patternRowsMax) - { + if(!IsValidSignature(rowsPerBeat, rowsPerMeasure)) return false; - } m_RowsPerBeat = rowsPerBeat; m_RowsPerMeasure = rowsPerMeasure; return true; } +bool CPattern::IsValidSignature(const ROWINDEX rowsPerBeat, const ROWINDEX rowsPerMeasure) noexcept +{ + return rowsPerBeat > 0 && rowsPerBeat <= MAX_ROWS_PER_BEAT + && rowsPerBeat <= rowsPerMeasure && rowsPerMeasure <= MAX_ROWS_PER_MEASURE; +} + + // Add or remove rows from the pattern. bool CPattern::Resize(const ROWINDEX newRowCount, bool enforceFormatLimits, bool resizeAtEnd) { @@ -146,6 +148,7 @@ void CPattern::Deallocate() { m_Rows = m_RowsPerBeat = m_RowsPerMeasure = 0; m_ModCommands.clear(); + m_tempoSwing.clear(); m_PatternName.clear(); } @@ -272,9 +275,9 @@ bool CPattern::Shrink() #endif // MODPLUG_TRACKER -bool CPattern::SetName(const std::string &newName) +bool CPattern::SetName(std::string newName) { - m_PatternName = newName; + m_PatternName = std::move(newName); return true; } @@ -379,9 +382,9 @@ bool CPattern::WriteEffect(EffectWriter &settings) m->command = settings.m_command; if(isS3M) - m->vol = static_cast((m->param + 1u) / 2u); + m->vol = static_cast((m->param + 1u) / 2u); else - m->vol = static_cast((m->param + 2u) / 4u); + m->vol = static_cast((m->param + 2u) / 4u); m->param = settings.m_param; return true; diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/pattern.h b/Frameworks/OpenMPT/OpenMPT/soundlib/pattern.h index bc75ac683..cf84b9e63 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/pattern.h +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/pattern.h @@ -68,7 +68,7 @@ public: // Deallocate pattern data. void Deallocate(); - // Removes all modcommands from the pattern. + // Empties all ModCommands in the pattern. void ClearCommands() noexcept; // Returns associated soundfile. @@ -81,6 +81,7 @@ public: // Set pattern signature (rows per beat, rows per measure). Returns true on success. bool SetSignature(const ROWINDEX rowsPerBeat, const ROWINDEX rowsPerMeasure) noexcept; void RemoveSignature() noexcept { m_RowsPerBeat = m_RowsPerMeasure = 0; } + static bool IsValidSignature(const ROWINDEX rowsPerBeat, const ROWINDEX rowsPerMeasure) noexcept; bool HasTempoSwing() const noexcept { return !m_tempoSwing.empty(); } const TempoSwing& GetTempoSwing() const noexcept { return m_tempoSwing; } @@ -88,7 +89,7 @@ public: void RemoveTempoSwing() noexcept { m_tempoSwing.clear(); } // Pattern name functions - bool functions return true on success. - bool SetName(const std::string &newName); + bool SetName(std::string newName); bool SetName(const char *newName, size_t maxChars); template bool SetName(const char (&buffer)[bufferSize]) @@ -131,7 +132,7 @@ protected: protected: std::vector m_ModCommands; ROWINDEX m_Rows = 0; - ROWINDEX m_RowsPerBeat = 0; // patterns-specific time signature. if != 0, this is implicitely set. + ROWINDEX m_RowsPerBeat = 0; // patterns-specific time signature. if != 0, the time signature is used automatically. ROWINDEX m_RowsPerMeasure = 0; // ditto TempoSwing m_tempoSwing; std::string m_PatternName; @@ -152,17 +153,17 @@ class EffectWriter friend class CPattern; // Row advance mode - enum RetryMode + enum RetryMode : uint8 { - rmIgnore, // If effect can't be written, abort. - rmTryNextRow, // If effect can't be written, try next row. - rmTryPreviousRow, // If effect can't be written, try previous row. + rmIgnore, // If effect can't be written, abort. + rmTryNextRow, // If effect can't be written, try next row. + rmTryPreviousRow, // If effect can't be written, try previous row. }; public: // Constructors with effect commands - EffectWriter(EffectCommand cmd, ModCommand::PARAM param) : m_command(cmd), m_param(param), m_isVolEffect(false) { Init(); } - EffectWriter(VolumeCommand cmd, ModCommand::VOL param) : m_volcmd(cmd), m_vol(param), m_isVolEffect(true) { Init(); } + EffectWriter(EffectCommand cmd, ModCommand::PARAM param) : m_command(cmd), m_param(param), m_isVolEffect(false) { } + EffectWriter(VolumeCommand cmd, ModCommand::VOL param) : m_volcmd(cmd), m_vol(param), m_isVolEffect(true) { } // Additional constructors: // Set row in which writing should start @@ -176,9 +177,9 @@ public: EffectWriter &RetryPreviousRow() { m_retryMode = rmTryPreviousRow; return *this; } protected: - RetryMode m_retryMode; - ROWINDEX m_row; - CHANNELINDEX m_channel; + ROWINDEX m_row = 0; + CHANNELINDEX m_channel = CHANNELINDEX_INVALID; // Any channel by default + RetryMode m_retryMode = rmIgnore; union { @@ -191,19 +192,9 @@ protected: ModCommand::VOL m_vol; }; - bool m_retry : 1; - bool m_allowMultiple : 1; - bool m_isVolEffect : 1; - - // Common data initialisation - void Init() - { - m_row = 0; - m_channel = CHANNELINDEX_INVALID; // Any channel - m_retryMode = rmIgnore; // If effect couldn't be written, abort. - m_retry = true; - m_allowMultiple = false; // Stop if same type of effect is encountered - } + bool m_retry = true; + bool m_allowMultiple = false; + bool m_isVolEffect = false; }; diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/patternContainer.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/patternContainer.cpp index 3a30af78c..bd534c37c 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/patternContainer.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/patternContainer.cpp @@ -37,13 +37,6 @@ CPatternContainer &CPatternContainer::operator=(CPatternContainer &&other) noexc } -void CPatternContainer::ClearPatterns() -{ - DestroyPatterns(); - m_Patterns.assign(m_Patterns.size(), CPattern(*this)); -} - - void CPatternContainer::DestroyPatterns() { m_Patterns.clear(); @@ -179,6 +172,18 @@ PATTERNINDEX CPatternContainer::GetNumNamedPatterns() const noexcept } +PATTERNINDEX CPatternContainer::GetRemainingCapacity() const noexcept +{ + PATTERNINDEX numRemaining = m_rSndFile.GetModSpecifications().patternsMax; + const PATTERNINDEX size = std::min(Size(), numRemaining); + for(PATTERNINDEX pat = 0; pat < size; pat++) + { + if(m_Patterns[pat].IsValid()) + numRemaining--; + } + return numRemaining; +} + void WriteModPatterns(std::ostream& oStrm, const CPatternContainer& patc) { diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/patternContainer.h b/Frameworks/OpenMPT/OpenMPT/soundlib/patternContainer.h index ee6f3860d..6f75c02c6 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/patternContainer.h +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/patternContainer.h @@ -33,8 +33,6 @@ public: CPatternContainer &operator=(const CPatternContainer &other); CPatternContainer &operator=(CPatternContainer &&other) noexcept; - // Empty and initialize all patterns. - void ClearPatterns(); // Delete all patterns. void DestroyPatterns(); @@ -91,6 +89,8 @@ public: // Returns index of highest pattern with pattern named + 1. PATTERNINDEX GetNumNamedPatterns() const noexcept; + // Number of patterns that can still be added, respecting the current format's limitations + PATTERNINDEX GetRemainingCapacity() const noexcept; private: std::vector m_Patterns; diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/DigiBoosterEcho.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/DigiBoosterEcho.cpp index bbc94ced3..4a1cb6bcb 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/DigiBoosterEcho.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/DigiBoosterEcho.cpp @@ -11,8 +11,8 @@ #include "stdafx.h" #ifndef NO_PLUGINS -#include "../Sndfile.h" #include "DigiBoosterEcho.h" +#include "../Sndfile.h" OPENMPT_NAMESPACE_BEGIN @@ -117,7 +117,7 @@ PlugParamValue DigiBoosterEcho::GetParameter(PlugParamIndex index) } -void DigiBoosterEcho::SetParameter(PlugParamIndex index, PlugParamValue value) +void DigiBoosterEcho::SetParameter(PlugParamIndex index, PlugParamValue value, PlayState *, CHANNELINDEX) { if(index < kEchoNumParameters) { diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/DigiBoosterEcho.h b/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/DigiBoosterEcho.h index 199e05882..9ea3868da 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/DigiBoosterEcho.h +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/DigiBoosterEcho.h @@ -85,7 +85,7 @@ public: PlugParamIndex GetNumParameters() const override { return kEchoNumParameters; } PlugParamValue GetParameter(PlugParamIndex index) override; - void SetParameter(PlugParamIndex index, PlugParamValue value) override; + void SetParameter(PlugParamIndex index, PlugParamValue value, PlayState * = nullptr, CHANNELINDEX = CHANNELINDEX_INVALID) override; void Resume() override; void Suspend() override { m_isResumed = false; } diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/LFOPlugin.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/LFOPlugin.cpp index 0e4331bf4..f9c3be393 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/LFOPlugin.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/LFOPlugin.cpp @@ -18,6 +18,7 @@ #include "../../mptrack/plugins/LFOPluginEditor.h" #endif // MODPLUG_TRACKER #include "mpt/base/numbers.hpp" +#include "mpt/random/seed.hpp" OPENMPT_NAMESPACE_BEGIN @@ -135,7 +136,7 @@ PlugParamValue LFOPlugin::GetParameter(PlugParamIndex index) } -void LFOPlugin::SetParameter(PlugParamIndex index, PlugParamValue value) +void LFOPlugin::SetParameter(PlugParamIndex index, PlugParamValue value, PlayState *, CHANNELINDEX) { ResetSilence(); value = mpt::safe_clamp(value, 0.0f, 1.0f); @@ -195,19 +196,10 @@ void LFOPlugin::PositionChanged() } -bool LFOPlugin::MidiSend(uint32 midiCode) +bool LFOPlugin::MidiSend(mpt::const_byte_span midiData) { if(IMixPlugin *plugin = GetOutputPlugin()) - return plugin->MidiSend(midiCode); - else - return true; -} - - -bool LFOPlugin::MidiSysexSend(mpt::const_byte_span sysex) -{ - if(IMixPlugin *plugin = GetOutputPlugin()) - return plugin->MidiSysexSend(sysex); + return plugin->MidiSend(midiData); else return true; } @@ -342,7 +334,7 @@ void LFOPlugin::SetChunk(const ChunkData &chunk, bool) { FileReader file(chunk); PluginData data; - if(file.ReadStructPartial(data, file.BytesLeft()) + if(file.ReadStructPartial(data, mpt::saturate_cast(file.BytesLeft())) && !memcmp(data.magic, "LFO ", 4) && data.version == 0) { diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/LFOPlugin.h b/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/LFOPlugin.h index 3ade76bda..2adf42c58 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/LFOPlugin.h +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/LFOPlugin.h @@ -85,8 +85,7 @@ public: float RenderSilence(uint32) override { return 0.0f; } // MIDI event handling (mostly passing it through to the follow-up plugin) - bool MidiSend(uint32 midiCode) override; - bool MidiSysexSend(mpt::const_byte_span sysex) override; + bool MidiSend(mpt::const_byte_span midiData) override; void MidiCC(MIDIEvents::MidiCC nController, uint8 nParam, CHANNELINDEX trackChannel) override; void MidiPitchBend(int32 increment, int8 pwd, CHANNELINDEX trackChannel) override; void MidiTonePortamento(int32 increment, uint8 newNote, int8 pwd, CHANNELINDEX trackChannel) override; @@ -101,7 +100,7 @@ public: PlugParamIndex GetNumParameters() const override { return kLFONumParameters; } PlugParamValue GetParameter(PlugParamIndex index) override; - void SetParameter(PlugParamIndex index, PlugParamValue value) override; + void SetParameter(PlugParamIndex index, PlugParamValue value, PlayState * = nullptr, CHANNELINDEX = CHANNELINDEX_INVALID) override; void Resume() override; void Suspend() override { m_isResumed = false; } diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/OpCodes.h b/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/OpCodes.h deleted file mode 100644 index a8d303bb6..000000000 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/OpCodes.h +++ /dev/null @@ -1,103 +0,0 @@ -/* - * OpCodes.h - * --------- - * Purpose: A human-readable list of VST opcodes, for error reporting purposes. - * Notes : (currently none) - * Authors: OpenMPT Devs - * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. - */ - - -#pragma once - -#include "openmpt/all/BuildSettings.hpp" - -OPENMPT_NAMESPACE_BEGIN - -#ifdef MPT_WITH_VST -inline constexpr const char *VstOpCodes[] = -{ - "effOpen", - "effClose", - "effSetProgram", - "effGetProgram", - "effSetProgramName", - "effGetProgramName", - "effGetParamLabel", - "effGetParamDisplay", - "effGetParamName", - "effGetVu", - "effSetSampleRate", - "effSetBlockSize", - "effMainsChanged", - "effEditGetRect", - "effEditOpen", - "effEditClose", - "effEditDraw", - "effEditMouse", - "effEditKey", - "effEditIdle", - "effEditTop", - "effEditSleep", - "effIdentify", - "effGetChunk", - "effSetChunk", - "effProcessEvents", - "effCanBeAutomated", - "effString2Parameter", - "effGetNumProgramCategories", - "effGetProgramNameIndexed", - "effCopyProgram", - "effConnectInput", - "effConnectOutput", - "effGetInputProperties", - "effGetOutputProperties", - "effGetPlugCategory", - "effGetCurrentPosition", - "effGetDestinationBuffer", - "effOfflineNotify", - "effOfflinePrepare", - "effOfflineRun", - "effProcessVarIo", - "effSetSpeakerArrangement", - "effSetBlockSizeAndSampleRate", - "effSetBypass", - "effGetEffectName", - "effGetErrorText", - "effGetVendorString", - "effGetProductString", - "effGetVendorVersion", - "effVendorSpecific", - "effCanDo", - "effGetTailSize", - "effIdle", - "effGetIcon", - "effSetViewPosition", - "effGetParameterProperties", - "effKeysRequired", - "effGetVstVersion", - "effEditKeyDown", - "effEditKeyUp", - "effSetEditKnobMode", - "effGetMidiProgramName", - "effGetCurrentMidiProgram", - "effGetMidiProgramCategory", - "effHasMidiProgramsChanged", - "effGetMidiKeyName", - "effBeginSetProgram", - "effEndSetProgram", - "effGetSpeakerArrangement", - "effShellGetNextPlugin", - "effStartProcess", - "effStopProcess", - "effSetTotalSampleToProcess", - "effSetPanLaw", - "effBeginLoadBank", - "effBeginLoadProgram", - "effSetProcessPrecision", - "effGetNumMidiInputChannels", - "effGetNumMidiOutputChannels" -}; -#endif - -OPENMPT_NAMESPACE_END diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/PlugInterface.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/PlugInterface.cpp index c71d29aab..e52d026a0 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/PlugInterface.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/PlugInterface.cpp @@ -9,26 +9,30 @@ #include "stdafx.h" -#include "../Sndfile.h" #include "PlugInterface.h" -#include "PluginManager.h" #include "../../common/FileReader.h" +#include "../Sndfile.h" +#include "PluginManager.h" #ifdef MODPLUG_TRACKER -#include "../../mptrack/Moddoc.h" -#include "../../mptrack/Mainfrm.h" -#include "../../mptrack/InputHandler.h" #include "../../mptrack/AbstractVstEditor.h" #include "../../mptrack/DefaultVstEditor.h" +#include "../../mptrack/InputHandler.h" +#include "../../mptrack/Mainfrm.h" +#include "../../mptrack/Moddoc.h" +#include "../../mptrack/Reporting.h" +#include "../../mptrack/TrackerSettings.h" +#include "../../mptrack/WindowMessages.h" // LoadProgram/SaveProgram +#include "../mod_specifications.h" +#include "../../common/mptFileIO.h" #include "../../mptrack/FileDialog.h" #include "../../mptrack/VstPresets.h" #include "mpt/io_file/inputfile.hpp" #include "mpt/io_file_read/inputfile_filecursor.hpp" #include "mpt/io_file/outputfile.hpp" -#include "../../common/mptFileIO.h" #include "mpt/fs/fs.hpp" -#include "../mod_specifications.h" #endif // MODPLUG_TRACKER + #include "../../soundlib/AudioCriticalSection.h" #include "mpt/base/aligned_array.hpp" #include "mpt/io/base.hpp" @@ -235,24 +239,15 @@ void IMixPlugin::ProcessMixOps(float * MPT_RESTRICT pOutL, float * MPT_RESTRICT // -> mixop == 4 : MIX -= middle - WET * wetRatio + middle - DRY // -> mixop == 5 : MIX_L += wetRatio * (WET_L - DRY_L) + dryRatio * (DRY_R - WET_R) // MIX_R += dryRatio * (WET_L - DRY_L) + wetRatio * (DRY_R - WET_R) + // -> mixop == 6: same as normal, but forces dry ratio to 1 - MPT_ASSERT(m_pMixStruct != nullptr); + const PluginMixMode mixop = m_pMixStruct->GetMixMode(); - int mixop; - if(IsInstrument()) - { - // Force normal mix mode for instruments - mixop = 0; - } else - { - mixop = m_pMixStruct->GetMixMode(); - } - - float wetRatio = 1 - m_pMixStruct->fDryRatio; - float dryRatio = IsInstrument() ? 1 : m_pMixStruct->fDryRatio; // Always mix full dry if this is an instrument + float wetRatio = 1.f - m_pMixStruct->fDryRatio; + float dryRatio = (mixop == PluginMixMode::Instrument) ? 1.f : m_pMixStruct->fDryRatio; // Wet / Dry range expansion [0,1] -> [-1,1] - if(GetNumInputChannels() > 0 && m_pMixStruct->IsExpandedMix()) + if(m_pMixStruct->IsExpandedMix()) { wetRatio = 2.0f * wetRatio - 1.0f; dryRatio = -wetRatio; @@ -269,17 +264,17 @@ void IMixPlugin::ProcessMixOps(float * MPT_RESTRICT pOutL, float * MPT_RESTRICT { // Default mix - case 0: + case PluginMixMode::Default: + case PluginMixMode::Instrument: for(uint32 i = 0; i < numFrames; i++) { - //rewbs.wetratio - added the factors. [20040123] pOutL[i] += leftPlugOutput[i] * wetRatio + plugInputL[i] * dryRatio; pOutR[i] += rightPlugOutput[i] * wetRatio + plugInputR[i] * dryRatio; } break; // Wet subtract - case 1: + case PluginMixMode::WetSubtract: for(uint32 i = 0; i < numFrames; i++) { pOutL[i] += plugInputL[i] - leftPlugOutput[i] * wetRatio; @@ -288,7 +283,7 @@ void IMixPlugin::ProcessMixOps(float * MPT_RESTRICT pOutL, float * MPT_RESTRICT break; // Dry subtract - case 2: + case PluginMixMode::DrySubtract: for(uint32 i = 0; i < numFrames; i++) { pOutL[i] += leftPlugOutput[i] - plugInputL[i] * dryRatio; @@ -297,7 +292,7 @@ void IMixPlugin::ProcessMixOps(float * MPT_RESTRICT pOutL, float * MPT_RESTRICT break; // Mix subtract - case 3: + case PluginMixMode::MixSubtract: for(uint32 i = 0; i < numFrames; i++) { pOutL[i] -= leftPlugOutput[i] - plugInputL[i] * wetRatio; @@ -306,17 +301,17 @@ void IMixPlugin::ProcessMixOps(float * MPT_RESTRICT pOutL, float * MPT_RESTRICT break; // Middle subtract - case 4: + case PluginMixMode::MiddleSubtract: for(uint32 i = 0; i < numFrames; i++) { - float middle = (pOutL[i] + plugInputL[i] + pOutR[i] + plugInputR[i]) / 2.0f; + float middle = (pOutL[i] + plugInputL[i] + pOutR[i] + plugInputR[i]) * 0.5f; pOutL[i] -= middle - leftPlugOutput[i] * wetRatio + middle - plugInputL[i]; pOutR[i] -= middle - rightPlugOutput[i] * wetRatio + middle - plugInputR[i]; } break; // Left / Right balance - case 5: + case PluginMixMode::LRBalance: if(m_pMixStruct->IsExpandedMix()) { wetRatio /= 2.0f; @@ -332,8 +327,8 @@ void IMixPlugin::ProcessMixOps(float * MPT_RESTRICT pOutL, float * MPT_RESTRICT } // If dry mix is ticked, we add the unprocessed buffer, - // except if this is an instrument since then it has already been done: - if(m_pMixStruct->IsWetMix() && !IsInstrument()) + // except with the instrument mixop as it has already been done: + if(m_pMixStruct->IsDryMix() && mixop != PluginMixMode::Instrument) { for(uint32 i = 0; i < numFrames; i++) { @@ -383,6 +378,14 @@ float IMixPlugin::RenderSilence(uint32 numFrames) } +bool IMixPlugin::MidiSend(uint32 midiCode) +{ + std::array midiData; + memcpy(midiData.data(), &midiCode, 4); + return MidiSend(mpt::as_span(midiData.data(), std::min(static_cast(midiData.size()), MIDIEvents::GetEventLength(mpt::byte_cast(midiData[0]))))); +} + + // Get list of plugins to which output is sent. A nullptr indicates master output. size_t IMixPlugin::GetOutputPlugList(std::vector &list) { @@ -639,7 +642,7 @@ bool IMixPlugin::SaveProgram() } CString progName = m_Factory.libraryName.ToCString() + _T(" - ") + GetCurrentProgramName(); - progName = SanitizePathComponent(progName); + progName = mpt::SanitizePathComponent(progName); FileDialog dlg = SaveFileDialog() .DefaultExtension("fxb") @@ -1000,32 +1003,24 @@ bool IMidiPlugin::IsNotePlaying(uint8 note, CHANNELINDEX trackerChn) } -void IMidiPlugin::ReceiveMidi(uint32 midiCode) +void IMidiPlugin::MoveChannel(CHANNELINDEX from, CHANNELINDEX to) { - ResetSilence(); + if(from >= std::size(m_MidiCh[GetMidiChannel(from)].noteOnMap[0]) || to >= std::size(m_MidiCh[GetMidiChannel(from)].noteOnMap[0])) + return; - // I think we should only route events to plugins that are explicitely specified as output plugins of the current plugin. - // This should probably use GetOutputPlugList here if we ever get to support multiple output plugins. - PLUGINDEX receiver; - if(m_pMixStruct != nullptr && (receiver = m_pMixStruct->GetOutputPlugin()) != PLUGINDEX_INVALID) + for(auto ¬eOnMap : m_MidiCh[GetMidiChannel(from)].noteOnMap) { - IMixPlugin *plugin = m_SndFile.m_MixPlugins[receiver].pMixPlugin; - // Add all events to the plugin's queue. - plugin->MidiSend(midiCode); + noteOnMap[to] = noteOnMap[from]; + noteOnMap[from] = 0; } - -#ifdef MODPLUG_TRACKER - if(m_recordMIDIOut) - { - // Spam MIDI data to all views - ::PostMessage(CMainFrame::GetMainFrame()->GetMidiRecordWnd(), WM_MOD_MIDIMSG, midiCode, reinterpret_cast(this)); - } -#endif // MODPLUG_TRACKER } -void IMidiPlugin::ReceiveSysex(mpt::const_byte_span sysex) +void IMidiPlugin::ReceiveMidi(mpt::const_byte_span midiData) { + if(midiData.empty()) + return; + ResetSilence(); // I think we should only route events to plugins that are explicitely specified as output plugins of the current plugin. @@ -1035,8 +1030,18 @@ void IMidiPlugin::ReceiveSysex(mpt::const_byte_span sysex) { IMixPlugin *plugin = m_SndFile.m_MixPlugins[receiver].pMixPlugin; // Add all events to the plugin's queue. - plugin->MidiSysexSend(sysex); + plugin->MidiSend(midiData); } + +#ifdef MODPLUG_TRACKER + if(m_recordMIDIOut && midiData[0] != std::byte{0xF0}) + { + // Spam MIDI data to all views + uint32 midiCode = 0; + memcpy(&midiCode, midiData.data(), std::min(sizeof(midiCode), midiData.size())); + ::PostMessage(CMainFrame::GetMainFrame()->GetMidiRecordWnd(), WM_MOD_MIDIMSG, midiCode, reinterpret_cast(this)); + } +#endif // MODPLUG_TRACKER } diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/PlugInterface.h b/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/PlugInterface.h index fabdb0b73..7561f942f 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/PlugInterface.h +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/PlugInterface.h @@ -26,6 +26,7 @@ struct VSTPluginLib; struct SNDMIXPLUGIN; struct ModInstrument; struct ModChannel; +struct PlayState; class CSoundFile; class CModDoc; class CAbstractVstEditor; @@ -135,8 +136,9 @@ public: virtual void SetCurrentProgram(int32 nIndex) = 0; virtual PlugParamIndex GetNumParameters() const = 0; - virtual void SetParameter(PlugParamIndex paramindex, PlugParamValue paramvalue) = 0; + virtual PlugParamIndex GetNumVisibleParameters() const { return GetNumParameters(); } virtual PlugParamValue GetParameter(PlugParamIndex nIndex) = 0; + virtual void SetParameter(PlugParamIndex paramIndex, PlugParamValue paramValue, PlayState *playState = nullptr, CHANNELINDEX chn = CHANNELINDEX_INVALID) = 0; // Save parameters for storing them in a module file virtual void SaveAllParameters(); @@ -148,8 +150,8 @@ public: virtual float RenderSilence(uint32 numSamples); // MIDI event handling - virtual bool MidiSend(uint32 /*midiCode*/) { return true; } - virtual bool MidiSysexSend(mpt::const_byte_span /*sysex*/) { return true; } + bool MidiSend(uint32 midiCode); + virtual bool MidiSend(mpt::const_byte_span /*midiData*/) { return true; } virtual void MidiCC(MIDIEvents::MidiCC /*nController*/, uint8 /*nParam*/, CHANNELINDEX /*trackChannel*/) { } virtual void MidiPitchBendRaw(int32 /*pitchbend*/, CHANNELINDEX /*trackChannel*/) {} virtual void MidiPitchBend(int32 /*increment*/, int8 /*pwd*/, CHANNELINDEX /*trackChannel*/) { } @@ -158,9 +160,10 @@ public: virtual void MidiCommand(const ModInstrument &/*instr*/, uint16 /*note*/, uint16 /*vol*/, CHANNELINDEX /*trackChannel*/) { } virtual void HardAllNotesOff() { } virtual bool IsNotePlaying(uint8 /*note*/, CHANNELINDEX /*trackerChn*/) { return false; } + virtual void MoveChannel(CHANNELINDEX /*from*/, CHANNELINDEX /*to*/) { } // Modify parameter by given amount. Only needs to be re-implemented if plugin architecture allows this to be performed atomically. - virtual void ModifyParameter(PlugParamIndex nIndex, PlugParamValue diff); + virtual void ModifyParameter(PlugParamIndex nIndex, PlugParamValue diff, PlayState &playState, CHANNELINDEX chn); virtual void NotifySongPlaying(bool playing) { m_isSongPlaying = playing; } virtual bool IsSongPlaying() const { return m_isSongPlaying; } virtual bool IsResumed() const { return m_isResumed; } @@ -240,11 +243,11 @@ public: }; -inline void IMixPlugin::ModifyParameter(PlugParamIndex nIndex, PlugParamValue diff) +inline void IMixPlugin::ModifyParameter(PlugParamIndex nIndex, PlugParamValue diff, PlayState &playState, CHANNELINDEX chn) { PlugParamValue val = GetParameter(nIndex) + diff; Limit(val, PlugParamValue(0), PlugParamValue(1)); - SetParameter(nIndex, val); + SetParameter(nIndex, val, &playState, chn); } @@ -284,6 +287,7 @@ public: void MidiVibrato(int32 depth, int8 pwd, CHANNELINDEX trackerChn) override; void MidiCommand(const ModInstrument &instr, uint16 note, uint16 vol, CHANNELINDEX trackChannel) override; bool IsNotePlaying(uint8 note, CHANNELINDEX trackerChn) override; + void MoveChannel(CHANNELINDEX from, CHANNELINDEX to) override; // Get the MIDI channel currently associated with a given tracker channel virtual uint8 GetMidiChannel(const ModChannel &chn, CHANNELINDEX trackChannel) const; @@ -292,8 +296,7 @@ protected: uint8 GetMidiChannel(CHANNELINDEX trackChannel) const; // Plugin wants to send MIDI to OpenMPT - virtual void ReceiveMidi(uint32 midiCode); - virtual void ReceiveSysex(mpt::const_byte_span sysex); + virtual void ReceiveMidi(mpt::const_byte_span midiData); // Converts a 14-bit MIDI pitch bend position to our internal pitch bend position representation static constexpr int32 EncodePitchBendParam(int32 position) { return (position << kPitchBendShift); } diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/PluginManager.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/PluginManager.cpp index 80280a153..89fdcf6cc 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/PluginManager.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/PluginManager.cpp @@ -12,9 +12,9 @@ #ifndef NO_PLUGINS -#include "../../common/version.h" #include "PluginManager.h" #include "PlugInterface.h" +#include "../../common/version.h" #if defined(MPT_WITH_DMO) #include "mpt/uuid/guid.hpp" @@ -59,6 +59,7 @@ #ifdef MODPLUG_TRACKER #include "../../mptrack/Mptrack.h" +#include "../../mptrack/Reporting.h" #include "../../mptrack/TrackerSettings.h" #include "../../mptrack/AbstractVstEditor.h" #include "../../soundlib/AudioCriticalSection.h" @@ -136,52 +137,6 @@ mpt::ustring VSTPluginLib::GetPluginArchName(uint8 arch) } -mpt::ustring VSTPluginLib::GetPluginArchNameUser(uint8 arch) -{ - mpt::ustring result; - #if defined(MPT_WITH_WINDOWS10) - switch(arch) - { - case PluginArch_x86: - result = U_("x86 (32bit)"); - break; - case PluginArch_amd64: - result = U_("amd64 (64bit)"); - break; - case PluginArch_arm: - result = U_("arm (32bit)"); - break; - case PluginArch_arm64: - result = U_("arm64 (64bit)"); - break; - default: - result = U_(""); - break; - } - #else // !MPT_WITH_WINDOWS10 - switch(arch) - { - case PluginArch_x86: - result = U_("32-Bit"); - break; - case PluginArch_amd64: - result = U_("64-Bit"); - break; - case PluginArch_arm: - result = U_("32-Bit"); - break; - case PluginArch_arm64: - result = U_("64-Bit"); - break; - default: - result = U_(""); - break; - } - #endif // MPT_WITH_WINDOWS10 - return result; -} - - uint8 VSTPluginLib::GetDllArch(bool fromCache) const { // Built-in plugins are always native. @@ -207,7 +162,7 @@ mpt::ustring VSTPluginLib::GetDllArchName(bool fromCache) const mpt::ustring VSTPluginLib::GetDllArchNameUser(bool fromCache) const { - return GetPluginArchNameUser(GetDllArch(fromCache)); + return GetPluginArchName(GetDllArch(fromCache)); } @@ -236,17 +191,21 @@ void VSTPluginLib::WriteToCache() const { SettingsContainer &cacheFile = theApp.GetPluginCache(); - const std::string crcName = dllPath.ToUTF8(); + mpt::ustring pathStr; + if(theApp.IsPortableMode()) + pathStr = theApp.PathAbsoluteToInstallRelative(dllPath).ToUnicode(); + else + pathStr = dllPath.ToUnicode(); + + if(shellPluginID) + pathStr += U_("|") + mpt::ufmt::HEX0<8>(shellPluginID); + + // CRC is used to distinguish plugins sharing the same IDs + const std::string crcName = mpt::ToCharset(mpt::Charset::UTF8, pathStr); const mpt::crc32 crc(crcName); const mpt::ustring IDs = mpt::ufmt::HEX0<8>(static_cast(pluginId1)) + mpt::ufmt::HEX0<8>(static_cast(pluginId2)) + mpt::ufmt::HEX0<8>(crc.result()); - mpt::PathString writePath = dllPath; - if(theApp.IsPortableMode()) - { - writePath = theApp.PathAbsoluteToInstallRelative(writePath); - } - - cacheFile.Write(cacheSection, writePath.ToUnicode(), IDs); + cacheFile.Write(cacheSection, pathStr, IDs); cacheFile.Write(cacheSection, IDs + U_(".Vendor"), vendor); cacheFile.Write(cacheSection, IDs + U_(".Flags"), EncodeCacheFlags()); } @@ -318,49 +277,45 @@ CVstPluginManager::CVstPluginManager() VSTPluginLib::CreateProc createProc; const char *filename, *name; uint32 pluginId1, pluginId2; - VSTPluginLib::PluginCategory category; + PluginCategory category; bool isInstrument, isOurs; } BuiltInPlugins[] = { // DirectX Media Objects Emulation - { DMO::Chorus::Create, "{EFE6629C-81F7-4281-BD91-C9D604A95AF6}", "Chorus", kDmoMagic, 0xEFE6629C, VSTPluginLib::catDMO, false, false }, - { DMO::Compressor::Create, "{EF011F79-4000-406D-87AF-BFFB3FC39D57}", "Compressor", kDmoMagic, 0xEF011F79, VSTPluginLib::catDMO, false, false }, - { DMO::Distortion::Create, "{EF114C90-CD1D-484E-96E5-09CFAF912A21}", "Distortion", kDmoMagic, 0xEF114C90, VSTPluginLib::catDMO, false, false }, - { DMO::Echo::Create, "{EF3E932C-D40B-4F51-8CCF-3F98F1B29D5D}", "Echo", kDmoMagic, 0xEF3E932C, VSTPluginLib::catDMO, false, false }, - { DMO::Flanger::Create, "{EFCA3D92-DFD8-4672-A603-7420894BAD98}", "Flanger", kDmoMagic, 0xEFCA3D92, VSTPluginLib::catDMO, false, false }, - { DMO::Gargle::Create, "{DAFD8210-5711-4B91-9FE3-F75B7AE279BF}", "Gargle", kDmoMagic, 0xDAFD8210, VSTPluginLib::catDMO, false, false }, - { DMO::I3DL2Reverb::Create, "{EF985E71-D5C7-42D4-BA4D-2D073E2E96F4}", "I3DL2Reverb", kDmoMagic, 0xEF985E71, VSTPluginLib::catDMO, false, false }, - { DMO::ParamEq::Create, "{120CED89-3BF4-4173-A132-3CB406CF3231}", "ParamEq", kDmoMagic, 0x120CED89, VSTPluginLib::catDMO, false, false }, - { DMO::WavesReverb::Create, "{87FC0268-9A55-4360-95AA-004A1D9DE26C}", "WavesReverb", kDmoMagic, 0x87FC0268, VSTPluginLib::catDMO, false, false }, + { DMO::Chorus::Create, "{EFE6629C-81F7-4281-BD91-C9D604A95AF6}", "Chorus", kDmoMagic, 0xEFE6629C, PluginCategory::DMO, false, false }, + { DMO::Compressor::Create, "{EF011F79-4000-406D-87AF-BFFB3FC39D57}", "Compressor", kDmoMagic, 0xEF011F79, PluginCategory::DMO, false, false }, + { DMO::Distortion::Create, "{EF114C90-CD1D-484E-96E5-09CFAF912A21}", "Distortion", kDmoMagic, 0xEF114C90, PluginCategory::DMO, false, false }, + { DMO::Echo::Create, "{EF3E932C-D40B-4F51-8CCF-3F98F1B29D5D}", "Echo", kDmoMagic, 0xEF3E932C, PluginCategory::DMO, false, false }, + { DMO::Flanger::Create, "{EFCA3D92-DFD8-4672-A603-7420894BAD98}", "Flanger", kDmoMagic, 0xEFCA3D92, PluginCategory::DMO, false, false }, + { DMO::Gargle::Create, "{DAFD8210-5711-4B91-9FE3-F75B7AE279BF}", "Gargle", kDmoMagic, 0xDAFD8210, PluginCategory::DMO, false, false }, + { DMO::I3DL2Reverb::Create, "{EF985E71-D5C7-42D4-BA4D-2D073E2E96F4}", "I3DL2Reverb", kDmoMagic, 0xEF985E71, PluginCategory::DMO, false, false }, + { DMO::ParamEq::Create, "{120CED89-3BF4-4173-A132-3CB406CF3231}", "ParamEq", kDmoMagic, 0x120CED89, PluginCategory::DMO, false, false }, + { DMO::WavesReverb::Create, "{87FC0268-9A55-4360-95AA-004A1D9DE26C}", "WavesReverb", kDmoMagic, 0x87FC0268, PluginCategory::DMO, false, false }, // First (inaccurate) Flanger implementation (will be chosen based on library name, shares ID1 and ID2 with regular Flanger) - { DMO::Flanger::CreateLegacy, "{EFCA3D92-DFD8-4672-A603-7420894BAD98}", "Flanger (Legacy)", kDmoMagic, 0xEFCA3D92, VSTPluginLib::catHidden, false, false }, + { DMO::Flanger::CreateLegacy, "{EFCA3D92-DFD8-4672-A603-7420894BAD98}", "Flanger (Legacy)", kDmoMagic, 0xEFCA3D92, PluginCategory::Hidden, false, false }, // DigiBooster Pro Echo DSP - { DigiBoosterEcho::Create, "", "DigiBooster Pro Echo", MagicLE("DBM0"), MagicLE("Echo"), VSTPluginLib::catRoomFx, false, true }, + { DigiBoosterEcho::Create, "", "DigiBooster Pro Echo", MagicLE("DBM0"), MagicLE("Echo"), PluginCategory::RoomFx, false, true }, // LFO - { LFOPlugin::Create, "", "LFO", MagicLE("OMPT"), MagicLE("LFO "), VSTPluginLib::catGenerator, false, true }, + { LFOPlugin::Create, "", "LFO", MagicLE("OMPT"), MagicLE("LFO "), PluginCategory::Generator, false, true }, // SymMOD Echo - { SymMODEcho::Create, "", "SymMOD Echo", MagicLE("SymM"), MagicLE("Echo"), VSTPluginLib::catRoomFx, false, true }, + { SymMODEcho::Create, "", "SymMOD Echo", MagicLE("SymM"), MagicLE("Echo"), PluginCategory::RoomFx, false, true }, #ifdef MODPLUG_TRACKER - { MidiInOut::Create, "", "MIDI Input Output", PLUGMAGIC('V','s','t','P'), PLUGMAGIC('M','M','I','D'), VSTPluginLib::catSynth, true, true }, + { MidiInOut::Create, "", "MIDI Input Output", PLUGMAGIC('V','s','t','P'), PLUGMAGIC('M','M','I','D'), PluginCategory::Synth, true, true }, #endif // MODPLUG_TRACKER }; pluginList.reserve(std::size(BuiltInPlugins)); for(const auto &plugin : BuiltInPlugins) { - VSTPluginLib *plug = new (std::nothrow) VSTPluginLib(plugin.createProc, true, mpt::PathString::FromUTF8(plugin.filename), mpt::PathString::FromUTF8(plugin.name)); - if(plug != nullptr) - { - pluginList.push_back(plug); - plug->pluginId1 = plugin.pluginId1; - plug->pluginId2 = plugin.pluginId2; - plug->category = plugin.category; - plug->isInstrument = plugin.isInstrument; + auto &plug = pluginList.emplace_back(std::make_unique(plugin.createProc, true, mpt::PathString::FromUTF8(plugin.filename), mpt::PathString::FromUTF8(plugin.name))); + plug->pluginId1 = plugin.pluginId1; + plug->pluginId2 = plugin.pluginId2; + plug->category = plugin.category; + plug->isInstrument = plugin.isInstrument; #ifdef MODPLUG_TRACKER - if(plugin.isOurs) - plug->vendor = _T("OpenMPT Project"); + if(plugin.isOurs) + plug->vendor = _T("OpenMPT Project"); #endif // MODPLUG_TRACKER - } } #ifdef MODPLUG_TRACKER @@ -380,7 +335,6 @@ CVstPluginManager::~CVstPluginManager() plug->RemovePluginInstanceFromList(*pluginInstance); pluginInstance->Release(); } - delete plug; } #if defined(MPT_WITH_DMO) if(MustUnInitilizeCOM) @@ -394,7 +348,10 @@ CVstPluginManager::~CVstPluginManager() bool CVstPluginManager::IsValidPlugin(const VSTPluginLib *pLib) const { - return mpt::contains(pluginList, pLib); + return std::find_if(pluginList.begin(), pluginList.end(), [pLib](const std::unique_ptr &value) + { + return value.get() == pLib; + }) != pluginList.end(); } @@ -414,6 +371,7 @@ void CVstPluginManager::EnumerateDirectXDMOs() "120CED89-3BF4-4173-A132-3CB406CF3231"_uuid, // ParamEq "87FC0268-9A55-4360-95AA-004A1D9DE26C"_uuid, // WavesReverb "F447B69E-1884-4A7E-8055-346F74D6EDB3"_uuid, // Resampler DMO (not usable) + "A8122FF4-9E52-4374-B3D9-B4063E77109D"_uuid, // XnaVisualizerDmo (not usable) }; HKEY hkEnum; @@ -441,24 +399,13 @@ void CVstPluginManager::EnumerateDirectXDMOs() if(ERROR_SUCCESS == RegQueryValueEx(hksub, nullptr, 0, &datatype, (LPBYTE)name, &datasize)) { - VSTPluginLib *plug = new (std::nothrow) VSTPluginLib(DMOPlugin::Create, true, mpt::PathString::FromNative(mpt::GUIDToString(clsid)), mpt::PathString::FromNative(ParseMaybeNullTerminatedStringFromBufferWithSizeInBytes(name, datasize))); - if(plug != nullptr) - { - try - { - pluginList.push_back(plug); - plug->pluginId1 = kDmoMagic; - plug->pluginId2 = clsid.Data1; - plug->category = VSTPluginLib::catDMO; - } catch(mpt::out_of_memory e) - { - mpt::delete_out_of_memory(e); - delete plug; - } + auto &plug = pluginList.emplace_back(std::make_unique(DMOPlugin::Create, true, mpt::PathString::FromNative(mpt::GUIDToString(clsid)), mpt::PathString::FromNative(ParseMaybeNullTerminatedStringFromBufferWithSizeInBytes(name, datasize)))); + plug->pluginId1 = kDmoMagic; + plug->pluginId2 = clsid.Data1; + plug->category = PluginCategory::DMO; #ifdef DMO_LOG - MPT_LOG_GLOBAL(LogDebug, "DMO", MPT_UFORMAT("Found \"{}\" clsid={}\n")(plug->libraryName, plug->dllPath)); + MPT_LOG_GLOBAL(LogDebug, "DMO", MPT_UFORMAT("Found \"{}\" clsid={}\n")(plug->libraryName, plug->dllPath)); #endif - } } RegCloseKey(hksub); } @@ -472,74 +419,50 @@ void CVstPluginManager::EnumerateDirectXDMOs() } -// Extract instrument and category information from plugin. #ifdef MPT_WITH_VST -static void GetPluginInformation(bool maskCrashes, Vst::AEffect *effect, VSTPluginLib &library) +// Convert CVstPlugin::LoadResult into a collection of VSTPluginLibs. +static std::vector GetPluginInformation(VSTPluginLib plug, const CVstPlugin::LoadResult &loadResult) { - unsigned long exception = 0; - library.category = static_cast(CVstPlugin::DispatchSEH(maskCrashes, effect, Vst::effGetPlugCategory, 0, 0, nullptr, 0, exception)); - library.isInstrument = ((effect->flags & Vst::effFlagsIsSynth) || !effect->numInputs); + plug.pluginId1 = loadResult.magic; + plug.pluginId2 = loadResult.uniqueID; + plug.isInstrument = CVstPlugin::IsInstrument(*loadResult.effect); - if(library.isInstrument) + std::vector containedPlugins; + if(loadResult.shellPlugins.empty()) { - library.category = VSTPluginLib::catSynth; - } else if(library.category >= VSTPluginLib::numCategories) - { - library.category = VSTPluginLib::catUnknown; - } - #ifdef MODPLUG_TRACKER - std::vector s(256, 0); - CVstPlugin::DispatchSEH(maskCrashes, effect, Vst::effGetVendorString, 0, 0, s.data(), 0, exception); - library.vendor = mpt::ToCString(mpt::Charset::Locale, s.data()); -#endif // MODPLUG_TRACKER -} -#endif // MPT_WITH_VST - - -#ifdef MPT_WITH_VST -static bool TryLoadPlugin(bool maskCrashes, VSTPluginLib *plug, HINSTANCE hLib, unsigned long &exception) -{ - Vst::AEffect *pEffect = CVstPlugin::LoadPlugin(maskCrashes, *plug, hLib, CVstPlugin::BridgeMode::DetectRequiredBridgeMode); - if(!pEffect || pEffect->magic != Vst::kEffectMagic || !pEffect->dispatcher) + plug.WriteToCache(); +#endif // MODPLUG_TRACKER + containedPlugins.push_back(std::move(plug)); + } else { - return false; + for(auto &shellPlug : loadResult.shellPlugins) + { + plug.shellPluginID = shellPlug.shellPluginID; + plug.libraryName = mpt::PathString::FromLocale(shellPlug.name); +#ifdef MODPLUG_TRACKER + plug.WriteToCache(); +#endif // MODPLUG_TRACKER + containedPlugins.push_back(plug); + } } - CVstPlugin::DispatchSEH(maskCrashes, pEffect, Vst::effOpen, 0, 0, 0, 0, exception); - - plug->pluginId1 = pEffect->magic; - plug->pluginId2 = pEffect->uniqueID; - - GetPluginInformation(maskCrashes, pEffect, *plug); - -#ifdef VST_LOG - intptr_t nver = CVstPlugin::DispatchSEH(maskCrashes, pEffect, Vst::effGetVstVersion, 0,0, nullptr, 0, exception); - if (!nver) nver = pEffect->version; - MPT_LOG_GLOBAL(LogDebug, "VST", MPT_UFORMAT("{}: v{}.0, {} in, {} out, {} programs, {} params, flags=0x{} realQ={} offQ={}")( - plug->libraryName, nver, - pEffect->numInputs, pEffect->numOutputs, - mpt::ufmt::dec0<2>(pEffect->numPrograms), mpt::ufmt::dec0<2>(pEffect->numParams), - mpt::ufmt::HEX0<4>(static_cast(pEffect->flags)), pEffect->realQualities, pEffect->offQualities)); -#endif // VST_LOG - - CVstPlugin::DispatchSEH(maskCrashes, pEffect, Vst::effClose, 0, 0, 0, 0, exception); - - return true; + return containedPlugins; } -#endif // !NO_NVST +#endif // !NO_VST #ifdef MODPLUG_TRACKER // Add a plugin to the list of known plugins. -VSTPluginLib *CVstPluginManager::AddPlugin(const mpt::PathString &dllPath, bool maskCrashes, const mpt::ustring &tags, bool fromCache, bool *fileFound) +std::vector CVstPluginManager::AddPlugin(const mpt::PathString &dllPath, bool maskCrashes, bool fromCache, bool *fileFound, uint32 shellPluginID) { const mpt::PathString fileName = dllPath.GetFilenameBase(); - // Check if this is already a known plugin. + // Check if this is already a known plugin for(const auto &dupePlug : pluginList) { - if(!mpt::PathCompareNoCase(dllPath, dupePlug->dllPath)) return dupePlug; + if(shellPluginID == dupePlug->shellPluginID && !mpt::PathCompareNoCase(dllPath, dupePlug->dllPath)) + return {dupePlug.get()}; } if(fileFound != nullptr) @@ -550,24 +473,23 @@ VSTPluginLib *CVstPluginManager::AddPlugin(const mpt::PathString &dllPath, bool // Look if the plugin info is stored in the PluginCache if(fromCache) { - SettingsContainer & cacheFile = theApp.GetPluginCache(); + mpt::ustring shellStr; + if(shellPluginID) + shellStr = UL_("|") + mpt::ufmt::HEX0<8>(shellPluginID); + + SettingsContainer &cacheFile = theApp.GetPluginCache(); // First try finding the full path - mpt::ustring IDs = cacheFile.Read(cacheSection, dllPath.ToUnicode(), U_("")); + mpt::ustring IDs = cacheFile.Read(cacheSection, dllPath.ToUnicode() + shellStr, U_("")); if(IDs.length() < 16) { // If that didn't work out, find relative path mpt::PathString relPath = theApp.PathAbsoluteToInstallRelative(dllPath); - IDs = cacheFile.Read(cacheSection, relPath.ToUnicode(), U_("")); + IDs = cacheFile.Read(cacheSection, relPath.ToUnicode() + shellStr, U_("")); } if(IDs.length() >= 16) { - VSTPluginLib *plug = new (std::nothrow) VSTPluginLib(nullptr, false, dllPath, fileName, tags); - if(plug == nullptr) - { - return nullptr; - } - pluginList.push_back(plug); + auto &plug = pluginList.emplace_back(std::make_unique(nullptr, false, dllPath, fileName)); // Extract plugin IDs uint32 id1 = 0, id2 = 0; @@ -588,11 +510,12 @@ VSTPluginLib *CVstPluginManager::AddPlugin(const mpt::PathString &dllPath, bool const mpt::ustring flagKey = IDs + U_(".Flags"); plug->DecodeCacheFlags(cacheFile.Read(cacheSection, flagKey, 0)); plug->vendor = cacheFile.Read(cacheSection, IDs + U_(".Vendor"), CString()); + plug->shellPluginID = shellPluginID; #ifdef VST_LOG MPT_LOG_GLOBAL(LogDebug, "VST", MPT_UFORMAT("Plugin \"{}\" found in PluginCache")(plug->libraryName)); #endif // VST_LOG - return plug; + return {plug.get()}; } else { #ifdef VST_LOG @@ -604,33 +527,42 @@ VSTPluginLib *CVstPluginManager::AddPlugin(const mpt::PathString &dllPath, bool // If this key contains a file name on program launch, a plugin previously crashed OpenMPT. theApp.GetSettings().Write(U_("VST Plugins"), U_("FailedPlugin"), dllPath, SettingWriteThrough); - bool validPlug = false; - - VSTPluginLib *plug = new (std::nothrow) VSTPluginLib(nullptr, false, dllPath, fileName, tags); - if(plug == nullptr) - { - return nullptr; - } + std::vector foundPlugins; #ifdef MPT_WITH_VST unsigned long exception = 0; // Always scan plugins in a separate process - HINSTANCE hLib = NULL; { -#ifdef MODPLUG_TRACKER - ExceptionHandler::Context ectx{ MPT_UFORMAT("VST Plugin: {}")(plug->dllPath.ToUnicode()) }; + ExceptionHandler::Context ectx{ MPT_UFORMAT("VST Plugin: {}")(dllPath.ToUnicode()) }; ExceptionHandler::ContextSetter ectxguard{&ectx}; -#endif // MODPLUG_TRACKER - validPlug = TryLoadPlugin(maskCrashes, plug, hLib, exception); - } - if(hLib) - { - FreeLibrary(hLib); + VSTPluginLib plug{nullptr, false, dllPath, fileName}; + auto loadResult = CVstPlugin::LoadPlugin(maskCrashes, plug, CVstPlugin::BridgeMode::DetectRequiredBridgeMode, exception); + Vst::AEffect *pEffect = loadResult.effect; + if(pEffect) + { + foundPlugins = AddPluginsToList(GetPluginInformation(std::move(plug), loadResult), + [&](VSTPluginLib &library, bool updateExisting) + { + if(updateExisting) + return; + if(library.shellPluginID) + { + if(!CVstPlugin::SelectShellPlugin(maskCrashes, loadResult, library)) + return; + } + CVstPlugin::GetPluginMetadata(maskCrashes, loadResult, library); + }); + CVstPlugin::DispatchSEH(maskCrashes, *pEffect, Vst::effClose, 0, 0, 0, 0, exception); + } + if(loadResult.library) + { + FreeLibrary(loadResult.library); + } } if(exception != 0) { - CVstPluginManager::ReportPlugException(MPT_UFORMAT("Exception {} while trying to load plugin \"{}\"!\n")(mpt::ufmt::HEX0<8>(exception), plug->libraryName)); + CVstPluginManager::ReportPlugException(MPT_UFORMAT("Exception {} while trying to load plugin \"{}\"!\n")(mpt::ufmt::HEX0<8>(exception), fileName)); } #endif // MPT_WITH_VST @@ -638,17 +570,7 @@ VSTPluginLib *CVstPluginManager::AddPlugin(const mpt::PathString &dllPath, bool // Now it should be safe to assume that this plugin loaded properly. :) theApp.GetSettings().Remove(U_("VST Plugins"), U_("FailedPlugin")); - // If OK, write the information in PluginCache - if(validPlug) - { - pluginList.push_back(plug); - plug->WriteToCache(); - } else - { - delete plug; - } - - return (validPlug ? plug : nullptr); + return foundPlugins; } @@ -657,7 +579,7 @@ bool CVstPluginManager::RemovePlugin(VSTPluginLib *pFactory) { for(const_iterator p = begin(); p != end(); p++) { - VSTPluginLib *plug = *p; + VSTPluginLib *plug = p->get(); if(plug == pFactory) { // Kill all instances of this plugin @@ -670,7 +592,6 @@ bool CVstPluginManager::RemovePlugin(VSTPluginLib *pFactory) pluginInstance->Release(); } pluginList.erase(p); - delete plug; return true; } } @@ -693,8 +614,8 @@ bool CVstPluginManager::CreateMixPlugin(SNDMIXPLUGIN &mixPlugin, CSoundFile &snd kMatchNameAndId, }; - PlugMatchQuality match = kNoMatch; // "Match quality" of found plugin. Higher value = better match. -#if MPT_OS_WINDOWS && !MPT_OS_WINDOWS_WINRT + PlugMatchQuality match = kNoMatch; // "Match quality" of found plugin. Higher value = better match. +#if defined(MODPLUG_TRACKER) && MPT_OS_WINDOWS && !MPT_OS_WINDOWS_WINRT const mpt::PathString libraryName = mpt::PathString::FromUnicode(mixPlugin.GetLibraryName()); #else const std::string libraryName = mpt::ToCharset(mpt::Charset::UTF8, mixPlugin.GetLibraryName()); @@ -702,8 +623,9 @@ bool CVstPluginManager::CreateMixPlugin(SNDMIXPLUGIN &mixPlugin, CSoundFile &snd for(const auto &plug : pluginList) { const bool matchID = (plug->pluginId1 == mixPlugin.Info.dwPluginId1) - && (plug->pluginId2 == mixPlugin.Info.dwPluginId2); -#if MPT_OS_WINDOWS && !MPT_OS_WINDOWS_WINRT + && (plug->pluginId2 == mixPlugin.Info.dwPluginId2) + && (plug->shellPluginID == mixPlugin.Info.shellPluginID); +#if defined(MODPLUG_TRACKER) && MPT_OS_WINDOWS && !MPT_OS_WINDOWS_WINRT const bool matchName = !mpt::PathCompareNoCase(plug->libraryName, libraryName); #else const bool matchName = !mpt::CompareNoCaseAscii(plug->libraryName.ToUTF8(), libraryName); @@ -711,7 +633,7 @@ bool CVstPluginManager::CreateMixPlugin(SNDMIXPLUGIN &mixPlugin, CSoundFile &snd if(matchID && matchName) { - pFound = plug; + pFound = plug.get(); #ifdef MPT_WITH_VST if(plug->IsNative(false)) { @@ -722,113 +644,152 @@ bool CVstPluginManager::CreateMixPlugin(SNDMIXPLUGIN &mixPlugin, CSoundFile &snd match = kMatchNameAndId; } else if(matchID && match < kMatchId) { - pFound = plug; + pFound = plug.get(); match = kMatchId; } else if(matchName && match < kMatchName) { - pFound = plug; + pFound = plug.get(); match = kMatchName; } } - if(pFound != nullptr && pFound->Create != nullptr) + if(!pFound) + return false; + + IMixPlugin *plugin = nullptr; + if(pFound->Create != nullptr) { - IMixPlugin *plugin = pFound->Create(*pFound, sndFile, mixPlugin); - if(plugin) - { - pFound->InsertPluginInstanceIntoList(*plugin); - } -#ifdef MODPLUG_TRACKER - CriticalSection cs; -#endif - mixPlugin.pMixPlugin = plugin; - return plugin != nullptr; + plugin = pFound->Create(*pFound, sndFile, mixPlugin); } - -#ifdef MODPLUG_TRACKER - bool maskCrashes = TrackerSettings::Instance().BrokenPluginsWorkaroundVSTMaskAllCrashes; - - if(!pFound && (mixPlugin.GetLibraryName() != U_(""))) - { - // Try finding the plugin DLL in the plugin directory or plugin cache instead. - mpt::PathString fullPath = TrackerSettings::Instance().PathPlugins.GetDefaultDir(); - if(fullPath.empty()) - { - fullPath = theApp.GetInstallPath() + P_("Plugins\\"); - } - fullPath += mpt::PathString::FromUnicode(mixPlugin.GetLibraryName()) + P_(".dll"); - - pFound = AddPlugin(fullPath, maskCrashes); - if(!pFound) - { - // Try plugin cache (search for library name) - SettingsContainer &cacheFile = theApp.GetPluginCache(); - mpt::ustring IDs = cacheFile.Read(cacheSection, mixPlugin.GetLibraryName(), U_("")); - if(IDs.length() >= 16) - { - fullPath = cacheFile.Read(cacheSection, IDs, P_("")); - if(!fullPath.empty()) - { - fullPath = theApp.PathInstallRelativeToAbsolute(fullPath); - if(mpt::native_fs{}.is_file(fullPath)) - { - pFound = AddPlugin(fullPath, maskCrashes); - } - } - } - } - } - #ifdef MPT_WITH_VST // Note: we don't check if dwPluginId1 matches Vst::kEffectMagic here, even if it should. // I have an old file I made with OpenMPT 1.17 where the primary plugin ID has an unexpected value. // No idea how that could happen, apart from some plugin.cache corruption (back then, the IDs were not re-checked // after instantiating a plugin and the cached plugin ID was blindly written to the module file) - if(pFound) + else { - Vst::AEffect *pEffect = nullptr; - HINSTANCE hLibrary = nullptr; - bool validPlugin = false; + bool maskCrashes = TrackerSettings::Instance().BrokenPluginsWorkaroundVSTMaskAllCrashes; - pEffect = CVstPlugin::LoadPlugin(maskCrashes, *pFound, hLibrary, TrackerSettings::Instance().bridgeAllPlugins ? CVstPlugin::BridgeMode::ForceBridgeWithFallback : CVstPlugin::BridgeMode::Automatic); + unsigned long exception = 0; + auto loadResult = CVstPlugin::LoadPlugin(maskCrashes, *pFound, TrackerSettings::Instance().bridgeAllPlugins ? CVstPlugin::BridgeMode::ForceBridgeWithFallback : CVstPlugin::BridgeMode::Automatic, exception); + Vst::AEffect *pEffect = loadResult.effect; - if(pEffect != nullptr && pEffect->dispatcher != nullptr && pEffect->magic == Vst::kEffectMagic) + if(pEffect != nullptr) { - GetPluginInformation(maskCrashes, pEffect, *pFound); + // If filename matched during load but plugin ID didn't (or vice versa), make sure it's updated. + pFound->pluginId1 = loadResult.magic; + pFound->pluginId2 = loadResult.uniqueID; - // Update cached information - pFound->WriteToCache(); + plugin = new (std::nothrow) CVstPlugin(maskCrashes, loadResult.library, *pFound, mixPlugin, *pEffect, sndFile); - CVstPlugin *pVstPlug = new (std::nothrow) CVstPlugin(maskCrashes, hLibrary, *pFound, mixPlugin, *pEffect, sndFile); - if(pVstPlug) - { - pFound->InsertPluginInstanceIntoList(*pVstPlug); - } - validPlugin = (pVstPlug != nullptr); - CriticalSection cs; - mixPlugin.pMixPlugin = pVstPlug; +#ifdef MODPLUG_TRACKER + AddPluginsToList(GetPluginInformation(*pFound, loadResult), + [&](VSTPluginLib &library, bool updateExisting) + { + if(&library == pFound && updateExisting) + CVstPlugin::GetPluginMetadata(maskCrashes, loadResult, library); + }); +#endif } - if(!validPlugin) + if(!plugin && loadResult.library) { - FreeLibrary(hLibrary); + FreeLibrary(loadResult.library); CVstPluginManager::ReportPlugException(MPT_UFORMAT("Unable to create plugin \"{}\"!\n")(pFound->libraryName)); } - return validPlugin; - } else - { - // "plug not found" notification code MOVED to CSoundFile::Create -#ifdef VST_LOG - MPT_LOG_GLOBAL(LogDebug, "VST", U_("Unknown plugin")); -#endif } #endif // MPT_WITH_VST -#endif // MODPLUG_TRACKER - return false; +#ifdef MODPLUG_TRACKER + CriticalSection cs; +#endif + if(plugin) + pFound->InsertPluginInstanceIntoList(*plugin); + mixPlugin.pMixPlugin = plugin; + // If filename matched during load but plugin ID didn't (or vice versa), make sure it's updated. + mixPlugin.Info.dwPluginId1 = pFound->pluginId1; + mixPlugin.Info.dwPluginId2 = pFound->pluginId2; + mixPlugin.Info.szLibraryName = pFound->libraryName.ToUTF8(); + + return plugin != nullptr; } +#ifdef MPT_WITH_VST +std::vector CVstPluginManager::AddPluginsToList(std::vector containedPlugins, std::function updateFunc) +{ + std::vector newPlugins; + if(containedPlugins.empty()) + return newPlugins; + + // Find existing shell plugins belonging to the same file first, so that we don't have to iterate through the whole plugin list again and again + std::map existingCandidates; + for(size_t i = 0; i < pluginList.size(); i++) + { + const auto &plug = pluginList[i]; + if(plug->pluginId1 == containedPlugins.front().pluginId1 && plug->pluginId2 == containedPlugins.front().pluginId2) + { + if(!mpt::PathCompareNoCase(plug->dllPath, containedPlugins.front().dllPath)) + existingCandidates[plug->shellPluginID] = i; + } + } + + // Add found plugins to list or update them if they already exist + std::set containedIDs; + VSTPluginLib *first = nullptr; + for(auto &containedPlug : containedPlugins) + { + containedIDs.insert(containedPlug.shellPluginID); + VSTPluginLib *found = nullptr; + if(auto it = existingCandidates.find(containedPlug.shellPluginID); it != existingCandidates.end()) + { + auto &plug = pluginList[it->second]; + MPT_ASSERT(plug->pluginId1 == containedPlug.pluginId1 && plug->pluginId2 == containedPlug.pluginId2); + if(plug->shellPluginID == containedPlug.shellPluginID) + found = plug.get(); + } + const bool updateExisting = found != nullptr; + if(updateExisting) + { + found->libraryName = containedPlug.libraryName; + } else + { + auto &plug = pluginList.emplace_back(std::make_unique(std::move(containedPlug))); + found = plug.get(); + newPlugins.push_back(found); + } + updateFunc(*found, updateExisting); + + if(!first) + first = found; + +#ifdef MODPLUG_TRACKER + found->WriteToCache(); +#endif // MODPLUG_TRACKER + } + + // Are there any shell plugins in our list that are no longer part of the shell plugin? + if(containedPlugins.size() > 1) + { + size_t deleted = 0; + for(const auto &[id, i] : existingCandidates) + { + if(!mpt::contains(containedIDs, id) && !pluginList[i - deleted]->pPluginsList) + { + MPT_ASSERT(pluginList[i - deleted]->shellPluginID == id); + pluginList.erase(pluginList.begin() + i - deleted); + deleted++; + } + } + } + + if(newPlugins.empty() && first) + newPlugins.push_back(first); + return newPlugins; +} +#endif // MPT_WITH_VST + + #ifdef MODPLUG_TRACKER void CVstPluginManager::OnIdle() { diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/PluginManager.h b/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/PluginManager.h index 4714f3c0e..730428209 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/PluginManager.h +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/PluginManager.h @@ -12,6 +12,8 @@ #include "openmpt/all/BuildSettings.hpp" +#include + OPENMPT_NAMESPACE_BEGIN constexpr int32 PLUGMAGIC(char a, char b, char c, char d) noexcept @@ -27,31 +29,30 @@ class IMixPlugin; struct SNDMIXPLUGIN; enum PluginArch : int; +enum class PluginCategory : uint8 +{ + // Same plugin categories as defined in VST SDK + Unknown = 0, + Effect, // Simple Effect + Synth, // VST Instrument (Synths, samplers,...) + Analysis, // Scope, Tuner, ... + Mastering, // Dynamics, ... + Spacializer, // Panners, ... + RoomFx, // Delays and Reverbs + SurroundFx, // Dedicated surround processor + Restoration, // Denoiser, ... + OfflineProcess, // Offline Process + Shell, // Plug-in is container of other plug-ins + Generator, // Tone Generator, ... + // Custom categories + DMO, // DirectX media object plugin + Hidden, // For internal plugins that should not be visible to the user (e.g. because they only exist for legacy reasons) + + NumCategories +}; + struct VSTPluginLib { -public: - enum PluginCategory : uint8 - { - // Same plugin categories as defined in VST SDK - catUnknown = 0, - catEffect, // Simple Effect - catSynth, // VST Instrument (Synths, samplers,...) - catAnalysis, // Scope, Tuner, ... - catMastering, // Dynamics, ... - catSpacializer, // Panners, ... - catRoomFx, // Delays and Reverbs - catSurroundFx, // Dedicated surround processor - catRestoration, // Denoiser, ... - catOfflineProcess, // Offline Process - catShell, // Plug-in is container of other plug-ins - catGenerator, // Tone Generator, ... - // Custom categories - catDMO, // DirectX media object plugin - catHidden, // For internal plugins that should not be visible to the user (e.g. because they only exist for legacy reasons) - - numCategories - }; - public: using CreateProc = IMixPlugin *(*)(VSTPluginLib &factory, CSoundFile &sndFile, SNDMIXPLUGIN &mixStruct); @@ -68,7 +69,8 @@ public: #endif // MODPLUG_TRACKER int32 pluginId1 = 0; // Plugin type (kEffectMagic, kDmoMagic, ...) int32 pluginId2 = 0; // Plugin unique ID - PluginCategory category = catUnknown; + uint32 shellPluginID = 0; // ID of shell child plugin + PluginCategory category = PluginCategory::Unknown; const bool isBuiltIn : 1; bool isInstrument : 1; bool useBridge : 1, shareBridgeInstance : 1, modernBridge : 1; @@ -76,29 +78,34 @@ protected: mutable uint8 dllArch = 0; public: - VSTPluginLib(CreateProc factoryProc, bool isBuiltIn, const mpt::PathString &dllPath, const mpt::PathString &libraryName -#ifdef MODPLUG_TRACKER - , const mpt::ustring &tags = mpt::ustring(), const CString &vendor = CString() -#endif // MODPLUG_TRACKER - ) + VSTPluginLib(CreateProc factoryProc, bool isBuiltIn, mpt::PathString dllPath, mpt::PathString libraryName) : Create(factoryProc) - , libraryName(libraryName), dllPath(dllPath) -#ifdef MODPLUG_TRACKER - , tags(tags) - , vendor(vendor) -#endif // MODPLUG_TRACKER - , category(catUnknown) + , libraryName(std::move(libraryName)), dllPath(std::move(dllPath)) + , category(PluginCategory::Unknown) , isBuiltIn(isBuiltIn), isInstrument(false) , useBridge(false), shareBridgeInstance(true), modernBridge(true) { } + VSTPluginLib(VSTPluginLib &&) = default; + VSTPluginLib(const VSTPluginLib &other) + : Create(other.Create) + , libraryName(other.libraryName), dllPath(other.dllPath) +#ifdef MODPLUG_TRACKER + , tags(other.tags), vendor(other.vendor) +#endif // MODPLUG_TRACKER + , pluginId1(other.pluginId1), pluginId2(other.pluginId2), shellPluginID(other.shellPluginID) + , category(other.category) + , isBuiltIn(other.isBuiltIn), isInstrument(other.isInstrument) + , useBridge(other.useBridge), shareBridgeInstance(other.shareBridgeInstance), modernBridge(other.modernBridge) + , dllArch(other.dllArch) + { + } #ifdef MPT_WITH_VST // Get native phost process arch encoded as plugin arch static uint8 GetNativePluginArch(); static mpt::ustring GetPluginArchName(uint8 arch); - static mpt::ustring GetPluginArchNameUser(uint8 arch); // Check whether a plugin can be hosted inside OpenMPT or requires bridging uint8 GetDllArch(bool fromCache = true) const; @@ -117,7 +124,7 @@ public: { // Format: 00000000.0000000M.AAAAAASB.CCCCCCCI return (isInstrument ? 1 : 0) - | (category << 1) + | (static_cast(category) << 1) | (useBridge ? 0x100 : 0) | (shareBridgeInstance ? 0x200 : 0) | ((dllArch / 8) << 10) @@ -128,14 +135,14 @@ public: void DecodeCacheFlags(uint32 flags) { category = static_cast((flags & 0xFF) >> 1); - if(category >= numCategories) + if(category >= PluginCategory::NumCategories) { - category = catUnknown; + category = PluginCategory::Unknown; } if(flags & 1) { isInstrument = true; - category = catSynth; + category = PluginCategory::Synth; } useBridge = (flags & 0x100) != 0; shareBridgeInstance = (flags & 0x200) != 0; @@ -152,14 +159,14 @@ protected: #if defined(MPT_WITH_DMO) bool MustUnInitilizeCOM = false; #endif - std::vector pluginList; + std::vector> pluginList; public: CVstPluginManager(); ~CVstPluginManager(); - using iterator = std::vector::iterator; - using const_iterator = std::vector::const_iterator; + using iterator = decltype(pluginList)::iterator; + using const_iterator = decltype(pluginList)::const_iterator; iterator begin() { return pluginList.begin(); } const_iterator begin() const { return pluginList.begin(); } @@ -169,7 +176,7 @@ public: size_t size() const { return pluginList.size(); } bool IsValidPlugin(const VSTPluginLib *pLib) const; - VSTPluginLib *AddPlugin(const mpt::PathString &dllPath, bool maskCrashes, const mpt::ustring &tags = mpt::ustring(), bool fromCache = true, bool *fileFound = nullptr); + std::vector AddPlugin(const mpt::PathString &dllPath, bool maskCrashes, bool fromCache = true, bool *fileFound = nullptr, uint32 shellPluginID = 0); bool RemovePlugin(VSTPluginLib *); bool CreateMixPlugin(SNDMIXPLUGIN &, CSoundFile &); void OnIdle(); @@ -178,6 +185,8 @@ public: protected: void EnumerateDirectXDMOs(); + std::vector AddPluginsToList(std::vector containedPlugins, std::function updateFunc); + #else // NO_PLUGINS public: const VSTPluginLib **begin() const { return nullptr; } diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/PluginStructs.h b/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/PluginStructs.h index 4dc58d30e..6595ab7ef 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/PluginStructs.h +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/PluginStructs.h @@ -22,9 +22,6 @@ OPENMPT_NAMESPACE_BEGIN //////////////////////////////////////////////////////////////////// // Mix Plugins -using PlugParamIndex = uint32; -using PlugParamValue = float; - struct SNDMIXPLUGINSTATE; struct SNDMIXPLUGIN; class IMixPlugin; @@ -32,6 +29,17 @@ class CSoundFile; #ifndef NO_PLUGINS +enum class PluginMixMode : uint8 +{ + Default = 0, + WetSubtract = 1, + DrySubtract = 2, + MixSubtract = 3, + MiddleSubtract = 4, + LRBalance = 5, + Instrument = 6, +}; + struct SNDMIXPLUGININFO { // dwInputRouting flags @@ -39,21 +47,22 @@ struct SNDMIXPLUGININFO { irApplyToMaster = 0x01, // Apply to master mix irBypass = 0x02, // Bypass effect - irWetMix = 0x04, // Wet Mix (dry added) + irDryMix = 0x04, // Wet Mix (dry added) irExpandMix = 0x08, // [0%,100%] -> [-200%,200%] irAutoSuspend = 0x10, // Plugin will automatically suspend on silence }; - int32le dwPluginId1; // Plugin type (kEffectMagic, kDmoMagic, kBuzzMagic) + int32le dwPluginId1; // Plugin type (kEffectMagic, kDmoMagic or custom for built-in plugins) int32le dwPluginId2; // Plugin unique ID uint8le routingFlags; // See RoutingFlags uint8le mixMode; uint8le gain; // Divide by 10 to get real gain uint8le reserved; uint32le dwOutputRouting; // 0 = send to master 0x80 + x = send to plugin x - uint32le dwReserved[4]; // Reserved for routing info + uint32le shellPluginID; // For shell plugins: The child plugin to load + uint32le dwReserved[3]; // Reserved for routing info mpt::modecharbuf<32, mpt::String::nullTerminated> szName; // User-chosen plugin display name - this is locale ANSI! - mpt::modecharbuf<64, mpt::String::nullTerminated> szLibraryName; // original DLL name - this is UTF-8! + mpt::modecharbuf<64, mpt::String::nullTerminated> szLibraryName; // original DLL name (shell plugins: child plugin name) - this is UTF-8! // Should only be called from SNDMIXPLUGIN::SetBypass() and IMixPlugin::Bypass() void SetBypass(bool bypass = true) { if(bypass) routingFlags |= irBypass; else routingFlags &= uint8(~irBypass); } @@ -92,12 +101,12 @@ struct SNDMIXPLUGIN // Input routing getters uint8 GetGain() const { return Info.gain; } - uint8 GetMixMode() const - { return Info.mixMode; } + PluginMixMode GetMixMode() const + { return static_cast(Info.mixMode.get()); } bool IsMasterEffect() const { return (Info.routingFlags & SNDMIXPLUGININFO::irApplyToMaster) != 0; } - bool IsWetMix() const - { return (Info.routingFlags & SNDMIXPLUGININFO::irWetMix) != 0; } + bool IsDryMix() const + { return (Info.routingFlags & SNDMIXPLUGININFO::irDryMix) != 0; } bool IsExpandedMix() const { return (Info.routingFlags & SNDMIXPLUGININFO::irExpandMix) != 0; } bool IsBypassed() const @@ -107,12 +116,12 @@ struct SNDMIXPLUGIN // Input routing setters void SetGain(uint8 gain); - void SetMixMode(uint8 mixMode) - { Info.mixMode = mixMode; } + void SetMixMode(PluginMixMode mixMode) + { Info.mixMode = static_cast(mixMode); } void SetMasterEffect(bool master = true) { if(master) Info.routingFlags |= SNDMIXPLUGININFO::irApplyToMaster; else Info.routingFlags &= uint8(~SNDMIXPLUGININFO::irApplyToMaster); } - void SetWetMix(bool wetMix = true) - { if(wetMix) Info.routingFlags |= SNDMIXPLUGININFO::irWetMix; else Info.routingFlags &= uint8(~SNDMIXPLUGININFO::irWetMix); } + void SetDryMix(bool wetMix = true) + { if(wetMix) Info.routingFlags |= SNDMIXPLUGININFO::irDryMix; else Info.routingFlags &= uint8(~SNDMIXPLUGININFO::irDryMix); } void SetExpandedMix(bool expanded = true) { if(expanded) Info.routingFlags |= SNDMIXPLUGININFO::irExpandMix; else Info.routingFlags &= uint8(~SNDMIXPLUGININFO::irExpandMix); } void SetBypass(bool bypass = true); diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/SymMODEcho.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/SymMODEcho.cpp index ee8d7ebee..dd9013298 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/SymMODEcho.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/SymMODEcho.cpp @@ -11,8 +11,8 @@ #include "stdafx.h" #ifndef NO_PLUGINS -#include "../Sndfile.h" #include "SymMODEcho.h" +#include "../Sndfile.h" OPENMPT_NAMESPACE_BEGIN @@ -141,7 +141,7 @@ PlugParamValue SymMODEcho::GetParameter(PlugParamIndex index) } -void SymMODEcho::SetParameter(PlugParamIndex index, PlugParamValue value) +void SymMODEcho::SetParameter(PlugParamIndex index, PlugParamValue value, PlayState *, CHANNELINDEX) { if(index < kEchoNumParameters) { @@ -209,15 +209,15 @@ CString SymMODEcho::GetParamDisplay(PlugParamIndex param) switch(static_cast(param)) { case kEchoType: - switch(GetDSPType()) - { - case DSPType::Off: return _T("Off"); - case DSPType::Normal: return _T("Normal"); - case DSPType::Cross: return _T("Cross"); - case DSPType::Cross2: return _T("Cross 2"); - case DSPType::Center: return _T("Center"); - case DSPType::NumTypes: break; - } + switch(GetDSPType()) + { + case DSPType::Off: return _T("Off"); + case DSPType::Normal: return _T("Normal"); + case DSPType::Cross: return _T("Cross"); + case DSPType::Cross2: return _T("Cross 2"); + case DSPType::Center: return _T("Center"); + case DSPType::NumTypes: break; + } break; case kEchoDelay: return mpt::cfmt::val(m_chunk.param[kEchoDelay]); diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/SymMODEcho.h b/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/SymMODEcho.h index a8d29fa9d..2457a0726 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/SymMODEcho.h +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/SymMODEcho.h @@ -85,7 +85,7 @@ public: PlugParamIndex GetNumParameters() const override { return kEchoNumParameters; } PlugParamValue GetParameter(PlugParamIndex index) override; - void SetParameter(PlugParamIndex index, PlugParamValue value) override; + void SetParameter(PlugParamIndex index, PlugParamValue value, PlayState * = nullptr, CHANNELINDEX = CHANNELINDEX_INVALID) override; void Resume() override; void Suspend() override { m_isResumed = false; } diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/Chorus.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/Chorus.cpp index ce211ac1d..a419bfff5 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/Chorus.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/Chorus.cpp @@ -149,7 +149,7 @@ PlugParamValue Chorus::GetParameter(PlugParamIndex index) } -void Chorus::SetParameter(PlugParamIndex index, PlugParamValue value) +void Chorus::SetParameter(PlugParamIndex index, PlugParamValue value, PlayState *, CHANNELINDEX) { if(index < kChorusNumParameters) { diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/Chorus.h b/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/Chorus.h index 327eb0ca6..473429067 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/Chorus.h +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/Chorus.h @@ -72,7 +72,7 @@ public: PlugParamIndex GetNumParameters() const override { return kChorusNumParameters; } PlugParamValue GetParameter(PlugParamIndex index) override; - void SetParameter(PlugParamIndex index, PlugParamValue value) override; + void SetParameter(PlugParamIndex index, PlugParamValue value, PlayState * = nullptr, CHANNELINDEX = CHANNELINDEX_INVALID) override; void Resume() override; void Suspend() override { m_isResumed = false; } diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/Compressor.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/Compressor.cpp index e25d6eefa..638f5a1af 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/Compressor.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/Compressor.cpp @@ -114,7 +114,7 @@ PlugParamValue Compressor::GetParameter(PlugParamIndex index) } -void Compressor::SetParameter(PlugParamIndex index, PlugParamValue value) +void Compressor::SetParameter(PlugParamIndex index, PlugParamValue value, PlayState *, CHANNELINDEX) { if(index < kCompNumParameters) { @@ -177,6 +177,8 @@ CString Compressor::GetParamLabel(PlugParamIndex param) case kCompRelease: case kCompPredelay: return _T("ms"); + case kCompRatio: + return _T(": 1"); } return CString(); } diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/Compressor.h b/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/Compressor.h index 7c2cee766..86d781606 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/Compressor.h +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/Compressor.h @@ -65,7 +65,7 @@ public: PlugParamIndex GetNumParameters() const override { return kCompNumParameters; } PlugParamValue GetParameter(PlugParamIndex index) override; - void SetParameter(PlugParamIndex index, PlugParamValue value) override; + void SetParameter(PlugParamIndex index, PlugParamValue value, PlayState * = nullptr, CHANNELINDEX = CHANNELINDEX_INVALID) override; void Resume() override; void Suspend() override { m_isResumed = false; } diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/DMOPlugin.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/DMOPlugin.cpp index 4c6e8a7d0..1c7e1f3b5 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/DMOPlugin.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/DMOPlugin.cpp @@ -14,9 +14,9 @@ #include "mpt/base/aligned_array.hpp" #if defined(MPT_WITH_DMO) +#include "DMOPlugin.h" #include "mpt/uuid/guid.hpp" #include "../../Sndfile.h" -#include "DMOPlugin.h" #include "../PluginManager.h" #include #include @@ -205,20 +205,15 @@ PlugParamValue DMOPlugin::GetParameter(PlugParamIndex index) { if(index < GetNumParameters() && m_pParamInfo != nullptr && m_pMediaParams != nullptr) { - MP_PARAMINFO mpi; - MP_DATA md; - - MemsetZero(mpi); - md = 0; + MP_PARAMINFO mpi{}; + MP_DATA md = 0; if (m_pParamInfo->GetParamInfo(index, &mpi) == S_OK && m_pMediaParams->GetParam(index, &md) == S_OK) { - float fValue, fMin, fMax, fDefault; - - fValue = md; - fMin = mpi.mpdMinValue; - fMax = mpi.mpdMaxValue; - fDefault = mpi.mpdNeutralValue; + float fValue = md; + float fMin = mpi.mpdMinValue; + float fMax = mpi.mpdMaxValue; + //float fDefault = mpi.mpdNeutralValue; if (mpi.mpType == MPT_BOOL) { fMin = 0; @@ -233,12 +228,11 @@ PlugParamValue DMOPlugin::GetParameter(PlugParamIndex index) } -void DMOPlugin::SetParameter(PlugParamIndex index, PlugParamValue value) +void DMOPlugin::SetParameter(PlugParamIndex index, PlugParamValue value, PlayState *, CHANNELINDEX) { if(index < GetNumParameters() && m_pParamInfo != nullptr && m_pMediaParams != nullptr) { - MP_PARAMINFO mpi; - MemsetZero(mpi); + MP_PARAMINFO mpi{}; if (m_pParamInfo->GetParamInfo(index, &mpi) == S_OK) { float fMin = mpi.mpdMinValue; diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/DMOPlugin.h b/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/DMOPlugin.h index 6260cd135..39cbc4232 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/DMOPlugin.h +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/DMOPlugin.h @@ -64,7 +64,7 @@ public: PlugParamIndex GetNumParameters() const override; PlugParamValue GetParameter(PlugParamIndex index) override; - void SetParameter(PlugParamIndex index, PlugParamValue value) override; + void SetParameter(PlugParamIndex index, PlugParamValue value, PlayState * = nullptr, CHANNELINDEX = CHANNELINDEX_INVALID) override; void Resume() override; void Suspend() override; diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/Distortion.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/Distortion.cpp index 3e05a0882..7f1ed8181 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/Distortion.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/Distortion.cpp @@ -13,8 +13,8 @@ #include "stdafx.h" #ifndef NO_PLUGINS -#include "../../Sndfile.h" #include "Distortion.h" +#include "../../Sndfile.h" #include "DMOUtils.h" #include "mpt/base/numbers.hpp" #endif // !NO_PLUGINS @@ -92,7 +92,7 @@ PlugParamValue Distortion::GetParameter(PlugParamIndex index) } -void Distortion::SetParameter(PlugParamIndex index, PlugParamValue value) +void Distortion::SetParameter(PlugParamIndex index, PlugParamValue value, PlayState *, CHANNELINDEX) { if(index < kDistNumParameters) { diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/Distortion.h b/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/Distortion.h index 9d0d63ce0..a23738fd8 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/Distortion.h +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/Distortion.h @@ -57,7 +57,7 @@ public: PlugParamIndex GetNumParameters() const override { return kDistNumParameters; } PlugParamValue GetParameter(PlugParamIndex index) override; - void SetParameter(PlugParamIndex index, PlugParamValue value) override; + void SetParameter(PlugParamIndex index, PlugParamValue value, PlayState * = nullptr, CHANNELINDEX = CHANNELINDEX_INVALID) override; void Resume() override; void Suspend() override { m_isResumed = false; } diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/Echo.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/Echo.cpp index 79ac4e23d..134422369 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/Echo.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/Echo.cpp @@ -11,8 +11,8 @@ #include "stdafx.h" #ifndef NO_PLUGINS -#include "../../Sndfile.h" #include "Echo.h" +#include "../../Sndfile.h" #endif // !NO_PLUGINS OPENMPT_NAMESPACE_BEGIN @@ -96,7 +96,7 @@ PlugParamValue Echo::GetParameter(PlugParamIndex index) } -void Echo::SetParameter(PlugParamIndex index, PlugParamValue value) +void Echo::SetParameter(PlugParamIndex index, PlugParamValue value, PlayState *, CHANNELINDEX) { if(index < kEchoNumParameters) { diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/Echo.h b/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/Echo.h index cffac15bb..99b52e3ec 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/Echo.h +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/Echo.h @@ -60,7 +60,7 @@ public: PlugParamIndex GetNumParameters() const override { return kEchoNumParameters; } PlugParamValue GetParameter(PlugParamIndex index) override; - void SetParameter(PlugParamIndex index, PlugParamValue value) override; + void SetParameter(PlugParamIndex index, PlugParamValue value, PlayState * = nullptr, CHANNELINDEX = CHANNELINDEX_INVALID) override; void Resume() override; void Suspend() override { m_isResumed = false; } diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/Flanger.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/Flanger.cpp index fa2925fc6..5791e62f8 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/Flanger.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/Flanger.cpp @@ -50,7 +50,7 @@ Flanger::Flanger(VSTPluginLib &factory, CSoundFile &sndFile, SNDMIXPLUGIN &mixSt } -void Flanger::SetParameter(PlugParamIndex index, PlugParamValue value) +void Flanger::SetParameter(PlugParamIndex index, PlugParamValue value, PlayState *, CHANNELINDEX) { if(index < kFlangerNumParameters) { @@ -124,7 +124,7 @@ CString Flanger::GetParamDisplay(PlugParamIndex param) value = FrequencyInHertz(); break; case kFlangerWaveShape: - return (value < 1) ? _T("Triangle") : _T("Sine"); + return (value < 1) ? _T("Square") : _T("Sine"); break; case kFlangerPhase: switch(Phase()) diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/Flanger.h b/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/Flanger.h index 4e7450217..1c844f0f6 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/Flanger.h +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/Flanger.h @@ -45,7 +45,7 @@ public: int32 GetUID() const override { return 0xEFCA3D92; } PlugParamIndex GetNumParameters() const override { return kFlangerNumParameters; } - void SetParameter(PlugParamIndex index, PlugParamValue value) override; + void SetParameter(PlugParamIndex index, PlugParamValue value, PlayState * = nullptr, CHANNELINDEX = CHANNELINDEX_INVALID) override; #ifdef MODPLUG_TRACKER CString GetDefaultEffectName() override { return _T("Flanger"); } diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/Gargle.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/Gargle.cpp index 28eac073b..ce792eb17 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/Gargle.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/Gargle.cpp @@ -11,9 +11,9 @@ #include "stdafx.h" #ifndef NO_PLUGINS -#include "../../Sndfile.h" #include "Gargle.h" -#endif // !NO_PLUGINS +#include "../../Sndfile.h" +#endif // !NO_PLUGINS OPENMPT_NAMESPACE_BEGIN @@ -116,7 +116,7 @@ PlugParamValue Gargle::GetParameter(PlugParamIndex index) } -void Gargle::SetParameter(PlugParamIndex index, PlugParamValue value) +void Gargle::SetParameter(PlugParamIndex index, PlugParamValue value, PlayState *, CHANNELINDEX) { if(index < kGargleNumParameters) { diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/Gargle.h b/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/Gargle.h index f2698335a..3fc93a1f7 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/Gargle.h +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/Gargle.h @@ -50,7 +50,7 @@ public: PlugParamIndex GetNumParameters() const override { return kGargleNumParameters; } PlugParamValue GetParameter(PlugParamIndex index) override; - void SetParameter(PlugParamIndex index, PlugParamValue value) override; + void SetParameter(PlugParamIndex index, PlugParamValue value, PlayState * = nullptr, CHANNELINDEX = CHANNELINDEX_INVALID) override; void Resume() override; void Suspend() override { m_isResumed = false; } diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/I3DL2Reverb.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/I3DL2Reverb.cpp index a566b87c2..80d16df3d 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/I3DL2Reverb.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/I3DL2Reverb.cpp @@ -11,8 +11,8 @@ #include "stdafx.h" #ifndef NO_PLUGINS -#include "../../Sndfile.h" #include "I3DL2Reverb.h" +#include "../../Sndfile.h" #ifdef MODPLUG_TRACKER #include "../../../sounddsp/Reverb.h" #endif // MODPLUG_TRACKER @@ -342,7 +342,7 @@ PlugParamValue I3DL2Reverb::GetParameter(PlugParamIndex index) } -void I3DL2Reverb::SetParameter(PlugParamIndex index, PlugParamValue value) +void I3DL2Reverb::SetParameter(PlugParamIndex index, PlugParamValue value, PlayState *, CHANNELINDEX) { if(index < kI3DL2ReverbNumParameters) { diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/I3DL2Reverb.h b/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/I3DL2Reverb.h index cceffeba0..6c4042650 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/I3DL2Reverb.h +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/I3DL2Reverb.h @@ -50,9 +50,9 @@ protected: class DelayLine : private std::vector { - int32 m_length; - int32 m_position; - int32 m_delayPosition; + int32 m_length = 0; + int32 m_position = 0; + int32 m_delayPosition = 0; public: void Init(int32 ms, int32 padding, uint32 sampleRate, int32 delayTap = 0); @@ -110,7 +110,7 @@ public: PlugParamIndex GetNumParameters() const override { return kI3DL2ReverbNumParameters; } PlugParamValue GetParameter(PlugParamIndex index) override; - void SetParameter(PlugParamIndex index, PlugParamValue value) override; + void SetParameter(PlugParamIndex index, PlugParamValue value, PlayState * = nullptr, CHANNELINDEX = CHANNELINDEX_INVALID) override; void Resume() override; void Suspend() override { m_isResumed = false; } diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/ParamEq.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/ParamEq.cpp index d1f35d4df..4457cd389 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/ParamEq.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/ParamEq.cpp @@ -11,8 +11,8 @@ #include "stdafx.h" #ifndef NO_PLUGINS -#include "../../Sndfile.h" #include "ParamEq.h" +#include "../../Sndfile.h" #include "mpt/base/numbers.hpp" #endif // !NO_PLUGINS @@ -86,7 +86,7 @@ PlugParamValue ParamEq::GetParameter(PlugParamIndex index) } -void ParamEq::SetParameter(PlugParamIndex index, PlugParamValue value) +void ParamEq::SetParameter(PlugParamIndex index, PlugParamValue value, PlayState *, CHANNELINDEX) { if(index < kEqNumParameters) { diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/ParamEq.h b/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/ParamEq.h index e59ce9967..f4ef56c60 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/ParamEq.h +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/ParamEq.h @@ -56,7 +56,7 @@ public: PlugParamIndex GetNumParameters() const override { return kEqNumParameters; } PlugParamValue GetParameter(PlugParamIndex index) override; - void SetParameter(PlugParamIndex index, PlugParamValue value) override; + void SetParameter(PlugParamIndex index, PlugParamValue value, PlayState * = nullptr, CHANNELINDEX = CHANNELINDEX_INVALID) override; void Resume() override; void Suspend() override { m_isResumed = false; } diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/WavesReverb.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/WavesReverb.cpp index a9076378e..679018c11 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/WavesReverb.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/WavesReverb.cpp @@ -11,8 +11,8 @@ #include "stdafx.h" #ifndef NO_PLUGINS -#include "../../Sndfile.h" #include "WavesReverb.h" +#include "../../Sndfile.h" #endif // !NO_PLUGINS OPENMPT_NAMESPACE_BEGIN @@ -129,7 +129,7 @@ PlugParamValue WavesReverb::GetParameter(PlugParamIndex index) } -void WavesReverb::SetParameter(PlugParamIndex index, PlugParamValue value) +void WavesReverb::SetParameter(PlugParamIndex index, PlugParamValue value, PlayState *, CHANNELINDEX) { if(index < kRvbNumParameters) { diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/WavesReverb.h b/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/WavesReverb.h index 5827a7212..9711dec1c 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/WavesReverb.h +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/plugins/dmo/WavesReverb.h @@ -65,7 +65,7 @@ public: PlugParamIndex GetNumParameters() const override { return kRvbNumParameters; } PlugParamValue GetParameter(PlugParamIndex index) override; - void SetParameter(PlugParamIndex index, PlugParamValue value) override; + void SetParameter(PlugParamIndex index, PlugParamValue value, PlayState * = nullptr, CHANNELINDEX = CHANNELINDEX_INVALID) override; void Resume() override; void Suspend() override { m_isResumed = false; } diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/tuning.cpp b/Frameworks/OpenMPT/OpenMPT/soundlib/tuning.cpp index 678dba48b..33d9a785c 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/tuning.cpp +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/tuning.cpp @@ -70,13 +70,14 @@ static_assert(CTuning::s_RatioTableFineSizeMaxDefault < static_cast -#include "../common/mptFileIO.h" #include "Loaders.h" #ifdef MODPLUG_TRACKER +#include "../common/mptFileIO.h" #include "mpt/fs/fs.hpp" #include "mpt/io_file/outputfile.hpp" #include "../mptrack/TrackerSettings.h" diff --git a/Frameworks/OpenMPT/OpenMPT/soundlib/tuningbase.h b/Frameworks/OpenMPT/OpenMPT/soundlib/tuningbase.h index e72146a2d..894bb6a29 100644 --- a/Frameworks/OpenMPT/OpenMPT/soundlib/tuningbase.h +++ b/Frameworks/OpenMPT/OpenMPT/soundlib/tuningbase.h @@ -32,7 +32,7 @@ enum class SerializationResult : int { using NOTEINDEXTYPE = int16; // Some signed integer-type. using UNOTEINDEXTYPE = uint16; // Unsigned NOTEINDEXTYPE. -using RATIOTYPE = float32; // Some 'real figure' type able to present ratios. If changing RATIOTYPE, serialization methods may need modifications. +using RATIOTYPE = somefloat32; // Some 'real figure' type able to present ratios. If changing RATIOTYPE, serialization methods may need modifications. // Counter of steps between notes. If there is no 'finetune'(finestepcount == 0), // then 'step difference' between notes is the diff --git a/Frameworks/OpenMPT/OpenMPT/src/mpt/arch/x86_amd64.hpp b/Frameworks/OpenMPT/OpenMPT/src/mpt/arch/x86_amd64.hpp index 162881efc..86ec3c4c1 100644 --- a/Frameworks/OpenMPT/OpenMPT/src/mpt/arch/x86_amd64.hpp +++ b/Frameworks/OpenMPT/OpenMPT/src/mpt/arch/x86_amd64.hpp @@ -37,8 +37,10 @@ #if MPT_ARCH_X86 || MPT_ARCH_AMD64 #if MPT_COMPILER_MSVC -#include #include +#endif +#if MPT_COMPILER_MSVC +#include #elif MPT_COMPILER_GCC || MPT_COMPILER_CLANG #include #include @@ -50,207 +52,6 @@ -// clang-format off - -#if MPT_ARCH_X86 || MPT_ARCH_AMD64 - -#if MPT_COMPILER_MSVC - -#if defined(_M_X64) - #define MPT_ARCH_X86_I386 - #define MPT_ARCH_X86_FPU - #define MPT_ARCH_X86_FSIN - #define MPT_ARCH_X86_I486 - #define MPT_ARCH_X86_CPUID - #define MPT_ARCH_X86_TSC - #define MPT_ARCH_X86_CX8 - #define MPT_ARCH_X86_CMOV - #define MPT_ARCH_X86_MMX - #define MPT_ARCH_X86_MMXEXT - #define MPT_ARCH_X86_FXSR - #define MPT_ARCH_X86_SSE - #define MPT_ARCH_X86_SSE2 -#elif defined(_M_IX86) && defined(_M_IX86_FP) - #if (_M_IX86_FP >= 2) - #define MPT_ARCH_X86_I386 - #define MPT_ARCH_X86_FPU - #define MPT_ARCH_X86_FSIN - #define MPT_ARCH_X86_I486 - #define MPT_ARCH_X86_CPUID - #define MPT_ARCH_X86_TSC - #define MPT_ARCH_X86_CX8 - #define MPT_ARCH_X86_CMOV - #define MPT_ARCH_X86_MMX - #define MPT_ARCH_X86_MMXEXT - #define MPT_ARCH_X86_FXSR - #define MPT_ARCH_X86_SSE - #define MPT_ARCH_X86_SSE2 - #elif (_M_IX86_FP == 1) - #define MPT_ARCH_X86_I386 - #define MPT_ARCH_X86_FPU - #define MPT_ARCH_X86_FSIN - #define MPT_ARCH_X86_I486 - #define MPT_ARCH_X86_CPUID - #define MPT_ARCH_X86_TSC - #define MPT_ARCH_X86_CX8 - #define MPT_ARCH_X86_CMOV - #define MPT_ARCH_X86_MMX - #define MPT_ARCH_X86_MMXEXT - #define MPT_ARCH_X86_FXSR - #define MPT_ARCH_X86_SSE - #elif MPT_MSVC_AT_LEAST(2008, 0) - #define MPT_ARCH_X86_I386 - #define MPT_ARCH_X86_FPU - #define MPT_ARCH_X86_FSIN - #define MPT_ARCH_X86_I486 - #define MPT_ARCH_X86_CPUID - #define MPT_ARCH_X86_TSC - #define MPT_ARCH_X86_CX8 - #elif MPT_MSVC_AT_LEAST(2005, 0) - #define MPT_ARCH_X86_I386 - #define MPT_ARCH_X86_FPU - #define MPT_ARCH_X86_FSIN - #define MPT_ARCH_X86_I486 - #elif MPT_MSVC_AT_LEAST(1998, 0) - #define MPT_ARCH_X86_I386 - #define MPT_ARCH_X86_FPU - #define MPT_ARCH_X86_FSIN - #else - #define MPT_ARCH_X86_I386 - #endif -#endif -#if defined(__AVX__) - #define MPT_ARCH_X86_3DNOWPREFETCH - #ifndef MPT_ARCH_X86_XSAVE - #define MPT_ARCH_X86_XSAVE - #endif - #define MPT_ARCH_X86_AVX -#endif -#if defined(__AVX2__) - #ifndef MPT_ARCH_X86_XSAVE - #define MPT_ARCH_X86_XSAVE - #endif - #define MPT_ARCH_X86_AVX2 - #define MPT_ARCH_X86_FMA - #define MPT_ARCH_X86_BMI1 -#endif - -#elif MPT_COMPILER_GCC || MPT_COMPILER_CLANG - -#define MPT_ARCH_X86_I386 -#if !defined(_SOFT_FLOAT) - #define MPT_ARCH_X86_FPU - // GCC does not provide a macro for FSIN. Deduce it from 486 later. -#endif -#if defined(__i486__) - // GCC does not consistently provide i486, deduce it later from cpuid. - #define MPT_ARCH_X86_I486 -#endif -#ifdef __GCC_HAVE_SYNC_COMPARE_AND_SWAP_8 - // GCC does not provide TSC or CPUID. - // Imply it by CX8. - #define MPT_ARCH_X86_CX8 - #define MPT_ARCH_X86_TSC - #define MPT_ARCH_X86_CPUID -#endif -#if defined(__i686__) || defined(__athlon__) - // GCC is broken here and does not set __i686__ for various non-Intel and even modern Intel CPUs - // Imply __i686__ by __SSE__ as a work-around. - #define MPT_ARCH_X86_CMOV -#endif -#if defined(MPT_ARCH_X86_CPUID) - #ifndef MPT_ARCH_X86_I486 - #define MPT_ARCH_X86_I486 - #endif -#endif -#if defined(MPT_ARCH_X86_I486) && defined(MPT_ARCH_X86_FPU) - #define MPT_ARCH_X86_FSIN -#endif -#ifdef __MMX__ - #define MPT_ARCH_X86_MMX -#endif -#ifdef __3dNOW__ - #define MPT_ARCH_X86_3DNOW -#endif -#ifdef __3dNOW_A__ - #define MPT_ARCH_X86_MMXEXT - #define MPT_ARCH_X86_3DNOWEXT -#endif -#ifdef __PRFCHW__ - #define MPT_ARCH_X86_3DNOWPREFETCH -#endif -#ifdef __FXSR__ - #define MPT_ARCH_X86_FXSR -#endif -#ifdef __SSE__ - #ifndef MPT_ARCH_X86_MMXEXT - #define MPT_ARCH_X86_MMXEXT - #endif - #define MPT_ARCH_X86_SSE - #ifndef MPT_ARCH_X86_CMOV - #define MPT_ARCH_X86_CMOV - #endif -#endif -#ifdef __SSE2__ - #define MPT_ARCH_X86_SSE2 -#endif -#ifdef __SSE3__ - #define MPT_ARCH_X86_SSE3 -#endif -#ifdef __SSSE3__ - #define MPT_ARCH_X86_SSSE3 -#endif -#ifdef __SSE4_1__ - #define MPT_ARCH_X86_SSE4_1 -#endif -#ifdef __SSE4_2__ - #define MPT_ARCH_X86_SSE4_2 -#endif -#ifdef __XSAVE__ - #define MPT_ARCH_X86_XSAVE -#endif -#ifdef __AVX__ - #define MPT_ARCH_X86_AVX -#endif -#ifdef __AVX2__ - #define MPT_ARCH_X86_AVX2 -#endif -#ifdef __GCC_HAVE_SYNC_COMPARE_AND_SWAP_16 - #define MPT_ARCH_X86_CX16 -#endif -#ifdef __LAHF_SAHF__ - #define MPT_ARCH_X86_LAHF -#endif -#ifdef __POPCNT__ - #define MPT_ARCH_X86_POPCNT -#endif -#ifdef __BMI__ - #define MPT_ARCH_X86_BMI1 -#endif -#ifdef __BMI2__ - #define MPT_ARCH_X86_BMI2 -#endif -#ifdef __F16C__ - #define MPT_ARCH_X86_F16C -#endif -#ifdef __FMA__ - #define MPT_ARCH_X86_FMA -#endif -#ifdef __LZCNT__ - #define MPT_ARCH_X86_LZCNT -#endif -#ifdef __MOVBE__ - #define MPT_ARCH_X86_MOVBE -#endif - -#endif // MPT_COMPILER - -#endif // MPT_ARCH - -// clang-format on - - - namespace mpt { inline namespace MPT_INLINE_NS { @@ -414,6 +215,9 @@ enum class vendor : uint8 { #ifdef MPT_ARCH_X86_FSIN flags |= feature::fsin; #endif + #ifdef MPT_ARCH_X86_I486 + flags |= feature::intel486; + #endif #ifdef MPT_ARCH_X86_CPUID flags |= feature::cpuid; #endif diff --git a/Frameworks/OpenMPT/OpenMPT/src/mpt/audio/sample.hpp b/Frameworks/OpenMPT/OpenMPT/src/mpt/audio/sample.hpp index ef347e7ec..f8e670dd6 100644 --- a/Frameworks/OpenMPT/OpenMPT/src/mpt/audio/sample.hpp +++ b/Frameworks/OpenMPT/OpenMPT/src/mpt/audio/sample.hpp @@ -5,7 +5,7 @@ -#include "mpt/base/floatingpoint.hpp" +#include "mpt/base/float.hpp" #include "mpt/base/integer.hpp" #include "mpt/base/namespace.hpp" diff --git a/Frameworks/OpenMPT/OpenMPT/src/mpt/base/algorithm.hpp b/Frameworks/OpenMPT/OpenMPT/src/mpt/base/algorithm.hpp index 5b1d2cff6..c183f714e 100644 --- a/Frameworks/OpenMPT/OpenMPT/src/mpt/base/algorithm.hpp +++ b/Frameworks/OpenMPT/OpenMPT/src/mpt/base/algorithm.hpp @@ -11,7 +11,9 @@ #include "mpt/base/saturate_cast.hpp" #include +#include #include +#include @@ -47,11 +49,57 @@ constexpr bool is_in_range(const T & val, const C & lo, const C & hi) { } +namespace detail { +namespace contains { + +template +struct has_find : std::false_type { +}; + +template +struct has_find { +private: + template + static constexpr inline auto check(T *) -> typename std::is_same().find(std::declval()...)), Ret>::type { + return {}; + } + template + static constexpr inline std::false_type check(...) { + return {}; + } +public: + typedef decltype(check(nullptr)) type; + static constexpr inline bool value = type::value; +}; + template -MPT_CONSTEXPR20_FUN bool contains(const Tcontainer & container, const Tval & value) noexcept(noexcept(std::find(std::begin(container), std::end(container), value))) { +MPT_CONSTEXPR20_FUN bool contains_class_find_impl(const Tcontainer & container, const Tval & value, std::true_type) noexcept(noexcept(container.find(value) != container.end())) { + return container.find(value) != container.end(); +} + +template +MPT_CONSTEXPR20_FUN bool contains_class_find_impl(const Tcontainer & container, const Tval & value, std::false_type) noexcept(noexcept(std::find(std::begin(container), std::end(container), value))) { return std::find(std::begin(container), std::end(container), value) != std::end(container); } +template +MPT_CONSTEXPR20_FUN bool contains_class_impl(const Tcontainer & container, const Tval & value, std::true_type) noexcept(noexcept(mpt::detail::contains::contains_class_find_impl(container, value, typename mpt::detail::contains::has_find::type{}))) { + return mpt::detail::contains::contains_class_find_impl(container, value, typename mpt::detail::contains::has_find::type{}); +} + +template +MPT_CONSTEXPR20_FUN bool contains_class_impl(const Tcontainer & container, const Tval & value, std::false_type) noexcept(noexcept(std::find(std::begin(container), std::end(container), value))) { + return std::find(std::begin(container), std::end(container), value) != std::end(container); +} + +} // namespace contains +} // namespace detail + +template +MPT_CONSTEXPR20_FUN bool contains(const Tcontainer & container, const Tval & value) noexcept(noexcept(mpt::detail::contains::contains_class_impl(container, value, typename std::is_class::type{}))) { + return mpt::detail::contains::contains_class_impl(container, value, typename std::is_class::type{}); +} + } // namespace MPT_INLINE_NS } // namespace mpt diff --git a/Frameworks/OpenMPT/OpenMPT/src/mpt/base/alloc.hpp b/Frameworks/OpenMPT/OpenMPT/src/mpt/base/alloc.hpp index 1eddf9f89..a155dafa7 100644 --- a/Frameworks/OpenMPT/OpenMPT/src/mpt/base/alloc.hpp +++ b/Frameworks/OpenMPT/OpenMPT/src/mpt/base/alloc.hpp @@ -174,6 +174,12 @@ public: T * operator->() { return m_value.get(); } + const T * get() const { + return m_value.get(); + } + T * get() { + return m_value.get(); + } }; diff --git a/Frameworks/OpenMPT/OpenMPT/src/mpt/base/array.hpp b/Frameworks/OpenMPT/OpenMPT/src/mpt/base/array.hpp index 255aff46d..9f8c9cea3 100644 --- a/Frameworks/OpenMPT/OpenMPT/src/mpt/base/array.hpp +++ b/Frameworks/OpenMPT/OpenMPT/src/mpt/base/array.hpp @@ -74,6 +74,16 @@ constexpr std::array init_array(const Tx & x) { } +template +constexpr std::array generate_array(Fgen generator) { + std::array result{}; + for (std::size_t i = 0; i < N; ++i) { + result[i] = generator(i); + } + return result; +} + + } // namespace MPT_INLINE_NS } // namespace mpt diff --git a/Frameworks/OpenMPT/OpenMPT/src/mpt/base/bit.hpp b/Frameworks/OpenMPT/OpenMPT/src/mpt/base/bit.hpp index 07ec7f980..5c634821c 100644 --- a/Frameworks/OpenMPT/OpenMPT/src/mpt/base/bit.hpp +++ b/Frameworks/OpenMPT/OpenMPT/src/mpt/base/bit.hpp @@ -19,7 +19,7 @@ #if MPT_CXX_AT_LEAST(20) #include #endif // C++20 -#if MPT_CXX_BEFORE(23) || MPT_COMPILER_MSVC || MPT_LIBCXX_GNU_BEFORE(10) || MPT_LIBCXX_LLVM_BEFORE(12000) +#if MPT_CXX_BEFORE(23) || MPT_COMPILER_MSVC || MPT_LIBCXX_GNU_BEFORE(12) || MPT_LIBCXX_LLVM_BEFORE(14000) #include #endif // !C++23 #include @@ -356,7 +356,7 @@ constexpr T rotr(T x, int s) noexcept { -#if MPT_CXX_AT_LEAST(23) && !MPT_COMPILER_MSVC +#if MPT_CXX_AT_LEAST(23) && !MPT_LIBCXX_GNU_BEFORE(12) && !MPT_LIBCXX_LLVM_BEFORE(14000) && !MPT_MSVC_BEFORE(2022, 1) using std::byteswap; diff --git a/Frameworks/OpenMPT/OpenMPT/src/mpt/base/debugging.hpp b/Frameworks/OpenMPT/OpenMPT/src/mpt/base/debugging.hpp new file mode 100644 index 000000000..fd059f5d4 --- /dev/null +++ b/Frameworks/OpenMPT/OpenMPT/src/mpt/base/debugging.hpp @@ -0,0 +1,228 @@ +/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */ + +#ifndef MPT_BASE_DEBUGGING_HPP +#define MPT_BASE_DEBUGGING_HPP + + + +#include "mpt/base/detect.hpp" +#include "mpt/base/integer.hpp" +#include "mpt/base/namespace.hpp" +#include "mpt/base/macros.hpp" + +#if MPT_CXX_AT_LEAST(26) +#include +#endif // C++26 + +#if MPT_CXX_BEFORE(26) +#if MPT_OS_LINUX +#include +#include +#endif +#endif // !C++26 + +#if MPT_CXX_BEFORE(26) +#if MPT_OS_LINUX +#include +#include +#include +#endif +#endif // !C++26 + +#if MPT_CXX_BEFORE(26) +#if MPT_OS_WINDOWS +#include +#endif +#endif // !C++26 + +#if MPT_CXX_BEFORE(26) +#if MPT_COMPILER_MSVC +#include +#endif +#endif // !C++26 + + + +namespace mpt { +inline namespace MPT_INLINE_NS { + + + +#if MPT_CXX_AT_LEAST(26) + +using std::breakpoint; +using std::breakpoint_if_debugging; +using std::is_debugger_present; + +#else // !C++26 + +#if MPT_OS_WINDOWS + +inline bool is_debugger_present() noexcept { + return (IsDebuggerPresent() != FALSE); +} + +MPT_FORCEINLINE void breakpoint() noexcept { +#if MPT_COMPILER_MSVC + __debugbreak(); +#elif MPT_COMPILER_CLANG + __builtin_debugtrap(); +#elif MPT_COMPILER_GCC && (MPT_ARCH_X86 || MPT_ARCH_AMD64) + __asm__ __volatile__("int 3"); +#else + DebugBreak(); +#endif +} + +#elif MPT_OS_LINUX + +namespace detail { +namespace debugging { + +inline bool parse_proc_status_line(char * buf, std::size_t size) noexcept { + if (std::strncmp(buf, "TracerPid:\t", 11) != 0) { + return false; + } + unsigned long long pid = 0; + std::size_t pos = 11; + while (pos < size) { + unsigned char byte = static_cast(buf[pos]); + if (!(static_cast('0') <= byte && byte <= static_cast('9'))) { + return false; + } + uint8 digit = static_cast(byte - static_cast('0')); + pid = (pid * 10) + digit; + pos++; + } + return (pid != 0); +} + +inline bool parse_proc_status() noexcept { + int olderrno = errno; + bool detected_debugger = false; + int error = 0; + int fd = -1; + bool open_done = false; + while (!open_done) { + int res = open("/proc/self/status", O_RDONLY); + if (res >= 0) { + fd = res; + open_done = true; + } else if (errno != EINTR) { + error = errno; + open_done = true; + } + } + if (error != 0) { + errno = olderrno; + return false; + } + if (fd < 0) { + errno = olderrno; + return false; + } + bool eof = false; + uint8 iobuf[1024]; + std::size_t iobuf_size = 0; + char linebuf[128]; + std::size_t linebuf_size = 0; + while (!eof) { + ssize_t bytes_read = read(fd, iobuf, 1024); + if (bytes_read == -1) { + if (errno == EINTR) { + continue; + } + } else if (bytes_read == 0) { + eof = true; + } + iobuf_size = static_cast(bytes_read); + for (std::size_t i = 0; i < iobuf_size; ++i) { + if (static_cast(iobuf[i]) == static_cast('\n')) { + if (parse_proc_status_line(linebuf, linebuf_size)) { + detected_debugger = true; + } + linebuf_size = 0; + } else { + if (linebuf_size < 128) { + linebuf[linebuf_size] = static_cast(static_cast(iobuf[i])); + linebuf_size++; + } + } + } + if (linebuf_size > 0) { + if (parse_proc_status_line(linebuf, linebuf_size)) { + detected_debugger = true; + } + linebuf_size = 0; + } + } + bool close_done = false; + while (!close_done) { + int res = close(fd); + if (res == 0) { + fd = -1; + close_done = true; + } else if (errno != EINTR) { + error = errno; + close_done = true; + } + } + if (error != 0) { + errno = olderrno; + return false; + } + errno = olderrno; + return detected_debugger; +} + +} // namespace debugging +} // namespace detail + +inline MPT_NOINLINE bool is_debugger_present() noexcept { + return mpt::detail::debugging::parse_proc_status(); +} + +MPT_FORCEINLINE void breakpoint() noexcept { +#if MPT_COMPILER_CLANG + __builtin_debugtrap(); +#elif MPT_COMPILER_GCC && (MPT_ARCH_X86 || MPT_ARCH_AMD64) + __asm__ __volatile__("int 3"); +#else + kill(getpid(), SIGTRAP); +#endif +} + +#else + +inline bool is_debugger_present() noexcept { + return false; +} + +MPT_FORCEINLINE void breakpoint() noexcept { +#if MPT_COMPILER_MSVC + __debugbreak(); +#elif MPT_COMPILER_CLANG + __builtin_debugtrap(); +#elif MPT_COMPILER_GCC && (MPT_ARCH_X86 || MPT_ARCH_AMD64) + __asm__ __volatile__("int 3"); +#endif +} + +#endif + +MPT_FORCEINLINE void breakpoint_if_debugging() noexcept { + if (mpt::is_debugger_present()) { + mpt::breakpoint(); + } +} + +#endif // C++26 + + + +} // namespace MPT_INLINE_NS +} // namespace mpt + + + +#endif // MPT_BASE_DEBUGGING_HPP diff --git a/Frameworks/OpenMPT/OpenMPT/src/mpt/base/detect_arch.hpp b/Frameworks/OpenMPT/OpenMPT/src/mpt/base/detect_arch.hpp index f4192a4ba..ed8b6b784 100644 --- a/Frameworks/OpenMPT/OpenMPT/src/mpt/base/detect_arch.hpp +++ b/Frameworks/OpenMPT/OpenMPT/src/mpt/base/detect_arch.hpp @@ -166,4 +166,355 @@ +// compiler assumed instruction set support + +// clang-format off + +#if MPT_ARCH_X86 || MPT_ARCH_AMD64 + +#if MPT_COMPILER_MSVC + +#if defined(_M_X64) + #define MPT_ARCH_X86_I386 + #define MPT_ARCH_X86_FPU + #define MPT_ARCH_X86_FSIN + #define MPT_ARCH_X86_I486 + #define MPT_ARCH_X86_CPUID + #define MPT_ARCH_X86_TSC + #define MPT_ARCH_X86_CX8 + #define MPT_ARCH_X86_CMOV + #define MPT_ARCH_X86_MMX + #define MPT_ARCH_X86_MMXEXT + #define MPT_ARCH_X86_FXSR + #define MPT_ARCH_X86_SSE + #define MPT_ARCH_X86_SSE2 +#elif defined(_M_IX86) && defined(_M_IX86_FP) + #if (_M_IX86_FP >= 2) + #define MPT_ARCH_X86_I386 + #define MPT_ARCH_X86_FPU + #define MPT_ARCH_X86_FSIN + #define MPT_ARCH_X86_I486 + #define MPT_ARCH_X86_CPUID + #define MPT_ARCH_X86_TSC + #define MPT_ARCH_X86_CX8 + #define MPT_ARCH_X86_CMOV + #define MPT_ARCH_X86_MMX + #define MPT_ARCH_X86_MMXEXT + #define MPT_ARCH_X86_FXSR + #define MPT_ARCH_X86_SSE + #define MPT_ARCH_X86_SSE2 + #elif (_M_IX86_FP == 1) + #define MPT_ARCH_X86_I386 + #define MPT_ARCH_X86_FPU + #define MPT_ARCH_X86_FSIN + #define MPT_ARCH_X86_I486 + #define MPT_ARCH_X86_CPUID + #define MPT_ARCH_X86_TSC + #define MPT_ARCH_X86_CX8 + #define MPT_ARCH_X86_CMOV + #define MPT_ARCH_X86_MMX + #define MPT_ARCH_X86_MMXEXT + #define MPT_ARCH_X86_FXSR + #define MPT_ARCH_X86_SSE + #elif MPT_MSVC_AT_LEAST(2008, 0) + #define MPT_ARCH_X86_I386 + #define MPT_ARCH_X86_FPU + #define MPT_ARCH_X86_FSIN + #define MPT_ARCH_X86_I486 + #define MPT_ARCH_X86_CPUID + #define MPT_ARCH_X86_TSC + #define MPT_ARCH_X86_CX8 + #elif MPT_MSVC_AT_LEAST(2005, 0) + #define MPT_ARCH_X86_I386 + #define MPT_ARCH_X86_FPU + #define MPT_ARCH_X86_FSIN + #define MPT_ARCH_X86_I486 + #elif MPT_MSVC_AT_LEAST(1998, 0) + #define MPT_ARCH_X86_I386 + #define MPT_ARCH_X86_FPU + #define MPT_ARCH_X86_FSIN + #else + #define MPT_ARCH_X86_I386 + #endif +#endif +#if defined(__AVX__) + #define MPT_ARCH_X86_3DNOWPREFETCH + #ifndef MPT_ARCH_X86_XSAVE + #define MPT_ARCH_X86_XSAVE + #endif + #define MPT_ARCH_X86_AVX +#endif +#if defined(__AVX2__) + #ifndef MPT_ARCH_X86_XSAVE + #define MPT_ARCH_X86_XSAVE + #endif + #define MPT_ARCH_X86_AVX2 + #define MPT_ARCH_X86_FMA + #define MPT_ARCH_X86_BMI1 +#endif + +#elif MPT_COMPILER_GCC || MPT_COMPILER_CLANG + +#define MPT_ARCH_X86_I386 +#if !defined(_SOFT_FLOAT) + #define MPT_ARCH_X86_FPU + // GCC does not provide a macro for FSIN. Deduce it from 486 later. +#endif +#if defined(__i486__) + // GCC does not consistently provide i486, deduce it later from cpuid. + #define MPT_ARCH_X86_I486 +#endif +#ifdef __GCC_HAVE_SYNC_COMPARE_AND_SWAP_8 + // GCC does not provide TSC or CPUID. + // Imply it by CX8. + #define MPT_ARCH_X86_CX8 + #define MPT_ARCH_X86_TSC + #define MPT_ARCH_X86_CPUID +#endif +#if defined(__i686__) || defined(__athlon__) + // GCC is broken here and does not set __i686__ for various non-Intel and even modern Intel CPUs + // Imply __i686__ by __SSE__ as a work-around. + #define MPT_ARCH_X86_CMOV +#endif +#if defined(MPT_ARCH_X86_CPUID) + #ifndef MPT_ARCH_X86_I486 + #define MPT_ARCH_X86_I486 + #endif +#endif +#if defined(MPT_ARCH_X86_I486) && defined(MPT_ARCH_X86_FPU) + #define MPT_ARCH_X86_FSIN +#endif +#ifdef __MMX__ + #define MPT_ARCH_X86_MMX +#endif +#ifdef __3dNOW__ + #define MPT_ARCH_X86_3DNOW +#endif +#ifdef __3dNOW_A__ + #define MPT_ARCH_X86_MMXEXT + #define MPT_ARCH_X86_3DNOWEXT +#endif +#ifdef __PRFCHW__ + #define MPT_ARCH_X86_3DNOWPREFETCH +#endif +#ifdef __FXSR__ + #define MPT_ARCH_X86_FXSR +#endif +#ifdef __SSE__ + #ifndef MPT_ARCH_X86_MMXEXT + #define MPT_ARCH_X86_MMXEXT + #endif + #define MPT_ARCH_X86_SSE + #ifndef MPT_ARCH_X86_CMOV + #define MPT_ARCH_X86_CMOV + #endif +#endif +#ifdef __SSE2__ + #define MPT_ARCH_X86_SSE2 +#endif +#ifdef __SSE3__ + #define MPT_ARCH_X86_SSE3 +#endif +#ifdef __SSSE3__ + #define MPT_ARCH_X86_SSSE3 +#endif +#ifdef __SSE4_1__ + #define MPT_ARCH_X86_SSE4_1 +#endif +#ifdef __SSE4_2__ + #define MPT_ARCH_X86_SSE4_2 +#endif +#ifdef __XSAVE__ + #define MPT_ARCH_X86_XSAVE +#endif +#ifdef __AVX__ + #define MPT_ARCH_X86_AVX +#endif +#ifdef __AVX2__ + #define MPT_ARCH_X86_AVX2 +#endif +#ifdef __GCC_HAVE_SYNC_COMPARE_AND_SWAP_16 + #define MPT_ARCH_X86_CX16 +#endif +#ifdef __LAHF_SAHF__ + #define MPT_ARCH_X86_LAHF +#endif +#ifdef __POPCNT__ + #define MPT_ARCH_X86_POPCNT +#endif +#ifdef __BMI__ + #define MPT_ARCH_X86_BMI1 +#endif +#ifdef __BMI2__ + #define MPT_ARCH_X86_BMI2 +#endif +#ifdef __F16C__ + #define MPT_ARCH_X86_F16C +#endif +#ifdef __FMA__ + #define MPT_ARCH_X86_FMA +#endif +#ifdef __LZCNT__ + #define MPT_ARCH_X86_LZCNT +#endif +#ifdef __MOVBE__ + #define MPT_ARCH_X86_MOVBE +#endif + +#endif // MPT_COMPILER + +#endif // MPT_ARCH + +// clang-format on + + + +// compiler supported instrinsics + +// clang-format off + +#if MPT_ARCH_X86 || MPT_ARCH_AMD64 + +#if MPT_COMPILER_MSVC + +#define MPT_ARCH_INTRINSICS_X86_I386 +#define MPT_ARCH_INTRINSICS_X86_FPU +#define MPT_ARCH_INTRINSICS_X86_FSIN +#define MPT_ARCH_INTRINSICS_X86_CPUID +#define MPT_ARCH_INTRINSICS_X86_TSC +#define MPT_ARCH_INTRINSICS_X86_CX8 +#define MPT_ARCH_INTRINSICS_X86_CMOV +#define MPT_ARCH_INTRINSICS_X86_MMX +#define MPT_ARCH_INTRINSICS_X86_MMXEXT +#define MPT_ARCH_INTRINSICS_X86_3DNOW +#define MPT_ARCH_INTRINSICS_X86_3DNOWEXT +#define MPT_ARCH_INTRINSICS_X86_3DNOWPREFETCH +#if MPT_MSVC_AT_LEAST(2003, 0) +#define MPT_ARCH_INTRINSICS_X86_FXSR +#define MPT_ARCH_INTRINSICS_X86_SSE +#define MPT_ARCH_INTRINSICS_X86_SSE2 +#endif +#if MPT_MSVC_AT_LEAST(2008, 0) +#define MPT_ARCH_INTRINSICS_X86_SSE3 +#define MPT_ARCH_INTRINSICS_X86_SSSE3 +#define MPT_ARCH_INTRINSICS_X86_SSE4_1 +#define MPT_ARCH_INTRINSICS_X86_SSE4_2 +#endif +#if MPT_MSVC_AT_LEAST(2010, 1) +#define MPT_ARCH_INTRINSICS_X86_XSAVE +#define MPT_ARCH_INTRINSICS_X86_AVX +#endif +#if MPT_MSVC_AT_LEAST(2012, 0) +#define MPT_ARCH_INTRINSICS_X86_AVX2 +#define MPT_ARCH_INTRINSICS_X86_FMA +#define MPT_ARCH_INTRINSICS_X86_BMI1 +#endif + +#elif MPT_COMPILER_GCC || MPT_COMPILER_CLANG + +#ifdef MPT_ARCH_X86_I386 +#define MPT_ARCH_INTRINSICS_X86_I386 +#endif +#ifdef MPT_ARCH_X86_FPU +#define MPT_ARCH_INTRINSICS_X86_FPU +#endif +#ifdef MPT_ARCH_X86_FSIN +#define MPT_ARCH_INTRINSICS_X86_FSIN +#endif +#ifdef MPT_ARCH_X86_I486 +#define MPT_ARCH_INTRINSICS_X86_I486 +#endif +#ifdef MPT_ARCH_X86_CPUID +#define MPT_ARCH_INTRINSICS_X86_CPUID +#endif +#ifdef MPT_ARCH_X86_TSC +#define MPT_ARCH_INTRINSICS_X86_TSC +#endif +#ifdef MPT_ARCH_X86_CX8 +#define MPT_ARCH_INTRINSICS_X86_CX8 +#endif +#ifdef MPT_ARCH_X86_CMOV +#define MPT_ARCH_INTRINSICS_X86_CMOV +#endif +#ifdef MPT_ARCH_X86_MMX +#define MPT_ARCH_INTRINSICS_X86_MMX +#endif +#ifdef MPT_ARCH_X86_MMXEXT +#define MPT_ARCH_INTRINSICS_X86_MMXEXT +#endif +#ifdef MPT_ARCH_X86_3DNOW +#define MPT_ARCH_INTRINSICS_X86_3DNOW +#endif +#ifdef MPT_ARCH_X86_3DNOWEXT +#define MPT_ARCH_INTRINSICS_X86_3DNOWEXT +#endif +#ifdef MPT_ARCH_X86_3DNOWPREFETCH +#define MPT_ARCH_INTRINSICS_X86_3DNOWPREFETCH +#endif +#ifdef MPT_ARCH_X86_FXSR +#define MPT_ARCH_INTRINSICS_X86_FXSR +#endif +#ifdef MPT_ARCH_X86_SSE +#define MPT_ARCH_INTRINSICS_X86_SSE +#endif +#ifdef MPT_ARCH_X86_SSE2 +#define MPT_ARCH_INTRINSICS_X86_SSE2 +#endif +#ifdef MPT_ARCH_X86_SSE3 +#define MPT_ARCH_INTRINSICS_X86_SSE3 +#endif +#ifdef MPT_ARCH_X86_SSSE3 +#define MPT_ARCH_INTRINSICS_X86_SSSE3 +#endif +#ifdef MPT_ARCH_X86_SSE4_1 +#define MPT_ARCH_INTRINSICS_X86_SSE4_1 +#endif +#ifdef MPT_ARCH_X86_SSE4_2 +#define MPT_ARCH_INTRINSICS_X86_SSE4_2 +#endif +#ifdef MPT_ARCH_X86_XSAVE +#define MPT_ARCH_INTRINSICS_X86_XSAVE +#endif +#ifdef MPT_ARCH_X86_AVX +#define MPT_ARCH_INTRINSICS_X86_AVX +#endif +#ifdef MPT_ARCH_X86_AVX2 +#define MPT_ARCH_INTRINSICS_X86_AVX2 +#endif +#ifdef MPT_ARCH_X86_CX16 +#define MPT_ARCH_INTRINSICS_X86_CX16 +#endif +#ifdef MPT_ARCH_X86_LAHF +#define MPT_ARCH_INTRINSICS_X86_LAHF +#endif +#ifdef MPT_ARCH_X86_POPCNT +#define MPT_ARCH_INTRINSICS_X86_POPCNT +#endif +#ifdef MPT_ARCH_X86_BMI1 +#define MPT_ARCH_INTRINSICS_X86_BMI1 +#endif +#ifdef MPT_ARCH_X86_BMI2 +#define MPT_ARCH_INTRINSICS_X86_BMI2 +#endif +#ifdef MPT_ARCH_X86_F16C +#define MPT_ARCH_INTRINSICS_X86_F16C +#endif +#ifdef MPT_ARCH_X86_FMA +#define MPT_ARCH_INTRINSICS_X86_FMA +#endif +#ifdef MPT_ARCH_X86_LZCNT +#define MPT_ARCH_INTRINSICS_X86_LZCNT +#endif +#ifdef MPT_ARCH_X86_MOVBE +#define MPT_ARCH_INTRINSICS_X86_MOVBE +#endif + +#endif // MPT_COMPILER + +#endif // MPT_ARCH + +// clang-format on + + + #endif // MPT_BASE_DETECT_ARCH_HPP diff --git a/Frameworks/OpenMPT/OpenMPT/src/mpt/base/detect_compiler.hpp b/Frameworks/OpenMPT/OpenMPT/src/mpt/base/detect_compiler.hpp index e5613bf17..00a627c09 100644 --- a/Frameworks/OpenMPT/OpenMPT/src/mpt/base/detect_compiler.hpp +++ b/Frameworks/OpenMPT/OpenMPT/src/mpt/base/detect_compiler.hpp @@ -50,7 +50,11 @@ #elif defined(_MSC_VER) #define MPT_COMPILER_MSVC 1 -#if (_MSC_VER >= 1942) +#if (_MSC_VER >= 1944) +#define MPT_COMPILER_MSVC_VERSION MPT_COMPILER_MAKE_VERSION2(2022, 14) +#elif (_MSC_VER >= 1943) +#define MPT_COMPILER_MSVC_VERSION MPT_COMPILER_MAKE_VERSION2(2022, 13) +#elif (_MSC_VER >= 1942) #define MPT_COMPILER_MSVC_VERSION MPT_COMPILER_MAKE_VERSION2(2022, 12) #elif (_MSC_VER >= 1941) #define MPT_COMPILER_MSVC_VERSION MPT_COMPILER_MAKE_VERSION2(2022, 11) @@ -167,9 +171,21 @@ -#if MPT_COMPILER_GENERIC || MPT_COMPILER_GCC || MPT_COMPILER_CLANG +#if MPT_COMPILER_GENERIC -#if (__cplusplus >= 202002) +#if (__cplusplus >= 202302) +#define MPT_CXX 23 +#elif (__cplusplus >= 202002) +#define MPT_CXX 20 +#elif (__cplusplus >= 201703) +#define MPT_CXX 17 +#endif + +#elif MPT_COMPILER_GCC || MPT_COMPILER_CLANG + +#if (__cplusplus >= 202302) +#define MPT_CXX 23 +#elif (__cplusplus >= 202002) #if defined(__APPLE__) && MPT_CLANG_BEFORE(13, 0, 0) // XCode 12.5 has a really weird mix of Clang and libc++. Just black-list C++20 support for XCode <= 12. #define MPT_CXX 17 @@ -178,24 +194,25 @@ #endif #elif (__cplusplus >= 201703) #define MPT_CXX 17 -#else -#define MPT_CXX 17 #endif #elif MPT_COMPILER_MSVC -#if MPT_MSVC_AT_LEAST(2019, 10) && (_MSVC_LANG >= 201705) +#if MPT_MSVC_AT_LEAST(2015, 3) +#if (_MSVC_LANG >= 202302) +#define MPT_CXX 23 +#elif (_MSVC_LANG >= 202002) #define MPT_CXX 20 #elif (_MSVC_LANG >= 201703) #define MPT_CXX 17 -#else -#define MPT_CXX 17 +#endif #endif -#else +#endif +// default to C++17 +#ifndef MPT_CXX #define MPT_CXX 17 - #endif // MPT_CXX is stricter than just using __cplusplus directly. diff --git a/Frameworks/OpenMPT/OpenMPT/src/mpt/base/detect_libc.hpp b/Frameworks/OpenMPT/OpenMPT/src/mpt/base/detect_libc.hpp index da6fc93ef..b5f12d401 100644 --- a/Frameworks/OpenMPT/OpenMPT/src/mpt/base/detect_libc.hpp +++ b/Frameworks/OpenMPT/OpenMPT/src/mpt/base/detect_libc.hpp @@ -12,15 +12,142 @@ +// Version numbers try to follow MinGW32 +// , +// and MinGW-w64 version numbers are adjusted for +// , +#define MPT_LIBC_MS_VER_CRTDLL 0x0000 +#define MPT_LIBC_MS_VER_MSVCR10 0x0100 +#define MPT_LIBC_MS_VER_MSVCR20 0x0200 +#define MPT_LIBC_MS_VER_MSVCR40 0x0400 +#define MPT_LIBC_MS_VER_MSVCRT40 0x0400 +#define MPT_LIBC_MS_VER_MSVCRT 0x0600 +#define MPT_LIBC_MS_VER_MSVCR60 0x0600 +#define MPT_LIBC_MS_VER_MSVCR61 0x0601 +#define MPT_LIBC_MS_VER_MSVCR70 0x0700 +#define MPT_LIBC_MS_VER_MSVCR71 0x0701 +#define MPT_LIBC_MS_VER_MSVCR80 0x0800 +#define MPT_LIBC_MS_VER_MSVCR90 0x0900 +#define MPT_LIBC_MS_VER_MSVCR100 0x1000 +#define MPT_LIBC_MS_VER_MSVCR110 0x1100 +#define MPT_LIBC_MS_VER_MSVCR120 0x1200 +#define MPT_LIBC_MS_VER_UCRT 0x1400 + + + // order of checks is important! #if MPT_COMPILER_GENERIC #define MPT_LIBC_GENERIC 1 #elif (defined(__MINGW32__) || defined(__MINGW64__)) #define MPT_LIBC_MINGW 1 +#if defined(__MINGW64_VERSION_MAJOR) && defined(__MINGW64_VERSION_MINOR) +#define MPT_LIBC_MINGW_MINGWW64 1 +#define MPT_LIBC_MINGW_MINGWW64_AT_LEAST(major, minor) (((major) > __MINGW64_VERSION_MAJOR) || (((major) == __MINGW64_VERSION_MAJOR) && ((minor) >= __MINGW64_VERSION_MINOR))) +#define MPT_LIBC_MINGW_MINGWW64_BEFORE(major, minor) (((major) < __MINGW64_VERSION_MAJOR) || (((major) == __MINGW64_VERSION_MAJOR) && ((minor) < __MINGW64_VERSION_MINOR))) +#elif defined(__MINGW64_VERSION_MAJOR) +#define MPT_LIBC_MINGW_MINGWW64 1 +#define MPT_LIBC_MINGW_MINGWW64_AT_LEAST(major, minor) ((major) >= __MINGW64_VERSION_MAJOR) +#define MPT_LIBC_MINGW_MINGWW64_BEFORE(major, minor) ((major) < __MINGW64_VERSION_MAJOR) +#elif defined(__MINGW64_VERSION_MINOR) +#define MPT_LIBC_MINGW_MINGWW64 1 +#define MPT_LIBC_MINGW_MINGWW64_AT_LEAST(major, minor) 0 +#define MPT_LIBC_MINGW_MINGWW64_BEFORE(major, minor) 1 +#elif defined(__MINGW32_VERSION_MAJOR) && defined(__MINGW32_VERSION_MINOR) +#define MPT_LIBC_MINGW_MINGW32 1 +#define MPT_LIBC_MINGW_MINGW32_AT_LEAST(major, minor) (((major) > __MINGW32_VERSION_MAJOR) || (((major) == __MINGW32_VERSION_MAJOR) && ((minor) >= __MINGW32_VERSION_MINOR))) +#define MPT_LIBC_MINGW_MINGW32_BEFORE(major, minor) (((major) < __MINGW32_VERSION_MAJOR) || (((major) == __MINGW32_VERSION_MAJOR) && ((minor) < __MINGW32_VERSION_MINOR))) +#elif defined(__MINGW32_VERSION_MAJOR) +#define MPT_LIBC_MINGW_MINGW32 1 +#define MPT_LIBC_MINGW_MINGW32_AT_LEAST(major, minor) ((major) >= __MINGW32_VERSION_MAJOR) +#define MPT_LIBC_MINGW_MINGW32_BEFORE(major, minor) ((major) < __MINGW32_VERSION_MAJOR) +#elif defined(__MINGW32_VERSION_MINOR) +#define MPT_LIBC_MINGW_MINGW32 1 +#define MPT_LIBC_MINGW_MINGW32_AT_LEAST(major, minor) 0 +#define MPT_LIBC_MINGW_MINGW32_BEFORE(major, minor) 1 +#endif +#if defined(MPT_LIBC_MINGW_MINGW32) +#ifdef __MSVCR60_DLL +static_assert(__MSVCR60_DLL == MPT_LIBC_MS_VER_MSVCR60); +#endif +#ifdef __MSVCR61_DLL +static_assert(__MSVCR61_DLL == MPT_LIBC_MS_VER_MSVCR61); +#endif +#ifdef __MSVCR70_DLL +static_assert(__MSVCR70_DLL == MPT_LIBC_MS_VER_MSVCR70); +#endif +#ifdef __MSVCR71_DLL +static_assert(__MSVCR71_DLL == MPT_LIBC_MS_VER_MSVCR71); +#endif +#ifdef __MSVCR80_DLL +static_assert(__MSVCR80_DLL == MPT_LIBC_MS_VER_MSVCR80); +#endif +#ifdef __MSVCR90_DLL +static_assert(__MSVCR90_DLL == MPT_LIBC_MS_VER_MSVCR90); +#endif +#ifdef __MSVCR100_DLL +static_assert(__MSVCR100_DLL == MPT_LIBC_MS_VER_MSVCR100); +#endif +#ifdef __MSVCR110_DLL +static_assert(__MSVCR110_DLL == MPT_LIBC_MS_VER_MSVCR110); +#endif +#ifdef __MSVCR120_DLL +static_assert(__MSVCR120_DLL == MPT_LIBC_MS_VER_MSVCR120); +#endif +#endif +#if defined(_UCRT) +#define MPT_LIBC_MS_UCRT 1 +#define MPT_LIBC_MS_VERSION MPT_LIBC_MS_VER_UCRT +#elif defined(__MSVCRT__) +#define MPT_LIBC_MS_MSVCRT 1 +#if defined(__MSVCRT_VERSION__) +#if defined(MPT_LIBC_MINGW_MINGWW64) +#if (__MSVCRT_VERSION__ > 0x09ff) +// MinGW-w64 completely fucked up version numbers in +// , +// so we need to compensate for that mother of brainfarts. +#define MPT_LIBC_MS_VERSION (__MSVCRT_VERSION__ + (0x1000 - 0x0A00)) +#else +#define MPT_LIBC_MS_VERSION __MSVCRT_VERSION__ +#endif +#define MPT_LIBC_MS_VERSION __MSVCRT_VERSION__ +#elif defined(MPT_LIBC_MINGW_MINGW32) +#define MPT_LIBC_MS_VERSION __MSVCRT_VERSION__ +#else +#define MPT_LIBC_MS_VERSION MPT_LIBC_MS_VER_MSVCRT +#endif +#endif +#elif defined(__CRTDLL__) +#define MPT_LIBC_MS_CRTDLL 1 +#define MPT_LIBC_MS_VERSION MPT_LIBC_MS_VER_CRTDLL +#endif #elif (defined(__GLIBC__) || defined(__GNU_LIBRARY__)) #define MPT_LIBC_GLIBC 1 +#elif defined(_UCRT) +#define MPT_LIBC_MS 1 +#define MPT_LIBC_MS_UCRT 1 +#define MPT_LIBC_MS_VERSION MPT_LIBC_MS_VER_UCRT #elif MPT_COMPILER_MSVC -#define MPT_LIBC_MS 1 +#define MPT_LIBC_MS 1 +#define MPT_LIBC_MS_MSVCRT 1 +#if MPT_MSVC_AT_LEAST(2015, 0) +#define MPT_LIBC_MS_VERSION MPT_LIBC_MS_VER_UCRT +#elif MPT_MSVC_AT_LEAST(2013, 0) +#define MPT_LIBC_MS_VERSION MPT_LIBC_MS_VER_MSVCR120 +#elif MPT_MSVC_AT_LEAST(2012, 0) +#define MPT_LIBC_MS_VERSION MPT_LIBC_MS_VER_MSVCR110 +#elif MPT_MSVC_AT_LEAST(2010, 0) +#define MPT_LIBC_MS_VERSION MPT_LIBC_MS_VER_MSVCR100 +#elif MPT_MSVC_AT_LEAST(2008, 0) +#define MPT_LIBC_MS_VERSION MPT_LIBC_MS_VER_MSVCR90 +#elif MPT_MSVC_AT_LEAST(2005, 0) +#define MPT_LIBC_MS_VERSION MPT_LIBC_MS_VER_MSVCR80 +#elif MPT_MSVC_AT_LEAST(2003, 0) +#define MPT_LIBC_MS_VERSION MPT_LIBC_MS_VER_MSVCR71 +#elif MPT_MSVC_AT_LEAST(2002, 0) +#define MPT_LIBC_MS_VERSION MPT_LIBC_MS_VER_MSVCR70 +#else +#define MPT_LIBC_MS_VERSION MPT_LIBC_MS_VER_MSVCRT +#endif #elif MPT_COMPILER_CLANG && MPT_OS_WINDOWS #define MPT_LIBC_MS 1 #elif defined(__BIONIC__) @@ -33,6 +160,11 @@ #define MPT_LIBC_GENERIC 1 #endif +#if defined(MPT_LIBC_MS_VERSION) +#define MPT_LIBC_MS_AT_LEAST(v) (MPT_LIBC_MS_VERSION >= (v)) +#define MPT_LIBC_MS_BEFORE(v) (MPT_LIBC_MS_VERSION < (v)) +#endif + #ifndef MPT_LIBC_GENERIC #define MPT_LIBC_GENERIC 0 #endif @@ -42,9 +174,42 @@ #ifndef MPT_LIBC_MINGW #define MPT_LIBC_MINGW 0 #endif +#ifndef MPT_LIBC_MINGW_MINGWW64 +#define MPT_LIBC_MINGW_MINGWW64 0 +#endif +#ifndef MPT_LIBC_MINGW_MINGW32 +#define MPT_LIBC_MINGW_MINGW32 0 +#endif +#ifndef MPT_LIBC_MINGW_MINGWW64_AT_LEAST +#define MPT_LIBC_MINGW_MINGWW64_AT_LEAST(major, minor) 0 +#endif +#ifndef MPT_LIBC_MINGW_MINGWW64_BEFORE +#define MPT_LIBC_MINGW_MINGWW64_BEFORE(major, minor) 0 +#endif +#ifndef MPT_LIBC_MINGW_MINGW32_AT_LEAST +#define MPT_LIBC_MINGW_MINGW32_AT_LEAST(major, minor) 0 +#endif +#ifndef MPT_LIBC_MINGW_MINGW32_BEFORE +#define MPT_LIBC_MINGW_MINGW32_BEFORE(major, minor) 0 +#endif #ifndef MPT_LIBC_MS #define MPT_LIBC_MS 0 #endif +#ifndef MPT_LIBC_MS_UCRT +#define MPT_LIBC_MS_UCRT 0 +#endif +#ifndef MPT_LIBC_MS_MSVCRT +#define MPT_LIBC_MS_MSVCRT 0 +#endif +#ifndef MPT_LIBC_MS_CRTDLL +#define MPT_LIBC_MS_CRTDLL 0 +#endif +#ifndef MPT_LIBC_MS_AT_LEAST +#define MPT_LIBC_MS_AT_LEAST(v) 0 +#endif +#ifndef MPT_LIBC_MS_BEFORE +#define MPT_LIBC_MS_BEFORE(v) 0 +#endif #ifndef MPT_LIBC_BIONIC #define MPT_LIBC_BIONIC 0 #endif diff --git a/Frameworks/OpenMPT/OpenMPT/src/mpt/base/detect_os.hpp b/Frameworks/OpenMPT/OpenMPT/src/mpt/base/detect_os.hpp index 630c518ba..e325d80cf 100644 --- a/Frameworks/OpenMPT/OpenMPT/src/mpt/base/detect_os.hpp +++ b/Frameworks/OpenMPT/OpenMPT/src/mpt/base/detect_os.hpp @@ -85,10 +85,10 @@ // ok #elif (__EMSCRIPTEN_major__ == 3) && (__EMSCRIPTEN_minor__ > 1) // ok -#elif (__EMSCRIPTEN_major__ == 3) && (__EMSCRIPTEN_minor__ == 1) && (__EMSCRIPTEN_tiny__ >= 1) +#elif (__EMSCRIPTEN_major__ == 3) && (__EMSCRIPTEN_minor__ == 1) && (__EMSCRIPTEN_tiny__ >= 51) // ok #else -#error "Emscripten >= 3.1.1 is required." +#error "Emscripten >= 3.1.51 is required." #endif #endif diff --git a/Frameworks/OpenMPT/OpenMPT/src/mpt/base/detect_quirks.hpp b/Frameworks/OpenMPT/OpenMPT/src/mpt/base/detect_quirks.hpp index 7734642ff..713df55b6 100644 --- a/Frameworks/OpenMPT/OpenMPT/src/mpt/base/detect_quirks.hpp +++ b/Frameworks/OpenMPT/OpenMPT/src/mpt/base/detect_quirks.hpp @@ -17,6 +17,17 @@ #include #endif // C++20 +#if MPT_LIBCXX_MS +// for _ITERATOR_DEBUG_LEVEL +#include +#endif // MPT_LIBCXX_MS + + + +#if MPT_OS_DJGPP +#define MPT_ARCH_QUIRK_NO_SIMD256 +#endif + #if MPT_COMPILER_MSVC @@ -235,6 +246,16 @@ +#if MPT_LIBC_MINGW +// MinGW32 runtime headers require __off64_t when including some C and/or C++ stdlib headers. +// This is declared in , which howeger is not included in some header chains. +#if (defined(__MINGW32__) && !defined(__MINGW64__)) +#define MPT_LIBC_QUIRK_REQUIRES_SYS_TYPES_H +#endif +#endif + + + #if MPT_LIBC_DJGPP #define MPT_LIBC_QUIRK_NO_FENV #endif @@ -251,6 +272,42 @@ +#if MPT_OS_WINDOWS && MPT_LIBCXX_GNU +#define MPT_LIBCXX_QUIRK_INCOMPLETE_IS_FUNCTION +#endif + + + +#if MPT_CXX_AT_LEAST(20) +#if MPT_LIBCXX_GNU_BEFORE(10) || MPT_LIBCXX_LLVM_BEFORE(13000) || (MPT_LIBCXX_MS && MPT_MSVC_BEFORE(2022, 0)) || (MPT_LIBCXX_MS && !MPT_COMPILER_MSVC) +#define MPT_LIBCXX_QUIRK_NO_CXX20_CONSTEXPR_ALGORITHM +#endif +#endif + + + +#if MPT_CXX_AT_LEAST(20) +#if MPT_LIBCXX_GNU_BEFORE(12) || MPT_LIBCXX_LLVM_BEFORE(15000) || (MPT_LIBCXX_MS && MPT_MSVC_BEFORE(2022, 0)) || (MPT_LIBCXX_MS && !MPT_COMPILER_MSVC) +#ifndef MPT_LIBCXX_QUIRK_NO_CXX20_CONSTEXPR_CONTAINER +#define MPT_LIBCXX_QUIRK_NO_CXX20_CONSTEXPR_CONTAINER +#endif +#endif +#if MPT_LIBCXX_MS +// So, in 2025, Microsoft still ships a STL that by default is not standard-compliant with its own default Debug options. +// constexpr auto foo = std::vector{}; does not compile with iterator debugging enabled (i.e. in Debug builds). +// See . +#if defined(_ITERATOR_DEBUG_LEVEL) +#if (_ITERATOR_DEBUG_LEVEL >= 1) +#ifndef MPT_LIBCXX_QUIRK_NO_CXX20_CONSTEXPR_CONTAINER +#define MPT_LIBCXX_QUIRK_NO_CXX20_CONSTEXPR_CONTAINER +#endif +#endif +#endif +#endif +#endif + + + #if MPT_CXX_AT_LEAST(20) // Clang 14 is incompatible with libstdc++ 13 in C++20 mode #if MPT_CLANG_BEFORE(15, 0, 0) && MPT_LIBCXX_GNU_AT_LEAST(13) @@ -276,7 +333,7 @@ #elif MPT_LIBCXX_GNU #define MPT_LIBCXX_QUIRK_NO_CHRONO_DATE_PARSE #endif -#if MPT_LIBCXX_MS && (MPT_MSVC_BEFORE(2022, 9) || !MPT_COMPILER_MSVC) +#if MPT_LIBCXX_MS && (MPT_MSVC_BEFORE(2022, 15) || !MPT_COMPILER_MSVC) // Causes massive memory leaks. // See // @@ -284,7 +341,9 @@ #define MPT_LIBCXX_QUIRK_CHRONO_TZ_MEMLEAK #endif #endif - +#if MPT_LIBCXX_GNU_BEFORE(13) +#define MPT_LIBCXX_QUIRK_CHRONO_DATE_NO_ZONED_TIME +#endif #if MPT_MSVC_AT_LEAST(2022, 6) && MPT_MSVC_BEFORE(2022, 7) // std::chrono triggers ICE in VS2022 17.6.0, see . #define MPT_LIBCXX_QUIRK_CHRONO_DATE_BROKEN_ZONED_TIME @@ -332,6 +391,12 @@ +#if MPT_LIBCXX_GNU_BEFORE(13) || (MPT_LIBCXX_MS && !MPT_MSVC_AT_LEAST(2022, 7)) || MPT_LIBCXX_LLVM +#define MPT_LIBCXX_QUIRK_NO_STDFLOAT +#endif + + + #if MPT_OS_MACOSX_OR_IOS #if defined(TARGET_OS_OSX) #if TARGET_OS_OSX diff --git a/Frameworks/OpenMPT/OpenMPT/src/mpt/base/float.hpp b/Frameworks/OpenMPT/OpenMPT/src/mpt/base/float.hpp new file mode 100644 index 000000000..ff5bda90a --- /dev/null +++ b/Frameworks/OpenMPT/OpenMPT/src/mpt/base/float.hpp @@ -0,0 +1,188 @@ +/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */ + +#ifndef MPT_BASE_FLOAT_HPP +#define MPT_BASE_FLOAT_HPP + + + +#include "mpt/base/detect.hpp" +#include "mpt/base/macros.hpp" +#include "mpt/base/namespace.hpp" + +#include +#if MPT_CXX_AT_LEAST(23) && !defined(MPT_LIBCXX_QUIRK_NO_STDFLOAT) +#include +#endif // C++23 +#include + + + +namespace mpt { +inline namespace MPT_INLINE_NS { + + +// fp half +// n/a + +// fp single +using single_float = float; +namespace float_literals { +MPT_CONSTEVAL single_float operator""_fs(long double lit) noexcept { + return static_cast(lit); +} +} // namespace float_literals + +// fp double +using double_float = float; +namespace float_literals { +MPT_CONSTEVAL double_float operator""_fd(long double lit) noexcept { + return static_cast(lit); +} +} // namespace float_literals + +// fp extended +using extended_float = long double; +namespace float_literals { +MPT_CONSTEVAL extended_float operator""_fe(long double lit) noexcept { + return static_cast(lit); +} +} // namespace float_literals + +// fp quad +// n/a + +#if MPT_CXX_AT_LEAST(23) && !defined(MPT_LIBCXX_QUIRK_NO_STDFLOAT) +#if defined(__STDCPP_FLOAT16_T__) +#if (__STDCPP_FLOAT16_T__ == 1) +#define MPT_BASE_STDFLOAT_FLOAT16 +#endif +#endif +#endif +#if MPT_CXX_AT_LEAST(23) && !defined(MPT_LIBCXX_QUIRK_NO_STDFLOAT) +#if defined(__STDCPP_FLOAT32_T__) +#if (__STDCPP_FLOAT32_T__ == 1) +#define MPT_BASE_STDFLOAT_FLOAT32 +#endif +#endif +#endif +#if MPT_CXX_AT_LEAST(23) && !defined(MPT_LIBCXX_QUIRK_NO_STDFLOAT) +#if defined(__STDCPP_FLOAT64_T__) +#if (__STDCPP_FLOAT64_T__ == 1) +#define MPT_BASE_STDFLOAT_FLOAT64 +#endif +#endif +#endif +#if MPT_CXX_AT_LEAST(23) && !defined(MPT_LIBCXX_QUIRK_NO_STDFLOAT) +#if defined(__STDCPP_FLOAT128_T__) +#if (__STDCPP_FLOAT128_T__ == 1) +#define MPT_BASE_STDFLOAT_FLOAT128 +#endif +#endif +#endif + +#if defined(MPT_BASE_STDFLOAT_FLOAT16) +using stdfloat16 = std::float16_t; +#else +using stdfloat16 = std::conditional::type>::type>::type; +#endif + +#if defined(MPT_BASE_STDFLOAT_FLOAT32) +using stdfloat32 = std::float32_t; +#else +using stdfloat32 = std::conditional::type>::type>::type; +#endif + +#if defined(MPT_BASE_STDFLOAT_FLOAT64) +using stdfloat64 = std::float64_t; +#else +using stdfloat64 = std::conditional::type>::type>::type; +#endif + +#if defined(MPT_BASE_STDFLOAT_FLOAT128) +using stdfloat128 = std::float128_t; +#else +using stdfloat128 = std::conditional::type>::type>::type; +#endif + +#undef MPT_BASE_STDFLOAT_FLOAT16 +#undef MPT_BASE_STDFLOAT_FLOAT32 +#undef MPT_BASE_STDFLOAT_FLOAT64 +#undef MPT_BASE_STDFLOAT_FLOAT128 + +namespace float_literals { +MPT_CONSTEVAL stdfloat16 operator""_stdf16(long double lit) noexcept { + return static_cast(lit); +} +MPT_CONSTEVAL stdfloat32 operator""_stdf32(long double lit) noexcept { + return static_cast(lit); +} +MPT_CONSTEVAL stdfloat64 operator""_stdf64(long double lit) noexcept { + return static_cast(lit); +} +MPT_CONSTEVAL stdfloat128 operator""_stdf128(long double lit) noexcept { + return static_cast(lit); +} +} // namespace float_literals + +// fast floating point types of roughly requested size + +using fastfloat32 = std::conditional::type>::type>::type; +namespace float_literals { +MPT_CONSTEVAL fastfloat32 operator""_ff32(long double lit) noexcept { + return static_cast(lit); +} +} // namespace float_literals + +using fastfloat64 = std::conditional::type>::type>::type; +namespace float_literals { +MPT_CONSTEVAL fastfloat64 operator""_ff64(long double lit) noexcept { + return static_cast(lit); +} +} // namespace float_literals + +// floating point type of roughly requested size + +using somefloat32 = std::conditional::type>::type; +namespace float_literals { +MPT_CONSTEVAL somefloat32 operator""_sf32(long double lit) noexcept { + return static_cast(lit); +} +} // namespace float_literals + +using somefloat64 = std::conditional::type>::type; +namespace float_literals { +MPT_CONSTEVAL somefloat64 operator""_sf64(long double lit) noexcept { + return static_cast(lit); +} +} // namespace float_literals + +template +struct float_traits { + static constexpr bool is_float = !std::numeric_limits::is_integer; + static constexpr bool is_hard = is_float && !MPT_COMPILER_QUIRK_FLOAT_EMULATED; + static constexpr bool is_soft = is_float && MPT_COMPILER_QUIRK_FLOAT_EMULATED; + static constexpr bool is_float16 = is_float && (sizeof(T) == 2); + static constexpr bool is_float32 = is_float && (sizeof(T) == 4); + static constexpr bool is_float64 = is_float && (sizeof(T) == 8); + static constexpr bool is_float128 = is_float && (sizeof(T) == 16); + static constexpr bool is_native_endian = is_float && !MPT_COMPILER_QUIRK_FLOAT_NOTNATIVEENDIAN; + static constexpr bool is_ieee754_binary = is_float && std::numeric_limits::is_iec559 && !MPT_COMPILER_QUIRK_FLOAT_NOTIEEE754; + static constexpr bool is_preferred = is_float && ((is_float32 && MPT_COMPILER_QUIRK_FLOAT_PREFER32) || (is_float64 && MPT_COMPILER_QUIRK_FLOAT_PREFER64)); +}; + +// prefer smaller floats, but try to use IEEE754 floats +using nativefloat = + std::conditional::is_preferred, somefloat32, std::conditional::is_preferred, somefloat64, std::conditional::is_iec559, float, std::conditional::is_iec559, double, std::conditional::is_iec559, long double, float>::type>::type>::type>::type>::type; +namespace float_literals { +MPT_CONSTEVAL nativefloat operator""_nf(long double lit) noexcept { + return static_cast(lit); +} +} // namespace float_literals + + +} // namespace MPT_INLINE_NS +} // namespace mpt + + + +#endif // MPT_BASE_FLOAT_HPP diff --git a/Frameworks/OpenMPT/OpenMPT/src/mpt/base/floatingpoint.hpp b/Frameworks/OpenMPT/OpenMPT/src/mpt/base/floatingpoint.hpp deleted file mode 100644 index d5df6cb64..000000000 --- a/Frameworks/OpenMPT/OpenMPT/src/mpt/base/floatingpoint.hpp +++ /dev/null @@ -1,93 +0,0 @@ -/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */ - -#ifndef MPT_BASE_FLOATINGPOINT_HPP -#define MPT_BASE_FLOATINGPOINT_HPP - - - -#include "mpt/base/detect.hpp" -#include "mpt/base/namespace.hpp" - -#include -#include - - - -namespace mpt { -inline namespace MPT_INLINE_NS { - - -// fp half -// n/a - -// fp single -using single = float; -namespace float_literals { -constexpr single operator""_fs(long double lit) noexcept { - return static_cast(lit); -} -} // namespace float_literals - -// fp double -namespace float_literals { -constexpr double operator""_fd(long double lit) noexcept { - return static_cast(lit); -} -} // namespace float_literals - -// fp extended -namespace float_literals { -constexpr long double operator""_fe(long double lit) noexcept { - return static_cast(lit); -} -} // namespace float_literals - -// fp quad -// n/a - -using float32 = std::conditional::type>::type>::type; -namespace float_literals { -constexpr float32 operator""_f32(long double lit) noexcept { - return static_cast(lit); -} -} // namespace float_literals - -using float64 = std::conditional::type>::type>::type; -namespace float_literals { -constexpr float64 operator""_f64(long double lit) noexcept { - return static_cast(lit); -} -} // namespace float_literals - -template -struct float_traits { - static constexpr bool is_float = !std::numeric_limits::is_integer; - static constexpr bool is_hard = is_float && !MPT_COMPILER_QUIRK_FLOAT_EMULATED; - static constexpr bool is_soft = is_float && MPT_COMPILER_QUIRK_FLOAT_EMULATED; - static constexpr bool is_float32 = is_float && (sizeof(T) == 4); - static constexpr bool is_float64 = is_float && (sizeof(T) == 8); - static constexpr bool is_native_endian = is_float && !MPT_COMPILER_QUIRK_FLOAT_NOTNATIVEENDIAN; - static constexpr bool is_ieee754_binary = is_float && std::numeric_limits::is_iec559 && !MPT_COMPILER_QUIRK_FLOAT_NOTIEEE754; - static constexpr bool is_ieee754_binary32 = is_float && is_ieee754_binary && is_float32; - static constexpr bool is_ieee754_binary64 = is_float && is_ieee754_binary && is_float64; - static constexpr bool is_ieee754_binary32ne = is_float && is_ieee754_binary && is_float32 && is_native_endian; - static constexpr bool is_ieee754_binary64ne = is_float && is_ieee754_binary && is_float64 && is_native_endian; - static constexpr bool is_preferred = is_float && ((is_float32 && MPT_COMPILER_QUIRK_FLOAT_PREFER32) || (is_float64 && MPT_COMPILER_QUIRK_FLOAT_PREFER64)); -}; - -// prefer smaller floats, but try to use IEEE754 floats -using nativefloat = - std::conditional::is_preferred, float32, std::conditional::is_preferred, float64, std::conditional::is_iec559, float, std::conditional::is_iec559, double, std::conditional::is_iec559, long double, float>::type>::type>::type>::type>::type; -namespace float_literals { -constexpr nativefloat operator""_nf(long double lit) noexcept { - return static_cast(lit); -} -} // namespace float_literals - - -} // namespace MPT_INLINE_NS -} // namespace mpt - - - -#endif // MPT_BASE_FLOATINGPOINT_HPP diff --git a/Frameworks/OpenMPT/OpenMPT/src/mpt/base/macros.hpp b/Frameworks/OpenMPT/OpenMPT/src/mpt/base/macros.hpp index db9e2a4a4..f78408b72 100644 --- a/Frameworks/OpenMPT/OpenMPT/src/mpt/base/macros.hpp +++ b/Frameworks/OpenMPT/OpenMPT/src/mpt/base/macros.hpp @@ -30,14 +30,41 @@ // constexpr + #define MPT_CONSTEXPRINLINE constexpr MPT_FORCEINLINE -#if MPT_CXX_AT_LEAST(20) + +#if MPT_CXX_AT_LEAST(23) #define MPT_CONSTEXPR20_FUN constexpr MPT_FORCEINLINE #define MPT_CONSTEXPR20_VAR constexpr -#else // !C++20 +#define MPT_CONSTEXPR23_FUN constexpr MPT_FORCEINLINE +#define MPT_CONSTEXPR23_VAR constexpr +#elif MPT_CXX_AT_LEAST(20) +#define MPT_CONSTEXPR20_FUN constexpr MPT_FORCEINLINE +#define MPT_CONSTEXPR20_VAR constexpr +#define MPT_CONSTEXPR23_FUN MPT_FORCEINLINE +#define MPT_CONSTEXPR23_VAR const +#else // C++ #define MPT_CONSTEXPR20_FUN MPT_FORCEINLINE #define MPT_CONSTEXPR20_VAR const -#endif // C++20 +#define MPT_CONSTEXPR23_FUN MPT_FORCEINLINE +#define MPT_CONSTEXPR23_VAR const +#endif // C++ + +#if !defined(MPT_LIBCXX_QUIRK_NO_CXX20_CONSTEXPR_ALGORITHM) +#define MPT_CONSTEXPR20_ALGORITHM_FUN MPT_CONSTEXPR20_FUN +#define MPT_CONSTEXPR20_ALGORITHM_VAR MPT_CONSTEXPR20_VAR +#else +#define MPT_CONSTEXPR20_ALGORITHM_FUN MPT_CONSTEXPR23_FUN +#define MPT_CONSTEXPR20_ALGORITHM_VAR MPT_CONSTEXPR23_VAR +#endif + +#if !defined(MPT_LIBCXX_QUIRK_NO_CXX20_CONSTEXPR_CONTAINER) +#define MPT_CONSTEXPR20_CONTAINER_FUN MPT_CONSTEXPR20_FUN +#define MPT_CONSTEXPR20_CONTAINER_VAR MPT_CONSTEXPR20_VAR +#else +#define MPT_CONSTEXPR20_CONTAINER_FUN MPT_CONSTEXPR23_FUN +#define MPT_CONSTEXPR20_CONTAINER_VAR MPT_CONSTEXPR23_VAR +#endif @@ -50,6 +77,25 @@ +#if MPT_CXX_AT_LEAST(20) +#define MPT_CONSTEVAL_NOEXCEPT noexcept +#else // !C++20 +#define MPT_CONSTEVAL_NOEXCEPT +#endif // C++20 + + + +#define MPT_FORCE_CONSTEXPR_EXPRESSION(expr) [&]() { \ + constexpr auto x = (expr); \ + return x; \ +}() +#define MPT_FORCE_CONSTEXPR_VALUE(val) []() { \ + constexpr auto x = (val); \ + return x; \ +}() + + + #if MPT_CXX_AT_LEAST(20) // this assumes that for C++20, a consteval function will be used #define MPT_FORCE_CONSTEVAL_EXPRESSION(expr) (expr) @@ -144,4 +190,28 @@ +#if MPT_CXX_AT_LEAST(23) && !MPT_GCC_BEFORE(13, 0, 0) && !MPT_CLANG_BEFORE(19, 0, 0) && !MPT_COMPILER_MSVC +#define MPT_ASSUME(expr) [[assume(expr)]] +#else // !C++23 +#if MPT_COMPILER_CLANG +#define MPT_ASSUME(expr) __builtin_assume(expr) +#endif +#if MPT_COMPILER_MSVC +#define MPT_ASSUME(expr) __assume(expr) +#endif +#if MPT_COMPILER_GCC +#define MPT_ASSUME(expr) \ + do { \ + if (!expr) { \ + __builtin_unreachable(); \ + } \ + } while (0) +#endif +#if !defined(MPT_ASSUME) +#define MPT_ASSUME(expr) MPT_DISCARD(expr) +#endif +#endif // C++23 + + + #endif // MPT_BASE_MACROS_HPP diff --git a/Frameworks/OpenMPT/OpenMPT/src/mpt/base/math.hpp b/Frameworks/OpenMPT/OpenMPT/src/mpt/base/math.hpp index a158ad4f7..062ea8851 100644 --- a/Frameworks/OpenMPT/OpenMPT/src/mpt/base/math.hpp +++ b/Frameworks/OpenMPT/OpenMPT/src/mpt/base/math.hpp @@ -63,6 +63,28 @@ using std::round; #endif // MPT_OS_DJGPP +#if MPT_OS_DJGPP + +inline long double trunc(const long double val) { + return ::truncl(val); +} + +inline double trunc(const double val) { + return ::trunc(val); +} + +inline float trunc(const float val) { + return ::truncf(val); +} + +#else // !MPT_OS_DJGPP + +// C++11 std::trunc +using std::trunc; + +#endif // MPT_OS_DJGPP + + template inline T sanitize_nan(T val) { static_assert(std::is_floating_point::value); diff --git a/Frameworks/OpenMPT/OpenMPT/src/mpt/base/namespace.hpp b/Frameworks/OpenMPT/OpenMPT/src/mpt/base/namespace.hpp index b08e25943..5fb817e54 100644 --- a/Frameworks/OpenMPT/OpenMPT/src/mpt/base/namespace.hpp +++ b/Frameworks/OpenMPT/OpenMPT/src/mpt/base/namespace.hpp @@ -31,6 +31,47 @@ #if MPT_LIBC_GENERIC #define MPT_VERSION_ABI_LIBC _ #elif MPT_LIBC_MS +#if MPT_LIBC_MS_UCRT +#if MPT_LIBC_MS_SHARED +#ifdef MPT_LIBC_MS_DEBUG +#define MPT_VERSION_ABI_LIBC UMDd +#else +#define MPT_VERSION_ABI_LIBC UMDr +#endif +#elif MPT_LIBC_MS_STATIC +#ifdef MPT_LIBC_MS_DEBUG +#define MPT_VERSION_ABI_LIBC UMTd +#else +#define MPT_VERSION_ABI_LIBC UMTr +#endif +#else +#ifdef MPT_LIBC_MS_DEBUG +#define MPT_VERSION_ABI_LIBC UMd +#else +#define MPT_VERSION_ABI_LIBC UMr +#endif +#endif +#elif MPT_LIBC_MS_MSVCRT +#if MPT_LIBC_MS_SHARED +#ifdef MPT_LIBC_MS_DEBUG +#define MPT_VERSION_ABI_LIBC MMDd +#else +#define MPT_VERSION_ABI_LIBC MMDr +#endif +#elif MPT_LIBC_MS_STATIC +#ifdef MPT_LIBC_MS_DEBUG +#define MPT_VERSION_ABI_LIBC MMTd +#else +#define MPT_VERSION_ABI_LIBC MMTr +#endif +#else +#ifdef MPT_LIBC_MS_DEBUG +#define MPT_VERSION_ABI_LIBC MMd +#else +#define MPT_VERSION_ABI_LIBC MMr +#endif +#endif +#else #if MPT_LIBC_MS_SHARED #ifdef MPT_LIBC_MS_DEBUG #define MPT_VERSION_ABI_LIBC MDd @@ -50,10 +91,19 @@ #define MPT_VERSION_ABI_LIBC Mr #endif #endif +#endif #elif MPT_LIBC_GLIBC #define MPT_VERSION_ABI_LIBC G #elif MPT_LIBC_MINGW +#if MPT_LIBC_MINGW_UCRT +#define MPT_VERSION_ABI_LIBC MWU +#elif MPT_LIBC_MINGW_MSVCRT +#define MPT_VERSION_ABI_LIBC MWM +#elif MPT_LIBC_MINGW_CRTDLL +#define MPT_VERSION_ABI_LIBC MWC +#else #define MPT_VERSION_ABI_LIBC MW +#endif #elif MPT_LIBC_BIONIC #define MPT_VERSION_ABI_LIBC B #elif MPT_LIBC_APPLE @@ -62,10 +112,16 @@ #define MPT_VERSION_ABI_LIBC _ #endif -#define MPT_BUILD_ABI_NAMESPACE_IMPL(a, b) ABI_##a##_##b -#define MPT_BUILD_ABI_NAMESPACE(a, b) MPT_BUILD_ABI_NAMESPACE_IMPL(a, b) +#ifdef NDEBUG +#define MPT_VERSION_ABI_LIBC_DEBUG _ +#else +#define MPT_VERSION_ABI_LIBC_DEBUG D +#endif -#define MPT_ABI_NAMESPACE MPT_BUILD_ABI_NAMESPACE(MPT_VERSION_ABI_OS, MPT_VERSION_ABI_LIBC) +#define MPT_BUILD_ABI_NAMESPACE_IMPL(a, b, c) ABI_##a##_##b +#define MPT_BUILD_ABI_NAMESPACE(a, b, c) MPT_BUILD_ABI_NAMESPACE_IMPL(a, b, c) + +#define MPT_ABI_NAMESPACE MPT_BUILD_ABI_NAMESPACE(MPT_VERSION_ABI_OS, MPT_VERSION_ABI_LIBC, MPT_VERSION_ABI_LIBC_DEBUG) #if !defined(MPT_PROJECT_NAMESPACE) MPT_WARNING("Please #define MPT_PROJECT_NAMESPACE or #define MPT_INLINE_NS in build configuration.") diff --git a/Frameworks/OpenMPT/OpenMPT/src/mpt/base/saturate_cast.hpp b/Frameworks/OpenMPT/OpenMPT/src/mpt/base/saturate_cast.hpp index bdcaec9b1..02c31927e 100644 --- a/Frameworks/OpenMPT/OpenMPT/src/mpt/base/saturate_cast.hpp +++ b/Frameworks/OpenMPT/OpenMPT/src/mpt/base/saturate_cast.hpp @@ -5,10 +5,14 @@ +#include "mpt/base/detect.hpp" #include "mpt/base/namespace.hpp" #include #include +#if MPT_CXX_AT_LEAST(26) +#include +#endif @@ -16,6 +20,12 @@ namespace mpt { inline namespace MPT_INLINE_NS { +#if MPT_CXX_AT_LEAST(26) + +using std::saturate_cast; + +#else + // Saturate the value of src to the domain of Tdst template constexpr Tdst saturate_cast(Tsrc src) noexcept { @@ -51,27 +61,7 @@ constexpr Tdst saturate_cast(Tsrc src) noexcept { } } -template -constexpr Tdst saturate_cast(double src) { - if (src >= static_cast(std::numeric_limits::max())) { - return std::numeric_limits::max(); - } - if (src <= static_cast(std::numeric_limits::min())) { - return std::numeric_limits::min(); - } - return static_cast(src); -} - -template -constexpr Tdst saturate_cast(float src) { - if (src >= static_cast(std::numeric_limits::max())) { - return std::numeric_limits::max(); - } - if (src <= static_cast(std::numeric_limits::min())) { - return std::numeric_limits::min(); - } - return static_cast(src); -} +#endif } // namespace MPT_INLINE_NS diff --git a/Frameworks/OpenMPT/OpenMPT/src/mpt/base/saturate_round.hpp b/Frameworks/OpenMPT/OpenMPT/src/mpt/base/saturate_round.hpp index 167a4607e..b86c2bcaa 100644 --- a/Frameworks/OpenMPT/OpenMPT/src/mpt/base/saturate_round.hpp +++ b/Frameworks/OpenMPT/OpenMPT/src/mpt/base/saturate_round.hpp @@ -8,9 +8,11 @@ #include "mpt/base/namespace.hpp" #include "mpt/base/math.hpp" -#include "mpt/base/saturate_cast.hpp" #include +#include + +#include @@ -18,25 +20,44 @@ namespace mpt { inline namespace MPT_INLINE_NS { + +template +constexpr Tdst saturate_trunc(Tsrc src) { + static_assert(std::is_floating_point::value); + if (src >= static_cast(std::numeric_limits::max())) { + return std::numeric_limits::max(); + } + if (src <= static_cast(std::numeric_limits::min())) { + return std::numeric_limits::min(); + } + return static_cast(src); +} + + // Rounds given double value to nearest integer value of type T. // Out-of-range values are saturated to the specified integer type's limits. -template -inline T saturate_round(float val) { - static_assert(std::numeric_limits::is_integer); - return mpt::saturate_cast(mpt::round(val)); +template +inline Tdst saturate_round(Tsrc val) { + static_assert(std::is_floating_point::value); + static_assert(std::numeric_limits::is_integer); + return mpt::saturate_trunc(mpt::round(val)); } -template -inline T saturate_round(double val) { - static_assert(std::numeric_limits::is_integer); - return mpt::saturate_cast(mpt::round(val)); + +template +inline Tdst saturate_ceil(Tsrc val) { + static_assert(std::is_floating_point::value); + static_assert(std::numeric_limits::is_integer); + return mpt::saturate_trunc(std::ceil(val)); } -template -inline T saturate_round(long double val) { - static_assert(std::numeric_limits::is_integer); - return mpt::saturate_cast(mpt::round(val)); + +template +inline Tdst saturate_floor(Tsrc val) { + static_assert(std::is_floating_point::value); + static_assert(std::numeric_limits::is_integer); + return mpt::saturate_trunc(std::floor(val)); } diff --git a/Frameworks/OpenMPT/OpenMPT/src/mpt/base/size.hpp b/Frameworks/OpenMPT/OpenMPT/src/mpt/base/size.hpp new file mode 100644 index 000000000..f0b1b8807 --- /dev/null +++ b/Frameworks/OpenMPT/OpenMPT/src/mpt/base/size.hpp @@ -0,0 +1,72 @@ +/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */ + +#ifndef MPT_BASE_SIZE_HPP +#define MPT_BASE_SIZE_HPP + + + +#include "mpt/base/detect.hpp" + +#if MPT_CXX_AT_LEAST(20) +#include "mpt/base/constexpr_throw.hpp" +#endif // C++20 +#include "mpt/base/namespace.hpp" +#if MPT_CXX_AT_LEAST(20) +#include "mpt/base/utility.hpp" +#endif // C++20 + +#if MPT_CXX_AT_LEAST(20) +#include +#endif // C++20 + +#include + + + +namespace mpt { +inline namespace MPT_INLINE_NS { + + + +using usize = std::size_t; +using ssize = std::ptrdiff_t; + + +namespace size_literals { +#if MPT_CXX_AT_LEAST(20) +consteval usize operator""_uz(unsigned long long val) noexcept { + if (!mpt::in_range(val)) { + mpt::constexpr_throw(std::domain_error("")); + } + return static_cast(val); +} +#else // !C++20 +constexpr usize operator""_uz(unsigned long long val) noexcept { + return static_cast(val); +} +#endif // C++20 +} // namespace size_literals + +namespace size_literals { +#if MPT_CXX_AT_LEAST(20) +consteval ssize operator""_z(unsigned long long val) noexcept { + if (!mpt::in_range(val)) { + mpt::constexpr_throw(std::domain_error("")); + } + return static_cast(val); +} +#else // !C++20 +constexpr ssize operator""_z(unsigned long long val) noexcept { + return static_cast(val); +} +#endif // C++20 +} // namespace size_literals + + + +} // namespace MPT_INLINE_NS +} // namespace mpt + + + +#endif // MPT_BASE_SIZE_HPP diff --git a/Frameworks/OpenMPT/OpenMPT/src/mpt/base/span.hpp b/Frameworks/OpenMPT/OpenMPT/src/mpt/base/span.hpp index 4a4d9c661..2d86a1fd9 100644 --- a/Frameworks/OpenMPT/OpenMPT/src/mpt/base/span.hpp +++ b/Frameworks/OpenMPT/OpenMPT/src/mpt/base/span.hpp @@ -122,14 +122,6 @@ public: return m_data[index]; } - bool operator==(const span & other) const noexcept { - return size() == other.size() && (m_data == other.m_data || std::equal(begin(), end(), other.begin())); - } - - bool operator!=(const span & other) const noexcept { - return !(*this == other); - } - pointer data() const noexcept { return m_data; } @@ -187,6 +179,11 @@ inline span as_span(const std::array & cont) { return span(cont); } +template +bool span_elements_equal(const Ca & a, const Cb & b) { + return a.size() == b.size() && (a.data() == b.data() || std::equal(a.begin(), a.end(), b.begin())); +} + } // namespace MPT_INLINE_NS } // namespace mpt diff --git a/Frameworks/OpenMPT/OpenMPT/src/mpt/base/tests/tests_base_math.hpp b/Frameworks/OpenMPT/OpenMPT/src/mpt/base/tests/tests_base_math.hpp index ba22dc1b3..40eff50b3 100644 --- a/Frameworks/OpenMPT/OpenMPT/src/mpt/base/tests/tests_base_math.hpp +++ b/Frameworks/OpenMPT/OpenMPT/src/mpt/base/tests/tests_base_math.hpp @@ -11,6 +11,8 @@ #include "mpt/test/test.hpp" #include "mpt/test/test_macros.hpp" +#include + namespace mpt { @@ -31,6 +33,14 @@ MPT_TEST_GROUP_INLINE("mpt/base/math") #pragma clang diagnostic pop #endif { + MPT_TEST_EXPECT_EQUAL(mpt::round(1.99f), 2.0f); + MPT_TEST_EXPECT_EQUAL(mpt::round(1.5f), 2.0f); + MPT_TEST_EXPECT_EQUAL(mpt::round(1.1f), 1.0f); + MPT_TEST_EXPECT_EQUAL(mpt::round(-0.1f), 0.0f); + MPT_TEST_EXPECT_EQUAL(mpt::round(-0.5f), -1.0f); + MPT_TEST_EXPECT_EQUAL(mpt::round(-0.9f), -1.0f); + MPT_TEST_EXPECT_EQUAL(mpt::round(-1.4f), -1.0f); + MPT_TEST_EXPECT_EQUAL(mpt::round(-1.7f), -2.0f); MPT_TEST_EXPECT_EQUAL(mpt::round(1.99), 2.0); MPT_TEST_EXPECT_EQUAL(mpt::round(1.5), 2.0); MPT_TEST_EXPECT_EQUAL(mpt::round(1.1), 1.0); @@ -39,6 +49,89 @@ MPT_TEST_GROUP_INLINE("mpt/base/math") MPT_TEST_EXPECT_EQUAL(mpt::round(-0.9), -1.0); MPT_TEST_EXPECT_EQUAL(mpt::round(-1.4), -1.0); MPT_TEST_EXPECT_EQUAL(mpt::round(-1.7), -2.0); + MPT_TEST_EXPECT_EQUAL(mpt::round(1.99l), 2.0l); + MPT_TEST_EXPECT_EQUAL(mpt::round(1.5l), 2.0l); + MPT_TEST_EXPECT_EQUAL(mpt::round(1.1l), 1.0l); + MPT_TEST_EXPECT_EQUAL(mpt::round(-0.1l), 0.0l); + MPT_TEST_EXPECT_EQUAL(mpt::round(-0.5l), -1.0l); + MPT_TEST_EXPECT_EQUAL(mpt::round(-0.9l), -1.0l); + MPT_TEST_EXPECT_EQUAL(mpt::round(-1.4l), -1.0l); + MPT_TEST_EXPECT_EQUAL(mpt::round(-1.7l), -2.0l); + + MPT_TEST_EXPECT_EQUAL(std::ceil(1.99f), 2.0f); + MPT_TEST_EXPECT_EQUAL(std::ceil(1.5f), 2.0f); + MPT_TEST_EXPECT_EQUAL(std::ceil(1.1f), 2.0f); + MPT_TEST_EXPECT_EQUAL(std::ceil(-0.1f), 0.0f); + MPT_TEST_EXPECT_EQUAL(std::ceil(-0.5f), 0.0f); + MPT_TEST_EXPECT_EQUAL(std::ceil(-0.9f), 0.0f); + MPT_TEST_EXPECT_EQUAL(std::ceil(-1.4f), -1.0f); + MPT_TEST_EXPECT_EQUAL(std::ceil(-1.7f), -1.0f); + MPT_TEST_EXPECT_EQUAL(std::ceil(1.99), 2.0); + MPT_TEST_EXPECT_EQUAL(std::ceil(1.5), 2.0); + MPT_TEST_EXPECT_EQUAL(std::ceil(1.1), 2.0); + MPT_TEST_EXPECT_EQUAL(std::ceil(-0.1), 0.0); + MPT_TEST_EXPECT_EQUAL(std::ceil(-0.5), 0.0); + MPT_TEST_EXPECT_EQUAL(std::ceil(-0.9), 0.0); + MPT_TEST_EXPECT_EQUAL(std::ceil(-1.4), -1.0); + MPT_TEST_EXPECT_EQUAL(std::ceil(-1.7), -1.0); + MPT_TEST_EXPECT_EQUAL(std::ceil(1.99l), 2.0l); + MPT_TEST_EXPECT_EQUAL(std::ceil(1.5l), 2.0l); + MPT_TEST_EXPECT_EQUAL(std::ceil(1.1l), 2.0l); + MPT_TEST_EXPECT_EQUAL(std::ceil(-0.1l), 0.0l); + MPT_TEST_EXPECT_EQUAL(std::ceil(-0.5l), 0.0l); + MPT_TEST_EXPECT_EQUAL(std::ceil(-0.9l), 0.0l); + MPT_TEST_EXPECT_EQUAL(std::ceil(-1.4l), -1.0l); + MPT_TEST_EXPECT_EQUAL(std::ceil(-1.7l), -1.0l); + + MPT_TEST_EXPECT_EQUAL(std::floor(1.99f), 1.0f); + MPT_TEST_EXPECT_EQUAL(std::floor(1.5f), 1.0f); + MPT_TEST_EXPECT_EQUAL(std::floor(1.1f), 1.0f); + MPT_TEST_EXPECT_EQUAL(std::floor(-0.1f), -1.0f); + MPT_TEST_EXPECT_EQUAL(std::floor(-0.5f), -1.0f); + MPT_TEST_EXPECT_EQUAL(std::floor(-0.9f), -1.0f); + MPT_TEST_EXPECT_EQUAL(std::floor(-1.4f), -2.0f); + MPT_TEST_EXPECT_EQUAL(std::floor(-1.7f), -2.0f); + MPT_TEST_EXPECT_EQUAL(std::floor(1.99), 1.0); + MPT_TEST_EXPECT_EQUAL(std::floor(1.5), 1.0); + MPT_TEST_EXPECT_EQUAL(std::floor(1.1), 1.0); + MPT_TEST_EXPECT_EQUAL(std::floor(-0.1), -1.0); + MPT_TEST_EXPECT_EQUAL(std::floor(-0.5), -1.0); + MPT_TEST_EXPECT_EQUAL(std::floor(-0.9), -1.0); + MPT_TEST_EXPECT_EQUAL(std::floor(-1.4), -2.0); + MPT_TEST_EXPECT_EQUAL(std::floor(-1.7), -2.0); + MPT_TEST_EXPECT_EQUAL(std::floor(1.99l), 1.0l); + MPT_TEST_EXPECT_EQUAL(std::floor(1.5l), 1.0l); + MPT_TEST_EXPECT_EQUAL(std::floor(1.1l), 1.0l); + MPT_TEST_EXPECT_EQUAL(std::floor(-0.1l), -1.0l); + MPT_TEST_EXPECT_EQUAL(std::floor(-0.5l), -1.0l); + MPT_TEST_EXPECT_EQUAL(std::floor(-0.9l), -1.0l); + MPT_TEST_EXPECT_EQUAL(std::floor(-1.4l), -2.0l); + MPT_TEST_EXPECT_EQUAL(std::floor(-1.7l), -2.0l); + + MPT_TEST_EXPECT_EQUAL(mpt::trunc(1.99f), 1.0f); + MPT_TEST_EXPECT_EQUAL(mpt::trunc(1.5f), 1.0f); + MPT_TEST_EXPECT_EQUAL(mpt::trunc(1.1f), 1.0f); + MPT_TEST_EXPECT_EQUAL(mpt::trunc(-0.1f), 0.0f); + MPT_TEST_EXPECT_EQUAL(mpt::trunc(-0.5f), 0.0f); + MPT_TEST_EXPECT_EQUAL(mpt::trunc(-0.9f), 0.0f); + MPT_TEST_EXPECT_EQUAL(mpt::trunc(-1.4f), -1.0f); + MPT_TEST_EXPECT_EQUAL(mpt::trunc(-1.7f), -1.0f); + MPT_TEST_EXPECT_EQUAL(mpt::trunc(1.99), 1.0); + MPT_TEST_EXPECT_EQUAL(mpt::trunc(1.5), 1.0); + MPT_TEST_EXPECT_EQUAL(mpt::trunc(1.1), 1.0); + MPT_TEST_EXPECT_EQUAL(mpt::trunc(-0.1), 0.0); + MPT_TEST_EXPECT_EQUAL(mpt::trunc(-0.5), 0.0); + MPT_TEST_EXPECT_EQUAL(mpt::trunc(-0.9), 0.0); + MPT_TEST_EXPECT_EQUAL(mpt::trunc(-1.4), -1.0); + MPT_TEST_EXPECT_EQUAL(mpt::trunc(-1.7), -1.0); + MPT_TEST_EXPECT_EQUAL(mpt::trunc(1.99l), 1.0l); + MPT_TEST_EXPECT_EQUAL(mpt::trunc(1.5l), 1.0l); + MPT_TEST_EXPECT_EQUAL(mpt::trunc(1.1l), 1.0l); + MPT_TEST_EXPECT_EQUAL(mpt::trunc(-0.1l), 0.0l); + MPT_TEST_EXPECT_EQUAL(mpt::trunc(-0.5l), 0.0l); + MPT_TEST_EXPECT_EQUAL(mpt::trunc(-0.9l), 0.0l); + MPT_TEST_EXPECT_EQUAL(mpt::trunc(-1.4l), -1.0l); + MPT_TEST_EXPECT_EQUAL(mpt::trunc(-1.7l), -1.0l); } } // namespace math diff --git a/Frameworks/OpenMPT/OpenMPT/src/mpt/base/tests/tests_base_saturate_cast.hpp b/Frameworks/OpenMPT/OpenMPT/src/mpt/base/tests/tests_base_saturate_cast.hpp index e2d69ad9d..52ca26c0c 100644 --- a/Frameworks/OpenMPT/OpenMPT/src/mpt/base/tests/tests_base_saturate_cast.hpp +++ b/Frameworks/OpenMPT/OpenMPT/src/mpt/base/tests/tests_base_saturate_cast.hpp @@ -94,8 +94,6 @@ MPT_TEST_GROUP_INLINE("mpt/base/saturate_cast") MPT_TEST_EXPECT_EQUAL(mpt::saturate_cast(std::numeric_limits::max() - 1), std::numeric_limits::max()); MPT_TEST_EXPECT_EQUAL(mpt::saturate_cast(std::numeric_limits::max() - 1), std::numeric_limits::max()); - - MPT_TEST_EXPECT_EQUAL(mpt::saturate_cast(static_cast(std::numeric_limits::max())), std::numeric_limits::max()); } } // namespace saturate_cast diff --git a/Frameworks/OpenMPT/OpenMPT/src/mpt/base/tests/tests_base_saturate_round.hpp b/Frameworks/OpenMPT/OpenMPT/src/mpt/base/tests/tests_base_saturate_round.hpp index 27f753e76..f67772750 100644 --- a/Frameworks/OpenMPT/OpenMPT/src/mpt/base/tests/tests_base_saturate_round.hpp +++ b/Frameworks/OpenMPT/OpenMPT/src/mpt/base/tests/tests_base_saturate_round.hpp @@ -34,6 +34,8 @@ MPT_TEST_GROUP_INLINE("mpt/base/saturate_round") #pragma clang diagnostic pop #endif { + MPT_TEST_EXPECT_EQUAL(mpt::saturate_trunc(static_cast(std::numeric_limits::max())), std::numeric_limits::max()); + MPT_TEST_EXPECT_EQUAL(mpt::saturate_round(std::numeric_limits::max() + 0.1), std::numeric_limits::max()); MPT_TEST_EXPECT_EQUAL(mpt::saturate_round(std::numeric_limits::max() - 0.4), std::numeric_limits::max()); MPT_TEST_EXPECT_EQUAL(mpt::saturate_round(std::numeric_limits::min() + 0.1), std::numeric_limits::min()); @@ -42,6 +44,34 @@ MPT_TEST_GROUP_INLINE("mpt/base/saturate_round") MPT_TEST_EXPECT_EQUAL(mpt::saturate_round(110.1), 110); MPT_TEST_EXPECT_EQUAL(mpt::saturate_round(-110.1), -110); + MPT_TEST_EXPECT_EQUAL(mpt::saturate_trunc(-0.6), 0); + MPT_TEST_EXPECT_EQUAL(mpt::saturate_trunc(-0.5), 0); + MPT_TEST_EXPECT_EQUAL(mpt::saturate_trunc(-0.4), 0); + MPT_TEST_EXPECT_EQUAL(mpt::saturate_trunc(0.4), 0); + MPT_TEST_EXPECT_EQUAL(mpt::saturate_trunc(0.5), 0); + MPT_TEST_EXPECT_EQUAL(mpt::saturate_trunc(0.6), 0); + + MPT_TEST_EXPECT_EQUAL(mpt::saturate_round(-0.6), -1); + MPT_TEST_EXPECT_EQUAL(mpt::saturate_round(-0.5), -1); + MPT_TEST_EXPECT_EQUAL(mpt::saturate_round(-0.4), 0); + MPT_TEST_EXPECT_EQUAL(mpt::saturate_round(0.4), 0); + MPT_TEST_EXPECT_EQUAL(mpt::saturate_round(0.5), 1); + MPT_TEST_EXPECT_EQUAL(mpt::saturate_round(0.6), 1); + + MPT_TEST_EXPECT_EQUAL(mpt::saturate_ceil(-0.6), 0); + MPT_TEST_EXPECT_EQUAL(mpt::saturate_ceil(-0.5), 0); + MPT_TEST_EXPECT_EQUAL(mpt::saturate_ceil(-0.4), 0); + MPT_TEST_EXPECT_EQUAL(mpt::saturate_ceil(0.4), 1); + MPT_TEST_EXPECT_EQUAL(mpt::saturate_ceil(0.5), 1); + MPT_TEST_EXPECT_EQUAL(mpt::saturate_ceil(0.6), 1); + + MPT_TEST_EXPECT_EQUAL(mpt::saturate_floor(-0.6), -1); + MPT_TEST_EXPECT_EQUAL(mpt::saturate_floor(-0.5), -1); + MPT_TEST_EXPECT_EQUAL(mpt::saturate_floor(-0.4), -1); + MPT_TEST_EXPECT_EQUAL(mpt::saturate_floor(0.4), 0); + MPT_TEST_EXPECT_EQUAL(mpt::saturate_floor(0.5), 0); + MPT_TEST_EXPECT_EQUAL(mpt::saturate_floor(0.6), 0); + // These should fail to compile //mpt::saturate_round(1.0); //mpt::saturate_round(1.0); diff --git a/Frameworks/OpenMPT/OpenMPT/src/mpt/base/type_traits.hpp b/Frameworks/OpenMPT/OpenMPT/src/mpt/base/type_traits.hpp new file mode 100644 index 000000000..211d88913 --- /dev/null +++ b/Frameworks/OpenMPT/OpenMPT/src/mpt/base/type_traits.hpp @@ -0,0 +1,40 @@ +/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */ + +#ifndef MPT_BASE_TYPE_TRAITS_HPP +#define MPT_BASE_TYPE_TRAITS_HPP + + + +#include "mpt/base/detect.hpp" +#include "mpt/base/namespace.hpp" + +#include + + + +namespace mpt { +inline namespace MPT_INLINE_NS { + + + +#if MPT_CXX_AT_LEAST(20) + +using std::type_identity; + +#else // ! C++20 + +template +struct type_identity { + using type = T; +}; + +#endif // C++20 + + + +} // namespace MPT_INLINE_NS +} // namespace mpt + + + +#endif // MPT_BASE_TYPE_TRAITS_HPP diff --git a/Frameworks/OpenMPT/OpenMPT/src/mpt/base/utility.hpp b/Frameworks/OpenMPT/OpenMPT/src/mpt/base/utility.hpp index 3ef9a8888..3210c61a2 100644 --- a/Frameworks/OpenMPT/OpenMPT/src/mpt/base/utility.hpp +++ b/Frameworks/OpenMPT/OpenMPT/src/mpt/base/utility.hpp @@ -5,14 +5,17 @@ -#include "mpt/base/detect_compiler.hpp" -#include "mpt/base/detect_libcxx.hpp" +#include "mpt/base/detect.hpp" #include "mpt/base/namespace.hpp" -#if MPT_CXX_BEFORE(20) +#if MPT_CXX_BEFORE(20) || MPT_LIBCXX_LLVM_BEFORE(13000) #include "mpt/base/saturate_cast.hpp" +#include "mpt/base/saturate_round.hpp" #endif +#if MPT_CXX_BEFORE(23) && !MPT_COMPILER_MSVC && !MPT_COMPILER_GCC && !MPT_COMPILER_CLANG +#include +#endif #include #include #include @@ -33,17 +36,58 @@ MPT_CONSTEXPRINLINE Tdst c_cast(Tsrc && x) { +template +MPT_CONSTEXPRINLINE Tdst function_pointer_cast(Tsrc f) { +#if !defined(MPT_LIBCXX_QUIRK_INCOMPLETE_IS_FUNCTION) + // MinGW64 std::is_function is always false for non __cdecl functions. + // Issue is similar to . + static_assert(std::is_pointer::type>::value); + static_assert(std::is_pointer::type>::value); + static_assert(std::is_function::type>::type>::value); + static_assert(std::is_function::type>::type>::value); +#endif +#if (MPT_CLANG_AT_LEAST(19, 0, 0) && !MPT_OS_ANDROID) || MPT_CLANG_AT_LEAST(20, 0, 0) +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wcast-function-type-mismatch" +#endif + return reinterpret_cast(f); +#if (MPT_CLANG_AT_LEAST(19, 0, 0) && !MPT_OS_ANDROID) || MPT_CLANG_AT_LEAST(20, 0, 0) +#pragma clang diagnostic pop +#endif +} + + + #if MPT_CXX_AT_LEAST(20) && !MPT_LIBCXX_LLVM_BEFORE(13000) using std::in_range; #else +namespace detail { + +template +constexpr Tdst saturate_cast(Tsrc src) noexcept { + return mpt::saturate_cast(src); +} + +template +constexpr Tdst saturate_cast(double src) { + return mpt::saturate_trunc(src); +} + +template +constexpr Tdst saturate_cast(float src) { + return mpt::saturate_trunc(src); +} + +} // namespace detail + // Returns true iff Tdst can represent the value val. // Use as if(mpt::in_range(-1)). template constexpr bool in_range(Tsrc val) { - return (static_cast(mpt::saturate_cast(val)) == val); + return (static_cast(mpt::detail::saturate_cast(val)) == val); } #endif @@ -53,14 +97,14 @@ constexpr bool in_range(Tsrc val) { using std::to_underlying; -#else +#else // !C++23 template constexpr std::underlying_type_t to_underlying(T value) noexcept { return static_cast::type>(value); } -#endif +#endif // C++23 @@ -193,6 +237,26 @@ constexpr bool cmp_greater_equal(Ta a, Tb b) noexcept { +#if MPT_CXX_AT_LEAST(23) && !MPT_LIBCXX_GNU_BEFORE(12) + +using std::unreachable; + +#else // !C++23 + +[[noreturn]] inline void unreachable() { +#if MPT_COMPILER_MSVC + __assume(false); +#elif MPT_COMPILER_GCC || MPT_COMPILER_CLANG + __builtin_unreachable(); +#else + std::terminate(); +#endif +} + +#endif // C++23 + + + } // namespace MPT_INLINE_NS } // namespace mpt diff --git a/Frameworks/OpenMPT/OpenMPT/src/mpt/check/compiler.hpp b/Frameworks/OpenMPT/OpenMPT/src/mpt/check/compiler.hpp index 01ce1bec6..c08d1e951 100644 --- a/Frameworks/OpenMPT/OpenMPT/src/mpt/check/compiler.hpp +++ b/Frameworks/OpenMPT/OpenMPT/src/mpt/check/compiler.hpp @@ -38,13 +38,21 @@ MPT_WARNING("C++ compiler assumes finite math only. This is not standard-conform #endif #ifndef MPT_CHECK_CXX_IGNORE_WARNING_NO_EXCEPTIONS -#if MPT_COMPILER_GCC +#if MPT_COMPILER_MSVC +#if !defined(_CPPUNWIND) +MPT_WARNING("C++ compiler has no exception support.") +#endif +#elif MPT_COMPILER_GCC #if (!defined(__EXCEPTIONS) || (__EXCEPTIONS != 1)) MPT_WARNING("C++ compiler has no exception support.") #endif -#elif MPT_COMPILER_CLANG && !defined(_MSC_VER) -#if (!defined(__EXCEPTIONS) || (__EXCEPTIONS != 1)) +#elif MPT_COMPILER_CLANG +#if (!__has_feature(cxx_exceptions) && (!defined(__EXCEPTIONS) || (__EXCEPTIONS != 1)) && !defined(_CPPUNWIND)) MPT_WARNING("C++ compiler has no exception support.") +#else +#if (MPT_CXX_AT_LEAST(20) && !defined(__cpp_exceptions)) +MPT_WARNING("C++ compiler has no exception support.") +#endif #endif #endif #endif diff --git a/Frameworks/OpenMPT/OpenMPT/src/mpt/check/libcxx.hpp b/Frameworks/OpenMPT/OpenMPT/src/mpt/check/libcxx.hpp new file mode 100644 index 000000000..b427d0621 --- /dev/null +++ b/Frameworks/OpenMPT/OpenMPT/src/mpt/check/libcxx.hpp @@ -0,0 +1,16 @@ +/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */ + +#ifndef MPT_CHECK_LIBCXX_HPP +#define MPT_CHECK_LIBCXX_HPP + +#include "mpt/base/detect_libcxx.hpp" +#include "mpt/base/detect_os.hpp" +#include "mpt/base/detect_quirks.hpp" + +#ifndef MPT_CHECK_LIBCXX_IGNORE_WARNING_NO_THREADS +#if MPT_OS_WINDOWS && MPT_WIN_BEFORE(MPT_WIN_7) && MPT_LIBCXX_GNU_AT_LEAST(13) && !defined(_GLIBCXX_HAS_GTHREADS) +#error "GNU libstdc++ is compiled without gthreads support (likely due to using Win32 threading model as opposed to POSIX or mcfgthread threading model. This a severely crippled C++11 implementation and no is no longer supported for libstdc++ version 13 or later." +#endif +#endif + +#endif // MPT_CHECK_LIBCXX_HPP diff --git a/Frameworks/OpenMPT/OpenMPT/src/mpt/endian/floatingpoint.hpp b/Frameworks/OpenMPT/OpenMPT/src/mpt/endian/floatingpoint.hpp index b90e4df0e..dca991e27 100644 --- a/Frameworks/OpenMPT/OpenMPT/src/mpt/endian/floatingpoint.hpp +++ b/Frameworks/OpenMPT/OpenMPT/src/mpt/endian/floatingpoint.hpp @@ -6,7 +6,7 @@ #include "mpt/base/bit.hpp" -#include "mpt/base/floatingpoint.hpp" +#include "mpt/base/float.hpp" #include "mpt/base/macros.hpp" #include "mpt/base/memory.hpp" #include "mpt/base/namespace.hpp" @@ -27,8 +27,8 @@ inline namespace MPT_INLINE_NS { // 1.0f --> 0x3f800000u -MPT_FORCEINLINE uint32 EncodeIEEE754binary32(float32 f) { - if constexpr (mpt::float_traits::is_ieee754_binary32ne) { +MPT_FORCEINLINE uint32 EncodeIEEE754binary32(somefloat32 f) { + if constexpr (mpt::float_traits::is_float32 && mpt::float_traits::is_ieee754_binary && mpt::float_traits::is_native_endian) { return mpt::bit_cast(f); } else { int e = 0; @@ -55,8 +55,8 @@ MPT_FORCEINLINE uint32 EncodeIEEE754binary32(float32 f) { } } -MPT_FORCEINLINE uint64 EncodeIEEE754binary64(float64 f) { - if constexpr (mpt::float_traits::is_ieee754_binary64ne) { +MPT_FORCEINLINE uint64 EncodeIEEE754binary64(somefloat64 f) { + if constexpr (mpt::float_traits::is_float64 && mpt::float_traits::is_ieee754_binary && mpt::float_traits::is_native_endian) { return mpt::bit_cast(f); } else { int e = 0; @@ -84,9 +84,9 @@ MPT_FORCEINLINE uint64 EncodeIEEE754binary64(float64 f) { } // 0x3f800000u --> 1.0f -MPT_FORCEINLINE float32 DecodeIEEE754binary32(uint32 i) { - if constexpr (mpt::float_traits::is_ieee754_binary32ne) { - return mpt::bit_cast(i); +MPT_FORCEINLINE somefloat32 DecodeIEEE754binary32(uint32 i) { + if constexpr (mpt::float_traits::is_float32 && mpt::float_traits::is_ieee754_binary && mpt::float_traits::is_native_endian) { + return mpt::bit_cast(i); } else { uint32 mant = (i & 0x007fffffu) >> 0; uint32 expo = (i & 0x7f800000u) >> 23; @@ -95,20 +95,20 @@ MPT_FORCEINLINE float32 DecodeIEEE754binary32(uint32 i) { float m = sign ? -static_cast(mant) : static_cast(mant); int e = static_cast(expo) - 127 + 1 - 24; float f = std::ldexp(m, e); - return static_cast(f); + return static_cast(f); } else { mant |= 0x00800000u; float m = sign ? -static_cast(mant) : static_cast(mant); int e = static_cast(expo) - 127 + 1 - 24; float f = std::ldexp(m, e); - return static_cast(f); + return static_cast(f); } } } -MPT_FORCEINLINE float64 DecodeIEEE754binary64(uint64 i) { - if constexpr (mpt::float_traits::is_ieee754_binary64ne) { - return mpt::bit_cast(i); +MPT_FORCEINLINE somefloat64 DecodeIEEE754binary64(uint64 i) { + if constexpr (mpt::float_traits::is_float64 && mpt::float_traits::is_ieee754_binary && mpt::float_traits::is_native_endian) { + return mpt::bit_cast(i); } else { uint64 mant = (i & 0x000fffffffffffffull) >> 0; uint64 expo = (i & 0x7ff0000000000000ull) >> 52; @@ -117,13 +117,13 @@ MPT_FORCEINLINE float64 DecodeIEEE754binary64(uint64 i) { double m = sign ? -static_cast(mant) : static_cast(mant); int e = static_cast(expo) - 1023 + 1 - 53; double f = std::ldexp(m, e); - return static_cast(f); + return static_cast(f); } else { mant |= 0x0010000000000000ull; double m = sign ? -static_cast(mant) : static_cast(mant); int e = static_cast(expo) - 1023 + 1 - 53; double f = std::ldexp(m, e); - return static_cast(f); + return static_cast(f); } } } @@ -141,10 +141,10 @@ public: return bytes[i]; } IEEE754binary32Emulated() = default; - MPT_FORCEINLINE explicit IEEE754binary32Emulated(float32 f) { + MPT_FORCEINLINE explicit IEEE754binary32Emulated(somefloat32 f) { SetInt32(EncodeIEEE754binary32(f)); } - MPT_FORCEINLINE IEEE754binary32Emulated & operator=(float32 f) { + MPT_FORCEINLINE IEEE754binary32Emulated & operator=(somefloat32 f) { SetInt32(EncodeIEEE754binary32(f)); return *this; } @@ -157,7 +157,14 @@ public: bytes[2] = b2; bytes[3] = b3; } - MPT_FORCEINLINE operator float32() const { + MPT_FORCEINLINE operator somefloat32() const { + return DecodeIEEE754binary32(GetInt32()); + } + MPT_FORCEINLINE self_t & set(somefloat32 f) { + SetInt32(EncodeIEEE754binary32(f)); + return *this; + } + MPT_FORCEINLINE somefloat32 get() const { return DecodeIEEE754binary32(GetInt32()); } MPT_FORCEINLINE self_t & SetInt32(uint32 i) { @@ -196,10 +203,10 @@ public: return bytes[i]; } IEEE754binary64Emulated() = default; - MPT_FORCEINLINE explicit IEEE754binary64Emulated(float64 f) { + MPT_FORCEINLINE explicit IEEE754binary64Emulated(somefloat64 f) { SetInt64(EncodeIEEE754binary64(f)); } - MPT_FORCEINLINE IEEE754binary64Emulated & operator=(float64 f) { + MPT_FORCEINLINE IEEE754binary64Emulated & operator=(somefloat64 f) { SetInt64(EncodeIEEE754binary64(f)); return *this; } @@ -213,7 +220,14 @@ public: bytes[6] = b6; bytes[7] = b7; } - MPT_FORCEINLINE operator float64() const { + MPT_FORCEINLINE operator somefloat64() const { + return DecodeIEEE754binary64(GetInt64()); + } + MPT_FORCEINLINE self_t & set(somefloat64 f) { + SetInt64(EncodeIEEE754binary64(f)); + return *this; + } + MPT_FORCEINLINE somefloat64 get() const { return DecodeIEEE754binary64(GetInt64()); } MPT_FORCEINLINE self_t & SetInt64(uint64 i) { @@ -280,7 +294,7 @@ static_assert(mpt::check_binary_size(8)); template struct IEEE754binary32Native { public: - float32 value; + somefloat32 value; public: MPT_FORCEINLINE std::byte GetByte(std::size_t i) const { @@ -293,10 +307,10 @@ public: } } IEEE754binary32Native() = default; - MPT_FORCEINLINE explicit IEEE754binary32Native(float32 f) { + MPT_FORCEINLINE explicit IEEE754binary32Native(somefloat32 f) { value = f; } - MPT_FORCEINLINE IEEE754binary32Native & operator=(float32 f) { + MPT_FORCEINLINE IEEE754binary32Native & operator=(somefloat32 f) { value = f; return *this; } @@ -312,7 +326,14 @@ public: value = DecodeIEEE754binary32(0u | (static_cast(b0) << 24) | (static_cast(b1) << 16) | (static_cast(b2) << 8) | (static_cast(b3) << 0)); } } - MPT_FORCEINLINE operator float32() const { + MPT_FORCEINLINE operator somefloat32() const { + return value; + } + MPT_FORCEINLINE IEEE754binary32Native & set(somefloat32 f) { + value = f; + return *this; + } + MPT_FORCEINLINE somefloat32 get() const { return value; } MPT_FORCEINLINE IEEE754binary32Native & SetInt32(uint32 i) { @@ -333,7 +354,7 @@ public: template struct IEEE754binary64Native { public: - float64 value; + somefloat64 value; public: MPT_FORCEINLINE std::byte GetByte(std::size_t i) const { @@ -346,10 +367,10 @@ public: } } IEEE754binary64Native() = default; - MPT_FORCEINLINE explicit IEEE754binary64Native(float64 f) { + MPT_FORCEINLINE explicit IEEE754binary64Native(somefloat64 f) { value = f; } - MPT_FORCEINLINE IEEE754binary64Native & operator=(float64 f) { + MPT_FORCEINLINE IEEE754binary64Native & operator=(somefloat64 f) { value = f; return *this; } @@ -362,7 +383,14 @@ public: value = DecodeIEEE754binary64(0ull | (static_cast(b0) << 56) | (static_cast(b1) << 48) | (static_cast(b2) << 40) | (static_cast(b3) << 32) | (static_cast(b4) << 24) | (static_cast(b5) << 16) | (static_cast(b6) << 8) | (static_cast(b7) << 0)); } } - MPT_FORCEINLINE operator float64() const { + MPT_FORCEINLINE operator somefloat64() const { + return value; + } + MPT_FORCEINLINE IEEE754binary64Native & set(somefloat64 f) { + value = f; + return *this; + } + MPT_FORCEINLINE somefloat64 get() const { return value; } MPT_FORCEINLINE IEEE754binary64Native & SetInt64(uint64 i) { @@ -412,10 +440,10 @@ struct IEEE754binary_types { using IEEE754binary64BE = IEEE754binary64Native<>; }; -using IEEE754binary32LE = IEEE754binary_types::is_ieee754_binary32ne, mpt::endian::native>::IEEE754binary32LE; -using IEEE754binary32BE = IEEE754binary_types::is_ieee754_binary32ne, mpt::endian::native>::IEEE754binary32BE; -using IEEE754binary64LE = IEEE754binary_types::is_ieee754_binary64ne, mpt::endian::native>::IEEE754binary64LE; -using IEEE754binary64BE = IEEE754binary_types::is_ieee754_binary64ne, mpt::endian::native>::IEEE754binary64BE; +using IEEE754binary32LE = IEEE754binary_types::is_float32 && mpt::float_traits::is_ieee754_binary && mpt::float_traits::is_native_endian, mpt::endian::native>::IEEE754binary32LE; +using IEEE754binary32BE = IEEE754binary_types::is_float32 && mpt::float_traits::is_ieee754_binary && mpt::float_traits::is_native_endian, mpt::endian::native>::IEEE754binary32BE; +using IEEE754binary64LE = IEEE754binary_types::is_float64 && mpt::float_traits::is_ieee754_binary && mpt::float_traits::is_native_endian, mpt::endian::native>::IEEE754binary64LE; +using IEEE754binary64BE = IEEE754binary_types::is_float64 && mpt::float_traits::is_ieee754_binary && mpt::float_traits::is_native_endian, mpt::endian::native>::IEEE754binary64BE; static_assert(sizeof(IEEE754binary32LE) == 4); static_assert(sizeof(IEEE754binary32BE) == 4); diff --git a/Frameworks/OpenMPT/OpenMPT/src/mpt/exception_text/exception_text.hpp b/Frameworks/OpenMPT/OpenMPT/src/mpt/exception_text/exception_text.hpp deleted file mode 100644 index e69de29bb..000000000 diff --git a/Frameworks/OpenMPT/OpenMPT/src/mpt/io_file_unique/unique_basename.hpp b/Frameworks/OpenMPT/OpenMPT/src/mpt/io_file_unique/unique_basename.hpp index f3e79f2bc..66ee44937 100644 --- a/Frameworks/OpenMPT/OpenMPT/src/mpt/io_file_unique/unique_basename.hpp +++ b/Frameworks/OpenMPT/OpenMPT/src/mpt/io_file_unique/unique_basename.hpp @@ -10,6 +10,7 @@ #include "mpt/path/os_path.hpp" #include "mpt/random/default_engines.hpp" #include "mpt/random/device.hpp" +#include "mpt/random/seed.hpp" #include "mpt/string_transcode/transcode.hpp" #include "mpt/uuid/uuid.hpp" diff --git a/Frameworks/OpenMPT/OpenMPT/src/mpt/io_read/filecursor.hpp b/Frameworks/OpenMPT/OpenMPT/src/mpt/io_read/filecursor.hpp index 788f0c9f8..e9252860d 100644 --- a/Frameworks/OpenMPT/OpenMPT/src/mpt/io_read/filecursor.hpp +++ b/Frameworks/OpenMPT/OpenMPT/src/mpt/io_read/filecursor.hpp @@ -6,9 +6,12 @@ #include "mpt/base/alloc.hpp" +#include "mpt/base/macros.hpp" #include "mpt/base/memory.hpp" #include "mpt/base/namespace.hpp" #include "mpt/base/span.hpp" +#include "mpt/base/utility.hpp" +#include "mpt/out_of_memory/out_of_memory.hpp" #include #include @@ -249,7 +252,7 @@ public: size_ = 0; pinnedData = nullptr; if (!file.CanRead(size)) { - size = file.BytesLeft(); + size = static_cast(file.BytesLeft()); } size_ = size; if (file.DataContainer().HasPinnedView()) { @@ -268,15 +271,20 @@ public: , pinnedData(nullptr) { } PinnedView(const FileCursor & file) { - Init(file, file.BytesLeft()); + MPT_MAYBE_CONSTANT_IF (!mpt::in_range(file.BytesLeft())) { + mpt::throw_out_of_memory(); + } + Init(file, static_cast(file.BytesLeft())); } PinnedView(const FileCursor & file, std::size_t size) { Init(file, size); } PinnedView(FileCursor & file, bool advance) { - Init(file, file.BytesLeft()); - if (advance) - { + MPT_MAYBE_CONSTANT_IF (!mpt::in_range(file.BytesLeft())) { + mpt::throw_out_of_memory(); + } + Init(file, static_cast(file.BytesLeft())); + if (advance) { file.Skip(size_); } } diff --git a/Frameworks/OpenMPT/OpenMPT/src/mpt/io_read/filecursor_traits_memory.hpp b/Frameworks/OpenMPT/OpenMPT/src/mpt/io_read/filecursor_traits_memory.hpp index 254fc6404..e89b39057 100644 --- a/Frameworks/OpenMPT/OpenMPT/src/mpt/io_read/filecursor_traits_memory.hpp +++ b/Frameworks/OpenMPT/OpenMPT/src/mpt/io_read/filecursor_traits_memory.hpp @@ -11,6 +11,8 @@ #include "mpt/io_read/filedata.hpp" #include "mpt/io_read/filedata_memory.hpp" +#include + namespace mpt { @@ -47,7 +49,7 @@ public: } static value_data_type make_chunk(shared_data_type data, pos_type position, pos_type size) { - return mpt::as_span(data.GetRawData() + position, size); + return mpt::as_span(data.GetRawData() + static_cast(position), static_cast(size)); } }; diff --git a/Frameworks/OpenMPT/OpenMPT/src/mpt/io_read/filedata.hpp b/Frameworks/OpenMPT/OpenMPT/src/mpt/io_read/filedata.hpp index 3f079d588..daf77c2de 100644 --- a/Frameworks/OpenMPT/OpenMPT/src/mpt/io_read/filedata.hpp +++ b/Frameworks/OpenMPT/OpenMPT/src/mpt/io_read/filedata.hpp @@ -5,12 +5,17 @@ +#if !defined(MPT_CONFIGURATION_IO_READ_FILEDATA_NO_64BIT) +#include "mpt/base/integer.hpp" +#endif // !MPT_CONFIGURATION_IO_READ_FILEDATA_NO_64BIT #include "mpt/base/memory.hpp" #include "mpt/base/namespace.hpp" #include +#if defined(MPT_CONFIGURATION_IO_READ_FILEDATA_NO_64BIT) #include +#endif // MPT_CONFIGURATION_IO_READ_FILEDATA_NO_64BIT @@ -25,7 +30,11 @@ namespace IO { class IFileData { public: +#if !defined(MPT_CONFIGURATION_IO_READ_FILEDATA_NO_64BIT) + using pos_type = uint64; +#else // MPT_CONFIGURATION_IO_READ_FILEDATA_NO_64BIT using pos_type = std::size_t; +#endif // MPT_CONFIGURATION_IO_READ_FILEDATA_NO_64BIT protected: IFileData() = default; diff --git a/Frameworks/OpenMPT/OpenMPT/src/mpt/io_read/filedata_base.hpp b/Frameworks/OpenMPT/OpenMPT/src/mpt/io_read/filedata_base.hpp index 7b89086b1..2f06c6bde 100644 --- a/Frameworks/OpenMPT/OpenMPT/src/mpt/io_read/filedata_base.hpp +++ b/Frameworks/OpenMPT/OpenMPT/src/mpt/io_read/filedata_base.hpp @@ -86,7 +86,7 @@ public: if (pos >= dataLength) { return dst.first(0); } - return data->Read(dataOffset + pos, dst.first(std::min(dst.size(), dataLength - pos))); + return data->Read(dataOffset + pos, dst.first(static_cast(std::min(static_cast(dst.size()), dataLength - pos)))); } bool CanRead(pos_type pos, pos_type length) const override { if ((pos == dataLength) && (length == 0)) { diff --git a/Frameworks/OpenMPT/OpenMPT/src/mpt/io_read/filedata_base_buffered.hpp b/Frameworks/OpenMPT/OpenMPT/src/mpt/io_read/filedata_base_buffered.hpp index 637be25c2..a1ea7f803 100644 --- a/Frameworks/OpenMPT/OpenMPT/src/mpt/io_read/filedata_base_buffered.hpp +++ b/Frameworks/OpenMPT/OpenMPT/src/mpt/io_read/filedata_base_buffered.hpp @@ -40,7 +40,7 @@ private: }; struct chunk_info { pos_type ChunkOffset = 0; - pos_type ChunkLength = 0; + std::size_t ChunkLength = 0; bool ChunkValid = false; }; mutable std::vector m_Buffer = std::vector(BUFFER_SIZE); @@ -90,19 +90,19 @@ private: while (count > 0) { std::size_t chunkIndex = InternalFillPageAndReturnIndex(pos); pos_type pageSkip = pos - m_ChunkInfo[chunkIndex].ChunkOffset; - pos_type chunkWanted = std::min(static_cast(CHUNK_SIZE) - pageSkip, count); - pos_type chunkGot = (m_ChunkInfo[chunkIndex].ChunkLength > pageSkip) ? (m_ChunkInfo[chunkIndex].ChunkLength - pageSkip) : 0; + pos_type chunkWanted = std::min(static_cast(CHUNK_SIZE) - pageSkip, static_cast(count)); + pos_type chunkGot = (static_cast(m_ChunkInfo[chunkIndex].ChunkLength) > pageSkip) ? (static_cast(m_ChunkInfo[chunkIndex].ChunkLength) - pageSkip) : 0; pos_type chunk = std::min(chunkWanted, chunkGot); - std::copy(chunk_data(chunkIndex).data() + pageSkip, chunk_data(chunkIndex).data() + pageSkip + chunk, pdst); + std::copy(chunk_data(chunkIndex).data() + static_cast(pageSkip), chunk_data(chunkIndex).data() + static_cast(pageSkip + chunk), pdst); pos += chunk; pdst += chunk; totalRead += chunk; - count -= chunk; + count -= static_cast(chunk); if (chunkWanted > chunk) { - return dst.first(totalRead); + return dst.first(static_cast(totalRead)); } } - return dst.first(totalRead); + return dst.first(static_cast(totalRead)); } virtual mpt::byte_span InternalReadBuffered(pos_type pos, mpt::byte_span dst) const = 0; diff --git a/Frameworks/OpenMPT/OpenMPT/src/mpt/io_read/filedata_base_seekable.hpp b/Frameworks/OpenMPT/OpenMPT/src/mpt/io_read/filedata_base_seekable.hpp index 0669e3ea7..538c3f30d 100644 --- a/Frameworks/OpenMPT/OpenMPT/src/mpt/io_read/filedata_base_seekable.hpp +++ b/Frameworks/OpenMPT/OpenMPT/src/mpt/io_read/filedata_base_seekable.hpp @@ -32,27 +32,12 @@ class FileDataSeekable : public IFileData { private: pos_type streamLength; - mutable bool cached; - mutable std::vector cache; - protected: FileDataSeekable(pos_type streamLength_) - : streamLength(streamLength_) - , cached(false) { + : streamLength(streamLength_) { return; } - -private: - void CacheStream() const { - if (cached) { - return; - } - cache.resize(streamLength); - InternalReadSeekable(0, mpt::as_span(cache)); - cached = true; - } - public: bool IsValid() const override { return true; @@ -63,12 +48,11 @@ public: } bool HasPinnedView() const override { - return cached; + return false; } const std::byte * GetRawData() const override { - CacheStream(); - return cache.data(); + return nullptr; } pos_type GetLength() const override { @@ -76,13 +60,7 @@ public: } mpt::byte_span Read(pos_type pos, mpt::byte_span dst) const override { - if (cached) { - IFileData::pos_type cache_avail = std::min(IFileData::pos_type(cache.size()) - pos, dst.size()); - std::copy(cache.begin() + pos, cache.begin() + pos + cache_avail, dst.data()); - return dst.first(cache_avail); - } else { - return InternalReadSeekable(pos, dst); - } + return InternalReadSeekable(pos, dst); } private: diff --git a/Frameworks/OpenMPT/OpenMPT/src/mpt/io_read/filedata_base_unseekable.hpp b/Frameworks/OpenMPT/OpenMPT/src/mpt/io_read/filedata_base_unseekable.hpp index dbc8d4fe1..2f6dfae68 100644 --- a/Frameworks/OpenMPT/OpenMPT/src/mpt/io_read/filedata_base_unseekable.hpp +++ b/Frameworks/OpenMPT/OpenMPT/src/mpt/io_read/filedata_base_unseekable.hpp @@ -74,7 +74,6 @@ private: streamFullyCached = true; } - void CacheStreamUpTo(pos_type pos, pos_type length) const { if (streamFullyCached) { return; @@ -99,7 +98,7 @@ private: private: void ReadCached(pos_type pos, mpt::byte_span dst) const { - std::copy(cache.begin() + pos, cache.begin() + pos + dst.size(), dst.data()); + std::copy(cache.begin() + static_cast(pos), cache.begin() + static_cast(pos + dst.size()), dst.data()); } public: @@ -127,23 +126,23 @@ public: mpt::byte_span Read(pos_type pos, mpt::byte_span dst) const override { CacheStreamUpTo(pos, dst.size()); - if (pos >= IFileData::pos_type(cachesize)) { + if (pos >= static_cast(cachesize)) { return dst.first(0); } - IFileData::pos_type cache_avail = std::min(IFileData::pos_type(cachesize) - pos, dst.size()); - ReadCached(pos, dst.subspan(0, cache_avail)); - return dst.subspan(0, cache_avail); + pos_type cache_avail = std::min(static_cast(cachesize) - pos, static_cast(dst.size())); + ReadCached(pos, dst.subspan(0, static_cast(cache_avail))); + return dst.subspan(0, static_cast(cache_avail)); } bool CanRead(pos_type pos, pos_type length) const override { CacheStreamUpTo(pos, length); - if ((pos == IFileData::pos_type(cachesize)) && (length == 0)) { + if ((pos == static_cast(cachesize)) && (length == 0)) { return true; } - if (pos >= IFileData::pos_type(cachesize)) { + if (pos >= static_cast(cachesize)) { return false; } - return length <= IFileData::pos_type(cachesize) - pos; + return length <= static_cast(cachesize) - pos; } pos_type GetReadableLength(pos_type pos, pos_type length) const override { @@ -151,7 +150,7 @@ public: if (pos >= cachesize) { return 0; } - return std::min(static_cast(cachesize) - pos, length); + return std::min(static_cast(cachesize) - pos, length); } private: diff --git a/Frameworks/OpenMPT/OpenMPT/src/mpt/io_read/filedata_base_unseekable_buffer.hpp b/Frameworks/OpenMPT/OpenMPT/src/mpt/io_read/filedata_base_unseekable_buffer.hpp new file mode 100644 index 000000000..f786a46a2 --- /dev/null +++ b/Frameworks/OpenMPT/OpenMPT/src/mpt/io_read/filedata_base_unseekable_buffer.hpp @@ -0,0 +1,150 @@ +/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */ + +#ifndef MPT_IO_READ_FILEDATA_BASE_UNSEEKABLE_BUFFER_HPP +#define MPT_IO_READ_FILEDATA_BASE_UNSEEKABLE_BUFFER_HPP + + + +#include "mpt/base/algorithm.hpp" +#include "mpt/base/memory.hpp" +#include "mpt/base/namespace.hpp" +#include "mpt/base/numeric.hpp" +#include "mpt/io/base.hpp" +#include "mpt/io_read/filedata.hpp" + +#include +#include +#include + +#include + + + +namespace mpt { +inline namespace MPT_INLINE_NS { + + + +namespace IO { + + + +class FileDataUnseekableBuffer : public IFileData { + +private: + mutable std::vector cache; + mutable bool streamFullyCached; + +protected: + FileDataUnseekableBuffer() + : streamFullyCached(false) { + return; + } + +private: + enum : std::size_t { + QUANTUM_SIZE = mpt::IO::BUFFERSIZE_SMALL, + BUFFER_SIZE = mpt::IO::BUFFERSIZE_NORMAL + }; + + void CacheStream() const { + if (streamFullyCached) { + return; + } + while (!InternalEof()) { + InternalReadContinue(cache, BUFFER_SIZE); + } + streamFullyCached = true; + } + + void CacheStreamUpTo(pos_type pos, pos_type length) const { + if (streamFullyCached) { + return; + } + if (length > std::numeric_limits::max() - pos) { + length = std::numeric_limits::max() - pos; + } + std::size_t target = mpt::saturate_cast(pos + length); + if (target <= cache.size()) { + return; + } + while (!InternalEof() && (cache.size() < target)) { + std::size_t readsize = ((target - cache.size()) > QUANTUM_SIZE) ? BUFFER_SIZE : QUANTUM_SIZE; + InternalReadContinue(cache, readsize); + } + } + +private: + void ReadCached(pos_type pos, mpt::byte_span dst) const { + std::copy(cache.begin() + static_cast(pos), cache.begin() + static_cast(pos + dst.size()), dst.data()); + } + +public: + bool IsValid() const override { + return true; + } + + bool HasFastGetLength() const override { + return false; + } + + bool HasPinnedView() const override { + return true; // we have the cache which is required for seeking anyway + } + + const std::byte * GetRawData() const override { + CacheStream(); + return cache.data(); + } + + pos_type GetLength() const override { + CacheStream(); + return cache.size(); + } + + mpt::byte_span Read(pos_type pos, mpt::byte_span dst) const override { + CacheStreamUpTo(pos, dst.size()); + if (pos >= static_cast(cache.size())) { + return dst.first(0); + } + pos_type cache_avail = std::min(static_cast(cache.size()) - pos, static_cast(dst.size())); + ReadCached(pos, dst.subspan(0, static_cast(cache_avail))); + return dst.subspan(0, static_cast(cache_avail)); + } + + bool CanRead(pos_type pos, pos_type length) const override { + CacheStreamUpTo(pos, length); + if ((pos == static_cast(cache.size())) && (length == 0)) { + return true; + } + if (pos >= static_cast(cache.size())) { + return false; + } + return length <= static_cast(cache.size()) - pos; + } + + pos_type GetReadableLength(pos_type pos, pos_type length) const override { + CacheStreamUpTo(pos, length); + if (pos >= cache.size()) { + return 0; + } + return std::min(static_cast(cache.size()) - pos, length); + } + +private: + virtual bool InternalEof() const = 0; + virtual void InternalReadContinue(std::vector & streamCache, std::size_t suggestedCount) const = 0; +}; + + + +} // namespace IO + + + +} // namespace MPT_INLINE_NS +} // namespace mpt + + + +#endif // MPT_IO_READ_FILEDATA_BASE_UNSEEKABLE_BUFFER_HPP diff --git a/Frameworks/OpenMPT/OpenMPT/src/mpt/io_read/filedata_memory.hpp b/Frameworks/OpenMPT/OpenMPT/src/mpt/io_read/filedata_memory.hpp index cde007650..cbf8fe170 100644 --- a/Frameworks/OpenMPT/OpenMPT/src/mpt/io_read/filedata_memory.hpp +++ b/Frameworks/OpenMPT/OpenMPT/src/mpt/io_read/filedata_memory.hpp @@ -64,9 +64,9 @@ public: if (pos >= streamLength) { return dst.first(0); } - pos_type avail = std::min(streamLength - pos, dst.size()); - std::copy(streamData + pos, streamData + pos + avail, dst.data()); - return dst.first(avail); + pos_type avail = std::min(streamLength - pos, static_cast(dst.size())); + std::copy(streamData + pos, streamData + static_cast(pos + avail), dst.data()); + return dst.first(static_cast(avail)); } bool CanRead(pos_type pos, pos_type length) const override { diff --git a/Frameworks/OpenMPT/OpenMPT/src/mpt/io_read/filereader.hpp b/Frameworks/OpenMPT/OpenMPT/src/mpt/io_read/filereader.hpp index 3e92cebf1..6c218ec7a 100644 --- a/Frameworks/OpenMPT/OpenMPT/src/mpt/io_read/filereader.hpp +++ b/Frameworks/OpenMPT/OpenMPT/src/mpt/io_read/filereader.hpp @@ -10,6 +10,7 @@ #include "mpt/base/integer.hpp" #include "mpt/base/memory.hpp" #include "mpt/base/namespace.hpp" +#include "mpt/base/saturate_cast.hpp" #include "mpt/base/span.hpp" #include "mpt/base/utility.hpp" #include "mpt/io/base.hpp" @@ -266,7 +267,7 @@ template bool ReadArray(TFileCursor & f, std::array & destArray) { static_assert(mpt::is_binary_safe::value); if (!f.CanRead(sizeof(destArray))) { - destArray.fill(T{}); + mpt::reset(destArray); return false; } f.ReadRaw(mpt::as_raw_memory(destArray)); @@ -321,7 +322,7 @@ T ReadIntBE(TFileCursor & f) { // Read a integer in little-endian format which has some of its higher bytes not stored in file. // If successful, the file cursor is advanced by the given size. template -T ReadTruncatedIntLE(TFileCursor & f, typename TFileCursor::pos_type size) { +T ReadTruncatedIntLE(TFileCursor & f, std::size_t size) { static_assert(std::numeric_limits::is_integer == true, "Target type is a not an integer"); assert(sizeof(T) >= size); if (size == 0) { @@ -351,7 +352,7 @@ T ReadTruncatedIntLE(TFileCursor & f, typename TFileCursor::pos_type size) { // If more bytes are stored, higher order bytes are silently ignored. // If successful, the file cursor is advanced by the given size. template -T ReadSizedIntLE(TFileCursor & f, typename TFileCursor::pos_type size) { +T ReadSizedIntLE(TFileCursor & f, std::size_t size) { static_assert(std::numeric_limits::is_integer == true, "Target type is a not an integer"); if (size == 0) { return 0; @@ -530,11 +531,11 @@ bool ReadStruct(TFileCursor & f, T & target) { // Allow to read a struct partially (if there's less memory available than the struct's size, fill it up with zeros). // The file cursor is advanced by "partialSize" bytes. template -typename TFileCursor::pos_type ReadStructPartial(TFileCursor & f, T & target, typename TFileCursor::pos_type partialSize = sizeof(T)) { +std::size_t ReadStructPartial(TFileCursor & f, T & target, std::size_t partialSize = sizeof(T)) { static_assert(mpt::is_binary_safe::value); - typename TFileCursor::pos_type copyBytes = std::min(partialSize, sizeof(T)); + std::size_t copyBytes = std::min(partialSize, sizeof(T)); if (!f.CanRead(copyBytes)) { - copyBytes = f.BytesLeft(); + copyBytes = mpt::saturate_cast(f.BytesLeft()); } f.GetRaw(mpt::span(mpt::as_raw_memory(target).data(), copyBytes)); std::memset(mpt::as_raw_memory(target).data() + copyBytes, 0, sizeof(target) - copyBytes); @@ -544,13 +545,13 @@ typename TFileCursor::pos_type ReadStructPartial(TFileCursor & f, T & target, ty // Read a null-terminated string into a std::string template -bool ReadNullString(TFileCursor & f, std::string & dest, const typename TFileCursor::pos_type maxLength = std::numeric_limits::max()) { +bool ReadNullString(TFileCursor & f, std::string & dest, const std::size_t maxLength = std::numeric_limits::max()) { dest.clear(); if (!f.CanRead(1)) { return false; } char buffer[mpt::IO::BUFFERSIZE_MINUSCULE]; - typename TFileCursor::pos_type avail = 0; + std::size_t avail = 0; while ((avail = std::min(f.GetRaw(mpt::as_span(buffer)).size(), maxLength - dest.length())) != 0) { auto end = std::find(buffer, buffer + avail, '\0'); dest.insert(dest.end(), buffer, end); @@ -566,14 +567,14 @@ bool ReadNullString(TFileCursor & f, std::string & dest, const typename TFileCur // Read a string up to the next line terminator into a std::string template -bool ReadLine(TFileCursor & f, std::string & dest, const typename TFileCursor::pos_type maxLength = std::numeric_limits::max()) { +bool ReadLine(TFileCursor & f, std::string & dest, const std::size_t maxLength = std::numeric_limits::max()) { dest.clear(); if (!f.CanRead(1)) { return false; } char buffer[mpt::IO::BUFFERSIZE_MINUSCULE]; char c = '\0'; - typename TFileCursor::pos_type avail = 0; + std::size_t avail = 0; while ((avail = std::min(f.GetRaw(mpt::as_span(buffer)).size(), maxLength - dest.length())) != 0) { auto end = std::find_if(buffer, buffer + avail, mpt::is_any_line_ending); dest.insert(dest.end(), buffer, end); @@ -602,7 +603,7 @@ bool ReadMagic(TFileCursor & f, const char (&magic)[N]) { for (std::size_t i = 0; i < N - 1; ++i) { assert(magic[i] != '\0'); } - constexpr typename TFileCursor::pos_type magicLength = N - 1; + constexpr std::size_t magicLength = N - 1; std::byte buffer[magicLength] = {}; if (f.GetRaw(mpt::span(buffer, magicLength)).size() != magicLength) { return false; @@ -628,8 +629,8 @@ bool ReadVarInt(TFileCursor & f, T & target) { } std::byte bytes[16]; // More than enough for any valid VarInt - typename TFileCursor::pos_type avail = f.GetRaw(mpt::as_span(bytes)).size(); - typename TFileCursor::pos_type readPos = 1; + std::size_t avail = f.GetRaw(mpt::as_span(bytes)).size(); + std::size_t readPos = 1; uint8 b = mpt::byte_cast(bytes[0]); target = (b & 0x7F); diff --git a/Frameworks/OpenMPT/OpenMPT/src/mpt/main/main.hpp b/Frameworks/OpenMPT/OpenMPT/src/mpt/main/main.hpp new file mode 100644 index 000000000..677a48c53 --- /dev/null +++ b/Frameworks/OpenMPT/OpenMPT/src/mpt/main/main.hpp @@ -0,0 +1,199 @@ +/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */ + +#ifndef MPT_MAIN_MAIN_HPP +#define MPT_MAIN_MAIN_HPP + + + +#include "mpt/base/check_platform.hpp" +#include "mpt/base/detect.hpp" +#include "mpt/base/integer.hpp" +#include "mpt/base/macros.hpp" +#include "mpt/base/namespace.hpp" +#include "mpt/string/types.hpp" +#include "mpt/string_transcode/transcode.hpp" + +#include +#include + +#if MPT_OS_DJGPP +#include +#endif // MPT_OS_DJGPP + +#if MPT_OS_DJGPP +#include +#endif // MPT_OS_DJGPP + +#if MPT_OS_WINDOWS +#include +#endif // MPT_OS_WINDOWS + + + +namespace mpt { +inline namespace MPT_INLINE_NS { + + + +namespace main { + + + +#if MPT_OS_DJGPP +/* Work-around */ +/* clang-format off */ +#define MPT_MAIN_PREFIX \ + extern "C" { \ + int _crt0_startup_flags = 0 \ + | _CRT0_FLAG_NONMOVE_SBRK /* force interrupt compatible allocation */ \ + | _CRT0_DISABLE_SBRK_ADDRESS_WRAP /* force NT compatible allocation */ \ + | _CRT0_FLAG_LOCK_MEMORY /* lock all code and data at program startup */ \ + | 0; \ + } \ +/* clang-format on */ +#endif /* MPT_OS_DJGPP */ + +#if !defined(MPT_MAIN_PREFIX) +#define MPT_MAIN_PREFIX +#endif + + + +#if MPT_OS_WINDOWS && defined(UNICODE) +#define MPT_MAIN_NAME wmain +#elif defined(MPT_OS_WINDOWS) +#define MPT_MAIN_NAME main +#endif + +#if !defined(MPT_MAIN_NAME) +#define MPT_MAIN_NAME main +#endif + + + +#if MPT_OS_WINDOWS && defined(UNICODE) +#define MPT_MAIN_ARGV_TYPE wchar_t +#elif defined(MPT_OS_WINDOWS) +#define MPT_MAIN_ARGV_TYPE char +#endif + +#if !defined(MPT_MAIN_NAME) +#define MPT_MAIN_ARGV_TYPE char +#endif + + + +#if MPT_OS_WINDOWS && (MPT_COMPILER_GCC || MPT_COMPILER_CLANG) +#if defined(UNICODE) +// mingw64 does only default to special C linkage for "main", but not for "wmain". +#define MPT_MAIN_DECL extern "C" int wmain(int argc, wchar_t * argv[]); +#endif +#endif + +#if !defined(MPT_MAIN_DECL) +#define MPT_MAIN_DECL +#endif + + + +#if MPT_OS_DJGPP +/* clang-format off */ +#define MPT_MAIN_PROLOG() \ + do { \ + assert(mpt::platform::libc().is_ok()); \ + _crt0_startup_flags &= ~_CRT0_FLAG_LOCK_MEMORY; \ + } while (0) \ +/**/ +/* clang-format on */ +#endif // MPT_OS_DJGPP + +#if !defined(MPT_MAIN_PROLOG) +/* clang-format off */ +#define MPT_MAIN_PROLOG() do { } while(0) +/* clang-format on */ +#endif + + + +#if MPT_OS_WINDOWS && (MPT_COMPILER_GCC || MPT_COMPILER_CLANG) +#if defined(UNICODE) +#define MPT_MAIN_DEF_PREFIX extern "C" +#endif +#endif // MPT_OS_WINDOWS && (MPT_COMPILER_GCC || MPT_COMPILER_CLANG) + +#if !defined(MPT_MAIN_DEF_PREFIX) +#define MPT_MAIN_DEF_PREFIX +#endif + + + +inline mpt::ustring transcode_arg(char * arg) { + return mpt::transcode(mpt::logical_encoding::locale, arg); +} + +#if !defined(MPT_COMPILER_QUIRK_NO_WCHAR) +inline mpt::ustring transcode_arg(wchar_t * arg) { + return mpt::transcode(arg); +} +#endif + +template +inline std::vector transcode_argv(int argc, Tchar * argv[]) { + std::vector args; + args.reserve(argc); + for (int arg = 0; arg < argc; ++arg) { + args.push_back(transcode_arg(argv[arg])); + } + return args; +} + + + +#if !defined(MPT_MAIN_POSTFIX) +#define MPT_MAIN_POSTFIX +#endif + + + +/* clang-format off */ +#define MPT_MAIN_IMPLEMENT_MAIN(ns) \ + MPT_MAIN_PREFIX \ + MPT_MAIN_DECL \ + MPT_MAIN_DEF_PREFIX int MPT_MAIN_NAME(int argc, MPT_MAIN_ARGV_TYPE * argv[]) { \ + MPT_MAIN_PROLOG(); \ + static_assert(std::is_same)>::value); \ + return static_cast(ns::main(mpt::main::transcode_argv(argc, argv))); \ + } \ + MPT_MAIN_POSTFIX \ +/**/ +/* clang-format on */ + + + +/* clang-format off */ +#define MPT_MAIN_IMPLEMENT_MAIN_NO_ARGS(ns) \ + MPT_MAIN_PREFIX \ + MPT_MAIN_DECL \ + MPT_MAIN_DEF_PREFIX int MPT_MAIN_NAME(int argc, MPT_MAIN_ARGV_TYPE * argv[]) { \ + MPT_MAIN_PROLOG(); \ + static_assert(std::is_same::value); \ + MPT_UNUSED(argc); \ + MPT_UNUSED(argv); \ + return static_cast(ns::main()); \ + } \ + MPT_MAIN_POSTFIX \ +/**/ +/* clang-format on */ + + + +} // namespace main + + + +} // namespace MPT_INLINE_NS +} // namespace mpt + + + +#endif // MPT_MAIN_MAIN_HPP diff --git a/Frameworks/OpenMPT/OpenMPT/src/mpt/random/any_engine.hpp b/Frameworks/OpenMPT/OpenMPT/src/mpt/random/any_engine.hpp new file mode 100644 index 000000000..31aeecc40 --- /dev/null +++ b/Frameworks/OpenMPT/OpenMPT/src/mpt/random/any_engine.hpp @@ -0,0 +1,68 @@ +/* SPDX-License-Identifier: BSL-1.0 OR BSD-3-Clause */ + +#ifndef MPT_RANDOM_ANY_ENGINE_HPP +#define MPT_RANDOM_ANY_ENGINE_HPP + + + +#include "mpt/base/macros.hpp" +#include "mpt/base/namespace.hpp" +#include "mpt/random/random.hpp" + +#include +#include + +#include + + +namespace mpt { +inline namespace MPT_INLINE_NS { + + +template +class any_engine { +public: + using result_type = Tvalue; +protected: + any_engine() = default; +public: + static MPT_CONSTEXPRINLINE result_type min() { + return static_cast(std::numeric_limits::min()); + } + static MPT_CONSTEXPRINLINE result_type max() { + return static_cast(std::numeric_limits::max()); + } + static MPT_CONSTEXPRINLINE int result_bits() { + return static_cast(sizeof(result_type) * 8); + } +public: + virtual result_type operator()() = 0; +public: + virtual ~any_engine() = default; +}; + + +template +class any_engine_wrapper : public any_engine { +private: + Trng & m_prng; +public: + any_engine_wrapper(Trng & prng) + : m_prng(prng) { + return; + } +public: + typename any_engine::result_type operator()() override { + return mpt::random::result_type>(m_prng); + } +public: + virtual ~any_engine_wrapper() = default; +}; + + +} // namespace MPT_INLINE_NS +} // namespace mpt + + + +#endif // MPT_RANDOM_ANY_ENGINE_HPP diff --git a/Frameworks/OpenMPT/OpenMPT/src/mpt/random/device.hpp b/Frameworks/OpenMPT/OpenMPT/src/mpt/random/device.hpp index 213b48cea..3c62fb9c1 100644 --- a/Frameworks/OpenMPT/OpenMPT/src/mpt/random/device.hpp +++ b/Frameworks/OpenMPT/OpenMPT/src/mpt/random/device.hpp @@ -129,7 +129,6 @@ private: std::string token; #if !defined(MPT_COMPILER_QUIRK_RANDOM_NO_RANDOM_DEVICE) std::unique_ptr prd; - bool rd_reliable{false}; #endif // !MPT_COMPILER_QUIRK_RANDOM_NO_RANDOM_DEVICE std::unique_ptr rd_fallback; @@ -165,13 +164,12 @@ public: #if !defined(MPT_COMPILER_QUIRK_RANDOM_NO_RANDOM_DEVICE) try { prd = std::make_unique(); - rd_reliable = ((*prd).entropy() > 0.0); + if (!((*prd).entropy() > 0.0)) { + init_fallback(); + } } catch (mpt::out_of_memory e) { mpt::rethrow_out_of_memory(e); } catch (const std::exception &) { - rd_reliable = false; - } - if (!rd_reliable) { init_fallback(); } #else // MPT_COMPILER_QUIRK_RANDOM_NO_RANDOM_DEVICE @@ -183,13 +181,12 @@ public: #if !defined(MPT_COMPILER_QUIRK_RANDOM_NO_RANDOM_DEVICE) try { prd = std::make_unique(token); - rd_reliable = ((*prd).entropy() > 0.0); + if (!((*prd).entropy() > 0.0)) { + init_fallback(); + } } catch (mpt::out_of_memory e) { mpt::rethrow_out_of_memory(e); } catch (const std::exception &) { - rd_reliable = false; - } - if (!rd_reliable) { init_fallback(); } #else // MPT_COMPILER_QUIRK_RANDOM_NO_RANDOM_DEVICE @@ -240,13 +237,10 @@ public: } } } catch (const std::exception &) { - rd_reliable = false; init_fallback(); } - } else { - rd_reliable = false; } - if (!rd_reliable) { + if (rd_fallback) { // std::random_device is unreliable // XOR the generated random number with more entropy from the time-seeded // PRNG. @@ -309,7 +303,7 @@ public: }; -using deterministc_random_device = mpt::prng_random_device; +using deterministic_random_device = mpt::prng_random_device; } // namespace MPT_INLINE_NS diff --git a/Frameworks/OpenMPT/OpenMPT/src/mpt/random/engine.hpp b/Frameworks/OpenMPT/OpenMPT/src/mpt/random/engine.hpp index a7ef27193..6af34cb78 100644 --- a/Frameworks/OpenMPT/OpenMPT/src/mpt/random/engine.hpp +++ b/Frameworks/OpenMPT/OpenMPT/src/mpt/random/engine.hpp @@ -7,10 +7,7 @@ #include "mpt/base/macros.hpp" #include "mpt/base/namespace.hpp" -#include "mpt/base/numeric.hpp" -#include "mpt/random/seed.hpp" -#include #include #include @@ -20,139 +17,109 @@ namespace mpt { inline namespace MPT_INLINE_NS { +template +struct engine_seed_traits { + static inline constexpr std::size_t seed_bits = Trng::seed_bits; +}; + template struct engine_traits { - typedef typename Trng::result_type result_type; + using result_type = typename Trng::result_type; static MPT_CONSTEXPRINLINE int result_bits() { return Trng::result_bits(); } - template - static inline Trng make(Trd & rd) { - return Trng(rd); - } }; // C++11 random does not provide any sane way to determine the amount of entropy // required to seed a particular engine. VERY STUPID. // List the ones we are likely to use. +template <> +struct engine_seed_traits { + static inline constexpr std::size_t seed_bits = sizeof(std::mt19937::result_type) * 8 * std::mt19937::state_size; +}; + template <> struct engine_traits { - enum : std::size_t { - seed_bits = sizeof(std::mt19937::result_type) * 8 * std::mt19937::state_size - }; - typedef std::mt19937 rng_type; - typedef rng_type::result_type result_type; + using rng_type = std::mt19937; + using result_type = rng_type::result_type; static MPT_CONSTEXPRINLINE int result_bits() { return rng_type::word_size; } - template - static inline rng_type make(Trd & rd) { - std::unique_ptr(seed_bits, sizeof(unsigned int) * 8) / (sizeof(unsigned int) * 8)>> values = std::make_unique(seed_bits, sizeof(unsigned int) * 8) / (sizeof(unsigned int) * 8)>>(rd); - std::seed_seq seed(values->begin(), values->end()); - return rng_type(seed); - } +}; + +template <> +struct engine_seed_traits { + static inline constexpr std::size_t seed_bits = sizeof(std::mt19937_64::result_type) * 8 * std::mt19937_64::state_size; }; template <> struct engine_traits { - enum : std::size_t { - seed_bits = sizeof(std::mt19937_64::result_type) * 8 * std::mt19937_64::state_size - }; - typedef std::mt19937_64 rng_type; - typedef rng_type::result_type result_type; + using rng_type = std::mt19937_64; + using result_type = rng_type::result_type; static MPT_CONSTEXPRINLINE int result_bits() { return rng_type::word_size; } - template - static inline rng_type make(Trd & rd) { - std::unique_ptr(seed_bits, sizeof(unsigned int) * 8) / (sizeof(unsigned int) * 8)>> values = std::make_unique(seed_bits, sizeof(unsigned int) * 8) / (sizeof(unsigned int) * 8)>>(rd); - std::seed_seq seed(values->begin(), values->end()); - return rng_type(seed); - } +}; + +template <> +struct engine_seed_traits { + static inline constexpr std::size_t seed_bits = std::ranlux24_base::word_size; }; template <> struct engine_traits { - enum : std::size_t { - seed_bits = std::ranlux24_base::word_size - }; - typedef std::ranlux24_base rng_type; - typedef rng_type::result_type result_type; + using rng_type = std::ranlux24_base; + using result_type = rng_type::result_type; static MPT_CONSTEXPRINLINE int result_bits() { return rng_type::word_size; } - template - static inline rng_type make(Trd & rd) { - mpt::seed_seq_values(seed_bits, sizeof(unsigned int) * 8) / (sizeof(unsigned int) * 8)> values(rd); - std::seed_seq seed(values.begin(), values.end()); - return rng_type(seed); - } +}; + +template <> +struct engine_seed_traits { + static inline constexpr std::size_t seed_bits = std::ranlux48_base::word_size; }; template <> struct engine_traits { - enum : std::size_t { - seed_bits = std::ranlux48_base::word_size - }; - typedef std::ranlux48_base rng_type; - typedef rng_type::result_type result_type; + using rng_type = std::ranlux48_base; + using result_type = rng_type::result_type; static MPT_CONSTEXPRINLINE int result_bits() { return rng_type::word_size; } - template - static inline rng_type make(Trd & rd) { - mpt::seed_seq_values(seed_bits, sizeof(unsigned int) * 8) / (sizeof(unsigned int) * 8)> values(rd); - std::seed_seq seed(values.begin(), values.end()); - return rng_type(seed); - } +}; + +template <> +struct engine_seed_traits { + static inline constexpr std::size_t seed_bits = std::ranlux24_base::word_size; }; template <> struct engine_traits { - enum : std::size_t { - seed_bits = std::ranlux24_base::word_size - }; - typedef std::ranlux24 rng_type; - typedef rng_type::result_type result_type; + using rng_type = std::ranlux24; + using result_type = rng_type::result_type; static MPT_CONSTEXPRINLINE int result_bits() { return std::ranlux24_base::word_size; } - template - static inline rng_type make(Trd & rd) { - mpt::seed_seq_values(seed_bits, sizeof(unsigned int) * 8) / (sizeof(unsigned int) * 8)> values(rd); - std::seed_seq seed(values.begin(), values.end()); - return rng_type(seed); - } +}; + +template <> +struct engine_seed_traits { + static inline constexpr std::size_t seed_bits = std::ranlux48_base::word_size; }; template <> struct engine_traits { - enum : std::size_t { - seed_bits = std::ranlux48_base::word_size - }; - typedef std::ranlux48 rng_type; - typedef rng_type::result_type result_type; + using rng_type = std::ranlux48; + using result_type = rng_type::result_type; static MPT_CONSTEXPRINLINE int result_bits() { return std::ranlux48_base::word_size; } - template - static inline rng_type make(Trd & rd) { - mpt::seed_seq_values(seed_bits, sizeof(unsigned int) * 8) / (sizeof(unsigned int) * 8)> values(rd); - std::seed_seq seed(values.begin(), values.end()); - return rng_type(seed); - } }; -template -inline Trng make_prng(Trd & rd) { - return mpt::engine_traits::make(rd); -} - - - } // namespace MPT_INLINE_NS } // namespace mpt diff --git a/Frameworks/OpenMPT/OpenMPT/src/mpt/random/engine_lcg.hpp b/Frameworks/OpenMPT/OpenMPT/src/mpt/random/engine_lcg.hpp index 68bc68a78..1b6441430 100644 --- a/Frameworks/OpenMPT/OpenMPT/src/mpt/random/engine_lcg.hpp +++ b/Frameworks/OpenMPT/OpenMPT/src/mpt/random/engine_lcg.hpp @@ -10,10 +10,12 @@ #include "mpt/base/integer.hpp" #include "mpt/base/namespace.hpp" #include "mpt/base/numeric.hpp" -#include "mpt/random/random.hpp" +#include #include +#include + namespace mpt { @@ -30,14 +32,27 @@ class lcg_engine { public: typedef Tstate state_type; typedef Tvalue result_type; + static inline constexpr std::size_t seed_bits = sizeof(state_type) * 8; private: state_type state; +private: + static inline state_type seed_state(std::seed_seq & seed) { + state_type result = 0; + std::array(seed_bits, sizeof(unsigned int) * 8) / (sizeof(unsigned int) * 8)> seeds = {}; + seed.generate(seeds.begin(), seeds.end()); + for (const auto & seed_value : seeds) { + result <<= 16; + result <<= 16; + result |= static_cast(seed_value); + } + return result; + } + public: - template - explicit inline lcg_engine(Trng & rd) - : state(mpt::random(rd)) { + explicit inline lcg_engine(std::seed_seq & seed) + : state(seed_state(seed)) { operator()(); // we return results from the current state and update state after returning. results in better pipelining. } explicit inline lcg_engine(state_type seed) diff --git a/Frameworks/OpenMPT/OpenMPT/src/mpt/random/random.hpp b/Frameworks/OpenMPT/OpenMPT/src/mpt/random/random.hpp index a0a9fdfa4..177460293 100644 --- a/Frameworks/OpenMPT/OpenMPT/src/mpt/random/random.hpp +++ b/Frameworks/OpenMPT/OpenMPT/src/mpt/random/random.hpp @@ -5,6 +5,7 @@ +#include "mpt/base/detect.hpp" #include "mpt/base/namespace.hpp" #include "mpt/random/engine.hpp" @@ -98,10 +99,36 @@ public: }; -template +template ::value, bool>::type = true> +inline T random(Trng & rng, T min, T max) { +#if MPT_COMPILER_MSVC +#pragma warning(push) +#pragma warning(disable : 4018) // '<': signed/unsigned mismatch +#endif // MPT_COMPILER_MSVC + static_assert(std::numeric_limits::is_integer); + if constexpr (std::is_same::value) { + using dis_type = std::uniform_int_distribution; + dis_type dis(min, max); + return static_cast(dis(rng)); + } else if constexpr (std::is_same::value) { + using dis_type = std::uniform_int_distribution; + dis_type dis(min, max); + return static_cast(dis(rng)); + } else { + using dis_type = std::uniform_int_distribution; + dis_type dis(min, max); + return static_cast(dis(rng)); + } +#if MPT_COMPILER_MSVC +#pragma warning(pop) +#endif // MPT_COMPILER_MSVC +} + + +template ::value, bool>::type = true> inline T random(Trng & rng, T min, T max) { static_assert(!std::numeric_limits::is_integer); - typedef mpt::uniform_real_distribution dis_type; + using dis_type = mpt::uniform_real_distribution; dis_type dis(min, max); return static_cast(dis(rng)); } diff --git a/Frameworks/OpenMPT/OpenMPT/src/mpt/random/seed.hpp b/Frameworks/OpenMPT/OpenMPT/src/mpt/random/seed.hpp index e6ca4bd5e..b7e03c7e1 100644 --- a/Frameworks/OpenMPT/OpenMPT/src/mpt/random/seed.hpp +++ b/Frameworks/OpenMPT/OpenMPT/src/mpt/random/seed.hpp @@ -6,8 +6,12 @@ #include "mpt/base/namespace.hpp" +#include "mpt/base/numeric.hpp" +#include "mpt/random/engine.hpp" +#include "mpt/random/random.hpp" #include +#include #include #include @@ -26,9 +30,8 @@ private: public: template explicit seed_seq_values(Trd & rd) { - std::uniform_int_distribution random_int{}; for (std::size_t i = 0; i < N; ++i) { - seeds[i] = random_int(rd); + seeds[i] = mpt::random(rd); } } const unsigned int * begin() const { @@ -41,6 +44,22 @@ public: +template +inline Trng make_prng(Trd & rd) { + constexpr std::size_t num_seed_values = mpt::align_up(mpt::engine_seed_traits::seed_bits, sizeof(unsigned int) * 8) / (sizeof(unsigned int) * 8); + if constexpr (num_seed_values > 128) { + std::unique_ptr> values = std::make_unique>(rd); + std::seed_seq seed(values.begin(), values.end()); + return Trng(seed); + } else { + mpt::seed_seq_values values(rd); + std::seed_seq seed(values.begin(), values.end()); + return Trng(seed); + } +} + + + } // namespace MPT_INLINE_NS } // namespace mpt diff --git a/Frameworks/OpenMPT/OpenMPT/src/mpt/random/tests/tests_random.hpp b/Frameworks/OpenMPT/OpenMPT/src/mpt/random/tests/tests_random.hpp index 1076b583a..b3771bd61 100644 --- a/Frameworks/OpenMPT/OpenMPT/src/mpt/random/tests/tests_random.hpp +++ b/Frameworks/OpenMPT/OpenMPT/src/mpt/random/tests/tests_random.hpp @@ -9,9 +9,11 @@ #include "mpt/base/detect_compiler.hpp" #include "mpt/base/integer.hpp" #include "mpt/base/namespace.hpp" +#include "mpt/random/any_engine.hpp" #include "mpt/random/default_engines.hpp" #include "mpt/random/device.hpp" #include "mpt/random/random.hpp" +#include "mpt/random/seed.hpp" #include "mpt/test/test.hpp" #include "mpt/test/test_macros.hpp" @@ -38,6 +40,8 @@ MPT_TEST_GROUP_INLINE("mpt/random") { mpt::sane_random_device rd; mpt::good_engine prng = mpt::make_prng(rd); + mpt::any_engine_wrapper prng64{prng}; + mpt::any_engine_wrapper prng8{prng}; bool failed = false; @@ -61,6 +65,11 @@ MPT_TEST_GROUP_INLINE("mpt/random") failed = failed || !mpt::is_in_range(mpt::random(prng, 9), 0, 511); failed = failed || !mpt::is_in_range(mpt::random(prng, 1), 0, 1); + failed = failed || !mpt::is_in_range(mpt::random(prng, -42, 69), -42, 69); + failed = failed || !mpt::is_in_range(mpt::random(prng, -42, 69), -42, 69); + failed = failed || !mpt::is_in_range(mpt::random(prng, -42, 69), -42, 69); + failed = failed || !mpt::is_in_range(mpt::random(prng, -42, 69), -42, 69); + failed = failed || !mpt::is_in_range(mpt::random(prng, 0.0f, 1.0f), 0.0f, 1.0f); failed = failed || !mpt::is_in_range(mpt::random(prng, 0.0, 1.0), 0.0, 1.0); failed = failed || !mpt::is_in_range(mpt::random(prng, -1.0, 1.0), -1.0, 1.0); @@ -69,6 +78,72 @@ MPT_TEST_GROUP_INLINE("mpt/random") failed = failed || !mpt::is_in_range(mpt::random(prng, 1.0, 3.0), 1.0, 3.0); } + for (std::size_t i = 0; i < 10000; ++i) { + + failed = failed || !mpt::is_in_range(mpt::random(prng64), 0u, 127u); + failed = failed || !mpt::is_in_range(mpt::random(prng64), 0u, 255u); + failed = failed || !mpt::is_in_range(mpt::random(prng64), 0u, 511u); + failed = failed || !mpt::is_in_range(mpt::random(prng64), 0u, 1u); + failed = failed || !mpt::is_in_range(mpt::random(prng64, 7), 0u, 127u); + failed = failed || !mpt::is_in_range(mpt::random(prng64, 8), 0u, 255u); + failed = failed || !mpt::is_in_range(mpt::random(prng64, 9), 0u, 511u); + failed = failed || !mpt::is_in_range(mpt::random(prng64, 1), 0u, 1u); + + failed = failed || !mpt::is_in_range(mpt::random(prng64), 0, 127); + failed = failed || !mpt::is_in_range(mpt::random(prng64), 0, 255); + failed = failed || !mpt::is_in_range(mpt::random(prng64), 0, 511); + failed = failed || !mpt::is_in_range(mpt::random(prng64), 0, 1); + failed = failed || !mpt::is_in_range(mpt::random(prng64, 7), 0, 127); + failed = failed || !mpt::is_in_range(mpt::random(prng64, 8), 0, 255); + failed = failed || !mpt::is_in_range(mpt::random(prng64, 9), 0, 511); + failed = failed || !mpt::is_in_range(mpt::random(prng64, 1), 0, 1); + + failed = failed || !mpt::is_in_range(mpt::random(prng64, -42, 69), -42, 69); + failed = failed || !mpt::is_in_range(mpt::random(prng64, -42, 69), -42, 69); + failed = failed || !mpt::is_in_range(mpt::random(prng64, -42, 69), -42, 69); + failed = failed || !mpt::is_in_range(mpt::random(prng64, -42, 69), -42, 69); + + failed = failed || !mpt::is_in_range(mpt::random(prng64, 0.0f, 1.0f), 0.0f, 1.0f); + failed = failed || !mpt::is_in_range(mpt::random(prng64, 0.0, 1.0), 0.0, 1.0); + failed = failed || !mpt::is_in_range(mpt::random(prng64, -1.0, 1.0), -1.0, 1.0); + failed = failed || !mpt::is_in_range(mpt::random(prng64, -1.0, 0.0), -1.0, 0.0); + failed = failed || !mpt::is_in_range(mpt::random(prng64, 1.0, 2.0), 1.0, 2.0); + failed = failed || !mpt::is_in_range(mpt::random(prng64, 1.0, 3.0), 1.0, 3.0); + } + + for (std::size_t i = 0; i < 10000; ++i) { + + failed = failed || !mpt::is_in_range(mpt::random(prng8), 0u, 127u); + failed = failed || !mpt::is_in_range(mpt::random(prng8), 0u, 255u); + failed = failed || !mpt::is_in_range(mpt::random(prng8), 0u, 511u); + failed = failed || !mpt::is_in_range(mpt::random(prng8), 0u, 1u); + failed = failed || !mpt::is_in_range(mpt::random(prng8, 7), 0u, 127u); + failed = failed || !mpt::is_in_range(mpt::random(prng8, 8), 0u, 255u); + failed = failed || !mpt::is_in_range(mpt::random(prng8, 9), 0u, 511u); + failed = failed || !mpt::is_in_range(mpt::random(prng8, 1), 0u, 1u); + + failed = failed || !mpt::is_in_range(mpt::random(prng8), 0, 127); + failed = failed || !mpt::is_in_range(mpt::random(prng8), 0, 255); + failed = failed || !mpt::is_in_range(mpt::random(prng8), 0, 511); + failed = failed || !mpt::is_in_range(mpt::random(prng8), 0, 1); + failed = failed || !mpt::is_in_range(mpt::random(prng8, 7), 0, 127); + failed = failed || !mpt::is_in_range(mpt::random(prng8, 8), 0, 255); + failed = failed || !mpt::is_in_range(mpt::random(prng8, 9), 0, 511); + failed = failed || !mpt::is_in_range(mpt::random(prng8, 1), 0, 1); + + failed = failed || !mpt::is_in_range(mpt::random(prng8, -42, 69), -42, 69); + failed = failed || !mpt::is_in_range(mpt::random(prng8, -42, 69), -42, 69); + failed = failed || !mpt::is_in_range(mpt::random(prng8, -42, 69), -42, 69); + failed = failed || !mpt::is_in_range(mpt::random(prng8, -42, 69), -42, 69); + + failed = failed || !mpt::is_in_range(mpt::random(prng8, 0.0f, 1.0f), 0.0f, 1.0f); + failed = failed || !mpt::is_in_range(mpt::random(prng8, 0.0, 1.0), 0.0, 1.0); + failed = failed || !mpt::is_in_range(mpt::random(prng8, -1.0, 1.0), -1.0, 1.0); + failed = failed || !mpt::is_in_range(mpt::random(prng8, -1.0, 0.0), -1.0, 0.0); + failed = failed || !mpt::is_in_range(mpt::random(prng8, 1.0, 2.0), 1.0, 2.0); + failed = failed || !mpt::is_in_range(mpt::random(prng8, 1.0, 3.0), 1.0, 3.0); + } + MPT_TEST_EXPECT_EXPR(!failed); } diff --git a/Frameworks/OpenMPT/OpenMPT/src/mpt/string/buffer.hpp b/Frameworks/OpenMPT/OpenMPT/src/mpt/string/buffer.hpp index f36f3e6cb..6b19cde48 100644 --- a/Frameworks/OpenMPT/OpenMPT/src/mpt/string/buffer.hpp +++ b/Frameworks/OpenMPT/OpenMPT/src/mpt/string/buffer.hpp @@ -307,16 +307,18 @@ public: } public: - Tchar buf[len]; + Tchar buf[len]{}; public: - charbuf() { - std::fill(std::begin(buf), std::end(buf), char_constants::null); + constexpr charbuf() { + for (std::size_t i = 0; i < len; ++i) { + buf[i] = char_constants::null; + } } - charbuf(const charbuf &) = default; - charbuf(charbuf &&) = default; - charbuf & operator=(const charbuf &) = default; - charbuf & operator=(charbuf &&) = default; + constexpr charbuf(const charbuf &) = default; + constexpr charbuf(charbuf &&) = default; + constexpr charbuf & operator=(const charbuf &) = default; + constexpr charbuf & operator=(charbuf &&) = default; const Tchar & operator[](std::size_t i) const { return buf[i]; } diff --git a/Frameworks/OpenMPT/OpenMPT/src/mpt/string/types.hpp b/Frameworks/OpenMPT/OpenMPT/src/mpt/string/types.hpp index a446739dd..93f67f88d 100644 --- a/Frameworks/OpenMPT/OpenMPT/src/mpt/string/types.hpp +++ b/Frameworks/OpenMPT/OpenMPT/src/mpt/string/types.hpp @@ -185,16 +185,18 @@ constexpr Tdstchar unsafe_char_convert(Tsrcchar src) noexcept { using widestring = std::wstring; using widestring_view = std::wstring_view; using widechar = wchar_t; -#define MPT_WIDECHAR(x) L##x -#define MPT_WIDELITERAL(x) L##x -#define MPT_WIDESTRING(x) std::wstring(L##x) +#define MPT_WIDECHAR(x) L##x +#define MPT_WIDELITERAL(x) L##x +#define MPT_WIDESTRING(x) std::wstring(L##x) +#define MPT_WIDESTRINGVIEW(x) std::wstring_view(L##x) #else // MPT_COMPILER_QUIRK_NO_WCHAR using widestring = std::u32string; using widestring_view = std::u32string_view; using widechar = char32_t; -#define MPT_WIDECHAR(x) U##x -#define MPT_WIDELITERAL(x) U##x -#define MPT_WIDESTRING(x) std::u32string(U##x) +#define MPT_WIDECHAR(x) U##x +#define MPT_WIDELITERAL(x) U##x +#define MPT_WIDESTRING(x) std::u32string(U##x) +#define MPT_WIDESTRINGVIEW(x) std::u32string_view(U##x) #endif // !MPT_COMPILER_QUIRK_NO_WCHAR @@ -283,10 +285,10 @@ using winstring_view = mpt::tstring_view; using u8string = std::u8string; using u8string_view = std::u8string_view; using u8char = char8_t; -#define MPT_U8CHAR(x) u8##x -#define MPT_U8LITERAL(x) u8##x -#define MPT_U8STRING(x) std::u8string(u8##x) -#define MPT_U8STRINVIEW(x) std::u8string_view(u8##x) +#define MPT_U8CHAR(x) u8##x +#define MPT_U8LITERAL(x) u8##x +#define MPT_U8STRING(x) std::u8string(u8##x) +#define MPT_U8STRINGVIEW(x) std::u8string_view(u8##x) #else // !C++20 diff --git a/Frameworks/OpenMPT/OpenMPT/src/mpt/string_transcode/tests/tests_string_transcode.hpp b/Frameworks/OpenMPT/OpenMPT/src/mpt/string_transcode/tests/tests_string_transcode.hpp index 6b852c9f1..49ff47f22 100644 --- a/Frameworks/OpenMPT/OpenMPT/src/mpt/string_transcode/tests/tests_string_transcode.hpp +++ b/Frameworks/OpenMPT/OpenMPT/src/mpt/string_transcode/tests/tests_string_transcode.hpp @@ -47,6 +47,8 @@ MPT_TEST_GROUP_INLINE("mpt/string_transcode") MPT_TEST_EXPECT_EQUAL(mpt::transcode(mpt::logical_encoding::locale, MPT_USTRING("a")), "a"); MPT_TEST_EXPECT_EQUAL(mpt::transcode(mpt::logical_encoding::locale, "a"), MPT_USTRING("a")); + MPT_TEST_EXPECT_EQUAL(mpt::transcode(mpt::logical_encoding::active_locale, MPT_USTRING("a")), "a"); + MPT_TEST_EXPECT_EQUAL(mpt::transcode(mpt::logical_encoding::active_locale, "a"), MPT_USTRING("a")); MPT_TEST_EXPECT_EQUAL(mpt::transcode(mpt::common_encoding::utf8, MPT_UTF8_STRING("a")), "a"); MPT_TEST_EXPECT_EQUAL(mpt::transcode(mpt::common_encoding::iso8859_1, MPT_UTF8_STRING("a")), "a"); @@ -57,6 +59,8 @@ MPT_TEST_GROUP_INLINE("mpt/string_transcode") MPT_TEST_EXPECT_EQUAL(mpt::transcode(mpt::logical_encoding::locale, MPT_UTF8_STRING("a")), "a"); MPT_TEST_EXPECT_EQUAL(mpt::transcode(mpt::logical_encoding::locale, "a"), MPT_UTF8_STRING("a")); + MPT_TEST_EXPECT_EQUAL(mpt::transcode(mpt::logical_encoding::active_locale, MPT_UTF8_STRING("a")), "a"); + MPT_TEST_EXPECT_EQUAL(mpt::transcode(mpt::logical_encoding::active_locale, "a"), MPT_UTF8_STRING("a")); #if MPT_OS_EMSCRIPTEN MPT_TEST_EXPECT_EQUAL(mpt::transcode(mpt::logical_encoding::locale, MPT_UTF8_STRING("\xe2\x8c\x82")), "\xe2\x8c\x82"); @@ -76,6 +80,8 @@ MPT_TEST_GROUP_INLINE("mpt/string_transcode") MPT_TEST_EXPECT_EXPR(mpt::ends_with(mpt::transcode(mpt::logical_encoding::locale, MPT_UTF8_STRING("abc\xC3\xA4xyz")), "xyz")); MPT_TEST_EXPECT_EXPR(mpt::starts_with(mpt::transcode(mpt::logical_encoding::locale, MPT_UTF8_STRING("abc\xC3\xA4xyz")), "abc")); + MPT_TEST_EXPECT_EXPR(mpt::ends_with(mpt::transcode(mpt::logical_encoding::active_locale, MPT_UTF8_STRING("abc\xC3\xA4xyz")), "xyz")); + MPT_TEST_EXPECT_EXPR(mpt::starts_with(mpt::transcode(mpt::logical_encoding::active_locale, MPT_UTF8_STRING("abc\xC3\xA4xyz")), "abc")); MPT_TEST_EXPECT_EXPR(mpt::ends_with(mpt::transcode(mpt::common_encoding::ascii, MPT_UTF8_STRING("abc\xE5\xAE\xB6xyz")), "xyz")); MPT_TEST_EXPECT_EXPR(mpt::ends_with(mpt::transcode(mpt::common_encoding::iso8859_1, MPT_UTF8_STRING("abc\xE5\xAE\xB6xyz")), "xyz")); @@ -88,6 +94,8 @@ MPT_TEST_GROUP_INLINE("mpt/string_transcode") MPT_TEST_EXPECT_EXPR(mpt::ends_with(mpt::transcode(mpt::logical_encoding::locale, MPT_UTF8_STRING("abc\xE5\xAE\xB6xyz")), "xyz")); MPT_TEST_EXPECT_EXPR(mpt::starts_with(mpt::transcode(mpt::logical_encoding::locale, MPT_UTF8_STRING("abc\xE5\xAE\xB6xyz")), "abc")); + MPT_TEST_EXPECT_EXPR(mpt::ends_with(mpt::transcode(mpt::logical_encoding::active_locale, MPT_UTF8_STRING("abc\xE5\xAE\xB6xyz")), "xyz")); + MPT_TEST_EXPECT_EXPR(mpt::starts_with(mpt::transcode(mpt::logical_encoding::active_locale, MPT_UTF8_STRING("abc\xE5\xAE\xB6xyz")), "abc")); MPT_TEST_EXPECT_EXPR(mpt::ends_with(mpt::transcode(mpt::common_encoding::ascii, "abc\xC3\xA4xyz"), MPT_USTRING("xyz"))); MPT_TEST_EXPECT_EXPR(mpt::ends_with(mpt::transcode(mpt::common_encoding::iso8859_1, "abc\xC3\xA4xyz"), MPT_USTRING("xyz"))); @@ -100,6 +108,8 @@ MPT_TEST_GROUP_INLINE("mpt/string_transcode") MPT_TEST_EXPECT_EXPR(mpt::ends_with(mpt::transcode(mpt::logical_encoding::locale, "abc\xC3\xA4xyz"), MPT_USTRING("xyz"))); MPT_TEST_EXPECT_EXPR(mpt::starts_with(mpt::transcode(mpt::logical_encoding::locale, "abc\xC3\xA4xyz"), MPT_USTRING("abc"))); + MPT_TEST_EXPECT_EXPR(mpt::ends_with(mpt::transcode(mpt::logical_encoding::active_locale, "abc\xC3\xA4xyz"), MPT_USTRING("xyz"))); + MPT_TEST_EXPECT_EXPR(mpt::starts_with(mpt::transcode(mpt::logical_encoding::active_locale, "abc\xC3\xA4xyz"), MPT_USTRING("abc"))); MPT_TEST_EXPECT_EXPR(mpt::ends_with(mpt::transcode(mpt::common_encoding::ascii, "abc\xE5\xAE\xB6xyz"), MPT_USTRING("xyz"))); MPT_TEST_EXPECT_EXPR(mpt::ends_with(mpt::transcode(mpt::common_encoding::iso8859_1, "abc\xE5\xAE\xB6xyz"), MPT_USTRING("xyz"))); @@ -112,6 +122,8 @@ MPT_TEST_GROUP_INLINE("mpt/string_transcode") MPT_TEST_EXPECT_EXPR(mpt::ends_with(mpt::transcode(mpt::logical_encoding::locale, "abc\xE5\xAE\xB6xyz"), MPT_USTRING("xyz"))); MPT_TEST_EXPECT_EXPR(mpt::starts_with(mpt::transcode(mpt::logical_encoding::locale, "abc\xE5\xAE\xB6xyz"), MPT_USTRING("abc"))); + MPT_TEST_EXPECT_EXPR(mpt::ends_with(mpt::transcode(mpt::logical_encoding::active_locale, "abc\xE5\xAE\xB6xyz"), MPT_USTRING("xyz"))); + MPT_TEST_EXPECT_EXPR(mpt::starts_with(mpt::transcode(mpt::logical_encoding::active_locale, "abc\xE5\xAE\xB6xyz"), MPT_USTRING("abc"))); // Check that characters are correctly converted // We test german umlaut-a (U+00E4) and CJK U+5BB6 @@ -156,6 +168,8 @@ MPT_TEST_GROUP_INLINE("mpt/string_transcode") MPT_TEST_EXPECT_EQUAL(mpt::transcode(mpt::logical_encoding::locale, L"a"), "a"); MPT_TEST_EXPECT_EQUAL(mpt::transcode(mpt::logical_encoding::locale, "a"), L"a"); + MPT_TEST_EXPECT_EQUAL(mpt::transcode(mpt::logical_encoding::active_locale, L"a"), "a"); + MPT_TEST_EXPECT_EQUAL(mpt::transcode(mpt::logical_encoding::active_locale, "a"), L"a"); // Check that some character replacement is done (and not just empty strings or truncated strings are returned) // We test german umlaut-a (U+00E4) and CJK U+5BB6 @@ -176,6 +190,8 @@ MPT_TEST_GROUP_INLINE("mpt/string_transcode") MPT_TEST_EXPECT_EXPR(mpt::ends_with(mpt::transcode(mpt::logical_encoding::locale, L"abc\u00E4xyz"), "xyz")); MPT_TEST_EXPECT_EXPR(mpt::starts_with(mpt::transcode(mpt::logical_encoding::locale, L"abc\u00E4xyz"), "abc")); + MPT_TEST_EXPECT_EXPR(mpt::ends_with(mpt::transcode(mpt::logical_encoding::active_locale, L"abc\u00E4xyz"), "xyz")); + MPT_TEST_EXPECT_EXPR(mpt::starts_with(mpt::transcode(mpt::logical_encoding::active_locale, L"abc\u00E4xyz"), "abc")); MPT_TEST_EXPECT_EXPR(mpt::ends_with(mpt::transcode(mpt::common_encoding::ascii, L"abc\u5BB6xyz"), "xyz")); MPT_TEST_EXPECT_EXPR(mpt::ends_with(mpt::transcode(mpt::common_encoding::iso8859_1, L"abc\u5BB6xyz"), "xyz")); @@ -188,6 +204,8 @@ MPT_TEST_GROUP_INLINE("mpt/string_transcode") MPT_TEST_EXPECT_EXPR(mpt::ends_with(mpt::transcode(mpt::logical_encoding::locale, L"abc\u5BB6xyz"), "xyz")); MPT_TEST_EXPECT_EXPR(mpt::starts_with(mpt::transcode(mpt::logical_encoding::locale, L"abc\u5BB6xyz"), "abc")); + MPT_TEST_EXPECT_EXPR(mpt::ends_with(mpt::transcode(mpt::logical_encoding::active_locale, L"abc\u5BB6xyz"), "xyz")); + MPT_TEST_EXPECT_EXPR(mpt::starts_with(mpt::transcode(mpt::logical_encoding::active_locale, L"abc\u5BB6xyz"), "abc")); MPT_TEST_EXPECT_EXPR(mpt::ends_with(mpt::transcode(mpt::common_encoding::ascii, "abc\xC3\xA4xyz"), L"xyz")); MPT_TEST_EXPECT_EXPR(mpt::ends_with(mpt::transcode(mpt::common_encoding::iso8859_1, "abc\xC3\xA4xyz"), L"xyz")); @@ -200,6 +218,8 @@ MPT_TEST_GROUP_INLINE("mpt/string_transcode") MPT_TEST_EXPECT_EXPR(mpt::ends_with(mpt::transcode(mpt::logical_encoding::locale, "abc\xC3\xA4xyz"), L"xyz")); MPT_TEST_EXPECT_EXPR(mpt::starts_with(mpt::transcode(mpt::logical_encoding::locale, "abc\xC3\xA4xyz"), L"abc")); + MPT_TEST_EXPECT_EXPR(mpt::ends_with(mpt::transcode(mpt::logical_encoding::active_locale, "abc\xC3\xA4xyz"), L"xyz")); + MPT_TEST_EXPECT_EXPR(mpt::starts_with(mpt::transcode(mpt::logical_encoding::active_locale, "abc\xC3\xA4xyz"), L"abc")); MPT_TEST_EXPECT_EXPR(mpt::ends_with(mpt::transcode(mpt::common_encoding::ascii, "abc\xE5\xAE\xB6xyz"), L"xyz")); MPT_TEST_EXPECT_EXPR(mpt::ends_with(mpt::transcode(mpt::common_encoding::iso8859_1, "abc\xE5\xAE\xB6xyz"), L"xyz")); @@ -212,6 +232,8 @@ MPT_TEST_GROUP_INLINE("mpt/string_transcode") MPT_TEST_EXPECT_EXPR(mpt::ends_with(mpt::transcode(mpt::logical_encoding::locale, "abc\xE5\xAE\xB6xyz"), L"xyz")); MPT_TEST_EXPECT_EXPR(mpt::starts_with(mpt::transcode(mpt::logical_encoding::locale, "abc\xE5\xAE\xB6xyz"), L"abc")); + MPT_TEST_EXPECT_EXPR(mpt::ends_with(mpt::transcode(mpt::logical_encoding::active_locale, "abc\xE5\xAE\xB6xyz"), L"xyz")); + MPT_TEST_EXPECT_EXPR(mpt::starts_with(mpt::transcode(mpt::logical_encoding::active_locale, "abc\xE5\xAE\xB6xyz"), L"abc")); // Check that characters are correctly converted // We test german umlaut-a (U+00E4) and CJK U+5BB6 diff --git a/Frameworks/OpenMPT/OpenMPT/src/mpt/string_transcode/transcode.hpp b/Frameworks/OpenMPT/OpenMPT/src/mpt/string_transcode/transcode.hpp index 67d9c0c2d..e5000b7cb 100644 --- a/Frameworks/OpenMPT/OpenMPT/src/mpt/string_transcode/transcode.hpp +++ b/Frameworks/OpenMPT/OpenMPT/src/mpt/string_transcode/transcode.hpp @@ -477,6 +477,98 @@ inline constexpr char32_t CharsetTableAtariST[256] = { 0x03B1, 0x03B2, 0x0393, 0x03C0, 0x03A3, 0x03C3, 0x00B5, 0x03C4, 0x03A6, 0x0398, 0x03A9, 0x03B4, 0x222E, 0x03C6, 0x2208, 0x2229, 0x2261, 0x00B1, 0x2265, 0x2264, 0x2320, 0x2321, 0x00F7, 0x2248, 0x00B0, 0x2219, 0x00B7, 0x221A, 0x207F, 0x00B2, 0x00B3, 0x00AF}; + + +template +inline void encode_single_utf16(Tdststring & out, char32_t ucs4) { + static_assert(sizeof(typename Tdststring::value_type) == 2); + if (ucs4 <= 0xffff) { + out.push_back(static_cast(static_cast(static_cast(ucs4)))); + } else { + uint32 surrogate = static_cast(ucs4) - 0x10000; + uint16 hi_sur = static_cast((0x36 << 10) | ((surrogate >> 10) & ((1 << 10) - 1))); + uint16 lo_sur = static_cast((0x37 << 10) | ((surrogate >> 0) & ((1 << 10) - 1))); + out.push_back(static_cast(hi_sur)); + out.push_back(static_cast(lo_sur)); + } +} + +template +inline char32_t decode_single_utf16(std::size_t & i, const Tsrcstring & in) { + static_assert(sizeof(typename Tsrcstring::value_type) == 2); + char32_t ucs4 = 0; + typename Tsrcstring::value_type wc = in[i]; + uint16 c = static_cast(wc); + if (i + 1 < in.length()) { + // check for surrogate pair + uint16 hi_sur = in[i + 0]; + uint16 lo_sur = in[i + 1]; + if (hi_sur >> 10 == 0x36 && lo_sur >> 10 == 0x37) { + // surrogate pair + ++i; + hi_sur &= (1 << 10) - 1; + lo_sur &= (1 << 10) - 1; + ucs4 = (static_cast(hi_sur) << 10) | (static_cast(lo_sur) << 0); + } else { + // no surrogate pair + ucs4 = static_cast(c); + } + } else { + // no surrogate possible + ucs4 = static_cast(c); + } + return ucs4; +} + + + +inline void encode_single_wide(mpt::widestring & out, char32_t ucs4) { + if constexpr (sizeof(mpt::widechar) == 2) { + if (ucs4 <= 0xffff) { + out.push_back(static_cast(static_cast(static_cast(ucs4)))); + } else { + uint32 surrogate = static_cast(ucs4) - 0x10000; + uint16 hi_sur = static_cast((0x36 << 10) | ((surrogate >> 10) & ((1 << 10) - 1))); + uint16 lo_sur = static_cast((0x37 << 10) | ((surrogate >> 0) & ((1 << 10) - 1))); + out.push_back(static_cast(hi_sur)); + out.push_back(static_cast(lo_sur)); + } + } else { + out.push_back(static_cast(static_cast(ucs4))); + } +} + +inline char32_t decode_single_wide(std::size_t & i, const mpt::widestring & in) { + char32_t ucs4 = 0; + mpt::widechar wc = in[i]; + if constexpr (sizeof(mpt::widechar) == 2) { + uint16 c = static_cast(wc); + if (i + 1 < in.length()) { + // check for surrogate pair + uint16 hi_sur = in[i + 0]; + uint16 lo_sur = in[i + 1]; + if (hi_sur >> 10 == 0x36 && lo_sur >> 10 == 0x37) { + // surrogate pair + ++i; + hi_sur &= (1 << 10) - 1; + lo_sur &= (1 << 10) - 1; + ucs4 = (static_cast(hi_sur) << 10) | (static_cast(lo_sur) << 0); + } else { + // no surrogate pair + ucs4 = static_cast(c); + } + } else { + // no surrogate possible + ucs4 = static_cast(c); + } + } else { + ucs4 = static_cast(static_cast(wc)); + } + return ucs4; +} + + + template inline mpt::widestring decode_8bit(const Tsrcstring & str, const char32_t (&table)[256], mpt::widechar replacement = MPT_WIDECHAR('\uFFFD')) { mpt::widestring res; @@ -484,7 +576,7 @@ inline mpt::widestring decode_8bit(const Tsrcstring & str, const char32_t (&tabl for (std::size_t i = 0; i < str.length(); ++i) { std::size_t c = static_cast(mpt::char_value(str[i])); if (c < std::size(table)) { - res.push_back(static_cast(table[c])); + encode_single_wide(res, table[c]); } else { res.push_back(replacement); } @@ -497,7 +589,7 @@ inline Tdststring encode_8bit(const mpt::widestring & str, const char32_t (&tabl Tdststring res; res.reserve(str.length()); for (std::size_t i = 0; i < str.length(); ++i) { - char32_t c = static_cast(str[i]); + char32_t c = decode_single_wide(i, str); bool found = false; // Try non-control characters first. // In cases where there are actual characters mirrored in this range (like in AMS/AMS2 character sets), @@ -535,7 +627,7 @@ inline mpt::widestring decode_8bit_no_c1(const Tsrcstring & str, const char32_t if ((0x80 <= c) && (c <= 0x9f)) { res.push_back(replacement); } else if (c < std::size(table)) { - res.push_back(static_cast(table[c])); + encode_single_wide(res, table[c]); } else { res.push_back(replacement); } @@ -548,7 +640,7 @@ inline Tdststring encode_8bit_no_c1(const mpt::widestring & str, const char32_t Tdststring res; res.reserve(str.length()); for (std::size_t i = 0; i < str.length(); ++i) { - char32_t c = static_cast(str[i]); + char32_t c = decode_single_wide(i, str); bool found = false; // Try non-control characters first. // In cases where there are actual characters mirrored in this range (like in AMS/AMS2 character sets), @@ -587,7 +679,7 @@ inline mpt::widestring decode_ascii(const Tsrcstring & str, mpt::widechar replac for (std::size_t i = 0; i < str.length(); ++i) { uint8 c = mpt::char_value(str[i]); if (c <= 0x7f) { - res.push_back(static_cast(static_cast(c))); + encode_single_wide(res, static_cast(static_cast(c))); } else { res.push_back(replacement); } @@ -600,9 +692,9 @@ inline Tdststring encode_ascii(const mpt::widestring & str, char replacement = ' Tdststring res; res.reserve(str.length()); for (std::size_t i = 0; i < str.length(); ++i) { - char32_t c = static_cast(str[i]); + char32_t c = decode_single_wide(i, str); if (c <= 0x7f) { - res.push_back(static_cast(static_cast(c))); + res.push_back(static_cast(static_cast(static_cast(c)))); } else { res.push_back(replacement); } @@ -617,7 +709,7 @@ inline mpt::widestring decode_iso8859_1(const Tsrcstring & str, mpt::widechar re res.reserve(str.length()); for (std::size_t i = 0; i < str.length(); ++i) { uint8 c = mpt::char_value(str[i]); - res.push_back(static_cast(static_cast(c))); + encode_single_wide(res, static_cast(static_cast(c))); } return res; } @@ -627,7 +719,7 @@ inline Tdststring encode_iso8859_1(const mpt::widestring & str, char replacement Tdststring res; res.reserve(str.length()); for (std::size_t i = 0; i < str.length(); ++i) { - char32_t c = static_cast(str[i]); + char32_t c = decode_single_wide(i, str); if (c <= 0xff) { res.push_back(static_cast(static_cast(c))); } else { @@ -681,18 +773,8 @@ inline mpt::widestring decode_utf8(const Tsrcstring & str, mpt::widechar replace charsleft = 0; continue; } - if (ucs4 <= 0xffff) { - out.push_back(static_cast(ucs4)); - } else { - uint32 surrogate = static_cast(ucs4) - 0x10000; - uint16 hi_sur = static_cast((0x36 << 10) | ((surrogate >> 10) & ((1 << 10) - 1))); - uint16 lo_sur = static_cast((0x37 << 10) | ((surrogate >> 0) & ((1 << 10) - 1))); - out.push_back(hi_sur); - out.push_back(lo_sur); - } - } else { - out.push_back(static_cast(ucs4)); } + encode_single_wide(out, ucs4); ucs4 = 0; } } @@ -710,31 +792,7 @@ inline Tdststring encode_utf8(const mpt::widestring & str, typename Tdststring:: const mpt::widestring & in = str; Tdststring out; for (std::size_t i = 0; i < in.length(); i++) { - mpt::widechar wc = in[i]; - char32_t ucs4 = 0; - if constexpr (sizeof(mpt::widechar) == 2) { - uint16 c = static_cast(wc); - if (i + 1 < in.length()) { - // check for surrogate pair - uint16 hi_sur = in[i + 0]; - uint16 lo_sur = in[i + 1]; - if (hi_sur >> 10 == 0x36 && lo_sur >> 10 == 0x37) { - // surrogate pair - ++i; - hi_sur &= (1 << 10) - 1; - lo_sur &= (1 << 10) - 1; - ucs4 = (static_cast(hi_sur) << 10) | (static_cast(lo_sur) << 0); - } else { - // no surrogate pair - ucs4 = static_cast(c); - } - } else { - // no surrogate possible - ucs4 = static_cast(c); - } - } else { - ucs4 = static_cast(wc); - } + char32_t ucs4 = decode_single_wide(i, in); if (ucs4 > 0x1fffff) { out.push_back(replacement); continue; @@ -783,27 +841,7 @@ inline Tdststring utf32_from_utf16(const Tsrcstring & in, mpt::widechar replacem Tdststring out; out.reserve(in.length()); for (std::size_t i = 0; i < in.length(); i++) { - char16_t wc = static_cast(static_cast(in[i])); - char32_t ucs4 = 0; - uint16 c = static_cast(wc); - if (i + 1 < in.length()) { - // check for surrogate pair - uint16 hi_sur = in[i + 0]; - uint16 lo_sur = in[i + 1]; - if (hi_sur >> 10 == 0x36 && lo_sur >> 10 == 0x37) { - // surrogate pair - ++i; - hi_sur &= (1 << 10) - 1; - lo_sur &= (1 << 10) - 1; - ucs4 = (static_cast(hi_sur) << 10) | (static_cast(lo_sur) << 0); - } else { - // no surrogate pair - ucs4 = static_cast(c); - } - } else { - // no surrogate possible - ucs4 = static_cast(c); - } + char32_t ucs4 = decode_single_utf16(i, in); out.push_back(static_cast(static_cast(ucs4))); } return out; @@ -821,15 +859,7 @@ inline Tdststring utf16_from_utf32(const Tsrcstring & in, mpt::widechar replacem out.push_back(static_cast(static_cast(replacement))); continue; } - if (ucs4 <= 0xffff) { - out.push_back(static_cast(static_cast(ucs4))); - } else { - uint32 surrogate = static_cast(ucs4) - 0x10000; - uint16 hi_sur = static_cast((0x36 << 10) | ((surrogate >> 10) & ((1 << 10) - 1))); - uint16 lo_sur = static_cast((0x37 << 10) | ((surrogate >> 0) & ((1 << 10) - 1))); - out.push_back(static_cast(hi_sur)); - out.push_back(static_cast(lo_sur)); - } + encode_single_utf16(out, ucs4); } return out; } diff --git a/Frameworks/OpenMPT/OpenMPT/src/mpt/uuid/tests/tests_uuid.hpp b/Frameworks/OpenMPT/OpenMPT/src/mpt/uuid/tests/tests_uuid.hpp index ef96cb671..9e979a629 100644 --- a/Frameworks/OpenMPT/OpenMPT/src/mpt/uuid/tests/tests_uuid.hpp +++ b/Frameworks/OpenMPT/OpenMPT/src/mpt/uuid/tests/tests_uuid.hpp @@ -9,6 +9,7 @@ #include "mpt/base/namespace.hpp" #include "mpt/random/default_engines.hpp" #include "mpt/random/device.hpp" +#include "mpt/random/seed.hpp" #include "mpt/string/types.hpp" #include "mpt/test/test.hpp" #include "mpt/test/test_macros.hpp" diff --git a/Frameworks/OpenMPT/OpenMPT/src/openmpt/all/PlatformFixes.hpp b/Frameworks/OpenMPT/OpenMPT/src/openmpt/all/PlatformFixes.hpp new file mode 100644 index 000000000..26cf98dbf --- /dev/null +++ b/Frameworks/OpenMPT/OpenMPT/src/openmpt/all/PlatformFixes.hpp @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */ + + +#pragma once + + + +#include "openmpt/all/BuildSettings.hpp" + + + +// immediate quirk fixups + +#if defined(MPT_LIBC_QUIRK_REQUIRES_SYS_TYPES_H) +#include +#endif diff --git a/Frameworks/OpenMPT/OpenMPT/src/openmpt/base/Endian.hpp b/Frameworks/OpenMPT/OpenMPT/src/openmpt/base/Endian.hpp index 772ec021a..bf48e0cfb 100644 --- a/Frameworks/OpenMPT/OpenMPT/src/openmpt/base/Endian.hpp +++ b/Frameworks/OpenMPT/OpenMPT/src/openmpt/base/Endian.hpp @@ -39,10 +39,10 @@ using uint8be = mpt::packed; -using IEEE754binary32LE = mpt::IEEE754binary_types::is_ieee754_binary32ne, mpt::endian::native>::IEEE754binary32LE; -using IEEE754binary32BE = mpt::IEEE754binary_types::is_ieee754_binary32ne, mpt::endian::native>::IEEE754binary32BE; -using IEEE754binary64LE = mpt::IEEE754binary_types::is_ieee754_binary64ne, mpt::endian::native>::IEEE754binary64LE; -using IEEE754binary64BE = mpt::IEEE754binary_types::is_ieee754_binary64ne, mpt::endian::native>::IEEE754binary64BE; +using IEEE754binary32LE = mpt::IEEE754binary_types::is_float32 && mpt::float_traits::is_ieee754_binary && mpt::float_traits::is_native_endian, mpt::endian::native>::IEEE754binary32LE; +using IEEE754binary32BE = mpt::IEEE754binary_types::is_float32 && mpt::float_traits::is_ieee754_binary && mpt::float_traits::is_native_endian, mpt::endian::native>::IEEE754binary32BE; +using IEEE754binary64LE = mpt::IEEE754binary_types::is_float64 && mpt::float_traits::is_ieee754_binary && mpt::float_traits::is_native_endian, mpt::endian::native>::IEEE754binary64LE; +using IEEE754binary64BE = mpt::IEEE754binary_types::is_float64 && mpt::float_traits::is_ieee754_binary && mpt::float_traits::is_native_endian, mpt::endian::native>::IEEE754binary64BE; // unaligned diff --git a/Frameworks/OpenMPT/OpenMPT/src/openmpt/base/Types.hpp b/Frameworks/OpenMPT/OpenMPT/src/openmpt/base/Types.hpp index b4def28ab..93671dbb2 100644 --- a/Frameworks/OpenMPT/OpenMPT/src/openmpt/base/Types.hpp +++ b/Frameworks/OpenMPT/OpenMPT/src/openmpt/base/Types.hpp @@ -6,8 +6,8 @@ #include "openmpt/all/BuildSettings.hpp" +#include "mpt/base/float.hpp" #include "mpt/base/integer.hpp" -#include "mpt/base/floatingpoint.hpp" @@ -25,8 +25,8 @@ using uint32 = mpt::uint32; using uint64 = mpt::uint64; using nativefloat = mpt::nativefloat; -using float32 = mpt::float32; -using float64 = mpt::float64; +using somefloat32 = mpt::somefloat32; +using somefloat64 = mpt::somefloat64; using namespace mpt::float_literals; diff --git a/Frameworks/OpenMPT/OpenMPT/src/openmpt/fileformat_base/magic.hpp b/Frameworks/OpenMPT/OpenMPT/src/openmpt/fileformat_base/magic.hpp new file mode 100644 index 000000000..72d4c7891 --- /dev/null +++ b/Frameworks/OpenMPT/OpenMPT/src/openmpt/fileformat_base/magic.hpp @@ -0,0 +1,44 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */ + + +#pragma once + +#include "openmpt/all/BuildSettings.hpp" + +#include "mpt/base/macros.hpp" +#include "openmpt/base/Types.hpp" + + + +OPENMPT_NAMESPACE_BEGIN + + + +// Functions to create 4-byte and 2-byte magic byte identifiers in little-endian format +// Use this together with uint32le/uint16le file members. +MPT_CONSTEVAL uint32 MagicLE(const char (&id)[5]) +{ + return static_cast((static_cast(id[3]) << 24) | (static_cast(id[2]) << 16) | (static_cast(id[1]) << 8) | static_cast(id[0])); +} +MPT_CONSTEVAL uint16 MagicLE(const char (&id)[3]) +{ + return static_cast((static_cast(id[1]) << 8) | static_cast(id[0])); +} + +// Functions to create 4-byte and 2-byte magic byte identifiers in big-endian format +// Use this together with uint32be/uint16be file members. +// Note: Historically, some magic bytes in MPT-specific fields are reversed (due to the use of multi-char literals). +// Such fields turned up reversed in files, so MagicBE is used to keep them readable in the code. +MPT_CONSTEVAL uint32 MagicBE(const char (&id)[5]) +{ + return static_cast((static_cast(id[0]) << 24) | (static_cast(id[1]) << 16) | (static_cast(id[2]) << 8) | static_cast(id[3])); +} +MPT_CONSTEVAL uint16 MagicBE(const char (&id)[3]) +{ + return static_cast((static_cast(id[0]) << 8) | static_cast(id[1])); +} + + + +OPENMPT_NAMESPACE_END diff --git a/Frameworks/OpenMPT/OpenMPT/src/openmpt/soundbase/Dither.hpp b/Frameworks/OpenMPT/OpenMPT/src/openmpt/soundbase/Dither.hpp index 2ba7153cb..eb26fd051 100644 --- a/Frameworks/OpenMPT/OpenMPT/src/openmpt/soundbase/Dither.hpp +++ b/Frameworks/OpenMPT/OpenMPT/src/openmpt/soundbase/Dither.hpp @@ -9,6 +9,7 @@ #include "mpt/base/macros.hpp" #include "mpt/random/default_engines.hpp" #include "mpt/random/engine.hpp" +#include "mpt/random/seed.hpp" #include "openmpt/soundbase/MixSample.hpp" #include diff --git a/Frameworks/OpenMPT/OpenMPT/src/openmpt/soundbase/DitherSimple.hpp b/Frameworks/OpenMPT/OpenMPT/src/openmpt/soundbase/DitherSimple.hpp index 832f01514..30ec5f55a 100644 --- a/Frameworks/OpenMPT/OpenMPT/src/openmpt/soundbase/DitherSimple.hpp +++ b/Frameworks/OpenMPT/OpenMPT/src/openmpt/soundbase/DitherSimple.hpp @@ -10,6 +10,7 @@ #include "mpt/random/engine.hpp" #include "mpt/random/default_engines.hpp" #include "mpt/random/random.hpp" +#include "mpt/random/seed.hpp" #include "openmpt/base/Types.hpp" #include "openmpt/soundbase/MixSample.hpp" #include "openmpt/soundbase/MixSampleConvert.hpp" diff --git a/Frameworks/OpenMPT/OpenMPT/src/openmpt/soundbase/MixSample.hpp b/Frameworks/OpenMPT/OpenMPT/src/openmpt/soundbase/MixSample.hpp index 2188d3b27..fceed0ab4 100644 --- a/Frameworks/OpenMPT/OpenMPT/src/openmpt/soundbase/MixSample.hpp +++ b/Frameworks/OpenMPT/OpenMPT/src/openmpt/soundbase/MixSample.hpp @@ -8,7 +8,7 @@ #include "openmpt/all/BuildSettings.hpp" #include "mpt/audio/sample.hpp" -#include "mpt/base/floatingpoint.hpp" +#include "mpt/base/float.hpp" #include diff --git a/Frameworks/OpenMPT/OpenMPT/src/openmpt/soundbase/SampleConvert.hpp b/Frameworks/OpenMPT/OpenMPT/src/openmpt/soundbase/SampleConvert.hpp index 8da1f1d25..3e68b0ece 100644 --- a/Frameworks/OpenMPT/OpenMPT/src/openmpt/soundbase/SampleConvert.hpp +++ b/Frameworks/OpenMPT/OpenMPT/src/openmpt/soundbase/SampleConvert.hpp @@ -10,6 +10,7 @@ #include "mpt/base/macros.hpp" #include "mpt/base/math.hpp" #include "mpt/base/saturate_cast.hpp" +#include "mpt/base/saturate_round.hpp" #include "openmpt/base/Int24.hpp" #include "openmpt/base/Types.hpp" #include "openmpt/soundbase/SampleConvert.hpp" @@ -153,9 +154,9 @@ struct Convert }; template <> -struct Convert +struct Convert { - using input_t = float32; + using input_t = somefloat32; using output_t = uint8; MPT_FORCEINLINE output_t operator()(input_t val) { @@ -234,9 +235,9 @@ struct Convert }; template <> -struct Convert +struct Convert { - using input_t = float32; + using input_t = somefloat32; using output_t = int8; MPT_FORCEINLINE output_t operator()(input_t val) { @@ -315,9 +316,9 @@ struct Convert }; template <> -struct Convert +struct Convert { - using input_t = float32; + using input_t = somefloat32; using output_t = int16; MPT_FORCEINLINE output_t operator()(input_t val) { @@ -396,9 +397,9 @@ struct Convert }; template <> -struct Convert +struct Convert { - using input_t = float32; + using input_t = somefloat32; using output_t = int24; MPT_FORCEINLINE output_t operator()(input_t val) { @@ -477,9 +478,9 @@ struct Convert }; template <> -struct Convert +struct Convert { - using input_t = float32; + using input_t = somefloat32; using output_t = int32; MPT_FORCEINLINE output_t operator()(input_t val) { @@ -558,15 +559,15 @@ struct Convert }; template <> -struct Convert +struct Convert { - using input_t = float32; + using input_t = somefloat32; using output_t = int64; MPT_FORCEINLINE output_t operator()(input_t val) { val = mpt::safe_clamp(val, -1.0f, 1.0f); val *= static_cast(uint64(1) << 63); - return mpt::saturate_cast(SC::fastround(val)); + return mpt::saturate_trunc(SC::fastround(val)); } }; @@ -579,73 +580,73 @@ struct Convert { val = std::clamp(val, -1.0, 1.0); val *= static_cast(uint64(1) << 63); - return mpt::saturate_cast(SC::fastround(val)); + return mpt::saturate_trunc(SC::fastround(val)); } }; template <> -struct Convert +struct Convert { using input_t = uint8; - using output_t = float32; + using output_t = somefloat32; MPT_FORCEINLINE output_t operator()(input_t val) { - return static_cast(static_cast(val) - 0x80) * (1.0f / static_cast(static_cast(1) << 7)); + return static_cast(static_cast(val) - 0x80) * (1.0f / static_cast(static_cast(1) << 7)); } }; template <> -struct Convert +struct Convert { using input_t = int8; - using output_t = float32; + using output_t = somefloat32; MPT_FORCEINLINE output_t operator()(input_t val) { - return static_cast(val) * (1.0f / static_cast(static_cast(1) << 7)); + return static_cast(val) * (1.0f / static_cast(static_cast(1) << 7)); } }; template <> -struct Convert +struct Convert { using input_t = int16; - using output_t = float32; + using output_t = somefloat32; MPT_FORCEINLINE output_t operator()(input_t val) { - return static_cast(val) * (1.0f / static_cast(static_cast(1) << 15)); + return static_cast(val) * (1.0f / static_cast(static_cast(1) << 15)); } }; template <> -struct Convert +struct Convert { using input_t = int24; - using output_t = float32; + using output_t = somefloat32; MPT_FORCEINLINE output_t operator()(input_t val) { - return static_cast(val) * (1.0f / static_cast(static_cast(1) << 23)); + return static_cast(val) * (1.0f / static_cast(static_cast(1) << 23)); } }; template <> -struct Convert +struct Convert { using input_t = int32; - using output_t = float32; + using output_t = somefloat32; MPT_FORCEINLINE output_t operator()(input_t val) { - return static_cast(val) * (1.0f / static_cast(static_cast(1) << 31)); + return static_cast(val) * (1.0f / static_cast(static_cast(1) << 31)); } }; template <> -struct Convert +struct Convert { using input_t = int64; - using output_t = float32; + using output_t = somefloat32; MPT_FORCEINLINE output_t operator()(input_t val) { - return static_cast(val) * (1.0f / static_cast(static_cast(1) << 63)); + return static_cast(val) * (1.0f / static_cast(static_cast(1) << 63)); } }; @@ -656,7 +657,7 @@ struct Convert using output_t = double; MPT_FORCEINLINE output_t operator()(input_t val) { - return static_cast(static_cast(val) - 0x80) * (1.0 / static_cast(static_cast(1) << 7)); + return static_cast(static_cast(val) - 0x80) * (1.0 / static_cast(static_cast(1) << 7)); } }; @@ -667,7 +668,7 @@ struct Convert using output_t = double; MPT_FORCEINLINE output_t operator()(input_t val) { - return static_cast(val) * (1.0 / static_cast(static_cast(1) << 7)); + return static_cast(val) * (1.0 / static_cast(static_cast(1) << 7)); } }; @@ -678,7 +679,7 @@ struct Convert using output_t = double; MPT_FORCEINLINE output_t operator()(input_t val) { - return static_cast(val) * (1.0 / static_cast(static_cast(1) << 15)); + return static_cast(val) * (1.0 / static_cast(static_cast(1) << 15)); } }; @@ -689,7 +690,7 @@ struct Convert using output_t = double; MPT_FORCEINLINE output_t operator()(input_t val) { - return static_cast(val) * (1.0 / static_cast(static_cast(1) << 23)); + return static_cast(val) * (1.0 / static_cast(static_cast(1) << 23)); } }; @@ -700,7 +701,7 @@ struct Convert using output_t = double; MPT_FORCEINLINE output_t operator()(input_t val) { - return static_cast(val) * (1.0 / static_cast(static_cast(1) << 31)); + return static_cast(val) * (1.0 / static_cast(static_cast(1) << 31)); } }; diff --git a/Frameworks/OpenMPT/OpenMPT/src/openmpt/soundbase/SampleConvertFixedPoint.hpp b/Frameworks/OpenMPT/OpenMPT/src/openmpt/soundbase/SampleConvertFixedPoint.hpp index e757f4d42..461dc69c3 100644 --- a/Frameworks/OpenMPT/OpenMPT/src/openmpt/soundbase/SampleConvertFixedPoint.hpp +++ b/Frameworks/OpenMPT/OpenMPT/src/openmpt/soundbase/SampleConvertFixedPoint.hpp @@ -110,10 +110,10 @@ struct ConvertFixedPoint }; template -struct ConvertFixedPoint +struct ConvertFixedPoint { using input_t = int32; - using output_t = float32; + using output_t = somefloat32; const float factor; MPT_FORCEINLINE ConvertFixedPoint() : factor(1.0f / static_cast(1 << fractionalBits)) @@ -123,15 +123,15 @@ struct ConvertFixedPoint MPT_FORCEINLINE output_t operator()(input_t val) { static_assert(fractionalBits >= 0 && fractionalBits <= sizeof(input_t) * 8 - 1); - return static_cast(val) * factor; + return static_cast(val) * factor; } }; template -struct ConvertFixedPoint +struct ConvertFixedPoint { using input_t = int32; - using output_t = float64; + using output_t = somefloat64; const double factor; MPT_FORCEINLINE ConvertFixedPoint() : factor(1.0 / static_cast(1 << fractionalBits)) @@ -141,7 +141,7 @@ struct ConvertFixedPoint MPT_FORCEINLINE output_t operator()(input_t val) { static_assert(fractionalBits >= 0 && fractionalBits <= sizeof(input_t) * 8 - 1); - return static_cast(val) * factor; + return static_cast(val) * factor; } }; @@ -218,9 +218,9 @@ struct ConvertToFixedPoint }; template -struct ConvertToFixedPoint +struct ConvertToFixedPoint { - using input_t = float32; + using input_t = somefloat32; using output_t = int32; const float factor; MPT_FORCEINLINE ConvertToFixedPoint() @@ -232,14 +232,14 @@ struct ConvertToFixedPoint { static_assert(fractionalBits >= 0 && fractionalBits <= sizeof(input_t) * 8 - 1); val = mpt::sanitize_nan(val); - return mpt::saturate_cast(SC::fastround(val * factor)); + return mpt::saturate_trunc(SC::fastround(val * factor)); } }; template -struct ConvertToFixedPoint +struct ConvertToFixedPoint { - using input_t = float64; + using input_t = somefloat64; using output_t = int32; const double factor; MPT_FORCEINLINE ConvertToFixedPoint() @@ -251,7 +251,7 @@ struct ConvertToFixedPoint { static_assert(fractionalBits >= 0 && fractionalBits <= sizeof(input_t) * 8 - 1); val = mpt::sanitize_nan(val); - return mpt::saturate_cast(SC::fastround(val * factor)); + return mpt::saturate_trunc(SC::fastround(val * factor)); } }; diff --git a/Frameworks/OpenMPT/OpenMPT/src/openmpt/soundbase/SampleDecode.hpp b/Frameworks/OpenMPT/OpenMPT/src/openmpt/soundbase/SampleDecode.hpp index 6ae306436..9e6220cae 100644 --- a/Frameworks/OpenMPT/OpenMPT/src/openmpt/soundbase/SampleDecode.hpp +++ b/Frameworks/OpenMPT/OpenMPT/src/openmpt/soundbase/SampleDecode.hpp @@ -6,7 +6,7 @@ #include "openmpt/all/BuildSettings.hpp" -#include "mpt/base/floatingpoint.hpp" +#include "mpt/base/float.hpp" #include "mpt/base/macros.hpp" #include "mpt/base/memory.hpp" #include "openmpt/base/Endian.hpp" @@ -290,11 +290,11 @@ template + + + +OPENMPT_NAMESPACE_BEGIN + + + +// RIFF header +struct RIFFHeader +{ + // 32-Bit chunk identifiers + enum RIFFMagic + { + idRIFF = MagicLE("RIFF"), // magic for WAV files + idLIST = MagicLE("LIST"), // magic for samples in DLS banks + idWAVE = MagicLE("WAVE"), // type for WAV files + idwave = MagicLE("wave"), // type for samples in DLS banks + }; + + uint32le magic; // RIFF (in WAV files) or LIST (in DLS banks) + uint32le length; // Size of the file, not including magic and length + uint32le type; // WAVE (in WAV files) or wave (in DLS banks) +}; + +MPT_BINARY_STRUCT(RIFFHeader, 12) + + +// General RIFF Chunk header +struct RIFFChunk +{ + // 32-Bit chunk identifiers + enum ChunkIdentifiers + { + idfmt_ = MagicLE("fmt "), // Sample format information + iddata = MagicLE("data"), // Sample data + idpcm_ = MagicLE("pcm "), // IMA ADPCM samples + idfact = MagicLE("fact"), // Compressed samples + idsmpl = MagicLE("smpl"), // Sampler and loop information + idinst = MagicLE("inst"), // Instrument information + idLIST = MagicLE("LIST"), // List of chunks + idxtra = MagicLE("xtra"), // OpenMPT extra infomration + idcue_ = MagicLE("cue "), // Cue points + idwsmp = MagicLE("wsmp"), // DLS bank samples + idCSET = MagicLE("CSET"), // Character Set + id____ = 0x00000000, // Found when loading buggy MPT samples + + // Identifiers in "LIST" chunk + idINAM = MagicLE("INAM"), // title + idISFT = MagicLE("ISFT"), // software + idICOP = MagicLE("ICOP"), // copyright + idIART = MagicLE("IART"), // artist + idIPRD = MagicLE("IPRD"), // product (album) + idICMT = MagicLE("ICMT"), // comment + idIENG = MagicLE("IENG"), // engineer + idISBJ = MagicLE("ISBJ"), // subject + idIGNR = MagicLE("IGNR"), // genre + idICRD = MagicLE("ICRD"), // date created + + idYEAR = MagicLE("YEAR"), // year + idTRCK = MagicLE("TRCK"), // track number + idTURL = MagicLE("TURL"), // url + }; + + uint32le id; // See ChunkIdentifiers + uint32le length; // Chunk size without header + + std::size_t GetLength() const + { + return length; + } + + ChunkIdentifiers GetID() const + { + return static_cast(id.get()); + } +}; + +MPT_BINARY_STRUCT(RIFFChunk, 8) + + +// Format Chunk +struct WAVFormatChunk +{ + // Sample formats + enum SampleFormats + { + fmtPCM = 1, + fmtFloat = 3, + fmtALaw = 6, + fmtULaw = 7, + fmtIMA_ADPCM = 17, + fmtMP3 = 85, + fmtExtensible = 0xFFFE, + }; + + uint16le format; // Sample format, see SampleFormats + uint16le numChannels; // Number of audio channels + uint32le sampleRate; // Sample rate in Hz + uint32le byteRate; // Bytes per second (should be freqHz * blockAlign) + uint16le blockAlign; // Size of a sample, in bytes (do not trust this value, it's incorrect in some files) + uint16le bitsPerSample; // Bits per sample +}; + +MPT_BINARY_STRUCT(WAVFormatChunk, 16) + + +// Extension of the WAVFormatChunk structure, used if format == formatExtensible +struct WAVFormatChunkExtension +{ + uint16le size; + uint16le validBitsPerSample; + uint32le channelMask; + mpt::GUIDms subFormat; +}; + +MPT_BINARY_STRUCT(WAVFormatChunkExtension, 24) + + +// Sample cue point structure for the "cue " chunk +struct WAVCuePoint +{ + uint32le id; // Unique identification value + uint32le position; // Play order position + uint32le riffChunkID; // RIFF ID of corresponding data chunk + uint32le chunkStart; // Byte Offset of Data Chunk + uint32le blockStart; // Byte Offset to sample of First Channel + uint32le offset; // Byte Offset to sample byte of First Channel +}; + +MPT_BINARY_STRUCT(WAVCuePoint, 24) + + + +OPENMPT_NAMESPACE_END diff --git a/Frameworks/OpenMPT/OpenMPT/src/openmpt/soundfile_write/wav_write.cpp b/Frameworks/OpenMPT/OpenMPT/src/openmpt/soundfile_write/wav_write.cpp new file mode 100644 index 000000000..81bc0165c --- /dev/null +++ b/Frameworks/OpenMPT/OpenMPT/src/openmpt/soundfile_write/wav_write.cpp @@ -0,0 +1,202 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */ + + +#include "openmpt/all/BuildSettings.hpp" +#include "openmpt/all/PlatformFixes.hpp" + +#include "wav_write.hpp" + +#include "mpt/base/memory.hpp" +#include "mpt/base/saturate_cast.hpp" +#include "mpt/base/span.hpp" +#include "mpt/base/utility.hpp" +#include "mpt/io/base.hpp" +#include "mpt/io/io.hpp" +#include "mpt/io/io_virtual_wrapper.hpp" +#include "mpt/string/types.hpp" +#include "mpt/string_transcode/transcode.hpp" + +#include "openmpt/base/Types.hpp" +#include "openmpt/soundfile_data/tags.hpp" +#include "openmpt/soundfile_data/wav.hpp" + +#include +#include + +#include + + + +OPENMPT_NAMESPACE_BEGIN + + + +WAVWriter::WAVWriter(mpt::IO::OFileBase &stream) + : s(stream) +{ + // Skip file header for now + mpt::IO::SeekRelative(s, sizeof(RIFFHeader)); +} + + +void WAVWriter::StartChunk(RIFFChunk::ChunkIdentifiers id) +{ + FinalizeChunk(); + + chunkHeaderPos = mpt::IO::TellWrite(s); + chunkHeader.id = id; + mpt::IO::SeekRelative(s, sizeof(chunkHeader)); +} + + +void WAVWriter::FinalizeChunk() +{ + if(chunkHeaderPos != 0) + { + const mpt::IO::Offset position = mpt::IO::TellWrite(s); + + const mpt::IO::Offset chunkSize = position - (chunkHeaderPos + sizeof(RIFFChunk)); + chunkHeader.length = mpt::saturate_cast(chunkSize); + + mpt::IO::SeekAbsolute(s, chunkHeaderPos); + mpt::IO::Write(s, chunkHeader); + + mpt::IO::SeekAbsolute(s, position); + + if((chunkSize % 2u) != 0) + { + // Write padding + uint8 padding = 0; + mpt::IO::Write(s, padding); + } + + chunkHeaderPos = 0; + } +} + + +void WAVWriter::WriteFormat(uint32 sampleRate, uint16 bitDepth, uint16 numChannels, WAVFormatChunk::SampleFormats encoding) +{ + StartChunk(RIFFChunk::idfmt_); + WAVFormatChunk wavFormat; + mpt::reset(wavFormat); + + bool extensible = (numChannels > 2); + + wavFormat.format = static_cast(extensible ? WAVFormatChunk::fmtExtensible : encoding); + wavFormat.numChannels = numChannels; + wavFormat.sampleRate = sampleRate; + wavFormat.blockAlign = static_cast((bitDepth * numChannels + 7u) / 8u); + wavFormat.byteRate = wavFormat.sampleRate * wavFormat.blockAlign; + wavFormat.bitsPerSample = bitDepth; + + mpt::IO::Write(s, wavFormat); + + if(extensible) + { + WAVFormatChunkExtension extFormat; + mpt::reset(extFormat); + extFormat.size = sizeof(WAVFormatChunkExtension) - sizeof(uint16); + extFormat.validBitsPerSample = bitDepth; + switch(numChannels) + { + case 1: + extFormat.channelMask = 0x0004; // FRONT_CENTER + break; + case 2: + extFormat.channelMask = 0x0003; // FRONT_LEFT | FRONT_RIGHT + break; + case 3: + extFormat.channelMask = 0x0103; // FRONT_LEFT | FRONT_RIGHT | BACK_CENTER + break; + case 4: + extFormat.channelMask = 0x0033; // FRONT_LEFT | FRONT_RIGHT | BACK_LEFT | BACK_RIGHT + break; + default: + extFormat.channelMask = 0; + break; + } + extFormat.subFormat = mpt::UUID(static_cast(encoding), 0x0000, 0x0010, 0x800000AA00389B71ull); + mpt::IO::Write(s, extFormat); + } +} + + +void WAVWriter::WriteMetatags(const FileTags &tags) +{ + StartChunk(RIFFChunk::idCSET); + mpt::IO::Write(s, mpt::as_le(uint16(65001))); // code page (UTF-8) + mpt::IO::Write(s, mpt::as_le(uint16(0))); // country code (unset) + mpt::IO::Write(s, mpt::as_le(uint16(0))); // language (unset) + mpt::IO::Write(s, mpt::as_le(uint16(0))); // dialect (unset) + + StartChunk(RIFFChunk::idLIST); + const char info[] = {'I', 'N', 'F', 'O'}; + mpt::IO::Write(s, info); + + WriteTag(RIFFChunk::idINAM, tags.title); + WriteTag(RIFFChunk::idIART, tags.artist); + WriteTag(RIFFChunk::idIPRD, tags.album); + WriteTag(RIFFChunk::idICRD, tags.year); + WriteTag(RIFFChunk::idICMT, tags.comments); + WriteTag(RIFFChunk::idIGNR, tags.genre); + WriteTag(RIFFChunk::idTURL, tags.url); + WriteTag(RIFFChunk::idISFT, tags.encoder); + WriteTag(RIFFChunk::idTRCK, tags.trackno); +} + + +void WAVWriter::WriteTag(RIFFChunk::ChunkIdentifiers id, const mpt::ustring &utext) +{ + std::string text = mpt::transcode(mpt::common_encoding::utf8, utext); + text = text.substr(0, std::numeric_limits::max() - 1u); + if(!text.empty()) + { + const uint32 length = mpt::saturate_cast(text.length() + 1); + + RIFFChunk chunk; + mpt::reset(chunk); + chunk.id = static_cast(id); + chunk.length = length; + mpt::IO::Write(s, chunk); + mpt::IO::Write(s, mpt::byte_cast(mpt::span(text.c_str(), length))); + + if((length % 2u) != 0) + { + uint8 padding = 0; + mpt::IO::Write(s, padding); + } + } +} + + +mpt::IO::Offset WAVWriter::Finalize() +{ + FinalizeChunk(); + + mpt::IO::Offset totalSize = mpt::IO::TellWrite(s); + + RIFFHeader fileHeader; + mpt::reset(fileHeader); + fileHeader.magic = RIFFHeader::idRIFF; + fileHeader.length = mpt::saturate_cast(totalSize - 8); + fileHeader.type = RIFFHeader::idWAVE; + + mpt::IO::SeekBegin(s); + mpt::IO::Write(s, fileHeader); + mpt::IO::SeekAbsolute(s, totalSize); + finalized = true; + + return totalSize; +} + + +WAVWriter::~WAVWriter() +{ + assert(finalized); +} + + + +OPENMPT_NAMESPACE_END diff --git a/Frameworks/OpenMPT/OpenMPT/src/openmpt/soundfile_write/wav_write.hpp b/Frameworks/OpenMPT/OpenMPT/src/openmpt/soundfile_write/wav_write.hpp new file mode 100644 index 000000000..e03df032b --- /dev/null +++ b/Frameworks/OpenMPT/OpenMPT/src/openmpt/soundfile_write/wav_write.hpp @@ -0,0 +1,65 @@ +/* SPDX-License-Identifier: BSD-3-Clause */ +/* SPDX-FileCopyrightText: OpenMPT Project Developers and Contributors */ + + +#pragma once + +#include "openmpt/all/BuildSettings.hpp" + +#include "mpt/io/base.hpp" +#include "mpt/io/io_virtual_wrapper.hpp" +#include "mpt/string/types.hpp" + +#include "openmpt/base/Types.hpp" +#include "openmpt/soundfile_data/tags.hpp" +#include "openmpt/soundfile_data/wav.hpp" + + + +OPENMPT_NAMESPACE_BEGIN + + + +class WAVWriter +{ +protected: + // Output stream + mpt::IO::OFileBase &s; + + // Currently written chunk + mpt::IO::Offset chunkHeaderPos = 0; + RIFFChunk chunkHeader; + bool finalized = false; + +public: + // Output to stream: Initialize with std::ostream*. + WAVWriter(mpt::IO::OFileBase &stream); + + // Begin writing a new chunk to the file. + void StartChunk(RIFFChunk::ChunkIdentifiers id); + +protected: + // End current chunk by updating the chunk header and writing a padding byte if necessary. + void FinalizeChunk(); + +public: + // Write the WAV format to the file. + void WriteFormat(uint32 sampleRate, uint16 bitDepth, uint16 numChannels, WAVFormatChunk::SampleFormats encoding); + + // Write text tags to the file. + void WriteMetatags(const FileTags &tags); + +protected: + // Write a single tag into a open idLIST chunk + void WriteTag(RIFFChunk::ChunkIdentifiers id, const mpt::ustring &utext); + +public: + // Finalize the file by closing the last open chunk and updating the file header. Returns total size of file. + mpt::IO::Offset Finalize(); + + ~WAVWriter(); +}; + + + +OPENMPT_NAMESPACE_END diff --git a/Frameworks/OpenMPT/OpenMPT/test/TestToolsTracker.h b/Frameworks/OpenMPT/OpenMPT/test/TestToolsTracker.h index 91d36dec9..11f47478a 100644 --- a/Frameworks/OpenMPT/OpenMPT/test/TestToolsTracker.h +++ b/Frameworks/OpenMPT/OpenMPT/test/TestToolsTracker.h @@ -30,6 +30,10 @@ namespace Test { #if MPT_COMPILER_MSVC // With MSVC, break directly using __debugbreak intrinsic instead of calling DebugBreak which breaks one stackframe deeper than we want #define MyDebugBreak() __debugbreak() +#elif MPT_COMPILER_CLANG +#define MyDebugBreak() __builtin_debugtrap() +#elif MPT_COMPILER_GCC && (MPT_ARCH_X86 || MPT_ARCH_AMD64) +#define MyDebugBreak() __asm__ __volatile__("int 3"); #else #define MyDebugBreak() DebugBreak() #endif diff --git a/Frameworks/OpenMPT/OpenMPT/test/libopenmpt_test.cpp b/Frameworks/OpenMPT/OpenMPT/test/libopenmpt_test.cpp deleted file mode 100644 index e4200ed4a..000000000 --- a/Frameworks/OpenMPT/OpenMPT/test/libopenmpt_test.cpp +++ /dev/null @@ -1,99 +0,0 @@ -/* - * libopenmpt_test.cpp - * ------------------- - * Purpose: libopenmpt test suite driver - * Notes : (currently none) - * Authors: OpenMPT Devs - * The OpenMPT source code is released under the BSD license. Read LICENSE for more details. - */ - -#include "openmpt/all/BuildSettings.hpp" - -#include "mpt/base/macros.hpp" - -#if defined( LIBOPENMPT_BUILD_TEST ) - -#if defined(__MINGW32__) && !defined(__MINGW64__) -#include -#endif - -#include "../libopenmpt/libopenmpt_internal.h" - -#include "test.h" - -#include -#include - -#include -#include - -#if defined( __DJGPP__ ) -#include -#endif /* __DJGPP__ */ - -using namespace OpenMPT; - -#if defined( __DJGPP__ ) -/* Work-around */ -/* clang-format off */ -extern "C" { - int _crt0_startup_flags = 0 - | _CRT0_FLAG_NONMOVE_SBRK /* force interrupt compatible allocation */ - | _CRT0_DISABLE_SBRK_ADDRESS_WRAP /* force NT compatible allocation */ - | _CRT0_FLAG_LOCK_MEMORY /* lock all code and data at program startup */ - | 0; -} -/* clang-format on */ -#endif /* __DJGPP__ */ -#if (defined(_WIN32) || defined(WIN32)) && (defined(_UNICODE) || defined(UNICODE)) -#if defined(__GNUC__) || (defined(__clang__) && !defined(_MSC_VER)) -// mingw-w64 g++ does only default to special C linkage for "main", but not for "wmain" (see ). -extern "C" int wmain( int /*argc*/ , wchar_t * /*argv*/ [] ); -extern "C" -#endif -int wmain( int /*argc*/ , wchar_t * /*argv*/ [] ) { -#else -int main( int /*argc*/ , char * /*argv*/ [] ) { -#endif -#if defined( __DJGPP__ ) - _crt0_startup_flags &= ~_CRT0_FLAG_LOCK_MEMORY; /* disable automatic locking for all further memory allocations */ -#endif /* __DJGPP__ */ - - try { - - // run test with "C" / classic() locale - Test::DoTests(); - - // try setting the C locale to the user locale - setlocale( LC_ALL, "" ); - - // run all tests again with a set C locale - Test::DoTests(); - - // try to set the C and C++ locales to the user locale - try { - std::locale old = std::locale::global( std::locale( "" ) ); - (void)old; - } catch ( ... ) { - // Setting c++ global locale does not work. - // This is no problem for libopenmpt, just continue. - } - - // and now, run all tests once again - Test::DoTests(); - - } catch ( const std::exception & e ) { - std::cerr << "TEST ERROR: exception: " << ( e.what() ? e.what() : "" ) << std::endl; - return -1; - } catch ( ... ) { - std::cerr << "TEST ERROR: unknown exception" << std::endl; - return -1; - } - return 0; -} - -#else // !LIBOPENMPT_BUILD_TEST - -unsigned char libopenmpt_test_dummy = 0; - -#endif // LIBOPENMPT_BUILD_TEST diff --git a/Frameworks/OpenMPT/OpenMPT/test/test.cpp b/Frameworks/OpenMPT/OpenMPT/test/test.cpp index d1a2465bf..946371cdd 100644 --- a/Frameworks/OpenMPT/OpenMPT/test/test.cpp +++ b/Frameworks/OpenMPT/OpenMPT/test/test.cpp @@ -23,18 +23,21 @@ #include "mpt/crc/crc.hpp" #include "mpt/environment/environment.hpp" #ifdef MODPLUG_TRACKER +#include "../mptrack/TrackerSettings.h" #include "mpt/fs/common_directories.hpp" #include "mpt/fs/fs.hpp" #endif // MODPLUG_TRACKER #include "mpt/io/base.hpp" #include "mpt/io/io.hpp" #include "mpt/io/io_stdstream.hpp" +#include "mpt/io_file/fstream.hpp" #include "mpt/io_read/filecursor_stdstream.hpp" #include "mpt/osinfo/class.hpp" #include "mpt/osinfo/dos_version.hpp" #include "mpt/osinfo/dos_memory.hpp" #include "mpt/parse/parse.hpp" #include "mpt/parse/split.hpp" +#include "mpt/random/seed.hpp" #include "mpt/test/test.hpp" #include "mpt/test/test_macros.hpp" #include "mpt/uuid/uuid.hpp" @@ -43,8 +46,10 @@ #include "../common/misc_util.h" #include "../common/mptStringBuffer.h" #include "../common/serialization_utils.h" -#include "../soundlib/Sndfile.h" #include "../common/FileReader.h" +#include "../soundlib/Sndfile.h" +#include "../soundlib/AudioReadTarget.h" +#include "../soundlib/MIDIEvents.h" #include "../soundlib/mod_specifications.h" #include "../soundlib/MIDIEvents.h" #include "../soundlib/MIDIMacros.h" @@ -55,17 +60,16 @@ #include "openmpt/soundbase/SampleFormat.hpp" #include "../soundlib/SampleCopy.h" #include "../soundlib/SampleNormalize.h" +#include "../soundlib/MIDIMacroParser.h" #include "../soundlib/ModSampleCopy.h" #include "../soundlib/ITCompression.h" #include "../soundlib/tuningcollection.h" #include "../soundlib/tuning.h" #include "openmpt/soundbase/Dither.hpp" -#include "../common/Dither.h" #ifdef MODPLUG_TRACKER #include "../mptrack/Mptrack.h" #include "../mptrack/Moddoc.h" #include "../mptrack/ModDocTemplate.h" -#include "../mptrack/Mainfrm.h" #include "../mptrack/Settings.h" #include "../mptrack/HTTP.h" #endif // MODPLUG_TRACKER @@ -114,6 +118,10 @@ #include "TestTools.h" +#if defined(LIBOPENMPT_BUILD) +#include "../libopenmpt/libopenmpt.hpp" +#endif + // enable tests which may fail spuriously //#define FLAKY_TESTS @@ -145,6 +153,7 @@ static MPT_NOINLINE void TestITCompression(); static MPT_NOINLINE void TestPCnoteSerialization(); static MPT_NOINLINE void TestLoadSaveFile(); static MPT_NOINLINE void TestEditing(); +static MPT_NOINLINE void TestMIDIMacroParser(); @@ -163,6 +172,33 @@ mpt::PathString GetPathPrefix() } +void PrintHeader() +{ + #ifdef LIBOPENMPT_BUILD + std::cout << "libopenmpt test suite starting." << std::endl; + std::cout << "libopenmpt library version: " << std::hex << std::setfill('0') << std::setw(8) << openmpt::get_library_version() << std::endl; + std::cout << "libopenmpt core version: " << std::hex << std::setfill('0') << std::setw(8) << openmpt::get_core_version() << std::endl; + std::cout << "libopenmpt library_version: " << openmpt::string::get("library_version") << std::endl; + std::cout << "libopenmpt library_features: " << openmpt::string::get("library_features") << std::endl; + std::cout << "libopenmpt core_version: " << openmpt::string::get("core_version") << std::endl; + std::cout << "libopenmpt source_url: " << openmpt::string::get("source_url") << std::endl; + std::cout << "libopenmpt source_date: " << openmpt::string::get("source_date") << std::endl; + std::cout << "libopenmpt source_revision: " << openmpt::string::get("source_revision") << std::endl; + std::cout << "libopenmpt source_is_modified: " << openmpt::string::get("source_is_modified") << std::endl; + std::cout << "libopenmpt source_has_mixed_revisions: " << openmpt::string::get("source_has_mixed_revisions") << std::endl; + std::cout << "libopenmpt source_is_package: " << openmpt::string::get("source_is_package") << std::endl; + std::cout << "libopenmpt build: " << openmpt::string::get("build") << std::endl; + std::cout << "libopenmpt build_compiler: " << openmpt::string::get("build_compiler") << std::endl; + #endif +} + +void PrintFooter() +{ + #ifdef LIBOPENMPT_BUILD + std::cout << "libopenmpt test suite finished." << std::endl; + #endif +} + void DoTests() { @@ -172,7 +208,7 @@ void DoTests() std::clog << std::flush; std::cerr << std::flush; - std::cout << "libopenmpt test suite" << std::endl; + std::cout << "libopenmpt test run" << std::endl; std::cout << "Version: " << mpt::ToCharset(mpt::Charset::ASCII, Build::GetVersionString(Build::StringVersion | Build::StringRevision | Build::StringSourceInfo | Build::StringBuildFlags | Build::StringBuildFeatures)) << std::endl; std::cout << "Compiler: " << mpt::ToCharset(mpt::Charset::ASCII, Build::GetBuildCompilerString()) << std::endl; @@ -439,6 +475,7 @@ void DoTests() DO_TEST(TestMIDIEvents); DO_TEST(TestSampleConversion); DO_TEST(TestITCompression); + DO_TEST(TestMIDIMacroParser); // slower tests, require opening a CModDoc DO_TEST(TestPCnoteSerialization); @@ -590,7 +627,7 @@ static MPT_NOINLINE void TestVersion() #if MPT_TEST_HAS_FILESYSTEM #if !MPT_OS_DJGPP mpt::PathString version_mk = GetPathPrefix() + P_("libopenmpt/libopenmpt_version.mk"); - mpt::ifstream f(version_mk, std::ios::in); + mpt::IO::ifstream f(version_mk, std::ios::in); VERIFY_EQUAL(f ? true : false, true); std::map fields; std::string line; @@ -1147,7 +1184,6 @@ static MPT_NOINLINE void TestMisc2() } #ifdef MODPLUG_TRACKER -#ifdef MPT_ENABLE_FILEIO { std::vector data; @@ -1169,7 +1205,6 @@ static MPT_NOINLINE void TestMisc2() RemoveFile(fn); } -#endif #endif // MODPLUG_TRACKER #ifdef MPT_WITH_ZLIB @@ -2698,12 +2733,12 @@ static void TestLoadXMFile(const CSoundFile &sndFile) // Global Variables VERIFY_EQUAL_NONCONT(sndFile.GetTitle(), "Test Module"); VERIFY_EQUAL_NONCONT(sndFile.m_songMessage.substr(0, 32), "OpenMPT Module Loader Test Suite"); - VERIFY_EQUAL_NONCONT(sndFile.m_nDefaultTempo, TEMPO(139, 0)); - VERIFY_EQUAL_NONCONT(sndFile.m_nDefaultSpeed, 5); + VERIFY_EQUAL_NONCONT(sndFile.Order().GetDefaultTempo(), TEMPO(139, 0)); + VERIFY_EQUAL_NONCONT(sndFile.Order().GetDefaultSpeed(), 5); VERIFY_EQUAL_NONCONT(sndFile.m_nDefaultGlobalVolume, 128); VERIFY_EQUAL_NONCONT(sndFile.m_nVSTiVolume, 42); VERIFY_EQUAL_NONCONT(sndFile.m_nSamplePreAmp, 23); - VERIFY_EQUAL_NONCONT((sndFile.m_SongFlags & SONG_FILE_FLAGS), SONG_LINEARSLIDES | SONG_EXFILTERRANGE); + VERIFY_EQUAL_NONCONT(sndFile.m_SongFlags, SONG_LINEARSLIDES | SONG_EXFILTERRANGE); VERIFY_EQUAL_NONCONT(sndFile.m_playBehaviour[MSF_COMPATIBLE_PLAY], true); VERIFY_EQUAL_NONCONT(sndFile.m_playBehaviour[kMIDICCBugEmulation], false); VERIFY_EQUAL_NONCONT(sndFile.m_playBehaviour[kMPTOldSwingBehaviour], false); @@ -2805,7 +2840,7 @@ static void TestLoadXMFile(const CSoundFile &sndFile) VERIFY_EQUAL_NONCONT(pIns->wMidiBank, 2); VERIFY_EQUAL_NONCONT(pIns->midiPWD, 8); - VERIFY_EQUAL_NONCONT(pIns->pTuning, sndFile.GetDefaultTuning()); + VERIFY_EQUAL_NONCONT(pIns->pTuning, nullptr); VERIFY_EQUAL_NONCONT(pIns->pitchToTempoLock, TEMPO(0, 0)); @@ -2864,7 +2899,11 @@ static void TestLoadXMFile(const CSoundFile &sndFile) VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(0, 0)->instr, 0); VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(0, 0)->volcmd, VOLCMD_VIBRATOSPEED); VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(0, 0)->vol, 15); - VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(31, 0)->IsEmpty(), true); + VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(0, 1)->note, NOTE_MIN + 12); + VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(1, 1)->note, NOTE_MIN + 12 + 95); + VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(2, 1)->note, NOTE_KEYOFF); + VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(30, 0)->IsEmpty(), true); // Test for resaved out-of-range note + VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(31, 0)->IsEmpty(), true); // Test for resaved out-of-range note VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(31, 1)->IsEmpty(), false); VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(31, 1)->IsPcNote(), false); VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(31, 1)->note, NOTE_MIDDLEC + 12); @@ -2898,12 +2937,10 @@ static void TestLoadMPTMFile(const CSoundFile &sndFile) // Global Variables VERIFY_EQUAL_NONCONT(sndFile.GetTitle(), "Test Module_____________X"); VERIFY_EQUAL_NONCONT(sndFile.m_songMessage.substr(0, 32), "OpenMPT Module Loader Test Suite"); - VERIFY_EQUAL_NONCONT(sndFile.m_nDefaultTempo, TEMPO(139, 999)); - VERIFY_EQUAL_NONCONT(sndFile.m_nDefaultSpeed, 5); VERIFY_EQUAL_NONCONT(sndFile.m_nDefaultGlobalVolume, 128); VERIFY_EQUAL_NONCONT(sndFile.m_nVSTiVolume, 42); VERIFY_EQUAL_NONCONT(sndFile.m_nSamplePreAmp, 23); - VERIFY_EQUAL_NONCONT((sndFile.m_SongFlags & SONG_FILE_FLAGS), SONG_LINEARSLIDES | SONG_EXFILTERRANGE | SONG_ITCOMPATGXX | SONG_ITOLDEFFECTS); + VERIFY_EQUAL_NONCONT(sndFile.m_SongFlags, SONG_LINEARSLIDES | SONG_EXFILTERRANGE | SONG_ITCOMPATGXX | SONG_ITOLDEFFECTS); VERIFY_EQUAL_NONCONT(sndFile.m_playBehaviour[MSF_COMPATIBLE_PLAY], true); VERIFY_EQUAL_NONCONT(sndFile.m_playBehaviour[kMIDICCBugEmulation], false); VERIFY_EQUAL_NONCONT(sndFile.m_playBehaviour[kMPTOldSwingBehaviour], false); @@ -3124,16 +3161,19 @@ static void TestLoadMPTMFile(const CSoundFile &sndFile) VERIFY_EQUAL_NONCONT(pIns->wMidiBank, 2); VERIFY_EQUAL_NONCONT(pIns->midiPWD, -1); - VERIFY_EQUAL_NONCONT(pIns->pTuning, sndFile.GetDefaultTuning()); + if(ins == 1) + VERIFY_EQUAL_NONCONT(pIns->pTuning, nullptr); + else + VERIFY_EQUAL_NONCONT(pIns->pTuning->GetName(), UL_("Test Tuning")); VERIFY_EQUAL_NONCONT(pIns->pitchToTempoLock, TEMPO(130, 2000)); VERIFY_EQUAL_NONCONT(pIns->pluginVelocityHandling, PLUGIN_VELOCITYHANDLING_VOLUME); VERIFY_EQUAL_NONCONT(pIns->pluginVolumeHandling, PLUGIN_VOLUMEHANDLING_MIDI); - for(size_t i = 0; i < NOTE_MAX; i++) + for(size_t i = 0; i < 120; i++) { - VERIFY_EQUAL_NONCONT(pIns->Keyboard[i], (i == NOTE_MIDDLEC - 1) ? 99 : 1); + VERIFY_EQUAL_NONCONT(pIns->Keyboard[i], (i == NOTE_MIDDLEC - 1) ? (ins * 1111) : 1); VERIFY_EQUAL_NONCONT(pIns->NoteMap[i], (i == NOTE_MIDDLEC - 1) ? (i + 13) : (i + 1)); } @@ -3143,13 +3183,20 @@ static void TestLoadMPTMFile(const CSoundFile &sndFile) VERIFY_EQUAL_NONCONT(pIns->VolEnv[2].tick, 96); VERIFY_EQUAL_NONCONT(pIns->VolEnv[2].value, 0); - VERIFY_EQUAL_NONCONT(pIns->PanEnv.dwFlags, ENV_LOOP); - VERIFY_EQUAL_NONCONT(pIns->PanEnv.size(), 76); - VERIFY_EQUAL_NONCONT(pIns->PanEnv.nLoopStart, 22); + VERIFY_EQUAL_NONCONT(pIns->PanEnv.dwFlags, ENV_LOOP | ENV_SUSTAIN); + VERIFY_EQUAL_NONCONT(pIns->PanEnv.size(), (ins == 1) ? 74u : 76u); + VERIFY_EQUAL_NONCONT(pIns->PanEnv.nLoopStart, 26); VERIFY_EQUAL_NONCONT(pIns->PanEnv.nLoopEnd, 29); + VERIFY_EQUAL_NONCONT(pIns->PanEnv.nSustainStart, 27); + VERIFY_EQUAL_NONCONT(pIns->PanEnv.nSustainEnd, 28); VERIFY_EQUAL_NONCONT(pIns->PanEnv.nReleaseNode, ENV_RELEASE_NODE_UNSET); - VERIFY_EQUAL_NONCONT(pIns->PanEnv[75].tick, 427); - VERIFY_EQUAL_NONCONT(pIns->PanEnv[75].value, 27); + VERIFY_EQUAL_NONCONT(pIns->PanEnv[73].tick, 417); + VERIFY_EQUAL_NONCONT(pIns->PanEnv[73].value, 23); + if(ins == 2) + { + VERIFY_EQUAL_NONCONT(pIns->PanEnv[75].tick, 427); + VERIFY_EQUAL_NONCONT(pIns->PanEnv[75].value, 27); + } VERIFY_EQUAL_NONCONT(pIns->PitchEnv.dwFlags, ENV_ENABLED | ENV_CARRY | ENV_SUSTAIN | ENV_FILTER); VERIFY_EQUAL_NONCONT(pIns->PitchEnv.size(), 3); @@ -3163,10 +3210,12 @@ static void TestLoadMPTMFile(const CSoundFile &sndFile) VERIFY_EQUAL_NONCONT(sndFile.Order(0).GetLengthTailTrimmed(), 3); VERIFY_EQUAL_NONCONT(sndFile.Order(0).GetName(), U_("First Sequence")); - VERIFY_EQUAL_NONCONT(sndFile.Order(0)[0], sndFile.Order.GetIgnoreIndex()); + VERIFY_EQUAL_NONCONT(sndFile.Order(0)[0], PATTERNINDEX_SKIP); VERIFY_EQUAL_NONCONT(sndFile.Order(0)[1], 0); - VERIFY_EQUAL_NONCONT(sndFile.Order(0)[2], sndFile.Order.GetIgnoreIndex()); + VERIFY_EQUAL_NONCONT(sndFile.Order(0)[2], PATTERNINDEX_SKIP); VERIFY_EQUAL_NONCONT(sndFile.Order(0).GetRestartPos(), 1); + VERIFY_EQUAL_NONCONT(sndFile.Order(0).GetDefaultTempo(), TEMPO(139, 999)); + VERIFY_EQUAL_NONCONT(sndFile.Order(0).GetDefaultSpeed(), 5); VERIFY_EQUAL_NONCONT(sndFile.Order(1).GetLengthTailTrimmed(), 3); VERIFY_EQUAL_NONCONT(sndFile.Order(1).GetName(), U_("Second Sequence")); @@ -3174,6 +3223,8 @@ static void TestLoadMPTMFile(const CSoundFile &sndFile) VERIFY_EQUAL_NONCONT(sndFile.Order(1)[1], 2); VERIFY_EQUAL_NONCONT(sndFile.Order(1)[2], 3); VERIFY_EQUAL_NONCONT(sndFile.Order(1).GetRestartPos(), 2); + VERIFY_EQUAL_NONCONT(sndFile.Order(1).GetDefaultTempo(), TEMPO(123, 4500)); + VERIFY_EQUAL_NONCONT(sndFile.Order(1).GetDefaultSpeed(), 67); // Patterns VERIFY_EQUAL_NONCONT(sndFile.Patterns.GetNumPatterns(), 2); @@ -3210,6 +3261,17 @@ static void TestLoadMPTMFile(const CSoundFile &sndFile) VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(0, 0)->instr, 99); VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(0, 0)->GetValueVolCol(), 1); VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(0, 0)->GetValueEffectCol(), 200); + VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(1, 0)->IsPcNote(), true); + VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(1, 0)->note, NOTE_PCS); + VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(1, 0)->instr, 250); + VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(1, 0)->GetValueVolCol(), 999); + VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(1, 0)->GetValueEffectCol(), 999); + + VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(0, 1)->note, NOTE_MIN); + VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(1, 1)->note, NOTE_MIN + 119); + VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(2, 1)->note, NOTE_KEYOFF); + VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(3, 1)->note, NOTE_NOTECUT); + VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(4, 1)->note, NOTE_FADE); VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(31, 0)->IsEmpty(), true); VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetpModCommand(31, 1)->IsEmpty(), false); @@ -3273,15 +3335,16 @@ static void TestLoadS3MFile(const CSoundFile &sndFile, bool resaved) // Global Variables VERIFY_EQUAL_NONCONT(sndFile.GetTitle(), "S3M_Test__________________X"); - VERIFY_EQUAL_NONCONT(sndFile.m_nDefaultTempo, TEMPO(33, 0)); - VERIFY_EQUAL_NONCONT(sndFile.m_nDefaultSpeed, 254); + VERIFY_EQUAL_NONCONT(sndFile.Order().GetDefaultTempo(), TEMPO(33, 0)); + VERIFY_EQUAL_NONCONT(sndFile.Order().GetDefaultSpeed(), 254); VERIFY_EQUAL_NONCONT(sndFile.m_nDefaultGlobalVolume, 32 * 4); VERIFY_EQUAL_NONCONT(sndFile.m_nVSTiVolume, 36); VERIFY_EQUAL_NONCONT(sndFile.m_nSamplePreAmp, 16); - VERIFY_EQUAL_NONCONT((sndFile.m_SongFlags & SONG_FILE_FLAGS), SONG_FASTVOLSLIDES); + VERIFY_EQUAL_NONCONT(sndFile.m_SongFlags, SONG_FASTVOLSLIDES); VERIFY_EQUAL_NONCONT(sndFile.GetMixLevels(), MixLevels::Compatible); VERIFY_EQUAL_NONCONT(sndFile.m_nTempoMode, TempoMode::Classic); - VERIFY_EQUAL_NONCONT(sndFile.m_dwLastSavedWithVersion, resaved ? Version::Current() : MPT_V("1.27.00.00")); + VERIFY_EQUAL_NONCONT(sndFile.m_dwLastSavedWithVersion, resaved ? Version::Current() : MPT_V("1.32.00.32")); + VERIFY_EQUAL_NONCONT(sndFile.GetFileHistory().size(), 1); // Channels VERIFY_EQUAL_NONCONT(sndFile.GetNumChannels(), 4); @@ -3369,8 +3432,8 @@ static void TestLoadS3MFile(const CSoundFile &sndFile, bool resaved) VERIFY_EQUAL_NONCONT(sndFile.Order().GetRestartPos(), 0); VERIFY_EQUAL_NONCONT(sndFile.Order().GetLengthTailTrimmed(), 5); VERIFY_EQUAL_NONCONT(sndFile.Order()[0], 0); - VERIFY_EQUAL_NONCONT(sndFile.Order()[1], sndFile.Order.GetIgnoreIndex()); - VERIFY_EQUAL_NONCONT(sndFile.Order()[2], sndFile.Order.GetInvalidPatIndex()); + VERIFY_EQUAL_NONCONT(sndFile.Order()[1], PATTERNINDEX_SKIP); + VERIFY_EQUAL_NONCONT(sndFile.Order()[2], PATTERNINDEX_INVALID); VERIFY_EQUAL_NONCONT(sndFile.Order()[3], 1); VERIFY_EQUAL_NONCONT(sndFile.Order()[4], 0); @@ -3382,6 +3445,7 @@ static void TestLoadS3MFile(const CSoundFile &sndFile, bool resaved) VERIFY_EQUAL_NONCONT(sndFile.Patterns[0].GetOverrideSignature(), false); VERIFY_EQUAL_NONCONT(sndFile.Patterns[0].GetpModCommand(0, 0)->note, NOTE_MIN + 12); VERIFY_EQUAL_NONCONT(sndFile.Patterns[0].GetpModCommand(1, 0)->note, NOTE_MIN + 107); + VERIFY_EQUAL_NONCONT(sndFile.Patterns[0].GetpModCommand(2, 0)->note, NOTE_NOTECUT); VERIFY_EQUAL_NONCONT(sndFile.Patterns[0].GetpModCommand(0, 1)->volcmd, VOLCMD_VOLUME); VERIFY_EQUAL_NONCONT(sndFile.Patterns[0].GetpModCommand(0, 1)->vol, 0); VERIFY_EQUAL_NONCONT(sndFile.Patterns[0].GetpModCommand(1, 1)->volcmd, VOLCMD_VOLUME); @@ -3392,6 +3456,11 @@ static void TestLoadS3MFile(const CSoundFile &sndFile, bool resaved) VERIFY_EQUAL_NONCONT(sndFile.Patterns[0].GetpModCommand(3, 1)->vol, 64); VERIFY_EQUAL_NONCONT(sndFile.Patterns[0].GetpModCommand(0, 3)->command, CMD_SPEED); VERIFY_EQUAL_NONCONT(sndFile.Patterns[0].GetpModCommand(0, 3)->param, 0x11); + VERIFY_EQUAL_NONCONT(sndFile.Patterns[0].GetpModCommand(3, 0)->IsEmpty(), false); + VERIFY_EQUAL_NONCONT(sndFile.Patterns[0].GetpModCommand(3, 0)->note, NOTE_NONE); + VERIFY_EQUAL_NONCONT(sndFile.Patterns[0].GetpModCommand(3, 0)->instr, 99); + VERIFY_EQUAL_NONCONT(sndFile.Patterns[0].GetpModCommand(4, 0)->IsEmpty(), true); // Test for resaved out-of-range note + VERIFY_EQUAL_NONCONT(sndFile.Patterns[0].GetpModCommand(5, 0)->IsEmpty(), true); // Test for resaved out-of-range note VERIFY_EQUAL_NONCONT(sndFile.Patterns[1].GetNumRows(), 64); VERIFY_EQUAL_NONCONT(sndFile.Patterns.IsPatternEmpty(1), false); @@ -3404,7 +3473,7 @@ static void TestLoadMODFile(CSoundFile &sndFile) { // Global Variables VERIFY_EQUAL_NONCONT(sndFile.GetTitle(), "MOD_Test___________X"); - VERIFY_EQUAL_NONCONT((sndFile.m_SongFlags & SONG_FILE_FLAGS), SONG_PT_MODE | SONG_AMIGALIMITS | SONG_ISAMIGA); + VERIFY_EQUAL_NONCONT(sndFile.m_SongFlags, SONG_PT_MODE | SONG_AMIGALIMITS | SONG_ISAMIGA | SONG_FORMAT_NO_VOLCOL); VERIFY_EQUAL_NONCONT(sndFile.GetMixLevels(), MixLevels::Compatible); VERIFY_EQUAL_NONCONT(sndFile.m_nTempoMode, TempoMode::Classic); VERIFY_EQUAL_NONCONT(sndFile.GetNumChannels(), 4); @@ -3420,10 +3489,10 @@ static void TestLoadMODFile(CSoundFile &sndFile) VERIFY_EQUAL_NONCONT(allSubSongs.size(), 2); VERIFY_EQUAL_EPS(allSubSongs[0].duration, 2.04, 0.1); VERIFY_EQUAL_EPS(allSubSongs[1].duration, 118.84, 0.1); - VERIFY_EQUAL_NONCONT(allSubSongs[0].lastOrder, 0); - VERIFY_EQUAL_NONCONT(allSubSongs[0].lastRow, 1); - VERIFY_EQUAL_NONCONT(allSubSongs[1].lastOrder, 2); - VERIFY_EQUAL_NONCONT(allSubSongs[1].lastRow, 61); + VERIFY_EQUAL_NONCONT(allSubSongs[0].restartOrder, 0); + VERIFY_EQUAL_NONCONT(allSubSongs[0].restartRow, 1); + VERIFY_EQUAL_NONCONT(allSubSongs[1].restartOrder, 2); + VERIFY_EQUAL_NONCONT(allSubSongs[1].restartRow, 61); VERIFY_EQUAL_NONCONT(allSubSongs[1].startOrder, 2); VERIFY_EQUAL_NONCONT(allSubSongs[1].startRow, 0); @@ -3576,7 +3645,7 @@ static CSoundFile &GetSoundFile(TSoundFileContainer &sndFile) static TSoundFileContainer CreateSoundFileContainer(const mpt::PathString &filename) { - mpt::ifstream stream(filename, std::ios::binary); + mpt::IO::ifstream stream(filename, std::ios::binary); FileReader file = mpt::IO::make_FileCursor(stream); std::shared_ptr pSndFile = std::make_shared(); pSndFile->Create(file, CSoundFile::loadCompleteModule); @@ -3592,25 +3661,25 @@ static void DestroySoundFileContainer(TSoundFileContainer & /* sndFile */ ) static void SaveIT(const TSoundFileContainer &sndFile, const mpt::PathString &filename) { - mpt::ofstream f(filename, std::ios::binary); + mpt::IO::ofstream f(filename, std::ios::binary); sndFile->SaveIT(f, filename, false); } static void SaveXM(const TSoundFileContainer &sndFile, const mpt::PathString &filename) { - mpt::ofstream f(filename, std::ios::binary); + mpt::IO::ofstream f(filename, std::ios::binary); sndFile->SaveXM(f, false); } static void SaveS3M(const TSoundFileContainer &sndFile, const mpt::PathString &filename) { - mpt::ofstream f(filename, std::ios::binary); + mpt::IO::ofstream f(filename, std::ios::binary); sndFile->SaveS3M(f); } static void SaveMOD(const TSoundFileContainer &sndFile, const mpt::PathString &filename) { - mpt::ofstream f(filename, std::ios::binary); + mpt::IO::ofstream f(filename, std::ios::binary); sndFile->SaveMod(f); } @@ -3693,6 +3762,8 @@ static MPT_NOINLINE void TestLoadSaveFile() // file still works. GetSoundFile(sndFileContainer).m_nSamples++; GetSoundFile(sndFileContainer).Instruments[1]->Keyboard[110] = GetSoundFile(sndFileContainer).GetNumSamples(); + GetSoundFile(sndFileContainer).Patterns[1].GetpModCommand(30, 0)->note = NOTE_MIN; // Should not be saved to file + GetSoundFile(sndFileContainer).Patterns[1].GetpModCommand(31, 0)->note = NOTE_MIN + 108; // Ditto #ifndef MODPLUG_NO_FILESAVE // Test file saving @@ -3753,6 +3824,8 @@ static MPT_NOINLINE void TestLoadSaveFile() #ifndef MODPLUG_NO_FILESAVE // Test file saving sndFile.ChnSettings[1].dwFlags.set(CHN_MUTE); + sndFile.Patterns[0].GetpModCommand(4, 0)->note = NOTE_MIN; // Should not be saved to file + sndFile.Patterns[0].GetpModCommand(5, 0)->note = NOTE_MIN + 108; // Ditto sndFile.m_dwLastSavedWithVersion = Version::Current(); #if MPT_OS_DJGPP SaveS3M(sndFileContainer, filenameBase + P_("ss3")); @@ -3857,9 +3930,7 @@ static MPT_NOINLINE void TestEditing() #ifdef MODPLUG_TRACKER auto modDoc = static_cast(theApp.GetModDocTemplate()->CreateNewDocument()); auto &sndFile = modDoc->GetSoundFile(); - sndFile.Create(FileReader(), CSoundFile::loadCompleteModule, modDoc); - sndFile.m_nChannels = 4; - sndFile.ChangeModTypeTo(MOD_TYPE_MPT); + sndFile.Create(MOD_TYPE_MPT, 4, modDoc); // Rearrange channels sndFile.Patterns.ResizeArray(2); @@ -3957,13 +4028,22 @@ static void RunITCompressionTest(const std::vector &sampleData, FlagSet sampleData(sampleDataSize, 0); - std::srand(0); for(int i = 0; i < sampleDataSize; i++) { sampleData[i] = mpt::random(*s_PRNG); } + // Fade in the first half of the sample so that we test lower bit widths as well + for(int i = 0; i < sampleDataSize / 2; i++) + { + sampleData[i] = static_cast(Util::muldivr(sampleData[i], i, sampleDataSize / 2)); + } + // Add a few irregularities to the signal to provoke temporary switches to higher bit width + for(int i = 99; i < sampleDataSize / 2; i += 100) + { + sampleData[i] ^= 99; + } // Run each compression test with IT215 compression and without. for(int i = 0; i < 2; i++) @@ -4055,11 +4135,9 @@ static void GenerateCommands(CPattern& pat, const double dProbPcs, const double static MPT_NOINLINE void TestPCnoteSerialization() { FileReader file; - std::unique_ptr pSndFile = std::make_unique(); - CSoundFile &sndFile = *pSndFile.get(); - sndFile.m_nType = MOD_TYPE_MPT; - sndFile.Patterns.DestroyPatterns(); - sndFile.m_nChannels = ModSpecs::mptm.channelsMax; + mpt::heap_value pSndFile; + CSoundFile &sndFile = *pSndFile; + sndFile.Create(MOD_TYPE_MPT, ModSpecs::mptm.channelsMax); sndFile.Patterns.Insert(0, ModSpecs::mptm.patternRowsMin); sndFile.Patterns.Insert(1, 64); @@ -4622,8 +4700,8 @@ static MPT_NOINLINE void TestSampleConversion() sample.nLength = 65536; sample.uFlags.set(CHN_16BIT); sample.pData.pSample = sampleBuf.data(); - CopyAndNormalizeSample, SC::DecodeFloat32 > >(sample, source32.data(), 4*65536); - CopySample, SC::DecodeFloat32 > >(truncated16.data(), 65536, 1, source32.data(), 65536 * 4, 1); + CopyAndNormalizeSample, SC::DecodeFloat32 > >(sample, source32.data(), 4*65536); + CopySample, SC::DecodeFloat32 > >(truncated16.data(), 65536, 1, source32.data(), 65536 * 4, 1); for(std::size_t i = 0; i < 65536; i++) { @@ -4797,6 +4875,69 @@ static MPT_NOINLINE void TestSampleConversion() } +static void TestMIDIMacroParser() +{ + uint8 rawData[] = {0x90, 0x40, 0x70, 0x50, 0x70, 0xF5, 0xF6, 0x60, 0x70, 0xF0}; + MIDIMacroParser rawParser{mpt::as_span(rawData)}; + mpt::span midiMsg; + rawParser.NextMessage(midiMsg); + VERIFY_EQUAL_NONCONT(mpt::span_elements_equal(midiMsg, std::array{0x90, 0x40, 0x70}), true); + rawParser.NextMessage(midiMsg); + VERIFY_EQUAL_NONCONT(mpt::span_elements_equal(midiMsg, std::array{0x90, 0x50, 0x70}), true); + rawParser.NextMessage(midiMsg); + VERIFY_EQUAL_NONCONT(mpt::span_elements_equal(midiMsg, std::array{0xF5}), true); + rawParser.NextMessage(midiMsg); + VERIFY_EQUAL_NONCONT(mpt::span_elements_equal(midiMsg, std::array{0xF6}), true); + rawParser.NextMessage(midiMsg); + VERIFY_EQUAL_NONCONT(mpt::span_elements_equal(midiMsg, std::array{0x90, 0x60, 0x70}), true); + rawParser.NextMessage(midiMsg); + VERIFY_EQUAL_NONCONT(mpt::span_elements_equal(midiMsg, std::array{0xF0}), true); + VERIFY_EQUAL_NONCONT(rawParser.NextMessage(midiMsg), false); + + mpt::heap_value sndFile; + mpt::heap_value playState; + mpt::heap_value instr; + sndFile->Create(MOD_TYPE_MPT, 2); + playState->m_nGlobalVolume = MAX_GLOBAL_VOLUME / 2; + playState->Chn[3].nMasterChn = 2; + playState->Chn[3].nLastNote = NOTE_MIN + 0x60; + playState->Chn[3].nVolume = 193; + playState->Chn[3].nVolSwing = 32; + playState->Chn[3].nCalcVolume = 8192; + playState->Chn[3].nGlobalVol = 32; + playState->Chn[3].nInsVol = 32; + playState->Chn[3].nPan = 32; + playState->Chn[3].nRealPan = 192; + playState->Chn[3].oldOffset = 0x123456; + playState->Chn[3].pModInstrument = instr.get(); + instr->nMidiChannel = MidiLastChannel; + instr->nMidiProgram = 1 + 9; + instr->wMidiBank = 1 + 130; + const char macro[] = "r42 9c z v 50 h F5 F6 n u Bc a b xyop F0 41 10 00 10 12 10 00 04 00 2 s"; + std::vector out(std::size(macro) + 1); + MIDIMacroParser macroParser{*sndFile, playState.get(), 3, false, mpt::as_span(macro), mpt::as_span(out), 64, 0}; + macroParser.NextMessage(midiMsg); + VERIFY_EQUAL_NONCONT(mpt::span_elements_equal(midiMsg, std::array{0x9F, 0x40, 0x0E}), true); + macroParser.NextMessage(midiMsg); + VERIFY_EQUAL_NONCONT(mpt::span_elements_equal(midiMsg, std::array{0x9F, 0x50, 0x01}), true); + macroParser.NextMessage(midiMsg); + VERIFY_EQUAL_NONCONT(mpt::span_elements_equal(midiMsg, std::array{0xF5}), true); + macroParser.NextMessage(midiMsg); + VERIFY_EQUAL_NONCONT(mpt::span_elements_equal(midiMsg, std::array{0xF6}), true); + macroParser.NextMessage(midiMsg); + VERIFY_EQUAL_NONCONT(mpt::span_elements_equal(midiMsg, std::array{0x9F, 0x60, 0x08}), true); + macroParser.NextMessage(midiMsg); + VERIFY_EQUAL_NONCONT(mpt::span_elements_equal(midiMsg, std::array{0xBF, 0x01, 0x02}), true); + macroParser.NextMessage(midiMsg); + VERIFY_EQUAL_NONCONT(mpt::span_elements_equal(midiMsg, std::array{0xBF, 0x10, 0x60}), true); + macroParser.NextMessage(midiMsg); + VERIFY_EQUAL_NONCONT(mpt::span_elements_equal(midiMsg, std::array{0xBF, 0x34, 0x09}), true); + macroParser.NextMessage(midiMsg); + VERIFY_EQUAL_NONCONT(mpt::span_elements_equal(midiMsg, std::array{0xF0, 0x41, 0x10, 0x00, 0x10, 0x12, 0x10, 0x00, 0x04, 0x00, 0x02, 0x6A, 0xF7}), true); + VERIFY_EQUAL_NONCONT(macroParser.NextMessage(midiMsg), false); +} + + } // namespace Test OPENMPT_NAMESPACE_END diff --git a/Frameworks/OpenMPT/OpenMPT/test/test.h b/Frameworks/OpenMPT/OpenMPT/test/test.h index 4c87487f0..792dc6aea 100644 --- a/Frameworks/OpenMPT/OpenMPT/test/test.h +++ b/Frameworks/OpenMPT/OpenMPT/test/test.h @@ -17,6 +17,10 @@ OPENMPT_NAMESPACE_BEGIN namespace Test { +void PrintHeader(); + +void PrintFooter(); + void DoTests(); } // namespace Test diff --git a/Frameworks/OpenMPT/libOpenMPT.xcodeproj/project.pbxproj b/Frameworks/OpenMPT/libOpenMPT.xcodeproj/project.pbxproj index fe59f6d62..671160dc2 100644 --- a/Frameworks/OpenMPT/libOpenMPT.xcodeproj/project.pbxproj +++ b/Frameworks/OpenMPT/libOpenMPT.xcodeproj/project.pbxproj @@ -7,7 +7,6 @@ objects = { /* Begin PBXBuildFile section */ - 830995B227787BB800857684 /* Dither.h in Headers */ = {isa = PBXBuildFile; fileRef = 830995B127787BB800857684 /* Dither.h */; }; 830995B927787BEE00857684 /* SampleCopy.h in Headers */ = {isa = PBXBuildFile; fileRef = 830995B327787BEE00857684 /* SampleCopy.h */; }; 830995BA27787BEE00857684 /* SampleNormalize.h in Headers */ = {isa = PBXBuildFile; fileRef = 830995B427787BEE00857684 /* SampleNormalize.h */; }; 830995BB27787BEE00857684 /* Load_fmt.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 830995B527787BEE00857684 /* Load_fmt.cpp */; }; @@ -106,7 +105,6 @@ 830996E527787E9A00857684 /* mfc.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 8309964227787E9A00857684 /* mfc.hpp */; }; 830996E627787E9A00857684 /* windows.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 8309964327787E9A00857684 /* windows.hpp */; }; 830996E727787E9A00857684 /* libc.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 8309964427787E9A00857684 /* libc.hpp */; }; - 830996E827787E9A00857684 /* exception_text.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 8309964627787E9A00857684 /* exception_text.hpp */; }; 830996E927787E9A00857684 /* wrapping_divide.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 8309964827787E9A00857684 /* wrapping_divide.hpp */; }; 830996EA27787E9A00857684 /* integer.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 8309964927787E9A00857684 /* integer.hpp */; }; 830996EB27787E9A00857684 /* arithmetic_shift.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 8309964A27787E9A00857684 /* arithmetic_shift.hpp */; }; @@ -123,7 +121,6 @@ 830996F627787E9A00857684 /* check_platform.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 8309965527787E9A00857684 /* check_platform.hpp */; }; 830996F727787E9A00857684 /* saturate_cast.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 8309965627787E9A00857684 /* saturate_cast.hpp */; }; 830996F827787E9A00857684 /* source_location.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 8309965727787E9A00857684 /* source_location.hpp */; }; - 830996F927787E9A00857684 /* floatingpoint.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 8309965827787E9A00857684 /* floatingpoint.hpp */; }; 830996FA27787E9A00857684 /* tests_base_math.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 8309965A27787E9A00857684 /* tests_base_math.hpp */; }; 830996FB27787E9A00857684 /* tests_base_wrapping_divide.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 8309965B27787E9A00857684 /* tests_base_wrapping_divide.hpp */; }; 830996FC27787E9A00857684 /* tests_base_saturate_cast.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 8309965C27787E9A00857684 /* tests_base_saturate_cast.hpp */; }; @@ -181,7 +178,6 @@ 831132EB21F9565F001F678F /* Load_c67.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 831132E621F9565F001F678F /* Load_c67.cpp */; }; 831132EC21F9565F001F678F /* OPL.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 831132E721F9565F001F678F /* OPL.cpp */; }; 83649B972A0340FF00CD0580 /* mptFileTemporary.h in Headers */ = {isa = PBXBuildFile; fileRef = 83649B922A0340FF00CD0580 /* mptFileTemporary.h */; }; - 83649B982A0340FF00CD0580 /* mptFileTemporary.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 83649B932A0340FF00CD0580 /* mptFileTemporary.cpp */; }; 83649B992A0340FF00CD0580 /* mptCPU.h in Headers */ = {isa = PBXBuildFile; fileRef = 83649B942A0340FF00CD0580 /* mptCPU.h */; }; 83649B9A2A0340FF00CD0580 /* mptFileType.h in Headers */ = {isa = PBXBuildFile; fileRef = 83649B952A0340FF00CD0580 /* mptFileType.h */; }; 83649B9B2A0340FF00CD0580 /* mptFileType.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 83649B962A0340FF00CD0580 /* mptFileType.cpp */; }; @@ -221,9 +217,47 @@ 83AA7D332519B694004C5298 /* SampleFormatSFZ.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 83AA7D2F2519B694004C5298 /* SampleFormatSFZ.cpp */; }; 83AA7D342519B694004C5298 /* SampleFormatBRR.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 83AA7D302519B694004C5298 /* SampleFormatBRR.cpp */; }; 83AA7D352519B694004C5298 /* TinyFFT.h in Headers */ = {isa = PBXBuildFile; fileRef = 83AA7D312519B694004C5298 /* TinyFFT.h */; }; + 83BB83AF2DF2BCD4002077FC /* GzipWriter.h in Headers */ = {isa = PBXBuildFile; fileRef = 83BB83AE2DF2BCD4002077FC /* GzipWriter.h */; }; + 83BB83C82DF2BD29002077FC /* Load_etx.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 83BB83B32DF2BD29002077FC /* Load_etx.cpp */; }; + 83BB83C92DF2BD29002077FC /* Load_unic.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 83BB83BF2DF2BD29002077FC /* Load_unic.cpp */; }; + 83BB83CA2DF2BD29002077FC /* PlaybackTest.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 83BB83C52DF2BD29002077FC /* PlaybackTest.cpp */; }; + 83BB83CB2DF2BD29002077FC /* Load_cba.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 83BB83B22DF2BD29002077FC /* Load_cba.cpp */; }; + 83BB83CC2DF2BD29002077FC /* PlayState.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 83BB83C72DF2BD29002077FC /* PlayState.cpp */; }; + 83BB83CD2DF2BD29002077FC /* Load_rtm.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 83BB83BC2DF2BD29002077FC /* Load_rtm.cpp */; }; + 83BB83CE2DF2BD29002077FC /* Load_ims.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 83BB83B82DF2BD29002077FC /* Load_ims.cpp */; }; + 83BB83CF2DF2BD29002077FC /* Load_tcb.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 83BB83BE2DF2BD29002077FC /* Load_tcb.cpp */; }; + 83BB83D02DF2BD29002077FC /* Load_ice.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 83BB83B72DF2BD29002077FC /* Load_ice.cpp */; }; + 83BB83D12DF2BD29002077FC /* MODTools.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 83BB83C32DF2BD29002077FC /* MODTools.cpp */; }; + 83BB83D22DF2BD29002077FC /* MIDIMacroParser.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 83BB83C12DF2BD29002077FC /* MIDIMacroParser.cpp */; }; + 83BB83D32DF2BD29002077FC /* Load_fc.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 83BB83B42DF2BD29002077FC /* Load_fc.cpp */; }; + 83BB83D42DF2BD29002077FC /* Load_kris.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 83BB83B92DF2BD29002077FC /* Load_kris.cpp */; }; + 83BB83D52DF2BD29002077FC /* InstrumentSynth.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 83BB83B12DF2BD29002077FC /* InstrumentSynth.cpp */; }; + 83BB83D62DF2BD29002077FC /* Load_ftm.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 83BB83B52DF2BD29002077FC /* Load_ftm.cpp */; }; + 83BB83D72DF2BD29002077FC /* Load_pt36.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 83BB83BA2DF2BD29002077FC /* Load_pt36.cpp */; }; + 83BB83D82DF2BD29002077FC /* Load_puma.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 83BB83BB2DF2BD29002077FC /* Load_puma.cpp */; }; + 83BB83D92DF2BD29002077FC /* Load_stk.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 83BB83BD2DF2BD29002077FC /* Load_stk.cpp */; }; + 83BB83DA2DF2BD29002077FC /* Load_gmc.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 83BB83B62DF2BD29002077FC /* Load_gmc.cpp */; }; + 83BB83DB2DF2BD29002077FC /* PlayState.h in Headers */ = {isa = PBXBuildFile; fileRef = 83BB83C62DF2BD29002077FC /* PlayState.h */; }; + 83BB83DC2DF2BD29002077FC /* PlaybackTest.h in Headers */ = {isa = PBXBuildFile; fileRef = 83BB83C42DF2BD29002077FC /* PlaybackTest.h */; }; + 83BB83DD2DF2BD29002077FC /* MIDIMacroParser.h in Headers */ = {isa = PBXBuildFile; fileRef = 83BB83C02DF2BD29002077FC /* MIDIMacroParser.h */; }; + 83BB83DE2DF2BD29002077FC /* MODTools.h in Headers */ = {isa = PBXBuildFile; fileRef = 83BB83C22DF2BD29002077FC /* MODTools.h */; }; + 83BB83DF2DF2BD29002077FC /* InstrumentSynth.h in Headers */ = {isa = PBXBuildFile; fileRef = 83BB83B02DF2BD29002077FC /* InstrumentSynth.h */; }; + 83BB83E42DF2BD49002077FC /* type_traits.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 83BB83E32DF2BD49002077FC /* type_traits.hpp */; }; + 83BB83E52DF2BD49002077FC /* size.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 83BB83E22DF2BD49002077FC /* size.hpp */; }; + 83BB83E62DF2BD49002077FC /* float.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 83BB83E12DF2BD49002077FC /* float.hpp */; }; + 83BB83E72DF2BD49002077FC /* debugging.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 83BB83E02DF2BD49002077FC /* debugging.hpp */; }; + 83BB83E92DF2BD5E002077FC /* libcxx.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 83BB83E82DF2BD5E002077FC /* libcxx.hpp */; }; + 83BB83EB2DF2BD75002077FC /* filedata_base_unseekable_buffer.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 83BB83EA2DF2BD75002077FC /* filedata_base_unseekable_buffer.hpp */; }; + 83BB83EE2DF2BD9B002077FC /* main.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 83BB83EC2DF2BD9B002077FC /* main.hpp */; }; + 83BB83F02DF2BDC1002077FC /* any_engine.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 83BB83EF2DF2BDC1002077FC /* any_engine.hpp */; }; + 83BB83F22DF2BDDE002077FC /* PlatformFixes.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 83BB83F12DF2BDDE002077FC /* PlatformFixes.hpp */; }; + 83BB83F52DF2BE03002077FC /* magic.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 83BB83F32DF2BE03002077FC /* magic.hpp */; }; + 83BB83FC2DF2BE22002077FC /* wav_write.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 83BB83FA2DF2BE22002077FC /* wav_write.cpp */; }; + 83BB83FD2DF2BE22002077FC /* wav.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 83BB83F72DF2BE22002077FC /* wav.hpp */; }; + 83BB83FE2DF2BE22002077FC /* wav_write.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 83BB83F92DF2BE22002077FC /* wav_write.hpp */; }; + 83BB83FF2DF2BE22002077FC /* tags.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 83BB83F62DF2BE22002077FC /* tags.hpp */; }; 83E5EFD01FFEF9D200659F0F /* config.h in Headers */ = {isa = PBXBuildFile; fileRef = 83E5EFCE1FFEF9D200659F0F /* config.h */; }; 83E5FC661FFEFA0D00659F0F /* version.h in Headers */ = {isa = PBXBuildFile; fileRef = 83E5FC2D1FFEFA0D00659F0F /* version.h */; }; - 83E5FC671FFEFA0D00659F0F /* mptStringParse.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 83E5FC2E1FFEFA0D00659F0F /* mptStringParse.cpp */; }; 83E5FC681FFEFA0D00659F0F /* stdafx.h in Headers */ = {isa = PBXBuildFile; fileRef = 83E5FC2F1FFEFA0D00659F0F /* stdafx.h */; }; 83E5FC691FFEFA0D00659F0F /* ComponentManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 83E5FC301FFEFA0D00659F0F /* ComponentManager.h */; }; 83E5FC6C1FFEFA0D00659F0F /* mptStringFormat.h in Headers */ = {isa = PBXBuildFile; fileRef = 83E5FC331FFEFA0D00659F0F /* mptStringFormat.h */; }; @@ -238,14 +272,10 @@ 83E5FC7C1FFEFA0D00659F0F /* misc_util.h in Headers */ = {isa = PBXBuildFile; fileRef = 83E5FC431FFEFA0D00659F0F /* misc_util.h */; }; 83E5FC7F1FFEFA0D00659F0F /* serialization_utils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 83E5FC461FFEFA0D00659F0F /* serialization_utils.cpp */; }; 83E5FC801FFEFA0D00659F0F /* mptFileIO.h in Headers */ = {isa = PBXBuildFile; fileRef = 83E5FC471FFEFA0D00659F0F /* mptFileIO.h */; }; - 83E5FC811FFEFA0D00659F0F /* mptStringFormat.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 83E5FC481FFEFA0D00659F0F /* mptStringFormat.cpp */; }; 83E5FC831FFEFA0D00659F0F /* serialization_utils.h in Headers */ = {isa = PBXBuildFile; fileRef = 83E5FC4A1FFEFA0D00659F0F /* serialization_utils.h */; }; 83E5FC861FFEFA0D00659F0F /* mptPathString.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 83E5FC4D1FFEFA0D00659F0F /* mptPathString.cpp */; }; 83E5FC881FFEFA0D00659F0F /* mptTime.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 83E5FC4F1FFEFA0D00659F0F /* mptTime.cpp */; }; - 83E5FC891FFEFA0D00659F0F /* mptString.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 83E5FC501FFEFA0D00659F0F /* mptString.cpp */; }; - 83E5FC8B1FFEFA0D00659F0F /* mptFileIO.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 83E5FC521FFEFA0D00659F0F /* mptFileIO.cpp */; }; 83E5FC8C1FFEFA0D00659F0F /* mptString.h in Headers */ = {isa = PBXBuildFile; fileRef = 83E5FC531FFEFA0D00659F0F /* mptString.h */; }; - 83E5FC8D1FFEFA0D00659F0F /* mptStringParse.h in Headers */ = {isa = PBXBuildFile; fileRef = 83E5FC541FFEFA0D00659F0F /* mptStringParse.h */; }; 83E5FC8E1FFEFA0D00659F0F /* mptRandom.h in Headers */ = {isa = PBXBuildFile; fileRef = 83E5FC551FFEFA0D00659F0F /* mptRandom.h */; }; 83E5FC911FFEFA0D00659F0F /* FileReader.h in Headers */ = {isa = PBXBuildFile; fileRef = 83E5FC581FFEFA0D00659F0F /* FileReader.h */; }; 83E5FC921FFEFA0D00659F0F /* mptPathString.h in Headers */ = {isa = PBXBuildFile; fileRef = 83E5FC591FFEFA0D00659F0F /* mptPathString.h */; }; @@ -256,7 +286,6 @@ 83E5FCCC1FFEFA1A00659F0F /* libopenmpt_stream_callbacks_fd.h in Headers */ = {isa = PBXBuildFile; fileRef = 83E5FC9D1FFEFA1A00659F0F /* libopenmpt_stream_callbacks_fd.h */; }; 83E5FCCD1FFEFA1A00659F0F /* libopenmpt.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 83E5FC9E1FFEFA1A00659F0F /* libopenmpt.hpp */; settings = {ATTRIBUTES = (Public, ); }; }; 83E5FCCE1FFEFA1A00659F0F /* libopenmpt.h in Headers */ = {isa = PBXBuildFile; fileRef = 83E5FC9F1FFEFA1A00659F0F /* libopenmpt.h */; settings = {ATTRIBUTES = (Public, ); }; }; - 83E5FCCF1FFEFA1A00659F0F /* libopenmpt_plugin_settings.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 83E5FCA01FFEFA1A00659F0F /* libopenmpt_plugin_settings.hpp */; }; 83E5FCD01FFEFA1A00659F0F /* libopenmpt_internal.h in Headers */ = {isa = PBXBuildFile; fileRef = 83E5FCA11FFEFA1A00659F0F /* libopenmpt_internal.h */; }; 83E5FCD31FFEFA1A00659F0F /* libopenmpt_c.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 83E5FCA41FFEFA1A00659F0F /* libopenmpt_c.cpp */; }; 83E5FCD51FFEFA1A00659F0F /* libopenmpt_ext_impl.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 83E5FCA61FFEFA1A00659F0F /* libopenmpt_ext_impl.hpp */; }; @@ -319,7 +348,6 @@ 83E5FDE01FFEFA8500659F0F /* DigiBoosterEcho.h in Headers */ = {isa = PBXBuildFile; fileRef = 83E5FD391FFEFA8400659F0F /* DigiBoosterEcho.h */; }; 83E5FDE11FFEFA8500659F0F /* PlugInterface.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 83E5FD3A1FFEFA8400659F0F /* PlugInterface.cpp */; }; 83E5FDE21FFEFA8500659F0F /* LFOPlugin.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 83E5FD3B1FFEFA8400659F0F /* LFOPlugin.cpp */; }; - 83E5FDE31FFEFA8500659F0F /* OpCodes.h in Headers */ = {isa = PBXBuildFile; fileRef = 83E5FD3C1FFEFA8400659F0F /* OpCodes.h */; }; 83E5FDE51FFEFA8500659F0F /* PluginManager.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 83E5FD3E1FFEFA8400659F0F /* PluginManager.cpp */; }; 83E5FDE61FFEFA8500659F0F /* DigiBoosterEcho.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 83E5FD3F1FFEFA8400659F0F /* DigiBoosterEcho.cpp */; }; 83E5FDE71FFEFA8500659F0F /* DMOPlugin.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 83E5FD411FFEFA8400659F0F /* DMOPlugin.cpp */; }; @@ -442,46 +470,8 @@ 83E5FE631FFEFEA600659F0F /* svn_version.h in Headers */ = {isa = PBXBuildFile; fileRef = 83E5FE601FFEFEA600659F0F /* svn_version.h */; }; 83E5FE661FFEFFA500659F0F /* libz.tbd in Frameworks */ = {isa = PBXBuildFile; fileRef = 83E5FE651FFEFFA500659F0F /* libz.tbd */; }; 83EC0B682C70B32C00DB51E5 /* BuildSettingsCompiler.h in Headers */ = {isa = PBXBuildFile; fileRef = 83EC0B672C70B32C00DB51E5 /* BuildSettingsCompiler.h */; }; - 83F30AB2286EBBEA0005EF06 /* l12tabs.h in Headers */ = {isa = PBXBuildFile; fileRef = 83F30A8A286EBBEA0005EF06 /* l12tabs.h */; }; - 83F30AB3286EBBEA0005EF06 /* synth_8bit.h in Headers */ = {isa = PBXBuildFile; fileRef = 83F30A8B286EBBEA0005EF06 /* synth_8bit.h */; }; - 83F30AB4286EBBEA0005EF06 /* index.h in Headers */ = {isa = PBXBuildFile; fileRef = 83F30A8C286EBBEA0005EF06 /* index.h */; }; - 83F30AB5286EBBEA0005EF06 /* synth_ntom.h in Headers */ = {isa = PBXBuildFile; fileRef = 83F30A8D286EBBEA0005EF06 /* synth_ntom.h */; }; - 83F30AB6286EBBEA0005EF06 /* reader.h in Headers */ = {isa = PBXBuildFile; fileRef = 83F30A8E286EBBEA0005EF06 /* reader.h */; }; - 83F30AB7286EBBEA0005EF06 /* debug.h in Headers */ = {isa = PBXBuildFile; fileRef = 83F30A8F286EBBEA0005EF06 /* debug.h */; }; 83F30AB8286EBBEA0005EF06 /* mpg123.h in Headers */ = {isa = PBXBuildFile; fileRef = 83F30A90286EBBEA0005EF06 /* mpg123.h */; }; - 83F30AB9286EBBEA0005EF06 /* dither.h in Headers */ = {isa = PBXBuildFile; fileRef = 83F30A91286EBBEA0005EF06 /* dither.h */; }; - 83F30ABA286EBBEA0005EF06 /* optimize.h in Headers */ = {isa = PBXBuildFile; fileRef = 83F30A92286EBBEA0005EF06 /* optimize.h */; }; - 83F30ABB286EBBEA0005EF06 /* getbits.h in Headers */ = {isa = PBXBuildFile; fileRef = 83F30A93286EBBEA0005EF06 /* getbits.h */; }; - 83F30ABC286EBBEA0005EF06 /* synth.h in Headers */ = {isa = PBXBuildFile; fileRef = 83F30A94286EBBEA0005EF06 /* synth.h */; }; - 83F30ABD286EBBEA0005EF06 /* synths.h in Headers */ = {isa = PBXBuildFile; fileRef = 83F30A95286EBBEA0005EF06 /* synths.h */; }; - 83F30ABE286EBBEA0005EF06 /* mangle.h in Headers */ = {isa = PBXBuildFile; fileRef = 83F30A96286EBBEA0005EF06 /* mangle.h */; }; - 83F30ABF286EBBEA0005EF06 /* init_layer12.h in Headers */ = {isa = PBXBuildFile; fileRef = 83F30A97286EBBEA0005EF06 /* init_layer12.h */; }; - 83F30AC0286EBBEA0005EF06 /* frame.h in Headers */ = {isa = PBXBuildFile; fileRef = 83F30A98286EBBEA0005EF06 /* frame.h */; }; - 83F30AC1286EBBEA0005EF06 /* parse.h in Headers */ = {isa = PBXBuildFile; fileRef = 83F30A99286EBBEA0005EF06 /* parse.h */; }; - 83F30AC2286EBBEA0005EF06 /* synth_sse3d.h in Headers */ = {isa = PBXBuildFile; fileRef = 83F30A9A286EBBEA0005EF06 /* synth_sse3d.h */; }; - 83F30AC3286EBBEA0005EF06 /* l3bandgain.h in Headers */ = {isa = PBXBuildFile; fileRef = 83F30A9B286EBBEA0005EF06 /* l3bandgain.h */; }; - 83F30AC4286EBBEA0005EF06 /* mpg123lib_intern.h in Headers */ = {isa = PBXBuildFile; fileRef = 83F30A9C286EBBEA0005EF06 /* mpg123lib_intern.h */; }; - 83F30AC5286EBBEA0005EF06 /* init_layer3.h in Headers */ = {isa = PBXBuildFile; fileRef = 83F30A9D286EBBEA0005EF06 /* init_layer3.h */; }; - 83F30AC6286EBBEA0005EF06 /* getcpuflags.h in Headers */ = {isa = PBXBuildFile; fileRef = 83F30A9E286EBBEA0005EF06 /* getcpuflags.h */; }; - 83F30AC7286EBBEA0005EF06 /* icy.h in Headers */ = {isa = PBXBuildFile; fileRef = 83F30A9F286EBBEA0005EF06 /* icy.h */; }; 83F30AC8286EBBEA0005EF06 /* fmt123.h in Headers */ = {isa = PBXBuildFile; fileRef = 83F30AA0286EBBEA0005EF06 /* fmt123.h */; }; - 83F30AC9286EBBEA0005EF06 /* newhuffman.h in Headers */ = {isa = PBXBuildFile; fileRef = 83F30AA1286EBBEA0005EF06 /* newhuffman.h */; }; - 83F30ACA286EBBEA0005EF06 /* synth_mono.h in Headers */ = {isa = PBXBuildFile; fileRef = 83F30AA2286EBBEA0005EF06 /* synth_mono.h */; }; - 83F30ACB286EBBEA0005EF06 /* gapless.h in Headers */ = {isa = PBXBuildFile; fileRef = 83F30AA3286EBBEA0005EF06 /* gapless.h */; }; - 83F30ACC286EBBEA0005EF06 /* costabs.h in Headers */ = {isa = PBXBuildFile; fileRef = 83F30AA4286EBBEA0005EF06 /* costabs.h */; }; - 83F30ACD286EBBEA0005EF06 /* l2tables.h in Headers */ = {isa = PBXBuildFile; fileRef = 83F30AA5286EBBEA0005EF06 /* l2tables.h */; }; - 83F30ACE286EBBEA0005EF06 /* swap_bytes_impl.h in Headers */ = {isa = PBXBuildFile; fileRef = 83F30AA6286EBBEA0005EF06 /* swap_bytes_impl.h */; }; - 83F30ACF286EBBEA0005EF06 /* icy2utf8.h in Headers */ = {isa = PBXBuildFile; fileRef = 83F30AA7286EBBEA0005EF06 /* icy2utf8.h */; }; - 83F30AD0286EBBEA0005EF06 /* l3tabs.h in Headers */ = {isa = PBXBuildFile; fileRef = 83F30AA8286EBBEA0005EF06 /* l3tabs.h */; }; - 83F30AD1286EBBEA0005EF06 /* dither_impl.h in Headers */ = {isa = PBXBuildFile; fileRef = 83F30AA9286EBBEA0005EF06 /* dither_impl.h */; }; - 83F30AD2286EBBEA0005EF06 /* mpeghead.h in Headers */ = {isa = PBXBuildFile; fileRef = 83F30AAA286EBBEA0005EF06 /* mpeghead.h */; }; - 83F30AD3286EBBEA0005EF06 /* huffman.h in Headers */ = {isa = PBXBuildFile; fileRef = 83F30AAB286EBBEA0005EF06 /* huffman.h */; }; - 83F30AD4286EBBEA0005EF06 /* sample.h in Headers */ = {isa = PBXBuildFile; fileRef = 83F30AAC286EBBEA0005EF06 /* sample.h */; }; - 83F30AD5286EBBEA0005EF06 /* init_costabs.h in Headers */ = {isa = PBXBuildFile; fileRef = 83F30AAD286EBBEA0005EF06 /* init_costabs.h */; }; - 83F30AD6286EBBEA0005EF06 /* decode.h in Headers */ = {isa = PBXBuildFile; fileRef = 83F30AAE286EBBEA0005EF06 /* decode.h */; }; - 83F30AD7286EBBEA0005EF06 /* abi_align.h in Headers */ = {isa = PBXBuildFile; fileRef = 83F30AAF286EBBEA0005EF06 /* abi_align.h */; }; - 83F30AD8286EBBEA0005EF06 /* true.h in Headers */ = {isa = PBXBuildFile; fileRef = 83F30AB0286EBBEA0005EF06 /* true.h */; }; - 83F30AD9286EBBEA0005EF06 /* id3.h in Headers */ = {isa = PBXBuildFile; fileRef = 83F30AB1286EBBEA0005EF06 /* id3.h */; }; 83F30ADD286EBC080005EF06 /* vorbisfile.h in Headers */ = {isa = PBXBuildFile; fileRef = 83F30ADB286EBC080005EF06 /* vorbisfile.h */; }; 83F30ADE286EBC080005EF06 /* codec.h in Headers */ = {isa = PBXBuildFile; fileRef = 83F30ADC286EBC080005EF06 /* codec.h */; }; 83F30AE0286EBC2B0005EF06 /* libvorbisfile.3.dylib in Frameworks */ = {isa = PBXBuildFile; fileRef = 83F30ADF286EBC2B0005EF06 /* libvorbisfile.3.dylib */; }; @@ -491,7 +481,6 @@ /* End PBXBuildFile section */ /* Begin PBXFileReference section */ - 830995B127787BB800857684 /* Dither.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Dither.h; sourceTree = ""; }; 830995B327787BEE00857684 /* SampleCopy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SampleCopy.h; sourceTree = ""; }; 830995B427787BEE00857684 /* SampleNormalize.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SampleNormalize.h; sourceTree = ""; }; 830995B527787BEE00857684 /* Load_fmt.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Load_fmt.cpp; sourceTree = ""; }; @@ -593,7 +582,6 @@ 8309964227787E9A00857684 /* mfc.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = mfc.hpp; sourceTree = ""; }; 8309964327787E9A00857684 /* windows.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = windows.hpp; sourceTree = ""; }; 8309964427787E9A00857684 /* libc.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = libc.hpp; sourceTree = ""; }; - 8309964627787E9A00857684 /* exception_text.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = exception_text.hpp; sourceTree = ""; }; 8309964827787E9A00857684 /* wrapping_divide.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = wrapping_divide.hpp; sourceTree = ""; }; 8309964927787E9A00857684 /* integer.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = integer.hpp; sourceTree = ""; }; 8309964A27787E9A00857684 /* arithmetic_shift.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = arithmetic_shift.hpp; sourceTree = ""; }; @@ -610,7 +598,6 @@ 8309965527787E9A00857684 /* check_platform.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = check_platform.hpp; sourceTree = ""; }; 8309965627787E9A00857684 /* saturate_cast.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = saturate_cast.hpp; sourceTree = ""; }; 8309965727787E9A00857684 /* source_location.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = source_location.hpp; sourceTree = ""; }; - 8309965827787E9A00857684 /* floatingpoint.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = floatingpoint.hpp; sourceTree = ""; }; 8309965A27787E9A00857684 /* tests_base_math.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = tests_base_math.hpp; sourceTree = ""; }; 8309965B27787E9A00857684 /* tests_base_wrapping_divide.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = tests_base_wrapping_divide.hpp; sourceTree = ""; }; 8309965C27787E9A00857684 /* tests_base_saturate_cast.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = tests_base_saturate_cast.hpp; sourceTree = ""; }; @@ -668,7 +655,6 @@ 831132E621F9565F001F678F /* Load_c67.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Load_c67.cpp; sourceTree = ""; }; 831132E721F9565F001F678F /* OPL.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = OPL.cpp; sourceTree = ""; }; 83649B922A0340FF00CD0580 /* mptFileTemporary.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mptFileTemporary.h; sourceTree = ""; }; - 83649B932A0340FF00CD0580 /* mptFileTemporary.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = mptFileTemporary.cpp; sourceTree = ""; }; 83649B942A0340FF00CD0580 /* mptCPU.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mptCPU.h; sourceTree = ""; }; 83649B952A0340FF00CD0580 /* mptFileType.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mptFileType.h; sourceTree = ""; }; 83649B962A0340FF00CD0580 /* mptFileType.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = mptFileType.cpp; sourceTree = ""; }; @@ -709,11 +695,49 @@ 83AA7D2F2519B694004C5298 /* SampleFormatSFZ.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SampleFormatSFZ.cpp; sourceTree = ""; }; 83AA7D302519B694004C5298 /* SampleFormatBRR.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SampleFormatBRR.cpp; sourceTree = ""; }; 83AA7D312519B694004C5298 /* TinyFFT.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TinyFFT.h; sourceTree = ""; }; + 83BB83AE2DF2BCD4002077FC /* GzipWriter.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GzipWriter.h; sourceTree = ""; }; + 83BB83B02DF2BD29002077FC /* InstrumentSynth.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = InstrumentSynth.h; sourceTree = ""; }; + 83BB83B12DF2BD29002077FC /* InstrumentSynth.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = InstrumentSynth.cpp; sourceTree = ""; }; + 83BB83B22DF2BD29002077FC /* Load_cba.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Load_cba.cpp; sourceTree = ""; }; + 83BB83B32DF2BD29002077FC /* Load_etx.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Load_etx.cpp; sourceTree = ""; }; + 83BB83B42DF2BD29002077FC /* Load_fc.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Load_fc.cpp; sourceTree = ""; }; + 83BB83B52DF2BD29002077FC /* Load_ftm.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Load_ftm.cpp; sourceTree = ""; }; + 83BB83B62DF2BD29002077FC /* Load_gmc.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Load_gmc.cpp; sourceTree = ""; }; + 83BB83B72DF2BD29002077FC /* Load_ice.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Load_ice.cpp; sourceTree = ""; }; + 83BB83B82DF2BD29002077FC /* Load_ims.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Load_ims.cpp; sourceTree = ""; }; + 83BB83B92DF2BD29002077FC /* Load_kris.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Load_kris.cpp; sourceTree = ""; }; + 83BB83BA2DF2BD29002077FC /* Load_pt36.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Load_pt36.cpp; sourceTree = ""; }; + 83BB83BB2DF2BD29002077FC /* Load_puma.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Load_puma.cpp; sourceTree = ""; }; + 83BB83BC2DF2BD29002077FC /* Load_rtm.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Load_rtm.cpp; sourceTree = ""; }; + 83BB83BD2DF2BD29002077FC /* Load_stk.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Load_stk.cpp; sourceTree = ""; }; + 83BB83BE2DF2BD29002077FC /* Load_tcb.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Load_tcb.cpp; sourceTree = ""; }; + 83BB83BF2DF2BD29002077FC /* Load_unic.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = Load_unic.cpp; sourceTree = ""; }; + 83BB83C02DF2BD29002077FC /* MIDIMacroParser.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MIDIMacroParser.h; sourceTree = ""; }; + 83BB83C12DF2BD29002077FC /* MIDIMacroParser.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MIDIMacroParser.cpp; sourceTree = ""; }; + 83BB83C22DF2BD29002077FC /* MODTools.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = MODTools.h; sourceTree = ""; }; + 83BB83C32DF2BD29002077FC /* MODTools.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = MODTools.cpp; sourceTree = ""; }; + 83BB83C42DF2BD29002077FC /* PlaybackTest.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PlaybackTest.h; sourceTree = ""; }; + 83BB83C52DF2BD29002077FC /* PlaybackTest.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = PlaybackTest.cpp; sourceTree = ""; }; + 83BB83C62DF2BD29002077FC /* PlayState.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = PlayState.h; sourceTree = ""; }; + 83BB83C72DF2BD29002077FC /* PlayState.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = PlayState.cpp; sourceTree = ""; }; + 83BB83E02DF2BD49002077FC /* debugging.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = debugging.hpp; sourceTree = ""; }; + 83BB83E12DF2BD49002077FC /* float.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = float.hpp; sourceTree = ""; }; + 83BB83E22DF2BD49002077FC /* size.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = size.hpp; sourceTree = ""; }; + 83BB83E32DF2BD49002077FC /* type_traits.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = type_traits.hpp; sourceTree = ""; }; + 83BB83E82DF2BD5E002077FC /* libcxx.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = libcxx.hpp; sourceTree = ""; }; + 83BB83EA2DF2BD75002077FC /* filedata_base_unseekable_buffer.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = filedata_base_unseekable_buffer.hpp; sourceTree = ""; }; + 83BB83EC2DF2BD9B002077FC /* main.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = main.hpp; sourceTree = ""; }; + 83BB83EF2DF2BDC1002077FC /* any_engine.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = any_engine.hpp; sourceTree = ""; }; + 83BB83F12DF2BDDE002077FC /* PlatformFixes.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = PlatformFixes.hpp; sourceTree = ""; }; + 83BB83F32DF2BE03002077FC /* magic.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = magic.hpp; sourceTree = ""; }; + 83BB83F62DF2BE22002077FC /* tags.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = tags.hpp; sourceTree = ""; }; + 83BB83F72DF2BE22002077FC /* wav.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = wav.hpp; sourceTree = ""; }; + 83BB83F92DF2BE22002077FC /* wav_write.hpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.h; path = wav_write.hpp; sourceTree = ""; }; + 83BB83FA2DF2BE22002077FC /* wav_write.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = wav_write.cpp; sourceTree = ""; }; 83E5EFBD1FFEF7CC00659F0F /* libOpenMPT.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = libOpenMPT.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 83E5EFCE1FFEF9D200659F0F /* config.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = config.h; sourceTree = SOURCE_ROOT; }; 83E5EFCF1FFEF9D200659F0F /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = SOURCE_ROOT; }; 83E5FC2D1FFEFA0D00659F0F /* version.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = version.h; sourceTree = ""; }; - 83E5FC2E1FFEFA0D00659F0F /* mptStringParse.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = mptStringParse.cpp; sourceTree = ""; }; 83E5FC2F1FFEFA0D00659F0F /* stdafx.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = stdafx.h; sourceTree = ""; }; 83E5FC301FFEFA0D00659F0F /* ComponentManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ComponentManager.h; sourceTree = ""; }; 83E5FC331FFEFA0D00659F0F /* mptStringFormat.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mptStringFormat.h; sourceTree = ""; }; @@ -728,14 +752,10 @@ 83E5FC431FFEFA0D00659F0F /* misc_util.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = misc_util.h; sourceTree = ""; }; 83E5FC461FFEFA0D00659F0F /* serialization_utils.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = serialization_utils.cpp; sourceTree = ""; }; 83E5FC471FFEFA0D00659F0F /* mptFileIO.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mptFileIO.h; sourceTree = ""; }; - 83E5FC481FFEFA0D00659F0F /* mptStringFormat.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = mptStringFormat.cpp; sourceTree = ""; }; 83E5FC4A1FFEFA0D00659F0F /* serialization_utils.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = serialization_utils.h; sourceTree = ""; }; 83E5FC4D1FFEFA0D00659F0F /* mptPathString.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = mptPathString.cpp; sourceTree = ""; }; 83E5FC4F1FFEFA0D00659F0F /* mptTime.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = mptTime.cpp; sourceTree = ""; }; - 83E5FC501FFEFA0D00659F0F /* mptString.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = mptString.cpp; sourceTree = ""; }; - 83E5FC521FFEFA0D00659F0F /* mptFileIO.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = mptFileIO.cpp; sourceTree = ""; }; 83E5FC531FFEFA0D00659F0F /* mptString.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mptString.h; sourceTree = ""; }; - 83E5FC541FFEFA0D00659F0F /* mptStringParse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mptStringParse.h; sourceTree = ""; }; 83E5FC551FFEFA0D00659F0F /* mptRandom.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mptRandom.h; sourceTree = ""; }; 83E5FC581FFEFA0D00659F0F /* FileReader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = FileReader.h; sourceTree = ""; }; 83E5FC591FFEFA0D00659F0F /* mptPathString.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mptPathString.h; sourceTree = ""; }; @@ -746,7 +766,6 @@ 83E5FC9D1FFEFA1A00659F0F /* libopenmpt_stream_callbacks_fd.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = libopenmpt_stream_callbacks_fd.h; sourceTree = ""; }; 83E5FC9E1FFEFA1A00659F0F /* libopenmpt.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = libopenmpt.hpp; sourceTree = ""; }; 83E5FC9F1FFEFA1A00659F0F /* libopenmpt.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = libopenmpt.h; sourceTree = ""; }; - 83E5FCA01FFEFA1A00659F0F /* libopenmpt_plugin_settings.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = libopenmpt_plugin_settings.hpp; sourceTree = ""; }; 83E5FCA11FFEFA1A00659F0F /* libopenmpt_internal.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = libopenmpt_internal.h; sourceTree = ""; }; 83E5FCA41FFEFA1A00659F0F /* libopenmpt_c.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = libopenmpt_c.cpp; sourceTree = ""; }; 83E5FCA61FFEFA1A00659F0F /* libopenmpt_ext_impl.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = libopenmpt_ext_impl.hpp; sourceTree = ""; }; @@ -809,7 +828,6 @@ 83E5FD391FFEFA8400659F0F /* DigiBoosterEcho.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = DigiBoosterEcho.h; sourceTree = ""; }; 83E5FD3A1FFEFA8400659F0F /* PlugInterface.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PlugInterface.cpp; sourceTree = ""; }; 83E5FD3B1FFEFA8400659F0F /* LFOPlugin.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = LFOPlugin.cpp; sourceTree = ""; }; - 83E5FD3C1FFEFA8400659F0F /* OpCodes.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OpCodes.h; sourceTree = ""; }; 83E5FD3E1FFEFA8400659F0F /* PluginManager.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PluginManager.cpp; sourceTree = ""; }; 83E5FD3F1FFEFA8400659F0F /* DigiBoosterEcho.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DigiBoosterEcho.cpp; sourceTree = ""; }; 83E5FD411FFEFA8400659F0F /* DMOPlugin.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = DMOPlugin.cpp; sourceTree = ""; }; @@ -933,46 +951,8 @@ 83E5FE601FFEFEA600659F0F /* svn_version.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = svn_version.h; sourceTree = ""; }; 83E5FE651FFEFFA500659F0F /* libz.tbd */ = {isa = PBXFileReference; lastKnownFileType = "sourcecode.text-based-dylib-definition"; name = libz.tbd; path = usr/lib/libz.tbd; sourceTree = SDKROOT; }; 83EC0B672C70B32C00DB51E5 /* BuildSettingsCompiler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = BuildSettingsCompiler.h; sourceTree = ""; }; - 83F30A8A286EBBEA0005EF06 /* l12tabs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = l12tabs.h; sourceTree = ""; }; - 83F30A8B286EBBEA0005EF06 /* synth_8bit.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = synth_8bit.h; sourceTree = ""; }; - 83F30A8C286EBBEA0005EF06 /* index.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = index.h; sourceTree = ""; }; - 83F30A8D286EBBEA0005EF06 /* synth_ntom.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = synth_ntom.h; sourceTree = ""; }; - 83F30A8E286EBBEA0005EF06 /* reader.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = reader.h; sourceTree = ""; }; - 83F30A8F286EBBEA0005EF06 /* debug.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = debug.h; sourceTree = ""; }; 83F30A90286EBBEA0005EF06 /* mpg123.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mpg123.h; sourceTree = ""; }; - 83F30A91286EBBEA0005EF06 /* dither.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = dither.h; sourceTree = ""; }; - 83F30A92286EBBEA0005EF06 /* optimize.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = optimize.h; sourceTree = ""; }; - 83F30A93286EBBEA0005EF06 /* getbits.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = getbits.h; sourceTree = ""; }; - 83F30A94286EBBEA0005EF06 /* synth.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = synth.h; sourceTree = ""; }; - 83F30A95286EBBEA0005EF06 /* synths.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = synths.h; sourceTree = ""; }; - 83F30A96286EBBEA0005EF06 /* mangle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mangle.h; sourceTree = ""; }; - 83F30A97286EBBEA0005EF06 /* init_layer12.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = init_layer12.h; sourceTree = ""; }; - 83F30A98286EBBEA0005EF06 /* frame.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = frame.h; sourceTree = ""; }; - 83F30A99286EBBEA0005EF06 /* parse.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = parse.h; sourceTree = ""; }; - 83F30A9A286EBBEA0005EF06 /* synth_sse3d.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = synth_sse3d.h; sourceTree = ""; }; - 83F30A9B286EBBEA0005EF06 /* l3bandgain.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = l3bandgain.h; sourceTree = ""; }; - 83F30A9C286EBBEA0005EF06 /* mpg123lib_intern.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mpg123lib_intern.h; sourceTree = ""; }; - 83F30A9D286EBBEA0005EF06 /* init_layer3.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = init_layer3.h; sourceTree = ""; }; - 83F30A9E286EBBEA0005EF06 /* getcpuflags.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = getcpuflags.h; sourceTree = ""; }; - 83F30A9F286EBBEA0005EF06 /* icy.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = icy.h; sourceTree = ""; }; 83F30AA0286EBBEA0005EF06 /* fmt123.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = fmt123.h; sourceTree = ""; }; - 83F30AA1286EBBEA0005EF06 /* newhuffman.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = newhuffman.h; sourceTree = ""; }; - 83F30AA2286EBBEA0005EF06 /* synth_mono.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = synth_mono.h; sourceTree = ""; }; - 83F30AA3286EBBEA0005EF06 /* gapless.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = gapless.h; sourceTree = ""; }; - 83F30AA4286EBBEA0005EF06 /* costabs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = costabs.h; sourceTree = ""; }; - 83F30AA5286EBBEA0005EF06 /* l2tables.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = l2tables.h; sourceTree = ""; }; - 83F30AA6286EBBEA0005EF06 /* swap_bytes_impl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = swap_bytes_impl.h; sourceTree = ""; }; - 83F30AA7286EBBEA0005EF06 /* icy2utf8.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = icy2utf8.h; sourceTree = ""; }; - 83F30AA8286EBBEA0005EF06 /* l3tabs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = l3tabs.h; sourceTree = ""; }; - 83F30AA9286EBBEA0005EF06 /* dither_impl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = dither_impl.h; sourceTree = ""; }; - 83F30AAA286EBBEA0005EF06 /* mpeghead.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = mpeghead.h; sourceTree = ""; }; - 83F30AAB286EBBEA0005EF06 /* huffman.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = huffman.h; sourceTree = ""; }; - 83F30AAC286EBBEA0005EF06 /* sample.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = sample.h; sourceTree = ""; }; - 83F30AAD286EBBEA0005EF06 /* init_costabs.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = init_costabs.h; sourceTree = ""; }; - 83F30AAE286EBBEA0005EF06 /* decode.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = decode.h; sourceTree = ""; }; - 83F30AAF286EBBEA0005EF06 /* abi_align.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = abi_align.h; sourceTree = ""; }; - 83F30AB0286EBBEA0005EF06 /* true.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = true.h; sourceTree = ""; }; - 83F30AB1286EBBEA0005EF06 /* id3.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = id3.h; sourceTree = ""; }; 83F30ADB286EBC080005EF06 /* vorbisfile.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = vorbisfile.h; sourceTree = ""; }; 83F30ADC286EBC080005EF06 /* codec.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = codec.h; sourceTree = ""; }; 83F30ADF286EBC2B0005EF06 /* libvorbisfile.3.dylib */ = {isa = PBXFileReference; lastKnownFileType = "compiled.mach-o.dylib"; name = libvorbisfile.3.dylib; path = ../../ThirdParty/vorbis/lib/libvorbisfile.3.dylib; sourceTree = ""; }; @@ -1020,7 +1000,6 @@ 830995D027787E9A00857684 /* endian */, 8309962027787E9A00857684 /* environment */, 83649BB32A03420100CD0580 /* exception */, - 8309964527787E9A00857684 /* exception_text */, 8309961027787E9A00857684 /* format */, 8309960027787E9A00857684 /* io */, 83649BC12A03424E00CD0580 /* io_file */, @@ -1031,6 +1010,7 @@ 8309960D27787E9A00857684 /* io_write */, 8309962227787E9A00857684 /* LICENSE.BSD-3-Clause.txt */, 830995F127787E9A00857684 /* LICENSE.BSL-1.0.txt */, + 83BB83ED2DF2BD9B002077FC /* main */, 8309963027787E9A00857684 /* mutex */, 830995D727787E9A00857684 /* osinfo */, 830995F227787E9A00857684 /* out_of_memory */, @@ -1049,10 +1029,10 @@ 830995CB27787E9A00857684 /* detect */ = { isa = PBXGroup; children = ( + 830995CF27787E9A00857684 /* dl.hpp */, + 830995CE27787E9A00857684 /* ltdl.hpp */, 830995CC27787E9A00857684 /* mfc.hpp */, 830995CD27787E9A00857684 /* nlohmann_json.hpp */, - 830995CE27787E9A00857684 /* ltdl.hpp */, - 830995CF27787E9A00857684 /* dl.hpp */, ); path = detect; sourceTree = ""; @@ -1112,23 +1092,24 @@ 830995DF27787E9A00857684 /* io_read */ = { isa = PBXGroup; children = ( - 830995E027787E9A00857684 /* filecursor_traits_memory.hpp */, + 830995EB27787E9A00857684 /* callbackstream.hpp */, 830995E127787E9A00857684 /* filecursor.hpp */, + 830995EA27787E9A00857684 /* filecursor_callbackstream.hpp */, 830995E227787E9A00857684 /* filecursor_filename_traits.hpp */, 830995E327787E9A00857684 /* filecursor_memory.hpp */, - 830995E427787E9A00857684 /* filedata_base_unseekable.hpp */, 830995E527787E9A00857684 /* filecursor_stdstream.hpp */, - 830995E627787E9A00857684 /* filereader.hpp */, - 830995E727787E9A00857684 /* filedata_base_seekable.hpp */, 830995E827787E9A00857684 /* filecursor_traits_filedata.hpp */, + 830995E027787E9A00857684 /* filecursor_traits_memory.hpp */, 830995E927787E9A00857684 /* filedata.hpp */, - 830995EA27787E9A00857684 /* filecursor_callbackstream.hpp */, - 830995EB27787E9A00857684 /* callbackstream.hpp */, + 830995F027787E9A00857684 /* filedata_base.hpp */, + 830995EE27787E9A00857684 /* filedata_base_buffered.hpp */, + 830995E727787E9A00857684 /* filedata_base_seekable.hpp */, + 830995E427787E9A00857684 /* filedata_base_unseekable.hpp */, + 83BB83EA2DF2BD75002077FC /* filedata_base_unseekable_buffer.hpp */, 830995EC27787E9A00857684 /* filedata_callbackstream.hpp */, 830995ED27787E9A00857684 /* filedata_memory.hpp */, - 830995EE27787E9A00857684 /* filedata_base_buffered.hpp */, 830995EF27787E9A00857684 /* filedata_stdstream.hpp */, - 830995F027787E9A00857684 /* filedata_base.hpp */, + 830995E627787E9A00857684 /* filereader.hpp */, ); path = io_read; sourceTree = ""; @@ -1144,8 +1125,8 @@ 830995F427787E9A00857684 /* test */ = { isa = PBXGroup; children = ( - 830995F527787E9A00857684 /* test_macros.hpp */, 830995F627787E9A00857684 /* test.hpp */, + 830995F527787E9A00857684 /* test_macros.hpp */, ); path = test; sourceTree = ""; @@ -1153,9 +1134,9 @@ 830995F727787E9A00857684 /* parse */ = { isa = PBXGroup; children = ( - 830995F827787E9A00857684 /* tests */, 830995FA27787E9A00857684 /* parse.hpp */, 830995FB27787E9A00857684 /* split.hpp */, + 830995F827787E9A00857684 /* tests */, ); path = parse; sourceTree = ""; @@ -1171,8 +1152,8 @@ 830995FC27787E9A00857684 /* crc */ = { isa = PBXGroup; children = ( - 830995FD27787E9A00857684 /* tests */, 830995FF27787E9A00857684 /* crc.hpp */, + 830995FD27787E9A00857684 /* tests */, ); path = crc; sourceTree = ""; @@ -1188,12 +1169,12 @@ 8309960027787E9A00857684 /* io */ = { isa = PBXGroup; children = ( + 8309960727787E9A00857684 /* base.hpp */, + 8309960327787E9A00857684 /* io.hpp */, 8309960127787E9A00857684 /* io_span.hpp */, 8309960227787E9A00857684 /* io_stdstream.hpp */, - 8309960327787E9A00857684 /* io.hpp */, - 8309960427787E9A00857684 /* tests */, 8309960627787E9A00857684 /* io_virtual_wrapper.hpp */, - 8309960727787E9A00857684 /* base.hpp */, + 8309960427787E9A00857684 /* tests */, ); path = io; sourceTree = ""; @@ -1217,8 +1198,8 @@ 8309960A27787E9A00857684 /* audio */ = { isa = PBXGroup; children = ( - 8309960B27787E9A00857684 /* span.hpp */, 8309960C27787E9A00857684 /* sample.hpp */, + 8309960B27787E9A00857684 /* span.hpp */, ); path = audio; sourceTree = ""; @@ -1241,12 +1222,12 @@ 8309961527787E9A00857684 /* default_string.hpp */, 8309961D27787E9A00857684 /* helpers.hpp */, 8309961127787E9A00857684 /* join.hpp */, - 8309961A27787E9A00857684 /* message_macros.hpp */, 8309961F27787E9A00857684 /* message.hpp */, + 8309961A27787E9A00857684 /* message_macros.hpp */, + 8309961B27787E9A00857684 /* simple.hpp */, 8309961427787E9A00857684 /* simple_floatingpoint.hpp */, 8309961327787E9A00857684 /* simple_integer.hpp */, 8309961E27787E9A00857684 /* simple_spec.hpp */, - 8309961B27787E9A00857684 /* simple.hpp */, 8309961727787E9A00857684 /* tests */, ); path = format; @@ -1255,8 +1236,8 @@ 8309961727787E9A00857684 /* tests */ = { isa = PBXGroup; children = ( - 8309961827787E9A00857684 /* tests_format_simple.hpp */, 8309961927787E9A00857684 /* tests_format_message.hpp */, + 8309961827787E9A00857684 /* tests_format_simple.hpp */, ); path = tests; sourceTree = ""; @@ -1272,10 +1253,10 @@ 8309962327787E9A00857684 /* binary */ = { isa = PBXGroup; children = ( - 8309962427787E9A00857684 /* tests */, + 8309962827787E9A00857684 /* base64.hpp */, 8309962627787E9A00857684 /* base64url.hpp */, 8309962727787E9A00857684 /* hex.hpp */, - 8309962827787E9A00857684 /* base64.hpp */, + 8309962427787E9A00857684 /* tests */, ); path = binary; sourceTree = ""; @@ -1291,10 +1272,10 @@ 8309962927787E9A00857684 /* string */ = { isa = PBXGroup; children = ( - 8309962A27787E9A00857684 /* utility.hpp */, - 8309962B27787E9A00857684 /* tests */, 8309962E27787E9A00857684 /* buffer.hpp */, + 8309962B27787E9A00857684 /* tests */, 8309962F27787E9A00857684 /* types.hpp */, + 8309962A27787E9A00857684 /* utility.hpp */, ); path = string; sourceTree = ""; @@ -1302,8 +1283,8 @@ 8309962B27787E9A00857684 /* tests */ = { isa = PBXGroup; children = ( - 8309962C27787E9A00857684 /* tests_string_utility.hpp */, 8309962D27787E9A00857684 /* tests_string_buffer.hpp */, + 8309962C27787E9A00857684 /* tests_string_utility.hpp */, ); path = tests; sourceTree = ""; @@ -1319,14 +1300,15 @@ 8309963227787E9A00857684 /* random */ = { isa = PBXGroup; children = ( - 8309963327787E9A00857684 /* engine.hpp */, - 8309963427787E9A00857684 /* default_engines.hpp */, - 8309963527787E9A00857684 /* tests */, - 8309963727787E9A00857684 /* seed.hpp */, - 8309963827787E9A00857684 /* random.hpp */, - 8309963927787E9A00857684 /* device.hpp */, + 83BB83EF2DF2BDC1002077FC /* any_engine.hpp */, 8309963A27787E9A00857684 /* crand.hpp */, + 8309963427787E9A00857684 /* default_engines.hpp */, + 8309963927787E9A00857684 /* device.hpp */, + 8309963327787E9A00857684 /* engine.hpp */, 8309963B27787E9A00857684 /* engine_lcg.hpp */, + 8309963827787E9A00857684 /* random.hpp */, + 8309963727787E9A00857684 /* seed.hpp */, + 8309963527787E9A00857684 /* tests */, ); path = random; sourceTree = ""; @@ -1343,8 +1325,8 @@ isa = PBXGroup; children = ( 8309963D27787E9A00857684 /* guid.hpp */, - 8309963E27787E9A00857684 /* uuid.hpp */, 8309963F27787E9A00857684 /* tests */, + 8309963E27787E9A00857684 /* uuid.hpp */, ); path = uuid; sourceTree = ""; @@ -1361,21 +1343,14 @@ isa = PBXGroup; children = ( 83649BAD2A0341B300CD0580 /* compiler.hpp */, + 8309964427787E9A00857684 /* libc.hpp */, + 83BB83E82DF2BD5E002077FC /* libcxx.hpp */, 8309964227787E9A00857684 /* mfc.hpp */, 8309964327787E9A00857684 /* windows.hpp */, - 8309964427787E9A00857684 /* libc.hpp */, ); path = check; sourceTree = ""; }; - 8309964527787E9A00857684 /* exception_text */ = { - isa = PBXGroup; - children = ( - 8309964627787E9A00857684 /* exception_text.hpp */, - ); - path = exception_text; - sourceTree = ""; - }; 8309964727787E9A00857684 /* base */ = { isa = PBXGroup; children = ( @@ -1388,14 +1363,15 @@ 8309965527787E9A00857684 /* check_platform.hpp */, 8309966827787E9A00857684 /* compiletime_warning.hpp */, 8309966D27787E9A00857684 /* constexpr_throw.hpp */, + 83BB83E02DF2BD49002077FC /* debugging.hpp */, + 8309965127787E9A00857684 /* detect.hpp */, 83649BAB2A03419D00CD0580 /* detect_arch.hpp */, 8309966C27787E9A00857684 /* detect_compiler.hpp */, 8309964B27787E9A00857684 /* detect_libc.hpp */, 8309966327787E9A00857684 /* detect_libcxx.hpp */, 8309966F27787E9A00857684 /* detect_os.hpp */, 8309966427787E9A00857684 /* detect_quirks.hpp */, - 8309965127787E9A00857684 /* detect.hpp */, - 8309965827787E9A00857684 /* floatingpoint.hpp */, + 83BB83E12DF2BD49002077FC /* float.hpp */, 8309964927787E9A00857684 /* integer.hpp */, 8309964E27787E9A00857684 /* macros.hpp */, 8309966627787E9A00857684 /* math.hpp */, @@ -1409,9 +1385,11 @@ 8309966727787E9A00857684 /* saturate_round.hpp */, 8309964F27787E9A00857684 /* secure.hpp */, 8309967027787E9A00857684 /* semantic_version.hpp */, + 83BB83E22DF2BD49002077FC /* size.hpp */, 8309965727787E9A00857684 /* source_location.hpp */, 8309964D27787E9A00857684 /* span.hpp */, 8309965927787E9A00857684 /* tests */, + 83BB83E32DF2BD49002077FC /* type_traits.hpp */, 8309964C27787E9A00857684 /* utility.hpp */, 8309966527787E9A00857684 /* version.hpp */, 8309964827787E9A00857684 /* wrapping_divide.hpp */, @@ -1436,10 +1414,13 @@ isa = PBXGroup; children = ( 8309967227787E9A00857684 /* all */, + 8309968827787E9A00857684 /* base */, + 83BB83F42DF2BE03002077FC /* fileformat_base */, + 8309968627787E9A00857684 /* logging */, 8309967427787E9A00857684 /* random */, 8309967627787E9A00857684 /* soundbase */, - 8309968627787E9A00857684 /* logging */, - 8309968827787E9A00857684 /* base */, + 83BB83F82DF2BE22002077FC /* soundfile_data */, + 83BB83FB2DF2BE22002077FC /* soundfile_write */, ); path = openmpt; sourceTree = ""; @@ -1448,6 +1429,7 @@ isa = PBXGroup; children = ( 8309967327787E9A00857684 /* BuildSettings.hpp */, + 83BB83F12DF2BDDE002077FC /* PlatformFixes.hpp */, ); path = all; sourceTree = ""; @@ -1463,21 +1445,21 @@ 8309967627787E9A00857684 /* soundbase */ = { isa = PBXGroup; children = ( - 8309967727787E9A00857684 /* SampleFormat.hpp */, - 8309967827787E9A00857684 /* SampleConvert.hpp */, - 8309967927787E9A00857684 /* MixSample.hpp */, - 8309967A27787E9A00857684 /* Dither.hpp */, - 8309967B27787E9A00857684 /* SampleDecode.hpp */, - 8309967C27787E9A00857684 /* SampleConvertFixedPoint.hpp */, - 8309967D27787E9A00857684 /* DitherSimple.hpp */, - 8309967E27787E9A00857684 /* MixSampleConvert.hpp */, - 8309967F27787E9A00857684 /* CopyMix.hpp */, - 8309968027787E9A00857684 /* DitherNone.hpp */, 8309968127787E9A00857684 /* Copy.hpp */, + 8309967F27787E9A00857684 /* CopyMix.hpp */, + 8309967A27787E9A00857684 /* Dither.hpp */, 8309968227787E9A00857684 /* DitherModPlug.hpp */, - 8309968327787E9A00857684 /* SampleEncode.hpp */, - 8309968427787E9A00857684 /* SampleClipFixedPoint.hpp */, + 8309968027787E9A00857684 /* DitherNone.hpp */, + 8309967D27787E9A00857684 /* DitherSimple.hpp */, + 8309967927787E9A00857684 /* MixSample.hpp */, + 8309967E27787E9A00857684 /* MixSampleConvert.hpp */, 8309968527787E9A00857684 /* SampleClip.hpp */, + 8309968427787E9A00857684 /* SampleClipFixedPoint.hpp */, + 8309967827787E9A00857684 /* SampleConvert.hpp */, + 8309967C27787E9A00857684 /* SampleConvertFixedPoint.hpp */, + 8309967B27787E9A00857684 /* SampleDecode.hpp */, + 8309968327787E9A00857684 /* SampleEncode.hpp */, + 8309967727787E9A00857684 /* SampleFormat.hpp */, ); path = soundbase; sourceTree = ""; @@ -1493,9 +1475,9 @@ 8309968827787E9A00857684 /* base */ = { isa = PBXGroup; children = ( - 8309968927787E9A00857684 /* Int24.hpp */, - 8309968A27787E9A00857684 /* FlagSet.hpp */, 8309968B27787E9A00857684 /* Endian.hpp */, + 8309968A27787E9A00857684 /* FlagSet.hpp */, + 8309968927787E9A00857684 /* Int24.hpp */, 8309968C27787E9A00857684 /* Types.hpp */, ); path = base; @@ -1504,8 +1486,8 @@ 83649BA42A03418500CD0580 /* arch */ = { isa = PBXGroup; children = ( - 83649BA52A03418500CD0580 /* feature_flags.hpp */, 83649BA62A03418500CD0580 /* arch.hpp */, + 83649BA52A03418500CD0580 /* feature_flags.hpp */, 83649BA72A03418500CD0580 /* x86_amd64.hpp */, ); path = arch; @@ -1514,10 +1496,10 @@ 83649BB32A03420100CD0580 /* exception */ = { isa = PBXGroup; children = ( - 83649BB42A03420100CD0580 /* logic_error.hpp */, - 83649BB52A03420100CD0580 /* exception_text.hpp */, - 83649BB62A03420100CD0580 /* runtime_error.hpp */, 83649BB72A03420100CD0580 /* exception.hpp */, + 83649BB52A03420100CD0580 /* exception_text.hpp */, + 83649BB42A03420100CD0580 /* logic_error.hpp */, + 83649BB62A03420100CD0580 /* runtime_error.hpp */, ); path = exception; sourceTree = ""; @@ -1525,8 +1507,8 @@ 83649BBE2A03424E00CD0580 /* io_file_unique */ = { isa = PBXGroup; children = ( - 83649BBF2A03424E00CD0580 /* unique_tempfilename.hpp */, 83649BC02A03424E00CD0580 /* unique_basename.hpp */, + 83649BBF2A03424E00CD0580 /* unique_tempfilename.hpp */, ); path = io_file_unique; sourceTree = ""; @@ -1534,10 +1516,10 @@ 83649BC12A03424E00CD0580 /* io_file */ = { isa = PBXGroup; children = ( - 83649BC22A03424E00CD0580 /* outputfile.hpp */, - 83649BC32A03424E00CD0580 /* inputfile.hpp */, 83649BC42A03424E00CD0580 /* fileref.hpp */, 83649BC52A03424E00CD0580 /* fstream.hpp */, + 83649BC32A03424E00CD0580 /* inputfile.hpp */, + 83649BC22A03424E00CD0580 /* outputfile.hpp */, ); path = io_file; sourceTree = ""; @@ -1561,11 +1543,11 @@ 83649BD82A0342AB00CD0580 /* path */ = { isa = PBXGroup; children = ( - 83649BD92A0342AB00CD0580 /* native_path.hpp */, - 83649BDA2A0342AB00CD0580 /* path.hpp */, 83649BDB2A0342AB00CD0580 /* basic_path.hpp */, - 83649BDC2A0342AB00CD0580 /* os_path_long.hpp */, + 83649BD92A0342AB00CD0580 /* native_path.hpp */, 83649BDD2A0342AB00CD0580 /* os_path.hpp */, + 83649BDC2A0342AB00CD0580 /* os_path_long.hpp */, + 83649BDA2A0342AB00CD0580 /* path.hpp */, ); path = path; sourceTree = ""; @@ -1579,6 +1561,40 @@ path = "../../Xcode-config"; sourceTree = ""; }; + 83BB83ED2DF2BD9B002077FC /* main */ = { + isa = PBXGroup; + children = ( + 83BB83EC2DF2BD9B002077FC /* main.hpp */, + ); + path = main; + sourceTree = ""; + }; + 83BB83F42DF2BE03002077FC /* fileformat_base */ = { + isa = PBXGroup; + children = ( + 83BB83F32DF2BE03002077FC /* magic.hpp */, + ); + path = fileformat_base; + sourceTree = ""; + }; + 83BB83F82DF2BE22002077FC /* soundfile_data */ = { + isa = PBXGroup; + children = ( + 83BB83F62DF2BE22002077FC /* tags.hpp */, + 83BB83F72DF2BE22002077FC /* wav.hpp */, + ); + path = soundfile_data; + sourceTree = ""; + }; + 83BB83FB2DF2BE22002077FC /* soundfile_write */ = { + isa = PBXGroup; + children = ( + 83BB83F92DF2BE22002077FC /* wav_write.hpp */, + 83BB83FA2DF2BE22002077FC /* wav_write.cpp */, + ); + path = soundfile_write; + sourceTree = ""; + }; 83E5EFB31FFEF7CC00659F0F = { isa = PBXGroup; children = ( @@ -1600,15 +1616,15 @@ 83E5EFBF1FFEF7CC00659F0F /* OpenMPT */ = { isa = PBXGroup; children = ( + 83E5FC281FFEFA0D00659F0F /* common */, + 83E5EFCE1FFEF9D200659F0F /* config.h */, + 83E5EFD21FFEF9E000659F0F /* include */, + 83E5EFCF1FFEF9D200659F0F /* Info.plist */, + 83E5FC9B1FFEFA1A00659F0F /* libopenmpt */, + 83E5FCFD1FFEFA7D00659F0F /* sounddsp */, + 83E5FD0E1FFEFA8400659F0F /* soundlib */, 830995C927787E9A00857684 /* src */, 83E5FE5D1FFEFEA600659F0F /* svn_version */, - 83E5FD0E1FFEFA8400659F0F /* soundlib */, - 83E5FCFD1FFEFA7D00659F0F /* sounddsp */, - 83E5FC9B1FFEFA1A00659F0F /* libopenmpt */, - 83E5FC281FFEFA0D00659F0F /* common */, - 83E5EFD21FFEF9E000659F0F /* include */, - 83E5EFCE1FFEF9D200659F0F /* config.h */, - 83E5EFCF1FFEF9D200659F0F /* Info.plist */, ); path = OpenMPT; sourceTree = ""; @@ -1616,8 +1632,8 @@ 83E5EFD21FFEF9E000659F0F /* include */ = { isa = PBXGroup; children = ( - 83F30ADA286EBC080005EF06 /* vorbis */, 83F30A89286EBBEA0005EF06 /* mpg123 */, + 83F30ADA286EBC080005EF06 /* vorbis */, ); path = include; sourceTree = ""; @@ -1627,46 +1643,40 @@ children = ( 83E5FC3B1FFEFA0D00659F0F /* BuildSettings.h */, 83EC0B672C70B32C00DB51E5 /* BuildSettingsCompiler.h */, - 83E5FC411FFEFA0D00659F0F /* ComponentManager.cpp */, 83E5FC301FFEFA0D00659F0F /* ComponentManager.h */, - 830995B127787BB800857684 /* Dither.h */, + 83E5FC411FFEFA0D00659F0F /* ComponentManager.cpp */, 83E5FC581FFEFA0D00659F0F /* FileReader.h */, 83E5FC3F1FFEFA0D00659F0F /* FileReaderFwd.h */, - 83E5FC341FFEFA0D00659F0F /* Logging.cpp */, + 83BB83AE2DF2BCD4002077FC /* GzipWriter.h */, 83E5FC401FFEFA0D00659F0F /* Logging.h */, + 83E5FC341FFEFA0D00659F0F /* Logging.cpp */, 83E5FC431FFEFA0D00659F0F /* misc_util.h */, 831132D421F955B2001F678F /* mptAssert.h */, 831132CB21F955B0001F678F /* mptBaseMacros.h */, 831132CE21F955B1001F678F /* mptBaseTypes.h */, 831132D121F955B1001F678F /* mptBaseUtils.h */, 83649B942A0340FF00CD0580 /* mptCPU.h */, - 83E5FC521FFEFA0D00659F0F /* mptFileIO.cpp */, 83E5FC471FFEFA0D00659F0F /* mptFileIO.h */, - 83649B932A0340FF00CD0580 /* mptFileTemporary.cpp */, 83649B922A0340FF00CD0580 /* mptFileTemporary.h */, - 83649B962A0340FF00CD0580 /* mptFileType.cpp */, 83649B952A0340FF00CD0580 /* mptFileType.h */, - 83E5FC4D1FFEFA0D00659F0F /* mptPathString.cpp */, + 83649B962A0340FF00CD0580 /* mptFileType.cpp */, 83E5FC591FFEFA0D00659F0F /* mptPathString.h */, - 83E5FC5B1FFEFA0D00659F0F /* mptRandom.cpp */, + 83E5FC4D1FFEFA0D00659F0F /* mptPathString.cpp */, 83E5FC551FFEFA0D00659F0F /* mptRandom.h */, - 83E5FC501FFEFA0D00659F0F /* mptString.cpp */, + 83E5FC5B1FFEFA0D00659F0F /* mptRandom.cpp */, 83E5FC531FFEFA0D00659F0F /* mptString.h */, - 831132CC21F955B1001F678F /* mptStringBuffer.cpp */, 831132D021F955B1001F678F /* mptStringBuffer.h */, - 83E5FC481FFEFA0D00659F0F /* mptStringFormat.cpp */, + 831132CC21F955B1001F678F /* mptStringBuffer.cpp */, 83E5FC331FFEFA0D00659F0F /* mptStringFormat.h */, - 83E5FC2E1FFEFA0D00659F0F /* mptStringParse.cpp */, - 83E5FC541FFEFA0D00659F0F /* mptStringParse.h */, - 83E5FC4F1FFEFA0D00659F0F /* mptTime.cpp */, 83E5FC3E1FFEFA0D00659F0F /* mptTime.h */, - 83E5FC361FFEFA0D00659F0F /* Profiler.cpp */, + 83E5FC4F1FFEFA0D00659F0F /* mptTime.cpp */, 83E5FC5A1FFEFA0D00659F0F /* Profiler.h */, - 83E5FC461FFEFA0D00659F0F /* serialization_utils.cpp */, + 83E5FC361FFEFA0D00659F0F /* Profiler.cpp */, 83E5FC4A1FFEFA0D00659F0F /* serialization_utils.h */, + 83E5FC461FFEFA0D00659F0F /* serialization_utils.cpp */, 83E5FC2F1FFEFA0D00659F0F /* stdafx.h */, - 83E5FC381FFEFA0D00659F0F /* version.cpp */, 83E5FC2D1FFEFA0D00659F0F /* version.h */, + 83E5FC381FFEFA0D00659F0F /* version.cpp */, 83E5FC601FFEFA0D00659F0F /* versionNumber.h */, ); path = common; @@ -1675,24 +1685,23 @@ 83E5FC9B1FFEFA1A00659F0F /* libopenmpt */ = { isa = PBXGroup; children = ( + 83E5FC9F1FFEFA1A00659F0F /* libopenmpt.h */, + 83E5FC9E1FFEFA1A00659F0F /* libopenmpt.hpp */, 83E5FCA41FFEFA1A00659F0F /* libopenmpt_c.cpp */, 83E5FCA91FFEFA1A00659F0F /* libopenmpt_config.h */, 83E5FCC81FFEFA1A00659F0F /* libopenmpt_cxx.cpp */, - 83E5FCAD1FFEFA1A00659F0F /* libopenmpt_ext_impl.cpp */, - 83E5FCA61FFEFA1A00659F0F /* libopenmpt_ext_impl.hpp */, 83E5FCAE1FFEFA1A00659F0F /* libopenmpt_ext.h */, 83E5FCC01FFEFA1A00659F0F /* libopenmpt_ext.hpp */, - 83E5FCBF1FFEFA1A00659F0F /* libopenmpt_impl.cpp */, + 83E5FCA61FFEFA1A00659F0F /* libopenmpt_ext_impl.hpp */, + 83E5FCAD1FFEFA1A00659F0F /* libopenmpt_ext_impl.cpp */, 83E5FC9C1FFEFA1A00659F0F /* libopenmpt_impl.hpp */, + 83E5FCBF1FFEFA1A00659F0F /* libopenmpt_impl.cpp */, 83E5FCA11FFEFA1A00659F0F /* libopenmpt_internal.h */, - 83E5FCA01FFEFA1A00659F0F /* libopenmpt_plugin_settings.hpp */, 83E5FCCA1FFEFA1A00659F0F /* libopenmpt_stream_callbacks_buffer.h */, 83E5FC9D1FFEFA1A00659F0F /* libopenmpt_stream_callbacks_fd.h */, - 83649B9C2A03414400CD0580 /* libopenmpt_stream_callbacks_file_posix_lfs64.h */, 83E5FCAC1FFEFA1A00659F0F /* libopenmpt_stream_callbacks_file.h */, + 83649B9C2A03414400CD0580 /* libopenmpt_stream_callbacks_file_posix_lfs64.h */, 83E5FCC51FFEFA1A00659F0F /* libopenmpt_version.h */, - 83E5FC9F1FFEFA1A00659F0F /* libopenmpt.h */, - 83E5FC9E1FFEFA1A00659F0F /* libopenmpt.hpp */, ); path = libopenmpt; sourceTree = ""; @@ -1700,14 +1709,14 @@ 83E5FCFD1FFEFA7D00659F0F /* sounddsp */ = { isa = PBXGroup; children = ( + 83E5FD051FFEFA7D00659F0F /* AGC.h */, 83E5FCFE1FFEFA7D00659F0F /* AGC.cpp */, - 83E5FCFF1FFEFA7D00659F0F /* Reverb.h */, - 83E5FD001FFEFA7D00659F0F /* EQ.cpp */, + 83E5FD041FFEFA7D00659F0F /* DSP.h */, 83E5FD011FFEFA7D00659F0F /* DSP.cpp */, 83E5FD021FFEFA7D00659F0F /* EQ.h */, + 83E5FD001FFEFA7D00659F0F /* EQ.cpp */, + 83E5FCFF1FFEFA7D00659F0F /* Reverb.h */, 83E5FD031FFEFA7D00659F0F /* Reverb.cpp */, - 83E5FD041FFEFA7D00659F0F /* DSP.h */, - 83E5FD051FFEFA7D00659F0F /* AGC.h */, ); path = sounddsp; sourceTree = ""; @@ -1715,8 +1724,8 @@ 83E5FD0E1FFEFA8400659F0F /* soundlib */ = { isa = PBXGroup; children = ( - 83E5FD111FFEFA8400659F0F /* AudioCriticalSection.cpp */, 83E5FD801FFEFA8400659F0F /* AudioCriticalSection.h */, + 83E5FD111FFEFA8400659F0F /* AudioCriticalSection.cpp */, 83E5FD9E1FFEFA8400659F0F /* AudioReadTarget.h */, 831132E321F9565E001F678F /* BitReader.h */, 83E5FD5B1FFEFA8400659F0F /* Container.h */, @@ -1724,35 +1733,45 @@ 83E5FD881FFEFA8400659F0F /* ContainerPP20.cpp */, 83E5FD581FFEFA8400659F0F /* ContainerUMX.cpp */, 83E5FD5D1FFEFA8400659F0F /* ContainerXPK.cpp */, - 83E5FD7A1FFEFA8400659F0F /* Dlsbank.cpp */, 83E5FD911FFEFA8400659F0F /* Dlsbank.h */, + 83E5FD7A1FFEFA8400659F0F /* Dlsbank.cpp */, 83E5FDAE1FFEFA8400659F0F /* Fastmix.cpp */, 83E5FD7E1FFEFA8400659F0F /* FloatMixer.h */, 83E5FDB61FFEFA8400659F0F /* InstrumentExtensions.cpp */, + 83BB83B02DF2BD29002077FC /* InstrumentSynth.h */, + 83BB83B12DF2BD29002077FC /* InstrumentSynth.cpp */, 83E5FD931FFEFA8400659F0F /* IntMixer.h */, - 83E5FDB11FFEFA8400659F0F /* ITCompression.cpp */, 83E5FD221FFEFA8400659F0F /* ITCompression.h */, - 83E5FD101FFEFA8400659F0F /* ITTools.cpp */, + 83E5FDB11FFEFA8400659F0F /* ITCompression.cpp */, 83E5FD301FFEFA8400659F0F /* ITTools.h */, + 83E5FD101FFEFA8400659F0F /* ITTools.cpp */, 83649BA02A03416B00CD0580 /* Load_667.cpp */, 83E5FD631FFEFA8400659F0F /* Load_669.cpp */, 83E5FD621FFEFA8400659F0F /* Load_amf.cpp */, 83E5FD561FFEFA8400659F0F /* Load_ams.cpp */, 831132E621F9565F001F678F /* Load_c67.cpp */, + 83BB83B22DF2BD29002077FC /* Load_cba.cpp */, 83E5FD151FFEFA8400659F0F /* Load_dbm.cpp */, 83E5FD961FFEFA8400659F0F /* Load_digi.cpp */, 83E5FD291FFEFA8400659F0F /* Load_dmf.cpp */, 83E5FD8C1FFEFA8400659F0F /* Load_dsm.cpp */, 830995B827787BEE00857684 /* Load_dsym.cpp */, 83E5FDB21FFEFA8400659F0F /* Load_dtm.cpp */, + 83BB83B32DF2BD29002077FC /* Load_etx.cpp */, 83E5FD261FFEFA8400659F0F /* Load_far.cpp */, + 83BB83B42DF2BD29002077FC /* Load_fc.cpp */, 830995B527787BEE00857684 /* Load_fmt.cpp */, + 83BB83B52DF2BD29002077FC /* Load_ftm.cpp */, 83E5FD171FFEFA8400659F0F /* Load_gdm.cpp */, + 83BB83B62DF2BD29002077FC /* Load_gmc.cpp */, 83649B9E2A03416B00CD0580 /* Load_gt2.cpp */, + 83BB83B72DF2BD29002077FC /* Load_ice.cpp */, 83E5FD8A1FFEFA8400659F0F /* Load_imf.cpp */, + 83BB83B82DF2BD29002077FC /* Load_ims.cpp */, 83E5FDA41FFEFA8400659F0F /* Load_it.cpp */, 83E5FD7F1FFEFA8400659F0F /* Load_itp.cpp */, 83E5FD701FFEFA8400659F0F /* load_j2b.cpp */, + 83BB83B92DF2BD29002077FC /* Load_kris.cpp */, 83E5FD9F1FFEFA8400659F0F /* Load_mdl.cpp */, 83E5FD281FFEFA8400659F0F /* Load_med.cpp */, 83E5FD1C1FFEFA8400659F0F /* Load_mid.cpp */, @@ -1764,66 +1783,80 @@ 83E5FDA81FFEFA8400659F0F /* Load_okt.cpp */, 83E5FD691FFEFA8400659F0F /* Load_plm.cpp */, 83E5FD231FFEFA8400659F0F /* Load_psm.cpp */, + 83BB83BA2DF2BD29002077FC /* Load_pt36.cpp */, 83E5FD591FFEFA8400659F0F /* Load_ptm.cpp */, + 83BB83BB2DF2BD29002077FC /* Load_puma.cpp */, + 83BB83BC2DF2BD29002077FC /* Load_rtm.cpp */, 83E5FD971FFEFA8400659F0F /* Load_s3m.cpp */, 83E5FD6E1FFEFA8400659F0F /* Load_sfx.cpp */, + 83BB83BD2DF2BD29002077FC /* Load_stk.cpp */, 83E5FD131FFEFA8400659F0F /* Load_stm.cpp */, 83E5FDA71FFEFA8400659F0F /* Load_stp.cpp */, 830995B627787BEE00857684 /* Load_symmod.cpp */, + 83BB83BE2DF2BD29002077FC /* Load_tcb.cpp */, 83E5FD331FFEFA8400659F0F /* Load_uax.cpp */, 83E5FDA91FFEFA8400659F0F /* Load_ult.cpp */, + 83BB83BF2DF2BD29002077FC /* Load_unic.cpp */, 83E5FDA21FFEFA8400659F0F /* Load_wav.cpp */, 83E5FD921FFEFA8400659F0F /* Load_xm.cpp */, 83649B9F2A03416B00CD0580 /* Load_xmf.cpp */, 83E5FDAF1FFEFA8400659F0F /* Loaders.h */, - 83E5FD2F1FFEFA8400659F0F /* Message.cpp */, 83E5FD671FFEFA8400659F0F /* Message.h */, - 83E5FD941FFEFA8400659F0F /* MIDIEvents.cpp */, + 83E5FD2F1FFEFA8400659F0F /* Message.cpp */, 83E5FD1B1FFEFA8400659F0F /* MIDIEvents.h */, - 83E5FD841FFEFA8400659F0F /* MIDIMacros.cpp */, + 83E5FD941FFEFA8400659F0F /* MIDIEvents.cpp */, + 83BB83C02DF2BD29002077FC /* MIDIMacroParser.h */, + 83BB83C12DF2BD29002077FC /* MIDIMacroParser.cpp */, 83E5FDAA1FFEFA8400659F0F /* MIDIMacros.h */, + 83E5FD841FFEFA8400659F0F /* MIDIMacros.cpp */, 83E5FD7D1FFEFA8400659F0F /* Mixer.h */, 83E5FD121FFEFA8400659F0F /* MixerInterface.h */, - 83E5FD141FFEFA8400659F0F /* MixerLoops.cpp */, 83E5FDA31FFEFA8400659F0F /* MixerLoops.h */, - 83E5FD8F1FFEFA8400659F0F /* MixerSettings.cpp */, + 83E5FD141FFEFA8400659F0F /* MixerLoops.cpp */, 83E5FD9D1FFEFA8400659F0F /* MixerSettings.h */, - 83E5FDAB1FFEFA8400659F0F /* MixFuncTable.cpp */, + 83E5FD8F1FFEFA8400659F0F /* MixerSettings.cpp */, 83E5FD731FFEFA8400659F0F /* MixFuncTable.h */, - 83E5FD1E1FFEFA8400659F0F /* mod_specifications.cpp */, + 83E5FDAB1FFEFA8400659F0F /* MixFuncTable.cpp */, 83E5FD2D1FFEFA8400659F0F /* mod_specifications.h */, - 83E5FD161FFEFA8400659F0F /* ModChannel.cpp */, + 83E5FD1E1FFEFA8400659F0F /* mod_specifications.cpp */, 83E5FD9C1FFEFA8400659F0F /* ModChannel.h */, - 83E5FD2E1FFEFA8400659F0F /* modcommand.cpp */, + 83E5FD161FFEFA8400659F0F /* ModChannel.cpp */, 83E5FD6A1FFEFA8400659F0F /* modcommand.h */, - 83E5FD771FFEFA8400659F0F /* ModInstrument.cpp */, + 83E5FD2E1FFEFA8400659F0F /* modcommand.cpp */, 83E5FD8D1FFEFA8400659F0F /* ModInstrument.h */, - 83E5FD791FFEFA8400659F0F /* ModSample.cpp */, + 83E5FD771FFEFA8400659F0F /* ModInstrument.cpp */, 83E5FD1A1FFEFA8400659F0F /* ModSample.h */, + 83E5FD791FFEFA8400659F0F /* ModSample.cpp */, 83E5FD1D1FFEFA8400659F0F /* ModSampleCopy.h */, - 83E5FD711FFEFA8400659F0F /* ModSequence.cpp */, 83E5FD5C1FFEFA8400659F0F /* ModSequence.h */, - 83E5FD651FFEFA8400659F0F /* modsmp_ctrl.cpp */, + 83E5FD711FFEFA8400659F0F /* ModSequence.cpp */, 83E5FD761FFEFA8400659F0F /* modsmp_ctrl.h */, - 83E5FDB31FFEFA8400659F0F /* MPEGFrame.cpp */, + 83E5FD651FFEFA8400659F0F /* modsmp_ctrl.cpp */, + 83BB83C22DF2BD29002077FC /* MODTools.h */, + 83BB83C32DF2BD29002077FC /* MODTools.cpp */, 83E5FD2A1FFEFA8400659F0F /* MPEGFrame.h */, - 83E5FD681FFEFA8400659F0F /* OggStream.cpp */, + 83E5FDB31FFEFA8400659F0F /* MPEGFrame.cpp */, 83E5FDAD1FFEFA8400659F0F /* OggStream.h */, + 83E5FD681FFEFA8400659F0F /* OggStream.cpp */, 831132E421F9565F001F678F /* opal.h */, - 831132E721F9565F001F678F /* OPL.cpp */, 831132E521F9565F001F678F /* OPL.h */, - 83E5FD951FFEFA8400659F0F /* pattern.cpp */, + 831132E721F9565F001F678F /* OPL.cpp */, 83E5FD741FFEFA8400659F0F /* pattern.h */, - 83E5FD271FFEFA8400659F0F /* patternContainer.cpp */, + 83E5FD951FFEFA8400659F0F /* pattern.cpp */, 83E5FD1F1FFEFA8400659F0F /* patternContainer.h */, - 83E5FD2B1FFEFA8400659F0F /* Paula.cpp */, + 83E5FD271FFEFA8400659F0F /* patternContainer.cpp */, 83E5FD871FFEFA8400659F0F /* Paula.h */, + 83E5FD2B1FFEFA8400659F0F /* Paula.cpp */, + 83BB83C42DF2BD29002077FC /* PlaybackTest.h */, + 83BB83C52DF2BD29002077FC /* PlaybackTest.cpp */, + 83BB83C62DF2BD29002077FC /* PlayState.h */, + 83BB83C72DF2BD29002077FC /* PlayState.cpp */, 83E5FD341FFEFA8400659F0F /* plugins */, 83E5FD9B1FFEFA8400659F0F /* Resampler.h */, - 83E5FD891FFEFA8400659F0F /* RowVisitor.cpp */, 83E5FD321FFEFA8400659F0F /* RowVisitor.h */, - 83E5FD901FFEFA8400659F0F /* S3MTools.cpp */, + 83E5FD891FFEFA8400659F0F /* RowVisitor.cpp */, 83E5FD251FFEFA8400659F0F /* S3MTools.h */, + 83E5FD901FFEFA8400659F0F /* S3MTools.cpp */, 830995B327787BEE00857684 /* SampleCopy.h */, 83AA7D302519B694004C5298 /* SampleFormatBRR.cpp */, 83E5FD751FFEFA8400659F0F /* SampleFormatFLAC.cpp */, @@ -1833,37 +1866,37 @@ 83E5FDA11FFEFA8400659F0F /* SampleFormats.cpp */, 83AA7D2F2519B694004C5298 /* SampleFormatSFZ.cpp */, 83E5FD8B1FFEFA8400659F0F /* SampleFormatVorbis.cpp */, - 83E5FD991FFEFA8400659F0F /* SampleIO.cpp */, 83E5FD5A1FFEFA8400659F0F /* SampleIO.h */, + 83E5FD991FFEFA8400659F0F /* SampleIO.cpp */, 830995B427787BEE00857684 /* SampleNormalize.h */, 83E5FD721FFEFA8400659F0F /* Snd_defs.h */, 83E5FD201FFEFA8400659F0F /* Snd_flt.cpp */, 83E5FD191FFEFA8400659F0F /* Snd_fx.cpp */, - 83E5FD601FFEFA8400659F0F /* Sndfile.cpp */, 83E5FD861FFEFA8400659F0F /* Sndfile.h */, + 83E5FD601FFEFA8400659F0F /* Sndfile.cpp */, 83E5FD6F1FFEFA8400659F0F /* Sndmix.cpp */, - 83E5FD311FFEFA8400659F0F /* SoundFilePlayConfig.cpp */, 83E5FD181FFEFA8400659F0F /* SoundFilePlayConfig.h */, - 83E5FD6B1FFEFA8400659F0F /* Tables.cpp */, + 83E5FD311FFEFA8400659F0F /* SoundFilePlayConfig.cpp */, 83E5FD811FFEFA8400659F0F /* Tables.h */, - 83E5FDB01FFEFA8400659F0F /* Tagging.cpp */, + 83E5FD6B1FFEFA8400659F0F /* Tables.cpp */, 83E5FD7B1FFEFA8400659F0F /* Tagging.h */, - 83AA7D2E2519B694004C5298 /* TinyFFT.cpp */, + 83E5FDB01FFEFA8400659F0F /* Tagging.cpp */, 83AA7D312519B694004C5298 /* TinyFFT.h */, - 83E5FD5F1FFEFA8400659F0F /* tuning.cpp */, + 83AA7D2E2519B694004C5298 /* TinyFFT.cpp */, 83E5FDA51FFEFA8400659F0F /* tuning.h */, + 83E5FD5F1FFEFA8400659F0F /* tuning.cpp */, 83E5FD831FFEFA8400659F0F /* tuningbase.h */, - 83E5FD981FFEFA8400659F0F /* tuningCollection.cpp */, 83E5FD7C1FFEFA8400659F0F /* tuningcollection.h */, - 83E5FDA61FFEFA8400659F0F /* UMXTools.cpp */, + 83E5FD981FFEFA8400659F0F /* tuningCollection.cpp */, 83E5FD641FFEFA8400659F0F /* UMXTools.h */, + 83E5FDA61FFEFA8400659F0F /* UMXTools.cpp */, 83E5FD821FFEFA8400659F0F /* UpgradeModule.cpp */, - 83E5FD0F1FFEFA8400659F0F /* WAVTools.cpp */, 83E5FD2C1FFEFA8400659F0F /* WAVTools.h */, - 83E5FDA01FFEFA8400659F0F /* WindowedFIR.cpp */, + 83E5FD0F1FFEFA8400659F0F /* WAVTools.cpp */, 83E5FD851FFEFA8400659F0F /* WindowedFIR.h */, - 83E5FDB41FFEFA8400659F0F /* XMTools.cpp */, + 83E5FDA01FFEFA8400659F0F /* WindowedFIR.cpp */, 83E5FD6C1FFEFA8400659F0F /* XMTools.h */, + 83E5FDB41FFEFA8400659F0F /* XMTools.cpp */, ); path = soundlib; sourceTree = ""; @@ -1871,20 +1904,19 @@ 83E5FD341FFEFA8400659F0F /* plugins */ = { isa = PBXGroup; children = ( - 83E5FD3F1FFEFA8400659F0F /* DigiBoosterEcho.cpp */, 83E5FD391FFEFA8400659F0F /* DigiBoosterEcho.h */, + 83E5FD3F1FFEFA8400659F0F /* DigiBoosterEcho.cpp */, 83E5FD401FFEFA8400659F0F /* dmo */, - 83E5FD3B1FFEFA8400659F0F /* LFOPlugin.cpp */, 83E5FD371FFEFA8400659F0F /* LFOPlugin.h */, - 83E5FD3C1FFEFA8400659F0F /* OpCodes.h */, - 83E5FD3E1FFEFA8400659F0F /* PluginManager.cpp */, + 83E5FD3B1FFEFA8400659F0F /* LFOPlugin.cpp */, 83E5FD551FFEFA8400659F0F /* PluginManager.h */, + 83E5FD3E1FFEFA8400659F0F /* PluginManager.cpp */, 83E5FD351FFEFA8400659F0F /* PluginMixBuffer.h */, 83E5FD361FFEFA8400659F0F /* PluginStructs.h */, - 83E5FD3A1FFEFA8400659F0F /* PlugInterface.cpp */, 83E5FD381FFEFA8400659F0F /* PlugInterface.h */, - 830995C027787C1000857684 /* SymMODEcho.cpp */, + 83E5FD3A1FFEFA8400659F0F /* PlugInterface.cpp */, 830995BF27787C1000857684 /* SymMODEcho.h */, + 830995C027787C1000857684 /* SymMODEcho.cpp */, ); path = plugins; sourceTree = ""; @@ -1892,28 +1924,28 @@ 83E5FD401FFEFA8400659F0F /* dmo */ = { isa = PBXGroup; children = ( - 83E5FD501FFEFA8400659F0F /* Chorus.cpp */, 83E5FD511FFEFA8400659F0F /* Chorus.h */, - 83E5FD491FFEFA8400659F0F /* Compressor.cpp */, + 83E5FD501FFEFA8400659F0F /* Chorus.cpp */, 83E5FD521FFEFA8400659F0F /* Compressor.h */, - 83E5FD431FFEFA8400659F0F /* Distortion.cpp */, + 83E5FD491FFEFA8400659F0F /* Compressor.cpp */, 83E5FD531FFEFA8400659F0F /* Distortion.h */, - 83E5FD411FFEFA8400659F0F /* DMOPlugin.cpp */, + 83E5FD431FFEFA8400659F0F /* Distortion.cpp */, 83E5FD4E1FFEFA8400659F0F /* DMOPlugin.h */, - 830995C327787C1C00857684 /* DMOUtils.cpp */, + 83E5FD411FFEFA8400659F0F /* DMOPlugin.cpp */, 830995C427787C1C00857684 /* DMOUtils.h */, - 83E5FD4F1FFEFA8400659F0F /* Echo.cpp */, + 830995C327787C1C00857684 /* DMOUtils.cpp */, 83E5FD451FFEFA8400659F0F /* Echo.h */, - 83E5FD421FFEFA8400659F0F /* Flanger.cpp */, + 83E5FD4F1FFEFA8400659F0F /* Echo.cpp */, 83E5FD541FFEFA8400659F0F /* Flanger.h */, - 83E5FD461FFEFA8400659F0F /* Gargle.cpp */, + 83E5FD421FFEFA8400659F0F /* Flanger.cpp */, 83E5FD4D1FFEFA8400659F0F /* Gargle.h */, - 83E5FD481FFEFA8400659F0F /* I3DL2Reverb.cpp */, + 83E5FD461FFEFA8400659F0F /* Gargle.cpp */, 83E5FD471FFEFA8400659F0F /* I3DL2Reverb.h */, - 83E5FD441FFEFA8400659F0F /* ParamEq.cpp */, + 83E5FD481FFEFA8400659F0F /* I3DL2Reverb.cpp */, 83E5FD4B1FFEFA8400659F0F /* ParamEq.h */, - 83E5FD4C1FFEFA8400659F0F /* WavesReverb.cpp */, + 83E5FD441FFEFA8400659F0F /* ParamEq.cpp */, 83E5FD4A1FFEFA8400659F0F /* WavesReverb.h */, + 83E5FD4C1FFEFA8400659F0F /* WavesReverb.cpp */, ); path = dmo; sourceTree = ""; @@ -1921,9 +1953,9 @@ 83E5FE5D1FFEFEA600659F0F /* svn_version */ = { isa = PBXGroup; children = ( + 83E5FE601FFEFEA600659F0F /* svn_version.h */, 83E5FE5E1FFEFEA600659F0F /* svn_version.template.subwcrev.h */, 83E5FE5F1FFEFEA600659F0F /* update_svn_version_vs_premake.cmd */, - 83E5FE601FFEFEA600659F0F /* svn_version.h */, ); name = svn_version; path = build/svn_version; @@ -1944,46 +1976,8 @@ 83F30A89286EBBEA0005EF06 /* mpg123 */ = { isa = PBXGroup; children = ( - 83F30A8A286EBBEA0005EF06 /* l12tabs.h */, - 83F30A8B286EBBEA0005EF06 /* synth_8bit.h */, - 83F30A8C286EBBEA0005EF06 /* index.h */, - 83F30A8D286EBBEA0005EF06 /* synth_ntom.h */, - 83F30A8E286EBBEA0005EF06 /* reader.h */, - 83F30A8F286EBBEA0005EF06 /* debug.h */, - 83F30A90286EBBEA0005EF06 /* mpg123.h */, - 83F30A91286EBBEA0005EF06 /* dither.h */, - 83F30A92286EBBEA0005EF06 /* optimize.h */, - 83F30A93286EBBEA0005EF06 /* getbits.h */, - 83F30A94286EBBEA0005EF06 /* synth.h */, - 83F30A95286EBBEA0005EF06 /* synths.h */, - 83F30A96286EBBEA0005EF06 /* mangle.h */, - 83F30A97286EBBEA0005EF06 /* init_layer12.h */, - 83F30A98286EBBEA0005EF06 /* frame.h */, - 83F30A99286EBBEA0005EF06 /* parse.h */, - 83F30A9A286EBBEA0005EF06 /* synth_sse3d.h */, - 83F30A9B286EBBEA0005EF06 /* l3bandgain.h */, - 83F30A9C286EBBEA0005EF06 /* mpg123lib_intern.h */, - 83F30A9D286EBBEA0005EF06 /* init_layer3.h */, - 83F30A9E286EBBEA0005EF06 /* getcpuflags.h */, - 83F30A9F286EBBEA0005EF06 /* icy.h */, 83F30AA0286EBBEA0005EF06 /* fmt123.h */, - 83F30AA1286EBBEA0005EF06 /* newhuffman.h */, - 83F30AA2286EBBEA0005EF06 /* synth_mono.h */, - 83F30AA3286EBBEA0005EF06 /* gapless.h */, - 83F30AA4286EBBEA0005EF06 /* costabs.h */, - 83F30AA5286EBBEA0005EF06 /* l2tables.h */, - 83F30AA6286EBBEA0005EF06 /* swap_bytes_impl.h */, - 83F30AA7286EBBEA0005EF06 /* icy2utf8.h */, - 83F30AA8286EBBEA0005EF06 /* l3tabs.h */, - 83F30AA9286EBBEA0005EF06 /* dither_impl.h */, - 83F30AAA286EBBEA0005EF06 /* mpeghead.h */, - 83F30AAB286EBBEA0005EF06 /* huffman.h */, - 83F30AAC286EBBEA0005EF06 /* sample.h */, - 83F30AAD286EBBEA0005EF06 /* init_costabs.h */, - 83F30AAE286EBBEA0005EF06 /* decode.h */, - 83F30AAF286EBBEA0005EF06 /* abi_align.h */, - 83F30AB0286EBBEA0005EF06 /* true.h */, - 83F30AB1286EBBEA0005EF06 /* id3.h */, + 83F30A90286EBBEA0005EF06 /* mpg123.h */, ); name = mpg123; path = ../../../../ThirdParty/mpg123/include/mpg123; @@ -2010,12 +2004,9 @@ 83E5FCCE1FFEFA1A00659F0F /* libopenmpt.h in Headers */, 83E5FCCD1FFEFA1A00659F0F /* libopenmpt.hpp in Headers */, 83E5FCD81FFEFA1A00659F0F /* libopenmpt_config.h in Headers */, - 83F30AC0286EBBEA0005EF06 /* frame.h in Headers */, 83E5FCF01FFEFA1A00659F0F /* libopenmpt_version.h in Headers */, 83E5FDC51FFEFA8500659F0F /* ModSampleCopy.h in Headers */, - 83F30AD7286EBBEA0005EF06 /* abi_align.h in Headers */, 8309969827787E9A00857684 /* macros.hpp in Headers */, - 83F30ACE286EBBEA0005EF06 /* swap_bytes_impl.h in Headers */, 83E5FE611FFEFEA600659F0F /* svn_version.template.subwcrev.h in Headers */, 83E5FE2C1FFEFA8500659F0F /* Sndfile.h in Headers */, 8309970527787E9A00857684 /* version.hpp in Headers */, @@ -2023,14 +2014,13 @@ 83649BE12A0342AB00CD0580 /* os_path_long.hpp in Headers */, 83E5FE531FFEFA8500659F0F /* OggStream.h in Headers */, 8309972127787E9A00857684 /* SampleClip.hpp in Headers */, - 83E5FC8D1FFEFA0D00659F0F /* mptStringParse.h in Headers */, 8309969F27787E9A00857684 /* filedata_base_unseekable.hpp in Headers */, 8309969527787E9A00857684 /* tests_endian_floatingpoint.hpp in Headers */, 83E5FDD51FFEFA8500659F0F /* mod_specifications.h in Headers */, 8309970927787E9A00857684 /* namespace.hpp in Headers */, - 83F30AB4286EBBEA0005EF06 /* index.h in Headers */, 83E5FE1C1FFEFA8500659F0F /* modsmp_ctrl.h in Headers */, 830996AD27787E9A00857684 /* out_of_memory.hpp in Headers */, + 83BB83AF2DF2BCD4002077FC /* GzipWriter.h in Headers */, 830996C327787E9A00857684 /* simple_floatingpoint.hpp in Headers */, 830996CA27787E9A00857684 /* default_floatingpoint.hpp in Headers */, 83E5FD071FFEFA7D00659F0F /* Reverb.h in Headers */, @@ -2046,13 +2036,10 @@ 8309971627787E9A00857684 /* Dither.hpp in Headers */, 83E5FC6C1FFEFA0D00659F0F /* mptStringFormat.h in Headers */, 83E5FDBA1FFEFA8500659F0F /* MixerInterface.h in Headers */, - 83F30ABE286EBBEA0005EF06 /* mangle.h in Headers */, 830996E127787E9A00857684 /* engine_lcg.hpp in Headers */, 83E5FDD81FFEFA8500659F0F /* ITTools.h in Headers */, 83E5FE241FFEFA8500659F0F /* FloatMixer.h in Headers */, - 830996E827787E9A00857684 /* exception_text.hpp in Headers */, 830996E527787E9A00857684 /* mfc.hpp in Headers */, - 83F30AC3286EBBEA0005EF06 /* l3bandgain.h in Headers */, 830996B427787E9A00857684 /* crc.hpp in Headers */, 83E5FC681FFEFA0D00659F0F /* stdafx.h in Headers */, 830996F427787E9A00857684 /* bit.hpp in Headers */, @@ -2063,7 +2050,6 @@ 8309969627787E9A00857684 /* class.hpp in Headers */, 830996C427787E9A00857684 /* default_string.hpp in Headers */, 83E5FDDF1FFEFA8500659F0F /* PlugInterface.h in Headers */, - 83F30AD5286EBBEA0005EF06 /* init_costabs.h in Headers */, 83E5FDF11FFEFA8500659F0F /* ParamEq.h in Headers */, 83E5FC661FFEFA0D00659F0F /* version.h in Headers */, 830996E627787E9A00857684 /* windows.hpp in Headers */, @@ -2071,18 +2057,15 @@ 83649BCB2A03424E00CD0580 /* inputfile.hpp in Headers */, 830996FD27787E9A00857684 /* tests_base_bit.hpp in Headers */, 83E5FE1A1FFEFA8500659F0F /* pattern.h in Headers */, - 83F30ABF286EBBEA0005EF06 /* init_layer12.h in Headers */, 83649BC92A03424E00CD0580 /* unique_basename.hpp in Headers */, 830996E927787E9A00857684 /* wrapping_divide.hpp in Headers */, 831132E021F955B2001F678F /* mptAssert.h in Headers */, 83E5FE211FFEFA8500659F0F /* Tagging.h in Headers */, - 83F30AD1286EBBEA0005EF06 /* dither_impl.h in Headers */, 83E5FE101FFEFA8500659F0F /* modcommand.h in Headers */, 83E5FE331FFEFA8500659F0F /* ModInstrument.h in Headers */, 83E5FC781FFEFA0D00659F0F /* FileReaderFwd.h in Headers */, 83649BCD2A03424E00CD0580 /* fstream.hpp in Headers */, 83E5FDF41FFEFA8500659F0F /* DMOPlugin.h in Headers */, - 83F30ABB286EBBEA0005EF06 /* getbits.h in Headers */, 830996EA27787E9A00857684 /* integer.hpp in Headers */, 83E5FE2B1FFEFA8500659F0F /* WindowedFIR.h in Headers */, 830996D127787E9A00857684 /* base64url.hpp in Headers */, @@ -2095,24 +2078,21 @@ 830996D027787E9A00857684 /* tests_binary.hpp in Headers */, 831132EA21F9565F001F678F /* OPL.h in Headers */, 83E5FC8E1FFEFA0D00659F0F /* mptRandom.h in Headers */, - 83F30AD9286EBBEA0005EF06 /* id3.h in Headers */, 830995BA27787BEE00857684 /* SampleNormalize.h in Headers */, 83E5FE491FFEFA8500659F0F /* MixerLoops.h in Headers */, 830996CC27787E9A00857684 /* simple_spec.hpp in Headers */, - 83F30AD6286EBBEA0005EF06 /* decode.h in Headers */, 830996B527787E9A00857684 /* io_span.hpp in Headers */, 830996DD27787E9A00857684 /* seed.hpp in Headers */, 8309971B27787E9A00857684 /* CopyMix.hpp in Headers */, - 83F30AB6286EBBEA0005EF06 /* reader.h in Headers */, 830996EE27787E9A00857684 /* span.hpp in Headers */, 830996F627787E9A00857684 /* check_platform.hpp in Headers */, 83649BE02A0342AB00CD0580 /* basic_path.hpp in Headers */, 8309972027787E9A00857684 /* SampleClipFixedPoint.hpp in Headers */, 830996DC27787E9A00857684 /* tests_random.hpp in Headers */, 830996C627787E9A00857684 /* tests_format_simple.hpp in Headers */, + 83BB83EE2DF2BD9B002077FC /* main.hpp in Headers */, 830996AA27787E9A00857684 /* filedata_stdstream.hpp in Headers */, 8309971E27787E9A00857684 /* DitherModPlug.hpp in Headers */, - 83F30ACC286EBBEA0005EF06 /* costabs.h in Headers */, 830996D627787E9A00857684 /* tests_string_buffer.hpp in Headers */, 830996FF27787E9A00857684 /* tests_base_saturate_round.hpp in Headers */, 83E5FE411FFEFA8500659F0F /* Resampler.h in Headers */, @@ -2122,13 +2102,11 @@ 830996D827787E9A00857684 /* types.hpp in Headers */, 830996A027787E9A00857684 /* filecursor_stdstream.hpp in Headers */, 83E5FCCC1FFEFA1A00659F0F /* libopenmpt_stream_callbacks_fd.h in Headers */, - 83F30AD2286EBBEA0005EF06 /* mpeghead.h in Headers */, 83E5FDD41FFEFA8500659F0F /* WAVTools.h in Headers */, 830996DF27787E9A00857684 /* device.hpp in Headers */, 83649BC82A03424E00CD0580 /* unique_tempfilename.hpp in Headers */, 83649B972A0340FF00CD0580 /* mptFileTemporary.h in Headers */, 830996CD27787E9A00857684 /* message.hpp in Headers */, - 83F30AC1286EBBEA0005EF06 /* parse.h in Headers */, 830996B027787E9A00857684 /* tests_parse.hpp in Headers */, 8309969427787E9A00857684 /* tests_endian_integer.hpp in Headers */, 830996EF27787E9A00857684 /* macros.hpp in Headers */, @@ -2143,15 +2121,20 @@ 83E5FE221FFEFA8500659F0F /* tuningcollection.h in Headers */, 8309971027787E9A00857684 /* semantic_version.hpp in Headers */, 830996A827787E9A00857684 /* filedata_memory.hpp in Headers */, + 83BB83E92DF2BD5E002077FC /* libcxx.hpp in Headers */, 8309971C27787E9A00857684 /* DitherNone.hpp in Headers */, 8309971827787E9A00857684 /* SampleConvertFixedPoint.hpp in Headers */, - 83F30AC5286EBBEA0005EF06 /* init_layer3.h in Headers */, 83E5FE121FFEFA8500659F0F /* XMTools.h in Headers */, 83E5FCCB1FFEFA1A00659F0F /* libopenmpt_impl.hpp in Headers */, 830996A627787E9A00857684 /* callbackstream.hpp in Headers */, 831132E821F9565F001F678F /* BitReader.h in Headers */, 83E5FC741FFEFA0D00659F0F /* BuildSettings.h in Headers */, 83649B9D2A03414400CD0580 /* libopenmpt_stream_callbacks_file_posix_lfs64.h in Headers */, + 83BB83DB2DF2BD29002077FC /* PlayState.h in Headers */, + 83BB83DC2DF2BD29002077FC /* PlaybackTest.h in Headers */, + 83BB83DD2DF2BD29002077FC /* MIDIMacroParser.h in Headers */, + 83BB83DE2DF2BD29002077FC /* MODTools.h in Headers */, + 83BB83DF2DF2BD29002077FC /* InstrumentSynth.h in Headers */, 8309970B27787E9A00857684 /* memory.hpp in Headers */, 83E5FDC01FFEFA8500659F0F /* SoundFilePlayConfig.h in Headers */, 83649BAC2A03419D00CD0580 /* detect_arch.hpp in Headers */, @@ -2159,7 +2142,7 @@ 83649BBB2A03420100CD0580 /* exception.hpp in Headers */, 830996C027787E9A00857684 /* join.hpp in Headers */, 8309969227787E9A00857684 /* int24.hpp in Headers */, - 83E5FCCF1FFEFA1A00659F0F /* libopenmpt_plugin_settings.hpp in Headers */, + 83BB83F02DF2BDC1002077FC /* any_engine.hpp in Headers */, 830996C127787E9A00857684 /* default_formatter.hpp in Headers */, 83649BAA2A03418600CD0580 /* x86_amd64.hpp in Headers */, 8309968F27787E9A00857684 /* ltdl.hpp in Headers */, @@ -2176,35 +2159,29 @@ 8309972427787E9A00857684 /* FlagSet.hpp in Headers */, 830996F727787E9A00857684 /* saturate_cast.hpp in Headers */, 830996D227787E9A00857684 /* hex.hpp in Headers */, - 83F30AB2286EBBEA0005EF06 /* l12tabs.h in Headers */, 83649BA92A03418600CD0580 /* arch.hpp in Headers */, 8309972627787E9A00857684 /* Types.hpp in Headers */, - 83F30ACB286EBBEA0005EF06 /* gapless.h in Headers */, 83E5FDFA1FFEFA8500659F0F /* Flanger.h in Headers */, 8309969927787E9A00857684 /* tests_string_transcode.hpp in Headers */, 830996AE27787E9A00857684 /* test_macros.hpp in Headers */, 83E5FC921FFEFA0D00659F0F /* mptPathString.h in Headers */, 830996DE27787E9A00857684 /* random.hpp in Headers */, - 83F30AC6286EBBEA0005EF06 /* getcpuflags.h in Headers */, + 83BB83E42DF2BD49002077FC /* type_traits.hpp in Headers */, + 83BB83E52DF2BD49002077FC /* size.hpp in Headers */, + 83BB83E62DF2BD49002077FC /* float.hpp in Headers */, + 83BB83E72DF2BD49002077FC /* debugging.hpp in Headers */, 83E5FC931FFEFA0D00659F0F /* Profiler.h in Headers */, - 83E5FDE31FFEFA8500659F0F /* OpCodes.h in Headers */, 83E5FE2D1FFEFA8500659F0F /* Paula.h in Headers */, - 83F30AD3286EBBEA0005EF06 /* huffman.h in Headers */, 830996B227787E9A00857684 /* split.hpp in Headers */, - 83F30AB3286EBBEA0005EF06 /* synth_8bit.h in Headers */, 8309969E27787E9A00857684 /* filecursor_memory.hpp in Headers */, 8309968E27787E9A00857684 /* nlohmann_json.hpp in Headers */, 8309969A27787E9A00857684 /* transcode.hpp in Headers */, 830996C527787E9A00857684 /* default_integer.hpp in Headers */, 83E5FDDD1FFEFA8500659F0F /* PluginStructs.h in Headers */, - 83F30ABA286EBBEA0005EF06 /* optimize.h in Headers */, 83E5FDDE1FFEFA8500659F0F /* LFOPlugin.h in Headers */, - 83F30ABD286EBBEA0005EF06 /* synths.h in Headers */, 83E5FDC71FFEFA8500659F0F /* patternContainer.h in Headers */, - 83F30AD0286EBBEA0005EF06 /* l3tabs.h in Headers */, 831132DC21F955B2001F678F /* mptStringBuffer.h in Headers */, 830996F027787E9A00857684 /* secure.hpp in Headers */, - 83F30AB9286EBBEA0005EF06 /* dither.h in Headers */, 8309969327787E9A00857684 /* floatingpoint.hpp in Headers */, 83649BD12A03427500CD0580 /* inputfile_filecursor.hpp in Headers */, 830996A427787E9A00857684 /* filedata.hpp in Headers */, @@ -2213,13 +2190,12 @@ 83649BDE2A0342AB00CD0580 /* native_path.hpp in Headers */, 83E5FC791FFEFA0D00659F0F /* Logging.h in Headers */, 83649BBA2A03420100CD0580 /* runtime_error.hpp in Headers */, - 83F30ACD286EBBEA0005EF06 /* l2tables.h in Headers */, 83F30ADD286EBC080005EF06 /* vorbisfile.h in Headers */, - 83F30AC9286EBBEA0005EF06 /* newhuffman.h in Headers */, - 83F30AD8286EBBEA0005EF06 /* true.h in Headers */, + 83BB83F52DF2BE03002077FC /* magic.hpp in Headers */, 830995C627787C1C00857684 /* DMOUtils.h in Headers */, 83E5FDE01FFEFA8500659F0F /* DigiBoosterEcho.h in Headers */, 83E5FD0A1FFEFA7D00659F0F /* EQ.h in Headers */, + 83BB83F22DF2BDDE002077FC /* PlatformFixes.hpp in Headers */, 83649BD62A03429300CD0580 /* windows_wine_version.hpp in Headers */, 83E5FD0D1FFEFA7D00659F0F /* AGC.h in Headers */, 8309969D27787E9A00857684 /* filecursor_filename_traits.hpp in Headers */, @@ -2239,8 +2215,6 @@ 83649BBD2A03422400CD0580 /* concat.hpp in Headers */, 8309969C27787E9A00857684 /* filecursor.hpp in Headers */, 83649BCE2A03424E00CD0580 /* fileadapter.hpp in Headers */, - 83F30AB7286EBBEA0005EF06 /* debug.h in Headers */, - 83F30AC7286EBBEA0005EF06 /* icy.h in Headers */, 83AA7D352519B694004C5298 /* TinyFFT.h in Headers */, 830996B627787E9A00857684 /* io_stdstream.hpp in Headers */, 830996B827787E9A00857684 /* tests_io.hpp in Headers */, @@ -2254,7 +2228,6 @@ 830996D927787E9A00857684 /* mutex.hpp in Headers */, 83E5FC8C1FFEFA0D00659F0F /* mptString.h in Headers */, 830996A727787E9A00857684 /* filedata_callbackstream.hpp in Headers */, - 830996F927787E9A00857684 /* floatingpoint.hpp in Headers */, 830996DA27787E9A00857684 /* engine.hpp in Headers */, 83649B992A0340FF00CD0580 /* mptCPU.h in Headers */, 8309970127787E9A00857684 /* numbers.hpp in Headers */, @@ -2280,6 +2253,7 @@ 83E5FE271FFEFA8500659F0F /* Tables.h in Headers */, 830996D427787E9A00857684 /* utility.hpp in Headers */, 83E5FE551FFEFA8500659F0F /* Loaders.h in Headers */, + 83BB83EB2DF2BD75002077FC /* filedata_base_unseekable_buffer.hpp in Headers */, 830996E727787E9A00857684 /* libc.hpp in Headers */, 830996F227787E9A00857684 /* detect.hpp in Headers */, 830996A927787E9A00857684 /* filedata_base_buffered.hpp in Headers */, @@ -2287,28 +2261,22 @@ 83E5FE631FFEFEA600659F0F /* svn_version.h in Headers */, 8309972227787E9A00857684 /* Logger.hpp in Headers */, 830996B327787E9A00857684 /* tests_crc.hpp in Headers */, - 83F30ACA286EBBEA0005EF06 /* synth_mono.h in Headers */, 830996C827787E9A00857684 /* message_macros.hpp in Headers */, 8309969127787E9A00857684 /* integer.hpp in Headers */, 830996EB27787E9A00857684 /* arithmetic_shift.hpp in Headers */, 8309972527787E9A00857684 /* Endian.hpp in Headers */, - 83F30AD4286EBBEA0005EF06 /* sample.h in Headers */, 83E5FC801FFEFA0D00659F0F /* mptFileIO.h in Headers */, 831132E921F9565F001F678F /* opal.h in Headers */, 830996E327787E9A00857684 /* uuid.hpp in Headers */, 830996D727787E9A00857684 /* buffer.hpp in Headers */, 83E5EFD01FFEF9D200659F0F /* config.h in Headers */, 8309970327787E9A00857684 /* detect_libcxx.hpp in Headers */, - 83F30AB5286EBBEA0005EF06 /* synth_ntom.h in Headers */, 830995C127787C1000857684 /* SymMODEcho.h in Headers */, 8309970E27787E9A00857684 /* preprocessor.hpp in Headers */, 830996A227787E9A00857684 /* filedata_base_seekable.hpp in Headers */, 83E5FE011FFEFA8500659F0F /* Container.h in Headers */, 83E5FE391FFEFA8500659F0F /* IntMixer.h in Headers */, - 83F30AC4286EBBEA0005EF06 /* mpg123lib_intern.h in Headers */, - 83F30ABC286EBBEA0005EF06 /* synth.h in Headers */, 830996FA27787E9A00857684 /* tests_base_math.hpp in Headers */, - 83F30AC2286EBBEA0005EF06 /* synth_sse3d.h in Headers */, 83649BB82A03420100CD0580 /* logic_error.hpp in Headers */, 830996A527787E9A00857684 /* filecursor_callbackstream.hpp in Headers */, 83E5FDF01FFEFA8500659F0F /* WavesReverb.h in Headers */, @@ -2339,13 +2307,14 @@ 83E5FCF51FFEFA1A00659F0F /* libopenmpt_stream_callbacks_buffer.h in Headers */, 830996FE27787E9A00857684 /* tests_base_arithmetic_shift.hpp in Headers */, 830996A327787E9A00857684 /* filecursor_traits_filedata.hpp in Headers */, + 83BB83FD2DF2BE22002077FC /* wav.hpp in Headers */, + 83BB83FE2DF2BE22002077FC /* wav_write.hpp in Headers */, + 83BB83FF2DF2BE22002077FC /* tags.hpp in Headers */, 8309970A27787E9A00857684 /* numeric.hpp in Headers */, 830996FB27787E9A00857684 /* tests_base_wrapping_divide.hpp in Headers */, - 830995B227787BB800857684 /* Dither.h in Headers */, 83649B9A2A0340FF00CD0580 /* mptFileType.h in Headers */, 830996BB27787E9A00857684 /* system_error.hpp in Headers */, 8309971927787E9A00857684 /* DitherSimple.hpp in Headers */, - 83F30ACF286EBBEA0005EF06 /* icy2utf8.h in Headers */, 83E5FDFB1FFEFA8500659F0F /* PluginManager.h in Headers */, ); runOnlyForDeploymentPostprocessing = 0; @@ -2430,6 +2399,7 @@ 83E5FE511FFEFA8500659F0F /* MixFuncTable.cpp in Sources */, 83E5FE4C1FFEFA8500659F0F /* UMXTools.cpp in Sources */, 83E5FE381FFEFA8500659F0F /* Load_xm.cpp in Sources */, + 83BB83FC2DF2BE22002077FC /* wav_write.cpp in Sources */, 83E5FDF51FFEFA8500659F0F /* Echo.cpp in Sources */, 83E5FDB81FFEFA8500659F0F /* ITTools.cpp in Sources */, 83E5FE471FFEFA8500659F0F /* SampleFormats.cpp in Sources */, @@ -2443,7 +2413,6 @@ 83E5FE4A1FFEFA8500659F0F /* Load_it.cpp in Sources */, 83E5FE3D1FFEFA8500659F0F /* Load_s3m.cpp in Sources */, 83E5FDBF1FFEFA8500659F0F /* Load_gdm.cpp in Sources */, - 83E5FC8B1FFEFA0D00659F0F /* mptFileIO.cpp in Sources */, 83E5FCEC1FFEFA1A00659F0F /* libopenmpt_impl.cpp in Sources */, 83E5FDFF1FFEFA8500659F0F /* Load_ptm.cpp in Sources */, 83E5FE3A1FFEFA8500659F0F /* MIDIEvents.cpp in Sources */, @@ -2466,7 +2435,6 @@ 83E5FC711FFEFA0D00659F0F /* version.cpp in Sources */, 83E5FDE61FFEFA8500659F0F /* DigiBoosterEcho.cpp in Sources */, 83E5FE4F1FFEFA8500659F0F /* Load_ult.cpp in Sources */, - 83E5FC811FFEFA0D00659F0F /* mptStringFormat.cpp in Sources */, 83E5FC861FFEFA0D00659F0F /* mptPathString.cpp in Sources */, 83E5FE561FFEFA8500659F0F /* Tagging.cpp in Sources */, 83E5FDCB1FFEFA8500659F0F /* Load_psm.cpp in Sources */, @@ -2480,13 +2448,11 @@ 83E5FE0E1FFEFA8500659F0F /* OggStream.cpp in Sources */, 83E5FE451FFEFA8500659F0F /* Load_mdl.cpp in Sources */, 831132EC21F9565F001F678F /* OPL.cpp in Sources */, - 83649B982A0340FF00CD0580 /* mptFileTemporary.cpp in Sources */, 83E5FDBB1FFEFA8500659F0F /* Load_stm.cpp in Sources */, 83E5FE1D1FFEFA8500659F0F /* ModInstrument.cpp in Sources */, 83E5FE461FFEFA8500659F0F /* WindowedFIR.cpp in Sources */, 83E5FE1E1FFEFA8500659F0F /* Load_mo3.cpp in Sources */, 83E5FE351FFEFA8500659F0F /* MixerSettings.cpp in Sources */, - 83E5FC891FFEFA0D00659F0F /* mptString.cpp in Sources */, 83E5FDC61FFEFA8500659F0F /* mod_specifications.cpp in Sources */, 83AA7D332519B694004C5298 /* SampleFormatSFZ.cpp in Sources */, 831132D821F955B2001F678F /* mptStringBuffer.cpp in Sources */, @@ -2539,11 +2505,29 @@ 83E5FE201FFEFA8500659F0F /* Dlsbank.cpp in Sources */, 83E5FD061FFEFA7D00659F0F /* AGC.cpp in Sources */, 83E5FDFE1FFEFA8500659F0F /* ContainerUMX.cpp in Sources */, - 83E5FC671FFEFA0D00659F0F /* mptStringParse.cpp in Sources */, 83E5FDE81FFEFA8500659F0F /* Flanger.cpp in Sources */, 83E5FE4E1FFEFA8500659F0F /* Load_okt.cpp in Sources */, 83E5FE521FFEFA8500659F0F /* SampleFormatOpus.cpp in Sources */, 83E5FC7F1FFEFA0D00659F0F /* serialization_utils.cpp in Sources */, + 83BB83C82DF2BD29002077FC /* Load_etx.cpp in Sources */, + 83BB83C92DF2BD29002077FC /* Load_unic.cpp in Sources */, + 83BB83CA2DF2BD29002077FC /* PlaybackTest.cpp in Sources */, + 83BB83CB2DF2BD29002077FC /* Load_cba.cpp in Sources */, + 83BB83CC2DF2BD29002077FC /* PlayState.cpp in Sources */, + 83BB83CD2DF2BD29002077FC /* Load_rtm.cpp in Sources */, + 83BB83CE2DF2BD29002077FC /* Load_ims.cpp in Sources */, + 83BB83CF2DF2BD29002077FC /* Load_tcb.cpp in Sources */, + 83BB83D02DF2BD29002077FC /* Load_ice.cpp in Sources */, + 83BB83D12DF2BD29002077FC /* MODTools.cpp in Sources */, + 83BB83D22DF2BD29002077FC /* MIDIMacroParser.cpp in Sources */, + 83BB83D32DF2BD29002077FC /* Load_fc.cpp in Sources */, + 83BB83D42DF2BD29002077FC /* Load_kris.cpp in Sources */, + 83BB83D52DF2BD29002077FC /* InstrumentSynth.cpp in Sources */, + 83BB83D62DF2BD29002077FC /* Load_ftm.cpp in Sources */, + 83BB83D72DF2BD29002077FC /* Load_pt36.cpp in Sources */, + 83BB83D82DF2BD29002077FC /* Load_puma.cpp in Sources */, + 83BB83D92DF2BD29002077FC /* Load_stk.cpp in Sources */, + 83BB83DA2DF2BD29002077FC /* Load_gmc.cpp in Sources */, 83E5FDEA1FFEFA8500659F0F /* ParamEq.cpp in Sources */, 83649B9B2A0340FF00CD0580 /* mptFileType.cpp in Sources */, 830995BC27787BEE00857684 /* Load_symmod.cpp in Sources */, @@ -2714,12 +2698,14 @@ 83E5EFC61FFEF7CC00659F0F /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "c++23"; CODE_SIGN_IDENTITY = ""; COMBINE_HIDPI_IMAGES = YES; DEAD_CODE_STRIPPING = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; FRAMEWORK_VERSION = A; + GCC_C_LANGUAGE_STANDARD = c23; HEADER_SEARCH_PATHS = ( "$(PROJECT_DIR)/OpenMPT", "$(PROJECT_DIR)/OpenMPT/include/modplug/include", @@ -2752,12 +2738,14 @@ 83E5EFC71FFEF7CC00659F0F /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "c++23"; CODE_SIGN_IDENTITY = ""; COMBINE_HIDPI_IMAGES = YES; DEAD_CODE_STRIPPING = YES; DYLIB_COMPATIBILITY_VERSION = 1; DYLIB_CURRENT_VERSION = 1; FRAMEWORK_VERSION = A; + GCC_C_LANGUAGE_STANDARD = c23; HEADER_SEARCH_PATHS = ( "$(PROJECT_DIR)/OpenMPT", "$(PROJECT_DIR)/OpenMPT/include/modplug/include",