So, I'm trying to create a method that animates the movement of a control on a form. I generate all the points the control is going to travel to beforehand, like this:
private static List<decimal> TSIncrement(int durationInMilliseconds, decimal startPoint, decimal endPoint)
{
List<decimal> tempPoints = new List<decimal>();
decimal distance = endPoint - startPoint;
decimal increment = distance / durationInMilliseconds;
decimal tempPoint = (decimal)startPoint;
for (decimal i = durationInMilliseconds; i > 0; i--)
{
tempPoint += increment;
tempPoints.Add(tempPoint);
}
return tempPoints;
}
This outputs a list with as many points as there are milliseconds in the duration of the animation. I think you can guess what I'm doing afterwards:
public static void ControlAnimation(Control control, Point locationEndpoint, int delay)
{
if (delay > 0)
{
List<decimal> tempXpoints = TSIncrement(delay, control.Location.X, locationEndpoint.X);
List<decimal> tempYpoints = TSIncrement(delay, control.Location.Y, locationEndpoint.Y);
for (int i = 0; i < delay; i++)
{
control.Location = new Point((int)Math.Round(tempXpoints[i]), (int)Math.Round(tempYpoints[i]));
Thread.Sleep(1); //I won't leave this obviously, it's just easier for now
}
}
}
In the actual method, I go through this list of points and use those to create the new location of the control (I actually use two lists for the abscissa and the ordinate).
My problem lies in creating one millisecond of delay between each shifting. Since the code in the loop takes a bit of time to execute, I usually end up with approximately 5 seconds more duration.
I tried using a stopwatch to measure the time it takes to set control.location, and subtracting that to the 1 millisecond delay. The stopwatch adds some delay as well though, since I gotta start, stop and reset it everytime.
So what should I do, and how could I improve my code? Any feedback is greatly appreciated :)
You won't get a reliable delay below around 50 milliseconds in WinForms, so that is the delay I used below:
private Random R = new Random();
private async void button1_Click(object sender, EventArgs e)
{
button1.Enabled = false;
label1.AutoSize = false;
label1.Size = button2.Size;
Point p = new Point(R.Next(this.Width - button2.Width), R.Next(this.Height - button2.Height));
label1.Location = p;
label1.SendToBack();
await MoveControl(button2, p, R.Next(2000, 7001));
button1.Enabled = true;
}
private Task MoveControl(Control control, Point LocationEndPoint, int delayInMilliseconds)
{
return Task.Run(new Action(() =>
{
decimal p;
int startX = control.Location.X;
int startY = control.Location.Y;
int deltaX = LocationEndPoint.X - startX;
int deltaY = LocationEndPoint.Y - startY;
System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
sw.Start();
while(sw.ElapsedMilliseconds < delayInMilliseconds)
{
System.Threading.Thread.Sleep(50);
p = Math.Min((decimal)1.0, (decimal)sw.ElapsedMilliseconds / (decimal)delayInMilliseconds);
control.Invoke((MethodInvoker)delegate {
control.Location = new Point(startX + (int)(p * deltaX), startY + (int)(p * deltaY));
});
}
}));
}
Related
Let's say I'm making a game where I want Timers or Stopwatches to slow down in response to slowmo effects. How do I go about doing that? Looking at the API docs, there doesn't seem to be any way to scale the rate that they do their thing, so I was wondering if there's some other trick I can use to get the same effect.
You can set the interval to part of its original value and use some variable to keep track of the elapsed time
Like this:
int counter = 0;
int scale = 2; //scaling factor
int originalInterval = 1000;
Timer timer = new Timer();
timer.Interval = originalInterval / scale;
timer.Tick += (sender, e) => {
counter++;
if (counter >= scale) {
// perform the needed action here
counter = 0; // reset the counter
}
};
If you want to use a stopwatch, then like this:
Stopwatch stopwatch = new Stopwatch();
int scale = 2;
stopwatch.Start();
while (true) {
//Scale the elapsed time and by the factor
double elapsed = stopwatch.Elapsed.TotalMilliseconds / scale;
//When the scaled elapsed time exceeds a threshold
if (elapsed > threshold) {
// perform the needed action here
break;
}
}
I am trying to read a video (.mp4) from a file and showing it on a Imagebox on forms. When I time the total time it played, it's actually more than actual video length. For example, if video is actually 10 seconds, it played for 12-13 seconds. Why is that? The framerate for my video is 31fps
I tried to make sure fps variable has the correct framerate already.
private void Play_Video(string filePath)
{
VideoCapture cameraCapture = new VideoCapture(filePath);
var stopwatch = new Stopwatch();
stopwatch.Start();
while (true)
{
Mat m = new Mat();
cameraCapture.Read(m);
if (!m.IsEmpty)
{
imageBox1.Image = m;
double fps = cameraCapture.GetCaptureProperty(CapProp.Fps);
Thread.Sleep(1000 / Convert.ToInt32(fps));
}
else
{
break;
}
}
stopwatch.Stop();
double elapsed_time = stopwatch.ElapsedMilliseconds * 0.001;
// My elapsed_time here shows about 13 seconds, when the actual length of video is 10 seconds. Why?
}
NEW EDIT:
I updated retrieval of frames in a separate function as to not cause delay during render, but there is still a few seconds delay. For example 30 seconds video plays for 32-33 seconds. My updated code:
private void Play_Video(List<Mat> frames, double fps, Emgu.CV.UI.ImageBox imageBox)
{
var stopwatch = new Stopwatch();
stopwatch.Start();
for (int i = 0; i < frames.Count; i++)
{
imageBox.Image = frames[i];
Thread.Sleep(1000 / Convert.ToInt32(fps));
}
stopwatch.Stop();
double elapsed_time = stopwatch.ElapsedMilliseconds * 0.001;
}
This was the only way I could get the exact fps because using thread.sleep takes 10-15 ms to cycle on and off. I had same problem with Task.Delay. This seems to work perfectly, if anyone has a better solution I would love to see it.
private void PlayVideo()
{
double framecount = video.Get(Emgu.CV.CvEnum.CapProp.FrameCount);
double fps = video.Get(Emgu.CV.CvEnum.CapProp.Fps);
video.Set(Emgu.CV.CvEnum.CapProp.PosFrames, (double)(framecount * (double)(savePercent / 100)));
int ms = Convert.ToInt32(((double)1000 / fps));
while (disablePreviewTimer == false)
{
DateTime dt = DateTime.Now;
Emgu.CV.Mat img = video.QueryFrame();
if (img == null)
{
disablePreviewTimer = true;
break;
}
else
{
Emgu.CV.Util.VectorOfByte vb = new Emgu.CV.Util.VectorOfByte();
Emgu.CV.CvInvoke.Resize(img, img, PreviewSize);
Emgu.CV.CvInvoke.Imencode(".jpg", img, vb);
pictureBox5.Image = Image.FromStream(new MemoryStream(vb.ToArray()));
Application.DoEvents();
while ((DateTime.Now - dt).TotalMilliseconds < ms)
{
Application.DoEvents();
}
label6.Text = ((int)(DateTime.Now - dt).TotalMilliseconds).ToString();
}
}
}
I have the following graphic in my Windows Form:
I set the X axis to display Time, as shown below:
Here are the methods I call to draw the graph:
private void funcaoQualquer()
{
while (true)
{
funcaoArray[funcaoArray.Length - 1] = hScrollBar1.Value;
Array.Copy(funcaoArray, 1, funcaoArray, 0, funcaoArray.Length - 1);
if (chart1.IsHandleCreated)
{
this.Invoke((MethodInvoker)delegate { atualizaGraficoFuncaoQualquer(hScrollBar1.Value); });
}
else
{
//...
}
Thread.Sleep(250);
}
}
private void atualizaGraficoFuncaoQualquer(int valor)
{
for (int i = 0; i < funcaoArray.Length - 1; ++i)
{
chart1.Series["Series1"].Points.Add(valor);
}
}
However, when drawing the graph and over time, the X axis does not change the values. I think it's being displayed as hours. How do I change to minutes or seconds?
you could set the Format like this:
this.chart1.ChartAreas[0].AxisX.LabelStyle.Format = "mm:ss";
if you have a sampling rate of 4 Hz you could also use seconds and milliseconds:
this.chart1.ChartAreas[0].AxisX.LabelStyle.Format = "ss.fff";
EDIT:
I would suggest to catch the sampling times in an extra List<DateTime> and feed the chart via AddXY with values. Here is a simple programm that draws a curve with a timer. The timer is set to 500 msec.
// Constructor
public Form1()
{
InitializeComponent();
// Format
this.chart1.ChartAreas[0].AxisX.LabelStyle.Format = "ss.fff";
// this sets the type of the X-Axis values
chart1.Series[0].XValueType = ChartValueType.DateTime;
timer1.Start();
}
int i = 0;
List<DateTime> TimeList = new List<DateTime>();
private void timer1_Tick(object sender, EventArgs e)
{
DateTime now = DateTime.Now;
TimeList.Add(now);
chart1.Series[0].Points.AddXY(now, Math.Sin(i / 60.0));
i+=2;
}
Now you can watch the x-axis values increment in the format that you like. Hope this helps
I want to develop an app to match your tinnitus frequency : A frequency is played and the user decrease or increase the freqency by pressing a plus or minus button. (see part of the codes, based on some coding from stackoverflow thx :-))
public static short[] BufferSamples = new short[44100 * 1 * 2];
private SourceVoice sourceVoice;
private AudioBuffer buffer;
private int Tfreq;
public MatchTinn()
{
InitializeComponent();
Loaded += MatchTinn_Loaded;
TFreq = 5000;
}
private void MatchTinn_Loaded(object sender, RoutedEventArgs e)
{
var dataStream = DataStream.Create(BufferSamples, true, true);
buffer = new AudioBuffer
{
LoopCount = AudioBuffer.LoopInfinite,
Stream = dataStream,
AudioBytes = (int)dataStream.Length,
Flags = BufferFlags.EndOfStream
};
FillBuffer(BufferSamples, 44100, Tfreq);
var waveFormat = new WaveFormat();
XAudio2 xaudio = new XAudio2();
MasteringVoice masteringVoice = new MasteringVoice(xaudio);
sourceVoice = new SourceVoice(xaudio, waveFormat, true);
// Submit the buffer
sourceVoice.SubmitSourceBuffer(buffer, null);
}
private void FillBuffer(short[] buffer, int sampleRate, int frequency)
{
if (sourceVoice != null)
{
sourceVoice.FlushSourceBuffers();
}
double totalTime = 0;
for (int i = 0; i < buffer.Length - 1; i += 2)
{
double time = (double)totalTime / (double)sampleRate;
short currentSample = (short)(Math.Sin(2 * Math.PI * frequency * time) * (double)short.MaxValue);
buffer[i] = currentSample;
buffer[i + 1] = currentSample;
totalTime++;
}
private void m1_OnTap(object sender, GestureEventArgs e)
{
Tfreq = Tfreq - 1;
if (Tfreq < 0)
{
Tfreq = 0;
}
FillBuffer(BufferSamples, 44100, Tfreq);
}
private void p1_OnTap(object sender, GestureEventArgs e)
{
Tfreq = Tfreq + 1;
if (Tfreq > 16000)
{
Tfreq = 16000;
}
FillBuffer(BufferSamples, 44100, Tfreq);
}
Playing the frequency is fine, but when the user presses a button you here a clicking sound when the frequency is updated. Do you have any idea what makes the sound and how i can get rid of it?
Thanks.
When you change the frequency, you're causing a discontinuity in the waveform that manifests as a click. Instead of making your signal calculations against absolute time, you should keep track of the phase of your sine calculation (e.g. a value from 0 to 2*pi), and figure out how much you need to add to your phase (subtracting 2*pi every time you exceed 2*pi) for the next sample when playing a specific frequency. This way, when you change frequency, the phase that you supply as a parameter to Math.Sin doesn't change abruptly causing a click.
Expanding on the answer #spender gave (I need 50 rep to add comment to his answer), I had a similar problem with naudio. I was able to solve the issue by adding two bool values that monitored the current sign of the sine value and the previous sign of the sine value. If the previous sine was negative and the current sine is positive, we know we can safely adjust the frequency of the sine wave.
double sine = amplitude * Math.Sin(Math.PI * 2 * frequency * time);
isPreviousSineWaveValPositive = isSineWaveValPositive;
if (sine < 0)
{
isSineWaveValPositive = false;
}
else
{
isSineWaveValPositive = true;
}
// When the time is right, change the frequency
if ( false == isPreviousSineWaveValPositive && true == isSineWaveValPositive )
{
time = 0.0;
frequency = newFrequency;
}
Here's an example how you can get rid of the clicking. Instead of using a time, you should keep track of the current phase and calculate how much the phase is changed on the required frequency. Also this _currentPhase must be persistent so it will have the previous value. (declaring it within the method would result in a click aswell (on most frequencies)
private double _currentPhase = 0;
private void FillBuffer(short[] buffer, int sampleRate, int frequency)
{
if (sourceVoice != null)
{
sourceVoice.FlushSourceBuffers();
}
var phaseStep = ((Math.PI * 2) / (double)sampleRate) * frequency;
for (int i = 0; i < buffer.Length - 1; i += 2)
{
_currentPhase += phaseStep;
short currentSample = (short)(Math.Sin(_currentPhase) * (double)short.MaxValue);
buffer[i] = currentSample;
buffer[i + 1] = currentSample;
}
}
Heres a code snippet from my attempt to make a 2D particle sim
static long lastTime = 0;
static double GetDeltaTime()
{
long now = DateTime.Now.Millisecond;
double dT = (now - lastTime); // / 1000
lastTime = now;
Console.WriteLine(dT);
return dT;
}
It should be pretty obvious that it would return the time (in milliseconds) since the last time that method was called. Only problem, this is what it prints
393
1
0
0
0
0
0
0
0
0
0
...
Ok so maybe thats just because each pass is taking less than a millisecond. So i changed it to
static long lastTime = 0;
static double GetDeltaTime()
{
long now = DateTime.Now.Ticks; // Changed this to ticks
double dT = (now - lastTime); // / 1000
lastTime = now;
Console.WriteLine(dT);
return dT;
}
but that still prints
6.35476136625848E+17
20023
0
0
0
0
0
0
...
and if "particle simulator" isnt a good enough indicator of how complex my program is, let me just say, it takes a lot longer than 0 ticks to complete a pass!
So whats going on here?
------- Code Reference ------
Heres the whole class just for reference
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using System.Windows.Forms;
using System.Threading;
namespace _2D_Particle_Sim
{
static class Program
{
public static Particle2DSim pSim;
static Form1 form;
public static Thread update = new Thread(new ThreadStart(Update));
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
form = new Form1();
pSim = new Particle2DSim(form);
pSim.AddParticle(new Vector2(-80, -7), 5);
pSim.AddParticle(new Vector2(8, 7), 3);
Console.WriteLine("Opening Thread");
Program.update.Start();
Application.Run(form);
// System.Threading.Timer timer;
// timer = new System.Threading.Timer(new TimerCallback(Update), null, 0, 30);
}
static void Update()
{
GetDeltaTime();
while (true)
{
pSim.Update(GetDeltaTime());
}
}
static long lastTime = 0;
static double GetDeltaTime()
{
long now = DateTime.Now.Ticks;
double dT = (now - lastTime); // / 1000
lastTime = now;
Console.WriteLine(dT);
return dT;
}
}
}
Also, if my analogy of how complex my code is still wasnt enough, heres the update methord from the Particle2DSim class
public void Update(double deltaTime)
{
foreach (Particle2D particle in particles)
{
List<Particle2D> collidedWith = new List<Particle2D>();
Vector2 acceleration = new Vector2();
double influenceSum = 0;
// Calculate acceleration due to Gravity
#region Gravity
foreach (Particle2D particle2 in particles)
{
double dist2 = particle.position.Distance2(particle.position);
double influence = dist2 != 0 ? particle2.mass / dist2 : 0;
acceleration.Add(particle.position.LookAt(particle2.position) * influence);
influenceSum += influence;
if (dist2 < ((particle.radius + particle2.radius) * (particle.radius + particle2.radius)) && dist2 != 0)
{
collidedWith.Add(particle2);
}
}
acceleration.Divide(influenceSum);
#endregion
particle.Update(deltaTime);
// Handle Collisions
#region Collisions
if (collidedWith.Count > 0)
{
Console.WriteLine("Crash!");
double newMass = 0;
double newRadius = 0;
Vector2 newPosition = new Vector2();
Vector2 newVelocity = new Vector2();
newMass += particle.mass;
newRadius += Math.Sqrt(particle.radius);
newPosition += particle.position;
newVelocity += particle.velocity * particle.mass;
particles.Remove(particle);
foreach (Particle2D particle2 in collidedWith)
{
newMass += particle2.mass;
newRadius += Math.Sqrt(particle2.radius);
newPosition += particle2.position;
newVelocity += particle2.velocity * particle2.mass;
particles.Remove(particle2);
}
newPosition.Divide(collidedWith.Count + 1);
newVelocity.Divide(newMass);
AddParticle(newPosition, newVelocity, newMass, newRadius);
}
#endregion
}
}
The problem is that you're using DateTime to try to measure the passage of time. DateTime is meant for representing a date and time, but not for measuring elapsed time.
Use the stopwatch class for measuring time:
Stopwatch sw = new Stopwatch();
sw.Start();
// Do something here
sw.Stop();
Console.WriteLine(sw.ElapsedMilliseconds);
// or sw.ElapsedTicks
For more details on the difference, check out Eric Lippert's blog HERE
DeltaTime like in Unity
using System;
class DeltaTimeExample
{
static void Main(string[] args)
{
DateTime time1 = DateTime.Now;
DateTime time2 = DateTime.Now;
// Here we find DeltaTime in while loop
while (true)
{
// This is it, use it where you want, it is time between
// two iterations of while loop
time2 = DateTime.Now;
float deltaTime = (time2.Ticks - time1.Ticks) / 10000000f;
Console.WriteLine(deltaTime); // *float* output {0,2493331}
Console.WriteLine(time2.Ticks - time1.Ticks); // *int* output {2493331}
time1 = time2;
}
}
}
class Program
{
static double DeltaTime;
static double Secondframe;
static double Counter;
static void Main(string[] args)
{
Stopwatch stopWatch = new Stopwatch();
stopWatch.Start();
while (true)
{
TimeSpan ts = stopWatch.Elapsed;
double FirstFrame = ts.TotalMilliseconds;
DeltaTime = FirstFrame - Secondframe;
Counter += DeltaTime;
Console.WriteLine(Counter);
Secondframe = ts.TotalMilliseconds;
}
}
}
}
DeltaTime class helps you implement the animation.
public class DeltaTime
{
DateTime FirstTime;
public static DeltaTimer CreatePoint()
{
return new DeltaTime(){ FirstTime = DateTime.Now};
}
public TimeSpan GetDeltaTime()
{
if (FirstTime != null)
{
return DateTime.Now - FirstTime;
}
return TimeSpan.FromSeconds(1/60); //If null then return 60 FPS.
}
}
Example 1:
public async void TEST1_Animation(Button button)
{
var pointer= DeltaTime.CreatePoint();
for(double h = 0; h<button.Height;h++)
{
var n= pointer.GetDeltaTime().TotalSeconds;
h = h * n;
await button.Dispatcher.InvokeAsync(() => { button.Height= h; });
await Task.Delay(TimeSpan.FromSeconds(1 / 60 * n));
}
}
And your code will look like this:
static void Update()
{
var Pointer = DeltaTimer.CreatePoint();
while (true)
{
pSim.Update(Pointer.GetDeltaTime().TotalMilliseconds);
}
}