Skip to content

Commit

Permalink
Added MediaStream as node type.
Browse files Browse the repository at this point in the history
  • Loading branch information
KristofferStrube committed Oct 16, 2023
1 parent 817fb52 commit 1e84441
Show file tree
Hide file tree
Showing 12 changed files with 295 additions and 51 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,11 @@
}
}

protected override async Task OnParametersSetAsync()
{
await base.OnParametersSetAsync();
}

protected List<SupportedElement> SupportedElements { get; set; } = new()
{
new(typeof(G), element => element.TagName == "G"),
Expand All @@ -50,6 +55,7 @@
new(typeof(Gain), element => element.TagName is "RECT" && element.GetAttribute("data-elementtype") == "gain"),
new(typeof(Analyser), element => element.TagName is "RECT" && element.GetAttribute("data-elementtype") == "analyser"),
new(typeof(BiquadFilter), element => element.TagName is "RECT" && element.GetAttribute("data-elementtype") == "biquad-filter"),
new(typeof(MediaStreamAudioSource), element => element.TagName is "RECT" && element.GetAttribute("data-elementtype") == "media-stream-audio-source"),
new(typeof(Connector), element => element.TagName is "LINE" && element.GetAttribute("data-elementtype") == "connector"),
};

Expand All @@ -58,6 +64,7 @@
new(typeof(AddNewConnectorMenuItem), (_,data) => data is Port { Ingoing: false }),
new(typeof(AddNewAudioDestinationMenuItem), (_,_) => true),
new(typeof(AddNewOscillatorMenuItem), (_,_) => true),
new(typeof(AddNewMediaStreamAudioSourceMenuItem), (_,_) => true),
new(typeof(AddNewGainMenuItem), (_,_) => true),
new(typeof(AddNewAnalyserMenuItem), (_,_) => true),
new(typeof(AddNewBiquadFilterMenuItem), (_,_) => true),
Expand All @@ -81,6 +88,10 @@
{
await audioSource.StopAsync();
}
else if (node is MediaStreamAudioSource mediaStreamAudioSourceNode)
{
await mediaStreamAudioSourceNode.StopAsync(await mediaStreamAudioSourceNode.AudioNode(AudioContext));
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,36 +20,43 @@ public Connector(IElement element, SVGEditor.SVGEditor svg) : base(element, svg)

public override string StateRepresentation => base.StateRepresentation + IsHovered.ToString();

private (Node node, ulong port)? from;
public (Node node, ulong port)? From
{
get
{
var fromNode = (Node?)SVG.Elements.FirstOrDefault(e => e is Node && e.Id == Element.GetAttribute("data-from-node"));
ulong fromPort = (ulong)Element.GetAttributeOrZero("data-from-port");
_ = fromNode?.OutgoingConnectors.Add((this, fromPort));
if (fromNode is null) return null;
if (To is { } to)
if (from is null)
{
QueuedTasks.Enqueue(async context => await (await fromNode.AudioNode(context)).ConnectAsync(await to.node.AudioNode(context), fromPort, to.port));
var fromNode = (Node?)SVG.Elements.FirstOrDefault(e => e is Node && e.Id == Element.GetAttribute("data-from-node"));
ulong fromPort = (ulong)Element.GetAttributeOrZero("data-from-port");
_ = fromNode?.OutgoingConnectors.Add((this, fromPort));
if (fromNode is null) return null;
if (To is { } to)
{
QueuedTasks.Enqueue(async context => await (await fromNode.AudioNode(context)).ConnectAsync(await to.node.AudioNode(context), fromPort, to.port));
}
from = (fromNode, fromPort);
}
return (fromNode, fromPort);
return from;
}
set
{
if (From is { } from)
if (from is { } previousValue)
{
_ = from.node.OutgoingConnectors.Remove((this, from.port));
QueuedTasks.Enqueue(async context => await (await from.node.AudioNode(context)).DisconnectAsync(from.port));
_ = previousValue.node.OutgoingConnectors.Remove((this, previousValue.port));
QueuedTasks.Enqueue(async context => await (await previousValue.node.AudioNode(context)).DisconnectAsync(previousValue.port));
}
if (value is null)
{
_ = Element.RemoveAttribute("data-from-node");
_ = Element.RemoveAttribute("data-from-port");
from = null;
}
else
{
Element.SetAttribute("data-from-node", value.Value.node.Id);
Element.SetAttribute("data-from-port", value.Value.port.ToString());
from = value;
_ = value.Value.node.OutgoingConnectors.Add((this, value.Value.port));
if (To is { } to)
{
Expand All @@ -60,35 +67,41 @@ public Connector(IElement element, SVGEditor.SVGEditor svg) : base(element, svg)
}
}


private (Node node, ulong port)? to;
public (Node node, ulong port)? To
{
get
{
var toNode = (Node?)SVG.Elements.FirstOrDefault(e => e is Node && e.Id == Element.GetAttribute("data-to-node"));
ulong toPort = (ulong)Element.GetAttributeOrZero("data-to-port");
_ = toNode?.OutgoingConnectors.Add((this, toPort));
return toNode is null ? null : (toNode, toPort);
if (to is null)
{
var toNode = (Node?)SVG.Elements.FirstOrDefault(e => e is Node && e.Id == Element.GetAttribute("data-to-node"));
ulong toPort = (ulong)Element.GetAttributeOrZero("data-to-port");
_ = toNode?.OutgoingConnectors.Add((this, toPort));
to = toNode is null ? null : (toNode, toPort);
}
return to;
}
set
{
if (To is { } to)
if (to is { } previousValue)
{
_ = to.node.IngoingConnectors.Remove((this, to.port));
if (From is { } from)
_ = previousValue.node.IngoingConnectors.Remove((this, previousValue.port));
if (from is { } previouvFromValue)
{
QueuedTasks.Enqueue(async context => await (await from.node.AudioNode(context)).DisconnectAsync(from.port));
QueuedTasks.Enqueue(async context => await (await previouvFromValue.node.AudioNode(context)).DisconnectAsync(previouvFromValue.port));
}
}
if (value is null)
{
_ = Element.RemoveAttribute("data-to-node");
_ = Element.RemoveAttribute("data-to-port");
to = null;
}
else
{
Element.SetAttribute("data-to-node", value.Value.node.Id);
Element.SetAttribute("data-to-port", value.Value.port.ToString());
to = value;
_ = value.Value.node.IngoingConnectors.Add((this, value.Value.port));
if (From is { } from)
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
@using BlazorContextMenu
<Item OnClick="_ => MediaStreamAudioSource.AddNew(SVGEditor)"><div class="icon">🎤</div> New MediaStream Source</Item>
@code {
[CascadingParameter]
public required SVGEditor.SVGEditor SVGEditor { get; set; }

[Parameter]
public required object Data { get; set; }
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@
</ContextMenuTrigger>

@code {
private AnalyserNode? analyser;

private byte[] timeDomainMeasurements = Array.Empty<byte>();

[CascadingParameter]
Expand All @@ -52,30 +54,31 @@
{
await base.OnAfterRenderAsync(firstRender);

if (!firstRender) return;

AnalyserNode analyser = ((AnalyserNode)await SVGElement.AudioNode(AudioContext));
if (analyser is null && AudioContext is not null)
{
analyser = (AnalyserNode)await SVGElement.AudioNode(AudioContext);

int bufferLength = (int)await analyser.GetFrequencyBinCountAsync();
Uint8Array timeDomainDataArray = await Uint8Array.CreateAsync(AudioContext.JSRuntime, bufferLength);
Uint8Array frequencyDataArray = await Uint8Array.CreateAsync(AudioContext.JSRuntime, bufferLength);
int bufferLength = (int)await analyser.GetFrequencyBinCountAsync();
Uint8Array timeDomainDataArray = await Uint8Array.CreateAsync(AudioContext.JSRuntime, bufferLength);
Uint8Array frequencyDataArray = await Uint8Array.CreateAsync(AudioContext.JSRuntime, bufferLength);

SVGElement.Running = true;
SVGElement.Running = true;

while (SVGElement.Running)
{
try
while (SVGElement.Running)
{
await analyser.GetByteTimeDomainDataAsync(timeDomainDataArray);
try
{
await analyser.GetByteTimeDomainDataAsync(timeDomainDataArray);

timeDomainMeasurements = await timeDomainDataArray.GetByteArrayAsync();
await Task.Delay(10);
SVGElement._stateRepresentation = "";
StateHasChanged();
}
catch (Exception)
{

timeDomainMeasurements = await timeDomainDataArray.GetByteArrayAsync();
await Task.Delay(10);
SVGElement._stateRepresentation = "";
StateHasChanged();
}
catch (Exception)
{

}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
<br />
@if (GainAudioParam is not null)
{
<AudioParamSlider AudioParam="GainAudioParam" Label="Volume" Min="0" Max="1" StepSize="0.01f" UpdateCallback="f => SVGElement.GainValue = f" />
<AudioParamSlider AudioParam="GainAudioParam" Label="Volume" Min="0" Max="5" StepSize="0.01f" UpdateCallback="f => SVGElement.GainValue = f" />
}
</foreignObject>
<ContextMenuTrigger MenuId="SVGMenu" WrapperTag="g" Data=@(new Port(SVGElement, 0, true) { Node = SVGElement, PortNumber = 0 }) MouseButtonTrigger="SVGElement.ShouldTriggerContextMenu ? MouseButtonTrigger.Right : (MouseButtonTrigger)4">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
@using BlazorContextMenu
@using KristofferStrube.Blazor.SVGEditor.Extensions
@inherits NodeEditor<MediaStreamAudioSource>

<ContextMenuTrigger MenuId="SVGMenu" WrapperTag="g" Data=@SVGElement MouseButtonTrigger="SVGElement.ShouldTriggerContextMenu ? MouseButtonTrigger.Right : (MouseButtonTrigger)4">
<g transform="translate(@SVGElement.SVG.Translate.x.AsString() @SVGElement.SVG.Translate.y.AsString()) scale(@SVGElement.SVG.Scale.AsString())">
<rect @ref=ElementReference
@onfocusin="FocusElement"
@onfocusout="UnfocusElement"
@onpointerdown="SelectAsync"
@onkeyup="KeyUp"
tabindex="@(SVGElement.IsChildElement ? -1 : 0)"
x=@SVGElement.X.AsString()
y=@SVGElement.Y.AsString()
width=@SVGElement.Width.AsString()
height=@SVGElement.Height.AsString()
stroke="@SVGElement.Stroke"
stroke-width="@SVGElement.StrokeWidth"
stroke-linecap="@SVGElement.StrokeLinecap.AsString()"
stroke-linejoin="@SVGElement.StrokeLinejoin.AsString()"
stroke-dasharray="@SVGElement.StrokeDasharray"
stroke-dashoffset="@SVGElement.StrokeDashoffset.AsString()"
fill="@SVGElement.Fill"
style="filter:brightness(@(SVGElement.Selected ? "0.9" : "1"))">
</rect>
<foreignObject @onpointermove:stopPropagation=!ChildContentIsNoninteractive x="@((SVGElement.X+10).AsString())" y="@((SVGElement.Y+10).AsString())" height="80" width="200" style="border:solid @(SVGElement.StrokeWidth)px @(SVGElement.Stroke);padding:2;pointer-events:@(ChildContentIsNoninteractive ? "none" : "inherit");touch-action:@(ChildContentIsNoninteractive ? "none" : "inherit");">
<label for="audioSource">MediaStream Source</label>
@if (SVGElement.AudioOptions.Count > 0)
{
<select id="audioSource" @bind=SVGElement.SelectedAudioSource @bind:after="SetMediaStreamAudioSourceNode">
@foreach (var option in SVGElement.AudioOptions)
{
<option value="@option.id" selected="@(option.id == SVGElement.SelectedAudioSource)">@option.label</option>
}
</select>
}
</foreignObject>
<ContextMenuTrigger MenuId="SVGMenu" WrapperTag="g" Data=@(new Port(SVGElement, 0, false)) MouseButtonTrigger="SVGElement.ShouldTriggerContextMenu ? MouseButtonTrigger.Right : (MouseButtonTrigger)4">
<circle cx=@((SVGElement.X+SVGElement.Width).AsString()) cy="@((SVGElement.Y+20).AsString())" r="10" fill="grey">

</circle>
</ContextMenuTrigger>
</g>
</ContextMenuTrigger>

@code {
private MediaStreamAudioSourceNode? mediaStreamAudioSourceNode;

[CascadingParameter]
public required AudioContext AudioContext { get; set; }

protected override async Task OnAfterRenderAsync(bool firstRender)
{
await base.OnAfterRenderAsync(firstRender);

if (mediaStreamAudioSourceNode is null && AudioContext is not null)
{
mediaStreamAudioSourceNode = (MediaStreamAudioSourceNode)await SVGElement.AudioNode(AudioContext);
StateHasChanged();
}
}

protected async Task SetMediaStreamAudioSourceNode()
{
await SVGElement.SetMediaStreamAudioSourceNode(AudioContext);
StateHasChanged();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
@inherits ShapeEditor<TNode>

@code {
public bool ChildContentIsNoninteractive => SVGElement.Selected && SVGElement.SVG.EditMode is EditMode.Move;
public bool ChildContentIsNoninteractive => SVGElement.SVG.EditMode is EditMode.Move && SVGElement.Selected || SVGElement.SVG.EditMode is EditMode.Add or EditMode.Move;

public void SelectPort(int port)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,6 @@ public BiquadFilter(IElement element, SVGEditor.SVGEditor svg) : base(element, s
return audioNode;
};


public new float Height
{
get => 280;
Expand All @@ -41,7 +40,7 @@ public BiquadFilterType? Type
{
get
{
return Element.GetAttribute("data-type") is { } value ? Deserialize<BiquadFilterType>(value) : null;
return Element.GetAttribute("data-type") is { } value ? Deserialize<BiquadFilterType>($"\"{value}\"") : null;
}
set
{
Expand All @@ -51,7 +50,7 @@ public BiquadFilterType? Type
}
else
{
Element.SetAttribute("data-type", Serialize(value.Value));
Element.SetAttribute("data-type", Serialize(value.Value)[1..^1]);
}
Changed?.Invoke(this);
}
Expand All @@ -61,17 +60,17 @@ public float? Q
{
get
{
return Element.GetAttribute("data-q") is { } value ? float.Parse(value, CultureInfo.InvariantCulture) : null;
return Element.GetAttribute("data-Q") is { } value ? float.Parse(value, CultureInfo.InvariantCulture) : null;
}
set
{
if (value is null)
{
_ = Element.RemoveAttribute("data-q");
_ = Element.RemoveAttribute("data-Q");
}
else
{
Element.SetAttribute("data-q", value.Value.AsString());
Element.SetAttribute("data-v", value.Value.AsString());
}
Changed?.Invoke(this);
}
Expand Down Expand Up @@ -101,17 +100,17 @@ public float? Frequency
{
get
{
return Element.GetAttribute("data-frequnecy") is { } value ? float.Parse(value, CultureInfo.InvariantCulture) : null;
return Element.GetAttribute("data-frequency") is { } value ? float.Parse(value, CultureInfo.InvariantCulture) : null;
}
set
{
if (value is null)
{
_ = Element.RemoveAttribute("data-frequnecy");
_ = Element.RemoveAttribute("data-frequency");
}
else
{
Element.SetAttribute("data-frequnecy", value.Value.AsString());
Element.SetAttribute("data-frequency", value.Value.AsString());
}
Changed?.Invoke(this);
}
Expand Down
Loading

0 comments on commit 1e84441

Please sign in to comment.