From 2e45deb8d3acacc2388fd4164b98b712c27cd90b Mon Sep 17 00:00:00 2001 From: Christopher Snowhill Date: Fri, 24 Jun 2022 02:46:23 -0700 Subject: [PATCH] [Audio Output] Resample unsupported sample rates These rates are too high for Apple's output routines, for some reason. Signed-off-by: Christopher Snowhill --- .gitmodules | 3 + Audio/AudioPlayer.m | 7 +- Audio/CogAudio.xcodeproj/project.pbxproj | 154 ++++++++++++++++ Audio/Output/OutputAVFoundation.h | 4 + Audio/Output/OutputAVFoundation.m | 222 ++++++++++++++++------- Audio/ThirdParty/r8brain-free-src | 1 + Audio/ThirdParty/r8bstate.cpp | 31 ++++ Audio/ThirdParty/r8bstate.h | 33 ++++ Audio/ThirdParty/r8bstate.hpp | 164 +++++++++++++++++ 9 files changed, 551 insertions(+), 68 deletions(-) create mode 160000 Audio/ThirdParty/r8brain-free-src create mode 100644 Audio/ThirdParty/r8bstate.cpp create mode 100644 Audio/ThirdParty/r8bstate.h create mode 100644 Audio/ThirdParty/r8bstate.hpp diff --git a/.gitmodules b/.gitmodules index a12986d3c..3adc906a4 100644 --- a/.gitmodules +++ b/.gitmodules @@ -19,3 +19,6 @@ [submodule "Frameworks/libsidplayfp/sidplayfp"] path = Frameworks/libsidplayfp/sidplayfp url = https://github.com/kode54/libsidplayfp.git +[submodule "Audio/ThirdParty/r8brain-free-src"] + path = Audio/ThirdParty/r8brain-free-src + url = https://github.com/kode54/r8brain-free-src diff --git a/Audio/AudioPlayer.m b/Audio/AudioPlayer.m index 0aa071bf1..c6ea5d61d 100644 --- a/Audio/AudioPlayer.m +++ b/Audio/AudioPlayer.m @@ -452,10 +452,12 @@ [self notifyPlaybackStopped:nil]; - return NO; + return YES; } - return YES; + [output setEndOfStream:NO]; + + return NO; } - (void)endOfInputPlayed { @@ -465,7 +467,6 @@ // - the buffer chain for the next entry is the first item in chainQueue [self notifyStreamChanged:[bufferChain userInfo]]; - [output setEndOfStream:NO]; } - (BOOL)chainQueueHasTracks { diff --git a/Audio/CogAudio.xcodeproj/project.pbxproj b/Audio/CogAudio.xcodeproj/project.pbxproj index 6eb16c495..47dd4d9a1 100644 --- a/Audio/CogAudio.xcodeproj/project.pbxproj +++ b/Audio/CogAudio.xcodeproj/project.pbxproj @@ -41,6 +41,35 @@ 17F94DD50B8D0F7000A34E87 /* PluginController.h in Headers */ = {isa = PBXBuildFile; fileRef = 17F94DD30B8D0F7000A34E87 /* PluginController.h */; settings = {ATTRIBUTES = (Public, ); }; }; 17F94DD60B8D0F7000A34E87 /* PluginController.mm in Sources */ = {isa = PBXBuildFile; fileRef = 17F94DD40B8D0F7000A34E87 /* PluginController.mm */; }; 17F94DDD0B8D101100A34E87 /* Plugin.h in Headers */ = {isa = PBXBuildFile; fileRef = 17F94DDC0B8D101100A34E87 /* Plugin.h */; settings = {ATTRIBUTES = (Public, ); }; }; + 831A4FDC2865A7DC0049CFE4 /* CDSPProcessor.h in Headers */ = {isa = PBXBuildFile; fileRef = 831A4F9D2865A7DC0049CFE4 /* CDSPProcessor.h */; }; + 831A4FDD2865A7DC0049CFE4 /* CDSPRealFFT.h in Headers */ = {isa = PBXBuildFile; fileRef = 831A4F9E2865A7DC0049CFE4 /* CDSPRealFFT.h */; }; + 831A4FDE2865A7DC0049CFE4 /* pffft_double.h in Headers */ = {isa = PBXBuildFile; fileRef = 831A4FA02865A7DC0049CFE4 /* pffft_double.h */; }; + 831A4FDF2865A7DC0049CFE4 /* pf_neon_double_from_avx.h in Headers */ = {isa = PBXBuildFile; fileRef = 831A4FA22865A7DC0049CFE4 /* pf_neon_double_from_avx.h */; }; + 831A4FE02865A7DC0049CFE4 /* pf_double.h in Headers */ = {isa = PBXBuildFile; fileRef = 831A4FA32865A7DC0049CFE4 /* pf_double.h */; }; + 831A4FE12865A7DC0049CFE4 /* pf_neon_double.h in Headers */ = {isa = PBXBuildFile; fileRef = 831A4FA42865A7DC0049CFE4 /* pf_neon_double.h */; }; + 831A4FE22865A7DC0049CFE4 /* pf_sse2_double.h in Headers */ = {isa = PBXBuildFile; fileRef = 831A4FA52865A7DC0049CFE4 /* pf_sse2_double.h */; }; + 831A4FE32865A7DC0049CFE4 /* pf_avx_double.h in Headers */ = {isa = PBXBuildFile; fileRef = 831A4FA62865A7DC0049CFE4 /* pf_avx_double.h */; }; + 831A4FE42865A7DC0049CFE4 /* pf_scalar_double.h in Headers */ = {isa = PBXBuildFile; fileRef = 831A4FA72865A7DC0049CFE4 /* pf_scalar_double.h */; }; + 831A4FE52865A7DC0049CFE4 /* pffft_priv_impl.h in Headers */ = {isa = PBXBuildFile; fileRef = 831A4FA82865A7DC0049CFE4 /* pffft_priv_impl.h */; }; + 831A4FE62865A7DC0049CFE4 /* pffft_double.c in Sources */ = {isa = PBXBuildFile; fileRef = 831A4FA92865A7DC0049CFE4 /* pffft_double.c */; }; + 831A4FF22865A7DC0049CFE4 /* CDSPHBUpsampler.inc in Sources */ = {isa = PBXBuildFile; fileRef = 831A4FB72865A7DC0049CFE4 /* CDSPHBUpsampler.inc */; }; + 831A4FF32865A7DC0049CFE4 /* r8butil.h in Headers */ = {isa = PBXBuildFile; fileRef = 831A4FB82865A7DC0049CFE4 /* r8butil.h */; }; + 831A4FF52865A7DC0049CFE4 /* r8bbase.h in Headers */ = {isa = PBXBuildFile; fileRef = 831A4FBA2865A7DC0049CFE4 /* r8bbase.h */; }; + 831A4FFE2865A7DC0049CFE4 /* CDSPSincFilterGen.h in Headers */ = {isa = PBXBuildFile; fileRef = 831A4FC42865A7DC0049CFE4 /* CDSPSincFilterGen.h */; }; + 831A50072865A7DC0049CFE4 /* LICENSE in Resources */ = {isa = PBXBuildFile; fileRef = 831A4FD02865A7DC0049CFE4 /* LICENSE */; }; + 831A50082865A7DC0049CFE4 /* CDSPResampler.h in Headers */ = {isa = PBXBuildFile; fileRef = 831A4FD12865A7DC0049CFE4 /* CDSPResampler.h */; }; + 831A50092865A7DC0049CFE4 /* CDSPHBUpsampler.h in Headers */ = {isa = PBXBuildFile; fileRef = 831A4FD22865A7DC0049CFE4 /* CDSPHBUpsampler.h */; }; + 831A500B2865A7DC0049CFE4 /* CDSPBlockConvolver.h in Headers */ = {isa = PBXBuildFile; fileRef = 831A4FD42865A7DC0049CFE4 /* CDSPBlockConvolver.h */; }; + 831A500C2865A7DC0049CFE4 /* fft4g.h in Headers */ = {isa = PBXBuildFile; fileRef = 831A4FD52865A7DC0049CFE4 /* fft4g.h */; }; + 831A500D2865A7DC0049CFE4 /* CDSPHBDownsampler.h in Headers */ = {isa = PBXBuildFile; fileRef = 831A4FD62865A7DC0049CFE4 /* CDSPHBDownsampler.h */; }; + 831A500E2865A7DC0049CFE4 /* r8bconf.h in Headers */ = {isa = PBXBuildFile; fileRef = 831A4FD72865A7DC0049CFE4 /* r8bconf.h */; }; + 831A500F2865A7DC0049CFE4 /* CDSPFracInterpolator.h in Headers */ = {isa = PBXBuildFile; fileRef = 831A4FD82865A7DC0049CFE4 /* CDSPFracInterpolator.h */; }; + 831A50102865A7DC0049CFE4 /* CDSPFIRFilter.h in Headers */ = {isa = PBXBuildFile; fileRef = 831A4FD92865A7DC0049CFE4 /* CDSPFIRFilter.h */; }; + 831A50112865A7DC0049CFE4 /* r8bbase.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 831A4FDA2865A7DC0049CFE4 /* r8bbase.cpp */; }; + 831A50122865A7DC0049CFE4 /* pffft.h in Headers */ = {isa = PBXBuildFile; fileRef = 831A4FDB2865A7DC0049CFE4 /* pffft.h */; }; + 831A50142865A7FD0049CFE4 /* r8bstate.hpp in Headers */ = {isa = PBXBuildFile; fileRef = 831A50132865A7FD0049CFE4 /* r8bstate.hpp */; }; + 831A50162865A8800049CFE4 /* r8bstate.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 831A50152865A8800049CFE4 /* r8bstate.cpp */; }; + 831A50182865A8B30049CFE4 /* r8bstate.h in Headers */ = {isa = PBXBuildFile; fileRef = 831A50172865A8B30049CFE4 /* r8bstate.h */; }; 8328995327CB511000D7F028 /* RedundantPlaylistDataStore.m in Sources */ = {isa = PBXBuildFile; fileRef = 8328995127CB510F00D7F028 /* RedundantPlaylistDataStore.m */; }; 8328995427CB511000D7F028 /* RedundantPlaylistDataStore.h in Headers */ = {isa = PBXBuildFile; fileRef = 8328995227CB511000D7F028 /* RedundantPlaylistDataStore.h */; }; 8328995727CB51B700D7F028 /* SHA256Digest.h in Headers */ = {isa = PBXBuildFile; fileRef = 8328995527CB51B700D7F028 /* SHA256Digest.h */; }; @@ -140,6 +169,35 @@ 17F94DD40B8D0F7000A34E87 /* PluginController.mm */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.cpp.objcpp; path = PluginController.mm; sourceTree = ""; }; 17F94DDC0B8D101100A34E87 /* Plugin.h */ = {isa = PBXFileReference; fileEncoding = 30; lastKnownFileType = sourcecode.c.h; path = Plugin.h; sourceTree = ""; }; 32DBCF5E0370ADEE00C91783 /* CogAudio_Prefix.pch */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CogAudio_Prefix.pch; sourceTree = ""; }; + 831A4F9D2865A7DC0049CFE4 /* CDSPProcessor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CDSPProcessor.h; sourceTree = ""; }; + 831A4F9E2865A7DC0049CFE4 /* CDSPRealFFT.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CDSPRealFFT.h; sourceTree = ""; }; + 831A4FA02865A7DC0049CFE4 /* pffft_double.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = pffft_double.h; sourceTree = ""; }; + 831A4FA22865A7DC0049CFE4 /* pf_neon_double_from_avx.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = pf_neon_double_from_avx.h; sourceTree = ""; }; + 831A4FA32865A7DC0049CFE4 /* pf_double.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = pf_double.h; sourceTree = ""; }; + 831A4FA42865A7DC0049CFE4 /* pf_neon_double.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = pf_neon_double.h; sourceTree = ""; }; + 831A4FA52865A7DC0049CFE4 /* pf_sse2_double.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = pf_sse2_double.h; sourceTree = ""; }; + 831A4FA62865A7DC0049CFE4 /* pf_avx_double.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = pf_avx_double.h; sourceTree = ""; }; + 831A4FA72865A7DC0049CFE4 /* pf_scalar_double.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = pf_scalar_double.h; sourceTree = ""; }; + 831A4FA82865A7DC0049CFE4 /* pffft_priv_impl.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = pffft_priv_impl.h; sourceTree = ""; }; + 831A4FA92865A7DC0049CFE4 /* pffft_double.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = pffft_double.c; sourceTree = ""; }; + 831A4FB72865A7DC0049CFE4 /* CDSPHBUpsampler.inc */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.pascal; path = CDSPHBUpsampler.inc; sourceTree = ""; }; + 831A4FB82865A7DC0049CFE4 /* r8butil.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = r8butil.h; sourceTree = ""; }; + 831A4FBA2865A7DC0049CFE4 /* r8bbase.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = r8bbase.h; sourceTree = ""; }; + 831A4FC42865A7DC0049CFE4 /* CDSPSincFilterGen.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CDSPSincFilterGen.h; sourceTree = ""; }; + 831A4FD02865A7DC0049CFE4 /* LICENSE */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = LICENSE; sourceTree = ""; }; + 831A4FD12865A7DC0049CFE4 /* CDSPResampler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CDSPResampler.h; sourceTree = ""; }; + 831A4FD22865A7DC0049CFE4 /* CDSPHBUpsampler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CDSPHBUpsampler.h; sourceTree = ""; }; + 831A4FD42865A7DC0049CFE4 /* CDSPBlockConvolver.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CDSPBlockConvolver.h; sourceTree = ""; }; + 831A4FD52865A7DC0049CFE4 /* fft4g.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = fft4g.h; sourceTree = ""; }; + 831A4FD62865A7DC0049CFE4 /* CDSPHBDownsampler.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CDSPHBDownsampler.h; sourceTree = ""; }; + 831A4FD72865A7DC0049CFE4 /* r8bconf.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = r8bconf.h; sourceTree = ""; }; + 831A4FD82865A7DC0049CFE4 /* CDSPFracInterpolator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CDSPFracInterpolator.h; sourceTree = ""; }; + 831A4FD92865A7DC0049CFE4 /* CDSPFIRFilter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = CDSPFIRFilter.h; sourceTree = ""; }; + 831A4FDA2865A7DC0049CFE4 /* r8bbase.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = r8bbase.cpp; sourceTree = ""; }; + 831A4FDB2865A7DC0049CFE4 /* pffft.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = pffft.h; sourceTree = ""; }; + 831A50132865A7FD0049CFE4 /* r8bstate.hpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.h; path = r8bstate.hpp; sourceTree = ""; }; + 831A50152865A8800049CFE4 /* r8bstate.cpp */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.cpp.cpp; path = r8bstate.cpp; sourceTree = ""; }; + 831A50172865A8B30049CFE4 /* r8bstate.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = r8bstate.h; sourceTree = ""; }; 8328995127CB510F00D7F028 /* RedundantPlaylistDataStore.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RedundantPlaylistDataStore.m; path = ../../Utils/RedundantPlaylistDataStore.m; sourceTree = ""; }; 8328995227CB511000D7F028 /* RedundantPlaylistDataStore.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RedundantPlaylistDataStore.h; path = ../../Utils/RedundantPlaylistDataStore.h; sourceTree = ""; }; 8328995527CB51B700D7F028 /* SHA256Digest.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SHA256Digest.h; path = ../../Utils/SHA256Digest.h; sourceTree = ""; }; @@ -332,6 +390,10 @@ 17D21CD80B8BE5B400D1EBDE /* ThirdParty */ = { isa = PBXGroup; children = ( + 831A50152865A8800049CFE4 /* r8bstate.cpp */, + 831A50172865A8B30049CFE4 /* r8bstate.h */, + 831A50132865A7FD0049CFE4 /* r8bstate.hpp */, + 831A4F9C2865A7DC0049CFE4 /* r8brain-free-src */, 8377C64A27B8C51500E8BC0F /* deadbeef */, 835C88AE279811A500E28EAE /* hdcd */, 17D21DC40B8BE79700D1EBDE /* CoreAudioUtils */, @@ -377,6 +439,55 @@ name = "Other Sources"; sourceTree = ""; }; + 831A4F9C2865A7DC0049CFE4 /* r8brain-free-src */ = { + isa = PBXGroup; + children = ( + 831A4F9D2865A7DC0049CFE4 /* CDSPProcessor.h */, + 831A4F9E2865A7DC0049CFE4 /* CDSPRealFFT.h */, + 831A4F9F2865A7DC0049CFE4 /* pffft_double */, + 831A4FB72865A7DC0049CFE4 /* CDSPHBUpsampler.inc */, + 831A4FB82865A7DC0049CFE4 /* r8butil.h */, + 831A4FBA2865A7DC0049CFE4 /* r8bbase.h */, + 831A4FC42865A7DC0049CFE4 /* CDSPSincFilterGen.h */, + 831A4FD02865A7DC0049CFE4 /* LICENSE */, + 831A4FD12865A7DC0049CFE4 /* CDSPResampler.h */, + 831A4FD22865A7DC0049CFE4 /* CDSPHBUpsampler.h */, + 831A4FD42865A7DC0049CFE4 /* CDSPBlockConvolver.h */, + 831A4FD52865A7DC0049CFE4 /* fft4g.h */, + 831A4FD62865A7DC0049CFE4 /* CDSPHBDownsampler.h */, + 831A4FD72865A7DC0049CFE4 /* r8bconf.h */, + 831A4FD82865A7DC0049CFE4 /* CDSPFracInterpolator.h */, + 831A4FD92865A7DC0049CFE4 /* CDSPFIRFilter.h */, + 831A4FDA2865A7DC0049CFE4 /* r8bbase.cpp */, + 831A4FDB2865A7DC0049CFE4 /* pffft.h */, + ); + path = "r8brain-free-src"; + sourceTree = ""; + }; + 831A4F9F2865A7DC0049CFE4 /* pffft_double */ = { + isa = PBXGroup; + children = ( + 831A4FA02865A7DC0049CFE4 /* pffft_double.h */, + 831A4FA12865A7DC0049CFE4 /* simd */, + 831A4FA82865A7DC0049CFE4 /* pffft_priv_impl.h */, + 831A4FA92865A7DC0049CFE4 /* pffft_double.c */, + ); + path = pffft_double; + sourceTree = ""; + }; + 831A4FA12865A7DC0049CFE4 /* simd */ = { + isa = PBXGroup; + children = ( + 831A4FA22865A7DC0049CFE4 /* pf_neon_double_from_avx.h */, + 831A4FA32865A7DC0049CFE4 /* pf_double.h */, + 831A4FA42865A7DC0049CFE4 /* pf_neon_double.h */, + 831A4FA52865A7DC0049CFE4 /* pf_sse2_double.h */, + 831A4FA62865A7DC0049CFE4 /* pf_avx_double.h */, + 831A4FA72865A7DC0049CFE4 /* pf_scalar_double.h */, + ); + path = simd; + sourceTree = ""; + }; 835C88AE279811A500E28EAE /* hdcd */ = { isa = PBXGroup; children = ( @@ -421,30 +532,54 @@ buildActionMask = 2147483647; files = ( 17D21CA10B8BE4BA00D1EBDE /* BufferChain.h in Headers */, + 831A4FE02865A7DC0049CFE4 /* pf_double.h in Headers */, + 831A50142865A7FD0049CFE4 /* r8bstate.hpp in Headers */, 17D21CA50B8BE4BA00D1EBDE /* InputNode.h in Headers */, 17D21CA70B8BE4BA00D1EBDE /* Node.h in Headers */, 8399CF2C27B5D1D5008751F1 /* NSDictionary+Merge.h in Headers */, + 831A4FF32865A7DC0049CFE4 /* r8butil.h in Headers */, 17D21CA90B8BE4BA00D1EBDE /* OutputNode.h in Headers */, 8328995427CB511000D7F028 /* RedundantPlaylistDataStore.h in Headers */, + 831A50102865A7DC0049CFE4 /* CDSPFIRFilter.h in Headers */, + 831A4FFE2865A7DC0049CFE4 /* CDSPSincFilterGen.h in Headers */, + 831A4FF52865A7DC0049CFE4 /* r8bbase.h in Headers */, + 831A50082865A7DC0049CFE4 /* CDSPResampler.h in Headers */, 17D21CC50B8BE4BA00D1EBDE /* OutputAVFoundation.h in Headers */, 83504165286447DA006B32CC /* Downmix.h in Headers */, + 831A4FDE2865A7DC0049CFE4 /* pffft_double.h in Headers */, + 831A4FE12865A7DC0049CFE4 /* pf_neon_double.h in Headers */, 17D21CC70B8BE4BA00D1EBDE /* Status.h in Headers */, 17D21CF30B8BE5EF00D1EBDE /* Semaphore.h in Headers */, 17D21DC70B8BE79700D1EBDE /* CoreAudioUtils.h in Headers */, 17D21EBD0B8BF44000D1EBDE /* AudioPlayer.h in Headers */, + 831A50182865A8B30049CFE4 /* r8bstate.h in Headers */, + 831A4FE52865A7DC0049CFE4 /* pffft_priv_impl.h in Headers */, + 831A4FE42865A7DC0049CFE4 /* pf_scalar_double.h in Headers */, + 831A500D2865A7DC0049CFE4 /* CDSPHBDownsampler.h in Headers */, + 831A500C2865A7DC0049CFE4 /* fft4g.h in Headers */, 8377C65227B8CAD100E8BC0F /* VisualizationController.h in Headers */, 834FD4F027AF93680063BC83 /* ChunkList.h in Headers */, 17F94DD50B8D0F7000A34E87 /* PluginController.h in Headers */, + 831A50122865A7DC0049CFE4 /* pffft.h in Headers */, + 831A4FDC2865A7DC0049CFE4 /* CDSPProcessor.h in Headers */, + 831A4FDD2865A7DC0049CFE4 /* CDSPRealFFT.h in Headers */, 17F94DDD0B8D101100A34E87 /* Plugin.h in Headers */, 8328995727CB51B700D7F028 /* SHA256Digest.h in Headers */, 834FD4EB27AF8F380063BC83 /* AudioChunk.h in Headers */, 17A2D3C50B8D1D37000778C4 /* AudioDecoder.h in Headers */, + 831A50092865A7DC0049CFE4 /* CDSPHBUpsampler.h in Headers */, + 831A500E2865A7DC0049CFE4 /* r8bconf.h in Headers */, 8347C7412796C58800FA8A7D /* NSFileHandle+CreateFile.h in Headers */, 17C940230B900909008627D6 /* AudioMetadataReader.h in Headers */, + 831A500F2865A7DC0049CFE4 /* CDSPFracInterpolator.h in Headers */, + 831A4FE22865A7DC0049CFE4 /* pf_sse2_double.h in Headers */, 839065F32853338700636FBB /* dsd2float.h in Headers */, 17B619300B909BC300BC003F /* AudioPropertiesReader.h in Headers */, + 831A4FDF2865A7DC0049CFE4 /* pf_neon_double_from_avx.h in Headers */, + 831A4FE32865A7DC0049CFE4 /* pf_avx_double.h in Headers */, 839366671815923C006DD712 /* CogPluginMulti.h in Headers */, 17ADB13C0B97926D00257CA2 /* AudioSource.h in Headers */, + 831A500B2865A7DC0049CFE4 /* CDSPBlockConvolver.h in Headers */, 835C88B1279811A500E28EAE /* hdcd_decode2.h in Headers */, 8EC1225F0B993BD500C5B3AD /* ConverterNode.h in Headers */, 8384912718080FF100E7332D /* Logging.h in Headers */, @@ -516,6 +651,7 @@ isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; files = ( + 831A50072865A7DC0049CFE4 /* LICENSE in Resources */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -528,8 +664,10 @@ files = ( 17D21CA20B8BE4BA00D1EBDE /* BufferChain.m in Sources */, 17D21CA60B8BE4BA00D1EBDE /* InputNode.m in Sources */, + 831A50112865A7DC0049CFE4 /* r8bbase.cpp in Sources */, 83504166286447DA006B32CC /* Downmix.m in Sources */, 8399CF2D27B5D1D5008751F1 /* NSDictionary+Merge.m in Sources */, + 831A50162865A8800049CFE4 /* r8bstate.cpp in Sources */, 17D21CA80B8BE4BA00D1EBDE /* Node.m in Sources */, 17D21CAA0B8BE4BA00D1EBDE /* OutputNode.m in Sources */, 8377C65327B8CAD100E8BC0F /* VisualizationController.m in Sources */, @@ -545,6 +683,8 @@ 839366681815923C006DD712 /* CogPluginMulti.m in Sources */, 17D21EBE0B8BF44000D1EBDE /* AudioPlayer.m in Sources */, 17F94DD60B8D0F7000A34E87 /* PluginController.mm in Sources */, + 831A4FE62865A7DC0049CFE4 /* pffft_double.c in Sources */, + 831A4FF22865A7DC0049CFE4 /* CDSPHBUpsampler.inc in Sources */, 17A2D3C60B8D1D37000778C4 /* AudioDecoder.m in Sources */, 8328995827CB51B700D7F028 /* SHA256Digest.m in Sources */, 17C940240B900909008627D6 /* AudioMetadataReader.m in Sources */, @@ -578,10 +718,17 @@ GCC_PREFIX_HEADER = CogAudio_Prefix.pch; GCC_PREPROCESSOR_DEFINITIONS = ( "DEBUG=1", + "R8B_EXTFFT=1", + "R8B_PFFFT_DOUBLE=1", ); INFOPLIST_FILE = Info.plist; INSTALL_PATH = "@executable_path/../Frameworks"; LD_RUNPATH_SEARCH_PATHS = "@loader_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/ThirdParty/r8brain-free-src/DLL/Win64", + "$(PROJECT_DIR)/ThirdParty/r8brain-free-src/DLL/Win32", + ); OTHER_LDFLAGS = ""; PRODUCT_BUNDLE_IDENTIFIER = org.cogx.cogaudio; PRODUCT_NAME = CogAudio; @@ -606,10 +753,17 @@ GCC_PRECOMPILE_PREFIX_HEADER = YES; GCC_PREFIX_HEADER = CogAudio_Prefix.pch; GCC_PREPROCESSOR_DEFINITIONS = ( + "R8B_EXTFFT=1", + "R8B_PFFFT_DOUBLE=1", ); INFOPLIST_FILE = Info.plist; INSTALL_PATH = "@executable_path/../Frameworks"; LD_RUNPATH_SEARCH_PATHS = "@loader_path/Frameworks"; + LIBRARY_SEARCH_PATHS = ( + "$(inherited)", + "$(PROJECT_DIR)/ThirdParty/r8brain-free-src/DLL/Win64", + "$(PROJECT_DIR)/ThirdParty/r8brain-free-src/DLL/Win32", + ); OTHER_LDFLAGS = ""; PRODUCT_BUNDLE_IDENTIFIER = org.cogx.cogaudio; PRODUCT_NAME = CogAudio; diff --git a/Audio/Output/OutputAVFoundation.h b/Audio/Output/OutputAVFoundation.h index 290801438..afd4c9669 100644 --- a/Audio/Output/OutputAVFoundation.h +++ b/Audio/Output/OutputAVFoundation.h @@ -36,6 +36,9 @@ using std::atomic_long; @interface OutputAVFoundation : NSObject { OutputNode *outputController; + BOOL r8bFlushing, r8bFlushed, r8bDone; + void *r8bstate, *r8bold; + BOOL stopInvoked; BOOL running; BOOL stopping; @@ -64,6 +67,7 @@ using std::atomic_long; AudioDeviceID outputDeviceID; AudioStreamBasicDescription streamFormat; // stream format last seen in render callback + AudioStreamBasicDescription newFormat; // in case of resampler flush AudioStreamBasicDescription visFormat; // Mono format for vis diff --git a/Audio/Output/OutputAVFoundation.m b/Audio/Output/OutputAVFoundation.m index d03a25cc1..1ee1be8aa 100644 --- a/Audio/Output/OutputAVFoundation.m +++ b/Audio/Output/OutputAVFoundation.m @@ -17,6 +17,8 @@ #import +#import "r8bstate.h" + extern void scale_by_volume(float *buffer, size_t count, float volume); static NSString *CogPlaybackDidBeginNotficiation = @"CogPlaybackDidBeginNotficiation"; @@ -84,19 +86,41 @@ static OSStatus eqRenderCallback(void *inRefCon, AudioUnitRenderActionFlags *ioA BOOL formatClipped = NO; BOOL isSurround = format.mChannelsPerFrame > 2; const double maxSampleRate = isSurround ? 352800.0 : 192000.0; + double srcRate = format.mSampleRate; + double dstRate = srcRate; if(format.mSampleRate > maxSampleRate) { format.mSampleRate = maxSampleRate; + dstRate = maxSampleRate; formatClipped = YES; } - if(!streamFormatStarted || config != streamChannelConfig || memcmp(&streamFormat, &format, sizeof(format)) != 0) { + if(!streamFormatStarted || config != streamChannelConfig || memcmp(&newFormat, &format, sizeof(format)) != 0) { if(formatClipped) { ALog(@"Sample rate clipped to no more than %f Hz!", maxSampleRate); + if(r8bstate) { + r8bold = r8bstate; + r8bstate = NULL; + r8bFlushing = YES; + } + r8bstate = r8bstate_new(format.mChannelsPerFrame, 1024, srcRate, dstRate); + } else if(r8bstate) { + r8bold = r8bstate; + r8bstate = NULL; + r8bFlushing = YES; } - streamFormat = format; + newFormat = format; streamChannelConfig = config; streamFormatStarted = YES; - [self updateStreamFormat]; + + visFormat = format; + visFormat.mChannelsPerFrame = 1; + visFormat.mBytesPerFrame = visFormat.mChannelsPerFrame * (visFormat.mBitsPerChannel / 8); + visFormat.mBytesPerPacket = visFormat.mBytesPerFrame * visFormat.mFramesPerPacket; + downmixerForVis = [[DownmixProcessor alloc] initWithInputFormat:format inputConfig:config andOutputFormat:visFormat outputConfig:AudioConfigMono]; + if(!r8bold) { + streamFormat = format; + [self updateStreamFormat]; + } } chunkDuration = [chunk duration]; @@ -107,13 +131,24 @@ static OSStatus eqRenderCallback(void *inRefCon, AudioUnitRenderActionFlags *ioA amount:frameCount * format.mChannelsPerFrame location:@"pre downmix"]; #endif + // It should be fine to request up to double, we'll only get downsampled + float outputBuffer[2048 * newFormat.mChannelsPerFrame]; + const float *outputPtr = (const float *)[samples bytes]; + if(r8bstate) { + size_t inDone = 0; + size_t framesDone = r8bstate_resample(r8bstate, outputPtr, frameCount, &inDone, &outputBuffer[0], 2048); + if(!framesDone) return 0; + frameCount = (int)framesDone; + outputPtr = &outputBuffer[0]; + chunkDuration = frameCount / newFormat.mSampleRate; + } - [downmixerForVis process:[samples bytes] + [downmixerForVis process:outputPtr frameCount:frameCount output:visAudio]; visTabulated += frameCount; - cblas_scopy((int)(frameCount * streamFormat.mChannelsPerFrame), (const float *)[samples bytes], 1, &inputBuffer[0], 1); + cblas_scopy((int)(frameCount * newFormat.mChannelsPerFrame), outputPtr, 1, &inputBuffer[0], 1); amountRead = frameCount; } else { return 0; @@ -141,9 +176,9 @@ static OSStatus eqRenderCallback(void *inRefCon, AudioUnitRenderActionFlags *ioA volumeScale *= eqPreamp; } - scale_by_volume(&inputBuffer[0], amountRead * streamFormat.mChannelsPerFrame, volumeScale); + scale_by_volume(&inputBuffer[0], amountRead * newFormat.mChannelsPerFrame, volumeScale); - [visController postSampleRate:streamFormat.mSampleRate]; + [visController postSampleRate:newFormat.mSampleRate]; [visController postVisPCM:visAudio amount:visTabulated]; return amountRead; @@ -222,6 +257,14 @@ current_device_listener(AudioObjectID inObjectID, UInt32 inNumberAddresses, cons return ret; } +- (BOOL)processEndOfStream { + if([outputController endOfStream] == YES && [self signalEndOfStream:secondsLatency]) { + stopping = YES; + return YES; + } + return NO; +} + - (void)threadEntry:(id)arg { running = YES; started = NO; @@ -258,20 +301,8 @@ current_device_listener(AudioObjectID inObjectID, UInt32 inNumberAddresses, cons [audioRenderer enqueueSampleBuffer:bufferRef]; } else { - if([outputController endOfStream] == YES && ![self signalEndOfStream:secondsLatency]) { - // Wait for output to catch up - CMTime currentTime; - [currentPtsLock lock]; - currentTime = currentPts; - [currentPtsLock unlock]; - CMTime timeToWait = CMTimeSubtract(outputPts, currentTime); - double secondsToWait = CMTimeGetSeconds(timeToWait); - if(secondsToWait > 0) { - usleep(secondsToWait * 1000000.0); - } - stopping = YES; - } break; + // r8b will absorb some samples first } } @@ -498,11 +529,6 @@ current_device_listener(AudioObjectID inObjectID, UInt32 inNumberAddresses, cons } - (void)updateStreamFormat { - visFormat = streamFormat; - visFormat.mChannelsPerFrame = 1; - visFormat.mBytesPerFrame = visFormat.mChannelsPerFrame * (visFormat.mBitsPerChannel / 8); - visFormat.mBytesPerPacket = visFormat.mBytesPerFrame * visFormat.mFramesPerPacket; - /* Set the channel layout for the audio queue */ AudioChannelLayoutTag tag = 0; AudioChannelLayout layout = { 0 }; @@ -598,62 +624,114 @@ current_device_listener(AudioObjectID inObjectID, UInt32 inNumberAddresses, cons status = CMBlockBufferCreateEmpty(kCFAllocatorDefault, 0, 0, &blockListBuffer); if(status != noErr || !blockListBuffer) return 0; - int samplesRendered = [self renderInput]; + int inputRendered; + do { + inputRendered = [self renderInput]; + if([self processEndOfStream]) break; + } while(!inputRendered); - if(samplesRendered) { - if(eqEnabled) { - const int channels = streamFormat.mChannelsPerFrame; - if(channels > 0) { - const size_t channelsminusone = channels - 1; - uint8_t tempBuffer[sizeof(AudioBufferList) + sizeof(AudioBuffer) * channelsminusone]; - AudioBufferList *ioData = (AudioBufferList *)&tempBuffer[0]; + float tempBuffer[2048 * 32]; - ioData->mNumberBuffers = channels; - for(size_t i = 0; i < channels; ++i) { - ioData->mBuffers[i].mData = &eqBuffer[1024 * i]; - ioData->mBuffers[i].mDataByteSize = 1024 * sizeof(float); - ioData->mBuffers[i].mNumberChannels = 1; - } + int samplesRenderedTotal = 0; - status = AudioUnitRender(_eq, NULL, &timeStamp, 0, samplesRendered, ioData); + for(size_t i = 0; i < 2;) { + float *samplePtr; + int samplesRendered; - if(status != noErr) { - return 0; - } - - timeStamp.mSampleTime += ((double)samplesRendered) / streamFormat.mSampleRate; - - for(int i = 0; i < channels; ++i) { - cblas_scopy(samplesRendered, &eqBuffer[1024 * i], 1, &inputBuffer[i], channels); - } + if(i == 0) { + if(!r8bold) { + ++i; + continue; + } + samplesRendered = r8bstate_flush(r8bold, &tempBuffer[0], 2048); + if(!samplesRendered) { + r8bstate_delete(r8bold); + r8bold = NULL; + r8bDone = YES; + } + samplePtr = &tempBuffer[0]; + } else { + samplesRendered = inputRendered; + samplePtr = &inputBuffer[0]; + if(r8bDone) { + r8bDone = NO; + streamFormat = newFormat; + [self updateStreamFormat]; } } - CMBlockBufferRef blockBuffer = nil; - size_t dataByteSize = samplesRendered * sizeof(float) * streamFormat.mChannelsPerFrame; + if(samplesRendered) { + if(eqEnabled) { + const int channels = streamFormat.mChannelsPerFrame; + if(channels > 0) { + const size_t channelsminusone = channels - 1; + uint8_t tempBuffer[sizeof(AudioBufferList) + sizeof(AudioBuffer) * channelsminusone]; + AudioBufferList *ioData = (AudioBufferList *)&tempBuffer[0]; - status = CMBlockBufferCreateWithMemoryBlock(kCFAllocatorDefault, nil, dataByteSize, kCFAllocatorDefault, nil, 0, dataByteSize, kCMBlockBufferAssureMemoryNowFlag, &blockBuffer); + ioData->mNumberBuffers = channels; + for(size_t i = 0; i < channels; ++i) { + ioData->mBuffers[i].mData = &eqBuffer[1024 * i]; + ioData->mBuffers[i].mDataByteSize = 1024 * sizeof(float); + ioData->mBuffers[i].mNumberChannels = 1; + } - if(status != noErr || !blockBuffer) { - return 0; + status = AudioUnitRender(_eq, NULL, &timeStamp, 0, samplesRendered, ioData); + + if(status != noErr) { + return 0; + } + + timeStamp.mSampleTime += ((double)samplesRendered) / streamFormat.mSampleRate; + + for(int i = 0; i < channels; ++i) { + cblas_scopy(samplesRendered, &eqBuffer[1024 * i], 1, samplePtr, channels); + } + } + } + + CMBlockBufferRef blockBuffer = nil; + size_t dataByteSize = samplesRendered * sizeof(float) * streamFormat.mChannelsPerFrame; + + status = CMBlockBufferCreateWithMemoryBlock(kCFAllocatorDefault, nil, dataByteSize, kCFAllocatorDefault, nil, 0, dataByteSize, kCMBlockBufferAssureMemoryNowFlag, &blockBuffer); + + if(status != noErr || !blockBuffer) { + return 0; + } + + status = CMBlockBufferReplaceDataBytes(samplePtr, blockBuffer, 0, dataByteSize); + + if(status != noErr) { + return 0; + } + + status = CMBlockBufferAppendBufferReference(blockListBuffer, blockBuffer, 0, CMBlockBufferGetDataLength(blockBuffer), 0); + + if(status != noErr) { + return 0; + } } - status = CMBlockBufferReplaceDataBytes(&inputBuffer[0], blockBuffer, 0, dataByteSize); - - if(status != noErr) { - return 0; - } - - status = CMBlockBufferAppendBufferReference(blockListBuffer, blockBuffer, 0, CMBlockBufferGetDataLength(blockBuffer), 0); - - if(status != noErr) { - return 0; + if(i == 0) { + if(!samplesRendered) { + *blockBufferOut = blockListBuffer; + return samplesRenderedTotal + samplesRendered; + } + } else { + samplesRenderedTotal += samplesRendered; + if(!samplesRendered || samplesRenderedTotal >= 1024) { + ++i; + } else { + do { + inputRendered = [self renderInput]; + if([self processEndOfStream]) break; + } while(!inputRendered); + } } } *blockBufferOut = blockListBuffer; - return samplesRendered; + return samplesRenderedTotal; } - (BOOL)setup { @@ -672,6 +750,12 @@ current_device_listener(AudioObjectID inObjectID, UInt32 inNumberAddresses, cons downmixerForVis = nil; + r8bFlushing = NO; + r8bFlushed = NO; + r8bDone = NO; + r8bstate = NULL; + r8bold = NULL; + AudioComponentDescription desc; NSError *err; @@ -907,6 +991,14 @@ current_device_listener(AudioObjectID inObjectID, UInt32 inNumberAddresses, cons #endif outputController = nil; visController = nil; + if(r8bstate) { + r8bstate_delete(r8bstate); + r8bstate = NULL; + } + if(r8bold) { + r8bstate_delete(r8bold); + r8bold = NULL; + } } } diff --git a/Audio/ThirdParty/r8brain-free-src b/Audio/ThirdParty/r8brain-free-src new file mode 160000 index 000000000..afd61e7ed --- /dev/null +++ b/Audio/ThirdParty/r8brain-free-src @@ -0,0 +1 @@ +Subproject commit afd61e7ed76d86a9bc6cb91fd0a9f305f853fe38 diff --git a/Audio/ThirdParty/r8bstate.cpp b/Audio/ThirdParty/r8bstate.cpp new file mode 100644 index 000000000..c238f6efb --- /dev/null +++ b/Audio/ThirdParty/r8bstate.cpp @@ -0,0 +1,31 @@ +// +// r8bstate.cpp +// CogAudio Framework +// +// Created by Christopher Snowhill on 6/24/22. +// + +#include "r8bstate.h" +#include "r8bstate.hpp" + +void *r8bstate_new(int channelCount, int bufferCapacity, double srcRate, + double dstRate) { + return (void *)new r8bstate(channelCount, bufferCapacity, srcRate, dstRate); +} + +void r8bstate_delete(void *state) { + delete(r8bstate *)state; +} + +double r8bstate_latency(void *state) { + return ((r8bstate *)state)->latency(); +} + +int r8bstate_resample(void *state, const float *input, size_t inCount, size_t *inDone, + float *output, size_t outMax) { + return ((r8bstate *)state)->resample(input, inCount, inDone, output, outMax); +} + +int r8bstate_flush(void *state, float *output, size_t outMax) { + return ((r8bstate *)state)->flush(output, outMax); +} diff --git a/Audio/ThirdParty/r8bstate.h b/Audio/ThirdParty/r8bstate.h new file mode 100644 index 000000000..b7f415d67 --- /dev/null +++ b/Audio/ThirdParty/r8bstate.h @@ -0,0 +1,33 @@ +// +// r8bstate.h +// CogAudio Framework +// +// Created by Christopher Snowhill on 6/24/22. +// + +#include +#include + +#ifndef r8bstate_h +#define r8bstate_h + +#ifdef __cplusplus +extern "C" { +#endif + +void *r8bstate_new(int channelCount, int bufferCapacity, double srcRate, + double dstRate); +void r8bstate_delete(void *); + +double r8bstate_latency(void *); + +int r8bstate_resample(void *, const float *input, size_t inCount, size_t *inDone, + float *output, size_t outMax); + +int r8bstate_flush(void *, float *output, size_t outMax); + +#ifdef __cplusplus +} +#endif + +#endif /* r8bstate_h */ diff --git a/Audio/ThirdParty/r8bstate.hpp b/Audio/ThirdParty/r8bstate.hpp new file mode 100644 index 000000000..b39f43e99 --- /dev/null +++ b/Audio/ThirdParty/r8bstate.hpp @@ -0,0 +1,164 @@ +// +// r8bstate.hpp +// CogAudio Framework +// +// Created by Christopher Snowhill on 3/3/22. +// + +#ifndef r8bstate_hpp +#define r8bstate_hpp + +#include + +#include "r8bbase.h" + +#include "CDSPResampler.h" + +struct r8bstate { + int channelCount; + int bufferCapacity; + size_t remainder; + uint64_t inProcessed; + uint64_t outProcessed; + double sampleRatio; + r8b::CFixedBuffer InBuf; + r8b::CFixedBuffer *OutBufs; + r8b::CDSPResampler24 **Resamps; + r8bstate(int _channelCount, int _bufferCapacity, double srcRate, double dstRate) + : channelCount(_channelCount), bufferCapacity(_bufferCapacity), inProcessed(0), outProcessed(0), remainder(0) { + InBuf.alloc(bufferCapacity); + OutBufs = new r8b::CFixedBuffer[channelCount]; + Resamps = new r8b::CDSPResampler24 *[channelCount]; + for(int i = 0; i < channelCount; ++i) { + Resamps[i] = new r8b::CDSPResampler24(srcRate, dstRate, bufferCapacity); + } + sampleRatio = dstRate / srcRate; + } + + ~r8bstate() { + delete[] OutBufs; + for(int i = 0; i < channelCount; ++i) { + delete Resamps[i]; + } + delete[] Resamps; + } + + double latency() { + return ((double)inProcessed * sampleRatio) - (double)outProcessed; + } + + int resample(const float *input, size_t inCount, size_t *inDone, float *output, size_t outMax) { + int ret = 0; + int i; + if(inDone) *inDone = 0; + while(remainder > 0) { + size_t blockCount = remainder; + if(blockCount > outMax) + blockCount = outMax; + for(i = 0; i < channelCount; ++i) { + vDSP_vdpsp(&OutBufs[i][0], 1, output + i, channelCount, blockCount); + } + remainder -= blockCount; + if(remainder > 0) { + for(i = 0; i < channelCount; ++i) { + memmove(&OutBufs[i][0], &OutBufs[i][blockCount], remainder * sizeof(double)); + } + } + output += channelCount * blockCount; + outProcessed += blockCount; + outMax -= blockCount; + ret += blockCount; + if(!outMax) + return ret; + } + while(inCount > 0) { + size_t blockCount = inCount; + if(blockCount > bufferCapacity) + blockCount = bufferCapacity; + int outputDone = 0; + for(i = 0; i < channelCount; ++i) { + double *outputPointer; + vDSP_vspdp(input + i, channelCount, &InBuf[0], 1, blockCount); + outputDone = Resamps[i]->process(InBuf, (int)blockCount, outputPointer); + if(outputDone) { + if(outputDone > outMax) { + vDSP_vdpsp(outputPointer, 1, output + i, channelCount, outMax); + remainder = outputDone - outMax; + OutBufs[i].alloc((int)remainder); + memcpy(&OutBufs[i][0], outputPointer + outMax, remainder); + } else { + vDSP_vdpsp(outputPointer, 1, output + i, channelCount, outputDone); + } + } + } + size_t outputActual = outputDone - remainder; + input += channelCount * blockCount; + output += channelCount * outputActual; + inCount -= blockCount; + if(inDone) *inDone += blockCount; + inProcessed += blockCount; + outProcessed += outputActual; + outMax -= outputActual; + ret += outputActual; + if(remainder) + break; + } + return ret; + } + + int flush(float *output, size_t outMax) { + int ret = 0; + int i; + if(remainder > 0) { + size_t blockCount = remainder; + if(blockCount > outMax) + blockCount = outMax; + for(i = 0; i < channelCount; ++i) { + vDSP_vdpsp(&OutBufs[i][0], 1, output + i, channelCount, blockCount); + } + remainder -= blockCount; + if(remainder > 0) { + for(i = 0; i < channelCount; ++i) { + memmove(&OutBufs[i][0], &OutBufs[i][blockCount], remainder * sizeof(double)); + } + } + output += channelCount * blockCount; + outProcessed += blockCount; + outMax -= blockCount; + ret += blockCount; + if(!outMax) + return ret; + } + uint64_t outputWanted = ceil(inProcessed * sampleRatio); + memset(&InBuf[0], 0, sizeof(double) * bufferCapacity); + while(outProcessed < outputWanted) { + int outputDone = 0; + for(int i = 0; i < channelCount; ++i) { + double *outputPointer; + outputDone = Resamps[i]->process(InBuf, bufferCapacity, outputPointer); + if(outputDone) { + if(outputDone > (outputWanted - outProcessed)) + outputDone = (int)(outputWanted - outProcessed); + if(outputDone > outMax) { + vDSP_vdpsp(outputPointer, 1, output + i, channelCount, outMax); + remainder = outputDone - outMax; + OutBufs[i].alloc((int)remainder); + memcpy(&OutBufs[i][0], outputPointer + outMax, remainder); + } else { + vDSP_vdpsp(outputPointer, 1, output + i, channelCount, outputDone); + } + } + } + size_t outputActual = outputDone - remainder; + outProcessed += outputActual; + output += channelCount * outputActual; + outMax -= outputActual; + ret += outputActual; + if(remainder) + break; + } + return ret; + } +}; + +#endif /* r8bstate_h */