Unable to get wave form image for audio using NAudio - c#

Unable to get wave form image for smaller duration audio stream using this code.
I am getting totally blank image. Is there any way to get correct wave form image for smaller duration audio stream. I am using NAudio's AudioFileReader function here.
Bitmap bim = new Bitmap(1800,200);
System.Drawing.Graphics g = Graphics.FromImage(bim);
using (var reader = new AudioFileReader("D:\\Test-Songs\\DawnJay.mp3"))
{
var samples = reader.Length / (reader.WaveFormat.Channels * reader.WaveFormat.BitsPerSample / 8);
var f = 0.0f;
var max = 0.0f;
// waveform will be a maximum of 4000 pixels wide:
var batch = (int)Math.Max(40, samples / 4000);
var mid = 100;
var yScale = 100;
float[] buffer = new float[batch];
int read;
var xPos = 0;
Pen pen = new Pen(Color.Red, 2.0f);
g.Clear(Color.Black);
while ((read = reader.Read(buffer, 0, batch)) == batch)
{
for (int n = 0; n < read; n++)
{
max = Math.Max(Math.Abs(buffer[n]), max);
}
int X1 = xPos;
int X2 = xPos;
float Y1 = mid + (max * yScale);
float Y2 = mid - (max * yScale);
g.DrawLine(pen,X1, Y1, X2, Y2);
max = 0;
xPos++;
}
}
bim.Save("D:\\Images\\waveform.png");

Your code here:
var batch = (int)Math.Max(40, samples / 4000);
This says that you are going to accept a minimum of 40 samples per column. For a small file this might mean that your data gets reduced to only a small number of columns of data in the output bitmap. If you are then scaling that data down to fit into a display area on screen, your audio data might disappear.
Try changing your minimum number of samples per block to a smaller value, which should give you a chance at actually visualizing small audio files. You should probably do full Min-Max calculations, or else your plots for very small files will look totally wrong.

Related

Converting Byte[] to drawing the waves in a picturebox (overflow error)

ive currently got a live voice call application using UDP and im trying to get the incoming voice to be plotted like a wave form in a picturebox. I looked at 2 open source ways for doing this and both bring back the overflow error when i try to draw a line. My code is below:
Incoming Voice via UDP:
while (LISTENING)
{
byte[] b = UDP_LISTENER.Receive(ref endPoint); //incoming audio bytes
byte[] decoded = listenerThreadState.Codec.Decode(b, 0, b.Length);
WAVE_PLAYER.AddSamples(decoded, 0, decoded.Length);//plays it out the speakers
float[] x = FloatArrayFromByteArray(decoded);//converts to a float array
DrawNormalizedAudios(ref x, PBWaveForm, Color.Red);//plots the wave form
}
Function to produce the waveform:
public static void DrawNormalizedAudios(ref float[] data, PictureBox pb,
Color color)
{
Bitmap bmp;
if (pb.Image == null)
{
bmp = new Bitmap(pb.Width, pb.Height);
}
else
{
bmp = (Bitmap)pb.Image;
}
int BORDER_WIDTH = 5;
int width = bmp.Width - (2 * BORDER_WIDTH);
int height = bmp.Height - (2 * BORDER_WIDTH);
using (Graphics g = Graphics.FromImage(bmp))
{
g.Clear(Color.Black);
Pen pen = new Pen(color);
int size = data.Length;
for (int iPixel = 0; iPixel < width; iPixel++)
{
// determine start and end points within WAV
int start = (int)((float)iPixel * ((float)size / (float)width));
int end = (int)((float)(iPixel + 1) * ((float)size / (float)width));
float min = float.MaxValue;
float max = float.MinValue;
for (int i = start; i < end; i++)
{
float val = data[i];
min = val < min ? val : min;
max = val > max ? val : max;
}
int yMax = BORDER_WIDTH + height - (int)((max + 1) * .5 * height);
int yMin = BORDER_WIDTH + height - (int)((min + 1) * .5 * height);
g.DrawLine(pen, iPixel + BORDER_WIDTH, yMax,
iPixel + BORDER_WIDTH, yMin);
}
}
pb.Image = bmp;
}
Now there was 2 funcitons but both of them gave the same error which was this exacly: "An unhandled exception of type 'System.OverflowException' occurred in System.Drawing.dll
Additional information: Overflow error."
So its gotta be a problem with the bytes of audio right? i tried putting the byte[] b instead of decoded but that didnt make it far and i dont think thats the problem. The audio being sent is sending every 50ms, so is it a fact that it just cant keep up? theres apps for drawing which keep up with more than 50ms so that cant be it. When i googled the error it said something to do with the boundarys but i didnt really understand it, any help is appriciated

Win2D Keystone Correction

I'm trying to use Win2D/C# to project an image using a overhead projector and I need to use a Win2D effect to do Keystone Correction (pre-warp the image) as the final step.
Basically I'm drawing a rectangle, then trying to use a Transform3DEffect to warp it before rendering. I can't figure out what Matrix transformation combination to use to get it to work. Doing a full camera projection seems like overkill since I only need warping in one direction (see image below). What transforms should I use?
Using an Image like following, can get you a similar effect.
https://i.stack.imgur.com/5QnEm.png
I am unsure what results in the "bending".
Code for creating the displacement map (with GDI+, because you can set pixels fast).
The LockBitmap you can find here
static void DrawDisplacement(int width, int height, LockBitmap lbmp)
{
for (int x = 0; x < width; x++)
for (int y = 0; y < height; y++)
{
int roff = (int)((((width >> 1) - x) / (float)(width >> 1)) * ((height - y) / (float)height) * 127);
int goff = 0;
lbmp.SetPixel(x, y, Color.FromArgb(127 - roff, 127 - goff, 0));
}
}
Drawing in Win2D looks something like this, where displacementImage is the loaded file and offscreen, is a 'CanvasRenderTarget' on which I drew the grid.
//Scaling for fitting the image to the content
ICanvasImage scaledDisplacement = new Transform2DEffect
{
BorderMode = EffectBorderMode.Hard,
Source = displacementImage,
TransformMatrix = Matrix3x2.CreateScale((float) (sender.Size.Width / displacementImage.Bounds.Width), (float) (sender.Size.Height / displacementImage.Bounds.Height)),
Sharpness = 1f,
BufferPrecision = CanvasBufferPrecision.Precision32Float,
InterpolationMode = CanvasImageInterpolation.HighQualityCubic,
};
//Blurring, for a better result
ICanvasImage displacement = new GaussianBlurEffect
{
BorderMode = EffectBorderMode.Hard,
Source = scaledDisplacement,
BufferPrecision = CanvasBufferPrecision.Precision32Float,
BlurAmount = 2,
Optimization = EffectOptimization.Quality,
};
ICanvasImage graphicsEffect = new DisplacementMapEffect
{
Source = offscreen,
Displacement = displacement,
XChannelSelect = EffectChannelSelect.Red,
YChannelSelect = EffectChannelSelect.Green,
Amount = 800,//change for more or less displacement
BufferPrecision = CanvasBufferPrecision.Precision32Float,
};

Rotate raw pixel data of an image 180 degrees

I'm trying to rotate raw pixel data from a DICOM file by 180 degrees (or flipped). I've successfully flipped the image correctly, however, upon writing the pixel data back to the file (in this case it's a DICOM file) and displaying it. The final output of the image is not correct.
Below is the sample a sample of the image I'm trying to flip 180 /mirror.
Here's the code I'm using to perform the flipping:
string file = #"adicomfile.dcm";
DicomFile df = new DicomFile();
df.Load(file);
// Get the amount of bits per pixel from the DICOM header.
int bitsPerPixel = df.DataSet[DicomTags.BitsAllocated].GetInt32(0, 0);
// Get the raw pixel data from the DICOM file.
byte[] bytes = df.DataSet[DicomTags.PixelData].Values as byte[];
// Get the width and height of the image.
int width = df.DataSet[DicomTags.Columns].GetInt32(0, 0);
int height = df.DataSet[DicomTags.Rows].GetInt32(0, 0);
byte[] original = bytes;
byte[] mirroredPixels = new byte[width * height * (bitsPerPixel / 8)];
width *= (bitsPerPixel / 8);
// The mirroring / image flipping.
for (int i = 0; i < original.Length; i++)
{
int mod = i % width;
int x = ((width - mod - 1) + i) - mod;
mirroredPixels[i] = original[x];
}
df.DataSet[DicomTags.PixelData].Values = mirroredPixels;
df.Save(#"flippedicom.dcm", DicomWriteOptions.Default);
And here's my output (incorrect). The white and distortion is not the desired output.
I'm using ClearCanvas DICOM library, however this shouldn't matter as I'm only trying to manipulate the raw pixel data contained within the file itself.
The desired output would preferably look like the original, but flipped 180 / mirrored.
Some assistance would be greatly appreciated. I've tried my best searching SO, but to no avail.
It took a while, but I ended up solving my problem by using a method from a Java library. You can see the class here.
string file = #"adicomfile.dcm";
DicomFile df = new DicomFile();
df.Load(file);
// Get the amount of bits per pixel from the DICOM header.
int bitsPerPixel = df.DataSet[DicomTags.BitsAllocated].GetInt32(0, 0);
// Get the raw pixel data from the DICOM file.
byte[] bytes = df.DataSet[DicomTags.PixelData].Values as byte[];
// Get the width and height of the image.
int width = df.DataSet[DicomTags.Columns].GetInt32(0, 0);
int height = df.DataSet[DicomTags.Rows].GetInt32(0, 0);
byte[] newBytes = new byte[height * width * (bitsPerPixel / 8)];
int stride = bitsPerPixel / 8;
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width * stride; x++)
{
newBytes[((height - y - 1) * (width * stride)) + x] = bytes[(y * (width * stride)) + x];
}
}
// Set patient orientation.
df.DataSet[DicomTags.PatientOrientation].Values = #"A\L";
// The pixel data of the DICOM file to the flipped/mirrored data.
df.DataSet[DicomTags.PixelData].Values = mirroredPixels;
// Save the DICOM file.
df.Save(#"flippedicom.dcm", DicomWriteOptions.Default);
The output was correct and I was able to continue other modifications to the raw pixel data.
Thank you all for the pointers.

How to render audio waveform?

Is there any audio/programming-related stack-exchange site?
I'm trying to make a wave form in WinForms
What algorithm should I use?
For example, if I have 200 samples per pixel (vertical line), should I draw the lowest and the highest sample from that portion of 200 samples? Or should I draw average of low and high samples? Maybe both in different colors?
This will help you to generate waveform from audio file using nAudio in C#...
using NAudio.Wave;
using System;
using System.Collections.Generic;
using System.Drawing;
using System.IO;
using System.Linq;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
public partial class test : System.Web.UI.Page
{
protected void Page_Load(object sender, EventArgs e)
{
string strPath = Server.MapPath("audio/060.mp3");
string SongID = "2";
byte[] bytes = File.ReadAllBytes(strPath);
WriteToFile(SongID,strPath, bytes);
Response.Redirect("Main.aspx");
}
private void WriteToFile(string SongID, string strPath, byte[] Buffer)
{
try
{
int samplesPerPixel = 128;
long startPosition = 0;
//FileStream newFile = new FileStream(GeneralUtils.Get_SongFilePath() + "/" + strPath, FileMode.Create);
float[] data = FloatArrayFromByteArray(Buffer);
Bitmap bmp = new Bitmap(1170, 200);
int BORDER_WIDTH = 5;
int width = bmp.Width - (2 * BORDER_WIDTH);
int height = bmp.Height - (2 * BORDER_WIDTH);
NAudio.Wave.Mp3FileReader reader = new NAudio.Wave.Mp3FileReader(strPath, wf => new NAudio.FileFormats.Mp3.DmoMp3FrameDecompressor(wf));
NAudio.Wave.WaveChannel32 channelStream = new NAudio.Wave.WaveChannel32(reader);
int bytesPerSample = (reader.WaveFormat.BitsPerSample / 8) * channelStream.WaveFormat.Channels;
using (Graphics g = Graphics.FromImage(bmp))
{
g.Clear(Color.White);
Pen pen1 = new Pen(Color.Gray);
int size = data.Length;
string hexValue1 = "#009adf";
Color colour1 = System.Drawing.ColorTranslator.FromHtml(hexValue1);
pen1.Color = colour1;
Stream wavestream = new NAudio.Wave.Mp3FileReader(strPath, wf => new NAudio.FileFormats.Mp3.DmoMp3FrameDecompressor(wf));
wavestream.Position = 0;
int bytesRead1;
byte[] waveData1 = new byte[samplesPerPixel * bytesPerSample];
wavestream.Position = startPosition + (width * bytesPerSample * samplesPerPixel);
for (float x = 0; x < width; x++)
{
short low = 0;
short high = 0;
bytesRead1 = wavestream.Read(waveData1, 0, samplesPerPixel * bytesPerSample);
if (bytesRead1 == 0)
break;
for (int n = 0; n < bytesRead1; n += 2)
{
short sample = BitConverter.ToInt16(waveData1, n);
if (sample < low) low = sample;
if (sample > high) high = sample;
}
float lowPercent = ((((float)low) - short.MinValue) / ushort.MaxValue);
float highPercent = ((((float)high) - short.MinValue) / ushort.MaxValue);
float lowValue = height * lowPercent;
float highValue = height * highPercent;
g.DrawLine(pen1, x, lowValue, x, highValue);
}
}
string filename = Server.MapPath("image/060.png");
bmp.Save(filename);
bmp.Dispose();
}
catch (Exception e)
{
}
}
public float[] FloatArrayFromStream(System.IO.MemoryStream stream)
{
return FloatArrayFromByteArray(stream.GetBuffer());
}
public float[] FloatArrayFromByteArray(byte[] input)
{
float[] output = new float[input.Length / 4];
for (int i = 0; i < output.Length; i++)
{
output[i] = BitConverter.ToSingle(input, i * 4);
}
return output;
}
}
Try dsp.stackexchange.com
At 200 samples per pixel, there are several approaches you can try. Whatever you do, it often works best to draw each vertical line both above and below 0, ie. treat positive and negative sample values seperately. Probably the easiest is to just calculate an RMS. At such a low resolution peak values will probably give you a misleading representation of the waveform.
You can use AudioControl from code project.
and see this one: Generating various audio waveforms in C#
these projects may be useful for you if implement your code originally:
High-Speed-Feature-Rich-and-Easy-To-Use-Graphs
and this
Incase anyone runs into this:
You can treat the samples per pixel as your zoom level, at higher levels (zoomed out more) you will probably want to subsample that for performance reasons.
You will most likely want a fixed width that fits on the screen to draw on and use virtual scrolling (so you don't potentially have a draw area of several million pixels).
You can calculate the value for each pixel by iterating over the audio data with: skip (scroll position * samples per pixel) + (pixel * samples per pixel) take samples per pixel.
This allows for performant infinite zoom and scroll as you only read and draw the minimum amount to fill the view.
The scroll width is calculated with audio data length / samples per pixel.
Audio samples are generally shown in one of two ways, the peak value of the sample range or the rms value. The rms value is calculated by summing the squares of all values in the sample range, divide the sum by sample length, the rms value if the squareroot of this (rms will be a bit higher than average and is a good measure of perceived loudness)
You can increase performance in multiple ways such as increasing sub sampling (causes loss of detail), throttling the scroll and making the draw requests cancelable incase new scroll fires before previous is rendered.
just to document it, if you want to make the audio file fill the width of the output image
samplesPerPixel = (reader.Length / bytesPerSample) / width ;

Draw types of signals with changing amplitude and frequency on-the-fly

As title said:
I have form with 2 trackbars. One for frequency and one for amplitude. I set up timer for on-the-fly changing.
private void timer1_Tick(object sender, EventArgs e)
{
float amplitude, frequency;
amplitude = Convert.ToSingle(trackBar1.Value) / 100;
label1.Text = amplitude.ToString() + " V";
frequency = trackBar2.Value;
label2.Text = frequency.ToString() + " Hz";
}
I have also 4 radio-buttons to decide, which type of signal will be displayed (sine, square, triangle, sawthoot)
Now I have this implemented with ImageList (change image of signal).
How can I draw type of signal and regulate it with with trackbars? So it will be like in osciloscope.
Thanks for your answers and code.
Lets start by creating the different signal types, this is a function that creates one wavelength of amplitude 1:
private PointF[] CreateBaseSignal(SignalType signalType)
{
switch (signalType)
{
case SignalType.Sine:
const int oversampling = 32;
PointF[] signal = new PointF[oversampling];
for (int i = 0; i < signal.Length; i++)
{
signal[i].X = (float) i / oversampling;
signal[i].Y = Convert.ToSingle(Math.Sin((double) i / oversampling * 2 * Math.PI));
}
return signal;
case SignalType.Square:
return new PointF[]
{
new PointF(0.0f, -1.0f),
new PointF(0.5f, -1.0f),
new PointF(0.5f, 1.0f),
new PointF(1.0f, 1.0f),
};
case SignalType.Triangle:
return new PointF[]
{
new PointF(0.0f, -1.0f),
new PointF(0.5f, 1.0f),
};
case SignalType.Sawtooth:
return new PointF[]
{
new PointF(0.0f, -1.0f),
new PointF(1.0f, 1.0f),
};
default:
throw new ArgumentException("Invalid signal type", "signalType");
}
}
Then we create the actual signal with the selected amplitude and frequency:
private PointF[] CreateSignal(PointF[] baseSignal, float frequency, float amplitude)
{
PointF[] signal = new PointF[Convert.ToInt32(Math.Ceiling(baseSignal.Length * frequency))];
for(int i = 0; i < signal.Length; i++)
{
signal[i].X = baseSignal[i % baseSignal.Length].X / frequency + (i / baseSignal.Length) / frequency;
signal[i].Y = baseSignal[i % baseSignal.Length].Y * amplitude;
}
return signal;
}
Before attempting to plot this signal to a PictureBox, we scale the signal to fit the width and height:
private PointF[] ScaleSignal(PointF[] signal, int width, int height)
{
const float maximumAmplitude = 10.0f;
PointF[] scaledSignal = new PointF[signal.Length];
for(int i = 0; i < signal.Length; i++)
{
scaledSignal[i].X = signal[i].X * width;
scaledSignal[i].Y = signal[i].Y * height / 2 / maximumAmplitude;
}
return scaledSignal;
}
Using Graphics.DrawLine to plot the signal is way better than Bitmap.SetPixel, since the data points will be connected even at high frequencies. Bitmap.SetPixel is also very slow, you really need to use Bitmap.LockBits and unsafe code for manipulating single pixels to achieve any decent performance. Using Graphics.DrawLine, you also have control over line width, anti-aliasing etc.
Since we have stored the signal in a PointF array, we can use the simple Graphics.DrawLines method to plot the signal instead of iterating over the data points:
private void PlotSignal(PointF[] signal, PictureBox pictureBox)
{
Bitmap bmp = new Bitmap(pictureBox.ClientSize.Width, pictureBox.ClientSize.Height);
signal = ScaleSignal(signal, bmp.Width, bmp.Height); // Scale signal to fit image
using(Graphics gfx = Graphics.FromImage(bmp))
{
gfx.SmoothingMode = SmoothingMode.HighQuality;
gfx.TranslateTransform(0, bmp.Height / 2); // Move Y=0 to center of image
gfx.ScaleTransform(1, -1); // Make positive Y axis point upward
gfx.DrawLine(Pens.Black, 0, 0, bmp.Width, 0); // Draw zero axis
gfx.DrawLines(Pens.Blue, signal); // Draw signal
}
// Make sure the bitmap is disposed the next time around
Image old = pictureBox.Image;
pictureBox.Image = bmp;
if(old != null)
old.Dispose();
}
If you redraw the signal often, you probably want to reuse the the Bitmap and Graphics objects instead of creating new ones each time. Just remember to call Graphics.Clear between each redraw.
Putting everything together in one big statement:
PlotSignal(
CreateSignal(
CreateBaseSignal(signalType),
frequency,
amplitude),
thePictureBox);
If you're after a fast plotting library, I really like Dynamic Data Display
Dynamic Data Display
This is a WPF component, but for fast, smooth drawing applications I really think it is worthwhile to port to WPF sooner rathar than later. It feels like you're not too far into your project at the moment anyway.
Development for WPF seems to have stopped for this component (although it continues to be worked on for Silverlight). The documentation is terrible but the source code is available from the link above so you can extend it as needed (it's quite well written and very extensible) and the source is invaluable as a substitute for the near complete lack of any documentation.
Assuming you want to plot a sin wave on a picture box control, create a picture box control on your form then perform the following:
int width = pictureBox1.Width;
int height = pictureBox1.Height;
Bitmap b = new Bitmap(width, height);
for (int i = 0; i < width; i++)
{
int y = (int)((Math.Sin((double)i * 2.0 * Math.PI / width) + 1.0) * (height - 1) / 2.0);
b.SetPixel(i, y, System.Drawing.Color.Red);
}
pictureBox1.Image = b;

Categories