I made a small program that plays back sounds when you press keys.
It uses a global keyboard hook to capture key presses and play back wav files using NAudio.
However playback lags on some computers and plays a few seconds after the key has been pressed. Could this be an HDD/SSD or CPU speed issue or is it a programming issue? What can be done to solve it?
Tried on 4 computers, 2 lagged, 2 did not.
My SSD/i7 - did not lag.
My HDD/Core2Duo - did not lag.
Friend's SSD/i7 - lagged.
Friend's HDD/i7 - lagged.
Program
Info
https://github.com/MattMcManis/Ink
Source
https://github.com/MattMcManis/Ink/tree/master/source/Ink
Download
https://github.com/MattMcManis/Ink/releases
App.xaml.cs
Start the Keyboard Listener.
// Application Startup
//
private void Application_Startup(object sender, StartupEventArgs e)
{
th = new Thread(() => RunKeyListener());
th.IsBackground = true;
th.Start();
th.Join();
}
// Keyboard Listener
//
private void RunKeyListener()
{
KListener.KeyDown += new RawKeyEventHandler(KListener_KeyDown);
}
// Key Down
//
void KListener_KeyDown(object sender, RawKeyEventArgs args)
{
Sound.KeyPressed(args);
}
MainWindow.xaml.cs
KeyboardListener Class is in here.
https://gist.github.com/Ciantic/471698
Sound.cs
private static string wavKeyChar = "Sounds\\character.wav";
private static string wavKeyNum = "Sounds\\number.wav";
public static WaveFileReader wav = null;
public static WaveOutEvent output = null;
// Key Pressed
//
public static void KeyPressed(RawKeyEventArgs args)
{
// Letters
if (args.Key >= Key.A && args.Key <= Key.Z)
{
PlaySound(wavKeyChar);
}
// Numbers
else if (args.Key >= Key.D0 && args.Key <= Key.D9)
{
PlaySound(wavKeyNum);
}
}
// Play Sound
//
public static void PlaySound(string sound)
{
wav = new WaveFileReader(sound);
output = new WaveOutEvent();
output.NumberOfBuffers = 3;
output.DesiredLatency = 500;
output.Init(wav);
output.Play();
}
Try to show a MessageBox or something, to understand if the delayed event is the sound itself or the keypress event.
If the MessageBox shows before the sound is played then it's not a problem of keyboard hook library.
Related
I'm currently working on a project that involve a stepper motor using c# forms and arduino.
I need to know in c# when the motor stops his movement. To do that, I programmed arduino to send a string trough Serial when the movement is finished. Then the string is interpreted by c# so that the app can do other things after that.
I now want to have the same result as the example below, but using async await.
I stored the information that the motor stopped in:
movimentFinished = true;
I solved the problem in a terrible way using: while(movimentFinished = false){await Task.Delay(1)}
The difference between a while loop and an await is going to effect other stuff on my WinApp. The while blocks all the application until movimentFinished == true;
The logic that I want is simple:
MoveMotor();
await "value change"
//do stuff after it finishes
Any help/tip to do that?
Thank you for reading
//this funcion will receive an array from arduino as a reply
private void risposta(object sender, SerialDataReceivedEventArgs e)
{
riceved = port.ReadLine();
rispos = riceved;
this.Invoke(new EventHandler(Reply));
}
//this function will run every time a reply is sent from arduino through serial
private void Reply(object sender, EventArgs e)
{
//string risposValore = rispos.Substring(2, rispos.Length - 2);
char[] pcr = rispos.ToCharArray();
rispos = rispos.Trim();
if (pcr[0] == 'M' && pcr[1] == 'F' && pcr[2] == '1')
{
movementFinished = true;
}
}
//this funcion will send a serial command to arduino to run the motor
public void MoveTheMotor(int position)
{
int command = 1000;
int movement = command + (position)
string com1 = movement.ToString();
port.Write(com1 + "\n");
movementFinished = false;
}
private async void Demo()
{
MoveTheMotor(800);
//this while blocks everything until the value is true
while (movementFinished == false) { await Task.Delay(1); }
//do something after the motor finished
}
Instead of using a boolean, you need a signal. In the blocking world, this would be a ManualResetEventSlim or similar. In the asynchronous world, this would be some form of TaskCompletionSource. As such:
private TaskCompletionSource movementFinished;
private void Reply(object sender, EventArgs e)
{
...
if (pcr[0] == 'M' && pcr[1] == 'F' && pcr[2] == '1')
{
movementFinished.TrySetResult();
}
}
public void MoveTheMotor(int position)
{
...
movementFinished = new TaskCompletionSource();
}
private async void Demo()
{
MoveTheMotor(800);
await movementFinished.Task;
//do something after the motor finished
}
A digital acoustic sensor is connected to my raspberry.
Everytime it detects sound, it sets the input HIGH.
But it only appears to be active for a couple of milliseconds.
How can i prolong the signal within my program that it stays up for 500ms?
It is a functionality which I know from PLC contollers.
Diagram
Start: sensor input
Q: prolonged signal
,
Here my approach with the answer of Michael:
But still, it doesn't hold for Task.Delay. It goes off directly.
public MainPage()
{
this.InitializeComponent();
GPIOinit();
Serial();
}
private void GPIOinit()
{
const int PIN_AKUSTIK = 19;
GpioController gpio = GpioController.GetDefault(); //GPIO-Controller mit Default belegen
pin_akustik = gpio.OpenPin(PIN_AKUSTIK);
}
public async void Serial()
{
//serial configuration
while (true)
{
//frequency of constant while loop 300ms
Sensor_Akustik();
}
}
public void Sensor_Akustik()
{
pin_akustik.ValueChanged += Pin_ValueChanged;
pin_akustik.SetDriveMode(GpioPinDriveMode.Input);
status_akustik = pin_akustik.Read().ToString();
textblock_DebugAkustik_live.Text = status_akustik;
Messenger.Default.Send<string, Page_Akustik>(status_akustik);
}
private void Pin_ValueChanged(GpioPin sender, GpioPinValueChangedEventArgs args)
{
if (args.Edge == GpioPinEdge.RisingEdge)
{
sender.Write(GpioPinValue.High);
Task.Delay(3000).Wait();
}
}
You might refer to following code. You can try to update the latched output value for the pin if the pin is configured as an input.
private void InitGPIO()
{
var gpio = GpioController.GetDefault();
// Show an error if there is no GPIO controller
if (gpio == null)
{
pin = null;
GpioStatus.Text = "There is no GPIO controller on this device.";
return;
}
pin = gpio.OpenPin(LED_PIN);
pin.ValueChanged += Pin_ValueChanged;
pin.SetDriveMode(GpioPinDriveMode.Input);
}
private void Pin_ValueChanged(GpioPin sender, GpioPinValueChangedEventArgs args)
{
if(args.Edge == GpioPinEdge.RisingEdge)
{
sender.Write(GpioPinValue.High);
Task.Delay(500).Wait();
}
}
I'm currently writing an app in c#, where this app will play background music once it started. And the button clicking sound and music shall stop when I press the mute button, or play again when unmuted.
namespace WindowsFormsApp1
{
public partial class mainMenu : Form
{
public bool sound = true; //variable of whether the sound is on or not
System.Media.SoundPlayer bgm = new System.Media.SoundPlayer(#"C:\Users\User\Desktop\HCI\New folder\bgm.wav");
public bool soundval //for other form to access sound variable value
{
get
{
return sound;
}
set
{
sound = value;
}
}
public mainMenu()
{
InitializeComponent();
play();
}
public void play() //function to play music
{
bgm.PlayLooping();
}
public void stop//function to stop music
{
bgm.Stop();
}
private void soundButton_Click(object sender, EventArgs e) //button that mute or unmute the sound (by changing sound value)
{
if (sound)
{
bgm.Stop();
sound = false;
}
else
{
var p2 = new System.Windows.Media.MediaPlayer();
p2.Open(new System.Uri(#"C:\Users\User\Desktop\HCI\New folder\button.wav"));
p2.Play();
play();
sound = true;
}
}
private void easy_Click(object sender, EventArgs e) //proceed to other form
{
if (sound)
{
var p2 = new System.Windows.Media.MediaPlayer();
p2.Open(new System.Uri(#"C:\Users\User\Desktop\HCI\New folder\button.wav"));
p2.Play();
}
Easy game2 = new Easy();
game2.Tag = this;
game2.Show(this);
Hide();
}
}
}
Until here, the app works fine. But then if I muted the sound and proceed to other form, the music will play again, the button sound will also play if I press anything on any button on that particular form.
Partial code of my other form(easy_Click):
mainMenu mmenu = new mainMenu();
private void thirdchoice_Click(object sender, EventArgs e)
{
if (mmenu.soundval) //to get the value from first form, button sound should play only when value is true
{
var p2 = new System.Windows.Media.MediaPlayer();
p2.Open(new System.Uri(#"C:\Users\User\Desktop\HCI\New folder\button.wav"));
p2.Play();
}
if (ansNum == choice3num) goodnews();
else badnews();
}
Not only music, but the button clicking sound is playing as well. It looks like the sound value set to true when proceed to second form, even though it was false in first form.
I been stuck on this issue for a long time, changing here and there, but nothing work.
Really appreciate if someone could help on this. Thanks!
I just finished an exercise from Head First C# where I built a Typing Game. The book leaves it to the reader to figure out how to make it so the player can start a new game once they've lost. After the user loses the game, the window shows the message "Game Over". I would like to have a new window pop up and ask the user if they would like to play again once they've closed out of the game over screen. I'd like there to be two buttons; one that says "no" and one that says "yes". What I'm stuck on is how I should (or would) go about restarting the app if the user decides they want to play again. I'll copy and paste my code below:
namespace _7HeadFirstProject
{
public partial class Form1 : Form
{
Random random = new Random();
Stats stats = new Stats();
public Form1()
{
InitializeComponent();
}
private void timer1_Tick(object sender, EventArgs e)
{
// Add a random key to the ListBox
listBox1.Items.Add((Keys)random.Next(65, 90));
if (listBox1.Items.Count > 7)
{
listBox1.Items.Clear();
listBox1.Items.Add("Game Over");
timer1.Stop();
}
}
private void Form1_KeyDown(object sender, KeyEventArgs e)
{
// If the user pressed a key that's in the ListBox...
// ... remove it and then make the game a little faster
if (listBox1.Items.Contains(e.KeyCode))
{
listBox1.Items.Remove(e.KeyCode);
listBox1.Refresh();
if (timer1.Interval > 400)
timer1.Interval -= 10;
if (timer1.Interval > 250)
timer1.Interval -= 7;
if (timer1.Interval > 100)
timer1.Interval -= 2;
difficultyProgressBar.Value = 800 - timer1.Interval;
// The user pressed a correct key, so update the Stats object...
// ...by calling its Update() method with the argument true
stats.Update(true);
}
else
{
// The user pressed an incorrect key, so update the Stats object...
// ...by calling its Update() method with the argument false
stats.Update(false);
}
// Update the labels on the StatusStrip
correctLabel.Text = "Correct: " + stats.Correct;
missedLabel.Text = "Missed: " + stats.Missed;
totalLabel.Text = "Total: " + stats.Total;
accuracyLabel.Text = "Accuracy: " + stats.Accuracy + "%";
}
private void Form1_FormClosed(object sender, FormClosedEventArgs e)
{
MessageBox.Show("Would you like to play again?");
if
}
}
}
DIFFERENT CLASS:
namespace _7HeadFirstProject
{
class Stats
{
public int Total = 0;
public int Missed = 0;
public int Correct = 0;
public int Accuracy = 0;
public void Update(bool correctKey)
{
Total++;
if (!correctKey)
{
Missed++;
}
else
{
Correct++;
}
Accuracy = 100 * Correct / Total;
}
}
}
You have the whole game working so leave that form alone. Add another form to your project and then set the new form as the startup form. You can set it as the startup form by opening Program.cs and modifying this line:
// Instead of Form1 put the name of your new form
Application.Run(new Form1());
Double click the new form and put this code in it:
// Note: Your load method may have a different name.
private void Form2_Load(object sender, EventArgs e)
{
this.StartNewGame();
}
private void GameForm_FormClosed(object sender, FormClosedEventArgs e)
{
if (MessageBox.Show("Continue?", "Continue?", MessageBoxButtons.YesNo) == DialogResult.Yes)
{
this.StartNewGame();
}
}
private void StartNewGame()
{
// Your game form may have a different name so change this to that name
var gameForm = new Form2();
gameForm.FormClosed += GameForm_FormClosed;
gameForm.Show();
}
Every time the user presses the yes button on the dialog, you are creating a brand new instance of the form (of the game). In this new form, you can also have an array which keeps track of the total number of games and what the score of each game was so you can show it in case the user selected No. All you need is something like this:
var games = new List<Stats>();
// keep adding to it every time you call StartNewGame() method.
Try this:
if ((MessageBox.Show("Would you like to play again?", "Message", MessageBoxButtons.YesNo)) ==
DialogResult.Yes)
{
Application.Restart();
}
I am trying to write an interface for a motorised stage. What I am trying to do is to create a scan feature such that the motor will move a certain distance, stop and wait a specified time and then move the same distance again. It will repeat the process until it has reached the total length specified by the user. To do this I am trying to use a Timer class features as I still want the GUI to be active during the scan.
I've got some idea of how to code it but get stuck. It would go something like:
private void btnGo_Click(object sender, EventArgs e) //On click
{
int i = 0;
int stop = 15; //number of times I want the motor to stop
System.Timers.Timer bTimer; //initialise timer
bTimer = new System.Timers.Timer(waittime); //time I want the motor to wait
bTimer.Elapsed += PerformMove;
bTimer.Enabled = true;
if(i==stop){bTimer.stop()}
}
private void PerformMove(Object source, ElapsedEventArgs e) //event to move motor
{
//movemotor
i++;
}
Not being particularly familiar with C# or timers is undoubtedly the cause of my confusion. What's the best way to approach this problem? Any example code would be great.
If somebody could clarify what the lines
bTimer.Elapsed += PerformMove;
bTimer.Enabled = true;
actually do too that would also be of great use!
EDIT (sorry, didn't think this was a key part): The value of stop is defined upon the user click of the button from a text box within the GUI. i.e.
int stop = Convert.ToDouble(tbIntervalStops.Text); //grab integer from user input upon button click
This would be the correct solution without memory leak
private int i = 0;
private int stop = 15; //number of times I want the motor to stop
private Timer bTimer; //initialise timer -> Thats wrong: nothing is INITIALIZED here its just defined
private void btnGo_Click(object sender, EventArgs e) //On click
{
i = 0;
stop = Convert.ToInt32(tbIntervalStops.Text); //using int because double is a floating point number like 12.34 and for counting only full numbers will be needed
bTimer = new System.Timers.Timer(waittime); //time I want the motor to wait + Here the Timer is INITIALIZED
bTimer.Elapsed += PerformMove; //Adds the Eventhandler, What should be called when the time is over
bTimer.Enabled = true; //This starts the timer. It enables running like pressing the start button on a stopwatch
}
private void PerformMove(Object source, ElapsedEventArgs e) //event to move motor
{
//movemotor
i++;
if (i == stop) //if stop would be a double here we will have the danger to get not a true because of rounding problems
{
bTimer.Stop();
//now enable the Garbage Collector to remove the Timer instance
bTimer.Elapsed -= PerformMove; //This removes the Eventhandler
bTimer.Dispose(); //This frees all resources held by the Timer instance.
bTimer = null;
}
}
Alternatively, you could also derive from the System.Timers.Timer object and create a wrapper which has properties specific to the task. In which case, you would simply need to instantiate a MoveTimer and subscribe to it's OnPerformMoveEvent.
Update: Added OnMovesCompletedEvent
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TimerExample
{
public class MoveTimer : System.Timers.Timer
{
public event EventHandler OnPerformMoveEvent = delegate { };
public event EventHandler OnMovesCompletedEvent = delegate { };
public MoveTimer()
{
Initialize(new TimeSpan(), 0);
}
public MoveTimer(TimeSpan wait, int moves)
{
this.Initialize(wait, moves);
}
private int _i;
private int _totalmoves;
public int Moves
{
get { return this._totalmoves; }
set { this._totalmoves = value; }
}
private TimeSpan _wait;
public TimeSpan Wait
{
get { return this._wait; }
set { this._wait = value; }
}
private System.Timers.Timer _timer;
private void Initialize(TimeSpan wait, int moves)
{
this._totalmoves = moves;
this._wait = wait;
this._timer = new System.Timers.Timer(wait.Milliseconds);
}
private void BindComponents()
{
this._timer.Elapsed += _timer_Elapsed;
}
private void UnBindComponents()
{
this._timer.Elapsed -= _timer_Elapsed;
}
public void StartTimer()
{
this._timer.Enabled = true;
}
public void StopTimer()
{
this._timer.Enabled = false;
}
void _timer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
this._i++;
if (this.OnPerformMoveEvent != null)
this.OnPerformMoveEvent(this, EventArgs.Empty);
if (this._i == this._totalmoves)
{
this._timer.Stop();
this.UnBindComponents();
this.Dispose();
if (this.OnMovesCompletedEvent != null)
this.OnMovesCompletedEvent(this, EventArgs.Empty);
}
}
}
}
In regards to the user input where the number of moves or stops is provided as a string. I would handle this outside of the MoveTimer object. Validation should always be performed.
First determine that the value can be parsed into an integer. If not, throw an exception to let the user know that the input was entered incorrectly.
To use the above, something like the following would be all it requires:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace TimerExample
{
class Program
{
static void Main(string[] args)
{
//Create move timer that will trigger 15 times, once every 30 seconds.
MoveTimer moveTimer = new MoveTimer(new TimeSpan(0, 0, 30), 15);
//Substribe to the move timer events
moveTimer.OnPerformMoveEvent += moveTimer_OnPerformMoveEvent;
moveTimer.OnMovesCompletedEvent += moveTimer_OnMovesCompletedEvent;
//Start the timer
moveTimer.StartTimer();
//What happens in between the moves performed?
}
static void moveTimer_OnMovesCompletedEvent(object sender, EventArgs e)
{
//All the moves have been performed, what would you like to happen? Eg. Beep or tell the user.
}
static void moveTimer_OnPerformMoveEvent(object sender, EventArgs e)
{
//Timer has lapsed, what needs to be done when a move is requested?
}
}
}