├── .EditorConfig ├── .github └── workflows │ └── main.yml ├── .gitignore ├── Blazor.WebAudio.sln ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── docs └── icon.png ├── samples └── KristofferStrube.Blazor.WebAudio.WasmExample │ ├── ADSREditor │ ├── ADSREditor.razor │ ├── ADSRLine.cs │ └── ADSRLineEditor.razor │ ├── App.razor │ ├── AudioEditor │ ├── AudioEditor.razor │ ├── Connector.cs │ ├── ConnectorEditor.razor │ ├── DoubleExtensions.cs │ ├── FloatExtensions.cs │ ├── IHasQueueableTasks.cs │ ├── MenuItems │ │ ├── AddNewAnalyserMenuItem.razor │ │ ├── AddNewAudioDestinationMenuItem.razor │ │ ├── AddNewBiquadFilterMenuItem.razor │ │ ├── AddNewConnectorMenuItem.razor │ │ ├── AddNewGainMenuItem.razor │ │ ├── AddNewMediaStreamAudioSourceMenuItem.razor │ │ └── AddNewOscillatorMenuItem.razor │ ├── NodeEditors │ │ ├── AnalyserEditor.razor │ │ ├── AudioDestinationEditor.razor │ │ ├── BiquadFilterEditor.razor │ │ ├── GainEditor.razor │ │ ├── MediaStreamAudioSourceEditor.razor │ │ ├── NodeEditor.razor │ │ └── OscillatorEditor.razor │ ├── Nodes │ │ ├── Analyser.cs │ │ ├── AudioDestination.cs │ │ ├── BiquadFilter.cs │ │ ├── Gain.cs │ │ ├── MediaStreamAudioSource.cs │ │ ├── Node.cs │ │ └── Oscillator.cs │ └── Port.cs │ ├── AudioPannerEditor │ ├── ListenerShape.cs │ ├── ListenerShapeEditor.razor │ ├── PannerNodeShape.cs │ └── PannerNodeShapeEditor.razor │ ├── CustomPeriodicWaves │ └── ExpressionTemplates.cs │ ├── DoubleExtensions.cs │ ├── KristofferStrube.Blazor.WebAudio.WasmExample.csproj │ ├── Pages │ ├── AnalyzeMediaStream.razor │ ├── ApplyFilter.razor │ ├── AudioPlayer.razor │ ├── AudioPlayer.razor.css │ ├── AudioWorklets.razor │ ├── DecodeAudio.razor │ ├── Index.razor │ ├── Instruments.razor │ ├── Keyboard.razor │ ├── Keyboard.razor.css │ ├── Panning.razor │ ├── Panning.razor.css │ ├── Playground.razor │ ├── RecordMediaStream.razor │ ├── Spectrogram.razor │ └── Status.razor │ ├── Program.cs │ ├── Properties │ └── launchSettings.json │ ├── Shared │ ├── AmplitudePlot.razor │ ├── AmplitudePlot.razor.cs │ ├── AudioParamSlider.razor │ ├── AudioSlicer.razor │ ├── AudioSlicer.razor.cs │ ├── DoublePlot.razor │ ├── EnumSelector.razor │ ├── GainSlider.razor │ ├── MainLayout.razor │ ├── MainLayout.razor.css │ ├── NavMenu.razor │ ├── NavMenu.razor.css │ ├── Plot.razor │ ├── SpectrogramPlot.razor │ ├── SpectrogramPlot.razor.cs │ ├── TimeDomainPlot.razor │ └── TimeDomainPlot.razor.cs │ ├── _Imports.razor │ └── wwwroot │ ├── 404.html │ ├── Data │ ├── BIG HALL E001 M2S.wav │ ├── drama-cue-synth-and-cello-114417.mp3 │ ├── dream-sound-effect-downscale-7134.mp3 │ ├── file_example_MP3_700KB.mp3 │ └── yamaha-psr-16-demo-music-25226.mp3 │ ├── css │ ├── app.css │ ├── bootstrap │ │ ├── bootstrap.min.css │ │ └── bootstrap.min.css.map │ └── open-iconic │ │ ├── FONT-LICENSE │ │ ├── ICON-LICENSE │ │ ├── README.md │ │ └── font │ │ ├── css │ │ └── open-iconic-bootstrap.min.css │ │ └── fonts │ │ ├── open-iconic.eot │ │ ├── open-iconic.otf │ │ ├── open-iconic.svg │ │ ├── open-iconic.ttf │ │ └── open-iconic.woff │ ├── favicon.png │ ├── icon-192.png │ ├── index.html │ └── js │ └── white-noise.js ├── src └── KristofferStrube.Blazor.WebAudio │ ├── AudioBuffer.cs │ ├── AudioContext.cs │ ├── AudioContextState.cs │ ├── AudioListener.cs │ ├── AudioNodes │ ├── AnalyserNode.cs │ ├── AudioBufferSourceNode.cs │ ├── AudioDestinationNode.cs │ ├── AudioNode.cs │ ├── AudioScheduledSourceNode.cs │ ├── BiquadFilterNode.cs │ ├── ChannelMergerNode.cs │ ├── ChannelSplitterNode.cs │ ├── ConstantSourceNode.cs │ ├── ConvolverNode.cs │ ├── DelayNode.cs │ ├── DynamicsCompressorNode.cs │ ├── GainNode.cs │ ├── IIRFilterNode.cs │ ├── MediaElementAudioSourceNode.cs │ ├── MediaStreamAudioDestinationNode.cs │ ├── MediaStreamAudioSourceNode.cs │ ├── MediaStreamTrackAudioSourceNode.cs │ ├── OscillatorNode.cs │ ├── PannerNode.cs │ ├── StereoPannerNode.cs │ └── WaveShaperNode.cs │ ├── AudioParam.cs │ ├── AudioTimestamp.cs │ ├── AudioWorklet │ ├── AudioParamDescriptor.cs │ ├── AudioParamMap.cs │ ├── AudioWorklet.cs │ ├── AudioWorkletGlobalScope.cs │ ├── AudioWorkletNode.cs │ ├── AudioWorkletProcessors │ │ ├── AudioWorkletProcessor.cs │ │ ├── PullAudioWorkletProcessor.Options.cs │ │ └── PullAudioWorkletProcessor.cs │ ├── MessageEvent.cs │ ├── MessagePort.InProcess.cs │ ├── MessagePort.cs │ ├── RequestCredentials.cs │ ├── Worklet.cs │ └── WorkletOptions.cs │ ├── BaseAudioContext.cs │ ├── BaseJSWrapper.cs │ ├── Converters │ ├── AutomationRateConverter.cs │ ├── BiquadFilterTypeConverter.cs │ ├── ChannelCountModeConverter.cs │ ├── ChannelInterpretationConverter.cs │ ├── DistanceModelTypeConverter.cs │ ├── OscillatorTypeConverter.cs │ ├── OverSampleTypeConverter.cs │ ├── PanningModelTypeConverter.cs │ ├── RequestCredentialsConverter.cs │ └── UnionTypeJsonConverter.cs │ ├── Events │ ├── OfflineAudioCompletionEvent.cs │ └── OfflineAudioCompletionEventInit.cs │ ├── Extensions │ └── IJSRuntimeExtensions.cs │ ├── KristofferStrube.Blazor.WebAudio.csproj │ ├── OfflineAudioContext.cs │ ├── Options │ ├── AnalyserOptions.cs │ ├── AudioBufferOptions.cs │ ├── AudioBufferSourceOptions.cs │ ├── AudioContextLatencyCategory.cs │ ├── AudioContextOptions.cs │ ├── AudioNodeOptions.cs │ ├── AudioWorkletNodeOptions.cs │ ├── AutomationRate.cs │ ├── BiquadFilterOptions.cs │ ├── BiquadFilterType.cs │ ├── ChannelCountMode.cs │ ├── ChannelInterpretation.cs │ ├── ChannelMergerOptions.cs │ ├── ChannelSplitterOptions.cs │ ├── ConstantSourceOptions.cs │ ├── ConvolverOptions.cs │ ├── DelayOptions.cs │ ├── DistanceModelType.cs │ ├── DynamicsCompressorOptions.cs │ ├── GainOptions.cs │ ├── IIRFilterOptions.cs │ ├── MediaElementAudioSourceOptions.cs │ ├── MediaStreamAudioDestinationOptions.cs │ ├── MediaStreamAudioSourceOptions.cs │ ├── MediaStreamTrackAudioSourceOptions.cs │ ├── OfflineAudioContextOptions.cs │ ├── OscillatorOptions.cs │ ├── OscillatorType.cs │ ├── OverSampleType.cs │ ├── PannerOptions.cs │ ├── PanningModelType.cs │ ├── PeriodicWaveConstraints.cs │ ├── PeriodicWaveOptions.cs │ ├── StereoPannerOptions.cs │ └── WaveShaperOptions.cs │ ├── PeriodicWave.cs │ ├── UnionTypes │ ├── AudioContextLatencyCategoryOrDouble.cs │ └── UnionType.cs │ └── wwwroot │ ├── KristofferStrube.Blazor.WebAudio.PullAudioProcessor.js │ └── KristofferStrube.Blazor.WebAudio.js └── tests ├── BlazorServer ├── App.razor ├── BlazorServer.csproj ├── EvaluationContext.cs ├── MainLayout.razor ├── Pages │ ├── Index.razor │ └── _Host.cshtml ├── Program.cs ├── Properties │ └── launchSettings.json ├── Startup.cs ├── _Imports.razor ├── appsettings.Development.json ├── appsettings.json └── wwwroot │ └── css │ └── site.css ├── IntegrationTests ├── AudioNodeTests │ ├── AnalyserNodeTest.cs │ ├── AudioBufferSourceNodeTest.cs │ ├── AudioDestinationNodeTest.cs │ ├── AudioNodeTest.cs │ ├── AudioNodeWithAudioNodeOptions.cs │ ├── BiquadFilterNodeTest.cs │ ├── ChannelMergerNodeTest.cs │ ├── ChannelSplitterNodeTest.cs │ ├── ConstantSourceNodeTest.cs │ ├── ConvolverNodeTest.cs │ ├── DelayNodeTest.cs │ ├── DynamicsCompressorNodeTest.cs │ ├── GainNodeTest.cs │ ├── IIRFilterNodeTest.cs │ ├── MediaElementAudioSourceNodeTest.cs │ ├── MediaStreamAudioDestinationNodeTest.cs │ ├── MediaStreamAudioSourceNodeTest.cs │ ├── OscillatorNodeTest.cs │ ├── PannerNodeTest.cs │ ├── StereoPannerNodeTest.cs │ └── WaveShaperNodeTest.cs ├── Infrastructure │ ├── AudioContextEvaluationContext.cs │ └── BlazorTest.cs ├── IntegrationTests.csproj └── Usings.cs └── tests.sln /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | name: 'Publish application' 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | paths-ignore: 8 | - '**/README.md' 9 | 10 | jobs: 11 | build: 12 | runs-on: ubuntu-latest 13 | steps: 14 | # Checkout the code 15 | - uses: actions/checkout@v2 16 | 17 | # Install .NET 9.0 SDK 18 | - name: Setup .NET 9 preview 19 | uses: actions/setup-dotnet@v1 20 | with: 21 | dotnet-version: '9.0.x' 22 | include-prerelease: true 23 | 24 | # Added Ahead-Of-Time workload 25 | - name: Add AOT Workload 26 | run: | 27 | dotnet workload install wasm-tools 28 | dotnet workload restore 29 | 30 | # Build 31 | - name: Build 32 | run: dotnet build 33 | 34 | # Install PlayWright 35 | - name: Install PlayWright 36 | run: pwsh tests/IntegrationTests/bin/Debug/net9.0/playwright.ps1 install --with-deps 37 | 38 | # Run tests 39 | - name: Test 40 | run: dotnet test 41 | 42 | # Generate the website 43 | - name: Publish 44 | run: dotnet publish samples/KristofferStrube.Blazor.WebAudio.WasmExample/KristofferStrube.Blazor.WebAudio.WasmExample.csproj --configuration Release --output build 45 | 46 | # Publish the website 47 | - name: GitHub Pages action 48 | if: ${{ github.ref == 'refs/heads/main' }} # Publish only when the push is on main 49 | uses: peaceiris/actions-gh-pages@v3.6.1 50 | with: 51 | github_token: ${{ secrets.PUBLISH_TOKEN }} 52 | publish_branch: gh-pages 53 | publish_dir: build/wwwroot 54 | allow_empty_commit: false 55 | keep_files: false 56 | force_orphan: true 57 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # How to contribute 2 | Contributing includes many different actions. It is not only writing code. Contributions like discussing solutions in open issues are easily as valuable as contributing code as we can then find the best solution with the perspective of multiple people. 3 | 4 | ## Bugs and feature requests 5 | If you find any bugs in the project or have requests for features, then please [Open a new Issue](https://github.com/KristofferStrube/Blazor.WebAudio/issues/new). 6 | 7 | ## Contributing Code and Samples 8 | Before you contribute to the project, you will need to get confirmation from the library author that the contribution is welcome. 9 | This can help align the scope of the contribution so that you and the author agree on the solution and how you ensure the change is maintainable with the existing users in mind. 10 | Once you are ready to start coding try to follow these code guidelines: 11 | - Make a fork of the current `main` branch. 12 | - Follow the existing coding conventions used in the project. This includes tab style, naming conventions, etc. 13 | - If your contribution is a new feature, try to add a demo that demonstrates how this will be used in the sample project. 14 | - Any code or sample you share as a part of the resulting Pull Request should fall under the MIT license agreement. 15 | - You don't need to update the version number of the project as the maintainer will do this when making the next release after the Pull Request has been merged. 16 | - Keep your Pull Request to the point. I.e., if your Pull Request is related to fixing a bug then try not to touch any other files than the ones related to that issue as this will make the chance of the PR being merged without change requests more likely. 17 | 18 | ## Submitting a Pull Request 19 | If you don't know what a pull request is, read this article: https://help.github.com/articles/using-pull-requests. Make sure the repository can be built and that the related sample project still works as intended. It is also a good idea to familiarize yourself with the project workflow and our coding conventions. 20 | 21 | ## Ensuring that your contribution will be accepted 22 | You might also read these two blog posts on contributing code: [Open Source Contribution Etiquette](http://tirania.org/blog/archive/2010/Dec-31.html) by Miguel de Icaza and [Don't "Push" Your Pull Requests](https://www.igvita.com/2011/12/19/dont-push-your-pull-requests/) by Ilya Grigorik. These blog posts highlight good open-source collaboration etiquette and help align expectations between you and us. 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Kristoffer Strube 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](/LICENSE) 2 | [![GitHub issues](https://img.shields.io/github/issues/KristofferStrube/Blazor.WebAudio)](https://github.com/KristofferStrube/Blazor.WebAudio/issues) 3 | [![GitHub forks](https://img.shields.io/github/forks/KristofferStrube/Blazor.WebAudio)](https://github.com/KristofferStrube/Blazor.WebAudio/network/members) 4 | [![GitHub stars](https://img.shields.io/github/stars/KristofferStrube/Blazor.WebAudio)](https://github.com/KristofferStrube/Blazor.WebAudio/stargazers) 5 | [![NuGet Downloads (official NuGet)](https://img.shields.io/nuget/dt/KristofferStrube.Blazor.WebAudio?label=NuGet%20Downloads)](https://www.nuget.org/packages/KristofferStrube.Blazor.WebAudio/) 6 | 7 | # Blazor.WebAudio 8 | A Blazor wrapper for the [Web Audio browser API.](https://www.w3.org/TR/webaudio/) 9 | This Web API standardizes methods for processing and synthesizing audio in web applications. The primary paradigm is of an audio routing graph, where a number of AudioNode objects are connected together to define the overall audio rendering. This project implements a wrapper around the API for Blazor so that we can easily and safely work with audio in the browser. 10 | 11 | **This wrapper is still under development, but you can get the preview release on NuGet.** 12 | 13 | # Demo 14 | The sample project can be demoed at https://kristofferstrube.github.io/Blazor.WebAudio/ 15 | 16 | On each page, you can find the corresponding code for the example in the top right corner. 17 | 18 | On the [API Coverage Status](https://kristofferstrube.github.io/Blazor.WebAudio/Status) page, you can see how much of the WebIDL specs this wrapper has covered. 19 | 20 | 21 | # Related repositories 22 | The library uses the following other packages to support its features: 23 | - https://github.com/KristofferStrube/Blazor.WebIDL (To make error handling JSInterop) 24 | - https://github.com/KristofferStrube/Blazor.DOM (To implement *EventTarget*'s in the package like `BaseAudioContext` and `AudioNode`) 25 | - https://github.com/KristofferStrube/Blazor.MediaCaptureStreams (To enable the creation of `MediaStreamAudioDestinationNode`, `MediaStreamAudioSourceNode`, and other other `MediaStream` related nodes) 26 | 27 | # Related articles 28 | This repository was built with inspiration and help from the following series of articles: 29 | 30 | - [Typed exceptions for JSInterop in Blazor](https://kristoffer-strube.dk/post/typed-exceptions-for-jsinterop-in-blazor/) 31 | - [Wrapping JavaScript libraries in Blazor WebAssembly/WASM](https://blog.elmah.io/wrapping-javascript-libraries-in-blazor-webassembly-wasm/) 32 | - [Call anonymous C# functions from JS in Blazor WASM](https://blog.elmah.io/call-anonymous-c-functions-from-js-in-blazor-wasm/) 33 | - [Using JS Object References in Blazor WASM to wrap JS libraries](https://blog.elmah.io/using-js-object-references-in-blazor-wasm-to-wrap-js-libraries/) 34 | - [Blazor WASM 404 error and fix for GitHub Pages](https://blog.elmah.io/blazor-wasm-404-error-and-fix-for-github-pages/) 35 | - [How to fix Blazor WASM base path problems](https://blog.elmah.io/how-to-fix-blazor-wasm-base-path-problems/) 36 | -------------------------------------------------------------------------------- /docs/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KristofferStrube/Blazor.WebAudio/6a0e6ad35fdec6dfa78a14b33fd8285bab67d74f/docs/icon.png -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.WebAudio.WasmExample/ADSREditor/ADSREditor.razor: -------------------------------------------------------------------------------- 1 | @using KristofferStrube.Blazor.SVGEditor 2 | 3 | 10 | 11 | @code { 12 | private string input = ""; 13 | private SVGEditor sVGEditor = default!; 14 | private ADSRLine? ADSRLine; 15 | private bool hasBeenRendered = false; 16 | 17 | private const int defaultAttack = 5; 18 | private const int defaultDecay = 20; 19 | private const int defaultSustain = 70; 20 | private const int sustainLength = 20; 21 | private const int defaultRelease = 20; 22 | 23 | protected void InputHasBeenRendered() 24 | { 25 | sVGEditor.Scale = 1 / (140 / sVGEditor.BBox.Height); 26 | sVGEditor.Translate = (20 * sVGEditor.Scale, 20 * sVGEditor.Scale); 27 | 28 | if (!hasBeenRendered) 29 | { 30 | sVGEditor.DisableAllInteractions(); 31 | sVGEditor.DisableMoveAnchorEditMode = false; 32 | 33 | ADSRLine = new ADSRLine(sVGEditor.Document.CreateElement("POLYLINE"), sVGEditor) 34 | { 35 | Changed = sVGEditor.UpdateInput, 36 | Stroke = "black", 37 | StrokeWidth = "3", 38 | Fill = "none", 39 | StrokeLinecap = Linecap.Round, 40 | StrokeLinejoin = Linejoin.Round, 41 | Points = [(0, 100), 42 | (defaultAttack, 0), 43 | (defaultAttack + defaultDecay, 100 - defaultSustain), 44 | (defaultAttack + defaultDecay + sustainLength, 100 - defaultSustain), 45 | (defaultAttack + defaultDecay + sustainLength + defaultRelease, 100)] 46 | }; 47 | ADSRLine.Element.SetAttribute("points", ADSRLine.PointsToString(ADSRLine.Points)); 48 | 49 | sVGEditor.AddElement(ADSRLine); 50 | sVGEditor.SelectedShapes.Add(ADSRLine); 51 | StateHasChanged(); 52 | hasBeenRendered = true; 53 | } 54 | } 55 | 56 | public void SetPreset(int attack, int decay, int sustain, int release) 57 | { 58 | if (ADSRLine is null) return; 59 | 60 | ADSRLine.Points = [(0, 100), 61 | (attack, 0), 62 | (attack + decay, 100 - sustain), 63 | (attack + decay + sustainLength, 100 - sustain), 64 | (attack + decay + sustainLength + release, 100) 65 | ]; 66 | 67 | ADSRLine.UpdatePoints(); 68 | } 69 | 70 | public double Attack => (ADSRLine!.Points[1].x) / 100; 71 | 72 | public double Decay => (ADSRLine!.Points[2].x) / 100 - Attack; 73 | 74 | public float Sustain => (float)(1 - (ADSRLine!.Points[3].y) / 100); 75 | 76 | public double Release => (ADSRLine!.Points[4].x - ADSRLine!.Points[3].x) / 100; 77 | } 78 | -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.WebAudio.WasmExample/ADSREditor/ADSRLineEditor.razor: -------------------------------------------------------------------------------- 1 | @using KristofferStrube.Blazor.SVGEditor.ShapeEditors 2 | @using KristofferStrube.Blazor.SVGEditor.Extensions 3 | @using KristofferStrube.Blazor.SVGEditor 4 | @using Microsoft.AspNetCore.Components.Web 5 | 6 | @inherits ShapeEditor 7 | 8 | 9 | 15 | 16 | 19 | 22 | 25 | 28 | 29 | @for (int i = 0; i < SVGElement.Points.Count(); i++) 30 | { 31 | var j = i; 32 | 33 | } -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.WebAudio.WasmExample/App.razor: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | Not found 8 | 9 |

Sorry, there's nothing at this address.

10 |
11 |
12 |
13 | -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.WebAudio.WasmExample/AudioEditor/ConnectorEditor.razor: -------------------------------------------------------------------------------- 1 | @using BlazorContextMenu 2 | @using KristofferStrube.Blazor.SVGEditor.Extensions; 3 | @using KristofferStrube.Blazor.SVGEditor.ShapeEditors; 4 | @using KristofferStrube.Blazor.SVGEditor; 5 | @using Microsoft.AspNetCore.Components.Web; 6 | @inherits ShapeEditor 7 | 8 | 9 | 10 | 26 | 27 | 28 | 29 | 30 | @code { 31 | protected override async Task OnAfterRenderAsync(bool firstRender) 32 | { 33 | if (firstRender) 34 | { 35 | SVGElement.UpdateLine(); 36 | } 37 | await base.OnAfterRenderAsync(firstRender); 38 | } 39 | } -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.WebAudio.WasmExample/AudioEditor/DoubleExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | 3 | namespace KristofferStrube.Blazor.WebAudio.WasmExample.AudioEditor; 4 | 5 | internal static class DoubleExtensions 6 | { 7 | internal static string AsString(this double d) 8 | { 9 | return Math.Round(d, 9).ToString(CultureInfo.InvariantCulture); 10 | } 11 | } -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.WebAudio.WasmExample/AudioEditor/FloatExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | 3 | namespace KristofferStrube.Blazor.WebAudio.WasmExample.AudioEditor; 4 | 5 | internal static class FloatExtensions 6 | { 7 | internal static string AsString(this float f) 8 | { 9 | return Math.Round(f, 4).ToString(CultureInfo.InvariantCulture); 10 | } 11 | } -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.WebAudio.WasmExample/AudioEditor/IHasQueueableTasks.cs: -------------------------------------------------------------------------------- 1 | namespace KristofferStrube.Blazor.WebAudio.WasmExample.AudioEditor; 2 | 3 | public interface ITaskQueueable 4 | { 5 | public Queue> QueuedTasks { get; } 6 | } 7 | -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.WebAudio.WasmExample/AudioEditor/MenuItems/AddNewAnalyserMenuItem.razor: -------------------------------------------------------------------------------- 1 | @using BlazorContextMenu 2 | 
📈
New Analyser
3 | @code { 4 | [CascadingParameter] 5 | public required SVGEditor.SVGEditor SVGEditor { get; set; } 6 | 7 | [Parameter] 8 | public required object Data { get; set; } 9 | } -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.WebAudio.WasmExample/AudioEditor/MenuItems/AddNewAudioDestinationMenuItem.razor: -------------------------------------------------------------------------------- 1 | @using BlazorContextMenu 2 | 
🔊
New Audio Destination
3 | @code { 4 | [CascadingParameter] 5 | public required SVGEditor.SVGEditor SVGEditor { get; set; } 6 | 7 | [Parameter] 8 | public required object Data { get; set; } 9 | } -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.WebAudio.WasmExample/AudioEditor/MenuItems/AddNewBiquadFilterMenuItem.razor: -------------------------------------------------------------------------------- 1 | @using BlazorContextMenu 2 | 
🅱
New Biquad Filter
3 | @code { 4 | [CascadingParameter] 5 | public required SVGEditor.SVGEditor SVGEditor { get; set; } 6 | 7 | [Parameter] 8 | public required object Data { get; set; } 9 | } -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.WebAudio.WasmExample/AudioEditor/MenuItems/AddNewConnectorMenuItem.razor: -------------------------------------------------------------------------------- 1 | @using BlazorContextMenu 2 | 3 | @if (Data is Port port) {
🧵
New Connector
} 4 | 5 | @code { 6 | [CascadingParameter] 7 | public required SVGEditor.SVGEditor SVGEditor { get; set; } 8 | 9 | [Parameter] 10 | public required object Data { get; set; } 11 | } -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.WebAudio.WasmExample/AudioEditor/MenuItems/AddNewGainMenuItem.razor: -------------------------------------------------------------------------------- 1 | @using BlazorContextMenu 2 | 
📏
New Gain
3 | @code { 4 | [CascadingParameter] 5 | public required SVGEditor.SVGEditor SVGEditor { get; set; } 6 | 7 | [Parameter] 8 | public required object Data { get; set; } 9 | } -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.WebAudio.WasmExample/AudioEditor/MenuItems/AddNewMediaStreamAudioSourceMenuItem.razor: -------------------------------------------------------------------------------- 1 | @using BlazorContextMenu 2 | 
🎤
New MediaStream Source
3 | @code { 4 | [CascadingParameter] 5 | public required SVGEditor.SVGEditor SVGEditor { get; set; } 6 | 7 | [Parameter] 8 | public required object Data { get; set; } 9 | } -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.WebAudio.WasmExample/AudioEditor/MenuItems/AddNewOscillatorMenuItem.razor: -------------------------------------------------------------------------------- 1 | @using BlazorContextMenu 2 | 
New Oscillator
3 | @code { 4 | [CascadingParameter] 5 | public required SVGEditor.SVGEditor SVGEditor { get; set; } 6 | 7 | [Parameter] 8 | public required object Data { get; set; } 9 | } -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.WebAudio.WasmExample/AudioEditor/NodeEditors/AudioDestinationEditor.razor: -------------------------------------------------------------------------------- 1 | @using BlazorContextMenu 2 | @using KristofferStrube.Blazor.SVGEditor.Extensions 3 | @inherits NodeEditor 4 | 5 | 6 | 7 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | Audio Destination 33 | 34 | 35 | -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.WebAudio.WasmExample/AudioEditor/NodeEditors/NodeEditor.razor: -------------------------------------------------------------------------------- 1 | @using BlazorContextMenu 2 | @using KristofferStrube.Blazor.SVGEditor.ShapeEditors 3 | @using KristofferStrube.Blazor.SVGEditor.Extensions 4 | @using KristofferStrube.Blazor.SVGEditor; 5 | @using Microsoft.AspNetCore.Components.Web; 6 | @typeparam TNode where TNode : Node 7 | @inherits ShapeEditor 8 | 9 | @code { 10 | public bool ChildContentIsNoninteractive => SVGElement.SVG.EditMode is EditMode.Move && SVGElement.Selected || SVGElement.SVG.EditMode is EditMode.Add or EditMode.Move; 11 | 12 | public void SelectPort() 13 | { 14 | if (SVGElement.SVG.EditMode is EditMode.Add && SVGElement.SVG.SelectedShapes.Any(s => s is Connector)) 15 | { 16 | SVGElement.SVG.SelectedShapes.Add(SVGElement); 17 | } 18 | } 19 | 20 | public void SelectAudioParam(string paramName) 21 | { 22 | if (SVGElement.SVG.EditMode is EditMode.Add && SVGElement.SVG.SelectedShapes.Any(s => s is Connector)) 23 | { 24 | SVGElement.CurrentActiveAudioParamIdentifier = paramName; 25 | SVGElement.SVG.SelectedShapes.Add(SVGElement); 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.WebAudio.WasmExample/AudioEditor/Nodes/Analyser.cs: -------------------------------------------------------------------------------- 1 | using AngleSharp.Dom; 2 | using KristofferStrube.Blazor.WebAudio.WasmExample.AudioEditor.NodeEditors; 3 | namespace KristofferStrube.Blazor.WebAudio.WasmExample.AudioEditor; 4 | 5 | public class Analyser : Node 6 | { 7 | public Analyser(IElement element, SVGEditor.SVGEditor svg) : base(element, svg) { } 8 | 9 | private AudioNode? audioNode; 10 | public override Func> AudioNode => async (context) => 11 | { 12 | _ = await audioNodeSlim.WaitAsync(200); 13 | if (audioNode is null) 14 | { 15 | AnalyserNode analyser = await AnalyserNode.CreateAsync(context.JSRuntime, context); 16 | 17 | audioNode = analyser; 18 | } 19 | _ = audioNodeSlim.Release(); 20 | return audioNode; 21 | }; 22 | 23 | public new float Height 24 | { 25 | get => 130; 26 | set => base.Height = 130; 27 | } 28 | 29 | public string Type 30 | { 31 | get => Element.GetAttribute("data-type")!; 32 | set 33 | { 34 | if (value is null) 35 | { 36 | _ = Element.RemoveAttribute("data-type"); 37 | } 38 | else 39 | { 40 | Element.SetAttribute("data-type", value); 41 | } 42 | Changed?.Invoke(this); 43 | } 44 | } 45 | 46 | public bool Running { get; set; } 47 | 48 | public override Type Presenter => typeof(AnalyserEditor); 49 | 50 | public static new void AddNew(SVGEditor.SVGEditor SVG) 51 | { 52 | IElement element = SVG.Document.CreateElement("RECT"); 53 | element.SetAttribute("data-elementtype", "analyser"); 54 | 55 | Analyser node = new(element, SVG) 56 | { 57 | Changed = SVG.UpdateInput, 58 | Stroke = "#9CCC66", 59 | StrokeWidth = "2", 60 | Height = 130, 61 | Width = 250, 62 | Type = "TimeDomain", 63 | }; 64 | 65 | (node.X, node.Y) = SVG.LocalDetransform(SVG.LastRightClick); 66 | 67 | SVG.ClearSelectedShapes(); 68 | SVG.SelectShape(node); 69 | SVG.AddElement(node); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.WebAudio.WasmExample/AudioEditor/Nodes/AudioDestination.cs: -------------------------------------------------------------------------------- 1 | using AngleSharp.Dom; 2 | 3 | namespace KristofferStrube.Blazor.WebAudio.WasmExample.AudioEditor; 4 | 5 | public class AudioDestination : Node 6 | { 7 | public AudioDestination(IElement element, SVGEditor.SVGEditor svg) : base(element, svg) { } 8 | 9 | private AudioNode? audioNode; 10 | public override Func> AudioNode => async (context) => 11 | { 12 | _ = await audioNodeSlim.WaitAsync(200); 13 | audioNode ??= await context.GetDestinationAsync(); 14 | _ = audioNodeSlim.Release(); 15 | return audioNode; 16 | }; 17 | 18 | public override Type Presenter => typeof(NodeEditors.AudioDestinationEditor); 19 | 20 | public static new void AddNew(SVGEditor.SVGEditor SVG) 21 | { 22 | IElement element = SVG.Document.CreateElement("RECT"); 23 | element.SetAttribute("data-elementtype", "audio-destination"); 24 | 25 | AudioDestination node = new(element, SVG) 26 | { 27 | Changed = SVG.UpdateInput, 28 | Stroke = "#EC407A", 29 | StrokeWidth = "2", 30 | Height = 100, 31 | Width = 250, 32 | }; 33 | 34 | (node.X, node.Y) = SVG.LocalDetransform(SVG.LastRightClick); 35 | 36 | SVG.ClearSelectedShapes(); 37 | SVG.SelectShape(node); 38 | SVG.AddElement(node); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.WebAudio.WasmExample/AudioEditor/Nodes/Gain.cs: -------------------------------------------------------------------------------- 1 | using AngleSharp.Dom; 2 | using KristofferStrube.Blazor.WebAudio.WasmExample.AudioEditor.NodeEditors; 3 | using System.Globalization; 4 | 5 | namespace KristofferStrube.Blazor.WebAudio.WasmExample.AudioEditor; 6 | 7 | public class Gain : Node 8 | { 9 | public Gain(IElement element, SVGEditor.SVGEditor svg) : base(element, svg) 10 | { 11 | AudioParams = new() 12 | { 13 | ["gain"] = async (audioContext) => await ((GainNode)await AudioNode(audioContext)).GetGainAsync() 14 | }; 15 | } 16 | 17 | public override Dictionary AudioParamPositions { get; set; } = new() 18 | { 19 | ["gain"] = 60 20 | }; 21 | 22 | private AudioNode? audioNode; 23 | public override Func> AudioNode => async (context) => 24 | { 25 | _ = await audioNodeSlim.WaitAsync(200); 26 | if (audioNode is null) 27 | { 28 | GainOptions options = new(); 29 | if (GainValue is { } g) 30 | { 31 | options.Gain = g; 32 | } 33 | GainNode oscillator = await GainNode.CreateAsync(context.JSRuntime, context, options); 34 | audioNode = oscillator; 35 | } 36 | _ = audioNodeSlim.Release(); 37 | return audioNode; 38 | }; 39 | 40 | public float? GainValue 41 | { 42 | get => Element.GetAttribute("data-gain") is { } value ? float.Parse(value, CultureInfo.InvariantCulture) : null; 43 | set 44 | { 45 | if (value is null) 46 | { 47 | _ = Element.RemoveAttribute("data-gain"); 48 | } 49 | else 50 | { 51 | Element.SetAttribute("data-gain", value.Value.AsString()); 52 | } 53 | Changed?.Invoke(this); 54 | } 55 | } 56 | 57 | public override Type Presenter => typeof(GainEditor); 58 | 59 | public static new void AddNew(SVGEditor.SVGEditor SVG) 60 | { 61 | IElement element = SVG.Document.CreateElement("RECT"); 62 | element.SetAttribute("data-elementtype", "gain"); 63 | 64 | Gain node = new(element, SVG) 65 | { 66 | Changed = SVG.UpdateInput, 67 | Stroke = "#EE534F", 68 | StrokeWidth = "2", 69 | Height = 100, 70 | Width = 250, 71 | }; 72 | 73 | (node.X, node.Y) = SVG.LocalDetransform(SVG.LastRightClick); 74 | 75 | SVG.ClearSelectedShapes(); 76 | SVG.SelectShape(node); 77 | SVG.AddElement(node); 78 | } 79 | } 80 | -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.WebAudio.WasmExample/AudioEditor/Nodes/Node.cs: -------------------------------------------------------------------------------- 1 | using AngleSharp.Dom; 2 | using KristofferStrube.Blazor.SVGEditor; 3 | using Microsoft.AspNetCore.Components.Web; 4 | 5 | namespace KristofferStrube.Blazor.WebAudio.WasmExample.AudioEditor; 6 | 7 | public abstract class Node : Rect, ITaskQueueable 8 | { 9 | protected readonly SemaphoreSlim audioNodeSlim = new(1, 1); 10 | 11 | public Dictionary>> AudioParams { get; set; } = []; 12 | public virtual Dictionary AudioParamPositions { get; set; } = []; 13 | 14 | public int Offset(string? identifier = null) 15 | { 16 | return identifier is not null && AudioParamPositions.TryGetValue(identifier, out int offset) ? offset : 20; 17 | } 18 | 19 | public Node(IElement element, SVGEditor.SVGEditor svg) : base(element, svg) 20 | { 21 | string? id = element.GetAttribute("id"); 22 | if (id is null || svg.Elements.Any(e => e.Id == id)) 23 | { 24 | Id = Guid.NewGuid().ToString()[..8]; 25 | } 26 | } 27 | 28 | public Queue> QueuedTasks { get; } = new(); 29 | 30 | public override string Fill 31 | { 32 | get 33 | { 34 | int[] parts = Stroke[1..].Chunk(2).Select(part => int.Parse(part, System.Globalization.NumberStyles.HexNumber)).ToArray(); 35 | return "#" + string.Join("", parts.Select(part => Math.Min(255, part + 50).ToString("X2"))); 36 | } 37 | } 38 | 39 | public new float Width 40 | { 41 | get => 250; 42 | set => base.Width = 250; 43 | } 44 | 45 | public new float Height 46 | { 47 | get => 100; 48 | set => base.Height = 100; 49 | } 50 | 51 | public abstract Func> AudioNode { get; } 52 | 53 | public HashSet IngoingConnectors { get; } = []; 54 | public HashSet OutgoingConnectors { get; } = []; 55 | 56 | public string? CurrentActiveAudioParamIdentifier { get; set; } 57 | 58 | public override void HandlePointerMove(PointerEventArgs eventArgs) 59 | { 60 | base.HandlePointerMove(eventArgs); 61 | if (SVG.EditMode is EditMode.Move) 62 | { 63 | foreach (Connector connector in IngoingConnectors) 64 | { 65 | connector.UpdateLine(); 66 | } 67 | foreach (Connector connector in OutgoingConnectors) 68 | { 69 | connector.UpdateLine(); 70 | } 71 | } 72 | } 73 | 74 | public override void BeforeBeingRemoved() 75 | { 76 | foreach (Connector connector in IngoingConnectors) 77 | { 78 | SVG.RemoveElement(connector); 79 | } 80 | foreach (Connector connector in OutgoingConnectors) 81 | { 82 | SVG.RemoveElement(connector); 83 | } 84 | } 85 | } -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.WebAudio.WasmExample/AudioEditor/Port.cs: -------------------------------------------------------------------------------- 1 | namespace KristofferStrube.Blazor.WebAudio.WasmExample.AudioEditor; 2 | 3 | public record Port(Node Node, bool Ingoing); 4 | -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.WebAudio.WasmExample/AudioPannerEditor/ListenerShape.cs: -------------------------------------------------------------------------------- 1 | using AngleSharp.Dom; 2 | using KristofferStrube.Blazor.SVGEditor; 3 | 4 | namespace KristofferStrube.Blazor.WebAudio.WasmExample.AudioPannerEditor; 5 | 6 | public class ListenerShape : Circle 7 | { 8 | public ListenerShape(IElement element, SVGEditor.SVGEditor svg) : base(element, svg) 9 | { 10 | } 11 | 12 | public override Type Presenter => typeof(ListenerShapeEditor); 13 | 14 | public static ListenerShape AddNew(SVGEditor.SVGEditor SVG, double x, double y) 15 | { 16 | IElement element = SVG.Document.CreateElement("CIRCLE"); 17 | 18 | ListenerShape circle = new(element, SVG) 19 | { 20 | Changed = SVG.UpdateInput, 21 | Fill = "lightgrey", 22 | Cx = x, 23 | Cy = y, 24 | R = 1 25 | }; 26 | 27 | SVG.AddElement(circle); 28 | 29 | return circle; 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.WebAudio.WasmExample/AudioPannerEditor/ListenerShapeEditor.razor: -------------------------------------------------------------------------------- 1 | @using KristofferStrube.Blazor.SVGEditor 2 | @using KristofferStrube.Blazor.SVGEditor.ShapeEditors 3 | @using KristofferStrube.Blazor.SVGEditor.Extensions 4 | @inherits ShapeEditor 5 | 6 | 7 | 16 | 17 | 25 | 26 | 34 | 35 | -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.WebAudio.WasmExample/AudioPannerEditor/PannerNodeShape.cs: -------------------------------------------------------------------------------- 1 | using AngleSharp.Dom; 2 | using KristofferStrube.Blazor.SVGEditor; 3 | using Microsoft.AspNetCore.Components.Web; 4 | 5 | namespace KristofferStrube.Blazor.WebAudio.WasmExample.AudioPannerEditor; 6 | 7 | public class PannerNodeShape : Rect 8 | { 9 | public PannerNodeShape(IElement element, SVGEditor.SVGEditor svg) : base(element, svg) 10 | { 11 | } 12 | 13 | public override Type Presenter => typeof(PannerNodeShapeEditor); 14 | 15 | public double Rotation { get; set; } 16 | 17 | public override string StateRepresentation => base.StateRepresentation + Rotation; 18 | 19 | public override void HandlePointerMove(PointerEventArgs eventArgs) 20 | { 21 | if (SVG.CurrentAnchor is not 4 || SVG.EditMode is not EditMode.MoveAnchor) 22 | { 23 | base.HandlePointerMove(eventArgs); 24 | return; 25 | } 26 | 27 | (double x, double y) = SVG.LocalDetransform((eventArgs.OffsetX, eventArgs.OffsetY)); 28 | 29 | (double x, double y) rotationVector = 30 | ( 31 | x - (X + (Width / 2)), 32 | y - (Y + (Height / 2)) 33 | ); 34 | 35 | Rotation = (-Math.Atan(rotationVector.x / rotationVector.y) * 180 / Math.PI) + (rotationVector.y < 0 ? 180 : 0); 36 | } 37 | 38 | public static PannerNodeShape AddNew(SVGEditor.SVGEditor SVG, double x, double y, double rotation, string color) 39 | { 40 | IElement element = SVG.Document.CreateElement("RECT"); 41 | 42 | PannerNodeShape pannerNode = new(element, SVG) 43 | { 44 | Changed = SVG.UpdateInput, 45 | StrokeWidth = "0", 46 | Fill = color, 47 | Rotation = rotation 48 | }; 49 | (pannerNode.X, pannerNode.Y) = (x, y); 50 | (pannerNode.Width, pannerNode.Height) = (2, 2); 51 | 52 | SVG.AddElement(pannerNode); 53 | 54 | return pannerNode; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.WebAudio.WasmExample/AudioPannerEditor/PannerNodeShapeEditor.razor: -------------------------------------------------------------------------------- 1 | @using KristofferStrube.Blazor.SVGEditor 2 | @using KristofferStrube.Blazor.SVGEditor.ShapeEditors 3 | @using KristofferStrube.Blazor.SVGEditor.Extensions 4 | @inherits ShapeEditor 5 | 6 | 7 | 8 | 20 | 21 | 26 | 27 | 32 | 33 | 34 | 35 | 36 | @code { 37 | private (double x, double y) anchorPosition => 38 | ( 39 | x: SVGElement.X + SVGElement.Width / 2 + Math.Sin(-SVGElement.Rotation / 180 * Math.PI) * 10, 40 | y: SVGElement.Y + SVGElement.Height / 2 + Math.Cos(-SVGElement.Rotation / 180 * Math.PI) * 10 41 | ); 42 | 43 | public string Cone(double angle) 44 | { 45 | List<(double x, double y)> points = 46 | [ 47 | (SVGElement.X + SVGElement.Width / 2, SVGElement.Y + SVGElement.Height), 48 | (SVGElement.X + SVGElement.Width / 2 + 10 * Math.Sin(angle / 180 * Math.PI), SVGElement.Y + SVGElement.Height + 10 * Math.Cos(angle / 180 * Math.PI)), 49 | (SVGElement.X + SVGElement.Width / 2 + 10 * Math.Sin(-angle / 180 * Math.PI), SVGElement.Y + SVGElement.Height + 10 * Math.Cos(-angle / 180 * Math.PI)), 50 | ]; 51 | 52 | return string.Join(" ", points.Select(point => $"{point.x.AsString()},{point.y.AsString()}")); 53 | } 54 | } -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.WebAudio.WasmExample/DoubleExtensions.cs: -------------------------------------------------------------------------------- 1 | using System.Globalization; 2 | 3 | namespace KristofferStrube.Blazor.WebAudio.WasmExample; 4 | 5 | public static class DoubleExtensions 6 | { 7 | public static string AsString(this double value) 8 | { 9 | return value.ToString(CultureInfo.InvariantCulture); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.WebAudio.WasmExample/KristofferStrube.Blazor.WebAudio.WasmExample.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net9.0 5 | true 6 | enable 7 | enable 8 | preview 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.WebAudio.WasmExample/Pages/AudioPlayer.razor.css: -------------------------------------------------------------------------------- 1 | .media-control { 2 | background: none; 3 | border: none; 4 | padding: 0; 5 | outline: inherit; 6 | font-size:40px; 7 | margin:2px; 8 | } 9 | -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.WebAudio.WasmExample/Pages/Keyboard.razor.css: -------------------------------------------------------------------------------- 1 | ::deep .anchor-inner-thin { 2 | stroke-width: 2; 3 | r: 10; 4 | } 5 | 6 | ::deep .anchor-inner-thick { 7 | stroke-width: 4; 8 | r: 10; 9 | } 10 | 11 | ::deep .anchor-inner { 12 | stroke-width: 2; 13 | r: 10; 14 | } 15 | 16 | ::deep .anchor-outer-thin { 17 | stroke-width: 2; 18 | r: 12; 19 | } 20 | 21 | ::deep .anchor-outer-thick { 22 | stroke-width: 4; 23 | r: 14; 24 | } -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.WebAudio.WasmExample/Pages/Panning.razor.css: -------------------------------------------------------------------------------- 1 | ::deep .anchor-inner-thin { 2 | stroke-width: 2; 3 | r: 10 4 | } 5 | 6 | ::deep .anchor-inner-thick { 7 | stroke-width: 4; 8 | r: 10 9 | } 10 | 11 | ::deep .anchor-outer-thin { 12 | stroke-width: 2; 13 | r: 12 14 | } 15 | 16 | ::deep .anchor-outer-thick { 17 | stroke-width: 4; 18 | r: 14 19 | } 20 | -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.WebAudio.WasmExample/Pages/Spectrogram.razor: -------------------------------------------------------------------------------- 1 | @page "/Spectrogram" 2 | 3 | @page "/Spectogram" 4 | @using KristofferStrube.Blazor.WebIDL 5 | @implements IAsyncDisposable 6 | @inject IJSRuntime JSRuntime 7 | WebAudio - Spectrogram 8 |

Spectrogram

9 | 10 |

11 | On this page we create a Spectrogram by passing the sound of a random OscillatorNode through an AnalyserNode. 12 |

13 | 14 | @if (oscillator is null) 15 | { 16 | 17 | } 18 | else 19 | { 20 | 21 | } 22 |
23 | 24 |
25 | @if (frequency is not 0) 26 | { 27 |
28 | frequency: @frequency Hz 29 |
30 | type: @type 31 |
32 | } 33 | 34 | 35 | 36 | @code { 37 | AudioContext context = default!; 38 | GainNode gainNode = default!; 39 | OscillatorNode? oscillator; 40 | AnalyserNode? analyser; 41 | byte[] timeDomainMeasurements = Array.Empty(); 42 | byte[] frequencyMeasurements = Array.Empty(); 43 | float frequency; 44 | OscillatorType type; 45 | 46 | protected override async Task OnInitializedAsync() 47 | { 48 | context = await AudioContext.CreateAsync(JSRuntime); 49 | gainNode = await GainNode.CreateAsync(JSRuntime, context, new() { Gain = 0.1f }); 50 | 51 | AudioDestinationNode destination = await context.GetDestinationAsync(); 52 | await gainNode.ConnectAsync(destination); 53 | } 54 | 55 | public async Task PlaySound() 56 | { 57 | await StopSound(); 58 | type = (OscillatorType)Random.Shared.Next(0, 4); 59 | frequency = Random.Shared.Next(100, 500); 60 | 61 | analyser = await context.CreateAnalyserAsync(); 62 | await analyser.ConnectAsync(gainNode); 63 | 64 | oscillator = await OscillatorNode.CreateAsync(JSRuntime, context, new() { Type = type, Frequency = frequency }); 65 | await oscillator.ConnectAsync(analyser); 66 | await oscillator.StartAsync(); 67 | } 68 | 69 | public async Task StopSound() 70 | { 71 | if (oscillator is null) return; 72 | await oscillator.StopAsync(); 73 | oscillator = null; 74 | } 75 | 76 | public async ValueTask DisposeAsync() 77 | { 78 | await StopSound(); 79 | } 80 | } 81 | 82 | 83 | -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.WebAudio.WasmExample/Program.cs: -------------------------------------------------------------------------------- 1 | using KristofferStrube.Blazor.MediaCaptureStreams; 2 | using KristofferStrube.Blazor.SVGEditor.Extensions; 3 | using KristofferStrube.Blazor.WebAudio.WasmExample; 4 | using KristofferStrube.Blazor.WebIDL; 5 | using KristofferStrube.Blazor.Window; 6 | using Microsoft.AspNetCore.Components.Web; 7 | using Microsoft.AspNetCore.Components.WebAssembly.Hosting; 8 | 9 | var builder = WebAssemblyHostBuilder.CreateDefault(args); 10 | builder.RootComponents.Add("#app"); 11 | builder.RootComponents.Add("head::after"); 12 | 13 | builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); 14 | 15 | builder.Services.AddMediaDevicesService(); 16 | builder.Services.AddSVGEditor(); 17 | builder.Services.AddWindowService(); 18 | 19 | WebAssemblyHost app = builder.Build(); 20 | 21 | await app.Services.SetupErrorHandlingJSInterop(); 22 | 23 | await app.RunAsync(); -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.WebAudio.WasmExample/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:40854", 7 | "sslPort": 44371 8 | } 9 | }, 10 | "profiles": { 11 | "http": { 12 | "commandName": "Project", 13 | "dotnetRunMessages": true, 14 | "launchBrowser": true, 15 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", 16 | "applicationUrl": "http://localhost:5126", 17 | "environmentVariables": { 18 | "ASPNETCORE_ENVIRONMENT": "Development" 19 | } 20 | }, 21 | "https": { 22 | "commandName": "Project", 23 | "dotnetRunMessages": true, 24 | "launchBrowser": true, 25 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", 26 | "applicationUrl": "https://localhost:7240;http://localhost:5126", 27 | "environmentVariables": { 28 | "ASPNETCORE_ENVIRONMENT": "Development" 29 | } 30 | }, 31 | "IIS Express": { 32 | "commandName": "IISExpress", 33 | "launchBrowser": true, 34 | "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", 35 | "environmentVariables": { 36 | "ASPNETCORE_ENVIRONMENT": "Development" 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.WebAudio.WasmExample/Shared/AmplitudePlot.razor: -------------------------------------------------------------------------------- 1 | @using Excubo.Blazor.Canvas 2 | 3 | -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.WebAudio.WasmExample/Shared/AmplitudePlot.razor.cs: -------------------------------------------------------------------------------- 1 | using Excubo.Blazor.Canvas; 2 | using Excubo.Blazor.Canvas.Contexts; 3 | using KristofferStrube.Blazor.WebIDL; 4 | using Microsoft.AspNetCore.Components; 5 | using Microsoft.JSInterop; 6 | 7 | namespace KristofferStrube.Blazor.WebAudio.WasmExample.Shared; 8 | 9 | public partial class AmplitudePlot : ComponentBase, IDisposable 10 | { 11 | private bool running; 12 | private Canvas canvas = default!; 13 | 14 | private string canvasStyle => $"height:{Height}px;width:100%;"; 15 | 16 | [Inject] 17 | public required IJSRuntime JSRuntime { get; set; } 18 | 19 | [Parameter, EditorRequired] 20 | public AnalyserNode? Analyser { get; set; } 21 | 22 | [Parameter] 23 | public int Height { get; set; } = 200; 24 | 25 | [Parameter] 26 | public int Width { get; set; } = 180; 27 | 28 | [Parameter] 29 | public string Color { get; set; } = "#000"; 30 | 31 | protected override async Task OnAfterRenderAsync(bool _) 32 | { 33 | if (running || Analyser is null) 34 | { 35 | return; 36 | } 37 | 38 | running = true; 39 | 40 | try 41 | { 42 | int bufferLength = (int)await Analyser.GetFftSizeAsync(); 43 | await using Uint8Array timeDomainData = await Uint8Array.CreateAsync(JSRuntime, bufferLength); 44 | 45 | while (running) 46 | { 47 | for (int i = 0; i < Width; i++) 48 | { 49 | if (!running) 50 | { 51 | break; 52 | } 53 | 54 | await Analyser.GetByteTimeDomainDataAsync(timeDomainData); 55 | 56 | byte[] reading = await timeDomainData.GetAsArrayAsync(); 57 | 58 | double amplitude = reading.Average(r => Math.Abs(r - 128)) / 128.0; 59 | 60 | await using (Context2D context = await canvas.GetContext2DAsync()) 61 | { 62 | if (i == 0) 63 | { 64 | await context.FillAndStrokeStyles.FillStyleAsync($"#fff"); 65 | await context.FillRectAsync(0, 0, Width * 10, Height * 10); 66 | } 67 | 68 | await context.FillAndStrokeStyles.FillStyleAsync($"#fff"); 69 | await context.FillRectAsync(i * 10, 0, 10, Height * 10); 70 | 71 | await context.FillAndStrokeStyles.FillStyleAsync(Color); 72 | await context.FillRectAsync(i * 10, (Height * 10 / 2.0) - (amplitude * Height * 10), 10, amplitude * 2 * Height * 10); 73 | } 74 | 75 | await Task.Delay(1); 76 | } 77 | } 78 | } 79 | catch (Exception e) 80 | { 81 | Console.WriteLine(e.Message); 82 | } 83 | } 84 | 85 | public void Dispose() 86 | { 87 | running = false; 88 | GC.SuppressFinalize(this); 89 | } 90 | } -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.WebAudio.WasmExample/Shared/AudioParamSlider.razor: -------------------------------------------------------------------------------- 1 | @using System.Globalization; 2 | 3 | 4 | 5 | 14 | 15 | 18 | 19 | 20 | 21 | @code { 22 | private string inputId = $"audioParam_{Guid.NewGuid()}"[..9]; 23 | private float value = 0; 24 | 25 | [Parameter, EditorRequired] 26 | public required AudioParam AudioParam { get; set; } 27 | 28 | [Parameter, EditorRequired] 29 | public required string Label { get; set; } 30 | 31 | [Parameter,] 32 | public float Min { get; set; } = 0; 33 | 34 | [Parameter] 35 | public float Max { get; set; } = 100; 36 | 37 | [Parameter] 38 | public float StepSize { get; set; } = 0.01f; 39 | 40 | [Parameter] 41 | public Action? UpdateCallback { get; set; } 42 | 43 | protected override async Task OnParametersSetAsync() 44 | { 45 | if (AudioParam is null) return; 46 | value = await AudioParam.GetValueAsync(); 47 | } 48 | 49 | public async Task AudioParamUpdatedAsync() 50 | { 51 | if (AudioParam is null) return; 52 | await AudioParam.SetValueAsync(value); 53 | UpdateCallback?.Invoke(value); 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.WebAudio.WasmExample/Shared/AudioSlicer.razor: -------------------------------------------------------------------------------- 1 | @using Excubo.Blazor.Canvas 2 | 3 |
4 | 5 |
-------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.WebAudio.WasmExample/Shared/DoublePlot.razor: -------------------------------------------------------------------------------- 1 |  2 | @for (int i = 1; i < Data1.Length; i++) 3 | { 4 | 5 | } 6 | @for (int i = 1; i < Data2.Length; i++) 7 | { 8 | 9 | } 10 | 11 | 12 | @code { 13 | [Parameter] 14 | public byte[] Data1 { get; set; } = Array.Empty(); 15 | 16 | [Parameter] 17 | public byte[] Data2 { get; set; } = Array.Empty(); 18 | } 19 | -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.WebAudio.WasmExample/Shared/EnumSelector.razor: -------------------------------------------------------------------------------- 1 | @typeparam TEnum where TEnum : Enum 2 | 3 | 9 | 10 | @code { 11 | [Parameter] 12 | public bool Disabled { get; set; } = false; 13 | 14 | [Parameter, EditorRequired] 15 | public required TEnum Value { get; set; } 16 | 17 | [Parameter] 18 | public EventCallback ValueChanged { get; set; } 19 | 20 | private async Task OnValueChanged() 21 | { 22 | await ValueChanged.InvokeAsync(Value); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.WebAudio.WasmExample/Shared/GainSlider.razor: -------------------------------------------------------------------------------- 1 |  2 | 11 | @Math.Round(Gain*100, 0)% 12 | 13 | @code { 14 | private string inputId = $"gain_{Guid.NewGuid()}"[..9]; 15 | 16 | [Parameter, EditorRequired] 17 | public GainNode? GainNode { get; set; } 18 | 19 | public float Gain { get; private set; } = 0.05f; 20 | 21 | protected override async Task OnParametersSetAsync() 22 | { 23 | if (GainNode is null) return; 24 | await using AudioParam audioParam = await GainNode.GetGainAsync(); 25 | Gain = await audioParam.GetValueAsync(); 26 | } 27 | 28 | public async Task GainUpdatedAsync() 29 | { 30 | if (GainNode is null) return; 31 | await using AudioParam audioParam = await GainNode.GetGainAsync(); 32 | await audioParam.SetValueAsync(Gain); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.WebAudio.WasmExample/Shared/MainLayout.razor: -------------------------------------------------------------------------------- 1 | @inherits LayoutComponentBase 2 | @inject NavigationManager NavigationManager 3 | 4 | 5 |
6 | 9 | 10 |
11 |
12 | 13 | 14 | 15 |
16 | 17 |
18 | @Body 19 |
20 |
21 |
22 | 23 | 24 | @code { 25 | private string relativeUri => NavigationManager.ToBaseRelativePath(NavigationManager.Uri.Split("?")[0]); 26 | 27 | protected string page => (string.IsNullOrEmpty(relativeUri) ? "Index" : relativeUri) + ".razor"; 28 | } -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.WebAudio.WasmExample/Shared/MainLayout.razor.css: -------------------------------------------------------------------------------- 1 | .page { 2 | position: relative; 3 | display: flex; 4 | flex-direction: column; 5 | } 6 | 7 | main { 8 | flex: 1; 9 | } 10 | 11 | .sidebar { 12 | background-image: linear-gradient(180deg, rgb(5, 39, 103) 0%, #3a0647 70%); 13 | } 14 | 15 | .top-row { 16 | background-color: #f7f7f7; 17 | border-bottom: 1px solid #d6d5d5; 18 | justify-content: flex-end; 19 | height: 3.5rem; 20 | display: flex; 21 | align-items: center; 22 | } 23 | 24 | .top-row ::deep a, .top-row ::deep .btn-link { 25 | white-space: nowrap; 26 | margin-left: 1.5rem; 27 | text-decoration: none; 28 | } 29 | 30 | .top-row ::deep a:hover, .top-row ::deep .btn-link:hover { 31 | text-decoration: underline; 32 | } 33 | 34 | .top-row ::deep a:first-child { 35 | overflow: hidden; 36 | text-overflow: ellipsis; 37 | } 38 | 39 | @media (max-width: 640.98px) { 40 | .top-row:not(.auth) { 41 | display: none; 42 | } 43 | 44 | .top-row.auth { 45 | justify-content: space-between; 46 | } 47 | 48 | .top-row ::deep a, .top-row ::deep .btn-link { 49 | margin-left: 0; 50 | } 51 | } 52 | 53 | @media (min-width: 641px) { 54 | .page { 55 | flex-direction: row; 56 | } 57 | 58 | .sidebar { 59 | width: 250px; 60 | height: 100vh; 61 | position: sticky; 62 | top: 0; 63 | } 64 | 65 | .top-row { 66 | position: sticky; 67 | top: 0; 68 | z-index: 1; 69 | } 70 | 71 | .top-row.auth ::deep a:first-child { 72 | flex: 1; 73 | text-align: right; 74 | width: 0; 75 | } 76 | 77 | .top-row, article { 78 | padding-left: 2rem !important; 79 | padding-right: 1.5rem !important; 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.WebAudio.WasmExample/Shared/NavMenu.razor.css: -------------------------------------------------------------------------------- 1 | .navbar-toggler { 2 | background-color: rgba(255, 255, 255, 0.1); 3 | } 4 | 5 | .top-row { 6 | height: 3.5rem; 7 | background-color: rgba(0,0,0,0.4); 8 | } 9 | 10 | .navbar-brand { 11 | font-size: 1.1rem; 12 | } 13 | 14 | .oi { 15 | width: 2rem; 16 | font-size: 1.1rem; 17 | vertical-align: text-top; 18 | top: -2px; 19 | } 20 | 21 | .bi { 22 | display: inline-block; 23 | position: relative; 24 | width: 1.25rem; 25 | height: 1.25rem; 26 | margin-right: 0.75rem; 27 | top: -1px; 28 | background-size: cover; 29 | } 30 | 31 | .nav-item { 32 | font-size: 0.9rem; 33 | padding-bottom: 0.5rem; 34 | } 35 | 36 | .nav-item:first-of-type { 37 | padding-top: 1rem; 38 | } 39 | 40 | .nav-item:last-of-type { 41 | padding-bottom: 1rem; 42 | } 43 | 44 | .nav-item ::deep a { 45 | color: #d7d7d7; 46 | border-radius: 4px; 47 | height: 3rem; 48 | display: flex; 49 | align-items: center; 50 | line-height: 3rem; 51 | } 52 | 53 | .nav-item ::deep a.active { 54 | background-color: rgba(255,255,255,0.25); 55 | color: white; 56 | } 57 | 58 | .nav-item ::deep a:hover { 59 | background-color: rgba(255,255,255,0.1); 60 | color: white; 61 | } 62 | 63 | @media (min-width: 641px) { 64 | .navbar-toggler { 65 | display: none; 66 | } 67 | 68 | .collapse { 69 | /* Never collapse the sidebar for wide screens */ 70 | display: block; 71 | } 72 | 73 | .nav-scrollable { 74 | /* Allow sidebar to scroll for tall menus */ 75 | height: calc(100vh - 3.5rem); 76 | overflow-y: auto; 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.WebAudio.WasmExample/Shared/Plot.razor: -------------------------------------------------------------------------------- 1 |  2 | @for (int i = 1; i < Data.Length; i++) 3 | { 4 | 5 | } 6 | 7 | 8 | @code { 9 | [Parameter] 10 | public byte[] Data { get; set; } = Array.Empty(); 11 | 12 | [Parameter] 13 | public int Height { get; set; } = 200; 14 | 15 | [Parameter] 16 | public string Color { get; set; } = "red"; 17 | } 18 | -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.WebAudio.WasmExample/Shared/SpectrogramPlot.razor: -------------------------------------------------------------------------------- 1 | @using Excubo.Blazor.Canvas 2 | 3 | -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.WebAudio.WasmExample/Shared/SpectrogramPlot.razor.cs: -------------------------------------------------------------------------------- 1 | using Excubo.Blazor.Canvas; 2 | using Excubo.Blazor.Canvas.Contexts; 3 | using KristofferStrube.Blazor.WebIDL; 4 | using Microsoft.AspNetCore.Components; 5 | using Microsoft.JSInterop; 6 | 7 | namespace KristofferStrube.Blazor.WebAudio.WasmExample.Shared; 8 | 9 | public partial class SpectrogramPlot : ComponentBase, IDisposable 10 | { 11 | private bool running; 12 | private Canvas canvas = default!; 13 | 14 | private string canvasStyle => $"height:{Height}px;width:100%;"; 15 | 16 | [Inject] 17 | public required IJSRuntime JSRuntime { get; set; } 18 | 19 | [Parameter, EditorRequired] 20 | public AnalyserNode? Analyser { get; set; } 21 | 22 | [Parameter] 23 | public int Height { get; set; } = 200; 24 | 25 | [Parameter] 26 | public int Width { get; set; } = 200; 27 | 28 | protected override async Task OnAfterRenderAsync(bool _) 29 | { 30 | if (running || Analyser is null) 31 | { 32 | return; 33 | } 34 | 35 | running = true; 36 | 37 | int bufferLength = (int)await Analyser.GetFrequencyBinCountAsync(); 38 | await using Uint8Array frequencyDataArray = await Uint8Array.CreateAsync(JSRuntime, bufferLength); 39 | 40 | while (running) 41 | { 42 | for (int i = 0; i < Width; i++) 43 | { 44 | if (!running) 45 | { 46 | break; 47 | } 48 | 49 | await Analyser.GetByteFrequencyDataAsync(frequencyDataArray); 50 | 51 | byte[] reading = await frequencyDataArray.GetAsArrayAsync(); 52 | 53 | await using (Context2D context = await canvas.GetContext2DAsync()) 54 | { 55 | await context.FillAndStrokeStyles.FillStyleAsync($"#fff"); 56 | await context.FillRectAsync(i, 0, 1, Height); 57 | 58 | for (int j = 0; j < reading.Length; j++) 59 | { 60 | string color = $"#F{(255 - reading[j]) / 16:X}{(255 - reading[j]) / 16:X}"; 61 | await context.FillAndStrokeStyles.FillStyleAsync(color); 62 | await context.FillRectAsync(i, j / (double)reading.Length * Height, 1, 1); 63 | } 64 | } 65 | await Task.Delay(1); 66 | } 67 | } 68 | } 69 | 70 | public void Dispose() 71 | { 72 | running = false; 73 | GC.SuppressFinalize(this); 74 | } 75 | } -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.WebAudio.WasmExample/Shared/TimeDomainPlot.razor: -------------------------------------------------------------------------------- 1 | @using Excubo.Blazor.Canvas 2 | 3 | -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.WebAudio.WasmExample/Shared/TimeDomainPlot.razor.cs: -------------------------------------------------------------------------------- 1 | using KristofferStrube.Blazor.WebIDL; 2 | using Microsoft.AspNetCore.Components; 3 | using Microsoft.JSInterop; 4 | 5 | namespace KristofferStrube.Blazor.WebAudio.WasmExample.Shared; 6 | 7 | public partial class TimeDomainPlot : ComponentBase, IDisposable 8 | { 9 | private bool running; 10 | private byte[] timeDomainMeasurements = Array.Empty(); 11 | 12 | [Inject] 13 | public required IJSRuntime JSRuntime { get; set; } 14 | 15 | [Parameter, EditorRequired] 16 | public AnalyserNode? Analyser { get; set; } 17 | 18 | [Parameter] 19 | public int Height { get; set; } = 200; 20 | 21 | [Parameter] 22 | public string Color { get; set; } = "red"; 23 | 24 | protected override async Task OnAfterRenderAsync(bool _) 25 | { 26 | if (running || Analyser is null) 27 | { 28 | return; 29 | } 30 | 31 | running = true; 32 | 33 | int bufferLength = (int)await Analyser.GetFrequencyBinCountAsync(); 34 | Uint8Array timeDomainDataArray = await Uint8Array.CreateAsync(JSRuntime, bufferLength); 35 | 36 | while (running) 37 | { 38 | await Analyser.GetByteTimeDomainDataAsync(timeDomainDataArray); 39 | try 40 | { 41 | timeDomainMeasurements = await timeDomainDataArray.GetAsArrayAsync(); 42 | } 43 | catch 44 | { 45 | // If others try to retrieve a byte array at the same time Blazor will fail, so we simply just do nothing if it fails. 46 | } 47 | 48 | await Task.Delay(20); 49 | StateHasChanged(); 50 | } 51 | } 52 | 53 | public void Dispose() 54 | { 55 | running = false; 56 | GC.SuppressFinalize(this); 57 | } 58 | } -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.WebAudio.WasmExample/_Imports.razor: -------------------------------------------------------------------------------- 1 | @using System.Net.Http 2 | @using System.Net.Http.Json 3 | @using Microsoft.AspNetCore.Components.Forms 4 | @using Microsoft.AspNetCore.Components.Routing 5 | @using Microsoft.AspNetCore.Components.Web 6 | @using Microsoft.AspNetCore.Components.Web.Virtualization 7 | @using Microsoft.AspNetCore.Components.WebAssembly.Http 8 | @using Microsoft.JSInterop 9 | @using KristofferStrube.Blazor.WebAudio.WasmExample 10 | @using KristofferStrube.Blazor.WebAudio.WasmExample.Shared 11 | -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.WebAudio.WasmExample/wwwroot/404.html: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Blazor Web Audio 6 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.WebAudio.WasmExample/wwwroot/Data/BIG HALL E001 M2S.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KristofferStrube/Blazor.WebAudio/6a0e6ad35fdec6dfa78a14b33fd8285bab67d74f/samples/KristofferStrube.Blazor.WebAudio.WasmExample/wwwroot/Data/BIG HALL E001 M2S.wav -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.WebAudio.WasmExample/wwwroot/Data/drama-cue-synth-and-cello-114417.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KristofferStrube/Blazor.WebAudio/6a0e6ad35fdec6dfa78a14b33fd8285bab67d74f/samples/KristofferStrube.Blazor.WebAudio.WasmExample/wwwroot/Data/drama-cue-synth-and-cello-114417.mp3 -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.WebAudio.WasmExample/wwwroot/Data/dream-sound-effect-downscale-7134.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KristofferStrube/Blazor.WebAudio/6a0e6ad35fdec6dfa78a14b33fd8285bab67d74f/samples/KristofferStrube.Blazor.WebAudio.WasmExample/wwwroot/Data/dream-sound-effect-downscale-7134.mp3 -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.WebAudio.WasmExample/wwwroot/Data/file_example_MP3_700KB.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KristofferStrube/Blazor.WebAudio/6a0e6ad35fdec6dfa78a14b33fd8285bab67d74f/samples/KristofferStrube.Blazor.WebAudio.WasmExample/wwwroot/Data/file_example_MP3_700KB.mp3 -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.WebAudio.WasmExample/wwwroot/Data/yamaha-psr-16-demo-music-25226.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KristofferStrube/Blazor.WebAudio/6a0e6ad35fdec6dfa78a14b33fd8285bab67d74f/samples/KristofferStrube.Blazor.WebAudio.WasmExample/wwwroot/Data/yamaha-psr-16-demo-music-25226.mp3 -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.WebAudio.WasmExample/wwwroot/css/open-iconic/ICON-LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 Waybury 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 13 | all 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 21 | THE SOFTWARE. -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.WebAudio.WasmExample/wwwroot/css/open-iconic/font/fonts/open-iconic.eot: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KristofferStrube/Blazor.WebAudio/6a0e6ad35fdec6dfa78a14b33fd8285bab67d74f/samples/KristofferStrube.Blazor.WebAudio.WasmExample/wwwroot/css/open-iconic/font/fonts/open-iconic.eot -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.WebAudio.WasmExample/wwwroot/css/open-iconic/font/fonts/open-iconic.otf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KristofferStrube/Blazor.WebAudio/6a0e6ad35fdec6dfa78a14b33fd8285bab67d74f/samples/KristofferStrube.Blazor.WebAudio.WasmExample/wwwroot/css/open-iconic/font/fonts/open-iconic.otf -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.WebAudio.WasmExample/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KristofferStrube/Blazor.WebAudio/6a0e6ad35fdec6dfa78a14b33fd8285bab67d74f/samples/KristofferStrube.Blazor.WebAudio.WasmExample/wwwroot/css/open-iconic/font/fonts/open-iconic.ttf -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.WebAudio.WasmExample/wwwroot/css/open-iconic/font/fonts/open-iconic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KristofferStrube/Blazor.WebAudio/6a0e6ad35fdec6dfa78a14b33fd8285bab67d74f/samples/KristofferStrube.Blazor.WebAudio.WasmExample/wwwroot/css/open-iconic/font/fonts/open-iconic.woff -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.WebAudio.WasmExample/wwwroot/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KristofferStrube/Blazor.WebAudio/6a0e6ad35fdec6dfa78a14b33fd8285bab67d74f/samples/KristofferStrube.Blazor.WebAudio.WasmExample/wwwroot/favicon.png -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.WebAudio.WasmExample/wwwroot/icon-192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/KristofferStrube/Blazor.WebAudio/6a0e6ad35fdec6dfa78a14b33fd8285bab67d74f/samples/KristofferStrube.Blazor.WebAudio.WasmExample/wwwroot/icon-192.png -------------------------------------------------------------------------------- /samples/KristofferStrube.Blazor.WebAudio.WasmExample/wwwroot/js/white-noise.js: -------------------------------------------------------------------------------- 1 | class WhiteNoiseProcessor extends AudioWorkletProcessor { 2 | process(inputs, outputs, parameters) { 3 | const output = outputs[0]; 4 | output.forEach((channel) => { 5 | for (let i = 0; i < channel.length; i++) { 6 | channel[i] = Math.random() * 2 - 1; 7 | } 8 | }); 9 | return true; 10 | } 11 | } 12 | 13 | registerProcessor("white-noise", WhiteNoiseProcessor); -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.WebAudio/AudioContextState.cs: -------------------------------------------------------------------------------- 1 | namespace KristofferStrube.Blazor.WebAudio; 2 | 3 | /// 4 | /// The state of a . 5 | /// 6 | /// See the API definition here. 7 | public enum AudioContextState 8 | { 9 | /// 10 | /// This context is currently suspended (context time is not proceeding, audio hardware may be powered down/released). 11 | /// 12 | Suspended, 13 | /// 14 | /// Audio is being processed. 15 | /// 16 | Running, 17 | /// 18 | /// This context has been released, and can no longer be used to process audio. All system audio resources have been released. 19 | /// 20 | Closed 21 | } 22 | 23 | internal static class AudioContextStateExtensions 24 | { 25 | public static AudioContextState Parse(string state) 26 | { 27 | return state switch 28 | { 29 | "suspended" => AudioContextState.Suspended, 30 | "running" => AudioContextState.Running, 31 | "closed" => AudioContextState.Closed, 32 | _ => throw new ArgumentException($"'{state}' was not a valid {nameof(AudioContextState)}") 33 | }; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.WebAudio/AudioNodes/AudioDestinationNode.cs: -------------------------------------------------------------------------------- 1 | using KristofferStrube.Blazor.WebIDL; 2 | using Microsoft.JSInterop; 3 | 4 | namespace KristofferStrube.Blazor.WebAudio; 5 | 6 | /// 7 | /// This is an representing the final audio destination and is what the user will ultimately hear. 8 | /// It can often be considered as an audio output device which is connected to speakers. 9 | /// All rendered audio to be heard will be routed to this node, a "terminal" node in the 's routing graph. 10 | /// There is only a single per , provided through the method. 11 | /// 12 | /// See the API definition here. 13 | [IJSWrapperConverter] 14 | public class AudioDestinationNode : AudioNode, IJSCreatable 15 | { 16 | /// 17 | public static new async Task CreateAsync(IJSRuntime jSRuntime, IJSObjectReference jSReference) 18 | { 19 | return await CreateAsync(jSRuntime, jSReference, new()); 20 | } 21 | 22 | /// 23 | public static new Task CreateAsync(IJSRuntime jSRuntime, IJSObjectReference jSReference, CreationOptions options) 24 | { 25 | return Task.FromResult(new AudioDestinationNode(jSRuntime, jSReference, options)); 26 | } 27 | 28 | /// 29 | protected AudioDestinationNode(IJSRuntime jSRuntime, IJSObjectReference jSReference, CreationOptions options) : base(jSRuntime, jSReference, options) 30 | { 31 | } 32 | 33 | /// 34 | /// The maximum number of channels that the channelCount attribute can be set to. 35 | /// An representing the audio hardware end-point (the normal case) can potentially output more than 2 channels of audio if the audio hardware is multi-channel. 36 | /// maxChannelCount is the maximum number of channels that this hardware is capable of supporting. 37 | /// 38 | public async Task GetMaxChannelCountAsync() 39 | { 40 | IJSObjectReference helper = await webAudioHelperTask.Value; 41 | return await helper.InvokeAsync("getAttribute", JSReference, "maxChannelCount"); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.WebAudio/AudioNodes/GainNode.cs: -------------------------------------------------------------------------------- 1 | using KristofferStrube.Blazor.WebAudio.Extensions; 2 | using KristofferStrube.Blazor.WebIDL; 3 | using Microsoft.JSInterop; 4 | 5 | namespace KristofferStrube.Blazor.WebAudio; 6 | 7 | /// 8 | /// Changing the gain of an audio signal is a fundamental operation in audio applications. This interface is an with a single input and single output. 9 | /// 10 | /// See the API definition here. 11 | [IJSWrapperConverter] 12 | public class GainNode : AudioNode, IJSCreatable 13 | { 14 | /// 15 | public static new async Task CreateAsync(IJSRuntime jSRuntime, IJSObjectReference jSReference) 16 | { 17 | return await CreateAsync(jSRuntime, jSReference, new()); 18 | } 19 | 20 | /// 21 | public static new Task CreateAsync(IJSRuntime jSRuntime, IJSObjectReference jSReference, CreationOptions options) 22 | { 23 | return Task.FromResult(new GainNode(jSRuntime, jSReference, options)); 24 | } 25 | 26 | /// 27 | /// Creates an using the standard constructor. 28 | /// 29 | /// An instance. 30 | /// The this new will be associated with. 31 | /// Optional initial parameter value for this . 32 | /// A new instance of a . 33 | public static async Task CreateAsync(IJSRuntime jSRuntime, BaseAudioContext context, GainOptions? options = null) 34 | { 35 | IJSObjectReference helper = await jSRuntime.GetHelperAsync(); 36 | IJSObjectReference jSInstance = await helper.InvokeAsync("constructGainNode", context, options); 37 | return new GainNode(jSRuntime, jSInstance, new() { DisposesJSReference = true }); 38 | } 39 | 40 | /// 41 | protected GainNode(IJSRuntime jSRuntime, IJSObjectReference jSReference, CreationOptions options) : base(jSRuntime, jSReference, options) { } 42 | 43 | /// 44 | /// Represents the amount of gain to apply. 45 | /// 46 | public async Task GetGainAsync() 47 | { 48 | IJSObjectReference helper = await webAudioHelperTask.Value; 49 | IJSObjectReference jSInstance = await helper.InvokeAsync("getAttribute", JSReference, "gain"); 50 | return await AudioParam.CreateAsync(JSRuntime, jSInstance, new() { DisposesJSReference = true }); 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.WebAudio/AudioNodes/IIRFilterNode.cs: -------------------------------------------------------------------------------- 1 | using KristofferStrube.Blazor.WebAudio.Extensions; 2 | using KristofferStrube.Blazor.WebIDL; 3 | using Microsoft.JSInterop; 4 | 5 | namespace KristofferStrube.Blazor.WebAudio; 6 | 7 | /// 8 | /// is an processor implementing a general IIR Filter.
9 | /// In general, it is best to use 's to implement higher-order filters for the following reasons: 10 | /// 11 | /// Generally less sensitive to numeric issues. 12 | /// Filter parameters can be automated. 13 | /// Can be used to create all even-ordered IIR filters. 14 | /// 15 | /// However, odd-ordered filters cannot be created, so if such filters are needed or automation is not needed, then IIR filters may be appropriate.
16 | /// Once created, the coefficients of the IIR filter cannot be changed. 17 | ///
18 | /// See the API definition here. 19 | [IJSWrapperConverter] 20 | public class IIRFilterNode : AudioNode, IJSCreatable 21 | { 22 | /// 23 | public static new async Task CreateAsync(IJSRuntime jSRuntime, IJSObjectReference jSReference) 24 | { 25 | return await CreateAsync(jSRuntime, jSReference, new()); 26 | } 27 | 28 | /// 29 | public static new Task CreateAsync(IJSRuntime jSRuntime, IJSObjectReference jSReference, CreationOptions options) 30 | { 31 | return Task.FromResult(new IIRFilterNode(jSRuntime, jSReference, options)); 32 | } 33 | 34 | /// 35 | /// Creates an using the standard constructor. 36 | /// 37 | /// An instance. 38 | /// The this new will be associated with. 39 | /// Initial parameter value for this . 40 | /// A new instance of a . 41 | public static async Task CreateAsync(IJSRuntime jSRuntime, BaseAudioContext context, IIRFilterOptions options) 42 | { 43 | IJSObjectReference helper = await jSRuntime.GetHelperAsync(); 44 | IJSObjectReference jSInstance = await helper.InvokeAsync("constructIIRFilterNode", context, options); 45 | return new IIRFilterNode(jSRuntime, jSInstance, new() { DisposesJSReference = true }); 46 | } 47 | 48 | /// 49 | protected IIRFilterNode(IJSRuntime jSRuntime, IJSObjectReference jSReference, CreationOptions options) : base(jSRuntime, jSReference, options) { } 50 | } 51 | -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.WebAudio/AudioNodes/MediaElementAudioSourceNode.cs: -------------------------------------------------------------------------------- 1 | using KristofferStrube.Blazor.WebAudio.Extensions; 2 | using KristofferStrube.Blazor.WebAudio.Options; 3 | using KristofferStrube.Blazor.WebIDL; 4 | using Microsoft.JSInterop; 5 | 6 | namespace KristofferStrube.Blazor.WebAudio; 7 | 8 | /// 9 | /// This interface represents an audio source from an audio or video element. 10 | /// 11 | /// See the API definition here. 12 | [IJSWrapperConverter] 13 | public class MediaElementAudioSourceNode : AudioNode, IJSCreatable 14 | { 15 | /// 16 | public static new Task CreateAsync(IJSRuntime jSRuntime, IJSObjectReference jSReference) 17 | { 18 | return CreateAsync(jSRuntime, jSReference, new()); 19 | } 20 | 21 | /// 22 | public static new Task CreateAsync(IJSRuntime jSRuntime, IJSObjectReference jSReference, CreationOptions options) 23 | { 24 | return Task.FromResult(new MediaElementAudioSourceNode(jSRuntime, jSReference, options)); 25 | } 26 | 27 | /// 28 | /// Creates an using the standard constructor. 29 | /// 30 | /// An instance. 31 | /// The this new will be associated with. 32 | /// Initial parameter value for this . 33 | /// A new instance of a . 34 | public static async Task CreateAsync(IJSRuntime jSRuntime, AudioContext context, MediaElementAudioSourceOptions options) 35 | { 36 | IJSObjectReference helper = await jSRuntime.GetHelperAsync(); 37 | IJSObjectReference jSInstance = await helper.InvokeAsync("constructMediaElementAudioSourceNode", context, options); 38 | return new MediaElementAudioSourceNode(jSRuntime, jSInstance, new() { DisposesJSReference = true }); 39 | } 40 | 41 | /// 42 | protected MediaElementAudioSourceNode(IJSRuntime jSRuntime, IJSObjectReference jSReference, CreationOptions options) : base(jSRuntime, jSReference, options) 43 | { 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.WebAudio/AudioNodes/MediaStreamAudioSourceNode.cs: -------------------------------------------------------------------------------- 1 | using KristofferStrube.Blazor.MediaCaptureStreams; 2 | using KristofferStrube.Blazor.WebAudio.Extensions; 3 | using KristofferStrube.Blazor.WebIDL; 4 | using Microsoft.JSInterop; 5 | 6 | namespace KristofferStrube.Blazor.WebAudio; 7 | 8 | /// 9 | /// This interface represents an audio source from a . 10 | /// 11 | /// See the API definition here. 12 | [IJSWrapperConverter] 13 | public class MediaStreamAudioSourceNode : AudioNode, IJSCreatable 14 | { 15 | /// 16 | public static new async Task CreateAsync(IJSRuntime jSRuntime, IJSObjectReference jSReference) 17 | { 18 | return await CreateAsync(jSRuntime, jSReference, new()); 19 | } 20 | 21 | /// 22 | public static new Task CreateAsync(IJSRuntime jSRuntime, IJSObjectReference jSReference, CreationOptions options) 23 | { 24 | return Task.FromResult(new MediaStreamAudioSourceNode(jSRuntime, jSReference, options)); 25 | } 26 | 27 | /// 28 | /// Creates a using the standard constructor. 29 | /// 30 | /// An instance. 31 | /// The this new will be associated with. 32 | /// Initial parameter value for this . 33 | /// A new instance of a . 34 | public static async Task CreateAsync(IJSRuntime jSRuntime, AudioContext context, MediaStreamAudioSourceOptions options) 35 | { 36 | IJSObjectReference helper = await jSRuntime.GetHelperAsync(); 37 | IJSObjectReference jSInstance = await helper.InvokeAsync("constructMediaStreamAudioSourceNode", context, options); 38 | return new MediaStreamAudioSourceNode(jSRuntime, jSInstance, new() { DisposesJSReference = true }); 39 | } 40 | 41 | /// 42 | protected MediaStreamAudioSourceNode(IJSRuntime jSRuntime, IJSObjectReference jSReference, CreationOptions options) : base(jSRuntime, jSReference, options) { } 43 | 44 | /// 45 | /// Gets the used when constructing this 46 | /// 47 | /// 48 | public async Task GetMediaStreamAsync() 49 | { 50 | IJSObjectReference helper = await webAudioHelperTask.Value; 51 | IJSObjectReference jSInstance = await helper.InvokeAsync("getAttribute", JSReference, "mediaStream"); 52 | return await MediaStream.CreateAsync(JSRuntime, jSInstance, new() { DisposesJSReference = true }); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.WebAudio/AudioNodes/MediaStreamTrackAudioSourceNode.cs: -------------------------------------------------------------------------------- 1 | using KristofferStrube.Blazor.MediaCaptureStreams; 2 | using KristofferStrube.Blazor.WebAudio.Extensions; 3 | using KristofferStrube.Blazor.WebIDL; 4 | using Microsoft.JSInterop; 5 | 6 | namespace KristofferStrube.Blazor.WebAudio; 7 | 8 | /// 9 | /// This interface represents an audio source from a . 10 | /// 11 | /// See the API definition here. 12 | [IJSWrapperConverter] 13 | public class MediaStreamTrackAudioSourceNode : AudioNode, IJSCreatable 14 | { 15 | /// 16 | public static new async Task CreateAsync(IJSRuntime jSRuntime, IJSObjectReference jSReference) 17 | { 18 | return await CreateAsync(jSRuntime, jSReference, new()); 19 | } 20 | 21 | /// 22 | public static new Task CreateAsync(IJSRuntime jSRuntime, IJSObjectReference jSReference, CreationOptions options) 23 | { 24 | return Task.FromResult(new MediaStreamTrackAudioSourceNode(jSRuntime, jSReference, options)); 25 | } 26 | 27 | /// 28 | /// Creates a using the standard constructor. 29 | /// 30 | /// An instance. 31 | /// The this new will be associated with. 32 | /// Initial parameter value for this . 33 | /// A new instance of a . 34 | public static async Task CreateAsync(IJSRuntime jSRuntime, AudioContext context, MediaStreamTrackAudioSourceOptions options) 35 | { 36 | IJSObjectReference helper = await jSRuntime.GetHelperAsync(); 37 | IJSObjectReference jSInstance = await helper.InvokeAsync("constructMediaStreamTrackAudioSourceNode", context, options); 38 | return new MediaStreamTrackAudioSourceNode(jSRuntime, jSInstance, new() { DisposesJSReference = true }); 39 | } 40 | 41 | /// 42 | protected MediaStreamTrackAudioSourceNode(IJSRuntime jSRuntime, IJSObjectReference jSReference, CreationOptions options) : base(jSRuntime, jSReference, options) { } 43 | } 44 | -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.WebAudio/AudioNodes/WaveShaperNode.cs: -------------------------------------------------------------------------------- 1 | using KristofferStrube.Blazor.WebAudio.Extensions; 2 | using KristofferStrube.Blazor.WebIDL; 3 | using Microsoft.JSInterop; 4 | 5 | namespace KristofferStrube.Blazor.WebAudio; 6 | 7 | /// 8 | /// is an processor implementing non-linear distortion effects.
9 | /// Non-linear waveshaping distortion is commonly used for both subtle non-linear warming, or more obvious distortion effects. 10 | /// Arbitrary non-linear shaping curves may be specified. 11 | /// The number of channels of the output always equals the number of channels of the input. 12 | ///
13 | /// See the API definition here. 14 | [IJSWrapperConverter] 15 | public class WaveShaperNode : AudioNode, IJSCreatable 16 | { 17 | /// 18 | public static new async Task CreateAsync(IJSRuntime jSRuntime, IJSObjectReference jSReference) 19 | { 20 | return await CreateAsync(jSRuntime, jSReference, new()); 21 | } 22 | 23 | /// 24 | public static new Task CreateAsync(IJSRuntime jSRuntime, IJSObjectReference jSReference, CreationOptions options) 25 | { 26 | return Task.FromResult(new WaveShaperNode(jSRuntime, jSReference, options)); 27 | } 28 | 29 | /// 30 | /// Creates an using the standard constructor. 31 | /// 32 | /// An instance. 33 | /// The this new will be associated with. 34 | /// Optional initial parameter value for this . 35 | /// A new instance of a . 36 | public static async Task CreateAsync(IJSRuntime jSRuntime, BaseAudioContext context, WaveShaperOptions? options = null) 37 | { 38 | IJSObjectReference helper = await jSRuntime.GetHelperAsync(); 39 | IJSObjectReference jSInstance = await helper.InvokeAsync("constructWaveShaperNode", context, options); 40 | return new WaveShaperNode(jSRuntime, jSInstance, new() { DisposesJSReference = true }); 41 | } 42 | 43 | /// 44 | protected WaveShaperNode(IJSRuntime jSRuntime, IJSObjectReference jSReference, CreationOptions options) : base(jSRuntime, jSReference, options) 45 | { 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.WebAudio/AudioTimestamp.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace KristofferStrube.Blazor.WebAudio; 4 | 5 | /// 6 | /// Holds the current time with regards to the related () 7 | /// and the current time with regards to the Performance interface. 8 | /// 9 | /// See the API definition here. 10 | public class AudioTimestamp 11 | { 12 | /// 13 | /// Represents a point in the time coordinate system of ’s currentTime. 14 | /// 15 | [JsonPropertyName("contextTime")] 16 | public double ContextTime { get; set; } 17 | 18 | /// 19 | /// Represents a point in the time coordinate system of a Performance interface implementation (described in the High Resolution Time standard). 20 | /// 21 | [JsonPropertyName("performanceTime")] 22 | public double PerformanceTime { get; set; } 23 | } 24 | -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.WebAudio/AudioWorklet/AudioParamDescriptor.cs: -------------------------------------------------------------------------------- 1 | namespace KristofferStrube.Blazor.WebAudio; 2 | 3 | /// 4 | /// This specifies the properties for an object that is used in an . 5 | /// 6 | /// See the API definition here. 7 | public class AudioParamDescriptor 8 | { 9 | } 10 | -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.WebAudio/AudioWorklet/AudioParamMap.cs: -------------------------------------------------------------------------------- 1 | using KristofferStrube.Blazor.WebIDL; 2 | using Microsoft.JSInterop; 3 | 4 | namespace KristofferStrube.Blazor.WebAudio; 5 | 6 | /// 7 | /// A collection of objects with associated names. 8 | /// This maplike object is populated from a list of s in the class constructor at the instantiation. 9 | /// 10 | /// See the API definition here. 11 | public class AudioParamMap : BaseJSWrapper, IJSCreatable 12 | { 13 | /// 14 | public static async Task CreateAsync(IJSRuntime jSRuntime, IJSObjectReference jSReference) 15 | { 16 | return await CreateAsync(jSRuntime, jSReference, new()); 17 | } 18 | 19 | /// 20 | public static Task CreateAsync(IJSRuntime jSRuntime, IJSObjectReference jSReference, CreationOptions options) 21 | { 22 | return Task.FromResult(new AudioParamMap(jSRuntime, jSReference, options)); 23 | } 24 | 25 | /// 26 | protected AudioParamMap(IJSRuntime jSRuntime, IJSObjectReference jSReference, CreationOptions options) : base(jSRuntime, jSReference, options) 27 | { 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.WebAudio/AudioWorklet/AudioWorklet.cs: -------------------------------------------------------------------------------- 1 | using KristofferStrube.Blazor.WebIDL; 2 | using Microsoft.JSInterop; 3 | 4 | namespace KristofferStrube.Blazor.WebAudio; 5 | 6 | /// 7 | /// The object allows developers to supply scripts (such as JavaScript or WebAssembly code) to process audio on the rendering thread, supporting custom s. 8 | /// This processing mechanism ensures synchronous execution of the script code with other built-in s in the audio graph. 9 | /// 10 | /// See the API definition here. 11 | public class AudioWorklet : Worklet, IJSCreatable 12 | { 13 | /// 14 | public static new async Task CreateAsync(IJSRuntime jSRuntime, IJSObjectReference jSReference) 15 | { 16 | return await CreateAsync(jSRuntime, jSReference, new()); 17 | } 18 | 19 | /// 20 | public static new Task CreateAsync(IJSRuntime jSRuntime, IJSObjectReference jSReference, CreationOptions options) 21 | { 22 | return Task.FromResult(new AudioWorklet(jSRuntime, jSReference, options)); 23 | } 24 | 25 | /// 26 | protected AudioWorklet(IJSRuntime jSRuntime, IJSObjectReference jSReference, CreationOptions options) : base(jSRuntime, jSReference, options) 27 | { 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.WebAudio/AudioWorklet/AudioWorkletGlobalScope.cs: -------------------------------------------------------------------------------- 1 | namespace KristofferStrube.Blazor.WebAudio; 2 | 3 | /// 4 | /// This special execution context is designed to enable the generation, processing, and analysis of audio data directly using a script in the audio rendering thread. 5 | /// The user-supplied script code is evaluated in this scope to define one or more subclasses, which in turn are used to instantiate s, in a 1:1 association with s in the main scope.
6 | /// Exactly one exists for each that contains one or more s. 7 | ///
8 | /// See the API definition here. 9 | public class AudioWorkletGlobalScope 10 | { 11 | } 12 | -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.WebAudio/AudioWorklet/AudioWorkletProcessors/AudioWorkletProcessor.cs: -------------------------------------------------------------------------------- 1 | namespace KristofferStrube.Blazor.WebAudio; 2 | 3 | /// 4 | /// This interface represents an audio processing code that runs on the audio rendering thread. 5 | /// It lives in the , and the definition of the class manifests the actual audio processing. 6 | /// 7 | /// See the API definition here. 8 | public abstract class AudioWorkletProcessor 9 | { 10 | /// 11 | /// Gets the that is registered from this . 12 | /// 13 | public abstract AudioWorkletNode Node { get; } 14 | } 15 | -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.WebAudio/AudioWorklet/AudioWorkletProcessors/PullAudioWorkletProcessor.Options.cs: -------------------------------------------------------------------------------- 1 | namespace KristofferStrube.Blazor.WebAudio; 2 | 3 | public partial class PullAudioWorkletProcessor 4 | { 5 | /// 6 | /// Options for creating . 7 | /// 8 | public class Options 9 | { 10 | /// 11 | /// Low tide for when the processor should request more chunks. 12 | /// 13 | public int LowTide { get; set; } = 10; 14 | 15 | /// 16 | /// High tide for when the processor should begin discarding chunks. 17 | /// 18 | public int HighTide { get; set; } = 50; 19 | 20 | /// 21 | /// Size of each individual chunk. 22 | /// 23 | public int BufferRequestSize { get; set; } = 10; 24 | 25 | /// 26 | /// Resolution used for transfering chunks. 27 | /// 28 | public Resolution Resolution { get; set; } = Resolution.Double; 29 | 30 | /// 31 | /// A functions that will be used to pull data to play in the audio processor as mono audio 32 | /// 33 | /// 34 | /// If is supplied then this will be ignored. 35 | /// 36 | public Func? ProduceMono { get; set; } 37 | 38 | /// 39 | /// A functions that will be used to pull data to play in the audio processor as stereo audio. 40 | /// 41 | public Func<(double left, double right)>? ProduceStereo { get; set; } 42 | } 43 | 44 | /// 45 | /// The resolution options for how the quality of the sound of the . 46 | /// 47 | public enum Resolution 48 | { 49 | /// 50 | /// This is the full resolution from the producing methods. 51 | /// 52 | Double, 53 | 54 | /// 55 | /// This down-grades the produced sound to 255 discrete values to make the datatransfer more compact. 56 | /// 57 | Byte, 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.WebAudio/AudioWorklet/MessagePort.cs: -------------------------------------------------------------------------------- 1 | using KristofferStrube.Blazor.DOM; 2 | using KristofferStrube.Blazor.WebIDL; 3 | using Microsoft.JSInterop; 4 | 5 | namespace KristofferStrube.Blazor.WebAudio; 6 | 7 | /// 8 | /// Each object can be entangled with another (a symmetric relationship). 9 | /// Each object also has a task source called the port message queue, initially empty. 10 | /// A port message queue can be enabled or disabled, and is initially disabled. 11 | /// Once enabled, a port can never be disabled again (though messages in the queue can get moved to another queue or removed altogether, which has much the same effect). 12 | /// 13 | /// See the API definition here. 14 | [IJSWrapperConverter] 15 | public class MessagePort : EventTarget, IJSCreatable 16 | { 17 | /// 18 | public static new async Task CreateAsync(IJSRuntime jSRuntime, IJSObjectReference jSReference) 19 | { 20 | return await CreateAsync(jSRuntime, jSReference, new()); 21 | } 22 | 23 | /// 24 | public static new Task CreateAsync(IJSRuntime jSRuntime, IJSObjectReference jSReference, CreationOptions options) 25 | { 26 | return Task.FromResult(new MessagePort(jSRuntime, jSReference, options)); 27 | } 28 | 29 | /// 30 | protected internal MessagePort(IJSRuntime jSRuntime, IJSObjectReference jSReference, CreationOptions options) : base(jSRuntime, jSReference, options) 31 | { 32 | } 33 | 34 | public async Task PostMessageAsync(object message, ITransferable[]? transfer = null) 35 | { 36 | await JSReference.InvokeVoidAsync("postMessage", message, transfer?.Select(e => e).ToArray()); 37 | } 38 | 39 | public async Task StartAsync() 40 | { 41 | await JSReference.InvokeVoidAsync("start"); 42 | } 43 | 44 | public async Task CloseAsync() 45 | { 46 | await JSReference.InvokeVoidAsync("close"); 47 | } 48 | 49 | /// 50 | public async Task AddOnMessageEventListenerAsync(EventListener callback, AddEventListenerOptions? options = null) 51 | { 52 | await AddEventListenerAsync("message", callback, options); 53 | } 54 | 55 | /// 56 | public async Task RemoveOnMessageEventListenerAsync(EventListener callback, EventListenerOptions? options = null) 57 | { 58 | await RemoveEventListenerAsync("message", callback, options); 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.WebAudio/AudioWorklet/RequestCredentials.cs: -------------------------------------------------------------------------------- 1 | using KristofferStrube.Blazor.WebAudio.Converters; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace KristofferStrube.Blazor.WebAudio; 5 | 6 | /// 7 | /// Controls the flow of credentials during a fetch. 8 | /// 9 | /// See the API definition here. 10 | [JsonConverter(typeof(RequestCredentialsConverter))] 11 | public enum RequestCredentials 12 | { 13 | /// 14 | /// Excludes credentials from this request, and causes any credentials sent back in the response to be ignored. 15 | /// 16 | Omit, 17 | 18 | /// 19 | /// Include credentials with requests made to same-origin URLs, and use any credentials sent back in responses from same-origin URLs. 20 | /// 21 | SameOrigin, 22 | 23 | /// 24 | /// Always includes credentials with this request, and always use any credentials sent back in the response. 25 | /// 26 | Include 27 | } 28 | -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.WebAudio/AudioWorklet/Worklet.cs: -------------------------------------------------------------------------------- 1 | using KristofferStrube.Blazor.WebIDL; 2 | using KristofferStrube.Blazor.WebIDL.Exceptions; 3 | using Microsoft.JSInterop; 4 | 5 | namespace KristofferStrube.Blazor.WebAudio; 6 | 7 | /// 8 | /// The class provides the capability to add module scripts into its associated WorkletGlobalScopes. 9 | /// The user agent can then create classes registered on the WorkletGlobalScopes and invoke their methods. 10 | /// 11 | /// See the API definition here. 12 | [IJSWrapperConverter] 13 | public class Worklet : BaseJSWrapper, IJSCreatable 14 | { 15 | /// 16 | /// An error handling reference to the underlying js object. 17 | /// 18 | protected IJSObjectReference ErrorHandlingJSReference { get; set; } 19 | 20 | /// 21 | public static async Task CreateAsync(IJSRuntime jSRuntime, IJSObjectReference jSReference) 22 | { 23 | return await CreateAsync(jSRuntime, jSReference, new()); 24 | } 25 | 26 | /// 27 | public static Task CreateAsync(IJSRuntime jSRuntime, IJSObjectReference jSReference, CreationOptions options) 28 | { 29 | return Task.FromResult(new Worklet(jSRuntime, jSReference, options)); 30 | } 31 | 32 | /// 33 | protected Worklet(IJSRuntime jSRuntime, IJSObjectReference jSReference, CreationOptions options) : base(jSRuntime, jSReference, options) 34 | { 35 | ErrorHandlingJSReference = new ErrorHandlingJSObjectReference(jSRuntime, jSReference); 36 | } 37 | 38 | /// 39 | /// Loads and executes the module script given by into all of worklet's global scopes. 40 | /// It can also create additional global scopes as part of this process, depending on the worklet type. 41 | /// The returned promise will fulfill once the script has been successfully loaded and run in all global scopes. 42 | /// 43 | /// 44 | /// Throws an if it fails to fetch the script.
45 | /// Throws the exception thrown within the module itself if it fails. 46 | ///
47 | /// 48 | /// 49 | /// 50 | public async Task AddModuleAsync(string moduleURL, WorkletOptions? options = null) 51 | { 52 | await ErrorHandlingJSReference.InvokeVoidAsync("addModule", moduleURL, options); 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.WebAudio/AudioWorklet/WorkletOptions.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace KristofferStrube.Blazor.WebAudio; 4 | 5 | /// 6 | /// The options used to add a module via 7 | /// 8 | /// See the API definition here. 9 | public class WorkletOptions 10 | { 11 | /// 12 | /// Can be set to a credentials mode to modify the script-fetching process. 13 | /// It defaults to . 14 | /// 15 | [JsonPropertyName("credentials")] 16 | public RequestCredentials Credentials { get; set; } = RequestCredentials.SameOrigin; 17 | } 18 | -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.WebAudio/BaseJSWrapper.cs: -------------------------------------------------------------------------------- 1 | using KristofferStrube.Blazor.WebAudio.Extensions; 2 | using KristofferStrube.Blazor.WebIDL; 3 | using Microsoft.JSInterop; 4 | 5 | namespace KristofferStrube.Blazor.WebAudio; 6 | 7 | /// 8 | /// Base class for wrapping objects in the Blazor.WebAudio library. 9 | /// 10 | [IJSWrapperConverter] 11 | public abstract class BaseJSWrapper : IJSWrapper, IAsyncDisposable 12 | { 13 | /// 14 | /// A lazily evaluated task that gives access to helper methods. 15 | /// 16 | protected readonly Lazy> helperTask; 17 | 18 | /// 19 | public IJSRuntime JSRuntime { get; } 20 | 21 | /// 22 | public IJSObjectReference JSReference { get; } 23 | 24 | /// 25 | public bool DisposesJSReference { get; } 26 | 27 | /// 28 | internal BaseJSWrapper(IJSRuntime jSRuntime, IJSObjectReference jSReference, CreationOptions options) 29 | { 30 | helperTask = new(jSRuntime.GetHelperAsync); 31 | JSReference = jSReference; 32 | JSRuntime = jSRuntime; 33 | DisposesJSReference = options.DisposesJSReference; 34 | } 35 | 36 | /// 37 | /// Disposes the underlying js object reference. 38 | /// 39 | /// 40 | public async ValueTask DisposeAsync() 41 | { 42 | if (helperTask.IsValueCreated) 43 | { 44 | IJSObjectReference module = await helperTask.Value; 45 | await module.DisposeAsync(); 46 | } 47 | await IJSWrapper.DisposeJSReference(this); 48 | GC.SuppressFinalize(this); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.WebAudio/Converters/AutomationRateConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace KristofferStrube.Blazor.WebAudio.Converters; 5 | 6 | internal class AutomationRateConverter : JsonConverter 7 | { 8 | public override AutomationRate Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 9 | { 10 | throw new NotImplementedException(); 11 | } 12 | 13 | public override void Write(Utf8JsonWriter writer, AutomationRate value, JsonSerializerOptions options) 14 | { 15 | writer.WriteStringValue(value switch 16 | { 17 | AutomationRate.ARate => "a-rate", 18 | AutomationRate.KRate => "k-rate", 19 | _ => throw new ArgumentException($"Value '{value}' was not a valid {nameof(AutomationRate)}.") 20 | }); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.WebAudio/Converters/BiquadFilterTypeConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace KristofferStrube.Blazor.WebAudio.Converters; 5 | 6 | internal class BiquadFilterTypeConverter : JsonConverter 7 | { 8 | public override BiquadFilterType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 9 | { 10 | return reader.GetString() switch 11 | { 12 | "highpass" => BiquadFilterType.Highpass, 13 | "lowpass" => BiquadFilterType.Lowpass, 14 | "bandpass" => BiquadFilterType.Bandpass, 15 | "lowshelf" => BiquadFilterType.Lowshelf, 16 | "highshelf" => BiquadFilterType.Highshelf, 17 | "peaking" => BiquadFilterType.Peaking, 18 | "notch" => BiquadFilterType.Notch, 19 | "allpass" => BiquadFilterType.Allpass, 20 | var value => throw new ArgumentException($"Value '{value}' was not a valid {nameof(BiquadFilterType)}."), 21 | }; 22 | } 23 | 24 | public override void Write(Utf8JsonWriter writer, BiquadFilterType value, JsonSerializerOptions options) 25 | { 26 | writer.WriteStringValue(value switch 27 | { 28 | BiquadFilterType.Highpass => "highpass", 29 | BiquadFilterType.Lowpass => "lowpass", 30 | BiquadFilterType.Bandpass => "bandpass", 31 | BiquadFilterType.Lowshelf => "lowshelf", 32 | BiquadFilterType.Highshelf => "highshelf", 33 | BiquadFilterType.Peaking => "peaking", 34 | BiquadFilterType.Notch => "notch", 35 | BiquadFilterType.Allpass => "allpass", 36 | _ => throw new ArgumentException($"Value '{value}' was not a valid {nameof(BiquadFilterType)}.") 37 | }); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.WebAudio/Converters/ChannelCountModeConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace KristofferStrube.Blazor.WebAudio.Converters; 5 | 6 | internal class ChannelCountModeConverter : JsonConverter 7 | { 8 | public override ChannelCountMode Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 9 | { 10 | return reader.GetString() switch 11 | { 12 | "max" => ChannelCountMode.Max, 13 | "clamped-max" => ChannelCountMode.ClampedMax, 14 | "explicit" => ChannelCountMode.Explicit, 15 | var value => throw new ArgumentException($"Value '{value}' was not a valid {nameof(ChannelCountMode)}.") 16 | }; 17 | } 18 | 19 | public override void Write(Utf8JsonWriter writer, ChannelCountMode value, JsonSerializerOptions options) 20 | { 21 | writer.WriteStringValue(value switch 22 | { 23 | ChannelCountMode.Max => "max", 24 | ChannelCountMode.ClampedMax => "clamped-max", 25 | ChannelCountMode.Explicit => "explicit", 26 | _ => throw new ArgumentException($"Value '{value}' was not a valid {nameof(ChannelCountMode)}.") 27 | }); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.WebAudio/Converters/ChannelInterpretationConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace KristofferStrube.Blazor.WebAudio.Converters; 5 | 6 | internal class ChannelInterpretationConverter : JsonConverter 7 | { 8 | public override ChannelInterpretation Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 9 | { 10 | return reader.GetString() switch 11 | { 12 | "speakers" => ChannelInterpretation.Speakers, 13 | "discrete" => ChannelInterpretation.Discrete, 14 | var value => throw new ArgumentException($"Value '{value}' was not a valid {nameof(ChannelInterpretation)}.") 15 | }; 16 | } 17 | 18 | public override void Write(Utf8JsonWriter writer, ChannelInterpretation value, JsonSerializerOptions options) 19 | { 20 | writer.WriteStringValue(value switch 21 | { 22 | ChannelInterpretation.Speakers => "speakers", 23 | ChannelInterpretation.Discrete => "discrete", 24 | _ => throw new ArgumentException($"Value '{value}' was not a valid {nameof(ChannelInterpretation)}.") 25 | }); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.WebAudio/Converters/DistanceModelTypeConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace KristofferStrube.Blazor.WebAudio.Converters; 5 | 6 | internal class DistanceModelTypeConverter : JsonConverter 7 | { 8 | public override DistanceModelType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 9 | { 10 | return reader.GetString() switch 11 | { 12 | "linear" => DistanceModelType.Linear, 13 | "inverse" => DistanceModelType.Inverse, 14 | "exponential" => DistanceModelType.Exponential, 15 | var value => throw new ArgumentException($"Value '{value}' was not a valid {nameof(DistanceModelType)}.") 16 | }; 17 | } 18 | 19 | public override void Write(Utf8JsonWriter writer, DistanceModelType value, JsonSerializerOptions options) 20 | { 21 | writer.WriteStringValue(value switch 22 | { 23 | DistanceModelType.Linear => "linear", 24 | DistanceModelType.Inverse => "inverse", 25 | DistanceModelType.Exponential => "exponential", 26 | _ => throw new ArgumentException($"Value '{value}' was not a valid {nameof(DistanceModelType)}.") 27 | }); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.WebAudio/Converters/OscillatorTypeConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace KristofferStrube.Blazor.WebAudio.Converters; 5 | 6 | internal class OscillatorTypeConverter : JsonConverter 7 | { 8 | public override OscillatorType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 9 | { 10 | return reader.GetString() switch 11 | { 12 | "sine" => OscillatorType.Sine, 13 | "square" => OscillatorType.Square, 14 | "sawtooth" => OscillatorType.Sawtooth, 15 | "triangle" => OscillatorType.Triangle, 16 | "custom" => OscillatorType.Custom, 17 | var value => throw new ArgumentException($"Value '{value}' was not a valid {nameof(OscillatorType)}."), 18 | }; 19 | } 20 | 21 | public override void Write(Utf8JsonWriter writer, OscillatorType value, JsonSerializerOptions options) 22 | { 23 | writer.WriteStringValue(value switch 24 | { 25 | OscillatorType.Sine => "sine", 26 | OscillatorType.Square => "square", 27 | OscillatorType.Sawtooth => "sawtooth", 28 | OscillatorType.Triangle => "triangle", 29 | OscillatorType.Custom => "custom", 30 | _ => throw new ArgumentException($"Value '{value}' was not a valid {nameof(OscillatorType)}.") 31 | }); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.WebAudio/Converters/OverSampleTypeConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace KristofferStrube.Blazor.WebAudio.Converters; 5 | 6 | internal class OverSampleTypeConverter : JsonConverter 7 | { 8 | public override OverSampleType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 9 | { 10 | return reader.GetString() switch 11 | { 12 | "none" => OverSampleType.None, 13 | "2x" => OverSampleType.TwoX, 14 | "4x" => OverSampleType.FourX, 15 | var value => throw new ArgumentException($"Value '{value}' was not a valid {nameof(OverSampleType)}.") 16 | }; 17 | } 18 | 19 | public override void Write(Utf8JsonWriter writer, OverSampleType value, JsonSerializerOptions options) 20 | { 21 | writer.WriteStringValue(value switch 22 | { 23 | OverSampleType.None => "none", 24 | OverSampleType.TwoX => "2x", 25 | OverSampleType.FourX => "4x", 26 | _ => throw new ArgumentException($"Value '{value}' was not a valid {nameof(OverSampleType)}.") 27 | }); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.WebAudio/Converters/PanningModelTypeConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace KristofferStrube.Blazor.WebAudio.Converters; 5 | 6 | internal class PanningModelTypeConverter : JsonConverter 7 | { 8 | public override PanningModelType Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 9 | { 10 | return reader.GetString() switch 11 | { 12 | "equalpower" => PanningModelType.EqualPower, 13 | "HRTF" => PanningModelType.HRTF, 14 | var value => throw new ArgumentException($"Value '{value}' was not a valid {nameof(PanningModelType)}.") 15 | }; 16 | } 17 | 18 | public override void Write(Utf8JsonWriter writer, PanningModelType value, JsonSerializerOptions options) 19 | { 20 | writer.WriteStringValue(value switch 21 | { 22 | PanningModelType.EqualPower => "equalpower", 23 | PanningModelType.HRTF => "HRTF", 24 | _ => throw new ArgumentException($"Value '{value}' was not a valid {nameof(PanningModelType)}.") 25 | }); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.WebAudio/Converters/RequestCredentialsConverter.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace KristofferStrube.Blazor.WebAudio.Converters; 5 | 6 | internal class RequestCredentialsConverter : JsonConverter 7 | { 8 | public override RequestCredentials Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 9 | { 10 | throw new NotImplementedException(); 11 | } 12 | 13 | public override void Write(Utf8JsonWriter writer, RequestCredentials value, JsonSerializerOptions options) 14 | { 15 | writer.WriteStringValue(value switch 16 | { 17 | RequestCredentials.Omit => "omit", 18 | RequestCredentials.SameOrigin => "same-origin", 19 | RequestCredentials.Include => "include", 20 | _ => throw new ArgumentException($"Value '{value}' was not a valid {nameof(RequestCredentials)}.") 21 | }); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.WebAudio/Converters/UnionTypeJsonConverter.cs: -------------------------------------------------------------------------------- 1 | using KristofferStrube.Blazor.WebAudio.UnionTypes; 2 | using System.Text.Json; 3 | using System.Text.Json.Serialization; 4 | using static System.Text.Json.JsonSerializer; 5 | 6 | namespace KristofferStrube.Blazor.WebAudio.Converters; 7 | 8 | internal class UnionTypeJsonConverter : JsonConverter where T : UnionType 9 | { 10 | public override T? Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) 11 | { 12 | throw new InvalidOperationException("Can't deserialize UnionTypes from the Blazor.WebAudio library."); 13 | } 14 | 15 | public override void Write(Utf8JsonWriter writer, T value, JsonSerializerOptions options) 16 | { 17 | if (value.Value is not null) 18 | { 19 | writer.WriteRawValue(Serialize(value.Value, options)); 20 | } 21 | } 22 | } -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.WebAudio/Events/OfflineAudioCompletionEventInit.cs: -------------------------------------------------------------------------------- 1 | using KristofferStrube.Blazor.DOM; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace KristofferStrube.Blazor.WebAudio.Events; 5 | 6 | /// 7 | /// Initialisation options for an . 8 | /// 9 | /// See the API definition here. 10 | public class OfflineAudioCompletionEventInit : EventInit 11 | { 12 | /// 13 | /// Value to be assigned to the of the event. 14 | /// 15 | [JsonPropertyName("renderedBuffer")] 16 | public required AudioBuffer RenderedBuffer { get; set; } 17 | } 18 | -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.WebAudio/Extensions/IJSRuntimeExtensions.cs: -------------------------------------------------------------------------------- 1 | using KristofferStrube.Blazor.WebIDL; 2 | using Microsoft.JSInterop; 3 | 4 | namespace KristofferStrube.Blazor.WebAudio.Extensions; 5 | 6 | internal static class IJSRuntimeExtensions 7 | { 8 | internal static async Task GetHelperAsync(this IJSRuntime jSRuntime) 9 | { 10 | return await jSRuntime.InvokeAsync( 11 | "import", "./_content/KristofferStrube.Blazor.WebAudio/KristofferStrube.Blazor.WebAudio.js"); 12 | } 13 | internal static async Task GetInProcessHelperAsync(this IJSRuntime jSRuntime) 14 | { 15 | return await jSRuntime.InvokeAsync( 16 | "import", "./_content/KristofferStrube.Blazor.WebAudio/KristofferStrube.Blazor.WebAudio.js"); 17 | } 18 | internal static async Task GetErrorHandlingHelperAsync(this IJSRuntime jSRuntime) 19 | { 20 | IJSObjectReference helper = await jSRuntime.GetHelperAsync(); 21 | return new ErrorHandlingJSObjectReference(jSRuntime, helper); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.WebAudio/KristofferStrube.Blazor.WebAudio.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net7.0;net8.0 5 | enable 6 | enable 7 | Blazor Web Audio API wrapper 8 | Web Audio API wrapper implementation for Blazor. 9 | KristofferStrube.Blazor.WebAudio 10 | Blazor;Wasm;Wrapper;Audio;AudioNode;AudioContext;AudioBufffer;Media;Music;Sound;JSInterop 11 | https://github.com/KristofferStrube/Blazor.WebAudio 12 | git 13 | MIT 14 | 0.1.0-alpha.13 15 | Kristoffer Strube 16 | README.md 17 | icon.png 18 | true 19 | 20 | 21 | 22 | 23 | True 24 | \ 25 | 26 | 27 | True 28 | \ 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.WebAudio/Options/AnalyserOptions.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace KristofferStrube.Blazor.WebAudio; 4 | 5 | /// 6 | /// This specifies the options to be used when constructing an . 7 | /// 8 | /// See the API definition here. 9 | public class AnalyserOptions : AudioNodeOptions 10 | { 11 | /// 12 | /// The desired initial size of the FFT for frequency-domain analysis. 13 | /// 14 | [JsonPropertyName("fftSize")] 15 | public ulong FftSize { get; set; } = 2048; 16 | 17 | /// 18 | /// The desired initial maximum power in dB for FFT analysis. 19 | /// 20 | [JsonPropertyName("maxDecibels")] 21 | public double MaxDecibels { get; set; } = -30; 22 | 23 | /// 24 | /// The desired initial minimum power in dB for FFT analysis. 25 | /// 26 | [JsonPropertyName("minDecibels")] 27 | public double MinDecibels { get; set; } = -100; 28 | 29 | /// 30 | /// The desired initial smoothing constant for the FFT analysis. 31 | /// 32 | [JsonPropertyName("smoothingTimeConstant")] 33 | public double SmoothingTimeConstant { get; set; } = 0.8; 34 | } 35 | -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.WebAudio/Options/AudioBufferOptions.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace KristofferStrube.Blazor.WebAudio; 4 | 5 | /// 6 | /// This specifies the options to use in constructing an . 7 | /// 8 | /// See the API definition here. 9 | public class AudioBufferOptions 10 | { 11 | /// 12 | /// The number of channels for the buffer. Browsers will support at least 32 channels. 13 | /// 14 | [JsonPropertyName("numberOfChannels")] 15 | public ulong NumberOfChannels { get; set; } = 1; 16 | 17 | /// 18 | /// The length in sample frames of the buffer. Must be at least 1. 19 | /// 20 | [JsonPropertyName("length")] 21 | public required ulong Length { get; set; } 22 | 23 | /// 24 | /// The sample rate in Hz for the buffer. 25 | /// The range is at least from 8000 to 96000 but browsers can support broader ranges. 26 | /// 27 | [JsonPropertyName("sampleRate")] 28 | public required float SampleRate { get; set; } 29 | } -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.WebAudio/Options/AudioBufferSourceOptions.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace KristofferStrube.Blazor.WebAudio; 4 | 5 | /// 6 | /// This specifies options for constructing an . 7 | /// 8 | /// See the API definition here. 9 | public class AudioBufferSourceOptions 10 | { 11 | /// 12 | /// The audio asset to be played. This is equivalent to calling . 13 | /// 14 | [JsonPropertyName("buffer")] 15 | public AudioBuffer? Buffer { get; set; } 16 | 17 | /// 18 | /// The initial value for . 19 | /// 20 | [JsonPropertyName("detune")] 21 | public float Detune { get; set; } = 0f; 22 | 23 | /// 24 | /// The initial value for 25 | /// 26 | [JsonPropertyName("loop")] 27 | public bool Loop { get; set; } = false; 28 | 29 | /// 30 | /// The initial value for 31 | /// 32 | [JsonPropertyName("loopEnd")] 33 | public double LoopEnd { get; set; } = 0; 34 | 35 | /// 36 | /// The initial value for 37 | /// 38 | [JsonPropertyName("loopStart")] 39 | public double LoopStart { get; set; } = 0; 40 | 41 | /// 42 | /// The initial value for 43 | /// 44 | /// 45 | /// Default is 1. 46 | /// 47 | [JsonPropertyName("playbackRate")] 48 | public float PlaybackRate { get; set; } = 1; 49 | } 50 | -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.WebAudio/Options/AudioContextLatencyCategory.cs: -------------------------------------------------------------------------------- 1 | namespace KristofferStrube.Blazor.WebAudio; 2 | 3 | /// 4 | /// An enum for specifying the strategy that the should use for balancing audio output latency and power consumption. 5 | /// 6 | /// See the API definition here. 7 | public enum AudioContextLatencyCategory 8 | { 9 | /// 10 | /// Provide the lowest audio output latency possible without glitching. This is the default. 11 | /// 12 | Interactive, 13 | /// 14 | /// Balance audio output latency and power consumption. 15 | /// 16 | Balanced, 17 | /// 18 | /// Prioritize sustained playback without interruption over audio output latency. Lowest power consumption. 19 | /// 20 | Playback, 21 | } 22 | 23 | internal static class AudioContextLatencyCategoryExtensions 24 | { 25 | public static string AsString(this AudioContextLatencyCategory type) 26 | { 27 | return type switch 28 | { 29 | AudioContextLatencyCategory.Balanced => "balanced", 30 | AudioContextLatencyCategory.Interactive => "interactive", 31 | AudioContextLatencyCategory.Playback => "playback", 32 | _ => throw new ArgumentException($"Value '{type}' was not a valid {nameof(AudioContextLatencyCategory)}.") 33 | }; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.WebAudio/Options/AudioContextOptions.cs: -------------------------------------------------------------------------------- 1 | using KristofferStrube.Blazor.WebIDL.Exceptions; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace KristofferStrube.Blazor.WebAudio; 5 | 6 | /// 7 | /// The is used to specify user-specified options for an . 8 | /// 9 | /// See the API definition here. 10 | public class AudioContextOptions 11 | { 12 | /// 13 | /// Identify the type of playback, which affects tradeoffs between audio output latency and power consumption. 14 | /// 15 | /// 16 | /// The preferred value of the is a value from . 17 | /// However, a can also be specified for the number of seconds of latency for finer control to balance latency and power consumption. 18 | /// It is at the browser’s discretion to interpret the number appropriately. 19 | /// The actual latency used is given by . 20 | /// 21 | [JsonPropertyName("latencyHint")] 22 | public AudioContextLatencyCategoryOrDouble LatencyHint { get; set; } = AudioContextLatencyCategory.Interactive; 23 | 24 | /// 25 | /// Sets the to this value for the that will be created. 26 | /// The supported values are the same as the sample rates for an (at least 8000 to 96000). 27 | /// 28 | /// 29 | /// A exception will be thrown if the specified sample rate is not supported.
30 | /// If sampleRate is not specified, the preferred sample rate of the output device for this is used. 31 | ///
32 | /// 33 | [JsonPropertyName("sampleRate")] 34 | public float SampleRate { get; set; } 35 | } 36 | -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.WebAudio/Options/AudioNodeOptions.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace KristofferStrube.Blazor.WebAudio; 4 | 5 | /// 6 | /// This specifies the options that can be used in constructing all s. 7 | /// All members are optional. However, the specific values used for each node depends on the actual node. 8 | /// 9 | /// See the API definition here. 10 | public class AudioNodeOptions 11 | { 12 | /// 13 | /// is the number of channels used when up-mixing and down-mixing connections to any inputs to the node. 14 | /// The default value is 2 except for specific nodes where its value is specially determined. 15 | /// This attribute has no effect for nodes with no inputs. 16 | /// 17 | [JsonPropertyName("channelCount")] 18 | [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] 19 | public ulong? ChannelCount { get; set; } 20 | 21 | /// 22 | /// determines how channels will be counted when up-mixing and down-mixing connections to any inputs to the node. 23 | /// 24 | /// 25 | /// The default value is . This attribute has no effect for nodes with no inputs. 26 | /// 27 | [JsonPropertyName("channelCountMode")] 28 | public virtual ChannelCountMode ChannelCountMode { get; set; } = ChannelCountMode.Max; 29 | 30 | /// 31 | /// determines how individual channels will be treated when up-mixing and down-mixing connections to any inputs to the node. 32 | /// 33 | /// 34 | /// The default value is . This attribute has no effect for nodes with no inputs. 35 | /// 36 | [JsonPropertyName("channelInterpretation")] 37 | public virtual ChannelInterpretation ChannelInterpretation { get; set; } = ChannelInterpretation.Speakers; 38 | } 39 | -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.WebAudio/Options/AudioWorkletNodeOptions.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace KristofferStrube.Blazor.WebAudio; 4 | 5 | /// 6 | /// This specifies the options to be used when constructing an . 7 | /// 8 | /// See the API definition here. 9 | public class AudioWorkletNodeOptions : AudioNodeOptions 10 | { 11 | /// 12 | /// This is used to initialize the value of . 13 | /// 14 | [JsonPropertyName("numberOfInputs")] 15 | public ulong NumberOfInputs { get; set; } = 1; 16 | 17 | /// 18 | /// This is used to initialize the value of . 19 | /// 20 | [JsonPropertyName("numberOfOutputs")] 21 | public ulong NumberOfOutputs { get; set; } = 1; 22 | 23 | /// 24 | /// This array is used to configure the number of channels in each output. 25 | /// 26 | [JsonPropertyName("outputChannelCount")] 27 | [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] 28 | public ulong[]? OutputChannelCount { get; set; } 29 | 30 | /// 31 | /// This is a list of user-defined key-value pairs that are used to set the initial value of an with the matched name in the . 32 | /// 33 | [JsonPropertyName("parameterData")] 34 | [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] 35 | public Dictionary? ParameterData { get; set; } 36 | 37 | /// 38 | /// This holds any user-defined data that may be used to initialize custom properties in an instance that is associated with the . 39 | /// 40 | [JsonPropertyName("processorOptions")] 41 | [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] 42 | public object? ProcessorOptions { get; set; } 43 | } -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.WebAudio/Options/AutomationRate.cs: -------------------------------------------------------------------------------- 1 | using KristofferStrube.Blazor.WebAudio.Converters; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace KristofferStrube.Blazor.WebAudio; 5 | 6 | /// 7 | /// The automation rate of an can be selected calling with one of the following values. 8 | /// However, some s have constraints on whether the automation rate can be changed. 9 | /// 10 | /// See the API definition here. 11 | [JsonConverter(typeof(AutomationRateConverter))] 12 | public enum AutomationRate 13 | { 14 | /// 15 | /// This is set for a-rate processing. 16 | /// 17 | /// 18 | /// a-rate parameters will be sampled for each sample-frame of the block. 19 | /// 20 | ARate, 21 | /// 22 | /// This is set for k-rate processing. 23 | /// 24 | /// 25 | /// k-rate parameter will be sampled at the time of the very first sample-frame, and that value will be used for the entire block. 26 | /// 27 | KRate 28 | } 29 | -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.WebAudio/Options/BiquadFilterOptions.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace KristofferStrube.Blazor.WebAudio; 4 | 5 | /// 6 | /// This specifies the options to be used when constructing a . 7 | /// 8 | /// See the API definition here. 9 | public class BiquadFilterOptions : AudioNodeOptions 10 | { 11 | /// 12 | /// The desired initial type of the filter. 13 | /// 14 | /// 15 | /// The default value is . 16 | /// 17 | [JsonPropertyName("type")] 18 | public BiquadFilterType Type { get; set; } = BiquadFilterType.Lowpass; 19 | 20 | /// 21 | /// The desired initial value for Q. 22 | /// 23 | /// 24 | /// The default value is 1. 25 | /// 26 | [JsonPropertyName("Q")] 27 | public float Q { get; set; } = 1; 28 | 29 | /// 30 | /// The desired initial value for detune. 31 | /// 32 | /// 33 | /// The default value is 0. 34 | /// 35 | [JsonPropertyName("detune")] 36 | public float Detune { get; set; } = 0; 37 | 38 | /// 39 | /// The desired initial value for frequency. 40 | /// 41 | /// 42 | /// The default value is 350. 43 | /// 44 | [JsonPropertyName("frequency")] 45 | public float Frequency { get; set; } = 350; 46 | 47 | /// 48 | /// The desired initial value for gain. 49 | /// 50 | /// 51 | /// The default value is 0. 52 | /// 53 | [JsonPropertyName("gain")] 54 | public float Gain { get; set; } = 0; 55 | } 56 | -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.WebAudio/Options/ChannelCountMode.cs: -------------------------------------------------------------------------------- 1 | using KristofferStrube.Blazor.WebAudio.Converters; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace KristofferStrube.Blazor.WebAudio; 5 | 6 | /// 7 | /// An enum for specifying how channels will be counted when up-mixing and down-mixing connections to any inputs to the node. 8 | /// 9 | /// See the API definition here. 10 | [JsonConverter(typeof(ChannelCountModeConverter))] 11 | public enum ChannelCountMode 12 | { 13 | /// 14 | /// The computed number of channels is the maximum of the number of channels of all connections to an input. In this mode is ignored. 15 | /// 16 | Max, 17 | 18 | /// 19 | /// The computed number of channels is determined as for and then clamped to a maximum value of . 20 | /// 21 | ClampedMax, 22 | 23 | /// 24 | /// The computed number of channels is the exact value as specified by . 25 | /// 26 | Explicit 27 | } -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.WebAudio/Options/ChannelInterpretation.cs: -------------------------------------------------------------------------------- 1 | using KristofferStrube.Blazor.WebAudio.Converters; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace KristofferStrube.Blazor.WebAudio; 5 | 6 | /// 7 | /// An enum for specifying how individual channels will be treated when up-mixing and down-mixing connections to any inputs to the node. 8 | /// 9 | /// See the API definition here. 10 | [JsonConverter(typeof(ChannelInterpretationConverter))] 11 | public enum ChannelInterpretation 12 | { 13 | /// 14 | /// Use up-mix equations or down-mix equations. 15 | /// In cases where the number of channels do not match any of these basic speaker layouts, revert to . 16 | /// 17 | Speakers, 18 | /// 19 | /// Up-mix by filling channels until they run out then zero out remaining channels. 20 | /// Down-mix by filling as many channels as possible, then dropping remaining channels. 21 | /// 22 | Discrete, 23 | } -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.WebAudio/Options/ChannelMergerOptions.cs: -------------------------------------------------------------------------------- 1 | using KristofferStrube.Blazor.WebIDL.Exceptions; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace KristofferStrube.Blazor.WebAudio; 5 | 6 | 7 | /// 8 | /// This specifies the options to use in constructing a . 9 | /// 10 | /// See the API definition here. 11 | public class ChannelMergerOptions : AudioNodeOptions 12 | { 13 | /// 14 | /// The number inputs for the . 15 | /// 16 | /// 17 | /// The default value is 6.
18 | /// It throws an if it is less than 1 or larger than the supported number of channels when used for constructing a . 19 | ///
20 | [JsonPropertyName("numberOfInputs")] 21 | public ulong NumberOfInputs { get; set; } = 6; 22 | 23 | /// 24 | /// 25 | /// The default value is . 26 | /// 27 | public override ChannelCountMode ChannelCountMode { get; set; } = ChannelCountMode.Explicit; 28 | } 29 | -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.WebAudio/Options/ChannelSplitterOptions.cs: -------------------------------------------------------------------------------- 1 | using KristofferStrube.Blazor.WebIDL.Exceptions; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace KristofferStrube.Blazor.WebAudio; 5 | 6 | /// 7 | /// This specifies the options to use in constructing a . 8 | /// 9 | /// See the API definition here. 10 | public class ChannelSplitterOptions : AudioNodeOptions 11 | { 12 | /// 13 | /// 14 | /// 15 | /// 16 | /// The default value is . 17 | /// 18 | [JsonPropertyName("channelCountMode")] 19 | public override ChannelCountMode ChannelCountMode { get; set; } = ChannelCountMode.Explicit; 20 | 21 | /// 22 | /// 23 | /// The default value is . 24 | /// 25 | [JsonPropertyName("channelInterpretation")] 26 | public override ChannelInterpretation ChannelInterpretation { get; set; } = ChannelInterpretation.Discrete; 27 | 28 | /// 29 | /// The number inputs for the . 30 | /// 31 | /// 32 | /// The default value is 6.
33 | /// It throws an if it is less than 1 or larger than the supported number of channels when used for constructing a . 34 | ///
35 | [JsonPropertyName("numberOfInputs")] 36 | public ulong NumberOfInputs { get; set; } = 6; 37 | } 38 | -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.WebAudio/Options/ConstantSourceOptions.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace KristofferStrube.Blazor.WebAudio; 4 | 5 | /// 6 | /// This specifies options for constructing a . 7 | /// 8 | /// See the API definition here. 9 | public class ConstantSourceOptions 10 | { 11 | /// 12 | /// The initial value for the of this node. 13 | /// 14 | /// 15 | /// The default value is 1. 16 | /// 17 | [JsonPropertyName("offset")] 18 | public float Offset { get; set; } = 1; 19 | } 20 | -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.WebAudio/Options/ConvolverOptions.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace KristofferStrube.Blazor.WebAudio; 4 | 5 | /// 6 | /// This specifies options for constructing a . 7 | /// 8 | /// See the API definition here. 9 | public class ConvolverOptions : AudioNodeOptions 10 | { 11 | /// 12 | /// The desired buffer for the . This buffer will be normalized according to the value of . 13 | /// 14 | [JsonPropertyName("buffer")] 15 | public AudioBuffer? Buffer { get; set; } 16 | 17 | /// 18 | /// The opposite of the desired initial value for . 19 | /// 20 | /// 21 | /// The default value is . 22 | /// 23 | [JsonPropertyName("disableNormalization")] 24 | public bool DisableNormalization { get; set; } = false; 25 | 26 | /// 27 | /// 28 | /// The default value is . 29 | /// 30 | public override ChannelCountMode ChannelCountMode { get; set; } = ChannelCountMode.ClampedMax; 31 | } 32 | -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.WebAudio/Options/DelayOptions.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace KristofferStrube.Blazor.WebAudio; 4 | 5 | /// 6 | /// This specifies options for constructing a . 7 | /// 8 | /// See the API definition here. 9 | public class DelayOptions : AudioNodeOptions 10 | { 11 | /// 12 | /// The maximum delay time for the node. Time is in seconds and must be greater than 0 and less than 3 minutes (180 seconds). 13 | /// 14 | /// 15 | /// The default value is 1. 16 | /// 17 | [JsonPropertyName("maxDelayTime")] 18 | public double MaxDelayTime { get; set; } = 1; 19 | 20 | /// 21 | /// The initial delay time for the node. 22 | /// 23 | /// 24 | /// The default value is 0. 25 | /// 26 | [JsonPropertyName("delayTime")] 27 | public double DelayTime { get; set; } = 0; 28 | } 29 | -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.WebAudio/Options/DistanceModelType.cs: -------------------------------------------------------------------------------- 1 | using KristofferStrube.Blazor.WebAudio.Converters; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace KristofferStrube.Blazor.WebAudio; 5 | 6 | /// 7 | /// The enum determines which algorithm will be used to reduce the volume of an audio source as it moves away from the listener. The default is "inverse". 8 | /// 9 | /// See the API definition here. 10 | [JsonConverter(typeof(DistanceModelTypeConverter))] 11 | public enum DistanceModelType 12 | { 13 | /// 14 | /// The volume decreases proportionally to the distance between the listener and the sound source, creating a uniform and predictable attenuation effect. 15 | /// 16 | Linear, 17 | 18 | /// 19 | /// The volume decreases sharply as the distance increases initially but tapers off at greater distances, closely resembling real-world acoustic behavior. 20 | /// 21 | Inverse, 22 | 23 | /// 24 | /// The volume decreases exponentially as the distance increases, resulting in a rapid attenuation of sound over shorter distances. 25 | /// 26 | Exponential, 27 | } -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.WebAudio/Options/DynamicsCompressorOptions.cs: -------------------------------------------------------------------------------- 1 |  2 | using System.Text.Json.Serialization; 3 | 4 | namespace KristofferStrube.Blazor.WebAudio; 5 | 6 | /// 7 | /// This specifies the options to use in constructing a . 8 | /// 9 | /// See the API definition here. 10 | public class DynamicsCompressorOptions : AudioNodeOptions 11 | { 12 | /// 13 | /// 14 | /// The default value is . 15 | /// 16 | [JsonPropertyName("channelCountMode")] 17 | public override ChannelCountMode ChannelCountMode { get; set; } = ChannelCountMode.ClampedMax; 18 | 19 | /// 20 | /// The initial value for the AudioParam. 21 | /// 22 | [JsonPropertyName("attack")] 23 | public float Attack { get; set; } = 0.003f; 24 | 25 | /// 26 | /// The initial value for the AudioParam. 27 | /// 28 | [JsonPropertyName("knee")] 29 | public float Knee { get; set; } = 30f; 30 | 31 | /// 32 | /// The initial value for the AudioParam. 33 | /// 34 | [JsonPropertyName("ratio")] 35 | public float Ratio { get; set; } = 12f; 36 | 37 | /// 38 | /// The initial value for the AudioParam. 39 | /// 40 | [JsonPropertyName("release")] 41 | public float Release { get; set; } = 0.25f; 42 | 43 | /// 44 | /// The initial value for the AudioParam. 45 | /// 46 | [JsonPropertyName("threshold")] 47 | public float Threshold { get; set; } = -24f; 48 | } 49 | -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.WebAudio/Options/GainOptions.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace KristofferStrube.Blazor.WebAudio; 4 | 5 | /// 6 | /// This specifies options for constructing a . 7 | /// 8 | /// See the API definition here. 9 | public class GainOptions : AudioNodeOptions 10 | { 11 | /// 12 | /// The initial value for 13 | /// 14 | [JsonPropertyName("gain")] 15 | public float Gain { get; set; } = 1; 16 | } 17 | -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.WebAudio/Options/IIRFilterOptions.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace KristofferStrube.Blazor.WebAudio; 4 | 5 | public class IIRFilterOptions : AudioNodeOptions 6 | { 7 | [JsonPropertyName("feedforward")] 8 | public required double[] Feedforward { get; set; } 9 | 10 | [JsonPropertyName("feedback")] 11 | public required double[] Feedback { get; set; } 12 | } 13 | -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.WebAudio/Options/MediaElementAudioSourceOptions.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.JSInterop; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace KristofferStrube.Blazor.WebAudio.Options; 5 | 6 | /// 7 | /// This specifies the options to use in constructing a . 8 | /// 9 | /// 10 | /// See the API definition here. 11 | /// 12 | public class MediaElementAudioSourceOptions 13 | { 14 | /// 15 | /// The media element that will be re-routed. 16 | /// 17 | [JsonPropertyName("mediaElement")] 18 | public required IJSObjectReference MediaElement { get; set; } 19 | } 20 | -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.WebAudio/Options/MediaStreamAudioDestinationOptions.cs: -------------------------------------------------------------------------------- 1 | namespace KristofferStrube.Blazor.WebAudio.Options; 2 | 3 | /// 4 | /// This specifies the options to use in constructing a . 5 | /// 6 | /// 7 | /// This type doesn't exist in the specs, but the has a non-standard value for the attribute which is why this type is added in this wrapper.
8 | /// See the API definition here. 9 | ///
10 | public class MediaStreamAudioDestinationOptions : AudioNodeOptions 11 | { 12 | /// 13 | /// 14 | /// The default value is . 15 | /// 16 | public override ChannelCountMode ChannelCountMode { get; set; } = ChannelCountMode.Explicit; 17 | } 18 | -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.WebAudio/Options/MediaStreamAudioSourceOptions.cs: -------------------------------------------------------------------------------- 1 | using KristofferStrube.Blazor.MediaCaptureStreams; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace KristofferStrube.Blazor.WebAudio; 5 | 6 | /// 7 | /// This specifies the options to use in constructing a . 8 | /// 9 | /// See the API definition here. 10 | public class MediaStreamAudioSourceOptions 11 | { 12 | /// 13 | /// The media stream that will act as a source. 14 | /// 15 | [JsonPropertyName("mediaStream")] 16 | public required MediaStream MediaStream { get; set; } 17 | } -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.WebAudio/Options/MediaStreamTrackAudioSourceOptions.cs: -------------------------------------------------------------------------------- 1 | using KristofferStrube.Blazor.MediaCaptureStreams; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace KristofferStrube.Blazor.WebAudio; 5 | 6 | /// 7 | /// This specifies the options to use in constructing a . 8 | /// 9 | /// See the API definition here. 10 | public class MediaStreamTrackAudioSourceOptions 11 | { 12 | /// 13 | /// The media stream track that will act as a source. 14 | /// 15 | [JsonPropertyName("mediaStreamTrack")] 16 | public required MediaStreamTrack MediaStreamTrack { get; set; } 17 | } -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.WebAudio/Options/OfflineAudioContextOptions.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace KristofferStrube.Blazor.WebAudio; 4 | 5 | /// 6 | /// This specifies the options to use in constructing an . 7 | /// 8 | /// See the API definition here. 9 | public class OfflineAudioContextOptions 10 | { 11 | /// 12 | /// The number of channels for this . 13 | /// 14 | [JsonPropertyName("numberOfChannelse")] 15 | public ulong NumberOfChannelse { get; set; } = 1; 16 | 17 | /// 18 | /// The length of the rendered in sample-frames. 19 | /// 20 | [JsonPropertyName("length")] 21 | public required ulong Length { get; set; } 22 | 23 | /// 24 | /// The sample rate for this . 25 | /// 26 | [JsonPropertyName("sampleRate")] 27 | public required float SampleRate { get; set; } 28 | } 29 | -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.WebAudio/Options/OscillatorOptions.cs: -------------------------------------------------------------------------------- 1 | using KristofferStrube.Blazor.WebIDL.Exceptions; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace KristofferStrube.Blazor.WebAudio; 5 | 6 | /// 7 | /// This specifies the options to be used when constructing an . 8 | /// 9 | /// See the API definition here. 10 | public class OscillatorOptions : AudioNodeOptions 11 | { 12 | /// 13 | /// The type of oscillator to be constructed. 14 | /// If periodicWave is specified, then any valid value for type is ignored; it is treated as if it were set to "custom". 15 | /// 16 | /// 17 | /// It throws an if this is set to without a being provided. 18 | /// 19 | [JsonPropertyName("type")] 20 | public OscillatorType Type { get; set; } = OscillatorType.Sine; 21 | 22 | /// 23 | /// The initial frequency for the . 24 | /// 25 | [JsonPropertyName("frequency")] 26 | public float Frequency { get; set; } = 440; 27 | 28 | /// 29 | /// The initial detune value for the . 30 | /// 31 | [JsonPropertyName("detune")] 32 | public float Detune { get; set; } = 0; 33 | 34 | /// 35 | /// The PeriodicWave for the . 36 | /// If this is specified, then any valid value for is ignored; it is treated as if were specified. 37 | /// 38 | [JsonPropertyName("periodicWave")] 39 | [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] 40 | public PeriodicWave? PeriodicWave { get; set; } 41 | } 42 | -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.WebAudio/Options/OscillatorType.cs: -------------------------------------------------------------------------------- 1 | using KristofferStrube.Blazor.WebAudio.Converters; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace KristofferStrube.Blazor.WebAudio; 5 | 6 | /// 7 | /// Changing the gain of an audio signal is a fundamental operation in audio applications. This interface is an with a single input and single output. 8 | /// 9 | /// See the API definition here. 10 | [JsonConverter(typeof(OscillatorTypeConverter))] 11 | public enum OscillatorType 12 | { 13 | /// 14 | /// A sine wave. 15 | /// 16 | Sine, 17 | /// 18 | /// A square wave of duty period 0.5. 19 | /// 20 | Square, 21 | /// 22 | /// A sawtooth wave. 23 | /// 24 | Sawtooth, 25 | /// 26 | /// A triangle wave. 27 | /// 28 | Triangle, 29 | /// 30 | /// A custom periodic wave. 31 | /// 32 | Custom 33 | } -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.WebAudio/Options/OverSampleType.cs: -------------------------------------------------------------------------------- 1 | using KristofferStrube.Blazor.WebAudio.Converters; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace KristofferStrube.Blazor.WebAudio; 5 | 6 | /// 7 | /// The enum determines which the type of oversampling to use when shaping a curve. 8 | /// 9 | /// See the API definition here. 10 | [JsonConverter(typeof(OverSampleTypeConverter))] 11 | public enum OverSampleType 12 | { 13 | /// 14 | /// Don’t oversample 15 | /// 16 | None, 17 | 18 | /// 19 | /// Oversample two times 20 | /// 21 | TwoX, 22 | 23 | /// 24 | /// Oversample four times 25 | /// 26 | FourX, 27 | } -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.WebAudio/Options/PanningModelType.cs: -------------------------------------------------------------------------------- 1 | using KristofferStrube.Blazor.WebAudio.Converters; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace KristofferStrube.Blazor.WebAudio; 5 | 6 | /// 7 | /// The enum determines which spatialization algorithm will be used to position the audio in 3D space. The default is . 8 | /// 9 | /// See the API definition here. 10 | [JsonConverter(typeof(PanningModelTypeConverter))] 11 | public enum PanningModelType 12 | { 13 | /// 14 | /// A simple and efficient spatialization algorithm using equal-power panning. 15 | /// 16 | /// 17 | /// When this panning model is used, all the s used to compute the output of this node are . 18 | /// 19 | EqualPower, 20 | 21 | /// 22 | /// A higher quality spatialization algorithm using a convolution with measured impulse responses from human subjects. This panning method renders stereo output. 23 | /// 24 | /// 25 | /// When this panning model is used, all the s used to compute the output of this node are . 26 | /// 27 | HRTF, 28 | } -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.WebAudio/Options/PeriodicWaveConstraints.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace KristofferStrube.Blazor.WebAudio; 4 | 5 | /// 6 | /// is used to specify how the waveform is normalized. 7 | /// 8 | /// See the API definition here. 9 | public class PeriodicWaveConstraints 10 | { 11 | /// 12 | /// Controls whether the periodic wave is normalized or not. 13 | /// If , the waveform is not normalized; otherwise, the waveform is normalized. 14 | /// 15 | [JsonPropertyName("disableNormalization")] 16 | public bool DisableNormalization { get; set; } = false; 17 | } 18 | -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.WebAudio/Options/PeriodicWaveOptions.cs: -------------------------------------------------------------------------------- 1 | using KristofferStrube.Blazor.WebIDL.Exceptions; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace KristofferStrube.Blazor.WebAudio; 5 | 6 | /// 7 | /// is used to specify how the waveform is constructed.
8 | /// If only one of real or imag is specified. 9 | /// The other is treated as if it were an array of all zeroes of the same length. 10 | /// If neither is given, a is created that will be equivalent to an with . 11 | /// If both are given, the sequences must have the same length; otherwise an error of type will be thrown. 12 | ///
13 | /// See the API definition here. 14 | public class PeriodicWaveOptions : PeriodicWaveConstraints 15 | { 16 | /// 17 | /// The imag parameter represents an array of sine terms. 18 | /// The first element (index 0) does not exist in the Fourier series. 19 | /// The second element (index 1) represents the fundamental frequency. 20 | /// The third represents the first overtone and so on. 21 | /// 22 | [JsonPropertyName("imag")] 23 | [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] 24 | public float[]? Imag { get; set; } 25 | 26 | /// 27 | /// The real parameter represents an array of cosine terms. 28 | /// The first element (index 0) is the DC-offset of the periodic waveform. 29 | /// The second element (index 1) represents the fundmental frequency. 30 | /// The third represents the first overtone and so on. 31 | /// 32 | [JsonPropertyName("real")] 33 | [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] 34 | public float[]? Real { get; set; } 35 | } 36 | -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.WebAudio/Options/StereoPannerOptions.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace KristofferStrube.Blazor.WebAudio; 4 | 5 | /// 6 | /// This specifies options for constructing a . 7 | /// 8 | /// See the API definition here. 9 | public class StereoPannerOptions : AudioNodeOptions 10 | { 11 | /// 12 | /// 13 | /// The default value is . 14 | /// 15 | public override ChannelCountMode ChannelCountMode { get; set; } = ChannelCountMode.ClampedMax; 16 | 17 | /// 18 | /// The initial value for 19 | /// 20 | [JsonPropertyName("pan")] 21 | public float Pan { get; set; } = 0; 22 | } 23 | -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.WebAudio/Options/WaveShaperOptions.cs: -------------------------------------------------------------------------------- 1 | using System.Text.Json.Serialization; 2 | 3 | namespace KristofferStrube.Blazor.WebAudio; 4 | 5 | /// 6 | /// This specifies options for constructing a . 7 | /// 8 | /// See the API definition here. 9 | public class WaveShaperOptions : AudioNodeOptions 10 | { 11 | /// 12 | /// The initial value for 13 | /// 14 | [JsonPropertyName("curve")] 15 | [JsonIgnore(Condition = JsonIgnoreCondition.WhenWritingNull)] 16 | public float[]? Curve { get; set; } 17 | 18 | /// 19 | /// The initial value for 20 | /// 21 | [JsonPropertyName("oversample")] 22 | public OverSampleType Oversample { get; set; } = OverSampleType.None; 23 | } 24 | -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.WebAudio/PeriodicWave.cs: -------------------------------------------------------------------------------- 1 | using KristofferStrube.Blazor.WebAudio.Extensions; 2 | using KristofferStrube.Blazor.WebIDL; 3 | using Microsoft.JSInterop; 4 | 5 | namespace KristofferStrube.Blazor.WebAudio; 6 | 7 | /// 8 | /// represents an arbitrary periodic waveform to be used with an . 9 | /// 10 | /// See the API definition here. 11 | [IJSWrapperConverter] 12 | public class PeriodicWave : BaseJSWrapper, IJSCreatable 13 | { 14 | /// 15 | public static async Task CreateAsync(IJSRuntime jSRuntime, IJSObjectReference jSReference) 16 | { 17 | return await CreateAsync(jSRuntime, jSReference, new()); 18 | } 19 | 20 | /// 21 | public static Task CreateAsync(IJSRuntime jSRuntime, IJSObjectReference jSReference, CreationOptions options) 22 | { 23 | return Task.FromResult(new PeriodicWave(jSRuntime, jSReference, options)); 24 | } 25 | 26 | /// 27 | /// Creates a using the standard constructor. 28 | /// 29 | /// An instance. 30 | /// The this new will be associated with. 31 | /// Initial parameter value for this . 32 | /// A new instance of a . 33 | public static async Task CreateAsync(IJSRuntime jSRuntime, BaseAudioContext context, PeriodicWaveOptions options) 34 | { 35 | IJSObjectReference helper = await jSRuntime.GetHelperAsync(); 36 | IJSObjectReference jSInstance = await helper.InvokeAsync("constructPeriodicWave", context, options); 37 | return new PeriodicWave(jSRuntime, jSInstance, new() { DisposesJSReference = true }); 38 | } 39 | 40 | /// 41 | protected PeriodicWave(IJSRuntime jSRuntime, IJSObjectReference jSReference, CreationOptions options) : base(jSRuntime, jSReference, options) 42 | { 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.WebAudio/UnionTypes/AudioContextLatencyCategoryOrDouble.cs: -------------------------------------------------------------------------------- 1 | using KristofferStrube.Blazor.WebAudio.Converters; 2 | using KristofferStrube.Blazor.WebAudio.UnionTypes; 3 | using System.Text.Json.Serialization; 4 | 5 | namespace KristofferStrube.Blazor.WebAudio; 6 | 7 | /// 8 | /// A value that is either a or a . 9 | /// 10 | [JsonConverter(typeof(UnionTypeJsonConverter))] 11 | public class AudioContextLatencyCategoryOrDouble : UnionType 12 | { 13 | /// 14 | /// Creates an from an explicitly instead of using the implicit converter. 15 | /// 16 | /// A . 17 | public AudioContextLatencyCategoryOrDouble(AudioContextLatencyCategory value) : base(value) { } 18 | 19 | /// 20 | /// Creates an from a explicitly instead of using the implicit converter. 21 | /// 22 | /// A . 23 | public AudioContextLatencyCategoryOrDouble(double value) : base(value) { } 24 | 25 | internal AudioContextLatencyCategoryOrDouble(object value) : base(value) { } 26 | 27 | /// 28 | /// Creates an from an . 29 | /// 30 | /// An . 31 | public static implicit operator AudioContextLatencyCategoryOrDouble(AudioContextLatencyCategory value) 32 | { 33 | return new(value); 34 | } 35 | 36 | /// 37 | /// Creates an from a . 38 | /// 39 | /// A . 40 | public static implicit operator AudioContextLatencyCategoryOrDouble(double value) 41 | { 42 | return new(value); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.WebAudio/UnionTypes/UnionType.cs: -------------------------------------------------------------------------------- 1 | using KristofferStrube.Blazor.WebAudio.Converters; 2 | using System.Text.Json.Serialization; 3 | 4 | namespace KristofferStrube.Blazor.WebAudio.UnionTypes; 5 | 6 | /// 7 | /// A common Union Type class. 8 | /// 9 | [JsonConverter(typeof(UnionTypeJsonConverter))] 10 | public class UnionType 11 | { 12 | /// 13 | /// Creates a Union Type class from a value. 14 | /// 15 | /// 16 | protected UnionType(object value) 17 | { 18 | Value = value; 19 | } 20 | 21 | /// 22 | /// The value of the Union Type. 23 | /// 24 | public object Value { get; } 25 | } -------------------------------------------------------------------------------- /src/KristofferStrube.Blazor.WebAudio/wwwroot/KristofferStrube.Blazor.WebAudio.PullAudioProcessor.js: -------------------------------------------------------------------------------- 1 | class PullAudioProcessor extends AudioWorkletProcessor { 2 | queue = []; 3 | backIndex = 0; 4 | frontIndex = 0; 5 | dataRequested = 0; 6 | 7 | static get parameterDescriptors() { 8 | return [{ 9 | name: 'lowTide', 10 | defaultValue: 10, 11 | minValue: 1, 12 | maxValue: 10000, 13 | automationRate: "k-rate" 14 | }, 15 | { 16 | name: 'highTide', 17 | defaultValue: 50, 18 | minValue: 1, 19 | maxValue: 10000, 20 | automationRate: "k-rate" 21 | }, 22 | { 23 | name: 'bufferRequestSize', 24 | defaultValue: 10, 25 | minValue: 1, 26 | maxValue: 10000, 27 | automationRate: "k-rate" 28 | }, 29 | { 30 | name: 'resolution', 31 | defaultValue: 1, 32 | minValue: 1, 33 | maxValue: 255, 34 | automationRate: "k-rate" 35 | }]; 36 | } 37 | 38 | constructor(...args) { 39 | super(...args); 40 | this.queue = []; 41 | this.port.onmessage = (e) => { 42 | for (let i = 0; i < e.data.length / 128; i++) { 43 | this.queue.push(e.data.slice(i * 128, (i + 1) * 128)); 44 | this.frontIndex++; 45 | this.dataRequested--; 46 | } 47 | }; 48 | } 49 | 50 | process(inputs, outputs, parameters) { 51 | const output = outputs[0]; 52 | const lowTide = parameters.lowTide[0]; 53 | const highTide = parameters.highTide[0]; 54 | const bufferRequestSize = parameters.bufferRequestSize[0]; 55 | const resolution = parameters.resolution[0]; 56 | 57 | try { 58 | const count = this.frontIndex - this.backIndex; 59 | if (count >= output.length) { 60 | for (let i = 0; i < output.length; i++) { 61 | let data = this.queue[this.backIndex]; 62 | this.backIndex++; 63 | 64 | let channel = output[i]; 65 | for (let j = 0; j < channel.length; j++) { 66 | if (resolution == 255) { 67 | channel[j] = data[j] / 255 * 2 - 1; 68 | } 69 | else { 70 | channel[j] = data[j]; 71 | } 72 | } 73 | } 74 | } 75 | if (count < lowTide && this.dataRequested + bufferRequestSize < highTide) { 76 | this.dataRequested += bufferRequestSize; 77 | this.port.postMessage(bufferRequestSize); 78 | } 79 | } 80 | catch (e) { 81 | //this.port.postMessage(e.message + "-----" + e.stack); 82 | } 83 | return true; 84 | } 85 | } 86 | 87 | registerProcessor("kristoffer-strube-webaudio-pull-audio-processor", PullAudioProcessor); -------------------------------------------------------------------------------- /tests/BlazorServer/App.razor: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | Not found 8 | 9 |

Sorry, there's nothing at this address.

10 |
11 |
12 |
13 | -------------------------------------------------------------------------------- /tests/BlazorServer/BlazorServer.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net9.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /tests/BlazorServer/EvaluationContext.cs: -------------------------------------------------------------------------------- 1 |  2 | namespace BlazorServer; 3 | 4 | public class EvaluationContext 5 | { 6 | } 7 | -------------------------------------------------------------------------------- /tests/BlazorServer/MainLayout.razor: -------------------------------------------------------------------------------- 1 | @inherits LayoutComponentBase 2 | 3 |
@Body
4 | -------------------------------------------------------------------------------- /tests/BlazorServer/Pages/Index.razor: -------------------------------------------------------------------------------- 1 | @page "/" 2 | 3 | @result 4 | 5 | 6 | 12 | 13 | 14 | @code { 15 | string? result; 16 | 17 | [Inject] 18 | public required EvaluationContext EvaluationContext { get; set; } 19 | 20 | protected override void OnAfterRender(bool firstRender) 21 | { 22 | if (!firstRender) return; 23 | result = "done"; 24 | StateHasChanged(); 25 | } 26 | } -------------------------------------------------------------------------------- /tests/BlazorServer/Pages/_Host.cshtml: -------------------------------------------------------------------------------- 1 | @page "/" 2 | @using Microsoft.AspNetCore.Components.Web 3 | @namespace BlazorServer.Pages 4 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | 19 | An error has occurred. This application may no longer respond until reloaded. 20 | 21 | 22 | An unhandled exception has occurred. See browser dev tools for details. 23 | 24 | Reload 25 | 🗙 26 |
27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /tests/BlazorServer/Program.cs: -------------------------------------------------------------------------------- 1 | namespace BlazorServer; 2 | 3 | public class Program 4 | { 5 | private static async Task Main(string[] args) 6 | { 7 | var host = BuildWebHost(args, _ => { }); 8 | await host.RunAsync(); 9 | } 10 | 11 | public static IHost BuildWebHost(string[] args, Action configureServices) 12 | => Host.CreateDefaultBuilder(args) 13 | .ConfigureWebHostDefaults(builder => 14 | { 15 | builder.UseStaticWebAssets(); 16 | builder.UseStartup(); 17 | builder.ConfigureServices(configureServices); 18 | }) 19 | .Build(); 20 | } -------------------------------------------------------------------------------- /tests/BlazorServer/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "iisExpress": { 4 | "applicationUrl": "http://localhost:1729", 5 | "sslPort": 44325 6 | } 7 | }, 8 | "profiles": { 9 | "http": { 10 | "commandName": "Project", 11 | "dotnetRunMessages": true, 12 | "launchBrowser": true, 13 | "applicationUrl": "http://localhost:5151", 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "https": { 19 | "commandName": "Project", 20 | "dotnetRunMessages": true, 21 | "launchBrowser": true, 22 | "applicationUrl": "https://localhost:7131;http://localhost:5151", 23 | "environmentVariables": { 24 | "ASPNETCORE_ENVIRONMENT": "Development" 25 | } 26 | }, 27 | "IIS Express": { 28 | "commandName": "IISExpress", 29 | "launchBrowser": true, 30 | "environmentVariables": { 31 | "ASPNETCORE_ENVIRONMENT": "Development" 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /tests/BlazorServer/Startup.cs: -------------------------------------------------------------------------------- 1 | namespace BlazorServer; 2 | 3 | public class Startup 4 | { 5 | public void ConfigureServices(IServiceCollection services) 6 | { 7 | services.AddRazorPages(); 8 | services.AddServerSideBlazor(); 9 | } 10 | 11 | public virtual void Configure(IApplicationBuilder app, IWebHostEnvironment env) 12 | { 13 | if (env.IsDevelopment()) 14 | { 15 | app.UseExceptionHandler("/Error"); 16 | // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts. 17 | app.UseHsts(); 18 | } 19 | 20 | app.UseHttpsRedirection(); 21 | 22 | app.UseStaticFiles(); 23 | 24 | app.UseRouting(); 25 | 26 | app.UseEndpoints(endpoints => 27 | { 28 | endpoints.MapBlazorHub(); 29 | endpoints.MapFallbackToPage("/_Host"); 30 | }); 31 | } 32 | } -------------------------------------------------------------------------------- /tests/BlazorServer/_Imports.razor: -------------------------------------------------------------------------------- 1 | @using Microsoft.AspNetCore.Components.Routing 2 | @using Microsoft.AspNetCore.Components.Web 3 | @using Microsoft.JSInterop 4 | @using BlazorServer 5 | -------------------------------------------------------------------------------- /tests/BlazorServer/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "DetailedErrors": true, 3 | "Logging": { 4 | "LogLevel": { 5 | "Default": "Information", 6 | "Microsoft.AspNetCore": "Warning" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /tests/BlazorServer/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "AllowedHosts": "*" 9 | } 10 | -------------------------------------------------------------------------------- /tests/BlazorServer/wwwroot/css/site.css: -------------------------------------------------------------------------------- 1 | #blazor-error-ui { 2 | background: lightyellow; 3 | bottom: 0; 4 | box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2); 5 | display: none; 6 | left: 0; 7 | padding: 0.6rem 1.25rem 0.7rem 1.25rem; 8 | position: fixed; 9 | width: 100%; 10 | z-index: 1000; 11 | } 12 | 13 | #blazor-error-ui .dismiss { 14 | cursor: pointer; 15 | position: absolute; 16 | right: 3.5rem; 17 | top: 0.5rem; 18 | } 19 | 20 | .blazor-error-boundary { 21 | background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121; 22 | padding: 1rem 1rem 1rem 3.7rem; 23 | color: white; 24 | } 25 | 26 | .blazor-error-boundary::after { 27 | content: "An error has occurred." 28 | } 29 | -------------------------------------------------------------------------------- /tests/IntegrationTests/AudioNodeTests/AudioDestinationNodeTest.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | 3 | namespace IntegrationTests.AudioNodeTests; 4 | 5 | public class AudioDestinationNodeTest : AudioNodeTest 6 | { 7 | public override async Task GetDefaultInstanceAsync() 8 | { 9 | return await AudioContext.GetDestinationAsync(); 10 | } 11 | 12 | [Test] 13 | public async Task GetMaxChannelCountAsync_RetrievesMaxChannelCount() 14 | { 15 | // Arrange 16 | await using AudioDestinationNode destination = await AudioContext.GetDestinationAsync(); 17 | 18 | // Act 19 | ulong maxChannelCount = await destination.GetMaxChannelCountAsync(); 20 | 21 | // Assert 22 | _ = maxChannelCount.Should().BeGreaterThan(0); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /tests/IntegrationTests/AudioNodeTests/AudioNodeTest.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using KristofferStrube.Blazor.WebIDL.Exceptions; 3 | 4 | namespace IntegrationTests.AudioNodeTests; 5 | 6 | public abstract class AudioNodeTest : BlazorTest where TAudioNode : AudioNode 7 | { 8 | public abstract Task GetDefaultInstanceAsync(); 9 | 10 | public virtual Dictionary UnsupportedChannelCountModes => []; 11 | 12 | public virtual Dictionary UnsupportedChannelInterpretations => []; 13 | 14 | [Test] 15 | public async Task CreateAsync_WithNoOptions_Succeeds() 16 | { 17 | // Act 18 | await using TAudioNode node = await GetDefaultInstanceAsync(); 19 | 20 | // Assert 21 | _ = node.Should().BeOfType(); 22 | } 23 | 24 | [TestCase(ChannelCountMode.Max)] 25 | [TestCase(ChannelCountMode.Explicit)] 26 | [TestCase(ChannelCountMode.ClampedMax)] 27 | [Test] 28 | public async Task SettingChannelCountMode_SetsChannelCountMode_ExceptForUnsupportedValues(ChannelCountMode mode) 29 | { 30 | // Act 31 | Func> action = async () => 32 | { 33 | await using TAudioNode node = await GetDefaultInstanceAsync(); 34 | await node.SetChannelCountModeAsync(mode); 35 | return await node.GetChannelCountModeAsync(); 36 | }; 37 | 38 | // Assert 39 | if (UnsupportedChannelCountModes.TryGetValue(mode, out Type? exceptionType)) 40 | { 41 | _ = (await action.Should().ThrowAsync()).And.Should().BeOfType(exceptionType); 42 | } 43 | else 44 | { 45 | ChannelCountMode result = await action(); 46 | _ = result.Should().Be(mode); 47 | } 48 | } 49 | 50 | [TestCase(ChannelInterpretation.Discrete)] 51 | [TestCase(ChannelInterpretation.Speakers)] 52 | [Test] 53 | public async Task SettingChannelInterpretation_SetsInterpretation_ExceptForUnsupportedValues(ChannelInterpretation interpretation) 54 | { 55 | // Act 56 | Func> action = async () => 57 | { 58 | await using TAudioNode node = await GetDefaultInstanceAsync(); 59 | await node.SetChannelInterpretationAsync(interpretation); 60 | return await node.GetChannelInterpretationAsync(); 61 | }; 62 | 63 | // Assert 64 | if (UnsupportedChannelInterpretations.TryGetValue(interpretation, out Type? exceptionType)) 65 | { 66 | _ = (await action.Should().ThrowAsync()).And.Should().BeOfType(exceptionType); 67 | } 68 | else 69 | { 70 | ChannelInterpretation result = await action(); 71 | _ = result.Should().Be(interpretation); 72 | } 73 | } 74 | 75 | } 76 | -------------------------------------------------------------------------------- /tests/IntegrationTests/AudioNodeTests/ChannelMergerNodeTest.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using KristofferStrube.Blazor.WebIDL.Exceptions; 3 | using Microsoft.JSInterop; 4 | 5 | namespace IntegrationTests.AudioNodeTests; 6 | 7 | public class ChannelMergerNodeTest : AudioNodeWithAudioNodeOptions 8 | { 9 | public override async Task CreateAsync(IJSRuntime jSRuntime, AudioContext context, ChannelMergerOptions? options) 10 | => await ChannelMergerNode.CreateAsync(JSRuntime, AudioContext, options); 11 | 12 | public override Dictionary UnsupportedChannelCountModes => new() 13 | { 14 | [ChannelCountMode.Max] = typeof(InvalidStateErrorException), 15 | [ChannelCountMode.ClampedMax] = typeof(InvalidStateErrorException), 16 | }; 17 | 18 | [Test] 19 | public async Task CreateAsync_WithNoOptions_DefaultsTo6Inputs() 20 | { 21 | // Arrange 22 | await using ChannelMergerNode node = await ChannelMergerNode.CreateAsync(JSRuntime, AudioContext); 23 | 24 | // Act 25 | ulong numberOfInputs = await node.GetNumberOfInputsAsync(); 26 | 27 | // Assert 28 | _ = numberOfInputs.Should().Be(6); 29 | } 30 | 31 | [Test] 32 | public async Task CreateAsync_WithEmptyOptions_DefaultsTo6Inputs() 33 | { 34 | // Arrange 35 | await using ChannelMergerNode node = await ChannelMergerNode.CreateAsync(JSRuntime, AudioContext, new()); 36 | 37 | // Act 38 | ulong numberOfInputs = await node.GetNumberOfInputsAsync(); 39 | 40 | // Assert 41 | _ = numberOfInputs.Should().Be(6); 42 | } 43 | } -------------------------------------------------------------------------------- /tests/IntegrationTests/AudioNodeTests/ChannelSplitterNodeTest.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using KristofferStrube.Blazor.WebIDL.Exceptions; 3 | using Microsoft.JSInterop; 4 | 5 | namespace IntegrationTests.AudioNodeTests; 6 | 7 | public class ChannelSplitterNodeTest : AudioNodeWithAudioNodeOptions 8 | { 9 | public override async Task CreateAsync(IJSRuntime jSRuntime, AudioContext context, ChannelSplitterOptions? options) 10 | => await ChannelSplitterNode.CreateAsync(JSRuntime, AudioContext, options); 11 | 12 | public override Dictionary UnsupportedChannelCountModes => new() 13 | { 14 | [ChannelCountMode.Max] = typeof(InvalidStateErrorException), 15 | [ChannelCountMode.ClampedMax] = typeof(InvalidStateErrorException), 16 | }; 17 | 18 | public override Dictionary UnsupportedChannelInterpretations => new() 19 | { 20 | [ChannelInterpretation.Speakers] = typeof(InvalidStateErrorException), 21 | }; 22 | 23 | [Test] 24 | public async Task CreateAsync_WithNoOptions_DefaultsTo6Outputs() 25 | { 26 | // Arrange 27 | await using ChannelSplitterNode node = await ChannelSplitterNode.CreateAsync(JSRuntime, AudioContext); 28 | 29 | // Act 30 | ulong numberOfOutputs = await node.GetNumberOfOutputsAsync(); 31 | 32 | // Assert 33 | _ = numberOfOutputs.Should().Be(6); 34 | } 35 | 36 | [Test] 37 | public async Task CreateAsync_WithEmptyOptions_DefaultsTo6Outputs() 38 | { 39 | // Arrange 40 | await using ChannelSplitterNode node = await ChannelSplitterNode.CreateAsync(JSRuntime, AudioContext, new()); 41 | 42 | // Act 43 | ulong numberOfOutputs = await node.GetNumberOfOutputsAsync(); 44 | 45 | // Assert 46 | _ = numberOfOutputs.Should().Be(6); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /tests/IntegrationTests/AudioNodeTests/ConstantSourceNodeTest.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | 3 | namespace IntegrationTests.AudioNodeTests; 4 | 5 | public class ConstantSourceNodeTest : AudioNodeTest 6 | { 7 | public override async Task GetDefaultInstanceAsync() 8 | { 9 | return await ConstantSourceNode.CreateAsync(JSRuntime, AudioContext); 10 | } 11 | 12 | [Test] 13 | [TestCase(1)] 14 | [TestCase(-0.5f)] 15 | public async Task GetOffsetAsync_ShouldRetrieveOffsetParameter(float offset) 16 | { 17 | // Arrange 18 | await using ConstantSourceNode node = await ConstantSourceNode.CreateAsync(JSRuntime, AudioContext, new() 19 | { 20 | Offset = offset 21 | }); 22 | 23 | // Act 24 | await using AudioParam offsetParameter = await node.GetOffsetAsync(); 25 | 26 | // Assert 27 | float readOffset = await offsetParameter.GetValueAsync(); 28 | _ = readOffset.Should().Be(offset); 29 | 30 | await offsetParameter.SetValueAsync(offset + 1); 31 | readOffset = await offsetParameter.GetValueAsync(); 32 | _ = readOffset.Should().Be(offset + 1); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tests/IntegrationTests/AudioNodeTests/DelayNodeTest.cs: -------------------------------------------------------------------------------- 1 | using FluentAssertions; 2 | using Microsoft.JSInterop; 3 | 4 | namespace IntegrationTests.AudioNodeTests; 5 | 6 | public class DelayNodeTest : AudioNodeWithAudioNodeOptions 7 | { 8 | public override async Task CreateAsync(IJSRuntime jSRuntime, AudioContext context, DelayOptions? options) 9 | => await DelayNode.CreateAsync(JSRuntime, AudioContext, options); 10 | 11 | [Test] 12 | [TestCase(0)] 13 | [TestCase(10)] 14 | public async Task GetDelayTimeAsync_ShouldRetrieveDelayTimeParameter(float delayTime) 15 | { 16 | // Arrange 17 | await using DelayNode node = await DelayNode.CreateAsync(JSRuntime, AudioContext, new() 18 | { 19 | DelayTime = delayTime, 20 | MaxDelayTime = delayTime + 1, 21 | }); 22 | 23 | // Act 24 | await using AudioParam delayTimeParameter = await node.GetDelayTimeAsync(); 25 | 26 | // Assert 27 | float readDelayTime = await delayTimeParameter.GetValueAsync(); 28 | _ = readDelayTime.Should().Be(delayTime); 29 | 30 | await delayTimeParameter.SetValueAsync(delayTime + 1); 31 | readDelayTime = await delayTimeParameter.GetValueAsync(); 32 | _ = readDelayTime.Should().Be(delayTime + 1); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tests/IntegrationTests/AudioNodeTests/DynamicsCompressorNodeTest.cs: -------------------------------------------------------------------------------- 1 | using KristofferStrube.Blazor.WebIDL.Exceptions; 2 | using Microsoft.JSInterop; 3 | 4 | namespace IntegrationTests.AudioNodeTests; 5 | 6 | public class DynamicsCompressorNodeTest : AudioNodeWithAudioNodeOptions 7 | { 8 | public override async Task CreateAsync(IJSRuntime jSRuntime, AudioContext context, DynamicsCompressorOptions? options) 9 | => await DynamicsCompressorNode.CreateAsync(JSRuntime, AudioContext, options); 10 | 11 | public override Dictionary UnsupportedChannelCountModes => new() 12 | { 13 | [ChannelCountMode.Max] = typeof(NotSupportedErrorException) 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /tests/IntegrationTests/AudioNodeTests/GainNodeTest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.JSInterop; 2 | 3 | namespace IntegrationTests.AudioNodeTests; 4 | 5 | public class GainNodeTest : AudioNodeWithAudioNodeOptions 6 | { 7 | public override async Task CreateAsync(IJSRuntime jSRuntime, AudioContext context, GainOptions? options) 8 | => await GainNode.CreateAsync(JSRuntime, AudioContext, options); 9 | } 10 | -------------------------------------------------------------------------------- /tests/IntegrationTests/AudioNodeTests/IIRFilterNodeTest.cs: -------------------------------------------------------------------------------- 1 | namespace IntegrationTests.AudioNodeTests; 2 | 3 | public class IIRFilterNodeTest : AudioNodeTest 4 | { 5 | public override async Task GetDefaultInstanceAsync() 6 | { 7 | return await IIRFilterNode.CreateAsync(JSRuntime, AudioContext, new IIRFilterOptions() 8 | { 9 | Feedforward = [1], 10 | Feedback = [1], 11 | }); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /tests/IntegrationTests/AudioNodeTests/MediaElementAudioSourceNodeTest.cs: -------------------------------------------------------------------------------- 1 | using KristofferStrube.Blazor.WebAudio.Options; 2 | using Microsoft.JSInterop; 3 | 4 | namespace IntegrationTests.AudioNodeTests; 5 | 6 | public class MediaElementAudioSourceNodeTest : AudioNodeTest 7 | { 8 | public override async Task GetDefaultInstanceAsync() 9 | { 10 | IJSObjectReference element = await EvaluationContext.GetAudioElementAyns(); 11 | return await MediaElementAudioSourceNode.CreateAsync(JSRuntime, AudioContext, new MediaElementAudioSourceOptions() 12 | { 13 | MediaElement = element 14 | }); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /tests/IntegrationTests/AudioNodeTests/MediaStreamAudioDestinationNodeTest.cs: -------------------------------------------------------------------------------- 1 | using KristofferStrube.Blazor.WebAudio.Options; 2 | using Microsoft.JSInterop; 3 | 4 | namespace IntegrationTests.AudioNodeTests; 5 | 6 | public class MediaStreamAudioDestinationNodeTest : AudioNodeWithAudioNodeOptions 7 | { 8 | public override async Task CreateAsync(IJSRuntime jSRuntime, AudioContext context, MediaStreamAudioDestinationOptions? options) 9 | => await MediaStreamAudioDestinationNode.CreateAsync(JSRuntime, AudioContext, options); 10 | } 11 | -------------------------------------------------------------------------------- /tests/IntegrationTests/AudioNodeTests/MediaStreamAudioSourceNodeTest.cs: -------------------------------------------------------------------------------- 1 | namespace IntegrationTests.AudioNodeTests; 2 | 3 | public class MediaStreamAudioSourceNodeTest : AudioNodeTest 4 | { 5 | public override async Task GetDefaultInstanceAsync() 6 | { 7 | return await MediaStreamAudioSourceNode.CreateAsync(JSRuntime, AudioContext, new MediaStreamAudioSourceOptions() 8 | { 9 | MediaStream = await EvaluationContext.GetMediaStream() 10 | }); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /tests/IntegrationTests/AudioNodeTests/OscillatorNodeTest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.JSInterop; 2 | 3 | namespace IntegrationTests.AudioNodeTests; 4 | 5 | public class OscillatorNodeTest : AudioNodeWithAudioNodeOptions 6 | { 7 | public override async Task CreateAsync(IJSRuntime jSRuntime, AudioContext context, OscillatorOptions? options) 8 | => await OscillatorNode.CreateAsync(JSRuntime, AudioContext, options); 9 | } 10 | -------------------------------------------------------------------------------- /tests/IntegrationTests/AudioNodeTests/PannerNodeTest.cs: -------------------------------------------------------------------------------- 1 | using KristofferStrube.Blazor.WebIDL.Exceptions; 2 | using Microsoft.JSInterop; 3 | 4 | namespace IntegrationTests.AudioNodeTests; 5 | 6 | public class PannerNodeTest : AudioNodeWithAudioNodeOptions 7 | { 8 | public override async Task CreateAsync(IJSRuntime jSRuntime, AudioContext context, PannerOptions? options) 9 | => await PannerNode.CreateAsync(JSRuntime, AudioContext, options); 10 | 11 | public override Dictionary UnsupportedChannelCountModes => new() 12 | { 13 | [ChannelCountMode.Max] = typeof(NotSupportedErrorException) 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /tests/IntegrationTests/AudioNodeTests/StereoPannerNodeTest.cs: -------------------------------------------------------------------------------- 1 | using KristofferStrube.Blazor.WebIDL.Exceptions; 2 | using Microsoft.JSInterop; 3 | 4 | namespace IntegrationTests.AudioNodeTests; 5 | 6 | public class StereoPannerNodeTest : AudioNodeWithAudioNodeOptions 7 | { 8 | public override async Task CreateAsync(IJSRuntime jSRuntime, AudioContext context, StereoPannerOptions? options) 9 | => await StereoPannerNode.CreateAsync(JSRuntime, AudioContext, options); 10 | 11 | public override Dictionary UnsupportedChannelCountModes => new() 12 | { 13 | [ChannelCountMode.Max] = typeof(NotSupportedErrorException) 14 | }; 15 | } 16 | -------------------------------------------------------------------------------- /tests/IntegrationTests/AudioNodeTests/WaveShaperNodeTest.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.JSInterop; 2 | 3 | namespace IntegrationTests.AudioNodeTests; 4 | 5 | public class WaveShaperNodeTest : AudioNodeWithAudioNodeOptions 6 | { 7 | public override async Task CreateAsync(IJSRuntime jSRuntime, AudioContext context, WaveShaperOptions? options) 8 | => await WaveShaperNode.CreateAsync(JSRuntime, AudioContext, options); 9 | } 10 | -------------------------------------------------------------------------------- /tests/IntegrationTests/Infrastructure/AudioContextEvaluationContext.cs: -------------------------------------------------------------------------------- 1 | using BlazorServer; 2 | using KristofferStrube.Blazor.MediaCaptureStreams; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using Microsoft.JSInterop; 5 | 6 | namespace IntegrationTests.Infrastructure; 7 | 8 | public class AudioContextEvaluationContext(IJSRuntime jSRuntime, IMediaDevicesService mediaDevicesService) : EvaluationContext 9 | { 10 | public IJSRuntime JSRuntime => jSRuntime; 11 | 12 | public IMediaDevicesService MediaDevicesService => mediaDevicesService; 13 | 14 | public static AudioContextEvaluationContext Create(IServiceProvider provider) 15 | { 16 | IMediaDevicesService mediaDevicesService = provider.GetRequiredService(); 17 | IJSRuntime jSRuntime = provider.GetRequiredService(); 18 | 19 | return new AudioContextEvaluationContext(jSRuntime, mediaDevicesService); 20 | } 21 | 22 | public async Task GetAudioContext() 23 | { 24 | AudioContext audioContext = await AudioContext.CreateAsync(jSRuntime); 25 | return audioContext; 26 | } 27 | 28 | public async Task GetMediaStream() 29 | { 30 | await using MediaDevices mediaDevices = await mediaDevicesService.GetMediaDevicesAsync(); 31 | MediaStream mediaStream = await mediaDevices.GetUserMediaAsync(new() { Audio = true }); 32 | return mediaStream; 33 | } 34 | 35 | public async Task GetAudioElementAyns() 36 | { 37 | return await JSRuntime.InvokeAsync("getAudioElement"); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /tests/IntegrationTests/IntegrationTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net9.0 5 | enable 6 | enable 7 | false 8 | true 9 | preview 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | all 21 | runtime; build; native; contentfiles; analyzers; buildtransitive 22 | 23 | 24 | all 25 | runtime; build; native; contentfiles; analyzers; buildtransitive 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | -------------------------------------------------------------------------------- /tests/IntegrationTests/Usings.cs: -------------------------------------------------------------------------------- 1 | global using KristofferStrube.Blazor.WebAudio; 2 | global using KristofferStrube.Blazor.WebAudio.IntegrationTests.Infrastructure; 3 | global using NUnit.Framework; 4 | -------------------------------------------------------------------------------- /tests/tests.sln: -------------------------------------------------------------------------------- 1 | Microsoft Visual Studio Solution File, Format Version 12.00 2 | # Visual Studio Version 17 3 | VisualStudioVersion = 17.5.2.0 4 | MinimumVisualStudioVersion = 10.0.40219.1 5 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BlazorServer", "BlazorServer\BlazorServer.csproj", "{A54C7275-E823-D105-A0DD-4D35CB594AFD}" 6 | EndProject 7 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "IntegrationTests", "IntegrationTests\IntegrationTests.csproj", "{59FDAA84-6701-32D0-9E5C-CA30D0B3B987}" 8 | EndProject 9 | Global 10 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 11 | Debug|Any CPU = Debug|Any CPU 12 | Release|Any CPU = Release|Any CPU 13 | EndGlobalSection 14 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 15 | {A54C7275-E823-D105-A0DD-4D35CB594AFD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 16 | {A54C7275-E823-D105-A0DD-4D35CB594AFD}.Debug|Any CPU.Build.0 = Debug|Any CPU 17 | {A54C7275-E823-D105-A0DD-4D35CB594AFD}.Release|Any CPU.ActiveCfg = Release|Any CPU 18 | {A54C7275-E823-D105-A0DD-4D35CB594AFD}.Release|Any CPU.Build.0 = Release|Any CPU 19 | {59FDAA84-6701-32D0-9E5C-CA30D0B3B987}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 20 | {59FDAA84-6701-32D0-9E5C-CA30D0B3B987}.Debug|Any CPU.Build.0 = Debug|Any CPU 21 | {59FDAA84-6701-32D0-9E5C-CA30D0B3B987}.Release|Any CPU.ActiveCfg = Release|Any CPU 22 | {59FDAA84-6701-32D0-9E5C-CA30D0B3B987}.Release|Any CPU.Build.0 = Release|Any CPU 23 | EndGlobalSection 24 | GlobalSection(SolutionProperties) = preSolution 25 | HideSolutionNode = FALSE 26 | EndGlobalSection 27 | GlobalSection(ExtensibilityGlobals) = postSolution 28 | SolutionGuid = {705E4106-ECBA-47B1-A459-A866AA21E2AE} 29 | EndGlobalSection 30 | EndGlobal 31 | --------------------------------------------------------------------------------