Skip to content

Commit 2ae2c29

Browse files
Start on DotNetBot dancer sample.
1 parent e30ce46 commit 2ae2c29

File tree

2 files changed

+162
-7
lines changed

2 files changed

+162
-7
lines changed

samples/KristofferStrube.Blazor.WebAudio.WasmExample/KristofferStrube.Blazor.WebAudio.WasmExample.csproj

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -21,11 +21,4 @@
2121
<ItemGroup>
2222
<ProjectReference Include="..\..\src\KristofferStrube.Blazor.WebAudio\KristofferStrube.Blazor.WebAudio.csproj" />
2323
</ItemGroup>
24-
25-
<ItemGroup>
26-
<Content Update="Shared\AudioSlicer.razor">
27-
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
28-
</Content>
29-
</ItemGroup>
30-
3124
</Project>
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
@page "/DotNetBot"
2+
@using KristofferStrube.Blazor.MediaCaptureStreams;
3+
@using KristofferStrube.Blazor.WebIDL;
4+
@using KristofferStrube.Blazor.WebIDL.Exceptions;
5+
@implements IAsyncDisposable
6+
@inject IJSRuntime JSRuntime
7+
@inject IMediaDevicesService MediaDevicesService
8+
<PageTitle>WebAudio - DotNet Bot</PageTitle>
9+
<h2>.NET Bot</h2>
10+
11+
<p>
12+
On this page we will use your microphone input and then use an analyzer to make the .NET BOT "dance" to the tune of whatever music you hear.
13+
</p>
14+
15+
<svg width="400px" height="400px" viewBox="0 0 1000 1000">
16+
<radialGradient id="botGradient">
17+
<stop offset="0.2" style="stop-color:#A08BE8"></stop>
18+
<stop offset="0.8" style="stop-color:#8065E0"></stop>
19+
</radialGradient>
20+
<g id="legs">
21+
<path stroke="black" stroke-width="1" fill="url(#botGradient)" d="m 400 650 q -90 @(130 - CalcLeftLeg / 2) -60 @(300 - CalcLeftLeg) q 50 20 70 0 q -60 @(-90 + CalcLeftLeg / 2) 20 -300 z"></path>
22+
<path stroke="black" stroke-width="1" fill="url(#botGradient)" d="m 600 650 q 90 @(130 - CalcRightLeg / 2) 60 @(300 - CalcRightLeg) q -50 20 -70 0 q 60 @(-90 + CalcRightLeg / 2) -20 -300 z"></path>
23+
</g>
24+
<g id="body">
25+
<circle cx="500" cy="500" r="200" fill="url(#botGradient)"></circle>
26+
</g>
27+
</svg>
28+
29+
<br />
30+
@if (legValues.Count > 0)
31+
{
32+
<input type="range" max="255" min="0" value="@legValues.First()" />
33+
}
34+
<br />
35+
@if (error is { } errorMessage)
36+
{
37+
<p style="color: red;">@errorMessage</p>
38+
}
39+
else if (mediaStreamAudioSourceNode is null)
40+
{
41+
<button class="btn btn-primary" @onclick="OpenAudio">Load Microphone</button>
42+
}
43+
else
44+
{
45+
@if (audioOptions.Count > 0)
46+
{
47+
<label for="audioSource">Audio Source</label>
48+
<select id="audioSource" @bind=selectedAudioSource @bind:after="OpenAudio">
49+
@foreach (var option in audioOptions)
50+
{
51+
<option value="@option.id" selected="@(option.id == selectedAudioSource)">@option.label</option>
52+
}
53+
</select>
54+
}
55+
}
56+
57+
@code {
58+
AudioContext audioContext = default!;
59+
string? error;
60+
bool stopped;
61+
62+
private byte[] frequencies = Array.Empty<byte>();
63+
64+
Queue<double> legValues = new();
65+
byte arms = 0;
66+
67+
double CalcLeftLeg => Math.Round(legValues.Count > 0 ? ((legValues.Take(5).Average() - legValues.Min()) / (legValues.Max() - (float)legValues.Min())) * 255 : 1);
68+
69+
double CalcRightLeg => Math.Round(legValues.Count > 0 ? (1 - ((legValues.Take(5).Average() - legValues.Min()) / (legValues.Max() - (float)legValues.Min()))) * 255 : 1);
70+
71+
MediaDevices? mediaDevices;
72+
MediaStream? mediaStream;
73+
MediaStreamAudioSourceNode? mediaStreamAudioSourceNode;
74+
List<(string label, string id)> audioOptions = new();
75+
string? selectedAudioSource;
76+
77+
async Task OpenAudio()
78+
{
79+
await StopAudioTrack();
80+
81+
try
82+
{
83+
if (audioContext is null)
84+
{
85+
audioContext = await AudioContext.CreateAsync(JSRuntime);
86+
}
87+
if (mediaDevices is null)
88+
{
89+
mediaDevices = await MediaDevicesService.GetMediaDevicesAsync();
90+
}
91+
92+
MediaTrackConstraints mediaTrackConstraints = new MediaTrackConstraints
93+
{
94+
EchoCancellation = false,
95+
NoiseSuppression = false,
96+
AutoGainControl = false,
97+
DeviceId = selectedAudioSource is null ? null : new ConstrainDomString(selectedAudioSource)
98+
};
99+
mediaStream = await mediaDevices.GetUserMediaAsync(new MediaStreamConstraints() { Audio = mediaTrackConstraints });
100+
101+
var deviceInfos = await mediaDevices.EnumerateDevicesAsync();
102+
audioOptions.Clear();
103+
foreach (var device in deviceInfos)
104+
{
105+
if (await device.GetKindAsync() is MediaDeviceKind.AudioInput)
106+
{
107+
audioOptions.Add((await device.GetLabelAsync(), await device.GetDeviceIdAsync()));
108+
}
109+
}
110+
111+
mediaStreamAudioSourceNode = await audioContext.CreateMediaStreamSourceAsync(mediaStream);
112+
113+
var analyserNode = await audioContext.CreateAnalyserAsync();
114+
115+
await mediaStreamAudioSourceNode.ConnectAsync(analyserNode);
116+
117+
await Task.Delay(500);
118+
119+
int bufferLength = (int)await analyserNode.GetFrequencyBinCountAsync();
120+
var dataArray = await Uint8Array.CreateAsync(JSRuntime, bufferLength);
121+
122+
for (int i = 0; i < 200; i++)
123+
{
124+
legValues.Enqueue(0);
125+
}
126+
127+
stopped = false;
128+
while (!stopped)
129+
{
130+
await analyserNode.GetByteFrequencyDataAsync(dataArray);
131+
132+
frequencies = await dataArray.GetAsArrayAsync();
133+
legValues.Enqueue(frequencies[20]);
134+
legValues.Dequeue();
135+
await Task.Delay(10);
136+
StateHasChanged();
137+
}
138+
}
139+
catch (Exception ex)
140+
{
141+
error = $"{ex.GetType().Name}: {ex.Message}";
142+
}
143+
}
144+
145+
async Task StopAudioTrack()
146+
{
147+
stopped = true;
148+
if (mediaStream is null) return;
149+
var audioTrack = (await mediaStream.GetAudioTracksAsync()).FirstOrDefault();
150+
if (audioTrack is not null)
151+
{
152+
await audioTrack.StopAsync();
153+
}
154+
}
155+
156+
public async ValueTask DisposeAsync()
157+
{
158+
await StopAudioTrack();
159+
}
160+
}
161+
162+

0 commit comments

Comments
 (0)