From 5792bfbd9ad162680577339e2e7a23e5f1df0b81 Mon Sep 17 00:00:00 2001 From: rattatwinko Date: Fri, 24 Oct 2025 11:36:26 +0200 Subject: [PATCH] some new algorithms and some new stuff. sounds increased! --- .../SortVisualizer/Algorithms/BeadSort.cs | 74 ++++++ .../SortVisualizer/Algorithms/RadixSort.cs | 77 ++++++ .../SortVisualizer/SortVisualizer.csproj | 4 + simulations/SortVisualizer/VisualizerForm.cs | 240 ++++++++---------- 4 files changed, 266 insertions(+), 129 deletions(-) create mode 100644 simulations/SortVisualizer/Algorithms/BeadSort.cs create mode 100644 simulations/SortVisualizer/Algorithms/RadixSort.cs diff --git a/simulations/SortVisualizer/Algorithms/BeadSort.cs b/simulations/SortVisualizer/Algorithms/BeadSort.cs new file mode 100644 index 0000000..73db1de --- /dev/null +++ b/simulations/SortVisualizer/Algorithms/BeadSort.cs @@ -0,0 +1,74 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace SortVisualizer; + +public class BeadSort : ISortAlgorithm +{ + public string Name => "Bead Sort"; + public BeadSort() { } + + public async Task Sort(int[] array, Action refresh, int delay, Action playSound, CancellationToken token) + { + try + { + int n = array.Length; + if (n == 0) return; + + int max = 0; + for (int i = 0; i < n; i++) + { + token.ThrowIfCancellationRequested(); + if (array[i] > max) max = array[i]; + } + + bool[,] beads = new bool[n, max]; + for (int i = 0; i < n; i++) + { + token.ThrowIfCancellationRequested(); + for (int j = 0; j < array[i]; j++) + beads[i, j] = true; + } + + for (int j = 0; j < max; j++) + { + token.ThrowIfCancellationRequested(); + int sum = 0; + for (int i = 0; i < n; i++) + if (beads[i, j]) sum++; + + for (int i = 0; i < n; i++) + beads[i, j] = i >= n - sum; + + for (int i = 0; i < n; i++) + { + array[i] = 0; + for (int k = 0; k < max; k++) + if (beads[i, k]) array[i]++; + playSound?.Invoke(array[i]); + } + + refresh?.Invoke(); + await Task.Delay(delay, token); + } + + for (int i = 0; i < n; i++) + { + token.ThrowIfCancellationRequested(); + int count = 0; + for (int j = 0; j < max; j++) + if (beads[i, j]) count++; + array[i] = count; + + playSound?.Invoke(array[i]); + refresh?.Invoke(); + await Task.Delay(delay, token); + } + } + catch (TaskCanceledException) + { + // Safe cancellation, just exit + } + } +} diff --git a/simulations/SortVisualizer/Algorithms/RadixSort.cs b/simulations/SortVisualizer/Algorithms/RadixSort.cs new file mode 100644 index 0000000..fea3760 --- /dev/null +++ b/simulations/SortVisualizer/Algorithms/RadixSort.cs @@ -0,0 +1,77 @@ +using System; +using System.Threading; +using System.Threading.Tasks; + +namespace SortVisualizer; + +public class RadixSort : ISortAlgorithm +{ + public string Name => "Radix Sort"; + public RadixSort() { } + + public async Task Sort(int[] array, Action refresh, int delay, Action playSound, CancellationToken token) + { + int max = GetMax(array); + int exp = 1; + + while (max / exp > 0) + { + token.ThrowIfCancellationRequested(); + await CountSort(array, exp, refresh, delay, playSound, token); + exp *= 10; + } + } + + private int GetMax(int[] array) + { + int max = array[0]; + for (int i = 1; i < array.Length; i++) + if (array[i] > max) max = array[i]; + return max; + } + + private async Task CountSort(int[] array, int exp, Action refresh, int delay, Action playSound, CancellationToken token) + { + int n = array.Length; + int[] output = new int[n]; + int[] count = new int[10]; // digits 0-9 + + // Count occurrences of digits + for (int i = 0; i < n; i++) + { + token.ThrowIfCancellationRequested(); + int index = (array[i] / exp) % 10; + count[index]++; + } + + // Convert count to positions + for (int i = 1; i < 10; i++) + { + count[i] += count[i - 1]; + } + + // Build the output array + for (int i = n - 1; i >= 0; i--) + { + token.ThrowIfCancellationRequested(); + int index = (array[i] / exp) % 10; + output[count[index] - 1] = array[i]; + count[index]--; + + // Visualize + refresh?.Invoke(); + playSound?.Invoke(array[i]); + await Task.Delay(10, token); // small delay per operation + } + + // Copy output to original array + for (int i = 0; i < n; i++) + { + token.ThrowIfCancellationRequested(); + array[i] = output[i]; + refresh?.Invoke(); + playSound?.Invoke(array[i]); + await Task.Delay(delay, token); + } + } +} diff --git a/simulations/SortVisualizer/SortVisualizer.csproj b/simulations/SortVisualizer/SortVisualizer.csproj index c27cd77..58f12e0 100644 --- a/simulations/SortVisualizer/SortVisualizer.csproj +++ b/simulations/SortVisualizer/SortVisualizer.csproj @@ -8,4 +8,8 @@ enable + + + + \ No newline at end of file diff --git a/simulations/SortVisualizer/VisualizerForm.cs b/simulations/SortVisualizer/VisualizerForm.cs index 81fa88e..1a78389 100644 --- a/simulations/SortVisualizer/VisualizerForm.cs +++ b/simulations/SortVisualizer/VisualizerForm.cs @@ -1,13 +1,11 @@ using System; using System.Collections.Generic; using System.Drawing; -using System.IO; using System.Linq; -using System.Media; -using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; +using NAudio.Wave; namespace SortVisualizer; @@ -22,13 +20,13 @@ public class VisualizerForm : Form Button startButton, stopButton, shuffleButton; TrackBar speedSlider; CheckBox soundToggle; - Label speedLabel, statusLabel; List algorithms; CancellationTokenSource? cts; - Dictionary toneCache = new(); - SemaphoreSlim soundSemaphore = new(1, 1); // prevent sound overlap + // Audio + BufferedWaveProvider? waveProvider; + WaveOutEvent? waveOut; int highlightedIndex1 = -1; int highlightedIndex2 = -1; @@ -41,11 +39,13 @@ public class VisualizerForm : Form Width = 1000; Height = 600; - algorithms = new() { new BubbleSort(), new InsertionSort(), new BogoSort(), - new MergeSort() , new StoogeSort() - }; + algorithms = new() + { + new BubbleSort(), new InsertionSort(), new BogoSort(), + new MergeSort(), new StoogeSort(), new RadixSort(), + new BeadSort() + }; - // --- Top Control Panel --- var topPanel = new FlowLayoutPanel { Dock = DockStyle.Top, @@ -68,7 +68,6 @@ public class VisualizerForm : Form soundToggle = new CheckBox { Width = 100, Height = buttonHeight, Text = "Sound On", Checked = true }; - speedLabel = new Label { Width = 60, Height = buttonHeight, Text = "Speed: 10", TextAlign = ContentAlignment.MiddleCenter }; speedSlider = new TrackBar { Width = 250, @@ -81,12 +80,10 @@ public class VisualizerForm : Form Height = buttonHeight }; - statusLabel = new Label { Width = 150, Height = buttonHeight, Text = "Idle", TextAlign = ContentAlignment.MiddleRight }; - topPanel.Controls.AddRange(new Control[] { algoSelector, startButton, stopButton, shuffleButton, - soundToggle, speedLabel, speedSlider, statusLabel + soundToggle, speedSlider }); Controls.Add(topPanel); @@ -94,7 +91,6 @@ public class VisualizerForm : Form speedSlider.ValueChanged += (s, e) => { delay = 101 - speedSlider.Value; - speedLabel.Text = $"Speed: {speedSlider.Value}"; }; startButton.Click += async (s, e) => await StartSort(); @@ -112,7 +108,6 @@ public class VisualizerForm : Form array = Enumerable.Range(1, 100).OrderBy(_ => rand.Next()).ToArray(); highlightedIndex1 = highlightedIndex2 = -1; Invalidate(); - statusLabel.Text = "Shuffled!"; } async Task StartSort() @@ -121,28 +116,39 @@ public class VisualizerForm : Form sorting = true; cts = new CancellationTokenSource(); - var algo = (ISortAlgorithm)algoSelector.SelectedItem!; - statusLabel.Text = $"Sorting ( {algo.Name} )..."; + var algo = algoSelector.SelectedItem as ISortAlgorithm; + if (algo == null) + { + sorting = false; + return; + } try { + if (soundToggle.Checked) + { + InitializeAudio(); + if (waveProvider != null) + { + _ = Task.Run(() => AudioLoopAsync(waveProvider, cts.Token)); + } + } + await algo.Sort( array, - () => Invalidate(), // keep original signature + () => Invalidate(), delay, - value => { if (soundToggle.Checked) _ = PlaySoundAsync(value); }, + value => { highlightedIndex1 = value; }, cts.Token ); highlightedIndex1 = highlightedIndex2 = -1; Invalidate(); - - if (!cts.Token.IsCancellationRequested) - statusLabel.Text = "Done!"; } finally { sorting = false; + StopAudio(); } } @@ -153,7 +159,76 @@ public class VisualizerForm : Form sorting = false; highlightedIndex1 = highlightedIndex2 = -1; Invalidate(); - statusLabel.Text = "Stopped."; + StopAudio(); + } + + void InitializeAudio() + { + waveProvider = new BufferedWaveProvider(new WaveFormat(44100, 16, 1)) + { + BufferDuration = TimeSpan.FromSeconds(5) + }; + + waveOut = new WaveOutEvent(); + waveOut.Init(waveProvider); + waveOut.Play(); + } + + void StopAudio() + { + waveOut?.Stop(); + waveOut?.Dispose(); + waveOut = null; + waveProvider = null; + } + + async Task AudioLoopAsync(BufferedWaveProvider provider, CancellationToken token) + { + short amplitude = 8000; + int sampleRate = 44100; + int chunkSamples = 512; + var buffer = new byte[chunkSamples * 2]; + double phase = 0; + + while (!token.IsCancellationRequested) + { + // Calculate frequency based on highlighted element or average + int freq = 220; + if (highlightedIndex1 >= 0 && highlightedIndex1 < array.Length) + { + freq = 220 + array[highlightedIndex1] * 15; // Rising pitch based on value + } + else if (array.Length > 0) + { + freq = 220 + (int)array.Average() * 15; + } + + for (int n = 0; n < chunkSamples; n++) + { + double sample = Math.Sin(phase); + phase += 2 * Math.PI * freq / sampleRate; + + // Keep phase in reasonable range + if (phase > 2 * Math.PI * 1000) + phase -= 2 * Math.PI * 1000; + + short s = (short)(sample * amplitude); + buffer[n * 2] = (byte)(s & 0xFF); + buffer[n * 2 + 1] = (byte)((s >> 8) & 0xFF); + } + + int space = provider.BufferLength - provider.BufferedBytes; + if (space >= buffer.Length) + { + provider.AddSamples(buffer, 0, buffer.Length); + } + else if (space > 0) + { + provider.AddSamples(buffer, buffer.Length - space, space); + } + + await Task.Delay(1, token); + } } protected override void OnPaint(PaintEventArgs e) @@ -163,15 +238,21 @@ public class VisualizerForm : Form int offsetY = 50; int marginX = 20; + int paddingTop = 5; + int paddingBottom = 5; if (array.Length == 0) return; int barWidth = (ClientSize.Width - 2 * marginX) / array.Length; - int availableHeight = ClientSize.Height - offsetY; + int top = offsetY + paddingTop; + int bottom = ClientSize.Height - paddingBottom; + int availableHeight = bottom - top; + + int maxVal = array.Max(); for (int i = 0; i < array.Length; i++) { - int height = (int)(array[i] * (availableHeight / (float)array.Length)); + int height = (int)Math.Round(array[i] * (availableHeight / (float)maxVal)); Brush brush = Brushes.LightGreen; if (sorting) @@ -181,109 +262,10 @@ public class VisualizerForm : Form else brush = Brushes.DeepSkyBlue; } - g.FillRectangle(brush, marginX + i * barWidth, ClientSize.Height - height, barWidth - 1, height); + int y = bottom - height; + if (y < top) y = top; + + g.FillRectangle(brush, marginX + i * barWidth, y, barWidth - 1, height); } } - - async Task PlaySoundAsync(int value) - { - // Use semaphore to prevent overlapping sounds - if (!await soundSemaphore.WaitAsync(0)) - return; // skip if already playing - - try - { - int freq = 220 + (int)(value * 12.0); - int durationMs = 10; - - if (!toneCache.TryGetValue(freq, out var wav)) - { - wav = GenerateToneWav(freq, durationMs, 44100); - - // Limit cache size - if (toneCache.Count > 200) - toneCache.Clear(); - - toneCache[freq] = wav; - } - - await Task.Run(() => - { - try - { - using var ms = new MemoryStream(wav); - using var player = new SoundPlayer(ms); - player.PlaySync(); // blocks on background thread - } - catch - { - try - { - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - Console.Beep(freq, durationMs); - } - catch { } - } - }); - } - finally - { - soundSemaphore.Release(); - } - } - - protected override void Dispose(bool disposing) - { - if (disposing) - { - cts?.Dispose(); - soundSemaphore?.Dispose(); - } - base.Dispose(disposing); - } - - #region Tone Generation - - public static byte[] GenerateToneWav(int frequency, int durationMs, int sampleRate = 44100, double amplitude = 0.25) - { - int samples = (int)((durationMs / 1000.0) * sampleRate); - using var ms = new MemoryStream(); - using var bw = new BinaryWriter(ms); - - short numChannels = 1; - short bitsPerSample = 16; - int byteRate = sampleRate * numChannels * bitsPerSample / 8; - short blockAlign = (short)(numChannels * bitsPerSample / 8); - int dataSize = samples * numChannels * (bitsPerSample / 8); - - bw.Write(System.Text.Encoding.ASCII.GetBytes("RIFF")); - bw.Write(36 + dataSize); - bw.Write(System.Text.Encoding.ASCII.GetBytes("WAVE")); - - bw.Write(System.Text.Encoding.ASCII.GetBytes("fmt ")); - bw.Write(16); - bw.Write((short)1); - bw.Write(numChannels); - bw.Write(sampleRate); - bw.Write(byteRate); - bw.Write(blockAlign); - bw.Write(bitsPerSample); - - bw.Write(System.Text.Encoding.ASCII.GetBytes("data")); - bw.Write(dataSize); - - double twoPiF = 2 * Math.PI * frequency; - for (int n = 0; n < samples; n++) - { - double t = n / (double)sampleRate; - double sample = amplitude * Math.Sin(twoPiF * t); - short s = (short)Math.Max(short.MinValue, Math.Min(short.MaxValue, sample * short.MaxValue)); - bw.Write(s); - } - - bw.Flush(); - return ms.ToArray(); - } - - #endregion } \ No newline at end of file