Skip to content

Commit 02c906a

Browse files
committed
started new microphone FFT project
1 parent 15efb63 commit 02c906a

File tree

23 files changed

+2327
-0
lines changed

23 files changed

+2327
-0
lines changed
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
# Realtime Microphone FFT with ScottPlot
2+
3+
This project demonstrates how to plot microphone data (and its FFT) with [ScottPlot](https://github.com/swharden/ScottPlot). Previous versions of this project used earlier versions of ScottPlot (which are no longer supported).
4+
5+
### Creating this Project from Scratch
6+
These are the steps I did to make this project
7+
* I cloned [ScottPlot](https://github.com/swharden/ScottPlot) into [ScottPlot-2018-09-09/](ScottPlot-2018-09-09) so it will always work with this project even if the latest ScottPlot API changes.
8+
* Add Project (ScottPlot.csproj) to the solution
9+
* ScottPlotUC showed up in the toolbox, so I could drag/drop onto a form
10+
*
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
7+
namespace ScottPlot
8+
{
9+
10+
/// <summary>
11+
/// The MouseAxis class simplifies adjusting axis edges for click-and-drag pan and zoom events.
12+
/// After being instantiated with an initial axis and mouse position, it can return axis limits
13+
/// for panning or zooming given a new mouse position later.
14+
/// </summary>
15+
class MouseAxis
16+
{
17+
public Axis xAxStart, yAxStart;
18+
public int xMouseStart, yMouseStart;
19+
public double x1, x2, y1, y2;
20+
21+
public MouseAxis(Axis xAxis, Axis yAxis, int mouseX, int mouseY)
22+
{
23+
xAxStart = new ScottPlot.Axis(xAxis.min, xAxis.max, xAxis.pxSize, xAxis.inverted);
24+
yAxStart = new ScottPlot.Axis(yAxis.min, yAxis.max, yAxis.pxSize, yAxis.inverted);
25+
xMouseStart = mouseX;
26+
yMouseStart = mouseY;
27+
Pan(0, 0);
28+
}
29+
30+
public void Pan(int xMouseNow, int yMouseNow)
31+
{
32+
int dX = xMouseStart - xMouseNow;
33+
int dY = yMouseNow - yMouseStart;
34+
x1 = xAxStart.min + dX * xAxStart.unitsPerPx;
35+
x2 = xAxStart.max + dX * xAxStart.unitsPerPx;
36+
y1 = yAxStart.min + dY * yAxStart.unitsPerPx;
37+
y2 = yAxStart.max + dY * yAxStart.unitsPerPx;
38+
}
39+
40+
public void Zoom(int xMouseNow, int yMouseNow)
41+
{
42+
double dX = (xMouseNow - xMouseStart) * xAxStart.unitsPerPx;
43+
double dY = (yMouseStart - yMouseNow) * yAxStart.unitsPerPx;
44+
45+
double dXFrac = dX / (Math.Abs(dX) + xAxStart.span);
46+
double dYFrac = dY / (Math.Abs(dY) + yAxStart.span);
47+
48+
double xNewSpan = xAxStart.span / Math.Pow(10, dXFrac);
49+
double yNewSpan = yAxStart.span / Math.Pow(10, dYFrac);
50+
51+
double xNewCenter = xAxStart.center;
52+
double yNewCenter = yAxStart.center;
53+
54+
x1 = xNewCenter - xNewSpan / 2;
55+
x2 = xNewCenter + xNewSpan / 2;
56+
57+
y1 = yNewCenter - yNewSpan / 2;
58+
y2 = yNewCenter + yNewSpan / 2;
59+
}
60+
}
61+
}
Lines changed: 205 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,205 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Text;
5+
using System.Threading.Tasks;
6+
7+
namespace ScottPlot
8+
{
9+
10+
public class Axis
11+
{
12+
// user provides these
13+
public double min { get; set; }
14+
public double max { get; set; }
15+
public int pxSize { get; private set; }
16+
public bool inverted { get; set; }
17+
18+
// these are calculated
19+
public double unitsPerPx { get; private set; }
20+
public double pxPerUnit { get; private set; }
21+
public double span { get { return max - min; } }
22+
public double center { get { return (max + min)/2.0; } }
23+
24+
/// <summary>
25+
/// Single-dimensional axis (i.e., x-axis)
26+
/// </summary>
27+
/// <param name="min">lower bound (units)</param>
28+
/// <param name="max">upper bound (units)</param>
29+
/// <param name="sizePx">size of this axis (pixels)</param>
30+
/// <param name="inverted">inverted axis vs. pixel position (common for Y-axis)</param>
31+
public Axis(double min, double max, int sizePx = 500, bool inverted = false)
32+
{
33+
this.min = min;
34+
this.max = max;
35+
this.inverted = inverted;
36+
Resize(sizePx);
37+
}
38+
39+
/// <summary>
40+
/// Tell the Axis how large it will be on the screen
41+
/// </summary>
42+
/// <param name="sizePx">size of this axis (pixels)</param>
43+
public void Resize(int sizePx)
44+
{
45+
this.pxSize = sizePx;
46+
RecalculateScale();
47+
}
48+
49+
/// <summary>
50+
/// Update units/pixels conversion scales.
51+
/// </summary>
52+
public void RecalculateScale()
53+
{
54+
this.pxPerUnit = (double)pxSize / (max - min);
55+
this.unitsPerPx = (max - min) / (double)pxSize;
56+
RecalculateTicks();
57+
}
58+
59+
/// <summary>
60+
/// Shift the Axis by a specified amount
61+
/// </summary>
62+
/// <param name="Shift">distance (units)</param>
63+
public void Pan(double Shift)
64+
{
65+
min += Shift;
66+
max += Shift;
67+
RecalculateScale();
68+
}
69+
70+
/// <summary>
71+
/// Zoom in on the center of Axis by a fraction.
72+
/// A fraction of 2 means that the new width will be 1/2 as wide as the old width.
73+
/// A fraction of 0.1 means the new width will show 10 times more axis length.
74+
/// </summary>
75+
/// <param name="zoomFrac">Fractional amount to zoom</param>
76+
public void Zoom(double zoomFrac)
77+
{
78+
double newSpan = span / zoomFrac;
79+
double newCenter = center;
80+
min = newCenter - newSpan / 2;
81+
max = newCenter + newSpan / 2;
82+
RecalculateScale();
83+
}
84+
85+
/// <summary>
86+
/// Given a position on the axis (in units), return its position on the screen (in pixels).
87+
/// Returned values may be negative, or greater than the pixel width.
88+
/// </summary>
89+
/// <param name="unit">position (units)</param>
90+
/// <returns></returns>
91+
public int GetPixel(double unit)
92+
{
93+
int px = (int)((unit - min) * pxPerUnit);
94+
if (inverted) px = pxSize - px;
95+
return px;
96+
}
97+
98+
/// <summary>
99+
/// Given a position on the screen (in pixels), return its location on the axis (in units).
100+
/// </summary>
101+
/// <param name="px">position (pixels)</param>
102+
/// <returns></returns>
103+
public double GetUnit(int px)
104+
{
105+
if (inverted) px = pxSize - px;
106+
return min + (double)px * unitsPerPx;
107+
}
108+
109+
/// <summary>
110+
/// Given an arbitrary number, return the nearerest round number
111+
/// (i.e., 1000, 500, 100, 50, 10, 5, 1, .5, .1, .05, .01)
112+
/// </summary>
113+
/// <param name="target">the number to approximate</param>
114+
/// <returns></returns>
115+
private double RoundNumberNear(double target)
116+
{
117+
target = Math.Abs(target);
118+
int lastDivision = 2;
119+
double round = 1000000000000;
120+
while (round > 0.00000000001)
121+
{
122+
if (round <= target) return round;
123+
round /= lastDivision;
124+
if (lastDivision == 2) lastDivision = 5;
125+
else lastDivision = 2;
126+
}
127+
return 0;
128+
}
129+
130+
/// <summary>
131+
/// Return an array of tick objects given a custom target tick count
132+
/// </summary>
133+
public Tick[] GenerateTicks(int targetTickCount)
134+
{
135+
List<Tick> ticks = new List<Tick>();
136+
137+
if (targetTickCount > 0)
138+
{
139+
double tickSize = RoundNumberNear(((max - min) / targetTickCount) * 1.5);
140+
int lastTick = 123456789;
141+
for (int i = 0; i < pxSize; i++)
142+
{
143+
double thisPosition = i * unitsPerPx + min;
144+
int thisTick = (int)(thisPosition / tickSize);
145+
if (thisTick != lastTick)
146+
{
147+
lastTick = thisTick;
148+
double thisPositionRounded = (double)((int)(thisPosition / tickSize) * tickSize);
149+
if (thisPositionRounded > min && thisPositionRounded < max)
150+
{
151+
ticks.Add(new Tick(thisPositionRounded, GetPixel(thisPositionRounded), max - min));
152+
}
153+
}
154+
}
155+
}
156+
return ticks.ToArray();
157+
}
158+
159+
/// <summary>
160+
/// Pre-prepare recommended major and minor ticks
161+
/// </summary>
162+
public Tick[] ticksMajor;
163+
public Tick[] ticksMinor;
164+
public double pixelsPerTick = 70;
165+
private void RecalculateTicks()
166+
{
167+
double tick_density_x = pxSize / pixelsPerTick; // approx. 1 tick per this many pixels
168+
ticksMajor = GenerateTicks((int)(tick_density_x * 5)); // relative density of minor to major ticks
169+
ticksMinor = GenerateTicks((int)(tick_density_x * 1));
170+
}
171+
172+
/// <summary>
173+
/// The Tick object stores details about a single tick and can generate relevant labels.
174+
/// </summary>
175+
public class Tick
176+
{
177+
public double posUnit { get; set; }
178+
public int posPixel { get; set; }
179+
public double spanUnits { get; set; }
180+
181+
public Tick(double value, int pixel, double axisSpan)
182+
{
183+
this.posUnit = value;
184+
this.posPixel = pixel;
185+
this.spanUnits = axisSpan;
186+
}
187+
188+
public string label
189+
{
190+
get
191+
{
192+
if (spanUnits < .01) return string.Format("{0:0.0000}", posUnit);
193+
if (spanUnits < .1) return string.Format("{0:0.000}", posUnit);
194+
if (spanUnits < 1) return string.Format("{0:0.00}", posUnit);
195+
if (spanUnits < 10) return string.Format("{0:0.0}", posUnit);
196+
return string.Format("{0:0}", posUnit);
197+
}
198+
}
199+
}
200+
201+
}
202+
203+
204+
205+
}

0 commit comments

Comments
 (0)