From b340c322eb630253cdf77f079cbb1d7363195387 Mon Sep 17 00:00:00 2001 From: Toni Karhu Date: Fri, 20 Jan 2023 09:52:53 +0200 Subject: [PATCH 01/10] Delete temp build files and update .gitignore Signed-off-by: Toni Karhu --- .gitignore | 4 ++++ .../executionHistory/executionHistory.bin | Bin 434766 -> 0 bytes .../executionHistory/executionHistory.lock | Bin 17 -> 0 bytes .../.gradle/6.7.1/fileChanges/last-build.bin | Bin 1 -> 0 bytes .../.gradle/6.7.1/fileHashes/fileHashes.bin | Bin 33547 -> 0 bytes .../.gradle/6.7.1/fileHashes/fileHashes.lock | Bin 17 -> 0 bytes .../6.7.1/fileHashes/resourceHashesCache.bin | Bin 18599 -> 0 bytes android/.gradle/6.7.1/gc.properties | 0 .../6.7.1/javaCompile/classAnalysis.bin | Bin 20375 -> 0 bytes .../.gradle/6.7.1/javaCompile/jarAnalysis.bin | Bin 19971 -> 0 bytes .../.gradle/6.7.1/javaCompile/javaCompile.lock | Bin 17 -> 0 bytes .../.gradle/6.7.1/javaCompile/taskHistory.bin | Bin 26329 -> 0 bytes .../buildOutputCleanup/buildOutputCleanup.lock | Bin 17 -> 0 bytes .../buildOutputCleanup/cache.properties | 2 -- .../.gradle/buildOutputCleanup/outputFiles.bin | Bin 53483 -> 0 bytes android/.gradle/checksums/checksums.lock | Bin 17 -> 0 bytes android/.gradle/checksums/md5-checksums.bin | Bin 18597 -> 0 bytes android/.gradle/checksums/sha1-checksums.bin | Bin 22763 -> 0 bytes android/.gradle/checksums/sha256-checksums.bin | Bin 18629 -> 0 bytes android/.gradle/checksums/sha512-checksums.bin | Bin 18693 -> 0 bytes .../.gradle/configuration-cache/gc.properties | 0 android/.gradle/vcs-1/gc.properties | 0 android/local.properties | 8 -------- 23 files changed, 4 insertions(+), 10 deletions(-) delete mode 100644 android/.gradle/6.7.1/executionHistory/executionHistory.bin delete mode 100644 android/.gradle/6.7.1/executionHistory/executionHistory.lock delete mode 100644 android/.gradle/6.7.1/fileChanges/last-build.bin delete mode 100644 android/.gradle/6.7.1/fileHashes/fileHashes.bin delete mode 100644 android/.gradle/6.7.1/fileHashes/fileHashes.lock delete mode 100644 android/.gradle/6.7.1/fileHashes/resourceHashesCache.bin delete mode 100644 android/.gradle/6.7.1/gc.properties delete mode 100644 android/.gradle/6.7.1/javaCompile/classAnalysis.bin delete mode 100644 android/.gradle/6.7.1/javaCompile/jarAnalysis.bin delete mode 100644 android/.gradle/6.7.1/javaCompile/javaCompile.lock delete mode 100644 android/.gradle/6.7.1/javaCompile/taskHistory.bin delete mode 100644 android/.gradle/buildOutputCleanup/buildOutputCleanup.lock delete mode 100644 android/.gradle/buildOutputCleanup/cache.properties delete mode 100644 android/.gradle/buildOutputCleanup/outputFiles.bin delete mode 100644 android/.gradle/checksums/checksums.lock delete mode 100644 android/.gradle/checksums/md5-checksums.bin delete mode 100644 android/.gradle/checksums/sha1-checksums.bin delete mode 100644 android/.gradle/checksums/sha256-checksums.bin delete mode 100644 android/.gradle/checksums/sha512-checksums.bin delete mode 100644 android/.gradle/configuration-cache/gc.properties delete mode 100644 android/.gradle/vcs-1/gc.properties delete mode 100644 android/local.properties diff --git a/.gitignore b/.gitignore index e43b0f98..6b2cb231 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,5 @@ .DS_Store +android/build +**/.* +!.gitignore +local.properties diff --git a/android/.gradle/6.7.1/executionHistory/executionHistory.bin b/android/.gradle/6.7.1/executionHistory/executionHistory.bin deleted file mode 100644 index 6e4a8ee3a86d984b275f9dafb13dac981f6833fb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 434766 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 z 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%0Aio!AwFEX_6gLLrJ_&pFRiidk0Aq_ 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≪6Hv@MqZ$4Rl~BVBCN*Plp%Pz5S)z#dOvUsecQYB6D^wVM2v_H`X7q zZ)5Go1NWi-3KW678M9K^DhByXR-OR(o>!G$%69oGeOQ#g6paAw`hDTcJ8qr$`^IIr z9RH@H8yOc;C=oQJ71(hrpPQDC5r|5H=3Ch+mkP(4?tCMp`JxQg-c|=@*BV>+%mAmA2c%0)SXWUwwjn>nOSb!G9A4}327y{(mJ`rR zR6gnCK$5}$vfhY*-AWFDp_r)=jGM~p`F$*Jv=P)&G}ttR%9yh51cH^-paD=P(}c1<^|lDsS5mDC;d#zBMFAd$rh*_ zo}mP>s6X8=Ki|dm5d`^7&6JcnDeH9jrQAFg!jv<7Xs*`27lf6k7*%|t27)%V+dR^9 z$=L+F%hUJ5nRu{@~?JUk#G sv*qVC&3!$9Kt=DYFg?F}?%7v=N4~kY?aAt^%CfhzY&(<@*a)}$Kg3*t_5c6? diff --git a/android/.gradle/6.7.1/executionHistory/executionHistory.lock b/android/.gradle/6.7.1/executionHistory/executionHistory.lock deleted file mode 100644 index 20ea9764b4a438c1cc2b89c4ebc70dff076fe032..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17 UcmZSPlQ{fznVO9W0|bNt04Bo&WB>pF diff --git a/android/.gradle/6.7.1/fileChanges/last-build.bin b/android/.gradle/6.7.1/fileChanges/last-build.bin deleted file mode 100644 index f76dd238ade08917e6712764a16a22005a50573d..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1 IcmZPo000310RR91 diff --git a/android/.gradle/6.7.1/fileHashes/fileHashes.bin b/android/.gradle/6.7.1/fileHashes/fileHashes.bin deleted file mode 100644 index d8dcd8f07c8468a44f9569ad0be89713d207bff9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 33547 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 MEtqr^e9!WK0KuN3kN^Mx diff --git a/android/.gradle/6.7.1/fileHashes/fileHashes.lock b/android/.gradle/6.7.1/fileHashes/fileHashes.lock deleted file mode 100644 index 6ea411fc66157c271590c56a4bbbd82041b306d2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17 VcmZR!+#@gZ{{Qyv3}C<*001@y1mOSx diff --git a/android/.gradle/6.7.1/fileHashes/resourceHashesCache.bin b/android/.gradle/6.7.1/fileHashes/resourceHashesCache.bin deleted file mode 100644 index 82f3b20d25b60f1ee4781468816b2b33b99182df..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18599 zcmeI%!74;i7zgkhDNSm|LS|yv&}=MB*sxL)YLt_!jCC66i24a7x041)QQs2J=lzn*VB9#)g%jn(Y`pxd6j>GXQ# zG(XDn)Ag{`xOi!#`E{0Odz9}dXI26P2oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF z5FkK+009C72oNAZfB*pk1PBlyK;SO~7NSc_(HxCuVf*RxsgI| zd{CdFxSQ6hhf+JX)J|~*0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fWZGE&`%y@ zJ$o2;YXd!L98#3_4dkTh#)-#mSFcUKVV6S@{`Z4BOU)~@9jjkh-fi-H^yw#UZFc_* z%fo4&mlluy>V0`+j^)AMJg>x#9Z<$RBh}o`^My+3)w&&1pIII_&hwAovsW$`WjKaBe7oCmF}~!l=FfP3@a#xQUtKP+yxYz5fw@CB7q%_muJ-@I z^W@^>cc+Nj7it84;hK~?{00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHaf zKmY;|fB*y_009U<00Izz00bZa0SG_<0uX=z1RwwbvA|w(xrY?}XLtX^ppo?^4I^O~ z88zXJsl`q`t?4Om)T8QJmUeR8x4nm6t{#2TZA~*zzvdser4Qh+?J08qNm>tVAJ8*s zWDEz(jZp=ezDX^c(&g7{^^KDIL$u2F>rLj>AvKXn$+sS$OAeFr(HggJttI7~MrhLr zsUWR~f~LBeN~atM$7$y{DLTSE)8fwg9GPm<$q(=xZQn~OKx@*zeMTKNiYB>9a*+FF z8)vm#R?RtV+)eK9q*bzCF_{ecu2I?L7RybT! z)z{f8a++W?G?Us)OS~O63N{n#Ti2bbI$N^nGTB-s-w44f*Z>MQ0>Lv{8>YktRew(q ckFsD1*4`>J0Rl?;>qKBwn|eC=WGc4*1qQ$jOaK4? diff --git a/android/.gradle/6.7.1/javaCompile/jarAnalysis.bin b/android/.gradle/6.7.1/javaCompile/jarAnalysis.bin deleted file mode 100644 index b5edd52845bdaf0cf81dd13220fdba18a000a02a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 19971 zcmeI%Ur19?90%~TsZ;0XPL!bZ2P?DkUy77rK}cpk6rq_#lsVnff!kg4s#q#&f)rV5 zQlQQJQ%WR-iIy4uNl>T_oC zhRE4KlSfFnN*c&2o5qKd_ss-24#&qV|9`OjDSf}waSRm#5P$##AOHafKmY;|fB*y_ z009U<00Izz00bZa0SG_<0uX=z1Rwwb2tWV=5P$##AOHafKmY;|fB*y_0D*r&Ad(Qb zZfS34JFT}}o+RNBeKKo8$HLxAHfe>lAzd(r=>#E75VEvsVM*GYL#7OaNgtLNkS*qC z>1k*|a^2YJ3yG-hibBrA%N8vLQwDu-J#+DA>et5!s=@8%`5V=>PM1X$hS|XH z@27k#mGSM(Plf|1k#V}VH$mi_6*yi5;=*Z)C7-zRPLQHb+*X-wqTsB?5oY4fO)v4_ zbZn0+@mxhdiSqA(BwqXk5*24D_Q(Yq=-;XY-Dm7C303=SaD^= zjTHqe?yPvQ;>n7V6)#p)tf*P>W<|pam1|^j7gslhyN9RJOQrVK5K=7tX!rgG)Vex| diff --git a/android/.gradle/6.7.1/javaCompile/javaCompile.lock b/android/.gradle/6.7.1/javaCompile/javaCompile.lock deleted file mode 100644 index 653850f34fd75a70b0ccff1a3b2b03ad1608e5a8..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17 UcmZSXxtTggQ?TJT0|ZC{05iY@H~;_u diff --git a/android/.gradle/6.7.1/javaCompile/taskHistory.bin b/android/.gradle/6.7.1/javaCompile/taskHistory.bin deleted file mode 100644 index 43a8f51ff3e288dc9d955d4f7ab6281b5586b7d4..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26329 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 C$mWm$ diff --git a/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/android/.gradle/buildOutputCleanup/buildOutputCleanup.lock deleted file mode 100644 index fb5903498d275c6c55a9fac0daa1c9a3fd8e8e49..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17 VcmZRc{x{)5qoh_60~j!s0{}G#1ZMyM diff --git a/android/.gradle/buildOutputCleanup/cache.properties b/android/.gradle/buildOutputCleanup/cache.properties deleted file mode 100644 index 911de871..00000000 --- a/android/.gradle/buildOutputCleanup/cache.properties +++ /dev/null @@ -1,2 +0,0 @@ -#Fri Apr 16 16:48:05 CEST 2021 -gradle.version=6.7.1 diff --git a/android/.gradle/buildOutputCleanup/outputFiles.bin b/android/.gradle/buildOutputCleanup/outputFiles.bin deleted file mode 100644 index 5a8a2e38c6fc42575dc76da0bf7a85e1e0e0e7f1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 53483 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 diff --git a/android/.gradle/checksums/checksums.lock b/android/.gradle/checksums/checksums.lock deleted file mode 100644 index d47d3613eb6fd2dc479f589a488ddf96ed844203..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 17 UcmZQp)OuO?qa%4T0|XQT05UfOkN^Mx diff --git a/android/.gradle/checksums/md5-checksums.bin b/android/.gradle/checksums/md5-checksums.bin deleted file mode 100644 index 2c65a403b7ace8ba5150e10be69e08616cc87e0a..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18597 zcmeI%y-EW?5CGscrHCF;;Sie?DFQ)-G`1mvibV=BFJNgY{^E03h+=6k#L_m6w$^81 zp@o%*g`j(0j^GPe$hWXN!z{z@e(kP=kS1Tl5k_Nnl*$MYAV7cs0RjXF5FkK+009C7 z2oNAZfB=F2BCr)7GG06;ZC3JR>L7%riI`L#U#d5It9PAZd#zpmA3S}wHm>XE&%^p| zSubO(w~IY10RjXF5FkK+009C72oNAZfB*pk1PBlyK!5-N0t5&UAV7cs0RjXF5FkK+ z009C72oNCfmjbPLSR>9_K9iG&^?ue#kF(~{W_R$q6T;YGc7K-q+-PQTKgNaM9ouiq ajrqCv>gC{c`nGp9S#(@{6dkpG-ti44=|yP( diff --git a/android/.gradle/checksums/sha1-checksums.bin b/android/.gradle/checksums/sha1-checksums.bin deleted file mode 100644 index 25144836ab92d1b419a4fb10b7016d492ba57907..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 22763 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 diff --git a/android/.gradle/checksums/sha256-checksums.bin b/android/.gradle/checksums/sha256-checksums.bin deleted file mode 100644 index a157a210931ecd3db28502b37359799b3451dfc0..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 18629 zcmeI%K}!Nb6u|LO2tgADDTMH}GcWcb#=1G+(DXL~o4sQZfV(KmY** z5I_I{1Q0*~0R#|0009ILKmY**5cn?wTk;`Z^%!l=Z70K7A?62V;Czn%dsb zo&ST^RycYwQ+V&zPo26GWnEW$tOy{000IagfB*srAbYdiU3{pslIes{ub z-o--ZiGOow+;e-pxO!6)!gcs;ocn%nI2t;TapiA^L}@x`RgFgAde+}e$6o?_pRrsy X_4bfFi?$2cg;25AtbJdD<&NEDfUf>5lI3dO@qcBrQ+ z;*_7=j@17_{6nnXGzFfM{pD0YbxVDR{Kkm@0tg_000IagfB*srAbhJVIFDMC3bneX8q!5OJ|%_ z&4jy3(l0yR)(x|1#4~8y*T0YM`jxk_yNZmsIHB@nH5TlJEy2inP3_}peBhwIGNiBx zLRx?9%&-2QAjjG(-Oaxf?xUsqt`nW{c;mV?W|+3`c+JtJ$yMh{%TT~}Q#3x+l-Dkn n4IldKk&e0fh3+<=!K)9hU1|3gUkjgd$K-_Io1Bopok;isf3J1` diff --git a/android/.gradle/configuration-cache/gc.properties b/android/.gradle/configuration-cache/gc.properties deleted file mode 100644 index e69de29b..00000000 diff --git a/android/.gradle/vcs-1/gc.properties b/android/.gradle/vcs-1/gc.properties deleted file mode 100644 index e69de29b..00000000 diff --git a/android/local.properties b/android/local.properties deleted file mode 100644 index d2e7944b..00000000 --- a/android/local.properties +++ /dev/null @@ -1,8 +0,0 @@ -## This file must *NOT* be checked into Version Control Systems, -# as it contains information specific to your local configuration. -# -# Location of the SDK. This is only used by Gradle. -# For customization when using a Version Control System, please read the -# header note. -#Sun Jul 11 21:17:08 CEST 2021 -sdk.dir=/Volumes/Home/larpoux/Library/Android/sdk From 7ded6c8cf04e1186a53d316a31d5083bb1c3ae41 Mon Sep 17 00:00:00 2001 From: Toni Karhu Date: Fri, 20 Jan 2023 09:53:07 +0200 Subject: [PATCH 02/10] Implement get audio session id Signed-off-by: Toni Karhu --- .../java/com/dooboolab/TauEngine/FlautoMediaPlayer.java | 4 ++++ .../main/java/com/dooboolab/TauEngine/FlautoPlayer.java | 7 +++++++ .../java/com/dooboolab/TauEngine/FlautoPlayerEngine.java | 4 ++++ .../com/dooboolab/TauEngine/FlautoPlayerEngineFromMic.java | 4 ++++ .../dooboolab/TauEngine/FlautoPlayerEngineInterface.java | 1 + .../java/com/dooboolab/TauEngine/FlautoPlayerMedia.java | 4 +++- 6 files changed, 23 insertions(+), 1 deletion(-) diff --git a/android/src/main/java/com/dooboolab/TauEngine/FlautoMediaPlayer.java b/android/src/main/java/com/dooboolab/TauEngine/FlautoMediaPlayer.java index 59627333..20cee7fc 100644 --- a/android/src/main/java/com/dooboolab/TauEngine/FlautoMediaPlayer.java +++ b/android/src/main/java/com/dooboolab/TauEngine/FlautoMediaPlayer.java @@ -50,6 +50,10 @@ void _play() mediaPlayer.start(); } + int _getAudioSessionId(){ + return mediaPlayer.getAudioSessionId(); + } + int feed(byte[] data) throws Exception { throw new Exception("Cannot feed a Media Player"); diff --git a/android/src/main/java/com/dooboolab/TauEngine/FlautoPlayer.java b/android/src/main/java/com/dooboolab/TauEngine/FlautoPlayer.java index 2174988f..82b7fccc 100644 --- a/android/src/main/java/com/dooboolab/TauEngine/FlautoPlayer.java +++ b/android/src/main/java/com/dooboolab/TauEngine/FlautoPlayer.java @@ -130,6 +130,13 @@ public boolean openPlayer () return true; } + public int getAudioSessionId() throws Exception { + if (player == null) { + throw new Exception("getAudioSessionId() : player is null"); + } + return player._getAudioSessionId(); + } + public void closePlayer ( ) { stop(); diff --git a/android/src/main/java/com/dooboolab/TauEngine/FlautoPlayerEngine.java b/android/src/main/java/com/dooboolab/TauEngine/FlautoPlayerEngine.java index dacf2db7..197fefb9 100644 --- a/android/src/main/java/com/dooboolab/TauEngine/FlautoPlayerEngine.java +++ b/android/src/main/java/com/dooboolab/TauEngine/FlautoPlayerEngine.java @@ -150,6 +150,10 @@ void _stop() blockThread = null; } + int _getAudioSessionId() { + return sessionId; + } + void _finish() { } diff --git a/android/src/main/java/com/dooboolab/TauEngine/FlautoPlayerEngineFromMic.java b/android/src/main/java/com/dooboolab/TauEngine/FlautoPlayerEngineFromMic.java index 712f3a3e..5a40cfea 100644 --- a/android/src/main/java/com/dooboolab/TauEngine/FlautoPlayerEngineFromMic.java +++ b/android/src/main/java/com/dooboolab/TauEngine/FlautoPlayerEngineFromMic.java @@ -282,6 +282,10 @@ void _stop() } } + int _getAudioSessionId() { + return sessionId; + } + void _finish() { } diff --git a/android/src/main/java/com/dooboolab/TauEngine/FlautoPlayerEngineInterface.java b/android/src/main/java/com/dooboolab/TauEngine/FlautoPlayerEngineInterface.java index 3a5af843..1d399e87 100644 --- a/android/src/main/java/com/dooboolab/TauEngine/FlautoPlayerEngineInterface.java +++ b/android/src/main/java/com/dooboolab/TauEngine/FlautoPlayerEngineInterface.java @@ -32,6 +32,7 @@ abstract class FlautoPlayerEngineInterface abstract long _getCurrentPosition(); abstract int feed(byte[] data) throws Exception; abstract void _play(); + abstract int _getAudioSessionId() throws Exception; } diff --git a/android/src/main/java/com/dooboolab/TauEngine/FlautoPlayerMedia.java b/android/src/main/java/com/dooboolab/TauEngine/FlautoPlayerMedia.java index 53549cd4..a92b01e1 100644 --- a/android/src/main/java/com/dooboolab/TauEngine/FlautoPlayerMedia.java +++ b/android/src/main/java/com/dooboolab/TauEngine/FlautoPlayerMedia.java @@ -114,7 +114,9 @@ void _stop() { } - + int _getAudioSessionId() { + return mediaPlayer.getAudioSessionId(); + } void _pausePlayer() throws Exception { if (mediaPlayer == null) { From 55cf2478436045cfa2625a57334159e9422ad36b Mon Sep 17 00:00:00 2001 From: Toni Karhu Date: Mon, 30 Jan 2023 14:29:28 +0200 Subject: [PATCH 03/10] Add some initial eq related files Signed-off-by: Toni Karhu --- ios/Classes/FlautoPlayerEngine.h | 31 ++ ios/Classes/FlautoPlayerEngine.mm | 197 ++++++++++++ .../equalizer/EqualizerEffectsData.swift | 65 ++++ ios/Classes/equalizer/EqualizerPlugin.h | 4 + ios/Classes/equalizer/EqualizerPlugin.m | 15 + .../equalizer/SwiftEqualizerPlugin.swift | 299 ++++++++++++++++++ 6 files changed, 611 insertions(+) create mode 100644 ios/Classes/equalizer/EqualizerEffectsData.swift create mode 100644 ios/Classes/equalizer/EqualizerPlugin.h create mode 100644 ios/Classes/equalizer/EqualizerPlugin.m create mode 100644 ios/Classes/equalizer/SwiftEqualizerPlugin.swift diff --git a/ios/Classes/FlautoPlayerEngine.h b/ios/Classes/FlautoPlayerEngine.h index b392959a..e581eafd 100644 --- a/ios/Classes/FlautoPlayerEngine.h +++ b/ios/Classes/FlautoPlayerEngine.h @@ -115,5 +115,36 @@ @end +typedef NS_ENUM(NSInteger, EffectType) { + DarwinEqualizer, +}; + + +@protocol EffectData + @property (nonatomic, readonly) EffectType type; +@end + + +@interface BandEqualizerData : NSObject + @property (nonatomic, readonly) int index; + @property (nonatomic, readonly) float centerFrequency; + @property (nonatomic, readonly) float gain; +@end + + +@interface ParamsEqualizerData : NSObject + @property (nonatomic, readonly) NSArray *bands; +@end + + +@interface EqualizerEffectData : NSObject + @property (nonatomic, readonly) BOOL enabled; + @property (nonatomic, readonly) ParamsEqualizerData *parameters; + + + (EqualizerEffectData *)fromJson:(NSDictionary *)map; + + (id)effectFrom:(NSDictionary *)map; + + (float)gainFrom:(float)value; +@end + #endif /* PlayerEngine_h */ diff --git a/ios/Classes/FlautoPlayerEngine.mm b/ios/Classes/FlautoPlayerEngine.mm index e071cfe2..638296a4 100644 --- a/ios/Classes/FlautoPlayerEngine.mm +++ b/ios/Classes/FlautoPlayerEngine.mm @@ -27,6 +27,7 @@ #import "Flauto.h" #import "FlautoPlayerEngine.h" #import "FlautoPlayer.h" +#import @implementation AudioPlayerFlauto { @@ -153,6 +154,7 @@ @implementation AudioEngine { FlautoPlayer* flutterSoundPlayer; // Owner AVAudioEngine* engine; + AVAudioUnitEQ* eq; AVAudioPlayerNode* playerNode; AVAudioFormat* playerFormat; AVAudioFormat* outputFormat; @@ -204,6 +206,115 @@ - (AudioEngine*)init: (FlautoPlayer*)owner return [super init]; } + - (void) attachEqualizer: (AVAudioUnitEQ*) eq + { + [engine attachNode: eq]; + [engine connect: playerNode to: eq format: playerFormat]; + [engine connect: eq to: outputNode format: outputFormat]; + } + + - (void) detachEqualizer: (AVAudioUnitEQ*) eq + { + [engine disconnectNodeInput: eq]; + [engine disconnectNodeInput: playerNode]; + [engine disconnectNodeInput: outputNode]; + [engine connect: playerNode to: outputNode format: outputFormat]; + [engine detachNode: eq]; + } + + - (void) enableEffect: (String) type, bool enabled + { + switch (type) + { + case "DarwinEqualizer": + + if (eq == nil) + { + [flutterSoundPlayer logDebug: @"Cannot enable the equalizer because it is not initialized"]; + return; + } + eq.bypass = !enabled; + + break; + default: + + break; + } + } + + - (void) setEqiualizerBandGain: (int) bandIndex gain: (double) gain + { + if (eq == nil) + { + [flutterSoundPlayer logDebug: @"Cannot set the equalizer band gain because the equalizer is not initialized"]; + return; + } + if (bandIndex < 0 || bandIndex >= eq.bands.count) + { + [flutterSoundPlayer logDebug: @"Cannot set the equalizer band gain because the band index is out of range"]; + return; + } + AVAudioUnitEQFilterParameters* band = eq.bands[bandIndex]; + band.gain = gain; + } + + - (void)initEqualizer:(FlutterMethodCall *)call result:(FlutterResult)result { + [flutterSoundPlayer logDebug:@"Initialize darwin EQ!"]; + NSDictionary *arguments = call.arguments; + if (![arguments isKindOfClass:[NSDictionary class]]) { + result([FlutterError errorWithCode:@"ABNORMAL_PARAMETER" message:@"no parameters" details:nil]); + return; + } + + [flutterSoundPlayer logDebug:@"%@", arguments]; + NSArray *effectsRaw = [arguments objectForKey:@"DarwinEqualizer"]; + if (!effectsRaw) { + result([FlutterError errorWithCode:@"ABNORMAL_PARAMETER" message:@"no DarwinEqualizer type found" details:nil]); + return; + } + + NSDictionary *equalizerRaw = [effectsRaw filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id _Nullable evaluatedObject, NSDictionary * _Nullable bindings) { + return [[evaluatedObject objectForKey:@"type"] isEqualToString:@"DarwinEqualizer"]; + }]].firstObject; + + NSDictionary *parameters = [equalizerRaw objectForKey:@"parameters"]; + NSArray *rawBands = [parameters objectForKey:@"bands"]; + BOOL enabled = [[equalizerRaw objectForKey:@"enabled"] boolValue]; + + NSArray *frequenciesAndBands = [rawBands filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id _Nullable evaluatedObject, NSDictionary * _Nullable bindings) { + return [evaluatedObject objectForKey:@"centerFrequency"] && [evaluatedObject objectForKey:@" Gain"]; + }]]; + + NSArray *frequencies = [frequenciesAndBands valueForKeyPath:@"centerFrequency"]; + NSArray *bands = [frequenciesAndBands valueForKeyPath:@" Gain"]; + + if (!audioEngine) { + [flutterSoundPlayer logDebug: @"Setting audio engine!"]; + audioEngine = [[AVAudioEngine alloc] init]; + + if (!eq) { + [flutterSoundPlayer logDebug: @"Setting Equalizer!"]; + eq = [[AVAudioUnitEQ alloc] initWithNumberOfBands:(unsigned int)bands.count]; + + for (int i = 0; i < rawBands.count; i++) { + NSDictionary *band = rawBands[i]; + eq.bands[i].filterType = AVAudioUnitEQFilterTypeParametric; + eq.bands[i].frequency = [[band objectForKey:@"centerFrequency"] floatValue]; + eq.bands[i].bandwidth = 1.0f; + eq.bands[i]. Gain = EqualizerEffectData.GainFrom([[band objectForKey:@" Gain"] floatValue]); + eq.bands[i].byPass = NO; + } + eq.bypass = !enabled; + + }else{ + [flutterSoundPlayer logDebug: @"Equalizer already set!"]; + } + } else { + [flutterSoundPlayer logDebug:@"Audio engine already set!"]; + } + } + + -(void) startPlayerFromBuffer: (NSData*) dataBuffer { [self feed: dataBuffer] ; @@ -505,5 +616,91 @@ - (int) feed: (NSData*)data //------------------------------------------------------------------------------------------------------------------------------------------- +// convert this to objective-c: +// struct EqualizerEffectData: EffectData, Codable { +// let type: EffectType +// let enabled: Bool +// let parameters: ParamsEqualizerData + +// static func fromJson(_ map: [String: Any]) -> EqualizerEffectData { +// return try! JSONDecoder().decode(EqualizerEffectData.self, from: JSONSerialization.data(withJSONObject: map)) +// } + +// static func effectFrom(_ map: [String: Any]) throws -> EffectData { +// let type = map["type"] as! String +// switch type { +// case EffectType.darwinEqualizer.rawValue: +// return EqualizerEffectData.fromJson(map) +// default: +// throw NotSupportedError(value: type, "When decoding effect") +// } +// } + +// static func gainFrom(_ value: Float) -> Float { +// // Equalize the level between iOS and android +// return value * 2.8 +// } +// } + +@implementation EqualizerEffectData +{ + // convert this to objective-c: + // let type: EffectType + // let enabled: Bool + // let parameters: ParamsEqualizerData + + EffectType: type; + bool enabled; + ParamsEqualizerData parameters; +} + + // convert this to objective-c: + // static func fromJson(_ map: [String: Any]) -> EqualizerEffectData { + // return try! JSONDecoder().decode(EqualizerEffectData.self, from: JSONSerialization.data(withJSONObject: map)) + // } + + -(EqualizerEffectData*) fromJson: (NSDictionary*) map + { + return JSONDecoder().decode(EqualizerEffectData.self, from: JSONSerialization.data(withJSONObject: map)); + } + + + + // convert this to objective-c: + // static func effectFrom(_ map: [String: Any]) throws -> EffectData { + // let type = map["type"] as! String + // switch type { + // case EffectType.darwinEqualizer.rawValue: + // return EqualizerEffectData.fromJson(map) + // default: + // throw NotSupportedError(value: type, "When decoding effect") + // } + // } + + -(EffectData*) effectFrom: (NSDictionary*) map + { + NSString* type = map[@"type"]; + switch (type) + { + case EffectType.darwinEqualizer.rawValue: + return [self fromJson: map]; + default: + throw NotSupportedError(value: type, "When decoding effect"); + } + } + + // convert this to objective-c: + // static func gainFrom(_ value: Float) -> Float { + // // Equalize the level between iOS and android + // return value * 2.8 + // } + + -(float) gainFrom: (float) value + { + // Equalize the level between iOS and android + return value * 2.8; + } + + @end diff --git a/ios/Classes/equalizer/EqualizerEffectsData.swift b/ios/Classes/equalizer/EqualizerEffectsData.swift new file mode 100644 index 00000000..2cf7696c --- /dev/null +++ b/ios/Classes/equalizer/EqualizerEffectsData.swift @@ -0,0 +1,65 @@ +import Foundation + +enum EffectType: String, Codable { + case darwinEqualizer = "DarwinEqualizer" +} + +protocol EffectData { + var type: EffectType { get } +} + +struct BandEqualizerData: Codable { + let index: Int + let centerFrequency: Float + let gain: Float +} + +struct ParamsEqualizerData: Codable { + let bands: [BandEqualizerData] +} + +struct EqualizerEffectData: EffectData, Codable { + let type: EffectType + let enabled: Bool + let parameters: ParamsEqualizerData + + static func fromJson(_ map: [String: Any]) -> EqualizerEffectData { + return try! JSONDecoder().decode(EqualizerEffectData.self, from: JSONSerialization.data(withJSONObject: map)) + } + + static func effectFrom(_ map: [String: Any]) throws -> EffectData { + let type = map["type"] as! String + switch type { + case EffectType.darwinEqualizer.rawValue: + return EqualizerEffectData.fromJson(map) + default: + throw NotSupportedError(value: type, "When decoding effect") + } + } + + static func gainFrom(_ value: Float) -> Float { + // Equalize the level between iOS and android + return value * 2.8 + } +} + + +class NotSupportedError: PluginError { + var value: Any + + init(value: Any, _ message: String) { + self.value = value + super.init(400, "Not support \(value)\n\(message)") + } +} + +class PluginError: Error { + var code: Int + var message: String + + init(_ code: Int, _ message: String) { + self.code = code + self.message = message + } +} + diff --git a/ios/Classes/equalizer/EqualizerPlugin.h b/ios/Classes/equalizer/EqualizerPlugin.h new file mode 100644 index 00000000..71d754eb --- /dev/null +++ b/ios/Classes/equalizer/EqualizerPlugin.h @@ -0,0 +1,4 @@ +#import + +@interface EqualizerPlugin : NSObject +@end diff --git a/ios/Classes/equalizer/EqualizerPlugin.m b/ios/Classes/equalizer/EqualizerPlugin.m new file mode 100644 index 00000000..51269e52 --- /dev/null +++ b/ios/Classes/equalizer/EqualizerPlugin.m @@ -0,0 +1,15 @@ +#import "EqualizerPlugin.h" +#if __has_include() +#import +#else +// Support project import fallback if the generated compatibility header +// is not copied when this plugin is created as a library. +// https://forums.swift.org/t/swift-static-libraries-dont-copy-generated-objective-c-header/19816 +#import "equalizer-Swift.h" +#endif + +@implementation EqualizerPlugin ++ (void)registerWithRegistrar:(NSObject*)registrar { + [SwiftEqualizerPlugin registerWithRegistrar:registrar]; +} +@end diff --git a/ios/Classes/equalizer/SwiftEqualizerPlugin.swift b/ios/Classes/equalizer/SwiftEqualizerPlugin.swift new file mode 100644 index 00000000..811ff442 --- /dev/null +++ b/ios/Classes/equalizer/SwiftEqualizerPlugin.swift @@ -0,0 +1,299 @@ +import Flutter +import AVFAudio +import UIKit +import Foundation + +public class SwiftEqualizerPlugin: NSObject, FlutterPlugin { + let registrar: FlutterPluginRegistrar + private var audioEngine: AVAudioEngine? + private var audioUnitEQ: AVAudioUnitEQ? + private var audioPlayerNode: AVAudioPlayerNode? + private var audioOutputNode:AVAudioOutputNode? + private var sessionId:Int? + private var audioEffects: [EffectData]? + private var flautoPlayer = FlautoPlayer() + + public static func register(with registrar: FlutterPluginRegistrar) { + let instance = SwiftEqualizerPlugin(registrar) + let channel = FlutterMethodChannel(name: "dev.offcode.equalizer/method", binaryMessenger: registrar.messenger()) + registrar.addMethodCallDelegate(instance, channel: channel) + } + + init(_ registrar: FlutterPluginRegistrar) { + self.registrar = registrar + super.init() + } + + public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { + switch call.method { + case "init": initEqualizer(call, result) + case "audioEffectSetEnabled": enableEffect(call, result) + case "darwinEqualizerBandSetGain": setEqualizerBandGain(call, result) + default: + result(FlutterMethodNotImplemented) + } + } + + func enableEffect(_ call: FlutterMethodCall, _ result: @escaping FlutterResult ) { + print("Enable Effect!") + guard let arguments = call.arguments as? Dictionary else { + result(FlutterError(code: "ABNORMAL_PARAMETER", message: "no parameters", details: nil)) + return + } + let type = arguments["type"] as! String + let enabled = arguments["enabled"] as! Bool + + print("Set enabled type: \(type) [ \(enabled) ]") + + switch type { + case "DarwinEqualizer": + audioUnitEQ!.bypass = !enabled + default: + result(FlutterError(code: "NOT_INITIALIZED", message: "Not initialized effect \(type)", details: nil)) + } + } + + func setEqualizerBandGain(_ call: FlutterMethodCall, _ result: @escaping FlutterResult ) { + guard let arguments = call.arguments as? Dictionary else { + result(FlutterError(code: "ABNORMAL_PARAMETER", message: "no parameters", details: nil)) + return + } + + let bandIndex = arguments["bandIndex"] as! Int + let gain = Float(arguments["gain"] as! Double) + print("Set band: \(bandIndex) gain to: \(gain)") + audioUnitEQ!.bands[bandIndex].gain = gain + } + + public func initEqualizer(_ call: FlutterMethodCall, _ result: @escaping FlutterResult){ + print("Initialize darwin EQ!") + + guard let arguments = call.arguments as? Dictionary else { + result(FlutterError(code: "ABNORMAL_PARAMETER", message: "no parameters", details: nil)) + return + } + + print(arguments) + + let effectsRaw: [[String: Any?]] = arguments.keys.contains("DarwinEqualizer") ? (arguments["DarwinEqualizer"] as! [[String: Any?]]) : [] + + let equalizerRaw = effectsRaw.filter { rawEffect in + (rawEffect["type"] as! String) == "DarwinEqualizer" + }.first + + if equalizerRaw!["type"] as? String != "DarwinEqualizer" { + result(FlutterError(code: "ABNORMAL_PARAMETER", message: "no DarwinEqualizer type found", details: nil)) + } + + let parameters = equalizerRaw!["parameters"] as! [String: Any] + let rawBands = parameters["bands"] as! [[String: Any]] + let enabled = equalizerRaw!["enabled"] as! Bool + + let frequenciesAndBands = rawBands.map { map in + let frequency = map["centerFrequency"] as! Double + let gain = map["gain"] as! Double + return (Int(frequency), EqualizerEffectData.gainFrom(Float(gain))) + } + + _ = frequenciesAndBands.map { frequency, _ in + frequency + } + + let bands = frequenciesAndBands.map { _, band in + band + } + + if audioEngine == nil { + print("Setting audio engine!") + audioEngine = AVAudioEngine() + + // add equalizer node + if audioUnitEQ == nil { + print("Setting Equalizer!") + audioUnitEQ = AVAudioUnitEQ(numberOfBands: bands.count) //Set bands count + + // Set bands, gains, etc. + for(i, band) in rawBands.enumerated(){ + audioUnitEQ!.bands[i].filterType = .parametric + audioUnitEQ!.bands[i].frequency = band["centerFrequency"] as! Float + audioUnitEQ!.bands[i].bandwidth = 1 // half an octave + audioUnitEQ!.bands[i].gain = EqualizerEffectData.gainFrom(band["gain"] as! Float) + audioUnitEQ!.bands[i].bypass = false + } + audioUnitEQ!.bypass = !enabled //Set enabled + + audioEngine!.attach(audioUnitEQ!) + } else { + print("EQ already initialized!") + } + }else { + print("Audio engine already running!") + } + result("SUCCESS") + } +} + +// fileprivate func createAudioEffects(_ _audioEffects: [EffectData]) throws { +// +// for effect in _audioEffects as [any EffectData] { +// print(effect) +// if let effect = effect as? EqualizerEffectData { +// audioUnitEQ = AVAudioUnitEQ(numberOfBands: effect.parameters.bands.count) +// +// for (i, band) in effect.parameters.bands.enumerated() { +// audioUnitEQ!.bands[i].filterType = .parametric +// audioUnitEQ!.bands[i].frequency = band.centerFrequency +// audioUnitEQ!.bands[i].bandwidth = 1 // half an octave +// audioUnitEQ!.bands[i].gain = EqualizerEffectData.gainFrom(band.gain) +// audioUnitEQ!.bands[i].bypass = false +// } +// +// audioUnitEQ!.bypass = !effect.enabled +// } else { +// throw NotSupportedError(value: effect.type, "When initialize effect") +// } +// } +// +// func parse(from map: [String: Any?]) throws { +// if map["type"] as? String != "DarwinEqualizer" { +// return nil +// } +// +// let parameters = map["parameters"] as! [String: Any] +// +// let rawBands = parameters["bands"] as! [[String: Any]] +// let frequenciesAndBands = rawBands.map { map in +// let frequency = map["centerFrequency"] as! Double +// let gain = map["gain"] as! Double +// return (Int(frequency), EqualizerEffectData.gainFrom(Float(gain))) +// } +// +// let frequencies = frequenciesAndBands.map { frequency, _ in +// frequency +// } +// +// let bands = frequenciesAndBands.map { _, band in +// band +// } + +// let equalizer = try Equalizer(frequencies: frequencies, preSets: [bands]) + +// try equalizer.activate(preset: 0) + +// return equalizer +// } +// } + + + + //===================================================================================================================// + + // public func setAudiosessionId(_ call:FlutterMethodCall, _ result: @escaping FlutterResult){ + // guard let arguments = call.arguments as? Dictionary else { + // result(FlutterError(code: "ABNORMAL_PARAMETER", message: "no parameters", details: nil)) + // return + // } + // audioEngine = AVAudioEngine() + // audioUnitEQ = AVAudioUnitEQ() + // audioPlayerNode = AVAudioPlayerNode() + + // let audioSession = AVAudioSession.sharedInstance() + // do { + // try audioSession.setCategory(.playAndRecord, options: .defaultToSpeaker) + // }catch let error as NSError{ + // print("ERROR:", error) + // } + // do{ + // try audioSession.setActive(true, options: .notifyOthersOnDeactivation) + // }catch let error as NSError { + // print("ERROR:", error) + // } + + // print("audioSession: \(audioSession)") + + // sessionId = arguments["sessionId"] as? Int + + // print("== Set audio session id to: \(String(describing: sessionId))") + // } + + + + + + + + + +// let numberOfBands = arguments["numberOfBands"] as? Int +// let freqs = arguments["freqs"] as? Array +// // in viewDidLoad(): +// audioUnitEQ = AVAudioUnitEQ(numberOfBands: numberOfBands!) +// audioEngine?.attach(audioPlayerNode!) +// audioEngine?.attach(audioUnitEQ!) +// let bands = audioUnitEQ!.bands +// audioEngine!.connect(audioPlayerNode!, to: audioUnitEQ!, format: nil) +// audioEngine!.connect(audioUnitEQ!, to: audioEngine!.outputNode, format: nil) +// for i in 0...(bands.count - 1) { +// bands[i].frequency = Float(freqs![i]) +// bands[i].bypass = false +// // bands[i].filtertype = .parametric +// } +// +// bands[0].gain = -10.0 +// bands[0].filterType = .lowShelf +// bands[1].gain = -10.0 +// bands[1].filterType = .lowShelf +// bands[2].gain = -10.0 +// bands[2].filterType = .lowShelf +// bands[3].gain = 10.0 +// bands[3].filterType = .highShelf +// bands[4].gain = 10.0 +// bands[4].filterType = .highShelf +// +// do { +// if let filepath = Bundle.main.path(forResource: "song", ofType: "mp3") { +// let filepathURL = NSURL.fileURL(withPath: filepath) +// do { +// audioFile = try AVAudioFile(forReading: filepathURL) +// } catch { +// print(error) +// } +// +// audioEngine.prepare() +// do{ +// try audioEngine.start() +// }catch{ +// print(error) +// } +// audioPlayerNode.scheduleFile(audioFile, at: nil, completionHandler: nil) +// audioPlayerNode.play() +// } +// } + +// fileprivate func createAudioEffects(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) throws { +// guard let arguments = call.arguments as? Dictionary else { +// result(FlutterError(code: "ABNORMAL_PARAMETER", message: "no parameters", details: nil)) +// return +// } +// +// +// for effect in audioEffects { +// if let effect = effect as? EqualizerEffectData { +// audioUnitEQ = AVAudioUnitEQ(numberOfBands: effect.parameters.bands.count) +// +// for (i, band) in effect.parameters.bands.enumerated() { +// audioUnitEQ!.bands[i].filterType = .parametric +// audioUnitEQ!.bands[i].frequency = band.centerFrequency +// audioUnitEQ!.bands[i].bandwidth = 1 // half an octave +// audioUnitEQ!.bands[i].gain = Util.gainFrom(band.gain) +// audioUnitEQ!.bands[i].bypass = false +// } +// +// audioUnitEQ!.bypass = !effect.enabled +// } else { +// throw NotSupportedError(value: effect.type, "When initialize effect") +// } +// } +// } + + From 55f34300d1134253ecac66032aa29d90152c7ada Mon Sep 17 00:00:00 2001 From: Toni Karhu Date: Mon, 30 Jan 2023 23:25:48 +0200 Subject: [PATCH 04/10] Add some init stuff. still not work properly Signed-off-by: Toni Karhu --- ios/Classes/FlautoPlayer.h | 1 + ios/Classes/FlautoPlayer.mm | 6 +- ios/Classes/FlautoPlayerEngine.h | 79 ++-- ios/Classes/FlautoPlayerEngine.mm | 342 +++++++----------- .../equalizer/EqualizerEffectsData.swift | 65 ---- ios/Classes/equalizer/EqualizerPlugin.h | 4 - ios/Classes/equalizer/EqualizerPlugin.m | 15 - .../equalizer/SwiftEqualizerPlugin.swift | 299 --------------- 8 files changed, 184 insertions(+), 627 deletions(-) delete mode 100644 ios/Classes/equalizer/EqualizerEffectsData.swift delete mode 100644 ios/Classes/equalizer/EqualizerPlugin.h delete mode 100644 ios/Classes/equalizer/EqualizerPlugin.m delete mode 100644 ios/Classes/equalizer/SwiftEqualizerPlugin.swift diff --git a/ios/Classes/FlautoPlayer.h b/ios/Classes/FlautoPlayer.h index 0f393afb..8a06253a 100644 --- a/ios/Classes/FlautoPlayer.h +++ b/ios/Classes/FlautoPlayer.h @@ -85,6 +85,7 @@ - (long)getPosition; - (long)getDuration; - (void)logDebug: (NSString*)msg; +- (bool)initEqualizer:(NSDictionary*) arguments; diff --git a/ios/Classes/FlautoPlayer.mm b/ios/Classes/FlautoPlayer.mm index b6d79648..664a2969 100644 --- a/ios/Classes/FlautoPlayer.mm +++ b/ios/Classes/FlautoPlayer.mm @@ -475,6 +475,11 @@ - (long)getDuration return [m_playerEngine getDuration]; } +- (bool)initEqualizer:(NSDictionary*) arguments +{ + return [m_playerEngine initEqualizer: arguments]; +} + - (NSDictionary*)getProgress { @@ -546,7 +551,6 @@ - (void)logDebug: (NSString*)msg [m_callBack log: DBG msg: msg]; } - @end //--------------------------------------------------------------------------------------------- diff --git a/ios/Classes/FlautoPlayerEngine.h b/ios/Classes/FlautoPlayerEngine.h index e581eafd..564788f9 100644 --- a/ios/Classes/FlautoPlayerEngine.h +++ b/ios/Classes/FlautoPlayerEngine.h @@ -48,6 +48,7 @@ - (bool) seek: (double) pos; - (t_PLAYER_STATE) getStatus; - (int) feed: (NSData*)data; + - (bool)initEqualizer:(NSDictionary*)arguments; @end @@ -69,6 +70,7 @@ - (AVAudioPlayer*) getAudioPlayer; - (void) setAudioPlayer: (AVAudioPlayer*)thePlayer; - (int) feed: (NSData*)data; + - (bool)initEqualizer:(NSDictionary*)arguments; @end @@ -90,6 +92,7 @@ - (bool) seek: (double) pos; - (int) getStatus; - (int) feed: (NSData*)data; + - (float)gainFrom:(float)value; @end @@ -114,37 +117,51 @@ @end - -typedef NS_ENUM(NSInteger, EffectType) { - DarwinEqualizer, -}; - - -@protocol EffectData - @property (nonatomic, readonly) EffectType type; -@end - - -@interface BandEqualizerData : NSObject - @property (nonatomic, readonly) int index; - @property (nonatomic, readonly) float centerFrequency; - @property (nonatomic, readonly) float gain; -@end - - -@interface ParamsEqualizerData : NSObject - @property (nonatomic, readonly) NSArray *bands; -@end - - -@interface EqualizerEffectData : NSObject - @property (nonatomic, readonly) BOOL enabled; - @property (nonatomic, readonly) ParamsEqualizerData *parameters; - - + (EqualizerEffectData *)fromJson:(NSDictionary *)map; - + (id)effectFrom:(NSDictionary *)map; - + (float)gainFrom:(float)value; -@end +//#pragma mark - Enum EffectType +//typedef NS_ENUM(NSUInteger, EffectType) { +// EffectTypeDarwinEqualizer, NotImplemented +// }; +// +//NSString *NSStringFromEffectType(EffectType type) { +// switch (type) { +// case EffectTypeDarwinEqualizer: +// return @"DarwinEqualizer"; +// default: +// return @""; +// } +//} +//EffectType EffectTypeFromNSString(NSString *string) { +// if ([string isEqualToString:@"DarwinEqualizer"]) { +// return EffectTypeDarwinEqualizer; +// } +// return NotImplemented; +//} +// +//#pragma mark - Protocol EffectData +//@protocol EffectData +// @property (nonatomic, assign, readonly) EffectType type; +//@end +// +//#pragma mark - Struct BandEqualizerData +//@interface BandEqualizerData : NSObject +// @property (nonatomic, assign) NSUInteger index; +// @property (nonatomic, assign) float centerFrequency; +// @property (nonatomic, assign) float gain; +//@end +// +//#pragma mark - Struct ParamsEqualizerData +//@interface ParamsEqualizerData : NSObject +// @property (nonatomic, strong) NSArray *bands; +//@end +// +//#pragma mark - Struct EqualizerEffectData +//@interface EqualizerEffectData : NSObject +// @property (nonatomic, assign) EffectType type; +// @property (nonatomic, assign) BOOL enabled; +// @property (nonatomic, strong) ParamsEqualizerData *parameters; +// - (instancetype)fromJson:(NSDictionary *)map; +// - (id)effectFrom:(NSDictionary *)map error:(NSError **)error; +//@end #endif /* PlayerEngine_h */ diff --git a/ios/Classes/FlautoPlayerEngine.mm b/ios/Classes/FlautoPlayerEngine.mm index 638296a4..ba0f6c98 100644 --- a/ios/Classes/FlautoPlayerEngine.mm +++ b/ios/Classes/FlautoPlayerEngine.mm @@ -27,7 +27,6 @@ #import "Flauto.h" #import "FlautoPlayerEngine.h" #import "FlautoPlayer.h" -#import @implementation AudioPlayerFlauto { @@ -170,149 +169,143 @@ @implementation AudioEngine - (AudioEngine*)init: (FlautoPlayer*)owner { - flutterSoundPlayer = owner; - waitingBlock = nil; - engine = [[AVAudioEngine alloc] init]; - outputNode = [engine outputNode]; + flutterSoundPlayer = owner; + waitingBlock = nil; + engine = [[AVAudioEngine alloc] init]; + outputNode = [engine outputNode]; - if (@available(iOS 13.0, *)) { - if ([flutterSoundPlayer isVoiceProcessingEnabled]) { - NSError* err; - if (![outputNode setVoiceProcessingEnabled:YES error:&err]) { - [flutterSoundPlayer logDebug:[NSString stringWithFormat:@"error enabling voiceProcessing => %@", err]]; - } else { - [flutterSoundPlayer logDebug: @"VoiceProcessing enabled"]; - } + if (@available(iOS 13.0, *)) { + if ([flutterSoundPlayer isVoiceProcessingEnabled]) { + NSError* err; + if (![outputNode setVoiceProcessingEnabled:YES error:&err]) { + [flutterSoundPlayer logDebug:[NSString stringWithFormat:@"error enabling voiceProcessing => %@", err]]; + } else { + [flutterSoundPlayer logDebug: @"VoiceProcessing enabled"]; } - } else { - [flutterSoundPlayer logDebug: @"WARNING! VoiceProcessing is only available on iOS13+"]; } - - outputFormat = [outputNode inputFormatForBus: 0]; - playerNode = [[AVAudioPlayerNode alloc] init]; - - [engine attachNode: playerNode]; - - [engine connect: playerNode to: outputNode format: outputFormat]; - bool b = [engine startAndReturnError: nil]; - if (!b) - { - [flutterSoundPlayer logDebug: @"Cannot start the audio engine"]; - } - - mPauseTime = 0.0; // Total number of seconds in pause mode - mStartPauseTime = -1; // Not in paused mode - systemTime = CACurrentMediaTime(); // The time when started - return [super init]; - } - - - (void) attachEqualizer: (AVAudioUnitEQ*) eq - { + } else { + [flutterSoundPlayer logDebug: @"WARNING! VoiceProcessing is only available on iOS13+"]; + } + + outputFormat = [outputNode inputFormatForBus: 0]; + playerNode = [[AVAudioPlayerNode alloc] init]; + + if(eq){ + NSLog(@"EQ found! Attach it"); [engine attachNode: eq]; + } else { + NSLog(@"EQ NOT FOUND!!!"); + } + + [engine attachNode: playerNode]; + + if(eq){ + NSLog(@"EQ found! Connect it"); [engine connect: playerNode to: eq format: playerFormat]; [engine connect: eq to: outputNode format: outputFormat]; - } + } - - (void) detachEqualizer: (AVAudioUnitEQ*) eq - { - [engine disconnectNodeInput: eq]; - [engine disconnectNodeInput: playerNode]; - [engine disconnectNodeInput: outputNode]; - [engine connect: playerNode to: outputNode format: outputFormat]; - [engine detachNode: eq]; - } + [engine connect: playerNode to: outputNode format: outputFormat]; + bool b = [engine startAndReturnError: nil]; + if (!b) + { + [flutterSoundPlayer logDebug: @"Cannot start the audio engine"]; + } - - (void) enableEffect: (String) type, bool enabled - { - switch (type) - { - case "DarwinEqualizer": - - if (eq == nil) - { - [flutterSoundPlayer logDebug: @"Cannot enable the equalizer because it is not initialized"]; - return; - } - eq.bypass = !enabled; - - break; - default: - - break; - } + mPauseTime = 0.0; // Total number of seconds in pause mode + mStartPauseTime = -1; // Not in paused mode + systemTime = CACurrentMediaTime(); // The time when started + return [super init]; } - - (void) setEqiualizerBandGain: (int) bandIndex gain: (double) gain + - (void) enableEffect: (NSString *) type enabled: (bool) enabled { + if ([type isEqual: @"DarwinEqualizer"]) + { if (eq == nil) { - [flutterSoundPlayer logDebug: @"Cannot set the equalizer band gain because the equalizer is not initialized"]; - return; + [flutterSoundPlayer logDebug: @"Cannot enable the equalizer because it is not initialized"]; + return; } - if (bandIndex < 0 || bandIndex >= eq.bands.count) - { - [flutterSoundPlayer logDebug: @"Cannot set the equalizer band gain because the band index is out of range"]; - return; - } - AVAudioUnitEQFilterParameters* band = eq.bands[bandIndex]; - band.gain = gain; + eq.bypass = !enabled; + } } - - (void)initEqualizer:(FlutterMethodCall *)call result:(FlutterResult)result { - [flutterSoundPlayer logDebug:@"Initialize darwin EQ!"]; - NSDictionary *arguments = call.arguments; - if (![arguments isKindOfClass:[NSDictionary class]]) { - result([FlutterError errorWithCode:@"ABNORMAL_PARAMETER" message:@"no parameters" details:nil]); - return; - } - - [flutterSoundPlayer logDebug:@"%@", arguments]; - NSArray *effectsRaw = [arguments objectForKey:@"DarwinEqualizer"]; - if (!effectsRaw) { - result([FlutterError errorWithCode:@"ABNORMAL_PARAMETER" message:@"no DarwinEqualizer type found" details:nil]); - return; - } - - NSDictionary *equalizerRaw = [effectsRaw filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id _Nullable evaluatedObject, NSDictionary * _Nullable bindings) { - return [[evaluatedObject objectForKey:@"type"] isEqualToString:@"DarwinEqualizer"]; - }]].firstObject; + - (void) setEqiualizerBandGain: (int) bandIndex gain: (double) gain + { + if (eq == nil) + { + [flutterSoundPlayer logDebug: @"Cannot set the equalizer band gain because the equalizer is not initialized"]; + return; + } + if (bandIndex < 0 || bandIndex >= eq.bands.count) + { + [flutterSoundPlayer logDebug: @"Cannot set the equalizer band gain because the band index is out of range"]; + return; + } + AVAudioUnitEQFilterParameters* band = eq.bands[bandIndex]; + band.gain = gain; + } + + - (bool) initEqualizer:(NSDictionary*) arguments { + NSLog(@"Initialize darwin EQ! (LAST PLACE TO CALL)"); + [flutterSoundPlayer logDebug:@"Initialize darwin EQ!"]; + + NSArray *effectsRaw = [arguments objectForKey:@"DarwinEqualizer"]; + if (!effectsRaw) { + [flutterSoundPlayer logDebug:@"no DarwinEqualizer type found"]; + return false; + } - NSDictionary *parameters = [equalizerRaw objectForKey:@"parameters"]; - NSArray *rawBands = [parameters objectForKey:@"bands"]; - BOOL enabled = [[equalizerRaw objectForKey:@"enabled"] boolValue]; + NSDictionary *equalizerRaw = [effectsRaw filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id _Nullable evaluatedObject, NSDictionary * _Nullable bindings) { + return [[evaluatedObject objectForKey:@"type"] isEqualToString:@"DarwinEqualizer"]; + }]].firstObject; - NSArray *frequenciesAndBands = [rawBands filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id _Nullable evaluatedObject, NSDictionary * _Nullable bindings) { - return [evaluatedObject objectForKey:@"centerFrequency"] && [evaluatedObject objectForKey:@" Gain"]; - }]]; + NSDictionary *parameters = [equalizerRaw objectForKey:@"parameters"]; + NSArray *rawBands = [parameters objectForKey:@"bands"]; + BOOL enabled = [[equalizerRaw objectForKey:@"enabled"] boolValue]; - NSArray *frequencies = [frequenciesAndBands valueForKeyPath:@"centerFrequency"]; - NSArray *bands = [frequenciesAndBands valueForKeyPath:@" Gain"]; +// NSArray *frequenciesAndBands = [rawBands filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id _Nullable evaluatedObject, NSDictionary * _Nullable bindings) { +// return [evaluatedObject objectForKey:@"centerFrequency"] && [evaluatedObject objectForKey:@" Gain"]; +// }]]; +// NSArray *frequencies = [frequenciesAndBands valueForKeyPath:@"centerFrequency"]; +// NSArray *bands = [frequenciesAndBands valueForKeyPath:@" Gain"]; - if (!audioEngine) { - [flutterSoundPlayer logDebug: @"Setting audio engine!"]; - audioEngine = [[AVAudioEngine alloc] init]; - - if (!eq) { - [flutterSoundPlayer logDebug: @"Setting Equalizer!"]; - eq = [[AVAudioUnitEQ alloc] initWithNumberOfBands:(unsigned int)bands.count]; - - for (int i = 0; i < rawBands.count; i++) { - NSDictionary *band = rawBands[i]; - eq.bands[i].filterType = AVAudioUnitEQFilterTypeParametric; - eq.bands[i].frequency = [[band objectForKey:@"centerFrequency"] floatValue]; - eq.bands[i].bandwidth = 1.0f; - eq.bands[i]. Gain = EqualizerEffectData.GainFrom([[band objectForKey:@" Gain"] floatValue]); - eq.bands[i].byPass = NO; - } - eq.bypass = !enabled; - - }else{ - [flutterSoundPlayer logDebug: @"Equalizer already set!"]; - } - } else { - [flutterSoundPlayer logDebug:@"Audio engine already set!"]; - } - } +// if (!engine) { +// // [flutterSoundPlayer logDebug: @"Setting audio engine!"]; +// // NSLog(@"Setting audio engine!"); +// // engine = [[AVAudioEngine alloc] init]; +// NSLog(@"No Audio engine found!"); +// return false; +// } + if (!eq) { + [flutterSoundPlayer logDebug: @"Setting Equalizer!"]; + NSLog(@"Setting Equalizer!"); + eq = [[AVAudioUnitEQ alloc] initWithNumberOfBands:(unsigned int)rawBands.count]; + + for (int i = 0; i < rawBands.count; i++) { + NSDictionary *band = rawBands[i]; + eq.bands[i].filterType = AVAudioUnitEQFilterTypeParametric; + eq.bands[i].frequency = [[band objectForKey:@"centerFrequency"] floatValue]; + eq.bands[i].bandwidth = 1.0f; + eq.bands[i].gain = [self gainFrom: ([[band objectForKey:@" Gain"] floatValue])]; + eq.bands[i].bypass = NO; + } + eq.bypass = !enabled; +// [self attachEqualizer: eq]; + }else{ + NSLog(@"Equalizer already set!"); + [flutterSoundPlayer logDebug: @"Equalizer already set!"]; + return true; + } + return true; + +// } else { +// NSLog(@"Audio engine already set!"); +// [flutterSoundPlayer logDebug:@"Audio engine already set!"]; +// return true; +// } + } -(void) startPlayerFromBuffer: (NSData*) dataBuffer @@ -483,6 +476,13 @@ - (bool) setSpeed: (double) speed } +- (float) gainFrom:(float) value +{ + //Equalize the level between iOS and Android + return value * 2.8; +} + + @end // --------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -612,95 +612,13 @@ - (int) feed: (NSData*)data return 0; } - -//------------------------------------------------------------------------------------------------------------------------------------------- - - -// convert this to objective-c: -// struct EqualizerEffectData: EffectData, Codable { -// let type: EffectType -// let enabled: Bool -// let parameters: ParamsEqualizerData - -// static func fromJson(_ map: [String: Any]) -> EqualizerEffectData { -// return try! JSONDecoder().decode(EqualizerEffectData.self, from: JSONSerialization.data(withJSONObject: map)) -// } - -// static func effectFrom(_ map: [String: Any]) throws -> EffectData { -// let type = map["type"] as! String -// switch type { -// case EffectType.darwinEqualizer.rawValue: -// return EqualizerEffectData.fromJson(map) -// default: -// throw NotSupportedError(value: type, "When decoding effect") -// } -// } - -// static func gainFrom(_ value: Float) -> Float { -// // Equalize the level between iOS and android -// return value * 2.8 -// } -// } - -@implementation EqualizerEffectData -{ - // convert this to objective-c: - // let type: EffectType - // let enabled: Bool - // let parameters: ParamsEqualizerData - - EffectType: type; - bool enabled; - ParamsEqualizerData parameters; -} - - // convert this to objective-c: - // static func fromJson(_ map: [String: Any]) -> EqualizerEffectData { - // return try! JSONDecoder().decode(EqualizerEffectData.self, from: JSONSerialization.data(withJSONObject: map)) - // } - - -(EqualizerEffectData*) fromJson: (NSDictionary*) map - { - return JSONDecoder().decode(EqualizerEffectData.self, from: JSONSerialization.data(withJSONObject: map)); - } - - - - // convert this to objective-c: - // static func effectFrom(_ map: [String: Any]) throws -> EffectData { - // let type = map["type"] as! String - // switch type { - // case EffectType.darwinEqualizer.rawValue: - // return EqualizerEffectData.fromJson(map) - // default: - // throw NotSupportedError(value: type, "When decoding effect") - // } - // } - - -(EffectData*) effectFrom: (NSDictionary*) map - { - NSString* type = map[@"type"]; - switch (type) - { - case EffectType.darwinEqualizer.rawValue: - return [self fromJson: map]; - default: - throw NotSupportedError(value: type, "When decoding effect"); - } - } - - // convert this to objective-c: - // static func gainFrom(_ value: Float) -> Float { - // // Equalize the level between iOS and android - // return value * 2.8 - // } - - -(float) gainFrom: (float) value - { - // Equalize the level between iOS and android - return value * 2.8; - } + - (bool)initEqualizer:(NSDictionary*)arguments{ + NSLog(@"Initialize darwin EQ! (MICROPHONE - NOT IMPLEMENTED)"); + return true; + //TODO + } +//------------------------------------------------------------------------------------------------------------------------------------------- @end diff --git a/ios/Classes/equalizer/EqualizerEffectsData.swift b/ios/Classes/equalizer/EqualizerEffectsData.swift deleted file mode 100644 index 2cf7696c..00000000 --- a/ios/Classes/equalizer/EqualizerEffectsData.swift +++ /dev/null @@ -1,65 +0,0 @@ -import Foundation - -enum EffectType: String, Codable { - case darwinEqualizer = "DarwinEqualizer" -} - -protocol EffectData { - var type: EffectType { get } -} - -struct BandEqualizerData: Codable { - let index: Int - let centerFrequency: Float - let gain: Float -} - -struct ParamsEqualizerData: Codable { - let bands: [BandEqualizerData] -} - -struct EqualizerEffectData: EffectData, Codable { - let type: EffectType - let enabled: Bool - let parameters: ParamsEqualizerData - - static func fromJson(_ map: [String: Any]) -> EqualizerEffectData { - return try! JSONDecoder().decode(EqualizerEffectData.self, from: JSONSerialization.data(withJSONObject: map)) - } - - static func effectFrom(_ map: [String: Any]) throws -> EffectData { - let type = map["type"] as! String - switch type { - case EffectType.darwinEqualizer.rawValue: - return EqualizerEffectData.fromJson(map) - default: - throw NotSupportedError(value: type, "When decoding effect") - } - } - - static func gainFrom(_ value: Float) -> Float { - // Equalize the level between iOS and android - return value * 2.8 - } -} - - -class NotSupportedError: PluginError { - var value: Any - - init(value: Any, _ message: String) { - self.value = value - super.init(400, "Not support \(value)\n\(message)") - } -} - -class PluginError: Error { - var code: Int - var message: String - - init(_ code: Int, _ message: String) { - self.code = code - self.message = message - } -} - diff --git a/ios/Classes/equalizer/EqualizerPlugin.h b/ios/Classes/equalizer/EqualizerPlugin.h deleted file mode 100644 index 71d754eb..00000000 --- a/ios/Classes/equalizer/EqualizerPlugin.h +++ /dev/null @@ -1,4 +0,0 @@ -#import - -@interface EqualizerPlugin : NSObject -@end diff --git a/ios/Classes/equalizer/EqualizerPlugin.m b/ios/Classes/equalizer/EqualizerPlugin.m deleted file mode 100644 index 51269e52..00000000 --- a/ios/Classes/equalizer/EqualizerPlugin.m +++ /dev/null @@ -1,15 +0,0 @@ -#import "EqualizerPlugin.h" -#if __has_include() -#import -#else -// Support project import fallback if the generated compatibility header -// is not copied when this plugin is created as a library. -// https://forums.swift.org/t/swift-static-libraries-dont-copy-generated-objective-c-header/19816 -#import "equalizer-Swift.h" -#endif - -@implementation EqualizerPlugin -+ (void)registerWithRegistrar:(NSObject*)registrar { - [SwiftEqualizerPlugin registerWithRegistrar:registrar]; -} -@end diff --git a/ios/Classes/equalizer/SwiftEqualizerPlugin.swift b/ios/Classes/equalizer/SwiftEqualizerPlugin.swift deleted file mode 100644 index 811ff442..00000000 --- a/ios/Classes/equalizer/SwiftEqualizerPlugin.swift +++ /dev/null @@ -1,299 +0,0 @@ -import Flutter -import AVFAudio -import UIKit -import Foundation - -public class SwiftEqualizerPlugin: NSObject, FlutterPlugin { - let registrar: FlutterPluginRegistrar - private var audioEngine: AVAudioEngine? - private var audioUnitEQ: AVAudioUnitEQ? - private var audioPlayerNode: AVAudioPlayerNode? - private var audioOutputNode:AVAudioOutputNode? - private var sessionId:Int? - private var audioEffects: [EffectData]? - private var flautoPlayer = FlautoPlayer() - - public static func register(with registrar: FlutterPluginRegistrar) { - let instance = SwiftEqualizerPlugin(registrar) - let channel = FlutterMethodChannel(name: "dev.offcode.equalizer/method", binaryMessenger: registrar.messenger()) - registrar.addMethodCallDelegate(instance, channel: channel) - } - - init(_ registrar: FlutterPluginRegistrar) { - self.registrar = registrar - super.init() - } - - public func handle(_ call: FlutterMethodCall, result: @escaping FlutterResult) { - switch call.method { - case "init": initEqualizer(call, result) - case "audioEffectSetEnabled": enableEffect(call, result) - case "darwinEqualizerBandSetGain": setEqualizerBandGain(call, result) - default: - result(FlutterMethodNotImplemented) - } - } - - func enableEffect(_ call: FlutterMethodCall, _ result: @escaping FlutterResult ) { - print("Enable Effect!") - guard let arguments = call.arguments as? Dictionary else { - result(FlutterError(code: "ABNORMAL_PARAMETER", message: "no parameters", details: nil)) - return - } - let type = arguments["type"] as! String - let enabled = arguments["enabled"] as! Bool - - print("Set enabled type: \(type) [ \(enabled) ]") - - switch type { - case "DarwinEqualizer": - audioUnitEQ!.bypass = !enabled - default: - result(FlutterError(code: "NOT_INITIALIZED", message: "Not initialized effect \(type)", details: nil)) - } - } - - func setEqualizerBandGain(_ call: FlutterMethodCall, _ result: @escaping FlutterResult ) { - guard let arguments = call.arguments as? Dictionary else { - result(FlutterError(code: "ABNORMAL_PARAMETER", message: "no parameters", details: nil)) - return - } - - let bandIndex = arguments["bandIndex"] as! Int - let gain = Float(arguments["gain"] as! Double) - print("Set band: \(bandIndex) gain to: \(gain)") - audioUnitEQ!.bands[bandIndex].gain = gain - } - - public func initEqualizer(_ call: FlutterMethodCall, _ result: @escaping FlutterResult){ - print("Initialize darwin EQ!") - - guard let arguments = call.arguments as? Dictionary else { - result(FlutterError(code: "ABNORMAL_PARAMETER", message: "no parameters", details: nil)) - return - } - - print(arguments) - - let effectsRaw: [[String: Any?]] = arguments.keys.contains("DarwinEqualizer") ? (arguments["DarwinEqualizer"] as! [[String: Any?]]) : [] - - let equalizerRaw = effectsRaw.filter { rawEffect in - (rawEffect["type"] as! String) == "DarwinEqualizer" - }.first - - if equalizerRaw!["type"] as? String != "DarwinEqualizer" { - result(FlutterError(code: "ABNORMAL_PARAMETER", message: "no DarwinEqualizer type found", details: nil)) - } - - let parameters = equalizerRaw!["parameters"] as! [String: Any] - let rawBands = parameters["bands"] as! [[String: Any]] - let enabled = equalizerRaw!["enabled"] as! Bool - - let frequenciesAndBands = rawBands.map { map in - let frequency = map["centerFrequency"] as! Double - let gain = map["gain"] as! Double - return (Int(frequency), EqualizerEffectData.gainFrom(Float(gain))) - } - - _ = frequenciesAndBands.map { frequency, _ in - frequency - } - - let bands = frequenciesAndBands.map { _, band in - band - } - - if audioEngine == nil { - print("Setting audio engine!") - audioEngine = AVAudioEngine() - - // add equalizer node - if audioUnitEQ == nil { - print("Setting Equalizer!") - audioUnitEQ = AVAudioUnitEQ(numberOfBands: bands.count) //Set bands count - - // Set bands, gains, etc. - for(i, band) in rawBands.enumerated(){ - audioUnitEQ!.bands[i].filterType = .parametric - audioUnitEQ!.bands[i].frequency = band["centerFrequency"] as! Float - audioUnitEQ!.bands[i].bandwidth = 1 // half an octave - audioUnitEQ!.bands[i].gain = EqualizerEffectData.gainFrom(band["gain"] as! Float) - audioUnitEQ!.bands[i].bypass = false - } - audioUnitEQ!.bypass = !enabled //Set enabled - - audioEngine!.attach(audioUnitEQ!) - } else { - print("EQ already initialized!") - } - }else { - print("Audio engine already running!") - } - result("SUCCESS") - } -} - -// fileprivate func createAudioEffects(_ _audioEffects: [EffectData]) throws { -// -// for effect in _audioEffects as [any EffectData] { -// print(effect) -// if let effect = effect as? EqualizerEffectData { -// audioUnitEQ = AVAudioUnitEQ(numberOfBands: effect.parameters.bands.count) -// -// for (i, band) in effect.parameters.bands.enumerated() { -// audioUnitEQ!.bands[i].filterType = .parametric -// audioUnitEQ!.bands[i].frequency = band.centerFrequency -// audioUnitEQ!.bands[i].bandwidth = 1 // half an octave -// audioUnitEQ!.bands[i].gain = EqualizerEffectData.gainFrom(band.gain) -// audioUnitEQ!.bands[i].bypass = false -// } -// -// audioUnitEQ!.bypass = !effect.enabled -// } else { -// throw NotSupportedError(value: effect.type, "When initialize effect") -// } -// } -// -// func parse(from map: [String: Any?]) throws { -// if map["type"] as? String != "DarwinEqualizer" { -// return nil -// } -// -// let parameters = map["parameters"] as! [String: Any] -// -// let rawBands = parameters["bands"] as! [[String: Any]] -// let frequenciesAndBands = rawBands.map { map in -// let frequency = map["centerFrequency"] as! Double -// let gain = map["gain"] as! Double -// return (Int(frequency), EqualizerEffectData.gainFrom(Float(gain))) -// } -// -// let frequencies = frequenciesAndBands.map { frequency, _ in -// frequency -// } -// -// let bands = frequenciesAndBands.map { _, band in -// band -// } - -// let equalizer = try Equalizer(frequencies: frequencies, preSets: [bands]) - -// try equalizer.activate(preset: 0) - -// return equalizer -// } -// } - - - - //===================================================================================================================// - - // public func setAudiosessionId(_ call:FlutterMethodCall, _ result: @escaping FlutterResult){ - // guard let arguments = call.arguments as? Dictionary else { - // result(FlutterError(code: "ABNORMAL_PARAMETER", message: "no parameters", details: nil)) - // return - // } - // audioEngine = AVAudioEngine() - // audioUnitEQ = AVAudioUnitEQ() - // audioPlayerNode = AVAudioPlayerNode() - - // let audioSession = AVAudioSession.sharedInstance() - // do { - // try audioSession.setCategory(.playAndRecord, options: .defaultToSpeaker) - // }catch let error as NSError{ - // print("ERROR:", error) - // } - // do{ - // try audioSession.setActive(true, options: .notifyOthersOnDeactivation) - // }catch let error as NSError { - // print("ERROR:", error) - // } - - // print("audioSession: \(audioSession)") - - // sessionId = arguments["sessionId"] as? Int - - // print("== Set audio session id to: \(String(describing: sessionId))") - // } - - - - - - - - - -// let numberOfBands = arguments["numberOfBands"] as? Int -// let freqs = arguments["freqs"] as? Array -// // in viewDidLoad(): -// audioUnitEQ = AVAudioUnitEQ(numberOfBands: numberOfBands!) -// audioEngine?.attach(audioPlayerNode!) -// audioEngine?.attach(audioUnitEQ!) -// let bands = audioUnitEQ!.bands -// audioEngine!.connect(audioPlayerNode!, to: audioUnitEQ!, format: nil) -// audioEngine!.connect(audioUnitEQ!, to: audioEngine!.outputNode, format: nil) -// for i in 0...(bands.count - 1) { -// bands[i].frequency = Float(freqs![i]) -// bands[i].bypass = false -// // bands[i].filtertype = .parametric -// } -// -// bands[0].gain = -10.0 -// bands[0].filterType = .lowShelf -// bands[1].gain = -10.0 -// bands[1].filterType = .lowShelf -// bands[2].gain = -10.0 -// bands[2].filterType = .lowShelf -// bands[3].gain = 10.0 -// bands[3].filterType = .highShelf -// bands[4].gain = 10.0 -// bands[4].filterType = .highShelf -// -// do { -// if let filepath = Bundle.main.path(forResource: "song", ofType: "mp3") { -// let filepathURL = NSURL.fileURL(withPath: filepath) -// do { -// audioFile = try AVAudioFile(forReading: filepathURL) -// } catch { -// print(error) -// } -// -// audioEngine.prepare() -// do{ -// try audioEngine.start() -// }catch{ -// print(error) -// } -// audioPlayerNode.scheduleFile(audioFile, at: nil, completionHandler: nil) -// audioPlayerNode.play() -// } -// } - -// fileprivate func createAudioEffects(_ call: FlutterMethodCall, _ result: @escaping FlutterResult) throws { -// guard let arguments = call.arguments as? Dictionary else { -// result(FlutterError(code: "ABNORMAL_PARAMETER", message: "no parameters", details: nil)) -// return -// } -// -// -// for effect in audioEffects { -// if let effect = effect as? EqualizerEffectData { -// audioUnitEQ = AVAudioUnitEQ(numberOfBands: effect.parameters.bands.count) -// -// for (i, band) in effect.parameters.bands.enumerated() { -// audioUnitEQ!.bands[i].filterType = .parametric -// audioUnitEQ!.bands[i].frequency = band.centerFrequency -// audioUnitEQ!.bands[i].bandwidth = 1 // half an octave -// audioUnitEQ!.bands[i].gain = Util.gainFrom(band.gain) -// audioUnitEQ!.bands[i].bypass = false -// } -// -// audioUnitEQ!.bypass = !effect.enabled -// } else { -// throw NotSupportedError(value: effect.type, "When initialize effect") -// } -// } -// } - - From a628e7c1654a9ce470161e25d4e7851f552f591b Mon Sep 17 00:00:00 2001 From: Toni Karhu Date: Tue, 31 Jan 2023 08:32:07 +0200 Subject: [PATCH 05/10] First woking initializer for eq Signed-off-by: Toni Karhu --- ios/Classes/FlautoPlayer.h | 2 +- ios/Classes/FlautoPlayer.mm | 7 +- ios/Classes/FlautoPlayerEngine.h | 113 ++++++++++++++-------------- ios/Classes/FlautoPlayerEngine.mm | 121 +++++++++++++----------------- 4 files changed, 112 insertions(+), 131 deletions(-) diff --git a/ios/Classes/FlautoPlayer.h b/ios/Classes/FlautoPlayer.h index 8a06253a..4b967bd7 100644 --- a/ios/Classes/FlautoPlayer.h +++ b/ios/Classes/FlautoPlayer.h @@ -85,7 +85,7 @@ - (long)getPosition; - (long)getDuration; - (void)logDebug: (NSString*)msg; -- (bool)initEqualizer:(NSDictionary*) arguments; +- (void)initEqualizer:(NSDictionary*) params; diff --git a/ios/Classes/FlautoPlayer.mm b/ios/Classes/FlautoPlayer.mm index 664a2969..a1829260 100644 --- a/ios/Classes/FlautoPlayer.mm +++ b/ios/Classes/FlautoPlayer.mm @@ -62,6 +62,7 @@ @implementation FlautoPlayer double latentSpeed; long latentSeek; bool voiceProcessing; + NSDictionary* equalizerParams; } @@ -185,7 +186,7 @@ - (bool)startPlayerCodec: (t_CODEC)codec [self stop]; // To start a fresh new playback if ( (path == nil || [path class] == [NSNull class] ) && codec == pcm16) - m_playerEngine = [[AudioEngine alloc] init: self ]; + m_playerEngine = [[AudioEngine alloc] init: self eqParams: equalizerParams ]; else m_playerEngine = [[AudioPlayerFlauto alloc]init: self]; @@ -475,9 +476,9 @@ - (long)getDuration return [m_playerEngine getDuration]; } -- (bool)initEqualizer:(NSDictionary*) arguments +- (void)initEqualizer:(NSDictionary*) params { - return [m_playerEngine initEqualizer: arguments]; + equalizerParams = params; } diff --git a/ios/Classes/FlautoPlayerEngine.h b/ios/Classes/FlautoPlayerEngine.h index 564788f9..6e0b4f07 100644 --- a/ios/Classes/FlautoPlayerEngine.h +++ b/ios/Classes/FlautoPlayerEngine.h @@ -35,42 +35,41 @@ @protocol FlautoPlayerEngineInterface - - (void) startPlayerFromBuffer: (NSData*)data ; - - (void) startPlayerFromURL: (NSURL*) url codec: (t_CODEC)codec channels: (int)numChannels sampleRate: (long)sampleRate ; - - (long) getDuration; - - (long) getPosition; - - (void) stop; - - (bool) play; - - (bool) resume; - - (bool) pause; - - (bool) setVolume: (double) volume fadeDuration: (NSTimeInterval)fadeDuration; // Volume is between 0.0 and 1.0 - - (bool) setSpeed: (double) speed ; // Speed is between 0.0 and 1.0 to go slower - - (bool) seek: (double) pos; - - (t_PLAYER_STATE) getStatus; - - (int) feed: (NSData*)data; - - (bool)initEqualizer:(NSDictionary*)arguments; + - (void) startPlayerFromBuffer: (NSData*)data ; + - (void) startPlayerFromURL: (NSURL*) url codec: (t_CODEC)codec channels: (int)numChannels sampleRate: (long)sampleRate ; + - (long) getDuration; + - (long) getPosition; + - (void) stop; + - (bool) play; + - (bool) resume; + - (bool) pause; + - (bool) setVolume: (double) volume fadeDuration: (NSTimeInterval)fadeDuration; // Volume is between 0.0 and 1.0 + - (bool) setSpeed: (double) speed ; // Speed is between 0.0 and 1.0 to go slower + - (bool) seek: (double) pos; + - (t_PLAYER_STATE) getStatus; + - (int) feed: (NSData*)data; @end @interface AudioPlayerFlauto : NSObject { } -- (AudioPlayerFlauto*) init: (NSObject*)owner ;// FlutterSoundPlayer* - - - (void) startPlayerFromBuffer: (NSData*)data ; - - (void) startPlayerFromURL: (NSURL*) url codec: (t_CODEC)codec channels: (int)numChannels sampleRate: (long)sampleRate ; - - (void) stop; - - (bool) play; - - (bool) resume; - - (bool) pause; - - (bool) setVolume: (double) volume fadeDuration: (NSTimeInterval)duration ;// Volume is between 0.0 and 1.0 - - (bool) setSpeed: (double) speed ;// Volume is between 0.0 and 1.0 - - (bool) seek: (double) pos; - - (t_PLAYER_STATE) getStatus; - - (AVAudioPlayer*) getAudioPlayer; - - (void) setAudioPlayer: (AVAudioPlayer*)thePlayer; - - (int) feed: (NSData*)data; - - (bool)initEqualizer:(NSDictionary*)arguments; + - (AudioPlayerFlauto*) init: (NSObject*)owner ;// FlutterSoundPlayer* + + - (void) startPlayerFromBuffer: (NSData*)data ; + - (void) startPlayerFromURL: (NSURL*) url codec: (t_CODEC)codec channels: (int)numChannels sampleRate: (long)sampleRate ; + - (void) stop; + - (bool) play; + - (bool) resume; + - (bool) pause; + - (bool) setVolume: (double) volume fadeDuration: (NSTimeInterval)duration ;// Volume is between 0.0 and 1.0 + - (bool) setSpeed: (double) speed ;// Volume is between 0.0 and 1.0 + - (bool) seek: (double) pos; + - (t_PLAYER_STATE) getStatus; + - (AVAudioPlayer*) getAudioPlayer; + - (void) setAudioPlayer: (AVAudioPlayer*)thePlayer; + - (int) feed: (NSData*)data; + - (AVAudioUnitEQ *) setEqualizer:(NSDictionary*) arguments; @end @@ -79,20 +78,20 @@ { // TODO FlutterSoundPlayer* flutterSoundPlayer; // Owner } - - (AudioEngine*) init: (NSObject*)owner; // FlutterSoundPlayer* - - - (void) startPlayerFromBuffer: (NSData*)data ; - - (void) startPlayerFromURL: (NSURL*) url codec: (t_CODEC)codec channels: (int)numChannels sampleRate: (long)sampleRate ; - - (bool) play; - - (void) stop; - - (bool) resume; - - (bool) pause; - - (bool) setVolume: (double) volume fadeDuration:(NSTimeInterval)duration ; - - (bool) setSpeed: (double) speed ; - - (bool) seek: (double) pos; - - (int) getStatus; - - (int) feed: (NSData*)data; - - (float)gainFrom:(float)value; + - (AudioEngine*)init: (NSObject*)owner eqParams: (NSDictionary*) params; + + - (void) startPlayerFromBuffer: (NSData*)data ; + - (void) startPlayerFromURL: (NSURL*) url codec: (t_CODEC)codec channels: (int)numChannels sampleRate: (long)sampleRate ; + - (bool) play; + - (void) stop; + - (bool) resume; + - (bool) pause; + - (bool) setVolume: (double) volume fadeDuration:(NSTimeInterval)duration ; + - (bool) setSpeed: (double) speed ; + - (bool) seek: (double) pos; + - (int) getStatus; + - (int) feed: (NSData*)data; + - (float)gainFrom:(float)value; @end @@ -101,19 +100,19 @@ { // TODO FlutterSoundPlayer* flutterSoundPlayer; // Owner } - - (AudioEngineFromMic*) init: (NSObject*)owner; // FlutterSoundPlayer* - - - (void) startPlayerFromBuffer: (NSData*)data ; - - (void) startPlayerFromURL: (NSURL*) url codec: (t_CODEC)codec channels: (int)numChannels sampleRate: (long)sampleRate ; - - (void) stop; - - (bool) play; - - (bool) resume; - - (bool) pause; - - (bool) setVolume: (double) volume fadeDuration:(NSTimeInterval)duration ; - - (bool) setSpeed: (double) speed; - - (bool) seek: (double) pos; - - (int) getStatus; - - (int) feed: (NSData*)data; + - (AudioEngineFromMic*) init: (NSObject*)owner; // FlutterSoundPlayer* + + - (void) startPlayerFromBuffer: (NSData*)data ; + - (void) startPlayerFromURL: (NSURL*) url codec: (t_CODEC)codec channels: (int)numChannels sampleRate: (long)sampleRate ; + - (void) stop; + - (bool) play; + - (bool) resume; + - (bool) pause; + - (bool) setVolume: (double) volume fadeDuration:(NSTimeInterval)duration ; + - (bool) setSpeed: (double) speed; + - (bool) seek: (double) pos; + - (int) getStatus; + - (int) feed: (NSData*)data; @end diff --git a/ios/Classes/FlautoPlayerEngine.mm b/ios/Classes/FlautoPlayerEngine.mm index ba0f6c98..78d475c8 100644 --- a/ios/Classes/FlautoPlayerEngine.mm +++ b/ios/Classes/FlautoPlayerEngine.mm @@ -160,19 +160,20 @@ @implementation AudioEngine AVAudioOutputNode* outputNode; AVAudioConverter* converter; CFTimeInterval mStartPauseTime ; // The time when playback was paused - CFTimeInterval systemTime ; //The time when StartPlayer() ; + CFTimeInterval systemTime ; //The time when StartPlayer() ; double mPauseTime ; // The number of seconds during the total Pause mode NSData* waitingBlock; long m_sampleRate ; int m_numChannels; } - - (AudioEngine*)init: (FlautoPlayer*)owner + - (AudioEngine*)init: (FlautoPlayer*)owner eqParams: (NSDictionary*) params { flutterSoundPlayer = owner; waitingBlock = nil; engine = [[AVAudioEngine alloc] init]; outputNode = [engine outputNode]; + eq = [self setEqualizer: params]; if (@available(iOS 13.0, *)) { if ([flutterSoundPlayer isVoiceProcessingEnabled]) { @@ -247,64 +248,51 @@ - (void) setEqiualizerBandGain: (int) bandIndex gain: (double) gain band.gain = gain; } - - (bool) initEqualizer:(NSDictionary*) arguments { + - (AVAudioUnitEQ *) setEqualizer:(NSDictionary*) arguments; { NSLog(@"Initialize darwin EQ! (LAST PLACE TO CALL)"); [flutterSoundPlayer logDebug:@"Initialize darwin EQ!"]; - + + AVAudioUnitEQ* _eq; + + // Collect all DarwinEqulizer related NSArray *effectsRaw = [arguments objectForKey:@"DarwinEqualizer"]; if (!effectsRaw) { [flutterSoundPlayer logDebug:@"no DarwinEqualizer type found"]; - return false; + throw @"no DarwinEqualizer type found"; } - + + // NSDictionary *equalizerRaw = [effectsRaw filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id _Nullable evaluatedObject, NSDictionary * _Nullable bindings) { return [[evaluatedObject objectForKey:@"type"] isEqualToString:@"DarwinEqualizer"]; }]].firstObject; - + + // Collect parameters NSDictionary *parameters = [equalizerRaw objectForKey:@"parameters"]; + // Collect bands NSArray *rawBands = [parameters objectForKey:@"bands"]; + // Get enabled status BOOL enabled = [[equalizerRaw objectForKey:@"enabled"] boolValue]; -// NSArray *frequenciesAndBands = [rawBands filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(id _Nullable evaluatedObject, NSDictionary * _Nullable bindings) { -// return [evaluatedObject objectForKey:@"centerFrequency"] && [evaluatedObject objectForKey:@" Gain"]; -// }]]; -// NSArray *frequencies = [frequenciesAndBands valueForKeyPath:@"centerFrequency"]; -// NSArray *bands = [frequenciesAndBands valueForKeyPath:@" Gain"]; - -// if (!engine) { -// // [flutterSoundPlayer logDebug: @"Setting audio engine!"]; -// // NSLog(@"Setting audio engine!"); -// // engine = [[AVAudioEngine alloc] init]; -// NSLog(@"No Audio engine found!"); -// return false; -// } - if (!eq) { - [flutterSoundPlayer logDebug: @"Setting Equalizer!"]; - NSLog(@"Setting Equalizer!"); - eq = [[AVAudioUnitEQ alloc] initWithNumberOfBands:(unsigned int)rawBands.count]; - - for (int i = 0; i < rawBands.count; i++) { - NSDictionary *band = rawBands[i]; - eq.bands[i].filterType = AVAudioUnitEQFilterTypeParametric; - eq.bands[i].frequency = [[band objectForKey:@"centerFrequency"] floatValue]; - eq.bands[i].bandwidth = 1.0f; - eq.bands[i].gain = [self gainFrom: ([[band objectForKey:@" Gain"] floatValue])]; - eq.bands[i].bypass = NO; - } - eq.bypass = !enabled; -// [self attachEqualizer: eq]; - }else{ - NSLog(@"Equalizer already set!"); - [flutterSoundPlayer logDebug: @"Equalizer already set!"]; - return true; + [flutterSoundPlayer logDebug: @"Setting Equalizer!"]; + NSLog(@"Setting Equalizer!"); + + // Init equalizer + _eq = [[AVAudioUnitEQ alloc] initWithNumberOfBands:(unsigned int)rawBands.count]; + + // Set bands + for (int i = 0; i < rawBands.count; i++) { + NSDictionary *band = rawBands[i]; + _eq.bands[i].filterType = AVAudioUnitEQFilterTypeParametric; + _eq.bands[i].frequency = [[band objectForKey:@"centerFrequency"] floatValue]; + _eq.bands[i].bandwidth = 1.0f; + _eq.bands[i].gain = [self gainFrom: ([[band objectForKey:@" Gain"] floatValue])]; + _eq.bands[i].bypass = NO; } - return true; -// } else { -// NSLog(@"Audio engine already set!"); -// [flutterSoundPlayer logDebug:@"Audio engine already set!"]; -// return true; -// } + // Enable eq + _eq.bypass = !enabled; + + return _eq; } @@ -463,24 +451,24 @@ - (int) feed: (NSData*)data waitingBlock = data; return 0; } - } + } --(bool) setVolume: (double) volume fadeDuration: (NSTimeInterval)fadeDuration// TODO -{ - return true; // TODO -} + -(bool) setVolume: (double) volume fadeDuration: (NSTimeInterval)fadeDuration// TODO + { + return true; // TODO + } -- (bool) setSpeed: (double) speed -{ - return true; // TODO -} + - (bool) setSpeed: (double) speed + { + return true; // TODO + } -- (float) gainFrom:(float) value -{ - //Equalize the level between iOS and Android - return value * 2.8; -} + - (float) gainFrom:(float) value + { + //Equalize the level between iOS and Android + return value * 2.8; + } @end @@ -555,7 +543,6 @@ -(void) startPlayerFromURL: (NSURL*) url codec: (t_CODEC)codec channels: (int)n -(void) stop { - if (engine != nil) { [engine stop]; @@ -607,16 +594,10 @@ -(bool) setSpeed: (double) speed // TODO return true; // TODO } - - (int) feed: (NSData*)data - { - return 0; - } - - - (bool)initEqualizer:(NSDictionary*)arguments{ - NSLog(@"Initialize darwin EQ! (MICROPHONE - NOT IMPLEMENTED)"); - return true; - //TODO - } + - (int) feed: (NSData*)data + { + return 0; + } From 0d1918cffd0f4032b4d545b0127a35a7b8cf7e73 Mon Sep 17 00:00:00 2001 From: Toni Karhu Date: Thu, 2 Feb 2023 12:50:56 +0200 Subject: [PATCH 06/10] First working version Signed-off-by: Toni Karhu --- ios/Classes/FlautoPlayer.h | 2 + ios/Classes/FlautoPlayer.mm | 10 ++ ios/Classes/FlautoPlayerEngine.h | 2 + ios/Classes/FlautoPlayerEngine.mm | 252 +++++++++++++++++++----------- 4 files changed, 173 insertions(+), 93 deletions(-) diff --git a/ios/Classes/FlautoPlayer.h b/ios/Classes/FlautoPlayer.h index 4b967bd7..efab9e7b 100644 --- a/ios/Classes/FlautoPlayer.h +++ b/ios/Classes/FlautoPlayer.h @@ -86,6 +86,8 @@ - (long)getDuration; - (void)logDebug: (NSString*)msg; - (void)initEqualizer:(NSDictionary*) params; +- (void)enableEqualizer:(bool)enable; +- (void) setEqualizerBandGain: (int) bandIndex gain: (float) gain; diff --git a/ios/Classes/FlautoPlayer.mm b/ios/Classes/FlautoPlayer.mm index a1829260..50d1df1a 100644 --- a/ios/Classes/FlautoPlayer.mm +++ b/ios/Classes/FlautoPlayer.mm @@ -481,6 +481,16 @@ - (void)initEqualizer:(NSDictionary*) params equalizerParams = params; } +- (void)enableEqualizer:(bool) enable +{ + [m_playerEngine enableEqualizer: enable]; +} + +- (void) setEqualizerBandGain: (int) bandIndex gain: (float) gain +{ + [m_playerEngine setEqualizerBandGain: bandIndex gain: gain]; +} + - (NSDictionary*)getProgress { diff --git a/ios/Classes/FlautoPlayerEngine.h b/ios/Classes/FlautoPlayerEngine.h index 6e0b4f07..05820c24 100644 --- a/ios/Classes/FlautoPlayerEngine.h +++ b/ios/Classes/FlautoPlayerEngine.h @@ -48,6 +48,8 @@ - (bool) seek: (double) pos; - (t_PLAYER_STATE) getStatus; - (int) feed: (NSData*)data; + - (void) enableEqualizer:(bool) enabled; + - (void) setEqualizerBandGain: (int) bandIndex gain: (float) gain; @end diff --git a/ios/Classes/FlautoPlayerEngine.mm b/ios/Classes/FlautoPlayerEngine.mm index 78d475c8..c9401cf0 100644 --- a/ios/Classes/FlautoPlayerEngine.mm +++ b/ios/Classes/FlautoPlayerEngine.mm @@ -36,59 +36,63 @@ @implementation AudioPlayerFlauto - (AVAudioPlayer*) getAudioPlayer { - return player; + return player; } - (void) setAudioPlayer: (AVAudioPlayer*)thePlayer { - player = thePlayer; + player = thePlayer; } + - (AudioPlayerFlauto*)init: (FlautoPlayer*)owner + { + flautoPlayer = owner; + return [super init]; + } - - - (AudioPlayerFlauto*)init: (FlautoPlayer*)owner - { - flautoPlayer = owner; - return [super init]; - } - - -(void) startPlayerFromBuffer: (NSData*) dataBuffer - { - NSError* error = [[NSError alloc] init]; - [self setAudioPlayer: [[AVAudioPlayer alloc] initWithData: dataBuffer error: &error]]; - [self getAudioPlayer].delegate = flautoPlayer; - } - - -(void) startPlayerFromURL: (NSURL*) url codec: (t_CODEC)codec channels: (int)numChannels sampleRate: (long)sampleRate - - { - [self setAudioPlayer: [[AVAudioPlayer alloc] initWithContentsOfURL: url error: nil] ]; - [self getAudioPlayer].delegate = flautoPlayer; + -(void) startPlayerFromBuffer: (NSData*) dataBuffer + { + NSError* error = [[NSError alloc] init]; + [self setAudioPlayer: [[AVAudioPlayer alloc] initWithData: dataBuffer error: &error]]; + [self getAudioPlayer].delegate = flautoPlayer; } + -(void) startPlayerFromURL: (NSURL*) url codec: (t_CODEC)codec channels: (int)numChannels sampleRate: (long)sampleRate + { + [self setAudioPlayer: [[AVAudioPlayer alloc] initWithContentsOfURL: url error: nil] ]; + [self getAudioPlayer].delegate = flautoPlayer; + } - -(long) getDuration - { - double duration = [self getAudioPlayer].duration; - return (long)(duration * 1000.0); - } + -(long) getDuration + { + double duration = [self getAudioPlayer].duration; + return (long)(duration * 1000.0); + } - -(long) getPosition - { - double position = [self getAudioPlayer].currentTime ; - return (long)( position * 1000.0); - } + -(long) getPosition + { + double position = [self getAudioPlayer].currentTime ; + return (long)( position * 1000.0); + } - -(void) stop - { - [ [self getAudioPlayer] stop]; - [self setAudioPlayer: nil]; - } + -(void) stop + { + [ [self getAudioPlayer] stop]; + [self setAudioPlayer: nil]; + } -(bool) play { - bool b = [ [self getAudioPlayer] play]; - return b; + // This fixes the audio output to the speaker (LAPSI fix) + NSError *error = nil; + [[AVAudioSession sharedInstance] + setCategory: AVAudioSessionCategoryPlayAndRecord + mode: AVAudioSessionModeDefault + options: AVAudioSessionCategoryOptionAllowBluetoothA2DP | AVAudioSessionCategoryOptionAllowBluetooth + error:&error]; + + bool b = [ [self getAudioPlayer] play]; + return b; } @@ -173,7 +177,7 @@ - (AudioEngine*)init: (FlautoPlayer*)owner eqParams: (NSDictionary*) params waitingBlock = nil; engine = [[AVAudioEngine alloc] init]; outputNode = [engine outputNode]; - eq = [self setEqualizer: params]; + playerNode = [[AVAudioPlayerNode alloc] init]; if (@available(iOS 13.0, *)) { if ([flutterSoundPlayer isVoiceProcessingEnabled]) { @@ -188,68 +192,96 @@ - (AudioEngine*)init: (FlautoPlayer*)owner eqParams: (NSDictionary*) params [flutterSoundPlayer logDebug: @"WARNING! VoiceProcessing is only available on iOS13+"]; } - outputFormat = [outputNode inputFormatForBus: 0]; - playerNode = [[AVAudioPlayerNode alloc] init]; + outputFormat = [outputNode inputFormatForBus: 0]; + + eq = [self setEqualizer: params]; + + if(eq){ + [flutterSoundPlayer logDebug: @"EQ found! Attach it"]; + [engine attachNode: eq]; + } else { + [flutterSoundPlayer logDebug: @"EQ NOT FOUND!!!"]; + } + + [engine attachNode: playerNode]; + AVAudioMixerNode *mixerNode = [engine mainMixerNode]; +// AVAudioFormat * format = [[engine mainMixerNode] outputFormatForBus:0]; if(eq){ - NSLog(@"EQ found! Attach it"); - [engine attachNode: eq]; + [flutterSoundPlayer logDebug: @"EQ found! Connect it"]; + [engine connect: playerNode to: eq format: outputFormat]; + [engine connect: eq to: mixerNode format: outputFormat]; } else { - NSLog(@"EQ NOT FOUND!!!"); - } - - [engine attachNode: playerNode]; - - if(eq){ - NSLog(@"EQ found! Connect it"); - [engine connect: playerNode to: eq format: playerFormat]; - [engine connect: eq to: outputNode format: outputFormat]; + [engine connect: playerNode to: outputNode format: outputFormat]; } + + [engine prepare]; - [engine connect: playerNode to: outputNode format: outputFormat]; bool b = [engine startAndReturnError: nil]; if (!b) { + NSLog(@"Cannot start the audio engine!"); [flutterSoundPlayer logDebug: @"Cannot start the audio engine"]; } mPauseTime = 0.0; // Total number of seconds in pause mode mStartPauseTime = -1; // Not in paused mode systemTime = CACurrentMediaTime(); // The time when started - return [super init]; - } - - - (void) enableEffect: (NSString *) type enabled: (bool) enabled - { - if ([type isEqual: @"DarwinEqualizer"]) - { - if (eq == nil) - { - [flutterSoundPlayer logDebug: @"Cannot enable the equalizer because it is not initialized"]; - return; - } - eq.bypass = !enabled; - } + return [super init]; } - - (void) setEqiualizerBandGain: (int) bandIndex gain: (double) gain + - (void) enableEqualizer:(bool) enabled { if (eq == nil) { - [flutterSoundPlayer logDebug: @"Cannot set the equalizer band gain because the equalizer is not initialized"]; + [flutterSoundPlayer logDebug: @"Cannot enable the equalizer because it is not initialized"]; return; } - if (bandIndex < 0 || bandIndex >= eq.bands.count) - { - [flutterSoundPlayer logDebug: @"Cannot set the equalizer band gain because the band index is out of range"]; - return; - } - AVAudioUnitEQFilterParameters* band = eq.bands[bandIndex]; - band.gain = gain; + + [engine prepare]; + eq.bypass = !enabled; + + // Start the engine. + NSError *error; + [engine startAndReturnError:&error]; + if (error) { + NSLog(@"error:%@", error); + } + } + + - (void) setEqualizerBandGain: (int) bandIndex gain: (float) gain + { + if (eq == nil) + { + [flutterSoundPlayer logDebug: @"Cannot set the equalizer band gain because the equalizer is not initialized"]; + NSLog(@"Cannot set the equalizer band gain because the equalizer is not initialized"); + return; + } + if (bandIndex < 0 || bandIndex >= eq.bands.count) + { + [flutterSoundPlayer logDebug: @"Cannot set the equalizer band gain because the band index is out of range"]; + NSLog(@"Cannot set the equalizer band gain because the band index is out of range"); + return; + } + + [engine prepare]; + +// NSLog(@"Received (flutter): band %d: to: %f ",bandIndex, gain); + +// NSLog(@"Setting gain for band: %d to %f", bandIndex, [self gainFrom: gain]); + + AVAudioUnitEQFilterParameters* band = eq.bands[bandIndex]; + band.gain = [self gainFrom: gain]; + + // Start the engine. + NSError *error; + [engine startAndReturnError:&error]; + if (error) { + NSLog(@"error:%@", error); + } } - (AVAudioUnitEQ *) setEqualizer:(NSDictionary*) arguments; { - NSLog(@"Initialize darwin EQ! (LAST PLACE TO CALL)"); [flutterSoundPlayer logDebug:@"Initialize darwin EQ!"]; AVAudioUnitEQ* _eq; @@ -274,24 +306,39 @@ - (AVAudioUnitEQ *) setEqualizer:(NSDictionary*) arguments; { BOOL enabled = [[equalizerRaw objectForKey:@"enabled"] boolValue]; [flutterSoundPlayer logDebug: @"Setting Equalizer!"]; - NSLog(@"Setting Equalizer!"); // Init equalizer - _eq = [[AVAudioUnitEQ alloc] initWithNumberOfBands:(unsigned int)rawBands.count]; +// _eq = [[AVAudioUnitEQ alloc] initWithNumberOfBands: 1]; +// [_eq setBypass : false]; +// [_eq setGlobalGain : 1]; +// _eq.bands[0].frequency = 1000; +// _eq.bands[0].gain = -60.0; +// _eq.bands[0].bypass = false; +// _eq.bands[0].filterType = AVAudioUnitEQFilterTypeParametric; + - // Set bands + _eq = [[AVAudioUnitEQ alloc] initWithNumberOfBands:(unsigned int)rawBands.count + 1]; + [_eq setBypass : !enabled]; + [_eq setGlobalGain : 1]; +// // Set bands for (int i = 0; i < rawBands.count; i++) { NSDictionary *band = rawBands[i]; - _eq.bands[i].filterType = AVAudioUnitEQFilterTypeParametric; _eq.bands[i].frequency = [[band objectForKey:@"centerFrequency"] floatValue]; - _eq.bands[i].bandwidth = 1.0f; +// _eq.bands[i].bandwidth = 1.0f; _eq.bands[i].gain = [self gainFrom: ([[band objectForKey:@" Gain"] floatValue])]; - _eq.bands[i].bypass = NO; + _eq.bands[i].bypass = false; + _eq.bands[i].filterType = AVAudioUnitEQFilterTypeParametric; } - // Enable eq - _eq.bypass = !enabled; - + //Band pass filter + AVAudioUnitEQFilterParameters *bandPassFilter; + bandPassFilter = _eq.bands[(unsigned int)rawBands.count]; + bandPassFilter.frequency = 1000; +// bandPassFilter.bandwidth = 2.0f; + bandPassFilter.gain = -60; + bandPassFilter.bypass = false; + bandPassFilter.filterType = AVAudioUnitEQFilterTypeLowPass; + return _eq; } @@ -344,13 +391,16 @@ -(void) stop { converter = nil; // ARC will dealloc the converter (I hope ;-) ) } + if(eq != nil){ + eq = nil; + } } } -(bool) play { - [playerNode play]; - return true; + [playerNode play]; + return true; } -(bool) resume @@ -393,10 +443,16 @@ - (int) feed: (NSData*)data if (ready < NB_BUFFERS ) { int ln = (int)[data length]; - int frameLn = ln/2; - int frameLength = 8*frameLn;// Two octets for a frame (Monophony, INT Linear 16) + int frameLn = ln/4; + int frameLength = frameLn;// Two octets for a frame (Monophony, INT Linear 16) - playerFormat = [[AVAudioFormat alloc] initWithCommonFormat: AVAudioPCMFormatInt16 sampleRate: (double)m_sampleRate channels: m_numChannels interleaved: NO]; + AVAudioChannelLayout *chLayout = [[AVAudioChannelLayout alloc] initWithLayoutTag:kAudioChannelLayoutTag_Stereo]; + playerFormat = [[AVAudioFormat alloc] + initWithCommonFormat: AVAudioPCMFormatInt16 + sampleRate: (double)m_sampleRate + // channels: m_numChannels + interleaved: YES + channelLayout: chLayout]; AVAudioPCMBuffer* thePCMInputBuffer = [[AVAudioPCMBuffer alloc] initWithPCMFormat: playerFormat frameCapacity: frameLn]; memcpy((unsigned char*)(thePCMInputBuffer.int16ChannelData[0]), [data bytes], ln); @@ -466,8 +522,8 @@ - (bool) setSpeed: (double) speed - (float) gainFrom:(float) value { - //Equalize the level between iOS and Android - return value * 2.8; + //Equalize the level between iOS and Android + return value * 2.8; } @@ -599,6 +655,16 @@ - (int) feed: (NSData*)data return 0; } +- (void)enableEqualizer:(bool)enabled { + NSLog(@"enableEqualizer not implemented (MIC)"); +} + + +- (void)setEqualizerBandGain:(int)bandIndex gain:(float)gain { + NSLog(@"setEqualizerBandGain not implemented (MIC)"); +} + + //------------------------------------------------------------------------------------------------------------------------------------------- From 3de70ed90947064277ca644629694ba59f5107b9 Mon Sep 17 00:00:00 2001 From: Toni Karhu Date: Thu, 2 Feb 2023 13:46:21 +0200 Subject: [PATCH 07/10] Change lowpass filter to 2000 Signed-off-by: Toni Karhu --- ios/Classes/FlautoPlayerEngine.mm | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/ios/Classes/FlautoPlayerEngine.mm b/ios/Classes/FlautoPlayerEngine.mm index c9401cf0..ac7c75f6 100644 --- a/ios/Classes/FlautoPlayerEngine.mm +++ b/ios/Classes/FlautoPlayerEngine.mm @@ -308,15 +308,6 @@ - (AVAudioUnitEQ *) setEqualizer:(NSDictionary*) arguments; { [flutterSoundPlayer logDebug: @"Setting Equalizer!"]; // Init equalizer -// _eq = [[AVAudioUnitEQ alloc] initWithNumberOfBands: 1]; -// [_eq setBypass : false]; -// [_eq setGlobalGain : 1]; -// _eq.bands[0].frequency = 1000; -// _eq.bands[0].gain = -60.0; -// _eq.bands[0].bypass = false; -// _eq.bands[0].filterType = AVAudioUnitEQFilterTypeParametric; - - _eq = [[AVAudioUnitEQ alloc] initWithNumberOfBands:(unsigned int)rawBands.count + 1]; [_eq setBypass : !enabled]; [_eq setGlobalGain : 1]; @@ -333,7 +324,7 @@ - (AVAudioUnitEQ *) setEqualizer:(NSDictionary*) arguments; { //Band pass filter AVAudioUnitEQFilterParameters *bandPassFilter; bandPassFilter = _eq.bands[(unsigned int)rawBands.count]; - bandPassFilter.frequency = 1000; + bandPassFilter.frequency = 2000; // bandPassFilter.bandwidth = 2.0f; bandPassFilter.gain = -60; bandPassFilter.bypass = false; From b4e5014735681ccd102467d67d7f45f54e7e236b Mon Sep 17 00:00:00 2001 From: Toni Karhu Date: Sun, 5 Feb 2023 15:27:39 +0200 Subject: [PATCH 08/10] Add first android related eq calls Signed-off-by: Toni Karhu --- .../com/dooboolab/TauEngine/FlautoPlayer.java | 199 +++++++++++++----- 1 file changed, 150 insertions(+), 49 deletions(-) diff --git a/android/src/main/java/com/dooboolab/TauEngine/FlautoPlayer.java b/android/src/main/java/com/dooboolab/TauEngine/FlautoPlayer.java index 82b7fccc..a8dc016c 100644 --- a/android/src/main/java/com/dooboolab/TauEngine/FlautoPlayer.java +++ b/android/src/main/java/com/dooboolab/TauEngine/FlautoPlayer.java @@ -20,22 +20,25 @@ import android.media.MediaPlayer; +import android.media.audiofx.AudioEffect; +import android.media.audiofx.Equalizer; +import android.media.audiofx.LoudnessEnhancer; import android.os.Build; import android.os.Handler; import android.os.Looper; -import android.media.AudioFocusRequest; - import java.io.File; import java.io.FileOutputStream; +import java.util.ArrayList; import java.util.HashMap; +import java.util.Iterator; +import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Timer; import java.util.TimerTask; -import java.lang.Thread; import com.dooboolab.TauEngine.Flauto.*; -import com.dooboolab.TauEngine.Flauto; public class FlautoPlayer implements MediaPlayer.OnErrorListener { @@ -102,6 +105,12 @@ public class FlautoPlayer implements MediaPlayer.OnErrorListener static int currentPlayerID = 0; private int myPlayerId = 0; + //--------------- EQ related -----------------------// + private List rawAudioEffects = new ArrayList(); + private List audioEffects = new ArrayList(); + private Map audioEffectsMap = new HashMap(); + private Integer audioSessionId; + static final String ERR_UNKNOWN = "ERR_UNKNOWN"; static final String ERR_PLAYER_IS_NULL = "ERR_PLAYER_IS_NULL"; @@ -195,7 +204,6 @@ public boolean startPlayer (t_CODEC codec, String fromURI, byte[] dataBuffer, in } } - try { if (fromURI == null && codec == t_CODEC.pcm16) @@ -325,34 +333,32 @@ void setTimer(long duration) TimerTask task = new TimerTask() { @Override public void run() { - mainHandler.post(new Runnable() + mainHandler.post(new Runnable() + { + @Override + public void run() { - @Override - public void run() + try { - try + if (player != null) { - if (player != null) - { - long position = player._getCurrentPosition(); - long duration = player._getDuration(); - if (position > duration) - { - position = duration; - } - m_callBack.updateProgress(position, duration); + long position = player._getCurrentPosition(); + long duration = player._getDuration(); + if (position > duration) + { + position = duration; } - } catch (Exception e) - { - logDebug( "Exception: " + e.toString()); - stopPlayer(); + m_callBack.updateProgress(position, duration); } + } catch (Exception e) + { + logDebug( "Exception: " + e.toString()); + stopPlayer(); } - }); - - - } + } + }); + } }; mTimer = new Timer(); @@ -398,34 +404,34 @@ void stop() public boolean play() { - if (player == null) + if (player == null) + { + return false; + } + try + { + if (latentVolume >= 0) { - return false; + setVolume(latentVolume); } - try + if (latentSpeed >= 0) { - if (latentVolume >= 0) - { - setVolume(latentVolume); - } - if (latentSpeed >= 0) - { - setSpeed(latentSpeed); - } - if (subsDurationMillis > 0) - setTimer(subsDurationMillis); - if (latentSeek >= 0) - { - seekToPlayer(latentSeek); - } + setSpeed(latentSpeed); + } + if (subsDurationMillis > 0) + setTimer(subsDurationMillis); + if (latentSeek >= 0) + { + seekToPlayer(latentSeek); + } - }catch (Exception e) - { + }catch (Exception e) + { - } - player._play(); - return true; + } + player._play(); + return true; } public boolean isDecoderSupported (t_CODEC codec ) @@ -568,9 +574,104 @@ public Map getProgress ( ) return dic; } + // ----------------------------Equalizer related----------------------------------------// + + public void initEqualizer(Integer audioSessionId) throws Exception { + System.out.printf("audioSessionId: %d", audioSessionId); + if(audioSessionId == null) { + this.audioSessionId = this.getAudioSessionId(); + } else { + this.audioSessionId = audioSessionId; + } + + // Init with basic info + this.rawAudioEffects.add(mapOf("type","AndroidEqualizer", "enabled", false)); + + clearAudioEffects(); + if (this.audioSessionId != null) { + for (Object rawAudioEffect : this.rawAudioEffects) { + Map json = (Map)rawAudioEffect; + AudioEffect audioEffect = decodeAudioEffect(rawAudioEffect, this.audioSessionId); + if ((Boolean)json.get("enabled")) { + audioEffect.setEnabled(true); + } + this.audioEffects.add(audioEffect); + this.audioEffectsMap.put((String)json.get("type"), audioEffect); + } + } + } + + public void setAudioSessionId(Integer audioSessionId) { + this.audioSessionId = audioSessionId; + } + + public void equalizerBandSetGain(int bandIndex, double gain) { + ((Equalizer) Objects.requireNonNull(this.audioEffectsMap.get("AndroidEqualizer"))).setBandLevel((short)bandIndex, + (short)(Math.round(gain * 1000.0))); + } + + public void audioEffectSetEnabled(String type, boolean enabled) { + Objects.requireNonNull(this.audioEffectsMap.get(type)).setEnabled(enabled); + } + + public Map equalizerAudioEffectGetParameters() { + Equalizer equalizer = (Equalizer)this.audioEffectsMap.get("AndroidEqualizer"); + ArrayList rawBands = new ArrayList<>(); + for (short i = 0; i < Objects.requireNonNull(equalizer).getNumberOfBands(); i++) { + rawBands.add(mapOf( + "index", i, + "lowerFrequency", (double)equalizer.getBandFreqRange(i)[0] / 1000.0, + "upperFrequency", (double)equalizer.getBandFreqRange(i)[1] / 1000.0, + "centerFrequency", (double)equalizer.getCenterFreq(i) / 1000.0, + "gain", equalizer.getBandLevel(i) / 1000.0 + )); + } + return mapOf( + "parameters", mapOf( + "minDecibels", equalizer.getBandLevelRange()[0] / 1000.0, + "maxDecibels", equalizer.getBandLevelRange()[1] / 1000.0, + "bands", rawBands + ) + ); + } + + private AudioEffect decodeAudioEffect(final Object json, int audioSessionId) { + Map map = (Map) json; + String type = (String) map.get("type"); + switch (Objects.requireNonNull(type)) { + case "AndroidLoudnessEnhancer": + if (Build.VERSION.SDK_INT < 19) + throw new RuntimeException("AndroidLoudnessEnhancer requires minSdkVersion >= 19"); + int targetGain = (int) Math.round((((Double) map.get("targetGain")) * 1000.0)); + LoudnessEnhancer loudnessEnhancer = new LoudnessEnhancer(audioSessionId); + loudnessEnhancer.setTargetGain(targetGain); + return loudnessEnhancer; + case "AndroidEqualizer": + return new Equalizer(0, audioSessionId); + default: + throw new IllegalArgumentException("Unknown AudioEffect type: " + map.get("type")); + } + } + + private void clearAudioEffects() { + for (Iterator it = this.audioEffects.iterator(); it.hasNext();) { + AudioEffect audioEffect = it.next(); + audioEffect.release(); + it.remove(); + } + this.audioEffectsMap.clear(); + } + + static Map mapOf(Object... args) { + Map map = new HashMap<>(); + for (int i = 0; i < args.length; i += 2) { + map.put((String)args[i], args[i + 1]); + } + return map; + } - void logDebug (String msg) + void logDebug (String msg) { m_callBack.log ( t_LOG_LEVEL.DBG , msg); } From 92e94a9af24f5c795dc3ec593746bf852493ec7f Mon Sep 17 00:00:00 2001 From: Toni Karhu Date: Tue, 7 Feb 2023 15:10:30 +0200 Subject: [PATCH 09/10] Remove low pass filter Signed-off-by: Toni Karhu --- ios/Classes/FlautoPlayerEngine.mm | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/ios/Classes/FlautoPlayerEngine.mm b/ios/Classes/FlautoPlayerEngine.mm index ac7c75f6..1d42ab04 100644 --- a/ios/Classes/FlautoPlayerEngine.mm +++ b/ios/Classes/FlautoPlayerEngine.mm @@ -321,14 +321,14 @@ - (AVAudioUnitEQ *) setEqualizer:(NSDictionary*) arguments; { _eq.bands[i].filterType = AVAudioUnitEQFilterTypeParametric; } - //Band pass filter - AVAudioUnitEQFilterParameters *bandPassFilter; - bandPassFilter = _eq.bands[(unsigned int)rawBands.count]; - bandPassFilter.frequency = 2000; -// bandPassFilter.bandwidth = 2.0f; - bandPassFilter.gain = -60; - bandPassFilter.bypass = false; - bandPassFilter.filterType = AVAudioUnitEQFilterTypeLowPass; +// //Band pass filter +// AVAudioUnitEQFilterParameters *bandPassFilter; +// bandPassFilter = _eq.bands[(unsigned int)rawBands.count]; +// bandPassFilter.frequency = 2000; +//// bandPassFilter.bandwidth = 2.0f; +// bandPassFilter.gain = -60; +// bandPassFilter.bypass = false; +// bandPassFilter.filterType = AVAudioUnitEQFilterTypeLowPass; return _eq; } From 397742ac2155f7e91a7509683e352be666af7c7c Mon Sep 17 00:00:00 2001 From: Toni Karhu Date: Thu, 9 Feb 2023 13:13:29 +0200 Subject: [PATCH 10/10] Remove unused call Signed-off-by: Toni Karhu --- ios/Classes/FlautoPlayerEngine.mm | 8 -------- 1 file changed, 8 deletions(-) diff --git a/ios/Classes/FlautoPlayerEngine.mm b/ios/Classes/FlautoPlayerEngine.mm index 1d42ab04..a2e29740 100644 --- a/ios/Classes/FlautoPlayerEngine.mm +++ b/ios/Classes/FlautoPlayerEngine.mm @@ -83,14 +83,6 @@ -(void) stop -(bool) play { - // This fixes the audio output to the speaker (LAPSI fix) - NSError *error = nil; - [[AVAudioSession sharedInstance] - setCategory: AVAudioSessionCategoryPlayAndRecord - mode: AVAudioSessionModeDefault - options: AVAudioSessionCategoryOptionAllowBluetoothA2DP | AVAudioSessionCategoryOptionAllowBluetooth - error:&error]; - bool b = [ [self getAudioPlayer] play]; return b; }