├── .github
└── workflows
│ ├── ci.yml
│ └── documentation.yml
├── .gitignore
├── .vsconfig
├── Assets
├── Adrenak.UniVoice.meta
└── Adrenak.UniVoice
│ ├── LICENSE
│ ├── LICENSE.meta
│ ├── README.md
│ ├── README.md.meta
│ ├── Runtime.meta
│ ├── Runtime
│ ├── Adrenak.UniVoice.Runtime.asmdef
│ ├── Adrenak.UniVoice.Runtime.asmdef.meta
│ ├── ClientSession.cs
│ ├── ClientSession.cs.meta
│ ├── Common.meta
│ ├── Common
│ │ ├── SimpleVad.cs
│ │ ├── SimpleVad.cs.meta
│ │ ├── Utils.cs
│ │ ├── Utils.cs.meta
│ │ ├── WavFileWriter.cs
│ │ └── WavFileWriter.cs.meta
│ ├── Impl.meta
│ ├── Impl
│ │ ├── Filters.meta
│ │ ├── Filters
│ │ │ ├── Concentus Opus Filters.meta
│ │ │ ├── Concentus Opus Filters
│ │ │ │ ├── ConcentusDecodeFilter.cs
│ │ │ │ ├── ConcentusDecodeFilter.cs.meta
│ │ │ │ ├── ConcentusEncodeFilter.cs
│ │ │ │ ├── ConcentusEncodeFilter.cs.meta
│ │ │ │ ├── ConcentusFrequencies.cs
│ │ │ │ └── ConcentusFrequencies.cs.meta
│ │ │ ├── GaussianAudioBlur.cs
│ │ │ ├── GaussianAudioBlur.cs.meta
│ │ │ ├── RNNoise.meta
│ │ │ ├── RNNoise
│ │ │ │ ├── RNNoiseFilter.cs
│ │ │ │ └── RNNoiseFilter.cs.meta
│ │ │ ├── SimpleVadFilter.cs
│ │ │ └── SimpleVadFilter.cs.meta
│ │ ├── Inputs.meta
│ │ ├── Inputs
│ │ │ ├── EmptyAudioInput.cs
│ │ │ ├── EmptyAudioInput.cs.meta
│ │ │ ├── UniMicInput.cs
│ │ │ └── UniMicInput.cs.meta
│ │ ├── Networks.meta
│ │ ├── Networks
│ │ │ ├── FakeNetwork.cs
│ │ │ ├── FakeNetwork.cs.meta
│ │ │ ├── FishNet.meta
│ │ │ ├── FishNet
│ │ │ │ ├── FishNetBroadcast.cs
│ │ │ │ ├── FishNetBroadcast.cs.meta
│ │ │ │ ├── FishNetBroadcastTags.cs
│ │ │ │ ├── FishNetBroadcastTags.cs.meta
│ │ │ │ ├── FishNetClient.cs
│ │ │ │ ├── FishNetClient.cs.meta
│ │ │ │ ├── FishNetServer.cs
│ │ │ │ └── FishNetServer.cs.meta
│ │ │ ├── Mirror.meta
│ │ │ └── Mirror
│ │ │ │ ├── MirrorClient.cs
│ │ │ │ ├── MirrorClient.cs.meta
│ │ │ │ ├── MirrorMessage.cs
│ │ │ │ ├── MirrorMessage.cs.meta
│ │ │ │ ├── MirrorMessageTags.cs
│ │ │ │ ├── MirrorMessageTags.cs.meta
│ │ │ │ ├── MirrorModeObserver.cs
│ │ │ │ ├── MirrorModeObserver.cs.meta
│ │ │ │ ├── MirrorServer.cs
│ │ │ │ └── MirrorServer.cs.meta
│ │ ├── Outputs.meta
│ │ └── Outputs
│ │ │ ├── StreamedAudioSourceOutput.cs
│ │ │ └── StreamedAudioSourceOutput.cs.meta
│ ├── Interfaces.meta
│ ├── Interfaces
│ │ ├── IAudioClient.cs
│ │ ├── IAudioClient.cs.meta
│ │ ├── IAudioFilter.cs
│ │ ├── IAudioFilter.cs.meta
│ │ ├── IAudioInput.cs
│ │ ├── IAudioInput.cs.meta
│ │ ├── IAudioOutput.cs
│ │ ├── IAudioOutput.cs.meta
│ │ ├── IAudioOutputFactory.cs
│ │ ├── IAudioOutputFactory.cs.meta
│ │ ├── IAudioServer.cs
│ │ └── IAudioServer.cs.meta
│ ├── Types.meta
│ └── Types
│ │ ├── AudioFrame.cs
│ │ ├── AudioFrame.cs.meta
│ │ ├── VoiceSettings.cs
│ │ └── VoiceSettings.cs.meta
│ ├── Samples.meta
│ ├── Samples
│ ├── Basic Setup Scripts.meta
│ ├── Basic Setup Scripts
│ │ ├── FishNet-SinglePrefabObjects.asset
│ │ ├── FishNet-SinglePrefabObjects.asset.meta
│ │ ├── UniVoiceFishNetSetupSample.cs
│ │ ├── UniVoiceFishNetSetupSample.cs.meta
│ │ ├── UniVoiceFishNetSetupSample.unity
│ │ ├── UniVoiceFishNetSetupSample.unity.meta
│ │ ├── UniVoiceMirrorSetupSample.cs
│ │ ├── UniVoiceMirrorSetupSample.cs.meta
│ │ ├── UniVoiceMirrorSetupSample.unity
│ │ └── UniVoiceMirrorSetupSample.unity.meta
│ ├── Group Chat Sample.meta
│ └── Group Chat Sample
│ │ ├── Prefabs.meta
│ │ ├── Prefabs
│ │ ├── Mic Toggle.prefab
│ │ ├── Mic Toggle.prefab.meta
│ │ ├── Peer View.prefab
│ │ └── Peer View.prefab.meta
│ │ ├── Scenes.meta
│ │ ├── Scenes
│ │ ├── GroupVoiceCallSample-Mirror.unity
│ │ └── GroupVoiceCallSample-Mirror.unity.meta
│ │ ├── Scripts.meta
│ │ ├── Scripts
│ │ ├── GroupVoiceCallMirrorSample.cs
│ │ ├── GroupVoiceCallMirrorSample.cs.meta
│ │ ├── PeerView.cs
│ │ └── PeerView.cs.meta
│ │ ├── Sprites.meta
│ │ └── Sprites
│ │ ├── mic.png
│ │ ├── mic.png.meta
│ │ ├── off.png
│ │ ├── off.png.meta
│ │ ├── on.png
│ │ ├── on.png.meta
│ │ ├── speaker.png
│ │ └── speaker.png.meta
│ ├── package.json
│ └── package.json.meta
├── Documentation
├── docfx.json
├── filterConfig.yml
└── toc.yml
├── LICENSE
├── Logs
└── Packages-Update.log
├── Packages
├── manifest.json
└── packages-lock.json
├── ProjectSettings
├── AudioManager.asset
├── ClusterInputManager.asset
├── DynamicsManager.asset
├── EditorBuildSettings.asset
├── EditorSettings.asset
├── GraphicsSettings.asset
├── InputManager.asset
├── MemorySettings.asset
├── NavMeshAreas.asset
├── NetworkManager.asset
├── PackageManagerSettings.asset
├── Physics2DSettings.asset
├── PresetManager.asset
├── ProjectSettings.asset
├── ProjectVersion.txt
├── QualitySettings.asset
├── TagManager.asset
├── TimeManager.asset
├── UnityConnectSettings.asset
├── VFXManager.asset
├── VersionControlSettings.asset
├── XRSettings.asset
└── boot.config
└── README.md
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | name: CI
2 | on:
3 | push:
4 | branches:
5 | - master
6 | jobs:
7 | release:
8 | name: Release
9 | runs-on: ubuntu-latest
10 | env:
11 | PKG_ROOT: "Assets/Adrenak.UniVoice"
12 | steps:
13 | - uses: actions/checkout@v2
14 | with:
15 | fetch-depth: 0
16 |
17 | - name: Check if README.md has changed
18 | id: readme_check
19 | run: |
20 | if git diff --name-only HEAD~1 HEAD | grep -q "README.md"; then
21 | echo "README_CHANGED=true" >> $GITHUB_ENV
22 | else
23 | echo "README_CHANGED=false" >> $GITHUB_ENV
24 | fi
25 |
26 | - name: Update package README
27 | if: env.README_CHANGED == 'true'
28 | run: |
29 | cp README.md "$PKG_ROOT/README.md"
30 | git config --global user.name 'github-bot'
31 | git config --global user.email 'github-bot@users.noreply.github.com'
32 | git commit -am "Updated package README from root README"
33 | git push -f -u origin master
34 |
35 | - name: Create UPM branch
36 | run: |
37 | git branch -d upm &> /dev/null || echo upm branch not found
38 | git subtree split -P "$PKG_ROOT" -b upm
39 | git checkout upm
40 | if [[ -d "Samples" ]]; then
41 | git mv Samples Samples~
42 | rm -f Samples.meta
43 | git config --global user.name 'github-bot'
44 | git config --global user.email 'github-bot@users.noreply.github.com'
45 | git commit -am "fix: Samples => Samples~"
46 | fi
47 | git push -f -u origin upm
--------------------------------------------------------------------------------
/.github/workflows/documentation.yml:
--------------------------------------------------------------------------------
1 | name: Documentation
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 |
8 | jobs:
9 | # Build the documentation
10 | build:
11 | runs-on: windows-latest # Required by DocFX
12 | steps:
13 | - name: Checkout
14 | uses: actions/checkout@v2
15 | with:
16 | fetch-depth: 0 # Ensure full history is fetched for the reset to work next
17 |
18 | - name: Hard Reset
19 | run: |
20 | git reset --hard HEAD
21 | git clean -fdx # Remove untracked files and directories
22 |
23 | - name: Install DocFX
24 | run: choco install -y docfx
25 |
26 | - name: Use README.md as index.md
27 | run: cp README.md Documentation/index.md
28 |
29 | - name: Build DocFX
30 | run: docfx Documentation/docfx.json
31 |
32 | - name: List generated site files
33 | run: dir _site
34 |
35 | - name: Deploy site
36 | uses: peaceiris/actions-gh-pages@v4
37 | with:
38 | github_token: ${{ secrets.GITHUB_TOKEN }}
39 | publish_branch: gh-pages
40 | publish_dir: _site
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | [Ll]ibrary/
2 | [Tt]emp/
3 | [Oo]bj/
4 | [Bb]uild/
5 | [Bb]uilds/
6 | Assets/AssetStoreTools*
7 | Assets/*
8 | !Assets/Adrenak.UniVoice
9 | !Assets/Adrenak.UniVoice.meta
10 | Logs/
11 | Logs/*
12 | UserSettings/
13 |
14 | # Visual Studio cache directory
15 | .vs/
16 |
17 | # Autogenerated VS/MD/Consulo solution and project files
18 | ExportedObj/
19 | .consulo/
20 | *.csproj
21 | *.unityproj
22 | *.sln
23 | *.suo
24 | *.tmp
25 | *.user
26 | *.userprefs
27 | *.pidb
28 | *.booproj
29 | *.svd
30 | *.pdb
31 | *.opendb
32 |
33 | # Unity3D generated meta files
34 | *.pidb.meta
35 | *.pdb.meta
36 |
37 | # Unity3D Generated File On Crash Reports
38 | sysinfo.txt
39 |
40 | # Builds
41 | *.apk
42 | *.unitypackage
43 |
--------------------------------------------------------------------------------
/.vsconfig:
--------------------------------------------------------------------------------
1 | {
2 | "version": "1.0",
3 | "components": [
4 | "Microsoft.VisualStudio.Workload.ManagedGame"
5 | ]
6 | }
7 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: c309c926e41d02c4c9b1ccc930801f63
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Vatsal Ambastha
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/LICENSE.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 789be013ec943714a826e42a9d7db452
3 | DefaultImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/README.md:
--------------------------------------------------------------------------------
1 | # UniVoice
2 | UniVoice is a voice chat/VoIP solution for Unity.
3 |
4 | Some features of UniVoice:
5 | - 🎨 Customize your audio input, output and networking layers.
6 | * 🌐 __Configurable Network__:
7 | - UniVoice is networking agnostic. Implement the `IAudioClient` and `IAudioServer` interfaces using the networking plugin of your choice to have it send audio data over any networking solution.
8 | - Built-in support for:
9 | - [Mirror networking](https://mirror-networking.com/)
10 | - [Fish Networking](https://fish-networking.gitbook.io/docs)
11 |
12 | * 🎤 __Configurable Audio Input__:
13 | - UniVoice is audio input agnostic. You can change the source of outgoing audio by implementing the `IAudioInput` interface.
14 | - Built-in support for:
15 | - Capturing Mic audio as device input.
16 |
17 | * 🔊 __Configurable Audio Output__:
18 | - UniVoice is audio output agnostic. You can divert incoming audio to anywhere you want by implementing the `IAudioOutput` interface.
19 | - Built-in support for:
20 | - Playing incoming audio using Unity AudioSource.
21 |
22 | * ✏️ __Audio Filters__:
23 | - Modify outgoing and incoming audio by implementing the `IAudioFilter` interface.
24 | - Built-in support for:
25 | - Opus (Concentus) encoding & decoding.
26 | - RNNoise based noise removal.
27 | - Energy based VAD (Voice Activity Detection)
28 | - Gaussian blurring for minor denoising.
29 |
30 | - 👥 Easy integration with your existing networking solution
31 | - Whether you're using Mirror or FishNet, UniVoice runs in the background in sync with your networking lifecycle
32 | - A basic integration involves just initializing it on start.
33 | - For advanced usage like teams, chatrooms, lobbies, you can use the UniVoice API to create runtime behaviour.
34 |
35 | - ⚙ Fine control over audio data flow.
36 | * Don't want to listen to a peer? Mute them. Don't want someone listening to you? Deafen them.
37 | * Group players using tags and control audio flow between them. For example:
38 | - "red", "blue" and "spectator" tags for two teams playing against each other.
39 | - Red and Blue teams can only hear each other
40 | - Spectators can hear everyone
41 | - clients with "contestant", "judge" and "audience" tags for a virtual talent show.
42 | - Contestant can be heard by everyone, but don't hear anyone else (for focus)
43 | - Judges can talk to and hear each other for discussions. They can hear the contestant. But not the audience (for less noise)
44 | - Audience can hear and talk to each other. They can hear the performer. But they cannot hear the judges.
45 |
46 | ## Installation
47 | ⚠️ [OpenUPM](https://openupm.com/packages/com.adrenak.univoice/?subPage=versions) may not have up to date releases. Install using NPM registry instead 👇
48 |
49 | Ensure you have the NPM registry in the `manifest.json` file of your Unity project with the following scopes:
50 | ```
51 | "scopedRegistries": [
52 | {
53 | "name": "npmjs",
54 | "url": "https://registry.npmjs.org",
55 | "scopes": [
56 | "com.npmjs",
57 | "com.adrenak.univoice",
58 | "com.adrenak.brw",
59 | "com.adrenak.unimic",
60 | "com.adrenak.concentus-unity"
61 | ]
62 | }
63 | ]
64 | ```
65 | Then add `com.adrenak.univoice:x.y.z` to the `dependencies` in your `manifest.json` file (where x.y.z is the version you wish to install). The list of versions is available on [the UniVoice NPM page](https://www.npmjs.com/package/com.adrenak.univoice?activeTab=versions).
66 |
67 | ## Useful links
68 | * API reference is available here: http://www.vatsalambastha.com/univoice
69 | * UniVoice blog: https://blog.vatsalambastha.com/search/label/univoice
70 | * Discord server: https://discord.gg/NGvkEVbdjQ
71 |
72 | ## Integration
73 | UniVoice isn't currently very drag-and-drop/low-code. The best way to integrate is to have some code perform a one time setup when your app starts and provides access to relevant objects that you can use throughout the rest of the apps runtime.
74 |
75 | ## Samples
76 | This repository contains two samples:
77 | * `UniVoiceMirrorSetupSample.cs` is a drag and drop component, a simple integration sample script. You can add it to your Mirror NetworkManager to get voice chat to work. No code required, it's as simple as that! It'll work as long as you have setup your project properly. For more instructions see the top of the `UniVoiceMirrorSetupSample.cs` file.
78 | * `UniVoiceFishNetSetypSample.cs` is also very similar. Just drag and drop and it should work!
79 | * A sample scene that shows the other clients in a UI as well as allows you to mute yourself/them. This sample is Mirror based.
80 |
81 | > UniVoice currently only supports Mirror and FishNetworking out of the box. Follow the instructions in the "Activating non-packaged dependencies" section below before trying it out the samples.
82 |
83 | ## Dependencies
84 | [com.adrenak.brw](https://www.github.com/adrenak/brw) for reading and writing messages for communication. See `MirrorServer.cs` and `MirrorClient.cs` where they're used.
85 |
86 | [com.adrenak.unimic](https://www.github.com/adrenak/unimic) for easily capturing audio from any connected mic devices. See `UniMicInput.cs` for usage. Also used for streaming audio playback. See `StreamedAudioSourceOutput.cs` for usage.
87 |
88 | [com.adrenak.concentus-unity](https://www.github.com/adrenak/concentus-unity) for Opus encoding and decoding. See `ConcentusEncodeFilter.cs` and `ConcentusDecodeFilter.cs` for usage
89 |
90 | ## Activating non-packaged dependencies
91 | UniVoice includes and installs the dependencies mentioned above along with itself. The following implementations are available out of the box when you install it:
92 | * Opus encoding/decoding filter (via Contentus-Unity)
93 | * GaussianAudioBlur filter (plain C#, no dependencies used)
94 | * Mic audio capture input (via UniMic)
95 | * AudioSource based playback output (via UniMic)
96 |
97 | UniVoice has code that uses dependencies that you have to install and sometimes enable via compilation symbols as they are _not_ UniVoice dependencies and _don't_ get installed along with UniVoice. This is because they are either third party modules or based on native libraries (not plain C#) that can pose build issues.
98 | * RNNoise Noise removal filter:
99 | * To enable, ensure the [RNNoise4Unity](https://github.com/adrenak/RNNoise4Unity) package is in your project and add `UNIVOICE_FILTER_RNNOISE4UNITY` to activate it
100 | * Mirror network:
101 | * Just add the Mirror package to your project. UniVoice will detect it.
102 | * Fish Networking:
103 | * Just install FishNet package in your project. UniVoice will detect it.
104 |
105 | ## License and Support
106 | This project is under the [MIT license](https://github.com/adrenak/univoice/blob/master/LICENSE).
107 |
108 | Community contributions are welcome.
109 |
110 | Commercial engagements with the author can be arranged, subject to schedule and availability.
111 |
112 | ## Acknowledgements and contributors
113 | * [@metater](https://github.com/Metater/) for helping make improvements to audio streaming quality. [A related blog post](https://blog.vatsalambastha.com/2025/07/unimic-330-many-streamedaudiosource.html)
114 | * [@FrantisekHolubec](https://github.com/FrantisekHolubec) for [FishNet support code](https://github.com/adrenak/univoice/commit/fdc3424180d8991c92b3e092b3edb50b6110c863). Here's a [related blog post](https://blog.vatsalambastha.com/2025/09/univoice-480-fishnet-support.html)
115 | * [Masaryk University](https://www.muni.cz/en) for using UniVoice in their projects and providing helpful feedback
116 |
117 | ## Contact
118 | The author can be reached at the following links:
119 |
120 | [Website](http://www.vatsalambastha.com)
121 | [LinkedIn](https://www.linkedin.com/in/vatsalAmbastha)
122 | [GitHub](https://www.github.com/adrenak)
123 | [Twitter](https://www.twitter.com/vatsalAmbastha)
124 |
125 | Discord: `adrenak#1934`
126 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/README.md.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: b497ccbc9f4029f4593611f820e24235
3 | TextScriptImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Runtime.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: e1b2e834bd331324e9157c87f86d1143
3 | folderAsset: yes
4 | timeCreated: 1547895114
5 | licenseType: Free
6 | DefaultImporter:
7 | userData:
8 | assetBundleName:
9 | assetBundleVariant:
10 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Runtime/Adrenak.UniVoice.Runtime.asmdef:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Adrenak.UniVoice.Runtime",
3 | "rootNamespace": "",
4 | "references": [
5 | "GUID:1f776cd02c03a7b4280b6b649d7758e2",
6 | "GUID:f87ecb857e752164ab814a3de8eb0262",
7 | "GUID:b118fd5a40c85ad4e9b38e8c4a42bbb1",
8 | "GUID:4653938bfdb5cf8409322ce17219d5f7",
9 | "GUID:30817c1a0e6d646d99c048fc403f5979",
10 | "GUID:7c88a4a7926ee5145ad2dfa06f454c67"
11 | ],
12 | "includePlatforms": [],
13 | "excludePlatforms": [],
14 | "allowUnsafeCode": false,
15 | "overrideReferences": false,
16 | "precompiledReferences": [],
17 | "autoReferenced": true,
18 | "defineConstraints": [],
19 | "versionDefines": [],
20 | "noEngineReferences": false
21 | }
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Runtime/Adrenak.UniVoice.Runtime.asmdef.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 6b289bb677194eb40b60db3a566aab7b
3 | AssemblyDefinitionImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Runtime/ClientSession.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 923d8509d2c05f2479d6af7331dabe58
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Runtime/Common.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 7e8f97d020fcd2740b34e8d69e4f882d
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Runtime/Common/SimpleVad.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 016b8c73d9808cc49a425fb3d7acabed
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Runtime/Common/Utils.cs:
--------------------------------------------------------------------------------
1 | using System.IO;
2 | using System.IO.Compression;
3 | using System.Net.Sockets;
4 | using System.Net;
5 | using System.Runtime.Serialization.Formatters.Binary;
6 | using System;
7 | using UnityEngine;
8 |
9 | namespace Adrenak.UniVoice {
10 | public class Utils {
11 | public class Bytes {
12 | public static byte[] FloatsToBytes(float[] floats) {
13 | int byteCount = sizeof(float) * floats.Length;
14 | byte[] byteArray = new byte[byteCount];
15 |
16 | Buffer.BlockCopy(floats, 0, byteArray, 0, byteCount);
17 |
18 | return byteArray;
19 | }
20 |
21 | public static float[] BytesToFloats(byte[] bytes) {
22 | int floatCount = bytes.Length / sizeof(float);
23 | float[] floatArray = new float[floatCount];
24 |
25 | Buffer.BlockCopy(bytes, 0, floatArray, 0, bytes.Length);
26 |
27 | return floatArray;
28 | }
29 | }
30 |
31 | public static class Audio {
32 | static float[] audioF;
33 | static float sumOfSquares;
34 | public static float CalculateRMS(byte[] audio) {
35 | audioF = Bytes.BytesToFloats(audio);
36 |
37 | foreach(var x in audioF)
38 | sumOfSquares += x * x;
39 | return Mathf.Sqrt(sumOfSquares / audioF.Length);
40 | }
41 | }
42 |
43 | public static class Network {
44 | public static string LocalIPv4Address {
45 | get {
46 | using (Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, 0)) {
47 | socket.Connect("8.8.8.8", 65530);
48 | IPEndPoint endPoint = socket.LocalEndPoint as IPEndPoint;
49 | return endPoint.Address.ToString();
50 | }
51 | }
52 | }
53 | }
54 | }
55 | }
56 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Runtime/Common/Utils.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: a3530a416ca433448ab4c50826faefb4
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Runtime/Common/WavFileWriter.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.IO;
3 | using System.Text;
4 |
5 | /*
6 | Example script for usage with UniMic. Just add it to a scene,
7 | play and then quit/exit play mode:
8 |
9 | public class WavFileWriterTest : MonoBehaviour {
10 | WavFileWriter writer;
11 |
12 | void Start() {
13 | string path = string.Empty;
14 | if (Application.isEditor)
15 | path = Application.dataPath.Replace("Assets", "output.wav");
16 | else
17 | path = Path.Combine(Application.persistentDataPath, "output.wav");
18 |
19 | writer = new WavFileWriter(path);
20 |
21 | Mic.Init();
22 |
23 | Mic.AvailableDevices[0].OnFrameCollected += OnFrameCollected;
24 | Mic.AvailableDevices[0].StartRecording(20);
25 | }
26 |
27 | private void OnFrameCollected(int arg1, int arg2, float[] arg3) {
28 | writer.Write(arg1, arg2, arg3);
29 | }
30 |
31 | private void OnDestroy() {
32 | writer.Dispose();
33 | }
34 | }
35 | */
36 |
37 | namespace Adrenak.UniVoice {
38 | ///
39 | /// A utility to write audio samples to a file on disk.
40 | /// Construct using the path you want to store the audio file at.
41 | /// Invoke Write with the sampling frequency, channel count and PCM samples
42 | /// and it will lazily initialize.
43 | ///
44 | public class WavFileWriter : IDisposable {
45 | FileStream fileStream;
46 | int sampleRate;
47 | short channels;
48 | readonly short bitsPerSample = 16;
49 |
50 | long dataChunkPos;
51 | int totalSampleCount = 0;
52 | bool isInitialized = false;
53 | readonly string path;
54 |
55 | public WavFileWriter(string path) {
56 | this.path = path;
57 | }
58 |
59 | public void Write(int frequency, int channelCount, float[] samples) {
60 | if (!isInitialized) {
61 | sampleRate = frequency;
62 | channels = (short)channelCount;
63 | fileStream = new FileStream(path, FileMode.Create, FileAccess.Write);
64 | WriteWavHeader();
65 | isInitialized = true;
66 | }
67 | else {
68 | if (frequency != sampleRate || channelCount != channels)
69 | throw new InvalidOperationException("Inconsistent frequency or channel count between calls.");
70 | }
71 |
72 | byte[] buffer = new byte[samples.Length * 2]; // 2 bytes per sample (16-bit PCM)
73 | for (int i = 0; i < samples.Length; i++) {
74 | short intSample = (short)Math.Clamp(samples[i] * short.MaxValue, short.MinValue, short.MaxValue);
75 | buffer[i * 2] = (byte)(intSample & 0xff);
76 | buffer[i * 2 + 1] = (byte)((intSample >> 8) & 0xff);
77 | }
78 |
79 | fileStream.Write(buffer, 0, buffer.Length);
80 | totalSampleCount += samples.Length;
81 | }
82 |
83 | void WriteWavHeader() {
84 | var writer = new BinaryWriter(fileStream, Encoding.ASCII, true);
85 |
86 | writer.Write(Encoding.ASCII.GetBytes("RIFF"));
87 | writer.Write(0); // placeholder for file size
88 | writer.Write(Encoding.ASCII.GetBytes("WAVE"));
89 |
90 | // fmt chunk
91 | writer.Write(Encoding.ASCII.GetBytes("fmt "));
92 | writer.Write(16); // PCM header size
93 | writer.Write((short)1); // PCM format
94 | writer.Write(channels);
95 | writer.Write(sampleRate);
96 | int byteRate = sampleRate * channels * bitsPerSample / 8;
97 | writer.Write(byteRate);
98 | short blockAlign = (short)(channels * bitsPerSample / 8);
99 | writer.Write(blockAlign);
100 | writer.Write(bitsPerSample);
101 |
102 | // data chunk
103 | writer.Write(Encoding.ASCII.GetBytes("data"));
104 | dataChunkPos = fileStream.Position;
105 | writer.Write(0); // placeholder for data chunk size
106 | }
107 |
108 | public void Dispose() {
109 | if (!isInitialized) return;
110 |
111 | long dataSize = totalSampleCount * bitsPerSample / 8;
112 |
113 | fileStream.Seek(dataChunkPos, SeekOrigin.Begin);
114 | fileStream.Write(BitConverter.GetBytes((int)dataSize), 0, 4);
115 |
116 | fileStream.Seek(4, SeekOrigin.Begin);
117 | int fileSize = (int)(fileStream.Length - 8);
118 | fileStream.Write(BitConverter.GetBytes(fileSize), 0, 4);
119 |
120 | fileStream.Dispose();
121 | isInitialized = false;
122 | }
123 | }
124 | }
125 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Runtime/Common/WavFileWriter.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 0ae012123ed9d8640980ccfe6efbb4e5
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Runtime/Impl.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: dd9d2878ff4b11f47be8de3fe43f866b
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Runtime/Impl/Filters.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 1c33181b4e7cd6f4588b07afff029c77
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Runtime/Impl/Filters/Concentus Opus Filters.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: d3ce124588cebfb4e812cb9b91e46525
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Runtime/Impl/Filters/Concentus Opus Filters/ConcentusDecodeFilter.cs:
--------------------------------------------------------------------------------
1 | using Concentus;
2 | using System;
3 |
4 | /*
5 | * Opus encoding and decoding are VERY important for any real world use of UniVoice as without
6 | * encoding the size of audio data is much (over 10x) larger.
7 | * For more info see https://www.github.com/adrenak/concentus-unity
8 | */
9 | namespace Adrenak.UniVoice.Filters {
10 | ///
11 | /// Decodes Opus encoded audio. Use this as a filter for incoming client audio.
12 | ///
13 | public class ConcentusDecodeFilter : IAudioFilter {
14 | IOpusDecoder decoder;
15 | float[] decodeBuffer;
16 | int inputChannelCount;
17 | int inputFrequency;
18 | byte[] floatsToBytes;
19 |
20 | ///
21 | /// Creates a Concentus decode filter.
22 | ///
23 | ///
24 | /// The length of the decode buffer. Default is 11520 to fit a large sample
25 | /// with frequency 48000, duration 120ms and 2 channels. This should be enough
26 | /// for almost all scenarios. Increase if you need more.
27 | ///
28 | public ConcentusDecodeFilter(int decodeBufferLength = 11520) {
29 | decodeBuffer = new float[decodeBufferLength];
30 | }
31 |
32 | public AudioFrame Run(AudioFrame input) {
33 | inputChannelCount = input.channelCount;
34 | inputFrequency = input.frequency;
35 |
36 | CreateNewDecoderIfNeeded();
37 |
38 | var decodeResult = Decode(input.samples, out Span decoded);
39 | if (decodeResult > 0) {
40 | floatsToBytes = Utils.Bytes.FloatsToBytes(decoded.ToArray());
41 | return new AudioFrame {
42 | timestamp = input.timestamp,
43 | samples = floatsToBytes,
44 | channelCount = inputChannelCount,
45 | frequency = inputFrequency
46 | };
47 | }
48 | else {
49 | return new AudioFrame {
50 | timestamp = input.timestamp,
51 | samples = new byte[0],
52 | channelCount = inputChannelCount,
53 | frequency = inputFrequency
54 | };
55 | }
56 | }
57 |
58 | int Decode(Span toDecode, out Span decoded) {
59 | // Decode the Opus packet into preallocated buffer
60 | int samplesPerChannel = decoder.Decode(toDecode, decodeBuffer, decodeBuffer.Length);
61 |
62 | if (samplesPerChannel > 0) {
63 | int totalSamples = samplesPerChannel * inputChannelCount; // Total samples across all channels
64 | decoded = decodeBuffer.AsSpan(0, totalSamples); // Trim to valid samples
65 | }
66 | else {
67 | decoded = Span.Empty;
68 | }
69 |
70 | return samplesPerChannel; // Return number of samples per channel or 0 on failure
71 | }
72 |
73 | void CreateNewDecoderIfNeeded() {
74 | if (decoder == null || decoder.SampleRate != inputFrequency || decoder.NumChannels != inputChannelCount) {
75 | decoder?.Dispose();
76 | decoder = OpusCodecFactory.CreateDecoder(inputFrequency, inputChannelCount);
77 | }
78 | }
79 | }
80 | }
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Runtime/Impl/Filters/Concentus Opus Filters/ConcentusDecodeFilter.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 31124d8beda4bc045b5ee5696140f0d8
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Runtime/Impl/Filters/Concentus Opus Filters/ConcentusEncodeFilter.cs:
--------------------------------------------------------------------------------
1 | using Concentus;
2 | using Concentus.Enums;
3 | using System;
4 |
5 | /*
6 | * Opus encoding and decoding are VERY important for any real world use of UniVoice as without
7 | * encoding the size of audio data is much (over 10x) larger.
8 | * For more info see https://www.github.com/adrenak/concentus-unity
9 | */
10 | namespace Adrenak.UniVoice.Filters {
11 | ///
12 | /// A filter that encodes audio using Opus. Use this as an output filter
13 | /// to reduce the size of outgoing client audio
14 | ///
15 | public class ConcentusEncodeFilter : IAudioFilter {
16 | public ConcentusFrequencies SamplingFrequency { get; private set; }
17 | IOpusEncoder encoder;
18 | IResampler resampler;
19 | byte[] encodeBuffer;
20 | float[] resampleBuffer;
21 | int inputDuration;
22 | int inputChannels;
23 | int inputFrequency;
24 | int resamplerChannelCount;
25 | float[] bytesToFloats;
26 | byte[] floatsToBytes;
27 | int resamplerQuality;
28 | int encoderComplexity;
29 | int encoderBitrate;
30 |
31 | ///
32 | /// Creates a Concentus encode filter
33 | ///
34 | ///
35 | /// The frequency the encoder runs at.
36 | /// If the input audio frequency is different from this value, it will be resampled before encode.
37 | ///
38 | /// Resampler quality [1, 10]
39 | /// Encoder complexity [1, 10]
40 | /// Encoder bitrate [16000, 256000]. Set to -1 to enable variable bitrate.
41 | ///
42 | /// The length of the encode buffer. Default is 46080 to fit a large sample
43 | /// with frequency 48000, duration 120ms and 2 channels. This should be enough
44 | /// for almost all scenarios. Increase if you need more.
45 | ///
46 | public ConcentusEncodeFilter(
47 | ConcentusFrequencies encodeFrequency = ConcentusFrequencies.Frequency_16000,
48 | int resamplerQuality = 2,
49 | int encoderComplexity = 3,
50 | int encoderBitrate = 64000,
51 | int encodeBufferLength = 46080
52 | ) {
53 | SamplingFrequency = encodeFrequency;
54 | this.resamplerQuality = Math.Clamp(resamplerQuality, 1, 10);
55 | this.encoderComplexity = Math.Clamp(encoderComplexity, 1, 10);
56 | this.encoderBitrate = Math.Clamp(encoderBitrate, 16000, 256000);
57 | encodeBuffer = new byte[encodeBufferLength];
58 | }
59 |
60 | public AudioFrame Run(AudioFrame input) {
61 | inputChannels = input.channelCount;
62 | inputFrequency = input.frequency;
63 | inputDuration = ((input.samples.Length / 4) * 1000) / (input.frequency * input.channelCount);
64 |
65 | CreateNewResamplerAndEncoderIfNeeded();
66 |
67 | Span toEncode;
68 | bytesToFloats = Utils.Bytes.BytesToFloats(input.samples);
69 | toEncode = bytesToFloats;
70 |
71 | if (inputFrequency != (int)SamplingFrequency)
72 | toEncode = Resample(bytesToFloats);
73 |
74 | var encodeResult = Encode(toEncode, out Span encoded);
75 | if (encodeResult > 0) {
76 | floatsToBytes = encoded.ToArray();
77 | return new AudioFrame {
78 | timestamp = input.timestamp,
79 | channelCount = inputChannels,
80 | samples = floatsToBytes,
81 | frequency = (int)SamplingFrequency
82 | };
83 | }
84 | else {
85 | return new AudioFrame {
86 | timestamp = input.timestamp,
87 | channelCount = inputChannels,
88 | samples = new byte[0],
89 | frequency = (int)SamplingFrequency
90 | };
91 | }
92 | }
93 |
94 | void CreateNewResamplerAndEncoderIfNeeded() {
95 | if (resampleBuffer == null || resampleBuffer.Length != (int)SamplingFrequency * inputDuration * inputChannels / 1000)
96 | resampleBuffer = new float[(int)SamplingFrequency * inputDuration * inputChannels / 1000];
97 |
98 | if (resampler == null) {
99 | resamplerChannelCount = inputChannels;
100 | resampler = ResamplerFactory.CreateResampler(inputChannels, inputFrequency, (int)SamplingFrequency, resamplerQuality);
101 | }
102 | else {
103 | resampler.GetRates(out int in_rate, out int out_rate);
104 | if (in_rate != inputFrequency || out_rate != (int)SamplingFrequency || resamplerChannelCount != inputChannels) {
105 | resampler.Dispose();
106 | resamplerChannelCount = inputChannels;
107 | resampler = ResamplerFactory.CreateResampler(inputChannels, inputFrequency, (int)SamplingFrequency, resamplerQuality);
108 | }
109 | }
110 |
111 | if (encoder == null || encoder.SampleRate != (int)SamplingFrequency || encoder.NumChannels != inputChannels) {
112 | encoder?.Dispose();
113 | encoder = OpusCodecFactory.CreateEncoder((int)SamplingFrequency, inputChannels, OpusApplication.OPUS_APPLICATION_VOIP);
114 | encoder.Complexity = encoderComplexity;
115 | if(encoderBitrate == -1)
116 | encoder.UseVBR = true;
117 | else {
118 | encoder.UseVBR = false;
119 | encoder.Bitrate = encoderBitrate;
120 | }
121 | }
122 | }
123 |
124 | Span Resample(Span samples) {
125 | // Calculate input and output lengths
126 | int in_len = samples.Length / inputChannels; // Input samples per channel
127 | int out_len = (int)SamplingFrequency * inputDuration / 1000; // Output samples per channel
128 |
129 | // Perform resampling into preallocated buffer
130 | resampler.ProcessInterleaved(samples, ref in_len, resampleBuffer, ref out_len);
131 |
132 | // Return only the valid portion of resampled data
133 | return resampleBuffer.AsSpan(0, out_len * inputChannels); // Trim to valid samples
134 | }
135 |
136 | int Encode(Span toEncode, out Span encoded) {
137 | int frameSize = (int)SamplingFrequency * inputDuration / 1000; // Samples per channel
138 | int totalSamples = frameSize * inputChannels; // Total interleaved samples
139 |
140 | if (toEncode.Length < totalSamples) {
141 | encoded = Span.Empty;
142 | return 0;
143 | }
144 |
145 | // Use preallocated encodeBuffer
146 | int result = encoder.Encode(toEncode.Slice(0, totalSamples), frameSize, encodeBuffer, encodeBuffer.Length);
147 |
148 | if (result > 0)
149 | encoded = encodeBuffer.AsSpan(0, result); // Trim to actual encoded size
150 | else
151 | encoded = Span.Empty;
152 |
153 | return result; // Return number of bytes written or 0 on failure
154 | }
155 | }
156 | }
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Runtime/Impl/Filters/Concentus Opus Filters/ConcentusEncodeFilter.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 7e548eaedfb8c1b49812a12b3fba2c8a
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Runtime/Impl/Filters/Concentus Opus Filters/ConcentusFrequencies.cs:
--------------------------------------------------------------------------------
1 | namespace Adrenak.UniVoice.Filters {
2 | ///
3 | /// Defines supported sampling frequencies for the Opus codec via Concentus.
4 | ///
5 | public enum ConcentusFrequencies : int {
6 | ///
7 | /// 8 kHz sampling frequency, typically used for narrowband audio.
8 | ///
9 | Frequency_8000 = 8000,
10 |
11 | ///
12 | /// 12 kHz sampling frequency, suitable for medium-band audio.
13 | ///
14 | Frequency_12000 = 12000,
15 |
16 | ///
17 | /// 16 kHz sampling frequency, commonly used for wideband speech.
18 | ///
19 | Frequency_16000 = 16000,
20 |
21 | ///
22 | /// 24 kHz sampling frequency, providing good quality for music and audio.
23 | ///
24 | Frequency_24000 = 24000,
25 |
26 | ///
27 | /// 48 kHz sampling frequency, offering the highest audio quality.
28 | ///
29 | Frequency_48000 = 48000,
30 | }
31 | }
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Runtime/Impl/Filters/Concentus Opus Filters/ConcentusFrequencies.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 53ba54c62b14cf4439d3b71fe7b1d08c
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Runtime/Impl/Filters/GaussianAudioBlur.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | using UnityEngine;
4 |
5 | namespace Adrenak.UniVoice.Filters {
6 | ///
7 | /// A filter that applies Gaussian blur over audio data to smoothen it.
8 | /// This is somewhat effective in removing noise from the audio.
9 | ///
10 | public class GaussianAudioBlur : IAudioFilter {
11 | readonly float sigma;
12 | readonly int range;
13 | byte[] lastInput;
14 |
15 | public GaussianAudioBlur(float sigma = 2, int range = 2) {
16 | this.sigma = sigma;
17 | this.range = range;
18 | }
19 |
20 | public AudioFrame Run(AudioFrame frame) {
21 | var input = frame.samples;
22 | if (input == null || input.Length == 0) {
23 | frame.samples = null;
24 | return frame;
25 | }
26 |
27 | // If this is the first audio input we've received, we simply apply the gaussian filter
28 | // and return the result.
29 | if (lastInput == null) {
30 | lastInput = input;
31 | frame.samples = Utils.Bytes.FloatsToBytes(
32 | ApplyGaussianFilter(Utils.Bytes.BytesToFloats(input))
33 | );
34 | return frame;
35 | }
36 |
37 | // Else, if we've had some input before, we also consider the previously processed
38 | // audio. We make an array that has both the previous and the current input, smoothen
39 | // it, and then return the second half of the array. This reducing jittering by making
40 | // the smoothing a little more seamless.
41 | else {
42 | // Create an all input, that also has the input from the last time this filter ran.
43 | byte[] allInput = new byte[lastInput.Length + input.Length];
44 | Buffer.BlockCopy(lastInput, 0, allInput, 0, lastInput.Length);
45 | Buffer.BlockCopy(input, 0, allInput, lastInput.Length, input.Length);
46 |
47 | // smoothen all input
48 | byte[] allInputSmooth = Utils.Bytes.FloatsToBytes(
49 | ApplyGaussianFilter(Utils.Bytes.BytesToFloats(allInput))
50 | );
51 |
52 | // get the second half of the smoothened values
53 | byte[] result = new byte[input.Length];
54 | Buffer.BlockCopy(allInputSmooth, lastInput.Length, result, 0, input.Length);
55 |
56 | lastInput = input;
57 | frame.samples = result;
58 | return frame;
59 | }
60 | }
61 |
62 | float[] ApplyGaussianFilter(float[] inputArray) {
63 | int length = inputArray.Length;
64 | float[] smoothedArray = new float[length];
65 |
66 | for (int i = 0; i < length; i++) {
67 | float sum = 0.0f;
68 | float weightSum = 0.0f;
69 |
70 | for (int j = -range; j <= range; j++) {
71 | int index = i + j;
72 | if (index >= 0 && index < length) {
73 | float weight = Gaussian(j, sigma);
74 | sum += inputArray[index] * weight;
75 | weightSum += weight;
76 | }
77 | }
78 |
79 | smoothedArray[i] = sum / weightSum;
80 | }
81 |
82 | return smoothedArray;
83 | }
84 |
85 | float Gaussian(int x, float sigma) {
86 | return (float)Mathf.Exp(-(x * x) / (2 * sigma * sigma))
87 | / ((float)Mathf.Sqrt(2 * Mathf.PI) * sigma);
88 | }
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Runtime/Impl/Filters/GaussianAudioBlur.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 5992899a0c52b09498191b477927699d
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Runtime/Impl/Filters/RNNoise.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 046f57b349547674cb5b8c8e31c89467
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Runtime/Impl/Filters/RNNoise/RNNoiseFilter.cs:
--------------------------------------------------------------------------------
1 | #if UNIVOICE_FILTER_RNNOISE4UNITY
2 | using System;
3 |
4 | using Adrenak.RNNoise4Unity;
5 |
6 | namespace Adrenak.UniVoice.Filters {
7 | public class RNNoiseFilter : IAudioFilter {
8 | readonly Denoiser denoiser;
9 |
10 | public RNNoiseFilter() {
11 | denoiser = new Denoiser();
12 | }
13 |
14 | public AudioFrame Run(AudioFrame input) {
15 | var data = Utils.Bytes.BytesToFloats(input.samples);
16 |
17 | denoiser.Denoise(data.AsSpan(), false);
18 |
19 | return new AudioFrame {
20 | timestamp = input.timestamp,
21 | channelCount = input.channelCount,
22 | frequency = input.frequency,
23 | samples = Utils.Bytes.FloatsToBytes(data)
24 | };
25 | }
26 | }
27 | }
28 | #endif
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Runtime/Impl/Filters/RNNoise/RNNoiseFilter.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: bc1ad3167deb8cf4896b4f4b1180ec9a
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Runtime/Impl/Filters/SimpleVadFilter.cs:
--------------------------------------------------------------------------------
1 | namespace Adrenak.UniVoice.Filters {
2 | public class SimpleVadFilter : IAudioFilter {
3 | private readonly SimpleVad _vad;
4 |
5 | public SimpleVadFilter(SimpleVad vad) {
6 | _vad = vad;
7 | }
8 |
9 | public AudioFrame Run(AudioFrame input) {
10 | _vad.Process(input.frequency, input.channelCount, Utils.Bytes.BytesToFloats(input.samples));
11 | if (_vad.IsSpeaking) {
12 | return input;
13 | }
14 | return default;
15 | }
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Runtime/Impl/Filters/SimpleVadFilter.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: abdb23219540abe46ba175163b23e00d
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Runtime/Impl/Inputs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 569cfdf74a194604aa8913c641f27024
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Runtime/Impl/Inputs/EmptyAudioInput.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Adrenak.UniVoice {
4 | ///
5 | /// An audio input implementation that doesn't do anything.
6 | /// Use this when the device doesn't have any input mode.
7 | /// This is especially useful when setting up the ClientSession
8 | /// object on a dedicated server that likely isn't going to have
9 | /// and mic or other audio capture devices.
10 | ///
11 | public class EmptyAudioInput : IAudioInput {
12 | public int Frequency => 1;
13 |
14 | public int ChannelCount => 1;
15 |
16 | public int SegmentRate => 1;
17 |
18 | #pragma warning disable CS0067
19 | public event Action OnFrameReady;
20 | #pragma warning restore
21 |
22 | public void Dispose() { }
23 | }
24 | }
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Runtime/Impl/Inputs/EmptyAudioInput.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: f1db3d25938d78548877be424a98cdd5
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Runtime/Impl/Inputs/UniMicInput.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | using Adrenak.UniMic;
4 |
5 | using UnityEngine;
6 |
7 | namespace Adrenak.UniVoice.Inputs {
8 | ///
9 | /// An implementation based on UniMic.
10 | /// For more on UniMic, visit https://www.github.com/adrenak/unimic
11 | ///
12 | public class UniMicInput : IAudioInput {
13 | const string TAG = "UniMicInput";
14 | public event Action OnFrameReady;
15 |
16 | public Mic.Device device;
17 | public Mic.Device Device {
18 | get => device;
19 | set {
20 | if (device == value)
21 | return;
22 | if(device != null)
23 | device.OnFrameCollected -= OnFrameCollected;
24 | device = value;
25 | if(device != null)
26 | device.OnFrameCollected += OnFrameCollected;
27 | }
28 | }
29 |
30 | public UniMicInput(Mic.Device device) {
31 | Device = device;
32 | }
33 |
34 | private void OnFrameCollected(int frequency, int channels, float[] samples) {
35 | var frame = new AudioFrame {
36 | timestamp = 0,
37 | frequency = frequency,
38 | channelCount = channels,
39 | samples = Utils.Bytes.FloatsToBytes(samples)
40 | };
41 | OnFrameReady?.Invoke(frame);
42 | }
43 |
44 | public void Dispose() {
45 | if(Device != null)
46 | Device.OnFrameCollected -= OnFrameCollected;
47 | Debug.unityLogger.Log(TAG, "Disposed");
48 | }
49 | }
50 | }
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Runtime/Impl/Inputs/UniMicInput.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 6a98c26fdc5135842a8a651f2bd5f8e9
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Runtime/Impl/Networks.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 94e11acf53928b445a24dd638c5d334a
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Runtime/Impl/Networks/FakeNetwork.cs:
--------------------------------------------------------------------------------
1 | namespace Adrenak.UniVoice.Networks {
2 | // Just an empty client to make sure that there's always
3 | // an Adrenak.UniVoice.Networks namespace even if there's
4 | // no network implementation in the project
5 | public class FakeNetwork { }
6 | }
7 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Runtime/Impl/Networks/FakeNetwork.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: af0bac6bd02832141b9897cc6b24529b
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Runtime/Impl/Networks/FishNet.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 40d4cba94d8644408e9ebc8f61a470c3
3 | timeCreated: 1755189627
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Runtime/Impl/Networks/FishNet/FishNetBroadcast.cs:
--------------------------------------------------------------------------------
1 | #if FISHNET
2 | using System;
3 | using FishNet.Broadcast;
4 |
5 | namespace Adrenak.UniVoice.Networks
6 | {
7 | ///
8 | /// The messages exchanged between the server and client.
9 | /// To see how the Mirror implementation of UniVoice uses this struct
10 | /// find the references to the object in the project.
11 | /// The gist is, it uses BRW (https://www.github.com/adrenak/brw) to
12 | /// write and read data. The data always starts with a tag. All the tags
13 | /// used for this UniVoice FishNet implementation are available in
14 | ///
15 | ///
16 | [Serializable]
17 | public struct FishNetBroadcast : IBroadcast
18 | {
19 | public byte[] data;
20 | }
21 | }
22 | #endif
23 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Runtime/Impl/Networks/FishNet/FishNetBroadcast.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: f0e5aa417fb8e6249bb05a011a5d7edf
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Runtime/Impl/Networks/FishNet/FishNetBroadcastTags.cs:
--------------------------------------------------------------------------------
1 | #if FISHNET
2 | namespace Adrenak.UniVoice.Networks
3 | {
4 | ///
5 | /// The different types of messages we send over FishNet
6 | /// to implement the and
7 | /// interfaces for FishNet
8 | ///
9 | public class FishNetBroadcastTags
10 | {
11 | public const string AUDIO_FRAME = "AUDIO_FRAME";
12 | public const string VOICE_SETTINGS = "VOICE_SETTINGS";
13 | }
14 | }
15 | #endif
16 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Runtime/Impl/Networks/FishNet/FishNetBroadcastTags.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: fa1db30850448a14b840e4974d687a3b
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Runtime/Impl/Networks/FishNet/FishNetClient.cs:
--------------------------------------------------------------------------------
1 | #if FISHNET
2 | using System;
3 | using System.Collections.Generic;
4 | using System.Linq;
5 | using Adrenak.BRW;
6 | using FishNet;
7 | using FishNet.Managing;
8 | using FishNet.Transporting;
9 | using UnityEngine;
10 |
11 | namespace Adrenak.UniVoice.Networks
12 | {
13 | ///
14 | /// This is the implementation of interface for FishNet.
15 | /// It uses the FishNet to send and receive UniVoice data to the server.
16 | ///
17 | public class FishNetClient : IAudioClient
18 | {
19 | private const string TAG = "[FishNetClient]";
20 | public int ID { get; private set; } = -1;
21 |
22 | public List PeerIDs { get; private set; }
23 | public VoiceSettings YourVoiceSettings { get; private set; }
24 |
25 | public event Action> OnJoined;
26 | public event Action OnLeft;
27 | public event Action OnPeerJoined;
28 | public event Action OnPeerLeft;
29 | public event Action OnReceivedPeerAudioFrame;
30 |
31 | private NetworkManager _networkManager;
32 |
33 | public FishNetClient()
34 | {
35 | PeerIDs = new List();
36 | YourVoiceSettings = new VoiceSettings();
37 |
38 | _networkManager = InstanceFinder.NetworkManager;
39 | _networkManager.ClientManager.OnClientConnectionState += OnClientConnectionStateChanged;
40 | _networkManager.ClientManager.OnAuthenticated += OnClientAuthenticated;
41 | _networkManager.ClientManager.OnRemoteConnectionState += OnRemoteConnectionStateChanged;
42 | _networkManager.ClientManager.RegisterBroadcast(OnReceivedMessage);
43 | }
44 |
45 | public void Dispose()
46 | {
47 | if (_networkManager)
48 | {
49 | _networkManager.ClientManager.OnClientConnectionState -= OnClientConnectionStateChanged;
50 | _networkManager.ClientManager.OnAuthenticated -= OnClientAuthenticated;
51 | _networkManager.ClientManager.OnRemoteConnectionState -= OnRemoteConnectionStateChanged;
52 | _networkManager.ClientManager.UnregisterBroadcast(OnReceivedMessage);
53 | }
54 | PeerIDs.Clear();
55 | }
56 |
57 | private void OnRemoteConnectionStateChanged(RemoteConnectionStateArgs args)
58 | {
59 | // Don't process connection state changes before the client is authenticated
60 | if (_networkManager.ClientManager.Connection.ClientId < 0)
61 | return;
62 |
63 | if (args.ConnectionState == RemoteConnectionState.Started)
64 | {
65 | var newPeerID = args.ConnectionId;
66 | if (!PeerIDs.Contains(newPeerID))
67 | {
68 | PeerIDs.Add(newPeerID);
69 | Debug.unityLogger.Log(LogType.Log, TAG,
70 | $"Peer {newPeerID} joined. Peer list is now {string.Join(", ", PeerIDs)}");
71 | OnPeerJoined?.Invoke(newPeerID);
72 | }
73 | }
74 | else if (args.ConnectionState == RemoteConnectionState.Stopped)
75 | {
76 | var leftPeerID = args.ConnectionId;
77 | if (PeerIDs.Contains(leftPeerID))
78 | {
79 | PeerIDs.Remove(leftPeerID);
80 | var log2 = $"Peer {leftPeerID} left. ";
81 | if (PeerIDs.Count == 0)
82 | log2 += "There are no peers anymore.";
83 | else
84 | log2 += $"Peer list is now {string.Join(", ", PeerIDs)}";
85 |
86 | Debug.unityLogger.Log(LogType.Log, TAG, log2);
87 | OnPeerLeft?.Invoke(leftPeerID);
88 | }
89 | }
90 | }
91 |
92 | private void OnClientAuthenticated()
93 | {
94 | // We need to use OnClientAuthenticated to ensure the client does have ClientId set
95 | ID = _networkManager.ClientManager.Connection.ClientId;
96 | PeerIDs = _networkManager.ClientManager.Clients.Keys.Where(x => x != ID).ToList();
97 |
98 | var log = $"Initialized with ID {ID}. ";
99 | if (PeerIDs.Count > 0)
100 | log += $"Peer list: {string.Join(", ", PeerIDs)}";
101 | else
102 | log += "There are currently no peers.";
103 | Debug.unityLogger.Log(LogType.Log, TAG, log);
104 |
105 | OnJoined?.Invoke(ID, PeerIDs);
106 | foreach (var peerId in PeerIDs)
107 | OnPeerJoined?.Invoke(peerId);
108 | }
109 |
110 | private void OnClientConnectionStateChanged(ClientConnectionStateArgs args)
111 | {
112 | // We check only for the stopped state here, as the started state is handled in OnClientAuthenticated
113 | if (args.ConnectionState == LocalConnectionState.Stopped)
114 | {
115 | YourVoiceSettings = new VoiceSettings();
116 | var oldPeerIds = PeerIDs.ToList();
117 | PeerIDs.Clear();
118 | ID = -1;
119 | foreach (var peerId in oldPeerIds)
120 | OnPeerLeft?.Invoke(peerId);
121 | OnLeft?.Invoke();
122 | }
123 | }
124 |
125 | private void OnReceivedMessage(FishNetBroadcast msg, Channel channel)
126 | {
127 | var reader = new BytesReader(msg.data);
128 | var tag = reader.ReadString();
129 | switch (tag)
130 | {
131 | // When the server sends audio from a peer meant for this client
132 | case FishNetBroadcastTags.AUDIO_FRAME:
133 | var sender = reader.ReadInt();
134 | if (sender == ID || !PeerIDs.Contains(sender))
135 | return;
136 | var frame = new AudioFrame
137 | {
138 | timestamp = reader.ReadLong(),
139 | frequency = reader.ReadInt(),
140 | channelCount = reader.ReadInt(),
141 | samples = reader.ReadByteArray()
142 | };
143 | OnReceivedPeerAudioFrame?.Invoke(sender, frame);
144 | break;
145 | }
146 | }
147 |
148 | ///
149 | /// Sends an audio frame captured on this client to the server
150 | ///
151 | ///
152 | public void SendAudioFrame(AudioFrame frame)
153 | {
154 | if (ID == -1)
155 | return;
156 | var writer = new BytesWriter();
157 | writer.WriteString(FishNetBroadcastTags.AUDIO_FRAME);
158 | writer.WriteInt(ID);
159 | writer.WriteLong(frame.timestamp);
160 | writer.WriteInt(frame.frequency);
161 | writer.WriteInt(frame.channelCount);
162 | writer.WriteByteArray(frame.samples);
163 |
164 | var message = new FishNetBroadcast
165 | {
166 | data = writer.Bytes
167 | };
168 |
169 | if (_networkManager.ClientManager.Started)
170 | _networkManager.ClientManager.Broadcast(message, Channel.Unreliable);
171 | }
172 |
173 | ///
174 | /// Updates the server with the voice settings of this client
175 | ///
176 | public void SubmitVoiceSettings()
177 | {
178 | if (ID == -1)
179 | return;
180 | var writer = new BytesWriter();
181 | writer.WriteString(FishNetBroadcastTags.VOICE_SETTINGS);
182 | writer.WriteInt(YourVoiceSettings.muteAll ? 1 : 0);
183 | writer.WriteIntArray(YourVoiceSettings.mutedPeers.ToArray());
184 | writer.WriteInt(YourVoiceSettings.deafenAll ? 1 : 0);
185 | writer.WriteIntArray(YourVoiceSettings.deafenedPeers.ToArray());
186 | writer.WriteString(string.Join(",", YourVoiceSettings.myTags));
187 | writer.WriteString(string.Join(",", YourVoiceSettings.mutedTags));
188 | writer.WriteString(string.Join(",", YourVoiceSettings.deafenedTags));
189 |
190 | var message = new FishNetBroadcast() {
191 | data = writer.Bytes
192 | };
193 | _networkManager.ClientManager.Broadcast(message);
194 | }
195 | }
196 | }
197 | #endif
198 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Runtime/Impl/Networks/FishNet/FishNetClient.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: e49db0da30bc5fc479ca244d58f82481
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Runtime/Impl/Networks/FishNet/FishNetServer.cs:
--------------------------------------------------------------------------------
1 | #if FISHNET
2 | using System;
3 | using System.Linq;
4 | using System.Collections.Generic;
5 | using UnityEngine;
6 | using Adrenak.BRW;
7 | using FishNet;
8 | using FishNet.Connection;
9 | using FishNet.Managing;
10 | using FishNet.Transporting;
11 |
12 | namespace Adrenak.UniVoice.Networks
13 | {
14 | ///
15 | /// This is an implementation of the interface for FishNet.
16 | /// It uses the FishNet to send and receive UniVoice audio data to and from clients.
17 | ///
18 | public class FishNetServer : IAudioServer
19 | {
20 | private const string TAG = "[FishNetServer]";
21 |
22 | public event Action OnServerStart;
23 | public event Action OnServerStop;
24 | public event Action OnClientVoiceSettingsUpdated;
25 |
26 | public List ClientIDs { get; private set; }
27 | public Dictionary ClientVoiceSettings { get; private set; }
28 |
29 | private NetworkManager _networkManager;
30 | private List _startedTransports = new();
31 |
32 | public FishNetServer()
33 | {
34 | ClientIDs = new List();
35 | ClientVoiceSettings = new Dictionary();
36 |
37 | _networkManager = InstanceFinder.NetworkManager;
38 | _networkManager.ServerManager.OnServerConnectionState += OnServerConnectionStateChanged;
39 | _networkManager.ServerManager.OnRemoteConnectionState += OnServerRemoteConnectionStateChanged;
40 | _networkManager.ClientManager.OnClientConnectionState += OnClientConnectionStateChanged;
41 | _networkManager.ServerManager.RegisterBroadcast(OnReceivedMessage, false);
42 | }
43 |
44 | public void Dispose()
45 | {
46 | if (_networkManager)
47 | {
48 | _networkManager.ServerManager.OnServerConnectionState -= OnServerConnectionStateChanged;
49 | _networkManager.ServerManager.OnRemoteConnectionState -= OnServerRemoteConnectionStateChanged;
50 | _networkManager.ClientManager.OnClientConnectionState -= OnClientConnectionStateChanged;
51 | _networkManager.ServerManager.UnregisterBroadcast(OnReceivedMessage);
52 | }
53 | OnServerShutdown();
54 | }
55 |
56 | private void OnServerStarted()
57 | {
58 | OnServerStart?.Invoke();
59 | }
60 |
61 | private void OnServerShutdown()
62 | {
63 | ClientIDs.Clear();
64 | ClientVoiceSettings.Clear();
65 | OnServerStop?.Invoke();
66 | }
67 |
68 | private void OnServerRemoteConnectionStateChanged(NetworkConnection connection, RemoteConnectionStateArgs args)
69 | {
70 | if (args.ConnectionState == RemoteConnectionState.Started)
71 | {
72 | OnServerConnected(connection.ClientId);
73 | }
74 | else if (args.ConnectionState == RemoteConnectionState.Stopped)
75 | {
76 | OnServerDisconnected(connection.ClientId);
77 | }
78 | }
79 |
80 | private void OnServerConnectionStateChanged(ServerConnectionStateArgs args)
81 | {
82 | // Connection can change for each transport, so we need to track them
83 | if (args.ConnectionState == LocalConnectionState.Started)
84 | {
85 | var wasStarted = _startedTransports.Count != 0;
86 | _startedTransports.Add(args.TransportIndex);
87 | if (!wasStarted)
88 | OnServerStarted();
89 | }
90 | else if (args.ConnectionState == LocalConnectionState.Stopped)
91 | {
92 | _startedTransports.Remove(args.TransportIndex);
93 | if(_startedTransports.Count == 0)
94 | OnServerShutdown();
95 | }
96 | }
97 |
98 | private void OnClientConnectionStateChanged(ClientConnectionStateArgs args)
99 | {
100 | // TODO - do we need to check if host or is this enough?
101 | if (args.ConnectionState == LocalConnectionState.Started)
102 | {
103 | OnServerConnected(0);
104 | }
105 | else if (args.ConnectionState == LocalConnectionState.Stopped)
106 | {
107 | OnServerDisconnected(0);
108 | }
109 | }
110 |
111 | private void OnReceivedMessage(NetworkConnection connection, FishNetBroadcast message, Channel channel)
112 | {
113 | var clientId = connection.ClientId;
114 | var reader = new BytesReader(message.data);
115 | var tag = reader.ReadString();
116 |
117 | if (tag.Equals(FishNetBroadcastTags.AUDIO_FRAME))
118 | {
119 | // We start with all the peers except the one that's
120 | // sent the audio
121 | var peersToForwardAudioTo = ClientIDs
122 | .Where(x => x != clientId);
123 |
124 | // Check the voice settings of the sender and eliminate any peers the sender
125 | // may have deafened
126 | if (ClientVoiceSettings.TryGetValue(clientId, out var senderSettings))
127 | {
128 | // If the client sending the audio has deafened everyone,
129 | // we simply return. Sender's audio should not be forwarded to anyone.
130 | if (senderSettings.deafenAll)
131 | return;
132 |
133 | // Filter the recipient list by removing all peers that the sender has
134 | // deafened using ID
135 | peersToForwardAudioTo = peersToForwardAudioTo
136 | .Where(x => !senderSettings.deafenedPeers.Contains(x));
137 |
138 | // Further filter the recipient list by removing peers that the sender has
139 | // deafened using tags
140 | peersToForwardAudioTo = peersToForwardAudioTo.Where(peer =>
141 | {
142 | // Get the voice settings of the peer
143 | if (ClientVoiceSettings.TryGetValue(peer, out var peerVoiceSettings))
144 | {
145 | // Check if sender has not deafened peer using tag
146 | var hasDeafenedPeer = senderSettings.deafenedTags.Intersect(peerVoiceSettings.myTags).Any();
147 | return !hasDeafenedPeer;
148 | }
149 | // If peer doesn't have voice settings, we can keep the peer in the list
150 | return true;
151 | });
152 | }
153 |
154 | // We iterate through each recipient peer that the sender wants to send audio to, checking if
155 | // they have muted the sender, before forwarding the audio to them.
156 | foreach (var recipient in peersToForwardAudioTo) {
157 | // Get the settings of a potential recipient
158 | if (ClientVoiceSettings.TryGetValue(recipient, out var recipientSettings)) {
159 | // If a peer has muted everyone, don't send audio
160 | if (recipientSettings.muteAll)
161 | continue;
162 |
163 | // If the peers has muted the sender using ID, skip sending audio
164 | if (recipientSettings.mutedPeers.Contains(clientId))
165 | continue;
166 |
167 | // If the peer has muted the sender using tag, skip sending audio
168 | if (recipientSettings.mutedTags.Intersect(senderSettings.myTags).Any())
169 | continue;
170 | }
171 | SendToClient(recipient, message.data, Channel.Unreliable);
172 | }
173 | }
174 | else if (tag.Equals(FishNetBroadcastTags.VOICE_SETTINGS)) {
175 | //Debug.unityLogger.Log(LogType.Log, TAG, "FishNet server stopped");
176 | // We create the VoiceSettings object by reading from the reader
177 | // and update the peer voice settings map
178 | var muteAll = reader.ReadInt() == 1;
179 | var mutedPeers = reader.ReadIntArray().ToList();
180 | var deafenAll = reader.ReadInt() == 1;
181 | var deafenedPeers = reader.ReadIntArray().ToList();
182 | var myTags = reader.ReadString().Split(",").ToList();
183 | var mutedTags = reader.ReadString().Split(",").ToList();
184 | var deafenedTags = reader.ReadString().Split(",").ToList();
185 |
186 | var voiceSettings = new VoiceSettings {
187 | muteAll = muteAll,
188 | mutedPeers = mutedPeers,
189 | deafenAll = deafenAll,
190 | deafenedPeers = deafenedPeers,
191 | myTags = myTags,
192 | mutedTags = mutedTags,
193 | deafenedTags = deafenedTags
194 | };
195 | ClientVoiceSettings[clientId] = voiceSettings;
196 | OnClientVoiceSettingsUpdated?.Invoke();
197 | }
198 | }
199 |
200 | private void OnServerConnected(int connId)
201 | {
202 | Debug.unityLogger.Log(LogType.Log, TAG, $"Client {connId} connected");
203 | ClientIDs.Add(connId);
204 | }
205 |
206 | private void OnServerDisconnected(int connId)
207 | {
208 | ClientIDs.Remove(connId);
209 | Debug.unityLogger.Log(LogType.Log, TAG, $"Client {connId} disconnected");
210 | }
211 |
212 | private void SendToClient(int clientConnId, byte[] bytes, Channel channel)
213 | {
214 | if (!TryGetConnectionToClient(clientConnId, out var connection))
215 | return;
216 |
217 | var message = new FishNetBroadcast {data = bytes};
218 | _networkManager.ServerManager.Broadcast(connection, message, false, channel);
219 | }
220 |
221 | private bool TryGetConnectionToClient(int desiredClientID, out NetworkConnection resultConnection)
222 | {
223 | resultConnection = null;
224 | foreach (var (clientID, conn) in _networkManager.ServerManager.Clients)
225 | {
226 | if (clientID == desiredClientID)
227 | {
228 | resultConnection = conn;
229 | return true;
230 | }
231 | }
232 | return false;
233 | }
234 | }
235 | }
236 | #endif
237 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Runtime/Impl/Networks/FishNet/FishNetServer.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 7a700664d8ca951499b4d5a0fdd8a5f5
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Runtime/Impl/Networks/Mirror.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 4ddfae6dcdf2d1f4a8ebf8fba7da1fc9
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Runtime/Impl/Networks/Mirror/MirrorClient.cs:
--------------------------------------------------------------------------------
1 | #if MIRROR
2 | using System;
3 | using System.Linq;
4 | using System.Collections.Generic;
5 |
6 | using Mirror;
7 | using Adrenak.BRW;
8 | using UnityEngine;
9 |
10 | namespace Adrenak.UniVoice.Networks {
11 | ///
12 | /// Activate this class by including the UNIVOICE_MIRROR_NETWORK compilaton symbol
13 | /// in your project.
14 | /// This is the implementation of interface for Mirror.
15 | /// It uses the Mirror transport to send and receive UniVoice data to the server.
16 | ///
17 | public class MirrorClient : IAudioClient {
18 | const string TAG = "[MirrorClient]";
19 |
20 | public int ID { get; private set; } = -1;
21 |
22 | public List PeerIDs { get; private set; }
23 |
24 | public VoiceSettings YourVoiceSettings { get; private set; }
25 |
26 | public event Action> OnJoined;
27 | public event Action OnLeft;
28 | public event Action OnPeerJoined;
29 | public event Action OnPeerLeft;
30 | public event Action OnReceivedPeerAudioFrame;
31 |
32 | readonly MirrorModeObserver mirrorEvents;
33 |
34 | public MirrorClient() {
35 | PeerIDs = new List();
36 | YourVoiceSettings = new VoiceSettings();
37 |
38 | mirrorEvents = MirrorModeObserver.New("for MirrorClient");
39 | mirrorEvents.ModeChanged += OnModeChanged;
40 |
41 | NetworkClient.RegisterHandler(OnReceivedMessage, false);
42 | }
43 |
44 | public void Dispose() {
45 | PeerIDs.Clear();
46 | }
47 |
48 | void OnModeChanged(NetworkManagerMode oldMode, NetworkManagerMode newMode) {
49 | // For some reason, handlers don't always work as expected when the connection mode changes
50 | NetworkClient.ReplaceHandler(OnReceivedMessage);
51 |
52 | bool clientOnlyToOffline = newMode == NetworkManagerMode.Offline && oldMode == NetworkManagerMode.ClientOnly;
53 | bool hostToServerOnlyOrOffline = oldMode == NetworkManagerMode.Host;
54 |
55 | if (clientOnlyToOffline || hostToServerOnlyOrOffline) {
56 | // We unregister the handler only when the device was a client.
57 | // If it was a Host that's now a ServerOnly, we still need the handler as it's used in MirrorServer
58 | if (clientOnlyToOffline)
59 | NetworkClient.UnregisterHandler();
60 |
61 | OnClientDisconnected();
62 | }
63 | }
64 |
65 | void OnClientDisconnected() {
66 | YourVoiceSettings = new VoiceSettings();
67 | var oldPeerIds = PeerIDs;
68 | PeerIDs.Clear();
69 | ID = -1;
70 | foreach (var peerId in oldPeerIds)
71 | OnPeerLeft?.Invoke(peerId);
72 | OnLeft?.Invoke();
73 | }
74 |
75 | void OnReceivedMessage(MirrorMessage msg) {
76 | var reader = new BytesReader(msg.data);
77 | var tag = reader.ReadString();
78 | switch (tag) {
79 | // When the server sends the data to initial this client with.
80 | // This includes the ID of this client along with the IDs of all the
81 | // peers that are already connected to the server
82 | case MirrorMessageTags.PEER_INIT:
83 | ID = reader.ReadInt();
84 | PeerIDs = reader.ReadIntArray().ToList();
85 |
86 | string log = $"Initialized with ID {ID}. ";
87 | if (PeerIDs.Count > 0)
88 | log += $"Peer list: {string.Join(", ", PeerIDs)}";
89 | else
90 | log += "There are currently no peers.";
91 | Debug.unityLogger.Log(LogType.Log, TAG, log);
92 |
93 | OnJoined?.Invoke(ID, PeerIDs);
94 | foreach (var peerId in PeerIDs)
95 | OnPeerJoined?.Invoke(peerId);
96 | break;
97 |
98 | // When the server notifies that a new peer has joined the network
99 | case MirrorMessageTags.PEER_JOINED:
100 | var newPeerID = reader.ReadInt();
101 | if (!PeerIDs.Contains(newPeerID)) {
102 | PeerIDs.Add(newPeerID);
103 | Debug.unityLogger.Log(LogType.Log, TAG,
104 | $"Peer {newPeerID} joined. Peer list is now {string.Join(", ", PeerIDs)}");
105 | OnPeerJoined?.Invoke(newPeerID);
106 | }
107 | break;
108 |
109 | // When the server notifies that a peer has left the network
110 | case MirrorMessageTags.PEER_LEFT:
111 | var leftPeerID = reader.ReadInt();
112 | if (PeerIDs.Contains(leftPeerID)) {
113 | PeerIDs.Remove(leftPeerID);
114 | string log2 = $"Peer {leftPeerID} left. ";
115 | if (PeerIDs.Count == 0)
116 | log2 += "There are no peers anymore.";
117 | else
118 | log2 += $"Peer list is now {string.Join(", ", PeerIDs)}";
119 |
120 | Debug.unityLogger.Log(LogType.Log, TAG, log2);
121 | OnPeerLeft?.Invoke(leftPeerID);
122 | }
123 | break;
124 |
125 | // When the server sends audio from a peer meant for this client
126 | case MirrorMessageTags.AUDIO_FRAME:
127 | var sender = reader.ReadInt();
128 | if (sender == ID || !PeerIDs.Contains(sender))
129 | return;
130 | var frame = new AudioFrame {
131 | timestamp = reader.ReadLong(),
132 | frequency = reader.ReadInt(),
133 | channelCount = reader.ReadInt(),
134 | samples = reader.ReadByteArray()
135 | };
136 | OnReceivedPeerAudioFrame?.Invoke(sender, frame);
137 | break;
138 | }
139 | }
140 |
141 | ///
142 | /// Sends an audio frame captured on this client to the server
143 | ///
144 | ///
145 | public void SendAudioFrame(AudioFrame frame) {
146 | if (ID == -1)
147 | return;
148 | var writer = new BytesWriter();
149 | writer.WriteString(MirrorMessageTags.AUDIO_FRAME);
150 | writer.WriteInt(ID);
151 | writer.WriteLong(frame.timestamp);
152 | writer.WriteInt(frame.frequency);
153 | writer.WriteInt(frame.channelCount);
154 | writer.WriteByteArray(frame.samples);
155 |
156 | var message = new MirrorMessage {
157 | data = writer.Bytes
158 | };
159 | NetworkClient.Send(message, Channels.Unreliable);
160 | }
161 |
162 | ///
163 | /// Updates the server with the voice settings of this client
164 | ///
165 | public void SubmitVoiceSettings() {
166 | if (ID == -1)
167 | return;
168 | var writer = new BytesWriter();
169 | writer.WriteString(MirrorMessageTags.VOICE_SETTINGS);
170 | writer.WriteInt(YourVoiceSettings.muteAll ? 1 : 0);
171 | writer.WriteIntArray(YourVoiceSettings.mutedPeers.ToArray());
172 | writer.WriteInt(YourVoiceSettings.deafenAll ? 1 : 0);
173 | writer.WriteIntArray(YourVoiceSettings.deafenedPeers.ToArray());
174 | writer.WriteString(string.Join(",", YourVoiceSettings.myTags));
175 | writer.WriteString(string.Join(",", YourVoiceSettings.mutedTags));
176 | writer.WriteString(string.Join(",", YourVoiceSettings.deafenedTags));
177 |
178 | var message = new MirrorMessage {
179 | data = writer.Bytes
180 | };
181 | NetworkClient.Send(message, Channels.Reliable);
182 | }
183 | }
184 | }
185 | #endif
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Runtime/Impl/Networks/Mirror/MirrorClient.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 504fe5edfdfe40245a3ad7149b63c1c0
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Runtime/Impl/Networks/Mirror/MirrorMessage.cs:
--------------------------------------------------------------------------------
1 | #if MIRROR
2 | using System;
3 |
4 | using Mirror;
5 |
6 | namespace Adrenak.UniVoice.Networks {
7 | ///
8 | /// The messages exchanged between the server and client.
9 | /// To see how the Mirror implementation of UniVoice uses this struct
10 | /// find the references to the object in the project.
11 | /// The gist is, it uses BRW (https://www.github.com/adrenak/brw) to
12 | /// write and read data. The data always starts with a tag. All the tags
13 | /// used for this UniVoice Mirror implementation are available in
14 | ///
15 | ///
16 | [Serializable]
17 | public struct MirrorMessage : NetworkMessage {
18 | public byte[] data;
19 | }
20 | }
21 | #endif
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Runtime/Impl/Networks/Mirror/MirrorMessage.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 26fed4dcaa91f29418e64833d3c475f7
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Runtime/Impl/Networks/Mirror/MirrorMessageTags.cs:
--------------------------------------------------------------------------------
1 | #if MIRROR
2 | namespace Adrenak.UniVoice.Networks {
3 | ///
4 | /// The different types of messages we send over Mirror
5 | /// to implement the and
6 | /// interfaces for Mirror
7 | ///
8 | public class MirrorMessageTags {
9 | public const string PEER_INIT = "PEER_INIT";
10 | public const string PEER_JOINED = "PEER_JOINED";
11 | public const string PEER_LEFT = "PEER_LEFT";
12 | public const string AUDIO_FRAME = "AUDIO_FRAME";
13 | public const string VOICE_SETTINGS = "VOICE_SETTINGS";
14 | }
15 | }
16 | #endif
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Runtime/Impl/Networks/Mirror/MirrorMessageTags.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: addae80214286ff4bb4a0676c91ad4b0
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Runtime/Impl/Networks/Mirror/MirrorModeObserver.cs:
--------------------------------------------------------------------------------
1 | #if MIRROR
2 | using Mirror;
3 |
4 | using System;
5 |
6 | using UnityEngine;
7 |
8 | namespace Adrenak.UniVoice.Networks {
9 | ///
10 | /// Observes the mode of the Mirror NetworkManager and fires an event
11 | /// when it changes
12 | ///
13 | public class MirrorModeObserver : MonoBehaviour {
14 | const string TAG = "[MirrorModeObserver]";
15 |
16 | ///
17 | /// Event fired when the Mirror NetworkManager changes the mode
18 | ///
19 | public event Action ModeChanged;
20 |
21 | [Obsolete("Use .New instead.", true)]
22 | public MirrorModeObserver() { }
23 |
24 | NetworkManagerMode lastMode = NetworkManagerMode.Offline;
25 |
26 | ///
27 | /// Creates a new instance of this class on a GameObject
28 | ///
29 | ///
30 | public static MirrorModeObserver New(string name = "") {
31 | var go = new GameObject($"MirrorEventProvider {name}");
32 | DontDestroyOnLoad(go);
33 | return go.AddComponent();
34 | }
35 |
36 | void Update() {
37 | var newMode = NetworkManager.singleton.mode;
38 | if (lastMode != newMode) {
39 | try {
40 | ModeChanged?.Invoke(lastMode, newMode);
41 | }
42 | catch (Exception e) {
43 | Debug.unityLogger.Log(LogType.Error, TAG, "Exception while handling Mirror Mode change: " + e);
44 | }
45 | lastMode = newMode;
46 | }
47 | }
48 | }
49 | }
50 | #endif
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Runtime/Impl/Networks/Mirror/MirrorModeObserver.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: d2ff1b74d93a3f740a02892fcda7e129
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Runtime/Impl/Networks/Mirror/MirrorServer.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 2f3e6da2ea4c92140aefa5dee6abeec6
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Runtime/Impl/Outputs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: ced8c50ab9df8284a8c637b091cc9aaf
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Runtime/Impl/Outputs/StreamedAudioSourceOutput.cs:
--------------------------------------------------------------------------------
1 | using Adrenak.UniMic;
2 |
3 | using UnityEngine;
4 |
5 | namespace Adrenak.UniVoice.Outputs {
6 | ///
7 | /// An implementation of that plays
8 | /// peer audio using StreamedAUdioSource, which is included in UniMic
9 | ///
10 | [RequireComponent(typeof(StreamedAudioSource))]
11 | public class StreamedAudioSourceOutput : MonoBehaviour, IAudioOutput {
12 | const string TAG = "[StreamedAudioSourceOutput]";
13 |
14 | public StreamedAudioSource Stream { get; private set; }
15 |
16 | [System.Obsolete("Cannot use new keyword to create an instance. Use the .New() method instead")]
17 | public StreamedAudioSourceOutput() { }
18 |
19 | ///
20 | /// Creates a new instance using the dependencies.
21 | ///
22 | public static StreamedAudioSourceOutput New() {
23 | var go = new GameObject("StreamedAudioSourceOutput");
24 | DontDestroyOnLoad(go);
25 | var cted = go.AddComponent();
26 | cted.Stream = go.GetComponent();
27 | Debug.unityLogger.Log(LogType.Log, TAG, "StreamedAudioSource created");
28 | return cted;
29 | }
30 |
31 | ///
32 | /// Feeds an incoming into the audio buffer.
33 | ///
34 | ///
35 | public void Feed(AudioFrame frame) {
36 | Stream.Feed(frame.frequency, frame.channelCount, Utils.Bytes.BytesToFloats(frame.samples));
37 | }
38 |
39 | ///
40 | /// Disposes the instance by deleting the GameObject of the component.
41 | ///
42 | public void Dispose() {
43 | Debug.unityLogger.Log(LogType.Log, TAG, "Disposing StreamedAudioSource");
44 | Destroy(gameObject);
45 | }
46 |
47 | ///
48 | /// Creates instances
49 | ///
50 | public class Factory : IAudioOutputFactory {
51 | public IAudioOutput Create() {
52 | return New();
53 | }
54 | }
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Runtime/Impl/Outputs/StreamedAudioSourceOutput.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 17486dc6a1c2cc54585b7e53976d63b9
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Runtime/Interfaces.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: a0b094f3fe5bc624fae2ffb1e18c19a1
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Runtime/Interfaces/IAudioClient.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | namespace Adrenak.UniVoice {
5 | ///
6 | /// Audio client interface.
7 | /// The implementation of this class is generally based on some networking
8 | /// framework such as Mirror, FishNet, Unity Netcode, etc.
9 | ///
10 | ///
11 | /// The identifier data type used by the framework you're using in your implementation.
12 | /// For example, Mirror identifies players using int. So, a MirrorAudioClient class
13 | /// that implements this interface would be MirrorAudioClient : IAudioClient
14 | ///
15 | public interface IAudioClient : IDisposable {
16 | ///
17 | /// The clients peer ID in the voice chat
18 | ///
19 | T ID { get; }
20 |
21 | ///
22 | /// IDs of all the peers (except this client) in the voice chat
23 | ///
24 | List PeerIDs { get; }
25 |
26 | ///
27 | /// The voice settings of this client. Call
28 | /// after making changes to this object to submit the updates to the server.
29 | ///
30 | VoiceSettings YourVoiceSettings { get; }
31 |
32 | ///
33 | /// Fired when this client connects and joins the voice chat
34 | /// Includes the following parameters
35 | /// - own peer ID (int). This should also get assigned to
36 | /// - IDs of other peers. This should also get assigned to
37 | ///
38 | event Action> OnJoined;
39 |
40 | ///
41 | /// Fired when this client disconnects and leaves the voice chat
42 | ///
43 | event Action OnLeft;
44 |
45 | ///
46 | /// Fired when a new peer joins the voice chat.
47 | /// Provides the ID of the client as event data.
48 | ///
49 | event Action OnPeerJoined;
50 |
51 | ///
52 | /// Fired when a client leaves the chatroom.
53 | /// Provides the ID of the client as event data.
54 | ///
55 | event Action OnPeerLeft;
56 |
57 | ///
58 | /// Event fired when an audio frame is received from
59 | /// another peer via the server. Parameters:
60 | /// - peer ID: the ID of the peer that sent the audio frame
61 | /// - AudioFrame: the frame containing audio data
62 | ///
63 | event Action OnReceivedPeerAudioFrame;
64 |
65 | ///
66 | /// Sends an audio frame to the server for being
67 | /// broadcasted to the other peers
68 | ///
69 | /// The audio frame to be sent
70 | void SendAudioFrame(AudioFrame frame);
71 |
72 | ///
73 | /// Submits to the server
74 | ///
75 | void SubmitVoiceSettings();
76 | }
77 | }
78 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Runtime/Interfaces/IAudioClient.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: fe301245402533549bcd0b1df29c1dd9
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Runtime/Interfaces/IAudioFilter.cs:
--------------------------------------------------------------------------------
1 | namespace Adrenak.UniVoice {
2 | ///
3 | /// Offers ways to modify audio after being captured.
4 | /// To prevent the audio from being sent (for example when performing some pass or gating)
5 | /// return an empty byte array (new byte[0])
6 | ///
7 | public interface IAudioFilter {
8 | AudioFrame Run(AudioFrame input);
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Runtime/Interfaces/IAudioFilter.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 7f0c17dee257ca046b3356a3ef107503
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Runtime/Interfaces/IAudioInput.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Adrenak.UniVoice {
4 | ///
5 | /// Source of user voice input. This would usually be implemented
6 | /// over a microphone to get the users voice. But it can also be used
7 | /// in other ways such as streaming an mp4 file from disk. It's just
8 | /// an input and the source doesn't matter.
9 | ///
10 | public interface IAudioInput : IDisposable {
11 | event Action OnFrameReady;
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Runtime/Interfaces/IAudioInput.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: d56c2c84daac5494b93669532bca0b98
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Runtime/Interfaces/IAudioOutput.cs:
--------------------------------------------------------------------------------
1 | using System;
2 |
3 | namespace Adrenak.UniVoice {
4 | ///
5 | /// Responsible for handling incoming audio.
6 | /// You'd normally want a
7 | /// based implementation to play the audio in Unity. But this class can
8 | /// be used in other ways such as streaming the received audio to a server
9 | /// or writing it to a local file. It's just an audio output and the
10 | /// destination depends on your implementation.
11 | ///
12 | public interface IAudioOutput : IDisposable {
13 | ///
14 | /// Feeds a object to the audio output.
15 | ///
16 | /// The audio data to be sent.
17 | void Feed(AudioFrame frame);
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Runtime/Interfaces/IAudioOutput.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 7d34118811133f44eb7ba1b5c3f6bb77
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Runtime/Interfaces/IAudioOutputFactory.cs:
--------------------------------------------------------------------------------
1 | namespace Adrenak.UniVoice {
2 | ///
3 | /// An abstract factory that creates a concrete
4 | ///
5 | public interface IAudioOutputFactory {
6 | ///
7 | /// Creates an instance of a concrete class
8 | ///
9 | IAudioOutput Create();
10 | }
11 | }
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Runtime/Interfaces/IAudioOutputFactory.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 095ec1523c98b7f4997c105a06856823
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Runtime/Interfaces/IAudioServer.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | namespace Adrenak.UniVoice {
5 | ///
6 | /// Audio server interface.
7 | /// The implementation of this class is generally based on some networking
8 | /// framework such as Mirror, FishNet, Unity Netcode, etc.
9 | ///
10 | ///
11 | /// The identifier data type used by the framework you're using in your implementation.
12 | /// For example, Mirror identifies players using int. So, a MirrorAudioClient class
13 | /// that implements this interface would be MirrorAudioServer : IAudioServer
14 | ///
15 | public interface IAudioServer : IDisposable {
16 | ///
17 | /// Event fired when the server starts
18 | ///
19 | event Action OnServerStart;
20 |
21 | ///
22 | /// Event fired when the server stops
23 | ///
24 | event Action OnServerStop;
25 |
26 | ///
27 | /// Event fired when the peer voice settings are updated
28 | ///
29 | event Action OnClientVoiceSettingsUpdated;
30 |
31 | ///
32 | /// IDs of all the clients in the voice chat
33 | ///
34 | List ClientIDs { get; }
35 |
36 | ///
37 | /// of every client connected to the server
38 | ///
39 | Dictionary ClientVoiceSettings { get; }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Runtime/Interfaces/IAudioServer.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 04f9cb1c5b36b564d9fcc45138c78705
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Runtime/Types.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: af3826214be11544e9a38629b9c93566
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Runtime/Types/AudioFrame.cs:
--------------------------------------------------------------------------------
1 | namespace Adrenak.UniVoice {
2 | [System.Serializable]
3 | ///
4 | /// A data structure representing the audio transmitted over the network.
5 | ///
6 | public struct AudioFrame {
7 | ///
8 | /// The UTC Unix timestamp (in ms) when the samples were captured.
9 | /// The timestamp is local to the client the audio was captured from.
10 | ///
11 | public long timestamp;
12 |
13 | ///
14 | /// The frequency (or sampling rate) of the audio
15 | ///
16 | public int frequency;
17 |
18 | ///
19 | /// The number of channels in the audio
20 | ///
21 | public int channelCount;
22 |
23 | ///
24 | /// A byte array representing the audio data
25 | ///
26 | public byte[] samples;
27 | }
28 | }
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Runtime/Types/AudioFrame.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: e3c9ef93e574acf4094f05ab7346bfd5
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Runtime/Types/VoiceSettings.cs:
--------------------------------------------------------------------------------
1 | using System.Collections.Generic;
2 |
3 | namespace Adrenak.UniVoice {
4 | public class VoiceSettings {
5 | ///
6 | /// If true, we don't want to listen to any peer
7 | ///
8 | public bool muteAll;
9 |
10 | ///
11 | /// The peers we don't want to listen to
12 | ///
13 | public List mutedPeers = new List();
14 |
15 | ///
16 | /// If true, we don't want any peer to listen to us
17 | ///
18 | public bool deafenAll;
19 |
20 | ///
21 | /// The peers we don't want listening to us
22 | ///
23 | public List deafenedPeers = new List();
24 |
25 | ///
26 | /// The tags associated with this client.
27 | /// DO NOT USE COMMAS (,)
28 | ///
29 | public List myTags = new List();
30 |
31 | ///
32 | /// The tags, which if associated with a peer, would cause
33 | /// those the audio of that peer to not be send to this peer
34 | /// DO NOT USE COMMAS (,)
35 | ///
36 | public List mutedTags = new List();
37 |
38 | ///
39 | /// The tags, which if associated with a peer, would cause
40 | /// those peers to not receive audio from this client
41 | /// DO NOT USE COMMAS (,)
42 | ///
43 | public List deafenedTags = new List();
44 |
45 | ///
46 | /// Sets the deaf status of a peer
47 | ///
48 | public void SetDeaf(int peerId, bool state) {
49 | if(state) {
50 | if (!deafenedPeers.Contains(peerId))
51 | deafenedPeers.Add(peerId);
52 | }
53 | else {
54 | if (deafenedPeers.Contains(peerId))
55 | deafenedPeers.Remove(peerId);
56 | }
57 | }
58 |
59 | ///
60 | /// Sets the mute status of a peer
61 | ///
62 | public void SetMute(int peerId, bool state) {
63 | if (state) {
64 | if (!mutedPeers.Contains(peerId))
65 | mutedPeers.Add(peerId);
66 | }
67 | else {
68 | if (mutedPeers.Contains(peerId))
69 | mutedPeers.Remove(peerId);
70 | }
71 | }
72 | }
73 | }
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Runtime/Types/VoiceSettings.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 0508f9903d5ba23429db06389f09b8b4
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Samples.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: dc0b6ac6ddea66b4b8aee68c2ee0e99a
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Samples/Basic Setup Scripts.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 7974694d7a30f894db7efac8d8d46573
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Samples/Basic Setup Scripts/FishNet-SinglePrefabObjects.asset:
--------------------------------------------------------------------------------
1 | %YAML 1.1
2 | %TAG !u! tag:unity3d.com,2011:
3 | --- !u!114 &11400000
4 | MonoBehaviour:
5 | m_ObjectHideFlags: 0
6 | m_CorrespondingSourceObject: {fileID: 0}
7 | m_PrefabInstance: {fileID: 0}
8 | m_PrefabAsset: {fileID: 0}
9 | m_GameObject: {fileID: 0}
10 | m_Enabled: 1
11 | m_EditorHideFlags: 0
12 | m_Script: {fileID: 11500000, guid: 4489d77032a81ef42b0067acf2737d4d, type: 3}
13 | m_Name: FishNet-SinglePrefabObjects
14 | m_EditorClassIdentifier:
15 | _prefabs: []
16 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Samples/Basic Setup Scripts/FishNet-SinglePrefabObjects.asset.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: b3f3638a0e223704fb042d01a269adcb
3 | NativeFormatImporter:
4 | externalObjects: {}
5 | mainObjectFileID: 11400000
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Samples/Basic Setup Scripts/UniVoiceFishNetSetupSample.cs:
--------------------------------------------------------------------------------
1 | using UnityEngine;
2 |
3 | using Adrenak.UniMic;
4 | using Adrenak.UniVoice.Networks;
5 | using Adrenak.UniVoice.Outputs;
6 | using Adrenak.UniVoice.Inputs;
7 | using Adrenak.UniVoice.Filters;
8 |
9 | namespace Adrenak.UniVoice.Samples {
10 | ///
11 | /// To get this setup sample to work, ensure that you have done the following:
12 | /// - Import Mirror and add the UNIVOICE_NETWORK_MIRROR compilation symbol to your project
13 | /// - If you want to use RNNoise filter, import RNNoise4Unity into your project and add UNIVOICE_FILTER_RNNOISE4UNITY
14 | /// - Add this component to the first scene of your Unity project
15 | ///
16 | /// *** More info on adding and activating non packaged dependencies is here: https://github.com/adrenak/univoice?tab=readme-ov-file#activating-non-packaged-dependencies ***
17 | ///
18 | /// This is a basic integration script that uses the following to setup UniVoice:
19 | /// - , an implementation of
20 | /// - , an implementation of
21 | /// - , an implementation of that captures audio from a mic
22 | /// - , an implementation of that is basically
23 | /// an idle audio input used when there is no input device
24 | /// - , an implementation of that removes noise from
25 | /// captured audio.
26 | /// - , an implementation of that encodes captured audio
27 | /// using Concentus (C# Opus) to reduce the size of audio frames
28 | /// - , an implementation of that decodes incoming audio
29 | /// using Concentus to decode and make the audio frame playable.
30 | ///
31 | public class UniVoiceFishNetSetupSample : MonoBehaviour {
32 | const string TAG = "[UniVoiceFishNetSetupSample]";
33 |
34 | ///
35 | /// Whether UniVoice has been setup successfully. This field will return true if the setup was successful.
36 | /// It runs on both server and client.
37 | ///
38 | public static bool HasSetUp { get; private set; }
39 |
40 | ///
41 | /// The server object.
42 | ///
43 | public static IAudioServer AudioServer { get; private set; }
44 |
45 | ///
46 | /// The client session.
47 | ///
48 | public static ClientSession ClientSession { get; private set; }
49 |
50 | [SerializeField] bool useRNNoise4UnityIfAvailable = true;
51 |
52 | [SerializeField] bool useConcentusEncodeAndDecode = true;
53 |
54 | [SerializeField] bool useVad = true;
55 |
56 | void Start() {
57 | if (HasSetUp) {
58 | Debug.unityLogger.Log(LogType.Log, TAG, "UniVoice is already set up. Ignoring...");
59 | return;
60 | }
61 | HasSetUp = Setup();
62 | }
63 |
64 | bool Setup() {
65 | Debug.unityLogger.Log(LogType.Log, TAG, "Trying to setup UniVoice");
66 |
67 | bool failed = false;
68 |
69 | // Set setup the AudioServer and ClientSession on ALL builds. This means that you'd
70 | // have a ClientSession on a dedicated server, even though there's not much you can do with it.
71 | // Similarly, a client would also have an AudioServer object. But it would just be inactive.
72 | // This sample is for ease of use and to get something working quickly, so we don't bother
73 | // with these minor details. Note that doing so does not have any performance implications
74 | // so you can do this, so you could keep this approach without any tradeoffs.
75 | var createdAudioServer = SetupAudioServer();
76 | if (!createdAudioServer) {
77 | Debug.unityLogger.Log(LogType.Error, TAG, "Could not setup UniVoice server.");
78 | failed = true;
79 | }
80 |
81 | var setupAudioClient = SetupClientSession();
82 | if (!setupAudioClient) {
83 | Debug.unityLogger.Log(LogType.Error, TAG, "Could not setup UniVoice client.");
84 | failed = true;
85 | }
86 |
87 | if (!failed)
88 | Debug.unityLogger.Log(LogType.Log, TAG, "UniVoice successfully setup!");
89 | else
90 | Debug.unityLogger.Log(LogType.Error, TAG, $"Refer to the notes on top of {typeof(UniVoiceMirrorSetupSample).Name}.cs for setup instructions.");
91 | return !failed;
92 | }
93 |
94 | bool SetupAudioServer() {
95 | #if FISHNET
96 | // ---- CREATE AUDIO SERVER AND SUBSCRIBE TO EVENTS TO PRINT LOGS ----
97 | // We create a server. If this code runs in server mode, MirrorServer will take care
98 | // or automatically handling all incoming messages. On a device connecting as a client,
99 | // this code doesn't do anything.
100 | AudioServer = new FishNetServer();
101 | Debug.unityLogger.Log(LogType.Log, TAG, "Created MirrorServer object");
102 |
103 | AudioServer.OnServerStart += () => {
104 | Debug.unityLogger.Log(LogType.Log, TAG, "Server started");
105 | };
106 |
107 | AudioServer.OnServerStop += () => {
108 | Debug.unityLogger.Log(LogType.Log, TAG, "Server stopped");
109 | };
110 | return true;
111 | #else
112 | Debug.unityLogger.Log(LogType.Error, TAG, "MirrorServer implementation not found!");
113 | return false;
114 | #endif
115 | }
116 |
117 | bool SetupClientSession() {
118 | #if FISHNET
119 | // ---- CREATE AUDIO CLIENT AND SUBSCRIBE TO EVENTS ----
120 | IAudioClient client = new FishNetClient();
121 | client.OnJoined += (id, peerIds) => {
122 | Debug.unityLogger.Log(LogType.Log, TAG, $"You are Peer ID {id}");
123 | };
124 |
125 | client.OnLeft += () => {
126 | Debug.unityLogger.Log(LogType.Log, TAG, "You left the chatroom");
127 | };
128 |
129 | // When a peer joins, we instantiate a new peer view
130 | client.OnPeerJoined += id => {
131 | Debug.unityLogger.Log(LogType.Log, TAG, $"Peer {id} joined");
132 | };
133 |
134 | // When a peer leaves, destroy the UI representing them
135 | client.OnPeerLeft += id => {
136 | Debug.unityLogger.Log(LogType.Log, TAG, $"Peer {id} left");
137 | };
138 |
139 | Debug.unityLogger.Log(LogType.Log, TAG, "Created MirrorClient object");
140 |
141 | // ---- CREATE AUDIO INPUT ----
142 | IAudioInput input;
143 | // Since in this sample we use microphone input via UniMic, we first check if there
144 | // are any mic devices available.
145 | Mic.Init(); // Must do this to use the Mic class
146 | if (Mic.AvailableDevices.Count == 0) {
147 | Debug.unityLogger.Log(LogType.Log, TAG, "Device has no microphones." +
148 | "Will only be able to hear other clients, cannot send any audio.");
149 | input = new EmptyAudioInput();
150 | Debug.unityLogger.Log(LogType.Log, TAG, "Created EmptyAudioInput");
151 | }
152 | else {
153 | // Get the first recording device that we have available and start it.
154 | // Then we create a UniMicInput instance that requires the mic object
155 | // For more info on UniMic refer to https://www.github.com/adrenak/unimic
156 | var mic = Mic.AvailableDevices[0];
157 | mic.StartRecording(60);
158 | Debug.unityLogger.Log(LogType.Log, TAG, "Started recording with Mic device named." +
159 | mic.Name + $" at frequency {mic.SamplingFrequency} with frame duration {mic.FrameDurationMS} ms.");
160 | input = new UniMicInput(mic);
161 | Debug.unityLogger.Log(LogType.Log, TAG, "Created UniMicInput");
162 | }
163 |
164 | // ---- CREATE AUDIO OUTPUT FACTORY ----
165 | IAudioOutputFactory outputFactory;
166 | // We want the incoming audio from peers to be played via the StreamedAudioSourceOutput
167 | // implementation of IAudioSource interface. So we get the factory for it.
168 | outputFactory = new StreamedAudioSourceOutput.Factory();
169 | Debug.unityLogger.Log(LogType.Log, TAG, "Using StreamedAudioSourceOutput.Factory as output factory");
170 |
171 | // ---- CREATE CLIENT SESSION AND ADD FILTERS TO IT ----
172 | // With the client, input and output factory ready, we create create the client session
173 | ClientSession = new ClientSession(client, input, outputFactory);
174 | Debug.unityLogger.Log(LogType.Log, TAG, "Created session");
175 |
176 | #if UNIVOICE_FILTER_RNNOISE4UNITY
177 | if(useRNNoise4UnityIfAvailable) {
178 | // RNNoiseFilter to remove noise from captured audio
179 | session.InputFilters.Add(new RNNoiseFilter());
180 | Debug.unityLogger.Log(LogType.Log, TAG, "Registered RNNoiseFilter as an input filter");
181 | }
182 | #endif
183 |
184 | if (useVad) {
185 | // We add the VAD filter after RNNoise.
186 | // This way lot of the background noise has been removed, VAD is truly trying to detect voice
187 | ClientSession.InputFilters.Add(new SimpleVadFilter(new SimpleVad()));
188 | }
189 |
190 | if (useConcentusEncodeAndDecode) {
191 | // ConcentureEncoder filter to encode captured audio that reduces the audio frame size
192 | ClientSession.InputFilters.Add(new ConcentusEncodeFilter());
193 | Debug.unityLogger.Log(LogType.Log, TAG, "Registered ConcentusEncodeFilter as an input filter");
194 |
195 | // For incoming audio register the ConcentusDecodeFilter to decode the encoded audio received from other clients
196 | ClientSession.AddOutputFilter(() => new ConcentusDecodeFilter());
197 | Debug.unityLogger.Log(LogType.Log, TAG, "Registered ConcentusDecodeFilter as an output filter");
198 | }
199 |
200 | return true;
201 | #else
202 | Debug.unityLogger.Log(LogType.Error, TAG, "MirrorClient implementation not found!");
203 | return false;
204 | #endif
205 | }
206 | }
207 | }
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Samples/Basic Setup Scripts/UniVoiceFishNetSetupSample.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 812e71335abfc894ea5415f23adf5ea6
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Samples/Basic Setup Scripts/UniVoiceFishNetSetupSample.unity.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: fcc19499518b4204d8fdeb4ddd323bde
3 | DefaultImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Samples/Basic Setup Scripts/UniVoiceMirrorSetupSample.cs:
--------------------------------------------------------------------------------
1 | using UnityEngine;
2 |
3 | using Adrenak.UniMic;
4 | using Adrenak.UniVoice.Networks;
5 | using Adrenak.UniVoice.Outputs;
6 | using Adrenak.UniVoice.Inputs;
7 | using Adrenak.UniVoice.Filters;
8 |
9 | namespace Adrenak.UniVoice.Samples {
10 | ///
11 | /// To get this setup sample to work, ensure that you have done the following:
12 | /// - Import Mirror and add the UNIVOICE_NETWORK_MIRROR compilation symbol to your project
13 | /// - If you want to use RNNoise filter, import RNNoise4Unity into your project and add UNIVOICE_FILTER_RNNOISE4UNITY
14 | /// - Add this component to the first scene of your Unity project
15 | ///
16 | /// *** More info on adding and activating non packaged dependencies is here: https://github.com/adrenak/univoice?tab=readme-ov-file#activating-non-packaged-dependencies ***
17 | ///
18 | /// This is a basic integration script that uses the following to setup UniVoice:
19 | /// - , an implementation of
20 | /// - , an implementation of
21 | /// - , an implementation of that captures audio from a mic
22 | /// - , an implementation of that is basically
23 | /// an idle audio input used when there is no input device
24 | /// - , an implementation of that removes noise from
25 | /// captured audio.
26 | /// - , an implementation of that encodes captured audio
27 | /// using Concentus (C# Opus) to reduce the size of audio frames
28 | /// - , an implementation of that decodes incoming audio
29 | /// using Concentus to decode and make the audio frame playable.
30 | ///
31 | public class UniVoiceMirrorSetupSample : MonoBehaviour {
32 | const string TAG = "[BasicUniVoiceSetupSample]";
33 |
34 | ///
35 | /// Whether UniVoice has been setup successfully. This field will return true if the setup was successful.
36 | /// It runs on both server and client.
37 | ///
38 | public static bool HasSetUp { get; private set; }
39 |
40 | ///
41 | /// The server object.
42 | ///
43 | public static IAudioServer AudioServer { get; private set; }
44 |
45 | ///
46 | /// The client session.
47 | ///
48 | public static ClientSession ClientSession { get; private set; }
49 |
50 | [SerializeField] bool useRNNoise4UnityIfAvailable = true;
51 |
52 | [SerializeField] bool useConcentusEncodeAndDecode = true;
53 |
54 | [SerializeField] bool useVad = true;
55 |
56 | void Start() {
57 | if (HasSetUp) {
58 | Debug.unityLogger.Log(LogType.Log, TAG, "UniVoice is already set up. Ignoring...");
59 | return;
60 | }
61 | HasSetUp = Setup();
62 | }
63 |
64 | bool Setup() {
65 | Debug.unityLogger.Log(LogType.Log, TAG, "Trying to setup UniVoice");
66 |
67 | bool failed = false;
68 |
69 | // Set setup the AudioServer and ClientSession on ALL builds. This means that you'd
70 | // have a ClientSession on a dedicated server, even though there's not much you can do with it.
71 | // Similarly, a client would also have an AudioServer object. But it would just be inactive.
72 | // This sample is for ease of use and to get something working quickly, so we don't bother
73 | // with these minor details. Note that doing so does not have any performance implications
74 | // so you can do this, so you could keep this approach without any tradeoffs.
75 | var createdAudioServer = SetupAudioServer();
76 | if (!createdAudioServer) {
77 | Debug.unityLogger.Log(LogType.Error, TAG, "Could not setup UniVoice server.");
78 | failed = true;
79 | }
80 |
81 | var setupAudioClient = SetupClientSession();
82 | if (!setupAudioClient) {
83 | Debug.unityLogger.Log(LogType.Error, TAG, "Could not setup UniVoice client.");
84 | failed = true;
85 | }
86 |
87 | if (!failed)
88 | Debug.unityLogger.Log(LogType.Log, TAG, "UniVoice successfully setup!");
89 | else
90 | Debug.unityLogger.Log(LogType.Error, TAG, $"Refer to the notes on top of {typeof(UniVoiceMirrorSetupSample).Name}.cs for setup instructions.");
91 | return !failed;
92 | }
93 |
94 | bool SetupAudioServer() {
95 | #if MIRROR
96 | // ---- CREATE AUDIO SERVER AND SUBSCRIBE TO EVENTS TO PRINT LOGS ----
97 | // We create a server. If this code runs in server mode, MirrorServer will take care
98 | // or automatically handling all incoming messages. On a device connecting as a client,
99 | // this code doesn't do anything.
100 | AudioServer = new MirrorServer();
101 | Debug.unityLogger.Log(LogType.Log, TAG, "Created MirrorServer object");
102 |
103 | AudioServer.OnServerStart += () => {
104 | Debug.unityLogger.Log(LogType.Log, TAG, "Server started");
105 | };
106 |
107 | AudioServer.OnServerStop += () => {
108 | Debug.unityLogger.Log(LogType.Log, TAG, "Server stopped");
109 | };
110 | return true;
111 | #else
112 | Debug.unityLogger.Log(LogType.Error, TAG, "MirrorServer implementation not found!");
113 | return false;
114 | #endif
115 | }
116 |
117 | bool SetupClientSession() {
118 | #if MIRROR
119 | // ---- CREATE AUDIO CLIENT AND SUBSCRIBE TO EVENTS ----
120 | IAudioClient client = new MirrorClient();
121 | client.OnJoined += (id, peerIds) => {
122 | Debug.unityLogger.Log(LogType.Log, TAG, $"You are Peer ID {id}");
123 | };
124 |
125 | client.OnLeft += () => {
126 | Debug.unityLogger.Log(LogType.Log, TAG, "You left the chatroom");
127 | };
128 |
129 | // When a peer joins, we instantiate a new peer view
130 | client.OnPeerJoined += id => {
131 | Debug.unityLogger.Log(LogType.Log, TAG, $"Peer {id} joined");
132 | };
133 |
134 | // When a peer leaves, destroy the UI representing them
135 | client.OnPeerLeft += id => {
136 | Debug.unityLogger.Log(LogType.Log, TAG, $"Peer {id} left");
137 | };
138 |
139 | Debug.unityLogger.Log(LogType.Log, TAG, "Created MirrorClient object");
140 |
141 | // ---- CREATE AUDIO INPUT ----
142 | IAudioInput input;
143 | // Since in this sample we use microphone input via UniMic, we first check if there
144 | // are any mic devices available.
145 | Mic.Init(); // Must do this to use the Mic class
146 | if (Mic.AvailableDevices.Count == 0) {
147 | Debug.unityLogger.Log(LogType.Log, TAG, "Device has no microphones." +
148 | "Will only be able to hear other clients, cannot send any audio.");
149 | input = new EmptyAudioInput();
150 | Debug.unityLogger.Log(LogType.Log, TAG, "Created EmptyAudioInput");
151 | }
152 | else {
153 | // Get the first recording device that we have available and start it.
154 | // Then we create a UniMicInput instance that requires the mic object
155 | // For more info on UniMic refer to https://www.github.com/adrenak/unimic
156 | var mic = Mic.AvailableDevices[0];
157 | mic.StartRecording(60);
158 | Debug.unityLogger.Log(LogType.Log, TAG, "Started recording with Mic device named." +
159 | mic.Name + $" at frequency {mic.SamplingFrequency} with frame duration {mic.FrameDurationMS} ms.");
160 | input = new UniMicInput(mic);
161 | Debug.unityLogger.Log(LogType.Log, TAG, "Created UniMicInput");
162 | }
163 |
164 | // ---- CREATE AUDIO OUTPUT FACTORY ----
165 | IAudioOutputFactory outputFactory;
166 | // We want the incoming audio from peers to be played via the StreamedAudioSourceOutput
167 | // implementation of IAudioSource interface. So we get the factory for it.
168 | outputFactory = new StreamedAudioSourceOutput.Factory();
169 | Debug.unityLogger.Log(LogType.Log, TAG, "Using StreamedAudioSourceOutput.Factory as output factory");
170 |
171 | // ---- CREATE CLIENT SESSION AND ADD FILTERS TO IT ----
172 | // With the client, input and output factory ready, we create create the client session
173 | ClientSession = new ClientSession(client, input, outputFactory);
174 | Debug.unityLogger.Log(LogType.Log, TAG, "Created session");
175 |
176 | #if UNIVOICE_FILTER_RNNOISE4UNITY
177 | if(useRNNoise4UnityIfAvailable) {
178 | // RNNoiseFilter to remove noise from captured audio
179 | session.InputFilters.Add(new RNNoiseFilter());
180 | Debug.unityLogger.Log(LogType.Log, TAG, "Registered RNNoiseFilter as an input filter");
181 | }
182 | #endif
183 |
184 | if (useVad) {
185 | // We add the VAD filter after RNNoise.
186 | // This way lot of the background noise has been removed, VAD is truly trying to detect voice
187 | ClientSession.InputFilters.Add(new SimpleVadFilter(new SimpleVad()));
188 | }
189 |
190 | if (useConcentusEncodeAndDecode) {
191 | // ConcentureEncoder filter to encode captured audio that reduces the audio frame size
192 | ClientSession.InputFilters.Add(new ConcentusEncodeFilter());
193 | Debug.unityLogger.Log(LogType.Log, TAG, "Registered ConcentusEncodeFilter as an input filter");
194 |
195 | // For incoming audio register the ConcentusDecodeFilter to decode the encoded audio received from other clients
196 | ClientSession.AddOutputFilter(() => new ConcentusDecodeFilter());
197 | Debug.unityLogger.Log(LogType.Log, TAG, "Registered ConcentusDecodeFilter as an output filter");
198 | }
199 |
200 | return true;
201 | #else
202 | Debug.unityLogger.Log(LogType.Error, TAG, "MirrorClient implementation not found!");
203 | return false;
204 | #endif
205 | }
206 | }
207 | }
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Samples/Basic Setup Scripts/UniVoiceMirrorSetupSample.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: d0dec1d2df214b44f8cb106b071695ce
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Samples/Basic Setup Scripts/UniVoiceMirrorSetupSample.unity.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: e2397deac1a1ddc489d2e846a22708fa
3 | DefaultImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Samples/Group Chat Sample.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 45c86a5896484134fa5fa36ff14a0d34
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Samples/Group Chat Sample/Prefabs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 919b175f03c1b974f91e263e12b82e38
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Samples/Group Chat Sample/Prefabs/Mic Toggle.prefab:
--------------------------------------------------------------------------------
1 | %YAML 1.1
2 | %TAG !u! tag:unity3d.com,2011:
3 | --- !u!1 &8149259125552772643
4 | GameObject:
5 | m_ObjectHideFlags: 0
6 | m_CorrespondingSourceObject: {fileID: 0}
7 | m_PrefabInstance: {fileID: 0}
8 | m_PrefabAsset: {fileID: 0}
9 | serializedVersion: 6
10 | m_Component:
11 | - component: {fileID: 8149259125552772640}
12 | - component: {fileID: 8149259125552772641}
13 | m_Layer: 5
14 | m_Name: Mic Toggle
15 | m_TagString: Untagged
16 | m_Icon: {fileID: 0}
17 | m_NavMeshLayer: 0
18 | m_StaticEditorFlags: 0
19 | m_IsActive: 1
20 | --- !u!224 &8149259125552772640
21 | RectTransform:
22 | m_ObjectHideFlags: 0
23 | m_CorrespondingSourceObject: {fileID: 0}
24 | m_PrefabInstance: {fileID: 0}
25 | m_PrefabAsset: {fileID: 0}
26 | m_GameObject: {fileID: 8149259125552772643}
27 | m_LocalRotation: {x: -0, y: -0, z: -0, w: 1}
28 | m_LocalPosition: {x: 0, y: 0, z: 0}
29 | m_LocalScale: {x: 1, y: 1, z: 1}
30 | m_Children:
31 | - {fileID: 8149259127049677661}
32 | m_Father: {fileID: 0}
33 | m_RootOrder: 0
34 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
35 | m_AnchorMin: {x: 1, y: 1}
36 | m_AnchorMax: {x: 1, y: 1}
37 | m_AnchoredPosition: {x: -97.72986, y: -207.73003}
38 | m_SizeDelta: {x: 153.73212, y: 63.91461}
39 | m_Pivot: {x: 0.5, y: 0.5}
40 | --- !u!114 &8149259125552772641
41 | MonoBehaviour:
42 | m_ObjectHideFlags: 0
43 | m_CorrespondingSourceObject: {fileID: 0}
44 | m_PrefabInstance: {fileID: 0}
45 | m_PrefabAsset: {fileID: 0}
46 | m_GameObject: {fileID: 8149259125552772643}
47 | m_Enabled: 1
48 | m_EditorHideFlags: 0
49 | m_Script: {fileID: 11500000, guid: 9085046f02f69544eb97fd06b6048fe2, type: 3}
50 | m_Name:
51 | m_EditorClassIdentifier:
52 | m_Navigation:
53 | m_Mode: 3
54 | m_SelectOnUp: {fileID: 0}
55 | m_SelectOnDown: {fileID: 0}
56 | m_SelectOnLeft: {fileID: 0}
57 | m_SelectOnRight: {fileID: 0}
58 | m_Transition: 1
59 | m_Colors:
60 | m_NormalColor: {r: 1, g: 1, b: 1, a: 1}
61 | m_HighlightedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
62 | m_PressedColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 1}
63 | m_SelectedColor: {r: 0.9607843, g: 0.9607843, b: 0.9607843, a: 1}
64 | m_DisabledColor: {r: 0.78431374, g: 0.78431374, b: 0.78431374, a: 0.5019608}
65 | m_ColorMultiplier: 1
66 | m_FadeDuration: 0.1
67 | m_SpriteState:
68 | m_HighlightedSprite: {fileID: 0}
69 | m_PressedSprite: {fileID: 0}
70 | m_SelectedSprite: {fileID: 0}
71 | m_DisabledSprite: {fileID: 0}
72 | m_AnimationTriggers:
73 | m_NormalTrigger: Normal
74 | m_HighlightedTrigger: Highlighted
75 | m_PressedTrigger: Pressed
76 | m_SelectedTrigger: Highlighted
77 | m_DisabledTrigger: Disabled
78 | m_Interactable: 1
79 | m_TargetGraphic: {fileID: 8149259127049677658}
80 | toggleTransition: 1
81 | graphic: {fileID: 8149259127216001340}
82 | m_Group: {fileID: 0}
83 | onValueChanged:
84 | m_PersistentCalls:
85 | m_Calls: []
86 | m_IsOn: 1
87 | --- !u!1 &8149259127049677660
88 | GameObject:
89 | m_ObjectHideFlags: 0
90 | m_CorrespondingSourceObject: {fileID: 0}
91 | m_PrefabInstance: {fileID: 0}
92 | m_PrefabAsset: {fileID: 0}
93 | serializedVersion: 6
94 | m_Component:
95 | - component: {fileID: 8149259127049677661}
96 | - component: {fileID: 8149259127049677659}
97 | - component: {fileID: 8149259127049677658}
98 | m_Layer: 5
99 | m_Name: Off
100 | m_TagString: Untagged
101 | m_Icon: {fileID: 0}
102 | m_NavMeshLayer: 0
103 | m_StaticEditorFlags: 0
104 | m_IsActive: 1
105 | --- !u!224 &8149259127049677661
106 | RectTransform:
107 | m_ObjectHideFlags: 0
108 | m_CorrespondingSourceObject: {fileID: 0}
109 | m_PrefabInstance: {fileID: 0}
110 | m_PrefabAsset: {fileID: 0}
111 | m_GameObject: {fileID: 8149259127049677660}
112 | m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
113 | m_LocalPosition: {x: 0, y: 0, z: 0}
114 | m_LocalScale: {x: 1, y: 1, z: 1}
115 | m_Children:
116 | - {fileID: 8149259127216001343}
117 | m_Father: {fileID: 8149259125552772640}
118 | m_RootOrder: 0
119 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
120 | m_AnchorMin: {x: 0, y: 0}
121 | m_AnchorMax: {x: 1, y: 1}
122 | m_AnchoredPosition: {x: 0, y: 0}
123 | m_SizeDelta: {x: -0.0004272461, y: 0}
124 | m_Pivot: {x: 0.5, y: 0.5}
125 | --- !u!222 &8149259127049677659
126 | CanvasRenderer:
127 | m_ObjectHideFlags: 0
128 | m_CorrespondingSourceObject: {fileID: 0}
129 | m_PrefabInstance: {fileID: 0}
130 | m_PrefabAsset: {fileID: 0}
131 | m_GameObject: {fileID: 8149259127049677660}
132 | m_CullTransparentMesh: 0
133 | --- !u!114 &8149259127049677658
134 | MonoBehaviour:
135 | m_ObjectHideFlags: 0
136 | m_CorrespondingSourceObject: {fileID: 0}
137 | m_PrefabInstance: {fileID: 0}
138 | m_PrefabAsset: {fileID: 0}
139 | m_GameObject: {fileID: 8149259127049677660}
140 | m_Enabled: 1
141 | m_EditorHideFlags: 0
142 | m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
143 | m_Name:
144 | m_EditorClassIdentifier:
145 | m_Material: {fileID: 0}
146 | m_Color: {r: 1, g: 1, b: 1, a: 1}
147 | m_RaycastTarget: 1
148 | m_Maskable: 1
149 | m_OnCullStateChanged:
150 | m_PersistentCalls:
151 | m_Calls: []
152 | m_Sprite: {fileID: 21300000, guid: fef297624ed5b2043ac6c92214afbcf3, type: 3}
153 | m_Type: 0
154 | m_PreserveAspect: 1
155 | m_FillCenter: 1
156 | m_FillMethod: 4
157 | m_FillAmount: 1
158 | m_FillClockwise: 1
159 | m_FillOrigin: 0
160 | m_UseSpriteMesh: 0
161 | m_PixelsPerUnitMultiplier: 1
162 | --- !u!1 &8149259127216001342
163 | GameObject:
164 | m_ObjectHideFlags: 0
165 | m_CorrespondingSourceObject: {fileID: 0}
166 | m_PrefabInstance: {fileID: 0}
167 | m_PrefabAsset: {fileID: 0}
168 | serializedVersion: 6
169 | m_Component:
170 | - component: {fileID: 8149259127216001343}
171 | - component: {fileID: 8149259127216001341}
172 | - component: {fileID: 8149259127216001340}
173 | m_Layer: 5
174 | m_Name: On
175 | m_TagString: Untagged
176 | m_Icon: {fileID: 0}
177 | m_NavMeshLayer: 0
178 | m_StaticEditorFlags: 0
179 | m_IsActive: 1
180 | --- !u!224 &8149259127216001343
181 | RectTransform:
182 | m_ObjectHideFlags: 0
183 | m_CorrespondingSourceObject: {fileID: 0}
184 | m_PrefabInstance: {fileID: 0}
185 | m_PrefabAsset: {fileID: 0}
186 | m_GameObject: {fileID: 8149259127216001342}
187 | m_LocalRotation: {x: 0, y: 0, z: 0, w: 1}
188 | m_LocalPosition: {x: 0, y: 0, z: 0}
189 | m_LocalScale: {x: 1, y: 1, z: 1}
190 | m_Children: []
191 | m_Father: {fileID: 8149259127049677661}
192 | m_RootOrder: 0
193 | m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0}
194 | m_AnchorMin: {x: 0, y: 0}
195 | m_AnchorMax: {x: 1, y: 1}
196 | m_AnchoredPosition: {x: -0.00011444092, y: 0}
197 | m_SizeDelta: {x: -0.0002746582, y: 0}
198 | m_Pivot: {x: 0.5, y: 0.5}
199 | --- !u!222 &8149259127216001341
200 | CanvasRenderer:
201 | m_ObjectHideFlags: 0
202 | m_CorrespondingSourceObject: {fileID: 0}
203 | m_PrefabInstance: {fileID: 0}
204 | m_PrefabAsset: {fileID: 0}
205 | m_GameObject: {fileID: 8149259127216001342}
206 | m_CullTransparentMesh: 0
207 | --- !u!114 &8149259127216001340
208 | MonoBehaviour:
209 | m_ObjectHideFlags: 0
210 | m_CorrespondingSourceObject: {fileID: 0}
211 | m_PrefabInstance: {fileID: 0}
212 | m_PrefabAsset: {fileID: 0}
213 | m_GameObject: {fileID: 8149259127216001342}
214 | m_Enabled: 1
215 | m_EditorHideFlags: 0
216 | m_Script: {fileID: 11500000, guid: fe87c0e1cc204ed48ad3b37840f39efc, type: 3}
217 | m_Name:
218 | m_EditorClassIdentifier:
219 | m_Material: {fileID: 0}
220 | m_Color: {r: 1, g: 1, b: 1, a: 1}
221 | m_RaycastTarget: 1
222 | m_Maskable: 1
223 | m_OnCullStateChanged:
224 | m_PersistentCalls:
225 | m_Calls: []
226 | m_Sprite: {fileID: 21300000, guid: 091b808e85d9e3843ab27e808a68ad44, type: 3}
227 | m_Type: 0
228 | m_PreserveAspect: 1
229 | m_FillCenter: 1
230 | m_FillMethod: 4
231 | m_FillAmount: 1
232 | m_FillClockwise: 1
233 | m_FillOrigin: 0
234 | m_UseSpriteMesh: 0
235 | m_PixelsPerUnitMultiplier: 1
236 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Samples/Group Chat Sample/Prefabs/Mic Toggle.prefab.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 4d8ec9d3bffb8a34498598c2c5a4eb64
3 | PrefabImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Samples/Group Chat Sample/Prefabs/Peer View.prefab.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 4df848340ed9e8d4aa53174f49205cc6
3 | PrefabImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Samples/Group Chat Sample/Scenes.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 08c719f363433af489cc02afdc250c3c
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Samples/Group Chat Sample/Scenes/GroupVoiceCallSample-Mirror.unity.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 112506fba9c2d954d9eec18ab175ec08
3 | DefaultImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Samples/Group Chat Sample/Scripts.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 37aed8b1bd69e794fad8f9688e8c68bb
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Samples/Group Chat Sample/Scripts/GroupVoiceCallMirrorSample.cs:
--------------------------------------------------------------------------------
1 | #if UNIVOICE_MIRROR_NETWORK || UNIVOICE_NETWORK_MIRROR
2 | using System.Linq;
3 | using System.Collections;
4 | using System.Collections.Generic;
5 |
6 | using UnityEngine;
7 | using UnityEngine.UI;
8 | using UnityEngine.Android;
9 |
10 | using Adrenak.UniMic;
11 | using Adrenak.UniVoice.Networks;
12 | using Adrenak.UniVoice.Outputs;
13 | using Adrenak.UniVoice.Inputs;
14 | using Adrenak.UniVoice.Filters;
15 |
16 | namespace Adrenak.UniVoice.Samples {
17 | public class GroupVoiceCallMirrorSample : MonoBehaviour {
18 | public Transform peerViewContainer;
19 | public PeerView peerViewTemplate;
20 | public Text chatroomMessage;
21 | public Toggle muteSelfToggle;
22 | public Toggle muteOthersToggle;
23 |
24 | ClientSession session;
25 | Dictionary peerViews = new Dictionary();
26 |
27 | IEnumerator Start() {
28 | Mic.Init();
29 | Screen.sleepTimeout = SleepTimeout.NeverSleep;
30 |
31 | #if UNITY_ANDROID
32 | while(!Permission.HasUserAuthorizedPermission("android.permission.RECORD_AUDIO")) {
33 | Permission.RequestUserPermission("android.permission.RECORD_AUDIO");
34 | yield return new WaitForSeconds(1);
35 | }
36 | #endif
37 | yield return null;
38 |
39 | InitializeSession();
40 |
41 | // Listen to changes in the toggles to deafen or mute all peers
42 | muteSelfToggle.SetIsOnWithoutNotify(session.Client.YourVoiceSettings.deafenAll);
43 | muteSelfToggle.onValueChanged.AddListener(value => {
44 | session.Client.YourVoiceSettings.deafenAll = value;
45 | session.Client.SubmitVoiceSettings();
46 | });
47 |
48 | muteOthersToggle.SetIsOnWithoutNotify(session.Client.YourVoiceSettings.muteAll);
49 | muteOthersToggle.onValueChanged.AddListener(value => {
50 | session.Client.YourVoiceSettings.muteAll = value;
51 | session.Client.SubmitVoiceSettings();
52 | });
53 | }
54 |
55 | void InitializeSession() {
56 | // We create a server. If this code runs in server mode, MirrorServer will take care
57 | // or automatically handling all incoming messages.
58 | var server = new MirrorServer();
59 |
60 | // Since in this sample we use microphone input via UniMic, we first check if there
61 | // are any mic devices available.
62 | if (Mic.AvailableDevices.Count == 0)
63 | return;
64 |
65 | // Create a client for this device
66 | var client = new MirrorClient();
67 |
68 | // Get the first recording device that we have available and start it.
69 | // Then we create a UniMicInput instance that requires the mic object
70 | // For more info on UniMic refer to https://www.github.com/adrenak/unimic
71 | var mic = Mic.AvailableDevices[0];
72 | mic.StartRecording();
73 | var input = new UniMicInput(mic);
74 |
75 | // We want the incoming audio from peers to be played via the StreamedAudioSourceOutput
76 | // implementation of IAudioSource interface. So we get the factory for it.
77 | var outputFactory = new StreamedAudioSourceOutput.Factory();
78 |
79 | // With the client, input and output factory ready, we create create the client session
80 | session = new ClientSession(client, input, outputFactory);
81 |
82 | // We add some filters to the input audio
83 | // - The first is audio blur, so that the audio that's been captured by this client
84 | // has lesser noise
85 | session.InputFilters.Add(new GaussianAudioBlur());
86 | // - The next one is the Opus encoder filter. This is VERY important. Without this the
87 | // outgoing data would be very large, usually by a factor of 10 or more.
88 | session.InputFilters.Add(new ConcentusEncodeFilter());
89 |
90 | // Next, for incoming audio we register the Concentus decode filter as the audio we'd
91 | // receive from other clients would be encoded and not readily playable
92 | session.AddOutputFilter(() => new ConcentusDecodeFilter());
93 |
94 | // Subscribe to some server events
95 | server.OnServerStart += () => {
96 | ShowMessage("Server started");
97 | };
98 |
99 | server.OnServerStop += () => {
100 | ShowMessage("Server stopped");
101 | };
102 |
103 | // We subscribe to some client events to show updates on the UI when you join or leave
104 | client.OnJoined += (id, peerIds) => {
105 | ShowMessage($"You are Peer ID {id} your peers are {string.Join(", ", peerIds)}");
106 | };
107 |
108 | client.OnLeft += () => {
109 | ShowMessage("You left the chatroom");
110 | foreach (var view in peerViews)
111 | Destroy(view.Value.gameObject);
112 | peerViews.Clear();
113 | };
114 |
115 | // When a peer joins, we instantiate a new peer view
116 | client.OnPeerJoined += id => {
117 | var view = Instantiate(peerViewTemplate, peerViewContainer);
118 | view.SetPeerID(id);
119 |
120 | // we listen to the changes in the individual toggle buttons
121 | // on this view to selectively mute or deafen the peer that
122 | // is represented by the view
123 | view.OnAllowIncomingAudioChange += x => {
124 | client.YourVoiceSettings.SetMute(id, !x);
125 | client.SubmitVoiceSettings();
126 | };
127 | view.OnAllowOutgoingAudioChange += x => {
128 | client.YourVoiceSettings.SetDeaf(id, !x);
129 | client.SubmitVoiceSettings();
130 | };
131 | peerViews.Add(id, view);
132 | };
133 |
134 | // When a peer leaves, destroy the UI representing them
135 | client.OnPeerLeft += id => {
136 | if (peerViews.ContainsKey(id)) {
137 | var peerViewInstance = peerViews[id];
138 | Destroy(peerViewInstance.gameObject);
139 | peerViews.Remove(id);
140 | }
141 | };
142 | }
143 |
144 | // Here we just show some audio visualization of incoming peer audio.
145 | void Update() {
146 | if (session == null) return;
147 |
148 | foreach (var output in session.PeerOutputs) {
149 | if (peerViews.ContainsKey(output.Key)) {
150 | /*
151 | * This is an inefficient way of showing a part of the
152 | * audio source spectrum. AudioSource.GetSpectrumData returns
153 | * frequency values up to 24000 Hz in some cases. Most human
154 | * speech is no more than 5000 Hz. Showing the entire spectrum
155 | * will therefore lead to a spectrum where much of it doesn't
156 | * change. So we take only the spectrum frequencies between
157 | * the average human vocal range.
158 | *
159 | * Great source of information here:
160 | * http://answers.unity.com/answers/158800/view.html
161 | */
162 | var size = 512;
163 | var minVocalFrequency = 50;
164 | var maxVocalFrequency = 8000;
165 | var sampleRate = AudioSettings.outputSampleRate;
166 | var frequencyResolution = sampleRate / 2 / size;
167 |
168 | var audioSource = (output.Value as StreamedAudioSourceOutput).Stream.UnityAudioSource;
169 | var spectrumData = new float[size];
170 | audioSource.GetSpectrumData(spectrumData, 0, FFTWindow.BlackmanHarris);
171 |
172 | var indices = Enumerable.Range(0, size - 1).ToList();
173 | var minVocalFrequencyIndex = indices.Min(x => (Mathf.Abs(x * frequencyResolution - minVocalFrequency), x)).x;
174 | var maxVocalFrequencyIndex = indices.Min(x => (Mathf.Abs(x * frequencyResolution - maxVocalFrequency), x)).x;
175 | var indexRange = maxVocalFrequencyIndex - minVocalFrequency;
176 |
177 | // Using LINQ here to keep it short. But this generates a lot of garbage.
178 | // If you're visualizing incoming audio data in your app/game, consider using some
179 | // caching and memory allocation saving techniques
180 | spectrumData = spectrumData.Select(x => 1000 * x)
181 | .ToList()
182 | .GetRange(minVocalFrequency, indexRange)
183 | .ToArray();
184 | peerViews[output.Key].DisplaySpectrum(spectrumData);
185 | }
186 | }
187 | }
188 |
189 | void ShowMessage(object obj) {
190 | Debug.Log("GroupVoiceCall_MirrorSample:" + obj);
191 | chatroomMessage.text = obj.ToString();
192 | }
193 | }
194 | }
195 | #endif
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Samples/Group Chat Sample/Scripts/GroupVoiceCallMirrorSample.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: f1fbe701d5771df469acad7064df7cc7
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Samples/Group Chat Sample/Scripts/PeerView.cs:
--------------------------------------------------------------------------------
1 | using System;
2 | using System.Collections.Generic;
3 |
4 | using UnityEngine;
5 | using UnityEngine.UI;
6 |
7 | namespace Adrenak.UniVoice.Samples {
8 | public static class ToggleExtension{
9 | public static void SetIsOnWithoutNotify(this Toggle instance, bool value) {
10 | var originalEvent = instance.onValueChanged;
11 | instance.onValueChanged = new Toggle.ToggleEvent();
12 | instance.isOn = value;
13 | instance.onValueChanged = originalEvent;
14 | }
15 | }
16 |
17 | public class PeerView : MonoBehaviour {
18 | public event Action OnAllowIncomingAudioChange;
19 | public event Action OnAllowOutgoingAudioChange;
20 |
21 | [SerializeField] Text idText;
22 | [SerializeField] Transform barContainer;
23 | [SerializeField] Transform barTemplate;
24 | [SerializeField] Toggle speakerToggle;
25 | [SerializeField] Toggle micToggle;
26 |
27 | public bool AllowIncomingAudio {
28 | get => speakerToggle.isOn;
29 | set => speakerToggle.SetIsOnWithoutNotify(value);
30 | }
31 |
32 | public bool AllowOutgoingAudio {
33 | get => micToggle.isOn;
34 | set => micToggle.SetIsOnWithoutNotify(value);
35 | }
36 |
37 | List bars = new List();
38 |
39 | void Start() {
40 | speakerToggle.onValueChanged.AddListener(value =>
41 | OnAllowIncomingAudioChange?.Invoke(value));
42 |
43 | micToggle.onValueChanged.AddListener(value =>
44 | OnAllowOutgoingAudioChange?.Invoke(value));
45 | }
46 |
47 | public void SetPeerID(int id) {
48 | idText.text = id.ToString();
49 | }
50 |
51 | public void DisplaySpectrum(float[] spectrum) {
52 | InitBars(spectrum.Length);
53 |
54 | if (spectrum.Length != bars.Count) return;
55 |
56 | for (int i = 0; i < bars.Count; i++)
57 | bars[i].localScale = new Vector3(1, Mathf.Clamp01(spectrum[i]), 1);
58 | }
59 |
60 | void InitBars(int count) {
61 | if (bars.Count == count) return;
62 |
63 | foreach (var bar in bars)
64 | Destroy(bar.gameObject);
65 | bars.Clear();
66 |
67 | for (int i = 0; i < count; i++) {
68 | var instance = Instantiate(barTemplate, barContainer);
69 | instance.gameObject.SetActive(true);
70 | bars.Add(instance);
71 | }
72 | }
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Samples/Group Chat Sample/Scripts/PeerView.cs.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: d7cbcf3d831071140b35bc0fd8205a16
3 | MonoImporter:
4 | externalObjects: {}
5 | serializedVersion: 2
6 | defaultReferences: []
7 | executionOrder: 0
8 | icon: {instanceID: 0}
9 | userData:
10 | assetBundleName:
11 | assetBundleVariant:
12 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Samples/Group Chat Sample/Sprites.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 918a405e0939fa54daf8eee8faaac909
3 | folderAsset: yes
4 | DefaultImporter:
5 | externalObjects: {}
6 | userData:
7 | assetBundleName:
8 | assetBundleVariant:
9 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Samples/Group Chat Sample/Sprites/mic.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adrenak/univoice/b4539c94336f8cda31b03b77b88d000f5b1bbf0a/Assets/Adrenak.UniVoice/Samples/Group Chat Sample/Sprites/mic.png
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Samples/Group Chat Sample/Sprites/mic.png.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 48991d65e071871499902c8cde27d649
3 | TextureImporter:
4 | fileIDToRecycleName: {}
5 | externalObjects: {}
6 | serializedVersion: 9
7 | mipmaps:
8 | mipMapMode: 0
9 | enableMipMap: 0
10 | sRGBTexture: 1
11 | linearTexture: 0
12 | fadeOut: 0
13 | borderMipMap: 0
14 | mipMapsPreserveCoverage: 0
15 | alphaTestReferenceValue: 0.5
16 | mipMapFadeDistanceStart: 1
17 | mipMapFadeDistanceEnd: 3
18 | bumpmap:
19 | convertToNormalMap: 0
20 | externalNormalMap: 0
21 | heightScale: 0.25
22 | normalMapFilter: 0
23 | isReadable: 0
24 | streamingMipmaps: 0
25 | streamingMipmapsPriority: 0
26 | grayScaleToAlpha: 0
27 | generateCubemap: 6
28 | cubemapConvolution: 0
29 | seamlessCubemap: 0
30 | textureFormat: 1
31 | maxTextureSize: 2048
32 | textureSettings:
33 | serializedVersion: 2
34 | filterMode: -1
35 | aniso: -1
36 | mipBias: -100
37 | wrapU: 1
38 | wrapV: 1
39 | wrapW: -1
40 | nPOTScale: 0
41 | lightmap: 0
42 | compressionQuality: 50
43 | spriteMode: 1
44 | spriteExtrude: 1
45 | spriteMeshType: 1
46 | alignment: 0
47 | spritePivot: {x: 0.5, y: 0.5}
48 | spritePixelsToUnits: 100
49 | spriteBorder: {x: 0, y: 0, z: 0, w: 0}
50 | spriteGenerateFallbackPhysicsShape: 1
51 | alphaUsage: 1
52 | alphaIsTransparency: 1
53 | spriteTessellationDetail: -1
54 | textureType: 8
55 | textureShape: 1
56 | singleChannelComponent: 0
57 | maxTextureSizeSet: 0
58 | compressionQualitySet: 0
59 | textureFormatSet: 0
60 | platformSettings:
61 | - serializedVersion: 2
62 | buildTarget: DefaultTexturePlatform
63 | maxTextureSize: 256
64 | resizeAlgorithm: 0
65 | textureFormat: -1
66 | textureCompression: 1
67 | compressionQuality: 50
68 | crunchedCompression: 0
69 | allowsAlphaSplitting: 0
70 | overridden: 0
71 | androidETC2FallbackOverride: 0
72 | spriteSheet:
73 | serializedVersion: 2
74 | sprites: []
75 | outline: []
76 | physicsShape: []
77 | bones: []
78 | spriteID: 9ff3a2e24a6348a4498d20d7fc001556
79 | vertices: []
80 | indices:
81 | edges: []
82 | weights: []
83 | spritePackingTag:
84 | pSDRemoveMatte: 0
85 | pSDShowRemoveMatteOption: 0
86 | userData:
87 | assetBundleName:
88 | assetBundleVariant:
89 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Samples/Group Chat Sample/Sprites/off.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adrenak/univoice/b4539c94336f8cda31b03b77b88d000f5b1bbf0a/Assets/Adrenak.UniVoice/Samples/Group Chat Sample/Sprites/off.png
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Samples/Group Chat Sample/Sprites/off.png.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: fef297624ed5b2043ac6c92214afbcf3
3 | TextureImporter:
4 | fileIDToRecycleName: {}
5 | externalObjects: {}
6 | serializedVersion: 9
7 | mipmaps:
8 | mipMapMode: 0
9 | enableMipMap: 0
10 | sRGBTexture: 1
11 | linearTexture: 0
12 | fadeOut: 0
13 | borderMipMap: 0
14 | mipMapsPreserveCoverage: 0
15 | alphaTestReferenceValue: 0.5
16 | mipMapFadeDistanceStart: 1
17 | mipMapFadeDistanceEnd: 3
18 | bumpmap:
19 | convertToNormalMap: 0
20 | externalNormalMap: 0
21 | heightScale: 0.25
22 | normalMapFilter: 0
23 | isReadable: 0
24 | streamingMipmaps: 0
25 | streamingMipmapsPriority: 0
26 | grayScaleToAlpha: 0
27 | generateCubemap: 6
28 | cubemapConvolution: 0
29 | seamlessCubemap: 0
30 | textureFormat: 1
31 | maxTextureSize: 2048
32 | textureSettings:
33 | serializedVersion: 2
34 | filterMode: -1
35 | aniso: -1
36 | mipBias: -100
37 | wrapU: 1
38 | wrapV: 1
39 | wrapW: -1
40 | nPOTScale: 0
41 | lightmap: 0
42 | compressionQuality: 50
43 | spriteMode: 1
44 | spriteExtrude: 1
45 | spriteMeshType: 1
46 | alignment: 0
47 | spritePivot: {x: 0.5, y: 0.5}
48 | spritePixelsToUnits: 100
49 | spriteBorder: {x: 0, y: 0, z: 0, w: 0}
50 | spriteGenerateFallbackPhysicsShape: 1
51 | alphaUsage: 1
52 | alphaIsTransparency: 1
53 | spriteTessellationDetail: -1
54 | textureType: 8
55 | textureShape: 1
56 | singleChannelComponent: 0
57 | maxTextureSizeSet: 0
58 | compressionQualitySet: 0
59 | textureFormatSet: 0
60 | platformSettings:
61 | - serializedVersion: 2
62 | buildTarget: DefaultTexturePlatform
63 | maxTextureSize: 256
64 | resizeAlgorithm: 0
65 | textureFormat: -1
66 | textureCompression: 1
67 | compressionQuality: 50
68 | crunchedCompression: 0
69 | allowsAlphaSplitting: 0
70 | overridden: 0
71 | androidETC2FallbackOverride: 0
72 | spriteSheet:
73 | serializedVersion: 2
74 | sprites: []
75 | outline: []
76 | physicsShape: []
77 | bones: []
78 | spriteID: 9c153eda0403ca546828095fc0904b7a
79 | vertices: []
80 | indices:
81 | edges: []
82 | weights: []
83 | spritePackingTag:
84 | pSDRemoveMatte: 0
85 | pSDShowRemoveMatteOption: 0
86 | userData:
87 | assetBundleName:
88 | assetBundleVariant:
89 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Samples/Group Chat Sample/Sprites/on.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adrenak/univoice/b4539c94336f8cda31b03b77b88d000f5b1bbf0a/Assets/Adrenak.UniVoice/Samples/Group Chat Sample/Sprites/on.png
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Samples/Group Chat Sample/Sprites/on.png.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 091b808e85d9e3843ab27e808a68ad44
3 | TextureImporter:
4 | fileIDToRecycleName: {}
5 | externalObjects: {}
6 | serializedVersion: 9
7 | mipmaps:
8 | mipMapMode: 0
9 | enableMipMap: 0
10 | sRGBTexture: 1
11 | linearTexture: 0
12 | fadeOut: 0
13 | borderMipMap: 0
14 | mipMapsPreserveCoverage: 0
15 | alphaTestReferenceValue: 0.5
16 | mipMapFadeDistanceStart: 1
17 | mipMapFadeDistanceEnd: 3
18 | bumpmap:
19 | convertToNormalMap: 0
20 | externalNormalMap: 0
21 | heightScale: 0.25
22 | normalMapFilter: 0
23 | isReadable: 0
24 | streamingMipmaps: 0
25 | streamingMipmapsPriority: 0
26 | grayScaleToAlpha: 0
27 | generateCubemap: 6
28 | cubemapConvolution: 0
29 | seamlessCubemap: 0
30 | textureFormat: 1
31 | maxTextureSize: 2048
32 | textureSettings:
33 | serializedVersion: 2
34 | filterMode: -1
35 | aniso: -1
36 | mipBias: -100
37 | wrapU: 1
38 | wrapV: 1
39 | wrapW: -1
40 | nPOTScale: 0
41 | lightmap: 0
42 | compressionQuality: 50
43 | spriteMode: 1
44 | spriteExtrude: 1
45 | spriteMeshType: 1
46 | alignment: 0
47 | spritePivot: {x: 0.5, y: 0.5}
48 | spritePixelsToUnits: 100
49 | spriteBorder: {x: 0, y: 0, z: 0, w: 0}
50 | spriteGenerateFallbackPhysicsShape: 1
51 | alphaUsage: 1
52 | alphaIsTransparency: 1
53 | spriteTessellationDetail: -1
54 | textureType: 8
55 | textureShape: 1
56 | singleChannelComponent: 0
57 | maxTextureSizeSet: 0
58 | compressionQualitySet: 0
59 | textureFormatSet: 0
60 | platformSettings:
61 | - serializedVersion: 2
62 | buildTarget: DefaultTexturePlatform
63 | maxTextureSize: 256
64 | resizeAlgorithm: 0
65 | textureFormat: -1
66 | textureCompression: 1
67 | compressionQuality: 50
68 | crunchedCompression: 0
69 | allowsAlphaSplitting: 0
70 | overridden: 0
71 | androidETC2FallbackOverride: 0
72 | spriteSheet:
73 | serializedVersion: 2
74 | sprites: []
75 | outline: []
76 | physicsShape: []
77 | bones: []
78 | spriteID: 1d85175d92546e14aa7525901d0b3fea
79 | vertices: []
80 | indices:
81 | edges: []
82 | weights: []
83 | spritePackingTag:
84 | pSDRemoveMatte: 0
85 | pSDShowRemoveMatteOption: 0
86 | userData:
87 | assetBundleName:
88 | assetBundleVariant:
89 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Samples/Group Chat Sample/Sprites/speaker.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adrenak/univoice/b4539c94336f8cda31b03b77b88d000f5b1bbf0a/Assets/Adrenak.UniVoice/Samples/Group Chat Sample/Sprites/speaker.png
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/Samples/Group Chat Sample/Sprites/speaker.png.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: f614e31fdd0562a4ebb9b6c2126ebf8a
3 | TextureImporter:
4 | fileIDToRecycleName: {}
5 | externalObjects: {}
6 | serializedVersion: 9
7 | mipmaps:
8 | mipMapMode: 0
9 | enableMipMap: 0
10 | sRGBTexture: 1
11 | linearTexture: 0
12 | fadeOut: 0
13 | borderMipMap: 0
14 | mipMapsPreserveCoverage: 0
15 | alphaTestReferenceValue: 0.5
16 | mipMapFadeDistanceStart: 1
17 | mipMapFadeDistanceEnd: 3
18 | bumpmap:
19 | convertToNormalMap: 0
20 | externalNormalMap: 0
21 | heightScale: 0.25
22 | normalMapFilter: 0
23 | isReadable: 0
24 | streamingMipmaps: 0
25 | streamingMipmapsPriority: 0
26 | grayScaleToAlpha: 0
27 | generateCubemap: 6
28 | cubemapConvolution: 0
29 | seamlessCubemap: 0
30 | textureFormat: 1
31 | maxTextureSize: 2048
32 | textureSettings:
33 | serializedVersion: 2
34 | filterMode: -1
35 | aniso: -1
36 | mipBias: -100
37 | wrapU: 1
38 | wrapV: 1
39 | wrapW: -1
40 | nPOTScale: 0
41 | lightmap: 0
42 | compressionQuality: 50
43 | spriteMode: 1
44 | spriteExtrude: 1
45 | spriteMeshType: 1
46 | alignment: 0
47 | spritePivot: {x: 0.5, y: 0.5}
48 | spritePixelsToUnits: 100
49 | spriteBorder: {x: 0, y: 0, z: 0, w: 0}
50 | spriteGenerateFallbackPhysicsShape: 1
51 | alphaUsage: 1
52 | alphaIsTransparency: 1
53 | spriteTessellationDetail: -1
54 | textureType: 8
55 | textureShape: 1
56 | singleChannelComponent: 0
57 | maxTextureSizeSet: 0
58 | compressionQualitySet: 0
59 | textureFormatSet: 0
60 | platformSettings:
61 | - serializedVersion: 2
62 | buildTarget: DefaultTexturePlatform
63 | maxTextureSize: 256
64 | resizeAlgorithm: 0
65 | textureFormat: -1
66 | textureCompression: 1
67 | compressionQuality: 50
68 | crunchedCompression: 0
69 | allowsAlphaSplitting: 0
70 | overridden: 0
71 | androidETC2FallbackOverride: 0
72 | spriteSheet:
73 | serializedVersion: 2
74 | sprites: []
75 | outline: []
76 | physicsShape: []
77 | bones: []
78 | spriteID: 3622c0ab7514b294e91e1e29cbfae826
79 | vertices: []
80 | indices:
81 | edges: []
82 | weights: []
83 | spritePackingTag:
84 | pSDRemoveMatte: 0
85 | pSDShowRemoveMatteOption: 0
86 | userData:
87 | assetBundleName:
88 | assetBundleVariant:
89 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "com.adrenak.univoice",
3 | "version": "4.10.1",
4 | "displayName": "Adrenak.UniVoice",
5 | "description": "Voice chat/VoIP framework for Unity.",
6 | "unity": "2021.2",
7 | "author": {
8 | "name": "Vatsal Ambastha",
9 | "email": "ambastha.vatsal@gmail.com",
10 | "url": "http://www.vatsalambastha.com"
11 | },
12 | "license": "MIT",
13 | "keywords": [
14 | "Mirror",
15 | "Networking",
16 | "Voice Chat",
17 | "voip",
18 | "Unity",
19 | "Unity3d",
20 | "VR",
21 | "AR",
22 | "Metaverse",
23 | "mic",
24 | "opus",
25 | "concentus"
26 | ],
27 | "samples": [
28 | {
29 | "displayName": "Samples",
30 | "description": "UniVoice Samples to get you started",
31 | "path": "Samples~"
32 | }
33 | ],
34 | "dependencies": {
35 | "com.adrenak.brw": "1.0.1",
36 | "com.adrenak.unimic": "3.4.0",
37 | "com.adrenak.concentus-unity": "1.0.1"
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/Assets/Adrenak.UniVoice/package.json.meta:
--------------------------------------------------------------------------------
1 | fileFormatVersion: 2
2 | guid: 5677af848094d404f835a6468b36f003
3 | TextScriptImporter:
4 | externalObjects: {}
5 | userData:
6 | assetBundleName:
7 | assetBundleVariant:
8 |
--------------------------------------------------------------------------------
/Documentation/docfx.json:
--------------------------------------------------------------------------------
1 | {
2 | "metadata": [
3 | {
4 | "src": [
5 | {
6 | "src": "..",
7 | "files": [
8 | "Assets/**/*.cs"
9 | ],
10 | "exclude": [
11 | "Assets/Plugins/**"
12 | ]
13 | }
14 | ],
15 | "globalNamespaceId": "Global",
16 | "filter": "filterConfig.yml",
17 | "dest": "api"
18 | }
19 | ],
20 | "build": {
21 | "globalMetadata": {
22 | "_appTitle": "UniVoice documentation",
23 | "_appFooter": "UniVoice documentation",
24 | "_enableSearch": true
25 | },
26 | "content": [
27 | {
28 | "files": [
29 | "toc.yml",
30 | "index.md"
31 | ]
32 | },
33 | {
34 | "src": "api",
35 | "files": [
36 | "*.yml"
37 | ],
38 | "dest": "api"
39 | },
40 | {
41 | "src": "manual",
42 | "files": [
43 | "toc.yml",
44 | "*.md"
45 | ],
46 | "dest": "manual"
47 | }
48 | ],
49 | "overwrite": [
50 | {
51 | "src": "..",
52 | "files": [
53 | "Assets/Scripts/**/*.md"
54 | ]
55 | }
56 | ],
57 | "resource": [
58 | {
59 | "files": [
60 | "resources/**/*"
61 | ]
62 | }
63 | ],
64 | "sitemap": {
65 | "baseUrl": "https://adrenak.github.io/univoice",
66 | "changefreq": "weekly",
67 | "fileOptions": {
68 | "api/*": {
69 | "changefreq": "daily"
70 | }
71 | }
72 | },
73 | "xref": [
74 | "https://normanderwan.github.io/UnityXrefMaps/xrefmap.yml"
75 | ],
76 | "xrefService": [
77 | "https://xref.docs.microsoft.com/query?uid={uid}"
78 | ],
79 | "dest": "../_site"
80 | }
81 | }
--------------------------------------------------------------------------------
/Documentation/filterConfig.yml:
--------------------------------------------------------------------------------
1 | apiRules:
2 | - include:
3 | uidRegex: ^Adrenak.UniVoice
4 | type: Namespace
5 | - exclude:
6 | uidRegex: .*
7 | type: Namespace
--------------------------------------------------------------------------------
/Documentation/toc.yml:
--------------------------------------------------------------------------------
1 | - name: Manual
2 | href: manual/
3 | - name: Scripting API
4 | href: api/
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 Vatsal Ambastha
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/Logs/Packages-Update.log:
--------------------------------------------------------------------------------
1 |
2 | === Sun May 23 18:33:12 2021
3 |
4 | Packages were changed.
5 | Update Mode: resetToDefaultDependencies
6 |
7 | The following packages were added:
8 | com.unity.analytics@3.2.3
9 | com.unity.purchasing@2.2.1
10 | com.unity.ads@2.0.8
11 | com.unity.textmeshpro@1.4.1
12 | com.unity.package-manager-ui@2.0.13
13 | com.unity.collab-proxy@1.2.15
14 | com.unity.modules.ai@1.0.0
15 | com.unity.modules.animation@1.0.0
16 | com.unity.modules.assetbundle@1.0.0
17 | com.unity.modules.audio@1.0.0
18 | com.unity.modules.cloth@1.0.0
19 | com.unity.modules.director@1.0.0
20 | com.unity.modules.imageconversion@1.0.0
21 | com.unity.modules.imgui@1.0.0
22 | com.unity.modules.jsonserialize@1.0.0
23 | com.unity.modules.particlesystem@1.0.0
24 | com.unity.modules.physics@1.0.0
25 | com.unity.modules.physics2d@1.0.0
26 | com.unity.modules.screencapture@1.0.0
27 | com.unity.modules.terrain@1.0.0
28 | com.unity.modules.terrainphysics@1.0.0
29 | com.unity.modules.tilemap@1.0.0
30 | com.unity.modules.ui@1.0.0
31 | com.unity.modules.uielements@1.0.0
32 | com.unity.modules.umbra@1.0.0
33 | com.unity.modules.unityanalytics@1.0.0
34 | com.unity.modules.unitywebrequest@1.0.0
35 | com.unity.modules.unitywebrequestassetbundle@1.0.0
36 | com.unity.modules.unitywebrequestaudio@1.0.0
37 | com.unity.modules.unitywebrequesttexture@1.0.0
38 | com.unity.modules.unitywebrequestwww@1.0.0
39 | com.unity.modules.vehicles@1.0.0
40 | com.unity.modules.video@1.0.0
41 | com.unity.modules.vr@1.0.0
42 | com.unity.modules.wind@1.0.0
43 | com.unity.modules.xr@1.0.0
44 |
45 | === Fri Aug 19 15:12:08 2022
46 |
47 | Packages were changed.
48 | Update Mode: updateDependencies
49 |
50 | The following packages were added:
51 | com.unity.2d.sprite@1.0.0
52 | com.unity.2d.tilemap@1.0.0
53 | com.unity.ide.rider@1.2.1
54 | com.unity.ide.visualstudio@2.0.14
55 | com.unity.ide.vscode@1.2.4
56 | com.unity.modules.androidjni@1.0.0
57 | com.unity.multiplayer-hlapi@1.0.8
58 | com.unity.test-framework@1.1.30
59 | com.unity.timeline@1.2.18
60 | com.unity.ugui@1.0.0
61 | com.unity.xr.legacyinputhelpers@2.1.8
62 | The following packages were removed:
63 | com.unity.package-manager-ui@2.2.0
64 |
65 | === Sun Dec 29 20:04:06 2024
66 |
67 | Packages were changed.
68 | Update Mode: mergeDefaultDependencies
69 |
70 | The following packages were updated:
71 | com.unity.nuget.newtonsoft-json from version 3.0.2 to 3.2.1
72 |
--------------------------------------------------------------------------------
/Packages/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "dependencies": {
3 | "com.adrenak.brw": "1.0.1",
4 | "com.adrenak.concentus-unity": "1.0.1",
5 | "com.adrenak.unimic": "3.4.0",
6 | "com.unity.ide.visualstudio": "2.0.22",
7 | "com.unity.nuget.newtonsoft-json": "3.2.1",
8 | "com.unity.ugui": "1.0.0",
9 | "com.unity.modules.ai": "1.0.0",
10 | "com.unity.modules.androidjni": "1.0.0",
11 | "com.unity.modules.animation": "1.0.0",
12 | "com.unity.modules.assetbundle": "1.0.0",
13 | "com.unity.modules.audio": "1.0.0",
14 | "com.unity.modules.cloth": "1.0.0",
15 | "com.unity.modules.director": "1.0.0",
16 | "com.unity.modules.imageconversion": "1.0.0",
17 | "com.unity.modules.imgui": "1.0.0",
18 | "com.unity.modules.jsonserialize": "1.0.0",
19 | "com.unity.modules.particlesystem": "1.0.0",
20 | "com.unity.modules.physics": "1.0.0",
21 | "com.unity.modules.physics2d": "1.0.0",
22 | "com.unity.modules.screencapture": "1.0.0",
23 | "com.unity.modules.terrain": "1.0.0",
24 | "com.unity.modules.terrainphysics": "1.0.0",
25 | "com.unity.modules.tilemap": "1.0.0",
26 | "com.unity.modules.ui": "1.0.0",
27 | "com.unity.modules.uielements": "1.0.0",
28 | "com.unity.modules.umbra": "1.0.0",
29 | "com.unity.modules.unityanalytics": "1.0.0",
30 | "com.unity.modules.unitywebrequest": "1.0.0",
31 | "com.unity.modules.unitywebrequestassetbundle": "1.0.0",
32 | "com.unity.modules.unitywebrequestaudio": "1.0.0",
33 | "com.unity.modules.unitywebrequesttexture": "1.0.0",
34 | "com.unity.modules.unitywebrequestwww": "1.0.0",
35 | "com.unity.modules.vehicles": "1.0.0",
36 | "com.unity.modules.video": "1.0.0",
37 | "com.unity.modules.vr": "1.0.0",
38 | "com.unity.modules.wind": "1.0.0",
39 | "com.unity.modules.xr": "1.0.0"
40 | },
41 | "scopedRegistries": [
42 | {
43 | "name": "NPM",
44 | "url": "https://registry.npmjs.org",
45 | "scopes": [
46 | "com.adrenak.brw",
47 | "com.adrenak.unimic",
48 | "com.adrenak.rnnoise4unity",
49 | "com.adrenak.concentus-unity"
50 | ]
51 | }
52 | ]
53 | }
54 |
--------------------------------------------------------------------------------
/Packages/packages-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "dependencies": {
3 | "com.adrenak.brw": {
4 | "version": "1.0.1",
5 | "depth": 0,
6 | "source": "registry",
7 | "dependencies": {},
8 | "url": "https://registry.npmjs.org"
9 | },
10 | "com.adrenak.concentus-unity": {
11 | "version": "1.0.1",
12 | "depth": 0,
13 | "source": "registry",
14 | "dependencies": {},
15 | "url": "https://registry.npmjs.org"
16 | },
17 | "com.adrenak.unimic": {
18 | "version": "3.4.0",
19 | "depth": 0,
20 | "source": "registry",
21 | "dependencies": {},
22 | "url": "https://registry.npmjs.org"
23 | },
24 | "com.unity.ext.nunit": {
25 | "version": "1.0.6",
26 | "depth": 2,
27 | "source": "registry",
28 | "dependencies": {},
29 | "url": "https://packages.unity.com"
30 | },
31 | "com.unity.ide.visualstudio": {
32 | "version": "2.0.22",
33 | "depth": 0,
34 | "source": "registry",
35 | "dependencies": {
36 | "com.unity.test-framework": "1.1.9"
37 | },
38 | "url": "https://packages.unity.com"
39 | },
40 | "com.unity.nuget.newtonsoft-json": {
41 | "version": "3.2.1",
42 | "depth": 0,
43 | "source": "registry",
44 | "dependencies": {},
45 | "url": "https://packages.unity.com"
46 | },
47 | "com.unity.test-framework": {
48 | "version": "1.1.33",
49 | "depth": 1,
50 | "source": "registry",
51 | "dependencies": {
52 | "com.unity.ext.nunit": "1.0.6",
53 | "com.unity.modules.imgui": "1.0.0",
54 | "com.unity.modules.jsonserialize": "1.0.0"
55 | },
56 | "url": "https://packages.unity.com"
57 | },
58 | "com.unity.ugui": {
59 | "version": "1.0.0",
60 | "depth": 0,
61 | "source": "builtin",
62 | "dependencies": {
63 | "com.unity.modules.ui": "1.0.0",
64 | "com.unity.modules.imgui": "1.0.0"
65 | }
66 | },
67 | "com.unity.modules.ai": {
68 | "version": "1.0.0",
69 | "depth": 0,
70 | "source": "builtin",
71 | "dependencies": {}
72 | },
73 | "com.unity.modules.androidjni": {
74 | "version": "1.0.0",
75 | "depth": 0,
76 | "source": "builtin",
77 | "dependencies": {}
78 | },
79 | "com.unity.modules.animation": {
80 | "version": "1.0.0",
81 | "depth": 0,
82 | "source": "builtin",
83 | "dependencies": {}
84 | },
85 | "com.unity.modules.assetbundle": {
86 | "version": "1.0.0",
87 | "depth": 0,
88 | "source": "builtin",
89 | "dependencies": {}
90 | },
91 | "com.unity.modules.audio": {
92 | "version": "1.0.0",
93 | "depth": 0,
94 | "source": "builtin",
95 | "dependencies": {}
96 | },
97 | "com.unity.modules.cloth": {
98 | "version": "1.0.0",
99 | "depth": 0,
100 | "source": "builtin",
101 | "dependencies": {
102 | "com.unity.modules.physics": "1.0.0"
103 | }
104 | },
105 | "com.unity.modules.director": {
106 | "version": "1.0.0",
107 | "depth": 0,
108 | "source": "builtin",
109 | "dependencies": {
110 | "com.unity.modules.audio": "1.0.0",
111 | "com.unity.modules.animation": "1.0.0"
112 | }
113 | },
114 | "com.unity.modules.imageconversion": {
115 | "version": "1.0.0",
116 | "depth": 0,
117 | "source": "builtin",
118 | "dependencies": {}
119 | },
120 | "com.unity.modules.imgui": {
121 | "version": "1.0.0",
122 | "depth": 0,
123 | "source": "builtin",
124 | "dependencies": {}
125 | },
126 | "com.unity.modules.jsonserialize": {
127 | "version": "1.0.0",
128 | "depth": 0,
129 | "source": "builtin",
130 | "dependencies": {}
131 | },
132 | "com.unity.modules.particlesystem": {
133 | "version": "1.0.0",
134 | "depth": 0,
135 | "source": "builtin",
136 | "dependencies": {}
137 | },
138 | "com.unity.modules.physics": {
139 | "version": "1.0.0",
140 | "depth": 0,
141 | "source": "builtin",
142 | "dependencies": {}
143 | },
144 | "com.unity.modules.physics2d": {
145 | "version": "1.0.0",
146 | "depth": 0,
147 | "source": "builtin",
148 | "dependencies": {}
149 | },
150 | "com.unity.modules.screencapture": {
151 | "version": "1.0.0",
152 | "depth": 0,
153 | "source": "builtin",
154 | "dependencies": {
155 | "com.unity.modules.imageconversion": "1.0.0"
156 | }
157 | },
158 | "com.unity.modules.subsystems": {
159 | "version": "1.0.0",
160 | "depth": 1,
161 | "source": "builtin",
162 | "dependencies": {
163 | "com.unity.modules.jsonserialize": "1.0.0"
164 | }
165 | },
166 | "com.unity.modules.terrain": {
167 | "version": "1.0.0",
168 | "depth": 0,
169 | "source": "builtin",
170 | "dependencies": {}
171 | },
172 | "com.unity.modules.terrainphysics": {
173 | "version": "1.0.0",
174 | "depth": 0,
175 | "source": "builtin",
176 | "dependencies": {
177 | "com.unity.modules.physics": "1.0.0",
178 | "com.unity.modules.terrain": "1.0.0"
179 | }
180 | },
181 | "com.unity.modules.tilemap": {
182 | "version": "1.0.0",
183 | "depth": 0,
184 | "source": "builtin",
185 | "dependencies": {
186 | "com.unity.modules.physics2d": "1.0.0"
187 | }
188 | },
189 | "com.unity.modules.ui": {
190 | "version": "1.0.0",
191 | "depth": 0,
192 | "source": "builtin",
193 | "dependencies": {}
194 | },
195 | "com.unity.modules.uielements": {
196 | "version": "1.0.0",
197 | "depth": 0,
198 | "source": "builtin",
199 | "dependencies": {
200 | "com.unity.modules.ui": "1.0.0",
201 | "com.unity.modules.imgui": "1.0.0",
202 | "com.unity.modules.jsonserialize": "1.0.0",
203 | "com.unity.modules.uielementsnative": "1.0.0"
204 | }
205 | },
206 | "com.unity.modules.uielementsnative": {
207 | "version": "1.0.0",
208 | "depth": 1,
209 | "source": "builtin",
210 | "dependencies": {
211 | "com.unity.modules.ui": "1.0.0",
212 | "com.unity.modules.imgui": "1.0.0",
213 | "com.unity.modules.jsonserialize": "1.0.0"
214 | }
215 | },
216 | "com.unity.modules.umbra": {
217 | "version": "1.0.0",
218 | "depth": 0,
219 | "source": "builtin",
220 | "dependencies": {}
221 | },
222 | "com.unity.modules.unityanalytics": {
223 | "version": "1.0.0",
224 | "depth": 0,
225 | "source": "builtin",
226 | "dependencies": {
227 | "com.unity.modules.unitywebrequest": "1.0.0",
228 | "com.unity.modules.jsonserialize": "1.0.0"
229 | }
230 | },
231 | "com.unity.modules.unitywebrequest": {
232 | "version": "1.0.0",
233 | "depth": 0,
234 | "source": "builtin",
235 | "dependencies": {}
236 | },
237 | "com.unity.modules.unitywebrequestassetbundle": {
238 | "version": "1.0.0",
239 | "depth": 0,
240 | "source": "builtin",
241 | "dependencies": {
242 | "com.unity.modules.assetbundle": "1.0.0",
243 | "com.unity.modules.unitywebrequest": "1.0.0"
244 | }
245 | },
246 | "com.unity.modules.unitywebrequestaudio": {
247 | "version": "1.0.0",
248 | "depth": 0,
249 | "source": "builtin",
250 | "dependencies": {
251 | "com.unity.modules.unitywebrequest": "1.0.0",
252 | "com.unity.modules.audio": "1.0.0"
253 | }
254 | },
255 | "com.unity.modules.unitywebrequesttexture": {
256 | "version": "1.0.0",
257 | "depth": 0,
258 | "source": "builtin",
259 | "dependencies": {
260 | "com.unity.modules.unitywebrequest": "1.0.0",
261 | "com.unity.modules.imageconversion": "1.0.0"
262 | }
263 | },
264 | "com.unity.modules.unitywebrequestwww": {
265 | "version": "1.0.0",
266 | "depth": 0,
267 | "source": "builtin",
268 | "dependencies": {
269 | "com.unity.modules.unitywebrequest": "1.0.0",
270 | "com.unity.modules.unitywebrequestassetbundle": "1.0.0",
271 | "com.unity.modules.unitywebrequestaudio": "1.0.0",
272 | "com.unity.modules.audio": "1.0.0",
273 | "com.unity.modules.assetbundle": "1.0.0",
274 | "com.unity.modules.imageconversion": "1.0.0"
275 | }
276 | },
277 | "com.unity.modules.vehicles": {
278 | "version": "1.0.0",
279 | "depth": 0,
280 | "source": "builtin",
281 | "dependencies": {
282 | "com.unity.modules.physics": "1.0.0"
283 | }
284 | },
285 | "com.unity.modules.video": {
286 | "version": "1.0.0",
287 | "depth": 0,
288 | "source": "builtin",
289 | "dependencies": {
290 | "com.unity.modules.audio": "1.0.0",
291 | "com.unity.modules.ui": "1.0.0",
292 | "com.unity.modules.unitywebrequest": "1.0.0"
293 | }
294 | },
295 | "com.unity.modules.vr": {
296 | "version": "1.0.0",
297 | "depth": 0,
298 | "source": "builtin",
299 | "dependencies": {
300 | "com.unity.modules.jsonserialize": "1.0.0",
301 | "com.unity.modules.physics": "1.0.0",
302 | "com.unity.modules.xr": "1.0.0"
303 | }
304 | },
305 | "com.unity.modules.wind": {
306 | "version": "1.0.0",
307 | "depth": 0,
308 | "source": "builtin",
309 | "dependencies": {}
310 | },
311 | "com.unity.modules.xr": {
312 | "version": "1.0.0",
313 | "depth": 0,
314 | "source": "builtin",
315 | "dependencies": {
316 | "com.unity.modules.physics": "1.0.0",
317 | "com.unity.modules.jsonserialize": "1.0.0",
318 | "com.unity.modules.subsystems": "1.0.0"
319 | }
320 | }
321 | }
322 | }
323 |
--------------------------------------------------------------------------------
/ProjectSettings/AudioManager.asset:
--------------------------------------------------------------------------------
1 | %YAML 1.1
2 | %TAG !u! tag:unity3d.com,2011:
3 | --- !u!11 &1
4 | AudioManager:
5 | m_ObjectHideFlags: 0
6 | m_Volume: 1
7 | Rolloff Scale: 1
8 | Doppler Factor: 1
9 | Default Speaker Mode: 2
10 | m_SampleRate: 0
11 | m_DSPBufferSize: 0
12 | m_VirtualVoiceCount: 512
13 | m_RealVoiceCount: 32
14 | m_SpatializerPlugin:
15 | m_AmbisonicDecoderPlugin:
16 | m_DisableAudio: 0
17 | m_VirtualizeEffects: 1
18 |
--------------------------------------------------------------------------------
/ProjectSettings/ClusterInputManager.asset:
--------------------------------------------------------------------------------
1 | %YAML 1.1
2 | %TAG !u! tag:unity3d.com,2011:
3 | --- !u!236 &1
4 | ClusterInputManager:
5 | m_ObjectHideFlags: 0
6 | m_Inputs: []
7 |
--------------------------------------------------------------------------------
/ProjectSettings/DynamicsManager.asset:
--------------------------------------------------------------------------------
1 | %YAML 1.1
2 | %TAG !u! tag:unity3d.com,2011:
3 | --- !u!55 &1
4 | PhysicsManager:
5 | m_ObjectHideFlags: 0
6 | serializedVersion: 3
7 | m_Gravity: {x: 0, y: -9.81, z: 0}
8 | m_DefaultMaterial: {fileID: 0}
9 | m_BounceThreshold: 2
10 | m_SleepThreshold: 0.005
11 | m_DefaultContactOffset: 0.01
12 | m_DefaultSolverIterations: 6
13 | m_DefaultSolverVelocityIterations: 1
14 | m_QueriesHitBackfaces: 0
15 | m_QueriesHitTriggers: 1
16 | m_EnableAdaptiveForce: 0
17 | m_EnablePCM: 1
18 | m_LayerCollisionMatrix: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
19 | m_AutoSimulation: 1
20 |
--------------------------------------------------------------------------------
/ProjectSettings/EditorBuildSettings.asset:
--------------------------------------------------------------------------------
1 | %YAML 1.1
2 | %TAG !u! tag:unity3d.com,2011:
3 | --- !u!1045 &1
4 | EditorBuildSettings:
5 | m_ObjectHideFlags: 0
6 | serializedVersion: 2
7 | m_Scenes:
8 | - enabled: 1
9 | path: Assets/Adrenak.UniVoice/Samples/Group Chat Sample/Scenes/GroupVoiceCallSample-Mirror.unity
10 | guid: 112506fba9c2d954d9eec18ab175ec08
11 | - enabled: 1
12 | path: Assets/Adrenak.UniVoice/Samples/Basic Setup Scripts/UniVoiceMirrorSetupSample.unity
13 | guid: e2397deac1a1ddc489d2e846a22708fa
14 | - enabled: 1
15 | path: Assets/Adrenak.UniVoice/Samples/Basic Setup Scripts/UniVoiceFishNetSetupSample.unity
16 | guid: fcc19499518b4204d8fdeb4ddd323bde
17 | m_configObjects: {}
18 |
--------------------------------------------------------------------------------
/ProjectSettings/EditorSettings.asset:
--------------------------------------------------------------------------------
1 | %YAML 1.1
2 | %TAG !u! tag:unity3d.com,2011:
3 | --- !u!159 &1
4 | EditorSettings:
5 | m_ObjectHideFlags: 0
6 | serializedVersion: 4
7 | m_ExternalVersionControlSupport: Visible Meta Files
8 | m_SerializationMode: 2
9 | m_DefaultBehaviorMode: 0
10 | m_SpritePackerMode: 0
11 | m_SpritePackerPaddingPower: 1
12 | m_ProjectGenerationIncludedExtensions: txt;xml;fnt;cd
13 | m_ProjectGenerationRootNamespace:
14 | m_UserGeneratedProjectSuffix:
15 | m_CollabEditorSettings:
16 | inProgressEnabled: 1
17 |
--------------------------------------------------------------------------------
/ProjectSettings/GraphicsSettings.asset:
--------------------------------------------------------------------------------
1 | %YAML 1.1
2 | %TAG !u! tag:unity3d.com,2011:
3 | --- !u!30 &1
4 | GraphicsSettings:
5 | m_ObjectHideFlags: 0
6 | serializedVersion: 13
7 | m_Deferred:
8 | m_Mode: 1
9 | m_Shader: {fileID: 69, guid: 0000000000000000f000000000000000, type: 0}
10 | m_DeferredReflections:
11 | m_Mode: 1
12 | m_Shader: {fileID: 74, guid: 0000000000000000f000000000000000, type: 0}
13 | m_ScreenSpaceShadows:
14 | m_Mode: 1
15 | m_Shader: {fileID: 64, guid: 0000000000000000f000000000000000, type: 0}
16 | m_LegacyDeferred:
17 | m_Mode: 1
18 | m_Shader: {fileID: 63, guid: 0000000000000000f000000000000000, type: 0}
19 | m_DepthNormals:
20 | m_Mode: 1
21 | m_Shader: {fileID: 62, guid: 0000000000000000f000000000000000, type: 0}
22 | m_MotionVectors:
23 | m_Mode: 1
24 | m_Shader: {fileID: 75, guid: 0000000000000000f000000000000000, type: 0}
25 | m_LightHalo:
26 | m_Mode: 1
27 | m_Shader: {fileID: 105, guid: 0000000000000000f000000000000000, type: 0}
28 | m_LensFlare:
29 | m_Mode: 1
30 | m_Shader: {fileID: 102, guid: 0000000000000000f000000000000000, type: 0}
31 | m_AlwaysIncludedShaders:
32 | - {fileID: 7, guid: 0000000000000000f000000000000000, type: 0}
33 | - {fileID: 15104, guid: 0000000000000000f000000000000000, type: 0}
34 | - {fileID: 15105, guid: 0000000000000000f000000000000000, type: 0}
35 | - {fileID: 15106, guid: 0000000000000000f000000000000000, type: 0}
36 | - {fileID: 10753, guid: 0000000000000000f000000000000000, type: 0}
37 | - {fileID: 10770, guid: 0000000000000000f000000000000000, type: 0}
38 | - {fileID: 16000, guid: 0000000000000000f000000000000000, type: 0}
39 | - {fileID: 17000, guid: 0000000000000000f000000000000000, type: 0}
40 | - {fileID: 16001, guid: 0000000000000000f000000000000000, type: 0}
41 | - {fileID: 16003, guid: 0000000000000000f000000000000000, type: 0}
42 | m_PreloadedShaders: []
43 | m_SpritesDefaultMaterial: {fileID: 10754, guid: 0000000000000000f000000000000000,
44 | type: 0}
45 | m_CustomRenderPipeline: {fileID: 0}
46 | m_TransparencySortMode: 0
47 | m_TransparencySortAxis: {x: 0, y: 0, z: 1}
48 | m_DefaultRenderingPath: 1
49 | m_DefaultMobileRenderingPath: 1
50 | m_TierSettings: []
51 | m_LightmapStripping: 0
52 | m_FogStripping: 0
53 | m_InstancingStripping: 0
54 | m_LightmapKeepPlain: 1
55 | m_LightmapKeepDirCombined: 1
56 | m_LightmapKeepDynamicPlain: 1
57 | m_LightmapKeepDynamicDirCombined: 1
58 | m_LightmapKeepShadowMask: 1
59 | m_LightmapKeepSubtractive: 1
60 | m_FogKeepLinear: 1
61 | m_FogKeepExp: 1
62 | m_FogKeepExp2: 1
63 | m_AlbedoSwatchInfos: []
64 | m_LightsUseLinearIntensity: 0
65 | m_LightsUseColorTemperature: 0
66 | m_LogWhenShaderIsCompiled: 0
67 | m_AllowEnlightenSupportForUpgradedProject: 1
68 |
--------------------------------------------------------------------------------
/ProjectSettings/InputManager.asset:
--------------------------------------------------------------------------------
1 | %YAML 1.1
2 | %TAG !u! tag:unity3d.com,2011:
3 | --- !u!13 &1
4 | InputManager:
5 | m_ObjectHideFlags: 0
6 | serializedVersion: 2
7 | m_Axes:
8 | - serializedVersion: 3
9 | m_Name: Horizontal
10 | descriptiveName:
11 | descriptiveNegativeName:
12 | negativeButton: left
13 | positiveButton: right
14 | altNegativeButton: a
15 | altPositiveButton: d
16 | gravity: 3
17 | dead: 0.001
18 | sensitivity: 3
19 | snap: 1
20 | invert: 0
21 | type: 0
22 | axis: 0
23 | joyNum: 0
24 | - serializedVersion: 3
25 | m_Name: Vertical
26 | descriptiveName:
27 | descriptiveNegativeName:
28 | negativeButton: down
29 | positiveButton: up
30 | altNegativeButton: s
31 | altPositiveButton: w
32 | gravity: 3
33 | dead: 0.001
34 | sensitivity: 3
35 | snap: 1
36 | invert: 0
37 | type: 0
38 | axis: 0
39 | joyNum: 0
40 | - serializedVersion: 3
41 | m_Name: Fire1
42 | descriptiveName:
43 | descriptiveNegativeName:
44 | negativeButton:
45 | positiveButton: left ctrl
46 | altNegativeButton:
47 | altPositiveButton: mouse 0
48 | gravity: 1000
49 | dead: 0.001
50 | sensitivity: 1000
51 | snap: 0
52 | invert: 0
53 | type: 0
54 | axis: 0
55 | joyNum: 0
56 | - serializedVersion: 3
57 | m_Name: Fire2
58 | descriptiveName:
59 | descriptiveNegativeName:
60 | negativeButton:
61 | positiveButton: left alt
62 | altNegativeButton:
63 | altPositiveButton: mouse 1
64 | gravity: 1000
65 | dead: 0.001
66 | sensitivity: 1000
67 | snap: 0
68 | invert: 0
69 | type: 0
70 | axis: 0
71 | joyNum: 0
72 | - serializedVersion: 3
73 | m_Name: Fire3
74 | descriptiveName:
75 | descriptiveNegativeName:
76 | negativeButton:
77 | positiveButton: left shift
78 | altNegativeButton:
79 | altPositiveButton: mouse 2
80 | gravity: 1000
81 | dead: 0.001
82 | sensitivity: 1000
83 | snap: 0
84 | invert: 0
85 | type: 0
86 | axis: 0
87 | joyNum: 0
88 | - serializedVersion: 3
89 | m_Name: Jump
90 | descriptiveName:
91 | descriptiveNegativeName:
92 | negativeButton:
93 | positiveButton: space
94 | altNegativeButton:
95 | altPositiveButton:
96 | gravity: 1000
97 | dead: 0.001
98 | sensitivity: 1000
99 | snap: 0
100 | invert: 0
101 | type: 0
102 | axis: 0
103 | joyNum: 0
104 | - serializedVersion: 3
105 | m_Name: Mouse X
106 | descriptiveName:
107 | descriptiveNegativeName:
108 | negativeButton:
109 | positiveButton:
110 | altNegativeButton:
111 | altPositiveButton:
112 | gravity: 0
113 | dead: 0
114 | sensitivity: 0.1
115 | snap: 0
116 | invert: 0
117 | type: 1
118 | axis: 0
119 | joyNum: 0
120 | - serializedVersion: 3
121 | m_Name: Mouse Y
122 | descriptiveName:
123 | descriptiveNegativeName:
124 | negativeButton:
125 | positiveButton:
126 | altNegativeButton:
127 | altPositiveButton:
128 | gravity: 0
129 | dead: 0
130 | sensitivity: 0.1
131 | snap: 0
132 | invert: 0
133 | type: 1
134 | axis: 1
135 | joyNum: 0
136 | - serializedVersion: 3
137 | m_Name: Mouse ScrollWheel
138 | descriptiveName:
139 | descriptiveNegativeName:
140 | negativeButton:
141 | positiveButton:
142 | altNegativeButton:
143 | altPositiveButton:
144 | gravity: 0
145 | dead: 0
146 | sensitivity: 0.1
147 | snap: 0
148 | invert: 0
149 | type: 1
150 | axis: 2
151 | joyNum: 0
152 | - serializedVersion: 3
153 | m_Name: Horizontal
154 | descriptiveName:
155 | descriptiveNegativeName:
156 | negativeButton:
157 | positiveButton:
158 | altNegativeButton:
159 | altPositiveButton:
160 | gravity: 0
161 | dead: 0.19
162 | sensitivity: 1
163 | snap: 0
164 | invert: 0
165 | type: 2
166 | axis: 0
167 | joyNum: 0
168 | - serializedVersion: 3
169 | m_Name: Vertical
170 | descriptiveName:
171 | descriptiveNegativeName:
172 | negativeButton:
173 | positiveButton:
174 | altNegativeButton:
175 | altPositiveButton:
176 | gravity: 0
177 | dead: 0.19
178 | sensitivity: 1
179 | snap: 0
180 | invert: 1
181 | type: 2
182 | axis: 1
183 | joyNum: 0
184 | - serializedVersion: 3
185 | m_Name: Fire1
186 | descriptiveName:
187 | descriptiveNegativeName:
188 | negativeButton:
189 | positiveButton: joystick button 0
190 | altNegativeButton:
191 | altPositiveButton:
192 | gravity: 1000
193 | dead: 0.001
194 | sensitivity: 1000
195 | snap: 0
196 | invert: 0
197 | type: 0
198 | axis: 0
199 | joyNum: 0
200 | - serializedVersion: 3
201 | m_Name: Fire2
202 | descriptiveName:
203 | descriptiveNegativeName:
204 | negativeButton:
205 | positiveButton: joystick button 1
206 | altNegativeButton:
207 | altPositiveButton:
208 | gravity: 1000
209 | dead: 0.001
210 | sensitivity: 1000
211 | snap: 0
212 | invert: 0
213 | type: 0
214 | axis: 0
215 | joyNum: 0
216 | - serializedVersion: 3
217 | m_Name: Fire3
218 | descriptiveName:
219 | descriptiveNegativeName:
220 | negativeButton:
221 | positiveButton: joystick button 2
222 | altNegativeButton:
223 | altPositiveButton:
224 | gravity: 1000
225 | dead: 0.001
226 | sensitivity: 1000
227 | snap: 0
228 | invert: 0
229 | type: 0
230 | axis: 0
231 | joyNum: 0
232 | - serializedVersion: 3
233 | m_Name: Jump
234 | descriptiveName:
235 | descriptiveNegativeName:
236 | negativeButton:
237 | positiveButton: joystick button 3
238 | altNegativeButton:
239 | altPositiveButton:
240 | gravity: 1000
241 | dead: 0.001
242 | sensitivity: 1000
243 | snap: 0
244 | invert: 0
245 | type: 0
246 | axis: 0
247 | joyNum: 0
248 | - serializedVersion: 3
249 | m_Name: Submit
250 | descriptiveName:
251 | descriptiveNegativeName:
252 | negativeButton:
253 | positiveButton: return
254 | altNegativeButton:
255 | altPositiveButton: joystick button 0
256 | gravity: 1000
257 | dead: 0.001
258 | sensitivity: 1000
259 | snap: 0
260 | invert: 0
261 | type: 0
262 | axis: 0
263 | joyNum: 0
264 | - serializedVersion: 3
265 | m_Name: Submit
266 | descriptiveName:
267 | descriptiveNegativeName:
268 | negativeButton:
269 | positiveButton: enter
270 | altNegativeButton:
271 | altPositiveButton: space
272 | gravity: 1000
273 | dead: 0.001
274 | sensitivity: 1000
275 | snap: 0
276 | invert: 0
277 | type: 0
278 | axis: 0
279 | joyNum: 0
280 | - serializedVersion: 3
281 | m_Name: Cancel
282 | descriptiveName:
283 | descriptiveNegativeName:
284 | negativeButton:
285 | positiveButton: escape
286 | altNegativeButton:
287 | altPositiveButton: joystick button 1
288 | gravity: 1000
289 | dead: 0.001
290 | sensitivity: 1000
291 | snap: 0
292 | invert: 0
293 | type: 0
294 | axis: 0
295 | joyNum: 0
296 |
--------------------------------------------------------------------------------
/ProjectSettings/MemorySettings.asset:
--------------------------------------------------------------------------------
1 | %YAML 1.1
2 | %TAG !u! tag:unity3d.com,2011:
3 | --- !u!387306366 &1
4 | MemorySettings:
5 | m_ObjectHideFlags: 0
6 | m_EditorMemorySettings:
7 | m_MainAllocatorBlockSize: -1
8 | m_ThreadAllocatorBlockSize: -1
9 | m_MainGfxBlockSize: -1
10 | m_ThreadGfxBlockSize: -1
11 | m_CacheBlockSize: -1
12 | m_TypetreeBlockSize: -1
13 | m_ProfilerBlockSize: -1
14 | m_ProfilerEditorBlockSize: -1
15 | m_BucketAllocatorGranularity: -1
16 | m_BucketAllocatorBucketsCount: -1
17 | m_BucketAllocatorBlockSize: -1
18 | m_BucketAllocatorBlockCount: -1
19 | m_ProfilerBucketAllocatorGranularity: -1
20 | m_ProfilerBucketAllocatorBucketsCount: -1
21 | m_ProfilerBucketAllocatorBlockSize: -1
22 | m_ProfilerBucketAllocatorBlockCount: -1
23 | m_TempAllocatorSizeMain: -1
24 | m_JobTempAllocatorBlockSize: -1
25 | m_BackgroundJobTempAllocatorBlockSize: -1
26 | m_JobTempAllocatorReducedBlockSize: -1
27 | m_TempAllocatorSizeGIBakingWorker: -1
28 | m_TempAllocatorSizeNavMeshWorker: -1
29 | m_TempAllocatorSizeAudioWorker: -1
30 | m_TempAllocatorSizeCloudWorker: -1
31 | m_TempAllocatorSizeGfx: -1
32 | m_TempAllocatorSizeJobWorker: -1
33 | m_TempAllocatorSizeBackgroundWorker: -1
34 | m_TempAllocatorSizePreloadManager: -1
35 | m_PlatformMemorySettings: {}
36 |
--------------------------------------------------------------------------------
/ProjectSettings/NavMeshAreas.asset:
--------------------------------------------------------------------------------
1 | %YAML 1.1
2 | %TAG !u! tag:unity3d.com,2011:
3 | --- !u!126 &1
4 | NavMeshProjectSettings:
5 | m_ObjectHideFlags: 0
6 | serializedVersion: 2
7 | areas:
8 | - name: Walkable
9 | cost: 1
10 | - name: Not Walkable
11 | cost: 1
12 | - name: Jump
13 | cost: 2
14 | - name:
15 | cost: 1
16 | - name:
17 | cost: 1
18 | - name:
19 | cost: 1
20 | - name:
21 | cost: 1
22 | - name:
23 | cost: 1
24 | - name:
25 | cost: 1
26 | - name:
27 | cost: 1
28 | - name:
29 | cost: 1
30 | - name:
31 | cost: 1
32 | - name:
33 | cost: 1
34 | - name:
35 | cost: 1
36 | - name:
37 | cost: 1
38 | - name:
39 | cost: 1
40 | - name:
41 | cost: 1
42 | - name:
43 | cost: 1
44 | - name:
45 | cost: 1
46 | - name:
47 | cost: 1
48 | - name:
49 | cost: 1
50 | - name:
51 | cost: 1
52 | - name:
53 | cost: 1
54 | - name:
55 | cost: 1
56 | - name:
57 | cost: 1
58 | - name:
59 | cost: 1
60 | - name:
61 | cost: 1
62 | - name:
63 | cost: 1
64 | - name:
65 | cost: 1
66 | - name:
67 | cost: 1
68 | - name:
69 | cost: 1
70 | - name:
71 | cost: 1
72 | m_LastAgentTypeID: -887442657
73 | m_Settings:
74 | - serializedVersion: 2
75 | agentTypeID: 0
76 | agentRadius: 0.5
77 | agentHeight: 2
78 | agentSlope: 45
79 | agentClimb: 0.75
80 | ledgeDropHeight: 0
81 | maxJumpAcrossDistance: 0
82 | minRegionArea: 2
83 | manualCellSize: 0
84 | cellSize: 0.16666667
85 | manualTileSize: 0
86 | tileSize: 256
87 | accuratePlacement: 0
88 | m_SettingNames:
89 | - Humanoid
90 |
--------------------------------------------------------------------------------
/ProjectSettings/NetworkManager.asset:
--------------------------------------------------------------------------------
1 | %YAML 1.1
2 | %TAG !u! tag:unity3d.com,2011:
3 | --- !u!149 &1
4 | NetworkManager:
5 | m_ObjectHideFlags: 0
6 | m_DebugLevel: 0
7 | m_Sendrate: 15
8 | m_AssetToPrefab: {}
9 |
--------------------------------------------------------------------------------
/ProjectSettings/PackageManagerSettings.asset:
--------------------------------------------------------------------------------
1 | %YAML 1.1
2 | %TAG !u! tag:unity3d.com,2011:
3 | --- !u!114 &1
4 | MonoBehaviour:
5 | m_ObjectHideFlags: 61
6 | m_CorrespondingSourceObject: {fileID: 0}
7 | m_PrefabInstance: {fileID: 0}
8 | m_PrefabAsset: {fileID: 0}
9 | m_GameObject: {fileID: 0}
10 | m_Enabled: 1
11 | m_EditorHideFlags: 0
12 | m_Script: {fileID: 13964, guid: 0000000000000000e000000000000000, type: 0}
13 | m_Name:
14 | m_EditorClassIdentifier:
15 | m_EnablePreReleasePackages: 0
16 | m_EnablePackageDependencies: 0
17 | m_AdvancedSettingsExpanded: 1
18 | m_ScopedRegistriesSettingsExpanded: 1
19 | m_SeeAllPackageVersions: 0
20 | oneTimeWarningShown: 0
21 | m_Registries:
22 | - m_Id: main
23 | m_Name:
24 | m_Url: https://packages.unity.com
25 | m_Scopes: []
26 | m_IsDefault: 1
27 | m_Capabilities: 7
28 | - m_Id: scoped:NPM
29 | m_Name: NPM
30 | m_Url: https://registry.npmjs.org
31 | m_Scopes:
32 | - com.adrenak.brw
33 | - com.adrenak.unimic
34 | - com.adrenak.rnnoise4unity
35 | - com.adrenak.concentus-unity
36 | m_IsDefault: 0
37 | m_Capabilities: 0
38 | m_UserSelectedRegistryName: NPM
39 | m_UserAddingNewScopedRegistry: 0
40 | m_RegistryInfoDraft:
41 | m_Modified: 0
42 | m_ErrorMessage:
43 | m_UserModificationsInstanceId: -848
44 | m_OriginalInstanceId: -852
45 | m_LoadAssets: 0
46 |
--------------------------------------------------------------------------------
/ProjectSettings/Physics2DSettings.asset:
--------------------------------------------------------------------------------
1 | %YAML 1.1
2 | %TAG !u! tag:unity3d.com,2011:
3 | --- !u!19 &1
4 | Physics2DSettings:
5 | m_ObjectHideFlags: 0
6 | serializedVersion: 3
7 | m_Gravity: {x: 0, y: -9.81}
8 | m_DefaultMaterial: {fileID: 0}
9 | m_VelocityIterations: 8
10 | m_PositionIterations: 3
11 | m_VelocityThreshold: 1
12 | m_MaxLinearCorrection: 0.2
13 | m_MaxAngularCorrection: 8
14 | m_MaxTranslationSpeed: 100
15 | m_MaxRotationSpeed: 360
16 | m_BaumgarteScale: 0.2
17 | m_BaumgarteTimeOfImpactScale: 0.75
18 | m_TimeToSleep: 0.5
19 | m_LinearSleepTolerance: 0.01
20 | m_AngularSleepTolerance: 2
21 | m_DefaultContactOffset: 0.01
22 | m_AutoSimulation: 1
23 | m_QueriesHitTriggers: 1
24 | m_QueriesStartInColliders: 1
25 | m_ChangeStopsCallbacks: 0
26 | m_CallbacksOnDisable: 1
27 | m_AlwaysShowColliders: 0
28 | m_ShowColliderSleep: 1
29 | m_ShowColliderContacts: 0
30 | m_ShowColliderAABB: 0
31 | m_ContactArrowScale: 0.2
32 | m_ColliderAwakeColor: {r: 0.5686275, g: 0.95686275, b: 0.54509807, a: 0.7529412}
33 | m_ColliderAsleepColor: {r: 0.5686275, g: 0.95686275, b: 0.54509807, a: 0.36078432}
34 | m_ColliderContactColor: {r: 1, g: 0, b: 1, a: 0.6862745}
35 | m_ColliderAABBColor: {r: 1, g: 1, b: 0, a: 0.2509804}
36 | m_LayerCollisionMatrix: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff
37 |
--------------------------------------------------------------------------------
/ProjectSettings/PresetManager.asset:
--------------------------------------------------------------------------------
1 | %YAML 1.1
2 | %TAG !u! tag:unity3d.com,2011:
3 | --- !u!1386491679 &1
4 | PresetManager:
5 | m_ObjectHideFlags: 0
6 | m_DefaultList: []
7 |
--------------------------------------------------------------------------------
/ProjectSettings/ProjectVersion.txt:
--------------------------------------------------------------------------------
1 | m_EditorVersion: 2021.3.45f2
2 | m_EditorVersionWithRevision: 2021.3.45f2 (88f88f591b2e)
3 |
--------------------------------------------------------------------------------
/ProjectSettings/QualitySettings.asset:
--------------------------------------------------------------------------------
1 | %YAML 1.1
2 | %TAG !u! tag:unity3d.com,2011:
3 | --- !u!47 &1
4 | QualitySettings:
5 | m_ObjectHideFlags: 0
6 | serializedVersion: 5
7 | m_CurrentQuality: 5
8 | m_QualitySettings:
9 | - serializedVersion: 2
10 | name: Very Low
11 | pixelLightCount: 0
12 | shadows: 0
13 | shadowResolution: 0
14 | shadowProjection: 1
15 | shadowCascades: 1
16 | shadowDistance: 15
17 | shadowNearPlaneOffset: 3
18 | shadowCascade2Split: 0.33333334
19 | shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667}
20 | shadowmaskMode: 0
21 | blendWeights: 1
22 | textureQuality: 1
23 | anisotropicTextures: 0
24 | antiAliasing: 0
25 | softParticles: 0
26 | softVegetation: 0
27 | realtimeReflectionProbes: 0
28 | billboardsFaceCameraPosition: 0
29 | vSyncCount: 0
30 | lodBias: 0.3
31 | maximumLODLevel: 0
32 | particleRaycastBudget: 4
33 | asyncUploadTimeSlice: 2
34 | asyncUploadBufferSize: 4
35 | resolutionScalingFixedDPIFactor: 1
36 | excludedTargetPlatforms: []
37 | - serializedVersion: 2
38 | name: Low
39 | pixelLightCount: 0
40 | shadows: 0
41 | shadowResolution: 0
42 | shadowProjection: 1
43 | shadowCascades: 1
44 | shadowDistance: 20
45 | shadowNearPlaneOffset: 3
46 | shadowCascade2Split: 0.33333334
47 | shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667}
48 | shadowmaskMode: 0
49 | blendWeights: 2
50 | textureQuality: 0
51 | anisotropicTextures: 0
52 | antiAliasing: 0
53 | softParticles: 0
54 | softVegetation: 0
55 | realtimeReflectionProbes: 0
56 | billboardsFaceCameraPosition: 0
57 | vSyncCount: 0
58 | lodBias: 0.4
59 | maximumLODLevel: 0
60 | particleRaycastBudget: 16
61 | asyncUploadTimeSlice: 2
62 | asyncUploadBufferSize: 4
63 | resolutionScalingFixedDPIFactor: 1
64 | excludedTargetPlatforms: []
65 | - serializedVersion: 2
66 | name: Medium
67 | pixelLightCount: 1
68 | shadows: 1
69 | shadowResolution: 0
70 | shadowProjection: 1
71 | shadowCascades: 1
72 | shadowDistance: 20
73 | shadowNearPlaneOffset: 3
74 | shadowCascade2Split: 0.33333334
75 | shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667}
76 | shadowmaskMode: 0
77 | blendWeights: 2
78 | textureQuality: 0
79 | anisotropicTextures: 1
80 | antiAliasing: 0
81 | softParticles: 0
82 | softVegetation: 0
83 | realtimeReflectionProbes: 0
84 | billboardsFaceCameraPosition: 0
85 | vSyncCount: 1
86 | lodBias: 0.7
87 | maximumLODLevel: 0
88 | particleRaycastBudget: 64
89 | asyncUploadTimeSlice: 2
90 | asyncUploadBufferSize: 4
91 | resolutionScalingFixedDPIFactor: 1
92 | excludedTargetPlatforms: []
93 | - serializedVersion: 2
94 | name: High
95 | pixelLightCount: 2
96 | shadows: 2
97 | shadowResolution: 1
98 | shadowProjection: 1
99 | shadowCascades: 2
100 | shadowDistance: 40
101 | shadowNearPlaneOffset: 3
102 | shadowCascade2Split: 0.33333334
103 | shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667}
104 | shadowmaskMode: 1
105 | blendWeights: 2
106 | textureQuality: 0
107 | anisotropicTextures: 1
108 | antiAliasing: 0
109 | softParticles: 0
110 | softVegetation: 1
111 | realtimeReflectionProbes: 1
112 | billboardsFaceCameraPosition: 1
113 | vSyncCount: 1
114 | lodBias: 1
115 | maximumLODLevel: 0
116 | particleRaycastBudget: 256
117 | asyncUploadTimeSlice: 2
118 | asyncUploadBufferSize: 4
119 | resolutionScalingFixedDPIFactor: 1
120 | excludedTargetPlatforms: []
121 | - serializedVersion: 2
122 | name: Very High
123 | pixelLightCount: 3
124 | shadows: 2
125 | shadowResolution: 2
126 | shadowProjection: 1
127 | shadowCascades: 2
128 | shadowDistance: 70
129 | shadowNearPlaneOffset: 3
130 | shadowCascade2Split: 0.33333334
131 | shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667}
132 | shadowmaskMode: 1
133 | blendWeights: 4
134 | textureQuality: 0
135 | anisotropicTextures: 2
136 | antiAliasing: 2
137 | softParticles: 1
138 | softVegetation: 1
139 | realtimeReflectionProbes: 1
140 | billboardsFaceCameraPosition: 1
141 | vSyncCount: 1
142 | lodBias: 1.5
143 | maximumLODLevel: 0
144 | particleRaycastBudget: 1024
145 | asyncUploadTimeSlice: 2
146 | asyncUploadBufferSize: 4
147 | resolutionScalingFixedDPIFactor: 1
148 | excludedTargetPlatforms: []
149 | - serializedVersion: 2
150 | name: Ultra
151 | pixelLightCount: 4
152 | shadows: 2
153 | shadowResolution: 2
154 | shadowProjection: 1
155 | shadowCascades: 4
156 | shadowDistance: 150
157 | shadowNearPlaneOffset: 3
158 | shadowCascade2Split: 0.33333334
159 | shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667}
160 | shadowmaskMode: 1
161 | blendWeights: 4
162 | textureQuality: 0
163 | anisotropicTextures: 2
164 | antiAliasing: 2
165 | softParticles: 1
166 | softVegetation: 1
167 | realtimeReflectionProbes: 1
168 | billboardsFaceCameraPosition: 1
169 | vSyncCount: 1
170 | lodBias: 2
171 | maximumLODLevel: 0
172 | particleRaycastBudget: 4096
173 | asyncUploadTimeSlice: 2
174 | asyncUploadBufferSize: 4
175 | resolutionScalingFixedDPIFactor: 1
176 | excludedTargetPlatforms: []
177 | m_PerPlatformDefaultQuality:
178 | Android: 2
179 | Nintendo 3DS: 5
180 | Nintendo Switch: 5
181 | PS4: 5
182 | PSM: 5
183 | PSP2: 2
184 | Samsung TV: 2
185 | Standalone: 5
186 | Tizen: 2
187 | Web: 5
188 | WebGL: 3
189 | WiiU: 5
190 | Windows Store Apps: 5
191 | XboxOne: 5
192 | iPhone: 2
193 | tvOS: 2
194 |
--------------------------------------------------------------------------------
/ProjectSettings/TagManager.asset:
--------------------------------------------------------------------------------
1 | %YAML 1.1
2 | %TAG !u! tag:unity3d.com,2011:
3 | --- !u!78 &1
4 | TagManager:
5 | serializedVersion: 2
6 | tags: []
7 | layers:
8 | - Default
9 | - TransparentFX
10 | - Ignore Raycast
11 | -
12 | - Water
13 | - UI
14 | -
15 | -
16 | -
17 | -
18 | -
19 | -
20 | -
21 | -
22 | -
23 | -
24 | -
25 | -
26 | -
27 | -
28 | -
29 | -
30 | -
31 | -
32 | -
33 | -
34 | -
35 | -
36 | -
37 | -
38 | -
39 | -
40 | m_SortingLayers:
41 | - name: Default
42 | uniqueID: 0
43 | locked: 0
44 |
--------------------------------------------------------------------------------
/ProjectSettings/TimeManager.asset:
--------------------------------------------------------------------------------
1 | %YAML 1.1
2 | %TAG !u! tag:unity3d.com,2011:
3 | --- !u!5 &1
4 | TimeManager:
5 | m_ObjectHideFlags: 0
6 | Fixed Timestep: 0.02
7 | Maximum Allowed Timestep: 0.33333334
8 | m_TimeScale: 1
9 | Maximum Particle Timestep: 0.03
10 |
--------------------------------------------------------------------------------
/ProjectSettings/UnityConnectSettings.asset:
--------------------------------------------------------------------------------
1 | %YAML 1.1
2 | %TAG !u! tag:unity3d.com,2011:
3 | --- !u!310 &1
4 | UnityConnectSettings:
5 | m_ObjectHideFlags: 0
6 | serializedVersion: 1
7 | m_Enabled: 0
8 | m_TestMode: 0
9 | m_EventOldUrl: https://api.uca.cloud.unity3d.com/v1/events
10 | m_EventUrl: https://cdp.cloud.unity3d.com/v1/events
11 | m_ConfigUrl: https://config.uca.cloud.unity3d.com
12 | m_DashboardUrl: https://dashboard.unity3d.com
13 | m_TestInitMode: 0
14 | CrashReportingSettings:
15 | m_EventUrl: https://perf-events.cloud.unity3d.com
16 | m_Enabled: 0
17 | m_LogBufferSize: 10
18 | m_CaptureEditorExceptions: 1
19 | UnityPurchasingSettings:
20 | m_Enabled: 0
21 | m_TestMode: 0
22 | UnityAnalyticsSettings:
23 | m_Enabled: 0
24 | m_TestMode: 0
25 | m_InitializeOnStartup: 1
26 | m_PackageRequiringCoreStatsPresent: 0
27 | UnityAdsSettings:
28 | m_Enabled: 0
29 | m_InitializeOnStartup: 1
30 | m_TestMode: 0
31 | m_IosGameId:
32 | m_AndroidGameId:
33 | m_GameIds: {}
34 | m_GameId:
35 | PerformanceReportingSettings:
36 | m_Enabled: 0
37 |
--------------------------------------------------------------------------------
/ProjectSettings/VFXManager.asset:
--------------------------------------------------------------------------------
1 | %YAML 1.1
2 | %TAG !u! tag:unity3d.com,2011:
3 | --- !u!937362698 &1
4 | VFXManager:
5 | m_ObjectHideFlags: 0
6 | m_IndirectShader: {fileID: 0}
7 | m_CopyBufferShader: {fileID: 0}
8 | m_SortShader: {fileID: 0}
9 | m_RenderPipeSettingsPath:
10 | m_FixedTimeStep: 0.016666668
11 | m_MaxDeltaTime: 0.05
12 |
--------------------------------------------------------------------------------
/ProjectSettings/VersionControlSettings.asset:
--------------------------------------------------------------------------------
1 | %YAML 1.1
2 | %TAG !u! tag:unity3d.com,2011:
3 | --- !u!890905787 &1
4 | VersionControlSettings:
5 | m_ObjectHideFlags: 0
6 | m_Mode: Visible Meta Files
7 | m_CollabEditorSettings:
8 | inProgressEnabled: 1
9 |
--------------------------------------------------------------------------------
/ProjectSettings/XRSettings.asset:
--------------------------------------------------------------------------------
1 | {
2 | "m_SettingKeys": [
3 | "VR Device Disabled",
4 | "VR Device User Alert"
5 | ],
6 | "m_SettingValues": [
7 | "False",
8 | "False"
9 | ]
10 | }
--------------------------------------------------------------------------------
/ProjectSettings/boot.config:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/adrenak/univoice/b4539c94336f8cda31b03b77b88d000f5b1bbf0a/ProjectSettings/boot.config
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # UniVoice
2 | UniVoice is a voice chat/VoIP solution for Unity.
3 |
4 | Some features of UniVoice:
5 | - 🎨 Customize your audio input, output and networking layers.
6 | * 🌐 __Configurable Network__:
7 | - UniVoice is networking agnostic. Implement the `IAudioClient` and `IAudioServer` interfaces using the networking plugin of your choice to have it send audio data over any networking solution.
8 | - Built-in support for:
9 | - [Mirror networking](https://mirror-networking.com/)
10 | - [Fish Networking](https://fish-networking.gitbook.io/docs)
11 |
12 | * 🎤 __Configurable Audio Input__:
13 | - UniVoice is audio input agnostic. You can change the source of outgoing audio by implementing the `IAudioInput` interface.
14 | - Built-in support for:
15 | - Capturing Mic audio as device input.
16 |
17 | * 🔊 __Configurable Audio Output__:
18 | - UniVoice is audio output agnostic. You can divert incoming audio to anywhere you want by implementing the `IAudioOutput` interface.
19 | - Built-in support for:
20 | - Playing incoming audio using Unity AudioSource.
21 |
22 | * ✏️ __Audio Filters__:
23 | - Modify outgoing and incoming audio by implementing the `IAudioFilter` interface.
24 | - Built-in support for:
25 | - Opus (Concentus) encoding & decoding.
26 | - RNNoise based noise removal.
27 | - Energy based VAD (Voice Activity Detection)
28 | - Gaussian blurring for minor denoising.
29 |
30 | - 👥 Easy integration with your existing networking solution
31 | - Whether you're using Mirror or FishNet, UniVoice runs in the background in sync with your networking lifecycle
32 | - A basic integration involves just initializing it on start.
33 | - For advanced usage like teams, chatrooms, lobbies, you can use the UniVoice API to create runtime behaviour.
34 |
35 | - ⚙ Fine control over audio data flow.
36 | * Don't want to listen to a peer? Mute them. Don't want someone listening to you? Deafen them.
37 | * Group players using tags and control audio flow between them. For example:
38 | - "red", "blue" and "spectator" tags for two teams playing against each other.
39 | - Red and Blue teams can only hear each other
40 | - Spectators can hear everyone
41 | - clients with "contestant", "judge" and "audience" tags for a virtual talent show.
42 | - Contestant can be heard by everyone, but don't hear anyone else (for focus)
43 | - Judges can talk to and hear each other for discussions. They can hear the contestant. But not the audience (for less noise)
44 | - Audience can hear and talk to each other. They can hear the performer. But they cannot hear the judges.
45 |
46 | ## Installation
47 | ⚠️ [OpenUPM](https://openupm.com/packages/com.adrenak.univoice/?subPage=versions) may not have up to date releases. Install using NPM registry instead 👇
48 |
49 | Ensure you have the NPM registry in the `manifest.json` file of your Unity project with the following scopes:
50 | ```
51 | "scopedRegistries": [
52 | {
53 | "name": "npmjs",
54 | "url": "https://registry.npmjs.org",
55 | "scopes": [
56 | "com.npmjs",
57 | "com.adrenak.univoice",
58 | "com.adrenak.brw",
59 | "com.adrenak.unimic",
60 | "com.adrenak.concentus-unity"
61 | ]
62 | }
63 | ]
64 | ```
65 | Then add `com.adrenak.univoice:x.y.z` to the `dependencies` in your `manifest.json` file (where x.y.z is the version you wish to install). The list of versions is available on [the UniVoice NPM page](https://www.npmjs.com/package/com.adrenak.univoice?activeTab=versions).
66 |
67 | ## Useful links
68 | * API reference is available here: http://www.vatsalambastha.com/univoice
69 | * UniVoice blog: https://blog.vatsalambastha.com/search/label/univoice
70 | * Discord server: https://discord.gg/NGvkEVbdjQ
71 |
72 | ## Integration
73 | UniVoice isn't currently very drag-and-drop/low-code. The best way to integrate is to have some code perform a one time setup when your app starts and provides access to relevant objects that you can use throughout the rest of the apps runtime.
74 |
75 | ## Samples
76 | This repository contains two samples:
77 | * `UniVoiceMirrorSetupSample.cs` is a drag and drop component, a simple integration sample script. You can add it to your Mirror NetworkManager to get voice chat to work. No code required, it's as simple as that! It'll work as long as you have setup your project properly. For more instructions see the top of the `UniVoiceMirrorSetupSample.cs` file.
78 | * `UniVoiceFishNetSetypSample.cs` is also very similar. Just drag and drop and it should work!
79 | * A sample scene that shows the other clients in a UI as well as allows you to mute yourself/them. This sample is Mirror based.
80 |
81 | > UniVoice currently only supports Mirror and FishNetworking out of the box. Follow the instructions in the "Activating non-packaged dependencies" section below before trying it out the samples.
82 |
83 | ## Dependencies
84 | [com.adrenak.brw](https://www.github.com/adrenak/brw) for reading and writing messages for communication. See `MirrorServer.cs` and `MirrorClient.cs` where they're used.
85 |
86 | [com.adrenak.unimic](https://www.github.com/adrenak/unimic) for easily capturing audio from any connected mic devices. See `UniMicInput.cs` for usage. Also used for streaming audio playback. See `StreamedAudioSourceOutput.cs` for usage.
87 |
88 | [com.adrenak.concentus-unity](https://www.github.com/adrenak/concentus-unity) for Opus encoding and decoding. See `ConcentusEncodeFilter.cs` and `ConcentusDecodeFilter.cs` for usage
89 |
90 | ## Activating non-packaged dependencies
91 | UniVoice includes and installs the dependencies mentioned above along with itself. The following implementations are available out of the box when you install it:
92 | * Opus encoding/decoding filter (via Contentus-Unity)
93 | * GaussianAudioBlur filter (plain C#, no dependencies used)
94 | * Mic audio capture input (via UniMic)
95 | * AudioSource based playback output (via UniMic)
96 |
97 | UniVoice has code that uses dependencies that you have to install and sometimes enable via compilation symbols as they are _not_ UniVoice dependencies and _don't_ get installed along with UniVoice. This is because they are either third party modules or based on native libraries (not plain C#) that can pose build issues.
98 | * RNNoise Noise removal filter:
99 | * To enable, ensure the [RNNoise4Unity](https://github.com/adrenak/RNNoise4Unity) package is in your project and add `UNIVOICE_FILTER_RNNOISE4UNITY` to activate it
100 | * Mirror network:
101 | * Just add the Mirror package to your project. UniVoice will detect it.
102 | * Fish Networking:
103 | * Just install FishNet package in your project. UniVoice will detect it.
104 |
105 | ## License and Support
106 | This project is under the [MIT license](https://github.com/adrenak/univoice/blob/master/LICENSE).
107 |
108 | Community contributions are welcome.
109 |
110 | Commercial engagements with the author can be arranged, subject to schedule and availability.
111 |
112 | ## Acknowledgements and contributors
113 | * [@metater](https://github.com/Metater/) for helping make improvements to audio streaming quality. [A related blog post](https://blog.vatsalambastha.com/2025/07/unimic-330-many-streamedaudiosource.html)
114 | * [@FrantisekHolubec](https://github.com/FrantisekHolubec) for [FishNet support code](https://github.com/adrenak/univoice/commit/fdc3424180d8991c92b3e092b3edb50b6110c863). Here's a [related blog post](https://blog.vatsalambastha.com/2025/09/univoice-480-fishnet-support.html)
115 | * [Masaryk University](https://www.muni.cz/en) for using UniVoice in their projects and providing helpful feedback
116 |
117 | ## Contact
118 | The author can be reached at the following links:
119 |
120 | [Website](http://www.vatsalambastha.com)
121 | [LinkedIn](https://www.linkedin.com/in/vatsalAmbastha)
122 | [GitHub](https://www.github.com/adrenak)
123 | [Twitter](https://www.twitter.com/vatsalAmbastha)
124 |
125 | Discord: `adrenak#1934`
126 |
--------------------------------------------------------------------------------