Skip to content

Commit c4feff5

Browse files
authored
Merge pull request #4 from neocortex-link/feature/webgl-audio
WebGL Audio Support
2 parents 5c2c3ca + 8fde2bf commit c4feff5

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

63 files changed

+4701
-137
lines changed

Editor/NeocortexAudioReceiverEditor.cs renamed to Editor/AudioReceiverEditor.cs

+10-8
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33

44
namespace Neocortex.Editor
55
{
6-
[CustomEditor(typeof(NeocortexAudioReceiver))]
7-
public class NeocortexAudioReceiverEditor : UnityEditor.Editor
6+
[CustomEditor(typeof(AudioReceiver), true)]
7+
public class AudioReceiverEditor : UnityEditor.Editor
88
{
99
private const string MIC_INDEX_KEY = "neocortex-mic-index";
1010

@@ -18,9 +18,11 @@ private void OnEnable()
1818
{
1919
microphoneOptions = Microphone.devices;
2020
selectedMicrophoneIndex = PlayerPrefs.GetInt(MIC_INDEX_KEY, 0);
21+
22+
AudioReceiver audioReceiver = (AudioReceiver)target;
2123

22-
NeocortexAudioReceiver audioReceiver = (NeocortexAudioReceiver)target;
23-
audioReceiver.SelectedMicrophone = microphoneOptions[selectedMicrophoneIndex];
24+
if(audioReceiver is NeocortexAudioReceiver neocortexAudioReceiver)
25+
neocortexAudioReceiver.SelectedMicrophone = microphoneOptions[selectedMicrophoneIndex];
2426

2527
onAudioRecorded = serializedObject.FindProperty("OnAudioRecorded");
2628
onRecordingFailed = serializedObject.FindProperty("OnRecordingFailed");
@@ -30,13 +32,13 @@ public override void OnInspectorGUI()
3032
{
3133
DrawDefaultInspector();
3234

33-
NeocortexAudioReceiver audioReceiver = (NeocortexAudioReceiver)target;
34-
35-
if (microphoneOptions is { Length: > 0 })
35+
AudioReceiver audioReceiver = (AudioReceiver)target;
36+
37+
if (microphoneOptions is { Length: > 0 } && audioReceiver is NeocortexAudioReceiver neocortexAudioReceiver)
3638
{
3739
selectedMicrophoneIndex = EditorGUILayout.Popup("Select Microphone", selectedMicrophoneIndex, microphoneOptions);
3840
PlayerPrefs.SetInt(MIC_INDEX_KEY, selectedMicrophoneIndex);
39-
audioReceiver.SelectedMicrophone = microphoneOptions[selectedMicrophoneIndex];
41+
neocortexAudioReceiver.SelectedMicrophone = microphoneOptions[selectedMicrophoneIndex];
4042
}
4143
else
4244
{

Editor/NeocortexSettingsWindow.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ public class NeocortexSettingsWindow : EditorWindow
88
{
99
private static NeocortexSettings settings;
1010

11-
[MenuItem("Tools/Neocortex Settings", false, 0)]
11+
[MenuItem("Tools/Neocortex/API Key Setup", false, 0)]
1212
public static void ShowWindow()
1313
{
1414
NeocortexSettingsWindow window = GetWindow<NeocortexSettingsWindow>("Neocortex Settings");

Editor/UI/NeocortexAudioChatInput.cs

-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
using System;
21
using UnityEditor;
32
using UnityEngine;
43

Editor/WebGLTemplateImporter.cs

+75
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
using System.IO;
2+
using UnityEditor;
3+
using UnityEngine;
4+
5+
namespace Neocortex.Editor
6+
{
7+
[InitializeOnLoad]
8+
public class WebGLTemplateImporter
9+
{
10+
private const string SourceFolder = "Packages/link.neocortex.sdk/WebGLTemplates/Neocortex";
11+
private const string DestinationFolder = "Assets/WebGLTemplates/Neocortex";
12+
private const string ImportCompletedKey = "Neocortex.WebGLTemplateImported";
13+
14+
static WebGLTemplateImporter()
15+
{
16+
EditorApplication.delayCall += OnEditorLoaded;
17+
}
18+
19+
[MenuItem("Tools/Neocortex/Import WebGL Template", false, 0)]
20+
public static void ImportWebGLTemplate()
21+
{
22+
if (EditorUtility.DisplayDialog("Import WebGL Template", "This will overwrite any changes you have made in the WebGL template. Are you sure you want to continue?", "Yes", "No"))
23+
{
24+
if (Directory.Exists(DestinationFolder))
25+
{
26+
Directory.Delete(DestinationFolder, recursive: true);
27+
}
28+
29+
OnEditorLoaded();
30+
}
31+
}
32+
33+
private static void OnEditorLoaded()
34+
{
35+
if (EditorPrefs.HasKey(ImportCompletedKey) && Directory.Exists(DestinationFolder))
36+
{
37+
return;
38+
}
39+
40+
EditorPrefs.DeleteKey(ImportCompletedKey);
41+
42+
try{
43+
CopyDirectory(SourceFolder, DestinationFolder);
44+
AssetDatabase.Refresh();
45+
Debug.Log($"WebGL Template copied to {DestinationFolder} successfully.");
46+
}
47+
catch (System.Exception ex)
48+
{
49+
Debug.LogError($"Failed to copy WebGL Template: {ex.Message}");
50+
}
51+
}
52+
53+
private static void CopyDirectory(string sourceDir, string destinationDir)
54+
{
55+
if (!Directory.Exists(destinationDir))
56+
{
57+
Directory.CreateDirectory(destinationDir);
58+
}
59+
60+
foreach (var file in Directory.GetFiles(sourceDir))
61+
{
62+
string destFile = Path.Combine(destinationDir, Path.GetFileName(file));
63+
File.Copy(file, destFile, overwrite: true);
64+
}
65+
66+
foreach (var directory in Directory.GetDirectories(sourceDir))
67+
{
68+
string destDir = Path.Combine(destinationDir, Path.GetFileName(directory));
69+
CopyDirectory(directory, destDir);
70+
}
71+
72+
EditorPrefs.SetBool(ImportCompletedKey, true);
73+
}
74+
}
75+
}

Editor/WebGLTemplateImporter.cs.meta

+11
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Runtime/AudioReceiver.cs

+20
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
using System;
2+
using UnityEngine;
3+
using UnityEngine.Events;
4+
5+
namespace Neocortex
6+
{
7+
public abstract class AudioReceiver: MonoBehaviour
8+
{
9+
[SerializeField] private bool usePushToTalk;
10+
public bool UsePushToTalk { get => usePushToTalk; protected set => usePushToTalk = value; }
11+
public float Amplitude { get; protected set; }
12+
public float ElapsedWaitTime { get; protected set; }
13+
14+
public abstract void StartMicrophone();
15+
public abstract void StopMicrophone();
16+
17+
[HideInInspector] public UnityEvent<AudioClip> OnAudioRecorded;
18+
[HideInInspector] public UnityEvent<string> OnRecordingFailed;
19+
}
20+
}

Runtime/AudioReceiver.cs.meta

+11
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Runtime/Data/Enums.meta

+8
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Runtime/Data/Enums/MicrophoneState.cs

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
namespace Neocortex.Data
2+
{
3+
public enum MicrophoneState
4+
{
5+
NotActive = 0,
6+
Booting = 1,
7+
Recording = 2,
8+
}
9+
}

Runtime/Data/Enums/MicrophoneState.cs.meta

+11
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Runtime/Data/FloatArray.cs

+8
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
namespace Neocortex.Data
2+
{
3+
public struct FloatArray
4+
{
5+
public float [] Buffer;
6+
public int Written;
7+
}
8+
}

Runtime/Data/FloatArray.cs.meta

+11
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Runtime/Extension Methods/AudioClipExtensions.cs

+7
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,13 @@ public static AudioClip Trim(this AudioClip audioClip, float treshold = 0.002f)
1717

1818
sampleList.RemoveAll(sample => Mathf.Abs(sample) < treshold);
1919

20+
// if audio is shorter than 0.2 seconds, return null
21+
// this is to prevent sound such as mouse clicks from being sent
22+
if (sampleList.Count < audioClip.frequency / 4)
23+
{
24+
return null;
25+
}
26+
2027
if (sampleList.Count > 0)
2128
{
2229
var lengthSamples = Mathf.Max(sampleList.Count, audioClip.frequency);

Runtime/NeocortexAudioReceiver.cs

+19-21
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,9 @@
11
using System;
22
using UnityEngine;
3-
using UnityEngine.Events;
43

54
namespace Neocortex
65
{
7-
public class NeocortexAudioReceiver : MonoBehaviour
6+
public class NeocortexAudioReceiver : AudioReceiver
87
{
98
private const int FREQUENCY = 22050;
109
private const int AUDIO_SAMPLE_WINDOW = 64;
@@ -14,24 +13,16 @@ public class NeocortexAudioReceiver : MonoBehaviour
1413
private bool initialized;
1514

1615
public string SelectedMicrophone { get; set; }
17-
public float Amplitude { get; private set; }
1816
public bool IsUserSpeaking { get; private set; }
19-
public float ElapsedWaitTime { get; private set; }
20-
21-
public bool UsePushToTalk => usePushToTalk;
22-
23-
[HideInInspector] public UnityEvent<AudioClip> OnAudioRecorded;
24-
[HideInInspector] public UnityEvent<string> OnRecordingFailed;
2517

26-
[SerializeField, Range(0, 1)] private float amplitudeTreshold = 0.1f;
18+
[SerializeField, Range(0, 1)] private float amplitudeThreshold = 0.1f;
2719
[SerializeField] private float maxWaitTime = 1f;
28-
[SerializeField] private bool usePushToTalk;
2920

30-
public void StartMicrophone()
21+
public override void StartMicrophone()
3122
{
3223
try
3324
{
34-
audioClip = Microphone.Start(SelectedMicrophone, true, 999, FREQUENCY);
25+
audioClip = NeocortexMicrophone.Start(SelectedMicrophone, true, 999, FREQUENCY);
3526
initialized = true;
3627
}
3728
catch (Exception e)
@@ -40,9 +31,9 @@ public void StartMicrophone()
4031
}
4132
}
4233

43-
public void StopMicrophone()
34+
public override void StopMicrophone()
4435
{
45-
Microphone.End(SelectedMicrophone);
36+
NeocortexMicrophone.End(SelectedMicrophone);
4637
initialized = false;
4738
IsUserSpeaking = false;
4839
AudioRecorded();
@@ -54,16 +45,16 @@ private void Update()
5445

5546
UpdateAmplitude();
5647

57-
if (usePushToTalk) return;
48+
if (UsePushToTalk) return;
5849

59-
if(!IsUserSpeaking && Amplitude > amplitudeTreshold)
50+
if(!IsUserSpeaking && Amplitude > amplitudeThreshold)
6051
{
6152
IsUserSpeaking = true;
6253
}
6354

6455
if (IsUserSpeaking)
6556
{
66-
if (Amplitude < amplitudeTreshold)
57+
if (Amplitude < amplitudeThreshold)
6758
{
6859
ElapsedWaitTime += Time.deltaTime;
6960

@@ -83,12 +74,19 @@ private void Update()
8374
private void AudioRecorded()
8475
{
8576
AudioClip trimmed = audioClip.Trim();
86-
OnAudioRecorded?.Invoke(trimmed);
77+
if (!trimmed)
78+
{
79+
StartMicrophone();
80+
}
81+
else
82+
{
83+
OnAudioRecorded?.Invoke(trimmed);
84+
}
8785
}
8886

8987
private void UpdateAmplitude()
9088
{
91-
int clipPosition = Microphone.GetPosition(SelectedMicrophone);
89+
int clipPosition = NeocortexMicrophone.GetPosition(SelectedMicrophone);
9290
int startPosition = Mathf.Max(0, clipPosition - AUDIO_SAMPLE_WINDOW);
9391
float[] audioSamples = new float[AUDIO_SAMPLE_WINDOW];
9492
audioClip.GetData(audioSamples, startPosition);
@@ -106,7 +104,7 @@ private void OnDestroy()
106104
{
107105
if (initialized)
108106
{
109-
Microphone.End(SelectedMicrophone);
107+
NeocortexMicrophone.End(SelectedMicrophone);
110108
}
111109
}
112110
}

Runtime/NeocortexMicrophone.cs

+34
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
using UnityEngine;
2+
3+
namespace Neocortex
4+
{
5+
public sealed class NeocortexMicrophone
6+
{
7+
public static AudioClip Start(string deviceName, bool loop, int lengthSec, int frequency)
8+
{
9+
#if !UNITY_WEBGL || UNITY_EDITOR
10+
return Microphone.Start(deviceName, loop, lengthSec, frequency);
11+
#endif
12+
13+
return null;
14+
}
15+
16+
public static void End(string deviceName)
17+
{
18+
#if !UNITY_WEBGL || UNITY_EDITOR
19+
Microphone.End(deviceName);
20+
#endif
21+
22+
return;
23+
}
24+
25+
public static int GetPosition(string deviceName)
26+
{
27+
#if !UNITY_WEBGL || UNITY_EDITOR
28+
return Microphone.GetPosition(deviceName);
29+
#endif
30+
31+
return 0;
32+
}
33+
}
34+
}

Runtime/NeocortexMicrophone.cs.meta

+11
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)