Repository: Picovoice/pvrecorder (language: python)

Issue:
V1.2 rust



Discussion / hints:

Fix patch:
diff --git a/.clang-format b/.clang-format
new file mode 100644
index 00000000..3dd3698a
--- /dev/null
+++ b/.clang-format
@@ -0,0 +1,88 @@
+# Generated from CLion C/C++ Code Style settings
+BasedOnStyle: LLVM
+Cpp11BracedListStyle: true
+AccessModifierOffset: -4
+AlignConsecutiveMacros: true
+AlignTrailingComments: false
+AlignAfterOpenBracket: Align
+AllowAllArgumentsOnNextLine: false
+AllowAllConstructorInitializersOnNextLine: false
+AllowAllParametersOfDeclarationOnNextLine: false
+AlignArrayOfStructures: Left
+AllowShortBlocksOnASingleLine: true
+AllowShortCaseLabelsOnASingleLine: false
+AllowShortFunctionsOnASingleLine: None
+AllowShortIfStatementsOnASingleLine: Never
+AllowShortLambdasOnASingleLine: None
+AllowShortLoopsOnASingleLine: false
+AlwaysBreakAfterReturnType: None
+AlwaysBreakTemplateDeclarations: Yes
+BreakBeforeBraces: Custom
+BinPackArguments: false
+BinPackParameters: false
+BraceWrapping:
+  AfterCaseLabel: false
+  AfterClass: false
+  AfterExternBlock: false
+  AfterControlStatement: Never
+  AfterEnum: false
+  AfterFunction: false
+  AfterNamespace: false
+  AfterUnion: false
+  BeforeCatch: false
+  BeforeElse: false
+  IndentBraces: false
+  SplitEmptyFunction: false
+  SplitEmptyRecord: true
+# BraceBreakingStyle: Attach
+BreakBeforeBinaryOperators: None
+BreakBeforeTernaryOperators: true
+BreakConstructorInitializers: BeforeColon
+BreakInheritanceList: BeforeColon
+ColumnLimit: 0
+CommentPragmas:  '^[^ ]'
+CompactNamespaces: false
+ContinuationIndentWidth: 8
+IncludeBlocks:   Preserve
+IncludeCategories:
+  - Regex:           '^"(llvm|llvm-c|clang|clang-c)/'
+    Priority:        2
+    SortPriority:    0
+    CaseSensitive:   false
+  - Regex:           '^(<|"(gtest|gmock|isl|json)/)'
+    Priority:        3
+    SortPriority:    0
+    CaseSensitive:   false
+  - Regex:           '.*'
+    Priority:        1
+    SortPriority:    0
+    CaseSensitive:   false
+IndentCaseLabels: true
+IndentPPDirectives: None
+IndentWidth: 4
+InsertTrailingCommas: Wrapped
+KeepEmptyLinesAtTheStartOfBlocks: true
+MaxEmptyLinesToKeep: 2
+NamespaceIndentation: All
+ObjCSpaceAfterProperty: false
+ObjCSpaceBeforeProtocolList: true
+PointerAlignment: Right
+ReflowComments: false
+SpaceAfterCStyleCast: true
+SpaceAfterLogicalNot: false
+SpaceAfterTemplateKeyword: false
+SpaceBeforeAssignmentOperators: true
+SpaceBeforeCpp11BracedList: false
+SpaceBeforeCtorInitializerColon: true
+SpaceBeforeInheritanceColon: true
+SpaceBeforeParens: ControlStatements
+SpaceBeforeRangeBasedForLoopColon: true
+SpaceInEmptyParentheses: false
+SpacesBeforeTrailingComments: 1
+SpacesInAngles: false
+SpacesInCStyleCastParentheses: false
+SpacesInContainerLiterals: false
+SpacesInParentheses: false
+SpacesInSquareBrackets: false
+TabWidth: 4
+UseTab: Never
diff --git a/.github/actions/c-template/action.yml b/.github/actions/c-template/action.yml
index 1eea61f5..30d5a891 100644
--- a/.github/actions/c-template/action.yml
+++ b/.github/actions/c-template/action.yml
@@ -1,31 +1,36 @@
 name: C Template
 
 inputs:
-  pv_platform:
+  pv_recorder_platform:
     required: true
   output_dir:
     required: true
+  shell:
+    required: false
+    default: bash
 
 runs:
   using: "composite"
   steps:
     - name: Create build directory
-      run: cmake -B ./build -S . -DPV_PLATFORM="${{ inputs.pv_platform }}" -DOUTPUT_DIR="${{ inputs.output_dir }}"
-      shell: bash
+      run: cmake -B ./build -S . -DPV_RECORDER_PLATFORM="${{ inputs.pv_recorder_platform }}" -DOUTPUT_DIR="${{ inputs.output_dir }}"
+      working-directory: project
+      shell: ${{ inputs.shell }}
 
     - name: Build
       run: cmake --build ./build
-      shell: bash
+      working-directory: project
+      shell: ${{ inputs.shell }}
 
-    - name: Run get audio devices
-      run: ./demo --show_audio_devices
-      working-directory: build
-      shell: bash
+    - name: Test
+      run: ctest --output-on-failure
+      working-directory: project/build
+      shell: ${{ inputs.shell }}
 
     - name: Upload build artifacts
       uses: actions/upload-artifact@v3
       with:
-        name: ${{ inputs.pv_platform }}
+        name: ${{ inputs.pv_recorder_platform }}
         path: |
           lib/${{ inputs.output_dir }}/libpv_recorder.*
           lib/node/${{ inputs.output_dir }}/pv_recorder.node
diff --git a/.github/workflows/c-codestyle.yml b/.github/workflows/c-codestyle.yml
new file mode 100644
index 00000000..9c2f4740
--- /dev/null
+++ b/.github/workflows/c-codestyle.yml
@@ -0,0 +1,32 @@
+name: C Codestyle
+
+on:
+  workflow_dispatch:
+  push:
+    branches: [ main ]
+    paths:
+      - '**/*.c'
+      - '.github/workflows/c-codestyle.yml'
+  pull_request:
+    branches: [ main, 'v[0-9]+.[0-9]+' ]
+    paths:
+      - '**/*.c'
+      - '.github/workflows/c-codestyle.yml'
+
+jobs:
+  check-c-codestyle:
+    runs-on: ubuntu-latest
+
+    steps:
+    - uses: actions/checkout@v3
+
+    - name: Set up Python 3.10
+      uses: actions/setup-python@v4
+      with:
+        python-version: '3.10'
+
+    - name: Install dependencies
+      run: sudo apt install clang-format
+
+    - name: Check c codestyle
+      run: python3 resources/.lint/c/formatter.py -c -v
diff --git a/.github/workflows/c-demo.yml b/.github/workflows/c-demo.yml
new file mode 100644
index 00000000..eb911a8e
--- /dev/null
+++ b/.github/workflows/c-demo.yml
@@ -0,0 +1,85 @@
+name: C Demo
+
+on:
+  workflow_dispatch:
+  push:
+    branches: [ main ]
+    paths:
+      - 'demo/c/**'
+      - '!demo/c/README.md'
+      - '.github/workflows/c-demo.yml'
+  pull_request:
+    branches: [ main, 'v[0-9]+.[0-9]+' ]
+    paths:
+      - 'demo/c/**'
+      - '!demo/c/README.md'
+      - '.github/workflows/c-demo.yml'
+
+defaults:
+  run:
+    working-directory: demo/c
+    shell: bash
+
+jobs:
+  build-github-hosted:
+    runs-on: ${{ matrix.os }}
+
+    strategy:
+      matrix:
+        os: [ubuntu-latest, windows-latest, mac-latest]
+        include:
+          - os: ubuntu-latest
+            pv_recorder_platform: linux
+          - os: windows-latest
+            pv_recorder_platform: windows
+          - os: mac-latest
+            pv_recorder_platform: mac
+
+    steps:
+      - uses: actions/checkout@v3
+        with:
+          submodules: recursive
+
+      - name: Create build directory
+        run: cmake -B ./build -S. -DPV_RECORDER_PLATFORM="${{ matrix.pv_recorder_platform }}"
+
+      - name: Build demo
+        run: cmake --build ./build
+
+      - name: Run demo
+        run: ./build/pv_recorder_demo --show_audio_devices
+
+  build-self-hosted:
+    runs-on: ${{ matrix.machine }}
+    strategy:
+      matrix:
+        machine: [rpi2, rpi3-32, rpi3-64, rpi4-32, rpi4-64, jetson, beaglebone]
+        include:
+          - machine: rpi2
+            pv_recorder_platform: raspberry-pi2
+          - machine: rpi3-32
+            pv_recorder_platform: raspberry-pi3
+          - machine: rpi3-64
+            pv_recorder_platform: raspberry-pi3-64
+          - machine: rpi4-32
+            pv_recorder_platform: raspberry-pi4
+          - machine: rpi4-64
+            pv_recorder_platform: raspberry-pi4-64
+          - machine: jetson
+            pv_recorder_platform: jetson-nano
+          - machine: beaglebone
+            pv_recorder_platform: beaglebone
+
+    steps:
+      - uses: actions/checkout@v3
+        with:
+          submodules: recursive
+
+      - name: Create build directory
+        run: cmake -B ./build -S . -DPV_RECORDER_PLATFORM="${{ matrix.pv_recorder_platform }}"
+
+      - name: Build demo
+        run: cmake --build ./build
+
+      - name: Run demo
+        run: ./build/pv_recorder_demo --show_audio_devices
diff --git a/.github/workflows/c.yml b/.github/workflows/c.yml
index edec9831..909eb6c6 100644
--- a/.github/workflows/c.yml
+++ b/.github/workflows/c.yml
@@ -5,19 +5,19 @@ on:
   push:
     branches: [ main ]
     paths:
-      - 'example/**'
-      - 'src/**'
-      - 'node/**'
-      - 'CMakeLists.txt'
+      - 'demo/c/**'
+      - 'project/**'
+      - '!project/README.md'
       - '.github/workflows/c.yml'
+      - '.github/actions/c-template/action.yml'
   pull_request:
-    branches: [ main ]
+    branches: [ main, 'v[0-9]+.[0-9]+' ]
     paths:
-      - 'example/**'
-      - 'src/**'
-      - 'node/**'
-      - 'CMakeLists.txt'
+      - 'demo/c/**'
+      - 'project/**'
+      - '!project/README.md'
       - '.github/workflows/c.yml'
+      - '.github/actions/c-template/action.yml'
 
 jobs:
   build-github-hosted:
@@ -25,14 +25,11 @@ jobs:
 
     strategy:
       matrix:
-        os: [ubuntu-latest, windows-latest]
+        os: [ubuntu-20.04]
         include:
-          - os: ubuntu-latest
-            pv_platform: linux
+          - os: ubuntu-20.04
+            pv_recorder_platform: linux
             output_dir: linux/x86_64
-          - os: windows-latest
-            pv_platform: windows
-            output_dir: windows/amd64
     steps:
       - uses: actions/checkout@v3
         with:
@@ -41,7 +38,7 @@ jobs:
       - uses: ./.github/actions/c-template
         name: Build and test
         with:
-          pv_platform: ${{ matrix.pv_platform }}
+          pv_recorder_platform: ${{ matrix.pv_recorder_platform }}
           output_dir: ${{ matrix.output_dir }}
 
   build-self-hosted:
@@ -51,25 +48,25 @@ jobs:
         machine: [rpi2, rpi3-32, rpi3-64, rpi4-32, rpi4-64, jetson, beaglebone]
         include:
           - machine: rpi2
-            pv_platform: raspberry-pi2
+            pv_recorder_platform: raspberry-pi2
             output_dir: raspberry-pi/cortex-a7
           - machine: rpi3-32
-            pv_platform: raspberry-pi3
+            pv_recorder_platform: raspberry-pi3
             output_dir: raspberry-pi/cortex-a53
           - machine: rpi3-64
-            pv_platform: raspberry-pi3-64
+            pv_recorder_platform: raspberry-pi3-64
             output_dir: raspberry-pi/cortex-a53-aarch64
           - machine: rpi4-32
-            pv_platform: raspberry-pi4
+            pv_recorder_platform: raspberry-pi4
             output_dir: raspberry-pi/cortex-a72
           - machine: rpi4-64
-            pv_platform: raspberry-pi4-64
+            pv_recorder_platform: raspberry-pi4-64
             output_dir: raspberry-pi/cortex-a72-aarch64
           - machine: jetson
-            pv_platform: jetson-nano
+            pv_recorder_platform: jetson-nano
             output_dir: jetson/cortex-a57-aarch64
           - machine: beaglebone
-            pv_platform: beaglebone
+            pv_recorder_platform: beaglebone
             output_dir: beaglebone
 
     steps:
@@ -80,18 +77,18 @@ jobs:
       - uses: ./.github/actions/c-template
         name: Build and test
         with:
-          pv_platform: ${{ matrix.pv_platform }}
+          pv_recorder_platform: ${{ matrix.pv_recorder_platform }}
           output_dir: ${{ matrix.output_dir }}
 
   build-mac:
     runs-on: pv-ios
     strategy:
       matrix:
-        pv_platform: [mac-arm64, mac-x86_64]
+        pv_recorder_platform: [mac-arm64, mac-x86_64]
         include:
-          - pv_platform: mac-arm64
+          - pv_recorder_platform: mac-arm64
             output_dir: mac/arm64
-          - pv_platform: mac-x86_64
+          - pv_recorder_platform: mac-x86_64
             output_dir: mac/x86_64
     steps:
       - uses: actions/checkout@v3
@@ -101,7 +98,7 @@ jobs:
       - uses: ./.github/actions/c-template
         name: Build and test
         with:
-          pv_platform: ${{ matrix.pv_platform }}
+          pv_recorder_platform: ${{ matrix.pv_recorder_platform }}
           output_dir: ${{ matrix.output_dir }}
 
   build-rpi0:
@@ -114,12 +111,26 @@ jobs:
       - uses: ./.github/actions/c-template
         name: Build and test
         with:
-          pv_platform: raspberry-pi
+          pv_recorder_platform: raspberry-pi
           output_dir: raspberry-pi/arm11
 
+  build-windows:
+    runs-on: pv-windows
+    steps:
+      - uses: actions/checkout@v3
+        with:
+          submodules: recursive
+
+      - uses: ./.github/actions/c-template
+        name: Build and test
+        with:
+          pv_recorder_platform: windows
+          output_dir: windows/amd64
+          shell: pwsh
+
   collect-artifacts:
     runs-on: ubuntu-latest
-    needs: [build-github-hosted, build-self-hosted, build-mac, build-rpi0]
+    needs: [build-github-hosted, build-self-hosted, build-mac, build-rpi0, build-windows]
 
     steps:
       - name: Download artifacts
diff --git a/.github/workflows/dotnet-codestyle.yml b/.github/workflows/dotnet-codestyle.yml
new file mode 100644
index 00000000..7d88bce3
--- /dev/null
+++ b/.github/workflows/dotnet-codestyle.yml
@@ -0,0 +1,36 @@
+name: .NET Codestyle
+
+on:
+  workflow_dispatch:
+  push:
+    branches: [ main ]
+    paths:
+      - 'binding/dotnet/**/*.cs'
+      - 'demo/dotnet/**/*.cs'
+      - '.github/workflows/dotnet-codestyle.yml'
+  pull_request:
+    branches: [ main, 'v[0-9]+.[0-9]+' ]
+    paths:
+      - 'binding/dotnet/**/*.cs'
+      - 'demo/dotnet/**/*.cs'
+      - '.github/workflows/dotnet-codestyle.yml'
+
+jobs:
+  check-dotnet-codestyle:
+    runs-on: ubuntu-latest
+
+    steps:
+    - uses: actions/checkout@v3
+
+    - name: Set up .NET 6.0
+      uses: actions/setup-dotnet@v3
+      with:
+        dotnet-version: 6.0.x
+
+    - name: Run Binding Codestyle
+      run: dotnet format --verify-no-changes
+      working-directory: binding/dotnet
+
+    - name: Run Demo Codestyle
+      run: dotnet format --verify-no-changes
+      working-directory: demo/dotnet
diff --git a/.github/workflows/dotnet-demo.yml b/.github/workflows/dotnet-demo.yml
new file mode 100644
index 00000000..4055fa41
--- /dev/null
+++ b/.github/workflows/dotnet-demo.yml
@@ -0,0 +1,65 @@
+name: .NET Demo
+
+on:
+  workflow_dispatch:
+  push:
+    branches: [ main ]
+    paths:
+      - 'demo/dotnet/**'
+      - '!demo/dotnet/README.md'
+      - '.github/workflows/dotnet-demo.yml'
+  pull_request:
+    branches: [ main, 'v[0-9]+.[0-9]+' ]
+    paths:
+      - 'demo/dotnet/**'
+      - '!demo/dotnet/README.md'
+      - '.github/workflows/dotnet-demo.yml'
+
+defaults:
+  run:
+    working-directory: demo/dotnet/PvRecorderDemo
+
+jobs:
+  build-github-hosted:
+    runs-on: ${{ matrix.os }}
+
+    strategy:
+      matrix:
+        os: [ubuntu-latest, windows-latest, mac-latest]
+        include:
+          - os: ubuntu-latest
+            pv_recorder_platform: linux
+          - os: windows-latest
+            pv_recorder_platform: windows
+          - os: mac-latest
+            pv_recorder_platform: mac
+
+    steps:
+    - uses: actions/checkout@v3
+
+    - name: Set up .NET 6.0
+      uses: actions/setup-dotnet@v3
+      with:
+        dotnet-version: 6.0.x
+
+    - name: Build .NET demo
+      run: dotnet build
+
+    - name: Run .NET demo
+      run: dotnet run -- --show_audio_devices
+
+  build-self-hosted:
+    runs-on: ${{ matrix.machine }}
+
+    strategy:
+      matrix:
+        machine: [rpi2, rpi3-32, rpi3-64, rpi4-32, rpi4-64, jetson, beaglebone]
+
+    steps:
+    - uses: actions/checkout@v3
+
+    - name: Build .NET demo
+      run: dotnet build
+
+    - name: Run .NET demo
+      run: dotnet run -- --show_audio_devices
diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml
index 9ce93b3d..e6ff41d4 100644
--- a/.github/workflows/dotnet.yml
+++ b/.github/workflows/dotnet.yml
@@ -5,8 +5,8 @@ on:
   push:
     branches: [ main ]
     paths:
-      - 'sdk/dotnet/**'
-      - '!sdk/dotnet/README.md'
+      - 'binding/dotnet/**'
+      - '!binding/dotnet/README.md'
       - 'lib/beaglebone/**'
       - 'lib/linux/**'
       - 'lib/jetson/**'
@@ -15,10 +15,10 @@ on:
       - 'lib/windows/**'
       - '.github/workflows/dotnet.yml'
   pull_request:
-    branches: [ main ]
+    branches: [ main, 'v[0-9]+.[0-9]+' ]
     paths:
-      - 'sdk/dotnet/**'
-      - '!sdk/dotnet/README.md'
+      - 'binding/dotnet/**'
+      - '!binding/dotnet/README.md'
       - 'lib/beaglebone/**'
       - 'lib/linux/**'
       - 'lib/jetson/**'
@@ -29,52 +29,58 @@ on:
 
 defaults:
   run:
-    working-directory: sdk/dotnet
+    working-directory: binding/dotnet
 
 jobs:
   build-github-hosted:
     runs-on: ${{ matrix.os }}
-
     strategy:
       matrix:
-        os: [ubuntu-latest, windows-latest, macos-latest]
+        os: [ubuntu-latest, macos-latest]
+        dotnet-version: [2.0.x, 3.0.x, 3.1.x, 5.0.x, 6.0.x]
+        include:
+          - dotnet-version: 2.0.x
+            binding-framework: netstandard2.0
+            test-framework: netcoreapp2.0
+          - dotnet-version: 3.0.x
+            binding-framework: netcoreapp3.0
+            test-framework: netcoreapp3.0
+          - dotnet-version: 3.1.x
+            binding-framework: netcoreapp3.0
+            test-framework: netcoreapp3.1
+          - dotnet-version: 5.0.x
+            binding-framework: netcoreapp3.0
+            test-framework: net5.0
+          - dotnet-version: 6.0.x
+            binding-framework: net6.0
+            test-framework: net6.0
 
     steps:
     - uses: actions/checkout@v3
-      with:
-        submodules: recursive
 
-    - name: Setup .NET 6.0
+    - name: Set up .NET
       uses: actions/setup-dotnet@v3
       with:
-        dotnet-version: 6.0.x
+        dotnet-version: ${{ matrix.dotnet-version }}
 
-    - name: Restore dependencies
-      run: dotnet restore
+    - name: Build binding
+      run: dotnet build PvRecorder/PvRecorder.csproj --framework ${{ matrix.binding-framework }}
 
-    - name: Build
-      run: dotnet build -c Release --no-restore
-
-    - name: Run get audio devices
-      run: dotnet run --project Demo -c Release -- --show_audio_devices
+    - name: Test
+      run: dotnet test --framework ${{ matrix.test-framework }} -v n
 
   build-self-hosted:
     runs-on: ${{ matrix.machine }}
 
     strategy:
       matrix:
-        machine: [rpi2, rpi3-32, rpi3-64, rpi4-32, rpi4-64, jetson, beaglebone]
+        machine: [rpi2, rpi3-32, rpi3-64, rpi4-32, rpi4-64, jetson, beaglebone, pv-windows]
 
     steps:
     - uses: actions/checkout@v3
-      with:
-        submodules: recursive
-
-    - name: Restore dependencies
-      run: dotnet restore
 
-    - name: Build
-      run: dotnet build -c Release --no-restore
+    - name: Build binding
+      run: dotnet build PvRecorder/PvRecorder.csproj --framework net6.0
 
-    - name: Run get audio devices
-      run: dotnet run --project Demo -c Release -- --show_audio_devices
+    - name: Test
+      run: dotnet test --framework net6.0 -v n
diff --git a/.github/workflows/go-codestyle.yml b/.github/workflows/go-codestyle.yml
new file mode 100644
index 00000000..fcb1e4be
--- /dev/null
+++ b/.github/workflows/go-codestyle.yml
@@ -0,0 +1,47 @@
+name: Go Codestyle
+
+on:
+  workflow_dispatch:
+  push:
+    branches: [ main ]
+    paths:
+      - '**/*.go'
+      - '.github/workflows/go-codestyle.yml'
+  pull_request:
+    branches: [ main, 'v[0-9]+.[0-9]+' ]
+    paths:
+      - '**/*.go'
+      - '.github/workflows/go-codestyle.yml'
+
+jobs:
+  check-go-binding-codestyle:
+    runs-on: ubuntu-latest
+
+    steps:
+    - uses: actions/checkout@v3
+
+    - name: Set up go
+      uses: actions/setup-go@v4
+      with:
+        go-version: 1.18
+
+    - name: golangci-lint
+      uses: golangci/golangci-lint-action@v3
+      with:
+        working-directory: binding/go
+
+  check-go-demo-codestyle:
+    runs-on: ubuntu-latest
+
+    steps:
+    - uses: actions/checkout@v3
+
+    - name: Set up go
+      uses: actions/setup-go@v4
+      with:
+        go-version: 1.18
+
+    - name: golangci-lint
+      uses: golangci/golangci-lint-action@v3
+      with:
+        working-directory: demo/go
diff --git a/.github/workflows/go-demos.yml b/.github/workflows/go-demos.yml
new file mode 100644
index 00000000..f423acc6
--- /dev/null
+++ b/.github/workflows/go-demos.yml
@@ -0,0 +1,69 @@
+name: Go Demos
+
+on:
+  workflow_dispatch:
+  push:
+    branches: [ main ]
+    paths:
+      - 'demo/go/**'
+      - '!demo/go/README.md'
+      - '.github/workflows/go-demos.yml'
+  pull_request:
+    branches: [ main, 'v[0-9]+.[0-9]+' ]
+    paths:
+      - 'demo/go/**'
+      - '!demo/go/README.md'
+      - '.github/workflows/go-demos.yml'
+
+defaults:
+  run:
+    working-directory: demo/go
+
+jobs:
+  build-github-hosted:
+    runs-on: ${{ matrix.os }}
+
+    strategy:
+      matrix:
+        os: [ubuntu-latest, windows-latest, macos-latest]
+        go: [ '1.16', '1.17', '1.18' ]
+
+    steps:
+    - uses: actions/checkout@v3
+      with:
+        submodules: recursive
+
+    - name: Setup go
+      uses: actions/setup-go@v4
+      with:
+        go-version: ${{ matrix.go }}
+
+    - name: Install dependencies
+      run: go get
+
+    - name: Build
+      run: go build
+
+    - name: Run get audio devices
+      run: go run demo.go --show_audio_devices
+
+  build-self-hosted:
+    runs-on: ${{ matrix.machine }}
+
+    strategy:
+      matrix:
+        machine: [rpi2, rpi3-32, rpi3-64, rpi4-32, rpi4-64, jetson, beaglebone, pv-windows]
+
+    steps:
+    - uses: actions/checkout@v3
+      with:
+        submodules: recursive
+
+    - name: Install dependencies
+      run: go get
+
+    - name: Build
+      run: go build
+
+    - name: Run get audio devices
+      run: go run demo.go --show_audio_devices
diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml
index 445d6030..6dd61039 100644
--- a/.github/workflows/go.yml
+++ b/.github/workflows/go.yml
@@ -5,8 +5,8 @@ on:
   push:
     branches: [ main ]
     paths:
-      - 'sdk/go/**'
-      - '!sdk/go/README.md'
+      - 'binding/go/**'
+      - '!binding/go/README.md'
       - 'lib/beaglebone/**'
       - 'lib/linux/**'
       - 'lib/jetson/**'
@@ -15,10 +15,10 @@ on:
       - 'lib/windows/**'
       - '.github/workflows/go.yml'
   pull_request:
-    branches: [ main ]
+    branches: [ main, 'v[0-9]+.[0-9]+' ]
     paths:
-      - 'sdk/go/**'
-      - '!sdk/go/README.md'
+      - 'binding/go/**'
+      - '!binding/go/README.md'
       - 'lib/beaglebone/**'
       - 'lib/linux/**'
       - 'lib/jetson/**'
@@ -29,7 +29,7 @@ on:
 
 defaults:
   run:
-    working-directory: sdk/go
+    working-directory: binding/go
 
 jobs:
   build-github-hosted:
@@ -37,8 +37,8 @@ jobs:
 
     strategy:
       matrix:
-        os: [ubuntu-latest, windows-latest, macos-latest]
-        go: [ '1.16', '1.17' ]
+        os: [ubuntu-latest, macos-latest]
+        go: [ '1.16', '1.17', '1.18' ]
 
     steps:
     - uses: actions/checkout@v3
@@ -59,15 +59,15 @@ jobs:
     - name: Build
       run: go build
 
-    - name: Run get audio devices
-      run: go run demo/demo.go --show_audio_devices
+    - name: Test
+      run: go test
 
   build-self-hosted:
     runs-on: ${{ matrix.machine }}
 
     strategy:
       matrix:
-        machine: [rpi2, rpi3-32, rpi3-64, rpi4-32, rpi4-64, jetson, beaglebone]
+        machine: [rpi2, rpi3-32, rpi3-64, rpi4-32, rpi4-64, jetson, beaglebone, pv-windows]
 
     steps:
     - uses: actions/checkout@v3
@@ -83,5 +83,5 @@ jobs:
     - name: Build
       run: go build
 
-    - name: Run get audio devices
-      run: go run demo/demo.go --show_audio_devices
+    - name: Test
+      run: go test
diff --git a/.github/workflows/link-check.yml b/.github/workflows/link-check.yml
index 9e513960..6b9bc357 100644
--- a/.github/workflows/link-check.yml
+++ b/.github/workflows/link-check.yml
@@ -3,9 +3,9 @@ name: Check Markdown links
 on:
   workflow_dispatch:
   push:
-    branches: [main]
+    branches: [ main ]
   pull_request:
-    branches: [main]
+    branches: [ main, 'v[0-9]+.[0-9]+' ]
 
 jobs:
   markdown-link-check:
diff --git a/.github/workflows/nodejs-codestyle.yml b/.github/workflows/nodejs-codestyle.yml
new file mode 100644
index 00000000..b075d18f
--- /dev/null
+++ b/.github/workflows/nodejs-codestyle.yml
@@ -0,0 +1,35 @@
+name: Node.js Codestyle
+
+on:
+  workflow_dispatch:
+  push:
+    branches: [ main ]
+    paths:
+      - '**/nodejs/*.js'
+      - '**/nodejs/*.ts'
+      - '.github/workflows/nodejs-codestyle.yml'
+  pull_request:
+    branches: [ main, 'v[0-9]+.[0-9]+' ]
+    paths:
+      - '**/nodejs/*.js'
+      - '**/nodejs/*.ts'
+      - '.github/workflows/nodejs-codestyle.yml'
+
+jobs:
+  check-nodejs-codestyle:
+    runs-on: ubuntu-latest
+
+    steps:
+    - uses: actions/checkout@v3
+
+    - name: Set up Node.js LTS
+      uses: actions/setup-node@v3
+      with:
+        node-version: lts/*
+
+    - name: Pre-build dependencies
+      run: npm install yarn
+
+    - name: Run Binding Linter
+      run: yarn && yarn lint
+      working-directory: binding/nodejs
\ No newline at end of file
diff --git a/.github/workflows/nodejs-demos.yml b/.github/workflows/nodejs-demos.yml
new file mode 100644
index 00000000..9b98767b
--- /dev/null
+++ b/.github/workflows/nodejs-demos.yml
@@ -0,0 +1,69 @@
+name: Node.js Demos
+
+on:
+  workflow_dispatch:
+  push:
+    branches: [ main ]
+    paths:
+      - 'demo/nodejs/**'
+      - '!demo/nodejs/README.md'
+      - '.github/workflows/nodejs-demos.yml'
+  pull_request:
+    branches: [ main, 'v[0-9]+.[0-9]+' ]
+    paths:
+      - 'demo/nodejs/**'
+      - '!demo/nodejs/README.md'
+      - '.github/workflows/nodejs-demos.yml'
+
+defaults:
+  run:
+    working-directory: demo/nodejs
+
+jobs:
+  build-github-hosted:
+    runs-on: ${{ matrix.os }}
+
+    strategy:
+      matrix:
+        os: [ubuntu-latest, macos-latest]
+        node-version: [14.x, 16.x, 18.x, 20.x]
+
+    steps:
+    - uses: actions/checkout@v3
+      with:
+        submodules: recursive
+
+    - name: Use Node.js ${{ matrix.node-version }}
+      uses: actions/setup-node@v3
+      with:
+        node-version: ${{ matrix.node-version }}
+
+    - name: Pre-build dependencies
+      run: npm install yarn
+
+    - name: Install dependencies
+      run: yarn install
+
+    - name: Run get audio devices
+      run: node demo.js --show_audio_devices
+
+  build-self-hosted:
+    runs-on: ${{ matrix.machine }}
+
+    strategy:
+      matrix:
+        machine: [rpi2, rpi3-32, rpi3-64, rpi4-32, rpi4-64, jetson, beaglebone, pv-windows]
+
+    steps:
+    - uses: actions/checkout@v3
+      with:
+        submodules: recursive
+
+    - name: Pre-build dependencies
+      run: npm install yarn
+
+    - name: Install dependencies
+      run: yarn install
+
+    - name: Run get audio devices
+      run: node demo.js --show_audio_devices
diff --git a/.github/workflows/nodejs.yml b/.github/workflows/nodejs.yml
index 254cd92b..d8a55687 100644
--- a/.github/workflows/nodejs.yml
+++ b/.github/workflows/nodejs.yml
@@ -5,21 +5,21 @@ on:
   push:
     branches: [ main ]
     paths:
-      - 'sdk/nodejs/**'
-      - '!sdk/nodejs/README.md'
+      - 'binding/nodejs/**'
+      - '!binding/nodejs/README.md'
       - 'lib/node/**'
       - '.github/workflows/nodejs.yml'
   pull_request:
-    branches: [ main ]
+    branches: [ main, 'v[0-9]+.[0-9]+' ]
     paths:
-      - 'sdk/nodejs/**'
-      - '!sdk/nodejs/README.md'
+      - 'binding/nodejs/**'
+      - '!binding/nodejs/README.md'
       - 'lib/node/**'
       - '.github/workflows/nodejs.yml'
 
 defaults:
   run:
-    working-directory: sdk/nodejs
+    working-directory: binding/nodejs
 
 jobs:
   build-github-hosted:
@@ -27,8 +27,8 @@ jobs:
 
     strategy:
       matrix:
-        os: [ubuntu-latest, windows-latest, macos-latest]
-        node-version: [14.x, 16.x]
+        os: [ubuntu-latest, macos-latest]
+        node-version: [14.x, 16.x, 18.x, 20.x]
 
     steps:
     - uses: actions/checkout@v3
@@ -52,15 +52,15 @@ jobs:
     - name: Build
       run: yarn build
 
-    - name: Run get audio devices
-      run: node demo/demo.js --show_audio_devices
+    - name: Test
+      run: yarn test
 
   build-self-hosted:
     runs-on: ${{ matrix.machine }}
 
     strategy:
       matrix:
-        machine: [rpi2, rpi3-32, rpi3-64, rpi4-32, rpi4-64, jetson, beaglebone]
+        machine: [rpi2, rpi3-32, rpi3-64, rpi4-32, rpi4-64, jetson, beaglebone, pv-windows]
 
     steps:
     - uses: actions/checkout@v3
@@ -79,5 +79,5 @@ jobs:
     - name: Build
       run: yarn build
 
-    - name: Run get audio devices
-      run: node demo/demo.js --show_audio_devices
+    - name: Test
+      run: yarn test
diff --git a/.github/workflows/python-codestyle.yml b/.github/workflows/python-codestyle.yml
new file mode 100644
index 00000000..e9325e3a
--- /dev/null
+++ b/.github/workflows/python-codestyle.yml
@@ -0,0 +1,32 @@
+name: Python Codestyle
+
+on:
+  workflow_dispatch:
+  push:
+    branches: [ main ]
+    paths:
+      - 'binding/python/*.py'
+      - 'demo/python/*.py'
+  pull_request:
+    branches: [ main, 'v[0-9]+.[0-9]+' ]
+    paths:
+      - 'binding/python/*.py'
+      - 'demo/python/*.py'
+
+jobs:
+  check-python-codestyle:
+    runs-on: ubuntu-latest
+
+    steps:
+    - uses: actions/checkout@v3
+
+    - name: Set up Python 3.10
+      uses: actions/setup-python@v4
+      with:
+        python-version: '3.10'
+
+    - name: Install dependencies
+      run: pip install flake8 pep8-naming
+
+    - name: Check python codestyle
+      run: flake8 --ignore=F401,F403,F405 --max-line-length=120 binding/python demo/python
diff --git a/.github/workflows/python-demos.yml b/.github/workflows/python-demos.yml
new file mode 100644
index 00000000..05a4d49d
--- /dev/null
+++ b/.github/workflows/python-demos.yml
@@ -0,0 +1,62 @@
+name: Python Demos
+
+on:
+  workflow_dispatch:
+  push:
+    branches: [ main ]
+    paths:
+      - '.github/workflows/python-demos.yml'
+      - 'demo/python/**'
+      - '!demo/python/README.md'
+  pull_request:
+    branches: [ main, 'v[0-9]+.[0-9]+' ]
+    paths:
+      - '.github/workflows/python-demos.yml'
+      - 'demo/python/**'
+      - '!demo/python/README.md'
+
+defaults:
+  run:
+    working-directory: demo/python
+
+jobs:
+  build-github-hosted:
+    runs-on: ${{ matrix.os }}
+
+    strategy:
+      matrix:
+        os: [ubuntu-latest, windows-latest, macos-latest]
+        python-version: ['3.7', '3.8', '3.9', '3.10', '3.11']
+
+    steps:
+    - uses: actions/checkout@v3
+
+    - name: Set up Python ${{ matrix.python-version }}
+      uses: actions/setup-python@v4
+      with:
+        python-version: ${{ matrix.python-version }}
+
+    - name: Pre-build dependencies
+      run: python -m pip install --upgrade pip
+
+    - name: Install dependencies
+      run: pip install -r requirements.txt
+
+    - name: Run demo
+      run: python3 pv_recorder_demo.py --show_audio_devices
+
+  build-self-hosted:
+    runs-on: ${{ matrix.machine }}
+
+    strategy:
+      matrix:
+        machine: [rpi3-32, rpi3-64, rpi4-32, rpi4-64, jetson]
+
+    steps:
+    - uses: actions/checkout@v3
+
+    - name: Install dependencies
+      run: pip3 install -r requirements.txt
+
+    - name: Run demo
+      run: python3 pv_recorder_demo.py --show_audio_devices
diff --git a/.github/workflows/python.yml b/.github/workflows/python.yml
index e4b0b8e8..d6fbe3e6 100644
--- a/.github/workflows/python.yml
+++ b/.github/workflows/python.yml
@@ -5,8 +5,8 @@ on:
   push:
     branches: [ main ]
     paths:
-      - 'sdk/python/**'
-      - '!sdk/python/README.md'
+      - 'binding/python/**'
+      - '!binding/python/README.md'
       - 'lib/beaglebone/**'
       - 'lib/linux/**'
       - 'lib/jetson/**'
@@ -15,10 +15,10 @@ on:
       - 'lib/windows/**'
       - '.github/workflows/python.yml'
   pull_request:
-    branches: [ main ]
+    branches: [ main, 'v[0-9]+.[0-9]+' ]
     paths:
-      - 'sdk/python/**'
-      - '!sdk/python/README.md'
+      - 'binding/python/**'
+      - '!binding/python/README.md'
       - 'lib/beaglebone/**'
       - 'lib/linux/**'
       - 'lib/jetson/**'
@@ -29,7 +29,7 @@ on:
 
 defaults:
   run:
-    working-directory: sdk/python
+    working-directory: binding/python
 
 jobs:
   build-github-hosted:
@@ -37,7 +37,7 @@ jobs:
 
     strategy:
       matrix:
-        os: [ubuntu-latest, windows-latest, macos-latest]
+        os: [ubuntu-latest, macos-latest]
         python-version: ['3.7', '3.8', '3.9', '3.10', '3.11']
 
     steps:
@@ -50,29 +50,25 @@ jobs:
       with:
         python-version: ${{ matrix.python-version }}
 
-    - name: Pre-build
-      run: pip3 install wheel
-
-    - name: Build
-      run: python3 setup.py sdist bdist_wheel
-
-    - name: Run get audio devices
-      run: python3 demo.py --show_audio_devices
+    - name: Test
+      run: python3 test_pv_recorder.py
 
   build-self-hosted:
     runs-on: ${{ matrix.machine }}
 
     strategy:
       matrix:
-        machine: [rpi2, rpi3-32, rpi3-64, rpi4-32, rpi4-64, jetson, beaglebone]
+        machine: [rpi2, rpi3-32, rpi3-64, rpi4-32, rpi4-64, jetson, beaglebone, pv-windows]
 
     steps:
     - uses: actions/checkout@v3
       with:
         submodules: recursive
 
-    - name: Build
-      run: python3 setup.py sdist bdist_wheel
+    - name: Test
+      run: python3 test_pv_recorder.py
+      if: ${{ matrix.machine != 'pv-windows' }}
 
-    - name: Run get audio devices
-      run: python3 demo.py --show_audio_devices
+    - name: Test
+      run: python test_pv_recorder.py
+      if: ${{ matrix.machine == 'pv-windows' }}
diff --git a/.github/workflows/rust-codestyle.yml b/.github/workflows/rust-codestyle.yml
new file mode 100644
index 00000000..8523b66c
--- /dev/null
+++ b/.github/workflows/rust-codestyle.yml
@@ -0,0 +1,79 @@
+name: Rust Codestyle
+
+on:
+  workflow_dispatch:
+  push:
+    branches: [ main ]
+    paths:
+      - '.github/workflows/rust-codestyle.yml'
+      - 'binding/rust/**/*.rs'
+      - 'demo/rust/**/*.rs'
+  pull_request:
+    branches: [ main, 'v[0-9]+.[0-9]+' ]
+    paths:
+      - '.github/workflows/rust-codestyle.yml'
+      - 'binding/rust/**/*.rs'
+      - 'demo/rust/**/*.rs'
+
+env:
+  CARGO_TERM_COLOR: always
+
+defaults:
+  run:
+    shell: bash
+
+jobs:
+  check-rust-binding-codestyle:
+    runs-on: ubuntu-latest
+    defaults:
+      run:
+        working-directory: binding/rust
+
+    steps:
+    - uses: actions/checkout@v3
+
+    - name: Rust pre-build
+      run: bash copy.sh
+
+    - name: Rust dependencies
+      run: sudo apt install libasound2-dev -y
+
+    - name: Rust pre-build
+      run: bash copy.sh
+      working-directory: binding/rust
+
+    - name: Install stable toolchain
+      uses: actions-rs/toolchain@v1
+      with:
+        profile: minimal
+        toolchain: stable
+        override: true
+
+    - name: Run clippy
+      run: cargo clippy -- -D warnings
+
+  check-rust-demo-codestyle:
+    runs-on: ubuntu-latest
+    defaults:
+      run:
+        working-directory: demo/rust
+
+    steps:
+    - uses: actions/checkout@v3
+
+    - name: Rust dependencies
+      run: sudo apt install libasound2-dev -y
+
+    - name: Rust pre-build
+      run: bash copy.sh
+      working-directory: binding/rust
+
+    - name: Install stable toolchain
+      uses: actions-rs/toolchain@v1
+      with:
+        profile: minimal
+        toolchain: stable
+        override: true
+
+    - name: Run clippy
+      run: cargo clippy -- -D warnings
diff --git a/.github/workflows/rust-demos.yml b/.github/workflows/rust-demos.yml
new file mode 100644
index 00000000..3e2d67c6
--- /dev/null
+++ b/.github/workflows/rust-demos.yml
@@ -0,0 +1,81 @@
+name: Rust Demos
+
+on:
+  workflow_dispatch:
+  push:
+    branches: [ main ]
+    paths:
+      - '.github/workflows/rust-demos.yml'
+      - 'demo/rust/**'
+      - '!demo/rust/README.md'
+  pull_request:
+    branches: [ main, 'v[0-9]+.[0-9]+' ]
+    paths:
+      - '.github/workflows/rust-demos.yml'
+      - 'demo/rust/**'
+      - '!demo/rust/README.md'
+
+env:
+  CARGO_TERM_COLOR: always
+
+defaults:
+  run:
+    working-directory: demo/rust
+
+jobs:
+  build-github-hosted:
+    runs-on: ${{ matrix.os }}
+
+    strategy:
+      matrix:
+        os: [ubuntu-latest, windows-latest, macos-latest]
+
+    steps:
+    - uses: actions/checkout@v3
+
+    - name: Rust dependencies
+      if: matrix.os == 'ubuntu-latest'
+      run: sudo apt install libasound2-dev -y
+
+    - name: Rust pre-build
+      run: bash copy.sh
+      working-directory: binding/rust
+
+    - name: Install stable toolchain
+      uses: actions-rs/toolchain@v1
+      with:
+        profile: minimal
+        toolchain: stable
+        override: true
+
+    - name: Rust build demo
+      run: cargo build --verbose
+
+    - name: Test
+      run: cargo run --release -- --show_audio_devices
+
+  build-self-hosted:
+    runs-on: ${{ matrix.machine }}
+
+    strategy:
+      matrix:
+        machine: [rpi3-32, rpi3-64, rpi4-32, rpi4-64, beaglebone, jetson]
+
+    steps:
+    - uses: actions/checkout@v3
+
+    - name: Rust pre-build
+      run: bash copy.sh
+      working-directory: binding/rust
+
+    - name: Install stable toolchain
+      uses: actions-rs/toolchain@v1
+      with:
+        toolchain: stable
+        override: true
+
+    - name: Rust build demo
+      run: cargo build --verbose
+
+    - name: Test
+      run: cargo run --release -- --show_audio_devices
diff --git a/.github/workflows/rust.yml b/.github/workflows/rust.yml
index 5685bd5d..c8361ab6 100644
--- a/.github/workflows/rust.yml
+++ b/.github/workflows/rust.yml
@@ -5,8 +5,8 @@ on:
   push:
     branches: [ main ]
     paths:
-      - 'sdk/rust/**'
-      - '!sdk/rust/README.md'
+      - 'binding/rust/**'
+      - '!binding/rust/README.md'
       - 'lib/beaglebone/**'
       - 'lib/linux/**'
       - 'lib/jetson/**'
@@ -15,10 +15,10 @@ on:
       - 'lib/windows/**'
       - '.github/workflows/rust.yml'
   pull_request:
-    branches: [ main ]
+    branches: [ main, 'v[0-9]+.[0-9]+' ]
     paths:
-      - 'sdk/rust/**'
-      - '!sdk/rust/README.md'
+      - 'binding/rust/**'
+      - '!binding/rust/README.md'
       - 'lib/beaglebone/**'
       - 'lib/linux/**'
       - 'lib/jetson/**'
@@ -32,8 +32,7 @@ env:
 
 defaults:
   run:
-    working-directory: sdk/rust
-    shell: bash
+    working-directory: binding/rust
 
 jobs:
   build-github-hosted:
@@ -41,13 +40,17 @@ jobs:
 
     strategy:
       matrix:
-        os: [ubuntu-latest, windows-latest, macos-latest]
+        os: [ubuntu-latest, macos-latest]
 
     steps:
     - uses: actions/checkout@v3
       with:
         submodules: recursive
 
+    - name: Rust dependencies
+      if: matrix.os == 'ubuntu-latest'
+      run: sudo apt install libasound2-dev -y
+
     - name: Rust pre-build
       run: bash copy.sh
 
@@ -61,15 +64,15 @@ jobs:
     - name: Build
       run: cargo build --verbose
 
-    - name: Run get audio devices
-      run: cargo run --release --example demo -- --show_audio_devices
+    - name: Test
+      run: cargo test --release
 
   build-self-hosted:
     runs-on: ${{ matrix.machine }}
 
     strategy:
       matrix:
-        machine: [rpi2, rpi3-32, rpi3-64, rpi4-32, rpi4-64, jetson, beaglebone]
+        machine: [rpi2, rpi3-32, rpi3-64, rpi4-32, rpi4-64, jetson, beaglebone, pv-windows]
 
     steps:
     - uses: actions/checkout@v3
@@ -77,8 +80,16 @@ jobs:
         submodules: recursive
 
     - name: Rust pre-build
+      if: matrix.machine != 'pv-windows'
       run: bash copy.sh
 
+    - name: Rust pre-build windows
+      if: matrix.machine == 'pv-windows'
+      run: |
+        New-Item -Path "." -Name "data" -ItemType "directory"
+        New-Item -Path ".\data" -Name "lib" -ItemType "directory"
+        Copy-Item -Path "..\..\lib\windows" -Destination ".\data\lib" -Recurse
+
     - name: Install stable toolchain
       uses: actions-rs/toolchain@v1
       with:
@@ -88,5 +99,5 @@ jobs:
     - name: Build
       run: cargo build --verbose
 
-    - name: Run get audio devices
-      run: cargo run --release --example demo -- --show_audio_devices
+    - name: Test
+      run: cargo test --release
diff --git a/.github/workflows/spell-check.yml b/.github/workflows/spell-check.yml
index bb030238..a3d298d1 100644
--- a/.github/workflows/spell-check.yml
+++ b/.github/workflows/spell-check.yml
@@ -3,13 +3,13 @@ name: SpellCheck
 on:
   workflow_dispatch:
   push:
-    branches: [main]
+    branches: [ main ]
   pull_request:
-    branches: [main]
+    branches: [ main, 'v[0-9]+.[0-9]+' ]
 
 jobs:
   markdown:
-    runs-on: ubuntu-20.04
+    runs-on: ubuntu-latest
 
     steps:
       - uses: actions/checkout@v3
@@ -22,4 +22,4 @@ jobs:
         run: npm install -g cspell
 
       - name: Run CSpell
-        run: cspell --config resources/spell-check/.cspell.json "**/*"
+        run: cspell --config resources/.lint/spell-check/.cspell.json "**/*"
diff --git a/.gitmodules b/.gitmodules
index 216c3aac..7a355cb6 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -1,3 +1,3 @@
-[submodule "src/miniaudio"]
-    path = src/miniaudio
-    url = ../../mackron/miniaudio.git
+[submodule "project/src/miniaudio"]
+	path = project/src/miniaudio
+	url = ../../mackron/miniaudio.git
diff --git a/CMakeLists.txt b/CMakeLists.txt
deleted file mode 100644
index f493a3af..00000000
--- a/CMakeLists.txt
+++ /dev/null
@@ -1,121 +0,0 @@
-cmake_minimum_required(VERSION 3.4)
-project(pv_recorder VERSION 1.0.0 DESCRIPTION "Audio recorder library for Picovoice.")
-
-set(CMAKE_C_STANDARD 99)
-set(CMAKE_BUILD_TYPE Release)
-set(CMAKE_POSITION_INDEPENDENT_CODE ON)
-set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fvisibility=hidden")
-
-if(NOT PV_PLATFORM)
-    message(FATAL_ERROR "No `PV_PLATFORM` value was given.")
-endif()
-
-if (${PV_PLATFORM} STREQUAL "beaglebone")
-    set(PV_LINK_ATOMIC ON)
-    add_definitions(-D__PV_PLATFORM_BEAGLEBONE__)
-    add_compile_options(-mcpu=cortex-a8 -mtune=cortex-a8 -mfpu=neon)
-    add_link_options(-mcpu=cortex-a8 -mtune=cortex-a8 -mfpu=neon)
-elseif (${PV_PLATFORM} STREQUAL "mac-arm64")
-    add_definitions(-D__PV_PLATFORM_DARWIN__)
-    set(CMAKE_OSX_ARCHITECTURES "arm64")
-elseif (${PV_PLATFORM} STREQUAL "mac-x86_64")
-    add_definitions(-D__PV_PLATFORM_DARWIN__)
-    set(CMAKE_OSX_ARCHITECTURES "x86_64")
-elseif (${PV_PLATFORM} STREQUAL "linux")
-    add_definitions(-D__PV_PLATFORM_LINUX__)
-elseif (${PV_PLATFORM} STREQUAL "jetson-nano")
-    set(PV_LINK_ATOMIC ON)
-    add_definitions(-D__PV_PLATFORM_JETSON__)
-    add_compile_options(-mcpu=cortex-a57 -mtune=cortex-a57)
-    add_link_options(-mcpu=cortex-a57 -mtune=cortex-a57)
-elseif (${PV_PLATFORM} STREQUAL "raspberry-pi")
-    set(PV_LINK_ATOMIC ON)
-    add_definitions(-D__PV_PLATFORM_RASPBERRYPI__)
-    add_compile_options(-mcpu=arm1176jzf-s -mtune=arm1176jzf-s -mfloat-abi=hard -mfpu=vfp)
-    add_link_options(-mcpu=arm1176jzf-s -mtune=arm1176jzf-s -mfloat-abi=hard -mfpu=vfp)
-elseif (${PV_PLATFORM} STREQUAL "raspberry-pi2")
-    set(PV_LINK_ATOMIC ON)
-    add_definitions(-D__PV_PLATFORM_RASPBERRYPI__)
-    add_compile_options(-mcpu=cortex-a7 -mtune=cortex-a7 -mfloat-abi=hard -mfpu=neon-vfpv4)
-    add_link_options(-mcpu=cortex-a7 -mtune=cortex-a7 -mfloat-abi=hard -mfpu=neon-vfpv4)
-elseif (${PV_PLATFORM} STREQUAL "raspberry-pi3")
-    set(PV_LINK_ATOMIC ON)
-    add_definitions(-D__PV_PLATFORM_RASPBERRYPI__)
-    add_compile_options(-mcpu=cortex-a53 -mtune=cortex-a53 -mfloat-abi=hard -mfpu=neon-fp-armv8)
-    add_link_options(-mcpu=cortex-a53 -mtune=cortex-a53 -mfloat-abi=hard -mfpu=neon-fp-armv8)
-elseif (${PV_PLATFORM} STREQUAL "raspberry-pi3-64")
-    set(PV_LINK_ATOMIC ON)
-    add_definitions(-D__PV_PLATFORM_RASPBERRYPI__)
-    add_compile_options(-mcpu=cortex-a53 -mtune=cortex-a53)
-    add_link_options(-mcpu=cortex-a53 -mtune=cortex-a53)
-elseif (${PV_PLATFORM} STREQUAL "raspberry-pi4")
-    set(PV_LINK_ATOMIC ON)
-    add_definitions(-D__PV_PLATFORM_RASPBERRYPI__)
-    add_compile_options(-mcpu=cortex-a72 -mtune=cortex-a72 -mfloat-abi=hard -mfpu=neon-fp-armv8)
-    add_link_options(-mcpu=cortex-a72 -mtune=cortex-a72 -mfloat-abi=hard -mfpu=neon-fp-armv8)
-elseif (${PV_PLATFORM} STREQUAL "raspberry-pi4-64")
-    set(PV_LINK_ATOMIC ON)
-    add_definitions(-D__PV_PLATFORM_RASPBERRYPI__)
-    add_compile_options(-mcpu=cortex-a72 -mtune=cortex-a72)
-    add_link_options(-mcpu=cortex-a72 -mtune=cortex-a72)
-elseif (${PV_PLATFORM} STREQUAL "windows")
-    add_definitions(-D__PV_PLATFORM_WINDOWS__)
-else ()
-    message(FATAL_ERROR "Unknown platform `${PV_PLATFORM}`.")
-endif ()
-
-add_library(pv_recorder_object OBJECT src/pv_circular_buffer.c src/pv_recorder.c)
-
-target_include_directories(pv_recorder_object PUBLIC include)
-target_include_directories(pv_recorder_object PRIVATE src/miniaudio)
-
-add_library(pv_recorder SHARED $<TARGET_OBJECTS:pv_recorder_object>)
-
-set_target_properties(pv_recorder PROPERTIES
-        PUBLIC_HEADER include/pv_recorder.h)
-
-if (NOT ${PV_PLATFORM} STREQUAL "windows")
-    target_link_libraries(pv_recorder pthread dl m)
-endif()
-
-if(DEFINED OUTPUT_DIR)
-    add_custom_command(TARGET pv_recorder POST_BUILD
-            COMMAND ${CMAKE_COMMAND} -E copy
-            $<TARGET_FILE:${PROJECT_NAME}>
-            "${CMAKE_SOURCE_DIR}/lib/${OUTPUT_DIR}/$<TARGET_FILE_NAME:${PROJECT_NAME}>"
-            COMMENT "Copying to output directory.")
-endif()
-
-add_executable(demo example/demo.c)
-
-target_include_directories(demo PUBLIC include)
-
-if (NOT ${PV_PLATFORM} STREQUAL "windows")
-    target_link_libraries(demo pv_recorder pthread dl m)
-    if(PV_LINK_ATOMIC)
-        target_link_libraries(demo pv_recorder atomic)
-    endif()
-else()
-    target_link_libraries(demo pv_recorder)
-endif()
-
-enable_testing()
-
-add_executable(test_circular_buffer test/test_pv_circular_buffer.c src/pv_circular_buffer.c)
-
-target_include_directories(test_circular_buffer PUBLIC include)
-
-add_test(
-        NAME test_circular_buffer
-        COMMAND test_circular_buffer
-)
-
-add_custom_command(
-        TARGET test_circular_buffer
-        COMMENT "Run Tests"
-        POST_BUILD
-        WORKING_DIRECTORY ${CMAKE_BINARY_DIR}
-        COMMAND ${CMAKE_CTEST_COMMAND} -C $<CONFIGURATION> -R "^test_circular_buffer" --output-on-failures
-)
-
-add_subdirectory(node)
\ No newline at end of file
diff --git a/README.md b/README.md
index f1a5d170..16ee56a8 100644
--- a/README.md
+++ b/README.md
@@ -2,7 +2,6 @@
 
 [![GitHub release](https://img.shields.io/github/release/Picovoice/pvrecorder.svg)](https://github.com/Picovoice/pvrecorder/releases)
 [![GitHub](https://img.shields.io/github/license/Picovoice/pvrecorder)](https://github.com/Picovoice/pvrecorder/)
-[![GitHub language count](https://img.shields.io/github/languages/count/Picovoice/pvrecorder)](https://github.com/Picovoice/pvrecorder/)
 
 <!-- markdown-link-check-disable -->
 [![PyPI](https://img.shields.io/pypi/v/pvrecorder)](https://pypi.org/project/pvrecorder/)
@@ -24,159 +23,341 @@ PvRecorder is an easy-to-use cross-platform audio recorder designed for real-tim
 ## Table of Contents
 - [PvRecorder](#pvrecorder)
   - [Table of Contents](#table-of-contents)
-  - [Requirements](#requirements)
-  - [Compatibility](#compatibility)
-  - [Compiling](#compiling)
-  - [Usage](#usage)
-    - [Selecting an Audio Device](#selecting-an-audio-device)
-  - [Demo](#demo)
+  - [Source Code](#source-code)
+  - [Demos](#demos)
+    - [Python](#python-demo)
+    - [.NET](#net-demo)
+    - [Go](#go-demo)
+    - [Node.js](#nodejs-demo)
+    - [Rust](#rust-demo)
+    - [C](#c-demo)
   - [SDKs](#sdks)
+    - [Python](#python)
+    - [.NET](#net)
+    - [Go](#go)
+    - [Node.js](#nodejs)
+    - [Rust](#rust)
 
-## Requirements
+## Source Code
 
-- CMake 3.4+.
-- C99 compatible compiler.
-- **Windows**: MinGW.
+If you are interested in building PvRecorder from source or integrating it into an existing C project, the PvRecorder
+source code is located under the [/project](./project) directory.
 
-## Compatibility
+## Demos
 
-- Linux (x86_64)
-- macOS (x86_64, arm64)
-- Windows (amd64)
-- Raspberry Pi (Zero, 2, 3 and 4)
-- NVIDIA Jetson Nano
-- BeagleBone
+If using SSH, clone the repository with:
 
-## Compiling
+```console
+git clone --recurse-submodules git@github.com:Picovoice/pvrecorder.git
+```
+
+If using HTTPS, clone the repository with:
+
+```console
+git clone --recurse-submodules https://github.com/Picovoice/pvrecorder.git
+```
 
-The variable `{OUTPUT_DIR}` will be used to select the directory to copy the shared object
-after a successful compilation. `{OUTPUT_DIR}` should be a directory **relative** to the [lib](lib) directory.
+### Python Demo
 
-Run the following commands to build and test (`{OUTPUT_DIR}` can be empty if you wish not to copy):
+Install the demo package:
 
 ```console
-git submodule update --init --recursive
-mkdir build && cd build
-cmake .. -DOUTPUT_DIR={OUTPUT_DIR}
-cmake --build .
+pip3 install pvrecorderdemo
 ```
 
-## Usage
-1. Create a PvRecorder object:
-```c
-#include "pv_recorder.h"
+To show the available audio devices run:
+
+```console
+pv_recorder_demo --show_audio_devices
+```
 
-const int32_t device_index = -1; // -1 == default device
-const int32_t frame_length = 512;
-const int32_t buffer_size_msec = 100;
-const bool log_overflow = true;
-const bool log_silence = false;
+With a working microphone connected to your device run the following in the terminal:
 
-pv_recorder_t *recorder = NULL;
-pv_recorder_status_t status = pv_recorder_init(
-        device_index, 
-        frame_length, 
-        buffer_size_msec, 
-        log_overflow, 
-        log_silence, 
-        &recorder);
-if (status != PV_RECORDER_STATUS_SUCCESS) {
-    // handle PvRecorder init error
-}
+```console
+pv_recorder_demo --output_wav_path {OUTPUT_WAV_PATH}
 ```
 
-2. Start recording audio:
+Replace `{OUTPUT_WAV_PATH}` with the path to save the audio data in `wav` format.
 
-```c
-pv_recorder_status_t status = pv_recorder_start(recorder);
-if (status != PV_RECORDER_STATUS_SUCCESS) {
-    // handle PvRecorder start error
-}
+For more information about the Python demos go to [demo/python](demo/python).
+
+
+### .NET Demo
+
+From [demo/dotnet/PvRecorderDemo](demo/dotnet/PvRecorderDemo) run the
+following in the terminal to build the demo:
+
+```console
+dotnet build
+```
+
+Make sure there is a working microphone connected to your device. From [demo/dotnet/PvRecorderDemo](demo/dotnet/PvRecorderDemo) run the
+following in the terminal:
+
+```console
+dotnet run -- --output_wav_path ${OUTPUT_WAV_PATH}
+```
+
+For more information about the .NET demo go to [demo/dotnet](demo/dotnet).
+
+### Go Demo
+
+The demo requires `cgo`, which on Windows may mean that you need to install a gcc compiler like [MinGW](http://mingw-w64.org) to build it properly.
+
+From [demo/go](demo/go) run the following commands from the terminal.
+
+```console
+go run demo.go --output_wav_path {OUTPUT_WAV_PATH}
+```
+
+Replace  `{OUTPUT_WAV_PATH}` with a file path to save the audio data in `wav` format.
+
+For more information about Go demos go to [demo/go](demo/go).
+
+### Node.js Demo
+
+Install the demo package:
+
+```console
+yarn global add @picovoice/pvrecorder-node-demo
+```
+
+With a working microphone connected to your device run the following in the terminal:
+
+```console
+pvrecorder-node-demo --output_wav_path ${OUTPUT_WAV_PATH}
+```
+
+Replace `{OUTPUT_WAV_PATH}` with the file path to save the audio data in `wav` format.
+
+For more information about NodeJS demos go to [demo/nodejs](demo/nodejs/).
+
+### Rust Demo
+
+Make sure there is a working microphone connected to your device. From [demo/rust/](demo/rust) run the following in the terminal to build and run the demo:
+
+```console
+cargo run --release -- --output_wav_path ${OUTPUT_WAV_PATH}
+```
+
+For more information about the Rust demo go to [demo/rust](demo/rust).
+
+### C Demo
+
+Run the following commands to build the demo app:
+
+```console
+cd demo/c
+cmake -S . -B build -DPV_RECORDER_PLATFORM={PV_RECORDER_PLATFORM}
+cmake --build build
+```
+
+The `{PV_RECORDER_PLATFORM}` variable will set the compilation flags for the given platform. Exclude this variable
+to get a list of possible values.
+
+Get a list of available audio recording devices:
+```console
+./pv_recorder_demo --show_audio_devices
+```
+
+Record to a file with a given audio device index:
+```console
+./pv_recorder_demo -o test.wav -d 2
+```
+
+Hit `Ctrl+C` to stop recording. If no audio device index (`-d`) is provided, the demo will use the system's default recording device.
+
+## SDKs
+
+### Python
+
+To start recording initialize an instance and run start:
+
+```python
+from pvrecorder import PvRecorder
+
+recorder = PvRecorder(frame_length=512)
+recorder.start()
+```
+
+Get the frames by calling the read function:
+
+```python
+frame = recorder.read()
+```
+
+To stop recording just run stop on the instance:
+
+```python
+recorder.stop()
 ```
 
-3. Read frames of audio from the recorder:
-```c
-// must have length equal to `frame_length` that was given to `pv_recorder_init()`
-int16_t *frame = malloc(frame_length * sizeof(int16_t));
+Once you are done, free the used resources. You do not have to call stop before delete:
+
+```python
+recorder.delete()
+```
+
+### .NET
+
+Install the .NET SDK using NuGet or the dotnet CLI:
 
+```console
+dotnet add package PvRecorder
+```
+
+Initialize and begin recording:
+
+```csharp
+using Pv;
+
+PvRecorder recorder = PvRecorder.Create(frameLength: 512);
+recorder.Start();
+```
+
+Read a frame of audio:
+
+```csharp
 while (true) {
-    pv_recorder_status_t status = pv_recorder_read(recorder, frame);
-    if (status != PV_RECORDER_STATUS_SUCCESS) {
-        // handle PvRecorder read error
-    }
-    
-    // use frame of audio data
-    // ...      
+    short[] frame = recorder.Read();
+    // do something with audio frame
 }
 ```
 
-4. Stop recording:
+To stop recording:
 
-```c
-pv_recorder_status_t status = pv_recorder_stop(recorder);
-if (status != PV_RECORDER_STATUS_SUCCESS) {
-    // handle PvRecorder stop error
+```csharp
+recorder.Stop();
+```
+
+Once you are done, free the used resources. You do not have to call `Stop()` before `Dispose()`:
+
+```csharp
+recorder.Dispose();
+```
+
+To have resources freed immediately after use without explicitly calling `Dispose()`, wrap `PvRecorder` in a `using` statement:
+
+```csharp
+using (PvRecorder recorder = PvRecorder.Create(frameLength: 512)) {
+    // PvRecorder usage
 }
 ```
 
-5. Release resources used by PvRecorder:
-```c
-pv_recorder_delete(recorder);
-free(frame);
+### Go
+
+To install the PvRecorder Go module to your project, use the command:
+
+```console
+go get github.com/Picovoice/pvrecorder/binding/go
 ```
 
-### Selecting an Audio Device
+To start recording initialize an instance and run start:
 
-To print a list of available audio devices:
-```c
-char **devices = NULL;
-int32_t count = 0;
+```go
+import . "github.com/Picovoice/pvrecorder/binding/go"
 
-pv_recorder_status_t status = pv_recorder_get_audio_devices(&count, &devices);
-if (status != PV_RECORDER_STATUS_SUCCESS) {
-    // handle PvRecorder get audio devices error
+recorder = NewPvRecorder(/*FrameLength*/512)
+recorder.Init()
+if err != nil {
+    // handle init error
 }
+defer recorder.Delete()
 
-fprintf(stdout, "Printing devices...\n");
-for (int32_t i = 0; i < count; i++) {
-    fprintf(stdout, "index: %d, name: %s\n", i, devices[i]);
+err = recorder.Start()
+if err != nil {
+    // handle start error
 }
+```
+
+Get a frame of audio by calling the `Read()` function:
 
-pv_recorder_free_device_list(count, devices);
+```go
+frame, err := recorder.Read()
+if err != nil {
+    // handle error
+}
+// do something with frame
 ```
 
-The index of the device in the returned list can be used in `pv_recorder_init()` to select that device for recording.
+To stop recording, run stop on the instance:
+
+```go
+recorder.Stop()
+```
 
-Refer to [example/demo.c](example/demo.c) for a full example of how to use `pv_recorder` to capture audio in C.
+Once you are done, free the used resources. You do not have to call stop before delete:
 
-## Demo
+```go
+recorder.Delete()
+```
 
-The executable `demo` is built as part of the compilation process.
+### Node.js
 
-To see the usage options for the demo:
+Install Node.js binding:
 
 ```console
-./demo
+yarn add @picovoice/pvrecorder-node
 ```
 
-Run the following to see what devices are available for audio capture:
+To start recording initialize the instance and run `start`:
 
-```console
-./demo --show_audio_devices
+```javascript
+const recorder = new PvRecorder(512);
+recorder.start()
 ```
 
-Run the demo with the desired audio device (or -1 for the default one) and an output file path:
-```console
-./demo ${DEVICE_INDEX} ${OUTPUT_FILE_PATH}
+Get a frame of audio by calling the read function:
+
+```javascript
+while (recorder.isRecording) {
+    const frame = await recorder.read();
+    // do something with frame
+}
 ```
 
-## SDKs
+To stop recording, run stop on the instance:
+
+```javascript
+recorder.stop();
+```
 
-Available SDKs for PvRecorder:
+Once you are done, free the used resources. You do not have to call stop before release:
 
-- [.NET](sdk/dotnet)
-- [Go](sdk/go)
-- [Node.js](sdk/nodejs)
-- [Python](sdk/python)
-- [Rust](sdk/rust)
+```javascript
+recorder.release();
+```
+
+### Rust
+
+Add `pv_recorder` to your app's `Cargo.toml` manifest:
+
+```toml
+[dependencies]
+pv_recorder = "*"
+```
+
+To start recording initialize the instance and run `start`:
 
+```rust
+use pv_recorder::PvRecorderBuilder
+
+let frame_length = 512;
+let recorder = PvRecorderBuilder::new(frame_length).init()?;
+recorder.start()?;
+```
+
+Get a frame of audio by calling the read function:
+
+```rust
+while recorder.is_recording() {
+    let frame = recorder.read()?;
+    // do something with frame
+}
+```
+
+To stop recording, run stop on the instance:
+
+```rust
+recorder.stop()?;
+```
diff --git a/binding/dotnet/.editorconfig b/binding/dotnet/.editorconfig
new file mode 100644
index 00000000..a52b6927
--- /dev/null
+++ b/binding/dotnet/.editorconfig
@@ -0,0 +1,373 @@
+root = true
+
+# All files
+[*]
+indent_style = space
+
+# Xml files
+[*.xml]
+indent_size = 2
+
+# C# files
+[*.cs]
+
+#### Core EditorConfig Options ####
+
+# Indentation and spacing
+indent_size = 4
+tab_width = 4
+
+# New line preferences
+insert_final_newline = false
+
+#### .NET Coding Conventions ####
+[*.{cs,vb}]
+
+# Organize usings
+dotnet_separate_import_directive_groups = true
+dotnet_sort_system_directives_first = true
+file_header_template = unset
+
+# this. and Me. preferences
+dotnet_style_qualification_for_event = false:silent
+dotnet_style_qualification_for_field = false:silent
+dotnet_style_qualification_for_method = false:silent
+dotnet_style_qualification_for_property = false:silent
+
+# Language keywords vs BCL types preferences
+dotnet_style_predefined_type_for_locals_parameters_members = true:silent
+dotnet_style_predefined_type_for_member_access = true:silent
+
+# Parentheses preferences
+dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent
+dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent
+dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent
+dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent
+
+# Modifier preferences
+dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent
+
+# Expression-level preferences
+dotnet_style_coalesce_expression = true:suggestion
+dotnet_style_collection_initializer = true:suggestion
+dotnet_style_explicit_tuple_names = true:suggestion
+dotnet_style_null_propagation = true:suggestion
+dotnet_style_object_initializer = true:suggestion
+dotnet_style_operator_placement_when_wrapping = beginning_of_line
+dotnet_style_prefer_auto_properties = true:suggestion
+dotnet_style_prefer_compound_assignment = false
+dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion
+dotnet_style_prefer_conditional_expression_over_return = true:suggestion
+dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
+dotnet_style_prefer_inferred_tuple_names = true:suggestion
+dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
+dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
+dotnet_style_prefer_simplified_interpolation = true:suggestion
+
+# Field preferences
+dotnet_style_readonly_field = true:warning
+
+# Parameter preferences
+dotnet_code_quality_unused_parameters = all:suggestion
+
+# Suppression preferences
+dotnet_remove_unnecessary_suppression_exclusions = none
+
+#### C# Coding Conventions ####
+[*.cs]
+
+# var preferences
+csharp_style_var_elsewhere = false:silent
+csharp_style_var_for_built_in_types = false:silent
+csharp_style_var_when_type_is_apparent = false:silent
+
+# Expression-bodied members
+csharp_style_expression_bodied_accessors = true:silent
+csharp_style_expression_bodied_constructors = false:silent
+csharp_style_expression_bodied_indexers = true:silent
+csharp_style_expression_bodied_lambdas = true:suggestion
+csharp_style_expression_bodied_local_functions = false:silent
+csharp_style_expression_bodied_methods = false:silent
+csharp_style_expression_bodied_operators = false:silent
+csharp_style_expression_bodied_properties = true:silent
+
+# Pattern matching preferences
+csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
+csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
+csharp_style_prefer_not_pattern = true:suggestion
+csharp_style_prefer_pattern_matching = true:silent
+csharp_style_prefer_switch_expression = false
+
+# Null-checking preferences
+csharp_style_conditional_delegate_call = true:suggestion
+
+# Modifier preferences
+csharp_prefer_static_local_function = true:warning
+csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:silent
+
+# Code-block preferences
+csharp_prefer_braces = true:silent
+csharp_prefer_simple_using_statement = true:suggestion
+
+# Expression-level preferences
+csharp_prefer_simple_default_expression = true:suggestion
+csharp_style_deconstructed_variable_declaration = true:suggestion
+csharp_style_inlined_variable_declaration = true:suggestion
+csharp_style_pattern_local_over_anonymous_function = true:suggestion
+csharp_style_prefer_index_operator = true:suggestion
+csharp_style_prefer_range_operator = true:suggestion
+csharp_style_throw_expression = true:suggestion
+csharp_style_unused_value_assignment_preference = discard_variable:suggestion
+csharp_style_unused_value_expression_statement_preference = discard_variable:silent
+
+# 'using' directive preferences
+csharp_using_directive_placement = outside_namespace:silent
+
+#### C# Formatting Rules ####
+
+# New line preferences
+csharp_new_line_before_catch = true
+csharp_new_line_before_else = true
+csharp_new_line_before_finally = true
+csharp_new_line_before_members_in_anonymous_types = true
+csharp_new_line_before_members_in_object_initializers = true
+csharp_new_line_before_open_brace = all
+csharp_new_line_between_query_expression_clauses = true
+
+# Indentation preferences
+csharp_indent_block_contents = true
+csharp_indent_braces = false
+csharp_indent_case_contents = true
+csharp_indent_case_contents_when_block = true
+csharp_indent_labels = one_less_than_current
+csharp_indent_switch_labels = true
+
+# Space preferences
+csharp_space_after_cast = false
+csharp_space_after_colon_in_inheritance_clause = true
+csharp_space_after_comma = true
+csharp_space_after_dot = false
+csharp_space_after_keywords_in_control_flow_statements = true
+csharp_space_after_semicolon_in_for_statement = true
+csharp_space_around_binary_operators = before_and_after
+csharp_space_around_declaration_statements = false
+csharp_space_before_colon_in_inheritance_clause = true
+csharp_space_before_comma = false
+csharp_space_before_dot = false
+csharp_space_before_open_square_brackets = false
+csharp_space_before_semicolon_in_for_statement = false
+csharp_space_between_empty_square_brackets = false
+csharp_space_between_method_call_empty_parameter_list_parentheses = false
+csharp_space_between_method_call_name_and_opening_parenthesis = false
+csharp_space_between_method_call_parameter_list_parentheses = false
+csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
+csharp_space_between_method_declaration_name_and_open_parenthesis = false
+csharp_space_between_method_declaration_parameter_list_parentheses = false
+csharp_space_between_parentheses = false
+csharp_space_between_square_brackets = false
+
+# Wrapping preferences
+csharp_preserve_single_line_blocks = true
+csharp_preserve_single_line_statements = true
+
+#### Naming styles ####
+[*.{cs,vb}]
+
+# Naming rules
+
+dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.severity = suggestion
+dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.symbols = types_and_namespaces
+dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.interfaces_should_be_ipascalcase.severity = suggestion
+dotnet_naming_rule.interfaces_should_be_ipascalcase.symbols = interfaces
+dotnet_naming_rule.interfaces_should_be_ipascalcase.style = ipascalcase
+
+dotnet_naming_rule.type_parameters_should_be_tpascalcase.severity = suggestion
+dotnet_naming_rule.type_parameters_should_be_tpascalcase.symbols = type_parameters
+dotnet_naming_rule.type_parameters_should_be_tpascalcase.style = tpascalcase
+
+dotnet_naming_rule.methods_should_be_pascalcase.severity = suggestion
+dotnet_naming_rule.methods_should_be_pascalcase.symbols = methods
+dotnet_naming_rule.methods_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.properties_should_be_pascalcase.severity = suggestion
+dotnet_naming_rule.properties_should_be_pascalcase.symbols = properties
+dotnet_naming_rule.properties_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.events_should_be_pascalcase.severity = suggestion
+dotnet_naming_rule.events_should_be_pascalcase.symbols = events
+dotnet_naming_rule.events_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.local_variables_should_be_camelcase.severity = suggestion
+dotnet_naming_rule.local_variables_should_be_camelcase.symbols = local_variables
+dotnet_naming_rule.local_variables_should_be_camelcase.style = camelcase
+
+dotnet_naming_rule.local_constants_should_be_camelcase.severity = suggestion
+dotnet_naming_rule.local_constants_should_be_camelcase.symbols = local_constants
+dotnet_naming_rule.local_constants_should_be_camelcase.style = camelcase
+
+dotnet_naming_rule.parameters_should_be_camelcase.severity = suggestion
+dotnet_naming_rule.parameters_should_be_camelcase.symbols = parameters
+dotnet_naming_rule.parameters_should_be_camelcase.style = camelcase
+
+dotnet_naming_rule.public_fields_should_be_pascalcase.severity = suggestion
+dotnet_naming_rule.public_fields_should_be_pascalcase.symbols = public_fields
+dotnet_naming_rule.public_fields_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.private_fields_should_be__camelcase.severity = suggestion
+dotnet_naming_rule.private_fields_should_be__camelcase.symbols = private_fields
+dotnet_naming_rule.private_fields_should_be__camelcase.style = _camelcase
+
+dotnet_naming_rule.private_static_fields_should_be_s_camelcase.severity = suggestion
+dotnet_naming_rule.private_static_fields_should_be_s_camelcase.symbols = private_static_fields
+dotnet_naming_rule.private_static_fields_should_be_s_camelcase.style = s_camelcase
+
+dotnet_naming_rule.public_constant_fields_should_be_pascalcase.severity = suggestion
+dotnet_naming_rule.public_constant_fields_should_be_pascalcase.symbols = public_constant_fields
+dotnet_naming_rule.public_constant_fields_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.private_constant_fields_should_be_pascalcase.severity = suggestion
+dotnet_naming_rule.private_constant_fields_should_be_pascalcase.symbols = private_constant_fields
+dotnet_naming_rule.private_constant_fields_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.public_static_readonly_fields_should_be_pascalcase.severity = suggestion
+dotnet_naming_rule.public_static_readonly_fields_should_be_pascalcase.symbols = public_static_readonly_fields
+dotnet_naming_rule.public_static_readonly_fields_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.private_static_readonly_fields_should_be_pascalcase.severity = suggestion
+dotnet_naming_rule.private_static_readonly_fields_should_be_pascalcase.symbols = private_static_readonly_fields
+dotnet_naming_rule.private_static_readonly_fields_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.enums_should_be_pascalcase.severity = suggestion
+dotnet_naming_rule.enums_should_be_pascalcase.symbols = enums
+dotnet_naming_rule.enums_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.local_functions_should_be_pascalcase.severity = suggestion
+dotnet_naming_rule.local_functions_should_be_pascalcase.symbols = local_functions
+dotnet_naming_rule.local_functions_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.non_field_members_should_be_pascalcase.severity = none
+dotnet_naming_rule.non_field_members_should_be_pascalcase.symbols = non_field_members
+dotnet_naming_rule.non_field_members_should_be_pascalcase.style = pascalcase
+
+# Symbol specifications
+
+dotnet_naming_symbols.interfaces.applicable_kinds = interface
+dotnet_naming_symbols.interfaces.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.interfaces.required_modifiers = 
+
+dotnet_naming_symbols.enums.applicable_kinds = enum
+dotnet_naming_symbols.enums.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.enums.required_modifiers = 
+
+dotnet_naming_symbols.events.applicable_kinds = event
+dotnet_naming_symbols.events.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.events.required_modifiers = 
+
+dotnet_naming_symbols.methods.applicable_kinds = method
+dotnet_naming_symbols.methods.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.methods.required_modifiers = 
+
+dotnet_naming_symbols.properties.applicable_kinds = property
+dotnet_naming_symbols.properties.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.properties.required_modifiers = 
+
+dotnet_naming_symbols.public_fields.applicable_kinds = field
+dotnet_naming_symbols.public_fields.applicable_accessibilities = public, internal
+dotnet_naming_symbols.public_fields.required_modifiers = 
+
+dotnet_naming_symbols.private_fields.applicable_kinds = field
+dotnet_naming_symbols.private_fields.applicable_accessibilities = private, protected, protected_internal, private_protected
+dotnet_naming_symbols.private_fields.required_modifiers = 
+
+dotnet_naming_symbols.private_static_fields.applicable_kinds = field
+dotnet_naming_symbols.private_static_fields.applicable_accessibilities = private, protected, protected_internal, private_protected
+dotnet_naming_symbols.private_static_fields.required_modifiers = static
+
+dotnet_naming_symbols.types_and_namespaces.applicable_kinds = namespace, class, struct, interface, enum
+dotnet_naming_symbols.types_and_namespaces.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.types_and_namespaces.required_modifiers = 
+
+dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
+dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.non_field_members.required_modifiers = 
+
+dotnet_naming_symbols.type_parameters.applicable_kinds = namespace
+dotnet_naming_symbols.type_parameters.applicable_accessibilities = *
+dotnet_naming_symbols.type_parameters.required_modifiers = 
+
+dotnet_naming_symbols.private_constant_fields.applicable_kinds = field
+dotnet_naming_symbols.private_constant_fields.applicable_accessibilities = private, protected, protected_internal, private_protected
+dotnet_naming_symbols.private_constant_fields.required_modifiers = const
+
+dotnet_naming_symbols.local_variables.applicable_kinds = local
+dotnet_naming_symbols.local_variables.applicable_accessibilities = local
+dotnet_naming_symbols.local_variables.required_modifiers = 
+
+dotnet_naming_symbols.local_constants.applicable_kinds = local
+dotnet_naming_symbols.local_constants.applicable_accessibilities = local
+dotnet_naming_symbols.local_constants.required_modifiers = const
+
+dotnet_naming_symbols.parameters.applicable_kinds = parameter
+dotnet_naming_symbols.parameters.applicable_accessibilities = *
+dotnet_naming_symbols.parameters.required_modifiers = 
+
+dotnet_naming_symbols.public_constant_fields.applicable_kinds = field
+dotnet_naming_symbols.public_constant_fields.applicable_accessibilities = public, internal
+dotnet_naming_symbols.public_constant_fields.required_modifiers = const
+
+dotnet_naming_symbols.public_static_readonly_fields.applicable_kinds = field
+dotnet_naming_symbols.public_static_readonly_fields.applicable_accessibilities = public, internal
+dotnet_naming_symbols.public_static_readonly_fields.required_modifiers = readonly, static
+
+dotnet_naming_symbols.private_static_readonly_fields.applicable_kinds = field
+dotnet_naming_symbols.private_static_readonly_fields.applicable_accessibilities = private, protected, protected_internal, private_protected
+dotnet_naming_symbols.private_static_readonly_fields.required_modifiers = readonly, static
+
+dotnet_naming_symbols.local_functions.applicable_kinds = local_function
+dotnet_naming_symbols.local_functions.applicable_accessibilities = *
+dotnet_naming_symbols.local_functions.required_modifiers = 
+
+# Naming styles
+
+dotnet_naming_style.pascalcase.required_prefix = 
+dotnet_naming_style.pascalcase.required_suffix = 
+dotnet_naming_style.pascalcase.word_separator = 
+dotnet_naming_style.pascalcase.capitalization = pascal_case
+
+dotnet_naming_style.ipascalcase.required_prefix = I
+dotnet_naming_style.ipascalcase.required_suffix = 
+dotnet_naming_style.ipascalcase.word_separator = 
+dotnet_naming_style.ipascalcase.capitalization = pascal_case
+
+dotnet_naming_style.tpascalcase.required_prefix = T
+dotnet_naming_style.tpascalcase.required_suffix = 
+dotnet_naming_style.tpascalcase.word_separator = 
+dotnet_naming_style.tpascalcase.capitalization = pascal_case
+
+dotnet_naming_style._camelcase.required_prefix = _
+dotnet_naming_style._camelcase.required_suffix = 
+dotnet_naming_style._camelcase.word_separator = 
+dotnet_naming_style._camelcase.capitalization = camel_case
+
+dotnet_naming_style.camelcase.required_prefix = 
+dotnet_naming_style.camelcase.required_suffix = 
+dotnet_naming_style.camelcase.word_separator = 
+dotnet_naming_style.camelcase.capitalization = camel_case
+
+dotnet_naming_style.s_camelcase.required_prefix = s_
+dotnet_naming_style.s_camelcase.required_suffix = 
+dotnet_naming_style.s_camelcase.word_separator = 
+dotnet_naming_style.s_camelcase.capitalization = camel_case
+
+#### Exclusions
+
+dotnet_diagnostic.IDE0130.severity = none
+dotnet_diagnostic.IDE1006.severity = none
+dotnet_diagnostic.IDE0018.severity = none
+dotnet_diagnostic.IDE0010.severity = none
+dotnet_diagnostic.IDE0090.severity = none
+dotnet_diagnostic.IDE0046.severity = none
+dotnet_diagnostic.IDE0078.severity = none
+dotnet_diagnostic.IDE0058.severity = none
diff --git a/binding/dotnet/.gitignore b/binding/dotnet/.gitignore
new file mode 100644
index 00000000..a3a090e7
--- /dev/null
+++ b/binding/dotnet/.gitignore
@@ -0,0 +1,8 @@
+.vs
+PvRecorder/bin/**
+PvRecorder/obj/**
+PvRecorder/Properties/**
+PvRecorderTest/bin/**
+PvRecorderTest/obj/**
+PvRecorderTest/Properties/**
+*.user
\ No newline at end of file
diff --git a/sdk/dotnet/PvRecorder.sln b/binding/dotnet/PvRecorder.sln
similarity index 70%
rename from sdk/dotnet/PvRecorder.sln
rename to binding/dotnet/PvRecorder.sln
index b9921d35..a1da9138 100644
--- a/sdk/dotnet/PvRecorder.sln
+++ b/binding/dotnet/PvRecorder.sln
@@ -1,11 +1,11 @@
 ﻿
 Microsoft Visual Studio Solution File, Format Version 12.00
-# Visual Studio Version 16
-VisualStudioVersion = 16.0.31515.178
+# Visual Studio Version 17
+VisualStudioVersion = 17.3.32929.385
 MinimumVisualStudioVersion = 10.0.40219.1
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PvRecorder", "PvRecorder\PvRecorder.csproj", "{F65B5A14-4D7F-4B24-8F1B-2DCBE8EA0482}"
 EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Demo", "Demo\Demo.csproj", "{C7AC1DE1-77B8-4A27-BA4D-5D5FD3812A28}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PvRecorderTest", "PvRecorderTest\PvRecorderTest.csproj", "{9242930B-A875-4677-8EA9-4B556B5AF3D0}"
 EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -17,10 +17,10 @@ Global
 		{F65B5A14-4D7F-4B24-8F1B-2DCBE8EA0482}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{F65B5A14-4D7F-4B24-8F1B-2DCBE8EA0482}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{F65B5A14-4D7F-4B24-8F1B-2DCBE8EA0482}.Release|Any CPU.Build.0 = Release|Any CPU
-		{C7AC1DE1-77B8-4A27-BA4D-5D5FD3812A28}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{C7AC1DE1-77B8-4A27-BA4D-5D5FD3812A28}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{C7AC1DE1-77B8-4A27-BA4D-5D5FD3812A28}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{C7AC1DE1-77B8-4A27-BA4D-5D5FD3812A28}.Release|Any CPU.Build.0 = Release|Any CPU
+		{9242930B-A875-4677-8EA9-4B556B5AF3D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{9242930B-A875-4677-8EA9-4B556B5AF3D0}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{9242930B-A875-4677-8EA9-4B556B5AF3D0}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{9242930B-A875-4677-8EA9-4B556B5AF3D0}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE
diff --git a/binding/dotnet/PvRecorder/Content/pv_circle_512.png b/binding/dotnet/PvRecorder/Content/pv_circle_512.png
new file mode 100644
index 00000000..07f7a134
Binary files /dev/null and b/binding/dotnet/PvRecorder/Content/pv_circle_512.png differ
diff --git a/sdk/dotnet/PvRecorder/PvRecorder.cs b/binding/dotnet/PvRecorder/PvRecorder.cs
similarity index 55%
rename from sdk/dotnet/PvRecorder/PvRecorder.cs
rename to binding/dotnet/PvRecorder/PvRecorder.cs
index 98c16185..b8de1dd4 100644
--- a/sdk/dotnet/PvRecorder/PvRecorder.cs
+++ b/binding/dotnet/PvRecorder/PvRecorder.cs
@@ -1,5 +1,5 @@
 ﻿/*
-    Copyright 2021-2022 Picovoice Inc.
+    Copyright 2021-2023 Picovoice Inc.
 
     You may not use this file except in compliance with the license. A copy of the license is located in the "LICENSE"
     file accompanying this source.
@@ -8,9 +8,10 @@ file accompanying this source.
     an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
     specific language governing permissions and limitations under the License.
 */
+
 using System;
-using System.IO;
 using System.Diagnostics;
+using System.IO;
 using System.Reflection;
 using System.Runtime.InteropServices;
 
@@ -33,79 +34,96 @@ public enum PvRecorderStatus
     }
 
     /// <summary>
-    /// .NET sdk for capturing and reading audio frames.
+    /// PvRecorder is a cross-platform audio recorder library for .NET that is designed for real-time audio processing.
     /// </summary>
     public class PvRecorder : IDisposable
     {
         private const string LIBRARY = "libpv_recorder";
         private IntPtr _libraryPointer = IntPtr.Zero;
 
-        private int frameLength;
-
         static PvRecorder()
         {
-#if NETCOREAPP3_1_OR_GREATER
+
+#if NETCOREAPP3_0_OR_GREATER
+
             NativeLibrary.SetDllImportResolver(typeof(PvRecorder).Assembly, ImportResolver);
+
 #endif
+
         }
 
-#if NETCOREAPP3_1_OR_GREATER
+#if NETCOREAPP3_0_OR_GREATER
+
         private static IntPtr ImportResolver(string libraryName, Assembly assembly, DllImportSearchPath? searchPath)
         {
+
+#pragma warning disable IDE0058
+#pragma warning disable IDE0059
+
             IntPtr libHandle = IntPtr.Zero;
             NativeLibrary.TryLoad(GetLibraryPath(), out libHandle);
             return libHandle;
         }
+
+#pragma warning restore IDE0059
+#pragma warning restore IDE0058
+
 #endif
-        [DllImport(LIBRARY, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
-        private static extern PvRecorderStatus pv_recorder_init(int deviceIndex, int frameLength, int bufferSizeMSec, bool logOverflow, bool logSilence, out IntPtr handle);
 
-        [DllImport(LIBRARY, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
+        [DllImport(LIBRARY, CallingConvention = CallingConvention.Cdecl)]
+        private static extern PvRecorderStatus pv_recorder_init(int frameLength, int deviceIndex, int bufferedFramesCount, out IntPtr handle);
+
+        [DllImport(LIBRARY, CallingConvention = CallingConvention.Cdecl)]
         private static extern void pv_recorder_delete(IntPtr handle);
 
-        [DllImport(LIBRARY, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
+        [DllImport(LIBRARY, CallingConvention = CallingConvention.Cdecl)]
         private static extern PvRecorderStatus pv_recorder_start(IntPtr handle);
 
-        [DllImport(LIBRARY, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
+        [DllImport(LIBRARY, CallingConvention = CallingConvention.Cdecl)]
         private static extern PvRecorderStatus pv_recorder_stop(IntPtr handle);
 
-        [DllImport(LIBRARY, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
-        private static extern PvRecorderStatus pv_recorder_read(IntPtr handle, short[] pcm);
+        [DllImport(LIBRARY, CallingConvention = CallingConvention.Cdecl)]
+        private static extern PvRecorderStatus pv_recorder_read(IntPtr handle, short[] frame);
+
+        [DllImport(LIBRARY, CallingConvention = CallingConvention.Cdecl)]
+        private static extern IntPtr pv_recorder_set_debug_logging(IntPtr handle, bool isDebugLoggingEnabled);
 
-        [DllImport(LIBRARY, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
+        [DllImport(LIBRARY, CallingConvention = CallingConvention.Cdecl)]
+        private static extern char pv_recorder_get_is_recording(IntPtr handle);
+
+        [DllImport(LIBRARY, CallingConvention = CallingConvention.Cdecl)]
         private static extern IntPtr pv_recorder_get_selected_device(IntPtr handle);
 
-        [DllImport(LIBRARY, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
-        private static extern PvRecorderStatus pv_recorder_get_audio_devices(out int count, out IntPtr devices);
+        [DllImport(LIBRARY, CallingConvention = CallingConvention.Cdecl)]
+        private static extern PvRecorderStatus pv_recorder_get_available_devices(out int deviceListLength, out IntPtr deviceList);
+
+        [DllImport(LIBRARY, CallingConvention = CallingConvention.Cdecl)]
+        private static extern void pv_recorder_free_available_devices(int deviceListLength, IntPtr deviceList);
 
-        [DllImport(LIBRARY, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
-        private static extern void pv_recorder_free_device_list(int count, IntPtr devices);
+        [DllImport(LIBRARY, CallingConvention = CallingConvention.Cdecl)]
+        private static extern int pv_recorder_sample_rate();
 
-        [DllImport(LIBRARY, CallingConvention = CallingConvention.Cdecl, CharSet = CharSet.Ansi)]
+        [DllImport(LIBRARY, CallingConvention = CallingConvention.Cdecl)]
         private static extern IntPtr pv_recorder_version();
 
         /// <summary>
-        /// Factory method for PvRecorder library.
+        /// Factory method for creating instances of PvRecorder.
         /// </summary>
-        /// <param name="deviceIndex">
-        /// The index of the audio device to capture audio from. A value of (-1) will use the default audio.
-        /// </param>
         /// <param name="frameLength">
-        /// Length of the audio frames to receive at each read call.
+        /// Length of the audio frame to receive with each call to read.
         /// </param>
-        /// <param name="bufferSizeMSec">
-        /// Time in milliseconds for the buffer size to hold recorded audio.
-        /// </param>
-        /// <param name="logOverflow">
-        /// Boolean value representing if buffer overflow warnings should be logged.
+        /// <param name="deviceIndex">
+        /// The index of the audio device to capture audio from. A value of (-1) will use the default audio.
         /// </param>
-        /// <param name="logSilence">
-        /// Boolean variable to enable silence logs. This will log when continuous audio buffers are detected as silent.
+        /// <param name="bufferedFramesCount">
+        /// The number of audio frames buffered internally for reading - i.e. internal circular buffer
+        /// will be of size `frame_length` * `buffered_frames_count`. If this value is too low, buffer overflows could occur
+        /// and audio frames could be dropped. A higher value will increase memory usage.
         /// </param>
         /// <returns>An instance of PvRecorder.</returns>
-        public static PvRecorder Create(int deviceIndex, int frameLength, int bufferSizeMSec = 1000, bool logOverflow = true, bool logSilence = true)
+        public static PvRecorder Create(int frameLength, int deviceIndex = -1, int bufferedFramesCount = 50)
         {
-            return new PvRecorder(deviceIndex, frameLength, bufferSizeMSec, logOverflow, logSilence);
+            return new PvRecorder(frameLength, deviceIndex, bufferedFramesCount);
         }
 
         /// <summary>
@@ -117,30 +135,42 @@ public static PvRecorder Create(int deviceIndex, int frameLength, int bufferSize
         /// <param name="frameLength">
         /// Length of the audio frames to receive at each read call.
         /// </param>
-        /// <param name="bufferSizeMSec">
-        /// Time in milliseconds for the buffer size to hold recorded audio.
-        /// </param>
-        /// <param name="logOverflow">
-        /// Boolean value representing if buffer overflow warnings should be logged.
+        /// <param name="bufferedFramesCount">
+        /// The number of audio frames buffered internally for reading - i.e. internal circular buffer
+        /// will be of size `frame_length` * `buffered_frames_count`. If this value is too low, buffer overflows could occur
+        /// and audio frames could be dropped. A higher value will increase memory usage.
         /// </param>
-        /// <param name="logSilence">
-        /// Boolean variable to enable silence logs. This will log when continuous audio buffers are detected as silent.
-        /// </param>
-        private PvRecorder(int deviceIndex, int frameLength, int bufferSizeMSec, bool logOverflow, bool logSilence)
+        private PvRecorder(int frameLength, int deviceIndex, int bufferedFramesCount)
         {
-            PvRecorderStatus status = pv_recorder_init(deviceIndex, frameLength, bufferSizeMSec, logOverflow, logSilence, out _libraryPointer);
+            if (frameLength <= 0)
+            {
+                throw new PvRecorderInvalidArgumentException($"Frame length of {frameLength} is invalid - must be greater than 0.");
+            }
+
+            if (deviceIndex < -1)
+            {
+                throw new PvRecorderInvalidArgumentException($"Device index of {deviceIndex} is invalid - must be greater than -1.");
+            }
+
+            if (bufferedFramesCount <= 0)
+            {
+                throw new PvRecorderInvalidArgumentException($"Buffered frames count of {bufferedFramesCount} is invalid - must be greater than 0.");
+            }
+
+            PvRecorderStatus status = pv_recorder_init(frameLength, deviceIndex, bufferedFramesCount, out _libraryPointer);
             if (status != PvRecorderStatus.SUCCESS)
             {
                 throw PvRecorderStatusToException(status);
             }
 
-            this.frameLength = frameLength;
+            FrameLength = frameLength;
+            SampleRate = pv_recorder_sample_rate();
             SelectedDevice = Marshal.PtrToStringAnsi(pv_recorder_get_selected_device(_libraryPointer));
             Version = Marshal.PtrToStringAnsi(pv_recorder_version());
         }
 
         /// <summary>
-        /// Starts recording audio.
+        /// Starts recording audio. Should be called before making any calls to `Read()` or `Stop()`.
         /// </summary>
         public void Start()
         {
@@ -152,7 +182,7 @@ public void Start()
         }
 
         /// <summary>
-        /// Stops recording audio.
+        /// Stops recording audio. Should only be called after a successful call to `Start()`.
         /// </summary>
         public void Stop()
         {
@@ -164,22 +194,60 @@ public void Stop()
         }
 
         /// <summary>
-        /// Reads audio frames.
+        /// Synchronously reads a frame of audio samples. Call between `Start()` and `Stop()`.
         /// </summary>
-        /// <returns>An array of audio frames with length ${frameLength} provided in the factory method,</returns>
+        /// <returns>An array of audio samples with length of `frameLength` that was provided upon initialization.</returns>
         public short[] Read()
         {
-            short[] pcm = new short[frameLength];
-            PvRecorderStatus status = pv_recorder_read(_libraryPointer, pcm);
+            short[] frame = new short[FrameLength];
+            PvRecorderStatus status = pv_recorder_read(_libraryPointer, frame);
             if (status != PvRecorderStatus.SUCCESS)
             {
                 throw PvRecorderStatusToException(status);
             }
-            return pcm;
+            return frame;
+        }
+
+        /// <summary>
+        /// Enable or disable debug logging. Debug logs will indicate when there are overflows
+        /// in the internal frame buffer and when an audio source is generating frames of silence.
+        /// </summary>
+        /// <param name="isDebugLoggingEnabled">Boolean indicating whether the debug logging is enabled or disabled.</param>
+        public void SetDebugLogging(bool isDebugLoggingEnabled)
+        {
+            pv_recorder_set_debug_logging(_libraryPointer, isDebugLoggingEnabled);
+        }
+
+        /// <summary>
+        /// Gets the length of frame returned by the recorder.
+        /// </summary>
+        public int FrameLength
+        {
+            get; private set;
+        }
+
+        /// <summary>
+        /// Gets whether the recorder is currently capturing audio or not.
+        /// </summary>
+        public bool IsRecording
+        {
+            get
+            {
+                return pv_recorder_get_is_recording(_libraryPointer) != 0;
+            }
         }
 
         /// <summary>
-        /// Gets the current selected device.
+        /// Gets the recording sample rate.
+        /// </summary>
+        public int SampleRate
+        {
+            get; private set;
+        }
+
+
+        /// <summary>
+        /// Gets the current selected audio device.
         /// </summary>
         public string SelectedDevice
         {
@@ -195,59 +263,59 @@ public string Version
         }
 
         /// <summary>
-        /// Gets the available input devices of the current machine.
+        /// Gets a list of the available audio input devices on the current system.
         /// </summary>
-        /// <returns>A list of strings containing the names of the audio devices.</returns>
-        public static string[] GetAudioDevices()
+        /// <returns>An array of strings containing the names of the audio devices.</returns>
+        public static string[] GetAvailableDevices()
         {
-            int count;
-            IntPtr devices;
+            int deviceListLength;
+            IntPtr deviceList;
 
-            PvRecorderStatus status = pv_recorder_get_audio_devices(out count, out devices);
+            PvRecorderStatus status = pv_recorder_get_available_devices(out deviceListLength, out deviceList);
             if (status != PvRecorderStatus.SUCCESS)
             {
                 throw PvRecorderStatusToException(status);
             }
 
             int elementSize = Marshal.SizeOf(typeof(IntPtr));
-            string[] deviceNames = new string[count];
-            for (int i = 0; i < count; i++)
+            string[] deviceNames = new string[deviceListLength];
+            for (int i = 0; i < deviceListLength; i++)
             {
-                deviceNames[i] = Marshal.PtrToStringAnsi(Marshal.ReadIntPtr(devices, i * elementSize));
+                deviceNames[i] = Marshal.PtrToStringAnsi(Marshal.ReadIntPtr(deviceList, i * elementSize));
             }
 
-            pv_recorder_free_device_list(count, devices);
+            pv_recorder_free_available_devices(deviceListLength, deviceList);
 
             return deviceNames;
         }
 
         /// <summary>
-        /// Converts status codes to .NET exceptions.
+        /// Converts status codes to PvRecorderExceptions.
         /// </summary>
         /// <param name="status">Status code.</param>
-        /// <returns>.NET exception</returns>
-        private static Exception PvRecorderStatusToException(PvRecorderStatus status)
+        /// <returns>PvRecorderExceptions</returns>
+        private static PvRecorderException PvRecorderStatusToException(PvRecorderStatus status)
         {
             switch (status)
             {
                 case PvRecorderStatus.OUT_OF_MEMORY:
-                    return new OutOfMemoryException();
+                    return new PvRecorderMemoryException();
                 case PvRecorderStatus.INVALID_ARGUMENT:
-                    return new ArgumentException();
+                    return new PvRecorderInvalidArgumentException();
                 case PvRecorderStatus.INVALID_STATE:
-                    return new Exception("PvRecorder failed with invalid state.");
+                    return new PvRecorderInvalidStateException("PvRecorder failed with invalid state.");
                 case PvRecorderStatus.BACKEND_ERROR:
-                    return new Exception("PvRecorder backend error.");
+                    return new PvRecorderBackendException("PvRecorder audio backend error.");
                 case PvRecorderStatus.DEVICE_ALREADY_INITIALIZED:
-                    return new Exception("PvRecorder device already initialized.");
+                    return new PvRecorderDeviceAlreadyInitializedException("PvRecorder audio device already initialized.");
                 case PvRecorderStatus.DEVICE_NOT_INITIALIZED:
-                    return new Exception("PvRecorder device not initialized.");
+                    return new PvRecorderDeviceNotInitializedException("PvRecorder audio device not initialized.");
                 case PvRecorderStatus.IO_ERROR:
-                    return new IOException();
+                    return new PvRecorderIOException();
                 case PvRecorderStatus.RUNTIME_ERROR:
-                    return new SystemException("PvRecorder runtime error.");
+                    return new PvRecorderRuntimeException("PvRecorder runtime error.");
                 default:
-                    return new Exception("Unknown status returned from PvRecorder.");
+                    return new PvRecorderException("Unknown status returned from PvRecorder.");
             }
         }
 
@@ -279,7 +347,7 @@ private static string GetLibraryPath()
             process.Start();
             process.WaitForExit();
 
-            if (process.ExitCode != 0 )
+            if (process.ExitCode != 0)
             {
                 throw new SystemException("System is not supported.");
             }
@@ -325,4 +393,4 @@ public void Dispose()
             Dispose();
         }
     }
-}
+}
\ No newline at end of file
diff --git a/sdk/dotnet/PvRecorder/PvRecorder.csproj b/binding/dotnet/PvRecorder/PvRecorder.csproj
similarity index 55%
rename from sdk/dotnet/PvRecorder/PvRecorder.csproj
rename to binding/dotnet/PvRecorder/PvRecorder.csproj
index 2d550a4c..a34a4ba6 100644
--- a/sdk/dotnet/PvRecorder/PvRecorder.csproj
+++ b/binding/dotnet/PvRecorder/PvRecorder.csproj
@@ -1,8 +1,8 @@
-﻿    <Project Sdk="Microsoft.NET.Sdk">
+﻿<Project Sdk="Microsoft.NET.Sdk">
 
     <PropertyGroup>
-        <TargetFrameworks>net6.0;netcoreapp3.1;netstandard2.0</TargetFrameworks>
-        <Version>1.1.1</Version>
+        <TargetFrameworks>net6.0;netcoreapp3.0;netstandard2.0</TargetFrameworks>
+        <Version>1.2.1</Version>
         <Authors>Picovoice</Authors>
         <Company />
         <Product>PvRecorder</Product>
@@ -10,58 +10,79 @@
         <PackageProjectUrl>https://github.com/Picovoice/pvrecorder</PackageProjectUrl>
         <RepositoryUrl>https://github.com/Picovoice/pvrecorder.git</RepositoryUrl>
         <RepositoryType>git</RepositoryType>
-        <PackageTags>audio, audio recorder</PackageTags>
+        <PackageTags>audio, audio recorder, audio capture, audio recording, audio processing, real-time processing</PackageTags>
         <PackageReleaseNotes>See https://github.com/Picovoice/pvrecorder/ </PackageReleaseNotes>
-        <Copyright>Picovoice 2021-2022</Copyright>
-        <Description>
-            PvRecorder is a library for .NET to record and capture audio frames.
-        </Description>
+        <Copyright>Picovoice 2021-2023</Copyright>
+        <Description>PvRecorder is a cross-platform audio recorder for .NET designed for real-time processing.</Description>
         <PackageRequireLicenseAcceptance>true</PackageRequireLicenseAcceptance>
+        <PackageIcon>pv_circle_512.png</PackageIcon>
     </PropertyGroup>
 
+    <!--Target files-->
     <ItemGroup>
+        <Content Include="PvRecorder.netstandard2.0.targets">
+            <PackagePath>
+                build/netstandard2.0/PvRecorder.targets;
+            </PackagePath>
+            <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+        </Content>
         <Content Include="PvRecorder.targets">
             <PackagePath>
-                build/netstandard2.0;
-                build/netcoreapp3.1;
-                build/net6.0;
+                build/netcoreapp3.0/PvRecorder.targets;
+                build/net6.0/PvRecorder.targets;
             </PackagePath>
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
         </Content>
+    </ItemGroup>
+
+    <!--.NET Standard 2.0+ libs-->
+    <ItemGroup>
         <Content Include="..\..\..\lib\windows\amd64\libpv_recorder.dll">
             <PackagePath>
-                build/netstandard2.0/lib/windows/amd64/libpv_recorder.dll;
-                build/netcoreapp3.1/lib/windows/amd64/libpv_recorder.dll;
+                build/netstandard2.0/libpv_recorder.dll;
+                build/netcoreapp3.0/lib/windows/amd64/libpv_recorder.dll;
                 build/net6.0/lib/windows/amd64/libpv_recorder.dll;
             </PackagePath>
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
             <Link>lib\windows\amd64\libpv_recorder.dll</Link>
             <Visible>false</Visible>
         </Content>
-        <Content Include="..\..\..\lib\mac\**\*">
+        <Content Include="..\..\..\lib\linux\x86_64\libpv_recorder.so">
             <PackagePath>
-                build/netstandard2.0/lib/mac;
-                build/netcoreapp3.1/lib/mac;
-                build/net6.0/lib/mac;
+                build/netstandard2.0/libpv_recorder.so;
+                build/netcoreapp3.0/lib/linux/x86_64/libpv_recorder.so;
+                build/net6.0/lib/linux/x86_64/libpv_recorder.so;
             </PackagePath>
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-            <Link>lib\mac\%(RecursiveDir)%(Filename)%(Extension)</Link>
+            <Link>lib\linux\x86_64\libpv_recorder.so</Link>
             <Visible>false</Visible>
         </Content>
-        <Content Include="..\..\..\lib\linux\x86_64\libpv_recorder.so">
+        <Content Include="..\..\..\lib\mac\x86_64\libpv_recorder.dylib">
             <PackagePath>
-                build/netstandard2.0/lib/linux/x86_64/libpv_recorder.so;
-                build/netcoreapp3.1/lib/linux/x86_64/libpv_recorder.so;
-                build/net6.0/lib/linux/x86_64/libpv_recorder.so;
+                build/netstandard2.0/libpv_recorder.dylib;
+                build/netcoreapp3.0/lib/mac/x86_64/libpv_recorder.dylib;
+                build/net6.0/lib/mac/x86_64/libpv_recorder.dylib;
             </PackagePath>
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-            <Link>lib\linux\x86_64\libpv_recorder.so</Link>
+            <Link>lib\mac\x86_64\libpv_recorder.dylib</Link>
+            <Visible>false</Visible>
+        </Content>
+    </ItemGroup>
+
+    <!--.NET Core 3.0+ libs-->
+    <ItemGroup>
+        <Content Include="..\..\..\lib\beaglebone\libpv_recorder.so">
+            <PackagePath>
+                build/netcoreapp3.0/lib/beaglebone/libpv_recorder.so;
+                build/net6.0/lib/beaglebone/libpv_recorder.so;
+            </PackagePath>
+            <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+            <Link>lib\beaglebone\libpv_recorder.so</Link>
             <Visible>false</Visible>
         </Content>
-        <Content Include="..\..\..\lib\raspberry-pi\**\*" Exclude="..\..\..\lib\raspberry-pi\arm11\*;">
+        <Content Include="..\..\..\lib\raspberry-pi\**\*" Exclude="..\..\..\lib\raspberry-pi\arm11\*">
             <PackagePath>
-                build/netstandard2.0/lib/raspberry-pi;
-                build/netcoreapp3.1/lib/raspberry-pi;
+                build/netcoreapp3.0/lib/raspberry-pi;
                 build/net6.0/lib/raspberry-pi;
             </PackagePath>
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
@@ -70,28 +91,33 @@
         </Content>
         <Content Include="..\..\..\lib\jetson\cortex-a57-aarch64\libpv_recorder.so">
             <PackagePath>
-                build/netstandard2.0/lib/jetson/cortex-a57-aarch64/libpv_recorder.so;
-                build/netcoreapp3.1/lib/jetson/cortex-a57-aarch64/libpv_recorder.so;
+                build/netcoreapp3.0/lib/jetson/cortex-a57-aarch64/libpv_recorder.so;
                 build/net6.0/lib/jetson/cortex-a57-aarch64/libpv_recorder.so;
             </PackagePath>
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
             <Link>lib\jetson\cortex-a57-aarch64\libpv_recorder.so</Link>
             <Visible>false</Visible>
         </Content>
-        <Content Include="..\..\..\lib\beaglebone\libpv_recorder.so">
+    </ItemGroup>
+
+    <!--.NET 6.0+ libs-->
+    <ItemGroup>
+        <Content Include="..\..\..\lib\mac\arm64\libpv_recorder.dylib">
             <PackagePath>
-                build/netstandard2.0/lib/beaglebone/libpv_recorder.so;
-                build/netcoreapp3.1/lib/beaglebone/libpv_recorder.so;
-                build/net6.0/lib/beaglebone/libpv_recorder.so;
+                build/net6.0/lib/mac/arm64/libpv_recorder.dylib;
             </PackagePath>
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-            <Link>lib\beaglebone\libpv_recorder.so</Link>
+            <Link>lib\mac\arm64\libpv_recorder.dylib</Link>
             <Visible>false</Visible>
         </Content>
-        <Content Include="..\..\..\scripts\*">
+    </ItemGroup>
+
+    <!--Resource files-->
+    <ItemGroup>
+        <Content Include="..\..\..\resources\scripts\*">
             <PackagePath>
                 build/netstandard2.0/scripts;
-                build/netcoreapp3.1/scripts;
+                build/netcoreapp3.0/scripts;
                 build/net6.0/scripts;
             </PackagePath>
             <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
@@ -99,5 +125,13 @@
             <Visible>False</Visible>
         </Content>
     </ItemGroup>
-
-    </Project>
+    <ItemGroup>
+        <Folder Include="Content\" />
+    </ItemGroup>
+    <ItemGroup>
+        <None Include="Content\pv_circle_512.png">
+            <Pack>True</Pack>
+            <PackagePath></PackagePath>
+        </None>
+    </ItemGroup>
+</Project>
diff --git a/binding/dotnet/PvRecorder/PvRecorder.netstandard2.0.targets b/binding/dotnet/PvRecorder/PvRecorder.netstandard2.0.targets
new file mode 100644
index 00000000..18788cc2
--- /dev/null
+++ b/binding/dotnet/PvRecorder/PvRecorder.netstandard2.0.targets
@@ -0,0 +1,26 @@
+﻿<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+	<ItemGroup>
+		<Content Include="$(MSBuildThisFileDirectory)libpv_recorder.dll">
+			<Link>libpv_recorder.dll</Link>
+			<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+			<Visible>false</Visible>
+		</Content>
+		<Content Include="$(MSBuildThisFileDirectory)libpv_recorder.dylib">
+			<Link>libpv_recorder.dylib</Link>
+			<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+			<Visible>false</Visible>
+		</Content>
+		<Content Include="$(MSBuildThisFileDirectory)libpv_recorder.so">
+			<Link>libpv_recorder.so</Link>
+			<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+			<Visible>false</Visible>
+		</Content>
+    </ItemGroup>
+    <ItemGroup>
+		<Content Include="$(MSBuildThisFileDirectory)scripts/**">
+			<Link>scripts/%(Filename)%(Extension)</Link>
+			<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+			<Visible>false</Visible>
+		</Content>
+	</ItemGroup>
+</Project>
diff --git a/binding/dotnet/PvRecorder/PvRecorder.targets b/binding/dotnet/PvRecorder/PvRecorder.targets
new file mode 100644
index 00000000..5b6fb27a
--- /dev/null
+++ b/binding/dotnet/PvRecorder/PvRecorder.targets
@@ -0,0 +1,14 @@
+﻿<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+	<ItemGroup>
+		<Content Include="$(MSBuildThisFileDirectory)lib/**">
+			<Link>lib/%(RecursiveDir)%(Filename)%(Extension)</Link>
+			<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+			<Visible>false</Visible>
+		</Content>
+        <Content Include="$(MSBuildThisFileDirectory)scripts/**">
+            <Link>scripts/%(Filename)%(Extension)</Link>
+            <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+            <Visible>false</Visible>
+        </Content>
+	</ItemGroup>
+</Project>
diff --git a/binding/dotnet/PvRecorder/PvRecorderException.cs b/binding/dotnet/PvRecorder/PvRecorderException.cs
new file mode 100644
index 00000000..b70b3304
--- /dev/null
+++ b/binding/dotnet/PvRecorder/PvRecorderException.cs
@@ -0,0 +1,80 @@
+﻿/*
+    Copyright 2023 Picovoice Inc.
+
+    You may not use this file except in compliance with the license. A copy of the license is located in the "LICENSE"
+    file accompanying this source.
+
+    Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+    an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+    specific language governing permissions and limitations under the License.
+*/
+
+using System;
+
+namespace Pv
+{
+    public class PvRecorderException : Exception
+    {
+        public PvRecorderException() { }
+
+        public PvRecorderException(string message) : base(message) { }
+
+    }
+
+    public class PvRecorderMemoryException : PvRecorderException
+    {
+        public PvRecorderMemoryException() { }
+
+        public PvRecorderMemoryException(string message) : base(message) { }
+    }
+
+    public class PvRecorderInvalidArgumentException : PvRecorderException
+    {
+        public PvRecorderInvalidArgumentException() { }
+
+        public PvRecorderInvalidArgumentException(string message) : base(message) { }
+    }
+
+    public class PvRecorderInvalidStateException : PvRecorderException
+    {
+        public PvRecorderInvalidStateException() { }
+
+        public PvRecorderInvalidStateException(string message) : base(message) { }
+    }
+
+    public class PvRecorderBackendException : PvRecorderException
+    {
+        public PvRecorderBackendException() { }
+
+        public PvRecorderBackendException(string message) : base(message) { }
+    }
+
+    public class PvRecorderDeviceAlreadyInitializedException : PvRecorderException
+    {
+        public PvRecorderDeviceAlreadyInitializedException() { }
+
+        public PvRecorderDeviceAlreadyInitializedException(string message) : base(message) { }
+    }
+
+    public class PvRecorderDeviceNotInitializedException : PvRecorderException
+    {
+        public PvRecorderDeviceNotInitializedException() { }
+
+        public PvRecorderDeviceNotInitializedException(string message) : base(message) { }
+    }
+
+
+    public class PvRecorderIOException : PvRecorderException
+    {
+        public PvRecorderIOException() { }
+
+        public PvRecorderIOException(string message) : base(message) { }
+    }
+
+    public class PvRecorderRuntimeException : PvRecorderException
+    {
+        public PvRecorderRuntimeException() { }
+
+        public PvRecorderRuntimeException(string message) : base(message) { }
+    }
+}
\ No newline at end of file
diff --git a/binding/dotnet/PvRecorderTest/MainTest.cs b/binding/dotnet/PvRecorderTest/MainTest.cs
new file mode 100644
index 00000000..243449ab
--- /dev/null
+++ b/binding/dotnet/PvRecorderTest/MainTest.cs
@@ -0,0 +1,72 @@
+/*
+    Copyright 2023 Picovoice Inc.
+
+    You may not use this file except in compliance with the license. A copy of the license is located in the "LICENSE"
+    file accompanying this source.
+
+    Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+    an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+    specific language governing permissions and limitations under the License.
+*/
+
+using System;
+
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+using Pv;
+
+namespace PvRecorderTest
+{
+    [TestClass]
+    public class MainTest
+    {
+        private static readonly int FRAME_LENGTH = 512;
+
+        [TestMethod]
+        public void TestInit()
+        {
+            PvRecorder recorder = PvRecorder.Create(FRAME_LENGTH, deviceIndex: 0, bufferedFramesCount: 60);
+            Assert.IsNotNull(recorder);
+            Assert.IsTrue(recorder.SampleRate > 0);
+            Assert.IsFalse(string.IsNullOrEmpty(recorder.SelectedDevice));
+            Assert.IsFalse(string.IsNullOrEmpty(recorder.Version));
+            recorder.Dispose();
+        }
+
+        [TestMethod]
+        public void TestStartStop()
+        {
+            using (PvRecorder recorder = PvRecorder.Create(FRAME_LENGTH, deviceIndex: 0))
+            {
+                recorder.SetDebugLogging(true);
+
+                Assert.IsFalse(recorder.IsRecording);
+                recorder.Start();
+                Assert.IsTrue(recorder.IsRecording);
+
+                short[] frame = recorder.Read();
+                Assert.IsNotNull(frame);
+                Assert.AreEqual(FRAME_LENGTH, frame.Length);
+
+                recorder.Stop();
+                Assert.IsFalse(recorder.IsRecording);
+            }
+        }
+
+        [TestMethod]
+        public void TestGetAudioDevices()
+        {
+            string[] devices = PvRecorder.GetAvailableDevices();
+
+            Assert.IsNotNull(devices);
+            Assert.IsTrue(devices.Length >= 0);
+            if (devices.Length > 0)
+            {
+                for (int i = 0; i < devices.Length; i++)
+                {
+                    Assert.IsNotNull(devices[i]);
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/binding/dotnet/PvRecorderTest/PvRecorderTest.csproj b/binding/dotnet/PvRecorderTest/PvRecorderTest.csproj
new file mode 100644
index 00000000..fb556677
--- /dev/null
+++ b/binding/dotnet/PvRecorderTest/PvRecorderTest.csproj
@@ -0,0 +1,34 @@
+<Project Sdk="Microsoft.NET.Sdk">
+    <PropertyGroup>
+        <TargetFrameworks>net6.0;net5.0;netcoreapp3.1;netcoreapp3.0;netcoreapp2.0;</TargetFrameworks>
+        <IsPackable>false</IsPackable>
+    </PropertyGroup>
+
+    <ItemGroup>
+        <PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.1.0" />
+        <PackageReference Include="MSTest.TestAdapter" Version="2.2.8" />
+        <PackageReference Include="MSTest.TestFramework" Version="2.2.8" />
+        <PackageReference Include="coverlet.collector" Version="3.1.2" />
+    </ItemGroup>
+    <ItemGroup>
+        <ProjectReference Include="..\PvRecorder\PvRecorder.csproj" />
+    </ItemGroup>
+
+    <ItemGroup>
+        <Content Include="..\..\..\lib\windows\amd64\libpv_recorder.dll">
+            <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+            <Link>libpv_recorder.dll</Link>
+            <Visible>false</Visible>
+        </Content>
+        <Content Include="..\..\..\lib\linux\x86_64\libpv_recorder.so">
+            <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+            <Link>libpv_recorder.so</Link>
+            <Visible>false</Visible>
+        </Content>
+        <Content Include="..\..\..\lib\mac\x86_64\libpv_recorder.dylib">
+            <CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
+            <Link>libpv_recorder.dylib</Link>
+            <Visible>false</Visible>
+        </Content>
+    </ItemGroup>
+</Project>
diff --git a/binding/dotnet/README.md b/binding/dotnet/README.md
new file mode 100644
index 00000000..2f9e9fdb
--- /dev/null
+++ b/binding/dotnet/README.md
@@ -0,0 +1,104 @@
+# PvRecorder Binding for .NET
+
+# PvRecorder
+
+PvRecorder is an easy-to-use cross-platform audio recorder designed for real-time audio processing.
+It allows developers access to an audio device's input stream, broken up into data frames of a given size.
+
+## Requirements
+
+- .NET 6.0
+
+## Compatibility
+
+Platform compatible with .NET Framework 4.6.1+:
+
+- Windows (x86_64)
+
+Platforms compatible with .NET Core 2.0+:
+
+- Linux (x86_64)
+- macOS (x86_64)
+- Windows (x86_64)
+
+Platforms compatible with .NET Core 3.0+:
+
+- Raspberry Pi:
+  - 2
+  - 3 (32 and 64 bit)
+  - 4 (32 and 64 bit)
+- NVIDIA Jetson Nano
+- BeagleBone
+
+Platform compatible with .NET 6.0+:
+
+- macOS (arm64)
+
+## Installation
+
+You can install the latest version of PvRecorder by adding the latest [PvRecorder Nuget package](https://www.nuget.org/packages/PvRecorder/)
+in Visual Studio or using by using the .NET CLI:
+
+```console
+dotnet add package PvRecorder
+```
+
+## Usage
+
+Initialize and begin recording:
+
+```csharp
+using Pv;
+
+PvRecorder recorder = PvRecorder.Create(frameLength: 512);
+recorder.Start();
+```
+
+Read a frame of audio:
+
+```csharp
+while (true) {
+    short[] frame = recorder.Read();
+    // do something with audio frame
+}
+```
+
+To stop recording:
+
+```csharp
+recorder.Stop();
+```
+
+Once you are done, free the used resources. You do not have to call `Stop()` before `Dispose()`:
+
+```csharp
+recorder.Dispose();
+```
+ 
+To have resources freed immediately after use without explicitly calling the `Dispose()` function, wrap `PvRecorder` in a `using` statement:
+
+```csharp
+using (PvRecorder recorder = PvRecorder.Create(frameLength: 512)) {
+    // PvRecorder usage
+}
+```
+
+### Selecting an Audio Device
+
+To print a list of available audio devices:
+```csharp
+string[] devices = PvRecorder.GetAudioDevices();
+```
+
+The index of the device in the returned list can be used in `Create()` to select that device for audio capture:
+```csharp
+PvRecorder recorder = PvRecorder.Create(
+    frameLength: 512,
+    deviceIndex: 2);
+```
+
+
+## Demo
+
+The [PvRecorder .NET demo](https://github.com/Picovoice/pvrecorder/tree/main/demo/dotnet) is a .NET command-line application that demonstrates how to
+use PvRecorder to record audio to a file.
diff --git a/binding/go/README.md b/binding/go/README.md
new file mode 100644
index 00000000..7c84ecc9
--- /dev/null
+++ b/binding/go/README.md
@@ -0,0 +1,81 @@
+# PvRecorder Binding for Go
+
+## Compatibility
+
+- Go 1.16+
+- Runs on Linux (x86_64), macOS (x86_64 and arm64), Windows (x86_64), Raspberry Pi (all variants), NVIDIA Jetson (Nano), and BeagleBone.
+
+## Installation
+
+```console
+go get github.com/Picovoice/pvrecorder/binding/go
+```
+
+## Usage
+
+To start recording initialize an instance and run start:
+
+```go
+import . "github.com/Picovoice/pvrecorder/binding/go"
+
+recorder = NewPvRecorder(/*FrameLength*/512)
+recorder.Init()
+if err != nil {
+    // handle init error
+}
+defer recorder.Delete()
+
+err = recorder.Start()
+if err != nil {
+    // handle start error
+}
+```
+
+(or)
+
+Use `GetAvailableDevices()` to get a list of available devices and then initialize the instance based on the index of a device:
+
+```go
+import . "github.com/Picovoice/pvrecorder/binding/go"
+
+devices = GetAvailableDevices() // select index of device
+
+recorder = NewPvRecorder(/*FrameLength*/512)
+recorder.DeviceIndex = 0
+recorder.Init()
+if err != nil {
+    // handle init error
+}
+defer recorder.Delete()
+
+err = recorder.Start()
+if err != nil {
+    // handle start error
+}
+```
+
+Get a frame of audio by calling the `Read()` function:
+
+```go
+frame, err := recorder.Read()
+if err != nil {
+    // handle error
+}
+// do something with frame
+```
+
+To stop recording, run stop on the instance:
+
+```go
+recorder.Stop()
+```
+
+Once you are done, free the used resources. You do not have to call stop before delete:
+
+```go
+recorder.Delete()
+```
+
+### Demo
+
+For more detailed information on how to use the PvRecorder Go binding, please that a look at [demo/demo.go](../../demo/go/demo.go). 
diff --git a/sdk/go/copy.sh b/binding/go/copy.sh
similarity index 94%
rename from sdk/go/copy.sh
rename to binding/go/copy.sh
index 14516ef6..8cea6afa 100755
--- a/sdk/go/copy.sh
+++ b/binding/go/copy.sh
@@ -1,7 +1,7 @@
 #!/bin/bash
 
 echo "Copying scripts..."
-cp -r ../../scripts/* ./embedded/scripts
+cp -r ../../resources/scripts/* ./embedded/scripts
 
 echo "Copying Linux lib..."
 cp ../../lib/linux/x86_64/libpv_recorder.so ./embedded/lib/linux/x86_64/libpv_recorder.so
diff --git a/binding/go/embedded/lib/beaglebone/libpv_recorder.so b/binding/go/embedded/lib/beaglebone/libpv_recorder.so
new file mode 100755
index 00000000..6461a713
Binary files /dev/null and b/binding/go/embedded/lib/beaglebone/libpv_recorder.so differ
diff --git a/binding/go/embedded/lib/jetson/cortex-a57-aarch64/libpv_recorder.so b/binding/go/embedded/lib/jetson/cortex-a57-aarch64/libpv_recorder.so
new file mode 100755
index 00000000..f15b5cf2
Binary files /dev/null and b/binding/go/embedded/lib/jetson/cortex-a57-aarch64/libpv_recorder.so differ
diff --git a/binding/go/embedded/lib/linux/x86_64/libpv_recorder.so b/binding/go/embedded/lib/linux/x86_64/libpv_recorder.so
new file mode 100755
index 00000000..2e1345ea
Binary files /dev/null and b/binding/go/embedded/lib/linux/x86_64/libpv_recorder.so differ
diff --git a/binding/go/embedded/lib/mac/arm64/libpv_recorder.dylib b/binding/go/embedded/lib/mac/arm64/libpv_recorder.dylib
new file mode 100755
index 00000000..287b497b
Binary files /dev/null and b/binding/go/embedded/lib/mac/arm64/libpv_recorder.dylib differ
diff --git a/binding/go/embedded/lib/mac/x86_64/libpv_recorder.dylib b/binding/go/embedded/lib/mac/x86_64/libpv_recorder.dylib
new file mode 100755
index 00000000..21722af6
Binary files /dev/null and b/binding/go/embedded/lib/mac/x86_64/libpv_recorder.dylib differ
diff --git a/lib/node/raspberry-pi/arm11/pv_recorder.node b/binding/go/embedded/lib/raspberry-pi/arm11/libpv_recorder.so
old mode 100644
new mode 100755
similarity index 63%
rename from lib/node/raspberry-pi/arm11/pv_recorder.node
rename to binding/go/embedded/lib/raspberry-pi/arm11/libpv_recorder.so
index bf096d4d..67da4f02
Binary files a/lib/node/raspberry-pi/arm11/pv_recorder.node and b/binding/go/embedded/lib/raspberry-pi/arm11/libpv_recorder.so differ
diff --git a/binding/go/embedded/lib/raspberry-pi/cortex-a53-aarch64/libpv_recorder.so b/binding/go/embedded/lib/raspberry-pi/cortex-a53-aarch64/libpv_recorder.so
new file mode 100755
index 00000000..7e04a378
Binary files /dev/null and b/binding/go/embedded/lib/raspberry-pi/cortex-a53-aarch64/libpv_recorder.so differ
diff --git a/binding/go/embedded/lib/raspberry-pi/cortex-a53/libpv_recorder.so b/binding/go/embedded/lib/raspberry-pi/cortex-a53/libpv_recorder.so
new file mode 100755
index 00000000..5ef24d62
Binary files /dev/null and b/binding/go/embedded/lib/raspberry-pi/cortex-a53/libpv_recorder.so differ
diff --git a/binding/go/embedded/lib/raspberry-pi/cortex-a7/libpv_recorder.so b/binding/go/embedded/lib/raspberry-pi/cortex-a7/libpv_recorder.so
new file mode 100755
index 00000000..9d3aabf3
Binary files /dev/null and b/binding/go/embedded/lib/raspberry-pi/cortex-a7/libpv_recorder.so differ
diff --git a/binding/go/embedded/lib/raspberry-pi/cortex-a72-aarch64/libpv_recorder.so b/binding/go/embedded/lib/raspberry-pi/cortex-a72-aarch64/libpv_recorder.so
new file mode 100755
index 00000000..4aa0e286
Binary files /dev/null and b/binding/go/embedded/lib/raspberry-pi/cortex-a72-aarch64/libpv_recorder.so differ
diff --git a/binding/go/embedded/lib/raspberry-pi/cortex-a72/libpv_recorder.so b/binding/go/embedded/lib/raspberry-pi/cortex-a72/libpv_recorder.so
new file mode 100755
index 00000000..0cc68ce7
Binary files /dev/null and b/binding/go/embedded/lib/raspberry-pi/cortex-a72/libpv_recorder.so differ
diff --git a/binding/go/embedded/lib/windows/amd64/libpv_recorder.dll b/binding/go/embedded/lib/windows/amd64/libpv_recorder.dll
new file mode 100644
index 00000000..99559076
Binary files /dev/null and b/binding/go/embedded/lib/windows/amd64/libpv_recorder.dll differ
diff --git a/scripts/platform.bat b/binding/go/embedded/scripts/platform.bat
similarity index 100%
rename from scripts/platform.bat
rename to binding/go/embedded/scripts/platform.bat
diff --git a/scripts/platform.sh b/binding/go/embedded/scripts/platform.sh
similarity index 99%
rename from scripts/platform.sh
rename to binding/go/embedded/scripts/platform.sh
index e8d4a52c..b4a050dc 100755
--- a/scripts/platform.sh
+++ b/binding/go/embedded/scripts/platform.sh
@@ -41,4 +41,4 @@ case $kernel in
 esac
 
 echo -n "$kernel $arch"
-exit 0
+exit 0
\ No newline at end of file
diff --git a/binding/go/go.mod b/binding/go/go.mod
new file mode 100644
index 00000000..a90fca4e
--- /dev/null
+++ b/binding/go/go.mod
@@ -0,0 +1,3 @@
+module github.com/Picovoice/pvrecorder/binding/go
+
+go 1.16
diff --git a/sdk/go/go.sum b/binding/go/go.sum
similarity index 100%
rename from sdk/go/go.sum
rename to binding/go/go.sum
diff --git a/sdk/go/pvrecorder.go b/binding/go/pv_recorder.go
similarity index 69%
rename from sdk/go/pvrecorder.go
rename to binding/go/pv_recorder.go
index a2d47e21..0e017820 100644
--- a/sdk/go/pvrecorder.go
+++ b/binding/go/pv_recorder.go
@@ -1,4 +1,4 @@
-// Copyright 2021-2022 Picovoice Inc.
+// Copyright 2021-2023 Picovoice Inc.
 //
 // You may not use this file except in compliance with the license. A copy of the license is
 // located in the "LICENSE" file accompanying this source.
@@ -83,23 +83,18 @@ func pvRecorderStatusToString(status PvRecorderStatus) string {
 
 // PvRecorder struct
 type PvRecorder struct {
-	// handle for pvrecorder instance in C.
+	// handle for pvRecorder instance in C.
 	handle uintptr
 
-	// Index of audio device to start recording and capture audio.
-	DeviceIndex int
-
-	// FrameLength to get for each read command.
+	// Length of each frame returned from read command.
 	FrameLength int
 
-	// BufferSizeMSec is the total amount of audio frames to store in milliseconds.
-	BufferSizeMSec int
-
-	// LogOverflow flag to enable logs when a buffer overflow occurs.
-	LogOverflow int
+	// Index of audio device to start recording and capture audio.
+	DeviceIndex int
 
-	// LogSilence flag to enable logs when continuous audio buffers are detected as silent.
-	LogSilence int
+	// BufferedFramesCount is the number of audio frames buffered internally for reading - i.e. internal circular buffer
+	// will be of size `frame_length` * `buffered_frames_count`. If this value is too low, buffer overflows could occur.
+	BufferedFramesCount int
 }
 
 type nativePvRecorderInterface interface {
@@ -107,10 +102,13 @@ type nativePvRecorderInterface interface {
 	nativeDelete(*PvRecorder)
 	nativeStart(*PvRecorder)
 	nativeStop(*PvRecorder)
+	nativeRead(*PvRecorder)
+	nativeSetDebugLogging(*PvRecorder)
 	nativeGetSelectedDevice(*PvRecorder)
 	nativeGetAudioDevices(*C.int32_t, ***C.char)
 	nativeFreeDeviceList(C.int32_t, **C.char)
 	nativeVersion()
+	nativeSampleRate()
 }
 
 type nativePvRecorderType struct{}
@@ -121,24 +119,44 @@ var (
 	nativePvRecorder = nativePvRecorderType{}
 )
 
+var (
+	// SampleRate Audio sample rate used by PvRecorder.
+	SampleRate int
+
+	// Version PvRecorder version
+	Version string
+)
+
+// NewPvRecorder returns a PvRecorder struct with default parameters
+func NewPvRecorder(frameLength int) PvRecorder {
+	return PvRecorder{
+		FrameLength:         frameLength,
+		DeviceIndex:         -1,
+		BufferedFramesCount: 50,
+	}
+}
+
 // Init function for PvRecorder
-func (pvrecorder *PvRecorder) Init() error {
-	ret := nativePvRecorder.nativeInit(pvrecorder)
+func (pvRecorder *PvRecorder) Init() error {
+	ret := nativePvRecorder.nativeInit(pvRecorder)
 	if ret != SUCCESS {
 		return fmt.Errorf("PvRecorder Init failed with: %s", pvRecorderStatusToString(ret))
 	}
 
+	SampleRate = nativePvRecorder.nativeSampleRate()
+	Version = nativePvRecorder.nativeVersion()
+
 	return nil
 }
 
 // Delete function releases resources acquired by PvRecorder
-func (pvrecorder *PvRecorder) Delete() {
-	nativePvRecorder.nativeDelete(pvrecorder)
+func (pvRecorder *PvRecorder) Delete() {
+	nativePvRecorder.nativeDelete(pvRecorder)
 }
 
 // Start function starts recording audio.
-func (pvrecorder *PvRecorder) Start() error {
-	ret := nativePvRecorder.nativeStart(pvrecorder)
+func (pvRecorder *PvRecorder) Start() error {
+	ret := nativePvRecorder.nativeStart(pvRecorder)
 	if ret != SUCCESS {
 		return fmt.Errorf("PvRecorder Start failed with: %s", pvRecorderStatusToString(ret))
 	}
@@ -147,8 +165,8 @@ func (pvrecorder *PvRecorder) Start() error {
 }
 
 // Stop function stops recording audio.
-func (pvrecorder *PvRecorder) Stop() error {
-	ret := nativePvRecorder.nativeStop(pvrecorder)
+func (pvRecorder *PvRecorder) Stop() error {
+	ret := nativePvRecorder.nativeStop(pvRecorder)
 	if ret != SUCCESS {
 		return fmt.Errorf("PvRecorder Stop failed with: %s", pvRecorderStatusToString(ret))
 	}
@@ -157,17 +175,17 @@ func (pvrecorder *PvRecorder) Stop() error {
 }
 
 // Read function reads audio frames.
-func (pvrecorder *PvRecorder) Read() ([]int16, error) {
-	pcm := C.malloc_cgo(C.int32_t(pvrecorder.FrameLength))
+func (pvRecorder *PvRecorder) Read() ([]int16, error) {
+	pcm := C.malloc_cgo(C.int32_t(pvRecorder.FrameLength))
 	defer C.free(unsafe.Pointer(pcm))
 
-	ret := nativePvRecorder.nativeRead(pvrecorder, pcm)
+	ret := nativePvRecorder.nativeRead(pvRecorder, pcm)
 	if ret != SUCCESS {
 		return nil, fmt.Errorf("PvRecorder Read failed with: %s", pvRecorderStatusToString(ret))
 	}
 
-	pcmCSlice := (*[1 << 28]C.int16_t)(unsafe.Pointer(pcm))[:pvrecorder.FrameLength:pvrecorder.FrameLength]
-	pcmSlice := make([]int16, pvrecorder.FrameLength)
+	pcmCSlice := (*[1 << 28]C.int16_t)(unsafe.Pointer(pcm))[:pvRecorder.FrameLength:pvRecorder.FrameLength]
+	pcmSlice := make([]int16, pvRecorder.FrameLength)
 	for i := range pcmSlice {
 		pcmSlice[i] = int16(pcmCSlice[i])
 	}
@@ -175,13 +193,24 @@ func (pvrecorder *PvRecorder) Read() ([]int16, error) {
 	return pcmSlice, nil
 }
 
+// SetDebugLogging enables or disables debug logging for PvRecorder. Debug logs will indicate when there are
+// overflows in the internal  frame buffer and when an audio source is generating frames of silence.
+func (pvRecorder *PvRecorder) SetDebugLogging(isDebugLoggingEnabled bool) {
+	nativePvRecorder.nativeSetDebugLogging(pvRecorder, isDebugLoggingEnabled)
+}
+
+// IsRecording gets whether the given instance is currently recording audio or not.
+func (pvRecorder *PvRecorder) IsRecording() bool {
+	return nativePvRecorder.nativeGetIsRecording(pvRecorder)
+}
+
 // GetSelectedDevice gets the current selected audio input device name
-func (pvrecorder *PvRecorder) GetSelectedDevice() string {
-	return nativePvRecorder.nativeGetSelectedDevice(pvrecorder)
+func (pvRecorder *PvRecorder) GetSelectedDevice() string {
+	return nativePvRecorder.nativeGetSelectedDevice(pvRecorder)
 }
 
-// GetAudioDevices function gets the currently available input audio devices.
-func GetAudioDevices() ([]string, error) {
+// GetAvailableDevices function gets the currently available input audio devices.
+func GetAvailableDevices() ([]string, error) {
 	var count int
 	var devices **C.char
 
@@ -200,13 +229,8 @@ func GetAudioDevices() ([]string, error) {
 	return deviceNames, nil
 }
 
-// Version function gets the current library version.
-func Version() string {
-	return nativePvRecorder.nativeVersion()
-}
-
 func extractLib() string {
-	extractionDir := filepath.Join(os.TempDir(), "pvrecorder")
+	extractionDir := filepath.Join(os.TempDir(), "pvRecorder")
 
 	var scriptPath string
 	if runtime.GOOS == "windows" {
@@ -262,7 +286,11 @@ func extractFile(srcFile string, dstDir string) string {
 	}
 
 	extractedFilepath := filepath.Join(dstDir, srcFile)
-	os.MkdirAll(filepath.Dir(extractedFilepath), 0777)
+	err := os.MkdirAll(filepath.Dir(extractedFilepath), 0777)
+	if err != nil {
+		log.Fatalf("%v", err)
+	}
+
 	writeErr := ioutil.WriteFile(extractedFilepath, bytes, 0777)
 	if writeErr != nil {
 		log.Fatalf("%v", writeErr)
diff --git a/binding/go/pv_recorder_native.go b/binding/go/pv_recorder_native.go
new file mode 100644
index 00000000..3eb6a9a9
--- /dev/null
+++ b/binding/go/pv_recorder_native.go
@@ -0,0 +1,243 @@
+// Copyright 2021-2023 Picovoice Inc.
+//
+// You may not use this file except in compliance with the license. A copy of the license is
+// located in the "LICENSE" file accompanying this source.
+//
+// Unless required by applicable law or agreed to in writing, software distributed under the
+// License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+// express or implied. See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package pvrecorder
+
+/*
+#cgo linux LDFLAGS: -lpthread -ldl -lm
+#cgo darwin LDFLAGS: -lpthread -ldl -lm
+#include <stdbool.h>
+#include <stdlib.h>
+#include <stdint.h>
+
+#if defined(_WIN32) || defined(_WIN64)
+
+    #include <windows.h>
+
+#else
+
+    #include <dlfcn.h>
+
+#endif
+
+static void *open_dl(const char *dl_path) {
+
+#if defined(_WIN32) || defined(_WIN64)
+
+    return LoadLibrary((LPCSTR) dl_path);
+
+#else
+
+    return dlopen(dl_path, RTLD_NOW);
+
+#endif
+
+}
+
+static void *load_symbol(void *handle, const char *symbol) {
+
+#if defined(_WIN32) || defined(_WIN64)
+
+    return GetProcAddress((HMODULE) handle, symbol);
+
+#else
+
+    return dlsym(handle, symbol);
+
+#endif
+
+}
+
+typedef int32_t (*pv_recorder_init_func)(int32_t, int32_t, int32_t, void **);
+
+int32_t pv_recorder_init_wrapper(void *f, int32_t frame_length, int32_t device_index, int32_t buffered_frames_count, void **object) {
+    return ((pv_recorder_init_func) f)(frame_length, device_index, buffered_frames_count, object);
+}
+
+typedef void (*pv_recorder_delete_func)(void *);
+
+void pv_recorder_delete_wrapper(void *f, void *object) {
+    return ((pv_recorder_delete_func) f)(object);
+}
+
+typedef int32_t (*pv_recorder_start_func)(void *);
+
+int32_t pv_recorder_start_wrapper(void *f, void *object) {
+    return ((pv_recorder_start_func) f)(object);
+}
+
+typedef int32_t (*pv_recorder_stop_func)(void *);
+
+int32_t pv_recorder_stop_wrapper(void *f, void *object) {
+    return ((pv_recorder_stop_func) f)(object);
+}
+
+typedef int32_t (*pv_recorder_read_func)(void *, int16_t *);
+
+int32_t pv_recorder_read_wrapper(void *f, void *object, int16_t *pcm) {
+    return ((pv_recorder_read_func) f)(object, pcm);
+}
+
+typedef void (*pv_recorder_set_debug_logging_func)(void *, bool);
+
+void pv_recorder_set_debug_logging_wrapper(void *f, void *object, bool is_debug_logging_enabled) {
+	return ((pv_recorder_set_debug_logging_func) f)(object, is_debug_logging_enabled);
+}
+
+typedef bool (*pv_recorder_get_is_recording_func)(void *);
+
+bool pv_recorder_get_is_recording_wrapper(void *f, void* object) {
+    return ((pv_recorder_get_is_recording_func) f)(object);
+}
+
+typedef const char *(*pv_recorder_get_selected_device_func)(void *);
+
+const char *pv_recorder_get_selected_device_wrapper(void *f, void* object) {
+    return ((pv_recorder_get_selected_device_func) f)(object);
+}
+
+typedef int32_t (*pv_recorder_get_available_devices_func)(int32_t *, char ***);
+
+int32_t pv_recorder_get_available_devices_wrapper(void *f, int32_t *count, char ***devices) {
+    return ((pv_recorder_get_available_devices_func) f)(count, devices);
+}
+
+typedef void (*pv_recorder_free_available_devices_func)(int32_t, char**);
+
+void pv_recorder_free_available_devices_wrapper(void *f, int32_t count, char **devices) {
+    return ((pv_recorder_free_available_devices_func) f)(count, devices);
+}
+
+typedef const char *(*pv_recorder_version_func)();
+
+const char *pv_recorder_version_wrapper(void *f) {
+    return ((pv_recorder_version_func) f)();
+}
+
+typedef int32_t (*pv_recorder_sample_rate_func)();
+
+int32_t pv_recorder_sample_rate_wrapper(void *f) {
+    return ((pv_recorder_sample_rate_func) f)();
+}
+
+*/
+import "C"
+
+import (
+	"unsafe"
+)
+
+// private vars
+var (
+	lib = C.open_dl(C.CString(libName))
+
+	pv_recorder_init_ptr                   = C.load_symbol(lib, C.CString("pv_recorder_init"))
+	pv_recorder_delete_ptr                 = C.load_symbol(lib, C.CString("pv_recorder_delete"))
+	pv_recorder_start_ptr                  = C.load_symbol(lib, C.CString("pv_recorder_start"))
+	pv_recorder_stop_ptr                   = C.load_symbol(lib, C.CString("pv_recorder_stop"))
+	pv_recorder_read_ptr                   = C.load_symbol(lib, C.CString("pv_recorder_read"))
+	pv_recorder_set_debug_logging_ptr      = C.load_symbol(lib, C.CString("pv_recorder_set_debug_logging"))
+	pv_recorder_get_is_recording_ptr       = C.load_symbol(lib, C.CString("pv_recorder_get_is_recording"))
+	pv_recorder_get_selected_device_ptr    = C.load_symbol(lib, C.CString("pv_recorder_get_selected_device"))
+	pv_recorder_get_available_devices_ptr  = C.load_symbol(lib, C.CString("pv_recorder_get_available_devices"))
+	pv_recorder_free_available_devices_ptr = C.load_symbol(lib, C.CString("pv_recorder_free_available_devices"))
+	pv_recorder_version_ptr                = C.load_symbol(lib, C.CString("pv_recorder_version"))
+	pv_recorder_sample_rate_ptr            = C.load_symbol(lib, C.CString("pv_recorder_sample_rate"))
+)
+
+func (np nativePvRecorderType) nativeInit(pvRecorder *PvRecorder) PvRecorderStatus {
+	var (
+		frameLength         = pvRecorder.FrameLength
+		deviceIndex         = pvRecorder.DeviceIndex
+		bufferedFramesCount = pvRecorder.BufferedFramesCount
+		ptrC                = make([]unsafe.Pointer, 1)
+	)
+
+	var ret = C.pv_recorder_init_wrapper(pv_recorder_init_ptr,
+		(C.int32_t)(frameLength),
+		(C.int32_t)(deviceIndex),
+		(C.int32_t)(bufferedFramesCount),
+		&ptrC[0])
+
+	pvRecorder.handle = uintptr(ptrC[0])
+	return PvRecorderStatus(ret)
+}
+
+func (nativePvRecorderType) nativeDelete(pvRecorder *PvRecorder) {
+	C.pv_recorder_delete_wrapper(pv_recorder_delete_ptr,
+		unsafe.Pointer(pvRecorder.handle))
+}
+
+func (nativePvRecorderType) nativeStart(pvRecorder *PvRecorder) PvRecorderStatus {
+	var ret = C.pv_recorder_start_wrapper(pv_recorder_start_ptr,
+		unsafe.Pointer(pvRecorder.handle))
+
+	return PvRecorderStatus(ret)
+}
+
+func (nativePvRecorderType) nativeStop(pvRecorder *PvRecorder) PvRecorderStatus {
+	var ret = C.pv_recorder_stop_wrapper(pv_recorder_stop_ptr,
+		unsafe.Pointer(pvRecorder.handle))
+
+	return PvRecorderStatus(ret)
+}
+
+func (nativePvRecorderType) nativeRead(pvRecorder *PvRecorder, pcm *C.int16_t) PvRecorderStatus {
+	var ret = C.pv_recorder_read_wrapper(pv_recorder_read_ptr,
+		unsafe.Pointer(pvRecorder.handle),
+		pcm)
+
+	return PvRecorderStatus(ret)
+}
+
+func (nativePvRecorderType) nativeSetDebugLogging(pvRecorder *PvRecorder, isDebugLoggingEnabled bool) {
+	C.pv_recorder_set_debug_logging_wrapper(pv_recorder_set_debug_logging_ptr,
+		unsafe.Pointer(pvRecorder.handle),
+		C.bool(isDebugLoggingEnabled))
+}
+
+func (nativePvRecorderType) nativeGetIsRecording(pvRecorder *PvRecorder) bool {
+	var ret = C.pv_recorder_get_is_recording_wrapper(pv_recorder_get_is_recording_ptr,
+		unsafe.Pointer(pvRecorder.handle))
+
+	return bool(ret)
+}
+
+func (nativePvRecorderType) nativeGetSelectedDevice(pvRecorder *PvRecorder) string {
+	var ret = C.pv_recorder_get_selected_device_wrapper(pv_recorder_get_selected_device_ptr,
+		unsafe.Pointer(pvRecorder.handle))
+
+	return C.GoString(ret)
+}
+
+func (nativePvRecorderType) nativeGetAudioDevices(count *int, devices ***C.char) PvRecorderStatus {
+	var ret = C.pv_recorder_get_available_devices_wrapper(pv_recorder_get_available_devices_ptr,
+		(*C.int32_t)(unsafe.Pointer(count)),
+		(***C.char)(unsafe.Pointer(devices)))
+
+	return PvRecorderStatus(ret)
+}
+
+func (nativePvRecorderType) nativeFreeDeviceList(count int, devices **C.char) {
+	C.pv_recorder_free_available_devices_wrapper(pv_recorder_free_available_devices_ptr,
+		(C.int32_t)(count),
+		(**C.char)(unsafe.Pointer(devices)))
+}
+
+func (nativePvRecorderType) nativeVersion() string {
+	var ret = C.pv_recorder_version_wrapper(pv_recorder_version_ptr)
+	return C.GoString(ret)
+}
+
+func (nativePvRecorderType) nativeSampleRate() int {
+	var ret = C.pv_recorder_sample_rate_wrapper(pv_recorder_sample_rate_ptr)
+	return int(ret)
+}
diff --git a/binding/nodejs/.eslintignore b/binding/nodejs/.eslintignore
new file mode 100644
index 00000000..b8f2c68a
--- /dev/null
+++ b/binding/nodejs/.eslintignore
@@ -0,0 +1,5 @@
+node_modules
+dist
+lib
+resources
+.eslintrc.js
diff --git a/binding/nodejs/.eslintrc.js b/binding/nodejs/.eslintrc.js
new file mode 100644
index 00000000..4ba6d8fe
--- /dev/null
+++ b/binding/nodejs/.eslintrc.js
@@ -0,0 +1,463 @@
+// Rules reference: http://eslint.org/docs/rules/
+module.exports = {
+  env: {
+    browser: true,
+    node: true,
+    es6: true,
+    jest: true,
+    jasmine: true
+  },
+
+  parser: '@typescript-eslint/parser',
+  parserOptions: {
+    ecmaVersion: 2018
+  },
+
+  ignorePatterns: ['**/*.js', 'node_modules', 'dist'],
+  overrides: [
+    {
+      files: ['src/**/*.ts'],
+      plugins: ['jest'],
+      extends: ['plugin:@typescript-eslint/recommended', 'plugin:jest/recommended'],
+      rules: {
+        '@typescript-eslint/no-parameter-properties': 2,
+        '@typescript-eslint/no-explicit-any': 0,
+        '@typescript-eslint/no-var-requires': 2,
+        '@typescript-eslint/no-non-null-assertion': 0,
+        '@typescript-eslint/no-use-before-define': 2,
+        '@typescript-eslint/camelcase': 0,
+        '@typescript-eslint/no-empty-interface': 2,
+        '@typescript-eslint/explicit-function-return-type': 2,
+        '@typescript-eslint/ban-ts-comment': 0,
+        '@typescript-eslint/no-empty-function': [2, { "allow": ["constructors"] }],
+        '@typescript-eslint/no-inferrable-types': [
+          2,
+          {
+            ignoreParameters: true,
+            ignoreProperties: true
+          }
+        ],
+        '@typescript-eslint/no-shadow': 2
+      }
+    }
+  ],
+
+  rules: {
+    //=========================================================================
+    //==================== Possible Errors ====================================
+    //=========================================================================
+
+    // disallow trailing commas in object literals
+    'comma-dangle': [0, 'always-multiline'], //TODO: need discussion
+    // disallow assignment in conditional expressions
+    'no-cond-assign': [2, 'always'],
+    // disallow use of console
+    'no-console': 1,
+    // disallow use of constant expressions in conditions
+    'no-constant-condition': [2, { checkLoops: false }],
+    // disallow control characters in regular expressions
+    'no-control-regex': 2,
+    // disallow use of debugger
+    'no-debugger': 2,
+    // disallow duplicate arguments in functions
+    'no-dupe-args': 2,
+    // disallow duplicate keys when creating object literals
+    'no-dupe-keys': 2,
+    // disallow a duplicate case label.
+    'no-duplicate-case': 2,
+    // disallow the use of empty character classes in regular expressions
+    'no-empty-character-class': 2,
+    // disallow empty statements
+    'no-empty': 2,
+    // disallow assigning to the exception in a catch block
+    'no-ex-assign': 2,
+    // disallow double-negation boolean casts in a boolean context
+    'no-extra-boolean-cast': 2,
+    // disallow unnecessary parentheses
+    'no-extra-parens': [2, 'functions'],
+    // disallow unnecessary semicolons
+    'no-extra-semi': 2,
+    // disallow overwriting functions written as function declarations
+    'no-func-assign': 2,
+    // disallow function or variable declarations in nested blocks
+    'no-inner-declarations': 2,
+    // disallow invalid regular expression strings in the RegExp constructor
+    'no-invalid-regexp': 2,
+    // disallow irregular whitespace outside of strings and comments
+    'no-irregular-whitespace': 2,
+    // disallow negation of the left operand of an in expression
+    'no-negated-in-lhs': 2,
+    // disallow the use of object properties of the global object (Math and JSON) as functions
+    'no-obj-calls': 2,
+    // disallow multiple spaces in a regular expression literal
+    'no-regex-spaces': 2,
+    // disallow sparse arrays
+    'no-sparse-arrays': 2,
+    // Avoid code that looks like two expressions but is actually one
+    'no-unexpected-multiline': 2,
+    // disallow unreachable statements after a return, throw, continue, or break statement
+    'no-unreachable': 2,
+    // disallow comparisons with the value NaN
+    'use-isnan': 2,
+    // ensure JSDoc comments are valid
+    'valid-jsdoc': [
+      0,
+      {
+        requireReturn: false,
+        requireReturnDescription: false
+      }
+    ],
+    // ensure that the results of typeof are compared against a valid string
+    'valid-typeof': 2,
+
+    //=========================================================================
+    //==================== Best Practices =====================================
+    //=========================================================================
+    // Enforces getter/setter pairs in objects
+    'accessor-pairs': 2,
+    // treat var statements as if they were block scoped
+    'block-scoped-var': 2,
+    // specify the maximum cyclomatic complexity allowed in a program
+    complexity: [0, 11],
+    // require return statements to either always or never specify values
+    'consistent-return': 2,
+    // specify curly brace conventions for all control statements
+    curly: [2, 'multi-line'],
+    // require default case in switch statements
+    'default-case': 2,
+    // encourages use of dot notation whenever possible
+    'dot-notation': [2, { allowKeywords: true }],
+    // enforces consistent newlines before or after dots
+    'dot-location': [2, 'property'],
+    // require the use of === and !==
+    eqeqeq: 2,
+    // make sure for-in loops have an if statement
+    'guard-for-in': 2,
+    // disallow the use of alert, confirm, and prompt
+    'no-alert': 2,
+    // disallow use of arguments.caller or arguments.callee
+    'no-caller': 2,
+    // disallow lexical declarations in case clauses
+    'no-case-declarations': 2,
+    // disallow division operators explicitly at beginning of regular expression
+    'no-div-regex': 2,
+    // disallow else after a return in an if
+    'no-else-return': 2,
+    // disallow use of empty destructuring patterns
+    'no-empty-pattern': 2,
+    // disallow comparisons to null without a type-checking operator
+    'no-eq-null': 2,
+    // disallow use of eval()
+    'no-eval': 2,
+    // disallow adding to native types
+    'no-extend-native': 2,
+    // disallow unnecessary function binding
+    'no-extra-bind': 2,
+    // disallow fallthrough of case statements
+    'no-fallthrough': 2,
+    // disallow the use of leading or trailing decimal points in numeric literals
+    'no-floating-decimal': 2,
+    // disallow the type conversions with shorter notations
+    'no-implicit-coercion': 2,
+    // disallow use of eval()-like methods
+    'no-implied-eval': 2,
+    // disallow this keywords outside of classes or class-like objects
+    'no-invalid-this': 0,
+    // disallow usage of __iterator__ property
+    'no-iterator': 2,
+    // disallow use of labeled statements
+    'no-labels': 2,
+    // disallow unnecessary nested blocks
+    'no-lone-blocks': 2,
+    // disallow creation of functions within loops
+    'no-loop-func': 2,
+    // disallow the use of magic numbers
+    'no-magic-numbers': 0, //TODO: need discussion
+    // disallow use of multiple spaces
+    'no-multi-spaces': 2,
+    // disallow use of multiline strings
+    'no-multi-str': 2,
+    // disallow reassignments of native objects
+    'no-native-reassign': 2,
+    // disallow use of new operator for Function object
+    'no-new-func': 2,
+    // disallows creating new instances of String,Number, and Boolean
+    'no-new-wrappers': 2,
+    // disallow use of new operator when not part of the assignment or comparison
+    'no-new': 0,
+    // disallow use of octal escape sequences in string literals, such as
+    // var foo = "Copyright \251";
+    'no-octal-escape': 2,
+    // disallow use of (old style) octal literals
+    'no-octal': 2,
+    // disallow reassignment of function parameters
+    'no-param-reassign': 2,
+    // disallow use of process.env
+    'no-process-env': 2,
+    // disallow usage of __proto__ property
+    'no-proto': 2,
+    // disallow declaring the same variable more then once
+    'no-redeclare': 2,
+    // disallow use of assignment in return statement
+    'no-return-assign': 2,
+    // disallow use of `javascript:` urls.
+    'no-script-url': 2,
+    // disallow comparisons where both sides are exactly the same
+    'no-self-compare': 2,
+    // disallow use of comma operator
+    'no-sequences': 2,
+    // restrict what can be thrown as an exception
+    'no-throw-literal': 0,
+    // disallow usage of expressions in statement position
+    'no-unused-expressions': 2,
+    // disallow unnecessary .call() and .apply()
+    'no-useless-call': 2,
+    // disallow unnecessary concatenation of literals or template literals
+    'no-useless-concat': 2,
+    // disallow use of void operator
+    'no-void': 2,
+    // disallow usage of configurable warning terms in comments: e.g. todo
+    'no-warning-comments': [
+      1,
+      { terms: ['todo', 'fixme', 'xxx'], location: 'start' }
+    ],
+    // disallow use of the with statement
+    'no-with': 2,
+    // require use of the second argument for parseInt()
+    radix: 2,
+    // requires to declare all vars on top of their containing scope
+    'vars-on-top': 0,
+    // require immediate function invocation to be wrapped in parentheses
+    'wrap-iife': [2, 'any'],
+    // require or disallow Yoda conditions
+    yoda: 2,
+
+    // //=========================================================================
+    // //==================== Strict Mode ========================================
+    // //=========================================================================
+    // require that all functions are run in strict mode
+    // "strict": [2, "global"],
+    //
+    //=========================================================================
+    //==================== Variables ==========================================
+    //=========================================================================
+    // enforce or disallow variable initializations at definition
+    'init-declarations': 0,
+    // disallow the catch clause parameter name being the same as a variable in the outer scope
+    'no-catch-shadow': 0,
+    // disallow deletion of variables
+    'no-delete-var': 2,
+    // disallow labels that share a name with a variable
+    'no-label-var': 2,
+    // disallow shadowing of names such as arguments
+    'no-shadow-restricted-names': 2,
+    // disallow declaration of variables already declared in the outer scope
+    'no-shadow': 0,
+    // disallow use of undefined when initializing variables
+    'no-undef-init': 0,
+    // disallow use of undeclared variables unless mentioned in a /*global */ block
+    'no-undef': 2,
+    // disallow use of undefined variable
+    'no-undefined': 0,
+    // disallow declaration of variables that are not used in the code
+    'no-unused-vars': [1, { vars: 'local', args: 'after-used' }],
+    // disallow use of variables before they are defined
+    'no-use-before-define': 2,
+
+    //=========================================================================
+    //==================== Node.js ============================================
+    //=========================================================================
+    // enforce return after a callback
+    'callback-return': 0,
+    // disallow require() outside of the top-level module scope
+    'global-require': 2,
+    // enforces error handling in callbacks (node environment)
+    'handle-callback-err': 2,
+    // disallow mixing regular variable and require declarations
+    'no-mixed-requires': 2,
+    // disallow use of new operator with the require function
+    'no-new-require': 2,
+    // disallow string concatenation with __dirname and __filename
+    'no-path-concat': 1,
+    // disallow process.exit()
+    'no-process-exit': 2,
+    // restrict usage of specified node modules
+    'no-restricted-modules': 0,
+    // disallow use of synchronous methods (off by default)
+    'no-sync': 0,
+
+    //=========================================================================
+    //==================== Stylistic Issues ===================================
+    //=========================================================================
+    // enforce spacing inside array brackets
+    'array-bracket-spacing': 0,
+    // disallow or enforce spaces inside of single line blocks
+    'block-spacing': 1,
+    // enforce one true brace style
+    'brace-style': [1, '1tbs', { allowSingleLine: true }],
+    // require camel case names
+    camelcase: [1, { properties: 'always' }],
+    // enforce spacing before and after comma
+    'comma-spacing': [1, { before: false, after: true }],
+    // enforce one true comma style
+    'comma-style': [1, 'last'],
+    // require or disallow padding inside computed properties
+    'computed-property-spacing': 0,
+    // enforces consistent naming when capturing the current execution context
+    'consistent-this': 0,
+    // enforce newline at the end of file, with no multiple empty lines
+    'eol-last': 1,
+    // require function expressions to have a name
+    'func-names': 0,
+    // enforces use of function declarations or expressions
+    'func-style': 0,
+    // this option enforces minimum and maximum identifier lengths (variable names, property names etc.)
+    'id-length': 0,
+    // require identifiers to match the provided regular expression
+    'id-match': 0,
+    // this option sets a specific tab width for your code
+    indent: [1, 2, { SwitchCase: 1 }],
+    // specify whether double or single quotes should be used in JSX attributes
+    'jsx-quotes': [1, 'prefer-double'],
+    // enforces spacing between keys and values in object literal properties
+    'key-spacing': [1, { beforeColon: false, afterColon: true }],
+    // disallow mixed "LF" and "CRLF" as linebreaks
+    'linebreak-style': 0,
+    // enforces empty lines around comments
+    'lines-around-comment': 0,
+    // specify the maximum depth that blocks can be nested
+    'max-depth': [0, 4],
+    // specify the maximum length of a line in your program
+    'max-len': [0, 80, 4],
+    // specify the maximum depth callbacks can be nested
+    'max-nested-callbacks': 0,
+    // limits the number of parameters that can be used in the function declaration.
+    'max-params': [0, 3],
+    // specify the maximum number of statement allowed in a function
+    'max-statements': [0, 10],
+    // require a capital letter for constructors
+    'new-cap': [1, { newIsCap: true }],
+    // disallow the omission of parentheses when invoking a constructor with no arguments
+    'new-parens': 0,
+    // allow/disallow an empty newline after var statement
+    'newline-after-var': 0,
+    // disallow use of the Array constructor
+    'no-array-constructor': 0,
+    // disallow use of bitwise operators
+    'no-bitwise': 0,
+    // disallow use of the continue statement
+    'no-continue': 0,
+    // disallow comments inline after code
+    'no-inline-comments': 0,
+    // disallow if as the only statement in an else block
+    'no-lonely-if': 0,
+    // disallow mixed spaces and tabs for indentation
+    'no-mixed-spaces-and-tabs': 1,
+    // disallow multiple empty lines
+    'no-multiple-empty-lines': [1, { max: 2, maxEOF: 1 }],
+    // disallow negated conditions
+    'no-negated-condition': 0,
+    // disallow nested ternary expressions
+    'no-nested-ternary': 1,
+    // disallow use of the Object constructor
+    'no-new-object': 1,
+    // disallow use of unary operators, ++ and --
+    'no-plusplus': 0,
+    // disallow use of certain syntax in code
+    'no-restricted-syntax': 0,
+    // disallow space between function identifier and application
+    'no-spaced-func': 1,
+    // disallow the use of ternary operators
+    'no-ternary': 0,
+    // disallow trailing whitespace at the end of lines
+    'no-trailing-spaces': 1,
+    // disallow dangling underscores in identifiers
+    'no-underscore-dangle': 0,
+    // disallow the use of Boolean literals in conditional expressions
+    'no-unneeded-ternary': 0,
+    // require or disallow padding inside curly braces
+    'object-curly-spacing': ["error", "always"],
+    // allow just one var statement per function
+    'one-var': [1, 'never'],
+    // require assignment operator shorthand where possible or prohibit it entirely
+    'operator-assignment': 0,
+    // enforce operators to be placed before or after line breaks
+    'operator-linebreak': 0,
+    // enforce padding within blocks
+    'padded-blocks': [1, 'never'],
+    // require quotes around object literal property names
+    'quote-props': 0,
+    // specify whether double or single quotes should be used
+    quotes: 0,
+    // Require JSDoc comment
+    'require-jsdoc': 0,
+    // enforce spacing before and after semicolons
+    'semi-spacing': [1, { before: false, after: true }],
+    // require or disallow use of semicolons instead of ASI
+    semi: [1, 'always'],
+    // sort variables within the same declaration block
+    'sort-vars': 0,
+    // require a space after certain keywords
+    'keyword-spacing': 1,
+    // require or disallow space before blocks
+    'space-before-blocks': 1,
+    // require or disallow space before function opening parenthesis
+    'space-before-function-paren': [0, { anonymous: 'always', named: 'never' }],
+    // require or disallow space before blocks
+    'space-in-parens': 0,
+    // require spaces around operators
+    'space-infix-ops': 1,
+    // Require or disallow spaces before/after unary operators
+    'space-unary-ops': 0,
+    // require or disallow a space immediately following the // or /* in a comment
+    'spaced-comment': [
+      1,
+      'always',
+      {
+        exceptions: ['-', '+', '/', '='],
+        markers: ['=', '!', '/'] // space here to support sprockets directives
+      }
+    ],
+    // require regex literals to be wrapped in parentheses
+    'wrap-regex': 0,
+
+    //=========================================================================
+    //==================== ES6 Rules ==========================================
+    //=========================================================================
+    'arrow-body-style': [1, 'as-needed'],
+    // require parens in arrow function arguments
+    'arrow-parens': [1, 'as-needed'],
+    // require space before/after arrow function"s arrow
+    'arrow-spacing': 1,
+    // verify super() callings in constructors
+    'constructor-super': 1,
+    // enforce the spacing around the * in generator functions
+    'generator-star-spacing': 1,
+    // disallow arrow functions where a condition is expected
+    'no-confusing-arrow': 1,
+    // disallow modifying variables of class declarations
+    'no-class-assign': 1,
+    // disallow modifying variables that are declared using const
+    'no-const-assign': 1,
+    // disallow duplicate name in class members
+    'no-dupe-class-members': 1,
+    // disallow to use this/super before super() calling in constructors.
+    'no-this-before-super': 1,
+    // require let or const instead of var
+    'no-var': 0, //TODO: enable on full migration to es6
+    // require method and property shorthand syntax for object literals
+    'object-shorthand': 0,
+    // suggest using arrow functions as callbacks
+    'prefer-arrow-callback': 0, //TODO: enable on full migration to es6
+    // suggest using of const declaration for variables that are never modified after declared
+    'prefer-const': 0, //TODO: enable on full migration to es6
+    // suggest using Reflect methods where applicable
+    'prefer-reflect': 0,
+    // suggest using the spread operator instead of .apply()
+    'prefer-spread': 0,
+    // suggest using template literals instead of strings concatenation
+    'prefer-template': 0, //TODO: enable on full migration to es6
+    // disallow generator functions that do not have yield
+    'require-yield': 0
+  }
+};
diff --git a/sdk/nodejs/.gitignore b/binding/nodejs/.gitignore
similarity index 100%
rename from sdk/nodejs/.gitignore
rename to binding/nodejs/.gitignore
diff --git a/sdk/nodejs/.npmignore b/binding/nodejs/.npmignore
similarity index 100%
rename from sdk/nodejs/.npmignore
rename to binding/nodejs/.npmignore
diff --git a/binding/nodejs/.prettierrc b/binding/nodejs/.prettierrc
new file mode 100644
index 00000000..a9ba028c
--- /dev/null
+++ b/binding/nodejs/.prettierrc
@@ -0,0 +1,1 @@
+.eslintrc.js
diff --git a/binding/nodejs/README.md b/binding/nodejs/README.md
new file mode 100644
index 00000000..a733881f
--- /dev/null
+++ b/binding/nodejs/README.md
@@ -0,0 +1,63 @@
+# PvRecorder Binding for Node.js
+
+## Compatibility
+
+- Node.js 14+
+- Runs on Linux (x86_64), macOS (x86_64 and arm64), Windows (x86_64), Raspberry Pi (2, 3, 4), NVIDIA Jetson (Nano), and BeagleBone.
+
+## Installation
+
+```console
+yarn add @picovoice/pvrecorder-node
+```
+
+## Usage
+
+To start recording initialize the instance and run `start`:
+
+```javascript
+const { PvRecorder } = require("@picovoice/pvrecorder-node");
+
+const recorder = new PvRecorder(/*frame length*/ 512);
+recorder.start()
+```
+
+(or)
+
+Use `get_available_devices()` to get a list of available devices and then initialize the instance based on the index of a device:
+
+```javascript
+
+const { PvRecorder } = require("@picovoice/pvrecorder-node");
+
+const devices = PvRecorder.getAvailableDevices()
+
+const recorder = new PvRecorder(512, /*device index*/0);
+recorder.start()
+```
+
+Get a frame of audio by calling the read function:
+
+```javascript
+while (recorder.isRecording) {
+    // const frame = recorder.readSync(), for synchronous calls
+    const frame = await recorder.read();
+    // do something with frame
+}
+```
+
+To stop recording, run stop on the instance:
+
+```javascript
+recorder.stop();
+```
+
+Once you are done, free the used resources. You do not have to call stop before release:
+
+```javascript
+recorder.release();
+```
+
+## Demos
+
+[@picovoice/pvrecorder-demo](https://www.npmjs.com/package/@picovoice/pvrecorder-demo) provides command-line utilities for recording audio to a file.
diff --git a/sdk/nodejs/copy.js b/binding/nodejs/copy.js
similarity index 93%
rename from sdk/nodejs/copy.js
rename to binding/nodejs/copy.js
index 03a4a445..d11b37f6 100644
--- a/sdk/nodejs/copy.js
+++ b/binding/nodejs/copy.js
@@ -1,5 +1,5 @@
 //
-// Copyright 2021 Picovoice Inc.
+// Copyright 2021-2023 Picovoice Inc.
 //
 // You may not use this file except in compliance with the license. A copy of the license is located in the "LICENSE"
 // file accompanying this source.
@@ -30,7 +30,7 @@ console.log("Copying scripts...");
 
 // Scripts
 ncp(
-    "../../scripts",
+    "../../resources/scripts",
     "./scripts",
     function (err) {
         if (err) {
@@ -38,4 +38,4 @@ ncp(
         }
         console.log("Successfully copied scripts.")
     }
-)
\ No newline at end of file
+)
diff --git a/binding/nodejs/jest.config.js b/binding/nodejs/jest.config.js
new file mode 100644
index 00000000..e86e13ba
--- /dev/null
+++ b/binding/nodejs/jest.config.js
@@ -0,0 +1,5 @@
+/** @type {import('ts-jest/dist/types').InitialOptionsTsJest} */
+module.exports = {
+  preset: 'ts-jest',
+  testEnvironment: 'node',
+};
diff --git a/sdk/nodejs/package.json b/binding/nodejs/package.json
similarity index 64%
rename from sdk/nodejs/package.json
rename to binding/nodejs/package.json
index b483c57f..abb29382 100644
--- a/sdk/nodejs/package.json
+++ b/binding/nodejs/package.json
@@ -1,6 +1,6 @@
 {
   "name": "@picovoice/pvrecorder-node",
-  "version": "1.1.2",
+  "version": "1.2.0",
   "description": "Audio recorder sdk for Nodejs.",
   "main": "dist/index.js",
   "keywords": [
@@ -11,20 +11,30 @@
   "repository": {
     "type": "git",
     "url": "https://github.com/Picovoice/pvrecorder.git",
-    "directory": "sdk/nodejs"
+    "directory": "binding/nodejs"
   },
   "scripts": {
     "build": "npm-run-all --parallel build:**",
     "build:all": "tsc",
     "build:types": "tsc --declaration --declarationMap --emitDeclarationOnly --outDir ./dist/types",
     "prepack": "npm run build",
-    "prepare": "node copy.js"
+    "prepare": "node copy.js",
+    "test": "jest --no-cache",
+    "lint": "eslint . --ext .js,.ts"
   },
   "devDependencies": {
+    "@types/jest": "^27.4.1",
     "@types/node": "^17.0.21",
+    "@typescript-eslint/eslint-plugin": "^5.19.0",
+    "@typescript-eslint/parser": "^5.19.0",
+    "eslint": "^8.13.0",
+    "eslint-plugin-jest": "^27.1.6",
+    "jest": "^27.5.1",
     "mkdirp": "^1.0.4",
     "ncp": "^2.0.0",
     "npm-run-all": "^4.1.5",
+    "prettier": "^2.6.2",
+    "ts-jest": "^27.1.3",
     "typescript": "^4.6.2"
   },
   "engines": {
diff --git a/sdk/nodejs/src/errors.ts b/binding/nodejs/src/errors.ts
similarity index 74%
rename from sdk/nodejs/src/errors.ts
rename to binding/nodejs/src/errors.ts
index e317a4b3..65ef5e55 100644
--- a/sdk/nodejs/src/errors.ts
+++ b/binding/nodejs/src/errors.ts
@@ -12,16 +12,16 @@
 
 import PvRecorderStatus from "./pv_recorder_status_t";
 
-class PvRecorderStatusOutOfMemoryError extends Error{}
-class PvRecorderStatusInvalidArgumentError extends Error{}
-class PvRecorderStatusInvalidStateError extends Error{}
-class PvRecorderStatusBackendError extends Error{}
-class PvRecorderStatusDeviceAlreadyInitializedError extends Error{}
-class PvRecorderStatusDeviceNotInitializedError extends Error{}
-class PvRecorderStatusIOError extends Error{}
-class PvRecorderStatusRuntimeError extends Error{}
+class PvRecorderStatusOutOfMemoryError extends Error {}
+class PvRecorderStatusInvalidArgumentError extends Error {}
+class PvRecorderStatusInvalidStateError extends Error {}
+class PvRecorderStatusBackendError extends Error {}
+class PvRecorderStatusDeviceAlreadyInitializedError extends Error {}
+class PvRecorderStatusDeviceNotInitializedError extends Error {}
+class PvRecorderStatusIOError extends Error {}
+class PvRecorderStatusRuntimeError extends Error {}
 
-function PvRecorderStatusToException(status: PvRecorderStatus, errorMessage: string) {
+function pvRecorderStatusToException(status: PvRecorderStatus, errorMessage: string): Error {
   switch (status) {
     case PvRecorderStatus.OUT_OF_MEMORY:
       return new PvRecorderStatusOutOfMemoryError(errorMessage);
@@ -40,9 +40,10 @@ function PvRecorderStatusToException(status: PvRecorderStatus, errorMessage: str
     case PvRecorderStatus.RUNTIME_ERROR:
       return new PvRecorderStatusRuntimeError(errorMessage);
     default:
+      // eslint-disable-next-line
       console.warn(`Unknown error code: ${status}`);
       return new Error(errorMessage);
   }
 }
 
-export default PvRecorderStatusToException;
+export default pvRecorderStatusToException;
diff --git a/sdk/nodejs/src/index.ts b/binding/nodejs/src/index.ts
similarity index 100%
rename from sdk/nodejs/src/index.ts
rename to binding/nodejs/src/index.ts
diff --git a/sdk/nodejs/src/pv_recorder.ts b/binding/nodejs/src/pv_recorder.ts
similarity index 54%
rename from sdk/nodejs/src/pv_recorder.ts
rename to binding/nodejs/src/pv_recorder.ts
index f6a772cd..98598f3f 100644
--- a/sdk/nodejs/src/pv_recorder.ts
+++ b/binding/nodejs/src/pv_recorder.ts
@@ -1,5 +1,5 @@
 //
-// Copyright 2022 Picovoice Inc.
+// Copyright 2022-2023 Picovoice Inc.
 //
 // You may not use this file except in compliance with the license. A copy of the license is located in the "LICENSE"
 // file accompanying this source.
@@ -15,46 +15,47 @@ import * as os from "os";
 import * as path from "path";
 
 import PvRecorderStatus from "./pv_recorder_status_t";
-import PvRecorderStatusToException from "./errors";
+import pvRecorderStatusToException from "./errors";
 
 /**
- * PvRecorder class to record audio.
+ * PvRecorder class for recording audio.
  */
 class PvRecorder {
+  // eslint-disable-next-line
   private static _pvRecorder = require(PvRecorder._getLibraryPath());
 
   private readonly _handle: number;
   private readonly _frameLength: number;
+  private readonly _sampleRate: number;
   private readonly _version: string;
 
   /**
    * PvRecorder constructor.
    *
-   * @param deviceIndex The audio device index to use to record audio. A value of (-1) will use machine's default audio device.
    * @param frameLength Length of the audio frames to receive per read call.
-   * @param bufferSizeMSec Time in milliseconds to store the audio frames received.
-   * @param logOverflow Boolean indicator to log warnings if the pcm frames buffer received an overflow.
-   * @param logSilence Boolean variable to enable silence logs. This will log when continuous audio buffers are detected as silent.
+   * @param deviceIndex The audio device index to use to record audio. A value of (-1) will use machine's default audio device.
+   * @param bufferedFramesCount The number of audio frames buffered internally for reading - i.e. internal circular buffer
+   * will be of size `frameLength` * `bufferedFramesCount`. If this value is too low, buffer overflows could occur
+   * and audio frames could be dropped. A higher value will increase memory usage.
    */
   constructor(
-    deviceIndex: number,
     frameLength: number,
-    bufferSizeMSec = 1000,
-    logOverflow = true,
-    logSilence = true
+    deviceIndex: number = -1,
+    bufferedFramesCount = 50,
   ) {
-    let porcupineHandleAndStatus;
+    let pvRecorderHandleAndStatus;
     try {
-      porcupineHandleAndStatus = PvRecorder._pvRecorder.init(deviceIndex, frameLength, bufferSizeMSec, logOverflow, logSilence);
+      pvRecorderHandleAndStatus = PvRecorder._pvRecorder.init(frameLength, deviceIndex, bufferedFramesCount);
     } catch (err: any) {
-      PvRecorderStatusToException(err.code, err);
+      pvRecorderStatusToException(err.code, err);
     }
-    const status = porcupineHandleAndStatus.status;
+    const status = pvRecorderHandleAndStatus.status;
     if (status !== PvRecorderStatus.SUCCESS) {
-      throw PvRecorderStatusToException(status, "PvRecorder failed to initialize.");
+      throw pvRecorderStatusToException(status, "PvRecorder failed to initialize.");
     }
-    this._handle = porcupineHandleAndStatus.handle;
+    this._handle = pvRecorderHandleAndStatus.handle;
     this._frameLength = frameLength;
+    this._sampleRate = PvRecorder._pvRecorder.sample_rate();
     this._version = PvRecorder._pvRecorder.version();
   }
 
@@ -65,6 +66,13 @@ class PvRecorder {
     return this._frameLength;
   }
 
+  /**
+   * @returns Audio sample rate used by PvRecorder.
+   */
+  get sampleRate(): number {
+    return this._sampleRate;
+  }
+
   /**
    * @returns the version of the PvRecorder
    */
@@ -72,13 +80,20 @@ class PvRecorder {
     return this._version;
   }
 
+  /**
+   * @returns Whether PvRecorder is currently recording audio or not.
+   */
+  get isRecording(): boolean {
+    return PvRecorder._pvRecorder.get_is_recording(this._handle);
+  }
+
   /**
    * Starts recording audio.
    */
   public start(): void {
     const status = PvRecorder._pvRecorder.start(this._handle);
     if (status !== PvRecorderStatus.SUCCESS) {
-      throw PvRecorderStatusToException(status, "PvRecorder failed to start.");
+      throw pvRecorderStatusToException(status, "PvRecorder failed to start.");
     }
   }
 
@@ -88,42 +103,52 @@ class PvRecorder {
   public stop(): void {
     const status = PvRecorder._pvRecorder.stop(this._handle);
     if (status !== PvRecorderStatus.SUCCESS) {
-      throw PvRecorderStatusToException(status, "PvRecorder failed to stop.");
+      throw pvRecorderStatusToException(status, "PvRecorder failed to stop.");
     }
   }
 
   /**
-   * Asynchronous call to read pcm frames.
+   * Asynchronous call to read a frame of audio data.
    *
-   * @returns {Promise<Int16Array>} Pcm frames.
+   * @returns {Promise<Int16Array>} Audio data frame.
    */
   public async read(): Promise<Int16Array> {
     return new Promise<Int16Array>((resolve, reject) => {
       setTimeout(() => {
-        let pcm = new Int16Array(this._frameLength);
+        const pcm = new Int16Array(this._frameLength);
         const status = PvRecorder._pvRecorder.read(this._handle, pcm);
         if (status !== PvRecorderStatus.SUCCESS) {
-          reject(PvRecorderStatusToException(status, "PvRecorder failed to read pcm frames."));
+          reject(pvRecorderStatusToException(status, "PvRecorder failed to read audio data frame."));
         }
         resolve(pcm);
-      })
+      });
     });
   }
 
   /**
-   * Synchronous call to read pcm frames.
+   * Synchronous call to read a frame of audio data.
    *
-   * @returns {Int16Array} Pcm frames.
+   * @returns {Int16Array} Audio data frame.
    */
   public readSync(): Int16Array {
-    let pcm = new Int16Array(this._frameLength);
+    const pcm = new Int16Array(this._frameLength);
     const status = PvRecorder._pvRecorder.read(this._handle, pcm);
     if (status !== PvRecorderStatus.SUCCESS) {
-      throw PvRecorderStatusToException(status, "PvRecorder failed to read pcm frames.");
+      throw pvRecorderStatusToException(status, "PvRecorder failed to read audio data frame.");
     }
     return pcm;
   }
 
+  /**
+   * Enable or disable debug logging for PvRecorder. Debug logs will indicate when there are overflows in the internal
+   * frame buffer and when an audio source is generating frames of silence.
+   *
+   * @param isDebugLoggingEnabled Boolean indicating whether the debug logging is enabled or disabled.
+   */
+  public setDebugLogging(isDebugLoggingEnabled: boolean): void {
+    PvRecorder._pvRecorder.set_debug_logging(this._handle, isDebugLoggingEnabled);
+  }
+
   /**
    * Returns the name of the selected device used to capture audio.
    *
@@ -138,35 +163,35 @@ class PvRecorder {
   }
 
   /**
-   * Destructor. Releases any resources used by PvRecorder.
+   * Destructor. Releases resources acquired by PvRecorder.
    */
   public release(): void {
     PvRecorder._pvRecorder.delete(this._handle);
   }
 
   /**
-   * Helper function to get the available devices.
+   * Helper function to get the list of available audio devices.
    *
-   * @returns {Array<string>} An array of the device names.
+   * @returns {Array<string>} An array of the available device names.
    */
-  public static getAudioDevices() {
-    const devices = PvRecorder._pvRecorder.get_audio_devices();
+  public static getAvailableDevices(): string[] {
+    const devices = PvRecorder._pvRecorder.get_available_devices();
     if ((devices === undefined) || (devices === null)) {
       throw new Error("Failed to get audio devices.");
     }
     return devices;
   }
 
-  private static _getLibraryPath() {
+  private static _getLibraryPath(): string {
     let scriptPath;
     if (os.platform() === "win32") {
-      scriptPath = path.resolve(__dirname, "..", "scripts", "platform.bat")
+      scriptPath = path.resolve(__dirname, "..", "scripts", "platform.bat");
     } else {
-      scriptPath = path.resolve(__dirname, "..", "scripts", "platform.sh")
+      scriptPath = path.resolve(__dirname, "..", "scripts", "platform.sh");
     }
 
-    let output = execSync(`"${scriptPath}"`).toString();
-    let [osName, cpu] = output.split(" ");
+    const output = execSync(`"${scriptPath}"`).toString();
+    const [osName, cpu] = output.split(" ");
 
     return path.resolve(__dirname, "..", "lib", osName, cpu, "pv_recorder.node");
   }
diff --git a/sdk/nodejs/src/pv_recorder_status_t.ts b/binding/nodejs/src/pv_recorder_status_t.ts
similarity index 100%
rename from sdk/nodejs/src/pv_recorder_status_t.ts
rename to binding/nodejs/src/pv_recorder_status_t.ts
diff --git a/sdk/nodejs/tsconfig.json b/binding/nodejs/tsconfig.json
similarity index 100%
rename from sdk/nodejs/tsconfig.json
rename to binding/nodejs/tsconfig.json
diff --git a/binding/nodejs/yarn.lock b/binding/nodejs/yarn.lock
new file mode 100644
index 00000000..aef54068
--- /dev/null
+++ b/binding/nodejs/yarn.lock
@@ -0,0 +1,3761 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+"@aashutoshrathi/word-wrap@^1.2.3":
+  version "1.2.6"
+  resolved "https://registry.yarnpkg.com/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz#bd9154aec9983f77b3a034ecaa015c2e4201f6cf"
+  integrity sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==
+
+"@ampproject/remapping@^2.2.0":
+  version "2.2.1"
+  resolved "https://registry.yarnpkg.com/@ampproject/remapping/-/remapping-2.2.1.tgz#99e8e11851128b8702cd57c33684f1d0f260b630"
+  integrity sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==
+  dependencies:
+    "@jridgewell/gen-mapping" "^0.3.0"
+    "@jridgewell/trace-mapping" "^0.3.9"
+
+"@babel/code-frame@^7.0.0", "@babel/code-frame@^7.12.13", "@babel/code-frame@^7.22.5":
+  version "7.22.5"
+  resolved "https://registry.yarnpkg.com/@babel/code-frame/-/code-frame-7.22.5.tgz#234d98e1551960604f1246e6475891a570ad5658"
+  integrity sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==
+  dependencies:
+    "@babel/highlight" "^7.22.5"
+
+"@babel/compat-data@^7.22.6":
+  version "7.22.6"
+  resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.22.6.tgz#15606a20341de59ba02cd2fcc5086fcbe73bf544"
+  integrity sha512-29tfsWTq2Ftu7MXmimyC0C5FDZv5DYxOZkh3XD3+QW4V/BYuv/LyEsjj3c0hqedEaDt6DBfDvexMKU8YevdqFg==
+
+"@babel/core@^7.1.0", "@babel/core@^7.12.3", "@babel/core@^7.7.2", "@babel/core@^7.8.0":
+  version "7.22.8"
+  resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.22.8.tgz#386470abe884302db9c82e8e5e87be9e46c86785"
+  integrity sha512-75+KxFB4CZqYRXjx4NlR4J7yGvKumBuZTmV4NV6v09dVXXkuYVYLT68N6HCzLvfJ+fWCxQsntNzKwwIXL4bHnw==
+  dependencies:
+    "@ampproject/remapping" "^2.2.0"
+    "@babel/code-frame" "^7.22.5"
+    "@babel/generator" "^7.22.7"
+    "@babel/helper-compilation-targets" "^7.22.6"
+    "@babel/helper-module-transforms" "^7.22.5"
+    "@babel/helpers" "^7.22.6"
+    "@babel/parser" "^7.22.7"
+    "@babel/template" "^7.22.5"
+    "@babel/traverse" "^7.22.8"
+    "@babel/types" "^7.22.5"
+    "@nicolo-ribaudo/semver-v6" "^6.3.3"
+    convert-source-map "^1.7.0"
+    debug "^4.1.0"
+    gensync "^1.0.0-beta.2"
+    json5 "^2.2.2"
+
+"@babel/generator@^7.22.7", "@babel/generator@^7.7.2":
+  version "7.22.7"
+  resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.22.7.tgz#a6b8152d5a621893f2c9dacf9a4e286d520633d5"
+  integrity sha512-p+jPjMG+SI8yvIaxGgeW24u7q9+5+TGpZh8/CuB7RhBKd7RCy8FayNEFNNKrNK/eUcY/4ExQqLmyrvBXKsIcwQ==
+  dependencies:
+    "@babel/types" "^7.22.5"
+    "@jridgewell/gen-mapping" "^0.3.2"
+    "@jridgewell/trace-mapping" "^0.3.17"
+    jsesc "^2.5.1"
+
+"@babel/helper-compilation-targets@^7.22.6":
+  version "7.22.6"
+  resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.22.6.tgz#e30d61abe9480aa5a83232eb31c111be922d2e52"
+  integrity sha512-534sYEqWD9VfUm3IPn2SLcH4Q3P86XL+QvqdC7ZsFrzyyPF3T4XGiVghF6PTYNdWg6pXuoqXxNQAhbYeEInTzA==
+  dependencies:
+    "@babel/compat-data" "^7.22.6"
+    "@babel/helper-validator-option" "^7.22.5"
+    "@nicolo-ribaudo/semver-v6" "^6.3.3"
+    browserslist "^4.21.9"
+    lru-cache "^5.1.1"
+
+"@babel/helper-environment-visitor@^7.22.5":
+  version "7.22.5"
+  resolved "https://registry.yarnpkg.com/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz#f06dd41b7c1f44e1f8da6c4055b41ab3a09a7e98"
+  integrity sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==
+
+"@babel/helper-function-name@^7.22.5":
+  version "7.22.5"
+  resolved "https://registry.yarnpkg.com/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz#ede300828905bb15e582c037162f99d5183af1be"
+  integrity sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==
+  dependencies:
+    "@babel/template" "^7.22.5"
+    "@babel/types" "^7.22.5"
+
+"@babel/helper-hoist-variables@^7.22.5":
+  version "7.22.5"
+  resolved "https://registry.yarnpkg.com/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz#c01a007dac05c085914e8fb652b339db50d823bb"
+  integrity sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==
+  dependencies:
+    "@babel/types" "^7.22.5"
+
+"@babel/helper-module-imports@^7.22.5":
+  version "7.22.5"
+  resolved "https://registry.yarnpkg.com/@babel/helper-module-imports/-/helper-module-imports-7.22.5.tgz#1a8f4c9f4027d23f520bd76b364d44434a72660c"
+  integrity sha512-8Dl6+HD/cKifutF5qGd/8ZJi84QeAKh+CEe1sBzz8UayBBGg1dAIJrdHOcOM5b2MpzWL2yuotJTtGjETq0qjXg==
+  dependencies:
+    "@babel/types" "^7.22.5"
+
+"@babel/helper-module-transforms@^7.22.5":
+  version "7.22.5"
+  resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.22.5.tgz#0f65daa0716961b6e96b164034e737f60a80d2ef"
+  integrity sha512-+hGKDt/Ze8GFExiVHno/2dvG5IdstpzCq0y4Qc9OJ25D4q3pKfiIP/4Vp3/JvhDkLKsDK2api3q3fpIgiIF5bw==
+  dependencies:
+    "@babel/helper-environment-visitor" "^7.22.5"
+    "@babel/helper-module-imports" "^7.22.5"
+    "@babel/helper-simple-access" "^7.22.5"
+    "@babel/helper-split-export-declaration" "^7.22.5"
+    "@babel/helper-validator-identifier" "^7.22.5"
+    "@babel/template" "^7.22.5"
+    "@babel/traverse" "^7.22.5"
+    "@babel/types" "^7.22.5"
+
+"@babel/helper-plugin-utils@^7.0.0", "@babel/helper-plugin-utils@^7.10.4", "@babel/helper-plugin-utils@^7.12.13", "@babel/helper-plugin-utils@^7.14.5", "@babel/helper-plugin-utils@^7.22.5", "@babel/helper-plugin-utils@^7.8.0":
+  version "7.22.5"
+  resolved "https://registry.yarnpkg.com/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz#dd7ee3735e8a313b9f7b05a773d892e88e6d7295"
+  integrity sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==
+
+"@babel/helper-simple-access@^7.22.5":
+  version "7.22.5"
+  resolved "https://registry.yarnpkg.com/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz#4938357dc7d782b80ed6dbb03a0fba3d22b1d5de"
+  integrity sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==
+  dependencies:
+    "@babel/types" "^7.22.5"
+
+"@babel/helper-split-export-declaration@^7.22.5", "@babel/helper-split-export-declaration@^7.22.6":
+  version "7.22.6"
+  resolved "https://registry.yarnpkg.com/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz#322c61b7310c0997fe4c323955667f18fcefb91c"
+  integrity sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==
+  dependencies:
+    "@babel/types" "^7.22.5"
+
+"@babel/helper-string-parser@^7.22.5":
+  version "7.22.5"
+  resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.22.5.tgz#533f36457a25814cf1df6488523ad547d784a99f"
+  integrity sha512-mM4COjgZox8U+JcXQwPijIZLElkgEpO5rsERVDJTc2qfCDfERyob6k5WegS14SX18IIjv+XD+GrqNumY5JRCDw==
+
+"@babel/helper-validator-identifier@^7.22.5":
+  version "7.22.5"
+  resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz#9544ef6a33999343c8740fa51350f30eeaaaf193"
+  integrity sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==
+
+"@babel/helper-validator-option@^7.22.5":
+  version "7.22.5"
+  resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.22.5.tgz#de52000a15a177413c8234fa3a8af4ee8102d0ac"
+  integrity sha512-R3oB6xlIVKUnxNUxbmgq7pKjxpru24zlimpE8WK47fACIlM0II/Hm1RS8IaOI7NgCr6LNS+jl5l75m20npAziw==
+
+"@babel/helpers@^7.22.6":
+  version "7.22.6"
+  resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.22.6.tgz#8e61d3395a4f0c5a8060f309fb008200969b5ecd"
+  integrity sha512-YjDs6y/fVOYFV8hAf1rxd1QvR9wJe1pDBZ2AREKq/SDayfPzgk0PBnVuTCE5X1acEpMMNOVUqoe+OwiZGJ+OaA==
+  dependencies:
+    "@babel/template" "^7.22.5"
+    "@babel/traverse" "^7.22.6"
+    "@babel/types" "^7.22.5"
+
+"@babel/highlight@^7.22.5":
+  version "7.22.5"
+  resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.22.5.tgz#aa6c05c5407a67ebce408162b7ede789b4d22031"
+  integrity sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==
+  dependencies:
+    "@babel/helper-validator-identifier" "^7.22.5"
+    chalk "^2.0.0"
+    js-tokens "^4.0.0"
+
+"@babel/parser@^7.1.0", "@babel/parser@^7.14.7", "@babel/parser@^7.20.7", "@babel/parser@^7.22.5", "@babel/parser@^7.22.7":
+  version "7.22.7"
+  resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.22.7.tgz#df8cf085ce92ddbdbf668a7f186ce848c9036cae"
+  integrity sha512-7NF8pOkHP5o2vpmGgNGcfAeCvOYhGLyA3Z4eBQkT1RJlWu47n63bCs93QfJ2hIAFCil7L5P2IWhs1oToVgrL0Q==
+
+"@babel/plugin-syntax-async-generators@^7.8.4":
+  version "7.8.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz#a983fb1aeb2ec3f6ed042a210f640e90e786fe0d"
+  integrity sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.8.0"
+
+"@babel/plugin-syntax-bigint@^7.8.3":
+  version "7.8.3"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-bigint/-/plugin-syntax-bigint-7.8.3.tgz#4c9a6f669f5d0cdf1b90a1671e9a146be5300cea"
+  integrity sha512-wnTnFlG+YxQm3vDxpGE57Pj0srRU4sHE/mDkt1qv2YJJSeUAec2ma4WLUnUPeKjyrfntVwe/N6dCXpU+zL3Npg==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.8.0"
+
+"@babel/plugin-syntax-class-properties@^7.8.3":
+  version "7.12.13"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz#b5c987274c4a3a82b89714796931a6b53544ae10"
+  integrity sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.12.13"
+
+"@babel/plugin-syntax-import-meta@^7.8.3":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51"
+  integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.10.4"
+
+"@babel/plugin-syntax-json-strings@^7.8.3":
+  version "7.8.3"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a"
+  integrity sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.8.0"
+
+"@babel/plugin-syntax-logical-assignment-operators@^7.8.3":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz#ca91ef46303530448b906652bac2e9fe9941f699"
+  integrity sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.10.4"
+
+"@babel/plugin-syntax-nullish-coalescing-operator@^7.8.3":
+  version "7.8.3"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz#167ed70368886081f74b5c36c65a88c03b66d1a9"
+  integrity sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.8.0"
+
+"@babel/plugin-syntax-numeric-separator@^7.8.3":
+  version "7.10.4"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz#b9b070b3e33570cd9fd07ba7fa91c0dd37b9af97"
+  integrity sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.10.4"
+
+"@babel/plugin-syntax-object-rest-spread@^7.8.3":
+  version "7.8.3"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz#60e225edcbd98a640332a2e72dd3e66f1af55871"
+  integrity sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.8.0"
+
+"@babel/plugin-syntax-optional-catch-binding@^7.8.3":
+  version "7.8.3"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz#6111a265bcfb020eb9efd0fdfd7d26402b9ed6c1"
+  integrity sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.8.0"
+
+"@babel/plugin-syntax-optional-chaining@^7.8.3":
+  version "7.8.3"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz#4f69c2ab95167e0180cd5336613f8c5788f7d48a"
+  integrity sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.8.0"
+
+"@babel/plugin-syntax-top-level-await@^7.8.3":
+  version "7.14.5"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.14.5.tgz#c1cfdadc35a646240001f06138247b741c34d94c"
+  integrity sha512-hx++upLv5U1rgYfwe1xBQUhRmU41NEvpUvrp8jkrSCdvGSnM5/qdRMtylJ6PG5OFkBaHkbTAKTnd3/YyESRHFw==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.14.5"
+
+"@babel/plugin-syntax-typescript@^7.7.2":
+  version "7.22.5"
+  resolved "https://registry.yarnpkg.com/@babel/plugin-syntax-typescript/-/plugin-syntax-typescript-7.22.5.tgz#aac8d383b062c5072c647a31ef990c1d0af90272"
+  integrity sha512-1mS2o03i7t1c6VzH6fdQ3OA8tcEIxwG18zIPRp+UY1Ihv6W+XZzBCVxExF9upussPXJ0xE9XRHwMoNs1ep/nRQ==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.22.5"
+
+"@babel/template@^7.22.5", "@babel/template@^7.3.3":
+  version "7.22.5"
+  resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.22.5.tgz#0c8c4d944509875849bd0344ff0050756eefc6ec"
+  integrity sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==
+  dependencies:
+    "@babel/code-frame" "^7.22.5"
+    "@babel/parser" "^7.22.5"
+    "@babel/types" "^7.22.5"
+
+"@babel/traverse@^7.22.5", "@babel/traverse@^7.22.6", "@babel/traverse@^7.22.8", "@babel/traverse@^7.7.2":
+  version "7.22.8"
+  resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.22.8.tgz#4d4451d31bc34efeae01eac222b514a77aa4000e"
+  integrity sha512-y6LPR+wpM2I3qJrsheCTwhIinzkETbplIgPBbwvqPKc+uljeA5gP+3nP8irdYt1mjQaDnlIcG+dw8OjAco4GXw==
+  dependencies:
+    "@babel/code-frame" "^7.22.5"
+    "@babel/generator" "^7.22.7"
+    "@babel/helper-environment-visitor" "^7.22.5"
+    "@babel/helper-function-name" "^7.22.5"
+    "@babel/helper-hoist-variables" "^7.22.5"
+    "@babel/helper-split-export-declaration" "^7.22.6"
+    "@babel/parser" "^7.22.7"
+    "@babel/types" "^7.22.5"
+    debug "^4.1.0"
+    globals "^11.1.0"
+
+"@babel/types@^7.0.0", "@babel/types@^7.20.7", "@babel/types@^7.22.5", "@babel/types@^7.3.3":
+  version "7.22.5"
+  resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.22.5.tgz#cd93eeaab025880a3a47ec881f4b096a5b786fbe"
+  integrity sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA==
+  dependencies:
+    "@babel/helper-string-parser" "^7.22.5"
+    "@babel/helper-validator-identifier" "^7.22.5"
+    to-fast-properties "^2.0.0"
+
+"@bcoe/v8-coverage@^0.2.3":
+  version "0.2.3"
+  resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39"
+  integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==
+
+"@eslint-community/eslint-utils@^4.2.0":
+  version "4.4.0"
+  resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59"
+  integrity sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==
+  dependencies:
+    eslint-visitor-keys "^3.3.0"
+
+"@eslint-community/regexpp@^4.4.0":
+  version "4.5.1"
+  resolved "https://registry.yarnpkg.com/@eslint-community/regexpp/-/regexpp-4.5.1.tgz#cdd35dce4fa1a89a4fd42b1599eb35b3af408884"
+  integrity sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ==
+
+"@eslint/eslintrc@^2.1.0":
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-2.1.0.tgz#82256f164cc9e0b59669efc19d57f8092706841d"
+  integrity sha512-Lj7DECXqIVCqnqjjHMPna4vn6GJcMgul/wuS0je9OZ9gsL0zzDpKPVtcG1HaDVc+9y+qgXneTeUMbCqXJNpH1A==
+  dependencies:
+    ajv "^6.12.4"
+    debug "^4.3.2"
+    espree "^9.6.0"
+    globals "^13.19.0"
+    ignore "^5.2.0"
+    import-fresh "^3.2.1"
+    js-yaml "^4.1.0"
+    minimatch "^3.1.2"
+    strip-json-comments "^3.1.1"
+
+"@eslint/js@8.44.0":
+  version "8.44.0"
+  resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.44.0.tgz#961a5903c74139390478bdc808bcde3fc45ab7af"
+  integrity sha512-Ag+9YM4ocKQx9AarydN0KY2j0ErMHNIocPDrVo8zAE44xLTjEtz81OdR68/cydGtk6m6jDb5Za3r2useMzYmSw==
+
+"@humanwhocodes/config-array@^0.11.10":
+  version "0.11.10"
+  resolved "https://registry.yarnpkg.com/@humanwhocodes/config-array/-/config-array-0.11.10.tgz#5a3ffe32cc9306365fb3fd572596cd602d5e12d2"
+  integrity sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==
+  dependencies:
+    "@humanwhocodes/object-schema" "^1.2.1"
+    debug "^4.1.1"
+    minimatch "^3.0.5"
+
+"@humanwhocodes/module-importer@^1.0.1":
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz#af5b2691a22b44be847b0ca81641c5fb6ad0172c"
+  integrity sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==
+
+"@humanwhocodes/object-schema@^1.2.1":
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45"
+  integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==
+
+"@istanbuljs/load-nyc-config@^1.0.0":
+  version "1.1.0"
+  resolved "https://registry.yarnpkg.com/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz#fd3db1d59ecf7cf121e80650bb86712f9b55eced"
+  integrity sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==
+  dependencies:
+    camelcase "^5.3.1"
+    find-up "^4.1.0"
+    get-package-type "^0.1.0"
+    js-yaml "^3.13.1"
+    resolve-from "^5.0.0"
+
+"@istanbuljs/schema@^0.1.2":
+  version "0.1.3"
+  resolved "https://registry.yarnpkg.com/@istanbuljs/schema/-/schema-0.1.3.tgz#e45e384e4b8ec16bce2fd903af78450f6bf7ec98"
+  integrity sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==
+
+"@jest/console@^27.5.1":
+  version "27.5.1"
+  resolved "https://registry.yarnpkg.com/@jest/console/-/console-27.5.1.tgz#260fe7239602fe5130a94f1aa386eff54b014bba"
+  integrity sha512-kZ/tNpS3NXn0mlXXXPNuDZnb4c0oZ20r4K5eemM2k30ZC3G0T02nXUvyhf5YdbXWHPEJLc9qGLxEZ216MdL+Zg==
+  dependencies:
+    "@jest/types" "^27.5.1"
+    "@types/node" "*"
+    chalk "^4.0.0"
+    jest-message-util "^27.5.1"
+    jest-util "^27.5.1"
+    slash "^3.0.0"
+
+"@jest/core@^27.5.1":
+  version "27.5.1"
+  resolved "https://registry.yarnpkg.com/@jest/core/-/core-27.5.1.tgz#267ac5f704e09dc52de2922cbf3af9edcd64b626"
+  integrity sha512-AK6/UTrvQD0Cd24NSqmIA6rKsu0tKIxfiCducZvqxYdmMisOYAsdItspT+fQDQYARPf8XgjAFZi0ogW2agH5nQ==
+  dependencies:
+    "@jest/console" "^27.5.1"
+    "@jest/reporters" "^27.5.1"
+    "@jest/test-result" "^27.5.1"
+    "@jest/transform" "^27.5.1"
+    "@jest/types" "^27.5.1"
+    "@types/node" "*"
+    ansi-escapes "^4.2.1"
+    chalk "^4.0.0"
+    emittery "^0.8.1"
+    exit "^0.1.2"
+    graceful-fs "^4.2.9"
+    jest-changed-files "^27.5.1"
+    jest-config "^27.5.1"
+    jest-haste-map "^27.5.1"
+    jest-message-util "^27.5.1"
+    jest-regex-util "^27.5.1"
+    jest-resolve "^27.5.1"
+    jest-resolve-dependencies "^27.5.1"
+    jest-runner "^27.5.1"
+    jest-runtime "^27.5.1"
+    jest-snapshot "^27.5.1"
+    jest-util "^27.5.1"
+    jest-validate "^27.5.1"
+    jest-watcher "^27.5.1"
+    micromatch "^4.0.4"
+    rimraf "^3.0.0"
+    slash "^3.0.0"
+    strip-ansi "^6.0.0"
+
+"@jest/environment@^27.5.1":
+  version "27.5.1"
+  resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-27.5.1.tgz#d7425820511fe7158abbecc010140c3fd3be9c74"
+  integrity sha512-/WQjhPJe3/ghaol/4Bq480JKXV/Rfw8nQdN7f41fM8VDHLcxKXou6QyXAh3EFr9/bVG3x74z1NWDkP87EiY8gA==
+  dependencies:
+    "@jest/fake-timers" "^27.5.1"
+    "@jest/types" "^27.5.1"
+    "@types/node" "*"
+    jest-mock "^27.5.1"
+
+"@jest/fake-timers@^27.5.1":
+  version "27.5.1"
+  resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-27.5.1.tgz#76979745ce0579c8a94a4678af7a748eda8ada74"
+  integrity sha512-/aPowoolwa07k7/oM3aASneNeBGCmGQsc3ugN4u6s4C/+s5M64MFo/+djTdiwcbQlRfFElGuDXWzaWj6QgKObQ==
+  dependencies:
+    "@jest/types" "^27.5.1"
+    "@sinonjs/fake-timers" "^8.0.1"
+    "@types/node" "*"
+    jest-message-util "^27.5.1"
+    jest-mock "^27.5.1"
+    jest-util "^27.5.1"
+
+"@jest/globals@^27.5.1":
+  version "27.5.1"
+  resolved "https://registry.yarnpkg.com/@jest/globals/-/globals-27.5.1.tgz#7ac06ce57ab966566c7963431cef458434601b2b"
+  integrity sha512-ZEJNB41OBQQgGzgyInAv0UUfDDj3upmHydjieSxFvTRuZElrx7tXg/uVQ5hYVEwiXs3+aMsAeEc9X7xiSKCm4Q==
+  dependencies:
+    "@jest/environment" "^27.5.1"
+    "@jest/types" "^27.5.1"
+    expect "^27.5.1"
+
+"@jest/reporters@^27.5.1":
+  version "27.5.1"
+  resolved "https://registry.yarnpkg.com/@jest/reporters/-/reporters-27.5.1.tgz#ceda7be96170b03c923c37987b64015812ffec04"
+  integrity sha512-cPXh9hWIlVJMQkVk84aIvXuBB4uQQmFqZiacloFuGiP3ah1sbCxCosidXFDfqG8+6fO1oR2dTJTlsOy4VFmUfw==
+  dependencies:
+    "@bcoe/v8-coverage" "^0.2.3"
+    "@jest/console" "^27.5.1"
+    "@jest/test-result" "^27.5.1"
+    "@jest/transform" "^27.5.1"
+    "@jest/types" "^27.5.1"
+    "@types/node" "*"
+    chalk "^4.0.0"
+    collect-v8-coverage "^1.0.0"
+    exit "^0.1.2"
+    glob "^7.1.2"
+    graceful-fs "^4.2.9"
+    istanbul-lib-coverage "^3.0.0"
+    istanbul-lib-instrument "^5.1.0"
+    istanbul-lib-report "^3.0.0"
+    istanbul-lib-source-maps "^4.0.0"
+    istanbul-reports "^3.1.3"
+    jest-haste-map "^27.5.1"
+    jest-resolve "^27.5.1"
+    jest-util "^27.5.1"
+    jest-worker "^27.5.1"
+    slash "^3.0.0"
+    source-map "^0.6.0"
+    string-length "^4.0.1"
+    terminal-link "^2.0.0"
+    v8-to-istanbul "^8.1.0"
+
+"@jest/source-map@^27.5.1":
+  version "27.5.1"
+  resolved "https://registry.yarnpkg.com/@jest/source-map/-/source-map-27.5.1.tgz#6608391e465add4205eae073b55e7f279e04e8cf"
+  integrity sha512-y9NIHUYF3PJRlHk98NdC/N1gl88BL08aQQgu4k4ZopQkCw9t9cV8mtl3TV8b/YCB8XaVTFrmUTAJvjsntDireg==
+  dependencies:
+    callsites "^3.0.0"
+    graceful-fs "^4.2.9"
+    source-map "^0.6.0"
+
+"@jest/test-result@^27.5.1":
+  version "27.5.1"
+  resolved "https://registry.yarnpkg.com/@jest/test-result/-/test-result-27.5.1.tgz#56a6585fa80f7cdab72b8c5fc2e871d03832f5bb"
+  integrity sha512-EW35l2RYFUcUQxFJz5Cv5MTOxlJIQs4I7gxzi2zVU7PJhOwfYq1MdC5nhSmYjX1gmMmLPvB3sIaC+BkcHRBfag==
+  dependencies:
+    "@jest/console" "^27.5.1"
+    "@jest/types" "^27.5.1"
+    "@types/istanbul-lib-coverage" "^2.0.0"
+    collect-v8-coverage "^1.0.0"
+
+"@jest/test-sequencer@^27.5.1":
+  version "27.5.1"
+  resolved "https://registry.yarnpkg.com/@jest/test-sequencer/-/test-sequencer-27.5.1.tgz#4057e0e9cea4439e544c6353c6affe58d095745b"
+  integrity sha512-LCheJF7WB2+9JuCS7VB/EmGIdQuhtqjRNI9A43idHv3E4KltCTsPsLxvdaubFHSYwY/fNjMWjl6vNRhDiN7vpQ==
+  dependencies:
+    "@jest/test-result" "^27.5.1"
+    graceful-fs "^4.2.9"
+    jest-haste-map "^27.5.1"
+    jest-runtime "^27.5.1"
+
+"@jest/transform@^27.5.1":
+  version "27.5.1"
+  resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-27.5.1.tgz#6c3501dcc00c4c08915f292a600ece5ecfe1f409"
+  integrity sha512-ipON6WtYgl/1329g5AIJVbUuEh0wZVbdpGwC99Jw4LwuoBNS95MVphU6zOeD9pDkon+LLbFL7lOQRapbB8SCHw==
+  dependencies:
+    "@babel/core" "^7.1.0"
+    "@jest/types" "^27.5.1"
+    babel-plugin-istanbul "^6.1.1"
+    chalk "^4.0.0"
+    convert-source-map "^1.4.0"
+    fast-json-stable-stringify "^2.0.0"
+    graceful-fs "^4.2.9"
+    jest-haste-map "^27.5.1"
+    jest-regex-util "^27.5.1"
+    jest-util "^27.5.1"
+    micromatch "^4.0.4"
+    pirates "^4.0.4"
+    slash "^3.0.0"
+    source-map "^0.6.1"
+    write-file-atomic "^3.0.0"
+
+"@jest/types@^27.5.1":
+  version "27.5.1"
+  resolved "https://registry.yarnpkg.com/@jest/types/-/types-27.5.1.tgz#3c79ec4a8ba61c170bf937bcf9e98a9df175ec80"
+  integrity sha512-Cx46iJ9QpwQTjIdq5VJu2QTMMs3QlEjI0x1QbBP5W1+nMzyc2XmimiRR/CbX9TO0cPTeUlxWMOu8mslYsJ8DEw==
+  dependencies:
+    "@types/istanbul-lib-coverage" "^2.0.0"
+    "@types/istanbul-reports" "^3.0.0"
+    "@types/node" "*"
+    "@types/yargs" "^16.0.0"
+    chalk "^4.0.0"
+
+"@jridgewell/gen-mapping@^0.3.0", "@jridgewell/gen-mapping@^0.3.2":
+  version "0.3.3"
+  resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz#7e02e6eb5df901aaedb08514203b096614024098"
+  integrity sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==
+  dependencies:
+    "@jridgewell/set-array" "^1.0.1"
+    "@jridgewell/sourcemap-codec" "^1.4.10"
+    "@jridgewell/trace-mapping" "^0.3.9"
+
+"@jridgewell/resolve-uri@3.1.0":
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78"
+  integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==
+
+"@jridgewell/set-array@^1.0.1":
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/@jridgewell/set-array/-/set-array-1.1.2.tgz#7c6cf998d6d20b914c0a55a91ae928ff25965e72"
+  integrity sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==
+
+"@jridgewell/sourcemap-codec@1.4.14":
+  version "1.4.14"
+  resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24"
+  integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==
+
+"@jridgewell/sourcemap-codec@^1.4.10":
+  version "1.4.15"
+  resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32"
+  integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==
+
+"@jridgewell/trace-mapping@^0.3.17", "@jridgewell/trace-mapping@^0.3.9":
+  version "0.3.18"
+  resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz#25783b2086daf6ff1dcb53c9249ae480e4dd4cd6"
+  integrity sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==
+  dependencies:
+    "@jridgewell/resolve-uri" "3.1.0"
+    "@jridgewell/sourcemap-codec" "1.4.14"
+
+"@nicolo-ribaudo/semver-v6@^6.3.3":
+  version "6.3.3"
+  resolved "https://registry.yarnpkg.com/@nicolo-ribaudo/semver-v6/-/semver-v6-6.3.3.tgz#ea6d23ade78a325f7a52750aab1526b02b628c29"
+  integrity sha512-3Yc1fUTs69MG/uZbJlLSI3JISMn2UV2rg+1D/vROUqZyh3l6iYHCs7GMp+M40ZD7yOdDbYjJcU1oTJhrc+dGKg==
+
+"@nodelib/fs.scandir@2.1.5":
+  version "2.1.5"
+  resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5"
+  integrity sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==
+  dependencies:
+    "@nodelib/fs.stat" "2.0.5"
+    run-parallel "^1.1.9"
+
+"@nodelib/fs.stat@2.0.5", "@nodelib/fs.stat@^2.0.2":
+  version "2.0.5"
+  resolved "https://registry.yarnpkg.com/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz#5bd262af94e9d25bd1e71b05deed44876a222e8b"
+  integrity sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==
+
+"@nodelib/fs.walk@^1.2.3", "@nodelib/fs.walk@^1.2.8":
+  version "1.2.8"
+  resolved "https://registry.yarnpkg.com/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz#e95737e8bb6746ddedf69c556953494f196fe69a"
+  integrity sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==
+  dependencies:
+    "@nodelib/fs.scandir" "2.1.5"
+    fastq "^1.6.0"
+
+"@sinonjs/commons@^1.7.0":
+  version "1.8.6"
+  resolved "https://registry.yarnpkg.com/@sinonjs/commons/-/commons-1.8.6.tgz#80c516a4dc264c2a69115e7578d62581ff455ed9"
+  integrity sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==
+  dependencies:
+    type-detect "4.0.8"
+
+"@sinonjs/fake-timers@^8.0.1":
+  version "8.1.0"
+  resolved "https://registry.yarnpkg.com/@sinonjs/fake-timers/-/fake-timers-8.1.0.tgz#3fdc2b6cb58935b21bfb8d1625eb1300484316e7"
+  integrity sha512-OAPJUAtgeINhh/TAlUID4QTs53Njm7xzddaVlEs/SXwgtiD1tW22zAB/W1wdqfrpmikgaWQ9Fw6Ws+hsiRm5Vg==
+  dependencies:
+    "@sinonjs/commons" "^1.7.0"
+
+"@tootallnate/once@1":
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82"
+  integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==
+
+"@types/babel__core@^7.0.0", "@types/babel__core@^7.1.14":
+  version "7.20.1"
+  resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.20.1.tgz#916ecea274b0c776fec721e333e55762d3a9614b"
+  integrity sha512-aACu/U/omhdk15O4Nfb+fHgH/z3QsfQzpnvRZhYhThms83ZnAOZz7zZAWO7mn2yyNQaA4xTO8GLK3uqFU4bYYw==
+  dependencies:
+    "@babel/parser" "^7.20.7"
+    "@babel/types" "^7.20.7"
+    "@types/babel__generator" "*"
+    "@types/babel__template" "*"
+    "@types/babel__traverse" "*"
+
+"@types/babel__generator@*":
+  version "7.6.4"
+  resolved "https://registry.yarnpkg.com/@types/babel__generator/-/babel__generator-7.6.4.tgz#1f20ce4c5b1990b37900b63f050182d28c2439b7"
+  integrity sha512-tFkciB9j2K755yrTALxD44McOrk+gfpIpvC3sxHjRawj6PfnQxrse4Clq5y/Rq+G3mrBurMax/lG8Qn2t9mSsg==
+  dependencies:
+    "@babel/types" "^7.0.0"
+
+"@types/babel__template@*":
+  version "7.4.1"
+  resolved "https://registry.yarnpkg.com/@types/babel__template/-/babel__template-7.4.1.tgz#3d1a48fd9d6c0edfd56f2ff578daed48f36c8969"
+  integrity sha512-azBFKemX6kMg5Io+/rdGT0dkGreboUVR0Cdm3fz9QJWpaQGJRQXl7C+6hOTCZcMll7KFyEQpgbYI2lHdsS4U7g==
+  dependencies:
+    "@babel/parser" "^7.1.0"
+    "@babel/types" "^7.0.0"
+
+"@types/babel__traverse@*", "@types/babel__traverse@^7.0.4", "@types/babel__traverse@^7.0.6":
+  version "7.20.1"
+  resolved "https://registry.yarnpkg.com/@types/babel__traverse/-/babel__traverse-7.20.1.tgz#dd6f1d2411ae677dcb2db008c962598be31d6acf"
+  integrity sha512-MitHFXnhtgwsGZWtT68URpOvLN4EREih1u3QtQiN4VdAxWKRVvGCSvw/Qth0M0Qq3pJpnGOu5JaM/ydK7OGbqg==
+  dependencies:
+    "@babel/types" "^7.20.7"
+
+"@types/graceful-fs@^4.1.2":
+  version "4.1.6"
+  resolved "https://registry.yarnpkg.com/@types/graceful-fs/-/graceful-fs-4.1.6.tgz#e14b2576a1c25026b7f02ede1de3b84c3a1efeae"
+  integrity sha512-Sig0SNORX9fdW+bQuTEovKj3uHcUL6LQKbCrrqb1X7J6/ReAbhCXRAhc+SMejhLELFj2QcyuxmUooZ4bt5ReSw==
+  dependencies:
+    "@types/node" "*"
+
+"@types/istanbul-lib-coverage@*", "@types/istanbul-lib-coverage@^2.0.0", "@types/istanbul-lib-coverage@^2.0.1":
+  version "2.0.4"
+  resolved "https://registry.yarnpkg.com/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.4.tgz#8467d4b3c087805d63580480890791277ce35c44"
+  integrity sha512-z/QT1XN4K4KYuslS23k62yDIDLwLFkzxOuMplDtObz0+y7VqJCaO2o+SPwHCvLFZh7xazvvoor2tA/hPz9ee7g==
+
+"@types/istanbul-lib-report@*":
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#c14c24f18ea8190c118ee7562b7ff99a36552686"
+  integrity sha512-plGgXAPfVKFoYfa9NpYDAkseG+g6Jr294RqeqcqDixSbU34MZVJRi/P+7Y8GDpzkEwLaGZZOpKIEmeVZNtKsrg==
+  dependencies:
+    "@types/istanbul-lib-coverage" "*"
+
+"@types/istanbul-reports@^3.0.0":
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/@types/istanbul-reports/-/istanbul-reports-3.0.1.tgz#9153fe98bba2bd565a63add9436d6f0d7f8468ff"
+  integrity sha512-c3mAZEuK0lvBp8tmuL74XRKn1+y2dcwOUpH7x4WrF6gk1GIgiluDRgMYQtw2OFcBvAJWlt6ASU3tSqxp0Uu0Aw==
+  dependencies:
+    "@types/istanbul-lib-report" "*"
+
+"@types/jest@^27.4.1":
+  version "27.5.2"
+  resolved "https://registry.yarnpkg.com/@types/jest/-/jest-27.5.2.tgz#ec49d29d926500ffb9fd22b84262e862049c026c"
+  integrity sha512-mpT8LJJ4CMeeahobofYWIjFo0xonRS/HfxnVEPMPFSQdGUt1uHCnoPT7Zhb+sjDU2wz0oKV0OLUR0WzrHNgfeA==
+  dependencies:
+    jest-matcher-utils "^27.0.0"
+    pretty-format "^27.0.0"
+
+"@types/json-schema@^7.0.9":
+  version "7.0.12"
+  resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.12.tgz#d70faba7039d5fca54c83c7dbab41051d2b6f6cb"
+  integrity sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==
+
+"@types/node@*":
+  version "20.4.1"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-20.4.1.tgz#a6033a8718653c50ac4962977e14d0f984d9527d"
+  integrity sha512-JIzsAvJeA/5iY6Y/OxZbv1lUcc8dNSE77lb2gnBH+/PJ3lFR1Ccvgwl5JWnHAkNHcRsT0TbpVOsiMKZ1F/yyJg==
+
+"@types/node@^17.0.21":
+  version "17.0.45"
+  resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.45.tgz#2c0fafd78705e7a18b7906b5201a522719dc5190"
+  integrity sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==
+
+"@types/prettier@^2.1.5":
+  version "2.7.3"
+  resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-2.7.3.tgz#3e51a17e291d01d17d3fc61422015a933af7a08f"
+  integrity sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==
+
+"@types/semver@^7.3.12":
+  version "7.5.0"
+  resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.0.tgz#591c1ce3a702c45ee15f47a42ade72c2fd78978a"
+  integrity sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==
+
+"@types/stack-utils@^2.0.0":
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.1.tgz#20f18294f797f2209b5f65c8e3b5c8e8261d127c"
+  integrity sha512-Hl219/BT5fLAaz6NDkSuhzasy49dwQS/DSdu4MdggFB8zcXv7vflBI3xp7FEmkmdDkBUI2bPUNeMttp2knYdxw==
+
+"@types/yargs-parser@*":
+  version "21.0.0"
+  resolved "https://registry.yarnpkg.com/@types/yargs-parser/-/yargs-parser-21.0.0.tgz#0c60e537fa790f5f9472ed2776c2b71ec117351b"
+  integrity sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==
+
+"@types/yargs@^16.0.0":
+  version "16.0.5"
+  resolved "https://registry.yarnpkg.com/@types/yargs/-/yargs-16.0.5.tgz#12cc86393985735a283e387936398c2f9e5f88e3"
+  integrity sha512-AxO/ADJOBFJScHbWhq2xAhlWP24rY4aCEG/NFaMvbT3X2MgRsLjhjQwsn0Zi5zn0LG9jUhCCZMeX9Dkuw6k+vQ==
+  dependencies:
+    "@types/yargs-parser" "*"
+
+"@typescript-eslint/eslint-plugin@^5.19.0":
+  version "5.62.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz#aeef0328d172b9e37d9bab6dbc13b87ed88977db"
+  integrity sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==
+  dependencies:
+    "@eslint-community/regexpp" "^4.4.0"
+    "@typescript-eslint/scope-manager" "5.62.0"
+    "@typescript-eslint/type-utils" "5.62.0"
+    "@typescript-eslint/utils" "5.62.0"
+    debug "^4.3.4"
+    graphemer "^1.4.0"
+    ignore "^5.2.0"
+    natural-compare-lite "^1.4.0"
+    semver "^7.3.7"
+    tsutils "^3.21.0"
+
+"@typescript-eslint/parser@^5.19.0":
+  version "5.62.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/parser/-/parser-5.62.0.tgz#1b63d082d849a2fcae8a569248fbe2ee1b8a56c7"
+  integrity sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==
+  dependencies:
+    "@typescript-eslint/scope-manager" "5.62.0"
+    "@typescript-eslint/types" "5.62.0"
+    "@typescript-eslint/typescript-estree" "5.62.0"
+    debug "^4.3.4"
+
+"@typescript-eslint/scope-manager@5.62.0":
+  version "5.62.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz#d9457ccc6a0b8d6b37d0eb252a23022478c5460c"
+  integrity sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==
+  dependencies:
+    "@typescript-eslint/types" "5.62.0"
+    "@typescript-eslint/visitor-keys" "5.62.0"
+
+"@typescript-eslint/type-utils@5.62.0":
+  version "5.62.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz#286f0389c41681376cdad96b309cedd17d70346a"
+  integrity sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==
+  dependencies:
+    "@typescript-eslint/typescript-estree" "5.62.0"
+    "@typescript-eslint/utils" "5.62.0"
+    debug "^4.3.4"
+    tsutils "^3.21.0"
+
+"@typescript-eslint/types@5.62.0":
+  version "5.62.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-5.62.0.tgz#258607e60effa309f067608931c3df6fed41fd2f"
+  integrity sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==
+
+"@typescript-eslint/typescript-estree@5.62.0":
+  version "5.62.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz#7d17794b77fabcac615d6a48fb143330d962eb9b"
+  integrity sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==
+  dependencies:
+    "@typescript-eslint/types" "5.62.0"
+    "@typescript-eslint/visitor-keys" "5.62.0"
+    debug "^4.3.4"
+    globby "^11.1.0"
+    is-glob "^4.0.3"
+    semver "^7.3.7"
+    tsutils "^3.21.0"
+
+"@typescript-eslint/utils@5.62.0", "@typescript-eslint/utils@^5.10.0":
+  version "5.62.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-5.62.0.tgz#141e809c71636e4a75daa39faed2fb5f4b10df86"
+  integrity sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==
+  dependencies:
+    "@eslint-community/eslint-utils" "^4.2.0"
+    "@types/json-schema" "^7.0.9"
+    "@types/semver" "^7.3.12"
+    "@typescript-eslint/scope-manager" "5.62.0"
+    "@typescript-eslint/types" "5.62.0"
+    "@typescript-eslint/typescript-estree" "5.62.0"
+    eslint-scope "^5.1.1"
+    semver "^7.3.7"
+
+"@typescript-eslint/visitor-keys@5.62.0":
+  version "5.62.0"
+  resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz#2174011917ce582875954ffe2f6912d5931e353e"
+  integrity sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==
+  dependencies:
+    "@typescript-eslint/types" "5.62.0"
+    eslint-visitor-keys "^3.3.0"
+
+abab@^2.0.3, abab@^2.0.5:
+  version "2.0.6"
+  resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291"
+  integrity sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==
+
+acorn-globals@^6.0.0:
+  version "6.0.0"
+  resolved "https://registry.yarnpkg.com/acorn-globals/-/acorn-globals-6.0.0.tgz#46cdd39f0f8ff08a876619b55f5ac8a6dc770b45"
+  integrity sha512-ZQl7LOWaF5ePqqcX4hLuv/bLXYQNfNWw2c0/yX/TsPRKamzHcTGQnlCjHT3TsmkOUVEPS3crCxiPfdzE/Trlhg==
+  dependencies:
+    acorn "^7.1.1"
+    acorn-walk "^7.1.1"
+
+acorn-jsx@^5.3.2:
+  version "5.3.2"
+  resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937"
+  integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==
+
+acorn-walk@^7.1.1:
+  version "7.2.0"
+  resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-7.2.0.tgz#0de889a601203909b0fbe07b8938dc21d2e967bc"
+  integrity sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==
+
+acorn@^7.1.1:
+  version "7.4.1"
+  resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa"
+  integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==
+
+acorn@^8.2.4, acorn@^8.9.0:
+  version "8.10.0"
+  resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.10.0.tgz#8be5b3907a67221a81ab23c7889c4c5526b62ec5"
+  integrity sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==
+
+agent-base@6:
+  version "6.0.2"
+  resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77"
+  integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==
+  dependencies:
+    debug "4"
+
+ajv@^6.10.0, ajv@^6.12.4:
+  version "6.12.6"
+  resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
+  integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
+  dependencies:
+    fast-deep-equal "^3.1.1"
+    fast-json-stable-stringify "^2.0.0"
+    json-schema-traverse "^0.4.1"
+    uri-js "^4.2.2"
+
+ansi-escapes@^4.2.1:
+  version "4.3.2"
+  resolved "https://registry.yarnpkg.com/ansi-escapes/-/ansi-escapes-4.3.2.tgz#6b2291d1db7d98b6521d5f1efa42d0f3a9feb65e"
+  integrity sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==
+  dependencies:
+    type-fest "^0.21.3"
+
+ansi-regex@^5.0.1:
+  version "5.0.1"
+  resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
+  integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==
+
+ansi-styles@^3.2.1:
+  version "3.2.1"
+  resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
+  integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==
+  dependencies:
+    color-convert "^1.9.0"
+
+ansi-styles@^4.0.0, ansi-styles@^4.1.0:
+  version "4.3.0"
+  resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937"
+  integrity sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==
+  dependencies:
+    color-convert "^2.0.1"
+
+ansi-styles@^5.0.0:
+  version "5.2.0"
+  resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b"
+  integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==
+
+anymatch@^3.0.3:
+  version "3.1.3"
+  resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.3.tgz#790c58b19ba1720a84205b57c618d5ad8524973e"
+  integrity sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==
+  dependencies:
+    normalize-path "^3.0.0"
+    picomatch "^2.0.4"
+
+argparse@^1.0.7:
+  version "1.0.10"
+  resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911"
+  integrity sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==
+  dependencies:
+    sprintf-js "~1.0.2"
+
+argparse@^2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38"
+  integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==
+
+array-buffer-byte-length@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz#fabe8bc193fea865f317fe7807085ee0dee5aead"
+  integrity sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==
+  dependencies:
+    call-bind "^1.0.2"
+    is-array-buffer "^3.0.1"
+
+array-union@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/array-union/-/array-union-2.1.0.tgz#b798420adbeb1de828d84acd8a2e23d3efe85e8d"
+  integrity sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==
+
+asynckit@^0.4.0:
+  version "0.4.0"
+  resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
+  integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==
+
+available-typed-arrays@^1.0.5:
+  version "1.0.5"
+  resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7"
+  integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==
+
+babel-jest@^27.5.1:
+  version "27.5.1"
+  resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-27.5.1.tgz#a1bf8d61928edfefd21da27eb86a695bfd691444"
+  integrity sha512-cdQ5dXjGRd0IBRATiQ4mZGlGlRE8kJpjPOixdNRdT+m3UcNqmYWN6rK6nvtXYfY3D76cb8s/O1Ss8ea24PIwcg==
+  dependencies:
+    "@jest/transform" "^27.5.1"
+    "@jest/types" "^27.5.1"
+    "@types/babel__core" "^7.1.14"
+    babel-plugin-istanbul "^6.1.1"
+    babel-preset-jest "^27.5.1"
+    chalk "^4.0.0"
+    graceful-fs "^4.2.9"
+    slash "^3.0.0"
+
+babel-plugin-istanbul@^6.1.1:
+  version "6.1.1"
+  resolved "https://registry.yarnpkg.com/babel-plugin-istanbul/-/babel-plugin-istanbul-6.1.1.tgz#fa88ec59232fd9b4e36dbbc540a8ec9a9b47da73"
+  integrity sha512-Y1IQok9821cC9onCx5otgFfRm7Lm+I+wwxOx738M/WLPZ9Q42m4IG5W0FNX8WLL2gYMZo3JkuXIH2DOpWM+qwA==
+  dependencies:
+    "@babel/helper-plugin-utils" "^7.0.0"
+    "@istanbuljs/load-nyc-config" "^1.0.0"
+    "@istanbuljs/schema" "^0.1.2"
+    istanbul-lib-instrument "^5.0.4"
+    test-exclude "^6.0.0"
+
+babel-plugin-jest-hoist@^27.5.1:
+  version "27.5.1"
+  resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-27.5.1.tgz#9be98ecf28c331eb9f5df9c72d6f89deb8181c2e"
+  integrity sha512-50wCwD5EMNW4aRpOwtqzyZHIewTYNxLA4nhB+09d8BIssfNfzBRhkBIHiaPv1Si226TQSvp8gxAJm2iY2qs2hQ==
+  dependencies:
+    "@babel/template" "^7.3.3"
+    "@babel/types" "^7.3.3"
+    "@types/babel__core" "^7.0.0"
+    "@types/babel__traverse" "^7.0.6"
+
+babel-preset-current-node-syntax@^1.0.0:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-1.0.1.tgz#b4399239b89b2a011f9ddbe3e4f401fc40cff73b"
+  integrity sha512-M7LQ0bxarkxQoN+vz5aJPsLBn77n8QgTFmo8WK0/44auK2xlCXrYcUxHFxgU7qW5Yzw/CjmLRK2uJzaCd7LvqQ==
+  dependencies:
+    "@babel/plugin-syntax-async-generators" "^7.8.4"
+    "@babel/plugin-syntax-bigint" "^7.8.3"
+    "@babel/plugin-syntax-class-properties" "^7.8.3"
+    "@babel/plugin-syntax-import-meta" "^7.8.3"
+    "@babel/plugin-syntax-json-strings" "^7.8.3"
+    "@babel/plugin-syntax-logical-assignment-operators" "^7.8.3"
+    "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3"
+    "@babel/plugin-syntax-numeric-separator" "^7.8.3"
+    "@babel/plugin-syntax-object-rest-spread" "^7.8.3"
+    "@babel/plugin-syntax-optional-catch-binding" "^7.8.3"
+    "@babel/plugin-syntax-optional-chaining" "^7.8.3"
+    "@babel/plugin-syntax-top-level-await" "^7.8.3"
+
+babel-preset-jest@^27.5.1:
+  version "27.5.1"
+  resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-27.5.1.tgz#91f10f58034cb7989cb4f962b69fa6eef6a6bc81"
+  integrity sha512-Nptf2FzlPCWYuJg41HBqXVT8ym6bXOevuCTbhxlUpjwtysGaIWFvDEjp4y+G7fl13FgOdjs7P/DmErqH7da0Ag==
+  dependencies:
+    babel-plugin-jest-hoist "^27.5.1"
+    babel-preset-current-node-syntax "^1.0.0"
+
+balanced-match@^1.0.0:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
+  integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
+
+brace-expansion@^1.1.7:
+  version "1.1.11"
+  resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
+  integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
+  dependencies:
+    balanced-match "^1.0.0"
+    concat-map "0.0.1"
+
+braces@^3.0.2:
+  version "3.0.2"
+  resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107"
+  integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==
+  dependencies:
+    fill-range "^7.0.1"
+
+browser-process-hrtime@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/browser-process-hrtime/-/browser-process-hrtime-1.0.0.tgz#3c9b4b7d782c8121e56f10106d84c0d0ffc94626"
+  integrity sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==
+
+browserslist@^4.21.9:
+  version "4.21.9"
+  resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.21.9.tgz#e11bdd3c313d7e2a9e87e8b4b0c7872b13897635"
+  integrity sha512-M0MFoZzbUrRU4KNfCrDLnvyE7gub+peetoTid3TBIqtunaDJyXlwhakT+/VkvSXcfIzFfK/nkCs4nmyTmxdNSg==
+  dependencies:
+    caniuse-lite "^1.0.30001503"
+    electron-to-chromium "^1.4.431"
+    node-releases "^2.0.12"
+    update-browserslist-db "^1.0.11"
+
+bs-logger@0.x:
+  version "0.2.6"
+  resolved "https://registry.yarnpkg.com/bs-logger/-/bs-logger-0.2.6.tgz#eb7d365307a72cf974cc6cda76b68354ad336bd8"
+  integrity sha512-pd8DCoxmbgc7hyPKOvxtqNcjYoOsABPQdcCUjGp3d42VR2CX1ORhk2A87oqqu5R1kk+76nsxZupkmyd+MVtCog==
+  dependencies:
+    fast-json-stable-stringify "2.x"
+
+bser@2.1.1:
+  version "2.1.1"
+  resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05"
+  integrity sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==
+  dependencies:
+    node-int64 "^0.4.0"
+
+buffer-from@^1.0.0:
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5"
+  integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==
+
+call-bind@^1.0.0, call-bind@^1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c"
+  integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==
+  dependencies:
+    function-bind "^1.1.1"
+    get-intrinsic "^1.0.2"
+
+callsites@^3.0.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
+  integrity sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==
+
+camelcase@^5.3.1:
+  version "5.3.1"
+  resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
+  integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
+
+camelcase@^6.2.0:
+  version "6.3.0"
+  resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.3.0.tgz#5685b95eb209ac9c0c177467778c9c84df58ba9a"
+  integrity sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==
+
+caniuse-lite@^1.0.30001503:
+  version "1.0.30001514"
+  resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001514.tgz#e2a7e184a23affc9367b7c8d734e7ec4628c1309"
+  integrity sha512-ENcIpYBmwAAOm/V2cXgM7rZUrKKaqisZl4ZAI520FIkqGXUxJjmaIssbRW5HVVR5tyV6ygTLIm15aU8LUmQSaQ==
+
+chalk@^2.0.0, chalk@^2.4.1:
+  version "2.4.2"
+  resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
+  integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
+  dependencies:
+    ansi-styles "^3.2.1"
+    escape-string-regexp "^1.0.5"
+    supports-color "^5.3.0"
+
+chalk@^4.0.0:
+  version "4.1.2"
+  resolved "https://registry.yarnpkg.com/chalk/-/chalk-4.1.2.tgz#aac4e2b7734a740867aeb16bf02aad556a1e7a01"
+  integrity sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==
+  dependencies:
+    ansi-styles "^4.1.0"
+    supports-color "^7.1.0"
+
+char-regex@^1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/char-regex/-/char-regex-1.0.2.tgz#d744358226217f981ed58f479b1d6bcc29545dcf"
+  integrity sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==
+
+ci-info@^3.2.0:
+  version "3.8.0"
+  resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.8.0.tgz#81408265a5380c929f0bc665d62256628ce9ef91"
+  integrity sha512-eXTggHWSooYhq49F2opQhuHWgzucfF2YgODK4e1566GQs5BIfP30B0oenwBJHfWxAs2fyPB1s7Mg949zLf61Yw==
+
+cjs-module-lexer@^1.0.0:
+  version "1.2.3"
+  resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.2.3.tgz#6c370ab19f8a3394e318fe682686ec0ac684d107"
+  integrity sha512-0TNiGstbQmCFwt4akjjBg5pLRTSyj/PkWQ1ZoO2zntmg9yLqSRxwEa4iCfQLGjqhiqBfOJa7W/E8wfGrTDmlZQ==
+
+cliui@^7.0.2:
+  version "7.0.4"
+  resolved "https://registry.yarnpkg.com/cliui/-/cliui-7.0.4.tgz#a0265ee655476fc807aea9df3df8df7783808b4f"
+  integrity sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==
+  dependencies:
+    string-width "^4.2.0"
+    strip-ansi "^6.0.0"
+    wrap-ansi "^7.0.0"
+
+co@^4.6.0:
+  version "4.6.0"
+  resolved "https://registry.yarnpkg.com/co/-/co-4.6.0.tgz#6ea6bdf3d853ae54ccb8e47bfa0bf3f9031fb184"
+  integrity sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==
+
+collect-v8-coverage@^1.0.0:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/collect-v8-coverage/-/collect-v8-coverage-1.0.2.tgz#c0b29bcd33bcd0779a1344c2136051e6afd3d9e9"
+  integrity sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==
+
+color-convert@^1.9.0:
+  version "1.9.3"
+  resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
+  integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==
+  dependencies:
+    color-name "1.1.3"
+
+color-convert@^2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3"
+  integrity sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==
+  dependencies:
+    color-name "~1.1.4"
+
+color-name@1.1.3:
+  version "1.1.3"
+  resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
+  integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==
+
+color-name@~1.1.4:
+  version "1.1.4"
+  resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
+  integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
+
+combined-stream@^1.0.8:
+  version "1.0.8"
+  resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
+  integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
+  dependencies:
+    delayed-stream "~1.0.0"
+
+concat-map@0.0.1:
+  version "0.0.1"
+  resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
+  integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
+
+convert-source-map@^1.4.0, convert-source-map@^1.6.0, convert-source-map@^1.7.0:
+  version "1.9.0"
+  resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-1.9.0.tgz#7faae62353fb4213366d0ca98358d22e8368b05f"
+  integrity sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==
+
+cross-spawn@^6.0.5:
+  version "6.0.5"
+  resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"
+  integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==
+  dependencies:
+    nice-try "^1.0.4"
+    path-key "^2.0.1"
+    semver "^5.5.0"
+    shebang-command "^1.2.0"
+    which "^1.2.9"
+
+cross-spawn@^7.0.2, cross-spawn@^7.0.3:
+  version "7.0.3"
+  resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6"
+  integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==
+  dependencies:
+    path-key "^3.1.0"
+    shebang-command "^2.0.0"
+    which "^2.0.1"
+
+cssom@^0.4.4:
+  version "0.4.4"
+  resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.4.4.tgz#5a66cf93d2d0b661d80bf6a44fb65f5c2e4e0a10"
+  integrity sha512-p3pvU7r1MyyqbTk+WbNJIgJjG2VmTIaB10rI93LzVPrmDJKkzKYMtxxyAvQXR/NS6otuzveI7+7BBq3SjBS2mw==
+
+cssom@~0.3.6:
+  version "0.3.8"
+  resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a"
+  integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==
+
+cssstyle@^2.3.0:
+  version "2.3.0"
+  resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-2.3.0.tgz#ff665a0ddbdc31864b09647f34163443d90b0852"
+  integrity sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==
+  dependencies:
+    cssom "~0.3.6"
+
+data-urls@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/data-urls/-/data-urls-2.0.0.tgz#156485a72963a970f5d5821aaf642bef2bf2db9b"
+  integrity sha512-X5eWTSXO/BJmpdIKCRuKUgSCgAN0OwliVK3yPKbwIWU1Tdw5BRajxlzMidvh+gwko9AfQ9zIj52pzF91Q3YAvQ==
+  dependencies:
+    abab "^2.0.3"
+    whatwg-mimetype "^2.3.0"
+    whatwg-url "^8.0.0"
+
+debug@4, debug@^4.1.0, debug@^4.1.1, debug@^4.3.2, debug@^4.3.4:
+  version "4.3.4"
+  resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
+  integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
+  dependencies:
+    ms "2.1.2"
+
+decimal.js@^10.2.1:
+  version "10.4.3"
+  resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.4.3.tgz#1044092884d245d1b7f65725fa4ad4c6f781cc23"
+  integrity sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==
+
+dedent@^0.7.0:
+  version "0.7.0"
+  resolved "https://registry.yarnpkg.com/dedent/-/dedent-0.7.0.tgz#2495ddbaf6eb874abb0e1be9df22d2e5a544326c"
+  integrity sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==
+
+deep-is@^0.1.3:
+  version "0.1.4"
+  resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831"
+  integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==
+
+deepmerge@^4.2.2:
+  version "4.3.1"
+  resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.3.1.tgz#44b5f2147cd3b00d4b56137685966f26fd25dd4a"
+  integrity sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==
+
+define-properties@^1.1.3, define-properties@^1.1.4, define-properties@^1.2.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.0.tgz#52988570670c9eacedd8064f4a990f2405849bd5"
+  integrity sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==
+  dependencies:
+    has-property-descriptors "^1.0.0"
+    object-keys "^1.1.1"
+
+delayed-stream@~1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
+  integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==
+
+detect-newline@^3.0.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/detect-newline/-/detect-newline-3.1.0.tgz#576f5dfc63ae1a192ff192d8ad3af6308991b651"
+  integrity sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==
+
+diff-sequences@^27.5.1:
+  version "27.5.1"
+  resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-27.5.1.tgz#eaecc0d327fd68c8d9672a1e64ab8dccb2ef5327"
+  integrity sha512-k1gCAXAsNgLwEL+Y8Wvl+M6oEFj5bgazfZULpS5CneoPPXRaCCW7dm+q21Ky2VEE5X+VeRDBVg1Pcvvsr4TtNQ==
+
+dir-glob@^3.0.1:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f"
+  integrity sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==
+  dependencies:
+    path-type "^4.0.0"
+
+doctrine@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961"
+  integrity sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==
+  dependencies:
+    esutils "^2.0.2"
+
+domexception@^2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/domexception/-/domexception-2.0.1.tgz#fb44aefba793e1574b0af6aed2801d057529f304"
+  integrity sha512-yxJ2mFy/sibVQlu5qHjOkf9J3K6zgmCxgJ94u2EdvDOV09H+32LtRswEcUsmUWN72pVLOEnTSRaIVVzVQgS0dg==
+  dependencies:
+    webidl-conversions "^5.0.0"
+
+electron-to-chromium@^1.4.431:
+  version "1.4.455"
+  resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.455.tgz#81fe4353ac970eb971c07088c8da8b7f6280ddc9"
+  integrity sha512-8tgdX0Odl24LtmLwxotpJCVjIndN559AvaOtd67u+2mo+IDsgsTF580NB+uuDCqsHw8yFg53l5+imFV9Fw3cbA==
+
+emittery@^0.8.1:
+  version "0.8.1"
+  resolved "https://registry.yarnpkg.com/emittery/-/emittery-0.8.1.tgz#bb23cc86d03b30aa75a7f734819dee2e1ba70860"
+  integrity sha512-uDfvUjVrfGJJhymx/kz6prltenw1u7WrCg1oa94zYY8xxVpLLUu045LAT0dhDZdXG58/EpPL/5kA180fQ/qudg==
+
+emoji-regex@^8.0.0:
+  version "8.0.0"
+  resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37"
+  integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==
+
+error-ex@^1.3.1:
+  version "1.3.2"
+  resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf"
+  integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==
+  dependencies:
+    is-arrayish "^0.2.1"
+
+es-abstract@^1.19.0, es-abstract@^1.20.4:
+  version "1.21.2"
+  resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.21.2.tgz#a56b9695322c8a185dc25975aa3b8ec31d0e7eff"
+  integrity sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg==
+  dependencies:
+    array-buffer-byte-length "^1.0.0"
+    available-typed-arrays "^1.0.5"
+    call-bind "^1.0.2"
+    es-set-tostringtag "^2.0.1"
+    es-to-primitive "^1.2.1"
+    function.prototype.name "^1.1.5"
+    get-intrinsic "^1.2.0"
+    get-symbol-description "^1.0.0"
+    globalthis "^1.0.3"
+    gopd "^1.0.1"
+    has "^1.0.3"
+    has-property-descriptors "^1.0.0"
+    has-proto "^1.0.1"
+    has-symbols "^1.0.3"
+    internal-slot "^1.0.5"
+    is-array-buffer "^3.0.2"
+    is-callable "^1.2.7"
+    is-negative-zero "^2.0.2"
+    is-regex "^1.1.4"
+    is-shared-array-buffer "^1.0.2"
+    is-string "^1.0.7"
+    is-typed-array "^1.1.10"
+    is-weakref "^1.0.2"
+    object-inspect "^1.12.3"
+    object-keys "^1.1.1"
+    object.assign "^4.1.4"
+    regexp.prototype.flags "^1.4.3"
+    safe-regex-test "^1.0.0"
+    string.prototype.trim "^1.2.7"
+    string.prototype.trimend "^1.0.6"
+    string.prototype.trimstart "^1.0.6"
+    typed-array-length "^1.0.4"
+    unbox-primitive "^1.0.2"
+    which-typed-array "^1.1.9"
+
+es-set-tostringtag@^2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz#338d502f6f674301d710b80c8592de8a15f09cd8"
+  integrity sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==
+  dependencies:
+    get-intrinsic "^1.1.3"
+    has "^1.0.3"
+    has-tostringtag "^1.0.0"
+
+es-to-primitive@^1.2.1:
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a"
+  integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==
+  dependencies:
+    is-callable "^1.1.4"
+    is-date-object "^1.0.1"
+    is-symbol "^1.0.2"
+
+escalade@^3.1.1:
+  version "3.1.1"
+  resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
+  integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==
+
+escape-string-regexp@^1.0.5:
+  version "1.0.5"
+  resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
+  integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==
+
+escape-string-regexp@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz#a30304e99daa32e23b2fd20f51babd07cffca344"
+  integrity sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==
+
+escape-string-regexp@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
+  integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
+
+escodegen@^2.0.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-2.1.0.tgz#ba93bbb7a43986d29d6041f99f5262da773e2e17"
+  integrity sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==
+  dependencies:
+    esprima "^4.0.1"
+    estraverse "^5.2.0"
+    esutils "^2.0.2"
+  optionalDependencies:
+    source-map "~0.6.1"
+
+eslint-plugin-jest@^27.1.6:
+  version "27.2.2"
+  resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-27.2.2.tgz#be4ded5f91905d9ec89aa8968d39c71f3b072c0c"
+  integrity sha512-euzbp06F934Z7UDl5ZUaRPLAc9MKjh0rMPERrHT7UhlCEwgb25kBj37TvMgWeHZVkR5I9CayswrpoaqZU1RImw==
+  dependencies:
+    "@typescript-eslint/utils" "^5.10.0"
+
+eslint-scope@^5.1.1:
+  version "5.1.1"
+  resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-5.1.1.tgz#e786e59a66cb92b3f6c1fb0d508aab174848f48c"
+  integrity sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==
+  dependencies:
+    esrecurse "^4.3.0"
+    estraverse "^4.1.1"
+
+eslint-scope@^7.2.0:
+  version "7.2.0"
+  resolved "https://registry.yarnpkg.com/eslint-scope/-/eslint-scope-7.2.0.tgz#f21ebdafda02352f103634b96dd47d9f81ca117b"
+  integrity sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==
+  dependencies:
+    esrecurse "^4.3.0"
+    estraverse "^5.2.0"
+
+eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1:
+  version "3.4.1"
+  resolved "https://registry.yarnpkg.com/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz#c22c48f48942d08ca824cc526211ae400478a994"
+  integrity sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==
+
+eslint@^8.13.0:
+  version "8.44.0"
+  resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.44.0.tgz#51246e3889b259bbcd1d7d736a0c10add4f0e500"
+  integrity sha512-0wpHoUbDUHgNCyvFB5aXLiQVfK9B0at6gUvzy83k4kAsQ/u769TQDX6iKC+aO4upIHO9WSaA3QoXYQDHbNwf1A==
+  dependencies:
+    "@eslint-community/eslint-utils" "^4.2.0"
+    "@eslint-community/regexpp" "^4.4.0"
+    "@eslint/eslintrc" "^2.1.0"
+    "@eslint/js" "8.44.0"
+    "@humanwhocodes/config-array" "^0.11.10"
+    "@humanwhocodes/module-importer" "^1.0.1"
+    "@nodelib/fs.walk" "^1.2.8"
+    ajv "^6.10.0"
+    chalk "^4.0.0"
+    cross-spawn "^7.0.2"
+    debug "^4.3.2"
+    doctrine "^3.0.0"
+    escape-string-regexp "^4.0.0"
+    eslint-scope "^7.2.0"
+    eslint-visitor-keys "^3.4.1"
+    espree "^9.6.0"
+    esquery "^1.4.2"
+    esutils "^2.0.2"
+    fast-deep-equal "^3.1.3"
+    file-entry-cache "^6.0.1"
+    find-up "^5.0.0"
+    glob-parent "^6.0.2"
+    globals "^13.19.0"
+    graphemer "^1.4.0"
+    ignore "^5.2.0"
+    import-fresh "^3.0.0"
+    imurmurhash "^0.1.4"
+    is-glob "^4.0.0"
+    is-path-inside "^3.0.3"
+    js-yaml "^4.1.0"
+    json-stable-stringify-without-jsonify "^1.0.1"
+    levn "^0.4.1"
+    lodash.merge "^4.6.2"
+    minimatch "^3.1.2"
+    natural-compare "^1.4.0"
+    optionator "^0.9.3"
+    strip-ansi "^6.0.1"
+    strip-json-comments "^3.1.0"
+    text-table "^0.2.0"
+
+espree@^9.6.0:
+  version "9.6.0"
+  resolved "https://registry.yarnpkg.com/espree/-/espree-9.6.0.tgz#80869754b1c6560f32e3b6929194a3fe07c5b82f"
+  integrity sha512-1FH/IiruXZ84tpUlm0aCUEwMl2Ho5ilqVh0VvQXw+byAz/4SAciyHLlfmL5WYqsvD38oymdUwBss0LtK8m4s/A==
+  dependencies:
+    acorn "^8.9.0"
+    acorn-jsx "^5.3.2"
+    eslint-visitor-keys "^3.4.1"
+
+esprima@^4.0.0, esprima@^4.0.1:
+  version "4.0.1"
+  resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
+  integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==
+
+esquery@^1.4.2:
+  version "1.5.0"
+  resolved "https://registry.yarnpkg.com/esquery/-/esquery-1.5.0.tgz#6ce17738de8577694edd7361c57182ac8cb0db0b"
+  integrity sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==
+  dependencies:
+    estraverse "^5.1.0"
+
+esrecurse@^4.3.0:
+  version "4.3.0"
+  resolved "https://registry.yarnpkg.com/esrecurse/-/esrecurse-4.3.0.tgz#7ad7964d679abb28bee72cec63758b1c5d2c9921"
+  integrity sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==
+  dependencies:
+    estraverse "^5.2.0"
+
+estraverse@^4.1.1:
+  version "4.3.0"
+  resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.3.0.tgz#398ad3f3c5a24948be7725e83d11a7de28cdbd1d"
+  integrity sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==
+
+estraverse@^5.1.0, estraverse@^5.2.0:
+  version "5.3.0"
+  resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-5.3.0.tgz#2eea5290702f26ab8fe5370370ff86c965d21123"
+  integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==
+
+esutils@^2.0.2:
+  version "2.0.3"
+  resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
+  integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==
+
+execa@^5.0.0:
+  version "5.1.1"
+  resolved "https://registry.yarnpkg.com/execa/-/execa-5.1.1.tgz#f80ad9cbf4298f7bd1d4c9555c21e93741c411dd"
+  integrity sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==
+  dependencies:
+    cross-spawn "^7.0.3"
+    get-stream "^6.0.0"
+    human-signals "^2.1.0"
+    is-stream "^2.0.0"
+    merge-stream "^2.0.0"
+    npm-run-path "^4.0.1"
+    onetime "^5.1.2"
+    signal-exit "^3.0.3"
+    strip-final-newline "^2.0.0"
+
+exit@^0.1.2:
+  version "0.1.2"
+  resolved "https://registry.yarnpkg.com/exit/-/exit-0.1.2.tgz#0632638f8d877cc82107d30a0fff1a17cba1cd0c"
+  integrity sha512-Zk/eNKV2zbjpKzrsQ+n1G6poVbErQxJ0LBOJXaKZ1EViLzH+hrLu9cdXI4zw9dBQJslwBEpbQ2P1oS7nDxs6jQ==
+
+expect@^27.5.1:
+  version "27.5.1"
+  resolved "https://registry.yarnpkg.com/expect/-/expect-27.5.1.tgz#83ce59f1e5bdf5f9d2b94b61d2050db48f3fef74"
+  integrity sha512-E1q5hSUG2AmYQwQJ041nvgpkODHQvB+RKlB4IYdru6uJsyFTRyZAP463M+1lINorwbqAmUggi6+WwkD8lCS/Dw==
+  dependencies:
+    "@jest/types" "^27.5.1"
+    jest-get-type "^27.5.1"
+    jest-matcher-utils "^27.5.1"
+    jest-message-util "^27.5.1"
+
+fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
+  version "3.1.3"
+  resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
+  integrity sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==
+
+fast-glob@^3.2.9:
+  version "3.3.0"
+  resolved "https://registry.yarnpkg.com/fast-glob/-/fast-glob-3.3.0.tgz#7c40cb491e1e2ed5664749e87bfb516dbe8727c0"
+  integrity sha512-ChDuvbOypPuNjO8yIDf36x7BlZX1smcUMTTcyoIjycexOxd6DFsKsg21qVBzEmr3G7fUKIRy2/psii+CIUt7FA==
+  dependencies:
+    "@nodelib/fs.stat" "^2.0.2"
+    "@nodelib/fs.walk" "^1.2.3"
+    glob-parent "^5.1.2"
+    merge2 "^1.3.0"
+    micromatch "^4.0.4"
+
+fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
+  integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==
+
+fast-levenshtein@^2.0.6:
+  version "2.0.6"
+  resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
+  integrity sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==
+
+fastq@^1.6.0:
+  version "1.15.0"
+  resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.15.0.tgz#d04d07c6a2a68fe4599fea8d2e103a937fae6b3a"
+  integrity sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==
+  dependencies:
+    reusify "^1.0.4"
+
+fb-watchman@^2.0.0:
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/fb-watchman/-/fb-watchman-2.0.2.tgz#e9524ee6b5c77e9e5001af0f85f3adbb8623255c"
+  integrity sha512-p5161BqbuCaSnB8jIbzQHOlpgsPmK5rJVDfDKO91Axs5NC1uu3HRQm6wt9cd9/+GtQQIO53JdGXXoyDpTAsgYA==
+  dependencies:
+    bser "2.1.1"
+
+file-entry-cache@^6.0.1:
+  version "6.0.1"
+  resolved "https://registry.yarnpkg.com/file-entry-cache/-/file-entry-cache-6.0.1.tgz#211b2dd9659cb0394b073e7323ac3c933d522027"
+  integrity sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==
+  dependencies:
+    flat-cache "^3.0.4"
+
+fill-range@^7.0.1:
+  version "7.0.1"
+  resolved "https://registry.yarnpkg.com/fill-range/-/fill-range-7.0.1.tgz#1919a6a7c75fe38b2c7c77e5198535da9acdda40"
+  integrity sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==
+  dependencies:
+    to-regex-range "^5.0.1"
+
+find-up@^4.0.0, find-up@^4.1.0:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19"
+  integrity sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==
+  dependencies:
+    locate-path "^5.0.0"
+    path-exists "^4.0.0"
+
+find-up@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc"
+  integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==
+  dependencies:
+    locate-path "^6.0.0"
+    path-exists "^4.0.0"
+
+flat-cache@^3.0.4:
+  version "3.0.4"
+  resolved "https://registry.yarnpkg.com/flat-cache/-/flat-cache-3.0.4.tgz#61b0338302b2fe9f957dcc32fc2a87f1c3048b11"
+  integrity sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==
+  dependencies:
+    flatted "^3.1.0"
+    rimraf "^3.0.2"
+
+flatted@^3.1.0:
+  version "3.2.7"
+  resolved "https://registry.yarnpkg.com/flatted/-/flatted-3.2.7.tgz#609f39207cb614b89d0765b477cb2d437fbf9787"
+  integrity sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==
+
+for-each@^0.3.3:
+  version "0.3.3"
+  resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e"
+  integrity sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==
+  dependencies:
+    is-callable "^1.1.3"
+
+form-data@^3.0.0:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f"
+  integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==
+  dependencies:
+    asynckit "^0.4.0"
+    combined-stream "^1.0.8"
+    mime-types "^2.1.12"
+
+fs.realpath@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f"
+  integrity sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==
+
+fsevents@^2.3.2:
+  version "2.3.2"
+  resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
+  integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
+
+function-bind@^1.1.1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
+  integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
+
+function.prototype.name@^1.1.5:
+  version "1.1.5"
+  resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.5.tgz#cce0505fe1ffb80503e6f9e46cc64e46a12a9621"
+  integrity sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==
+  dependencies:
+    call-bind "^1.0.2"
+    define-properties "^1.1.3"
+    es-abstract "^1.19.0"
+    functions-have-names "^1.2.2"
+
+functions-have-names@^1.2.2, functions-have-names@^1.2.3:
+  version "1.2.3"
+  resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834"
+  integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==
+
+gensync@^1.0.0-beta.2:
+  version "1.0.0-beta.2"
+  resolved "https://registry.yarnpkg.com/gensync/-/gensync-1.0.0-beta.2.tgz#32a6ee76c3d7f52d46b2b1ae5d93fea8580a25e0"
+  integrity sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==
+
+get-caller-file@^2.0.5:
+  version "2.0.5"
+  resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
+  integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
+
+get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.0:
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.1.tgz#d295644fed4505fc9cde952c37ee12b477a83d82"
+  integrity sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==
+  dependencies:
+    function-bind "^1.1.1"
+    has "^1.0.3"
+    has-proto "^1.0.1"
+    has-symbols "^1.0.3"
+
+get-package-type@^0.1.0:
+  version "0.1.0"
+  resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a"
+  integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==
+
+get-stream@^6.0.0:
+  version "6.0.1"
+  resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-6.0.1.tgz#a262d8eef67aced57c2852ad6167526a43cbf7b7"
+  integrity sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==
+
+get-symbol-description@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.0.tgz#7fdb81c900101fbd564dd5f1a30af5aadc1e58d6"
+  integrity sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==
+  dependencies:
+    call-bind "^1.0.2"
+    get-intrinsic "^1.1.1"
+
+glob-parent@^5.1.2:
+  version "5.1.2"
+  resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4"
+  integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==
+  dependencies:
+    is-glob "^4.0.1"
+
+glob-parent@^6.0.2:
+  version "6.0.2"
+  resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-6.0.2.tgz#6d237d99083950c79290f24c7642a3de9a28f9e3"
+  integrity sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==
+  dependencies:
+    is-glob "^4.0.3"
+
+glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4:
+  version "7.2.3"
+  resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.3.tgz#b8df0fb802bbfa8e89bd1d938b4e16578ed44f2b"
+  integrity sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==
+  dependencies:
+    fs.realpath "^1.0.0"
+    inflight "^1.0.4"
+    inherits "2"
+    minimatch "^3.1.1"
+    once "^1.3.0"
+    path-is-absolute "^1.0.0"
+
+globals@^11.1.0:
+  version "11.12.0"
+  resolved "https://registry.yarnpkg.com/globals/-/globals-11.12.0.tgz#ab8795338868a0babd8525758018c2a7eb95c42e"
+  integrity sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==
+
+globals@^13.19.0:
+  version "13.20.0"
+  resolved "https://registry.yarnpkg.com/globals/-/globals-13.20.0.tgz#ea276a1e508ffd4f1612888f9d1bad1e2717bf82"
+  integrity sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==
+  dependencies:
+    type-fest "^0.20.2"
+
+globalthis@^1.0.3:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.3.tgz#5852882a52b80dc301b0660273e1ed082f0b6ccf"
+  integrity sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==
+  dependencies:
+    define-properties "^1.1.3"
+
+globby@^11.1.0:
+  version "11.1.0"
+  resolved "https://registry.yarnpkg.com/globby/-/globby-11.1.0.tgz#bd4be98bb042f83d796f7e3811991fbe82a0d34b"
+  integrity sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==
+  dependencies:
+    array-union "^2.1.0"
+    dir-glob "^3.0.1"
+    fast-glob "^3.2.9"
+    ignore "^5.2.0"
+    merge2 "^1.4.1"
+    slash "^3.0.0"
+
+gopd@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c"
+  integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==
+  dependencies:
+    get-intrinsic "^1.1.3"
+
+graceful-fs@^4.1.2, graceful-fs@^4.2.9:
+  version "4.2.11"
+  resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3"
+  integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
+
+graphemer@^1.4.0:
+  version "1.4.0"
+  resolved "https://registry.yarnpkg.com/graphemer/-/graphemer-1.4.0.tgz#fb2f1d55e0e3a1849aeffc90c4fa0dd53a0e66c6"
+  integrity sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==
+
+has-bigints@^1.0.1, has-bigints@^1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa"
+  integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==
+
+has-flag@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
+  integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==
+
+has-flag@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b"
+  integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==
+
+has-property-descriptors@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz#610708600606d36961ed04c196193b6a607fa861"
+  integrity sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==
+  dependencies:
+    get-intrinsic "^1.1.1"
+
+has-proto@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.1.tgz#1885c1305538958aff469fef37937c22795408e0"
+  integrity sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==
+
+has-symbols@^1.0.2, has-symbols@^1.0.3:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8"
+  integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==
+
+has-tostringtag@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25"
+  integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==
+  dependencies:
+    has-symbols "^1.0.2"
+
+has@^1.0.3:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
+  integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==
+  dependencies:
+    function-bind "^1.1.1"
+
+hosted-git-info@^2.1.4:
+  version "2.8.9"
+  resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9"
+  integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==
+
+html-encoding-sniffer@^2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz#42a6dc4fd33f00281176e8b23759ca4e4fa185f3"
+  integrity sha512-D5JbOMBIR/TVZkubHT+OyT2705QvogUW4IBn6nHd756OwieSF9aDYFj4dv6HHEVGYbHaLETa3WggZYWWMyy3ZQ==
+  dependencies:
+    whatwg-encoding "^1.0.5"
+
+html-escaper@^2.0.0:
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/html-escaper/-/html-escaper-2.0.2.tgz#dfd60027da36a36dfcbe236262c00a5822681453"
+  integrity sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==
+
+http-proxy-agent@^4.0.1:
+  version "4.0.1"
+  resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz#8a8c8ef7f5932ccf953c296ca8291b95aa74aa3a"
+  integrity sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==
+  dependencies:
+    "@tootallnate/once" "1"
+    agent-base "6"
+    debug "4"
+
+https-proxy-agent@^5.0.0:
+  version "5.0.1"
+  resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6"
+  integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==
+  dependencies:
+    agent-base "6"
+    debug "4"
+
+human-signals@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0"
+  integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==
+
+iconv-lite@0.4.24:
+  version "0.4.24"
+  resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b"
+  integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==
+  dependencies:
+    safer-buffer ">= 2.1.2 < 3"
+
+ignore@^5.2.0:
+  version "5.2.4"
+  resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.2.4.tgz#a291c0c6178ff1b960befe47fcdec301674a6324"
+  integrity sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==
+
+import-fresh@^3.0.0, import-fresh@^3.2.1:
+  version "3.3.0"
+  resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-3.3.0.tgz#37162c25fcb9ebaa2e6e53d5b4d88ce17d9e0c2b"
+  integrity sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==
+  dependencies:
+    parent-module "^1.0.0"
+    resolve-from "^4.0.0"
+
+import-local@^3.0.2:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/import-local/-/import-local-3.1.0.tgz#b4479df8a5fd44f6cdce24070675676063c95cb4"
+  integrity sha512-ASB07uLtnDs1o6EHjKpX34BKYDSqnFerfTOJL2HvMqF70LnxpjkzDB8J44oT9pu4AMPkQwf8jl6szgvNd2tRIg==
+  dependencies:
+    pkg-dir "^4.2.0"
+    resolve-cwd "^3.0.0"
+
+imurmurhash@^0.1.4:
+  version "0.1.4"
+  resolved "https://registry.yarnpkg.com/imurmurhash/-/imurmurhash-0.1.4.tgz#9218b9b2b928a238b13dc4fb6b6d576f231453ea"
+  integrity sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==
+
+inflight@^1.0.4:
+  version "1.0.6"
+  resolved "https://registry.yarnpkg.com/inflight/-/inflight-1.0.6.tgz#49bd6331d7d02d0c09bc910a1075ba8165b56df9"
+  integrity sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==
+  dependencies:
+    once "^1.3.0"
+    wrappy "1"
+
+inherits@2:
+  version "2.0.4"
+  resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
+  integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
+
+internal-slot@^1.0.5:
+  version "1.0.5"
+  resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.5.tgz#f2a2ee21f668f8627a4667f309dc0f4fb6674986"
+  integrity sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==
+  dependencies:
+    get-intrinsic "^1.2.0"
+    has "^1.0.3"
+    side-channel "^1.0.4"
+
+is-array-buffer@^3.0.1, is-array-buffer@^3.0.2:
+  version "3.0.2"
+  resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.2.tgz#f2653ced8412081638ecb0ebbd0c41c6e0aecbbe"
+  integrity sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==
+  dependencies:
+    call-bind "^1.0.2"
+    get-intrinsic "^1.2.0"
+    is-typed-array "^1.1.10"
+
+is-arrayish@^0.2.1:
+  version "0.2.1"
+  resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
+  integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==
+
+is-bigint@^1.0.1:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3"
+  integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==
+  dependencies:
+    has-bigints "^1.0.1"
+
+is-boolean-object@^1.1.0:
+  version "1.1.2"
+  resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719"
+  integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==
+  dependencies:
+    call-bind "^1.0.2"
+    has-tostringtag "^1.0.0"
+
+is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7:
+  version "1.2.7"
+  resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055"
+  integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==
+
+is-core-module@^2.11.0:
+  version "2.12.1"
+  resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.12.1.tgz#0c0b6885b6f80011c71541ce15c8d66cf5a4f9fd"
+  integrity sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==
+  dependencies:
+    has "^1.0.3"
+
+is-date-object@^1.0.1:
+  version "1.0.5"
+  resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f"
+  integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==
+  dependencies:
+    has-tostringtag "^1.0.0"
+
+is-extglob@^2.1.1:
+  version "2.1.1"
+  resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2"
+  integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==
+
+is-fullwidth-code-point@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
+  integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
+
+is-generator-fn@^2.0.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/is-generator-fn/-/is-generator-fn-2.1.0.tgz#7d140adc389aaf3011a8f2a2a4cfa6faadffb118"
+  integrity sha512-cTIB4yPYL/Grw0EaSzASzg6bBy9gqCofvWN8okThAYIxKJZC+udlRAmGbM0XLeniEJSs8uEgHPGuHSe1XsOLSQ==
+
+is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3:
+  version "4.0.3"
+  resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084"
+  integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==
+  dependencies:
+    is-extglob "^2.1.1"
+
+is-negative-zero@^2.0.2:
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.2.tgz#7bf6f03a28003b8b3965de3ac26f664d765f3150"
+  integrity sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==
+
+is-number-object@^1.0.4:
+  version "1.0.7"
+  resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.7.tgz#59d50ada4c45251784e9904f5246c742f07a42fc"
+  integrity sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==
+  dependencies:
+    has-tostringtag "^1.0.0"
+
+is-number@^7.0.0:
+  version "7.0.0"
+  resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b"
+  integrity sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==
+
+is-path-inside@^3.0.3:
+  version "3.0.3"
+  resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283"
+  integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==
+
+is-potential-custom-element-name@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5"
+  integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==
+
+is-regex@^1.1.4:
+  version "1.1.4"
+  resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958"
+  integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==
+  dependencies:
+    call-bind "^1.0.2"
+    has-tostringtag "^1.0.0"
+
+is-shared-array-buffer@^1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz#8f259c573b60b6a32d4058a1a07430c0a7344c79"
+  integrity sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==
+  dependencies:
+    call-bind "^1.0.2"
+
+is-stream@^2.0.0:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/is-stream/-/is-stream-2.0.1.tgz#fac1e3d53b97ad5a9d0ae9cef2389f5810a5c077"
+  integrity sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==
+
+is-string@^1.0.5, is-string@^1.0.7:
+  version "1.0.7"
+  resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd"
+  integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==
+  dependencies:
+    has-tostringtag "^1.0.0"
+
+is-symbol@^1.0.2, is-symbol@^1.0.3:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c"
+  integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==
+  dependencies:
+    has-symbols "^1.0.2"
+
+is-typed-array@^1.1.10, is-typed-array@^1.1.9:
+  version "1.1.10"
+  resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.10.tgz#36a5b5cb4189b575d1a3e4b08536bfb485801e3f"
+  integrity sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==
+  dependencies:
+    available-typed-arrays "^1.0.5"
+    call-bind "^1.0.2"
+    for-each "^0.3.3"
+    gopd "^1.0.1"
+    has-tostringtag "^1.0.0"
+
+is-typedarray@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/is-typedarray/-/is-typedarray-1.0.0.tgz#e479c80858df0c1b11ddda6940f96011fcda4a9a"
+  integrity sha512-cyA56iCMHAh5CdzjJIa4aohJyeO1YbwLi3Jc35MmRU6poroFjIGZzUzupGiRPOjgHg9TLu43xbpwXk523fMxKA==
+
+is-weakref@^1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2"
+  integrity sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==
+  dependencies:
+    call-bind "^1.0.2"
+
+isexe@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
+  integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==
+
+istanbul-lib-coverage@^3.0.0, istanbul-lib-coverage@^3.2.0:
+  version "3.2.0"
+  resolved "https://registry.yarnpkg.com/istanbul-lib-coverage/-/istanbul-lib-coverage-3.2.0.tgz#189e7909d0a39fa5a3dfad5b03f71947770191d3"
+  integrity sha512-eOeJ5BHCmHYvQK7xt9GkdHuzuCGS1Y6g9Gvnx3Ym33fz/HpLRYxiS0wHNr+m/MBC8B647Xt608vCDEvhl9c6Mw==
+
+istanbul-lib-instrument@^5.0.4, istanbul-lib-instrument@^5.1.0:
+  version "5.2.1"
+  resolved "https://registry.yarnpkg.com/istanbul-lib-instrument/-/istanbul-lib-instrument-5.2.1.tgz#d10c8885c2125574e1c231cacadf955675e1ce3d"
+  integrity sha512-pzqtp31nLv/XFOzXGuvhCb8qhjmTVo5vjVk19XE4CRlSWz0KoeJ3bw9XsA7nOp9YBf4qHjwBxkDzKcME/J29Yg==
+  dependencies:
+    "@babel/core" "^7.12.3"
+    "@babel/parser" "^7.14.7"
+    "@istanbuljs/schema" "^0.1.2"
+    istanbul-lib-coverage "^3.2.0"
+    semver "^6.3.0"
+
+istanbul-lib-report@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#7518fe52ea44de372f460a76b5ecda9ffb73d8a6"
+  integrity sha512-wcdi+uAKzfiGT2abPpKZ0hSU1rGQjUQnLvtY5MpQ7QCTahD3VODhcu4wcfY1YtkGaDD5yuydOLINXsfbus9ROw==
+  dependencies:
+    istanbul-lib-coverage "^3.0.0"
+    make-dir "^3.0.0"
+    supports-color "^7.1.0"
+
+istanbul-lib-source-maps@^4.0.0:
+  version "4.0.1"
+  resolved "https://registry.yarnpkg.com/istanbul-lib-source-maps/-/istanbul-lib-source-maps-4.0.1.tgz#895f3a709fcfba34c6de5a42939022f3e4358551"
+  integrity sha512-n3s8EwkdFIJCG3BPKBYvskgXGoy88ARzvegkitk60NxRdwltLOTaH7CUiMRXvwYorl0Q712iEjcWB+fK/MrWVw==
+  dependencies:
+    debug "^4.1.1"
+    istanbul-lib-coverage "^3.0.0"
+    source-map "^0.6.1"
+
+istanbul-reports@^3.1.3:
+  version "3.1.5"
+  resolved "https://registry.yarnpkg.com/istanbul-reports/-/istanbul-reports-3.1.5.tgz#cc9a6ab25cb25659810e4785ed9d9fb742578bae"
+  integrity sha512-nUsEMa9pBt/NOHqbcbeJEgqIlY/K7rVWUX6Lql2orY5e9roQOthbR3vtY4zzf2orPELg80fnxxk9zUyPlgwD1w==
+  dependencies:
+    html-escaper "^2.0.0"
+    istanbul-lib-report "^3.0.0"
+
+jest-changed-files@^27.5.1:
+  version "27.5.1"
+  resolved "https://registry.yarnpkg.com/jest-changed-files/-/jest-changed-files-27.5.1.tgz#a348aed00ec9bf671cc58a66fcbe7c3dfd6a68f5"
+  integrity sha512-buBLMiByfWGCoMsLLzGUUSpAmIAGnbR2KJoMN10ziLhOLvP4e0SlypHnAel8iqQXTrcbmfEY9sSqae5sgUsTvw==
+  dependencies:
+    "@jest/types" "^27.5.1"
+    execa "^5.0.0"
+    throat "^6.0.1"
+
+jest-circus@^27.5.1:
+  version "27.5.1"
+  resolved "https://registry.yarnpkg.com/jest-circus/-/jest-circus-27.5.1.tgz#37a5a4459b7bf4406e53d637b49d22c65d125ecc"
+  integrity sha512-D95R7x5UtlMA5iBYsOHFFbMD/GVA4R/Kdq15f7xYWUfWHBto9NYRsOvnSauTgdF+ogCpJ4tyKOXhUifxS65gdw==
+  dependencies:
+    "@jest/environment" "^27.5.1"
+    "@jest/test-result" "^27.5.1"
+    "@jest/types" "^27.5.1"
+    "@types/node" "*"
+    chalk "^4.0.0"
+    co "^4.6.0"
+    dedent "^0.7.0"
+    expect "^27.5.1"
+    is-generator-fn "^2.0.0"
+    jest-each "^27.5.1"
+    jest-matcher-utils "^27.5.1"
+    jest-message-util "^27.5.1"
+    jest-runtime "^27.5.1"
+    jest-snapshot "^27.5.1"
+    jest-util "^27.5.1"
+    pretty-format "^27.5.1"
+    slash "^3.0.0"
+    stack-utils "^2.0.3"
+    throat "^6.0.1"
+
+jest-cli@^27.5.1:
+  version "27.5.1"
+  resolved "https://registry.yarnpkg.com/jest-cli/-/jest-cli-27.5.1.tgz#278794a6e6458ea8029547e6c6cbf673bd30b145"
+  integrity sha512-Hc6HOOwYq4/74/c62dEE3r5elx8wjYqxY0r0G/nFrLDPMFRu6RA/u8qINOIkvhxG7mMQ5EJsOGfRpI8L6eFUVw==
+  dependencies:
+    "@jest/core" "^27.5.1"
+    "@jest/test-result" "^27.5.1"
+    "@jest/types" "^27.5.1"
+    chalk "^4.0.0"
+    exit "^0.1.2"
+    graceful-fs "^4.2.9"
+    import-local "^3.0.2"
+    jest-config "^27.5.1"
+    jest-util "^27.5.1"
+    jest-validate "^27.5.1"
+    prompts "^2.0.1"
+    yargs "^16.2.0"
+
+jest-config@^27.5.1:
+  version "27.5.1"
+  resolved "https://registry.yarnpkg.com/jest-config/-/jest-config-27.5.1.tgz#5c387de33dca3f99ad6357ddeccd91bf3a0e4a41"
+  integrity sha512-5sAsjm6tGdsVbW9ahcChPAFCk4IlkQUknH5AvKjuLTSlcO/wCZKyFdn7Rg0EkC+OGgWODEy2hDpWB1PgzH0JNA==
+  dependencies:
+    "@babel/core" "^7.8.0"
+    "@jest/test-sequencer" "^27.5.1"
+    "@jest/types" "^27.5.1"
+    babel-jest "^27.5.1"
+    chalk "^4.0.0"
+    ci-info "^3.2.0"
+    deepmerge "^4.2.2"
+    glob "^7.1.1"
+    graceful-fs "^4.2.9"
+    jest-circus "^27.5.1"
+    jest-environment-jsdom "^27.5.1"
+    jest-environment-node "^27.5.1"
+    jest-get-type "^27.5.1"
+    jest-jasmine2 "^27.5.1"
+    jest-regex-util "^27.5.1"
+    jest-resolve "^27.5.1"
+    jest-runner "^27.5.1"
+    jest-util "^27.5.1"
+    jest-validate "^27.5.1"
+    micromatch "^4.0.4"
+    parse-json "^5.2.0"
+    pretty-format "^27.5.1"
+    slash "^3.0.0"
+    strip-json-comments "^3.1.1"
+
+jest-diff@^27.5.1:
+  version "27.5.1"
+  resolved "https://registry.yarnpkg.com/jest-diff/-/jest-diff-27.5.1.tgz#a07f5011ac9e6643cf8a95a462b7b1ecf6680def"
+  integrity sha512-m0NvkX55LDt9T4mctTEgnZk3fmEg3NRYutvMPWM/0iPnkFj2wIeF45O1718cMSOFO1vINkqmxqD8vE37uTEbqw==
+  dependencies:
+    chalk "^4.0.0"
+    diff-sequences "^27.5.1"
+    jest-get-type "^27.5.1"
+    pretty-format "^27.5.1"
+
+jest-docblock@^27.5.1:
+  version "27.5.1"
+  resolved "https://registry.yarnpkg.com/jest-docblock/-/jest-docblock-27.5.1.tgz#14092f364a42c6108d42c33c8cf30e058e25f6c0"
+  integrity sha512-rl7hlABeTsRYxKiUfpHrQrG4e2obOiTQWfMEH3PxPjOtdsfLQO4ReWSZaQ7DETm4xu07rl4q/h4zcKXyU0/OzQ==
+  dependencies:
+    detect-newline "^3.0.0"
+
+jest-each@^27.5.1:
+  version "27.5.1"
+  resolved "https://registry.yarnpkg.com/jest-each/-/jest-each-27.5.1.tgz#5bc87016f45ed9507fed6e4702a5b468a5b2c44e"
+  integrity sha512-1Ff6p+FbhT/bXQnEouYy00bkNSY7OUpfIcmdl8vZ31A1UUaurOLPA8a8BbJOF2RDUElwJhmeaV7LnagI+5UwNQ==
+  dependencies:
+    "@jest/types" "^27.5.1"
+    chalk "^4.0.0"
+    jest-get-type "^27.5.1"
+    jest-util "^27.5.1"
+    pretty-format "^27.5.1"
+
+jest-environment-jsdom@^27.5.1:
+  version "27.5.1"
+  resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-27.5.1.tgz#ea9ccd1fc610209655a77898f86b2b559516a546"
+  integrity sha512-TFBvkTC1Hnnnrka/fUb56atfDtJ9VMZ94JkjTbggl1PEpwrYtUBKMezB3inLmWqQsXYLcMwNoDQwoBTAvFfsfw==
+  dependencies:
+    "@jest/environment" "^27.5.1"
+    "@jest/fake-timers" "^27.5.1"
+    "@jest/types" "^27.5.1"
+    "@types/node" "*"
+    jest-mock "^27.5.1"
+    jest-util "^27.5.1"
+    jsdom "^16.6.0"
+
+jest-environment-node@^27.5.1:
+  version "27.5.1"
+  resolved "https://registry.yarnpkg.com/jest-environment-node/-/jest-environment-node-27.5.1.tgz#dedc2cfe52fab6b8f5714b4808aefa85357a365e"
+  integrity sha512-Jt4ZUnxdOsTGwSRAfKEnE6BcwsSPNOijjwifq5sDFSA2kesnXTvNqKHYgM0hDq3549Uf/KzdXNYn4wMZJPlFLw==
+  dependencies:
+    "@jest/environment" "^27.5.1"
+    "@jest/fake-timers" "^27.5.1"
+    "@jest/types" "^27.5.1"
+    "@types/node" "*"
+    jest-mock "^27.5.1"
+    jest-util "^27.5.1"
+
+jest-get-type@^27.5.1:
+  version "27.5.1"
+  resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-27.5.1.tgz#3cd613c507b0f7ace013df407a1c1cd578bcb4f1"
+  integrity sha512-2KY95ksYSaK7DMBWQn6dQz3kqAf3BB64y2udeG+hv4KfSOb9qwcYQstTJc1KCbsix+wLZWZYN8t7nwX3GOBLRw==
+
+jest-haste-map@^27.5.1:
+  version "27.5.1"
+  resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-27.5.1.tgz#9fd8bd7e7b4fa502d9c6164c5640512b4e811e7f"
+  integrity sha512-7GgkZ4Fw4NFbMSDSpZwXeBiIbx+t/46nJ2QitkOjvwPYyZmqttu2TDSimMHP1EkPOi4xUZAN1doE5Vd25H4Jng==
+  dependencies:
+    "@jest/types" "^27.5.1"
+    "@types/graceful-fs" "^4.1.2"
+    "@types/node" "*"
+    anymatch "^3.0.3"
+    fb-watchman "^2.0.0"
+    graceful-fs "^4.2.9"
+    jest-regex-util "^27.5.1"
+    jest-serializer "^27.5.1"
+    jest-util "^27.5.1"
+    jest-worker "^27.5.1"
+    micromatch "^4.0.4"
+    walker "^1.0.7"
+  optionalDependencies:
+    fsevents "^2.3.2"
+
+jest-jasmine2@^27.5.1:
+  version "27.5.1"
+  resolved "https://registry.yarnpkg.com/jest-jasmine2/-/jest-jasmine2-27.5.1.tgz#a037b0034ef49a9f3d71c4375a796f3b230d1ac4"
+  integrity sha512-jtq7VVyG8SqAorDpApwiJJImd0V2wv1xzdheGHRGyuT7gZm6gG47QEskOlzsN1PG/6WNaCo5pmwMHDf3AkG2pQ==
+  dependencies:
+    "@jest/environment" "^27.5.1"
+    "@jest/source-map" "^27.5.1"
+    "@jest/test-result" "^27.5.1"
+    "@jest/types" "^27.5.1"
+    "@types/node" "*"
+    chalk "^4.0.0"
+    co "^4.6.0"
+    expect "^27.5.1"
+    is-generator-fn "^2.0.0"
+    jest-each "^27.5.1"
+    jest-matcher-utils "^27.5.1"
+    jest-message-util "^27.5.1"
+    jest-runtime "^27.5.1"
+    jest-snapshot "^27.5.1"
+    jest-util "^27.5.1"
+    pretty-format "^27.5.1"
+    throat "^6.0.1"
+
+jest-leak-detector@^27.5.1:
+  version "27.5.1"
+  resolved "https://registry.yarnpkg.com/jest-leak-detector/-/jest-leak-detector-27.5.1.tgz#6ec9d54c3579dd6e3e66d70e3498adf80fde3fb8"
+  integrity sha512-POXfWAMvfU6WMUXftV4HolnJfnPOGEu10fscNCA76KBpRRhcMN2c8d3iT2pxQS3HLbA+5X4sOUPzYO2NUyIlHQ==
+  dependencies:
+    jest-get-type "^27.5.1"
+    pretty-format "^27.5.1"
+
+jest-matcher-utils@^27.0.0, jest-matcher-utils@^27.5.1:
+  version "27.5.1"
+  resolved "https://registry.yarnpkg.com/jest-matcher-utils/-/jest-matcher-utils-27.5.1.tgz#9c0cdbda8245bc22d2331729d1091308b40cf8ab"
+  integrity sha512-z2uTx/T6LBaCoNWNFWwChLBKYxTMcGBRjAt+2SbP929/Fflb9aa5LGma654Rz8z9HLxsrUaYzxE9T/EFIL/PAw==
+  dependencies:
+    chalk "^4.0.0"
+    jest-diff "^27.5.1"
+    jest-get-type "^27.5.1"
+    pretty-format "^27.5.1"
+
+jest-message-util@^27.5.1:
+  version "27.5.1"
+  resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-27.5.1.tgz#bdda72806da10d9ed6425e12afff38cd1458b6cf"
+  integrity sha512-rMyFe1+jnyAAf+NHwTclDz0eAaLkVDdKVHHBFWsBWHnnh5YeJMNWWsv7AbFYXfK3oTqvL7VTWkhNLu1jX24D+g==
+  dependencies:
+    "@babel/code-frame" "^7.12.13"
+    "@jest/types" "^27.5.1"
+    "@types/stack-utils" "^2.0.0"
+    chalk "^4.0.0"
+    graceful-fs "^4.2.9"
+    micromatch "^4.0.4"
+    pretty-format "^27.5.1"
+    slash "^3.0.0"
+    stack-utils "^2.0.3"
+
+jest-mock@^27.5.1:
+  version "27.5.1"
+  resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-27.5.1.tgz#19948336d49ef4d9c52021d34ac7b5f36ff967d6"
+  integrity sha512-K4jKbY1d4ENhbrG2zuPWaQBvDly+iZ2yAW+T1fATN78hc0sInwn7wZB8XtlNnvHug5RMwV897Xm4LqmPM4e2Og==
+  dependencies:
+    "@jest/types" "^27.5.1"
+    "@types/node" "*"
+
+jest-pnp-resolver@^1.2.2:
+  version "1.2.3"
+  resolved "https://registry.yarnpkg.com/jest-pnp-resolver/-/jest-pnp-resolver-1.2.3.tgz#930b1546164d4ad5937d5540e711d4d38d4cad2e"
+  integrity sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==
+
+jest-regex-util@^27.5.1:
+  version "27.5.1"
+  resolved "https://registry.yarnpkg.com/jest-regex-util/-/jest-regex-util-27.5.1.tgz#4da143f7e9fd1e542d4aa69617b38e4a78365b95"
+  integrity sha512-4bfKq2zie+x16okqDXjXn9ql2B0dScQu+vcwe4TvFVhkVyuWLqpZrZtXxLLWoXYgn0E87I6r6GRYHF7wFZBUvg==
+
+jest-resolve-dependencies@^27.5.1:
+  version "27.5.1"
+  resolved "https://registry.yarnpkg.com/jest-resolve-dependencies/-/jest-resolve-dependencies-27.5.1.tgz#d811ecc8305e731cc86dd79741ee98fed06f1da8"
+  integrity sha512-QQOOdY4PE39iawDn5rzbIePNigfe5B9Z91GDD1ae/xNDlu9kaat8QQ5EKnNmVWPV54hUdxCVwwj6YMgR2O7IOg==
+  dependencies:
+    "@jest/types" "^27.5.1"
+    jest-regex-util "^27.5.1"
+    jest-snapshot "^27.5.1"
+
+jest-resolve@^27.5.1:
+  version "27.5.1"
+  resolved "https://registry.yarnpkg.com/jest-resolve/-/jest-resolve-27.5.1.tgz#a2f1c5a0796ec18fe9eb1536ac3814c23617b384"
+  integrity sha512-FFDy8/9E6CV83IMbDpcjOhumAQPDyETnU2KZ1O98DwTnz8AOBsW/Xv3GySr1mOZdItLR+zDZ7I/UdTFbgSOVCw==
+  dependencies:
+    "@jest/types" "^27.5.1"
+    chalk "^4.0.0"
+    graceful-fs "^4.2.9"
+    jest-haste-map "^27.5.1"
+    jest-pnp-resolver "^1.2.2"
+    jest-util "^27.5.1"
+    jest-validate "^27.5.1"
+    resolve "^1.20.0"
+    resolve.exports "^1.1.0"
+    slash "^3.0.0"
+
+jest-runner@^27.5.1:
+  version "27.5.1"
+  resolved "https://registry.yarnpkg.com/jest-runner/-/jest-runner-27.5.1.tgz#071b27c1fa30d90540805c5645a0ec167c7b62e5"
+  integrity sha512-g4NPsM4mFCOwFKXO4p/H/kWGdJp9V8kURY2lX8Me2drgXqG7rrZAx5kv+5H7wtt/cdFIjhqYx1HrlqWHaOvDaQ==
+  dependencies:
+    "@jest/console" "^27.5.1"
+    "@jest/environment" "^27.5.1"
+    "@jest/test-result" "^27.5.1"
+    "@jest/transform" "^27.5.1"
+    "@jest/types" "^27.5.1"
+    "@types/node" "*"
+    chalk "^4.0.0"
+    emittery "^0.8.1"
+    graceful-fs "^4.2.9"
+    jest-docblock "^27.5.1"
+    jest-environment-jsdom "^27.5.1"
+    jest-environment-node "^27.5.1"
+    jest-haste-map "^27.5.1"
+    jest-leak-detector "^27.5.1"
+    jest-message-util "^27.5.1"
+    jest-resolve "^27.5.1"
+    jest-runtime "^27.5.1"
+    jest-util "^27.5.1"
+    jest-worker "^27.5.1"
+    source-map-support "^0.5.6"
+    throat "^6.0.1"
+
+jest-runtime@^27.5.1:
+  version "27.5.1"
+  resolved "https://registry.yarnpkg.com/jest-runtime/-/jest-runtime-27.5.1.tgz#4896003d7a334f7e8e4a53ba93fb9bcd3db0a1af"
+  integrity sha512-o7gxw3Gf+H2IGt8fv0RiyE1+r83FJBRruoA+FXrlHw6xEyBsU8ugA6IPfTdVyA0w8HClpbK+DGJxH59UrNMx8A==
+  dependencies:
+    "@jest/environment" "^27.5.1"
+    "@jest/fake-timers" "^27.5.1"
+    "@jest/globals" "^27.5.1"
+    "@jest/source-map" "^27.5.1"
+    "@jest/test-result" "^27.5.1"
+    "@jest/transform" "^27.5.1"
+    "@jest/types" "^27.5.1"
+    chalk "^4.0.0"
+    cjs-module-lexer "^1.0.0"
+    collect-v8-coverage "^1.0.0"
+    execa "^5.0.0"
+    glob "^7.1.3"
+    graceful-fs "^4.2.9"
+    jest-haste-map "^27.5.1"
+    jest-message-util "^27.5.1"
+    jest-mock "^27.5.1"
+    jest-regex-util "^27.5.1"
+    jest-resolve "^27.5.1"
+    jest-snapshot "^27.5.1"
+    jest-util "^27.5.1"
+    slash "^3.0.0"
+    strip-bom "^4.0.0"
+
+jest-serializer@^27.5.1:
+  version "27.5.1"
+  resolved "https://registry.yarnpkg.com/jest-serializer/-/jest-serializer-27.5.1.tgz#81438410a30ea66fd57ff730835123dea1fb1f64"
+  integrity sha512-jZCyo6iIxO1aqUxpuBlwTDMkzOAJS4a3eYz3YzgxxVQFwLeSA7Jfq5cbqCY+JLvTDrWirgusI/0KwxKMgrdf7w==
+  dependencies:
+    "@types/node" "*"
+    graceful-fs "^4.2.9"
+
+jest-snapshot@^27.5.1:
+  version "27.5.1"
+  resolved "https://registry.yarnpkg.com/jest-snapshot/-/jest-snapshot-27.5.1.tgz#b668d50d23d38054a51b42c4039cab59ae6eb6a1"
+  integrity sha512-yYykXI5a0I31xX67mgeLw1DZ0bJB+gpq5IpSuCAoyDi0+BhgU/RIrL+RTzDmkNTchvDFWKP8lp+w/42Z3us5sA==
+  dependencies:
+    "@babel/core" "^7.7.2"
+    "@babel/generator" "^7.7.2"
+    "@babel/plugin-syntax-typescript" "^7.7.2"
+    "@babel/traverse" "^7.7.2"
+    "@babel/types" "^7.0.0"
+    "@jest/transform" "^27.5.1"
+    "@jest/types" "^27.5.1"
+    "@types/babel__traverse" "^7.0.4"
+    "@types/prettier" "^2.1.5"
+    babel-preset-current-node-syntax "^1.0.0"
+    chalk "^4.0.0"
+    expect "^27.5.1"
+    graceful-fs "^4.2.9"
+    jest-diff "^27.5.1"
+    jest-get-type "^27.5.1"
+    jest-haste-map "^27.5.1"
+    jest-matcher-utils "^27.5.1"
+    jest-message-util "^27.5.1"
+    jest-util "^27.5.1"
+    natural-compare "^1.4.0"
+    pretty-format "^27.5.1"
+    semver "^7.3.2"
+
+jest-util@^27.0.0, jest-util@^27.5.1:
+  version "27.5.1"
+  resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-27.5.1.tgz#3ba9771e8e31a0b85da48fe0b0891fb86c01c2f9"
+  integrity sha512-Kv2o/8jNvX1MQ0KGtw480E/w4fBCDOnH6+6DmeKi6LZUIlKA5kwY0YNdlzaWTiVgxqAqik11QyxDOKk543aKXw==
+  dependencies:
+    "@jest/types" "^27.5.1"
+    "@types/node" "*"
+    chalk "^4.0.0"
+    ci-info "^3.2.0"
+    graceful-fs "^4.2.9"
+    picomatch "^2.2.3"
+
+jest-validate@^27.5.1:
+  version "27.5.1"
+  resolved "https://registry.yarnpkg.com/jest-validate/-/jest-validate-27.5.1.tgz#9197d54dc0bdb52260b8db40b46ae668e04df067"
+  integrity sha512-thkNli0LYTmOI1tDB3FI1S1RTp/Bqyd9pTarJwL87OIBFuqEb5Apv5EaApEudYg4g86e3CT6kM0RowkhtEnCBQ==
+  dependencies:
+    "@jest/types" "^27.5.1"
+    camelcase "^6.2.0"
+    chalk "^4.0.0"
+    jest-get-type "^27.5.1"
+    leven "^3.1.0"
+    pretty-format "^27.5.1"
+
+jest-watcher@^27.5.1:
+  version "27.5.1"
+  resolved "https://registry.yarnpkg.com/jest-watcher/-/jest-watcher-27.5.1.tgz#71bd85fb9bde3a2c2ec4dc353437971c43c642a2"
+  integrity sha512-z676SuD6Z8o8qbmEGhoEUFOM1+jfEiL3DXHK/xgEiG2EyNYfFG60jluWcupY6dATjfEsKQuibReS1djInQnoVw==
+  dependencies:
+    "@jest/test-result" "^27.5.1"
+    "@jest/types" "^27.5.1"
+    "@types/node" "*"
+    ansi-escapes "^4.2.1"
+    chalk "^4.0.0"
+    jest-util "^27.5.1"
+    string-length "^4.0.1"
+
+jest-worker@^27.5.1:
+  version "27.5.1"
+  resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.5.1.tgz#8d146f0900e8973b106b6f73cc1e9a8cb86f8db0"
+  integrity sha512-7vuh85V5cdDofPyxn58nrPjBktZo0u9x1g8WtjQol+jZDaE+fhN+cIvTj11GndBnMnyfrUOG1sZQxCdjKh+DKg==
+  dependencies:
+    "@types/node" "*"
+    merge-stream "^2.0.0"
+    supports-color "^8.0.0"
+
+jest@^27.5.1:
+  version "27.5.1"
+  resolved "https://registry.yarnpkg.com/jest/-/jest-27.5.1.tgz#dadf33ba70a779be7a6fc33015843b51494f63fc"
+  integrity sha512-Yn0mADZB89zTtjkPJEXwrac3LHudkQMR+Paqa8uxJHCBr9agxztUifWCyiYrjhMPBoUVBjyny0I7XH6ozDr7QQ==
+  dependencies:
+    "@jest/core" "^27.5.1"
+    import-local "^3.0.2"
+    jest-cli "^27.5.1"
+
+js-tokens@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/js-tokens/-/js-tokens-4.0.0.tgz#19203fb59991df98e3a287050d4647cdeaf32499"
+  integrity sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==
+
+js-yaml@^3.13.1:
+  version "3.14.1"
+  resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537"
+  integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==
+  dependencies:
+    argparse "^1.0.7"
+    esprima "^4.0.0"
+
+js-yaml@^4.1.0:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602"
+  integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==
+  dependencies:
+    argparse "^2.0.1"
+
+jsdom@^16.6.0:
+  version "16.7.0"
+  resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-16.7.0.tgz#918ae71965424b197c819f8183a754e18977b710"
+  integrity sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw==
+  dependencies:
+    abab "^2.0.5"
+    acorn "^8.2.4"
+    acorn-globals "^6.0.0"
+    cssom "^0.4.4"
+    cssstyle "^2.3.0"
+    data-urls "^2.0.0"
+    decimal.js "^10.2.1"
+    domexception "^2.0.1"
+    escodegen "^2.0.0"
+    form-data "^3.0.0"
+    html-encoding-sniffer "^2.0.1"
+    http-proxy-agent "^4.0.1"
+    https-proxy-agent "^5.0.0"
+    is-potential-custom-element-name "^1.0.1"
+    nwsapi "^2.2.0"
+    parse5 "6.0.1"
+    saxes "^5.0.1"
+    symbol-tree "^3.2.4"
+    tough-cookie "^4.0.0"
+    w3c-hr-time "^1.0.2"
+    w3c-xmlserializer "^2.0.0"
+    webidl-conversions "^6.1.0"
+    whatwg-encoding "^1.0.5"
+    whatwg-mimetype "^2.3.0"
+    whatwg-url "^8.5.0"
+    ws "^7.4.6"
+    xml-name-validator "^3.0.0"
+
+jsesc@^2.5.1:
+  version "2.5.2"
+  resolved "https://registry.yarnpkg.com/jsesc/-/jsesc-2.5.2.tgz#80564d2e483dacf6e8ef209650a67df3f0c283a4"
+  integrity sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==
+
+json-parse-better-errors@^1.0.1:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9"
+  integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==
+
+json-parse-even-better-errors@^2.3.0:
+  version "2.3.1"
+  resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d"
+  integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==
+
+json-schema-traverse@^0.4.1:
+  version "0.4.1"
+  resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
+  integrity sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==
+
+json-stable-stringify-without-jsonify@^1.0.1:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651"
+  integrity sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==
+
+json5@2.x, json5@^2.2.2:
+  version "2.2.3"
+  resolved "https://registry.yarnpkg.com/json5/-/json5-2.2.3.tgz#78cd6f1a19bdc12b73db5ad0c61efd66c1e29283"
+  integrity sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==
+
+kleur@^3.0.3:
+  version "3.0.3"
+  resolved "https://registry.yarnpkg.com/kleur/-/kleur-3.0.3.tgz#a79c9ecc86ee1ce3fa6206d1216c501f147fc07e"
+  integrity sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==
+
+leven@^3.1.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/leven/-/leven-3.1.0.tgz#77891de834064cccba82ae7842bb6b14a13ed7f2"
+  integrity sha512-qsda+H8jTaUaN/x5vzW2rzc+8Rw4TAQ/4KjB46IwK5VH+IlVeeeje/EoZRpiXvIqjFgK84QffqPztGI3VBLG1A==
+
+levn@^0.4.1:
+  version "0.4.1"
+  resolved "https://registry.yarnpkg.com/levn/-/levn-0.4.1.tgz#ae4562c007473b932a6200d403268dd2fffc6ade"
+  integrity sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==
+  dependencies:
+    prelude-ls "^1.2.1"
+    type-check "~0.4.0"
+
+lines-and-columns@^1.1.6:
+  version "1.2.4"
+  resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632"
+  integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==
+
+load-json-file@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b"
+  integrity sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==
+  dependencies:
+    graceful-fs "^4.1.2"
+    parse-json "^4.0.0"
+    pify "^3.0.0"
+    strip-bom "^3.0.0"
+
+locate-path@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-5.0.0.tgz#1afba396afd676a6d42504d0a67a3a7eb9f62aa0"
+  integrity sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==
+  dependencies:
+    p-locate "^4.1.0"
+
+locate-path@^6.0.0:
+  version "6.0.0"
+  resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-6.0.0.tgz#55321eb309febbc59c4801d931a72452a681d286"
+  integrity sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==
+  dependencies:
+    p-locate "^5.0.0"
+
+lodash.memoize@4.x:
+  version "4.1.2"
+  resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
+  integrity sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==
+
+lodash.merge@^4.6.2:
+  version "4.6.2"
+  resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a"
+  integrity sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==
+
+lodash@^4.7.0:
+  version "4.17.21"
+  resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
+  integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
+
+lru-cache@^5.1.1:
+  version "5.1.1"
+  resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920"
+  integrity sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==
+  dependencies:
+    yallist "^3.0.2"
+
+lru-cache@^6.0.0:
+  version "6.0.0"
+  resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-6.0.0.tgz#6d6fe6570ebd96aaf90fcad1dafa3b2566db3a94"
+  integrity sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==
+  dependencies:
+    yallist "^4.0.0"
+
+make-dir@^3.0.0:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-3.1.0.tgz#415e967046b3a7f1d185277d84aa58203726a13f"
+  integrity sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==
+  dependencies:
+    semver "^6.0.0"
+
+make-error@1.x:
+  version "1.3.6"
+  resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2"
+  integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==
+
+makeerror@1.0.12:
+  version "1.0.12"
+  resolved "https://registry.yarnpkg.com/makeerror/-/makeerror-1.0.12.tgz#3e5dd2079a82e812e983cc6610c4a2cb0eaa801a"
+  integrity sha512-JmqCvUhmt43madlpFzG4BQzG2Z3m6tvQDNKdClZnO3VbIudJYmxsT0FNJMeiB2+JTSlTQTSbU8QdesVmwJcmLg==
+  dependencies:
+    tmpl "1.0.5"
+
+memorystream@^0.3.1:
+  version "0.3.1"
+  resolved "https://registry.yarnpkg.com/memorystream/-/memorystream-0.3.1.tgz#86d7090b30ce455d63fbae12dda51a47ddcaf9b2"
+  integrity sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==
+
+merge-stream@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60"
+  integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==
+
+merge2@^1.3.0, merge2@^1.4.1:
+  version "1.4.1"
+  resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae"
+  integrity sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==
+
+micromatch@^4.0.4:
+  version "4.0.5"
+  resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6"
+  integrity sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==
+  dependencies:
+    braces "^3.0.2"
+    picomatch "^2.3.1"
+
+mime-db@1.52.0:
+  version "1.52.0"
+  resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
+  integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
+
+mime-types@^2.1.12:
+  version "2.1.35"
+  resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
+  integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
+  dependencies:
+    mime-db "1.52.0"
+
+mimic-fn@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/mimic-fn/-/mimic-fn-2.1.0.tgz#7ed2c2ccccaf84d3ffcb7a69b57711fc2083401b"
+  integrity sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==
+
+minimatch@^3.0.4, minimatch@^3.0.5, minimatch@^3.1.1, minimatch@^3.1.2:
+  version "3.1.2"
+  resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
+  integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
+  dependencies:
+    brace-expansion "^1.1.7"
+
+mkdirp@^1.0.4:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
+  integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
+
+ms@2.1.2:
+  version "2.1.2"
+  resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009"
+  integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==
+
+natural-compare-lite@^1.4.0:
+  version "1.4.0"
+  resolved "https://registry.yarnpkg.com/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz#17b09581988979fddafe0201e931ba933c96cbb4"
+  integrity sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==
+
+natural-compare@^1.4.0:
+  version "1.4.0"
+  resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7"
+  integrity sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==
+
+ncp@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/ncp/-/ncp-2.0.0.tgz#195a21d6c46e361d2fb1281ba38b91e9df7bdbb3"
+  integrity sha512-zIdGUrPRFTUELUvr3Gmc7KZ2Sw/h1PiVM0Af/oHB6zgnV1ikqSfRk+TOufi79aHYCW3NiOXmr1BP5nWbzojLaA==
+
+nice-try@^1.0.4:
+  version "1.0.5"
+  resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
+  integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
+
+node-int64@^0.4.0:
+  version "0.4.0"
+  resolved "https://registry.yarnpkg.com/node-int64/-/node-int64-0.4.0.tgz#87a9065cdb355d3182d8f94ce11188b825c68a3b"
+  integrity sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==
+
+node-releases@^2.0.12:
+  version "2.0.13"
+  resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.13.tgz#d5ed1627c23e3461e819b02e57b75e4899b1c81d"
+  integrity sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==
+
+normalize-package-data@^2.3.2:
+  version "2.5.0"
+  resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8"
+  integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==
+  dependencies:
+    hosted-git-info "^2.1.4"
+    resolve "^1.10.0"
+    semver "2 || 3 || 4 || 5"
+    validate-npm-package-license "^3.0.1"
+
+normalize-path@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65"
+  integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==
+
+npm-run-all@^4.1.5:
+  version "4.1.5"
+  resolved "https://registry.yarnpkg.com/npm-run-all/-/npm-run-all-4.1.5.tgz#04476202a15ee0e2e214080861bff12a51d98fba"
+  integrity sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==
+  dependencies:
+    ansi-styles "^3.2.1"
+    chalk "^2.4.1"
+    cross-spawn "^6.0.5"
+    memorystream "^0.3.1"
+    minimatch "^3.0.4"
+    pidtree "^0.3.0"
+    read-pkg "^3.0.0"
+    shell-quote "^1.6.1"
+    string.prototype.padend "^3.0.0"
+
+npm-run-path@^4.0.1:
+  version "4.0.1"
+  resolved "https://registry.yarnpkg.com/npm-run-path/-/npm-run-path-4.0.1.tgz#b7ecd1e5ed53da8e37a55e1c2269e0b97ed748ea"
+  integrity sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==
+  dependencies:
+    path-key "^3.0.0"
+
+nwsapi@^2.2.0:
+  version "2.2.7"
+  resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.7.tgz#738e0707d3128cb750dddcfe90e4610482df0f30"
+  integrity sha512-ub5E4+FBPKwAZx0UwIQOjYWGHTEq5sPqHQNRN8Z9e4A7u3Tj1weLJsL59yH9vmvqEtBHaOmT6cYQKIZOxp35FQ==
+
+object-inspect@^1.12.3, object-inspect@^1.9.0:
+  version "1.12.3"
+  resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.3.tgz#ba62dffd67ee256c8c086dfae69e016cd1f198b9"
+  integrity sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==
+
+object-keys@^1.1.1:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
+  integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==
+
+object.assign@^4.1.4:
+  version "4.1.4"
+  resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.4.tgz#9673c7c7c351ab8c4d0b516f4343ebf4dfb7799f"
+  integrity sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==
+  dependencies:
+    call-bind "^1.0.2"
+    define-properties "^1.1.4"
+    has-symbols "^1.0.3"
+    object-keys "^1.1.1"
+
+once@^1.3.0:
+  version "1.4.0"
+  resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
+  integrity sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==
+  dependencies:
+    wrappy "1"
+
+onetime@^5.1.2:
+  version "5.1.2"
+  resolved "https://registry.yarnpkg.com/onetime/-/onetime-5.1.2.tgz#d0e96ebb56b07476df1dd9c4806e5237985ca45e"
+  integrity sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==
+  dependencies:
+    mimic-fn "^2.1.0"
+
+optionator@^0.9.3:
+  version "0.9.3"
+  resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.9.3.tgz#007397d44ed1872fdc6ed31360190f81814e2c64"
+  integrity sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==
+  dependencies:
+    "@aashutoshrathi/word-wrap" "^1.2.3"
+    deep-is "^0.1.3"
+    fast-levenshtein "^2.0.6"
+    levn "^0.4.1"
+    prelude-ls "^1.2.1"
+    type-check "^0.4.0"
+
+p-limit@^2.2.0:
+  version "2.3.0"
+  resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-2.3.0.tgz#3dd33c647a214fdfffd835933eb086da0dc21db1"
+  integrity sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==
+  dependencies:
+    p-try "^2.0.0"
+
+p-limit@^3.0.2:
+  version "3.1.0"
+  resolved "https://registry.yarnpkg.com/p-limit/-/p-limit-3.1.0.tgz#e1daccbe78d0d1388ca18c64fea38e3e57e3706b"
+  integrity sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==
+  dependencies:
+    yocto-queue "^0.1.0"
+
+p-locate@^4.1.0:
+  version "4.1.0"
+  resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-4.1.0.tgz#a3428bb7088b3a60292f66919278b7c297ad4f07"
+  integrity sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==
+  dependencies:
+    p-limit "^2.2.0"
+
+p-locate@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/p-locate/-/p-locate-5.0.0.tgz#83c8315c6785005e3bd021839411c9e110e6d834"
+  integrity sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==
+  dependencies:
+    p-limit "^3.0.2"
+
+p-try@^2.0.0:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6"
+  integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==
+
+parent-module@^1.0.0:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2"
+  integrity sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==
+  dependencies:
+    callsites "^3.0.0"
+
+parse-json@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0"
+  integrity sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==
+  dependencies:
+    error-ex "^1.3.1"
+    json-parse-better-errors "^1.0.1"
+
+parse-json@^5.2.0:
+  version "5.2.0"
+  resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-5.2.0.tgz#c76fc66dee54231c962b22bcc8a72cf2f99753cd"
+  integrity sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==
+  dependencies:
+    "@babel/code-frame" "^7.0.0"
+    error-ex "^1.3.1"
+    json-parse-even-better-errors "^2.3.0"
+    lines-and-columns "^1.1.6"
+
+parse5@6.0.1:
+  version "6.0.1"
+  resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b"
+  integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==
+
+path-exists@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/path-exists/-/path-exists-4.0.0.tgz#513bdbe2d3b95d7762e8c1137efa195c6c61b5b3"
+  integrity sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==
+
+path-is-absolute@^1.0.0:
+  version "1.0.1"
+  resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
+  integrity sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==
+
+path-key@^2.0.1:
+  version "2.0.1"
+  resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40"
+  integrity sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==
+
+path-key@^3.0.0, path-key@^3.1.0:
+  version "3.1.1"
+  resolved "https://registry.yarnpkg.com/path-key/-/path-key-3.1.1.tgz#581f6ade658cbba65a0d3380de7753295054f375"
+  integrity sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==
+
+path-parse@^1.0.7:
+  version "1.0.7"
+  resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
+  integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
+
+path-type@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f"
+  integrity sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==
+  dependencies:
+    pify "^3.0.0"
+
+path-type@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
+  integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
+
+picocolors@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/picocolors/-/picocolors-1.0.0.tgz#cb5bdc74ff3f51892236eaf79d68bc44564ab81c"
+  integrity sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==
+
+picomatch@^2.0.4, picomatch@^2.2.3, picomatch@^2.3.1:
+  version "2.3.1"
+  resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42"
+  integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==
+
+pidtree@^0.3.0:
+  version "0.3.1"
+  resolved "https://registry.yarnpkg.com/pidtree/-/pidtree-0.3.1.tgz#ef09ac2cc0533df1f3250ccf2c4d366b0d12114a"
+  integrity sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==
+
+pify@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176"
+  integrity sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==
+
+pirates@^4.0.4:
+  version "4.0.6"
+  resolved "https://registry.yarnpkg.com/pirates/-/pirates-4.0.6.tgz#3018ae32ecfcff6c29ba2267cbf21166ac1f36b9"
+  integrity sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==
+
+pkg-dir@^4.2.0:
+  version "4.2.0"
+  resolved "https://registry.yarnpkg.com/pkg-dir/-/pkg-dir-4.2.0.tgz#f099133df7ede422e81d1d8448270eeb3e4261f3"
+  integrity sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==
+  dependencies:
+    find-up "^4.0.0"
+
+prelude-ls@^1.2.1:
+  version "1.2.1"
+  resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
+  integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==
+
+prettier@^2.6.2:
+  version "2.8.8"
+  resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.8.8.tgz#e8c5d7e98a4305ffe3de2e1fc4aca1a71c28b1da"
+  integrity sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==
+
+pretty-format@^27.0.0, pretty-format@^27.5.1:
+  version "27.5.1"
+  resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-27.5.1.tgz#2181879fdea51a7a5851fb39d920faa63f01d88e"
+  integrity sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==
+  dependencies:
+    ansi-regex "^5.0.1"
+    ansi-styles "^5.0.0"
+    react-is "^17.0.1"
+
+prompts@^2.0.1:
+  version "2.4.2"
+  resolved "https://registry.yarnpkg.com/prompts/-/prompts-2.4.2.tgz#7b57e73b3a48029ad10ebd44f74b01722a4cb069"
+  integrity sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==
+  dependencies:
+    kleur "^3.0.3"
+    sisteransi "^1.0.5"
+
+psl@^1.1.33:
+  version "1.9.0"
+  resolved "https://registry.yarnpkg.com/psl/-/psl-1.9.0.tgz#d0df2a137f00794565fcaf3b2c00cd09f8d5a5a7"
+  integrity sha512-E/ZsdU4HLs/68gYzgGTkMicWTLPdAftJLfJFlLUAAKZGkStNU72sZjT66SnMDVOfOWY/YAoiD7Jxa9iHvngcag==
+
+punycode@^2.1.0, punycode@^2.1.1:
+  version "2.3.0"
+  resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.3.0.tgz#f67fa67c94da8f4d0cfff981aee4118064199b8f"
+  integrity sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==
+
+querystringify@^2.1.1:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/querystringify/-/querystringify-2.2.0.tgz#3345941b4153cb9d082d8eee4cda2016a9aef7f6"
+  integrity sha512-FIqgj2EUvTa7R50u0rGsyTftzjYmv/a3hO345bZNrqabNqjtgiDMgmo4mkUjd+nzU5oF3dClKqFIPUKybUyqoQ==
+
+queue-microtask@^1.2.2:
+  version "1.2.3"
+  resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243"
+  integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==
+
+react-is@^17.0.1:
+  version "17.0.2"
+  resolved "https://registry.yarnpkg.com/react-is/-/react-is-17.0.2.tgz#e691d4a8e9c789365655539ab372762b0efb54f0"
+  integrity sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==
+
+read-pkg@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-3.0.0.tgz#9cbc686978fee65d16c00e2b19c237fcf6e38389"
+  integrity sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==
+  dependencies:
+    load-json-file "^4.0.0"
+    normalize-package-data "^2.3.2"
+    path-type "^3.0.0"
+
+regexp.prototype.flags@^1.4.3:
+  version "1.5.0"
+  resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz#fe7ce25e7e4cca8db37b6634c8a2c7009199b9cb"
+  integrity sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==
+  dependencies:
+    call-bind "^1.0.2"
+    define-properties "^1.2.0"
+    functions-have-names "^1.2.3"
+
+require-directory@^2.1.1:
+  version "2.1.1"
+  resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
+  integrity sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==
+
+requires-port@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/requires-port/-/requires-port-1.0.0.tgz#925d2601d39ac485e091cf0da5c6e694dc3dcaff"
+  integrity sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==
+
+resolve-cwd@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-3.0.0.tgz#0f0075f1bb2544766cf73ba6a6e2adfebcb13f2d"
+  integrity sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==
+  dependencies:
+    resolve-from "^5.0.0"
+
+resolve-from@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
+  integrity sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==
+
+resolve-from@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69"
+  integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==
+
+resolve.exports@^1.1.0:
+  version "1.1.1"
+  resolved "https://registry.yarnpkg.com/resolve.exports/-/resolve.exports-1.1.1.tgz#05cfd5b3edf641571fd46fa608b610dda9ead999"
+  integrity sha512-/NtpHNDN7jWhAaQ9BvBUYZ6YTXsRBgfqWFWP7BZBaoMJO/I3G5OFzvTuWNlZC3aPjins1F+TNrLKsGbH4rfsRQ==
+
+resolve@^1.10.0, resolve@^1.20.0:
+  version "1.22.2"
+  resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.2.tgz#0ed0943d4e301867955766c9f3e1ae6d01c6845f"
+  integrity sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==
+  dependencies:
+    is-core-module "^2.11.0"
+    path-parse "^1.0.7"
+    supports-preserve-symlinks-flag "^1.0.0"
+
+reusify@^1.0.4:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76"
+  integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==
+
+rimraf@^3.0.0, rimraf@^3.0.2:
+  version "3.0.2"
+  resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a"
+  integrity sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==
+  dependencies:
+    glob "^7.1.3"
+
+run-parallel@^1.1.9:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/run-parallel/-/run-parallel-1.2.0.tgz#66d1368da7bdf921eb9d95bd1a9229e7f21a43ee"
+  integrity sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==
+  dependencies:
+    queue-microtask "^1.2.2"
+
+safe-regex-test@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.0.tgz#793b874d524eb3640d1873aad03596db2d4f2295"
+  integrity sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==
+  dependencies:
+    call-bind "^1.0.2"
+    get-intrinsic "^1.1.3"
+    is-regex "^1.1.4"
+
+"safer-buffer@>= 2.1.2 < 3":
+  version "2.1.2"
+  resolved "https://registry.yarnpkg.com/safer-buffer/-/safer-buffer-2.1.2.tgz#44fa161b0187b9549dd84bb91802f9bd8385cd6a"
+  integrity sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==
+
+saxes@^5.0.1:
+  version "5.0.1"
+  resolved "https://registry.yarnpkg.com/saxes/-/saxes-5.0.1.tgz#eebab953fa3b7608dbe94e5dadb15c888fa6696d"
+  integrity sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==
+  dependencies:
+    xmlchars "^2.2.0"
+
+"semver@2 || 3 || 4 || 5", semver@^5.5.0:
+  version "5.7.1"
+  resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
+  integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
+
+semver@7.x, semver@^7.3.2, semver@^7.3.7:
+  version "7.5.4"
+  resolved "https://registry.yarnpkg.com/semver/-/semver-7.5.4.tgz#483986ec4ed38e1c6c48c34894a9182dbff68a6e"
+  integrity sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==
+  dependencies:
+    lru-cache "^6.0.0"
+
+semver@^6.0.0, semver@^6.3.0:
+  version "6.3.0"
+  resolved "https://registry.yarnpkg.com/semver/-/semver-6.3.0.tgz#ee0a64c8af5e8ceea67687b133761e1becbd1d3d"
+  integrity sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==
+
+shebang-command@^1.2.0:
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"
+  integrity sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==
+  dependencies:
+    shebang-regex "^1.0.0"
+
+shebang-command@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea"
+  integrity sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==
+  dependencies:
+    shebang-regex "^3.0.0"
+
+shebang-regex@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3"
+  integrity sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==
+
+shebang-regex@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-3.0.0.tgz#ae16f1644d873ecad843b0307b143362d4c42172"
+  integrity sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==
+
+shell-quote@^1.6.1:
+  version "1.8.1"
+  resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.8.1.tgz#6dbf4db75515ad5bac63b4f1894c3a154c766680"
+  integrity sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==
+
+side-channel@^1.0.4:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf"
+  integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==
+  dependencies:
+    call-bind "^1.0.0"
+    get-intrinsic "^1.0.2"
+    object-inspect "^1.9.0"
+
+signal-exit@^3.0.2, signal-exit@^3.0.3:
+  version "3.0.7"
+  resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9"
+  integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==
+
+sisteransi@^1.0.5:
+  version "1.0.5"
+  resolved "https://registry.yarnpkg.com/sisteransi/-/sisteransi-1.0.5.tgz#134d681297756437cc05ca01370d3a7a571075ed"
+  integrity sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==
+
+slash@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634"
+  integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==
+
+source-map-support@^0.5.6:
+  version "0.5.21"
+  resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f"
+  integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==
+  dependencies:
+    buffer-from "^1.0.0"
+    source-map "^0.6.0"
+
+source-map@^0.6.0, source-map@^0.6.1, source-map@~0.6.1:
+  version "0.6.1"
+  resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263"
+  integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==
+
+source-map@^0.7.3:
+  version "0.7.4"
+  resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.7.4.tgz#a9bbe705c9d8846f4e08ff6765acf0f1b0898656"
+  integrity sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==
+
+spdx-correct@^3.0.0:
+  version "3.2.0"
+  resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.2.0.tgz#4f5ab0668f0059e34f9c00dce331784a12de4e9c"
+  integrity sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==
+  dependencies:
+    spdx-expression-parse "^3.0.0"
+    spdx-license-ids "^3.0.0"
+
+spdx-exceptions@^2.1.0:
+  version "2.3.0"
+  resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d"
+  integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==
+
+spdx-expression-parse@^3.0.0:
+  version "3.0.1"
+  resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679"
+  integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==
+  dependencies:
+    spdx-exceptions "^2.1.0"
+    spdx-license-ids "^3.0.0"
+
+spdx-license-ids@^3.0.0:
+  version "3.0.13"
+  resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.13.tgz#7189a474c46f8d47c7b0da4b987bb45e908bd2d5"
+  integrity sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w==
+
+sprintf-js@~1.0.2:
+  version "1.0.3"
+  resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c"
+  integrity sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==
+
+stack-utils@^2.0.3:
+  version "2.0.6"
+  resolved "https://registry.yarnpkg.com/stack-utils/-/stack-utils-2.0.6.tgz#aaf0748169c02fc33c8232abccf933f54a1cc34f"
+  integrity sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==
+  dependencies:
+    escape-string-regexp "^2.0.0"
+
+string-length@^4.0.1:
+  version "4.0.2"
+  resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.2.tgz#a8a8dc7bd5c1a82b9b3c8b87e125f66871b6e57a"
+  integrity sha512-+l6rNN5fYHNhZZy41RXsYptCjA2Igmq4EG7kZAYFQI1E1VTXarr6ZPXBg6eq7Y6eK4FEhY6AJlyuFIb/v/S0VQ==
+  dependencies:
+    char-regex "^1.0.2"
+    strip-ansi "^6.0.0"
+
+string-width@^4.1.0, string-width@^4.2.0:
+  version "4.2.3"
+  resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010"
+  integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==
+  dependencies:
+    emoji-regex "^8.0.0"
+    is-fullwidth-code-point "^3.0.0"
+    strip-ansi "^6.0.1"
+
+string.prototype.padend@^3.0.0:
+  version "3.1.4"
+  resolved "https://registry.yarnpkg.com/string.prototype.padend/-/string.prototype.padend-3.1.4.tgz#2c43bb3a89eb54b6750de5942c123d6c98dd65b6"
+  integrity sha512-67otBXoksdjsnXXRUq+KMVTdlVRZ2af422Y0aTyTjVaoQkGr3mxl2Bc5emi7dOQ3OGVVQQskmLEWwFXwommpNw==
+  dependencies:
+    call-bind "^1.0.2"
+    define-properties "^1.1.4"
+    es-abstract "^1.20.4"
+
+string.prototype.trim@^1.2.7:
+  version "1.2.7"
+  resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz#a68352740859f6893f14ce3ef1bb3037f7a90533"
+  integrity sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==
+  dependencies:
+    call-bind "^1.0.2"
+    define-properties "^1.1.4"
+    es-abstract "^1.20.4"
+
+string.prototype.trimend@^1.0.6:
+  version "1.0.6"
+  resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz#c4a27fa026d979d79c04f17397f250a462944533"
+  integrity sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==
+  dependencies:
+    call-bind "^1.0.2"
+    define-properties "^1.1.4"
+    es-abstract "^1.20.4"
+
+string.prototype.trimstart@^1.0.6:
+  version "1.0.6"
+  resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz#e90ab66aa8e4007d92ef591bbf3cd422c56bdcf4"
+  integrity sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==
+  dependencies:
+    call-bind "^1.0.2"
+    define-properties "^1.1.4"
+    es-abstract "^1.20.4"
+
+strip-ansi@^6.0.0, strip-ansi@^6.0.1:
+  version "6.0.1"
+  resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9"
+  integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==
+  dependencies:
+    ansi-regex "^5.0.1"
+
+strip-bom@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3"
+  integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==
+
+strip-bom@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-4.0.0.tgz#9c3505c1db45bcedca3d9cf7a16f5c5aa3901878"
+  integrity sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==
+
+strip-final-newline@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad"
+  integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==
+
+strip-json-comments@^3.1.0, strip-json-comments@^3.1.1:
+  version "3.1.1"
+  resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
+  integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
+
+supports-color@^5.3.0:
+  version "5.5.0"
+  resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
+  integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
+  dependencies:
+    has-flag "^3.0.0"
+
+supports-color@^7.0.0, supports-color@^7.1.0:
+  version "7.2.0"
+  resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da"
+  integrity sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==
+  dependencies:
+    has-flag "^4.0.0"
+
+supports-color@^8.0.0:
+  version "8.1.1"
+  resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c"
+  integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==
+  dependencies:
+    has-flag "^4.0.0"
+
+supports-hyperlinks@^2.0.0:
+  version "2.3.0"
+  resolved "https://registry.yarnpkg.com/supports-hyperlinks/-/supports-hyperlinks-2.3.0.tgz#3943544347c1ff90b15effb03fc14ae45ec10624"
+  integrity sha512-RpsAZlpWcDwOPQA22aCH4J0t7L8JmAvsCxfOSEwm7cQs3LshN36QaTkwd70DnBOXDWGssw2eUoc8CaRWT0XunA==
+  dependencies:
+    has-flag "^4.0.0"
+    supports-color "^7.0.0"
+
+supports-preserve-symlinks-flag@^1.0.0:
+  version "1.0.0"
+  resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
+  integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
+
+symbol-tree@^3.2.4:
+  version "3.2.4"
+  resolved "https://registry.yarnpkg.com/symbol-tree/-/symbol-tree-3.2.4.tgz#430637d248ba77e078883951fb9aa0eed7c63fa2"
+  integrity sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==
+
+terminal-link@^2.0.0:
+  version "2.1.1"
+  resolved "https://registry.yarnpkg.com/terminal-link/-/terminal-link-2.1.1.tgz#14a64a27ab3c0df933ea546fba55f2d078edc994"
+  integrity sha512-un0FmiRUQNr5PJqy9kP7c40F5BOfpGlYTrxonDChEZB7pzZxRNp/bt+ymiy9/npwXya9KH99nJ/GXFIiUkYGFQ==
+  dependencies:
+    ansi-escapes "^4.2.1"
+    supports-hyperlinks "^2.0.0"
+
+test-exclude@^6.0.0:
+  version "6.0.0"
+  resolved "https://registry.yarnpkg.com/test-exclude/-/test-exclude-6.0.0.tgz#04a8698661d805ea6fa293b6cb9e63ac044ef15e"
+  integrity sha512-cAGWPIyOHU6zlmg88jwm7VRyXnMN7iV68OGAbYDk/Mh/xC/pzVPlQtY6ngoIH/5/tciuhGfvESU8GrHrcxD56w==
+  dependencies:
+    "@istanbuljs/schema" "^0.1.2"
+    glob "^7.1.4"
+    minimatch "^3.0.4"
+
+text-table@^0.2.0:
+  version "0.2.0"
+  resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4"
+  integrity sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==
+
+throat@^6.0.1:
+  version "6.0.2"
+  resolved "https://registry.yarnpkg.com/throat/-/throat-6.0.2.tgz#51a3fbb5e11ae72e2cf74861ed5c8020f89f29fe"
+  integrity sha512-WKexMoJj3vEuK0yFEapj8y64V0A6xcuPuK9Gt1d0R+dzCSJc0lHqQytAbSB4cDAK0dWh4T0E2ETkoLE2WZ41OQ==
+
+tmpl@1.0.5:
+  version "1.0.5"
+  resolved "https://registry.yarnpkg.com/tmpl/-/tmpl-1.0.5.tgz#8683e0b902bb9c20c4f726e3c0b69f36518c07cc"
+  integrity sha512-3f0uOEAQwIqGuWW2MVzYg8fV/QNnc/IpuJNG837rLuczAaLVHslWHZQj4IGiEl5Hs3kkbhwL9Ab7Hrsmuj+Smw==
+
+to-fast-properties@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e"
+  integrity sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==
+
+to-regex-range@^5.0.1:
+  version "5.0.1"
+  resolved "https://registry.yarnpkg.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
+  integrity sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==
+  dependencies:
+    is-number "^7.0.0"
+
+tough-cookie@^4.0.0:
+  version "4.1.3"
+  resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.1.3.tgz#97b9adb0728b42280aa3d814b6b999b2ff0318bf"
+  integrity sha512-aX/y5pVRkfRnfmuX+OdbSdXvPe6ieKX/G2s7e98f4poJHnqH3281gDPm/metm6E/WRamfx7WC4HUqkWHfQHprw==
+  dependencies:
+    psl "^1.1.33"
+    punycode "^2.1.1"
+    universalify "^0.2.0"
+    url-parse "^1.5.3"
+
+tr46@^2.1.0:
+  version "2.1.0"
+  resolved "https://registry.yarnpkg.com/tr46/-/tr46-2.1.0.tgz#fa87aa81ca5d5941da8cbf1f9b749dc969a4e240"
+  integrity sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==
+  dependencies:
+    punycode "^2.1.1"
+
+ts-jest@^27.1.3:
+  version "27.1.5"
+  resolved "https://registry.yarnpkg.com/ts-jest/-/ts-jest-27.1.5.tgz#0ddf1b163fbaae3d5b7504a1e65c914a95cff297"
+  integrity sha512-Xv6jBQPoBEvBq/5i2TeSG9tt/nqkbpcurrEG1b+2yfBrcJelOZF9Ml6dmyMh7bcW9JyFbRYpR5rxROSlBLTZHA==
+  dependencies:
+    bs-logger "0.x"
+    fast-json-stable-stringify "2.x"
+    jest-util "^27.0.0"
+    json5 "2.x"
+    lodash.memoize "4.x"
+    make-error "1.x"
+    semver "7.x"
+    yargs-parser "20.x"
+
+tslib@^1.8.1:
+  version "1.14.1"
+  resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00"
+  integrity sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==
+
+tsutils@^3.21.0:
+  version "3.21.0"
+  resolved "https://registry.yarnpkg.com/tsutils/-/tsutils-3.21.0.tgz#b48717d394cea6c1e096983eed58e9d61715b623"
+  integrity sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==
+  dependencies:
+    tslib "^1.8.1"
+
+type-check@^0.4.0, type-check@~0.4.0:
+  version "0.4.0"
+  resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.4.0.tgz#07b8203bfa7056c0657050e3ccd2c37730bab8f1"
+  integrity sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==
+  dependencies:
+    prelude-ls "^1.2.1"
+
+type-detect@4.0.8:
+  version "4.0.8"
+  resolved "https://registry.yarnpkg.com/type-detect/-/type-detect-4.0.8.tgz#7646fb5f18871cfbb7749e69bd39a6388eb7450c"
+  integrity sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==
+
+type-fest@^0.20.2:
+  version "0.20.2"
+  resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4"
+  integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==
+
+type-fest@^0.21.3:
+  version "0.21.3"
+  resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.21.3.tgz#d260a24b0198436e133fa26a524a6d65fa3b2e37"
+  integrity sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==
+
+typed-array-length@^1.0.4:
+  version "1.0.4"
+  resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.4.tgz#89d83785e5c4098bec72e08b319651f0eac9c1bb"
+  integrity sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==
+  dependencies:
+    call-bind "^1.0.2"
+    for-each "^0.3.3"
+    is-typed-array "^1.1.9"
+
+typedarray-to-buffer@^3.1.5:
+  version "3.1.5"
+  resolved "https://registry.yarnpkg.com/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz#a97ee7a9ff42691b9f783ff1bc5112fe3fca9080"
+  integrity sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==
+  dependencies:
+    is-typedarray "^1.0.0"
+
+typescript@^4.6.2:
+  version "4.9.5"
+  resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a"
+  integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==
+
+unbox-primitive@^1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e"
+  integrity sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==
+  dependencies:
+    call-bind "^1.0.2"
+    has-bigints "^1.0.2"
+    has-symbols "^1.0.3"
+    which-boxed-primitive "^1.0.2"
+
+universalify@^0.2.0:
+  version "0.2.0"
+  resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.2.0.tgz#6451760566fa857534745ab1dde952d1b1761be0"
+  integrity sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==
+
+update-browserslist-db@^1.0.11:
+  version "1.0.11"
+  resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz#9a2a641ad2907ae7b3616506f4b977851db5b940"
+  integrity sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==
+  dependencies:
+    escalade "^3.1.1"
+    picocolors "^1.0.0"
+
+uri-js@^4.2.2:
+  version "4.4.1"
+  resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e"
+  integrity sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==
+  dependencies:
+    punycode "^2.1.0"
+
+url-parse@^1.5.3:
+  version "1.5.10"
+  resolved "https://registry.yarnpkg.com/url-parse/-/url-parse-1.5.10.tgz#9d3c2f736c1d75dd3bd2be507dcc111f1e2ea9c1"
+  integrity sha512-WypcfiRhfeUP9vvF0j6rw0J3hrWrw6iZv3+22h6iRMJ/8z1Tj6XfLP4DsUix5MhMPnXpiHDoKyoZ/bdCkwBCiQ==
+  dependencies:
+    querystringify "^2.1.1"
+    requires-port "^1.0.0"
+
+v8-to-istanbul@^8.1.0:
+  version "8.1.1"
+  resolved "https://registry.yarnpkg.com/v8-to-istanbul/-/v8-to-istanbul-8.1.1.tgz#77b752fd3975e31bbcef938f85e9bd1c7a8d60ed"
+  integrity sha512-FGtKtv3xIpR6BYhvgH8MI/y78oT7d8Au3ww4QIxymrCtZEh5b8gCw2siywE+puhEmuWKDtmfrvF5UlB298ut3w==
+  dependencies:
+    "@types/istanbul-lib-coverage" "^2.0.1"
+    convert-source-map "^1.6.0"
+    source-map "^0.7.3"
+
+validate-npm-package-license@^3.0.1:
+  version "3.0.4"
+  resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a"
+  integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==
+  dependencies:
+    spdx-correct "^3.0.0"
+    spdx-expression-parse "^3.0.0"
+
+w3c-hr-time@^1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/w3c-hr-time/-/w3c-hr-time-1.0.2.tgz#0a89cdf5cc15822df9c360543676963e0cc308cd"
+  integrity sha512-z8P5DvDNjKDoFIHK7q8r8lackT6l+jo/Ye3HOle7l9nICP9lf1Ci25fy9vHd0JOWewkIFzXIEig3TdKT7JQ5fQ==
+  dependencies:
+    browser-process-hrtime "^1.0.0"
+
+w3c-xmlserializer@^2.0.0:
+  version "2.0.0"
+  resolved "https://registry.yarnpkg.com/w3c-xmlserializer/-/w3c-xmlserializer-2.0.0.tgz#3e7104a05b75146cc60f564380b7f683acf1020a"
+  integrity sha512-4tzD0mF8iSiMiNs30BiLO3EpfGLZUT2MSX/G+o7ZywDzliWQ3OPtTZ0PTC3B3ca1UAf4cJMHB+2Bf56EriJuRA==
+  dependencies:
+    xml-name-validator "^3.0.0"
+
+walker@^1.0.7:
+  version "1.0.8"
+  resolved "https://registry.yarnpkg.com/walker/-/walker-1.0.8.tgz#bd498db477afe573dc04185f011d3ab8a8d7653f"
+  integrity sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==
+  dependencies:
+    makeerror "1.0.12"
+
+webidl-conversions@^5.0.0:
+  version "5.0.0"
+  resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-5.0.0.tgz#ae59c8a00b121543a2acc65c0434f57b0fc11aff"
+  integrity sha512-VlZwKPCkYKxQgeSbH5EyngOmRp7Ww7I9rQLERETtf5ofd9pGeswWiOtogpEO850jziPRarreGxn5QIiTqpb2wA==
+
+webidl-conversions@^6.1.0:
+  version "6.1.0"
+  resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-6.1.0.tgz#9111b4d7ea80acd40f5270d666621afa78b69514"
+  integrity sha512-qBIvFLGiBpLjfwmYAaHPXsn+ho5xZnGvyGvsarywGNc8VyQJUMHJ8OBKGGrPER0okBeMDaan4mNBlgBROxuI8w==
+
+whatwg-encoding@^1.0.5:
+  version "1.0.5"
+  resolved "https://registry.yarnpkg.com/whatwg-encoding/-/whatwg-encoding-1.0.5.tgz#5abacf777c32166a51d085d6b4f3e7d27113ddb0"
+  integrity sha512-b5lim54JOPN9HtzvK9HFXvBma/rnfFeqsic0hSpjtDbVxR3dJKLc+KB4V6GgiGOvl7CY/KNh8rxSo9DKQrnUEw==
+  dependencies:
+    iconv-lite "0.4.24"
+
+whatwg-mimetype@^2.3.0:
+  version "2.3.0"
+  resolved "https://registry.yarnpkg.com/whatwg-mimetype/-/whatwg-mimetype-2.3.0.tgz#3d4b1e0312d2079879f826aff18dbeeca5960fbf"
+  integrity sha512-M4yMwr6mAnQz76TbJm914+gPpB/nCwvZbJU28cUD6dR004SAxDLOOSUaB1JDRqLtaOV/vi0IC5lEAGFgrjGv/g==
+
+whatwg-url@^8.0.0, whatwg-url@^8.5.0:
+  version "8.7.0"
+  resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-8.7.0.tgz#656a78e510ff8f3937bc0bcbe9f5c0ac35941b77"
+  integrity sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==
+  dependencies:
+    lodash "^4.7.0"
+    tr46 "^2.1.0"
+    webidl-conversions "^6.1.0"
+
+which-boxed-primitive@^1.0.2:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6"
+  integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==
+  dependencies:
+    is-bigint "^1.0.1"
+    is-boolean-object "^1.1.0"
+    is-number-object "^1.0.4"
+    is-string "^1.0.5"
+    is-symbol "^1.0.3"
+
+which-typed-array@^1.1.9:
+  version "1.1.9"
+  resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.9.tgz#307cf898025848cf995e795e8423c7f337efbde6"
+  integrity sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==
+  dependencies:
+    available-typed-arrays "^1.0.5"
+    call-bind "^1.0.2"
+    for-each "^0.3.3"
+    gopd "^1.0.1"
+    has-tostringtag "^1.0.0"
+    is-typed-array "^1.1.10"
+
+which@^1.2.9:
+  version "1.3.1"
+  resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
+  integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==
+  dependencies:
+    isexe "^2.0.0"
+
+which@^2.0.1:
+  version "2.0.2"
+  resolved "https://registry.yarnpkg.com/which/-/which-2.0.2.tgz#7c6a8dd0a636a0327e10b59c9286eee93f3f51b1"
+  integrity sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==
+  dependencies:
+    isexe "^2.0.0"
+
+wrap-ansi@^7.0.0:
+  version "7.0.0"
+  resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
+  integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==
+  dependencies:
+    ansi-styles "^4.0.0"
+    string-width "^4.1.0"
+    strip-ansi "^6.0.0"
+
+wrappy@1:
+  version "1.0.2"
+  resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f"
+  integrity sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==
+
+write-file-atomic@^3.0.0:
+  version "3.0.3"
+  resolved "https://registry.yarnpkg.com/write-file-atomic/-/write-file-atomic-3.0.3.tgz#56bd5c5a5c70481cd19c571bd39ab965a5de56e8"
+  integrity sha512-AvHcyZ5JnSfq3ioSyjrBkH9yW4m7Ayk8/9My/DD9onKeu/94fwrMocemO2QAJFAlnnDN+ZDS+ZjAR5ua1/PV/Q==
+  dependencies:
+    imurmurhash "^0.1.4"
+    is-typedarray "^1.0.0"
+    signal-exit "^3.0.2"
+    typedarray-to-buffer "^3.1.5"
+
+ws@^7.4.6:
+  version "7.5.9"
+  resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.9.tgz#54fa7db29f4c7cec68b1ddd3a89de099942bb591"
+  integrity sha512-F+P9Jil7UiSKSkppIiD94dN07AwvFixvLIj1Og1Rl9GGMuNipJnV9JzjD6XuqmAeiswGvUmNLjr5cFuXwNS77Q==
+
+xml-name-validator@^3.0.0:
+  version "3.0.0"
+  resolved "https://registry.yarnpkg.com/xml-name-validator/-/xml-name-validator-3.0.0.tgz#6ae73e06de4d8c6e47f9fb181f78d648ad457c6a"
+  integrity sha512-A5CUptxDsvxKJEU3yO6DuWBSJz/qizqzJKOMIfUJHETbBw/sFaDxgd6fxm1ewUaM0jZ444Fc5vC5ROYurg/4Pw==
+
+xmlchars@^2.2.0:
+  version "2.2.0"
+  resolved "https://registry.yarnpkg.com/xmlchars/-/xmlchars-2.2.0.tgz#060fe1bcb7f9c76fe2a17db86a9bc3ab894210cb"
+  integrity sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==
+
+y18n@^5.0.5:
+  version "5.0.8"
+  resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55"
+  integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==
+
+yallist@^3.0.2:
+  version "3.1.1"
+  resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd"
+  integrity sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==
+
+yallist@^4.0.0:
+  version "4.0.0"
+  resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"
+  integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==
+
+yargs-parser@20.x, yargs-parser@^20.2.2:
+  version "20.2.9"
+  resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee"
+  integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==
+
+yargs@^16.2.0:
+  version "16.2.0"
+  resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66"
+  integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==
+  dependencies:
+    cliui "^7.0.2"
+    escalade "^3.1.1"
+    get-caller-file "^2.0.5"
+    require-directory "^2.1.1"
+    string-width "^4.2.0"
+    y18n "^5.0.5"
+    yargs-parser "^20.2.2"
+
+yocto-queue@^0.1.0:
+  version "0.1.0"
+  resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b"
+  integrity sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==
diff --git a/binding/python/.gitignore b/binding/python/.gitignore
new file mode 100644
index 00000000..19fe99ed
--- /dev/null
+++ b/binding/python/.gitignore
@@ -0,0 +1,7 @@
+venv
+dist
+MANIFEST.in
+pvrecorder
+pvrecorder.egg-info
+build
+__pycache__
diff --git a/binding/python/README.md b/binding/python/README.md
new file mode 100644
index 00000000..68f9fdb8
--- /dev/null
+++ b/binding/python/README.md
@@ -0,0 +1,57 @@
+# PvRecorder Binding for Python
+
+## Compatibility
+
+- Python 3.5+
+- Runs on Linux (x86_64), macOS (x86_64 and arm64), Windows (x86_64), Raspberry Pi (all variants), NVIDIA Jetson (Nano), and BeagleBone.
+
+## Installation
+
+pip3 install pvrecorder
+
+## Usage
+
+To start recording initialize an instance and run start:
+
+```python
+from pvrecorder import PvRecorder
+
+recorder = PvRecorder(frame_length=512)
+recorder.start()
+```
+
+(or)
+
+Use `get_available_devices()` to get a list of available devices and then initialize the instance based on the index of a device:
+
+```python
+from pvrecorder import PvRecorder
+
+devices = PvRecorder.get_available_devices() # select index of device
+
+recorder = PvRecorder(frame_length=512, device_index=0)
+recorder.start()
+```
+
+Get a frame of audio by calling the `read()` function:
+
+```python
+frame = recorder.read()
+# do something with frame
+```
+
+To stop recording just run stop on the instance:
+
+```python
+recorder.stop()
+```
+
+Once you are done, free the used resources. You do not have to call stop before delete:
+
+```python
+recorder.delete()
+```
+
+## Demos
+
+[pvrecorderdemo](https://pypi.org/project/pvrecorderdemo/) provides command-line utilities for recording audio to a file.
diff --git a/sdk/python/__init__.py b/binding/python/__init__.py
similarity index 86%
rename from sdk/python/__init__.py
rename to binding/python/__init__.py
index 92c1ac66..0f7a2059 100644
--- a/sdk/python/__init__.py
+++ b/binding/python/__init__.py
@@ -1,5 +1,5 @@
 #
-# Copyright 2021 Picovoice Inc.
+# Copyright 2021-2023 Picovoice Inc.
 #
 # You may not use this file except in compliance with the license. A copy of the license is located in the "LICENSE"
 # file accompanying this source.
@@ -9,4 +9,4 @@
 # specific language governing permissions and limitations under the License.
 #
 
-from .pvrecorder import PvRecorder
+from ._pvrecorder import PvRecorder
diff --git a/binding/python/_pvrecorder.py b/binding/python/_pvrecorder.py
new file mode 100644
index 00000000..ee90e4b1
--- /dev/null
+++ b/binding/python/_pvrecorder.py
@@ -0,0 +1,269 @@
+#
+# Copyright 2021-2023 Picovoice Inc.
+#
+# You may not use this file except in compliance with the license. A copy of the license is located in the "LICENSE"
+# file accompanying this source.
+#
+# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+# an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+# specific language governing permissions and limitations under the License.
+#
+
+import os
+import platform
+import subprocess
+from ctypes import *
+from enum import Enum
+from typing import *
+
+CALLBACK = CFUNCTYPE(None, POINTER(c_int16))
+
+
+def default_library_path(relative: str = ''):
+    """A helper function to get the library path."""
+
+    if platform.system() == "Windows":
+        script_path = os.path.join(os.path.dirname(__file__), relative, "resources", "scripts", "platform.bat")
+    else:
+        script_path = os.path.join(os.path.dirname(__file__), relative, "resources", "scripts", "platform.sh")
+
+    command = subprocess.run(script_path, stdout=subprocess.PIPE)
+
+    if command.returncode != 0:
+        raise RuntimeError("Current system is not supported.")
+    os_name, cpu = str(command.stdout.decode("utf-8")).split(" ")
+
+    if os_name == "windows":
+        extension = "dll"
+    elif os_name == "mac":
+        extension = "dylib"
+    else:
+        extension = "so"
+
+    return os.path.join(os.path.dirname(__file__), relative, "lib", os_name, cpu, "libpv_recorder.%s" % extension)
+
+
+class PvRecorder(object):
+    """
+    A cross-platform Python SDK for PvRecorder to process audio recordings. It lists the available
+    input devices. Also given the audio device index and frame_length, processes the frame and runs
+    a callback each time a frame_length is given.
+    """
+
+    class PvRecorderStatuses(Enum):
+        SUCCESS = 0
+        OUT_OF_MEMORY = 1
+        INVALID_ARGUMENT = 2
+        INVALID_STATE = 3
+        BACKEND_ERROR = 4
+        DEVICE_ALREADY_INITIALIZED = 5
+        DEVICE_NOT_INITIALIZED = 6
+        IO_ERROR = 7
+        RUNTIME_ERROR = 8
+
+    _PVRECORDER_STATUS_TO_EXCEPTION = {
+        PvRecorderStatuses.OUT_OF_MEMORY: MemoryError,
+        PvRecorderStatuses.INVALID_ARGUMENT: ValueError,
+        PvRecorderStatuses.INVALID_STATE: ValueError,
+        PvRecorderStatuses.BACKEND_ERROR: SystemError,
+        PvRecorderStatuses.DEVICE_ALREADY_INITIALIZED: ValueError,
+        PvRecorderStatuses.DEVICE_NOT_INITIALIZED: ValueError,
+        PvRecorderStatuses.IO_ERROR: IOError,
+        PvRecorderStatuses.RUNTIME_ERROR: RuntimeError
+    }
+
+    class CPvRecorder(Structure):
+        pass
+
+    _library = None
+    _relative_library_path = ''
+
+    def __init__(
+            self,
+            frame_length: int,
+            device_index: int = -1,
+            buffered_frames_count: int = 50):
+        """
+        Constructor
+
+        :param frame_length: The length of audio frame to get for each read call.
+        :param device_index: The index of the audio device to use. A value of (-1) will resort to default device.
+        :param buffered_frames_count: The number of audio frames buffered internally for reading - i.e. internal
+        circular buffer will be of size `frame_length` * `buffered_frames_count`. If this value is too low,
+        buffer overflows could occur audio frames could be dropped. A higher value will increase memory usage.
+        """
+
+        library = self._get_library()
+
+        init_func = library.pv_recorder_init
+        init_func.argtypes = [
+            c_int32,
+            c_int32,
+            c_int32,
+            POINTER(POINTER(self.CPvRecorder))
+        ]
+        init_func.restype = self.PvRecorderStatuses
+
+        self._handle = POINTER(self.CPvRecorder)()
+        self._frame_length = frame_length
+
+        status = init_func(frame_length, device_index, buffered_frames_count, byref(self._handle))
+        if status is not self.PvRecorderStatuses.SUCCESS:
+            raise self._PVRECORDER_STATUS_TO_EXCEPTION[status]("Failed to initialize PvRecorder.")
+
+        self._delete_func = library.pv_recorder_delete
+        self._delete_func.argtypes = [POINTER(self.CPvRecorder)]
+        self._delete_func.restype = None
+
+        self._start_func = library.pv_recorder_start
+        self._start_func.argtypes = [POINTER(self.CPvRecorder)]
+        self._start_func.restype = self.PvRecorderStatuses
+
+        self._stop_func = library.pv_recorder_stop
+        self._stop_func.argtypes = [POINTER(self.CPvRecorder)]
+        self._stop_func.restype = self.PvRecorderStatuses
+
+        self._set_debug_logging_func = library.pv_recorder_set_debug_logging
+        self._set_debug_logging_func.argtypes = [POINTER(self.CPvRecorder), c_bool]
+        self._set_debug_logging_func.restype = None
+
+        self._read_func = library.pv_recorder_read
+        self._read_func.argtypes = [POINTER(self.CPvRecorder), POINTER(c_int16)]
+        self._read_func.restype = self.PvRecorderStatuses
+
+        self._get_is_recording_func = library.pv_recorder_get_is_recording
+        self._get_is_recording_func.argtypes = [POINTER(self.CPvRecorder)]
+        self._get_is_recording_func.restype = c_bool
+
+        self._get_selected_device_func = library.pv_recorder_get_selected_device
+        self._get_selected_device_func.argtypes = [POINTER(self.CPvRecorder)]
+        self._get_selected_device_func.restype = c_char_p
+
+        self._version_func = library.pv_recorder_version
+        self._version_func.argtypes = None
+        self._version_func.restype = c_char_p
+
+        self._sample_rate_func = library.pv_recorder_sample_rate
+        self._sample_rate_func.argtypes = None
+        self._sample_rate_func.restype = c_int32
+
+    def delete(self) -> None:
+        """Releases any resources used by PvRecorder."""
+
+        self._delete_func(self._handle)
+
+    def start(self) -> None:
+        """Starts recording and buffering audio frames."""
+
+        status = self._start_func(self._handle)
+        if status is not self.PvRecorderStatuses.SUCCESS:
+            raise self._PVRECORDER_STATUS_TO_EXCEPTION[status]("Failed to start device.")
+
+    def stop(self) -> None:
+        """Stops recording audio."""
+
+        status = self._stop_func(self._handle)
+        if status is not self.PvRecorderStatuses.SUCCESS:
+            raise self._PVRECORDER_STATUS_TO_EXCEPTION[status]("Failed to stop device.")
+
+    def read(self) -> List[int]:
+        """Synchronous call to read a frame of audio.
+
+        :return: A frame with size `frame_length` matching the value given to `__init__()`.
+        """
+
+        pcm = (c_int16 * self._frame_length)()
+        status = self._read_func(self._handle, pcm)
+        if status is not self.PvRecorderStatuses.SUCCESS:
+            raise self._PVRECORDER_STATUS_TO_EXCEPTION[status]("Failed to read from device.")
+        return list(pcm[0:self._frame_length])
+
+    def set_debug_logging(self, is_debug_logging_enabled: bool) -> None:
+        """
+        Enable or disable debug logging for PvRecorder. Debug logs will indicate when there are overflows
+        in the internal frame buffer and when an audio source is generating frames of silence.
+
+        :param is_debug_logging_enabled: Boolean indicating whether the debug logging is enabled or disabled.
+        """
+
+        self._set_debug_logging_func(self._handle, is_debug_logging_enabled)
+
+    @property
+    def is_recording(self) -> bool:
+        """Gets whether the recorder is currently recording audio or not."""
+
+        return bool(self._get_is_recording_func(self._handle))
+
+    @property
+    def selected_device(self) -> str:
+        """Gets the audio device that the given `PvRecorder` instance is using."""
+
+        device_name = self._get_selected_device_func(self._handle)
+        return device_name.decode('utf-8')
+
+    @property
+    def version(self) -> str:
+        """Gets the current version of PvRecorder library."""
+
+        version = self._version_func()
+        return version.decode('utf-8')
+
+    @property
+    def frame_length(self) -> int:
+        """Gets the frame length matching the value given to `__init__()`."""
+
+        return self._frame_length
+
+    @property
+    def sample_rate(self) -> int:
+        """Gets the audio sample rate used by PvRecorder."""
+
+        sample_rate = self._sample_rate_func()
+        return sample_rate
+
+    @staticmethod
+    def get_available_devices() -> List[str]:
+        """Gets the list of available audio devices that can be used for recording.
+
+        :return: A list of strings, indicating the names of audio devices.
+        """
+
+        get_available_devices_func = PvRecorder._get_library().pv_recorder_get_available_devices
+        get_available_devices_func.argstype = [POINTER(c_int32), POINTER(POINTER(c_char_p))]
+        get_available_devices_func.restype = PvRecorder.PvRecorderStatuses
+
+        free_available_devices_func = PvRecorder._get_library().pv_recorder_free_available_devices
+        free_available_devices_func.argstype = [c_int32, POINTER(c_char_p)]
+        free_available_devices_func.restype = None
+
+        count = c_int32()
+        devices = POINTER(c_char_p)()
+
+        status = get_available_devices_func(byref(count), byref(devices))
+        if status is not PvRecorder.PvRecorderStatuses.SUCCESS:
+            raise PvRecorder._PVRECORDER_STATUS_TO_EXCEPTION[status]("Failed to get device list")
+
+        device_list = list()
+        for i in range(count.value):
+            device_list.append(devices[i].decode('utf-8'))
+
+        free_available_devices_func(count, devices)
+
+        return device_list
+
+    @classmethod
+    def set_default_library_path(cls, relative: str):
+        cls._relative_library_path = default_library_path(relative)
+
+    @classmethod
+    def _get_library(cls):
+        if len(cls._relative_library_path) == 0:
+            cls._relative_library_path = default_library_path()
+        if cls._library is None:
+            cls._library = cdll.LoadLibrary(cls._relative_library_path)
+        return cls._library
+
+
+__all__ = [
+    'PvRecorder',
+]
diff --git a/sdk/python/setup.py b/binding/python/setup.py
similarity index 86%
rename from sdk/python/setup.py
rename to binding/python/setup.py
index 9b33a0db..01ce64fb 100644
--- a/sdk/python/setup.py
+++ b/binding/python/setup.py
@@ -1,5 +1,5 @@
 #
-# Copyright 2021-2022 Picovoice Inc.
+# Copyright 2021-2023 Picovoice Inc.
 #
 # You may not use this file except in compliance with the license. A copy of the license is located in the "LICENSE"
 # file accompanying this source.
@@ -22,11 +22,11 @@
 shutil.copy(os.path.join(os.path.dirname(__file__), '../../LICENSE'), package_folder)
 
 shutil.copy(os.path.join(os.path.dirname(__file__), '__init__.py'), os.path.join(package_folder, '__init__.py'))
-shutil.copy(os.path.join(os.path.dirname(__file__), 'pvrecorder.py'), os.path.join(package_folder, 'pvrecorder.py'))
+shutil.copy(os.path.join(os.path.dirname(__file__), '_pvrecorder.py'), os.path.join(package_folder, '_pvrecorder.py'))
 
 shutil.copytree(
-    os.path.join(os.path.dirname(__file__), '../../scripts'),
-    os.path.join(package_folder, 'scripts'))
+    os.path.join(os.path.dirname(__file__), '../../resources/scripts'),
+    os.path.join(package_folder, 'resources/scripts'))
 
 platforms = ('beaglebone', 'jetson', 'linux', 'mac', 'raspberry-pi', 'windows')
 
@@ -39,7 +39,7 @@
 MANIFEST_IN = """
 include pvrecorder/LICENSE
 include pvrecorder/__init__.py
-include pvrecorder/pv_recorder.py
+include pvrecorder/_pv_recorder.py
 include pvrecorder/lib/beaglebone/libpv_recorder.so
 recursive-include pvrecorder/lib/jetson *
 include pvrecorder/lib/linux/x86_64/libpv_recorder.so
@@ -47,7 +47,7 @@
 include pvrecorder/lib/mac/arm64/libpv_recorder.dylib
 recursive-include pvrecorder/lib/raspberry-pi *
 include pvrecorder/lib/windows/amd64/libpv_recorder.dll
-recursive-include pvrecorder/scripts *
+recursive-include pvrecorder/resources/scripts *
 """
 
 with open(os.path.join(os.path.dirname(__file__), 'MANIFEST.in'), 'w') as f:
@@ -58,7 +58,7 @@
 
 setuptools.setup(
     name="pvrecorder",
-    version="1.1.2",
+    version="1.2.0",
     author="Picovoice",
     author_email="hello@picovoice.ai",
     description="Recorder library for Picovoice.",
@@ -77,5 +77,5 @@
         "Topic :: Multimedia :: Sound/Audio :: Speech"
     ],
     python_requires='>=3.5',
-    keywords="audio recorder",
+    keywords="Audio Recorder",
 )
diff --git a/sdk/rust/.gitignore b/binding/rust/.gitignore
similarity index 100%
rename from sdk/rust/.gitignore
rename to binding/rust/.gitignore
diff --git a/sdk/rust/Cargo.toml b/binding/rust/Cargo.toml
similarity index 80%
rename from sdk/rust/Cargo.toml
rename to binding/rust/Cargo.toml
index 97769e9c..b471de37 100644
--- a/sdk/rust/Cargo.toml
+++ b/binding/rust/Cargo.toml
@@ -1,6 +1,6 @@
 [package]
 name = "pv_recorder"
-version = "1.1.2"
+version = "1.2.0"
 edition = "2018"
 description = "Rust recorder library for Picovoice"
 license = "Apache-2.0"
@@ -26,9 +26,4 @@ crate_type = ["lib"]
 
 [dependencies]
 libc = "0.2"
-libloading = "0.7"
-
-[dev-dependencies]
-hound = "3.4.0"
-clap = "2.33.3"
-ctrlc = "3.1.9"
+libloading = "0.8"
diff --git a/binding/rust/README.md b/binding/rust/README.md
new file mode 100644
index 00000000..2461fe13
--- /dev/null
+++ b/binding/rust/README.md
@@ -0,0 +1,66 @@
+# PvRecorder Binding for Python
+
+## Requirements
+
+- Rust 1.54+
+
+## Compatibility
+
+- Linux (x86_64)
+- macOS (x86_64 and arm64)
+- Windows (x86_64)
+- Raspberry Pi:
+    - Zero
+    - 2
+    - 3 (32 and 64 bit)
+    - 4 (32 and 64 bit)
+- NVIDIA Jetson Nano
+- BeagleBone
+
+## Installation
+
+To add the pvrecorder library into your app, add `pv_recorder` to your app's `Cargo.toml` manifest:
+```toml
+[dependencies]
+pv_recorder = "*"
+```
+
+## Usage
+
+Getting the list of input devices does not require an instance:
+
+```rust
+use pv_recorder::PvRecorderBuilder
+
+let audio_devices = PvRecorderBuilder::default().get_audio_devices()?;
+```
+
+To start recording initialize an instance using the builder and run `start`:
+
+```rust
+use pv_recorder::PvRecorderBuilder;
+
+let frame_length = 512;
+let recorder = PvRecorderBuilder::new(frame_length).init()?;
+recorder.start()?
+```
+
+Get frame of audio by calling the `read()` function:
+
+```rust
+while recorder.is_recording() {
+    let frame = recorder.read()?;
+    // process audio frame
+}
+```
+
+To stop recording just run stop on the instance:
+
+```rust
+recorder.stop()?;
+```
+
+## Demo
+
+The [PvRecorder Rust demo](https://github.com/Picovoice/pvrecorder/tree/main/demo/rust) is a Rust command-line application that demonstrates how to
+use PvRecorder to record audio to a file.
diff --git a/sdk/rust/build.rs b/binding/rust/build.rs
similarity index 100%
rename from sdk/rust/build.rs
rename to binding/rust/build.rs
diff --git a/sdk/rust/copy.sh b/binding/rust/copy.sh
similarity index 100%
rename from sdk/rust/copy.sh
rename to binding/rust/copy.sh
diff --git a/sdk/rust/src/lib.rs b/binding/rust/src/lib.rs
similarity index 100%
rename from sdk/rust/src/lib.rs
rename to binding/rust/src/lib.rs
diff --git a/binding/rust/src/pvrecorder.rs b/binding/rust/src/pvrecorder.rs
new file mode 100644
index 00000000..f2576387
--- /dev/null
+++ b/binding/rust/src/pvrecorder.rs
@@ -0,0 +1,502 @@
+/*
+    Copyright 2021-2023 Picovoice Inc.
+
+    You may not use this file except in compliance with the license. A copy of the license is located in the "LICENSE"
+    file accompanying this source.
+
+    Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+    an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+    specific language governing permissions and limitations under the License.
+*/
+
+use std::ffi::CStr;
+use std::path::Path;
+use std::ptr::addr_of_mut;
+use std::sync::Arc;
+use std::{cmp::PartialEq, path::PathBuf};
+
+use libc::c_char;
+use libloading::{Library, Symbol};
+
+use crate::util::pv_library_path;
+
+#[cfg(unix)]
+use libloading::os::unix::Symbol as RawSymbol;
+#[cfg(windows)]
+use libloading::os::windows::Symbol as RawSymbol;
+
+#[repr(C)]
+struct CPvRecorder {}
+
+#[repr(C)]
+#[derive(PartialEq, Clone, Debug)]
+#[allow(non_camel_case_types)]
+pub enum PvRecorderStatus {
+    SUCCESS = 0,
+    OUT_OF_MEMORY = 1,
+    INVALID_ARGUMENT = 2,
+    INVALID_STATE = 3,
+    BACKEND_ERROR = 4,
+    DEVICE_ALREADY_INITIALIZED = 5,
+    DEVICE_NOT_INITIALIZED = 6,
+    IO_ERROR = 7,
+    RUNTIME_ERROR = 8,
+}
+
+type PvRecorderInitFn = unsafe extern "C" fn(
+    frame_length: i32,
+    device_index: i32,
+    buffered_frames_count: i32,
+    object: *mut *mut CPvRecorder,
+) -> PvRecorderStatus;
+type PvRecorderDeleteFn = unsafe extern "C" fn(object: *mut CPvRecorder);
+type PvRecorderStartFn = unsafe extern "C" fn(object: *mut CPvRecorder) -> PvRecorderStatus;
+type PvRecorderStopFn = unsafe extern "C" fn(object: *mut CPvRecorder) -> PvRecorderStatus;
+type PvRecorderReadFn =
+    unsafe extern "C" fn(object: *mut CPvRecorder, pcm: *mut i16) -> PvRecorderStatus;
+type PvRecorderSetDebugLoggingFn =
+    unsafe extern "C" fn(object: *mut CPvRecorder, is_debug_logging: bool);
+type PvRecorderGetIsRecordingFn = unsafe extern "C" fn(object: *mut CPvRecorder) -> bool;
+type PvRecorderGetSelectedDeviceFn =
+    unsafe extern "C" fn(object: *mut CPvRecorder) -> *const c_char;
+type PvRecorderGetAvailableDevicesFn = unsafe extern "C" fn(
+    device_list_length: *mut i32,
+    device_list: *mut *mut *mut c_char,
+) -> PvRecorderStatus;
+type PvRecorderFreeAvailableDevicesList =
+    unsafe extern "C" fn(device_list_length: i32, device_list: *mut *mut c_char);
+
+type PvRecorderSampleRate = unsafe extern "C" fn() -> i32;
+type PvRecorderVersion = unsafe extern "C" fn() -> *const c_char;
+
+#[derive(Clone, Debug)]
+pub enum PvRecorderErrorStatus {
+    LibraryError(PvRecorderStatus),
+    LibraryLoadError,
+    ArgumentError,
+    OtherError,
+}
+
+#[derive(Clone, Debug)]
+pub struct PvRecorderError {
+    status: PvRecorderErrorStatus,
+    message: String,
+}
+
+impl PvRecorderError {
+    pub fn new(status: PvRecorderErrorStatus, message: impl Into<String>) -> Self {
+        Self {
+            status,
+            message: message.into(),
+        }
+    }
+}
+
+impl std::fmt::Display for PvRecorderError {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        write!(f, "{}: {:?}", self.message, self.status)
+    }
+}
+
+impl std::error::Error for PvRecorderError {}
+
+const DEFAULT_DEVICE_INDEX: i32 = -1;
+const DEFAULT_FRAME_LENGTH: i32 = 512;
+const DEFAULT_BUFFERED_FRAMES_COUNT: i32 = 50;
+
+pub struct PvRecorderBuilder {
+    frame_length: i32,
+    device_index: i32,
+    buffered_frames_count: i32,
+    library_path: PathBuf,
+}
+
+impl Default for PvRecorderBuilder {
+    fn default() -> Self {
+        Self::new(DEFAULT_FRAME_LENGTH)
+    }
+}
+
+impl PvRecorderBuilder {
+    pub fn new(frame_length: i32) -> Self {
+        Self {
+            frame_length,
+            device_index: DEFAULT_DEVICE_INDEX,
+            buffered_frames_count: DEFAULT_BUFFERED_FRAMES_COUNT,
+            library_path: pv_library_path(),
+        }
+    }
+
+    pub fn frame_length(&mut self, frame_length: i32) -> &mut Self {
+        self.frame_length = frame_length;
+        self
+    }
+
+    pub fn device_index(&mut self, device_index: i32) -> &mut Self {
+        self.device_index = device_index;
+        self
+    }
+
+    pub fn buffered_frames_count(&mut self, buffered_frames_count: i32) -> &mut Self {
+        self.buffered_frames_count = buffered_frames_count;
+        self
+    }
+
+    pub fn library_path(&mut self, library_path: &Path) -> &mut Self {
+        self.library_path = library_path.into();
+        self
+    }
+
+    pub fn init(&self) -> Result<PvRecorder, PvRecorderError> {
+        if self.frame_length <= 0 {
+            return Err(PvRecorderError::new(
+                PvRecorderErrorStatus::ArgumentError,
+                format!(
+                    "frame_length needs to be greater than or equal to 0, got: {}",
+                    self.frame_length
+                ),
+            ));
+        }
+
+        let recorder_inner = PvRecorderInner::init(
+            self.frame_length,
+            self.device_index,
+            self.buffered_frames_count,
+            &self.library_path,
+        );
+        recorder_inner.map(|inner| PvRecorder {
+            inner: Arc::new(inner),
+        })
+    }
+
+    pub fn get_available_devices(&self) -> Result<Vec<String>, PvRecorderError> {
+        PvRecorderInner::get_available_devices(&self.library_path)
+    }
+}
+
+#[derive(Clone)]
+pub struct PvRecorder {
+    inner: Arc<PvRecorderInner>,
+}
+
+impl PvRecorder {
+    pub fn start(&self) -> Result<(), PvRecorderError> {
+        self.inner.start()
+    }
+
+    pub fn stop(&self) -> Result<(), PvRecorderError> {
+        self.inner.stop()
+    }
+
+    pub fn read(&self) -> Result<Vec<i16>, PvRecorderError> {
+        self.inner.read()
+    }
+
+    pub fn set_debug_logging(&self, is_debug_logging_enabled: bool) {
+        self.inner.set_debug_logging(is_debug_logging_enabled)
+    }
+
+    pub fn frame_length(&self) -> usize {
+        self.inner.frame_length() as usize
+    }
+
+    pub fn is_recording(&self) -> bool {
+        self.inner.is_recording()
+    }
+
+    pub fn sample_rate(&self) -> usize {
+        self.inner.sample_rate() as usize
+    }
+
+    pub fn selected_device(&self) -> String {
+        self.inner.selected_device()
+    }
+
+    pub fn version(&self) -> String {
+        self.inner.version()
+    }
+}
+
+unsafe fn load_library_fn<T>(
+    library: &Library,
+    function_name: &[u8],
+) -> Result<RawSymbol<T>, PvRecorderError> {
+    library
+        .get(function_name)
+        .map(|s: Symbol<T>| s.into_raw())
+        .map_err(|err| {
+            PvRecorderError::new(
+                PvRecorderErrorStatus::LibraryLoadError,
+                format!(
+                    "Failed to load function symbol from pvrecorder library: {}",
+                    err
+                ),
+            )
+        })
+}
+
+fn check_fn_call_status(
+    status: PvRecorderStatus,
+    function_name: &str,
+) -> Result<(), PvRecorderError> {
+    match status {
+        PvRecorderStatus::SUCCESS => Ok(()),
+        _ => Err(PvRecorderError::new(
+            PvRecorderErrorStatus::LibraryError(status),
+            format!(
+                "Function '{}' in the pvrecorder library failed",
+                function_name
+            ),
+        )),
+    }
+}
+
+struct PvRecorderInnerVTable {
+    pv_recorder_init: RawSymbol<PvRecorderInitFn>,
+    pv_recorder_delete: RawSymbol<PvRecorderDeleteFn>,
+    pv_recorder_start: RawSymbol<PvRecorderStartFn>,
+    pv_recorder_stop: RawSymbol<PvRecorderStopFn>,
+    pv_recorder_read: RawSymbol<PvRecorderReadFn>,
+    pv_recorder_set_debug_logging: RawSymbol<PvRecorderSetDebugLoggingFn>,
+    pv_recorder_get_is_recording: RawSymbol<PvRecorderGetIsRecordingFn>,
+    pv_recorder_get_selected_device: RawSymbol<PvRecorderGetSelectedDeviceFn>,
+    pv_recorder_get_available_devices: RawSymbol<PvRecorderGetAvailableDevicesFn>,
+    pv_recorder_free_available_devices: RawSymbol<PvRecorderFreeAvailableDevicesList>,
+    pv_recorder_sample_rate: RawSymbol<PvRecorderSampleRate>,
+    pv_recorder_version: RawSymbol<PvRecorderVersion>,
+
+    _lib_guard: Library,
+}
+
+impl PvRecorderInnerVTable {
+    pub fn new(lib: Library) -> Result<Self, PvRecorderError> {
+        // SAFETY: the library will be hold by this struct and therefore the symbols can't outlive the library
+        unsafe {
+            Ok(Self {
+                pv_recorder_init: load_library_fn(&lib, b"pv_recorder_init")?,
+                pv_recorder_delete: load_library_fn(&lib, b"pv_recorder_delete")?,
+                pv_recorder_start: load_library_fn(&lib, b"pv_recorder_start")?,
+                pv_recorder_stop: load_library_fn(&lib, b"pv_recorder_stop")?,
+                pv_recorder_read: load_library_fn(&lib, b"pv_recorder_read")?,
+                pv_recorder_set_debug_logging: load_library_fn(
+                    &lib,
+                    b"pv_recorder_set_debug_logging",
+                )?,
+                pv_recorder_get_is_recording: load_library_fn(
+                    &lib,
+                    b"pv_recorder_get_is_recording",
+                )?,
+                pv_recorder_get_selected_device: load_library_fn(
+                    &lib,
+                    b"pv_recorder_get_selected_device",
+                )?,
+                pv_recorder_get_available_devices: load_library_fn(
+                    &lib,
+                    b"pv_recorder_get_available_devices",
+                )?,
+                pv_recorder_free_available_devices: load_library_fn(
+                    &lib,
+                    b"pv_recorder_free_available_devices",
+                )?,
+                pv_recorder_sample_rate: load_library_fn(&lib, b"pv_recorder_sample_rate")?,
+                pv_recorder_version: load_library_fn(&lib, b"pv_recorder_version")?,
+
+                _lib_guard: lib,
+            })
+        }
+    }
+}
+
+struct PvRecorderInner {
+    cpvrecorder: *mut CPvRecorder,
+    frame_length: i32,
+    sample_rate: i32,
+    selected_device: String,
+    version: String,
+    vtable: PvRecorderInnerVTable,
+}
+
+impl PvRecorderInner {
+    pub fn init(
+        frame_length: i32,
+        device_index: i32,
+        buffered_frames_count: i32,
+        library_path: &Path,
+    ) -> Result<Self, PvRecorderError> {
+        if frame_length <= 0 {
+            return Err(PvRecorderError::new(
+                PvRecorderErrorStatus::ArgumentError,
+                format!(
+                    "frame_length value `{}` should be greater than zero",
+                    frame_length
+                ),
+            ));
+        }
+
+        if device_index < -1 {
+            return Err(PvRecorderError::new(
+                PvRecorderErrorStatus::ArgumentError,
+                format!(
+                    "device_index value `{}` should be greater than or equal to -1",
+                    device_index
+                ),
+            ));
+        }
+
+        if buffered_frames_count <= 0 {
+            return Err(PvRecorderError::new(
+                PvRecorderErrorStatus::ArgumentError,
+                format!(
+                    "buffered_frames_count value `{}` should be greater than zero",
+                    buffered_frames_count
+                ),
+            ));
+        }
+
+        let lib = unsafe { Library::new(library_path) }.map_err(|err| {
+            PvRecorderError::new(
+                PvRecorderErrorStatus::LibraryLoadError,
+                format!("Failed to load pvrecorder dynamic library: {}", err),
+            )
+        })?;
+        let vtable = PvRecorderInnerVTable::new(lib)?;
+
+        let mut cpvrecorder = std::ptr::null_mut();
+
+        unsafe {
+            let status = (vtable.pv_recorder_init)(
+                frame_length,
+                device_index,
+                buffered_frames_count,
+                addr_of_mut!(cpvrecorder),
+            );
+            check_fn_call_status(status, "pv_recorder_init")?;
+        }
+
+        let selected_device = unsafe {
+            let selected_device_c = (vtable.pv_recorder_get_selected_device)(cpvrecorder);
+            String::from(CStr::from_ptr(selected_device_c).to_str().map_err(|_| {
+                PvRecorderError::new(
+                    PvRecorderErrorStatus::OtherError,
+                    "Failed to convert selected device string",
+                )
+            })?)
+        };
+
+        let sample_rate = unsafe { (vtable.pv_recorder_sample_rate)() };
+
+        let version = unsafe {
+            let version_c = (vtable.pv_recorder_version)();
+            String::from(CStr::from_ptr(version_c).to_str().map_err(|_| {
+                PvRecorderError::new(
+                    PvRecorderErrorStatus::OtherError,
+                    "Failed to convert version string",
+                )
+            })?)
+        };
+
+        Ok(Self {
+            cpvrecorder,
+            frame_length,
+            sample_rate,
+            selected_device,
+            version,
+            vtable,
+        })
+    }
+
+    fn start(&self) -> Result<(), PvRecorderError> {
+        let status = unsafe { (self.vtable.pv_recorder_start)(self.cpvrecorder) };
+        check_fn_call_status(status, "pv_recorder_start")
+    }
+
+    fn stop(&self) -> Result<(), PvRecorderError> {
+        let status = unsafe { (self.vtable.pv_recorder_stop)(self.cpvrecorder) };
+        check_fn_call_status(status, "pv_recorder_stop")
+    }
+
+    fn read(&self) -> Result<Vec<i16>, PvRecorderError> {
+        let mut frame = vec![0; self.frame_length() as usize];
+        let status =
+            unsafe { (self.vtable.pv_recorder_read)(self.cpvrecorder, frame.as_mut_ptr()) };
+        check_fn_call_status(status, "pv_recorder_read")?;
+        Ok(frame)
+    }
+
+    fn set_debug_logging(&self, is_debug_logging_enabled: bool) {
+        unsafe {
+            (self.vtable.pv_recorder_set_debug_logging)(self.cpvrecorder, is_debug_logging_enabled)
+        };
+    }
+
+    fn frame_length(&self) -> i32 {
+        self.frame_length
+    }
+
+    fn is_recording(&self) -> bool {
+        unsafe { (self.vtable.pv_recorder_get_is_recording)(self.cpvrecorder) }
+    }
+
+    fn sample_rate(&self) -> i32 {
+        self.sample_rate
+    }
+
+    fn selected_device(&self) -> String {
+        self.selected_device.clone()
+    }
+
+    fn version(&self) -> String {
+        self.version.clone()
+    }
+
+    pub fn get_available_devices<P: AsRef<Path>>(
+        library_path: P,
+    ) -> Result<Vec<String>, PvRecorderError> {
+        let lib = unsafe { Library::new(library_path.as_ref()) }.map_err(|err| {
+            PvRecorderError::new(
+                PvRecorderErrorStatus::LibraryLoadError,
+                format!("Failed to load pvrecorder dynamic library: {}", err),
+            )
+        })?;
+
+        let vtable = PvRecorderInnerVTable::new(lib)?;
+
+        let mut device_list = Vec::new();
+        let mut device_list_length = 0;
+
+        unsafe {
+            let mut device_list_ptr: *mut c_char = std::ptr::null_mut();
+            let mut device_list_ptr_ptr: *mut *mut c_char = addr_of_mut!(device_list_ptr);
+
+            let status = (vtable.pv_recorder_get_available_devices)(
+                addr_of_mut!(device_list_length),
+                addr_of_mut!(device_list_ptr_ptr),
+            );
+            check_fn_call_status(status, "pv_recorder_get_available_devices")?;
+
+            for i in 0..device_list_length as usize {
+                let device = CStr::from_ptr(*device_list_ptr_ptr.add(i));
+                device_list.push(String::from(device.to_str().map_err(|_| {
+                    PvRecorderError::new(
+                        PvRecorderErrorStatus::OtherError,
+                        "Failed to convert device strings",
+                    )
+                })?));
+            }
+
+            (vtable.pv_recorder_free_available_devices)(device_list_length, device_list_ptr_ptr);
+        }
+        Ok(device_list)
+    }
+}
+
+unsafe impl Send for PvRecorderInner {}
+unsafe impl Sync for PvRecorderInner {}
+
+impl Drop for PvRecorderInner {
+    fn drop(&mut self) {
+        unsafe {
+            (self.vtable.pv_recorder_delete)(self.cpvrecorder);
+        }
+    }
+}
diff --git a/sdk/rust/src/util.rs b/binding/rust/src/util.rs
similarity index 100%
rename from sdk/rust/src/util.rs
rename to binding/rust/src/util.rs
diff --git a/demo/c/CMakeLists.txt b/demo/c/CMakeLists.txt
new file mode 100644
index 00000000..b2d45558
--- /dev/null
+++ b/demo/c/CMakeLists.txt
@@ -0,0 +1,47 @@
+cmake_minimum_required(VERSION 3.4)
+project(pv_recorder_demo VERSION 1.2.0 DESCRIPTION "Picovoice audio recorder library demo.")
+
+if(NOT PV_RECORDER_PLATFORM)
+    message(FATAL_ERROR "No `PV_RECORDER_PLATFORM` value was given. Valid platforms are: \n"
+            "linux, mac-arm64, mac-x86_64, windows, raspberry-pi, raspberry-pi2, "
+            "raspberry-pi3-32, raspberry-pi3-64, raspberry-pi4-32, raspberry-pi4-64, "
+            "jetson-nano, beaglebone")
+endif()
+
+if (${PV_RECORDER_PLATFORM} STREQUAL "beaglebone")
+    set(PV_RECORDER_LIB_DIR ${CMAKE_SOURCE_DIR}/../../lib/beaglebone)
+elseif (${PV_RECORDER_PLATFORM} STREQUAL "mac-arm64")
+    set(PV_RECORDER_LIB_DIR ${CMAKE_SOURCE_DIR}/../../lib/mac/arm64)
+elseif (${PV_RECORDER_PLATFORM} STREQUAL "mac-x86_64")
+    set(PV_RECORDER_LIB_DIR ${CMAKE_SOURCE_DIR}/../../lib/mac/x86_64)
+elseif (${PV_RECORDER_PLATFORM} STREQUAL "linux")
+    set(PV_RECORDER_LIB_DIR ${CMAKE_SOURCE_DIR}/../../lib/linux/x86_64)
+elseif (${PV_RECORDER_PLATFORM} STREQUAL "jetson-nano")
+    set(PV_RECORDER_LIB_DIR ${CMAKE_SOURCE_DIR}/../../lib/jetson/cortex-a57-aarch64)
+elseif (${PV_RECORDER_PLATFORM} STREQUAL "raspberry-pi")
+    set(PV_RECORDER_LIB_DIR ${CMAKE_SOURCE_DIR}/../../lib/raspberry-pi/arm11)
+elseif (${PV_RECORDER_PLATFORM} STREQUAL "raspberry-pi2")
+    set(PV_RECORDER_LIB_DIR ${CMAKE_SOURCE_DIR}/../../lib/raspberry-pi/cortex-a7)
+elseif (${PV_RECORDER_PLATFORM} STREQUAL "raspberry-pi3")
+    set(PV_RECORDER_LIB_DIR ${CMAKE_SOURCE_DIR}/../../lib/raspberry-pi/cortex-a53)
+elseif (${PV_RECORDER_PLATFORM} STREQUAL "raspberry-pi3-64")
+    set(PV_RECORDER_LIB_DIR ${CMAKE_SOURCE_DIR}/../../lib/raspberry-pi/cortex-a53-aarch64)
+elseif (${PV_RECORDER_PLATFORM} STREQUAL "raspberry-pi4")
+    set(PV_RECORDER_LIB_DIR ${CMAKE_SOURCE_DIR}/../../lib/raspberry-pi/cortex-a72)
+elseif (${PV_RECORDER_PLATFORM} STREQUAL "raspberry-pi4-64")
+    set(PV_RECORDER_LIB_DIR ${CMAKE_SOURCE_DIR}/../../lib/raspberry-pi/cortex-a72-aarch64)
+elseif (${PV_RECORDER_PLATFORM} STREQUAL "windows")
+    set(PV_RECORDER_LIB_DIR ${CMAKE_SOURCE_DIR}/../../lib/windows/amd64)
+else ()
+    message(FATAL_ERROR "Unknown platform `${PV_RECORDER_PLATFORM}`.")
+endif ()
+
+include_directories(../../project/include)
+link_directories(${PV_RECORDER_LIB_DIR})
+add_executable(pv_recorder_demo pv_recorder_demo.c)
+target_link_libraries(pv_recorder_demo pv_recorder)
+
+add_custom_command(TARGET pv_recorder_demo
+    POST_BUILD
+    COMMAND ${CMAKE_COMMAND} -E copy_directory ${PV_RECORDER_LIB_DIR} ${CMAKE_BINARY_DIR}
+)
\ No newline at end of file
diff --git a/Preload.CMake b/demo/c/Preload.CMake
similarity index 92%
rename from Preload.CMake
rename to demo/c/Preload.CMake
index 4aa05d98..d4b14183 100644
--- a/Preload.CMake
+++ b/demo/c/Preload.CMake
@@ -1,4 +1,4 @@
-if(WIN32)
+if (WIN32)
     set(CMAKE_GENERATOR "MinGW Makefiles" CACHE STRING "" FORCE)
 else()
     set(CMAKE_GENERATOR "Unix Makefiles" CACHE STRING "" FORCE)
diff --git a/demo/c/README.md b/demo/c/README.md
new file mode 100644
index 00000000..aa0af41c
--- /dev/null
+++ b/demo/c/README.md
@@ -0,0 +1,48 @@
+# PvRecorder Demo
+
+## Requirements
+
+- CMake 3.4+.
+- C99 compatible compiler.
+- **Windows**: MinGW.
+
+## Compatibility
+
+- Linux (x86_64)
+- macOS (x86_64, arm64)
+- Windows (amd64)
+- Raspberry Pi (Zero, 2, 3 and 4)
+- NVIDIA Jetson Nano
+- BeagleBone
+
+## Compiling
+
+Run the following commands to build the demo app:
+
+```console
+git submodule update --init --recursive
+cmake -S . -B build -DPV_RECORDER_PLATFORM={PV_RECORDER_PLATFORM}
+cmake --build build
+```
+
+The `{PV_RECORDER_PLATFORM}` variable will set the compilation flags for the given platform. Exclude this variable
+to get a list of possible values.
+
+## Usage
+
+To see the usage options for the demo:
+```console
+./pv_recorder_demo
+```
+
+Get a list of available audio recording devices:
+```console
+./pv_recorder_demo --show_audio_devices
+```
+
+Record to a file with a given audio device index:
+```console
+./pv_recorder_demo -o test.wav -d 2
+```
+
+Hit `Ctrl+C` to stop recording. If no audio device index (`-d`) is provided, the demo will use the system's default recording device.
diff --git a/demo/c/pv_recorder_demo.c b/demo/c/pv_recorder_demo.c
new file mode 100644
index 00000000..0c920dc2
--- /dev/null
+++ b/demo/c/pv_recorder_demo.c
@@ -0,0 +1,195 @@
+/*
+    Copyright 2021-2023 Picovoice Inc.
+
+    You may not use this file except in compliance with the license. A copy of the license is located in the "LICENSE"
+    file accompanying this source.
+
+    Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+    an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+    specific language governing permissions and limitations under the License.
+*/
+
+#include <getopt.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include "pv_recorder.h"
+
+static volatile bool is_interrupted = false;
+
+void interrupt_handler(int _) {
+    (void) _;
+    is_interrupted = true;
+}
+
+static struct option long_options[] = {
+        {"show_audio_devices", no_argument,       NULL, 's'},
+        {"output_wav_path",    required_argument, NULL, 'o'},
+        {"audio_device_index", required_argument, NULL, 'd'}
+};
+
+static void print_usage(const char *program_name) {
+    fprintf(stderr,
+            "Usage : %s -o OUTPUT_WAV_PATH [-d AUDIO_DEVICE_INDEX]\n"
+            "        %s --show_audio_devices\n",
+            program_name,
+            program_name);
+}
+
+static void show_audio_devices(void) {
+    char **device_list = NULL;
+    int32_t device_list_length = 0;
+
+    // List devices
+    pv_recorder_status_t status = pv_recorder_get_available_devices(
+            &device_list_length,
+            &device_list);
+    if (status != PV_RECORDER_STATUS_SUCCESS) {
+        fprintf(stderr, "Failed to get audio devices with: %s.\n", pv_recorder_status_to_string(status));
+        exit(1);
+    }
+
+    fprintf(stdout, "Printing devices...\n");
+    for (int32_t i = 0; i < device_list_length; i++) {
+        fprintf(stdout, "index: %d, name: %s\n", i, device_list[i]);
+    }
+
+    pv_recorder_free_available_devices(device_list_length, device_list);
+}
+
+static void write_wav_header(FILE *file, int32_t num_samples) {
+    int32_t sample_rate = pv_recorder_sample_rate();
+    const char *chunk_id = "RIFF";
+    const char *format = "WAVE";
+    const char *subchunk1_id = "fmt ";
+    uint32_t subchunk1_size = 16;
+    uint16_t audio_format = 1;
+    uint16_t num_channels = 1;
+    uint32_t byte_rate = sample_rate * num_channels * (sizeof(int16_t));
+    uint16_t block_align = num_channels * (sizeof(int16_t));
+    uint16_t bits_per_sample = 16;
+    const char *subchunk2_id = "data";
+    uint32_t subchunk2_size = num_samples * num_channels * (sizeof(int16_t));;
+    uint32_t chunk_size = 36 + subchunk2_size;
+
+    // Write WAV header
+    fwrite(chunk_id, 4, 1, file);
+    fwrite(&chunk_size, sizeof(chunk_size), 1, file);
+    fwrite(format, 4, 1, file);
+    fwrite(subchunk1_id, 4, 1, file);
+    fwrite(&subchunk1_size, sizeof(subchunk1_size), 1, file);
+    fwrite(&audio_format, sizeof(audio_format), 1, file);
+    fwrite(&num_channels, sizeof(num_channels), 1, file);
+    fwrite(&sample_rate, sizeof(sample_rate), 1, file);
+    fwrite(&byte_rate, sizeof(byte_rate), 1, file);
+    fwrite(&block_align, sizeof(block_align), 1, file);
+    fwrite(&bits_per_sample, sizeof(bits_per_sample), 1, file);
+    fwrite(subchunk2_id, 4, 1, file);
+    fwrite(&subchunk2_size, sizeof(subchunk2_size), 1, file);
+}
+
+int main(int argc, char *argv[]) {
+    const char *output_wav_path = NULL;
+    int32_t device_index = -1;
+
+    int c;
+    while ((c = getopt_long(argc, argv, "so:d:", long_options, NULL)) != -1) {
+        switch (c) {
+            case 's':
+                show_audio_devices();
+                return 0;
+            case 'o':
+                output_wav_path = optarg;
+                break;
+            case 'd':
+                device_index = (int32_t) strtol(optarg, NULL, 10);
+                break;
+            default:
+                exit(1);
+        }
+    }
+
+    if (!output_wav_path) {
+        print_usage(argv[0]);
+        exit(1);
+    }
+
+    signal(SIGINT, interrupt_handler);
+    fprintf(stdout, "pv_recorder version: %s\n", pv_recorder_version());
+
+    fprintf(stdout, "Initializing pv_recorder...\n");
+    const int32_t frame_length = 512;
+    pv_recorder_t *recorder = NULL;
+    pv_recorder_status_t status = pv_recorder_init(
+            frame_length,
+            device_index,
+            10,
+            &recorder);
+    if (status != PV_RECORDER_STATUS_SUCCESS) {
+        fprintf(stderr, "Failed to initialize device with %s.\n", pv_recorder_status_to_string(status));
+        exit(1);
+    }
+
+    pv_recorder_set_debug_logging(recorder, true);
+
+    const char *selected_device = pv_recorder_get_selected_device(recorder);
+    fprintf(stdout, "Selected device: %s.\n", selected_device);
+
+    fprintf(stdout, "Start recording...\n");
+    status = pv_recorder_start(recorder);
+    if (status != PV_RECORDER_STATUS_SUCCESS) {
+        fprintf(stderr, "Failed to start device with %s.\n", pv_recorder_status_to_string(status));
+        exit(1);
+    }
+
+    int16_t *pcm = malloc(frame_length * sizeof(int16_t));
+    if (!pcm) {
+        fprintf(stderr, "Failed to allocate pcm memory.\n");
+        exit(1);
+    }
+
+    FILE *file = fopen(output_wav_path, "wb");
+    if (!file) {
+        fprintf(stderr, "Failed to open file.\n");
+        exit(1);
+    }
+
+    int32_t num_sample_recorded = 0;
+    write_wav_header(file, num_sample_recorded);
+
+    while (!is_interrupted) {
+        status = pv_recorder_read(recorder, pcm);
+        if (status != PV_RECORDER_STATUS_SUCCESS) {
+            fprintf(stderr, "Failed to read with %s.\n", pv_recorder_status_to_string(status));
+            exit(1);
+        }
+
+        const size_t length = fwrite(pcm, sizeof(int16_t), frame_length, file);
+        if (length != frame_length) {
+            fprintf(stderr, "Failed to write bytes to file.\n");
+            exit(1);
+        }
+
+        num_sample_recorded += frame_length;
+    }
+
+    rewind(file);
+    write_wav_header(file, num_sample_recorded);
+
+    fclose(file);
+
+    fprintf(stdout, "Stop recording...\n");
+    status = pv_recorder_stop(recorder);
+    if (status != PV_RECORDER_STATUS_SUCCESS) {
+        fprintf(stderr, "Failed to stop device with %s.\n", pv_recorder_status_to_string(status));
+        exit(1);
+    }
+
+    fprintf(stdout, "Deleting pv_recorder...\n");
+    pv_recorder_delete(recorder);
+    free(pcm);
+
+    return 0;
+}
\ No newline at end of file
diff --git a/demo/dotnet/.editorconfig b/demo/dotnet/.editorconfig
new file mode 100644
index 00000000..a52b6927
--- /dev/null
+++ b/demo/dotnet/.editorconfig
@@ -0,0 +1,373 @@
+root = true
+
+# All files
+[*]
+indent_style = space
+
+# Xml files
+[*.xml]
+indent_size = 2
+
+# C# files
+[*.cs]
+
+#### Core EditorConfig Options ####
+
+# Indentation and spacing
+indent_size = 4
+tab_width = 4
+
+# New line preferences
+insert_final_newline = false
+
+#### .NET Coding Conventions ####
+[*.{cs,vb}]
+
+# Organize usings
+dotnet_separate_import_directive_groups = true
+dotnet_sort_system_directives_first = true
+file_header_template = unset
+
+# this. and Me. preferences
+dotnet_style_qualification_for_event = false:silent
+dotnet_style_qualification_for_field = false:silent
+dotnet_style_qualification_for_method = false:silent
+dotnet_style_qualification_for_property = false:silent
+
+# Language keywords vs BCL types preferences
+dotnet_style_predefined_type_for_locals_parameters_members = true:silent
+dotnet_style_predefined_type_for_member_access = true:silent
+
+# Parentheses preferences
+dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity:silent
+dotnet_style_parentheses_in_other_binary_operators = always_for_clarity:silent
+dotnet_style_parentheses_in_other_operators = never_if_unnecessary:silent
+dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity:silent
+
+# Modifier preferences
+dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent
+
+# Expression-level preferences
+dotnet_style_coalesce_expression = true:suggestion
+dotnet_style_collection_initializer = true:suggestion
+dotnet_style_explicit_tuple_names = true:suggestion
+dotnet_style_null_propagation = true:suggestion
+dotnet_style_object_initializer = true:suggestion
+dotnet_style_operator_placement_when_wrapping = beginning_of_line
+dotnet_style_prefer_auto_properties = true:suggestion
+dotnet_style_prefer_compound_assignment = false
+dotnet_style_prefer_conditional_expression_over_assignment = true:suggestion
+dotnet_style_prefer_conditional_expression_over_return = true:suggestion
+dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion
+dotnet_style_prefer_inferred_tuple_names = true:suggestion
+dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion
+dotnet_style_prefer_simplified_boolean_expressions = true:suggestion
+dotnet_style_prefer_simplified_interpolation = true:suggestion
+
+# Field preferences
+dotnet_style_readonly_field = true:warning
+
+# Parameter preferences
+dotnet_code_quality_unused_parameters = all:suggestion
+
+# Suppression preferences
+dotnet_remove_unnecessary_suppression_exclusions = none
+
+#### C# Coding Conventions ####
+[*.cs]
+
+# var preferences
+csharp_style_var_elsewhere = false:silent
+csharp_style_var_for_built_in_types = false:silent
+csharp_style_var_when_type_is_apparent = false:silent
+
+# Expression-bodied members
+csharp_style_expression_bodied_accessors = true:silent
+csharp_style_expression_bodied_constructors = false:silent
+csharp_style_expression_bodied_indexers = true:silent
+csharp_style_expression_bodied_lambdas = true:suggestion
+csharp_style_expression_bodied_local_functions = false:silent
+csharp_style_expression_bodied_methods = false:silent
+csharp_style_expression_bodied_operators = false:silent
+csharp_style_expression_bodied_properties = true:silent
+
+# Pattern matching preferences
+csharp_style_pattern_matching_over_as_with_null_check = true:suggestion
+csharp_style_pattern_matching_over_is_with_cast_check = true:suggestion
+csharp_style_prefer_not_pattern = true:suggestion
+csharp_style_prefer_pattern_matching = true:silent
+csharp_style_prefer_switch_expression = false
+
+# Null-checking preferences
+csharp_style_conditional_delegate_call = true:suggestion
+
+# Modifier preferences
+csharp_prefer_static_local_function = true:warning
+csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async:silent
+
+# Code-block preferences
+csharp_prefer_braces = true:silent
+csharp_prefer_simple_using_statement = true:suggestion
+
+# Expression-level preferences
+csharp_prefer_simple_default_expression = true:suggestion
+csharp_style_deconstructed_variable_declaration = true:suggestion
+csharp_style_inlined_variable_declaration = true:suggestion
+csharp_style_pattern_local_over_anonymous_function = true:suggestion
+csharp_style_prefer_index_operator = true:suggestion
+csharp_style_prefer_range_operator = true:suggestion
+csharp_style_throw_expression = true:suggestion
+csharp_style_unused_value_assignment_preference = discard_variable:suggestion
+csharp_style_unused_value_expression_statement_preference = discard_variable:silent
+
+# 'using' directive preferences
+csharp_using_directive_placement = outside_namespace:silent
+
+#### C# Formatting Rules ####
+
+# New line preferences
+csharp_new_line_before_catch = true
+csharp_new_line_before_else = true
+csharp_new_line_before_finally = true
+csharp_new_line_before_members_in_anonymous_types = true
+csharp_new_line_before_members_in_object_initializers = true
+csharp_new_line_before_open_brace = all
+csharp_new_line_between_query_expression_clauses = true
+
+# Indentation preferences
+csharp_indent_block_contents = true
+csharp_indent_braces = false
+csharp_indent_case_contents = true
+csharp_indent_case_contents_when_block = true
+csharp_indent_labels = one_less_than_current
+csharp_indent_switch_labels = true
+
+# Space preferences
+csharp_space_after_cast = false
+csharp_space_after_colon_in_inheritance_clause = true
+csharp_space_after_comma = true
+csharp_space_after_dot = false
+csharp_space_after_keywords_in_control_flow_statements = true
+csharp_space_after_semicolon_in_for_statement = true
+csharp_space_around_binary_operators = before_and_after
+csharp_space_around_declaration_statements = false
+csharp_space_before_colon_in_inheritance_clause = true
+csharp_space_before_comma = false
+csharp_space_before_dot = false
+csharp_space_before_open_square_brackets = false
+csharp_space_before_semicolon_in_for_statement = false
+csharp_space_between_empty_square_brackets = false
+csharp_space_between_method_call_empty_parameter_list_parentheses = false
+csharp_space_between_method_call_name_and_opening_parenthesis = false
+csharp_space_between_method_call_parameter_list_parentheses = false
+csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
+csharp_space_between_method_declaration_name_and_open_parenthesis = false
+csharp_space_between_method_declaration_parameter_list_parentheses = false
+csharp_space_between_parentheses = false
+csharp_space_between_square_brackets = false
+
+# Wrapping preferences
+csharp_preserve_single_line_blocks = true
+csharp_preserve_single_line_statements = true
+
+#### Naming styles ####
+[*.{cs,vb}]
+
+# Naming rules
+
+dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.severity = suggestion
+dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.symbols = types_and_namespaces
+dotnet_naming_rule.types_and_namespaces_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.interfaces_should_be_ipascalcase.severity = suggestion
+dotnet_naming_rule.interfaces_should_be_ipascalcase.symbols = interfaces
+dotnet_naming_rule.interfaces_should_be_ipascalcase.style = ipascalcase
+
+dotnet_naming_rule.type_parameters_should_be_tpascalcase.severity = suggestion
+dotnet_naming_rule.type_parameters_should_be_tpascalcase.symbols = type_parameters
+dotnet_naming_rule.type_parameters_should_be_tpascalcase.style = tpascalcase
+
+dotnet_naming_rule.methods_should_be_pascalcase.severity = suggestion
+dotnet_naming_rule.methods_should_be_pascalcase.symbols = methods
+dotnet_naming_rule.methods_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.properties_should_be_pascalcase.severity = suggestion
+dotnet_naming_rule.properties_should_be_pascalcase.symbols = properties
+dotnet_naming_rule.properties_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.events_should_be_pascalcase.severity = suggestion
+dotnet_naming_rule.events_should_be_pascalcase.symbols = events
+dotnet_naming_rule.events_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.local_variables_should_be_camelcase.severity = suggestion
+dotnet_naming_rule.local_variables_should_be_camelcase.symbols = local_variables
+dotnet_naming_rule.local_variables_should_be_camelcase.style = camelcase
+
+dotnet_naming_rule.local_constants_should_be_camelcase.severity = suggestion
+dotnet_naming_rule.local_constants_should_be_camelcase.symbols = local_constants
+dotnet_naming_rule.local_constants_should_be_camelcase.style = camelcase
+
+dotnet_naming_rule.parameters_should_be_camelcase.severity = suggestion
+dotnet_naming_rule.parameters_should_be_camelcase.symbols = parameters
+dotnet_naming_rule.parameters_should_be_camelcase.style = camelcase
+
+dotnet_naming_rule.public_fields_should_be_pascalcase.severity = suggestion
+dotnet_naming_rule.public_fields_should_be_pascalcase.symbols = public_fields
+dotnet_naming_rule.public_fields_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.private_fields_should_be__camelcase.severity = suggestion
+dotnet_naming_rule.private_fields_should_be__camelcase.symbols = private_fields
+dotnet_naming_rule.private_fields_should_be__camelcase.style = _camelcase
+
+dotnet_naming_rule.private_static_fields_should_be_s_camelcase.severity = suggestion
+dotnet_naming_rule.private_static_fields_should_be_s_camelcase.symbols = private_static_fields
+dotnet_naming_rule.private_static_fields_should_be_s_camelcase.style = s_camelcase
+
+dotnet_naming_rule.public_constant_fields_should_be_pascalcase.severity = suggestion
+dotnet_naming_rule.public_constant_fields_should_be_pascalcase.symbols = public_constant_fields
+dotnet_naming_rule.public_constant_fields_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.private_constant_fields_should_be_pascalcase.severity = suggestion
+dotnet_naming_rule.private_constant_fields_should_be_pascalcase.symbols = private_constant_fields
+dotnet_naming_rule.private_constant_fields_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.public_static_readonly_fields_should_be_pascalcase.severity = suggestion
+dotnet_naming_rule.public_static_readonly_fields_should_be_pascalcase.symbols = public_static_readonly_fields
+dotnet_naming_rule.public_static_readonly_fields_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.private_static_readonly_fields_should_be_pascalcase.severity = suggestion
+dotnet_naming_rule.private_static_readonly_fields_should_be_pascalcase.symbols = private_static_readonly_fields
+dotnet_naming_rule.private_static_readonly_fields_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.enums_should_be_pascalcase.severity = suggestion
+dotnet_naming_rule.enums_should_be_pascalcase.symbols = enums
+dotnet_naming_rule.enums_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.local_functions_should_be_pascalcase.severity = suggestion
+dotnet_naming_rule.local_functions_should_be_pascalcase.symbols = local_functions
+dotnet_naming_rule.local_functions_should_be_pascalcase.style = pascalcase
+
+dotnet_naming_rule.non_field_members_should_be_pascalcase.severity = none
+dotnet_naming_rule.non_field_members_should_be_pascalcase.symbols = non_field_members
+dotnet_naming_rule.non_field_members_should_be_pascalcase.style = pascalcase
+
+# Symbol specifications
+
+dotnet_naming_symbols.interfaces.applicable_kinds = interface
+dotnet_naming_symbols.interfaces.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.interfaces.required_modifiers = 
+
+dotnet_naming_symbols.enums.applicable_kinds = enum
+dotnet_naming_symbols.enums.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.enums.required_modifiers = 
+
+dotnet_naming_symbols.events.applicable_kinds = event
+dotnet_naming_symbols.events.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.events.required_modifiers = 
+
+dotnet_naming_symbols.methods.applicable_kinds = method
+dotnet_naming_symbols.methods.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.methods.required_modifiers = 
+
+dotnet_naming_symbols.properties.applicable_kinds = property
+dotnet_naming_symbols.properties.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.properties.required_modifiers = 
+
+dotnet_naming_symbols.public_fields.applicable_kinds = field
+dotnet_naming_symbols.public_fields.applicable_accessibilities = public, internal
+dotnet_naming_symbols.public_fields.required_modifiers = 
+
+dotnet_naming_symbols.private_fields.applicable_kinds = field
+dotnet_naming_symbols.private_fields.applicable_accessibilities = private, protected, protected_internal, private_protected
+dotnet_naming_symbols.private_fields.required_modifiers = 
+
+dotnet_naming_symbols.private_static_fields.applicable_kinds = field
+dotnet_naming_symbols.private_static_fields.applicable_accessibilities = private, protected, protected_internal, private_protected
+dotnet_naming_symbols.private_static_fields.required_modifiers = static
+
+dotnet_naming_symbols.types_and_namespaces.applicable_kinds = namespace, class, struct, interface, enum
+dotnet_naming_symbols.types_and_namespaces.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.types_and_namespaces.required_modifiers = 
+
+dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method
+dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected
+dotnet_naming_symbols.non_field_members.required_modifiers = 
+
+dotnet_naming_symbols.type_parameters.applicable_kinds = namespace
+dotnet_naming_symbols.type_parameters.applicable_accessibilities = *
+dotnet_naming_symbols.type_parameters.required_modifiers = 
+
+dotnet_naming_symbols.private_constant_fields.applicable_kinds = field
+dotnet_naming_symbols.private_constant_fields.applicable_accessibilities = private, protected, protected_internal, private_protected
+dotnet_naming_symbols.private_constant_fields.required_modifiers = const
+
+dotnet_naming_symbols.local_variables.applicable_kinds = local
+dotnet_naming_symbols.local_variables.applicable_accessibilities = local
+dotnet_naming_symbols.local_variables.required_modifiers = 
+
+dotnet_naming_symbols.local_constants.applicable_kinds = local
+dotnet_naming_symbols.local_constants.applicable_accessibilities = local
+dotnet_naming_symbols.local_constants.required_modifiers = const
+
+dotnet_naming_symbols.parameters.applicable_kinds = parameter
+dotnet_naming_symbols.parameters.applicable_accessibilities = *
+dotnet_naming_symbols.parameters.required_modifiers = 
+
+dotnet_naming_symbols.public_constant_fields.applicable_kinds = field
+dotnet_naming_symbols.public_constant_fields.applicable_accessibilities = public, internal
+dotnet_naming_symbols.public_constant_fields.required_modifiers = const
+
+dotnet_naming_symbols.public_static_readonly_fields.applicable_kinds = field
+dotnet_naming_symbols.public_static_readonly_fields.applicable_accessibilities = public, internal
+dotnet_naming_symbols.public_static_readonly_fields.required_modifiers = readonly, static
+
+dotnet_naming_symbols.private_static_readonly_fields.applicable_kinds = field
+dotnet_naming_symbols.private_static_readonly_fields.applicable_accessibilities = private, protected, protected_internal, private_protected
+dotnet_naming_symbols.private_static_readonly_fields.required_modifiers = readonly, static
+
+dotnet_naming_symbols.local_functions.applicable_kinds = local_function
+dotnet_naming_symbols.local_functions.applicable_accessibilities = *
+dotnet_naming_symbols.local_functions.required_modifiers = 
+
+# Naming styles
+
+dotnet_naming_style.pascalcase.required_prefix = 
+dotnet_naming_style.pascalcase.required_suffix = 
+dotnet_naming_style.pascalcase.word_separator = 
+dotnet_naming_style.pascalcase.capitalization = pascal_case
+
+dotnet_naming_style.ipascalcase.required_prefix = I
+dotnet_naming_style.ipascalcase.required_suffix = 
+dotnet_naming_style.ipascalcase.word_separator = 
+dotnet_naming_style.ipascalcase.capitalization = pascal_case
+
+dotnet_naming_style.tpascalcase.required_prefix = T
+dotnet_naming_style.tpascalcase.required_suffix = 
+dotnet_naming_style.tpascalcase.word_separator = 
+dotnet_naming_style.tpascalcase.capitalization = pascal_case
+
+dotnet_naming_style._camelcase.required_prefix = _
+dotnet_naming_style._camelcase.required_suffix = 
+dotnet_naming_style._camelcase.word_separator = 
+dotnet_naming_style._camelcase.capitalization = camel_case
+
+dotnet_naming_style.camelcase.required_prefix = 
+dotnet_naming_style.camelcase.required_suffix = 
+dotnet_naming_style.camelcase.word_separator = 
+dotnet_naming_style.camelcase.capitalization = camel_case
+
+dotnet_naming_style.s_camelcase.required_prefix = s_
+dotnet_naming_style.s_camelcase.required_suffix = 
+dotnet_naming_style.s_camelcase.word_separator = 
+dotnet_naming_style.s_camelcase.capitalization = camel_case
+
+#### Exclusions
+
+dotnet_diagnostic.IDE0130.severity = none
+dotnet_diagnostic.IDE1006.severity = none
+dotnet_diagnostic.IDE0018.severity = none
+dotnet_diagnostic.IDE0010.severity = none
+dotnet_diagnostic.IDE0090.severity = none
+dotnet_diagnostic.IDE0046.severity = none
+dotnet_diagnostic.IDE0078.severity = none
+dotnet_diagnostic.IDE0058.severity = none
diff --git a/demo/dotnet/.gitignore b/demo/dotnet/.gitignore
new file mode 100644
index 00000000..24bc1cb6
--- /dev/null
+++ b/demo/dotnet/.gitignore
@@ -0,0 +1,5 @@
+.vs
+PvRecorderDemo/bin/**
+PvRecorderDemo/obj/**
+PvRecorderDemo/Properties/**
+*.user
diff --git a/demo/dotnet/PvRecorderDemo.sln b/demo/dotnet/PvRecorderDemo.sln
new file mode 100644
index 00000000..1f999461
--- /dev/null
+++ b/demo/dotnet/PvRecorderDemo.sln
@@ -0,0 +1,25 @@
+﻿
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 16
+VisualStudioVersion = 16.0.30517.126
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PvRecorderDemo", "PvRecorderDemo\PvRecorderDemo.csproj", "{5AE5D341-BE0B-4E35-A4F0-CBCD339C844E}"
+EndProject
+Global
+	GlobalSection(SolutionConfigurationPlatforms) = preSolution
+		Debug|AnyCPU = Debug|AnyCPU
+		Release|AnyCPU = Release|AnyCPU
+	EndGlobalSection
+	GlobalSection(ProjectConfigurationPlatforms) = postSolution
+		{5AE5D341-BE0B-4E35-A4F0-CBCD339C844E}.Debug|AnyCPU.ActiveCfg = Debug|Any CPU
+		{5AE5D341-BE0B-4E35-A4F0-CBCD339C844E}.Debug|AnyCPU.Build.0 = Debug|Any CPU
+		{5AE5D341-BE0B-4E35-A4F0-CBCD339C844E}.Release|AnyCPU.ActiveCfg = Release|Any CPU
+		{5AE5D341-BE0B-4E35-A4F0-CBCD339C844E}.Release|AnyCPU.Build.0 = Release|Any CPU
+	EndGlobalSection
+	GlobalSection(SolutionProperties) = preSolution
+		HideSolutionNode = FALSE
+	EndGlobalSection
+	GlobalSection(ExtensibilityGlobals) = postSolution
+		SolutionGuid = {718FE840-6F27-4751-957F-382E9780BBFC}
+	EndGlobalSection
+EndGlobal
diff --git a/demo/dotnet/PvRecorderDemo/PvRecorderDemo.cs b/demo/dotnet/PvRecorderDemo/PvRecorderDemo.cs
new file mode 100644
index 00000000..0ed0b73f
--- /dev/null
+++ b/demo/dotnet/PvRecorderDemo/PvRecorderDemo.cs
@@ -0,0 +1,173 @@
+﻿/*
+    Copyright 2023 Picovoice Inc.
+
+    You may not use this file except in compliance with the license. A copy of the license is located in the "LICENSE"
+    file accompanying this source.
+
+    Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+    an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+    specific language governing permissions and limitations under the License.
+*/
+
+using System;
+using System.IO;
+using System.Text;
+using System.Threading;
+
+using Pv;
+
+namespace PvRecorderDemo
+{
+    class Demo
+    {
+        private static readonly int FRAME_LENGTH = 512;
+
+        /// <summary>
+        /// Writes the RIFF header for a file in WAV format
+        /// </summary>
+        /// <param name="writer">Output stream to WAV file</param>
+        /// <param name="channelCount">Number of channels</param>
+        /// <param name="bitDepth">Number of bits per sample</param>
+        /// <param name="sampleRate">Sampling rate in Hz</param>
+        /// <param name="totalSampleCount">Total number of samples written to the file</param>
+        private static void WriteWavHeader(BinaryWriter writer, ushort channelCount, ushort bitDepth, int sampleRate, int totalSampleCount)
+        {
+            if (writer == null)
+                return;
+
+            writer.Seek(0, SeekOrigin.Begin);
+            writer.Write(Encoding.ASCII.GetBytes("RIFF"));
+            writer.Write((bitDepth / 8 * totalSampleCount) + 36);
+            writer.Write(Encoding.ASCII.GetBytes("WAVE"));
+            writer.Write(Encoding.ASCII.GetBytes("fmt "));
+            writer.Write(16);
+            writer.Write((ushort)1);
+            writer.Write(channelCount);
+            writer.Write(sampleRate);
+            writer.Write(sampleRate * channelCount * bitDepth / 8);
+            writer.Write((ushort)(channelCount * bitDepth / 8));
+            writer.Write(bitDepth);
+            writer.Write(Encoding.ASCII.GetBytes("data"));
+            writer.Write(bitDepth / 8 * totalSampleCount);
+        }
+
+        /// <summary>
+        /// Lists available audio input devices.
+        /// </summary>
+        public static void ShowAudioDevices()
+        {
+            string[] devices = PvRecorder.GetAvailableDevices();
+            for (int i = 0; i < devices.Length; i++)
+            {
+                Console.WriteLine($"index: {i}, device name: {devices[i]}");
+            }
+        }
+
+        private static readonly string HELP_STR = "Available options: \n" +
+            " --output_wav_path: Path to write recorded audio to in WAV format.\n" +
+            " --audio_device_index: Index of audio device to use for recording.\n" +
+            " --show_audio_devices: Print available recording devices.\n";
+
+        static void Main(string[] args)
+        {
+            if (args.Length == 0)
+            {
+                Console.WriteLine(HELP_STR);
+                Console.Read();
+                return;
+            }
+
+            bool showAudioDevices = false;
+            int audioDeviceIndex = -1;
+            string outputWavPath = null;
+
+            int argIndex = 0;
+            while (argIndex < args.Length)
+            {
+                if (args[argIndex] == "--show_audio_devices")
+                {
+                    showAudioDevices = true;
+                    argIndex++;
+                }
+                else if (args[argIndex] == "--audio_device_index")
+                {
+                    if (++argIndex < args.Length && int.TryParse(args[argIndex], out int deviceIndex))
+                    {
+                        audioDeviceIndex = deviceIndex;
+                        argIndex++;
+                    }
+                }
+                else if (args[argIndex] == "--output_wav_path")
+                {
+                    if (++argIndex < args.Length)
+                    {
+                        outputWavPath = args[argIndex++];
+
+                    }
+                }
+                else
+                {
+                    argIndex++;
+                }
+            }
+
+            if (showAudioDevices)
+            {
+                ShowAudioDevices();
+                return;
+            }
+
+            try
+            {
+                using (PvRecorder recorder = PvRecorder.Create(FRAME_LENGTH))
+                {
+                    int totalSamplesWritten = 0;
+                    BinaryWriter outputFileWriter = null;
+                    if (outputWavPath != null)
+                    {
+                        outputFileWriter = new BinaryWriter(new FileStream(outputWavPath, FileMode.OpenOrCreate, FileAccess.Write));
+                        WriteWavHeader(outputFileWriter, 1, 16, recorder.SampleRate, totalSamplesWritten);
+                    }
+
+                    Console.CancelKeyPress += delegate (object sender, ConsoleCancelEventArgs e)
+                    {
+                        e.Cancel = true;
+                        recorder.Stop();
+                        Console.WriteLine("Stopping...");
+                    };
+
+                    recorder.Start();
+                    Console.WriteLine($"Using PvRecorder version: {recorder.Version}");
+                    Console.WriteLine($"Using device: {recorder.SelectedDevice}");
+
+                    while (recorder.IsRecording)
+                    {
+                        short[] frame = recorder.Read();
+                        if (outputFileWriter != null)
+                        {
+                            foreach (short sample in frame)
+                            {
+                                outputFileWriter.Write(sample);
+                            }
+                            totalSamplesWritten += frame.Length;
+                        }
+
+                        Thread.Yield();
+                    }
+
+                    if (outputFileWriter != null)
+                    {
+                        WriteWavHeader(outputFileWriter, 1, 16, recorder.SampleRate, totalSamplesWritten);
+                        outputFileWriter.Flush();
+                        outputFileWriter.Dispose();
+                        Console.WriteLine($"Output file written to '{outputWavPath}'");
+                    }
+                }
+            }
+            catch (Exception e)
+            {
+                Console.WriteLine($"Exception: {e.Message}");
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/sdk/dotnet/Demo/Demo.csproj b/demo/dotnet/PvRecorderDemo/PvRecorderDemo.csproj
similarity index 52%
rename from sdk/dotnet/Demo/Demo.csproj
rename to demo/dotnet/PvRecorderDemo/PvRecorderDemo.csproj
index 6a9e2953..51329bba 100644
--- a/sdk/dotnet/Demo/Demo.csproj
+++ b/demo/dotnet/PvRecorderDemo/PvRecorderDemo.csproj
@@ -1,12 +1,13 @@
-<Project Sdk="Microsoft.NET.Sdk">
+﻿<Project Sdk="Microsoft.NET.Sdk">
 
     <PropertyGroup>
         <OutputType>Exe</OutputType>
         <TargetFramework>net6.0</TargetFramework>
+        <Configurations>Debug;Release</Configurations>
     </PropertyGroup>
 
     <ItemGroup>
-        <ProjectReference Include="..\PvRecorder\PvRecorder.csproj" />
+      <PackageReference Include="PvRecorder" Version="1.2.1" />
     </ItemGroup>
 
 </Project>
diff --git a/demo/dotnet/README.md b/demo/dotnet/README.md
new file mode 100644
index 00000000..de844dc5
--- /dev/null
+++ b/demo/dotnet/README.md
@@ -0,0 +1,61 @@
+# PvRecorder Demo for .NET
+
+This project contains a .NET command-line demo for PvRecorder that demonstrates how to use PvRecorder to record audio to a WAV file.
+
+## PvRecorder
+
+PvRecorder is a user-friendly, cross-platform audio recorder that has been specifically designed for real-time audio processing.
+It provides developers with access to the input stream of an audio device, which is divided into data frames of a given size.
+
+## Requirements
+
+- .NET 6.0
+
+## Compatibility
+
+- Linux (x86_64)
+- macOS (x86_64, arm64)
+- Windows (x86_64)
+- Raspberry Pi:
+  - 2
+  - 3 (32 and 64 bit)
+  - 4 (32 and 64 bit)
+- NVIDIA Jetson Nano
+- BeagleBone
+
+## Installation
+
+This demo uses [Microsoft's .NET SDK](https://dotnet.microsoft.com/download).
+
+Build with the dotnet CLI:
+
+```console
+dotnet build
+```
+
+## Usage
+
+To build the demo:
+
+```console
+cd demo/dotnet/PvRecorderDemo
+dotnet build
+```
+
+To show the available audio devices run:
+
+```console
+dotnet run -- --show_audio_devices
+```
+
+To run the demo, give it a file to record audio to:
+
+```console
+dotnet run -- --output_wav_path ${OUTPUT_WAV_FILE}
+```
+
+You can also select the audio device index to use for recording (use `--show_audio_devices` to see options):
+
+```console
+dotnet run -- --output_wav_path ${OUTPUT_WAV_FILE} --audio_device_index ${DEVICE_INDEX}
+```
\ No newline at end of file
diff --git a/demo/go/README.md b/demo/go/README.md
new file mode 100644
index 00000000..ca494abc
--- /dev/null
+++ b/demo/go/README.md
@@ -0,0 +1,31 @@
+# PvRecorder Demo for Go
+
+## Compatibility
+
+- Go 1.16+
+- Runs on Linux (x86_64), macOS (x86_64 and arm64), Windows (x86_64), Raspberry Pi (all variants), NVIDIA Jetson (Nano), and BeagleBone.
+- **Windows**: The demo requires `cgo`, which means that you need to install a gcc compiler like [Mingw](http://mingw-w64.org/) to build it properly.
+
+## Usage
+
+NOTE: The working directory for the following go commands is:
+
+```console
+pvrecorder/demo/go
+```
+
+In the following instructions, we will refer to  `{AUDIO_DEVICE_INDEX}` as the index of the audio device to use, and `{OUTPUT_WAV_PATH}` as the file path to save the audio data in `wav` format.
+
+`{AUDIO_DEVICE_INDEX}` defaults to -1 and `{OUTPUT_WAV_PATH}` can be empty if you wish to not save any data.
+
+To show the available audio devices run:
+
+```console
+go run demo.go --show_audio_devices
+```
+
+To run PvRecorder run:
+
+```console
+go run demo.go --audio_device_index {AUDIO_DEVICE_INDEX} --output_wav_path {OUTPUT_WAV_PATH}
+```
diff --git a/demo/go/demo.go b/demo/go/demo.go
new file mode 100644
index 00000000..75e5616e
--- /dev/null
+++ b/demo/go/demo.go
@@ -0,0 +1,107 @@
+// Copyright 2021-2023 Picovoice Inc.
+//
+// You may not use this file except in compliance with the license. A copy of the license is
+// located in the "LICENSE" file accompanying this source.
+//
+// Unless required by applicable law or agreed to in writing, software distributed under the
+// License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+// express or implied. See the License for the specific language governing permissions and
+// limitations under the License.
+//
+
+package main
+
+import (
+	"flag"
+	"log"
+	"os"
+	"os/signal"
+	"path/filepath"
+
+	pvrecorder "github.com/Picovoice/pvrecorder/binding/go"
+	"github.com/go-audio/wav"
+)
+
+func main() {
+	showAudioDevices := flag.Bool("show_audio_devices", false, "Display all the available input devices")
+	audioDeviceIndex := flag.Int("audio_device_index", -1, "Index of audio input device to use.")
+	outputWavPath := flag.String("output_wav_path", "", "Output path to save recorded a wav file.")
+	flag.Parse()
+
+	log.Printf("pvrecorder.go version: %s\n", pvrecorder.Version)
+
+	if *showAudioDevices {
+		log.Println("Printing devices...")
+		if devices, err := pvrecorder.GetAvailableDevices(); err != nil {
+			log.Fatalf("Error: %s.\n", err.Error())
+		} else {
+			for i, device := range devices {
+				log.Printf("index: %d, device name: %s\n", i, device)
+			}
+		}
+		return
+	}
+
+	recorder := pvrecorder.PvRecorder{
+		DeviceIndex:         *audioDeviceIndex,
+		FrameLength:         512,
+		BufferedFramesCount: 10,
+	}
+
+	log.Println("Initializing...")
+	if err := recorder.Init(); err != nil {
+		log.Fatalf("Error: %s.\n", err.Error())
+	}
+	defer recorder.Delete()
+
+	log.Printf("Using device: %s", recorder.GetSelectedDevice())
+
+	log.Println("Starting...")
+	if err := recorder.Start(); err != nil {
+		log.Fatalf("Error: %s.\n", err.Error())
+	}
+
+	signalCh := make(chan os.Signal, 1)
+	waitCh := make(chan struct{})
+	signal.Notify(signalCh, os.Interrupt)
+
+	go func() {
+		<-signalCh
+		close(waitCh)
+	}()
+
+	var outputWav *wav.Encoder
+	if *outputWavPath != "" {
+		outputFilePath, _ := filepath.Abs(*outputWavPath)
+		outputFile, err := os.Create(outputFilePath)
+		if err != nil {
+			log.Fatalf("Failed to create output audio at path %s", outputFilePath)
+		}
+		defer outputFile.Close()
+
+		outputWav = wav.NewEncoder(outputFile, pvrecorder.SampleRate, 16, 1, 1)
+		defer outputWav.Close()
+	}
+
+waitLoop:
+	for {
+		select {
+		case <-waitCh:
+			log.Println("Stopping...")
+			break waitLoop
+		default:
+			pcm, err := recorder.Read()
+			if err != nil {
+				log.Fatalf("Error: %s.\n", err.Error())
+			}
+			if outputWav != nil {
+				for outputBufIndex := range pcm {
+					err := outputWav.WriteFrame(pcm[outputBufIndex])
+					if err != nil {
+						log.Fatal(err)
+					}
+				}
+			}
+		}
+	}
+}
diff --git a/demo/go/go.mod b/demo/go/go.mod
new file mode 100644
index 00000000..f6e7017d
--- /dev/null
+++ b/demo/go/go.mod
@@ -0,0 +1,8 @@
+module pvrecorderdemo
+
+go 1.16
+
+require (
+	github.com/Picovoice/pvrecorder/binding/go v1.2.0
+	github.com/go-audio/wav v1.0.0
+)
\ No newline at end of file
diff --git a/demo/go/go.sum b/demo/go/go.sum
new file mode 100644
index 00000000..d2b34519
--- /dev/null
+++ b/demo/go/go.sum
@@ -0,0 +1,8 @@
+github.com/Picovoice/pvrecorder/binding/go v1.2.0 h1:MbfURadQ2C5c4HLv+0nXU9zStqwhnKsAF6D16t7sUn0=
+github.com/Picovoice/pvrecorder/binding/go v1.2.0/go.mod h1:gQdvBAjoKmRxMFh8W9cVKWcqHsWvu+d13sCPVFm7dhg=
+github.com/go-audio/audio v1.0.0 h1:zS9vebldgbQqktK4H0lUqWrG8P0NxCJVqcj7ZpNnwd4=
+github.com/go-audio/audio v1.0.0/go.mod h1:6uAu0+H2lHkwdGsAY+j2wHPNPpPoeg5AaEFh9FlA+Zs=
+github.com/go-audio/riff v1.0.0 h1:d8iCGbDvox9BfLagY94fBynxSPHO80LmZCaOsmKxokA=
+github.com/go-audio/riff v1.0.0/go.mod h1:l3cQwc85y79NQFCRB7TiPoNiaijp6q8Z0Uv38rVG498=
+github.com/go-audio/wav v1.0.0 h1:WdSGLhtyud6bof6XHL28xKeCQRzCV06pOFo3LZsFdyE=
+github.com/go-audio/wav v1.0.0/go.mod h1:3yoReyQOsiARkvPl3ERCi8JFjihzG6WhjYpZCf5zAWE=
diff --git a/demo/nodejs/.gitignore b/demo/nodejs/.gitignore
new file mode 100644
index 00000000..3c3629e6
--- /dev/null
+++ b/demo/nodejs/.gitignore
@@ -0,0 +1,1 @@
+node_modules
diff --git a/demo/nodejs/.npmignore b/demo/nodejs/.npmignore
new file mode 100644
index 00000000..3c3629e6
--- /dev/null
+++ b/demo/nodejs/.npmignore
@@ -0,0 +1,1 @@
+node_modules
diff --git a/demo/nodejs/README.md b/demo/nodejs/README.md
new file mode 100644
index 00000000..230643a4
--- /dev/null
+++ b/demo/nodejs/README.md
@@ -0,0 +1,38 @@
+# PvRecorder Demo for Node.js
+
+## Compatibility
+
+- Node.js 14+
+- Runs on Linux (x86_64), macOS (x86_64 and arm64), Windows (x86_64), Raspberry Pi (2, 3, 4), NVIDIA Jetson (Nano), and BeagleBone.
+
+## Installation:
+
+To install the demos and make them available on the command line, use either of the following `yarn` or `npm` commands:
+
+```console
+yarn global add @picovoice/pvrecorder-node-demo
+```
+
+(or)
+
+```console
+npm install -g @picovoice/pvrecorder-node-demo
+```
+
+## Usage
+
+In the following instructions, we will refer to  `{AUDIO_DEVICE_INDEX}` as the index of the audio device to use, and `{OUTPUT_WAV_PATH}` as the file path to save the audio data in `wav` format.
+
+`{AUDIO_DEVICE_INDEX}` defaults to -1 and `{OUTPUT_WAV_PATH}` can be empty if you wish to not save any data.
+
+To show the available audio devices run:
+
+```console
+pvrecorder-node-demo --show_audio_devices
+```
+
+To run the audio recorder:
+
+```console
+pvrecorder-node-demo --audio_device_index {AUDIO_DEVICE_INDEX} --output_wav_path {OUTPUT_WAV_PATH}
+```
diff --git a/demo/nodejs/demo.js b/demo/nodejs/demo.js
new file mode 100644
index 00000000..6902b89a
--- /dev/null
+++ b/demo/nodejs/demo.js
@@ -0,0 +1,95 @@
+#! /usr/bin/env node
+
+// Copyright 2021-2023 Picovoice Inc.
+//
+// You may not use this file except in compliance with the license. A copy of the license is located in the "LICENSE"
+// file accompanying this source.
+//
+// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+// an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations under the License.
+//
+"use strict";
+
+const fs = require("fs");
+const { program } = require("commander");
+const { WaveFile } = require("wavefile");
+
+const { PvRecorder } = require("@picovoice/pvrecorder-node");
+
+let isInterrupted = false;
+
+program
+.option(
+  "-i, --audio_device_index <number>",
+  "index of audio device to use to record audio",
+  Number,
+  -1
+).option(
+  "-d, --show_audio_devices",
+  "show the list of available devices"
+).option(
+  "-o, --output_wav_path <string>",
+  "output_path to save wav file"
+);
+
+if (process.argv.length < 2) {
+  program.help();
+}
+program.parse(process.argv);
+
+async function runDemo() {
+  let audioDeviceIndex = program["audio_device_index"];
+  let showAudioDevices = program["show_audio_devices"];
+  let outputWavPath = program["output_wav_path"];
+
+  if (showAudioDevices) {
+    const devices = PvRecorder.getAvailableDevices();
+    for (let i = 0; i < devices.length; i++) {
+      console.log(`index: ${i}, device name: ${devices[i]}`)
+    }
+  } else {
+    const wav = new WaveFile();
+    const frames = [];
+
+    const frameLength = 512;
+    const recorder = new PvRecorder(frameLength, audioDeviceIndex);
+    console.log(`Using PvRecorder version: ${recorder.version}`);
+
+    recorder.start();
+    console.log(`Using device: ${recorder.getSelectedDevice()}`);
+
+    while (!isInterrupted) {
+      const frame = await recorder.read();
+      if (outputWavPath) {
+        frames.push(frame);
+      }
+    }
+
+    if (outputWavPath) {
+      const audioData = new Int16Array(recorder.frameLength * frames.length);
+      for (let i = 0; i < frames.length; i++) {
+        audioData.set(frames[i], i * recorder.frameLength);
+      }
+
+      wav.fromScratch(1, recorder.sampleRate, '16', audioData);
+      fs.writeFileSync(outputWavPath, wav.toBuffer());
+    }
+
+    console.log("Stopping...");
+    recorder.release();
+  }
+}
+
+// setup interrupt
+process.on("SIGINT", function () {
+  isInterrupted = true;
+});
+
+(async function () {
+  try {
+    await runDemo();
+  } catch (e) {
+    console.error(e.toString());
+  }
+})();
diff --git a/demo/nodejs/package.json b/demo/nodejs/package.json
new file mode 100644
index 00000000..6b158a68
--- /dev/null
+++ b/demo/nodejs/package.json
@@ -0,0 +1,30 @@
+{
+  "name": "@picovoice/pvrecorder-node-demo",
+  "version": "1.2.0",
+  "description": "Picovoice PvRecorder demo",
+  "scripts": {
+    "demo": "node demo.js"
+  },
+  "bin": {
+    "pvrecorder-node-demo": "./demo.js"
+  },
+  "keywords": [
+    "microphone, mic, sound, audio, recorder"
+  ],
+  "author": "Picovoice, Inc.",
+  "license": "Apache-2.0",
+  "dependencies": {
+    "@picovoice/pvrecorder-node": "~1.2.0",
+    "commander": "^6.1.0",
+    "wavefile": "^11.0.0"
+  },
+  "devDependencies": {},
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/Picovoice/pvrecorder.git",
+    "directory": "demo/nodejs"
+  },
+  "engines": {
+    "node": ">=14.0.0"
+  }
+}
diff --git a/demo/nodejs/yarn.lock b/demo/nodejs/yarn.lock
new file mode 100644
index 00000000..8ebe3496
--- /dev/null
+++ b/demo/nodejs/yarn.lock
@@ -0,0 +1,18 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
+# yarn lockfile v1
+
+
+"@picovoice/pvrecorder-node@~1.2.0":
+  version "1.2.0"
+  resolved "https://registry.yarnpkg.com/@picovoice/pvrecorder-node/-/pvrecorder-node-1.2.0.tgz#a53c3fd589679629f00148f4dd893659141c0d75"
+  integrity sha512-wb04sy/2wsJOYyiQ65Uy9MFDJ1vhyXGp5led6daf7Pw7E2NjH6mdXxooGvs4+TbpF+88E4QgyYIZhUrzlpf40g==
+
+commander@^6.1.0:
+  version "6.2.1"
+  resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c"
+  integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA==
+
+wavefile@^11.0.0:
+  version "11.0.0"
+  resolved "https://registry.yarnpkg.com/wavefile/-/wavefile-11.0.0.tgz#9302165874327ff63a704d00b154c753eaa1b8e7"
+  integrity sha512-/OBiAALgWU24IG7sC84cDO/KfFuvajWc5Uec0oV2zrpOOZZDgGdOwHwgEzOrwh8jkubBk7PtZfQBIcI1OaE5Ng==
diff --git a/demo/python/.gitignore b/demo/python/.gitignore
new file mode 100644
index 00000000..27f447a7
--- /dev/null
+++ b/demo/python/.gitignore
@@ -0,0 +1,7 @@
+venv
+dist
+MANIFEST.in
+pvrecorderdemo
+pvrecorderdemo.egg-info
+build
+__pycache__
diff --git a/demo/python/README.md b/demo/python/README.md
new file mode 100644
index 00000000..b0f4f960
--- /dev/null
+++ b/demo/python/README.md
@@ -0,0 +1,30 @@
+# PvRecorder Demo for Python
+
+## Compatibility
+
+- Python 3.5+
+- Runs on Linux (x86_64), macOS (x86_64 and arm64), Windows (x86_64), Raspberry Pi (all variants), NVIDIA Jetson (Nano), and BeagleBone.
+
+## Installation
+
+```console
+pip3 install pvrecorderdemo
+```
+
+## Usage
+
+In the following instructions, we will refer to  `{AUDIO_DEVICE_INDEX}` as the index of the audio device to use, and `{OUTPUT_WAV_PATH}` as the path to save the audio data in `wav` format.
+
+`{AUDIO_DEVICE_INDEX}` defaults to -1 and `{OUTPUT_WAV_PATH}` can be empty if you wish to not save any data.
+
+To show the available audio devices run:
+
+```console
+pv_recorder_demo --show_audio_devices
+```
+
+To run PvRecorder run:
+
+```console
+pv_recorder_demo --audio_device_index {AUDIO_DEVICE_INDEX} --output_wav_path {OUTPUT_WAV_PATH}
+```
diff --git a/sdk/python/demo.py b/demo/python/pv_recorder_demo.py
similarity index 77%
rename from sdk/python/demo.py
rename to demo/python/pv_recorder_demo.py
index 4984252a..3e6646ee 100644
--- a/sdk/python/demo.py
+++ b/demo/python/pv_recorder_demo.py
@@ -1,5 +1,5 @@
 #
-# Copyright 2021-2022 Picovoice Inc.
+# Copyright 2021-2023 Picovoice Inc.
 #
 # You may not use this file except in compliance with the license. A copy of the license is located in the "LICENSE"
 # file accompanying this source.
@@ -32,22 +32,22 @@ def main():
         default=-1)
 
     parser.add_argument(
-        "--output_path",
+        "--output_wav_path",
         help="Path to file to store raw audio.",
         default=None)
 
     args = parser.parse_args()
 
     if args.show_audio_devices:
-        devices = PvRecorder.get_audio_devices()
+        devices = PvRecorder.get_available_devices()
         for i in range(len(devices)):
             print("index: %d, device name: %s" % (i, devices[i]))
     else:
         device_index = args.audio_device_index
-        output_path = args.output_path
+        output_path = args.output_wav_path
 
-        recorder = PvRecorder(device_index=device_index, frame_length=512)
-        print("pvrecorder.py version: %s" % recorder.version)
+        recorder = PvRecorder(frame_length=512, device_index=device_index)
+        print("pvrecorder version: %s" % recorder.version)
 
         recorder.start()
         print("Using device: %s" % recorder.selected_device)
@@ -58,12 +58,12 @@ def main():
             if output_path is not None:
                 wavfile = wave.open(output_path, "w")
                 # noinspection PyTypeChecker
-                wavfile.setparams((1, 2, 16000, 512, "NONE", "NONE"))
+                wavfile.setparams((1, 2, recorder.sample_rate, recorder.frame_length, "NONE", "NONE"))
 
             while True:
-                pcm = recorder.read()
+                frame = recorder.read()
                 if wavfile is not None:
-                    wavfile.writeframes(struct.pack("h" * len(pcm), *pcm))
+                    wavfile.writeframes(struct.pack("h" * len(frame), *frame))
 
         except KeyboardInterrupt:
             print("Stopping...")
diff --git a/demo/python/requirements.txt b/demo/python/requirements.txt
new file mode 100644
index 00000000..d87aec9d
--- /dev/null
+++ b/demo/python/requirements.txt
@@ -0,0 +1,1 @@
+pvrecorder==1.2.0
diff --git a/demo/python/setup.py b/demo/python/setup.py
new file mode 100644
index 00000000..09195ed2
--- /dev/null
+++ b/demo/python/setup.py
@@ -0,0 +1,51 @@
+import os
+import shutil
+
+import setuptools
+
+os.system('git clean -dfx')
+
+package_folder = os.path.join(os.path.dirname(__file__), 'pvrecorderdemo')
+os.mkdir(package_folder)
+
+shutil.copy(os.path.join(os.path.dirname(__file__), '../../LICENSE'), package_folder)
+
+shutil.copy(
+    os.path.join(os.path.dirname(__file__), 'pv_recorder_demo.py'),
+    os.path.join(package_folder, 'pv_recorder_demo.py'))
+
+with open(os.path.join(os.path.dirname(__file__), 'MANIFEST.in'), 'w') as f:
+    f.write('include pvrecorderdemo/LICENSE\n')
+    f.write('include pvrecorderdemo/pv_recorder_demo.py\n')
+
+with open(os.path.join(os.path.dirname(__file__), 'README.md'), 'r') as f:
+    long_description = f.read()
+
+setuptools.setup(
+    name="pvrecorderdemo",
+    version="1.2.0",
+    author="Picovoice",
+    author_email="hello@picovoice.ai",
+    description="Recorder library for Picovoice.",
+    long_description=long_description,
+    long_description_content_type="text/markdown",
+    url="https://github.com/Picovoice/pvrecorder",
+    packages=["pvrecorderdemo"],
+    install_requires=["pvrecorder==1.2.0"],
+    include_package_data=True,
+    classifiers=[
+        "Development Status :: 5 - Production/Stable",
+        "Intended Audience :: Developers",
+        "License :: OSI Approved :: Apache Software License",
+        "Operating System :: OS Independent",
+        "Programming Language :: Python :: 3",
+        "Topic :: Multimedia :: Sound/Audio :: Speech"
+    ],
+    entry_points=dict(
+        console_scripts=[
+            'pv_recorder_demo=pvrecorderdemo.pv_recorder_demo:main',
+        ],
+    ),
+    python_requires='>=3.5',
+    keywords="Audio Recorder",
+)
diff --git a/demo/rust/.gitignore b/demo/rust/.gitignore
new file mode 100644
index 00000000..cb325102
--- /dev/null
+++ b/demo/rust/.gitignore
@@ -0,0 +1,5 @@
+Cargo.lock
+target/
+data/resources/*
+data/lib/*
+*.wav
diff --git a/demo/rust/Cargo.toml b/demo/rust/Cargo.toml
new file mode 100644
index 00000000..cb450554
--- /dev/null
+++ b/demo/rust/Cargo.toml
@@ -0,0 +1,10 @@
+[package]
+name = "pv_recorder_demo"
+version = "0.1.0"
+edition = "2018"
+
+[dependencies]
+clap = "4.3"
+ctrlc = "3.4"
+hound = "3.5"
+pv_recorder = "1.2"
diff --git a/demo/rust/README.md b/demo/rust/README.md
new file mode 100644
index 00000000..3925b77f
--- /dev/null
+++ b/demo/rust/README.md
@@ -0,0 +1,51 @@
+# PvRecorder Demo for Rust
+
+This project contains a Rust command-line demo for PvRecorder that demonstrates how to use PvRecorder to record audio to a WAV file.
+
+## PvRecorder
+
+PvRecorder is a user-friendly, cross-platform audio recorder that has been specifically designed for real-time audio processing.
+It provides developers with access to the input stream of an audio device, which is divided into data frames of a given size.
+
+## Requirements
+
+- Rust 1.54+
+
+## Compatibility
+
+- Linux (x86_64)
+- macOS (x86_64 and arm64)
+- Windows (x86_64)
+- Raspberry Pi:
+    - Zero
+    - 2
+    - 3 (32 and 64 bit)
+    - 4 (32 and 64 bit)
+- NVIDIA Jetson Nano
+- BeagleBone
+
+## Usage
+
+To build and run the demo:
+
+```console
+cargo run --release --
+```
+
+To show the available audio devices run:
+
+```console
+cargo run --release -- --show_audio_devices
+```
+
+To run the demo, give it a file to record audio to:
+
+```console
+cargo run --release -- --output_wav_path ${OUTPUT_WAV_FILE}
+```
+
+You can also select the audio device index to use for recording (use `--show_audio_devices` to see options):
+
+```console
+cargo run --release -- --output_wav_path ${OUTPUT_WAV_FILE} --audio_device_index ${DEVICE_INDEX}
+```
diff --git a/sdk/rust/examples/demo.rs b/demo/rust/src/main.rs
similarity index 67%
rename from sdk/rust/examples/demo.rs
rename to demo/rust/src/main.rs
index e68b4dd6..1a1526a1 100644
--- a/sdk/rust/examples/demo.rs
+++ b/demo/rust/src/main.rs
@@ -1,5 +1,5 @@
 /*
-    Copyright 2021-2022 Picovoice Inc.
+    Copyright 2021-2023 Picovoice Inc.
 
     You may not use this file except in compliance with the license. A copy of the license is located in the "LICENSE"
     file accompanying this source.
@@ -8,10 +8,8 @@
     an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
     specific language governing permissions and limitations under the License.
 */
-use clap::{App, Arg};
-use ctrlc;
-use hound;
-use pv_recorder::RecorderBuilder;
+use clap::{value_parser, Arg, ArgAction, Command};
+use pv_recorder::PvRecorderBuilder;
 use std::sync::atomic::{AtomicBool, Ordering};
 
 const SAMPLE_RATE: usize = 16000;
@@ -21,7 +19,7 @@ static LISTENING: AtomicBool = AtomicBool::new(false);
 fn show_audio_devices() {
     println!("Printing audio devices...");
 
-    let audio_devices = RecorderBuilder::default().get_audio_devices();
+    let audio_devices = PvRecorderBuilder::default().get_available_devices();
     match audio_devices {
         Ok(audio_devices) => {
             for (idx, device) in audio_devices.iter().enumerate() {
@@ -30,44 +28,43 @@ fn show_audio_devices() {
         }
         Err(err) => panic!("Failed to get audio devices: {}", err),
     };
-    println!("");
+    println!();
 }
 
 fn main() {
-    let matches = App::new("PvRecorder Demo")
+    let matches = Command::new("PvRecorder Demo")
         .arg(
-            Arg::with_name("audio_device_index")
+            Arg::new("audio_device_index")
                 .long("audio_device_index")
                 .value_name("INDEX")
                 .help("Index of input audio device.")
-                .takes_value(true)
+                .value_parser(value_parser!(i32))
                 .default_value("-1"),
         )
         .arg(
-            Arg::with_name("output_path")
-                .long("output_path")
+            Arg::new("output_wav_path")
+                .long("output_wav_path")
                 .value_name("PATH")
                 .help("Path to write recorded audio wav file to.")
-                .takes_value(true)
                 .default_value("example.wav"),
         )
-        .arg(Arg::with_name("show_audio_devices").long("show_audio_devices"))
+        .arg(
+            Arg::new("show_audio_devices")
+                .long("show_audio_devices")
+                .action(ArgAction::SetTrue),
+        )
         .get_matches();
 
-    if matches.is_present("show_audio_devices") {
+    if matches.get_flag("show_audio_devices") {
         return show_audio_devices();
     }
 
-    let audio_device_index = matches
-        .value_of("audio_device_index")
-        .unwrap()
-        .parse()
-        .unwrap();
+    let audio_device_index = *matches.get_one::<i32>("audio_device_index").unwrap();
 
-    let output_path = matches.value_of("output_path").unwrap();
+    let output_wav_path = matches.get_one::<String>("output_wav_path").unwrap();
 
     println!("Initializing pvrecorder...");
-    let recorder = RecorderBuilder::new()
+    let recorder = PvRecorderBuilder::new(512)
         .device_index(audio_device_index)
         .init()
         .expect("Failed to initialize pvrecorder");
@@ -83,11 +80,8 @@ fn main() {
 
     let mut audio_data = Vec::new();
     while LISTENING.load(Ordering::SeqCst) {
-        let mut frame_buffer = vec![0; recorder.frame_length()];
-        recorder
-            .read(&mut frame_buffer)
-            .expect("Failed to read audio frame");
-        audio_data.extend_from_slice(&frame_buffer);
+        let frame = recorder.read().expect("Failed to read audio frame");
+        audio_data.extend_from_slice(&frame);
     }
 
     println!("Stop recording...");
@@ -100,7 +94,7 @@ fn main() {
         bits_per_sample: 16,
         sample_format: hound::SampleFormat::Int,
     };
-    let mut writer = hound::WavWriter::create(output_path, spec).unwrap();
+    let mut writer = hound::WavWriter::create(output_wav_path, spec).unwrap();
     for sample in audio_data {
         writer.write_sample(sample).unwrap();
     }
diff --git a/example/demo.c b/example/demo.c
deleted file mode 100644
index ed8eea39..00000000
--- a/example/demo.c
+++ /dev/null
@@ -1,134 +0,0 @@
-/*
-    Copyright 2021-2022 Picovoice Inc.
-
-    You may not use this file except in compliance with the license. A copy of the license is located in the "LICENSE"
-    file accompanying this source.
-
-    Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
-    an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
-    specific language governing permissions and limitations under the License.
-*/
-
-#include <signal.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <string.h>
-
-#include "pv_recorder.h"
-
-static volatile bool is_interrupted = false;
-
-void interrupt_handler(int _) {
-    (void) _;
-    is_interrupted = true;
-}
-
-static void print_usage(const char *program) {
-    fprintf(stderr, "usage: %s --show_audio_devices\n"
-                    "       %s audio_device_index path_to_raw_file\n", program, program);
-}
-
-int main(int argc, char *argv[]) {
-    fprintf(stdout, "pv_recorder version: %s\n", pv_recorder_version());
-    if ((argc != 2) && (argc != 3)) {
-        print_usage(argv[0]);
-        exit(1);
-    }
-
-    signal(SIGINT, interrupt_handler);
-
-    if (strcmp(argv[1], "--show_audio_devices") == 0) {
-        char **devices = NULL;
-        int32_t count = 0;
-
-        // List devices
-        pv_recorder_status_t status = pv_recorder_get_audio_devices(&count, &devices);
-        if (status != PV_RECORDER_STATUS_SUCCESS) {
-            fprintf(stderr, "Failed to get audio devices with: %s.\n", pv_recorder_status_to_string(status));
-            exit(1);
-        }
-
-        fprintf(stdout, "Printing devices...\n");
-        for (int32_t i = 0; i < count; i++) {
-            fprintf(stdout, "index: %d, name: %s\n", i, devices[i]);
-        }
-
-        pv_recorder_free_device_list(count, devices);
-        return 0;
-    }
-
-    const int32_t device_index = (int32_t) strtol(argv[1], NULL, 10);
-    const char *path_to_raw_file = NULL;
-
-    if (argc == 3) {
-        path_to_raw_file = argv[2];
-    }
-
-    // Use PV_Recorder
-    fprintf(stdout, "Initializing pv_recorder...\n");
-
-    const int32_t frame_length = 512;
-    pv_recorder_t *recorder = NULL;
-    pv_recorder_status_t status = pv_recorder_init(device_index, frame_length, 100, true, true, &recorder);
-    if (status != PV_RECORDER_STATUS_SUCCESS) {
-        fprintf(stderr, "Failed to initialize device with %s.\n", pv_recorder_status_to_string(status));
-        exit(1);
-    }
-
-    const char *selected_device = pv_recorder_get_selected_device(recorder);
-    fprintf(stdout, "Selected device: %s.\n", selected_device);
-
-    fprintf(stdout, "Start recording...\n");
-    status = pv_recorder_start(recorder);
-    if (status != PV_RECORDER_STATUS_SUCCESS) {
-        fprintf(stderr, "Failed to start device with %s.\n", pv_recorder_status_to_string(status));
-        exit(1);
-    }
-
-    int16_t *pcm = malloc(frame_length * sizeof(int16_t));
-    if (!pcm) {
-        fprintf(stderr, "Failed to allocate pcm memory.\n");
-        exit(1);
-    }
-
-    FILE *file = NULL;
-    if (path_to_raw_file) {
-        file = fopen(path_to_raw_file, "wb");
-        if (!file) {
-            fprintf(stderr, "Failed to open file.\n");
-            exit(1);
-        }
-    }
-
-    while (!is_interrupted) {
-        status = pv_recorder_read(recorder, pcm);
-        if (status != PV_RECORDER_STATUS_SUCCESS) {
-            fprintf(stderr, "Failed to read with %s.\n", pv_recorder_status_to_string(status));
-            exit(1);
-        }
-        if (file) {
-            const size_t length = fwrite(pcm, sizeof(int16_t), frame_length, file);
-            if (length != frame_length) {
-                fprintf(stderr, "Failed to write raw bytes to file.\n");
-                exit(1);
-            }
-        }
-    }
-
-    if (file) {
-        fclose(file);
-    }
-
-    fprintf(stdout, "Stop recording...\n");
-    status = pv_recorder_stop(recorder);
-    if (status != PV_RECORDER_STATUS_SUCCESS) {
-        fprintf(stderr, "Failed to stop device with %s.\n", pv_recorder_status_to_string(status));
-        exit(1);
-    }
-
-    fprintf(stdout, "Deleting pv_recorder...\n");
-    pv_recorder_delete(recorder);
-    free(pcm);
-
-    return 0;
-}
\ No newline at end of file
diff --git a/lib/beaglebone/libpv_recorder.so b/lib/beaglebone/libpv_recorder.so
index d5b3370f..6461a713 100644
Binary files a/lib/beaglebone/libpv_recorder.so and b/lib/beaglebone/libpv_recorder.so differ
diff --git a/lib/jetson/cortex-a57-aarch64/libpv_recorder.so b/lib/jetson/cortex-a57-aarch64/libpv_recorder.so
index e1c26bb4..f15b5cf2 100644
Binary files a/lib/jetson/cortex-a57-aarch64/libpv_recorder.so and b/lib/jetson/cortex-a57-aarch64/libpv_recorder.so differ
diff --git a/lib/linux/x86_64/libpv_recorder.so b/lib/linux/x86_64/libpv_recorder.so
index 1df357b0..2e1345ea 100644
Binary files a/lib/linux/x86_64/libpv_recorder.so and b/lib/linux/x86_64/libpv_recorder.so differ
diff --git a/lib/mac/arm64/libpv_recorder.dylib b/lib/mac/arm64/libpv_recorder.dylib
index b068fb7e..287b497b 100644
Binary files a/lib/mac/arm64/libpv_recorder.dylib and b/lib/mac/arm64/libpv_recorder.dylib differ
diff --git a/lib/mac/x86_64/libpv_recorder.dylib b/lib/mac/x86_64/libpv_recorder.dylib
index fba98b75..21722af6 100644
Binary files a/lib/mac/x86_64/libpv_recorder.dylib and b/lib/mac/x86_64/libpv_recorder.dylib differ
diff --git a/lib/node/beaglebone/pv_recorder.node b/lib/node/beaglebone/pv_recorder.node
index 9bdea4de..e5c129ef 100644
Binary files a/lib/node/beaglebone/pv_recorder.node and b/lib/node/beaglebone/pv_recorder.node differ
diff --git a/lib/node/jetson/cortex-a57-aarch64/pv_recorder.node b/lib/node/jetson/cortex-a57-aarch64/pv_recorder.node
index f145133c..8c7171e5 100644
Binary files a/lib/node/jetson/cortex-a57-aarch64/pv_recorder.node and b/lib/node/jetson/cortex-a57-aarch64/pv_recorder.node differ
diff --git a/lib/node/linux/x86_64/pv_recorder.node b/lib/node/linux/x86_64/pv_recorder.node
old mode 100644
new mode 100755
index 737be2e6..5cff72fa
Binary files a/lib/node/linux/x86_64/pv_recorder.node and b/lib/node/linux/x86_64/pv_recorder.node differ
diff --git a/lib/node/mac/arm64/pv_recorder.node b/lib/node/mac/arm64/pv_recorder.node
index 4243195b..f1409d69 100644
Binary files a/lib/node/mac/arm64/pv_recorder.node and b/lib/node/mac/arm64/pv_recorder.node differ
diff --git a/lib/node/mac/x86_64/pv_recorder.node b/lib/node/mac/x86_64/pv_recorder.node
index 656ca7e6..9b49ff36 100644
Binary files a/lib/node/mac/x86_64/pv_recorder.node and b/lib/node/mac/x86_64/pv_recorder.node differ
diff --git a/lib/node/raspberry-pi/cortex-a53-aarch64/pv_recorder.node b/lib/node/raspberry-pi/cortex-a53-aarch64/pv_recorder.node
index 33a69191..98725461 100644
Binary files a/lib/node/raspberry-pi/cortex-a53-aarch64/pv_recorder.node and b/lib/node/raspberry-pi/cortex-a53-aarch64/pv_recorder.node differ
diff --git a/lib/node/raspberry-pi/cortex-a53/pv_recorder.node b/lib/node/raspberry-pi/cortex-a53/pv_recorder.node
index 85e97495..710a8ba1 100644
Binary files a/lib/node/raspberry-pi/cortex-a53/pv_recorder.node and b/lib/node/raspberry-pi/cortex-a53/pv_recorder.node differ
diff --git a/lib/node/raspberry-pi/cortex-a7/pv_recorder.node b/lib/node/raspberry-pi/cortex-a7/pv_recorder.node
index 7853fb04..80601a58 100644
Binary files a/lib/node/raspberry-pi/cortex-a7/pv_recorder.node and b/lib/node/raspberry-pi/cortex-a7/pv_recorder.node differ
diff --git a/lib/node/raspberry-pi/cortex-a72-aarch64/pv_recorder.node b/lib/node/raspberry-pi/cortex-a72-aarch64/pv_recorder.node
index 9fb4a0ff..95a8607d 100644
Binary files a/lib/node/raspberry-pi/cortex-a72-aarch64/pv_recorder.node and b/lib/node/raspberry-pi/cortex-a72-aarch64/pv_recorder.node differ
diff --git a/lib/node/raspberry-pi/cortex-a72/pv_recorder.node b/lib/node/raspberry-pi/cortex-a72/pv_recorder.node
index 32e8216d..eccbd05d 100644
Binary files a/lib/node/raspberry-pi/cortex-a72/pv_recorder.node and b/lib/node/raspberry-pi/cortex-a72/pv_recorder.node differ
diff --git a/lib/node/windows/amd64/pv_recorder.node b/lib/node/windows/amd64/pv_recorder.node
index c56d40ab..eaf450cc 100644
Binary files a/lib/node/windows/amd64/pv_recorder.node and b/lib/node/windows/amd64/pv_recorder.node differ
diff --git a/lib/raspberry-pi/arm11/libpv_recorder.so b/lib/raspberry-pi/arm11/libpv_recorder.so
index fa405bbf..67da4f02 100644
Binary files a/lib/raspberry-pi/arm11/libpv_recorder.so and b/lib/raspberry-pi/arm11/libpv_recorder.so differ
diff --git a/lib/raspberry-pi/cortex-a53-aarch64/libpv_recorder.so b/lib/raspberry-pi/cortex-a53-aarch64/libpv_recorder.so
index b55bccc1..7e04a378 100644
Binary files a/lib/raspberry-pi/cortex-a53-aarch64/libpv_recorder.so and b/lib/raspberry-pi/cortex-a53-aarch64/libpv_recorder.so differ
diff --git a/lib/raspberry-pi/cortex-a53/libpv_recorder.so b/lib/raspberry-pi/cortex-a53/libpv_recorder.so
index 86f7fa28..5ef24d62 100644
Binary files a/lib/raspberry-pi/cortex-a53/libpv_recorder.so and b/lib/raspberry-pi/cortex-a53/libpv_recorder.so differ
diff --git a/lib/raspberry-pi/cortex-a7/libpv_recorder.so b/lib/raspberry-pi/cortex-a7/libpv_recorder.so
index 0e1f73e2..9d3aabf3 100644
Binary files a/lib/raspberry-pi/cortex-a7/libpv_recorder.so and b/lib/raspberry-pi/cortex-a7/libpv_recorder.so differ
diff --git a/lib/raspberry-pi/cortex-a72-aarch64/libpv_recorder.so b/lib/raspberry-pi/cortex-a72-aarch64/libpv_recorder.so
index af7a31ef..4aa0e286 100644
Binary files a/lib/raspberry-pi/cortex-a72-aarch64/libpv_recorder.so and b/lib/raspberry-pi/cortex-a72-aarch64/libpv_recorder.so differ
diff --git a/lib/raspberry-pi/cortex-a72/libpv_recorder.so b/lib/raspberry-pi/cortex-a72/libpv_recorder.so
index 7a62d3bf..0cc68ce7 100644
Binary files a/lib/raspberry-pi/cortex-a72/libpv_recorder.so and b/lib/raspberry-pi/cortex-a72/libpv_recorder.so differ
diff --git a/lib/windows/amd64/libpv_recorder.dll b/lib/windows/amd64/libpv_recorder.dll
index 32a697ff..99559076 100644
Binary files a/lib/windows/amd64/libpv_recorder.dll and b/lib/windows/amd64/libpv_recorder.dll differ
diff --git a/project/CMakeLists.txt b/project/CMakeLists.txt
new file mode 100644
index 00000000..2a110aa9
--- /dev/null
+++ b/project/CMakeLists.txt
@@ -0,0 +1,116 @@
+cmake_minimum_required(VERSION 3.4)
+project(pv_recorder VERSION 1.2.0 DESCRIPTION "Picovoice audio recorder library.")
+
+set(CMAKE_C_STANDARD 99)
+set(CMAKE_BUILD_TYPE Release)
+set(CMAKE_POSITION_INDEPENDENT_CODE ON)
+set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fvisibility=hidden")
+
+option(PV_BUILD_TESTS "Build and run library tests" ON)
+option(PV_BUILD_NODE "Build Node.js libraries" ON)
+
+if(NOT PV_RECORDER_PLATFORM)
+    message(FATAL_ERROR "No `PV_RECORDER_PLATFORM` value was given. Valid platforms are: \n"
+            "linux, mac-arm64, mac-x86_64, windows, raspberry-pi, raspberry-pi2, "
+            "raspberry-pi3-32, raspberry-pi3-64, raspberry-pi4-32, raspberry-pi4-64, "
+            "jetson-nano, beaglebone")
+endif()
+
+if (${PV_RECORDER_PLATFORM} STREQUAL "beaglebone")
+    set(PV_LINK_ATOMIC ON)
+    add_definitions(-D__PV_RECORDER_PLATFORM_BEAGLEBONE__)
+    add_compile_options(-mcpu=cortex-a8 -mtune=cortex-a8 -mfpu=neon)
+    add_link_options(-mcpu=cortex-a8 -mtune=cortex-a8 -mfpu=neon)
+elseif (${PV_RECORDER_PLATFORM} STREQUAL "mac-arm64")
+    add_definitions(-D__PV_RECORDER_PLATFORM_DARWIN__)
+    set(CMAKE_OSX_ARCHITECTURES "arm64")
+elseif (${PV_RECORDER_PLATFORM} STREQUAL "mac-x86_64")
+    add_definitions(-D__PV_RECORDER_PLATFORM_DARWIN__)
+    set(CMAKE_OSX_ARCHITECTURES "x86_64")
+elseif (${PV_RECORDER_PLATFORM} STREQUAL "linux")
+    add_definitions(-D__PV_RECORDER_PLATFORM_LINUX__)
+elseif (${PV_RECORDER_PLATFORM} STREQUAL "jetson-nano")
+    set(PV_LINK_ATOMIC ON)
+    add_definitions(-D__PV_RECORDER_PLATFORM_JETSON__)
+    add_compile_options(-mcpu=cortex-a57 -mtune=cortex-a57)
+    add_link_options(-mcpu=cortex-a57 -mtune=cortex-a57)
+elseif (${PV_RECORDER_PLATFORM} STREQUAL "raspberry-pi")
+    set(PV_LINK_ATOMIC ON)
+    add_definitions(-D__PV_RECORDER_PLATFORM_RASPBERRYPI__)
+    add_compile_options(-mcpu=arm1176jzf-s -mtune=arm1176jzf-s -mfloat-abi=hard -mfpu=vfp)
+    add_link_options(-mcpu=arm1176jzf-s -mtune=arm1176jzf-s -mfloat-abi=hard -mfpu=vfp)
+elseif (${PV_RECORDER_PLATFORM} STREQUAL "raspberry-pi2")
+    set(PV_LINK_ATOMIC ON)
+    add_definitions(-D__PV_RECORDER_PLATFORM_RASPBERRYPI__)
+    add_compile_options(-mcpu=cortex-a7 -mtune=cortex-a7 -mfloat-abi=hard -mfpu=neon-vfpv4)
+    add_link_options(-mcpu=cortex-a7 -mtune=cortex-a7 -mfloat-abi=hard -mfpu=neon-vfpv4)
+elseif (${PV_RECORDER_PLATFORM} STREQUAL "raspberry-pi3")
+    set(PV_LINK_ATOMIC ON)
+    add_definitions(-D__PV_RECORDER_PLATFORM_RASPBERRYPI__)
+    add_compile_options(-mcpu=cortex-a53 -mtune=cortex-a53 -mfloat-abi=hard -mfpu=neon-fp-armv8)
+    add_link_options(-mcpu=cortex-a53 -mtune=cortex-a53 -mfloat-abi=hard -mfpu=neon-fp-armv8)
+elseif (${PV_RECORDER_PLATFORM} STREQUAL "raspberry-pi3-64")
+    set(PV_LINK_ATOMIC ON)
+    add_definitions(-D__PV_RECORDER_PLATFORM_RASPBERRYPI__)
+    add_compile_options(-mcpu=cortex-a53 -mtune=cortex-a53)
+    add_link_options(-mcpu=cortex-a53 -mtune=cortex-a53)
+elseif (${PV_RECORDER_PLATFORM} STREQUAL "raspberry-pi4")
+    set(PV_LINK_ATOMIC ON)
+    add_definitions(-D__PV_RECORDER_PLATFORM_RASPBERRYPI__)
+    add_compile_options(-mcpu=cortex-a72 -mtune=cortex-a72 -mfloat-abi=hard -mfpu=neon-fp-armv8)
+    add_link_options(-mcpu=cortex-a72 -mtune=cortex-a72 -mfloat-abi=hard -mfpu=neon-fp-armv8)
+elseif (${PV_RECORDER_PLATFORM} STREQUAL "raspberry-pi4-64")
+    set(PV_LINK_ATOMIC ON)
+    add_definitions(-D__PV_RECORDER_PLATFORM_RASPBERRYPI__)
+    add_compile_options(-mcpu=cortex-a72 -mtune=cortex-a72)
+    add_link_options(-mcpu=cortex-a72 -mtune=cortex-a72)
+elseif (${PV_RECORDER_PLATFORM} STREQUAL "windows")
+    add_definitions(-D__PV_RECORDER_PLATFORM_WINDOWS__)
+else ()
+    message(FATAL_ERROR "Unknown platform `${PV_RECORDER_PLATFORM}`.")
+endif ()
+
+add_library(pv_recorder_object OBJECT src/pv_circular_buffer.c src/pv_recorder.c)
+target_include_directories(pv_recorder_object PUBLIC include)
+target_include_directories(pv_recorder_object PRIVATE src/miniaudio)
+
+add_library(pv_recorder SHARED $<TARGET_OBJECTS:pv_recorder_object>)
+set_target_properties(pv_recorder PROPERTIES PUBLIC_HEADER include/pv_recorder.h)
+target_include_directories(pv_recorder PUBLIC include)
+
+if (NOT ${PV_RECORDER_PLATFORM} STREQUAL "windows")
+    target_link_libraries(pv_recorder PRIVATE pthread dl m)
+    if(PV_LINK_ATOMIC)
+        target_link_libraries(pv_recorder PRIVATE atomic)
+    endif()
+endif()
+
+if(DEFINED OUTPUT_DIR)
+    add_custom_command(TARGET pv_recorder POST_BUILD
+            COMMAND ${CMAKE_COMMAND} -E copy
+            $<TARGET_FILE:${PROJECT_NAME}>
+            "${CMAKE_SOURCE_DIR}/../lib/${OUTPUT_DIR}/$<TARGET_FILE_NAME:${PROJECT_NAME}>"
+            COMMENT "Copying to output directory.")
+endif()
+
+if (PV_BUILD_TESTS)
+    enable_testing()
+
+    add_executable(test_circular_buffer test/test_pv_circular_buffer.c src/pv_circular_buffer.c)
+    target_include_directories(test_circular_buffer PUBLIC include)
+    add_test(
+            NAME test_circular_buffer
+            COMMAND test_circular_buffer
+    )
+
+    add_executable(test_recorder test/test_pv_recorder.c)
+    target_link_libraries(test_recorder pv_recorder)
+    add_test(
+            NAME test_recorder
+            COMMAND test_recorder
+    )
+endif()
+
+if (PV_BUILD_NODE)
+    add_subdirectory(node)
+endif()
\ No newline at end of file
diff --git a/project/Preload.CMake b/project/Preload.CMake
new file mode 100644
index 00000000..d4b14183
--- /dev/null
+++ b/project/Preload.CMake
@@ -0,0 +1,5 @@
+if (WIN32)
+    set(CMAKE_GENERATOR "MinGW Makefiles" CACHE STRING "" FORCE)
+else()
+    set(CMAKE_GENERATOR "Unix Makefiles" CACHE STRING "" FORCE)
+endif()
\ No newline at end of file
diff --git a/project/README.md b/project/README.md
new file mode 100644
index 00000000..df09f8c1
--- /dev/null
+++ b/project/README.md
@@ -0,0 +1,117 @@
+# PvRecorder Source Project
+
+## Requirements
+
+- CMake 3.4+.
+- C99 compatible compiler.
+- **Windows**: MinGW.
+
+## Compatibility
+
+- Linux (x86_64)
+- macOS (x86_64, arm64)
+- Windows (amd64)
+- Raspberry Pi (Zero, 2, 3 and 4)
+- NVIDIA Jetson Nano
+- BeagleBone
+
+## Compiling
+
+Run the following commands to build and test (`{OUTPUT_DIR}` can be empty if you wish not to copy):
+
+```console
+git submodule update --init --recursive
+cmake -S . -B build -DOUTPUT_DIR={OUTPUT_DIR} -DPV_RECORDER_PLATFORM={PV_RECORDER_PLATFORM}
+cmake --build build
+```
+
+The variable `{OUTPUT_DIR}` will be used to select the directory to copy the shared object
+after a successful compilation. `{OUTPUT_DIR}` should be a directory **relative** to the [lib](../lib) directory.
+
+The `{PV_RECORDER_PLATFORM}` variable will set the compilation flags for the given platform. Exclude this variable
+to get a list of possible values.
+
+## Usage
+
+1. Create a PvRecorder object:
+```c
+#include "pv_recorder.h"
+
+const int32_t frame_length = 512;
+const int32_t device_index = -1; // -1 == default device
+const int32_t buffered_frame_count = 10;
+
+pv_recorder_t *recorder = NULL;
+pv_recorder_status_t status = pv_recorder_init(
+        frame_length,
+        device_index,
+        buffered_frame_count,
+        &recorder);
+if (status != PV_RECORDER_STATUS_SUCCESS) {
+    // handle PvRecorder init error
+}
+```
+
+2. Start recording audio:
+
+```c
+pv_recorder_status_t status = pv_recorder_start(recorder);
+if (status != PV_RECORDER_STATUS_SUCCESS) {
+    // handle PvRecorder start error
+}
+```
+
+3. Read frames of audio from the recorder:
+```c
+// must have length equal to `frame_length` that was given to `pv_recorder_init()`
+int16_t *frame = malloc(frame_length * sizeof(int16_t));
+
+while (true) {
+    pv_recorder_status_t status = pv_recorder_read(recorder, frame);
+    if (status != PV_RECORDER_STATUS_SUCCESS) {
+        // handle PvRecorder read error
+    }
+
+    // use frame of audio data
+    // ...
+}
+```
+
+4. Stop recording:
+
+```c
+pv_recorder_status_t status = pv_recorder_stop(recorder);
+if (status != PV_RECORDER_STATUS_SUCCESS) {
+    // handle PvRecorder stop error
+}
+```
+
+5. Release resources used by PvRecorder:
+```c
+pv_recorder_delete(recorder);
+free(frame);
+```
+
+### Selecting an Audio Device
+
+To print a list of available audio devices:
+```c
+char **device_list = NULL;
+int32_t device_list_length = 0;
+
+pv_recorder_status_t status = pv_recorder_get_available_devices(&device_list_length, &device_list);
+if (status != PV_RECORDER_STATUS_SUCCESS) {
+    // handle PvRecorder get audio devices error
+}
+
+fprintf(stdout, "Printing devices...\n");
+for (int32_t i = 0; i < device_list_length; i++) {
+    fprintf(stdout, "index: %d, name: %s\n", i, device_list[i]);
+}
+
+pv_recorder_free_available_devices(device_list_length, device_list);
+```
+
+The index of the device in the returned list can be used in `pv_recorder_init()` to select that device for recording.
+
+Refer to [pv_recorder_demo.c](../demo/c/pv_recorder_demo.c) for a full example of how to use `pv_recorder` to capture audio in C.
diff --git a/include/pv_circular_buffer.h b/project/include/pv_circular_buffer.h
similarity index 74%
rename from include/pv_circular_buffer.h
rename to project/include/pv_circular_buffer.h
index 19802881..32e7b5dc 100644
--- a/include/pv_circular_buffer.h
+++ b/project/include/pv_circular_buffer.h
@@ -1,5 +1,5 @@
 /*
-    Copyright 2021 Picovoice Inc.
+    Copyright 2021-2023 Picovoice Inc.
 
     You may not use this file except in compliance with the license. A copy of the license is located in the "LICENSE"
     file accompanying this source.
@@ -16,7 +16,7 @@
 #include <stdint.h>
 
 /**
- * Forward declaration of PV_circular_buffer object. It handles reading and writing to a buffer.
+ * Forward declaration of pv_circular_buffer object. It handles reading and writing to a buffer.
  */
 typedef struct pv_circular_buffer pv_circular_buffer_t;
 
@@ -31,21 +31,21 @@ typedef enum {
 } pv_circular_buffer_status_t;
 
 /**
- * Constructor for PV_circular_buffer object.
+ * Constructor for pv_circular_buffer object.
  *
- * @param capacity Capacity of the buffer to read and write.
+ * @param element_count Capacity of the buffer to read and write.
  * @param element_size Size of each element in the buffer.
  * @param object[out] Circular buffer object.
  * @return Status Code. Returns PV_CIRCULAR_BUFFER_STATUS_OUT_OF_MEMORY or PV_CIRCULAR_BUFFER_STATUS_INVALID_ARGUMENT
  * on failure.
  */
 pv_circular_buffer_status_t pv_circular_buffer_init(
-        int32_t capacity,
+        int32_t element_count,
         int32_t element_size,
         pv_circular_buffer_t **object);
 
 /**
- * Destructor for PV_circular_buffer object.
+ * Destructor for pv_circular_buffer object.
  *
  * @param object Circular buffer object.
  */
@@ -56,22 +56,28 @@ void pv_circular_buffer_delete(pv_circular_buffer_t *object);
  *
  * @param object Circular buffer object.
  * @param buffer[out] A pointer to copy the elements into.
- * @param length The amount to copy to read from the buffer.
+ * @param buffer_length The amount to copy to read from the buffer.
  * is not PV_CIRCULAR_BUFFER_STATUS_SUCCESS.
  * @return Returns the total length of frames copied to buffer.
  */
-int32_t pv_circular_buffer_read(pv_circular_buffer_t *object, void *buffer, int32_t length);
+int32_t pv_circular_buffer_read(
+        pv_circular_buffer_t *object,
+        void *buffer,
+        int32_t buffer_length);
 
 /**
- * Writes and copies the elements of param ${buffer} to the object's buffer. Overwrites existing frames if the buffer
+ * Writes and copies the elements of `buffer` to the object's buffer. Overwrites existing frames if the buffer
  * is full and returns PV_CIRCULAR_BUFFER_STATUS_WRITE_OVERFLOW which is not a failure.
  *
  * @param object Circular buffer object.
  * @param buffer A pointer to copy its elements to the object's buffer.
- * @param length The amount of elements to copy.
+ * @param buffer_length The amount of elements to copy.
  * @return Status Code. Returns PV_CIRCULAR_BUFFER_STATUS_INVALID_ARGUMENT on failure.
  */
-pv_circular_buffer_status_t pv_circular_buffer_write(pv_circular_buffer_t *object, const void *buffer, int32_t length);
+pv_circular_buffer_status_t pv_circular_buffer_write(
+        pv_circular_buffer_t *object,
+        const void *buffer,
+        int32_t buffer_length);
 
 /**
  * Reset the buffer pointers to start.
diff --git a/include/pv_recorder.h b/project/include/pv_recorder.h
similarity index 61%
rename from include/pv_recorder.h
rename to project/include/pv_recorder.h
index 5575891b..2a5e4822 100644
--- a/include/pv_recorder.h
+++ b/project/include/pv_recorder.h
@@ -15,7 +15,7 @@
 #include <stdbool.h>
 #include <stdint.h>
 
-#if defined(_WIN32) || defined(_WIN64)
+#if __PV_PLATFORM_WINDOWS__
 
 #define PV_API __attribute__ ((dllexport))
 
@@ -25,8 +25,6 @@
 
 #endif
 
-#define PV_RECORDER_DEFAULT_DEVICE_INDEX (-1)
-
 /**
  * Struct representing the PvRecorder object.
  */
@@ -48,24 +46,22 @@ typedef enum {
 } pv_recorder_status_t;
 
 /**
- * Creates a PvRecorder instance.
- * When finished with the instance, resources should be cleaned up using the `pv_recorder_delete() function.
+ * Creates a PvRecorder instance. When finished with the instance, resources should be released
+ * using the `pv_recorder_delete() function.
  *
- * @param device_index The index of the audio device to use. A value of (-1) will resort to default device.
  * @param frame_length The length of audio frame to get for each read call.
- * @param buffer_size_msec Time in milliseconds to store audio frames to a temporary buffer.
- * @param log_overflow Boolean variable to enable overflow logs. This will enable warning logs when buffer overflow occurs.
- * @param log_silence Boolean variable to enable silence logs. This will log when continuous audio buffers are detected as silent.
+ * @param device_index The index of the audio device to use. A value of (-1) will resort to default device.
+ * @param buffered_frames_count The number of audio frames buffered internally for reading - i.e. internal circular buffer
+ * will be of size `frame_length` * `buffered_frames_count`. If this value is too low, buffer overflows could occur
+ * and audio frames could be dropped. A higher value will increase memory usage.
  * @param[out] object PvRecorder object to be initialized.
  * @return Status Code. PV_RECORDER_STATUS_INVALID_ARGUMENT, PV_RECORDER_STATUS_BACKEND_ERROR,
  * PV_RECORDER_STATUS_DEVICE_INITIALIZED or PV_RECORDER_STATUS_OUT_OF_MEMORY on failure.
  */
 PV_API pv_recorder_status_t pv_recorder_init(
-        int32_t device_index,
         int32_t frame_length,
-        int32_t buffer_size_msec,
-        bool log_overflow,
-        bool log_silence,
+        int32_t device_index,
+        int32_t buffered_frames_count,
         pv_recorder_t **object);
 
 /**
@@ -94,43 +90,66 @@ PV_API pv_recorder_status_t pv_recorder_start(pv_recorder_t *object);
 PV_API pv_recorder_status_t pv_recorder_stop(pv_recorder_t *object);
 
 /**
- * Synchronous call to read frames. Copies amount of frames to `pcm` array provided to input.
+ * Synchronous call to read frames. Copies amount of frames to `frame` array provided to input.
  * Array size must match the `frame_length` value that was given to `pv_recorder_init()`.
  *
  * @param object PvRecorder object.
- * @param pcm[out] An array for the frames to be copied to.
+ * @param frame[out] An array for the frame to be copied to.
  * @return Status Code. Returns PV_RECORDER_STATUS_INVALID_ARGUMENT, PV_RECORDER_INVALID_STATE or PV_RECORDER_IO_ERROR on failure.
  * Returns PV_RECORDER_STATUS_BUFFER_OVERFLOW if audio frames aren't being read fast enough. This means old audio frames
  * are being replaced by new audio frames.
  */
-PV_API pv_recorder_status_t pv_recorder_read(pv_recorder_t *object, int16_t *pcm);
+PV_API pv_recorder_status_t pv_recorder_read(pv_recorder_t *object, int16_t *frame);
+
+/**
+ * Enable or disable debug logging for PvRecorder. Debug logs will indicate when there are overflows in the internal
+ * frame buffer and when an audio source is generating frames of silence.
+ *
+ * @param object PvRecorder object.
+ * @param is_debug_logging_enabled Boolean indicating whether the debug logging is enabled or disabled.
+ */
+PV_API void pv_recorder_set_debug_logging(
+        pv_recorder_t *object,
+        bool is_debug_logging_enabled);
+
+/**
+ * Gets whether the given `pv_recorder_t` instance is currently recording audio or not.
+ *
+ * @param object PvRecorder object.
+ * @returns A boolean indicating whether PvRecorder is currently recording audio or not.
+ */
+PV_API bool pv_recorder_get_is_recording(pv_recorder_t *object);
 
 /**
  * Gets the audio device that the given `pv_recorder_t` instance is using.
  *
  * @param object PvRecorder object.
- * @return A string containing the name of the device.
+ * @return A string containing the name of the current recording device.
  */
 PV_API const char *pv_recorder_get_selected_device(pv_recorder_t *object);
 
 /**
- * Gets the input audio devices currently available.
- * Free the returned `devices` array using `pv_recorder_free_device_list()`.
+ * Gets the list of available audio devices that can be used for recording.
+ * Free the returned `device_list` array using `pv_recorder_free_device_list()`.
  *
- * @param[out] count The number of available audio devices.
- * @param[out] devices The output array containing the list of available audio devices.
+ * @param[out] device_list_length The number of available audio devices.
+ * @param[out] device_list The output array containing the list of available audio devices.
  * @return Status Code. Returns PV_RECORDER_STATUS_OUT_OF_MEMORY, PV_RECORDER_STATUS_BACKEND_ERROR or
  * PV_RECORDER_STATUS_INVALID_ARGUMENT on failure.
  */
-PV_API pv_recorder_status_t pv_recorder_get_audio_devices(int32_t *count, char ***devices);
+PV_API pv_recorder_status_t pv_recorder_get_available_devices(
+        int32_t *device_list_length,
+        char ***device_list);
 
 /**
- * Frees the device list initialized by `pv_recorder_get_audio_devices()`.
+ * Frees the device list initialized by `pv_recorder_get_available_devices()`.
  *
- * @param count The number of audio devices.
- * @param devices The array containing the list of audio devices.
+ * @param device_list_length The number of audio devices.
+ * @param device_list The array containing the list of audio devices.
  */
-PV_API void pv_recorder_free_device_list(int32_t count, char **devices);
+PV_API void pv_recorder_free_available_devices(
+        int32_t device_list_length,
+        char **device_list);
 
 /**
  * Provides string representations of the given status code.
@@ -141,7 +160,14 @@ PV_API void pv_recorder_free_device_list(int32_t count, char **devices);
 PV_API const char *pv_recorder_status_to_string(pv_recorder_status_t status);
 
 /**
- * Getter for the PvRecorder version.
+ * Gets the audio sample rate used by PvRecorder.
+ *
+ * @return Sample rate.
+ */
+PV_API int32_t pv_recorder_sample_rate(void);
+
+/**
+ * Gets the PvRecorder version.
  *
  * @return Version.
  */
diff --git a/node/CMakeLists.txt b/project/node/CMakeLists.txt
similarity index 55%
rename from node/CMakeLists.txt
rename to project/node/CMakeLists.txt
index 2c3dd654..4def122d 100644
--- a/node/CMakeLists.txt
+++ b/project/node/CMakeLists.txt
@@ -3,7 +3,7 @@ project(pv_recorder_napi)
 
 include(ExternalProject)
 
-set(NODE_VERSION 14.17.5)
+set(NODE_VERSION 18.16.1)
 
 ExternalProject_Add(node_headers
   URL https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-headers.tar.xz
@@ -14,7 +14,7 @@ ExternalProject_Add(node_headers
 ExternalProject_Get_Property(node_headers SOURCE_DIR)
 set(NODE_INCLUDE_DIR ${SOURCE_DIR}/include/node/)
 
-if (WIN32)
+if (${PV_RECORDER_PLATFORM} STREQUAL "windows")
   set(NODE_WIN_LIB_DIR ${CMAKE_CURRENT_BINARY_DIR}/node_win_lib )
   ExternalProject_Add(node_win_lib
     URL https://nodejs.org/dist/v${NODE_VERSION}/win-x64/node.exe 
@@ -28,33 +28,31 @@ if (WIN32)
   set(NODE_LIB ${NODE_WIN_LIB_DIR}/node.a)
 endif()
 
-add_library(${PROJECT_NAME} SHARED pv_recorder_napi.c $<TARGET_OBJECTS:pv_recorder_object>)
-
-set_target_properties(${PROJECT_NAME} PROPERTIES
+add_library(pv_recorder_napi SHARED pv_recorder_napi.c $<TARGET_OBJECTS:pv_recorder_object>)
+set_target_properties(pv_recorder_napi PROPERTIES
     PREFIX ""
     OUTPUT_NAME "pv_recorder"
     SUFFIX ".node"
 )
+add_dependencies(pv_recorder_napi node_headers)
 
-add_dependencies(${PROJECT_NAME} node_headers)
-
-if (WIN32)
-    add_dependencies(${PROJECT_NAME} node_win_lib)
+if (${PV_RECORDER_PLATFORM} STREQUAL "windows")
+    add_dependencies(pv_recorder_napi node_win_lib)
 endif()
 
-target_include_directories(${PROJECT_NAME} PUBLIC ${PROJECT_SOURCE_DIR}/../include)
-target_include_directories(${PROJECT_NAME} PRIVATE ${NODE_INCLUDE_DIR})
+target_include_directories(pv_recorder_napi PUBLIC ${PROJECT_SOURCE_DIR}/../include)
+target_include_directories(pv_recorder_napi PRIVATE ${NODE_INCLUDE_DIR})
 
-target_link_libraries(${PROJECT_NAME} ${NODE_LIB})
+target_link_libraries(pv_recorder_napi ${NODE_LIB})
 
-if (APPLE)
-    target_link_options(${PROJECT_NAME} PRIVATE "-undefined" "dynamic_lookup")
+if (${PV_RECORDER_PLATFORM} STREQUAL "mac-x86_64" OR ${PV_RECORDER_PLATFORM} STREQUAL "mac-arm64")
+    target_link_options(pv_recorder_napi PRIVATE "-undefined" "dynamic_lookup")
 endif()
 
 if(DEFINED OUTPUT_DIR)
-    add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD
+    add_custom_command(TARGET pv_recorder_napi POST_BUILD
             COMMAND ${CMAKE_COMMAND} -E copy
             $<TARGET_FILE:${PROJECT_NAME}>
-            "${CMAKE_SOURCE_DIR}/lib/node/${OUTPUT_DIR}/$<TARGET_FILE_NAME:${PROJECT_NAME}>"
+            "${CMAKE_SOURCE_DIR}/../lib/node/${OUTPUT_DIR}/$<TARGET_FILE_NAME:${PROJECT_NAME}>"
             COMMENT "Copying node lib to output directory.")
 endif()
diff --git a/node/pv_recorder_napi.c b/project/node/pv_recorder_napi.c
similarity index 74%
rename from node/pv_recorder_napi.c
rename to project/node/pv_recorder_napi.c
index 7d86988c..dcaf3237 100644
--- a/node/pv_recorder_napi.c
+++ b/project/node/pv_recorder_napi.c
@@ -1,11 +1,11 @@
 #include <assert.h>
-#include <node_api.h>
+#include "node_api.h"
 
 #include "pv_recorder.h"
 
 napi_value napi_pv_recorder_init(napi_env env, napi_callback_info info) {
-    size_t argc = 4;
-    napi_value args[4];
+    size_t argc = 3;
+    napi_value args[3];
     napi_status status = napi_get_cb_info(env, info, &argc, args, NULL, NULL);
     if (status != napi_ok) {
         napi_throw_error(
@@ -15,8 +15,8 @@ napi_value napi_pv_recorder_init(napi_env env, napi_callback_info info) {
         return NULL;
     }
 
-    int32_t device_index;
-    status = napi_get_value_int32(env, args[0], &device_index);
+    int32_t frame_length;
+    status = napi_get_value_int32(env, args[0], &frame_length);
     if (status != napi_ok) {
         napi_throw_error(
                 env,
@@ -25,8 +25,8 @@ napi_value napi_pv_recorder_init(napi_env env, napi_callback_info info) {
         return NULL;
     }
 
-    int32_t frame_length;
-    status = napi_get_value_int32(env, args[1], &frame_length);
+    int32_t device_index;
+    status = napi_get_value_int32(env, args[1], &device_index);
     if (status != napi_ok) {
         napi_throw_error(
                 env,
@@ -35,38 +35,22 @@ napi_value napi_pv_recorder_init(napi_env env, napi_callback_info info) {
         return NULL;
     }
 
-    int32_t buffer_size_msec;
-    status = napi_get_value_int32(env, args[2], &buffer_size_msec);
-    if (status != napi_ok) {
-        napi_throw_error(
-                env,
-                pv_recorder_status_to_string(PV_RECORDER_STATUS_INVALID_ARGUMENT),
-                "Unable to get the buffer size");
-        return NULL;
-    }
-
-    bool log_overflow;
-    status = napi_get_value_bool(env, args[3], &log_overflow);
+    int32_t buffered_frames_count;
+    status = napi_get_value_int32(env, args[2], &buffered_frames_count);
     if (status != napi_ok) {
         napi_throw_error(
                 env,
                 pv_recorder_status_to_string(PV_RECORDER_STATUS_INVALID_ARGUMENT),
-                "Unable to get the log overflow flag");
-        return NULL;
-    }
-
-    bool log_silence;
-    status = napi_get_value_bool(env, args[3], &log_silence);
-    if (status != napi_ok) {
-        napi_throw_error(
-                env,
-                pv_recorder_status_to_string(PV_RECORDER_STATUS_INVALID_ARGUMENT),
-                "Unable to get the log silence flag");
+                "Unable to get the buffered frames count");
         return NULL;
     }
 
     pv_recorder_t *handle = NULL;
-    pv_recorder_status_t pv_recorder_status = pv_recorder_init(device_index, frame_length, buffer_size_msec, log_overflow, log_silence, &handle);
+    pv_recorder_status_t pv_recorder_status = pv_recorder_init(
+            frame_length,
+            device_index,
+            buffered_frames_count,
+            &handle);
     if (pv_recorder_status != PV_RECORDER_STATUS_SUCCESS) {
         handle = NULL;
     }
@@ -249,10 +233,10 @@ napi_value napi_pv_recorder_read(napi_env env, napi_callback_info info) {
 
     napi_typedarray_type arr_type = -1;
     size_t length = 0;
-    void *data = NULL;
+    void *frame = NULL;
     napi_value arr_value = NULL;
     size_t offset = 0;
-    status = napi_get_typedarray_info(env, args[1], &arr_type, &length, &data, &arr_value, &offset);
+    status = napi_get_typedarray_info(env, args[1], &arr_type, &length, &frame, &arr_value, &offset);
     if (status != napi_ok) {
         napi_throw_error(
                 env,
@@ -264,7 +248,7 @@ napi_value napi_pv_recorder_read(napi_env env, napi_callback_info info) {
         napi_throw_error(
                 env,
                 pv_recorder_status_to_string(PV_RECORDER_STATUS_INVALID_ARGUMENT),
-                "Invalid type of input pcm buffer. The input frame has to be 'Int16Array'");
+                "Invalid type of input frame. The input frame has to be 'Int16Array'");
         return NULL;
     }
     if (length == 0) {
@@ -283,7 +267,9 @@ napi_value napi_pv_recorder_read(napi_env env, napi_callback_info info) {
     }
 
 
-    pv_recorder_status_t pv_recorder_status = pv_recorder_read((pv_recorder_t *)(uintptr_t) object_id, (int16_t *) data);
+    pv_recorder_status_t pv_recorder_status = pv_recorder_read(
+            (pv_recorder_t *)(uintptr_t) object_id,
+            (int16_t *) frame);
 
     napi_value result;
     status = napi_create_int32(env, pv_recorder_status, &result);
@@ -298,6 +284,82 @@ napi_value napi_pv_recorder_read(napi_env env, napi_callback_info info) {
     return result;
 }
 
+napi_value napi_pv_recorder_get_is_recording(napi_env env, napi_callback_info info) {
+    size_t argc = 1;
+    napi_value args[1];
+    napi_status status = napi_get_cb_info(env, info, &argc, args, NULL, NULL);
+    if (status != napi_ok) {
+        napi_throw_error(
+                env,
+                pv_recorder_status_to_string(PV_RECORDER_STATUS_RUNTIME_ERROR),
+                "Unable to get input arguments");
+        return NULL;
+    }
+
+    uint64_t object_id = 0;
+    bool lossless = false;
+    status = napi_get_value_bigint_uint64(env, args[0], &object_id, &lossless);
+    if ((status != napi_ok) || !lossless) {
+        napi_throw_error(
+                env,
+                pv_recorder_status_to_string(PV_RECORDER_STATUS_RUNTIME_ERROR),
+                "Unable to get the address of the instance of PvRecorder properly");
+        return NULL;
+    }
+
+    bool is_recording = pv_recorder_get_is_recording((pv_recorder_t *)(uintptr_t) object_id);
+
+    napi_value result;
+    status = napi_get_boolean(env, is_recording, &result);
+    if (status != napi_ok) {
+        napi_throw_error(
+                env,
+                pv_recorder_status_to_string(PV_RECORDER_STATUS_INVALID_ARGUMENT),
+                "Unable to get is recording flag.");
+        return NULL;
+    }
+
+    return result;
+}
+
+napi_value napi_pv_recorder_set_debug_logging(napi_env env, napi_callback_info info) {
+    size_t argc = 2;
+    napi_value args[2];
+    napi_status status = napi_get_cb_info(env, info, &argc, args, NULL, NULL);
+    if (status != napi_ok) {
+        napi_throw_error(
+                env,
+                pv_recorder_status_to_string(PV_RECORDER_STATUS_RUNTIME_ERROR),
+                "Unable to get input arguments");
+        return NULL;
+    }
+
+    uint64_t object_id = 0;
+    bool lossless = false;
+    status = napi_get_value_bigint_uint64(env, args[0], &object_id, &lossless);
+    if ((status != napi_ok) || !lossless) {
+        napi_throw_error(
+                env,
+                pv_recorder_status_to_string(PV_RECORDER_STATUS_RUNTIME_ERROR),
+                "Unable to get the address of the instance of PvRecorder properly");
+        return NULL;
+    }
+
+    bool is_debug_logging_enabled;
+    status = napi_get_value_bool(env, args[1], &is_debug_logging_enabled);
+    if (status != napi_ok) {
+        napi_throw_error(
+                env,
+                pv_recorder_status_to_string(PV_RECORDER_STATUS_INVALID_ARGUMENT),
+                "Unable to get debug logging flag");
+        return NULL;
+    }
+
+    pv_recorder_set_debug_logging((pv_recorder_t *)(uintptr_t) object_id, is_debug_logging_enabled);
+    return NULL;
+}
+
+
 napi_value napi_pv_recorder_get_selected_device(napi_env env, napi_callback_info info) {
     size_t argc = 1;
     napi_value args[1];
@@ -334,7 +396,7 @@ napi_value napi_pv_recorder_get_selected_device(napi_env env, napi_callback_info
     return result;
 }
 
-napi_value napi_pv_recorder_get_audio_devices(napi_env env, napi_callback_info info) {
+napi_value napi_pv_recorder_get_available_devices(napi_env env, napi_callback_info info) {
     size_t argc = 0;
     napi_status status = napi_get_cb_info(env, info, &argc, NULL, NULL, NULL);
     if (status != napi_ok) {
@@ -345,16 +407,16 @@ napi_value napi_pv_recorder_get_audio_devices(napi_env env, napi_callback_info i
         return NULL;
     }
 
-    int32_t count = 0;
-    char **devices = NULL;
-    pv_recorder_status_t pv_recorder_status = pv_recorder_get_audio_devices(&count, &devices);
+    int32_t device_list_length = 0;
+    char **device_list = NULL;
+    pv_recorder_status_t pv_recorder_status = pv_recorder_get_available_devices(&device_list_length, &device_list);
 
     if (pv_recorder_status != PV_RECORDER_STATUS_SUCCESS) {
         return NULL;
     }
 
     napi_value result;
-    status = napi_create_array_with_length(env, count, &result);
+    status = napi_create_array_with_length(env, device_list_length, &result);
     if (status != napi_ok) {
         napi_throw_error(
                 env,
@@ -363,9 +425,9 @@ napi_value napi_pv_recorder_get_audio_devices(napi_env env, napi_callback_info i
         return NULL;
     }
 
-    for (int32_t i = 0; i < count; i++) {
+    for (int32_t i = 0; i < device_list_length; i++) {
         napi_value device_name;
-        status = napi_create_string_utf8(env, devices[i], NAPI_AUTO_LENGTH, &device_name);
+        status = napi_create_string_utf8(env, device_list[i], NAPI_AUTO_LENGTH, &device_name);
         if (status != napi_ok) {
             napi_throw_error(
                     env,
@@ -384,10 +446,27 @@ napi_value napi_pv_recorder_get_audio_devices(napi_env env, napi_callback_info i
         }
     }
 
-    pv_recorder_free_device_list(count, devices);
+    pv_recorder_free_available_devices(device_list_length, device_list);
     return result;
 }
 
+napi_value napi_pv_recorder_sample_rate(napi_env env, napi_callback_info info) {
+    (void) info;
+
+    napi_value result;
+    napi_status status = napi_create_int32(env, pv_recorder_sample_rate(), &result);
+    if (status != napi_ok) {
+        napi_throw_error(
+                env,
+                pv_recorder_status_to_string(PV_RECORDER_STATUS_RUNTIME_ERROR),
+                "Unable to get input arguments");
+        return NULL;
+    }
+
+    return result;
+}
+
+
 napi_value napi_pv_recorder_version(napi_env env, napi_callback_info info) {
     (void)(info);
 
@@ -427,11 +506,23 @@ napi_value Init(napi_env env, napi_value exports) {
     status = napi_define_properties(env, exports, 1, &desc);
     assert(status == napi_ok);
 
+    desc = DECLARE_NAPI_METHOD("get_is_recording", napi_pv_recorder_get_is_recording);
+    status = napi_define_properties(env, exports, 1, &desc);
+    assert(status == napi_ok);
+
+    desc = DECLARE_NAPI_METHOD("set_debug_logging", napi_pv_recorder_set_debug_logging);
+    status = napi_define_properties(env, exports, 1, &desc);
+    assert(status == napi_ok);
+
     desc = DECLARE_NAPI_METHOD("get_selected_device", napi_pv_recorder_get_selected_device);
     status = napi_define_properties(env, exports, 1, &desc);
     assert(status == napi_ok);
 
-    desc = DECLARE_NAPI_METHOD("get_audio_devices", napi_pv_recorder_get_audio_devices);
+    desc = DECLARE_NAPI_METHOD("get_available_devices", napi_pv_recorder_get_available_devices);
+    status = napi_define_properties(env, exports, 1, &desc);
+    assert(status == napi_ok);
+
+    desc = DECLARE_NAPI_METHOD("sample_rate", napi_pv_recorder_sample_rate);
     status = napi_define_properties(env, exports, 1, &desc);
     assert(status == napi_ok);
 
@@ -442,4 +533,4 @@ napi_value Init(napi_env env, napi_value exports) {
     return exports;
 }
 
-NAPI_MODULE(NODE_GYB_MODULE_NAME, Init)
\ No newline at end of file
+NAPI_MODULE(NODE_GYB_MODULE_NAME, Init)
diff --git a/src/miniaudio b/project/src/miniaudio
similarity index 100%
rename from src/miniaudio
rename to project/src/miniaudio
diff --git a/src/pv_circular_buffer.c b/project/src/pv_circular_buffer.c
similarity index 84%
rename from src/pv_circular_buffer.c
rename to project/src/pv_circular_buffer.c
index 158831ca..92a90a0f 100644
--- a/src/pv_circular_buffer.c
+++ b/project/src/pv_circular_buffer.c
@@ -1,5 +1,5 @@
 /*
-    Copyright 2021-2022 Picovoice Inc.
+    Copyright 2021-2023 Picovoice Inc.
 
     You may not use this file except in compliance with the license. A copy of the license is located in the "LICENSE"
     file accompanying this source.
@@ -24,10 +24,10 @@ struct pv_circular_buffer {
 };
 
 pv_circular_buffer_status_t pv_circular_buffer_init(
-        int32_t capacity,
+        int32_t element_count,
         int32_t element_size,
         pv_circular_buffer_t **object) {
-    if (capacity <= 0) {
+    if (element_count <= 0) {
         return PV_CIRCULAR_BUFFER_STATUS_INVALID_ARGUMENT;
     }
     if (element_size <= 0) {
@@ -44,13 +44,13 @@ pv_circular_buffer_status_t pv_circular_buffer_init(
         return PV_CIRCULAR_BUFFER_STATUS_OUT_OF_MEMORY;
     }
 
-    o->buffer = malloc(capacity * element_size);
+    o->buffer = malloc(element_count * element_size);
     if (!(o->buffer)) {
         pv_circular_buffer_delete(o);
         return PV_CIRCULAR_BUFFER_STATUS_OUT_OF_MEMORY;
     }
 
-    o->capacity = capacity;
+    o->capacity = element_count;
     o->element_size = element_size;
 
     *object = o;
@@ -65,17 +65,20 @@ void pv_circular_buffer_delete(pv_circular_buffer_t *object) {
     }
 }
 
-int32_t pv_circular_buffer_read(pv_circular_buffer_t *object, void *buffer, int32_t length) {
+int32_t pv_circular_buffer_read(
+        pv_circular_buffer_t *object,
+        void *buffer,
+        int32_t buffer_length) {
     if (!object) {
         return PV_CIRCULAR_BUFFER_STATUS_INVALID_ARGUMENT;
     }
     if (!buffer) {
         return PV_CIRCULAR_BUFFER_STATUS_INVALID_ARGUMENT;
     }
-    if (!length) {
+    if (!buffer_length) {
         return PV_CIRCULAR_BUFFER_STATUS_INVALID_ARGUMENT;
     }
-    if ((length <= 0) || (length > object->capacity)) {
+    if ((buffer_length <= 0) || (buffer_length > object->capacity)) {
         return PV_CIRCULAR_BUFFER_STATUS_INVALID_ARGUMENT;
     }
 
@@ -83,7 +86,7 @@ int32_t pv_circular_buffer_read(pv_circular_buffer_t *object, void *buffer, int3
     const void *src_ptr = (char *) object->buffer + (object->read_index * object->element_size);
 
     const int32_t available = object->capacity - object->read_index;
-    const int32_t max_copy = (object->count < length) ? object->count : length;
+    const int32_t max_copy = (object->count < buffer_length) ? object->count : buffer_length;
     const int32_t to_copy = (max_copy < available) ? max_copy : available;
 
     memcpy(dst_ptr, src_ptr, to_copy * object->element_size);
@@ -105,14 +108,17 @@ int32_t pv_circular_buffer_read(pv_circular_buffer_t *object, void *buffer, int3
     return max_copy;
 }
 
-pv_circular_buffer_status_t pv_circular_buffer_write(pv_circular_buffer_t *object, const void *buffer, int32_t length) {
+pv_circular_buffer_status_t pv_circular_buffer_write(
+        pv_circular_buffer_t *object,
+        const void *buffer,
+        int32_t buffer_length) {
     if (!object) {
         return PV_CIRCULAR_BUFFER_STATUS_INVALID_ARGUMENT;
     }
     if (!buffer) {
         return PV_CIRCULAR_BUFFER_STATUS_INVALID_ARGUMENT;
     }
-    if ((length <= 0) || (length > object->capacity)) {
+    if ((buffer_length <= 0) || (buffer_length > object->capacity)) {
         return PV_CIRCULAR_BUFFER_STATUS_INVALID_ARGUMENT;
     }
 
@@ -122,14 +128,14 @@ pv_circular_buffer_status_t pv_circular_buffer_write(pv_circular_buffer_t *objec
     const void *src_ptr = buffer;
 
     const int32_t available = object->capacity - object->write_index;
-    const int32_t to_copy = (length < available) ? length : available;
+    const int32_t to_copy = (buffer_length < available) ? buffer_length : available;
 
     memcpy(dst_ptr, src_ptr, to_copy * object->element_size);
 
     object->write_index = (object->write_index + to_copy) % object->capacity;
     object->count += to_copy;
 
-    const int32_t remaining = length - to_copy;
+    const int32_t remaining = buffer_length - to_copy;
     if (remaining > 0) {
         dst_ptr = object->buffer;
         src_ptr = (char *) buffer + (to_copy * object->element_size);
diff --git a/src/pv_recorder.c b/project/src/pv_recorder.c
similarity index 81%
rename from src/pv_recorder.c
rename to project/src/pv_recorder.c
index 3ff98db6..d326e8ec 100644
--- a/src/pv_recorder.c
+++ b/project/src/pv_recorder.c
@@ -1,5 +1,5 @@
 /*
-    Copyright 2021-2022 Picovoice Inc.
+    Copyright 2021-2023 Picovoice Inc.
 
     You may not use this file except in compliance with the license. A copy of the license is located in the "LICENSE"
     file accompanying this source.
@@ -22,9 +22,13 @@
 #include "pv_circular_buffer.h"
 #include "pv_recorder.h"
 
+#define PV_RECORDER_DEFAULT_DEVICE_INDEX (-1)
+#define PV_RECORDER_SAMPLE_RATE (16000)
+#define PV_RECORDER_VERSION "1.2.0"
+
 static const int32_t READ_RETRY_COUNT = 500;
 static const int32_t READ_SLEEP_MILLI_SECONDS = 2;
-static const int32_t MAX_SILENCE_BUFFER_SIZE = 2 * 16000;
+static const int32_t MAX_SILENCE_BUFFER_SIZE = 2 * PV_RECORDER_SAMPLE_RATE;
 static const int32_t ABSOLUTE_SILENCE_THRESHOLD = 1;
 
 struct pv_recorder {
@@ -34,8 +38,7 @@ struct pv_recorder {
     int32_t frame_length;
     int32_t current_silent_samples;
     bool is_started;
-    bool log_overflow;
-    bool log_silence;
+    bool is_debug_logging_enabled;
     ma_mutex mutex;
 };
 
@@ -46,7 +49,7 @@ static void pv_recorder_ma_callback(ma_device *device, void *output, const void
 
     ma_mutex_lock(&object->mutex);
     pv_circular_buffer_status_t status = pv_circular_buffer_write(object->buffer, input, (int32_t) frame_count);
-    if ((status == PV_CIRCULAR_BUFFER_STATUS_WRITE_OVERFLOW) && (object->log_overflow)) {
+    if ((status == PV_CIRCULAR_BUFFER_STATUS_WRITE_OVERFLOW) && (object->is_debug_logging_enabled)) {
         fprintf(stdout, "[WARN] Overflow - reader is not reading fast enough.\n");
     }
 
@@ -54,11 +57,9 @@ static void pv_recorder_ma_callback(ma_device *device, void *output, const void
 }
 
 PV_API pv_recorder_status_t pv_recorder_init(
-        int32_t device_index,
         int32_t frame_length,
-        int32_t buffer_size_msec,
-        bool log_overflow,
-        bool log_silence,
+        int32_t device_index,
+        int32_t buffered_frames_count,
         pv_recorder_t **object) {
     if (device_index < PV_RECORDER_DEFAULT_DEVICE_INDEX) {
         return PV_RECORDER_STATUS_INVALID_ARGUMENT;
@@ -66,19 +67,13 @@ PV_API pv_recorder_status_t pv_recorder_init(
     if (frame_length <= 0) {
         return PV_RECORDER_STATUS_INVALID_ARGUMENT;
     }
-    if (buffer_size_msec <= 0) {
+    if (buffered_frames_count < 1) {
         return PV_RECORDER_STATUS_INVALID_ARGUMENT;
     }
     if (!object) {
         return PV_RECORDER_STATUS_INVALID_ARGUMENT;
     }
 
-    // capacity = 16kHz * seconds
-    const int32_t capacity = (int32_t) ((16000 * buffer_size_msec) / 1000);
-    if (capacity < frame_length) {
-        return PV_RECORDER_STATUS_INVALID_ARGUMENT;
-    }
-
     *object = NULL;
 
     pv_recorder_t *o = calloc(1, sizeof(pv_recorder_t));
@@ -109,7 +104,12 @@ PV_API pv_recorder_status_t pv_recorder_init(
     if (device_index != PV_RECORDER_DEFAULT_DEVICE_INDEX) {
         ma_device_info *capture_info = NULL;
         ma_uint32 count = 0;
-        result = ma_context_get_devices(&(o->context), NULL, NULL, &capture_info, &count);
+        result = ma_context_get_devices(
+                &(o->context),
+                NULL,
+                NULL,
+                &capture_info,
+                &count);
         if (result != MA_SUCCESS) {
             pv_recorder_delete(o);
             if (result == MA_OUT_OF_MEMORY) {
@@ -147,8 +147,9 @@ PV_API pv_recorder_status_t pv_recorder_init(
         }
     }
 
+    const int32_t buffer_capacity = frame_length * buffered_frames_count;
     pv_circular_buffer_status_t status = pv_circular_buffer_init(
-            capacity,
+            buffer_capacity,
             sizeof(int16_t),
             &(o->buffer));
 
@@ -158,8 +159,6 @@ PV_API pv_recorder_status_t pv_recorder_init(
     }
 
     o->frame_length = frame_length;
-    o->log_overflow = log_overflow;
-    o->log_silence = log_silence;
 
     *object = o;
 
@@ -201,6 +200,7 @@ PV_API pv_recorder_status_t pv_recorder_stop(pv_recorder_t *object) {
         return PV_RECORDER_STATUS_INVALID_ARGUMENT;
     }
 
+
     ma_result result = ma_device_stop(&(object->device));
     if (result != MA_SUCCESS) {
         if (result == MA_DEVICE_NOT_INITIALIZED) {
@@ -211,39 +211,46 @@ PV_API pv_recorder_status_t pv_recorder_stop(pv_recorder_t *object) {
         }
     }
 
+    ma_mutex_lock(&object->mutex);
     pv_circular_buffer_reset(object->buffer);
     object->is_started = false;
+    ma_mutex_unlock(&object->mutex);
 
     return PV_RECORDER_STATUS_SUCCESS;
 }
 
-PV_API pv_recorder_status_t pv_recorder_read(pv_recorder_t *object, int16_t *pcm) {
+PV_API pv_recorder_status_t pv_recorder_read(pv_recorder_t *object, int16_t *frame) {
     if (!object) {
         return PV_RECORDER_STATUS_INVALID_ARGUMENT;
     }
-    if (!pcm) {
+    if (!frame) {
         return PV_RECORDER_STATUS_INVALID_ARGUMENT;
     }
     if (!(object->is_started)) {
         return PV_RECORDER_STATUS_INVALID_STATE;
     }
 
-    int16_t *read_ptr = pcm;
+    int16_t *read_ptr = frame;
     int32_t processed = 0;
     int32_t remaining = object->frame_length;
 
     for (int32_t i = 0; i < READ_RETRY_COUNT; i++) {
         ma_mutex_lock(&object->mutex);
 
+        if (!(object->is_started)) {
+            ma_mutex_unlock(&object->mutex);
+            return PV_RECORDER_STATUS_SUCCESS;
+        }
+
         const int32_t length = pv_circular_buffer_read(object->buffer, read_ptr, remaining);
         processed += length;
 
         if (processed == object->frame_length) {
             ma_mutex_unlock(&object->mutex);
 
-            if (object->log_silence) {
+            if (object->is_debug_logging_enabled) {
                 for (int32_t j = 0; j < object->frame_length; j++) {
-                    if ((pcm[j] > ABSOLUTE_SILENCE_THRESHOLD) || (pcm[j] < -ABSOLUTE_SILENCE_THRESHOLD)) {
+                    if ((frame[j] > ABSOLUTE_SILENCE_THRESHOLD) || (frame[j] < -ABSOLUTE_SILENCE_THRESHOLD)) {
                         object->current_silent_samples = 0;
                         return PV_RECORDER_STATUS_SUCCESS;
                     }
@@ -269,6 +276,23 @@ PV_API pv_recorder_status_t pv_recorder_read(pv_recorder_t *object, int16_t *pcm
     return PV_RECORDER_STATUS_IO_ERROR;
 }
 
+PV_API void pv_recorder_set_debug_logging(
+        pv_recorder_t *object,
+        bool is_debug_logging_enabled) {
+    if (!object) {
+        return;
+    }
+
+    object->is_debug_logging_enabled = is_debug_logging_enabled;
+}
+
+PV_API bool pv_recorder_get_is_recording(pv_recorder_t *object) {
+    if (!object) {
+        return false;
+    }
+    return object->is_started;
+}
+
 PV_API const char *pv_recorder_get_selected_device(pv_recorder_t *object) {
     if (!object) {
         return NULL;
@@ -276,11 +300,13 @@ PV_API const char *pv_recorder_get_selected_device(pv_recorder_t *object) {
     return object->device.capture.name;
 }
 
-PV_API pv_recorder_status_t pv_recorder_get_audio_devices(int32_t *count, char ***devices) {
-    if (!count) {
+PV_API pv_recorder_status_t pv_recorder_get_available_devices(
+        int32_t *device_list_length,
+        char ***device_list) {
+    if (!device_list_length) {
         return PV_RECORDER_STATUS_INVALID_ARGUMENT;
     }
-    if (!devices) {
+    if (!device_list) {
         return PV_RECORDER_STATUS_INVALID_ARGUMENT;
     }
 
@@ -298,7 +324,12 @@ PV_API pv_recorder_status_t pv_recorder_get_audio_devices(int32_t *count, char *
 
     ma_device_info *capture_info;
     ma_uint32 capture_count;
-    result = ma_context_get_devices(&context, NULL, NULL, &capture_info, &capture_count);
+    result = ma_context_get_devices(
+            &context,
+            NULL,
+            NULL,
+            &capture_info,
+            &capture_count);
     if (result != MA_SUCCESS) {
         ma_context_uninit(&context);
         if (result == MA_OUT_OF_MEMORY) {
@@ -328,18 +359,20 @@ PV_API pv_recorder_status_t pv_recorder_get_audio_devices(int32_t *count, char *
 
     ma_context_uninit(&context);
 
-    *count = (int32_t) capture_count;
-    *devices = d;
+    *device_list_length = (int32_t) capture_count;
+    *device_list = d;
 
     return PV_RECORDER_STATUS_SUCCESS;
 }
 
-PV_API void pv_recorder_free_device_list(int32_t count, char **devices) {
-    if (devices && (count > 0)) {
-        for (int32_t i = 0; i < count; i++) {
-            free(devices[i]);
+PV_API void pv_recorder_free_available_devices(
+        int32_t device_list_length,
+        char **device_list) {
+    if (device_list && (device_list_length > 0)) {
+        for (int32_t i = 0; i < device_list_length; i++) {
+            free(device_list[i]);
         }
-        free(devices);
+        free(device_list);
     }
 }
 
@@ -363,6 +396,10 @@ PV_API const char *pv_recorder_status_to_string(pv_recorder_status_t status) {
     return STRINGS[status - PV_RECORDER_STATUS_SUCCESS];
 }
 
+PV_API int32_t pv_recorder_sample_rate(void) {
+    return PV_RECORDER_SAMPLE_RATE;
+}
+
 PV_API const char *pv_recorder_version(void) {
-    return "v1.1.0";
+    return PV_RECORDER_VERSION;
 }
diff --git a/resources/.lint/c/formatter.py b/resources/.lint/c/formatter.py
new file mode 100644
index 00000000..8a615b03
--- /dev/null
+++ b/resources/.lint/c/formatter.py
@@ -0,0 +1,80 @@
+import fnmatch
+import os
+import re
+import subprocess
+from argparse import ArgumentParser
+
+import sys
+
+IGNORE_LIST = {
+    'project/src/miniaudio',
+    'lib'
+}
+
+EXCLUDE_PATTERN = {
+    '.*ios.*',
+    '.*node_modules.*',
+    '.*?/mcu(?!.*src/(pv|main)).*',
+    '.*build.*',
+}
+
+
+def main():
+    parser = ArgumentParser()
+
+    parser.add_argument('--verbose',
+                        '-v',
+                        action='store_true',
+                        help='If set, shows the list of processed files')
+    parser.add_argument('--check-only',
+                        '-c',
+                        action='store_true',
+                        help='If set, checks for warnings only')
+
+    input_args = parser.parse_args()
+    formatter(input_args.verbose, input_args.check_only)
+
+
+def formatter(verbose, check_only):
+    if check_only:
+        cmd = "clang-format --dry-run --Werror --verbose"
+    else:
+        cmd = "clang-format -i -style=file --verbose"
+    print(cmd)
+
+    src_dir = os.path.join(os.path.abspath(os.path.dirname(__file__)), '../../../')
+    all_files = find('*.c', src_dir)
+    all_files.extend(find('*.h', src_dir))
+
+    c_source_files = [file_path for file_path in all_files if
+                      not any(ignored_path in file_path for ignored_path in IGNORE_LIST)]
+
+    c_source_files = [file_path for file_path in c_source_files if
+                      not any(re.match(pattern, file_path, flags=re.IGNORECASE) for pattern in EXCLUDE_PATTERN)]
+
+    c_source_files_num = len(c_source_files)
+    for index, c_source_file in enumerate(c_source_files):
+        format_command = f"{cmd} {c_source_file}"
+        try:
+            result = subprocess.check_output(format_command, shell=True, stderr=subprocess.STDOUT).decode('utf-8')
+        except subprocess.CalledProcessError as e:
+            print(f'Formatter failed with ({e.returncode}):\n{e.output.decode("utf-8")}')
+            continue
+
+        if verbose:
+            print(result)
+        sys.stdout.write(f"Completion: {index / c_source_files_num * 100:.2f}%\r")
+        sys.stdout.flush()
+
+
+def find(pattern, path):
+    file_list = []
+    for root, dirs, files in os.walk(path):
+        for name in files:
+            if fnmatch.fnmatch(name, pattern):
+                file_list.append(os.path.join(root, name))
+    return file_list
+
+
+if __name__ == '__main__':
+    main()
diff --git a/resources/spell-check/.cspell.json b/resources/.lint/spell-check/.cspell.json
similarity index 87%
rename from resources/spell-check/.cspell.json
rename to resources/.lint/spell-check/.cspell.json
index a19c4dad..0862c79a 100644
--- a/resources/spell-check/.cspell.json
+++ b/resources/.lint/spell-check/.cspell.json
@@ -11,10 +11,11 @@
     }
   ],
   "ignorePaths": [
-    "../../CMakeLists.txt",
+    "**/CMakeLists.txt",
+    "**/Preload.CMake",
 
     // submodules
-    "../../src/miniaudio",
+    "../../project/src/miniaudio",
 
     // binaries
     "**/*.a",
diff --git a/resources/spell-check/dict.txt b/resources/.lint/spell-check/dict.txt
similarity index 87%
rename from resources/spell-check/dict.txt
rename to resources/.lint/spell-check/dict.txt
index 45cb1865..0f963bb3 100644
--- a/resources/spell-check/dict.txt
+++ b/resources/.lint/spell-check/dict.txt
@@ -1,43 +1,48 @@
-CTEST
-Cdecl
-DOUTPUT
-ERRORLEVEL
-HMODULE
-LDFLAGS
-LPCSTR
-Makefiles
-NETCOREAPP
-Picovoice
-RTLD
-Wunused
 aarch
 argstype
 armv
 beaglebone
+bitwidth
+Cdecl
 cpvrecorder
+CTEST
 dlfcn
 dlltool
 dlopen
 dlsym
+DOUTPUT
 dylib
+ERRORLEVEL
 fprintf
 fvisibility
 gendef
+getconf
+HMODULE
 jetson
+LDFLAGS
 libc
 libloading
 libpv
+LPCSTR
 lpthread
+Makefiles
 malloc
 miniaudio
 napi
+NETCOREAPP
+Picovoice
 pthread
 pvrecorder
+pvrecorderdemo
 repr
+RTLD
+stdbool
 stdint
 stdlib
+subchunk
 typedarray
 vtable
+wavefile
 wavfile
-bitwidth
-getconf
+Wunused
+xcopy
diff --git a/sdk/go/embedded/scripts/platform.bat b/resources/scripts/platform.bat
similarity index 100%
rename from sdk/go/embedded/scripts/platform.bat
rename to resources/scripts/platform.bat
diff --git a/sdk/go/embedded/scripts/platform.sh b/resources/scripts/platform.sh
similarity index 93%
rename from sdk/go/embedded/scripts/platform.sh
rename to resources/scripts/platform.sh
index 82fdc98d..b4a050dc 100755
--- a/sdk/go/embedded/scripts/platform.sh
+++ b/resources/scripts/platform.sh
@@ -18,8 +18,9 @@ case $kernel in
     case $arch in
       "x86_64") ;;
       "arm"* | "aarch64")
+        bitwidth=$(getconf LONG_BIT)
         arch_info=''
-        if [[ $arch == "aarch64" ]]; then
+        if [[ $bitwidth == "64" ]]; then
           arch_info=-$arch
         fi
         IFS=$'\n'
diff --git a/sdk/dotnet/.gitignore b/sdk/dotnet/.gitignore
deleted file mode 100644
index 3f869cbd..00000000
--- a/sdk/dotnet/.gitignore
+++ /dev/null
@@ -1,8 +0,0 @@
-.vs
-Demo/bin/**
-Demo/obj/**
-Demo/Properties/**
-PvRecorder/bin/**
-PvRecorder/obj/**
-PvRecorder/Properties/**
-*.user
\ No newline at end of file
diff --git a/sdk/dotnet/Demo/Demo.cs b/sdk/dotnet/Demo/Demo.cs
deleted file mode 100644
index 0757dfb4..00000000
--- a/sdk/dotnet/Demo/Demo.cs
+++ /dev/null
@@ -1,114 +0,0 @@
-﻿using System;
-using System.IO;
-using Pv;
-
-namespace demo
-{
-    class Demo
-    {
-        private static bool isInterrupted = false;
-
-        static void Main(string[] args)
-        {
-            bool showAudioDevices = false;
-            int audioDeviceIndex = -1;
-            string rawOutputPath = null;
-
-            int argIndex = 0;
-            while (argIndex < args.Length)
-            {
-                if (args[argIndex] == "--show_audio_devices")
-                {
-                    showAudioDevices = true;
-                    argIndex++;
-                }
-                else if (args[argIndex] == "--audio_device_index")
-                {
-                    if (++argIndex < args.Length && int.TryParse(args[argIndex], out int deviceIndex))
-                    {
-                        audioDeviceIndex = deviceIndex;
-                        argIndex++;
-                    }
-                }
-                else if (args[argIndex] == "--raw_output_path")
-                {
-                    if (++argIndex < args.Length)
-                    {
-                        rawOutputPath = args[argIndex++];
-
-                    }
-                }
-                else
-                {
-                    argIndex++;
-                }
-            }
-
-            if (showAudioDevices)
-            {
-                string[] devices = PvRecorder.GetAudioDevices();
-                for (int i = 0; i < devices.Length; i++)
-                {
-                    Console.WriteLine($"index: {i}, name: {devices[i]}");
-                }
-                return;
-            }
-            PvRecorder recorder = null;
-            FileStream fileStream = null;
-            BinaryWriter binaryWriter = null;
-
-            try
-            {
-                Console.CancelKeyPress += delegate (object sender, ConsoleCancelEventArgs e)
-                {
-                    e.Cancel = true;
-                    Demo.isInterrupted = true;
-                };
-
-                int frameLength = 512;
-                recorder = PvRecorder.Create(audioDeviceIndex, frameLength);
-                Console.WriteLine($"Using PvRecorder version: {recorder.Version}");
-
-                recorder.Start();
-                Console.WriteLine($"Using device: {recorder.SelectedDevice}");
-
-                if (rawOutputPath != null)
-                {
-                    fileStream = new FileStream(rawOutputPath, FileMode.Create);
-                    binaryWriter = new BinaryWriter(fileStream);
-                }
-
-                while (!isInterrupted)
-                {
-                    short[] pcm = recorder.Read();
-                    if (binaryWriter != null)
-                    {
-                        foreach (short frame in pcm)
-                        {
-                            binaryWriter.Write(frame);
-                        }
-                    }
-                }
-            }
-            catch (Exception e) {
-                Console.WriteLine($"Exception: {e.Message}");
-            }
-            finally
-            {
-
-                Console.WriteLine("Stopping...");
-                if (binaryWriter != null)
-                {
-                    binaryWriter.Close();
-                }
-
-                if (fileStream != null)
-                {
-                    fileStream.Close();
-                }
-                recorder?.Dispose();
-            }
-    
-        }
-    }
-}
diff --git a/sdk/dotnet/PvRecorder/PvRecorder.targets b/sdk/dotnet/PvRecorder/PvRecorder.targets
deleted file mode 100644
index f2489351..00000000
--- a/sdk/dotnet/PvRecorder/PvRecorder.targets
+++ /dev/null
@@ -1,35 +0,0 @@
-<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
-	<ItemGroup Condition="$(TargetFramework) == 'netstandard2.0'">
-		<Content Include="$(MSBuildThisFileDirectory)../../lib/netstandard2.0/PvRecorder.dll">
-			<Link>PvRecorder.dll</Link>
-			<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-			<Visible>False</Visible>
-		</Content>
-	</ItemGroup>
-	<ItemGroup Condition="$(TargetFramework) == 'netcoreapp3.1'">
-		<Content Include="$(MSBuildThisFileDirectory)../../lib/netcoreapp3.1/PvRecorder.dll">
-			<Link>PvRecorder.dll</Link>
-			<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-			<Visible>False</Visible>
-		</Content>
-	</ItemGroup>
-	<ItemGroup Condition="$(TargetFramework) == 'net6.0'">
-		<Content Include="$(MSBuildThisFileDirectory)../../lib/net6.0/PvRecorder.dll">
-			<Link>PvRecorder.dll</Link>
-			<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-			<Visible>False</Visible>
-		</Content>
-	</ItemGroup>
-	<ItemGroup>
-		<Content Include="$(MSBuildThisFileDirectory)scripts/**">
-			<Link>scripts/%(Filename)%(Extension)</Link>
-			<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-			<Visible>False</Visible>
-		</Content>
-		<Content Include="$(MSBuildThisFileDirectory)lib/**">
-			<Link>lib/%(RecursiveDir)%(Filename)%(Extension)</Link>
-			<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
-			<Visible>false</Visible>
-		</Content>
-	</ItemGroup>
-</Project>
diff --git a/sdk/dotnet/README.md b/sdk/dotnet/README.md
deleted file mode 100644
index 3eb1f100..00000000
--- a/sdk/dotnet/README.md
+++ /dev/null
@@ -1,104 +0,0 @@
-# PV_Recorder
-
-A cross platform audio recorder that captures single-channel audio at a sample rate of 16kHz.
-
-## Requirements
-
-- .NET Standard 2.0, .NET Core 3.1, .NET 6.0
-
-## Compatibility
-
-- Windows (amd64)
-- macOS (x86_64, arm64)
-- Linux (x86_64)
-- Raspberry Pi:
-    - 2
-    - 3 (32 and 64 bit)
-    - 4 (32 and 64 bit)
-- NVIDIA Jetson Nano
-- BeagleBone
-
-## Installation
-
-```console
-dotnet add package PvRecorder
-```
-
-## Usage
-
-Getting the list of input devices does not require an instance:
-
-```csharp
-using Pv;
-
-string[] devices = PvRecorder.GetAudioDevices();
-```
-
-To start recording initialize the instance and run start:
-
-```csharp
-PvRecorder recorder = PvRecorder.Create(
-    deviceIndex: -1, // uses default index
-    frameLength: 512,
-);
-
-recorder.Start();
-```
-
-Get the pcm frames by calling the read function:
-
-```csharp
-while (true) {
-    pcm = recorder.Read();
-    // do something with pcm
-}
-```
-
-To stop recording just run stop on the instance:
-
-```csharp
-recorder.Stop();
-```
-
-Once you are done, free the used resources. You do not have to call stop before Dispose:
-
-```csharp
-recorder.Dispose();
-```
-
-To have to resources freed immediately after use without explicitly calling the `Dispose` function, wrap PvRecorder in a using statement:
-
-```csharp
-using(PvRecorder recorder = PvRecorder.Create(deviceIndex: -1, frameLength: 512)) {
-    // PvRecorder usage
-}
-```
-
-### Demo
-
-**NOTE**: The demo is built on .Net Core 3.1.
-
-For more detailed information on how to use the pv_recorder .NET sdk, please that a look at [Demo/Demo.cs](Demo/Demo.cs).
-
-In the following instructions we will refer to  `{AUDIO_DEVICE_INDEX}` as the index of the audio device to use, and `{RAW_OUTPUT_PATH}` as the path to save the raw audio data
-
-`{AUDIO_DEVICE_INDEX}` defaults to -1 and `{RAW_OUTPUT_PATH}` can be empty if you wish to not save any data.
-
-To build the demo:
-
-```console
-dotnet build -c Release
-```
-
-To show the available audio devices run:
-
-```console
-dotnet run --project Demo -c Release -- --show_audio_devices
-```
-
-To run audio recorder run:
-
-```console
-dotnet run --project Demo -c Release
--- --audio_device_index {AUDIO_DEVICE_INDEX} --raw_output_path {RAW_OUTPUT_PATH}
-```
diff --git a/sdk/go/README.md b/sdk/go/README.md
deleted file mode 100644
index 70460bc7..00000000
--- a/sdk/go/README.md
+++ /dev/null
@@ -1,92 +0,0 @@
-# PV_Recorder
-
-A cross platform audio recorder to read one channel and 16kHz samples.
-
-## Requirements
-
-- Go 1.16+
-
-## Compatibility
-
-- Windows (amd64)
-- macOS
-    - x86_64
-    - arm64
-- Linux (x86_64)
-- Raspberry Pi:
-    - Zero
-    - 2
-    - 3 (32 and 64 bit)
-    - 4 (32 and 64 bit)
-- NVIDIA Jetson Nano
-- BeagleBone
-
-## Installation
-
-```console
-go get github.com/Picovoice/pvrecorder/sdk/go
-```
-
-## Usage
-
-To get the list of available devices:
-
-```go
-import . "github.com/Picovoice/pvrecorder/sdk/go"
-
-devices, err := GetAudioDevices()
-if err != nil {
-    // error
-}
-```
-
-To start recording, initialize the instance and run start function:
-
-```go
-import . "github.com/Picovoice/pvrecorder/sdk/go"
-
-recorder := PvRecorder{
-    DeviceIndex: -1, // Using -1 for index uses default audio input device.
-    FrameLength: 512,
-    BufferSizeMSec: 1000,
-    LogOverflow: 1,
-}
-if err := recorder.Init(); err != nil {
-    // error
-}
-if err := recorder.Start(); err != nil {
-    // error
-}
-```
-
-To read the pcm frames, run:
-
-```go
-pcm, err := recorder.Read()
-if err != nil {
-    // handle error
-}
-// do something with pcm
-```
-
-To stop recording just run stop on the instance:
-
-```go
-recorder.Stop()
-```
-
-Once you are done, free the used resources. You do not have to call stop before delete:
-
-```go
-recorder.Delete()
-```
-
-### Demo
-
-For more detailed information on how to use the pv_recorder go sdk, please that a look at [demo/demo.go](demo/demo.go). 
-
-To run the demo:
-
-```console
-go run demo/demo.go
-```
\ No newline at end of file
diff --git a/sdk/go/demo/demo.go b/sdk/go/demo/demo.go
deleted file mode 100644
index 31d2b242..00000000
--- a/sdk/go/demo/demo.go
+++ /dev/null
@@ -1,109 +0,0 @@
-// Copyright 2021 Picovoice Inc.
-//
-// You may not use this file except in compliance with the license. A copy of the license is
-// located in the "LICENSE" file accompanying this source.
-//
-// Unless required by applicable law or agreed to in writing, software distributed under the
-// License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
-// express or implied. See the License for the specific language governing permissions and
-// limitations under the License.
-//
-
-package main
-
-import (
-    "bytes"
-    "encoding/binary"
-    "flag"
-    "log"
-    "os"
-    "os/signal"
-
-    pvrecorder "github.com/Picovoice/pvrecorder/sdk/go"
-)
-
-func main() {
-    showAudioDevices := flag.Bool("show_audio_devices", false, "Display all the available input devices")
-    audioDeviceIndex := flag.Int("audio_device_index", -1, "Index of audio input device to use.")
-    rawOutputPath := flag.String("raw_output_path", "", "Output path to save recorded audio raw bytes.")
-    flag.Parse()
-
-    log.Printf("pvrecorder.go version: %s\n", pvrecorder.Version())
-
-    if *showAudioDevices {
-        log.Println("Printing devices...")
-        if devices, err := pvrecorder.GetAudioDevices(); err != nil {
-            log.Fatalf("Error: %s.\n", err.Error())
-        } else {
-            for i, device := range devices {
-                log.Printf("index: %d, device name: %s\n", i, device)
-            }
-        }
-        return;
-    }
-
-    recorder := pvrecorder.PvRecorder{
-        DeviceIndex: *audioDeviceIndex,
-        FrameLength: 512,
-        BufferSizeMSec: 1000,
-        LogOverflow: 1,
-    }
-
-    log.Println("Initializing...")
-    if err := recorder.Init(); err != nil {
-        log.Fatalf("Error: %s.\n", err.Error())
-    }
-    defer recorder.Delete()
-
-    log.Printf("Using device: %s", recorder.GetSelectedDevice())
-
-    log.Println("Starting...")
-    if err := recorder.Start(); err != nil {
-        log.Fatalf("Error: %s.\n", err.Error())
-    }
-
-    signalCh := make(chan os.Signal, 1)
-    waitCh := make(chan struct{})
-    signal.Notify(signalCh, os.Interrupt)
-
-    go func () {
-        <- signalCh
-        close(waitCh)
-    }()
-
-    var f *os.File
-    var buf *bytes.Buffer
-    if *rawOutputPath != "" {
-        file, err := os.Create(*rawOutputPath)
-        if err != nil {
-            log.Fatalf("Failed to create file: %s\n", err.Error())
-        }
-        defer file.Close()
-
-        f = file
-        buf = new(bytes.Buffer)
-    }
-    
-    waitLoop:
-    for {
-        select {
-        case <- waitCh:
-            log.Println("Stopping...")
-            break waitLoop
-        default:
-            pcm, err := recorder.Read()
-            if err != nil {
-                log.Fatalf("Error: %s.\n", err.Error())
-            }
-            if *rawOutputPath != "" {
-                if err := binary.Write(buf, binary.LittleEndian, pcm); err != nil {
-                    log.Fatalf("Failed to write pcm to buffer: %s.\n", err.Error())
-                }
-                if _, err := f.Write(buf.Bytes()); err != nil {
-                    log.Fatalf("Failed to write bytes to file: %s\n", err.Error())
-                }
-                buf.Reset()
-            }
-        }
-    }
-}
\ No newline at end of file
diff --git a/sdk/go/embedded/lib/beaglebone/libpv_recorder.so b/sdk/go/embedded/lib/beaglebone/libpv_recorder.so
deleted file mode 100755
index 799505dd..00000000
Binary files a/sdk/go/embedded/lib/beaglebone/libpv_recorder.so and /dev/null differ
diff --git a/sdk/go/embedded/lib/jetson/cortex-a57-aarch64/libpv_recorder.so b/sdk/go/embedded/lib/jetson/cortex-a57-aarch64/libpv_recorder.so
deleted file mode 100755
index 21e1bdda..00000000
Binary files a/sdk/go/embedded/lib/jetson/cortex-a57-aarch64/libpv_recorder.so and /dev/null differ
diff --git a/sdk/go/embedded/lib/linux/x86_64/libpv_recorder.so b/sdk/go/embedded/lib/linux/x86_64/libpv_recorder.so
deleted file mode 100755
index 65a39ec4..00000000
Binary files a/sdk/go/embedded/lib/linux/x86_64/libpv_recorder.so and /dev/null differ
diff --git a/sdk/go/embedded/lib/mac/arm64/libpv_recorder.dylib b/sdk/go/embedded/lib/mac/arm64/libpv_recorder.dylib
deleted file mode 100755
index 82b1d4ae..00000000
Binary files a/sdk/go/embedded/lib/mac/arm64/libpv_recorder.dylib and /dev/null differ
diff --git a/sdk/go/embedded/lib/mac/x86_64/libpv_recorder.dylib b/sdk/go/embedded/lib/mac/x86_64/libpv_recorder.dylib
deleted file mode 100755
index 0f798bfb..00000000
Binary files a/sdk/go/embedded/lib/mac/x86_64/libpv_recorder.dylib and /dev/null differ
diff --git a/sdk/go/embedded/lib/raspberry-pi/arm11/libpv_recorder.so b/sdk/go/embedded/lib/raspberry-pi/arm11/libpv_recorder.so
deleted file mode 100755
index e938a33a..00000000
Binary files a/sdk/go/embedded/lib/raspberry-pi/arm11/libpv_recorder.so and /dev/null differ
diff --git a/sdk/go/embedded/lib/raspberry-pi/cortex-a53-aarch64/libpv_recorder.so b/sdk/go/embedded/lib/raspberry-pi/cortex-a53-aarch64/libpv_recorder.so
deleted file mode 100755
index 5200d24f..00000000
Binary files a/sdk/go/embedded/lib/raspberry-pi/cortex-a53-aarch64/libpv_recorder.so and /dev/null differ
diff --git a/sdk/go/embedded/lib/raspberry-pi/cortex-a53/libpv_recorder.so b/sdk/go/embedded/lib/raspberry-pi/cortex-a53/libpv_recorder.so
deleted file mode 100755
index c0a0b744..00000000
Binary files a/sdk/go/embedded/lib/raspberry-pi/cortex-a53/libpv_recorder.so and /dev/null differ
diff --git a/sdk/go/embedded/lib/raspberry-pi/cortex-a7/libpv_recorder.so b/sdk/go/embedded/lib/raspberry-pi/cortex-a7/libpv_recorder.so
deleted file mode 100755
index 90069f62..00000000
Binary files a/sdk/go/embedded/lib/raspberry-pi/cortex-a7/libpv_recorder.so and /dev/null differ
diff --git a/sdk/go/embedded/lib/raspberry-pi/cortex-a72-aarch64/libpv_recorder.so b/sdk/go/embedded/lib/raspberry-pi/cortex-a72-aarch64/libpv_recorder.so
deleted file mode 100755
index 8412df81..00000000
Binary files a/sdk/go/embedded/lib/raspberry-pi/cortex-a72-aarch64/libpv_recorder.so and /dev/null differ
diff --git a/sdk/go/embedded/lib/raspberry-pi/cortex-a72/libpv_recorder.so b/sdk/go/embedded/lib/raspberry-pi/cortex-a72/libpv_recorder.so
deleted file mode 100755
index 986e6f13..00000000
Binary files a/sdk/go/embedded/lib/raspberry-pi/cortex-a72/libpv_recorder.so and /dev/null differ
diff --git a/sdk/go/embedded/lib/windows/amd64/libpv_recorder.dll b/sdk/go/embedded/lib/windows/amd64/libpv_recorder.dll
deleted file mode 100644
index c3f4dbcd..00000000
Binary files a/sdk/go/embedded/lib/windows/amd64/libpv_recorder.dll and /dev/null differ
diff --git a/sdk/go/go.mod b/sdk/go/go.mod
deleted file mode 100644
index 8b7e722b..00000000
--- a/sdk/go/go.mod
+++ /dev/null
@@ -1,3 +0,0 @@
-module github.com/Picovoice/pvrecorder/sdk/go
-
-go 1.16
diff --git a/sdk/go/pvrecorder_native.go b/sdk/go/pvrecorder_native.go
deleted file mode 100644
index 073d0422..00000000
--- a/sdk/go/pvrecorder_native.go
+++ /dev/null
@@ -1,207 +0,0 @@
-// Copyright 2021-2022 Picovoice Inc.
-//
-// You may not use this file except in compliance with the license. A copy of the license is
-// located in the "LICENSE" file accompanying this source.
-//
-// Unless required by applicable law or agreed to in writing, software distributed under the
-// License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
-// express or implied. See the License for the specific language governing permissions and
-// limitations under the License.
-//
-
-package pvrecorder
-
-/*
-#cgo linux LDFLAGS: -lpthread -ldl -lm
-#cgo darwin LDFLAGS: -lpthread -ldl -lm
-#include <stdlib.h>
-#include <stdint.h>
-
-#if defined(_WIN32) || defined(_WIN64)
-
-    #include <windows.h>
-
-#else
-
-    #include <dlfcn.h>
-
-#endif
-
-static void *open_dl(const char *dl_path) {
-
-#if defined(_WIN32) || defined(_WIN64)
-
-    return LoadLibrary((LPCSTR) dl_path);
-
-#else
-
-    return dlopen(dl_path, RTLD_NOW);
-
-#endif
-
-}
-
-static void *load_symbol(void *handle, const char *symbol) {
-
-#if defined(_WIN32) || defined(_WIN64)
-
-    return GetProcAddress((HMODULE) handle, symbol);
-
-#else
-
-    return dlsym(handle, symbol);
-
-#endif
-
-}
-
-typedef int32_t (*pv_recorder_init_func)(int32_t, int32_t, int32_t, int32_t, int32_t, void **);
-
-int32_t pv_recorder_init_wrapper(void *f, int32_t device_index, int32_t frame_length, int32_t buffer_size_msec, int32_t log_overflow, int32_t log_silence, void **object) {
-    return ((pv_recorder_init_func) f)(device_index, frame_length, buffer_size_msec, log_overflow, log_silence, object);
-}
-
-typedef void (*pv_recorder_delete_func)(void *);
-
-void pv_recorder_delete_wrapper(void *f, void *object) {
-    return ((pv_recorder_delete_func) f)(object);
-}
-
-typedef int32_t (*pv_recorder_start_func)(void *);
-
-int32_t pv_recorder_start_wrapper(void *f, void *object) {
-    return ((pv_recorder_start_func) f)(object);
-}
-
-typedef int32_t (*pv_recorder_stop_func)(void *);
-
-int32_t pv_recorder_stop_wrapper(void *f, void *object) {
-    return ((pv_recorder_stop_func) f)(object);
-}
-
-typedef int32_t (*pv_recorder_read_func)(void *, int16_t *);
-
-int32_t pv_recorder_read_wrapper(void *f, void *object, int16_t *pcm) {
-    return ((pv_recorder_read_func) f)(object, pcm);
-}
-
-typedef const char *(*pv_recorder_get_selected_device_func)(void *);
-
-const char *pv_recorder_get_selected_device_wrapper(void *f, void* object) {
-    return ((pv_recorder_get_selected_device_func) f)(object);
-}
-
-typedef int32_t (*pv_recorder_get_audio_devices_func)(int32_t *, char ***);
-
-int32_t pv_recorder_get_audio_devices_wrapper(void *f, int32_t *count, char ***devices) {
-    return ((pv_recorder_get_audio_devices_func) f)(count, devices);
-}
-
-typedef void (*pv_recorder_free_device_list_func)(int32_t, char**);
-
-void pv_recorder_free_device_list_wrapper(void *f, int32_t count, char **devices) {
-    return ((pv_recorder_free_device_list_func) f)(count, devices);
-}
-
-typedef const char *(*pv_recorder_version_func)();
-
-const char *pv_recorder_version_wrapper(void *f) {
-    return ((pv_recorder_version_func) f)();
-}
-
-*/
-import "C"
-
-import (
-	"unsafe"
-)
-
-// private vars
-var (
-	lib = C.open_dl(C.CString(libName))
-
-	pv_recorder_init_ptr                = C.load_symbol(lib, C.CString("pv_recorder_init"))
-	pv_recorder_delete_ptr              = C.load_symbol(lib, C.CString("pv_recorder_delete"))
-	pv_recorder_start_ptr               = C.load_symbol(lib, C.CString("pv_recorder_start"))
-	pv_recorder_stop_ptr                = C.load_symbol(lib, C.CString("pv_recorder_stop"))
-	pv_recorder_read_ptr                = C.load_symbol(lib, C.CString("pv_recorder_read"))
-	pv_recorder_get_selected_device_ptr = C.load_symbol(lib, C.CString("pv_recorder_get_selected_device"))
-	pv_recorder_get_audio_devices_ptr   = C.load_symbol(lib, C.CString("pv_recorder_get_audio_devices"))
-	pv_recorder_free_device_list_ptr    = C.load_symbol(lib, C.CString("pv_recorder_free_device_list"))
-	pv_recorder_version_ptr             = C.load_symbol(lib, C.CString("pv_recorder_version"))
-)
-
-func (np nativePvRecorderType) nativeInit(pvrecorder *PvRecorder) PvRecorderStatus {
-	var (
-		deviceIndex    = pvrecorder.DeviceIndex
-		frameLength    = pvrecorder.FrameLength
-		bufferSizeMSec = pvrecorder.BufferSizeMSec
-		logOverflow    = pvrecorder.LogOverflow
-		logSilence     = pvrecorder.LogSilence
-		ptrC           = make([]unsafe.Pointer, 1)
-	)
-
-	var ret = C.pv_recorder_init_wrapper(pv_recorder_init_ptr,
-		(C.int32_t)(deviceIndex),
-		(C.int32_t)(frameLength),
-		(C.int32_t)(bufferSizeMSec),
-		(C.int32_t)(logOverflow),
-		(C.int32_t)(logSilence),
-		&ptrC[0])
-
-	pvrecorder.handle = uintptr(ptrC[0])
-	return PvRecorderStatus(ret)
-}
-
-func (nativePvRecorderType) nativeDelete(pvrecorder *PvRecorder) {
-	C.pv_recorder_delete_wrapper(pv_recorder_delete_ptr,
-		unsafe.Pointer(pvrecorder.handle))
-}
-
-func (nativePvRecorderType) nativeStart(pvrecorder *PvRecorder) PvRecorderStatus {
-	var ret = C.pv_recorder_start_wrapper(pv_recorder_start_ptr,
-		unsafe.Pointer(pvrecorder.handle))
-
-	return PvRecorderStatus(ret)
-}
-
-func (nativePvRecorderType) nativeStop(pvrecorder *PvRecorder) PvRecorderStatus {
-	var ret = C.pv_recorder_stop_wrapper(pv_recorder_stop_ptr,
-		unsafe.Pointer(pvrecorder.handle))
-
-	return PvRecorderStatus(ret)
-}
-
-func (nativePvRecorderType) nativeRead(pvrecorder *PvRecorder, pcm *C.int16_t) PvRecorderStatus {
-	var ret = C.pv_recorder_read_wrapper(pv_recorder_read_ptr,
-		unsafe.Pointer(pvrecorder.handle),
-		pcm)
-
-	return PvRecorderStatus(ret)
-}
-
-func (nativePvRecorderType) nativeGetSelectedDevice(pvrecorder *PvRecorder) string {
-	var ret = C.pv_recorder_get_selected_device_wrapper(pv_recorder_get_selected_device_ptr,
-		unsafe.Pointer(pvrecorder.handle))
-
-	return C.GoString(ret)
-}
-
-func (nativePvRecorderType) nativeGetAudioDevices(count *int, devices ***C.char) PvRecorderStatus {
-	var ret = C.pv_recorder_get_audio_devices_wrapper(pv_recorder_get_audio_devices_ptr,
-		(*C.int32_t)(unsafe.Pointer(count)),
-		(***C.char)(unsafe.Pointer(devices)))
-
-	return PvRecorderStatus(ret)
-}
-
-func (nativePvRecorderType) nativeFreeDeviceList(count int, devices **C.char) {
-	C.pv_recorder_free_device_list_wrapper(pv_recorder_free_device_list_ptr,
-		(C.int32_t)(count),
-		(**C.char)(unsafe.Pointer(devices)))
-}
-
-func (nativePvRecorderType) nativeVersion() string {
-	var ret = C.pv_recorder_version_wrapper(pv_recorder_version_ptr)
-	return C.GoString(ret)
-}
diff --git a/sdk/nodejs/README.md b/sdk/nodejs/README.md
deleted file mode 100644
index 4d8720f3..00000000
--- a/sdk/nodejs/README.md
+++ /dev/null
@@ -1,86 +0,0 @@
-# PV_Recorder
-
-A cross platform audio recorder that captures single-channel audio at a sample rate of 16kHz.
-
-## Requirements
-
-- Node.js 14+
-
-## Compatibility
-
-- Windows (x86_64)
-- macOS
-    - x86_64
-    - arm64
-- Linux (x86_64)
-- Raspberry Pi:
-    - 2
-    - 3 (32 and 64 bit)
-    - 4 (32 and 64 bit)
-- NVIDIA Jetson Nano
-- BeagleBone
-
-## Installation
-
-```console
-yarn add @picovoice/pvrecorder-node
-```
-
-## Usage
-
-Getting the list of input devices does not require an instance:
-
-```javascript
-const { PvRecorder } = require("@picovoice/pvrecorder-node");
-
-const devices = PvRecorder.getAudioDevices();
-```
-
-To start recording initialize the instance and run `start`:
-
-```javascript
-const recorder = new PvRecorder(/*sets to default device*/-1, /*frame length*/ 512);
-recorder.start()
-```
-
-Get the pcm frames by calling the read function:
-
-```javascript
-while (true) {
-    /*const pcm = recorder.readSync(), for synchronous calls*/
-    const pcm = await recorder.read();
-    // do something with pcm
-}
-```
-
-To stop recording just run stop on the instance:
-
-```javascript
-recorder.stop();
-```
-
-Once you are done, free the used resources. You do not have to call stop before release:
-
-```csharp
-recorder.release()
-```
-
-### Demo
-
-For more detailed information on how to use the pv_recorder Node.js sdk, see [demo/demo.js](demo/demo.js). 
-
-In the following instructions, we will refer to  `{AUDIO_DEVICE_INDEX}` as the index of the audio device to use, and `{RAW_OUTPUT_PATH}` as the path to save the raw audio data 
-
-`{AUDIO_DEVICE_INDEX}` defaults to -1 and `{RAW_OUTPUT_PATH}` can be empty if you wish to not save any data.
-
-To show the available audio devices run:
-
-```console
-node demo/demo.js --show_audio_devices
-```
-
-To run the audio recorder:
-
-```console
-node demo/demo.js --audio_device_index {AUDIO_DEVICE_INDEX} --raw_output_path {RAW_OUTPUT_PATH}
-```
diff --git a/sdk/nodejs/demo/demo.js b/sdk/nodejs/demo/demo.js
deleted file mode 100644
index e15cfcd0..00000000
--- a/sdk/nodejs/demo/demo.js
+++ /dev/null
@@ -1,100 +0,0 @@
-//
-// Copyright 2021-2022 Picovoice Inc.
-//
-// You may not use this file except in compliance with the license. A copy of the license is located in the "LICENSE"
-// file accompanying this source.
-//
-// Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
-// an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
-// specific language governing permissions and limitations under the License.
-//
-"use strict";
-
-const fs = require("fs");
-
-const { PvRecorder } = require("../dist/index");
-
-let isInterrupted = false;
-
-const inputArguments = {
-    showAudioDevices: false,
-    audioDeviceIndex: -1,
-    rawOutputPath: null,
-};
-
-function parseArguments() {
-    const argv = process.argv.slice(2);
-
-    for (let i = 0; i < argv.length; i++) {
-        if ((argv[i] === "-h") || (argv[i] === "--help")) {
-            console.log("Usage: node demo/demo.js [-h | --help]");
-            console.log("\t--show_audio_devices Prints the list of available input audio devices.");
-            console.log("\t--audio_device_index {AUDIO_DEVICE_INDEX} The audio device to capture audio. If empty, default audio device will be used.");
-            console.log("\t--raw_output_path {RAW_OUTPUT_PATH} The path to save save raw audio frames. If empty, saving will be omitted.");
-            process.exit();
-        } else if (argv[i] === "--show_audio_devices") {
-            inputArguments.showAudioDevices = true;
-        } else if (argv[i] === "--audio_device_index") {
-            if (++i < argv.length) {
-                inputArguments.audioDeviceIndex = Number(argv[i]);
-                if (isNaN(inputArguments.audioDeviceIndex)) {
-                    inputArguments.audioDeviceIndex = -1;
-                }
-            }
-        } else if (argv[i] === "--raw_output_path") {
-            if (++i < argv.length) {
-                inputArguments.rawOutputPath = argv[i];
-            }
-        }
-    }
-}
-
-async function runDemo() {
-    parseArguments();
-
-    if (inputArguments.showAudioDevices) {
-        const devices = PvRecorder.getAudioDevices();
-        for (let i = 0; i < devices.length; i++) {
-            console.log(`index: ${i}, device name: ${devices[i]}`)
-        }
-    } else {
-        const frameLength = 512;
-        const recorder = new PvRecorder(inputArguments.audioDeviceIndex, frameLength);
-        console.log(`Using PvRecorder version: ${recorder.version}`);
-
-        recorder.start();
-        console.log(`Using device: ${recorder.getSelectedDevice()}`);
-
-        let stream;
-        if (inputArguments.rawOutputPath !== null) {
-            stream = fs.createWriteStream(inputArguments.rawOutputPath, {flags: 'w'});
-        }
-
-        while (!isInterrupted) {
-            const pcm = await recorder.read();
-            if (inputArguments.rawOutputPath !== null) {
-                stream.write(Buffer.from(pcm.buffer));
-            }
-        }
-
-        if (inputArguments.rawOutputPath !== null) {
-            stream.close();
-        }
-
-        console.log("Stopping...");
-        recorder.release();
-    }
-}
-
-// setup interrupt
-process.on("SIGINT", function () {
-    isInterrupted = true;
-});
-
-(async function () {
-    try {
-        await runDemo();
-    } catch (e) {
-        console.error(e.toString());
-    }
-})();
diff --git a/sdk/nodejs/yarn.lock b/sdk/nodejs/yarn.lock
deleted file mode 100644
index 6b13a6bd..00000000
--- a/sdk/nodejs/yarn.lock
+++ /dev/null
@@ -1,725 +0,0 @@
-# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY.
-# yarn lockfile v1
-
-
-"@types/node@^17.0.21":
-  version "17.0.45"
-  resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.45.tgz#2c0fafd78705e7a18b7906b5201a522719dc5190"
-  integrity sha512-w+tIMs3rq2afQdsPJlODhoUEKzFP1ayaoyl1CcnwtIlsVe7K7bA1NGm4s3PraqTLlXnbIN84zuBlxBWo1u9BLw==
-
-ansi-styles@^3.2.1:
-  version "3.2.1"
-  resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-3.2.1.tgz#41fbb20243e50b12be0f04b8dedbf07520ce841d"
-  integrity sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==
-  dependencies:
-    color-convert "^1.9.0"
-
-array-buffer-byte-length@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz#fabe8bc193fea865f317fe7807085ee0dee5aead"
-  integrity sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==
-  dependencies:
-    call-bind "^1.0.2"
-    is-array-buffer "^3.0.1"
-
-available-typed-arrays@^1.0.5:
-  version "1.0.5"
-  resolved "https://registry.yarnpkg.com/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz#92f95616501069d07d10edb2fc37d3e1c65123b7"
-  integrity sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==
-
-balanced-match@^1.0.0:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee"
-  integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==
-
-brace-expansion@^1.1.7:
-  version "1.1.11"
-  resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
-  integrity sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==
-  dependencies:
-    balanced-match "^1.0.0"
-    concat-map "0.0.1"
-
-call-bind@^1.0.0, call-bind@^1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c"
-  integrity sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==
-  dependencies:
-    function-bind "^1.1.1"
-    get-intrinsic "^1.0.2"
-
-chalk@^2.4.1:
-  version "2.4.2"
-  resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
-  integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
-  dependencies:
-    ansi-styles "^3.2.1"
-    escape-string-regexp "^1.0.5"
-    supports-color "^5.3.0"
-
-color-convert@^1.9.0:
-  version "1.9.3"
-  resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-1.9.3.tgz#bb71850690e1f136567de629d2d5471deda4c1e8"
-  integrity sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==
-  dependencies:
-    color-name "1.1.3"
-
-color-name@1.1.3:
-  version "1.1.3"
-  resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.3.tgz#a7d0558bd89c42f795dd42328f740831ca53bc25"
-  integrity sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==
-
-concat-map@0.0.1:
-  version "0.0.1"
-  resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
-  integrity sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
-
-cross-spawn@^6.0.5:
-  version "6.0.5"
-  resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"
-  integrity sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==
-  dependencies:
-    nice-try "^1.0.4"
-    path-key "^2.0.1"
-    semver "^5.5.0"
-    shebang-command "^1.2.0"
-    which "^1.2.9"
-
-define-properties@^1.1.3, define-properties@^1.1.4, define-properties@^1.2.0:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/define-properties/-/define-properties-1.2.0.tgz#52988570670c9eacedd8064f4a990f2405849bd5"
-  integrity sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==
-  dependencies:
-    has-property-descriptors "^1.0.0"
-    object-keys "^1.1.1"
-
-error-ex@^1.3.1:
-  version "1.3.2"
-  resolved "https://registry.yarnpkg.com/error-ex/-/error-ex-1.3.2.tgz#b4ac40648107fdcdcfae242f428bea8a14d4f1bf"
-  integrity sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==
-  dependencies:
-    is-arrayish "^0.2.1"
-
-es-abstract@^1.19.0, es-abstract@^1.20.4:
-  version "1.21.2"
-  resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.21.2.tgz#a56b9695322c8a185dc25975aa3b8ec31d0e7eff"
-  integrity sha512-y/B5POM2iBnIxCiernH1G7rC9qQoM77lLIMQLuob0zhp8C56Po81+2Nj0WFKnd0pNReDTnkYryc+zhOzpEIROg==
-  dependencies:
-    array-buffer-byte-length "^1.0.0"
-    available-typed-arrays "^1.0.5"
-    call-bind "^1.0.2"
-    es-set-tostringtag "^2.0.1"
-    es-to-primitive "^1.2.1"
-    function.prototype.name "^1.1.5"
-    get-intrinsic "^1.2.0"
-    get-symbol-description "^1.0.0"
-    globalthis "^1.0.3"
-    gopd "^1.0.1"
-    has "^1.0.3"
-    has-property-descriptors "^1.0.0"
-    has-proto "^1.0.1"
-    has-symbols "^1.0.3"
-    internal-slot "^1.0.5"
-    is-array-buffer "^3.0.2"
-    is-callable "^1.2.7"
-    is-negative-zero "^2.0.2"
-    is-regex "^1.1.4"
-    is-shared-array-buffer "^1.0.2"
-    is-string "^1.0.7"
-    is-typed-array "^1.1.10"
-    is-weakref "^1.0.2"
-    object-inspect "^1.12.3"
-    object-keys "^1.1.1"
-    object.assign "^4.1.4"
-    regexp.prototype.flags "^1.4.3"
-    safe-regex-test "^1.0.0"
-    string.prototype.trim "^1.2.7"
-    string.prototype.trimend "^1.0.6"
-    string.prototype.trimstart "^1.0.6"
-    typed-array-length "^1.0.4"
-    unbox-primitive "^1.0.2"
-    which-typed-array "^1.1.9"
-
-es-set-tostringtag@^2.0.1:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/es-set-tostringtag/-/es-set-tostringtag-2.0.1.tgz#338d502f6f674301d710b80c8592de8a15f09cd8"
-  integrity sha512-g3OMbtlwY3QewlqAiMLI47KywjWZoEytKr8pf6iTC8uJq5bIAH52Z9pnQ8pVL6whrCto53JZDuUIsifGeLorTg==
-  dependencies:
-    get-intrinsic "^1.1.3"
-    has "^1.0.3"
-    has-tostringtag "^1.0.0"
-
-es-to-primitive@^1.2.1:
-  version "1.2.1"
-  resolved "https://registry.yarnpkg.com/es-to-primitive/-/es-to-primitive-1.2.1.tgz#e55cd4c9cdc188bcefb03b366c736323fc5c898a"
-  integrity sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==
-  dependencies:
-    is-callable "^1.1.4"
-    is-date-object "^1.0.1"
-    is-symbol "^1.0.2"
-
-escape-string-regexp@^1.0.5:
-  version "1.0.5"
-  resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
-  integrity sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==
-
-for-each@^0.3.3:
-  version "0.3.3"
-  resolved "https://registry.yarnpkg.com/for-each/-/for-each-0.3.3.tgz#69b447e88a0a5d32c3e7084f3f1710034b21376e"
-  integrity sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==
-  dependencies:
-    is-callable "^1.1.3"
-
-function-bind@^1.1.1:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
-  integrity sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==
-
-function.prototype.name@^1.1.5:
-  version "1.1.5"
-  resolved "https://registry.yarnpkg.com/function.prototype.name/-/function.prototype.name-1.1.5.tgz#cce0505fe1ffb80503e6f9e46cc64e46a12a9621"
-  integrity sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==
-  dependencies:
-    call-bind "^1.0.2"
-    define-properties "^1.1.3"
-    es-abstract "^1.19.0"
-    functions-have-names "^1.2.2"
-
-functions-have-names@^1.2.2, functions-have-names@^1.2.3:
-  version "1.2.3"
-  resolved "https://registry.yarnpkg.com/functions-have-names/-/functions-have-names-1.2.3.tgz#0404fe4ee2ba2f607f0e0ec3c80bae994133b834"
-  integrity sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==
-
-get-intrinsic@^1.0.2, get-intrinsic@^1.1.1, get-intrinsic@^1.1.3, get-intrinsic@^1.2.0:
-  version "1.2.1"
-  resolved "https://registry.yarnpkg.com/get-intrinsic/-/get-intrinsic-1.2.1.tgz#d295644fed4505fc9cde952c37ee12b477a83d82"
-  integrity sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==
-  dependencies:
-    function-bind "^1.1.1"
-    has "^1.0.3"
-    has-proto "^1.0.1"
-    has-symbols "^1.0.3"
-
-get-symbol-description@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/get-symbol-description/-/get-symbol-description-1.0.0.tgz#7fdb81c900101fbd564dd5f1a30af5aadc1e58d6"
-  integrity sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==
-  dependencies:
-    call-bind "^1.0.2"
-    get-intrinsic "^1.1.1"
-
-globalthis@^1.0.3:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/globalthis/-/globalthis-1.0.3.tgz#5852882a52b80dc301b0660273e1ed082f0b6ccf"
-  integrity sha512-sFdI5LyBiNTHjRd7cGPWapiHWMOXKyuBNX/cWJ3NfzrZQVa8GI/8cofCl74AOVqq9W5kNmguTIzJ/1s2gyI9wA==
-  dependencies:
-    define-properties "^1.1.3"
-
-gopd@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c"
-  integrity sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==
-  dependencies:
-    get-intrinsic "^1.1.3"
-
-graceful-fs@^4.1.2:
-  version "4.2.11"
-  resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.11.tgz#4183e4e8bf08bb6e05bbb2f7d2e0c8f712ca40e3"
-  integrity sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==
-
-has-bigints@^1.0.1, has-bigints@^1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/has-bigints/-/has-bigints-1.0.2.tgz#0871bd3e3d51626f6ca0966668ba35d5602d6eaa"
-  integrity sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==
-
-has-flag@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
-  integrity sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==
-
-has-property-descriptors@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz#610708600606d36961ed04c196193b6a607fa861"
-  integrity sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==
-  dependencies:
-    get-intrinsic "^1.1.1"
-
-has-proto@^1.0.1:
-  version "1.0.1"
-  resolved "https://registry.yarnpkg.com/has-proto/-/has-proto-1.0.1.tgz#1885c1305538958aff469fef37937c22795408e0"
-  integrity sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==
-
-has-symbols@^1.0.2, has-symbols@^1.0.3:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/has-symbols/-/has-symbols-1.0.3.tgz#bb7b2c4349251dce87b125f7bdf874aa7c8b39f8"
-  integrity sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==
-
-has-tostringtag@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/has-tostringtag/-/has-tostringtag-1.0.0.tgz#7e133818a7d394734f941e73c3d3f9291e658b25"
-  integrity sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==
-  dependencies:
-    has-symbols "^1.0.2"
-
-has@^1.0.3:
-  version "1.0.3"
-  resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
-  integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==
-  dependencies:
-    function-bind "^1.1.1"
-
-hosted-git-info@^2.1.4:
-  version "2.8.9"
-  resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9"
-  integrity sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==
-
-internal-slot@^1.0.5:
-  version "1.0.5"
-  resolved "https://registry.yarnpkg.com/internal-slot/-/internal-slot-1.0.5.tgz#f2a2ee21f668f8627a4667f309dc0f4fb6674986"
-  integrity sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==
-  dependencies:
-    get-intrinsic "^1.2.0"
-    has "^1.0.3"
-    side-channel "^1.0.4"
-
-is-array-buffer@^3.0.1, is-array-buffer@^3.0.2:
-  version "3.0.2"
-  resolved "https://registry.yarnpkg.com/is-array-buffer/-/is-array-buffer-3.0.2.tgz#f2653ced8412081638ecb0ebbd0c41c6e0aecbbe"
-  integrity sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==
-  dependencies:
-    call-bind "^1.0.2"
-    get-intrinsic "^1.2.0"
-    is-typed-array "^1.1.10"
-
-is-arrayish@^0.2.1:
-  version "0.2.1"
-  resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
-  integrity sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==
-
-is-bigint@^1.0.1:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/is-bigint/-/is-bigint-1.0.4.tgz#08147a1875bc2b32005d41ccd8291dffc6691df3"
-  integrity sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==
-  dependencies:
-    has-bigints "^1.0.1"
-
-is-boolean-object@^1.1.0:
-  version "1.1.2"
-  resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719"
-  integrity sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==
-  dependencies:
-    call-bind "^1.0.2"
-    has-tostringtag "^1.0.0"
-
-is-callable@^1.1.3, is-callable@^1.1.4, is-callable@^1.2.7:
-  version "1.2.7"
-  resolved "https://registry.yarnpkg.com/is-callable/-/is-callable-1.2.7.tgz#3bc2a85ea742d9e36205dcacdd72ca1fdc51b055"
-  integrity sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==
-
-is-core-module@^2.11.0:
-  version "2.12.1"
-  resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.12.1.tgz#0c0b6885b6f80011c71541ce15c8d66cf5a4f9fd"
-  integrity sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==
-  dependencies:
-    has "^1.0.3"
-
-is-date-object@^1.0.1:
-  version "1.0.5"
-  resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f"
-  integrity sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==
-  dependencies:
-    has-tostringtag "^1.0.0"
-
-is-negative-zero@^2.0.2:
-  version "2.0.2"
-  resolved "https://registry.yarnpkg.com/is-negative-zero/-/is-negative-zero-2.0.2.tgz#7bf6f03a28003b8b3965de3ac26f664d765f3150"
-  integrity sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==
-
-is-number-object@^1.0.4:
-  version "1.0.7"
-  resolved "https://registry.yarnpkg.com/is-number-object/-/is-number-object-1.0.7.tgz#59d50ada4c45251784e9904f5246c742f07a42fc"
-  integrity sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==
-  dependencies:
-    has-tostringtag "^1.0.0"
-
-is-regex@^1.1.4:
-  version "1.1.4"
-  resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958"
-  integrity sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==
-  dependencies:
-    call-bind "^1.0.2"
-    has-tostringtag "^1.0.0"
-
-is-shared-array-buffer@^1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz#8f259c573b60b6a32d4058a1a07430c0a7344c79"
-  integrity sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==
-  dependencies:
-    call-bind "^1.0.2"
-
-is-string@^1.0.5, is-string@^1.0.7:
-  version "1.0.7"
-  resolved "https://registry.yarnpkg.com/is-string/-/is-string-1.0.7.tgz#0dd12bf2006f255bb58f695110eff7491eebc0fd"
-  integrity sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==
-  dependencies:
-    has-tostringtag "^1.0.0"
-
-is-symbol@^1.0.2, is-symbol@^1.0.3:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/is-symbol/-/is-symbol-1.0.4.tgz#a6dac93b635b063ca6872236de88910a57af139c"
-  integrity sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==
-  dependencies:
-    has-symbols "^1.0.2"
-
-is-typed-array@^1.1.10, is-typed-array@^1.1.9:
-  version "1.1.10"
-  resolved "https://registry.yarnpkg.com/is-typed-array/-/is-typed-array-1.1.10.tgz#36a5b5cb4189b575d1a3e4b08536bfb485801e3f"
-  integrity sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==
-  dependencies:
-    available-typed-arrays "^1.0.5"
-    call-bind "^1.0.2"
-    for-each "^0.3.3"
-    gopd "^1.0.1"
-    has-tostringtag "^1.0.0"
-
-is-weakref@^1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/is-weakref/-/is-weakref-1.0.2.tgz#9529f383a9338205e89765e0392efc2f100f06f2"
-  integrity sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==
-  dependencies:
-    call-bind "^1.0.2"
-
-isexe@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/isexe/-/isexe-2.0.0.tgz#e8fbf374dc556ff8947a10dcb0572d633f2cfa10"
-  integrity sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==
-
-json-parse-better-errors@^1.0.1:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz#bb867cfb3450e69107c131d1c514bab3dc8bcaa9"
-  integrity sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==
-
-load-json-file@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b"
-  integrity sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==
-  dependencies:
-    graceful-fs "^4.1.2"
-    parse-json "^4.0.0"
-    pify "^3.0.0"
-    strip-bom "^3.0.0"
-
-memorystream@^0.3.1:
-  version "0.3.1"
-  resolved "https://registry.yarnpkg.com/memorystream/-/memorystream-0.3.1.tgz#86d7090b30ce455d63fbae12dda51a47ddcaf9b2"
-  integrity sha512-S3UwM3yj5mtUSEfP41UZmt/0SCoVYUcU1rkXv+BQ5Ig8ndL4sPoJNBUJERafdPb5jjHJGuMgytgKvKIf58XNBw==
-
-minimatch@^3.0.4:
-  version "3.1.2"
-  resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b"
-  integrity sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==
-  dependencies:
-    brace-expansion "^1.1.7"
-
-mkdirp@^1.0.4:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e"
-  integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==
-
-ncp@^2.0.0:
-  version "2.0.0"
-  resolved "https://registry.yarnpkg.com/ncp/-/ncp-2.0.0.tgz#195a21d6c46e361d2fb1281ba38b91e9df7bdbb3"
-  integrity sha512-zIdGUrPRFTUELUvr3Gmc7KZ2Sw/h1PiVM0Af/oHB6zgnV1ikqSfRk+TOufi79aHYCW3NiOXmr1BP5nWbzojLaA==
-
-nice-try@^1.0.4:
-  version "1.0.5"
-  resolved "https://registry.yarnpkg.com/nice-try/-/nice-try-1.0.5.tgz#a3378a7696ce7d223e88fc9b764bd7ef1089e366"
-  integrity sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==
-
-normalize-package-data@^2.3.2:
-  version "2.5.0"
-  resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8"
-  integrity sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==
-  dependencies:
-    hosted-git-info "^2.1.4"
-    resolve "^1.10.0"
-    semver "2 || 3 || 4 || 5"
-    validate-npm-package-license "^3.0.1"
-
-npm-run-all@^4.1.5:
-  version "4.1.5"
-  resolved "https://registry.yarnpkg.com/npm-run-all/-/npm-run-all-4.1.5.tgz#04476202a15ee0e2e214080861bff12a51d98fba"
-  integrity sha512-Oo82gJDAVcaMdi3nuoKFavkIHBRVqQ1qvMb+9LHk/cF4P6B2m8aP04hGf7oL6wZ9BuGwX1onlLhpuoofSyoQDQ==
-  dependencies:
-    ansi-styles "^3.2.1"
-    chalk "^2.4.1"
-    cross-spawn "^6.0.5"
-    memorystream "^0.3.1"
-    minimatch "^3.0.4"
-    pidtree "^0.3.0"
-    read-pkg "^3.0.0"
-    shell-quote "^1.6.1"
-    string.prototype.padend "^3.0.0"
-
-object-inspect@^1.12.3, object-inspect@^1.9.0:
-  version "1.12.3"
-  resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.3.tgz#ba62dffd67ee256c8c086dfae69e016cd1f198b9"
-  integrity sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==
-
-object-keys@^1.1.1:
-  version "1.1.1"
-  resolved "https://registry.yarnpkg.com/object-keys/-/object-keys-1.1.1.tgz#1c47f272df277f3b1daf061677d9c82e2322c60e"
-  integrity sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==
-
-object.assign@^4.1.4:
-  version "4.1.4"
-  resolved "https://registry.yarnpkg.com/object.assign/-/object.assign-4.1.4.tgz#9673c7c7c351ab8c4d0b516f4343ebf4dfb7799f"
-  integrity sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==
-  dependencies:
-    call-bind "^1.0.2"
-    define-properties "^1.1.4"
-    has-symbols "^1.0.3"
-    object-keys "^1.1.1"
-
-parse-json@^4.0.0:
-  version "4.0.0"
-  resolved "https://registry.yarnpkg.com/parse-json/-/parse-json-4.0.0.tgz#be35f5425be1f7f6c747184f98a788cb99477ee0"
-  integrity sha512-aOIos8bujGN93/8Ox/jPLh7RwVnPEysynVFE+fQZyg6jKELEHwzgKdLRFHUgXJL6kylijVSBC4BvN9OmsB48Rw==
-  dependencies:
-    error-ex "^1.3.1"
-    json-parse-better-errors "^1.0.1"
-
-path-key@^2.0.1:
-  version "2.0.1"
-  resolved "https://registry.yarnpkg.com/path-key/-/path-key-2.0.1.tgz#411cadb574c5a140d3a4b1910d40d80cc9f40b40"
-  integrity sha512-fEHGKCSmUSDPv4uoj8AlD+joPlq3peND+HRYyxFz4KPw4z926S/b8rIuFs2FYJg3BwsxJf6A9/3eIdLaYC+9Dw==
-
-path-parse@^1.0.7:
-  version "1.0.7"
-  resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735"
-  integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==
-
-path-type@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f"
-  integrity sha512-T2ZUsdZFHgA3u4e5PfPbjd7HDDpxPnQb5jN0SrDsjNSuVXHJqtwTnWqG0B1jZrgmJ/7lj1EmVIByWt1gxGkWvg==
-  dependencies:
-    pify "^3.0.0"
-
-pidtree@^0.3.0:
-  version "0.3.1"
-  resolved "https://registry.yarnpkg.com/pidtree/-/pidtree-0.3.1.tgz#ef09ac2cc0533df1f3250ccf2c4d366b0d12114a"
-  integrity sha512-qQbW94hLHEqCg7nhby4yRC7G2+jYHY4Rguc2bjw7Uug4GIJuu1tvf2uHaZv5Q8zdt+WKJ6qK1FOI6amaWUo5FA==
-
-pify@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/pify/-/pify-3.0.0.tgz#e5a4acd2c101fdf3d9a4d07f0dbc4db49dd28176"
-  integrity sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==
-
-read-pkg@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-3.0.0.tgz#9cbc686978fee65d16c00e2b19c237fcf6e38389"
-  integrity sha512-BLq/cCO9two+lBgiTYNqD6GdtK8s4NpaWrl6/rCO9w0TUS8oJl7cmToOZfRYllKTISY6nt1U7jQ53brmKqY6BA==
-  dependencies:
-    load-json-file "^4.0.0"
-    normalize-package-data "^2.3.2"
-    path-type "^3.0.0"
-
-regexp.prototype.flags@^1.4.3:
-  version "1.5.0"
-  resolved "https://registry.yarnpkg.com/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz#fe7ce25e7e4cca8db37b6634c8a2c7009199b9cb"
-  integrity sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==
-  dependencies:
-    call-bind "^1.0.2"
-    define-properties "^1.2.0"
-    functions-have-names "^1.2.3"
-
-resolve@^1.10.0:
-  version "1.22.2"
-  resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.2.tgz#0ed0943d4e301867955766c9f3e1ae6d01c6845f"
-  integrity sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==
-  dependencies:
-    is-core-module "^2.11.0"
-    path-parse "^1.0.7"
-    supports-preserve-symlinks-flag "^1.0.0"
-
-safe-regex-test@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/safe-regex-test/-/safe-regex-test-1.0.0.tgz#793b874d524eb3640d1873aad03596db2d4f2295"
-  integrity sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==
-  dependencies:
-    call-bind "^1.0.2"
-    get-intrinsic "^1.1.3"
-    is-regex "^1.1.4"
-
-"semver@2 || 3 || 4 || 5", semver@^5.5.0:
-  version "5.7.1"
-  resolved "https://registry.yarnpkg.com/semver/-/semver-5.7.1.tgz#a954f931aeba508d307bbf069eff0c01c96116f7"
-  integrity sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==
-
-shebang-command@^1.2.0:
-  version "1.2.0"
-  resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-1.2.0.tgz#44aac65b695b03398968c39f363fee5deafdf1ea"
-  integrity sha512-EV3L1+UQWGor21OmnvojK36mhg+TyIKDh3iFBKBohr5xeXIhNBcx8oWdgkTEEQ+BEFFYdLRuqMfd5L84N1V5Vg==
-  dependencies:
-    shebang-regex "^1.0.0"
-
-shebang-regex@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/shebang-regex/-/shebang-regex-1.0.0.tgz#da42f49740c0b42db2ca9728571cb190c98efea3"
-  integrity sha512-wpoSFAxys6b2a2wHZ1XpDSgD7N9iVjg29Ph9uV/uaP9Ex/KXlkTZTeddxDPSYQpgvzKLGJke2UU0AzoGCjNIvQ==
-
-shell-quote@^1.6.1:
-  version "1.8.1"
-  resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.8.1.tgz#6dbf4db75515ad5bac63b4f1894c3a154c766680"
-  integrity sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA==
-
-side-channel@^1.0.4:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.4.tgz#efce5c8fdc104ee751b25c58d4290011fa5ea2cf"
-  integrity sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==
-  dependencies:
-    call-bind "^1.0.0"
-    get-intrinsic "^1.0.2"
-    object-inspect "^1.9.0"
-
-spdx-correct@^3.0.0:
-  version "3.2.0"
-  resolved "https://registry.yarnpkg.com/spdx-correct/-/spdx-correct-3.2.0.tgz#4f5ab0668f0059e34f9c00dce331784a12de4e9c"
-  integrity sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==
-  dependencies:
-    spdx-expression-parse "^3.0.0"
-    spdx-license-ids "^3.0.0"
-
-spdx-exceptions@^2.1.0:
-  version "2.3.0"
-  resolved "https://registry.yarnpkg.com/spdx-exceptions/-/spdx-exceptions-2.3.0.tgz#3f28ce1a77a00372683eade4a433183527a2163d"
-  integrity sha512-/tTrYOC7PPI1nUAgx34hUpqXuyJG+DTHJTnIULG4rDygi4xu/tfgmq1e1cIRwRzwZgo4NLySi+ricLkZkw4i5A==
-
-spdx-expression-parse@^3.0.0:
-  version "3.0.1"
-  resolved "https://registry.yarnpkg.com/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz#cf70f50482eefdc98e3ce0a6833e4a53ceeba679"
-  integrity sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==
-  dependencies:
-    spdx-exceptions "^2.1.0"
-    spdx-license-ids "^3.0.0"
-
-spdx-license-ids@^3.0.0:
-  version "3.0.13"
-  resolved "https://registry.yarnpkg.com/spdx-license-ids/-/spdx-license-ids-3.0.13.tgz#7189a474c46f8d47c7b0da4b987bb45e908bd2d5"
-  integrity sha512-XkD+zwiqXHikFZm4AX/7JSCXA98U5Db4AFd5XUg/+9UNtnH75+Z9KxtpYiJZx36mUDVOwH83pl7yvCer6ewM3w==
-
-string.prototype.padend@^3.0.0:
-  version "3.1.4"
-  resolved "https://registry.yarnpkg.com/string.prototype.padend/-/string.prototype.padend-3.1.4.tgz#2c43bb3a89eb54b6750de5942c123d6c98dd65b6"
-  integrity sha512-67otBXoksdjsnXXRUq+KMVTdlVRZ2af422Y0aTyTjVaoQkGr3mxl2Bc5emi7dOQ3OGVVQQskmLEWwFXwommpNw==
-  dependencies:
-    call-bind "^1.0.2"
-    define-properties "^1.1.4"
-    es-abstract "^1.20.4"
-
-string.prototype.trim@^1.2.7:
-  version "1.2.7"
-  resolved "https://registry.yarnpkg.com/string.prototype.trim/-/string.prototype.trim-1.2.7.tgz#a68352740859f6893f14ce3ef1bb3037f7a90533"
-  integrity sha512-p6TmeT1T3411M8Cgg9wBTMRtY2q9+PNy9EV1i2lIXUN/btt763oIfxwN3RR8VU6wHX8j/1CFy0L+YuThm6bgOg==
-  dependencies:
-    call-bind "^1.0.2"
-    define-properties "^1.1.4"
-    es-abstract "^1.20.4"
-
-string.prototype.trimend@^1.0.6:
-  version "1.0.6"
-  resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz#c4a27fa026d979d79c04f17397f250a462944533"
-  integrity sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==
-  dependencies:
-    call-bind "^1.0.2"
-    define-properties "^1.1.4"
-    es-abstract "^1.20.4"
-
-string.prototype.trimstart@^1.0.6:
-  version "1.0.6"
-  resolved "https://registry.yarnpkg.com/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz#e90ab66aa8e4007d92ef591bbf3cd422c56bdcf4"
-  integrity sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==
-  dependencies:
-    call-bind "^1.0.2"
-    define-properties "^1.1.4"
-    es-abstract "^1.20.4"
-
-strip-bom@^3.0.0:
-  version "3.0.0"
-  resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3"
-  integrity sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==
-
-supports-color@^5.3.0:
-  version "5.5.0"
-  resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-5.5.0.tgz#e2e69a44ac8772f78a1ec0b35b689df6530efc8f"
-  integrity sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==
-  dependencies:
-    has-flag "^3.0.0"
-
-supports-preserve-symlinks-flag@^1.0.0:
-  version "1.0.0"
-  resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09"
-  integrity sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==
-
-typed-array-length@^1.0.4:
-  version "1.0.4"
-  resolved "https://registry.yarnpkg.com/typed-array-length/-/typed-array-length-1.0.4.tgz#89d83785e5c4098bec72e08b319651f0eac9c1bb"
-  integrity sha512-KjZypGq+I/H7HI5HlOoGHkWUUGq+Q0TPhQurLbyrVrvnKTBgzLhIJ7j6J/XTQOi0d1RjyZ0wdas8bKs2p0x3Ng==
-  dependencies:
-    call-bind "^1.0.2"
-    for-each "^0.3.3"
-    is-typed-array "^1.1.9"
-
-typescript@^4.6.2:
-  version "4.9.5"
-  resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.9.5.tgz#095979f9bcc0d09da324d58d03ce8f8374cbe65a"
-  integrity sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==
-
-unbox-primitive@^1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/unbox-primitive/-/unbox-primitive-1.0.2.tgz#29032021057d5e6cdbd08c5129c226dff8ed6f9e"
-  integrity sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==
-  dependencies:
-    call-bind "^1.0.2"
-    has-bigints "^1.0.2"
-    has-symbols "^1.0.3"
-    which-boxed-primitive "^1.0.2"
-
-validate-npm-package-license@^3.0.1:
-  version "3.0.4"
-  resolved "https://registry.yarnpkg.com/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz#fc91f6b9c7ba15c857f4cb2c5defeec39d4f410a"
-  integrity sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==
-  dependencies:
-    spdx-correct "^3.0.0"
-    spdx-expression-parse "^3.0.0"
-
-which-boxed-primitive@^1.0.2:
-  version "1.0.2"
-  resolved "https://registry.yarnpkg.com/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz#13757bc89b209b049fe5d86430e21cf40a89a8e6"
-  integrity sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==
-  dependencies:
-    is-bigint "^1.0.1"
-    is-boolean-object "^1.1.0"
-    is-number-object "^1.0.4"
-    is-string "^1.0.5"
-    is-symbol "^1.0.3"
-
-which-typed-array@^1.1.9:
-  version "1.1.9"
-  resolved "https://registry.yarnpkg.com/which-typed-array/-/which-typed-array-1.1.9.tgz#307cf898025848cf995e795e8423c7f337efbde6"
-  integrity sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==
-  dependencies:
-    available-typed-arrays "^1.0.5"
-    call-bind "^1.0.2"
-    for-each "^0.3.3"
-    gopd "^1.0.1"
-    has-tostringtag "^1.0.0"
-    is-typed-array "^1.1.10"
-
-which@^1.2.9:
-  version "1.3.1"
-  resolved "https://registry.yarnpkg.com/which/-/which-1.3.1.tgz#a45043d54f5805316da8d62f9f50918d3da70b0a"
-  integrity sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==
-  dependencies:
-    isexe "^2.0.0"
diff --git a/sdk/python/.gitignore b/sdk/python/.gitignore
deleted file mode 100644
index 778e0817..00000000
--- a/sdk/python/.gitignore
+++ /dev/null
@@ -1,5 +0,0 @@
-venv
-dist
-MANIFEST.in
-pvrecorder
-pvrecorder.egg-info
\ No newline at end of file
diff --git a/sdk/python/README.md b/sdk/python/README.md
deleted file mode 100644
index 02ba5f85..00000000
--- a/sdk/python/README.md
+++ /dev/null
@@ -1,84 +0,0 @@
-# PV_Recorder
-
-A cross-platform audio recorder to read one channel and 16kHz samples.
-
-## Requirements
-
-- Python 3
-
-## Compatibility
-
-- Windows (amd64)
-- macOS 
-    - x86_64
-    - arm64
-- Linux (x86_64)
-- Raspberry Pi:
-    - Zero
-    - 2
-    - 3 (32 and 64 bit)
-    - 4 (32 and 64 bit)
-- NVIDIA Jetson Nano
-- BeagleBone
-
-## Installation
-
-pip3 install pvrecorder
-
-## Usage
-
-Getting the list of input devices does not require an instance:
-
-```python
-from pvrecorder import PvRecorder
-
-devices = PvRecorder.get_audio_devices()
-```
-
-To start recording initialize the instance and run start:
-
-```python
-from pvrecorder import PvRecorder
-
-recorder = PvRecorder(device_index=-1, frame_length=512)
-recorder.start()
-```
-
-Get the pcm frames by calling the read function:
-
-```python
-pcm = recorder.read()
-# do something with pcm
-```
-
-To stop recording just run stop on the instance:
-
-```python
-recorder.stop()
-```
-
-Once you are done, free the used resources. You do not have to call stop before delete:
-
-```python
-recorder.delete()
-```
-
-### Demo
-
-For more detailed information on how to use the pv_recorder python sdk, please that a look at [demo.py](demo.py).
-
-In the following instructions, we will refer to  `{AUDIO_DEVICE_INDEX}` as the index of the audio device to use, and `{OUTPUT_PATH}` as the path to save the audio data in `wav` format.
-
-`{AUDIO_DEVICE_INDEX}` defaults to -1 and `{OUTPUT_PATH}` can be empty if you wish to not save any data.
-
-To show the available audio devices run:
-
-```console
-python3 demo.py --show_audio_devices
-```
-
-To run audio recorder run:
-
-```console
-python3 demo.py --audio_device_index {AUDIO_DEVICE_INDEX} --output_path {OUTPUT_PATH}
-```
diff --git a/sdk/python/pvrecorder.py b/sdk/python/pvrecorder.py
deleted file mode 100644
index 7adbbbe2..00000000
--- a/sdk/python/pvrecorder.py
+++ /dev/null
@@ -1,204 +0,0 @@
-#
-# Copyright 2021-2022 Picovoice Inc.
-#
-# You may not use this file except in compliance with the license. A copy of the license is located in the "LICENSE"
-# file accompanying this source.
-#
-# Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
-# an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations under the License.
-#
-
-import os
-import platform
-import subprocess
-from ctypes import *
-from enum import Enum
-
-CALLBACK = CFUNCTYPE(None, POINTER(c_int16))
-
-
-class PvRecorder(object):
-    """
-    A cross-platform Python SDK for PvRecorder to process audio recordings. It lists the available
-    input devices. Also given the audio device index and frame_length, processes the frame and runs
-    a callback each time a frame_length is given.
-    """
-
-    class PvRecorderStatuses(Enum):
-        SUCCESS = 0
-        OUT_OF_MEMORY = 1
-        INVALID_ARGUMENT = 2
-        INVALID_STATE = 3
-        BACKEND_ERROR = 4
-        DEVICE_ALREADY_INITIALIZED = 5
-        DEVICE_NOT_INITIALIZED = 6
-        IO_ERROR = 7
-        RUNTIME_ERROR = 8
-
-    _PVRECORDER_STATUS_TO_EXCEPTION = {
-        PvRecorderStatuses.OUT_OF_MEMORY: MemoryError,
-        PvRecorderStatuses.INVALID_ARGUMENT: ValueError,
-        PvRecorderStatuses.INVALID_STATE: ValueError,
-        PvRecorderStatuses.BACKEND_ERROR: SystemError,
-        PvRecorderStatuses.DEVICE_ALREADY_INITIALIZED: ValueError,
-        PvRecorderStatuses.DEVICE_NOT_INITIALIZED: ValueError,
-        PvRecorderStatuses.IO_ERROR: IOError,
-        PvRecorderStatuses.RUNTIME_ERROR: RuntimeError
-    }
-
-    class CPvRecorder(Structure):
-        pass
-
-    def __init__(self, device_index, frame_length, buffer_size_msec=1000, log_overflow=True, log_silence=True):
-        """
-        Constructor
-
-        :param device_index: The device index of the audio device to use. A (-1) will choose default audio device.
-        :param frame_length: The length of the frame to receive at each read call.
-        :param buffer_size_msec: Time in milliseconds indicating the total amount of time to store audio frames.
-        :param log_overflow: Boolean variable to indicate to log overflow warnings. A log warning should indicate
-        read is not being called fast enough from the callers point.
-        :param log_silence: Boolean variable to enable silence logs. This will log when continuous audio buffers
-        are detected as silent.
-        """
-
-        init_func = self._LIBRARY.pv_recorder_init
-        init_func.argtypes = [
-            c_int32,
-            c_int32,
-            c_int32,
-            c_bool,
-            c_bool,
-            POINTER(POINTER(self.CPvRecorder))
-        ]
-        init_func.restype = self.PvRecorderStatuses
-
-        self._handle = POINTER(self.CPvRecorder)()
-        self._frame_length = frame_length
-
-        status = init_func(device_index, frame_length, buffer_size_msec, log_overflow, log_silence, byref(self._handle))
-        if status is not self.PvRecorderStatuses.SUCCESS:
-            raise self._PVRECORDER_STATUS_TO_EXCEPTION[status]("Failed to initialize pv_recorder.")
-
-        self._delete_func = self._LIBRARY.pv_recorder_delete
-        self._delete_func.argtypes = [POINTER(self.CPvRecorder)]
-        self._delete_func.restype = None
-
-        self._start_func = self._LIBRARY.pv_recorder_start
-        self._start_func.argtypes = [POINTER(self.CPvRecorder)]
-        self._start_func.restype = self.PvRecorderStatuses
-
-        self._stop_func = self._LIBRARY.pv_recorder_stop
-        self._stop_func.argtypes = [POINTER(self.CPvRecorder)]
-        self._stop_func.restype = self.PvRecorderStatuses
-
-        self._read_func = self._LIBRARY.pv_recorder_read
-        self._read_func.argtypes = [POINTER(self.CPvRecorder), POINTER(c_int16)]
-        self._read_func.restype = self.PvRecorderStatuses
-
-        self._get_selected_device_func = self._LIBRARY.pv_recorder_get_selected_device
-        self._get_selected_device_func.argtypes = [POINTER(self.CPvRecorder)]
-        self._get_selected_device_func.restype = c_char_p
-
-        self._version_func = PvRecorder._LIBRARY.pv_recorder_version
-        self._version_func.argtypes = None
-        self._version_func.restype = c_char_p
-
-    def delete(self):
-        """Releases any resources used by PV_Recorder."""
-
-        self._delete_func(self._handle)
-
-    def start(self):
-        """Starts recording audio."""
-
-        status = self._start_func(self._handle)
-        if status is not self.PvRecorderStatuses.SUCCESS:
-            raise self._PVRECORDER_STATUS_TO_EXCEPTION[status]("Failed to start device.")
-
-    def stop(self):
-        """Stops recording audio."""
-
-        status = self._stop_func(self._handle)
-        if status is not self.PvRecorderStatuses.SUCCESS:
-            raise self._PVRECORDER_STATUS_TO_EXCEPTION[status]("Failed to stop device.")
-
-    def read(self):
-        """Reads audio frames and returns a list containing the audio frames."""
-
-        pcm = (c_int16 * self._frame_length)()
-        status = self._read_func(self._handle, pcm)
-        if status is not self.PvRecorderStatuses.SUCCESS:
-            raise self._PVRECORDER_STATUS_TO_EXCEPTION[status]("Failed to read from device.")
-        return pcm[0:self._frame_length]
-
-    @property
-    def selected_device(self):
-        """Gets the current selected device."""
-
-        device_name = self._get_selected_device_func(self._handle)
-        return device_name.decode('utf-8')
-
-    @property
-    def version(self):
-        """Gets the current version of pv_recorder library."""
-
-        version = self._version_func()
-        return version.decode('utf-8')
-
-    @staticmethod
-    def get_audio_devices():
-        """Gets the audio devices currently available on device.
-
-        :return: A list of strings, indicating the names of audio devices.
-        """
-
-        get_audio_devices_func = PvRecorder._LIBRARY.pv_recorder_get_audio_devices
-        get_audio_devices_func.argstype = [POINTER(c_int32), POINTER(POINTER(c_char_p))]
-        get_audio_devices_func.restype = PvRecorder.PvRecorderStatuses
-
-        free_device_list_func = PvRecorder._LIBRARY.pv_recorder_free_device_list
-        free_device_list_func.argstype = [c_int32, POINTER(c_char_p)]
-        free_device_list_func.restype = None
-
-        count = c_int32()
-        devices = POINTER(c_char_p)()
-
-        status = get_audio_devices_func(byref(count), byref(devices))
-        if status is not PvRecorder.PvRecorderStatuses.SUCCESS:
-            raise PvRecorder._PVRECORDER_STATUS_TO_EXCEPTION[status]("Failed to get device list")
-
-        device_list = list()
-        for i in range(count.value):
-            device_list.append(devices[i].decode('utf-8'))
-
-        free_device_list_func(count, devices)
-
-        return device_list
-
-    @staticmethod
-    def _lib_path():
-        """A helper function to get the library path."""
-
-        if platform.system() == "Windows":
-            script_path = os.path.join(os.path.dirname(__file__), "scripts", "platform.bat")
-        else:
-            script_path = os.path.join(os.path.dirname(__file__), "scripts", "platform.sh")
-
-        command = subprocess.run(script_path, stdout=subprocess.PIPE)
-
-        if command.returncode != 0:
-            raise RuntimeError("Current system is not supported.")
-        os_name, cpu = str(command.stdout.decode("utf-8")).split(" ")
-
-        if os_name == "windows":
-            extension = "dll"
-        elif os_name == "mac":
-            extension = "dylib"
-        else:
-            extension = "so"
-
-        return os.path.join(os.path.dirname(__file__), "lib", os_name, cpu, "libpv_recorder.%s" % extension)
-
-    _LIBRARY = cdll.LoadLibrary(_lib_path.__func__())
diff --git a/sdk/rust/README.md b/sdk/rust/README.md
deleted file mode 100644
index d0c89f38..00000000
--- a/sdk/rust/README.md
+++ /dev/null
@@ -1,89 +0,0 @@
-# PvRecorder Rust Binding
-
-A cross platform audio recorder that captures single-channel audio at a sample rate of 16kHz.
-
-## Requirements
-
-- Rust 1.54+
-
-## Compatibility
-
-- Windows (x86_64)
-- macOS (x86_64)
-- Linux (x86_64)
-- Raspberry Pi:
-    - Zero
-    - 2
-    - 3 (32 and 64 bit)
-    - 4 (32 and 64 bit)
-- NVIDIA Jetson Nano
-- BeagleBone
-
-## Installation
-
-To add the pvrecorder library into your app, add `pv_recorder` to your app's `Cargo.toml` manifest:
-```toml
-[dependencies]
-pv_recorder = "*"
-```
-
-## Usage
-
-Getting the list of input devices does not require an instance:
-
-```rust
-use pv_recorder::RecorderBuilder
-
-let audio_devices = RecorderBuilder::default().get_audio_devices()?;
-```
-
-To start recording initialize an instance using the builder and run `start`:
-
-```rust
-use pv_recorder::RecorderBuilder;
-
-let recorder = RecorderBuilder::new().init()?;
-recorder.start()?
-```
-
-Get the pcm frames by calling the read function:
-
-```rust
-loop {
-    let mut pcm_frame_buffer: [i16; FRAME_LENGTH] = [0; FRAME_LENGTH];
-    recorder.read(&mut pcm_frame_buffer)?;
-    // do something with pcm
-}
-```
-
-To stop recording just run stop on the instance:
-
-```rust
-recorder.stop()?;
-```
-
-### Demo
-
-For more detailed information on how to use the pv_recorder Rust sdk, see [examples/demo.rs](examples/demo.rs).
-
-In the following instructions, we will refer to  `{AUDIO_DEVICE_INDEX}` as the index of the audio device to use, and `{OUTPUT_PATH}` as the path to save the raw audio data
-
-`{AUDIO_DEVICE_INDEX}` defaults to -1 and `{OUTPUT_PATH}` can be empty if you wish to not save any data.
-
-To show the available audio devices run:
-
-```console
-cargo run --release --example demo -- --show_audio_devices
-```
-
-To run the audio recorder:
-
-```console
-cargo run --release --example demo -- --audio_device_index {AUDIO_DEVICE_INDEX} --output_path {OUTPUT_PATH}
-```
-
-See additional options by calling `-h/--help`:
-
-```console
-cargo run --release --example demo -- -h
-```
diff --git a/sdk/rust/src/pvrecorder.rs b/sdk/rust/src/pvrecorder.rs
deleted file mode 100644
index 561a75a2..00000000
--- a/sdk/rust/src/pvrecorder.rs
+++ /dev/null
@@ -1,430 +0,0 @@
-/*
-    Copyright 2021-2023 Picovoice Inc.
-
-    You may not use this file except in compliance with the license. A copy of the license is located in the "LICENSE"
-    file accompanying this source.
-
-    Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
-    an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
-    specific language governing permissions and limitations under the License.
-*/
-
-use std::ffi::CStr;
-use std::path::Path;
-use std::ptr::addr_of_mut;
-use std::sync::Arc;
-use std::{cmp::PartialEq, path::PathBuf};
-
-use libc::c_char;
-use libloading::{Library, Symbol};
-
-use crate::util::pv_library_path;
-
-#[cfg(unix)]
-use libloading::os::unix::Symbol as RawSymbol;
-#[cfg(windows)]
-use libloading::os::windows::Symbol as RawSymbol;
-
-#[repr(C)]
-struct CPvRecorder {}
-
-#[repr(C)]
-#[derive(PartialEq, Clone, Debug)]
-#[allow(non_camel_case_types)]
-pub enum PvRecorderStatus {
-    SUCCESS = 0,
-    OUT_OF_MEMORY = 1,
-    INVALID_ARGUMENT = 2,
-    INVALID_STATE = 3,
-    BACKEND_ERROR = 4,
-    DEVICE_ALREADY_INITIALIZED = 5,
-    DEVICE_NOT_INITIALIZED = 6,
-    IO_ERROR = 7,
-    RUNTIME_ERROR = 8,
-}
-
-type PvRecorderInitFn = unsafe extern "C" fn(
-    device_index: i32,
-    frame_length: i32,
-    buffer_size_msec: i32,
-    log_overflow: bool,
-    log_silence: bool,
-    object: *mut *mut CPvRecorder,
-) -> PvRecorderStatus;
-type PvRecorderDeleteFn = unsafe extern "C" fn(object: *mut CPvRecorder);
-type PvRecorderStartFn = unsafe extern "C" fn(object: *mut CPvRecorder) -> PvRecorderStatus;
-type PvRecorderStopFn = unsafe extern "C" fn(object: *mut CPvRecorder) -> PvRecorderStatus;
-type PvRecorderReadFn =
-    unsafe extern "C" fn(object: *mut CPvRecorder, pcm: *mut i16) -> PvRecorderStatus;
-type PvRecorderGetAudioDevicesFn =
-    unsafe extern "C" fn(count: *mut i32, devices: *mut *mut *mut c_char) -> PvRecorderStatus;
-type PvRecorderFreeDeviceList = unsafe extern "C" fn(count: i32, devices: *mut *mut c_char);
-
-#[derive(Clone, Debug)]
-pub enum RecorderErrorStatus {
-    LibraryError(PvRecorderStatus),
-    LibraryLoadError,
-    ArgumentError,
-    OtherError,
-}
-
-#[derive(Clone, Debug)]
-pub struct RecorderError {
-    status: RecorderErrorStatus,
-    message: String,
-}
-
-impl RecorderError {
-    pub fn new(status: RecorderErrorStatus, message: impl Into<String>) -> Self {
-        Self {
-            status,
-            message: message.into(),
-        }
-    }
-}
-
-impl std::fmt::Display for RecorderError {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        write!(f, "{}: {:?}", self.message, self.status)
-    }
-}
-
-impl std::error::Error for RecorderError {}
-
-const DEFAULT_DEVICE_INDEX: i32 = -1;
-const DEFAULT_FRAME_LENGTH: i32 = 512;
-const DEFAULT_MILLISECONDS: i32 = 1000;
-
-pub struct RecorderBuilder {
-    device_index: i32,
-    frame_length: i32,
-    buffer_size_msec: i32,
-    log_overflow: bool,
-    log_silence: bool,
-    library_path: PathBuf,
-}
-
-impl Default for RecorderBuilder {
-    fn default() -> Self {
-        Self::new()
-    }
-}
-
-impl RecorderBuilder {
-    pub fn new() -> Self {
-        Self {
-            device_index: DEFAULT_DEVICE_INDEX,
-            frame_length: DEFAULT_FRAME_LENGTH,
-            buffer_size_msec: DEFAULT_MILLISECONDS,
-            log_overflow: false,
-            log_silence: true,
-            library_path: pv_library_path(),
-        }
-    }
-
-    pub fn device_index(&mut self, device_index: i32) -> &mut Self {
-        self.device_index = device_index;
-        self
-    }
-
-    pub fn frame_length(&mut self, frame_length: i32) -> &mut Self {
-        self.frame_length = frame_length;
-        self
-    }
-
-    pub fn buffer_size_msec(&mut self, buffer_size_msec: i32) -> &mut Self {
-        self.buffer_size_msec = buffer_size_msec;
-        self
-    }
-
-    pub fn log_overflow(&mut self, log_overflow: bool) -> &mut Self {
-        self.log_overflow = log_overflow;
-        self
-    }
-
-    pub fn log_silence(&mut self, log_silence: bool) -> &mut Self {
-        self.log_silence = log_silence;
-        self
-    }
-
-    pub fn library_path(&mut self, library_path: &Path) -> &mut Self {
-        self.library_path = library_path.into();
-        self
-    }
-
-    pub fn init(&self) -> Result<Recorder, RecorderError> {
-        let recorder_inner = RecorderInner::init(
-            self.device_index,
-            self.frame_length,
-            self.buffer_size_msec,
-            self.log_overflow,
-            self.log_silence,
-            &self.library_path,
-        );
-        recorder_inner.map(|inner| Recorder {
-            inner: Arc::new(inner),
-        })
-    }
-
-    pub fn get_audio_devices(&self) -> Result<Vec<String>, RecorderError> {
-        RecorderInner::get_audio_devices(&self.library_path)
-    }
-}
-
-#[derive(Clone)]
-pub struct Recorder {
-    inner: Arc<RecorderInner>,
-}
-
-impl Recorder {
-    pub fn start(&self) -> Result<(), RecorderError> {
-        self.inner.start()
-    }
-
-    pub fn stop(&self) -> Result<(), RecorderError> {
-        self.inner.stop()
-    }
-
-    pub fn read(&self, buffer: &mut [i16]) -> Result<(), RecorderError> {
-        self.inner.read(buffer)
-    }
-
-    pub fn frame_length(&self) -> usize {
-        self.inner.frame_length() as usize
-    }
-}
-
-unsafe fn load_library_fn<T>(
-    library: &Library,
-    function_name: &[u8],
-) -> Result<RawSymbol<T>, RecorderError> {
-    library
-        .get(function_name)
-        .map(|s: Symbol<T>| s.into_raw())
-        .map_err(|err| {
-            RecorderError::new(
-                RecorderErrorStatus::LibraryLoadError,
-                format!(
-                    "Failed to load function symbol from pvrecorder library: {}",
-                    err
-                ),
-            )
-        })
-}
-
-fn check_fn_call_status(
-    status: PvRecorderStatus,
-    function_name: &str,
-) -> Result<(), RecorderError> {
-    match status {
-        PvRecorderStatus::SUCCESS => Ok(()),
-        _ => Err(RecorderError::new(
-            RecorderErrorStatus::LibraryError(status),
-            format!(
-                "Function '{}' in the pvrecorder library failed",
-                function_name
-            ),
-        )),
-    }
-}
-
-struct RecorderInnerVTable {
-    pv_recorder_delete: RawSymbol<PvRecorderDeleteFn>,
-    pv_recorder_start: RawSymbol<PvRecorderStartFn>,
-    pv_recorder_stop: RawSymbol<PvRecorderStopFn>,
-    pv_recorder_read: RawSymbol<PvRecorderReadFn>,
-    pv_recorder_get_audio_devices: RawSymbol<PvRecorderGetAudioDevicesFn>,
-    pv_recorder_free_device_list: RawSymbol<PvRecorderFreeDeviceList>,
-
-    _lib_guard: Library,
-}
-
-impl RecorderInnerVTable {
-    pub fn new(lib: Library) -> Result<Self, RecorderError> {
-        // SAFETY: the library will be hold by this struct and therefore the symbols can't outlive the library
-        unsafe {
-            Ok(Self {
-                pv_recorder_delete: load_library_fn(&lib, b"pv_recorder_delete")?,
-                pv_recorder_start: load_library_fn(&lib, b"pv_recorder_start")?,
-                pv_recorder_stop: load_library_fn(&lib, b"pv_recorder_stop")?,
-                pv_recorder_read: load_library_fn(&lib, b"pv_recorder_read")?,
-                pv_recorder_get_audio_devices: load_library_fn(
-                    &lib,
-                    b"pv_recorder_get_audio_devices",
-                )?,
-                pv_recorder_free_device_list: load_library_fn(
-                    &lib,
-                    b"pv_recorder_free_device_list",
-                )?,
-
-                _lib_guard: lib,
-            })
-        }
-    }
-}
-
-struct RecorderInner {
-    cpvrecorder: *mut CPvRecorder,
-    frame_length: i32,
-    vtable: RecorderInnerVTable,
-}
-
-impl RecorderInner {
-    pub fn init(
-        device_index: i32,
-        frame_length: i32,
-        buffer_size_msec: i32,
-        log_overflow: bool,
-        log_silence: bool,
-        library_path: &Path,
-    ) -> Result<Self, RecorderError> {
-        if device_index < -1 {
-            return Err(RecorderError::new(
-                RecorderErrorStatus::ArgumentError,
-                format!(
-                    "device_index value {} should be greater than zero",
-                    device_index
-                ),
-            ));
-        }
-
-        if frame_length < 0 {
-            return Err(RecorderError::new(
-                RecorderErrorStatus::ArgumentError,
-                format!(
-                    "frame_length value {} should be greater than zero",
-                    frame_length
-                ),
-            ));
-        }
-
-        if buffer_size_msec < 0 {
-            return Err(RecorderError::new(
-                RecorderErrorStatus::ArgumentError,
-                format!(
-                    "buffer_size_msec value {} should be greater than zero",
-                    buffer_size_msec
-                ),
-            ));
-        }
-
-        if buffer_size_msec < frame_length {
-            return Err(RecorderError::new(
-                RecorderErrorStatus::ArgumentError,
-                format!(
-                    "buffer_size_msec value {} should be greater than the frame length {}",
-                    buffer_size_msec, frame_length
-                ),
-            ));
-        }
-
-        let lib = unsafe { Library::new(library_path) }.map_err(|err| {
-            RecorderError::new(
-                RecorderErrorStatus::LibraryLoadError,
-                format!("Failed to load pvrecorder dynamic library: {}", err),
-            )
-        })?;
-
-        let mut cpvrecorder = std::ptr::null_mut();
-
-        // SAFETY: loading the library is safe, because we still have the lib handle
-        unsafe {
-            let pv_recorder_init = load_library_fn::<PvRecorderInitFn>(&lib, b"pv_recorder_init")?;
-
-            let status = pv_recorder_init(
-                device_index,
-                frame_length,
-                buffer_size_msec,
-                log_overflow,
-                log_silence,
-                addr_of_mut!(cpvrecorder),
-            );
-            check_fn_call_status(status, "pv_recorder_init")?;
-        }
-
-        Ok(Self {
-            cpvrecorder,
-            frame_length,
-            vtable: RecorderInnerVTable::new(lib)?,
-        })
-    }
-
-    fn start(&self) -> Result<(), RecorderError> {
-        let status = unsafe { (self.vtable.pv_recorder_start)(self.cpvrecorder) };
-        check_fn_call_status(status, "pv_recorder_start")
-    }
-
-    fn stop(&self) -> Result<(), RecorderError> {
-        let status = unsafe { (self.vtable.pv_recorder_stop)(self.cpvrecorder) };
-        check_fn_call_status(status, "pv_recorder_stop")
-    }
-
-    pub fn read(&self, pcm: &mut [i16]) -> Result<(), RecorderError> {
-        if pcm.len() < self.frame_length as usize {
-            return Err(RecorderError::new(
-                RecorderErrorStatus::ArgumentError,
-                format!(
-                    "PCM buffer needs to be at least the frame_size {}, currently {}",
-                    self.frame_length,
-                    pcm.len()
-                ),
-            ));
-        }
-
-        let status = unsafe { (self.vtable.pv_recorder_read)(self.cpvrecorder, pcm.as_mut_ptr()) };
-        check_fn_call_status(status, "pv_recorder_read")
-    }
-
-    pub fn get_audio_devices(library_path: &Path) -> Result<Vec<String>, RecorderError> {
-        let lib = unsafe { Library::new(library_path) }.map_err(|err| {
-            RecorderError::new(
-                RecorderErrorStatus::LibraryLoadError,
-                format!("Failed to load pvrecorder dynamic library: {}", err),
-            )
-        })?;
-
-        let vtable = RecorderInnerVTable::new(lib)?;
-
-        let mut devices = Vec::new();
-        let mut count = 0;
-
-        unsafe {
-            let mut devices_ptr: *mut c_char = std::ptr::null_mut();
-            let mut devices_ptr_ptr: *mut *mut c_char = addr_of_mut!(devices_ptr);
-
-            let status = (vtable.pv_recorder_get_audio_devices)(
-                addr_of_mut!(count),
-                addr_of_mut!(devices_ptr_ptr),
-            );
-            check_fn_call_status(status, "pv_recorder_get_audio_devices")?;
-
-            for i in 0..count as usize {
-                let device = CStr::from_ptr(*devices_ptr_ptr.add(i));
-                devices.push(String::from(device.to_str().map_err(|_| {
-                    RecorderError::new(
-                        RecorderErrorStatus::OtherError,
-                        "Failed to convert device strings",
-                    )
-                })?));
-            }
-
-            (vtable.pv_recorder_free_device_list)(count, devices_ptr_ptr);
-        }
-        Ok(devices)
-    }
-
-    pub fn frame_length(&self) -> i32 {
-        self.frame_length
-    }
-}
-
-unsafe impl Send for RecorderInner {}
-unsafe impl Sync for RecorderInner {}
-
-impl Drop for RecorderInner {
-    fn drop(&mut self) {
-        unsafe {
-            (self.vtable.pv_recorder_delete)(self.cpvrecorder);
-        }
-    }
-}


Test patch:
diff --git a/binding/go/pv_recorder_test.go b/binding/go/pv_recorder_test.go
new file mode 100644
index 00000000..99502413
--- /dev/null
+++ b/binding/go/pv_recorder_test.go
@@ -0,0 +1,164 @@
+// Copyright 2023 Picovoice Inc.
+//
+// You may not use this file except in compliance with the license. A copy of the license is
+// located in the "LICENSE" file accompanying this source.
+//
+// Unless required by applicable law or agreed to in writing, software distributed under the
+// License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+// express or implied. See the License for the specific language governing permissions and
+// limitations under the License.
+
+package pvrecorder
+
+import (
+	"os"
+	"testing"
+)
+
+func TestMain(m *testing.M) {
+	os.Exit(m.Run())
+}
+
+func TestInvalidDeviceIndex(t *testing.T) {
+	recorder := NewPvRecorder(512)
+	recorder.DeviceIndex = -2
+
+	err := recorder.Init()
+	if err == nil {
+		t.Fatalf("Init succeeded with invalid device index")
+	} else {
+		recorder.Delete()
+	}
+}
+
+func TestInvalidFrameLength(t *testing.T) {
+	recorder := NewPvRecorder(0)
+
+	err := recorder.Init()
+	if err == nil {
+		t.Fatalf("Init succeeded with invalid frame length")
+	} else {
+		recorder.Delete()
+	}
+}
+
+func TestInvalidBufferedFramesCount(t *testing.T) {
+	recorder := NewPvRecorder(512)
+	recorder.BufferedFramesCount = 0
+
+	err := recorder.Init()
+	if err == nil {
+		t.Fatalf("Init succeeded with invalid buffered frames count")
+	} else {
+		recorder.Delete()
+	}
+}
+
+func TestStartStop(t *testing.T) {
+	recorder := NewPvRecorder(512)
+	recorder.DeviceIndex = 0
+
+	err := recorder.Init()
+	if err != nil {
+		t.Fatalf("Init failed: %v", err)
+	}
+	defer recorder.Delete()
+
+	err = recorder.Start()
+	if err != nil {
+		t.Fatalf("Start failed: %v", err)
+	}
+
+	for i := 0; i < 5; i++ {
+		frame, err := recorder.Read()
+		if err != nil {
+			t.Fatalf("Read failed: %v", err)
+		}
+		if len(frame) != recorder.FrameLength {
+			t.Fatalf("Frame length is not equal")
+		}
+	}
+}
+
+func TestSetDebugLogging(t *testing.T) {
+	recorder := NewPvRecorder(512)
+	recorder.DeviceIndex = 0
+
+	err := recorder.Init()
+	if err != nil {
+		t.Fatalf("Init failed: %v", err)
+	}
+	defer recorder.Delete()
+
+	recorder.SetDebugLogging(true)
+	recorder.SetDebugLogging(false)
+}
+
+func TestGetIsRecording(t *testing.T) {
+	recorder := NewPvRecorder(512)
+	recorder.DeviceIndex = 0
+
+	err := recorder.Init()
+	if err != nil {
+		t.Fatalf("Init failed: %v", err)
+	}
+	defer recorder.Delete()
+
+	err = recorder.Start()
+	if err != nil {
+		t.Fatalf("Start failed: %v", err)
+	}
+
+	isRecording := recorder.IsRecording()
+	if !isRecording {
+		t.Fatalf("Recorder should be recording audio")
+	}
+
+	err = recorder.Stop()
+	if err != nil {
+		t.Fatalf("Stop failed: %v", err)
+	}
+
+	isRecording = recorder.IsRecording()
+	if isRecording {
+		t.Fatalf("Recorder should have stopped recording audio")
+	}
+}
+
+func TestGetSelectedDevice(t *testing.T) {
+	recorder := NewPvRecorder(512)
+	recorder.DeviceIndex = 0
+
+	err := recorder.Init()
+	if err != nil {
+		t.Fatalf("Init failed: %v", err)
+	}
+	defer recorder.Delete()
+
+	device := recorder.GetSelectedDevice()
+
+	if len(device) == 0 {
+		t.Fatalf("Failed to get selected device")
+	}
+}
+
+func TestGetAvailableDevices(t *testing.T) {
+	_, err := GetAvailableDevices()
+	if err != nil {
+		t.Fatalf("Failed to get available devices: %v", err)
+	}
+}
+
+func TestVersion(t *testing.T) {
+	version := Version
+	if len(version) == 0 {
+		t.Fatalf("Failed to get version")
+	}
+}
+
+func TestSampleRate(t *testing.T) {
+	sampleRate := SampleRate
+	if sampleRate <= 0 {
+		t.Fatalf("Failed to get sample rate")
+	}
+}
diff --git a/binding/nodejs/test/pv_recorder.test.ts b/binding/nodejs/test/pv_recorder.test.ts
new file mode 100644
index 00000000..2f152a8d
--- /dev/null
+++ b/binding/nodejs/test/pv_recorder.test.ts
@@ -0,0 +1,102 @@
+import { PvRecorder } from "../src";
+
+describe("Test PvRecorder", () => {
+  test("invalid device index", () => {
+    const f = () => {
+      new PvRecorder(512, -2);
+    }
+
+    expect(f).toThrow(Error);
+  });
+
+  test("invalid frame length", () => {
+    const f = () => {
+      new PvRecorder(0, 0);
+    }
+
+    expect(f).toThrow(Error);
+  });
+
+  test("invalid buffered frames count", () => {
+    const f = () => {
+      new PvRecorder(512, 0, 0);
+    }
+
+    expect(f).toThrow(Error);
+  });
+
+  test("start stop", async () => {
+    const recorder = new PvRecorder(512, 0);
+    recorder.start();
+
+    for (let i = 0; i < 5; i++) {
+      const frames = recorder.readSync();
+      expect(frames.length).toEqual(recorder.frameLength);
+    }
+
+    for (let i = 0; i < 5; i++) {
+      const frames = await recorder.read();
+      expect(frames.length).toEqual(recorder.frameLength);
+    }
+
+    recorder.release();
+  });
+
+  test("set debug logging", () => {
+    const recorder = new PvRecorder(512, 0);
+    recorder.setDebugLogging(true);
+    recorder.setDebugLogging(false);
+    recorder.release();
+  });
+
+  test("is recording", () => {
+    const recorder = new PvRecorder(512, 0);
+
+    recorder.start();
+    expect(recorder.isRecording).toBeTruthy();
+
+    recorder.stop();
+    expect(recorder.isRecording).toBeFalsy();
+
+    recorder.release();
+  });
+
+  test("get selected device", () => {
+    const recorder = new PvRecorder(512, 0);
+    const device = recorder.getSelectedDevice();
+
+    expect(device).toBeDefined();
+    expect(typeof device).toBe("string");
+
+    recorder.release();
+  });
+
+  test("get available devices", () => {
+    const devices = PvRecorder.getAvailableDevices();
+
+    for (const device of devices) {
+      expect(device).toBeDefined();
+      expect(typeof device).toBe("string");
+    }
+  });
+
+  test("version", () => {
+    const recorder = new PvRecorder(512, 0);
+
+    expect(recorder.version).toBeDefined();
+    expect(typeof recorder.version).toBe("string");
+    expect(recorder.version.length).toBeGreaterThan(0);
+
+    recorder.release();
+  });
+
+  test("sample rate", () => {
+    const recorder = new PvRecorder(512, 0);
+
+    expect(recorder.sampleRate).toBeDefined();
+    expect(typeof recorder.sampleRate).toBe("number");
+    expect(recorder.sampleRate).toBeGreaterThan(0);
+
+    recorder.release();
+  });
+});
diff --git a/binding/python/test_pv_recorder.py b/binding/python/test_pv_recorder.py
new file mode 100644
index 00000000..9e61d38f
--- /dev/null
+++ b/binding/python/test_pv_recorder.py
@@ -0,0 +1,91 @@
+#
+#    Copyright 2023 Picovoice Inc.
+#
+#    You may not use this file except in compliance with the license. A copy of the license is located in the "LICENSE"
+#    file accompanying this source.
+#
+#    Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+#    an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+#    specific language governing permissions and limitations under the License.
+#
+
+import os.path
+import unittest
+
+from _pvrecorder import *
+
+
+class PvRecorderTestCase(unittest.TestCase):
+    @classmethod
+    def setUpClass(cls):
+        PvRecorder.set_default_library_path(os.path.join('..', '..'))
+
+    def test_invalid_device_index(self):
+        with self.assertRaises(ValueError):
+            _ = PvRecorder(512, -2)
+
+    def test_invalid_frame_length(self):
+        with self.assertRaises(ValueError):
+            _ = PvRecorder(-1, 0)
+
+    def test_invalid_buffered_frame_count(self):
+        with self.assertRaises(ValueError):
+            _ = PvRecorder(512, 0, 0)
+
+    def test_start_stop(self):
+        recorder = PvRecorder(512, 0)
+        recorder.start()
+        for i in range(5):
+            frame = recorder.read()
+            self.assertEqual(len(frame), 512)
+        recorder.stop()
+        recorder.delete()
+
+    def test_set_debug_logging(self):
+        recorder = PvRecorder(512, 0)
+        recorder.set_debug_logging(True)
+        recorder.set_debug_logging(False)
+        self.assertIsNotNone(recorder)
+        recorder.delete()
+
+    def test_is_recording(self):
+        recorder = PvRecorder(512, 0)
+        recorder.start()
+        self.assertTrue(recorder.is_recording)
+        recorder.stop()
+        self.assertFalse(recorder.is_recording)
+        recorder.delete()
+
+    def test_selected_device(self):
+        recorder = PvRecorder(512, 0)
+        device = recorder.selected_device
+        self.assertIsNotNone(device)
+        self.assertIsInstance(device, str)
+        recorder.delete()
+
+    def test_get_available_devices(self):
+        recorder = PvRecorder(512, 0)
+        devices = recorder.get_available_devices()
+        self.assertIsNotNone(devices)
+        for device in devices:
+            self.assertIsNotNone(device)
+            self.assertIsInstance(device, str)
+        recorder.delete()
+
+    def test_version(self):
+        recorder = PvRecorder(512, 0)
+        version = recorder.version
+        self.assertGreater(len(version), 0)
+        self.assertIsInstance(version, str)
+        recorder.delete()
+
+    def test_sample_rate(self):
+        recorder = PvRecorder(512, 0)
+        sample_rate = recorder.sample_rate
+        self.assertGreater(sample_rate, 0)
+        self.assertIsInstance(sample_rate, int)
+        recorder.delete()
+
+
+if __name__ == '__main__':
+    unittest.main()
diff --git a/binding/rust/tests/pvrecorder_tests.rs b/binding/rust/tests/pvrecorder_tests.rs
new file mode 100644
index 00000000..f6890c27
--- /dev/null
+++ b/binding/rust/tests/pvrecorder_tests.rs
@@ -0,0 +1,59 @@
+/*
+    Copyright 2023 Picovoice Inc.
+
+    You may not use this file except in compliance with the license. A copy of the license is located in the "LICENSE"
+    file accompanying this source.
+
+    Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+    an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+    specific language governing permissions and limitations under the License.
+*/
+
+#[cfg(test)]
+mod tests {
+    use pv_recorder::{PvRecorderBuilder, PvRecorderError};
+
+    #[test]
+    fn test_init() -> Result<(), PvRecorderError> {
+        let recorder = PvRecorderBuilder::new(512).device_index(0).init()?;
+        assert!(recorder.sample_rate() > 0);
+        assert!(recorder.selected_device().len() > 0);
+        assert!(recorder.version().len() > 0);
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_start_stop() -> Result<(), PvRecorderError> {
+        let frame_length = 666;
+
+        let recorder = PvRecorderBuilder::new(frame_length)
+            .device_index(0)
+            .frame_length(frame_length)
+            .init()?;
+        recorder.set_debug_logging(true);
+
+        assert!(recorder.is_recording() == false);
+        recorder.start()?;
+        assert!(recorder.is_recording() == true);
+
+        let frame = recorder.read()?;
+        assert!(frame.len() == frame_length as usize);
+
+        recorder.stop()?;
+        assert!(recorder.is_recording() == false);
+
+        Ok(())
+    }
+
+    #[test]
+    fn test_get_available_devices() -> Result<(), PvRecorderError> {
+        let devices = PvRecorderBuilder::default().get_available_devices()?;
+
+        for device in devices {
+            assert!(device.len() >= 0)
+        }
+
+        Ok(())
+    }
+}
diff --git a/project/test/test_helper.h b/project/test/test_helper.h
new file mode 100644
index 00000000..a719dc22
--- /dev/null
+++ b/project/test/test_helper.h
@@ -0,0 +1,22 @@
+#include <stdarg.h>
+#include <stdbool.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <time.h>
+
+static char error_message[256] = {0};
+
+static const char *test_error_message(const char *message, va_list args) {
+    vsnprintf(error_message, sizeof(error_message) / sizeof(error_message[0]), message, args);
+    return error_message;
+}
+
+static void check_condition(bool condition, const char *function, int32_t line, const char *message, ...) {
+    if (condition == 0) {
+        va_list args;
+        va_start(args, message);
+        fprintf(stderr, "%s:%s():at_line %d: %s\n", __FILE__, function, line, test_error_message(message, args));
+        va_end(args);
+        exit(1);
+    }
+}
\ No newline at end of file
diff --git a/test/test_pv_circular_buffer.c b/project/test/test_pv_circular_buffer.c
similarity index 92%
rename from test/test_pv_circular_buffer.c
rename to project/test/test_pv_circular_buffer.c
index 9a3c5855..9abf9b50 100644
--- a/test/test_pv_circular_buffer.c
+++ b/project/test/test_pv_circular_buffer.c
@@ -9,29 +9,8 @@
     specific language governing permissions and limitations under the License.
 */
 
-#include <stdarg.h>
-#include <stdio.h>
-#include <stdlib.h>
-#include <time.h>
-
 #include "pv_circular_buffer.h"
-
-static char error_message[256] = {0};
-
-static const char *test_error_message(const char *message, va_list args) {
-    vsnprintf(error_message, sizeof(error_message) / sizeof (error_message[0]), message, args);
-    return error_message;
-}
-
-static void check_condition(bool condition, const char *function, int32_t line, const char *message, ...) {
-    if (condition == 0) {
-        va_list args;
-        va_start(args, message);
-        fprintf(stderr, "%s:%s():at_line %d: %s", __FILE__, function, line, test_error_message(message, args));
-        va_end(args);
-        exit(1);
-    }
-}
+#include "test_helper.h"
 
 static void test_pv_circular_buffer_once(void) {
     pv_circular_buffer_t *cb;
diff --git a/project/test/test_pv_recorder.c b/project/test/test_pv_recorder.c
new file mode 100644
index 00000000..e786a44e
--- /dev/null
+++ b/project/test/test_pv_recorder.c
@@ -0,0 +1,313 @@
+/*
+    Copyright 2023 Picovoice Inc.
+
+    You may not use this file except in compliance with the license. A copy of the license is located in the "LICENSE"
+    file accompanying this source.
+
+    Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on
+    an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the
+    specific language governing permissions and limitations under the License.
+*/
+
+#include "string.h"
+
+#include "pv_recorder.h"
+#include "test_helper.h"
+
+static void init_test_helper(
+        int32_t frame_length,
+        int32_t device_index,
+        int32_t buffered_frames_count,
+        pv_recorder_status_t expected_status) {
+    pv_recorder_t *recorder = NULL;
+    pv_recorder_status_t status;
+
+    status = pv_recorder_init(frame_length, device_index, buffered_frames_count, &recorder);
+
+    check_condition(
+            status == expected_status,
+            __FUNCTION__,
+            __LINE__,
+            "Recorder initialization returned %s - expected %s.",
+            pv_recorder_status_to_string(status),
+            pv_recorder_status_to_string(expected_status));
+    if (recorder) {
+        pv_recorder_delete(recorder);
+    }
+}
+
+static void test_pv_recorder_init(void) {
+    printf("Initialize with valid parameters\n");
+    init_test_helper(512, 0, 10, PV_RECORDER_STATUS_SUCCESS);
+
+    printf("Initialize with invalid device index (negative)\n");
+    init_test_helper(512, -2, 10, PV_RECORDER_STATUS_INVALID_ARGUMENT);
+
+    printf("Initialize with invalid device index (too high)\n");
+    init_test_helper(512, 500, 10, PV_RECORDER_STATUS_INVALID_ARGUMENT);
+
+    printf("Initialize with invalid frame length\n");
+    init_test_helper(-1, 0, 10, PV_RECORDER_STATUS_INVALID_ARGUMENT);
+
+    printf("Initialize with invalid buffered frames count\n");
+    init_test_helper(512, 0, 0, PV_RECORDER_STATUS_INVALID_ARGUMENT);
+
+    printf("Initialize with null recorder pointer\n");
+    pv_recorder_status_t status = pv_recorder_init(512, 0, 10, NULL);
+    check_condition(
+            status == PV_RECORDER_STATUS_INVALID_ARGUMENT,
+            __FUNCTION__,
+            __LINE__,
+            "Recorder initialization returned %s - expected %s.",
+            pv_recorder_status_to_string(status),
+            pv_recorder_status_to_string(PV_RECORDER_STATUS_INVALID_ARGUMENT));
+}
+
+static void test_pv_recorder_start_stop(void) {
+    pv_recorder_t *recorder = NULL;
+    pv_recorder_status_t status;
+    int16_t frame[512];
+
+    status = pv_recorder_init(512, 0, 10, &recorder);
+    check_condition(
+            status == PV_RECORDER_STATUS_SUCCESS,
+            __FUNCTION__,
+            __LINE__,
+            "Recorder initialization returned %s - expected %s.",
+            pv_recorder_status_to_string(status),
+            pv_recorder_status_to_string(PV_RECORDER_STATUS_SUCCESS));
+
+    printf("Check is_recording on NULL\n");
+    bool is_recording = pv_recorder_get_is_recording(NULL);
+    check_condition(
+            is_recording == false,
+            __FUNCTION__,
+            __LINE__,
+            "get_is_recording returned true on a NULL object.");
+
+    printf("Check is_recording on before start\n");
+    is_recording = pv_recorder_get_is_recording(recorder);
+    check_condition(
+            is_recording == false,
+            __FUNCTION__,
+            __LINE__,
+            "get_is_recording returned true - expected false.");
+
+    printf("Call start on null object\n");
+    status = pv_recorder_start(NULL);
+    check_condition(
+            status == PV_RECORDER_STATUS_INVALID_ARGUMENT,
+            __FUNCTION__,
+            __LINE__,
+            "Recorder start returned %s - expected %s.",
+            pv_recorder_status_to_string(status),
+            pv_recorder_status_to_string(PV_RECORDER_STATUS_INVALID_ARGUMENT));
+
+    printf("Call read before start object\n");
+    status = pv_recorder_read(recorder, frame);
+    check_condition(
+            status == PV_RECORDER_STATUS_INVALID_STATE,
+            __FUNCTION__,
+            __LINE__,
+            "Recorder read returned %s - expected %s.",
+            pv_recorder_status_to_string(status),
+            pv_recorder_status_to_string(PV_RECORDER_STATUS_INVALID_STATE));
+
+    printf("Call start on valid recorder object\n");
+    status = pv_recorder_start(recorder);
+    check_condition(
+            status == PV_RECORDER_STATUS_SUCCESS,
+            __FUNCTION__,
+            __LINE__,
+            "Recorder start returned %s - expected %s.",
+            pv_recorder_status_to_string(status),
+            pv_recorder_status_to_string(PV_RECORDER_STATUS_SUCCESS));
+
+    printf("Call read on null recorder\n");
+    status = pv_recorder_read(NULL, frame);
+    check_condition(
+            status == PV_RECORDER_STATUS_INVALID_ARGUMENT,
+            __FUNCTION__,
+            __LINE__,
+            "Recorder read returned %s - expected %s.",
+            pv_recorder_status_to_string(status),
+            pv_recorder_status_to_string(PV_RECORDER_STATUS_INVALID_ARGUMENT));
+
+    printf("Call read with null frame\n");
+    status = pv_recorder_read(recorder, NULL);
+    check_condition(
+            status == PV_RECORDER_STATUS_INVALID_ARGUMENT,
+            __FUNCTION__,
+            __LINE__,
+            "Recorder read returned %s - expected %s.",
+            pv_recorder_status_to_string(status),
+            pv_recorder_status_to_string(PV_RECORDER_STATUS_INVALID_ARGUMENT));
+
+    printf("Call read with valid args\n");
+    status = pv_recorder_read(recorder, frame);
+    check_condition(
+            status == PV_RECORDER_STATUS_SUCCESS,
+            __FUNCTION__,
+            __LINE__,
+            "Recorder read returned %s - expected %s.",
+            pv_recorder_status_to_string(status),
+            pv_recorder_status_to_string(PV_RECORDER_STATUS_SUCCESS));
+
+    printf("Check is_recording on started recorder\n");
+    is_recording = pv_recorder_get_is_recording(recorder);
+    check_condition(
+            is_recording == true,
+            __FUNCTION__,
+            __LINE__,
+            "get_is_recording returned false - expected true.");
+
+    printf("Call stop on null recorder object\n");
+    status = pv_recorder_stop(NULL);
+    check_condition(
+            status == PV_RECORDER_STATUS_INVALID_ARGUMENT,
+            __FUNCTION__,
+            __LINE__,
+            "Recorder stop returned %s - expected %s.",
+            pv_recorder_status_to_string(status),
+            pv_recorder_status_to_string(PV_RECORDER_STATUS_INVALID_ARGUMENT));
+
+    printf("Call stop on valid recorder object\n");
+    status = pv_recorder_stop(recorder);
+    check_condition(
+            status == PV_RECORDER_STATUS_SUCCESS,
+            __FUNCTION__,
+            __LINE__,
+            "Recorder stop returned %s - expected %s.",
+            pv_recorder_status_to_string(status),
+            pv_recorder_status_to_string(PV_RECORDER_STATUS_SUCCESS));
+
+    printf("Check is_recording on stopped recorder\n");
+    is_recording = pv_recorder_get_is_recording(recorder);
+    check_condition(
+            is_recording == false,
+            __FUNCTION__,
+            __LINE__,
+            "get_is_recording returned true - expected false.");
+
+    pv_recorder_delete(recorder);
+}
+
+static void test_pv_recorder_set_debug_logging(void) {
+    pv_recorder_t *recorder = NULL;
+    pv_recorder_status_t status = pv_recorder_init(512, 0, 10, &recorder);
+    check_condition(
+            status == PV_RECORDER_STATUS_SUCCESS,
+            __FUNCTION__,
+            __LINE__,
+            "Recorder initialization returned %s - expected %s.",
+            pv_recorder_status_to_string(status),
+            pv_recorder_status_to_string(PV_RECORDER_STATUS_SUCCESS));
+
+    pv_recorder_set_debug_logging(NULL, true);
+    pv_recorder_set_debug_logging(recorder, true);
+
+    pv_recorder_delete(recorder);
+}
+
+static void test_pv_recorder_get_selected_device(void) {
+    pv_recorder_t *recorder = NULL;
+    pv_recorder_status_t status = pv_recorder_init(512, 0, 10, &recorder);
+    check_condition(
+            status == PV_RECORDER_STATUS_SUCCESS,
+            __FUNCTION__,
+            __LINE__,
+            "Recorder initialization returned %s - expected %s.",
+            pv_recorder_status_to_string(status),
+            pv_recorder_status_to_string(PV_RECORDER_STATUS_SUCCESS));
+
+    check_condition(
+            pv_recorder_get_selected_device(NULL) == NULL,
+            __FUNCTION__,
+            __LINE__,
+            "pv_recorder_get_selected_device should have returned NULL.");
+
+    check_condition(
+            strcmp(pv_recorder_get_selected_device(recorder), "") != 0,
+            __FUNCTION__,
+            __LINE__,
+            "pv_recorder_get_selected_device should have returned a device name");
+
+    pv_recorder_delete(recorder);
+}
+
+static void test_pv_recorder_get_available_devices(void) {
+    pv_recorder_status_t status;
+    int32_t device_list_length = -1;
+    char **device_list = NULL;
+
+    status = pv_recorder_get_available_devices(NULL, &device_list);
+    check_condition(
+            status == PV_RECORDER_STATUS_INVALID_ARGUMENT,
+            __FUNCTION__,
+            __LINE__,
+            "pv_recorder_get_available_devices returned %s - expected %s.",
+            pv_recorder_status_to_string(status),
+            pv_recorder_status_to_string(PV_RECORDER_STATUS_INVALID_ARGUMENT));
+
+    status = pv_recorder_get_available_devices(&device_list_length, NULL);
+    check_condition(
+            status == PV_RECORDER_STATUS_INVALID_ARGUMENT,
+            __FUNCTION__,
+            __LINE__,
+            "pv_recorder_get_available_devices returned %s - expected %s.",
+            pv_recorder_status_to_string(status),
+            pv_recorder_status_to_string(PV_RECORDER_STATUS_INVALID_ARGUMENT));
+
+    status = pv_recorder_get_available_devices(&device_list_length, &device_list);
+    check_condition(
+            status == PV_RECORDER_STATUS_SUCCESS,
+            __FUNCTION__,
+            __LINE__,
+            "pv_recorder_get_available_devices returned %s - expected %s.",
+            pv_recorder_status_to_string(status),
+            pv_recorder_status_to_string(PV_RECORDER_STATUS_SUCCESS));
+    check_condition(
+            device_list_length >= 0,
+            __FUNCTION__,
+            __LINE__,
+            "device_list_length should have been greater than 0, instead got %d",
+            device_list_length);
+    check_condition(
+            device_list != NULL,
+            __FUNCTION__,
+            __LINE__,
+            "device_list should have not been NULL");
+
+    pv_recorder_free_available_devices(device_list_length, device_list);
+}
+
+static void test_pv_recorder_sample_rate(void) {
+    int32_t sample_rate = pv_recorder_sample_rate();
+    check_condition(
+            sample_rate > 0,
+            __FUNCTION__,
+            __LINE__,
+            "Sample rate was invalid (%d).",
+            sample_rate);
+}
+
+static void test_pv_recorder_version(void) {
+    const char *version = pv_recorder_version();
+    check_condition(
+            strcmp(version, "") != 0,
+            __FUNCTION__,
+            __LINE__,
+            "Version was supposed to be a non-empty string.");
+}
+
+int main() {
+    srand(time(NULL));
+    test_pv_recorder_get_available_devices();
+    test_pv_recorder_sample_rate();
+    test_pv_recorder_version();
+    test_pv_recorder_init();
+    test_pv_recorder_start_stop();
+    test_pv_recorder_set_debug_logging();
+    test_pv_recorder_get_selected_device();
+    return 0;
+}
\ No newline at end of file