diff --git a/.vscode/launch.json b/.vscode/launch.json index 72ab720a..1574bd71 100755 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -8,7 +8,7 @@ "name": "Flutter debug", "type": "dart", "request": "launch", - "program": "lib/main.dart", + "program": "lib/filters/pitchshift.dart", "flutterMode": "debug", "cwd": "${workspaceFolder}/example" }, @@ -16,7 +16,7 @@ "name": "Flutter profile", "type": "dart", "request": "launch", - "program": "lib/main.dart", + "program": "lib/filters/pitchshift.dart", "flutterMode": "profile", "cwd": "${workspaceFolder}/example" }, @@ -24,7 +24,7 @@ "name": "Flutter release", "type": "dart", "request": "launch", - "program": "lib/main.dart", + "program": "lib/filters/pitchshift.dart", "flutterMode": "release", "cwd": "${workspaceFolder}/example" }, diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 729c81a2..5163682e 100755 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -3,13 +3,13 @@ "tasks": [ { "label": "compile linux debug verbose", - "command": "cd ${workspaceFolder}/example; flutter build linux -t lib/main.dart --debug --verbose", + "command": "cd ${workspaceFolder}/example; flutter build linux -t lib/filters/sound_filter.dart --debug --verbose", // "args": ["build", "linux", "--verbose"], "type": "shell" }, { "label": "compile linux debug", - "command": "cd ${workspaceFolder}/example; flutter build linux -t lib/main.dart --debug", + "command": "cd ${workspaceFolder}/example; flutter build linux -t lib/filters/sound_filter.dart --debug", "type": "shell" }, { @@ -29,7 +29,7 @@ }, { "label": "compile web debug", - "command": "cd ${workspaceFolder}/example; flutter run -d chrome --web-renderer canvaskit --web-browser-flag '--disable-web-security' -t lib/main.dart --release", + "command": "cd ${workspaceFolder}/example; flutter run -d chrome --web-renderer canvaskit --web-browser-flag '--disable-web-security' -t lib/filters/sound_filter.dart --release", "type": "shell" } ] diff --git a/CHANGELOG.md b/CHANGELOG.md index 9519c63c..5fd1bd74 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,14 +1,15 @@ ### 2.1.0 -- added `getPan()`, `setPan()` and `setPanAbsolute()`. - added support for the Web platform. +- added `getPan()`, `setPan()` and `setPanAbsolute()`. - added `loadMem()` to read the give audio file bytes buffer (not RAW data). Useful for the Web platform. - fixed `getFilterParamNames()`. - added `AudioData` class to manage audio samples. - added player initialization parameters: sample rate, buffer size, number of channels (mono, stereo, quad, 5.1, 7.1). - added voice groups. -- it's now possible to set filters not only globally, but also to single audio sources. +- it's now possible to set filters not only globally, but also to single audio sources (not on the web platform). - fade and oscillate filter parameters. - experimental capture feature removed. +- now accessing to filter has been simplified with the use of `SoLoud.filters` and `AudioSource.filters` to use global and single sound filters. ### 2.0.2 (23 May 2024) - Fixed wrong exception raised by `setVolume()` when a handle is no more valid. diff --git a/README.md b/README.md index 0effbe18..843fe45f 100755 --- a/README.md +++ b/README.md @@ -43,9 +43,19 @@ with the [miniaudio](https://miniaud.io/) backend through [Dart's C interop](https://dart.dev/interop/c-interop) (`dart:ffi`). In other words, it is calling the C/C++ methods of the underlying audio engine directly — there are no method channels in use. + +#### Web platform To use this plugin on the **Web platform**, please refer to [WEB_NOTES](https://github.com/alnitak/flutter_soloud/blob/main/WEB_NOTES.md). +#### Stripping iOS symbols +When creating a release archive (IPA), the symbols are [stripped by Xcode](https://docs.flutter.dev/platform-integration/ios/c-interop#stripping-ios-symbols), so the command `flutter build ipa` may throw a `Failed to lookup symbol ... symbol not found error`. To work around this: + +1. In Xcode, go to Target Runner > Build Settings > Strip Style +2. Change from All Symbols to Non-Global Symbols + + + ## Example The following example loads an MP3 asset, @@ -209,6 +219,5 @@ For Windows users, SoLoud utilizes *Openmpt* through a DLL, which can be obtaine #### Web -Work on web support (using WASM) is tracked in -https://github.com/alnitak/flutter_soloud/issues/46. +Please see [WEB_NOTES.md](https://github.com/alnitak/flutter_soloud/blob/main/WEB_NOTES.md). diff --git a/WEB_NOTES.md b/WEB_NOTES.md index 3eb4d1b3..dba0b061 100644 --- a/WEB_NOTES.md +++ b/WEB_NOTES.md @@ -4,6 +4,7 @@ ## Description The web platform is now supported, but some testing is welcome. +Please note that filters for single sounds are not supported on the web. ## How to use diff --git a/android/CMakeLists.txt b/android/CMakeLists.txt index b66a1092..98f68940 100644 --- a/android/CMakeLists.txt +++ b/android/CMakeLists.txt @@ -43,6 +43,8 @@ list(APPEND PLUGIN_SOURCES "${SRC_DIR}/analyzer.cpp" "${SRC_DIR}/synth/basic_wave.cpp" "${SRC_DIR}/filters/filters.cpp" + "${SRC_DIR}/filters/pitch_shift_filter.cpp" + "${SRC_DIR}/filters/smbPitchShift.cpp" ${TARGET_SOURCES} ) diff --git a/example/assets/audio/12Bands/audiocheck.net_sin_1000Hz_-3dBFS_2s.wav b/example/assets/audio/12Bands/audiocheck.net_sin_1000Hz_-3dBFS_2s.wav deleted file mode 100644 index d4ad09c3..00000000 Binary files a/example/assets/audio/12Bands/audiocheck.net_sin_1000Hz_-3dBFS_2s.wav and /dev/null differ diff --git a/example/assets/audio/12Bands/audiocheck.net_sin_125Hz_-3dBFS_2s.wav b/example/assets/audio/12Bands/audiocheck.net_sin_125Hz_-3dBFS_2s.wav deleted file mode 100644 index ee09fbee..00000000 Binary files a/example/assets/audio/12Bands/audiocheck.net_sin_125Hz_-3dBFS_2s.wav and /dev/null differ diff --git a/example/assets/audio/12Bands/audiocheck.net_sin_16000Hz_-3dBFS_2s.wav b/example/assets/audio/12Bands/audiocheck.net_sin_16000Hz_-3dBFS_2s.wav deleted file mode 100644 index f9f6d482..00000000 Binary files a/example/assets/audio/12Bands/audiocheck.net_sin_16000Hz_-3dBFS_2s.wav and /dev/null differ diff --git a/example/assets/audio/12Bands/audiocheck.net_sin_16Hz_-3dBFS_2s.wav b/example/assets/audio/12Bands/audiocheck.net_sin_16Hz_-3dBFS_2s.wav deleted file mode 100644 index 3f48da1f..00000000 Binary files a/example/assets/audio/12Bands/audiocheck.net_sin_16Hz_-3dBFS_2s.wav and /dev/null differ diff --git a/example/assets/audio/12Bands/audiocheck.net_sin_20000Hz_-3dBFS_2s.wav b/example/assets/audio/12Bands/audiocheck.net_sin_20000Hz_-3dBFS_2s.wav deleted file mode 100644 index 26a561d4..00000000 Binary files a/example/assets/audio/12Bands/audiocheck.net_sin_20000Hz_-3dBFS_2s.wav and /dev/null differ diff --git a/example/assets/audio/12Bands/audiocheck.net_sin_2000Hz_-3dBFS_2s.wav b/example/assets/audio/12Bands/audiocheck.net_sin_2000Hz_-3dBFS_2s.wav deleted file mode 100644 index 61326232..00000000 Binary files a/example/assets/audio/12Bands/audiocheck.net_sin_2000Hz_-3dBFS_2s.wav and /dev/null differ diff --git a/example/assets/audio/12Bands/audiocheck.net_sin_250Hz_-3dBFS_2s.wav b/example/assets/audio/12Bands/audiocheck.net_sin_250Hz_-3dBFS_2s.wav deleted file mode 100644 index bcd9c5b5..00000000 Binary files a/example/assets/audio/12Bands/audiocheck.net_sin_250Hz_-3dBFS_2s.wav and /dev/null differ diff --git a/example/assets/audio/12Bands/audiocheck.net_sin_31.5Hz_-3dBFS_2s.wav b/example/assets/audio/12Bands/audiocheck.net_sin_31.5Hz_-3dBFS_2s.wav deleted file mode 100644 index 03b6c89a..00000000 Binary files a/example/assets/audio/12Bands/audiocheck.net_sin_31.5Hz_-3dBFS_2s.wav and /dev/null differ diff --git a/example/assets/audio/12Bands/audiocheck.net_sin_4000Hz_-3dBFS_2s.wav b/example/assets/audio/12Bands/audiocheck.net_sin_4000Hz_-3dBFS_2s.wav deleted file mode 100644 index 90051eea..00000000 Binary files a/example/assets/audio/12Bands/audiocheck.net_sin_4000Hz_-3dBFS_2s.wav and /dev/null differ diff --git a/example/assets/audio/12Bands/audiocheck.net_sin_500Hz_-3dBFS_2s.wav b/example/assets/audio/12Bands/audiocheck.net_sin_500Hz_-3dBFS_2s.wav deleted file mode 100644 index 5d1ff144..00000000 Binary files a/example/assets/audio/12Bands/audiocheck.net_sin_500Hz_-3dBFS_2s.wav and /dev/null differ diff --git a/example/assets/audio/12Bands/audiocheck.net_sin_63Hz_-3dBFS_2s.wav b/example/assets/audio/12Bands/audiocheck.net_sin_63Hz_-3dBFS_2s.wav deleted file mode 100644 index 93f5abdd..00000000 Binary files a/example/assets/audio/12Bands/audiocheck.net_sin_63Hz_-3dBFS_2s.wav and /dev/null differ diff --git a/example/assets/audio/12Bands/audiocheck.net_sin_8000Hz_-3dBFS_2s.wav b/example/assets/audio/12Bands/audiocheck.net_sin_8000Hz_-3dBFS_2s.wav deleted file mode 100644 index 59c5589d..00000000 Binary files a/example/assets/audio/12Bands/audiocheck.net_sin_8000Hz_-3dBFS_2s.wav and /dev/null differ diff --git a/example/assets/audio/IveSeenThings.mp3 b/example/assets/audio/IveSeenThings.mp3 new file mode 100644 index 00000000..c4064252 Binary files /dev/null and b/example/assets/audio/IveSeenThings.mp3 differ diff --git a/example/assets/audio/TropicalBeeper.mp3 b/example/assets/audio/TropicalBeeper.mp3 deleted file mode 100644 index 6d3bf602..00000000 Binary files a/example/assets/audio/TropicalBeeper.mp3 and /dev/null differ diff --git a/example/assets/audio/XtrackTure.mp3 b/example/assets/audio/XtrackTure.mp3 deleted file mode 100644 index 90436511..00000000 Binary files a/example/assets/audio/XtrackTure.mp3 and /dev/null differ diff --git a/example/assets/audio/audiocheck.net_sin_8000Hz_48000_-3dBFS_3s.wav b/example/assets/audio/audiocheck.net_sin_8000Hz_48000_-3dBFS_3s.wav deleted file mode 100644 index 14671cd4..00000000 Binary files a/example/assets/audio/audiocheck.net_sin_8000Hz_48000_-3dBFS_3s.wav and /dev/null differ diff --git a/example/assets/audio/audiocheck.net_sweep_20Hz_20000Hz_-3dBFS_4s_linear.wav b/example/assets/audio/audiocheck.net_sweep_20Hz_20000Hz_-3dBFS_4s_linear.wav deleted file mode 100644 index 28a3d816..00000000 Binary files a/example/assets/audio/audiocheck.net_sweep_20Hz_20000Hz_-3dBFS_4s_linear.wav and /dev/null differ diff --git a/example/assets/audio/audiocheck.net_sweep_20Hz_20000Hz_-3dBFS_4s_logarithmic.wav b/example/assets/audio/audiocheck.net_sweep_20Hz_20000Hz_-3dBFS_4s_logarithmic.wav deleted file mode 100644 index b710c9c8..00000000 Binary files a/example/assets/audio/audiocheck.net_sweep_20Hz_20000Hz_-3dBFS_4s_logarithmic.wav and /dev/null differ diff --git a/example/assets/audio/police.mp3 b/example/assets/audio/police.mp3 deleted file mode 100644 index ea2381cb..00000000 Binary files a/example/assets/audio/police.mp3 and /dev/null differ diff --git a/example/assets/audio/siren.mp3 b/example/assets/audio/siren.mp3 deleted file mode 100644 index 3897d22b..00000000 Binary files a/example/assets/audio/siren.mp3 and /dev/null differ diff --git a/example/assets/audio/tic-1.wav b/example/assets/audio/tic-1.wav new file mode 100644 index 00000000..a63f1b28 Binary files /dev/null and b/example/assets/audio/tic-1.wav differ diff --git a/example/assets/audio/tic-2.wav b/example/assets/audio/tic-2.wav new file mode 100644 index 00000000..0ee8c28b Binary files /dev/null and b/example/assets/audio/tic-2.wav differ diff --git a/example/assets/shaders/test1.frag b/example/assets/shaders/test1.frag deleted file mode 100644 index f07cf900..00000000 --- a/example/assets/shaders/test1.frag +++ /dev/null @@ -1,69 +0,0 @@ -#version 460 core -#include -precision mediump float; - -uniform sampler2D iChannel0; -uniform vec2 uResolution; -uniform float iTime; - -out vec4 fragColor; - -vec3 iResolution; - -// credits: -// https://www.shadertoy.com/view/Mlj3WV - -// ------ START SHADERTOY CODE ----- - -/* -2D LED Spectrum - Visualiser -Based on Led Spectrum Analyser by: simesgreen - 27th February, 2013 https://www.shadertoy.com/view/Msl3zr -2D LED Spectrum by: uNiversal - 27th May, 2015 -Creative Commons Attribution-NonCommercial-ShareAlike 3.0 Unported License. -*/ - -void mainImage( out vec4 fragColor, in vec2 fragCoord ) -{ - // create pixel coordinates - vec2 uv = fragCoord.xy / iResolution.xy; - uv = vec2(uv.x, 1.0-uv.y); - - // quantize coordinates - const float bands = 30.0; - const float segs = 40.0; - vec2 p; - p.x = floor(uv.x*bands)/bands; - p.y = floor(uv.y*segs)/segs; - - // read frequency data from first row of texture - float fft = texture( iChannel0, vec2(p.x,0.25) ).x; - - // led color - vec3 color = mix(vec3(0.0, 2.0, 0.0), vec3(2.0, 0.0, 0.0), sqrt(uv.y)); - - // mask for bar graph - float mask = (p.y < fft) ? 1.0 : 0.1; - - // led shape - vec2 d = fract((uv - p) *vec2(bands, segs)) - 0.5; - float led = smoothstep(0.5, 0.35, abs(d.x)) * - smoothstep(0.5, 0.35, abs(d.y)); - vec3 ledColor = led*color*mask; - - // output final color - fragColor = vec4(ledColor, 1.0); -} -// ------ END SHADERTOY CODE ----- - - - -void main() { - iResolution = vec3(uResolution.x, uResolution.y, 0.); - - // in this frag iTime is not used and on Android the SPIRV compiler remove it, so - // when passing it with setFloat() it gives error - mainImage( fragColor, FlutterFragCoord().xy - iTime/9999999.); -} - - - diff --git a/example/assets/shaders/test2.frag b/example/assets/shaders/test2.frag deleted file mode 100644 index 4754e49a..00000000 --- a/example/assets/shaders/test2.frag +++ /dev/null @@ -1,81 +0,0 @@ -#version 460 core -#include -precision mediump float; - -uniform sampler2D iChannel0; -uniform vec2 uResolution; -uniform float iTime; - -out vec4 fragColor; - -vec3 iResolution; - -// credits: -// https://www.shadertoy.com/view/MdVSWG - -// ------ START SHADERTOY CODE ----- -vec3 B2_spline(vec3 x) { // returns 3 B-spline functions of degree 2 - vec3 t = 3.0 * x; - vec3 b0 = step(0.0, t) * step(0.0, 1.0-t); - vec3 b1 = step(0.0, t-1.0) * step(0.0, 2.0-t); - vec3 b2 = step(0.0, t-2.0) * step(0.0, 3.0-t); - return 0.5 * ( - b0 * pow(t, vec3(2.0)) + - b1 * (-2.0*pow(t, vec3(2.0)) + 6.0*t - 3.0) + - b2 * pow(3.0-t,vec3(2.0)) - ); -} - -void mainImage( out vec4 fragColor, in vec2 fragCoord ) -{ - // create pixel coordinates - vec2 uv = fragCoord.xy / iResolution.xy; - uv = vec2(uv.x, 1.0-uv.y); - - float fVBars = 100.; - float fHSpacing = 1.00; - - float fHFreq = (uv.x * 3.14); - float squarewave = sign(sin(fHFreq*fVBars)+1.0-fHSpacing); - - float x = floor(uv.x * fVBars)/fVBars; - float fSample = texture( iChannel0, vec2(abs(2.0 * x - 1.0), 0.25)).x; - - float fft = squarewave * fSample* 0.5; - - float fHBars = 100.0; - float fVSpacing = 0.180; - float fVFreq = (uv.y * 3.14); - fVFreq = sign(sin(fVFreq * fHBars)+1.0-fVSpacing); - - vec2 centered = vec2(1.0) * uv - vec2(1.0); - float t = iTime / 100.0; - float polychrome = 1.0; - vec3 spline_args = fract(vec3(polychrome*uv.x-t) + vec3(0.0, -1.0/3.0, -2.0/3.0)); - vec3 spline = B2_spline(spline_args); - - float f = abs(centered.y); - vec3 base_color = vec3(1.0, 1.0, 1.0) - f*spline; - vec3 flame_color = pow(base_color, vec3(3.0)); - - float tt = 0.3 - uv.y; - float df = sign(tt); - df = (df + 1.0)/0.5; - vec3 col = flame_color * vec3(1.0 - step(fft, abs(0.3-uv.y))) * vec3(fVFreq); - col -= col * df * 0.180; - - // output final color - fragColor = vec4(col,1.0); -} -// ------ END SHADERTOY CODE ----- - - - -void main() { - iResolution = vec3(uResolution.x, uResolution.y, 0.); - - mainImage( fragColor, FlutterFragCoord().xy ); -} - - - diff --git a/example/assets/shaders/test3.frag b/example/assets/shaders/test3.frag deleted file mode 100644 index 376cfae3..00000000 --- a/example/assets/shaders/test3.frag +++ /dev/null @@ -1,112 +0,0 @@ -#version 460 core -#include -precision mediump float; - -uniform sampler2D iChannel0; -uniform vec2 uResolution; -uniform float iTime; - -out vec4 fragColor; - -vec3 iResolution; - -// credits: -// https://www.shadertoy.com/view/ls3BDH - -// ------ START SHADERTOY CODE ----- -// credit: https://www.shadertoy.com/view/4tGXzt - -#define BEATMOVE 1 - -const float FREQ_RANGE = 64.0; -const float PI = 3.1415; -const float RADIUS = 0.6; -const float BRIGHTNESS = 0.2; -const float SPEED = 0.5; - -//convert HSV to RGB -vec3 hsv2rgb(vec3 c){ - vec4 K = vec4(1.0, 2.0 / 3.0, 1.0 / 3.0, 3.0); - vec3 p = abs(fract(c.xxx + K.xyz) * 6.0 - K.www); - return c.z * mix(K.xxx, clamp(p - K.xxx, 0.0, 1.0), c.y); -} - -float luma(vec3 color) { - return dot(color, vec3(0.299, 0.587, 0.114)); -} - -float getfrequency(float x) { - return texture(iChannel0, vec2(floor(x * FREQ_RANGE + 1.0) / FREQ_RANGE, 0.25)).x + 0.06; -} - -float getfrequency_smooth(float x) { - float index = floor(x * FREQ_RANGE) / FREQ_RANGE; - float next = floor(x * FREQ_RANGE + 1.0) / FREQ_RANGE; - return mix(getfrequency(index), getfrequency(next), smoothstep(0.0, 1.0, fract(x * FREQ_RANGE))); -} - -float getfrequency_blend(float x) { - return mix(getfrequency(x), getfrequency_smooth(x), 0.5); -} - -vec3 doHalo(vec2 fragment, float radius) { - float dist = length(fragment); - float ring = 1.0 / abs(dist - radius); - - float b = dist < radius ? BRIGHTNESS * 0.3 : BRIGHTNESS; - - vec3 col = vec3(0.0); - - float angle = atan(fragment.x, fragment.y); - col += hsv2rgb( vec3( ( angle + iTime * 0.25 ) / (PI * 2.0), 1.0, 1.0 ) ) * ring * b; - - float frequency = max(getfrequency_blend(abs(angle / PI)) - 0.02, 0.0); - col *= frequency; - - // Black halo - col *= smoothstep(radius * 0.5, radius, dist); - - return col; -} - -vec3 doLine(vec2 fragment, float radius, float x) { - vec3 col = hsv2rgb(vec3(x * 0.23 + iTime * 0.12, 1.0, 1.0)); - - float freq = abs(fragment.x * 0.5); - - col *= (1.0 / abs(fragment.y)) * BRIGHTNESS * getfrequency(freq); - col = col * smoothstep(radius, radius * 1.8, abs(fragment.x)); - - return col; -} - - -void mainImage( out vec4 fragColor, in vec2 fragCoord ) { - vec2 fragPos = fragCoord / iResolution.xy; - fragPos = (fragPos - 0.5) * 2.0; - fragPos.x *= iResolution.x / iResolution.y; - - vec3 color = vec3(0.0134, 0.052, 0.1); - color += doHalo(fragPos, RADIUS); - - float c = cos(iTime * SPEED); - float s = sin(iTime * SPEED); - vec2 rot = mat2(c,s,-s,c) * fragPos; - color += doLine(rot, RADIUS, rot.x); - - color += max(luma(color) - 1.0, 0.0); - - fragColor = vec4(color, 1.0); -} -// ------ END SHADERTOY CODE ----- - - - -void main() { - iResolution = vec3(uResolution.x, uResolution.y, 0.); - - mainImage( fragColor, FlutterFragCoord().xy ); -} - - - diff --git a/example/assets/shaders/test4.frag b/example/assets/shaders/test4.frag deleted file mode 100644 index c5997d2e..00000000 --- a/example/assets/shaders/test4.frag +++ /dev/null @@ -1,55 +0,0 @@ -#version 460 core -#include -precision mediump float; - -uniform sampler2D iChannel0; -uniform vec2 uResolution; -uniform float iTime; - -out vec4 fragColor; - -vec3 iResolution; - -// credits: -// https://www.shadertoy.com/view/ws2yRt - -// ------ START SHADERTOY CODE ----- -vec3 COL1 = vec3(0.6,0.0,0.0); -vec3 COL2 = vec3(0.8,0.0,0.9); - -void mainImage( out vec4 fragColor, in vec2 fragCoord ) -{ - vec2 uv = fragCoord.xy/iResolution.xy; -// uv = vec2(uv.x, 1.0-uv.y); - - - float grid = float(mod(floor(uv.x * 500.0),14.0) < 0.5); - grid += float(mod(floor(uv.y * 200.0),11.0) < 0.5); - grid = float(grid>0.5); - - vec2 uvn = 2.0 * uv - 1.0; - - grid *= 1.0-clamp(0.0,1.0,pow(length(uvn),1.2)); - - // aquire wave - float wa = texture(iChannel0,vec2(uv.x,0.75)).x; - - //attenuate - float i = pow(1.0-abs(uv.y-wa),20.0); - vec3 col = vec3(0.0,0.4,0.0)*grid+ vec3(i) * mix(COL1,COL2,i); - fragColor = vec4(col,0.7); -} -// ------ END SHADERTOY CODE ----- - - - -void main() { - iResolution = vec3(uResolution.x, uResolution.y, 0.); - - // in this frag iTime is not used and on Android the SPIRV compiler remove it, so - // when passing it with setFloat() it gives error - mainImage( fragColor, FlutterFragCoord().xy + iTime/999999999.); -} - - - diff --git a/example/assets/shaders/test5.frag b/example/assets/shaders/test5.frag deleted file mode 100644 index 29339c24..00000000 --- a/example/assets/shaders/test5.frag +++ /dev/null @@ -1,50 +0,0 @@ -#version 460 core -#include -precision mediump float; - -uniform sampler2D iChannel0; -uniform vec2 uResolution; -uniform float iTime; - -out vec4 fragColor; - -vec3 iResolution; - -// credits: -// https://www.shadertoy.com/view/XsX3zS - -// ------ START SHADERTOY CODE ----- -#define WAVES 8.0 - -void mainImage( out vec4 fragColor, in vec2 fragCoord ) { - vec2 uv = -1.0 + 2.0 * fragCoord.xy / iResolution.xy; - - float time = iTime * 1.0; - - vec3 color = vec3(0.0); - - for (float i=0.0; i -precision mediump float; - -uniform sampler2D iChannel0; -uniform vec2 uResolution; -uniform float iTime; - -out vec4 fragColor; - -vec3 iResolution; - -// credits: -// https://www.shadertoy.com/view/Xd3GRX - -// ------ START SHADERTOY CODE ----- -/* - If you have any ideas please let me know in the comments! :) - Especially if you have a better idea for the frequency distribution. - Right now it's MAXF/2^(x/2) where x is a frequency band numbered from 0 to 15. - It would probably be smart to include more than a single frequency over each band. - -*/ -//Enable dithering -#define ENABLEDITHER - -//Enable rotation -//#define ENABLEROT - -//Enable zooming with beat (sucks ass) -//#define ENABLEBEAT - -//Show only the back wall without perspective -//#define FLAT - -//Enable waveform -#define ENABLEWAVE - -//Waveform amplitude; adjust according to audio source volume -#define WAVEAMP 1.0 - -//Height for gradient middle color; set to 1.0 for 2 color gradient -#define GRADVAL 0.8 - -#define time iTime - - -const vec3 Color1 = vec3(0.25,0.625,0.625); -const vec3 Color2 = vec3(0.25,0.25,1.00); // TEAL->BLUE->RED -const vec3 Color3 = vec3(1.00,0.25,0.25); - -//Some color combinations - -/*const vec3 Color1 = vec3(0.25,1.00,0.25); -const vec3 Color2 = vec3(0.75,0.50,0.25); // GREEN->YELLOW->RED -const vec3 Color3 = vec3(1.00,0.25,0.25);*/ - -/*const vec3 Color1 = vec3(0.00,0.00,0.00); -const vec3 Color2 = vec3(0.70,0.55,0.25); // BLACK->YELLOW->WHITE -const vec3 Color3 = vec3(0.5);*/ - -/*const vec3 Color1 = vec3(0.25,0.25,1.00); -const vec3 Color2 = vec3(0.25,0.625,0.625); // BLUE->TEAL->WHITE -const vec3 Color3 = vec3(0.5);*/ - - -/*const vec3 Color1 = vec3(0.75); // WHITE -//const vec3 Color1 = vec3(0.25,0.50,1.00); // LIGHT BLUE -const vec3 Color2 = Color1; -const vec3 Color3 = Color1;*/ - -float noise(vec2 p) -{ - return fract(sin(dot(p ,vec2(12.9898,78.233))) * 43758.5453); -} - -float noise3D(vec3 p) -{ - return fract(sin(dot(p ,vec3(12.9898,78.233,128.852))) * 43758.5453); -} - - -mat3 rot(vec3 ang) -{ - mat3 x = mat3(1.0,0.0,0.0,0.0,cos(ang.x),-sin(ang.x),0.0,sin(ang.x),cos(ang.x)); - mat3 y = mat3(cos(ang.y),0.0,sin(ang.y),0.0,1.0,0.0,-sin(ang.y),0.0,cos(ang.y)); - mat3 z = mat3(cos(ang.z),-sin(ang.z),0.0,sin(ang.z),cos(ang.z),0.0,0.0,0.0,1.0); - return x*y*z; -} - -vec3 getCol(float v, vec3 col1, vec3 col2) -{ - v = clamp(v,0.0,1.0); - vec3 res = vec3(0.0); - for(int i = 0; i<3; i++) - { - res[i] = col1[i] + v * (col2[i] - col1[i]); - } - return res; -} - -float udBox( vec3 p, vec3 b ) -{ - return length(max(abs(p)-b,0.0)); -} - -vec4 map(vec3 rp) -{ - vec4 d = vec4(0.0); - - vec3 bgCol = vec3(0.125,0.125,0.125); - - #ifdef ENABLEWAVE - float waveF = texture(iChannel0, vec2((rp.x+1.8)/3.6, 0.75)).r; - float waveDist = udBox(rp-vec3(0.0,-waveF*WAVEAMP+WAVEAMP/2.0,0.25), vec3(1.8,0.02,0.001)); - vec4 wave = vec4(vec3(0.3), waveDist); - #endif - - float backWallDist = udBox(rp-vec3(0.0,0.0,0.75), vec3(1.8, 1.1, 0.5)); - vec4 backWall = vec4(bgCol, backWallDist); - - float edgeLeftDist = udBox(rp-vec3(1.9,0.0,0.3725), vec3(0.1,1.1,1.0)); - vec4 edgeLeft = vec4(bgCol, edgeLeftDist); - - float edgeRightDist = udBox(rp-vec3(-1.9,0.0,0.3725), vec3(0.1,1.1,1.0)); - vec4 edgeRight = vec4(bgCol, edgeRightDist); - - float edgeTopDist = udBox(rp-vec3(0.0,1.2,0.3725), vec3(2.0,0.1,1.0)); - vec4 edgeTop = vec4(bgCol,edgeTopDist); - - float edgeBotDist = udBox(rp-vec3(0.0,-1.2,0.3725), vec3(2.0,0.1,1.0)); - vec4 edgeBot = vec4(bgCol,edgeBotDist); - - d = edgeLeft.a > backWall.a ? backWall : edgeLeft; - d = edgeRight.a > d.a ? d : edgeRight; - d = edgeTop.a > d.a ? d : edgeTop; - d = edgeBot.a > d.a ? d : edgeBot; - - #ifdef ENABLEWAVE - d = wave.a > d.a ? d : wave; - #endif - - return d; - -} - -vec3 normal(vec3 rp) -{ - vec3 eps = vec3( 0.002 ,0.0,0.0); - return normalize( vec3( - map(rp+eps.xyy).a - map(rp-eps.xyy).a, - map(rp+eps.yxy).a - map(rp-eps.yxy).a, - map(rp+eps.yyx).a - map(rp-eps.yyx).a ) ); - -} - -float light(vec3 lp, vec3 rp, vec3 n, float pulse) -{ - return (1.5*(pulse+0.05)/pow(distance(lp,rp),2.0))*max(dot(normalize(lp-rp), n), 0.0)*0.25; -} - -float drawLight(vec3 lp, vec3 rd, vec3 ro, float td, float pulse) -{ - float res = 0.0; - if(td > distance(lp,ro)) - { - float dlp = length(cross(lp-ro, lp-(ro+rd)))/length((ro+rd)-ro); - res=max(exp(-dlp*64.0*((1.0-pulse)+0.5)),0.0); - } - return res; -} - -void mainImage( out vec4 fragColor, in vec2 fragCoord ) -{ - vec2 uv = fragCoord.xy / iResolution.xy; - uv = vec2(uv.x, 1.0-uv.y); - vec2 p = 2.0*uv-1.0; - p.x*=iResolution.x/iResolution.y; -// vec2 m = iMouse.xy/iResolution.xy; -// m.x = iMouse.x == 0.0 ? 0.5 : iMouse.x/iResolution.x; - vec2 m = vec2(0.5, 0.5); - m = 2.0*m-1.0; - vec3 col = vec3(0.00); - - float hArr[16]; - for(int i = 0; i < 16; i++) - { - hArr[i] = texture(iChannel0, vec2( 1.0/pow(2.0,float(i)*0.5)-0.001,0.25)).r; - } - - float z = -0.5; - - #ifdef ENABLEROT - z = -2.0*(sin(time*0.25)*0.5+0.5); - #endif - #ifdef ENABLEBEAT - z += -2.0*hArr[14]; - #endif - vec3 ro = vec3(0.0,0.0,z-2.0); - #ifdef FLAT - ro = vec3(p/vec2(1.0,1.25), z-2.0); - #endif - vec3 rd = normalize( vec3(p, z) - ro ); - - vec3 ang = vec3( 0.0, 0.0 , 0.0); - #ifdef ENABLEROT - ang = vec3(1.54*sin(time*0.125)*0.25, 1.54*cos(time*0.25)*0.25, 0.0); - #endif - ro*=rot(ang); - rd*=rot(ang); - - vec3 rp = vec3(0.0); - - vec4 d = vec4(0.0); - float td = 0.5; - float dMax = 25.0; - - for(int i = 0; i < 64; i++) - { - if(td >= dMax) break; - rp = ro+rd*td; - d = map(rp); - if(d.a < 0.001) - { - break; - } - td += d.a*0.75; - } - - vec3 lp[16]; - - for(int i = 0; i < 16; i++) - { - lp[i] = vec3( (7.5-float(i))*0.2125, hArr[i]*2.0-1.0, 0.0); - } - - float g = 1.0/(1.0-GRADVAL); - float r = 1.0/GRADVAL; - - vec3 lc[16]; - - for(int i = 0; i < 16; i++) - { - lc[i] = getCol((hArr[i]-GRADVAL)*g, getCol(hArr[i]*r, Color1, Color2), Color3); - } - - vec3 n = normal(rp); - if(d.a < 0.001) - { - - vec3 illumination = vec3(0.01); - - for(int i = 0; i < 16; i++) - { - illumination += lc[i] * light(lp[i], rp, n, hArr[i] ); - } - - col = d.rgb*illumination; - } - - for(int i = 0; i < 16; i++) - { - col += lc[i] * drawLight(lp[i], rd, ro, td, hArr[i] ); - } - - col = clamp(col, 0.0, 1.0); - - col = pow(col, vec3(0.45)); //gamma adjust - - float f = 8.0; - col = (1.0/(1.0+exp(4.0-f*col))-0.0003)/(0.982-0.018); //contrast - p.x/=iResolution.x/iResolution.y; - col *= smoothstep( 1.325, 0.825, abs(p.x) ); //dark edges - col *= smoothstep( 1.325, 0.825, abs(p.y) ); - - #ifdef ENABLEDITHER - float dither = 4.0/256.0; - - col += (noise3D(vec3(p,fract(time)))*2.0-1.0)*dither; - #endif - - fragColor = vec4(col,1.0); -} -// ------ END SHADERTOY CODE ----- - - - -void main() { - iResolution = vec3(uResolution.x, uResolution.y, 0.); - - mainImage( fragColor, FlutterFragCoord().xy ); -} - - - diff --git a/example/assets/shaders/test7.frag b/example/assets/shaders/test7.frag deleted file mode 100644 index 5a3b9520..00000000 --- a/example/assets/shaders/test7.frag +++ /dev/null @@ -1,111 +0,0 @@ -#version 460 core -#include -precision mediump float; - -uniform sampler2D iChannel0; -uniform vec2 uResolution; -uniform float iTime; - -out vec4 fragColor; - -vec3 iResolution; - -// credits: -// https://www.shadertoy.com/view/3dSyRK - -// ------ START SHADERTOY CODE ----- -#define PI 3.1415926 -vec3 palette( in float t, in vec3 a, in vec3 b, in vec3 c, in vec3 d ) { - return a + b*cos( 6.28318*(c*t+d) ); -} - -mat3 rot(vec3 ang) { - mat3 x = mat3(1.0,0.0,0.0,0.0,cos(ang.x),-sin(ang.x),0.0,sin(ang.x),cos(ang.x)); - mat3 y = mat3(cos(ang.y),0.0,sin(ang.y),0.0,1.0,0.0,-sin(ang.y),0.0,cos(ang.y)); - mat3 z = mat3(cos(ang.z),-sin(ang.z),0.0,sin(ang.z),cos(ang.z),0.0,0.0,0.0,1.0); - return x*y*z; -} - -float noise3D(vec3 p) -{ - return fract(sin(dot(p ,vec3(12.9898,78.233,128.852))) * 43758.5453)*2.0-1.0; -} - -float loudness(float i, float mag) { - return 10.0*mag*sqrt(log(i + 1.0)); -} - -float sphereDistance(vec3 st, vec3 center, float radius) { - return (distance(st, center) - radius); -} - -vec3 opRep( in vec3 p, in vec3 c) { - vec3 q = mod(p,c)-0.5*c; - return q; -} - -vec3 map( in vec3 p ) { - vec3 rep = vec3(118.0); - vec3 randIndex = floor(p.xyz/rep.xyz); - float rand = noise3D(randIndex); - float f = 1.0*noise3D(1.13*floor(p.xyz/rep.xyz)); - float spectrumRange = abs(f)*0.40; - float freqMag = 1.0*texture(iChannel0, vec2(spectrumRange,0.0)).x; - freqMag = loudness(spectrumRange, freqMag); - p = opRep( p, rep); - vec4 sphere = vec4(rand*0.15*rep.x, f*0.15*rep.y, (0.5*f + 0.5*rand)*0.15*rep.z, 1.0 + 5.0*abs(f)); - float dist = sphereDistance(p, sphere.xyz, sphere.w); - - vec3 result = vec3(dist, f, freqMag); - return result; -} - -vec3 GetSphereIndexColor(float index) { - vec3 cA = vec3(0.2, 0.6, 0.9); - vec3 cB = vec3(0.3, 0.5, 0.2); - vec3 cC = vec3(1.0, 1.0, 1.0); - vec3 cD = vec3(0.1, 0.2, 0.5); - return palette(index + 0.4, cA, cB, cC, cD); -} - -void mainImage( out vec4 fragColor, in vec2 fragCoord ) { - vec2 p = (2.*fragCoord - iResolution.xy ) / iResolution.y; - // Camera setup. - vec3 viewDir = vec3(0.0,0.0,1.0); - vec3 camUp = vec3(0.0,1.0,0.0); - vec3 camPos = vec3(0.0); - vec3 u = normalize(cross(camUp,viewDir)); - vec3 v = cross(viewDir,u); - vec3 vcv = (camPos + viewDir); - vec3 srcCoord = vcv+p.x*u+p.y*v; - vec3 rayDir = rot(vec3(0.3*iTime,sin(0.3*iTime),cos(0.223*iTime)))*normalize(srcCoord - camPos); - - vec4 c = vec4(0.0,0.0,0.0,1.0); - - float depth = 0.0; - float d = 0.0; - vec3 pos = vec3(0); - vec3 colorAcc = vec3(0); - for (int i = 0; i < 109; i++) { - pos = camPos + rayDir * depth + 40.0*iTime; - vec3 mapRes = map(pos); - d = mapRes.x; - float lightFalloffFactor = 1.2 - 0.6*mapRes.z; - lightFalloffFactor = clamp(lightFalloffFactor, 0.1, 1.0); - colorAcc += exp(-d*lightFalloffFactor) * 1.0*GetSphereIndexColor(mapRes.y); - depth += d*(0.4); - } - c = vec4(colorAcc*0.24,1.0); - - fragColor = c; -} - -// ------ END SHADERTOY CODE ----- - - - -void main() { - iResolution = vec3(uResolution.x, uResolution.y, 0.); - - mainImage( fragColor, FlutterFragCoord().xy ); -} diff --git a/example/assets/shaders/test8.frag b/example/assets/shaders/test8.frag deleted file mode 100644 index 0d7b5a1b..00000000 --- a/example/assets/shaders/test8.frag +++ /dev/null @@ -1,22 +0,0 @@ -#version 460 core -#include -precision mediump float; - -uniform sampler2D iChannel0; -uniform vec2 uResolution; -uniform float iTime; - -out vec4 fragColor; - -vec3 iResolution; - -void main() { - iResolution = vec3(uResolution.x, uResolution.y, 0.); - - // in this frag iTime is not used and on Android the SPIRV compiler - // remove it, so when passing it with setFloat() it gives error - vec2 uv = FlutterFragCoord().xy / iResolution.xy + iTime/999999999.; - - float data = texture( iChannel0, vec2(1.0 - uv.y, 1.0 - uv.x) ).x; - fragColor = vec4(data, data*data, 0., 1.); -} diff --git a/example/assets/shaders/test9.frag b/example/assets/shaders/test9.frag deleted file mode 100644 index 8cd31a8b..00000000 --- a/example/assets/shaders/test9.frag +++ /dev/null @@ -1,253 +0,0 @@ -#version 460 core -// this doesn't work. Maybe because Flutter shader uses GLES1? -//#extension GL_OES_standard_derivatives : enable -#include -precision mediump float; - -uniform sampler2D iChannel0; -uniform vec2 uResolution; -uniform float iTime; - -out vec4 fragColor; - -vec3 iResolution; -vec2 uv; - -// ref: https://madebyevan.com/shaders/grid/ -// but fwidth() doesn't work. Below a workaround but it's not working well - -/// workaround for [fwidth()] not present -/// http://stackoverflow.com/questions/22442304/glsl-es-dfdx-dfdy-analog -float dfd(vec2 p){ - return p.x*p.x - p.y; // that's our function. We want derivative from it. -} - -float myFwidth(vec2 p) -{ - // this calculated in vertex shader - // width/height = triangle/quad width/height in px; - vec2 pixel_step = vec2(1 / uResolution.x, 1 / uResolution.y) * (1./1.75); - - float current = dfd(uv); - float dfdx = dfd(uv + pixel_step.x) - current; - float dfdy = dfd(uv + pixel_step.y) - current; - - return abs(dfdx) + abs(dfdy); // from khronos doc #http://www.khronos.org/registry/gles/extensions/OES/OES_standard_derivatives.txt -} - -// based on: -// https://www.shadertoy.com/view/ldXGzN - -// ------ START SHADERTOY CODE ----- -// Based on video heightfield by @simesgreen, https://www.shadertoy.com/view/Xss3zr - -/* To try: - - adjust range and scale of sound frequencies. - x use x^2 or something so they're spread more evenly - - adjust upward - are we missing some at the bottom? - No, there are no lower frequencies to show, apparently. -*/ - -const int _Steps = 256; -const vec3 lightDir = vec3(0.577, 0.577, 0.577); - -const float bands = 256.0; - -// transforms -vec3 rotateX(vec3 p, float a) -{ - float sa = sin(a); - float ca = cos(a); - vec3 r; - r.x = p.x; - r.y = ca*p.y - sa*p.z; - r.z = sa*p.y + ca*p.z; - return r; -} - -vec3 rotateY(vec3 p, float a) -{ - float sa = sin(a); - float ca = cos(a); - vec3 r; - r.x = ca*p.x + sa*p.z; - r.y = p.y; - r.z = -sa*p.x + ca*p.z; - return r; -} - -bool -intersectBox(vec3 ro, vec3 rd, vec3 boxmin, vec3 boxmax, out float tnear, out float tfar) -{ - // compute intersection of ray with all six bbox planes - vec3 invR = 1.0 / rd; - vec3 tbot = invR * (boxmin - ro); - vec3 ttop = invR * (boxmax - ro); - // re-order intersections to find smallest and largest on each axis - vec3 tmin = min (ttop, tbot); - vec3 tmax = max (ttop, tbot); - // find the largest tmin and the smallest tmax - vec2 t0 = max (tmin.xx, tmin.yz); - tnear = max (t0.x, t0.y); - t0 = min (tmax.xx, tmax.yz); - tfar = min (t0.x, t0.y); - // check for hit - bool hit; - if ((tnear > tfar)) - hit = false; - else - hit = true; - return hit; -} - - -float normalCurve(float x) { - const float pi = 3.141592653589; - // const float e = 2.71828; - // return pow(e, -x*x*0.5) / sqrt(2.0 * pi); - // Cauchy: - return 1.0/(pi * (1.0 + x*x)); -} - -// return texture coords from 0 to 1 -vec2 worldToTex(vec3 p) -{ - vec2 uv = p.xz*0.5+0.5; - uv.y = 1.0 - uv.y; - return uv; -} - -float h1(vec2 uv) { -// float band = pow(uv.x, 2.); // floor(uv.x * bands) / bands; -// float amp = texture(iChannel0, vec2(band, 0.25)).x; -// return amp * normalCurve((uv.y - 0.5) * 5.0) * 1.5; // * (1.0 - abs(p.z - 0.5)); - - float amp = texture(iChannel0, vec2(uv.x, uv.y)).x; - return amp; -} - -// return a value from 0 to 1 -float heightField(vec3 p) -{ - vec2 uv = worldToTex(p); - // Get amplitude of the frequency that corresponds to p.x - return h1(uv); - - // return sin(p.x * 4.0) * sin(p.z * 4.0) * 0.5 + 0.5; -} - -bool traceHeightField(vec3 ro, vec3 rayStep, out vec3 hitPos) -{ - vec3 p = ro; - bool hit = false; - float pH = 0.0; - vec3 pP = p; - for(int i=0; i<_Steps; i++) { - float h = heightField(p); - if ((p.y < h) && !hit) { - hit = true; - //hitPos = p; - // interpolate based on height - hitPos = mix(pP, p, (pH - pP.y) / ((p.y - pP.y) - (h - pH))); - } - pH = h; - pP = p; - p += rayStep; - } - return hit; -} - -vec3 background(vec3 rd) -{ - return mix(vec3(1.0, 1.0, 1.0), vec3(0.6, 0.6, 1.0), abs(rd.y)); -} - -void mainImage( out vec4 fragColor, in vec2 fragCoord ) -{ - vec2 pixel = (fragCoord.xy / iResolution.xy)*2.0-1.0; - pixel = vec2(pixel.x, -pixel.y); - - // compute ray origin and direction - float asp = iResolution.x / iResolution.y; - vec3 rd = normalize(vec3(asp*pixel.x, pixel.y, -2.0)); - vec3 ro = vec3(0.0, 0.0, 2.0); - - // rotate view - float a; - a = -0.8; - rd = rotateX(rd, a); - ro = rotateX(ro, a); - - a = 1.8; -// a = sin(iTime)*.5 + 1.570796327; - rd = rotateY(rd, a); - ro = rotateY(ro, a); - - // intersect with bounding box - bool hit; - const vec3 boxMin = vec3(-1.0, -0.01, -1.0); - const vec3 boxMax = vec3(1.0, 0.5, 1.0); - float tnear, tfar; - hit = intersectBox(ro, rd, boxMin, boxMax, tnear, tfar); - - tnear -= 0.0001; - vec3 pnear = ro + rd*tnear; - vec3 pfar = ro + rd*tfar; - - float stepSize = length(pfar - pnear) / float(_Steps); - - vec3 rgb = background(rd); - if(hit) - { - // intersect with heightfield - ro = pnear; - vec3 hitPos; - hit = traceHeightField(ro, rd*stepSize, hitPos); - if (hit) { - // rgb = hitPos*0.5+0.5; - - vec2 uv = worldToTex(hitPos); - // rgb = texture(iChannel0, uv).xyz; - float amp = h1(uv) * 2.0; - - // float amp = hitPos.y * 2.0; - // Compute hue - rgb = vec3(amp, 4.0 * amp * (1.0 - amp), 0.5 * (1.0 - amp)); - // Add white waveform -// float wave = texture(iChannel0, vec2(uv.x, 0.75)).x; -// rgb += 1.0 - smoothstep( 0.0, 0.01, abs(wave - uv.y)); - // vec3(amp, amp * 0.7 + 0.2, amp * 0.5 + 0.2); - //vec2 g = gradient(iChannel0, uv, vec2(1.0) / iResolution.xy); - //vec3 n = normalize(vec3(g.x, 0.01, g.y)); - //rgb = n*0.5+0.5; - #if 1 - // shadows - hitPos += vec3(0.0, 0.01, 0.0); - bool shadow = traceHeightField(hitPos, lightDir*0.01, hitPos); - if (shadow) { - rgb *= 0.75; - } - #endif - } - } - - fragColor=vec4(rgb, 1.0); - //fragColor = vec4(vec3(tfar - tnear)*0.2, 1.0); -} - -// ------ END SHADERTOY CODE ----- - - - -void main() { - iResolution = vec3(uResolution.x, uResolution.y, 0.); - uv = FlutterFragCoord().xy; - - // in this frag iTime is not used and on Android the SPIRV compiler remove it, so - // when passing it with setFloat() it gives error - mainImage( fragColor, FlutterFragCoord().xy - iTime/9999999.); -} - - - - diff --git a/example/lib/filters/pitchshift.dart b/example/lib/filters/pitchshift.dart new file mode 100644 index 00000000..9d1725cc --- /dev/null +++ b/example/lib/filters/pitchshift.dart @@ -0,0 +1,418 @@ +import 'dart:async'; +import 'dart:developer' as dev; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_soloud/flutter_soloud.dart'; +import 'package:logging/logging.dart'; + +/// Pitch shift example. +/// +/// Filters can be accessed globally using `SoLoud.instance.filters` or for +/// single [AudioSource] sounds with `sound.filters`: +// ``` +// /// For global filters. +// final ps = SoLoud.instance.filters.pitchShiftFilter; +// ps.activate(); +// ps.shift(soundHandle: soundHandle).value = 0.6; // set value. +// final shift = ps.shift(soundHandle: soundHandle).value; // get value. +// ps.fadeFilterParameter(to: 3, time: const Duration(milliseconds: 2500)); +// ps.oscillateFilterParameter( +// from: 0.5, +// to: 1.5, +// time: const Duration(milliseconds: 2500), +// ); +// ps.queryShift.[min | max | def] // to get filter min, max and default values. + +// /// For single sound filters. +// final ps = sound.filters.pitchShiftFilter; +// [as for global filters] + +// ``` + +/// Use the filter globally or attached to the sound. Filters for single sounds +/// are not supported in the Web platform. +const bool useGlobalFilter = true; + +void main() async { + // The `flutter_soloud` package logs everything + // (from severe warnings to fine debug messages) + // using the standard `package:logging`. + // You can listen to the logs as shown below. + Logger.root.level = kDebugMode ? Level.FINE : Level.INFO; + Logger.root.onRecord.listen((record) { + dev.log( + record.message, + time: record.time, + level: record.level.value, + name: record.loggerName, + zone: record.zone, + error: record.error, + stackTrace: record.stackTrace, + ); + }); + WidgetsFlutterBinding.ensureInitialized(); + + /// Initialize the player. + await SoLoud.instance.init(); + + runApp( + const MaterialApp( + home: PitchShift(), + ), + ); +} + +class PitchShift extends StatefulWidget { + const PitchShift({super.key}); + + @override + State createState() => _PitchShiftState(); +} + +class _PitchShiftState extends State { + final timeStretching = ValueNotifier(1); + final playSpeed = ValueNotifier(1); + final wet = ValueNotifier(1); + final shift = ValueNotifier(1); + final semitones = ValueNotifier(0); + + AudioSource? sound; + SoundHandle? soundHandle; + + @override + void initState() { + super.initState(); + try { + SoLoud.instance + .loadAsset('assets/audio/IveSeenThings.mp3') + .then((value) async { + sound = value; + + /// Add the filter to this sound handle before playing it. + if (useGlobalFilter) { + SoLoud.instance.filters.pitchShiftFilter.activate(); + } else { + sound!.filters.pitchShiftFilter.activate(); + } + + /// start playing. + soundHandle = await SoLoud.instance.play(sound!, looping: true); + }); + } catch (e) { + debugPrint(e.toString()); + } + } + + @override + void dispose() { + SoLoud.instance.deinit(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Stack( + children: [ + Padding( + padding: const EdgeInsets.all(16), + child: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Text( + 'Pitch Shift Tempo example', + textScaler: TextScaler.linear(3), + ), + const SizedBox(height: 32), + + /// Play Speed without changing the pitch. + ValueListenableBuilder( + valueListenable: timeStretching, + builder: (_, ts, __) { + return Row( + children: [ + Text('time stretching: ${ts.toStringAsFixed(2)}'), + Expanded( + child: Slider.adaptive( + max: 5, + value: ts, + onChanged: (value) { + if (soundHandle == null) return; + timeStretching.value = value; + playSpeed.value = value; + // Adjust the play speed + SoLoud.instance + .setRelativePlaySpeed(soundHandle!, value); + + // Adjust the pitchShift relatively to the + // speed. The relation between speed and shift + // is shift = 1 / speed. + shift.value = 1 / value; + if (useGlobalFilter) { + SoLoud.instance.filters.pitchShiftFilter.shift + .value = shift.value; + } else { + sound!.filters.pitchShiftFilter + .shift(soundHandle: soundHandle) + .value = shift.value; + } + }, + ), + ), + ], + ); + }, + ), + const SizedBox(height: 32), + + /// Play Speed + ValueListenableBuilder( + valueListenable: playSpeed, + builder: (_, speed, __) { + return Row( + children: [ + Text('speed: ${(speed * 100).toInt()}%'), + Expanded( + child: Slider.adaptive( + max: 5, + value: speed, + onChanged: (value) { + if (soundHandle == null) return; + playSpeed.value = value; + SoLoud.instance + .setRelativePlaySpeed(soundHandle!, value); + }, + ), + ), + ], + ); + }, + ), + const SizedBox(height: 32), + + /// Pitch Shift Wet parameter + ValueListenableBuilder( + valueListenable: wet, + builder: (_, w, __) { + return Row( + children: [ + Text('wet: ${w.toStringAsFixed(2)}'), + Expanded( + child: Slider.adaptive( + value: w, + onChanged: (value) { + if (soundHandle == null || sound == null) { + return; + } + wet.value = value; + if (useGlobalFilter) { + SoLoud.instance.filters.pitchShiftFilter.wet + .value = w; + } else { + sound!.filters.pitchShiftFilter + .wet(soundHandle: soundHandle) + .value = w; + } + }, + ), + ), + ], + ); + }, + ), + + /// Pitch Shift shift parameter + ValueListenableBuilder( + valueListenable: shift, + builder: (_, s, __) { + return Row( + children: [ + Text('shift: ${s.toStringAsFixed(2)}'), + Expanded( + child: Slider.adaptive( + value: s, + min: SoLoud.instance.filters.pitchShiftFilter + .queryShift.min, + max: SoLoud.instance.filters.pitchShiftFilter + .queryShift.max, + onChanged: (value) { + if (soundHandle == null || sound == null) { + return; + } + shift.value = value; + if (useGlobalFilter) { + SoLoud.instance.filters.pitchShiftFilter.shift + .value = value; + } else { + sound!.filters.pitchShiftFilter + .shift(soundHandle: soundHandle) + .value = value; + } + + /// Changing the shift value also changes the + /// semitones. Update the semitones slider. + if (useGlobalFilter) { + semitones.value = SoLoud.instance.filters + .pitchShiftFilter.semitones.value + .toInt() + .clamp( + SoLoud.instance.filters.pitchShiftFilter + .querySemitones.min + .toInt(), + SoLoud.instance.filters.pitchShiftFilter + .querySemitones.max + .toInt(), + ); + } else { + semitones.value = sound! + .filters.pitchShiftFilter + .semitones(soundHandle: soundHandle) + .value + .toInt() + .clamp( + SoLoud.instance.filters.pitchShiftFilter + .querySemitones.min + .toInt(), + SoLoud.instance.filters.pitchShiftFilter + .querySemitones.max + .toInt(), + ); + } + }, + ), + ), + ], + ); + }, + ), + + /// Pitch Shift semitones parameter + ValueListenableBuilder( + valueListenable: semitones, + builder: (_, s, __) { + return Row( + children: [ + Text('semitones: $s'), + Expanded( + child: Slider.adaptive( + value: s.toDouble(), + min: SoLoud.instance.filters.pitchShiftFilter + .querySemitones.min, + max: SoLoud.instance.filters.pitchShiftFilter + .querySemitones.max, + divisions: 49, + onChanged: (value) { + if (soundHandle == null || sound == null) { + return; + } + semitones.value = value.toInt(); + if (useGlobalFilter) { + SoLoud + .instance + .filters + .pitchShiftFilter + .semitones + .value = semitones.value.toDouble(); + } else { + sound!.filters.pitchShiftFilter + .semitones(soundHandle: soundHandle) + .value = semitones.value.toDouble(); + } + + /// Changing the semitones value also the shift + /// changes. Update the shift slider. + if (useGlobalFilter) { + shift.value = SoLoud.instance.filters + .pitchShiftFilter.shift.value + .clamp( + SoLoud.instance.filters.pitchShiftFilter + .queryShift.min, + SoLoud.instance.filters.pitchShiftFilter + .queryShift.max, + ); + } else { + shift.value = sound!.filters.pitchShiftFilter + .shift(soundHandle: soundHandle) + .value + .clamp( + SoLoud.instance.filters.pitchShiftFilter + .queryShift.min, + SoLoud.instance.filters.pitchShiftFilter + .queryShift.max, + ); + } + }, + ), + ), + ], + ); + }, + ), + const SizedBox(height: 32), + + OutlinedButton( + onPressed: () { + if (soundHandle == null || sound == null) return; + + /// Oscillate shift parameter from 0.4 to 1.6 in 1500ms + if (useGlobalFilter) { + SoLoud.instance.filters.pitchShiftFilter.shift + .oscillateFilterParameter( + from: 0.4, + to: 1.6, + time: const Duration(milliseconds: 1500), + ); + } else { + sound!.filters.pitchShiftFilter + .shift(soundHandle: soundHandle) + .oscillateFilterParameter( + from: 0.4, + to: 1.6, + time: const Duration(milliseconds: 1500), + ); + } + }, + child: const Text('Oscillate shift'), + ), + + OutlinedButton( + onPressed: () { + if (soundHandle == null || sound == null) return; + + /// Fade shift parameter from the current value to 3.0 + /// in 1500ms + if (useGlobalFilter) { + SoLoud.instance.filters.pitchShiftFilter.shift + .fadeFilterParameter( + to: 3, + time: const Duration(milliseconds: 1500), + ); + } else { + sound!.filters.pitchShiftFilter + .shift(soundHandle: soundHandle) + .fadeFilterParameter( + to: 3, + time: const Duration(milliseconds: 1500), + ); + } + + /// Restore shift + Future.delayed(const Duration(milliseconds: 1500), () { + sound!.filters.pitchShiftFilter + .shift(soundHandle: soundHandle) + .value = 1; + }); + }, + child: const Text('Fade shift'), + ), + ], + ), + ), + ), + ], + ), + ); + } +} diff --git a/example/lib/main.dart b/example/lib/main.dart index cbc0e62d..3b7d4cad 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -1,14 +1,8 @@ import 'dart:developer' as dev; -import 'dart:ui'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:flutter_soloud/flutter_soloud.dart'; -import 'package:flutter_soloud_example/page_3d_audio.dart'; -import 'package:flutter_soloud_example/page_hello_flutter.dart'; -import 'package:flutter_soloud_example/page_multi_track.dart'; -import 'package:flutter_soloud_example/page_visualizer.dart'; -import 'package:flutter_soloud_example/page_waveform.dart'; import 'package:logging/logging.dart'; void main() async { @@ -31,30 +25,27 @@ void main() async { WidgetsFlutterBinding.ensureInitialized(); - /// Initialize the player - await SoLoud.instance.init().then( - (_) { - Logger('main').info('player started'); - SoLoud.instance.setVisualizationEnabled(true); - SoLoud.instance.setGlobalVolume(1); - SoLoud.instance.setMaxActiveVoiceCount(32); - }, - onError: (Object e) { - Logger('main').severe('player starting error: $e'); - }, - ); + /// Initialize the player. + await SoLoud.instance.init(); - runApp(const MyApp()); + runApp( + const MaterialApp( + home: HelloFlutterSoLoud(), + ), + ); } -class MyApp extends StatefulWidget { - const MyApp({super.key}); +/// Simple usecase of flutter_soloud plugin +class HelloFlutterSoLoud extends StatefulWidget { + const HelloFlutterSoLoud({super.key}); @override - State createState() => _MyAppState(); + State createState() => _HelloFlutterSoLoudState(); } -class _MyAppState extends State { +class _HelloFlutterSoLoudState extends State { + AudioSource? currentSound; + @override void dispose() { SoLoud.instance.deinit(); @@ -63,59 +54,33 @@ class _MyAppState extends State { @override Widget build(BuildContext context) { - return MaterialApp( - themeMode: ThemeMode.dark, - darkTheme: ThemeData.dark(useMaterial3: true), - scrollBehavior: const MaterialScrollBehavior().copyWith( - // enable mouse dragging also on desktop - dragDevices: PointerDeviceKind.values.toSet(), - ), - home: const MyHomePage(), - ); - } -} + if (!SoLoud.instance.isInitialized) return const SizedBox.shrink(); -class MyHomePage extends StatelessWidget { - const MyHomePage({super.key}); + return Scaffold( + body: Center( + child: ElevatedButton( + onPressed: () async { + await SoLoud.instance.disposeAllSources(); - @override - Widget build(BuildContext context) { - return DefaultTabController( - length: 5, - child: SafeArea( - child: Scaffold( - body: Column( - children: [ - SingleChildScrollView( - scrollDirection: Axis.horizontal, - child: TabBar( - isScrollable: true, - onTap: (value) { - SoLoud.instance.disposeAllSources(); - }, - tabs: const [ - Tab(text: 'hello world!'), - Tab(text: 'visualizer'), - Tab(text: 'multi track'), - Tab(text: '3D audio'), - Tab(text: 'wave form'), - ], - ), - ), - const SizedBox(height: 8), - const Expanded( - child: TabBarView( - physics: NeverScrollableScrollPhysics(), - children: [ - PageHelloFlutterSoLoud(), - PageVisualizer(), - PageMultiTrack(), - Page3DAudio(), - PageWaveform(), - ], - ), - ), - ], + if (kIsWeb) { + /// load the audio file using [LoadMode.disk] (better for the + /// Web platform). + currentSound = await SoLoud.instance.loadAsset( + 'assets/audio/8_bit_mentality.mp3', + mode: LoadMode.disk, + ); + } else { + /// load the audio file + currentSound = await SoLoud.instance + .loadAsset('assets/audio/8_bit_mentality.mp3'); + } + + /// play it + await SoLoud.instance.play(currentSound!); + }, + child: const Text( + 'play asset', + textAlign: TextAlign.center, ), ), ), diff --git a/example/lib/main_wav_stream.dart b/example/lib/main_wav_stream.dart deleted file mode 100644 index f2e90fc4..00000000 --- a/example/lib/main_wav_stream.dart +++ /dev/null @@ -1,214 +0,0 @@ -import 'dart:async'; -import 'dart:ui'; - -import 'package:flutter/material.dart'; -import 'package:flutter_soloud/flutter_soloud.dart'; - -/// Example to test `mode: LoadMode.disk` parameter to seek all audio files -/// with a single slider. -/// -/// When using `SoloudTools.loadFrom*()` you can choose to load the audio -/// as a stream. This means that the audio is decoded when needed when -/// using `mode: LoadMode.disk` parameter. By default `LoadMode.disk` is used, -/// hence the audio file is loaded as raw data into memory. -/// -/// `mode: LoadMode.memory` this will be useful when -/// loading few audio files, ie for game sounds, mainly used to prevent -/// gaps or lags when starting/seeking a sound (less CPU, more memory allocated). -/// `mode: LoadMode.disk` the audio data is loaded from the given -/// file when needed (more CPU, less memory allocated). -/// The drawback is seeking: when using MP3s the seek operation is -/// performed but there will be delays. This occurs because -/// the MP3 codec must compute each frame length to gain a new position. -/// This mode is useful ie for background music, not for a music player -/// where a seek slider for MP3s is a must. -/// If you need seeking MP3s, please, use `mode: LoadMode.memory` instead, -/// or other audio formats! -/// -/// Please, in the the following example, add as many -/// `await addSound('audio/path');` you wish in the `start()` method. - -void main() { - runApp(const MyApp()); -} - -class MyApp extends StatelessWidget { - const MyApp({super.key}); - - @override - Widget build(BuildContext context) { - return MaterialApp( - themeMode: ThemeMode.dark, - darkTheme: ThemeData.dark(useMaterial3: true), - scrollBehavior: const MaterialScrollBehavior().copyWith( - // enable mouse dragging - dragDevices: PointerDeviceKind.values.toSet(), - ), - home: const MyHomePage(), - ); - } -} - -class MyHomePage extends StatefulWidget { - const MyHomePage({super.key}); - - @override - State createState() => _MyHomePageState(); -} - -class _MyHomePageState extends State { - List sounds = []; - bool isStarted = false; - Duration minLength = const Duration(days: 99); - ValueNotifier seekPos = ValueNotifier(Duration.zero); - late Timer timer; - - @override - void initState() { - super.initState(); - } - - Future start() async { - try { - await SoLoud.instance.init(); - } catch (e) { - debugPrint('isolate starting error: $e'); - return; - } - - debugPrint('isolate started'); - if (context.mounted) { - isStarted = true; - } - - await SoLoud.instance.disposeAllSources(); - sounds.clear(); - seekPos.value = Duration.zero; - - /// Add here the complete audio file path. - /// MP3s will have lags problem when seeking - await addSound(''); - - for (final s in sounds) { - await SoLoud.instance.play(s); - } - if (context.mounted) setState(() {}); - - timer = Timer.periodic(const Duration(milliseconds: 100), (timer) { - if (sounds.isEmpty || sounds[0].handles.isEmpty) return; - final p = SoLoud.instance.getPosition(sounds[0].handles.first); - if (p <= minLength) seekPos.value = p; - }); - } - - Future addSound(String file) async { - var l = Duration.zero; - final s = await SoLoud.instance.loadFile(file, mode: LoadMode.disk); - sounds.add(s); - l = SoLoud.instance.getLength(s); - if (l < minLength) minLength = l; - } - - Future stop() async { - SoLoud.instance.deinit(); - sounds.clear(); - } - - @override - void dispose() { - stop(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return SafeArea( - child: Scaffold( - body: Column( - children: [ - ElevatedButton( - onPressed: start, - child: const Text('start'), - ), - ...List.generate( - sounds.length, - (index) { - return SoundRow(soundProps: sounds[index]); - }, - ), - ValueListenableBuilder( - valueListenable: seekPos, - builder: (_, v, __) { - return Slider( - max: - minLength.inMilliseconds / Duration.millisecondsPerSecond, - value: v.inMilliseconds / Duration.millisecondsPerSecond, - onChanged: (value) { - final time = Duration( - milliseconds: - (value * Duration.millisecondsPerSecond).round(), - ); - seekPos.value = time; - for (final s in sounds) { - if (s.handles.isEmpty) continue; - SoLoud.instance.seek(s.handles.first, time); - } - }, - ); - }, - ), - ], - ), - ), - ); - } -} - -class SoundRow extends StatefulWidget { - const SoundRow({ - required this.soundProps, - super.key, - }); - final AudioSource soundProps; - - @override - State createState() => _SoundRowState(); -} - -class _SoundRowState extends State { - late final Timer timer; - Duration pos = Duration.zero; - Duration max = const Duration(days: 99); - - @override - void initState() { - super.initState(); - max = SoLoud.instance.getLength(widget.soundProps); - timer = Timer.periodic(const Duration(milliseconds: 100), (timer) { - if (widget.soundProps.handles.isEmpty) return; - pos = SoLoud.instance.getPosition(widget.soundProps.handles.first); - if (context.mounted) setState(() {}); - }); - } - - @override - void dispose() { - timer.cancel(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return Row( - children: [ - Text(widget.soundProps.soundHash.toString()), - Slider.adaptive( - max: max.inMilliseconds * Duration.millisecondsPerSecond.toDouble(), - value: pos.inMilliseconds * Duration.millisecondsPerSecond.toDouble(), - onChanged: null, - ), - Text(pos.toString()), - ], - ); - } -} diff --git a/example/lib/metronome/metronome.dart b/example/lib/metronome/metronome.dart new file mode 100644 index 00000000..ffd2753b --- /dev/null +++ b/example/lib/metronome/metronome.dart @@ -0,0 +1,156 @@ +import 'dart:async'; +import 'dart:developer' as dev; + +import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_soloud/flutter_soloud.dart'; +import 'package:logging/logging.dart'; + +/// Metronome example. +/// +/// For this example the player is initialized with a buffer size of 256. By +/// default it is set to 2048, this means that for a playback at 44100 Hz the +/// buffer is processed in about 40ms. When we use the `play()` method, the +/// sound will start at the upcoming audio buffer and this could happen with +/// a delay at least of 40ms. If we reduce the buffer size also this gap will +/// be shorter. A value of 256 or 512 will reduce this latency but at the same +/// time, the smaller the buffer, the more likely the +/// system hits buffer underruns (ie, the play head marches on but there's no +/// data ready to be played) and the sound breaks down horribly. + +void main() async { + // The `flutter_soloud` package logs everything + // (from severe warnings to fine debug messages) + // using the standard `package:logging`. + // You can listen to the logs as shown below. + Logger.root.level = kDebugMode ? Level.FINE : Level.INFO; + Logger.root.onRecord.listen((record) { + dev.log( + record.message, + time: record.time, + level: record.level.value, + name: record.loggerName, + zone: record.zone, + error: record.error, + stackTrace: record.stackTrace, + ); + }); + WidgetsFlutterBinding.ensureInitialized(); + + /// Initialize the player. + await SoLoud.instance.init(bufferSize: 256, channels: Channels.mono); + + runApp( + const MaterialApp( + home: Metronome(), + ), + ); +} + +class Metronome extends StatefulWidget { + const Metronome({super.key}); + + @override + State createState() => _MetronomeState(); +} + +class _MetronomeState extends State { + /// delay between ticks. + final delay = ValueNotifier(100); + + /// duration of the tick sound. + final tickDurationMs = ValueNotifier(45); + + Timer? timer; + AudioSource? tick1; + AudioSource? tick2; + SoundHandle? tick1Handle; + SoundHandle? tick2Handle; + + @override + void initState() { + super.initState(); + SoLoud.instance.loadAsset('assets/audio/tic-1.wav').then((value) async { + /// start playing the tick in a paused state, so it can be + /// unpaused/paused in the `Timer` callback. + tick1 = value; + tick1Handle = await SoLoud.instance.play(tick1!, paused: true); + await SoLoud.instance + .loadAsset('assets/audio/tic-2.wav') + .then((value) async { + /// start playing the tick in a paused state, so it can be + /// unpaused/paused in the `Timer` callback. + tick2 = value; + tick2Handle = await SoLoud.instance.play(tick2!, paused: true); + }); + }); + } + + @override + void dispose() { + SoLoud.instance.deinit(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Stack( + children: [ + Padding( + padding: const EdgeInsets.all(16), + child: Center( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + const Text( + 'Metronome example', + textScaler: TextScaler.linear(3), + ), + const SizedBox(height: 32), + ValueListenableBuilder( + valueListenable: delay, + builder: (_, ms, __) { + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + Slider.adaptive( + min: 15, + max: 500, + value: ms.toDouble(), + onChanged: (value) { + delay.value = value.toInt(); + start(); + }, + ), + Text('delay ms: $ms BPM: ${60000 ~/ ms}'), + ], + ); + }, + ), + ], + ), + ), + ), + ], + ), + ); + } + + int count = 0; + void start() { + timer?.cancel(); + timer = Timer.periodic(Duration(milliseconds: delay.value), (_) { + if (count % 8 == 0) { + if (tick2 != null) { + SoLoud.instance.play(tick2!); + } + } else { + if (tick1 != null) { + SoLoud.instance.play(tick1!); + } + } + count++; + }); + } +} diff --git a/example/lib/page_3d_audio.dart b/example/lib/page_3d_audio.dart deleted file mode 100644 index 33f84e29..00000000 --- a/example/lib/page_3d_audio.dart +++ /dev/null @@ -1,324 +0,0 @@ -import 'dart:math'; - -import 'package:flutter/material.dart'; -import 'package:flutter/scheduler.dart'; -import 'package:flutter_soloud/flutter_soloud.dart'; -import 'package:logging/logging.dart'; - -class Page3DAudio extends StatefulWidget { - const Page3DAudio({super.key}); - - @override - State createState() => _Page3DAudioState(); -} - -class _Page3DAudioState extends State { - static final Logger _log = Logger('_Page3DAudioState'); - - AudioSource? currentSound; - bool spinAround = false; - - @override - Widget build(BuildContext context) { - if (!SoLoud.instance.isInitialized) return const SizedBox.shrink(); - - return Scaffold( - body: Center( - child: Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - ElevatedButton( - onPressed: playFromUrl, - child: const Text('spin around\nsound from network'), - ), - const SizedBox(width: 16), - ElevatedButton( - onPressed: () => - play((MediaQuery.sizeOf(context).width - 20) / 2), - child: const Text('play'), - ), - ], - ), - const SizedBox(height: 16), - Audio3DWidget( - key: UniqueKey(), - spinAround: spinAround, - sound: currentSound, - width: MediaQuery.sizeOf(context).width * .8 - 20, - height: MediaQuery.sizeOf(context).width * .8 - 20, - ), - ], - ), - ), - ); - } - - /// Play an audio downloaded from url - /// - Future playFromUrl() async { - /// stop any previous sound loaded - if (currentSound != null) { - try { - await SoLoud.instance.disposeSource(currentSound!); - } catch (e) { - _log.severe('error disposing sound: $e'); - return; - } - } - - /// load the audio file - currentSound = await SoLoud.instance.loadUrl( - // From https://freetestdata.com/audio-files/mp3/ - 'https://marcobavagnoli.com/Free_Test_Data_500KB_MP3.mp3', - ); - - /// play it - await SoLoud.instance.play3d(currentSound!, 0, 0, 0, looping: true); - - spinAround = true; - - if (mounted) setState(() {}); - } - - /// Play the audio setting min and max distance and attenuation - /// - Future play(double maxDistance) async { - /// stop any previous sound loaded - if (currentSound != null) { - try { - await SoLoud.instance.disposeSource(currentSound!); - } catch (e) { - _log.severe('error disposing sound: $e'); - return; - } - } - - /// load the audio file - currentSound = await SoLoud.instance.loadAsset('assets/audio/siren.mp3'); - - /// play it - final newHandle = await SoLoud.instance.play3d( - currentSound!, - 0, - 0, - 0, - looping: true, - ); - - SoLoud.instance.set3dSourceMinMaxDistance( - newHandle, - 50, - maxDistance, - ); - SoLoud.instance.set3dSourceAttenuation(newHandle, 1, 0.5); - - spinAround = false; - - if (mounted) setState(() {}); - } -} - -class Audio3DWidget extends StatefulWidget { - const Audio3DWidget({ - required this.spinAround, - required this.width, - required this.height, - this.sound, - super.key, - }); - - final bool spinAround; - final AudioSource? sound; - final double width; - final double height; - - @override - State createState() => _Audio3DWidgetState(); -} - -class _Audio3DWidgetState extends State - with SingleTickerProviderStateMixin { - late Ticker ticker; - final doppler = ValueNotifier(true); - Duration precTime = Duration.zero; - double posX = 0; - double posY = -50; - double posZ = 0; - double velX = 0; - double velY = 0; - double animVel = 2; - double angle = 0; - Offset center = Offset.zero; - late double dx; - - @override - void initState() { - super.initState(); - dx = animVel; - ticker = Ticker(_tick); - if (widget.sound != null) ticker.start(); - } - - @override - void dispose() { - ticker.dispose(); - super.dispose(); - } - - void _tick(Duration elapsed) { - final prevX = posX; - final prevY = posY; - if (widget.spinAround) { - final circleRadius = Offset(posX - center.dx, posY - center.dy).distance; - angle += pi / (animVel / (circleRadius / widget.width)) / 50; - posX = circleRadius * cos(angle); - posY = circleRadius * sin(angle); - } else { - if (posX + dx > widget.width / 2) dx = -animVel; - if (posX + dx < -widget.width / 2) dx = animVel; - posX += dx; - } - updatePos(Offset(posX - prevX, posY - prevY), elapsed); - } - - void updatePos(Offset delta, Duration timeStamp) { - velX = - -100 * delta.dx / (timeStamp.inMilliseconds - precTime.inMilliseconds); - velY = - -100 * delta.dy / (timeStamp.inMilliseconds - precTime.inMilliseconds); - if (widget.sound != null) { - SoLoud.instance.set3dSourceParameters( - widget.sound!.handles.first, - posX, - posY, - posZ, - doppler.value ? velX : 0, - doppler.value ? velY : 0, - 0, - ); - } - - precTime = timeStamp; - if (mounted) setState(() {}); - } - - @override - Widget build(BuildContext context) { - return Column( - children: [ - Listener( - onPointerDown: (event) { - precTime = Duration.zero; - ticker.stop(); - }, - onPointerUp: (event) => ticker.start(), - onPointerMove: (event) { - posX = event.localPosition.dx - widget.width / 2; - posY = event.localPosition.dy - widget.height / 2; - updatePos(event.delta, event.timeStamp); - }, - child: Stack( - alignment: Alignment.center, - children: [ - RepaintBoundary( - child: CustomPaint( - size: Size(widget.width, widget.height), - painter: Audio3DPainter( - posX: posX, - posY: posY, - posZ: posZ, - ), - ), - ), - const Icon(Icons.accessibility), - ], - ), - ), - const SizedBox(height: 16), - ValueListenableBuilder( - valueListenable: doppler, - builder: (_, d, __) { - return Row( - mainAxisSize: MainAxisSize.min, - children: [ - Checkbox( - value: d, - onChanged: (value) { - doppler.value = value!; - if (mounted) setState(() {}); - }, - ), - const SizedBox(width: 12), - const Text('enable/disable doppler effect'), - ], - ); - }, - ), - Text('Pos x: ${posX.toStringAsFixed(1)}'), - Text('Pos y: ${posY.toStringAsFixed(1)}'), - Text('Pos z: ${posZ.toStringAsFixed(1)}'), - Text('Velocity x: ${velX.toStringAsFixed(1)}'), - Text('Velocity y: ${velY.toStringAsFixed(1)}'), - const Text('Velocity z: 0'), - ], - ); - } -} - -/// Custom painter to draw the wave in a circle -/// -class Audio3DPainter extends CustomPainter { - Audio3DPainter({ - required this.posX, - required this.posY, - required this.posZ, - }); - - double posX; - double posY; - double posZ; - - @override - void paint(Canvas canvas, Size size) { - final paint = Paint() - ..color = Colors.green - ..style = PaintingStyle.stroke - ..strokeWidth = 1.5; - - /// draw background circle - canvas - ..drawCircle( - Offset(size.width / 2, size.height / 2), - size.height / 2, - paint, - ) - - /// draw cross axis - ..drawLine( - Offset(size.width / 2, 0), - Offset(size.width / 2, size.height), - paint, - ) - ..drawLine( - Offset(0, size.height / 2), - Offset(size.width, size.height / 2), - paint, - ) - - /// draw sound position - ..drawCircle( - Offset(posX + size.width / 2, posY + size.height / 2), - size.width / 50, - Paint() - ..color = Colors.red - ..style = PaintingStyle.fill, - ); - } - - @override - bool shouldRepaint(covariant CustomPainter oldDelegate) { - return true; - } -} diff --git a/example/lib/page_hello_flutter.dart b/example/lib/page_hello_flutter.dart deleted file mode 100644 index 471c42cb..00000000 --- a/example/lib/page_hello_flutter.dart +++ /dev/null @@ -1,138 +0,0 @@ -import 'dart:async'; -import 'dart:io'; - -import 'package:file_picker/file_picker.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_soloud/flutter_soloud.dart'; -import 'package:logging/logging.dart'; - -/// Simple usecase of flutter_soloud plugin -class PageHelloFlutterSoLoud extends StatefulWidget { - const PageHelloFlutterSoLoud({super.key}); - - @override - State createState() => _PageHelloFlutterSoLoudState(); -} - -class _PageHelloFlutterSoLoudState extends State { - static final Logger _log = Logger('_PageHelloFlutterSoLoudState'); - - AudioSource? currentSound; - - @override - Widget build(BuildContext context) { - if (!SoLoud.instance.isInitialized) return const SizedBox.shrink(); - - return Scaffold( - body: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - /// pick audio file - OutlinedButton( - onPressed: () async { - final paths = (await FilePicker.platform.pickFiles( - type: FileType.custom, - allowedExtensions: ['mp3', 'wav', 'ogg', 'flac'], - onFileLoading: print, - dialogTitle: 'Pick audio file\n(not for web)', - )) - ?.files; - if (paths != null) { - unawaited(playFile(paths.first.path!)); - } - }, - child: const Text( - 'pick audio\n(not for web)', - textAlign: TextAlign.center, - ), - ), - - /// pick audio file - OutlinedButton( - onPressed: () async { - final paths = (await FilePicker.platform.pickFiles( - type: FileType.custom, - allowedExtensions: ['mp3', 'wav', 'ogg', 'flac'], - onFileLoading: print, - dialogTitle: 'Pick audio file', - )) - ?.files; - - if (paths != null) { - if (kIsWeb) { - unawaited(playBuffer(paths.first.name, paths.first.bytes!)); - } else { - final f = File(paths.first.path!); - final buffer = f.readAsBytesSync(); - unawaited(playBuffer(paths.first.path!, buffer)); - } - } - }, - child: const Text( - 'pick audio using "loadMem()"\n(all platforms)', - textAlign: TextAlign.center, - ), - ), - ], - ), - ), - ); - } - - /// play file - Future playFile(String file) async { - /// stop any previous sound loaded - if (currentSound != null) { - try { - await SoLoud.instance.disposeSource(currentSound!); - } catch (e) { - _log.severe('dispose error', e); - return; - } - } - - /// load the audio file - final AudioSource newSound; - try { - newSound = await SoLoud.instance.loadFile(file); - } catch (e) { - _log.severe('load error', e); - return; - } - - currentSound = newSound; - - /// play it - await SoLoud.instance.play(currentSound!); - } - - /// play bytes for web. - Future playBuffer(String fileName, Uint8List bytes) async { - /// stop any previous sound loaded - if (currentSound != null) { - try { - await SoLoud.instance.disposeSource(currentSound!); - } catch (e) { - _log.severe('dispose error', e); - return; - } - } - - /// load the audio file - final AudioSource newSound; - try { - newSound = await SoLoud.instance.loadMem(fileName, bytes); - } catch (e) { - _log.severe('load error', e); - return; - } - - currentSound = newSound; - - /// play it - await SoLoud.instance.play(currentSound!); - } -} diff --git a/example/lib/page_multi_track.dart b/example/lib/page_multi_track.dart deleted file mode 100644 index dbb27e81..00000000 --- a/example/lib/page_multi_track.dart +++ /dev/null @@ -1,367 +0,0 @@ -// ignore_for_file: public_member_api_docs, sort_constructors_first -import 'dart:async'; - -import 'package:flutter/material.dart'; -import 'package:flutter_soloud/flutter_soloud.dart'; -import 'package:logging/logging.dart'; - -class PageMultiTrack extends StatefulWidget { - const PageMultiTrack({super.key}); - - @override - State createState() => _PageMultiTrackState(); -} - -class _PageMultiTrackState extends State { - final _looping = ValueNotifier(false); - final _loopingStartAt = ValueNotifier(0); - final _playSoundController = PlaySoundController(); - - @override - Widget build(BuildContext context) { - if (!SoLoud.instance.isInitialized) return const SizedBox.shrink(); - - return Scaffold( - body: Padding( - padding: const EdgeInsets.only(top: 50, right: 8, left: 8), - child: Column( - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - PlaySoundWidget( - assetsAudio: 'assets/audio/8_bit_mentality.mp3', - text: '8 bit mentality', - controller: _playSoundController, - ), - const SizedBox(height: 32), - ValueListenableBuilder( - valueListenable: _looping, - builder: (_, looping, __) { - return ValueListenableBuilder( - valueListenable: _loopingStartAt, - builder: (_, loopingStartAt, __) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - const Text('looping'), - const SizedBox(width: 16), - Checkbox( - value: looping, - onChanged: (value) { - _looping.value = !looping; - _playSoundController.setLooping( - looping: value!, - ); - }, - ), - Expanded( - child: Slider( - value: loopingStartAt, - max: 3.8, // the length of explosion.mp3 - onChanged: (value) { - _loopingStartAt.value = value; - _playSoundController - .setLoopStartAT(value); - }, - ), - ), - ], - ), - PlaySoundWidget( - assetsAudio: 'assets/audio/explosion.mp3', - text: 'game explosion', - controller: _playSoundController, - ), - ], - ); - }, - ); - }, - ), - ], - ), - const SizedBox(height: 12), - ], - ), - ), - ); - } -} - -/// Controller to manage loop and loopStartAt parameters of all sounds. -class PlaySoundController { - void Function({required bool looping})? _looping; - void Function(double loopStartAt)? _loopingStartAt; - - void _setController( - void Function({required bool looping})? looping, - void Function(double loopStartAt)? loopingStartAt, - ) { - _looping = looping; - _loopingStartAt = loopingStartAt; - } - - void setLooping({required bool looping}) => _looping?.call(looping: looping); - void setLoopStartAT(double loopStartAt) => _loopingStartAt?.call(loopStartAt); -} - -class PlaySoundWidget extends StatefulWidget { - const PlaySoundWidget({ - required this.assetsAudio, - required this.text, - required this.controller, - super.key, - }); - - final String assetsAudio; - final String text; - final PlaySoundController controller; - - @override - State createState() => _PlaySoundWidgetState(); -} - -class _PlaySoundWidgetState extends State { - static final Logger _log = Logger('_PlaySoundWidgetState'); - - late Duration soundLength; - final Map> isPaused = {}; - final Map> soundPosition = {}; - StreamSubscription? _subscription; - AudioSource? sound; - bool _looping = false; - double _loopingStartAt = 0; - - @override - void initState() { - super.initState(); - widget.controller._setController(_setLooping, _setLoopStartAt); - } - - @override - void dispose() { - _subscription?.cancel(); - super.dispose(); - } - - void _setLooping({required bool looping}) { - _looping = looping; - if (sound != null) { - for (final element in sound!.handles) { - SoLoud.instance.setLooping(element, looping); - } - } - } - - void _setLoopStartAt(double loopingStartAt) { - _loopingStartAt = loopingStartAt; - if (sound != null) { - for (final element in sound!.handles) { - SoLoud.instance.setLoopPoint( - element, - Duration( - milliseconds: - (loopingStartAt * Duration.millisecondsPerSecond).round(), - ), - ); - } - } - } - - Future loadAsset() async { - final AudioSource? newSound; - newSound = await SoLoud.instance.loadAsset(widget.assetsAudio); - - soundLength = SoLoud.instance.getLength(newSound); - sound = newSound; - - /// Listen to this sound events - _subscription = sound!.soundEvents.listen( - (event) { - _log.fine('Received StreamSoundEvent ${event.event}'); - - /// if handle has been stoppend of has finished to play - if (event.event == SoundEventType.handleIsNoMoreValid) { - isPaused.remove(event.handle); - soundPosition.remove(event.handle); - } - - /// if the sound has been disposed - if (event.event == SoundEventType.soundDisposed) { - isPaused.clear(); - soundPosition.clear(); - _subscription?.cancel(); - sound = null; - } - - if (mounted) setState(() {}); - }, - ); - return true; - } - - @override - Widget build(BuildContext context) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ElevatedButton( - onPressed: () async { - await playAnotherInstance(); - if (mounted) setState(() {}); - }, - child: Text('load\n${widget.text}'), - ), - if (sound != null) - for (int i = 0; i < sound!.handles.length; ++i) - PlayingRow( - handle: sound!.handles.elementAt(i), - soundLength: soundLength, - onStopped: () { - if (mounted) setState(() {}); - }, - ), - ], - ); - } - - /// plays an assets file - /// - /// the 1st time call, the sound must be loaded. - /// Other calls, the sound is already in memory and no more - /// lag before play will happens - Future playAnotherInstance() async { - if (sound == null) { - if (!(await loadAsset())) return; - } - - final newHandle = await SoLoud.instance.play( - sound!, - looping: _looping, - loopingStartAt: Duration( - milliseconds: - (_loopingStartAt * Duration.millisecondsPerSecond).round(), - ), - ); - - isPaused[newHandle] = ValueNotifier(false); - soundPosition[newHandle] = ValueNotifier(0); - } -} - -/// row widget containing play/pause and time slider -class PlayingRow extends StatefulWidget { - const PlayingRow({ - required this.handle, - required this.soundLength, - required this.onStopped, - super.key, - }); - - final Duration soundLength; - final SoundHandle handle; - final VoidCallback onStopped; - - @override - State createState() => _PlayingRowState(); -} - -class _PlayingRowState extends State { - final ValueNotifier isPaused = ValueNotifier(true); - final ValueNotifier soundPosition = ValueNotifier(Duration.zero); - Timer? timer; - - @override - void dispose() { - stopTimer(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return Row( - children: [ - ValueListenableBuilder( - valueListenable: isPaused, - builder: (_, paused, __) { - if (paused) { - startTimer(); - } else { - stopTimer(); - } - return IconButton( - onPressed: () async { - SoLoud.instance.pauseSwitch(widget.handle); - isPaused.value = SoLoud.instance.getPause(widget.handle); - }, - icon: paused - ? const Icon(Icons.pause_circle_outline, size: 48) - : const Icon(Icons.play_circle_outline, size: 48), - iconSize: 48, - ); - }, - ), - const SizedBox(width: 16), - IconButton( - onPressed: () async { - await SoLoud.instance.stop(widget.handle); - widget.onStopped(); - }, - icon: const Icon(Icons.stop_circle_outlined, size: 48), - iconSize: 48, - ), - - /// Seek slider - Expanded( - child: ValueListenableBuilder( - valueListenable: soundPosition, - builder: (_, position, __) { - if (position >= widget.soundLength) { - position = Duration.zero; - } - final positionText = '${position.inMinutes}:' - '${(position.inSeconds % 60).toString().padLeft(2, '0')}'; - - return Row( - children: [ - Text(positionText), - Expanded( - child: Slider( - value: position.inMilliseconds.toDouble(), - max: widget.soundLength < position - ? position.inMilliseconds.toDouble() - : widget.soundLength.inMilliseconds.toDouble(), - onChanged: (value) { - final newPosition = - Duration(milliseconds: value.round()); - soundPosition.value = newPosition; - SoLoud.instance.seek(widget.handle, newPosition); - }, - ), - ), - Text(widget.soundLength.inSeconds.toString()), - ], - ); - }, - ), - ), - ], - ); - } - - /// start timer to update the audio position slider - void startTimer() { - timer?.cancel(); - timer = Timer.periodic(const Duration(milliseconds: 100), (_) { - soundPosition.value = SoLoud.instance.getPosition(widget.handle); - }); - } - - /// stop timer - void stopTimer() { - timer?.cancel(); - } -} diff --git a/example/lib/page_visualizer.dart b/example/lib/page_visualizer.dart deleted file mode 100644 index d76c7047..00000000 --- a/example/lib/page_visualizer.dart +++ /dev/null @@ -1,459 +0,0 @@ -import 'dart:async'; - -import 'package:file_picker/file_picker.dart'; -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter_soloud/flutter_soloud.dart'; -import 'package:flutter_soloud_example/visualizer/visualizer.dart'; -import 'package:logging/logging.dart'; -import 'package:star_menu/star_menu.dart'; - -class PageVisualizer extends StatefulWidget { - const PageVisualizer({super.key}); - - @override - State createState() => _PageVisualizerState(); -} - -class _PageVisualizerState extends State { - static final Logger _log = Logger('_PageVisualizerState'); - - String shader = 'assets/shaders/test9.frag'; - final regExp = RegExp('_(s[w|i].*)_-'); - final List audioChecks = [ - 'assets/audio/audiocheck.net_sweep_20Hz_20000Hz_-3dBFS_4s_logarithmic.wav', - 'assets/audio/audiocheck.net_sin_8000Hz_48000_-3dBFS_3s.wav', - 'assets/audio/12Bands/audiocheck.net_sin_16Hz_-3dBFS_2s.wav', - 'assets/audio/12Bands/audiocheck.net_sin_31.5Hz_-3dBFS_2s.wav', - 'assets/audio/12Bands/audiocheck.net_sin_63Hz_-3dBFS_2s.wav', - 'assets/audio/12Bands/audiocheck.net_sin_125Hz_-3dBFS_2s.wav', - 'assets/audio/12Bands/audiocheck.net_sin_250Hz_-3dBFS_2s.wav', - 'assets/audio/12Bands/audiocheck.net_sin_500Hz_-3dBFS_2s.wav', - 'assets/audio/12Bands/audiocheck.net_sin_1000Hz_-3dBFS_2s.wav', - 'assets/audio/12Bands/audiocheck.net_sin_2000Hz_-3dBFS_2s.wav', - 'assets/audio/12Bands/audiocheck.net_sin_4000Hz_-3dBFS_2s.wav', - 'assets/audio/12Bands/audiocheck.net_sin_8000Hz_-3dBFS_2s.wav', - 'assets/audio/12Bands/audiocheck.net_sin_16000Hz_-3dBFS_2s.wav', - 'assets/audio/12Bands/audiocheck.net_sin_20000Hz_-3dBFS_2s.wav', - ]; - late final ValueNotifier samplesKind; - final ValueNotifier fftSmoothing = ValueNotifier(0.8); - final ValueNotifier isVisualizerEnabled = ValueNotifier(true); - late ValueNotifier fftImageRange; - final ValueNotifier soundLength = ValueNotifier(0); - final ValueNotifier soundPosition = ValueNotifier(0); - Timer? timer; - AudioSource? currentSound; - late final VisualizerController visualizerController; - - @override - void initState() { - super.initState(); - samplesKind = ValueNotifier(GetSamplesKind.linear); - visualizerController = VisualizerController(samplesKind: samplesKind.value); - fftImageRange = ValueNotifier( - RangeValues( - visualizerController.minRange.toDouble(), - visualizerController.maxRange.toDouble(), - ), - ); - } - - @override - void dispose() { - visualizerController.audioData.dispose(); - super.dispose(); - } - - @override - Widget build(BuildContext context) { - return Scaffold( - body: SingleChildScrollView( - child: Column( - children: [ - /// audio, shader and texture kind popup menu - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - /// audio samples popup menu - StarMenu( - params: StarMenuParameters( - shape: MenuShape.linear, - boundaryBackground: BoundaryBackground( - color: Colors.white.withOpacity(0.1), - blurSigmaX: 6, - blurSigmaY: 6, - ), - linearShapeParams: LinearShapeParams( - angle: -90, - space: defaultTargetPlatform == TargetPlatform.android || - defaultTargetPlatform == TargetPlatform.iOS - ? -10 - : 10, - alignment: LinearAlignment.left, - ), - ), - onItemTapped: (index, controller) { - controller.closeMenu!(); - }, - items: [ - ActionChip( - backgroundColor: Colors.blue, - onPressed: () { - playAsset('assets/audio/8_bit_mentality.mp3'); - }, - label: const Text('8 bit mentality'), - ), - ActionChip( - backgroundColor: Colors.blue, - onPressed: () { - playAsset('assets/audio/TropicalBeeper.mp3'); - }, - label: const Text('Tropical Beeper'), - ), - ActionChip( - backgroundColor: Colors.blue, - onPressed: () { - playAsset('assets/audio/XtrackTure.mp3'); - }, - label: const Text('X trackTure'), - ), - for (int i = 0; i < audioChecks.length; i++) - ActionChip( - backgroundColor: Colors.blue, - onPressed: () { - playAsset(audioChecks[i]); - }, - label: Text( - regExp.firstMatch(audioChecks[i])?.group(1) ?? '', - ), - ), - ], - child: const Chip( - label: Text('samples'), - backgroundColor: Colors.blue, - avatar: Icon(Icons.arrow_drop_down), - ), - ), - const SizedBox(width: 10), - - /// shaders popup menu - StarMenu( - params: StarMenuParameters( - shape: MenuShape.linear, - boundaryBackground: BoundaryBackground( - color: Colors.white.withOpacity(0.1), - blurSigmaX: 6, - blurSigmaY: 6, - ), - linearShapeParams: const LinearShapeParams( - angle: -90, - space: 10, - alignment: LinearAlignment.left, - ), - ), - onItemTapped: (index, controller) { - controller.closeMenu!(); - }, - items: [ - for (int i = 1; i <= 9; ++i) - ActionChip( - backgroundColor: Colors.blue, - onPressed: () { - setState(() { - shader = 'assets/shaders/test$i.frag'; - }); - }, - label: Text(i.toString()), - ), - ], - child: const Chip( - label: Text('shaders'), - backgroundColor: Colors.blue, - avatar: Icon(Icons.arrow_drop_down), - ), - ), - const SizedBox(width: 10), - - /// texture kind - StarMenu( - params: StarMenuParameters( - shape: MenuShape.linear, - boundaryBackground: BoundaryBackground( - color: Colors.white.withOpacity(0.1), - blurSigmaX: 6, - blurSigmaY: 6, - ), - linearShapeParams: const LinearShapeParams( - angle: -90, - space: 10, - alignment: LinearAlignment.left, - ), - ), - onItemTapped: (index, controller) { - controller.closeMenu!(); - }, - items: [ - /// wave data (amplitudes) - ActionChip( - backgroundColor: Colors.blue, - onPressed: () { - samplesKind.value = GetSamplesKind.wave; - visualizerController - .changeSamplesKind(GetSamplesKind.wave); - fftImageRange.value = const RangeValues(0, 255); - }, - label: const Text('wave data'), - ), - - /// frequencies on 1st 256 px row - /// wave on 2nd 256 px row - ActionChip( - backgroundColor: Colors.blue, - onPressed: () { - samplesKind.value = GetSamplesKind.linear; - visualizerController - .changeSamplesKind(GetSamplesKind.linear); - fftImageRange.value = const RangeValues(0, 255); - }, - label: const Text('linear'), - ), - - /// both fft and wave - ActionChip( - backgroundColor: Colors.blue, - onPressed: () { - samplesKind.value = GetSamplesKind.texture; - visualizerController - .changeSamplesKind(GetSamplesKind.texture); - fftImageRange.value = const RangeValues(0, 511); - }, - label: const Text('texture'), - ), - ], - child: ValueListenableBuilder( - valueListenable: samplesKind, - builder: (_, type, __) { - return Chip( - label: Text(type.name), - backgroundColor: Colors.blue, - avatar: const Icon(Icons.arrow_drop_down), - ); - }, - ), - ), - ], - ), - const SizedBox(height: 8), - - /// Text to Speech & pick audio file - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - /// text 2 speech - ElevatedButton( - onPressed: () { - SoLoud.instance - .speechText('Hello Flutter. Text to speech!!'); - }, - child: const Text('T2S'), - ), - const SizedBox(width: 6), - - /// pick audio file - ElevatedButton( - onPressed: () async { - final paths = (await FilePicker.platform.pickFiles( - type: FileType.custom, - allowedExtensions: ['mp3', 'wav', 'ogg', 'flac'], - onFileLoading: print, - dialogTitle: 'Pick audio file', - )) - ?.files; - - if (paths != null) { - final AudioSource audioFile; - if (kIsWeb) { - audioFile = await SoLoud.instance - .loadMem(paths.first.name, paths.first.bytes!); - } else { - audioFile = - await SoLoud.instance.loadFile(paths.first.path!); - } - unawaited(play(audioFile)); - } - }, - child: const Text('pick audio'), - ), - ], - ), - - /// Seek slider. - /// Not used on web platforms because [LoadMode.disk] - /// is used with `loadMem()`. Otherwise the seek problem will - /// be noticeable while seeking. See [SoLoud.seek] note. - if (!kIsWeb) - ValueListenableBuilder( - valueListenable: soundLength, - builder: (_, length, __) { - return ValueListenableBuilder( - valueListenable: soundPosition, - builder: (_, position, __) { - if (position >= length) { - position = 0; - if (length == 0) length = 1; - } - - return Row( - children: [ - Text(position.toInt().toString()), - Expanded( - child: Slider.adaptive( - value: position, - max: length < position ? position : length, - onChanged: (value) { - if (currentSound == null) return; - stopTimer(); - final position = Duration( - milliseconds: - (value * Duration.millisecondsPerSecond) - .round(), - ); - SoLoud.instance - .seek(currentSound!.handles.last, position); - soundPosition.value = value; - startTimer(); - }, - ), - ), - Text(length.toInt().toString()), - ], - ); - }, - ); - }, - ), - - /// fft range slider values to put into the texture - ValueListenableBuilder( - valueListenable: fftImageRange, - builder: (_, fftRange, __) { - return Row( - children: [ - Text('FFT range ${fftRange.start.toInt()}'), - Expanded( - child: RangeSlider( - max: visualizerController.maxRangeLimit.toDouble() + 1, - values: fftRange, - onChanged: (values) { - fftImageRange.value = values; - visualizerController - ..changeMin(values.start.toInt()) - ..changeMax(values.end.toInt()); - }, - ), - ), - Text('${fftRange.end.toInt()}'), - ], - ); - }, - ), - - /// fft smoothing slider - ValueListenableBuilder( - valueListenable: fftSmoothing, - builder: (_, smoothing, __) { - return Row( - children: [ - Text('FFT smooth: ${smoothing.toStringAsFixed(2)}'), - Expanded( - child: Slider.adaptive( - value: smoothing, - onChanged: (smooth) { - SoLoud.instance.setFftSmoothing(smooth); - fftSmoothing.value = smooth; - }, - ), - ), - ], - ); - }, - ), - - /// VISUALIZER - Visualizer( - // key: UniqueKey(), - controller: visualizerController, - shader: shader, - ), - ], - ), - ), - ); - } - - /// play file - Future play(AudioSource source) async { - if (currentSound != null) { - try { - await SoLoud.instance.disposeSource(currentSound!); - } catch (e) { - _log.severe('error disposing the current sound', e); - return; - } - stopTimer(); - } - currentSound = source; - - /// play it - await SoLoud.instance.play(currentSound!); - - /// get its length and notify it - soundLength.value = - SoLoud.instance.getLength(currentSound!).inMilliseconds / - Duration.millisecondsPerSecond; - - /// Stop the timer and dispose the sound when the sound ends - currentSound!.soundEvents.listen( - (event) { - stopTimer(); - - /// It's needed to call dispose when it ends else it will - /// not be cleared - if (currentSound != null) { - SoLoud.instance.disposeSource(currentSound!); - currentSound = null; - } - }, - ); - startTimer(); - } - - /// plays an assets file - Future playAsset(String assetsFile) async { - // final audioFile = await getAssetFile(assetsFile); - final audioFile = await SoLoud.instance.loadAsset( - assetsFile, - mode: kIsWeb ? LoadMode.disk : LoadMode.memory, - ); - return play(audioFile); - } - - /// start timer to update the audio position slider - void startTimer() { - timer?.cancel(); - timer = Timer.periodic(const Duration(seconds: 1), (_) { - if (currentSound != null) { - soundPosition.value = SoLoud.instance - .getPosition(currentSound!.handles.last) - .inMilliseconds / - Duration.millisecondsPerSecond; - } - }); - } - - /// stop timer - void stopTimer() { - timer?.cancel(); - } -} diff --git a/example/lib/page_waveform.dart b/example/lib/page_waveform.dart deleted file mode 100644 index 3e13a60a..00000000 --- a/example/lib/page_waveform.dart +++ /dev/null @@ -1,336 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_soloud/flutter_soloud.dart'; -import 'package:flutter_soloud_example/ui/bars.dart'; -import 'package:flutter_soloud_example/ui/filter_fx.dart'; -import 'package:flutter_soloud_example/ui/keyboard_widget.dart'; -import 'package:flutter_soloud_example/ui/knobs_groups.dart'; -import 'package:flutter_soloud_example/ui/text_slider.dart'; -import 'package:star_menu/star_menu.dart'; - -/// Example to demostrate how waveforms work with a keyboard -/// -class PageWaveform extends StatefulWidget { - const PageWaveform({super.key}); - - @override - State createState() => _PageWaveformState(); -} - -class _PageWaveformState extends State { - ValueNotifier scale = ValueNotifier(0.25); - ValueNotifier detune = ValueNotifier(1); - ValueNotifier waveForm = ValueNotifier(WaveForm.fSquare); - bool superWave = false; - int octave = 2; - double fadeIn = 0.3; - double fadeOut = 0.3; - double fadeSpeedIn = 0; - double fadeSpeedOut = 0; - double oscillateVol = 0; - double oscillatePan = 0; - double oscillateSpeed = 0; - List notes = []; - AudioSource? sound; - bool canBuild = false; - - @override - void initState() { - super.initState(); - - /// Only when the [notes] are set up, build the UI - setupNotes().then((value) { - if (context.mounted) { - setState(() { - canBuild = true; - }); - } - }); - } - - Future setupNotes() async { - await SoLoud.instance.disposeAllSources(); - notes = await SoLoudTools.createNotes( - octave: octave, - superwave: superWave, - waveForm: waveForm.value, - ); - - /// set all sounds to pause state - for (final s in notes) { - await SoLoud.instance.play(s, paused: true); - } - } - - @override - Widget build(BuildContext context) { - if (!SoLoud.instance.isInitialized || !canBuild) { - return const SizedBox.shrink(); - } - - return Scaffold( - body: SingleChildScrollView( - padding: const EdgeInsets.all(8), - child: Column( - children: [ - Row( - children: [ - ElevatedButton( - onPressed: () async { - if (sound != null) { - await SoLoud.instance.disposeSource(sound!); - } - - await SoLoud.instance - .speechText('Hello Flutter Soloud!') - .then((value) => sound = value); - }, - child: const Text('T2S'), - ), - const SizedBox(width: 8), - ElevatedButton( - onPressed: () async { - final newSound = await SoLoud.instance.loadAsset( - 'assets/audio/8_bit_mentality.mp3', - ); - await SoLoud.instance - .play(newSound) - .then((value) => sound = newSound); - }, - child: const Text('play sample'), - ), - const SizedBox(width: 8), - ElevatedButton( - onPressed: () { - if (sound != null) { - SoLoud.instance.disposeSource(sound!).then((value) { - sound = null; - }); - } - }, - child: const Text('stop'), - ), - ], - ), - - /// Scale - ValueListenableBuilder( - valueListenable: scale, - builder: (_, newScale, __) { - return TextSlider( - text: 'scale ', - min: 0, - max: 2, - value: newScale, - enabled: superWave, - onChanged: (value) { - scale.value = value; - for (var i = 0; i < notes.length; i++) { - SoLoud.instance.setWaveformScale(notes[i], value); - } - }, - ); - }, - ), - - /// Detune - ValueListenableBuilder( - valueListenable: detune, - builder: (_, newDetune, __) { - return TextSlider( - text: 'detune', - min: 0, - max: 1, - value: newDetune, - enabled: superWave, - onChanged: (value) { - detune.value = value; - for (var i = 0; i < notes.length; i++) { - SoLoud.instance.setWaveformDetune(notes[i], value); - } - }, - ); - }, - ), - - /// Octave - TextSlider( - text: 'octave', - min: 0, - max: 4, - value: octave.toDouble(), - enabled: true, - isDivided: true, - onChanged: (value) async { - octave = value.toInt(); - await setupNotes(); - if (mounted) setState(() {}); - }, - ), - - /// SuperWave - Row( - children: [ - const Text('superWave '), - Checkbox.adaptive( - value: superWave, - onChanged: (value) { - superWave = value!; - for (var i = 0; i < notes.length; i++) { - SoLoud.instance.setWaveformSuperWave(notes[i], value); - } - if (mounted) setState(() {}); - }, - ), - ], - ), - - DefaultTabController( - length: 11, - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - const TabBar( - isScrollable: true, - dividerColor: Colors.blue, - tabs: [ - Tab(text: 'faders'), - Tab(text: 'oscillators'), - Tab(text: 'Biquad Filter'), - Tab(text: 'Eq'), - Tab(text: 'Echo'), - Tab(text: 'Lofi'), - Tab(text: 'Flanger'), - Tab(text: 'Bassboost'), - Tab(text: 'Wave shaper'), - Tab(text: 'Robotize'), - Tab(text: 'Freeverb'), - ], - ), - const SizedBox(height: 8), - SizedBox( - height: 210, - child: TabBarView( - physics: const NeverScrollableScrollPhysics(), - children: [ - /// Faders - KnobsGroup( - texts: const ['in', 'out', 'speed in', 'speed out'], - values: [fadeIn, fadeOut, fadeSpeedIn, fadeSpeedOut], - mins: const [0, 0, 0, 0], - maxs: const [2, 2, 2, 2], - onChanges: [ - (value) => setState(() => fadeIn = value), - (value) => setState(() => fadeOut = value), - (value) => setState(() => fadeSpeedIn = value), - (value) => setState(() => fadeSpeedOut = value), - ], - ), - - /// Oscillators - KnobsGroup( - texts: const ['volume', 'pan', 'speed'], - values: [oscillateVol, oscillatePan, oscillateSpeed], - mins: const [0, 0, 0], - maxs: const [0.5, 0.5, 0.5], - onChanges: [ - (value) => setState(() => oscillateVol = value), - (value) => setState(() => oscillatePan = value), - (value) => setState(() => oscillateSpeed = value), - ], - ), - - /// Biquad Resonant - const FilterFx( - filterType: FilterType.biquadResonantFilter, - ), - - /// Eq - const FilterFx(filterType: FilterType.eqFilter), - - /// Echo - const FilterFx(filterType: FilterType.echoFilter), - - /// Lofi - const FilterFx(filterType: FilterType.lofiFilter), - - /// Flanger - const FilterFx(filterType: FilterType.flangerFilter), - - /// Bassboost - const FilterFx(filterType: FilterType.bassboostFilter), - - /// Wave Shaper - const FilterFx(filterType: FilterType.waveShaperFilter), - - /// Robotize - const FilterFx(filterType: FilterType.robotizeFilter), - - /// Freeverb - const FilterFx(filterType: FilterType.freeverbFilter), - ], - ), - ), - ], - ), - ), - - /// Choose wave form - StarMenu( - params: StarMenuParameters( - shape: MenuShape.linear, - boundaryBackground: BoundaryBackground( - color: Colors.white.withOpacity(0.1), - blurSigmaX: 6, - blurSigmaY: 6, - ), - linearShapeParams: const LinearShapeParams( - angle: -90, - alignment: LinearAlignment.left, - ), - ), - onItemTapped: (index, controller) { - controller.closeMenu!(); - }, - items: [ - for (int i = 0; i < WaveForm.values.length; i++) - ActionChip( - backgroundColor: Colors.blue, - onPressed: () async { - waveForm.value = WaveForm.values[i]; - await setupNotes(); - if (mounted) setState(() {}); - }, - label: Text(WaveForm.values[i].name), - ), - ], - child: Chip( - label: Text(WaveForm.values[waveForm.value.index].name), - backgroundColor: Colors.blue, - avatar: const Icon(Icons.arrow_drop_down), - ), - ), - - const SizedBox(height: 8), - KeyboardWidget( - notes: notes, - fadeIn: _duration(fadeIn), - fadeOut: _duration(fadeOut), - fadeSpeedIn: _duration(fadeSpeedIn), - fadeSpeedOut: _duration(fadeSpeedOut), - oscillateVolume: _duration(oscillateVol), - oscillatePan: _duration(oscillatePan), - oscillateSpeed: _duration(oscillateSpeed), - ), - const SizedBox(height: 8), - Bars(key: UniqueKey()), - const SizedBox(height: 8), - ], - ), - ), - ); - } - - static Duration _duration(double seconds) => Duration( - microseconds: (seconds * Duration.microsecondsPerSecond).round(), - ); -} diff --git a/example/lib/ui/bars.dart b/example/lib/ui/bars.dart deleted file mode 100644 index b048a575..00000000 --- a/example/lib/ui/bars.dart +++ /dev/null @@ -1,70 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/scheduler.dart'; -import 'package:flutter_soloud/flutter_soloud.dart'; -import 'package:flutter_soloud_example/visualizer/bars_fft_widget.dart'; -import 'package:flutter_soloud_example/visualizer/bars_wave_widget.dart'; - -/// Visualizer for FFT and wave data -class Bars extends StatefulWidget { - /// If true get audio data from the player else from the mic - - const Bars({super.key}); - - @override - State createState() => BarsState(); -} - -class BarsState extends State with SingleTickerProviderStateMixin { - late final Ticker ticker; - final AudioData audioData = AudioData( - GetSamplesKind.linear, - ); - @override - void initState() { - super.initState(); - ticker = createTicker(_tick); - ticker.start(); - } - - @override - void dispose() { - ticker.stop(); - audioData.dispose(); - super.dispose(); - } - - void _tick(Duration elapsed) { - if (context.mounted) { - try { - audioData.updateSamples(); - setState(() {}); - } on Exception catch (e) { - debugPrint('$e'); - } - } - } - - @override - Widget build(BuildContext context) { - return Padding( - padding: const EdgeInsets.all(6), - child: Row( - children: [ - BarsFftWidget( - audioData: audioData, - minFreq: 0, - maxFreq: 128, - width: MediaQuery.sizeOf(context).width / 2 - 17, - height: MediaQuery.sizeOf(context).width / 6, - ), - const SizedBox(width: 6), - BarsWaveWidget( - audioData: audioData, - width: MediaQuery.sizeOf(context).width / 2 - 17, - height: MediaQuery.sizeOf(context).width / 6, - ), - ], - ), - ); - } -} diff --git a/example/lib/ui/filter_fx.dart b/example/lib/ui/filter_fx.dart deleted file mode 100644 index 97b9347f..00000000 --- a/example/lib/ui/filter_fx.dart +++ /dev/null @@ -1,114 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_soloud/flutter_soloud.dart'; -import 'package:flutter_soloud_example/ui/touch_slider.dart'; - -class FilterFx extends StatefulWidget { - const FilterFx({ - required this.filterType, - super.key, - }); - - final FilterType filterType; - - @override - State createState() => _FilterFxState(); -} - -class _FilterFxState extends State { - late bool enabled; - late FxParams fxParams; - late List params; - - @override - void initState() { - super.initState(); - enabled = SoLoud.instance.isFilterActive(widget.filterType) != -1; - - switch (widget.filterType) { - case FilterType.biquadResonantFilter: - params = List.from(fxBiquadResonant.defs); - fxParams = fxBiquadResonant; - case FilterType.eqFilter: - params = List.from(fxEq.defs); - fxParams = fxEq; - case FilterType.echoFilter: - params = List.from(fxEcho.defs); - fxParams = fxEcho; - case FilterType.lofiFilter: - params = List.from(fxLofi.defs); - fxParams = fxLofi; - case FilterType.flangerFilter: - params = List.from(fxFlanger.defs); - fxParams = fxFlanger; - case FilterType.bassboostFilter: - params = List.from(fxBassboost.defs); - fxParams = fxBassboost; - case FilterType.waveShaperFilter: - params = List.from(fxWaveShaper.defs); - fxParams = fxWaveShaper; - case FilterType.robotizeFilter: - params = List.from(fxRobotize.defs); - fxParams = fxRobotize; - case FilterType.freeverbFilter: - params = List.from(fxFreeverb.defs); - fxParams = fxFreeverb; - } - } - - @override - Widget build(BuildContext context) { - return Column( - children: [ - /// enable / disable FX - Row( - children: [ - Text(fxParams.title), - Checkbox.adaptive( - value: enabled, - onChanged: (value) { - enabled = value!; - if (enabled) { - try { - SoLoud.instance.addGlobalFilter(widget.filterType); - } catch (e) { - enabled = false; - } - for (var i = 0; i < params.length; i++) { - params[i] = SoLoud.instance - .getGlobalFilterParameter(widget.filterType, i); - } - } else { - try { - SoLoud.instance.removeGlobalFilter(widget.filterType); - } catch (e) { - enabled = true; - } - } - if (mounted) setState(() {}); - }, - ), - ], - ), - - /// Params knobs - Wrap( - children: List.generate(params.length, (index) { - return TouchSlider( - text: fxParams.names[index], - diameter: 75, - min: fxParams.mins[index], - max: fxParams.maxs[index], - value: params[index], - onChanged: (value) async { - params[index] = value; - SoLoud.instance - .setGlobalFilterParameter(widget.filterType, index, value); - if (mounted) setState(() {}); - }, - ); - }), - ), - ], - ); - } -} diff --git a/example/lib/ui/keyboard_widget.dart b/example/lib/ui/keyboard_widget.dart deleted file mode 100644 index 568e4edf..00000000 --- a/example/lib/ui/keyboard_widget.dart +++ /dev/null @@ -1,207 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter/services.dart'; -import 'package:flutter_soloud/flutter_soloud.dart'; - -/// Widget to display and manage touch/keys event -/// -class KeyboardWidget extends StatefulWidget { - const KeyboardWidget({ - required this.notes, - required this.fadeIn, - required this.fadeOut, - required this.fadeSpeedIn, - required this.fadeSpeedOut, - required this.oscillateVolume, - required this.oscillatePan, - required this.oscillateSpeed, - super.key, - }); - - final Duration fadeIn; - final Duration fadeOut; - final Duration fadeSpeedIn; - final Duration fadeSpeedOut; - final Duration oscillateVolume; - final Duration oscillatePan; - final Duration oscillateSpeed; - final List notes; - - @override - State createState() => _KeyboardWidgetState(); -} - -class _KeyboardWidgetState extends State { - late final List notesText; - late final List notesKeys; - late final List> isPressed; - late double noteKeyWidth; - int lastKeyPress = -1; - - @override - void initState() { - super.initState(); - notesText = [ - 'C', - 'C#', - 'D', - 'D#', - 'E', - 'F', - 'F#', - 'G', - 'G#', - 'A', - 'A#', - 'B', - ]; - notesKeys = [ - 'Q', - '2', - 'W', - '3', - 'E', - 'R', - '5', - 'T', - '6', - 'Y', - '7', - 'U', - ]; - isPressed = List.generate(12, (index) { - return ValueNotifier(false); - }); - - ServicesBinding.instance.keyboard.addHandler(onKey); - } - - @override - void dispose() { - ServicesBinding.instance.keyboard.removeHandler(onKey); - super.dispose(); - } - - /// Play a sound when key is pressed - bool onKey(KeyEvent event) { - final key = event.logicalKey.keyLabel; - final keyId = notesKeys.indexOf(key); - if (keyId == -1) return true; - - if (event is KeyDownEvent) { - play(keyId); - } else if (event is KeyUpEvent) { - stop(keyId); - } - - return false; - } - - Future play(int index) async { - if (index < 0 || index >= notesKeys.length) return; - if (isPressed[index].value) return; - final handle = widget.notes[index].handles.first; - SoLoud.instance.setRelativePlaySpeed(handle, 0); - SoLoud.instance.setVolume(handle, 0); - SoLoud.instance.fadeVolume(handle, 1, widget.fadeIn); - SoLoud.instance.fadeRelativePlaySpeed( - handle, - 1, - widget.fadeSpeedIn, - ); - if (widget.oscillateVolume > Duration.zero) { - SoLoud.instance.oscillateVolume(handle, 0.3, 1, widget.oscillateVolume); - } - if (widget.oscillatePan > Duration.zero) { - SoLoud.instance.oscillatePan(handle, 0.3, 1, widget.oscillatePan); - } - if (widget.oscillateSpeed > Duration.zero) { - SoLoud.instance - .oscillateRelativePlaySpeed(handle, 0.3, 1, widget.oscillateSpeed); - } - SoLoud.instance.setPause(handle, false); - isPressed[index].value = true; - } - - void stop(int index) { - if (index < 0 || index >= notesKeys.length) return; - for (final h in widget.notes[index].handles) { - SoLoud.instance.fadeVolume(h, 0, widget.fadeOut); - SoLoud.instance.fadeRelativePlaySpeed(h, 0, widget.fadeSpeedOut); - SoLoud.instance.schedulePause(h, widget.fadeOut); - } - isPressed[index].value = false; - } - - @override - Widget build(BuildContext context) { - if (widget.notes.length != 12) return const SizedBox.shrink(); - noteKeyWidth = (MediaQuery.sizeOf(context).width - 16) / 12; - - return SizedBox( - height: 120, - child: GestureDetector( - onPanDown: (e) { - stop(lastKeyPress); - lastKeyPress = e.localPosition.dx.toInt() ~/ noteKeyWidth; - play(lastKeyPress); - }, - onPanUpdate: (e) { - if (lastKeyPress != e.localPosition.dx.toInt() ~/ noteKeyWidth) { - stop(lastKeyPress); - } - lastKeyPress = e.localPosition.dx.toInt() ~/ noteKeyWidth; - play(lastKeyPress); - }, - onPanCancel: () { - stop(lastKeyPress); - lastKeyPress = -1; - }, - onPanEnd: (e) { - stop(lastKeyPress); - lastKeyPress = -1; - }, - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - for (int i = 0; i < 12; i++) - ValueListenableBuilder( - valueListenable: isPressed[i], - builder: (_, pressed, __) { - return Container( - width: noteKeyWidth, - height: 120, - decoration: BoxDecoration( - color: pressed ? Colors.grey : Colors.white, - border: Border.all( - width: 2, - ), - ), - alignment: Alignment.bottomCenter, - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - notesKeys[i].toLowerCase(), - style: const TextStyle( - color: Colors.black, - fontWeight: FontWeight.w900, - ), - ), - Text( - notesText[i], - style: const TextStyle( - color: Colors.black, - fontWeight: FontWeight.w900, - ), - ), - ], - ), - ); - }, - ), - ], - ), - ), - ); - } -} diff --git a/example/lib/ui/knobs_groups.dart b/example/lib/ui/knobs_groups.dart deleted file mode 100644 index 07a9cabf..00000000 --- a/example/lib/ui/knobs_groups.dart +++ /dev/null @@ -1,58 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_soloud_example/ui/touch_slider.dart'; - -class KnobsGroup extends StatefulWidget { - const KnobsGroup({ - required this.texts, - required this.values, - required this.mins, - required this.maxs, - required this.onChanges, - super.key, - }) : assert( - texts.length == values.length && - texts.length == mins.length && - texts.length == maxs.length && - texts.length == onChanges.length, - 'Sizes mismatch!', - ); - - final List texts; - final List values; - final List mins; - final List maxs; - final List onChanges; - - @override - State createState() => _KnobsGroupState(); -} - -class _KnobsGroupState extends State { - late List values; - @override - void initState() { - super.initState(); - values = widget.values; - } - - @override - Widget build(BuildContext context) { - return Row( - mainAxisAlignment: MainAxisAlignment.center, - children: List.generate(widget.texts.length, (i) { - return TouchSlider( - text: widget.texts[i], - diameter: 80, - min: widget.mins[i], - max: widget.maxs[i], - value: values[i], - onChanged: (value) { - widget.onChanges[i](value); - values[i] = value; - if (mounted) setState(() {}); - }, - ); - }), - ); - } -} diff --git a/example/lib/ui/text_slider.dart b/example/lib/ui/text_slider.dart deleted file mode 100644 index 2064a752..00000000 --- a/example/lib/ui/text_slider.dart +++ /dev/null @@ -1,44 +0,0 @@ -import 'package:flutter/material.dart'; - -/// Custom slider with text -/// -class TextSlider extends StatelessWidget { - const TextSlider({ - required this.text, - required this.min, - required this.max, - required this.value, - required this.enabled, - required this.onChanged, - this.isDivided = false, - super.key, - }); - - final String text; - final double min; - final double max; - final double value; - final bool enabled; - final bool isDivided; - final void Function(double) onChanged; - - @override - Widget build(BuildContext context) { - return Row( - children: [ - Text( - '$text: ${isDivided ? value.toInt() : value.toStringAsFixed(2)}\t', - ), - Expanded( - child: Slider.adaptive( - value: value, - min: min, - max: max, - divisions: isDivided ? 4 : null, - onChanged: enabled ? onChanged : null, - ), - ), - ], - ); - } -} diff --git a/example/lib/ui/touch_slider.dart b/example/lib/ui/touch_slider.dart deleted file mode 100644 index 53221979..00000000 --- a/example/lib/ui/touch_slider.dart +++ /dev/null @@ -1,78 +0,0 @@ -import 'package:flutter/material.dart'; - -class TouchSlider extends StatelessWidget { - const TouchSlider({ - required this.diameter, - required this.value, - required this.min, - required this.max, - required this.onChanged, - this.text = '', - super.key, - }); - - final String text; - final double diameter; - final double value; - final double min; - final double max; - final void Function(double newValue) onChanged; - - @override - Widget build(BuildContext context) { - final newValue = ValueNotifier(value); - final step = max == double.maxFinite ? 0.1 : (max - min) / (diameter * 3); - newValue.value = value; - return GestureDetector( - onPanUpdate: (event) { - newValue.value += event.delta.dx * step; - if (newValue.value > max) newValue.value = max; - if (newValue.value < min) newValue.value = min; - onChanged(newValue.value); - }, - child: ValueListenableBuilder( - valueListenable: newValue, - builder: (_, val, __) { - return Container( - width: diameter, - height: diameter, - decoration: BoxDecoration( - color: Colors.white, - border: Border.all(width: 3), - borderRadius: BorderRadius.circular(diameter / 2), - ), - child: FittedBox( - child: SizedBox( - width: 130, - height: 130, - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text( - text, - textAlign: TextAlign.center, - style: const TextStyle( - fontWeight: FontWeight.bold, - fontSize: 18, - color: Colors.black, - ), - ), - const Text('<-->', style: TextStyle(color: Colors.black)), - const SizedBox(height: 4), - Text( - val.toStringAsFixed(3), - style: const TextStyle( - fontWeight: FontWeight.bold, - color: Colors.black, - ), - ), - ], - ), - ), - ), - ); - }, - ), - ); - } -} diff --git a/example/lib/visualizer/audio_shader.dart b/example/lib/visualizer/audio_shader.dart deleted file mode 100644 index 0aa2368c..00000000 --- a/example/lib/visualizer/audio_shader.dart +++ /dev/null @@ -1,76 +0,0 @@ -import 'dart:ui' as ui; - -import 'package:flutter/material.dart'; - -/// Draw the the shader using the built image as a parameter -/// -class AudioShader extends StatelessWidget { - const AudioShader({ - required this.image, - required this.shader, - required this.iTime, - super.key, - this.width, - this.height, - }); - - final double? width; - final double? height; - final ui.Image image; - final ui.FragmentShader shader; - final double iTime; - - @override - Widget build(BuildContext context) { - return Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - width: width, - height: height, - child: CustomPaint( - // size: Size(width, height), - painter: ShaderPainter( - shader, - image, - iTime, - ), - ), - ), - ], - ); - } -} - -/// -class ShaderPainter extends CustomPainter { - ShaderPainter( - this.shader, - this.image, - this.iTime, - ); - - final ui.Image image; - final ui.FragmentShader shader; - final double iTime; - - @override - void paint(Canvas canvas, ui.Size size) { - shader - ..setImageSampler(0, image) - ..setFloat(0, size.width) - ..setFloat(1, size.height) - ..setFloat(2, iTime); - - canvas.drawRect( - Rect.fromLTWH(0, 0, size.width, size.height), - Paint()..shader = shader, - ); - } - - @override - bool shouldRepaint(covariant ShaderPainter oldDelegate) { - return oldDelegate.iTime != iTime; - } -} diff --git a/example/lib/visualizer/bars_fft_widget.dart b/example/lib/visualizer/bars_fft_widget.dart deleted file mode 100644 index 85920af1..00000000 --- a/example/lib/visualizer/bars_fft_widget.dart +++ /dev/null @@ -1,102 +0,0 @@ -// ignore_for_file: public_member_api_docs -import 'package:flutter/material.dart'; - -import 'package:flutter_soloud/flutter_soloud.dart'; - -/// Draw the audio FFT data -/// -class BarsFftWidget extends StatelessWidget { - const BarsFftWidget({ - required this.audioData, - required this.minFreq, - required this.maxFreq, - required this.width, - required this.height, - super.key, - }); - - final AudioData audioData; - final int minFreq; - final int maxFreq; - final double width; - final double height; - - @override - Widget build(BuildContext context) { - if (audioData.getSamplesKind == GetSamplesKind.wave) { - return const Placeholder(); - } - - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ColoredBox( - color: Colors.black, - child: RepaintBoundary( - child: ClipRRect( - child: CustomPaint( - size: Size(width, height), - painter: FftPainter( - audioData: audioData, - minFreq: minFreq, - maxFreq: maxFreq, - ), - ), - ), - ), - ), - ], - ); - } -} - -/// Custom painter to draw the wave in a circle -/// -class FftPainter extends CustomPainter { - const FftPainter({ - required this.audioData, - required this.minFreq, - required this.maxFreq, - }); - final AudioData audioData; - final int minFreq; - final int maxFreq; - - @override - void paint(Canvas canvas, Size size) { - final barWidth = size.width / (maxFreq - minFreq).clamp(0, 255); - final paint = Paint() - ..color = Colors.yellow - ..strokeWidth = barWidth * 0.8 - ..style = PaintingStyle.stroke; - - for (var i = minFreq; i <= maxFreq.clamp(0, 255); i++) { - late final double barHeight; - try { - final double data; - if (audioData.getSamplesKind == GetSamplesKind.linear) { - data = audioData.getLinearFft(SampleLinear(i)); - } else { - data = audioData.getTexture(SampleRow(0), SampleColumn(i)); - } - barHeight = size.height * data; - } on Exception { - barHeight = 0; - } - canvas.drawRect( - Rect.fromLTWH( - barWidth * (i - minFreq), - size.height - barHeight, - barWidth, - barHeight, - ), - paint, - ); - } - } - - @override - bool shouldRepaint(covariant CustomPainter oldDelegate) { - return true; - } -} diff --git a/example/lib/visualizer/bars_wave_widget.dart b/example/lib/visualizer/bars_wave_widget.dart deleted file mode 100644 index 14250c17..00000000 --- a/example/lib/visualizer/bars_wave_widget.dart +++ /dev/null @@ -1,86 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_soloud/flutter_soloud.dart'; - -/// Draw the audio wave data -/// -class BarsWaveWidget extends StatelessWidget { - const BarsWaveWidget({ - required this.audioData, - required this.width, - required this.height, - super.key, - }); - - final AudioData audioData; - final double width; - final double height; - - @override - Widget build(BuildContext context) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ColoredBox( - color: Colors.black, - child: RepaintBoundary( - child: ClipRRect( - child: CustomPaint( - size: Size(width, height), - painter: WavePainter(audioData: audioData), - ), - ), - ), - ), - ], - ); - } -} - -/// Custom painter to draw the wave in a circle -/// -class WavePainter extends CustomPainter { - const WavePainter({ - required this.audioData, - }); - final AudioData audioData; - - @override - void paint(Canvas canvas, Size size) { - final barWidth = size.width / 256; - final paint = Paint() - ..color = Colors.yellow - ..strokeWidth = barWidth * 0.8 - ..style = PaintingStyle.stroke; - - for (var i = 0; i < 256; i++) { - late final double barHeight; - try { - final double data; - if (audioData.getSamplesKind == GetSamplesKind.wave) { - data = audioData.getWave(SampleWave(i)); - } else if (audioData.getSamplesKind == GetSamplesKind.linear) { - data = audioData.getLinearWave(SampleLinear(i)); - } else { - data = audioData.getTexture(SampleRow(0), SampleColumn(i + 256)); - } - barHeight = size.height * data; - } on Exception { - barHeight = 0; - } - canvas.drawRect( - Rect.fromLTWH( - barWidth * i, - (size.height - barHeight) / 2, - barWidth, - barHeight, - ), - paint, - ); - } - } - - @override - bool shouldRepaint(covariant CustomPainter oldDelegate) { - return true; - } -} diff --git a/example/lib/visualizer/bmp_header.dart b/example/lib/visualizer/bmp_header.dart deleted file mode 100644 index d3e708c8..00000000 --- a/example/lib/visualizer/bmp_header.dart +++ /dev/null @@ -1,77 +0,0 @@ -// ignore_for_file: sort_constructors_first - -import 'dart:typed_data'; - -/// Class to construct an uncompressed 32bit BMP image from raw data -class Bmp32Header { - late int width; - late int height; - late Uint8List bmp; - late int contentSize; - int rgba32HeaderSize = 122; - int bytesPerPixel = 4; - - /// set a BMP from bytes - Bmp32Header.setBmp(Uint8List imgBytes) { - final bd = imgBytes.buffer.asByteData(); - width = bd.getInt32(0x12, Endian.little); - height = -bd.getInt32(0x16, Endian.little); - contentSize = bd.getInt32(0x02, Endian.little) - rgba32HeaderSize; - bmp = imgBytes; - } - - /// set BMP header and memory to use - Bmp32Header.setHeader(this.width, this.height) { - contentSize = width * height; - bmp = Uint8List(rgba32HeaderSize + contentSize * bytesPerPixel); - - bmp.buffer.asByteData() - ..setUint8(0x00, 0x42) // 'B' - ..setUint8(0x01, 0x4d) // 'M' - - ..setInt32(0x02, rgba32HeaderSize + contentSize, Endian.little) - ..setInt32(0x0A, rgba32HeaderSize, Endian.little) - ..setUint32(0x0E, 108, Endian.little) - ..setUint32(0x12, width, Endian.little) - ..setUint32(0x16, -height, Endian.little) - ..setUint16(0x1A, 1, Endian.little) - ..setUint8(0x1C, 32) - ..setUint32(0x1E, 3, Endian.little) - ..setUint32(0x22, contentSize, Endian.little) - ..setUint32(0x36, 0x000000ff, Endian.little) - ..setUint32(0x3A, 0x0000ff00, Endian.little) - ..setUint32(0x3E, 0x00ff0000, Endian.little) - ..setUint32(0x42, 0xff000000, Endian.little); - } - - /// Insert the [bitmap] after the header and return the BMP - Uint8List storeBitmap(Uint8List bitmap) { - bmp.setRange(rgba32HeaderSize, bmp.length, bitmap); - return bmp; - } - - /// clear BMP pixels leaving the header untouched - Uint8List clearBitmap() { - bmp.fillRange(rgba32HeaderSize, bmp.length, 0); - return bmp; - } - - /// set BMP pixels color - Uint8List setBitmapBackgroundColor(int r, int g, int b, int a) { - final value = (((r & 0xff) << 0) | - ((g & 0xff) << 8) | - ((b & 0xff) << 16) | - ((a & 0xff) << 24)) & - 0xFFFFFFFF; - final tmp = bmp.sublist(rgba32HeaderSize).buffer.asUint32List(); - tmp.fillRange(0, tmp.length, value); - - final bytes = BytesBuilder() - ..add(bmp.sublist(0, rgba32HeaderSize)) - ..add(tmp.buffer.asUint8List()); - // ignore: join_return_with_assignment - bmp = bytes.toBytes(); - - return bmp; - } -} diff --git a/example/lib/visualizer/paint_texture.dart b/example/lib/visualizer/paint_texture.dart deleted file mode 100644 index 9e891bc6..00000000 --- a/example/lib/visualizer/paint_texture.dart +++ /dev/null @@ -1,63 +0,0 @@ -import 'dart:ui' as ui; - -import 'package:flutter/material.dart'; - -/// Draw the image painted using audio data -/// -class PaintTexture extends StatelessWidget { - const PaintTexture({ - required this.width, - required this.height, - required this.image, - super.key, - }); - - final double width; - final double height; - final ui.Image image; - - @override - Widget build(BuildContext context) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - RepaintBoundary( - child: CustomPaint( - size: Size(width, height), - painter: DisplayUiImagePainter( - image, - ), - ), - ), - ], - ); - } -} - -/// CustomPainter to draw the ui.Image -/// -class DisplayUiImagePainter extends CustomPainter { - DisplayUiImagePainter( - this.image, - ); - - final ui.Image image; - - @override - void paint(Canvas canvas, ui.Size size) { - // canvas.drawImage(image, Offset.zero, Paint()); - paintImage( - canvas: canvas, - rect: Rect.fromLTRB(0, 0, size.width, size.height), - image: image, - fit: BoxFit.fill, - filterQuality: FilterQuality.none, - ); - } - - @override - bool shouldRepaint(covariant DisplayUiImagePainter oldDelegate) { - // return oldDelegate.shader != shader; - return true; - } -} diff --git a/example/lib/visualizer/visualizer.dart b/example/lib/visualizer/visualizer.dart deleted file mode 100644 index bf3f9b17..00000000 --- a/example/lib/visualizer/visualizer.dart +++ /dev/null @@ -1,557 +0,0 @@ -// ignore_for_file: avoid_positional_boolean_parameters - -import 'dart:async'; -import 'dart:ui' as ui; - -import 'package:flutter/foundation.dart'; -import 'package:flutter/material.dart'; -import 'package:flutter/scheduler.dart'; -import 'package:flutter_soloud/flutter_soloud.dart'; -import 'package:flutter_soloud_example/visualizer/audio_shader.dart'; -import 'package:flutter_soloud_example/visualizer/bars_fft_widget.dart'; -import 'package:flutter_soloud_example/visualizer/bars_wave_widget.dart'; -import 'package:flutter_soloud_example/visualizer/bmp_header.dart'; -import 'package:flutter_soloud_example/visualizer/paint_texture.dart'; - -class VisualizerController extends ChangeNotifier { - VisualizerController({ - this.isVisualizerEnabled = true, - this.isVisualizerForPlayer = true, - this.samplesKind = GetSamplesKind.texture, - }) : maxRangeLimit = samplesKind == GetSamplesKind.texture ? 511 : 255, - maxRange = samplesKind == GetSamplesKind.texture ? 511 : 255, - minRange = 0 { - audioData = AudioData(samplesKind); - } - - int maxRangeLimit; - int minRange; - int maxRange; - bool isVisualizerEnabled; - bool isVisualizerForPlayer; - GetSamplesKind samplesKind; - late AudioData audioData; - - void changeMin(int min, {bool notify = true}) { - minRange = min.clamp(0, maxRange); - if (notify) { - notifyListeners(); - } - } - - void changeMax(int max, {bool notify = true}) { - final nMax = samplesKind == GetSamplesKind.texture ? 511 : 255; - maxRange = max.clamp(minRange, nMax); - if (notify) { - notifyListeners(); - } - } - - void changeIsVisualizerForPlayer(bool isForPlayer) { - isVisualizerForPlayer = isForPlayer; - audioData.dispose(); - audioData = AudioData(samplesKind); - notifyListeners(); - } - - void changeIsVisualizerEnabled(bool enable) { - isVisualizerEnabled = enable; - notifyListeners(); - SoLoud.instance.setVisualizationEnabled(enable); - } - - void changeSamplesKind(GetSamplesKind kind) { - samplesKind = kind; - switch (kind) { - case GetSamplesKind.linear: - changeMin(0, notify: false); - changeMax(255, notify: false); - maxRangeLimit = 255; - case GetSamplesKind.texture: - changeMin(0, notify: false); - changeMax(511, notify: false); - maxRangeLimit = 511; - case GetSamplesKind.wave: - changeMin(0, notify: false); - changeMax(255, notify: false); - maxRangeLimit = 255; - } - audioData.changeSamplesKind(samplesKind); - notifyListeners(); - } -} - -class Visualizer extends StatefulWidget { - const Visualizer({ - required this.controller, - required this.shader, - super.key, - }); - - final VisualizerController controller; - final String shader; - - @override - State createState() => _VisualizerState(); -} - -class _VisualizerState extends State with TickerProviderStateMixin { - late Ticker ticker; - late Stopwatch sw; - late Bmp32Header image; - late int bitmapRange; - late Future Function() buildImageCallback; - late int Function(SampleRow row, SampleColumn col) textureTypeCallback; - int nFrames = 0; - - @override - void initState() { - super.initState(); - - ticker = createTicker(_tick); - sw = Stopwatch(); - sw.start(); - setupBitmapSize(); - ticker.start(); - - widget.controller.addListener(() { - ticker - ..stop() - ..dispose(); - ticker = createTicker(_tick); - setupBitmapSize(); - ticker.start(); - sw.reset(); - nFrames = 0; - }); - } - - @override - void dispose() { - ticker - ..stop() - ..dispose(); - sw.stop(); - super.dispose(); - } - - void _tick(Duration elapsed) { - nFrames++; - if (context.mounted) { - try { - widget.controller.audioData.updateSamples(); - setState(() {}); - } on Exception catch (e) { - debugPrint('$e'); - } - } - } - - // @override - // Widget build(BuildContext context) { - // return Row( - // children: [ - // Column( - // children: [ - // const Text( - // 'FFT data', - // style: TextStyle(fontWeight: FontWeight.bold), - // ), - - // /// FFT bars - // BarsFftWidget( - // audioData: widget.controller.audioData, - // minFreq: widget.controller.minRange, - // maxFreq: widget.controller.maxRange, - // width: 250, - // height: 120, - // ), - // ], - // ), - // const SizedBox(width: 6), - // Column( - // children: [ - // const Text( - // '256 wave data', - // style: TextStyle(fontWeight: FontWeight.bold), - // ), - - // /// wave data bars - // BarsWaveWidget( - // audioData: widget.controller.audioData, - // width: 250, - // height: 120, - // ), - // ], - // ), - // ], - // ); - // } - - @override - Widget build(BuildContext context) { - return FutureBuilder( - future: buildImageCallback(), - builder: (context, dataTexture) { - final fps = nFrames.toDouble() / (sw.elapsedMilliseconds / 1000.0); - if (!dataTexture.hasData || dataTexture.data == null) { - return const Placeholder( - color: Colors.red, - strokeWidth: 0.5, - child: Text("\n can't get audio samples \n"), - ); - } - - return LayoutBuilder( - builder: (context, constraints) { - return Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - 'FPS: ${fps.toStringAsFixed(1)} ' - 'the texture sent to the shader', - style: const TextStyle(fontWeight: FontWeight.bold), - ), - - /// paint texture passed to the shader - DisableButton( - width: constraints.maxWidth, - height: constraints.maxWidth / 6, - onPressed: () { - sw.reset(); - nFrames = 0; - }, - child: PaintTexture( - width: constraints.maxWidth, - height: constraints.maxWidth / 6, - image: dataTexture.data!, - ), - ), - - const Text( - 'SHADER', - style: TextStyle(fontWeight: FontWeight.bold), - ), - DisableButton( - width: constraints.maxWidth, - height: constraints.maxWidth / 2.4, - onPressed: () { - sw.reset(); - nFrames = 0; - }, - child: FutureBuilder( - future: loadShader(), - builder: (context, snapshot) { - if (snapshot.hasData) { - return AudioShader( - width: constraints.maxWidth, - height: constraints.maxWidth / 2.4, - image: dataTexture.data!, - shader: snapshot.data!, - iTime: sw.elapsedMilliseconds / 1000.0, - ); - } else { - if (snapshot.data == null) { - return const Placeholder( - child: Align( - child: Text('Error compiling shader.\nSee log'), - ), - ); - } - return const CircularProgressIndicator(); - } - }, - ), - ), - - Row( - children: [ - Column( - children: [ - const Text( - 'FFT data', - style: TextStyle(fontWeight: FontWeight.bold), - ), - - /// FFT bars - DisableButton( - width: constraints.maxWidth / 2 - 3, - height: constraints.maxWidth / 6, - onPressed: () { - sw.reset(); - nFrames = 0; - }, - child: BarsFftWidget( - audioData: widget.controller.audioData, - minFreq: widget.controller.minRange, - maxFreq: widget.controller.maxRange, - width: constraints.maxWidth / 2 - 3, - height: constraints.maxWidth / 6, - ), - ), - ], - ), - const SizedBox(width: 6), - Column( - children: [ - const Text( - '256 wave data', - style: TextStyle(fontWeight: FontWeight.bold), - ), - - /// wave data bars - DisableButton( - width: constraints.maxWidth / 2 - 3, - height: constraints.maxWidth / 6, - onPressed: () { - sw.reset(); - nFrames = 0; - }, - child: BarsWaveWidget( - audioData: widget.controller.audioData, - width: constraints.maxWidth / 2 - 3, - height: constraints.maxWidth / 6, - ), - ), - ], - ), - ], - ), - ], - ); - }, - ); - }, - ); - } - - /// load asynchronously the fragment shader - Future loadShader() async { - try { - final program = await ui.FragmentProgram.fromAsset(widget.shader); - return program.fragmentShader(); - } catch (e) { - debugPrint('error compiling the shader $e'); - } - return null; - } - - void setupBitmapSize() { - bitmapRange = widget.controller.maxRange - widget.controller.minRange + 1; - - switch (widget.controller.samplesKind) { - case GetSamplesKind.wave: - { - image = Bmp32Header.setHeader(bitmapRange, 1); - buildImageCallback = buildImageForWave; - break; - } - case GetSamplesKind.linear: - { - image = Bmp32Header.setHeader(bitmapRange, 2); - buildImageCallback = buildImageForLinear; - break; - } - case GetSamplesKind.texture: - { - image = Bmp32Header.setHeader(bitmapRange, 256); - buildImageCallback = buildImageForTexture; - break; - } - } - } - - /// Build an image to be passed to the shader. - /// The image is a matrix of 256x1 RGBA pixels representing the wave data. - Future buildImageForWave() async { - if (!context.mounted) { - return null; - } - if (!(widget.controller.isVisualizerEnabled && - SoLoud.instance.getVoiceCount() > 0)) { - return null; - } - - final completer = Completer(); - final bytes = Uint8List(bitmapRange * 4); - // Fill the texture bitmap - var col = 0; - for (var i = widget.controller.minRange; - i <= widget.controller.maxRange; - ++i, ++col) { - // fill bitmap row with wave data - final z = getWave(SampleWave(i)); - bytes[col * 4 + 0] = z; - bytes[col * 4 + 1] = 0; - bytes[col * 4 + 2] = 0; - bytes[col * 4 + 3] = 255; - } - - final img = image.storeBitmap(bytes); - ui.decodeImageFromList(img, completer.complete); - - return completer.future; - } - - /// Build an image to be passed to the shader. - /// The image is a matrix of 256x2 RGBA pixels representing: - /// in the 1st row the frequencies data - /// in the 2nd row the wave data - Future buildImageForLinear() async { - if (!context.mounted) { - return null; - } - if (!(widget.controller.isVisualizerEnabled && - SoLoud.instance.getVoiceCount() > 0)) { - return null; - } - - final completer = Completer(); - final bytes = Uint8List(bitmapRange * 4 * 2); - var col = 0; - // Fill the texture bitmap - for (var i = widget.controller.minRange; - i <= widget.controller.maxRange; - ++i, ++col) { - // fill 1st bitmap row with FFT magnitude - bytes[col * 4 + 0] = getLinearFft(SampleLinear(i)); - bytes[col * 4 + 1] = 0; - bytes[col * 4 + 2] = 0; - bytes[col * 4 + 3] = 255; - // fill 2nd bitmap row with wave amplitudes - bytes[col * 4 + 256 * 4 + 0] = getLinearWave(SampleLinear(i)); - bytes[col * 4 + 256 * 4 + 1] = 0; - bytes[col * 4 + 256 * 4 + 2] = 0; - bytes[col * 4 + 256 * 4 + 3] = 255; - } - - final img = image.storeBitmap(bytes); - ui.decodeImageFromList(img, completer.complete); - - return completer.future; - } - - /// Build an image to be passed to the shader. - /// The image is a matrix of 256x256 RGBA pixels representing - /// rows of wave data or frequencies data. - Future buildImageForTexture() async { - if (!context.mounted) { - return null; - } - if (!(widget.controller.isVisualizerEnabled && - SoLoud.instance.getVoiceCount() > 0)) { - return null; - } - - final width = widget.controller.maxRange - widget.controller.minRange; - - /// On the web there are worst performance getting data because for every - /// single data a JS function must be called. - /// Setting here an height of 100 instead of 256 to improve. - const height = kIsWeb ? 100 : 256; - - final completer = Completer(); - final bytes = Uint8List(width * height * 4); - - // Fill the texture bitmap with wave data - var row = 0; - for (var y = 0; y < height; ++y, ++row) { - var col = 0; - for (var x = 0; x < width; ++x, ++col) { - final z = getTexture(SampleRow(y), SampleColumn(x)); - bytes[row * width * 4 + col * 4 + 0] = z; - bytes[row * width * 4 + col * 4 + 1] = 0; - bytes[row * width * 4 + col * 4 + 2] = 0; - bytes[row * width * 4 + col * 4 + 3] = 255; - } - } - - image = Bmp32Header.setHeader(width, height); - final img = image.storeBitmap(bytes); - ui.decodeImageFromList(img, completer.complete); - // final ui.Codec codec = await ui.instantiateImageCodec(img); - // final ui.FrameInfo frameInfo = await codec.getNextFrame(); - // completer.complete(frameInfo.image); - - return completer.future; - } - - int getWave(SampleWave offset) { - final n = widget.controller.audioData.getWave(offset); - return (((n + 1.0) / 2.0).clamp(0, 1) * 128).toInt(); - } - - int getLinearFft(SampleLinear offset) { - return (widget.controller.audioData.getLinearFft(offset).clamp(0, 1) * 255) - .toInt(); - } - - int getLinearWave(SampleLinear offset) { - final n = widget.controller.audioData.getLinearWave(offset).abs(); - return (((n + 1.0) / 2.0).clamp(0, 1) * 128).toInt(); - } - - int getTexture(SampleRow row, SampleColumn col) { - final n = widget.controller.audioData.getTexture(row, col); - - /// With col<256 we are asking for FFT values. - if (col.value < 256) return (n.clamp(0, 1) * 255).toInt(); - - /// With col>256 we are asking for wave values. - return (((n + 1.0) / 2.0).clamp(0, 1) * 128).toInt(); - } -} - -class DisableButton extends StatefulWidget { - const DisableButton({ - required this.width, - required this.height, - required this.child, - required this.onPressed, - super.key, - }); - - final VoidCallback onPressed; - final double width; - final double height; - final Widget child; - - @override - State createState() => _DisableButtonState(); -} - -class _DisableButtonState extends State { - late bool isChildVisible; - - @override - void initState() { - super.initState(); - isChildVisible = true; - } - - @override - Widget build(BuildContext context) { - return SizedBox( - width: widget.width, - height: widget.height, - child: Stack( - children: [ - if (isChildVisible) widget.child else const Placeholder(), - Align( - alignment: Alignment.topRight, - child: FloatingActionButton.small( - onPressed: () { - isChildVisible = !isChildVisible; - setState(() {}); - widget.onPressed(); - }, - backgroundColor: Colors.blue, - child: const Icon(Icons.close, color: Colors.white), - ), - ), - ], - ), - ); - } -} diff --git a/example/macos/Podfile.lock b/example/macos/Podfile.lock index 6dc0311a..ade9727f 100644 --- a/example/macos/Podfile.lock +++ b/example/macos/Podfile.lock @@ -20,10 +20,10 @@ EXTERNAL SOURCES: :path: Flutter/ephemeral/.symlinks/plugins/path_provider_foundation/darwin SPEC CHECKSUMS: - flutter_soloud: 83719ca1f200cf5f537de0740f4ef97fa36e9bb2 + flutter_soloud: 401bea07fc43636ab5c6777f7adf691403d0ea48 FlutterMacOS: 8f6f14fa908a6fb3fba0cd85dbd81ec4b251fb24 - path_provider_foundation: 3784922295ac71e43754bd15e0653ccfd36a147c + path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 PODFILE CHECKSUM: 236401fc2c932af29a9fcf0e97baeeb2d750d367 -COCOAPODS: 1.15.0 +COCOAPODS: 1.15.2 diff --git a/example/macos/Runner/AppDelegate.swift b/example/macos/Runner/AppDelegate.swift index d53ef643..8e02df28 100755 --- a/example/macos/Runner/AppDelegate.swift +++ b/example/macos/Runner/AppDelegate.swift @@ -1,7 +1,7 @@ import Cocoa import FlutterMacOS -@NSApplicationMain +@main class AppDelegate: FlutterAppDelegate { override func applicationShouldTerminateAfterLastWindowClosed(_ sender: NSApplication) -> Bool { return true diff --git a/example/pubspec.lock b/example/pubspec.lock index a86bf3af..bc106123 100644 --- a/example/pubspec.lock +++ b/example/pubspec.lock @@ -41,6 +41,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.18.0" + cross_file: + dependency: transitive + description: + name: cross_file + sha256: "7caf6a750a0c04effbb52a676dce9a4a592e10ad35c34d6d2d0e4811160d5670" + url: "https://pub.dev" + source: hosted + version: "0.3.4+2" cupertino_icons: dependency: "direct main" description: @@ -69,10 +77,10 @@ packages: dependency: "direct main" description: name: file_picker - sha256: be325344c1f3070354a1d84a231a1ba75ea85d413774ec4bdf444c023342e030 + sha256: "167bb619cdddaa10ef2907609feb8a79c16dfa479d3afaf960f8e223f754bf12" url: "https://pub.dev" source: hosted - version: "5.5.0" + version: "8.1.2" flutter: dependency: "direct main" description: flutter @@ -107,10 +115,10 @@ packages: dependency: transitive description: name: http - sha256: "761a297c042deedc1ffbb156d6e2af13886bb305c2a343a4d972504cd67dd938" + sha256: b9c29a161230ee03d3ccf545097fccd9b87a5264228c5d348202e0f0c28f9010 url: "https://pub.dev" source: hosted - version: "1.2.1" + version: "1.2.2" http_parser: dependency: transitive description: @@ -123,18 +131,18 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "7f0df31977cb2c0b88585095d168e689669a2cc9b97c309665e3386f3e9d341a" + sha256: "3f87a60e8c63aecc975dda1ceedbc8f24de75f09e4856ea27daf8958f2f0ce05" url: "https://pub.dev" source: hosted - version: "10.0.4" + version: "10.0.5" leak_tracker_flutter_testing: dependency: transitive description: name: leak_tracker_flutter_testing - sha256: "06e98f569d004c1315b991ded39924b21af84cf14cc94791b8aea337d25b57f8" + sha256: "932549fb305594d82d7183ecd9fa93463e9914e1b67cacc34bc40906594a1806" url: "https://pub.dev" source: hosted - version: "3.0.3" + version: "3.0.5" leak_tracker_testing: dependency: transitive description: @@ -163,18 +171,18 @@ packages: dependency: transitive description: name: material_color_utilities - sha256: "0e0a020085b65b6083975e499759762399b4475f766c21668c4ecca34ea74e5a" + sha256: f7142bb1154231d7ea5f96bc7bde4bda2a0945d2806bb11670e30b850d56bdec url: "https://pub.dev" source: hosted - version: "0.8.0" + version: "0.11.1" meta: dependency: transitive description: name: meta - sha256: "7687075e408b093f36e6bbf6c91878cc0d4cd10f409506f7bc996f68220b9136" + sha256: bdb68674043280c3428e9ec998512fb681678676b3c54e773629ffe74419f8c7 url: "https://pub.dev" source: hosted - version: "1.12.0" + version: "1.15.0" path: dependency: transitive description: @@ -235,42 +243,50 @@ packages: dependency: "direct main" description: name: permission_handler - sha256: bc56bfe9d3f44c3c612d8d393bd9b174eb796d706759f9b495ac254e4294baa5 + sha256: "18bf33f7fefbd812f37e72091a15575e72d5318854877e0e4035a24ac1113ecb" url: "https://pub.dev" source: hosted - version: "10.4.5" + version: "11.3.1" permission_handler_android: dependency: transitive description: name: permission_handler_android - sha256: "59c6322171c29df93a22d150ad95f3aa19ed86542eaec409ab2691b8f35f9a47" + sha256: b29a799ca03be9f999aa6c39f7de5209482d638e6f857f6b93b0875c618b7e54 url: "https://pub.dev" source: hosted - version: "10.3.6" + version: "12.0.7" permission_handler_apple: dependency: transitive description: name: permission_handler_apple - sha256: "99e220bce3f8877c78e4ace901082fb29fa1b4ebde529ad0932d8d664b34f3f5" + sha256: e6f6d73b12438ef13e648c4ae56bd106ec60d17e90a59c4545db6781229082a0 + url: "https://pub.dev" + source: hosted + version: "9.4.5" + permission_handler_html: + dependency: transitive + description: + name: permission_handler_html + sha256: "54bf176b90f6eddd4ece307e2c06cf977fb3973719c35a93b85cc7093eb6070d" url: "https://pub.dev" source: hosted - version: "9.1.4" + version: "0.1.1" permission_handler_platform_interface: dependency: transitive description: name: permission_handler_platform_interface - sha256: "6760eb5ef34589224771010805bea6054ad28453906936f843a8cc4d3a55c4a4" + sha256: "48d4fcf201a1dad93ee869ab0d4101d084f49136ec82a8a06ed9cfeacab9fd20" url: "https://pub.dev" source: hosted - version: "3.12.0" + version: "4.2.1" permission_handler_windows: dependency: transitive description: name: permission_handler_windows - sha256: cc074aace208760f1eee6aa4fae766b45d947df85bc831cde77009cdb4720098 + sha256: "1a790728016f79a41216d88672dbc5df30e686e811ad4e698bfc51f76ad91f1e" url: "https://pub.dev" source: hosted - version: "0.1.3" + version: "0.2.1" platform: dependency: transitive description: @@ -312,10 +328,10 @@ packages: dependency: "direct main" description: name: star_menu - sha256: b3147a753f2db3f4830a4d6f63bf56c96cde8bd0ff54d3464934b7726e4efa3a + sha256: f29c7d255677c49ec2412ec2d17220d967f54b72b9e6afc5688fe122ea4d1d78 url: "https://pub.dev" source: hosted - version: "3.1.9" + version: "4.0.1" stream_channel: dependency: transitive description: @@ -344,10 +360,10 @@ packages: dependency: transitive description: name: test_api - sha256: "9955ae474176f7ac8ee4e989dadfb411a58c30415bcfb648fa04b2b8a03afa7f" + sha256: "5b8a98dafc4d5c4c9c72d8b31ab2b23fc13422348d2997120294d3bac86b4ddb" url: "https://pub.dev" source: hosted - version: "0.7.0" + version: "0.7.2" typed_data: dependency: transitive description: @@ -368,34 +384,34 @@ packages: dependency: "direct dev" description: name: very_good_analysis - sha256: "9ae7f3a3bd5764fb021b335ca28a34f040cd0ab6eec00a1b213b445dae58a4b8" + sha256: "1fb637c0022034b1f19ea2acb42a3603cbd8314a470646a59a2fb01f5f3a8629" url: "https://pub.dev" source: hosted - version: "5.1.0" + version: "6.0.0" vm_service: dependency: transitive description: name: vm_service - sha256: "3923c89304b715fb1eb6423f017651664a03bf5f4b29983627c4da791f74a4ec" + sha256: "5c5f338a667b4c644744b661f309fb8080bb94b18a7e91ef1dbd343bed00ed6d" url: "https://pub.dev" source: hosted - version: "14.2.1" + version: "14.2.5" web: dependency: transitive description: name: web - sha256: "97da13628db363c635202ad97068d47c5b8aa555808e7a9411963c533b449b27" + sha256: d43c1d6b787bf0afad444700ae7f4db8827f701bc61c255ac8d328c6f4d52062 url: "https://pub.dev" source: hosted - version: "0.5.1" + version: "1.0.0" win32: dependency: transitive description: name: win32 - sha256: "0eaf06e3446824099858367950a813472af675116bf63f008a4c2a75ae13e9cb" + sha256: "68d1e89a91ed61ad9c370f9f8b6effed9ae5e0ede22a270bdfa6daf79fc2290a" url: "https://pub.dev" source: hosted - version: "5.5.0" + version: "5.5.4" xdg_directories: dependency: transitive description: @@ -405,5 +421,5 @@ packages: source: hosted version: "1.0.4" sdks: - dart: ">=3.3.0 <4.0.0" - flutter: ">=3.18.0-18.0.pre.54" + dart: ">=3.4.0 <4.0.0" + flutter: ">=3.22.0" diff --git a/example/pubspec.yaml b/example/pubspec.yaml index e4801685..58462420 100755 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -10,13 +10,13 @@ environment: sdk: '>=3.0.0 <4.0.0' dependencies: - cupertino_icons: ^1.0.2 + cupertino_icons: ^1.0.8 #https://pub.dev/packages/ffi - ffi: ^2.0.2 + ffi: ^2.1.2 #https://pub.dev/packages/file_picker - file_picker: ^5.3.3 + file_picker: ^8.1.2 flutter: sdk: flutter @@ -25,37 +25,25 @@ dependencies: path: ../ #https://pub.dev/packages/logging - logging: ^1.0.0 + logging: ^1.2.0 #https://pub.dev/packages/path_provider - path_provider: ^2.0.15 + path_provider: ^2.1.3 #https://pub.dev/packages/permission_handler - permission_handler: ^10.4.3 + permission_handler: ^11.3.1 #https://pub.dev/packages/star_menu - star_menu: ^3.1.4 + star_menu: ^4.0.1 dev_dependencies: flutter_test: sdk: flutter - very_good_analysis: ^5.0.0+1 + very_good_analysis: ^6.0.0 flutter: uses-material-design: true assets: - assets/audio/ - - assets/audio/12Bands/ - - shaders: - - assets/shaders/test1.frag - - assets/shaders/test2.frag - - assets/shaders/test3.frag - - assets/shaders/test4.frag - - assets/shaders/test5.frag - - assets/shaders/test6.frag - - assets/shaders/test7.frag - - assets/shaders/test8.frag - - assets/shaders/test9.frag diff --git a/example/tests/tests.dart b/example/tests/tests.dart index c59f9a57..99f8c533 100644 --- a/example/tests/tests.dart +++ b/example/tests/tests.dart @@ -76,8 +76,8 @@ class _MyHomePageState extends State { _Test(name: 'testSynchronousDeinit', callback: testSynchronousDeinit), _Test(name: 'testAsynchronousDeinit', callback: testAsynchronousDeinit), _Test(name: 'testVoiceGroups', callback: testVoiceGroups), - _Test(name: 'testSoundFilters', callback: testSoundFilters), - _Test(name: 'testGlobalFilters', callback: testGlobalFilters), + // _Test(name: 'testSoundFilters', callback: testSoundFilters), + // _Test(name: 'testGlobalFilters', callback: testGlobalFilters), ]); } @@ -694,137 +694,137 @@ Future testVoiceGroups() async { return StringBuffer(); } -/// Test sound filters. -Future testSoundFilters() async { - final strBuf = StringBuffer(); - await initialize(); - - final sound = await SoLoud.instance.loadAsset( - 'assets/audio/8_bit_mentality.mp3', - // mode: LoadMode.disk, - ); - - /// Add filter to the sound. - sound.addFilter(FilterType.echoFilter); - - /// Set a handle filter. It must be set before it starts playing. - final h1 = await SoLoud.instance.play(sound); - - /// Use the `Wet` attribute index. - const attributeId = 0; - const value = 1.2; - sound.setFilterParameter( - h1, - FilterType.echoFilter, - attributeId, - value, - ); - final g = sound.getFilterParameter(h1, FilterType.echoFilter, attributeId); - assert( - closeTo(g, value, 0.001), - 'Setting attribute to $value but obtained $g', - ); - - sound.oscillateFilterParameter( - h1, - FilterType.echoFilter, - attributeId, - 0.01, - 2, - const Duration(seconds: 2), - ); - - assert( - sound.isFilterActive(FilterType.echoFilter) >= 0, - 'The filter is not active!', - ); - - await delay(6000); - - /// Remove the filter - try { - sound.removeFilter(FilterType.echoFilter); - } on Exception catch (e) { - strBuf - ..write(e) - ..writeln(); - } - assert( - sound.isFilterActive(FilterType.echoFilter) < 0, - 'The filter is still active after removing it!', - ); - - deinit(); - return strBuf; -} - -/// Test global filters. -Future testGlobalFilters() async { - final strBuf = StringBuffer(); - await initialize(); - - late final AudioSource sound; - try { - sound = await SoLoud.instance.loadAsset( - 'assets/audio/8_bit_mentality.mp3', - mode: LoadMode.disk, - ); - } on Exception catch (e) { - strBuf - ..write(e) - ..writeln(); - } - - /// Add filter to the sound. - SoLoud.instance.addGlobalFilter(FilterType.echoFilter); - - await SoLoud.instance.play(sound); - - /// Use the `Wet` attribute index. - const attributeId = 0; - const value = 1.2; - SoLoud.instance.setGlobalFilterParameter( - FilterType.echoFilter, - attributeId, - value, - ); - final g = SoLoud.instance.getGlobalFilterParameter( - FilterType.echoFilter, - attributeId, - ); - assert( - closeTo(g, value, 0.001), - 'Setting attribute to $value but optained $g', - ); - - SoLoud.instance.oscillateGlobalFilterParameter( - FilterType.echoFilter, - attributeId, - 0.01, - 2, - const Duration(seconds: 2), - ); - - assert( - SoLoud.instance.isFilterActive(FilterType.echoFilter) >= 0, - 'The filter is not active!', - ); - - await delay(6000); - - /// Remove the filter - try { - SoLoud.instance.removeGlobalFilter(FilterType.echoFilter); - } on Exception catch (e) { - strBuf - ..write(e) - ..writeln(); - } - assert( - SoLoud.instance.isFilterActive(FilterType.echoFilter) < 0, - 'The filter is still active after removing it!', - ); - - deinit(); - return strBuf; -} +// /// Test sound filters. +// Future testSoundFilters() async { +// final strBuf = StringBuffer(); +// await initialize(); + +// final sound = await SoLoud.instance.loadAsset( +// 'assets/audio/8_bit_mentality.mp3', +// // mode: LoadMode.disk, +// ); + +// /// Add filter to the sound. +// sound.addFilter(FilterType.echoFilter); + +// /// Set a handle filter. It must be set before it starts playing. +// final h1 = await SoLoud.instance.play(sound); + +// /// Use the `Wet` attribute index. +// const attributeId = 0; +// const value = 1.2; +// sound.setFilterParameter( +// h1, +// FilterType.echoFilter, +// attributeId, +// value, +// ); +// final g = sound.getFilterParameter(h1, FilterType.echoFilter, attributeId); +// assert( +// closeTo(g, value, 0.001), +// 'Setting attribute to $value but obtained $g', +// ); + +// sound.oscillateFilterParameter( +// h1, +// FilterType.echoFilter, +// attributeId, +// 0.01, +// 2, +// const Duration(seconds: 2), +// ); + +// assert( +// sound.isFilterActive(FilterType.echoFilter) >= 0, +// 'The filter is not active!', +// ); + +// await delay(6000); + +// /// Remove the filter +// try { +// sound.removeFilter(FilterType.echoFilter); +// } on Exception catch (e) { +// strBuf +// ..write(e) +// ..writeln(); +// } +// assert( +// sound.isFilterActive(FilterType.echoFilter) < 0, +// 'The filter is still active after removing it!', +// ); + +// deinit(); +// return strBuf; +// } + +// /// Test global filters. +// Future testGlobalFilters() async { +// final strBuf = StringBuffer(); +// await initialize(); + +// late final AudioSource sound; +// try { +// sound = await SoLoud.instance.loadAsset( +// 'assets/audio/8_bit_mentality.mp3', +// mode: LoadMode.disk, +// ); +// } on Exception catch (e) { +// strBuf +// ..write(e) +// ..writeln(); +// } + +// /// Add filter to the sound. +// SoLoud.instance.addGlobalFilter(FilterType.echoFilter); + +// await SoLoud.instance.play(sound); + +// /// Use the `Wet` attribute index. +// const attributeId = 0; +// const value = 1.2; +// SoLoud.instance.setGlobalFilterParameter( +// FilterType.echoFilter, +// attributeId, +// value, +// ); +// final g = SoLoud.instance.getGlobalFilterParameter( +// FilterType.echoFilter, +// attributeId, +// ); +// assert( +// closeTo(g, value, 0.001), +// 'Setting attribute to $value but optained $g', +// ); + +// SoLoud.instance.oscillateGlobalFilterParameter( +// FilterType.echoFilter, +// attributeId, +// 0.01, +// 2, +// const Duration(seconds: 2), +// ); + +// assert( +// SoLoud.instance.isFilterActive(FilterType.echoFilter) >= 0, +// 'The filter is not active!', +// ); + +// await delay(6000); + +// /// Remove the filter +// try { +// SoLoud.instance.removeGlobalFilter(FilterType.echoFilter); +// } on Exception catch (e) { +// strBuf +// ..write(e) +// ..writeln(); +// } +// assert( +// SoLoud.instance.isFilterActive(FilterType.echoFilter) < 0, +// 'The filter is still active after removing it!', +// ); + +// deinit(); +// return strBuf; +// } diff --git a/ios/flutter_soloud.podspec b/ios/flutter_soloud.podspec index 1beabb84..4600e3ca 100755 --- a/ios/flutter_soloud.podspec +++ b/ios/flutter_soloud.podspec @@ -38,7 +38,9 @@ Flutter audio plugin using SoLoud library and FFI ], 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited)', 'DEFINES_MODULE' => 'YES', - 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' + 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386', + "CLANG_CXX_LANGUAGE_STANDARD" => "c++17", + "CLANG_CXX_LIBRARY" => "libc++" } s.swift_version = '5.0' # spec.framework = 'SystemConfiguration' diff --git a/lib/fix_data.yaml b/lib/fix_data.yaml index 7a5dbad5..7a3b25ab 100644 --- a/lib/fix_data.yaml +++ b/lib/fix_data.yaml @@ -9,6 +9,28 @@ version: 1 transforms: + # SoLoud.getFilterParameter => SoLoud.getGlobalFilterParameter + - title: "Rename to 'getGlobalFilterParameter'" + date: 2024-04-03 + element: + uris: [ 'flutter_soloud.dart', 'package:flutter_soloud/flutter_soloud.dart' ] + method: 'getFilterParameter' + inClass: 'SoLoud' + changes: + - kind: 'rename' + newName: 'getGlobalFilterParameter' + + # SoLoud.setFilterParameter => SoLoud.setGlobalFilterParameter + - title: "Rename to 'setGlobalFilterParameter'" + date: 2024-04-03 + element: + uris: [ 'flutter_soloud.dart', 'package:flutter_soloud/flutter_soloud.dart' ] + method: 'setFilterParameter' + inClass: 'SoLoud' + changes: + - kind: 'rename' + newName: 'setGlobalFilterParameter' + # SoLoud.disposeAllSound => SoLoud.disposeAllSources - title: "Rename to 'disposeAllSources'" date: 2024-04-03 diff --git a/lib/flutter_soloud.dart b/lib/flutter_soloud.dart index 59d92a18..aaa966ee 100644 --- a/lib/flutter_soloud.dart +++ b/lib/flutter_soloud.dart @@ -7,6 +7,7 @@ export 'src/bindings/audio_data_extensions.dart'; export 'src/enums.dart' hide PlayerErrors, PlayerStateNotification; export 'src/exceptions/exceptions.dart'; export 'src/filter_params.dart'; +export 'src/filters/filters.dart' show FilterType; export 'src/soloud.dart'; export 'src/sound_handle.dart'; export 'src/sound_hash.dart'; diff --git a/lib/src/audio_source.dart b/lib/src/audio_source.dart index d2768159..d33f7176 100644 --- a/lib/src/audio_source.dart +++ b/lib/src/audio_source.dart @@ -1,15 +1,9 @@ import 'dart:async'; import 'dart:collection'; -import 'package:flutter_soloud/src/bindings/bindings_player.dart'; -import 'package:flutter_soloud/src/bindings/soloud_controller.dart'; -import 'package:flutter_soloud/src/enums.dart'; -import 'package:flutter_soloud/src/exceptions/exceptions.dart'; -import 'package:flutter_soloud/src/filter_params.dart'; -import 'package:flutter_soloud/src/soloud.dart'; +import 'package:flutter_soloud/src/filters/filters.dart'; import 'package:flutter_soloud/src/sound_handle.dart'; import 'package:flutter_soloud/src/sound_hash.dart'; -import 'package:logging/logging.dart'; import 'package:meta/meta.dart'; /// the type sent back to the user when a sound event occurs @@ -61,8 +55,6 @@ class AudioSource { @internal AudioSource(this.soundHash); - static final Logger _log = Logger('flutter_soloud.AudioSource'); - /// The hash uniquely identifying this loaded sound. final SoundHash soundHash; @@ -122,184 +114,62 @@ class AudioSource { Stream get allInstancesFinished => allInstancesFinishedController.stream; - // /////////////////////////////////////// - // / Filters for this [soundHash] - // /////////////////////////////////////// - - /// Checks whether the given [filterType] is active. - /// - /// Returns `-1` if the filter is not active. Otherwise, returns - /// the index of the given filter. - int isFilterActive(FilterType filterType) { - final ret = SoLoudController().soLoudFFI.isFilterActive( - filterType, - soundHash: soundHash, - ); - if (ret.error != PlayerErrors.noError) { - _log.severe(() => 'isFilterActive(): ${ret.error}'); - throw SoLoudCppException.fromPlayerError(ret.error); - } - return ret.index; - } - - /// Adds a [filterType] to this sound. - /// - /// Throws [SoLoudMaxFilterNumberReachedException] when the max number of - /// concurrent filter is reached (default max filter is 8). - /// Throws [SoLoudFilterAlreadyAddedException] when trying to add a filter - /// that has already been added. - PlayerErrors addFilter(FilterType filterType) { - final error = SoLoudController().soLoudFFI.addFilter( - filterType, - soundHash: soundHash, - ); - if (error != PlayerErrors.noError) { - _log.severe(() => 'addGlobalFilter(): $error'); - throw SoLoudCppException.fromPlayerError(error); - } - return error; - } - - /// Removes [filterType] from all sounds. - PlayerErrors removeFilter(FilterType filterType) { - final error = SoLoudController().soLoudFFI.removeFilter( - filterType, - soundHash: soundHash, - ); - if (error != PlayerErrors.noError) { - _log.severe(() => 'removeGlobalFilter(): $error'); - throw SoLoudCppException.fromPlayerError(error); - } - return error; - } - - /// Set the effect parameter with id [attributeId] of [filterType] - /// with [value] value. - /// - /// Specify the [attributeId] of the parameter (which you can learn from - /// [SoLoud.getFilterParamNames]), and its new [value]. - /// - /// [handle] the handle to set the filter to. - /// [filterType] filter to modify a param. - /// Returns [PlayerErrors.noError] if no errors. - PlayerErrors setFilterParameter( - SoundHandle handle, - FilterType filterType, - int attributeId, - double value, - ) { - final error = SoLoudController().soLoudFFI.setFilterParams( - filterType, - attributeId, - value, - handle: handle, - ); - if (error != PlayerErrors.noError) { - _log.severe(() => 'setFxParams(): $error'); - throw SoLoudCppException.fromPlayerError(error); - } - return error; - } - - /// Get the effect parameter value with id [attributeId] of [filterType]. + /// This can be used to access all the available filter functionalities + /// for this [AudioSource]. /// - /// Specify the [attributeId] of the parameter (which you can learn from - /// [SoLoud.getFilterParamNames]). + /// **IMPORTANT**: filters for individual sounds are not supported on the + /// web platform. + /// **IMPORTANT**: the filter must be added before playing. Only voice handles + /// played after adding a filter will play with the filter chosen: /// - /// [handle] the handle to get the attribute value from. If equal to 0, - /// it gets the global filter value. - /// [filterType] the filter to modify a parameter. - /// Returns the value of the parameter. - double getFilterParameter( - SoundHandle handle, - FilterType filterType, - int attributeId, - ) { - final ret = SoLoudController().soLoudFFI.getFilterParams( - filterType, - attributeId, - handle: handle, - ); - if (ret.error == PlayerErrors.filterNotFound) { - throw const SoLoudFilterNotFoundException(); - } - if (ret.error == PlayerErrors.soundHandleNotFound) { - throw const SoLoudSoundHandleNotFoundCppException(); - } - if (ret.error != PlayerErrors.noError) { - throw SoLoudCppException.fromPlayerError(ret.error); - } - return ret.value; - } - - /// Fade a parameter of a filter. + /// ```dart + /// AudioSource sound = await SoLoud.instance.loadAsset(...); + /// /// activate the filter. + /// sound.filters.pitchShiftFilter.activate(); + /// /// start playing it. + /// soundHandle = await SoLoud.instance.play(sound, looping: true); + /// /// deactivate it. + /// sound.filters.pitchShiftFilter.deactivate(); + /// ``` /// - /// [handle] the handle of the voice to apply the fade. If equal to 0, - /// it fades the global filter. - /// [filterType] filter to modify a param. - /// [attributeId] the attribute index to fade. - /// [to] value the attribute should go in [time] duration. - /// [time] the fade slope duration. + /// It's possible to get and set filter parameters: + /// ```dart + /// /// Set + /// sound.filters.pitchShiftFilter.wet(soundHandle: soundHandle).value = 0.6; + /// /// Get + /// final shiftValue = sound.filters.pitchShiftFilter.wet( + /// soundHandle: soundHandle, + /// ).value; + /// ``` + /// or fade/oscillate a parameter: + /// ```dart + /// /// Fade + /// sound.filters.pitchShiftFilter.shift(soundHandle: soundHandle) + /// .fadeFilterParameter( + /// to: 3, + /// time: const Duration(milliseconds: 2500), + /// ); + /// /// Oscillate + /// sound.filters.pitchShiftFilter.shift(soundHandle: soundHandle) + /// .oscillateFilterParameter( + /// from: 0.4, + /// to: 1.8, + /// time: const Duration(milliseconds: 2500), + /// ); + /// ``` /// - /// Throws [SoLoudNotInitializedException] if the engine is not initialized. - void fadeFilterParameter( - SoundHandle handle, - FilterType filterType, - int attributeId, - double to, - Duration time, - ) { - if (!SoLoud.instance.isInitialized) { - throw const SoLoudNotInitializedException(); - } - final error = SoLoudController().soLoudFFI.fadeFilterParameter( - filterType, - attributeId, - to, - time.toDouble(), - handle: handle, - ); - if (error != PlayerErrors.noError) { - _log.severe(() => 'fadeFilterParameter(): $error'); - throw SoLoudCppException.fromPlayerError(error); - } - } - - /// Oscillate a parameter of a filter. + /// It's possible to query filter parameters: + /// ```dart + /// final shiftParams = sound.filters.pitchShiftFilter.queryShift; + /// ``` /// - /// [handle] the handle of the voice to apply the fade. If equal to 0, - /// it fades the global filter. - /// [filterType] filter to modify a param. - /// [attributeId] the attribute index to fade. - /// [from] the starting value the attribute sould start to oscillate. - /// [to] the ending value the attribute sould end to oscillate. - /// [time] the fade slope duration. + /// Now with "shiftParams" you have access to: + /// - `toString()` gives the "human readable" parameter name. + /// - `min` which represent the "shift" minimum accepted value. + /// - `max` which represent the "shift" maximum accepted value. + /// - `def` which represent the "shift" default value. /// - /// Throws [SoLoudNotInitializedException] if the engine is not initialized. - void oscillateFilterParameter( - SoundHandle handle, - FilterType filterType, - int attributeId, - double from, - double to, - Duration time, - ) { - if (!SoLoud.instance.isInitialized) { - throw const SoLoudNotInitializedException(); - } - final error = SoLoudController().soLoudFFI.oscillateFilterParameter( - filterType, - attributeId, - from, - to, - time.toDouble(), - handle: handle, - ); - if (error != PlayerErrors.noError) { - _log.severe(() => 'oscillateFilterParameter(): $error'); - throw SoLoudCppException.fromPlayerError(error); - } - } + late final filters = FiltersSingle(soundHash: soundHash); @override String toString() { diff --git a/lib/src/bindings/bindings_player.dart b/lib/src/bindings/bindings_player.dart index 8a7867d0..4c1fe69f 100644 --- a/lib/src/bindings/bindings_player.dart +++ b/lib/src/bindings/bindings_player.dart @@ -3,7 +3,7 @@ import 'dart:typed_data'; import 'package:flutter_soloud/src/bindings/audio_data.dart'; import 'package:flutter_soloud/src/enums.dart'; -import 'package:flutter_soloud/src/filter_params.dart'; +import 'package:flutter_soloud/src/filters/filters.dart'; import 'package:flutter_soloud/src/sound_handle.dart'; import 'package:flutter_soloud/src/sound_hash.dart'; import 'package:meta/meta.dart'; @@ -608,7 +608,7 @@ abstract class FlutterSoLoud { int attributeId, double to, double time, { - SoundHandle handle = const SoundHandle(0), + SoundHandle? handle, }); /// Oscillate a parameter of a filter. @@ -628,7 +628,7 @@ abstract class FlutterSoLoud { double from, double to, double time, { - SoundHandle handle = const SoundHandle(0), + SoundHandle? handle, }); // /////////////////////////////////////// @@ -643,7 +643,7 @@ abstract class FlutterSoLoud { @mustBeOverridden ({PlayerErrors error, int index}) isFilterActive( FilterType filterType, { - SoundHash soundHash = const SoundHash.invalid(), + SoundHash? soundHash, }); /// Get parameters names of the given filter. @@ -668,7 +668,7 @@ abstract class FlutterSoLoud { @mustBeOverridden PlayerErrors addFilter( FilterType filterType, { - SoundHash soundHash = const SoundHash.invalid(), + SoundHash? soundHash, }); /// Remove the filter [filterType]. @@ -678,7 +678,7 @@ abstract class FlutterSoLoud { @mustBeOverridden PlayerErrors removeFilter( FilterType filterType, { - SoundHash soundHash = const SoundHash.invalid(), + SoundHash? soundHash, }); /// Set the effect parameter with id [attributeId] of [filterType] @@ -693,7 +693,7 @@ abstract class FlutterSoLoud { FilterType filterType, int attributeId, double value, { - SoundHandle handle = const SoundHandle.error(), + SoundHandle? handle, }); /// Get the effect parameter value with id [attributeId] of [filterType]. @@ -706,7 +706,7 @@ abstract class FlutterSoLoud { ({PlayerErrors error, double value}) getFilterParams( FilterType filterType, int attributeId, { - SoundHandle handle = const SoundHandle.error(), + SoundHandle? handle, }); // /////////////////////////////////////// diff --git a/lib/src/bindings/bindings_player_ffi.dart b/lib/src/bindings/bindings_player_ffi.dart index 1e320bc3..aa9ba084 100644 --- a/lib/src/bindings/bindings_player_ffi.dart +++ b/lib/src/bindings/bindings_player_ffi.dart @@ -11,7 +11,7 @@ import 'package:ffi/ffi.dart'; import 'package:flutter_soloud/src/bindings/audio_data.dart'; import 'package:flutter_soloud/src/bindings/bindings_player.dart'; import 'package:flutter_soloud/src/enums.dart'; -import 'package:flutter_soloud/src/filter_params.dart'; +import 'package:flutter_soloud/src/filters/filters.dart'; import 'package:flutter_soloud/src/sound_handle.dart'; import 'package:flutter_soloud/src/sound_hash.dart'; import 'package:logging/logging.dart'; @@ -1068,10 +1068,10 @@ class FlutterSoLoudFfi extends FlutterSoLoud { int attributeId, double to, double time, { - SoundHandle handle = const SoundHandle(0), + SoundHandle? handle, }) { final e = _fadeFilterParameter( - handle.id, + handle?.id ?? 0, filterType.index, attributeId, to, @@ -1094,10 +1094,10 @@ class FlutterSoLoudFfi extends FlutterSoLoud { double from, double to, double time, { - SoundHandle handle = const SoundHandle(0), + SoundHandle? handle, }) { final e = _oscillateFilterParameter( - handle.id, + handle?.id ?? 0, filterType.index, attributeId, from, @@ -1121,11 +1121,11 @@ class FlutterSoLoudFfi extends FlutterSoLoud { @override ({PlayerErrors error, int index}) isFilterActive( FilterType filterType, { - SoundHash soundHash = const SoundHash.invalid(), + SoundHash? soundHash, }) { // ignore: omit_local_variable_types final ffi.Pointer id = calloc(ffi.sizeOf()); - final e = _isFilterActive(soundHash.hash, filterType.index, id); + final e = _isFilterActive(soundHash?.hash ?? 0, filterType.index, id); final ret = (error: PlayerErrors.values[e], index: id.value); calloc.free(id); return ret; @@ -1182,9 +1182,9 @@ class FlutterSoLoudFfi extends FlutterSoLoud { @override PlayerErrors addFilter( FilterType filterType, { - SoundHash soundHash = const SoundHash.invalid(), + SoundHash? soundHash, }) { - final e = _addFilter(soundHash.hash, filterType.index); + final e = _addFilter(soundHash?.hash ?? 0, filterType.index); return PlayerErrors.values[e]; } @@ -1196,9 +1196,9 @@ class FlutterSoLoudFfi extends FlutterSoLoud { @override PlayerErrors removeFilter( FilterType filterType, { - SoundHash soundHash = const SoundHash.invalid(), + SoundHash? soundHash, }) { - final e = _removeFilter(soundHash.hash, filterType.index); + final e = _removeFilter(soundHash?.hash ?? 0, filterType.index); return PlayerErrors.values[e]; } @@ -1213,10 +1213,10 @@ class FlutterSoLoudFfi extends FlutterSoLoud { FilterType filterType, int attributeId, double value, { - SoundHandle handle = const SoundHandle.error(), + SoundHandle? handle, }) { final e = _setFilterParams( - handle.isError ? 0 : handle.id, + handle?.id ?? 0, filterType.index, attributeId, value, @@ -1235,12 +1235,12 @@ class FlutterSoLoudFfi extends FlutterSoLoud { ({PlayerErrors error, double value}) getFilterParams( FilterType filterType, int attributeId, { - SoundHandle handle = const SoundHandle.error(), + SoundHandle? handle, }) { // ignore: omit_local_variable_types final ffi.Pointer paramValue = calloc(); final error = _getFilterParams( - handle.isError ? 0 : handle.id, + handle?.id ?? 0, filterType.index, attributeId, paramValue, diff --git a/lib/src/bindings/bindings_player_web.dart b/lib/src/bindings/bindings_player_web.dart index 681d088c..2efabb71 100644 --- a/lib/src/bindings/bindings_player_web.dart +++ b/lib/src/bindings/bindings_player_web.dart @@ -5,7 +5,7 @@ import 'package:flutter_soloud/src/bindings/audio_data.dart'; import 'package:flutter_soloud/src/bindings/bindings_player.dart'; import 'package:flutter_soloud/src/bindings/js_extension.dart'; import 'package:flutter_soloud/src/enums.dart'; -import 'package:flutter_soloud/src/filter_params.dart'; +import 'package:flutter_soloud/src/filters/filters.dart'; import 'package:flutter_soloud/src/sound_handle.dart'; import 'package:flutter_soloud/src/sound_hash.dart'; import 'package:flutter_soloud/src/worker/worker.dart'; @@ -553,10 +553,10 @@ class FlutterSoLoudWeb extends FlutterSoLoud { int attributeId, double to, double time, { - SoundHandle handle = const SoundHandle.error(), + SoundHandle? handle, }) { final e = wasmFadeFilterParameter( - handle.isError ? 0 : handle.id, + handle?.id ?? 0, filterType.index, attributeId, to, @@ -572,10 +572,10 @@ class FlutterSoLoudWeb extends FlutterSoLoud { double from, double to, double time, { - SoundHandle handle = const SoundHandle.error(), + SoundHandle? handle, }) { final e = wasmOscillateFilterParameter( - handle.isError ? 0 : handle.id, + handle?.id ?? 0, filterType.index, attributeId, from, @@ -592,11 +592,11 @@ class FlutterSoLoudWeb extends FlutterSoLoud { @override ({PlayerErrors error, int index}) isFilterActive( FilterType filterType, { - SoundHash soundHash = const SoundHash.invalid(), + SoundHash? soundHash, }) { // ignore: omit_local_variable_types final idPtr = wasmMalloc(4); // 4 bytes for an int - final e = wasmIsFilterActive(soundHash.hash, filterType.index, idPtr); + final e = wasmIsFilterActive(soundHash?.hash ?? 0, filterType.index, idPtr); final index = wasmGetI32Value(idPtr, 'i32'); final ret = (error: PlayerErrors.values[e], index: index); wasmFree(idPtr); @@ -632,18 +632,18 @@ class FlutterSoLoudWeb extends FlutterSoLoud { @override PlayerErrors addFilter( FilterType filterType, { - SoundHash soundHash = const SoundHash.invalid(), + SoundHash? soundHash, }) { - final e = wasmAddFilter(soundHash.hash, filterType.index); + final e = wasmAddFilter(soundHash?.hash ?? 0, filterType.index); return PlayerErrors.values[e]; } @override PlayerErrors removeFilter( FilterType filterType, { - SoundHash soundHash = const SoundHash.invalid(), + SoundHash? soundHash, }) { - final e = wasmRemoveFilter(soundHash.hash, filterType.index); + final e = wasmRemoveFilter(soundHash?.hash ?? 0, filterType.index); return PlayerErrors.values[e]; } @@ -652,10 +652,10 @@ class FlutterSoLoudWeb extends FlutterSoLoud { FilterType filterType, int attributeId, double value, { - SoundHandle handle = const SoundHandle(0), + SoundHandle? handle, }) { final e = wasmSetFilterParams( - handle.id, + handle?.id ?? 0, filterType.index, attributeId, value, @@ -667,11 +667,11 @@ class FlutterSoLoudWeb extends FlutterSoLoud { ({PlayerErrors error, double value}) getFilterParams( FilterType filterType, int attributeId, { - SoundHandle handle = const SoundHandle(0), + SoundHandle? handle, }) { final paramValuePtr = wasmMalloc(4); final error = wasmGetFilterParams( - handle.id, + handle?.id ?? 0, filterType.index, attributeId, paramValuePtr, diff --git a/lib/src/bindings/soloud_controller_ffi.dart b/lib/src/bindings/soloud_controller_ffi.dart index 2eb43b4e..91a972c3 100644 --- a/lib/src/bindings/soloud_controller_ffi.dart +++ b/lib/src/bindings/soloud_controller_ffi.dart @@ -5,7 +5,7 @@ import 'dart:io'; import 'package:flutter_soloud/src/bindings/bindings_player_ffi.dart'; -/// Controller that expose method channel and FFI +/// Controller that expose FFI. class SoLoudController { factory SoLoudController() => _instance ??= SoLoudController._(); diff --git a/lib/src/bindings/soloud_controller_web.dart b/lib/src/bindings/soloud_controller_web.dart index 351b3156..086f18ab 100644 --- a/lib/src/bindings/soloud_controller_web.dart +++ b/lib/src/bindings/soloud_controller_web.dart @@ -2,7 +2,7 @@ import 'package:flutter_soloud/src/bindings/bindings_player_web.dart'; -/// Controller that expose method channel and FFI +/// Controller that expose FFI. class SoLoudController { factory SoLoudController() => _instance ??= SoLoudController._(); diff --git a/lib/src/enums.dart b/lib/src/enums.dart index ab756409..c2692bec 100644 --- a/lib/src/enums.dart +++ b/lib/src/enums.dart @@ -59,7 +59,10 @@ enum PlayerErrors { playerAlreadyInited(16), /// Audio handle is not found - soundHandleNotFound(17); + soundHandleNotFound(17), + + /// Error getting filter parameter. + filterParameterGetError(18); const PlayerErrors(this.value); @@ -110,6 +113,9 @@ enum PlayerErrors { case PlayerErrors.soundHandleNotFound: return 'The handle is not found! The playing handle could have been ' 'stopped or ended and it is no more valid!'; + case PlayerErrors.filterParameterGetError: + return 'An error (nan or inf value) occurred while getting a ' + 'filter parameter!'; } } diff --git a/lib/src/exceptions/exceptions.dart b/lib/src/exceptions/exceptions.dart index e4e1afc3..295704df 100644 --- a/lib/src/exceptions/exceptions.dart +++ b/lib/src/exceptions/exceptions.dart @@ -1,3 +1,4 @@ +import 'package:flutter_soloud/src/audio_source.dart'; import 'package:flutter_soloud/src/enums.dart'; import 'package:flutter_soloud/src/sound_hash.dart'; @@ -101,6 +102,8 @@ abstract class SoLoudCppException extends SoLoudException { return const SoLoudPlayerAlreadyInitializedException(); case PlayerErrors.soundHandleNotFound: return const SoLoudSoundHandleNotFoundCppException(); + case PlayerErrors.filterParameterGetError: + return const SoLoudFilterParameterGetErrorCppException(); } } } diff --git a/lib/src/exceptions/exceptions_from_cpp.dart b/lib/src/exceptions/exceptions_from_cpp.dart index abb26d24..e821b14f 100644 --- a/lib/src/exceptions/exceptions_from_cpp.dart +++ b/lib/src/exceptions/exceptions_from_cpp.dart @@ -175,3 +175,15 @@ class SoLoudSoundHandleNotFoundCppException extends SoLoudCppException { String get description => 'The sound handle is not found ' '(on the C++ side).'; } + +/// An error occurred while getting a filter parameter +class SoLoudFilterParameterGetErrorCppException extends SoLoudCppException { + /// Creates a new [SoLoudFilterParameterGetErrorCppException]. + const SoLoudFilterParameterGetErrorCppException([super.message]); + + @override + String get description => 'An error occurred while getting a filter ' + 'parameter. This could happen when passing a value outside the parameter ' + 'range and then trying to get it ' + '(on the C++ side).'; +} diff --git a/lib/src/exceptions/exceptions_from_dart.dart b/lib/src/exceptions/exceptions_from_dart.dart index cb2b30f2..cb83d57b 100644 --- a/lib/src/exceptions/exceptions_from_dart.dart +++ b/lib/src/exceptions/exceptions_from_dart.dart @@ -89,7 +89,7 @@ class SoLoudSoundHashNotFoundDartException extends SoLoudDartException { } /// An exception that is thrown when SoLoud (Dart) tries to create a voice -/// group but something goes wrong. +/// group but something gone wrong. class SoLoudCreateVoiceGroupDartException extends SoLoudDartException { /// Creates a new [SoLoudCreateVoiceGroupDartException]. const SoLoudCreateVoiceGroupDartException([super.message]); @@ -98,3 +98,14 @@ class SoLoudCreateVoiceGroupDartException extends SoLoudDartException { String get description => 'SoLoud.createVoiceGroup() was not able to create ' ' a new voice group.'; } + +/// An exception that is thrown when trying to set a filter for a single +/// [AudioSource] on the Web platform. +class SoLoudFilterForSingleSoundOnWebDartException extends SoLoudDartException { + /// Creates a new [SoLoudFilterForSingleSoundOnWebDartException]. + const SoLoudFilterForSingleSoundOnWebDartException([super.message]); + + @override + String get description => 'Filters for single sounds are not supported on ' + 'the Web platform.'; +} diff --git a/lib/src/filter_params.dart b/lib/src/filter_params.dart index 001ec901..c5d041d0 100644 --- a/lib/src/filter_params.dart +++ b/lib/src/filter_params.dart @@ -1,34 +1,11 @@ -/// The different types of audio filters. -enum FilterType { - /// A biquad resonant filter. - biquadResonantFilter, +// ignore_for_file: public_member_api_docs - /// An equalizer filter. - eqFilter, - - /// An echo filter. - echoFilter, - - /// A lo-fi filter. - lofiFilter, - - /// A flanger filter. - flangerFilter, - - /// A bass-boost filter. - bassboostFilter, - - /// A wave shaper filter. - waveShaperFilter, - - /// A robotize filter. - robotizeFilter, - - /// A reverb filter. - freeverbFilter, -} +// /////////////////////////////////////////////// +// Old way to manage filters. Deprecating these +// /////////////////////////////////////////////// /// The parameters for each filter. +@Deprecated('Please use the SoLoud.filters or AudioSource.filters.') typedef FxParams = ({ String title, List names, @@ -38,6 +15,7 @@ typedef FxParams = ({ }); /// Biquad Resonant filter +@Deprecated('Please use the SoLoud.filters or AudioSource.filters.') const FxParams fxBiquadResonant = ( title: 'Biquad Resonant', @@ -49,6 +27,7 @@ const FxParams fxBiquadResonant = ( ); /// EQ filter +@Deprecated('Please use the SoLoud.filters or AudioSource.filters.') const FxParams fxEq = ( title: 'Equalizer', names: [ @@ -68,6 +47,7 @@ const FxParams fxEq = ( ); /// Echo filter +@Deprecated('Please use the SoLoud.filters or AudioSource.filters.') const FxParams fxEcho = ( title: 'Echo', names: ['Wet', 'Delay', 'Decay', 'Filter'], @@ -77,6 +57,7 @@ const FxParams fxEcho = ( ); /// Lo-fi filter +@Deprecated('Please use the SoLoud.filters or AudioSource.filters.') const FxParams fxLofi = ( title: 'Lofi', names: ['Wet', 'Samplerate', 'Bitdepth'], @@ -86,6 +67,7 @@ const FxParams fxLofi = ( ); /// Flanger filter +@Deprecated('Please use the SoLoud.filters or AudioSource.filters.') const FxParams fxFlanger = ( title: 'Flanger', names: ['Wet', 'Delay', 'Freq'], @@ -95,6 +77,7 @@ const FxParams fxFlanger = ( ); /// Bass-boost filter +@Deprecated('Please use the SoLoud.filters or AudioSource.filters.') const FxParams fxBassboost = ( title: 'Bassboost', names: ['Wet', 'Boost'], @@ -104,6 +87,7 @@ const FxParams fxBassboost = ( ); /// WaveShaper filter +@Deprecated('Please use the SoLoud.filters or AudioSource.filters.') const FxParams fxWaveShaper = ( title: 'Wave Shaper', names: ['Wet', 'Amount'], @@ -113,6 +97,7 @@ const FxParams fxWaveShaper = ( ); /// Robotize filter +@Deprecated('Please use the SoLoud.filters or AudioSource.filters.') const FxParams fxRobotize = ( title: 'Robotize', @@ -124,6 +109,7 @@ const FxParams fxRobotize = ( ); /// Freeverb (reverb) filter +@Deprecated('Please use the SoLoud.filters or AudioSource.filters.') const FxParams fxFreeverb = ( title: 'Freeverb', @@ -133,3 +119,13 @@ const FxParams fxFreeverb = ( maxs: [1, 1, 1, 1, 1], defs: [1, 0, 0.5, 0.5, 1], ); + +/// Pitch shift filter +@Deprecated('Please use the SoLoud.filters or AudioSource.filters.') +const FxParams fxPitchShift = ( + title: 'PitchShift', + names: ['Wet', 'Shift', 'Semitones'], + mins: [0, 0, -48], + maxs: [1, 3, 48], + defs: [1, 1, 0], +); diff --git a/lib/src/filters/bassboost_filter.dart b/lib/src/filters/bassboost_filter.dart new file mode 100644 index 00000000..83280dab --- /dev/null +++ b/lib/src/filters/bassboost_filter.dart @@ -0,0 +1,78 @@ +// ignore_for_file: public_member_api_docs + +import 'package:flutter_soloud/src/filters/filters.dart'; +import 'package:flutter_soloud/src/sound_handle.dart'; +import 'package:flutter_soloud/src/sound_hash.dart'; + +enum BassBoostEnum { + wet, + boost; + + /// use iterables? + final List _mins = const [0, 0]; + final List _maxs = const [1, 10]; + final List _defs = const [1, 2]; + + double get min => _mins[index]; + double get max => _maxs[index]; + double get def => _defs[index]; + + @override + String toString() => switch (this) { + BassBoostEnum.wet => 'Wet', + BassBoostEnum.boost => 'Boost', + }; +} + +abstract class BassBoostInternal { + const BassBoostInternal(SoundHash? soundHash) : _soundHash = soundHash; + + final SoundHash? _soundHash; + FilterType get filterType => FilterType.bassboostFilter; + BassBoostEnum get queryWet => BassBoostEnum.wet; + BassBoostEnum get queryBoost => BassBoostEnum.boost; + + void activate() => filterType.activate(_soundHash); + + void deactivate() => filterType.deactivate(_soundHash); +} + +class BassBoostSingle extends BassBoostInternal { + BassBoostSingle(super.soundHash); + + FilterParam wet({SoundHandle? soundHandle}) => FilterParam( + soundHandle, + filterType, + BassBoostEnum.wet.index, + BassBoostEnum.wet.min, + BassBoostEnum.wet.max, + ); + + FilterParam boost({SoundHandle? soundHandle}) => FilterParam( + soundHandle, + filterType, + BassBoostEnum.boost.index, + BassBoostEnum.boost.min, + BassBoostEnum.boost.max, + ); +} + +class BassBoostGlobal extends BassBoostInternal { + const BassBoostGlobal() : super(null); + + FilterParam get wet => FilterParam( + null, + filterType, + BassBoostEnum.wet.index, + BassBoostEnum.wet.min, + BassBoostEnum.wet.max, + ); + + FilterParam get boost => FilterParam( + null, + filterType, + BassBoostEnum.boost.index, + BassBoostEnum.boost.min, + BassBoostEnum.boost.max, + ); +} diff --git a/lib/src/filters/biquad_resonant_filter.dart b/lib/src/filters/biquad_resonant_filter.dart new file mode 100644 index 00000000..50880fcb --- /dev/null +++ b/lib/src/filters/biquad_resonant_filter.dart @@ -0,0 +1,133 @@ +// ignore_for_file: public_member_api_docs + +import 'package:flutter_soloud/src/filters/filters.dart'; +import 'package:flutter_soloud/src/sound_handle.dart'; +import 'package:flutter_soloud/src/sound_hash.dart'; + +/// From [SoLoud doc](https://solhsa.com/soloud/biquadfilter.html): +/// The biquad resonant filter is a surprisingly cheap way to implement low and +/// high pass filters, as well as some kind of band bass filter. +/// The implementation in SoLoud is based on "Using the Biquad Resonant Filter", +/// Phil Burk, Game Programming Gems 3, p. 606. +/// +/// The filter has three parameters - sample rate, cutoff frequency and +/// resonance. These can also be adjusted on live streams, for instance to +/// fade the low pass filter cutoff frequency for a outdoors/indoors +/// transition effect. +/// +/// The resonance parameter adjusts the sharpness (or bandwidth) of the cutoff. +/// +/// The [type] is treat as an int value and it means: +/// LOWPASS = 0, +/// HIGHPASS = 1, +/// BANDPASS = 2 +enum BiquadResonantEnum { + wet, + type, + frequency, + resonance; + + /// use iterables? + final List _mins = const [0, 0.0, 10, 0.1]; + final List _maxs = const [1, 2.0, 16000, 20]; + final List _defs = const [1, 0.0, 0.5, 0.1]; + + double get min => _mins[index]; + double get max => _maxs[index]; + double get def => _defs[index]; + + @override + String toString() => switch (this) { + BiquadResonantEnum.wet => 'Wet', + BiquadResonantEnum.type => 'Type', + BiquadResonantEnum.frequency => 'Frequency', + BiquadResonantEnum.resonance => 'Resonance', + }; +} + +abstract class BiquadResonantInternal { + const BiquadResonantInternal(SoundHash? soundHash) : _soundHash = soundHash; + + final SoundHash? _soundHash; + FilterType get filterType => FilterType.biquadResonantFilter; + BiquadResonantEnum get queryWet => BiquadResonantEnum.wet; + BiquadResonantEnum get queryType => BiquadResonantEnum.type; + BiquadResonantEnum get queryFrequency => BiquadResonantEnum.frequency; + BiquadResonantEnum get queryResonance => BiquadResonantEnum.resonance; + + void activate() => filterType.activate(_soundHash); + + void deactivate() => filterType.deactivate(_soundHash); +} + +class BiquadResonantSingle extends BiquadResonantInternal { + BiquadResonantSingle(super.soundHash); + + FilterParam wet({SoundHandle? soundHandle}) => FilterParam( + soundHandle, + filterType, + BiquadResonantEnum.wet.index, + BiquadResonantEnum.wet.min, + BiquadResonantEnum.wet.max, + ); + + FilterParam type({SoundHandle? soundHandle}) => FilterParam( + soundHandle, + filterType, + BiquadResonantEnum.type.index, + BiquadResonantEnum.type.min, + BiquadResonantEnum.type.max, + ); + + FilterParam frequency({SoundHandle? soundHandle}) => FilterParam( + soundHandle, + filterType, + BiquadResonantEnum.frequency.index, + BiquadResonantEnum.frequency.min, + BiquadResonantEnum.frequency.max, + ); + + FilterParam resonance({SoundHandle? soundHandle}) => FilterParam( + soundHandle, + filterType, + BiquadResonantEnum.resonance.index, + BiquadResonantEnum.resonance.min, + BiquadResonantEnum.resonance.max, + ); +} + +class BiquadResonantGlobal extends BiquadResonantInternal { + const BiquadResonantGlobal() : super(null); + + FilterParam get wet => FilterParam( + null, + filterType, + BiquadResonantEnum.wet.index, + BiquadResonantEnum.wet.min, + BiquadResonantEnum.wet.max, + ); + + FilterParam get type => FilterParam( + null, + filterType, + BiquadResonantEnum.type.index, + BiquadResonantEnum.type.min, + BiquadResonantEnum.type.max, + ); + + FilterParam get frequency => FilterParam( + null, + filterType, + BiquadResonantEnum.frequency.index, + BiquadResonantEnum.frequency.min, + BiquadResonantEnum.frequency.max, + ); + + FilterParam get resonance => FilterParam( + null, + filterType, + BiquadResonantEnum.resonance.index, + BiquadResonantEnum.resonance.min, + BiquadResonantEnum.resonance.max, + ); +} diff --git a/lib/src/filters/echo_filter.dart b/lib/src/filters/echo_filter.dart new file mode 100644 index 00000000..0a08b2e5 --- /dev/null +++ b/lib/src/filters/echo_filter.dart @@ -0,0 +1,116 @@ +// ignore_for_file: public_member_api_docs + +import 'package:flutter_soloud/src/filters/filters.dart'; +import 'package:flutter_soloud/src/sound_handle.dart'; +import 'package:flutter_soloud/src/sound_hash.dart'; + +enum EchoEnum { + wet, + delay, + decay, + filter; + + /// use iterables? + final List _mins = const [0, 0.001, 0.001, 0]; + final List _maxs = const [1, double.maxFinite, 1, 1]; + final List _defs = const [1, 0.3, 0.7, 0]; + + double get min => _mins[index]; + double get max => _maxs[index]; + double get def => _defs[index]; + + @override + String toString() => switch (this) { + EchoEnum.wet => 'Wet', + EchoEnum.delay => 'Delay', + EchoEnum.decay => 'Decay', + EchoEnum.filter => 'Filter', + }; +} + +abstract class EchoInternal { + const EchoInternal(SoundHash? soundHash) : _soundHash = soundHash; + + final SoundHash? _soundHash; + FilterType get filterType => FilterType.echoFilter; + EchoEnum get queryWet => EchoEnum.wet; + EchoEnum get queryDelay => EchoEnum.delay; + EchoEnum get queryDecay => EchoEnum.decay; + EchoEnum get queryFilter => EchoEnum.filter; + + void activate() => filterType.activate(_soundHash); + + void deactivate() => filterType.deactivate(_soundHash); +} + +class EchoSingle extends EchoInternal { + EchoSingle(super.soundHash); + + FilterParam wet({SoundHandle? soundHandle}) => FilterParam( + soundHandle, + filterType, + EchoEnum.wet.index, + EchoEnum.wet.min, + EchoEnum.wet.max, + ); + + FilterParam delay({SoundHandle? soundHandle}) => FilterParam( + soundHandle, + filterType, + EchoEnum.delay.index, + EchoEnum.delay.min, + EchoEnum.delay.max, + ); + + FilterParam decay({SoundHandle? soundHandle}) => FilterParam( + soundHandle, + filterType, + EchoEnum.decay.index, + EchoEnum.decay.min, + EchoEnum.decay.max, + ); + + FilterParam filter({SoundHandle? soundHandle}) => FilterParam( + soundHandle, + filterType, + EchoEnum.filter.index, + EchoEnum.filter.min, + EchoEnum.filter.max, + ); +} + +class EchoGlobal extends EchoInternal { + const EchoGlobal() : super(null); + + FilterParam get wet => FilterParam( + null, + filterType, + EchoEnum.wet.index, + EchoEnum.wet.min, + EchoEnum.wet.max, + ); + + FilterParam get delay => FilterParam( + null, + filterType, + EchoEnum.delay.index, + EchoEnum.delay.min, + EchoEnum.delay.max, + ); + + FilterParam get decay => FilterParam( + null, + filterType, + EchoEnum.decay.index, + EchoEnum.decay.min, + EchoEnum.decay.max, + ); + + FilterParam get filter => FilterParam( + null, + filterType, + EchoEnum.filter.index, + EchoEnum.filter.min, + EchoEnum.filter.max, + ); +} diff --git a/lib/src/filters/equalizer_filter.dart b/lib/src/filters/equalizer_filter.dart new file mode 100644 index 00000000..1a793477 --- /dev/null +++ b/lib/src/filters/equalizer_filter.dart @@ -0,0 +1,211 @@ +// ignore_for_file: public_member_api_docs + +import 'package:flutter_soloud/src/filters/filters.dart'; +import 'package:flutter_soloud/src/sound_handle.dart'; +import 'package:flutter_soloud/src/sound_hash.dart'; + +enum EqualizerEnum { + wet, + band1, + band2, + band3, + band4, + band5, + band6, + band7, + band8; + + /// use iterables? + final List _mins = const [0, 0, 0, 0, 0, 0, 0, 0, 0]; + final List _maxs = const [1, 4, 4, 4, 4, 4, 4, 4, 4]; + final List _defs = const [1, 1, 1, 1, 1, 1, 1, 1, 1]; + + double get min => _mins[index]; + double get max => _maxs[index]; + double get def => _defs[index]; + + @override + String toString() => switch (this) { + EqualizerEnum.wet => 'Wet', + EqualizerEnum.band1 => 'Band 1', + EqualizerEnum.band2 => 'Band 2', + EqualizerEnum.band3 => 'Band 3', + EqualizerEnum.band4 => 'Band 4', + EqualizerEnum.band5 => 'Band 5', + EqualizerEnum.band6 => 'Band 6', + EqualizerEnum.band7 => 'Band 7', + EqualizerEnum.band8 => 'Band 8', + }; +} + +abstract class EqualizerInternal { + const EqualizerInternal(SoundHash? soundHash) : _soundHash = soundHash; + + final SoundHash? _soundHash; + FilterType get filterType => FilterType.eqFilter; + EqualizerEnum get queryWet => EqualizerEnum.wet; + EqualizerEnum get queryBand1 => EqualizerEnum.band1; + EqualizerEnum get queryBand2 => EqualizerEnum.band2; + EqualizerEnum get queryBand3 => EqualizerEnum.band3; + EqualizerEnum get queryBand4 => EqualizerEnum.band4; + EqualizerEnum get queryBand5 => EqualizerEnum.band5; + EqualizerEnum get queryBand6 => EqualizerEnum.band6; + EqualizerEnum get queryBand7 => EqualizerEnum.band7; + EqualizerEnum get queryBand8 => EqualizerEnum.band8; + + void activate() => filterType.activate(_soundHash); + + void deactivate() => filterType.deactivate(_soundHash); +} + +class EqualizerSingle extends EqualizerInternal { + EqualizerSingle(super.soundHash); + + FilterParam wet({SoundHandle? soundHandle}) => FilterParam( + soundHandle, + filterType, + EqualizerEnum.wet.index, + EqualizerEnum.wet.min, + EqualizerEnum.wet.max, + ); + + FilterParam band1({SoundHandle? soundHandle}) => FilterParam( + soundHandle, + filterType, + EqualizerEnum.band1.index, + EqualizerEnum.band1.min, + EqualizerEnum.band1.max, + ); + + FilterParam band2({SoundHandle? soundHandle}) => FilterParam( + soundHandle, + filterType, + EqualizerEnum.band2.index, + EqualizerEnum.band2.min, + EqualizerEnum.band2.max, + ); + + FilterParam band3({SoundHandle? soundHandle}) => FilterParam( + soundHandle, + filterType, + EqualizerEnum.band3.index, + EqualizerEnum.band3.min, + EqualizerEnum.band3.max, + ); + + FilterParam band4({SoundHandle? soundHandle}) => FilterParam( + soundHandle, + filterType, + EqualizerEnum.band4.index, + EqualizerEnum.band4.min, + EqualizerEnum.band4.max, + ); + + FilterParam band5({SoundHandle? soundHandle}) => FilterParam( + soundHandle, + filterType, + EqualizerEnum.band5.index, + EqualizerEnum.band5.min, + EqualizerEnum.band5.max, + ); + + FilterParam band6({SoundHandle? soundHandle}) => FilterParam( + soundHandle, + filterType, + EqualizerEnum.band6.index, + EqualizerEnum.band6.min, + EqualizerEnum.band6.max, + ); + + FilterParam band7({SoundHandle? soundHandle}) => FilterParam( + soundHandle, + filterType, + EqualizerEnum.band7.index, + EqualizerEnum.band7.min, + EqualizerEnum.band7.max, + ); + + FilterParam band8({SoundHandle? soundHandle}) => FilterParam( + soundHandle, + filterType, + EqualizerEnum.band8.index, + EqualizerEnum.band8.min, + EqualizerEnum.band8.max, + ); +} + +class EqualizerGlobal extends EqualizerInternal { + const EqualizerGlobal() : super(null); + + FilterParam get wet => FilterParam( + null, + filterType, + EqualizerEnum.wet.index, + EqualizerEnum.wet.min, + EqualizerEnum.wet.max, + ); + + FilterParam get band1 => FilterParam( + null, + filterType, + EqualizerEnum.band1.index, + EqualizerEnum.band1.min, + EqualizerEnum.band1.max, + ); + + FilterParam get band2 => FilterParam( + null, + filterType, + EqualizerEnum.band2.index, + EqualizerEnum.band2.min, + EqualizerEnum.band2.max, + ); + + FilterParam get band3 => FilterParam( + null, + filterType, + EqualizerEnum.band3.index, + EqualizerEnum.band3.min, + EqualizerEnum.band3.max, + ); + + FilterParam get band4 => FilterParam( + null, + filterType, + EqualizerEnum.band4.index, + EqualizerEnum.band4.min, + EqualizerEnum.band4.max, + ); + + FilterParam get band5 => FilterParam( + null, + filterType, + EqualizerEnum.band5.index, + EqualizerEnum.band5.min, + EqualizerEnum.band5.max, + ); + + FilterParam get band6 => FilterParam( + null, + filterType, + EqualizerEnum.band6.index, + EqualizerEnum.band6.min, + EqualizerEnum.band6.max, + ); + + FilterParam get band7 => FilterParam( + null, + filterType, + EqualizerEnum.band7.index, + EqualizerEnum.band7.min, + EqualizerEnum.band7.max, + ); + + FilterParam get band8 => FilterParam( + null, + filterType, + EqualizerEnum.band8.index, + EqualizerEnum.band8.min, + EqualizerEnum.band8.max, + ); +} diff --git a/lib/src/filters/filters.dart b/lib/src/filters/filters.dart new file mode 100644 index 00000000..9e6e02d6 --- /dev/null +++ b/lib/src/filters/filters.dart @@ -0,0 +1,371 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter_soloud/src/audio_source.dart'; +import 'package:flutter_soloud/src/bindings/bindings_player.dart'; +import 'package:flutter_soloud/src/bindings/soloud_controller.dart'; +import 'package:flutter_soloud/src/enums.dart'; +import 'package:flutter_soloud/src/exceptions/exceptions.dart'; +import 'package:flutter_soloud/src/filters/bassboost_filter.dart'; +import 'package:flutter_soloud/src/filters/biquad_resonant_filter.dart'; +import 'package:flutter_soloud/src/filters/echo_filter.dart'; +import 'package:flutter_soloud/src/filters/equalizer_filter.dart'; +import 'package:flutter_soloud/src/filters/flanger_filter.dart'; +import 'package:flutter_soloud/src/filters/freeverb_filter.dart'; +import 'package:flutter_soloud/src/filters/lofi_filter.dart'; +import 'package:flutter_soloud/src/filters/pitchshift_filter.dart'; +import 'package:flutter_soloud/src/filters/robotize_filter.dart'; +import 'package:flutter_soloud/src/filters/wave_shaper_filter.dart'; +import 'package:flutter_soloud/src/soloud.dart'; +import 'package:flutter_soloud/src/sound_handle.dart'; +import 'package:flutter_soloud/src/sound_hash.dart'; +import 'package:logging/logging.dart'; +import 'package:meta/meta.dart'; + +/// These classes are not exposed to the APIs. They are used internally in +/// [AudioSource.filters] and [SoLoud.filters]. + +/// Filters instance used in [AudioSource.filters]. This differentiate from the +/// below [FiltersGlobal] class for the required [SoundHash] parameter used to +/// add a filter to that specific [AudioSource]. +final class FiltersSingle { + /// The class to get access to all the filters available to sounds. + const FiltersSingle({required this.soundHash}); + + /// The unique hash code of the sound. + final SoundHash soundHash; + + /// The `Bass Boost` filter for this sound. + BassBoostSingle get bassBoostFilter => BassBoostSingle(soundHash); + + /// The `Biquad Resonant` filter for this sound. + BiquadResonantSingle get biquadFilter => BiquadResonantSingle(soundHash); + + /// The `Echo` filter for this sound. + EchoSingle get echoFilter => EchoSingle(soundHash); + + /// The `Equalizer` filter for this sound. + EqualizerSingle get equalizerFilter => EqualizerSingle(soundHash); + + /// The `Flanger` filter for this sound. + FlangerSingle get flangerFilter => FlangerSingle(soundHash); + + /// The `Freeverb` filter for this sound. + FreeverbSingle get freeverbFilter => FreeverbSingle(soundHash); + + /// The `Lofi` filter for this sound. + LofiSingle get lofiFilter => LofiSingle(soundHash); + + /// The `Pitch Shift` filter for this sound. + PitchShiftSingle get pitchShiftFilter => PitchShiftSingle(soundHash); + + /// The `Robotize` filter for this sound. + RobotizeSingle get robotizeFilter => RobotizeSingle(soundHash); + + /// The `Wave Shaper` filter for this sound. + WaveShaperSingle get waveShaperFilter => WaveShaperSingle(soundHash); +} + +/// Filters instance used in [SoLoud.filters]. This differentiate from the +/// above [FiltersSingle] class for the unneeded [SoundHash] parameter because +/// the filter is managed globally. +final class FiltersGlobal { + /// The class to get access to all the filters available globally. + const FiltersGlobal(); + + /// The `Bass Boost` filter used globally. + BassBoostGlobal get bassBoostFilter => const BassBoostGlobal(); + + /// The `Biquad Resonant` filter used globally. + BiquadResonantGlobal get biquadResonantFilter => const BiquadResonantGlobal(); + + /// The `Echo` filter used globally. + EchoGlobal get echoFilter => const EchoGlobal(); + + /// The `Equalizer` filter used globally. + EqualizerGlobal get equalizerFilter => const EqualizerGlobal(); + + /// The `Flanger` filter used globally. + FlangerGlobal get flangerFilter => const FlangerGlobal(); + + /// The `Freeverb` filter used globally. + FreeverbGlobal get freeverbFilter => const FreeverbGlobal(); + + /// The `Lofi` filter used globally. + LofiGlobal get lofiFilter => const LofiGlobal(); + + /// The `Pitch Shift` filter used globally. + PitchShiftGlobal get pitchShiftFilter => const PitchShiftGlobal(); + + /// The `Robotize` filter used globally. + RobotizeGlobal get robotizeFilter => const RobotizeGlobal(); + + /// The `Wave Shaper` filter used globally. + WaveShaperGlobal get waveShaperFilter => const WaveShaperGlobal(); +} + +/// Common class for single and global filters. +class FilterParam { + /// Every filter parameter values can be set/get/fade/oscillate. + FilterParam( + this._soundHandle, + this._type, + this._attributeId, + this._min, + this._max, + ); + + final SoundHandle? _soundHandle; + final FilterType _type; + final int _attributeId; + final double _min; + final double _max; + + /// Get the parameter value. + /// + /// Throws [SoLoudFilterForSingleSoundOnWebDartException] if trying to use + /// [FiltersSingle] on the Web platform. + double get value { + if (kIsWeb && _soundHandle != null) { + throw const SoLoudFilterForSingleSoundOnWebDartException(); + } + final ret = SoLoudController().soLoudFFI.getFilterParams( + handle: _soundHandle, + _type, + _attributeId, + ); + + if (ret.error != PlayerErrors.noError) { + Logger('flutter_soloud.${_type.name}Filter') + .severe(() => 'get value: ${ret.error}'); + throw SoLoudCppException.fromPlayerError(ret.error); + } + return ret.value; + } + + /// Set the parameter value. + /// + /// Throws [SoLoudFilterForSingleSoundOnWebDartException] if trying to use + /// [FiltersSingle] on the Web platform. + set value(double val) { + if (kIsWeb && _soundHandle != null) { + throw const SoLoudFilterForSingleSoundOnWebDartException(); + } + if (val < _min || val > _max) { + Logger('flutter_soloud.${_type.name}Filter') + .warning(() => 'value [$val] out of accepted range [$_min, $_max]'); + return; + } + final error = SoLoudController().soLoudFFI.setFilterParams( + handle: _soundHandle, + _type, + _attributeId, + val, + ); + if (error != PlayerErrors.noError) { + Logger('flutter_soloud.${_type.name}Filter') + .severe(() => 'set value: $error'); + throw SoLoudCppException.fromPlayerError(error); + } + } + + /// Fade a parameter value to a new value [to] in [time] time duration. + /// + /// Throws [SoLoudFilterForSingleSoundOnWebDartException] if trying to use + /// [FiltersSingle] on the Web platform. + void fadeFilterParameter({ + required double to, + required Duration time, + }) => + _type.fadeFilterParameter(_soundHandle, _attributeId, to, time); + + /// Oscillate a parameter value from [from] value to a new value [to] + /// in [time] time duration. + /// + /// Throws [SoLoudFilterForSingleSoundOnWebDartException] if trying to use + /// [FiltersSingle] on the Web platform. + void oscillateFilterParameter({ + required double from, + required double to, + required Duration time, + }) => + _type.oscillateFilterParameter( + _soundHandle, + _attributeId, + from, + to, + time, + ); +} + +/// The different types of audio filters. +enum FilterType { + /// A biquad resonant filter. + biquadResonantFilter, + + /// An equalizer filter. + eqFilter, + + /// An echo filter. + echoFilter, + + /// A lo-fi filter. + lofiFilter, + + /// A flanger filter. + flangerFilter, + + /// A bass-boost filter. + bassboostFilter, + + /// A wave shaper filter. + waveShaperFilter, + + /// A robotize filter. + robotizeFilter, + + /// A reverb filter. + freeverbFilter, + + /// A pitch shift filter. + pitchShiftFilter; + + @override + String toString() => switch (this) { + FilterType.biquadResonantFilter => 'Biquad Resonant', + FilterType.eqFilter => 'Equalizer', + FilterType.echoFilter => 'Echo', + FilterType.lofiFilter => 'Lofi', + FilterType.flangerFilter => 'Flanger', + FilterType.bassboostFilter => 'Bassboost', + FilterType.waveShaperFilter => 'Wave Shaper', + FilterType.robotizeFilter => 'Robotize', + FilterType.freeverbFilter => 'Freeverb', + FilterType.pitchShiftFilter => 'Pitchshift', + }; + + /// The number of parameter this filter owns. + int get numParameters => switch (this) { + FilterType.biquadResonantFilter => 4, + FilterType.eqFilter => 9, + FilterType.echoFilter => 4, + FilterType.lofiFilter => 3, + FilterType.flangerFilter => 3, + FilterType.bassboostFilter => 2, + FilterType.waveShaperFilter => 2, + FilterType.robotizeFilter => 3, + FilterType.freeverbFilter => 5, + FilterType.pitchShiftFilter => 3, + }; + + /// Activate this filter. If [soundHash] is null this filter is applied + /// globally, else to the given [soundHash]. + /// + /// Throws [SoLoudFilterForSingleSoundOnWebDartException] if trying to use + /// a filter for a single sound on the Web platform. + @internal + void activate(SoundHash? soundHash) { + if (kIsWeb && soundHash != null) { + throw const SoLoudFilterForSingleSoundOnWebDartException(); + } + final error = SoLoudController().soLoudFFI.addFilter( + this, + soundHash: soundHash ?? const SoundHash.invalid(), + ); + if (error != PlayerErrors.noError) { + Logger.root.severe( + () => '$name activate() ' + '${soundHash == null ? 'global ' : 'single '} filter: $error', + ); + throw SoLoudCppException.fromPlayerError(error); + } + } + + /// Deactivate this filter. If [soundHash] is null this filter is removed + /// globally, else from the given [soundHash]. + /// + /// Throws [SoLoudFilterForSingleSoundOnWebDartException] if trying to use + /// a filter for a single sound on the Web platform. + @internal + void deactivate(SoundHash? soundHash) { + if (kIsWeb && soundHash != null) { + throw const SoLoudFilterForSingleSoundOnWebDartException(); + } + final error = SoLoudController().soLoudFFI.removeFilter( + this, + soundHash: soundHash ?? const SoundHash.invalid(), + ); + if (error != PlayerErrors.noError) { + Logger.root.severe( + () => '$name deactivate() ' + '${soundHash == null ? 'global ' : 'single '} filter: $error', + ); + throw SoLoudCppException.fromPlayerError(error); + } + } + + /// Fade a parameter with index [attributeId], to a value [to] in [time] time. + /// If [soundHandle] is null the fade is applied to this global filter else + /// to the given sound handle. + /// + /// Throws [SoLoudFilterForSingleSoundOnWebDartException] if trying to use + /// a filter for a single sound on the Web platform. + @internal + void fadeFilterParameter( + SoundHandle? soundHandle, + int attributeId, + double to, + Duration time, + ) { + if (kIsWeb && soundHandle != null) { + throw const SoLoudFilterForSingleSoundOnWebDartException(); + } + if (!SoLoud.instance.isInitialized) { + throw const SoLoudNotInitializedException(); + } + final error = SoLoudController().soLoudFFI.fadeFilterParameter( + this, + attributeId, + to, + time.toDouble(), + handle: soundHandle, + ); + if (error != PlayerErrors.noError) { + Logger.root.severe(() => 'fadeFilterParameter(): $error'); + throw SoLoudCppException.fromPlayerError(error); + } + } + + /// Oscillate a parameter with index [attributeId], to a value [to] in + /// [time] time. + /// If [soundHandle] is null the fade is applied to this global filter else + /// to the given sound handle. + /// + /// Throws [SoLoudFilterForSingleSoundOnWebDartException] if trying to use + /// a filter for a single sound on the Web platform. + @internal + void oscillateFilterParameter( + SoundHandle? soundHandle, + int attributeId, + double from, + double to, + Duration time, + ) { + if (kIsWeb && soundHandle != null) { + throw const SoLoudFilterForSingleSoundOnWebDartException(); + } + if (!SoLoud.instance.isInitialized) { + throw const SoLoudNotInitializedException(); + } + final error = SoLoudController().soLoudFFI.oscillateFilterParameter( + this, + attributeId, + from, + to, + time.toDouble(), + handle: soundHandle, + ); + if (error != PlayerErrors.noError) { + Logger.root.severe(() => 'oscillateFilterParameter(): $error'); + throw SoLoudCppException.fromPlayerError(error); + } + } +} diff --git a/lib/src/filters/flanger_filter.dart b/lib/src/filters/flanger_filter.dart new file mode 100644 index 00000000..d24c9756 --- /dev/null +++ b/lib/src/filters/flanger_filter.dart @@ -0,0 +1,97 @@ +// ignore_for_file: public_member_api_docs + +import 'package:flutter_soloud/src/filters/filters.dart'; +import 'package:flutter_soloud/src/sound_handle.dart'; +import 'package:flutter_soloud/src/sound_hash.dart'; + +enum FlangerEnum { + wet, + delay, + freq; + + /// use iterables? + final List _mins = const [0, 0, -48]; + final List _maxs = const [1, 3, 48]; + final List _defs = const [1, 1, 0]; + + double get min => _mins[index]; + double get max => _maxs[index]; + double get def => _defs[index]; + + @override + String toString() => switch (this) { + FlangerEnum.wet => 'Wet', + FlangerEnum.delay => 'Delay', + FlangerEnum.freq => 'Freq', + }; +} + +abstract class FlangerInternal { + const FlangerInternal(SoundHash? soundHash) : _soundHash = soundHash; + + final SoundHash? _soundHash; + FilterType get filterType => FilterType.flangerFilter; + FlangerEnum get queryWet => FlangerEnum.wet; + FlangerEnum get queryDelay => FlangerEnum.delay; + FlangerEnum get queryFreq => FlangerEnum.freq; + + void activate() => filterType.activate(_soundHash); + + void deactivate() => filterType.deactivate(_soundHash); +} + +class FlangerSingle extends FlangerInternal { + FlangerSingle(super.soundHash); + + FilterParam wet({SoundHandle? soundHandle}) => FilterParam( + soundHandle, + filterType, + FlangerEnum.wet.index, + FlangerEnum.wet.min, + FlangerEnum.wet.max, + ); + + FilterParam delay({SoundHandle? soundHandle}) => FilterParam( + soundHandle, + filterType, + FlangerEnum.delay.index, + FlangerEnum.delay.min, + FlangerEnum.delay.max, + ); + + FilterParam freq({SoundHandle? soundHandle}) => FilterParam( + soundHandle, + filterType, + FlangerEnum.freq.index, + FlangerEnum.freq.min, + FlangerEnum.freq.max, + ); +} + +class FlangerGlobal extends FlangerInternal { + const FlangerGlobal() : super(null); + + FilterParam get wet => FilterParam( + null, + filterType, + FlangerEnum.wet.index, + FlangerEnum.wet.min, + FlangerEnum.wet.max, + ); + + FilterParam get delay => FilterParam( + null, + filterType, + FlangerEnum.delay.index, + FlangerEnum.delay.min, + FlangerEnum.delay.max, + ); + + FilterParam get freq => FilterParam( + null, + filterType, + FlangerEnum.freq.index, + FlangerEnum.freq.min, + FlangerEnum.freq.max, + ); +} diff --git a/lib/src/filters/freeverb_filter.dart b/lib/src/filters/freeverb_filter.dart new file mode 100644 index 00000000..69c56e28 --- /dev/null +++ b/lib/src/filters/freeverb_filter.dart @@ -0,0 +1,135 @@ +// ignore_for_file: public_member_api_docs + +import 'package:flutter_soloud/src/filters/filters.dart'; +import 'package:flutter_soloud/src/sound_handle.dart'; +import 'package:flutter_soloud/src/sound_hash.dart'; + +enum FreeverbEnum { + wet, + freeze, + roomSize, + damp, + width; + + /// use iterables? + final List _mins = const [0, 0, 0, 0, 0]; + final List _maxs = const [1, 1, 1, 1, 1]; + final List _defs = const [1, 0, 0.5, 0.5, 1]; + + double get min => _mins[index]; + double get max => _maxs[index]; + double get def => _defs[index]; + + @override + String toString() => switch (this) { + FreeverbEnum.wet => 'Wet', + FreeverbEnum.freeze => 'Freeze', + FreeverbEnum.roomSize => 'Room Size', + FreeverbEnum.damp => 'Damp', + FreeverbEnum.width => 'Width', + }; +} + +abstract class FreeverbInternal { + const FreeverbInternal(SoundHash? soundHash) : _soundHash = soundHash; + + final SoundHash? _soundHash; + FilterType get filterType => FilterType.freeverbFilter; + FreeverbEnum get queryWet => FreeverbEnum.wet; + FreeverbEnum get queryFreeze => FreeverbEnum.freeze; + FreeverbEnum get queryRoomSize => FreeverbEnum.roomSize; + FreeverbEnum get queryDamp => FreeverbEnum.damp; + FreeverbEnum get queryWidth => FreeverbEnum.width; + + void activate() => filterType.activate(_soundHash); + + void deactivate() => filterType.deactivate(_soundHash); +} + +class FreeverbSingle extends FreeverbInternal { + FreeverbSingle(super.soundHash); + + FilterParam wet({SoundHandle? soundHandle}) => FilterParam( + soundHandle, + filterType, + FreeverbEnum.wet.index, + FreeverbEnum.wet.min, + FreeverbEnum.wet.max, + ); + + FilterParam freeze({SoundHandle? soundHandle}) => FilterParam( + soundHandle, + filterType, + FreeverbEnum.freeze.index, + FreeverbEnum.freeze.min, + FreeverbEnum.freeze.max, + ); + + FilterParam roomSize({SoundHandle? soundHandle}) => FilterParam( + soundHandle, + filterType, + FreeverbEnum.roomSize.index, + FreeverbEnum.roomSize.min, + FreeverbEnum.roomSize.max, + ); + + FilterParam damp({SoundHandle? soundHandle}) => FilterParam( + soundHandle, + filterType, + FreeverbEnum.damp.index, + FreeverbEnum.damp.min, + FreeverbEnum.damp.max, + ); + + FilterParam width({SoundHandle? soundHandle}) => FilterParam( + soundHandle, + filterType, + FreeverbEnum.width.index, + FreeverbEnum.width.min, + FreeverbEnum.width.max, + ); +} + +class FreeverbGlobal extends FreeverbInternal { + const FreeverbGlobal() : super(null); + + FilterParam get wet => FilterParam( + null, + filterType, + FreeverbEnum.wet.index, + FreeverbEnum.wet.min, + FreeverbEnum.wet.max, + ); + + FilterParam get freeze => FilterParam( + null, + filterType, + FreeverbEnum.freeze.index, + FreeverbEnum.freeze.min, + FreeverbEnum.freeze.max, + ); + + FilterParam get roomSize => FilterParam( + null, + filterType, + FreeverbEnum.roomSize.index, + FreeverbEnum.roomSize.min, + FreeverbEnum.roomSize.max, + ); + + FilterParam get damp => FilterParam( + null, + filterType, + FreeverbEnum.damp.index, + FreeverbEnum.damp.min, + FreeverbEnum.damp.max, + ); + + FilterParam get width => FilterParam( + null, + filterType, + FreeverbEnum.width.index, + FreeverbEnum.width.min, + FreeverbEnum.width.max, + ); +} diff --git a/lib/src/filters/lofi_filter.dart b/lib/src/filters/lofi_filter.dart new file mode 100644 index 00000000..2d030bf8 --- /dev/null +++ b/lib/src/filters/lofi_filter.dart @@ -0,0 +1,97 @@ +// ignore_for_file: public_member_api_docs + +import 'package:flutter_soloud/src/filters/filters.dart'; +import 'package:flutter_soloud/src/sound_handle.dart'; +import 'package:flutter_soloud/src/sound_hash.dart'; + +enum LofiEnum { + wet, + samplerate, + bitdepth; + + /// use iterables? + final List _mins = const [0, 100, 0.5]; + final List _maxs = const [1, 22000, 16]; + final List _defs = const [1, 4000, 3]; + + double get min => _mins[index]; + double get max => _maxs[index]; + double get def => _defs[index]; + + @override + String toString() => switch (this) { + LofiEnum.wet => 'Wet', + LofiEnum.samplerate => 'Samplerate', + LofiEnum.bitdepth => 'Bitdepth', + }; +} + +abstract class LofiInternal { + const LofiInternal(SoundHash? soundHash) : _soundHash = soundHash; + + final SoundHash? _soundHash; + FilterType get filterType => FilterType.lofiFilter; + LofiEnum get queryWet => LofiEnum.wet; + LofiEnum get querySamplerate => LofiEnum.samplerate; + LofiEnum get queryBitdepth => LofiEnum.bitdepth; + + void activate() => filterType.activate(_soundHash); + + void deactivate() => filterType.deactivate(_soundHash); +} + +class LofiSingle extends LofiInternal { + LofiSingle(super.soundHash); + + FilterParam wet({SoundHandle? soundHandle}) => FilterParam( + soundHandle, + filterType, + LofiEnum.wet.index, + LofiEnum.wet.min, + LofiEnum.wet.max, + ); + + FilterParam samplerate({SoundHandle? soundHandle}) => FilterParam( + soundHandle, + filterType, + LofiEnum.samplerate.index, + LofiEnum.samplerate.min, + LofiEnum.samplerate.max, + ); + + FilterParam bitdepth({SoundHandle? soundHandle}) => FilterParam( + soundHandle, + filterType, + LofiEnum.bitdepth.index, + LofiEnum.bitdepth.min, + LofiEnum.bitdepth.max, + ); +} + +class LofiGlobal extends LofiInternal { + const LofiGlobal() : super(null); + + FilterParam get wet => FilterParam( + null, + filterType, + LofiEnum.wet.index, + LofiEnum.wet.min, + LofiEnum.wet.max, + ); + + FilterParam get samplerate => FilterParam( + null, + filterType, + LofiEnum.samplerate.index, + LofiEnum.samplerate.min, + LofiEnum.samplerate.max, + ); + + FilterParam get bitdepth => FilterParam( + null, + filterType, + LofiEnum.bitdepth.index, + LofiEnum.bitdepth.min, + LofiEnum.bitdepth.max, + ); +} diff --git a/lib/src/filters/pitchshift_filter.dart b/lib/src/filters/pitchshift_filter.dart new file mode 100644 index 00000000..84d1ee52 --- /dev/null +++ b/lib/src/filters/pitchshift_filter.dart @@ -0,0 +1,97 @@ +// ignore_for_file: public_member_api_docs + +import 'package:flutter_soloud/src/filters/filters.dart'; +import 'package:flutter_soloud/src/sound_handle.dart'; +import 'package:flutter_soloud/src/sound_hash.dart'; + +enum PitchShiftEnum { + wet, + shift, + semitones; + + /// use iterables? + final List _mins = const [0, 0, -36]; + final List _maxs = const [1, 3, 36]; + final List _defs = const [1, 1, 0]; + + double get min => _mins[index]; + double get max => _maxs[index]; + double get def => _defs[index]; + + @override + String toString() => switch (this) { + PitchShiftEnum.wet => 'Wet', + PitchShiftEnum.shift => 'Shift', + PitchShiftEnum.semitones => 'Semitones', + }; +} + +abstract class PitchShiftInternal { + const PitchShiftInternal(SoundHash? soundHash) : _soundHash = soundHash; + + final SoundHash? _soundHash; + FilterType get filterType => FilterType.pitchShiftFilter; + PitchShiftEnum get queryWet => PitchShiftEnum.wet; + PitchShiftEnum get queryShift => PitchShiftEnum.shift; + PitchShiftEnum get querySemitones => PitchShiftEnum.semitones; + + void activate() => filterType.activate(_soundHash); + + void deactivate() => filterType.deactivate(_soundHash); +} + +class PitchShiftSingle extends PitchShiftInternal { + PitchShiftSingle(super.soundHash); + + FilterParam wet({SoundHandle? soundHandle}) => FilterParam( + soundHandle, + filterType, + PitchShiftEnum.wet.index, + PitchShiftEnum.wet.min, + PitchShiftEnum.wet.max, + ); + + FilterParam shift({SoundHandle? soundHandle}) => FilterParam( + soundHandle, + filterType, + PitchShiftEnum.shift.index, + PitchShiftEnum.shift.min, + PitchShiftEnum.shift.max, + ); + + FilterParam semitones({SoundHandle? soundHandle}) => FilterParam( + soundHandle, + filterType, + PitchShiftEnum.semitones.index, + PitchShiftEnum.semitones.min, + PitchShiftEnum.semitones.max, + ); +} + +class PitchShiftGlobal extends PitchShiftInternal { + const PitchShiftGlobal() : super(null); + + FilterParam get wet => FilterParam( + null, + filterType, + PitchShiftEnum.wet.index, + PitchShiftEnum.wet.min, + PitchShiftEnum.wet.max, + ); + + FilterParam get shift => FilterParam( + null, + filterType, + PitchShiftEnum.shift.index, + PitchShiftEnum.shift.min, + PitchShiftEnum.shift.max, + ); + + FilterParam get semitones => FilterParam( + null, + filterType, + PitchShiftEnum.semitones.index, + PitchShiftEnum.semitones.min, + PitchShiftEnum.semitones.max, + ); +} diff --git a/lib/src/filters/robotize_filter.dart b/lib/src/filters/robotize_filter.dart new file mode 100644 index 00000000..97b31b31 --- /dev/null +++ b/lib/src/filters/robotize_filter.dart @@ -0,0 +1,97 @@ +// ignore_for_file: public_member_api_docs + +import 'package:flutter_soloud/src/filters/filters.dart'; +import 'package:flutter_soloud/src/sound_handle.dart'; +import 'package:flutter_soloud/src/sound_hash.dart'; + +enum RobotizeEnum { + wet, + frequency, + waveform; + + /// use iterables? + final List _mins = const [0, 0.1, 0]; + final List _maxs = const [1, 100, 6]; + final List _defs = const [1, 30, 0]; + + double get min => _mins[index]; + double get max => _maxs[index]; + double get def => _defs[index]; + + @override + String toString() => switch (this) { + RobotizeEnum.wet => 'Wet', + RobotizeEnum.frequency => 'Frequency', + RobotizeEnum.waveform => 'Waveform', + }; +} + +abstract class RobotizeInternal { + const RobotizeInternal(SoundHash? soundHash) : _soundHash = soundHash; + + final SoundHash? _soundHash; + FilterType get filterType => FilterType.robotizeFilter; + RobotizeEnum get queryWet => RobotizeEnum.wet; + RobotizeEnum get queryFrequency => RobotizeEnum.frequency; + RobotizeEnum get queryWaveform => RobotizeEnum.waveform; + + void activate() => filterType.activate(_soundHash); + + void deactivate() => filterType.deactivate(_soundHash); +} + +class RobotizeSingle extends RobotizeInternal { + RobotizeSingle(super.soundHash); + + FilterParam wet({SoundHandle? soundHandle}) => FilterParam( + soundHandle, + filterType, + RobotizeEnum.wet.index, + RobotizeEnum.wet.min, + RobotizeEnum.wet.max, + ); + + FilterParam frequency({SoundHandle? soundHandle}) => FilterParam( + soundHandle, + filterType, + RobotizeEnum.frequency.index, + RobotizeEnum.frequency.min, + RobotizeEnum.frequency.max, + ); + + FilterParam waveform({SoundHandle? soundHandle}) => FilterParam( + soundHandle, + filterType, + RobotizeEnum.waveform.index, + RobotizeEnum.waveform.min, + RobotizeEnum.waveform.max, + ); +} + +class RobotizeGlobal extends RobotizeInternal { + const RobotizeGlobal() : super(null); + + FilterParam get wet => FilterParam( + null, + filterType, + RobotizeEnum.wet.index, + RobotizeEnum.wet.min, + RobotizeEnum.wet.max, + ); + + FilterParam get frequency => FilterParam( + null, + filterType, + RobotizeEnum.frequency.index, + RobotizeEnum.frequency.min, + RobotizeEnum.frequency.max, + ); + + FilterParam get waveform => FilterParam( + null, + filterType, + RobotizeEnum.waveform.index, + RobotizeEnum.waveform.min, + RobotizeEnum.waveform.max, + ); +} diff --git a/lib/src/filters/wave_shaper_filter.dart b/lib/src/filters/wave_shaper_filter.dart new file mode 100644 index 00000000..ec14dce9 --- /dev/null +++ b/lib/src/filters/wave_shaper_filter.dart @@ -0,0 +1,78 @@ +// ignore_for_file: public_member_api_docs + +import 'package:flutter_soloud/src/filters/filters.dart'; +import 'package:flutter_soloud/src/sound_handle.dart'; +import 'package:flutter_soloud/src/sound_hash.dart'; + +enum WaveShaperEnum { + wet, + amount; + + /// use iterables? + final List _mins = const [0, -1]; + final List _maxs = const [1, 1]; + final List _defs = const [1, 0]; + + double get min => _mins[index]; + double get max => _maxs[index]; + double get def => _defs[index]; + + @override + String toString() => switch (this) { + WaveShaperEnum.wet => 'Wet', + WaveShaperEnum.amount => 'Amount', + }; +} + +abstract class WaveShaperInternal { + const WaveShaperInternal(SoundHash? soundHash) : _soundHash = soundHash; + + final SoundHash? _soundHash; + FilterType get filterType => FilterType.waveShaperFilter; + WaveShaperEnum get queryWet => WaveShaperEnum.wet; + WaveShaperEnum get queryAmount => WaveShaperEnum.amount; + + void activate() => filterType.activate(_soundHash); + + void deactivate() => filterType.deactivate(_soundHash); +} + +class WaveShaperSingle extends WaveShaperInternal { + WaveShaperSingle(super.soundHash); + + FilterParam wet({SoundHandle? soundHandle}) => FilterParam( + soundHandle, + filterType, + WaveShaperEnum.wet.index, + WaveShaperEnum.wet.min, + WaveShaperEnum.wet.max, + ); + + FilterParam amount({SoundHandle? soundHandle}) => FilterParam( + soundHandle, + filterType, + WaveShaperEnum.amount.index, + WaveShaperEnum.amount.min, + WaveShaperEnum.amount.max, + ); +} + +class WaveShaperGlobal extends WaveShaperInternal { + const WaveShaperGlobal() : super(null); + + FilterParam get wet => FilterParam( + null, + filterType, + WaveShaperEnum.wet.index, + WaveShaperEnum.wet.min, + WaveShaperEnum.wet.max, + ); + + FilterParam get amount => FilterParam( + null, + filterType, + WaveShaperEnum.amount.index, + WaveShaperEnum.amount.min, + WaveShaperEnum.amount.max, + ); +} diff --git a/lib/src/soloud.dart b/lib/src/soloud.dart index fa71d43f..e5243453 100644 --- a/lib/src/soloud.dart +++ b/lib/src/soloud.dart @@ -10,7 +10,7 @@ import 'package:flutter_soloud/src/bindings/bindings_player.dart'; import 'package:flutter_soloud/src/bindings/soloud_controller.dart'; import 'package:flutter_soloud/src/enums.dart'; import 'package:flutter_soloud/src/exceptions/exceptions.dart'; -import 'package:flutter_soloud/src/filter_params.dart'; +import 'package:flutter_soloud/src/filters/filters.dart'; import 'package:flutter_soloud/src/sound_handle.dart'; import 'package:flutter_soloud/src/sound_hash.dart'; import 'package:flutter_soloud/src/utils/loader.dart'; @@ -47,8 +47,59 @@ interface class SoLoud { static final Logger _log = Logger('flutter_soloud.SoLoud'); + /// The controller. final _controller = SoLoudController(); + /// This can be used to access all the available filter functionalities + /// for the player output (formerly called global filters). + /// + /// ```dart + /// await SoLoud.instance.init(); + /// ... + /// /// activate the filter. + /// SoLoud.instance.filters.echoFilter.activate(); + /// + /// /// Later on, deactivate it. + /// SoLoud.instance.filters.echoFilter.deactivate(); + /// ``` + /// + /// It's possible to get and set filter parameters: + /// ```dart + /// /// Set + /// SoLoud.instance.filters.echoFilter.delay.value = 0.6; + /// /// Get + /// final delayValue = SoLoud.instance.filters.echoFilter.delay.value; + /// ``` + /// or fade/oscillate a parameter: + /// ```dart + /// /// Fade + /// SoLoud.instance.filters.echoFilter.delay + /// .fadeFilterParameter( + /// to: 3, + /// time: const Duration(milliseconds: 2500), + /// ); + /// /// Oscillate + /// SoLoud.instance.filters.echoFilter.delay + /// .oscillateFilterParameter( + /// from: 0.4, + /// to: 1.8, + /// time: const Duration(milliseconds: 2500), + /// ); + /// ``` + /// + /// It's possible to query filter parameters: + /// ```dart + /// final delayParams = SoLoud.instance.filters.echoFilter.queryDelay; + /// ``` + /// + /// Now with "delayParams" you have access to: + /// - `toString()` gives the "human readable" parameter name. + /// - `min` which represent the "shift" minimum accepted value. + /// - `max` which represent the "shift" maximum accepted value. + /// - `def` which represent the "shift" default value. + /// + late final filters = const FiltersGlobal(); + /// The singleton instance of [SoLoud]. Only one SoLoud instance /// can exist in C++ land, so – for consistency and to avoid confusion /// – only one instance can exist in Dart land. @@ -1676,6 +1727,7 @@ interface class SoLoud { /// /// Returns `-1` if the filter is not active. Otherwise, returns /// the index of the given filter. + @Deprecated('Please, to manage global filters use SoLoud.filters instead') int isFilterActive(FilterType filterType) { final ret = _controller.soLoudFFI.isFilterActive(filterType); if (ret.error != PlayerErrors.noError) { @@ -1688,6 +1740,7 @@ interface class SoLoud { /// Gets parameters of the given [filterType]. /// /// Returns the list of param names. + @Deprecated('Please, to manage global filters use SoLoud.filters instead') List getFilterParamNames(FilterType filterType) { final ret = _controller.soLoudFFI.getFilterParamNames(filterType); if (ret.error != PlayerErrors.noError) { @@ -1703,6 +1756,7 @@ interface class SoLoud { /// concurrent filter is reached (default max filter is 8). /// Throws [SoLoudFilterAlreadyAddedException] when trying to add a filter /// that has already been added. + @Deprecated('Please, to manage global filters use SoLoud.filters instead') void addGlobalFilter(FilterType filterType) { final error = _controller.soLoudFFI.addFilter(filterType); if (error != PlayerErrors.noError) { @@ -1712,6 +1766,7 @@ interface class SoLoud { } /// Removes [filterType] from all sounds. + @Deprecated('Please, to manage global filters use SoLoud.filters instead') void removeGlobalFilter(FilterType filterType) { final error = _controller.soLoudFFI.removeFilter(filterType); if (error != PlayerErrors.noError) { @@ -1729,6 +1784,7 @@ interface class SoLoud { /// applyed to the global filter. /// [filterType] filter to modify a param. /// Returns [PlayerErrors.noError] if no errors. + @Deprecated('Please, to manage global filters use SoLoud.filters instead') void setGlobalFilterParameter( FilterType filterType, int attributeId, @@ -1747,7 +1803,7 @@ interface class SoLoud { /// Set the effect parameter with id [attributeId] of [filterType] /// with [value] value. - @Deprecated('Please use setGlobalFilterParameter class instead.') + @Deprecated('Please, to manage global filters use SoLoud.filters instead') void setFilterParameter( FilterType filterType, int attributeId, @@ -1763,6 +1819,7 @@ interface class SoLoud { /// it gets the global filter value. /// [filterType] the filter to modify a parameter. /// Returns the value of the parameter. + @Deprecated('Please, to manage global filters use SoLoud.filters instead') double getGlobalFilterParameter( FilterType filterType, int attributeId, @@ -1780,11 +1837,11 @@ interface class SoLoud { } /// Get the effect parameter value with id [attributeId] of [filterType]. - @Deprecated('Please use getGlobalFilterParameter class instead.') + @Deprecated('Please, to manage global filters use SoLoud.filters instead') double getFilterParameter( FilterType filterType, int attributeId, { - SoundHandle handle = const SoundHandle(0), + SoundHandle handle = const SoundHandle.error(), }) => getGlobalFilterParameter(filterType, attributeId); diff --git a/lib/src/worker/worker.dart b/lib/src/worker/worker.dart index 20704d5e..7e2825ae 100644 --- a/lib/src/worker/worker.dart +++ b/lib/src/worker/worker.dart @@ -14,9 +14,10 @@ class WorkerController { /// Spawn a new web Worker with the given JS source (not used now). static Future spawn(String path) async { + final JSAny newPath = (path.endsWith('.dart') ? '$path.js' : path).toJS; final controller = WorkerController() .._outputController = StreamController() - .._worker = web.Worker(path.endsWith('.dart') ? '$path.js' : path); + .._worker = web.Worker(newPath); controller._worker?.onmessage = ((web.MessageEvent event) { controller._outputController?.add(event.data.dartify()); diff --git a/linux/CMakeLists.txt b/linux/CMakeLists.txt index 86c51d26..b424d427 100644 --- a/linux/CMakeLists.txt +++ b/linux/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.10) set(PROJECT_NAME "flutter_soloud") project(${PROJECT_NAME} LANGUAGES CXX) -set(CMAKE_CXX_STANDARD 14) +set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) # set(CMAKE_CXX_EXTENSIONS OFF) # set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -v -fvisibility=hidden") @@ -25,7 +25,7 @@ if (MSVC) add_definitions (-D_CRT_SECURE_NO_WARNINGS) endif() if (NOT DEFINED LIB_POSTFIX) - set (LIB_POSTFIX "") + set (LIB_POSTFIX "") endif () include_directories(${SRC_DIR}/soloud/include) @@ -43,6 +43,8 @@ list(APPEND PLUGIN_SOURCES "${SRC_DIR}/analyzer.cpp" "${SRC_DIR}/synth/basic_wave.cpp" "${SRC_DIR}/filters/filters.cpp" + "${SRC_DIR}/filters/pitch_shift_filter.cpp" + "${SRC_DIR}/filters/smbPitchShift.cpp" ${TARGET_SOURCES} ) @@ -60,7 +62,7 @@ target_include_directories(${PLUGIN_NAME} INTERFACE "${CMAKE_CURRENT_SOURCE_DIR} target_link_libraries(${PLUGIN_NAME} PRIVATE flutter) target_link_libraries(${PLUGIN_NAME} PRIVATE PkgConfig::GTK) -target_compile_options("${PLUGIN_NAME}" PRIVATE -Wall -Wno-error -fPIC) # -ldl -lpthread -lm +target_compile_options("${PLUGIN_NAME}" PRIVATE -Wall -Wno-error -fPIC -msse3 -msse2) # -ldl -lpthread -lm # List of absolute paths to libraries that should be bundled with the plugin. set(flutter_soloud_bundled_libraries diff --git a/macos/flutter_soloud.podspec b/macos/flutter_soloud.podspec index 51a48c20..2f4c572c 100755 --- a/macos/flutter_soloud.podspec +++ b/macos/flutter_soloud.podspec @@ -20,7 +20,7 @@ Flutter audio plugin using SoLoud library and FFI s.source = { :path => '.' } s.source_files = 'Classes/**/*' s.dependency 'FlutterMacOS' - s.platform = :osx, '10.11' + s.platform = :osx, '10.15' s.pod_target_xcconfig = { # Enable equivalent of '-Isrc/include' to make '#include ' work @@ -30,7 +30,9 @@ Flutter audio plugin using SoLoud library and FFI ], 'GCC_PREPROCESSOR_DEFINITIONS' => '$(inherited)', 'DEFINES_MODULE' => 'YES', - 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386' + 'EXCLUDED_ARCHS[sdk=iphonesimulator*]' => 'i386', + "CLANG_CXX_LANGUAGE_STANDARD" => "c++17", + "CLANG_CXX_LIBRARY" => "libc++" } s.swift_version = '5.0' s.ios.framework = ['AudioToolbox', 'AVFAudio'] diff --git a/pubspec.yaml b/pubspec.yaml index 5254cda2..021c9b2a 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -23,7 +23,7 @@ screenshots: path: img/screenshot.png environment: - sdk: '>=3.3.0 <4.0.0' + sdk: '>=3.4.0 <4.0.0' flutter: '>=3.3.0' dependencies: @@ -36,7 +36,7 @@ dependencies: path: ^1.9.0 path_provider: ^2.1.3 plugin_platform_interface: ^2.0.2 - web: ^0.5.1 + web: ^1.0.0 dev_dependencies: ffigen: ^12.0.0 diff --git a/src/active_sound.h b/src/active_sound.h index ce70879a..4fbcdf22 100644 --- a/src/active_sound.h +++ b/src/active_sound.h @@ -15,7 +15,7 @@ class Filters; /// but this can be adjusted at runtime typedef struct ActiveSound { - std::shared_ptr sound; + std::unique_ptr sound; SoundType soundType; std::vector handle; std::unique_ptr filters; diff --git a/src/bindings.cpp b/src/bindings.cpp index 539fe78b..aed7835b 100644 --- a/src/bindings.cpp +++ b/src/bindings.cpp @@ -1188,6 +1188,8 @@ extern "C" else { *filterValue = s->filters.get()->getFilterParams(handle, filterType, attributeId); + if (!(isnormal(*filterValue) || isnan(*filterValue))) + return filterParameterGetError; if (*filterValue == 9999.0f) return filterNotFound; return noError; diff --git a/src/enums.h b/src/enums.h index 200a0c87..27458158 100644 --- a/src/enums.h +++ b/src/enums.h @@ -42,8 +42,10 @@ typedef enum PlayerErrors filterAlreadyAdded = 15, /// Player already inited. playerAlreadyInited = 16, - /// Audio handle is not found + /// Audio handle is not found. soundHandleNotFound = 17, + /// Error getting filter parameter. + filterParameterGetError = 18 } PlayerErrors_t; /// Possible capture errors @@ -89,7 +91,8 @@ typedef enum FilterType BassboostFilter, WaveShaperFilter, RobotizeFilter, - FreeverbFilter + FreeverbFilter, + PitchShiftFilter } FilterType_t; #endif // ENUMS_H \ No newline at end of file diff --git a/src/filters/filters.cpp b/src/filters/filters.cpp index 2de459bf..b70e238d 100644 --- a/src/filters/filters.cpp +++ b/src/filters/filters.cpp @@ -5,23 +5,18 @@ #include Filters::Filters(SoLoud::Soloud *soloud, ActiveSound *sound) - : mSoloud(soloud), mSound(sound), filters({}) {} + : mSoloud(soloud), mSound(sound) {} Filters::~Filters() {} int Filters::isFilterActive(FilterType filter) { - auto it = std::find(filters.begin(), filters.end(), filter); - - if (it != filters.end()) - { - int index = it - filters.begin(); - return index; - } - else + for (int i = 0; i < filters.size(); i++) { - return -1; + if (filters[i].get()->type == filter) + return i; } + return -1; } std::vector Filters::getFilterParamNames(FilterType filterType) @@ -119,6 +114,16 @@ std::vector Filters::getFilterParamNames(FilterType filterType) } } break; + case PitchShiftFilter: + { + PitchShift f; + int nParams = f.getParamCount(); + for (int i = 0; i < nParams; i++) + { + ret.push_back(f.getParamName(i)); + } + } + break; } return ret; @@ -126,7 +131,9 @@ std::vector Filters::getFilterParamNames(FilterType filterType) PlayerErrors Filters::addFilter(FilterType filterType) { - if ((int)filters.size() >= FILTERS_PER_STREAM) + int filtersSize = (int)(filters.size()); + + if (filtersSize >= FILTERS_PER_STREAM) return maxNumberOfFiltersReached; // Check if the new filter is already here. @@ -134,93 +141,55 @@ PlayerErrors Filters::addFilter(FilterType filterType) if (isFilterActive(filterType) >= 0) return filterAlreadyAdded; - const unsigned int filtersSize = static_cast(filters.size()); + std::unique_ptr newFilter; switch (filterType) { case BiquadResonantFilter: - if (!mBiquadResonantFilter) - mBiquadResonantFilter = std::make_unique(); - if (mSound == nullptr) - mSoloud->setGlobalFilter(filtersSize, mBiquadResonantFilter.get()); - else - mSound->sound.get()->setFilter(filtersSize, mBiquadResonantFilter.get()); - filters.push_back({filterType, static_cast(mBiquadResonantFilter.get())}); + newFilter = std::make_unique(); break; case EqFilter: - if (!mEqFilter) - mEqFilter = std::make_unique(); - if (mSound == nullptr) - mSoloud->setGlobalFilter(filtersSize, mEqFilter.get()); - else - mSound->sound.get()->setFilter(filtersSize, mEqFilter.get()); - filters.push_back({filterType, static_cast(mEqFilter.get())}); + newFilter = std::make_unique(); break; case EchoFilter: - if (!mEchoFilter) - mEchoFilter = std::make_unique(); - if (mSound == nullptr) - mSoloud->setGlobalFilter(filtersSize, mEchoFilter.get()); - else - mSound->sound.get()->setFilter(filtersSize, mEchoFilter.get()); - filters.push_back({filterType, static_cast(mEchoFilter.get())}); + newFilter = std::make_unique(); break; case LofiFilter: - if (!mLofiFilter) - mLofiFilter = std::make_unique(); - if (mSound == nullptr) - mSoloud->setGlobalFilter(filtersSize, mLofiFilter.get()); - else - mSound->sound.get()->setFilter(filtersSize, mLofiFilter.get()); - filters.push_back({filterType, static_cast(mLofiFilter.get())}); + newFilter = std::make_unique(); break; case FlangerFilter: - if (!mFlangerFilter) - mFlangerFilter = std::make_unique(); - if (mSound == nullptr) - mSoloud->setGlobalFilter(filtersSize, mFlangerFilter.get()); - else - mSound->sound.get()->setFilter(filtersSize, mFlangerFilter.get()); - filters.push_back({filterType, static_cast(mFlangerFilter.get())}); + newFilter = std::make_unique(); break; case BassboostFilter: - if (!mBassboostFilter) - mBassboostFilter = std::make_unique(); - if (mSound == nullptr) - mSoloud->setGlobalFilter(filtersSize, mBassboostFilter.get()); - else - mSound->sound.get()->setFilter(filtersSize, mBassboostFilter.get()); - filters.push_back({filterType, static_cast(mBassboostFilter.get())}); + newFilter = std::make_unique(); break; case WaveShaperFilter: - if (!mWaveShaperFilter) - mWaveShaperFilter = std::make_unique(); - if (mSound == nullptr) - mSoloud->setGlobalFilter(filtersSize, mWaveShaperFilter.get()); - else - mSound->sound.get()->setFilter(filtersSize, mWaveShaperFilter.get()); - filters.push_back({filterType, static_cast(mWaveShaperFilter.get())}); + newFilter = std::make_unique(); break; case RobotizeFilter: - if (!mRobotizeFilter) - mRobotizeFilter = std::make_unique(); - if (mSound == nullptr) - mSoloud->setGlobalFilter(filtersSize, mRobotizeFilter.get()); - else - mSound->sound.get()->setFilter(filtersSize, mRobotizeFilter.get()); - filters.push_back({filterType, static_cast(mRobotizeFilter.get())}); + newFilter = std::make_unique(); break; case FreeverbFilter: - if (!mFreeverbFilter) - mFreeverbFilter = std::make_unique(); - if (mSound == nullptr) - mSoloud->setGlobalFilter(filtersSize, mFreeverbFilter.get()); - else - mSound->sound.get()->setFilter(filtersSize, mFreeverbFilter.get()); - filters.push_back({filterType, static_cast(mFreeverbFilter.get())}); + newFilter = std::make_unique(); + break; + case PitchShiftFilter: + newFilter = std::make_unique(); break; default: return filterNotFound; } + + if (mSound == nullptr) + { + mSoloud->setGlobalFilter(filtersSize, newFilter.get()); + } + else + { + mSound->sound.get()->setFilter(filtersSize, newFilter.get()); + } + + std::unique_ptr nfo = std::make_unique(filterType, std::move(newFilter)); + filters.push_back(std::move(nfo)); + return noError; } @@ -231,38 +200,9 @@ bool Filters::removeFilter(FilterType filterType) if (index < 0) return false; - // TODO check if also mEchoFilter is disposed + // TODO: single filter mSoloud->setGlobalFilter(index, 0); - switch (filterType) - { - case BiquadResonantFilter: - mBiquadResonantFilter.reset(); - break; - case EqFilter: - mEqFilter.reset(); - break; - case EchoFilter: - mEchoFilter.reset(); - break; - case LofiFilter: - mLofiFilter.reset(); - break; - case FlangerFilter: - mFlangerFilter.reset(); - break; - case BassboostFilter: - mBassboostFilter.reset(); - break; - case WaveShaperFilter: - mWaveShaperFilter.reset(); - break; - case RobotizeFilter: - mRobotizeFilter.reset(); - break; - case FreeverbFilter: - mFreeverbFilter.reset(); - break; - } + filters[index].get()->filter.reset(); /// shift filters down by 1 from [index] for (int i = index; i < filters.size() - 1; i++) @@ -272,9 +212,9 @@ bool Filters::removeFilter(FilterType filterType) else mSound->sound.get()->setFilter(i + 1, 0); if (mSound == nullptr) - mSoloud->setGlobalFilter(i, filters[i + 1].filter); + mSoloud->setGlobalFilter(i, filters[i + 1].get()->filter.get()); else - mSound->sound.get()->setFilter(i, filters[i + 1].filter); + mSound->sound.get()->setFilter(i, filters[i + 1].get()->filter.get()); } /// remove the filter from the list filters.erase(filters.begin() + index); @@ -287,7 +227,6 @@ void Filters::setFilterParams(SoLoud::handle handle, FilterType filterType, int int index = isFilterActive(filterType); if (index < 0) return; - mSoloud->setFilterParameter(handle, index, attributeId, value); } diff --git a/src/filters/filters.h b/src/filters/filters.h index cf6b7986..154a9bb2 100644 --- a/src/filters/filters.h +++ b/src/filters/filters.h @@ -5,6 +5,7 @@ #include "soloud.h" #include "soloud_filter.h" +#include "pitch_shift_filter.h" #include "soloud_biquadresonantfilter.h" // #include "soloud_duckfilter.h" #include "soloud_eqfilter.h" @@ -25,7 +26,11 @@ struct FilterObject { FilterType type; - SoLoud::Filter *filter; + std::unique_ptr filter; + + FilterObject(FilterType t, std::unique_ptr f) + : type(t), filter(std::move(f)) {} + bool operator==(FilterType const &i) { return (i == type); @@ -35,9 +40,6 @@ struct FilterObject /// Class to manage global filters. class Filters { - /// TODO(marco): Soloud.setGlobalFilter() - /// Sets, or clears, the global filter. - /// /// Setting the global filter to NULL will clear the global filter. /// The default maximum number of global filters active is 4, but this /// can be changed in a global constant in soloud.h (and rebuilding SoLoud). @@ -78,23 +80,7 @@ class Filters /// The sound to manage filters for. If null the filters are managed globally. ActiveSound *mSound; - std::vector filters; - - std::unique_ptr mBiquadResonantFilter; - /// not yet available - // std::unique_ptr mDuckFilter; - std::unique_ptr mEqFilter; - std::unique_ptr mEchoFilter; - std::unique_ptr mLofiFilter; - std::unique_ptr mFlangerFilter; - /// not yet available - // std::unique_ptr mDCRemovalFilter; - /// not yet available - std::unique_ptr mFFTFilter; - std::unique_ptr mBassboostFilter; - std::unique_ptr mWaveShaperFilter; - std::unique_ptr mRobotizeFilter; - std::unique_ptr mFreeverbFilter; + std::vector> filters; }; #endif // PLAYER_H diff --git a/src/filters/pitch_shift_filter.cpp b/src/filters/pitch_shift_filter.cpp new file mode 100644 index 00000000..ebb80b65 --- /dev/null +++ b/src/filters/pitch_shift_filter.cpp @@ -0,0 +1,169 @@ +#include +#include +#include + +#include "pitch_shift_filter.h" + +PitchShiftInstance::PitchShiftInstance(PitchShift *aParent) +{ + mParent = aParent; + pitchShift = CSmbPitchShift(); + initParams(3); + mParam[PitchShift::SHIFT] = aParent->mShift; + mParam[PitchShift::SEMITONES] = aParent->mSemitones; +} + +void PitchShiftInstance::filter( + float *aBuffer, + unsigned int aSamples, + unsigned int aBufferSize, + unsigned int aChannels, + float aSamplerate, + SoLoud::time aTime) +{ + updateParams(aTime); + float *in = (float *)calloc((aSamples), sizeof(float)); + for (int j = 0; j < aSamples; j++) + { + // shrink channels to one to feed smbPitchShift. + for (int n = 0; n < aChannels; n++) + in[j] += aBuffer[j + aSamples * n]; + in[j] /= (float)aChannels; + } + +#if defined(SOLOUD_SSE_INTRINSICS) + // trying to get more quality when SIMD is enabled. + pitchShift.smbPitchShift(mParam[PitchShift::SHIFT], aSamples, 4096, 16, aSamplerate, in, in); +#else + pitchShift.smbPitchShift(mParam[PitchShift::SHIFT], aSamples, 2048, 8, aSamplerate, in, in); +#endif + for (int j = 0; j < aSamples; j++) + { + aBuffer[j] = aBuffer[j] * (1.0f - mParam[PitchShift::WET]) + in[j] * mParam[PitchShift::WET]; // L chan + for (int n = 1; n < aChannels; n++) + aBuffer[j + aSamples] = aBuffer[j + aSamples] * (1.0f - mParam[PitchShift::WET]) + in[j] * mParam[PitchShift::WET]; // R chan + } + free(in); +} + +void PitchShiftInstance::setFilterParameter(unsigned int aAttributeId, float aValue) +{ + if (aAttributeId >= mNumParams) + return; + + mParamFader[aAttributeId].mActive = 0; + + switch (aAttributeId) + { + case PitchShift::WET: + if (aValue < 0.f || aValue > 1.f) + break; + mParam[PitchShift::WET] = aValue; + break; + case PitchShift::SHIFT: + if (aValue < mParent->getParamMin(PitchShift::SHIFT) || + aValue > mParent->getParamMax(PitchShift::SHIFT)) + return; + mParam[PitchShift::SHIFT] = aValue; + mParam[PitchShift::SEMITONES] = 12 * log2f(aValue); + break; + case PitchShift::SEMITONES: + if (aValue < mParent->getParamMin(PitchShift::SEMITONES) || + aValue > mParent->getParamMax(PitchShift::SEMITONES)) + return; + mParam[PitchShift::SEMITONES] = aValue; + mParam[PitchShift::SHIFT] = pow(2., aValue / 12.); + break; + } + + mParamChanged |= 1 << aAttributeId; +} + +SoLoud::result PitchShift::setParam(unsigned int aParamIndex, float aValue) +{ + switch (aParamIndex) + { + case WET: + if (aValue < 0.f || aValue > 1.f) + break; + mWet = aValue; + break; + case SHIFT: + if (aValue < getParamMin(SHIFT) || aValue > getParamMax(SHIFT)) + return SoLoud::INVALID_PARAMETER; + mShift = aValue; + mSemitones = 12 * log2f(mShift); + break; + case SEMITONES: + if (aValue < getParamMin(SEMITONES) || aValue > getParamMax(SEMITONES)) + return SoLoud::INVALID_PARAMETER; + mSemitones = aValue; + mShift = pow(2., mSemitones / 12.); + break; + } + return SoLoud::SO_NO_ERROR; +} + +int PitchShift::getParamCount() +{ + return 3; +} + +const char *PitchShift::getParamName(unsigned int aParamIndex) +{ + switch (aParamIndex) + { + case WET: + return "Wet"; + case SHIFT: + return "Shift"; + case SEMITONES: + return "Semitones"; + } + return "Wet"; +} + +unsigned int PitchShift::getParamType(unsigned int aParamIndex) +{ + return FLOAT_PARAM; +} + +float PitchShift::getParamMax(unsigned int aParamIndex) +{ + switch (aParamIndex) + { + case WET: + return 1.f; + case SHIFT: + return 3.f; + case SEMITONES: + return 36.f; + } + return 1; +} + +float PitchShift::getParamMin(unsigned int aParamIndex) +{ + switch (aParamIndex) + { + case WET: + return 0.f; + case SHIFT: + return 0.1f; + case SEMITONES: + return -36.f; + } + return 1; +} + +PitchShift::PitchShift() +{ + mWet = 1.0f; + mShift = 1.0f; + mSemitones = 0.0f; +} + +SoLoud::FilterInstance *PitchShift::createInstance() +{ + return new PitchShiftInstance(this); +} diff --git a/src/filters/pitch_shift_filter.h b/src/filters/pitch_shift_filter.h new file mode 100644 index 00000000..8a26c75b --- /dev/null +++ b/src/filters/pitch_shift_filter.h @@ -0,0 +1,48 @@ +#ifndef PITCH_SHIFT_FILTER_H +#define PITCH_SHIFT_FILTER_H + +#include "soloud.h" +#include "smbPitchShift.h" + +class PitchShift; + +class PitchShiftInstance : public SoLoud::FilterInstance +{ + CSmbPitchShift pitchShift; + PitchShift *mParent; + +public: + virtual void filter( + float *aBuffer, + unsigned int aSamples, + unsigned int aBufferSize, + unsigned int aChannels, + float aSamplerate, + SoLoud::time aTime); + PitchShiftInstance(PitchShift *aParent); + void setFilterParameter(unsigned int aAttributeId, float aValue); +}; + +class PitchShift : public SoLoud::Filter { + +public: + enum FILTERATTRIBUTE + { + WET = 0, + SHIFT = 1, + SEMITONES = 2 + }; + float mWet; + float mShift; + float mSemitones; + virtual int getParamCount(); + virtual const char *getParamName(unsigned int aParamIndex); + virtual unsigned int getParamType(unsigned int aParamIndex); + virtual float getParamMax(unsigned int aParamIndex); + virtual float getParamMin(unsigned int aParamIndex); + SoLoud::result setParam(unsigned int aParamIndex, float aValue); + virtual SoLoud::FilterInstance *createInstance(); + PitchShift(); +}; + +#endif \ No newline at end of file diff --git a/src/filters/smbPitchShift.cpp b/src/filters/smbPitchShift.cpp new file mode 100644 index 00000000..24f69a50 --- /dev/null +++ b/src/filters/smbPitchShift.cpp @@ -0,0 +1,461 @@ +/**************************************************************************** +* +* NAME: smbPitchShift.cpp +* VERSION: 1.2 +* HOME URL: http://blogs.zynaptiq.com/bernsee +* KNOWN BUGS: none +* +* SYNOPSIS: Routine for doing pitch shifting while maintaining +* duration using the Short Time Fourier Transform. +* +* DESCRIPTION: The routine takes a pitchShift factor value which is between 0.5 +* (one octave down) and 2. (one octave up). A value of exactly 1 does not change +* the pitch. numSampsToProcess tells the routine how many samples in indata[0... +* numSampsToProcess-1] should be pitch shifted and moved to outdata[0 ... +* numSampsToProcess-1]. The two buffers can be identical (ie. it can process the +* data in-place). fftFrameSize defines the FFT frame size used for the +* processing. Typical values are 1024, 2048 and 4096. It may be any value <= +* MAX_FRAME_LENGTH but it MUST be a power of 2. osamp is the STFT +* oversampling factor which also determines the overlap between adjacent STFT +* frames. It should at least be 4 for moderate scaling ratios. A value of 32 is +* recommended for best quality. sampleRate takes the sample rate for the signal +* in unit Hz, ie. 44100 for 44.1 kHz audio. The data passed to the routine in +* indata[] should be in the range [-1.0, 1.0), which is also the output range +* for the data, make sure you scale the data accordingly (for 16bit signed integers +* you would have to divide (and multiply) by 32768). +* +* COPYRIGHT 1999-2015 Stephan M. Bernsee +* +* The Wide Open License (WOL) +* +* Permission to use, copy, modify, distribute and sell this software and its +* documentation for any purpose is hereby granted without fee, provided that +* the above copyright notice and this license appear in all source copies. +* THIS SOFTWARE IS PROVIDED "AS IS" WITHOUT EXPRESS OR IMPLIED WARRANTY OF +* ANY KIND. See http://www.dspguru.com/wol.htm for more information. +* +*****************************************************************************/ + +#include "../common.h" +#include "smbPitchShift.h" +#include "soloud.h" + +#include +#include +#include +#include + +namespace { + +#if defined(SOLOUD_SSE_INTRINSICS) +#include +#include +#include + +FFI_PLUGIN_EXPORT void smbFft(float *fftBuffer, long fftFrameSize, long sign) +/* + FFT routine, (C)1996 S.M.Bernsee. Sign = -1 is FFT, 1 is iFFT (inverse) + Fills fftBuffer[0...2*fftFrameSize-1] with the Fourier transform of the + time domain data in fftBuffer[0...2*fftFrameSize-1]. The FFT array takes + and returns the cosine and sine parts in an interleaved manner, ie. + fftBuffer[0] = cosPart[0], fftBuffer[1] = sinPart[0], asf. fftFrameSize + must be a power of 2. It expects a complex input signal (see footnote 2), + ie. when working with 'common' audio signals our input signal has to be + passed as {in[0],0.,in[1],0.,in[2],0.,...} asf. In that case, the transform + of the frequencies of interest is in fftBuffer[0...fftFrameSize]. +*/ +{ + const auto number = 2 * fftFrameSize - 2; + for (long i = 2, j = 0; i < number; i += 2) { + for (long bitm = fftFrameSize; bitm != 1; bitm >>= 1) + { + if (j & bitm) + j &= ~bitm; + else + { + j |= bitm; + break; + } + } + if (i < j) { + auto p1 = fftBuffer+i; + auto p2 = fftBuffer+j; + auto temp = *p1; *(p1++) = *p2; + *(p2++) = temp; temp = *p1; + *p1 = *p2; *p2 = temp; + } + } + for (long k = 0, le = 2; k < (long)(log(fftFrameSize)/log(2.)+.5); k++) { + le <<= 1; + const auto le2 = le>>1; + /* __declspec(align(8)) */ struct { float r, i; } u{ 1.0, 0.0 }; + + const float arg = M_PI / (le2>>1); + const float wr = cos(arg); + const float wi = sign*sin(arg); + for (long j = 0; j < le2; j += 2) { + auto p1r = fftBuffer+j; + auto p2r = p1r+le2; + + __m128 u_ = _mm_castpd_ps(_mm_movedup_pd(_mm_load_sd((const double*)&u))); + + __m128 ldup = _mm_moveldup_ps(u_); + __m128 hdup = _mm_movehdup_ps(u_); + + long i = j; + for (; i < 2*fftFrameSize - le; i += le * 2) + { + __m128 p2 = _mm_loadh_pi(_mm_castpd_ps(_mm_load_sd((const double*)p2r)), (const __m64*)(p2r + le)); + __m128 part1 = _mm_mul_ps(p2, ldup); + __m128 part2 = _mm_mul_ps(p2, hdup); + part2 = _mm_castsi128_ps(_mm_shuffle_epi32(_mm_castps_si128(part2), _MM_SHUFFLE(2, 3, 0, 1))); + __m128 t = _mm_addsub_ps(part1, part2); + + __m128 p1 = _mm_loadh_pi(_mm_castpd_ps(_mm_load_sd((const double*)p1r)), (const __m64*)(p1r + le)); + __m128 buffer = _mm_sub_ps(p1, t); + + _mm_storeh_pi((__m64*)(p2r + le), buffer); + _mm_storel_pi((__m64*)p2r, buffer); + + buffer = _mm_add_ps(p1, t); + _mm_storeh_pi((__m64*)(p1r + le), buffer); + _mm_storel_pi((__m64*)p1r, buffer); + + p1r += le * 2; + p2r += le * 2; + } + + + if (i < 2 * fftFrameSize) { + const auto p1i = p1r + 1; + const auto p2i = p2r + 1; + const float tr = *p2r * u.r - *p2i * u.i; + const float ti = *p2r * u.i + *p2i * u.r; + *p2r = *p1r - tr; *p2i = *p1i - ti; + *p1r += tr; *p1i += ti; + } + + + const float tr = u.r*wr - u.i*wi; + u.i = u.r*wi + u.i*wr; + u.r = tr; + } + } +} +#else +/// Without SIMD +FFI_PLUGIN_EXPORT void smbFft(float *fftBuffer, long fftFrameSize, long sign) +/* + FFT routine, (C)1996 S.M.Bernsee. Sign = -1 is FFT, 1 is iFFT (inverse) + Fills fftBuffer[0...2*fftFrameSize-1] with the Fourier transform of the + time domain data in fftBuffer[0...2*fftFrameSize-1]. The FFT array takes + and returns the cosine and sine parts in an interleaved manner, ie. + fftBuffer[0] = cosPart[0], fftBuffer[1] = sinPart[0], asf. fftFrameSize + must be a power of 2. It expects a complex input signal (see footnote 2), + ie. when working with 'common' audio signals our input signal has to be + passed as {in[0],0.,in[1],0.,in[2],0.,...} asf. In that case, the transform + of the frequencies of interest is in fftBuffer[0...fftFrameSize]. +*/ +{ + float wr, wi, arg, *p1, *p2, temp; + float tr, ti, ur, ui, *p1r, *p1i, *p2r, *p2i; + long i, bitm, j, le, le2, k; + + for (i = 2; i < 2*fftFrameSize-2; i += 2) { + for (bitm = 2, j = 0; bitm < 2*fftFrameSize; bitm <<= 1) { + if (i & bitm) j++; + j <<= 1; + } + if (i < j) { + p1 = fftBuffer+i; p2 = fftBuffer+j; + temp = *p1; *(p1++) = *p2; + *(p2++) = temp; temp = *p1; + *p1 = *p2; *p2 = temp; + } + } + for (k = 0, le = 2; k < (long)(log(fftFrameSize)/log(2.)+.5); k++) { + le <<= 1; + le2 = le>>1; + ur = 1.0; + ui = 0.0; + arg = M_PI / (le2>>1); + wr = cos(arg); + wi = sign*sin(arg); + for (j = 0; j < le2; j += 2) { + p1r = fftBuffer+j; p1i = p1r+1; + p2r = p1r+le2; p2i = p2r+1; + for (i = j; i < 2*fftFrameSize; i += le) { + tr = *p2r * ur - *p2i * ui; + ti = *p2r * ui + *p2i * ur; + *p2r = *p1r - tr; *p2i = *p1i - ti; + *p1r += tr; *p1i += ti; + p1r += le; p1i += le; + p2r += le; p2i += le; + } + tr = ur*wr - ui*wi; + ui = ur*wi + ui*wr; + ur = tr; + } + } +} +#endif + +// ----------------------------------------------------------------------------------------------------------------- + +/* + + 12/12/02, smb + + PLEASE NOTE: + + There have been some reports on domain errors when the atan2() function was used + as in the above code. Usually, a domain error should not interrupt the program flow + (maybe except in Debug mode) but rather be handled "silently" and a global variable + should be set according to this error. However, on some occasions people ran into + this kind of scenario, so a replacement atan2() function is provided here. + + If you are experiencing domain errors and your program stops, simply replace all + instances of atan2() with calls to the smbAtan2() function below. + +*/ + +// Approximation was taken from: +// http://www-labs.iro.umontreal.ca/~mignotte/IFT2425/Documents/EfficientApproximationArctgFunction.pdf +// +// |Error = fast_atan2(y, x) - atan2f(y, x)| < 0.00468 rad +// +// Octants: +// pi/2 +// ` 3 | 2 / +// ` | / +// 4 ` | / 1 +// pi -----+----- 0 +// 5 / | ` 8 +// / | ` +// / 6 | 7 ` +// 3pi/2 + +template T CopySign(T v, T x) +{ + return (x >= 0) ? v : -v; +} + +FFI_PLUGIN_EXPORT double smbAtan2(double y, double x) +{ + constexpr double scaling_constant = 0.28086; + + if (x == 0.) { + // Special case atan2(0.0, 0.0) = 0.0 + if (y == 0.) { + return 0.; + } + + // x is zero so we are either at pi/2 for (y > 0) or -pi/2 for (y < 0) + return CopySign(M_PI_2, y); + } + + // Calculate quotient of y and x + const auto div = y / x; + + // Determine in which octants we can be, if |y| is smaller than |x| (|div|<1) + // then we are either in 1,4,5 or 8 else we are in 2,3,6 or 7. + if (fabs(div) < 1.) { + // We are in 1,4,5 or 8 + + const auto atan = div / (1. + scaling_constant * div * div); + + // If we are in 4 or 5 we need to add pi or -pi respectively + if (x < 0.) { + return CopySign(M_PI, y) + atan; + } + return atan; + } + + // We are in 2,3,6 or 7 + return CopySign(M_PI_2, y) - div / (div * div + scaling_constant); +} + +//double smbAtan2(double x, double y) +//{ +// double signx; +// if (x > 0.) signx = 1.; +// else signx = -1.; +// +// if (x == 0.) return 0.; +// if (y == 0.) return signx * M_PI / 2.; +// +// return atan2(x, y); +//} + + +} // namespace + + +// ----------------------------------------------------------------------------------------------------------------- + +CSmbPitchShift::CSmbPitchShift() { +} + +void CSmbPitchShift::smbPitchShift(float pitchShift, long numSampsToProcess, long fftFrameSize, long osamp, float sampleRate, float *indata, float *outdata) +/* + Routine smbPitchShift(). See top of file for explanation + Purpose: doing pitch shifting while maintaining duration using the Short + Time Fourier Transform. + Author: (c)1999-2015 Stephan M. Bernsee +*/ +{ + /* set up some handy variables */ + const long fftFrameSize2 = fftFrameSize/2; + const long stepSize = fftFrameSize/osamp; + const double freqPerBin = sampleRate/(double)fftFrameSize; + const double expct = 2.*M_PI*(double)stepSize/(double)fftFrameSize; + const long inFifoLatency = fftFrameSize-stepSize; + if (!gRover) + gRover = inFifoLatency; + + /* initialize our static arrays */ + if (!gInit) { + memset(gInFIFO, 0, MAX_FRAME_LENGTH*sizeof(float)); + memset(gOutFIFO, 0, MAX_FRAME_LENGTH*sizeof(float)); + memset(gFFTworksp, 0, 2*MAX_FRAME_LENGTH*sizeof(float)); + memset(gLastPhase, 0, (MAX_FRAME_LENGTH/2+1)*sizeof(float)); + memset(gSumPhase, 0, (MAX_FRAME_LENGTH/2+1)*sizeof(float)); + memset(gOutputAccum, 0, 2*MAX_FRAME_LENGTH*sizeof(float)); + memset(gAnaFreq, 0, MAX_FRAME_LENGTH*sizeof(float)); + memset(gAnaMagn, 0, MAX_FRAME_LENGTH*sizeof(float)); + + memset(gErrors, 0, MAX_FRAME_LENGTH * sizeof(float)); + + gInit = true; + } + + auto window = std::make_unique(fftFrameSize); + for (long k = 0; k < fftFrameSize; k++) { + window[k] = -.5*cos(2.*M_PI*(double)k / (double)fftFrameSize) + .5; + } + + /* main processing loop */ + for (long i = 0; i < numSampsToProcess; i++){ + + /* As long as we have not yet collected enough data just read in */ + gInFIFO[gRover] = indata[i]; + outdata[i] = gOutFIFO[gRover-inFifoLatency]; + gRover++; + + /* now we have enough data for processing */ + if (gRover >= fftFrameSize) { + gRover = inFifoLatency; + + /* do windowing and re,im interleave */ + for (long k = 0; k < fftFrameSize;k++) { + gFFTworksp[2*k] = gInFIFO[k] * window[k]; + gFFTworksp[2*k+1] = 0.; + } + + + /* ***************** ANALYSIS ******************* */ + /* do transform */ + smbFft(gFFTworksp, fftFrameSize, -1); + + /* this is the analysis step */ + for (long k = 0; k <= fftFrameSize2; k++) { + + /* de-interlace FFT buffer */ + const auto real = gFFTworksp[2*k]; + const auto imag = gFFTworksp[2*k+1]; + + /* compute magnitude and phase */ + const auto magn = 2.*hypotf(real, imag); + const auto phase = smbAtan2(imag,real); + + /* compute phase difference */ + double tmp = phase - gLastPhase[k]; + gLastPhase[k] = phase; + + /* subtract expected phase difference */ + tmp -= (double)k*expct; + + /* map delta phase into +/- Pi interval */ + /* get deviation from bin frequency from the +/- Pi interval */ + tmp /= (2.*M_PI); + tmp = osamp * (tmp - floor(tmp + 0.5)); // faster than round + + /* compute the k-th partials' true frequency */ + tmp = (k + tmp) * freqPerBin; + + /* store magnitude and true frequency in analysis arrays */ + gAnaMagn[k] = magn; + gAnaFreq[k] = tmp; + + } + + /* ***************** PROCESSING ******************* */ + /* this does the actual pitch shifting */ + memset(gSynMagn, 0, fftFrameSize*sizeof(float)); + memset(gSynFreq, 0, fftFrameSize*sizeof(float)); + for (long k = 0; k <= fftFrameSize2; k++) { + + //const long index = k*pitchShift; + const auto originalIndex = k*pitchShift + gErrors[k]; + const long index = originalIndex; + gErrors[k] = originalIndex - index; + + if (index <= fftFrameSize2) { + const bool useSynFreq = gSynMagn[index] < gAnaMagn[k]; + + gSynMagn[index] += gAnaMagn[k]; + if (useSynFreq) { + gSynFreq[index] = gAnaFreq[k] * pitchShift; + } + } + } + + /* ***************** SYNTHESIS ******************* */ + /* this is the synthesis step */ + for (long k = 0; k <= fftFrameSize2; k++) { + + /* get magnitude and true frequency from synthesis arrays */ + const auto magn = gSynMagn[k]; + double tmp = gSynFreq[k]; + + /* get bin deviation from freq deviation */ + tmp /= freqPerBin; + + /* subtract bin mid frequency */ + tmp -= k; + + /* take osamp into account */ + tmp = 2.*M_PI*tmp/osamp; + + /* add the overlap phase advance back in */ + tmp += (double)k*expct; + + /* accumulate delta phase to get bin phase */ + gSumPhase[k] += tmp; + const auto phase = gSumPhase[k]; + + /* get real and imag part and re-interleave */ + gFFTworksp[2*k] = magn*cosf(phase); + gFFTworksp[2*k+1] = magn*sinf(phase); + } + + /* zero negative frequencies */ + for (long k = fftFrameSize+2; k < 2*fftFrameSize; k++) gFFTworksp[k] = 0.; + + /* do inverse transform */ + smbFft(gFFTworksp, fftFrameSize, 1); + + /* do windowing and add to output accumulator */ + for(long k=0; k < fftFrameSize; k++) { + gOutputAccum[k] += 2. * window[k] * gFFTworksp[2*k]/(fftFrameSize2*osamp); + } + for (long k = 0; k < stepSize; k++) gOutFIFO[k] = gOutputAccum[k]; + + /* shift accumulator */ + memmove(gOutputAccum, gOutputAccum+stepSize, fftFrameSize*sizeof(float)); + + /* move input FIFO */ + for (long k = 0; k < inFifoLatency; k++) gInFIFO[k] = gInFIFO[k+stepSize]; + } + } +} diff --git a/src/filters/smbPitchShift.h b/src/filters/smbPitchShift.h new file mode 100644 index 00000000..54f18fc2 --- /dev/null +++ b/src/filters/smbPitchShift.h @@ -0,0 +1,37 @@ +#ifndef SMB_PITCHSHIFT_H +#define SMB_PITCHSHIFT_H +// http://blogs.zynaptiq.com/bernsee/pitch-shifting-using-the-ft/ + +class CSmbPitchShift +{ + enum { MAX_FRAME_LENGTH = 8192 }; + +public: + CSmbPitchShift(); + + void reset() + { + gRover = 0; + gInit = false; + } + void smbPitchShift(float pitchShift, long numSampsToProcess, long fftFrameSize, long osamp, float sampleRate, float *indata, float *outdata); + +private: + float gInFIFO[MAX_FRAME_LENGTH]; + float gOutFIFO[MAX_FRAME_LENGTH]; + float gFFTworksp[2 * MAX_FRAME_LENGTH]; + float gLastPhase[MAX_FRAME_LENGTH / 2 + 1]; + float gSumPhase[MAX_FRAME_LENGTH / 2 + 1]; + float gOutputAccum[2 * MAX_FRAME_LENGTH]; + float gAnaFreq[MAX_FRAME_LENGTH]; + float gAnaMagn[MAX_FRAME_LENGTH]; + float gSynFreq[MAX_FRAME_LENGTH]; + float gSynMagn[MAX_FRAME_LENGTH]; + + float gErrors[MAX_FRAME_LENGTH]; + + long gRover = 0; + bool gInit = false; +}; + +#endif \ No newline at end of file diff --git a/src/flutter_soloud.cpp b/src/flutter_soloud.cpp index f414b246..a68fae61 100755 --- a/src/flutter_soloud.cpp +++ b/src/flutter_soloud.cpp @@ -96,7 +96,7 @@ // vizsn -#include "soloud/src/audiosource/vizsn/soloud_vizsn.cpp" +// #include "soloud/src/audiosource/vizsn/soloud_vizsn.cpp" #include "common.cpp" @@ -105,6 +105,8 @@ #include "analyzer.cpp" #include "synth/basic_wave.cpp" #include "filters/filters.cpp" +#include "filters/pitch_shift_filter.cpp" +#include "filters/smbPitchShift.cpp" // A very short-lived native function. // diff --git a/src/player.cpp b/src/player.cpp index 4cc6d1b0..d6a13465 100644 --- a/src/player.cpp +++ b/src/player.cpp @@ -110,6 +110,8 @@ const std::string Player::getErrorString(PlayerErrors errorCode) const return "error: the player is already initialized!"; case soundHandleNotFound: return "error: audio handle is not found!"; + case filterParameterGetError: + return "error: getting filter parameter error!"; } return "Other error"; } @@ -124,7 +126,7 @@ PlayerErrors Player::loadFile( *hash = 0; - unsigned int newHash = (unsigned int)std::hash{}(completeFileName); + unsigned int newHash = (int32_t)std::hash{}(completeFileName) & 0x7fffffff; /// check if the sound has been already loaded auto const s = findByHash(newHash); @@ -134,20 +136,20 @@ PlayerErrors Player::loadFile( return fileAlreadyLoaded; } - sounds.push_back(std::make_shared()); + sounds.push_back(std::make_unique()); sounds.back().get()->completeFileName = std::string(completeFileName); sounds.back().get()->soundHash = newHash; SoLoud::result result; if (loadIntoMem) { - sounds.back().get()->sound = std::make_shared(); + sounds.back().get()->sound = std::make_unique(); sounds.back().get()->soundType = TYPE_WAV; result = static_cast(sounds.back().get()->sound.get())->load(completeFileName.c_str()); } else { - sounds.back().get()->sound = std::make_shared(); + sounds.back().get()->sound = std::make_unique(); sounds.back().get()->soundType = TYPE_WAVSTREAM; result = static_cast(sounds.back().get()->sound.get())->load(completeFileName.c_str()); } @@ -175,7 +177,7 @@ PlayerErrors Player::loadMem( hash = 0; - unsigned int newHash = (unsigned int)std::hash{}(uniqueName); + unsigned int newHash = (int32_t)std::hash{}(uniqueName) & 0x7fffffff; /// check if the sound has been already loaded auto const s = findByHash(newHash); @@ -185,29 +187,28 @@ PlayerErrors Player::loadMem( return fileAlreadyLoaded; } - sounds.push_back(std::make_shared()); - sounds.back().get()->completeFileName = std::string(uniqueName); - hash = sounds.back().get()->soundHash = newHash; - + std::unique_ptr newSound = std::make_unique(); + newSound.get()->completeFileName = std::string(uniqueName); + hash = newHash; + newSound.get()->soundHash = newHash; SoLoud::result result; if (loadIntoMem) { - sounds.back().get()->sound = std::make_shared(); - sounds.back().get()->soundType = TYPE_WAV; - result = static_cast(sounds.back().get()->sound.get())->loadMem(mem, length, false, true); + newSound.get()->sound = std::make_unique(); + newSound.get()->soundType = TYPE_WAV; + result = static_cast(newSound.get()->sound.get())->loadMem(mem, length, false, true); } else { - sounds.back().get()->sound = std::make_shared(); - sounds.back().get()->soundType = TYPE_WAVSTREAM; - result = static_cast(sounds.back().get()->sound.get())->loadMem(mem, length, false, true); + newSound.get()->sound = std::make_unique(); + newSound.get()->soundType = TYPE_WAVSTREAM; + result = static_cast(newSound.get()->sound.get())->loadMem(mem, length, false, true); } - - if (result != SoLoud::SO_NO_ERROR) + + if (result == SoLoud::SO_NO_ERROR) { - sounds.pop_back(); - } else { - sounds.back().get()->filters = std::make_unique(&soloud, sounds.back().get()); + newSound.get()->filters = std::make_unique(&soloud, newSound.get()); + sounds.push_back(std::move(newSound)); } return (PlayerErrors)result; @@ -231,10 +232,10 @@ PlayerErrors Player::loadWaveform( hash = dist(g); - sounds.push_back(std::make_shared()); + sounds.push_back(std::make_unique()); sounds.back().get()->completeFileName = ""; sounds.back().get()->soundHash = hash; - sounds.back().get()->sound = std::make_shared((SoLoud::Soloud::WAVEFORM)waveform, superWave, detune, scale); + sounds.back().get()->sound = std::make_unique((SoLoud::Soloud::WAVEFORM)waveform, superWave, detune, scale); sounds.back().get()->soundType = TYPE_SYNTH; sounds.back().get()->filters = std::make_unique(&soloud, sounds.back().get()); @@ -383,7 +384,7 @@ void Player::disposeSound(unsigned int soundHash) sound->sound.get()->stop(); // remove the sound from the list sounds.erase(std::remove_if(sounds.begin(), sounds.end(), - [soundHash](std::shared_ptr &f) + [soundHash](std::unique_ptr &f) { return f.get()->soundHash == soundHash; })); } @@ -418,7 +419,7 @@ PlayerErrors Player::textToSpeech(const std::string &textToSpeech, unsigned int if (!mInited) return backendNotInited; - sounds.push_back(std::make_shared()); + sounds.push_back(std::make_unique()); sounds.back().get()->completeFileName = std::string(""); SoLoud::result result = speech.setText(textToSpeech.c_str()); if (result == SoLoud::SO_NO_ERROR) @@ -586,7 +587,6 @@ void Player::setMaxActiveVoiceCount(unsigned int maxVoiceCount) ActiveSound *Player::findByHandle(SoLoud::handle handle) { int i = 0; - // TODO(marco): do a better std pls! \o/ while (i < (int)sounds.size()) { int index = 0; @@ -607,7 +607,7 @@ ActiveSound *Player::findByHandle(SoLoud::handle handle) ActiveSound *Player::findByHash(unsigned int soundHash) { auto const &s = std::find_if(sounds.begin(), sounds.end(), - [&](std::shared_ptr const &f) + [&](std::unique_ptr const &f) { return f->soundHash == soundHash; }); if (s == sounds.end()) return nullptr; diff --git a/src/player.h b/src/player.h index 3a015764..97f86a99 100644 --- a/src/player.h +++ b/src/player.h @@ -3,11 +3,11 @@ #ifndef PLAYER_H #define PLAYER_H +#include "soloud.h" +#include "soloud_speech.h" #include "enums.h" #include "filters/filters.h" #include "active_sound.h" -#include "soloud.h" -#include "soloud_speech.h" #include #include @@ -523,7 +523,7 @@ class Player public: /// all the sounds loaded - std::vector> sounds; + std::vector> sounds; /// true when the backend is initialized bool mInited; diff --git a/web/compile_wasm.sh b/web/compile_wasm.sh index 3d1ab9ee..3581f4bb 100755 --- a/web/compile_wasm.sh +++ b/web/compile_wasm.sh @@ -17,10 +17,13 @@ cd build # https://emscripten.org/docs/tools_reference/settings_reference.html -# -s ASSERTIONS=1 +# -s ASSERTIONS=1 \ # -s TOTAL_MEMORY=512MB \ # -s DEFAULT_TO_CXX \ -# -s STACK_SIZE=1048576 +# -s STACK_SIZE=1048576 \ +# -s TOTAL_STACK=5242880 \ +# -msimd128 for sse3 https://emscripten.org/docs/porting/simd.html +# -std=c++17 ## Compiling with "-O2" or "-O3" doesn't work @@ -38,12 +41,13 @@ em++ \ ../../src/player.cpp \ ../../src/analyzer.cpp \ ../../src/synth/basic_wave.cpp \ -../../src/filters/filters.cpp \ +../../src/filters/*.cpp \ -O3 -D WITH_MINIAUDIO \ --I ~/.emscripten_cache/sysroot/include \ +-msimd128 -msse3 \ -s "EXPORTED_RUNTIME_METHODS=['ccall','cwrap']" \ -s "EXPORTED_FUNCTIONS=['_free', '_malloc']" \ -s EXPORT_ALL=1 -s NO_EXIT_RUNTIME=1 \ -s SAFE_HEAP=1 \ +-s STACK_SIZE=4194304 \ -s ALLOW_MEMORY_GROWTH \ -o ../../web/libflutter_soloud_plugin.js diff --git a/web/libflutter_soloud_plugin.js b/web/libflutter_soloud_plugin.js index f564e63e..d9665dc3 100644 --- a/web/libflutter_soloud_plugin.js +++ b/web/libflutter_soloud_plugin.js @@ -1 +1 @@ -var Module=typeof Module!="undefined"?Module:{};var ENVIRONMENT_IS_WEB=typeof window=="object";var ENVIRONMENT_IS_WORKER=typeof importScripts=="function";var ENVIRONMENT_IS_NODE=typeof process=="object"&&typeof process.versions=="object"&&typeof process.versions.node=="string";if(ENVIRONMENT_IS_NODE){}var moduleOverrides=Object.assign({},Module);var arguments_=[];var thisProgram="./this.program";var quit_=(status,toThrow)=>{throw toThrow};var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var read_,readAsync,readBinary;if(ENVIRONMENT_IS_NODE){var fs=require("fs");var nodePath=require("path");scriptDirectory=__dirname+"/";read_=(filename,binary)=>{filename=isFileURI(filename)?new URL(filename):nodePath.normalize(filename);return fs.readFileSync(filename,binary?undefined:"utf8")};readBinary=filename=>{var ret=read_(filename,true);if(!ret.buffer){ret=new Uint8Array(ret)}return ret};readAsync=(filename,onload,onerror,binary=true)=>{filename=isFileURI(filename)?new URL(filename):nodePath.normalize(filename);fs.readFile(filename,binary?undefined:"utf8",(err,data)=>{if(err)onerror(err);else onload(binary?data.buffer:data)})};if(!Module["thisProgram"]&&process.argv.length>1){thisProgram=process.argv[1].replace(/\\/g,"/")}arguments_=process.argv.slice(2);if(typeof module!="undefined"){module["exports"]=Module}process.on("uncaughtException",ex=>{if(ex!=="unwind"&&!(ex instanceof ExitStatus)&&!(ex.context instanceof ExitStatus)){throw ex}});quit_=(status,toThrow)=>{process.exitCode=status;throw toThrow}}else if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){if(ENVIRONMENT_IS_WORKER){scriptDirectory=self.location.href}else if(typeof document!="undefined"&&document.currentScript){scriptDirectory=document.currentScript.src}if(scriptDirectory.startsWith("blob:")){scriptDirectory=""}else{scriptDirectory=scriptDirectory.substr(0,scriptDirectory.replace(/[?#].*/,"").lastIndexOf("/")+1)}{read_=url=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.send(null);return xhr.responseText};if(ENVIRONMENT_IS_WORKER){readBinary=url=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.responseType="arraybuffer";xhr.send(null);return new Uint8Array(xhr.response)}}readAsync=(url,onload,onerror)=>{if(isFileURI(url)){var xhr=new XMLHttpRequest;xhr.open("GET",url,true);xhr.responseType="arraybuffer";xhr.onload=()=>{if(xhr.status==200||xhr.status==0&&xhr.response){onload(xhr.response);return}onerror()};xhr.onerror=onerror;xhr.send(null);return}fetch(url,{credentials:"same-origin"}).then(response=>{if(response.ok){return response.arrayBuffer()}return Promise.reject(new Error(response.status+" : "+response.url))}).then(onload,onerror)}}}else{}var out=Module["print"]||console.log.bind(console);var err=Module["printErr"]||console.error.bind(console);Object.assign(Module,moduleOverrides);moduleOverrides=null;if(Module["arguments"])arguments_=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];if(Module["quit"])quit_=Module["quit"];var wasmBinary;if(Module["wasmBinary"])wasmBinary=Module["wasmBinary"];function getSafeHeapType(bytes,isFloat){switch(bytes){case 1:return"i8";case 2:return"i16";case 4:return isFloat?"float":"i32";case 8:return isFloat?"double":"i64";default:abort(`getSafeHeapType() invalid bytes=${bytes}`)}}function SAFE_HEAP_STORE(dest,value,bytes,isFloat){if(dest<=0)abort(`segmentation fault storing ${bytes} bytes to address ${dest}`);if(dest%bytes!==0)abort(`alignment error storing to address ${dest}, which was expected to be aligned to a multiple of ${bytes}`);if(runtimeInitialized){var brk=_sbrk(0);if(dest+bytes>brk)abort(`segmentation fault, exceeded the top of the available dynamic heap when storing ${bytes} bytes to address ${dest}. DYNAMICTOP=${brk}`);if(brk<_emscripten_stack_get_base())abort(`brk >= _emscripten_stack_get_base() (brk=${brk}, _emscripten_stack_get_base()=${_emscripten_stack_get_base()})`);if(brk>wasmMemory.buffer.byteLength)abort(`brk <= wasmMemory.buffer.byteLength (brk=${brk}, wasmMemory.buffer.byteLength=${wasmMemory.buffer.byteLength})`)}setValue_safe(dest,value,getSafeHeapType(bytes,isFloat));return value}function SAFE_HEAP_STORE_D(dest,value,bytes){return SAFE_HEAP_STORE(dest,value,bytes,true)}function SAFE_HEAP_LOAD(dest,bytes,unsigned,isFloat){if(dest<=0)abort(`segmentation fault loading ${bytes} bytes from address ${dest}`);if(dest%bytes!==0)abort(`alignment error loading from address ${dest}, which was expected to be aligned to a multiple of ${bytes}`);if(runtimeInitialized){var brk=_sbrk(0);if(dest+bytes>brk)abort(`segmentation fault, exceeded the top of the available dynamic heap when loading ${bytes} bytes from address ${dest}. DYNAMICTOP=${brk}`);if(brk<_emscripten_stack_get_base())abort(`brk >= _emscripten_stack_get_base() (brk=${brk}, _emscripten_stack_get_base()=${_emscripten_stack_get_base()})`);if(brk>wasmMemory.buffer.byteLength)abort(`brk <= wasmMemory.buffer.byteLength (brk=${brk}, wasmMemory.buffer.byteLength=${wasmMemory.buffer.byteLength})`)}var type=getSafeHeapType(bytes,isFloat);var ret=getValue_safe(dest,type);if(unsigned)ret=unSign(ret,parseInt(type.substr(1),10));return ret}function SAFE_HEAP_LOAD_D(dest,bytes,unsigned){return SAFE_HEAP_LOAD(dest,bytes,unsigned,true)}function segfault(){abort("segmentation fault")}function alignfault(){abort("alignment fault")}var wasmMemory;var ABORT=false;var EXITSTATUS;var HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAPF32,HEAPF64;function updateMemoryViews(){var b=wasmMemory.buffer;Module["HEAP8"]=HEAP8=new Int8Array(b);Module["HEAP16"]=HEAP16=new Int16Array(b);Module["HEAPU8"]=HEAPU8=new Uint8Array(b);Module["HEAPU16"]=HEAPU16=new Uint16Array(b);Module["HEAP32"]=HEAP32=new Int32Array(b);Module["HEAPU32"]=HEAPU32=new Uint32Array(b);Module["HEAPF32"]=HEAPF32=new Float32Array(b);Module["HEAPF64"]=HEAPF64=new Float64Array(b)}var __ATPRERUN__=[];var __ATINIT__=[];var __ATPOSTRUN__=[];var runtimeInitialized=false;function preRun(){if(Module["preRun"]){if(typeof Module["preRun"]=="function")Module["preRun"]=[Module["preRun"]];while(Module["preRun"].length){addOnPreRun(Module["preRun"].shift())}}callRuntimeCallbacks(__ATPRERUN__)}function initRuntime(){runtimeInitialized=true;if(!Module["noFSInit"]&&!FS.init.initialized)FS.init();FS.ignorePermissions=false;TTY.init();callRuntimeCallbacks(__ATINIT__)}function postRun(){if(Module["postRun"]){if(typeof Module["postRun"]=="function")Module["postRun"]=[Module["postRun"]];while(Module["postRun"].length){addOnPostRun(Module["postRun"].shift())}}callRuntimeCallbacks(__ATPOSTRUN__)}function addOnPreRun(cb){__ATPRERUN__.unshift(cb)}function addOnInit(cb){__ATINIT__.unshift(cb)}function addOnPostRun(cb){__ATPOSTRUN__.unshift(cb)}var runDependencies=0;var runDependencyWatcher=null;var dependenciesFulfilled=null;function getUniqueRunDependency(id){return id}function addRunDependency(id){runDependencies++;Module["monitorRunDependencies"]?.(runDependencies)}function removeRunDependency(id){runDependencies--;Module["monitorRunDependencies"]?.(runDependencies);if(runDependencies==0){if(runDependencyWatcher!==null){clearInterval(runDependencyWatcher);runDependencyWatcher=null}if(dependenciesFulfilled){var callback=dependenciesFulfilled;dependenciesFulfilled=null;callback()}}}function abort(what){Module["onAbort"]?.(what);what="Aborted("+what+")";err(what);ABORT=true;EXITSTATUS=1;what+=". Build with -sASSERTIONS for more info.";var e=new WebAssembly.RuntimeError(what);throw e}var dataURIPrefix="data:application/octet-stream;base64,";var isDataURI=filename=>filename.startsWith(dataURIPrefix);var isFileURI=filename=>filename.startsWith("file://");function findWasmBinary(){var f="libflutter_soloud_plugin.wasm";if(!isDataURI(f)){return locateFile(f)}return f}var wasmBinaryFile;function getBinarySync(file){if(file==wasmBinaryFile&&wasmBinary){return new Uint8Array(wasmBinary)}if(readBinary){return readBinary(file)}throw"both async and sync fetching of the wasm failed"}function getBinaryPromise(binaryFile){if(!wasmBinary){return new Promise((resolve,reject)=>{readAsync(binaryFile,response=>resolve(new Uint8Array(response)),error=>{try{resolve(getBinarySync(binaryFile))}catch(e){reject(e)}})})}return Promise.resolve().then(()=>getBinarySync(binaryFile))}function instantiateArrayBuffer(binaryFile,imports,receiver){return getBinaryPromise(binaryFile).then(binary=>WebAssembly.instantiate(binary,imports)).then(receiver,reason=>{err(`failed to asynchronously prepare wasm: ${reason}`);abort(reason)})}function instantiateAsync(binary,binaryFile,imports,callback){if(!binary&&typeof WebAssembly.instantiateStreaming=="function"&&!isDataURI(binaryFile)&&!isFileURI(binaryFile)&&!ENVIRONMENT_IS_NODE&&typeof fetch=="function"){return fetch(binaryFile,{credentials:"same-origin"}).then(response=>{var result=WebAssembly.instantiateStreaming(response,imports);return result.then(callback,function(reason){err(`wasm streaming compile failed: ${reason}`);err("falling back to ArrayBuffer instantiation");return instantiateArrayBuffer(binaryFile,imports,callback)})})}return instantiateArrayBuffer(binaryFile,imports,callback)}function getWasmImports(){return{a:wasmImports}}function createWasm(){var info=getWasmImports();function receiveInstance(instance,module){wasmExports=instance.exports;wasmMemory=wasmExports["q"];updateMemoryViews();addOnInit(wasmExports["r"]);removeRunDependency("wasm-instantiate");return wasmExports}addRunDependency("wasm-instantiate");function receiveInstantiationResult(result){receiveInstance(result["instance"])}if(Module["instantiateWasm"]){try{return Module["instantiateWasm"](info,receiveInstance)}catch(e){err(`Module.instantiateWasm callback failed with error: ${e}`);return false}}if(!wasmBinaryFile)wasmBinaryFile=findWasmBinary();instantiateAsync(wasmBinary,wasmBinaryFile,info,receiveInstantiationResult);return{}}var tempDouble;var tempI64;var ASM_CONSTS={67024:($0,$1,$2,$3,$4)=>{if(typeof window==="undefined"||(window.AudioContext||window.webkitAudioContext)===undefined){return 0}if(typeof window.miniaudio==="undefined"){window.miniaudio={referenceCount:0};window.miniaudio.device_type={};window.miniaudio.device_type.playback=$0;window.miniaudio.device_type.capture=$1;window.miniaudio.device_type.duplex=$2;window.miniaudio.device_state={};window.miniaudio.device_state.stopped=$3;window.miniaudio.device_state.started=$4;let miniaudio=window.miniaudio;miniaudio.devices=[];miniaudio.track_device=function(device){for(var iDevice=0;iDevice0){if(miniaudio.devices[miniaudio.devices.length-1]==null){miniaudio.devices.pop()}else{break}}};miniaudio.untrack_device=function(device){for(var iDevice=0;iDevice{_ma_device__on_notification_unlocked(device.pDevice)},error=>{console.error("Failed to resume audiocontext",error)})}}miniaudio.unlock_event_types.map(function(event_type){document.removeEventListener(event_type,miniaudio.unlock,true)})};miniaudio.unlock_event_types.map(function(event_type){document.addEventListener(event_type,miniaudio.unlock,true)})}window.miniaudio.referenceCount+=1;return 1},69202:()=>{if(typeof window.miniaudio!=="undefined"){miniaudio.unlock_event_types.map(function(event_type){document.removeEventListener(event_type,miniaudio.unlock,true)});window.miniaudio.referenceCount-=1;if(window.miniaudio.referenceCount===0){delete window.miniaudio}}},69492:()=>navigator.mediaDevices!==undefined&&navigator.mediaDevices.getUserMedia!==undefined,69596:()=>{try{var temp=new(window.AudioContext||window.webkitAudioContext);var sampleRate=temp.sampleRate;temp.close();return sampleRate}catch(e){return 0}},69767:($0,$1,$2,$3,$4,$5)=>{var deviceType=$0;var channels=$1;var sampleRate=$2;var bufferSize=$3;var pIntermediaryBuffer=$4;var pDevice=$5;if(typeof window.miniaudio==="undefined"){return-1}var device={};var audioContextOptions={};if(deviceType==window.miniaudio.device_type.playback&&sampleRate!=0){audioContextOptions.sampleRate=sampleRate}device.webaudio=new(window.AudioContext||window.webkitAudioContext)(audioContextOptions);device.webaudio.suspend();device.state=window.miniaudio.device_state.stopped;var channelCountIn=0;var channelCountOut=channels;if(deviceType!=window.miniaudio.device_type.playback){channelCountIn=channels}device.scriptNode=device.webaudio.createScriptProcessor(bufferSize,channelCountIn,channelCountOut);device.scriptNode.onaudioprocess=function(e){if(device.intermediaryBufferView==null||device.intermediaryBufferView.length==0){device.intermediaryBufferView=new Float32Array(HEAPF32.buffer,pIntermediaryBuffer,bufferSize*channels)}if(deviceType==window.miniaudio.device_type.capture||deviceType==window.miniaudio.device_type.duplex){for(var iChannel=0;iChannelwindow.miniaudio.get_device_by_index($0).webaudio.sampleRate,72717:$0=>{var device=window.miniaudio.get_device_by_index($0);if(device.scriptNode!==undefined){device.scriptNode.onaudioprocess=function(e){};device.scriptNode.disconnect();device.scriptNode=undefined}if(device.streamNode!==undefined){device.streamNode.disconnect();device.streamNode=undefined}device.webaudio.close();device.webaudio=undefined;device.pDevice=undefined},73117:$0=>{window.miniaudio.untrack_device_by_index($0)},73167:$0=>{var device=window.miniaudio.get_device_by_index($0);device.webaudio.resume();device.state=window.miniaudio.device_state.started},73306:$0=>{var device=window.miniaudio.get_device_by_index($0);device.webaudio.suspend();device.state=window.miniaudio.device_state.stopped},73446:()=>{if(!Module.wasmWorker){var workerUri="assets/packages/flutter_soloud/web/worker.dart.js";console.log("EM_ASM creating web worker!");Module.wasmWorker=new Worker(workerUri)}else{console.log("EM_ASM web worker already created!")}},73694:($0,$1)=>{if(Module.wasmWorker){console.log("EM_ASM posting message "+UTF8ToString($0)+" with value "+$1);Module.wasmWorker.postMessage(JSON.stringify({message:UTF8ToString($0),value:$1}))}else{console.error("Worker not found.")}}};function ExitStatus(status){this.name="ExitStatus";this.message=`Program terminated with exit(${status})`;this.status=status}Module["ExitStatus"]=ExitStatus;var callRuntimeCallbacks=callbacks=>{while(callbacks.length>0){callbacks.shift()(Module)}};Module["callRuntimeCallbacks"]=callRuntimeCallbacks;function getValue(ptr,type="i8"){if(type.endsWith("*"))type="*";switch(type){case"i1":return SAFE_HEAP_LOAD(ptr,1,0);case"i8":return SAFE_HEAP_LOAD(ptr,1,0);case"i16":return SAFE_HEAP_LOAD((ptr>>1)*2,2,0);case"i32":return SAFE_HEAP_LOAD((ptr>>2)*4,4,0);case"i64":abort("to do getValue(i64) use WASM_BIGINT");case"float":return SAFE_HEAP_LOAD_D((ptr>>2)*4,4,0);case"double":return SAFE_HEAP_LOAD_D((ptr>>3)*8,8,0);case"*":return SAFE_HEAP_LOAD((ptr>>2)*4,4,1);default:abort(`invalid type for getValue: ${type}`)}}Module["getValue"]=getValue;function getValue_safe(ptr,type="i8"){if(type.endsWith("*"))type="*";switch(type){case"i1":return HEAP8[ptr];case"i8":return HEAP8[ptr];case"i16":return HEAP16[ptr>>1];case"i32":return HEAP32[ptr>>2];case"i64":abort("to do getValue(i64) use WASM_BIGINT");case"float":return HEAPF32[ptr>>2];case"double":return HEAPF64[ptr>>3];case"*":return HEAPU32[ptr>>2];default:abort(`invalid type for getValue: ${type}`)}}Module["getValue_safe"]=getValue_safe;var noExitRuntime=Module["noExitRuntime"]||true;Module["noExitRuntime"]=noExitRuntime;function setValue(ptr,value,type="i8"){if(type.endsWith("*"))type="*";switch(type){case"i1":SAFE_HEAP_STORE(ptr,value,1);break;case"i8":SAFE_HEAP_STORE(ptr,value,1);break;case"i16":SAFE_HEAP_STORE((ptr>>1)*2,value,2);break;case"i32":SAFE_HEAP_STORE((ptr>>2)*4,value,4);break;case"i64":abort("to do setValue(i64) use WASM_BIGINT");case"float":SAFE_HEAP_STORE_D((ptr>>2)*4,value,4);break;case"double":SAFE_HEAP_STORE_D((ptr>>3)*8,value,8);break;case"*":SAFE_HEAP_STORE((ptr>>2)*4,value,4);break;default:abort(`invalid type for setValue: ${type}`)}}Module["setValue"]=setValue;function setValue_safe(ptr,value,type="i8"){if(type.endsWith("*"))type="*";switch(type){case"i1":HEAP8[ptr]=value;break;case"i8":HEAP8[ptr]=value;break;case"i16":HEAP16[ptr>>1]=value;break;case"i32":HEAP32[ptr>>2]=value;break;case"i64":abort("to do setValue(i64) use WASM_BIGINT");case"float":HEAPF32[ptr>>2]=value;break;case"double":HEAPF64[ptr>>3]=value;break;case"*":HEAPU32[ptr>>2]=value;break;default:abort(`invalid type for setValue: ${type}`)}}Module["setValue_safe"]=setValue_safe;var stackRestore=val=>__emscripten_stack_restore(val);Module["stackRestore"]=stackRestore;var stackSave=()=>_emscripten_stack_get_current();Module["stackSave"]=stackSave;var unSign=(value,bits)=>{if(value>=0){return value}return bits<=32?2*Math.abs(1<{var endIdx=idx+maxBytesToRead;var endPtr=idx;while(heapOrArray[endPtr]&&!(endPtr>=endIdx))++endPtr;if(endPtr-idx>16&&heapOrArray.buffer&&UTF8Decoder){return UTF8Decoder.decode(heapOrArray.subarray(idx,endPtr))}var str="";while(idx>10,56320|ch&1023)}}return str};Module["UTF8ArrayToString"]=UTF8ArrayToString;var UTF8ToString=(ptr,maxBytesToRead)=>ptr?UTF8ArrayToString(HEAPU8,ptr,maxBytesToRead):"";Module["UTF8ToString"]=UTF8ToString;var ___assert_fail=(condition,filename,line,func)=>{abort(`Assertion failed: ${UTF8ToString(condition)}, at: `+[filename?UTF8ToString(filename):"unknown filename",line,func?UTF8ToString(func):"unknown function"])};Module["___assert_fail"]=___assert_fail;class ExceptionInfo{constructor(excPtr){this.excPtr=excPtr;this.ptr=excPtr-24}set_type(type){SAFE_HEAP_STORE((this.ptr+4>>2)*4,type,4)}get_type(){return SAFE_HEAP_LOAD((this.ptr+4>>2)*4,4,1)}set_destructor(destructor){SAFE_HEAP_STORE((this.ptr+8>>2)*4,destructor,4)}get_destructor(){return SAFE_HEAP_LOAD((this.ptr+8>>2)*4,4,1)}set_caught(caught){caught=caught?1:0;SAFE_HEAP_STORE(this.ptr+12,caught,1)}get_caught(){return SAFE_HEAP_LOAD(this.ptr+12,1,0)!=0}set_rethrown(rethrown){rethrown=rethrown?1:0;SAFE_HEAP_STORE(this.ptr+13,rethrown,1)}get_rethrown(){return SAFE_HEAP_LOAD(this.ptr+13,1,0)!=0}init(type,destructor){this.set_adjusted_ptr(0);this.set_type(type);this.set_destructor(destructor)}set_adjusted_ptr(adjustedPtr){SAFE_HEAP_STORE((this.ptr+16>>2)*4,adjustedPtr,4)}get_adjusted_ptr(){return SAFE_HEAP_LOAD((this.ptr+16>>2)*4,4,1)}get_exception_ptr(){var isPointer=___cxa_is_pointer_type(this.get_type());if(isPointer){return SAFE_HEAP_LOAD((this.excPtr>>2)*4,4,1)}var adjusted=this.get_adjusted_ptr();if(adjusted!==0)return adjusted;return this.excPtr}}Module["ExceptionInfo"]=ExceptionInfo;var exceptionLast=0;Module["exceptionLast"]=exceptionLast;var uncaughtExceptionCount=0;Module["uncaughtExceptionCount"]=uncaughtExceptionCount;var ___cxa_throw=(ptr,type,destructor)=>{var info=new ExceptionInfo(ptr);info.init(type,destructor);exceptionLast=ptr;uncaughtExceptionCount++;throw exceptionLast};Module["___cxa_throw"]=___cxa_throw;function syscallGetVarargI(){var ret=SAFE_HEAP_LOAD((+SYSCALLS.varargs>>2)*4,4,0);SYSCALLS.varargs+=4;return ret}Module["syscallGetVarargI"]=syscallGetVarargI;var syscallGetVarargP=syscallGetVarargI;Module["syscallGetVarargP"]=syscallGetVarargP;var PATH={isAbs:path=>path.charAt(0)==="/",splitPath:filename=>{var splitPathRe=/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;return splitPathRe.exec(filename).slice(1)},normalizeArray:(parts,allowAboveRoot)=>{var up=0;for(var i=parts.length-1;i>=0;i--){var last=parts[i];if(last==="."){parts.splice(i,1)}else if(last===".."){parts.splice(i,1);up++}else if(up){parts.splice(i,1);up--}}if(allowAboveRoot){for(;up;up--){parts.unshift("..")}}return parts},normalize:path=>{var isAbsolute=PATH.isAbs(path),trailingSlash=path.substr(-1)==="/";path=PATH.normalizeArray(path.split("/").filter(p=>!!p),!isAbsolute).join("/");if(!path&&!isAbsolute){path="."}if(path&&trailingSlash){path+="/"}return(isAbsolute?"/":"")+path},dirname:path=>{var result=PATH.splitPath(path),root=result[0],dir=result[1];if(!root&&!dir){return"."}if(dir){dir=dir.substr(0,dir.length-1)}return root+dir},basename:path=>{if(path==="/")return"/";path=PATH.normalize(path);path=path.replace(/\/$/,"");var lastSlash=path.lastIndexOf("/");if(lastSlash===-1)return path;return path.substr(lastSlash+1)},join:(...paths)=>PATH.normalize(paths.join("/")),join2:(l,r)=>PATH.normalize(l+"/"+r)};Module["PATH"]=PATH;var initRandomFill=()=>{if(typeof crypto=="object"&&typeof crypto["getRandomValues"]=="function"){return view=>crypto.getRandomValues(view)}else if(ENVIRONMENT_IS_NODE){try{var crypto_module=require("crypto");var randomFillSync=crypto_module["randomFillSync"];if(randomFillSync){return view=>crypto_module["randomFillSync"](view)}var randomBytes=crypto_module["randomBytes"];return view=>(view.set(randomBytes(view.byteLength)),view)}catch(e){}}abort("initRandomDevice")};Module["initRandomFill"]=initRandomFill;var randomFill=view=>(randomFill=initRandomFill())(view);Module["randomFill"]=randomFill;var PATH_FS={resolve:(...args)=>{var resolvedPath="",resolvedAbsolute=false;for(var i=args.length-1;i>=-1&&!resolvedAbsolute;i--){var path=i>=0?args[i]:FS.cwd();if(typeof path!="string"){throw new TypeError("Arguments to path.resolve must be strings")}else if(!path){return""}resolvedPath=path+"/"+resolvedPath;resolvedAbsolute=PATH.isAbs(path)}resolvedPath=PATH.normalizeArray(resolvedPath.split("/").filter(p=>!!p),!resolvedAbsolute).join("/");return(resolvedAbsolute?"/":"")+resolvedPath||"."},relative:(from,to)=>{from=PATH_FS.resolve(from).substr(1);to=PATH_FS.resolve(to).substr(1);function trim(arr){var start=0;for(;start=0;end--){if(arr[end]!=="")break}if(start>end)return[];return arr.slice(start,end-start+1)}var fromParts=trim(from.split("/"));var toParts=trim(to.split("/"));var length=Math.min(fromParts.length,toParts.length);var samePartsLength=length;for(var i=0;i{var len=0;for(var i=0;i=55296&&c<=57343){len+=4;++i}else{len+=3}}return len};Module["lengthBytesUTF8"]=lengthBytesUTF8;var stringToUTF8Array=(str,heap,outIdx,maxBytesToWrite)=>{if(!(maxBytesToWrite>0))return 0;var startIdx=outIdx;var endIdx=outIdx+maxBytesToWrite-1;for(var i=0;i=55296&&u<=57343){var u1=str.charCodeAt(++i);u=65536+((u&1023)<<10)|u1&1023}if(u<=127){if(outIdx>=endIdx)break;heap[outIdx++]=u}else if(u<=2047){if(outIdx+1>=endIdx)break;heap[outIdx++]=192|u>>6;heap[outIdx++]=128|u&63}else if(u<=65535){if(outIdx+2>=endIdx)break;heap[outIdx++]=224|u>>12;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}else{if(outIdx+3>=endIdx)break;heap[outIdx++]=240|u>>18;heap[outIdx++]=128|u>>12&63;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}}heap[outIdx]=0;return outIdx-startIdx};Module["stringToUTF8Array"]=stringToUTF8Array;function intArrayFromString(stringy,dontAddNull,length){var len=length>0?length:lengthBytesUTF8(stringy)+1;var u8array=new Array(len);var numBytesWritten=stringToUTF8Array(stringy,u8array,0,u8array.length);if(dontAddNull)u8array.length=numBytesWritten;return u8array}Module["intArrayFromString"]=intArrayFromString;var FS_stdin_getChar=()=>{if(!FS_stdin_getChar_buffer.length){var result=null;if(ENVIRONMENT_IS_NODE){var BUFSIZE=256;var buf=Buffer.alloc(BUFSIZE);var bytesRead=0;var fd=process.stdin.fd;try{bytesRead=fs.readSync(fd,buf,0,BUFSIZE)}catch(e){if(e.toString().includes("EOF"))bytesRead=0;else throw e}if(bytesRead>0){result=buf.slice(0,bytesRead).toString("utf-8")}}else if(typeof window!="undefined"&&typeof window.prompt=="function"){result=window.prompt("Input: ");if(result!==null){result+="\n"}}else{}if(!result){return null}FS_stdin_getChar_buffer=intArrayFromString(result,true)}return FS_stdin_getChar_buffer.shift()};Module["FS_stdin_getChar"]=FS_stdin_getChar;var TTY={ttys:[],init(){},shutdown(){},register(dev,ops){TTY.ttys[dev]={input:[],output:[],ops:ops};FS.registerDevice(dev,TTY.stream_ops)},stream_ops:{open(stream){var tty=TTY.ttys[stream.node.rdev];if(!tty){throw new FS.ErrnoError(43)}stream.tty=tty;stream.seekable=false},close(stream){stream.tty.ops.fsync(stream.tty)},fsync(stream){stream.tty.ops.fsync(stream.tty)},read(stream,buffer,offset,length,pos){if(!stream.tty||!stream.tty.ops.get_char){throw new FS.ErrnoError(60)}var bytesRead=0;for(var i=0;i0){out(UTF8ArrayToString(tty.output,0));tty.output=[]}},ioctl_tcgets(tty){return{c_iflag:25856,c_oflag:5,c_cflag:191,c_lflag:35387,c_cc:[3,28,127,21,4,0,1,0,17,19,26,0,18,15,23,22,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]}},ioctl_tcsets(tty,optional_actions,data){return 0},ioctl_tiocgwinsz(tty){return[24,80]}},default_tty1_ops:{put_char(tty,val){if(val===null||val===10){err(UTF8ArrayToString(tty.output,0));tty.output=[]}else{if(val!=0)tty.output.push(val)}},fsync(tty){if(tty.output&&tty.output.length>0){err(UTF8ArrayToString(tty.output,0));tty.output=[]}}}};Module["TTY"]=TTY;var zeroMemory=(address,size)=>{HEAPU8.fill(0,address,address+size);return address};Module["zeroMemory"]=zeroMemory;var alignMemory=(size,alignment)=>Math.ceil(size/alignment)*alignment;Module["alignMemory"]=alignMemory;var mmapAlloc=size=>{abort()};Module["mmapAlloc"]=mmapAlloc;var MEMFS={ops_table:null,mount(mount){return MEMFS.createNode(null,"/",16384|511,0)},createNode(parent,name,mode,dev){if(FS.isBlkdev(mode)||FS.isFIFO(mode)){throw new FS.ErrnoError(63)}MEMFS.ops_table||={dir:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr,lookup:MEMFS.node_ops.lookup,mknod:MEMFS.node_ops.mknod,rename:MEMFS.node_ops.rename,unlink:MEMFS.node_ops.unlink,rmdir:MEMFS.node_ops.rmdir,readdir:MEMFS.node_ops.readdir,symlink:MEMFS.node_ops.symlink},stream:{llseek:MEMFS.stream_ops.llseek}},file:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr},stream:{llseek:MEMFS.stream_ops.llseek,read:MEMFS.stream_ops.read,write:MEMFS.stream_ops.write,allocate:MEMFS.stream_ops.allocate,mmap:MEMFS.stream_ops.mmap,msync:MEMFS.stream_ops.msync}},link:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr,readlink:MEMFS.node_ops.readlink},stream:{}},chrdev:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr},stream:FS.chrdev_stream_ops}};var node=FS.createNode(parent,name,mode,dev);if(FS.isDir(node.mode)){node.node_ops=MEMFS.ops_table.dir.node;node.stream_ops=MEMFS.ops_table.dir.stream;node.contents={}}else if(FS.isFile(node.mode)){node.node_ops=MEMFS.ops_table.file.node;node.stream_ops=MEMFS.ops_table.file.stream;node.usedBytes=0;node.contents=null}else if(FS.isLink(node.mode)){node.node_ops=MEMFS.ops_table.link.node;node.stream_ops=MEMFS.ops_table.link.stream}else if(FS.isChrdev(node.mode)){node.node_ops=MEMFS.ops_table.chrdev.node;node.stream_ops=MEMFS.ops_table.chrdev.stream}node.timestamp=Date.now();if(parent){parent.contents[name]=node;parent.timestamp=node.timestamp}return node},getFileDataAsTypedArray(node){if(!node.contents)return new Uint8Array(0);if(node.contents.subarray)return node.contents.subarray(0,node.usedBytes);return new Uint8Array(node.contents)},expandFileStorage(node,newCapacity){var prevCapacity=node.contents?node.contents.length:0;if(prevCapacity>=newCapacity)return;var CAPACITY_DOUBLING_MAX=1024*1024;newCapacity=Math.max(newCapacity,prevCapacity*(prevCapacity>>0);if(prevCapacity!=0)newCapacity=Math.max(newCapacity,256);var oldContents=node.contents;node.contents=new Uint8Array(newCapacity);if(node.usedBytes>0)node.contents.set(oldContents.subarray(0,node.usedBytes),0)},resizeFileStorage(node,newSize){if(node.usedBytes==newSize)return;if(newSize==0){node.contents=null;node.usedBytes=0}else{var oldContents=node.contents;node.contents=new Uint8Array(newSize);if(oldContents){node.contents.set(oldContents.subarray(0,Math.min(newSize,node.usedBytes)))}node.usedBytes=newSize}},node_ops:{getattr(node){var attr={};attr.dev=FS.isChrdev(node.mode)?node.id:1;attr.ino=node.id;attr.mode=node.mode;attr.nlink=1;attr.uid=0;attr.gid=0;attr.rdev=node.rdev;if(FS.isDir(node.mode)){attr.size=4096}else if(FS.isFile(node.mode)){attr.size=node.usedBytes}else if(FS.isLink(node.mode)){attr.size=node.link.length}else{attr.size=0}attr.atime=new Date(node.timestamp);attr.mtime=new Date(node.timestamp);attr.ctime=new Date(node.timestamp);attr.blksize=4096;attr.blocks=Math.ceil(attr.size/attr.blksize);return attr},setattr(node,attr){if(attr.mode!==undefined){node.mode=attr.mode}if(attr.timestamp!==undefined){node.timestamp=attr.timestamp}if(attr.size!==undefined){MEMFS.resizeFileStorage(node,attr.size)}},lookup(parent,name){throw FS.genericErrors[44]},mknod(parent,name,mode,dev){return MEMFS.createNode(parent,name,mode,dev)},rename(old_node,new_dir,new_name){if(FS.isDir(old_node.mode)){var new_node;try{new_node=FS.lookupNode(new_dir,new_name)}catch(e){}if(new_node){for(var i in new_node.contents){throw new FS.ErrnoError(55)}}}delete old_node.parent.contents[old_node.name];old_node.parent.timestamp=Date.now();old_node.name=new_name;new_dir.contents[new_name]=old_node;new_dir.timestamp=old_node.parent.timestamp},unlink(parent,name){delete parent.contents[name];parent.timestamp=Date.now()},rmdir(parent,name){var node=FS.lookupNode(parent,name);for(var i in node.contents){throw new FS.ErrnoError(55)}delete parent.contents[name];parent.timestamp=Date.now()},readdir(node){var entries=[".",".."];for(var key of Object.keys(node.contents)){entries.push(key)}return entries},symlink(parent,newname,oldpath){var node=MEMFS.createNode(parent,newname,511|40960,0);node.link=oldpath;return node},readlink(node){if(!FS.isLink(node.mode)){throw new FS.ErrnoError(28)}return node.link}},stream_ops:{read(stream,buffer,offset,length,position){var contents=stream.node.contents;if(position>=stream.node.usedBytes)return 0;var size=Math.min(stream.node.usedBytes-position,length);if(size>8&&contents.subarray){buffer.set(contents.subarray(position,position+size),offset)}else{for(var i=0;i0||position+length{var dep=!noRunDep?getUniqueRunDependency(`al ${url}`):"";readAsync(url,arrayBuffer=>{onload(new Uint8Array(arrayBuffer));if(dep)removeRunDependency(dep)},event=>{if(onerror){onerror()}else{throw`Loading data file "${url}" failed.`}});if(dep)addRunDependency(dep)};Module["asyncLoad"]=asyncLoad;var FS_createDataFile=(parent,name,fileData,canRead,canWrite,canOwn)=>{FS.createDataFile(parent,name,fileData,canRead,canWrite,canOwn)};Module["FS_createDataFile"]=FS_createDataFile;var preloadPlugins=Module["preloadPlugins"]||[];Module["preloadPlugins"]=preloadPlugins;var FS_handledByPreloadPlugin=(byteArray,fullname,finish,onerror)=>{if(typeof Browser!="undefined")Browser.init();var handled=false;preloadPlugins.forEach(plugin=>{if(handled)return;if(plugin["canHandle"](fullname)){plugin["handle"](byteArray,fullname,finish,onerror);handled=true}});return handled};Module["FS_handledByPreloadPlugin"]=FS_handledByPreloadPlugin;var FS_createPreloadedFile=(parent,name,url,canRead,canWrite,onload,onerror,dontCreateFile,canOwn,preFinish)=>{var fullname=name?PATH_FS.resolve(PATH.join2(parent,name)):parent;var dep=getUniqueRunDependency(`cp ${fullname}`);function processData(byteArray){function finish(byteArray){preFinish?.();if(!dontCreateFile){FS_createDataFile(parent,name,byteArray,canRead,canWrite,canOwn)}onload?.();removeRunDependency(dep)}if(FS_handledByPreloadPlugin(byteArray,fullname,finish,()=>{onerror?.();removeRunDependency(dep)})){return}finish(byteArray)}addRunDependency(dep);if(typeof url=="string"){asyncLoad(url,processData,onerror)}else{processData(url)}};Module["FS_createPreloadedFile"]=FS_createPreloadedFile;var FS_modeStringToFlags=str=>{var flagModes={r:0,"r+":2,w:512|64|1,"w+":512|64|2,a:1024|64|1,"a+":1024|64|2};var flags=flagModes[str];if(typeof flags=="undefined"){throw new Error(`Unknown file open mode: ${str}`)}return flags};Module["FS_modeStringToFlags"]=FS_modeStringToFlags;var FS_getMode=(canRead,canWrite)=>{var mode=0;if(canRead)mode|=292|73;if(canWrite)mode|=146;return mode};Module["FS_getMode"]=FS_getMode;var FS={root:null,mounts:[],devices:{},streams:[],nextInode:1,nameTable:null,currentPath:"/",initialized:false,ignorePermissions:true,ErrnoError:class{constructor(errno){this.name="ErrnoError";this.errno=errno}},genericErrors:{},filesystems:null,syncFSRequests:0,FSStream:class{constructor(){this.shared={}}get object(){return this.node}set object(val){this.node=val}get isRead(){return(this.flags&2097155)!==1}get isWrite(){return(this.flags&2097155)!==0}get isAppend(){return this.flags&1024}get flags(){return this.shared.flags}set flags(val){this.shared.flags=val}get position(){return this.shared.position}set position(val){this.shared.position=val}},FSNode:class{constructor(parent,name,mode,rdev){if(!parent){parent=this}this.parent=parent;this.mount=parent.mount;this.mounted=null;this.id=FS.nextInode++;this.name=name;this.mode=mode;this.node_ops={};this.stream_ops={};this.rdev=rdev;this.readMode=292|73;this.writeMode=146}get read(){return(this.mode&this.readMode)===this.readMode}set read(val){val?this.mode|=this.readMode:this.mode&=~this.readMode}get write(){return(this.mode&this.writeMode)===this.writeMode}set write(val){val?this.mode|=this.writeMode:this.mode&=~this.writeMode}get isFolder(){return FS.isDir(this.mode)}get isDevice(){return FS.isChrdev(this.mode)}},lookupPath(path,opts={}){path=PATH_FS.resolve(path);if(!path)return{path:"",node:null};var defaults={follow_mount:true,recurse_count:0};opts=Object.assign(defaults,opts);if(opts.recurse_count>8){throw new FS.ErrnoError(32)}var parts=path.split("/").filter(p=>!!p);var current=FS.root;var current_path="/";for(var i=0;i40){throw new FS.ErrnoError(32)}}}}return{path:current_path,node:current}},getPath(node){var path;while(true){if(FS.isRoot(node)){var mount=node.mount.mountpoint;if(!path)return mount;return mount[mount.length-1]!=="/"?`${mount}/${path}`:mount+path}path=path?`${node.name}/${path}`:node.name;node=node.parent}},hashName(parentid,name){var hash=0;for(var i=0;i>>0)%FS.nameTable.length},hashAddNode(node){var hash=FS.hashName(node.parent.id,node.name);node.name_next=FS.nameTable[hash];FS.nameTable[hash]=node},hashRemoveNode(node){var hash=FS.hashName(node.parent.id,node.name);if(FS.nameTable[hash]===node){FS.nameTable[hash]=node.name_next}else{var current=FS.nameTable[hash];while(current){if(current.name_next===node){current.name_next=node.name_next;break}current=current.name_next}}},lookupNode(parent,name){var errCode=FS.mayLookup(parent);if(errCode){throw new FS.ErrnoError(errCode)}var hash=FS.hashName(parent.id,name);for(var node=FS.nameTable[hash];node;node=node.name_next){var nodeName=node.name;if(node.parent.id===parent.id&&nodeName===name){return node}}return FS.lookup(parent,name)},createNode(parent,name,mode,rdev){var node=new FS.FSNode(parent,name,mode,rdev);FS.hashAddNode(node);return node},destroyNode(node){FS.hashRemoveNode(node)},isRoot(node){return node===node.parent},isMountpoint(node){return!!node.mounted},isFile(mode){return(mode&61440)===32768},isDir(mode){return(mode&61440)===16384},isLink(mode){return(mode&61440)===40960},isChrdev(mode){return(mode&61440)===8192},isBlkdev(mode){return(mode&61440)===24576},isFIFO(mode){return(mode&61440)===4096},isSocket(mode){return(mode&49152)===49152},flagsToPermissionString(flag){var perms=["r","w","rw"][flag&3];if(flag&512){perms+="w"}return perms},nodePermissions(node,perms){if(FS.ignorePermissions){return 0}if(perms.includes("r")&&!(node.mode&292)){return 2}else if(perms.includes("w")&&!(node.mode&146)){return 2}else if(perms.includes("x")&&!(node.mode&73)){return 2}return 0},mayLookup(dir){if(!FS.isDir(dir.mode))return 54;var errCode=FS.nodePermissions(dir,"x");if(errCode)return errCode;if(!dir.node_ops.lookup)return 2;return 0},mayCreate(dir,name){try{var node=FS.lookupNode(dir,name);return 20}catch(e){}return FS.nodePermissions(dir,"wx")},mayDelete(dir,name,isdir){var node;try{node=FS.lookupNode(dir,name)}catch(e){return e.errno}var errCode=FS.nodePermissions(dir,"wx");if(errCode){return errCode}if(isdir){if(!FS.isDir(node.mode)){return 54}if(FS.isRoot(node)||FS.getPath(node)===FS.cwd()){return 10}}else{if(FS.isDir(node.mode)){return 31}}return 0},mayOpen(node,flags){if(!node){return 44}if(FS.isLink(node.mode)){return 32}else if(FS.isDir(node.mode)){if(FS.flagsToPermissionString(flags)!=="r"||flags&512){return 31}}return FS.nodePermissions(node,FS.flagsToPermissionString(flags))},MAX_OPEN_FDS:4096,nextfd(){for(var fd=0;fd<=FS.MAX_OPEN_FDS;fd++){if(!FS.streams[fd]){return fd}}throw new FS.ErrnoError(33)},getStreamChecked(fd){var stream=FS.getStream(fd);if(!stream){throw new FS.ErrnoError(8)}return stream},getStream:fd=>FS.streams[fd],createStream(stream,fd=-1){stream=Object.assign(new FS.FSStream,stream);if(fd==-1){fd=FS.nextfd()}stream.fd=fd;FS.streams[fd]=stream;return stream},closeStream(fd){FS.streams[fd]=null},dupStream(origStream,fd=-1){var stream=FS.createStream(origStream,fd);stream.stream_ops?.dup?.(stream);return stream},chrdev_stream_ops:{open(stream){var device=FS.getDevice(stream.node.rdev);stream.stream_ops=device.stream_ops;stream.stream_ops.open?.(stream)},llseek(){throw new FS.ErrnoError(70)}},major:dev=>dev>>8,minor:dev=>dev&255,makedev:(ma,mi)=>ma<<8|mi,registerDevice(dev,ops){FS.devices[dev]={stream_ops:ops}},getDevice:dev=>FS.devices[dev],getMounts(mount){var mounts=[];var check=[mount];while(check.length){var m=check.pop();mounts.push(m);check.push(...m.mounts)}return mounts},syncfs(populate,callback){if(typeof populate=="function"){callback=populate;populate=false}FS.syncFSRequests++;if(FS.syncFSRequests>1){err(`warning: ${FS.syncFSRequests} FS.syncfs operations in flight at once, probably just doing extra work`)}var mounts=FS.getMounts(FS.root.mount);var completed=0;function doCallback(errCode){FS.syncFSRequests--;return callback(errCode)}function done(errCode){if(errCode){if(!done.errored){done.errored=true;return doCallback(errCode)}return}if(++completed>=mounts.length){doCallback(null)}}mounts.forEach(mount=>{if(!mount.type.syncfs){return done(null)}mount.type.syncfs(mount,populate,done)})},mount(type,opts,mountpoint){var root=mountpoint==="/";var pseudo=!mountpoint;var node;if(root&&FS.root){throw new FS.ErrnoError(10)}else if(!root&&!pseudo){var lookup=FS.lookupPath(mountpoint,{follow_mount:false});mountpoint=lookup.path;node=lookup.node;if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}if(!FS.isDir(node.mode)){throw new FS.ErrnoError(54)}}var mount={type:type,opts:opts,mountpoint:mountpoint,mounts:[]};var mountRoot=type.mount(mount);mountRoot.mount=mount;mount.root=mountRoot;if(root){FS.root=mountRoot}else if(node){node.mounted=mount;if(node.mount){node.mount.mounts.push(mount)}}return mountRoot},unmount(mountpoint){var lookup=FS.lookupPath(mountpoint,{follow_mount:false});if(!FS.isMountpoint(lookup.node)){throw new FS.ErrnoError(28)}var node=lookup.node;var mount=node.mounted;var mounts=FS.getMounts(mount);Object.keys(FS.nameTable).forEach(hash=>{var current=FS.nameTable[hash];while(current){var next=current.name_next;if(mounts.includes(current.mount)){FS.destroyNode(current)}current=next}});node.mounted=null;var idx=node.mount.mounts.indexOf(mount);node.mount.mounts.splice(idx,1)},lookup(parent,name){return parent.node_ops.lookup(parent,name)},mknod(path,mode,dev){var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;var name=PATH.basename(path);if(!name||name==="."||name===".."){throw new FS.ErrnoError(28)}var errCode=FS.mayCreate(parent,name);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.mknod){throw new FS.ErrnoError(63)}return parent.node_ops.mknod(parent,name,mode,dev)},create(path,mode){mode=mode!==undefined?mode:438;mode&=4095;mode|=32768;return FS.mknod(path,mode,0)},mkdir(path,mode){mode=mode!==undefined?mode:511;mode&=511|512;mode|=16384;return FS.mknod(path,mode,0)},mkdirTree(path,mode){var dirs=path.split("/");var d="";for(var i=0;iFS.currentPath,chdir(path){var lookup=FS.lookupPath(path,{follow:true});if(lookup.node===null){throw new FS.ErrnoError(44)}if(!FS.isDir(lookup.node.mode)){throw new FS.ErrnoError(54)}var errCode=FS.nodePermissions(lookup.node,"x");if(errCode){throw new FS.ErrnoError(errCode)}FS.currentPath=lookup.path},createDefaultDirectories(){FS.mkdir("/tmp");FS.mkdir("/home");FS.mkdir("/home/web_user")},createDefaultDevices(){FS.mkdir("/dev");FS.registerDevice(FS.makedev(1,3),{read:()=>0,write:(stream,buffer,offset,length,pos)=>length});FS.mkdev("/dev/null",FS.makedev(1,3));TTY.register(FS.makedev(5,0),TTY.default_tty_ops);TTY.register(FS.makedev(6,0),TTY.default_tty1_ops);FS.mkdev("/dev/tty",FS.makedev(5,0));FS.mkdev("/dev/tty1",FS.makedev(6,0));var randomBuffer=new Uint8Array(1024),randomLeft=0;var randomByte=()=>{if(randomLeft===0){randomLeft=randomFill(randomBuffer).byteLength}return randomBuffer[--randomLeft]};FS.createDevice("/dev","random",randomByte);FS.createDevice("/dev","urandom",randomByte);FS.mkdir("/dev/shm");FS.mkdir("/dev/shm/tmp")},createSpecialDirectories(){FS.mkdir("/proc");var proc_self=FS.mkdir("/proc/self");FS.mkdir("/proc/self/fd");FS.mount({mount(){var node=FS.createNode(proc_self,"fd",16384|511,73);node.node_ops={lookup(parent,name){var fd=+name;var stream=FS.getStreamChecked(fd);var ret={parent:null,mount:{mountpoint:"fake"},node_ops:{readlink:()=>stream.path}};ret.parent=ret;return ret}};return node}},{},"/proc/self/fd")},createStandardStreams(){if(Module["stdin"]){FS.createDevice("/dev","stdin",Module["stdin"])}else{FS.symlink("/dev/tty","/dev/stdin")}if(Module["stdout"]){FS.createDevice("/dev","stdout",null,Module["stdout"])}else{FS.symlink("/dev/tty","/dev/stdout")}if(Module["stderr"]){FS.createDevice("/dev","stderr",null,Module["stderr"])}else{FS.symlink("/dev/tty1","/dev/stderr")}var stdin=FS.open("/dev/stdin",0);var stdout=FS.open("/dev/stdout",1);var stderr=FS.open("/dev/stderr",1)},staticInit(){[44].forEach(code=>{FS.genericErrors[code]=new FS.ErrnoError(code);FS.genericErrors[code].stack=""});FS.nameTable=new Array(4096);FS.mount(MEMFS,{},"/");FS.createDefaultDirectories();FS.createDefaultDevices();FS.createSpecialDirectories();FS.filesystems={MEMFS:MEMFS}},init(input,output,error){FS.init.initialized=true;Module["stdin"]=input||Module["stdin"];Module["stdout"]=output||Module["stdout"];Module["stderr"]=error||Module["stderr"];FS.createStandardStreams()},quit(){FS.init.initialized=false;for(var i=0;ithis.length-1||idx<0){return undefined}var chunkOffset=idx%this.chunkSize;var chunkNum=idx/this.chunkSize|0;return this.getter(chunkNum)[chunkOffset]}setDataGetter(getter){this.getter=getter}cacheLength(){var xhr=new XMLHttpRequest;xhr.open("HEAD",url,false);xhr.send(null);if(!(xhr.status>=200&&xhr.status<300||xhr.status===304))throw new Error("Couldn't load "+url+". Status: "+xhr.status);var datalength=Number(xhr.getResponseHeader("Content-length"));var header;var hasByteServing=(header=xhr.getResponseHeader("Accept-Ranges"))&&header==="bytes";var usesGzip=(header=xhr.getResponseHeader("Content-Encoding"))&&header==="gzip";var chunkSize=1024*1024;if(!hasByteServing)chunkSize=datalength;var doXHR=(from,to)=>{if(from>to)throw new Error("invalid range ("+from+", "+to+") or no bytes requested!");if(to>datalength-1)throw new Error("only "+datalength+" bytes available! programmer error!");var xhr=new XMLHttpRequest;xhr.open("GET",url,false);if(datalength!==chunkSize)xhr.setRequestHeader("Range","bytes="+from+"-"+to);xhr.responseType="arraybuffer";if(xhr.overrideMimeType){xhr.overrideMimeType("text/plain; charset=x-user-defined")}xhr.send(null);if(!(xhr.status>=200&&xhr.status<300||xhr.status===304))throw new Error("Couldn't load "+url+". Status: "+xhr.status);if(xhr.response!==undefined){return new Uint8Array(xhr.response||[])}return intArrayFromString(xhr.responseText||"",true)};var lazyArray=this;lazyArray.setDataGetter(chunkNum=>{var start=chunkNum*chunkSize;var end=(chunkNum+1)*chunkSize-1;end=Math.min(end,datalength-1);if(typeof lazyArray.chunks[chunkNum]=="undefined"){lazyArray.chunks[chunkNum]=doXHR(start,end)}if(typeof lazyArray.chunks[chunkNum]=="undefined")throw new Error("doXHR failed!");return lazyArray.chunks[chunkNum]});if(usesGzip||!datalength){chunkSize=datalength=1;datalength=this.getter(0).length;chunkSize=datalength;out("LazyFiles on gzip forces download of the whole file when length is accessed")}this._length=datalength;this._chunkSize=chunkSize;this.lengthKnown=true}get length(){if(!this.lengthKnown){this.cacheLength()}return this._length}get chunkSize(){if(!this.lengthKnown){this.cacheLength()}return this._chunkSize}}if(typeof XMLHttpRequest!="undefined"){if(!ENVIRONMENT_IS_WORKER)throw"Cannot do synchronous binary XHRs outside webworkers in modern browsers. Use --embed-file or --preload-file in emcc";var lazyArray=new LazyUint8Array;var properties={isDevice:false,contents:lazyArray}}else{var properties={isDevice:false,url:url}}var node=FS.createFile(parent,name,properties,canRead,canWrite);if(properties.contents){node.contents=properties.contents}else if(properties.url){node.contents=null;node.url=properties.url}Object.defineProperties(node,{usedBytes:{get:function(){return this.contents.length}}});var stream_ops={};var keys=Object.keys(node.stream_ops);keys.forEach(key=>{var fn=node.stream_ops[key];stream_ops[key]=(...args)=>{FS.forceLoadFile(node);return fn(...args)}});function writeChunks(stream,buffer,offset,length,position){var contents=stream.node.contents;if(position>=contents.length)return 0;var size=Math.min(contents.length-position,length);if(contents.slice){for(var i=0;i{FS.forceLoadFile(node);return writeChunks(stream,buffer,offset,length,position)};stream_ops.mmap=(stream,length,position,prot,flags)=>{FS.forceLoadFile(node);var ptr=mmapAlloc(length);if(!ptr){throw new FS.ErrnoError(48)}writeChunks(stream,HEAP8,ptr,length,position);return{ptr:ptr,allocated:true}};node.stream_ops=stream_ops;return node}};Module["FS"]=FS;var SYSCALLS={DEFAULT_POLLMASK:5,calculateAt(dirfd,path,allowEmpty){if(PATH.isAbs(path)){return path}var dir;if(dirfd===-100){dir=FS.cwd()}else{var dirstream=SYSCALLS.getStreamFromFD(dirfd);dir=dirstream.path}if(path.length==0){if(!allowEmpty){throw new FS.ErrnoError(44)}return dir}return PATH.join2(dir,path)},doStat(func,path,buf){var stat=func(path);SAFE_HEAP_STORE((buf>>2)*4,stat.dev,4);SAFE_HEAP_STORE((buf+4>>2)*4,stat.mode,4);SAFE_HEAP_STORE((buf+8>>2)*4,stat.nlink,4);SAFE_HEAP_STORE((buf+12>>2)*4,stat.uid,4);SAFE_HEAP_STORE((buf+16>>2)*4,stat.gid,4);SAFE_HEAP_STORE((buf+20>>2)*4,stat.rdev,4);tempI64=[stat.size>>>0,(tempDouble=stat.size,+Math.abs(tempDouble)>=1?tempDouble>0?+Math.floor(tempDouble/4294967296)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],SAFE_HEAP_STORE((buf+24>>2)*4,tempI64[0],4),SAFE_HEAP_STORE((buf+28>>2)*4,tempI64[1],4);SAFE_HEAP_STORE((buf+32>>2)*4,4096,4);SAFE_HEAP_STORE((buf+36>>2)*4,stat.blocks,4);var atime=stat.atime.getTime();var mtime=stat.mtime.getTime();var ctime=stat.ctime.getTime();tempI64=[Math.floor(atime/1e3)>>>0,(tempDouble=Math.floor(atime/1e3),+Math.abs(tempDouble)>=1?tempDouble>0?+Math.floor(tempDouble/4294967296)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],SAFE_HEAP_STORE((buf+40>>2)*4,tempI64[0],4),SAFE_HEAP_STORE((buf+44>>2)*4,tempI64[1],4);SAFE_HEAP_STORE((buf+48>>2)*4,atime%1e3*1e3,4);tempI64=[Math.floor(mtime/1e3)>>>0,(tempDouble=Math.floor(mtime/1e3),+Math.abs(tempDouble)>=1?tempDouble>0?+Math.floor(tempDouble/4294967296)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],SAFE_HEAP_STORE((buf+56>>2)*4,tempI64[0],4),SAFE_HEAP_STORE((buf+60>>2)*4,tempI64[1],4);SAFE_HEAP_STORE((buf+64>>2)*4,mtime%1e3*1e3,4);tempI64=[Math.floor(ctime/1e3)>>>0,(tempDouble=Math.floor(ctime/1e3),+Math.abs(tempDouble)>=1?tempDouble>0?+Math.floor(tempDouble/4294967296)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],SAFE_HEAP_STORE((buf+72>>2)*4,tempI64[0],4),SAFE_HEAP_STORE((buf+76>>2)*4,tempI64[1],4);SAFE_HEAP_STORE((buf+80>>2)*4,ctime%1e3*1e3,4);tempI64=[stat.ino>>>0,(tempDouble=stat.ino,+Math.abs(tempDouble)>=1?tempDouble>0?+Math.floor(tempDouble/4294967296)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],SAFE_HEAP_STORE((buf+88>>2)*4,tempI64[0],4),SAFE_HEAP_STORE((buf+92>>2)*4,tempI64[1],4);return 0},doMsync(addr,stream,len,flags,offset){if(!FS.isFile(stream.node.mode)){throw new FS.ErrnoError(43)}if(flags&2){return 0}var buffer=HEAPU8.slice(addr,addr+len);FS.msync(stream,buffer,offset,len,flags)},getStreamFromFD(fd){var stream=FS.getStreamChecked(fd);return stream},varargs:undefined,getStr(ptr){var ret=UTF8ToString(ptr);return ret}};Module["SYSCALLS"]=SYSCALLS;function ___syscall_fcntl64(fd,cmd,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD(fd);switch(cmd){case 0:{var arg=syscallGetVarargI();if(arg<0){return-28}while(FS.streams[arg]){arg++}var newStream;newStream=FS.dupStream(stream,arg);return newStream.fd}case 1:case 2:return 0;case 3:return stream.flags;case 4:{var arg=syscallGetVarargI();stream.flags|=arg;return 0}case 12:{var arg=syscallGetVarargP();var offset=0;SAFE_HEAP_STORE((arg+offset>>1)*2,2,2);return 0}case 13:case 14:return 0}return-28}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}Module["___syscall_fcntl64"]=___syscall_fcntl64;function ___syscall_ioctl(fd,op,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD(fd);switch(op){case 21509:{if(!stream.tty)return-59;return 0}case 21505:{if(!stream.tty)return-59;if(stream.tty.ops.ioctl_tcgets){var termios=stream.tty.ops.ioctl_tcgets(stream);var argp=syscallGetVarargP();SAFE_HEAP_STORE((argp>>2)*4,termios.c_iflag||0,4);SAFE_HEAP_STORE((argp+4>>2)*4,termios.c_oflag||0,4);SAFE_HEAP_STORE((argp+8>>2)*4,termios.c_cflag||0,4);SAFE_HEAP_STORE((argp+12>>2)*4,termios.c_lflag||0,4);for(var i=0;i<32;i++){SAFE_HEAP_STORE(argp+i+17,termios.c_cc[i]||0,1)}return 0}return 0}case 21510:case 21511:case 21512:{if(!stream.tty)return-59;return 0}case 21506:case 21507:case 21508:{if(!stream.tty)return-59;if(stream.tty.ops.ioctl_tcsets){var argp=syscallGetVarargP();var c_iflag=SAFE_HEAP_LOAD((argp>>2)*4,4,0);var c_oflag=SAFE_HEAP_LOAD((argp+4>>2)*4,4,0);var c_cflag=SAFE_HEAP_LOAD((argp+8>>2)*4,4,0);var c_lflag=SAFE_HEAP_LOAD((argp+12>>2)*4,4,0);var c_cc=[];for(var i=0;i<32;i++){c_cc.push(SAFE_HEAP_LOAD(argp+i+17,1,0))}return stream.tty.ops.ioctl_tcsets(stream.tty,op,{c_iflag:c_iflag,c_oflag:c_oflag,c_cflag:c_cflag,c_lflag:c_lflag,c_cc:c_cc})}return 0}case 21519:{if(!stream.tty)return-59;var argp=syscallGetVarargP();SAFE_HEAP_STORE((argp>>2)*4,0,4);return 0}case 21520:{if(!stream.tty)return-59;return-28}case 21531:{var argp=syscallGetVarargP();return FS.ioctl(stream,op,argp)}case 21523:{if(!stream.tty)return-59;if(stream.tty.ops.ioctl_tiocgwinsz){var winsize=stream.tty.ops.ioctl_tiocgwinsz(stream.tty);var argp=syscallGetVarargP();SAFE_HEAP_STORE((argp>>1)*2,winsize[0],2);SAFE_HEAP_STORE((argp+2>>1)*2,winsize[1],2)}return 0}case 21524:{if(!stream.tty)return-59;return 0}case 21515:{if(!stream.tty)return-59;return 0}default:return-28}}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}Module["___syscall_ioctl"]=___syscall_ioctl;function ___syscall_openat(dirfd,path,flags,varargs){SYSCALLS.varargs=varargs;try{path=SYSCALLS.getStr(path);path=SYSCALLS.calculateAt(dirfd,path);var mode=varargs?syscallGetVarargI():0;return FS.open(path,flags,mode).fd}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}Module["___syscall_openat"]=___syscall_openat;var __abort_js=()=>{abort("")};Module["__abort_js"]=__abort_js;var __emscripten_memcpy_js=(dest,src,num)=>HEAPU8.copyWithin(dest,src,src+num);Module["__emscripten_memcpy_js"]=__emscripten_memcpy_js;var readEmAsmArgsArray=[];Module["readEmAsmArgsArray"]=readEmAsmArgsArray;var readEmAsmArgs=(sigPtr,buf)=>{readEmAsmArgsArray.length=0;var ch;while(ch=SAFE_HEAP_LOAD(sigPtr++,1,1)){var wide=ch!=105;wide&=ch!=112;buf+=wide&&buf%8?4:0;readEmAsmArgsArray.push(ch==112?SAFE_HEAP_LOAD((buf>>2)*4,4,1):ch==105?SAFE_HEAP_LOAD((buf>>2)*4,4,0):SAFE_HEAP_LOAD_D((buf>>3)*8,8,0));buf+=wide?8:4}return readEmAsmArgsArray};Module["readEmAsmArgs"]=readEmAsmArgs;var runEmAsmFunction=(code,sigPtr,argbuf)=>{var args=readEmAsmArgs(sigPtr,argbuf);return ASM_CONSTS[code](...args)};Module["runEmAsmFunction"]=runEmAsmFunction;var _emscripten_asm_const_int=(code,sigPtr,argbuf)=>runEmAsmFunction(code,sigPtr,argbuf);Module["_emscripten_asm_const_int"]=_emscripten_asm_const_int;var getHeapMax=()=>2147483648;Module["getHeapMax"]=getHeapMax;var growMemory=size=>{var b=wasmMemory.buffer;var pages=(size-b.byteLength+65535)/65536;try{wasmMemory.grow(pages);updateMemoryViews();return 1}catch(e){}};Module["growMemory"]=growMemory;var _emscripten_resize_heap=requestedSize=>{var oldSize=HEAPU8.length;requestedSize>>>=0;var maxHeapSize=getHeapMax();if(requestedSize>maxHeapSize){return false}var alignUp=(x,multiple)=>x+(multiple-x%multiple)%multiple;for(var cutDown=1;cutDown<=4;cutDown*=2){var overGrownHeapSize=oldSize*(1+.2/cutDown);overGrownHeapSize=Math.min(overGrownHeapSize,requestedSize+100663296);var newSize=Math.min(maxHeapSize,alignUp(Math.max(requestedSize,overGrownHeapSize),65536));var replacement=growMemory(newSize);if(replacement){return true}}return false};Module["_emscripten_resize_heap"]=_emscripten_resize_heap;function _fd_close(fd){try{var stream=SYSCALLS.getStreamFromFD(fd);FS.close(stream);return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}Module["_fd_close"]=_fd_close;var doReadv=(stream,iov,iovcnt,offset)=>{var ret=0;for(var i=0;i>2)*4,4,1);var len=SAFE_HEAP_LOAD((iov+4>>2)*4,4,1);iov+=8;var curr=FS.read(stream,HEAP8,ptr,len,offset);if(curr<0)return-1;ret+=curr;if(curr>2)*4,num,4);return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}Module["_fd_read"]=_fd_read;var convertI32PairToI53Checked=(lo,hi)=>hi+2097152>>>0<4194305-!!lo?(lo>>>0)+hi*4294967296:NaN;Module["convertI32PairToI53Checked"]=convertI32PairToI53Checked;function _fd_seek(fd,offset_low,offset_high,whence,newOffset){var offset=convertI32PairToI53Checked(offset_low,offset_high);try{if(isNaN(offset))return 61;var stream=SYSCALLS.getStreamFromFD(fd);FS.llseek(stream,offset,whence);tempI64=[stream.position>>>0,(tempDouble=stream.position,+Math.abs(tempDouble)>=1?tempDouble>0?+Math.floor(tempDouble/4294967296)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],SAFE_HEAP_STORE((newOffset>>2)*4,tempI64[0],4),SAFE_HEAP_STORE((newOffset+4>>2)*4,tempI64[1],4);if(stream.getdents&&offset===0&&whence===0)stream.getdents=null;return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}Module["_fd_seek"]=_fd_seek;var doWritev=(stream,iov,iovcnt,offset)=>{var ret=0;for(var i=0;i>2)*4,4,1);var len=SAFE_HEAP_LOAD((iov+4>>2)*4,4,1);iov+=8;var curr=FS.write(stream,HEAP8,ptr,len,offset);if(curr<0)return-1;ret+=curr;if(typeof offset!="undefined"){offset+=curr}}return ret};Module["doWritev"]=doWritev;function _fd_write(fd,iov,iovcnt,pnum){try{var stream=SYSCALLS.getStreamFromFD(fd);var num=doWritev(stream,iov,iovcnt);SAFE_HEAP_STORE((pnum>>2)*4,num,4);return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}Module["_fd_write"]=_fd_write;var _getentropy=(buffer,size)=>{randomFill(HEAPU8.subarray(buffer,buffer+size));return 0};Module["_getentropy"]=_getentropy;var getCFunc=ident=>{var func=Module["_"+ident];return func};Module["getCFunc"]=getCFunc;var writeArrayToMemory=(array,buffer)=>{HEAP8.set(array,buffer)};Module["writeArrayToMemory"]=writeArrayToMemory;var stringToUTF8=(str,outPtr,maxBytesToWrite)=>stringToUTF8Array(str,HEAPU8,outPtr,maxBytesToWrite);Module["stringToUTF8"]=stringToUTF8;var stackAlloc=sz=>__emscripten_stack_alloc(sz);Module["stackAlloc"]=stackAlloc;var stringToUTF8OnStack=str=>{var size=lengthBytesUTF8(str)+1;var ret=stackAlloc(size);stringToUTF8(str,ret,size);return ret};Module["stringToUTF8OnStack"]=stringToUTF8OnStack;var ccall=(ident,returnType,argTypes,args,opts)=>{var toC={string:str=>{var ret=0;if(str!==null&&str!==undefined&&str!==0){ret=stringToUTF8OnStack(str)}return ret},array:arr=>{var ret=stackAlloc(arr.length);writeArrayToMemory(arr,ret);return ret}};function convertReturnValue(ret){if(returnType==="string"){return UTF8ToString(ret)}if(returnType==="boolean")return Boolean(ret);return ret}var func=getCFunc(ident);var cArgs=[];var stack=0;if(args){for(var i=0;i{var numericArgs=!argTypes||argTypes.every(type=>type==="number"||type==="boolean");var numericRet=returnType!=="string";if(numericRet&&numericArgs&&!opts){return getCFunc(ident)}return(...args)=>ccall(ident,returnType,argTypes,args,opts)};Module["cwrap"]=cwrap;FS.createPreloadedFile=FS_createPreloadedFile;FS.staticInit();var wasmImports={a:___assert_fail,h:___cxa_throw,g:___syscall_fcntl64,j:___syscall_ioctl,k:___syscall_openat,o:__abort_js,l:__emscripten_memcpy_js,c:alignfault,d:_emscripten_asm_const_int,p:_emscripten_resize_heap,e:_fd_close,i:_fd_read,m:_fd_seek,f:_fd_write,n:_getentropy,b:segfault};var wasmExports=createWasm();var ___wasm_call_ctors=()=>(___wasm_call_ctors=wasmExports["r"])();var _malloc=Module["_malloc"]=a0=>(_malloc=Module["_malloc"]=wasmExports["t"])(a0);var _free=Module["_free"]=a0=>(_free=Module["_free"]=wasmExports["u"])(a0);var _ma_device__on_notification_unlocked=Module["_ma_device__on_notification_unlocked"]=a0=>(_ma_device__on_notification_unlocked=Module["_ma_device__on_notification_unlocked"]=wasmExports["v"])(a0);var _ma_malloc_emscripten=Module["_ma_malloc_emscripten"]=(a0,a1)=>(_ma_malloc_emscripten=Module["_ma_malloc_emscripten"]=wasmExports["w"])(a0,a1);var _ma_free_emscripten=Module["_ma_free_emscripten"]=(a0,a1)=>(_ma_free_emscripten=Module["_ma_free_emscripten"]=wasmExports["x"])(a0,a1);var _ma_device_process_pcm_frames_capture__webaudio=Module["_ma_device_process_pcm_frames_capture__webaudio"]=(a0,a1,a2)=>(_ma_device_process_pcm_frames_capture__webaudio=Module["_ma_device_process_pcm_frames_capture__webaudio"]=wasmExports["y"])(a0,a1,a2);var _ma_device_process_pcm_frames_playback__webaudio=Module["_ma_device_process_pcm_frames_playback__webaudio"]=(a0,a1,a2)=>(_ma_device_process_pcm_frames_playback__webaudio=Module["_ma_device_process_pcm_frames_playback__webaudio"]=wasmExports["z"])(a0,a1,a2);var _createWorkerInWasm=Module["_createWorkerInWasm"]=()=>(_createWorkerInWasm=Module["_createWorkerInWasm"]=wasmExports["A"])();var _sendToWorker=Module["_sendToWorker"]=(a0,a1)=>(_sendToWorker=Module["_sendToWorker"]=wasmExports["B"])(a0,a1);var _nativeFree=Module["_nativeFree"]=a0=>(_nativeFree=Module["_nativeFree"]=wasmExports["C"])(a0);var _voiceEndedCallback=Module["_voiceEndedCallback"]=a0=>(_voiceEndedCallback=Module["_voiceEndedCallback"]=wasmExports["D"])(a0);var _setDartEventCallback=Module["_setDartEventCallback"]=(a0,a1,a2)=>(_setDartEventCallback=Module["_setDartEventCallback"]=wasmExports["E"])(a0,a1,a2);var _initEngine=Module["_initEngine"]=(a0,a1,a2)=>(_initEngine=Module["_initEngine"]=wasmExports["F"])(a0,a1,a2);var _dispose=Module["_dispose"]=()=>(_dispose=Module["_dispose"]=wasmExports["G"])();var _isInited=Module["_isInited"]=()=>(_isInited=Module["_isInited"]=wasmExports["H"])();var _loadFile=Module["_loadFile"]=(a0,a1)=>(_loadFile=Module["_loadFile"]=wasmExports["I"])(a0,a1);var _loadMem=Module["_loadMem"]=(a0,a1,a2,a3,a4)=>(_loadMem=Module["_loadMem"]=wasmExports["J"])(a0,a1,a2,a3,a4);var _loadWaveform=Module["_loadWaveform"]=(a0,a1,a2,a3,a4)=>(_loadWaveform=Module["_loadWaveform"]=wasmExports["K"])(a0,a1,a2,a3,a4);var _setWaveformScale=Module["_setWaveformScale"]=(a0,a1)=>(_setWaveformScale=Module["_setWaveformScale"]=wasmExports["L"])(a0,a1);var _setWaveformDetune=Module["_setWaveformDetune"]=(a0,a1)=>(_setWaveformDetune=Module["_setWaveformDetune"]=wasmExports["M"])(a0,a1);var _setWaveformFreq=Module["_setWaveformFreq"]=(a0,a1)=>(_setWaveformFreq=Module["_setWaveformFreq"]=wasmExports["N"])(a0,a1);var _setSuperWave=Module["_setSuperWave"]=(a0,a1)=>(_setSuperWave=Module["_setSuperWave"]=wasmExports["O"])(a0,a1);var _setWaveform=Module["_setWaveform"]=(a0,a1)=>(_setWaveform=Module["_setWaveform"]=wasmExports["P"])(a0,a1);var _speechText=Module["_speechText"]=(a0,a1)=>(_speechText=Module["_speechText"]=wasmExports["Q"])(a0,a1);var _pauseSwitch=Module["_pauseSwitch"]=a0=>(_pauseSwitch=Module["_pauseSwitch"]=wasmExports["R"])(a0);var _setPause=Module["_setPause"]=(a0,a1)=>(_setPause=Module["_setPause"]=wasmExports["S"])(a0,a1);var _getPause=Module["_getPause"]=a0=>(_getPause=Module["_getPause"]=wasmExports["T"])(a0);var _setRelativePlaySpeed=Module["_setRelativePlaySpeed"]=(a0,a1)=>(_setRelativePlaySpeed=Module["_setRelativePlaySpeed"]=wasmExports["U"])(a0,a1);var _getRelativePlaySpeed=Module["_getRelativePlaySpeed"]=a0=>(_getRelativePlaySpeed=Module["_getRelativePlaySpeed"]=wasmExports["V"])(a0);var _play=Module["_play"]=(a0,a1,a2,a3,a4,a5,a6)=>(_play=Module["_play"]=wasmExports["W"])(a0,a1,a2,a3,a4,a5,a6);var _stop=Module["_stop"]=a0=>(_stop=Module["_stop"]=wasmExports["X"])(a0);var _disposeSound=Module["_disposeSound"]=a0=>(_disposeSound=Module["_disposeSound"]=wasmExports["Y"])(a0);var _disposeAllSound=Module["_disposeAllSound"]=()=>(_disposeAllSound=Module["_disposeAllSound"]=wasmExports["Z"])();var _getLooping=Module["_getLooping"]=a0=>(_getLooping=Module["_getLooping"]=wasmExports["_"])(a0);var _setLooping=Module["_setLooping"]=(a0,a1)=>(_setLooping=Module["_setLooping"]=wasmExports["$"])(a0,a1);var _getLoopPoint=Module["_getLoopPoint"]=a0=>(_getLoopPoint=Module["_getLoopPoint"]=wasmExports["aa"])(a0);var _setLoopPoint=Module["_setLoopPoint"]=(a0,a1)=>(_setLoopPoint=Module["_setLoopPoint"]=wasmExports["ba"])(a0,a1);var _setVisualizationEnabled=Module["_setVisualizationEnabled"]=a0=>(_setVisualizationEnabled=Module["_setVisualizationEnabled"]=wasmExports["ca"])(a0);var _getVisualizationEnabled=Module["_getVisualizationEnabled"]=()=>(_getVisualizationEnabled=Module["_getVisualizationEnabled"]=wasmExports["da"])();var _getFft=Module["_getFft"]=a0=>(_getFft=Module["_getFft"]=wasmExports["ea"])(a0);var _getWave=Module["_getWave"]=a0=>(_getWave=Module["_getWave"]=wasmExports["fa"])(a0);var _setFftSmoothing=Module["_setFftSmoothing"]=a0=>(_setFftSmoothing=Module["_setFftSmoothing"]=wasmExports["ga"])(a0);var _getAudioTexture=Module["_getAudioTexture"]=a0=>(_getAudioTexture=Module["_getAudioTexture"]=wasmExports["ha"])(a0);var _getAudioTexture2D=Module["_getAudioTexture2D"]=a0=>(_getAudioTexture2D=Module["_getAudioTexture2D"]=wasmExports["ia"])(a0);var _getTextureValue=Module["_getTextureValue"]=(a0,a1)=>(_getTextureValue=Module["_getTextureValue"]=wasmExports["ja"])(a0,a1);var _getLength=Module["_getLength"]=a0=>(_getLength=Module["_getLength"]=wasmExports["ka"])(a0);var _seek=Module["_seek"]=(a0,a1)=>(_seek=Module["_seek"]=wasmExports["la"])(a0,a1);var _getPosition=Module["_getPosition"]=a0=>(_getPosition=Module["_getPosition"]=wasmExports["ma"])(a0);var _getGlobalVolume=Module["_getGlobalVolume"]=()=>(_getGlobalVolume=Module["_getGlobalVolume"]=wasmExports["na"])();var _setGlobalVolume=Module["_setGlobalVolume"]=a0=>(_setGlobalVolume=Module["_setGlobalVolume"]=wasmExports["oa"])(a0);var _getVolume=Module["_getVolume"]=a0=>(_getVolume=Module["_getVolume"]=wasmExports["pa"])(a0);var _setVolume=Module["_setVolume"]=(a0,a1)=>(_setVolume=Module["_setVolume"]=wasmExports["qa"])(a0,a1);var _getPan=Module["_getPan"]=a0=>(_getPan=Module["_getPan"]=wasmExports["ra"])(a0);var _setPan=Module["_setPan"]=(a0,a1)=>(_setPan=Module["_setPan"]=wasmExports["sa"])(a0,a1);var _setPanAbsolute=Module["_setPanAbsolute"]=(a0,a1,a2)=>(_setPanAbsolute=Module["_setPanAbsolute"]=wasmExports["ta"])(a0,a1,a2);var _getIsValidVoiceHandle=Module["_getIsValidVoiceHandle"]=a0=>(_getIsValidVoiceHandle=Module["_getIsValidVoiceHandle"]=wasmExports["ua"])(a0);var _getActiveVoiceCount=Module["_getActiveVoiceCount"]=()=>(_getActiveVoiceCount=Module["_getActiveVoiceCount"]=wasmExports["va"])();var _countAudioSource=Module["_countAudioSource"]=a0=>(_countAudioSource=Module["_countAudioSource"]=wasmExports["wa"])(a0);var _getVoiceCount=Module["_getVoiceCount"]=()=>(_getVoiceCount=Module["_getVoiceCount"]=wasmExports["xa"])();var _getProtectVoice=Module["_getProtectVoice"]=a0=>(_getProtectVoice=Module["_getProtectVoice"]=wasmExports["ya"])(a0);var _setProtectVoice=Module["_setProtectVoice"]=(a0,a1)=>(_setProtectVoice=Module["_setProtectVoice"]=wasmExports["za"])(a0,a1);var _getMaxActiveVoiceCount=Module["_getMaxActiveVoiceCount"]=()=>(_getMaxActiveVoiceCount=Module["_getMaxActiveVoiceCount"]=wasmExports["Aa"])();var _setMaxActiveVoiceCount=Module["_setMaxActiveVoiceCount"]=a0=>(_setMaxActiveVoiceCount=Module["_setMaxActiveVoiceCount"]=wasmExports["Ba"])(a0);var _createVoiceGroup=Module["_createVoiceGroup"]=()=>(_createVoiceGroup=Module["_createVoiceGroup"]=wasmExports["Ca"])();var _destroyVoiceGroup=Module["_destroyVoiceGroup"]=a0=>(_destroyVoiceGroup=Module["_destroyVoiceGroup"]=wasmExports["Da"])(a0);var _addVoiceToGroup=Module["_addVoiceToGroup"]=(a0,a1)=>(_addVoiceToGroup=Module["_addVoiceToGroup"]=wasmExports["Ea"])(a0,a1);var _isVoiceGroup=Module["_isVoiceGroup"]=a0=>(_isVoiceGroup=Module["_isVoiceGroup"]=wasmExports["Fa"])(a0);var _isVoiceGroupEmpty=Module["_isVoiceGroupEmpty"]=a0=>(_isVoiceGroupEmpty=Module["_isVoiceGroupEmpty"]=wasmExports["Ga"])(a0);var _fadeGlobalVolume=Module["_fadeGlobalVolume"]=(a0,a1)=>(_fadeGlobalVolume=Module["_fadeGlobalVolume"]=wasmExports["Ha"])(a0,a1);var _fadeVolume=Module["_fadeVolume"]=(a0,a1,a2)=>(_fadeVolume=Module["_fadeVolume"]=wasmExports["Ia"])(a0,a1,a2);var _fadePan=Module["_fadePan"]=(a0,a1,a2)=>(_fadePan=Module["_fadePan"]=wasmExports["Ja"])(a0,a1,a2);var _fadeRelativePlaySpeed=Module["_fadeRelativePlaySpeed"]=(a0,a1,a2)=>(_fadeRelativePlaySpeed=Module["_fadeRelativePlaySpeed"]=wasmExports["Ka"])(a0,a1,a2);var _schedulePause=Module["_schedulePause"]=(a0,a1)=>(_schedulePause=Module["_schedulePause"]=wasmExports["La"])(a0,a1);var _scheduleStop=Module["_scheduleStop"]=(a0,a1)=>(_scheduleStop=Module["_scheduleStop"]=wasmExports["Ma"])(a0,a1);var _oscillateVolume=Module["_oscillateVolume"]=(a0,a1,a2,a3)=>(_oscillateVolume=Module["_oscillateVolume"]=wasmExports["Na"])(a0,a1,a2,a3);var _oscillatePan=Module["_oscillatePan"]=(a0,a1,a2,a3)=>(_oscillatePan=Module["_oscillatePan"]=wasmExports["Oa"])(a0,a1,a2,a3);var _oscillateRelativePlaySpeed=Module["_oscillateRelativePlaySpeed"]=(a0,a1,a2,a3)=>(_oscillateRelativePlaySpeed=Module["_oscillateRelativePlaySpeed"]=wasmExports["Pa"])(a0,a1,a2,a3);var _oscillateGlobalVolume=Module["_oscillateGlobalVolume"]=(a0,a1,a2)=>(_oscillateGlobalVolume=Module["_oscillateGlobalVolume"]=wasmExports["Qa"])(a0,a1,a2);var _isFilterActive=Module["_isFilterActive"]=(a0,a1,a2)=>(_isFilterActive=Module["_isFilterActive"]=wasmExports["Ra"])(a0,a1,a2);var _getFilterParamNames=Module["_getFilterParamNames"]=(a0,a1,a2)=>(_getFilterParamNames=Module["_getFilterParamNames"]=wasmExports["Sa"])(a0,a1,a2);var _addFilter=Module["_addFilter"]=(a0,a1)=>(_addFilter=Module["_addFilter"]=wasmExports["Ta"])(a0,a1);var _removeFilter=Module["_removeFilter"]=(a0,a1)=>(_removeFilter=Module["_removeFilter"]=wasmExports["Ua"])(a0,a1);var _setFilterParams=Module["_setFilterParams"]=(a0,a1,a2,a3)=>(_setFilterParams=Module["_setFilterParams"]=wasmExports["Va"])(a0,a1,a2,a3);var _getFilterParams=Module["_getFilterParams"]=(a0,a1,a2,a3)=>(_getFilterParams=Module["_getFilterParams"]=wasmExports["Wa"])(a0,a1,a2,a3);var _fadeFilterParameter=Module["_fadeFilterParameter"]=(a0,a1,a2,a3,a4)=>(_fadeFilterParameter=Module["_fadeFilterParameter"]=wasmExports["Xa"])(a0,a1,a2,a3,a4);var _oscillateFilterParameter=Module["_oscillateFilterParameter"]=(a0,a1,a2,a3,a4,a5)=>(_oscillateFilterParameter=Module["_oscillateFilterParameter"]=wasmExports["Ya"])(a0,a1,a2,a3,a4,a5);var _play3d=Module["_play3d"]=(a0,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11)=>(_play3d=Module["_play3d"]=wasmExports["Za"])(a0,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11);var _set3dSoundSpeed=Module["_set3dSoundSpeed"]=a0=>(_set3dSoundSpeed=Module["_set3dSoundSpeed"]=wasmExports["_a"])(a0);var _get3dSoundSpeed=Module["_get3dSoundSpeed"]=()=>(_get3dSoundSpeed=Module["_get3dSoundSpeed"]=wasmExports["$a"])();var _set3dListenerParameters=Module["_set3dListenerParameters"]=(a0,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11)=>(_set3dListenerParameters=Module["_set3dListenerParameters"]=wasmExports["ab"])(a0,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11);var _set3dListenerPosition=Module["_set3dListenerPosition"]=(a0,a1,a2)=>(_set3dListenerPosition=Module["_set3dListenerPosition"]=wasmExports["bb"])(a0,a1,a2);var _set3dListenerAt=Module["_set3dListenerAt"]=(a0,a1,a2)=>(_set3dListenerAt=Module["_set3dListenerAt"]=wasmExports["cb"])(a0,a1,a2);var _set3dListenerUp=Module["_set3dListenerUp"]=(a0,a1,a2)=>(_set3dListenerUp=Module["_set3dListenerUp"]=wasmExports["db"])(a0,a1,a2);var _set3dListenerVelocity=Module["_set3dListenerVelocity"]=(a0,a1,a2)=>(_set3dListenerVelocity=Module["_set3dListenerVelocity"]=wasmExports["eb"])(a0,a1,a2);var _set3dSourceParameters=Module["_set3dSourceParameters"]=(a0,a1,a2,a3,a4,a5,a6)=>(_set3dSourceParameters=Module["_set3dSourceParameters"]=wasmExports["fb"])(a0,a1,a2,a3,a4,a5,a6);var _set3dSourcePosition=Module["_set3dSourcePosition"]=(a0,a1,a2,a3)=>(_set3dSourcePosition=Module["_set3dSourcePosition"]=wasmExports["gb"])(a0,a1,a2,a3);var _set3dSourceVelocity=Module["_set3dSourceVelocity"]=(a0,a1,a2,a3)=>(_set3dSourceVelocity=Module["_set3dSourceVelocity"]=wasmExports["hb"])(a0,a1,a2,a3);var _set3dSourceMinMaxDistance=Module["_set3dSourceMinMaxDistance"]=(a0,a1,a2)=>(_set3dSourceMinMaxDistance=Module["_set3dSourceMinMaxDistance"]=wasmExports["ib"])(a0,a1,a2);var _set3dSourceAttenuation=Module["_set3dSourceAttenuation"]=(a0,a1,a2)=>(_set3dSourceAttenuation=Module["_set3dSourceAttenuation"]=wasmExports["jb"])(a0,a1,a2);var _set3dSourceDopplerFactor=Module["_set3dSourceDopplerFactor"]=(a0,a1)=>(_set3dSourceDopplerFactor=Module["_set3dSourceDopplerFactor"]=wasmExports["kb"])(a0,a1);var _emscripten_get_sbrk_ptr=()=>(_emscripten_get_sbrk_ptr=wasmExports["lb"])();var _sbrk=a0=>(_sbrk=wasmExports["mb"])(a0);var _emscripten_stack_get_base=()=>(_emscripten_stack_get_base=wasmExports["nb"])();var __emscripten_stack_restore=a0=>(__emscripten_stack_restore=wasmExports["ob"])(a0);var __emscripten_stack_alloc=a0=>(__emscripten_stack_alloc=wasmExports["pb"])(a0);var _emscripten_stack_get_current=()=>(_emscripten_stack_get_current=wasmExports["qb"])();var ___cxa_is_pointer_type=a0=>(___cxa_is_pointer_type=wasmExports["rb"])(a0);var dynCall_iiiji=Module["dynCall_iiiji"]=(a0,a1,a2,a3,a4,a5)=>(dynCall_iiiji=Module["dynCall_iiiji"]=wasmExports["sb"])(a0,a1,a2,a3,a4,a5);var dynCall_jii=Module["dynCall_jii"]=(a0,a1,a2)=>(dynCall_jii=Module["dynCall_jii"]=wasmExports["tb"])(a0,a1,a2);var dynCall_jiji=Module["dynCall_jiji"]=(a0,a1,a2,a3,a4)=>(dynCall_jiji=Module["dynCall_jiji"]=wasmExports["ub"])(a0,a1,a2,a3,a4);Module["ccall"]=ccall;Module["cwrap"]=cwrap;var calledRun;dependenciesFulfilled=function runCaller(){if(!calledRun)run();if(!calledRun)dependenciesFulfilled=runCaller};function run(){if(runDependencies>0){return}preRun();if(runDependencies>0){return}function doRun(){if(calledRun)return;calledRun=true;Module["calledRun"]=true;if(ABORT)return;initRuntime();if(Module["onRuntimeInitialized"])Module["onRuntimeInitialized"]();postRun()}if(Module["setStatus"]){Module["setStatus"]("Running...");setTimeout(function(){setTimeout(function(){Module["setStatus"]("")},1);doRun()},1)}else{doRun()}}if(Module["preInit"]){if(typeof Module["preInit"]=="function")Module["preInit"]=[Module["preInit"]];while(Module["preInit"].length>0){Module["preInit"].pop()()}}run(); +var Module=typeof Module!="undefined"?Module:{};var ENVIRONMENT_IS_WEB=typeof window=="object";var ENVIRONMENT_IS_WORKER=typeof importScripts=="function";var ENVIRONMENT_IS_NODE=typeof process=="object"&&typeof process.versions=="object"&&typeof process.versions.node=="string";if(ENVIRONMENT_IS_NODE){}var moduleOverrides=Object.assign({},Module);var arguments_=[];var thisProgram="./this.program";var quit_=(status,toThrow)=>{throw toThrow};var scriptDirectory="";function locateFile(path){if(Module["locateFile"]){return Module["locateFile"](path,scriptDirectory)}return scriptDirectory+path}var read_,readAsync,readBinary;if(ENVIRONMENT_IS_NODE){var fs=require("fs");var nodePath=require("path");scriptDirectory=__dirname+"/";read_=(filename,binary)=>{filename=isFileURI(filename)?new URL(filename):nodePath.normalize(filename);return fs.readFileSync(filename,binary?undefined:"utf8")};readBinary=filename=>{var ret=read_(filename,true);if(!ret.buffer){ret=new Uint8Array(ret)}return ret};readAsync=(filename,onload,onerror,binary=true)=>{filename=isFileURI(filename)?new URL(filename):nodePath.normalize(filename);fs.readFile(filename,binary?undefined:"utf8",(err,data)=>{if(err)onerror(err);else onload(binary?data.buffer:data)})};if(!Module["thisProgram"]&&process.argv.length>1){thisProgram=process.argv[1].replace(/\\/g,"/")}arguments_=process.argv.slice(2);if(typeof module!="undefined"){module["exports"]=Module}process.on("uncaughtException",ex=>{if(ex!=="unwind"&&!(ex instanceof ExitStatus)&&!(ex.context instanceof ExitStatus)){throw ex}});quit_=(status,toThrow)=>{process.exitCode=status;throw toThrow}}else if(ENVIRONMENT_IS_WEB||ENVIRONMENT_IS_WORKER){if(ENVIRONMENT_IS_WORKER){scriptDirectory=self.location.href}else if(typeof document!="undefined"&&document.currentScript){scriptDirectory=document.currentScript.src}if(scriptDirectory.startsWith("blob:")){scriptDirectory=""}else{scriptDirectory=scriptDirectory.substr(0,scriptDirectory.replace(/[?#].*/,"").lastIndexOf("/")+1)}{read_=url=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.send(null);return xhr.responseText};if(ENVIRONMENT_IS_WORKER){readBinary=url=>{var xhr=new XMLHttpRequest;xhr.open("GET",url,false);xhr.responseType="arraybuffer";xhr.send(null);return new Uint8Array(xhr.response)}}readAsync=(url,onload,onerror)=>{if(isFileURI(url)){var xhr=new XMLHttpRequest;xhr.open("GET",url,true);xhr.responseType="arraybuffer";xhr.onload=()=>{if(xhr.status==200||xhr.status==0&&xhr.response){onload(xhr.response);return}onerror()};xhr.onerror=onerror;xhr.send(null);return}fetch(url,{credentials:"same-origin"}).then(response=>{if(response.ok){return response.arrayBuffer()}return Promise.reject(new Error(response.status+" : "+response.url))}).then(onload,onerror)}}}else{}var out=Module["print"]||console.log.bind(console);var err=Module["printErr"]||console.error.bind(console);Object.assign(Module,moduleOverrides);moduleOverrides=null;if(Module["arguments"])arguments_=Module["arguments"];if(Module["thisProgram"])thisProgram=Module["thisProgram"];if(Module["quit"])quit_=Module["quit"];var wasmBinary;if(Module["wasmBinary"])wasmBinary=Module["wasmBinary"];function getSafeHeapType(bytes,isFloat){switch(bytes){case 1:return"i8";case 2:return"i16";case 4:return isFloat?"float":"i32";case 8:return isFloat?"double":"i64";default:abort(`getSafeHeapType() invalid bytes=${bytes}`)}}function SAFE_HEAP_STORE(dest,value,bytes,isFloat){if(dest<=0)abort(`segmentation fault storing ${bytes} bytes to address ${dest}`);if(dest%bytes!==0)abort(`alignment error storing to address ${dest}, which was expected to be aligned to a multiple of ${bytes}`);if(runtimeInitialized){var brk=_sbrk(0);if(dest+bytes>brk)abort(`segmentation fault, exceeded the top of the available dynamic heap when storing ${bytes} bytes to address ${dest}. DYNAMICTOP=${brk}`);if(brk<_emscripten_stack_get_base())abort(`brk >= _emscripten_stack_get_base() (brk=${brk}, _emscripten_stack_get_base()=${_emscripten_stack_get_base()})`);if(brk>wasmMemory.buffer.byteLength)abort(`brk <= wasmMemory.buffer.byteLength (brk=${brk}, wasmMemory.buffer.byteLength=${wasmMemory.buffer.byteLength})`)}setValue_safe(dest,value,getSafeHeapType(bytes,isFloat));return value}function SAFE_HEAP_STORE_D(dest,value,bytes){return SAFE_HEAP_STORE(dest,value,bytes,true)}function SAFE_HEAP_LOAD(dest,bytes,unsigned,isFloat){if(dest<=0)abort(`segmentation fault loading ${bytes} bytes from address ${dest}`);if(dest%bytes!==0)abort(`alignment error loading from address ${dest}, which was expected to be aligned to a multiple of ${bytes}`);if(runtimeInitialized){var brk=_sbrk(0);if(dest+bytes>brk)abort(`segmentation fault, exceeded the top of the available dynamic heap when loading ${bytes} bytes from address ${dest}. DYNAMICTOP=${brk}`);if(brk<_emscripten_stack_get_base())abort(`brk >= _emscripten_stack_get_base() (brk=${brk}, _emscripten_stack_get_base()=${_emscripten_stack_get_base()})`);if(brk>wasmMemory.buffer.byteLength)abort(`brk <= wasmMemory.buffer.byteLength (brk=${brk}, wasmMemory.buffer.byteLength=${wasmMemory.buffer.byteLength})`)}var type=getSafeHeapType(bytes,isFloat);var ret=getValue_safe(dest,type);if(unsigned)ret=unSign(ret,parseInt(type.substr(1),10));return ret}function SAFE_HEAP_LOAD_D(dest,bytes,unsigned){return SAFE_HEAP_LOAD(dest,bytes,unsigned,true)}function segfault(){abort("segmentation fault")}function alignfault(){abort("alignment fault")}var wasmMemory;var ABORT=false;var EXITSTATUS;var HEAP8,HEAPU8,HEAP16,HEAPU16,HEAP32,HEAPU32,HEAPF32,HEAPF64;function updateMemoryViews(){var b=wasmMemory.buffer;Module["HEAP8"]=HEAP8=new Int8Array(b);Module["HEAP16"]=HEAP16=new Int16Array(b);Module["HEAPU8"]=HEAPU8=new Uint8Array(b);Module["HEAPU16"]=HEAPU16=new Uint16Array(b);Module["HEAP32"]=HEAP32=new Int32Array(b);Module["HEAPU32"]=HEAPU32=new Uint32Array(b);Module["HEAPF32"]=HEAPF32=new Float32Array(b);Module["HEAPF64"]=HEAPF64=new Float64Array(b)}var __ATPRERUN__=[];var __ATINIT__=[];var __ATPOSTRUN__=[];var runtimeInitialized=false;function preRun(){if(Module["preRun"]){if(typeof Module["preRun"]=="function")Module["preRun"]=[Module["preRun"]];while(Module["preRun"].length){addOnPreRun(Module["preRun"].shift())}}callRuntimeCallbacks(__ATPRERUN__)}function initRuntime(){runtimeInitialized=true;if(!Module["noFSInit"]&&!FS.init.initialized)FS.init();FS.ignorePermissions=false;TTY.init();callRuntimeCallbacks(__ATINIT__)}function postRun(){if(Module["postRun"]){if(typeof Module["postRun"]=="function")Module["postRun"]=[Module["postRun"]];while(Module["postRun"].length){addOnPostRun(Module["postRun"].shift())}}callRuntimeCallbacks(__ATPOSTRUN__)}function addOnPreRun(cb){__ATPRERUN__.unshift(cb)}function addOnInit(cb){__ATINIT__.unshift(cb)}function addOnPostRun(cb){__ATPOSTRUN__.unshift(cb)}var runDependencies=0;var runDependencyWatcher=null;var dependenciesFulfilled=null;function getUniqueRunDependency(id){return id}function addRunDependency(id){runDependencies++;Module["monitorRunDependencies"]?.(runDependencies)}function removeRunDependency(id){runDependencies--;Module["monitorRunDependencies"]?.(runDependencies);if(runDependencies==0){if(runDependencyWatcher!==null){clearInterval(runDependencyWatcher);runDependencyWatcher=null}if(dependenciesFulfilled){var callback=dependenciesFulfilled;dependenciesFulfilled=null;callback()}}}function abort(what){Module["onAbort"]?.(what);what="Aborted("+what+")";err(what);ABORT=true;EXITSTATUS=1;what+=". Build with -sASSERTIONS for more info.";var e=new WebAssembly.RuntimeError(what);throw e}var dataURIPrefix="data:application/octet-stream;base64,";var isDataURI=filename=>filename.startsWith(dataURIPrefix);var isFileURI=filename=>filename.startsWith("file://");function findWasmBinary(){var f="libflutter_soloud_plugin.wasm";if(!isDataURI(f)){return locateFile(f)}return f}var wasmBinaryFile;function getBinarySync(file){if(file==wasmBinaryFile&&wasmBinary){return new Uint8Array(wasmBinary)}if(readBinary){return readBinary(file)}throw"both async and sync fetching of the wasm failed"}function getBinaryPromise(binaryFile){if(!wasmBinary){return new Promise((resolve,reject)=>{readAsync(binaryFile,response=>resolve(new Uint8Array(response)),error=>{try{resolve(getBinarySync(binaryFile))}catch(e){reject(e)}})})}return Promise.resolve().then(()=>getBinarySync(binaryFile))}function instantiateArrayBuffer(binaryFile,imports,receiver){return getBinaryPromise(binaryFile).then(binary=>WebAssembly.instantiate(binary,imports)).then(receiver,reason=>{err(`failed to asynchronously prepare wasm: ${reason}`);abort(reason)})}function instantiateAsync(binary,binaryFile,imports,callback){if(!binary&&typeof WebAssembly.instantiateStreaming=="function"&&!isDataURI(binaryFile)&&!isFileURI(binaryFile)&&!ENVIRONMENT_IS_NODE&&typeof fetch=="function"){return fetch(binaryFile,{credentials:"same-origin"}).then(response=>{var result=WebAssembly.instantiateStreaming(response,imports);return result.then(callback,function(reason){err(`wasm streaming compile failed: ${reason}`);err("falling back to ArrayBuffer instantiation");return instantiateArrayBuffer(binaryFile,imports,callback)})})}return instantiateArrayBuffer(binaryFile,imports,callback)}function getWasmImports(){return{a:wasmImports}}function createWasm(){var info=getWasmImports();function receiveInstance(instance,module){wasmExports=instance.exports;wasmMemory=wasmExports["q"];updateMemoryViews();addOnInit(wasmExports["r"]);removeRunDependency("wasm-instantiate");return wasmExports}addRunDependency("wasm-instantiate");function receiveInstantiationResult(result){receiveInstance(result["instance"])}if(Module["instantiateWasm"]){try{return Module["instantiateWasm"](info,receiveInstance)}catch(e){err(`Module.instantiateWasm callback failed with error: ${e}`);return false}}if(!wasmBinaryFile)wasmBinaryFile=findWasmBinary();instantiateAsync(wasmBinary,wasmBinaryFile,info,receiveInstantiationResult);return{}}var tempDouble;var tempI64;var ASM_CONSTS={71120:($0,$1,$2,$3,$4)=>{if(typeof window==="undefined"||(window.AudioContext||window.webkitAudioContext)===undefined){return 0}if(typeof window.miniaudio==="undefined"){window.miniaudio={referenceCount:0};window.miniaudio.device_type={};window.miniaudio.device_type.playback=$0;window.miniaudio.device_type.capture=$1;window.miniaudio.device_type.duplex=$2;window.miniaudio.device_state={};window.miniaudio.device_state.stopped=$3;window.miniaudio.device_state.started=$4;let miniaudio=window.miniaudio;miniaudio.devices=[];miniaudio.track_device=function(device){for(var iDevice=0;iDevice0){if(miniaudio.devices[miniaudio.devices.length-1]==null){miniaudio.devices.pop()}else{break}}};miniaudio.untrack_device=function(device){for(var iDevice=0;iDevice{_ma_device__on_notification_unlocked(device.pDevice)},error=>{console.error("Failed to resume audiocontext",error)})}}miniaudio.unlock_event_types.map(function(event_type){document.removeEventListener(event_type,miniaudio.unlock,true)})};miniaudio.unlock_event_types.map(function(event_type){document.addEventListener(event_type,miniaudio.unlock,true)})}window.miniaudio.referenceCount+=1;return 1},73298:()=>{if(typeof window.miniaudio!=="undefined"){miniaudio.unlock_event_types.map(function(event_type){document.removeEventListener(event_type,miniaudio.unlock,true)});window.miniaudio.referenceCount-=1;if(window.miniaudio.referenceCount===0){delete window.miniaudio}}},73588:()=>navigator.mediaDevices!==undefined&&navigator.mediaDevices.getUserMedia!==undefined,73692:()=>{try{var temp=new(window.AudioContext||window.webkitAudioContext);var sampleRate=temp.sampleRate;temp.close();return sampleRate}catch(e){return 0}},73863:($0,$1,$2,$3,$4,$5)=>{var deviceType=$0;var channels=$1;var sampleRate=$2;var bufferSize=$3;var pIntermediaryBuffer=$4;var pDevice=$5;if(typeof window.miniaudio==="undefined"){return-1}var device={};var audioContextOptions={};if(deviceType==window.miniaudio.device_type.playback&&sampleRate!=0){audioContextOptions.sampleRate=sampleRate}device.webaudio=new(window.AudioContext||window.webkitAudioContext)(audioContextOptions);device.webaudio.suspend();device.state=window.miniaudio.device_state.stopped;var channelCountIn=0;var channelCountOut=channels;if(deviceType!=window.miniaudio.device_type.playback){channelCountIn=channels}device.scriptNode=device.webaudio.createScriptProcessor(bufferSize,channelCountIn,channelCountOut);device.scriptNode.onaudioprocess=function(e){if(device.intermediaryBufferView==null||device.intermediaryBufferView.length==0){device.intermediaryBufferView=new Float32Array(HEAPF32.buffer,pIntermediaryBuffer,bufferSize*channels)}if(deviceType==window.miniaudio.device_type.capture||deviceType==window.miniaudio.device_type.duplex){for(var iChannel=0;iChannelwindow.miniaudio.get_device_by_index($0).webaudio.sampleRate,76813:$0=>{var device=window.miniaudio.get_device_by_index($0);if(device.scriptNode!==undefined){device.scriptNode.onaudioprocess=function(e){};device.scriptNode.disconnect();device.scriptNode=undefined}if(device.streamNode!==undefined){device.streamNode.disconnect();device.streamNode=undefined}device.webaudio.close();device.webaudio=undefined;device.pDevice=undefined},77213:$0=>{window.miniaudio.untrack_device_by_index($0)},77263:$0=>{var device=window.miniaudio.get_device_by_index($0);device.webaudio.resume();device.state=window.miniaudio.device_state.started},77402:$0=>{var device=window.miniaudio.get_device_by_index($0);device.webaudio.suspend();device.state=window.miniaudio.device_state.stopped},77542:()=>{if(!Module.wasmWorker){var workerUri="assets/packages/flutter_soloud/web/worker.dart.js";console.log("EM_ASM creating web worker!");Module.wasmWorker=new Worker(workerUri)}else{console.log("EM_ASM web worker already created!")}},77790:($0,$1)=>{if(Module.wasmWorker){console.log("EM_ASM posting message "+UTF8ToString($0)+" with value "+$1);Module.wasmWorker.postMessage(JSON.stringify({message:UTF8ToString($0),value:$1}))}else{console.error("Worker not found.")}}};function ExitStatus(status){this.name="ExitStatus";this.message=`Program terminated with exit(${status})`;this.status=status}Module["ExitStatus"]=ExitStatus;var callRuntimeCallbacks=callbacks=>{while(callbacks.length>0){callbacks.shift()(Module)}};Module["callRuntimeCallbacks"]=callRuntimeCallbacks;function getValue(ptr,type="i8"){if(type.endsWith("*"))type="*";switch(type){case"i1":return SAFE_HEAP_LOAD(ptr,1,0);case"i8":return SAFE_HEAP_LOAD(ptr,1,0);case"i16":return SAFE_HEAP_LOAD((ptr>>1)*2,2,0);case"i32":return SAFE_HEAP_LOAD((ptr>>2)*4,4,0);case"i64":abort("to do getValue(i64) use WASM_BIGINT");case"float":return SAFE_HEAP_LOAD_D((ptr>>2)*4,4,0);case"double":return SAFE_HEAP_LOAD_D((ptr>>3)*8,8,0);case"*":return SAFE_HEAP_LOAD((ptr>>2)*4,4,1);default:abort(`invalid type for getValue: ${type}`)}}Module["getValue"]=getValue;function getValue_safe(ptr,type="i8"){if(type.endsWith("*"))type="*";switch(type){case"i1":return HEAP8[ptr];case"i8":return HEAP8[ptr];case"i16":return HEAP16[ptr>>1];case"i32":return HEAP32[ptr>>2];case"i64":abort("to do getValue(i64) use WASM_BIGINT");case"float":return HEAPF32[ptr>>2];case"double":return HEAPF64[ptr>>3];case"*":return HEAPU32[ptr>>2];default:abort(`invalid type for getValue: ${type}`)}}Module["getValue_safe"]=getValue_safe;var noExitRuntime=Module["noExitRuntime"]||true;Module["noExitRuntime"]=noExitRuntime;function setValue(ptr,value,type="i8"){if(type.endsWith("*"))type="*";switch(type){case"i1":SAFE_HEAP_STORE(ptr,value,1);break;case"i8":SAFE_HEAP_STORE(ptr,value,1);break;case"i16":SAFE_HEAP_STORE((ptr>>1)*2,value,2);break;case"i32":SAFE_HEAP_STORE((ptr>>2)*4,value,4);break;case"i64":abort("to do setValue(i64) use WASM_BIGINT");case"float":SAFE_HEAP_STORE_D((ptr>>2)*4,value,4);break;case"double":SAFE_HEAP_STORE_D((ptr>>3)*8,value,8);break;case"*":SAFE_HEAP_STORE((ptr>>2)*4,value,4);break;default:abort(`invalid type for setValue: ${type}`)}}Module["setValue"]=setValue;function setValue_safe(ptr,value,type="i8"){if(type.endsWith("*"))type="*";switch(type){case"i1":HEAP8[ptr]=value;break;case"i8":HEAP8[ptr]=value;break;case"i16":HEAP16[ptr>>1]=value;break;case"i32":HEAP32[ptr>>2]=value;break;case"i64":abort("to do setValue(i64) use WASM_BIGINT");case"float":HEAPF32[ptr>>2]=value;break;case"double":HEAPF64[ptr>>3]=value;break;case"*":HEAPU32[ptr>>2]=value;break;default:abort(`invalid type for setValue: ${type}`)}}Module["setValue_safe"]=setValue_safe;var stackRestore=val=>__emscripten_stack_restore(val);Module["stackRestore"]=stackRestore;var stackSave=()=>_emscripten_stack_get_current();Module["stackSave"]=stackSave;var unSign=(value,bits)=>{if(value>=0){return value}return bits<=32?2*Math.abs(1<{var endIdx=idx+maxBytesToRead;var endPtr=idx;while(heapOrArray[endPtr]&&!(endPtr>=endIdx))++endPtr;if(endPtr-idx>16&&heapOrArray.buffer&&UTF8Decoder){return UTF8Decoder.decode(heapOrArray.subarray(idx,endPtr))}var str="";while(idx>10,56320|ch&1023)}}return str};Module["UTF8ArrayToString"]=UTF8ArrayToString;var UTF8ToString=(ptr,maxBytesToRead)=>ptr?UTF8ArrayToString(HEAPU8,ptr,maxBytesToRead):"";Module["UTF8ToString"]=UTF8ToString;var ___assert_fail=(condition,filename,line,func)=>{abort(`Assertion failed: ${UTF8ToString(condition)}, at: `+[filename?UTF8ToString(filename):"unknown filename",line,func?UTF8ToString(func):"unknown function"])};Module["___assert_fail"]=___assert_fail;class ExceptionInfo{constructor(excPtr){this.excPtr=excPtr;this.ptr=excPtr-24}set_type(type){SAFE_HEAP_STORE((this.ptr+4>>2)*4,type,4)}get_type(){return SAFE_HEAP_LOAD((this.ptr+4>>2)*4,4,1)}set_destructor(destructor){SAFE_HEAP_STORE((this.ptr+8>>2)*4,destructor,4)}get_destructor(){return SAFE_HEAP_LOAD((this.ptr+8>>2)*4,4,1)}set_caught(caught){caught=caught?1:0;SAFE_HEAP_STORE(this.ptr+12,caught,1)}get_caught(){return SAFE_HEAP_LOAD(this.ptr+12,1,0)!=0}set_rethrown(rethrown){rethrown=rethrown?1:0;SAFE_HEAP_STORE(this.ptr+13,rethrown,1)}get_rethrown(){return SAFE_HEAP_LOAD(this.ptr+13,1,0)!=0}init(type,destructor){this.set_adjusted_ptr(0);this.set_type(type);this.set_destructor(destructor)}set_adjusted_ptr(adjustedPtr){SAFE_HEAP_STORE((this.ptr+16>>2)*4,adjustedPtr,4)}get_adjusted_ptr(){return SAFE_HEAP_LOAD((this.ptr+16>>2)*4,4,1)}get_exception_ptr(){var isPointer=___cxa_is_pointer_type(this.get_type());if(isPointer){return SAFE_HEAP_LOAD((this.excPtr>>2)*4,4,1)}var adjusted=this.get_adjusted_ptr();if(adjusted!==0)return adjusted;return this.excPtr}}Module["ExceptionInfo"]=ExceptionInfo;var exceptionLast=0;Module["exceptionLast"]=exceptionLast;var uncaughtExceptionCount=0;Module["uncaughtExceptionCount"]=uncaughtExceptionCount;var ___cxa_throw=(ptr,type,destructor)=>{var info=new ExceptionInfo(ptr);info.init(type,destructor);exceptionLast=ptr;uncaughtExceptionCount++;throw exceptionLast};Module["___cxa_throw"]=___cxa_throw;function syscallGetVarargI(){var ret=SAFE_HEAP_LOAD((+SYSCALLS.varargs>>2)*4,4,0);SYSCALLS.varargs+=4;return ret}Module["syscallGetVarargI"]=syscallGetVarargI;var syscallGetVarargP=syscallGetVarargI;Module["syscallGetVarargP"]=syscallGetVarargP;var PATH={isAbs:path=>path.charAt(0)==="/",splitPath:filename=>{var splitPathRe=/^(\/?|)([\s\S]*?)((?:\.{1,2}|[^\/]+?|)(\.[^.\/]*|))(?:[\/]*)$/;return splitPathRe.exec(filename).slice(1)},normalizeArray:(parts,allowAboveRoot)=>{var up=0;for(var i=parts.length-1;i>=0;i--){var last=parts[i];if(last==="."){parts.splice(i,1)}else if(last===".."){parts.splice(i,1);up++}else if(up){parts.splice(i,1);up--}}if(allowAboveRoot){for(;up;up--){parts.unshift("..")}}return parts},normalize:path=>{var isAbsolute=PATH.isAbs(path),trailingSlash=path.substr(-1)==="/";path=PATH.normalizeArray(path.split("/").filter(p=>!!p),!isAbsolute).join("/");if(!path&&!isAbsolute){path="."}if(path&&trailingSlash){path+="/"}return(isAbsolute?"/":"")+path},dirname:path=>{var result=PATH.splitPath(path),root=result[0],dir=result[1];if(!root&&!dir){return"."}if(dir){dir=dir.substr(0,dir.length-1)}return root+dir},basename:path=>{if(path==="/")return"/";path=PATH.normalize(path);path=path.replace(/\/$/,"");var lastSlash=path.lastIndexOf("/");if(lastSlash===-1)return path;return path.substr(lastSlash+1)},join:(...paths)=>PATH.normalize(paths.join("/")),join2:(l,r)=>PATH.normalize(l+"/"+r)};Module["PATH"]=PATH;var initRandomFill=()=>{if(typeof crypto=="object"&&typeof crypto["getRandomValues"]=="function"){return view=>crypto.getRandomValues(view)}else if(ENVIRONMENT_IS_NODE){try{var crypto_module=require("crypto");var randomFillSync=crypto_module["randomFillSync"];if(randomFillSync){return view=>crypto_module["randomFillSync"](view)}var randomBytes=crypto_module["randomBytes"];return view=>(view.set(randomBytes(view.byteLength)),view)}catch(e){}}abort("initRandomDevice")};Module["initRandomFill"]=initRandomFill;var randomFill=view=>(randomFill=initRandomFill())(view);Module["randomFill"]=randomFill;var PATH_FS={resolve:(...args)=>{var resolvedPath="",resolvedAbsolute=false;for(var i=args.length-1;i>=-1&&!resolvedAbsolute;i--){var path=i>=0?args[i]:FS.cwd();if(typeof path!="string"){throw new TypeError("Arguments to path.resolve must be strings")}else if(!path){return""}resolvedPath=path+"/"+resolvedPath;resolvedAbsolute=PATH.isAbs(path)}resolvedPath=PATH.normalizeArray(resolvedPath.split("/").filter(p=>!!p),!resolvedAbsolute).join("/");return(resolvedAbsolute?"/":"")+resolvedPath||"."},relative:(from,to)=>{from=PATH_FS.resolve(from).substr(1);to=PATH_FS.resolve(to).substr(1);function trim(arr){var start=0;for(;start=0;end--){if(arr[end]!=="")break}if(start>end)return[];return arr.slice(start,end-start+1)}var fromParts=trim(from.split("/"));var toParts=trim(to.split("/"));var length=Math.min(fromParts.length,toParts.length);var samePartsLength=length;for(var i=0;i{var len=0;for(var i=0;i=55296&&c<=57343){len+=4;++i}else{len+=3}}return len};Module["lengthBytesUTF8"]=lengthBytesUTF8;var stringToUTF8Array=(str,heap,outIdx,maxBytesToWrite)=>{if(!(maxBytesToWrite>0))return 0;var startIdx=outIdx;var endIdx=outIdx+maxBytesToWrite-1;for(var i=0;i=55296&&u<=57343){var u1=str.charCodeAt(++i);u=65536+((u&1023)<<10)|u1&1023}if(u<=127){if(outIdx>=endIdx)break;heap[outIdx++]=u}else if(u<=2047){if(outIdx+1>=endIdx)break;heap[outIdx++]=192|u>>6;heap[outIdx++]=128|u&63}else if(u<=65535){if(outIdx+2>=endIdx)break;heap[outIdx++]=224|u>>12;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}else{if(outIdx+3>=endIdx)break;heap[outIdx++]=240|u>>18;heap[outIdx++]=128|u>>12&63;heap[outIdx++]=128|u>>6&63;heap[outIdx++]=128|u&63}}heap[outIdx]=0;return outIdx-startIdx};Module["stringToUTF8Array"]=stringToUTF8Array;function intArrayFromString(stringy,dontAddNull,length){var len=length>0?length:lengthBytesUTF8(stringy)+1;var u8array=new Array(len);var numBytesWritten=stringToUTF8Array(stringy,u8array,0,u8array.length);if(dontAddNull)u8array.length=numBytesWritten;return u8array}Module["intArrayFromString"]=intArrayFromString;var FS_stdin_getChar=()=>{if(!FS_stdin_getChar_buffer.length){var result=null;if(ENVIRONMENT_IS_NODE){var BUFSIZE=256;var buf=Buffer.alloc(BUFSIZE);var bytesRead=0;var fd=process.stdin.fd;try{bytesRead=fs.readSync(fd,buf,0,BUFSIZE)}catch(e){if(e.toString().includes("EOF"))bytesRead=0;else throw e}if(bytesRead>0){result=buf.slice(0,bytesRead).toString("utf-8")}}else if(typeof window!="undefined"&&typeof window.prompt=="function"){result=window.prompt("Input: ");if(result!==null){result+="\n"}}else{}if(!result){return null}FS_stdin_getChar_buffer=intArrayFromString(result,true)}return FS_stdin_getChar_buffer.shift()};Module["FS_stdin_getChar"]=FS_stdin_getChar;var TTY={ttys:[],init(){},shutdown(){},register(dev,ops){TTY.ttys[dev]={input:[],output:[],ops:ops};FS.registerDevice(dev,TTY.stream_ops)},stream_ops:{open(stream){var tty=TTY.ttys[stream.node.rdev];if(!tty){throw new FS.ErrnoError(43)}stream.tty=tty;stream.seekable=false},close(stream){stream.tty.ops.fsync(stream.tty)},fsync(stream){stream.tty.ops.fsync(stream.tty)},read(stream,buffer,offset,length,pos){if(!stream.tty||!stream.tty.ops.get_char){throw new FS.ErrnoError(60)}var bytesRead=0;for(var i=0;i0){out(UTF8ArrayToString(tty.output,0));tty.output=[]}},ioctl_tcgets(tty){return{c_iflag:25856,c_oflag:5,c_cflag:191,c_lflag:35387,c_cc:[3,28,127,21,4,0,1,0,17,19,26,0,18,15,23,22,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0]}},ioctl_tcsets(tty,optional_actions,data){return 0},ioctl_tiocgwinsz(tty){return[24,80]}},default_tty1_ops:{put_char(tty,val){if(val===null||val===10){err(UTF8ArrayToString(tty.output,0));tty.output=[]}else{if(val!=0)tty.output.push(val)}},fsync(tty){if(tty.output&&tty.output.length>0){err(UTF8ArrayToString(tty.output,0));tty.output=[]}}}};Module["TTY"]=TTY;var zeroMemory=(address,size)=>{HEAPU8.fill(0,address,address+size);return address};Module["zeroMemory"]=zeroMemory;var alignMemory=(size,alignment)=>Math.ceil(size/alignment)*alignment;Module["alignMemory"]=alignMemory;var mmapAlloc=size=>{abort()};Module["mmapAlloc"]=mmapAlloc;var MEMFS={ops_table:null,mount(mount){return MEMFS.createNode(null,"/",16384|511,0)},createNode(parent,name,mode,dev){if(FS.isBlkdev(mode)||FS.isFIFO(mode)){throw new FS.ErrnoError(63)}MEMFS.ops_table||={dir:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr,lookup:MEMFS.node_ops.lookup,mknod:MEMFS.node_ops.mknod,rename:MEMFS.node_ops.rename,unlink:MEMFS.node_ops.unlink,rmdir:MEMFS.node_ops.rmdir,readdir:MEMFS.node_ops.readdir,symlink:MEMFS.node_ops.symlink},stream:{llseek:MEMFS.stream_ops.llseek}},file:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr},stream:{llseek:MEMFS.stream_ops.llseek,read:MEMFS.stream_ops.read,write:MEMFS.stream_ops.write,allocate:MEMFS.stream_ops.allocate,mmap:MEMFS.stream_ops.mmap,msync:MEMFS.stream_ops.msync}},link:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr,readlink:MEMFS.node_ops.readlink},stream:{}},chrdev:{node:{getattr:MEMFS.node_ops.getattr,setattr:MEMFS.node_ops.setattr},stream:FS.chrdev_stream_ops}};var node=FS.createNode(parent,name,mode,dev);if(FS.isDir(node.mode)){node.node_ops=MEMFS.ops_table.dir.node;node.stream_ops=MEMFS.ops_table.dir.stream;node.contents={}}else if(FS.isFile(node.mode)){node.node_ops=MEMFS.ops_table.file.node;node.stream_ops=MEMFS.ops_table.file.stream;node.usedBytes=0;node.contents=null}else if(FS.isLink(node.mode)){node.node_ops=MEMFS.ops_table.link.node;node.stream_ops=MEMFS.ops_table.link.stream}else if(FS.isChrdev(node.mode)){node.node_ops=MEMFS.ops_table.chrdev.node;node.stream_ops=MEMFS.ops_table.chrdev.stream}node.timestamp=Date.now();if(parent){parent.contents[name]=node;parent.timestamp=node.timestamp}return node},getFileDataAsTypedArray(node){if(!node.contents)return new Uint8Array(0);if(node.contents.subarray)return node.contents.subarray(0,node.usedBytes);return new Uint8Array(node.contents)},expandFileStorage(node,newCapacity){var prevCapacity=node.contents?node.contents.length:0;if(prevCapacity>=newCapacity)return;var CAPACITY_DOUBLING_MAX=1024*1024;newCapacity=Math.max(newCapacity,prevCapacity*(prevCapacity>>0);if(prevCapacity!=0)newCapacity=Math.max(newCapacity,256);var oldContents=node.contents;node.contents=new Uint8Array(newCapacity);if(node.usedBytes>0)node.contents.set(oldContents.subarray(0,node.usedBytes),0)},resizeFileStorage(node,newSize){if(node.usedBytes==newSize)return;if(newSize==0){node.contents=null;node.usedBytes=0}else{var oldContents=node.contents;node.contents=new Uint8Array(newSize);if(oldContents){node.contents.set(oldContents.subarray(0,Math.min(newSize,node.usedBytes)))}node.usedBytes=newSize}},node_ops:{getattr(node){var attr={};attr.dev=FS.isChrdev(node.mode)?node.id:1;attr.ino=node.id;attr.mode=node.mode;attr.nlink=1;attr.uid=0;attr.gid=0;attr.rdev=node.rdev;if(FS.isDir(node.mode)){attr.size=4096}else if(FS.isFile(node.mode)){attr.size=node.usedBytes}else if(FS.isLink(node.mode)){attr.size=node.link.length}else{attr.size=0}attr.atime=new Date(node.timestamp);attr.mtime=new Date(node.timestamp);attr.ctime=new Date(node.timestamp);attr.blksize=4096;attr.blocks=Math.ceil(attr.size/attr.blksize);return attr},setattr(node,attr){if(attr.mode!==undefined){node.mode=attr.mode}if(attr.timestamp!==undefined){node.timestamp=attr.timestamp}if(attr.size!==undefined){MEMFS.resizeFileStorage(node,attr.size)}},lookup(parent,name){throw FS.genericErrors[44]},mknod(parent,name,mode,dev){return MEMFS.createNode(parent,name,mode,dev)},rename(old_node,new_dir,new_name){if(FS.isDir(old_node.mode)){var new_node;try{new_node=FS.lookupNode(new_dir,new_name)}catch(e){}if(new_node){for(var i in new_node.contents){throw new FS.ErrnoError(55)}}}delete old_node.parent.contents[old_node.name];old_node.parent.timestamp=Date.now();old_node.name=new_name;new_dir.contents[new_name]=old_node;new_dir.timestamp=old_node.parent.timestamp},unlink(parent,name){delete parent.contents[name];parent.timestamp=Date.now()},rmdir(parent,name){var node=FS.lookupNode(parent,name);for(var i in node.contents){throw new FS.ErrnoError(55)}delete parent.contents[name];parent.timestamp=Date.now()},readdir(node){var entries=[".",".."];for(var key of Object.keys(node.contents)){entries.push(key)}return entries},symlink(parent,newname,oldpath){var node=MEMFS.createNode(parent,newname,511|40960,0);node.link=oldpath;return node},readlink(node){if(!FS.isLink(node.mode)){throw new FS.ErrnoError(28)}return node.link}},stream_ops:{read(stream,buffer,offset,length,position){var contents=stream.node.contents;if(position>=stream.node.usedBytes)return 0;var size=Math.min(stream.node.usedBytes-position,length);if(size>8&&contents.subarray){buffer.set(contents.subarray(position,position+size),offset)}else{for(var i=0;i0||position+length{var dep=!noRunDep?getUniqueRunDependency(`al ${url}`):"";readAsync(url,arrayBuffer=>{onload(new Uint8Array(arrayBuffer));if(dep)removeRunDependency(dep)},event=>{if(onerror){onerror()}else{throw`Loading data file "${url}" failed.`}});if(dep)addRunDependency(dep)};Module["asyncLoad"]=asyncLoad;var FS_createDataFile=(parent,name,fileData,canRead,canWrite,canOwn)=>{FS.createDataFile(parent,name,fileData,canRead,canWrite,canOwn)};Module["FS_createDataFile"]=FS_createDataFile;var preloadPlugins=Module["preloadPlugins"]||[];Module["preloadPlugins"]=preloadPlugins;var FS_handledByPreloadPlugin=(byteArray,fullname,finish,onerror)=>{if(typeof Browser!="undefined")Browser.init();var handled=false;preloadPlugins.forEach(plugin=>{if(handled)return;if(plugin["canHandle"](fullname)){plugin["handle"](byteArray,fullname,finish,onerror);handled=true}});return handled};Module["FS_handledByPreloadPlugin"]=FS_handledByPreloadPlugin;var FS_createPreloadedFile=(parent,name,url,canRead,canWrite,onload,onerror,dontCreateFile,canOwn,preFinish)=>{var fullname=name?PATH_FS.resolve(PATH.join2(parent,name)):parent;var dep=getUniqueRunDependency(`cp ${fullname}`);function processData(byteArray){function finish(byteArray){preFinish?.();if(!dontCreateFile){FS_createDataFile(parent,name,byteArray,canRead,canWrite,canOwn)}onload?.();removeRunDependency(dep)}if(FS_handledByPreloadPlugin(byteArray,fullname,finish,()=>{onerror?.();removeRunDependency(dep)})){return}finish(byteArray)}addRunDependency(dep);if(typeof url=="string"){asyncLoad(url,processData,onerror)}else{processData(url)}};Module["FS_createPreloadedFile"]=FS_createPreloadedFile;var FS_modeStringToFlags=str=>{var flagModes={r:0,"r+":2,w:512|64|1,"w+":512|64|2,a:1024|64|1,"a+":1024|64|2};var flags=flagModes[str];if(typeof flags=="undefined"){throw new Error(`Unknown file open mode: ${str}`)}return flags};Module["FS_modeStringToFlags"]=FS_modeStringToFlags;var FS_getMode=(canRead,canWrite)=>{var mode=0;if(canRead)mode|=292|73;if(canWrite)mode|=146;return mode};Module["FS_getMode"]=FS_getMode;var FS={root:null,mounts:[],devices:{},streams:[],nextInode:1,nameTable:null,currentPath:"/",initialized:false,ignorePermissions:true,ErrnoError:class{constructor(errno){this.name="ErrnoError";this.errno=errno}},genericErrors:{},filesystems:null,syncFSRequests:0,FSStream:class{constructor(){this.shared={}}get object(){return this.node}set object(val){this.node=val}get isRead(){return(this.flags&2097155)!==1}get isWrite(){return(this.flags&2097155)!==0}get isAppend(){return this.flags&1024}get flags(){return this.shared.flags}set flags(val){this.shared.flags=val}get position(){return this.shared.position}set position(val){this.shared.position=val}},FSNode:class{constructor(parent,name,mode,rdev){if(!parent){parent=this}this.parent=parent;this.mount=parent.mount;this.mounted=null;this.id=FS.nextInode++;this.name=name;this.mode=mode;this.node_ops={};this.stream_ops={};this.rdev=rdev;this.readMode=292|73;this.writeMode=146}get read(){return(this.mode&this.readMode)===this.readMode}set read(val){val?this.mode|=this.readMode:this.mode&=~this.readMode}get write(){return(this.mode&this.writeMode)===this.writeMode}set write(val){val?this.mode|=this.writeMode:this.mode&=~this.writeMode}get isFolder(){return FS.isDir(this.mode)}get isDevice(){return FS.isChrdev(this.mode)}},lookupPath(path,opts={}){path=PATH_FS.resolve(path);if(!path)return{path:"",node:null};var defaults={follow_mount:true,recurse_count:0};opts=Object.assign(defaults,opts);if(opts.recurse_count>8){throw new FS.ErrnoError(32)}var parts=path.split("/").filter(p=>!!p);var current=FS.root;var current_path="/";for(var i=0;i40){throw new FS.ErrnoError(32)}}}}return{path:current_path,node:current}},getPath(node){var path;while(true){if(FS.isRoot(node)){var mount=node.mount.mountpoint;if(!path)return mount;return mount[mount.length-1]!=="/"?`${mount}/${path}`:mount+path}path=path?`${node.name}/${path}`:node.name;node=node.parent}},hashName(parentid,name){var hash=0;for(var i=0;i>>0)%FS.nameTable.length},hashAddNode(node){var hash=FS.hashName(node.parent.id,node.name);node.name_next=FS.nameTable[hash];FS.nameTable[hash]=node},hashRemoveNode(node){var hash=FS.hashName(node.parent.id,node.name);if(FS.nameTable[hash]===node){FS.nameTable[hash]=node.name_next}else{var current=FS.nameTable[hash];while(current){if(current.name_next===node){current.name_next=node.name_next;break}current=current.name_next}}},lookupNode(parent,name){var errCode=FS.mayLookup(parent);if(errCode){throw new FS.ErrnoError(errCode)}var hash=FS.hashName(parent.id,name);for(var node=FS.nameTable[hash];node;node=node.name_next){var nodeName=node.name;if(node.parent.id===parent.id&&nodeName===name){return node}}return FS.lookup(parent,name)},createNode(parent,name,mode,rdev){var node=new FS.FSNode(parent,name,mode,rdev);FS.hashAddNode(node);return node},destroyNode(node){FS.hashRemoveNode(node)},isRoot(node){return node===node.parent},isMountpoint(node){return!!node.mounted},isFile(mode){return(mode&61440)===32768},isDir(mode){return(mode&61440)===16384},isLink(mode){return(mode&61440)===40960},isChrdev(mode){return(mode&61440)===8192},isBlkdev(mode){return(mode&61440)===24576},isFIFO(mode){return(mode&61440)===4096},isSocket(mode){return(mode&49152)===49152},flagsToPermissionString(flag){var perms=["r","w","rw"][flag&3];if(flag&512){perms+="w"}return perms},nodePermissions(node,perms){if(FS.ignorePermissions){return 0}if(perms.includes("r")&&!(node.mode&292)){return 2}else if(perms.includes("w")&&!(node.mode&146)){return 2}else if(perms.includes("x")&&!(node.mode&73)){return 2}return 0},mayLookup(dir){if(!FS.isDir(dir.mode))return 54;var errCode=FS.nodePermissions(dir,"x");if(errCode)return errCode;if(!dir.node_ops.lookup)return 2;return 0},mayCreate(dir,name){try{var node=FS.lookupNode(dir,name);return 20}catch(e){}return FS.nodePermissions(dir,"wx")},mayDelete(dir,name,isdir){var node;try{node=FS.lookupNode(dir,name)}catch(e){return e.errno}var errCode=FS.nodePermissions(dir,"wx");if(errCode){return errCode}if(isdir){if(!FS.isDir(node.mode)){return 54}if(FS.isRoot(node)||FS.getPath(node)===FS.cwd()){return 10}}else{if(FS.isDir(node.mode)){return 31}}return 0},mayOpen(node,flags){if(!node){return 44}if(FS.isLink(node.mode)){return 32}else if(FS.isDir(node.mode)){if(FS.flagsToPermissionString(flags)!=="r"||flags&512){return 31}}return FS.nodePermissions(node,FS.flagsToPermissionString(flags))},MAX_OPEN_FDS:4096,nextfd(){for(var fd=0;fd<=FS.MAX_OPEN_FDS;fd++){if(!FS.streams[fd]){return fd}}throw new FS.ErrnoError(33)},getStreamChecked(fd){var stream=FS.getStream(fd);if(!stream){throw new FS.ErrnoError(8)}return stream},getStream:fd=>FS.streams[fd],createStream(stream,fd=-1){stream=Object.assign(new FS.FSStream,stream);if(fd==-1){fd=FS.nextfd()}stream.fd=fd;FS.streams[fd]=stream;return stream},closeStream(fd){FS.streams[fd]=null},dupStream(origStream,fd=-1){var stream=FS.createStream(origStream,fd);stream.stream_ops?.dup?.(stream);return stream},chrdev_stream_ops:{open(stream){var device=FS.getDevice(stream.node.rdev);stream.stream_ops=device.stream_ops;stream.stream_ops.open?.(stream)},llseek(){throw new FS.ErrnoError(70)}},major:dev=>dev>>8,minor:dev=>dev&255,makedev:(ma,mi)=>ma<<8|mi,registerDevice(dev,ops){FS.devices[dev]={stream_ops:ops}},getDevice:dev=>FS.devices[dev],getMounts(mount){var mounts=[];var check=[mount];while(check.length){var m=check.pop();mounts.push(m);check.push(...m.mounts)}return mounts},syncfs(populate,callback){if(typeof populate=="function"){callback=populate;populate=false}FS.syncFSRequests++;if(FS.syncFSRequests>1){err(`warning: ${FS.syncFSRequests} FS.syncfs operations in flight at once, probably just doing extra work`)}var mounts=FS.getMounts(FS.root.mount);var completed=0;function doCallback(errCode){FS.syncFSRequests--;return callback(errCode)}function done(errCode){if(errCode){if(!done.errored){done.errored=true;return doCallback(errCode)}return}if(++completed>=mounts.length){doCallback(null)}}mounts.forEach(mount=>{if(!mount.type.syncfs){return done(null)}mount.type.syncfs(mount,populate,done)})},mount(type,opts,mountpoint){var root=mountpoint==="/";var pseudo=!mountpoint;var node;if(root&&FS.root){throw new FS.ErrnoError(10)}else if(!root&&!pseudo){var lookup=FS.lookupPath(mountpoint,{follow_mount:false});mountpoint=lookup.path;node=lookup.node;if(FS.isMountpoint(node)){throw new FS.ErrnoError(10)}if(!FS.isDir(node.mode)){throw new FS.ErrnoError(54)}}var mount={type:type,opts:opts,mountpoint:mountpoint,mounts:[]};var mountRoot=type.mount(mount);mountRoot.mount=mount;mount.root=mountRoot;if(root){FS.root=mountRoot}else if(node){node.mounted=mount;if(node.mount){node.mount.mounts.push(mount)}}return mountRoot},unmount(mountpoint){var lookup=FS.lookupPath(mountpoint,{follow_mount:false});if(!FS.isMountpoint(lookup.node)){throw new FS.ErrnoError(28)}var node=lookup.node;var mount=node.mounted;var mounts=FS.getMounts(mount);Object.keys(FS.nameTable).forEach(hash=>{var current=FS.nameTable[hash];while(current){var next=current.name_next;if(mounts.includes(current.mount)){FS.destroyNode(current)}current=next}});node.mounted=null;var idx=node.mount.mounts.indexOf(mount);node.mount.mounts.splice(idx,1)},lookup(parent,name){return parent.node_ops.lookup(parent,name)},mknod(path,mode,dev){var lookup=FS.lookupPath(path,{parent:true});var parent=lookup.node;var name=PATH.basename(path);if(!name||name==="."||name===".."){throw new FS.ErrnoError(28)}var errCode=FS.mayCreate(parent,name);if(errCode){throw new FS.ErrnoError(errCode)}if(!parent.node_ops.mknod){throw new FS.ErrnoError(63)}return parent.node_ops.mknod(parent,name,mode,dev)},create(path,mode){mode=mode!==undefined?mode:438;mode&=4095;mode|=32768;return FS.mknod(path,mode,0)},mkdir(path,mode){mode=mode!==undefined?mode:511;mode&=511|512;mode|=16384;return FS.mknod(path,mode,0)},mkdirTree(path,mode){var dirs=path.split("/");var d="";for(var i=0;iFS.currentPath,chdir(path){var lookup=FS.lookupPath(path,{follow:true});if(lookup.node===null){throw new FS.ErrnoError(44)}if(!FS.isDir(lookup.node.mode)){throw new FS.ErrnoError(54)}var errCode=FS.nodePermissions(lookup.node,"x");if(errCode){throw new FS.ErrnoError(errCode)}FS.currentPath=lookup.path},createDefaultDirectories(){FS.mkdir("/tmp");FS.mkdir("/home");FS.mkdir("/home/web_user")},createDefaultDevices(){FS.mkdir("/dev");FS.registerDevice(FS.makedev(1,3),{read:()=>0,write:(stream,buffer,offset,length,pos)=>length});FS.mkdev("/dev/null",FS.makedev(1,3));TTY.register(FS.makedev(5,0),TTY.default_tty_ops);TTY.register(FS.makedev(6,0),TTY.default_tty1_ops);FS.mkdev("/dev/tty",FS.makedev(5,0));FS.mkdev("/dev/tty1",FS.makedev(6,0));var randomBuffer=new Uint8Array(1024),randomLeft=0;var randomByte=()=>{if(randomLeft===0){randomLeft=randomFill(randomBuffer).byteLength}return randomBuffer[--randomLeft]};FS.createDevice("/dev","random",randomByte);FS.createDevice("/dev","urandom",randomByte);FS.mkdir("/dev/shm");FS.mkdir("/dev/shm/tmp")},createSpecialDirectories(){FS.mkdir("/proc");var proc_self=FS.mkdir("/proc/self");FS.mkdir("/proc/self/fd");FS.mount({mount(){var node=FS.createNode(proc_self,"fd",16384|511,73);node.node_ops={lookup(parent,name){var fd=+name;var stream=FS.getStreamChecked(fd);var ret={parent:null,mount:{mountpoint:"fake"},node_ops:{readlink:()=>stream.path}};ret.parent=ret;return ret}};return node}},{},"/proc/self/fd")},createStandardStreams(){if(Module["stdin"]){FS.createDevice("/dev","stdin",Module["stdin"])}else{FS.symlink("/dev/tty","/dev/stdin")}if(Module["stdout"]){FS.createDevice("/dev","stdout",null,Module["stdout"])}else{FS.symlink("/dev/tty","/dev/stdout")}if(Module["stderr"]){FS.createDevice("/dev","stderr",null,Module["stderr"])}else{FS.symlink("/dev/tty1","/dev/stderr")}var stdin=FS.open("/dev/stdin",0);var stdout=FS.open("/dev/stdout",1);var stderr=FS.open("/dev/stderr",1)},staticInit(){[44].forEach(code=>{FS.genericErrors[code]=new FS.ErrnoError(code);FS.genericErrors[code].stack=""});FS.nameTable=new Array(4096);FS.mount(MEMFS,{},"/");FS.createDefaultDirectories();FS.createDefaultDevices();FS.createSpecialDirectories();FS.filesystems={MEMFS:MEMFS}},init(input,output,error){FS.init.initialized=true;Module["stdin"]=input||Module["stdin"];Module["stdout"]=output||Module["stdout"];Module["stderr"]=error||Module["stderr"];FS.createStandardStreams()},quit(){FS.init.initialized=false;for(var i=0;ithis.length-1||idx<0){return undefined}var chunkOffset=idx%this.chunkSize;var chunkNum=idx/this.chunkSize|0;return this.getter(chunkNum)[chunkOffset]}setDataGetter(getter){this.getter=getter}cacheLength(){var xhr=new XMLHttpRequest;xhr.open("HEAD",url,false);xhr.send(null);if(!(xhr.status>=200&&xhr.status<300||xhr.status===304))throw new Error("Couldn't load "+url+". Status: "+xhr.status);var datalength=Number(xhr.getResponseHeader("Content-length"));var header;var hasByteServing=(header=xhr.getResponseHeader("Accept-Ranges"))&&header==="bytes";var usesGzip=(header=xhr.getResponseHeader("Content-Encoding"))&&header==="gzip";var chunkSize=1024*1024;if(!hasByteServing)chunkSize=datalength;var doXHR=(from,to)=>{if(from>to)throw new Error("invalid range ("+from+", "+to+") or no bytes requested!");if(to>datalength-1)throw new Error("only "+datalength+" bytes available! programmer error!");var xhr=new XMLHttpRequest;xhr.open("GET",url,false);if(datalength!==chunkSize)xhr.setRequestHeader("Range","bytes="+from+"-"+to);xhr.responseType="arraybuffer";if(xhr.overrideMimeType){xhr.overrideMimeType("text/plain; charset=x-user-defined")}xhr.send(null);if(!(xhr.status>=200&&xhr.status<300||xhr.status===304))throw new Error("Couldn't load "+url+". Status: "+xhr.status);if(xhr.response!==undefined){return new Uint8Array(xhr.response||[])}return intArrayFromString(xhr.responseText||"",true)};var lazyArray=this;lazyArray.setDataGetter(chunkNum=>{var start=chunkNum*chunkSize;var end=(chunkNum+1)*chunkSize-1;end=Math.min(end,datalength-1);if(typeof lazyArray.chunks[chunkNum]=="undefined"){lazyArray.chunks[chunkNum]=doXHR(start,end)}if(typeof lazyArray.chunks[chunkNum]=="undefined")throw new Error("doXHR failed!");return lazyArray.chunks[chunkNum]});if(usesGzip||!datalength){chunkSize=datalength=1;datalength=this.getter(0).length;chunkSize=datalength;out("LazyFiles on gzip forces download of the whole file when length is accessed")}this._length=datalength;this._chunkSize=chunkSize;this.lengthKnown=true}get length(){if(!this.lengthKnown){this.cacheLength()}return this._length}get chunkSize(){if(!this.lengthKnown){this.cacheLength()}return this._chunkSize}}if(typeof XMLHttpRequest!="undefined"){if(!ENVIRONMENT_IS_WORKER)throw"Cannot do synchronous binary XHRs outside webworkers in modern browsers. Use --embed-file or --preload-file in emcc";var lazyArray=new LazyUint8Array;var properties={isDevice:false,contents:lazyArray}}else{var properties={isDevice:false,url:url}}var node=FS.createFile(parent,name,properties,canRead,canWrite);if(properties.contents){node.contents=properties.contents}else if(properties.url){node.contents=null;node.url=properties.url}Object.defineProperties(node,{usedBytes:{get:function(){return this.contents.length}}});var stream_ops={};var keys=Object.keys(node.stream_ops);keys.forEach(key=>{var fn=node.stream_ops[key];stream_ops[key]=(...args)=>{FS.forceLoadFile(node);return fn(...args)}});function writeChunks(stream,buffer,offset,length,position){var contents=stream.node.contents;if(position>=contents.length)return 0;var size=Math.min(contents.length-position,length);if(contents.slice){for(var i=0;i{FS.forceLoadFile(node);return writeChunks(stream,buffer,offset,length,position)};stream_ops.mmap=(stream,length,position,prot,flags)=>{FS.forceLoadFile(node);var ptr=mmapAlloc(length);if(!ptr){throw new FS.ErrnoError(48)}writeChunks(stream,HEAP8,ptr,length,position);return{ptr:ptr,allocated:true}};node.stream_ops=stream_ops;return node}};Module["FS"]=FS;var SYSCALLS={DEFAULT_POLLMASK:5,calculateAt(dirfd,path,allowEmpty){if(PATH.isAbs(path)){return path}var dir;if(dirfd===-100){dir=FS.cwd()}else{var dirstream=SYSCALLS.getStreamFromFD(dirfd);dir=dirstream.path}if(path.length==0){if(!allowEmpty){throw new FS.ErrnoError(44)}return dir}return PATH.join2(dir,path)},doStat(func,path,buf){var stat=func(path);SAFE_HEAP_STORE((buf>>2)*4,stat.dev,4);SAFE_HEAP_STORE((buf+4>>2)*4,stat.mode,4);SAFE_HEAP_STORE((buf+8>>2)*4,stat.nlink,4);SAFE_HEAP_STORE((buf+12>>2)*4,stat.uid,4);SAFE_HEAP_STORE((buf+16>>2)*4,stat.gid,4);SAFE_HEAP_STORE((buf+20>>2)*4,stat.rdev,4);tempI64=[stat.size>>>0,(tempDouble=stat.size,+Math.abs(tempDouble)>=1?tempDouble>0?+Math.floor(tempDouble/4294967296)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],SAFE_HEAP_STORE((buf+24>>2)*4,tempI64[0],4),SAFE_HEAP_STORE((buf+28>>2)*4,tempI64[1],4);SAFE_HEAP_STORE((buf+32>>2)*4,4096,4);SAFE_HEAP_STORE((buf+36>>2)*4,stat.blocks,4);var atime=stat.atime.getTime();var mtime=stat.mtime.getTime();var ctime=stat.ctime.getTime();tempI64=[Math.floor(atime/1e3)>>>0,(tempDouble=Math.floor(atime/1e3),+Math.abs(tempDouble)>=1?tempDouble>0?+Math.floor(tempDouble/4294967296)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],SAFE_HEAP_STORE((buf+40>>2)*4,tempI64[0],4),SAFE_HEAP_STORE((buf+44>>2)*4,tempI64[1],4);SAFE_HEAP_STORE((buf+48>>2)*4,atime%1e3*1e3,4);tempI64=[Math.floor(mtime/1e3)>>>0,(tempDouble=Math.floor(mtime/1e3),+Math.abs(tempDouble)>=1?tempDouble>0?+Math.floor(tempDouble/4294967296)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],SAFE_HEAP_STORE((buf+56>>2)*4,tempI64[0],4),SAFE_HEAP_STORE((buf+60>>2)*4,tempI64[1],4);SAFE_HEAP_STORE((buf+64>>2)*4,mtime%1e3*1e3,4);tempI64=[Math.floor(ctime/1e3)>>>0,(tempDouble=Math.floor(ctime/1e3),+Math.abs(tempDouble)>=1?tempDouble>0?+Math.floor(tempDouble/4294967296)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],SAFE_HEAP_STORE((buf+72>>2)*4,tempI64[0],4),SAFE_HEAP_STORE((buf+76>>2)*4,tempI64[1],4);SAFE_HEAP_STORE((buf+80>>2)*4,ctime%1e3*1e3,4);tempI64=[stat.ino>>>0,(tempDouble=stat.ino,+Math.abs(tempDouble)>=1?tempDouble>0?+Math.floor(tempDouble/4294967296)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],SAFE_HEAP_STORE((buf+88>>2)*4,tempI64[0],4),SAFE_HEAP_STORE((buf+92>>2)*4,tempI64[1],4);return 0},doMsync(addr,stream,len,flags,offset){if(!FS.isFile(stream.node.mode)){throw new FS.ErrnoError(43)}if(flags&2){return 0}var buffer=HEAPU8.slice(addr,addr+len);FS.msync(stream,buffer,offset,len,flags)},getStreamFromFD(fd){var stream=FS.getStreamChecked(fd);return stream},varargs:undefined,getStr(ptr){var ret=UTF8ToString(ptr);return ret}};Module["SYSCALLS"]=SYSCALLS;function ___syscall_fcntl64(fd,cmd,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD(fd);switch(cmd){case 0:{var arg=syscallGetVarargI();if(arg<0){return-28}while(FS.streams[arg]){arg++}var newStream;newStream=FS.dupStream(stream,arg);return newStream.fd}case 1:case 2:return 0;case 3:return stream.flags;case 4:{var arg=syscallGetVarargI();stream.flags|=arg;return 0}case 12:{var arg=syscallGetVarargP();var offset=0;SAFE_HEAP_STORE((arg+offset>>1)*2,2,2);return 0}case 13:case 14:return 0}return-28}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}Module["___syscall_fcntl64"]=___syscall_fcntl64;function ___syscall_ioctl(fd,op,varargs){SYSCALLS.varargs=varargs;try{var stream=SYSCALLS.getStreamFromFD(fd);switch(op){case 21509:{if(!stream.tty)return-59;return 0}case 21505:{if(!stream.tty)return-59;if(stream.tty.ops.ioctl_tcgets){var termios=stream.tty.ops.ioctl_tcgets(stream);var argp=syscallGetVarargP();SAFE_HEAP_STORE((argp>>2)*4,termios.c_iflag||0,4);SAFE_HEAP_STORE((argp+4>>2)*4,termios.c_oflag||0,4);SAFE_HEAP_STORE((argp+8>>2)*4,termios.c_cflag||0,4);SAFE_HEAP_STORE((argp+12>>2)*4,termios.c_lflag||0,4);for(var i=0;i<32;i++){SAFE_HEAP_STORE(argp+i+17,termios.c_cc[i]||0,1)}return 0}return 0}case 21510:case 21511:case 21512:{if(!stream.tty)return-59;return 0}case 21506:case 21507:case 21508:{if(!stream.tty)return-59;if(stream.tty.ops.ioctl_tcsets){var argp=syscallGetVarargP();var c_iflag=SAFE_HEAP_LOAD((argp>>2)*4,4,0);var c_oflag=SAFE_HEAP_LOAD((argp+4>>2)*4,4,0);var c_cflag=SAFE_HEAP_LOAD((argp+8>>2)*4,4,0);var c_lflag=SAFE_HEAP_LOAD((argp+12>>2)*4,4,0);var c_cc=[];for(var i=0;i<32;i++){c_cc.push(SAFE_HEAP_LOAD(argp+i+17,1,0))}return stream.tty.ops.ioctl_tcsets(stream.tty,op,{c_iflag:c_iflag,c_oflag:c_oflag,c_cflag:c_cflag,c_lflag:c_lflag,c_cc:c_cc})}return 0}case 21519:{if(!stream.tty)return-59;var argp=syscallGetVarargP();SAFE_HEAP_STORE((argp>>2)*4,0,4);return 0}case 21520:{if(!stream.tty)return-59;return-28}case 21531:{var argp=syscallGetVarargP();return FS.ioctl(stream,op,argp)}case 21523:{if(!stream.tty)return-59;if(stream.tty.ops.ioctl_tiocgwinsz){var winsize=stream.tty.ops.ioctl_tiocgwinsz(stream.tty);var argp=syscallGetVarargP();SAFE_HEAP_STORE((argp>>1)*2,winsize[0],2);SAFE_HEAP_STORE((argp+2>>1)*2,winsize[1],2)}return 0}case 21524:{if(!stream.tty)return-59;return 0}case 21515:{if(!stream.tty)return-59;return 0}default:return-28}}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}Module["___syscall_ioctl"]=___syscall_ioctl;function ___syscall_openat(dirfd,path,flags,varargs){SYSCALLS.varargs=varargs;try{path=SYSCALLS.getStr(path);path=SYSCALLS.calculateAt(dirfd,path);var mode=varargs?syscallGetVarargI():0;return FS.open(path,flags,mode).fd}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return-e.errno}}Module["___syscall_openat"]=___syscall_openat;var __abort_js=()=>{abort("")};Module["__abort_js"]=__abort_js;var __emscripten_memcpy_js=(dest,src,num)=>HEAPU8.copyWithin(dest,src,src+num);Module["__emscripten_memcpy_js"]=__emscripten_memcpy_js;var readEmAsmArgsArray=[];Module["readEmAsmArgsArray"]=readEmAsmArgsArray;var readEmAsmArgs=(sigPtr,buf)=>{readEmAsmArgsArray.length=0;var ch;while(ch=SAFE_HEAP_LOAD(sigPtr++,1,1)){var wide=ch!=105;wide&=ch!=112;buf+=wide&&buf%8?4:0;readEmAsmArgsArray.push(ch==112?SAFE_HEAP_LOAD((buf>>2)*4,4,1):ch==105?SAFE_HEAP_LOAD((buf>>2)*4,4,0):SAFE_HEAP_LOAD_D((buf>>3)*8,8,0));buf+=wide?8:4}return readEmAsmArgsArray};Module["readEmAsmArgs"]=readEmAsmArgs;var runEmAsmFunction=(code,sigPtr,argbuf)=>{var args=readEmAsmArgs(sigPtr,argbuf);return ASM_CONSTS[code](...args)};Module["runEmAsmFunction"]=runEmAsmFunction;var _emscripten_asm_const_int=(code,sigPtr,argbuf)=>runEmAsmFunction(code,sigPtr,argbuf);Module["_emscripten_asm_const_int"]=_emscripten_asm_const_int;var getHeapMax=()=>2147483648;Module["getHeapMax"]=getHeapMax;var growMemory=size=>{var b=wasmMemory.buffer;var pages=(size-b.byteLength+65535)/65536;try{wasmMemory.grow(pages);updateMemoryViews();return 1}catch(e){}};Module["growMemory"]=growMemory;var _emscripten_resize_heap=requestedSize=>{var oldSize=HEAPU8.length;requestedSize>>>=0;var maxHeapSize=getHeapMax();if(requestedSize>maxHeapSize){return false}var alignUp=(x,multiple)=>x+(multiple-x%multiple)%multiple;for(var cutDown=1;cutDown<=4;cutDown*=2){var overGrownHeapSize=oldSize*(1+.2/cutDown);overGrownHeapSize=Math.min(overGrownHeapSize,requestedSize+100663296);var newSize=Math.min(maxHeapSize,alignUp(Math.max(requestedSize,overGrownHeapSize),65536));var replacement=growMemory(newSize);if(replacement){return true}}return false};Module["_emscripten_resize_heap"]=_emscripten_resize_heap;function _fd_close(fd){try{var stream=SYSCALLS.getStreamFromFD(fd);FS.close(stream);return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}Module["_fd_close"]=_fd_close;var doReadv=(stream,iov,iovcnt,offset)=>{var ret=0;for(var i=0;i>2)*4,4,1);var len=SAFE_HEAP_LOAD((iov+4>>2)*4,4,1);iov+=8;var curr=FS.read(stream,HEAP8,ptr,len,offset);if(curr<0)return-1;ret+=curr;if(curr>2)*4,num,4);return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}Module["_fd_read"]=_fd_read;var convertI32PairToI53Checked=(lo,hi)=>hi+2097152>>>0<4194305-!!lo?(lo>>>0)+hi*4294967296:NaN;Module["convertI32PairToI53Checked"]=convertI32PairToI53Checked;function _fd_seek(fd,offset_low,offset_high,whence,newOffset){var offset=convertI32PairToI53Checked(offset_low,offset_high);try{if(isNaN(offset))return 61;var stream=SYSCALLS.getStreamFromFD(fd);FS.llseek(stream,offset,whence);tempI64=[stream.position>>>0,(tempDouble=stream.position,+Math.abs(tempDouble)>=1?tempDouble>0?+Math.floor(tempDouble/4294967296)>>>0:~~+Math.ceil((tempDouble-+(~~tempDouble>>>0))/4294967296)>>>0:0)],SAFE_HEAP_STORE((newOffset>>2)*4,tempI64[0],4),SAFE_HEAP_STORE((newOffset+4>>2)*4,tempI64[1],4);if(stream.getdents&&offset===0&&whence===0)stream.getdents=null;return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}Module["_fd_seek"]=_fd_seek;var doWritev=(stream,iov,iovcnt,offset)=>{var ret=0;for(var i=0;i>2)*4,4,1);var len=SAFE_HEAP_LOAD((iov+4>>2)*4,4,1);iov+=8;var curr=FS.write(stream,HEAP8,ptr,len,offset);if(curr<0)return-1;ret+=curr;if(typeof offset!="undefined"){offset+=curr}}return ret};Module["doWritev"]=doWritev;function _fd_write(fd,iov,iovcnt,pnum){try{var stream=SYSCALLS.getStreamFromFD(fd);var num=doWritev(stream,iov,iovcnt);SAFE_HEAP_STORE((pnum>>2)*4,num,4);return 0}catch(e){if(typeof FS=="undefined"||!(e.name==="ErrnoError"))throw e;return e.errno}}Module["_fd_write"]=_fd_write;var _getentropy=(buffer,size)=>{randomFill(HEAPU8.subarray(buffer,buffer+size));return 0};Module["_getentropy"]=_getentropy;var getCFunc=ident=>{var func=Module["_"+ident];return func};Module["getCFunc"]=getCFunc;var writeArrayToMemory=(array,buffer)=>{HEAP8.set(array,buffer)};Module["writeArrayToMemory"]=writeArrayToMemory;var stringToUTF8=(str,outPtr,maxBytesToWrite)=>stringToUTF8Array(str,HEAPU8,outPtr,maxBytesToWrite);Module["stringToUTF8"]=stringToUTF8;var stackAlloc=sz=>__emscripten_stack_alloc(sz);Module["stackAlloc"]=stackAlloc;var stringToUTF8OnStack=str=>{var size=lengthBytesUTF8(str)+1;var ret=stackAlloc(size);stringToUTF8(str,ret,size);return ret};Module["stringToUTF8OnStack"]=stringToUTF8OnStack;var ccall=(ident,returnType,argTypes,args,opts)=>{var toC={string:str=>{var ret=0;if(str!==null&&str!==undefined&&str!==0){ret=stringToUTF8OnStack(str)}return ret},array:arr=>{var ret=stackAlloc(arr.length);writeArrayToMemory(arr,ret);return ret}};function convertReturnValue(ret){if(returnType==="string"){return UTF8ToString(ret)}if(returnType==="boolean")return Boolean(ret);return ret}var func=getCFunc(ident);var cArgs=[];var stack=0;if(args){for(var i=0;i{var numericArgs=!argTypes||argTypes.every(type=>type==="number"||type==="boolean");var numericRet=returnType!=="string";if(numericRet&&numericArgs&&!opts){return getCFunc(ident)}return(...args)=>ccall(ident,returnType,argTypes,args,opts)};Module["cwrap"]=cwrap;FS.createPreloadedFile=FS_createPreloadedFile;FS.staticInit();var wasmImports={a:___assert_fail,h:___cxa_throw,g:___syscall_fcntl64,j:___syscall_ioctl,k:___syscall_openat,o:__abort_js,l:__emscripten_memcpy_js,c:alignfault,d:_emscripten_asm_const_int,p:_emscripten_resize_heap,e:_fd_close,i:_fd_read,m:_fd_seek,f:_fd_write,n:_getentropy,b:segfault};var wasmExports=createWasm();var ___wasm_call_ctors=()=>(___wasm_call_ctors=wasmExports["r"])();var _malloc=Module["_malloc"]=a0=>(_malloc=Module["_malloc"]=wasmExports["t"])(a0);var _free=Module["_free"]=a0=>(_free=Module["_free"]=wasmExports["u"])(a0);var _ma_device__on_notification_unlocked=Module["_ma_device__on_notification_unlocked"]=a0=>(_ma_device__on_notification_unlocked=Module["_ma_device__on_notification_unlocked"]=wasmExports["v"])(a0);var _ma_malloc_emscripten=Module["_ma_malloc_emscripten"]=(a0,a1)=>(_ma_malloc_emscripten=Module["_ma_malloc_emscripten"]=wasmExports["w"])(a0,a1);var _ma_free_emscripten=Module["_ma_free_emscripten"]=(a0,a1)=>(_ma_free_emscripten=Module["_ma_free_emscripten"]=wasmExports["x"])(a0,a1);var _ma_device_process_pcm_frames_capture__webaudio=Module["_ma_device_process_pcm_frames_capture__webaudio"]=(a0,a1,a2)=>(_ma_device_process_pcm_frames_capture__webaudio=Module["_ma_device_process_pcm_frames_capture__webaudio"]=wasmExports["y"])(a0,a1,a2);var _ma_device_process_pcm_frames_playback__webaudio=Module["_ma_device_process_pcm_frames_playback__webaudio"]=(a0,a1,a2)=>(_ma_device_process_pcm_frames_playback__webaudio=Module["_ma_device_process_pcm_frames_playback__webaudio"]=wasmExports["z"])(a0,a1,a2);var _createWorkerInWasm=Module["_createWorkerInWasm"]=()=>(_createWorkerInWasm=Module["_createWorkerInWasm"]=wasmExports["A"])();var _sendToWorker=Module["_sendToWorker"]=(a0,a1)=>(_sendToWorker=Module["_sendToWorker"]=wasmExports["B"])(a0,a1);var _nativeFree=Module["_nativeFree"]=a0=>(_nativeFree=Module["_nativeFree"]=wasmExports["C"])(a0);var _voiceEndedCallback=Module["_voiceEndedCallback"]=a0=>(_voiceEndedCallback=Module["_voiceEndedCallback"]=wasmExports["D"])(a0);var _setDartEventCallback=Module["_setDartEventCallback"]=(a0,a1,a2)=>(_setDartEventCallback=Module["_setDartEventCallback"]=wasmExports["E"])(a0,a1,a2);var _initEngine=Module["_initEngine"]=(a0,a1,a2)=>(_initEngine=Module["_initEngine"]=wasmExports["F"])(a0,a1,a2);var _dispose=Module["_dispose"]=()=>(_dispose=Module["_dispose"]=wasmExports["G"])();var _isInited=Module["_isInited"]=()=>(_isInited=Module["_isInited"]=wasmExports["H"])();var _loadFile=Module["_loadFile"]=(a0,a1)=>(_loadFile=Module["_loadFile"]=wasmExports["I"])(a0,a1);var _loadMem=Module["_loadMem"]=(a0,a1,a2,a3,a4)=>(_loadMem=Module["_loadMem"]=wasmExports["J"])(a0,a1,a2,a3,a4);var _loadWaveform=Module["_loadWaveform"]=(a0,a1,a2,a3,a4)=>(_loadWaveform=Module["_loadWaveform"]=wasmExports["K"])(a0,a1,a2,a3,a4);var _setWaveformScale=Module["_setWaveformScale"]=(a0,a1)=>(_setWaveformScale=Module["_setWaveformScale"]=wasmExports["L"])(a0,a1);var _setWaveformDetune=Module["_setWaveformDetune"]=(a0,a1)=>(_setWaveformDetune=Module["_setWaveformDetune"]=wasmExports["M"])(a0,a1);var _setWaveformFreq=Module["_setWaveformFreq"]=(a0,a1)=>(_setWaveformFreq=Module["_setWaveformFreq"]=wasmExports["N"])(a0,a1);var _setSuperWave=Module["_setSuperWave"]=(a0,a1)=>(_setSuperWave=Module["_setSuperWave"]=wasmExports["O"])(a0,a1);var _setWaveform=Module["_setWaveform"]=(a0,a1)=>(_setWaveform=Module["_setWaveform"]=wasmExports["P"])(a0,a1);var _speechText=Module["_speechText"]=(a0,a1)=>(_speechText=Module["_speechText"]=wasmExports["Q"])(a0,a1);var _pauseSwitch=Module["_pauseSwitch"]=a0=>(_pauseSwitch=Module["_pauseSwitch"]=wasmExports["R"])(a0);var _setPause=Module["_setPause"]=(a0,a1)=>(_setPause=Module["_setPause"]=wasmExports["S"])(a0,a1);var _getPause=Module["_getPause"]=a0=>(_getPause=Module["_getPause"]=wasmExports["T"])(a0);var _setRelativePlaySpeed=Module["_setRelativePlaySpeed"]=(a0,a1)=>(_setRelativePlaySpeed=Module["_setRelativePlaySpeed"]=wasmExports["U"])(a0,a1);var _getRelativePlaySpeed=Module["_getRelativePlaySpeed"]=a0=>(_getRelativePlaySpeed=Module["_getRelativePlaySpeed"]=wasmExports["V"])(a0);var _play=Module["_play"]=(a0,a1,a2,a3,a4,a5,a6)=>(_play=Module["_play"]=wasmExports["W"])(a0,a1,a2,a3,a4,a5,a6);var _stop=Module["_stop"]=a0=>(_stop=Module["_stop"]=wasmExports["X"])(a0);var _disposeSound=Module["_disposeSound"]=a0=>(_disposeSound=Module["_disposeSound"]=wasmExports["Y"])(a0);var _disposeAllSound=Module["_disposeAllSound"]=()=>(_disposeAllSound=Module["_disposeAllSound"]=wasmExports["Z"])();var _getLooping=Module["_getLooping"]=a0=>(_getLooping=Module["_getLooping"]=wasmExports["_"])(a0);var _setLooping=Module["_setLooping"]=(a0,a1)=>(_setLooping=Module["_setLooping"]=wasmExports["$"])(a0,a1);var _getLoopPoint=Module["_getLoopPoint"]=a0=>(_getLoopPoint=Module["_getLoopPoint"]=wasmExports["aa"])(a0);var _setLoopPoint=Module["_setLoopPoint"]=(a0,a1)=>(_setLoopPoint=Module["_setLoopPoint"]=wasmExports["ba"])(a0,a1);var _setVisualizationEnabled=Module["_setVisualizationEnabled"]=a0=>(_setVisualizationEnabled=Module["_setVisualizationEnabled"]=wasmExports["ca"])(a0);var _getVisualizationEnabled=Module["_getVisualizationEnabled"]=()=>(_getVisualizationEnabled=Module["_getVisualizationEnabled"]=wasmExports["da"])();var _getFft=Module["_getFft"]=a0=>(_getFft=Module["_getFft"]=wasmExports["ea"])(a0);var _getWave=Module["_getWave"]=a0=>(_getWave=Module["_getWave"]=wasmExports["fa"])(a0);var _setFftSmoothing=Module["_setFftSmoothing"]=a0=>(_setFftSmoothing=Module["_setFftSmoothing"]=wasmExports["ga"])(a0);var _getAudioTexture=Module["_getAudioTexture"]=a0=>(_getAudioTexture=Module["_getAudioTexture"]=wasmExports["ha"])(a0);var _getAudioTexture2D=Module["_getAudioTexture2D"]=a0=>(_getAudioTexture2D=Module["_getAudioTexture2D"]=wasmExports["ia"])(a0);var _getTextureValue=Module["_getTextureValue"]=(a0,a1)=>(_getTextureValue=Module["_getTextureValue"]=wasmExports["ja"])(a0,a1);var _getLength=Module["_getLength"]=a0=>(_getLength=Module["_getLength"]=wasmExports["ka"])(a0);var _seek=Module["_seek"]=(a0,a1)=>(_seek=Module["_seek"]=wasmExports["la"])(a0,a1);var _getPosition=Module["_getPosition"]=a0=>(_getPosition=Module["_getPosition"]=wasmExports["ma"])(a0);var _getGlobalVolume=Module["_getGlobalVolume"]=()=>(_getGlobalVolume=Module["_getGlobalVolume"]=wasmExports["na"])();var _setGlobalVolume=Module["_setGlobalVolume"]=a0=>(_setGlobalVolume=Module["_setGlobalVolume"]=wasmExports["oa"])(a0);var _getVolume=Module["_getVolume"]=a0=>(_getVolume=Module["_getVolume"]=wasmExports["pa"])(a0);var _setVolume=Module["_setVolume"]=(a0,a1)=>(_setVolume=Module["_setVolume"]=wasmExports["qa"])(a0,a1);var _getPan=Module["_getPan"]=a0=>(_getPan=Module["_getPan"]=wasmExports["ra"])(a0);var _setPan=Module["_setPan"]=(a0,a1)=>(_setPan=Module["_setPan"]=wasmExports["sa"])(a0,a1);var _setPanAbsolute=Module["_setPanAbsolute"]=(a0,a1,a2)=>(_setPanAbsolute=Module["_setPanAbsolute"]=wasmExports["ta"])(a0,a1,a2);var _getIsValidVoiceHandle=Module["_getIsValidVoiceHandle"]=a0=>(_getIsValidVoiceHandle=Module["_getIsValidVoiceHandle"]=wasmExports["ua"])(a0);var _getActiveVoiceCount=Module["_getActiveVoiceCount"]=()=>(_getActiveVoiceCount=Module["_getActiveVoiceCount"]=wasmExports["va"])();var _countAudioSource=Module["_countAudioSource"]=a0=>(_countAudioSource=Module["_countAudioSource"]=wasmExports["wa"])(a0);var _getVoiceCount=Module["_getVoiceCount"]=()=>(_getVoiceCount=Module["_getVoiceCount"]=wasmExports["xa"])();var _getProtectVoice=Module["_getProtectVoice"]=a0=>(_getProtectVoice=Module["_getProtectVoice"]=wasmExports["ya"])(a0);var _setProtectVoice=Module["_setProtectVoice"]=(a0,a1)=>(_setProtectVoice=Module["_setProtectVoice"]=wasmExports["za"])(a0,a1);var _getMaxActiveVoiceCount=Module["_getMaxActiveVoiceCount"]=()=>(_getMaxActiveVoiceCount=Module["_getMaxActiveVoiceCount"]=wasmExports["Aa"])();var _setMaxActiveVoiceCount=Module["_setMaxActiveVoiceCount"]=a0=>(_setMaxActiveVoiceCount=Module["_setMaxActiveVoiceCount"]=wasmExports["Ba"])(a0);var _createVoiceGroup=Module["_createVoiceGroup"]=()=>(_createVoiceGroup=Module["_createVoiceGroup"]=wasmExports["Ca"])();var _destroyVoiceGroup=Module["_destroyVoiceGroup"]=a0=>(_destroyVoiceGroup=Module["_destroyVoiceGroup"]=wasmExports["Da"])(a0);var _addVoiceToGroup=Module["_addVoiceToGroup"]=(a0,a1)=>(_addVoiceToGroup=Module["_addVoiceToGroup"]=wasmExports["Ea"])(a0,a1);var _isVoiceGroup=Module["_isVoiceGroup"]=a0=>(_isVoiceGroup=Module["_isVoiceGroup"]=wasmExports["Fa"])(a0);var _isVoiceGroupEmpty=Module["_isVoiceGroupEmpty"]=a0=>(_isVoiceGroupEmpty=Module["_isVoiceGroupEmpty"]=wasmExports["Ga"])(a0);var _fadeGlobalVolume=Module["_fadeGlobalVolume"]=(a0,a1)=>(_fadeGlobalVolume=Module["_fadeGlobalVolume"]=wasmExports["Ha"])(a0,a1);var _fadeVolume=Module["_fadeVolume"]=(a0,a1,a2)=>(_fadeVolume=Module["_fadeVolume"]=wasmExports["Ia"])(a0,a1,a2);var _fadePan=Module["_fadePan"]=(a0,a1,a2)=>(_fadePan=Module["_fadePan"]=wasmExports["Ja"])(a0,a1,a2);var _fadeRelativePlaySpeed=Module["_fadeRelativePlaySpeed"]=(a0,a1,a2)=>(_fadeRelativePlaySpeed=Module["_fadeRelativePlaySpeed"]=wasmExports["Ka"])(a0,a1,a2);var _schedulePause=Module["_schedulePause"]=(a0,a1)=>(_schedulePause=Module["_schedulePause"]=wasmExports["La"])(a0,a1);var _scheduleStop=Module["_scheduleStop"]=(a0,a1)=>(_scheduleStop=Module["_scheduleStop"]=wasmExports["Ma"])(a0,a1);var _oscillateVolume=Module["_oscillateVolume"]=(a0,a1,a2,a3)=>(_oscillateVolume=Module["_oscillateVolume"]=wasmExports["Na"])(a0,a1,a2,a3);var _oscillatePan=Module["_oscillatePan"]=(a0,a1,a2,a3)=>(_oscillatePan=Module["_oscillatePan"]=wasmExports["Oa"])(a0,a1,a2,a3);var _oscillateRelativePlaySpeed=Module["_oscillateRelativePlaySpeed"]=(a0,a1,a2,a3)=>(_oscillateRelativePlaySpeed=Module["_oscillateRelativePlaySpeed"]=wasmExports["Pa"])(a0,a1,a2,a3);var _oscillateGlobalVolume=Module["_oscillateGlobalVolume"]=(a0,a1,a2)=>(_oscillateGlobalVolume=Module["_oscillateGlobalVolume"]=wasmExports["Qa"])(a0,a1,a2);var _isFilterActive=Module["_isFilterActive"]=(a0,a1,a2)=>(_isFilterActive=Module["_isFilterActive"]=wasmExports["Ra"])(a0,a1,a2);var _getFilterParamNames=Module["_getFilterParamNames"]=(a0,a1,a2)=>(_getFilterParamNames=Module["_getFilterParamNames"]=wasmExports["Sa"])(a0,a1,a2);var _addFilter=Module["_addFilter"]=(a0,a1)=>(_addFilter=Module["_addFilter"]=wasmExports["Ta"])(a0,a1);var _removeFilter=Module["_removeFilter"]=(a0,a1)=>(_removeFilter=Module["_removeFilter"]=wasmExports["Ua"])(a0,a1);var _setFilterParams=Module["_setFilterParams"]=(a0,a1,a2,a3)=>(_setFilterParams=Module["_setFilterParams"]=wasmExports["Va"])(a0,a1,a2,a3);var _getFilterParams=Module["_getFilterParams"]=(a0,a1,a2,a3)=>(_getFilterParams=Module["_getFilterParams"]=wasmExports["Wa"])(a0,a1,a2,a3);var _fadeFilterParameter=Module["_fadeFilterParameter"]=(a0,a1,a2,a3,a4)=>(_fadeFilterParameter=Module["_fadeFilterParameter"]=wasmExports["Xa"])(a0,a1,a2,a3,a4);var _oscillateFilterParameter=Module["_oscillateFilterParameter"]=(a0,a1,a2,a3,a4,a5)=>(_oscillateFilterParameter=Module["_oscillateFilterParameter"]=wasmExports["Ya"])(a0,a1,a2,a3,a4,a5);var _play3d=Module["_play3d"]=(a0,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11)=>(_play3d=Module["_play3d"]=wasmExports["Za"])(a0,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11);var _set3dSoundSpeed=Module["_set3dSoundSpeed"]=a0=>(_set3dSoundSpeed=Module["_set3dSoundSpeed"]=wasmExports["_a"])(a0);var _get3dSoundSpeed=Module["_get3dSoundSpeed"]=()=>(_get3dSoundSpeed=Module["_get3dSoundSpeed"]=wasmExports["$a"])();var _set3dListenerParameters=Module["_set3dListenerParameters"]=(a0,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11)=>(_set3dListenerParameters=Module["_set3dListenerParameters"]=wasmExports["ab"])(a0,a1,a2,a3,a4,a5,a6,a7,a8,a9,a10,a11);var _set3dListenerPosition=Module["_set3dListenerPosition"]=(a0,a1,a2)=>(_set3dListenerPosition=Module["_set3dListenerPosition"]=wasmExports["bb"])(a0,a1,a2);var _set3dListenerAt=Module["_set3dListenerAt"]=(a0,a1,a2)=>(_set3dListenerAt=Module["_set3dListenerAt"]=wasmExports["cb"])(a0,a1,a2);var _set3dListenerUp=Module["_set3dListenerUp"]=(a0,a1,a2)=>(_set3dListenerUp=Module["_set3dListenerUp"]=wasmExports["db"])(a0,a1,a2);var _set3dListenerVelocity=Module["_set3dListenerVelocity"]=(a0,a1,a2)=>(_set3dListenerVelocity=Module["_set3dListenerVelocity"]=wasmExports["eb"])(a0,a1,a2);var _set3dSourceParameters=Module["_set3dSourceParameters"]=(a0,a1,a2,a3,a4,a5,a6)=>(_set3dSourceParameters=Module["_set3dSourceParameters"]=wasmExports["fb"])(a0,a1,a2,a3,a4,a5,a6);var _set3dSourcePosition=Module["_set3dSourcePosition"]=(a0,a1,a2,a3)=>(_set3dSourcePosition=Module["_set3dSourcePosition"]=wasmExports["gb"])(a0,a1,a2,a3);var _set3dSourceVelocity=Module["_set3dSourceVelocity"]=(a0,a1,a2,a3)=>(_set3dSourceVelocity=Module["_set3dSourceVelocity"]=wasmExports["hb"])(a0,a1,a2,a3);var _set3dSourceMinMaxDistance=Module["_set3dSourceMinMaxDistance"]=(a0,a1,a2)=>(_set3dSourceMinMaxDistance=Module["_set3dSourceMinMaxDistance"]=wasmExports["ib"])(a0,a1,a2);var _set3dSourceAttenuation=Module["_set3dSourceAttenuation"]=(a0,a1,a2)=>(_set3dSourceAttenuation=Module["_set3dSourceAttenuation"]=wasmExports["jb"])(a0,a1,a2);var _set3dSourceDopplerFactor=Module["_set3dSourceDopplerFactor"]=(a0,a1)=>(_set3dSourceDopplerFactor=Module["_set3dSourceDopplerFactor"]=wasmExports["kb"])(a0,a1);var _emscripten_get_sbrk_ptr=()=>(_emscripten_get_sbrk_ptr=wasmExports["lb"])();var _sbrk=a0=>(_sbrk=wasmExports["mb"])(a0);var _emscripten_stack_get_base=()=>(_emscripten_stack_get_base=wasmExports["nb"])();var __emscripten_stack_restore=a0=>(__emscripten_stack_restore=wasmExports["ob"])(a0);var __emscripten_stack_alloc=a0=>(__emscripten_stack_alloc=wasmExports["pb"])(a0);var _emscripten_stack_get_current=()=>(_emscripten_stack_get_current=wasmExports["qb"])();var ___cxa_is_pointer_type=a0=>(___cxa_is_pointer_type=wasmExports["rb"])(a0);var dynCall_iiiji=Module["dynCall_iiiji"]=(a0,a1,a2,a3,a4,a5)=>(dynCall_iiiji=Module["dynCall_iiiji"]=wasmExports["sb"])(a0,a1,a2,a3,a4,a5);var dynCall_jii=Module["dynCall_jii"]=(a0,a1,a2)=>(dynCall_jii=Module["dynCall_jii"]=wasmExports["tb"])(a0,a1,a2);var dynCall_jiji=Module["dynCall_jiji"]=(a0,a1,a2,a3,a4)=>(dynCall_jiji=Module["dynCall_jiji"]=wasmExports["ub"])(a0,a1,a2,a3,a4);Module["ccall"]=ccall;Module["cwrap"]=cwrap;var calledRun;dependenciesFulfilled=function runCaller(){if(!calledRun)run();if(!calledRun)dependenciesFulfilled=runCaller};function run(){if(runDependencies>0){return}preRun();if(runDependencies>0){return}function doRun(){if(calledRun)return;calledRun=true;Module["calledRun"]=true;if(ABORT)return;initRuntime();if(Module["onRuntimeInitialized"])Module["onRuntimeInitialized"]();postRun()}if(Module["setStatus"]){Module["setStatus"]("Running...");setTimeout(function(){setTimeout(function(){Module["setStatus"]("")},1);doRun()},1)}else{doRun()}}if(Module["preInit"]){if(typeof Module["preInit"]=="function")Module["preInit"]=[Module["preInit"]];while(Module["preInit"].length>0){Module["preInit"].pop()()}}run(); diff --git a/web/libflutter_soloud_plugin.wasm b/web/libflutter_soloud_plugin.wasm index 915af820..06f86921 100755 Binary files a/web/libflutter_soloud_plugin.wasm and b/web/libflutter_soloud_plugin.wasm differ diff --git a/windows/CMakeLists.txt b/windows/CMakeLists.txt index 66dda51b..0e3b433f 100644 --- a/windows/CMakeLists.txt +++ b/windows/CMakeLists.txt @@ -44,6 +44,8 @@ list(APPEND PLUGIN_SOURCES "${SRC_DIR}/analyzer.cpp" "${SRC_DIR}/synth/basic_wave.cpp" "${SRC_DIR}/filters/filters.cpp" + "${SRC_DIR}/filters/pitch_shift_filter.cpp" + "${SRC_DIR}/filters/smbPitchShift.cpp" ) add_library(${PLUGIN_NAME} SHARED