Created
May 9, 2014 20:57
-
-
Save DomDomHaas/f0a619b7fc0e6fc805a9 to your computer and use it in GitHub Desktop.
Spectrum Analyzer converted from c++ to c#
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
using System; | |
/* | |
* main.cpp - spectrum_analyzer | |
* https://bitbucket.org/andor44/spectrum_analyzer | |
* | |
* Copyright (c) 2011 TÖRÖK Attila <torokati44@gmail.com> | |
* | |
* This software is provided 'as-is', without any express or implied | |
* warranty. In no event will the authors be held liable for any damages | |
* arising from the use of this software. | |
* | |
* Permission is granted to anyone to use this software for any purpose, | |
* including commercial applications, and to alter it and redistribute it | |
* freely, subject to the following restrictions: | |
* | |
* 1. The origin of this software must not be misrepresented; you must not | |
* claim that you wrote the original software. If you use this software | |
* in a product, an acknowledgment in the product documentation would be | |
* appreciated but is not required. | |
* | |
* 2. Altered source versions must be plainly marked as such, and must not be | |
* misrepresented as being the original software. | |
* | |
* 3. This notice may not be removed or altered from any source | |
* distribution. | |
*/ | |
/* | |
* forked by andor44, same license applies | |
*/ | |
public static class GlobalMembersConstants | |
{ | |
// this file is public domain | |
public static readonly uint[] pot = {2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192, 16384, 32768, 65536, 131072, 262144, 524288, 1048576, 2097152}; | |
public const uint num_threads = 2; | |
// ---- CONFIGURATION ---- | |
public static uint window_size = 2048; // should be a power of two | |
// ^ Erm, not the rendering window that displays the spectrum, | |
// but the window of samples to be analyzed at once. | |
public static uint band_width = 16; // should be an integer divisor of window_size/2 | |
public static float hamming_alpha = 0.54f; | |
public static float height_coeff = 0.005f; | |
public static float fading_coeff = 0.5f; | |
internal static void remove_whitespace(string str) | |
{ | |
for (int i = 0;i < str.Length;i++) | |
{ | |
if (str[i] == ' ') | |
{ | |
str = str.Remove(i,1); | |
i--; | |
} | |
} | |
} | |
//C++ TO C# CONVERTER TODO TASK: The original C++ template specifier was replaced with a C# generic specifier, which may not produce the same behavior: | |
//ORIGINAL LINE: template <class T, uint N> | |
public static uint size<T, uint N>(T[] x) | |
{ | |
return N; | |
} | |
internal static bool is_pot(uint val) | |
{ | |
uint len = GlobalMembersConstants.size(pot); | |
for (uint i = 0; i < len; ++i) | |
{ | |
if (pot[i] == val) | |
{ | |
return true; | |
} | |
} | |
return false; | |
} | |
internal static string int_to_str(int val) | |
{ | |
std.stringstream s = new std.stringstream(); | |
s << val; | |
return s.str(); | |
} | |
internal static string conv_str = new string(new char[200]); | |
internal static string construct_text(sf.Sound snd, uint si) | |
{ | |
conv_str = string.Format("Offset: {0,4:f2} \nVolume: {1:f0} \nStarting[]: {2:D}", snd.GetPlayingOffset(), snd.GetVolume(), si); | |
return (string)conv_str; | |
} | |
} | |
public static class GlobalMembersMain | |
{ | |
internal static void load_cfg(string loc) | |
{ | |
std.ifstream reader = new std.ifstream(); | |
reader.open(loc); | |
while (!reader.eof()) | |
{ | |
string line; | |
//reader >> line; | |
getline(reader,line); | |
//std::cout << line << std::endl; | |
string key; | |
string value; | |
if (line[0] == '#') // the whole line is a comment | |
continue; | |
else if (!(line.IndexOf("#") == string.npos)) // line contains a comment, ignore everything after the comment | |
{ | |
line = line.Substring(0,line.IndexOf("#")); | |
} | |
// we still assume it's not good | |
if (!(line.IndexOf("=") == string.npos)) | |
{ | |
key = line.Substring(0,line.IndexOf("=")); // the part before the equation sign is the key | |
GlobalMembersConstants.remove_whitespace(key); | |
// we oughta have our key by now | |
value = line.Substring(line.IndexOf("=") + 1, line.Length); | |
GlobalMembersConstants.remove_whitespace(value); | |
if (key.CompareTo("window_size") == 0) | |
{ | |
uint v = Convert.ToInt32(value); | |
if (GlobalMembersConstants.is_pot(v)) | |
{ | |
GlobalMembersConstants.window_size = v; | |
} | |
Console.Write("setting window_size to: "); | |
Console.Write(v); | |
Console.Write("\n"); | |
} | |
else if (key.CompareTo("band_width") == 0) | |
{ | |
uint v = Convert.ToInt32(value); | |
if (GlobalMembersConstants.is_pot(v)) | |
{ | |
GlobalMembersConstants.band_width = v; | |
} | |
Console.Write("setting band_width to: "); | |
Console.Write(v); | |
Console.Write("\n"); | |
} | |
else if (key.CompareTo("hamming_alpha") == 0) | |
{ | |
float v = Convert.ToDouble(value); | |
if (v > 0.0f && v < 1.0f) | |
{ | |
GlobalMembersConstants.hamming_alpha = v; | |
} | |
Console.Write("setting hamming_alpha to: "); | |
Console.Write(v); | |
Console.Write("\n"); | |
} | |
else if (key.CompareTo("height_coeff") == 0) | |
{ | |
float v = Convert.ToDouble(value); | |
if (v > 0.0f) // we allow semi-sane values, negative values wouldn't make sense | |
{ | |
GlobalMembersConstants.height_coeff = v; | |
} | |
Console.Write("setting height_coeff to: "); | |
Console.Write(v); | |
Console.Write("\n"); | |
} | |
else if (key.CompareTo("fading_coeff") == 0) | |
{ | |
float v = Convert.ToDouble(value); | |
if (v > 0.0f && v <= 1.0f) // 1f means it's never fading, meh | |
{ | |
GlobalMembersConstants.fading_coeff = v; | |
} | |
Console.Write("setting fading_coeff to: "); | |
Console.Write(v); | |
Console.Write("\n"); | |
} | |
else | |
{ | |
Console.Write("Unknown key '" + key + "'. Ignoring.\n"); | |
} | |
// these will do for now | |
} | |
} | |
reader.close(); | |
} | |
static int Main(int argc, string[] args) | |
{ | |
// ---- LOADING SOUND ---- | |
// obsolete, will redo later | |
bool display_stats = true; | |
if (argc != 2) | |
{ | |
Console.Write("usage: <filename>"); | |
Console.Write("\n"); | |
return 4; | |
} | |
string filename = args[1]; | |
Console.Write("Reading config file..."); | |
Console.Write("\n"); | |
GlobalMembersMain.load_cfg("config.cfg"); | |
// it must be defined here, otherwise it'll be strrev("kcuf")(tm)'d | |
uint num_bands = GlobalMembersConstants.window_size / GlobalMembersConstants.band_width / 2; | |
Console.Write("Loading sound file..."); | |
std.cout.flush(); | |
sf.SoundBuffer sound_buffer = new sf.SoundBuffer(); | |
if (!sound_buffer.LoadFromFile(filename)) | |
{ | |
Console.Write("\nFailed to load the sound file! Exiting.\n"); | |
Console.Write("Bye!\n"); | |
return 1; | |
} | |
if ((sound_buffer.GetChannelsCount() != 2) && (sound_buffer.GetChannelsCount() != 1)) | |
{ | |
Console.Write("\nToo many channels! Exiting.\n"); | |
Console.Write("Bye!\n"); | |
return 2; | |
} | |
Console.Write("OK!\n"); | |
Console.Write(sound_buffer.GetSamplesCount()); | |
Console.Write(" samples\n"); | |
Console.Write(sound_buffer.GetChannelsCount()); | |
Console.Write(" channels\n"); | |
Console.Write(sound_buffer.GetSampleRate()); | |
Console.Write(" Hz sampling rate\n"); | |
Console.Write(sound_buffer.GetDuration()); | |
Console.Write(" seconds long\n\n"); | |
// ---- INITIALIZING ANALYZER ---- | |
Console.Write("Allocating buffers..."); | |
std.cout.flush(); | |
float[] float_samples = new float[sound_buffer.GetSamplesCount() / sound_buffer.GetChannelsCount()]; | |
if (float_samples == 0) | |
{ | |
Console.Write("\nNot enough memory! Exiting.\n"); | |
return 3; | |
} | |
float[] hamming_window = new float[GlobalMembersConstants.window_size]; | |
if (hamming_window == 0) | |
{ | |
Console.Write("\nNot enough memory! Exiting.\n"); | |
float_samples = null; | |
Console.Write("Bye!\n"); | |
return 3; | |
} | |
float[] bands = new float[num_bands]; | |
if (bands == 0) | |
{ | |
Console.Write("\nNot enough memory! Exiting.\n"); | |
float_samples = null; | |
hamming_window = null; | |
Console.Write("Bye!\n"); | |
return 3; | |
} | |
// spawn threads | |
if (!fftw_init_threads()) | |
{ | |
Console.Write("Unable to spawn threads. Quitting."); | |
Console.Write("\n"); | |
return 1; | |
} | |
fftw_plan_with_nthreads(GlobalMembersConstants.num_threads); | |
fftw_complex in_buffer; | |
fftw_complex out_buffer; | |
in_buffer = (fftw_complex) fftw_malloc(sizeof(fftw_complex) * GlobalMembersConstants.window_size); | |
if (in_buffer == 0) | |
{ | |
Console.Write("\nNot enough memory! Exiting.\n"); | |
float_samples = null; | |
hamming_window = null; | |
bands = null; | |
Console.Write("Bye!\n"); | |
fftw_cleanup_threads(); | |
return 3; | |
} | |
out_buffer = (fftw_complex) fftw_malloc(sizeof(fftw_complex) * GlobalMembersConstants.window_size); | |
if (out_buffer == 0) | |
{ | |
Console.Write("\nNot enough memory! Exiting.\n"); | |
float_samples = null; | |
hamming_window = null; | |
bands = null; | |
fftw_free(in_buffer); | |
Console.Write("Bye!\n"); | |
fftw_cleanup_threads(); | |
return 3; | |
} | |
Console.Write("OK!\n\n"); | |
Console.Write("Initializing buffers..."); | |
std.cout.flush(); | |
for (uint i = 0; i < GlobalMembersConstants.window_size; ++i) | |
{ | |
hamming_window[i] = GlobalMembersConstants.hamming_alpha - (1.0f - GlobalMembersConstants.hamming_alpha) * Math.Cos((2.0f * Math.PI * i) / (GlobalMembersConstants.window_size - 1.0f)); | |
} | |
if (sound_buffer.GetChannelsCount() == 1) | |
{ | |
for (uint i = 0; i < sound_buffer.GetSamplesCount(); ++i) | |
{ | |
float_samples[i] = sound_buffer.GetSamples()[i] / ((sound_buffer.GetSamples()[i] > 0) ? 32767.0f : 32768.0f); | |
} | |
} | |
else | |
{ | |
for (uint i = 0; i < sound_buffer.GetSamplesCount() / 2; ++i) | |
{ | |
float_samples[i] = (sound_buffer.GetSamples()[2 * i] + sound_buffer.GetSamples()[2 * i + 1]); | |
float_samples[i] /= ((float_samples[i] > 0) ? 65534.0f : 65536.0f); | |
} | |
} | |
Console.Write("OK!\n\n"); | |
Console.Write("Planning DFT..."); | |
std.cout.flush(); | |
fftw_plan plan = fftw_plan_dft_1d(GlobalMembersConstants.window_size, in_buffer, out_buffer, FFTW_FORWARD, FFTW_PATIENT); | |
Console.Write("OK!\n\n"); | |
sf.RenderWindow win = new sf.RenderWindow(sf.VideoMode(1024,768), "Spectrum analyzer by torokati44, forked by andor44", sf.Style.Titlebar | sf.Style.Close); | |
win.UseVerticalSync(true); | |
win.PreserveOpenGLStates(true); | |
win.Clear(); | |
sf.PostFX post_fx = new sf.PostFX(); | |
if (sf.PostFX.CanUsePostFX()) | |
{ | |
// nemtom mi köze van az inceptionhoz de biztos :D vagy arra a githubos példaprogramra gondolsz? | |
// Inception, huh?! :D | |
post_fx.LoadFromMemory("texture frame\n" + "float coeff\n" + "\n" + "effect\n" + "{\n" + " _out = frame(_in) * coeff;\n" + " _out.a = 1.0;" + "}\n"); | |
post_fx.SetTexture("frame", null); | |
post_fx.SetParameter("coeff", GlobalMembersConstants.fading_coeff); | |
} | |
Console.Write("Starting the main loop.\n\n"); | |
// ---- MAIN LOOP ---- | |
sf.Sound sound = new sf.Sound(sound_buffer); | |
sound.SetVolume(85); | |
sound.Play(); | |
sf.String text = new sf.String(); // TODO: REMOVE , duration; | |
sf.Font f = new sf.Font(); | |
f.LoadFromFile("/usr/share/fonts/TTF/TerminusMedium.ttf", 64); | |
text.SetFont(f); | |
text.SetSize(32); | |
text.SetPosition(12,4); | |
sf.Shape dur = new sf.Shape(); | |
sf.Shape offs = new sf.Shape(); | |
dur = sf.Shape.Rectangle(sf.Vector2f(20,720), sf.Vector2f(1001,740), sf.Color(250,10,10,200), 3.0f, sf.Color(250,10,10,100)); | |
dur.EnableFill(false); | |
sf.Event event = new sf.Event(); | |
while ((sound.GetStatus() == sf.Sound.Playing || sound.GetStatus() == sf.Sound.Paused) && win.IsOpened()) | |
{ | |
while (win.GetEvent(event)) | |
{ | |
switch (event.Type) | |
{ | |
case sf.Event.Closed: | |
{ | |
win.Close(); | |
} | |
break; | |
case sf.Event.KeyPressed: | |
{ | |
switch (event.Key.Code) | |
{ | |
case sf.Key.Escape: | |
{ | |
win.Close(); | |
} | |
break; | |
case sf.Key.Space: | |
sound.GetStatus() == sf.Sound.Paused ? sound.Play() : sound.Pause(); | |
break; | |
case sf.Key.F1: | |
display_stats = !display_stats; | |
break; | |
case sf.Key.Right: | |
{ | |
float pos = sound.GetPlayingOffset() + 5.0f; | |
Console.Write("Seeking to "); | |
Console.Write(pos); | |
Console.Write(" (+5)"); | |
sound.SetPlayingOffset(pos); | |
} | |
break; | |
case sf.Key.Left: | |
{ | |
float pos = sound.GetPlayingOffset() - 5.0f; | |
Console.Write("Seeking to "); | |
Console.Write(pos); | |
Console.Write(" (-5)"); | |
sound.SetPlayingOffset(pos); | |
} | |
break; | |
default: | |
{ | |
} | |
break; | |
} | |
} | |
break; | |
case sf.Event.MouseButtonPressed: | |
{ | |
float pos = sound_buffer.GetDuration() * ((float)event.MouseButton.X / (float)win.GetWidth()); | |
Console.Write("Seeking to "); | |
Console.Write(pos); | |
Console.Write(" seconds.\n"); | |
sound.SetPlayingOffset(pos); | |
} | |
break; | |
case sf.Event.MouseWheelMoved: | |
{ | |
if ((event.MouseWheel.Delta < 0 && sound.GetVolume() <= 1) || (event.MouseWheel.Delta > 0 && sound.GetVolume() >= 98)) | |
{ | |
break; | |
} | |
else | |
{ | |
sound.SetVolume(sound.GetVolume() + event.MouseWheel.Delta * 2); | |
break; | |
} | |
} | |
break; | |
default: | |
{ | |
} | |
break; | |
} | |
} | |
if (!win.IsOpened()) | |
{ | |
break; | |
} | |
uint starting_index = (uint)(sound.GetPlayingOffset() * sound_buffer.GetSampleRate()); | |
if (starting_index > sound_buffer.GetSamplesCount() - GlobalMembersConstants.window_size) | |
{ | |
starting_index = sound_buffer.GetSamplesCount() - GlobalMembersConstants.window_size; | |
} | |
for (uint i = 0; i < GlobalMembersConstants.window_size; ++i) | |
{ | |
in_buffer[i][0] = float_samples[i + starting_index] * hamming_window[i]; | |
in_buffer[i][1] = 0; | |
} | |
fftw_execute(plan); | |
for (uint i = 0; i < num_bands; ++i) | |
{ | |
bands[i] = 0.0f; | |
for (uint j = 0; j < GlobalMembersConstants.band_width; ++j) | |
{ | |
bands[i] += Math.Sqrt(Math.Pow(out_buffer[i * GlobalMembersConstants.band_width + j][0], 2) + Math.Pow(out_buffer[i * GlobalMembersConstants.band_width + j][1], 2)); | |
} | |
} | |
if (sf.PostFX.CanUsePostFX()) | |
{ | |
win.Draw(post_fx); // just fades | |
} | |
else | |
{ | |
win.Clear(); // clears totally | |
} | |
glBegin(GL_LINE_STRIP); | |
for (uint i = 0; i < num_bands; ++i) | |
{ | |
float x = (float)i / (float)(num_bands - 1); // 0..1 | |
float y = bands[i] * GlobalMembersConstants.height_coeff; // 0..1 | |
float red = (y > 0.5f) ? 1.0f : ((y * 2.0f)); | |
float green = (y > 0.5f) ? (2.0f - (2.0f * y)) : 1.0f; | |
glColor3f(red, green, 0.0f); | |
glVertex2f(x * 2.0f - 1.0f, y * 2.0f - 1.0f); // -1..1 | |
} | |
glEnd(); | |
if (display_stats) | |
{ | |
text.SetText(GlobalMembersConstants.construct_text(sound, starting_index)); | |
offs = sf.Shape.Rectangle(sf.Vector2f(22,722), sf.Vector2f(22 + 978 * (sound.GetPlayingOffset() / sound_buffer.GetDuration()), 738), sf.Color(250,10,10,120)); | |
offs.EnableFill(true); | |
win.Draw(offs); | |
win.Draw(dur); | |
win.Draw(text); | |
} | |
win.Display(); | |
} | |
// ---- UNINITIALIZING ---- | |
Console.Write("\nPeacefully freeing everything...\n"); | |
fftw_destroy_plan(plan); | |
fftw_free(in_buffer); | |
fftw_free(out_buffer); | |
fftw_cleanup(); | |
fftw_cleanup_threads(); | |
float_samples = null; | |
hamming_window = null; | |
bands = null; | |
win.Close(); | |
Console.Write("Bye!\n"); | |
} | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment