Skip to content

Commit

Permalink
Merge pull request #32 from stevehjohn/sa
Browse files Browse the repository at this point in the history
Sa
  • Loading branch information
stevehjohn authored Aug 10, 2024
2 parents 696ab4d + 2f2f422 commit 9373ddf
Show file tree
Hide file tree
Showing 7 changed files with 292 additions and 4 deletions.
3 changes: 3 additions & 0 deletions src/Zen.Common/Constants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,9 @@ public static class Constants
/* Wave Visualiser */
public const int WaveVisualisationPanelWidth = 200;

/* Spectrum Analyser Visualiser 8*/
public const int SpectrumVisualisationPanelWidth = 200;

/* Counters Visualiser */
public const int CountersPanelHeight = 24;

Expand Down
228 changes: 228 additions & 0 deletions src/Zen.Desktop.Host/Features/SpectrumAnalyser.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,228 @@
using System;
using System.Numerics;
using MathNet.Numerics.IntegralTransforms;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Zen.Common;

namespace Zen.Desktop.Host.Features;

public class SpectrumAnalyser
{
private const int BufferSize = System.Modules.Audio.Constants.SampleRate / Constants.SpectrumFramesPerSecond;

private const int MagnitudeDivisor = 250;

private const int BarWidth = 8;

private const int BarSpacing = 4;

private const int SegmentHeight = 5;

// ReSharper disable once PrivateFieldCanBeConvertedToLocalVariable
private readonly GraphicsDeviceManager _graphicsDeviceManager;

private readonly Color[] _data;

private readonly Complex[][] _buffers;

private readonly Texture2D _spectrum;

private int _bufferPosition;

private bool _rendering;

private readonly Color[] _palette;

private readonly FrequencyRange[] _frequencyRanges =
[
new FrequencyRange(30, 40),
new FrequencyRange(40, 60), // Bass
new FrequencyRange(60, 80),
new FrequencyRange(80, 120), // Lower midrange
new FrequencyRange(120, 160),
new FrequencyRange(160, 240), // Midrange
new FrequencyRange(240, 320),
new FrequencyRange(320, 480), // Upper midrange
new FrequencyRange(480, 640),
new FrequencyRange(640, 960), // Presence
new FrequencyRange(960, 1280),
new FrequencyRange(1280, 1920), // Brilliance
new FrequencyRange(1920, 2560)
];

public Texture2D Spectrum => _spectrum;

public SpectrumAnalyser(GraphicsDeviceManager graphicsDeviceManager)
{
_graphicsDeviceManager = graphicsDeviceManager;

_data = new Color[Constants.WaveVisualisationPanelWidth * Constants.ScreenHeightPixels];

_spectrum = new Texture2D(_graphicsDeviceManager.GraphicsDevice, Constants.WaveVisualisationPanelWidth, Constants.ScreenHeightPixels);

_buffers = new Complex[4][];

for (var i = 0; i < 4; i++)
{
_buffers[i] = new Complex[BufferSize];
}

_palette = PaletteGenerator.GetPalette(46,
[
new Color(119, 35, 172),
new Color(176, 83, 203),
new Color(255, 168, 76),
new Color(254, 211, 56),
new Color(254, 253, 0)
]);
}

public void ReceiveSignals(float[] signals)
{
if (_rendering)
{
return;
}

_buffers[0][_bufferPosition] = new Complex(signals[0], 0);
_buffers[1][_bufferPosition] = new Complex(signals[1], 0);
_buffers[2][_bufferPosition] = new Complex(signals[2], 0);

_bufferPosition++;

if (_bufferPosition >= BufferSize)
{
_bufferPosition = 0;

if (! _rendering)
{
RenderSpectrum();
}
}
}

private void RenderSpectrum()
{
_rendering = true;

Array.Fill(_data, Color.Black);

RenderSpectrumChannel(0);
RenderSpectrumChannel(1);
RenderSpectrumChannel(2);

_spectrum.SetData(_data);

_rendering = false;
}

private void RenderSpectrumChannel(int channel)
{
Fourier.Forward(_buffers[channel], FourierOptions.Matlab);

var height = Constants.ScreenHeightPixels / 4;

var magnitudes = new float[_buffers[channel].Length];

var axis = height * Constants.SpectrumVisualisationPanelWidth * (channel + 1) - Constants.SpectrumVisualisationPanelWidth;

for (var i = 0; i < magnitudes.Length; i++)
{
magnitudes[i] = (float) Math.Sqrt(_buffers[channel][i].Real * _buffers[channel][i].Real + _buffers[channel][i].Imaginary * _buffers[channel][i].Imaginary);
}

var groupedMagnitudes = new float[_frequencyRanges.Length];

for (var i = 0; i < _frequencyRanges.Length; i++)
{
var lowBin = (int)Math.Round(_frequencyRanges[i].Low * BufferSize / System.Modules.Audio.Constants.SampleRate);
var highBin = (int)Math.Round(_frequencyRanges[i].High * BufferSize / System.Modules.Audio.Constants.SampleRate);

float sum = 0;

for (var j = lowBin; j <= highBin; j++)
{
sum += magnitudes[j];
}

groupedMagnitudes[i] = sum / (highBin - lowBin + 1);
}

for (var i = 0; i < _frequencyRanges.Length; i++)
{
var dataPoint = groupedMagnitudes[i] / MagnitudeDivisor;

for (var x = 0; x < BarWidth; x++)
{
var offset = -(int) (dataPoint * height * (channel == 3 ? 1 : 4));

while (offset <= 0)
{
if (Math.Abs(offset) % SegmentHeight == 4)
{
offset++;

continue;
}

_data[22 + axis + i * (BarWidth + BarSpacing) + x + offset * Constants.SpectrumVisualisationPanelWidth] = _palette[-offset];

offset++;
}
}
}
}

private class FrequencyRange
{
public float Low { get; }
public float High { get; }

public FrequencyRange(float low, float high)
{
Low = low;
High = high;
}
}

private static class PaletteGenerator
{
public static Color[] GetPalette(int steps, Color[] markers)
{
var palette = new Color[steps];

var markerPeriod = steps / (markers.Length - 2);

var current = markers[0];

var next = markers[1];

var counter = markerPeriod;

var markerIndex = 1;

for (var i = 0; i < steps; i++)
{
palette[i] = new Color(current.R, current.G, current.B);

current.R += (byte) ((next.R - current.R) / markerPeriod);
current.G += (byte) ((next.G - current.G) / markerPeriod);
current.B += (byte) ((next.B - current.B) / markerPeriod);

counter--;

if (counter == 0)
{
counter = markerPeriod;

markerIndex++;

next = markers[markerIndex];
}
}

return palette;
}
}
}
50 changes: 50 additions & 0 deletions src/Zen.Desktop.Host/Infrastructure/Host.cs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ public class Host : Game

private WaveVisualiser _waveVisualiser;

private SpectrumAnalyser _spectrumAnalyser;

private CountersVisualiser _countersVisualiser;

private VideoRamVisualiser _videoRamVisualiser;
Expand All @@ -60,6 +62,11 @@ public Host()
width += Constants.WaveVisualisationPanelWidth * _scaleFactor;
}

if (AppSettings.Instance.Visualisation == Visualisation.SpectrumAnalyser)
{
width += Constants.SpectrumVisualisationPanelWidth * _scaleFactor;
}

if (AppSettings.Instance.Visualisation == Visualisation.VideoRam)
{
width += Constants.VideoRamVisualisationPanelWidth * _scaleFactor;
Expand Down Expand Up @@ -125,6 +132,11 @@ private void SetMotherboard(Model model)
{
_videoRamVisualiser.Ram = _motherboard.Ram;
}

if (_spectrumAnalyser != null)
{
_motherboard.AyAudio.AySignalHook = _spectrumAnalyser.ReceiveSignals;
}
}

protected override void OnActivated(object sender, EventArgs args)
Expand Down Expand Up @@ -170,6 +182,13 @@ protected override void LoadContent()
_motherboard.AyAudio.BeeperSignalHook = _waveVisualiser.ReceiveSignal;
}

if (AppSettings.Instance.Visualisation == Visualisation.SpectrumAnalyser)
{
_spectrumAnalyser = new SpectrumAnalyser(_graphicsDeviceManager);

_motherboard.AyAudio.AySignalHook = _spectrumAnalyser.ReceiveSignals;
}

if (AppSettings.Instance.Visualisation == Visualisation.VideoRam)
{
_videoRamVisualiser = new VideoRamVisualiser(_graphicsDeviceManager, _motherboard.Ram, false, _videoRenderer);
Expand Down Expand Up @@ -316,6 +335,7 @@ private void MenuFinished(MenuResult result, object arguments)
AppSettings.Instance.Save();

_waveVisualiser = null;
_videoRamVisualiser = null;
_motherboard.AyAudio.AySignalHook = null;
_motherboard.AyAudio.BeeperSignalHook = null;

Expand All @@ -336,6 +356,19 @@ private void MenuFinished(MenuResult result, object arguments)

break;

case MenuResult.VisualisationSpectrumAnalyser:
AppSettings.Instance.Visualisation = Visualisation.SpectrumAnalyser;
AppSettings.Instance.Save();

_spectrumAnalyser = new SpectrumAnalyser(_graphicsDeviceManager);
_motherboard.AyAudio.AySignalHook = _spectrumAnalyser.ReceiveSignals;
_videoRamVisualiser = null;
_waveVisualiser = null;

ChangeScale(_scaleFactor);

break;

case MenuResult.VisualisationVideoRam:
AppSettings.Instance.Visualisation = Visualisation.VideoRam;
AppSettings.Instance.Save();
Expand Down Expand Up @@ -415,6 +448,11 @@ private void ChangeScale(int scale)
width += Constants.WaveVisualisationPanelWidth * _scaleFactor;
}

if (AppSettings.Instance.Visualisation == Visualisation.SpectrumAnalyser)
{
width += Constants.SpectrumVisualisationPanelWidth * _scaleFactor;
}

if (AppSettings.Instance.Visualisation == Visualisation.VideoRam)
{
width += Constants.VideoRamVisualisationPanelWidth * _scaleFactor;
Expand Down Expand Up @@ -544,6 +582,18 @@ protected override void Draw(GameTime gameTime)
}
}

if (_spectrumAnalyser != null)
{
var spectrum = _spectrumAnalyser.Spectrum;

if (spectrum != null)
{
_spriteBatch.Draw(spectrum,
new Rectangle(Constants.ScreenWidthPixels * _scaleFactor, 0, Constants.SpectrumVisualisationPanelWidth * _scaleFactor, Constants.ScreenHeightPixels * _scaleFactor),
new Rectangle(0, 0, Constants.SpectrumVisualisationPanelWidth, Constants.ScreenHeightPixels), Color.White);
}
}

if (_videoRamVisualiser != null)
{
if (_videoRamVisualiser.BanksView)
Expand Down
1 change: 1 addition & 0 deletions src/Zen.Desktop.Host/Infrastructure/Menu/MenuResult.cs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public enum MenuResult
SoundOff,
VisualisationOff,
VisualisationWaveform,
VisualisationSpectrumAnalyser,
VisualisationVideoRam,
VisualisationVideoBanks,
CountersOn,
Expand Down
12 changes: 8 additions & 4 deletions src/Zen.Desktop.Host/Infrastructure/Menu/VisualisationMenu.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ public override List<Label> GetMenu()
new(0, true, "Zen - Visualisation", Color.White, 0, 0, null),
new(1, false, $"[1] {(AppSettings.Instance.Visualisation == Visualisation.Off ? ">" : " ")} Off {(AppSettings.Instance.Visualisation == Visualisation.Off ? "<" : " ")}", Color.Yellow, 1, 3, Keys.D1, Color.LightGreen),
new(2, false, $"[2] {(AppSettings.Instance.Visualisation == Visualisation.Waveforms ? ">" : " ")} Waveforms {(AppSettings.Instance.Visualisation == Visualisation.Waveforms ? "<" : " ")}", Color.Yellow, 1, 5, Keys.D2, Color.LightGreen),
new(3, false, $"[3] {(AppSettings.Instance.Visualisation == Visualisation.VideoRam ? ">" : " ")} Video Memory {(AppSettings.Instance.Visualisation == Visualisation.VideoRam ? "<" : " ")}", Color.Yellow, 1, 7, Keys.D3, Color.LightGreen),
new(4, false, $"[4] {(AppSettings.Instance.Visualisation == Visualisation.VideoBanks ? ">" : " ")} 128k Video Banks {(AppSettings.Instance.Visualisation == Visualisation.VideoBanks ? "<" : " ")}", Color.Yellow, 1, 9, Keys.D4, Color.LightGreen),
new(3, false, $"[3] {(AppSettings.Instance.Visualisation == Visualisation.SpectrumAnalyser ? ">" : " ")} Spectrum Analyser {(AppSettings.Instance.Visualisation == Visualisation.SpectrumAnalyser ? "<" : " ")}", Color.Yellow, 1, 7, Keys.D3, Color.LightGreen),
new(4, false, $"[4] {(AppSettings.Instance.Visualisation == Visualisation.VideoRam ? ">" : " ")} Video Memory {(AppSettings.Instance.Visualisation == Visualisation.VideoRam ? "<" : " ")}", Color.Yellow, 1, 9, Keys.D4, Color.LightGreen),
new(5, false, $"[5] {(AppSettings.Instance.Visualisation == Visualisation.VideoBanks ? ">" : " ")} 128k Video Banks {(AppSettings.Instance.Visualisation == Visualisation.VideoBanks ? "<" : " ")}", Color.Yellow, 1, 11, Keys.D5, Color.LightGreen),
new(99, true, "[ESC] Close Menu", Color.FromNonPremultiplied(255, 64, 64, 255), 0, 21, Keys.Escape, Color.LightGreen)
};

Expand All @@ -31,11 +32,14 @@ public override (MenuResult Result, MenuBase NewMenu, object Arguments) ItemSele

case 2:
return (MenuResult.VisualisationWaveform, null, null);

case 3:
return (MenuResult.VisualisationVideoRam, null, null);
return (MenuResult.VisualisationSpectrumAnalyser, null, null);

case 4:
return (MenuResult.VisualisationVideoRam, null, null);

case 5:
return (MenuResult.VisualisationVideoBanks, null, null);

default:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ public enum Visualisation
{
Off,
Waveforms,
SpectrumAnalyser,
VideoRam,
VideoBanks
}
1 change: 1 addition & 0 deletions src/Zen.Desktop.Host/Zen.Desktop.Host.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
<EmbeddedResource Include="Icon.ico" />
</ItemGroup>
<ItemGroup>
<PackageReference Include="MathNet.Numerics" Version="5.0.0" />
<PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.1.303" />
<PackageReference Include="MonoGame.Content.Builder.Task" Version="3.8.1.303" />
</ItemGroup>
Expand Down

0 comments on commit 9373ddf

Please sign in to comment.