I'm trying to write a mini synth program. Essentially, every 200mS a timer is fired. When this is fired, 200mS worth of PCM samples are generated by the loop (e.g. sawtooth, sine wave etc, etc.), the number of samples related to the sample rate and scan period. e.g. every 200mS, 4000 samples are ready in the wave array for playing. My question is, how do I play this array i.e.what should the PlaySound method do? Invoking PlaySound every 200mS should play subsequent samples in a continuous manner. I have played around with DirectSound and NAudio to no avail.
const int SAMPLE_RATE = 20000;
const double SCAN_PERIOD = .2; // S
Timer _ScanTimer = new Timer();
double[] wave = new double[(int)((double)SAMPLE_RATE * SCAN_PERIOD)];
_ScanTimer.Tick += new EventHandler(_ScanTimer_Tick);
private void _ScanTimer_Tick(object sender, EventArgs e) {
int numSamplesPerTick = (int)((double)SAMPLE_RATE * SCAN_PERIOD);
double secondsPerTick = (double)2 / SAMPLE_RATE;
for (int i = 0; i < numSamplesPerTick; i++) {
wave[i] = GetSynthOutput();
_CurrentTime += secondsPerTick;
if (_CurrentTime > double.MaxValue - 1)
_CurrentTime = 0;
}
PlaySound(wave);
}
I realise that the timer may not fire exactly every 200mS, and that the 4000 samples may contain slightly too few or too many samples for the actual time. I'm not worried about this as I can tweak the number of samples according to the actual time between subsequent timer firing.
Instead of creating sound on a timer, most audio synthesis programs work with two buffers - one is being filled while the other one is being played. NAudio allows you to do this by implementing the Read function in an ISampleProvider or IWaveProvider derived class. This method will be called whenever the soundcard wants more audio to play. You can set up your buffer sizes to be 200ms if you want.
Related
I want my game's screen to have a timer on screen that shows how many seconds have passed (representing the player's point score). I am able to get the timer on screen, however, the counter freaks out and my console doesn't print the result correctly either. Any ideas?
I've tried to use timer.Elapsed however SplashKit (what i must use) does not seem to recognise that.
Sorry if this is a repeated question, I am new to programming and have searched around but couldn't find anything I could comprehend/assist.
public void Timer()
{
//begin timer and print results
timer.Start();
//write to console how many milliseconds have passed, and divide by 1000 for seconds.
Console.WriteLine($":{timer.Ticks} milliseconds have passed");
Console.WriteLine($"which is {timer.Ticks /1000} seconds");
//covert timer.Ticks to string and store into string 'score
score = Convert.ToString(timer.Ticks);
//assign font
Font Quicksand = SplashKit.LoadFont("Quicksand", "Resources\\fonts\\Quicksand-Regular.otf");
//use SplashKit to print to screen..
SplashKit.DrawText(score, Color.Black, Quicksand, 70, 700, 900);
}
+1 to Eric j's comment- all the Timer types the framework that I know of are not for providing stopwatch style "the game has been running 5 minutes" type functions directly themselves. They're classes that raise an event at some predefined interval. The actual timing of the game, if using a Timer, would be done by you by recording the start time and differencing the time now to it upon the timer elapsing it's interval:
public class Whatever{
private Timer _t = new Timer();
private DateTime _start;
public Whatever(){ //constructor
_t.Elapsed += TimerElapsed; //elapsed event handled by TimerElapsed method
_t.Interval = 1000; //fire every second
}
public void StartGame(){
_start = DateTime.UtcNow;
_t.Start();
}
private void TimerElapsed(){
Console.WriteLine("Game has been running for " + (DateTime.UtcNow - _start));
}
The timer interval merely controls how often the clock will update on screen. If you're offering game times of 10.1,10.2 seconds etc then make the timer interval less than 100 (updates more than once every 0.1 seconds) for example
It's not clear what type timer is, but the property Elapsed is probably a TimeSpan.
The total seconds including decimal amount is present in the double value:
timer.Elapsed.TotalSeconds
You can truncate that to an integer by casting it
var seconds = (int)timer.Elapsed.TotalSeconds;
I'm currently using Naudio to create a mixer with multiple inputs and multiple outputs.
For each input, while the audio is playing, I would like to be able to add a sample sized delay whenever I choose to.
The effect will be similar to pausing for, say 100 samples, and then playing again.
For example, a mixingSampleProvider is currently playing audio to output Channel 5, using multiplexingSampleProvider. On a certain button click, i would like to delay mixingSampleProvider by 200 samples, before it continues playing from the point it paused for a moment.
I would like to know if it's possible to do so, and what options can be looked at to achieve this effect.
Edit:
The specific problem which requires fixing, would be the knowledge gap I have in inserting Zeros to the buffer, while pushing all the rest of the samples back, while audio is playing. Here, I'm not looking for code to be written, but am looking for resources to attempt to create a new class to solve this issue. Or whether it is even possible at all.
Edit 2: Code I have tried (failed)
public int Read(float[] buffer, int offset, int count)
{
int sampleRead = source.Read(buffer,offset,count);
int delaySamples = Math.Min(count, DelayBySamples - delayPos);
float[] copyBuffer = buffer;
for (int n = 0; n < delaySamples;n++ )
{
buffer[offset + n] = 0;
}
for (int n = 0; n < sampleRead;n++ )
{
buffer[offset + delaySamples + n] = copyBuffer[offset+n];
}
delayPos += delaySamples;
sampleRead += delaySamples;
return sampleRead;
}
You should implement this by creating your own custom ISampleProvider that wraps each input to your mixer (Decorator pattern). Then have a method that tells it to insert n samples of silence. Then in the Read method of your SampleProvider if silence is required, then put up to the desired number of silent samples into the read buffer. And when the pause finishes, start returning samples from the source provider again.
You can have a look at the source code to the OffsetSampleProvider for inspiration, which allows you to add a specified number of silence samples at the start of your stream.
I try to increase the noise by doing this :
public void maakgeluid() {
WaveOut waveOut = new WaveOut();
SineWaveOscillator osc = new SineWaveOscillator(44100);
for (short amplitude = 1; amplitude <500; amplitude+=1) {
Console.WriteLine("amplitude:" + amplitude);
for (int i = 1; i < 10; i++) {
Console.WriteLine(i);
osc.Frequency = 500;
osc.Amplitude = amplitude;
try {
waveOut.Init(osc);
} catch (Exception ) { Console.WriteLine("error"); }
waveOut.Play();
}
}
}
The purpose is to generate a sound, like when you go to the ear specialist and take a hearing test. So it should start very silently, and then slowly get loader.
But I have different problems :
i hear the sound immediately
the sound increases to fast
i use the loop with the i counter, to lengthen the duration of the sound, but i don't think that is the right way to do it.
the looping to increase the sound level stops to quickly but I don't see why?
THx
based on the following code
msdn.microsoft.com/en-us/magazine/ee309883.asp
The bel scale is logarithmic, so you need to do maths to convert between decibels and a sample scaling factor.
In digital (floating point) audio a full signal (i.e. a waveform that reaches from +1 to -1, peak to trough) is considered to be 0dB.
As such, you'd probably want to go from -60dB to 0dB.
The conversion is as follows (assuming signed audio over unsigned, as with 8-bit audio)
double sampleValue = 1.0d;
//every -3db represents an approximate halving of linear signal level
double decibelValue = -3.0d;
double linearScalingRatio = Math.Pow(10d, decibelValue/10d);
var newSampleValue = sampleValue * linearScalingRatio;
So now, newSampleValue is 0.501187233627272
Your current code keeps recreating WaveOut devices which is not a good idea. Open the soundcard once, and then feed a single signal to it that gradually increases in volume over time. One way you could do this is to use the SignalGenerator class to make the sin wave, then pass that through a FadeInSampleProvider to gradually fade it in:
var sg = new SignalGenerator(44100,1);
sg.Frequency = 500;
sg.Type = SignalGeneratorType.Sin;
var fadeIn = new FadeInOutSampleProvider(sg, true);
fadeIn.BeginFadeIn(20000); // fade in over 20 seconds
waveOut.Init(fadein);
waveOut.Play();
As spender rightly points out, 0dB is maximum, so this is going from negative infinity decibels up to 0dB over the duration of the fade-in time. If you wanted to make it start at -60dB, or for the ramp-up of the multiplier to not be linear, then you'd need to make your own custom ISampleProvider similar to FadeInOutSampleProvider and use that instead.
I have a working implementation of NAudio's wasapi loopback recording and the FFT of the data.
Most of the data I get is just as it should be but every once in a while (10 sec to minutes intervals) it shows amplitude on almost all frequencies.
Basicly the picture is rolling from right to left with time and frequencies going on logarithmic scale from lowest frequencies on the bottom. The lines are the errors. As far as i can tell those are not supposed to be there.
I get the audio buffer and send the samples to an aggregator (applies Hamming window) which implements the NAudio FFT. I have checked the data (FFT result) before I modify it in any way (the picture is not from the raw FFT output, but desibel scaled) confirming the FFT result is giving those lines. I could also point out the picture is modified with LockBits so I thought I had something wrong with the logic there, but that's why I checked the FFT output data which shows the same problem.
Well I could be wrong and the problem might be somewhere I said it isn't but it really seems it originates from the FFT OR the buffer data (data itself or the aggregation of samples). Somehow I doubt the buffer itself is corrupted like this.
If anyone has any idea what could cause this I would greatly appreciate it!
UPDATE
So I decided to draw the whole FFT result range rather than half of it. It showed something strange. I'm not sure of FFT but I thought Fourier transformation should give a result that is mirrored around the middle. This certainly is not the case here.
The picture is in linear scale so the exact middle of the picture is the middle point of the FFT result. Bottom is the first and top is the last.
I was playing a 10kHz sine wave which gives the two horizontal lines there but the top part is beyond me. It also seems like the lines are mirrored around the bottom quarter of the picture so that seems strange to me as well.
UPDATE 2
So I increased the FFT size from 4096 to 8192 and tried again. This is the output with me messing with the sine frequency.
It would seem the result is mirrored twice. Once in the middle and then again on the top and bottom halves. And the huge lines are now gone.. And it would seem like the lines only appear on the bottom half now.
After some further testing with different FFT lengths it seems the lines are completely random in that account.
UPDATE 3
I have done some testing with many things. The latest thing I added was overlapping of samples so that I reuse the last half of the sample array in the beginning of the next FFT. On Hamming and Hann windows it gives me massive intensities (quite like in the second picture I posted) but not with BlackmannHarris. Disabling overlapping removes the biggest errors on every window function. The smaller errors like in the top picture still remain even with BH window. I still have no idea why those lines appear.
My current form allows control over which window function to use (of the three previously mentioned), overlapping (on/off) and multiple different drawing options. This allows me to compare all the affecting parties effects when changed.
I shall investigate further (I am quite sure I have made a mistake at some point) but good suggestions are more than welcome!
The problem was in the way I handled the data arrays. Working like a charm now.
Code (removed excess and might have added mistakes):
// Other inputs are also usable. Just look through the NAudio library.
private IWaveIn waveIn;
private static int fftLength = 8192; // NAudio fft wants powers of two!
// There might be a sample aggregator in NAudio somewhere but I made a variation for my needs
private SampleAggregator sampleAggregator = new SampleAggregator(fftLength);
public Main()
{
sampleAggregator.FftCalculated += new EventHandler<FftEventArgs>(FftCalculated);
sampleAggregator.PerformFFT = true;
// Here you decide what you want to use as the waveIn.
// There are many options in NAudio and you can use other streams/files.
// Note that the code varies for each different source.
waveIn = new WasapiLoopbackCapture();
waveIn.DataAvailable += OnDataAvailable;
waveIn.StartRecording();
}
void OnDataAvailable(object sender, WaveInEventArgs e)
{
if (this.InvokeRequired)
{
this.BeginInvoke(new EventHandler<WaveInEventArgs>(OnDataAvailable), sender, e);
}
else
{
byte[] buffer = e.Buffer;
int bytesRecorded = e.BytesRecorded;
int bufferIncrement = waveIn.WaveFormat.BlockAlign;
for (int index = 0; index < bytesRecorded; index += bufferIncrement)
{
float sample32 = BitConverter.ToSingle(buffer, index);
sampleAggregator.Add(sample32);
}
}
}
void FftCalculated(object sender, FftEventArgs e)
{
// Do something with e.result!
}
And the Sample Aggregator class:
using NAudio.Dsp; // The Complex and FFT are here!
class SampleAggregator
{
// FFT
public event EventHandler<FftEventArgs> FftCalculated;
public bool PerformFFT { get; set; }
// This Complex is NAudio's own!
private Complex[] fftBuffer;
private FftEventArgs fftArgs;
private int fftPos;
private int fftLength;
private int m;
public SampleAggregator(int fftLength)
{
if (!IsPowerOfTwo(fftLength))
{
throw new ArgumentException("FFT Length must be a power of two");
}
this.m = (int)Math.Log(fftLength, 2.0);
this.fftLength = fftLength;
this.fftBuffer = new Complex[fftLength];
this.fftArgs = new FftEventArgs(fftBuffer);
}
bool IsPowerOfTwo(int x)
{
return (x & (x - 1)) == 0;
}
public void Add(float value)
{
if (PerformFFT && FftCalculated != null)
{
// Remember the window function! There are many others as well.
fftBuffer[fftPos].X = (float)(value * FastFourierTransform.HammingWindow(fftPos, fftLength));
fftBuffer[fftPos].Y = 0; // This is always zero with audio.
fftPos++;
if (fftPos >= fftLength)
{
fftPos = 0;
FastFourierTransform.FFT(true, m, fftBuffer);
FftCalculated(this, fftArgs);
}
}
}
}
public class FftEventArgs : EventArgs
{
[DebuggerStepThrough]
public FftEventArgs(Complex[] result)
{
this.Result = result;
}
public Complex[] Result { get; private set; }
}
And that is it I think. I might have missed something though.
Hope this helps!
I'm trying to have a stopwatch start and stop when recording positions for the Kinect:
//process x and y coordinates
public void calculateJoints(Skeleton skeleton)
{
Joint rightHand = skeleton.Joints[JointType.HandRight];
Joint leftHand = skeleton.Joints[JointType.HandRight];
rightX = rightHand.Position.X;
rightY = rightHand.Position.Y;
}
//start the stopwatch (tried to use a greater time between positions 1 and 5 vs 1 and 2
public void processJointsAndRepeat(Skeleton skeleton)
{
startTime();
while (numPositions < 5)
{
calculateJoints(skeleton);
numPositions++;
}
stopTime();
double tempTime = calculateTimeElapsed();
}
//calculate time in milliseconds
private double calculateTimeElapsed()
{
long milliseconds = stopWatch.ElapsedMilliseconds;
return (double)milliseconds;
}
But whenever I try to put in the x, y, and time values with time as the key, it throws an error for duplicate keys. When I retrieved the value of tempTime, it only showed 0.
Is this a problem with my code, or do I need a more precise stopwatch?
I realize that getting a time for something that is 30 fps is difficult, so if you have any other suggestions, that'd be great! I'm basically just trying to calculate the average velocities between points to adjust the playback speed of an audio file. Thanks!
Stopwatch is wrapper around timer with higerst resolution on regular Windows box. You can use less fancy functions to get higer than MS resolution by using Stopwatch.ElapsedTicks and Stopwatch.Frequency.
Note that your problem is probably not related to timers but rather some other code you did not show...