play audio periodically from a memory stream c# - c#

I have some buffer containing samples of sinus which I want to be played from a memory stream periodically. Is there any efficient way to play it without having gaps in time? I try to make my own signal generator (I know that are some libraries providing that but I want to generate it by myself).
The platform is Windows Phone 8.1 silverlight
Update: the code is taken from this forum somewhere
public static void PlayBeep(UInt16 frequency, int msDuration, UInt16 volume = 16383)
{
var mStrm = new MemoryStream();
BinaryWriter writer = new BinaryWriter(mStrm);
const double TAU = 2 * Math.PI;
int samplesPerSecond = 44100;
{
double theta = frequency * TAU / (double)samplesPerSecond;
// 'volume' is UInt16 with range 0 thru Uint16.MaxValue ( = 65 535)
// we need 'amp' to have the range of 0 thru Int16.MaxValue ( = 32 767)
double amp = volume >> 2; // so we simply set amp = volume / 2
for (int step = 0; step < samples; step++)
{
short s = (short)(amp * Math.Sin(theta * (double)step));
writer.Write(s);
}
}
}

Here how I did it - just create a new SoundEffectInstance object and set it to the return value of SoundEffect.CreateInstance.
https://msdn.microsoft.com/en-us/library/dd940203.aspx
SoundEffect mySoundPlay = new SoundEffect(mStrm.ToArray(), 16000,AudioChannels.Mono);
SoundEffectInstance instance = mySoundPlay.CreateInstance();
instance.IsLooped = true;
instance.Play();

Related

How to smooth between multiple perlin noise chunks?

Smoothing Between Chunks
So I've been working on a game in unity and want to expand my world from a 150x150 map into a seemingly infinite procedural world. My plan is to use Perlin Noise as the base and use the different values from 0-1 to determine the terrain type. The issue I'm running into is when I draw out my chunks and offset accordingly my chunks do not line up correctly, which kind of break the illusion of an infinite world.
(seen here)
WorldChunk.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using Unity.Mathematics;
[System.Serializable]
public class WorldChunk
{
public int2 Position;
public int[,] Data;
public float[,] Sample;
public WorldChunk(int chunkSize = 16){
Data = new int[chunkSize, chunkSize];
Sample = new float[chunkSize, chunkSize];
}
}
WorldGenerator.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System;
using Unity.Mathematics;
public class WorldGenerator : MonoBehaviour
{
// Base World Data
public int ChunkSize = 75;
public string Seed = "";
[Range(1f, 40f)]
public float PerlinScale = 10f;
// Pseudo Random Number Generator
private System.Random pseudoRandom;
// Chunk Data Split into Sections (Each Chunk having Coords (x, y))
public Dictionary<string, WorldChunk> chunks = new Dictionary<string, WorldChunk>();
//============================================================
// Set Warm-Up Data
//============================================================
private void Awake() {
// Get/Create Seed
if (Seed == ""){
Seed = GenerateRandomSeed();
}
// Get Random Number Generator
pseudoRandom = new System.Random(Seed.GetHashCode());
// Using to Clear while Making Test Adjustments
chunks.Clear();
// Generate Starting Chunk
for (int x = -1; x <= 1; x++)
{
for (int y = -1; y <= 1; y++)
{
// Draw Test Chunks
GenerateChunk(x, y);
}
}
}
//============================================================
// Generation Code
//============================================================
// ===
// Create New Chunks
// ===
public void GenerateChunk(int x, int y){
// Set Key to use
string key = $"{x},{y}";
// Check if key exists if not Generate New Chunk
if (!chunks.ContainsKey(key)){
// Add Chunk, Set Position in chunk grid (for calling and block data later), Then Generate data
chunks.Add(key, new WorldChunk(ChunkSize));
chunks[key].Position = new int2(x, y);
GenerateChunkData(chunks[key]);
}
}
// ===
// Fill Chunks with Perlin Data
// ===
private void GenerateChunkData(WorldChunk chunk){
// Set Offsets
float xOffset = (float)chunk.Position.x * ChunkSize;
float yOffset = (float)chunk.Position.y * ChunkSize;
// Set Data to Chunk
for (int x = 0; x < ChunkSize; x++)
{
for (int y = 0; y < ChunkSize; y++)
{
// Get Perlin Map
float px = (float)(x) / ChunkSize * PerlinScale + xOffset;
float py = (float)(y) / ChunkSize * PerlinScale + yOffset;
// Set Temp Sample For Testing (This will change for Map Data (Hills and Water) later)
chunk.Sample[x,y] = Mathf.PerlinNoise(px, py);
}
}
}
// ===
// Generate Random Seed of Length
// ===
private string GenerateRandomSeed(int maxCharAmount = 10, int minCharAmount = 10){
//Set Characters To Pick from
const string glyphs= "abcdefghijklmnopqrstuvwxyz0123456789";
//Set Length from min to max
int charAmount = UnityEngine.Random.Range(minCharAmount, maxCharAmount);
// Set output Variable
string output = "";
// Do Random Addition
for(int i=0; i<charAmount; i++)
{
output += glyphs[UnityEngine.Random.Range(0, glyphs.Length)];
}
// Output New Random String
return output;
}
//============================================================
// Draw Example
//============================================================
private void OnDrawGizmos() {
// Do this because I'm lazy and don't want to draw pixels to generated Sprites
Awake();
// For Each WorldChunk in the chunk Data
foreach (WorldChunk c in chunks.Values)
{
// Check if it exists (Foreach is stupid sometimes... When live editing)
if (c != null){
// Get World Positions for Chunk (Should probably Set to a Variable in the Chunk Data)
Vector3 ChunkPosition = new Vector3(c.Position.x * ChunkSize, c.Position.y * ChunkSize);
// For Each X & For Each Y in the chunk
for (int x = 0; x < ChunkSize; x++)
{
for (int y = 0; y < ChunkSize; y++)
{
// Get Cell position
Vector3 cellPos = new Vector3((ChunkPosition.x - ChunkSize/2f) + x, (ChunkPosition.y - ChunkSize/2f) + y);
// Get Temp Sample and set to color
float samp = c.Sample[x,y];
Gizmos.color = new Color(samp, samp, samp);
// Draw Tile as Sample black or white.
Gizmos.DrawCube(cellPos, Vector3.one);
}
}
// Size for Cubes
Vector3 size = new Vector3(ChunkSize, ChunkSize, 1f);
// Set Color Opaque Green
Gizmos.color = new Color(0f, 1f, 0f, 0.25f);
// Draw Chunk Borders (Disable to show issue)
// Gizmos.DrawWireCube(ChunkPosition, size);
}
}
}
}
I would like to point out when I use:
// Get Perlin Map
float px = (float)(x + xOffset) / ChunkSize * PerlinScale;
float py = (float)(y + yOffset) / ChunkSize * PerlinScale;
instead of
// Get Perlin Map
float px = (float)(x) / ChunkSize * PerlinScale + xOffset;
float py = (float)(y) / ChunkSize * PerlinScale + yOffset;
Everything aligns up correctly but the perlin noise just repeats.
What would be the best way for me to smooth between the chunks so that everything matches up?
Is there a better way to write this maybe?
EDIT:
Thanks for the help Draykoon D! here is the updated info and links to the updated scripts on pastebin if anyone needs them!
Here is the update code for anyone who wants it:
** WorldGenerator.cs**
https://pastebin.com/3BjLy5Hk
** WorldGenerator.cs**
https://pastebin.com/v3JJte3N
Hope that helps!
The key word you are looking for is tileable.
But I have a great news for you, noise function such as perlin are periodic in nature.
So instead of calling ChunckSize * ChunkSize a noise function you should only call it once and then divide the results.
I will advice you to read this excellent tutorial:
https://www.scratchapixel.com/lessons/procedural-generation-virtual-worlds/procedural-patterns-noise-part-1/creating-simple-1D-noise
Don't use Perlin noise. It has heavy bias towards the 45 and 90 degree directions. Your hills are all aligned to these, and aren't oriented along a more interesting variety of directions. You could use Unity.mathematics.noise.snoise(float2) but its repeat period is rather small, and it might not be very fast if you aren't using Unity Burst jobs. this is what I created/use/recommend, but it's certainly not the only option out there! Note that all these noises are range -1 to 1 rather than 0 to 1, so if that's important than do value=value*0.5+0.5; to rescale it.
Now that that's out of the way, to solve your issue you need to separate the idea of chunks and generation. This is a good idea in general, and I always believe in hiding backend implementation details (e.g chunks) from gameplay as much as possible (e.g. avoid visible boundaries). Each time you generate a chunk, you should find its start coordinate in the world, so that coordinates continue seamlessly with the rest. For example, if the chunks are 128x128, then the chunk starting at (0, 0) should have starting coordinate (0, 0), then the chunk starting at (0, 1) should have starting coordinate (0, 128). Only then, convert a world coordinate into a noise coordinate by multiplying by your desired frequency.

How to delay audio playback to sync with FFT

I am using C# WPF to make a real-time FFT.
I am using NAudio's WaveIn and BufferedWaveProvider to capture any sound recorded by Stereo Mix. I take the FFT of the buffer many times per second and display it using a bitmap so that the display shows a real-time fourier transform of any audio playing through the speakers.
My problem is that, as expected, the displayed FFT lags behind the audio coming from the speakers by a small amount (maybe 200 ms).
Is there any way I can record the current audio that is supposed to be playing from the speakers so that I can perform the FFT on it and then play it back a small amount of time later (ex. 200 ms) while muting the original real-time audio.
The end result would simply be to effectively remove the perceived delay from the displayed FFT. Audio from a youtube video, for example, would lag slightly behind the video while my program is running.
Here are the relevant methods from what I have right now:
public MainWindow()
{
sampleSize = (int)Math.Pow(2, 13);
BUFFERSIZE = sampleSize * 2;
InitializeComponent();
// get the WaveIn class started
WaveIn wi = new WaveIn();
wi.DeviceNumber = deviceNo;
wi.WaveFormat = new NAudio.Wave.WaveFormat(RATE, WaveIn.GetCapabilities(wi.DeviceNumber).Channels);
// create a wave buffer and start the recording
wi.DataAvailable += new EventHandler<WaveInEventArgs>(wi_DataAvailable);
bwp = new BufferedWaveProvider(wi.WaveFormat);
bwp.BufferLength = BUFFERSIZE; //each sample is 2 bytes
bwp.DiscardOnBufferOverflow = true;
wi.StartRecording();
}
public void UpdateFFT()
{
// read the bytes from the stream
byte[] buffer = new byte[BUFFERSIZE];
bwp.Read(buffer, 0, BUFFERSIZE);
if (buffer[BUFFERSIZE - 2] == 0) return;
Int32[] vals = new Int32[sampleSize];
Ys = new double[sampleSize];
for (int i = 0; i < vals.Length; i++)
{
// bit shift the byte buffer into the right variable format
byte hByte = buffer[i * 2 + 1];
byte lByte = buffer[i * 2 + 0];
vals[i] = (short)((hByte << 8) | lByte);
Ys[i] = vals[i];
}
FFT(Ys);
}
I am still new to audio processing - any help would be appreciated.
The cause of your delay is the latency of WaveIn which is about 200ms by default. You can reduce that but at the risk of dropouts.
Whilst you can capture audio being played by the system with WasapiCapture there is no way to change it with NAudio, or delay its playback.

How to properly run an FFT on a windowed set of data from a pure sine wave

I am trying to experiment with Math.Net, specifically the FFT portion. I am attempting to extract the frequency domain information from a pure sine wave. Here is the code:
private void Form1_Load(object sender, EventArgs e)
{
//Set up the wave and derive some useful info
Double WaveFreq = 500;
Double WavePeriod = 1 / WaveFreq;
Double SampleFreq = 20000;
Double SampleTime = (1 / SampleFreq);
//Generate the wave using the above parameters
var points = Generate.Sinusoidal(100000, SampleFreq, WaveFreq, 1);
//Array to hold our complex numbers
var data = new Complex[points.Length];
//Set up the series to display our raw wave
Series WaveSeries = new Series("Waveform");
WaveSeries.ChartType = SeriesChartType.Line;
//Creat the series for displaying the FFT
Series FFTSeries = new Series("FFT Test");
FFTSeries.ChartType = SeriesChartType.Column;
//Populate both the wave series and the data array
for (int i = 0; i < points.Length; i++)
{
Double x = SampleTime * i;
WaveSeries.Points.AddXY(x, points[i]);
data[i] = new Complex(x, points[i]);
}
//Create the window to evaluate (using a window 5 times wider than the wavelength of the lowest ferequency being measured)
int WindowWidth = (int)Math.Round((1 / WaveFreq) / (1 / SampleFreq) * 5 + 0.5f);
var HannWindow = Window.HannPeriodic(WindowWidth);
var window = new Complex[WindowWidth];
for(int i = 0; i < WindowWidth; i++)
{
var y = data[i].Imaginary * HannWindow[i];
window[i] = new Complex(data[i].Real, y);
}
//Perform the FFT
Fourier.Forward(window);
//Add the calculated FFT to our FFTSeries
foreach(Complex sample in window)
{
FFTSeries.Points.AddXY(sample.Phase, sample.Magnitude);
}
chart2.Series.Add(WaveSeries);
chart2.ChartAreas[0].AxisX.Minimum = 0;
chart2.ChartAreas[0].AxisX.Maximum = .01;
chart2.ChartAreas[0].AxisY.Minimum = -2;
chart2.ChartAreas[0].AxisY.Maximum = 2;
chart1.Series.Add(FFTSeries);
chart1.ChartAreas[0].AxisX.Minimum = 0;
chart1.ChartAreas[0].AxisX.Maximum = 1000;
chart1.ChartAreas[0].AxisY.Minimum = 0;
chart1.ChartAreas[0].AxisY.Maximum = 5;
}
As you can see, I am generating a sine wave at a frequency of 500Hz, sampling at 20kHz and generating 10k Samples.
The output is as the following (FFT on the left, wave on the right)
The FFT shows absolutely nothing (asides from a peak of 1.8 around 0Hz)! I suspect it is probably an error with the windowing but for the life of me I can't see what it is.
There seems to be some misunderstanding on complex numbers. In your code they seem to be used like points (x,y-tuples), but they have nothing to do with points at all. The complex equivalent of your real data points is an array where the real part of the complex numers match your real data points and the imaginary part is all zero. Essentially:
var window = new Complex[WindowWidth];
for (int i = 0; i < WindowWidth; i++)
{
window[i] = new Complex(points[i] * HannWindow[i], 0.0);
}
If you need an easy way to get the correct x axis for your frequency plot, you can use the FrequencyScale function, along the lines of:
var scale = Fourier.FrequencyScale(WindowWidth, SampleFreq);
for (int i = 0; i < WindowWidth; i++)
{
FFTSeries.Points.AddXY(scale[i], window[i].Magnitude);
}
You should see a spike at index 5, which according to the computed scale array corresponds to frequency 500, which matches with your wave frequency.
Note that the FFT routine returns the full spectrum including negative frequencies, so you should also see a spike of the same size at frequency -500.
The FFT is definitely there but the scale that you have mapped is wrong.
Just change the X axis and you will see it
chart1.ChartAreas[0].AxisX.Maximum = 10;
It also seems like the sinusoidal wave form you generate is not right although I am no math net expert so I don't know. The center doesn't seem to be at zero.

Unable to get wave form image for audio using NAudio

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.

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 ;

Categories