From f0e50acfa5e3ac826c0fe42553bda7f1d67676d2 Mon Sep 17 00:00:00 2001 From: Michael Bond Date: Tue, 9 Dec 2025 00:27:19 -0500 Subject: [PATCH 1/4] Remove project files --- .vscode/launch.json | 11 -- .vscode/tasks.json | 42 ----- SoundTest.code-workspace | 8 - SoundTest.sln | 31 ---- SoundTest/Components/App.razor | 18 --- SoundTest/Components/Layout/MainLayout.razor | 53 ------- .../Components/Layout/MainLayout.razor.cs | 28 ---- SoundTest/Components/Pages/Home.razor | 8 - SoundTest/Components/Pages/Home.razor.cs | 10 -- SoundTest/Components/SoundComponent.razor | 87 ---------- SoundTest/Components/SoundComponent.razor.cs | 149 ------------------ SoundTest/Components/SoundComponent.razor.js | 61 ------- SoundTest/Constants.cs | 22 --- SoundTest/GlobalUsings.cs | 10 -- SoundTest/JsInterop.cs | 34 ---- SoundTest/Program.cs | 8 - SoundTest/Properties/launchSettings.json | 41 ----- SoundTest/SoundTest.csproj | 28 ---- SoundTest/_Imports.razor | 13 -- SoundTest/wwwroot/css/app.css | 32 ---- SoundTest/wwwroot/icon-192.png | Bin 2626 -> 0 bytes SoundTest/wwwroot/icon-512.png | Bin 6311 -> 0 bytes SoundTest/wwwroot/index.html | 38 ----- SoundTest/wwwroot/manifest.json | 20 --- SoundTest/wwwroot/service-worker.js | 4 - SoundTest/wwwroot/service-worker.published.js | 48 ------ 26 files changed, 804 deletions(-) delete mode 100644 .vscode/launch.json delete mode 100644 .vscode/tasks.json delete mode 100644 SoundTest.code-workspace delete mode 100644 SoundTest.sln delete mode 100644 SoundTest/Components/App.razor delete mode 100644 SoundTest/Components/Layout/MainLayout.razor delete mode 100644 SoundTest/Components/Layout/MainLayout.razor.cs delete mode 100644 SoundTest/Components/Pages/Home.razor delete mode 100644 SoundTest/Components/Pages/Home.razor.cs delete mode 100644 SoundTest/Components/SoundComponent.razor delete mode 100644 SoundTest/Components/SoundComponent.razor.cs delete mode 100644 SoundTest/Components/SoundComponent.razor.js delete mode 100644 SoundTest/Constants.cs delete mode 100644 SoundTest/GlobalUsings.cs delete mode 100644 SoundTest/JsInterop.cs delete mode 100644 SoundTest/Program.cs delete mode 100644 SoundTest/Properties/launchSettings.json delete mode 100644 SoundTest/SoundTest.csproj delete mode 100644 SoundTest/_Imports.razor delete mode 100644 SoundTest/wwwroot/css/app.css delete mode 100644 SoundTest/wwwroot/icon-192.png delete mode 100644 SoundTest/wwwroot/icon-512.png delete mode 100644 SoundTest/wwwroot/index.html delete mode 100644 SoundTest/wwwroot/manifest.json delete mode 100644 SoundTest/wwwroot/service-worker.js delete mode 100644 SoundTest/wwwroot/service-worker.published.js diff --git a/.vscode/launch.json b/.vscode/launch.json deleted file mode 100644 index 88dc5ff..0000000 --- a/.vscode/launch.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "version": "0.2.0", - "configurations": [ - { - "name": "Launch and Debug Standalone Blazor WebAssembly App", - "type": "blazorwasm", - "request": "launch", - "cwd": "${workspaceFolder}/SoundTest" - } - ] -} \ No newline at end of file diff --git a/.vscode/tasks.json b/.vscode/tasks.json deleted file mode 100644 index f6bcb59..0000000 --- a/.vscode/tasks.json +++ /dev/null @@ -1,42 +0,0 @@ -{ - "version": "2.0.0", - "tasks": [ - { - "label": "build", - "command": "dotnet", - "type": "process", - "args": [ - "build", - "${workspaceFolder}/SoundTest/SoundTest.csproj", - "/property:GenerateFullPaths=true", - "/consoleloggerparameters:NoSummary" - ], - "problemMatcher": "$msCompile" - }, - { - "label": "publish", - "command": "dotnet", - "type": "process", - "args": [ - "publish", - "${workspaceFolder}/SoundTest/SoundTest.csproj", - "/property:GenerateFullPaths=true", - "/consoleloggerparameters:NoSummary" - ], - "problemMatcher": "$msCompile" - }, - { - "label": "watch", - "command": "dotnet", - "type": "process", - "args": [ - "watch", - "run", - "${workspaceFolder}/SoundTest/SoundTest.csproj", - "/property:GenerateFullPaths=true", - "/consoleloggerparameters:NoSummary" - ], - "problemMatcher": "$msCompile" - } - ] -} \ No newline at end of file diff --git a/SoundTest.code-workspace b/SoundTest.code-workspace deleted file mode 100644 index 876a149..0000000 --- a/SoundTest.code-workspace +++ /dev/null @@ -1,8 +0,0 @@ -{ - "folders": [ - { - "path": "." - } - ], - "settings": {} -} \ No newline at end of file diff --git a/SoundTest.sln b/SoundTest.sln deleted file mode 100644 index 2d7f60e..0000000 --- a/SoundTest.sln +++ /dev/null @@ -1,31 +0,0 @@ - -Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.0.31710.8 -MinimumVisualStudioVersion = 10.0.40219.1 -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{EFED9F50-B827-4097-875C-A2AA85F69403}" - ProjectSection(SolutionItems) = preProject - .editorconfig = .editorconfig - global.json = global.json - EndProjectSection -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SoundTest", "SoundTest\SoundTest.csproj", "{0BC06B08-73F8-4A3F-802D-C641B2BFD7A7}" -EndProject -Global - GlobalSection(SolutionConfigurationPlatforms) = preSolution - Debug|Any CPU = Debug|Any CPU - Release|Any CPU = Release|Any CPU - EndGlobalSection - GlobalSection(ProjectConfigurationPlatforms) = postSolution - {0BC06B08-73F8-4A3F-802D-C641B2BFD7A7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {0BC06B08-73F8-4A3F-802D-C641B2BFD7A7}.Debug|Any CPU.Build.0 = Debug|Any CPU - {0BC06B08-73F8-4A3F-802D-C641B2BFD7A7}.Release|Any CPU.ActiveCfg = Release|Any CPU - {0BC06B08-73F8-4A3F-802D-C641B2BFD7A7}.Release|Any CPU.Build.0 = Release|Any CPU - EndGlobalSection - GlobalSection(SolutionProperties) = preSolution - HideSolutionNode = FALSE - EndGlobalSection - GlobalSection(ExtensibilityGlobals) = postSolution - SolutionGuid = {0B9C045E-F681-447E-B97B-4BDCF49F4F80} - EndGlobalSection -EndGlobal diff --git a/SoundTest/Components/App.razor b/SoundTest/Components/App.razor deleted file mode 100644 index 79ecbc6..0000000 --- a/SoundTest/Components/App.razor +++ /dev/null @@ -1,18 +0,0 @@ - - - - - - - - Not found - - -

- Sorry, there's nothing at this address. -

-
-
-
diff --git a/SoundTest/Components/Layout/MainLayout.razor b/SoundTest/Components/Layout/MainLayout.razor deleted file mode 100644 index 84b61f9..0000000 --- a/SoundTest/Components/Layout/MainLayout.razor +++ /dev/null @@ -1,53 +0,0 @@ -@inherits LayoutComponentBase - - - - - - - - - - @AppTitle - - - @AppTitle - - - @AppTitle - - - - - - Source code on GitHub - - - - - - - @Body - - - diff --git a/SoundTest/Components/Layout/MainLayout.razor.cs b/SoundTest/Components/Layout/MainLayout.razor.cs deleted file mode 100644 index a380e77..0000000 --- a/SoundTest/Components/Layout/MainLayout.razor.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace SoundTest.Components.Layout; - -public partial class MainLayout -{ - private const string AppTitle = "Sound Test"; - - private bool isDarkMode; - private MudThemeProvider? mudThemeProvider; - - protected override async Task OnAfterRenderAsync(bool firstRender) - { - if (!firstRender || mudThemeProvider is null) - { - return; - } - - isDarkMode = await mudThemeProvider.GetSystemDarkModeAsync(); - await mudThemeProvider.WatchSystemDarkModeAsync(OnSystemPreferenceChanged); - StateHasChanged(); - } - - private Task OnSystemPreferenceChanged(bool newValue) - { - isDarkMode = newValue; - StateHasChanged(); - return Task.CompletedTask; - } -} diff --git a/SoundTest/Components/Pages/Home.razor b/SoundTest/Components/Pages/Home.razor deleted file mode 100644 index 2591b33..0000000 --- a/SoundTest/Components/Pages/Home.razor +++ /dev/null @@ -1,8 +0,0 @@ -@page "/" - - - Sound Test - - - diff --git a/SoundTest/Components/Pages/Home.razor.cs b/SoundTest/Components/Pages/Home.razor.cs deleted file mode 100644 index a291927..0000000 --- a/SoundTest/Components/Pages/Home.razor.cs +++ /dev/null @@ -1,10 +0,0 @@ -namespace SoundTest.Components.Pages; - -public partial class Home -{ - [Parameter, SupplyParameterFromQuery] - public int Type { get; set; } - - [Parameter, SupplyParameterFromQuery] - public int Frequency { get; set; } -} diff --git a/SoundTest/Components/SoundComponent.razor b/SoundTest/Components/SoundComponent.razor deleted file mode 100644 index f03c06e..0000000 --- a/SoundTest/Components/SoundComponent.razor +++ /dev/null @@ -1,87 +0,0 @@ - - Sound Test - - - - - - @foreach (var t in Enum.GetValues()) - { - - } - - - - Frequency = @Frequency.ToString() - - - - - @(isPlaying - ? "Stop" - : "Start") - - - Comfortable Tone - - - Neuro Tone - - - Perfect C - - - - @if (AudioDevices is { Count: > 0 }) - { - - } - - - diff --git a/SoundTest/Components/SoundComponent.razor.cs b/SoundTest/Components/SoundComponent.razor.cs deleted file mode 100644 index 6458203..0000000 --- a/SoundTest/Components/SoundComponent.razor.cs +++ /dev/null @@ -1,149 +0,0 @@ -namespace SoundTest.Components; - -public partial class SoundComponent(IJSRuntime jsRuntime, ISnackbar snackbar, NavigationManager navigation) -{ - private bool isPlaying; - private string? soundLink; - - private IJSObjectReference? module; - private bool isJsInitialized; - - private List? AudioDevices { get; set; } - - private string? SelectedDeviceId { get; set; } - - [Parameter] - public Types Type - { - get; - set - { - field = value; - _ = SetParametersAndUpdate(); - } - } - - [Parameter] - public int Frequency - { - get; - set - { - field = value switch - { - < MinFrequency => MinFrequency, - > MaxFrequency => MaxFrequency, - _ => value, - }; - _ = SetParametersAndUpdate(); - } - } - - private async Task SetParametersAndUpdate() - { - await SetParameters(); - UpdateUri(); - } - - private void UpdateUri() - { - var uri = navigation.GetUriWithQueryParameters(new Dictionary() { [nameof(Type)] = (int)Type, [nameof(Frequency)] = Frequency, }); - soundLink = uri; - } - - protected override async Task OnInitializedAsync() => await InitializeJs(); - - private async Task InitializeJs() - { - if (OperatingSystem.IsBrowser() && !isJsInitialized) - { - try - { - await JSHost.ImportAsync("soundtest.js", - $"../{nameof(Components)}/{nameof(SoundComponent)}.razor.js"); - - module = await jsRuntime.InvokeAsync( - "import", $"../{nameof(Components)}/{nameof(SoundComponent)}.razor.js"); - - isJsInitialized = true; - } - catch (Exception ex) - { - Debug.WriteLine(ex); - } - } - } - - private async Task SetParameters() - { - if (!isJsInitialized) - { - await InitializeJs(); - } - - JsInterop.SetParameters(Type.ToString().ToLower(), Frequency); - } - - private void StartPlaying() - { - JsInterop.StartPlaying(); - isPlaying = true; - } - - private void StopPlaying() - { - JsInterop.StopPlaying(); - isPlaying = false; - } - - private void CopySoundLink() - { - if (soundLink == null) - { - return; - } - - JsInterop.CopyTextToClipboard(soundLink); - snackbar.Configuration.PositionClass = Defaults.Classes.Position.BottomLeft; - snackbar.Add("Sound link copied to clipboard"); - } - - private async Task SetComfortableTone() - { - Type = Types.Sine; - Frequency = ComfortableFrequency; - await SetParameters(); - } - - private async Task SetNeuroTone() - { - Type = Types.Sine; - Frequency = NeuroFrequency; - await SetParameters(); - } - - private async Task SetPerfectCTone() - { - Type = Types.Sine; - Frequency = (int)PerfectCFrequency; - await SetParameters(); - } - - private async Task SetAudioDevice(string deviceId) - { - if (module is null) - { - return; - } - - await module.SetAudioDevice(deviceId); - } - - private void AudioDeviceChanged() - { - if (SelectedDeviceId is not null) - { - _ = SetAudioDevice(SelectedDeviceId); - } - } -} diff --git a/SoundTest/Components/SoundComponent.razor.js b/SoundTest/Components/SoundComponent.razor.js deleted file mode 100644 index 5699c01..0000000 --- a/SoundTest/Components/SoundComponent.razor.js +++ /dev/null @@ -1,61 +0,0 @@ -let context = null; -let osc = null; -let oscType = null; -let oscFreq = null; - -export function SetParameters(type = "sine", frequency = 440) { - oscType = type ?? "sine"; - oscFreq = frequency ?? 440; - if (osc !== null) { - osc.type = oscType ?? "sine"; - osc.frequency.value = oscFreq ?? 440; - } -} - -export function StartPlaying() { - // ReSharper disable once PossiblyUnassignedProperty - context = new (window.AudioContext || window.webkitAudioContext)(); - osc = context.createOscillator(); - osc.type = oscType ?? "sine"; - osc.frequency.value = oscFreq ?? 440; - osc.start(); - osc.connect(context.destination); -} - -export function StopPlaying() { - osc.stop(context.currentTime); - osc.disconnect(context.destination); - osc = null; - context = null; -} - -export function CopyTextToClipboard(textToCopy) { - navigator.clipboard.writeText(textToCopy) - .catch(function (error) { - alert(error); - }); -} - -export async function GetAudioOutputDevices() { - let devices = []; - - devices = (await navigator.mediaDevices.enumerateDevices()).filter(function (entry) { - return entry.kind === 'audiooutput' && entry.deviceId !== null && entry.deviceId !== ""; - }); - ; - - if (devices === null || devices.length === 0) { - await navigator.mediaDevices.getUserMedia({audio: true}); - devices = (await navigator.mediaDevices.enumerateDevices()).filter(function (entry) { - return entry.kind === 'audiooutput' && entry.deviceId !== null && entry.deviceId !== ""; - }); - ; - } - - return devices; -} - -export function SetAudioDevice(deviceId) { - console.log(`setting device to ${deviceId}`) - context.setSinkId(deviceId); -} diff --git a/SoundTest/Constants.cs b/SoundTest/Constants.cs deleted file mode 100644 index 4bfe60b..0000000 --- a/SoundTest/Constants.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace SoundTest; - -public static class Constants -{ - public const int MinFrequency = 1; - - public const int MaxFrequency = 1500; // 20154; - - public const int ComfortableFrequency = 528; - - public const int NeuroFrequency = 852; - - public const decimal PerfectCFrequency = 261.63M; // C4 - - public enum Types - { - Sine, - Square, - Triangle, - Sawtooth, - } -} diff --git a/SoundTest/GlobalUsings.cs b/SoundTest/GlobalUsings.cs deleted file mode 100644 index cb703b6..0000000 --- a/SoundTest/GlobalUsings.cs +++ /dev/null @@ -1,10 +0,0 @@ -global using Microsoft.AspNetCore.Components; -global using Microsoft.AspNetCore.Components.Web; -global using Microsoft.AspNetCore.Components.WebAssembly.Hosting; -global using Microsoft.JSInterop; -global using MudBlazor; -global using MudBlazor.Services; -global using SoundTest.Components; -global using System.Diagnostics; -global using System.Runtime.InteropServices.JavaScript; -global using static SoundTest.Constants; diff --git a/SoundTest/JsInterop.cs b/SoundTest/JsInterop.cs deleted file mode 100644 index a8274b6..0000000 --- a/SoundTest/JsInterop.cs +++ /dev/null @@ -1,34 +0,0 @@ -namespace SoundTest; - -// ReSharper disable once ClassNeverInstantiated.Global -public partial class JsInterop -{ - private const string JsFileName = "soundtest.js"; - - [JSImport(nameof(SetParameters), JsFileName)] - public static partial void SetParameters(string type, int frequency); - - [JSImport(nameof(StartPlaying), JsFileName)] - public static partial void StartPlaying(); - - [JSImport(nameof(StopPlaying), JsFileName)] - public static partial void StopPlaying(); - - [JSImport(nameof(CopyTextToClipboard), JsFileName)] - public static partial void CopyTextToClipboard(string text); -} - -// ReSharper disable once InconsistentNaming -public static class IJSObjectReferenceExtensions -{ - extension(IJSObjectReference jsModule) - { - public async Task> GetAudioOutputDevices() => - await jsModule.InvokeAsync>(nameof(GetAudioOutputDevices)); - - public async Task SetAudioDevice(string deviceId) => - await jsModule.InvokeVoidAsync(nameof(SetAudioDevice), deviceId); - } -} - -public record AudioDevice(string? DeviceId, string? Label, string? GroupId, bool IsDefault); diff --git a/SoundTest/Program.cs b/SoundTest/Program.cs deleted file mode 100644 index e4ac182..0000000 --- a/SoundTest/Program.cs +++ /dev/null @@ -1,8 +0,0 @@ -var builder = WebAssemblyHostBuilder.CreateDefault(args); - -builder.RootComponents.Add("#app"); -builder.RootComponents.Add("head::after"); - -builder.Services.AddMudServices(); - -await builder.Build().RunAsync(); diff --git a/SoundTest/Properties/launchSettings.json b/SoundTest/Properties/launchSettings.json deleted file mode 100644 index 86dcd87..0000000 --- a/SoundTest/Properties/launchSettings.json +++ /dev/null @@ -1,41 +0,0 @@ -{ - "$schema": "http://json.schemastore.org/launchsettings.json", - "iisSettings": { - "windowsAuthentication": false, - "anonymousAuthentication": true, - "iisExpress": { - "applicationUrl": "http://localhost:38548", - "sslPort": 44368 - } - }, - "profiles": { - "http": { - "commandName": "Project", - "dotnetRunMessages": true, - "launchBrowser": true, - "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", - "applicationUrl": "http://localhost:5046", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "https": { - "commandName": "Project", - "dotnetRunMessages": true, - "launchBrowser": true, - "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", - "applicationUrl": "https://localhost:7095;http://localhost:5046", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - }, - "IIS Express": { - "commandName": "IISExpress", - "launchBrowser": true, - "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", - "environmentVariables": { - "ASPNETCORE_ENVIRONMENT": "Development" - } - } - } -} diff --git a/SoundTest/SoundTest.csproj b/SoundTest/SoundTest.csproj deleted file mode 100644 index b6e5cdb..0000000 --- a/SoundTest/SoundTest.csproj +++ /dev/null @@ -1,28 +0,0 @@ - - - - net10.0 - enable - enable - false - false - true - service-worker-assets.js - true - - - - true - - - - - - - - - - - - - diff --git a/SoundTest/_Imports.razor b/SoundTest/_Imports.razor deleted file mode 100644 index 3fe648b..0000000 --- a/SoundTest/_Imports.razor +++ /dev/null @@ -1,13 +0,0 @@ -@using System.Net.Http -@using System.Net.Http.Json -@using Microsoft.AspNetCore.Components.Forms -@using Microsoft.AspNetCore.Components.Routing -@using Microsoft.AspNetCore.Components.Web -@using Microsoft.AspNetCore.Components.Web.Virtualization -@using Microsoft.AspNetCore.Components.WebAssembly.Http -@using Microsoft.JSInterop -@using MudBlazor -@using SoundTest -@using SoundTest.Components -@using SoundTest.Components.Layout -@using static SoundTest.Constants diff --git a/SoundTest/wwwroot/css/app.css b/SoundTest/wwwroot/css/app.css deleted file mode 100644 index 2f5fbaf..0000000 --- a/SoundTest/wwwroot/css/app.css +++ /dev/null @@ -1,32 +0,0 @@ -.loading-progress { - position: relative; - display: block; - width: 8rem; - height: 8rem; - margin: 20vh auto 1rem auto; -} - -.loading-progress circle { - fill: none; - stroke: #e0e0e0; - stroke-width: 0.6rem; - transform-origin: 50% 50%; - transform: rotate(-90deg); -} - -.loading-progress circle:last-child { - stroke: #1b6ec2; - stroke-dasharray: calc(3.141 * var(--blazor-load-percentage, 0%) * 0.8), 500%; - transition: stroke-dasharray 0.05s ease-in-out; -} - -.loading-progress-text { - position: absolute; - text-align: center; - font-weight: bold; - inset: calc(20vh + 3.25rem) 0 auto 0.2rem; -} - -.loading-progress-text:after { - content: var(--blazor-load-percentage-text, "Loading"); -} diff --git a/SoundTest/wwwroot/icon-192.png b/SoundTest/wwwroot/icon-192.png deleted file mode 100644 index 166f56da7612ea74df6a297154c8d281a4f28a14..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 2626 zcmV-I3cdA-P)v0A9xRwxP|bki~~&uFk>U z#P+PQh zyZ;-jwXKqnKbb6)@RaxQz@vm={%t~VbaZrdbaZrdbaeEeXj>~BG?&`J0XrqR#sSlO zg~N5iUk*15JibvlR1f^^1czzNKWvoJtc!Sj*G37QXbZ8LeD{Fzxgdv#Q{x}ytfZ5q z+^k#NaEp>zX_8~aSaZ`O%B9C&YLHb(mNtgGD&Kezd5S@&C=n~Uy1NWHM`t07VQP^MopUXki{2^#ryd94>UJMYW|(#4qV`kb7eD)Q=~NN zaVIRi@|TJ!Rni8J=5DOutQ#bEyMVr8*;HU|)MEKmVC+IOiDi9y)vz=rdtAUHW$yjt zrj3B7v(>exU=IrzC<+?AE=2vI;%fafM}#ShGDZx=0Nus5QHKdyb9pw&4>4XCpa-o?P(Gnco1CGX|U> z$f+_tA3+V~<{MU^A%eP!8R*-sD9y<>Jc7A(;aC5hVbs;kX9&Sa$JMG!W_BLFQa*hM zri__C@0i0U1X#?)Y=)>JpvTnY6^s;fu#I}K9u>OldV}m!Ch`d1Vs@v9 zb}w(!TvOmSzmMBa9gYvD4xocL2r0ds6%Hs>Z& z#7#o9PGHDmfG%JQq`O5~dt|MAQN@2wyJw_@``7Giyy(yyk(m8U*kk5$X1^;3$a3}N^Lp6hE5!#8l z#~NYHmKAs6IAe&A;bvM8OochRmXN>`D`{N$%#dZCRxp4-dJ?*3P}}T`tYa3?zz5BA zTu7uE#GsDpZ$~j9q=Zq!LYjLbZPXFILZK4?S)C-zE1(dC2d<7nO4-nSCbV#9E|E1MM|V<9>i4h?WX*r*ul1 z5#k6;po8z=fdMiVVz*h+iaTlz#WOYmU^SX5#97H~B32s-#4wk<1NTN#g?LrYieCu> zF7pbOLR;q2D#Q`^t%QcY06*X-jM+ei7%ZuanUTH#9Y%FBi*Z#22({_}3^=BboIsbg zR0#jJ>9QR8SnmtSS6x($?$}6$x+q)697#m${Z@G6Ujf=6iO^S}7P`q8DkH!IHd4lB zDzwxt3BHsPAcXFFY^Fj}(073>NL_$A%v2sUW(CRutd%{G`5ow?L`XYSO*Qu?x+Gzv zBtR}Y6`XF4xX7)Z04D+fH;TMapdQFFameUuHL34NN)r@aF4RO%x&NApeWGtr#mG~M z6sEIZS;Uj1HB1*0hh=O@0q1=Ia@L>-tETu-3n(op+97E z#&~2xggrl(LA|giII;RwBlX2^Q`B{_t}gxNL;iB11gEPC>v` zb4SJ;;BFOB!{chn>?cCeGDKuqI0+!skyWTn*k!WiPNBf=8rn;@y%( znhq%8fj2eAe?`A5mP;TE&iLEmQ^xV%-kmC-8mWao&EUK_^=GW-Y3z ksi~={si~={skwfB0gq6itke#r1ONa407*qoM6N<$g11Kq@c;k- diff --git a/SoundTest/wwwroot/icon-512.png b/SoundTest/wwwroot/icon-512.png deleted file mode 100644 index c2dd4842dc93df73c322218ee03eca142a19a338..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6311 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 diff --git a/SoundTest/wwwroot/index.html b/SoundTest/wwwroot/index.html deleted file mode 100644 index 6564f10..0000000 --- a/SoundTest/wwwroot/index.html +++ /dev/null @@ -1,38 +0,0 @@ - - - - - - - Sound Test - - - - - - - - - - - - -
- - - - -
-
- -
- An unhandled error has occurred. - Reload - 🗙 -
- - - - - - diff --git a/SoundTest/wwwroot/manifest.json b/SoundTest/wwwroot/manifest.json deleted file mode 100644 index 197f273..0000000 --- a/SoundTest/wwwroot/manifest.json +++ /dev/null @@ -1,20 +0,0 @@ -{ - "name": "Sound Test", - "short_name": "SoundTest", - "start_url": "./", - "display": "standalone", - "background_color": "#ffffff", - "theme_color": "#03173d", - "icons": [ - { - "src": "icon-512.png", - "type": "image/png", - "sizes": "512x512" - }, - { - "src": "icon-192.png", - "type": "image/png", - "sizes": "192x192" - } - ] -} diff --git a/SoundTest/wwwroot/service-worker.js b/SoundTest/wwwroot/service-worker.js deleted file mode 100644 index fe614da..0000000 --- a/SoundTest/wwwroot/service-worker.js +++ /dev/null @@ -1,4 +0,0 @@ -// In development, always fetch from the network and do not enable offline support. -// This is because caching would make development more difficult (changes would not -// be reflected on the first load after each change). -self.addEventListener('fetch', () => { }); diff --git a/SoundTest/wwwroot/service-worker.published.js b/SoundTest/wwwroot/service-worker.published.js deleted file mode 100644 index 18209f3..0000000 --- a/SoundTest/wwwroot/service-worker.published.js +++ /dev/null @@ -1,48 +0,0 @@ -// Caution! Be sure you understand the caveats before publishing an application with -// offline support. See https://aka.ms/blazor-offline-considerations - -self.importScripts('./service-worker-assets.js'); -self.addEventListener('install', event => event.waitUntil(onInstall(event))); -self.addEventListener('activate', event => event.waitUntil(onActivate(event))); -self.addEventListener('fetch', event => event.respondWith(onFetch(event))); - -const cacheNamePrefix = 'offline-cache-'; -const cacheName = `${cacheNamePrefix}${self.assetsManifest.version}`; -const offlineAssetsInclude = [/\.dll$/, /\.pdb$/, /\.wasm/, /\.html/, /\.js$/, /\.json$/, /\.css$/, /\.woff$/, /\.png$/, /\.jpe?g$/, /\.gif$/, /\.ico$/, /\.blat$/, /\.dat$/]; -const offlineAssetsExclude = [/^service-worker\.js$/]; - -async function onInstall(event) { - console.info('Service worker: Install'); - - // Fetch and cache all matching items from the assets manifest - const assetsRequests = self.assetsManifest.assets - .filter(asset => offlineAssetsInclude.some(pattern => pattern.test(asset.url))) - .filter(asset => !offlineAssetsExclude.some(pattern => pattern.test(asset.url))) - .map(asset => new Request(asset.url, { integrity: asset.hash })); - await caches.open(cacheName).then(cache => cache.addAll(assetsRequests)); -} - -async function onActivate(event) { - console.info('Service worker: Activate'); - - // Delete unused caches - const cacheKeys = await caches.keys(); - await Promise.all(cacheKeys - .filter(key => key.startsWith(cacheNamePrefix) && key !== cacheName) - .map(key => caches.delete(key))); -} - -async function onFetch(event) { - let cachedResponse = null; - if (event.request.method === 'GET') { - // For all navigation requests, try to serve index.html from cache - // If you need some URLs to be server-rendered, edit the following check to exclude those URLs - const shouldServeIndexHtml = event.request.mode === 'navigate'; - - const request = shouldServeIndexHtml ? 'index.html' : event.request; - const cache = await caches.open(cacheName); - cachedResponse = await cache.match(request); - } - - return cachedResponse || fetch(event.request); -} From 98e6f860cbea798af0e0a1f7d031bd51a3ce4222 Mon Sep 17 00:00:00 2001 From: Michael Bond Date: Tue, 9 Dec 2025 00:53:55 -0500 Subject: [PATCH 2/4] Initial commit: Blazor WASM SoundTest app with PWA support Introduces the SoundTest Blazor WebAssembly app for generating and playing test tones with selectable type and frequency. Features MudBlazor UI, audio device selection, shareable sound links, and JS interop for audio and clipboard. Includes PWA setup, service workers, icons, and responsive layout. --- SoundTest.slnx | 7 + SoundTest/App.razor | 6 + SoundTest/Components/SoundComponent.razor | 83 ++++++++++ SoundTest/Components/SoundComponent.razor.cs | 149 ++++++++++++++++++ SoundTest/Components/SoundComponent.razor.js | 61 +++++++ SoundTest/Constants.cs | 22 +++ SoundTest/GlobalUsings.cs | 12 ++ SoundTest/JsInterop.cs | 34 ++++ SoundTest/Layout/MainLayout.razor | 53 +++++++ SoundTest/Layout/MainLayout.razor.cs | 28 ++++ SoundTest/Pages/Home.razor | 8 + SoundTest/Pages/Home.razor.cs | 10 ++ SoundTest/Pages/NotFound.razor | 5 + SoundTest/Program.cs | 8 + SoundTest/Properties/launchSettings.json | 25 +++ SoundTest/SoundTest.csproj | 29 ++++ SoundTest/_Imports.razor | 15 ++ SoundTest/wwwroot/css/app.css | 33 ++++ SoundTest/wwwroot/icon-192.png | Bin 0 -> 2626 bytes SoundTest/wwwroot/icon-512.png | Bin 0 -> 6311 bytes SoundTest/wwwroot/index.html | 41 +++++ SoundTest/wwwroot/manifest.webmanifest | 22 +++ SoundTest/wwwroot/service-worker.js | 4 + SoundTest/wwwroot/service-worker.published.js | 55 +++++++ 24 files changed, 710 insertions(+) create mode 100644 SoundTest.slnx create mode 100644 SoundTest/App.razor create mode 100644 SoundTest/Components/SoundComponent.razor create mode 100644 SoundTest/Components/SoundComponent.razor.cs create mode 100644 SoundTest/Components/SoundComponent.razor.js create mode 100644 SoundTest/Constants.cs create mode 100644 SoundTest/GlobalUsings.cs create mode 100644 SoundTest/JsInterop.cs create mode 100644 SoundTest/Layout/MainLayout.razor create mode 100644 SoundTest/Layout/MainLayout.razor.cs create mode 100644 SoundTest/Pages/Home.razor create mode 100644 SoundTest/Pages/Home.razor.cs create mode 100644 SoundTest/Pages/NotFound.razor create mode 100644 SoundTest/Program.cs create mode 100644 SoundTest/Properties/launchSettings.json create mode 100644 SoundTest/SoundTest.csproj create mode 100644 SoundTest/_Imports.razor create mode 100644 SoundTest/wwwroot/css/app.css create mode 100644 SoundTest/wwwroot/icon-192.png create mode 100644 SoundTest/wwwroot/icon-512.png create mode 100644 SoundTest/wwwroot/index.html create mode 100644 SoundTest/wwwroot/manifest.webmanifest create mode 100644 SoundTest/wwwroot/service-worker.js create mode 100644 SoundTest/wwwroot/service-worker.published.js diff --git a/SoundTest.slnx b/SoundTest.slnx new file mode 100644 index 0000000..26b1f03 --- /dev/null +++ b/SoundTest.slnx @@ -0,0 +1,7 @@ + + + + + + + diff --git a/SoundTest/App.razor b/SoundTest/App.razor new file mode 100644 index 0000000..a8a79e5 --- /dev/null +++ b/SoundTest/App.razor @@ -0,0 +1,6 @@ + + + + + + diff --git a/SoundTest/Components/SoundComponent.razor b/SoundTest/Components/SoundComponent.razor new file mode 100644 index 0000000..4ed6057 --- /dev/null +++ b/SoundTest/Components/SoundComponent.razor @@ -0,0 +1,83 @@ + + Sound Test + + + + + + @foreach (var t in Enum.GetValues()) + { + + } + + + + Frequency = @Frequency.ToString() + + + + + @(isPlaying ? "Stop" : "Start") + + + Comfortable Tone + + + Neuro Tone + + + Perfect C + + + + @if (AudioDevices is { Count: > 0 }) + { + + } + + + diff --git a/SoundTest/Components/SoundComponent.razor.cs b/SoundTest/Components/SoundComponent.razor.cs new file mode 100644 index 0000000..1257a43 --- /dev/null +++ b/SoundTest/Components/SoundComponent.razor.cs @@ -0,0 +1,149 @@ +namespace SoundTest.Components; + +public partial class SoundComponent(IJSRuntime jsRuntime, ISnackbar snackbar, NavigationManager navigation) +{ + private bool isPlaying; + private string? soundLink; + + private IJSObjectReference? module; + private bool isJsInitialized; + + private List? AudioDevices { get; set; } + + private string? SelectedDeviceId { get; set; } + + [Parameter] + public Types Type + { + get; + set + { + field = value; + _ = SetParametersAndUpdate(); + } + } + + [Parameter] + public int Frequency + { + get; + set + { + field = value switch + { + < MinFrequency => MinFrequency, + > MaxFrequency => MaxFrequency, + _ => value, + }; + _ = SetParametersAndUpdate(); + } + } + + private async Task SetParametersAndUpdate() + { + await SetParameters(); + UpdateUri(); + } + + private void UpdateUri() + { + var uri = navigation.GetUriWithQueryParameters(new Dictionary() { [nameof(Type)] = (int)Type, [nameof(Frequency)] = Frequency, }); + soundLink = uri; + } + + protected override async Task OnInitializedAsync() => await InitializeJs(); + + private async Task InitializeJs() + { + if (OperatingSystem.IsBrowser() && !isJsInitialized) + { + try + { + await JSHost.ImportAsync("soundtest.js", + $"../{nameof(Components)}/{nameof(SoundComponent)}.razor.js"); + + module = await jsRuntime.InvokeAsync( + "import", $"../{nameof(Components)}/{nameof(SoundComponent)}.razor.js"); + + isJsInitialized = true; + } + catch (Exception ex) + { + Debug.WriteLine(ex); + } + } + } + + private async Task SetParameters() + { + if (!isJsInitialized) + { + await InitializeJs(); + } + + JsInterop.SetParameters(Type.ToString().ToLower(), Frequency); + } + + private void StartPlaying() + { + JsInterop.StartPlaying(); + isPlaying = true; + } + + private void StopPlaying() + { + JsInterop.StopPlaying(); + isPlaying = false; + } + + private void CopySoundLink() + { + if (soundLink == null) + { + return; + } + + JsInterop.CopyTextToClipboard(soundLink); + snackbar.Configuration.PositionClass = Defaults.Classes.Position.BottomLeft; + snackbar.Add("Sound link copied to clipboard"); + } + + private async Task SetComfortableTone() + { + Type = Types.Sine; + Frequency = ComfortableFrequency; + await SetParameters(); + } + + private async Task SetNeuroTone() + { + Type = Types.Sine; + Frequency = NeuroFrequency; + await SetParameters(); + } + + private async Task SetPerfectCTone() + { + Type = Types.Sine; + Frequency = (int)PerfectCFrequency; + await SetParameters(); + } + + private async Task SetAudioDevice(string deviceId) + { + if (module is null) + { + return; + } + + await module.SetAudioDevice(deviceId); + } + + private void AudioDeviceChanged() + { + if (SelectedDeviceId is not null) + { + _ = SetAudioDevice(SelectedDeviceId); + } + } +} diff --git a/SoundTest/Components/SoundComponent.razor.js b/SoundTest/Components/SoundComponent.razor.js new file mode 100644 index 0000000..c56a0e4 --- /dev/null +++ b/SoundTest/Components/SoundComponent.razor.js @@ -0,0 +1,61 @@ +let context = null; +let osc = null; +let oscType = null; +let oscFreq = null; + +export function SetParameters(type = "sine", frequency = 440) { + oscType = type ?? "sine"; + oscFreq = frequency ?? 440; + if (osc !== null) { + osc.type = oscType ?? "sine"; + osc.frequency.value = oscFreq ?? 440; + } +} + +export function StartPlaying() { + // ReSharper disable once PossiblyUnassignedProperty + context = new (window.AudioContext || window.webkitAudioContext)(); + osc = context.createOscillator(); + osc.type = oscType ?? "sine"; + osc.frequency.value = oscFreq ?? 440; + osc.start(); + osc.connect(context.destination); +} + +export function StopPlaying() { + osc.stop(context.currentTime); + osc.disconnect(context.destination); + osc = null; + context = null; +} + +export function CopyTextToClipboard(textToCopy) { + navigator.clipboard.writeText(textToCopy) + .catch(function (error) { + alert(error); + }); +} + +export async function GetAudioOutputDevices() { + let devices = []; + + devices = (await navigator.mediaDevices.enumerateDevices()).filter(function (entry) { + return entry.kind === 'audiooutput' && entry.deviceId !== null && entry.deviceId !== ""; + }); + ; + + if (devices === null || devices.length === 0) { + await navigator.mediaDevices.getUserMedia({ audio: true }); + devices = (await navigator.mediaDevices.enumerateDevices()).filter(function (entry) { + return entry.kind === 'audiooutput' && entry.deviceId !== null && entry.deviceId !== ""; + }); + ; + } + + return devices; +} + +export function SetAudioDevice(deviceId) { + console.log(`setting device to ${deviceId}`) + context.setSinkId(deviceId); +} diff --git a/SoundTest/Constants.cs b/SoundTest/Constants.cs new file mode 100644 index 0000000..4bfe60b --- /dev/null +++ b/SoundTest/Constants.cs @@ -0,0 +1,22 @@ +namespace SoundTest; + +public static class Constants +{ + public const int MinFrequency = 1; + + public const int MaxFrequency = 1500; // 20154; + + public const int ComfortableFrequency = 528; + + public const int NeuroFrequency = 852; + + public const decimal PerfectCFrequency = 261.63M; // C4 + + public enum Types + { + Sine, + Square, + Triangle, + Sawtooth, + } +} diff --git a/SoundTest/GlobalUsings.cs b/SoundTest/GlobalUsings.cs new file mode 100644 index 0000000..37410d7 --- /dev/null +++ b/SoundTest/GlobalUsings.cs @@ -0,0 +1,12 @@ +global using Microsoft.AspNetCore.Components; +global using Microsoft.AspNetCore.Components.Web; +global using Microsoft.AspNetCore.Components.WebAssembly.Hosting; +global using Microsoft.JSInterop; +global using MudBlazor; +global using MudBlazor.Services; +global using SoundTest.Layout; +global using SoundTest.Pages; +global using System.Diagnostics; +global using System.Runtime.InteropServices.JavaScript; +global using static SoundTest.Constants; +global using SoundTest; diff --git a/SoundTest/JsInterop.cs b/SoundTest/JsInterop.cs new file mode 100644 index 0000000..a8274b6 --- /dev/null +++ b/SoundTest/JsInterop.cs @@ -0,0 +1,34 @@ +namespace SoundTest; + +// ReSharper disable once ClassNeverInstantiated.Global +public partial class JsInterop +{ + private const string JsFileName = "soundtest.js"; + + [JSImport(nameof(SetParameters), JsFileName)] + public static partial void SetParameters(string type, int frequency); + + [JSImport(nameof(StartPlaying), JsFileName)] + public static partial void StartPlaying(); + + [JSImport(nameof(StopPlaying), JsFileName)] + public static partial void StopPlaying(); + + [JSImport(nameof(CopyTextToClipboard), JsFileName)] + public static partial void CopyTextToClipboard(string text); +} + +// ReSharper disable once InconsistentNaming +public static class IJSObjectReferenceExtensions +{ + extension(IJSObjectReference jsModule) + { + public async Task> GetAudioOutputDevices() => + await jsModule.InvokeAsync>(nameof(GetAudioOutputDevices)); + + public async Task SetAudioDevice(string deviceId) => + await jsModule.InvokeVoidAsync(nameof(SetAudioDevice), deviceId); + } +} + +public record AudioDevice(string? DeviceId, string? Label, string? GroupId, bool IsDefault); diff --git a/SoundTest/Layout/MainLayout.razor b/SoundTest/Layout/MainLayout.razor new file mode 100644 index 0000000..84b61f9 --- /dev/null +++ b/SoundTest/Layout/MainLayout.razor @@ -0,0 +1,53 @@ +@inherits LayoutComponentBase + + + + + + + + + + @AppTitle + + + @AppTitle + + + @AppTitle + + + + + + Source code on GitHub + + + + + + + @Body + + + diff --git a/SoundTest/Layout/MainLayout.razor.cs b/SoundTest/Layout/MainLayout.razor.cs new file mode 100644 index 0000000..eb7e196 --- /dev/null +++ b/SoundTest/Layout/MainLayout.razor.cs @@ -0,0 +1,28 @@ +namespace SoundTest.Layout; + +public partial class MainLayout +{ + private const string AppTitle = "Sound Test"; + + private bool isDarkMode; + private MudThemeProvider? mudThemeProvider; + + protected override async Task OnAfterRenderAsync(bool firstRender) + { + if (!firstRender || mudThemeProvider is null) + { + return; + } + + isDarkMode = await mudThemeProvider.GetSystemDarkModeAsync(); + await mudThemeProvider.WatchSystemDarkModeAsync(OnSystemPreferenceChanged); + StateHasChanged(); + } + + private Task OnSystemPreferenceChanged(bool newValue) + { + isDarkMode = newValue; + StateHasChanged(); + return Task.CompletedTask; + } +} diff --git a/SoundTest/Pages/Home.razor b/SoundTest/Pages/Home.razor new file mode 100644 index 0000000..2591b33 --- /dev/null +++ b/SoundTest/Pages/Home.razor @@ -0,0 +1,8 @@ +@page "/" + + + Sound Test + + + diff --git a/SoundTest/Pages/Home.razor.cs b/SoundTest/Pages/Home.razor.cs new file mode 100644 index 0000000..15b2a00 --- /dev/null +++ b/SoundTest/Pages/Home.razor.cs @@ -0,0 +1,10 @@ +namespace SoundTest.Pages; + +public partial class Home +{ + [Parameter, SupplyParameterFromQuery] + public int Type { get; set; } + + [Parameter, SupplyParameterFromQuery] + public int Frequency { get; set; } +} diff --git a/SoundTest/Pages/NotFound.razor b/SoundTest/Pages/NotFound.razor new file mode 100644 index 0000000..917ada1 --- /dev/null +++ b/SoundTest/Pages/NotFound.razor @@ -0,0 +1,5 @@ +@page "/not-found" +@layout MainLayout + +

Not Found

+

Sorry, the content you are looking for does not exist.

\ No newline at end of file diff --git a/SoundTest/Program.cs b/SoundTest/Program.cs new file mode 100644 index 0000000..e4ac182 --- /dev/null +++ b/SoundTest/Program.cs @@ -0,0 +1,8 @@ +var builder = WebAssemblyHostBuilder.CreateDefault(args); + +builder.RootComponents.Add("#app"); +builder.RootComponents.Add("head::after"); + +builder.Services.AddMudServices(); + +await builder.Build().RunAsync(); diff --git a/SoundTest/Properties/launchSettings.json b/SoundTest/Properties/launchSettings.json new file mode 100644 index 0000000..c8a976d --- /dev/null +++ b/SoundTest/Properties/launchSettings.json @@ -0,0 +1,25 @@ +{ + "$schema": "https://json.schemastore.org/launchsettings.json", + "profiles": { + "http": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", + "applicationUrl": "http://localhost:5129", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + }, + "https": { + "commandName": "Project", + "dotnetRunMessages": true, + "launchBrowser": true, + "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}", + "applicationUrl": "https://localhost:7079;http://localhost:5129", + "environmentVariables": { + "ASPNETCORE_ENVIRONMENT": "Development" + } + } + } +} diff --git a/SoundTest/SoundTest.csproj b/SoundTest/SoundTest.csproj new file mode 100644 index 0000000..b3bfb7b --- /dev/null +++ b/SoundTest/SoundTest.csproj @@ -0,0 +1,29 @@ + + + + net10.0 + enable + enable + true + false + false + true + service-worker-assets.js + true + + + + true + + + + + + + + + + + + + diff --git a/SoundTest/_Imports.razor b/SoundTest/_Imports.razor new file mode 100644 index 0000000..563a926 --- /dev/null +++ b/SoundTest/_Imports.razor @@ -0,0 +1,15 @@ +@using System.Net.Http +@using System.Net.Http.Json +@using Microsoft.AspNetCore.Components.Forms +@using Microsoft.AspNetCore.Components.Routing +@using Microsoft.AspNetCore.Components.Web +@using Microsoft.AspNetCore.Components.Web.Virtualization +@using Microsoft.AspNetCore.Components.WebAssembly.Http +@using Microsoft.JSInterop +@using SoundTest +@using SoundTest.Components +@using SoundTest.Layout +@using SoundTest.Pages +@using MudBlazor + +@using static SoundTest.Constants diff --git a/SoundTest/wwwroot/css/app.css b/SoundTest/wwwroot/css/app.css new file mode 100644 index 0000000..7baf393 --- /dev/null +++ b/SoundTest/wwwroot/css/app.css @@ -0,0 +1,33 @@ +.loading-progress { + position: absolute; + display: block; + width: 8rem; + height: 8rem; + inset: 20vh 0 auto 0; + margin: 0 auto 0 auto; +} + + .loading-progress circle { + fill: none; + stroke: #e0e0e0; + stroke-width: 0.6rem; + transform-origin: 50% 50%; + transform: rotate(-90deg); + } + + .loading-progress circle:last-child { + stroke: #1b6ec2; + stroke-dasharray: calc(3.141 * var(--blazor-load-percentage, 0%) * 0.8), 500%; + transition: stroke-dasharray 0.05s ease-in-out; + } + +.loading-progress-text { + position: absolute; + text-align: center; + font-weight: bold; + inset: calc(20vh + 3.25rem) 0 auto 0.2rem; +} + + .loading-progress-text:after { + content: var(--blazor-load-percentage-text, "Loading"); + } diff --git a/SoundTest/wwwroot/icon-192.png b/SoundTest/wwwroot/icon-192.png new file mode 100644 index 0000000000000000000000000000000000000000..166f56da7612ea74df6a297154c8d281a4f28a14 GIT binary patch literal 2626 zcmV-I3cdA-P)v0A9xRwxP|bki~~&uFk>U z#P+PQh zyZ;-jwXKqnKbb6)@RaxQz@vm={%t~VbaZrdbaZrdbaeEeXj>~BG?&`J0XrqR#sSlO zg~N5iUk*15JibvlR1f^^1czzNKWvoJtc!Sj*G37QXbZ8LeD{Fzxgdv#Q{x}ytfZ5q z+^k#NaEp>zX_8~aSaZ`O%B9C&YLHb(mNtgGD&Kezd5S@&C=n~Uy1NWHM`t07VQP^MopUXki{2^#ryd94>UJMYW|(#4qV`kb7eD)Q=~NN zaVIRi@|TJ!Rni8J=5DOutQ#bEyMVr8*;HU|)MEKmVC+IOiDi9y)vz=rdtAUHW$yjt zrj3B7v(>exU=IrzC<+?AE=2vI;%fafM}#ShGDZx=0Nus5QHKdyb9pw&4>4XCpa-o?P(Gnco1CGX|U> z$f+_tA3+V~<{MU^A%eP!8R*-sD9y<>Jc7A(;aC5hVbs;kX9&Sa$JMG!W_BLFQa*hM zri__C@0i0U1X#?)Y=)>JpvTnY6^s;fu#I}K9u>OldV}m!Ch`d1Vs@v9 zb}w(!TvOmSzmMBa9gYvD4xocL2r0ds6%Hs>Z& z#7#o9PGHDmfG%JQq`O5~dt|MAQN@2wyJw_@``7Giyy(yyk(m8U*kk5$X1^;3$a3}N^Lp6hE5!#8l z#~NYHmKAs6IAe&A;bvM8OochRmXN>`D`{N$%#dZCRxp4-dJ?*3P}}T`tYa3?zz5BA zTu7uE#GsDpZ$~j9q=Zq!LYjLbZPXFILZK4?S)C-zE1(dC2d<7nO4-nSCbV#9E|E1MM|V<9>i4h?WX*r*ul1 z5#k6;po8z=fdMiVVz*h+iaTlz#WOYmU^SX5#97H~B32s-#4wk<1NTN#g?LrYieCu> zF7pbOLR;q2D#Q`^t%QcY06*X-jM+ei7%ZuanUTH#9Y%FBi*Z#22({_}3^=BboIsbg zR0#jJ>9QR8SnmtSS6x($?$}6$x+q)697#m${Z@G6Ujf=6iO^S}7P`q8DkH!IHd4lB zDzwxt3BHsPAcXFFY^Fj}(073>NL_$A%v2sUW(CRutd%{G`5ow?L`XYSO*Qu?x+Gzv zBtR}Y6`XF4xX7)Z04D+fH;TMapdQFFameUuHL34NN)r@aF4RO%x&NApeWGtr#mG~M z6sEIZS;Uj1HB1*0hh=O@0q1=Ia@L>-tETu-3n(op+97E z#&~2xggrl(LA|giII;RwBlX2^Q`B{_t}gxNL;iB11gEPC>v` zb4SJ;;BFOB!{chn>?cCeGDKuqI0+!skyWTn*k!WiPNBf=8rn;@y%( znhq%8fj2eAe?`A5mP;TE&iLEmQ^xV%-kmC-8mWao&EUK_^=GW-Y3z ksi~={si~={skwfB0gq6itke#r1ONa407*qoM6N<$g11Kq@c;k- literal 0 HcmV?d00001 diff --git a/SoundTest/wwwroot/icon-512.png b/SoundTest/wwwroot/icon-512.png new file mode 100644 index 0000000000000000000000000000000000000000..c2dd4842dc93df73c322218ee03eca142a19a338 GIT binary patch literal 6311 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 literal 0 HcmV?d00001 diff --git a/SoundTest/wwwroot/index.html b/SoundTest/wwwroot/index.html new file mode 100644 index 0000000..dab5e48 --- /dev/null +++ b/SoundTest/wwwroot/index.html @@ -0,0 +1,41 @@ + + + + + + + Sound Test + + + + + + + + + + + + + + +
+ + + + +
+
+ +
+ An unhandled error has occurred. + Reload + 🗙 +
+ + + + + + + diff --git a/SoundTest/wwwroot/manifest.webmanifest b/SoundTest/wwwroot/manifest.webmanifest new file mode 100644 index 0000000..41f530e --- /dev/null +++ b/SoundTest/wwwroot/manifest.webmanifest @@ -0,0 +1,22 @@ +{ + "name": "SoundTest", + "short_name": "SoundTest", + "id": "./", + "start_url": "./", + "display": "standalone", + "background_color": "#ffffff", + "theme_color": "#03173d", + "prefer_related_applications": false, + "icons": [ + { + "src": "icon-512.png", + "type": "image/png", + "sizes": "512x512" + }, + { + "src": "icon-192.png", + "type": "image/png", + "sizes": "192x192" + } + ] +} diff --git a/SoundTest/wwwroot/service-worker.js b/SoundTest/wwwroot/service-worker.js new file mode 100644 index 0000000..fe614da --- /dev/null +++ b/SoundTest/wwwroot/service-worker.js @@ -0,0 +1,4 @@ +// In development, always fetch from the network and do not enable offline support. +// This is because caching would make development more difficult (changes would not +// be reflected on the first load after each change). +self.addEventListener('fetch', () => { }); diff --git a/SoundTest/wwwroot/service-worker.published.js b/SoundTest/wwwroot/service-worker.published.js new file mode 100644 index 0000000..51a0e5c --- /dev/null +++ b/SoundTest/wwwroot/service-worker.published.js @@ -0,0 +1,55 @@ +// Caution! Be sure you understand the caveats before publishing an application with +// offline support. See https://aka.ms/blazor-offline-considerations + +self.importScripts('./service-worker-assets.js'); +self.addEventListener('install', event => event.waitUntil(onInstall(event))); +self.addEventListener('activate', event => event.waitUntil(onActivate(event))); +self.addEventListener('fetch', event => event.respondWith(onFetch(event))); + +const cacheNamePrefix = 'offline-cache-'; +const cacheName = `${cacheNamePrefix}${self.assetsManifest.version}`; +const offlineAssetsInclude = [ /\.dll$/, /\.pdb$/, /\.wasm/, /\.html/, /\.js$/, /\.json$/, /\.css$/, /\.woff$/, /\.png$/, /\.jpe?g$/, /\.gif$/, /\.ico$/, /\.blat$/, /\.dat$/, /\.webmanifest$/ ]; +const offlineAssetsExclude = [ /^service-worker\.js$/ ]; + +// Replace with your base path if you are hosting on a subfolder. Ensure there is a trailing '/'. +const base = "/"; +const baseUrl = new URL(base, self.origin); +const manifestUrlList = self.assetsManifest.assets.map(asset => new URL(asset.url, baseUrl).href); + +async function onInstall(event) { + console.info('Service worker: Install'); + + // Fetch and cache all matching items from the assets manifest + const assetsRequests = self.assetsManifest.assets + .filter(asset => offlineAssetsInclude.some(pattern => pattern.test(asset.url))) + .filter(asset => !offlineAssetsExclude.some(pattern => pattern.test(asset.url))) + .map(asset => new Request(asset.url, { integrity: asset.hash, cache: 'no-cache' })); + await caches.open(cacheName).then(cache => cache.addAll(assetsRequests)); +} + +async function onActivate(event) { + console.info('Service worker: Activate'); + + // Delete unused caches + const cacheKeys = await caches.keys(); + await Promise.all(cacheKeys + .filter(key => key.startsWith(cacheNamePrefix) && key !== cacheName) + .map(key => caches.delete(key))); +} + +async function onFetch(event) { + let cachedResponse = null; + if (event.request.method === 'GET') { + // For all navigation requests, try to serve index.html from cache, + // unless that request is for an offline resource. + // If you need some URLs to be server-rendered, edit the following check to exclude those URLs + const shouldServeIndexHtml = event.request.mode === 'navigate' + && !manifestUrlList.some(url => url === event.request.url); + + const request = shouldServeIndexHtml ? 'index.html' : event.request; + const cache = await caches.open(cacheName); + cachedResponse = await cache.match(request); + } + + return cachedResponse || fetch(event.request); +} From d1977922b0c71d73cd0f42db50f8865c92a78569 Mon Sep 17 00:00:00 2001 From: Michael Bond Date: Tue, 9 Dec 2025 00:55:52 -0500 Subject: [PATCH 3/4] Code style and formatting improvements across project Refactored code for consistency and readability in multiple files, including .editorconfig, Razor components, JS, CSS, and project configuration. Standardized attribute formatting, improved enum and dictionary usage, clarified naming rules, and enhanced maintainability without changing functionality. --- .editorconfig | 18 +++++------ SoundTest.slnx | 10 +++---- SoundTest/App.razor | 9 ++++-- SoundTest/Components/SoundComponent.razor | 12 +++++--- SoundTest/Components/SoundComponent.razor.cs | 8 ++--- SoundTest/Components/SoundComponent.razor.js | 6 ++-- SoundTest/Constants.cs | 16 +++++----- SoundTest/Pages/Home.razor | 2 +- SoundTest/SoundTest.csproj | 8 ++--- SoundTest/_Imports.razor | 2 +- SoundTest/wwwroot/css/app.css | 30 +++++++++---------- SoundTest/wwwroot/index.html | 23 ++++++++------ SoundTest/wwwroot/service-worker.js | 3 +- SoundTest/wwwroot/service-worker.published.js | 6 ++-- 14 files changed, 83 insertions(+), 70 deletions(-) diff --git a/.editorconfig b/.editorconfig index 95e2beb..daf3ec5 100644 --- a/.editorconfig +++ b/.editorconfig @@ -102,7 +102,7 @@ csharp_style_conditional_delegate_call = true:suggestion # Modifier preferences csharp_prefer_static_local_function = true:suggestion -csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async +csharp_preferred_modifier_order = public, private, protected, internal, static, extern, new, virtual, abstract, sealed, override, readonly, unsafe, volatile, async # Code-block preferences csharp_prefer_braces = true:suggestion @@ -198,26 +198,26 @@ dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case dotnet_naming_symbols.interface.applicable_kinds = interface dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected -dotnet_naming_symbols.interface.required_modifiers = +dotnet_naming_symbols.interface.required_modifiers = dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected -dotnet_naming_symbols.types.required_modifiers = +dotnet_naming_symbols.types.required_modifiers = dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected -dotnet_naming_symbols.non_field_members.required_modifiers = +dotnet_naming_symbols.non_field_members.required_modifiers = # Naming styles -dotnet_naming_style.pascal_case.required_prefix = -dotnet_naming_style.pascal_case.required_suffix = -dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = dotnet_naming_style.pascal_case.capitalization = pascal_case dotnet_naming_style.begins_with_i.required_prefix = I -dotnet_naming_style.begins_with_i.required_suffix = -dotnet_naming_style.begins_with_i.word_separator = +dotnet_naming_style.begins_with_i.required_suffix = +dotnet_naming_style.begins_with_i.word_separator = dotnet_naming_style.begins_with_i.capitalization = pascal_case csharp_style_prefer_method_group_conversion = true:suggestion csharp_style_prefer_parameter_null_checking = true:suggestion diff --git a/SoundTest.slnx b/SoundTest.slnx index 26b1f03..36c65ad 100644 --- a/SoundTest.slnx +++ b/SoundTest.slnx @@ -1,7 +1,7 @@ - - - - - + + + + + diff --git a/SoundTest/App.razor b/SoundTest/App.razor index a8a79e5..cc1cd15 100644 --- a/SoundTest/App.razor +++ b/SoundTest/App.razor @@ -1,6 +1,9 @@ - + - - + + diff --git a/SoundTest/Components/SoundComponent.razor b/SoundTest/Components/SoundComponent.razor index 4ed6057..f03c06e 100644 --- a/SoundTest/Components/SoundComponent.razor +++ b/SoundTest/Components/SoundComponent.razor @@ -10,7 +10,7 @@ Label="Sound Type"> @foreach (var t in Enum.GetValues()) { - + } @@ -25,8 +25,12 @@ Spacing="3"> - @(isPlaying ? "Stop" : "Start") + OnClick="@(isPlaying + ? StopPlaying + : StartPlaying)"> + @(isPlaying + ? "Stop" + : "Start") + OnAdornmentClick="@CopySoundLink"/> diff --git a/SoundTest/Components/SoundComponent.razor.cs b/SoundTest/Components/SoundComponent.razor.cs index 1257a43..5e52f81 100644 --- a/SoundTest/Components/SoundComponent.razor.cs +++ b/SoundTest/Components/SoundComponent.razor.cs @@ -2,11 +2,11 @@ public partial class SoundComponent(IJSRuntime jsRuntime, ISnackbar snackbar, NavigationManager navigation) { + private bool isJsInitialized; private bool isPlaying; - private string? soundLink; private IJSObjectReference? module; - private bool isJsInitialized; + private string? soundLink; private List? AudioDevices { get; set; } @@ -33,7 +33,7 @@ public int Frequency { < MinFrequency => MinFrequency, > MaxFrequency => MaxFrequency, - _ => value, + _ => value }; _ = SetParametersAndUpdate(); } @@ -47,7 +47,7 @@ private async Task SetParametersAndUpdate() private void UpdateUri() { - var uri = navigation.GetUriWithQueryParameters(new Dictionary() { [nameof(Type)] = (int)Type, [nameof(Frequency)] = Frequency, }); + var uri = navigation.GetUriWithQueryParameters(new Dictionary { [nameof(Type)] = (int)Type, [nameof(Frequency)] = Frequency }); soundLink = uri; } diff --git a/SoundTest/Components/SoundComponent.razor.js b/SoundTest/Components/SoundComponent.razor.js index c56a0e4..de063cb 100644 --- a/SoundTest/Components/SoundComponent.razor.js +++ b/SoundTest/Components/SoundComponent.razor.js @@ -42,14 +42,14 @@ export async function GetAudioOutputDevices() { devices = (await navigator.mediaDevices.enumerateDevices()).filter(function (entry) { return entry.kind === 'audiooutput' && entry.deviceId !== null && entry.deviceId !== ""; }); - ; + if (devices === null || devices.length === 0) { - await navigator.mediaDevices.getUserMedia({ audio: true }); + await navigator.mediaDevices.getUserMedia({audio: true}); devices = (await navigator.mediaDevices.enumerateDevices()).filter(function (entry) { return entry.kind === 'audiooutput' && entry.deviceId !== null && entry.deviceId !== ""; }); - ; + } return devices; diff --git a/SoundTest/Constants.cs b/SoundTest/Constants.cs index 4bfe60b..887a460 100644 --- a/SoundTest/Constants.cs +++ b/SoundTest/Constants.cs @@ -2,6 +2,14 @@ public static class Constants { + public enum Types + { + Sine, + Square, + Triangle, + Sawtooth + } + public const int MinFrequency = 1; public const int MaxFrequency = 1500; // 20154; @@ -11,12 +19,4 @@ public static class Constants public const int NeuroFrequency = 852; public const decimal PerfectCFrequency = 261.63M; // C4 - - public enum Types - { - Sine, - Square, - Triangle, - Sawtooth, - } } diff --git a/SoundTest/Pages/Home.razor b/SoundTest/Pages/Home.razor index 2591b33..ee003ab 100644 --- a/SoundTest/Pages/Home.razor +++ b/SoundTest/Pages/Home.razor @@ -5,4 +5,4 @@ + Frequency="@Frequency"/> diff --git a/SoundTest/SoundTest.csproj b/SoundTest/SoundTest.csproj index b3bfb7b..ca1a56d 100644 --- a/SoundTest/SoundTest.csproj +++ b/SoundTest/SoundTest.csproj @@ -17,13 +17,13 @@ - - - + + + - + diff --git a/SoundTest/_Imports.razor b/SoundTest/_Imports.razor index 563a926..86051d8 100644 --- a/SoundTest/_Imports.razor +++ b/SoundTest/_Imports.razor @@ -12,4 +12,4 @@ @using SoundTest.Pages @using MudBlazor -@using static SoundTest.Constants +@using static Constants diff --git a/SoundTest/wwwroot/css/app.css b/SoundTest/wwwroot/css/app.css index 7baf393..7c91049 100644 --- a/SoundTest/wwwroot/css/app.css +++ b/SoundTest/wwwroot/css/app.css @@ -7,19 +7,19 @@ margin: 0 auto 0 auto; } - .loading-progress circle { - fill: none; - stroke: #e0e0e0; - stroke-width: 0.6rem; - transform-origin: 50% 50%; - transform: rotate(-90deg); - } +.loading-progress circle { + fill: none; + stroke: #e0e0e0; + stroke-width: 0.6rem; + transform-origin: 50% 50%; + transform: rotate(-90deg); +} - .loading-progress circle:last-child { - stroke: #1b6ec2; - stroke-dasharray: calc(3.141 * var(--blazor-load-percentage, 0%) * 0.8), 500%; - transition: stroke-dasharray 0.05s ease-in-out; - } +.loading-progress circle:last-child { + stroke: #1b6ec2; + stroke-dasharray: calc(3.141 * var(--blazor-load-percentage, 0%) * 0.8), 500%; + transition: stroke-dasharray 0.05s ease-in-out; +} .loading-progress-text { position: absolute; @@ -28,6 +28,6 @@ inset: calc(20vh + 3.25rem) 0 auto 0.2rem; } - .loading-progress-text:after { - content: var(--blazor-load-percentage-text, "Loading"); - } +.loading-progress-text:after { + content: var(--blazor-load-percentage-text, "Loading"); +} diff --git a/SoundTest/wwwroot/index.html b/SoundTest/wwwroot/index.html index dab5e48..2b667cf 100644 --- a/SoundTest/wwwroot/index.html +++ b/SoundTest/wwwroot/index.html @@ -3,16 +3,16 @@ - + Sound Test - - + + - - + + @@ -21,21 +21,26 @@
- - + +
An unhandled error has occurred. - Reload + Reload 🗙
- + diff --git a/SoundTest/wwwroot/service-worker.js b/SoundTest/wwwroot/service-worker.js index fe614da..4882765 100644 --- a/SoundTest/wwwroot/service-worker.js +++ b/SoundTest/wwwroot/service-worker.js @@ -1,4 +1,5 @@ // In development, always fetch from the network and do not enable offline support. // This is because caching would make development more difficult (changes would not // be reflected on the first load after each change). -self.addEventListener('fetch', () => { }); +self.addEventListener('fetch', () => { +}); diff --git a/SoundTest/wwwroot/service-worker.published.js b/SoundTest/wwwroot/service-worker.published.js index 51a0e5c..9de510d 100644 --- a/SoundTest/wwwroot/service-worker.published.js +++ b/SoundTest/wwwroot/service-worker.published.js @@ -8,8 +8,8 @@ self.addEventListener('fetch', event => event.respondWith(onFetch(event))); const cacheNamePrefix = 'offline-cache-'; const cacheName = `${cacheNamePrefix}${self.assetsManifest.version}`; -const offlineAssetsInclude = [ /\.dll$/, /\.pdb$/, /\.wasm/, /\.html/, /\.js$/, /\.json$/, /\.css$/, /\.woff$/, /\.png$/, /\.jpe?g$/, /\.gif$/, /\.ico$/, /\.blat$/, /\.dat$/, /\.webmanifest$/ ]; -const offlineAssetsExclude = [ /^service-worker\.js$/ ]; +const offlineAssetsInclude = [/\.dll$/, /\.pdb$/, /\.wasm/, /\.html/, /\.js$/, /\.json$/, /\.css$/, /\.woff$/, /\.png$/, /\.jpe?g$/, /\.gif$/, /\.ico$/, /\.blat$/, /\.dat$/, /\.webmanifest$/]; +const offlineAssetsExclude = [/^service-worker\.js$/]; // Replace with your base path if you are hosting on a subfolder. Ensure there is a trailing '/'. const base = "/"; @@ -23,7 +23,7 @@ async function onInstall(event) { const assetsRequests = self.assetsManifest.assets .filter(asset => offlineAssetsInclude.some(pattern => pattern.test(asset.url))) .filter(asset => !offlineAssetsExclude.some(pattern => pattern.test(asset.url))) - .map(asset => new Request(asset.url, { integrity: asset.hash, cache: 'no-cache' })); + .map(asset => new Request(asset.url, {integrity: asset.hash, cache: 'no-cache'})); await caches.open(cacheName).then(cache => cache.addAll(assetsRequests)); } From 820ff1a9fc6b0137d222178017c9456306d3a355 Mon Sep 17 00:00:00 2001 From: Michael Bond Date: Tue, 9 Dec 2025 00:58:04 -0500 Subject: [PATCH 4/4] Update app name in manifest to "Sound Test" Changed the "name" and "short_name" fields in manifest.webmanifest from "SoundTest" to "Sound Test" for improved readability and consistency. --- SoundTest/wwwroot/manifest.webmanifest | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SoundTest/wwwroot/manifest.webmanifest b/SoundTest/wwwroot/manifest.webmanifest index 41f530e..f54f85a 100644 --- a/SoundTest/wwwroot/manifest.webmanifest +++ b/SoundTest/wwwroot/manifest.webmanifest @@ -1,6 +1,6 @@ { - "name": "SoundTest", - "short_name": "SoundTest", + "name": "Sound Test", + "short_name": "Sound Test", "id": "./", "start_url": "./", "display": "standalone",