diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c811a04c..b329380b 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -14,7 +14,7 @@ jobs: run: | python -m pip install --upgrade pip pip install Pillow - choco install -y directx-sdk zip --no-progress --yes + choco install -y directx-sdk zip ffmpeg-shared --no-progress --yes shell: powershell - name: Download and Extract VCE9 release @@ -151,7 +151,7 @@ jobs: - name: Install required libraries run: | sudo apt update - sudo apt install -y make pkgconf libsdl2-dev libasound2-plugins libjack-dev python3-pillow + sudo apt install -y make pkgconf libsdl2-dev libasound2-plugins libjack-dev python3-pillow libavcodec-dev libavformat-dev libavfilter-dev libavutil-dev libswresample-dev - name: Build X64 working-directory: projects @@ -319,7 +319,7 @@ jobs: wget -qO ./debian-archive-keyring.deb "$BASE$LATEST" dpkg -i ./debian-archive-keyring.deb - apt update && apt install -y python3 python3-pillow + apt update && apt install -y python3 python3-pillow cd projects make PLATFORM=GARLIC ' @@ -358,7 +358,7 @@ jobs: - name: Install Garlic Plus toolchain run: | sudo apt update && sudo apt install -y python3-pillow - wget -O /tmp/rg35xxplus-toolchain.tar.xz https://github.com/simotek/union-rg35xxplus-toolchain/releases/download/20240830/rg35xxplus-toolchain.tar.xz + wget -O /tmp/rg35xxplus-toolchain.tar.xz https://github.com/djdiskmachine/union-rg35xxplus-toolchain/releases/download/1.1/rg35xxplus-toolchain.tar.gz mkdir /opt/rg35xxplus-toolchain tar -xvf /tmp/rg35xxplus-toolchain.tar.xz -C /opt/rg35xxplus-toolchain --strip-components=1 @@ -439,6 +439,7 @@ jobs: wget https://www.libsdl.org/release/SDL2-2.0.14.dmg hdiutil attach SDL2-2.0.14.dmg sudo cp -R /Volumes/SDL2/SDL2.framework /Library/Frameworks/ + brew install ffmpeg - name: Build Xcode project run: | diff --git a/.github/workflows/check.yml b/.github/workflows/check.yml index cac485d8..cd7c1478 100644 --- a/.github/workflows/check.yml +++ b/.github/workflows/check.yml @@ -60,8 +60,12 @@ jobs: uses: actions/checkout@v4.1.7 - name: Install build deps - run: choco install -y directx-sdk zip --no-progress --yes + run: choco install -y directx-sdk zip ffmpeg-shared --no-progress --yes shell: powershell + + - name: Install FFmpeg development libraries + run: vcpkg install ffmpeg[avcodec,avformat,avfilter]:x86-windows-static + shell: cmd - name: Download and Extract VCE9 release run: | @@ -116,7 +120,7 @@ jobs: run: | sudo dpkg --add-architecture i386 sudo apt update - sudo apt install -y make pkgconf gcc-multilib g++-multilib libsdl1.2-dev:i386 libasound2-plugins:i386 libjack-dev:i386 python3-pillow + sudo apt install -y make pkgconf gcc-multilib g++-multilib libsdl1.2-dev:i386 libasound2-plugins:i386 libjack-dev:i386 python3-pillow libavcodec-dev:i386 libavformat-dev:i386 libavfilter-dev:i386 libavutil-dev:i386 libswresample-dev:i386 - name: Build DEB working-directory: projects @@ -148,7 +152,7 @@ jobs: run: | sudo dpkg --add-architecture i386 sudo apt update - sudo apt install -y make pkgconf gcc-multilib g++-multilib libsdl2-dev:i386 libasound2-plugins:i386 libjack-dev:i386 python3-pillow + sudo apt install -y make pkgconf gcc-multilib g++-multilib libsdl2-dev:i386 libasound2-plugins:i386 libjack-dev:i386 python3-pillow libavcodec-dev:i386 libavformat-dev:i386 libavfilter-dev:i386 libavutil-dev:i386 libswresample-dev:i386 - name: Build X86 working-directory: projects @@ -292,7 +296,7 @@ jobs: - name: Install Garlic Plus toolchain run: | sudo apt update && sudo apt install -y python3-pillow - wget -O /tmp/rg35xxplus-toolchain.tar.xz https://github.com/simotek/union-rg35xxplus-toolchain/releases/download/20240830/rg35xxplus-toolchain.tar.xz + wget -O /tmp/rg35xxplus-toolchain.tar.xz https://github.com/djdiskmachine/union-rg35xxplus-toolchain/releases/download/1.1/rg35xxplus-toolchain.tar.gz mkdir /opt/rg35xxplus-toolchain tar -xvf /tmp/rg35xxplus-toolchain.tar.xz -C /opt/rg35xxplus-toolchain --strip-components=1 @@ -360,7 +364,7 @@ jobs: - name: Install Miyoo Mini toolchain run: | sudo apt update && sudo apt install -y python3-pillow - wget -O /tmp/miyoomini-toolchain.tar.xz https://github.com/djdiskmachine/miyoomini-toolchain-buildroot/releases/download/1.0.0/miyoomini-toolchain.tar.xz + wget -O /tmp/miyoomini-toolchain.tar.xz https://github.com/djdiskmachine/miyoomini-toolchain-buildroot/releases/download/1.1/miyoomini-toolchain.tar.xz mkdir /opt/miyoomini-toolchain tar -xvf /tmp/miyoomini-toolchain.tar.xz -C /opt/miyoomini-toolchain --strip-components=1 @@ -395,7 +399,8 @@ jobs: - name: Install required libraries run: | sudo apt update - sudo apt install -y make pkgconf libsdl2-dev libasound2-plugins libjack-dev python3-pillow + sudo apt install -y make pkgconf libsdl2-dev libasound2-plugins libjack-dev python3-pillow libavcodec-dev libavformat-dev libavfilter-dev libavutil-dev libswresample-dev + - name: Build X64 working-directory: projects @@ -429,6 +434,7 @@ jobs: wget https://www.libsdl.org/release/SDL2-2.0.14.dmg hdiutil attach SDL2-2.0.14.dmg sudo cp -R /Volumes/SDL2/SDL2.framework /Library/Frameworks/ + brew install ffmpeg - name: Build Xcode project run: | diff --git a/README.md b/README.md index ce6d7ee7..a8492d1d 100644 --- a/README.md +++ b/README.md @@ -40,25 +40,24 @@ Recommended reading to get you started: ## Features per platform -| Platform | MIDI_Possible | MIDI_enabled | Soundfonts | Note | -|-------------|---------------|--------------|------------|--------------------------------------| -| PSP | NO | NO | YES | [See notes](projects/resources/PSP/INSTALL_HOW_TO.txt) | -| DEB | YES | YES | YES | | -| X64 | YES | YES | NO | | -| X86 | YES | YES | YES | | -| STEAM | YES | YES | NO | | -| MIYOO | NO | NO | YES | Port by [Nine-H](https://ninethehacker.xyz) | -| W32 | YES | YES | YES | Built in VS2008 with love | -| RASPI | YES | YES | YES | Versatile platform | -| CHIP | YES | YES | YES | [See notes](projects/resources/CHIP/INSTALL_HOW_TO.txt) | -| BITTBOY | MAYBE | NO | YES | | -| GARLIC | MAYBE | NO | YES | Port by [Simotek](http://simotek.net)| -| GARLICPLUS | MAYBE | NO | YES | Port by [Simotek](http://simotek.net)| -| RG35XXPLUS | MAYBE | NO | YES | Port by [Simotek](http://simotek.net)| -| MACOS | YES | YES | NO | Port by [clsource](https://genserver.social/clsource) | - +| Platform | MIDI_Possible | MIDI_enabled | Soundfonts | PrintFX | Note | +|-------------|---------------|--------------|------------|---------|--------------------------------------| +| PSP | NO | NO | YES | NO | [See notes](projects/resources/PSP/INSTALL_HOW_TO.txt) | +| DEB | YES | YES | YES | YES | | +| X64 | YES | YES | NO | YES | | +| X86 | YES | YES | YES | YES | | +| STEAM | YES | YES | NO | YES | | +| MIYOO | NO | NO | YES | NO | Port by [Nine-H](https://ninethehacker.xyz) | +| W32 | YES | YES | YES | YES | Built in VS2008 with love | +| RASPI | YES | YES | YES | YES | Versatile platform | +| CHIP | YES | YES | YES | YES | [See notes](projects/resources/CHIP/INSTALL_HOW_TO.txt) | +| BITTBOY | MAYBE | NO | YES | YES | | +| GARLIC | MAYBE | NO | YES | YES | Port by [Simotek](http://simotek.net)| +| GARLICPLUS | MAYBE | NO | YES | YES | Port by [Simotek](http://simotek.net)| +| RG35XXPLUS | MAYBE | NO | YES | NO | Port by [Simotek](http://simotek.net)| +| MACOS | YES | YES | NO | NO | Port by [clsource](https://genserver.social/clsource) | * **Soundfont library is currently not ported for 64bit OS** * **MIDI functionality __greatly__ depends on kernel support, please feature request your favourite OS maintainer =)** * **Install ffmpeg by following install instructions for your platform [here](https://www.ffmpeg.org/download.html)** -* **PrintFX requires full ffmpeg. If marked as TBA, it requires a redesign using [libav](https://trac.ffmpeg.org/wiki/Using%20libav*)** +* **PrintFX requires toolchain support of ffmpeg libs, for all targets marked maybe contact the maintainer of your consoles toolchain** diff --git a/projects/Makefile b/projects/Makefile index f735c9d7..4bed97eb 100644 --- a/projects/Makefile +++ b/projects/Makefile @@ -259,7 +259,8 @@ COMMONFILES := \ char.o n_assert.o fixed.o wildcard.o \ SyncMaster.o TablePlayback.o Player.o \ Table.o TableView.o\ - InstrumentBank.o WavFileWriter.o WavFile.o MidiInstrument.o Filters.o SampleVariable.o SampleInstrument.o SamplePool.o CommandList.o FxPrinter.o\ + InstrumentBank.o WavFileWriter.o WavFile.o MidiInstrument.o Filters.o \ + SampleVariable.o SampleInstrument.o SamplePool.o CommandList.o FxPrinter.o\ PersistencyService.o Persistent.o \ Observable.o SingletonRegistry.o \ Audio.o AudioMixer.o AudioOutDriver.o AudioDriver.o \ @@ -274,6 +275,12 @@ COMMONFILES := \ HexBuffers.o lz.o \ tinyxmlparser.o tinyxml.o tinyxmlerror.o tinystr.o Tiny2NosStub.o +ifneq ($(FFMPEG_ENABLED), 0) +ifneq ($(strip $(FFMPEG_LIBS)),) + COMMONFILES += LibavProcessor.o +endif +endif + #--------------------------------------------------------------------------------- # Linux #--------------------------------------------------------------------------------- diff --git a/projects/Makefile.BITTBOY b/projects/Makefile.BITTBOY index 2b505bcf..067ce5fc 100644 --- a/projects/Makefile.BITTBOY +++ b/projects/Makefile.BITTBOY @@ -1,4 +1,5 @@ -include $(PWD)/rules_base + STRIP = $(CROSS_COMPILE)strip DEFINES := \ @@ -8,6 +9,8 @@ DEFINES := \ -DHAVE_STDINT_H \ -D_NDEBUG \ -D__LINUX_ALSA__ \ + -DFFMPEG_ENABLED \ + -DFFMPEG_LEGACY_API \ -D_NO_JACK_ DEVKIT=/opt/arm-buildroot-linux-musleabi_sdk-buildroot @@ -19,15 +22,17 @@ SDL_CFLAGS := $(shell $(SYSROOT)/usr/bin/sdl-config --cflags) SDL_LIBS := $(shell $(SYSROOT)/usr/bin/sdl-config --libs) SDL_BASE = $(DEVKIT)/arm-buildroot-linux-musleabi/sysroot/usr/bin/ +-include $(PWD)/rules_libav + TOOLPATH=$(DEVKIT)/usr/bin PREFIX := arm-linux- OPT_FLAGS = -O3 -Ofast -INCLUDES = -I$(PWD)/../sources -Iinclude $(SDL_CFLAGS) +INCLUDES = -I$(PWD)/../sources -Iinclude $(SDL_CFLAGS) $(FFMPEG_CFLAGS) CFLAGS := $(DEFINES) $(INCLUDES) $(OPT_FLAGS) $(SDL_CFLAGS) -Wall -DRS97 CXXFLAGS:= $(CFLAGS) -std=gnu++03 -LIBS := $(SDL_LIBS) -lSDL -lSDL_mixer -lasound -lpthread -LIBDIRS := $(DEKVIT)/usr/lib +LIBS := $(SDL_LIBS) -lSDL -lSDL_mixer -lasound -lpthread $(FFMPEG_LIBS) +LIBDIRS := $(SYSROOT)/usr/lib OUTPUT = ../lgpt-bittboy EXTENSION:= elf diff --git a/projects/Makefile.GARLIC b/projects/Makefile.GARLIC index 657f6abe..63fa2393 100644 --- a/projects/Makefile.GARLIC +++ b/projects/Makefile.GARLIC @@ -6,6 +6,8 @@ DEFINES := \ -DCPP_MEMORY \ -D_NDEBUG \ -DHAVE_STDINT_H \ + -DFFMPEG_ENABLED \ + -DFFMPEG_LEGACY_API \ -D_NO_JACK_ DEVKIT = /opt/miyoo/ @@ -19,13 +21,15 @@ SYSROOT := $(shell $(CROSS_COMPILE)gcc --print-sysroot) SDL_CFLAGS := $(shell $(SYSROOT)/usr/bin/sdl-config --cflags) SDL_LIBS := $(shell $(SYSROOT)/usr/bin/sdl-config --libs) +-include $(PWD)/rules_libav + # optimization OPT_FLAGS = -O3 -Ofast -fdata-sections -fdata-sections -fno-common -fno-PIC -flto -marm -mtune=cortex-a53 -mfpu=neon-vfpv4 -mfloat-abi=hard PREFIX := arm-linux-gnueabihf- -INCLUDES:= -Iinclude $(SDL_CFLAGS) -I$(PWD)/../sources +INCLUDES:= -Iinclude $(SDL_CFLAGS) $(FFMPEG_CFLAGS) -I$(PWD)/../sources CFLAGS := $(DEFINES) $(INCLUDES) $(OPT_FLAGS) -Wall CXXFLAGS:= $(CFLAGS) -std=gnu++03 -LIBS := $(SDL_LIBS) -lpthread +LIBS := $(SDL_LIBS) $(FFMPEG_LIBS) -lpthread LIBDIRS := $(DEKVIT)/usr/lib LIBDIRS += $(DEKVIT)/usr/include OUTPUT := ../lgpt-garlic diff --git a/projects/Makefile.GARLICPLUS b/projects/Makefile.GARLICPLUS index 72ce69c4..51b588d3 100644 --- a/projects/Makefile.GARLICPLUS +++ b/projects/Makefile.GARLICPLUS @@ -9,7 +9,10 @@ DEFINES := \ -DCPP_MEMORY \ -D_NDEBUG \ -DHAVE_STDINT_H \ - -D_NO_JACK_ + -D_NO_JACK_ \ + -DFFMPEG_ENABLED \ + -DFFMPEG_LEGACY_API + # compiled using the https://github.com/shauninman/union-rg35xxplus-toolchain @@ -23,13 +26,24 @@ SYSROOT := $(shell $(CROSS_COMPILE)gcc --print-sysroot) SDL_CFLAGS := $(shell $(SYSROOT)/usr/bin/sdl-config --cflags) SDL_LIBS := $(shell $(SYSROOT)/usr/bin/sdl-config --libs) +FFMPEG_INCL= libavformat \ + libavfilter \ + libavcodec \ + libavutil \ + +FFMPEG_CFLAGS := $(shell $(DEVKIT)/usr/bin/pkg-config --cflags $(FFMPEG_INCL)) +FFMPEG_LIBS := $(shell $(DEVKIT)/usr/bin/pkg-config --libs $(FFMPEG_INCL)) + +$(info FFMPEG_CFLAGS: $(FFMPEG_CFLAGS)) +$(info FFMPEG_LIBS: $(FFMPEG_LIBS)) + # optimization OPT_FLAGS = -O3 -Ofast -fdata-sections -fdata-sections -fno-common -fno-PIC -flto -marm -mtune=cortex-a53 -mfpu=neon-vfpv4 -mfloat-abi=hard PREFIX := arm-linux-gnueabihf- -INCLUDES = -Iinclude $(SDL_CFLAGS) -I$(PWD)/../sources +INCLUDES = -Iinclude $(SDL_CFLAGS) $(FFMPEG_CFLAGS) -I$(PWD)/../sources CFLAGS := $(DEFINES) $(INCLUDES) $(OPT_FLAGS) -Wall CXXFLAGS:= $(CFLAGS) -std=gnu++03 -LIBS := -lSDL -lpthread +LIBS := -lSDL -lpthread $(FFMPEG_LIBS) LIBDIRS := $(DEKVIT)/usr/lib LIBDIRS += $(DEKVIT)/usr/include OUTPUT = ../lgpt-garlicplus diff --git a/projects/Makefile.MIYOO b/projects/Makefile.MIYOO index e38c4117..9567f158 100644 --- a/projects/Makefile.MIYOO +++ b/projects/Makefile.MIYOO @@ -6,6 +6,8 @@ DEFINES := \ -DCPP_MEMORY \ -DHAVE_STDINT_H \ -D_NDEBUG \ + -DFFMPEG_ENABLED \ + -DFFMPEG_LEGACY_API \ -D_NO_JACK_ DEVKIT = /opt/miyoomini-toolchain/ @@ -18,18 +20,28 @@ SYSROOT := $(shell $(CROSS_COMPILE)gcc --print-sysroot) SDL_CFLAGS := $(shell $(SYSROOT)/usr/bin/sdl-config --cflags) SDL_LIBS := $(shell $(SYSROOT)/usr/bin/sdl-config --libs) -INCLUDES = -Iinclude $(SDL_CFLAGS) -I$(PWD)/../sources -OPT_FLAGS = -O3 -Ofast -fdata-sections -fdata-sections -fno-common -fno-PIC -flto -marm -mtune=cortex-a7 -mfpu=neon-vfpv4 -mfloat-abi=hard -march=armv7ve+simd +LIBAV_PATH := arm-linux-gnueabihf/libc/usr/include +FFMPEG_CFLAGS := -I$(DEVKIT)/$(LIBAV_PATH)/libavformat \ + -I$(DEVKIT)/$(LIBAV_PATH)/libavfilter \ + -I$(DEVKIT)/$(LIBAV_PATH)/libavcodec \ + -I$(DEVKIT)/$(LIBAV_PATH)/libavutil \ + +FFMPEG_LIBS := -lavformat -lavfilter -lavcodec -lavutil +$(info FFMPEG_CFLAGS: $(FFMPEG_CFLAGS)) +$(info FFMPEG_LIBS: $(FFMPEG_LIBS)) + +INCLUDES = -Iinclude $(SDL_CFLAGS) $(FFMPEG_CFLAGS) -I$(PWD)/../sources +OPT_FLAGS = -O3 -Ofast -fdata-sections -fdata-sections -fno-common -fno-PIC -flto -marm -mtune=cortex-a7 -mfpu=neon-vfpv4 -mfloat-abi=hard -march=armv7ve+simd TOOLPATH=$(DEVKIT)/usr/bin PREFIX := arm-linux-gnueabihf- CFLAGS := $(DEFINES) $(INCLUDES) $(SDL_CFLAGS) $(OPT_FLAGS) -Wall CXXFLAGS:= $(CFLAGS) -std=gnu++03 -LIBS := -lSDL -lSDL_mixer -lpthread $(SDL_LIBS) -LIBDIRS := $(DEKVIT)/usr/lib -LIBDIRS += $(DEKVIT)/usr/include +LIBS := -lSDL -lSDL_mixer -lpthread $(SDL_LIBS) $(FFMPEG_LIBS) +LIBDIRS := $(DEVKIT)/usr/lib +LIBDIRS += $(DEVKIT)/usr/include OUTPUT = ../lgpt-miyoo EXTENSION:= elf diff --git a/projects/Makefile.RG35XXPLUS b/projects/Makefile.RG35XXPLUS index dc04b211..e9f647f7 100644 --- a/projects/Makefile.RG35XXPLUS +++ b/projects/Makefile.RG35XXPLUS @@ -24,11 +24,23 @@ TOOLPATH=$(DEVKIT)/usr/bin SDL_CFLAGS := -I/$(SYSROOT)/include -D_REENTRANT SDL_LIBS := -lSDL2 +FFMPEG_INCL= libavformat \ + libavfilter \ + libavcodec \ + libavutil \ + +LIBAV_PATH := aarch64-linux-gnu/include +FFMPEG_CFLAGS := -I$(DEVKIT)/$(LIBAV_PATH)/libavformat -I$(DEVKIT)/$(LIBAV_PATH)/libavfilter -I$(DEVKIT)/$(LIBAV_PATH)/libavcodec -I$(SYSROOT)/$(LIBAV_PATH)/libavutil +FFMPEG_LIBS := -lavformat -lavfilter -lavcodec -lavutil + +$(info FFMPEG_CFLAGS: $(FFMPEG_CFLAGS)) +$(info FFMPEG_LIBS: $(FFMPEG_LIBS)) + OPT_FLAGS = -O3 -mlittle-endian -mabi=lp64 -march=armv8-a+crypto+crc -fasynchronous-unwind-tables -fstack-protector-strong -Wformat -Wformat-security -fstack-clash-protection -dumpbase -INCLUDES = -Iinclude $(SDL_CFLAGS) -I$(DEVKIT)/$(TRIPLET)/include -I$(DEVKIT)/$(TRIPLET)/include/c++/11/ -I$(PWD)/../sources +INCLUDES = -Iinclude $(SDL_CFLAGS) $(FFMPEG_CFLAGS) -I$(DEVKIT)/$(TRIPLET)/include -I$(DEVKIT)/$(TRIPLET)/include/c++/11/ -I$(PWD)/../sources CFLAGS := $(DEFINES) $(INCLUDES) $(OPT_FLAGS) -Wall CXXFLAGS:= $(CFLAGS) -std=gnu++03 -LIBS := -Wl,-rpath-link,$(DEVKIT)/$(TRIPLET)/lib -Wl,-rpath-link,$(DEVKIT)/$(TRIPLET)/lib/pulseaudio $(SDL_LIBS) -lpthread +LIBS := -Wl,-rpath-link,$(DEVKIT)/$(TRIPLET)/lib -Wl,-rpath-link,$(DEVKIT)/$(TRIPLET)/lib/pulseaudio $(SDL_LIBS) $(FFMPEG_LIBS) -lpthread OUTPUT = ../lgpt-rg35xxplus EXTENSION:= elf diff --git a/projects/Makefile.X64 b/projects/Makefile.X64 index 6460f413..d9fa59a8 100644 --- a/projects/Makefile.X64 +++ b/projects/Makefile.X64 @@ -1,4 +1,5 @@ -include $(PWD)/rules_base +-include $(PWD)/rules_libav # config DEFINES := \ @@ -24,10 +25,10 @@ SDL_LIBS := $(shell pkg-config sdl2 --libs) OPT_FLAGS := -O3 #For debugging OPT_FLAGS := -g -INCLUDES := $(ALSA_CFLAGS) $(JACK_CFLAGS) $(SDL_CFLAGS) -I$(PWD)/../sources +INCLUDES := $(ALSA_CFLAGS) $(JACK_CFLAGS) $(SDL_CFLAGS) $(FFMPEG_CFLAGS) -I$(PWD)/../sources CFLAGS := $(OPT_FLAGS) $(DEFINES) $(INCLUDES) -Wall CXXFLAGS := $(CFLAGS) -std=gnu++11 -LIBS := $(ALSA_LIBS) $(JACK_LIBS) $(SDL_LIBS) +LIBS := $(ALSA_LIBS) $(JACK_LIBS) $(SDL_LIBS) $(FFMPEG_LIBS) OUTPUT := ../lgpt EXTENSION := x64 diff --git a/projects/Makefile.X86 b/projects/Makefile.X86 index 4c8b3b86..8255db5e 100644 --- a/projects/Makefile.X86 +++ b/projects/Makefile.X86 @@ -11,21 +11,31 @@ DEFINES := \ -DRTMIDI \ -DFFMPEG_ENABLED +FFMPEG_INCL= libavformat \ + libavfilter \ + libavcodec \ + libavutil \ + ALSA_CFLAGS := $(shell i686-linux-gnu-pkg-config alsa --cflags) ALSA_LIBS := $(shell i686-linux-gnu-pkg-config alsa --libs) JACK_CFLAGS := $(shell i686-linux-gnu-pkg-config jack --cflags) JACK_LIBS := $(shell i686-linux-gnu-pkg-config jack --libs) SDL_CFLAGS := $(shell i686-linux-gnu-pkg-config sdl2 --cflags) SDL_LIBS := $(shell i686-linux-gnu-pkg-config sdl2 --libs) +FFMPEG_CFLAGS := $(shell i686-linux-gnu-pkg-config $(FFMPEG_INCL) --cflags) +FFMPEG_LIBS := $(shell i686-linux-gnu-pkg-config $(FFMPEG_INCL) --libs) + +$(info FFMPEG_CFLAGS: $(FFMPEG_CFLAGS)) +$(info FFMPEG_LIBS: $(FFMPEG_LIBS)) # optimization OPT_FLAGS := -O3 -m32 #For debugging #OPT_FLAGS := -g -m32 -INCLUDES := $(ALSA_CFLAGS) $(JACK_CFLAGS) $(SDL_CFLAGS) -I$(PWD)/../sources +INCLUDES := $(ALSA_CFLAGS) $(JACK_CFLAGS) $(SDL_CFLAGS) $(FFMPEG_CFLAGS) -I$(PWD)/../sources CFLAGS := $(OPT_FLAGS) $(DEFINES) $(INCLUDES) -Wall CXXFLAGS := $(CFLAGS) -LIBS := $(ALSA_LIBS) $(JACK_LIBS) $(SDL_LIBS) +LIBS := $(ALSA_LIBS) $(JACK_LIBS) $(SDL_LIBS) $(FFMPEG_LIBS) LDFLAGS := -m32 OUTPUT = ../lgpt EXTENSION := x86 diff --git a/projects/lgpt.vcproj b/projects/lgpt.vcproj index d50935b8..6439789c 100644 --- a/projects/lgpt.vcproj +++ b/projects/lgpt.vcproj @@ -42,8 +42,8 @@ Name="VCCLCompilerTool" AdditionalOptions="/MP" Optimization="0" - AdditionalIncludeDirectories="../sources/Externals;../sources/" - PreprocessorDefinitions="WIN32;_DEBUG;__WINDOWS_DS__;__WINDOWS_ASIO__;_CRT_SECURE_NO_WARNINGS;__WINDOWS_MM__;CPP_MEMORY;_LGPT_NO_SCREEN_CACHE_" + AdditionalIncludeDirectories="../sources/Externals;../sources/;C:\vcpkg\installed\x86-windows-static\include" + PreprocessorDefinitions="WIN32;_DEBUG;__WINDOWS_DS__;__WINDOWS_ASIO__;_CRT_SECURE_NO_WARNINGS;__WINDOWS_MM__;CPP_MEMORY;_LGPT_NO_SCREEN_CACHE_;FFMPEG_ENABLED;FFMPEG_LEGACY_API" MinimalRebuild="false" BasicRuntimeChecks="3" RuntimeLibrary="3" @@ -65,10 +65,10 @@ + + + + project_->GetInstrumentBank(); instrument_ = static_cast(bank->GetInstrument(curInstr)); notificationResult_ = ""; - // Assume ffmpeg exists but swap for local ffmpig if it doesn't - ffmpeg_ = "ffmpeg"; - Path pigPath("bin:ffmpig"); - Path ffmpigPath(pigPath.GetPath().c_str()); - if(ffmpigPath.Exists()) ffmpeg_ = pigPath.GetPath(); } void FxPrinter::setParams() { @@ -21,10 +16,11 @@ void FxPrinter::setParams() { } void FxPrinter::setPaths() { - fi_ = std::string(instrument_->GetName()); + fi_ = std::string(instrument_->GetFullName()); + fiPath_ = samples_dir.GetPath() + '/' + fi_; - foWav_ = fi_.substr(0, fi_.find_last_of('.')) + "_.wav"; - fo_ = "\"" + samples_dir.GetPath() + '/' + foWav_ + "\""; + fo_ = fi_.substr(0, fi_.find_last_of('.')) + "_.wav"; + foPath_ = samples_dir.GetPath() + '/' + fo_; ir_ = impulse_dir.GetPath() + "/IR-s/"; ir_ += std::string(instrument_->FindVariable(SIP_PRINTFX)->GetString()); @@ -36,9 +32,9 @@ std::string FxPrinter::parseCommand() { float smplLength = static_cast(instrument_->GetSampleSize()) / 44100; std::ostringstream cm1, cm2, cm3, cm4, cm5; - cm1 << ffmpeg_ << " -y -i " - << "\"" << samples_dir.GetPath() << "/" << fi_ << "\"" - << " -i " << ir_ << " -filter_complex "; + // cm1 << ffmpeg_ << " -y -i " + // << "\"" << fi_ << "\"" + // << " -i " << ir_ << " -filter_complex "; // bug in ffmpeg version 4.4.2 // requires pad_dur to be over 0 or output will be infinitely long if (irPad_ > 0) { @@ -64,17 +60,22 @@ bool FxPrinter::Run() { setParams(); setPaths(); // Are we overwriting an already imported sample? - bool imported = SamplePool::GetInstance()->IsImported(foWav_); - std::string cmd = parseCommand(); - Trace::Log("Processed", cmd.c_str()); - if (system(cmd.c_str()) == 0) { - int newIndex = SamplePool::GetInstance()->Reassign(foWav_, imported); + bool imported = SamplePool::GetInstance()->IsImported(fo_); + parseCommand(); +#ifdef FFMPEG_ENABLED + if (encode(fiPath_.c_str(), ir_.c_str(), foPath_.c_str(), irWet_, irPad_) == 0) { + int newIndex = SamplePool::GetInstance()->Reassign(fo_, imported); instrument_->AssignSample(newIndex); - notificationResult_ = "OK!"; + notificationResult_ = "libav OK!"; return true; } else { Trace::Log("PRINTFX", "Failed"); notificationResult_ = "Failed, check lgpt.log"; return false; } +#else + Trace::Log("PRINTFX", "Failed"); + notificationResult_ = "Failed, check lgpt.log"; + return false; +#endif } diff --git a/sources/Application/FX/FxPrinter.h b/sources/Application/FX/FxPrinter.h index 61ad65ad..912fdae2 100644 --- a/sources/Application/FX/FxPrinter.h +++ b/sources/Application/FX/FxPrinter.h @@ -1,13 +1,16 @@ #ifndef FxPrinter_H #define FxPrinter_H -#include -#include -#include "Application/Views/ViewData.h" #include "Application/Instruments/InstrumentBank.h" #include "Application/Instruments/SampleInstrument.h" -#include "System/FileSystem/FileSystem.h" #include "Application/Instruments/SamplePool.h" +#include "Application/Views/ViewData.h" +#include "System/FileSystem/FileSystem.h" +#include +#include +#ifdef FFMPEG_ENABLED +#include "LibavProcessor.h" +#endif class FxPrinter { public: @@ -26,10 +29,10 @@ class FxPrinter { int irPad_; int irWet_; std::string fi_; + std::string fiPath_; std::string fo_; + std::string foPath_; std::string ir_; - std::string foWav_; - std::string ffmpeg_; char* notificationResult_; }; diff --git a/sources/Application/FX/LibavProcessor.c b/sources/Application/FX/LibavProcessor.c new file mode 100644 index 00000000..2c894cf5 --- /dev/null +++ b/sources/Application/FX/LibavProcessor.c @@ -0,0 +1,789 @@ +/** + * Apply effects to a file using libav + */ + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +static AVFormatContext *ifmt_ctx; +static AVFormatContext *ir_fmt_ctx; +static AVFormatContext *ofmt_ctx; +static AVFilterGraph *filter_graph; +static AVFilterContext *buffersrc_ctx; +static AVFilterContext *ir_buffersrc_ctx; +static AVFilterContext *buffersink_ctx; +static AVCodecContext *enc_ctx = NULL; + +static int open_input_file(const char *filename) +{ + const AVCodec *dec; + AVCodecContext *dec_ctx; + AVStream *stream; + int ret; + + if ((ret = avformat_open_input(&ifmt_ctx, filename, NULL, NULL)) < 0) { + av_log(NULL, AV_LOG_ERROR, "[LibAvProc] Cannot open input file\n"); + return ret; + } + + if ((ret = avformat_find_stream_info(ifmt_ctx, NULL)) < 0) { + av_log(NULL, AV_LOG_ERROR, "[LibAvProc] Cannot find stream information\n"); + return ret; + } + + /* select the audio stream */ + ret = av_find_best_stream(ifmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, &dec, 0); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "[LibAvProc] Cannot find an audio stream in the input file\n"); + return ret; + } + + stream = ifmt_ctx->streams[ret]; + + /* create decoding context */ + dec_ctx = avcodec_alloc_context3(dec); + if (!dec_ctx) + return AVERROR(ENOMEM); + avcodec_parameters_to_context(dec_ctx, stream->codecpar); + + /* init the audio decoder */ + if ((ret = avcodec_open2(dec_ctx, dec, NULL)) < 0) { + av_log(NULL, AV_LOG_ERROR, "[LibAvProc] Cannot open audio decoder\n"); + return ret; + } + + stream->codecpar->codec_id = dec_ctx->codec_id; + stream->codecpar->sample_rate = dec_ctx->sample_rate; + stream->codecpar->format = dec_ctx->sample_fmt; +#ifdef FFMPEG_LEGACY_API + stream->codecpar->channels = dec_ctx->channels; + stream->codecpar->channel_layout = dec_ctx->channel_layout; +#else + av_channel_layout_copy(&stream->codecpar->ch_layout, &dec_ctx->ch_layout); +#endif + + avcodec_free_context(&dec_ctx); + + return 0; +} + +static int open_ir_file(const char *filename) +{ + const AVCodec *dec; + AVCodecContext *dec_ctx; + AVStream *stream; + int ret; + + if ((ret = avformat_open_input(&ir_fmt_ctx, filename, NULL, NULL)) < 0) { + av_log(NULL, AV_LOG_ERROR, "[LibAvProc] Cannot open IR file\n"); + return ret; + } + + if ((ret = avformat_find_stream_info(ir_fmt_ctx, NULL)) < 0) { + av_log(NULL, AV_LOG_ERROR, "[LibAvProc] Cannot find stream information in IR file\n"); + return ret; + } + + /* select the audio stream */ + ret = av_find_best_stream(ir_fmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, &dec, 0); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "[LibAvProc] Cannot find an audio stream in the IR file\n"); + return ret; + } + + stream = ir_fmt_ctx->streams[ret]; + + /* create decoding context */ + dec_ctx = avcodec_alloc_context3(dec); + if (!dec_ctx) + return AVERROR(ENOMEM); + avcodec_parameters_to_context(dec_ctx, stream->codecpar); + + /* init the audio decoder */ + if ((ret = avcodec_open2(dec_ctx, dec, NULL)) < 0) { + av_log(NULL, AV_LOG_ERROR, "[LibAvProc] Cannot open IR audio decoder\n"); + return ret; + } + + stream->codecpar->codec_id = dec_ctx->codec_id; + stream->codecpar->sample_rate = dec_ctx->sample_rate; + stream->codecpar->format = dec_ctx->sample_fmt; +#ifdef FFMPEG_LEGACY_API + stream->codecpar->channels = dec_ctx->channels; + stream->codecpar->channel_layout = dec_ctx->channel_layout; +#else + av_channel_layout_copy(&stream->codecpar->ch_layout, &dec_ctx->ch_layout); +#endif + + avcodec_free_context(&dec_ctx); + + return 0; +} + +static int open_output_file(const char *filename) +{ + AVStream *out_stream; + const AVCodec *encoder; + int ret; + + avformat_alloc_output_context2(&ofmt_ctx, NULL, NULL, filename); + if (!ofmt_ctx) { + av_log(NULL, AV_LOG_ERROR, "[LibAvProc] Could not create output context\n"); + return AVERROR_UNKNOWN; + } + out_stream = avformat_new_stream(ofmt_ctx, NULL); + if (!out_stream) { + av_log(NULL, AV_LOG_ERROR, "[LibAvProc] Failed allocating output stream\n"); + return AVERROR_UNKNOWN; + } + + /* find encoder - force PCM 16-bit little-endian as requested */ + encoder = avcodec_find_encoder(AV_CODEC_ID_PCM_S16LE); + if (!encoder) { + av_log(NULL, AV_LOG_FATAL, "Necessary encoder not found\n"); + return AVERROR_INVALIDDATA; + } + + enc_ctx = avcodec_alloc_context3(encoder); + if (!enc_ctx) { + av_log(NULL, AV_LOG_FATAL, "Failed to allocate the encoder context\n"); + return AVERROR(ENOMEM); + } + + /* Set output parameters to target format: 44.1kHz, 16-bit, stereo unless IR is mono */ + enc_ctx->sample_rate = 44100; + + /* Set channel layout based on IR format */ +#ifdef FFMPEG_LEGACY_API + int ir_channels = ir_fmt_ctx->streams[0]->codecpar->channels; + if (ir_channels == 0) { + ir_channels = ir_fmt_ctx->streams[0]->codecpar->channels; + } + + if (ir_channels == 1) { + enc_ctx->channels = 1; + enc_ctx->channel_layout = AV_CH_LAYOUT_MONO; + } else { + enc_ctx->channels = 2; + enc_ctx->channel_layout = AV_CH_LAYOUT_STEREO; + } +#else + int ir_channels = ir_fmt_ctx->streams[0]->codecpar->ch_layout.nb_channels; + if (ir_channels == 0) { + ir_channels = ir_fmt_ctx->streams[0]->codecpar->channels; + } + + if (ir_channels == 1) { + av_channel_layout_default(&enc_ctx->ch_layout, 1); /* Mono */ + } else { + av_channel_layout_default(&enc_ctx->ch_layout, 2); /* Stereo */ + } +#endif + + /* Use S16 format to match encoder */ + enc_ctx->sample_fmt = AV_SAMPLE_FMT_S16; + + /* Third parameter can be used to pass settings to encoder */ + ret = avcodec_open2(enc_ctx, encoder, NULL); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "[LibAvProc] Cannot open video encoder for stream\n"); + return ret; + } + ret = avcodec_parameters_from_context(out_stream->codecpar, enc_ctx); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "[LibAvProc] Failed to copy encoder parameters to output stream\n"); + return ret; + } + + out_stream->time_base = enc_ctx->time_base; + + av_dump_format(ofmt_ctx, 0, filename, 1); + + /* open the output file, if needed */ + if (!(ofmt_ctx->oformat->flags & AVFMT_NOFILE)) { + ret = avio_open(&ofmt_ctx->pb, filename, AVIO_FLAG_WRITE); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "[LibAvProc] Could not open output file '%s'", filename); + return ret; + } + } + + /* init muxer, write output file header */ + ret = avformat_write_header(ofmt_ctx, NULL); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "[LibAvProc] Error occurred when opening output file\n"); + return ret; + } + + return 0; +} + +static int init_filters(int ir_wet, int ir_pad) +{ + char args[512]; + char ir_args[512]; + int ret = 0; + const AVFilter *abuffer, *abuffersink; + AVFilterInOut *outputs = avfilter_inout_alloc(); + AVFilterInOut *inputs = avfilter_inout_alloc(); + AVRational time_base = ifmt_ctx->streams[0]->time_base; + AVRational ir_time_base = ir_fmt_ctx->streams[0]->time_base; + + /* Determine the target format - always 44.1kHz, stereo unless IR is mono */ + char target_layout[64]; + int target_sample_rate = 44100; + + /* Determine output channel layout based on IR format */ +#ifdef FFMPEG_LEGACY_API + int ir_channels = ir_fmt_ctx->streams[0]->codecpar->channels; + if (ir_channels == 0) { + ir_channels = ir_fmt_ctx->streams[0]->codecpar->channels; + } +#else + int ir_channels = ir_fmt_ctx->streams[0]->codecpar->ch_layout.nb_channels; + if (ir_channels == 0) { + ir_channels = ir_fmt_ctx->streams[0]->codecpar->channels; + } +#endif + + if (ir_channels == 1) { + strcpy(target_layout, "mono"); /* If IR is mono, output mono for simplicity */ + } else { + strcpy(target_layout, "stereo"); /* Otherwise, output stereo */ + } + + filter_graph = avfilter_graph_alloc(); + if (!outputs || !inputs || !filter_graph) { + ret = AVERROR(ENOMEM); + goto end; + } + + /* buffer audio source: the decoded frames from the decoder will be inserted here. */ + abuffer = avfilter_get_by_name("abuffer"); + if (!abuffer) { + av_log(NULL, AV_LOG_ERROR, "[LibAvProc] Could not find the abuffer filter.\n"); + ret = AVERROR_FILTER_NOT_FOUND; + goto end; + } + + buffersrc_ctx = avfilter_graph_alloc_filter(filter_graph, abuffer, "in"); + if (!buffersrc_ctx) { + av_log(NULL, AV_LOG_ERROR, "[LibAvProc] Could not allocate the abuffer instance.\n"); + ret = AVERROR(ENOMEM); + goto end; + } + + /* Set the filter options through the AVOptions API. */ + char ch_layout_str[64]; + // Use the actual channel layout from the input file +#ifdef FFMPEG_LEGACY_API + if (ifmt_ctx->streams[0]->codecpar->channels == 1) { + strcpy(ch_layout_str, "mono"); + } else if (ifmt_ctx->streams[0]->codecpar->channels == 2) { + strcpy(ch_layout_str, "stereo"); + } else { + snprintf(ch_layout_str, sizeof(ch_layout_str), "%dc", ifmt_ctx->streams[0]->codecpar->channels); + } +#else + if (ifmt_ctx->streams[0]->codecpar->ch_layout.nb_channels == 0) { + // If channel layout is unknown, default based on channel count + if (ifmt_ctx->streams[0]->codecpar->channels == 1) { + strcpy(ch_layout_str, "mono"); + } else if (ifmt_ctx->streams[0]->codecpar->channels == 2) { + strcpy(ch_layout_str, "stereo"); + } else { + snprintf(ch_layout_str, sizeof(ch_layout_str), "%dc", ifmt_ctx->streams[0]->codecpar->channels); + } + } else { + // Use the input stream's channel layout + av_channel_layout_describe(&ifmt_ctx->streams[0]->codecpar->ch_layout, + ch_layout_str, sizeof(ch_layout_str)); + } +#endif + snprintf(args, sizeof(args), + "time_base=%d/%d:sample_rate=%d:sample_fmt=%s:channel_layout=%s", + time_base.num, time_base.den, ifmt_ctx->streams[0]->codecpar->sample_rate, + av_get_sample_fmt_name(ifmt_ctx->streams[0]->codecpar->format), + ch_layout_str); + ret = avfilter_init_str(buffersrc_ctx, args); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "[LibAvProc] Could not initialize the abuffer filter.\n"); + goto end; + } + + /* buffer audio source for IR: the decoded IR frames will be inserted here. */ + ir_buffersrc_ctx = avfilter_graph_alloc_filter(filter_graph, abuffer, "ir_in"); + if (!ir_buffersrc_ctx) { + av_log(NULL, AV_LOG_ERROR, "[LibAvProc] Could not allocate the IR abuffer instance.\n"); + ret = AVERROR(ENOMEM); + goto end; + } + + char ir_ch_layout_str[64]; + // Use the actual channel layout from the IR file +#ifdef FFMPEG_LEGACY_API + if (ir_fmt_ctx->streams[0]->codecpar->channels == 1) { + strcpy(ir_ch_layout_str, "mono"); + } else if (ir_fmt_ctx->streams[0]->codecpar->channels == 2) { + strcpy(ir_ch_layout_str, "stereo"); + } else { + snprintf(ir_ch_layout_str, sizeof(ir_ch_layout_str), "%dc", ir_fmt_ctx->streams[0]->codecpar->channels); + } +#else + if (ir_fmt_ctx->streams[0]->codecpar->ch_layout.nb_channels == 0) { + // If channel layout is unknown, default based on channel count + if (ir_fmt_ctx->streams[0]->codecpar->channels == 1) { + strcpy(ir_ch_layout_str, "mono"); + } else if (ir_fmt_ctx->streams[0]->codecpar->channels == 2) { + strcpy(ir_ch_layout_str, "stereo"); + } else { + snprintf(ir_ch_layout_str, sizeof(ir_ch_layout_str), "%dc", ir_fmt_ctx->streams[0]->codecpar->channels); + } + } else { + // Use the IR stream's channel layout + av_channel_layout_describe(&ir_fmt_ctx->streams[0]->codecpar->ch_layout, + ir_ch_layout_str, sizeof(ir_ch_layout_str)); + } +#endif + snprintf(ir_args, sizeof(ir_args), + "time_base=%d/%d:sample_rate=%d:sample_fmt=%s:channel_layout=%s", + ir_time_base.num, ir_time_base.den, ir_fmt_ctx->streams[0]->codecpar->sample_rate, + av_get_sample_fmt_name(ir_fmt_ctx->streams[0]->codecpar->format), + ir_ch_layout_str); + ret = avfilter_init_str(ir_buffersrc_ctx, ir_args); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "[LibAvProc] Could not initialize the IR abuffer filter.\n"); + goto end; + } + + /* buffer audio sink: to terminate the filter chain. */ + abuffersink = avfilter_get_by_name("abuffersink"); + if (!abuffersink) { + av_log(NULL, AV_LOG_ERROR, "[LibAvProc] Could not find the abuffersink filter.\n"); + ret = AVERROR_FILTER_NOT_FOUND; + goto end; + } + + buffersink_ctx = avfilter_graph_alloc_filter(filter_graph, abuffersink, "out"); + if (!buffersink_ctx) { + av_log(NULL, AV_LOG_ERROR, "[LibAvProc] Could not allocate the abuffersink instance.\n"); + ret = AVERROR(ENOMEM); + goto end; + } + + /* Configure the buffersink to accept the expected format */ + static const enum AVSampleFormat out_sample_fmts[] = { AV_SAMPLE_FMT_S16, -1 }; + ret = av_opt_set_int_list(buffersink_ctx, "sample_fmts", out_sample_fmts, -1, AV_OPT_SEARCH_CHILDREN); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "[LibAvProc] Cannot set output sample format\n"); + goto end; + } + + /* This filter takes no options. */ + ret = avfilter_init_str(buffersink_ctx, NULL); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "[LibAvProc] Could not initialize the abuffersink instance.\n"); + goto end; + } + + /* + * Set the endpoints for the filter graph. The filter_graph will + * be linked to the graph described by filters_descr. + */ + + /* + * The buffer source output must be connected to the input pad of + * the first filter described by filters_descr; since the first + * filter input label is "in", with the pointer, we leave this + * pad open. + */ + outputs->name = av_strdup("in"); + outputs->filter_ctx = buffersrc_ctx; + outputs->pad_idx = 0; + outputs->next = avfilter_inout_alloc(); + + outputs->next->name = av_strdup("ir_in"); + outputs->next->filter_ctx = ir_buffersrc_ctx; + outputs->next->pad_idx = 0; + outputs->next->next = NULL; + + /* + * The buffer sink input must be connected to the output pad of + * the last filter described by filters_descr; since the last + * filter output label is "out", with the pointer, we leave this + * pad open. + */ + inputs->name = av_strdup("out"); + inputs->filter_ctx = buffersink_ctx; + inputs->pad_idx = 0; + inputs->next = NULL; + + /* Apply convolution reverb using afir filter with format normalization */ + char filters_descr[512]; + /* Scale ir_wet from 0-100 to 0-10 for afir filter */ + int afir_wet = (ir_wet * 10) / 100; + if (afir_wet > 10) afir_wet = 10; + if (afir_wet < 0) afir_wet = 0; + + snprintf(filters_descr, sizeof(filters_descr), + "[ir_in]aresample=%d,aformat=sample_fmts=fltp:channel_layouts=%s[" + "ir_norm];" + "[in]aresample=%d,aformat=sample_fmts=fltp:channel_layouts=%s," + "asplit[in_1][in_2];" + "[in_1][ir_norm]afir=dry=10:wet=%d[reverb];" + "[in_2][reverb]amix=inputs=2:weights=1 " + "1,volume=2.5,aformat=sample_fmts=s16:channel_layouts=%s[out]", + target_sample_rate, target_layout, target_sample_rate, + target_layout, afir_wet, target_layout); + + av_log(NULL, AV_LOG_INFO, "Filter graph: %s\n", filters_descr); + + if ((ret = avfilter_graph_parse_ptr(filter_graph, filters_descr, + &inputs, &outputs, NULL)) < 0) + goto end; + + if ((ret = avfilter_graph_config(filter_graph, NULL)) < 0) + goto end; + +end: + avfilter_inout_free(&inputs); + avfilter_inout_free(&outputs); + + return ret; +} + +static int encode_write_frame(AVFrame *filt_frame, unsigned int stream_index, int *got_frame) { + int ret; + int got_frame_local; + AVPacket *enc_pkt; + + if (!got_frame) + got_frame = &got_frame_local; + + if (!enc_ctx) { + return AVERROR(EINVAL); + } + + enc_pkt = av_packet_alloc(); + if (!enc_pkt) { + return AVERROR(ENOMEM); + } + + /* encode filtered frame */ + ret = avcodec_send_frame(enc_ctx, filt_frame); + if (ret < 0) + goto end; + + while (ret >= 0) { + ret = avcodec_receive_packet(enc_ctx, enc_pkt); + if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { + ret = 0; /* These are not errors */ + break; + } else if (ret < 0) + goto end; + + /* prepare packet for muxing */ + enc_pkt->stream_index = stream_index; + av_packet_rescale_ts(enc_pkt, + enc_ctx->time_base, + ofmt_ctx->streams[stream_index]->time_base); + + /* mux encoded frame */ + ret = av_interleaved_write_frame(ofmt_ctx, enc_pkt); + if (ret < 0) + break; + } + +end: + av_packet_free(&enc_pkt); + return ret; +} + +static int filter_encode_write_frame(AVFrame *frame, AVFrame *ir_frame, unsigned int stream_index) +{ + int ret; + AVFrame *filt_frame; + + /* Only add frames if they are provided */ + if (frame) { + ret = av_buffersrc_add_frame_flags(buffersrc_ctx, frame, AV_BUFFERSRC_FLAG_KEEP_REF); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "[LibAvProc] Error while feeding the input filtergraph\n"); + return ret; + } + } + + if (ir_frame) { + ret = av_buffersrc_add_frame_flags(ir_buffersrc_ctx, ir_frame, AV_BUFFERSRC_FLAG_KEEP_REF); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "[LibAvProc] Error while feeding the IR filtergraph\n"); + return ret; + } + } + + /* Always try to pull filtered frames from the filtergraph */ + while (1) { + filt_frame = av_frame_alloc(); + if (!filt_frame) { + ret = AVERROR(ENOMEM); + break; + } + ret = av_buffersink_get_frame(buffersink_ctx, filt_frame); + if (ret < 0) { + /* if no more frames for output - returns AVERROR(EAGAIN) + * if flushed and no more frames for output - returns AVERROR_EOF + * rewrite retcode to 0 to show it as normal procedure completion + */ + if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) + ret = 0; + av_frame_free(&filt_frame); + break; + } + + printf("Got filtered frame: %d samples, %d channels, %d Hz\n", +#ifdef FFMPEG_LEGACY_API + filt_frame->nb_samples, filt_frame->channels, + filt_frame->sample_rate); +#else + filt_frame->nb_samples, filt_frame->ch_layout.nb_channels, + filt_frame->sample_rate); +#endif + + ret = encode_write_frame(filt_frame, stream_index, NULL); + av_frame_free(&filt_frame); + if (ret < 0) + break; + } + + return ret; +} + +int encode(const char *fi, const char *ir, const char *fo, int irWet, + int irPad) { + int ret; + AVPacket *packet = NULL, *ir_packet = NULL; + AVFrame *frame = NULL, *ir_frame = NULL; + AVCodecContext *dec_ctx = NULL, *ir_dec_ctx = NULL; + int ir_loaded = 0; + + if ((ret = open_input_file(fi)) < 0) + goto end; + if ((ret = open_ir_file(ir)) < 0) + goto end; + if ((ret = open_output_file(fo)) < 0) + goto end; + if ((ret = init_filters(irWet, irPad)) < 0) + goto end; + + packet = av_packet_alloc(); + ir_packet = av_packet_alloc(); + frame = av_frame_alloc(); + ir_frame = av_frame_alloc(); + if (!packet || !ir_packet || !frame || !ir_frame) { + fprintf(stderr, "Could not allocate frame or packet\n"); + ret = AVERROR(ENOMEM); + goto end; + } + + /* Set up decoder contexts */ + const AVCodec *dec = + avcodec_find_decoder(ifmt_ctx->streams[0]->codecpar->codec_id); + const AVCodec *ir_dec = + avcodec_find_decoder(ir_fmt_ctx->streams[0]->codecpar->codec_id); + + if (!dec || !ir_dec) { + fprintf(stderr, "Could not find decoders (dec=%p, ir_dec=%p)\n", dec, ir_dec); + ret = AVERROR_DECODER_NOT_FOUND; + goto end; + } + + dec_ctx = avcodec_alloc_context3(dec); + ir_dec_ctx = avcodec_alloc_context3(ir_dec); + + if (!dec_ctx || !ir_dec_ctx) { + fprintf(stderr, "Could not allocate decoder contexts\n"); + ret = AVERROR(ENOMEM); + goto end; + } + + avcodec_parameters_to_context(dec_ctx, ifmt_ctx->streams[0]->codecpar); + avcodec_parameters_to_context(ir_dec_ctx, ir_fmt_ctx->streams[0]->codecpar); + + ret = avcodec_open2(dec_ctx, dec, NULL); + if (ret < 0) { + fprintf(stderr, "Could not open decoder: %s\n", av_err2str(ret)); + goto end; + } + + ret = avcodec_open2(ir_dec_ctx, ir_dec, NULL); + if (ret < 0) { + fprintf(stderr, "Could not open IR decoder: %s\n", av_err2str(ret)); + goto end; + } + + /* Process both streams - load ALL IR data first, then process main audio */ + int ir_eof = 0, main_eof = 0; + + /* First load all IR data */ + while (!ir_eof && av_read_frame(ir_fmt_ctx, ir_packet) >= 0) { + if (ir_packet->stream_index == 0) { + ret = avcodec_send_packet(ir_dec_ctx, ir_packet); + while (ret >= 0) { + ret = avcodec_receive_frame(ir_dec_ctx, ir_frame); + if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { + break; + } else if (ret < 0) { + fprintf(stderr, "Error while decoding IR\n"); + goto end; + } + + ret = av_buffersrc_add_frame_flags(ir_buffersrc_ctx, ir_frame, AV_BUFFERSRC_FLAG_KEEP_REF); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "[LibAvProc] Error while feeding the IR filtergraph\n"); + goto end; + } + ir_loaded = 1; + } + } + av_packet_unref(ir_packet); + } + + /* Signal end of IR stream */ + ret = av_buffersrc_add_frame_flags(ir_buffersrc_ctx, NULL, 0); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "[LibAvProc] Error closing IR filtergraph\n"); + goto end; + } + + if (!ir_loaded) { + fprintf(stderr, "No impulse response data loaded\n"); + goto end; + } + + /* Now process the main audio */ + while (!main_eof && av_read_frame(ifmt_ctx, packet) >= 0) { + if (packet->stream_index == 0) { + ret = avcodec_send_packet(dec_ctx, packet); + while (ret >= 0) { + ret = avcodec_receive_frame(dec_ctx, frame); + if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { + break; + } else if (ret < 0) { + fprintf(stderr, "Error while decoding\n"); + goto end; + } + + ret = av_buffersrc_add_frame_flags(buffersrc_ctx, frame, AV_BUFFERSRC_FLAG_KEEP_REF); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "[LibAvProc] Error while feeding the input filtergraph\n"); + goto end; + } + /* Pull filtered frames immediately */ + while (1) { + AVFrame *filt_frame = av_frame_alloc(); + ret = av_buffersink_get_frame(buffersink_ctx, filt_frame); + if (ret == AVERROR(EAGAIN)) { + av_frame_free(&filt_frame); + break; /* Need more input */ + } else if (ret == AVERROR_EOF) { + av_frame_free(&filt_frame); + break; /* No more frames */ + } else if (ret < 0) { + printf("Error getting frame from filter: %s\n", av_err2str(ret)); + av_frame_free(&filt_frame); + goto end; + } + + ret = encode_write_frame(filt_frame, 0, NULL); + av_frame_free(&filt_frame); + if (ret < 0) { + goto end; + } + } + } + } + av_packet_unref(packet); + } + + /* flush the filter graph by sending EOF to input sources */ + ret = av_buffersrc_add_frame_flags(buffersrc_ctx, NULL, 0); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "[LibAvProc] Error while closing the input filtergraph\n"); + goto end; + } + + /* Pull any remaining frames from the filter graph */ + ret = filter_encode_write_frame(NULL, NULL, 0); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "[LibAvProc] Flushing filter failed\n"); + goto end; + } + + /* flush the encoder */ + ret = avcodec_send_frame(enc_ctx, NULL); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "[LibAvProc] Error sending NULL frame to encoder\n"); + goto end; + } + + while (ret >= 0) { + AVPacket *enc_pkt = av_packet_alloc(); + ret = avcodec_receive_packet(enc_ctx, enc_pkt); + if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) { + av_packet_free(&enc_pkt); + break; + } else if (ret < 0) { + av_packet_free(&enc_pkt); + av_log(NULL, AV_LOG_ERROR, "[LibAvProc] Error during encoder flush\n"); + goto end; + } + + enc_pkt->stream_index = 0; + av_packet_rescale_ts(enc_pkt, enc_ctx->time_base, ofmt_ctx->streams[0]->time_base); + ret = av_interleaved_write_frame(ofmt_ctx, enc_pkt); + av_packet_free(&enc_pkt); + if (ret < 0) { + av_log(NULL, AV_LOG_ERROR, "[LibAvProc] Error writing final packet\n"); + goto end; + } + } + + av_write_trailer(ofmt_ctx); +end: + if (dec_ctx) { + avcodec_free_context(&dec_ctx); + } + if (ir_dec_ctx) { + avcodec_free_context(&ir_dec_ctx); + } + av_frame_free(&frame); + av_frame_free(&ir_frame); + av_packet_free(&packet); + av_packet_free(&ir_packet); + avfilter_graph_free(&filter_graph); + avformat_close_input(&ifmt_ctx); + avformat_close_input(&ir_fmt_ctx); + + if (enc_ctx) { + avcodec_free_context(&enc_ctx); + } + + if (ofmt_ctx && !(ofmt_ctx->oformat->flags & AVFMT_NOFILE)) { + avio_closep(&ofmt_ctx->pb); + } + avformat_free_context(ofmt_ctx); + + if (ret < 0 && ret != AVERROR_EOF) { + fprintf(stderr, "Error occurred: %s\n", av_err2str(ret)); + return(1); + } + return 0; +} diff --git a/sources/Application/FX/LibavProcessor.h b/sources/Application/FX/LibavProcessor.h new file mode 100644 index 00000000..10b24fd9 --- /dev/null +++ b/sources/Application/FX/LibavProcessor.h @@ -0,0 +1,14 @@ +#ifndef LIBAV_PROC_H +#define LIBAV_PROC_H + +#ifdef __cplusplus +extern "C" { +#endif + +int encode(const char *fi, const char *ir, const char *fo, int irWet, int irPad); + +#ifdef __cplusplus +} +#endif + +#endif \ No newline at end of file diff --git a/sources/Application/Instruments/SampleInstrument.cpp b/sources/Application/Instruments/SampleInstrument.cpp index c1b4f6b6..6d08e834 100644 --- a/sources/Application/Instruments/SampleInstrument.cpp +++ b/sources/Application/Instruments/SampleInstrument.cpp @@ -1410,6 +1410,17 @@ const char *SampleInstrument::GetName() { return src; } +/* + Get full name, don't shorten it for + display width limitation purposes +*/ +const char *SampleInstrument::GetFullName() { + Variable *v = FindVariable(SIP_SAMPLE); + const char *src = v->GetString(); + + return src; +} + void SampleInstrument::Purge() { IteratorPtr it(GetIterator()) ; for (it->Begin();!it->IsDone();it->Next()) { diff --git a/sources/Application/Instruments/SampleInstrument.h b/sources/Application/Instruments/SampleInstrument.h index ebf47e28..ea0e2166 100644 --- a/sources/Application/Instruments/SampleInstrument.h +++ b/sources/Application/Instruments/SampleInstrument.h @@ -92,16 +92,16 @@ class SampleInstrument: public I_Instrument,I_Observer { int GetLoopEnd(); virtual const char *GetName() ; // returns sample name until real // namer is implemented - - static void EnableDownsamplingLegacy(); + virtual const char *GetFullName(); + static void EnableDownsamplingLegacy(); -protected: - void updateInstrumentData(bool search) ; - void doTickUpdate(int channel) ; - void doKRateUpdate(int channel) ; - void updateFeedback(renderParams *rp) ; + protected: + void updateInstrumentData(bool search); + void doTickUpdate(int channel); + void doKRateUpdate(int channel); + void updateFeedback(renderParams *rp); -private: + private: SoundSource *source_ ; struct renderParams renderParams_[SONG_CHANNEL_COUNT] ; bool running_ ; diff --git a/sources/Application/Model/Project.h b/sources/Application/Model/Project.h index f4bb4410..33829259 100644 --- a/sources/Application/Model/Project.h +++ b/sources/Application/Model/Project.h @@ -19,8 +19,8 @@ #define VAR_SCALE MAKE_FOURCC('S', 'C', 'A', 'L') #define PROJECT_NUMBER "1" -#define PROJECT_RELEASE "5" -#define BUILD_COUNT "0-bacon3" +#define PROJECT_RELEASE "6" +#define BUILD_COUNT "0-bacon0" #define MAX_TAP 3